[llvm] [RISCV] Track the value range of vsetvlimax (PR #218313)

Pengcheng Wang via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 23 21:04:43 PDT 2026


https://github.com/wangpc-pp created https://github.com/llvm/llvm-project/pull/218313

According to the spec, we know the range of VLEN is [32, 65536]
so we can calculate the range of VLMAX.

If we know the `vscable_range`, we can further reduce the range
to a more exact one.



>From 27ff836ccb0185239b88ffb0ce37afd0f750e959 Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Fri, 21 Aug 2026 17:58:56 +0800
Subject: [PATCH 1/2] [RISCV] Precommit tests for vsetvlimax ranges

---
 .../RISCV/riscv-vsetvlimax-range.ll           | 123 ++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/RISCV/riscv-vsetvlimax-range.ll

diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vsetvlimax-range.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vsetvlimax-range.ll
new file mode 100644
index 0000000000000..bc863dcf9a8b5
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vsetvlimax-range.ll
@@ -0,0 +1,123 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i1 @e8m1_min_i64() #0 {
+; CHECK-LABEL: define i1 @e8m1_min_i64(
+; CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[VL]], 15
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
+  %cmp = icmp uge i64 %vl, 16
+  ret i1 %cmp
+}
+
+define i1 @e8m1_max_i64() #0 {
+; CHECK-LABEL: define i1 @e8m1_max_i64(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i64 [[VL]], 8193
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
+  %cmp = icmp ule i64 %vl, 8192
+  ret i1 %cmp
+}
+
+define i1 @e32mf2_min_i32() #0 {
+; CHECK-LABEL: define i1 @e32mf2_min_i32(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    [[VL:%.*]] = call i32 @llvm.riscv.vsetvlimax.i32(i32 2, i32 7)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i32 [[VL]], 1
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i32 @llvm.riscv.vsetvlimax.i32(i32 2, i32 7)
+  %cmp = icmp uge i32 %vl, 2
+  ret i1 %cmp
+}
+
+define i1 @e8m8_min_i64() #0 {
+; CHECK-LABEL: define i1 @e8m8_min_i64(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[VL]], 127
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
+  %cmp = icmp uge i64 %vl, 128
+  ret i1 %cmp
+}
+
+define i1 @e8m8_max_i64() #0 {
+; CHECK-LABEL: define i1 @e8m8_max_i64(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i64 [[VL]], 65537
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
+  %cmp = icmp ule i64 %vl, 65536
+  ret i1 %cmp
+}
+
+define i1 @fixed_e16m2_i64() #1 {
+; CHECK-LABEL: define i1 @fixed_e16m2_i64(
+; CHECK-SAME: ) #[[ATTR2:[0-9]+]] {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 1, i64 1)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[VL]], 32
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 1, i64 1)
+  %cmp = icmp eq i64 %vl, 32
+  ret i1 %cmp
+}
+
+define i1 @no_vscale_range_i64() {
+; CHECK-LABEL: define i1 @no_vscale_range_i64() {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[VL]], 3
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
+  %cmp = icmp uge i64 %vl, 4
+  ret i1 %cmp
+}
+
+define i1 @no_vscale_range_e8m8_i64() {
+; CHECK-LABEL: define i1 @no_vscale_range_e8m8_i64() {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[VL]], 31
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
+  %cmp = icmp uge i64 %vl, 32
+  ret i1 %cmp
+}
+
+define i1 @e64mf8_zero_i64() #0 {
+; CHECK-LABEL: define i1 @e64mf8_zero_i64(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 3, i64 5)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[VL]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 3, i64 5)
+  %cmp = icmp eq i64 %vl, 0
+  ret i1 %cmp
+}
+
+define i64 @power_of_two_i64() #0 {
+; CHECK-LABEL: define i64 @power_of_two_i64(
+; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
+; CHECK-NEXT:    [[COUNT:%.*]] = call range(i64 0, 15) i64 @llvm.ctpop.i64(i64 [[VL]])
+; CHECK-NEXT:    ret i64 [[COUNT]]
+;
+  %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
+  %count = call i64 @llvm.ctpop.i64(i64 %vl)
+  ret i64 %count
+}
+
+attributes #0 = { vscale_range(2,1024) }
+attributes #1 = { vscale_range(4,4) }

>From f11367b23a5cb43dd122baafb4ea415b2abb3cfa Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Fri, 21 Aug 2026 18:00:51 +0800
Subject: [PATCH 2/2] [RISCV] Track the value range of vsetvlimax

---
 llvm/lib/Analysis/ValueTracking.cpp           | 57 ++++++++++++++++---
 .../RISCV/riscv-vsetvlimax-range.ll           | 52 ++++++-----------
 2 files changed, 66 insertions(+), 43 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index f4ed5e07038da..d20e229096822 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -87,6 +87,38 @@
 using namespace llvm;
 using namespace llvm::PatternMatch;
 
+// Return the architectural range for VLMAX. RISC-V defines VLEN in the range
+// [32, 65536], while vscale_range can provide tighter bounds for VLEN >= 64.
+static ConstantRange getRISCVVSetVLMaxRange(const IntrinsicInst &II,
+                                            unsigned ArgOffset = 0) {
+  unsigned Width = II.getType()->getScalarSizeInBits();
+  constexpr unsigned MinVLen = 32;
+  constexpr unsigned MaxVLen = 65536;
+  ConstantRange VLenRange(APInt(Width, MinVLen), APInt(Width, MaxVLen + 1));
+  if (II.getFunction() &&
+      II.getFunction()->hasFnAttribute(Attribute::VScaleRange)) {
+    ConstantRange VScaleRange = getVScaleRange(II.getFunction(), Width);
+    VScaleRange = VScaleRange.intersectWith(ConstantRange(
+        APInt(Width, 1),
+        APInt(Width, MaxVLen / RISCV::RVVBitsPerBlock + 1)));
+    VLenRange = VScaleRange.multiply(
+        ConstantRange(APInt(Width, RISCV::RVVBitsPerBlock)));
+  }
+
+  auto *VSEW = dyn_cast<ConstantInt>(II.getArgOperand(ArgOffset));
+  auto *VLMULArg = dyn_cast<ConstantInt>(II.getArgOperand(ArgOffset + 1));
+  // These are immarg operands, but keep this helper conservative for malformed
+  // IR rather than asserting while performing generic value analysis.
+  if (!VSEW || !VLMULArg || VSEW->getZExtValue() > 3 ||
+      VLMULArg->getZExtValue() > 7 || VLMULArg->getZExtValue() == 4)
+    return ConstantRange::getFull(Width);
+
+  unsigned SEW = RISCVVType::decodeVSEW(VSEW->getZExtValue());
+  auto VLMUL = static_cast<RISCVVType::VLMUL>(VLMULArg->getZExtValue());
+  unsigned Ratio = RISCVVType::getSEWLMULRatio(SEW, VLMUL);
+  return VLenRange.udiv(ConstantRange(APInt(Width, Ratio)));
+}
+
 // Controls the number of uses of the value searched for possible
 // dominating comparisons.
 static cl::opt<unsigned> DomConditionsMaxUses("dom-conditions-max-uses",
@@ -2275,20 +2307,21 @@ static void computeKnownBitsFromOperator(const Operator *I,
       case Intrinsic::riscv_vsetvli:
       case Intrinsic::riscv_vsetvlimax: {
         bool HasAVL = II->getIntrinsicID() == Intrinsic::riscv_vsetvli;
-        const ConstantRange Range = getVScaleRange(II->getFunction(), BitWidth);
-        uint64_t SEW = RISCVVType::decodeVSEW(
-            cast<ConstantInt>(II->getArgOperand(HasAVL))->getZExtValue());
-        RISCVVType::VLMUL VLMUL = static_cast<RISCVVType::VLMUL>(
-            cast<ConstantInt>(II->getArgOperand(1 + HasAVL))->getZExtValue());
-        uint64_t MaxVLEN =
-            Range.getUnsignedMax().getZExtValue() * RISCV::RVVBitsPerBlock;
-        uint64_t MaxVL = MaxVLEN / RISCVVType::getSEWLMULRatio(SEW, VLMUL);
+        ConstantRange Range = getRISCVVSetVLMaxRange(*II, HasAVL);
+        if (Range.isFullSet())
+          break;
+        uint64_t MaxVL = Range.getUnsignedMax().getZExtValue();
 
         // Result of vsetvli must be not larger than AVL.
         if (HasAVL)
           if (auto *CI = dyn_cast<ConstantInt>(II->getArgOperand(0)))
             MaxVL = std::min(MaxVL, CI->getZExtValue());
 
+        if (MaxVL == 0) {
+          Known.setAllZero();
+          break;
+        }
+
         unsigned KnownZeroFirstBit = Log2_32(MaxVL) + 1;
         if (BitWidth > KnownZeroFirstBit)
           Known.Zero.setBitsFrom(KnownZeroFirstBit);
@@ -2871,6 +2904,12 @@ bool llvm::isKnownToBeAPowerOfTwo(const Value *V, bool OrZero,
         if (II->getArgOperand(0) == II->getArgOperand(1))
           return isKnownToBeAPowerOfTwo(II->getArgOperand(0), OrZero, Q, Depth);
         break;
+      case Intrinsic::riscv_vsetvlimax:
+        // VLEN and LMUL are powers of two, and SEW is a power of two.
+        if (ConstantRange Range = getRISCVVSetVLMaxRange(*II);
+            !Range.isFullSet())
+          return OrZero || !Range.contains(APInt(Range.getBitWidth(), 0));
+        return false;
       default:
         break;
       }
@@ -10390,6 +10429,8 @@ static ConstantRange getRangeForIntrinsic(const IntrinsicInst &II,
   unsigned Width = II.getType()->getScalarSizeInBits();
   const APInt *C;
   switch (II.getIntrinsicID()) {
+  case Intrinsic::riscv_vsetvlimax:
+    return getRISCVVSetVLMaxRange(II);
   case Intrinsic::ctlz:
   case Intrinsic::cttz: {
     APInt Upper(Width, Width);
diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vsetvlimax-range.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vsetvlimax-range.ll
index bc863dcf9a8b5..52d7dac8f6c53 100644
--- a/llvm/test/Transforms/InstCombine/RISCV/riscv-vsetvlimax-range.ll
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vsetvlimax-range.ll
@@ -3,10 +3,8 @@
 
 define i1 @e8m1_min_i64() #0 {
 ; CHECK-LABEL: define i1 @e8m1_min_i64(
-; CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
-; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[VL]], 15
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    ret i1 true
 ;
   %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
   %cmp = icmp uge i64 %vl, 16
@@ -15,10 +13,8 @@ define i1 @e8m1_min_i64() #0 {
 
 define i1 @e8m1_max_i64() #0 {
 ; CHECK-LABEL: define i1 @e8m1_max_i64(
-; CHECK-SAME: ) #[[ATTR1]] {
-; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i64 [[VL]], 8193
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret i1 true
 ;
   %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
   %cmp = icmp ule i64 %vl, 8192
@@ -27,10 +23,8 @@ define i1 @e8m1_max_i64() #0 {
 
 define i1 @e32mf2_min_i32() #0 {
 ; CHECK-LABEL: define i1 @e32mf2_min_i32(
-; CHECK-SAME: ) #[[ATTR1]] {
-; CHECK-NEXT:    [[VL:%.*]] = call i32 @llvm.riscv.vsetvlimax.i32(i32 2, i32 7)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i32 [[VL]], 1
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret i1 true
 ;
   %vl = call i32 @llvm.riscv.vsetvlimax.i32(i32 2, i32 7)
   %cmp = icmp uge i32 %vl, 2
@@ -39,10 +33,8 @@ define i1 @e32mf2_min_i32() #0 {
 
 define i1 @e8m8_min_i64() #0 {
 ; CHECK-LABEL: define i1 @e8m8_min_i64(
-; CHECK-SAME: ) #[[ATTR1]] {
-; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[VL]], 127
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret i1 true
 ;
   %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
   %cmp = icmp uge i64 %vl, 128
@@ -51,10 +43,8 @@ define i1 @e8m8_min_i64() #0 {
 
 define i1 @e8m8_max_i64() #0 {
 ; CHECK-LABEL: define i1 @e8m8_max_i64(
-; CHECK-SAME: ) #[[ATTR1]] {
-; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i64 [[VL]], 65537
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret i1 true
 ;
   %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
   %cmp = icmp ule i64 %vl, 65536
@@ -63,10 +53,8 @@ define i1 @e8m8_max_i64() #0 {
 
 define i1 @fixed_e16m2_i64() #1 {
 ; CHECK-LABEL: define i1 @fixed_e16m2_i64(
-; CHECK-SAME: ) #[[ATTR2:[0-9]+]] {
-; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 1, i64 1)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[VL]], 32
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
+; CHECK-NEXT:    ret i1 true
 ;
   %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 1, i64 1)
   %cmp = icmp eq i64 %vl, 32
@@ -75,9 +63,7 @@ define i1 @fixed_e16m2_i64() #1 {
 
 define i1 @no_vscale_range_i64() {
 ; CHECK-LABEL: define i1 @no_vscale_range_i64() {
-; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[VL]], 3
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 true
 ;
   %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
   %cmp = icmp uge i64 %vl, 4
@@ -86,9 +72,7 @@ define i1 @no_vscale_range_i64() {
 
 define i1 @no_vscale_range_e8m8_i64() {
 ; CHECK-LABEL: define i1 @no_vscale_range_e8m8_i64() {
-; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[VL]], 31
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 true
 ;
   %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 3)
   %cmp = icmp uge i64 %vl, 32
@@ -97,7 +81,7 @@ define i1 @no_vscale_range_e8m8_i64() {
 
 define i1 @e64mf8_zero_i64() #0 {
 ; CHECK-LABEL: define i1 @e64mf8_zero_i64(
-; CHECK-SAME: ) #[[ATTR1]] {
+; CHECK-SAME: ) #[[ATTR0]] {
 ; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 3, i64 5)
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[VL]], 0
 ; CHECK-NEXT:    ret i1 [[CMP]]
@@ -109,10 +93,8 @@ define i1 @e64mf8_zero_i64() #0 {
 
 define i64 @power_of_two_i64() #0 {
 ; CHECK-LABEL: define i64 @power_of_two_i64(
-; CHECK-SAME: ) #[[ATTR1]] {
-; CHECK-NEXT:    [[VL:%.*]] = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
-; CHECK-NEXT:    [[COUNT:%.*]] = call range(i64 0, 15) i64 @llvm.ctpop.i64(i64 [[VL]])
-; CHECK-NEXT:    ret i64 [[COUNT]]
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret i64 1
 ;
   %vl = call i64 @llvm.riscv.vsetvlimax.i64(i64 0, i64 0)
   %count = call i64 @llvm.ctpop.i64(i64 %vl)



More information about the llvm-commits mailing list