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.
Ref: vrull/gcc#253
---
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 ff46ffdb4566..7a62d3bd3bfb 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -5801,6 +5801,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 000000000000..50b3a8b9dd8f
--- /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" } } */
--
2.34.1