Skip to content

Commit 14b110d

Browse files
committed
Sema: Associated type inference optimization
This addresses a performance regression from 83cb420. In the old associated type inference implementation, we used to fold valid solutions by comparing type witnesses, but this was not correct as described in the commit message there. After my fix we started to record more valid solutions from the same search space, and this caused a performance regression because we were already doing exponential work. However in the program in question, each possible choice of witness for a requirement would introduce the same bindings, so there was nothing to gain from trying them all. Since ranking only compares pairs of witnesses for the same requirement, we can optimize this problem another way: by folding identical terms in a disjunction, but only if *all* terms are identical, which avoids the correctness issue in the old search strategy. Fixes rdar://problem/123334433.
1 parent a2a2083 commit 14b110d

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

lib/Sema/AssociatedTypeInference.cpp

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "swift/AST/Types.h"
4646
#include "swift/AST/TypeCheckRequests.h"
4747
#include "swift/Basic/Defer.h"
48+
#include "swift/Basic/Statistic.h"
4849
#include "swift/ClangImporter/ClangModule.h"
4950
#include "llvm/ADT/Statistic.h"
5051
#include "llvm/ADT/TinyPtrVector.h"
@@ -578,6 +579,24 @@ struct InferredAssociatedTypesByWitness {
578579

579580
void dump(llvm::raw_ostream &out, unsigned indent) const;
580581

582+
bool operator==(const InferredAssociatedTypesByWitness &other) const {
583+
if (Inferred.size() != other.Inferred.size())
584+
return false;
585+
586+
for (unsigned i = 0, e = Inferred.size(); i < e; ++i) {
587+
if (Inferred[i].first != other.Inferred[i].first)
588+
return false;
589+
if (!Inferred[i].second->isEqual(other.Inferred[i].second))
590+
return false;
591+
}
592+
593+
return true;
594+
}
595+
596+
bool operator!=(const InferredAssociatedTypesByWitness &other) const {
597+
return !(*this == other);
598+
}
599+
581600
SWIFT_DEBUG_DUMP;
582601
};
583602

@@ -1533,6 +1552,39 @@ static InferenceCandidateKind checkInferenceCandidate(
15331552
return InferenceCandidateKind::Good;
15341553
}
15351554

1555+
/// If all terms introduce identical bindings and none come from a protocol
1556+
/// extension, no choice between them can change the chosen solution, so
1557+
/// collapse down to one.
1558+
///
1559+
/// WARNING: This does not readily generalize to disjunctions that have
1560+
/// multiple duplicated terms, eg A \/ A \/ B \/ B, because the relative
1561+
/// order of the value witnesses binding each A and each B might be weird.
1562+
static void tryOptimizeDisjunction(InferredAssociatedTypesByWitnesses &result) {
1563+
// We assume there is at least one term.
1564+
if (result.empty())
1565+
return;
1566+
1567+
for (unsigned i = 0, e = result.size(); i < e; ++i) {
1568+
// Skip the optimization if we have non-viable bindings anywhere.
1569+
if (!result[i].NonViable.empty())
1570+
return;
1571+
1572+
// Skip the optimization if anything came from a default type alias
1573+
// or protocol extension; the ranking is hairier in that case.
1574+
if (!result[i].Witness ||
1575+
result[i].Witness->getDeclContext()->getExtendedProtocolDecl())
1576+
return;
1577+
1578+
// Skip the optimization if any two consecutive terms contain distinct
1579+
// bindings.
1580+
if (i > 0 && result[i - 1] != result[i])
1581+
return;
1582+
}
1583+
1584+
// This disjunction is trivial.
1585+
result.resize(1);
1586+
}
1587+
15361588
/// Create an initial constraint system for the associated type inference solver.
15371589
///
15381590
/// Each protocol requirement defines a disjunction, where each disjunction
@@ -1743,7 +1795,9 @@ AssociatedTypeInference::getPotentialTypeWitnessesFromRequirement(
17431795

17441796
result.push_back(std::move(witnessResult));
17451797
next_witness:;
1746-
}
1798+
}
1799+
1800+
tryOptimizeDisjunction(result);
17471801

17481802
if (hadTautologicalWitness && !result.empty()) {
17491803
// Create a dummy entry, but only if there was at least one other witness;
@@ -3380,6 +3434,9 @@ AssociatedTypeDecl *AssociatedTypeInference::inferAbstractTypeWitnesses(
33803434
void AssociatedTypeInference::findSolutions(
33813435
ArrayRef<AssociatedTypeDecl *> unresolvedAssocTypes,
33823436
SmallVectorImpl<InferredTypeWitnessesSolution> &solutions) {
3437+
FrontendStatsTracer StatsTracer(getASTContext().Stats,
3438+
"associated-type-inference", conformance);
3439+
33833440
SmallVector<InferredTypeWitnessesSolution, 4> nonViableSolutions;
33843441
SmallVector<std::pair<ValueDecl *, ValueDecl *>, 4> valueWitnesses;
33853442
findSolutionsRec(unresolvedAssocTypes, solutions, nonViableSolutions,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
protocol P1 {}
4+
protocol P2 {}
5+
protocol P3 {}
6+
protocol P4 {}
7+
protocol P5 {}
8+
protocol P6 {}
9+
protocol P7 {}
10+
protocol P8 {}
11+
protocol P9 {}
12+
protocol P10 {}
13+
protocol P11 {}
14+
protocol P12 {}
15+
protocol P13 {}
16+
protocol P14 {}
17+
protocol P15 {}
18+
protocol P16 {}
19+
20+
protocol P {
21+
associatedtype A
22+
associatedtype B
23+
24+
func f<T: P1>(_: T, _: A, _: B)
25+
func f<T: P2>(_: T, _: A, _: B)
26+
func f<T: P3>(_: T, _: A, _: B)
27+
func f<T: P4>(_: T, _: A, _: B)
28+
func f<T: P5>(_: T, _: A, _: B)
29+
func f<T: P6>(_: T, _: A, _: B)
30+
func f<T: P7>(_: T, _: A, _: B)
31+
func f<T: P8>(_: T, _: A, _: B)
32+
func f<T: P9>(_: T, _: A, _: B)
33+
func f<T: P10>(_: T, _: A, _: B)
34+
func f<T: P11>(_: T, _: A, _: B)
35+
func f<T: P12>(_: T, _: A, _: B)
36+
func f<T: P13>(_: T, _: A, _: B)
37+
func f<T: P14>(_: T, _: A, _: B)
38+
func f<T: P15>(_: T, _: A, _: B)
39+
func f<T: P16>(_: T, _: A, _: B)
40+
}
41+
42+
struct G<A>: P {
43+
func f<T: P1>(_: T, _: A, _: Bool) {}
44+
func f<T: P2>(_: T, _: A, _: Bool) {}
45+
func f<T: P3>(_: T, _: A, _: Bool) {}
46+
func f<T: P4>(_: T, _: A, _: Bool) {}
47+
func f<T: P5>(_: T, _: A, _: Bool) {}
48+
func f<T: P6>(_: T, _: A, _: Bool) {}
49+
func f<T: P7>(_: T, _: A, _: Bool) {}
50+
func f<T: P8>(_: T, _: A, _: Bool) {}
51+
func f<T: P9>(_: T, _: A, _: Bool) {}
52+
func f<T: P10>(_: T, _: A, _: Bool) {}
53+
func f<T: P11>(_: T, _: A, _: Bool) {}
54+
func f<T: P12>(_: T, _: A, _: Bool) {}
55+
func f<T: P13>(_: T, _: A, _: Bool) {}
56+
func f<T: P14>(_: T, _: A, _: Bool) {}
57+
func f<T: P15>(_: T, _: A, _: Bool) {}
58+
func f<T: P16>(_: T, _: A, _: Bool) {}
59+
}
60+
61+
let x: Int.Type = G<Int>.A.self
62+
let y: Bool.Type = G<Int>.B.self

0 commit comments

Comments
 (0)