[llvm] [RISCV] Simplify the insertelement 0, x, x in vector.reduce with an initial value to 0 (PR #193709)

via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 23 03:14:18 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-risc-v

Author: Liao Chunyu (ChunyuLiao)

<details>
<summary>Changes</summary>

For reduction operations with an initial value, we typically use insertelement (<vscale x 2 x i64> zeroinitializer, i64 13, i32 0), which generates multiple vmv instructions. By initializing with zeroinitializer and adding the initial value after the reduction, some vmv instructions can be avoided.

623.xalancbmk_s modified 150+ llvm.vector.reduce intrinsics at compile time. 602.gcc_s 40+. 

---
Full diff: https://github.com/llvm/llvm-project/pull/193709.diff


2 Files Affected:

- (modified) llvm/lib/Target/RISCV/RISCVCodeGenPrepare.cpp (+108) 
- (added) llvm/test/CodeGen/RISCV/rvv/reduction-insertelemet-init.ll (+94) 


``````````diff
diff --git a/llvm/lib/Target/RISCV/RISCVCodeGenPrepare.cpp b/llvm/lib/Target/RISCV/RISCVCodeGenPrepare.cpp
index 4ce5432d67fcf..89c8ccd341b70 100644
--- a/llvm/lib/Target/RISCV/RISCVCodeGenPrepare.cpp
+++ b/llvm/lib/Target/RISCV/RISCVCodeGenPrepare.cpp
@@ -50,6 +50,7 @@ class RISCVCodeGenPrepare : public InstVisitor<RISCVCodeGenPrepare, bool> {
   bool visitIntrinsicInst(IntrinsicInst &I);
   bool expandVPStrideLoad(IntrinsicInst &I);
   bool expandMulReduction(IntrinsicInst &I);
+  bool simplyInsertElementForReduction(IntrinsicInst &I);
   bool widenVPMerge(Instruction *I);
   bool visitFreezeInst(FreezeInst &BO);
 };
@@ -233,6 +234,9 @@ bool RISCVCodeGenPrepare::visitIntrinsicInst(IntrinsicInst &I) {
   if (expandMulReduction(I))
     return true;
 
+  if (simplyInsertElementForReduction(I))
+    return true;
+
   if (widenVPMerge(&I))
     return true;
 
@@ -264,6 +268,110 @@ bool RISCVCodeGenPrepare::visitIntrinsicInst(IntrinsicInst &I) {
   return true;
 }
 
+// For reduction operations with an initial value, ir:
+//  %6 = phi <vscale x 4 x i32> [ %12, %4 ],
+//          [ insertelement (<vscale x 4 x i32> zeroinitializer, i32 13, i32 0), %2 ]
+//  %11 = add <vscale x 4 x i32> %a10, %6
+//  %12 = tail call <vscale x 4 x i32> @llvm.vp.merge.nxv4i32(<vscale x 4 x i1> splat (i1 true),
+//                                <vscale x 4 x i32> %11, <vscale x 4 x i32> %6, i32 %vl)
+//
+//bb:
+//  %18 = tail call i32 @llvm.vector.reduce.add.nxv4i32(<vscale x 4 x i32> %12)
+//
+//  insertelement (<vscale x 2 x i64> zeroinitializer, i64 13, i32 0),
+//  which generates multiple vmv instructions. By initializing with zeroinitializer and adding
+//  the initial value after the reduction, some vmv instructions can be avoided. ir:
+//
+//  %6 = phi <vscale x 4 x i32> [ %12, %4 ], [zeroinitializer, %2 ]
+//  %11 = add <vscale x 4 x i32> %a10, %6
+//  %12 = tail call <vscale x 4 x i32> @llvm.vp.merge.nxv4i32(<vscale x 4 x i1> splat (i1 true),
+//                                <vscale x 4 x i32> %11, <vscale x 4 x i32> %6, i32 %vl)
+//
+//bb:
+//  %18 = tail call i32 @llvm.vector.reduce.add.nxv4i32(<vscale x 4 x i32> %12)
+//  %19 = add i32 %18, 13
+bool RISCVCodeGenPrepare::simplyInsertElementForReduction(IntrinsicInst &II) {
+  auto Opcode = II.getIntrinsicID();
+  if (Opcode != Intrinsic::vector_reduce_xor &&
+      Opcode != Intrinsic::vector_reduce_add &&
+      Opcode != Intrinsic::vector_reduce_or)
+    return false;
+
+  Value *C, *X, *Y;
+  Value *Op0 = II.getArgOperand(0);
+  using namespace PatternMatch;
+  switch (Opcode) {
+  case Intrinsic::vector_reduce_xor:
+    if (!match(Op0, m_Intrinsic<Intrinsic::vp_merge>(
+                        m_One(), m_OneUse(m_Xor(m_Value(X), m_Value(Y))),
+                        m_Value(C))) ||
+        (X != C && Y != C))
+      return false;
+    break;
+  case Intrinsic::vector_reduce_add:
+    if (!match(Op0, m_Intrinsic<Intrinsic::vp_merge>(
+                        m_One(), m_OneUse(m_Add(m_Value(X), m_Value(Y))),
+                        m_Value(C))) ||
+        (X != C && Y != C))
+      return false;
+    break;
+  case Intrinsic::vector_reduce_or:
+    if (!match(Op0, m_Intrinsic<Intrinsic::vp_merge>(
+                        m_One(), m_OneUse(m_Or(m_Value(X), m_Value(Y))),
+                        m_Value(C))) ||
+        (X != C && Y != C))
+      return false;
+    break;
+  default:
+    return false;
+  };
+  auto *PHI = dyn_cast<PHINode>(C);
+  if (!PHI || !PHI->hasNUses(2) || PHI->getNumIncomingValues() != 2)
+    return false;
+
+  auto isScalar = [&](const Value *X) -> llvm::Value * {
+    if (auto *E = dyn_cast<ConstantExpr>(X);
+        E && E->getOpcode() == Instruction::InsertElement &&
+        match(E->getOperand(0), m_Zero()))
+      return E->getOperand(1);
+    if (auto *I = dyn_cast<Instruction>(X);
+        I && I->getOpcode() == Instruction::InsertElement &&
+        match(I->getOperand(0), m_Zero()))
+      return I->getOperand(1);
+    return nullptr;
+  };
+
+  auto *Phi0 = PHI->getIncomingValue(0);
+  auto *Phi1 = PHI->getIncomingValue(1);
+  Value *Scalar;
+  if (Scalar = isScalar(Phi0); Scalar && Phi1 == Op0)
+    PHI->setIncomingValue(0, ConstantInt::get(PHI->getType(), 0));
+  else if (Scalar = isScalar(Phi1); Scalar && Phi0 == Op0)
+    PHI->setIncomingValue(1, ConstantInt::get(PHI->getType(), 0));
+  else
+    return false;
+
+  IRBuilder<> Builder(&II);
+  Value *NewOp = Builder.CreateIntrinsic(Opcode, Op0->getType(), Op0);
+  Value *NewScalar;
+  switch (Opcode) {
+  case Intrinsic::vector_reduce_xor:
+    NewScalar = Builder.CreateXor(NewOp, Scalar);
+    break;
+  case Intrinsic::vector_reduce_add:
+    NewScalar = Builder.CreateAdd(NewOp, Scalar);
+    break;
+  case Intrinsic::vector_reduce_or:
+    NewScalar = Builder.CreateOr(NewOp, Scalar);
+    break;
+  default:
+    break;
+  };
+  II.replaceAllUsesWith(NewScalar);
+  II.eraseFromParent();
+  return true;
+}
+
 // Partially expand a vector_reduce_mul wider than M1 to reduce the
 // number of vsetvlis required when VLEN is exactly known, and
 // reducing register pressure in all cases.
diff --git a/llvm/test/CodeGen/RISCV/rvv/reduction-insertelemet-init.ll b/llvm/test/CodeGen/RISCV/rvv/reduction-insertelemet-init.ll
new file mode 100644
index 0000000000000..6b269a8ef21a8
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/rvv/reduction-insertelemet-init.ll
@@ -0,0 +1,94 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -mattr=+v -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,RV32
+; RUN: llc -mtriple=riscv64 -mattr=+v -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,RV64
+;
+; testcase from:
+; int reduc_plus (int *a, int n)  {
+;  int r = 13;
+;  for (int i = 0; i < n; ++i)
+;   r += a[i];
+;  return r;
+; }
+
+
+define dso_local signext i32 @reduc_plus(ptr noundef readonly captures(none) %0, i64 noundef %1, <vscale x 4 x i32> %a10, i32 %vl) {
+; RV32-LABEL: reduc_plus:
+; RV32:       # %bb.0:
+; RV32-NEXT:    beqz a2, .LBB0_3
+; RV32-NEXT:  # %bb.1:
+; RV32-NEXT:    srli a0, a2, 31
+; RV32-NEXT:    beqz a0, .LBB0_4
+; RV32-NEXT:  .LBB0_2:
+; RV32-NEXT:    li a0, 13
+; RV32-NEXT:    ret
+; RV32-NEXT:  .LBB0_3:
+; RV32-NEXT:    seqz a0, a1
+; RV32-NEXT:    bnez a0, .LBB0_2
+; RV32-NEXT:  .LBB0_4: # %.preheader
+; RV32-NEXT:    vsetvli a0, zero, e32, m2, ta, ma
+; RV32-NEXT:    vmv.v.i v10, 0
+; RV32-NEXT:  .LBB0_5: # =>This Inner Loop Header: Depth=1
+; RV32-NEXT:    sltu a0, a1, a3
+; RV32-NEXT:    sub a1, a1, a3
+; RV32-NEXT:    sub a2, a2, a0
+; RV32-NEXT:    or a0, a1, a2
+; RV32-NEXT:    vsetvli zero, a3, e32, m2, tu, ma
+; RV32-NEXT:    vadd.vv v10, v8, v10
+; RV32-NEXT:    bnez a0, .LBB0_5
+; RV32-NEXT:  # %bb.6:
+; RV32-NEXT:    vsetivli zero, 1, e32, m1, ta, ma
+; RV32-NEXT:    vmv.v.i v8, 13
+; RV32-NEXT:    vsetvli a0, zero, e32, m2, ta, ma
+; RV32-NEXT:    vredsum.vs v8, v10, v8
+; RV32-NEXT:    vmv.x.s a0, v8
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: reduc_plus:
+; RV64:       # %bb.0:
+; RV64-NEXT:    blez a1, .LBB0_4
+; RV64-NEXT:  # %bb.1: # %.preheader
+; RV64-NEXT:    neg a0, a1
+; RV64-NEXT:    slli a1, a2, 32
+; RV64-NEXT:    srli a1, a1, 32
+; RV64-NEXT:    vsetvli a2, zero, e32, m2, ta, ma
+; RV64-NEXT:    vmv.v.i v10, 0
+; RV64-NEXT:  .LBB0_2: # =>This Inner Loop Header: Depth=1
+; RV64-NEXT:    add a0, a0, a1
+; RV64-NEXT:    vsetvli zero, a1, e32, m2, tu, ma
+; RV64-NEXT:    vadd.vv v10, v8, v10
+; RV64-NEXT:    bnez a0, .LBB0_2
+; RV64-NEXT:  # %bb.3:
+; RV64-NEXT:    vsetivli zero, 1, e32, m1, ta, ma
+; RV64-NEXT:    vmv.v.i v8, 13
+; RV64-NEXT:    vsetvli a0, zero, e32, m2, ta, ma
+; RV64-NEXT:    vredsum.vs v8, v10, v8
+; RV64-NEXT:    vmv.x.s a0, v8
+; RV64-NEXT:    ret
+; RV64-NEXT:  .LBB0_4:
+; RV64-NEXT:    li a0, 13
+; RV64-NEXT:    ret
+  %3 = icmp sgt i64 %1, 0
+  br i1 %3, label %4, label %19
+
+4:
+  %5 = phi i64 [ %14, %4 ], [ 0, %2 ]
+  %6 = phi <vscale x 4 x i32> [ %12, %4 ], [ insertelement (<vscale x 4 x i32> zeroinitializer, i32 13, i32 0), %2 ]
+  %7 = phi i64 [ %15, %4 ], [ %1, %2 ]
+  %11 = add <vscale x 4 x i32> %a10, %6
+  %12 = tail call <vscale x 4 x i32> @llvm.vp.merge.nxv4i32(<vscale x 4 x i1> splat (i1 true), <vscale x 4 x i32> %11, <vscale x 4 x i32> %6, i32 %vl)
+  %13 = zext i32 %vl to i64
+  %14 = add i64 %5, %13
+  %15 = sub nuw i64 %7, %13
+  %16 = icmp eq i64 %15, 0
+  br i1 %16, label %17, label %4
+
+17:
+  %18 = tail call i32 @llvm.vector.reduce.add.nxv4i32(<vscale x 4 x i32> %12)
+  br label %19
+
+19:
+  %20 = phi i32 [ 13, %2 ], [ %18, %17 ]
+  ret i32 %20
+}
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; CHECK: {{.*}}

``````````

</details>


https://github.com/llvm/llvm-project/pull/193709


More information about the llvm-commits mailing list