HAL
solver.cpp
Go to the documentation of this file.
2 
5 #include "subprocess/process.h"
6 
7 #include <numeric>
8 #include <set>
9 
10 #ifdef BITWUZLA_LIBRARY
11 #include "bitwuzla/cpp/bitwuzla.h"
12 #include "bitwuzla/cpp/parser.h"
13 #endif
14 
15 namespace hal
16 {
17  namespace SMT
18  {
19  namespace Z3
20  {
21  bool is_linked = false;
22 
25  {
26  static const std::vector<std::string> z3_binary_paths = {
27  "/usr/bin/z3",
28  "/usr/local/bin/z3",
29  "/opt/homebrew/bin/z3",
30  };
31 
32  for (const auto& path : z3_binary_paths)
33  {
34  if (std::filesystem::exists(path))
35  {
36  return OK(path);
37  }
38  }
39 
40  return ERR("could not query binary path: no binary found for Z3 solver");
41  }
42 
53  Result<std::tuple<bool, std::string>> query_binary(const std::string& input, const QueryConfig& config)
54  {
55  auto binary_path = query_binary_path();
56  if (binary_path.is_error())
57  {
58  return ERR_APPEND(binary_path.get_error(), "could not query Z3: unable to locate binary");
59  }
60 
61  auto z3 = subprocess::Popen({binary_path.get(),
62  // read SMT2LIB formula from stdin
63  "-in",
64  // kill execution after a given time
65  "-T:" + std::to_string(config.timeout_in_seconds)},
66  subprocess::error{subprocess::PIPE},
67  subprocess::output{subprocess::PIPE},
68  subprocess::input{subprocess::PIPE});
69 
70  z3.send(input.c_str(), input.size());
71 
72  auto z3_communication = z3.communicate();
73 
74  std::vector<char> output_buf = z3_communication.first.buf;
75  std::string output(output_buf.begin(), output_buf.end());
76 
77  // TODO:
78  // check whether process was terminated (i.e. killed) via the subprocess
79  // API to channel this to the caller
80  z3.close_input();
81  z3.close_output();
82  z3.close_error();
83  z3.kill();
84 
85  return OK({false, output});
86  }
87 
98  Result<std::tuple<bool, std::string>> query_library(const std::string& input, const QueryConfig& config)
99  {
100  UNUSED(input);
101  UNUSED(config);
102  return ERR("could not call Z3 solver library: Library call not implemented");
103  }
104  } // namespace Z3
105 
106  namespace Boolector
107  {
108  bool is_linked = false;
109 
112  {
113  static const std::vector<std::string> boolector_binary_paths = {
114  "/usr/bin/boolector",
115  "/usr/local/bin/boolector",
116  "/opt/homebrew/bin/boolector",
117  };
118 
119  for (const auto& path : boolector_binary_paths)
120  {
121  if (std::filesystem::exists(path))
122  {
123  return OK(path);
124  }
125  }
126 
127  return ERR("could not query binary path: no binary found for Boolector solver");
128  }
129 
140  Result<std::tuple<bool, std::string>> query_binary(const std::string& input, const QueryConfig& config)
141  {
142  auto binary_path = query_binary_path();
143  if (binary_path.is_error())
144  {
145  return ERR_APPEND(binary_path.get_error(), "could not query Boolector: unable to locate binary");
146  }
147 
148  auto boolector = subprocess::Popen(
149  {
150  binary_path.get(),
151  // NOTE the boolector binary provided as package takes different parameters. The ones below do not seem to work.
152  // kill execution after a given time
153  "--time=" + std::to_string(config.timeout_in_seconds),
154  // generate SMT-LIB v2 compatible output
155  "--output-format=smt2",
156  // set model generation if required
157  std::string("--model-gen=") + ((config.generate_model) ? "1" : "0"),
158  },
159  subprocess::output{subprocess::PIPE},
160  subprocess::input{subprocess::PIPE});
161 
162  boolector.send(input.c_str(), input.size());
163  auto boolector_communication = boolector.communicate();
164 
165  std::vector<char> output_buf = boolector_communication.first.buf;
166  std::string output(output_buf.begin(), output_buf.end());
167 
168  // TODO:
169  // check whether process was terminated (i.e. killed) via the subprocess
170  // API to channel this to the caller
171  boolector.close_input();
172  boolector.close_output();
173  boolector.close_error();
174  boolector.kill();
175  return OK({false, output});
176  }
177 
188  Result<std::tuple<bool, std::string>> query_library(const std::string& input, const QueryConfig& config)
189  {
190  UNUSED(input);
191  UNUSED(config);
192  return ERR("could not call Boolector solver library: Library call not implemented");
193  }
194  } // namespace Boolector
195 
196  namespace Bitwuzla
197  {
198 #ifdef BITWUZLA_LIBRARY
199  bool is_linked = true;
200 #else
201  bool is_linked = false;
202 #endif
203 
206  {
207  static const std::vector<std::string> bitwuzla_binary_paths = {
208  "/usr/bin/bitwuzla",
209  "/usr/local/bin/bitwuzla",
210  "/opt/homebrew/bin/bitwuzla",
211  };
212 
213  for (const auto& path : bitwuzla_binary_paths)
214  {
215  if (std::filesystem::exists(path))
216  {
217  return OK(path);
218  }
219  }
220 
221  return ERR("could not query binary path: no binary found for Bitwuzla solver");
222  }
223 
234  Result<std::tuple<bool, std::string>> query_library(const std::string& input, const QueryConfig& config)
235  {
236 #ifdef BITWUZLA_LIBRARY
237 
238  // First, create a Bitwuzla options instance.
239  bitwuzla::Options options;
240  bitwuzla::TermManager tm;
241  // We will parse example file `smt2/quickstart.smt2`.
242  // Create parser instance.
243  // We expect no error to occur.
244 
245  std::stringbuf result_string;
246  std::ostream output_stream(&result_string);
247 
248  std::vector<bitwuzla::Term> all_vars;
249 
250  if (config.generate_model)
251  {
252  options.set(bitwuzla::Option::PRODUCE_MODELS, true);
253  }
254 
255  // std::filesystem::path tmp_path = utils::get_unique_temp_directory().get();
256  // std::string output_file = std::string(tmp_path) + "/out.smt2";
257 
258  bitwuzla::parser::Parser parser(tm, options, "smt2", &output_stream);
259  // Now parse the input file.
260  try
261  {
262  parser.parse(input, false, false);
263  }
264  catch (bitwuzla::Exception& e)
265  {
266  return ERR("failed to parse input file: " + e.msg());
267  }
268 
269  std::string output(result_string.str());
270  // std::cout << "output" << std::endl;
271  // std::cout << output << std::endl;
272  return OK({false, output});
273 #else
274  return ERR("Bitwuzla Library not linked!");
275 #endif
276  }
277 
288  Result<std::tuple<bool, std::string>> query_binary(const std::string& input, const QueryConfig& config)
289  {
290  auto binary_path = query_binary_path();
291  if (binary_path.is_error())
292  {
293  return ERR_APPEND(binary_path.get_error(), "could not query Bitwuzla: unable to locate binary");
294  }
295 
296  // TODO check how to timeout bitwuzla
297  UNUSED(config);
298 
299  return ERR("could not query Bitwuzla: binary call not implemented");
300 
301  /*
302  auto bitwuzla = subprocess::Popen(
303  {
304  binary_path.get(),
305  },
306  subprocess::error{subprocess::PIPE},
307  subprocess::output{subprocess::PIPE},
308  subprocess::input{subprocess::PIPE});
309 
310  bitwuzla.send(input.c_str(), input.size());
311 
312  auto bitwuzla_communication = bitwuzla.communicate();
313 
314  std::vector<char> output_buf = bitwuzla_communication.first.buf;
315  std::string output(output_buf.begin(), output_buf.end());
316 
317  // TODO:
318  // check whether process was terminated (i.e. killed) via the subprocess
319  // API to channel this to the caller
320  bitwuzla.close_input();
321  bitwuzla.close_output();
322  bitwuzla.close_error();
323  bitwuzla.kill();
324 
325  return OK({false, output});
326  */
327  }
328  } // namespace Bitwuzla
329 
330  std::map<SolverType, std::function<Result<std::string>()>> Solver::type2query_binary = {
334  };
335 
336  std::map<SolverType, bool> Solver::type2link_status = {
340  };
341 
342  std::map<std::pair<SolverType, SolverCall>, std::function<Result<std::tuple<bool, std::string>>(const std::string&, const QueryConfig&)>> Solver::spec2query = {
349  };
350 
351  Solver::Solver(const std::vector<Constraint>& constraints) : m_constraints(constraints)
352  {
353  }
354 
356  {
357  this->m_constraints.emplace_back(std::move(constraint));
358  return *this;
359  }
360 
361  Solver& Solver::with_constraints(const std::vector<Constraint>& constraints)
362  {
363  for (auto&& constraint : constraints)
364  {
365  this->m_constraints.emplace_back(std::move(constraint));
366  }
367  return *this;
368  }
369 
370  const std::vector<Constraint>& Solver::get_constraints() const
371  {
372  return m_constraints;
373  }
374 
376  {
377  if (call == SolverCall::Binary)
378  {
379  switch (auto it = type2query_binary.find(type); it != type2query_binary.end())
380  {
381  case true:
382  return it->second().is_ok();
383  default:
384  return false;
385  }
386  }
387  else if (call == SolverCall::Binary)
388  {
389  switch (auto it = type2link_status.find(type); it != type2link_status.end())
390  {
391  case true:
392  return it->second;
393  default:
394  return false;
395  }
396  }
397 
398  return false;
399  }
400 
402  {
403  if (config.local)
404  {
405  if (auto res = this->query_local(config); res.is_error())
406  {
407  return ERR_APPEND(res.get_error(), "unable to query SMT solver: local query failed");
408  }
409  else
410  {
411  return res;
412  }
413  }
414  else
415  {
416  if (auto res = this->query_remote(config); res.is_error())
417  {
418  return ERR_APPEND(res.get_error(), "unable to query SMT solver: remote query failed");
419  }
420  else
421  {
422  return res;
423  }
424  }
425  }
426 
428  {
429  auto input = Solver::translate_to_smt2(this->m_constraints, config);
430  if (input.is_error())
431  {
432  return ERR_APPEND(input.get_error(), "could not query local SMT solver: unable to translate SMT constraints and configuration to string");
433  }
434 
435  auto input_str = input.get();
436  return query_local_with_smt2(config, input_str);
437  }
438 
439  Result<SolverResult> Solver::query_local_with_smt2(const QueryConfig& config, const std::string& smt2)
440  {
441  auto query = spec2query.at({config.solver, config.call})(smt2, config);
442  if (query.is_ok())
443  {
444  auto [was_killed, output] = query.get();
445  return Solver::translate_from_smt2(was_killed, output, config);
446  }
447  return ERR_APPEND(query.get_error(), "could not query local SMT solver: unable to parse SMT result from string");
448  }
449 
451  {
452  // unimplemented as this is feature not required at the moment
453  return ERR("could not query remote SMT solver: currently not supported");
454  }
455 
457  {
458  return translate_to_smt2(this->m_constraints, config);
459  }
460 
461  Result<std::string> Solver::translate_to_smt2(const std::vector<Constraint>& constraints, const QueryConfig& config)
462  {
467  auto translate_declarations = [](const std::vector<Constraint>& _constraints) -> std::string {
468  std::set<std::tuple<std::string, u16>> inputs;
469  for (const auto& constraint : _constraints)
470  {
471  if (constraint.is_assignment())
472  {
473  for (const auto& node : constraint.get_assignment().get()->first.get_nodes())
474  {
475  if (node.is_variable())
476  {
477  inputs.insert(std::make_tuple(node.variable, node.size));
478  }
479  }
480  }
481  else
482  {
483  for (const auto& node : constraint.get_function().get()->get_nodes())
484  {
485  if (node.is_variable())
486  {
487  inputs.insert(std::make_tuple(node.variable, node.size));
488  }
489  }
490  }
491  }
492 
493  return std::accumulate(inputs.begin(), inputs.end(), std::string(), [](auto accumulator, auto entry) -> std::string {
494  return accumulator + "(declare-fun " + std::get<0>(entry) + " () (_ BitVec " + std::to_string(std::get<1>(entry)) + "))\n";
495  });
496  };
497 
502  auto translate_constraints = [](const std::vector<Constraint>& _constraints) -> Result<std::string> {
503  return std::accumulate(_constraints.cbegin(), _constraints.cend(), Result<std::string>::Ok({}), [](auto accumulator, const auto& constraint) -> Result<std::string> {
504  // (1) short-hand termination in case accumulator is an error
505  if (accumulator.is_error())
506  {
507  return ERR(accumulator.get_error());
508  }
509 
510  if (constraint.is_assignment())
511  {
512  const auto assignment = constraint.get_assignment().get();
513  auto lhs = Translator::translate_to_smt2(assignment->first);
514  auto rhs = Translator::translate_to_smt2(assignment->second);
515  if (lhs.is_error())
516  {
517  return ERR_APPEND(lhs.get_error(), "could not translate constraint to SMT-LIB v2: '" + constraint.to_string() + "'");
518  }
519  else if (rhs.is_error())
520  {
521  return ERR_APPEND(rhs.get_error(), "could not translate constraint to SMT-LIB v2: '" + constraint.to_string() + "'");
522  }
523  else
524  {
525  return OK(accumulator.get() + "(assert (= " + lhs.get() + " " + rhs.get() + "))\n");
526  }
527  }
528  else
529  {
530  auto lhs = Translator::translate_to_smt2(*constraint.get_function().get());
531  if (lhs.is_ok())
532  {
533  return OK(accumulator.get() + "(assert (= #b1 " + lhs.get() + "))\n");
534  }
535  return ERR_APPEND(lhs.get_error(), "could not translate constraint to SMT-LIB v2: '" + constraint.to_string() + "'");
536  }
537  });
538  };
539 
540  auto theory = std::string("(set-logic QF_ABV)");
541  auto declarations = translate_declarations(constraints);
542  auto constraints_str = translate_constraints(constraints);
543  auto epilogue = std::string("(check-sat)") + ((config.generate_model) ? "\n(get-model)" : "");
544 
545  if (constraints_str.is_ok())
546  {
547  return OK(theory + "\n" + declarations + "\n" + constraints_str.get() + "\n" + epilogue);
548  }
549  return ERR_APPEND(constraints_str.get_error(), "could not translate constraint to SMT-LIB v2: unable to generate translation constraints");
550  }
551 
552  Result<SolverResult> Solver::translate_from_smt2(bool was_killed, std::string stdout, const QueryConfig& config)
553  {
554  if (was_killed)
555  {
556  return ERR("could not parse SMT result from string: the SMT solver was killed");
557  }
558 
559  auto position = stdout.find_first_of('\n');
560  auto [result, model_str] = std::make_tuple(std::string(stdout, 0, position), std::string(stdout, position + 1));
561 
562  auto to_lowercase = [](const auto& s) -> std::string {
563  auto lowercase = s;
564  std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);
565  return lowercase;
566  };
567 
568  if (to_lowercase(result) == "sat")
569  {
570  if (config.generate_model)
571  {
572  if (auto res = Model::parse(model_str, config.solver).map<SolverResult>([](const auto& model) -> Result<SolverResult> { return OK(SolverResult::Sat(model)); }); res.is_error())
573  {
574  return ERR_APPEND(res.get_error(), "could not parse SMT result from string: unable to generate model");
575  }
576  else
577  {
578  return res;
579  }
580  }
581 
582  return OK(SolverResult::Sat());
583  }
584  if (to_lowercase(result) == "unsat")
585  {
586  return OK(SolverResult::UnSat());
587  }
588 
589  if ((to_lowercase(result) == "unknown") || result.rfind("[btor>main] ALARM TRIGGERED: time limit", 0))
590  {
591  return OK(SolverResult::Unknown());
592  }
593 
594  return ERR("could not parse SMT result from string: invalid result");
595  }
596  } // namespace SMT
597 } // namespace hal
static Result< T > Ok(const T &value)
Definition: result.h:95
Result< SolverResult > query_local(const QueryConfig &config) const
Definition: solver.cpp:427
Result< std::string > to_smt2(const QueryConfig &config) const
Definition: solver.cpp:456
static bool has_local_solver_for(SolverType type, SolverCall call)
Definition: solver.cpp:375
Solver & with_constraints(const std::vector< Constraint > &constraints)
Definition: solver.cpp:361
const std::vector< Constraint > & get_constraints() const
Definition: solver.cpp:370
static Result< SolverResult > query_local_with_smt2(const QueryConfig &config, const std::string &smt2)
Definition: solver.cpp:439
Solver(const std::vector< Constraint > &constraints={})
Definition: solver.cpp:351
Result< SolverResult > query_remote(const QueryConfig &config) const
Definition: solver.cpp:450
Result< SolverResult > query(const QueryConfig &config=QueryConfig()) const
Definition: solver.cpp:401
Solver & with_constraint(const Constraint &constraint)
Definition: solver.cpp:355
#define UNUSED(expr)
Definition: defines.h:49
#define ERR(message)
Definition: result.h:53
#define OK(...)
Definition: result.h:49
#define ERR_APPEND(prev_error, message)
Definition: result.h:57
parser
Definition: control.py:13
Result< std::string > query_binary_path()
Checks whether a Bitwuzla binary is available on the system.
Definition: solver.cpp:205
Result< std::tuple< bool, std::string > > query_binary(const std::string &input, const QueryConfig &config)
Definition: solver.cpp:288
Result< std::tuple< bool, std::string > > query_library(const std::string &input, const QueryConfig &config)
Definition: solver.cpp:234
Result< std::tuple< bool, std::string > > query_binary(const std::string &input, const QueryConfig &config)
Definition: solver.cpp:140
Result< std::string > query_binary_path()
Checks whether a Boolector binary is available on the system.
Definition: solver.cpp:111
Result< std::tuple< bool, std::string > > query_library(const std::string &input, const QueryConfig &config)
Definition: solver.cpp:188
Result< std::string > translate_to_smt2(const BooleanFunction &function)
Definition: translator.cpp:137
bool is_linked
Definition: solver.cpp:21
Result< std::tuple< bool, std::string > > query_library(const std::string &input, const QueryConfig &config)
Definition: solver.cpp:98
Result< std::tuple< bool, std::string > > query_binary(const std::string &input, const QueryConfig &config)
Definition: solver.cpp:53
Result< std::string > query_binary_path()
Checks whether a Z3 binary is available on the system.
Definition: solver.cpp:24
SolverCall
Definition: types.h:52
SolverType
Definition: types.h:44
std::unique_ptr< GateLibrary > parse(std::filesystem::path file_path)
PinType type
if(BUILD_TESTS) include_directories($
Definition: CMakeLists.txt:1
bool generate_model
Controls whether the SMT solver should generate a model in case formula is satisfiable.
Definition: types.h:73
SolverType solver
The SMT solver identifier.
Definition: types.h:67
u64 timeout_in_seconds
The timeout after which the SMT solver is killed in seconds.
Definition: types.h:75
SolverCall call
The calling format for the SMT solver.
Definition: types.h:69