[llvm] [VPlan] Handle calls in VPInstruction:opcodeMayReadOrWriteFromMemory. (PR #190681)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 12 08:58:04 PDT 2026


https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/190681

>From 568c2b78bfd9ec56ee876aa0536001c54e34fcf8 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 6 Apr 2026 21:45:27 +0100
Subject: [PATCH 1/3] [VPlan] Handle calls in
 VPInstruction:opcodeMayReadOrWriteFromMemory.

Retrieve the called function and check its memory attributes, to
determine if a VPInstruction calling a function reads or writes memory.

Use it to strengthen assert in areAllLoadsDereferenceable.
---
 llvm/lib/Transforms/Vectorize/VPlanConstruction.cpp | 6 ++++--
 llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp      | 4 ++++
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VPlanConstruction.cpp b/llvm/lib/Transforms/Vectorize/VPlanConstruction.cpp
index c1df51841076f..2895d93c031ea 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanConstruction.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanConstruction.cpp
@@ -951,12 +951,14 @@ static bool areAllLoadsDereferenceable(VPBasicBlock *HeaderVPBB,
   for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(
            vp_depth_first_shallow(HeaderVPBB))) {
     // Skip blocks outside the loop (exit blocks and their successors).
-    if (VPBB == MiddleVPBB)
+    if (VPBB == MiddleVPBB || isa<VPIRBasicBlock>(VPBB))
       continue;
     for (VPRecipeBase &R : *VPBB) {
       auto *VPI = dyn_cast<VPInstructionWithType>(&R);
-      if (!VPI || VPI->getOpcode() != Instruction::Load)
+      if (!VPI || VPI->getOpcode() != Instruction::Load) {
+        assert(!R.mayReadFromMemory() && "unexpected recipe accessing memory");
         continue;
+      }
 
       // Get the pointer SCEV for dereferenceability checking.
       VPValue *Ptr = VPI->getOperand(0);
diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index b496278ebd6ec..c584ecf39f797 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -1373,6 +1373,10 @@ bool VPInstruction::opcodeMayReadOrWriteFromMemory() const {
   case VPInstruction::VScale:
   case VPInstruction::Unpack:
     return false;
+  case Instruction::Call:
+    return !cast<Function>(
+                cast<VPIRValue>(getOperand(getNumOperands() - 1))->getValue())
+                ->doesNotAccessMemory();
   default:
     return true;
   }

>From 984e962ee6a9993cba594c7ac702d7d99b5461ad Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Tue, 7 Apr 2026 17:00:00 +0100
Subject: [PATCH 2/3] !fixup add helper.

---
 .../lib/Transforms/Vectorize/VPlanRecipes.cpp | 32 +++++++++++++------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index c584ecf39f797..d858efaeb6026 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -440,6 +440,22 @@ VPInstruction::VPInstruction(unsigned Opcode, ArrayRef<VPValue *> Operands,
          "number of operands does not match opcode");
 }
 
+/// For call VPInstructions, return the operand index of the called function and
+/// the called function itself. The function is either the last operand (for
+/// unmasked calls) or the second-to-last operand (for masked calls).
+static std::pair<unsigned, Function *>
+getCalledFunction(const VPInstruction &VPI) {
+  assert(VPI.getOpcode() == Instruction::Call && "must be a call");
+  unsigned NumOps = VPI.getNumOperands();
+  auto *LastOp = dyn_cast<VPIRValue>(VPI.getOperand(NumOps - 1));
+  if (LastOp)
+    if (auto *F = dyn_cast<Function>(LastOp->getValue()))
+      return {NumOps - 1, F};
+  return {
+      NumOps - 2,
+      cast<Function>(cast<VPIRValue>(VPI.getOperand(NumOps - 2))->getValue())};
+}
+
 unsigned VPInstruction::getNumOperandsForOpcode() const {
   if (Instruction::isUnaryOp(Opcode) || Instruction::isCast(Opcode))
     return 1;
@@ -487,12 +503,8 @@ unsigned VPInstruction::getNumOperandsForOpcode() const {
   case VPInstruction::ReductionStartVector:
     return 3;
   case Instruction::Call: {
-    // For unmasked calls, the last argument will the called function. Use that
-    // to compute the number of operands without the mask.
-    VPValue *LastOp = getOperand(getNumOperands() - 1);
-    if (isa<VPIRValue>(LastOp) && isa<Function>(LastOp->getLiveInIRValue()))
-      return getNumOperands();
-    return getNumOperands() - 1;
+    const auto &[Idx, _] = getCalledFunction(*this);
+    return Idx + 1;
   }
   case Instruction::GetElementPtr:
   case Instruction::PHI:
@@ -1373,10 +1385,10 @@ bool VPInstruction::opcodeMayReadOrWriteFromMemory() const {
   case VPInstruction::VScale:
   case VPInstruction::Unpack:
     return false;
-  case Instruction::Call:
-    return !cast<Function>(
-                cast<VPIRValue>(getOperand(getNumOperands() - 1))->getValue())
-                ->doesNotAccessMemory();
+  case Instruction::Call: {
+    const auto &[_, CalledFn] = getCalledFunction(*this);
+    return !CalledFn->doesNotAccessMemory();
+  }
   default:
     return true;
   }

>From 4c23919394d7c53ae2b04fcd907eafa74fffe95b Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 10 Apr 2026 10:04:41 +0100
Subject: [PATCH 3/3] !fixup split functions

---
 .../lib/Transforms/Vectorize/VPlanRecipes.cpp | 39 ++++++++++---------
 1 file changed, 20 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index 4a0e5ae803c47..44aec2ce62bc9 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -440,20 +440,25 @@ VPInstruction::VPInstruction(unsigned Opcode, ArrayRef<VPValue *> Operands,
          "number of operands does not match opcode");
 }
 
-/// For call VPInstructions, return the operand index of the called function and
-/// the called function itself. The function is either the last operand (for
-/// unmasked calls) or the second-to-last operand (for masked calls).
-static std::pair<unsigned, Function *>
-getCalledFunction(const VPInstruction &VPI) {
+/// For call VPInstructions, return the operand index of the called function.
+/// The function is either the last operand (for unmasked calls) or the
+/// second-to-last operand (for masked calls).
+static unsigned getCalledFnOperandIndex(const VPInstruction &VPI) {
   assert(VPI.getOpcode() == Instruction::Call && "must be a call");
   unsigned NumOps = VPI.getNumOperands();
   auto *LastOp = dyn_cast<VPIRValue>(VPI.getOperand(NumOps - 1));
-  if (LastOp)
-    if (auto *F = dyn_cast<Function>(LastOp->getValue()))
-      return {NumOps - 1, F};
-  return {
-      NumOps - 2,
-      cast<Function>(cast<VPIRValue>(VPI.getOperand(NumOps - 2))->getValue())};
+  if (LastOp && isa<Function>(LastOp->getValue()))
+    return NumOps - 1;
+  assert(
+      isa<Function>(cast<VPIRValue>(VPI.getOperand(NumOps - 2))->getValue()) &&
+      "expected function operand");
+  return NumOps - 2;
+}
+
+/// For call VPInstructions, return the called function.
+static Function *getCalledFunction(const VPInstruction &VPI) {
+  unsigned Idx = getCalledFnOperandIndex(VPI);
+  return cast<Function>(cast<VPIRValue>(VPI.getOperand(Idx))->getValue());
 }
 
 unsigned VPInstruction::getNumOperandsForOpcode() const {
@@ -502,10 +507,8 @@ unsigned VPInstruction::getNumOperandsForOpcode() const {
   case VPInstruction::ActiveLaneMask:
   case VPInstruction::ReductionStartVector:
     return 3;
-  case Instruction::Call: {
-    const auto &[Idx, _] = getCalledFunction(*this);
-    return Idx + 1;
-  }
+  case Instruction::Call:
+    return getCalledFnOperandIndex(*this) + 1;
   case Instruction::GetElementPtr:
   case Instruction::PHI:
   case Instruction::Switch:
@@ -1370,10 +1373,8 @@ bool VPInstruction::opcodeMayReadOrWriteFromMemory() const {
   case VPInstruction::VScale:
   case VPInstruction::Unpack:
     return false;
-  case Instruction::Call: {
-    const auto &[_, CalledFn] = getCalledFunction(*this);
-    return !CalledFn->doesNotAccessMemory();
-  }
+  case Instruction::Call:
+    return !getCalledFunction(*this)->doesNotAccessMemory();
   default:
     return true;
   }



More information about the llvm-commits mailing list