Skip to content

Commit 40b6764

Browse files
committed
[Constraint solver] Handle disjunctions as separate connected components.
The constraint graph models type variables (as the nodes) and constraints (as the multi-edges connecting nodes). The connected components within this (multi-)graph are independent subproblems that are solved separately; the results from each subproblem are then combined. The approach helps curtail exponential behavior, because (e.g.) the disjunctions/type variables in one component won't ever be explored while solving for another component This approach assumes that all of the constraints that cannot be immediately solved are associated with one or more type variables. This is almost entirely true---constraints that don't involve type variables are immediately simplified. Except for disjunctions. A disjunction involving no type variables would not appear *at all* in the constraint graph. Worse, it's independence from other constraints could not be established, so the constraint solver would go exponential for every one of these constraints. This has always been an issue, but it got worse with the separation of type checking of "as" into the "coercion" case and the "bridging" case, which introduced more of these disjunctions. This led to counterintuitive behavior where adding "as Foo" would cause the type checking to take *more* time than leaving it off, if both sides of the "as" were known to be concrete. rdar://problem/30545483 captures a case (now in the new test case) where we saw such exponential blow-ups. Teach the constraint graph to keep track of "orphaned" constraints that don't reference any type variables, and treat each "orphaned" constraint as a separate connected component. That way, they're solved independently. Fixes rdar://problem/30545483 and will likely curtain other exponential behavior we're seeing in the solver.
1 parent 098df72 commit 40b6764

File tree

4 files changed

+109
-21
lines changed

4 files changed

+109
-21
lines changed

lib/Sema/CSSolver.cpp

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,6 +2089,15 @@ bool ConstraintSystem::solveRec(SmallVectorImpl<Solution> &solutions,
20892089
constraintComponent[constraint] = components[i];
20902090
}
20912091

2092+
// Add the orphaned components to the mapping from constraints to components.
2093+
unsigned firstOrphanedConstraint =
2094+
numComponents - CG.getOrphanedConstraints().size();
2095+
{
2096+
unsigned component = firstOrphanedConstraint;
2097+
for (auto constraint : CG.getOrphanedConstraints())
2098+
constraintComponent[constraint] = component++;
2099+
}
2100+
20922101
// Sort the constraints into buckets based on component number.
20932102
std::unique_ptr<ConstraintList[]> constraintBuckets(
20942103
new ConstraintList[numComponents]);
@@ -2098,6 +2107,9 @@ bool ConstraintSystem::solveRec(SmallVectorImpl<Solution> &solutions,
20982107
constraintBuckets[constraintComponent[constraint]].push_back(constraint);
20992108
}
21002109

2110+
// Remove all of the orphaned constraints; we'll introduce them as needed.
2111+
auto allOrphanedConstraints = CG.takeOrphanedConstraints();
2112+
21012113
// Function object that returns all constraints placed into buckets
21022114
// back to the list of constraints.
21032115
auto returnAllConstraints = [&] {
@@ -2106,6 +2118,7 @@ bool ConstraintSystem::solveRec(SmallVectorImpl<Solution> &solutions,
21062118
InactiveConstraints.splice(InactiveConstraints.end(),
21072119
constraintBuckets[component]);
21082120
}
2121+
CG.setOrphanedConstraints(std::move(allOrphanedConstraints));
21092122
};
21102123

21112124
// Compute the partial solutions produced for each connected component.
@@ -2118,24 +2131,31 @@ bool ConstraintSystem::solveRec(SmallVectorImpl<Solution> &solutions,
21182131
++solverState->NumComponentsSplit;
21192132

21202133
// Collect the constraints for this component.
2121-
InactiveConstraints.splice(InactiveConstraints.end(),
2134+
InactiveConstraints.splice(InactiveConstraints.end(),
21222135
constraintBuckets[component]);
21232136

2124-
// Collect the type variables that are not part of a different
2125-
// component; this includes type variables that are part of the
2126-
// component as well as already-resolved type variables.
2127-
// FIXME: The latter could be avoided if we had already
2128-
// substituted all of those other type variables through.
2129-
llvm::SmallVector<TypeVariableType *, 16> allTypeVariables
2137+
llvm::SmallVector<TypeVariableType *, 16> allTypeVariables
21302138
= std::move(TypeVariables);
2131-
for (auto typeVar : allTypeVariables) {
2132-
auto known = typeVarComponent.find(typeVar);
2133-
if (known != typeVarComponent.end() && known->second != component)
2134-
continue;
21352139

2136-
TypeVariables.push_back(typeVar);
2140+
Constraint *orphaned = nullptr;
2141+
if (component < firstOrphanedConstraint) {
2142+
// Collect the type variables that are not part of a different
2143+
// component; this includes type variables that are part of the
2144+
// component as well as already-resolved type variables.
2145+
for (auto typeVar : allTypeVariables) {
2146+
auto known = typeVarComponent.find(typeVar);
2147+
if (known != typeVarComponent.end() && known->second != component)
2148+
continue;
2149+
2150+
TypeVariables.push_back(typeVar);
2151+
}
2152+
} else {
2153+
// Get the orphaned constraint.
2154+
assert(InactiveConstraints.size() == 1 && "supposed to be an orphan!");
2155+
orphaned = &InactiveConstraints.front();
21372156
}
2138-
2157+
CG.setOrphanedConstraint(orphaned);
2158+
21392159
// Solve for this component. If it fails, we're done.
21402160
bool failed;
21412161
if (TC.getLangOpts().DebugConstraintSolver) {

lib/Sema/ConstraintGraph.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,12 @@ void ConstraintGraph::addConstraint(Constraint *constraint) {
349349
}
350350
}
351351

352+
// If the constraint doesn't reference any type variables, it's orphaned;
353+
// track it as such.
354+
if (referencedTypeVars.empty()) {
355+
OrphanedConstraints.push_back(constraint);
356+
}
357+
352358
// Record the change, if there are active scopes.
353359
if (ActiveScope)
354360
Changes.push_back(Change::addedConstraint(constraint));
@@ -375,6 +381,16 @@ void ConstraintGraph::removeConstraint(Constraint *constraint) {
375381
}
376382
}
377383

384+
// If this is an orphaned constraint, remove it from the list.
385+
if (referencedTypeVars.empty()) {
386+
auto known = std::find(OrphanedConstraints.begin(),
387+
OrphanedConstraints.end(),
388+
constraint);
389+
assert(known != OrphanedConstraints.end() && "missing orphaned constraint");
390+
*known = OrphanedConstraints.back();
391+
OrphanedConstraints.pop_back();
392+
}
393+
378394
// Record the change, if there are active scopes.
379395
if (ActiveScope)
380396
Changes.push_back(Change::removedConstraint(constraint));
@@ -578,9 +594,9 @@ unsigned ConstraintGraph::computeConnectedComponents(
578594
if (CS.getFixedType(TypeVariables[i]))
579595
continue;
580596

581-
// If we only care about a subset, and this type variable isn't in that
582-
// subset, skip it.
583-
if (!typeVarSubset.empty() && typeVarSubset.count(TypeVariables[i]) == 0)
597+
// If this type variable isn't in the subset of type variables we care
598+
// about, skip it.
599+
if (typeVarSubset.count(TypeVariables[i]) == 0)
584600
continue;
585601

586602
componentHasUnboundTypeVar[components[i]] = true;
@@ -611,7 +627,7 @@ unsigned ConstraintGraph::computeConnectedComponents(
611627
}
612628
components.erase(components.begin() + outIndex, components.end());
613629

614-
return numComponents;
630+
return numComponents + getOrphanedConstraints().size();
615631
}
616632

617633

@@ -861,6 +877,7 @@ void ConstraintGraph::dump() {
861877

862878
void ConstraintGraph::printConnectedComponents(llvm::raw_ostream &out) {
863879
SmallVector<TypeVariableType *, 16> typeVars;
880+
typeVars.append(TypeVariables.begin(), TypeVariables.end());
864881
SmallVector<unsigned, 16> components;
865882
unsigned numComponents = computeConnectedComponents(typeVars, components);
866883
for (unsigned component = 0; component != numComponents; ++component) {

lib/Sema/ConstraintGraph.h

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,18 +237,47 @@ class ConstraintGraph {
237237

238238
/// Compute the connected components of the graph.
239239
///
240-
/// \param typeVars The type variables that occur within the
241-
/// connected components. If a non-empty vector is passed in, the algorithm
242-
/// will only consider type variables reachable from the initial set.
240+
/// \param typeVars The type variables that should be included in the
241+
/// set of connected components that are returned.
243242
///
244243
/// \param components Receives the component numbers for each type variable
245244
/// in \c typeVars.
246245
///
247-
/// \returns the number of connected components in the graph.
246+
/// \returns the number of connected components in the graph, which includes
247+
/// one component for each of the constraints produced by
248+
/// \c getOrphanedConstraints().
248249
unsigned computeConnectedComponents(
249250
SmallVectorImpl<TypeVariableType *> &typeVars,
250251
SmallVectorImpl<unsigned> &components);
251252

253+
/// Retrieve the set of "orphaned" constraints, which are known to the
254+
/// constraint graph but have no type variables to anchor them.
255+
ArrayRef<Constraint *> getOrphanedConstraints() const {
256+
return OrphanedConstraints;
257+
}
258+
259+
/// Replace the orphaned constraints with the constraints in the given list,
260+
/// returning the old set of orphaned constraints.
261+
SmallVector<Constraint *, 4> takeOrphanedConstraints() {
262+
auto result = std::move(OrphanedConstraints);
263+
OrphanedConstraints.clear();
264+
return result;
265+
}
266+
267+
/// Set the orphaned constraints.
268+
void setOrphanedConstraints(SmallVector<Constraint *, 4> &&newConstraints) {
269+
OrphanedConstraints = std::move(newConstraints);
270+
}
271+
272+
/// Set the list of orphaned constraints to a single constraint.
273+
///
274+
/// If \c orphaned is null, just clear out the list.
275+
void setOrphanedConstraint(Constraint *orphaned) {
276+
OrphanedConstraints.clear();
277+
if (orphaned)
278+
OrphanedConstraints.push_back(orphaned);
279+
}
280+
252281
/// Print the graph.
253282
void print(llvm::raw_ostream &out);
254283

@@ -300,6 +329,9 @@ class ConstraintGraph {
300329
/// The type variables in this graph, in stable order.
301330
SmallVector<TypeVariableType *, 4> TypeVariables;
302331

332+
/// Constraints that are "orphaned" because they contain no type variables.
333+
SmallVector<Constraint *, 4> OrphanedConstraints;
334+
303335
/// The kind of change made to the graph.
304336
enum class ChangeKind {
305337
/// Added a type variable.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: %target-swift-frontend -typecheck %s
2+
3+
public class Foo : CustomReflectable {
4+
public var booleanValue : Bool?
5+
public var customMirror: Mirror {
6+
return Mirror(self, children: [
7+
"booleanValue": booleanValue as Bool? as Any,
8+
"booleanValue": booleanValue as Bool? as Any,
9+
"booleanValue": booleanValue as Bool? as Any,
10+
"booleanValue": booleanValue as Bool? as Any,
11+
"booleanValue": booleanValue as Bool? as Any,
12+
"booleanValue": booleanValue as Bool? as Any,
13+
"booleanValue": booleanValue as Bool? as Any,
14+
"booleanValue": booleanValue as Bool? as Any,
15+
"booleanValue": booleanValue as Bool? as Any,
16+
"booleanValue": booleanValue as Bool? as Any
17+
])
18+
}
19+
}

0 commit comments

Comments
 (0)