https://gcc.gnu.org/g:08c26561e4fe2cb6a26f5217673ed72c22d33e5b
commit r17-5106-g08c26561e4fe2cb6a26f5217673ed72c22d33e5b Author: Jakub Jelinek <[email protected]> Date: Tue Oct 13 10:47:53 2026 +0200 tailc: Conservatively punt on some return type mismatches in tail call opt [PR127848] 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. 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. Reviewed-by: Richard Biener <[email protected]> Diff: --- gcc/testsuite/gcc.dg/pr127848-1.c | 8 ++++++++ gcc/testsuite/gcc.dg/pr127848-2.c | 18 ++++++++++++++++++ gcc/testsuite/gcc.dg/pr127848-3.c | 18 ++++++++++++++++++ gcc/testsuite/gcc.dg/pr127848-4.c | 20 ++++++++++++++++++++ gcc/testsuite/gcc.dg/pr127848-5.c | 20 ++++++++++++++++++++ gcc/tree-tailcall.cc | 40 +++++++++++++++++++++++++++++++++++++-- 6 files changed, 122 insertions(+), 2 deletions(-) diff --git a/gcc/testsuite/gcc.dg/pr127848-1.c b/gcc/testsuite/gcc.dg/pr127848-1.c new file mode 100644 index 000000000000..8d3a3e88751f --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr127848-1.c @@ -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 (); } diff --git a/gcc/testsuite/gcc.dg/pr127848-2.c b/gcc/testsuite/gcc.dg/pr127848-2.c new file mode 100644 index 000000000000..518986da3af0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr127848-2.c @@ -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 (); } diff --git a/gcc/testsuite/gcc.dg/pr127848-3.c b/gcc/testsuite/gcc.dg/pr127848-3.c new file mode 100644 index 000000000000..ac5125d8acbc --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr127848-3.c @@ -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 (); } diff --git a/gcc/testsuite/gcc.dg/pr127848-4.c b/gcc/testsuite/gcc.dg/pr127848-4.c new file mode 100644 index 000000000000..16ad7e287ea9 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr127848-4.c @@ -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 (); } diff --git a/gcc/testsuite/gcc.dg/pr127848-5.c b/gcc/testsuite/gcc.dg/pr127848-5.c new file mode 100644 index 000000000000..f3b8b37fc9d0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr127848-5.c @@ -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 (); } diff --git a/gcc/tree-tailcall.cc b/gcc/tree-tailcall.cc index 821427699cc7..3df1f442342b 100644 --- a/gcc/tree-tailcall.cc +++ b/gcc/tree-tailcall.cc @@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see #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; }
