Skip to content

Commit fea7bcf

Browse files
committed
[ConstraintSystem] Prevent shrink from solving "too complex" sub-expressions
Add additional checking for complexity of the shrinking candidate given the number of the expressions solved so far and total number of disjunctions present. This allows us to bail quicker in complex expression cases which, at the very least, produces an error instead of being "stuck" in solver for a long time. Resolves: rdar://problem/32034560
1 parent fd693b4 commit fea7bcf

File tree

3 files changed

+59
-7
lines changed

3 files changed

+59
-7
lines changed

lib/Sema/CSSolver.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,7 +1469,8 @@ ConstraintSystem::solveSingle(FreeTypeVariableBinding allowFreeTypeVariables) {
14691469
return std::move(solutions[0]);
14701470
}
14711471

1472-
bool ConstraintSystem::Candidate::solve() {
1472+
bool ConstraintSystem::Candidate::solve(
1473+
llvm::SmallDenseSet<Expr *> &shrunkExprs) {
14731474
// Don't attempt to solve candidate if there is closure
14741475
// expression involved, because it's handled specially
14751476
// by parent constraint system (e.g. parameter lists).
@@ -1512,6 +1513,11 @@ bool ConstraintSystem::Candidate::solve() {
15121513
return true;
15131514
}
15141515

1516+
// If this candidate is too complex given the number
1517+
// of the domains we have reduced so far, let's bail out early.
1518+
if (isTooComplexGiven(&cs, shrunkExprs))
1519+
return false;
1520+
15151521
if (TC.getLangOpts().DebugConstraintSolver) {
15161522
auto &log = cs.getASTContext().TypeCheckerDebug->getStream();
15171523
log << "--- Solving candidate for shrinking at ";
@@ -1565,7 +1571,7 @@ bool ConstraintSystem::Candidate::solve() {
15651571
}
15661572

15671573
// Record found solutions as suggestions.
1568-
this->applySolutions(solutions);
1574+
this->applySolutions(solutions, shrunkExprs);
15691575

15701576
// Let's double-check if we have any implicit expressions
15711577
// with type variables and nullify their types.
@@ -1577,7 +1583,8 @@ bool ConstraintSystem::Candidate::solve() {
15771583
}
15781584

15791585
void ConstraintSystem::Candidate::applySolutions(
1580-
llvm::SmallVectorImpl<Solution> &solutions) const {
1586+
llvm::SmallVectorImpl<Solution> &solutions,
1587+
llvm::SmallDenseSet<Expr *> &shrunkExprs) const {
15811588
// A collection of OSRs with their newly reduced domains,
15821589
// it's domains are sets because multiple solutions can have the same
15831590
// choice for one of the type variables, and we want no duplication.
@@ -1624,6 +1631,9 @@ void ConstraintSystem::Candidate::applySolutions(
16241631
= TC.Context.AllocateUninitialized<ValueDecl *>(choices.size());
16251632
std::uninitialized_copy(choices.begin(), choices.end(), decls.begin());
16261633
OSR->setDecls(decls);
1634+
1635+
// Record successfully shrunk expression.
1636+
shrunkExprs.insert(OSR);
16271637
}
16281638
}
16291639

@@ -1692,7 +1702,8 @@ void ConstraintSystem::shrink(Expr *expr) {
16921702
auto func = applyExpr->getFn();
16931703
// Let's record this function application for post-processing
16941704
// as well as if it contains overload set, see walkToExprPost.
1695-
ApplyExprs.push_back({applyExpr, isa<OverloadSetRefExpr>(func)});
1705+
ApplyExprs.push_back(
1706+
{applyExpr, isa<OverloadSetRefExpr>(func) || isa<TypeExpr>(func)});
16961707
}
16971708

16981709
return { true, expr };
@@ -1926,11 +1937,12 @@ void ConstraintSystem::shrink(Expr *expr) {
19261937
// so we can start solving them separately.
19271938
expr->walk(collector);
19281939

1940+
llvm::SmallDenseSet<Expr *> shrunkExprs;
19291941
for (auto &candidate : collector.Candidates) {
19301942
// If there are no results, let's forget everything we know about the
19311943
// system so far. This actually is ok, because some of the expressions
19321944
// might require manual salvaging.
1933-
if (candidate.solve()) {
1945+
if (candidate.solve(shrunkExprs)) {
19341946
// Let's restore all of the original OSR domains for this sub-expression,
19351947
// this means that we can still make forward progress with solving of the
19361948
// top sub-expressions.
@@ -1941,6 +1953,7 @@ void ConstraintSystem::shrink(Expr *expr) {
19411953
return childExpr;
19421954

19431955
OSR->setDecls(domain->getSecond());
1956+
shrunkExprs.erase(OSR);
19441957
}
19451958

19461959
return childExpr;

lib/Sema/ConstraintSystem.h

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -998,15 +998,45 @@ class ConstraintSystem {
998998
/// \brief Try to solve this candidate sub-expression
999999
/// and re-write it's OSR domains afterwards.
10001000
///
1001+
/// \param shrunkExprs The set of expressions which
1002+
/// domains have been successfully shrunk so far.
1003+
///
10011004
/// \returns true on solver failure, false otherwise.
1002-
bool solve();
1005+
bool solve(llvm::SmallDenseSet<Expr *> &shrunkExprs);
10031006

10041007
/// \brief Apply solutions found by solver as reduced OSR sets for
10051008
/// for current and all of it's sub-expressions.
10061009
///
10071010
/// \param solutions The solutions found by running solver on the
10081011
/// this candidate expression.
1009-
void applySolutions(llvm::SmallVectorImpl<Solution> &solutions) const;
1012+
///
1013+
/// \param shrunkExprs The set of expressions which
1014+
/// domains have been successfully shrunk so far.
1015+
void applySolutions(llvm::SmallVectorImpl<Solution> &solutions,
1016+
llvm::SmallDenseSet<Expr *> &shrunkExprs) const;
1017+
1018+
/// Check if attempt at solving of the candidate makes sense given
1019+
/// the current conditions - number of shrunk domains which is related
1020+
/// to the given candidate over the total number of disjunctions present.
1021+
static bool isTooComplexGiven(ConstraintSystem *const cs,
1022+
llvm::SmallDenseSet<Expr *> &shrunkExprs) {
1023+
SmallVector<Constraint *, 8> disjunctions;
1024+
cs->collectDisjunctions(disjunctions);
1025+
1026+
unsigned unsolvedDisjunctions = disjunctions.size();
1027+
for (auto *disjunction : disjunctions) {
1028+
auto *locator = disjunction->getLocator();
1029+
if (!locator)
1030+
continue;
1031+
1032+
if (auto *anchor = locator->getAnchor()) {
1033+
if (shrunkExprs.count(anchor) > 0)
1034+
--unsolvedDisjunctions;
1035+
}
1036+
}
1037+
1038+
return unsolvedDisjunctions >= 5;
1039+
}
10101040
};
10111041

10121042
/// \brief Describes the current solver state.

test/Sema/complex_expressions.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,12 @@ let sr3668Dict2: [Int: (Int, Int) -> Bool] =
117117
8: { $0 != $1 }, 9: { $0 != $1 }, 10: { $0 != $1 }, 11: { $0 != $1 },
118118
12: { $0 != $1 }, 13: { $0 != $1 }, 14: { $0 != $1 }, 15: { $0 != $1 },
119119
16: { $0 != $1 }, 17: { $0 != $1 }, 18: { $0 != $1 }, 19: { $0 != $1 } ]
120+
121+
// rdar://problem/32034560 - type-checker hangs trying to solve expression
122+
struct R32034560 {
123+
private var R32034560: Array<Array<UInt32>>
124+
private func foo(x: UInt32) -> UInt32 {
125+
return ((self.R32034560[0][Int(x >> 24) & 0xFF] &+ self.R32034560[1][Int(x >> 16) & 0xFF]) ^ self.R32034560[2][Int(x >> 8) & 0xFF]) &+ self.R32034560[3][Int(x & 0xFF)]
126+
// expected-error@-1 {{expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions}}
127+
}
128+
}

0 commit comments

Comments
 (0)