Skip to content

Commit 7f55a4a

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 69ff99f commit 7f55a4a

12 files changed

+234
-36
lines changed

lib/IRGen/ClassLayout.cpp

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,22 @@ ClassLayout::ClassLayout(const StructLayoutBuilder &builder,
4040
AllElements(allElements) { }
4141

4242
Size ClassLayout::getInstanceStart() const {
43-
if (AllElements.empty())
44-
return getSize();
43+
ArrayRef<ElementLayout> elements = AllElements;
44+
while (!elements.empty()) {
45+
auto element = elements.front();
46+
elements = elements.drop_front();
4547

46-
auto element = AllElements[0];
47-
if (element.getKind() == ElementLayout::Kind::Fixed ||
48-
element.getKind() == ElementLayout::Kind::Empty) {
49-
// FIXME: assumes layout is always sequential!
50-
return element.getByteOffset();
48+
// Ignore empty elements.
49+
if (element.isEmpty()) {
50+
continue;
51+
} else if (element.hasByteOffset()) {
52+
// FIXME: assumes layout is always sequential!
53+
return element.getByteOffset();
54+
} else {
55+
return Size(0);
56+
}
5157
}
5258

53-
return Size(0);
59+
// If there are no non-empty elements, just return the computed size.
60+
return getSize();
5461
}

lib/IRGen/ClassLayout.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ class ClassLayout {
157157
Options.contains(ClassMetadataFlags::ClassHasObjCAncestry));
158158
}
159159

160+
/// Returns true if the runtime may attempt to assign non-zero offsets to
161+
/// empty fields for this class. The ObjC runtime will do this if it
162+
/// decides it needs to slide ivars. This is the one exception to the
163+
/// general rule that the runtime will not try to assign a different offset
164+
/// than was computed statically for a field with a fixed offset.
165+
bool mayRuntimeAssignNonZeroOffsetsToEmptyFields() const {
166+
return Options.contains(ClassMetadataFlags::ClassHasObjCAncestry);
167+
}
168+
160169
/// Returns true iff everything about the class metadata layout is statically
161170
/// known except field offsets and the instance size and alignment.
162171
///

lib/IRGen/GenClass.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ namespace {
314314
}
315315

316316
auto element = ElementLayout::getIncomplete(*eltType);
317-
addField(element, LayoutStrategy::Universal);
317+
bool isKnownEmpty = !addField(element, LayoutStrategy::Universal);
318318

319319
// The 'Elements' list only contains superclass fields when we're
320320
// building a layout for tail allocation.
@@ -323,7 +323,7 @@ namespace {
323323

324324
if (!superclass) {
325325
AllStoredProperties.push_back(var);
326-
AllFieldAccesses.push_back(getFieldAccess());
326+
AllFieldAccesses.push_back(getFieldAccess(isKnownEmpty));
327327
}
328328
}
329329

@@ -381,7 +381,12 @@ namespace {
381381
}
382382
}
383383

384-
FieldAccess getFieldAccess() {
384+
FieldAccess getFieldAccess(bool isKnownEmpty) {
385+
// If the field known empty, then its access pattern is always
386+
// constant-direct.
387+
if (isKnownEmpty)
388+
return FieldAccess::ConstantDirect;
389+
385390
// If layout so far depends on generic parameters, we have to load the
386391
// offset from the field offset vector in class metadata.
387392
if (Options.contains(ClassMetadataFlags::ClassHasGenericLayout))
@@ -556,8 +561,7 @@ irgen::getClassFieldOffset(IRGenModule &IGM, SILType baseType, VarDecl *field) {
556561

557562
auto fieldInfo = classLayout.getFieldAccessAndElement(field);
558563
auto element = fieldInfo.second;
559-
assert(element.getKind() == ElementLayout::Kind::Fixed ||
560-
element.getKind() == ElementLayout::Kind::Empty);
564+
assert(element.hasByteOffset());
561565
return element.getByteOffset();
562566
}
563567

lib/IRGen/GenMeta.cpp

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

23502350
llvm::Constant *fieldOffsetOrZero;
23512351

2352-
if (element.getKind() == ElementLayout::Kind::Fixed) {
2352+
if (element.hasByteOffset()) {
23532353
// Use a fixed offset if we have one.
23542354
fieldOffsetOrZero = IGM.getSize(element.getByteOffset());
23552355
} else {
@@ -2377,11 +2377,21 @@ static void emitFieldOffsetGlobals(IRGenModule &IGM,
23772377
// If it is constant in the fragile layout only, newer Objective-C
23782378
// runtimes will still update them in place, so make sure to check the
23792379
// correct layout.
2380+
//
2381+
// The one exception to this rule is with empty fields with
2382+
// ObjC-resilient heritage. The ObjC runtime will attempt to slide
2383+
// these offsets if it slides the rest of the class, and in doing so
2384+
// it will compute a different offset than we computed statically.
2385+
// But this is ultimately unimportant because we do not care about the
2386+
// offset of an empty field.
23802387
auto resilientInfo = resilientLayout.getFieldAccessAndElement(prop);
2381-
if (resilientInfo.first == FieldAccess::ConstantDirect) {
2388+
if (resilientInfo.first == FieldAccess::ConstantDirect &&
2389+
(!resilientInfo.second.isEmpty() ||
2390+
!resilientLayout.mayRuntimeAssignNonZeroOffsetsToEmptyFields())) {
23822391
// If it is constant in the resilient layout, it should be constant in
23832392
// the fragile layout also.
23842393
assert(access == FieldAccess::ConstantDirect);
2394+
assert(element.hasByteOffset());
23852395
offsetVar->setConstant(true);
23862396
}
23872397

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 -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-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)