CIRCT 24.0.0git
Loading...
Searching...
No Matches
SATSolver.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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 incremental SAT solvers with an IPASIR-style interface.
10//
11//===----------------------------------------------------------------------===//
12
14#include "llvm/ADT/ArrayRef.h"
15#include "llvm/ADT/SmallVector.h"
16#include "llvm/Support/ErrorHandling.h"
17#include "llvm/Support/SMTAPI.h"
18
19#ifdef CIRCT_CADICAL_ENABLED
20#include "third_party/cadical/cadical.hpp"
21#endif
22
23#include <cassert>
24#include <cstdlib>
25#include <string>
26
27namespace circt {
28
29namespace {
30
31//===----------------------------------------------------------------------===//
32// CaDiCaL Backend
33//===----------------------------------------------------------------------===//
34
35#ifdef CIRCT_CADICAL_ENABLED
36
37const char *
38toCadicalConfigName(CadicalSATSolverOptions::CadicalSolverConfig config) {
39 switch (config) {
41 return "default";
43 return "plain";
45 return "sat";
47 return "unsat";
48 }
49 llvm_unreachable("unknown CaDiCaL configuration");
50}
51
52class CadicalSATSolver : public IncrementalSATSolver {
53public:
54 explicit CadicalSATSolver(const CadicalSATSolverOptions &options) {
55 if (options.config !=
56 CadicalSATSolverOptions::CadicalSolverConfig::Default) {
57 bool configured = solver.configure(toCadicalConfigName(options.config));
58 assert(configured && "invalid CaDiCaL configuration");
59 (void)configured;
60 }
61 }
62 void add(int lit) override { solver.add(lit); }
63 void assume(int lit) override {
64 if (lit != 0)
65 solver.assume(lit);
66 }
67 Result solve() override {
68 if (conflictLimit >= 0)
69 solver.limit("conflicts", conflictLimit);
70 switch (solver.solve()) {
71 case CaDiCaL::SATISFIABLE:
72 return kSAT;
73 case CaDiCaL::UNSATISFIABLE:
74 return kUNSAT;
75 default:
76 return kUNKNOWN;
77 }
78 }
79 int val(int v) const override {
80 if (v <= 0 || v > maxVariable)
81 return 0;
82 return solver.val(v);
83 }
84 void setConflictLimit(int limit) override { conflictLimit = limit; }
85 void reserveVars(int maxVar) override {
86 if (maxVar <= maxVariable)
87 return;
88 solver.resize(maxVar);
89 maxVariable = maxVar;
90 }
91 void addClause(llvm::ArrayRef<int> lits) override {
92 if (lits.empty()) {
93 solver.add(0);
94 return;
95 }
96 solver.clause(lits.data(), lits.size());
97 }
98 int newVar() override {
99 int var = solver.declare_one_more_variable();
100 if (var > maxVariable)
101 maxVariable = var;
102 return var;
103 }
104
105private:
106 mutable CaDiCaL::Solver solver;
107 int maxVariable = 0;
108 int conflictLimit = -1;
109};
110
111#endif // CIRCT_CADICAL_ENABLED
112
113//===----------------------------------------------------------------------===//
114// Z3 Backend
115//===----------------------------------------------------------------------===//
116
117#if LLVM_WITH_Z3
118
119class Z3SATSolver : public IncrementalSATSolver {
120public:
121 Z3SATSolver();
122 ~Z3SATSolver() override;
123
124 void add(int lit) override;
125 void assume(int lit) override;
126 Result solve() override;
127 Result solve(llvm::ArrayRef<int> assumptions) override;
128 int val(int v) const override;
129 void reserveVars(int maxVar) override;
130 int newVar() override;
131
132private:
133 void clearSolveScope();
134 int newVariable();
135 llvm::SMTExprRef literalToExpr(int lit);
136 void addClauseInternal(llvm::ArrayRef<int> lits);
137
138 llvm::SMTSolverRef solver;
139 llvm::SmallVector<llvm::SMTExprRef> variables;
140 llvm::SmallVector<int> assumptions;
141 llvm::SmallVector<int> clauseBuffer;
142 int maxVariable = 0;
143 Result lastResult = kUNKNOWN;
144 bool solveScopeActive = false;
145};
146
147Z3SATSolver::Z3SATSolver() : solver(llvm::CreateZ3Solver()) {}
148
149Z3SATSolver::~Z3SATSolver() { clearSolveScope(); }
150
151void Z3SATSolver::add(int lit) {
152 clearSolveScope();
153 if (lit == 0) {
154 addClauseInternal(clauseBuffer);
155 clauseBuffer.clear();
156 return;
157 }
158
159 reserveVars(std::abs(lit));
160 clauseBuffer.push_back(lit);
161}
162
163void Z3SATSolver::assume(int lit) {
164 clearSolveScope();
165 if (lit == 0)
166 return;
167 assumptions.push_back(lit);
168}
169
170IncrementalSATSolver::Result Z3SATSolver::solve() {
171 auto localAssumptions = assumptions;
172 assumptions.clear();
173 return solve(localAssumptions);
174}
175
176IncrementalSATSolver::Result
177Z3SATSolver::solve(llvm::ArrayRef<int> assumptions) {
178 clearSolveScope();
179 solver->push();
180 solveScopeActive = true;
181 for (int lit : assumptions)
182 solver->addConstraint(literalToExpr(lit));
183 auto result = solver->check();
184 if (!result)
185 return lastResult = kUNKNOWN;
186 if (*result)
187 return lastResult = kSAT;
188 return lastResult = kUNSAT;
189}
190
191int Z3SATSolver::val(int v) const {
192 if (lastResult != kSAT || v <= 0 || v > maxVariable)
193 return 0;
194 llvm::APSInt value(llvm::APInt(1, 0), true);
195 // Z3 returns an interpretation for all variables, even those not involved
196 // in the problem. If the variable is not involved, return 0 to indicate
197 // "undefined" rather than a potentially misleading true/false value.
198 if (!solver->getInterpretation(variables[v - 1], value))
199 return 0;
200 return value != 0 ? v : -v;
201}
202
203void Z3SATSolver::reserveVars(int maxVar) {
204 if (maxVar <= maxVariable)
205 return;
206 while (static_cast<int>(variables.size()) < maxVar)
207 newVariable();
208 maxVariable = maxVar;
209}
210
211int Z3SATSolver::newVar() {
212 int var = newVariable();
213 if (var > maxVariable)
214 maxVariable = var;
215 return var;
216}
217
218void Z3SATSolver::clearSolveScope() {
219 if (!solveScopeActive)
220 return;
221 solver->pop();
222 solveScopeActive = false;
223 lastResult = kUNKNOWN;
224}
225
226int Z3SATSolver::newVariable() {
227 int varIndex = static_cast<int>(variables.size()) + 1;
228 std::string name = "v" + std::to_string(varIndex);
229 variables.push_back(solver->mkSymbol(name.c_str(), solver->getBoolSort()));
230 return varIndex;
231}
232
233llvm::SMTExprRef Z3SATSolver::literalToExpr(int lit) {
234 int absLit = std::abs(lit);
235 // Ensure variable exists for this literal.
236 reserveVars(absLit);
237 auto *variable = variables[absLit - 1];
238 return lit > 0 ? variable : solver->mkNot(variable);
239}
240
241void Z3SATSolver::addClauseInternal(llvm::ArrayRef<int> lits) {
242 if (lits.empty()) {
243 solver->addConstraint(solver->mkBoolean(false));
244 return;
245 }
246
247 llvm::SMTExprRef clause = nullptr;
248 for (int lit : lits) {
249 if (lit == 0)
250 continue;
251 auto *expr = literalToExpr(lit);
252 clause = clause ? solver->mkOr(clause, expr) : expr;
253 }
254
255 if (!clause) {
256 solver->addConstraint(solver->mkBoolean(false));
257 return;
258 }
259 solver->addConstraint(clause);
260}
261
262#endif // LLVM_WITH_Z3
263
264} // namespace
265
266void addAndClauses(int outVar, llvm::ArrayRef<int> inputLits,
267 llvm::function_ref<void(llvm::ArrayRef<int>)> addClause) {
268 for (int lit : inputLits)
269 addClause({-outVar, lit});
270
271 llvm::SmallVector<int> clause;
272 clause.reserve(inputLits.size() + 1);
273 for (int lit : inputLits)
274 clause.push_back(-lit);
275 clause.push_back(outVar);
276 addClause(clause);
277}
278
279void addOrClauses(int outVar, llvm::ArrayRef<int> inputLits,
280 llvm::function_ref<void(llvm::ArrayRef<int>)> addClause) {
281 for (int lit : inputLits)
282 addClause({-lit, outVar});
283
284 llvm::SmallVector<int> clause;
285 clause.reserve(inputLits.size() + 1);
286 clause.push_back(-outVar);
287 clause.append(inputLits.begin(), inputLits.end());
288 addClause(clause);
289}
290
291void addXorClauses(int outVar, int lhsLit, int rhsLit,
292 llvm::function_ref<void(llvm::ArrayRef<int>)> addClause) {
293 addClause({-lhsLit, -rhsLit, -outVar});
294 addClause({lhsLit, rhsLit, -outVar});
295 addClause({lhsLit, -rhsLit, outVar});
296 addClause({-lhsLit, rhsLit, outVar});
297}
298
299void addParityClauses(int outVar, llvm::ArrayRef<int> inputLits,
300 llvm::function_ref<void(llvm::ArrayRef<int>)> addClause,
301 llvm::function_ref<int()> newVar) {
302 assert(!inputLits.empty() && "parity requires at least one input");
303 if (inputLits.size() == 1) {
304 addClause({-outVar, inputLits.front()});
305 addClause({outVar, -inputLits.front()});
306 return;
307 }
308
309 int accumulatedLit = inputLits.front();
310 for (auto [index, lit] : llvm::enumerate(inputLits.drop_front())) {
311 bool isLast = index + 2 == inputLits.size();
312 int outLit = isLast ? outVar : newVar();
313 addXorClauses(outLit, accumulatedLit, lit, addClause);
314 accumulatedLit = outLit;
315 }
316}
317
319 llvm::ArrayRef<int> inputLits,
320 llvm::function_ref<void(llvm::ArrayRef<int>)> addClause,
321 llvm::function_ref<int()> newVar) {
322 if (inputLits.size() < 2)
323 return;
324
325 // Emit the clause encoding `lhs => rhs`.
326 auto imply = [&](int lhs, int rhs) { addClause({-lhs, rhs}); };
327
328 // Use a sequential-ladder encoding for the at-most-one constraint; see
329 // Carsten Sinz, "Towards an Optimal CNF Encoding of Boolean Cardinality
330 // Constraints", CP 2005.
331 //
332 // `ladder[i]` means "at least one of `inputLits[0]` through `inputLits[i]`
333 // is true".
334 // We use these helper variables to remember when an earlier choice was made.
335 llvm::SmallVector<int, 8> ladder(inputLits.size() - 1);
336 for (int &var : ladder)
337 var = newVar();
338
339 imply(inputLits.front(), ladder.front());
340 for (unsigned i = 1, e = inputLits.size() - 1; i < e; ++i) {
341 // If `inputLits[i]` is true, remember that one of `inputLits[0..i]` is
342 // true.
343 imply(inputLits[i], ladder[i]);
344 // If an earlier variable was already true, keep that fact true for the
345 // larger range `inputLits[0..i]`.
346 imply(ladder[i - 1], ladder[i]);
347 // If `ladder[i - 1]` is true, then some earlier variable was true, so
348 // `inputLits[i]` must be false since at most one variable can be true.
349 imply(ladder[i - 1], -inputLits[i]);
350 }
351
352 // If `ladder.back()` is true, then some earlier variable was true, so the
353 // last variable must be false.
354 imply(ladder.back(), -inputLits.back());
355}
356
358 llvm::ArrayRef<int> inputLits,
359 llvm::function_ref<void(llvm::ArrayRef<int>)> addClause,
360 llvm::function_ref<int()> newVar) {
361 addClause(inputLits);
362 addAtMostOneClauses(inputLits, addClause, newVar);
363}
364
365std::unique_ptr<IncrementalSATSolver> createZ3SATSolver() {
366#if LLVM_WITH_Z3
367 return std::make_unique<Z3SATSolver>();
368#else
369 return {};
370#endif
371}
372
373std::unique_ptr<IncrementalSATSolver>
375#ifdef CIRCT_CADICAL_ENABLED
376 return std::make_unique<CadicalSATSolver>(options);
377#else
378 return {};
379#endif
380}
381
382std::unique_ptr<IncrementalSATSolver> createSATSolver(llvm::StringRef backend) {
383 if (backend == "auto") {
384 if (auto solver = createCadicalSATSolver())
385 return solver;
386 return createZ3SATSolver();
387 }
388 if (backend == "cadical")
389 return createCadicalSATSolver();
390 if (backend == "z3")
391 return createZ3SATSolver();
392 return {};
393}
394
396 return static_cast<bool>(createSATSolver());
397}
398
399} // namespace circt
assert(baseType &&"element must be base type")
Abstract interface for incremental SAT solvers with an IPASIR-style API.
Definition SATSolver.h:23
virtual void add(int lit)=0
Add one literal to the clause currently under construction.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
bool hasIncrementalSATSolverBackend()
Return true when at least one incremental SAT backend is available.
void addExactlyOneClauses(llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause, llvm::function_ref< int()> newVar)
Emit clauses encoding that exactly one literal in inputLits is true.
void addAndClauses(int outVar, llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause)
Emit clauses encoding outVar <=> and(inputLits).
void addXorClauses(int outVar, int lhsLit, int rhsLit, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause)
Emit clauses encoding outVar <=> (lhsLit xor rhsLit).
std::unique_ptr< IncrementalSATSolver > createZ3SATSolver()
Construct a Z3-backed incremental IPASIR-style SAT solver.
std::unique_ptr< IncrementalSATSolver > createCadicalSATSolver(const CadicalSATSolverOptions &options={})
Construct a CaDiCaL-backed incremental IPASIR-style SAT solver.
void addOrClauses(int outVar, llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause)
Emit clauses encoding outVar <=> or(inputLits).
void addParityClauses(int outVar, llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause, llvm::function_ref< int()> newVar)
Emit clauses encoding outVar <=> parity(inputLits).
void addAtMostOneClauses(llvm::ArrayRef< int > inputLits, llvm::function_ref< void(llvm::ArrayRef< int >)> addClause, llvm::function_ref< int()> newVar)
Emit clauses encoding that at most one literal in inputLits can be true.
std::unique_ptr< IncrementalSATSolver > createSATSolver(llvm::StringRef backend="auto")
Construct an incremental SAT solver using the requested backend.