[llvm] [VPlan] Add SCEV support for abs intrinsic (PR #195678)
Madhur Amilkanthwar via llvm-commits
llvm-commits at lists.llvm.org
Fri May 8 00:32:26 PDT 2026
https://github.com/madhur13490 updated https://github.com/llvm/llvm-project/pull/195678
>From f6705a5dc4df1c1e652520bc1bf06fd14fdba5cb Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Mon, 4 May 2026 08:29:27 -0700
Subject: [PATCH 1/5] [VPlan] Add SCEV support for abs intrinsic
Teach getSCEVExprForVPValue to model llvm.abs via ScalarEvolution::getAbsExpr,
preserving the intrinsic's is_int_min_poison flag as the SCEV IsNSW argument.
Add a unit test covering both poison and wrapping llvm.abs forms.
---
llvm/lib/Transforms/Vectorize/VPlanUtils.cpp | 7 ++++
.../Transforms/Vectorize/VPlanTest.cpp | 40 +++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
index a60b490a69ce6..2ab58242e6de2 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
@@ -258,6 +258,13 @@ const SCEV *vputils::getSCEVExprForVPValue(const VPValue *V,
return CreateSCEV({LHSVal, RHSVal}, [&](ArrayRef<SCEVUse> Ops) {
return SE.getSMinExpr(Ops[0], Ops[1]);
});
+ const APInt *IsIntMinPoison;
+ if (match(V, m_Intrinsic<Intrinsic::abs>(m_VPValue(LHSVal),
+ m_APInt(IsIntMinPoison))) &&
+ IsIntMinPoison->getBitWidth() == 1)
+ return CreateSCEV({LHSVal}, [&](ArrayRef<SCEVUse> Ops) {
+ return SE.getAbsExpr(Ops[0], IsIntMinPoison->isOne());
+ });
ArrayRef<VPValue *> Ops;
Type *SourceElementType;
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index a1ddda7eda969..270900bf9cbe9 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -10,6 +10,7 @@
#include "../lib/Transforms/Vectorize/VPlan.h"
#include "../lib/Transforms/Vectorize/VPlanCFG.h"
#include "../lib/Transforms/Vectorize/VPlanHelpers.h"
+#include "../lib/Transforms/Vectorize/VPlanUtils.h"
#include "VPlanTestBase.h"
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/ADT/PostOrderIterator.h"
@@ -33,6 +34,45 @@ namespace {
} while (0)
using VPInstructionTest = VPlanTestBase;
+using VPlanSCEVTest = VPlanTestIRBase;
+
+TEST_F(VPlanSCEVTest, GetSCEVExprForVPValueAbs) {
+ const char *ModuleString =
+ "define void @f(i32 %x) {\n"
+ "entry:\n"
+ " br label %loop\n"
+ "loop:\n"
+ " %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]\n"
+ " %iv.next = add nuw nsw i32 %iv, 1\n"
+ " %exit = icmp eq i32 %iv.next, 4\n"
+ " br i1 %exit, label %exit.block, label %loop\n"
+ "exit.block:\n"
+ " ret void\n"
+ "}\n";
+
+ Module &M = parseModule(ModuleString);
+ Function *F = M.getFunction("f");
+ BasicBlock *LoopHeader = F->getEntryBlock().getSingleSuccessor();
+ doAnalysis(*F);
+
+ Loop *L = LI->getLoopFor(LoopHeader);
+ PredicatedScalarEvolution PSE(*SE, *L);
+ VPlan Plan(LoopHeader);
+ Argument *X = F->getArg(0);
+ VPValue *Op = Plan.getOrAddLiveIn(X);
+ VPValue *IsIntMinPoison = Plan.getOrAddLiveIn(ConstantInt::getTrue(*Ctx));
+ VPWidenIntrinsicRecipe Abs(Intrinsic::abs, {Op, IsIntMinPoison},
+ X->getType());
+
+ const SCEV *Expr = vputils::getSCEVExprForVPValue(&Abs, PSE, L);
+ EXPECT_EQ(SE->getAbsExpr(SE->getSCEV(X), /*IsNSW=*/true), Expr);
+
+ IsIntMinPoison = Plan.getOrAddLiveIn(ConstantInt::getFalse(*Ctx));
+ VPWidenIntrinsicRecipe WrappingAbs(Intrinsic::abs, {Op, IsIntMinPoison},
+ X->getType());
+ Expr = vputils::getSCEVExprForVPValue(&WrappingAbs, PSE, L);
+ EXPECT_EQ(SE->getAbsExpr(SE->getSCEV(X), /*IsNSW=*/false), Expr);
+}
TEST_F(VPInstructionTest, insertBefore) {
VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
>From 72ec4d34fbd5047c384725da4a53dc405865db1c Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Mon, 4 May 2026 10:12:40 -0700
Subject: [PATCH 2/5] fixup! address comments
---
.../Transforms/Vectorize/VPlanTest.cpp | 29 ++++++++++---------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index 270900bf9cbe9..1f45612726d90 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -37,18 +37,19 @@ using VPInstructionTest = VPlanTestBase;
using VPlanSCEVTest = VPlanTestIRBase;
TEST_F(VPlanSCEVTest, GetSCEVExprForVPValueAbs) {
- const char *ModuleString =
- "define void @f(i32 %x) {\n"
- "entry:\n"
- " br label %loop\n"
- "loop:\n"
- " %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]\n"
- " %iv.next = add nuw nsw i32 %iv, 1\n"
- " %exit = icmp eq i32 %iv.next, 4\n"
- " br i1 %exit, label %exit.block, label %loop\n"
- "exit.block:\n"
- " ret void\n"
- "}\n";
+ const char *ModuleString = R"(
+define void @f(i32 %x) {
+entry:
+ br label %loop
+loop:
+ %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+ %iv.next = add nuw nsw i32 %iv, 1
+ %exit = icmp eq i32 %iv.next, 4
+ br i1 %exit, label %exit.block, label %loop
+exit.block:
+ ret void
+}
+)";
Module &M = parseModule(ModuleString);
Function *F = M.getFunction("f");
@@ -60,14 +61,14 @@ TEST_F(VPlanSCEVTest, GetSCEVExprForVPValueAbs) {
VPlan Plan(LoopHeader);
Argument *X = F->getArg(0);
VPValue *Op = Plan.getOrAddLiveIn(X);
- VPValue *IsIntMinPoison = Plan.getOrAddLiveIn(ConstantInt::getTrue(*Ctx));
+ VPValue *IsIntMinPoison = Plan.getTrue();
VPWidenIntrinsicRecipe Abs(Intrinsic::abs, {Op, IsIntMinPoison},
X->getType());
const SCEV *Expr = vputils::getSCEVExprForVPValue(&Abs, PSE, L);
EXPECT_EQ(SE->getAbsExpr(SE->getSCEV(X), /*IsNSW=*/true), Expr);
- IsIntMinPoison = Plan.getOrAddLiveIn(ConstantInt::getFalse(*Ctx));
+ IsIntMinPoison = Plan.getFalse();
VPWidenIntrinsicRecipe WrappingAbs(Intrinsic::abs, {Op, IsIntMinPoison},
X->getType());
Expr = vputils::getSCEVExprForVPValue(&WrappingAbs, PSE, L);
>From 6e4e17069e80e590676dd7b5a68a09f09458b5b9 Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Mon, 4 May 2026 21:42:49 -0700
Subject: [PATCH 3/5] fixup! [VPlan] Add SCEV support for abs intrinsic
---
llvm/lib/Transforms/Vectorize/VPlanUtils.cpp | 13 ++++++++-----
.../Transforms/Vectorize/VPlanTest.cpp | 19 ++++++++++---------
2 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
index 2ab58242e6de2..ac493d98e9423 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
@@ -258,12 +258,15 @@ const SCEV *vputils::getSCEVExprForVPValue(const VPValue *V,
return CreateSCEV({LHSVal, RHSVal}, [&](ArrayRef<SCEVUse> Ops) {
return SE.getSMinExpr(Ops[0], Ops[1]);
});
- const APInt *IsIntMinPoison;
- if (match(V, m_Intrinsic<Intrinsic::abs>(m_VPValue(LHSVal),
- m_APInt(IsIntMinPoison))) &&
- IsIntMinPoison->getBitWidth() == 1)
+ if (match(V, m_Intrinsic<Intrinsic::abs>(m_VPValue(LHSVal), m_VPValue())))
return CreateSCEV({LHSVal}, [&](ArrayRef<SCEVUse> Ops) {
- return SE.getAbsExpr(Ops[0], IsIntMinPoison->isOne());
+ // The is_int_min_poison operand of llvm.abs only states that this
+ // particular call is poison if its input is INT_MIN; it does not
+ // prove the input is never INT_MIN elsewhere. Forwarding it as
+ // SCEV's IsNSW would tag the cached negation SCEV as no-signed-wrap
+ // globally, which is unsound for other uses of the same SCEV.
+ // Revisit once getAbsExpr can carry the flag on a SCEVUse.
+ return SE.getAbsExpr(Ops[0], /*IsNSW=*/false);
});
ArrayRef<VPValue *> Ops;
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index 1f45612726d90..df0f3abb387c7 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -61,18 +61,19 @@ exit.block:
VPlan Plan(LoopHeader);
Argument *X = F->getArg(0);
VPValue *Op = Plan.getOrAddLiveIn(X);
- VPValue *IsIntMinPoison = Plan.getTrue();
- VPWidenIntrinsicRecipe Abs(Intrinsic::abs, {Op, IsIntMinPoison},
- X->getType());
- const SCEV *Expr = vputils::getSCEVExprForVPValue(&Abs, PSE, L);
- EXPECT_EQ(SE->getAbsExpr(SE->getSCEV(X), /*IsNSW=*/true), Expr);
+ // is_int_min_poison is intentionally ignored when building the SCEV: it
+ // is a local poison property of the call, not a global no-wrap fact.
+ // Both forms must therefore produce the same wrapping abs SCEV.
+ const SCEV *ExpectedExpr = SE->getAbsExpr(SE->getSCEV(X), /*IsNSW=*/false);
+
+ VPWidenIntrinsicRecipe Abs(Intrinsic::abs, {Op, Plan.getTrue()},
+ X->getType());
+ EXPECT_EQ(ExpectedExpr, vputils::getSCEVExprForVPValue(&Abs, PSE, L));
- IsIntMinPoison = Plan.getFalse();
- VPWidenIntrinsicRecipe WrappingAbs(Intrinsic::abs, {Op, IsIntMinPoison},
+ VPWidenIntrinsicRecipe WrappingAbs(Intrinsic::abs, {Op, Plan.getFalse()},
X->getType());
- Expr = vputils::getSCEVExprForVPValue(&WrappingAbs, PSE, L);
- EXPECT_EQ(SE->getAbsExpr(SE->getSCEV(X), /*IsNSW=*/false), Expr);
+ EXPECT_EQ(ExpectedExpr, vputils::getSCEVExprForVPValue(&WrappingAbs, PSE, L));
}
TEST_F(VPInstructionTest, insertBefore) {
>From 02d6f3e31d69910aa002590d1f3c9b59a3e6f10f Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Tue, 5 May 2026 04:00:22 -0700
Subject: [PATCH 4/5] fixup! have an empty loop
---
llvm/unittests/Transforms/Vectorize/VPlanTest.cpp | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index df0f3abb387c7..3f3ba7bd6ea25 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -42,12 +42,7 @@ define void @f(i32 %x) {
entry:
br label %loop
loop:
- %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
- %iv.next = add nuw nsw i32 %iv, 1
- %exit = icmp eq i32 %iv.next, 4
- br i1 %exit, label %exit.block, label %loop
-exit.block:
- ret void
+ br label %loop
}
)";
>From 03d4ce7ca8fab4aa1c006152ea914c0fcbd83c42 Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Thu, 7 May 2026 23:29:57 -0700
Subject: [PATCH 5/5] fixup! address comments
---
llvm/lib/Transforms/Vectorize/VPlanUtils.cpp | 10 ++++------
llvm/unittests/Transforms/Vectorize/VPlanTest.cpp | 13 +++++++------
2 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
index ac493d98e9423..53345d1da1b82 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
@@ -260,12 +260,10 @@ const SCEV *vputils::getSCEVExprForVPValue(const VPValue *V,
});
if (match(V, m_Intrinsic<Intrinsic::abs>(m_VPValue(LHSVal), m_VPValue())))
return CreateSCEV({LHSVal}, [&](ArrayRef<SCEVUse> Ops) {
- // The is_int_min_poison operand of llvm.abs only states that this
- // particular call is poison if its input is INT_MIN; it does not
- // prove the input is never INT_MIN elsewhere. Forwarding it as
- // SCEV's IsNSW would tag the cached negation SCEV as no-signed-wrap
- // globally, which is unsound for other uses of the same SCEV.
- // Revisit once getAbsExpr can carry the flag on a SCEVUse.
+ // is_int_min_poison only makes this call poison on INT_MIN; it does
+ // not prove the input is never INT_MIN, so forwarding it as IsNSW
+ // would attach a no-signed-wrap fact to the SCEV that is unsound
+ // for any other use of the same expression.
return SE.getAbsExpr(Ops[0], /*IsNSW=*/false);
});
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index 3f3ba7bd6ea25..61acb5846a9cb 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -57,18 +57,19 @@ define void @f(i32 %x) {
Argument *X = F->getArg(0);
VPValue *Op = Plan.getOrAddLiveIn(X);
- // is_int_min_poison is intentionally ignored when building the SCEV: it
- // is a local poison property of the call, not a global no-wrap fact.
- // Both forms must therefore produce the same wrapping abs SCEV.
- const SCEV *ExpectedExpr = SE->getAbsExpr(SE->getSCEV(X), /*IsNSW=*/false);
+ // is_int_min_poison is local to the call, not a global no-wrap fact.
+ // Exercise both getAbsExpr flag paths; SCEV drops IsNSW for this input.
+ const SCEV *XSCEV = SE->getSCEV(X);
VPWidenIntrinsicRecipe Abs(Intrinsic::abs, {Op, Plan.getTrue()},
X->getType());
- EXPECT_EQ(ExpectedExpr, vputils::getSCEVExprForVPValue(&Abs, PSE, L));
+ EXPECT_EQ(SE->getAbsExpr(XSCEV, /*IsNSW=*/true),
+ vputils::getSCEVExprForVPValue(&Abs, PSE, L));
VPWidenIntrinsicRecipe WrappingAbs(Intrinsic::abs, {Op, Plan.getFalse()},
X->getType());
- EXPECT_EQ(ExpectedExpr, vputils::getSCEVExprForVPValue(&WrappingAbs, PSE, L));
+ EXPECT_EQ(SE->getAbsExpr(XSCEV, /*IsNSW=*/false),
+ vputils::getSCEVExprForVPValue(&WrappingAbs, PSE, L));
}
TEST_F(VPInstructionTest, insertBefore) {
More information about the llvm-commits
mailing list