Skip to content

Commit 08d902f

Browse files
authored
Merge pull request #39918 from slavapestov/rqm-identity-conformance
RequirementMachine: Improved handling of "identity conformances" [P].[P] => [P]
2 parents e51c88d + 5067bfe commit 08d902f

26 files changed

+1266
-610
lines changed

lib/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ add_swift_host_library(swiftAST STATIC
8282
RequirementMachine/RequirementMachine.cpp
8383
RequirementMachine/RequirementMachineRequests.cpp
8484
RequirementMachine/RewriteContext.cpp
85+
RequirementMachine/RewriteLoop.cpp
8586
RequirementMachine/RewriteSystem.cpp
8687
RequirementMachine/RewriteSystemCompletion.cpp
8788
RequirementMachine/Symbol.cpp

lib/AST/GenericSignature.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1483,7 +1483,11 @@ int Requirement::compare(const Requirement &other) const {
14831483
// We should only have multiple conformance requirements.
14841484
assert(getKind() == RequirementKind::Conformance);
14851485

1486-
return TypeDecl::compare(getProtocolDecl(), other.getProtocolDecl());
1486+
int compareProtos =
1487+
TypeDecl::compare(getProtocolDecl(), other.getProtocolDecl());
1488+
assert(compareProtos != 0 && "Duplicate conformance requirements");
1489+
1490+
return compareProtos;
14871491
}
14881492

14891493
/// Compare two associated types.

lib/AST/RequirementMachine/GeneratingConformances.cpp

Lines changed: 117 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
// they are added by completion, or they are redundant rules written by the
3131
// user.
3232
//
33-
// Using the 3-cells that generate the homotopy relation on rewrite paths,
33+
// Using the rewrite loops that generate the homotopy relation on rewrite paths,
3434
// decompositions can be found for all "derived" conformance rules, producing
3535
// a minimal set of generating conformances.
3636
//
@@ -69,63 +69,76 @@
6969
using namespace swift;
7070
using namespace rewriting;
7171

72-
/// Finds all protocol conformance rules appearing in a 3-cell, both without
73-
/// context, and with a non-empty left context. Applications of rules with a
74-
/// non-empty right context are ignored.
72+
/// Finds all protocol conformance rules appearing in a rewrite loop, both
73+
/// in empty context, and with a non-empty left context. Applications of rules
74+
/// with a non-empty right context are ignored.
7575
///
7676
/// The rules are organized by protocol. For each protocol, the first element
7777
/// of the pair stores conformance rules that appear without context. The
7878
/// second element of the pair stores rules that appear with non-empty left
7979
/// context. For each such rule, the left prefix is also stored alongside.
80-
void HomotopyGenerator::findProtocolConformanceRules(
80+
void RewriteLoop::findProtocolConformanceRules(
8181
llvm::SmallDenseMap<const ProtocolDecl *,
82-
std::pair<SmallVector<unsigned, 2>,
83-
SmallVector<std::pair<MutableTerm, unsigned>, 2>>>
84-
&result,
82+
ProtocolConformanceRules, 2> &result,
8583
const RewriteSystem &system) const {
8684

87-
auto redundantRules = Path.findRulesAppearingOnceInEmptyContext();
85+
auto redundantRules = findRulesAppearingOnceInEmptyContext(system);
8886

8987
bool foundAny = false;
9088
for (unsigned ruleID : redundantRules) {
9189
const auto &rule = system.getRule(ruleID);
90+
9291
if (auto *proto = rule.isProtocolConformanceRule()) {
93-
result[proto].first.push_back(ruleID);
92+
if (rule.isIdentityConformanceRule()) {
93+
result[proto].SawIdentityConformance = true;
94+
continue;
95+
}
96+
97+
result[proto].RulesInEmptyContext.push_back(ruleID);
9498
foundAny = true;
9599
}
96100
}
97101

98102
if (!foundAny)
99103
return;
100104

101-
MutableTerm term = Basepoint;
105+
RewritePathEvaluator evaluator(Basepoint);
102106

103107
// Now look for rewrite steps with conformance rules in empty right context,
104-
// that is something like X.(Y.[P] => Z) (or it's inverse, X.(Z => Y.[P])).
108+
// that is something like X.(Y.[P] => Y) (or it's inverse, X.(Y => Y.[P])).
105109
for (const auto &step : Path) {
106-
switch (step.Kind) {
107-
case RewriteStep::ApplyRewriteRule: {
108-
const auto &rule = system.getRule(step.RuleID);
109-
if (auto *proto = rule.isProtocolConformanceRule()) {
110-
if (step.StartOffset > 0 &&
111-
step.EndOffset == 0) {
112-
// Record the prefix term that is left unchanged by this rewrite step.
113-
//
114-
// In the above example where the rewrite step is X.(Y.[P] => Z),
115-
// the prefix term is 'X'.
116-
MutableTerm prefix(term.begin(), term.begin() + step.StartOffset);
117-
result[proto].second.emplace_back(prefix, step.RuleID);
110+
if (!evaluator.isInContext()) {
111+
switch (step.Kind) {
112+
case RewriteStep::ApplyRewriteRule: {
113+
const auto &rule = system.getRule(step.RuleID);
114+
115+
if (rule.isIdentityConformanceRule())
116+
break;
117+
118+
if (auto *proto = rule.isProtocolConformanceRule()) {
119+
if (step.StartOffset > 0 &&
120+
step.EndOffset == 0) {
121+
// Record the prefix term that is left unchanged by this rewrite step.
122+
//
123+
// In the above example where the rewrite step is X.(Y.[P] => Z),
124+
// the prefix term is 'X'.
125+
const auto &term = evaluator.getCurrentTerm();
126+
MutableTerm prefix(term.begin(), term.begin() + step.StartOffset);
127+
result[proto].RulesInContext.emplace_back(prefix, step.RuleID);
128+
}
118129
}
119-
}
120130

121-
break;
122-
}
131+
break;
132+
}
123133

124-
case RewriteStep::AdjustConcreteType:
125-
break;
134+
case RewriteStep::AdjustConcreteType:
135+
case RewriteStep::Shift:
136+
case RewriteStep::Decompose:
137+
break;
138+
}
126139
}
127140

128-
step.apply(term, system);
141+
step.apply(evaluator, system);
129142
}
130143
}
131144

@@ -152,6 +165,13 @@ RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides(
152165
"Canonical conformance term should simplify in one step");
153166

154167
const auto &step = *steps.begin();
168+
169+
#ifndef NDEBUG
170+
const auto &rule = getRule(step.RuleID);
171+
assert(rule.isProtocolConformanceRule());
172+
assert(!rule.isIdentityConformanceRule());
173+
#endif
174+
155175
assert(step.Kind == RewriteStep::ApplyRewriteRule);
156176
assert(step.EndOffset == 0);
157177
assert(!step.Inverse);
@@ -177,6 +197,7 @@ RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides(
177197
SmallVectorImpl<unsigned> &result) const {
178198
const auto &rule = getRule(ruleID);
179199
assert(rule.isProtocolConformanceRule());
200+
assert(!rule.isIdentityConformanceRule());
180201

181202
// Compute domain(V).
182203
const auto &lhs = rule.getLHS();
@@ -253,14 +274,12 @@ RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides(
253274
void RewriteSystem::computeCandidateConformancePaths(
254275
llvm::MapVector<unsigned,
255276
std::vector<SmallVector<unsigned, 2>>> &conformancePaths) const {
256-
for (const auto &loop : HomotopyGenerators) {
277+
for (const auto &loop : Loops) {
257278
if (loop.isDeleted())
258279
continue;
259280

260281
llvm::SmallDenseMap<const ProtocolDecl *,
261-
std::pair<SmallVector<unsigned, 2>,
262-
SmallVector<std::pair<MutableTerm, unsigned>, 2>>>
263-
result;
282+
ProtocolConformanceRules, 2> result;
264283

265284
loop.findProtocolConformanceRules(result, *this);
266285

@@ -275,21 +294,18 @@ void RewriteSystem::computeCandidateConformancePaths(
275294

276295
for (const auto &pair : result) {
277296
const auto *proto = pair.first;
278-
const auto &notInContext = pair.second.first;
279-
const auto &inContext = pair.second.second;
297+
const auto &inEmptyContext = pair.second.RulesInEmptyContext;
298+
const auto &inContext = pair.second.RulesInContext;
299+
bool sawIdentityConformance = pair.second.SawIdentityConformance;
280300

281301
// No rules appear without context.
282-
if (notInContext.empty())
283-
continue;
284-
285-
// No replacement rules.
286-
if (notInContext.size() == 1 && inContext.empty())
302+
if (inEmptyContext.empty())
287303
continue;
288304

289305
if (Debug.contains(DebugFlags::GeneratingConformances)) {
290306
llvm::dbgs() << "* Protocol " << proto->getName() << ":\n";
291307
llvm::dbgs() << "** Conformance rules not in context:\n";
292-
for (unsigned ruleID : notInContext) {
308+
for (unsigned ruleID : inEmptyContext) {
293309
llvm::dbgs() << "-- (#" << ruleID << ") " << getRule(ruleID) << "\n";
294310
}
295311

@@ -300,6 +316,10 @@ void RewriteSystem::computeCandidateConformancePaths(
300316
llvm::dbgs() << " (#" << ruleID << ") " << getRule(ruleID) << "\n";
301317
}
302318

319+
if (sawIdentityConformance) {
320+
llvm::dbgs() << "** Equivalent to identity conformance\n";
321+
}
322+
303323
llvm::dbgs() << "\n";
304324
}
305325

@@ -310,8 +330,8 @@ void RewriteSystem::computeCandidateConformancePaths(
310330
//
311331
// (T.[P] => T) := (T'.[P])
312332
// (T'.[P] => T') := (T.[P])
313-
for (unsigned candidateRuleID : notInContext) {
314-
for (unsigned otherRuleID : notInContext) {
333+
for (unsigned candidateRuleID : inEmptyContext) {
334+
for (unsigned otherRuleID : inEmptyContext) {
315335
if (otherRuleID == candidateRuleID)
316336
continue;
317337

@@ -321,11 +341,22 @@ void RewriteSystem::computeCandidateConformancePaths(
321341
}
322342
}
323343

324-
// Suppose a 3-cell contains a conformance rule (T.[P] => T) in an empty
325-
// context, and a conformance rule (V.[P] => V) with a non-empty left
344+
// If a rewrite loop contains a conformance rule (T.[P] => T) together
345+
// with the identity conformance ([P].[P] => [P]), both in empty context,
346+
// the conformance rule (T.[P] => T) is equivalent to the *empty product*
347+
// of conformance rules; that is, it is trivially redundant.
348+
if (sawIdentityConformance) {
349+
for (unsigned candidateRuleID : inEmptyContext) {
350+
SmallVector<unsigned, 2> emptyPath;
351+
conformancePaths[candidateRuleID].push_back(emptyPath);
352+
}
353+
}
354+
355+
// Suppose a rewrite loop contains a conformance rule (T.[P] => T) in
356+
// empty context, and a conformance rule (V.[P] => V) in non-empty left
326357
// context U.
327358
//
328-
// The 3-cell looks something like this:
359+
// The rewrite loop looks something like this:
329360
//
330361
// ... ⊗ (T.[P] => T) ⊗ ... ⊗ U.(V => V.[P]) ⊗ ...
331362
// ^ ^
@@ -360,7 +391,7 @@ void RewriteSystem::computeCandidateConformancePaths(
360391

361392
// This decomposition defines a conformance access path for each
362393
// conformance rule we saw in empty context.
363-
for (unsigned otherRuleID : notInContext)
394+
for (unsigned otherRuleID : inEmptyContext)
364395
conformancePaths[otherRuleID].push_back(conformancePath);
365396
}
366397
}
@@ -449,6 +480,11 @@ bool RewriteSystem::isValidRefinementPath(
449480
void RewriteSystem::dumpConformancePath(
450481
llvm::raw_ostream &out,
451482
const SmallVectorImpl<unsigned> &path) const {
483+
if (path.empty()) {
484+
out << "1";
485+
return;
486+
}
487+
452488
for (unsigned ruleID : path)
453489
out << "(" << getRule(ruleID).getLHS() << ")";
454490
}
@@ -483,6 +519,9 @@ void RewriteSystem::verifyGeneratingConformanceEquations(
483519
(void) simplify(baseTerm);
484520

485521
for (const auto &path : pair.second) {
522+
if (path.empty())
523+
continue;
524+
486525
const auto &otherRule = getRule(path.back());
487526
auto *otherProto = otherRule.getLHS().back().getProtocol();
488527

@@ -568,6 +607,9 @@ static const ProtocolDecl *getParentConformanceForTerm(Term lhs) {
568607
/// conformance rules.
569608
void RewriteSystem::computeGeneratingConformances(
570609
llvm::DenseSet<unsigned> &redundantConformances) {
610+
// All conformance rules, sorted by left hand side.
611+
SmallVector<std::pair<unsigned, Term>, 4> conformanceRules;
612+
571613
// Maps a conformance rule to a conformance path deriving the subject type's
572614
// base type. For example, consider the following conformance rule:
573615
//
@@ -586,27 +628,33 @@ void RewriteSystem::computeGeneratingConformances(
586628
// the form [P].[Q] => [P].
587629
llvm::DenseSet<unsigned> protocolRefinements;
588630

589-
// Prepare the initial set of equations: every non-redundant conformance rule
590-
// can be expressed as itself.
631+
// Prepare the initial set of equations.
591632
for (unsigned ruleID : indices(Rules)) {
592633
const auto &rule = getRule(ruleID);
634+
if (rule.isPermanent())
635+
continue;
636+
593637
if (rule.isRedundant())
594638
continue;
595639

596640
if (!rule.isProtocolConformanceRule())
597641
continue;
598642

643+
auto lhs = rule.getLHS();
644+
conformanceRules.emplace_back(ruleID, lhs);
645+
646+
// Initially, every non-redundant conformance rule can be expressed
647+
// as itself.
599648
SmallVector<unsigned, 2> path;
600649
path.push_back(ruleID);
601650
conformancePaths[ruleID].push_back(path);
602651

652+
// Save protocol refinement relations in a side table.
603653
if (rule.isProtocolRefinementRule()) {
604654
protocolRefinements.insert(ruleID);
605655
continue;
606656
}
607657

608-
auto lhs = rule.getLHS();
609-
610658
// Record a parent path if the subject type itself requires a non-trivial
611659
// conformance path to derive.
612660
if (auto *parentProto = getParentConformanceForTerm(lhs)) {
@@ -645,22 +693,33 @@ void RewriteSystem::computeGeneratingConformances(
645693

646694
verifyGeneratingConformanceEquations(conformancePaths);
647695

696+
// Sort the list of conformance rules in reverse order; we're going to try
697+
// to minimize away less canonical rules first.
698+
std::sort(conformanceRules.begin(), conformanceRules.end(),
699+
[&](const std::pair<unsigned, Term> &lhs,
700+
const std::pair<unsigned, Term> &rhs) -> bool {
701+
return lhs.second.compare(rhs.second, Context) > 0;
702+
});
703+
648704
// Find a minimal set of generating conformances.
649-
for (const auto &pair : conformancePaths) {
650-
bool isProtocolRefinement = protocolRefinements.count(pair.first) > 0;
705+
for (const auto &pair : conformanceRules) {
706+
unsigned ruleID = pair.first;
707+
const auto &paths = conformancePaths[ruleID];
651708

652-
for (const auto &path : pair.second) {
709+
bool isProtocolRefinement = protocolRefinements.count(ruleID) > 0;
710+
711+
for (const auto &path : paths) {
653712
// Only consider a protocol refinement rule to be redundant if it is
654713
// witnessed by a composition of other protocol refinement rules.
655714
if (isProtocolRefinement && !isValidRefinementPath(path))
656715
continue;
657716

658717
llvm::SmallDenseSet<unsigned, 4> visited;
659-
visited.insert(pair.first);
718+
visited.insert(ruleID);
660719

661720
if (isValidConformancePath(visited, redundantConformances, path,
662721
parentPaths, conformancePaths)) {
663-
redundantConformances.insert(pair.first);
722+
redundantConformances.insert(ruleID);
664723
break;
665724
}
666725
}
@@ -700,4 +759,4 @@ void RewriteSystem::computeGeneratingConformances(
700759
llvm::dbgs() << "- " << getRule(pair.first) << "\n";
701760
}
702761
}
703-
}
762+
}

0 commit comments

Comments
 (0)