Skip to content

Commit ef5a44f

Browse files
authored
Merge pull request #41292 from slavapestov/rqm-concrete-conformance-fixes
RequirementMachine: A couple of small fixes for generic signature minimization
2 parents 508af6b + d39c667 commit ef5a44f

17 files changed

+339
-212
lines changed

lib/AST/GenericSignature.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,12 @@ int Requirement::compare(const Requirement &other) const {
729729
return compareKind;
730730

731731
// We should only have multiple conformance requirements.
732-
assert(getKind() == RequirementKind::Conformance);
732+
if (getKind() != RequirementKind::Conformance) {
733+
llvm::errs() << "Unordered generic requirements\n";
734+
llvm::errs() << "LHS: "; dump(llvm::errs()); llvm::errs() << "\n";
735+
llvm::errs() << "RHS: "; other.dump(llvm::errs()); llvm::errs() << "\n";
736+
abort();
737+
}
733738

734739
int compareProtos =
735740
TypeDecl::compare(getProtocolDecl(), other.getProtocolDecl());

lib/AST/RequirementMachine/ConcreteTypeWitness.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,14 @@ MutableTerm PropertyMap::computeConstraintTermForTypeWitness(
314314
return result;
315315
}
316316

317+
// Compute the concrete type symbol [concrete: C.X].
318+
SmallVector<Term, 3> result;
319+
auto typeWitnessSchema =
320+
Context.getRelativeSubstitutionSchemaFromType(typeWitness, substitutions,
321+
result);
322+
auto typeWitnessSymbol =
323+
Symbol::forConcreteType(typeWitnessSchema, result, Context);
324+
317325
// If the type witness is completely concrete, check if one of our prefix
318326
// types has the same concrete type, and if so, introduce a same-type
319327
// requirement between the subject type and the prefix.
@@ -326,10 +334,14 @@ MutableTerm PropertyMap::computeConstraintTermForTypeWitness(
326334
if (auto *props = lookUpProperties(prefix)) {
327335
if (props->isConcreteType() &&
328336
props->getConcreteType() == typeWitness) {
329-
auto result = props->getKey();
337+
// Record a relation U.[concrete: C.X] =>> U.V.[concrete: C : P].[P:X]
338+
// where U is the parent such that U.[concrete: C:X] => U.
339+
MutableTerm result(props->getKey());
340+
result.add(typeWitnessSymbol);
330341

331342
unsigned relationID = System.recordRelation(
332-
result, Term::get(subjectType, Context));
343+
Term::get(result, Context),
344+
Term::get(subjectType, Context));
333345
path.add(RewriteStep::forRelation(
334346
/*startOffset=*/0, relationID,
335347
/*inverse=*/false));
@@ -339,7 +351,7 @@ MutableTerm PropertyMap::computeConstraintTermForTypeWitness(
339351
<< result << "\n";
340352
}
341353

342-
return MutableTerm(result);
354+
return result;
343355
}
344356
}
345357

@@ -350,14 +362,6 @@ MutableTerm PropertyMap::computeConstraintTermForTypeWitness(
350362
// Otherwise the type witness is concrete, but may contain type
351363
// parameters in structural position.
352364

353-
// Compute the concrete type symbol [concrete: C.X].
354-
SmallVector<Term, 3> result;
355-
auto typeWitnessSchema =
356-
Context.getRelativeSubstitutionSchemaFromType(typeWitness, substitutions,
357-
result);
358-
auto typeWitnessSymbol =
359-
Symbol::forConcreteType(typeWitnessSchema, result, Context);
360-
361365
auto concreteConformanceSymbol = *(subjectType.end() - 2);
362366
auto associatedTypeSymbol = *(subjectType.end() - 1);
363367

lib/AST/RequirementMachine/Debug.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,17 @@ enum class DebugFlags : unsigned {
4848
/// Print debug output from the homotopy reduction algorithm.
4949
HomotopyReduction = (1<<8),
5050

51+
/// Print more detailed debug output from the homotopy reduction algorithm.
52+
HomotopyReductionDetail = (1<<9),
53+
5154
/// Print debug output from the minimal conformances algorithm.
52-
MinimalConformances = (1<<9),
55+
MinimalConformances = (1<<10),
5356

5457
/// Print debug output from the protocol dependency graph.
55-
ProtocolDependencies = (1<<10),
58+
ProtocolDependencies = (1<<11),
5659

5760
/// Print debug output from generic signature minimization.
58-
Minimization = (1<<11),
61+
Minimization = (1<<12),
5962
};
6063

6164
using DebugOptions = OptionSet<DebugFlags>;

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 82 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,13 @@
6363
using namespace swift;
6464
using namespace rewriting;
6565

66-
/// A rewrite rule is redundant if it appears exactly once in a loop
67-
/// without context.
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>
73-
RewriteLoop::findRulesAppearingOnceInEmptyContext(
74-
const RewriteSystem &system) const {
75-
// If we're allowed to use the cached result, return that.
66+
/// Recompute RulesInEmptyContext and DecomposeCount if needed.
67+
void RewriteLoop::recompute(const RewriteSystem &system) {
7668
if (!Dirty)
77-
return RulesInEmptyContext;
69+
return;
70+
Dirty = 0;
71+
72+
ProjectionCount = 0;
7873

7974
// Rules appearing in empty context (possibly more than once).
8075
llvm::SmallDenseSet<unsigned, 2> rulesInEmptyContext;
@@ -94,36 +89,51 @@ RewriteLoop::findRulesAppearingOnceInEmptyContext(
9489
break;
9590
}
9691

92+
case RewriteStep::LeftConcreteProjection:
93+
++ProjectionCount;
94+
break;
95+
9796
case RewriteStep::PrefixSubstitutions:
9897
case RewriteStep::Shift:
9998
case RewriteStep::Decompose:
10099
case RewriteStep::Relation:
101100
case RewriteStep::DecomposeConcrete:
102-
case RewriteStep::LeftConcreteProjection:
103101
case RewriteStep::RightConcreteProjection:
104102
break;
105103
}
106104

107105
evaluator.apply(step, system);
108106
}
109107

110-
auto *mutThis = const_cast<RewriteLoop *>(this);
111-
mutThis->RulesInEmptyContext.clear();
108+
RulesInEmptyContext.clear();
112109

113110
// Collect all rules that we saw exactly once in empty context.
114111
for (auto rule : rulesInEmptyContext) {
115112
auto found = ruleMultiplicity.find(rule);
116113
assert(found != ruleMultiplicity.end());
117114

118115
if (found->second == 1)
119-
mutThis->RulesInEmptyContext.push_back(rule);
116+
RulesInEmptyContext.push_back(rule);
120117
}
118+
}
121119

122-
// Cache the result for later.
123-
mutThis->Dirty = 0;
120+
/// A rewrite rule is redundant if it appears exactly once in a loop
121+
/// without context.
122+
ArrayRef<unsigned>
123+
RewriteLoop::findRulesAppearingOnceInEmptyContext(
124+
const RewriteSystem &system) const {
125+
const_cast<RewriteLoop *>(this)->recompute(system);
124126
return RulesInEmptyContext;
125127
}
126128

129+
/// The number of LeftConcreteProjection steps, used by the elimination order to
130+
/// prioritize loops that are not concrete unification projections.
131+
unsigned RewriteLoop::getProjectionCount(
132+
const RewriteSystem &system) const {
133+
const_cast<RewriteLoop *>(this)->recompute(system);
134+
return ProjectionCount;
135+
}
136+
127137
/// If a rewrite loop contains an explicit rule in empty context, propagate the
128138
/// explicit bit to all other rules appearing in empty context within the same
129139
/// loop.
@@ -375,6 +385,10 @@ findRuleToDelete(llvm::function_ref<bool(unsigned)> isRedundantRuleFn) {
375385

376386
Optional<std::pair<unsigned, unsigned>> found;
377387

388+
if (Debug.contains(DebugFlags::HomotopyReduction)) {
389+
llvm::dbgs() << "\n";
390+
}
391+
378392
for (const auto &pair : redundancyCandidates) {
379393
unsigned ruleID = pair.second;
380394
const auto &rule = getRule(ruleID);
@@ -402,7 +416,7 @@ findRuleToDelete(llvm::function_ref<bool(unsigned)> isRedundantRuleFn) {
402416
continue;
403417
}
404418

405-
if (Debug.contains(DebugFlags::HomotopyReduction)) {
419+
if (Debug.contains(DebugFlags::HomotopyReductionDetail)) {
406420
llvm::dbgs() << "** Candidate " << rule << " from loop #"
407421
<< pair.first << "\n";
408422
}
@@ -411,26 +425,42 @@ findRuleToDelete(llvm::function_ref<bool(unsigned)> isRedundantRuleFn) {
411425
// we've found so far.
412426
const auto &otherRule = getRule(found->second);
413427

414-
unsigned ruleNesting = rule.getNesting();
415-
unsigned otherRuleNesting = otherRule.getNesting();
428+
const auto &loop = Loops[pair.first];
429+
const auto &otherLoop = Loops[found->first];
416430

417-
// If both rules are concrete type requirements, first compare nesting
418-
// depth. This breaks the tie when we have two rules that each imply
419-
// the other via an induced rule that comes from a protocol.
431+
// If one of the rules was a concrete unification projection, prefer to
432+
// eliminate the *other* rule.
420433
//
421-
// For example,
434+
// For example, if 'X.T == G<U, V>' is implied by the conformance on X,
435+
// and the following three rules are defined in the current protocol:
422436
//
423-
// T == G<Int>
424-
// U == Int
437+
// X.T == G<Int, W>
438+
// X.U == Int
439+
// X.V == W
425440
//
426-
// Where T == G<U> is implied elsewhere.
427-
if (ruleNesting > 0 && otherRuleNesting > 0) {
428-
if (ruleNesting > otherRuleNesting) {
441+
// Then we can either eliminate a) alone, or b) and c). Since b) and c)
442+
// are projections, they are "simpler", and we would rather keep both and
443+
// eliminate a).
444+
unsigned projectionCount = loop.getProjectionCount(*this);
445+
unsigned otherProjectionCount = otherLoop.getProjectionCount(*this);
446+
447+
if (projectionCount != otherProjectionCount) {
448+
if (projectionCount < otherProjectionCount)
429449
found = pair;
430-
continue;
431-
} else if (otherRuleNesting > ruleNesting) {
432-
continue;
433-
}
450+
451+
continue;
452+
}
453+
454+
// If one of the rules is a concrete type requirement, prefer to
455+
// eliminate the *other* rule.
456+
bool ruleIsConcrete = rule.getLHS().back().hasSubstitutions();
457+
bool otherRuleIsConcrete = otherRule.getRHS().back().hasSubstitutions();
458+
459+
if (ruleIsConcrete != otherRuleIsConcrete) {
460+
if (otherRuleIsConcrete)
461+
found = pair;
462+
463+
continue;
434464
}
435465

436466
// Otherwise, perform a shortlex comparison on (LHS, RHS).
@@ -474,7 +504,8 @@ void RewriteSystem::deleteRule(unsigned ruleID,
474504
const RewritePath &replacementPath) {
475505
// Replace all occurrences of the rule with the replacement path in
476506
// all remaining rewrite loops.
477-
for (auto &loop : Loops) {
507+
for (unsigned loopID : indices(Loops)) {
508+
auto &loop = Loops[loopID];
478509
if (loop.isDeleted())
479510
continue;
480511

@@ -486,8 +517,8 @@ void RewriteSystem::deleteRule(unsigned ruleID,
486517
// result of findRulesAppearingOnceInEmptyContext().
487518
loop.markDirty();
488519

489-
if (Debug.contains(DebugFlags::HomotopyReduction)) {
490-
llvm::dbgs() << "** Updated loop: ";
520+
if (Debug.contains(DebugFlags::HomotopyReductionDetail)) {
521+
llvm::dbgs() << "** Updated loop #" << loopID << ": ";
491522
loop.dump(llvm::dbgs(), *this);
492523
llvm::dbgs() << "\n";
493524
}
@@ -534,6 +565,12 @@ void RewriteSystem::performHomotopyReduction(
534565
///
535566
/// Redundant rules are mutated to set their isRedundant() bit.
536567
void RewriteSystem::minimizeRewriteSystem() {
568+
if (Debug.contains(DebugFlags::HomotopyReduction)) {
569+
llvm::dbgs() << "-----------------------------\n";
570+
llvm::dbgs() << "- Minimizing rewrite system -\n";
571+
llvm::dbgs() << "-----------------------------\n";
572+
}
573+
537574
assert(Complete);
538575
assert(!Minimized);
539576
Minimized = 1;
@@ -545,7 +582,9 @@ void RewriteSystem::minimizeRewriteSystem() {
545582
// - Eliminate all RHS-simplified and substitution-simplified rules.
546583
// - Eliminate all rules with unresolved symbols.
547584
if (Debug.contains(DebugFlags::HomotopyReduction)) {
548-
llvm::dbgs() << "\nFirst pass: simplified and unresolved rules\n\n";
585+
llvm::dbgs() << "---------------------------------------------\n";
586+
llvm::dbgs() << "First pass: simplified and unresolved rules -\n";
587+
llvm::dbgs() << "---------------------------------------------\n";
549588
}
550589

551590
performHomotopyReduction([&](unsigned ruleID) -> bool {
@@ -577,7 +616,9 @@ void RewriteSystem::minimizeRewriteSystem() {
577616

578617
// Second pass: Eliminate all non-minimal conformance rules.
579618
if (Debug.contains(DebugFlags::HomotopyReduction)) {
580-
llvm::dbgs() << "\nSecond pass: non-minimal conformance rules\n\n";
619+
llvm::dbgs() << "--------------------------------------------\n";
620+
llvm::dbgs() << "Second pass: non-minimal conformance rules -\n";
621+
llvm::dbgs() << "--------------------------------------------\n";
581622
}
582623

583624
performHomotopyReduction([&](unsigned ruleID) -> bool {
@@ -592,7 +633,9 @@ void RewriteSystem::minimizeRewriteSystem() {
592633

593634
// Third pass: Eliminate all other redundant non-conformance rules.
594635
if (Debug.contains(DebugFlags::HomotopyReduction)) {
595-
llvm::dbgs() << "\nThird pass: all other redundant rules\n\n";
636+
llvm::dbgs() << "---------------------------------------\n";
637+
llvm::dbgs() << "Third pass: all other redundant rules -\n";
638+
llvm::dbgs() << "---------------------------------------\n";
596639
}
597640

598641
performHomotopyReduction([&](unsigned ruleID) -> bool {

0 commit comments

Comments
 (0)