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.
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk and
release branches?
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