On Tue, 13 Oct 2026, Jakub Jelinek wrote:

> Hi!
> 
> The first testcase is miscompiled on x86_64 and various other
> bitint_ext_undef targets, where the padding bits are unspecified.
> So, if callee returns unsigned _BitInt(15), only low 15 bits are
> specified, when caller returns unsigned short on x86_64, the bits 16-63
> are unspecified, but bit 15 needs to be cleared, so we can't tail
> call.
> 
> Now, tailc for casts currently checks TYPE_MODE differences and for
> the destination doesn't have non-mode precision (that one was added
> because of callee returning unsigned char and caller returning bool,
> those have the same mode but need to emit some code in between the
> call to the callee and return from caller).
> For useless_type_conversion_p we keep the distinction between normal
> INTEGER_TYPEs and BITINT_TYPE_P types with the same precision, because
> at function boundaries the target can have different calling convention.
> We don't really know at GIMPLE if the target returns unsigned _BitInt(16)
> and unsigned short the same way, there could be e.g. differences in padding
> bits etc.
> The following patch is a conservative fix, it punts on
> BITINT_TYPE_P != BITINT_TYPE_P conversions (exactly because we don't
> know if it changes ABI or not), and if both are BITINT_TYPE_P with the
> same TYPE_MODE, does some further checks - if TYPE_SIZE is different
> (and mode the same, that could be just BLKmode) it punts on TYPE_SIZE
> differences, for narrowing or same precision casts it punts except
> for bitint_ext_undef targets (on those the padding bits are unspecified
> and if we have more or same amount of unspecified padding bits it is ok),
> for widening casts it punts on the other side for all but bitint_ext_full
> targets (I'm not motivated enough to bother with figuring out all the
> details for bitint_ext_partial), because on those targets the narrower
> bitint has the bits already extended and if the narrower and wider _BitInt
> type have the same signedness, they are fine (now that I think about it,
> perhaps we could allow even narrower unsigned wider signed, can test
> that incrementally).
> In any case, the patch may pessimize code which would work just fine,
> callee returning unsigned _BitInt(16) and caller unsigned short or vice
> versa on many targets, etc.  But, there can be multiple casts, so if we
> want to improve it, we could e.g. have casts where the callee type is
> compatible with the src_var type and dest type is compatible with the
> caller's type (so have these checks for intermediate calls but allow
> one cast from callee to caller return types), and then somehow deal with it
> during expansion (either in generic code try to expand code which converts
> the callee type temporary to caller type and check (and throw away) a
> sequence emitted by that whether it contains only normal moves and no other
> insns, or make sure every target verifies everything in their target hook).
> I don't have time to do that right now though and it might be too risky
> for backports anyway.

I agree.  Call boundary details on GIMPLE are somewhat of a pain to
deal with ...

> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk and
> release branches?

OK.

Thanks,
Richard.

> 2026-10-13  Jakub Jelinek  <[email protected]>
> 
>       PR middle-end/127848
>       * tree-tailcall.cc: Include target.h.
>       (process_assignment): Punt if one of dest and src_var types
>       is BITINT_TYPE_P and the other is not.  If both are BITINT_TYPE_P,
>       punt if TYPE_SIZE is different, or for narrowing or same precision
>       conversions on != bitint_ext_undef targets, or for widening
>       conversions on != bitint_ext_full targets or if signedness differs.
> 
>       * gcc.dg/pr127848-1.c: New test.
>       * gcc.dg/pr127848-2.c: New test.
>       * gcc.dg/pr127848-3.c: New test.
>       * gcc.dg/pr127848-4.c: New test.
>       * gcc.dg/pr127848-5.c: New test.
> 
> --- a/gcc/tree-tailcall.cc    2026-03-27 10:17:21.505210458 +0100
> +++ b/gcc/tree-tailcall.cc    2026-10-12 12:57:39.198647988 +0200
> @@ -53,6 +53,7 @@ along with GCC; see the file COPYING3.
>  #include "ipa-prop.h"
>  #include "attribs.h"
>  #include "asan.h"
> +#include "target.h"
>  
>  /* The file implements the tail recursion elimination.  It is also used to
>     analyze the tail calls in general, passing the results to the rtl level
> @@ -369,11 +370,46 @@ process_assignment (gassign *stmt,
>         if (TYPE_MODE (TREE_TYPE (dest)) != TYPE_MODE (TREE_TYPE (src_var)))
>           return FAIL;
>  
> +       /* While say unsigned short vs. unsigned _BitInt(16) can be
> +          returned the same on some targets, we don't have a guarantee
> +          that is the case on all targets, the ABI can differ, so just
> +          punt.  */
> +       if (BITINT_TYPE_P (TREE_TYPE (dest))
> +           != BITINT_TYPE_P (TREE_TYPE (src_var)))
> +         return FAIL;
> +
> +       if (BITINT_TYPE_P (TREE_TYPE (dest)))
> +         {
> +           /* For _BitInt <-> _BitInt we have more target information.
> +              For BLKmode we need to punt if it has different size.  */
> +           if (wi::to_widest (TYPE_SIZE (TREE_TYPE (dest)))
> +               != wi::to_widest (TYPE_SIZE (TREE_TYPE (src_var))))
> +             return FAIL;
> +           struct bitint_info info;
> +           unsigned dest_prec = TYPE_PRECISION (TREE_TYPE (dest));
> +           bool ok = targetm.c.bitint_type_info (dest_prec, &info);
> +           gcc_assert (ok);
> +           if (TYPE_PRECISION (TREE_TYPE (dest))
> +               <= TYPE_PRECISION (TREE_TYPE (src_var)))
> +             {
> +               /* Narrowing conversion or possibly same precision
> +                  different sign.  This is fine on bitint_ext_undef
> +                  targets only.  */
> +               if (info.extended != bitint_ext_undef)
> +                 return FAIL;
> +             }
> +           /* Widening conversion.  This is fine on bitint_ext_full
> +              target if signedness is the same.  */
> +           else if (info.extended != bitint_ext_full
> +                    || (TYPE_UNSIGNED (TREE_TYPE (dest))
> +                        != TYPE_UNSIGNED (TREE_TYPE (src_var))))
> +             return FAIL;
> +         }
>         /* Even if the type modes are the same, if the precision of the
>            type is smaller than mode's precision,
>            reduce_to_bit_field_precision would generate additional code.  */
> -       if (INTEGRAL_TYPE_P (TREE_TYPE (dest))
> -           && !type_has_mode_precision_p (TREE_TYPE (dest)))
> +       else if (INTEGRAL_TYPE_P (TREE_TYPE (dest))
> +                && !type_has_mode_precision_p (TREE_TYPE (dest)))
>           return FAIL;
>       }
>  
> --- a/gcc/testsuite/gcc.dg/pr127848-1.c       2026-10-12 12:37:00.733671531 
> +0200
> +++ b/gcc/testsuite/gcc.dg/pr127848-1.c       2026-10-12 12:36:55.976736908 
> +0200
> @@ -0,0 +1,8 @@
> +/* PR middle-end/127848 */
> +/* { dg-do compile { target bitint } } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +/* For bitint_ext_undef targets, tail call is definitely wrong-code, bit 15 
> is unspecified from foo and needs to be cleared in bar.  */
> +/* { dg-final { scan-tree-dump-not "= foo \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +
> +unsigned _BitInt(15) foo ();
> +unsigned short bar () { return foo (); }
> --- a/gcc/testsuite/gcc.dg/pr127848-2.c       2026-10-12 12:37:08.139569741 
> +0200
> +++ b/gcc/testsuite/gcc.dg/pr127848-2.c       2026-10-12 12:44:15.527695725 
> +0200
> @@ -0,0 +1,18 @@
> +/* PR middle-end/127848 */
> +/* { dg-do compile { target bitint } } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +/* For bitint_ext_undef targets, narrowing or same precision _BitInt 
> conversion within the same mode and size is ok.  */
> +/* { dg-final { scan-tree-dump-times "= foo \\\(\\\); \\\[tail call\\\]" 3 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +/* { dg-final { scan-tree-dump-times "= qux \\\(\\\); \\\[tail call\\\]" 3 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +/* For bitint_ext_full targets, such conversions are definitely not ok.  In 
> these cases even on bitint_ext_partial.  */
> +/* { dg-final { scan-tree-dump-not "= foo \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target arm*-*-* riscv*-*-* s390*-*-* loongarch*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "= qux \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target arm*-*-* riscv*-*-* s390*-*-* loongarch*-*-* } } } */
> +
> +unsigned _BitInt(15) foo ();
> +[[gnu::noipa]] unsigned _BitInt(13) bar () { return foo (); }
> +[[gnu::noipa]] signed _BitInt(13) baz () { return foo (); }
> +[[gnu::noipa]] signed _BitInt(15) corge () { return foo (); }
> +signed _BitInt(15) qux ();
> +[[gnu::noipa]] signed _BitInt(13) fred () { return qux (); }
> +[[gnu::noipa]] unsigned _BitInt(13) waldo () { return qux (); }
> +[[gnu::noipa]] unsigned _BitInt(13) xyzzy () { return qux (); }
> --- a/gcc/testsuite/gcc.dg/pr127848-3.c       2026-10-12 13:01:08.546769959 
> +0200
> +++ b/gcc/testsuite/gcc.dg/pr127848-3.c       2026-10-12 13:01:56.633108887 
> +0200
> @@ -0,0 +1,18 @@
> +/* PR middle-end/127848 */
> +/* { dg-do compile { target bitint } } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +/* For bitint_ext_undef targets, narrowing or same precision _BitInt 
> conversion within the same mode and size is ok.  */
> +/* { dg-final { scan-tree-dump-times "= foo \\\(\\\); \\\[tail call\\\]" 3 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +/* { dg-final { scan-tree-dump-times "= qux \\\(\\\); \\\[tail call\\\]" 3 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +/* For bitint_ext_full targets, such conversions are definitely not ok.  In 
> these cases even on bitint_ext_partial.  */
> +/* { dg-final { scan-tree-dump-not "= foo \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target arm*-*-* riscv*-*-* s390*-*-* loongarch*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "= qux \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target arm*-*-* riscv*-*-* s390*-*-* loongarch*-*-* } } } */
> +
> +unsigned _BitInt(31) foo ();
> +[[gnu::noipa]] unsigned _BitInt(25) bar () { return foo (); }
> +[[gnu::noipa]] signed _BitInt(25) baz () { return foo (); }
> +[[gnu::noipa]] signed _BitInt(31) corge () { return foo (); }
> +signed _BitInt(31) qux ();
> +[[gnu::noipa]] signed _BitInt(25) fred () { return qux (); }
> +[[gnu::noipa]] unsigned _BitInt(25) waldo () { return qux (); }
> +[[gnu::noipa]] unsigned _BitInt(25) xyzzy () { return qux (); }
> --- a/gcc/testsuite/gcc.dg/pr127848-4.c       2026-10-12 12:47:40.663876336 
> +0200
> +++ b/gcc/testsuite/gcc.dg/pr127848-4.c       2026-10-12 13:02:21.573766013 
> +0200
> @@ -0,0 +1,20 @@
> +/* PR middle-end/127848 */
> +/* { dg-do compile { target bitint } } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +/* For bitint_ext_undef targets, widening precision _BitInt conversion are 
> not ok.  */
> +/* For bitint_ext_full targets, widening precisions with the same size are 
> ok if the sign is the same.  */
> +/* { dg-final { scan-tree-dump-not "= foo \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "= baz \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "= fred \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* arm*-*-* 
> riscv*-*-* s390*-*-* loongarch*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "= corge \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* arm*-*-* 
> riscv*-*-* s390*-*-* loongarch*-*-* } } } */
> +/* { dg-final { scan-tree-dump "= foo \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target arm*-*-* riscv*-*-* s390*-*-* } } } */
> +/* { dg-final { scan-tree-dump "= qux \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target arm*-*-* riscv*-*-* s390*-*-* } } } */
> +
> +unsigned _BitInt(13) foo ();
> +[[gnu::noipa]] unsigned _BitInt(15) bar () { return foo (); }
> +signed _BitInt(13) baz ();
> +[[gnu::noipa]] signed _BitInt(15) qux () { return baz (); }
> +unsigned _BitInt(13) fred ();
> +[[gnu::noipa]] signed _BitInt(15) waldo () { return fred (); }
> +signed _BitInt(13) corge ();
> +[[gnu::noipa]] unsigned _BitInt(15) xyzzy () { return corge (); }
> --- a/gcc/testsuite/gcc.dg/pr127848-5.c       2026-10-12 12:51:52.539413714 
> +0200
> +++ b/gcc/testsuite/gcc.dg/pr127848-5.c       2026-10-12 13:02:43.359466513 
> +0200
> @@ -0,0 +1,20 @@
> +/* PR middle-end/127848 */
> +/* { dg-do compile { target bitint } } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +/* For bitint_ext_undef targets, widening precision _BitInt conversion are 
> not ok.  */
> +/* For bitint_ext_full targets, widening precisions with the same size are 
> ok if the sign is the same.  */
> +/* { dg-final { scan-tree-dump-not "= foo \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "= baz \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "= fred \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* arm*-*-* 
> riscv*-*-* s390*-*-* loongarch*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "= corge \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target i?86-*-* x86_64*-*-* aarch64*-*-* powerpc*-*-* arm*-*-* 
> riscv*-*-* s390*-*-* loongarch*-*-* } } } */
> +/* { dg-final { scan-tree-dump "= foo \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target arm*-*-* riscv*-*-* s390*-*-* } } } */
> +/* { dg-final { scan-tree-dump "= qux \\\(\\\); \\\[tail call\\\]" 
> "optimized" { target arm*-*-* riscv*-*-* s390*-*-* } } } */
> +
> +unsigned _BitInt(25) foo ();
> +[[gnu::noipa]] unsigned _BitInt(31) bar () { return foo (); }
> +signed _BitInt(25) baz ();
> +[[gnu::noipa]] signed _BitInt(31) qux () { return baz (); }
> +unsigned _BitInt(25) fred ();
> +[[gnu::noipa]] signed _BitInt(31) waldo () { return fred (); }
> +signed _BitInt(25) corge ();
> +[[gnu::noipa]] unsigned _BitInt(31) xyzzy () { return corge (); }
> 
>       Jakub
> 
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri; (HRB 36809, AG Nuernberg)

Reply via email to