Path: blob/main/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp
35269 views
//===- WatchedLiteralsSolver.cpp --------------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file defines a SAT solver implementation that can be used by dataflow9// analyses.10//11//===----------------------------------------------------------------------===//1213#include <cassert>14#include <vector>1516#include "clang/Analysis/FlowSensitive/CNFFormula.h"17#include "clang/Analysis/FlowSensitive/Formula.h"18#include "clang/Analysis/FlowSensitive/Solver.h"19#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"20#include "llvm/ADT/ArrayRef.h"21#include "llvm/ADT/DenseMap.h"22#include "llvm/ADT/DenseSet.h"23#include "llvm/ADT/STLExtras.h"242526namespace clang {27namespace dataflow {2829namespace {3031class WatchedLiteralsSolverImpl {32/// Stores the variable identifier and Atom for atomic booleans in the33/// formula.34llvm::DenseMap<Variable, Atom> Atomics;3536/// A boolean formula in conjunctive normal form that the solver will attempt37/// to prove satisfiable. The formula will be modified in the process.38CNFFormula CNF;3940/// Maps literals (indices of the vector) to clause identifiers (elements of41/// the vector) that watch the respective literals.42///43/// For a given clause, its watched literal is always its first literal in44/// `Clauses`. This invariant is maintained when watched literals change.45std::vector<ClauseID> WatchedHead;4647/// Maps clause identifiers (elements of the vector) to identifiers of other48/// clauses that watch the same literals, forming a set of linked lists.49///50/// The element at index 0 stands for the identifier of the clause that51/// follows the null clause. It is set to 0 and isn't used. Identifiers of52/// clauses in the formula start from the element at index 1.53std::vector<ClauseID> NextWatched;5455/// The search for a satisfying assignment of the variables in `Formula` will56/// proceed in levels, starting from 1 and going up to `Formula.LargestVar`57/// (inclusive). The current level is stored in `Level`. At each level the58/// solver will assign a value to an unassigned variable. If this leads to a59/// consistent partial assignment, `Level` will be incremented. Otherwise, if60/// it results in a conflict, the solver will backtrack by decrementing61/// `Level` until it reaches the most recent level where a decision was made.62size_t Level = 0;6364/// Maps levels (indices of the vector) to variables (elements of the vector)65/// that are assigned values at the respective levels.66///67/// The element at index 0 isn't used. Variables start from the element at68/// index 1.69std::vector<Variable> LevelVars;7071/// State of the solver at a particular level.72enum class State : uint8_t {73/// Indicates that the solver made a decision.74Decision = 0,7576/// Indicates that the solver made a forced move.77Forced = 1,78};7980/// State of the solver at a particular level. It keeps track of previous81/// decisions that the solver can refer to when backtracking.82///83/// The element at index 0 isn't used. States start from the element at index84/// 1.85std::vector<State> LevelStates;8687enum class Assignment : int8_t {88Unassigned = -1,89AssignedFalse = 0,90AssignedTrue = 191};9293/// Maps variables (indices of the vector) to their assignments (elements of94/// the vector).95///96/// The element at index 0 isn't used. Variable assignments start from the97/// element at index 1.98std::vector<Assignment> VarAssignments;99100/// A set of unassigned variables that appear in watched literals in101/// `Formula`. The vector is guaranteed to contain unique elements.102std::vector<Variable> ActiveVars;103104public:105explicit WatchedLiteralsSolverImpl(106const llvm::ArrayRef<const Formula *> &Vals)107// `Atomics` needs to be initialized first so that we can use it as an108// output argument of `buildCNF()`.109: Atomics(), CNF(buildCNF(Vals, Atomics)),110LevelVars(CNF.largestVar() + 1), LevelStates(CNF.largestVar() + 1) {111assert(!Vals.empty());112113// Skip initialization if the formula is known to be contradictory.114if (CNF.knownContradictory())115return;116117// Initialize `NextWatched` and `WatchedHead`.118NextWatched.push_back(0);119const size_t NumLiterals = 2 * CNF.largestVar() + 1;120WatchedHead.resize(NumLiterals + 1, 0);121for (ClauseID C = 1; C <= CNF.numClauses(); ++C) {122// Designate the first literal as the "watched" literal of the clause.123Literal FirstLit = CNF.clauseLiterals(C).front();124NextWatched.push_back(WatchedHead[FirstLit]);125WatchedHead[FirstLit] = C;126}127128// Initialize the state at the root level to a decision so that in129// `reverseForcedMoves` we don't have to check that `Level >= 0` on each130// iteration.131LevelStates[0] = State::Decision;132133// Initialize all variables as unassigned.134VarAssignments.resize(CNF.largestVar() + 1, Assignment::Unassigned);135136// Initialize the active variables.137for (Variable Var = CNF.largestVar(); Var != NullVar; --Var) {138if (isWatched(posLit(Var)) || isWatched(negLit(Var)))139ActiveVars.push_back(Var);140}141}142143// Returns the `Result` and the number of iterations "remaining" from144// `MaxIterations` (that is, `MaxIterations` - iterations in this call).145std::pair<Solver::Result, std::int64_t> solve(std::int64_t MaxIterations) && {146if (CNF.knownContradictory()) {147// Short-cut the solving process. We already found out at CNF148// construction time that the formula is unsatisfiable.149return std::make_pair(Solver::Result::Unsatisfiable(), MaxIterations);150}151size_t I = 0;152while (I < ActiveVars.size()) {153if (MaxIterations == 0)154return std::make_pair(Solver::Result::TimedOut(), 0);155--MaxIterations;156157// Assert that the following invariants hold:158// 1. All active variables are unassigned.159// 2. All active variables form watched literals.160// 3. Unassigned variables that form watched literals are active.161// FIXME: Consider replacing these with test cases that fail if the any162// of the invariants is broken. That might not be easy due to the163// transformations performed by `buildCNF`.164assert(activeVarsAreUnassigned());165assert(activeVarsFormWatchedLiterals());166assert(unassignedVarsFormingWatchedLiteralsAreActive());167168const Variable ActiveVar = ActiveVars[I];169170// Look for unit clauses that contain the active variable.171const bool unitPosLit = watchedByUnitClause(posLit(ActiveVar));172const bool unitNegLit = watchedByUnitClause(negLit(ActiveVar));173if (unitPosLit && unitNegLit) {174// We found a conflict!175176// Backtrack and rewind the `Level` until the most recent non-forced177// assignment.178reverseForcedMoves();179180// If the root level is reached, then all possible assignments lead to181// a conflict.182if (Level == 0)183return std::make_pair(Solver::Result::Unsatisfiable(), MaxIterations);184185// Otherwise, take the other branch at the most recent level where a186// decision was made.187LevelStates[Level] = State::Forced;188const Variable Var = LevelVars[Level];189VarAssignments[Var] = VarAssignments[Var] == Assignment::AssignedTrue190? Assignment::AssignedFalse191: Assignment::AssignedTrue;192193updateWatchedLiterals();194} else if (unitPosLit || unitNegLit) {195// We found a unit clause! The value of its unassigned variable is196// forced.197++Level;198199LevelVars[Level] = ActiveVar;200LevelStates[Level] = State::Forced;201VarAssignments[ActiveVar] =202unitPosLit ? Assignment::AssignedTrue : Assignment::AssignedFalse;203204// Remove the variable that was just assigned from the set of active205// variables.206if (I + 1 < ActiveVars.size()) {207// Replace the variable that was just assigned with the last active208// variable for efficient removal.209ActiveVars[I] = ActiveVars.back();210} else {211// This was the last active variable. Repeat the process from the212// beginning.213I = 0;214}215ActiveVars.pop_back();216217updateWatchedLiterals();218} else if (I + 1 == ActiveVars.size()) {219// There are no remaining unit clauses in the formula! Make a decision220// for one of the active variables at the current level.221++Level;222223LevelVars[Level] = ActiveVar;224LevelStates[Level] = State::Decision;225VarAssignments[ActiveVar] = decideAssignment(ActiveVar);226227// Remove the variable that was just assigned from the set of active228// variables.229ActiveVars.pop_back();230231updateWatchedLiterals();232233// This was the last active variable. Repeat the process from the234// beginning.235I = 0;236} else {237++I;238}239}240return std::make_pair(Solver::Result::Satisfiable(buildSolution()),241MaxIterations);242}243244private:245/// Returns a satisfying truth assignment to the atoms in the boolean formula.246llvm::DenseMap<Atom, Solver::Result::Assignment> buildSolution() {247llvm::DenseMap<Atom, Solver::Result::Assignment> Solution;248for (auto &Atomic : Atomics) {249// A variable may have a definite true/false assignment, or it may be250// unassigned indicating its truth value does not affect the result of251// the formula. Unassigned variables are assigned to true as a default.252Solution[Atomic.second] =253VarAssignments[Atomic.first] == Assignment::AssignedFalse254? Solver::Result::Assignment::AssignedFalse255: Solver::Result::Assignment::AssignedTrue;256}257return Solution;258}259260/// Reverses forced moves until the most recent level where a decision was261/// made on the assignment of a variable.262void reverseForcedMoves() {263for (; LevelStates[Level] == State::Forced; --Level) {264const Variable Var = LevelVars[Level];265266VarAssignments[Var] = Assignment::Unassigned;267268// If the variable that we pass through is watched then we add it to the269// active variables.270if (isWatched(posLit(Var)) || isWatched(negLit(Var)))271ActiveVars.push_back(Var);272}273}274275/// Updates watched literals that are affected by a variable assignment.276void updateWatchedLiterals() {277const Variable Var = LevelVars[Level];278279// Update the watched literals of clauses that currently watch the literal280// that falsifies `Var`.281const Literal FalseLit = VarAssignments[Var] == Assignment::AssignedTrue282? negLit(Var)283: posLit(Var);284ClauseID FalseLitWatcher = WatchedHead[FalseLit];285WatchedHead[FalseLit] = NullClause;286while (FalseLitWatcher != NullClause) {287const ClauseID NextFalseLitWatcher = NextWatched[FalseLitWatcher];288289// Pick the first non-false literal as the new watched literal.290const CNFFormula::Iterator FalseLitWatcherStart =291CNF.startOfClause(FalseLitWatcher);292CNFFormula::Iterator NewWatchedLitIter = FalseLitWatcherStart.next();293while (isCurrentlyFalse(*NewWatchedLitIter))294++NewWatchedLitIter;295const Literal NewWatchedLit = *NewWatchedLitIter;296const Variable NewWatchedLitVar = var(NewWatchedLit);297298// Swap the old watched literal for the new one in `FalseLitWatcher` to299// maintain the invariant that the watched literal is at the beginning of300// the clause.301*NewWatchedLitIter = FalseLit;302*FalseLitWatcherStart = NewWatchedLit;303304// If the new watched literal isn't watched by any other clause and its305// variable isn't assigned we need to add it to the active variables.306if (!isWatched(NewWatchedLit) && !isWatched(notLit(NewWatchedLit)) &&307VarAssignments[NewWatchedLitVar] == Assignment::Unassigned)308ActiveVars.push_back(NewWatchedLitVar);309310NextWatched[FalseLitWatcher] = WatchedHead[NewWatchedLit];311WatchedHead[NewWatchedLit] = FalseLitWatcher;312313// Go to the next clause that watches `FalseLit`.314FalseLitWatcher = NextFalseLitWatcher;315}316}317318/// Returns true if and only if one of the clauses that watch `Lit` is a unit319/// clause.320bool watchedByUnitClause(Literal Lit) const {321for (ClauseID LitWatcher = WatchedHead[Lit]; LitWatcher != NullClause;322LitWatcher = NextWatched[LitWatcher]) {323llvm::ArrayRef<Literal> Clause = CNF.clauseLiterals(LitWatcher);324325// Assert the invariant that the watched literal is always the first one326// in the clause.327// FIXME: Consider replacing this with a test case that fails if the328// invariant is broken by `updateWatchedLiterals`. That might not be easy329// due to the transformations performed by `buildCNF`.330assert(Clause.front() == Lit);331332if (isUnit(Clause))333return true;334}335return false;336}337338/// Returns true if and only if `Clause` is a unit clause.339bool isUnit(llvm::ArrayRef<Literal> Clause) const {340return llvm::all_of(Clause.drop_front(),341[this](Literal L) { return isCurrentlyFalse(L); });342}343344/// Returns true if and only if `Lit` evaluates to `false` in the current345/// partial assignment.346bool isCurrentlyFalse(Literal Lit) const {347return static_cast<int8_t>(VarAssignments[var(Lit)]) ==348static_cast<int8_t>(Lit & 1);349}350351/// Returns true if and only if `Lit` is watched by a clause in `Formula`.352bool isWatched(Literal Lit) const { return WatchedHead[Lit] != NullClause; }353354/// Returns an assignment for an unassigned variable.355Assignment decideAssignment(Variable Var) const {356return !isWatched(posLit(Var)) || isWatched(negLit(Var))357? Assignment::AssignedFalse358: Assignment::AssignedTrue;359}360361/// Returns a set of all watched literals.362llvm::DenseSet<Literal> watchedLiterals() const {363llvm::DenseSet<Literal> WatchedLiterals;364for (Literal Lit = 2; Lit < WatchedHead.size(); Lit++) {365if (WatchedHead[Lit] == NullClause)366continue;367WatchedLiterals.insert(Lit);368}369return WatchedLiterals;370}371372/// Returns true if and only if all active variables are unassigned.373bool activeVarsAreUnassigned() const {374return llvm::all_of(ActiveVars, [this](Variable Var) {375return VarAssignments[Var] == Assignment::Unassigned;376});377}378379/// Returns true if and only if all active variables form watched literals.380bool activeVarsFormWatchedLiterals() const {381const llvm::DenseSet<Literal> WatchedLiterals = watchedLiterals();382return llvm::all_of(ActiveVars, [&WatchedLiterals](Variable Var) {383return WatchedLiterals.contains(posLit(Var)) ||384WatchedLiterals.contains(negLit(Var));385});386}387388/// Returns true if and only if all unassigned variables that are forming389/// watched literals are active.390bool unassignedVarsFormingWatchedLiteralsAreActive() const {391const llvm::DenseSet<Variable> ActiveVarsSet(ActiveVars.begin(),392ActiveVars.end());393for (Literal Lit : watchedLiterals()) {394const Variable Var = var(Lit);395if (VarAssignments[Var] != Assignment::Unassigned)396continue;397if (ActiveVarsSet.contains(Var))398continue;399return false;400}401return true;402}403};404405} // namespace406407Solver::Result408WatchedLiteralsSolver::solve(llvm::ArrayRef<const Formula *> Vals) {409if (Vals.empty())410return Solver::Result::Satisfiable({{}});411auto [Res, Iterations] = WatchedLiteralsSolverImpl(Vals).solve(MaxIterations);412MaxIterations = Iterations;413return Res;414}415416} // namespace dataflow417} // namespace clang418419420