CBMC
boolbv_quantifier.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 "boolbv.h"
10 
11 #include <util/arith_tools.h>
12 #include <util/expr_util.h>
13 #include <util/invariant.h>
14 #include <util/simplify_expr.h>
15 
17 static bool expr_eq(const exprt &expr1, const exprt &expr2)
18 {
19  return skip_typecast(expr1) == skip_typecast(expr2);
20 }
21 
25 static std::optional<constant_exprt>
26 get_quantifier_var_min(const exprt &var_expr, const exprt &quantifier_expr)
27 {
28  if(quantifier_expr.id()==ID_or)
29  {
34  for(auto &x : quantifier_expr.operands())
35  {
36  if(x.id()!=ID_not)
37  continue;
38  exprt y = to_not_expr(x).op();
39  if(y.id()!=ID_ge)
40  continue;
41  const auto &y_binary = to_binary_relation_expr(y);
42  if(expr_eq(var_expr, y_binary.lhs()) && y_binary.rhs().is_constant())
43  {
44  return to_constant_expr(y_binary.rhs());
45  }
46  }
47 
48  if(var_expr.type().id() == ID_unsignedbv)
49  return from_integer(0, var_expr.type());
50  }
51  else if(quantifier_expr.id() == ID_and)
52  {
53  // The minimum variable can be of the form `var_expr >= constant`, or
54  // it can be of the form `var_expr == constant` (e.g. in the case where
55  // the interval that bounds the variable is a singleton interval (set
56  // with only one element)).
57  for(auto &x : quantifier_expr.operands())
58  {
59  if(x.id() != ID_ge && x.id() != ID_equal)
60  continue;
61  const auto &x_binary = to_binary_relation_expr(x);
62  if(expr_eq(var_expr, x_binary.lhs()) && x_binary.rhs().is_constant())
63  {
64  return to_constant_expr(x_binary.rhs());
65  }
66  }
67 
68  if(var_expr.type().id() == ID_unsignedbv)
69  return from_integer(0, var_expr.type());
70  }
71 
72  return {};
73 }
74 
77 static std::optional<constant_exprt>
78 get_quantifier_var_max(const exprt &var_expr, const exprt &quantifier_expr)
79 {
80  if(quantifier_expr.id()==ID_or)
81  {
86  for(auto &x : quantifier_expr.operands())
87  {
88  if(x.id()!=ID_ge)
89  continue;
90  const auto &x_binary = to_binary_relation_expr(x);
91  if(expr_eq(var_expr, x_binary.lhs()) && x_binary.rhs().is_constant())
92  {
93  const constant_exprt &over_expr = to_constant_expr(x_binary.rhs());
94 
95  mp_integer over_i = numeric_cast_v<mp_integer>(over_expr);
96 
102  over_i-=1;
103  return from_integer(over_i, x_binary.rhs().type());
104  }
105  }
106  }
107  else
108  {
109  // There are two potential forms we could come across here. The first one
110  // is `!(var_expr >= constant)` - identified by the first if branch - and
111  // the second is `var_expr == constant` - identified by the second else-if
112  // branch. The second form could be met if previous simplification has
113  // identified a singleton interval - see simplify_boolean_expr.cpp.
114  for(auto &x : quantifier_expr.operands())
115  {
116  if(x.id() == ID_not)
117  {
118  exprt y = to_not_expr(x).op();
119  if(y.id() != ID_ge)
120  continue;
121  const auto &y_binary = to_binary_relation_expr(y);
122  if(expr_eq(var_expr, y_binary.lhs()) && y_binary.rhs().is_constant())
123  {
124  const constant_exprt &over_expr = to_constant_expr(y_binary.rhs());
125  mp_integer over_i = numeric_cast_v<mp_integer>(over_expr);
126  over_i -= 1;
127  return from_integer(over_i, y_binary.rhs().type());
128  }
129  }
130  else if(x.id() == ID_equal)
131  {
132  const auto &y_binary = to_binary_relation_expr(x);
133  if(expr_eq(var_expr, y_binary.lhs()) && y_binary.rhs().is_constant())
134  {
135  return to_constant_expr(y_binary.rhs());
136  }
137  }
138  else
139  {
140  // If you need special handling for a particular expression type (say,
141  // after changes to the simplifier) you need to make sure that you add
142  // an `else if` branch above, otherwise the expression will get skipped
143  // and the constraints will not propagate correctly.
144  continue;
145  }
146  }
147  }
148 
149  return {};
150 }
151 
152 static std::optional<exprt> eager_quantifier_instantiation(
153  const quantifier_exprt &expr,
154  const namespacet &ns)
155 {
156  if(expr.variables().size() > 1)
157  {
158  // Qx,y.P(x,y) is the same as Qx.Qy.P(x,y)
159  auto new_variables = expr.variables();
160  new_variables.pop_back();
161  auto new_expression = quantifier_exprt(
162  expr.id(),
163  expr.variables().back(),
164  quantifier_exprt(expr.id(), new_variables, expr.where()));
165  return eager_quantifier_instantiation(new_expression, ns);
166  }
167 
168  const symbol_exprt &var_expr = expr.symbol();
169 
175  const exprt where_simplified = simplify_expr(expr.where(), ns);
176 
177  if(where_simplified.is_true() || where_simplified.is_false())
178  {
179  return where_simplified;
180  }
181 
182  if(var_expr.is_boolean())
183  {
184  // Expand in full.
185  // This grows worst-case exponentially in the quantifier nesting depth.
186  if(expr.id() == ID_forall)
187  {
188  // ∀b.f(b) <===> f(0)∧f(1)
189  return and_exprt(
190  expr.instantiate({false_exprt()}), expr.instantiate({true_exprt()}));
191  }
192  else if(expr.id() == ID_exists)
193  {
194  // ∃b.f(b) <===> f(0)∨f(1)
195  return or_exprt(
196  expr.instantiate({false_exprt()}), expr.instantiate({true_exprt()}));
197  }
198  else
199  UNREACHABLE;
200  }
201 
202  const std::optional<constant_exprt> min_i =
203  get_quantifier_var_min(var_expr, where_simplified);
204  const std::optional<constant_exprt> max_i =
205  get_quantifier_var_max(var_expr, where_simplified);
206 
207  if(!min_i.has_value() || !max_i.has_value())
208  return {};
209 
210  mp_integer lb = numeric_cast_v<mp_integer>(min_i.value());
211  mp_integer ub = numeric_cast_v<mp_integer>(max_i.value());
212 
213  if(lb > ub)
214  return {};
215 
216  auto expr_simplified =
217  quantifier_exprt(expr.id(), expr.variables(), where_simplified);
218 
219  std::vector<exprt> expr_insts;
220  for(mp_integer i = lb; i <= ub; ++i)
221  {
222  exprt constraint_expr =
223  expr_simplified.instantiate({from_integer(i, var_expr.type())});
224  expr_insts.push_back(constraint_expr);
225  }
226 
227  if(expr.id() == ID_forall)
228  {
229  // maintain the domain constraint if it isn't guaranteed
230  // by the instantiations (for a disjunction the domain
231  // constraint is implied by the instantiations)
232  if(where_simplified.id() == ID_and)
233  {
234  expr_insts.push_back(binary_predicate_exprt(
235  var_expr, ID_gt, from_integer(lb, var_expr.type())));
236  expr_insts.push_back(binary_predicate_exprt(
237  var_expr, ID_le, from_integer(ub, var_expr.type())));
238  }
239 
240  return simplify_expr(conjunction(expr_insts), ns);
241  }
242  else if(expr.id() == ID_exists)
243  {
244  // maintain the domain constraint if it isn't trivially satisfied
245  // by the instantiations (for a conjunction the instantiations are
246  // stronger constraints)
247  if(where_simplified.id() == ID_or)
248  {
249  expr_insts.push_back(binary_predicate_exprt(
250  var_expr, ID_gt, from_integer(lb, var_expr.type())));
251  expr_insts.push_back(binary_predicate_exprt(
252  var_expr, ID_le, from_integer(ub, var_expr.type())));
253  }
254 
255  return simplify_expr(disjunction(expr_insts), ns);
256  }
257 
258  UNREACHABLE;
259 }
260 
262 {
263  PRECONDITION(src.id() == ID_forall || src.id() == ID_exists);
264 
265  // We first worry about the scoping of the symbols bound by the quantifier.
266  auto fresh_symbols = fresh_binding(src);
267 
268  // replace in where()
269  auto where_replaced = src.instantiate(fresh_symbols);
270 
271  // produce new quantifier expression
272  auto new_src =
273  quantifier_exprt(src.id(), std::move(fresh_symbols), where_replaced);
274 
275  const auto res = eager_quantifier_instantiation(src, ns);
276 
277  if(res)
278  return convert_bool(*res);
279 
280  // we failed to instantiate here, need to pass to post-processing
281  quantifier_list.emplace_back(quantifiert(src, prop.new_variable()));
282 
283  return quantifier_list.back().l;
284 }
285 
287 {
288  if(quantifier_list.empty())
289  return;
290 
291  // we do not yet have any elaborate post-processing
292  for(const auto &q : quantifier_list)
293  conversion_failed(q.expr);
294 }
constant_exprt from_integer(const mp_integer &int_value, const typet &type)
Definition: arith_tools.cpp:99
static bool expr_eq(const exprt &expr1, const exprt &expr2)
A method to detect equivalence between experts that can contain typecast.
static std::optional< constant_exprt > get_quantifier_var_min(const exprt &var_expr, const exprt &quantifier_expr)
To obtain the min value for the quantifier variable of the specified forall/exists operator.
static std::optional< constant_exprt > get_quantifier_var_max(const exprt &var_expr, const exprt &quantifier_expr)
To obtain the max value for the quantifier variable of the specified forall/exists operator.
static std::optional< exprt > eager_quantifier_instantiation(const quantifier_exprt &expr, const namespacet &ns)
Boolean AND.
Definition: std_expr.h:2125
const namespacet & ns
Definition: arrays.h:56
A base class for expressions that are predicates, i.e., Boolean-typed, and that take exactly two argu...
Definition: std_expr.h:731
exprt & where()
Definition: std_expr.h:3148
exprt instantiate(const exprt::operandst &) const
substitute free occurrences of the variables in where() by the given values
Definition: std_expr.cpp:225
variablest & variables()
Definition: std_expr.h:3138
binding_exprt::variablest fresh_binding(const binding_exprt &)
create new, unique variables for the given binding
Definition: boolbv.cpp:554
void finish_eager_conversion_quantifiers()
virtual literalt convert_quantifier(const quantifier_exprt &expr)
bvt conversion_failed(const exprt &expr)
Print that the expression of x has failed conversion, then return a vector of x's width.
Definition: boolbv.cpp:94
quantifier_listt quantifier_list
Definition: boolbv.h:272
A constant literal expression.
Definition: std_expr.h:3000
Base class for all expressions.
Definition: expr.h:56
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
typet & type()
Return the type of the expression.
Definition: expr.h:84
operandst & operands()
Definition: expr.h:94
const irep_idt & id() const
Definition: irep.h:388
A namespacet is essentially one or two symbol tables bound together, to allow for symbol lookups in t...
Definition: namespace.h:94
Boolean OR.
Definition: std_expr.h:2233
virtual literalt convert_bool(const exprt &expr)
virtual literalt new_variable()=0
A base class for quantifier expressions.
symbol_exprt & symbol()
Expression to hold a symbol (variable)
Definition: std_expr.h:131
const exprt & op() const
Definition: std_expr.h:391
const exprt & skip_typecast(const exprt &expr)
find the expression nested inside typecasts, if any
Definition: expr_util.cpp:193
Deprecated expression utility functions.
exprt simplify_expr(exprt src, const namespacet &ns)
BigInt mp_integer
Definition: smt_terms.h:17
#define UNREACHABLE
This should be used to mark dead code.
Definition: invariant.h:525
#define PRECONDITION(CONDITION)
Definition: invariant.h:463
exprt conjunction(const exprt::operandst &op)
1) generates a conjunction for two or more operands 2) for one operand, returns the operand 3) return...
Definition: std_expr.cpp:83
exprt disjunction(const exprt::operandst &op)
1) generates a disjunction for two or more operands 2) for one operand, returns the operand 3) return...
Definition: std_expr.cpp:71
const constant_exprt & to_constant_expr(const exprt &expr)
Cast an exprt to a constant_exprt.
Definition: std_expr.h:3055
const not_exprt & to_not_expr(const exprt &expr)
Cast an exprt to an not_exprt.
Definition: std_expr.h:2362
const binary_relation_exprt & to_binary_relation_expr(const exprt &expr)
Cast an exprt to a binary_relation_exprt.
Definition: std_expr.h:895