[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.
This commit is contained in:
Philipp Tomsich
2026-05-01 07:32:17 -06:00
committed by Jeff Law
parent 0727299846
commit c29a38d644
2 changed files with 56 additions and 0 deletions

View File

@@ -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

View File

@@ -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" } } */