Skip to content

Commit 48c6529

Browse files
authored
Merge pull request swiftlang#40567 from slavapestov/rqm-abstract-type-witness
RequirementMachine: Implement minimization for abstract nested type requirements
2 parents 88fb8c2 + 82a494e commit 48c6529

19 files changed

+564
-289
lines changed

lib/AST/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ add_swift_host_library(swiftAST STATIC
7474
ProtocolConformance.cpp
7575
RawComment.cpp
7676
RequirementEnvironment.cpp
77-
RequirementMachine/GeneratingConformances.cpp
7877
RequirementMachine/GenericSignatureQueries.cpp
7978
RequirementMachine/HomotopyReduction.cpp
8079
RequirementMachine/KnuthBendix.cpp
80+
RequirementMachine/MinimalConformances.cpp
8181
RequirementMachine/PropertyMap.cpp
8282
RequirementMachine/PropertyUnification.cpp
8383
RequirementMachine/RequirementLowering.cpp

lib/AST/RequirementMachine/Debug.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ enum class DebugFlags : unsigned {
4141
/// Print debug output from the homotopy reduction algorithm.
4242
HomotopyReduction = (1<<6),
4343

44-
/// Print debug output from the generating conformances algorithm.
45-
GeneratingConformances = (1<<7),
44+
/// Print debug output from the minimal conformances algorithm.
45+
MinimalConformances = (1<<7),
4646

4747
/// Print debug output from the protocol dependency graph.
4848
ProtocolDependencies = (1<<8),

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 107 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@
4646
//
4747
// Also, for a conformance rule (V.[P] => V) to be redundant, a stronger
4848
// condition is needed than appearing once in a loop and without context;
49-
// the rule must not be a _generating conformance_. The algorithm for computing
50-
// a minimal set of generating conformances is implemented in
51-
// GeneratingConformances.cpp.
49+
// the rule must not be a _minimal conformance_. The algorithm for computing
50+
// minimal conformances is implemented in MinimalConformances.cpp.
5251
//
5352
//===----------------------------------------------------------------------===//
5453

@@ -66,9 +65,17 @@ using namespace rewriting;
6665

6766
/// A rewrite rule is redundant if it appears exactly once in a loop
6867
/// without context.
69-
llvm::SmallVector<unsigned, 1>
68+
///
69+
/// This method will cache the result; markDirty() must be called after
70+
/// the underlying rewrite path is modified to invalidate the cached
71+
/// result.
72+
ArrayRef<unsigned>
7073
RewriteLoop::findRulesAppearingOnceInEmptyContext(
7174
const RewriteSystem &system) const {
75+
// If we're allowed to use the cached result, return that.
76+
if (!Dirty)
77+
return RulesInEmptyContext;
78+
7279
// Rules appearing in empty context (possibly more than once).
7380
llvm::SmallDenseSet<unsigned, 2> rulesInEmptyContext;
7481

@@ -94,30 +101,35 @@ RewriteLoop::findRulesAppearingOnceInEmptyContext(
94101
case RewriteStep::SuperclassConformance:
95102
case RewriteStep::ConcreteTypeWitness:
96103
case RewriteStep::SameTypeWitness:
104+
case RewriteStep::AbstractTypeWitness:
97105
break;
98106
}
99107

100108
evaluator.apply(step, system);
101109
}
102110

111+
auto *mutThis = const_cast<RewriteLoop *>(this);
112+
mutThis->RulesInEmptyContext.clear();
113+
103114
// Collect all rules that we saw exactly once in empty context.
104-
SmallVector<unsigned, 1> result;
105115
for (auto rule : rulesInEmptyContext) {
106116
auto found = ruleMultiplicity.find(rule);
107117
assert(found != ruleMultiplicity.end());
108118

109119
if (found->second == 1)
110-
result.push_back(rule);
120+
mutThis->RulesInEmptyContext.push_back(rule);
111121
}
112122

113-
return result;
123+
// Cache the result for later.
124+
mutThis->Dirty = 0;
125+
return RulesInEmptyContext;
114126
}
115127

116128
/// If a rewrite loop contains an explicit rule in empty context, propagate the
117129
/// explicit bit to all other rules appearing in empty context within the same
118130
/// loop.
119131
///
120-
/// When computing generating conformances we prefer to eliminate non-explicit
132+
/// When computing minimal conformances we prefer to eliminate non-explicit
121133
/// rules, as a heuristic to ensure that minimized conformance requirements
122134
/// remain in the same protocol as originally written, in cases where they can
123135
/// be moved between protocols.
@@ -145,7 +157,7 @@ RewriteLoop::findRulesAppearingOnceInEmptyContext(
145157
/// explicit bit from the original rule to the canonical rule.
146158
void RewriteSystem::propagateExplicitBits() {
147159
for (const auto &loop : Loops) {
148-
SmallVector<unsigned, 1> rulesInEmptyContext =
160+
auto rulesInEmptyContext =
149161
loop.findRulesAppearingOnceInEmptyContext(*this);
150162

151163
bool sawExplicitRule = false;
@@ -212,6 +224,7 @@ RewritePath RewritePath::splitCycleAtRule(unsigned ruleID) const {
212224
case RewriteStep::SuperclassConformance:
213225
case RewriteStep::ConcreteTypeWitness:
214226
case RewriteStep::SameTypeWitness:
227+
case RewriteStep::AbstractTypeWitness:
215228
break;
216229
}
217230

@@ -312,6 +325,7 @@ bool RewritePath::replaceRuleWithPath(unsigned ruleID,
312325
case RewriteStep::SuperclassConformance:
313326
case RewriteStep::ConcreteTypeWitness:
314327
case RewriteStep::SameTypeWitness:
328+
case RewriteStep::AbstractTypeWitness:
315329
newSteps.push_back(step);
316330
break;
317331
}
@@ -321,56 +335,6 @@ bool RewritePath::replaceRuleWithPath(unsigned ruleID,
321335
return true;
322336
}
323337

324-
/// Check if a rewrite rule is a candidate for deletion in this pass of the
325-
/// minimization algorithm.
326-
bool RewriteSystem::
327-
isCandidateForDeletion(unsigned ruleID,
328-
const llvm::DenseSet<unsigned> *redundantConformances) const {
329-
const auto &rule = getRule(ruleID);
330-
331-
// We should not find a rule that has already been marked redundant
332-
// here; it should have already been replaced with a rewrite path
333-
// in all homotopy generators.
334-
assert(!rule.isRedundant());
335-
336-
// Associated type introduction rules are 'permanent'. They're
337-
// not worth eliminating since they are re-added every time; it
338-
// is better to find other candidates to eliminate in the same
339-
// loop instead.
340-
if (rule.isPermanent())
341-
return false;
342-
343-
// Other rules involving unresolved name symbols are derived from an
344-
// associated type introduction rule together with a conformance rule.
345-
// They are eliminated in the first pass.
346-
if (rule.getLHS().containsUnresolvedSymbols())
347-
return true;
348-
349-
// Protocol conformance rules are eliminated via a different
350-
// algorithm which computes "generating conformances".
351-
//
352-
// The first pass skips protocol conformance rules.
353-
//
354-
// The second pass eliminates any protocol conformance rule which is
355-
// redundant according to both homotopy reduction and the generating
356-
// conformances algorithm.
357-
//
358-
// Later on, we verify that any conformance redundant via generating
359-
// conformances was also redundant via homotopy reduction. This
360-
// means that the set of generating conformances is always a superset
361-
// (or equal to) of the set of minimal protocol conformance
362-
// requirements that homotopy reduction alone would produce.
363-
if (rule.isAnyConformanceRule()) {
364-
if (!redundantConformances)
365-
return false;
366-
367-
if (!redundantConformances->count(ruleID))
368-
return false;
369-
}
370-
371-
return true;
372-
}
373-
374338
/// Find a rule to delete by looking through all loops for rewrite rules appearing
375339
/// once in empty context. Returns a redundant rule to delete if one was found,
376340
/// otherwise returns None.
@@ -380,13 +344,13 @@ isCandidateForDeletion(unsigned ruleID,
380344
/// 1) First, rules that are not conformance rules are deleted, with
381345
/// \p redundantConformances equal to nullptr.
382346
///
383-
/// 2) Second, generating conformances are computed.
347+
/// 2) Second, minimal conformances are computed.
384348
///
385349
/// 3) Finally, redundant conformance rules are deleted, with
386350
/// \p redundantConformances equal to the set of conformance rules that are
387-
/// not generating conformances.
351+
/// not minimal conformances.
388352
Optional<unsigned> RewriteSystem::
389-
findRuleToDelete(const llvm::DenseSet<unsigned> *redundantConformances,
353+
findRuleToDelete(llvm::function_ref<bool(unsigned)> isRedundantRuleFn,
390354
RewritePath &replacementPath) {
391355
SmallVector<std::pair<unsigned, unsigned>, 2> redundancyCandidates;
392356
for (unsigned loopID : indices(Loops)) {
@@ -408,15 +372,28 @@ findRuleToDelete(const llvm::DenseSet<unsigned> *redundantConformances,
408372

409373
for (const auto &pair : redundancyCandidates) {
410374
unsigned ruleID = pair.second;
411-
if (!isCandidateForDeletion(ruleID, redundantConformances))
375+
const auto &rule = getRule(ruleID);
376+
377+
// We should not find a rule that has already been marked redundant
378+
// here; it should have already been replaced with a rewrite path
379+
// in all homotopy generators.
380+
assert(!rule.isRedundant());
381+
382+
// Associated type introduction rules are 'permanent'. They're
383+
// not worth eliminating since they are re-added every time; it
384+
// is better to find other candidates to eliminate in the same
385+
// loop instead.
386+
if (rule.isPermanent())
387+
continue;
388+
389+
if (!isRedundantRuleFn(ruleID))
412390
continue;
413391

414392
if (!found) {
415393
found = pair;
416394
continue;
417395
}
418396

419-
const auto &rule = getRule(ruleID);
420397
const auto &otherRule = getRule(found->second);
421398

422399
// Prefer to delete "less canonical" rules.
@@ -457,8 +434,8 @@ void RewriteSystem::deleteRule(unsigned ruleID,
457434
llvm::dbgs() << "\n";
458435
}
459436

460-
// Replace all occurrences of the rule with the replacement path and
461-
// normalize all loops.
437+
// Replace all occurrences of the rule with the replacement path in
438+
// all remaining rewrite loops.
462439
for (auto &loop : Loops) {
463440
if (loop.isDeleted())
464441
continue;
@@ -467,6 +444,10 @@ void RewriteSystem::deleteRule(unsigned ruleID,
467444
if (!changed)
468445
continue;
469446

447+
// The loop's path has changed, so we must invalidate the cached
448+
// result of findRulesAppearingOnceInEmptyContext().
449+
loop.markDirty();
450+
470451
if (Debug.contains(DebugFlags::HomotopyReduction)) {
471452
llvm::dbgs() << "** Updated loop: ";
472453
loop.dump(llvm::dbgs(), *this);
@@ -476,10 +457,10 @@ void RewriteSystem::deleteRule(unsigned ruleID,
476457
}
477458

478459
void RewriteSystem::performHomotopyReduction(
479-
const llvm::DenseSet<unsigned> *redundantConformances) {
460+
llvm::function_ref<bool(unsigned)> isRedundantRuleFn) {
480461
while (true) {
481462
RewritePath replacementPath;
482-
auto optRuleID = findRuleToDelete(redundantConformances,
463+
auto optRuleID = findRuleToDelete(isRedundantRuleFn,
483464
replacementPath);
484465

485466
// If no redundant rules remain which can be eliminated by this pass, stop.
@@ -505,26 +486,60 @@ void RewriteSystem::minimizeRewriteSystem() {
505486

506487
propagateExplicitBits();
507488

508-
// First pass: Eliminate all redundant rules that are not conformance rules.
509-
performHomotopyReduction(/*redundantConformances=*/nullptr);
489+
// First pass:
490+
// - Eliminate all simplified non-conformance rules.
491+
// - Eliminate all rules with unresolved symbols.
492+
performHomotopyReduction([&](unsigned ruleID) -> bool {
493+
const auto &rule = getRule(ruleID);
494+
495+
if (rule.isSimplified() &&
496+
!rule.isAnyConformanceRule())
497+
return true;
498+
499+
// Other rules involving unresolved name symbols are derived from an
500+
// associated type introduction rule together with a conformance rule.
501+
// They are eliminated in the first pass.
502+
if (rule.getLHS().containsUnresolvedSymbols())
503+
return true;
510504

511-
// Now find a minimal set of generating conformances.
505+
return false;
506+
});
507+
508+
// Now compute a set of minimal conformances.
512509
//
513510
// FIXME: For now this just produces a set of redundant conformances, but
514-
// it should actually output the canonical generating conformance equation
515-
// for each non-generating conformance. We can then use information to
511+
// it should actually output the canonical minimal conformance equation
512+
// for each non-minimal conformance. We can then use information to
516513
// compute conformance access paths, instead of the current "brute force"
517514
// algorithm used for that purpose.
518515
llvm::DenseSet<unsigned> redundantConformances;
519-
computeGeneratingConformances(redundantConformances);
516+
computeMinimalConformances(redundantConformances);
520517

521-
// Second pass: Eliminate all redundant conformance rules.
522-
performHomotopyReduction(/*redundantConformances=*/&redundantConformances);
518+
// Second pass: Eliminate all non-minimal conformance rules.
519+
performHomotopyReduction([&](unsigned ruleID) -> bool {
520+
const auto &rule = getRule(ruleID);
521+
522+
if (rule.isAnyConformanceRule() &&
523+
redundantConformances.count(ruleID))
524+
return true;
525+
526+
return false;
527+
});
528+
529+
// Third pass: Eliminate all other redundant non-conformance rules.
530+
performHomotopyReduction([&](unsigned ruleID) -> bool {
531+
const auto &rule = getRule(ruleID);
532+
533+
if (!rule.isAnyConformanceRule())
534+
return true;
535+
536+
return false;
537+
});
523538

524539
// Check invariants after homotopy reduction.
525540
verifyRewriteLoops();
526541
verifyRedundantConformances(redundantConformances);
527-
verifyMinimizedRules();
542+
verifyMinimizedRules(redundantConformances);
528543
}
529544

530545
/// In a conformance-valid rewrite system, any rule with unresolved symbols on
@@ -637,7 +652,7 @@ void RewriteSystem::verifyRewriteLoops() const {
637652
/// Assert if homotopy reduction failed to eliminate a redundant conformance,
638653
/// since this suggests a misunderstanding on my part.
639654
void RewriteSystem::verifyRedundantConformances(
640-
llvm::DenseSet<unsigned> redundantConformances) const {
655+
const llvm::DenseSet<unsigned> &redundantConformances) const {
641656
#ifndef NDEBUG
642657
for (unsigned ruleID : redundantConformances) {
643658
const auto &rule = getRule(ruleID);
@@ -661,9 +676,12 @@ void RewriteSystem::verifyRedundantConformances(
661676

662677
// Assert if homotopy reduction failed to eliminate a rewrite rule it was
663678
// supposed to delete.
664-
void RewriteSystem::verifyMinimizedRules() const {
679+
void RewriteSystem::verifyMinimizedRules(
680+
const llvm::DenseSet<unsigned> &redundantConformances) const {
665681
#ifndef NDEBUG
666-
for (const auto &rule : Rules) {
682+
for (unsigned ruleID : indices(Rules)) {
683+
const auto &rule = getRule(ruleID);
684+
667685
// Note that sometimes permanent rules can be simplified, but they can never
668686
// be redundant.
669687
if (rule.isPermanent()) {
@@ -687,6 +705,15 @@ void RewriteSystem::verifyMinimizedRules() const {
687705
dump(llvm::errs());
688706
abort();
689707
}
708+
709+
if (rule.isRedundant() &&
710+
rule.isAnyConformanceRule() &&
711+
!rule.containsUnresolvedSymbols() &&
712+
!redundantConformances.count(ruleID)) {
713+
llvm::errs() << "Minimal conformance is redundant: " << rule << "\n\n";
714+
dump(llvm::errs());
715+
abort();
716+
}
690717
}
691718
#endif
692719
}

0 commit comments

Comments
 (0)