30
30
// they are added by completion, or they are redundant rules written by the
31
31
// user.
32
32
//
33
- // Using the 3-cells that generate the homotopy relation on rewrite paths,
33
+ // Using the rewrite loops that generate the homotopy relation on rewrite paths,
34
34
// decompositions can be found for all "derived" conformance rules, producing
35
35
// a minimal set of generating conformances.
36
36
//
69
69
using namespace swift ;
70
70
using namespace rewriting ;
71
71
72
- // / Finds all protocol conformance rules appearing in a 3-cell , both without
73
- // / context, and with a non-empty left context. Applications of rules with a
74
- // / non-empty right context are ignored.
72
+ // / Finds all protocol conformance rules appearing in a rewrite loop , both
73
+ // / in empty context, and with a non-empty left context. Applications of rules
74
+ // / with a non-empty right context are ignored.
75
75
// /
76
76
// / The rules are organized by protocol. For each protocol, the first element
77
77
// / of the pair stores conformance rules that appear without context. The
78
78
// / second element of the pair stores rules that appear with non-empty left
79
79
// / context. For each such rule, the left prefix is also stored alongside.
80
- void HomotopyGenerator ::findProtocolConformanceRules (
80
+ void RewriteLoop ::findProtocolConformanceRules (
81
81
llvm::SmallDenseMap<const ProtocolDecl *,
82
- std::pair<SmallVector<unsigned , 2 >,
83
- SmallVector<std::pair<MutableTerm, unsigned >, 2 >>>
84
- &result,
82
+ ProtocolConformanceRules, 2 > &result,
85
83
const RewriteSystem &system) const {
86
84
87
- auto redundantRules = Path. findRulesAppearingOnceInEmptyContext ();
85
+ auto redundantRules = findRulesAppearingOnceInEmptyContext (system );
88
86
89
87
bool foundAny = false ;
90
88
for (unsigned ruleID : redundantRules) {
91
89
const auto &rule = system.getRule (ruleID);
90
+
92
91
if (auto *proto = rule.isProtocolConformanceRule ()) {
93
- result[proto].first .push_back (ruleID);
92
+ if (rule.isIdentityConformanceRule ()) {
93
+ result[proto].SawIdentityConformance = true ;
94
+ continue ;
95
+ }
96
+
97
+ result[proto].RulesInEmptyContext .push_back (ruleID);
94
98
foundAny = true ;
95
99
}
96
100
}
97
101
98
102
if (!foundAny)
99
103
return ;
100
104
101
- MutableTerm term = Basepoint;
105
+ RewritePathEvaluator evaluator ( Basepoint) ;
102
106
103
107
// Now look for rewrite steps with conformance rules in empty right context,
104
- // that is something like X.(Y.[P] => Z ) (or it's inverse, X.(Z => Y.[P])).
108
+ // that is something like X.(Y.[P] => Y ) (or it's inverse, X.(Y => Y.[P])).
105
109
for (const auto &step : Path) {
106
- switch (step.Kind ) {
107
- case RewriteStep::ApplyRewriteRule: {
108
- const auto &rule = system.getRule (step.RuleID );
109
- if (auto *proto = rule.isProtocolConformanceRule ()) {
110
- if (step.StartOffset > 0 &&
111
- step.EndOffset == 0 ) {
112
- // Record the prefix term that is left unchanged by this rewrite step.
113
- //
114
- // In the above example where the rewrite step is X.(Y.[P] => Z),
115
- // the prefix term is 'X'.
116
- MutableTerm prefix (term.begin (), term.begin () + step.StartOffset );
117
- result[proto].second .emplace_back (prefix, step.RuleID );
110
+ if (!evaluator.isInContext ()) {
111
+ switch (step.Kind ) {
112
+ case RewriteStep::ApplyRewriteRule: {
113
+ const auto &rule = system.getRule (step.RuleID );
114
+
115
+ if (rule.isIdentityConformanceRule ())
116
+ break ;
117
+
118
+ if (auto *proto = rule.isProtocolConformanceRule ()) {
119
+ if (step.StartOffset > 0 &&
120
+ step.EndOffset == 0 ) {
121
+ // Record the prefix term that is left unchanged by this rewrite step.
122
+ //
123
+ // In the above example where the rewrite step is X.(Y.[P] => Z),
124
+ // the prefix term is 'X'.
125
+ const auto &term = evaluator.getCurrentTerm ();
126
+ MutableTerm prefix (term.begin (), term.begin () + step.StartOffset );
127
+ result[proto].RulesInContext .emplace_back (prefix, step.RuleID );
128
+ }
118
129
}
119
- }
120
130
121
- break ;
122
- }
131
+ break ;
132
+ }
123
133
124
- case RewriteStep::AdjustConcreteType:
125
- break ;
134
+ case RewriteStep::AdjustConcreteType:
135
+ case RewriteStep::Shift:
136
+ case RewriteStep::Decompose:
137
+ break ;
138
+ }
126
139
}
127
140
128
- step.apply (term , system);
141
+ step.apply (evaluator , system);
129
142
}
130
143
}
131
144
@@ -152,6 +165,13 @@ RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides(
152
165
" Canonical conformance term should simplify in one step" );
153
166
154
167
const auto &step = *steps.begin ();
168
+
169
+ #ifndef NDEBUG
170
+ const auto &rule = getRule (step.RuleID );
171
+ assert (rule.isProtocolConformanceRule ());
172
+ assert (!rule.isIdentityConformanceRule ());
173
+ #endif
174
+
155
175
assert (step.Kind == RewriteStep::ApplyRewriteRule);
156
176
assert (step.EndOffset == 0 );
157
177
assert (!step.Inverse );
@@ -177,6 +197,7 @@ RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides(
177
197
SmallVectorImpl<unsigned > &result) const {
178
198
const auto &rule = getRule (ruleID);
179
199
assert (rule.isProtocolConformanceRule ());
200
+ assert (!rule.isIdentityConformanceRule ());
180
201
181
202
// Compute domain(V).
182
203
const auto &lhs = rule.getLHS ();
@@ -253,14 +274,12 @@ RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides(
253
274
void RewriteSystem::computeCandidateConformancePaths (
254
275
llvm::MapVector<unsigned ,
255
276
std::vector<SmallVector<unsigned , 2 >>> &conformancePaths) const {
256
- for (const auto &loop : HomotopyGenerators ) {
277
+ for (const auto &loop : Loops ) {
257
278
if (loop.isDeleted ())
258
279
continue ;
259
280
260
281
llvm::SmallDenseMap<const ProtocolDecl *,
261
- std::pair<SmallVector<unsigned , 2 >,
262
- SmallVector<std::pair<MutableTerm, unsigned >, 2 >>>
263
- result;
282
+ ProtocolConformanceRules, 2 > result;
264
283
265
284
loop.findProtocolConformanceRules (result, *this );
266
285
@@ -275,21 +294,18 @@ void RewriteSystem::computeCandidateConformancePaths(
275
294
276
295
for (const auto &pair : result) {
277
296
const auto *proto = pair.first ;
278
- const auto ¬InContext = pair.second .first ;
279
- const auto &inContext = pair.second .second ;
297
+ const auto &inEmptyContext = pair.second .RulesInEmptyContext ;
298
+ const auto &inContext = pair.second .RulesInContext ;
299
+ bool sawIdentityConformance = pair.second .SawIdentityConformance ;
280
300
281
301
// No rules appear without context.
282
- if (notInContext.empty ())
283
- continue ;
284
-
285
- // No replacement rules.
286
- if (notInContext.size () == 1 && inContext.empty ())
302
+ if (inEmptyContext.empty ())
287
303
continue ;
288
304
289
305
if (Debug.contains (DebugFlags::GeneratingConformances)) {
290
306
llvm::dbgs () << " * Protocol " << proto->getName () << " :\n " ;
291
307
llvm::dbgs () << " ** Conformance rules not in context:\n " ;
292
- for (unsigned ruleID : notInContext ) {
308
+ for (unsigned ruleID : inEmptyContext ) {
293
309
llvm::dbgs () << " -- (#" << ruleID << " ) " << getRule (ruleID) << " \n " ;
294
310
}
295
311
@@ -300,6 +316,10 @@ void RewriteSystem::computeCandidateConformancePaths(
300
316
llvm::dbgs () << " (#" << ruleID << " ) " << getRule (ruleID) << " \n " ;
301
317
}
302
318
319
+ if (sawIdentityConformance) {
320
+ llvm::dbgs () << " ** Equivalent to identity conformance\n " ;
321
+ }
322
+
303
323
llvm::dbgs () << " \n " ;
304
324
}
305
325
@@ -310,8 +330,8 @@ void RewriteSystem::computeCandidateConformancePaths(
310
330
//
311
331
// (T.[P] => T) := (T'.[P])
312
332
// (T'.[P] => T') := (T.[P])
313
- for (unsigned candidateRuleID : notInContext ) {
314
- for (unsigned otherRuleID : notInContext ) {
333
+ for (unsigned candidateRuleID : inEmptyContext ) {
334
+ for (unsigned otherRuleID : inEmptyContext ) {
315
335
if (otherRuleID == candidateRuleID)
316
336
continue ;
317
337
@@ -321,11 +341,22 @@ void RewriteSystem::computeCandidateConformancePaths(
321
341
}
322
342
}
323
343
324
- // Suppose a 3-cell contains a conformance rule (T.[P] => T) in an empty
325
- // context, and a conformance rule (V.[P] => V) with a non-empty left
344
+ // If a rewrite loop contains a conformance rule (T.[P] => T) together
345
+ // with the identity conformance ([P].[P] => [P]), both in empty context,
346
+ // the conformance rule (T.[P] => T) is equivalent to the *empty product*
347
+ // of conformance rules; that is, it is trivially redundant.
348
+ if (sawIdentityConformance) {
349
+ for (unsigned candidateRuleID : inEmptyContext) {
350
+ SmallVector<unsigned , 2 > emptyPath;
351
+ conformancePaths[candidateRuleID].push_back (emptyPath);
352
+ }
353
+ }
354
+
355
+ // Suppose a rewrite loop contains a conformance rule (T.[P] => T) in
356
+ // empty context, and a conformance rule (V.[P] => V) in non-empty left
326
357
// context U.
327
358
//
328
- // The 3-cell looks something like this:
359
+ // The rewrite loop looks something like this:
329
360
//
330
361
// ... ⊗ (T.[P] => T) ⊗ ... ⊗ U.(V => V.[P]) ⊗ ...
331
362
// ^ ^
@@ -360,7 +391,7 @@ void RewriteSystem::computeCandidateConformancePaths(
360
391
361
392
// This decomposition defines a conformance access path for each
362
393
// conformance rule we saw in empty context.
363
- for (unsigned otherRuleID : notInContext )
394
+ for (unsigned otherRuleID : inEmptyContext )
364
395
conformancePaths[otherRuleID].push_back (conformancePath);
365
396
}
366
397
}
@@ -449,6 +480,11 @@ bool RewriteSystem::isValidRefinementPath(
449
480
void RewriteSystem::dumpConformancePath (
450
481
llvm::raw_ostream &out,
451
482
const SmallVectorImpl<unsigned > &path) const {
483
+ if (path.empty ()) {
484
+ out << " 1" ;
485
+ return ;
486
+ }
487
+
452
488
for (unsigned ruleID : path)
453
489
out << " (" << getRule (ruleID).getLHS () << " )" ;
454
490
}
@@ -483,6 +519,9 @@ void RewriteSystem::verifyGeneratingConformanceEquations(
483
519
(void ) simplify (baseTerm);
484
520
485
521
for (const auto &path : pair.second ) {
522
+ if (path.empty ())
523
+ continue ;
524
+
486
525
const auto &otherRule = getRule (path.back ());
487
526
auto *otherProto = otherRule.getLHS ().back ().getProtocol ();
488
527
@@ -568,6 +607,9 @@ static const ProtocolDecl *getParentConformanceForTerm(Term lhs) {
568
607
// / conformance rules.
569
608
void RewriteSystem::computeGeneratingConformances (
570
609
llvm::DenseSet<unsigned > &redundantConformances) {
610
+ // All conformance rules, sorted by left hand side.
611
+ SmallVector<std::pair<unsigned , Term>, 4 > conformanceRules;
612
+
571
613
// Maps a conformance rule to a conformance path deriving the subject type's
572
614
// base type. For example, consider the following conformance rule:
573
615
//
@@ -586,27 +628,33 @@ void RewriteSystem::computeGeneratingConformances(
586
628
// the form [P].[Q] => [P].
587
629
llvm::DenseSet<unsigned > protocolRefinements;
588
630
589
- // Prepare the initial set of equations: every non-redundant conformance rule
590
- // can be expressed as itself.
631
+ // Prepare the initial set of equations.
591
632
for (unsigned ruleID : indices (Rules)) {
592
633
const auto &rule = getRule (ruleID);
634
+ if (rule.isPermanent ())
635
+ continue ;
636
+
593
637
if (rule.isRedundant ())
594
638
continue ;
595
639
596
640
if (!rule.isProtocolConformanceRule ())
597
641
continue ;
598
642
643
+ auto lhs = rule.getLHS ();
644
+ conformanceRules.emplace_back (ruleID, lhs);
645
+
646
+ // Initially, every non-redundant conformance rule can be expressed
647
+ // as itself.
599
648
SmallVector<unsigned , 2 > path;
600
649
path.push_back (ruleID);
601
650
conformancePaths[ruleID].push_back (path);
602
651
652
+ // Save protocol refinement relations in a side table.
603
653
if (rule.isProtocolRefinementRule ()) {
604
654
protocolRefinements.insert (ruleID);
605
655
continue ;
606
656
}
607
657
608
- auto lhs = rule.getLHS ();
609
-
610
658
// Record a parent path if the subject type itself requires a non-trivial
611
659
// conformance path to derive.
612
660
if (auto *parentProto = getParentConformanceForTerm (lhs)) {
@@ -645,22 +693,33 @@ void RewriteSystem::computeGeneratingConformances(
645
693
646
694
verifyGeneratingConformanceEquations (conformancePaths);
647
695
696
+ // Sort the list of conformance rules in reverse order; we're going to try
697
+ // to minimize away less canonical rules first.
698
+ std::sort (conformanceRules.begin (), conformanceRules.end (),
699
+ [&](const std::pair<unsigned , Term> &lhs,
700
+ const std::pair<unsigned , Term> &rhs) -> bool {
701
+ return lhs.second .compare (rhs.second , Context) > 0 ;
702
+ });
703
+
648
704
// Find a minimal set of generating conformances.
649
- for (const auto &pair : conformancePaths) {
650
- bool isProtocolRefinement = protocolRefinements.count (pair.first ) > 0 ;
705
+ for (const auto &pair : conformanceRules) {
706
+ unsigned ruleID = pair.first ;
707
+ const auto &paths = conformancePaths[ruleID];
651
708
652
- for (const auto &path : pair.second ) {
709
+ bool isProtocolRefinement = protocolRefinements.count (ruleID) > 0 ;
710
+
711
+ for (const auto &path : paths) {
653
712
// Only consider a protocol refinement rule to be redundant if it is
654
713
// witnessed by a composition of other protocol refinement rules.
655
714
if (isProtocolRefinement && !isValidRefinementPath (path))
656
715
continue ;
657
716
658
717
llvm::SmallDenseSet<unsigned , 4 > visited;
659
- visited.insert (pair. first );
718
+ visited.insert (ruleID );
660
719
661
720
if (isValidConformancePath (visited, redundantConformances, path,
662
721
parentPaths, conformancePaths)) {
663
- redundantConformances.insert (pair. first );
722
+ redundantConformances.insert (ruleID );
664
723
break ;
665
724
}
666
725
}
@@ -700,4 +759,4 @@ void RewriteSystem::computeGeneratingConformances(
700
759
llvm::dbgs () << " - " << getRule (pair.first ) << " \n " ;
701
760
}
702
761
}
703
- }
762
+ }
0 commit comments