Skip to content

Commit b00ea61

Browse files
committed
SILGen: Lower keypath references to other modules' properties and subscripts as external keypath components.
This way we'll link against the key path component the other module provides instead of making fragile assumptions about its current implementation. Since external keypath lowering isn't fully implemented elsewhere in the compiler, this is enabled behind a staging flag. external keypath staging
1 parent 75e90ca commit b00ea61

File tree

7 files changed

+170
-36
lines changed

7 files changed

+170
-36
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ namespace swift {
219219
/// This is for bootstrapping. It can't be in SILOptions because the
220220
/// TypeChecker uses it to set resolve the ParameterConvention.
221221
bool EnableSILOpaqueValues = false;
222+
223+
/// Enables key path resilience.
224+
bool EnableKeyPathResilience = false;
222225

223226
/// If set to true, the diagnosis engine can assume the emitted diagnostics
224227
/// will be used in editor. This usually leads to more aggressive fixit.

include/swift/Option/FrontendOptions.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,4 +463,7 @@ def enable_verify_exclusivity : Flag<["-"], "enable-verify-exclusivity">,
463463
def disable_verify_exclusivity : Flag<["-"], "disable-verify-exclusivity">,
464464
HelpText<"Diable verification of access markers used to enforce exclusivity.">;
465465

466+
def enable_key_path_resilience : Flag<["-"], "enable-key-path-resilience">,
467+
HelpText<"Enable key path resilience.">;
468+
466469
} // end let Flags = [FrontendOption, NoDriverOption, HelpHidden]

lib/Frontend/CompilerInvocation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
326326
Target.isOSDarwin());
327327
Opts.EnableSILOpaqueValues |= Args.hasArg(OPT_enable_sil_opaque_values);
328328

329+
Opts.EnableKeyPathResilience |= Args.hasArg(OPT_enable_key_path_resilience);
330+
329331
#if SWIFT_DARWIN_ENABLE_STABLE_ABI_BIT
330332
Opts.UseDarwinPreStableABIBit = false;
331333
#else

lib/SIL/SILVerifier.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3999,7 +3999,7 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
39993999
auto substType = component.getExternalDecl()->getStorageInterfaceType()
40004000
.subst(subs);
40014001
require(substType->isEqual(component.getComponentType()),
4002-
"component type should make storage type of referenced "
4002+
"component type should match storage type of referenced "
40034003
"declaration");
40044004

40054005
// Index types should match the lowered index types expected by the

lib/SILGen/SILGenExpr.cpp

Lines changed: 103 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3702,6 +3702,96 @@ RValue RValueEmitter::visitKeyPathExpr(KeyPathExpr *E, SGFContext C) {
37023702
auto baseTy = rootTy;
37033703
SmallVector<SILValue, 4> operands;
37043704

3705+
auto lowerSubscriptIndices =
3706+
[this, &operands, &needsGenericContext, baseTy, E]
3707+
(const KeyPathExpr::Component &component) ->
3708+
SmallVector<KeyPathPatternComponent::Index, 4> {
3709+
// Capturing an index value dependent on the generic context means we
3710+
// need the generic context captured in the key path.
3711+
needsGenericContext |=
3712+
component.getIndexExpr()->getType()->hasArchetype()
3713+
| baseTy->hasTypeParameter();
3714+
3715+
// Evaluate the index arguments.
3716+
SmallVector<RValue, 2> indexValues;
3717+
auto indexResult = visit(component.getIndexExpr(), SGFContext());
3718+
if (isa<TupleType>(indexResult.getType())) {
3719+
std::move(indexResult).extractElements(indexValues);
3720+
} else {
3721+
indexValues.push_back(std::move(indexResult));
3722+
}
3723+
3724+
SmallVector<KeyPathPatternComponent::Index, 4> indexPatterns;
3725+
for (unsigned i : indices(indexValues)) {
3726+
auto hashable = component.getSubscriptIndexHashableConformances()[i];
3727+
assert(hashable.isAbstract() ||
3728+
hashable.getConcrete()->getType()->isEqual(indexValues[i].getType()));
3729+
auto &value = indexValues[i];
3730+
3731+
auto indexTy = value.getType()->mapTypeOutOfContext()->getCanonicalType();
3732+
auto indexLoweredTy = SGF.getLoweredType(value.getType());
3733+
indexLoweredTy = SILType::getPrimitiveType(
3734+
indexLoweredTy.getSwiftRValueType()->mapTypeOutOfContext()
3735+
->getCanonicalType(),
3736+
indexLoweredTy.getCategory());
3737+
indexPatterns.push_back({(unsigned)operands.size(),
3738+
indexTy, indexLoweredTy,
3739+
hashable});
3740+
operands.push_back(
3741+
std::move(indexValues[i]).forwardAsSingleValue(SGF, E));
3742+
}
3743+
return indexPatterns;
3744+
};
3745+
3746+
/// Returns true if a key path component for the given property or
3747+
/// subscript should be externally referenced.
3748+
auto shouldUseExternalKeyPathComponent =
3749+
[&](AbstractStorageDecl *storage) -> bool {
3750+
return SGF.getASTContext().LangOpts.EnableKeyPathResilience
3751+
&& storage->getModuleContext() != SGF.SGM.SwiftModule;
3752+
};
3753+
3754+
/// Build an external key path component referencing a property or subscript
3755+
/// from another module.
3756+
auto makeExternalKeyPathComponent =
3757+
[&](const KeyPathExpr::Component &component,
3758+
CanType ty) -> KeyPathPatternComponent {
3759+
SmallVector<KeyPathPatternComponent::Index, 4> indices;
3760+
SubstitutionList subs = component.getDeclRef().getSubstitutions();
3761+
3762+
// Map the substitutions out of context.
3763+
if (!subs.empty()) {
3764+
// If any of the substitutions involve local archetypes, then the
3765+
// key path pattern needs to capture the generic context, and we need
3766+
// to map the pattern substitutions out of this context.
3767+
if (std::any_of(subs.begin(), subs.end(),
3768+
[](const Substitution &s) -> bool {
3769+
return s.getReplacement()->hasArchetype();
3770+
})) {
3771+
needsGenericContext = true;
3772+
auto sig = component.getDeclRef().getDecl()
3773+
->getInnermostDeclContext()
3774+
->getGenericSignatureOfContext();
3775+
auto subMap = sig
3776+
->getSubstitutionMap(component.getDeclRef().getSubstitutions());
3777+
subMap = subMap.mapReplacementTypesOutOfContext();
3778+
SmallVector<Substitution, 4> subsBuf;
3779+
3780+
sig->getSubstitutions(subMap, subsBuf);
3781+
3782+
subs = SGF.getASTContext().AllocateCopy(subsBuf);
3783+
}
3784+
}
3785+
3786+
if (component.getKind() == KeyPathExpr::Component::Kind::Subscript)
3787+
indices = lowerSubscriptIndices(component);
3788+
return KeyPathPatternComponent::forExternal(
3789+
cast<AbstractStorageDecl>(component.getDeclRef().getDecl()),
3790+
subs,
3791+
SGF.getASTContext().AllocateCopy(indices),
3792+
ty);
3793+
};
3794+
37053795
for (auto &component : E->getComponents()) {
37063796
switch (auto kind = component.getKind()) {
37073797
case KeyPathExpr::Component::Kind::Property: {
@@ -3711,6 +3801,12 @@ RValue RValueEmitter::visitKeyPathExpr(KeyPathExpr *E, SGFContext C) {
37113801
->getReferenceStorageReferent()
37123802
->getCanonicalType();
37133803

3804+
if (shouldUseExternalKeyPathComponent(decl)) {
3805+
loweredComponents.push_back(makeExternalKeyPathComponent(component,
3806+
baseTy));
3807+
continue;
3808+
}
3809+
37143810
switch (auto strategy = decl->getAccessStrategy(AccessSemantics::Ordinary,
37153811
AccessKind::ReadWrite)) {
37163812
case AccessStrategy::Storage: {
@@ -3805,44 +3901,16 @@ RValue RValueEmitter::visitKeyPathExpr(KeyPathExpr *E, SGFContext C) {
38053901
->substGenericArgs(component.getDeclRef().getSubstitutions());
38063902
auto baseSubscriptInterfaceTy = cast<AnyFunctionType>(
38073903
baseSubscriptTy->mapTypeOutOfContext()->getCanonicalType());
3808-
38093904
baseTy = baseSubscriptInterfaceTy.getResult();
3810-
3811-
// Capturing an index value dependent on the generic context means we
3812-
// need the generic context captured in the key path.
3813-
needsGenericContext |=
3814-
component.getIndexExpr()->getType()->hasArchetype()
3815-
| baseTy->hasTypeParameter();
3816-
3817-
// Evaluate the index arguments.
3818-
SmallVector<RValue, 2> indexValues;
3819-
auto indexResult = visit(component.getIndexExpr(), SGFContext());
3820-
if (isa<TupleType>(indexResult.getType())) {
3821-
std::move(indexResult).extractElements(indexValues);
3822-
} else {
3823-
indexValues.push_back(std::move(indexResult));
3905+
3906+
if (shouldUseExternalKeyPathComponent(decl)) {
3907+
loweredComponents.push_back(makeExternalKeyPathComponent(component,
3908+
baseTy));
3909+
continue;
38243910
}
3825-
3826-
SmallVector<KeyPathPatternComponent::Index, 4> indexPatterns;
3911+
3912+
auto indexPatterns = lowerSubscriptIndices(component);
38273913
SILFunction *indexEquals = nullptr, *indexHash = nullptr;
3828-
for (unsigned i : indices(indexValues)) {
3829-
auto hashable = component.getSubscriptIndexHashableConformances()[i];
3830-
assert(hashable.isAbstract() ||
3831-
hashable.getConcrete()->getType()->isEqual(indexValues[i].getType()));
3832-
auto &value = indexValues[i];
3833-
3834-
auto indexTy = value.getType()->mapTypeOutOfContext()->getCanonicalType();
3835-
auto indexLoweredTy = SGF.getLoweredType(value.getType());
3836-
indexLoweredTy = SILType::getPrimitiveType(
3837-
indexLoweredTy.getSwiftRValueType()->mapTypeOutOfContext()
3838-
->getCanonicalType(),
3839-
indexLoweredTy.getCategory());
3840-
indexPatterns.push_back({(unsigned)operands.size(),
3841-
indexTy, indexLoweredTy,
3842-
hashable});
3843-
operands.push_back(
3844-
std::move(indexValues[i]).forwardAsSingleValue(SGF, E));
3845-
}
38463914
getOrCreateKeyPathEqualsAndHash(SGF, SILLocation(E),
38473915
needsGenericContext ? SGF.F.getGenericEnvironment() : nullptr,
38483916
indexPatterns,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public struct External<A> {
2+
public var property: A
3+
public var intProperty: Int
4+
public subscript<B: Hashable>(index: B) -> A { return property }
5+
}

test/SILGen/external-keypath.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: %target-swift-frontend -emit-module -o %t/ExternalKeyPaths.swiftmodule -module-name ExternalKeyPaths %S/Inputs/ExternalKeyPaths.swift
4+
// RUN: %target-swift-frontend -enable-key-path-resilience -emit-silgen -I %t %s | %FileCheck %s
5+
6+
import ExternalKeyPaths
7+
8+
struct Local {
9+
var x: Int
10+
var y: String
11+
}
12+
13+
// CHECK-LABEL: sil hidden @{{.*}}16externalKeyPaths
14+
func externalKeyPaths<T: Hashable, U>(_ x: T, _ y: U, _ z: Int) {
15+
// CHECK: keypath $WritableKeyPath<External<Int>, Int>, (root $External<Int>; external #External.property<Int> : $Int)
16+
_ = \External<Int>.property
17+
18+
// CHECK: keypath $WritableKeyPath<External<Int>, Int>, (root $External<Int>; external #External.intProperty<Int> : $Int)
19+
_ = \External<Int>.intProperty
20+
21+
// CHECK: keypath $WritableKeyPath<External<T>, T>, <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (root $External<τ_0_0>; external #External.property<T> : $τ_0_0) <T, U>
22+
_ = \External<T>.property
23+
24+
// CHECK: keypath $WritableKeyPath<External<T>, Int>, <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (root $External<τ_0_0>; external #External.intProperty<T> : $Int) <T, U>
25+
_ = \External<T>.intProperty
26+
27+
// CHECK: keypath $WritableKeyPath<External<U>, U>, <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (root $External<τ_0_1>; external #External.property<U> : $τ_0_1) <T, U>
28+
_ = \External<U>.property
29+
30+
// CHECK: keypath $WritableKeyPath<External<U>, Int>, <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (root $External<τ_0_1>; external #External.intProperty<U> : $Int) <T, U>
31+
_ = \External<U>.intProperty
32+
33+
// CHECK: keypath $KeyPath<External<Int>, Int>, (root $External<Int>; external #External.subscript<Int, Int>[%$0 : $Int : $Int] : $Int) (%2)
34+
_ = \External<Int>.[z]
35+
36+
// CHECK: keypath $KeyPath<External<T>, T>, <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (root $External<τ_0_0>; external #External.subscript<T, T>[%$0 : $τ_0_0 : $*τ_0_0] : $τ_0_0) <T, U> ({{.*}})
37+
_ = \External<T>.[x]
38+
39+
// CHECK: keypath $KeyPath<External<U>, U>, <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (root $External<τ_0_1>; external #External.subscript<U, T>[%$0 : $τ_0_0 : $*τ_0_0] : $τ_0_1) <T, U> ({{.*}})
40+
_ = \External<U>.[x]
41+
42+
// CHECK: keypath $KeyPath<External<Local>, Int>, <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (
43+
// CHECK-SAME: root $External<Local>;
44+
// CHECK-SAME: external #External.subscript<Local, T>[%$0 : $τ_0_0 : $*τ_0_0] : $Local;
45+
// CHECK-SAME: stored_property #Local.x : $Int) <T, U> ({{.*}})
46+
_ = \External<Local>.[x].x
47+
48+
// CHECK: keypath $KeyPath<External<Local>, String>, <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (
49+
// CHECK-SAME: root $External<Local>;
50+
// CHECK-SAME: external #External.subscript<Local, T>[%$0 : $τ_0_0 : $*τ_0_0] : $Local;
51+
// CHECK-SAME: stored_property #Local.y : $String) <T, U> ({{.*}})
52+
_ = \External<Local>.[x].y
53+
}

0 commit comments

Comments
 (0)