Skip to content

Commit 2335116

Browse files
authored
Merge pull request #39606 from slavapestov/rqm-requirement-signature-minimization
RequirementMachine: More progress toward computing protocol requirement signatures
2 parents 92520d5 + 62de909 commit 2335116

File tree

11 files changed

+467
-149
lines changed

11 files changed

+467
-149
lines changed

include/swift/AST/ASTContext.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,11 @@ class ASTContext final {
12011201
bool isRecursivelyConstructingRequirementMachine(
12021202
CanGenericSignature sig);
12031203

1204+
/// Retrieve or create a term rewriting system for answering queries on
1205+
/// type parameters written against the given protocol requirement signature.
1206+
rewriting::RequirementMachine *getOrCreateRequirementMachine(
1207+
const ProtocolDecl *proto);
1208+
12041209
/// Retrieve a generic signature with a single unconstrained type parameter,
12051210
/// like `<T>`.
12061211
CanGenericSignature getSingleGenericParameterSignature() const;

lib/AST/ASTContext.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,6 +1979,15 @@ bool ASTContext::isRecursivelyConstructingRequirementMachine(
19791979
return rewriteCtx->isRecursivelyConstructingRequirementMachine(sig);
19801980
}
19811981

1982+
rewriting::RequirementMachine *
1983+
ASTContext::getOrCreateRequirementMachine(const ProtocolDecl *proto) {
1984+
auto &rewriteCtx = getImpl().TheRewriteContext;
1985+
if (!rewriteCtx)
1986+
rewriteCtx.reset(new rewriting::RewriteContext(*this));
1987+
1988+
return rewriteCtx->getRequirementMachine(proto);
1989+
}
1990+
19821991
Optional<llvm::TinyPtrVector<ValueDecl *>>
19831992
OverriddenDeclsRequest::getCachedResult() const {
19841993
auto decl = std::get<0>(getStorage());

lib/AST/RequirementMachine/Debug.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ enum class DebugFlags : unsigned {
4343

4444
/// Print debug output from the generating conformances algorithm.
4545
GeneratingConformances = (1<<7),
46+
47+
/// Print debug output from the protocol dependency graph.
48+
ProtocolDependencies = (1<<8),
49+
50+
/// Print debug output from generic signature minimization.
51+
Minimization = (1<<9),
4652
};
4753

4854
using DebugOptions = OptionSet<DebugFlags>;

lib/AST/RequirementMachine/GeneratingConformances.cpp

Lines changed: 97 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
//
3737
//===----------------------------------------------------------------------===//
3838

39+
#include "swift/AST/Decl.h"
3940
#include "swift/Basic/Defer.h"
4041
#include "swift/Basic/Range.h"
4142
#include "llvm/ADT/DenseMap.h"
@@ -51,50 +52,18 @@ using namespace rewriting;
5152
/// Finds all protocol conformance rules appearing in a 3-cell, both without
5253
/// context, and with a non-empty left context. Applications of rules with a
5354
/// non-empty right context are ignored.
55+
///
56+
/// The rules are organized by protocol. For each protocol, the first element
57+
/// of the pair stores conformance rules that appear without context. The
58+
/// second element of the pair stores rules that appear with non-empty left
59+
/// context. For each such rule, the left prefix is also stored alongside.
5460
void HomotopyGenerator::findProtocolConformanceRules(
55-
SmallVectorImpl<unsigned> &notInContext,
56-
SmallVectorImpl<std::pair<MutableTerm, unsigned>> &inContext,
61+
llvm::SmallDenseMap<const ProtocolDecl *,
62+
std::pair<SmallVector<unsigned, 2>,
63+
SmallVector<std::pair<MutableTerm, unsigned>, 2>>>
64+
&result,
5765
const RewriteSystem &system) const {
5866

59-
auto redundancyCandidates = Path.findRulesAppearingOnceInEmptyContext();
60-
if (redundancyCandidates.empty())
61-
return;
62-
63-
for (const auto &step : Path) {
64-
switch (step.Kind) {
65-
case RewriteStep::ApplyRewriteRule: {
66-
const auto &rule = system.getRule(step.RuleID);
67-
if (!rule.isProtocolConformanceRule())
68-
break;
69-
70-
if (!step.isInContext() &&
71-
step.Inverse &&
72-
std::find(redundancyCandidates.begin(),
73-
redundancyCandidates.end(),
74-
step.RuleID) != redundancyCandidates.end()) {
75-
notInContext.push_back(step.RuleID);
76-
}
77-
78-
break;
79-
}
80-
81-
case RewriteStep::AdjustConcreteType:
82-
break;
83-
}
84-
}
85-
86-
if (notInContext.empty())
87-
return;
88-
89-
if (notInContext.size() > 1) {
90-
llvm::errs() << "Multiple conformance rules appear once without context:\n";
91-
for (unsigned ruleID : notInContext)
92-
llvm::errs() << system.getRule(ruleID) << "\n";
93-
dump(llvm::errs(), system);
94-
llvm::errs() << "\n";
95-
abort();
96-
}
97-
9867
MutableTerm term = Basepoint;
9968

10069
for (const auto &step : Path) {
@@ -104,12 +73,16 @@ void HomotopyGenerator::findProtocolConformanceRules(
10473
if (!rule.isProtocolConformanceRule())
10574
break;
10675

107-
if (step.StartOffset > 0 &&
108-
step.EndOffset == 0 &&
109-
rule.getLHS().back() == system.getRule(notInContext[0]).getLHS().back()) {
76+
auto *proto = rule.getLHS().back().getProtocol();
77+
78+
if (!step.isInContext()) {
79+
result[proto].first.push_back(step.RuleID);
80+
} else if (step.StartOffset > 0 &&
81+
step.EndOffset == 0) {
11082
MutableTerm prefix(term.begin(), term.begin() + step.StartOffset);
111-
inContext.emplace_back(prefix, step.RuleID);
83+
result[proto].second.emplace_back(prefix, step.RuleID);
11284
}
85+
11386
break;
11487
}
11588

@@ -119,18 +92,6 @@ void HomotopyGenerator::findProtocolConformanceRules(
11992

12093
step.apply(term, system);
12194
}
122-
123-
if (inContext.empty()) {
124-
notInContext.clear();
125-
return;
126-
}
127-
128-
if (inContext.size() > 1) {
129-
llvm::errs() << "Multiple candidate conformance rules in context?\n";
130-
dump(llvm::errs(), system);
131-
llvm::errs() << "\n";
132-
abort();
133-
}
13495
}
13596

13697
/// Write the term as a product of left hand sides of protocol conformance
@@ -261,89 +222,102 @@ void RewriteSystem::computeCandidateConformancePaths(
261222
if (loop.isDeleted())
262223
continue;
263224

264-
SmallVector<unsigned, 2> notInContext;
265-
SmallVector<std::pair<MutableTerm, unsigned>, 2> inContext;
225+
llvm::SmallDenseMap<const ProtocolDecl *,
226+
std::pair<SmallVector<unsigned, 2>,
227+
SmallVector<std::pair<MutableTerm, unsigned>, 2>>>
228+
result;
266229

267-
loop.findProtocolConformanceRules(notInContext, inContext, *this);
230+
loop.findProtocolConformanceRules(result, *this);
268231

269-
if (notInContext.empty())
232+
if (result.empty())
270233
continue;
271234

272-
// We must either have multiple conformance rules in empty context, or
273-
// at least one conformance rule in non-empty context. Otherwise, we have
274-
// a conformance rule which is written as a series of same-type rules,
275-
// which doesn't make sense.
276-
assert(inContext.size() > 0 || notInContext.size() > 1);
277-
278235
if (Debug.contains(DebugFlags::GeneratingConformances)) {
279236
llvm::dbgs() << "Candidate homotopy generator: ";
280237
loop.dump(llvm::dbgs(), *this);
281238
llvm::dbgs() << "\n";
239+
}
282240

283-
llvm::dbgs() << "* Conformance rules not in context:\n";
284-
for (unsigned ruleID : notInContext) {
285-
llvm::dbgs() << "- (#" << ruleID << ") " << getRule(ruleID) << "\n";
286-
}
241+
for (const auto &pair : result) {
242+
const auto *proto = pair.first;
243+
const auto &notInContext = pair.second.first;
244+
const auto &inContext = pair.second.second;
287245

288-
llvm::dbgs() << "* Conformance rules in context:\n";
289-
for (auto pair : inContext) {
290-
llvm::dbgs() << "- " << pair.first;
291-
unsigned ruleID = pair.second;
292-
llvm::dbgs() << " (#" << ruleID << ") " << getRule(ruleID) << "\n";
246+
// No rules appear without context.
247+
if (notInContext.empty())
248+
continue;
249+
250+
// No replacement rules.
251+
if (notInContext.size() == 1 && inContext.empty())
252+
continue;
253+
254+
if (Debug.contains(DebugFlags::GeneratingConformances)) {
255+
llvm::dbgs() << "* Protocol " << proto->getName() << ":\n";
256+
llvm::dbgs() << "** Conformance rules not in context:\n";
257+
for (unsigned ruleID : notInContext) {
258+
llvm::dbgs() << "-- (#" << ruleID << ") " << getRule(ruleID) << "\n";
259+
}
260+
261+
llvm::dbgs() << "** Conformance rules in context:\n";
262+
for (auto pair : inContext) {
263+
llvm::dbgs() << "-- " << pair.first;
264+
unsigned ruleID = pair.second;
265+
llvm::dbgs() << " (#" << ruleID << ") " << getRule(ruleID) << "\n";
266+
}
267+
268+
llvm::dbgs() << "\n";
293269
}
294270

295-
llvm::dbgs() << "\n";
296-
}
271+
// Suppose a 3-cell contains a conformance rule (T.[P] => T) in an empty
272+
// context, and a conformance rule (V.[P] => V) with a possibly non-empty
273+
// left context U and empty right context.
274+
//
275+
// We can decompose U into a product of conformance rules:
276+
//
277+
// (V1.[P1] => V1)...(Vn.[Pn] => Vn),
278+
//
279+
// Now, we can record a candidate decomposition of (T.[P] => T) as a
280+
// product of conformance rules:
281+
//
282+
// (T.[P] => T) := (V1.[P1] => V1)...(Vn.[Pn] => Vn).(V.[P] => V)
283+
//
284+
// Now if U is empty, this becomes the trivial candidate:
285+
//
286+
// (T.[P] => T) := (V.[P] => V)
287+
SmallVector<SmallVector<unsigned, 2>, 2> candidatePaths;
288+
for (auto pair : inContext) {
289+
// We have a term U, and a rule V.[P] => V.
290+
SmallVector<unsigned, 2> conformancePath;
297291

298-
// Suppose a 3-cell contains a conformance rule (T.[P] => T) in an empty
299-
// context, and a conformance rule (V.[P] => V) with a possibly non-empty
300-
// left context U and empty right context.
301-
//
302-
// We can decompose U into a product of conformance rules:
303-
//
304-
// (V1.[P1] => V1)...(Vn.[Pn] => Vn),
305-
//
306-
// Now, we can record a candidate decomposition of (T.[P] => T) as a
307-
// product of conformance rules:
308-
//
309-
// (T.[P] => T) := (V1.[P1] => V1)...(Vn.[Pn] => Vn).(V.[P] => V)
310-
//
311-
// Now if U is empty, this becomes the trivial candidate:
312-
//
313-
// (T.[P] => T) := (V.[P] => V)
314-
SmallVector<SmallVector<unsigned, 2>, 2> candidatePaths;
315-
for (auto pair : inContext) {
316-
// We have a term U, and a rule V.[P] => V.
317-
SmallVector<unsigned, 2> conformancePath;
318-
319-
// Simplify U to get U'.
320-
MutableTerm term = pair.first;
321-
(void) simplify(term);
322-
323-
// Write U'.[domain(V)] as a product of left hand sides of protocol
324-
// conformance rules.
325-
decomposeTermIntoConformanceRuleLeftHandSides(term, pair.second,
326-
conformancePath);
327-
328-
candidatePaths.push_back(conformancePath);
329-
}
292+
// Simplify U to get U'.
293+
MutableTerm term = pair.first;
294+
(void) simplify(term);
330295

331-
for (unsigned candidateRuleID : notInContext) {
332-
// If multiple conformance rules appear in an empty context, each one
333-
// can be replaced with any other conformance rule.
334-
for (unsigned otherRuleID : notInContext) {
335-
if (otherRuleID == candidateRuleID)
336-
continue;
296+
// Write U'.[domain(V)] as a product of left hand sides of protocol
297+
// conformance rules.
298+
decomposeTermIntoConformanceRuleLeftHandSides(term, pair.second,
299+
conformancePath);
337300

338-
SmallVector<unsigned, 2> path;
339-
path.push_back(otherRuleID);
340-
conformancePaths[candidateRuleID].push_back(path);
301+
candidatePaths.push_back(conformancePath);
341302
}
342303

343-
// If conformance rules appear in non-empty context, they define a
344-
// conformance access path for each conformance rule in empty context.
345-
for (const auto &path : candidatePaths) {
346-
conformancePaths[candidateRuleID].push_back(path);
304+
for (unsigned candidateRuleID : notInContext) {
305+
// If multiple conformance rules appear in an empty context, each one
306+
// can be replaced with any other conformance rule.
307+
for (unsigned otherRuleID : notInContext) {
308+
if (otherRuleID == candidateRuleID)
309+
continue;
310+
311+
SmallVector<unsigned, 2> path;
312+
path.push_back(otherRuleID);
313+
conformancePaths[candidateRuleID].push_back(path);
314+
}
315+
316+
// If conformance rules appear in non-empty context, they define a
317+
// conformance access path for each conformance rule in empty context.
318+
for (const auto &path : candidatePaths) {
319+
conformancePaths[candidateRuleID].push_back(path);
320+
}
347321
}
348322
}
349323
}

lib/AST/RequirementMachine/ProtocolGraph.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ void ProtocolGraph::visitRequirements(ArrayRef<Requirement> reqs) {
2828
}
2929
}
3030

31+
/// Adds information about all protocols transitvely referenced from
32+
/// \p protos.
33+
void ProtocolGraph::visitProtocols(ArrayRef<const ProtocolDecl *> protos) {
34+
for (auto proto : protos) {
35+
addProtocol(proto);
36+
}
37+
}
38+
3139
/// Return true if we know about this protocol.
3240
bool ProtocolGraph::isKnownProtocol(const ProtocolDecl *proto) const {
3341
return Info.count(proto) > 0;

lib/AST/RequirementMachine/ProtocolGraph.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class ProtocolGraph {
8585
bool Debug = false;
8686

8787
public:
88+
void visitProtocols(ArrayRef<const ProtocolDecl *> protos);
8889
void visitRequirements(ArrayRef<Requirement> reqs);
8990

9091
bool isKnownProtocol(const ProtocolDecl *proto) const;

0 commit comments

Comments
 (0)