Skip to content

Commit 14fce73

Browse files
authored
Merge pull request #7761 from NlightNFotis/simplify_interval_domain
2 parents bd87ba9 + de8b169 commit 14fce73

File tree

7 files changed

+198
-21
lines changed

7 files changed

+198
-21
lines changed

regression/cbmc-primitives/alternating_quantifiers_6231/forall_in_exists.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ int main(int argc, char **argv)
88

99
__CPROVER_assert(
1010
__CPROVER_exists { int z; (0 < z && z < 2) &&
11-
__CPROVER_forall { int o; (10 < o && o < 20) ==> o > z && z == * i }},
11+
__CPROVER_forall { int o; (10 < o && o < 20) ==> o > z && z == *i }},
1212
"there exists a z between 0 and 2 so that for all o between 10 and 20, o > z and z = 1");
1313
}
1414
// clang-format on
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CORE
2+
--trace
3+
singleton_interval_simp_neg.c
4+
^VERIFICATION FAILED$
5+
^\[main\.assertion\.1\] line \d expected failure: paths where x is unbounded explored: FAILURE$
6+
^\[main\.assertion\.2\] line \d+ expected failure: paths where 0 \<= x \<= 15 explored: FAILURE$
7+
^\[main\.assertion\.3\] line \d+ expected success: paths where x \<= 15 explored: SUCCESS$
8+
y=-6 \(11111111 11111111 11111111 11111010\)
9+
x=14 \(00000000 00000000 00000000 00001110\)
10+
y=34 \(00000000 00000000 00000000 00100010\)
11+
^EXIT=10$
12+
^SIGNAL=0$
13+
--
14+
--
15+
This tests the negative case of the simplification of the singleton interval
16+
(i.e when the presented interval *is* the *not* the singleton interval -
17+
the set of possible values that the bounded variable can take has cardinality
18+
> 1).
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
--trace
3+
singleton_interval_simp.c
4+
^VERIFICATION FAILED$
5+
^\[main\.assertion\.1\] line \d+ expected failure: only paths where x == 15 explored: FAILURE$
6+
^\[main\.assertion\.2\] line \d+ expected failure: only paths where x == 15 explored: FAILURE$
7+
x=15 \(00000000 00000000 00000000 00001111\)
8+
y=35 \(00000000 00000000 00000000 00100011\)
9+
^EXIT=10$
10+
^SIGNAL=0$
11+
--
12+
--
13+
This tests the positive case of the simplification of the singleton interval
14+
(i.e when the presented interval *is* the singleton interval)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Positive test for singleton interval simplification.
2+
// Notice that the sequence of the inequalities in this
3+
// expression is different to the one in
4+
// `singleton_interval_in_assume_7690.c`.
5+
6+
int main()
7+
{
8+
int x;
9+
__CPROVER_assume(x >= 15 && x <= 15);
10+
int y = x + 20;
11+
12+
__CPROVER_assert(
13+
y != 35, "expected failure: only paths where x == 15 explored");
14+
__CPROVER_assert(
15+
y == 34, "expected failure: only paths where x == 15 explored");
16+
return 0;
17+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Negative test for singleton interval simplification.
2+
3+
int main()
4+
{
5+
int x;
6+
int y = x + 20;
7+
8+
__CPROVER_assert(
9+
y != -6, "expected failure: paths where x is unbounded explored");
10+
11+
__CPROVER_assume(x >= 0 && x <= 15);
12+
__CPROVER_assert(
13+
y != 34, "expected failure: paths where 0 <= x <= 15 explored");
14+
15+
int z = x + 20;
16+
__CPROVER_assert(z != 36, "expected success: paths where x <= 15 explored");
17+
return 0;
18+
}

src/solvers/flattening/boolbv_quantifier.cpp

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ get_quantifier_var_min(const exprt &var_expr, const exprt &quantifier_expr)
5050
}
5151
else if(quantifier_expr.id() == ID_and)
5252
{
53-
/**
54-
* The min variable
55-
* is in the form of "var_expr >= constant".
56-
*/
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)).
5757
for(auto &x : quantifier_expr.operands())
5858
{
59-
if(x.id()!=ID_ge)
59+
if(x.id() != ID_ge && x.id() != ID_equal)
6060
continue;
6161
const auto &x_binary = to_binary_relation_expr(x);
6262
if(expr_eq(var_expr, x_binary.lhs()) && x_binary.rhs().is_constant())
@@ -106,24 +106,42 @@ get_quantifier_var_max(const exprt &var_expr, const exprt &quantifier_expr)
106106
}
107107
else
108108
{
109-
/**
110-
* The max variable
111-
* is in the form of "!(var_expr >= constant)".
112-
*/
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.
113114
for(auto &x : quantifier_expr.operands())
114115
{
115-
if(x.id()!=ID_not)
116-
continue;
117-
exprt y = to_not_expr(x).op();
118-
if(y.id()!=ID_ge)
119-
continue;
120-
const auto &y_binary = to_binary_relation_expr(y);
121-
if(expr_eq(var_expr, y_binary.lhs()) && y_binary.rhs().is_constant())
116+
if(x.id() == ID_not)
122117
{
123-
const constant_exprt &over_expr = to_constant_expr(y_binary.rhs());
124-
mp_integer over_i = numeric_cast_v<mp_integer>(over_expr);
125-
over_i-=1;
126-
return from_integer(over_i, y_binary.rhs().type());
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;
127145
}
128146
}
129147
}

src/util/simplify_expr_boolean.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ simplify_exprt::resultt<> simplify_exprt::simplify_boolean(const exprt &expr)
9999
bool no_change = true;
100100
bool may_be_reducible_to_interval =
101101
expr.id() == ID_or && expr.operands().size() > 2;
102+
bool may_be_reducible_to_singleton_interval =
103+
expr.id() == ID_and && expr.operands().size() == 2;
102104

103105
exprt::operandst new_operands = expr.operands();
104106

@@ -137,6 +139,96 @@ simplify_exprt::resultt<> simplify_exprt::simplify_boolean(const exprt &expr)
137139
}
138140
}
139141

142+
// NOLINTNEXTLINE(whitespace/line_length)
143+
// This block reduces singleton intervals like (value >= 255 && value <= 255)
144+
// to just (value == 255). We also need to be careful with the operands
145+
// as some of them are erased in the previous step. We proceed only if
146+
// no operands have been erased (i.e. the expression structure has been
147+
// preserved by previous simplification rounds.)
148+
if(may_be_reducible_to_singleton_interval && new_operands.size() == 2)
149+
{
150+
struct boundst
151+
{
152+
mp_integer lower;
153+
mp_integer higher;
154+
};
155+
boundst bounds;
156+
157+
// Before we do anything else, we need to "pattern match" against the
158+
// expression and make sure that it has the structure we're looking for.
159+
// The structure we're looking for is going to be
160+
// (value >= 255 && !(value >= 256)) -- 255, 256 values indicative.
161+
// (this is because previous simplification runs will have transformed
162+
// the less_than_or_equal expression to a not(greater_than_or_equal)
163+
// expression)
164+
165+
// matching (value >= 255)
166+
auto const match_first_operand = [&bounds](const exprt &op) -> bool {
167+
if(
168+
const auto ge_expr =
169+
expr_try_dynamic_cast<greater_than_or_equal_exprt>(op))
170+
{
171+
// The construction of these expressions ensures that the RHS
172+
// is constant, therefore if we don't have a constant, it's a
173+
// different expression, so we bail.
174+
if(!ge_expr->rhs().is_constant())
175+
return false;
176+
if(
177+
auto int_opt =
178+
numeric_cast<mp_integer>(to_constant_expr(ge_expr->rhs())))
179+
{
180+
bounds.lower = *int_opt;
181+
return true;
182+
}
183+
return false;
184+
}
185+
return false;
186+
};
187+
188+
// matching !(value >= 256)
189+
auto const match_second_operand = [&bounds](const exprt &op) -> bool {
190+
if(const auto not_expr = expr_try_dynamic_cast<not_exprt>(op))
191+
{
192+
PRECONDITION(not_expr->operands().size() == 1);
193+
if(
194+
const auto ge_expr =
195+
expr_try_dynamic_cast<greater_than_or_equal_exprt>(
196+
not_expr->op()))
197+
{
198+
// If the rhs() is not constant, it has a different structure
199+
// (e.g. i >= j)
200+
if(!ge_expr->rhs().is_constant())
201+
return false;
202+
if(
203+
auto int_opt =
204+
numeric_cast<mp_integer>(to_constant_expr(ge_expr->rhs())))
205+
{
206+
bounds.higher = *int_opt - 1;
207+
return true;
208+
}
209+
return false;
210+
}
211+
return false;
212+
}
213+
return false;
214+
};
215+
216+
// We need to match both operands, at the particular sequence we expect.
217+
bool structure_matched = match_first_operand(new_operands[0]) &&
218+
match_second_operand(new_operands[1]);
219+
220+
if(structure_matched && bounds.lower == bounds.higher)
221+
{
222+
// If we are here, we have matched the structure we expected, so we can
223+
// make some reasonable assumptions about where certain info we need is
224+
// located at.
225+
const auto ge_expr =
226+
expr_dynamic_cast<greater_than_or_equal_exprt>(new_operands[0]);
227+
equal_exprt new_expr{ge_expr.lhs(), ge_expr.rhs()};
228+
return changed(new_expr);
229+
}
230+
}
231+
140232
if(may_be_reducible_to_interval)
141233
{
142234
optionalt<symbol_exprt> symbol_opt;

0 commit comments

Comments
 (0)