Skip to content

Commit 56a7a49

Browse files
committed
Add saturating addition/subtraction
Rust natively supports saturating arithmetic. For C code, it takes MMX instructions to have access to this (which will be implemented in the next commit), and even then it is limited to addition and subtraction. The implementation now is equally restricted to these two arithmetic operations. Fixes: #5841
1 parent 57c59fc commit 56a7a49

File tree

16 files changed

+389
-3
lines changed

16 files changed

+389
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <limits.h>
2+
3+
int main()
4+
{
5+
__CPROVER_assert(
6+
__CPROVER_saturating_minus(INT_MIN, 1) == INT_MIN,
7+
"subtracting from INT_MIN");
8+
__CPROVER_assert(
9+
__CPROVER_saturating_plus(LONG_MAX, 1l) == LONG_MAX, "adding to LONG_MAX");
10+
__CPROVER_assert(
11+
__CPROVER_saturating_minus(-1, INT_MIN) == INT_MAX, "no overflow");
12+
__CPROVER_assert(
13+
__CPROVER_saturating_plus(ULONG_MAX, 1ul) == ULONG_MAX,
14+
"adding to ULONG_MAX");
15+
__CPROVER_assert(
16+
__CPROVER_saturating_minus(10ul, ULONG_MAX) == 0, "subtracting ULONG_MAX");
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--program-only
4+
ASSERT\(__CPROVER_saturating_minus\(.*\)
5+
ASSERT\(__CPROVER_saturating_plus\(.*\)
6+
^EXIT=0$
7+
^SIGNAL=0$
8+
--
9+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--show-goto-functions
4+
ASSERT saturating-\(.*\)
5+
ASSERT saturating\+\(.*\)
6+
^EXIT=0$
7+
^SIGNAL=0$
8+
--
9+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE broken-smt-backend
2+
main.c
3+
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^warning: ignoring

regression/validate-trace-xml-schema/check.py

+2
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
['enum_is_in_range', 'format.desc'],
3030
['r_w_ok9', 'simplify.desc'],
3131
['reachability-slice-interproc2', 'test.desc'],
32+
['saturating_arithmetric', 'output-goto.desc'],
3233
# this one wants show-properties instead producing a trace
3334
['show_properties1', 'test.desc'],
3435
# program-only instead of trace
3536
['vla1', 'program-only.desc'],
3637
['Pointer_Arithmetic19', 'test.desc'],
3738
['Quantifiers-simplify', 'simplify_not_forall.desc'],
3839
['array-cell-sensitivity15', 'test.desc'],
40+
['saturating_arithmetric', 'output-formula.desc'],
3941
# these test for invalid command line handling
4042
['bad_option', 'test_multiple.desc'],
4143
['bad_option', 'test.desc'],

src/ansi-c/c_typecheck_base.h

+2
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ class c_typecheck_baset:
205205
exprt typecheck_builtin_overflow(
206206
side_effect_expr_function_callt &expr,
207207
const irep_idt &arith_op);
208+
exprt
209+
typecheck_saturating_arithmetic(const side_effect_expr_function_callt &expr);
208210
virtual optionalt<symbol_exprt> typecheck_gcc_polymorphic_builtin(
209211
const irep_idt &identifier,
210212
const exprt::operandst &arguments,

src/ansi-c/c_typecheck_expr.cpp

+57-3
Original file line numberDiff line numberDiff line change
@@ -1967,6 +1967,15 @@ void c_typecheck_baset::typecheck_side_effect_function_call(
19671967

19681968
return;
19691969
}
1970+
else if(
1971+
identifier == CPROVER_PREFIX "saturating_minus" ||
1972+
identifier == CPROVER_PREFIX "saturating_plus")
1973+
{
1974+
exprt result = typecheck_saturating_arithmetic(expr);
1975+
expr.swap(result);
1976+
1977+
return;
1978+
}
19701979
else if(
19711980
auto gcc_polymorphic = typecheck_gcc_polymorphic_builtin(
19721981
identifier, expr.arguments(), f_op.source_location()))
@@ -3298,6 +3307,35 @@ exprt c_typecheck_baset::typecheck_builtin_overflow(
32983307
expr.source_location()};
32993308
}
33003309

3310+
exprt c_typecheck_baset::typecheck_saturating_arithmetic(
3311+
const side_effect_expr_function_callt &expr)
3312+
{
3313+
const irep_idt &identifier = to_symbol_expr(expr.function()).get_identifier();
3314+
3315+
// check function signature
3316+
if(expr.arguments().size() != 2)
3317+
{
3318+
std::ostringstream error_message;
3319+
error_message << expr.source_location().as_string() << ": " << identifier
3320+
<< " takes exactly two arguments, but "
3321+
<< expr.arguments().size() << " were provided";
3322+
throw invalid_source_file_exceptiont{error_message.str()};
3323+
}
3324+
3325+
exprt result;
3326+
if(identifier == CPROVER_PREFIX "saturating_minus")
3327+
result = saturating_minus_exprt{expr.arguments()[0], expr.arguments()[1]};
3328+
else if(identifier == CPROVER_PREFIX "saturating_plus")
3329+
result = saturating_plus_exprt{expr.arguments()[0], expr.arguments()[1]};
3330+
else
3331+
UNREACHABLE;
3332+
3333+
typecheck_expr_binary_arithmetic(result);
3334+
result.add_source_location() = expr.source_location();
3335+
3336+
return result;
3337+
}
3338+
33013339
/// Typecheck the parameters in a function call expression, and where
33023340
/// necessary, make implicit casts around parameters explicit.
33033341
void c_typecheck_baset::typecheck_function_call_arguments(
@@ -3499,9 +3537,15 @@ void c_typecheck_baset::typecheck_expr_binary_arithmetic(exprt &expr)
34993537
return;
35003538
}
35013539

3502-
// promote!
3503-
3504-
implicit_typecast_arithmetic(op0, op1);
3540+
if(expr.id() == ID_saturating_minus || expr.id() == ID_saturating_plus)
3541+
{
3542+
implicit_typecast(op1, o_type0);
3543+
}
3544+
else
3545+
{
3546+
// promote!
3547+
implicit_typecast_arithmetic(op0, op1);
3548+
}
35053549

35063550
const typet &type0 = op0.type();
35073551
const typet &type1 = op1.type();
@@ -3562,6 +3606,16 @@ void c_typecheck_baset::typecheck_expr_binary_arithmetic(exprt &expr)
35623606
}
35633607
}
35643608
}
3609+
else if(expr.id() == ID_saturating_minus || expr.id() == ID_saturating_plus)
3610+
{
3611+
if(
3612+
type0 == type1 &&
3613+
(type0.id() == ID_signedbv || type0.id() == ID_unsignedbv))
3614+
{
3615+
expr.type() = type0;
3616+
return;
3617+
}
3618+
}
35653619

35663620
error().source_location = expr.source_location();
35673621
error() << "operator '" << expr.id() << "' not defined for types '"

src/ansi-c/expr2c.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -3977,6 +3977,8 @@ optionalt<std::string> expr2ct::convert_function(const exprt &src)
39773977
{ID_object_size, "OBJECT_SIZE"},
39783978
{ID_pointer_object, "POINTER_OBJECT"},
39793979
{ID_pointer_offset, "POINTER_OFFSET"},
3980+
{ID_saturating_minus, CPROVER_PREFIX "saturating_minus"},
3981+
{ID_saturating_plus, CPROVER_PREFIX "saturating_plus"},
39803982
{ID_r_ok, "R_OK"},
39813983
{ID_w_ok, "W_OK"},
39823984
{ID_rw_ok, "RW_OK"},

src/solvers/flattening/boolbv.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ bvt boolbvt::convert_bitvector(const exprt &expr)
229229
}
230230
else if(expr.id() == ID_bitreverse)
231231
return convert_bitreverse(to_bitreverse_expr(expr));
232+
else if(expr.id() == ID_saturating_minus || expr.id() == ID_saturating_plus)
233+
return convert_saturating_add_sub(to_binary_expr(expr));
232234

233235
return conversion_failed(expr);
234236
}

src/solvers/flattening/boolbv.h

+1
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ class boolbvt:public arrayst
194194
virtual bvt convert_function_application(
195195
const function_application_exprt &expr);
196196
virtual bvt convert_bitreverse(const bitreverse_exprt &expr);
197+
virtual bvt convert_saturating_add_sub(const binary_exprt &expr);
197198

198199
virtual exprt make_bv_expr(const typet &type, const bvt &bv);
199200
virtual exprt make_free_bv_expr(const typet &type);

src/solvers/flattening/boolbv_add_sub.cpp

+89
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,92 @@ bvt boolbvt::convert_add_sub(const exprt &expr)
142142

143143
return bv;
144144
}
145+
146+
bvt boolbvt::convert_saturating_add_sub(const binary_exprt &expr)
147+
{
148+
PRECONDITION(
149+
expr.id() == ID_saturating_minus || expr.id() == ID_saturating_plus);
150+
151+
const typet &type = expr.type();
152+
153+
if(
154+
type.id() != ID_unsignedbv && type.id() != ID_signedbv &&
155+
type.id() != ID_fixedbv && type.id() != ID_complex &&
156+
type.id() != ID_vector)
157+
{
158+
return conversion_failed(expr);
159+
}
160+
161+
std::size_t width = boolbv_width(type);
162+
163+
if(width == 0)
164+
return conversion_failed(expr);
165+
166+
DATA_INVARIANT(
167+
expr.lhs().type() == type && expr.rhs().type() == type,
168+
"saturating add/sub with mixed types:\n" + expr.pretty());
169+
170+
const bool subtract = expr.id() == ID_saturating_minus;
171+
172+
typet arithmetic_type = (type.id() == ID_vector || type.id() == ID_complex)
173+
? to_type_with_subtype(type).subtype()
174+
: type;
175+
176+
bv_utilst::representationt rep =
177+
(arithmetic_type.id() == ID_signedbv || arithmetic_type.id() == ID_fixedbv)
178+
? bv_utilst::representationt::SIGNED
179+
: bv_utilst::representationt::UNSIGNED;
180+
181+
bvt bv = convert_bv(expr.lhs(), width);
182+
const bvt &op = convert_bv(expr.rhs(), width);
183+
184+
if(type.id() != ID_vector && type.id() != ID_complex)
185+
return bv_utils.saturating_add_sub(bv, op, subtract, rep);
186+
187+
std::size_t sub_width = boolbv_width(to_type_with_subtype(type).subtype());
188+
189+
INVARIANT(sub_width != 0, "vector elements shall have nonzero bit width");
190+
INVARIANT(
191+
width % sub_width == 0,
192+
"total vector bit width shall be a multiple of the element bit width");
193+
194+
std::size_t size = width / sub_width;
195+
196+
for(std::size_t i = 0; i < size; i++)
197+
{
198+
bvt tmp_op;
199+
tmp_op.resize(sub_width);
200+
201+
for(std::size_t j = 0; j < tmp_op.size(); j++)
202+
{
203+
const std::size_t index = i * sub_width + j;
204+
INVARIANT(index < op.size(), "bit index shall be within bounds");
205+
tmp_op[j] = op[index];
206+
}
207+
208+
bvt tmp_result;
209+
tmp_result.resize(sub_width);
210+
211+
for(std::size_t j = 0; j < tmp_result.size(); j++)
212+
{
213+
const std::size_t index = i * sub_width + j;
214+
INVARIANT(index < bv.size(), "bit index shall be within bounds");
215+
tmp_result[j] = bv[index];
216+
}
217+
218+
tmp_result = bv_utils.saturating_add_sub(tmp_result, tmp_op, subtract, rep);
219+
220+
INVARIANT(
221+
tmp_result.size() == sub_width,
222+
"applying the add/sub operation shall not change the bitwidth");
223+
224+
for(std::size_t j = 0; j < tmp_result.size(); j++)
225+
{
226+
const std::size_t index = i * sub_width + j;
227+
INVARIANT(index < bv.size(), "bit index shall be within bounds");
228+
bv[index] = tmp_result[j];
229+
}
230+
}
231+
232+
return bv;
233+
}

src/solvers/flattening/bv_utils.cpp

+70
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,76 @@ bvt bv_utilst::add_sub(const bvt &op0, const bvt &op1, literalt subtract)
360360
return result;
361361
}
362362

363+
bvt bv_utilst::saturating_add_sub(
364+
const bvt &op0,
365+
const bvt &op1,
366+
bool subtract,
367+
representationt rep)
368+
{
369+
PRECONDITION(op0.size() == op1.size());
370+
PRECONDITION(
371+
rep == representationt::SIGNED || rep == representationt::UNSIGNED);
372+
373+
literalt carry_in = const_literal(subtract);
374+
literalt carry_out;
375+
376+
bvt add_sub_result = op0;
377+
bvt tmp_op1 = subtract ? inverted(op1) : op1;
378+
379+
adder(add_sub_result, tmp_op1, carry_in, carry_out);
380+
381+
bvt result;
382+
result.reserve(add_sub_result.size());
383+
if(rep == representationt::UNSIGNED)
384+
{
385+
// An unsigned overflow has occurred when carry_out is not equal to
386+
// subtract: addition with a carry-out means an overflow beyond the maximum
387+
// representable value, subtraction without a carry-out means an underflow
388+
// below zero. For saturating arithmetic the former implies that all bits
389+
// should be set to 1, in the latter case all bits should be set to zero.
390+
for(const auto &literal : add_sub_result)
391+
{
392+
result.push_back(
393+
subtract ? prop.land(literal, carry_out)
394+
: prop.lor(literal, carry_out));
395+
}
396+
}
397+
else
398+
{
399+
// A signed overflow beyond the maximum representable value occurs when
400+
// adding two positive numbers and the wrap-around result being negative, or
401+
// when subtracting a negative from a positive number (and, again, the
402+
// result being negative).
403+
literalt overflow_to_max_int = prop.land(bvt{
404+
!sign_bit(op0),
405+
subtract ? sign_bit(op1) : !sign_bit(op1),
406+
sign_bit(add_sub_result)});
407+
// A signed underflow below the minimum representable value occurs when
408+
// adding two negative numbers and arriving at a positive result, or
409+
// subtracting a positive from a negative number (and, again, obtaining a
410+
// positive wrap-around result).
411+
literalt overflow_to_min_int = prop.land(bvt{
412+
sign_bit(op0),
413+
subtract ? !sign_bit(op1) : sign_bit(op1),
414+
!sign_bit(add_sub_result)});
415+
416+
// set all bits except for the sign bit
417+
PRECONDITION(!add_sub_result.empty());
418+
for(std::size_t i = 0; i < add_sub_result.size() - 1; ++i)
419+
{
420+
const auto &literal = add_sub_result[i];
421+
result.push_back(prop.land(
422+
prop.lor(overflow_to_max_int, literal), !overflow_to_min_int));
423+
}
424+
// finally add the sign bit
425+
result.push_back(prop.land(
426+
prop.lor(overflow_to_min_int, add_sub_result.back()),
427+
!overflow_to_max_int));
428+
}
429+
430+
return result;
431+
}
432+
363433
literalt bv_utilst::overflow_add(
364434
const bvt &op0, const bvt &op1, representationt rep)
365435
{

src/solvers/flattening/bv_utils.h

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ class bv_utilst
5757
const bvt &op1,
5858
bool subtract,
5959
representationt rep);
60+
bvt saturating_add_sub(
61+
const bvt &op0,
62+
const bvt &op1,
63+
bool subtract,
64+
representationt rep);
6065

6166
bvt add(const bvt &op0, const bvt &op1) { return add_sub(op0, op1, false); }
6267
bvt sub(const bvt &op0, const bvt &op1) { return add_sub(op0, op1, true); }

0 commit comments

Comments
 (0)