https://gcc.gnu.org/g:4a97237ac39074b1083df4490d0fa6815c2ed595
commit r17-845-g4a97237ac39074b1083df4490d0fa6815c2ed595 Author: Philipp Tomsich <[email protected]> Date: Fri Mar 20 17:14:15 2026 +0100 ext-dce: narrow sign-extending loads to zero-extending when upper bits are dead The ext-dce pass tracks bit-level liveness and can replace sign extensions with zero extensions when the upper bits are dead. However, ext_dce_try_optimize_extension bails out when the inner operand is MEM rather than REG, missing the opportunity to narrow sign-extending loads (e.g. lh -> lhu on RISC-V, ldrsh -> ldrh on AArch64). Add handling for SIGN_EXTEND of MEM: when the liveness analysis has already determined the sign bits are dead, replace the sign-extending load with a zero-extending load via validate_change, which ensures the target has a matching instruction pattern. gcc/ChangeLog: * ext-dce.cc (ext_dce_try_optimize_extension): Handle SIGN_EXTEND of MEM by replacing with ZERO_EXTEND of MEM when upper bits are dead. gcc/testsuite/ChangeLog: * gcc.target/aarch64/ext-dce-1.c: New test. * gcc.target/riscv/ext-dce-3.c: New test. * gcc.target/riscv/ext-dce-4.c: New test. Co-authored-by: Konstantinos Eleftheriou <[email protected]> Diff: --- gcc/ext-dce.cc | 46 ++++++++++++++++++++++ gcc/testsuite/gcc.target/aarch64/ext-dce-1.c | 58 ++++++++++++++++++++++++++++ gcc/testsuite/gcc.target/riscv/ext-dce-3.c | 33 ++++++++++++++++ gcc/testsuite/gcc.target/riscv/ext-dce-4.c | 58 ++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+) diff --git a/gcc/ext-dce.cc b/gcc/ext-dce.cc index 86457d31af52..929c8530d17c 100644 --- a/gcc/ext-dce.cc +++ b/gcc/ext-dce.cc @@ -487,6 +487,52 @@ ext_dce_try_optimize_extension (rtx_insn *insn, rtx set) rtx src = SET_SRC (set); rtx inner = XEXP (src, 0); + /* For sign-extending loads from memory, try to replace with a + zero-extending load when the upper bits are dead. E.g. on RISC-V + this turns lh+zext.h into just lhu. */ + if (MEM_P (inner) && GET_CODE (src) == SIGN_EXTEND) + { + if (dump_file) + { + fprintf (dump_file, "Processing insn:\n"); + dump_insn_slim (dump_file, insn); + fprintf (dump_file, "Trying to narrow sign_extend to zero_extend:\n"); + print_rtl_single (dump_file, SET_SRC (set)); + } + + if (!dbg_cnt (::ext_dce)) + { + if (dump_file) + fprintf (dump_file, "Rejected due to debug counter.\n"); + return; + } + + rtx new_pattern = gen_rtx_ZERO_EXTEND (GET_MODE (src), inner); + int ok = validate_change (insn, &SET_SRC (set), new_pattern, false); + + rtx x = SET_DEST (set); + while (SUBREG_P (x) || GET_CODE (x) == ZERO_EXTRACT) + x = XEXP (x, 0); + + gcc_assert (REG_P (x)); + if (ok) + { + bitmap_set_bit (changed_pseudos, REGNO (x)); + remove_reg_equal_equiv_notes (insn, false); + } + + if (dump_file) + { + if (ok) + fprintf (dump_file, "Successfully transformed to:\n"); + else + fprintf (dump_file, "Failed transformation to:\n"); + print_rtl_single (dump_file, new_pattern); + fprintf (dump_file, "\n"); + } + return; + } + /* Avoid (subreg (mem)) and other constructs which may be valid RTL, but not useful for this optimization. */ if (!(REG_P (inner) || (SUBREG_P (inner) && REG_P (SUBREG_REG (inner))))) diff --git a/gcc/testsuite/gcc.target/aarch64/ext-dce-1.c b/gcc/testsuite/gcc.target/aarch64/ext-dce-1.c new file mode 100644 index 000000000000..0c6fb0005af0 --- /dev/null +++ b/gcc/testsuite/gcc.target/aarch64/ext-dce-1.c @@ -0,0 +1,58 @@ +/* Verify that ext-dce narrows sign-extending loads to zero-extending loads + when the upper bits are dead. */ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ +/* { dg-final { check-function-bodies "**" "" } } */ + +/* +** test_half: +** ... +** ldrh .* +** ... +*/ +/* Positive: halfword load-modify-store — sign bits are dead. */ +void +test_half (signed short *p) +{ + *p = (*p & 0xff00) | (0x00ff & (*p >> 8)); +} + +/* +** test_byte: +** ... +** ldrb .* +** ... +*/ +/* Positive: byte load-modify-store — sign bits are dead. */ +void +test_byte (signed char *p) +{ + *p = (*p & 0xf0) | (0x0f & (*p >> 4)); +} + +/* +** test_half_sign_needed: +** ... +** ldrsb .* +** ... +*/ +/* Negative: arithmetic right shift needs the sign extension. */ +int +test_half_sign_needed (signed short *p) +{ + return *p >> 8; +} + +/* +** test_half_compare: +** ... +** ldrsh .* +** ... +*/ +/* Negative: sign-dependent comparison. */ +int +test_half_compare (signed short *p) +{ + return *p < 0; +} + diff --git a/gcc/testsuite/gcc.target/riscv/ext-dce-3.c b/gcc/testsuite/gcc.target/riscv/ext-dce-3.c new file mode 100644 index 000000000000..e62655ae286c --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/ext-dce-3.c @@ -0,0 +1,33 @@ +/* Verify ext-dce for word loads on RV64: lw sign-extends to 64 bits, + so when the upper 32 bits are dead lw should be narrowed to lwu. */ +/* { dg-do compile } */ +/* { dg-require-effective-target rv64 } */ +/* { dg-options "-march=rv64gc -mabi=lp64d -O2" } */ +/* { dg-final { check-function-bodies "**" "" } } */ + +/* +** test_word: +** ... +** lwu .* +** ... +*/ +/* Positive: word load-modify-store — upper 32 bits are dead. */ +void +test_word (signed int *p) +{ + *p = (*p & 0xffff0000) | (0x0000ffff & ((unsigned int)*p >> 16)); +} + +/* +** test_word_sign_needed: +** ... +** lw .* +** ... +*/ +/* Negative: return value is long — sign extension is needed. */ +long +test_word_sign_needed (signed int *p) +{ + return *p; +} + diff --git a/gcc/testsuite/gcc.target/riscv/ext-dce-4.c b/gcc/testsuite/gcc.target/riscv/ext-dce-4.c new file mode 100644 index 000000000000..21f2d9a13ebb --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/ext-dce-4.c @@ -0,0 +1,58 @@ +/* Verify that ext-dce narrows sign-extending loads to zero-extending loads + when the upper bits are dead. */ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ +/* { dg-final { check-function-bodies "**" "" } } */ + +/* +** test_half: +** ... +** lhu .* +** ... +*/ +/* Positive: halfword load-modify-store — sign bits are dead. */ +void +test_half (signed short *p) +{ + *p = (*p & 0xff00) | (0x00ff & (*p >> 8)); +} + +/* +** test_byte: +** ... +** lbu .* +** ... +*/ +/* Positive: byte load-modify-store — sign bits are dead. */ +void +test_byte (signed char *p) +{ + *p = (*p & 0xf0) | (0x0f & (*p >> 4)); +} + +/* +** test_half_sign_needed: +** ... +** lh .* +** ... +*/ +/* Negative: arithmetic right shift needs the sign extension. */ +int +test_half_sign_needed (signed short *p) +{ + return *p >> 8; +} + +/* +** test_half_compare: +** ... +** lh .* +** ... +*/ +/* Negative: sign-dependent comparison. */ +int +test_half_compare (signed short *p) +{ + return *p < 0; +} +
