Skip to content

Commit 730c461

Browse files
committed
Implement __builtin_ctz{,l,ll} via count_trailing_zeros_exprt
__builtin_ctz returns the number of trailing zeros. Just like ffs and clz, introduce a new bitvector expression.
1 parent 1c54a96 commit 730c461

File tree

14 files changed

+253
-9
lines changed

14 files changed

+253
-9
lines changed

regression/cbmc/__builtin_clz-01/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CORE
22
main.c
33
--bounds-check
4-
^\[main.bit_count.\d+\] line 61 count leading zeros argument in __builtin_clz\(0u\): FAILURE$
4+
^\[main.bit_count.\d+\] line 61 count leading zeros is undefined for value zero in __builtin_clz\(0u\): FAILURE$
55
^\*\* 1 of \d+ failed
66
^VERIFICATION FAILED$
77
^EXIT=10$
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <assert.h>
2+
#include <limits.h>
3+
4+
#ifndef __GNUC__
5+
int __builtin_ctz(unsigned int);
6+
int __builtin_ctzl(unsigned long);
7+
int __builtin_ctzll(unsigned long long);
8+
#endif
9+
10+
#ifdef _MSC_VER
11+
# define _Static_assert(x, m) static_assert(x, m)
12+
#endif
13+
14+
unsigned __VERIFIER_nondet_unsigned();
15+
16+
// http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup
17+
static const int multiply_de_bruijn_bit_position[32] = {
18+
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
19+
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
20+
21+
int main()
22+
{
23+
_Static_assert(__builtin_ctz(1) == 0, "");
24+
_Static_assert(__builtin_ctz(UINT_MAX) == 0, "");
25+
_Static_assert(
26+
__builtin_ctz(1U << (sizeof(unsigned) * 8 - 1)) == sizeof(unsigned) * 8 - 1,
27+
"");
28+
_Static_assert(
29+
__builtin_ctzl(1UL << (sizeof(unsigned) * 8 - 1)) ==
30+
sizeof(unsigned) * 8 - 1,
31+
"");
32+
_Static_assert(
33+
__builtin_ctzll(1ULL << (sizeof(unsigned) * 8 - 1)) ==
34+
sizeof(unsigned) * 8 - 1,
35+
"");
36+
37+
unsigned u = __VERIFIER_nondet_unsigned();
38+
__CPROVER_assume(u != 0);
39+
__CPROVER_assume(sizeof(u) == 4);
40+
__CPROVER_assume(u > INT_MIN);
41+
assert(
42+
multiply_de_bruijn_bit_position
43+
[((unsigned)((u & -u) * 0x077CB531U)) >> 27] == __builtin_ctz(u));
44+
45+
// a failing assertion should be generated as __builtin_ctz is undefined for 0
46+
__builtin_ctz(0U);
47+
48+
return 0;
49+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--bounds-check
4+
^\[main.bit_count.\d+\] line 46 count trailing zeros is undefined for value zero in __builtin_ctz\(0u\): FAILURE$
5+
^\*\* 1 of \d+ failed
6+
^VERIFICATION FAILED$
7+
^EXIT=10$
8+
^SIGNAL=0$
9+
--
10+
^warning: ignoring

src/analyses/goto_check.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class goto_checkt
174174

175175
void bounds_check(const exprt &, const guardt &);
176176
void bounds_check_index(const index_exprt &, const guardt &);
177-
void bounds_check_clz(const count_leading_zeros_exprt &, const guardt &);
177+
void bounds_check_bit_count(const unary_exprt &, const guardt &);
178178
void div_by_zero_check(const div_exprt &, const guardt &);
179179
void mod_by_zero_check(const mod_exprt &, const guardt &);
180180
void mod_overflow_check(const mod_exprt &, const guardt &);
@@ -1336,8 +1336,11 @@ void goto_checkt::bounds_check(const exprt &expr, const guardt &guard)
13361336

13371337
if(expr.id() == ID_index)
13381338
bounds_check_index(to_index_expr(expr), guard);
1339-
else if(expr.id() == ID_count_leading_zeros)
1340-
bounds_check_clz(to_count_leading_zeros_expr(expr), guard);
1339+
else if(
1340+
expr.id() == ID_count_leading_zeros || expr.id() == ID_count_trailing_zeros)
1341+
{
1342+
bounds_check_bit_count(to_unary_expr(expr), guard);
1343+
}
13411344
}
13421345

13431346
void goto_checkt::bounds_check_index(
@@ -1519,13 +1522,22 @@ void goto_checkt::bounds_check_index(
15191522
}
15201523
}
15211524

1522-
void goto_checkt::bounds_check_clz(
1523-
const count_leading_zeros_exprt &expr,
1525+
void goto_checkt::bounds_check_bit_count(
1526+
const unary_exprt &expr,
15241527
const guardt &guard)
15251528
{
1529+
std::string name;
1530+
1531+
if(expr.id() == ID_count_leading_zeros)
1532+
name = "leading";
1533+
else if(expr.id() == ID_count_trailing_zeros)
1534+
name = "trailing";
1535+
else
1536+
PRECONDITION(false);
1537+
15261538
add_guarded_property(
15271539
notequal_exprt{expr.op(), from_integer(0, expr.op().type())},
1528-
"count leading zeros argument",
1540+
"count " + name + " zeros is undefined for value zero",
15291541
"bit count",
15301542
expr.find_source_location(),
15311543
expr,
@@ -1788,7 +1800,8 @@ void goto_checkt::check_rec(const exprt &expr, guardt &guard)
17881800
{
17891801
pointer_primitive_check(expr, guard);
17901802
}
1791-
else if(expr.id() == ID_count_leading_zeros)
1803+
else if(
1804+
expr.id() == ID_count_leading_zeros || expr.id() == ID_count_trailing_zeros)
17921805
{
17931806
bounds_check(expr, guard);
17941807
}

src/ansi-c/c_typecheck_expr.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2727,6 +2727,25 @@ exprt c_typecheck_baset::do_special_functions(
27272727

27282728
return std::move(clz);
27292729
}
2730+
else if(
2731+
identifier == "__builtin_ctz" || identifier == "__builtin_ctzl" ||
2732+
identifier == "__builtin_ctzll")
2733+
{
2734+
if(expr.arguments().size() != 1)
2735+
{
2736+
error().source_location = f_op.source_location();
2737+
error() << identifier << " expects one operand" << eom;
2738+
throw 0;
2739+
}
2740+
2741+
typecheck_function_call_arguments(expr);
2742+
2743+
count_trailing_zeros_exprt ctz{
2744+
expr.arguments().front(), false, expr.type()};
2745+
ctz.add_source_location() = source_location;
2746+
2747+
return std::move(ctz);
2748+
}
27302749
else if(identifier==CPROVER_PREFIX "equal")
27312750
{
27322751
if(expr.arguments().size()!=2)

src/ansi-c/expr2c.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3822,6 +3822,7 @@ optionalt<std::string> expr2ct::convert_function(const exprt &src)
38223822
{ID_complex_real, "__real__"},
38233823
{ID_concatenation, "CONCATENATION"},
38243824
{ID_count_leading_zeros, "__builtin_clz"},
3825+
{ID_count_trailing_zeros, "__builtin_ctz"},
38253826
{ID_dynamic_object, "DYNAMIC_OBJECT"},
38263827
{ID_floatbv_div, "FLOAT/"},
38273828
{ID_floatbv_minus, "FLOAT-"},

src/solvers/flattening/boolbv.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ bvt boolbvt::convert_bitvector(const exprt &expr)
218218
return convert_bv(
219219
simplify_expr(to_count_leading_zeros_expr(expr).lower(), ns));
220220
}
221+
else if(expr.id() == ID_count_trailing_zeros)
222+
{
223+
return convert_bv(
224+
simplify_expr(to_count_trailing_zeros_expr(expr).lower(), ns));
225+
}
221226

222227
return conversion_failed(expr);
223228
}

src/solvers/smt2/smt2_conv.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,6 +2035,10 @@ void smt2_convt::convert_expr(const exprt &expr)
20352035
{
20362036
convert_expr(simplify_expr(to_count_leading_zeros_expr(expr).lower(), ns));
20372037
}
2038+
else if(expr.id() == ID_count_trailing_zeros)
2039+
{
2040+
convert_expr(simplify_expr(to_count_trailing_zeros_expr(expr).lower(), ns));
2041+
}
20382042
else
20392043
INVARIANT_WITH_DIAGNOSTICS(
20402044
false,

src/util/bitvector_expr.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,20 @@ exprt count_leading_zeros_exprt::lower() const
125125
bitnot_exprt{typecast_exprt::conditional_cast(x, op().type())}, type()}
126126
.lower();
127127
}
128+
129+
exprt count_trailing_zeros_exprt::lower() const
130+
{
131+
exprt x = op();
132+
const auto int_width = to_bitvector_type(x.type()).get_width();
133+
CHECK_RETURN(int_width >= 1);
134+
135+
// popcount(x ^ ((unsigned)x - 1)) - 1
136+
const unsignedbv_typet ut{int_width};
137+
minus_exprt minus_one{typecast_exprt::conditional_cast(x, ut),
138+
from_integer(1, ut)};
139+
popcount_exprt popcount{
140+
bitxor_exprt{x, typecast_exprt::conditional_cast(minus_one, x.type())}};
141+
minus_exprt result{popcount.lower(), from_integer(1, x.type())};
142+
143+
return typecast_exprt::conditional_cast(result, type());
144+
}

src/util/bitvector_expr.h

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ inline unary_overflow_exprt &to_unary_overflow_expr(exprt &expr)
825825
/// \brief The count leading zeros (counting the number of zero bits starting
826826
/// from the most-significant bit) expression. When \c zero_permitted is set to
827827
/// false, goto_checkt must generate an assertion that the operand does not
828-
/// evaluates to zero. The result is always defined, even for zero (where the
828+
/// evaluate to zero. The result is always defined, even for zero (where the
829829
/// result is the bit width).
830830
class count_leading_zeros_exprt : public unary_exprt
831831
{
@@ -915,4 +915,97 @@ inline count_leading_zeros_exprt &to_count_leading_zeros_expr(exprt &expr)
915915
return ret;
916916
}
917917

918+
/// \brief The count trailing zeros (counting the number of zero bits starting
919+
/// from the least-significant bit) expression. When \c zero_permitted is set to
920+
/// false, goto_checkt must generate an assertion that the operand does not
921+
/// evaluate to zero. The result is always defined, even for zero (where the
922+
/// result is the bit width).
923+
class count_trailing_zeros_exprt : public unary_exprt
924+
{
925+
public:
926+
count_trailing_zeros_exprt(exprt _op, bool _zero_permitted, typet _type)
927+
: unary_exprt(ID_count_trailing_zeros, std::move(_op), std::move(_type))
928+
{
929+
zero_permitted(_zero_permitted);
930+
}
931+
932+
explicit count_trailing_zeros_exprt(const exprt &_op)
933+
: count_trailing_zeros_exprt(_op, true, _op.type())
934+
{
935+
}
936+
937+
bool zero_permitted() const
938+
{
939+
return !get_bool(ID_C_bounds_check);
940+
}
941+
942+
void zero_permitted(bool value)
943+
{
944+
set(ID_C_bounds_check, !value);
945+
}
946+
947+
static void check(
948+
const exprt &expr,
949+
const validation_modet vm = validation_modet::INVARIANT)
950+
{
951+
DATA_CHECK(
952+
vm,
953+
expr.operands().size() == 1,
954+
"unary expression must have a single operand");
955+
DATA_CHECK(
956+
vm,
957+
can_cast_type<bitvector_typet>(to_unary_expr(expr).op().type()),
958+
"operand must be of bitvector type");
959+
}
960+
961+
static void validate(
962+
const exprt &expr,
963+
const namespacet &,
964+
const validation_modet vm = validation_modet::INVARIANT)
965+
{
966+
check(expr, vm);
967+
}
968+
969+
/// Lower a count_trailing_zeros_exprt to arithmetic and logic expressions.
970+
/// \return Semantically equivalent expression
971+
exprt lower() const;
972+
};
973+
974+
template <>
975+
inline bool can_cast_expr<count_trailing_zeros_exprt>(const exprt &base)
976+
{
977+
return base.id() == ID_count_trailing_zeros;
978+
}
979+
980+
inline void validate_expr(const count_trailing_zeros_exprt &value)
981+
{
982+
validate_operands(value, 1, "count_trailing_zeros must have one operand");
983+
}
984+
985+
/// \brief Cast an exprt to a \ref count_trailing_zeros_exprt
986+
///
987+
/// \a expr must be known to be \ref count_trailing_zeros_exprt.
988+
///
989+
/// \param expr: Source expression
990+
/// \return Object of type \ref count_trailing_zeros_exprt
991+
inline const count_trailing_zeros_exprt &
992+
to_count_trailing_zeros_expr(const exprt &expr)
993+
{
994+
PRECONDITION(expr.id() == ID_count_trailing_zeros);
995+
const count_trailing_zeros_exprt &ret =
996+
static_cast<const count_trailing_zeros_exprt &>(expr);
997+
validate_expr(ret);
998+
return ret;
999+
}
1000+
1001+
/// \copydoc to_count_trailing_zeros_expr(const exprt &)
1002+
inline count_trailing_zeros_exprt &to_count_trailing_zeros_expr(exprt &expr)
1003+
{
1004+
PRECONDITION(expr.id() == ID_count_trailing_zeros);
1005+
count_trailing_zeros_exprt &ret =
1006+
static_cast<count_trailing_zeros_exprt &>(expr);
1007+
validate_expr(ret);
1008+
return ret;
1009+
}
1010+
9181011
#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
@@ -153,6 +153,8 @@ static std::ostream &format_rec(std::ostream &os, const unary_exprt &src)
153153
os << '-';
154154
else if(src.id() == ID_count_leading_zeros)
155155
os << "clz";
156+
else if(src.id() == ID_count_trailing_zeros)
157+
os << "ctz";
156158
else
157159
return os << src.pretty();
158160

src/util/irep_ids.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,7 @@ IREP_ID_TWO(vector_le, vector-<=)
853853
IREP_ID_TWO(vector_gt, vector->)
854854
IREP_ID_TWO(vector_lt, vector-<)
855855
IREP_ID_ONE(shuffle_vector)
856+
IREP_ID_ONE(count_trailing_zeros)
856857

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

src/util/simplify_expr.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,28 @@ simplify_exprt::simplify_clz(const count_leading_zeros_exprt &expr)
172172
return from_integer(n_leading_zeros, expr.type());
173173
}
174174

175+
simplify_exprt::resultt<>
176+
simplify_exprt::simplify_ctz(const count_trailing_zeros_exprt &expr)
177+
{
178+
const auto const_bits_opt = expr2bits(
179+
expr.op(), byte_extract_id() == ID_byte_extract_little_endian, ns);
180+
181+
if(!const_bits_opt.has_value())
182+
return unchanged(expr);
183+
184+
// expr2bits generates a bit string starting with the least-significant bit
185+
std::size_t n_trailing_zeros = const_bits_opt->find('1');
186+
if(n_trailing_zeros == std::string::npos)
187+
{
188+
if(!expr.zero_permitted())
189+
return unchanged(expr);
190+
191+
n_trailing_zeros = const_bits_opt->size();
192+
}
193+
194+
return from_integer(n_trailing_zeros, expr.type());
195+
}
196+
175197
/// Simplify String.endsWith function when arguments are constant
176198
/// \param expr: the expression to simplify
177199
/// \param ns: namespace
@@ -2421,6 +2443,10 @@ simplify_exprt::resultt<> simplify_exprt::simplify_node(exprt node)
24212443
{
24222444
r = simplify_clz(to_count_leading_zeros_expr(expr));
24232445
}
2446+
else if(expr.id() == ID_count_trailing_zeros)
2447+
{
2448+
r = simplify_ctz(to_count_trailing_zeros_expr(expr));
2449+
}
24242450
else if(expr.id() == ID_function_application)
24252451
{
24262452
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
@@ -38,6 +38,7 @@ class byte_extract_exprt;
3838
class byte_update_exprt;
3939
class concatenation_exprt;
4040
class count_leading_zeros_exprt;
41+
class count_trailing_zeros_exprt;
4142
class dereference_exprt;
4243
class div_exprt;
4344
class exprt;
@@ -206,6 +207,9 @@ class simplify_exprt
206207
/// Try to simplify count-leading-zeros to a constant expression.
207208
NODISCARD resultt<> simplify_clz(const count_leading_zeros_exprt &);
208209

210+
/// Try to simplify count-trailing-zeros to a constant expression.
211+
NODISCARD resultt<> simplify_ctz(const count_trailing_zeros_exprt &);
212+
209213
// auxiliary
210214
bool simplify_if_implies(
211215
exprt &expr, const exprt &cond, bool truth, bool &new_truth);

0 commit comments

Comments
 (0)