[llvm] [VPlan] Add SCEV support for abs intrinsic (PR #195678)

Madhur Amilkanthwar via llvm-commits llvm-commits at lists.llvm.org
Mon May 4 21:52:45 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/3] [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/3] 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/3] 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) {



More information about the llvm-commits mailing list