Skip to content

Commit e80da7b

Browse files
committed
Always give known-empty class properties a zero offset in the static layout.
Field offset vectors are always filled out with either zero or the static layout's offset, depending on the metadata initialization strategy. This change means that the static layout's offset will only be non-zero for properties with a statically-known layout. Existing runtimes doing dynamic class layout assign class properties a zero offset if the field offset vector entry is zero and the property is zero-sized. So this effectively brings the compiler into accord with the runtime (for all newly-compiled Swift code, which will eventually be all Swift code because the current public releases of Swift 5 are not yet considered ABI-stable) and guarantees a zero value for the offset everywhere. Since the runtime will agree with the compiler about the zero value of the offset, the compiler can continue to emit such offset variables as constant. The exception to this rule is if the class has non-fragile ObjC ancestry, in which case the ObjC runtime (which is not aware of this special rule for empty fields) will attempt to slide it along with everything else. Fixes rdar://48031465, in which the `FixedClassMetadataBuilder` for a class with a legacy-fixed layout was writing a non-zero offset for an empty field into the field offset vector, causing the runtime to not apply the special case and thus to compute a non-zero offset, which it then attempted to copy into the global field offset variable, which the compiler had emitted as a true-constant zero.
1 parent c94688b commit e80da7b

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,11 +1,18 @@
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
// XFAIL: CPU=powerpc64le
57

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

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

0 commit comments

Comments
 (0)