]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
[RISC-V][PR target/124009] Improve select between 2^n and 0 on RISC-V
authorJeff Law <jeffrey.law@oss.qualcomm.com>
Sun, 3 May 2026 12:45:19 +0000 (06:45 -0600)
committerJeff Law <jeffrey.law@oss.qualcomm.com>
Sun, 3 May 2026 13:03:23 +0000 (07:03 -0600)
So this was something I noticed a while back, I'm pretty sure while throwing
hot blocks into an LLM to see what the LLM thought might be optimizable.  In
this case it was mcf from spec2017.

So the basic idea is for code like this:

int foo(int x, int y) { return (y < x) ? 1 : -1; }

We get something like this for rv64gcbv_zicond:

        slt     a1,a1,a0        # 27    [c=4 l=4]  slt_didi3
        li      a5,2            # 28    [c=4 l=4]  *movdi_64bit/1
        czero.eqz       a0,a5,a1        # 29    [c=4 l=4]  *czero.eqz.didi
        addi    a0,a0,-1        # 17    [c=4 l=4]  *adddi3/1

That's not bad, in particular it avoids a likely tough to predict conditional
branch.  But we can do better.

Essentially the code is selecting between 1 and -1.  So if we take the output
of the SLT (0/1) shift it left by one position (0/2), then subtract one we get
a select for -1, 1.

After this patch we get the expected:

        slt     a1,a1,a0        # 28    [c=4 l=4]  slt_didi3
        slli    a0,a1,1 # 29    [c=4 l=4]  ashldi3
        addi    a0,a0,-1        # 17    [c=4 l=4]  *adddi3/1

It's probably not any faster on a modern design, but it will encode more
efficiently, saving either 2 or 4 bytes (potentially improving performance by
getting more ops per fetch block).    There's some very obvious
generalizations.  We can select between 2^n and 0, we can select between 2^n-1
and -1.  But we can also do things like select between 3, 5 or 9 and 0 (think
using shNadd where both source operands are the output of the slt).    There's
all kinds of interesting possibilities here.

The key is to implement a splitter which handles 2^n and 0.  Once that is in
place pre-existing code will handle the 2^n-1 and -1 case automatically.  While
cases like selecting between 9 and 0 aren't yet handled, it would be a fairly
simple extension to these new splitters with the basic framework in place.

Anyway, while working on this I realized the scc_0 iterator didn't include
any_lt, which seems like a dreadful oversight on my part. So I fixed that as
well.

Given the high degree of non-orthogonality in the sCC capabilities of the
RISC-V ISA, this is actually several splitters to deal with the different cases
of sCC we can handle in a single instruction.

Tested on riscv32-elf and riscv64-elf.  Will wait for pre-commit CI before
moving forward.

PR target/124009
gcc/

* config/riscv/iterators.md (scc_0): Add any_lt.
* config/riscv/zicond.md: Add splitters to select between 2^n and 0.

gcc/testsuite/

* gcc.target/riscv/pr124009.c: New test.

gcc/config/riscv/iterators.md
gcc/config/riscv/zicond.md
gcc/testsuite/gcc.target/riscv/pr124009.c [new file with mode: 0644]

index 192556e92e27dc6c8b0cc53655a6ed1e8c6c86b7..076911e5e7f1d0ccf9b1b69385d97649d2523092 100644 (file)
 (define_code_iterator any_eq [eq ne])
 
 ;; Iterators for conditions we can emit a sCC against 0 or a reg directly
-(define_code_iterator scc_0  [eq ne gt gtu])
+(define_code_iterator scc_0  [any_eq any_gt any_lt])
 
 ; atomics code iterator
 (define_code_iterator any_atomic [plus ior xor and])
index ac1aa3a6b7ea6add15bddd8bbedb5eadbc5e2f40..34fe2ce29c6d0ab5cac92205e9fc2a58c2885e19 100644 (file)
   "operands[4] = gen_reg_rtx (word_mode);
    operands[5] = gen_reg_rtx (word_mode);")
 
+;; We want to select between 2^n and 0.  Use an sCC insn to generate 1/0, then
+;; left shift that by N to get the final result
+(define_split
+  [(set (match_operand:X 0 "register_operand")
+       (if_then_else:X
+        (scc_0:X (match_operand:X 1 "register_operand")
+                 (const_int 0))
+        (match_operand 2 "const_int_operand")
+        (const_int 0)))
+   (clobber (match_operand:X 3 "register_operand"))]
+  "exact_log2 (INTVAL (operands[2])) >= 0"
+  [(set (match_dup 3) (scc_0:X (match_dup 1) (const_int 0)))
+   (set (match_dup 0) (ashift:X (match_dup 3) (match_dup 4)))]
+  { operands[4] = GEN_INT (exact_log2 (INTVAL (operands[2]))); })
+
+(define_split
+  [(set (match_operand:X 0 "register_operand")
+       (if_then_else:X
+        (any_gt:X (match_operand:X 1 "register_operand")
+                  (match_operand:X 2 "reg_or_0_operand"))
+        (match_operand 3 "const_int_operand")
+        (const_int 0)))
+   (clobber (match_operand:X 4 "register_operand"))]
+  "exact_log2 (INTVAL (operands[3])) >= 0"
+  [(set (match_dup 4) (any_gt:X (match_dup 1) (match_dup 2)))
+   (set (match_dup 0) (ashift:X (match_dup 4) (match_dup 5)))]
+  { operands[5] = GEN_INT (exact_log2 (INTVAL (operands[3]))); })
+
+(define_split
+  [(set (match_operand:X 0 "register_operand")
+       (if_then_else:X
+        (any_ge:X (match_operand:X 1 "register_operand")
+                  (const_int 1))
+        (match_operand 2 "const_int_operand")
+        (const_int 0)))
+   (clobber (match_operand:X 3 "register_operand"))]
+  "exact_log2 (INTVAL (operands[2])) >= 0"
+  [(set (match_dup 3) (any_gt:X (match_dup 1) (const_int 1)))
+   (set (match_dup 0) (ashift:X (match_dup 3) (match_dup 4)))]
+  { operands[4] = GEN_INT (exact_log2 (INTVAL (operands[2]))); })
+
+(define_split
+  [(set (match_operand:X 0 "register_operand")
+       (if_then_else:X
+        (any_lt:X (match_operand:X 1 "register_operand")
+                  (match_operand:X 2 "arith_operand"))
+        (match_operand 3 "const_int_operand")
+        (const_int 0)))
+   (clobber (match_operand:X 4 "register_operand"))]
+  "exact_log2 (INTVAL (operands[3])) >= 0"
+  [(set (match_dup 4) (any_lt:X (match_dup 1) (match_dup 2)))
+   (set (match_dup 0) (ashift:X (match_dup 4) (match_dup 5)))]
+  { operands[5] = GEN_INT (exact_log2 (INTVAL (operands[3]))); })
+
+(define_split
+  [(set (match_operand:X 0 "register_operand")
+       (if_then_else:X
+        (any_le:X (match_operand:X 1 "register_operand")
+                  (match_operand:X 2 "sle_operand"))
+        (match_operand 3 "const_int_operand")
+        (const_int 0)))
+   (clobber (match_operand:X 4 "register_operand"))]
+  "exact_log2 (INTVAL (operands[3])) >= 0"
+  [(set (match_dup 4) (any_le:X (match_dup 1) (match_dup 2)))
+   (set (match_dup 0) (ashift:X (match_dup 4) (match_dup 5)))]
+  { operands[5] = GEN_INT (exact_log2 (INTVAL (operands[3]))); })
diff --git a/gcc/testsuite/gcc.target/riscv/pr124009.c b/gcc/testsuite/gcc.target/riscv/pr124009.c
new file mode 100644 (file)
index 0000000..6f541ca
--- /dev/null
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv64gcbv_zicond -mabi=lp64d" { target { rv64 } } } */
+/* { dg-options "-O2 -march=rv32gcbv_zicond -mabi=ilp32" { target { rv32 } } } */
+
+int foo(int x, int y) { return (y < x) ? 1 : -1; }
+
+
+/* { dg-final { scan-assembler-times {slli\t} 1 } } */
+/* { dg-final { scan-assembler-times {addi\t} 1 } } */
+/* { dg-final { scan-assembler-not {czero.eqz\t} } } */
+