From c29a38d644bb6984875d92acac9b30a133dacd6f Mon Sep 17 00:00:00 2001 From: Philipp Tomsich Date: Fri, 1 May 2026 07:32:17 -0600 Subject: [PATCH] [PATCH GCC17-stage1] riscv: Optimize power-of-2 boundary comparisons in conditional moves In riscv_expand_conditional_move, detect unsigned comparisons against power-of-2 boundaries and convert them to shift-based equality tests. This avoids materializing large constants (e.g. 2^56 - 1) that may require multiple instructions (bseti + sltu), replacing them with a single srli that feeds directly into czero.eqz/czero.nez. The transformation handles four cases: GTU x, (2^N-1) -> NE (x >> N), 0 LEU x, (2^N-1) -> EQ (x >> N), 0 GEU x, 2^N -> NE (x >> N), 0 LTU x, 2^N -> EQ (x >> N), 0 For example, `(a & (0xff << 56)) ? b : 0` previously generated: bseti a5, zero, 56 sltu a0, a0, a5 czero.nez a0, a1, a0 Now generates: srli a0, a0, 56 czero.eqz a0, a1, a0 Existing define_split patterns in riscv.md (lines 3727-3748) handle the same optimization for standalone SCC operations, but they don't fire in the conditional move expansion path which goes through riscv_expand_int_scc directly. gcc/ChangeLog: * config/riscv/riscv.cc (riscv_expand_conditional_move): Convert unsigned comparisons against power-of-2 boundaries to shift-based equality tests. gcc/testsuite/ChangeLog: * gcc.target/riscv/zicond-shift-cond.c: New test. --- gcc/config/riscv/riscv.cc | 36 +++++++++++++++++++ .../gcc.target/riscv/zicond-shift-cond.c | 20 +++++++++++ 2 files changed, 56 insertions(+) create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-shift-cond.c diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc index b0365f68234..e09587386ca 100644 --- a/gcc/config/riscv/riscv.cc +++ b/gcc/config/riscv/riscv.cc @@ -5808,6 +5808,42 @@ riscv_expand_conditional_move (rtx dest, rtx op, rtx cons, rtx alt) ? word_mode : dst_mode); bool invert = false; + /* For unsigned comparisons against power-of-2 boundaries, convert + to a shift-based equality test. This avoids materializing large + constants like (2^N - 1) which may require multiple instructions. + GTU x, (2^N-1) -> NE (x >> N), 0 + LEU x, (2^N-1) -> EQ (x >> N), 0 + GEU x, 2^N -> NE (x >> N), 0 + LTU x, 2^N -> EQ (x >> N), 0 */ + if (INTEGRAL_MODE_P (mode0) && CONST_INT_P (op1)) + { + int shift = -1; + rtx_code new_code = UNKNOWN; + + if ((code == GTU || code == LEU) + && exact_log2 (UINTVAL (op1) + 1) > 0) + { + shift = exact_log2 (UINTVAL (op1) + 1); + new_code = (code == GTU) ? NE : EQ; + } + else if ((code == GEU || code == LTU) + && exact_log2 (UINTVAL (op1)) > 0) + { + shift = exact_log2 (UINTVAL (op1)); + new_code = (code == GEU) ? NE : EQ; + } + + if (shift > 0) + { + op0 = expand_simple_binop (mode0, LSHIFTRT, op0, + GEN_INT (shift), NULL_RTX, + 1, OPTAB_DIRECT); + op1 = const0_rtx; + code = new_code; + op = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1); + } + } + /* Canonicalize the comparison. It must be an equality comparison of integer operands, or with SFB it can be any comparison of integer operands. If it isn't, then emit an SCC instruction diff --git a/gcc/testsuite/gcc.target/riscv/zicond-shift-cond.c b/gcc/testsuite/gcc.target/riscv/zicond-shift-cond.c new file mode 100644 index 00000000000..50b3a8b9dd8 --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/zicond-shift-cond.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -march=rv64gc_zicond -mabi=lp64d" } */ +/* { dg-skip-if "" { *-*-* } { "-flto" } } */ + +/* Verify that power-of-2 boundary comparisons use shift instead of + materializing large constants (vrull/gcc#253). */ + +long long high_byte (long long a, long long b) +{ + return (a & (0xffULL << 56)) ? b : 0; +} + +long long high_half (long long a, long long b) +{ + return (a & (0xffffULL << 48)) ? b : 0; +} + +/* { dg-final { scan-assembler-times "srli\t" 2 } } */ +/* { dg-final { scan-assembler-times "czero\\.eqz\t" 2 } } */ +/* { dg-final { scan-assembler-not "bseti\t" } } */