CIRCT 23.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
383 return static_cast<bool>(createCadicalSATSolver()) ||
384 static_cast<bool>(createZ3SATSolver());
385}
386
387} // namespace circt
assert(baseType &&"element must be base type")
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.