Skip to content

Commit fe88bd5

Browse files
committed
KeyPaths: Correctly instantiate offsets for final stored properties in NSObject subclasses.
We need to use the ivar offset variables in this case, since the Swift field offset vector doesn't pick up the adjusted offsets from the ObjC runtime. Fixes SR-5036 | rdar://problem/32488871.
1 parent 3663c36 commit fe88bd5

File tree

8 files changed

+177
-37
lines changed

8 files changed

+177
-37
lines changed

include/swift/ABI/KeyPath.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ class KeyPathComponentHeader {
104104
}
105105

106106
constexpr static KeyPathComponentHeader
107-
forStructComponentWithUnresolvedOffset() {
107+
forStructComponentWithUnresolvedFieldOffset() {
108108
return KeyPathComponentHeader(
109109
(_SwiftKeyPathComponentHeader_StructTag
110110
<< _SwiftKeyPathComponentHeader_DiscriminatorShift)
111-
| _SwiftKeyPathComponentHeader_UnresolvedOffsetPayload);
111+
| _SwiftKeyPathComponentHeader_UnresolvedFieldOffsetPayload);
112112
}
113113

114114
constexpr static KeyPathComponentHeader
@@ -128,11 +128,19 @@ class KeyPathComponentHeader {
128128
}
129129

130130
constexpr static KeyPathComponentHeader
131-
forClassComponentWithUnresolvedOffset() {
131+
forClassComponentWithUnresolvedFieldOffset() {
132132
return KeyPathComponentHeader(
133-
(_SwiftKeyPathComponentHeader_StructTag
133+
(_SwiftKeyPathComponentHeader_ClassTag
134+
<< _SwiftKeyPathComponentHeader_DiscriminatorShift)
135+
| _SwiftKeyPathComponentHeader_UnresolvedFieldOffsetPayload);
136+
}
137+
138+
constexpr static KeyPathComponentHeader
139+
forClassComponentWithUnresolvedIndirectOffset() {
140+
return KeyPathComponentHeader(
141+
(_SwiftKeyPathComponentHeader_ClassTag
134142
<< _SwiftKeyPathComponentHeader_DiscriminatorShift)
135-
| _SwiftKeyPathComponentHeader_UnresolvedOffsetPayload);
143+
| _SwiftKeyPathComponentHeader_UnresolvedIndirectOffsetPayload);
136144
}
137145

138146
constexpr static KeyPathComponentHeader

lib/IRGen/GenClass.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,13 @@ irgen::tryEmitConstantClassFragilePhysicalMemberOffset(IRGenModule &IGM,
506506
}
507507
}
508508

509-
509+
FieldAccess
510+
irgen::getClassFieldAccess(IRGenModule &IGM, SILType baseType, VarDecl *field) {
511+
auto &baseClassTI = IGM.getTypeInfo(baseType).as<ClassTypeInfo>();
512+
auto &classLayout = baseClassTI.getClassLayout(IGM, baseType);
513+
unsigned fieldIndex = classLayout.getFieldIndex(field);
514+
return classLayout.AllFieldAccesses[fieldIndex];
515+
}
510516

511517
OwnedAddress irgen::projectPhysicalClassMemberAddress(IRGenFunction &IGF,
512518
llvm::Value *base,

lib/IRGen/GenClass.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ namespace irgen {
4848
enum class ReferenceCounting : unsigned char;
4949
enum class IsaEncoding : unsigned char;
5050
enum class ClassDeallocationKind : unsigned char;
51+
enum class FieldAccess : uint8_t;
5152

5253
OwnedAddress projectPhysicalClassMemberAddress(IRGenFunction &IGF,
5354
llvm::Value *base,
@@ -138,6 +139,10 @@ namespace irgen {
138139
tryEmitConstantClassFragilePhysicalMemberOffset(IRGenModule &IGM,
139140
SILType baseType,
140141
VarDecl *field);
142+
143+
FieldAccess getClassFieldAccess(IRGenModule &IGM,
144+
SILType baseType,
145+
VarDecl *field);
141146

142147
/// What reference counting mechanism does a class-like type use?
143148
ReferenceCounting getReferenceCountingForType(IRGenModule &IGM,

lib/IRGen/GenKeyPath.cpp

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "IRGenFunction.h"
2626
#include "IRGenModule.h"
2727
#include "ProtocolInfo.h"
28+
#include "StructLayout.h"
2829
#include "llvm/ADT/SetVector.h"
2930
#include "swift/SIL/SILInstruction.h"
3031
#include "swift/SIL/SILLocation.h"
@@ -209,47 +210,98 @@ IRGenModule::getAddrOfKeyPathPattern(KeyPathPattern *pattern,
209210
auto &component = pattern->getComponents()[i];
210211
switch (auto kind = component.getKind()) {
211212
case KeyPathPatternComponent::Kind::StoredProperty: {
212-
// Try to get a constant offset if we can.
213213
auto property = cast<VarDecl>(component.getStoredPropertyDecl());
214-
llvm::Constant *offset;
215-
bool isResolved;
216-
std::tie(offset, isResolved)
217-
= getPropertyOffsetOrIndirectOffset(loweredBaseTy, property);
218-
offset = llvm::ConstantExpr::getTruncOrBitCast(offset, Int32Ty);
219-
bool isStruct = (bool)loweredBaseTy.getStructOrBoundGenericStruct();
220214

221-
// If the projection is a statically known integer, try to pack it into
222-
// the key path payload.
223-
if (isResolved) {
215+
auto addFixedOffset = [&](bool isStruct, llvm::Constant *offset) {
216+
offset = llvm::ConstantExpr::getTruncOrBitCast(offset, Int32Ty);
224217
if (auto offsetInt = dyn_cast_or_null<llvm::ConstantInt>(offset)) {
225218
auto offsetValue = offsetInt->getValue().getZExtValue();
226219
if (KeyPathComponentHeader::offsetCanBeInline(offsetValue)) {
227220
auto header = isStruct
228221
? KeyPathComponentHeader::forStructComponentWithInlineOffset(offsetValue)
229222
: KeyPathComponentHeader::forClassComponentWithInlineOffset(offsetValue);
230223
fields.addInt32(header.getData());
231-
break;
224+
return;
232225
}
233226
}
234-
235227
auto header = isStruct
236228
? KeyPathComponentHeader::forStructComponentWithOutOfLineOffset()
237229
: KeyPathComponentHeader::forClassComponentWithOutOfLineOffset();
238230
fields.addInt32(header.getData());
239-
240231
fields.add(offset);
241-
} else {
242-
// Otherwise, stash the offset of the field offset within the metadata
243-
// object, so we can pull it out at instantiation time.
244-
// TODO: We'll also need a way to handle resilient field offsets, once
245-
// field offset vectors no longer cover all fields in the type.
246-
KeyPathComponentHeader header = isStruct
247-
? KeyPathComponentHeader::forStructComponentWithUnresolvedOffset()
248-
: KeyPathComponentHeader::forClassComponentWithUnresolvedOffset();
232+
};
233+
234+
// For a struct stored property, we may know the fixed offset of the field,
235+
// or we may need to fetch it out of the type's metadata at instantiation
236+
// time.
237+
if (loweredBaseTy.getStructOrBoundGenericStruct()) {
238+
if (auto offset = emitPhysicalStructMemberFixedOffset(*this,
239+
loweredBaseTy,
240+
property)) {
241+
// We have a known constant fixed offset.
242+
addFixedOffset(/*struct*/ true, offset);
243+
break;
244+
}
245+
246+
// If the offset isn't fixed, try instead to get the field offset out
247+
// of the type metadata at instantiation time.
248+
auto fieldOffset = emitPhysicalStructMemberOffsetOfFieldOffset(
249+
*this, loweredBaseTy, property);
250+
fieldOffset = llvm::ConstantExpr::getTruncOrBitCast(fieldOffset,
251+
Int32Ty);
252+
auto header = KeyPathComponentHeader::forStructComponentWithUnresolvedFieldOffset();
249253
fields.addInt32(header.getData());
250-
fields.add(offset);
254+
fields.add(fieldOffset);
255+
break;
256+
}
257+
258+
// For a class, we may know the fixed offset of a field at compile time,
259+
// or we may need to fetch it at instantiation time. Depending on the
260+
// ObjC-ness and resilience of the class hierarchy, there might be a few
261+
// different ways we need to go about this.
262+
if (loweredBaseTy.getClassOrBoundGenericClass()) {
263+
switch (getClassFieldAccess(*this, loweredBaseTy, property)) {
264+
case FieldAccess::ConstantDirect: {
265+
// Known constant fixed offset.
266+
auto offset = tryEmitConstantClassFragilePhysicalMemberOffset(*this,
267+
loweredBaseTy,
268+
property);
269+
assert(offset && "no constant offset for ConstantDirect field?!");
270+
addFixedOffset(/*struct*/ false, offset);
271+
break;
272+
}
273+
case FieldAccess::NonConstantDirect: {
274+
// A constant offset that's determined at class realization time.
275+
// We have to load the offset from a global ivar.
276+
auto header =
277+
KeyPathComponentHeader::forClassComponentWithUnresolvedIndirectOffset();
278+
fields.addInt32(header.getData());
279+
auto offsetVar = getAddrOfFieldOffset(property, /*indirect*/ false,
280+
NotForDefinition);
281+
fields.add(cast<llvm::Constant>(offsetVar.getAddress()));
282+
break;
283+
}
284+
case FieldAccess::ConstantIndirect: {
285+
// An offset that depends on the instance's generic parameterization,
286+
// but whose field offset is at a known vtable offset.
287+
auto header =
288+
KeyPathComponentHeader::forClassComponentWithUnresolvedFieldOffset();
289+
fields.addInt32(header.getData());
290+
auto fieldOffset =
291+
getClassFieldOffset(*this, loweredBaseTy.getClassOrBoundGenericClass(),
292+
property);
293+
fields.addInt32(fieldOffset.getValue());
294+
break;
295+
}
296+
case FieldAccess::NonConstantIndirect:
297+
// An offset that depends on the instance's generic parameterization,
298+
// whose vtable offset is also unknown.
299+
// TODO: This doesn't happen until class resilience is enabled.
300+
llvm_unreachable("not implemented");
301+
}
302+
break;
251303
}
252-
break;
304+
llvm_unreachable("not struct or class");
253305
}
254306
case KeyPathPatternComponent::Kind::GettableProperty:
255307
case KeyPathPatternComponent::Kind::SettableProperty: {

stdlib/public/SwiftShims/KeyPath.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ static const __swift_uint32_t _SwiftKeyPathComponentHeader_OptionalTag
5555
= 3;
5656

5757
static const __swift_uint32_t _SwiftKeyPathComponentHeader_MaximumOffsetPayload
58+
= 0x1FFFFFFCU;
59+
60+
static const __swift_uint32_t _SwiftKeyPathComponentHeader_UnresolvedIndirectOffsetPayload
5861
= 0x1FFFFFFDU;
59-
60-
static const __swift_uint32_t _SwiftKeyPathComponentHeader_UnresolvedOffsetPayload
62+
63+
static const __swift_uint32_t _SwiftKeyPathComponentHeader_UnresolvedFieldOffsetPayload
6164
= 0x1FFFFFFEU;
6265

6366
static const __swift_uint32_t _SwiftKeyPathComponentHeader_OutOfLineOffsetPayload

stdlib/public/core/KeyPath.swift

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,11 @@ internal struct RawKeyPathComponent {
614614
static var outOfLineOffsetPayload: UInt32 {
615615
return _SwiftKeyPathComponentHeader_OutOfLineOffsetPayload
616616
}
617-
static var unresolvedOffsetPayload: UInt32 {
618-
return _SwiftKeyPathComponentHeader_UnresolvedOffsetPayload
617+
static var unresolvedFieldOffsetPayload: UInt32 {
618+
return _SwiftKeyPathComponentHeader_UnresolvedFieldOffsetPayload
619+
}
620+
static var unresolvedIndirectOffsetPayload: UInt32 {
621+
return _SwiftKeyPathComponentHeader_UnresolvedIndirectOffsetPayload
619622
}
620623
static var computedMutatingFlag: UInt32 {
621624
return _SwiftKeyPathComponentHeader_ComputedMutatingFlag
@@ -1613,10 +1616,13 @@ internal func _getKeyPathClassAndInstanceSize(
16131616
let header = buffer.pop(RawKeyPathComponent.Header.self)
16141617

16151618
func popOffset() {
1616-
if header.payload == RawKeyPathComponent.Header.unresolvedOffsetPayload
1619+
if header.payload == RawKeyPathComponent.Header.unresolvedFieldOffsetPayload
16171620
|| header.payload == RawKeyPathComponent.Header.outOfLineOffsetPayload {
16181621
_ = buffer.pop(UInt32.self)
16191622
}
1623+
if header.payload == RawKeyPathComponent.Header.unresolvedIndirectOffsetPayload {
1624+
_ = buffer.pop(Int.self)
1625+
}
16201626
}
16211627

16221628
switch header.kind {
@@ -1712,6 +1718,12 @@ internal func _instantiateKeyPathBuffer(
17121718
count: origDestData.count - MemoryLayout<KeyPathBuffer.Header>.size)
17131719

17141720
func pushDest<T>(_ value: T) {
1721+
// TODO: If key path patterns were better optimized to try to be constant-
1722+
// memory objects, then it might become profitable to try to avoid writes
1723+
// here in the case when the dest memory contains the value we want to write
1724+
// here so that we don't dirty memory. (In practice the current
1725+
// implementation will always dirty the page when the key path is
1726+
// instantiated.)
17151727
_sanityCheck(_isPOD(T.self))
17161728
var value2 = value
17171729
let size = MemoryLayout<T>.size
@@ -1729,12 +1741,15 @@ internal func _instantiateKeyPathBuffer(
17291741

17301742
// Instantiate components that need it.
17311743
var base: Any.Type = rootType
1744+
// Some pattern forms are pessimistically larger than what we need in the
1745+
// instantiated key path. Keep track of this.
1746+
var shrinkage = 0
17321747
while true {
17331748
let componentAddr = destData.baseAddress.unsafelyUnwrapped
17341749
let header = patternBuffer.pop(RawKeyPathComponent.Header.self)
17351750

17361751
func tryToResolveOffset() {
1737-
if header.payload == RawKeyPathComponent.Header.unresolvedOffsetPayload {
1752+
if header.payload == RawKeyPathComponent.Header.unresolvedFieldOffsetPayload {
17381753
// Look up offset in type metadata. The value in the pattern is the
17391754
// offset within the metadata object.
17401755
let metadataPtr = unsafeBitCast(base, to: UnsafeRawPointer.self)
@@ -1749,6 +1764,21 @@ internal func _instantiateKeyPathBuffer(
17491764
return
17501765
}
17511766

1767+
if header.payload == RawKeyPathComponent.Header.unresolvedIndirectOffsetPayload {
1768+
// Look up offset in the indirectly-referenced variable we have a
1769+
// pointer.
1770+
let offsetVar = patternBuffer.pop(UnsafeRawPointer.self)
1771+
let offsetValue = UInt32(offsetVar.load(as: UInt.self))
1772+
// Rewrite the header for a resolved offset.
1773+
var newHeader = header
1774+
newHeader.payload = RawKeyPathComponent.Header.outOfLineOffsetPayload
1775+
pushDest(newHeader)
1776+
pushDest(offsetValue)
1777+
shrinkage += MemoryLayout<UnsafeRawPointer>.size
1778+
- MemoryLayout<UInt32>.size
1779+
return
1780+
}
1781+
17521782
// Otherwise, just transfer the pre-resolved component.
17531783
pushDest(header)
17541784
if header.payload == RawKeyPathComponent.Header.outOfLineOffsetPayload {
@@ -1846,10 +1876,11 @@ internal func _instantiateKeyPathBuffer(
18461876
}
18471877

18481878
// We should have traversed both buffers.
1849-
_sanityCheck(patternBuffer.data.isEmpty && destData.isEmpty)
1879+
_sanityCheck(patternBuffer.data.isEmpty && destData.count == shrinkage)
18501880

18511881
// Write out the header.
1852-
let destHeader = KeyPathBuffer.Header(size: origPatternBuffer.data.count,
1882+
let destHeader = KeyPathBuffer.Header(
1883+
size: origPatternBuffer.data.count - shrinkage,
18531884
trivial: true, // TODO: nontrivial indexes
18541885
hasReferencePrefix: endOfReferencePrefixComponent != nil)
18551886

test/IRGen/keypaths_objc.sil

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Foundation
66

77
class C: NSObject {
88
dynamic var x: NSString { get }
9+
@sil_stored final var stored: Int
910
override init()
1011
}
1112

@@ -18,6 +19,11 @@ sil @x_get : $@convention(thin) (@in C) -> @out NSString
1819
// CHECK-SAME: i32 536870914
1920
// CHECK-SAME: i8** @"\01L_selector(x)"
2021

22+
// CHECK: [[KEYPATH_B:@keypath.*]] = private global
23+
// -- 0x5ffffffd: class stored property with indirect offset
24+
// CHECK-SAME: i32 1610612733,
25+
// CHECK-SAME: @_T013keypaths_objc1CC6storedSivWvd
26+
2127
// CHECK-LABEL: define swiftcc void @objc_only_property()
2228
sil @objc_only_property : $@convention(thin) () -> () {
2329
entry:
@@ -35,3 +41,10 @@ sil hidden @_T013keypaths_objc1CCACycfcTo : $@convention(objc_method) (@objc_met
3541
entry(%0 : $@objc_metatype C.Type):
3642
unreachable
3743
}
44+
45+
sil @objc_stored_property : $@convention(thin) () -> () {
46+
entry:
47+
// CHECK: call %swift.refcounted* @swift_getKeyPath({{.*}} [[KEYPATH_B]]
48+
%b = keypath $KeyPath<C, Int>, (objc "stored"; root $C; stored_property #C.stored : $Int)
49+
unreachable
50+
}

test/stdlib/KeyPathObjC.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,34 @@ class Foo: NSObject {
2222
@objc subscript(x: Bar) -> Foo { return self }
2323

2424
dynamic var dynamic: Bar { fatalError() }
25+
26+
let storedLet = LifetimeTracked(0)
27+
}
28+
29+
// We just need some non-empty ObjC-defined class here to ensure we get the
30+
// right offset for a 'let' or final stored property after the ObjC runtime
31+
// slides offsets
32+
class MyWeirdFormatter: DateFormatter {
33+
let storedLet = LifetimeTracked(1)
2534
}
2635

2736
class Bar: NSObject {
2837
@objc var foo: Foo { fatalError() }
2938
}
3039

40+
var testStoredProperties = TestSuite("stored properties in ObjC subclasses")
41+
42+
testStoredProperties.test("final stored properties in ObjC subclasses") {
43+
let fooLet = \Foo.storedLet
44+
let formatterLet = \MyWeirdFormatter.storedLet
45+
46+
let foo = Foo()
47+
let formatter = MyWeirdFormatter()
48+
49+
expectTrue(foo[keyPath: fooLet] === foo.storedLet)
50+
expectTrue(formatter[keyPath: formatterLet] === formatter.storedLet)
51+
}
52+
3153
var testKVCStrings = TestSuite("KVC strings")
3254

3355
testKVCStrings.test("KVC strings") {

0 commit comments

Comments
 (0)