]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
[RISC-V] Improve SI->DI zero/sign extension patterns for RISC-V
authorJeff Law <jeffrey.law@oss.qualcomm.com>
Mon, 18 May 2026 21:24:02 +0000 (15:24 -0600)
committerJeff Law <jeffrey.law@oss.qualcomm.com>
Mon, 18 May 2026 21:33:21 +0000 (15:33 -0600)
--  From the original submission in Oct 2025 --

So this is a slightly scaled back variant of a patch I've been working
on.  I'd originally planned to handle both zero and sign extensions, but
there's some fallout with the sign extension adjustments that I'm going
to need more time to tackle.  This piece stands on its own and unlocks a
subsequent patch to improve codegen.  No sense in having it possibly
miss the merge window.

This patch adjusts the core zero-extension patterns as well as one
closely related combiner pattern.

For the named expanders, we now generate shift pairs if the Zba/Zbb
extensions are not available and the source operand is a REG.  Things
are kept as-is for MEMs.

The existing define_insn_and_split it turned into a define_insn that
only handles MEM sources.  Those instructions are always available, so
no need to mess with shift pairs.  This avoids regressions with a
follow-up patch which enhances a closely related combiner pattern.

That closely related combiner pattern is a define_insn_and_split which
can now turn into a simpler define_split.  So that's adjusted as well.

The net is we drop 3 define_insn_and_splits and occasionally get better
code as a result.  It also makes it possible to improve some additional
cases which I'll handle as a followup.

The test changes are minimal and mostly related to making sure we have
the right Zb* things enabled based on what the test relies on under the
hood.  It's not even clear that part of the change is strictly necessary
anymore.  I see it more as test hygiene than anything.

This has been bootstrapped and regression tested on the Pioneer which is
a good test since it doesn't have any of the Zb* extensions and thus
relies heavily on the shift-pair approach to zero extensions.
riscv32-elf and riscv64-elf have also been regression tested.  The BPI
hasn't started chewing on this patch yet.

--

Subsequent changes were to the testsuite to ensure that --with-cpu or
--with-tune configure time options wouldn't impact the testresults.

gcc/

* config/riscv/riscv.cc (riscv_rtx_costs): Properly cost pack insns
for Zbkb.
* config/riscv/riscv.md (zero_extendsidi2): Expand into shift pairs
when the appropriate instructions are not available.
(zero_extendhi<GPR:mode>2): Simlarly.
(*zero_extendsidi2_internal): Make a simple define_insn.  Only handle
MEM sources.
(*zero_extendhi<GPR2:mode>2): Similarly.
(zero_extendsidi2_shifted): Turn into a define_split and generalize
to handle more constants.
* config/riscv/predicates.md (dimode_shift_operand): New predicate.

gcc/testsuite/

* gcc.target/riscv/slt-1.c: Skip for -Oz as well.  Set explicit branch
cost.
* gcc.target/riscv/zba-shNadd-04.c: Add Zbb to command line switches.
* gcc.target/riscv/zba-slliuw.c: Add Zbs to command line switches.
* gcc.target/riscv/zbs-zext.c: Add Zbs to command line switches.
* gcc.target/riscv/shift-shift-6.c: New test.
* gcc.target/riscv/shift-shift-7.c: New test.
* gcc.target/riscv/amo/a-rvwmo-load-relaxed.c: Accept lh or lhu.
* gcc.target/riscv/amo/a-ztso-load-relaxed.c: Accept lh or lhu.
* gcc.target/riscv/amo/zalasr-rvwmo-load-relaxed.c: Accept lh or lhu.
* gcc.target/riscv/amo/zalasr-ztso-load-relaxed.c: Accept lh or lhu.
* gcc.target/riscv/pr105314.c: Set explicitly branch cost.
* gcc.target/riscv/pr105314-rtl.c: Set explicitly branch cost.

15 files changed:
gcc/config/riscv/predicates.md
gcc/config/riscv/riscv.cc
gcc/config/riscv/riscv.md
gcc/testsuite/gcc.target/riscv/amo/a-rvwmo-load-relaxed.c
gcc/testsuite/gcc.target/riscv/amo/a-ztso-load-relaxed.c
gcc/testsuite/gcc.target/riscv/amo/zalasr-rvwmo-load-relaxed.c
gcc/testsuite/gcc.target/riscv/amo/zalasr-ztso-load-relaxed.c
gcc/testsuite/gcc.target/riscv/pr105314-rtl.c
gcc/testsuite/gcc.target/riscv/pr105314.c
gcc/testsuite/gcc.target/riscv/shift-shift-6.c [new file with mode: 0644]
gcc/testsuite/gcc.target/riscv/shift-shift-7.c [new file with mode: 0644]
gcc/testsuite/gcc.target/riscv/slt-1.c
gcc/testsuite/gcc.target/riscv/zba-shNadd-04.c
gcc/testsuite/gcc.target/riscv/zba-slliuw.c
gcc/testsuite/gcc.target/riscv/zbs-zext.c

index 47e634ad87f118382515cc3989d21dbe18fb5097..df1a76049f40d5fe02acf62ac14f4afc5dbb5339 100644 (file)
                     : IN_RANGE (REGNO (op), S0_REGNUM, S1_REGNUM)
                     || IN_RANGE (REGNO (op), S2_REGNUM, S7_REGNUM)")))
 
+(define_predicate "dimode_shift_operand"
+  (and (match_code "const_int")
+       (match_test "IN_RANGE (INTVAL (op), 1, GET_MODE_BITSIZE (DImode) - 1)")))
+
 ;; Only use branch-on-bit sequences when the mask is not an ANDI immediate.
 (define_predicate "branch_on_bit_operand"
   (and (match_code "const_int")
index 2a2193767c7c5274aa165841bdd720fe81703746..2fb649840605ad56fd9877586e13877591964f34 100644 (file)
@@ -4435,6 +4435,26 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
       gcc_fallthrough ();
     case IOR:
     case XOR:
+      /* packh for zbkb.  Alternate forms haven't shown up as a
+        costing problem.  Obviously we can add the additional
+        variants if needed.  */
+      if (TARGET_ZBKB
+         && GET_CODE (x) == IOR
+         && GET_CODE (XEXP (x, 0)) == AND
+         && GET_CODE (XEXP (XEXP (x, 0), 0)) == ASHIFT
+         && register_operand (XEXP (XEXP (XEXP (x, 0), 0), 0), word_mode)
+         && CONST_INT_P (XEXP (XEXP (XEXP (x, 0), 0), 1))
+         && INTVAL (XEXP (XEXP (XEXP (x, 0), 0), 1)) == 8
+         && CONST_INT_P (XEXP (XEXP (x, 0), 1))
+         && INTVAL (XEXP (XEXP (x, 0), 1)) == 0xff00
+         && GET_CODE (XEXP (x, 1)) == ZERO_EXTEND
+         && GET_MODE (XEXP (x, 1)) == word_mode
+         && GET_MODE (XEXP (XEXP (x, 1), 0)) == QImode)
+       {
+         *total = COSTS_N_INSNS (1);
+         return true;
+       }
+
       /* orn, andn and xorn pattern for zbb.  */
       if (TARGET_ZBB
          && GET_CODE (XEXP (x, 0)) == NOT)
index 869061e18ae1c9ea5c88e4db5d125a680bed9f3c..322910c577b1060cd0a47531e7d58d4830fbecf1 100644 (file)
        (zero_extend:DI (match_operand:SI 1 "nonimmediate_operand")))]
   "TARGET_64BIT"
 {
+  /* If the source is a suitably extended subreg, then this is just
+     a simple move.  */
   if (SUBREG_P (operands[1]) && SUBREG_PROMOTED_VAR_P (operands[1])
       && SUBREG_PROMOTED_UNSIGNED_P (operands[1]))
     {
       emit_insn (gen_movdi (operands[0], SUBREG_REG (operands[1])));
       DONE;
     }
+
+  /* If the source is a register and we do not have ZBA or similar
+     extensions with similar capabilities, then emit the two
+     shifts now.  */
+  if (!TARGET_ZBA && !TARGET_XTHEADBB
+      && !TARGET_XTHEADMEMIDX && !TARGET_XANDESPERF
+      && register_operand (operands[1], SImode))
+    {
+      /* Intermediate register.  */
+      rtx ireg = gen_reg_rtx (DImode);
+      operands[1] = gen_lowpart (DImode, operands[1]);
+      rtx shiftval = GEN_INT (32);
+      rtx t = gen_rtx_ASHIFT (DImode, operands[1], shiftval);
+      emit_move_insn (ireg, t);
+      t = gen_rtx_LSHIFTRT (DImode, ireg, shiftval);
+      emit_move_insn (operands[0], t);
+      DONE;
+    }
 })
 
-(define_insn_and_split "*zero_extendsidi2_internal"
-  [(set (match_operand:DI     0 "register_operand"     "=r,r")
-       (zero_extend:DI
-           (match_operand:SI 1 "nonimmediate_operand" " r,m")))]
+(define_insn "*zero_extendsidi2_internal"
+  [(set (match_operand:DI     0 "register_operand"     "=r")
+       (zero_extend:DI (match_operand:SI 1 "memory_operand" "m")))]
   "TARGET_64BIT && !TARGET_ZBA && !TARGET_XTHEADBB && !TARGET_XTHEADMEMIDX
-   && !TARGET_XANDESPERF
-   && !(REG_P (operands[1]) && VL_REG_P (REGNO (operands[1])))"
-  "@
-   #
-   lwu\t%0,%1"
-  "&& reload_completed
-   && REG_P (operands[1])
-   && !paradoxical_subreg_p (operands[0])"
-  [(set (match_dup 0)
-       (ashift:DI (match_dup 1) (const_int 32)))
-   (set (match_dup 0)
-       (lshiftrt:DI (match_dup 0) (const_int 32)))]
-  { operands[1] = gen_lowpart (DImode, operands[1]); }
-  [(set_attr "move_type" "shift_shift,load")
+   && !TARGET_XANDESPERF"
+  "lwu\t%0,%1"
+  [(set_attr "move_type" "load")
    (set_attr "type" "load")
    (set_attr "mode" "DI")])
 
   [(set (match_operand:GPR    0 "register_operand")
        (zero_extend:GPR
            (match_operand:HI 1 "nonimmediate_operand")))]
-  "")
+  ""
+{
+  /* If the source is a suitably extended subreg, then this is just
+     a simple move.  */
+  if (SUBREG_P (operands[1]) && SUBREG_PROMOTED_VAR_P (operands[1])
+      && SUBREG_PROMOTED_UNSIGNED_P (operands[1]))
+    {
+      emit_insn (gen_mov<GPR:mode> (operands[0], SUBREG_REG (operands[1])));
+      DONE;
+    }
 
-(define_insn_and_split "*zero_extendhi<GPR:mode>2"
-  [(set (match_operand:GPR    0 "register_operand"     "=r,r")
-       (zero_extend:GPR
-           (match_operand:HI 1 "nonimmediate_operand" " r,m")))]
+  /* If the source is a register and we do not have ZBB or similar
+     extensions with similar capabilities, then emit the two
+     shifts now.  */
+  if (!TARGET_ZBB && !TARGET_XTHEADBB
+      && !TARGET_XTHEADMEMIDX && !TARGET_XANDESPERF
+      && register_operand (operands[1], HImode))
+    {
+      /* Intermediate register.  */
+      rtx ireg = gen_reg_rtx (<GPR:MODE>mode);
+      operands[1] = gen_lowpart (<GPR:MODE>mode, operands[1]);
+      rtx shiftval = GEN_INT (GET_MODE_BITSIZE (<GPR:MODE>mode) - 16);
+      rtx t = gen_rtx_ASHIFT (<GPR:MODE>mode, operands[1], shiftval);
+      emit_move_insn (ireg, t);
+      t = gen_rtx_LSHIFTRT (<GPR:MODE>mode, ireg, shiftval);
+      emit_move_insn (operands[0], t);
+      DONE;
+    }
+})
+
+(define_insn "*zero_extendhi<GPR:mode>2"
+  [(set (match_operand:GPR    0 "register_operand"     "=r")
+       (zero_extend:GPR (match_operand:HI 1 "memory_operand" "m")))]
   "!TARGET_ZBB && !TARGET_XTHEADBB && !TARGET_XTHEADMEMIDX
    && !TARGET_XANDESPERF"
-  "@
-   #
-   lhu\t%0,%1"
-  "&& reload_completed
-   && REG_P (operands[1])
-   && !paradoxical_subreg_p (operands[0])"
-  [(set (match_dup 0)
-       (ashift:GPR (match_dup 1) (match_dup 2)))
-   (set (match_dup 0)
-       (lshiftrt:GPR (match_dup 0) (match_dup 2)))]
-  {
-    operands[1] = gen_lowpart (<GPR:MODE>mode, operands[1]);
-    operands[2] = GEN_INT(GET_MODE_BITSIZE(<GPR:MODE>mode) - 16);
-  }
-  [(set_attr "move_type" "shift_shift,load")
+  "lhu\t%0,%1"
+  [(set_attr "move_type" "load")
    (set_attr "type" "load")
    (set_attr "mode" "<GPR:MODE>")])
 
 ;; Handle SImode to DImode zero-extend combined with a left shift.  This can
 ;; occur when unsigned int is used for array indexing.  Split this into two
 ;; shifts.  Otherwise we can get 3 shifts.
-
-(define_insn_and_split "zero_extendsidi2_shifted"
-  [(set (match_operand:DI 0 "register_operand" "=r")
-       (and:DI (ashift:DI (match_operand:DI 1 "register_operand" "r")
-                          (match_operand:QI 2 "immediate_operand" "I"))
-               (match_operand 3 "immediate_operand" "")))
-   (clobber (match_scratch:DI 4 "=&r"))]
-  "TARGET_64BIT && !TARGET_ZBA
-   && ((INTVAL (operands[3]) >> INTVAL (operands[2])) == 0xffffffff)"
-  "#"
-  "&& reload_completed"
-  [(set (match_dup 4)
-       (ashift:DI (match_dup 1) (const_int 32)))
-   (set (match_dup 0)
-       (lshiftrt:DI (match_dup 4) (match_dup 5)))]
-  "operands[5] = GEN_INT (32 - (INTVAL (operands [2])));"
-  [(set_attr "type" "shift")
-   (set_attr "mode" "DI")])
+(define_split
+  [(set (match_operand:DI 0 "register_operand")
+       (and:DI (ashift:DI (match_operand:DI 1 "register_operand")
+                          (match_operand:QI 2 "dimode_shift_operand"))
+               (match_operand 3 "consecutive_bits_operand")))
+   (clobber (match_operand:DI 4 "register_operand"))]
+  "TARGET_64BIT
+   && riscv_shamt_matches_mask_p (INTVAL (operands[2]), INTVAL (operands[3]))
+   && !(TARGET_ZBA && clz_hwi (INTVAL (operands[3])) <= 32)"
+  [(set (match_dup 4) (ashift:DI (match_dup 1) (match_dup 5)))
+   (set (match_dup 0) (lshiftrt:DI (match_dup 4) (match_dup 6)))]
+{
+  unsigned HOST_WIDE_INT mask = INTVAL (operands[3]);
+  int leading  = clz_hwi (mask);
+  int trailing = ctz_hwi (mask);
+  operands[5] = GEN_INT (leading + trailing);
+  operands[6] = GEN_INT (leading);
+})
 
 ;; Handle logical AND feeding an equality test against zero where an operand
 ;; to the AND is a constant requiring synthesis.  Because we only care about
index 01a1e3c5c8635e134ad642b7e01130a74dc91db0..bee32d07a624734bbd82e664173e0bb6c7bdab95 100644 (file)
@@ -30,7 +30,7 @@ void atomic_load_int_relaxed (int* bar, int* baz)
 
 /*
 ** atomic_load_short_relaxed:
-**     lh\t[atx][0-9]+,0\(a0\)
+**     (lh|lhu)\t[atx][0-9]+,0\(a0\)
 **     sh\t[atx][0-9]+,0\(a1\)
 **     ret
 */
index 2ee094679eb4531d8e7f017463ac4ae050b38091..4090afa5bc84161ce7e612846333d7ee46dc9d4c 100644 (file)
@@ -30,7 +30,7 @@ void atomic_load_int_relaxed (int* bar, int* baz)
 
 /*
 ** atomic_load_short_relaxed:
-**     lh\t[atx][0-9]+,0\(a0\)
+**     (lh|lhu)\t[atx][0-9]+,0\(a0\)
 **     sh\t[atx][0-9]+,0\(a1\)
 **     ret
 */
index eee5f8d5bb98e57634fcf5d4ce21de9a3d46d0cd..44551776753b399ba139e6353314cdc2f1bacbba 100644 (file)
@@ -30,7 +30,7 @@ void atomic_load_int_relaxed (int* bar, int* baz)
 
 /*
 ** atomic_load_short_relaxed:
-**     lh\t[atx][0-9]+,0\(a0\)
+**     (lh|lhu)\t[atx][0-9]+,0\(a0\)
 **     sh\t[atx][0-9]+,0\(a1\)
 **     ret
 */
index ce2c41563a473e93227e3a87a40f99bd75515b74..e38f5d32894df382bde9a29611a8b8ca9171edc0 100644 (file)
@@ -30,7 +30,7 @@ void atomic_load_int_relaxed (int* bar, int* baz)
 
 /*
 ** atomic_load_short_relaxed:
-**     lh\t[atx][0-9]+,0\(a0\)
+**     (lh|lhu)\t[atx][0-9]+,0\(a0\)
 **     sh\t[atx][0-9]+,0\(a1\)
 **     ret
 */
index 570918f9d9ab10ea0852bba358547bfd01f015fc..027b65fe17fd6db293daab2c02399ff48b5d45b1 100644 (file)
@@ -2,7 +2,7 @@
 /* { dg-do compile } */
 /* { dg-require-effective-target rv64 } */
 /* { dg-skip-if "" { *-*-* } { "-march=*zicond*" "-O0" "-Og" "-Os" "-Oz" "-flto" } } */
-/* { dg-options "-fdump-rtl-ce1" } */
+/* { dg-options "-fdump-rtl-ce1 -mbranch-cost=4" } */
 
 long __RTL (startwith ("ce1"))
 foo (long a, long b, long c)
index 75f6ecda2bbec29ae88dab0b8b7c87973a9eaeaa..861ac7b25515457937fff3372c6c39c97a9a87f8 100644 (file)
@@ -1,7 +1,7 @@
 /* PR rtl-optimization/105314 */
 /* { dg-do compile } */
 /* { dg-skip-if "" { *-*-* } { "-march=*zicond*" "-O0" "-Og" "-Os" "-Oz" } } */
-/* { dg-options "-fdump-rtl-ce1" } */
+/* { dg-options "-fdump-rtl-ce1 -mbranch-cost=4" } */
 
 long
 foo (long a, long b, long c)
diff --git a/gcc/testsuite/gcc.target/riscv/shift-shift-6.c b/gcc/testsuite/gcc.target/riscv/shift-shift-6.c
new file mode 100644 (file)
index 0000000..083f5c4
--- /dev/null
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc -mabi=lp64" } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-Og" } } */
+
+/* Test for zero_extendsidi2_shifted handling arbitrary mask widths
+   (not just 32 bits). */
+unsigned sub1(unsigned a, unsigned b)
+{
+  b = (b << 2) >> 2;
+  return a + (b << 1);
+}
+
+/* { dg-final { scan-assembler-times "slli" 1 } } */
+/* { dg-final { scan-assembler-times "srli" 1 } } */
diff --git a/gcc/testsuite/gcc.target/riscv/shift-shift-7.c b/gcc/testsuite/gcc.target/riscv/shift-shift-7.c
new file mode 100644 (file)
index 0000000..3ecd9eb
--- /dev/null
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc -mabi=lp64" } */
+/* { dg-skip-if "" { *-*-* } { "-O0" } } */
+
+/* Test for zero_extendsidi2_shifted handling arbitrary mask widths
+   (not just 32 bits). */
+unsigned long f(unsigned int a, unsigned long b)
+{
+  a = a << 1;
+  unsigned long c = (unsigned long) a;
+  c = b + (c<<4);
+  return c;
+}
+
+/* { dg-final { scan-assembler-times "slli" 1 } } */
+/* { dg-final { scan-assembler-times "srli" 1 } } */
index 29a6406608101a04ab8d59aab027a65415ef4866..5711757e706da7da4bb55258987e9efc523669c6 100644 (file)
@@ -1,6 +1,6 @@
 /* { dg-do compile } */
-/* { dg-options "-march=rv64gc -mabi=lp64d" } */
-/* { dg-skip-if "" { *-*-* } { "-O0" "-Og" } } */
+/* { dg-options "-march=rv64gc -mabi=lp64d -mbranch-cost=4" } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-Og" "-Os" "-Oz" } } */
 
 #include <stdint.h>
 
index 48e225d3f1e7286a68351de1d7ba4a051d5185e6..ca80e874e8d1a5b08de51c25892b6cffcfd94384 100644 (file)
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-march=rv64gc_zba -mabi=lp64" } */
+/* { dg-options "-march=rv64gc_zba_zbb -mabi=lp64" } */
 /* { dg-skip-if "" { *-*-* } { "-O0" "-Og" } } */
 
 long long sub1(unsigned long long a, unsigned long long b)
index 69914db95a2c6c03c3516ae4200e074e17597e90..1e100b555c2e5947825a07830c707e5054466d7c 100644 (file)
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-march=rv64gc_zba_zbs -mabi=lp64" } */
+/* { dg-options "-march=rv64gc_zba_zbb_zbs -mabi=lp64" } */
 /* { dg-skip-if "" { *-*-* } { "-O0" "-O1" "-Og" } } */
 
 long
index 5773b15d298782f7eb10a4d2e8a068e0ae69c84a..1bebc36c31c89100a5b797ff77c4c52f1bf919bb 100644 (file)
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-march=rv64gc_zbs -mabi=lp64" } */
+/* { dg-options "-march=rv64gc_zba_zbs -mabi=lp64" } */
 /* { dg-skip-if "" { *-*-* } { "-O0" "-Og" "-O1" } } */
 typedef unsigned long uint64_t;
 typedef unsigned int uint32_t;