Skip to content

Commit 426df36

Browse files
committed
RequirementMachine: More elimination order tweaking
- Prefer rewrite loops not involving Decompose - Prefer more deeply nested concrete types
1 parent 57d89a6 commit 426df36

File tree

3 files changed

+146
-44
lines changed

3 files changed

+146
-44
lines changed

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 107 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ void RewriteLoop::recompute(const RewriteSystem &system) {
7070
Dirty = 0;
7171

7272
ProjectionCount = 0;
73+
DecomposeCount = 0;
7374

7475
// Rules appearing in empty context (possibly more than once).
7576
llvm::SmallDenseSet<unsigned, 2> rulesInEmptyContext;
@@ -93,9 +94,12 @@ void RewriteLoop::recompute(const RewriteSystem &system) {
9394
++ProjectionCount;
9495
break;
9596

97+
case RewriteStep::Decompose:
98+
++DecomposeCount;
99+
break;
100+
96101
case RewriteStep::PrefixSubstitutions:
97102
case RewriteStep::Shift:
98-
case RewriteStep::Decompose:
99103
case RewriteStep::Relation:
100104
case RewriteStep::DecomposeConcrete:
101105
case RewriteStep::RightConcreteProjection:
@@ -134,6 +138,14 @@ unsigned RewriteLoop::getProjectionCount(
134138
return ProjectionCount;
135139
}
136140

141+
/// The number of Decompose steps, used by the elimination order to prioritize
142+
/// loops that are not concrete simplifications.
143+
unsigned RewriteLoop::getDecomposeCount(
144+
const RewriteSystem &system) const {
145+
const_cast<RewriteLoop *>(this)->recompute(system);
146+
return DecomposeCount;
147+
}
148+
137149
/// If a rewrite loop contains an explicit rule in empty context, propagate the
138150
/// explicit bit to all other rules appearing in empty context within the same
139151
/// loop.
@@ -428,56 +440,108 @@ findRuleToDelete(llvm::function_ref<bool(unsigned)> isRedundantRuleFn) {
428440
const auto &loop = Loops[pair.first];
429441
const auto &otherLoop = Loops[found->first];
430442

431-
// If one of the rules was a concrete unification projection, prefer to
432-
// eliminate the *other* rule.
433-
//
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:
436-
//
437-
// X.T == G<Int, W>
438-
// X.U == Int
439-
// X.V == W
440-
//
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)
449-
found = pair;
450-
451-
continue;
443+
{
444+
// If one of the rules was a concrete unification projection, prefer to
445+
// eliminate the *other* rule.
446+
//
447+
// For example, if 'X.T == G<U, V>' is implied by the conformance on X,
448+
// and the following three rules are defined in the current protocol:
449+
//
450+
// a) X.T == G<Int, W>
451+
// b) X.U == Int
452+
// c) X.V == W
453+
//
454+
// Then we can either eliminate a) alone, or b) and c). Since b) and c)
455+
// are projections, they are "simpler", and we would rather keep both and
456+
// eliminate a).
457+
unsigned projectionCount = loop.getProjectionCount(*this);
458+
unsigned otherProjectionCount = otherLoop.getProjectionCount(*this);
459+
460+
if (projectionCount != otherProjectionCount) {
461+
if (projectionCount < otherProjectionCount)
462+
found = pair;
463+
464+
continue;
465+
}
452466
}
453467

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.getLHS().back().hasSubstitutions();
468+
{
469+
// If one of the rules is a concrete type requirement, prefer to
470+
// eliminate the *other* rule.
471+
bool ruleIsConcrete = rule.getLHS().back().hasSubstitutions();
472+
bool otherRuleIsConcrete = otherRule.getLHS().back().hasSubstitutions();
458473

459-
if (ruleIsConcrete != otherRuleIsConcrete) {
460-
if (otherRuleIsConcrete)
461-
found = pair;
474+
if (ruleIsConcrete != otherRuleIsConcrete) {
475+
if (otherRuleIsConcrete)
476+
found = pair;
462477

463-
continue;
478+
continue;
479+
}
464480
}
465481

466-
// Otherwise, perform a shortlex comparison on (LHS, RHS).
467-
Optional<int> comparison = rule.compare(otherRule, Context);
468-
if (!comparison.hasValue()) {
469-
// Two rules (T.[C] => T) and (T.[C'] => T) are incomparable if
470-
// C and C' are superclass, concrete type or concrete conformance
471-
// symbols.
472-
found = pair;
473-
continue;
482+
{
483+
// If both are concrete type requirements, prefer to eliminate the
484+
// one with the more deeply nested type.
485+
unsigned ruleNesting = rule.getNesting();
486+
unsigned otherRuleNesting = otherRule.getNesting();
487+
488+
if (ruleNesting != otherRuleNesting) {
489+
if (ruleNesting > otherRuleNesting)
490+
found = pair;
491+
492+
continue;
493+
}
474494
}
475495

476-
if (*comparison > 0) {
477-
// Otherwise, if the new rule is less canonical than the best one so
478-
// far, it becomes the new candidate for elimination.
479-
found = pair;
480-
continue;
496+
{
497+
// Otherwise, perform a shortlex comparison on (LHS, RHS).
498+
Optional<int> comparison = rule.compare(otherRule, Context);
499+
500+
if (!comparison.hasValue()) {
501+
// Two rules (T.[C] => T) and (T.[C'] => T) are incomparable if
502+
// C and C' are superclass, concrete type or concrete conformance
503+
// symbols.
504+
found = pair;
505+
continue;
506+
}
507+
508+
if (*comparison == 0) {
509+
// Given two rewrite loops that both eliminate the same rule, prefer
510+
// the one that was not recorded by substitution simplification;
511+
// substitution simplification rules contain the projections in
512+
// context, which then prevents the projections from being eliminated.
513+
//
514+
// An example is if you have two rules implied by conformances on X,
515+
//
516+
// a) X.T == G<Y>
517+
// b) X.T == G<Z>
518+
//
519+
// then the induced rule Y == Z is a projection.
520+
//
521+
// The rule X.T == G<Z> can be eliminated with a loop that begins at
522+
// X.T.[concrete: G<Y>] followed by a decomposition and rewrite of
523+
// Y into Z, finally followed by an inverse decomposition back to
524+
// X.T.[concrete: G<Z>].
525+
//
526+
// However, if we can eliminate G<Y> via some other loop, we prefer
527+
// to do that, since that might *also* allow us to eliminate Y == Z.
528+
unsigned decomposeCount = loop.getDecomposeCount(*this);
529+
unsigned otherDecomposeCount = otherLoop.getDecomposeCount(*this);
530+
531+
if (decomposeCount != otherDecomposeCount) {
532+
if (decomposeCount < otherDecomposeCount)
533+
found = pair;
534+
535+
continue;
536+
}
537+
}
538+
539+
if (*comparison > 0) {
540+
// Otherwise, if the new rule is less canonical than the best one so
541+
// far, it becomes the new candidate for elimination.
542+
found = pair;
543+
continue;
544+
}
481545
}
482546
}
483547

lib/AST/RequirementMachine/RewriteLoop.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,10 @@ class RewriteLoop {
431431
SmallVector<unsigned, 1> RulesInEmptyContext;
432432

433433
/// Cached value for getProjectionCount().
434-
unsigned ProjectionCount : 30;
434+
unsigned ProjectionCount : 15;
435+
436+
/// Cached value for getDecomposeCount().
437+
unsigned DecomposeCount : 15;
435438

436439
/// Loops are deleted once they no longer contain rules in empty context,
437440
/// since at that point they don't participate in minimization and do not
@@ -447,6 +450,7 @@ class RewriteLoop {
447450
RewriteLoop(MutableTerm basepoint, RewritePath path)
448451
: Basepoint(basepoint), Path(path) {
449452
ProjectionCount = 0;
453+
DecomposeCount = 0;
450454

451455
Deleted = 0;
452456

@@ -476,6 +480,8 @@ class RewriteLoop {
476480

477481
unsigned getProjectionCount(const RewriteSystem &system) const;
478482

483+
unsigned getDecomposeCount(const RewriteSystem &system) const;
484+
479485
void findProtocolConformanceRules(
480486
llvm::SmallDenseMap<const ProtocolDecl *,
481487
ProtocolConformanceRules, 2> &result,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: %target-swift-frontend -typecheck %s -debug-generic-signatures -requirement-machine-protocol-signatures=on 2>&1 | %FileCheck %s
2+
3+
struct G<T> {}
4+
5+
protocol P {
6+
associatedtype T where T == G<X>
7+
8+
associatedtype X
9+
}
10+
11+
protocol Q {
12+
associatedtype T where T == G<Y>
13+
14+
associatedtype Y
15+
}
16+
17+
// Make sure that substitution simplification records a projection path for
18+
// the induced rule.
19+
20+
// CHECK-LABEL: simplify_concrete_substitutions_projection.(file).R1@
21+
// CHECK-NEXT: Requirement signature: <Self where Self.[R1]T : P, Self.[R1]T : Q>
22+
23+
protocol R1 {
24+
associatedtype T where T : P, T : Q
25+
}
26+
27+
// CHECK-LABEL: simplify_concrete_substitutions_projection.(file).R2@
28+
// CHECK-NEXT: Requirement signature: <Self where Self.[R2]T : P, Self.[R2]T : Q>
29+
30+
protocol R2 {
31+
associatedtype T where T : P, T : Q, T.X == T.Y
32+
}

0 commit comments

Comments
 (0)