Skip to content

Commit 990c3df

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.
1 parent 3f175c9 commit 990c3df

File tree

13 files changed

+338
-1
lines changed

13 files changed

+338
-1
lines changed
Lines changed: 17 additions & 0 deletions
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+
}
Lines changed: 8 additions & 0 deletions
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

src/ansi-c/c_typecheck_base.h

Lines changed: 2 additions & 0 deletions
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

Lines changed: 41 additions & 1 deletion
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(
@@ -3523,7 +3561,9 @@ void c_typecheck_baset::typecheck_expr_binary_arithmetic(exprt &expr)
35233561
}
35243562
}
35253563
}
3526-
else if(expr.id()==ID_mod)
3564+
else if(
3565+
expr.id() == ID_mod || expr.id() == ID_saturating_minus ||
3566+
expr.id() == ID_saturating_plus)
35273567
{
35283568
if(type0==type1)
35293569
{

src/ansi-c/expr2c.cpp

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 89 additions & 0 deletions
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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,61 @@ 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+
for(const auto &literal : add_sub_result)
386+
{
387+
result.push_back(
388+
subtract ? prop.land(literal, carry_out)
389+
: prop.lor(literal, carry_out));
390+
}
391+
}
392+
else
393+
{
394+
literalt overflow_to_max_int = prop.land(bvt{
395+
!sign_bit(op0),
396+
subtract ? sign_bit(op1) : !sign_bit(op1),
397+
sign_bit(add_sub_result)});
398+
literalt overflow_to_min_int = prop.land(bvt{
399+
sign_bit(op0),
400+
subtract ? !sign_bit(op1) : sign_bit(op1),
401+
!sign_bit(add_sub_result)});
402+
403+
PRECONDITION(!add_sub_result.empty());
404+
for(std::size_t i = 0; i < add_sub_result.size() - 1; ++i)
405+
{
406+
const auto &literal = add_sub_result[i];
407+
result.push_back(prop.land(
408+
prop.lor(overflow_to_max_int, literal), !overflow_to_min_int));
409+
}
410+
result.push_back(prop.land(
411+
prop.lor(overflow_to_min_int, add_sub_result.back()),
412+
!overflow_to_max_int));
413+
}
414+
415+
return result;
416+
}
417+
363418
literalt bv_utilst::overflow_add(
364419
const bvt &op0, const bvt &op1, representationt rep)
365420
{

src/solvers/flattening/bv_utils.h

Lines changed: 5 additions & 0 deletions
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); }

src/util/bitvector_expr.h

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,4 +1056,103 @@ inline bitreverse_exprt &to_bitreverse_expr(exprt &expr)
10561056
return ret;
10571057
}
10581058

1059+
/// \brief The saturating plus expression
1060+
class saturating_plus_exprt : public binary_exprt
1061+
{
1062+
public:
1063+
saturating_plus_exprt(exprt _lhs, exprt _rhs)
1064+
: binary_exprt(std::move(_lhs), ID_saturating_plus, std::move(_rhs))
1065+
{
1066+
}
1067+
1068+
saturating_plus_exprt(exprt _lhs, exprt _rhs, typet _type)
1069+
: binary_exprt(
1070+
std::move(_lhs),
1071+
ID_saturating_plus,
1072+
std::move(_rhs),
1073+
std::move(_type))
1074+
{
1075+
}
1076+
};
1077+
1078+
template <>
1079+
inline bool can_cast_expr<saturating_plus_exprt>(const exprt &base)
1080+
{
1081+
return base.id() == ID_saturating_plus;
1082+
}
1083+
1084+
inline void validate_expr(const saturating_plus_exprt &value)
1085+
{
1086+
validate_operands(value, 2, "saturating plus must have two operands");
1087+
}
1088+
1089+
/// \brief Cast an exprt to a \ref saturating_plus_exprt
1090+
///
1091+
/// \a expr must be known to be \ref saturating_plus_exprt.
1092+
///
1093+
/// \param expr: Source expression
1094+
/// \return Object of type \ref saturating_plus_exprt
1095+
inline const saturating_plus_exprt &to_saturating_plus_expr(const exprt &expr)
1096+
{
1097+
PRECONDITION(expr.id() == ID_saturating_plus);
1098+
const saturating_plus_exprt &ret =
1099+
static_cast<const saturating_plus_exprt &>(expr);
1100+
validate_expr(ret);
1101+
return ret;
1102+
}
1103+
1104+
/// \copydoc to_saturating_plus_expr(const exprt &)
1105+
inline saturating_plus_exprt &to_saturating_plus_expr(exprt &expr)
1106+
{
1107+
PRECONDITION(expr.id() == ID_saturating_plus);
1108+
saturating_plus_exprt &ret = static_cast<saturating_plus_exprt &>(expr);
1109+
validate_expr(ret);
1110+
return ret;
1111+
}
1112+
1113+
/// \brief Saturating subtraction expression.
1114+
class saturating_minus_exprt : public binary_exprt
1115+
{
1116+
public:
1117+
saturating_minus_exprt(exprt _lhs, exprt _rhs)
1118+
: binary_exprt(std::move(_lhs), ID_saturating_minus, std::move(_rhs))
1119+
{
1120+
}
1121+
};
1122+
1123+
template <>
1124+
inline bool can_cast_expr<saturating_minus_exprt>(const exprt &base)
1125+
{
1126+
return base.id() == ID_saturating_minus;
1127+
}
1128+
1129+
inline void validate_expr(const saturating_minus_exprt &value)
1130+
{
1131+
validate_operands(value, 2, "saturating minus must have two operands");
1132+
}
1133+
1134+
/// \brief Cast an exprt to a \ref saturating_minus_exprt
1135+
///
1136+
/// \a expr must be known to be \ref saturating_minus_exprt.
1137+
///
1138+
/// \param expr: Source expression
1139+
/// \return Object of type \ref saturating_minus_exprt
1140+
inline const saturating_minus_exprt &to_saturating_minus_expr(const exprt &expr)
1141+
{
1142+
PRECONDITION(expr.id() == ID_saturating_minus);
1143+
const saturating_minus_exprt &ret =
1144+
static_cast<const saturating_minus_exprt &>(expr);
1145+
validate_expr(ret);
1146+
return ret;
1147+
}
1148+
1149+
/// \copydoc to_saturating_minus_expr(const exprt &)
1150+
inline saturating_minus_exprt &to_saturating_minus_expr(exprt &expr)
1151+
{
1152+
PRECONDITION(expr.id() == ID_saturating_minus);
1153+
saturating_minus_exprt &ret = static_cast<saturating_minus_exprt &>(expr);
1154+
validate_expr(ret);
1155+
return ret;
1156+
}
1157+
10591158
#endif // CPROVER_UTIL_BITVECTOR_EXPR_H

0 commit comments

Comments
 (0)