//===--- CSStep.cpp - Constraint Solver Steps -----------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This file implements the \c SolverStep class and its related types, // which is used by constraint solver to do iterative solving. // //===----------------------------------------------------------------------===// #include "CSStep.h" #include "ConstraintSystem.h" #include "swift/AST/Types.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/STLExtras.h" using namespace llvm; using namespace swift; using namespace constraints; ComponentStep::Scope::Scope(ComponentStep &component) : CS(component.CS), Component(component) { TypeVars = std::move(CS.TypeVariables); for (auto *typeVar : component.TypeVars) CS.TypeVariables.push_back(typeVar); auto &workList = CS.InactiveConstraints; workList.splice(workList.end(), *component.Constraints); auto &CG = CS.getConstraintGraph(); if (component.OrphanedConstraint) CG.setOrphanedConstraint(component.OrphanedConstraint); SolverScope = new ConstraintSystem::SolverScope(CS); PrevPartialScope = CS.solverState->PartialSolutionScope; CS.solverState->PartialSolutionScope = SolverScope; } StepResult SplitterStep::take(bool prevFailed) { if (prevFailed || CS.failedConstraint) return done(/*isSuccess=*/false); SmallVector components; // Try to run "connected components" algorithm and split // type variables and their constraints into independent // sub-systems to solve. computeFollowupSteps(components); // If there is only one component, there is no reason to // try to merge solutions, "split" step should be considered // done and replaced by a single component step. if (components.size() < 2) return replaceWith(components.front()); /// Wait until all of the component steps are done. return suspend(components); } StepResult SplitterStep::resume(bool prevFailed) { // Restore the state of the constraint system to before split. CS.CG.setOrphanedConstraints(std::move(OrphanedConstraints)); auto &workList = CS.InactiveConstraints; for (auto &component : Components) workList.splice(workList.end(), component); // If we came back to this step and previous (one of the components) // failed, it means that we can't solve this step either. if (prevFailed) return done(/*isSuccess=*/false); // Otherwise let's try to merge partial soltuions together // and form a complete solution(s) for this split. return done(mergePartialSolutions()); } void SplitterStep::computeFollowupSteps( SmallVectorImpl &componentSteps) { // Compute next steps based on that connected components // algorithm tells us is splittable. auto &CG = CS.getConstraintGraph(); // Contract the edges of the constraint graph. CG.optimize(); // Compute the connected components of the constraint graph. // FIXME: We're seeding typeVars with TypeVariables so that the // connected-components algorithm only considers those type variables within // our component. There are clearly better ways to do this. SmallVector typeVars(CS.TypeVariables); SmallVector components; unsigned numComponents = CG.computeConnectedComponents(typeVars, components); if (numComponents < 2) { componentSteps.push_back(ComponentStep::create( CS, 0, /*single=*/true, &CS.InactiveConstraints, Solutions)); return; } Components.resize(numComponents); PartialSolutions = std::unique_ptr[]>( new SmallVector[numComponents]); for (unsigned i = 0, n = numComponents; i != n; ++i) { componentSteps.push_back(ComponentStep::create( CS, i, /*single=*/false, &Components[i], PartialSolutions[i])); } if (numComponents > 1 && CS.getASTContext().LangOpts.DebugConstraintSolver) { auto &log = CS.getASTContext().TypeCheckerDebug->getStream().indent( CS.solverState->depth * 2); // Verify that the constraint graph is valid. CG.verify(); log << "---Constraint graph---\n"; CG.print(log); log << "---Connected components---\n"; CG.printConnectedComponents(log); } // Map type variables and constraints into appropriate steps. llvm::DenseMap constraintComponent; for (unsigned i = 0, n = typeVars.size(); i != n; ++i) { auto *typeVar = typeVars[i]; auto *component = componentSteps[components[i]]; component->record(typeVar); for (auto *constraint : CG[typeVar].getConstraints()) constraintComponent[constraint] = components[i]; } // Add the orphaned components to the mapping from constraints to components. unsigned firstOrphanedConstraint = numComponents - CG.getOrphanedConstraints().size(); { unsigned component = firstOrphanedConstraint; for (auto constraint : CG.getOrphanedConstraints()) componentSteps[component++]->recordOrphan(constraint); } // Transfer all of the constraints from the work list to // the appropriate component. auto &workList = CS.InactiveConstraints; while (!workList.empty()) { auto *constraint = &workList.front(); workList.pop_front(); componentSteps[constraintComponent[constraint]]->record(constraint); } // Remove all of the orphaned constraints; they'll be re-introduced // by each component independently. OrphanedConstraints = CG.takeOrphanedConstraints(); // Create component ordering based on the information associated // with constraints in each step - e.g. number of disjunctions, // since components are going to be executed in LIFO order, we'd // want to have smaller/faster components at the back of the list. std::sort(componentSteps.begin(), componentSteps.end(), [](const ComponentStep *lhs, const ComponentStep *rhs) { return lhs > rhs; }); } bool SplitterStep::mergePartialSolutions() const { assert(Components.size() >= 2); auto numComponents = Components.size(); // Produce all combinations of partial solutions. SmallVector indices(numComponents, 0); bool done = false; bool anySolutions = false; do { // Create a new solver scope in which we apply all of the partial // solutions. ConstraintSystem::SolverScope scope(CS); for (unsigned i = 0; i != numComponents; ++i) CS.applySolution(PartialSolutions[i][indices[i]]); // This solution might be worse than the best solution found so far. // If so, skip it. if (!CS.worseThanBestSolution()) { // Finalize this solution. auto solution = CS.finalize(); if (CS.TC.getLangOpts().DebugConstraintSolver) { auto &log = CS.getASTContext().TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth * 2) << "(composed solution " << CS.CurrentScore << ")\n"; } // Save this solution. Solutions.push_back(std::move(solution)); anySolutions = true; } // Find the next combination. for (unsigned n = numComponents; n > 0; --n) { ++indices[n - 1]; // If we haven't run out of solutions yet, we're done. if (indices[n - 1] < PartialSolutions[n - 1].size()) break; // If we ran out of solutions at the first position, we're done. if (n == 1) { done = true; break; } // Zero out the indices from here to the end. for (unsigned i = n - 1; i != numComponents; ++i) indices[i] = 0; } } while (!done); return anySolutions; } StepResult ComponentStep::take(bool prevFailed) { // If we came either back to this step and previous // (either disjunction or type var) failed, or // one of the previous components created by "split" // failed, it means that we can't solve this component. if (prevFailed) return done(/*isSuccess=*/false); if (!IsSingle && CS.TC.getLangOpts().DebugConstraintSolver) { auto &log = CS.getASTContext().TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth * 2) << "(solving component #" << Index << '\n'; } /// Try to figure out what this step is going to be, /// after the scope has been established. auto *disjunction = CS.selectDisjunction(); auto bestBindings = CS.determineBestBindings(); if (bestBindings && (!disjunction || (!bestBindings->InvolvesTypeVariables && !bestBindings->FullyBound))) { // Produce a type variable step. return suspend(TypeVariableStep::create(CS, *bestBindings, Solutions)); } else if (disjunction) { // Produce a disjunction step. return suspend(DisjunctionStep::create(CS, disjunction, Solutions)); } // If there are no disjunctions or type variables to bind // we can't solve this system unless we have free type variables // allowed in the solution. if (!CS.solverState->allowsFreeTypeVariables() && CS.hasFreeTypeVariables()) return done(/*isSuccess=*/false); // If this solution is worse than the best solution we've seen so far, // skip it. if (CS.worseThanBestSolution()) return done(/*isSuccess=*/false); // If we only have relational or member constraints and are allowing // free type variables, save the solution. for (auto &constraint : CS.InactiveConstraints) { switch (constraint.getClassification()) { case ConstraintClassification::Relational: case ConstraintClassification::Member: continue; default: return done(/*isSuccess=*/false); } } auto solution = CS.finalize(); if (CS.TC.getLangOpts().DebugConstraintSolver) { auto &log = CS.getASTContext().TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth * 2) << "(found solution " << getCurrentScore() << ")\n"; } Solutions.push_back(std::move(solution)); return done(/*isSuccess=*/true); } StepResult ComponentStep::resume(bool prevFailed) { if (prevFailed) return done(/*isSuccess=*/false); // For each of the partial solutions, subtract off the current score. // It doesn't contribute. for (auto &solution : Solutions) solution.getFixedScore() -= OriginalScore; // When there are multiple partial solutions for a given connected component, // rank those solutions to pick the best ones. This limits the number of // combinations we need to produce; in the common case, down to a single // combination. filterSolutions(Solutions, /*minimize=*/true); return done(/*isSuccess=*/true); } void TypeVariableStep::setup() { auto &TC = CS.TC; ++CS.solverState->NumTypeVariablesBound; if (TC.getLangOpts().DebugConstraintSolver) { auto &log = TC.Context.TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth * 2) << "Initial bindings: "; interleave(InitialBindings.begin(), InitialBindings.end(), [&](const Binding &binding) { log << TypeVar->getString() << " := " << binding.BindingType->getString(); }, [&log] { log << ", "; }); log << '\n'; } } StepResult TypeVariableStep::take(bool prevFailed) { auto &TC = CS.TC; while (auto binding = Producer()) { // Try each of the bindings in turn. ++CS.solverState->NumTypeVariableBindings; if (AnySolved) { // If this is a defaultable binding and we have found solutions, // don't explore the default binding. if (binding->isDefaultable()) continue; // If we were able to solve this without considering // default literals, don't bother looking at default literals. if (binding->hasDefaultedProtocol() && !SawFirstLiteralConstraint) break; } if (TC.getLangOpts().DebugConstraintSolver) { auto &log = TC.Context.TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth * 2) << "(trying "; binding->print(log, &TC.Context.SourceMgr); log << '\n'; } if (binding->hasDefaultedProtocol()) SawFirstLiteralConstraint = true; { // Try to solve the system with typeVar := type auto scope = llvm::make_unique(CS); if (binding->attempt(CS)) { ActiveChoice = std::move(scope); // Looks like binding attempt has been successful, // let's try to see if it leads to any solutions. return suspend(SplitterStep::create(CS, Solutions)); } } } // No more bindings to try, or producer has been short-circuited. return done(/*isSuccess=*/AnySolved); } StepResult TypeVariableStep::resume(bool prevFailed) { assert(ActiveChoice); // Rewind back all of the changes made to constraint system. ActiveChoice.reset(); // If there was no failure in the sub-path it means // that active binding has a solution. AnySolved |= !prevFailed; auto &TC = CS.TC; if (TC.getLangOpts().DebugConstraintSolver) { auto &log = TC.Context.TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth * 2) << ")\n"; } // If there has been at least one solution so far // at a current batch of bindings is done it's a // success because each new batch would be less // and less precise. if (AnySolved && Producer.needsToComputeNext()) return done(/*isSuccess=*/true); // Attempt next type variable binding. return take(prevFailed); } StepResult DisjunctionStep::take(bool prevFailed) { while (auto binding = Producer()) { auto ¤tChoice = *binding; if (shouldSkipChoice(currentChoice)) continue; if (shouldShortCircuitAt(currentChoice)) break; if (CS.TC.getLangOpts().DebugConstraintSolver) { auto &ctx = CS.getASTContext(); auto &log = ctx.TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth) << "(assuming "; currentChoice.print(log, &ctx.SourceMgr); log << '\n'; } { auto scope = llvm::make_unique(CS); // Attempt current disjunction choice, which is going to simplify // constraint system by binding some of the type variables. Since // the system has been simplified and is splittable, we simplify // have to return "split" step which is going to take care of the rest. if (!currentChoice.attempt(CS)) continue; // Establish the "active" choice which maintains new scope in the // constraint system, be be able to rollback all of the changes later. ActiveChoice.emplace(std::move(scope), currentChoice); return suspend(SplitterStep::create(CS, Solutions)); } } return done(/*isSuccess=*/bool(LastSolvedChoice)); } StepResult DisjunctionStep::resume(bool prevFailed) { // If disjunction step is re-taken and there should be // active choice, let's see if it has be solved or not. assert(ActiveChoice); if (CS.TC.getLangOpts().DebugConstraintSolver) { auto &log = CS.getASTContext().TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth) << ")\n"; } // If choice (sub-path) has failed, it's okay, other // choices have to be attempted regardless, since final // decision could be made only after attempting all // of the choices, so let's just ignore failed ones. if (!prevFailed) { auto &choice = ActiveChoice->second; auto score = getBestScore(Solutions); if (!choice.isGenericOperator() && choice.isSymmetricOperator()) { if (!BestNonGenericScore || score < BestNonGenericScore) BestNonGenericScore = score; } // Remember the last successfully solved choice, // it would be useful when disjunction is exhausted. LastSolvedChoice = {choice, *score}; } // Rewind back the constraint system information. ActiveChoice.reset(); // Attempt next disjunction choice (if any left). return take(prevFailed); } bool DisjunctionStep::shouldSkipChoice(const TypeBinding &choice) const { auto &TC = CS.TC; if (choice.isDisabled()) { if (TC.getLangOpts().DebugConstraintSolver) { auto &log = CS.getASTContext().TypeCheckerDebug->getStream(); log.indent(CS.solverState->depth) << "(skipping "; choice.print(log, &TC.Context.SourceMgr); log << '\n'; } return true; } // Skip unavailable overloads unless solver is in the "diagnostic" mode. if (!CS.shouldAttemptFixes() && choice.isUnavailable()) return true; if (TC.getLangOpts().DisableConstraintSolverPerformanceHacks) return false; // Don't attempt to solve for generic operators if we already have // a non-generic solution. // FIXME: Less-horrible but still horrible hack to attempt to // speed things up. Skip the generic operators if we // already have a solution involving non-generic operators, // but continue looking for a better non-generic operator // solution. if (BestNonGenericScore && choice.isGenericOperator()) { auto &score = BestNonGenericScore->Data; // Let's skip generic overload choices only in case if // non-generic score indicates that there were no forced // unwrappings of optional(s), no unavailable overload // choices present in the solution, no fixes required, // and there are no non-trivial function conversions. if (score[SK_ForceUnchecked] == 0 && score[SK_Unavailable] == 0 && score[SK_Fix] == 0 && score[SK_FunctionConversion] == 0) return true; } return false; } bool DisjunctionStep::shouldShortCircuitAt( const DisjunctionChoice &choice) const { if (!LastSolvedChoice) return false; auto *lastChoice = LastSolvedChoice->first; auto delta = LastSolvedChoice->second - getCurrentScore(); bool hasUnavailableOverloads = delta.Data[SK_Unavailable] > 0; bool hasFixes = delta.Data[SK_Fix] > 0; // Attempt to short-circuit evaluation of this disjunction only // if the disjunction choice we are comparing to did not involve // selecting unavailable overloads or result in fixes being // applied to reach a solution. return !hasUnavailableOverloads && !hasFixes && shortCircuitDisjunctionAt(choice, lastChoice); } bool DisjunctionStep::shortCircuitDisjunctionAt( Constraint *currentChoice, Constraint *lastSuccessfulChoice) const { auto &ctx = CS.getASTContext(); if (ctx.LangOpts.DisableConstraintSolverPerformanceHacks) return false; // If the successfully applied constraint is favored, we'll consider that to // be the "best". if (lastSuccessfulChoice->isFavored() && !currentChoice->isFavored()) { #if !defined(NDEBUG) if (lastSuccessfulChoice->getKind() == ConstraintKind::BindOverload) { auto overloadChoice = lastSuccessfulChoice->getOverloadChoice(); assert((!overloadChoice.isDecl() || !overloadChoice.getDecl()->getAttrs().isUnavailable(ctx)) && "Unavailable decl should not be favored!"); } #endif return true; } // Anything without a fix is better than anything with a fix. if (currentChoice->getFix() && !lastSuccessfulChoice->getFix()) return true; if (auto restriction = currentChoice->getRestriction()) { // Non-optional conversions are better than optional-to-optional // conversions. if (*restriction == ConversionRestrictionKind::OptionalToOptional) return true; // Array-to-pointer conversions are better than inout-to-pointer // conversions. if (auto successfulRestriction = lastSuccessfulChoice->getRestriction()) { if (*successfulRestriction == ConversionRestrictionKind::ArrayToPointer && *restriction == ConversionRestrictionKind::InoutToPointer) return true; } } // Implicit conversions are better than checked casts. if (currentChoice->getKind() == ConstraintKind::CheckedCast) return true; return false; }