Skip to content

Commit 92520d5

Browse files
authored
Merge pull request #39588 from slavapestov/circular-conformance-path-fix
IRGen: Fix subtle issue with "circular" conformance access paths
2 parents e01f75a + a74dcd8 commit 92520d5

File tree

6 files changed

+208
-51
lines changed

6 files changed

+208
-51
lines changed

lib/AST/RequirementMachine/GenericSignatureQueries.cpp

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -434,19 +434,6 @@ Type RequirementMachine::getCanonicalTypeInContext(
434434
});
435435
}
436436

437-
/// Replace 'Self' in the given dependent type (\c depTy) with the given
438-
/// dependent type, producing a type that refers to the nested type.
439-
static Type replaceSelfWithType(Type selfType, Type depTy) {
440-
if (auto depMemTy = depTy->getAs<DependentMemberType>()) {
441-
Type baseType = replaceSelfWithType(selfType, depMemTy->getBase());
442-
assert(depMemTy->getAssocType() && "Missing associated type");
443-
return DependentMemberType::get(baseType, depMemTy->getAssocType());
444-
}
445-
446-
assert(depTy->is<GenericTypeParamType>() && "missing Self?");
447-
return selfType;
448-
}
449-
450437
/// Retrieve the conformance access path used to extract the conformance of
451438
/// interface \c type to the given \c protocol.
452439
///
@@ -462,12 +449,29 @@ static Type replaceSelfWithType(Type selfType, Type depTy) {
462449
ConformanceAccessPath
463450
RequirementMachine::getConformanceAccessPath(Type type,
464451
ProtocolDecl *protocol) {
465-
auto canType = getCanonicalTypeInContext(type, { })->getCanonicalType();
466-
assert(canType->isTypeParameter());
452+
assert(type->isTypeParameter());
453+
454+
auto mutTerm = Context.getMutableTermForType(type->getCanonicalType(),
455+
/*proto=*/nullptr);
456+
System.simplify(mutTerm);
457+
verify(mutTerm);
458+
459+
#ifndef NDEBUG
460+
auto *props = Map.lookUpProperties(mutTerm);
461+
assert(props &&
462+
"Subject type of conformance access path should be known");
463+
assert(!props->isConcreteType() &&
464+
"Concrete types do not have conformance access paths");
465+
auto conformsTo = props->getConformsTo();
466+
assert(std::find(conformsTo.begin(), conformsTo.end(), protocol) &&
467+
"Subject type of conformance access path must conform to protocol");
468+
#endif
469+
470+
auto term = Term::get(mutTerm, Context);
467471

468472
// Check if we've already cached the result before doing anything else.
469473
auto found = ConformanceAccessPaths.find(
470-
std::make_pair(canType, protocol));
474+
std::make_pair(term, protocol));
471475
if (found != ConformanceAccessPaths.end()) {
472476
return found->second;
473477
}
@@ -476,13 +480,13 @@ RequirementMachine::getConformanceAccessPath(Type type,
476480

477481
FrontendStatsTracer tracer(Stats, "get-conformance-access-path");
478482

479-
auto recordPath = [&](CanType type, ProtocolDecl *proto,
483+
auto recordPath = [&](Term term, ProtocolDecl *proto,
480484
ConformanceAccessPath path) {
481485
// Add the path to the buffer.
482-
CurrentConformanceAccessPaths.emplace_back(type, path);
486+
CurrentConformanceAccessPaths.emplace_back(term, path);
483487

484488
// Add the path to the map.
485-
auto key = std::make_pair(type, proto);
489+
auto key = std::make_pair(term, proto);
486490
auto inserted = ConformanceAccessPaths.insert(
487491
std::make_pair(key, path));
488492
assert(inserted.second);
@@ -508,22 +512,27 @@ RequirementMachine::getConformanceAccessPath(Type type,
508512
ArrayRef<ConformanceAccessPath::Entry> path(root);
509513
ConformanceAccessPath result(ctx.AllocateCopy(path));
510514

511-
recordPath(rootType, rootProto, result);
515+
auto mutTerm = Context.getMutableTermForType(rootType, nullptr);
516+
System.simplify(mutTerm);
517+
518+
auto rootTerm = Term::get(mutTerm, Context);
519+
recordPath(rootTerm, rootProto, result);
512520
}
513521
}
514522

515523
// We enumerate conformance access paths in shortlex order until we find the
516524
// path whose corresponding type canonicalizes to the one we are looking for.
517525
while (true) {
518526
auto found = ConformanceAccessPaths.find(
519-
std::make_pair(canType, protocol));
527+
std::make_pair(term, protocol));
520528
if (found != ConformanceAccessPaths.end()) {
521529
return found->second;
522530
}
523531

524532
if (CurrentConformanceAccessPaths.empty()) {
525533
llvm::errs() << "Failed to find conformance access path for ";
526-
llvm::errs() << type << " " << protocol->getName() << ":\n";
534+
llvm::errs() << type << " (" << term << ")" << " : ";
535+
llvm::errs() << protocol->getName() << ":\n";
527536
type.dump(llvm::errs());
528537
llvm::errs() << "\n";
529538
dump(llvm::errs());
@@ -533,7 +542,7 @@ RequirementMachine::getConformanceAccessPath(Type type,
533542
// The buffer consists of all conformance access paths of length N.
534543
// Swap it out with an empty buffer, and fill it with all paths of
535544
// length N+1.
536-
std::vector<std::pair<CanType, ConformanceAccessPath>> oldPaths;
545+
std::vector<std::pair<Term, ConformanceAccessPath>> oldPaths;
537546
std::swap(CurrentConformanceAccessPaths, oldPaths);
538547

539548
for (const auto &pair : oldPaths) {
@@ -551,21 +560,19 @@ RequirementMachine::getConformanceAccessPath(Type type,
551560
auto nextSubjectType = req.getFirstType()->getCanonicalType();
552561
auto *nextProto = req.getProtocolDecl();
553562

554-
// Compute the canonical anchor for this conformance requirement.
555-
auto nextType = replaceSelfWithType(pair.first, nextSubjectType);
556-
auto nextCanType = getCanonicalTypeInContext(nextType, { })
557-
->getCanonicalType();
563+
MutableTerm mutTerm(pair.first);
564+
mutTerm.append(Context.getMutableTermForType(nextSubjectType,
565+
/*proto=*/lastProto));
566+
System.simplify(mutTerm);
558567

559-
// Skip "derived via concrete" sources.
560-
if (!nextCanType->isTypeParameter())
561-
continue;
568+
auto nextTerm = Term::get(mutTerm, Context);
562569

563570
// If we've already seen a path for this conformance, skip it and
564571
// don't add it to the buffer. Note that because we iterate over
565572
// conformance access paths in shortlex order, the existing
566573
// conformance access path is shorter than the one we found just now.
567574
if (ConformanceAccessPaths.count(
568-
std::make_pair(nextCanType, nextProto)))
575+
std::make_pair(nextTerm, nextProto)))
569576
continue;
570577

571578
if (entries.empty()) {
@@ -580,7 +587,7 @@ RequirementMachine::getConformanceAccessPath(Type type,
580587
ConformanceAccessPath result = ctx.AllocateCopy(entries);
581588
entries.pop_back();
582589

583-
recordPath(nextCanType, nextProto, result);
590+
recordPath(nextTerm, nextProto, result);
584591
}
585592
}
586593
}

lib/AST/RequirementMachine/RequirementMachine.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ class RequirementMachine final {
6161
UnifiedStatsReporter *Stats;
6262

6363
/// All conformance access paths computed so far.
64-
llvm::DenseMap<std::pair<CanType, ProtocolDecl *>,
64+
llvm::DenseMap<std::pair<Term, ProtocolDecl *>,
6565
ConformanceAccessPath> ConformanceAccessPaths;
6666

6767
/// Conformance access paths computed during the last round. All elements
6868
/// have the same length. If a conformance access path of greater length
6969
/// is requested, we refill CurrentConformanceAccessPaths with all paths of
7070
/// length N+1, and add them to the ConformanceAccessPaths map.
71-
std::vector<std::pair<CanType, ConformanceAccessPath>>
71+
std::vector<std::pair<Term, ConformanceAccessPath>>
7272
CurrentConformanceAccessPaths;
7373

7474
explicit RequirementMachine(RewriteContext &rewriteCtx);

lib/AST/RequirementMachine/Term.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ class Term final {
6565
return Ptr;
6666
}
6767

68+
static Term fromOpaquePointer(void *ptr) {
69+
return Term((Storage *) ptr);
70+
}
71+
6872
static Term get(const MutableTerm &term, RewriteContext &ctx);
6973

7074
ArrayRef<const ProtocolDecl *> getRootProtocols() const {
@@ -203,4 +207,24 @@ class MutableTerm final {
203207

204208
} // end namespace swift
205209

210+
namespace llvm {
211+
template<> struct DenseMapInfo<swift::rewriting::Term> {
212+
static swift::rewriting::Term getEmptyKey() {
213+
return swift::rewriting::Term::fromOpaquePointer(
214+
llvm::DenseMapInfo<void *>::getEmptyKey());
215+
}
216+
static swift::rewriting::Term getTombstoneKey() {
217+
return swift::rewriting::Term::fromOpaquePointer(
218+
llvm::DenseMapInfo<void *>::getTombstoneKey());
219+
}
220+
static unsigned getHashValue(swift::rewriting::Term Val) {
221+
return DenseMapInfo<void *>::getHashValue(Val.getOpaquePointer());
222+
}
223+
static bool isEqual(swift::rewriting::Term LHS,
224+
swift::rewriting::Term RHS) {
225+
return LHS == RHS;
226+
}
227+
};
228+
} // end namespace llvm
229+
206230
#endif

lib/IRGen/GenProto.cpp

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2599,14 +2599,7 @@ MetadataResponse MetadataPath::followComponent(IRGenFunction &IGF,
25992599

26002600
CanType associatedType =
26012601
sourceConformance.getAssociatedType(sourceType, association)
2602-
->getCanonicalType();
2603-
if (sourceConformance.isConcrete() &&
2604-
isa<NormalProtocolConformance>(sourceConformance.getConcrete())) {
2605-
associatedType =
2606-
sourceConformance.getConcrete()->getDeclContext()
2607-
->mapTypeIntoContext(associatedType)
2608-
->getCanonicalType();
2609-
}
2602+
->getCanonicalType();
26102603
sourceKey.Type = associatedType;
26112604

26122605
auto associatedConformance =
@@ -2621,10 +2614,77 @@ MetadataResponse MetadataPath::followComponent(IRGenFunction &IGF,
26212614

26222615
if (!source) return MetadataResponse();
26232616

2624-
auto sourceMetadata =
2625-
IGF.emitAbstractTypeMetadataRef(sourceType);
2626-
auto associatedMetadata =
2627-
IGF.emitAbstractTypeMetadataRef(sourceKey.Type);
2617+
auto *sourceMetadata =
2618+
IGF.emitAbstractTypeMetadataRef(sourceType);
2619+
2620+
// Try to avoid circularity when realizing the associated metadata.
2621+
//
2622+
// Suppose we are asked to produce the witness table for some
2623+
// conformance access path (T : P)(Self.X : Q)(Self.Y : R).
2624+
//
2625+
// The associated conformance accessor for (Self.Y : R) takes the
2626+
// metadata for T.X and T.X : Q as arguments. If T.X is concrete,
2627+
// there are two ways of building it:
2628+
//
2629+
// a) Using the knowledge of the concrete type to build it directly,
2630+
// by first constructing the type metadata for its generic arguments.
2631+
//
2632+
// b) Obtaining T.X from the witness table for T : P. This will also
2633+
// construct the generic type, but from the generic environment
2634+
// of the concrete type of T, and not the abstract environment of
2635+
// our conformance access path.
2636+
//
2637+
// Now, say that T.X == Foo<T.X.Y>, with "Foo<A> where A : R".
2638+
//
2639+
// If approach a) is taken, then constructing Foo<T.X.Y> requires
2640+
// recovering the conformance T.X.Y : R, which recursively evaluates
2641+
// the same conformance access path, eventually causing a stack
2642+
// overflow.
2643+
//
2644+
// With approach b) on the other hand, the type metadata for
2645+
// Foo<T.X.Y> is constructed from the concrete type metadata for T,
2646+
// which must provide some other conformance access path for the
2647+
// conformance to R.
2648+
//
2649+
// This is not very principled, and a remaining issue is with conformance
2650+
// requirements where the subject type consists of multiple terms.
2651+
//
2652+
// A better approach would be for conformance access paths to directly
2653+
// record how type metadata at each intermediate step is constructed.
2654+
llvm::Value *associatedMetadata = nullptr;
2655+
2656+
if (auto response = IGF.tryGetLocalTypeMetadata(associatedType,
2657+
MetadataState::Abstract)) {
2658+
// The associated type metadata was already cached, so we're fine.
2659+
associatedMetadata = response.getMetadata();
2660+
} else {
2661+
// If the associated type is concrete and the parent type is an archetype,
2662+
// it is better to realize the associated type metadata from the witness
2663+
// table of the parent's conformance, instead of realizing the concrete
2664+
// type directly.
2665+
auto depMemType = cast<DependentMemberType>(association);
2666+
CanType baseSubstType =
2667+
sourceConformance.getAssociatedType(sourceType, depMemType.getBase())
2668+
->getCanonicalType();
2669+
if (auto archetypeType = cast<ArchetypeType>(baseSubstType)) {
2670+
AssociatedType baseAssocType(depMemType->getAssocType());
2671+
2672+
MetadataResponse response =
2673+
emitAssociatedTypeMetadataRef(IGF, archetypeType, baseAssocType,
2674+
MetadataState::Abstract);
2675+
2676+
// Cache this response in case we have to realize the associated type
2677+
// again later.
2678+
IGF.setScopedLocalTypeMetadata(associatedType, response);
2679+
2680+
associatedMetadata = response.getMetadata();
2681+
} else {
2682+
// Ok, fall back to realizing the (possibly concrete) type.
2683+
associatedMetadata =
2684+
IGF.emitAbstractTypeMetadataRef(sourceKey.Type);
2685+
}
2686+
}
2687+
26282688
auto sourceWTable = source.getMetadata();
26292689

26302690
AssociatedConformance associatedConformanceRef(sourceProtocol,

test/Generics/rdar83687967.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// RUN: %target-typecheck-verify-swift
2+
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures %s 2>&1 | %FileCheck %s
3+
// RUN: %target-swift-frontend -emit-ir %s
4+
5+
public protocol P1 {}
6+
7+
public protocol P2 {
8+
associatedtype A: P1
9+
}
10+
11+
public protocol P3 {
12+
associatedtype B: P2
13+
associatedtype A where B.A == A
14+
}
15+
16+
public struct G<A: P1>: P2 {}
17+
18+
public func callee<T: P1>(_: T.Type) {}
19+
20+
// CHECK: rdar83687967.(file).caller11@
21+
// CHECK: Generic signature: <Child where Child : P3, Child.B == G<Child.A>>
22+
public func caller11<Child: P3>(_: Child)
23+
where Child.B == G<Child.A> {
24+
callee(Child.A.self)
25+
}
26+
27+
// CHECK: rdar83687967.(file).caller12@
28+
// CHECK: Generic signature: <Child where Child : P3, Child.B == G<Child.A>>
29+
public func caller12<Child: P3>(_: Child)
30+
// expected-note@-1 {{conformance constraint 'Child.A' : 'P1' implied here}}
31+
where Child.B == G<Child.A>, Child.A : P1 {
32+
// expected-warning@-1 {{redundant conformance constraint 'Child.A' : 'P1'}}
33+
34+
// Make sure IRGen can evaluate the conformance access path
35+
// (Child : P3)(Self.B : P2)(Self.A : P1).
36+
callee(Child.A.self)
37+
}
38+
39+
// CHECK: rdar83687967.(file).X1@
40+
// CHECK: Requirement signature: <Self where Self.Child : P3, Self.Child.B == G<Self.Child.A>>
41+
public protocol X1 {
42+
associatedtype Child: P3
43+
where Child.B == G<Child.A>
44+
}
45+
46+
// CHECK: rdar83687967.(file).X2@
47+
// CHECK: Requirement signature: <Self where Self.Child : P3, Self.Child.B == G<Self.Child.A>>
48+
49+
public protocol X2 {
50+
associatedtype Child: P3
51+
// expected-note@-1 {{conformance constraint 'Self.Child.A' : 'P1' implied here}}
52+
where Child.B == G<Child.A>, Child.A : P1
53+
// expected-warning@-1 {{redundant conformance constraint 'Self.Child.A' : 'P1'}}
54+
}
55+
56+
public func caller21<T : X1>(_: T) {
57+
// Make sure IRGen can evaluate the conformance access path
58+
// (T : X1)(Child : P3)(Self.B : P2)(Self.A : P1).
59+
callee(T.Child.A.self)
60+
}
61+
62+
public func caller22<T : X2>(_: T) {
63+
// Make sure IRGen can evaluate the conformance access path
64+
// (T : X2)(Child : P3)(Self.B : P2)(Self.A : P1).
65+
callee(T.Child.A.self)
66+
}

test/IRGen/associated_types.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,17 @@ func testFastRuncible<T: Runcible, U: FastRuncible>(_ t: T, u: U)
7575
// 1. Get the type metadata for U.RuncerType.Runcee.
7676
// 1a. Get the type metadata for U.RuncerType.
7777
// Note that we actually look things up in T, which is going to prove unfortunate.
78-
// CHECK: [[T2:%.*]] = call swiftcc %swift.metadata_response @swift_getAssociatedTypeWitness([[INT]] 255, i8** %T.Runcible, %swift.type* %T, %swift.protocol_requirement* getelementptr{{.*}}i32 12), i32 -1), %swift.protocol_requirement* getelementptr inbounds (<{{.*}}>, <{{.*}}>* @"$s16associated_types8RuncibleMp", i32 0, i32 14)) [[NOUNWIND_READNONE:#.*]]
79-
// CHECK-NEXT: %T.RuncerType = extractvalue %swift.metadata_response [[T2]], 0
78+
// CHECK: [[T2:%.*]] = call swiftcc %swift.metadata_response @swift_getAssociatedTypeWitness([[INT]] 255, i8** %U.FastRuncible, %swift.type* %U, %swift.protocol_requirement* getelementptr{{.*}}i32 9), i32 -1), %swift.protocol_requirement* getelementptr inbounds (<{{.*}}>, <{{.*}}>* @"$s16associated_types12FastRuncibleMp", i32 0, i32 10)) [[NOUNWIND_READNONE:#.*]]
79+
// CHECK-NEXT: [[T3:%.*]] = extractvalue %swift.metadata_response [[T2]], 0
8080
// CHECK-NEXT: extractvalue %swift.metadata_response [[T2]], 1
8181
// 2. Get the witness table for U.RuncerType.Runcee : Speedy
8282
// 2a. Get the protocol witness table for U.RuncerType : FastRuncer.
83-
// CHECK-NEXT: %T.RuncerType.FastRuncer = call swiftcc i8** @swift_getAssociatedConformanceWitness(i8** %U.FastRuncible, %swift.type* %U, %swift.type* %T.RuncerType
83+
// CHECK-NEXT: %T.RuncerType.FastRuncer = call swiftcc i8** @swift_getAssociatedConformanceWitness(i8** %U.FastRuncible, %swift.type* %U, %swift.type* [[T3]]
8484
// 1c. Get the type metadata for U.RuncerType.Runcee.
85-
// CHECK-NEXT: [[T2:%.*]] = call swiftcc %swift.metadata_response @swift_getAssociatedTypeWitness([[INT]] 0, i8** %T.RuncerType.FastRuncer, %swift.type* %T.RuncerType, {{.*}}, %swift.protocol_requirement* getelementptr inbounds (<{{.*}}>, <{{.*}}>* @"$s16associated_types10FastRuncerMp", i32 0, i32 10))
85+
// CHECK-NEXT: [[T2:%.*]] = call swiftcc %swift.metadata_response @swift_getAssociatedTypeWitness([[INT]] 0, i8** %T.RuncerType.FastRuncer, %swift.type* [[T3]], {{.*}}, %swift.protocol_requirement* getelementptr inbounds (<{{.*}}>, <{{.*}}>* @"$s16associated_types10FastRuncerMp", i32 0, i32 10))
8686
// CHECK-NEXT: %T.RuncerType.Runcee = extractvalue %swift.metadata_response [[T2]], 0
8787
// 2b. Get the witness table for U.RuncerType.Runcee : Speedy.
88-
// CHECK-NEXT: %T.RuncerType.Runcee.Speedy = call swiftcc i8** @swift_getAssociatedConformanceWitness(i8** %T.RuncerType.FastRuncer, %swift.type* %T.RuncerType, %swift.type* %T.RuncerType.Runcee
88+
// CHECK-NEXT: %T.RuncerType.Runcee.Speedy = call swiftcc i8** @swift_getAssociatedConformanceWitness(i8** %T.RuncerType.FastRuncer, %swift.type* [[T3]], %swift.type* %T.RuncerType.Runcee
8989
// 3. Perform the actual call.
9090
// CHECK-NEXT: [[T0_GEP:%.*]] = getelementptr inbounds i8*, i8** %T.RuncerType.Runcee.Speedy, i32 1
9191
// CHECK-NEXT: [[T0:%.*]] = load i8*, i8** [[T0_GEP]]

0 commit comments

Comments
 (0)