Skip to content

Commit 5fd48a0

Browse files
authored
Merge pull request #39629 from slavapestov/rqm-protocol-signatures
RequirementMachine: Wire up protocol requirement signature minimization
2 parents 790b549 + 8ab330a commit 5fd48a0

33 files changed

+845
-247
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,11 @@ namespace swift {
481481
/// algorithm.
482482
unsigned RequirementMachineDepthLimit = 10;
483483

484+
/// Enable the new experimental protocol requirement signature minimization
485+
/// algorithm.
486+
RequirementMachineMode RequirementMachineProtocolSignatures =
487+
RequirementMachineMode::Disabled;
488+
484489
/// Sets the target we are building for and updates platform conditions
485490
/// to match.
486491
///

include/swift/Option/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,10 @@ def requirement_machine_EQ : Joined<["-"], "requirement-machine=">,
616616
Flags<[FrontendOption, ModuleInterfaceOption]>,
617617
HelpText<"Control usage of experimental generics implementation: 'on', 'off', or 'verify'">;
618618

619+
def requirement_machine_protocol_signatures_EQ : Joined<["-"], "requirement-machine-protocol-signatures=">,
620+
Flags<[FrontendOption]>,
621+
HelpText<"Control usage of experimental protocol requirement signature minimization: 'on', 'off', or 'verify'">;
622+
619623
// Diagnostic control options
620624
def suppress_warnings : Flag<["-"], "suppress-warnings">,
621625
Flags<[FrontendOption]>,

lib/AST/GenericSignature.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,11 +1483,7 @@ int Requirement::compare(const Requirement &other) const {
14831483
// We should only have multiple conformance requirements.
14841484
assert(getKind() == RequirementKind::Conformance);
14851485

1486-
int compareProtos =
1487-
TypeDecl::compare(getProtocolDecl(), other.getProtocolDecl());
1488-
1489-
assert(compareProtos != 0 && "Duplicate conformance requirement");
1490-
return compareProtos;
1486+
return TypeDecl::compare(getProtocolDecl(), other.getProtocolDecl());
14911487
}
14921488

14931489
/// Compare two associated types.

lib/AST/RequirementMachine/GeneratingConformances.cpp

Lines changed: 101 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@
3434
// decompositions can be found for all "derived" conformance rules, producing
3535
// a minimal set of generating conformances.
3636
//
37+
// There are two small complications to handle implementation details of
38+
// Swift generics:
39+
//
40+
// 1) Inherited witness tables must be derivable by following other protocol
41+
// refinement requirements only, without looking at non-Self associated
42+
// types. This is expressed by saying that the generating conformance
43+
// equations for a protocol refinement can only be written in terms of
44+
// other protocol refinements; conformance paths involving non-Self
45+
// associated types are not considered.
46+
//
47+
// 2) The subject type of each conformance requirement must be derivable at
48+
// runtime as well, so for each generating conformance, it must be
49+
// possible to write down a conformance path for the parent type without
50+
// using any generating conformance recursively in the parent path of
51+
// itself.
52+
//
53+
// The generating conformances finds fewer conformance requirements to be
54+
// redundant than homotopy reduction, which is why homotopy reduction only
55+
// deletes non-protocol conformance requirements.
56+
//
3757
//===----------------------------------------------------------------------===//
3858

3959
#include "swift/AST/Decl.h"
@@ -283,23 +303,48 @@ void RewriteSystem::computeCandidateConformancePaths(
283303
llvm::dbgs() << "\n";
284304
}
285305

306+
// Two conformance rules in empty context (T.[P] => T) and (T'.[P] => T)
307+
// are interchangeable, and contribute a trivial pair of conformance
308+
// equations expressing that each one can be written in terms of the
309+
// other:
310+
//
311+
// (T.[P] => T) := (T'.[P])
312+
// (T'.[P] => T') := (T.[P])
313+
for (unsigned candidateRuleID : notInContext) {
314+
for (unsigned otherRuleID : notInContext) {
315+
if (otherRuleID == candidateRuleID)
316+
continue;
317+
318+
SmallVector<unsigned, 2> path;
319+
path.push_back(otherRuleID);
320+
conformancePaths[candidateRuleID].push_back(path);
321+
}
322+
}
323+
286324
// Suppose a 3-cell contains a conformance rule (T.[P] => T) in an empty
287-
// context, and a conformance rule (V.[P] => V) with a possibly non-empty
288-
// left context U and empty right context.
325+
// context, and a conformance rule (V.[P] => V) with a non-empty left
326+
// context U.
327+
//
328+
// The 3-cell looks something like this:
329+
//
330+
// ... ⊗ (T.[P] => T) ⊗ ... ⊗ U.(V => V.[P]) ⊗ ...
331+
// ^ ^
332+
// | |
333+
// + basepoint ========================= basepoint +
289334
//
290335
// We can decompose U into a product of conformance rules:
291336
//
292337
// (V1.[P1] => V1)...(Vn.[Pn] => Vn),
293338
//
339+
// Note that (V1)...(Vn) is canonically equivalent to U.
340+
//
294341
// Now, we can record a candidate decomposition of (T.[P] => T) as a
295342
// product of conformance rules:
296343
//
297344
// (T.[P] => T) := (V1.[P1] => V1)...(Vn.[Pn] => Vn).(V.[P] => V)
298345
//
299-
// Now if U is empty, this becomes the trivial candidate:
300-
//
301-
// (T.[P] => T) := (V.[P] => V)
302-
SmallVector<SmallVector<unsigned, 2>, 2> candidatePaths;
346+
// Again, note that (V1)...(Vn).V is canonically equivalent to U.V,
347+
// and therefore T.
303348
for (auto pair : inContext) {
304349
// We have a term U, and a rule V.[P] => V.
305350
SmallVector<unsigned, 2> conformancePath;
@@ -313,26 +358,10 @@ void RewriteSystem::computeCandidateConformancePaths(
313358
decomposeTermIntoConformanceRuleLeftHandSides(term, pair.second,
314359
conformancePath);
315360

316-
candidatePaths.push_back(conformancePath);
317-
}
318-
319-
for (unsigned candidateRuleID : notInContext) {
320-
// If multiple conformance rules appear in an empty context, each one
321-
// can be replaced with any other conformance rule.
322-
for (unsigned otherRuleID : notInContext) {
323-
if (otherRuleID == candidateRuleID)
324-
continue;
325-
326-
SmallVector<unsigned, 2> path;
327-
path.push_back(otherRuleID);
328-
conformancePaths[candidateRuleID].push_back(path);
329-
}
330-
331-
// If conformance rules appear in non-empty context, they define a
332-
// conformance access path for each conformance rule in empty context.
333-
for (const auto &path : candidatePaths) {
334-
conformancePaths[candidateRuleID].push_back(path);
335-
}
361+
// This decomposition defines a conformance access path for each
362+
// conformance rule we saw in empty context.
363+
for (unsigned otherRuleID : notInContext)
364+
conformancePaths[otherRuleID].push_back(conformancePath);
336365
}
337366
}
338367
}
@@ -492,6 +521,48 @@ void RewriteSystem::verifyGeneratingConformanceEquations(
492521
#endif
493522
}
494523

524+
static const ProtocolDecl *getParentConformanceForTerm(Term lhs) {
525+
// The last element is a protocol symbol, because this is the left hand side
526+
// of a conformance rule.
527+
assert(lhs.back().getKind() == Symbol::Kind::Protocol);
528+
529+
// The second to last symbol is either an associated type, protocol or generic
530+
// parameter symbol.
531+
assert(lhs.size() >= 2);
532+
533+
auto parentSymbol = lhs[lhs.size() - 2];
534+
535+
switch (parentSymbol.getKind()) {
536+
case Symbol::Kind::AssociatedType: {
537+
// In a conformance rule of the form [P:T].[Q] => [P:T], the parent type is
538+
// trivial.
539+
if (lhs.size() == 2)
540+
return nullptr;
541+
542+
// If we have a rule of the form X.[P:Y].[Q] => X.[P:Y] wih non-empty X,
543+
// then the parent type is X.[P].
544+
const auto protos = parentSymbol.getProtocols();
545+
assert(protos.size() == 1);
546+
547+
return protos[0];
548+
}
549+
550+
case Symbol::Kind::GenericParam:
551+
case Symbol::Kind::Protocol:
552+
// The parent type is trivial (either a generic parameter, or the protocol
553+
// 'Self' type).
554+
return nullptr;
555+
556+
case Symbol::Kind::Name:
557+
case Symbol::Kind::Layout:
558+
case Symbol::Kind::Superclass:
559+
case Symbol::Kind::ConcreteType:
560+
break;
561+
}
562+
563+
llvm_unreachable("Bad symbol kind");
564+
}
565+
495566
/// Computes a minimal set of generating conformances, assuming that homotopy
496567
/// reduction has already eliminated all redundant rewrite rules that are not
497568
/// conformance rules.
@@ -536,49 +607,21 @@ void RewriteSystem::computeGeneratingConformances(
536607

537608
auto lhs = rule.getLHS();
538609

539-
auto parentSymbol = lhs[lhs.size() - 2];
540-
541-
// The last element is a protocol symbol, because this is a conformance rule.
542-
// The second to last symbol is either an associated type, protocol or generic
543-
// parameter symbol.
544-
switch (parentSymbol.getKind()) {
545-
case Symbol::Kind::AssociatedType: {
546-
// If we have a rule of the form X.[P:Y].[Q] => X.[P:Y] wih non-empty X,
547-
// then the parent type is X.[P].
548-
if (lhs.size() == 2)
549-
continue;
550-
610+
// Record a parent path if the subject type itself requires a non-trivial
611+
// conformance path to derive.
612+
if (auto *parentProto = getParentConformanceForTerm(lhs)) {
551613
MutableTerm mutTerm(lhs.begin(), lhs.end() - 2);
552614
assert(!mutTerm.empty());
553615

554-
const auto protos = parentSymbol.getProtocols();
555-
assert(protos.size() == 1);
556-
557616
bool simplified = simplify(mutTerm);
558617
assert(!simplified || rule.isSimplified());
559618
(void) simplified;
560619

561-
mutTerm.add(Symbol::forProtocol(protos[0], Context));
620+
mutTerm.add(Symbol::forProtocol(parentProto, Context));
562621

563622
// Get a conformance path for X.[P] and record it.
564623
decomposeTermIntoConformanceRuleLeftHandSides(mutTerm, parentPaths[ruleID]);
565-
continue;
566624
}
567-
568-
case Symbol::Kind::GenericParam:
569-
case Symbol::Kind::Protocol:
570-
// Don't record a parent path, since the parent type is trivial (either a
571-
// generic parameter, or the protocol 'Self' type).
572-
continue;
573-
574-
case Symbol::Kind::Name:
575-
case Symbol::Kind::Layout:
576-
case Symbol::Kind::Superclass:
577-
case Symbol::Kind::ConcreteType:
578-
break;
579-
}
580-
581-
llvm_unreachable("Bad symbol kind");
582625
}
583626

584627
computeCandidateConformancePaths(conformancePaths);

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ void RewriteSystem::performHomotopyReduction(
751751
redundantConformances,
752752
replacementPath);
753753

754-
// If there no redundant rules remain in this pass, stop.
754+
// If no redundant rules remain which can be eliminated by this pass, stop.
755755
if (!optRuleID)
756756
return;
757757

lib/AST/RequirementMachine/ProtocolGraph.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ using namespace rewriting;
2323
void ProtocolGraph::visitRequirements(ArrayRef<Requirement> reqs) {
2424
for (auto req : reqs) {
2525
if (req.getKind() == RequirementKind::Conformance) {
26-
addProtocol(req.getProtocolDecl());
26+
addProtocol(req.getProtocolDecl(), /*initialComponent=*/false);
2727
}
2828
}
2929
}
@@ -32,7 +32,7 @@ void ProtocolGraph::visitRequirements(ArrayRef<Requirement> reqs) {
3232
/// \p protos.
3333
void ProtocolGraph::visitProtocols(ArrayRef<const ProtocolDecl *> protos) {
3434
for (auto proto : protos) {
35-
addProtocol(proto);
35+
addProtocol(proto, /*initialComponent=*/true);
3636
}
3737
}
3838

@@ -50,13 +50,15 @@ const ProtocolInfo &ProtocolGraph::getProtocolInfo(
5050
}
5151

5252
/// Record information about a protocol if we have no seen it yet.
53-
void ProtocolGraph::addProtocol(const ProtocolDecl *proto) {
53+
void ProtocolGraph::addProtocol(const ProtocolDecl *proto,
54+
bool initialComponent) {
5455
if (Info.count(proto) > 0)
5556
return;
5657

5758
Info[proto] = {proto->getInheritedProtocols(),
5859
proto->getAssociatedTypeMembers(),
59-
proto->getRequirementSignature()};
60+
proto->getProtocolDependencies(),
61+
initialComponent};
6062
Protocols.push_back(proto);
6163
}
6264

@@ -66,7 +68,9 @@ void ProtocolGraph::computeTransitiveClosure() {
6668
unsigned i = 0;
6769
while (i < Protocols.size()) {
6870
auto *proto = Protocols[i++];
69-
visitRequirements(getProtocolInfo(proto).Requirements);
71+
for (auto *proto : getProtocolInfo(proto).Dependencies) {
72+
addProtocol(proto, /*initialComponent=*/false);
73+
}
7074
}
7175
}
7276

lib/AST/RequirementMachine/ProtocolGraph.h

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ struct ProtocolInfo {
4242
/// ProtocolGraph::computeInheritedAssociatedTypes().
4343
llvm::TinyPtrVector<AssociatedTypeDecl *> InheritedAssociatedTypes;
4444

45-
/// The protocol's requirement signature.
46-
ArrayRef<Requirement> Requirements;
45+
/// The protocol's dependencies.
46+
ArrayRef<ProtocolDecl *> Dependencies;
4747

4848
/// Used by ProtocolGraph::computeProtocolDepth() to detect circularity.
4949
unsigned Mark : 1;
@@ -55,27 +55,34 @@ struct ProtocolInfo {
5555

5656
/// Index of the protocol in the linear order. Computed by
5757
/// ProtocolGraph::computeLinearOrder().
58-
unsigned Index : 32;
58+
unsigned Index : 31;
59+
60+
/// When building a protocol requirement signature, the initial set of
61+
/// protocols are marked with this bit.
62+
unsigned InitialComponent : 1;
5963

6064
ProtocolInfo() {
6165
Mark = 0;
6266
Depth = 0;
6367
Index = 0;
68+
InitialComponent = 0;
6469
}
6570

6671
ProtocolInfo(ArrayRef<ProtocolDecl *> inherited,
6772
ArrayRef<AssociatedTypeDecl *> &&types,
68-
ArrayRef<Requirement> reqs)
73+
ArrayRef<ProtocolDecl *> deps,
74+
bool initialComponent)
6975
: Inherited(inherited),
7076
AssociatedTypes(types),
71-
Requirements(reqs) {
77+
Dependencies(deps) {
7278
Mark = 0;
7379
Depth = 0;
7480
Index = 0;
81+
InitialComponent = initialComponent;
7582
}
7683
};
7784

78-
/// Stores cached information about all protocols transtively
85+
/// Stores cached information about all protocols transitively
7986
/// referenced from a set of generic requirements.
8087
///
8188
/// Out-of-line methods are documented in ProtocolGraph.cpp.
@@ -101,7 +108,8 @@ class ProtocolGraph {
101108
const ProtocolDecl *proto) const;
102109

103110
private:
104-
void addProtocol(const ProtocolDecl *proto);
111+
void addProtocol(const ProtocolDecl *proto,
112+
bool initialComponent);
105113
void computeTransitiveClosure();
106114
void computeLinearOrder();
107115
void computeInheritedAssociatedTypes();

0 commit comments

Comments
 (0)