HAL  v4.5.0-83-g30c8f0afc
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
z3_utils.cpp
Go to the documentation of this file.
1 #include "z3_utils/z3_utils.h"
2 
5 #include "z3_api.h"
8 
9 #include <sstream>
10 
11 namespace hal
12 {
13  namespace z3_utils
14  {
15  z3::expr from_bf(const BooleanFunction& bf, z3::context& ctx, const std::map<std::string, z3::expr>& var2expr)
16  {
17  // Helper function to reduce a abstract syntax subtree to z3 expressions
18  //
19  // node - Boolean function node.
20  // p - Boolean function node parameters.
21  // returns (1) status (true on success, false otherwise),
22  // (2) SMT-LIB string representation of node and operands.
23  auto reduce_to_z3 = [&ctx, &var2expr](const auto& node, auto&& p) -> std::tuple<bool, z3::expr> {
24  if (node.get_arity() != p.size())
25  {
26  return {false, z3::expr(ctx)};
27  }
28 
29  switch (node.type)
30  {
32  return {true, ctx.bv_val(node.index, node.size)};
34  std::vector<u8> bits;
35  for (u32 i = 0; i < node.constant.size(); i++)
36  {
37  if (node.constant.at(i) == BooleanFunction::Value::ONE)
38  {
39  bits.push_back(1);
40  }
41  else if (node.constant.at(i) == BooleanFunction::Value::ZERO)
42  {
43  bits.push_back(0);
44  }
45  else
46  {
47  return {false, z3::expr(ctx)};
48  }
49  }
50 
51  return {true, ctx.bv_val(bits.size(), reinterpret_cast<bool*>(bits.data()))};
52  }
54  if (auto it = var2expr.find(node.variable); it != var2expr.end())
55  {
56  return {true, it->second};
57  }
58  return {true, ctx.bv_const(node.variable.c_str(), node.size)};
59  }
60 
62  return {true, p[0] & p[1]};
64  return {true, p[0] | p[1]};
66  return {true, ~p[0]};
68  return {true, p[0] ^ p[1]};
70  return {true, p[0] + p[1]};
72  return {true, p[0] - p[1]};
74  return {true, p[0] * p[1]};
76  return {true, p[0] / p[1]};
78  return {true, z3::udiv(p[0], p[1])};
80  return {true, z3::srem(p[0], p[1])};
82  return {true, z3::urem(p[0], p[1])};
84  return {true, z3::concat(p[0], p[1])};
86  return {true, p[0].extract(p[2].get_numeral_uint(), p[1].get_numeral_uint())};
88  return {true, z3::zext(p[0], p[1].get_numeral_uint() - p[0].get_sort().bv_size())};
90  return {true, z3::sext(p[0], p[1].get_numeral_uint() - p[0].get_sort().bv_size())};
92  return {true, z3::shl(p[0], p[1])};
94  return {true, z3::lshr(p[0], p[1])};
96  return {true, z3::ashr(p[0], p[1])};
98  return {true, p[0].rotate_left(p[1].get_numeral_uint())};
100  return {true, p[0].rotate_right(p[1].get_numeral_uint())};
102  return {true, p[0] == p[1]};
104  return {true, z3::sle(p[0], p[1])};
106  return {true, z3::slt(p[0], p[1])};
108  return {true, z3::ule(p[0], p[1])};
110  return {true, z3::ult(p[0], p[1])};
112  return {true, z3::ite(p[0], p[1], p[2])};
113  default:
114  log_error("netlist", "Not implemented reached for nodetype {} in z3 conversion", node.type);
115  return {false, z3::expr(ctx)};
116  }
117  };
118 
119  std::vector<z3::expr> stack;
120  for (const auto& node : bf.get_nodes())
121  {
122  std::vector<z3::expr> operands;
123  std::move(stack.end() - static_cast<i64>(node.get_arity()), stack.end(), std::back_inserter(operands));
124  stack.erase(stack.end() - static_cast<i64>(node.get_arity()), stack.end());
125 
126  if (auto [ok, reduction] = reduce_to_z3(node, std::move(operands)); ok)
127  {
128  stack.emplace_back(reduction);
129  }
130  else
131  {
132  return z3::expr(ctx);
133  }
134  }
135 
136  switch (stack.size())
137  {
138  case 1:
139  return stack.back();
140  default:
141  return z3::expr(ctx);
142  }
143  }
144 
145  Result<z3::expr> value_from_binary_string(z3::context& ctx, const std::string& bit_string)
146  {
147  std::vector<u8> bits;
148  for (u32 i = 0; i < bit_string.length(); i++)
149  {
150  if (bit_string.at(i) == '1')
151  {
152  bits.push_back(1);
153  }
154  else if (bit_string.at(i) == '0')
155  {
156  bits.push_back(0);
157  }
158  else
159  {
160  return ERR("cannot generate value from binary string: encountered unexpected character " + bit_string.at(i));
161  }
162  }
163 
164  return OK(ctx.bv_val(bits.size(), reinterpret_cast<bool*>(bits.data())));
165  }
166 
167  namespace
168  {
169  Result<BooleanFunction> to_bf_internal(const z3::expr& e, std::map<z3::expr, BooleanFunction>& cache)
170  {
171  u64 size;
172  if (e.is_bv())
173  {
174  size = e.get_sort().bv_size();
175 
176  if (e.is_numeral())
177  {
178  if (size <= 64)
179  {
180  return OK(BooleanFunction::Const(e.get_numeral_uint64(), size));
181  }
182  else
183  {
184  std::vector<BooleanFunction::Value> boolean_values;
185  const std::string val_str = Z3_get_numeral_binary_string(e.ctx(), e);
186  for (u32 idx = 0; idx < val_str.size(); idx++)
187  {
188  if (val_str.at(idx) == '1')
189  {
190  boolean_values.push_back(BooleanFunction::Value::ONE);
191  }
192  else if (val_str.at(idx) == '0')
193  {
194  boolean_values.push_back(BooleanFunction::Value::ZERO);
195  }
196  else
197  {
198  return ERR("cannot convert expression to boolean function: failed to translate character " + std::to_string(val_str.at(idx)) + " to Boolean value.");
199  }
200  }
201 
202  return OK(BooleanFunction::Const(boolean_values));
203  }
204  }
205  else if (e.is_const())
206  {
207  // std::cout << e << std::endl;
208  const std::string name = e.decl().name().str();
209  return OK(BooleanFunction::Var(name, size));
210  }
211  else if (e.is_var())
212  {
213  const std::string name = e.decl().name().str();
214  return OK(BooleanFunction::Var(name, size));
215  }
216  }
217 
218  // if (const auto it = cache.find(e); it != cache.end())
219  // {
220  // return OK(it->second);
221  // }
222 
223  const auto op = e.decl().decl_kind();
224  auto num_args = e.num_args();
225  std::vector<BooleanFunction> args;
226 
227  for (u32 i = 0; i < e.num_args(); i++)
228  {
229  const auto arg = e.arg(i);
230  if (const auto res = to_bf_internal(arg, cache); res.is_ok())
231  {
232  // const auto [it, _] = cache.insert({arg, res.get()});
233  // args.push_back(it->second.clone());
234  args.push_back(res.get());
235  }
236  else
237  {
238  return ERR(res.get_error());
239  }
240  }
241 
242  switch (op)
243  {
244  case Z3_OP_BAND: {
245  auto bf_res = BooleanFunction::And(std::move(args.at(0)), std::move(args.at(1)), size);
246  for (u64 i = 2; i < num_args; i++)
247  {
248  bf_res =
249  bf_res.map<BooleanFunction>([arg = std::move(args.at(i)), size](BooleanFunction&& bf) mutable { return BooleanFunction::And(std::move(bf), std::move(arg), size); });
250  }
251  return bf_res;
252  }
253  case Z3_OP_BOR: {
254  auto bf_res = BooleanFunction::Or(std::move(args.at(0)), std::move(args.at(1)), size);
255  for (u64 i = 2; i < num_args; i++)
256  {
257  bf_res =
258  bf_res.map<BooleanFunction>([arg = std::move(args.at(i)), size](BooleanFunction&& bf) mutable { return BooleanFunction::Or(std::move(bf), std::move(arg), size); });
259  }
260  return bf_res;
261  }
262  case Z3_OP_BNOT: {
263  if (num_args != 1)
264  {
265  return ERR("operation 'NOT' must have arity 1");
266  }
267  return BooleanFunction::Not(std::move(args.at(0)), size);
268  }
269  case Z3_OP_BXOR: {
270  auto bf_res = BooleanFunction::Xor(std::move(args.at(0)), std::move(args.at(1)), size);
271  for (u64 i = 2; i < num_args; i++)
272  {
273  bf_res =
274  bf_res.map<BooleanFunction>([arg = std::move(args.at(i)), size](BooleanFunction&& bf) mutable { return BooleanFunction::Xor(std::move(bf), std::move(arg), size); });
275  }
276  return bf_res;
277  }
278  case Z3_OP_BNEG: {
279  if (num_args != 1)
280  {
281  return ERR("operation 'NEG' must have arity 1");
282  }
283  return ERR("Negation not implemented");
284  }
285  case Z3_OP_BADD: {
286  auto bf_res = BooleanFunction::Add(std::move(args.at(0)), std::move(args.at(1)), size);
287  for (u64 i = 2; i < num_args; i++)
288  {
289  bf_res =
290  bf_res.map<BooleanFunction>([arg = std::move(args.at(i)), size](BooleanFunction&& bf) mutable { return BooleanFunction::Add(std::move(bf), std::move(arg), size); });
291  }
292  return bf_res;
293  }
294  case Z3_OP_BSUB: {
295  auto bf_res = BooleanFunction::Sub(std::move(args.at(0)), std::move(args.at(1)), size);
296  for (u64 i = 2; i < num_args; i++)
297  {
298  bf_res =
299  bf_res.map<BooleanFunction>([arg = std::move(args.at(i)), size](BooleanFunction&& bf) mutable { return BooleanFunction::Sub(std::move(bf), std::move(arg), size); });
300  }
301  return bf_res;
302  }
303  case Z3_OP_BMUL: {
304  auto bf_res = BooleanFunction::Mul(std::move(args.at(0)), std::move(args.at(1)), size);
305  for (u64 i = 2; i < num_args; i++)
306  {
307  bf_res =
308  bf_res.map<BooleanFunction>([arg = std::move(args.at(i)), size](BooleanFunction&& bf) mutable { return BooleanFunction::Mul(std::move(bf), std::move(arg), size); });
309  }
310  return bf_res;
311  }
312  case Z3_OP_BSDIV:
313  if (num_args != 2)
314  {
315  return ERR("operation 'SDIV' must have arity 2");
316  }
317  return BooleanFunction::Sdiv(std::move(args.at(0)), std::move(args.at(1)), size);
318  case Z3_OP_BUDIV:
319  if (num_args != 2)
320  {
321  return ERR("operation 'UDIV' must have arity 2");
322  }
323  return BooleanFunction::Udiv(std::move(args.at(0)), std::move(args.at(1)), size);
324  case Z3_OP_BSREM:
325  if (num_args != 2)
326  {
327  return ERR("operation 'SREM' must have arity 2");
328  }
329  return BooleanFunction::Srem(std::move(args.at(0)), std::move(args.at(1)), size);
330  case Z3_OP_BUREM:
331  if (num_args != 2)
332  {
333  return ERR("operation 'UREM' must have arity 2");
334  }
335  return BooleanFunction::Urem(std::move(args.at(0)), std::move(args.at(1)), size);
336  case Z3_OP_CONCAT: {
337  auto bf_res = BooleanFunction::Concat(std::move(args.at(0)), std::move(args.at(1)), args.at(0).size() + args.at(1).size());
338  for (u64 i = 2; i < num_args; i++)
339  {
340  bf_res = bf_res.map<BooleanFunction>(
341  [arg = std::move(args.at(i)), size](BooleanFunction&& bf) mutable { return BooleanFunction::Concat(std::move(bf), std::move(arg), bf.size() + arg.size()); });
342  }
343  return bf_res;
344  }
345  case Z3_OP_EXTRACT: {
346  if (num_args != 1)
347  {
348  return ERR("operation 'SLICE' must have arity 1");
349  }
350 
351  const u32 operand_size = args.at(0).size();
352 
353  return BooleanFunction::Slice(std::move(args.at(0)), BooleanFunction::Index(e.lo(), operand_size), BooleanFunction::Index(e.hi(), operand_size), size);
354  }
355  case Z3_OP_ZERO_EXT: {
356  if (num_args != 1)
357  {
358  return ERR("operation 'ZEXT' must have arity 1");
359  }
360 
361  return BooleanFunction::Zext(std::move(args.at(0)), BooleanFunction::Index(size, size), size);
362  }
363  case Z3_OP_SIGN_EXT: {
364  if (num_args != 1)
365  {
366  return ERR("operation 'SEXT' must have arity 1");
367  }
368 
369  return BooleanFunction::Sext(std::move(args.at(0)), BooleanFunction::Index(size, size), size);
370  }
371  case Z3_OP_BSHL:
372  if (num_args != 2)
373  {
374  return ERR("operation 'SHL' must have arity 2");
375  }
376  return BooleanFunction::Shl(std::move(args.at(0)), BooleanFunction::Index((u16)args.at(1).get_constant_value_u64().get(), size), size);
377  case Z3_OP_BLSHR:
378  if (num_args != 2)
379  {
380  return ERR("operation 'LSHR' must have arity 2");
381  }
382  return BooleanFunction::Lshr(std::move(args.at(0)), BooleanFunction::Index((u16)args.at(1).get_constant_value_u64().get(), size), size);
383  case Z3_OP_BASHR:
384  if (num_args != 2)
385  {
386  return ERR("operation 'ASHR' must have arity 2");
387  }
388  return BooleanFunction::Ashr(std::move(args.at(0)), BooleanFunction::Index((u16)args.at(1).get_constant_value_u64().get(), size), size);
389  case Z3_OP_ROTATE_LEFT:
390  if (num_args != 1)
391  {
392  return ERR("operation 'ROL' must have arity 1");
393  }
394  return BooleanFunction::Rol(std::move(args.at(0)), BooleanFunction::Index((u16)Z3_get_decl_int_parameter(Z3_context(e.ctx()), Z3_func_decl(e.decl()), 0), size), size);
395  case Z3_OP_ROTATE_RIGHT:
396  if (num_args != 1)
397  {
398  return ERR("operation 'ROR' must have arity 1");
399  }
400  return BooleanFunction::Ror(std::move(args.at(0)), BooleanFunction::Index((u16)Z3_get_decl_int_parameter(Z3_context(e.ctx()), Z3_func_decl(e.decl()), 0), size), size);
401  case Z3_OP_EQ:
402  if (num_args != 2)
403  {
404  return ERR("operation 'EQ' must have arity 2");
405  }
406  return BooleanFunction::Eq(std::move(args.at(0)), std::move(args.at(1)), 1);
407  case Z3_OP_SLEQ:
408  if (num_args != 2)
409  {
410  return ERR("operation 'SLE' must have arity 2");
411  }
412  return BooleanFunction::Sle(std::move(args.at(0)), std::move(args.at(1)), 1);
413  case Z3_OP_SLT:
414  if (num_args != 2)
415  {
416  return ERR("operation 'SLT' must have arity 2");
417  }
418  return BooleanFunction::Slt(std::move(args.at(0)), std::move(args.at(1)), 1);
419  case Z3_OP_ULEQ:
420  if (num_args != 2)
421  {
422  return ERR("operation 'ULE' must have arity 2");
423  }
424  return BooleanFunction::Ule(std::move(args.at(0)), std::move(args.at(1)), 1);
425  case Z3_OP_ULT:
426  if (num_args != 2)
427  {
428  return ERR("operation 'ULT' must have arity 2");
429  }
430  return BooleanFunction::Ult(std::move(args.at(0)), std::move(args.at(1)), 1);
431  case Z3_OP_ITE:
432  if (num_args != 3)
433  {
434  return ERR("operation 'ITE' must have arity 3");
435  }
436  return BooleanFunction::Ite(std::move(args.at(0)), std::move(args.at(1)), std::move(args.at(2)), size);
437  default:
438  return ERR("operation '" + e.decl().name().str() + "' with arity " + std::to_string(num_args) + " is not yet implemented");
439  }
440  }
441  } // namespace
442 
443  Result<BooleanFunction> to_bf(const z3::expr& e)
444  {
445  std::map<z3::expr, BooleanFunction> cache;
446  return to_bf_internal(e, cache);
447  }
448 
449  std::string to_smt2(const z3::expr& e)
450  {
451  auto s = z3::solver(e.ctx());
452  if (e.get_sort().is_bv())
453  {
454  s.add(e == e.ctx().bv_val(0, e.get_sort().bv_size()));
455  }
456  else
457  {
458  s.add(e == e.ctx().bool_val(true));
459  }
460  return s.to_smt2();
461  }
462 
463  std::string to_cpp(const z3::expr& e)
464  {
465  const auto converter = Cpp_Converter();
466  const auto c_file = converter.convert_z3_expr_to_func(e);
467 
468  return c_file;
469  }
470 
471  std::string to_verilog(const z3::expr& e, const std::map<std::string, bool>& control_mapping)
472  {
473  auto converter = VerilogConverter();
474  converter.set_control_mapping(control_mapping);
475 
476  const auto verilog_file = converter.convert_z3_expr_to_func(e);
477 
478  return verilog_file;
479  }
480 
481  std::set<std::string> get_variable_names(const z3::expr& e)
482  {
483  std::set<u32> visited = {e.id()};
484  std::vector<z3::expr> stack = {e};
485 
486  std::set<std::string> var_names;
487 
488  while (!stack.empty())
489  {
490  const auto n = stack.back();
491  stack.pop_back();
492 
493  if (n.is_numeral())
494  {
495  continue;
496  }
497 
498  if (n.is_var() || n.is_const())
499  {
500  var_names.insert(n.to_string());
501  }
502  else
503  {
504  for (u32 i = 0; i < n.num_args(); i++)
505  {
506  const auto a_i = n.arg(i);
507  if (visited.find(a_i.id()) == visited.end())
508  {
509  visited.insert(a_i.id());
510  stack.push_back(a_i);
511  }
512  }
513  }
514  }
515 
516  return var_names;
517  }
518 
519  std::set<u32> extract_net_ids(const z3::expr& e)
520  {
522  }
523 
524  // TODO make this return a result
525  std::set<u32> extract_net_ids(const std::set<std::string>& variable_names)
526  {
527  std::set<u32> net_ids;
528 
529  for (const auto& var : variable_names)
530  {
531  const auto id_res = BooleanFunctionNetDecorator::get_net_id_from(var);
532  if (id_res.is_error())
533  {
534  log_error("z3_utils", "{}", id_res.get_error().get());
535  }
536  net_ids.insert(id_res.get());
537  }
538 
539  return net_ids;
540  }
541 
542  z3::expr get_expr_in_ctx(const z3::expr& e, z3::context& ctx)
543  {
544  auto expr_vec = ctx.parse_string(to_smt2(e).c_str());
545  return expr_vec.back().arg(0).simplify();
546  }
547  } // namespace z3_utils
548 } // namespace hal
u32 size
static Result< BooleanFunction > Slt(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Ite(BooleanFunction &&p0, BooleanFunction &&p1, BooleanFunction &&p2, u16 size)
static Result< BooleanFunction > Eq(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static BooleanFunction Var(const std::string &name, u16 size=1)
static Result< BooleanFunction > Xor(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Add(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Lshr(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Zext(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Mul(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Ule(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Sub(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Sext(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Udiv(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Concat(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Ult(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static BooleanFunction Index(u16 index, u16 size)
static Result< BooleanFunction > Sle(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Slice(BooleanFunction &&p0, BooleanFunction &&p1, BooleanFunction &&p2, u16 size)
const std::vector< BooleanFunction::Node > & get_nodes() const
static Result< BooleanFunction > Or(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Urem(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Ror(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Sdiv(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static BooleanFunction Const(const BooleanFunction::Value &value)
static Result< BooleanFunction > Ashr(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Not(BooleanFunction &&p0, u16 size)
static Result< BooleanFunction > And(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Shl(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Rol(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< BooleanFunction > Srem(BooleanFunction &&p0, BooleanFunction &&p1, u16 size)
static Result< u32 > get_net_id_from(const BooleanFunction &var)
uint64_t u64
Definition: defines.h:42
uint16_t u16
Definition: defines.h:40
uint32_t u32
Definition: defines.h:41
int64_t i64
Definition: defines.h:37
#define log_error(channel,...)
Definition: log.h:78
#define ERR(message)
Definition: result.h:60
#define OK(...)
Definition: result.h:56
std::set< std::string > get_variable_names(const z3::expr &e)
Extracts all variable names from a z3 expression.
Definition: z3_utils.cpp:481
std::set< u32 > extract_net_ids(const z3::expr &e)
Extracts all net IDs from the variables of a z3 expression.
Definition: z3_utils.cpp:519
std::string to_smt2(const z3::expr &e)
Definition: z3_utils.cpp:449
z3::expr from_bf(const BooleanFunction &bf, z3::context &ctx, const std::map< std::string, z3::expr > &var2expr={})
Definition: z3_utils.cpp:15
std::string to_verilog(const z3::expr &e, const std::map< std::string, bool > &control_mapping={})
Translates a z3 expression into a verilog network representation.
Definition: z3_utils.cpp:471
std::string to_cpp(const z3::expr &e)
Definition: z3_utils.cpp:463
z3::expr get_expr_in_ctx(const z3::expr &e, z3::context &ctx)
Translates the expr to another context.
Definition: z3_utils.cpp:542
Result< z3::expr > value_from_binary_string(z3::context &ctx, const std::string &bit_string)
Definition: z3_utils.cpp:145
Result< BooleanFunction > to_bf(const z3::expr &e)
Definition: z3_utils.cpp:443
Definition: defines.h:45
std::string name