5 #include "subprocess/process.h"
10 #ifdef BITWUZLA_LIBRARY
11 #include "bitwuzla/cpp/bitwuzla.h"
12 #include "bitwuzla/cpp/parser.h"
26 static const std::vector<std::string> z3_binary_paths = {
29 "/opt/homebrew/bin/z3",
32 for (
const auto& path : z3_binary_paths)
34 if (std::filesystem::exists(path))
40 return ERR(
"could not query binary path: no binary found for Z3 solver");
56 if (binary_path.is_error())
58 return ERR_APPEND(binary_path.get_error(),
"could not query Z3: unable to locate binary");
61 auto z3 = subprocess::Popen({binary_path.get(),
66 subprocess::error{subprocess::PIPE},
67 subprocess::output{subprocess::PIPE},
68 subprocess::input{subprocess::PIPE});
72 auto z3_communication = z3.communicate();
74 std::vector<char> output_buf = z3_communication.first.buf;
75 std::string
output(output_buf.begin(), output_buf.end());
102 return ERR(
"could not call Z3 solver library: Library call not implemented");
113 static const std::vector<std::string> boolector_binary_paths = {
114 "/usr/bin/boolector",
115 "/usr/local/bin/boolector",
116 "/opt/homebrew/bin/boolector",
119 for (
const auto& path : boolector_binary_paths)
121 if (std::filesystem::exists(path))
127 return ERR(
"could not query binary path: no binary found for Boolector solver");
143 if (binary_path.is_error())
145 return ERR_APPEND(binary_path.get_error(),
"could not query Boolector: unable to locate binary");
148 auto boolector = subprocess::Popen(
155 "--output-format=smt2",
157 std::string(
"--model-gen=") + ((config.
generate_model) ?
"1" :
"0"),
159 subprocess::output{subprocess::PIPE},
160 subprocess::input{subprocess::PIPE});
163 auto boolector_communication = boolector.communicate();
165 std::vector<char> output_buf = boolector_communication.first.buf;
166 std::string
output(output_buf.begin(), output_buf.end());
171 boolector.close_input();
172 boolector.close_output();
173 boolector.close_error();
192 return ERR(
"could not call Boolector solver library: Library call not implemented");
198 #ifdef BITWUZLA_LIBRARY
207 static const std::vector<std::string> bitwuzla_binary_paths = {
209 "/usr/local/bin/bitwuzla",
210 "/opt/homebrew/bin/bitwuzla",
213 for (
const auto& path : bitwuzla_binary_paths)
215 if (std::filesystem::exists(path))
221 return ERR(
"could not query binary path: no binary found for Bitwuzla solver");
236 #ifdef BITWUZLA_LIBRARY
239 bitwuzla::Options options;
243 const char* smt2_char_string =
input.c_str();
245 auto in_stream = fmemopen((
void*)smt2_char_string, strlen(smt2_char_string),
"r");
246 std::stringbuf result_string;
247 std::ostream output_stream(&result_string);
249 std::vector<bitwuzla::Term> all_vars;
253 options.set(bitwuzla::Option::PRODUCE_MODELS,
true);
259 bitwuzla::parser::Parser
parser(options,
"VIRTUAL_FILE", in_stream,
"smt2", &output_stream);
261 std::string err_msg =
parser.parse(
false);
263 if (!err_msg.empty())
265 return ERR(
"failed to parse input file: " + err_msg);
270 std::string
output(result_string.str());
275 return ERR(
"Bitwuzla Library not linked!");
292 if (binary_path.is_error())
294 return ERR_APPEND(binary_path.get_error(),
"could not query Bitwuzla: unable to locate binary");
300 return ERR(
"could not query Bitwuzla: binary call not implemented");
331 std::map<SolverType, std::function<Result<std::string>()>> Solver::type2query_binary = {
337 std::map<SolverType, bool> Solver::type2link_status = {
343 std::map<std::pair<SolverType, SolverCall>, std::function<Result<std::tuple<bool, std::string>>(
const std::string&,
const QueryConfig&)>> Solver::spec2query = {
352 Solver::Solver(
const std::vector<Constraint>& constraints) : m_constraints(constraints)
358 this->m_constraints.emplace_back(std::move(constraint));
364 for (
auto&& constraint : constraints)
366 this->m_constraints.emplace_back(std::move(constraint));
373 return m_constraints;
380 switch (
auto it = type2query_binary.find(
type); it != type2query_binary.end())
383 return it->second().is_ok();
390 switch (
auto it = type2link_status.find(
type); it != type2link_status.end())
406 if (
auto res = this->
query_local(config); res.is_error())
408 return ERR_APPEND(res.get_error(),
"unable to query SMT solver: local query failed");
417 if (
auto res = this->
query_remote(config); res.is_error())
419 return ERR_APPEND(res.get_error(),
"unable to query SMT solver: remote query failed");
430 auto input = Solver::translate_to_smt2(this->m_constraints, config);
431 if (
input.is_error())
433 return ERR_APPEND(
input.get_error(),
"could not query local SMT solver: unable to translate SMT constraints and configuration to string");
436 auto input_str =
input.get();
442 auto query = spec2query.at({config.
solver, config.
call})(smt2, config);
446 return Solver::translate_from_smt2(was_killed,
output, config);
448 return ERR_APPEND(
query.get_error(),
"could not query local SMT solver: unable to parse SMT result from string");
454 return ERR(
"could not query remote SMT solver: currently not supported");
459 return translate_to_smt2(this->m_constraints, config);
468 auto translate_declarations = [](
const std::vector<Constraint>& _constraints) -> std::string {
469 std::set<std::tuple<std::string, u16>> inputs;
470 for (
const auto& constraint : _constraints)
472 if (constraint.is_assignment())
474 for (
const auto& node : constraint.get_assignment().get()->first.get_nodes())
476 if (node.is_variable())
478 inputs.insert(std::make_tuple(node.variable, node.size));
484 for (
const auto& node : constraint.get_function().get()->get_nodes())
486 if (node.is_variable())
488 inputs.insert(std::make_tuple(node.variable, node.size));
494 return std::accumulate(inputs.begin(), inputs.end(), std::string(), [](
auto accumulator,
auto entry) -> std::string {
495 return accumulator +
"(declare-fun " + std::get<0>(entry) +
" () (_ BitVec " + std::to_string(std::get<1>(entry)) +
"))\n";
503 auto translate_constraints = [](
const std::vector<Constraint>& _constraints) -> Result<std::string> {
504 return std::accumulate(_constraints.cbegin(), _constraints.cend(),
Result<std::string>::Ok({}), [](
auto accumulator,
const auto& constraint) -> Result<std::string> {
506 if (accumulator.is_error())
508 return ERR(accumulator.get_error());
511 if (constraint.is_assignment())
513 const auto assignment = constraint.get_assignment().get();
514 auto lhs = Translator::translate_to_smt2(assignment->first);
515 auto rhs = Translator::translate_to_smt2(assignment->second);
518 return ERR_APPEND(lhs.get_error(),
"could not translate constraint to SMT-LIB v2: '" + constraint.to_string() +
"'");
520 else if (rhs.is_error())
522 return ERR_APPEND(rhs.get_error(),
"could not translate constraint to SMT-LIB v2: '" + constraint.to_string() +
"'");
526 return OK(accumulator.get() +
"(assert (= " + lhs.get() +
" " + rhs.get() +
"))\n");
534 return OK(accumulator.get() +
"(assert (= #b1 " + lhs.get() +
"))\n");
536 return ERR_APPEND(lhs.get_error(),
"could not translate constraint to SMT-LIB v2: '" + constraint.to_string() +
"'");
541 auto theory = std::string(
"(set-logic QF_ABV)");
542 auto declarations = translate_declarations(constraints);
543 auto constraints_str = translate_constraints(constraints);
544 auto epilogue = std::string(
"(check-sat)") + ((config.
generate_model) ?
"\n(get-model)" :
"");
546 if (constraints_str.is_ok())
548 return OK(theory +
"\n" + declarations +
"\n" + constraints_str.get() +
"\n" + epilogue);
550 return ERR_APPEND(constraints_str.get_error(),
"could not translate constraint to SMT-LIB v2: unable to generate translation constraints");
553 Result<SolverResult> Solver::translate_from_smt2(
bool was_killed, std::string stdout,
const QueryConfig& config)
557 return ERR(
"could not parse SMT result from string: the SMT solver was killed");
560 auto position = stdout.find_first_of(
'\n');
561 auto [result, model_str] = std::make_tuple(std::string(stdout, 0, position), std::string(stdout, position + 1));
563 auto to_lowercase = [](
const auto& s) -> std::string {
565 std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);
569 if (to_lowercase(result) ==
"sat")
571 if (config.generate_model)
573 if (
auto res =
Model::parse(model_str, config.solver).map<SolverResult>([](
const auto& model) -> Result<SolverResult> { return OK(SolverResult::Sat(model)); }); res.is_error())
575 return ERR_APPEND(res.get_error(),
"could not parse SMT result from string: unable to generate model");
583 return OK(SolverResult::Sat());
585 if (to_lowercase(result) ==
"unsat")
587 return OK(SolverResult::UnSat());
590 if ((to_lowercase(result) ==
"unknown") || result.rfind(
"[btor>main] ALARM TRIGGERED: time limit", 0))
592 return OK(SolverResult::Unknown());
595 return ERR(
"could not parse SMT result from string: invalid result");
static Result< T > Ok(const T &value)
Result< SolverResult > query_local(const QueryConfig &config) const
Result< std::string > to_smt2(const QueryConfig &config) const
static bool has_local_solver_for(SolverType type, SolverCall call)
Solver & with_constraints(const std::vector< Constraint > &constraints)
const std::vector< Constraint > & get_constraints() const
static Result< SolverResult > query_local_with_smt2(const QueryConfig &config, const std::string &smt2)
Solver(const std::vector< Constraint > &constraints={})
Result< SolverResult > query_remote(const QueryConfig &config) const
Result< SolverResult > query(const QueryConfig &config=QueryConfig()) const
Solver & with_constraint(const Constraint &constraint)
#define ERR_APPEND(prev_error, message)
Result< std::string > query_binary_path()
Checks whether a Bitwuzla binary is available on the system.
Result< std::tuple< bool, std::string > > query_binary(const std::string &input, const QueryConfig &config)
Result< std::tuple< bool, std::string > > query_library(const std::string &input, const QueryConfig &config)
Result< std::tuple< bool, std::string > > query_binary(const std::string &input, const QueryConfig &config)
Result< std::string > query_binary_path()
Checks whether a Boolector binary is available on the system.
Result< std::tuple< bool, std::string > > query_library(const std::string &input, const QueryConfig &config)
Result< std::string > translate_to_smt2(const BooleanFunction &function)
Result< std::tuple< bool, std::string > > query_library(const std::string &input, const QueryConfig &config)
Result< std::tuple< bool, std::string > > query_binary(const std::string &input, const QueryConfig &config)
Result< std::string > query_binary_path()
Checks whether a Z3 binary is available on the system.
std::unique_ptr< GateLibrary > parse(std::filesystem::path file_path)
if(BUILD_TESTS) include_directories($
bool generate_model
Controls whether the SMT solver should generate a model in case formula is satisfiable.
SolverType solver
The SMT solver identifier.
u64 timeout_in_seconds
The timeout after which the SMT solver is killed in seconds.
SolverCall call
The calling format for the SMT solver.