bitintlower: Fix up bswapg/bitreverseg lowering [PR127669]
Checks
Commit Message
Hi!
The following testcase is miscompiled for 2 reasons.
One missed optimization is that fold_stmt_1 doesn't seem to attempt to
use fold_const_call for internal functions into whose argument a constant
is propagated. For normal builtins, I think fold_stmt_1 -> gimple_fold_call
-> gimple_fold_builtin -> fold_call_stmt -> fold_builtin_n ->
fold_builtin_{1,2,3} -> fold_const_call optimizes it, but for internal
fns we just have selected set of optimizations we do on them but
fold_const_call isn't among those. That would be certainly nice to get
fixed, but this patch doesn't do that.
The other problem is forgotten flag. Because of the above missed
optimization we end up with .BITREVERSE (1) call before bitintlower, where
that 1 is unsigned _BitInt(129). If there is a partial limb (like in
this case 1 bit), we emit handling of the result partial limb in straight
line code (but that actually reads some bits from the least significant limb
of the function argument), and then have a loop to handle the remaining
limbs. We use there handle_operand with an index which is not constant
in the loop (obviously) and using this read any limbs in the argument in
reverse order, including the partial limb.
Now, handle_operand has an optimization, for most of the lowerings we have
a guarantee that the partial limb is accessed only in straight line code with
a constant index and e.g. for constants reading the partial limb in that
case simply folds into the constant value of that partial limb. So, we
optimize and emit into .rodata only fewer limbs (all but the partial one)
unless it is one of the even more optimized ones (where we treat 0, all ones
or values fitting into fewer limbs by always using a constant or by
extending at runtime). The exception to this has been the LSHIFT_EXPR
lowering and we have the m_var_msb flag to tell we need all the bits
including the partial limb. Given the way .BSWAP/.BITREVERSE expansion
works, we need that flag for those too.
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
2026-10-01 Jakub Jelinek <jakub@redhat.com>
PR middle-end/127669
* gimple-lower-bitint.cc (bitint_large_huge::lower_bswap_bitreverse):
Set m_var_msb to true.
* gcc.dg/torture/bitint-110.c: New test.
Jakub
Comments
On Thu, 1 Oct 2026, Jakub Jelinek wrote:
> Hi!
>
> The following testcase is miscompiled for 2 reasons.
>
> One missed optimization is that fold_stmt_1 doesn't seem to attempt to
> use fold_const_call for internal functions into whose argument a constant
> is propagated. For normal builtins, I think fold_stmt_1 -> gimple_fold_call
> -> gimple_fold_builtin -> fold_call_stmt -> fold_builtin_n ->
> fold_builtin_{1,2,3} -> fold_const_call optimizes it, but for internal
> fns we just have selected set of optimizations we do on them but
> fold_const_call isn't among those. That would be certainly nice to get
> fixed, but this patch doesn't do that.
Indeed, but the following should already do it:
/* Dispatch to pattern-based folding. */
if (!inplace
|| is_gimple_assign (stmt)
|| gimple_code (stmt) == GIMPLE_COND)
{
gimple_seq seq = NULL;
gimple_match_op res_op;
if (gimple_simplify (stmt, &res_op, inplace ? NULL : &seq,
valueize, valueize)
&& replace_stmt_with_simplification (gsi, &res_op, &seq,
inplace,
dce_worklist))
changed = true;
via
/* Helper that matches and simplifies the toplevel result from
a gimple_simplify run (where we don't want to build
a stmt in case it's used in in-place folding). Replaces
RES_OP with a simplified and/or canonicalized result and
returns whether any change was made. */
static bool
gimple_resimplify1 (gimple_seq *seq, gimple_match_op *res_op,
tree (*valueize)(tree))
{
if (constant_for_folding (res_op->ops[0]))
{
tree tem = NULL_TREE;
if (res_op->code.is_tree_code ())
{
auto code = tree_code (res_op->code);
if (IS_EXPR_CODE_CLASS (TREE_CODE_CLASS (code))
&& TREE_CODE_LENGTH (code) == 1)
tem = const_unop (code, res_op->type, res_op->ops[0]);
}
else
tem = fold_const_call (combined_fn (res_op->code), res_op->type,
res_op->ops[0]);
> The other problem is forgotten flag. Because of the above missed
> optimization we end up with .BITREVERSE (1) call before bitintlower, where
> that 1 is unsigned _BitInt(129). If there is a partial limb (like in
> this case 1 bit), we emit handling of the result partial limb in straight
> line code (but that actually reads some bits from the least significant limb
> of the function argument), and then have a loop to handle the remaining
> limbs. We use there handle_operand with an index which is not constant
> in the loop (obviously) and using this read any limbs in the argument in
> reverse order, including the partial limb.
> Now, handle_operand has an optimization, for most of the lowerings we have
> a guarantee that the partial limb is accessed only in straight line code with
> a constant index and e.g. for constants reading the partial limb in that
> case simply folds into the constant value of that partial limb. So, we
> optimize and emit into .rodata only fewer limbs (all but the partial one)
> unless it is one of the even more optimized ones (where we treat 0, all ones
> or values fitting into fewer limbs by always using a constant or by
> extending at runtime). The exception to this has been the LSHIFT_EXPR
> lowering and we have the m_var_msb flag to tell we need all the bits
> including the partial limb. Given the way .BSWAP/.BITREVERSE expansion
> works, we need that flag for those too.
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
OK.
> 2026-10-01 Jakub Jelinek <jakub@redhat.com>
>
> PR middle-end/127669
> * gimple-lower-bitint.cc (bitint_large_huge::lower_bswap_bitreverse):
> Set m_var_msb to true.
>
> * gcc.dg/torture/bitint-110.c: New test.
>
> --- a/gcc/gimple-lower-bitint.cc 2026-09-18 11:03:21.676039449 +0200
> +++ b/gcc/gimple-lower-bitint.cc 2026-09-30 14:29:15.870162767 +0200
> @@ -6172,6 +6172,9 @@ bitint_large_huge::lower_bswap_bitrevers
> unsigned prec = TYPE_PRECISION (type);
> tree p = build_int_cst (sizetype,
> prec / limb_prec - (prec % limb_prec == 0));
> + /* For .BSWAP/.BITREVERSE, we can use handle_operand with non-INTEGER_CST
> + idx even to access the most significant partial limb. */
> + m_var_msb = true;
> /* For IFN .BSWAP or .BITREVERSE and
> FN corresponding __builtin_bswapN or __builtin_bitreverseN where N
> is limb_prec, lower
> --- a/gcc/testsuite/gcc.dg/torture/bitint-110.c 2026-09-30 14:29:49.463717815 +0200
> +++ b/gcc/testsuite/gcc.dg/torture/bitint-110.c 2026-09-30 14:31:29.115397910 +0200
> @@ -0,0 +1,30 @@
> +/* PR middle-end/127669 */
> +/* { dg-do run { target bitint } } */
> +
> +#if __BITINT_MAXWIDTH__ >= 129
> +typedef unsigned _BitInt(129) T;
> +
> +[[gnu::noipa]] static T
> +foo (T v)
> +{
> + return v;
> +}
> +
> +static T
> +bar ()
> +{
> + return 1;
> +}
> +#endif
> +
> +int
> +main ()
> +{
> +#if __BITINT_MAXWIDTH__ >= 129
> + T x = __builtin_bitreverseg ((T) 1);
> + T y = __builtin_bitreverseg (bar ());
> + T z = __builtin_bitreverseg (foo (1));
> + if (x != y || x != z)
> + __builtin_abort ();
> +#endif
> +}
>
> Jakub
>
>
On Thu, 1 Oct 2026, Richard Biener wrote:
> On Thu, 1 Oct 2026, Jakub Jelinek wrote:
>
> > Hi!
> >
> > The following testcase is miscompiled for 2 reasons.
> >
> > One missed optimization is that fold_stmt_1 doesn't seem to attempt to
> > use fold_const_call for internal functions into whose argument a constant
> > is propagated. For normal builtins, I think fold_stmt_1 -> gimple_fold_call
> > -> gimple_fold_builtin -> fold_call_stmt -> fold_builtin_n ->
> > fold_builtin_{1,2,3} -> fold_const_call optimizes it, but for internal
> > fns we just have selected set of optimizations we do on them but
> > fold_const_call isn't among those. That would be certainly nice to get
> > fixed, but this patch doesn't do that.
>
> Indeed, but the following should already do it:
>
> /* Dispatch to pattern-based folding. */
> if (!inplace
> || is_gimple_assign (stmt)
> || gimple_code (stmt) == GIMPLE_COND)
> {
> gimple_seq seq = NULL;
> gimple_match_op res_op;
> if (gimple_simplify (stmt, &res_op, inplace ? NULL : &seq,
> valueize, valueize)
> && replace_stmt_with_simplification (gsi, &res_op, &seq,
> inplace,
> dce_worklist))
> changed = true;
>
> via
>
> /* Helper that matches and simplifies the toplevel result from
> a gimple_simplify run (where we don't want to build
> a stmt in case it's used in in-place folding). Replaces
> RES_OP with a simplified and/or canonicalized result and
> returns whether any change was made. */
>
> static bool
> gimple_resimplify1 (gimple_seq *seq, gimple_match_op *res_op,
> tree (*valueize)(tree))
> {
> if (constant_for_folding (res_op->ops[0]))
> {
> tree tem = NULL_TREE;
> if (res_op->code.is_tree_code ())
> {
> auto code = tree_code (res_op->code);
> if (IS_EXPR_CODE_CLASS (TREE_CODE_CLASS (code))
> && TREE_CODE_LENGTH (code) == 1)
> tem = const_unop (code, res_op->type, res_op->ops[0]);
> }
> else
> tem = fold_const_call (combined_fn (res_op->code), res_op->type,
> res_op->ops[0]);
And the reason it doesn't work is that fold_const_call only
handles it via fold_const_call_1 which does
if (integer_cst_p (arg))
{
if (SCALAR_INT_MODE_P (mode))
{
wide_int result;
if (fold_const_call_ss (&result, fn, wi::to_wide (arg),
TYPE_PRECISION (type), TREE_TYPE (arg)))
return wide_int_to_tree (type, result);
and the bit int constant fails the SCALAR_INT_MODE_P check
(but fold_const_call_ss wouldn't handle bitreverse). So it seems
there's no constant folding of bitreverse implemented anywhere
or if it is, it's in the wrong place?
>
> > The other problem is forgotten flag. Because of the above missed
> > optimization we end up with .BITREVERSE (1) call before bitintlower, where
> > that 1 is unsigned _BitInt(129). If there is a partial limb (like in
> > this case 1 bit), we emit handling of the result partial limb in straight
> > line code (but that actually reads some bits from the least significant limb
> > of the function argument), and then have a loop to handle the remaining
> > limbs. We use there handle_operand with an index which is not constant
> > in the loop (obviously) and using this read any limbs in the argument in
> > reverse order, including the partial limb.
> > Now, handle_operand has an optimization, for most of the lowerings we have
> > a guarantee that the partial limb is accessed only in straight line code with
> > a constant index and e.g. for constants reading the partial limb in that
> > case simply folds into the constant value of that partial limb. So, we
> > optimize and emit into .rodata only fewer limbs (all but the partial one)
> > unless it is one of the even more optimized ones (where we treat 0, all ones
> > or values fitting into fewer limbs by always using a constant or by
> > extending at runtime). The exception to this has been the LSHIFT_EXPR
> > lowering and we have the m_var_msb flag to tell we need all the bits
> > including the partial limb. Given the way .BSWAP/.BITREVERSE expansion
> > works, we need that flag for those too.
> >
> > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
>
> OK.
>
> > 2026-10-01 Jakub Jelinek <jakub@redhat.com>
> >
> > PR middle-end/127669
> > * gimple-lower-bitint.cc (bitint_large_huge::lower_bswap_bitreverse):
> > Set m_var_msb to true.
> >
> > * gcc.dg/torture/bitint-110.c: New test.
> >
> > --- a/gcc/gimple-lower-bitint.cc 2026-09-18 11:03:21.676039449 +0200
> > +++ b/gcc/gimple-lower-bitint.cc 2026-09-30 14:29:15.870162767 +0200
> > @@ -6172,6 +6172,9 @@ bitint_large_huge::lower_bswap_bitrevers
> > unsigned prec = TYPE_PRECISION (type);
> > tree p = build_int_cst (sizetype,
> > prec / limb_prec - (prec % limb_prec == 0));
> > + /* For .BSWAP/.BITREVERSE, we can use handle_operand with non-INTEGER_CST
> > + idx even to access the most significant partial limb. */
> > + m_var_msb = true;
> > /* For IFN .BSWAP or .BITREVERSE and
> > FN corresponding __builtin_bswapN or __builtin_bitreverseN where N
> > is limb_prec, lower
> > --- a/gcc/testsuite/gcc.dg/torture/bitint-110.c 2026-09-30 14:29:49.463717815 +0200
> > +++ b/gcc/testsuite/gcc.dg/torture/bitint-110.c 2026-09-30 14:31:29.115397910 +0200
> > @@ -0,0 +1,30 @@
> > +/* PR middle-end/127669 */
> > +/* { dg-do run { target bitint } } */
> > +
> > +#if __BITINT_MAXWIDTH__ >= 129
> > +typedef unsigned _BitInt(129) T;
> > +
> > +[[gnu::noipa]] static T
> > +foo (T v)
> > +{
> > + return v;
> > +}
> > +
> > +static T
> > +bar ()
> > +{
> > + return 1;
> > +}
> > +#endif
> > +
> > +int
> > +main ()
> > +{
> > +#if __BITINT_MAXWIDTH__ >= 129
> > + T x = __builtin_bitreverseg ((T) 1);
> > + T y = __builtin_bitreverseg (bar ());
> > + T z = __builtin_bitreverseg (foo (1));
> > + if (x != y || x != z)
> > + __builtin_abort ();
> > +#endif
> > +}
> >
> > Jakub
> >
> >
>
>
On Thu, 1 Oct 2026, Richard Biener wrote:
> On Thu, 1 Oct 2026, Richard Biener wrote:
>
> > On Thu, 1 Oct 2026, Jakub Jelinek wrote:
> >
> > > Hi!
> > >
> > > The following testcase is miscompiled for 2 reasons.
> > >
> > > One missed optimization is that fold_stmt_1 doesn't seem to attempt to
> > > use fold_const_call for internal functions into whose argument a constant
> > > is propagated. For normal builtins, I think fold_stmt_1 -> gimple_fold_call
> > > -> gimple_fold_builtin -> fold_call_stmt -> fold_builtin_n ->
> > > fold_builtin_{1,2,3} -> fold_const_call optimizes it, but for internal
> > > fns we just have selected set of optimizations we do on them but
> > > fold_const_call isn't among those. That would be certainly nice to get
> > > fixed, but this patch doesn't do that.
> >
> > Indeed, but the following should already do it:
> >
> > /* Dispatch to pattern-based folding. */
> > if (!inplace
> > || is_gimple_assign (stmt)
> > || gimple_code (stmt) == GIMPLE_COND)
> > {
> > gimple_seq seq = NULL;
> > gimple_match_op res_op;
> > if (gimple_simplify (stmt, &res_op, inplace ? NULL : &seq,
> > valueize, valueize)
> > && replace_stmt_with_simplification (gsi, &res_op, &seq,
> > inplace,
> > dce_worklist))
> > changed = true;
> >
> > via
> >
> > /* Helper that matches and simplifies the toplevel result from
> > a gimple_simplify run (where we don't want to build
> > a stmt in case it's used in in-place folding). Replaces
> > RES_OP with a simplified and/or canonicalized result and
> > returns whether any change was made. */
> >
> > static bool
> > gimple_resimplify1 (gimple_seq *seq, gimple_match_op *res_op,
> > tree (*valueize)(tree))
> > {
> > if (constant_for_folding (res_op->ops[0]))
> > {
> > tree tem = NULL_TREE;
> > if (res_op->code.is_tree_code ())
> > {
> > auto code = tree_code (res_op->code);
> > if (IS_EXPR_CODE_CLASS (TREE_CODE_CLASS (code))
> > && TREE_CODE_LENGTH (code) == 1)
> > tem = const_unop (code, res_op->type, res_op->ops[0]);
> > }
> > else
> > tem = fold_const_call (combined_fn (res_op->code), res_op->type,
> > res_op->ops[0]);
>
> And the reason it doesn't work is that fold_const_call only
> handles it via fold_const_call_1 which does
>
> if (integer_cst_p (arg))
> {
> if (SCALAR_INT_MODE_P (mode))
> {
> wide_int result;
> if (fold_const_call_ss (&result, fn, wi::to_wide (arg),
> TYPE_PRECISION (type), TREE_TYPE (arg)))
> return wide_int_to_tree (type, result);
>
> and the bit int constant fails the SCALAR_INT_MODE_P check
> (but fold_const_call_ss wouldn't handle bitreverse). So it seems
> there's no constant folding of bitreverse implemented anywhere
> or if it is, it's in the wrong place?
Ah, it's there. So
diff --git a/gcc/fold-const-call.cc b/gcc/fold-const-call.cc
index 94841e8fb65..2a8316bf406 100644
--- a/gcc/fold-const-call.cc
+++ b/gcc/fold-const-call.cc
@@ -1311,7 +1311,7 @@ fold_const_call_1 (combined_fn fn, tree type, tree
arg)
if (integer_cst_p (arg))
{
- if (SCALAR_INT_MODE_P (mode))
+ if (INTEGRAL_TYPE_P (type))
{
wide_int result;
if (fold_const_call_ss (&result, fn, wi::to_wide (arg),
works, I wonder why we had the mode check there ... I'm testing this.
Richard.
> >
> > > The other problem is forgotten flag. Because of the above missed
> > > optimization we end up with .BITREVERSE (1) call before bitintlower, where
> > > that 1 is unsigned _BitInt(129). If there is a partial limb (like in
> > > this case 1 bit), we emit handling of the result partial limb in straight
> > > line code (but that actually reads some bits from the least significant limb
> > > of the function argument), and then have a loop to handle the remaining
> > > limbs. We use there handle_operand with an index which is not constant
> > > in the loop (obviously) and using this read any limbs in the argument in
> > > reverse order, including the partial limb.
> > > Now, handle_operand has an optimization, for most of the lowerings we have
> > > a guarantee that the partial limb is accessed only in straight line code with
> > > a constant index and e.g. for constants reading the partial limb in that
> > > case simply folds into the constant value of that partial limb. So, we
> > > optimize and emit into .rodata only fewer limbs (all but the partial one)
> > > unless it is one of the even more optimized ones (where we treat 0, all ones
> > > or values fitting into fewer limbs by always using a constant or by
> > > extending at runtime). The exception to this has been the LSHIFT_EXPR
> > > lowering and we have the m_var_msb flag to tell we need all the bits
> > > including the partial limb. Given the way .BSWAP/.BITREVERSE expansion
> > > works, we need that flag for those too.
> > >
> > > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> >
> > OK.
> >
> > > 2026-10-01 Jakub Jelinek <jakub@redhat.com>
> > >
> > > PR middle-end/127669
> > > * gimple-lower-bitint.cc (bitint_large_huge::lower_bswap_bitreverse):
> > > Set m_var_msb to true.
> > >
> > > * gcc.dg/torture/bitint-110.c: New test.
> > >
> > > --- a/gcc/gimple-lower-bitint.cc 2026-09-18 11:03:21.676039449 +0200
> > > +++ b/gcc/gimple-lower-bitint.cc 2026-09-30 14:29:15.870162767 +0200
> > > @@ -6172,6 +6172,9 @@ bitint_large_huge::lower_bswap_bitrevers
> > > unsigned prec = TYPE_PRECISION (type);
> > > tree p = build_int_cst (sizetype,
> > > prec / limb_prec - (prec % limb_prec == 0));
> > > + /* For .BSWAP/.BITREVERSE, we can use handle_operand with non-INTEGER_CST
> > > + idx even to access the most significant partial limb. */
> > > + m_var_msb = true;
> > > /* For IFN .BSWAP or .BITREVERSE and
> > > FN corresponding __builtin_bswapN or __builtin_bitreverseN where N
> > > is limb_prec, lower
> > > --- a/gcc/testsuite/gcc.dg/torture/bitint-110.c 2026-09-30 14:29:49.463717815 +0200
> > > +++ b/gcc/testsuite/gcc.dg/torture/bitint-110.c 2026-09-30 14:31:29.115397910 +0200
> > > @@ -0,0 +1,30 @@
> > > +/* PR middle-end/127669 */
> > > +/* { dg-do run { target bitint } } */
> > > +
> > > +#if __BITINT_MAXWIDTH__ >= 129
> > > +typedef unsigned _BitInt(129) T;
> > > +
> > > +[[gnu::noipa]] static T
> > > +foo (T v)
> > > +{
> > > + return v;
> > > +}
> > > +
> > > +static T
> > > +bar ()
> > > +{
> > > + return 1;
> > > +}
> > > +#endif
> > > +
> > > +int
> > > +main ()
> > > +{
> > > +#if __BITINT_MAXWIDTH__ >= 129
> > > + T x = __builtin_bitreverseg ((T) 1);
> > > + T y = __builtin_bitreverseg (bar ());
> > > + T z = __builtin_bitreverseg (foo (1));
> > > + if (x != y || x != z)
> > > + __builtin_abort ();
> > > +#endif
> > > +}
> > >
> > > Jakub
> > >
> > >
> >
> >
>
>
On Thu, Oct 01, 2026 at 09:43:12AM +0200, Richard Biener wrote:
> works, I wonder why we had the mode check there ... I'm testing this.
When Richard Sandiford introduced it back in 2015, at least for clz/ctz
it was using TYPE_MODE in the C?Z_DEFINED_VALUE_AT_ZERO cases and for the
rest it would be supported for scalar integer modes only anyway.
Now it uses SCALAR_INT_TYPE_MODE in those cases, so will also ICE if it is
not like that, but BITINT_TYPE is an exception:
if (wi::ne_p (arg, 0))
tmp = wi::clz (arg);
else if (BITINT_TYPE_P (arg_type))
tmp = TYPE_PRECISION (arg_type);
else if (!CLZ_DEFINED_VALUE_AT_ZERO (SCALAR_INT_TYPE_MODE (arg_type),
tmp))
tmp = TYPE_PRECISION (arg_type);
Except the
if (SCALAR_INT_MODE_P (mode))
check is not about the argument type (types) but about the result type.
When it was introduced, all the handled functions
(ffs/clz/ctz/clrsb/popcount/parity) had int return type, check for argument
mode would be
if (SCALAR_INT_MODE_P (arg_mode))
in one case and
if (SCALAR_INT_MODE_P (arg0_mode))
in the other case.
I'd say we can just remove the if conditions altogether,
fold_const_call_ss{,s} will just return false for unknown builtins and
for the known ones that it handles all the results are BITINT_TYPE or
SCALAR_INT_MODE_P ones.
Jakub
On Thu, 1 Oct 2026, Jakub Jelinek wrote:
> On Thu, Oct 01, 2026 at 09:43:12AM +0200, Richard Biener wrote:
> > works, I wonder why we had the mode check there ... I'm testing this.
>
> When Richard Sandiford introduced it back in 2015, at least for clz/ctz
> it was using TYPE_MODE in the C?Z_DEFINED_VALUE_AT_ZERO cases and for the
> rest it would be supported for scalar integer modes only anyway.
> Now it uses SCALAR_INT_TYPE_MODE in those cases, so will also ICE if it is
> not like that, but BITINT_TYPE is an exception:
> if (wi::ne_p (arg, 0))
> tmp = wi::clz (arg);
> else if (BITINT_TYPE_P (arg_type))
> tmp = TYPE_PRECISION (arg_type);
> else if (!CLZ_DEFINED_VALUE_AT_ZERO (SCALAR_INT_TYPE_MODE (arg_type),
> tmp))
> tmp = TYPE_PRECISION (arg_type);
> Except the
> if (SCALAR_INT_MODE_P (mode))
> check is not about the argument type (types) but about the result type.
> When it was introduced, all the handled functions
> (ffs/clz/ctz/clrsb/popcount/parity) had int return type, check for argument
> mode would be
> if (SCALAR_INT_MODE_P (arg_mode))
> in one case and
> if (SCALAR_INT_MODE_P (arg0_mode))
> in the other case.
> I'd say we can just remove the if conditions altogether,
> fold_const_call_ss{,s} will just return false for unknown builtins and
> for the known ones that it handles all the results are BITINT_TYPE or
> SCALAR_INT_MODE_P ones.
I think it makes sense to gate on the return type just for the sake
of early out - I suppose it was supposed to gate the opposite of
real_cst_p (arg) && INTEGRAL_TYPE_P (type) (which also uses a type
check). It also acts as somewhat of a safety against those
pesky invalid testcases prototyping existing builtins with bogus types...
Richard.
On Thu, Oct 01, 2026 at 10:36:46AM +0200, Richard Biener wrote:
> I think it makes sense to gate on the return type just for the sake
> of early out - I suppose it was supposed to gate the opposite of
> real_cst_p (arg) && INTEGRAL_TYPE_P (type) (which also uses a type
> check). It also acts as somewhat of a safety against those
> pesky invalid testcases prototyping existing builtins with bogus types...
All these builtins are __builtin_* so I think incorrect declaration is
not possible, but sure, I'm fine with INTEGRAL_TYPE_P (type), just
it should be done in both spots for consistency, not just one
(of course, it is less important in the second case because it is just
CLZ/CTZ in there are that will always have int return type).
Jakub
@@ -6172,6 +6172,9 @@ bitint_large_huge::lower_bswap_bitrevers
unsigned prec = TYPE_PRECISION (type);
tree p = build_int_cst (sizetype,
prec / limb_prec - (prec % limb_prec == 0));
+ /* For .BSWAP/.BITREVERSE, we can use handle_operand with non-INTEGER_CST
+ idx even to access the most significant partial limb. */
+ m_var_msb = true;
/* For IFN .BSWAP or .BITREVERSE and
FN corresponding __builtin_bswapN or __builtin_bitreverseN where N
is limb_prec, lower
@@ -0,0 +1,30 @@
+/* PR middle-end/127669 */
+/* { dg-do run { target bitint } } */
+
+#if __BITINT_MAXWIDTH__ >= 129
+typedef unsigned _BitInt(129) T;
+
+[[gnu::noipa]] static T
+foo (T v)
+{
+ return v;
+}
+
+static T
+bar ()
+{
+ return 1;
+}
+#endif
+
+int
+main ()
+{
+#if __BITINT_MAXWIDTH__ >= 129
+ T x = __builtin_bitreverseg ((T) 1);
+ T y = __builtin_bitreverseg (bar ());
+ T z = __builtin_bitreverseg (foo (1));
+ if (x != y || x != z)
+ __builtin_abort ();
+#endif
+}