clang  19.0.0git
WatchedLiteralsSolver.cpp
Go to the documentation of this file.
1 //===- WatchedLiteralsSolver.cpp --------------------------------*- C++ -*-===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file defines a SAT solver implementation that can be used by dataflow
10 // analyses.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include <cassert>
15 #include <vector>
16 
21 #include "llvm/ADT/ArrayRef.h"
22 #include "llvm/ADT/DenseMap.h"
23 #include "llvm/ADT/DenseSet.h"
24 #include "llvm/ADT/STLExtras.h"
25 
26 
27 namespace clang {
28 namespace dataflow {
29 
30 namespace {
31 
32 class WatchedLiteralsSolverImpl {
33  /// Stores the variable identifier and Atom for atomic booleans in the
34  /// formula.
35  llvm::DenseMap<Variable, Atom> Atomics;
36 
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.
39  CNFFormula CNF;
40 
41  /// Maps literals (indices of the vector) to clause identifiers (elements of
42  /// the vector) that watch the respective literals.
43  ///
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;
47 
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.
50  ///
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;
55 
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.
63  size_t Level = 0;
64 
65  /// Maps levels (indices of the vector) to variables (elements of the vector)
66  /// that are assigned values at the respective levels.
67  ///
68  /// The element at index 0 isn't used. Variables start from the element at
69  /// index 1.
70  std::vector<Variable> LevelVars;
71 
72  /// State of the solver at a particular level.
73  enum class State : uint8_t {
74  /// Indicates that the solver made a decision.
75  Decision = 0,
76 
77  /// Indicates that the solver made a forced move.
78  Forced = 1,
79  };
80 
81  /// State of the solver at a particular level. It keeps track of previous
82  /// decisions that the solver can refer to when backtracking.
83  ///
84  /// The element at index 0 isn't used. States start from the element at index
85  /// 1.
86  std::vector<State> LevelStates;
87 
88  enum class Assignment : int8_t {
89  Unassigned = -1,
90  AssignedFalse = 0,
91  AssignedTrue = 1
92  };
93 
94  /// Maps variables (indices of the vector) to their assignments (elements of
95  /// the vector).
96  ///
97  /// The element at index 0 isn't used. Variable assignments start from the
98  /// element at index 1.
99  std::vector<Assignment> VarAssignments;
100 
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;
104 
105 public:
106  explicit WatchedLiteralsSolverImpl(
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());
113 
114  // Skip initialization if the formula is known to be contradictory.
115  if (CNF.knownContradictory())
116  return;
117 
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;
127  }
128 
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
131  // iteration.
132  LevelStates[0] = State::Decision;
133 
134  // Initialize all variables as unassigned.
135  VarAssignments.resize(CNF.largestVar() + 1, Assignment::Unassigned);
136 
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);
141  }
142  }
143 
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);
151  }
152  size_t I = 0;
153  while (I < ActiveVars.size()) {
154  if (MaxIterations == 0)
155  return std::make_pair(Solver::Result::TimedOut(), 0);
156  --MaxIterations;
157 
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());
168 
169  const Variable ActiveVar = ActiveVars[I];
170 
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!
176 
177  // Backtrack and rewind the `Level` until the most recent non-forced
178  // assignment.
179  reverseForcedMoves();
180 
181  // If the root level is reached, then all possible assignments lead to
182  // a conflict.
183  if (Level == 0)
184  return std::make_pair(Solver::Result::Unsatisfiable(), MaxIterations);
185 
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;
193 
194  updateWatchedLiterals();
195  } else if (unitPosLit || unitNegLit) {
196  // We found a unit clause! The value of its unassigned variable is
197  // forced.
198  ++Level;
199 
200  LevelVars[Level] = ActiveVar;
201  LevelStates[Level] = State::Forced;
202  VarAssignments[ActiveVar] =
203  unitPosLit ? Assignment::AssignedTrue : Assignment::AssignedFalse;
204 
205  // Remove the variable that was just assigned from the set of active
206  // variables.
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();
211  } else {
212  // This was the last active variable. Repeat the process from the
213  // beginning.
214  I = 0;
215  }
216  ActiveVars.pop_back();
217 
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.
222  ++Level;
223 
224  LevelVars[Level] = ActiveVar;
225  LevelStates[Level] = State::Decision;
226  VarAssignments[ActiveVar] = decideAssignment(ActiveVar);
227 
228  // Remove the variable that was just assigned from the set of active
229  // variables.
230  ActiveVars.pop_back();
231 
232  updateWatchedLiterals();
233 
234  // This was the last active variable. Repeat the process from the
235  // beginning.
236  I = 0;
237  } else {
238  ++I;
239  }
240  }
241  return std::make_pair(Solver::Result::Satisfiable(buildSolution()),
242  MaxIterations);
243  }
244 
245 private:
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
257  }
258  return Solution;
259  }
260 
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];
266 
267  VarAssignments[Var] = Assignment::Unassigned;
268 
269  // If the variable that we pass through is watched then we add it to the
270  // active variables.
271  if (isWatched(posLit(Var)) || isWatched(negLit(Var)))
272  ActiveVars.push_back(Var);
273  }
274  }
275 
276  /// Updates watched literals that are affected by a variable assignment.
277  void updateWatchedLiterals() {
278  const Variable Var = LevelVars[Level];
279 
280  // Update the watched literals of clauses that currently watch the literal
281  // that falsifies `Var`.
282  const Literal FalseLit = VarAssignments[Var] == Assignment::AssignedTrue
283  ? negLit(Var)
284  : posLit(Var);
285  ClauseID FalseLitWatcher = WatchedHead[FalseLit];
286  WatchedHead[FalseLit] = NullClause;
287  while (FalseLitWatcher != NullClause) {
288  const ClauseID NextFalseLitWatcher = NextWatched[FalseLitWatcher];
289 
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))
295  ++NewWatchedLitIter;
296  const Literal NewWatchedLit = *NewWatchedLitIter;
297  const Variable NewWatchedLitVar = var(NewWatchedLit);
298 
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
301  // the clause.
302  *NewWatchedLitIter = FalseLit;
303  *FalseLitWatcherStart = NewWatchedLit;
304 
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);
310 
311  NextWatched[FalseLitWatcher] = WatchedHead[NewWatchedLit];
312  WatchedHead[NewWatchedLit] = FalseLitWatcher;
313 
314  // Go to the next clause that watches `FalseLit`.
315  FalseLitWatcher = NextFalseLitWatcher;
316  }
317  }
318 
319  /// Returns true if and only if one of the clauses that watch `Lit` is a unit
320  /// clause.
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);
325 
326  // Assert the invariant that the watched literal is always the first one
327  // in the clause.
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);
332 
333  if (isUnit(Clause))
334  return true;
335  }
336  return false;
337  }
338 
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); });
343  }
344 
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);
350  }
351 
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; }
354 
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;
360  }
361 
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)
367  continue;
368  WatchedLiterals.insert(Lit);
369  }
370  return WatchedLiterals;
371  }
372 
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;
377  });
378  }
379 
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));
386  });
387  }
388 
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(),
393  ActiveVars.end());
394  for (Literal Lit : watchedLiterals()) {
395  const Variable Var = var(Lit);
396  if (VarAssignments[Var] != Assignment::Unassigned)
397  continue;
398  if (ActiveVarsSet.contains(Var))
399  continue;
400  return false;
401  }
402  return true;
403  }
404 };
405 
406 } // namespace
407 
408 Solver::Result
410  if (Vals.empty())
411  return Solver::Result::Satisfiable({{}});
412  auto [Res, Iterations] = WatchedLiteralsSolverImpl(Vals).solve(MaxIterations);
413  MaxIterations = Iterations;
414  return Res;
415 }
416 
417 } // namespace dataflow
418 } // namespace clang
LineState State
Result solve(llvm::ArrayRef< const Formula * > Vals) override
Checks if the conjunction of Vals is satisfiable and returns the corresponding result.
uint32_t ClauseID
Clause identifiers are represented as positive integers.
Definition: CNFFormula.h:42
constexpr Literal posLit(Variable V)
Returns the positive literal V.
Definition: CNFFormula.h:49
constexpr Variable NullVar
A null boolean variable is used as a placeholder in various data structures and algorithms.
Definition: CNFFormula.h:29
CNFFormula buildCNF(const llvm::ArrayRef< const Formula * > &Formulas, llvm::DenseMap< Variable, Atom > &Atomics)
Converts the conjunction of Vals into a formula in conjunctive normal form where each clause has at l...
Definition: CNFFormula.cpp:116
constexpr ClauseID NullClause
A null clause identifier is used as a placeholder in various data structures and algorithms.
Definition: CNFFormula.h:46
constexpr Variable var(Literal L)
Returns the variable of L.
Definition: CNFFormula.h:64
constexpr Literal negLit(Variable V)
Returns the negative literal !V.
Definition: CNFFormula.h:52
uint32_t Literal
Literals are represented as positive integers.
Definition: CNFFormula.h:35
uint32_t Variable
Boolean variables are represented as positive integers.
Definition: CNFFormula.h:25
constexpr Literal notLit(Literal L)
Returns the negated literal !L.
Definition: CNFFormula.h:61
The JSON file list parser is used to communicate input to InstallAPI.
long int64_t
static Result TimedOut()
Constructs a result indicating that satisfiability checking on the queried boolean formula was not co...
Definition: Solver.h:60
static Result Unsatisfiable()
Constructs a result indicating that the queried boolean formula is unsatisfiable.
Definition: Solver.h:56
static Result Satisfiable(llvm::DenseMap< Atom, Assignment > Solution)
Constructs a result indicating that the queried boolean formula is satisfiable.
Definition: Solver.h:50