Skip to content

Commit cb09c33

Browse files
authored
Merge pull request #22738 from rjmccall/empty-field-offset-variables
Always give known-empty class properties a zero offset in the static layout
2 parents 345e988 + 7f55a4a commit cb09c33

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)