1 //===- WatchedLiteralsSolver.cpp --------------------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file defines a SAT solver implementation that can be used by dataflow
12 //===----------------------------------------------------------------------===//
17 #include "clang/Analysis/FlowSensitive/CNFFormula.h"
18 #include "clang/Analysis/FlowSensitive/Formula.h"
19 #include "clang/Analysis/FlowSensitive/Solver.h"
20 #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
21 #include "llvm/ADT/ArrayRef.h"
22 #include "llvm/ADT/DenseMap.h"
23 #include "llvm/ADT/DenseSet.h"
24 #include "llvm/ADT/STLExtras.h"
32 class WatchedLiteralsSolverImpl
{
33 /// Stores the variable identifier and Atom for atomic booleans in the
35 llvm::DenseMap
<Variable
, Atom
> Atomics
;
37 /// A boolean formula in conjunctive normal form that the solver will attempt
38 /// to prove satisfiable. The formula will be modified in the process.
41 /// Maps literals (indices of the vector) to clause identifiers (elements of
42 /// the vector) that watch the respective literals.
44 /// For a given clause, its watched literal is always its first literal in
45 /// `Clauses`. This invariant is maintained when watched literals change.
46 std::vector
<ClauseID
> WatchedHead
;
48 /// Maps clause identifiers (elements of the vector) to identifiers of other
49 /// clauses that watch the same literals, forming a set of linked lists.
51 /// The element at index 0 stands for the identifier of the clause that
52 /// follows the null clause. It is set to 0 and isn't used. Identifiers of
53 /// clauses in the formula start from the element at index 1.
54 std::vector
<ClauseID
> NextWatched
;
56 /// The search for a satisfying assignment of the variables in `Formula` will
57 /// proceed in levels, starting from 1 and going up to `Formula.LargestVar`
58 /// (inclusive). The current level is stored in `Level`. At each level the
59 /// solver will assign a value to an unassigned variable. If this leads to a
60 /// consistent partial assignment, `Level` will be incremented. Otherwise, if
61 /// it results in a conflict, the solver will backtrack by decrementing
62 /// `Level` until it reaches the most recent level where a decision was made.
65 /// Maps levels (indices of the vector) to variables (elements of the vector)
66 /// that are assigned values at the respective levels.
68 /// The element at index 0 isn't used. Variables start from the element at
70 std::vector
<Variable
> LevelVars
;
72 /// State of the solver at a particular level.
73 enum class State
: uint8_t {
74 /// Indicates that the solver made a decision.
77 /// Indicates that the solver made a forced move.
81 /// State of the solver at a particular level. It keeps track of previous
82 /// decisions that the solver can refer to when backtracking.
84 /// The element at index 0 isn't used. States start from the element at index
86 std::vector
<State
> LevelStates
;
88 enum class Assignment
: int8_t {
94 /// Maps variables (indices of the vector) to their assignments (elements of
97 /// The element at index 0 isn't used. Variable assignments start from the
98 /// element at index 1.
99 std::vector
<Assignment
> VarAssignments
;
101 /// A set of unassigned variables that appear in watched literals in
102 /// `Formula`. The vector is guaranteed to contain unique elements.
103 std::vector
<Variable
> ActiveVars
;
106 explicit WatchedLiteralsSolverImpl(
107 const llvm::ArrayRef
<const Formula
*> &Vals
)
108 // `Atomics` needs to be initialized first so that we can use it as an
109 // output argument of `buildCNF()`.
110 : Atomics(), CNF(buildCNF(Vals
, Atomics
)),
111 LevelVars(CNF
.largestVar() + 1), LevelStates(CNF
.largestVar() + 1) {
112 assert(!Vals
.empty());
114 // Skip initialization if the formula is known to be contradictory.
115 if (CNF
.knownContradictory())
118 // Initialize `NextWatched` and `WatchedHead`.
119 NextWatched
.push_back(0);
120 const size_t NumLiterals
= 2 * CNF
.largestVar() + 1;
121 WatchedHead
.resize(NumLiterals
+ 1, 0);
122 for (ClauseID C
= 1; C
<= CNF
.numClauses(); ++C
) {
123 // Designate the first literal as the "watched" literal of the clause.
124 Literal FirstLit
= CNF
.clauseLiterals(C
).front();
125 NextWatched
.push_back(WatchedHead
[FirstLit
]);
126 WatchedHead
[FirstLit
] = C
;
129 // Initialize the state at the root level to a decision so that in
130 // `reverseForcedMoves` we don't have to check that `Level >= 0` on each
132 LevelStates
[0] = State::Decision
;
134 // Initialize all variables as unassigned.
135 VarAssignments
.resize(CNF
.largestVar() + 1, Assignment::Unassigned
);
137 // Initialize the active variables.
138 for (Variable Var
= CNF
.largestVar(); Var
!= NullVar
; --Var
) {
139 if (isWatched(posLit(Var
)) || isWatched(negLit(Var
)))
140 ActiveVars
.push_back(Var
);
144 // Returns the `Result` and the number of iterations "remaining" from
145 // `MaxIterations` (that is, `MaxIterations` - iterations in this call).
146 std::pair
<Solver::Result
, std::int64_t> solve(std::int64_t MaxIterations
) && {
147 if (CNF
.knownContradictory()) {
148 // Short-cut the solving process. We already found out at CNF
149 // construction time that the formula is unsatisfiable.
150 return std::make_pair(Solver::Result::Unsatisfiable(), MaxIterations
);
153 while (I
< ActiveVars
.size()) {
154 if (MaxIterations
== 0)
155 return std::make_pair(Solver::Result::TimedOut(), 0);
158 // Assert that the following invariants hold:
159 // 1. All active variables are unassigned.
160 // 2. All active variables form watched literals.
161 // 3. Unassigned variables that form watched literals are active.
162 // FIXME: Consider replacing these with test cases that fail if the any
163 // of the invariants is broken. That might not be easy due to the
164 // transformations performed by `buildCNF`.
165 assert(activeVarsAreUnassigned());
166 assert(activeVarsFormWatchedLiterals());
167 assert(unassignedVarsFormingWatchedLiteralsAreActive());
169 const Variable ActiveVar
= ActiveVars
[I
];
171 // Look for unit clauses that contain the active variable.
172 const bool unitPosLit
= watchedByUnitClause(posLit(ActiveVar
));
173 const bool unitNegLit
= watchedByUnitClause(negLit(ActiveVar
));
174 if (unitPosLit
&& unitNegLit
) {
175 // We found a conflict!
177 // Backtrack and rewind the `Level` until the most recent non-forced
179 reverseForcedMoves();
181 // If the root level is reached, then all possible assignments lead to
184 return std::make_pair(Solver::Result::Unsatisfiable(), MaxIterations
);
186 // Otherwise, take the other branch at the most recent level where a
187 // decision was made.
188 LevelStates
[Level
] = State::Forced
;
189 const Variable Var
= LevelVars
[Level
];
190 VarAssignments
[Var
] = VarAssignments
[Var
] == Assignment::AssignedTrue
191 ? Assignment::AssignedFalse
192 : Assignment::AssignedTrue
;
194 updateWatchedLiterals();
195 } else if (unitPosLit
|| unitNegLit
) {
196 // We found a unit clause! The value of its unassigned variable is
200 LevelVars
[Level
] = ActiveVar
;
201 LevelStates
[Level
] = State::Forced
;
202 VarAssignments
[ActiveVar
] =
203 unitPosLit
? Assignment::AssignedTrue
: Assignment::AssignedFalse
;
205 // Remove the variable that was just assigned from the set of active
207 if (I
+ 1 < ActiveVars
.size()) {
208 // Replace the variable that was just assigned with the last active
209 // variable for efficient removal.
210 ActiveVars
[I
] = ActiveVars
.back();
212 // This was the last active variable. Repeat the process from the
216 ActiveVars
.pop_back();
218 updateWatchedLiterals();
219 } else if (I
+ 1 == ActiveVars
.size()) {
220 // There are no remaining unit clauses in the formula! Make a decision
221 // for one of the active variables at the current level.
224 LevelVars
[Level
] = ActiveVar
;
225 LevelStates
[Level
] = State::Decision
;
226 VarAssignments
[ActiveVar
] = decideAssignment(ActiveVar
);
228 // Remove the variable that was just assigned from the set of active
230 ActiveVars
.pop_back();
232 updateWatchedLiterals();
234 // This was the last active variable. Repeat the process from the
241 return std::make_pair(Solver::Result::Satisfiable(buildSolution()),
246 /// Returns a satisfying truth assignment to the atoms in the boolean formula.
247 llvm::DenseMap
<Atom
, Solver::Result::Assignment
> buildSolution() {
248 llvm::DenseMap
<Atom
, Solver::Result::Assignment
> Solution
;
249 for (auto &Atomic
: Atomics
) {
250 // A variable may have a definite true/false assignment, or it may be
251 // unassigned indicating its truth value does not affect the result of
252 // the formula. Unassigned variables are assigned to true as a default.
253 Solution
[Atomic
.second
] =
254 VarAssignments
[Atomic
.first
] == Assignment::AssignedFalse
255 ? Solver::Result::Assignment::AssignedFalse
256 : Solver::Result::Assignment::AssignedTrue
;
261 /// Reverses forced moves until the most recent level where a decision was
262 /// made on the assignment of a variable.
263 void reverseForcedMoves() {
264 for (; LevelStates
[Level
] == State::Forced
; --Level
) {
265 const Variable Var
= LevelVars
[Level
];
267 VarAssignments
[Var
] = Assignment::Unassigned
;
269 // If the variable that we pass through is watched then we add it to the
271 if (isWatched(posLit(Var
)) || isWatched(negLit(Var
)))
272 ActiveVars
.push_back(Var
);
276 /// Updates watched literals that are affected by a variable assignment.
277 void updateWatchedLiterals() {
278 const Variable Var
= LevelVars
[Level
];
280 // Update the watched literals of clauses that currently watch the literal
281 // that falsifies `Var`.
282 const Literal FalseLit
= VarAssignments
[Var
] == Assignment::AssignedTrue
285 ClauseID FalseLitWatcher
= WatchedHead
[FalseLit
];
286 WatchedHead
[FalseLit
] = NullClause
;
287 while (FalseLitWatcher
!= NullClause
) {
288 const ClauseID NextFalseLitWatcher
= NextWatched
[FalseLitWatcher
];
290 // Pick the first non-false literal as the new watched literal.
291 const CNFFormula::Iterator FalseLitWatcherStart
=
292 CNF
.startOfClause(FalseLitWatcher
);
293 CNFFormula::Iterator NewWatchedLitIter
= FalseLitWatcherStart
.next();
294 while (isCurrentlyFalse(*NewWatchedLitIter
))
296 const Literal NewWatchedLit
= *NewWatchedLitIter
;
297 const Variable NewWatchedLitVar
= var(NewWatchedLit
);
299 // Swap the old watched literal for the new one in `FalseLitWatcher` to
300 // maintain the invariant that the watched literal is at the beginning of
302 *NewWatchedLitIter
= FalseLit
;
303 *FalseLitWatcherStart
= NewWatchedLit
;
305 // If the new watched literal isn't watched by any other clause and its
306 // variable isn't assigned we need to add it to the active variables.
307 if (!isWatched(NewWatchedLit
) && !isWatched(notLit(NewWatchedLit
)) &&
308 VarAssignments
[NewWatchedLitVar
] == Assignment::Unassigned
)
309 ActiveVars
.push_back(NewWatchedLitVar
);
311 NextWatched
[FalseLitWatcher
] = WatchedHead
[NewWatchedLit
];
312 WatchedHead
[NewWatchedLit
] = FalseLitWatcher
;
314 // Go to the next clause that watches `FalseLit`.
315 FalseLitWatcher
= NextFalseLitWatcher
;
319 /// Returns true if and only if one of the clauses that watch `Lit` is a unit
321 bool watchedByUnitClause(Literal Lit
) const {
322 for (ClauseID LitWatcher
= WatchedHead
[Lit
]; LitWatcher
!= NullClause
;
323 LitWatcher
= NextWatched
[LitWatcher
]) {
324 llvm::ArrayRef
<Literal
> Clause
= CNF
.clauseLiterals(LitWatcher
);
326 // Assert the invariant that the watched literal is always the first one
328 // FIXME: Consider replacing this with a test case that fails if the
329 // invariant is broken by `updateWatchedLiterals`. That might not be easy
330 // due to the transformations performed by `buildCNF`.
331 assert(Clause
.front() == Lit
);
339 /// Returns true if and only if `Clause` is a unit clause.
340 bool isUnit(llvm::ArrayRef
<Literal
> Clause
) const {
341 return llvm::all_of(Clause
.drop_front(),
342 [this](Literal L
) { return isCurrentlyFalse(L
); });
345 /// Returns true if and only if `Lit` evaluates to `false` in the current
346 /// partial assignment.
347 bool isCurrentlyFalse(Literal Lit
) const {
348 return static_cast<int8_t>(VarAssignments
[var(Lit
)]) ==
349 static_cast<int8_t>(Lit
& 1);
352 /// Returns true if and only if `Lit` is watched by a clause in `Formula`.
353 bool isWatched(Literal Lit
) const { return WatchedHead
[Lit
] != NullClause
; }
355 /// Returns an assignment for an unassigned variable.
356 Assignment
decideAssignment(Variable Var
) const {
357 return !isWatched(posLit(Var
)) || isWatched(negLit(Var
))
358 ? Assignment::AssignedFalse
359 : Assignment::AssignedTrue
;
362 /// Returns a set of all watched literals.
363 llvm::DenseSet
<Literal
> watchedLiterals() const {
364 llvm::DenseSet
<Literal
> WatchedLiterals
;
365 for (Literal Lit
= 2; Lit
< WatchedHead
.size(); Lit
++) {
366 if (WatchedHead
[Lit
] == NullClause
)
368 WatchedLiterals
.insert(Lit
);
370 return WatchedLiterals
;
373 /// Returns true if and only if all active variables are unassigned.
374 bool activeVarsAreUnassigned() const {
375 return llvm::all_of(ActiveVars
, [this](Variable Var
) {
376 return VarAssignments
[Var
] == Assignment::Unassigned
;
380 /// Returns true if and only if all active variables form watched literals.
381 bool activeVarsFormWatchedLiterals() const {
382 const llvm::DenseSet
<Literal
> WatchedLiterals
= watchedLiterals();
383 return llvm::all_of(ActiveVars
, [&WatchedLiterals
](Variable Var
) {
384 return WatchedLiterals
.contains(posLit(Var
)) ||
385 WatchedLiterals
.contains(negLit(Var
));
389 /// Returns true if and only if all unassigned variables that are forming
390 /// watched literals are active.
391 bool unassignedVarsFormingWatchedLiteralsAreActive() const {
392 const llvm::DenseSet
<Variable
> ActiveVarsSet(ActiveVars
.begin(),
394 for (Literal Lit
: watchedLiterals()) {
395 const Variable Var
= var(Lit
);
396 if (VarAssignments
[Var
] != Assignment::Unassigned
)
398 if (ActiveVarsSet
.contains(Var
))
409 WatchedLiteralsSolver::solve(llvm::ArrayRef
<const Formula
*> Vals
) {
411 return Solver::Result::Satisfiable({{}});
412 auto [Res
, Iterations
] = WatchedLiteralsSolverImpl(Vals
).solve(MaxIterations
);
413 MaxIterations
= Iterations
;
417 } // namespace dataflow