Skip to content

SILGen/IRGen/KeyPaths: Components for ObjC properties need to be identified by selector. #9516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ CHANGELOG
Swift 4.0
---------

* [SE-0161][] is partially implemented. Swift now natively supports key path
objects for properties. Similar to KVC key path strings in Cocoa, key path
objects allow a property to be referenced independently of accessing it
from a value:

```swift
struct Point {
var x, y: Double
}
let x = \Point.x
let y = \Point.y

let p = Point(x: 3, y: 4)
p[keyPath: x] // gives 3
p[keyPath: y] // gives 4
```

* Core Foundation types implicitly conform to Hashable (and Equatable), using
CFHash and CFEqual as the implementation. This change applies even to "Swift
3 mode", so if you were previously adding this conformance yourself, use
Expand Down
12 changes: 10 additions & 2 deletions include/swift/ABI/KeyPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,18 @@ class KeyPathComponentHeader {
};

enum ComputedPropertyIDKind {
Getter,
Pointer,
StoredPropertyOffset,
VTableOffset,
};

constexpr static uint32_t
getResolutionStrategy(ComputedPropertyIDKind idKind) {
return idKind == Pointer ? _SwiftKeyPathComponentHeader_ComputedIDUnresolvedIndirectPointer
: idKind == StoredPropertyOffset ? _SwiftKeyPathComponentHeader_ComputedIDUnresolvedFieldOffset
: (assert("no resolution strategy implemented" && false), 0);
}

constexpr static KeyPathComponentHeader
forComputedProperty(ComputedPropertyKind kind,
ComputedPropertyIDKind idKind,
Expand All @@ -186,7 +193,8 @@ class KeyPathComponentHeader {
| (idKind == VTableOffset
? _SwiftKeyPathComponentHeader_ComputedIDByVTableOffsetFlag : 0)
| (hasArguments ? _SwiftKeyPathComponentHeader_ComputedHasArgumentsFlag : 0)
| (resolvedID ? 0 : _SwiftKeyPathComponentHeader_ComputedUnresolvedIDFlag));
| (resolvedID ? _SwiftKeyPathComponentHeader_ComputedIDResolved
: getResolutionStrategy(idKind)));
}

constexpr uint32_t getData() const { return Data; }
Expand Down
39 changes: 25 additions & 14 deletions lib/IRGen/GenKeyPath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,26 +271,37 @@ IRGenModule::getAddrOfKeyPathPattern(KeyPathPattern *pattern,
bool idResolved;
switch (id.getKind()) {
case KeyPathPatternComponent::ComputedPropertyId::Function:
idKind = KeyPathComponentHeader::Getter;
idKind = KeyPathComponentHeader::Pointer;
idValue = getAddrOfSILFunction(id.getFunction(), NotForDefinition);
idResolved = true;
break;
case KeyPathPatternComponent::ComputedPropertyId::DeclRef: {
idKind = KeyPathComponentHeader::VTableOffset;
auto declRef = id.getDeclRef();
auto dc = declRef.getDecl()->getDeclContext();
if (isa<ClassDecl>(dc)) {
auto index = getVirtualMethodIndex(*this, declRef);
idValue = llvm::ConstantInt::get(SizeTy, index);
idResolved = true;
} else if (auto methodProto = dyn_cast<ProtocolDecl>(dc)) {
auto &protoInfo = getProtocolInfo(methodProto);
auto index = protoInfo.getFunctionIndex(
cast<AbstractFunctionDecl>(declRef.getDecl()));
idValue = llvm::ConstantInt::get(SizeTy, -index.getValue());
idResolved = true;

// Foreign method refs identify using a selector
// reference, which is doubly-indirected and filled in with a unique
// pointer by dyld.
if (declRef.isForeign) {
assert(ObjCInterop && "foreign keypath component w/o objc interop?!");
idKind = KeyPathComponentHeader::Pointer;
idValue = getAddrOfObjCSelectorRef(declRef);
idResolved = false;
} else {
llvm_unreachable("neither a class nor protocol dynamic method?");
idKind = KeyPathComponentHeader::VTableOffset;
auto dc = declRef.getDecl()->getDeclContext();
if (isa<ClassDecl>(dc)) {
auto index = getVirtualMethodIndex(*this, declRef);
idValue = llvm::ConstantInt::get(SizeTy, index);
idResolved = true;
} else if (auto methodProto = dyn_cast<ProtocolDecl>(dc)) {
auto &protoInfo = getProtocolInfo(methodProto);
auto index = protoInfo.getFunctionIndex(
cast<AbstractFunctionDecl>(declRef.getDecl()));
idValue = llvm::ConstantInt::get(SizeTy, -index.getValue());
idResolved = true;
} else {
llvm_unreachable("neither a class nor protocol dynamic method?");
}
}
break;
}
Expand Down
5 changes: 5 additions & 0 deletions lib/IRGen/GenObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,11 @@ namespace {
};
} // end anonymous namespace

llvm::Constant *IRGenModule::getAddrOfObjCSelectorRef(SILDeclRef method) {
assert(method.isForeign);
return getAddrOfObjCSelectorRef(Selector(method).str());
}

static void emitSuperArgument(IRGenFunction &IGF, bool isInstanceMethod,
llvm::Value *selfValue,
Explosion &selfValues,
Expand Down
1 change: 1 addition & 0 deletions lib/IRGen/IRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ class IRGenModule {
llvm::Constant *getAddrOfGlobalConstantString(StringRef utf8);
llvm::Constant *getAddrOfGlobalUTF16ConstantString(StringRef utf8);
llvm::Constant *getAddrOfObjCSelectorRef(StringRef selector);
llvm::Constant *getAddrOfObjCSelectorRef(SILDeclRef method);
llvm::Constant *getAddrOfObjCMethodName(StringRef methodName);
llvm::Constant *getAddrOfObjCProtocolRecord(ProtocolDecl *proto,
ForDefinition_t forDefinition);
Expand Down
7 changes: 6 additions & 1 deletion lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2694,7 +2694,12 @@ getIdForKeyPathComponentComputedProperty(SILGenFunction &SGF,
}
case AccessStrategy::DispatchToAccessor: {
// Identify the property by its vtable or wtable slot.
return SILDeclRef(property->getGetter(), SILDeclRef::Kind::Func);
// Use the foreign selector if the decl is ObjC-imported, dynamic, or
// otherwise requires objc_msgSend for its ABI.
return SILDeclRef(property->getGetter(), SILDeclRef::Kind::Func,
ResilienceExpansion::Minimal,
/*curried*/ false,
/*foreign*/ property->requiresForeignGetterAndSetter());
}
case AccessStrategy::BehaviorStorage:
llvm_unreachable("unpossible");
Expand Down
8 changes: 7 additions & 1 deletion stdlib/public/SwiftShims/KeyPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,14 @@ static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedIDByVTableOff
= 0x02000000U;
static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedHasArgumentsFlag
= 0x01000000U;
static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedUnresolvedIDFlag
static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedIDResolutionMask
= 0x0000000FU;
static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedIDResolved
= 0x00000000U;
static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedIDUnresolvedFieldOffset
= 0x00000001U;
static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedIDUnresolvedIndirectPointer
= 0x00000002U;


#ifdef __cplusplus
Expand Down
37 changes: 30 additions & 7 deletions stdlib/public/core/KeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -623,8 +623,18 @@ internal struct RawKeyPathComponent {
static var computedHasArgumentsFlag: UInt32 {
return _SwiftKeyPathComponentHeader_ComputedHasArgumentsFlag
}
static var computedUnresolvedIDFlag: UInt32 {
return _SwiftKeyPathComponentHeader_ComputedUnresolvedIDFlag

static var computedIDResolutionMask: UInt32 {
return _SwiftKeyPathComponentHeader_ComputedIDResolutionMask
}
static var computedIDResolved: UInt32 {
return _SwiftKeyPathComponentHeader_ComputedIDResolved
}
static var computedIDUnresolvedFieldOffset: UInt32 {
return _SwiftKeyPathComponentHeader_ComputedIDUnresolvedFieldOffset
}
static var computedIDUnresolvedIndirectPointer: UInt32 {
return _SwiftKeyPathComponentHeader_ComputedIDUnresolvedIndirectPointer
}

var _value: UInt32
Expand Down Expand Up @@ -1733,16 +1743,29 @@ internal func _instantiateKeyPathBuffer(
// property.
var newHeader = header
var id = patternBuffer.pop(Int.self)
if header.payload
& RawKeyPathComponent.Header.computedUnresolvedIDFlag != 0 {
switch header.payload
& RawKeyPathComponent.Header.computedIDResolutionMask {
case RawKeyPathComponent.Header.computedIDResolved:
// Nothing to do.
break
case RawKeyPathComponent.Header.computedIDUnresolvedFieldOffset:
// The value in the pattern is an offset into the type metadata that
// points to the field offset for the stored property identifying the
// component.
_sanityCheck(header.payload
& RawKeyPathComponent.Header.computedIDByStoredPropertyFlag != 0,
"only stored property IDs should need resolution")
newHeader.payload &=
~RawKeyPathComponent.Header.computedUnresolvedIDFlag
"only stored property IDs should need offset resolution")
let metadataPtr = unsafeBitCast(base, to: UnsafeRawPointer.self)
id = metadataPtr.load(fromByteOffset: id, as: Int.self)
case RawKeyPathComponent.Header.computedIDUnresolvedIndirectPointer:
// The value in the pattern is a pointer to the actual unique word-sized
// value in memory.
let idPtr = UnsafeRawPointer(bitPattern: id).unsafelyUnwrapped
id = idPtr.load(as: Int.self)
default:
_sanityCheckFailure("unpossible")
}
newHeader.payload &= ~RawKeyPathComponent.Header.computedIDResolutionMask
pushDest(newHeader)
pushDest(id)
// Carry over the accessors.
Expand Down
37 changes: 37 additions & 0 deletions test/IRGen/keypaths_objc.sil
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize
// REQUIRES: objc_interop

import Swift
import Foundation

class C: NSObject {
dynamic var x: NSString { get }
override init()
}

sil_vtable C {}

sil @x_get : $@convention(thin) (@in C) -> @out NSString

// CHECK: [[KEYPATH_A:@keypath.*]] = private global
// -- 0x2000_0002: computed, get-only, indirect identifier
// CHECK-SAME: i32 536870914
// CHECK-SAME: i8** @"\01L_selector(x)"

// CHECK-LABEL: define swiftcc void @objc_only_property()
sil @objc_only_property : $@convention(thin) () -> () {
entry:
// CHECK: call %swift.refcounted* @swift_getKeyPath({{.*}} [[KEYPATH_A]]
%a = keypath $KeyPath<C, NSString>, (objc "x"; root $C; gettable_property $NSString, id #C.x!getter.1.foreign, getter @x_get : $@convention(thin) (@in C) -> @out NSString)
unreachable
}

sil hidden @_T013keypaths_objc1CC1xSo8NSStringCfgTo : $@convention(objc_method) (@guaranteed C) -> NSString {
entry(%0 : $C):
unreachable
}

sil hidden @_T013keypaths_objc1CCACycfcTo : $@convention(objc_method) (@objc_metatype C.Type) -> @owned C {
entry(%0 : $@objc_metatype C.Type):
unreachable
}
7 changes: 7 additions & 0 deletions test/SILGen/Inputs/keypaths_objc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import Foundation;

@interface ObjCFoo

@property(readonly) NSString *_Nonnull objcProp;

@end
14 changes: 13 additions & 1 deletion test/SILGen/keypaths_objc.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-experimental-keypaths -emit-silgen %s | %FileCheck %s
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-experimental-keypaths -emit-silgen -import-objc-header %S/Inputs/keypaths_objc.h %s | %FileCheck %s
// REQUIRES: objc_interop

import Foundation
Expand All @@ -16,6 +16,8 @@ class Foo: NSObject {

@objc subscript(x: Int) -> Foo { return self }
@objc subscript(x: Bar) -> Foo { return self }

dynamic var dyn: String { fatalError() }
}

class Bar: NSObject {
Expand Down Expand Up @@ -43,3 +45,13 @@ func objcKeypaths() {
// CHECK: keypath $KeyPath<Foo, Bar>, (objc "thisIsADifferentName"
_ = \Foo.differentName
}

// CHECK-LABEL: sil hidden @_T013keypaths_objc0B18KeypathIdentifiersyyF
func objcKeypathIdentifiers() {
// CHECK: keypath $KeyPath<ObjCFoo, String>, (objc "objcProp"; {{.*}} id #ObjCFoo.objcProp!getter.1.foreign
_ = \ObjCFoo.objcProp
// CHECK: keypath $KeyPath<Foo, String>, (objc "dyn"; {{.*}} id #Foo.dyn!getter.1.foreign
_ = \Foo.dyn
// CHECK: keypath $KeyPath<Foo, Int>, (objc "int"; {{.*}} id #Foo.int!getter.1 :
_ = \Foo.int
}
62 changes: 62 additions & 0 deletions test/stdlib/KeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,66 @@ keyPath.test("key path generic instantiation") {
expectEqual(s_c_x_lt, s_c_x_lt2)
}

struct TestComputed {
static var numNonmutatingSets = 0
static var numMutatingSets = 0

static func resetCounts() {
numNonmutatingSets = 0
numMutatingSets = 0
}

var canary = LifetimeTracked(0)

var readonly: LifetimeTracked {
return LifetimeTracked(1)
}
var nonmutating: LifetimeTracked {
get {
return LifetimeTracked(2)
}
nonmutating set { TestComputed.numNonmutatingSets += 1 }
}
var mutating: LifetimeTracked {
get {
return LifetimeTracked(3)
}
set {
canary = newValue
}
}
}

keyPath.test("computed properties") {
var test = TestComputed()

do {
let tc_readonly = \TestComputed.readonly
expectTrue(test[keyPath: tc_readonly] !== test[keyPath: tc_readonly])
expectEqual(test[keyPath: tc_readonly].value,
test[keyPath: tc_readonly].value)
}

do {
let tc_nonmutating = \TestComputed.nonmutating
expectTrue(test[keyPath: tc_nonmutating] !== test[keyPath: tc_nonmutating])
expectEqual(test[keyPath: tc_nonmutating].value,
test[keyPath: tc_nonmutating].value)
TestComputed.resetCounts()
test[keyPath: tc_nonmutating] = LifetimeTracked(4)
expectEqual(TestComputed.numNonmutatingSets, 1)
}

do {
let tc_mutating = \TestComputed.mutating
TestComputed.resetCounts()
expectTrue(test[keyPath: tc_mutating] !== test[keyPath: tc_mutating])
expectEqual(test[keyPath: tc_mutating].value,
test[keyPath: tc_mutating].value)
let newObject = LifetimeTracked(5)
test[keyPath: tc_mutating] = newObject
expectTrue(test.canary === newObject)
}
}

runAllTests()
10 changes: 10 additions & 0 deletions test/stdlib/KeyPathObjC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class Foo: NSObject {

@objc subscript(x: Int) -> Foo { return self }
@objc subscript(x: Bar) -> Foo { return self }

dynamic var dynamic: Bar { fatalError() }
}

class Bar: NSObject {
Expand Down Expand Up @@ -70,4 +72,12 @@ testKVCStrings.test("KVC strings") {
}
}

testKVCStrings.test("identification by selector") {
let foo_dynamic = \Foo.dynamic
let bar_foo = \Bar.foo
let foo_dynamic_foo = \Foo.dynamic.foo

expectEqual(foo_dynamic.appending(path: bar_foo), foo_dynamic_foo)
}

runAllTests()