1
+ // ===--- GeneratingConformances.cpp - Reasoning about conformance rules ---===//
2
+ //
3
+ // This source file is part of the Swift.org open source project
4
+ //
5
+ // Copyright (c) 2021 Apple Inc. and the Swift project authors
6
+ // Licensed under Apache License v2.0 with Runtime Library Exception
7
+ //
8
+ // See https://swift.org/LICENSE.txt for license information
9
+ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10
+ //
11
+ // ===----------------------------------------------------------------------===//
12
+ //
13
+ // This file implements an algorithm to find a minimal set of "generating
14
+ // conformances", which are rules (V1.[P1] => V1), ..., (Vn.[Pn] => Vn) such
15
+ // that any valid term of the form T.[P] can be written as a product of terms
16
+ // (Vi.[Pi]), where each Vi.[Pi] is a left hand side of a generating
17
+ // conformance.
18
+ //
19
+ // ===----------------------------------------------------------------------===//
20
+
21
+ #include " swift/Basic/Defer.h"
22
+ #include " swift/Basic/Range.h"
23
+ #include " llvm/ADT/DenseMap.h"
24
+ #include " llvm/ADT/DenseSet.h"
25
+ #include " llvm/Support/Debug.h"
26
+ #include " llvm/Support/raw_ostream.h"
27
+ #include < algorithm>
28
+ #include " RewriteSystem.h"
29
+
30
+ using namespace swift ;
31
+ using namespace rewriting ;
32
+
33
+ void HomotopyGenerator::findProtocolConformanceRules (
34
+ SmallVectorImpl<unsigned > ¬InContext,
35
+ SmallVectorImpl<std::pair<MutableTerm, unsigned >> &inContext,
36
+ const RewriteSystem &system) const {
37
+
38
+ MutableTerm term = Basepoint;
39
+
40
+ for (const auto &step : Path) {
41
+ switch (step.Kind ) {
42
+ case RewriteStep::ApplyRewriteRule: {
43
+ const auto &rule = system.getRule (step.RuleID );
44
+ if (!rule.isProtocolConformanceRule ())
45
+ break ;
46
+
47
+ if (!step.isInContext ()) {
48
+ assert (std::find (notInContext.begin (),
49
+ notInContext.end (),
50
+ step.RuleID ) == notInContext.end () &&
51
+ " A conformance rule appears more than once without context?" );
52
+ notInContext.push_back (step.RuleID );
53
+ } else if (step.EndOffset == 0 ) {
54
+ assert (step.StartOffset > 0 );
55
+ MutableTerm prefix (term.begin (), term.begin () + step.StartOffset );
56
+ inContext.emplace_back (prefix, step.RuleID );
57
+ }
58
+ break ;
59
+ }
60
+
61
+ case RewriteStep::AdjustConcreteType:
62
+ break ;
63
+ }
64
+
65
+ step.apply (term, system);
66
+ }
67
+
68
+ assert (notInContext.empty () || !inContext.empty () &&
69
+ " A conformance rule not based on another conformance rule?" );
70
+ }
71
+
72
+ // / Write the term as a product of left hand sides of protocol conformance
73
+ // / rules.
74
+ // /
75
+ // / The term should already be simplified, except for a protocol symbol
76
+ // / at the end.
77
+ void
78
+ RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides (
79
+ MutableTerm term, SmallVectorImpl<unsigned > &result) const {
80
+ assert (term.back ().getKind () == Symbol::Kind::Protocol);
81
+
82
+ // If T is canonical and T.[P] => T, then by confluence, T.[P]
83
+ // reduces to T in a single step, via a rule V.[P] => V, where
84
+ // T == U.V.
85
+ RewritePath steps;
86
+ bool simplified = simplify (term, &steps);
87
+ if (!simplified) {
88
+ llvm::errs () << " Term does not conform to protocol: " << term << " \n " ;
89
+ abort ();
90
+ }
91
+
92
+ assert (steps.size () == 1 &&
93
+ " Canonical conformance term should simplify in one step" );
94
+
95
+ const auto &step = *steps.begin ();
96
+ assert (step.Kind == RewriteStep::ApplyRewriteRule);
97
+ assert (step.EndOffset == 0 );
98
+ assert (!step.Inverse );
99
+
100
+ const auto &rule = getRule (step.RuleID );
101
+ assert (rule.isProtocolConformanceRule ());
102
+
103
+ // If |U| > 0, recurse with the term U.[domain(V)]. Since T is
104
+ // canonical, we know that U is canonical as well.
105
+ if (step.StartOffset > 0 ) {
106
+ // Build the term U.
107
+ MutableTerm prefix (term.begin (), term.begin () + step.StartOffset );
108
+
109
+ // Compute domain(V).
110
+ const auto &lhs = rule.getLHS ();
111
+ auto protocols = lhs[0 ].getProtocols ();
112
+ assert (protocols.size () == 1 );
113
+
114
+ // Build the term U.[domain(V)].
115
+ prefix.add (Symbol::forProtocol (protocols[0 ], Context));
116
+
117
+ decomposeTermIntoConformanceRuleLeftHandSides (prefix, result);
118
+ }
119
+
120
+ result.push_back (step.RuleID );
121
+ }
122
+
123
+ // / Use homotopy information to discover all ways of writing the left hand side
124
+ // / of each conformance rule as a product of left hand sides of other conformance
125
+ // / rules.
126
+ // /
127
+ // / Each conformance rule (Vi.[P] => Vi) can always be written in terms of itself,
128
+ // / so the first term of each disjunction is always (Vi.[P] => Vi).
129
+ // /
130
+ // / Conformance rules can also be circular, so not every choice of disjunctions
131
+ // / produces a valid result; for example, if you have these definitions:
132
+ // /
133
+ // / protocol P {
134
+ // / associatedtype T : P
135
+ // / }
136
+ // /
137
+ // / struct G<X, Y> where X : P, X.T == Y, Y : P, Y.T == X {}
138
+ // /
139
+ // / We have three conformance rules:
140
+ // /
141
+ // / [P:T].[P] => [P:T]
142
+ // / <X>.[P] => <X>
143
+ // / <Y>.[P] => <Y>
144
+ // /
145
+ // / The first rule, <X>.[P] => <X> has an alternate conformance path:
146
+ // /
147
+ // / (<Y>.[P]).([P:T].[P])
148
+ // /
149
+ // / The second rule similarly has an alternate conformance path:
150
+ // /
151
+ // / (<X>.[P]).([P:T].[P])
152
+ // /
153
+ // / This gives us the following initial set of candidate conformance paths:
154
+ // /
155
+ // / [P:T].[P] := ([P:T].[P])
156
+ // / <X>.[P] := (<X>.[P]) ∨ (<Y>.[P]).([P:T].[P])
157
+ // / <Y>.[P] := (<Y>.[P]) ∨ (<X>.[P]).([P:T].[P])
158
+ // /
159
+ // / One valid solution is the following set of assignments:
160
+ // /
161
+ // / [P:T].[P] := ([P:T].[P])
162
+ // / <X>.[P] := (<X>.[P])
163
+ // / <Y>.[P] := (<X>.[P]).([P:T].[P])
164
+ // /
165
+ // / That is, we can choose to eliminate <X>.[P], but not <Y>.[P], or vice
166
+ // / versa; but it is never valid to eliminate both.
167
+ void RewriteSystem::computeCandidateConformancePaths (
168
+ llvm::MapVector<unsigned ,
169
+ std::vector<SmallVector<unsigned , 2 >>> &conformancePaths) const {
170
+ for (const auto &loop : HomotopyGenerators) {
171
+ if (loop.isDeleted ())
172
+ continue ;
173
+
174
+ SmallVector<unsigned , 2 > notInContext;
175
+ SmallVector<std::pair<MutableTerm, unsigned >, 2 > inContext;
176
+
177
+ loop.findProtocolConformanceRules (notInContext, inContext, *this );
178
+
179
+ if (notInContext.empty ())
180
+ continue ;
181
+
182
+ // We must either have multiple conformance rules in empty context, or
183
+ // at least one conformance rule in non-empty context. Otherwise, we have
184
+ // a conformance rule which is written as a series of same-type rules,
185
+ // which doesn't make sense.
186
+ assert (inContext.size () > 0 || notInContext.size () > 1 );
187
+
188
+ if (Debug.contains (DebugFlags::GeneratingConformances)) {
189
+ llvm::dbgs () << " Candidate homotopy generator: " ;
190
+ loop.dump (llvm::dbgs (), *this );
191
+ llvm::dbgs () << " \n " ;
192
+
193
+ llvm::dbgs () << " * Conformance rules not in context:\n " ;
194
+ for (unsigned ruleID : notInContext) {
195
+ llvm::dbgs () << " - (#" << ruleID << " ) " << getRule (ruleID) << " \n " ;
196
+ }
197
+
198
+ llvm::dbgs () << " * Conformance rules in context:\n " ;
199
+ for (auto pair : inContext) {
200
+ llvm::dbgs () << " - " << pair.first ;
201
+ unsigned ruleID = pair.second ;
202
+ llvm::dbgs () << " (#" << ruleID << " ) " << getRule (ruleID) << " \n " ;
203
+ }
204
+ }
205
+
206
+ // Suppose a 3-cell contains a conformance rule (T.[P] => T) in an empty
207
+ // context, and a conformance rule (V.[P] => V) with a possibly non-empty
208
+ // left context U and empty right context.
209
+ //
210
+ // We can decompose U into a product of conformance rules:
211
+ //
212
+ // (V1.[P1] => V1)...(Vn.[Pn] => Vn),
213
+ //
214
+ // Now, we can record a candidate decomposition of (T.[P] => T) as a
215
+ // product of conformance rules:
216
+ //
217
+ // (T.[P] => T) := (V1.[P1] => V1)...(Vn.[Pn] => Vn).(V.[P] => V)
218
+ //
219
+ // Now if U is empty, this becomes the trivial candidate:
220
+ //
221
+ // (T.[P] => T) := (V.[P] => V)
222
+ SmallVector<SmallVector<unsigned , 2 >, 2 > candidatePaths;
223
+ for (auto pair : inContext) {
224
+ // We have a term U, and a rule V.[P] => V.
225
+ const auto &rule = getRule (pair.second );
226
+ assert (rule.isProtocolConformanceRule ());
227
+
228
+ SmallVector<unsigned , 2 > conformancePath;
229
+
230
+ // Simplify U to get U'.
231
+ MutableTerm term = pair.first ;
232
+ (void ) simplify (term);
233
+
234
+ // Compute domain(V).
235
+ const auto &lhs = rule.getLHS ();
236
+ auto protocols = lhs[0 ].getProtocols ();
237
+ assert (protocols.size () == 1 );
238
+
239
+ // Build the term U'.[domain(V)].
240
+ term.add (Symbol::forProtocol (protocols[0 ], Context));
241
+
242
+ // Write U'.[domain(V)] as a product of left hand sides of protocol
243
+ // conformance rules.
244
+ decomposeTermIntoConformanceRuleLeftHandSides (term, conformancePath);
245
+
246
+ // Add the rule V => V.[P].
247
+ conformancePath.push_back (pair.second );
248
+
249
+ candidatePaths.push_back (conformancePath);
250
+ }
251
+
252
+ for (unsigned candidateRuleID : notInContext) {
253
+ // If multiple conformance rules appear in an empty context, each one
254
+ // can be replaced with any other conformance rule.
255
+ for (unsigned otherRuleID : notInContext) {
256
+ if (otherRuleID == candidateRuleID)
257
+ continue ;
258
+
259
+ SmallVector<unsigned , 2 > path;
260
+ path.push_back (otherRuleID);
261
+ conformancePaths[candidateRuleID].push_back (path);
262
+ }
263
+
264
+ // If conformance rules appear in non-empty context, they define a
265
+ // conformance access path for each conformance rule in empty context.
266
+ for (const auto &path : candidatePaths) {
267
+ conformancePaths[candidateRuleID].push_back (path);
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ bool RewriteSystem::isValidConformancePath (
274
+ llvm::SmallDenseSet<unsigned , 4 > &visited,
275
+ llvm::DenseSet<unsigned > &redundantConformances,
276
+ const llvm::SmallVectorImpl<unsigned > &path,
277
+ const llvm::MapVector<unsigned ,
278
+ std::vector<SmallVector<unsigned , 2 >>>
279
+ &conformancePaths) const {
280
+ for (unsigned ruleID : path) {
281
+ if (visited.count (ruleID) > 0 )
282
+ return false ;
283
+
284
+ if (!redundantConformances.count (ruleID))
285
+ continue ;
286
+
287
+ SWIFT_DEFER {
288
+ visited.erase (ruleID);
289
+ };
290
+ visited.insert (ruleID);
291
+
292
+ auto found = conformancePaths.find (ruleID);
293
+ assert (found != conformancePaths.end ());
294
+
295
+ bool foundValidConformancePath = false ;
296
+ for (const auto &otherPath : found->second ) {
297
+ if (isValidConformancePath (visited, redundantConformances,
298
+ otherPath, conformancePaths)) {
299
+ foundValidConformancePath = true ;
300
+ break ;
301
+ }
302
+ }
303
+
304
+ if (!foundValidConformancePath)
305
+ return false ;
306
+ }
307
+
308
+ return true ;
309
+ }
310
+
311
+ void RewriteSystem::computeGeneratingConformances (
312
+ llvm::DenseSet<unsigned > &redundantConformances) {
313
+ llvm::MapVector<unsigned , std::vector<SmallVector<unsigned , 2 >>> conformancePaths;
314
+
315
+ for (unsigned ruleID : indices (Rules)) {
316
+ const auto &rule = getRule (ruleID);
317
+ if (rule.isProtocolConformanceRule ()) {
318
+ SmallVector<unsigned , 2 > path;
319
+ path.push_back (ruleID);
320
+ conformancePaths[ruleID].push_back (path);
321
+ }
322
+ }
323
+
324
+ computeCandidateConformancePaths (conformancePaths);
325
+
326
+ if (Debug.contains (DebugFlags::GeneratingConformances)) {
327
+ llvm::dbgs () << " Initial set of equations:\n " ;
328
+ for (const auto &pair : conformancePaths) {
329
+ llvm::dbgs () << " - " << getRule (pair.first ).getLHS () << " := " ;
330
+
331
+ bool first = true ;
332
+ for (const auto &path : pair.second ) {
333
+ if (!first)
334
+ llvm::dbgs () << " ∨ " ;
335
+ else
336
+ first = false ;
337
+ for (unsigned ruleID : path)
338
+ llvm::dbgs () << " (" << getRule (ruleID).getLHS () << " )" ;
339
+ }
340
+
341
+ llvm::dbgs () << " \n " ;
342
+ }
343
+ }
344
+
345
+ for (const auto &pair : conformancePaths) {
346
+ for (const auto &path : pair.second ) {
347
+ llvm::SmallDenseSet<unsigned , 4 > visited;
348
+ visited.insert (pair.first );
349
+
350
+ if (isValidConformancePath (visited, redundantConformances,
351
+ path, conformancePaths)) {
352
+ redundantConformances.insert (pair.first );
353
+ break ;
354
+ }
355
+ }
356
+ }
357
+
358
+ if (Debug.contains (DebugFlags::GeneratingConformances)) {
359
+ llvm::dbgs () << " Generating conformances:\n " ;
360
+
361
+ for (const auto &pair : conformancePaths) {
362
+ if (redundantConformances.count (pair.first ) > 0 )
363
+ continue ;
364
+
365
+ llvm::dbgs () << " - " << getRule (pair.first ) << " \n " ;
366
+ }
367
+ }
368
+ }
0 commit comments