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  // We will parse example file `smt2/quickstart.smt2`.
241  // Create parser instance.
242  // We expect no error to occur.
243  const char* smt2_char_string = input.c_str();
244 
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);
248 
249  std::vector<bitwuzla::Term> all_vars;
250 
251  if (config.generate_model)
252  {
253  options.set(bitwuzla::Option::PRODUCE_MODELS, true);
254  }
255 
256  // std::filesystem::path tmp_path = utils::get_unique_temp_directory().get();
257  // std::string output_file = std::string(tmp_path) + "/out.smt2";
258 
259  bitwuzla::parser::Parser parser(options, "VIRTUAL_FILE", in_stream, "smt2", &output_stream);
260  // Now parse the input file.
261  std::string err_msg = parser.parse(false);
262 
263  if (!err_msg.empty())
264  {
265  return ERR("failed to parse input file: " + err_msg);
266  }
267 
268  fclose(in_stream);
269 
270  std::string output(result_string.str());
271  // std::cout << "output" << std::endl;
272  // std::cout << output << std::endl;
273  return OK({false, output});
274 #else
275  return ERR("Bitwuzla Library not linked!");
276 #endif
277  }
278 
289  Result<std::tuple<bool, std::string>> query_binary(const std::string& input, const QueryConfig& config)
290  {
291  auto binary_path = query_binary_path();
292  if (binary_path.is_error())
293  {
294  return ERR_APPEND(binary_path.get_error(), "could not query Bitwuzla: unable to locate binary");
295  }
296 
297  // TODO check how to timeout bitwuzla
298  UNUSED(config);
299 
300  return ERR("could not query Bitwuzla: binary call not implemented");
301 
302  /*
303  auto bitwuzla = subprocess::Popen(
304  {
305  binary_path.get(),
306  },
307  subprocess::error{subprocess::PIPE},
308  subprocess::output{subprocess::PIPE},
309  subprocess::input{subprocess::PIPE});
310 
311  bitwuzla.send(input.c_str(), input.size());
312 
313  auto bitwuzla_communication = bitwuzla.communicate();
314 
315  std::vector<char> output_buf = bitwuzla_communication.first.buf;
316  std::string output(output_buf.begin(), output_buf.end());
317 
318  // TODO:
319  // check whether process was terminated (i.e. killed) via the subprocess
320  // API to channel this to the caller
321  bitwuzla.close_input();
322  bitwuzla.close_output();
323  bitwuzla.close_error();
324  bitwuzla.kill();
325 
326  return OK({false, output});
327  */
328  }
329  } // namespace Bitwuzla
330 
331  std::map<SolverType, std::function<Result<std::string>()>> Solver::type2query_binary = {
335  };
336 
337  std::map<SolverType, bool> Solver::type2link_status = {
341  };
342 
343  std::map<std::pair<SolverType, SolverCall>, std::function<Result<std::tuple<bool, std::string>>(const std::string&, const QueryConfig&)>> Solver::spec2query = {
350  };
351 
352  Solver::Solver(const std::vector<Constraint>& constraints) : m_constraints(constraints)
353  {
354  }
355 
357  {
358  this->m_constraints.emplace_back(std::move(constraint));
359  return *this;
360  }
361 
362  Solver& Solver::with_constraints(const std::vector<Constraint>& constraints)
363  {
364  for (auto&& constraint : constraints)
365  {
366  this->m_constraints.emplace_back(std::move(constraint));
367  }
368  return *this;
369  }
370 
371  const std::vector<Constraint>& Solver::get_constraints() const
372  {
373  return m_constraints;
374  }
375 
377  {
378  if (call == SolverCall::Binary)
379  {
380  switch (auto it = type2query_binary.find(type); it != type2query_binary.end())
381  {
382  case true:
383  return it->second().is_ok();
384  default:
385  return false;
386  }
387  }
388  else if (call == SolverCall::Binary)
389  {
390  switch (auto it = type2link_status.find(type); it != type2link_status.end())
391  {
392  case true:
393  return it->second;
394  default:
395  return false;
396  }
397  }
398 
399  return false;
400  }
401 
403  {
404  if (config.local)
405  {
406  if (auto res = this->query_local(config); res.is_error())
407  {
408  return ERR_APPEND(res.get_error(), "unable to query SMT solver: local query failed");
409  }
410  else
411  {
412  return res;
413  }
414  }
415  else
416  {
417  if (auto res = this->query_remote(config); res.is_error())
418  {
419  return ERR_APPEND(res.get_error(), "unable to query SMT solver: remote query failed");
420  }
421  else
422  {
423  return res;
424  }
425  }
426  }
427 
429  {
430  auto input = Solver::translate_to_smt2(this->m_constraints, config);
431  if (input.is_error())
432  {
433  return ERR_APPEND(input.get_error(), "could not query local SMT solver: unable to translate SMT constraints and configuration to string");
434  }
435 
436  auto input_str = input.get();
437  return query_local_with_smt2(config, input_str);
438  }
439 
440  Result<SolverResult> Solver::query_local_with_smt2(const QueryConfig& config, const std::string& smt2)
441  {
442  auto query = spec2query.at({config.solver, config.call})(smt2, config);
443  if (query.is_ok())
444  {
445  auto [was_killed, output] = query.get();
446  return Solver::translate_from_smt2(was_killed, output, config);
447  }
448  return ERR_APPEND(query.get_error(), "could not query local SMT solver: unable to parse SMT result from string");
449  }
450 
452  {
453  // unimplemented as this is feature not required at the moment
454  return ERR("could not query remote SMT solver: currently not supported");
455  }
456 
458  {
459  return translate_to_smt2(this->m_constraints, config);
460  }
461 
462  Result<std::string> Solver::translate_to_smt2(const std::vector<Constraint>& constraints, const QueryConfig& config)
463  {
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)
471  {
472  if (constraint.is_assignment())
473  {
474  for (const auto& node : constraint.get_assignment().get()->first.get_nodes())
475  {
476  if (node.is_variable())
477  {
478  inputs.insert(std::make_tuple(node.variable, node.size));
479  }
480  }
481  }
482  else
483  {
484  for (const auto& node : constraint.get_function().get()->get_nodes())
485  {
486  if (node.is_variable())
487  {
488  inputs.insert(std::make_tuple(node.variable, node.size));
489  }
490  }
491  }
492  }
493 
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";
496  });
497  };
498 
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> {
505  // (1) short-hand termination in case accumulator is an error
506  if (accumulator.is_error())
507  {
508  return ERR(accumulator.get_error());
509  }
510 
511  if (constraint.is_assignment())
512  {
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);
516  if (lhs.is_error())
517  {
518  return ERR_APPEND(lhs.get_error(), "could not translate constraint to SMT-LIB v2: '" + constraint.to_string() + "'");
519  }
520  else if (rhs.is_error())
521  {
522  return ERR_APPEND(rhs.get_error(), "could not translate constraint to SMT-LIB v2: '" + constraint.to_string() + "'");
523  }
524  else
525  {
526  return OK(accumulator.get() + "(assert (= " + lhs.get() + " " + rhs.get() + "))\n");
527  }
528  }
529  else
530  {
531  auto lhs = Translator::translate_to_smt2(*constraint.get_function().get());
532  if (lhs.is_ok())
533  {
534  return OK(accumulator.get() + "(assert (= #b1 " + lhs.get() + "))\n");
535  }
536  return ERR_APPEND(lhs.get_error(), "could not translate constraint to SMT-LIB v2: '" + constraint.to_string() + "'");
537  }
538  });
539  };
540 
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)" : "");
545 
546  if (constraints_str.is_ok())
547  {
548  return OK(theory + "\n" + declarations + "\n" + constraints_str.get() + "\n" + epilogue);
549  }
550  return ERR_APPEND(constraints_str.get_error(), "could not translate constraint to SMT-LIB v2: unable to generate translation constraints");
551  }
552 
553  Result<SolverResult> Solver::translate_from_smt2(bool was_killed, std::string stdout, const QueryConfig& config)
554  {
555  if (was_killed)
556  {
557  return ERR("could not parse SMT result from string: the SMT solver was killed");
558  }
559 
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));
562 
563  auto to_lowercase = [](const auto& s) -> std::string {
564  auto lowercase = s;
565  std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);
566  return lowercase;
567  };
568 
569  if (to_lowercase(result) == "sat")
570  {
571  if (config.generate_model)
572  {
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())
574  {
575  return ERR_APPEND(res.get_error(), "could not parse SMT result from string: unable to generate model");
576  }
577  else
578  {
579  return res;
580  }
581  }
582 
583  return OK(SolverResult::Sat());
584  }
585  if (to_lowercase(result) == "unsat")
586  {
587  return OK(SolverResult::UnSat());
588  }
589 
590  if ((to_lowercase(result) == "unknown") || result.rfind("[btor>main] ALARM TRIGGERED: time limit", 0))
591  {
592  return OK(SolverResult::Unknown());
593  }
594 
595  return ERR("could not parse SMT result from string: invalid result");
596  }
597  } // namespace SMT
598 } // namespace hal
static Result< T > Ok(const T &value)
Definition: result.h:95
Result< SolverResult > query_local(const QueryConfig &config) const
Definition: solver.cpp:428
Result< std::string > to_smt2(const QueryConfig &config) const
Definition: solver.cpp:457
static bool has_local_solver_for(SolverType type, SolverCall call)
Definition: solver.cpp:376
Solver & with_constraints(const std::vector< Constraint > &constraints)
Definition: solver.cpp:362
const std::vector< Constraint > & get_constraints() const
Definition: solver.cpp:371
static Result< SolverResult > query_local_with_smt2(const QueryConfig &config, const std::string &smt2)
Definition: solver.cpp:440
Solver(const std::vector< Constraint > &constraints={})
Definition: solver.cpp:352
Result< SolverResult > query_remote(const QueryConfig &config) const
Definition: solver.cpp:451
Result< SolverResult > query(const QueryConfig &config=QueryConfig()) const
Definition: solver.cpp:402
Solver & with_constraint(const Constraint &constraint)
Definition: solver.cpp:356
#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:289
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