//===--- CSPropagate.cpp - Constraint Propagation -------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 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 constraint propagation algorithm in the solver. // //===----------------------------------------------------------------------===// #include "ConstraintGraph.h" #include "ConstraintSystem.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/raw_ostream.h" using namespace swift; using namespace constraints; bool isBindOverloadDisjunction(Constraint *disjunction) { assert(disjunction->getKind() == ConstraintKind::Disjunction && "Expected disjunction constraint!"); assert(!disjunction->getNestedConstraints().empty() && "Unexpected empty disjunction!"); auto *nested = disjunction->getNestedConstraints().front(); return nested->getKind() == ConstraintKind::BindOverload; } // Find the disjunction of bind overload constraints related to this // applicable function constraint, if it exists. Constraint * getBindOverloadDisjunction(ConstraintSystem &CS, Constraint *applicableFn) { assert(applicableFn->getKind() == ConstraintKind::ApplicableFunction && "Expected ApplicableFunction disjunction!"); auto *tyvar = applicableFn->getSecondType()->getAs(); assert(tyvar && "Expected type variable!"); Constraint *found = nullptr; for (auto *constraint : CS.getConstraintGraph()[tyvar].getConstraints()) { if (constraint->getKind() == ConstraintKind::Disjunction) { found = constraint; break; } } if (!found) return nullptr; #if !defined(NDEBUG) for (auto *constraint : CS.getConstraintGraph()[tyvar].getConstraints()) { if (constraint == found) continue; assert(constraint->getKind() != ConstraintKind::Disjunction && "Type variable is involved in more than one disjunction!"); } #endif // Verify the disjunction consists of BindOverload constraints. assert(isBindOverloadDisjunction(found)); return found; } void ConstraintSystem::collectNeighboringBindOverloadDisjunctions( llvm::SetVector &neighbors) { while (!ActiveConstraints.empty()) { auto *constraint = &ActiveConstraints.front(); ActiveConstraints.pop_front(); assert(constraint->isActive() && "Expected constraints to be active?"); assert(!constraint->isDisabled() && "Unexpected disabled constraint!"); if (constraint->getKind() == ConstraintKind::Disjunction) { if (isBindOverloadDisjunction(constraint)) { neighbors.insert(constraint); } } else if (constraint->getKind() == ConstraintKind::ApplicableFunction) { if (auto *bindDisjunction = getBindOverloadDisjunction(*this, constraint)) { neighbors.insert(bindDisjunction); } } solverState->retireConstraint(constraint); CG.removeConstraint(constraint); constraint->setActive(false); } } // Simplify any active constraints, returning true on success, false // on failure. bool ConstraintSystem::simplifyForConstraintPropagation() { while (!ActiveConstraints.empty()) { auto *constraint = &ActiveConstraints.front(); ActiveConstraints.pop_front(); assert(constraint->isActive() && "Expected constraints to be active?"); assert(!constraint->isDisabled() && "Unexpected disabled constraint!"); bool failed = false; // Simplify this constraint. switch (simplifyConstraint(*constraint)) { case SolutionKind::Error: failed = true; LLVM_FALLTHROUGH; case SolutionKind::Solved: solverState->retireConstraint(constraint); CG.removeConstraint(constraint); break; case SolutionKind::Unsolved: InactiveConstraints.push_back(constraint); break; } constraint->setActive(false); if (failed) return false; } return true; } bool ConstraintSystem::areBindPairConsistent(Constraint *first, Constraint *second) { // Set up a scope that will be torn down when we're done testing // this constraint. ConstraintSystem::SolverScope scope(*this); if (TC.getLangOpts().DebugConstraintSolver) { auto &log = getASTContext().TypeCheckerDebug->getStream(); log << "Testing constraints for consistency: "; first->print(log, &TC.Context.SourceMgr); log << "\nversus: "; second->print(log, &TC.Context.SourceMgr); } auto result = simplifyConstraint(*first); assert(result == ConstraintSystem::SolutionKind::Solved && "Expected the first bind constraint to work!"); solverState->retireConstraint(first); solverState->addGeneratedConstraint(first); result = simplifyConstraint(*second); assert(result == ConstraintSystem::SolutionKind::Solved && "Expected the second bind constraint to work!"); solverState->retireConstraint(second); solverState->addGeneratedConstraint(second); auto success = simplifyForConstraintPropagation(); if (TC.getLangOpts().DebugConstraintSolver) { auto &log = getASTContext().TypeCheckerDebug->getStream(); if (success) log << "Consistent!\n"; else log << "Not consistent!\n"; } return success; } // Test a bind overload constraint to see if it is consistent with the // rest of the constraint system. bool ConstraintSystem::isBindOverloadConsistent( Constraint *bindConstraint, llvm::SetVector &workList) { llvm::SetVector otherDisjunctions; { // Set up a scope that will be torn down when we're done testing // this constraint. ConstraintSystem::SolverScope scope(*this); assert(bindConstraint->getKind() == ConstraintKind::BindOverload && "Expected a BindOverload constraint!"); // Test this bind overload constraint, activating neighboring // constraints. auto result = simplifyConstraint(*bindConstraint); assert(result == ConstraintSystem::SolutionKind::Solved && "Expected the bind constraint to work!"); (void)result; solverState->retireConstraint(bindConstraint); solverState->addGeneratedConstraint(bindConstraint); collectNeighboringBindOverloadDisjunctions(otherDisjunctions); } // Test the our primary constraint against all of the members of // neighboring disjunctions. If this constraint fails with all // members of a neighboring disjunction, we'll disable it and queue // up these neighbors for further processing. If this constraint // works with any member of each of the disjunctions, we do not need // to test the remaining members. for (auto *disjunction : otherDisjunctions) { auto insertPt = InactiveConstraints.erase(disjunction); CG.removeConstraint(disjunction); bool foundConsistent = false; for (auto *nested : disjunction->getNestedConstraints()) { assert(nested->getKind() == ConstraintKind::BindOverload && "Expected a BindOverload constraint"); if (nested->isDisabled()) continue; if (areBindPairConsistent(bindConstraint, nested)) { foundConsistent = true; break; } } CG.addConstraint(disjunction); InactiveConstraints.insert(insertPt, disjunction); // We failed to find a working pair between the bind overload // constraint we started with and the members of this // disjunction. We'll mark this bind overload as disabled and // queue up the neighboring disjunctions for re-processing. if (!foundConsistent) { if (TC.getLangOpts().DebugConstraintSolver) { auto &log = getASTContext().TypeCheckerDebug->getStream(); log << "Disabling bind constraint: "; bindConstraint->print(log, &TC.Context.SourceMgr); log << "\n"; } bindConstraint->setDisabled(); for (auto *disjunction : otherDisjunctions) workList.insert(disjunction); return false; } } return true; } void ConstraintSystem::reviseBindOverloadDisjunction( Constraint *disjunction, llvm::SetVector &workList, bool *foundConsistent) { assert(disjunction->getKind() == ConstraintKind::Disjunction && "Expected disjunction constraint on work list!"); // Set up a scope that will be torn down when we're done testing // this constraint. ConstraintSystem::SolverScope scope(*this); // Temporarily remove the disjunction from the constraint system and // constraint graph. auto insertPt = InactiveConstraints.erase(disjunction); CG.removeConstraint(disjunction); *foundConsistent = false; for (auto *bindConstraint : disjunction->getNestedConstraints()) { assert(bindConstraint->getKind() == ConstraintKind::BindOverload && "Expected a BindOverload constraint!"); if (bindConstraint->isDisabled()) continue; if (isBindOverloadConsistent(bindConstraint, workList)) *foundConsistent = true; } CG.addConstraint(disjunction); InactiveConstraints.insert(insertPt, disjunction); } // Do a form of constraint propagation consisting of examining // applicable function constraints and their associated disjunction of // bind overload constraints. Disable bind overload constraints in the // disjunction if they are inconsistent with the rest of the // constraint system. By doing this we can eliminate a lot of the work // that we'll perform in the constraint solver. bool ConstraintSystem::propagateConstraints() { assert(!failedConstraint && "Unexpected failed constraint!"); assert(getActiveConstraints().empty() && "Expected no active constraints!"); if (TC.getLangOpts().DebugConstraintSolver) { auto &log = getASTContext().TypeCheckerDebug->getStream(); log << "---Propagating constraints---\n"; } // Queue an initial list of bind overload disjunction constraints to // process. llvm::SetVector workList; for (auto &constraint : getConstraints()) if (constraint.getKind() == ConstraintKind::Disjunction) if (isBindOverloadDisjunction(&constraint)) workList.insert(&constraint); // Process each disjunction in the work list. If we modify the // active constraints in the disjunction as a result of processing // it, we'll add it's neighbors back to the worklist for // reprocessing. while (!workList.empty()) { auto *disjunction = workList.pop_back_val(); bool foundConsistent; reviseBindOverloadDisjunction(disjunction, workList, &foundConsistent); if (!foundConsistent) return true; } return false; }