Skip to content

Commit 5ff6d51

Browse files
authored
Merge pull request #22739 from rjmccall/empty-field-offset-variables-5.0
[5.0] Always give known-empty class properties a zero offset in the static layout
2 parents 1745316 + f9d1129 commit 5ff6d51

12 files changed

+241
-36
lines changed

lib/IRGen/ClassLayout.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ ClassLayout::ClassLayout(const StructLayoutBuilder &builder,
2828
bool isFixedSize,
2929
bool metadataRequiresInitialization,
3030
bool metadataRequiresRelocation,
31+
bool classHasObjCAncestry,
3132
llvm::Type *classTy,
3233
ArrayRef<VarDecl *> allStoredProps,
3334
ArrayRef<FieldAccess> allFieldAccesses,
@@ -38,21 +39,29 @@ ClassLayout::ClassLayout(const StructLayoutBuilder &builder,
3839
IsFixedSize(isFixedSize),
3940
MetadataRequiresInitialization(metadataRequiresInitialization),
4041
MetadataRequiresRelocation(metadataRequiresRelocation),
42+
ClassHasObjCAncestry(classHasObjCAncestry),
4143
Ty(classTy),
4244
AllStoredProperties(allStoredProps),
4345
AllFieldAccesses(allFieldAccesses),
4446
AllElements(allElements) { }
4547

4648
Size ClassLayout::getInstanceStart() const {
47-
if (AllElements.empty())
48-
return getSize();
49+
ArrayRef<ElementLayout> elements = AllElements;
50+
while (!elements.empty()) {
51+
auto element = elements.front();
52+
elements = elements.drop_front();
4953

50-
auto element = AllElements[0];
51-
if (element.getKind() == ElementLayout::Kind::Fixed ||
52-
element.getKind() == ElementLayout::Kind::Empty) {
53-
// FIXME: assumes layout is always sequential!
54-
return element.getByteOffset();
54+
// Ignore empty elements.
55+
if (element.isEmpty()) {
56+
continue;
57+
} else if (element.hasByteOffset()) {
58+
// FIXME: assumes layout is always sequential!
59+
return element.getByteOffset();
60+
} else {
61+
return Size(0);
62+
}
5563
}
5664

57-
return Size(0);
65+
// If there are no non-empty elements, just return the computed size.
66+
return getSize();
5867
}

lib/IRGen/ClassLayout.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class ClassLayout {
6363
/// Does the class metadata require relocation?
6464
bool MetadataRequiresRelocation;
6565

66+
/// Does the class have ObjC ancestry?
67+
bool ClassHasObjCAncestry;
68+
6669
/// The LLVM type for instances of this class.
6770
llvm::Type *Ty;
6871

@@ -82,6 +85,7 @@ class ClassLayout {
8285
bool isFixedSize,
8386
bool metadataRequiresInitialization,
8487
bool metadataRequiresRelocation,
88+
bool classHasObjCAncestry,
8589
llvm::Type *classTy,
8690
ArrayRef<VarDecl *> allStoredProps,
8791
ArrayRef<FieldAccess> allFieldAccesses,
@@ -98,6 +102,15 @@ class ClassLayout {
98102

99103
bool isFixedSize() const { return IsFixedSize; }
100104

105+
/// Returns true if the runtime may attempt to assign non-zero offsets to
106+
/// empty fields for this class. The ObjC runtime will do this if it
107+
/// decides it needs to slide ivars. This is the one exception to the
108+
/// general rule that the runtime will not try to assign a different offset
109+
/// than was computed statically for a field with a fixed offset.
110+
bool mayRuntimeAssignNonZeroOffsetsToEmptyFields() const {
111+
return ClassHasObjCAncestry;
112+
}
113+
101114
bool doesMetadataRequireInitialization() const {
102115
return MetadataRequiresInitialization;
103116
}

lib/IRGen/GenClass.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ namespace {
293293
isFixedSize(),
294294
doesMetadataRequireInitialization(),
295295
doesMetadataRequireRelocation(),
296+
ClassHasObjCAncestry,
296297
classTy,
297298
allStoredProps,
298299
allFieldAccesses,
@@ -393,7 +394,7 @@ namespace {
393394
}
394395

395396
auto element = ElementLayout::getIncomplete(*eltType);
396-
addField(element, LayoutStrategy::Universal);
397+
bool isKnownEmpty = !addField(element, LayoutStrategy::Universal);
397398

398399
// The 'Elements' list only contains superclass fields when we're
399400
// building a layout for tail allocation.
@@ -402,7 +403,7 @@ namespace {
402403

403404
if (!superclass) {
404405
AllStoredProperties.push_back(var);
405-
AllFieldAccesses.push_back(getFieldAccess());
406+
AllFieldAccesses.push_back(getFieldAccess(isKnownEmpty));
406407
}
407408
}
408409

@@ -459,7 +460,12 @@ namespace {
459460
}
460461
}
461462

462-
FieldAccess getFieldAccess() {
463+
FieldAccess getFieldAccess(bool isKnownEmpty) {
464+
// If the field is known empty, then its access pattern is always
465+
// constant-direct.
466+
if (isKnownEmpty)
467+
return FieldAccess::ConstantDirect;
468+
463469
// If the layout so far has a fixed size, the field offset is known
464470
// statically.
465471
if (isFixedSize())
@@ -635,8 +641,7 @@ irgen::getClassFieldOffset(IRGenModule &IGM, SILType baseType, VarDecl *field) {
635641

636642
auto fieldInfo = classLayout.getFieldAccessAndElement(field);
637643
auto element = fieldInfo.second;
638-
assert(element.getKind() == ElementLayout::Kind::Fixed ||
639-
element.getKind() == ElementLayout::Kind::Empty);
644+
assert(element.hasByteOffset());
640645
return element.getByteOffset();
641646
}
642647

lib/IRGen/GenMeta.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2318,7 +2318,7 @@ static void emitFieldOffsetGlobals(IRGenModule &IGM,
23182318

23192319
llvm::Constant *fieldOffsetOrZero;
23202320

2321-
if (element.getKind() == ElementLayout::Kind::Fixed) {
2321+
if (element.hasByteOffset()) {
23222322
// Use a fixed offset if we have one.
23232323
fieldOffsetOrZero = IGM.getSize(element.getByteOffset());
23242324
} else {
@@ -2346,11 +2346,21 @@ static void emitFieldOffsetGlobals(IRGenModule &IGM,
23462346
// If it is constant in the fragile layout only, newer Objective-C
23472347
// runtimes will still update them in place, so make sure to check the
23482348
// correct layout.
2349+
//
2350+
// The one exception to this rule is with empty fields with
2351+
// ObjC-resilient heritage. The ObjC runtime will attempt to slide
2352+
// these offsets if it slides the rest of the class, and in doing so
2353+
// it will compute a different offset than we computed statically.
2354+
// But this is ultimately unimportant because we do not care about the
2355+
// offset of an empty field.
23492356
auto resilientInfo = resilientLayout.getFieldAccessAndElement(prop);
2350-
if (resilientInfo.first == FieldAccess::ConstantDirect) {
2357+
if (resilientInfo.first == FieldAccess::ConstantDirect &&
2358+
(!resilientInfo.second.isEmpty() ||
2359+
!resilientLayout.mayRuntimeAssignNonZeroOffsetsToEmptyFields())) {
23512360
// If it is constant in the resilient layout, it should be constant in
23522361
// the fragile layout also.
23532362
assert(access == FieldAccess::ConstantDirect);
2363+
assert(element.hasByteOffset());
23542364
offsetVar->setConstant(true);
23552365
}
23562366

lib/IRGen/GenRecord.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ template <class FieldImpl> class RecordField {
8282
ElementLayout::Kind getKind() const {
8383
return Layout.getKind();
8484
}
85+
86+
bool hasFixedByteOffset() const {
87+
return Layout.hasByteOffset();
88+
}
8589

8690
Size getFixedByteOffset() const {
8791
return Layout.getByteOffset();
@@ -763,7 +767,7 @@ class RecordTypeInfo<Impl, Base, FieldImpl,
763767
Explosion &src,
764768
unsigned startOffset) const override {
765769
for (auto &field : getFields()) {
766-
if (field.getKind() != ElementLayout::Kind::Empty) {
770+
if (!field.isEmpty()) {
767771
unsigned offset = field.getFixedByteOffset().getValueInBits()
768772
+ startOffset;
769773
cast<LoadableTypeInfo>(field.getTypeInfo())
@@ -776,7 +780,7 @@ class RecordTypeInfo<Impl, Base, FieldImpl,
776780
Explosion &dest, unsigned startOffset)
777781
const override {
778782
for (auto &field : getFields()) {
779-
if (field.getKind() != ElementLayout::Kind::Empty) {
783+
if (!field.isEmpty()) {
780784
unsigned offset = field.getFixedByteOffset().getValueInBits()
781785
+ startOffset;
782786
cast<LoadableTypeInfo>(field.getTypeInfo())

lib/IRGen/GenStruct.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,7 @@ namespace {
172172
llvm::Constant *getConstantFieldOffset(IRGenModule &IGM,
173173
VarDecl *field) const {
174174
auto &fieldInfo = getFieldInfo(field);
175-
if (fieldInfo.getKind() == ElementLayout::Kind::Fixed
176-
|| fieldInfo.getKind() == ElementLayout::Kind::Empty) {
175+
if (fieldInfo.hasFixedByteOffset()) {
177176
return llvm::ConstantInt::get(
178177
IGM.Int32Ty, fieldInfo.getFixedByteOffset().getValue());
179178
}
@@ -791,8 +790,7 @@ class ClangRecordLowering {
791790
ElementLayout layout = ElementLayout::getIncomplete(fieldType);
792791
auto isEmpty = fieldType.isKnownEmpty(ResilienceExpansion::Maximal);
793792
if (isEmpty)
794-
layout.completeEmpty(fieldType.isPOD(ResilienceExpansion::Maximal),
795-
NextOffset);
793+
layout.completeEmpty(fieldType.isPOD(ResilienceExpansion::Maximal));
796794
else
797795
layout.completeFixed(fieldType.isPOD(ResilienceExpansion::Maximal),
798796
NextOffset, LLVMFields.size());

lib/IRGen/StructLayout.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,7 @@ void StructLayoutBuilder::addNonFixedSizeElement(ElementLayout &elt) {
304304

305305
/// Add an empty element to the aggregate.
306306
void StructLayoutBuilder::addEmptyElement(ElementLayout &elt) {
307-
elt.completeEmpty(elt.getType().isPOD(ResilienceExpansion::Maximal),
308-
CurSize);
307+
elt.completeEmpty(elt.getType().isPOD(ResilienceExpansion::Maximal));
309308
}
310309

311310
/// Add an element at the fixed offset of the current end of the

lib/IRGen/StructLayout.h

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class ElementLayout {
8181
public:
8282
enum class Kind {
8383
/// The element is known to require no storage in the aggregate.
84+
/// Its offset in the aggregate is always statically zero.
8485
Empty,
8586

8687
/// The element can be positioned at a fixed offset within the
@@ -95,10 +96,12 @@ class ElementLayout {
9596
/// offset zero. This is necessary because LLVM forbids even a
9697
/// 'gep 0' on an unsized type.
9798
InitialNonFixedSize
99+
100+
// IncompleteKind comes here
98101
};
99102

100103
private:
101-
enum : unsigned { IncompleteKind = 4 };
104+
enum : unsigned { IncompleteKind = unsigned(Kind::InitialNonFixedSize) + 1 };
102105

103106
/// The swift type information for this element's layout.
104107
const TypeInfo *Type;
@@ -137,19 +140,17 @@ class ElementLayout {
137140
Index = other.Index;
138141
}
139142

140-
void completeEmpty(IsPOD_t isPOD, Size byteOffset) {
143+
void completeEmpty(IsPOD_t isPOD) {
141144
TheKind = unsigned(Kind::Empty);
142145
IsPOD = unsigned(isPOD);
143-
// We still want to give empty fields an offset for use by things like
144-
// ObjC ivar emission. We use the first field in a class layout as the
145-
// instanceStart.
146-
ByteOffset = byteOffset.getValue();
146+
ByteOffset = 0;
147147
Index = 0; // make a complete write of the bitfield
148148
}
149149

150150
void completeInitialNonFixedSize(IsPOD_t isPOD) {
151151
TheKind = unsigned(Kind::InitialNonFixedSize);
152152
IsPOD = unsigned(isPOD);
153+
ByteOffset = 0;
153154
Index = 0; // make a complete write of the bitfield
154155
}
155156

@@ -189,10 +190,25 @@ class ElementLayout {
189190
return IsPOD_t(IsPOD);
190191
}
191192

193+
/// Can we access this element at a static offset?
194+
bool hasByteOffset() const {
195+
switch (getKind()) {
196+
case Kind::Empty:
197+
case Kind::Fixed:
198+
return true;
199+
200+
// FIXME: InitialNonFixedSize should go in the above, but I'm being
201+
// paranoid about changing behavior.
202+
case Kind::InitialNonFixedSize:
203+
case Kind::NonFixed:
204+
return false;
205+
}
206+
llvm_unreachable("bad kind");
207+
}
208+
192209
/// Given that this element has a fixed offset, return that offset in bytes.
193210
Size getByteOffset() const {
194-
assert(isCompleted() &&
195-
(getKind() == Kind::Fixed || getKind() == Kind::Empty));
211+
assert(isCompleted() && hasByteOffset());
196212
return Size(ByteOffset);
197213
}
198214

test/IRGen/class_resilience.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
// CHECK: @"$s16class_resilience21ResilientGenericChildCMo" = {{(protected )?}}{{(dllexport )?}}global [[BOUNDS:{ (i32|i64), i32, i32 }]] zeroinitializer
1818

19+
// CHECK: @"$s16class_resilience27ClassWithEmptyThenResilientC9resilient0H7_struct0G3IntVvpWvd" = hidden global [[INT]] 0,
20+
// CHECK: @"$s16class_resilience27ClassWithResilientThenEmptyC9resilient0H7_struct0E3IntVvpWvd" = hidden global [[INT]] 0,
21+
1922
// CHECK: @"$s16class_resilience26ClassWithResilientPropertyCMo" = {{(protected )?}}{{(dllexport )?}}constant [[BOUNDS]]
2023
// CHECK-SAME-32: { [[INT]] 52, i32 2, i32 13 }
2124
// CHECK-SAME-64: { [[INT]] 80, i32 2, i32 10 }
@@ -113,6 +116,9 @@
113116
// CHECK-SAME-32: { [[INT]] 64, i32 2, i32 16 }
114117
// CHECK-SAME-64: { [[INT]] 104, i32 2, i32 13 }
115118

119+
// CHECK: @"$s16class_resilience27ClassWithEmptyThenResilientC5emptyAA0E0VvpWvd" = hidden constant [[INT]] 0,
120+
// CHECK: @"$s16class_resilience27ClassWithResilientThenEmptyC5emptyAA0G0VvpWvd" = hidden constant [[INT]] 0,
121+
116122
// CHECK: @"$s16class_resilience14ResilientChildC5fields5Int32VvgTq" = {{(protected )?}}{{(dllexport )?}}alias %swift.method_descriptor, getelementptr inbounds
117123
// CHECK: @"$s16class_resilience14ResilientChildC5fields5Int32VvsTq" = {{(protected )?}}{{(dllexport )?}}alias %swift.method_descriptor, getelementptr inbounds
118124
// CHECK: @"$s16class_resilience14ResilientChildC5fields5Int32VvMTq" = {{(protected )?}}{{(dllexport )?}}alias %swift.method_descriptor, getelementptr inbounds
@@ -244,6 +250,34 @@ extension ResilientGenericOutsideParent {
244250
}
245251
}
246252

253+
// rdar://48031465
254+
// Field offsets for empty fields in resilient classes should be initialized
255+
// to their best-known value and made non-constant if that value might
256+
// disagree with the dynamic value.
257+
258+
@_fixed_layout
259+
public struct Empty {}
260+
261+
public class ClassWithEmptyThenResilient {
262+
public let empty: Empty
263+
public let resilient: ResilientInt
264+
265+
public init(empty: Empty, resilient: ResilientInt) {
266+
self.empty = empty
267+
self.resilient = resilient
268+
}
269+
}
270+
271+
public class ClassWithResilientThenEmpty {
272+
public let resilient: ResilientInt
273+
public let empty: Empty
274+
275+
public init(empty: Empty, resilient: ResilientInt) {
276+
self.empty = empty
277+
self.resilient = resilient
278+
}
279+
}
280+
247281
// ClassWithResilientProperty.color getter
248282

249283
// CHECK-LABEL: define{{( dllexport)?}}{{( protected)?}} swiftcc i32 @"$s16class_resilience26ClassWithResilientPropertyC5colors5Int32Vvg"(%T16class_resilience26ClassWithResilientPropertyC* swiftself)

test/IRGen/class_resilience_objc.swift

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-objc-interop -emit-ir -o - -primary-file %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -enable-resilience -enable-class-resilience -emit-module-path=%t/resilient_struct.swiftmodule -module-name=resilient_struct %S/../Inputs/resilient_struct.swift
3+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -I %t -enable-resilience -enable-class-resilience -enable-objc-interop -emit-ir -o - -primary-file %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize -DINT=i%target-ptrsize
24

35
// XFAIL: CPU=armv7k
46

5-
// CHECK: %swift.type = type { [[INT:i32|i64]] }
6-
77
import Foundation
8+
import resilient_struct
9+
10+
// Note that these are all mutable to allow for the runtime to slide them.
11+
// CHECK: @"$s21class_resilience_objc27ClassWithEmptyThenResilientC9resilient0I7_struct0H3IntVvpWvd" = hidden global [[INT]] 0,
12+
// CHECK: @"$s21class_resilience_objc27ClassWithResilientThenEmptyC9resilient0I7_struct0F3IntVvpWvd" = hidden global [[INT]] 0,
13+
// CHECK: @"$s21class_resilience_objc27ClassWithEmptyThenResilientC5emptyAA0F0VvpWvd" = hidden global [[INT]] 0,
14+
// CHECK: @"$s21class_resilience_objc27ClassWithResilientThenEmptyC5emptyAA0H0VvpWvd" = hidden global [[INT]] 0,
815

916
public class FixedLayoutObjCSubclass : NSObject {
1017
// This field could use constant direct access because NSObject has
@@ -89,3 +96,26 @@ func testConstantIndirectFieldAccess<T>(_ o: GenericObjCSubclass<T>) {
8996
// because the field offset vector only contains Swift field offsets.
9097
o.field = 10
9198
}
99+
100+
@_fixed_layout
101+
public struct Empty {}
102+
103+
public class ClassWithEmptyThenResilient : DummyClass {
104+
public let empty: Empty
105+
public let resilient: ResilientInt
106+
107+
public init(empty: Empty, resilient: ResilientInt) {
108+
self.empty = empty
109+
self.resilient = resilient
110+
}
111+
}
112+
113+
public class ClassWithResilientThenEmpty : DummyClass {
114+
public let resilient: ResilientInt
115+
public let empty: Empty
116+
117+
public init(empty: Empty, resilient: ResilientInt) {
118+
self.empty = empty
119+
self.resilient = resilient
120+
}
121+
}

0 commit comments

Comments
 (0)