Skip to content

Commit b09b5c4

Browse files
authored
Merge pull request #41818 from slavapestov/rqm-optimize-path-normalization
RequirementMachine: Optimize rewrite path normalization
2 parents 8484f39 + 2a634ba commit b09b5c4

File tree

10 files changed

+967
-805
lines changed

10 files changed

+967
-805
lines changed

lib/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ add_swift_host_library(swiftAST STATIC
9494
RequirementMachine/RewriteContext.cpp
9595
RequirementMachine/RewriteLoop.cpp
9696
RequirementMachine/RewriteSystem.cpp
97+
RequirementMachine/Rule.cpp
9798
RequirementMachine/SimplifySubstitutions.cpp
9899
RequirementMachine/Symbol.cpp
99100
RequirementMachine/Term.cpp

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 41 additions & 304 deletions
Original file line numberDiff line numberDiff line change
@@ -65,93 +65,6 @@
6565
using namespace swift;
6666
using namespace rewriting;
6767

68-
/// Recompute various cached values if needed.
69-
void RewriteLoop::recompute(const RewriteSystem &system) {
70-
if (!Dirty)
71-
return;
72-
Dirty = 0;
73-
74-
Useful = 0;
75-
ProjectionCount = 0;
76-
DecomposeCount = 0;
77-
HasConcreteTypeAliasRule = 0;
78-
79-
RewritePathEvaluator evaluator(Basepoint);
80-
for (auto step : Path) {
81-
switch (step.Kind) {
82-
case RewriteStep::Rule: {
83-
Useful |= (!step.isInContext() && !evaluator.isInContext());
84-
85-
const auto &rule = system.getRule(step.getRuleID());
86-
if (rule.isDerivedFromConcreteProtocolTypeAliasRule())
87-
HasConcreteTypeAliasRule = 1;
88-
89-
break;
90-
}
91-
92-
case RewriteStep::LeftConcreteProjection:
93-
++ProjectionCount;
94-
break;
95-
96-
case RewriteStep::Decompose:
97-
++DecomposeCount;
98-
break;
99-
100-
case RewriteStep::PrefixSubstitutions:
101-
case RewriteStep::Shift:
102-
case RewriteStep::Relation:
103-
case RewriteStep::DecomposeConcrete:
104-
case RewriteStep::RightConcreteProjection:
105-
break;
106-
}
107-
108-
evaluator.apply(step, system);
109-
}
110-
111-
RulesInEmptyContext = Path.getRulesInEmptyContext(Basepoint, system);
112-
}
113-
114-
/// A rewrite rule is redundant if it appears exactly once in a loop
115-
/// without context.
116-
ArrayRef<unsigned>
117-
RewriteLoop::findRulesAppearingOnceInEmptyContext(
118-
const RewriteSystem &system) const {
119-
const_cast<RewriteLoop *>(this)->recompute(system);
120-
return RulesInEmptyContext;
121-
}
122-
123-
/// The number of LeftConcreteProjection steps, used by the elimination order to
124-
/// prioritize loops that are not concrete unification projections.
125-
unsigned RewriteLoop::getProjectionCount(
126-
const RewriteSystem &system) const {
127-
const_cast<RewriteLoop *>(this)->recompute(system);
128-
return ProjectionCount;
129-
}
130-
131-
/// The number of Decompose steps, used by the elimination order to prioritize
132-
/// loops that are not concrete simplifications.
133-
unsigned RewriteLoop::getDecomposeCount(
134-
const RewriteSystem &system) const {
135-
const_cast<RewriteLoop *>(this)->recompute(system);
136-
return DecomposeCount;
137-
}
138-
139-
/// Returns true if the loop contains at least one concrete protocol typealias rule,
140-
/// which have the form ([P].A.[concrete: C] => [P].A).
141-
bool RewriteLoop::hasConcreteTypeAliasRule(
142-
const RewriteSystem &system) const {
143-
const_cast<RewriteLoop *>(this)->recompute(system);
144-
return HasConcreteTypeAliasRule;
145-
}
146-
147-
/// The number of Decompose steps, used by the elimination order to prioritize
148-
/// loops that are not concrete simplifications.
149-
bool RewriteLoop::isUseful(
150-
const RewriteSystem &system) const {
151-
const_cast<RewriteLoop *>(this)->recompute(system);
152-
return Useful;
153-
}
154-
15568
/// If a rewrite loop contains an explicit rule in empty context, propagate the
15669
/// explicit bit to all other rules appearing in empty context within the same
15770
/// loop.
@@ -268,214 +181,6 @@ void RewriteSystem::processConflicts() {
268181
}
269182
}
270183

271-
/// Given a rewrite rule which appears exactly once in a loop
272-
/// without context, return a new definition for this rewrite rule.
273-
/// The new definition is the path obtained by deleting the
274-
/// rewrite rule from the loop.
275-
RewritePath RewritePath::splitCycleAtRule(unsigned ruleID) const {
276-
// A cycle is a path from the basepoint to the basepoint.
277-
// Somewhere in this path, an application of \p ruleID
278-
// appears in an empty context.
279-
280-
// First, we split the cycle into two paths:
281-
//
282-
// (1) A path from the basepoint to the rule's
283-
// left hand side,
284-
RewritePath basepointToLhs;
285-
// (2) And a path from the rule's right hand side
286-
// to the basepoint.
287-
RewritePath rhsToBasepoint;
288-
289-
// Because the rule only appears once, we know that basepointToLhs
290-
// and rhsToBasepoint do not involve the rule itself.
291-
292-
// If the rule is inverted, we have to invert the whole thing
293-
// again at the end.
294-
bool ruleWasInverted = false;
295-
296-
bool sawRule = false;
297-
298-
for (auto step : Steps) {
299-
switch (step.Kind) {
300-
case RewriteStep::Rule: {
301-
if (step.getRuleID() != ruleID)
302-
break;
303-
304-
assert(!sawRule && "Rule appears more than once?");
305-
assert(!step.isInContext() && "Rule appears in context?");
306-
307-
ruleWasInverted = step.Inverse;
308-
sawRule = true;
309-
continue;
310-
}
311-
case RewriteStep::PrefixSubstitutions:
312-
case RewriteStep::Shift:
313-
case RewriteStep::Decompose:
314-
case RewriteStep::Relation:
315-
case RewriteStep::DecomposeConcrete:
316-
case RewriteStep::LeftConcreteProjection:
317-
case RewriteStep::RightConcreteProjection:
318-
break;
319-
}
320-
321-
if (sawRule)
322-
rhsToBasepoint.add(step);
323-
else
324-
basepointToLhs.add(step);
325-
}
326-
327-
// Build a path from the rule's lhs to the rule's rhs via the
328-
// basepoint.
329-
RewritePath result = rhsToBasepoint;
330-
result.append(basepointToLhs);
331-
332-
// We want a path from the lhs to the rhs, so invert it unless
333-
// the rewrite step was also inverted.
334-
if (!ruleWasInverted)
335-
result.invert();
336-
337-
return result;
338-
}
339-
340-
/// Replace every rewrite step involving the given rewrite rule with
341-
/// either the replacement path (or its inverse, if the step was
342-
/// inverted).
343-
///
344-
/// The replacement path is re-contextualized at each occurrence of a
345-
/// rewrite step involving the given rule.
346-
///
347-
/// Returns true if any rewrite steps were replaced; false means the
348-
/// rule did not appear in this path.
349-
bool RewritePath::replaceRuleWithPath(unsigned ruleID,
350-
const RewritePath &path) {
351-
bool foundAny = false;
352-
353-
for (const auto &step : Steps) {
354-
if (step.Kind == RewriteStep::Rule &&
355-
step.getRuleID() == ruleID) {
356-
foundAny = true;
357-
break;
358-
}
359-
}
360-
361-
if (!foundAny)
362-
return false;
363-
364-
SmallVector<RewriteStep, 4> newSteps;
365-
366-
for (const auto &step : Steps) {
367-
switch (step.Kind) {
368-
case RewriteStep::Rule: {
369-
// All other rewrite rules remain unchanged.
370-
if (step.getRuleID() != ruleID) {
371-
newSteps.push_back(step);
372-
break;
373-
}
374-
375-
// Ok, we found a rewrite step referencing the redundant rule.
376-
// Replace this step with the provided path. If this rewrite step has
377-
// context, the path's own steps must be re-contextualized.
378-
379-
// Keep track of rewrite step pairs which push and pop the stack. Any
380-
// rewrite steps enclosed with a push/pop are not re-contextualized.
381-
unsigned pushCount = 0;
382-
383-
auto recontextualizeStep = [&](RewriteStep newStep) {
384-
bool inverse = newStep.Inverse ^ step.Inverse;
385-
386-
if (newStep.pushesTermsOnStack() && inverse) {
387-
assert(pushCount > 0);
388-
--pushCount;
389-
}
390-
391-
if (pushCount == 0) {
392-
newStep.StartOffset += step.StartOffset;
393-
newStep.EndOffset += step.EndOffset;
394-
}
395-
396-
newStep.Inverse = inverse;
397-
newSteps.push_back(newStep);
398-
399-
if (newStep.pushesTermsOnStack() && !inverse) {
400-
++pushCount;
401-
}
402-
};
403-
404-
// If this rewrite step is inverted, invert the entire path.
405-
if (step.Inverse) {
406-
for (auto newStep : llvm::reverse(path))
407-
recontextualizeStep(newStep);
408-
} else {
409-
for (auto newStep : path)
410-
recontextualizeStep(newStep);
411-
}
412-
413-
// Rewrite steps which push and pop the stack must come in balanced pairs.
414-
assert(pushCount == 0);
415-
416-
break;
417-
}
418-
case RewriteStep::PrefixSubstitutions:
419-
case RewriteStep::Shift:
420-
case RewriteStep::Decompose:
421-
case RewriteStep::Relation:
422-
case RewriteStep::DecomposeConcrete:
423-
case RewriteStep::LeftConcreteProjection:
424-
case RewriteStep::RightConcreteProjection:
425-
newSteps.push_back(step);
426-
break;
427-
}
428-
}
429-
430-
std::swap(newSteps, Steps);
431-
return true;
432-
}
433-
434-
SmallVector<unsigned, 1>
435-
RewritePath::getRulesInEmptyContext(const MutableTerm &term,
436-
const RewriteSystem &system) {
437-
// Rules appearing in empty context (possibly more than once).
438-
llvm::SmallDenseSet<unsigned, 2> rulesInEmptyContext;
439-
// The number of times each rule appears (with or without context).
440-
llvm::SmallDenseMap<unsigned, unsigned, 2> ruleFrequency;
441-
442-
RewritePathEvaluator evaluator(term);
443-
for (auto step : Steps) {
444-
switch (step.Kind) {
445-
case RewriteStep::Rule: {
446-
if (!step.isInContext() && !evaluator.isInContext())
447-
rulesInEmptyContext.insert(step.getRuleID());
448-
449-
++ruleFrequency[step.getRuleID()];
450-
break;
451-
}
452-
453-
case RewriteStep::LeftConcreteProjection:
454-
case RewriteStep::Decompose:
455-
case RewriteStep::PrefixSubstitutions:
456-
case RewriteStep::Shift:
457-
case RewriteStep::Relation:
458-
case RewriteStep::DecomposeConcrete:
459-
case RewriteStep::RightConcreteProjection:
460-
break;
461-
}
462-
463-
evaluator.apply(step, system);
464-
}
465-
466-
// Collect all rules that we saw exactly once in empty context.
467-
SmallVector<unsigned, 1> rulesOnceInEmptyContext;
468-
for (auto rule : rulesInEmptyContext) {
469-
auto found = ruleFrequency.find(rule);
470-
assert(found != ruleFrequency.end());
471-
472-
if (found->second == 1)
473-
rulesOnceInEmptyContext.push_back(rule);
474-
}
475-
476-
return rulesOnceInEmptyContext;
477-
}
478-
479184
/// Find a rule to delete by looking through all loops for rewrite rules appearing
480185
/// once in empty context. Returns a pair consisting of a loop ID and a rule ID,
481186
/// otherwise returns None.
@@ -680,12 +385,6 @@ findRuleToDelete(EliminationPredicate isRedundantRuleFn) {
680385
/// occurrences of the rule in all loops with the replacement path.
681386
void RewriteSystem::deleteRule(unsigned ruleID,
682387
const RewritePath &replacementPath) {
683-
// Replace all occurrences of the rule with the replacement path in
684-
// all redundant rule paths recorded so far.
685-
for (auto &pair : RedundantRules) {
686-
(void) pair.second.replaceRuleWithPath(ruleID, replacementPath);
687-
}
688-
689388
// Replace all occurrences of the rule with the replacement path in
690389
// all remaining rewrite loops.
691390
for (unsigned loopID : indices(Loops)) {
@@ -753,8 +452,34 @@ void RewriteSystem::performHomotopyReduction(
753452
}
754453

755454
void RewriteSystem::normalizeRedundantRules() {
756-
for (auto &pair : RedundantRules) {
455+
llvm::DenseMap<unsigned, unsigned> RedundantRuleMap;
456+
457+
// A redundant path in the range [0, i-1] might contain rewrite steps naming
458+
// rules that subsequently became redundant in the range [i, e-1].
459+
//
460+
// We back-substitute later rules into earlier paths here.
461+
for (unsigned i = 0, e = RedundantRules.size(); i < e; ++i) {
462+
// Pre-condition: Redundant paths in the range [i+1, e-1] do not involve
463+
// any other redundant rules.
464+
unsigned j = e - i - 1;
465+
466+
// Replace all occurrences of redundant rules with their path at
467+
// RedundantRules[i].
468+
auto &pair = RedundantRules[j];
469+
pair.second.replaceRulesWithPaths(
470+
[&](unsigned ruleID) -> RewritePath * {
471+
auto found = RedundantRuleMap.find(ruleID);
472+
if (found != RedundantRuleMap.end())
473+
return &RedundantRules[found->second].second;
474+
475+
return nullptr;
476+
});
757477
pair.second.computeNormalForm(*this);
478+
479+
RedundantRuleMap[RedundantRules[j].first] = j;
480+
481+
// Post-condition: the path for RedundantRules[i] does not contain any
482+
// redundant rules.
758483
}
759484

760485
if (Debug.contains(DebugFlags::RedundantRules)) {
@@ -911,12 +636,12 @@ void RewriteSystem::minimizeRewriteSystem() {
911636
return false;
912637
});
913638

639+
normalizeRedundantRules();
640+
914641
// Check invariants after homotopy reduction.
915642
verifyRewriteLoops();
916643
verifyRedundantConformances(redundantConformances);
917644
verifyMinimizedRules(redundantConformances);
918-
919-
normalizeRedundantRules();
920645
}
921646

922647
/// Returns flags indicating if the rewrite system has unresolved or
@@ -1121,5 +846,17 @@ void RewriteSystem::verifyMinimizedRules(
1121846
dump(llvm::errs());
1122847
abort();
1123848
}
849+
850+
for (const auto &step : pair.second) {
851+
if (step.Kind == RewriteStep::Rule) {
852+
const auto &rule = getRule(step.getRuleID());
853+
if (rule.isRedundant()) {
854+
llvm::errs() << "Redundant requirement path contains a redundant "
855+
"rule " << rule << "\n";
856+
dump(llvm::errs());
857+
abort();
858+
}
859+
}
860+
}
1124861
}
1125862
}

0 commit comments

Comments
 (0)