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 >
7073RewriteLoop::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.
146158void 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.
388352Optional<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
478459void 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.
639654void 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