[PATCH v2 4/4] RISC-V: define_insn_and_split for multiply-add and bit-extract fusion

Luis Silva <[email protected]> Wed, 5 Aug 2026 11:02:12 +0100
Newsgroups gmane.comp.gcc.patches
Message-ID <LO6P265MB6224F724A261C7B5BD9BC484B4D32@LO6P265MB6224.GBRP265.PROD.OUTLOOK.COM>
From: Luis Silva <[email protected]>

Introduce RTL patterns for multiply-add fusion.  On RV32 the expanders
emit madd_fused (mul+add).  On RV64 they emit madd_fused_extended
(mulw+addw).

Also cheapen zero_extract / sign_extract in riscv_rtx_costs when
RISCV_FUSE_BFEXT_SRLI / RISCV_FUSE_BFEXT_SRAI is enabled.

gcc/ChangeLog:

	* config/riscv/riscv.cc (riscv_rtx_costs): Cheapen zero_extract /
	sign_extract when BFEXT_SRLI / BFEXT_SRAI is enabled.
	* config/riscv/riscv.md (maddhisi4): Emit madd_fused /
	madd_fused_extended when RISCV_FUSE_MULT_ADD and not
	TARGET_XTHEADMAC.
	(umaddhisi4): New expand.  On TARGET_64BIT, zero-extend HI
	operands to DI and pass SI lowparts to madd_fused_extended.
	(madd_fused): New insn_and_split.
	(madd_fused_extended): New insn_and_split.

gcc/testsuite/ChangeLog:

	* gcc.target/riscv/fusion-bfext-2.c: New test.
	* gcc.target/riscv/fusion-limm-condbr.c: New test.
	* gcc.target/riscv/fusion-madd.c: New test.

Co-authored-by: Artemiy Volkov <[email protected]>
Co-authored-by: Michiel Derhaeg <[email protected]>
Signed-off-by: Luis Silva <[email protected]>
---
 gcc/config/riscv/riscv.cc                     |  15 ++-
 gcc/config/riscv/riscv.md                     | 122 +++++++++++++++++-
 .../gcc.target/riscv/fusion-bfext-2.c         |  14 ++
 .../gcc.target/riscv/fusion-limm-condbr.c     |  12 ++
 gcc/testsuite/gcc.target/riscv/fusion-madd.c  |  13 ++
 5 files changed, 172 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-bfext-2.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-limm-condbr.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/fusion-madd.c

diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index dd80da2d55a..4a23c734bdf 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -4712,11 +4712,22 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
 	  *total = COSTS_N_INSNS (SINGLE_SHIFT_COST);
 	  return true;
 	}
+      if (riscv_fusion_enabled_p (RISCV_FUSE_BFEXT_SRLI)
+	  && outer_code == SET
+	  && CONST_INT_P (XEXP (x, 1))
+	  && CONST_INT_P (XEXP (x, 2)))
+	{
+	  *total = COSTS_N_INSNS (SINGLE_SHIFT_COST);
+	  return true;
+	}
       gcc_fallthrough ();
     case SIGN_EXTRACT:
-      if (TARGET_XTHEADBB && outer_code == SET
+      if (outer_code == SET
 	  && CONST_INT_P (XEXP (x, 1))
-	  && CONST_INT_P (XEXP (x, 2)))
+	  && CONST_INT_P (XEXP (x, 2))
+	  && ((GET_CODE (x) == SIGN_EXTRACT
+	       && riscv_fusion_enabled_p (RISCV_FUSE_BFEXT_SRAI))
+	      || TARGET_XTHEADBB))
 	{
 	  *total = COSTS_N_INSNS (SINGLE_SHIFT_COST);
 	  return true;
diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
index 6f66f983d87..71d2ce09899 100644
--- a/gcc/config/riscv/riscv.md
+++ b/gcc/config/riscv/riscv.md
@@ -4792,8 +4792,69 @@
 	  (mult:SI (sign_extend:SI (match_operand:HI 1 "register_operand"))
 		   (sign_extend:SI (match_operand:HI 2 "register_operand")))
 	  (match_operand:SI 3 "register_operand")))]
-  "TARGET_XTHEADMAC"
-)
+  "TARGET_XTHEADMAC || (riscv_fusion_enabled_p (RISCV_FUSE_MULT_ADD)
+			&& (TARGET_ZMMUL || TARGET_MUL))"
+{
+  if (riscv_fusion_enabled_p (RISCV_FUSE_MULT_ADD)
+      && !TARGET_XTHEADMAC)
+    {
+      rtx tmp0 = gen_reg_rtx (SImode), tmp1 = gen_reg_rtx (SImode);
+      emit_insn (gen_extendhisi2 (tmp0, operands[1]));
+      emit_insn (gen_extendhisi2 (tmp1, operands[2]));
+
+      if (TARGET_64BIT)
+	{
+	  rtx op0 = gen_reg_rtx (DImode);
+	  emit_insn (gen_madd_fused_extended (op0, tmp0, tmp1, operands[3]));
+	  op0 = gen_lowpart (SImode, op0);
+	  SUBREG_PROMOTED_VAR_P (op0) = 1;
+	  SUBREG_PROMOTED_SET (op0, SRP_SIGNED);
+	  emit_move_insn (operands[0], op0);
+	}
+      else
+	{
+	  emit_insn (gen_madd_fused (operands[0], tmp0, tmp1, operands[3]));
+	}
+
+      DONE;
+    }
+})
+
+(define_expand "umaddhisi4"
+  [(set (match_operand:SI 0 "register_operand")
+	(plus:SI
+	  (mult:SI (zero_extend:SI (match_operand:HI 1 "register_operand"))
+		   (zero_extend:SI (match_operand:HI 2 "register_operand")))
+	  (match_operand:SI 3 "register_operand")))]
+  "riscv_fusion_enabled_p (RISCV_FUSE_MULT_ADD)
+   && (TARGET_ZMMUL || TARGET_MUL)"
+{
+  if (TARGET_64BIT)
+    {
+      rtx tmp0 = gen_reg_rtx (DImode), tmp1 = gen_reg_rtx (DImode);
+      emit_insn (gen_zero_extendhidi2 (tmp0, operands[1]));
+      emit_insn (gen_zero_extendhidi2 (tmp1, operands[2]));
+
+      rtx op0 = gen_reg_rtx (DImode);
+      emit_insn (gen_madd_fused_extended (op0,
+					  gen_lowpart (SImode, tmp0),
+					  gen_lowpart (SImode, tmp1),
+					  operands[3]));
+      op0 = gen_lowpart (SImode, op0);
+      SUBREG_PROMOTED_VAR_P (op0) = 1;
+      SUBREG_PROMOTED_SET (op0, SRP_SIGNED);
+      emit_move_insn (operands[0], op0);
+    }
+  else
+    {
+      rtx tmp0 = gen_reg_rtx (SImode), tmp1 = gen_reg_rtx (SImode);
+      emit_insn (gen_zero_extendhisi2 (tmp0, operands[1]));
+      emit_insn (gen_zero_extendhisi2 (tmp1, operands[2]));
+      emit_insn (gen_madd_fused (operands[0], tmp0, tmp1, operands[3]));
+    }
+
+  DONE;
+})
 
 (define_expand "msubhisi4"
   [(set (match_operand:SI 0 "register_operand")
@@ -4804,6 +4865,63 @@
   "TARGET_XTHEADMAC"
 )
 
+(define_insn_and_split "madd_fused"
+  [(set (match_operand:SI 0 "register_operand" "=&r")
+     (plus:SI
+	(mult:SI (match_operand:SI 1 "register_operand" "r")
+		 (match_operand:SI 2 "register_operand" "r"))
+	(match_operand:SI 3 "register_operand" "r")))]
+  "riscv_fusion_enabled_p (RISCV_FUSE_MULT_ADD)
+   && !TARGET_XTHEADMAC
+   && !TARGET_64BIT && (TARGET_ZMMUL || TARGET_MUL)"
+  "#"
+  "&& 1"
+  [(const_int 0)]
+  "{
+     rtx addend = operands[3];
+     if (can_create_pseudo_p ()
+	 && reg_overlap_mentioned_p (operands[0], operands[3]))
+       {
+	 addend = gen_reg_rtx (SImode);
+	 emit_move_insn (addend, operands[3]);
+       }
+
+     emit_insn (gen_mulsi3 (operands[0], operands[1], operands[2]));
+     emit_insn (gen_addsi3 (operands[0], operands[0], addend));
+     DONE;
+   }"
+  [(set_attr "type" "imul")])
+
+(define_insn_and_split "madd_fused_extended"
+  [(set (match_operand:DI 0 "register_operand" "=&r")
+     (sign_extend:DI
+      (plus:SI
+	(mult:SI (match_operand:SI 1 "register_operand" "r")
+		 (match_operand:SI 2 "register_operand" "r"))
+	(match_operand:SI 3 "register_operand" "r"))))]
+  "riscv_fusion_enabled_p (RISCV_FUSE_MULT_ADD)
+   && !TARGET_XTHEADMAC
+   && TARGET_64BIT && (TARGET_ZMMUL || TARGET_MUL)"
+  "#"
+  "&& 1"
+  [(const_int 0)]
+  "{
+     rtx addend = operands[3];
+     if (can_create_pseudo_p ()
+	 && reg_overlap_mentioned_p (operands[0], operands[3]))
+       {
+	 addend = gen_reg_rtx (SImode);
+	 emit_move_insn (addend, operands[3]);
+       }
+
+     emit_insn (gen_mulsi3_extended (operands[0], operands[1], operands[2]));
+     emit_insn (gen_addsi3_extended (operands[0],
+				     gen_lowpart (SImode, operands[0]),
+				     addend));
+     DONE;
+   }"
+  [(set_attr "type" "imul")])
+
 ;; String compare with length insn.
 ;; Argument 0 is the target (result)
 ;; Argument 1 is the source1
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-bfext-2.c b/gcc/testsuite/gcc.target/riscv/fusion-bfext-2.c
new file mode 100644
index 00000000000..b471c20ae57
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-bfext-2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target rv32 } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-Oz" "-Os" } } */
+/* { dg-options "-mtune=arc-v-rhx-100-series -march=rv32im_zbs -mabi=ilp32" } */
+
+#define bit_extract(x,start,amt) (((x)>>(start)) & (~(0xffffffff << (amt))))
+
+int
+f (int x)
+{
+  return bit_extract(x,10,14) + bit_extract(x,1,1);
+}
+
+/* { dg-final { scan-assembler {\sslli\s([ast][0-9]+),a0,8\n\ssrli\s([ast][0-9]+),\1,18\n\sbexti\sa0,a0,1.*\n\sadd\sa0,\2,a0.*\n} } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-limm-condbr.c b/gcc/testsuite/gcc.target/riscv/fusion-limm-condbr.c
new file mode 100644
index 00000000000..cc2a56a2e08
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-limm-condbr.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mtune=arc-v-rhx-100-series" } */
+
+int
+f (int x)
+{
+  begin:
+  if (x <= 3)
+    goto begin;
+}
+
+/* { dg-final { scan-assembler "\\sli\\sa5,3\n\\sble\\sa0,a5,.L\[0-9\]+\n" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/fusion-madd.c b/gcc/testsuite/gcc.target/riscv/fusion-madd.c
new file mode 100644
index 00000000000..e58fc830e47
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/fusion-madd.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-g" "-flto" "-O0" "-O1" "-O3" "-Oz" "-Os" } } */
+/* { dg-options "-mtune=arc-v-rhx-100-series -march=rv32im -mabi=ilp32" { target { rv32 } } } */
+/* { dg-options "-mtune=arc-v-rhx-100-series -march=rv64im -mabi=lp64" { target { rv64 } } } */
+
+int
+f (int x, short y, short z, short v, short w)
+{
+  return x + (int) y * (int) z + (int) v * (int) w;
+}
+
+/* { dg-final { scan-assembler {\smul\s([ast][0-9]+),a1,a2\n\sadd\s\1,\1,a0\n\smul\sa0,a3,a4\n\sadd\sa0,a0,\1\n} { target rv32 } } } */
+/* { dg-final { scan-assembler {\smulw\sa2,a2,a1\n\saddw\sa2,a2,a0\n\smulw\sa0,a4,a3\n\saddw\sa0,a0,a2\n} { target rv64 } } } */
-- 
2.47.3