CBMC
prop_conv_solver.cpp
Go to the documentation of this file.
1 /*******************************************************************\
2 
3 Module:
4 
5 Author: Daniel Kroening, kroening@kroening.com
6 
7 \*******************************************************************/
8 
9 #include "prop_conv_solver.h"
10 
11 #include "literal_expr.h"
12 
13 #include <algorithm>
14 #include <chrono> // IWYU pragma: keep
15 
17 {
19 }
20 
22 {
23  for(const auto &bit : bv)
24  if(!bit.is_constant())
25  set_frozen(bit);
26 }
27 
29 {
30  prop.set_frozen(a);
31 }
32 
34 {
35  freeze_all = true;
36 }
37 
39 {
40  // We can only improve Booleans.
41  if(!expr.is_boolean())
42  return expr;
43 
44  // We convert to a literal to obtain a 'small' handle
45  literalt l = convert(expr);
46 
47  // The literal may be a constant as a result of non-trivial
48  // propagation. We return constants as such.
49  if(l.is_true())
50  return true_exprt();
51  else if(l.is_false())
52  return false_exprt();
53 
54  // freeze to enable incremental use
55  set_frozen(l);
56 
57  return literal_exprt(l);
58 }
59 
61 {
62  auto result =
63  symbols.insert(std::pair<irep_idt, literalt>(identifier, literalt()));
64 
65  if(!result.second)
66  return result.first->second;
67 
68  literalt literal = prop.new_variable();
69  prop.set_variable_name(literal, identifier);
70 
71  // insert
72  result.first->second = literal;
73 
74  return literal;
75 }
76 
77 std::optional<bool> prop_conv_solvert::get_bool(const exprt &expr) const
78 {
79  // trivial cases
80 
81  if(expr.is_true())
82  {
83  return true;
84  }
85  else if(expr.is_false())
86  {
87  return false;
88  }
89  else if(expr.id() == ID_symbol)
90  {
91  symbolst::const_iterator result =
92  symbols.find(to_symbol_expr(expr).get_identifier());
93 
94  // This may fail if the symbol isn't Boolean or
95  // not in the formula.
96  if(result == symbols.end())
97  return {};
98 
99  return prop.l_get(result->second).is_true();
100  }
101  else if(expr.id() == ID_literal)
102  {
103  return prop.l_get(to_literal_expr(expr).get_literal()).is_true();
104  }
105 
106  // sub-expressions
107 
108  if(expr.id() == ID_not)
109  {
110  if(expr.is_boolean())
111  {
112  auto tmp = get_bool(to_not_expr(expr).op());
113  if(tmp.has_value())
114  return !*tmp;
115  else
116  return {};
117  }
118  }
119  else if(expr.id() == ID_and || expr.id() == ID_or)
120  {
121  if(expr.is_boolean() && expr.operands().size() >= 1)
122  {
123  for(const auto &op : expr.operands())
124  {
125  auto tmp = get_bool(op);
126  if(!tmp.has_value())
127  return {};
128 
129  if(expr.id() == ID_and)
130  {
131  if(*tmp == false)
132  return false;
133  }
134  else // or
135  {
136  if(*tmp == true)
137  return true;
138  }
139  }
140 
141  return expr.id() == ID_and;
142  }
143  }
144 
145  // check cache
146 
147  cachet::const_iterator cache_result = cache.find(expr);
148  if(cache_result == cache.end())
149  return {}; // not in formula
150  else
151  return prop.l_get(cache_result->second).is_true();
152 }
153 
155 {
156  if(!use_cache || expr.id() == ID_symbol || expr.is_constant())
157  {
158  literalt literal = convert_bool(expr);
159  if(freeze_all && !literal.is_constant())
160  prop.set_frozen(literal);
161  return literal;
162  }
163 
164  // check cache first
165  auto result = cache.insert({expr, literalt()});
166 
167  // get a reference to the cache entry
168  auto &cache_entry = result.first->second;
169 
170  if(!result.second) // found in cache
171  return cache_entry;
172 
173  // The following may invalidate the iterator result.first,
174  // but note that the _reference_ is guaranteed to remain valid.
175  literalt literal = convert_bool(expr);
176 
177  // store the literal in the cache using the reference
178  cache_entry = literal;
179 
180  if(freeze_all && !literal.is_constant())
181  prop.set_frozen(literal);
182 
183 #if 0
184  std::cout << literal << "=" << expr << '\n';
185 #endif
186 
187  return literal;
188 }
189 
191 {
192  PRECONDITION(expr.is_boolean());
193 
194  const exprt::operandst &op = expr.operands();
195 
196  if(expr.is_constant())
197  {
198  if(expr.is_true())
199  return const_literal(true);
200  else
201  {
202  INVARIANT(
203  expr.is_false(),
204  "constant expression of type bool should be either true or false");
205  return const_literal(false);
206  }
207  }
208  else if(expr.id() == ID_symbol)
209  {
210  return get_literal(to_symbol_expr(expr).get_identifier());
211  }
212  else if(expr.id() == ID_literal)
213  {
214  return to_literal_expr(expr).get_literal();
215  }
216  else if(expr.id() == ID_nondet_symbol)
217  {
218  return prop.new_variable();
219  }
220  else if(expr.id() == ID_implies)
221  {
222  const implies_exprt &implies_expr = to_implies_expr(expr);
223  return prop.limplies(
224  convert(implies_expr.op0()), convert(implies_expr.op1()));
225  }
226  else if(expr.id() == ID_if)
227  {
228  const if_exprt &if_expr = to_if_expr(expr);
229  return prop.lselect(
230  convert(if_expr.cond()),
231  convert(if_expr.true_case()),
232  convert(if_expr.false_case()));
233  }
234  else if(expr.id() == ID_constraint_select_one)
235  {
237  op.size() >= 2,
238  "constraint_select_one should have at least two operands");
239 
240  std::vector<literalt> op_bv;
241  op_bv.reserve(op.size());
242 
243  for(const auto &op : expr.operands())
244  op_bv.push_back(convert(op));
245 
246  // add constraints
247 
248  bvt b;
249  b.reserve(op_bv.size() - 1);
250 
251  for(unsigned i = 1; i < op_bv.size(); i++)
252  b.push_back(prop.lequal(op_bv[0], op_bv[i]));
253 
255 
256  return op_bv[0];
257  }
258  else if(
259  expr.id() == ID_or || expr.id() == ID_and || expr.id() == ID_xor ||
260  expr.id() == ID_nor || expr.id() == ID_nand)
261  {
262  INVARIANT(
263  !op.empty(),
264  "operator '" + expr.id_string() + "' takes at least one operand");
265 
266  bvt bv;
267 
268  for(const auto &operand : op)
269  bv.push_back(convert(operand));
270 
271  if(!bv.empty())
272  {
273  if(expr.id() == ID_or)
274  return prop.lor(bv);
275  else if(expr.id() == ID_nor)
276  return !prop.lor(bv);
277  else if(expr.id() == ID_and)
278  return prop.land(bv);
279  else if(expr.id() == ID_nand)
280  return !prop.land(bv);
281  else if(expr.id() == ID_xor)
282  return prop.lxor(bv);
283  }
284  }
285  else if(expr.id() == ID_not)
286  {
287  INVARIANT(op.size() == 1, "not takes one operand");
288  return !convert(op.front());
289  }
290  else if(expr.id() == ID_equal || expr.id() == ID_notequal)
291  {
292  INVARIANT(op.size() == 2, "equality takes two operands");
293  bool equal = (expr.id() == ID_equal);
294 
295  if(op[0].is_boolean() && op[1].is_boolean())
296  {
297  literalt tmp1 = convert(op[0]), tmp2 = convert(op[1]);
298  return equal ? prop.lequal(tmp1, tmp2) : prop.lxor(tmp1, tmp2);
299  }
300  }
301  else if(expr.id() == ID_named_term)
302  {
303  const auto &named_term_expr = to_named_term_expr(expr);
304  literalt value_converted = convert(named_term_expr.value());
305  set_to_true(
306  equal_exprt(named_term_expr.symbol(), literal_exprt(value_converted)));
307  return value_converted;
308  }
309 
310  return convert_rest(expr);
311 }
312 
314 {
315  // fall through
316  ignoring(expr);
317  return prop.new_variable();
318 }
319 
321 {
323  return true;
324 
325  // optimization for constraint of the form
326  // new_variable = value
327 
328  if(expr.lhs().id() == ID_symbol)
329  {
330  const irep_idt &identifier = to_symbol_expr(expr.lhs()).get_identifier();
331 
332  literalt tmp = convert(expr.rhs());
333 
334  std::pair<symbolst::iterator, bool> result =
335  symbols.insert(std::pair<irep_idt, literalt>(identifier, tmp));
336 
337  if(result.second)
338  return false; // ok, inserted!
339 
340  // nah, already there
341  }
342 
343  return true;
344 }
345 
347 {
348  PRECONDITION(expr.is_boolean());
349 
350  const bool has_only_boolean_operands = std::all_of(
351  expr.operands().begin(), expr.operands().end(), [](const exprt &expr) {
352  return expr.is_boolean();
353  });
354 
355  if(has_only_boolean_operands)
356  {
357  if(expr.id() == ID_not)
358  {
359  add_constraints_to_prop(to_not_expr(expr).op(), !value);
360  return;
361  }
362  else
363  {
364  if(value)
365  {
366  // set_to_true
367 
368  if(expr.id() == ID_and)
369  {
370  for(const auto &op : expr.operands())
371  add_constraints_to_prop(op, true);
372 
373  return;
374  }
375  else if(expr.id() == ID_or)
376  {
377  // Special case for a CNF-clause,
378  // i.e., a constraint that's a disjunction.
379 
380  if(!expr.operands().empty())
381  {
382  bvt bv;
383  bv.reserve(expr.operands().size());
384 
385  for(const auto &op : expr.operands())
386  bv.push_back(convert(op));
387 
388  prop.lcnf(bv);
389  return;
390  }
391  }
392  else if(expr.id() == ID_implies)
393  {
394  const auto &implies_expr = to_implies_expr(expr);
395  literalt l_lhs = convert(implies_expr.lhs());
396  literalt l_rhs = convert(implies_expr.rhs());
397  prop.lcnf(!l_lhs, l_rhs);
398  return;
399  }
400  else if(expr.id() == ID_equal)
401  {
403  return;
404  }
405  }
406  else
407  {
408  // set_to_false
409  if(expr.id() == ID_implies) // !(a=>b) == (a && !b)
410  {
411  const implies_exprt &implies_expr = to_implies_expr(expr);
412 
413  add_constraints_to_prop(implies_expr.op0(), true);
414  add_constraints_to_prop(implies_expr.op1(), false);
415  return;
416  }
417  else if(expr.id() == ID_or) // !(a || b) == (!a && !b)
418  {
419  for(const auto &op : expr.operands())
420  add_constraints_to_prop(op, false);
421  return;
422  }
423  }
424  }
425  }
426 
427  // fall back to convert
428  prop.l_set_to(convert(expr), value);
429 }
430 
432 {
433  // fall through
434 
435  log.warning() << "warning: ignoring " << expr.pretty() << messaget::eom;
436 }
437 
439 {
440 }
441 
444 {
445  // post-processing isn't incremental yet
447  {
448  const auto post_process_start = std::chrono::steady_clock::now();
449 
450  log.statistics() << "Post-processing" << messaget::eom;
452  post_processing_done = true;
453 
454  const auto post_process_stop = std::chrono::steady_clock::now();
455  std::chrono::duration<double> post_process_runtime =
456  std::chrono::duration<double>(post_process_stop - post_process_start);
457  log.status() << "Runtime Post-process: " << post_process_runtime.count()
458  << "s" << messaget::eom;
459  }
460 
461  log.statistics() << "Solving with " << prop.solver_text() << messaget::eom;
462 
463  if(assumption.is_nil())
464  push();
465  else
466  push({assumption});
467 
468  auto prop_result = prop.prop_solve(assumption_stack);
469 
470  pop();
471 
472  switch(prop_result)
473  {
475  return resultt::D_SATISFIABLE;
479  return resultt::D_ERROR;
480  }
481 
482  UNREACHABLE;
483 }
484 
486 {
487  if(expr.is_boolean())
488  {
489  auto value = get_bool(expr);
490 
491  if(value.has_value())
492  {
493  if(*value)
494  return true_exprt();
495  else
496  return false_exprt();
497  }
498  }
499 
500  exprt tmp = expr;
501  for(auto &op : tmp.operands())
502  {
503  exprt tmp_op = get(op);
504  op.swap(tmp_op);
505  }
506  return tmp;
507 }
508 
509 void prop_conv_solvert::print_assignment(std::ostream &out) const
510 {
511  for(const auto &symbol : symbols)
512  out << symbol.first << " = " << prop.l_get(symbol.second) << '\n';
513 }
514 
516 {
518 }
519 
520 const char *prop_conv_solvert::context_prefix = "prop_conv::context$";
521 
522 void prop_conv_solvert::set_to(const exprt &expr, bool value)
523 {
524  if(assumption_stack.empty())
525  {
526  // We are in the root context.
527  add_constraints_to_prop(expr, value);
528  }
529  else
530  {
531  // We have a child context. We add context_literal ==> expr to the formula.
533  or_exprt(literal_exprt(!assumption_stack.back()), expr), value);
534  }
535 }
536 
537 void prop_conv_solvert::push(const std::vector<exprt> &assumptions)
538 {
539  // We push the given assumptions as a single context onto the stack.
540  assumption_stack.reserve(assumption_stack.size() + assumptions.size());
541  for(const auto &assumption : assumptions)
542  {
543  auto literal = convert(assumption);
544  if(!literal.is_constant())
545  set_frozen(literal);
546  assumption_stack.push_back(literal);
547  }
548  context_size_stack.push_back(assumptions.size());
549 }
550 
552 {
553  // We create a new context literal.
554  literalt context_literal = convert(symbol_exprt(
556 
557  assumption_stack.push_back(context_literal);
558  context_size_stack.push_back(1);
559 }
560 
562 {
563  // We remove the context from the stack.
564  assumption_stack.resize(assumption_stack.size() - context_size_stack.back());
565  context_size_stack.pop_back();
566 }
567 
568 // This method out-of-line because gcc-5.5.0-12ubuntu1 20171010 miscompiles it
569 // when inline (in certain build configurations, notably -O2 -g0) by producing
570 // a non-virtual thunk with c++03 ABI but a function body with c++11 ABI, the
571 // mismatch leading to a missing vtable entry and consequent null-pointer deref
572 // whenever this function is called.
574 {
575  return "propositional reduction";
576 }
exprt & op1()
Definition: expr.h:136
exprt & lhs()
Definition: std_expr.h:668
exprt & op0()
Definition: expr.h:133
exprt & rhs()
Definition: std_expr.h:678
The Boolean type.
Definition: std_types.h:36
resultt
Result of running the decision procedure.
void set_to_true(const exprt &)
For a Boolean expression expr, add the constraint 'expr'.
dstringt has one field, an unsigned integer no which is an index into a static table of strings.
Definition: dstring.h:38
Equality.
Definition: std_expr.h:1361
Base class for all expressions.
Definition: expr.h:56
std::vector< exprt > operandst
Definition: expr.h:58
bool is_true() const
Return whether the expression is a constant representing true.
Definition: expr.cpp:27
bool is_boolean() const
Return whether the expression represents a Boolean.
Definition: expr.h:224
bool is_false() const
Return whether the expression is a constant representing false.
Definition: expr.cpp:34
bool is_constant() const
Return whether the expression is a constant.
Definition: expr.h:212
operandst & operands()
Definition: expr.h:94
The Boolean constant false.
Definition: std_expr.h:3064
The trinary if-then-else operator.
Definition: std_expr.h:2370
exprt & true_case()
Definition: std_expr.h:2397
exprt & false_case()
Definition: std_expr.h:2407
exprt & cond()
Definition: std_expr.h:2387
Boolean implication.
Definition: std_expr.h:2183
std::string pretty(unsigned indent=0, unsigned max_indent=0) const
Definition: irep.cpp:482
const std::string & id_string() const
Definition: irep.h:387
const irep_idt & id() const
Definition: irep.h:384
bool is_nil() const
Definition: irep.h:364
literalt get_literal() const
Definition: literal_expr.h:26
bool is_true() const
Definition: literal.h:156
bool is_constant() const
Definition: literal.h:166
bool is_false() const
Definition: literal.h:161
mstreamt & statistics() const
Definition: message.h:419
mstreamt & warning() const
Definition: message.h:404
mstreamt & status() const
Definition: message.h:414
static eomt eom
Definition: message.h:297
Boolean OR.
Definition: std_expr.h:2228
void pop() override
Pop whatever is on top of the stack.
virtual bool set_equality_to_true(const equal_exprt &expr)
virtual literalt convert_bool(const exprt &expr)
decision_proceduret::resultt dec_solve(const exprt &) override
Implementation of the decision procedure.
void set_frozen(literalt)
std::vector< size_t > context_size_stack
assumption_stack is segmented in contexts; Number of assumptions in each context on the stack
std::size_t context_literal_counter
To generate unique literal names for contexts.
std::size_t get_number_of_solver_calls() const override
Return the number of incremental solver calls.
virtual void finish_eager_conversion()
virtual literalt get_literal(const irep_idt &symbol)
std::string decision_procedure_text() const override
Return a textual description of the decision procedure.
virtual void ignoring(const exprt &expr)
void add_constraints_to_prop(const exprt &expr, bool value)
Helper method used by set_to for adding the constraints to prop.
void push() override
Push a new context on the stack This context becomes a child context nested in the current context.
virtual std::optional< bool > get_bool(const exprt &expr) const
Get a boolean value from the model if the formula is satisfiable.
literalt convert(const exprt &expr) override
Convert a Boolean expression and return the corresponding literal.
exprt get(const exprt &expr) const override
Return expr with variables replaced by values from satisfying assignment if available.
bvt assumption_stack
Assumptions on the stack.
virtual literalt convert_rest(const exprt &expr)
void set_to(const exprt &expr, bool value) override
For a Boolean expression expr, add the constraint 'current_context => expr' if value is true,...
exprt handle(const exprt &expr) override
Generate a handle, which is an expression that has the same value as the argument in any model that i...
static const char * context_prefix
bool is_in_conflict(const exprt &expr) const override
Returns true if an expression is in the final conflict leading to UNSAT.
void print_assignment(std::ostream &out) const override
Print satisfying assignment to out.
void l_set_to_true(literalt a)
Definition: prop.h:52
virtual literalt land(literalt a, literalt b)=0
std::size_t get_number_of_solver_calls() const
Definition: prop.cpp:52
virtual void set_variable_name(literalt, const irep_idt &)
Definition: prop.h:95
virtual literalt limplies(literalt a, literalt b)=0
virtual literalt lselect(literalt a, literalt b, literalt c)=0
virtual void set_frozen(literalt)
Definition: prop.h:117
virtual void l_set_to(literalt a, bool value)
Definition: prop.h:47
virtual literalt lxor(literalt a, literalt b)=0
virtual std::string solver_text() const =0
void lcnf(literalt l0, literalt l1)
Definition: prop.h:58
resultt prop_solve()
Definition: prop.cpp:39
virtual literalt new_variable()=0
virtual literalt lequal(literalt a, literalt b)=0
virtual literalt lor(literalt a, literalt b)=0
virtual bool is_in_conflict(literalt l) const =0
Returns true if an assumption is in the final conflict.
virtual tvt l_get(literalt a) const =0
Expression to hold a symbol (variable)
Definition: std_expr.h:131
const irep_idt & get_identifier() const
Definition: std_expr.h:160
The Boolean constant true.
Definition: std_expr.h:3055
bool is_true() const
Definition: threeval.h:25
std::vector< literalt > bvt
Definition: literal.h:201
literalt const_literal(bool value)
Definition: literal.h:188
const literal_exprt & to_literal_expr(const exprt &expr)
Cast a generic exprt to a literal_exprt.
Definition: literal_expr.h:56
#define UNREACHABLE
This should be used to mark dead code.
Definition: invariant.h:525
#define DATA_INVARIANT(CONDITION, REASON)
This condition should be used to document that assumptions that are made on goto_functions,...
Definition: invariant.h:534
#define PRECONDITION(CONDITION)
Definition: invariant.h:463
const if_exprt & to_if_expr(const exprt &expr)
Cast an exprt to an if_exprt.
Definition: std_expr.h:2450
const symbol_exprt & to_symbol_expr(const exprt &expr)
Cast an exprt to a symbol_exprt.
Definition: std_expr.h:272
const not_exprt & to_not_expr(const exprt &expr)
Cast an exprt to an not_exprt.
Definition: std_expr.h:2352
const named_term_exprt & to_named_term_expr(const exprt &expr)
Cast an exprt to a named_term_exprt.
Definition: std_expr.h:3654
const equal_exprt & to_equal_expr(const exprt &expr)
Cast an exprt to an equal_exprt.
Definition: std_expr.h:1402
const implies_exprt & to_implies_expr(const exprt &expr)
Cast an exprt to a implies_exprt.
Definition: std_expr.h:2208
std::string to_string(const string_not_contains_constraintt &expr)
Used for debug printing.