Skip to content

Commit

Permalink
Runtime support for identity key paths.
Browse files Browse the repository at this point in the history
Make sure the implementation can handle a key path with zero components by removing inappropriate assumptions that the number of components is always non-empty. Identity key paths also need some special behavior:

- Appending an identity key path should be an identity operation for the other operand
- Identity key paths have a `MemoryLayout.offset(of:)` zero
- Identity key paths interop with KVC as key paths to `@"self"`

To be able to exercise and test this behavior, add a `Builtin.identityKeyPath()` function and `WritableKeyPath._identity` accessor in lieu of finalized syntax.
  • Loading branch information
jckarter committed Aug 20, 2018
1 parent c914ce5 commit 44b55ae
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 8 deletions.
5 changes: 5 additions & 0 deletions include/swift/AST/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,11 @@ BUILTIN_SIL_OPERATION(AllocWithTailElems, "allocWithTailElems", Special)
/// Projects the first tail-allocated element of type E from a class C.
BUILTIN_SIL_OPERATION(ProjectTailElems, "projectTailElems", Special)

/// identityKeyPath : <T> () -> WritableKeyPath<T, T>
///
/// Creates an identity key path object. (TODO: replace with proper syntax)
BUILTIN_SIL_OPERATION(IdentityKeyPath, "identityKeyPath", Special)

#undef BUILTIN_SIL_OPERATION

// BUILTIN_RUNTIME_CALL - A call into a runtime function.
Expand Down
2 changes: 0 additions & 2 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -583,8 +583,6 @@ ERROR(sil_keypath_computed_property_missing_part,none,
ERROR(sil_keypath_external_missing_part,none,
"keypath external component with indices needs an indices_equals and "
"indices_hash function", ())
ERROR(sil_keypath_no_components,none,
"keypath must have at least one component", ())
ERROR(sil_keypath_no_root,none,
"keypath must have a root component declared",())
ERROR(sil_keypath_index_not_hashable,none,
Expand Down
26 changes: 26 additions & 0 deletions lib/AST/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,20 @@ makeTuple(const Gs & ...elementGenerators) {
};
}

template <class... Gs>
static BuiltinGenericSignatureBuilder::LambdaGenerator
makeBoundGenericType(NominalTypeDecl *decl,
const Gs & ...argumentGenerators) {
return {
[=](BuiltinGenericSignatureBuilder &builder) -> Type {
Type args[] = {
argumentGenerators.build(builder)...
};
return BoundGenericType::get(decl, Type(), args);
}
};
}

template <class T>
static BuiltinGenericSignatureBuilder::MetatypeGenerator<T>
makeMetatype(const T &object, Optional<MetatypeRepresentation> repr = None) {
Expand Down Expand Up @@ -972,6 +986,15 @@ static ValueDecl *getTypeJoinMetaOperation(ASTContext &Context, Identifier Id) {
return builder.build(Id);
}

static ValueDecl *getIdentityKeyPathOperation(ASTContext &Context,
Identifier Id) {
BuiltinGenericSignatureBuilder builder(Context, 1);
auto arg = makeGenericParam();
builder.setResult(makeBoundGenericType(Context.getWritableKeyPathDecl(),
arg, arg));
return builder.build(Id);
}

static ValueDecl *getCanBeObjCClassOperation(ASTContext &Context,
Identifier Id) {
// <T> T.Type -> Builtin.Int8
Expand Down Expand Up @@ -1865,6 +1888,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {

case BuiltinValueKind::TypeJoinMeta:
return getTypeJoinMetaOperation(Context, Id);

case BuiltinValueKind::IdentityKeyPath:
return getIdentityKeyPathOperation(Context, Id);
}

llvm_unreachable("bad builtin value!");
Expand Down
9 changes: 6 additions & 3 deletions lib/ParseSIL/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2884,8 +2884,6 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
return true;
}

if (components.empty())
P.diagnose(InstLoc.getSourceLoc(), diag::sil_keypath_no_components);
if (rootType.isNull())
P.diagnose(InstLoc.getSourceLoc(), diag::sil_keypath_no_root);

Expand Down Expand Up @@ -2943,8 +2941,13 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
if (patternEnv && patternEnv->getGenericSignature()) {
canSig = patternEnv->getGenericSignature()->getCanonicalSignature();
}
CanType leafType;
if (!components.empty())
leafType = components.back().getComponentType();
else
leafType = rootType;
auto pattern = KeyPathPattern::get(B.getModule(), canSig,
rootType, components.back().getComponentType(),
rootType, leafType,
components, objcString);

ResultVal = B.createKeyPath(InstLoc, pattern, subMap, operands, Ty);
Expand Down
53 changes: 53 additions & 0 deletions lib/SILGen/SILGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "swift/AST/ASTContext.h"
#include "swift/AST/Builtins.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/Module.h"
#include "swift/AST/ReferenceCounting.h"
#include "swift/SIL/SILArgument.h"
Expand Down Expand Up @@ -1055,6 +1056,58 @@ static ManagedValue emitBuiltinProjectTailElems(SILGenFunction &SGF,
return ManagedValue::forUnmanaged(result);
}

static ManagedValue emitBuiltinIdentityKeyPath(SILGenFunction &SGF,
SILLocation loc,
SubstitutionMap subs,
ArrayRef<ManagedValue> args,
SGFContext C) {
assert(subs.getReplacementTypes().size() == 1 &&
"identityKeyPath should have one substitution");
assert(args.size() == 0 &&
"identityKeyPath should have no args");

auto identityTy = subs.getReplacementTypes()[0]->getCanonicalType();

// The `self` key can be used for identity in Cocoa KVC as well.
StringRef objcString = SGF.getASTContext().LangOpts.EnableObjCInterop
? "self" : "";

// The key path pattern has to capture some generic context if the type is
// dependent on this generic context. We only need the specific type, though,
// not the entire generic environment.
bool isDependent = identityTy->hasArchetype();
CanType identityPatternTy = identityTy;
CanGenericSignature patternSig = nullptr;
SubstitutionMap patternSubs;
if (isDependent) {
auto param = GenericTypeParamType::get(0, 0, SGF.getASTContext());
identityPatternTy = param->getCanonicalType();
patternSig = GenericSignature::get(param, {})->getCanonicalSignature();
patternSubs = SubstitutionMap::get(patternSig,
llvm::makeArrayRef((Type)identityTy),
{});
}

auto identityPattern = KeyPathPattern::get(SGF.SGM.M,
patternSig,
identityPatternTy,
identityPatternTy,
{},
objcString);

auto kpTy = BoundGenericType::get(SGF.getASTContext().getWritableKeyPathDecl(),
Type(),
{identityTy, identityTy})
->getCanonicalType();

auto keyPath = SGF.B.createKeyPath(loc, identityPattern,
patternSubs,
{},
SILType::getPrimitiveObjectType(kpTy));
return SGF.emitManagedRValueWithCleanup(keyPath);
}


/// Specialized emitter for type traits.
template<TypeTraitResult (TypeBase::*Trait)(),
BuiltinValueKind Kind>
Expand Down
48 changes: 46 additions & 2 deletions stdlib/public/core/KeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
ObjectIdentifier(type(of: self)).hash(into: &hasher)
return withBuffer {
var buffer = $0
if buffer.data.isEmpty { return }
while true {
let (component, type) = buffer.next()
hasher.combine(component.value)
Expand Down Expand Up @@ -88,6 +89,11 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
return false
}

// Identity is equal to identity
if aBuffer.data.isEmpty {
return bBuffer.data.isEmpty
}

while true {
let (aComponent, aType) = aBuffer.next()
let (bComponent, bType) = bBuffer.next()
Expand Down Expand Up @@ -153,6 +159,11 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
internal var _storedInlineOffset: Int? {
return withBuffer {
var buffer = $0

// The identity key path is effectively a stored keypath of type Self
// at offset zero
if buffer.data.isEmpty { return 0 }

var offset = 0
while true {
let (rawComponent, optNextType) = buffer.next()
Expand Down Expand Up @@ -220,6 +231,9 @@ public class KeyPath<Root, Value>: PartialKeyPath<Root> {
var curBase: Any = root
return withBuffer {
var buffer = $0
if buffer.data.isEmpty {
return unsafeBitCast(root, to: Value.self)
}
while true {
let (rawComponent, optNextType) = buffer.next()
let valueType = optNextType ?? Value.self
Expand Down Expand Up @@ -279,6 +293,13 @@ public class WritableKeyPath<Root, Value>: KeyPath<Root, Value> {
_sanityCheck(!buffer.hasReferencePrefix,
"WritableKeyPath should not have a reference prefix")

if buffer.data.isEmpty {
return (
UnsafeMutablePointer<Value>(
mutating: p.assumingMemoryBound(to: Value.self)),
nil)
}

while true {
let (rawComponent, optNextType) = buffer.next()
let nextType = optNextType ?? Value.self
Expand Down Expand Up @@ -306,7 +327,16 @@ public class WritableKeyPath<Root, Value>: KeyPath<Root, Value> {
owner: keepAlive)
}
}
}

extension WritableKeyPath where Root == Value {
// FIXME: Replace with proper surface syntax

/// Returns an identity key path that references the entire input value.
@inlinable
public static var _identity: WritableKeyPath<Root, Root> {
return Builtin.identityKeyPath()
}
}

/// A key path that supports reading from and writing to the resulting value
Expand Down Expand Up @@ -1941,6 +1971,16 @@ internal func _appendingKeyPaths<
var rootBuffer = $0
return leaf.withBuffer {
var leafBuffer = $0

// If either operand is the identity key path, then we should return
// the other operand back untouched.
if leafBuffer.data.isEmpty {
return unsafeDowncast(root, to: Result.self)
}
if rootBuffer.data.isEmpty {
return unsafeDowncast(leaf, to: Result.self)
}

// Reserve room for the appended KVC string, if both key paths are
// KVC-compatible.
let appendedKVCLength: Int, rootKVCLength: Int, leafKVCLength: Int
Expand Down Expand Up @@ -2275,7 +2315,8 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern(
var buffer = KeyPathBuffer(base: bufferPtr)
var size = buffer.data.count + MemoryLayout<Int>.size

while true {
if !buffer.data.isEmpty {
while true {
let header = buffer.pop(RawKeyPathComponent.Header.self)

// Ensure that we pop an amount of data consistent with what
Expand Down Expand Up @@ -2552,6 +2593,7 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern(
// Pop the type accessor reference.
_ = buffer.popRaw(size: MemoryLayout<Int>.size,
alignment: Int.self)
}
}

_sanityCheck(buffer.data.isEmpty, "didn't read entire pattern")
Expand Down Expand Up @@ -2628,7 +2670,8 @@ internal func _instantiateKeyPathBuffer(
var base: Any.Type = rootType
// Some pattern forms are pessimistically larger than what we need in the
// instantiated key path. Keep track of this.
while true {
if !patternBuffer.data.isEmpty {
while true {
let componentAddr = destData.baseAddress.unsafelyUnwrapped
let header = patternBuffer.pop(RawKeyPathComponent.Header.self)

Expand Down Expand Up @@ -2996,6 +3039,7 @@ internal func _instantiateKeyPathBuffer(
base = unsafeBitCast(componentTyAccessor(arguments), to: Any.Type.self)
pushDest(base)
previousComponentAddr = componentAddr
}
}

// We should have traversed both buffers.
Expand Down
8 changes: 8 additions & 0 deletions test/IRGen/keypaths.sil
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,14 @@ entry(%0 : @trivial $*A, %1 : @trivial $*B, %2 : @trivial $*A, %3 : @trivial $*B
return undef : $()
}

sil @identity : $@convention(thin) <T> () -> () {
entry:
%v = keypath $WritableKeyPath<T, T>, <A> (root $A; objc "self") <T>
%w = keypath $WritableKeyPath<Int, Int>, (root $Int; objc "self")

return undef : $()
}

sil @s_get : $@convention(thin) <A: Hashable, B: Hashable> (@in_guaranteed A, UnsafeRawPointer) -> @out B
sil @s_set : $@convention(thin) <A: Hashable, B: Hashable> (@in_guaranteed B, @in_guaranteed A, UnsafeRawPointer) -> ()
sil @s_equals : $@convention(thin) <A: Hashable, B: Hashable> (UnsafeRawPointer, UnsafeRawPointer) -> Bool
Expand Down
15 changes: 14 additions & 1 deletion test/SILGen/keypaths.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// RUN: %target-swift-emit-silgen -module-name keypaths %s | %FileCheck %s
// RUN: %target-swift-emit-silgen -parse-stdlib -module-name keypaths %s | %FileCheck %s

import Swift

struct S<T> {
var x: T
Expand Down Expand Up @@ -383,3 +385,14 @@ func subclass_generics<T: C<Int>, U: C<V>, V/*: PoC*/>(_: T, _: U, _: V) {
_ = \PoC.extension
*/
}

// CHECK-LABEL: sil hidden @{{.*}}identity
func identity<T>(_: T) {
// CHECK: keypath $WritableKeyPath<T, T>, <τ_0_0> ({{.*}}root $τ_0_0) <T>
let _: WritableKeyPath<T, T> = Builtin.identityKeyPath()
// CHECK: keypath $WritableKeyPath<Array<T>, Array<T>>, <τ_0_0> ({{.*}}root $τ_0_0) <Array<T>>
let _: WritableKeyPath<[T], [T]> = Builtin.identityKeyPath()
// CHECK: keypath $WritableKeyPath<String, String>, ({{.*}}root $String)
let _: WritableKeyPath<String, String> = Builtin.identityKeyPath()
}

41 changes: 41 additions & 0 deletions test/stdlib/KeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,10 @@ struct NonOffsetableProperties {
var z: Int { return 0 }
}

func getIdentityKeyPathOfType<T>(_: T.Type) -> KeyPath<T, T> {
return WritableKeyPath<T, T>._identity
}

keyPath.test("offsets") {
let SLayout = MemoryLayout<S<Int>>.self
expectNotNil(SLayout.offset(of: \S<Int>.x))
Expand All @@ -699,6 +703,43 @@ keyPath.test("offsets") {
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.x))
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.y))
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.z))

expectEqual(SLayout.offset(of: WritableKeyPath<S<Int>, S<Int>>._identity),
0)
expectEqual(SLayout.offset(of: getIdentityKeyPathOfType(S<Int>.self)), 0)
}

keyPath.test("identity key path") {
var x = LifetimeTracked(1738)

let id = WritableKeyPath<LifetimeTracked, LifetimeTracked>._identity
expectTrue(x === x[keyPath: id])

let newX = LifetimeTracked(679)
x[keyPath: id] = newX
expectTrue(x === newX)

let id2 = getIdentityKeyPathOfType(LifetimeTracked.self)
expectEqual(id, id2)
expectEqual(id.hashValue, id2.hashValue)
expectNotNil(id2 as? WritableKeyPath)

let id3 = id.appending(path: id2)
expectEqual(id, id3)
expectEqual(id.hashValue, id3.hashValue)
expectNotNil(id3 as? WritableKeyPath)

let valueKey = \LifetimeTracked.value
let valueKey2 = id.appending(path: valueKey)
let valueKey3 = (valueKey as KeyPath).appending(path: WritableKeyPath<Int, Int>._identity)

expectEqual(valueKey, valueKey2)
expectEqual(valueKey.hashValue, valueKey2.hashValue)
expectEqual(valueKey, valueKey3)
expectEqual(valueKey.hashValue, valueKey3.hashValue)

expectEqual(x[keyPath: valueKey2], 679)
expectEqual(x[keyPath: valueKey3], 679)
}

keyPath.test("let-ness") {
Expand Down

0 comments on commit 44b55ae

Please sign in to comment.