Skip to content

Commit c9688d8

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 43b43ee commit c9688d8

File tree

13 files changed

+251
-7
lines changed

13 files changed

+251
-7
lines changed
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: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
/// evaluates 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

0 commit comments

Comments
 (0)