Skip to content

Commit 4b05dcb

Browse files
authored
Merge pull request #2522 from smowton/smowton/feature/constant-propagator-improvement
Constant propagator: improve GOTO condition propagation
2 parents dac8c7b + 897ffda commit 4b05dcb

File tree

3 files changed

+393
-45
lines changed

3 files changed

+393
-45
lines changed

src/analyses/constant_propagator.cpp

Lines changed: 126 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Author: Peter Schrammel
1818

1919
#include <util/arith_tools.h>
2020
#include <util/base_type.h>
21+
#include <util/c_types.h>
2122
#include <util/cprover_prefix.h>
2223
#include <util/expr_util.h>
2324
#include <util/find_symbols.h>
@@ -29,40 +30,64 @@ Author: Peter Schrammel
2930
#include <algorithm>
3031
#include <array>
3132

33+
/// Assign value `rhs` to `lhs`, recording any newly-known constants in
34+
/// `dest_values`.
35+
/// \param [out] dest_values: results of the assignment are recorded here. We
36+
/// might add extra entries (if we determine some symbol is constant), or
37+
/// might remove existing ones (if the lhs expression is unknown), except if
38+
/// `is_assignment` is false, in which case only the former is done.
39+
/// \param lhs: lhs expression to assign
40+
/// \param rhs: rhs expression to assign to lhs
41+
/// \param ns: namespace, used to check for type mismatches
42+
/// \param cp: owning constant propagator instance, used to filter out symbols
43+
/// that the user doesn't want tracked
44+
/// \param is_assignment: if true, assign_rec may remove entries from
45+
/// dest_values when a constant assignment cannot be determined. This is used
46+
/// when an actual assignment instruction is processed. If false, new entries
47+
/// can be added but existing ones will not be removed; this is used when the
48+
/// "assignment" is actually implied by a read-only operation, such as passing
49+
/// "IF x == y" -- if we know what 'y' is that tells us the value for x, but
50+
/// if we don't there is no reason to discard pre-existing knowledge about x.
3251
void constant_propagator_domaint::assign_rec(
3352
valuest &dest_values,
3453
const exprt &lhs,
3554
const exprt &rhs,
3655
const namespacet &ns,
37-
const constant_propagator_ait *cp)
56+
const constant_propagator_ait *cp,
57+
bool is_assignment)
3858
{
3959
if(lhs.id() == ID_dereference)
4060
{
4161
exprt eval_lhs = lhs;
4262
if(partial_evaluate(dest_values, eval_lhs, ns))
4363
{
44-
const bool have_dirty = (cp != nullptr);
64+
if(is_assignment)
65+
{
66+
const bool have_dirty = (cp != nullptr);
4567

46-
if(have_dirty)
47-
dest_values.set_dirty_to_top(cp->dirty, ns);
48-
else
49-
dest_values.set_to_top();
68+
if(have_dirty)
69+
dest_values.set_dirty_to_top(cp->dirty, ns);
70+
else
71+
dest_values.set_to_top();
72+
}
73+
// Otherwise disregard this unknown deref in a read-only context.
5074
}
5175
else
52-
assign_rec(dest_values, eval_lhs, rhs, ns, cp);
76+
assign_rec(dest_values, eval_lhs, rhs, ns, cp, is_assignment);
5377
}
5478
else if(lhs.id() == ID_index)
5579
{
5680
const index_exprt &index_expr = to_index_expr(lhs);
5781
with_exprt new_rhs(index_expr.array(), index_expr.index(), rhs);
58-
assign_rec(dest_values, index_expr.array(), new_rhs, ns, cp);
82+
assign_rec(dest_values, index_expr.array(), new_rhs, ns, cp, is_assignment);
5983
}
6084
else if(lhs.id() == ID_member)
6185
{
6286
const member_exprt &member_expr = to_member_expr(lhs);
6387
with_exprt new_rhs(member_expr.compound(), exprt(ID_member_name), rhs);
6488
new_rhs.where().set(ID_component_name, member_expr.get_component_name());
65-
assign_rec(dest_values, member_expr.compound(), new_rhs, ns, cp);
89+
assign_rec(
90+
dest_values, member_expr.compound(), new_rhs, ns, cp, is_assignment);
6691
}
6792
else if(lhs.id() == ID_symbol)
6893
{
@@ -82,13 +107,15 @@ void constant_propagator_domaint::assign_rec(
82107
dest_values.set_to(s, tmp);
83108
}
84109
else
85-
dest_values.set_to_top(s);
110+
{
111+
if(is_assignment)
112+
dest_values.set_to_top(s);
113+
}
86114
}
87-
else
115+
else if(is_assignment)
88116
{
89117
// it's an assignment, but we don't really know what object is being written
90-
// to on the left-hand side - bail and set all values to top to be on the
91-
// safe side in terms of soundness
118+
// to: assume it may write to anything.
92119
dest_values.set_to_top();
93120
}
94121
}
@@ -137,7 +164,7 @@ void constant_propagator_domaint::transform(
137164
const code_assignt &assignment=to_code_assign(from->code);
138165
const exprt &lhs=assignment.lhs();
139166
const exprt &rhs=assignment.rhs();
140-
assign_rec(values, lhs, rhs, ns, cp);
167+
assign_rec(values, lhs, rhs, ns, cp, true);
141168
}
142169
else if(from->is_assume())
143170
{
@@ -157,15 +184,7 @@ void constant_propagator_domaint::transform(
157184
if(g.is_false())
158185
values.set_to_bottom();
159186
else
160-
{
161187
two_way_propagate_rec(g, ns, cp);
162-
// If two way propagate is enabled then it may be possible to detect
163-
// that the branch condition is infeasible and thus the domain should
164-
// be set to bottom. Without it the domain will only be set to bottom
165-
// if the guard expression is trivially (i.e. without context) false.
166-
INVARIANT(!values.is_bottom,
167-
"Without two-way propagation this should be impossible.");
168-
}
169188
}
170189
else if(from->is_dead())
171190
{
@@ -223,7 +242,7 @@ void constant_propagator_domaint::transform(
223242
break;
224243

225244
const symbol_exprt parameter_expr(p_it->get_identifier(), arg.type());
226-
assign_rec(values, parameter_expr, arg, ns, cp);
245+
assign_rec(values, parameter_expr, arg, ns, cp, true);
227246

228247
++p_it;
229248
}
@@ -264,6 +283,26 @@ void constant_propagator_domaint::transform(
264283
#endif
265284
}
266285

286+
static void
287+
replace_typecast_of_bool(exprt &lhs, exprt &rhs, const namespacet &ns)
288+
{
289+
// (int)var xx 0 ==> var xx (_Bool)0 or similar (xx is == or !=)
290+
// Note this is restricted to bools because in general turning a widening
291+
// into a narrowing typecast is not correct.
292+
if(lhs.id() != ID_typecast)
293+
return;
294+
295+
const exprt &lhs_underlying = to_typecast_expr(lhs).op();
296+
if(
297+
lhs_underlying.type() == bool_typet() ||
298+
lhs_underlying.type() == c_bool_type())
299+
{
300+
rhs = typecast_exprt(rhs, lhs_underlying.type());
301+
simplify(rhs, ns);
302+
303+
lhs = lhs_underlying;
304+
}
305+
}
267306

268307
/// handles equalities and conjunctions containing equalities
269308
bool constant_propagator_domaint::two_way_propagate_rec(
@@ -280,26 +319,81 @@ bool constant_propagator_domaint::two_way_propagate_rec(
280319
if(expr.id()==ID_and)
281320
{
282321
// need a fixed point here to get the most out of it
322+
bool change_this_time;
283323
do
284324
{
285-
change = false;
325+
change_this_time = false;
286326

287327
forall_operands(it, expr)
288-
if(two_way_propagate_rec(*it, ns, cp))
289-
change=true;
328+
{
329+
change_this_time |= two_way_propagate_rec(*it, ns, cp);
330+
if(change_this_time)
331+
change = true;
332+
}
333+
} while(change_this_time);
334+
}
335+
else if(expr.id() == ID_not)
336+
{
337+
if(expr.op0().id() == ID_equal || expr.op0().id() == ID_notequal)
338+
{
339+
exprt subexpr = expr.op0();
340+
subexpr.id(subexpr.id() == ID_equal ? ID_notequal : ID_equal);
341+
change = two_way_propagate_rec(subexpr, ns, cp);
342+
}
343+
else if(expr.op0().id() == ID_symbol && expr.type() == bool_typet())
344+
{
345+
// Treat `IF !x` like `IF x == FALSE`:
346+
change =
347+
two_way_propagate_rec(equal_exprt(expr.op0(), false_exprt()), ns, cp);
348+
}
349+
}
350+
else if(expr.id() == ID_symbol)
351+
{
352+
if(expr.type() == bool_typet())
353+
{
354+
// Treat `IF x` like `IF x == TRUE`:
355+
change = two_way_propagate_rec(equal_exprt(expr, true_exprt()), ns, cp);
290356
}
291-
while(change);
292357
}
293-
else if(expr.id()==ID_equal)
358+
else if(expr.id() == ID_notequal)
294359
{
295-
const exprt &lhs=expr.op0();
296-
const exprt &rhs=expr.op1();
360+
// Treat "symbol != constant" like "symbol == !constant" when the constant
361+
// is a boolean.
362+
exprt lhs = expr.op0();
363+
exprt rhs = expr.op1();
364+
365+
if(lhs.is_constant() && !rhs.is_constant())
366+
std::swap(lhs, rhs);
367+
368+
if(lhs.is_constant() || !rhs.is_constant())
369+
return false;
370+
371+
replace_typecast_of_bool(lhs, rhs, ns);
372+
373+
if(lhs.type() != bool_typet() && lhs.type() != c_bool_type())
374+
return false;
375+
376+
// x != FALSE ==> x == TRUE
377+
378+
if(rhs.is_zero() || rhs.is_false())
379+
rhs = from_integer(1, rhs.type());
380+
else
381+
rhs = from_integer(0, rhs.type());
382+
383+
change = two_way_propagate_rec(equal_exprt(lhs, rhs), ns, cp);
384+
}
385+
else if(expr.id() == ID_equal)
386+
{
387+
exprt lhs = expr.op0();
388+
exprt rhs = expr.op1();
389+
390+
replace_typecast_of_bool(lhs, rhs, ns);
297391

298392
// two-way propagation
299393
valuest copy_values=values;
300-
assign_rec(copy_values, lhs, rhs, ns, cp);
394+
assign_rec(copy_values, lhs, rhs, ns, cp, false);
301395
if(!values.is_constant(rhs) || values.is_constant(lhs))
302-
assign_rec(values, rhs, lhs, ns, cp);
396+
assign_rec(values, rhs, lhs, ns, cp, false);
303397
change = values.meet(copy_values, ns);
304398
}
305399

src/analyses/constant_propagator.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ class constant_propagator_domaint:public ai_domain_baset
146146
const exprt &lhs,
147147
const exprt &rhs,
148148
const namespacet &ns,
149-
const constant_propagator_ait *cp);
149+
const constant_propagator_ait *cp,
150+
bool is_assignment);
150151

151152
bool two_way_propagate_rec(
152153
const exprt &expr,

0 commit comments

Comments
 (0)