Skip to content

Commit 3ee99d6

Browse files
committed
RequirementMachine: NFC cleanups for generating conformances
1 parent 286e91d commit 3ee99d6

File tree

2 files changed

+102
-59
lines changed

2 files changed

+102
-59
lines changed

lib/AST/RequirementMachine/GeneratingConformances.cpp

Lines changed: 101 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@
3434
// decompositions can be found for all "derived" conformance rules, producing
3535
// a minimal set of generating conformances.
3636
//
37+
// There are two small complications to handle implementation details of
38+
// Swift generics:
39+
//
40+
// 1) Inherited witness tables must be derivable by following other protocol
41+
// refinement requirements only, without looking at non-Self associated
42+
// types. This is expressed by saying that the generating conformance
43+
// equations for a protocol refinement can only be written in terms of
44+
// other protocol refinements; conformance paths involving non-Self
45+
// associated types are not considered.
46+
//
47+
// 2) The subject type of each conformance requirement must be derivable at
48+
// runtime as well, so for each generating conformance, it must be
49+
// possible to write down a conformance path for the parent type without
50+
// using any generating conformance recursively in the parent path of
51+
// itself.
52+
//
53+
// The generating conformances finds fewer conformance requirements to be
54+
// redundant than homotopy reduction, which is why homotopy reduction only
55+
// deletes non-protocol conformance requirements.
56+
//
3757
//===----------------------------------------------------------------------===//
3858

3959
#include "swift/AST/Decl.h"
@@ -283,23 +303,48 @@ void RewriteSystem::computeCandidateConformancePaths(
283303
llvm::dbgs() << "\n";
284304
}
285305

306+
// Two conformance rules in empty context (T.[P] => T) and (T'.[P] => T)
307+
// are interchangeable, and contribute a trivial pair of conformance
308+
// equations expressing that each one can be written in terms of the
309+
// other:
310+
//
311+
// (T.[P] => T) := (T'.[P])
312+
// (T'.[P] => T') := (T.[P])
313+
for (unsigned candidateRuleID : notInContext) {
314+
for (unsigned otherRuleID : notInContext) {
315+
if (otherRuleID == candidateRuleID)
316+
continue;
317+
318+
SmallVector<unsigned, 2> path;
319+
path.push_back(otherRuleID);
320+
conformancePaths[candidateRuleID].push_back(path);
321+
}
322+
}
323+
286324
// Suppose a 3-cell contains a conformance rule (T.[P] => T) in an empty
287-
// context, and a conformance rule (V.[P] => V) with a possibly non-empty
288-
// left context U and empty right context.
325+
// context, and a conformance rule (V.[P] => V) with a non-empty left
326+
// context U.
327+
//
328+
// The 3-cell looks something like this:
329+
//
330+
// ... ⊗ (T.[P] => T) ⊗ ... ⊗ U.(V => V.[P]) ⊗ ...
331+
// ^ ^
332+
// | |
333+
// + basepoint ========================= basepoint +
289334
//
290335
// We can decompose U into a product of conformance rules:
291336
//
292337
// (V1.[P1] => V1)...(Vn.[Pn] => Vn),
293338
//
339+
// Note that (V1)...(Vn) is canonically equivalent to U.
340+
//
294341
// Now, we can record a candidate decomposition of (T.[P] => T) as a
295342
// product of conformance rules:
296343
//
297344
// (T.[P] => T) := (V1.[P1] => V1)...(Vn.[Pn] => Vn).(V.[P] => V)
298345
//
299-
// Now if U is empty, this becomes the trivial candidate:
300-
//
301-
// (T.[P] => T) := (V.[P] => V)
302-
SmallVector<SmallVector<unsigned, 2>, 2> candidatePaths;
346+
// Again, note that (V1)...(Vn).V is canonically equivalent to U.V,
347+
// and therefore T.
303348
for (auto pair : inContext) {
304349
// We have a term U, and a rule V.[P] => V.
305350
SmallVector<unsigned, 2> conformancePath;
@@ -313,26 +358,10 @@ void RewriteSystem::computeCandidateConformancePaths(
313358
decomposeTermIntoConformanceRuleLeftHandSides(term, pair.second,
314359
conformancePath);
315360

316-
candidatePaths.push_back(conformancePath);
317-
}
318-
319-
for (unsigned candidateRuleID : notInContext) {
320-
// If multiple conformance rules appear in an empty context, each one
321-
// can be replaced with any other conformance rule.
322-
for (unsigned otherRuleID : notInContext) {
323-
if (otherRuleID == candidateRuleID)
324-
continue;
325-
326-
SmallVector<unsigned, 2> path;
327-
path.push_back(otherRuleID);
328-
conformancePaths[candidateRuleID].push_back(path);
329-
}
330-
331-
// If conformance rules appear in non-empty context, they define a
332-
// conformance access path for each conformance rule in empty context.
333-
for (const auto &path : candidatePaths) {
334-
conformancePaths[candidateRuleID].push_back(path);
335-
}
361+
// This decomposition defines a conformance access path for each
362+
// conformance rule we saw in empty context.
363+
for (unsigned otherRuleID : notInContext)
364+
conformancePaths[otherRuleID].push_back(conformancePath);
336365
}
337366
}
338367
}
@@ -492,6 +521,48 @@ void RewriteSystem::verifyGeneratingConformanceEquations(
492521
#endif
493522
}
494523

524+
static const ProtocolDecl *getParentConformanceForTerm(Term lhs) {
525+
// The last element is a protocol symbol, because this is the left hand side
526+
// of a conformance rule.
527+
assert(lhs.back().getKind() == Symbol::Kind::Protocol);
528+
529+
// The second to last symbol is either an associated type, protocol or generic
530+
// parameter symbol.
531+
assert(lhs.size() >= 2);
532+
533+
auto parentSymbol = lhs[lhs.size() - 2];
534+
535+
switch (parentSymbol.getKind()) {
536+
case Symbol::Kind::AssociatedType: {
537+
// In a conformance rule of the form [P:T].[Q] => [P:T], the parent type is
538+
// trivial.
539+
if (lhs.size() == 2)
540+
return nullptr;
541+
542+
// If we have a rule of the form X.[P:Y].[Q] => X.[P:Y] wih non-empty X,
543+
// then the parent type is X.[P].
544+
const auto protos = parentSymbol.getProtocols();
545+
assert(protos.size() == 1);
546+
547+
return protos[0];
548+
}
549+
550+
case Symbol::Kind::GenericParam:
551+
case Symbol::Kind::Protocol:
552+
// The parent type is trivial (either a generic parameter, or the protocol
553+
// 'Self' type).
554+
return nullptr;
555+
556+
case Symbol::Kind::Name:
557+
case Symbol::Kind::Layout:
558+
case Symbol::Kind::Superclass:
559+
case Symbol::Kind::ConcreteType:
560+
break;
561+
}
562+
563+
llvm_unreachable("Bad symbol kind");
564+
}
565+
495566
/// Computes a minimal set of generating conformances, assuming that homotopy
496567
/// reduction has already eliminated all redundant rewrite rules that are not
497568
/// conformance rules.
@@ -536,49 +607,21 @@ void RewriteSystem::computeGeneratingConformances(
536607

537608
auto lhs = rule.getLHS();
538609

539-
auto parentSymbol = lhs[lhs.size() - 2];
540-
541-
// The last element is a protocol symbol, because this is a conformance rule.
542-
// The second to last symbol is either an associated type, protocol or generic
543-
// parameter symbol.
544-
switch (parentSymbol.getKind()) {
545-
case Symbol::Kind::AssociatedType: {
546-
// If we have a rule of the form X.[P:Y].[Q] => X.[P:Y] wih non-empty X,
547-
// then the parent type is X.[P].
548-
if (lhs.size() == 2)
549-
continue;
550-
610+
// Record a parent path if the subject type itself requires a non-trivial
611+
// conformance path to derive.
612+
if (auto *parentProto = getParentConformanceForTerm(lhs)) {
551613
MutableTerm mutTerm(lhs.begin(), lhs.end() - 2);
552614
assert(!mutTerm.empty());
553615

554-
const auto protos = parentSymbol.getProtocols();
555-
assert(protos.size() == 1);
556-
557616
bool simplified = simplify(mutTerm);
558617
assert(!simplified || rule.isSimplified());
559618
(void) simplified;
560619

561-
mutTerm.add(Symbol::forProtocol(protos[0], Context));
620+
mutTerm.add(Symbol::forProtocol(parentProto, Context));
562621

563622
// Get a conformance path for X.[P] and record it.
564623
decomposeTermIntoConformanceRuleLeftHandSides(mutTerm, parentPaths[ruleID]);
565-
continue;
566624
}
567-
568-
case Symbol::Kind::GenericParam:
569-
case Symbol::Kind::Protocol:
570-
// Don't record a parent path, since the parent type is trivial (either a
571-
// generic parameter, or the protocol 'Self' type).
572-
continue;
573-
574-
case Symbol::Kind::Name:
575-
case Symbol::Kind::Layout:
576-
case Symbol::Kind::Superclass:
577-
case Symbol::Kind::ConcreteType:
578-
break;
579-
}
580-
581-
llvm_unreachable("Bad symbol kind");
582625
}
583626

584627
computeCandidateConformancePaths(conformancePaths);

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ void RewriteSystem::performHomotopyReduction(
751751
redundantConformances,
752752
replacementPath);
753753

754-
// If there no redundant rules remain in this pass, stop.
754+
// If no redundant rules remain which can be eliminated by this pass, stop.
755755
if (!optRuleID)
756756
return;
757757

0 commit comments

Comments
 (0)