On Tue, Sep 22, 2026 at 12:22 PM Dominic P <[email protected]> wrote: > > (x ^ 2^(N-1)) - 2^(N-1) and "bit N-1 of x set ? x - 2^N : x" are > sign_extend<N> (x) when x has no bit above bit N - 1 set. The PR125405 > rule reads that range off an explicit (x & M), so neither spelling > reaches it when the range comes from x's type instead. > > The xor constant must be positive in the xor's own type: as the sign bit > of a signed N-bit type the conversion sign-extends and the fold loses a > - 2^N. MAX_FIXED_MODE_SIZE bounds N: _BitInt(256) at N = 128 folds and > runs on a 64-bit host, but ICEs arm's handle_cast.
Maybe this should be only done before lowering of bitint ... Or you not using bitint type for larger ones which is causing the ICE I think. > The cond rule fires > in phiopt through match_simplify_replacement and costs one convert. > Vectors decline on INTEGRAL_TYPE_P (type); a ternary with an outer > conversion on the arms and the unsigned-compare spelling are not > handled. All 25 relative-branch sites in PiTubeDirect's 6502 > interpreter fold: -46 branches and -600 B in M6502_run, LTO image. > > Bootstrapped and regression tested on x86_64-pc-linux-gnu. > > Assisted-by: Claude Opus 5 (Anthropic) > Signed-off-by: Dominic P <[email protected]> > > gcc/ChangeLog: > > * match.pd (`(x ^ CST) + CST2`): New pattern. > (`(signed:N) x < 0 ? (T) x + CST : (T) x`): New pattern. > > gcc/testsuite/ChangeLog: > > * gcc.dg/tree-ssa/sext-xor-1.c: New test. > * gcc.dg/tree-ssa/sext-xor-2.c: New test. > * gcc.dg/tree-ssa/sext-cond-1.c: New test. > * gcc.dg/tree-ssa/sext-cond-2.c: New test. > * gcc.dg/tree-ssa/sext-bitint-1.c: New test. > * gcc.dg/tree-ssa/sext-vector-1.c: New test. > * gcc.dg/torture/sext-idiom-1.c: New test. > * gcc.target/arm/sext-idiom-1.c: New test. > --- > gcc/match.pd | 35 +++++ > gcc/testsuite/gcc.dg/torture/sext-idiom-1.c | 125 ++++++++++++++++++ > gcc/testsuite/gcc.dg/tree-ssa/sext-bitint-1.c | 31 +++++ > gcc/testsuite/gcc.dg/tree-ssa/sext-cond-1.c | 31 +++++ > gcc/testsuite/gcc.dg/tree-ssa/sext-cond-2.c | 38 ++++++ > gcc/testsuite/gcc.dg/tree-ssa/sext-vector-1.c | 39 ++++++ > gcc/testsuite/gcc.dg/tree-ssa/sext-xor-1.c | 33 +++++ > gcc/testsuite/gcc.dg/tree-ssa/sext-xor-2.c | 49 +++++++ > gcc/testsuite/gcc.target/arm/sext-idiom-1.c | 47 +++++++ > 9 files changed, 428 insertions(+) > create mode 100644 gcc/testsuite/gcc.dg/torture/sext-idiom-1.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/sext-bitint-1.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/sext-cond-1.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/sext-cond-2.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/sext-vector-1.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/sext-xor-1.c > create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/sext-xor-2.c > create mode 100644 gcc/testsuite/gcc.target/arm/sext-idiom-1.c > > diff --git a/gcc/match.pd b/gcc/match.pd > index 268bf3f11e5..1290a2c8b54 100644 > --- a/gcc/match.pd > +++ b/gcc/match.pd > @@ -5472,6 +5472,41 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT) > (if (ntype && type_has_mode_precision_p (ntype)) > (convert (convert:ntype @0)))))) > > +/* (x ^ (1 << (N - 1))) - (1 << (N - 1)) -> sign_extend<N> (x), for an x > + with no bits above bit N - 1 possibly set. */ Small comment nit here, let's use CST and then describe what CST is like: ``` (x ^ CST) - CST -> sign_extend<N> (x) where CST is (1 << (N - 1)), for an x with no bits above bit N - 1 possibly set. ``` As I was originally looking for a shift in the pattern and noticed there was now. > +(simplify > + (plus (convert? (bit_xor @0 INTEGER_CST@1)) INTEGER_CST@2) I think INTEGER_CST here should be integer_pow2p, otherwise ... > + (if (INTEGRAL_TYPE_P (type)) > + (with { > + unsigned int prec = TYPE_PRECISION (type); > + int width = wi::exact_log2 (wi::to_wide (@1)) + 1; width will be 0 here so we can skip over those earlier. And it is easier to notice we are looking for a power of 2 constant. > + tree ntype = NULL_TREE; > + > + if (tree_int_cst_sgn (@1) > 0 && width > 0 Then you can move the `width > 0` also. > + && width <= MAX_FIXED_MODE_SIZE && width < (int) prec Trying to understand why you special case MAX_FIXED_MODE_SIZE here except for > MAX_FIXED_MODE_SIZE, we should use bitint type. And we should still optimize large bitint types before lowering them anyways. > + && wi::to_wide (@2) == -wi::set_bit_in_zero (width - 1, prec) I am trying to think of a better way of doing this. than using - with set_bit_in_zero. I think we can use shifted_mask with a true operand. > + && wi::bit_and_not (wide_int::from (get_nonzero_bits (@0), prec, > + TYPE_SIGN (TREE_TYPE (@0))), > + wi::mask (width, false, prec)) == 0) > + ntype = build_nonstandard_integer_type (width, SIGNED); > + } > + (if (ntype && (width == 1 || type_has_mode_precision_p (ntype))) > + (convert (convert:ntype @0)))))) Why are you special casing only type_has_mode_precision_p here? > + > +/* (signed:N) x < 0 ? (T) x - (1 << N) : (T) x -> (T) (signed:N) x, for an Same comment about the CST here. > + x with no bits above bit N - 1 possibly set. */ > +(simplify > + (cond (lt (convert@2 @0) integer_zerop) (plus (convert@1 @0) INTEGER_CST@3) > @1) > + (if (INTEGRAL_TYPE_P (type) && INTEGRAL_TYPE_P (TREE_TYPE (@2))) > + (with { unsigned int prec = TYPE_PRECISION (type); > + unsigned int width = TYPE_PRECISION (TREE_TYPE (@2)); } > + (if (width < prec > + && wi::to_wide (@3) == -wi::set_bit_in_zero (width, prec) > + && wi::bit_and_not (wide_int::from (get_nonzero_bits (@0), prec, > + TYPE_SIGN (TREE_TYPE (@0))), > + wi::mask (width, false, prec)) == 0) Similar comments here about set_bit_in_zero . Also even though you say signed in the comment, you don't check if you have signed for the lt (yes <0 will be false for unsigned but it is safer to have it). > + (convert @2))))) > + > /* Optimize x >> x into 0 */ > (simplify > (rshift @0 @0) > diff --git a/gcc/testsuite/gcc.dg/torture/sext-idiom-1.c > b/gcc/testsuite/gcc.dg/torture/sext-idiom-1.c > new file mode 100644 > index 00000000000..9ef27bd37f4 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/torture/sext-idiom-1.c > @@ -0,0 +1,125 @@ > +/* { dg-do run } */ > +/* { dg-require-effective-target int32 } */ > + > +typedef unsigned char u8; > +typedef unsigned short u16; > +typedef signed char s8; > + > +__attribute__((noipa)) static long > +sext_ref (unsigned long x, volatile int *pn) > +{ > + int n = *pn; > + volatile unsigned long b = 1ul << (n - 1); > + volatile unsigned long v = x & (b + b - 1); > + return (v & b) ? (long) v - (long) (b + b) : (long) v; > +} > + > +__attribute__((noipa)) static int > +xor_sub (int x, volatile int *pm) > +{ > + return (x ^ *pm) - *pm; > +} > + > +__attribute__((noipa)) static int > +xa8 (u8 x) > +{ > + return (int) ((x ^ 0x80u) - 0x80u); > +} > + > +__attribute__((noipa)) static int > +xa16 (u16 x) > +{ > + return (int) ((x ^ 0x8000u) - 0x8000u); > +} > + > +/* N = 1: the narrow type is a 1-bit signed type. */ > +__attribute__((noipa)) static int > +xa1 (u8 c) > +{ > + int x = c >> 7; > + return (x ^ 1) - 1; > +} > + > +__attribute__((noipa)) static int > +xa_signed (s8 x) > +{ > + return (int) (s8) (x ^ (s8) 0x80) - 128; > +} > + > +/* The xor is done in a signed type of precision 8 whose operand does fit > + 8 bits, so only the sign of the xor constant refuses the fold. */ > +__attribute__((noipa)) static int > +xa_signed_ranged (int a) > +{ > + s8 x = (s8) (a & 0x3f); > + s8 t = x ^ (s8) 0x80; > + return (int) t - 128; > +} > + > +__attribute__((noipa)) static int > +xa_unknown (int x) > +{ > + return (x ^ 0x80) - 0x80; > +} > + > +__attribute__((noipa)) static int > +cb8 (u8 x) > +{ > + unsigned e = x; > + if (e & 0x80) > + e -= 0x100; > + return (int) e; > +} > + > +__attribute__((noipa)) static int > +cb16 (u16 x) > +{ > + unsigned e = x; > + if (e & 0x8000) > + e -= 0x10000; > + return (int) e; > +} > + > +__attribute__((noipa)) static int > +cb_wrong_const (u8 x) > +{ > + int e = x; > + if ((s8) x < 0) > + e -= 0x80; > + return e; > +} > + > +int > +main (void) > +{ > + volatile int n8 = 8, n16 = 16, m80 = 0x80; > + int i; > + > + for (i = 0; i < 256; i++) > + { > + u8 x = (u8) i; > + if (xa8 (x) != sext_ref (x, &n8)) > + __builtin_abort (); > + if (cb8 (x) != sext_ref (x, &n8)) > + __builtin_abort (); > + if (xa1 (x) != -(x >> 7)) > + __builtin_abort (); > + if (xa_signed ((s8) i) != (int) (s8) ((s8) i ^ (s8) 0x80) - 128) > + __builtin_abort (); > + if (xa_signed_ranged (i) != (i & 0x3f) - 256) > + __builtin_abort (); > + if (xa_unknown (i - 128) != xor_sub (i - 128, &m80)) > + __builtin_abort (); > + if (cb_wrong_const (x) != (((s8) x < 0) ? (int) x - 0x80 : (int) x)) > + __builtin_abort (); > + } > + for (i = 0; i < 65536; i++) > + { > + u16 x = (u16) i; > + if (xa16 (x) != sext_ref (x, &n16)) > + __builtin_abort (); > + if (cb16 (x) != sext_ref (x, &n16)) > + __builtin_abort (); > + } > + return 0; > +} > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/sext-bitint-1.c > b/gcc/testsuite/gcc.dg/tree-ssa/sext-bitint-1.c > new file mode 100644 > index 00000000000..7bf527ce52a > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/sext-bitint-1.c > @@ -0,0 +1,31 @@ > +/* { dg-do compile } */ > +/* { dg-require-effective-target bitint575 } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > + > +typedef unsigned _BitInt(256) u256; > + > +/* N = 8 has an integer mode everywhere, so this folds. */ > +u256 > +sext8 (u256 x) > +{ > + return ((x & 0xff) ^ 0x80) - 0x80; > +} > + > +/* N = 191 has no mode at all. */ > +u256 > +no_sext_191 (u256 x) > +{ > + u256 m = ((u256) 1) << 190; > + return ((x & (m + m - 1)) ^ m) - m; > +} > + > +/* N = 128 has a mode only where MAX_FIXED_MODE_SIZE allows it; both ways > + round must compile. */ > +u256 > +sext128 (u256 x) > +{ > + u256 m = ((u256) 1) << 127; > + return ((x & (m + m - 1)) ^ m) - m; > +} > + > +/* { dg-final { scan-tree-dump-times "\\(signed char\\)" 1 "optimized" } } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/sext-cond-1.c > b/gcc/testsuite/gcc.dg/tree-ssa/sext-cond-1.c > new file mode 100644 > index 00000000000..6e32f7f2f43 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/sext-cond-1.c > @@ -0,0 +1,31 @@ > +/* { dg-do compile } */ > +/* { dg-require-effective-target int32 } */ > +/* { dg-options "-O2 -fdump-tree-phiopt2 -fdump-tree-optimized" } */ > + > +typedef unsigned char u8; > +typedef unsigned short u16; > + > +/* if (x & 0x80) x -= 0x100; is (signed char) x, and the sign test is > + already materialised for the branch. */ > +int > +sext8 (u8 x) > +{ > + unsigned e = x; > + if (e & 0x80) > + e -= 0x100; > + return (int) e; > +} > + > +int > +sext16 (u16 x) > +{ > + unsigned e = x; > + if (e & 0x8000) > + e -= 0x10000; > + return (int) e; > +} > + > +/* { dg-final { scan-tree-dump-not "if \\(" "phiopt2" } } */ > +/* { dg-final { scan-tree-dump-not "PHI <" "phiopt2" } } */ > +/* { dg-final { scan-tree-dump-not "if \\(" "optimized" } } */ > +/* { dg-final { scan-tree-dump-not "PHI <" "optimized" } } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/sext-cond-2.c > b/gcc/testsuite/gcc.dg/tree-ssa/sext-cond-2.c > new file mode 100644 > index 00000000000..17354658f39 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/sext-cond-2.c > @@ -0,0 +1,38 @@ > +/* { dg-do compile } */ > +/* { dg-require-effective-target int32 } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > + > +typedef unsigned char u8; > +typedef signed char s8; > + > +/* An unknown x: 0x100 is not the weight of x's bit 7. */ > +int > +no_sext_unknown (int x) > +{ > + int e = x; > + if (e & 0x80) > + e -= 0x100; > + return e; > +} > + > +/* The wrong constant: 0x80 is not 2^8. */ > +int > +no_sext_wrong_const (u8 x) > +{ > + int e = x; > + if ((s8) x < 0) > + e -= 0x80; > + return e; > +} > + > +/* The arms are not the tested value. */ > +int > +no_sext_other_value (u8 x, u8 y) > +{ > + int e = y; > + if ((s8) x < 0) > + e -= 0x100; > + return e; > +} > + > +/* { dg-final { scan-tree-dump-times "if \\(" 3 "optimized" } } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/sext-vector-1.c > b/gcc/testsuite/gcc.dg/tree-ssa/sext-vector-1.c > new file mode 100644 > index 00000000000..149422e27bd > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/sext-vector-1.c > @@ -0,0 +1,39 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -Wno-psabi -fdump-tree-optimized" } */ > + > +typedef unsigned char v8qi __attribute__((vector_size (8))); > +typedef unsigned int v4si __attribute__((vector_size (16))); > +typedef int s4si __attribute__((vector_size (16))); > + > +/* Both rules want a scalar integral type and an INTEGER_CST, so a vector > + must decline rather than reach get_nonzero_bits (PR127379). */ > +v4si > +vx (v4si x) > +{ > + v4si m = { 0x80, 0x80, 0x80, 0x80 }; > + return ((x & (v4si) { 0xff, 0xff, 0xff, 0xff }) ^ m) - m; > +} > + > +s4si > +vxs (s4si x) > +{ > + s4si m = { 0x80, 0x80, 0x80, 0x80 }; > + return ((x & (s4si) { 0xff, 0xff, 0xff, 0xff }) ^ m) - m; > +} > + > +v8qi > +vq (v8qi x) > +{ > + v8qi m = { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 }; > + v8qi l = { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f }; > + return ((x & l) ^ m) - m; > +} > + > +v4si > +vnu (v4si x) > +{ > + v4si m = { 0x80, 0x8000, 0x80, 0x80 }; > + return (x ^ m) - m; > +} > + > +/* { dg-final { scan-tree-dump-times " \\^ " 4 "optimized" } } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/sext-xor-1.c > b/gcc/testsuite/gcc.dg/tree-ssa/sext-xor-1.c > new file mode 100644 > index 00000000000..06fd081147e > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/sext-xor-1.c > @@ -0,0 +1,33 @@ > +/* { dg-do compile } */ > +/* { dg-require-effective-target int32 } */ > +/* { dg-options "-O2 -fdump-tree-forwprop1 -fdump-tree-optimized" } */ > + > +typedef unsigned char u8; > +typedef unsigned short u16; > + > +/* The narrow type supplies the range, so nonzero_bits has to be consulted: > + the PR125405 rule needs both masks in the IL. */ > +int > +sext8 (u8 x) > +{ > + return (int) ((x ^ 0x80u) - 0x80u); > +} > + > +int > +sext16 (u16 x) > +{ > + return (int) ((x ^ 0x8000u) - 0x8000u); > +} > + > +long > +sext8_from_mask (unsigned long x) > +{ > + unsigned long e = x & 0xff; > + return (long) ((e ^ 0x80UL) - 0x80UL); > +} > + > +/* { dg-final { scan-tree-dump-times "\\(signed char\\)" 2 "forwprop1" } } */ > +/* { dg-final { scan-tree-dump-times "\\(signed short\\)" 1 "forwprop1" } } > */ > +/* { dg-final { scan-tree-dump-times "\\(signed char\\)" 2 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times "\\(signed short\\)" 1 "optimized" } } > */ > +/* { dg-final { scan-tree-dump-not " \\^ " "optimized" } } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/sext-xor-2.c > b/gcc/testsuite/gcc.dg/tree-ssa/sext-xor-2.c > new file mode 100644 > index 00000000000..385ebbe8186 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/sext-xor-2.c > @@ -0,0 +1,49 @@ > +/* { dg-do compile } */ > +/* { dg-require-effective-target int32 } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > + > +typedef unsigned char u8; > +typedef signed char s8; > + > +/* An unknown x: (x ^ 128) - 128 is not a sign-extension. */ > +int > +no_sext_unknown (int x) > +{ > + return (x ^ 0x80) - 0x80; > +} > + > +/* The xor is done in a signed type of precision 8, so the conversion > + sign-extends it and the result is not (signed char) x: x = 1 gives > + -255. */ > +int > +no_sext_signed_xor (s8 x) > +{ > + return (int) (s8) (x ^ (s8) 0x80) - 128; > +} > + > +/* The same, with a range that does fit 8 bits: here nonzero_bits does not > + refuse, only the sign of the xor constant does. For x in [0, 63] the > + value is x - 256. */ > +int > +no_sext_signed_xor_ranged (int a) > +{ > + s8 x = (s8) (a & 0x3f); > + s8 t = x ^ (s8) 0x80; > + return (int) t - 128; > +} > + > +/* The two constants do not pair as 2^(N-1) and -2^(N-1). */ > +int > +no_sext_bad_pair (u8 x) > +{ > + return (int) ((x ^ 0x80u) - 0x40u); > +} > + > +/* Not a narrowing: the result type is 8 bits wide itself. */ > +int > +no_sext_not_narrowing (u8 x) > +{ > + return (int) (u8) ((x ^ 0x80u) - 0x80u); > +} > + > +/* { dg-final { scan-tree-dump-times " \\^ " 5 "optimized" } } */ > diff --git a/gcc/testsuite/gcc.target/arm/sext-idiom-1.c > b/gcc/testsuite/gcc.target/arm/sext-idiom-1.c > new file mode 100644 > index 00000000000..ccbc1a6a146 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/arm/sext-idiom-1.c > @@ -0,0 +1,47 @@ > +/* { dg-do compile } */ > +/* { dg-require-effective-target arm_arch_v6_ok } */ > +/* { dg-options "-O2" } */ > +/* { dg-add-options arm_arch_v6 } */ > +/* { dg-final { check-function-bodies "**" "" } } */ > + > +typedef unsigned short word; > + > +/* > +** cond_spelling: > +** sxtb r0, r0 > +** bx lr > +*/ > +int > +cond_spelling (unsigned char c) > +{ > + unsigned e = c; > + if (e & 0x80) > + e -= 0x100; > + return (int) e; > +} > + > +/* > +** xor_spelling: > +** sxtb r0, r0 > +** bx lr > +*/ > +int > +xor_spelling (unsigned char x) > +{ > + return (int) ((x ^ 0x80u) - 0x80u); > +} > + > +/* The 6502 relative-branch shape: the sign extension folds into the load, > + so neither the mask nor the subtract survives. */ > +word > +relative (unsigned char *mem, word pc) > +{ > + unsigned ea = mem[pc++]; > + if (ea & 0x80) > + ea -= 0x100; > + return (word) (pc + ea); > +} > + > +/* { dg-final { scan-assembler-times "ldrsb" 1 } } */ > +/* { dg-final { scan-assembler-not "#256" } } */ > +/* { dg-final { scan-assembler-not "eor" } } */ > -- > 2.55.0 >
