[llvm] [GVN] Eliminate redundant scalar binop by reusing vector binop result (PR #186102)

via llvm-commits llvm-commits at lists.llvm.org
Thu Mar 12 04:54:22 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Vedant Neve (0bVdnt)

<details>
<summary>Changes</summary>

Fixes #<!-- -->185564 

When a scalar binary operation computes the same thing as one lane of an existing dominating vector binary operation, GVN now replaces it with an `extractelement` from the vector result.


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


2 Files Affected:

- (modified) llvm/lib/Transforms/Scalar/GVN.cpp (+82) 
- (added) llvm/test/Transforms/GVN/scalar-binop-extract-redundancy.ll (+278) 


``````````diff
diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index c18cafd75a0a5..c2120a124c94b 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -2657,6 +2657,39 @@ bool GVNPass::propagateEquality(
   return Changed;
 }
 
+/// Return true if every poison-generating flag on VecBO is also present on
+/// ScalarBO. This ensures that replacing the scalar op with an extract from
+/// the vector op is safe: the extracted lane cannot be poison in any case
+/// where the scalar op would not also be poison.
+static bool hasSameOrStricterFlags(BinaryOperator *VecBO,
+                                   BinaryOperator *ScalarBO) {
+  assert(VecBO->getOpcode() == ScalarBO->getOpcode());
+  if (isa<OverflowingBinaryOperator>(VecBO)) {
+    if (VecBO->hasNoUnsignedWrap() && !ScalarBO->hasNoUnsignedWrap())
+      return false;
+    if (VecBO->hasNoSignedWrap() && !ScalarBO->hasNoSignedWrap())
+      return false;
+  }
+  if (isa<PossiblyExactOperator>(VecBO)) {
+    if (VecBO->isExact() && !ScalarBO->isExact())
+      return false;
+  }
+  if (auto *VPD = dyn_cast<PossiblyDisjointInst>(VecBO)) {
+    if (VPD->isDisjoint() &&
+        !cast<PossiblyDisjointInst>(ScalarBO)->isDisjoint())
+      return false;
+  }
+  if (isa<FPMathOperator>(VecBO)) {
+    FastMathFlags VF = VecBO->getFastMathFlags();
+    FastMathFlags SF = ScalarBO->getFastMathFlags();
+    if (VF.noNaNs() && !SF.noNaNs())
+      return false;
+    if (VF.noInfs() && !SF.noInfs())
+      return false;
+  }
+  return true;
+}
+
 /// When calculating availability, handle an instruction
 /// by inserting it into the appropriate sets.
 bool GVNPass::processInstruction(Instruction *I) {
@@ -2759,6 +2792,55 @@ bool GVNPass::processInstruction(Instruction *I) {
   if (I->getType()->isVoidTy())
     return false;
 
+  // Try to replace a scalar binop whose operands are both extracted from
+  // vectors at the same index, with an extractelement from an existing
+  // vector binop of the same kind. For example:
+  //   %v6 = fmul <2 x float> %v0, %v3
+  //   %a  = extractelement %v0, 1
+  //   %b  = extractelement %v3, 1
+  //   %r  = fmul float %a, %b       ; replaced by extractelement %v6, 1
+  if (auto *BO = dyn_cast<BinaryOperator>(I)) {
+    Value *V0, *V1;
+    uint64_t Idx0, Idx1;
+    if (match(BO->getOperand(0),
+              m_ExtractElt(m_Value(V0), m_ConstantInt(Idx0))) &&
+        match(BO->getOperand(1),
+              m_ExtractElt(m_Value(V1), m_ConstantInt(Idx1))) &&
+        Idx0 == Idx1 && V0->getType() == V1->getType()) {
+      for (User *U : V0->users()) {
+        auto *VecBO = dyn_cast<BinaryOperator>(U);
+        if (!VecBO || VecBO->getOpcode() != BO->getOpcode())
+          continue;
+        bool Match =
+            (VecBO->getOperand(0) == V0 && VecBO->getOperand(1) == V1) ||
+            (VecBO->isCommutative() && VecBO->getOperand(0) == V1 &&
+             VecBO->getOperand(1) == V0);
+        if (!Match)
+          continue;
+        if (!DT->dominates(VecBO, BO))
+          continue;
+        // The vector binop must not have poison-generating flags that the
+        // scalar binop does not, otherwise the extracted lane could be poison
+        // where the scalar result would not be.
+        if (VecBO->hasPoisonGeneratingFlags() &&
+            !hasSameOrStricterFlags(VecBO, BO))
+          continue;
+        // Create extractelement to reuse the vector result.
+        Value *IdxVal =
+            cast<ExtractElementInst>(BO->getOperand(0))->getIndexOperand();
+        auto *Ext = ExtractElementInst::Create(VecBO, IdxVal);
+        Ext->insertBefore(BO->getIterator());
+        Ext->setDebugLoc(BO->getDebugLoc());
+        // Value-number the new instruction and register it.
+        unsigned ExtNum = VN.lookupOrAdd(Ext);
+        LeaderTable.insert(ExtNum, Ext, Ext->getParent());
+        patchAndReplaceAllUsesWith(BO, Ext);
+        salvageAndRemoveInstruction(BO);
+        return true;
+      }
+    }
+  }
+
   uint32_t NextNum = VN.getNextUnusedValueNumber();
   unsigned Num = VN.lookupOrAdd(I);
 
diff --git a/llvm/test/Transforms/GVN/scalar-binop-extract-redundancy.ll b/llvm/test/Transforms/GVN/scalar-binop-extract-redundancy.ll
new file mode 100644
index 0000000000000..075b76d2ceb67
--- /dev/null
+++ b/llvm/test/Transforms/GVN/scalar-binop-extract-redundancy.ll
@@ -0,0 +1,278 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=gvn -S < %s | FileCheck %s
+
+; When a scalar binop computes the same thing as an element of an existing
+; vector binop, GVN should replace it with an extractelement.
+
+; The motivating example from issue #185564: the scalar fmul of extracted
+; elements should be replaced by an extractelement from the vector fmul.
+define i1 @src(ptr %arg0, ptr %arg1) {
+; CHECK-LABEL: @src(
+; CHECK-NEXT:    [[V0:%.*]] = load <2 x float>, ptr [[ARG1:%.*]], align 4
+; CHECK-NEXT:    [[V1:%.*]] = getelementptr inbounds nuw i8, ptr [[ARG1]], i64 8
+; CHECK-NEXT:    [[V2:%.*]] = load float, ptr [[V1]], align 4
+; CHECK-NEXT:    [[V3:%.*]] = load <2 x float>, ptr [[ARG0:%.*]], align 4
+; CHECK-NEXT:    [[V4:%.*]] = getelementptr inbounds nuw i8, ptr [[ARG0]], i64 8
+; CHECK-NEXT:    [[V5:%.*]] = load float, ptr [[V4]], align 4
+; CHECK-NEXT:    [[V6:%.*]] = fmul <2 x float> [[V0]], [[V3]]
+; CHECK-NEXT:    [[V7:%.*]] = extractelement <2 x float> [[V6]], i64 0
+; CHECK-NEXT:    [[V8:%.*]] = extractelement <2 x float> [[V0]], i64 1
+; CHECK-NEXT:    [[V9:%.*]] = extractelement <2 x float> [[V3]], i64 1
+; CHECK-NEXT:    [[TMP1:%.*]] = extractelement <2 x float> [[V6]], i64 1
+; CHECK-NEXT:    [[V11:%.*]] = fadd float [[V7]], [[TMP1]]
+; CHECK-NEXT:    [[V12:%.*]] = fmul float [[V2]], [[V5]]
+; CHECK-NEXT:    [[V13:%.*]] = fadd float [[V12]], [[V11]]
+; CHECK-NEXT:    [[V14:%.*]] = fcmp olt float [[V13]], 0.000000e+00
+; CHECK-NEXT:    ret i1 [[V14]]
+;
+  %v0 = load <2 x float>, ptr %arg1, align 4
+  %v1 = getelementptr inbounds nuw i8, ptr %arg1, i64 8
+  %v2 = load float, ptr %v1, align 4
+  %v3 = load <2 x float>, ptr %arg0, align 4
+  %v4 = getelementptr inbounds nuw i8, ptr %arg0, i64 8
+  %v5 = load float, ptr %v4, align 4
+  %v6 = fmul <2 x float> %v0, %v3
+  %v7 = extractelement <2 x float> %v6, i64 0
+  %v8 = extractelement <2 x float> %v0, i64 1
+  %v9 = extractelement <2 x float> %v3, i64 1
+  %v10 = fmul float %v8, %v9
+  %v11 = fadd float %v7, %v10
+  %v12 = fmul float %v2, %v5
+  %v13 = fadd float %v12, %v11
+  %v14 = fcmp olt float %v13, 0.000000e+00
+  ret i1 %v14
+}
+
+; The already-optimised form from issue #185564 — GVN should leave it alone.
+define i1 @tgt(ptr %arg0, ptr %arg1) {
+; CHECK-LABEL: @tgt(
+; CHECK-NEXT:    [[V0:%.*]] = load <2 x float>, ptr [[ARG1:%.*]], align 4
+; CHECK-NEXT:    [[V1:%.*]] = getelementptr inbounds nuw i8, ptr [[ARG1]], i64 8
+; CHECK-NEXT:    [[V2:%.*]] = load float, ptr [[V1]], align 4
+; CHECK-NEXT:    [[V3:%.*]] = load <2 x float>, ptr [[ARG0:%.*]], align 4
+; CHECK-NEXT:    [[V4:%.*]] = getelementptr inbounds nuw i8, ptr [[ARG0]], i64 8
+; CHECK-NEXT:    [[V5:%.*]] = load float, ptr [[V4]], align 4
+; CHECK-NEXT:    [[V6:%.*]] = fmul <2 x float> [[V0]], [[V3]]
+; CHECK-NEXT:    [[V7:%.*]] = extractelement <2 x float> [[V6]], i64 0
+; CHECK-NEXT:    [[V8:%.*]] = extractelement <2 x float> [[V6]], i64 1
+; CHECK-NEXT:    [[V9:%.*]] = fadd float [[V7]], [[V8]]
+; CHECK-NEXT:    [[V10:%.*]] = fmul float [[V2]], [[V5]]
+; CHECK-NEXT:    [[V11:%.*]] = fadd float [[V10]], [[V9]]
+; CHECK-NEXT:    [[V12:%.*]] = fcmp olt float [[V11]], 0.000000e+00
+; CHECK-NEXT:    ret i1 [[V12]]
+;
+  %v0 = load <2 x float>, ptr %arg1, align 4
+  %v1 = getelementptr inbounds nuw i8, ptr %arg1, i64 8
+  %v2 = load float, ptr %v1, align 4
+  %v3 = load <2 x float>, ptr %arg0, align 4
+  %v4 = getelementptr inbounds nuw i8, ptr %arg0, i64 8
+  %v5 = load float, ptr %v4, align 4
+  %v6 = fmul <2 x float> %v0, %v3
+  %v7 = extractelement <2 x float> %v6, i64 0
+  %v8 = extractelement <2 x float> %v6, i64 1
+  %v9 = fadd float %v7, %v8
+  %v10 = fmul float %v2, %v5
+  %v11 = fadd float %v10, %v9
+  %v12 = fcmp olt float %v11, 0.000000e+00
+  ret i1 %v12
+}
+
+; Integer binop (add) with the same pattern.
+define i32 @add_extract_redundant(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @add_extract_redundant(
+; CHECK-NEXT:    [[VEC_ADD:%.*]] = add <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[A2:%.*]] = extractelement <4 x i32> [[A]], i64 2
+; CHECK-NEXT:    [[B2:%.*]] = extractelement <4 x i32> [[B]], i64 2
+; CHECK-NEXT:    [[TMP1:%.*]] = extractelement <4 x i32> [[VEC_ADD]], i64 2
+; CHECK-NEXT:    ret i32 [[TMP1]]
+;
+  %vec_add = add <4 x i32> %a, %b
+  %a2 = extractelement <4 x i32> %a, i64 2
+  %b2 = extractelement <4 x i32> %b, i64 2
+  %scalar_add = add i32 %a2, %b2
+  ret i32 %scalar_add
+}
+
+; Commutative: vector is mul(%a, %b), scalar is mul(%b_elem, %a_elem).
+define i32 @mul_commuted(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @mul_commuted(
+; CHECK-NEXT:    [[VEC_MUL:%.*]] = mul <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[B1:%.*]] = extractelement <4 x i32> [[B]], i64 1
+; CHECK-NEXT:    [[A1:%.*]] = extractelement <4 x i32> [[A]], i64 1
+; CHECK-NEXT:    [[TMP1:%.*]] = extractelement <4 x i32> [[VEC_MUL]], i64 1
+; CHECK-NEXT:    ret i32 [[TMP1]]
+;
+  %vec_mul = mul <4 x i32> %a, %b
+  %b1 = extractelement <4 x i32> %b, i64 1
+  %a1 = extractelement <4 x i32> %a, i64 1
+  %scalar_mul = mul i32 %b1, %a1
+  ret i32 %scalar_mul
+}
+
+; Negative test: different indices — should NOT be optimized.
+define i32 @different_indices(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @different_indices(
+; CHECK-NEXT:    [[VEC_ADD:%.*]] = add <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[A1:%.*]] = extractelement <4 x i32> [[A]], i64 1
+; CHECK-NEXT:    [[B2:%.*]] = extractelement <4 x i32> [[B]], i64 2
+; CHECK-NEXT:    [[SCALAR_ADD:%.*]] = add i32 [[A1]], [[B2]]
+; CHECK-NEXT:    ret i32 [[SCALAR_ADD]]
+;
+  %vec_add = add <4 x i32> %a, %b
+  %a1 = extractelement <4 x i32> %a, i64 1
+  %b2 = extractelement <4 x i32> %b, i64 2
+  %scalar_add = add i32 %a1, %b2
+  ret i32 %scalar_add
+}
+
+; Negative test: no vector binop exists — should NOT be optimized.
+define i32 @no_vector_binop(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @no_vector_binop(
+; CHECK-NEXT:    [[A2:%.*]] = extractelement <4 x i32> [[A:%.*]], i64 2
+; CHECK-NEXT:    [[B2:%.*]] = extractelement <4 x i32> [[B:%.*]], i64 2
+; CHECK-NEXT:    [[SCALAR_ADD:%.*]] = add i32 [[A2]], [[B2]]
+; CHECK-NEXT:    ret i32 [[SCALAR_ADD]]
+;
+  %a2 = extractelement <4 x i32> %a, i64 2
+  %b2 = extractelement <4 x i32> %b, i64 2
+  %scalar_add = add i32 %a2, %b2
+  ret i32 %scalar_add
+}
+
+; Negative test: non-commutative op (sub) with swapped operands.
+define i32 @sub_not_commutative(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @sub_not_commutative(
+; CHECK-NEXT:    [[VEC_SUB:%.*]] = sub <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[B1:%.*]] = extractelement <4 x i32> [[B]], i64 1
+; CHECK-NEXT:    [[A1:%.*]] = extractelement <4 x i32> [[A]], i64 1
+; CHECK-NEXT:    [[SCALAR_SUB:%.*]] = sub i32 [[B1]], [[A1]]
+; CHECK-NEXT:    ret i32 [[SCALAR_SUB]]
+;
+  %vec_sub = sub <4 x i32> %a, %b
+  %b1 = extractelement <4 x i32> %b, i64 1
+  %a1 = extractelement <4 x i32> %a, i64 1
+  %scalar_sub = sub i32 %b1, %a1
+  ret i32 %scalar_sub
+}
+
+; Vector binop has nuw but scalar does not — should NOT use vector result.
+define i32 @poison_flags_mismatch(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @poison_flags_mismatch(
+; CHECK-NEXT:    [[VEC_ADD:%.*]] = add nuw <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[A2:%.*]] = extractelement <4 x i32> [[A]], i64 2
+; CHECK-NEXT:    [[B2:%.*]] = extractelement <4 x i32> [[B]], i64 2
+; CHECK-NEXT:    [[SCALAR_ADD:%.*]] = add i32 [[A2]], [[B2]]
+; CHECK-NEXT:    ret i32 [[SCALAR_ADD]]
+;
+  %vec_add = add nuw <4 x i32> %a, %b
+  %a2 = extractelement <4 x i32> %a, i64 2
+  %b2 = extractelement <4 x i32> %b, i64 2
+  %scalar_add = add i32 %a2, %b2
+  ret i32 %scalar_add
+}
+
+; Both have nuw — should optimize.
+define i32 @matching_nuw_flags(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @matching_nuw_flags(
+; CHECK-NEXT:    [[VEC_ADD:%.*]] = add nuw <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[A2:%.*]] = extractelement <4 x i32> [[A]], i64 2
+; CHECK-NEXT:    [[B2:%.*]] = extractelement <4 x i32> [[B]], i64 2
+; CHECK-NEXT:    [[TMP1:%.*]] = extractelement <4 x i32> [[VEC_ADD]], i64 2
+; CHECK-NEXT:    ret i32 [[TMP1]]
+;
+  %vec_add = add nuw <4 x i32> %a, %b
+  %a2 = extractelement <4 x i32> %a, i64 2
+  %b2 = extractelement <4 x i32> %b, i64 2
+  %scalar_add = add nuw i32 %a2, %b2
+  ret i32 %scalar_add
+}
+
+; Negative test: vector fmul has nnan but scalar does not.
+define float @nnan_flag_mismatch(<2 x float> %a, <2 x float> %b) {
+; CHECK-LABEL: @nnan_flag_mismatch(
+; CHECK-NEXT:    [[VEC_MUL:%.*]] = fmul nnan <2 x float> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[A0:%.*]] = extractelement <2 x float> [[A]], i64 0
+; CHECK-NEXT:    [[B0:%.*]] = extractelement <2 x float> [[B]], i64 0
+; CHECK-NEXT:    [[SCALAR_MUL:%.*]] = fmul float [[A0]], [[B0]]
+; CHECK-NEXT:    ret float [[SCALAR_MUL]]
+;
+  %vec_mul = fmul nnan <2 x float> %a, %b
+  %a0 = extractelement <2 x float> %a, i64 0
+  %b0 = extractelement <2 x float> %b, i64 0
+  %scalar_mul = fmul float %a0, %b0
+  ret float %scalar_mul
+}
+
+; Negative test: vector udiv is exact, scalar is not.
+define i32 @exact_flag_mismatch(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @exact_flag_mismatch(
+; CHECK-NEXT:    [[VEC_DIV:%.*]] = udiv exact <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[A1:%.*]] = extractelement <4 x i32> [[A]], i64 1
+; CHECK-NEXT:    [[B1:%.*]] = extractelement <4 x i32> [[B]], i64 1
+; CHECK-NEXT:    [[SCALAR_DIV:%.*]] = udiv i32 [[A1]], [[B1]]
+; CHECK-NEXT:    ret i32 [[SCALAR_DIV]]
+;
+  %vec_div = udiv exact <4 x i32> %a, %b
+  %a1 = extractelement <4 x i32> %a, i64 1
+  %b1 = extractelement <4 x i32> %b, i64 1
+  %scalar_div = udiv i32 %a1, %b1
+  ret i32 %scalar_div
+}
+
+; Negative test: vector binop does NOT dominate scalar (sibling blocks).
+define i32 @non_dominating_block(<4 x i32> %a, <4 x i32> %b, i1 %cond) {
+; CHECK-LABEL: @non_dominating_block(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 [[COND:%.*]], label [[LEFT:%.*]], label [[RIGHT:%.*]]
+; CHECK:       left:
+; CHECK-NEXT:    [[VEC_ADD:%.*]] = add <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[E:%.*]] = extractelement <4 x i32> [[VEC_ADD]], i64 0
+; CHECK-NEXT:    ret i32 [[E]]
+; CHECK:       right:
+; CHECK-NEXT:    [[A0:%.*]] = extractelement <4 x i32> [[A]], i64 0
+; CHECK-NEXT:    [[B0:%.*]] = extractelement <4 x i32> [[B]], i64 0
+; CHECK-NEXT:    [[SCALAR_ADD:%.*]] = add i32 [[A0]], [[B0]]
+; CHECK-NEXT:    ret i32 [[SCALAR_ADD]]
+;
+entry:
+  br i1 %cond, label %left, label %right
+
+left:
+  %vec_add = add <4 x i32> %a, %b
+  %e = extractelement <4 x i32> %vec_add, i64 0
+  ret i32 %e
+
+right:
+  %a0 = extractelement <4 x i32> %a, i64 0
+  %b0 = extractelement <4 x i32> %b, i64 0
+  %scalar_add = add i32 %a0, %b0
+  ret i32 %scalar_add
+}
+
+; Vector binop in a different (dominating) block.
+define i32 @cross_block(<4 x i32> %a, <4 x i32> %b, i1 %cond) {
+; CHECK-LABEL: @cross_block(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[VEC_ADD:%.*]] = add <4 x i32> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    br i1 [[COND:%.*]], label [[THEN:%.*]], label [[ELSE:%.*]]
+; CHECK:       then:
+; CHECK-NEXT:    [[A3:%.*]] = extractelement <4 x i32> [[A]], i64 3
+; CHECK-NEXT:    [[B3:%.*]] = extractelement <4 x i32> [[B]], i64 3
+; CHECK-NEXT:    [[TMP0:%.*]] = extractelement <4 x i32> [[VEC_ADD]], i64 3
+; CHECK-NEXT:    ret i32 [[TMP0]]
+; CHECK:       else:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  %vec_add = add <4 x i32> %a, %b
+  br i1 %cond, label %then, label %else
+
+then:
+  %a3 = extractelement <4 x i32> %a, i64 3
+  %b3 = extractelement <4 x i32> %b, i64 3
+  %scalar_add = add i32 %a3, %b3
+  ret i32 %scalar_add
+
+else:
+  ret i32 0
+}

``````````

</details>


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


More information about the llvm-commits mailing list