Skip to content

Commit 5f3d781

Browse files
committed
RequirementMachine: Homotopy reduction tweaks
- Skip permanent rules (there's no point, we'll add them back next time) - Skip conformance rules (these will be handled separately) - Delete 3-cells that are entirely "in-context" (but don't quote me on this one, I'm not quite yet convinced it's correct, but it feels right)
1 parent 2923a33 commit 5f3d781

File tree

5 files changed

+190
-92
lines changed

5 files changed

+190
-92
lines changed

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 132 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ RewritePath::findRulesAppearingOnceInEmptyContext() const {
158158
for (auto step : Steps) {
159159
switch (step.Kind) {
160160
case RewriteStep::ApplyRewriteRule: {
161-
if (step.StartOffset == 0 && step.EndOffset == 0)
161+
if (!step.isInContext())
162162
rulesInEmptyContext.insert(step.RuleID);
163163

164164
++ruleMultiplicity[step.RuleID];
@@ -216,8 +216,7 @@ RewritePath RewritePath::splitCycleAtRule(unsigned ruleID) const {
216216
break;
217217

218218
assert(!sawRule && "Rule appears more than once?");
219-
assert(step.StartOffset == 0 || step.EndOffset == 0 &&
220-
"Rule appears in context?");
219+
assert(!step.isInContext() && "Rule appears in context?");
221220

222221
ruleWasInverted = step.Inverse;
223222
sawRule = true;
@@ -460,6 +459,7 @@ bool RewritePath::computeCyclicallyReducedLoop(MutableTerm &basepoint,
460459
return count > 0;
461460
}
462461

462+
/// Apply the interchange rule until fixed point (see maybeSwapRewriteSteps()).
463463
bool RewritePath::computeLeftCanonicalForm(const RewriteSystem &system) {
464464
bool changed = false;
465465

@@ -491,36 +491,103 @@ void RewritePath::dump(llvm::raw_ostream &out,
491491
}
492492
}
493493

494-
/// Use the 3-cells to delete rewrite rules, updating and simplifying existing
495-
/// 3-cells as each rule is deleted.
496-
void RewriteSystem::minimizeRewriteSystem() {
497-
llvm::SmallDenseSet<unsigned> deletedRules;
498-
llvm::SmallDenseSet<unsigned> deletedHomotopyGenerators;
494+
/// Compute cyclically-reduced left-canonical normal form of a 3-cell.
495+
void HomotopyGenerator::normalize(const RewriteSystem &system) {
496+
// FIXME: This can be more efficient.
497+
bool changed;
498+
do {
499+
changed = false;
500+
changed |= Path.computeFreelyReducedPath();
501+
changed |= Path.computeCyclicallyReducedLoop(Basepoint, system);
502+
changed |= Path.computeLeftCanonicalForm(system);
503+
} while (changed);
504+
}
499505

500-
auto findRuleToDelete = [&]() -> Optional<std::pair<unsigned, RewritePath>> {
501-
for (unsigned loopID : indices(HomotopyGenerators)) {
502-
if (deletedHomotopyGenerators.count(loopID))
503-
continue;
506+
/// A 3-cell is "in context" if every rewrite step has a left or right whisker.
507+
bool HomotopyGenerator::isInContext() const {
508+
unsigned minStartOffset = (unsigned) -1;
509+
unsigned minEndOffset = (unsigned) -1;
504510

505-
const auto &loop = HomotopyGenerators[loopID];
511+
for (const auto &step : Path) {
512+
switch (step.Kind) {
513+
case RewriteStep::ApplyRewriteRule:
514+
minStartOffset = std::min(minStartOffset, step.StartOffset);
515+
minEndOffset = std::min(minEndOffset, step.EndOffset);
516+
break;
506517

507-
SmallVector<unsigned> redundancyCandidates =
508-
loop.Path.findRulesAppearingOnceInEmptyContext();
509-
if (redundancyCandidates.empty())
510-
continue;
518+
case RewriteStep::AdjustConcreteType:
519+
break;
520+
}
511521

512-
auto ruleID = redundancyCandidates.front();
513-
RewritePath replacementPath = loop.Path.splitCycleAtRule(ruleID);
522+
if (minStartOffset == 0 && minEndOffset == 0)
523+
break;
524+
}
514525

515-
deletedRules.insert(ruleID);
516-
deletedHomotopyGenerators.insert(loopID);
526+
return (minStartOffset > 0 || minEndOffset > 0);
527+
}
517528

518-
return std::make_pair(ruleID, replacementPath);
519-
}
529+
void HomotopyGenerator::dump(llvm::raw_ostream &out,
530+
const RewriteSystem &system) const {
531+
out << Basepoint << ": ";
532+
Path.dump(out, Basepoint, system);
533+
if (isDeleted())
534+
out << " [deleted]";
535+
}
520536

521-
return None;
522-
};
537+
Optional<unsigned>
538+
RewriteSystem::findRuleToDelete(RewritePath &replacementPath) {
539+
for (auto &loop : HomotopyGenerators) {
540+
if (loop.isDeleted())
541+
continue;
542+
543+
SmallVector<unsigned> redundancyCandidates =
544+
loop.Path.findRulesAppearingOnceInEmptyContext();
545+
546+
auto found = std::find_if(
547+
redundancyCandidates.begin(),
548+
redundancyCandidates.end(),
549+
[&](unsigned ruleID) -> bool {
550+
const auto &rule = getRule(ruleID);
551+
552+
// We should not find a rule that has already been marked redundant
553+
// here; it should have already been replaced with a rewrite path
554+
// in all homotopy generators.
555+
assert(!rule.isRedundant());
556+
557+
// Associated type introduction rules are 'permanent'. They're
558+
// not worth eliminating since they are re-added every time; it
559+
// is better to find other candidates to eliminate in the same
560+
// 3-cell instead.
561+
if (rule.isPermanent())
562+
return false;
563+
564+
// Protocol conformance rules are eliminated via a different
565+
// algorithm which computes "generating conformances".
566+
if (rule.isProtocolConformanceRule())
567+
return false;
568+
569+
return true;
570+
});
571+
572+
if (found == redundancyCandidates.end())
573+
continue;
574+
575+
auto ruleID = *found;
576+
assert(replacementPath.empty());
577+
replacementPath = loop.Path.splitCycleAtRule(ruleID);
578+
579+
loop.markDeleted();
580+
getRule(ruleID).markRedundant();
581+
582+
return ruleID;
583+
}
584+
585+
return None;
586+
}
523587

588+
/// Use the 3-cells to delete rewrite rules, updating and simplifying existing
589+
/// 3-cells as each rule is deleted.
590+
void RewriteSystem::minimizeRewriteSystem() {
524591
auto deleteRule = [&](unsigned ruleID, RewritePath replacementPath) {
525592
if (Debug.contains(DebugFlags::HomotopyReduction)) {
526593
const auto &rule = getRule(ruleID);
@@ -533,74 +600,63 @@ void RewriteSystem::minimizeRewriteSystem() {
533600
llvm::dbgs() << "\n";
534601
}
535602

536-
for (unsigned loopID : indices(HomotopyGenerators)) {
537-
if (deletedHomotopyGenerators.count(loopID))
603+
// Replace all occurrences of the rule with the replacement path and
604+
// normalize all 3-cells.
605+
for (auto &loop : HomotopyGenerators) {
606+
if (loop.isDeleted())
538607
continue;
539608

540-
auto &loop = HomotopyGenerators[loopID];
541609
bool changed = loop.Path.replaceRuleWithPath(ruleID, replacementPath);
610+
if (!changed)
611+
continue;
542612

543-
if (changed) {
544-
unsigned size = loop.Path.size();
613+
unsigned size = loop.Path.size();
545614

546-
bool changed;
547-
do {
548-
changed = false;
549-
changed |= loop.Path.computeFreelyReducedPath();
550-
changed |= loop.Path.computeCyclicallyReducedLoop(loop.Basepoint, *this);
551-
changed |= loop.Path.computeLeftCanonicalForm(*this);
552-
} while (changed);
615+
loop.normalize(*this);
553616

554-
if (Debug.contains(DebugFlags::HomotopyReduction)) {
555-
if (size != loop.Path.size()) {
556-
llvm::dbgs() << "** Note: Reducing the loop eliminated "
557-
<< (size - loop.Path.size()) << " steps\n";
558-
}
617+
if (Debug.contains(DebugFlags::HomotopyReduction)) {
618+
if (size != loop.Path.size()) {
619+
llvm::dbgs() << "** Note: 3-cell normalization eliminated "
620+
<< (size - loop.Path.size()) << " steps\n";
559621
}
622+
}
560623

624+
if (loop.Path.empty()) {
561625
if (Debug.contains(DebugFlags::HomotopyReduction)) {
562-
llvm::dbgs() << "** Updated homotopy generator: ";
563-
llvm::dbgs() << "- " << loop.Basepoint << ": ";
564-
loop.Path.dump(llvm::dbgs(), loop.Basepoint, *this);
565-
llvm::dbgs() << "\n";
626+
llvm::dbgs() << "** Deleting trivial 3-cell at basepoint ";
627+
llvm::dbgs() << loop.Basepoint << "\n";
566628
}
567-
}
568-
}
569-
};
570-
571-
while (auto pair = findRuleToDelete()) {
572-
deleteRule(pair->first, pair->second);
573-
}
574629

575-
if (Debug.contains(DebugFlags::HomotopyReduction)) {
576-
llvm::dbgs() << "Minimized rewrite system:\n";
577-
for (unsigned ruleID : indices(Rules)) {
578-
if (deletedRules.count(ruleID))
630+
loop.markDeleted();
579631
continue;
632+
}
580633

581-
llvm::dbgs() << "(#" << ruleID << ") " << getRule(ruleID) << "\n";
582-
}
583-
584-
llvm::dbgs() << "Minimized homotopy generators:\n";
585-
for (unsigned loopID : indices(HomotopyGenerators)) {
586-
if (deletedHomotopyGenerators.count(loopID))
587-
continue;
634+
// FIXME: Is this correct?
635+
if (loop.isInContext()) {
636+
if (Debug.contains(DebugFlags::HomotopyReduction)) {
637+
llvm::dbgs() << "** Deleting 3-cell in context: ";
638+
loop.dump(llvm::dbgs(), *this);
639+
llvm::dbgs() << "\n";
640+
}
588641

589-
const auto &loop = HomotopyGenerators[loopID];
590-
if (loop.Path.empty())
642+
loop.markDeleted();
591643
continue;
644+
}
592645

593-
llvm::dbgs() << "(#" << loopID << ") ";
594-
llvm::dbgs() << loop.Basepoint << ": ";
595-
loop.Path.dump(llvm::dbgs(), loop.Basepoint, *this);
596-
llvm::dbgs() << "\n";
597-
598-
MutableTerm basepoint = loop.Basepoint;
599-
for (auto step : loop.Path) {
600-
step.apply(basepoint, *this);
601-
llvm::dbgs() << "- " << basepoint << "\n";
646+
if (Debug.contains(DebugFlags::HomotopyReduction)) {
647+
llvm::dbgs() << "** Updated 3-cell: ";
648+
loop.dump(llvm::dbgs(), *this);
649+
llvm::dbgs() << "\n";
602650
}
603651
}
652+
};
653+
654+
while (true) {
655+
RewritePath replacementPath;
656+
if (auto optRuleID = findRuleToDelete(replacementPath))
657+
deleteRule(*optRuleID, replacementPath);
658+
else
659+
break;
604660
}
605661
}
606662

@@ -616,7 +672,7 @@ void RewriteSystem::verifyHomotopyGenerators() const {
616672

617673
if (term != loop.Basepoint) {
618674
llvm::errs() << "Not a loop: ";
619-
loop.Path.dump(llvm::errs(), loop.Basepoint, *this);
675+
loop.dump(llvm::errs(), *this);
620676
llvm::errs() << "\n";
621677
abort();
622678
}

lib/AST/RequirementMachine/RequirementMachine.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,14 @@ void RequirementMachine::dump(llvm::raw_ostream &out) const {
322322
System.dump(out);
323323
Map.dump(out);
324324

325-
out << "\nConformance access paths:\n";
325+
out << "Conformance access paths: {\n";
326326
for (auto pair : ConformanceAccessPaths) {
327327
out << "- " << pair.first.first << " : ";
328328
out << pair.first.second->getName() << " => ";
329329
pair.second.print(out);
330330
out << "\n";
331331
}
332+
out << "}\n";
332333
}
333334

334335
RequirementMachine::RequirementMachine(RewriteContext &ctx)

lib/AST/RequirementMachine/RewriteSystem.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
using namespace swift;
2424
using namespace rewriting;
2525

26+
/// If this is a rule of the form T.[p] => T where [p] is a property symbol,
27+
/// returns the symbol. Otherwise, returns None.
28+
///
29+
/// Note that this is meant to be used with a simplified rewrite system,
30+
/// where the right hand sides of rules are canonical, since this also means
31+
/// that T is canonical.
2632
Optional<Symbol> Rule::isPropertyRule() const {
2733
auto property = LHS.back();
2834

@@ -38,6 +44,8 @@ Optional<Symbol> Rule::isPropertyRule() const {
3844
return property;
3945
}
4046

47+
/// If this is a rule of the form T.[p] => T where [p] is a protocol symbol,
48+
/// return true, otherwise return false.
4149
bool Rule::isProtocolConformanceRule() const {
4250
if (auto property = isPropertyRule())
4351
return property->getKind() == Symbol::Kind::Protocol;
@@ -418,8 +426,8 @@ void RewriteSystem::dump(llvm::raw_ostream &out) const {
418426
out << "}\n";
419427
out << "Homotopy generators: {\n";
420428
for (const auto &loop : HomotopyGenerators) {
421-
out << "- " << loop.Basepoint << ": ";
422-
loop.Path.dump(out, loop.Basepoint, *this);
429+
out << "- ";
430+
loop.dump(out, *this);
423431
out << "\n";
424432
}
425433
out << "}\n";

0 commit comments

Comments
 (0)