https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126476
Bug ID: 126476
Summary: Wrong code with bitint shifts
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Keywords: wrong-code
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: ktkachov at gcc dot gnu.org
CC: jakub at gcc dot gnu.org
Target Milestone: ---
__attribute__((noipa)) int
f (unsigned _BitInt(4) n)
{
return ((1ULL << n) & (1ULL << 20)) != 0;
}
__attribute__((noipa)) int
g (unsigned _BitInt(4) n)
{
return (((1ULL << 40) >> n) & (1ULL << 20)) != 0;
}
int
main (void)
{
for (unsigned i = 0; i < 16; i++)
{
unsigned _BitInt(4) n = (unsigned _BitInt(4)) i;
if (f (n) != 0)
__builtin_abort ();
if (g (n) != 0)
__builtin_abort ();
}
return 0;
}
aborts with GCC but passes with Clang.
(for cmp (ne eq)
icmp (eq ne)
(simplify
(cmp (bit_and (lshift integer_pow2p@1 @0) integer_pow2p@2)
integer_zerop)
(with { int c1 = wi::clz (wi::to_wide (@1));
int c2 = wi::clz (wi::to_wide (@2)); }
(if (c1 < c2)
{ constant_boolean_node (cmp == NE_EXPR ? false : true, type); }
(icmp @0 { build_int_cst (TREE_TYPE (@0), c1 - c2); }))))
((C << x) & D) != 0 holds exactly when x equals the bit-position distance
c1 - c2, and the rule materialises that distance with
build_int_cst (TREE_TYPE (@0), c1 - c2). TREE_TYPE (@0) is the type of the
*shift count*, which nothing requires to be able to hold c1 - c2.
build_int_cst truncates modulo 2^precision silently.
When the distance does not fit but its residue is a legal shift count, an
expression that is false for every valid x becomes a comparison that is true
for one of them. The rule already has the right answer to hand: an
unrepresentable distance means the AND is unconditionally zero, which is
exactly the c1 < c2 branch. A fits-in-type check routing there is missing.
Reachable because C23 does not apply the integer promotions to bit-precise
types, so a _BitInt shift count keeps its narrow type into GIMPLE.
Here c1 = clz (1) = 63 and c2 = clz (1 << 20) = 43, so the distance is 20,
which does not fit in unsigned _BitInt(4); 20 mod 16 = 4, and the fold
emits `n == 4`. But (1ULL << 4) & (1ULL << 20) is 0.
Every n in [0, 15] is a legal count for a 64-bit shift, so the program has
no undefined behaviour.