[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