Skip to content

Commit 5a42754

Browse files
committed
New expression: find_first_set_exprt
Rather than ad-hoc handling __builtin_ffs (and its variants) in the C front-end, make find-first-set available across the code base.
1 parent 0e1865a commit 5a42754

File tree

13 files changed

+163
-61
lines changed

13 files changed

+163
-61
lines changed

regression/cbmc-library/__builtin_ffs-01/main.c renamed to regression/cbmc/__builtin_ffs-01/main.c

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ int __builtin_ffsl(long);
77
int __builtin_ffsll(long long);
88
#endif
99

10+
#ifdef _MSC_VER
11+
# define _Static_assert(x, m) static_assert(x, m)
12+
#endif
13+
1014
int __VERIFIER_nondet_int();
11-
long __VERIFIER_nondet_long();
12-
long long __VERIFIER_nondet_long_long();
1315

1416
// http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup
1517
static const int multiply_de_bruijn_bit_position[32] = {
@@ -18,14 +20,14 @@ static const int multiply_de_bruijn_bit_position[32] = {
1820

1921
int main()
2022
{
21-
assert(__builtin_ffs(0) == 0);
22-
assert(__builtin_ffs(-1) == 1);
23-
assert(__builtin_ffs(INT_MIN) == sizeof(int) * 8);
24-
assert(__builtin_ffsl(INT_MIN) == sizeof(int) * 8);
25-
assert(__builtin_ffsl(LONG_MIN) == sizeof(long) * 8);
26-
assert(__builtin_ffsll(INT_MIN) == sizeof(int) * 8);
27-
assert(__builtin_ffsll(LLONG_MIN) == sizeof(long long) * 8);
28-
assert(__builtin_ffs(INT_MAX) == 1);
23+
_Static_assert(__builtin_ffs(0) == 0, "");
24+
_Static_assert(__builtin_ffs(-1) == 1, "");
25+
_Static_assert(__builtin_ffs(INT_MIN) == sizeof(int) * 8, "");
26+
_Static_assert(__builtin_ffsl(INT_MIN) == sizeof(int) * 8, "");
27+
_Static_assert(__builtin_ffsl(LONG_MIN) == sizeof(long) * 8, "");
28+
_Static_assert(__builtin_ffsll(INT_MIN) == sizeof(int) * 8, "");
29+
_Static_assert(__builtin_ffsll(LLONG_MIN) == sizeof(long long) * 8, "");
30+
_Static_assert(__builtin_ffs(INT_MAX) == 1, "");
2931

3032
int i = __VERIFIER_nondet_int();
3133
__CPROVER_assume(i != 0);

src/ansi-c/c_typecheck_expr.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,6 +2820,24 @@ exprt c_typecheck_baset::do_special_functions(
28202820

28212821
return std::move(ctz);
28222822
}
2823+
else if(
2824+
identifier == "__builtin_ffs" || identifier == "__builtin_ffsl" ||
2825+
identifier == "__builtin_ffsll")
2826+
{
2827+
if(expr.arguments().size() != 1)
2828+
{
2829+
error().source_location = f_op.source_location();
2830+
error() << identifier << " expects one operand" << eom;
2831+
throw 0;
2832+
}
2833+
2834+
typecheck_function_call_arguments(expr);
2835+
2836+
find_first_set_exprt ffs{expr.arguments().front(), expr.type()};
2837+
ffs.add_source_location() = source_location;
2838+
2839+
return std::move(ffs);
2840+
}
28232841
else if(identifier==CPROVER_PREFIX "equal")
28242842
{
28252843
if(expr.arguments().size()!=2)

src/ansi-c/expr2c.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3823,6 +3823,7 @@ optionalt<std::string> expr2ct::convert_function(const exprt &src)
38233823
{ID_count_leading_zeros, "__builtin_clz"},
38243824
{ID_count_trailing_zeros, "__builtin_ctz"},
38253825
{ID_dynamic_object, "DYNAMIC_OBJECT"},
3826+
{ID_find_first_set, "__builtin_ffs"},
38263827
{ID_floatbv_div, "FLOAT/"},
38273828
{ID_floatbv_minus, "FLOAT-"},
38283829
{ID_floatbv_mult, "FLOAT*"},

src/ansi-c/library/gcc.c

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -33,57 +33,6 @@ inline void __sync_synchronize(void)
3333
#endif
3434
}
3535

36-
/* FUNCTION: __builtin_ffs */
37-
38-
int __builtin_clz(unsigned int x);
39-
40-
inline int __builtin_ffs(int x)
41-
{
42-
if(x == 0)
43-
return 0;
44-
45-
#pragma CPROVER check push
46-
#pragma CPROVER check disable "conversion"
47-
unsigned int u = (unsigned int)x;
48-
#pragma CPROVER check pop
49-
50-
return sizeof(int) * 8 - __builtin_clz(u & ~(u - 1));
51-
}
52-
53-
/* FUNCTION: __builtin_ffsl */
54-
55-
int __builtin_clzl(unsigned long x);
56-
57-
inline int __builtin_ffsl(long x)
58-
{
59-
if(x == 0)
60-
return 0;
61-
62-
#pragma CPROVER check push
63-
#pragma CPROVER check disable "conversion"
64-
unsigned long u = (unsigned long)x;
65-
#pragma CPROVER check pop
66-
67-
return sizeof(long) * 8 - __builtin_clzl(u & ~(u - 1));
68-
}
69-
70-
/* FUNCTION: __builtin_ffsll */
71-
72-
int __builtin_clzll(unsigned long long x);
73-
74-
inline int __builtin_ffsll(long long x)
75-
{
76-
if(x == 0)
77-
return 0;
78-
79-
#pragma CPROVER check push
80-
#pragma CPROVER check disable "conversion"
81-
unsigned long long u = (unsigned long long)x;
82-
#pragma CPROVER check pop
83-
84-
return sizeof(long long) * 8 - __builtin_clzll(u & ~(u - 1));
85-
}
86-
8736
/* FUNCTION: __atomic_test_and_set */
8837

8938
void __atomic_thread_fence(int memorder);

src/solvers/flattening/boolbv.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ bvt boolbvt::convert_bitvector(const exprt &expr)
216216
return convert_bv(
217217
simplify_expr(to_count_trailing_zeros_expr(expr).lower(), ns));
218218
}
219+
else if(expr.id() == ID_find_first_set)
220+
return convert_bv(simplify_expr(to_find_first_set_expr(expr).lower(), ns));
219221

220222
return conversion_failed(expr);
221223
}

src/solvers/smt2/smt2_conv.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,6 +2042,10 @@ void smt2_convt::convert_expr(const exprt &expr)
20422042
{
20432043
convert_expr(simplify_expr(to_count_trailing_zeros_expr(expr).lower(), ns));
20442044
}
2045+
else if(expr.id() == ID_find_first_set)
2046+
{
2047+
convert_expr(simplify_expr(to_find_first_set_expr(expr).lower(), ns));
2048+
}
20452049
else
20462050
INVARIANT_WITH_DIAGNOSTICS(
20472051
false,

src/util/bitvector_expr.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,20 @@ exprt count_trailing_zeros_exprt::lower() const
142142

143143
return typecast_exprt::conditional_cast(result, type());
144144
}
145+
146+
exprt find_first_set_exprt::lower() const
147+
{
148+
exprt x = op();
149+
const auto int_width = to_bitvector_type(x.type()).get_width();
150+
CHECK_RETURN(int_width >= 1);
151+
152+
// bitwidth(x) - clz(x & ~((unsigned)x - 1));
153+
const unsignedbv_typet ut{int_width};
154+
minus_exprt minus_one{typecast_exprt::conditional_cast(x, ut),
155+
from_integer(1, ut)};
156+
count_leading_zeros_exprt clz{bitand_exprt{
157+
x, bitnot_exprt{typecast_exprt::conditional_cast(minus_one, x.type())}}};
158+
minus_exprt result{from_integer(int_width, x.type()), clz.lower()};
159+
160+
return typecast_exprt::conditional_cast(result, type());
161+
}

src/util/bitvector_expr.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,4 +1008,81 @@ inline count_trailing_zeros_exprt &to_count_trailing_zeros_expr(exprt &expr)
10081008
return ret;
10091009
}
10101010

1011+
/// \brief Returns one plus the index of the least-significant one bit, or zero
1012+
/// if the operand is zero.
1013+
class find_first_set_exprt : public unary_exprt
1014+
{
1015+
public:
1016+
find_first_set_exprt(exprt _op, typet _type)
1017+
: unary_exprt(ID_find_first_set, std::move(_op), std::move(_type))
1018+
{
1019+
}
1020+
1021+
explicit find_first_set_exprt(const exprt &_op)
1022+
: find_first_set_exprt(_op, _op.type())
1023+
{
1024+
}
1025+
1026+
static void check(
1027+
const exprt &expr,
1028+
const validation_modet vm = validation_modet::INVARIANT)
1029+
{
1030+
DATA_CHECK(
1031+
vm,
1032+
expr.operands().size() == 1,
1033+
"unary expression must have a single operand");
1034+
DATA_CHECK(
1035+
vm,
1036+
can_cast_type<bitvector_typet>(to_unary_expr(expr).op().type()),
1037+
"operand must be of bitvector type");
1038+
}
1039+
1040+
static void validate(
1041+
const exprt &expr,
1042+
const namespacet &,
1043+
const validation_modet vm = validation_modet::INVARIANT)
1044+
{
1045+
check(expr, vm);
1046+
}
1047+
1048+
/// Lower a find_first_set_exprt to arithmetic and logic expressions.
1049+
/// \return Semantically equivalent expression
1050+
exprt lower() const;
1051+
};
1052+
1053+
template <>
1054+
inline bool can_cast_expr<find_first_set_exprt>(const exprt &base)
1055+
{
1056+
return base.id() == ID_find_first_set;
1057+
}
1058+
1059+
inline void validate_expr(const find_first_set_exprt &value)
1060+
{
1061+
validate_operands(value, 1, "find_first_set must have one operand");
1062+
}
1063+
1064+
/// \brief Cast an exprt to a \ref find_first_set_exprt
1065+
///
1066+
/// \a expr must be known to be \ref find_first_set_exprt.
1067+
///
1068+
/// \param expr: Source expression
1069+
/// \return Object of type \ref find_first_set_exprt
1070+
inline const find_first_set_exprt &to_find_first_set_expr(const exprt &expr)
1071+
{
1072+
PRECONDITION(expr.id() == ID_find_first_set);
1073+
const find_first_set_exprt &ret =
1074+
static_cast<const find_first_set_exprt &>(expr);
1075+
validate_expr(ret);
1076+
return ret;
1077+
}
1078+
1079+
/// \copydoc to_find_first_set_expr(const exprt &)
1080+
inline find_first_set_exprt &to_find_first_set_expr(exprt &expr)
1081+
{
1082+
PRECONDITION(expr.id() == ID_find_first_set);
1083+
find_first_set_exprt &ret = static_cast<find_first_set_exprt &>(expr);
1084+
validate_expr(ret);
1085+
return ret;
1086+
}
1087+
10111088
#endif // CPROVER_UTIL_BITVECTOR_EXPR_H

src/util/format_expr.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ static std::ostream &format_rec(std::ostream &os, const unary_exprt &src)
148148
os << "clz";
149149
else if(src.id() == ID_count_trailing_zeros)
150150
os << "ctz";
151+
else if(src.id() == ID_find_first_set)
152+
os << "ffs";
151153
else
152154
return os << src.pretty();
153155

src/util/irep_ids.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,7 @@ IREP_ID_TWO(vector_gt, vector->)
859859
IREP_ID_TWO(vector_lt, vector-<)
860860
IREP_ID_ONE(shuffle_vector)
861861
IREP_ID_ONE(count_trailing_zeros)
862+
IREP_ID_ONE(find_first_set)
862863

863864
// Projects depending on this code base that wish to extend the list of
864865
// available ids should provide a file local_irep_ids.def in their source tree

src/util/simplify_expr.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,27 @@ simplify_exprt::simplify_ctz(const count_trailing_zeros_exprt &expr)
197197
return from_integer(n_trailing_zeros, expr.type());
198198
}
199199

200+
simplify_exprt::resultt<>
201+
simplify_exprt::simplify_ffs(const find_first_set_exprt &expr)
202+
{
203+
const auto const_bits_opt = expr2bits(
204+
expr.op(),
205+
config.ansi_c.endianness == configt::ansi_ct::endiannesst::IS_LITTLE_ENDIAN,
206+
ns);
207+
208+
if(!const_bits_opt.has_value())
209+
return unchanged(expr);
210+
211+
// expr2bits generates a bit string starting with the least-significant bit
212+
std::size_t first_one_bit = const_bits_opt->find('1');
213+
if(first_one_bit == std::string::npos)
214+
first_one_bit = 0;
215+
else
216+
++first_one_bit;
217+
218+
return from_integer(first_one_bit, expr.type());
219+
}
220+
200221
/// Simplify String.endsWith function when arguments are constant
201222
/// \param expr: the expression to simplify
202223
/// \param ns: namespace
@@ -2523,6 +2544,10 @@ simplify_exprt::resultt<> simplify_exprt::simplify_node(exprt node)
25232544
{
25242545
r = simplify_ctz(to_count_trailing_zeros_expr(expr));
25252546
}
2547+
else if(expr.id() == ID_find_first_set)
2548+
{
2549+
r = simplify_ffs(to_find_first_set_expr(expr));
2550+
}
25262551
else if(expr.id() == ID_function_application)
25272552
{
25282553
r = simplify_function_application(to_function_application_expr(expr));

src/util/simplify_expr_class.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class div_exprt;
4444
class exprt;
4545
class extractbit_exprt;
4646
class extractbits_exprt;
47+
class find_first_set_exprt;
4748
class floatbv_typecast_exprt;
4849
class function_application_exprt;
4950
class ieee_float_op_exprt;
@@ -210,6 +211,9 @@ class simplify_exprt
210211
/// Try to simplify count-trailing-zeros to a constant expression.
211212
NODISCARD resultt<> simplify_ctz(const count_trailing_zeros_exprt &);
212213

214+
/// Try to simplify find-first-set to a constant expression.
215+
NODISCARD resultt<> simplify_ffs(const find_first_set_exprt &);
216+
213217
// auxiliary
214218
bool simplify_if_implies(
215219
exprt &expr, const exprt &cond, bool truth, bool &new_truth);

0 commit comments

Comments
 (0)