Skip to content

Commit d9c6fcf

Browse files
authored
Merge pull request #18305 from DougGregor/constraint-solver-timer-perf
[Constraint solver] Reduce timer overhead associated with "expression too complex"
2 parents 3b59384 + 5530298 commit d9c6fcf

File tree

9 files changed

+45
-64
lines changed

9 files changed

+45
-64
lines changed

lib/Sema/CSSolver.cpp

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -484,9 +484,6 @@ ConstraintSystem::SolverScope::SolverScope(ConstraintSystem &cs)
484484
numDefaultedConstraints = cs.DefaultedConstraints.size();
485485
numCheckedConformances = cs.CheckedConformances.size();
486486
PreviousScore = cs.CurrentScore;
487-
if (cs.Timer) {
488-
startTime = cs.Timer->getElapsedProcessTimeInFractionalSeconds();
489-
}
490487

491488
cs.solverState->registerScope(this);
492489
assert(!cs.failedConstraint && "Unexpected failed constraint!");
@@ -565,10 +562,6 @@ bool ConstraintSystem::tryTypeVariableBindings(
565562
auto &tc = getTypeChecker();
566563
++solverState->NumTypeVariablesBound;
567564

568-
// If we've already explored a lot of potential solutions, bail.
569-
if (getExpressionTooComplex(solutions))
570-
return true;
571-
572565
for (unsigned tryCount = 0; !anySolved && !bindings.empty(); ++tryCount) {
573566
// Try each of the bindings in turn.
574567
++solverState->NumTypeVariableBindings;
@@ -1462,15 +1455,7 @@ ConstraintSystem::solveImpl(Expr *&expr,
14621455

14631456
// If there are no solutions let's mark system as unsolved,
14641457
// and solved otherwise even if there are multiple solutions still present.
1465-
1466-
// There was a Swift 3 bug that allowed us to return Solved if we
1467-
// had found at least one solution before deciding an expression was
1468-
// "too complex". Maintain that behavior, but for Swift > 3 return
1469-
// Unsolved in these cases.
1470-
auto tooComplex = getExpressionTooComplex(solutions);
1471-
auto unsolved = tooComplex || solutions.empty();
1472-
1473-
return unsolved ? SolutionKind::Unsolved : SolutionKind::Solved;
1458+
return solutions.empty() ? SolutionKind::Unsolved : SolutionKind::Solved;
14741459
}
14751460

14761461
bool ConstraintSystem::solve(Expr *const expr,
@@ -1501,8 +1486,8 @@ bool ConstraintSystem::solve(Expr *const expr,
15011486
if (!retainAllSolutions())
15021487
filterSolutions(solutions, state.ExprWeights);
15031488

1504-
// We fail if there is no solution.
1505-
return solutions.empty();
1489+
// We fail if there is no solution or the expression was too complex.
1490+
return solutions.empty() || getExpressionTooComplex(solutions);
15061491
}
15071492

15081493
bool ConstraintSystem::solveRec(SmallVectorImpl<Solution> &solutions,
@@ -2045,10 +2030,6 @@ bool ConstraintSystem::solveForDisjunctionChoices(
20452030
break;
20462031
}
20472032

2048-
// If the expression was deemed "too complex", stop now and salvage.
2049-
if (getExpressionTooComplex(solutions))
2050-
break;
2051-
20522033
// Try to solve the system with this option in the disjunction.
20532034
SolverScope scope(*this);
20542035
++solverState->NumDisjunctionTerms;
@@ -2119,6 +2100,10 @@ bool ConstraintSystem::solveSimplified(
21192100

21202101
auto bestBindings = determineBestBindings();
21212102

2103+
// If we've already explored a lot of potential solutions, bail.
2104+
if (getExpressionTooComplex(solutions))
2105+
return true;
2106+
21222107
// If we have a binding that does not involve type variables, and is
21232108
// not fully bound, or we have no disjunction to attempt instead,
21242109
// go ahead and try the bindings for this type variable.
@@ -2130,13 +2115,8 @@ bool ConstraintSystem::solveSimplified(
21302115
}
21312116

21322117
if (disjunction) {
2133-
bool foundSolution = solveForDisjunctionChoices(disjunction, solutions,
2134-
allowFreeTypeVariables);
2135-
2136-
// If we are exiting due to an expression that is too complex, do
2137-
// not allow our caller to continue as if we have been successful.
2138-
auto tooComplex = getExpressionTooComplex(solutions);
2139-
return tooComplex || !foundSolution;
2118+
return !solveForDisjunctionChoices(disjunction, solutions,
2119+
allowFreeTypeVariables);
21402120
}
21412121

21422122
// If there are no disjunctions we can't solve this system unless we have

lib/Sema/ConstraintSystem.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,9 +1453,6 @@ class ConstraintSystem {
14531453
/// The scope number of this scope. Set when the scope is registered.
14541454
unsigned scopeNumber = 0;
14551455

1456-
/// Time in fractional seconds at which we entered this scope.
1457-
double startTime;
1458-
14591456
/// Constraint graph scope associated with this solver scope.
14601457
ConstraintGraphScope CGScope;
14611458

@@ -1467,13 +1464,6 @@ class ConstraintSystem {
14671464
public:
14681465
explicit SolverScope(ConstraintSystem &cs);
14691466
~SolverScope();
1470-
1471-
Optional<double> getElapsedTimeInFractionalSeconds() {
1472-
if (!cs.Timer)
1473-
return None;
1474-
1475-
return cs.Timer->getElapsedProcessTimeInFractionalSeconds() - startTime;
1476-
}
14771467
};
14781468

14791469
ConstraintSystem(TypeChecker &tc, DeclContext *dc,

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,9 @@ deriveEquatable_eq(DerivedConformance &derived, Identifier generatedIdentifier,
665665

666666
bool DerivedConformance::canDeriveEquatable(TypeChecker &tc, DeclContext *DC,
667667
NominalTypeDecl *type) {
668-
auto equatableProto = tc.Context.getProtocol(KnownProtocolKind::Equatable);
668+
ASTContext &ctx = DC->getASTContext();
669+
auto equatableProto = ctx.getProtocol(KnownProtocolKind::Equatable);
670+
if (!equatableProto) return false;
669671
return canDeriveConformance(tc, DC, type, equatableProto);
670672
}
671673

@@ -1135,8 +1137,7 @@ getHashableConformance(Decl *parentDecl) {
11351137
return nullptr;
11361138
}
11371139

1138-
bool DerivedConformance::canDeriveHashable(TypeChecker &tc,
1139-
NominalTypeDecl *type) {
1140+
bool DerivedConformance::canDeriveHashable(NominalTypeDecl *type) {
11401141
if (!isa<EnumDecl>(type) && !isa<StructDecl>(type) && !isa<ClassDecl>(type))
11411142
return false;
11421143
// FIXME: This is not actually correct. We cannot promise to always

lib/Sema/DerivedConformances.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &TC,
6161
// We can always complete a partial Hashable implementation, and we can
6262
// synthesize a full Hashable implementation for structs and enums with
6363
// Hashable components.
64-
return canDeriveHashable(TC, Nominal);
64+
return canDeriveHashable(Nominal);
6565
}
6666

6767
if (auto *enumDecl = dyn_cast<EnumDecl>(Nominal)) {

lib/Sema/DerivedConformances.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class DerivedConformance {
137137
/// associated values, and for structs with all-Hashable stored properties.
138138
///
139139
/// \returns True if the requirement can be derived.
140-
static bool canDeriveHashable(TypeChecker &tc, NominalTypeDecl *type);
140+
static bool canDeriveHashable(NominalTypeDecl *type);
141141

142142
/// Derive a Hashable requirement for a type.
143143
///

lib/Sema/TypeCheckGeneric.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,7 +1210,7 @@ RequirementCheckResult TypeChecker::checkGenericArguments(
12101210
pendingReqs.push_back({requirements, {}});
12111211

12121212
auto *env = dc->getGenericEnvironmentOfContext();
1213-
1213+
ASTContext &ctx = dc->getASTContext();
12141214
while (!pendingReqs.empty()) {
12151215
auto current = pendingReqs.pop_back_val();
12161216

@@ -1342,19 +1342,19 @@ RequirementCheckResult TypeChecker::checkGenericArguments(
13421342

13431343
if (loc.isValid()) {
13441344
// FIXME: Poor source-location information.
1345-
diagnose(loc, diagnostic, owner, firstType, secondType);
1345+
ctx.Diags.diagnose(loc, diagnostic, owner, firstType, secondType);
13461346

13471347
std::string genericParamBindingsText;
13481348
if (!genericParams.empty()) {
13491349
genericParamBindingsText =
13501350
gatherGenericParamBindingsText(
13511351
{rawFirstType, rawSecondType}, genericParams, substitutions);
13521352
}
1353-
diagnose(noteLoc, diagnosticNote, rawFirstType, rawSecondType,
1354-
genericParamBindingsText);
1353+
ctx.Diags.diagnose(noteLoc, diagnosticNote, rawFirstType, rawSecondType,
1354+
genericParamBindingsText);
13551355

1356-
ParentConditionalConformance::diagnoseConformanceStack(Diags, noteLoc,
1357-
current.Parents);
1356+
ParentConditionalConformance::diagnoseConformanceStack(
1357+
ctx.Diags, noteLoc, current.Parents);
13581358
}
13591359

13601360
return RequirementCheckResult::Failure;

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3663,26 +3663,29 @@ static void diagnoseConformanceFailure(TypeChecker &TC, Type T,
36633663
if (T->hasError())
36643664
return;
36653665

3666+
ASTContext &ctx = DC->getASTContext();
3667+
auto &diags = ctx.Diags;
3668+
36663669
// If we're checking conformance of an existential type to a protocol,
36673670
// do a little bit of extra work to produce a better diagnostic.
36683671
if (T->isExistentialType() &&
36693672
TC.containsProtocol(T, Proto, DC, None)) {
36703673

36713674
if (!T->isObjCExistentialType()) {
3672-
TC.diagnose(ComplainLoc, diag::protocol_does_not_conform_objc,
3673-
T, Proto->getDeclaredType());
3675+
diags.diagnose(ComplainLoc, diag::protocol_does_not_conform_objc,
3676+
T, Proto->getDeclaredType());
36743677
return;
36753678
}
36763679

3677-
TC.diagnose(ComplainLoc, diag::protocol_does_not_conform_static,
3678-
T, Proto->getDeclaredType());
3680+
diags.diagnose(ComplainLoc, diag::protocol_does_not_conform_static,
3681+
T, Proto->getDeclaredType());
36793682
return;
36803683
}
36813684

36823685
// Special case: diagnose conversion to ExpressibleByNilLiteral, since we
36833686
// know this is something involving 'nil'.
36843687
if (Proto->isSpecificProtocol(KnownProtocolKind::ExpressibleByNilLiteral)) {
3685-
TC.diagnose(ComplainLoc, diag::cannot_use_nil_with_this_type, T);
3688+
diags.diagnose(ComplainLoc, diag::cannot_use_nil_with_this_type, T);
36863689
return;
36873690
}
36883691

@@ -3697,29 +3700,28 @@ static void diagnoseConformanceFailure(TypeChecker &TC, Type T,
36973700

36983701
auto rawType = enumDecl->getRawType();
36993702

3700-
TC.diagnose(enumDecl->getInherited()[0].getSourceRange().Start,
3701-
diag::enum_raw_type_nonconforming_and_nonsynthable,
3702-
T, rawType);
3703+
diags.diagnose(enumDecl->getInherited()[0].getSourceRange().Start,
3704+
diag::enum_raw_type_nonconforming_and_nonsynthable,
3705+
T, rawType);
37033706

37043707
// If the reason is that the raw type does not conform to
37053708
// Equatable, say so.
3706-
auto equatableProto = TC.getProtocol(enumDecl->getLoc(),
3707-
KnownProtocolKind::Equatable);
3709+
auto equatableProto = ctx.getProtocol(KnownProtocolKind::Equatable);
37083710
if (!equatableProto)
37093711
return;
37103712

37113713
if (!TC.conformsToProtocol(rawType, equatableProto, enumDecl, None)) {
37123714
SourceLoc loc = enumDecl->getInherited()[0].getSourceRange().Start;
3713-
TC.diagnose(loc, diag::enum_raw_type_not_equatable, rawType);
3715+
diags.diagnose(loc, diag::enum_raw_type_not_equatable, rawType);
37143716
return;
37153717
}
37163718

37173719
return;
37183720
}
37193721
}
37203722

3721-
TC.diagnose(ComplainLoc, diag::type_does_not_conform,
3722-
T, Proto->getDeclaredType());
3723+
diags.diagnose(ComplainLoc, diag::type_does_not_conform,
3724+
T, Proto->getDeclaredType());
37233725
}
37243726

37253727
void ConformanceChecker::diagnoseOrDefer(

stdlib/public/core/Hasher.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ internal struct _HasherTailBuffer {
9090
internal init(tail: UInt64, byteCount: UInt64) {
9191
// byteCount can be any value, but we only keep the lower 8 bits. (The
9292
// lower three bits specify the count of bytes stored in this buffer.)
93-
_sanityCheck(tail & ~(1 << ((byteCount & 7) << 3) - 1) == 0)
93+
// FIXME: The "as UInt64" annotations here eliminate some exponential
94+
// behavior in the expression type checker <rdar://problem/42672946>.
95+
_sanityCheck(tail & ~((1 as UInt64) << ((byteCount & (7 as UInt64))
96+
<< (3 as UInt64)) - (1 as UInt64)) == (0 as UInt64))
9497
self.value = (byteCount &<< 56 | tail)
9598
}
9699

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1
2+
func f(tail: UInt64, byteCount: UInt64) {
3+
if tail & ~(1 << ((byteCount & 7) << 3) - 1) == 0 { }
4+
// expected-error@-1 {{reasonable time}}
5+
}

0 commit comments

Comments
 (0)