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