[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