[llvm] [LV] Add initial legality checks for loops with unbound loads. (PR #152422)

Shih-Po Hung via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 22 00:27:50 PDT 2025


https://github.com/arcbbb updated https://github.com/llvm/llvm-project/pull/152422

>From dc5b7c14e92f0850fc1f097401ed1c5c97c02613 Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Wed, 6 Aug 2025 17:44:19 -0700
Subject: [PATCH 1/9] [LV] Add initial legality checks for loops with
 non-dereferenceable load.

---
 llvm/include/llvm/Analysis/Loads.h            |  8 +++++
 .../llvm/Analysis/TargetTransformInfo.h       |  4 +++
 .../llvm/Analysis/TargetTransformInfoImpl.h   |  2 ++
 .../Vectorize/LoopVectorizationLegality.h     |  8 +++++
 llvm/lib/Analysis/Loads.cpp                   | 16 ++++++++++
 llvm/lib/Analysis/TargetTransformInfo.cpp     |  4 +++
 .../Target/RISCV/RISCVTargetTransformInfo.h   |  3 ++
 .../Vectorize/LoopVectorizationLegality.cpp   | 31 +++++++++++++++++--
 .../Transforms/Vectorize/LoopVectorize.cpp    |  7 +++++
 9 files changed, 80 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/Analysis/Loads.h b/llvm/include/llvm/Analysis/Loads.h
index 84564563de8e3..080757b6d1fe0 100644
--- a/llvm/include/llvm/Analysis/Loads.h
+++ b/llvm/include/llvm/Analysis/Loads.h
@@ -91,6 +91,14 @@ LLVM_ABI bool isDereferenceableReadOnlyLoop(
     Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
     SmallVectorImpl<const SCEVPredicate *> *Predicates = nullptr);
 
+/// Return true if the loop \p L cannot fault on any iteration and only
+/// contains read-only memory accesses. Also collect loads that are not
+/// guaranteed to be dereferenceable.
+LLVM_ABI bool isReadOnlyLoopWithSafeOrSpeculativeLoads(
+    Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
+    SmallVectorImpl<LoadInst *> *SpeculativeLoads,
+    SmallVectorImpl<const SCEVPredicate *> *Predicates = nullptr);
+
 /// Return true if we know that executing a load from this value cannot trap.
 ///
 /// If DT and ScanFrom are specified this method performs context-sensitive
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index aa4550de455e0..2b8e6be92238c 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -1857,6 +1857,10 @@ class TargetTransformInfo {
   /// \returns True if the target supports scalable vectors.
   LLVM_ABI bool supportsScalableVectors() const;
 
+  /// \returns True if the target supports speculative load intrinsics (e.g.,
+  /// vp.load.ff).
+  LLVM_ABI bool supportsSpeculativeLoads() const;
+
   /// \return true when scalable vectorization is preferred.
   LLVM_ABI bool enableScalableVectorization() const;
 
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
index abdbca04488db..1df93ecc7ec16 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -1106,6 +1106,8 @@ class TargetTransformInfoImplBase {
 
   virtual bool supportsScalableVectors() const { return false; }
 
+  virtual bool supportsSpeculativeLoads() const { return false; }
+
   virtual bool enableScalableVectorization() const { return false; }
 
   virtual bool hasActiveVectorLength() const { return false; }
diff --git a/llvm/include/llvm/Transforms/Vectorize/LoopVectorizationLegality.h b/llvm/include/llvm/Transforms/Vectorize/LoopVectorizationLegality.h
index 43ff084816d18..3b5638f3f570a 100644
--- a/llvm/include/llvm/Transforms/Vectorize/LoopVectorizationLegality.h
+++ b/llvm/include/llvm/Transforms/Vectorize/LoopVectorizationLegality.h
@@ -445,6 +445,11 @@ class LoopVectorizationLegality {
   /// Returns a list of all known histogram operations in the loop.
   bool hasHistograms() const { return !Histograms.empty(); }
 
+  /// Returns the loads that may fault and need to be speculative.
+  const SmallPtrSetImpl<const Instruction *> &getSpeculativeLoads() const {
+    return SpeculativeLoads;
+  }
+
   PredicatedScalarEvolution *getPredicatedScalarEvolution() const {
     return &PSE;
   }
@@ -630,6 +635,9 @@ class LoopVectorizationLegality {
   /// may work on the same memory location.
   SmallVector<HistogramInfo, 1> Histograms;
 
+  /// Hold all loads that need to be speculative.
+  SmallPtrSet<const Instruction *, 4> SpeculativeLoads;
+
   /// BFI and PSI are used to check for profile guided size optimizations.
   BlockFrequencyInfo *BFI;
   ProfileSummaryInfo *PSI;
diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index 78d0887d5d87e..c5a55e9903d41 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -870,3 +870,19 @@ bool llvm::isDereferenceableReadOnlyLoop(
   }
   return true;
 }
+
+bool llvm::isReadOnlyLoopWithSafeOrSpeculativeLoads(
+    Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
+    SmallVectorImpl<LoadInst *> *SpeculativeLoads,
+    SmallVectorImpl<const SCEVPredicate *> *Predicates) {
+  for (BasicBlock *BB : L->blocks()) {
+    for (Instruction &I : *BB) {
+      if (auto *LI = dyn_cast<LoadInst>(&I)) {
+        if (!isDereferenceableAndAlignedInLoop(LI, L, *SE, *DT, AC, Predicates))
+          SpeculativeLoads->push_back(LI);
+      } else if (I.mayReadFromMemory() || I.mayWriteToMemory() || I.mayThrow())
+        return false;
+    }
+  }
+  return true;
+}
diff --git a/llvm/lib/Analysis/TargetTransformInfo.cpp b/llvm/lib/Analysis/TargetTransformInfo.cpp
index c7eb2ec18c679..9f05e01d34781 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -1457,6 +1457,10 @@ bool TargetTransformInfo::supportsScalableVectors() const {
   return TTIImpl->supportsScalableVectors();
 }
 
+bool TargetTransformInfo::supportsSpeculativeLoads() const {
+  return TTIImpl->supportsSpeculativeLoads();
+}
+
 bool TargetTransformInfo::enableScalableVectorization() const {
   return TTIImpl->enableScalableVectorization();
 }
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
index 05d504cbcb6bb..54e9c8346b6e2 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
@@ -110,6 +110,9 @@ class RISCVTTIImpl final : public BasicTTIImplBase<RISCVTTIImpl> {
   bool supportsScalableVectors() const override {
     return ST->hasVInstructions();
   }
+  bool supportsSpeculativeLoads() const override {
+    return ST->hasVInstructions();
+  }
   bool enableOrderedReductions() const override { return true; }
   bool enableScalableVectorization() const override {
     return ST->hasVInstructions();
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
index c47fd9421fddd..46660866741ea 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
@@ -1760,16 +1760,41 @@ bool LoopVectorizationLegality::isVectorizableEarlyExitLoop() {
   assert(LatchBB->getUniquePredecessor() == SingleUncountableExitingBlock &&
          "Expected latch predecessor to be the early exiting block");
 
-  // TODO: Handle loops that may fault.
   Predicates.clear();
-  if (!isDereferenceableReadOnlyLoop(TheLoop, PSE.getSE(), DT, AC,
-                                     &Predicates)) {
+  SmallVector<LoadInst *, 4> NonDerefLoads;
+  bool HasSafeAccess =
+      TTI->supportsSpeculativeLoads()
+          ? isReadOnlyLoopWithSafeOrSpeculativeLoads(
+                TheLoop, PSE.getSE(), DT, AC, &NonDerefLoads, &Predicates)
+          : isDereferenceableReadOnlyLoop(TheLoop, PSE.getSE(), DT, AC,
+                                          &Predicates);
+  if (!HasSafeAccess) {
     reportVectorizationFailure(
         "Loop may fault",
         "Cannot vectorize potentially faulting early exit loop",
         "PotentiallyFaultingEarlyExitLoop", ORE, TheLoop);
     return false;
   }
+  // Speculative loads need to be unit-stride.
+  for (LoadInst *LI : NonDerefLoads) {
+    if (LI->getParent() != TheLoop->getHeader()) {
+      reportVectorizationFailure("Cannot vectorize predicated speculative load",
+                                 "SpeculativeLoadNeedsPredication", ORE,
+                                 TheLoop);
+      return false;
+    }
+    int Stride = isConsecutivePtr(LI->getType(), LI->getPointerOperand());
+    if (Stride != 1) {
+      reportVectorizationFailure("Loop contains non-unit-stride load",
+                                 "Cannot vectorize early exit loop with "
+                                 "speculative non-unit-stride load",
+                                 "SpeculativeNonUnitStrideLoadEarlyExitLoop",
+                                 ORE, TheLoop);
+      return false;
+    }
+    SpeculativeLoads.insert(LI);
+    LLVM_DEBUG(dbgs() << "LV: Found speculative load: " << *LI << "\n");
+  }
 
   [[maybe_unused]] const SCEV *SymbolicMaxBTC =
       PSE.getSymbolicMaxBackedgeTakenCount();
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index 9667b506e594f..790a5236d4f04 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -10041,6 +10041,13 @@ bool LoopVectorizePass::processLoop(Loop *L) {
     return false;
   }
 
+  if (!LVL.getSpeculativeLoads().empty()) {
+    reportVectorizationFailure("Auto-vectorization of loops with speculative "
+                               "load is not supported",
+                               "SpeculativeLoadsNotSupported", ORE, L);
+    return false;
+  }
+
   // Entrance to the VPlan-native vectorization path. Outer loops are processed
   // here. They may require CFG and instruction level transformations before
   // even evaluating whether vectorization is profitable. Since we cannot modify

>From 78f05df3753e6a7f167559560e9122a9f96b488c Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Fri, 8 Aug 2025 00:28:14 -0700
Subject: [PATCH 2/9] Fix braces

---
 llvm/lib/Analysis/Loads.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index c5a55e9903d41..d05b89f86f554 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -880,8 +880,9 @@ bool llvm::isReadOnlyLoopWithSafeOrSpeculativeLoads(
       if (auto *LI = dyn_cast<LoadInst>(&I)) {
         if (!isDereferenceableAndAlignedInLoop(LI, L, *SE, *DT, AC, Predicates))
           SpeculativeLoads->push_back(LI);
-      } else if (I.mayReadFromMemory() || I.mayWriteToMemory() || I.mayThrow())
+      } else if (I.mayReadFromMemory() || I.mayWriteToMemory() || I.mayThrow()) {
         return false;
+      }
     }
   }
   return true;

>From f5315f8e262ec049b7e498157b48c5a5e3d36f8e Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Sat, 9 Aug 2025 15:38:09 -0700
Subject: [PATCH 3/9] Limit to single unbound access. Add tests

---
 .../Vectorize/LoopVectorizationLegality.cpp   | 15 ++--
 .../RISCV/unbound-access-legality.ll          | 89 +++++++++++++++++++
 2 files changed, 97 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
index 46660866741ea..b20fab14d5384 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
@@ -1777,15 +1777,9 @@ bool LoopVectorizationLegality::isVectorizableEarlyExitLoop() {
   }
   // Speculative loads need to be unit-stride.
   for (LoadInst *LI : NonDerefLoads) {
-    if (LI->getParent() != TheLoop->getHeader()) {
-      reportVectorizationFailure("Cannot vectorize predicated speculative load",
-                                 "SpeculativeLoadNeedsPredication", ORE,
-                                 TheLoop);
-      return false;
-    }
     int Stride = isConsecutivePtr(LI->getType(), LI->getPointerOperand());
     if (Stride != 1) {
-      reportVectorizationFailure("Loop contains non-unit-stride load",
+      reportVectorizationFailure("Loop contains strided unbound access",
                                  "Cannot vectorize early exit loop with "
                                  "speculative non-unit-stride load",
                                  "SpeculativeNonUnitStrideLoadEarlyExitLoop",
@@ -1795,6 +1789,13 @@ bool LoopVectorizationLegality::isVectorizableEarlyExitLoop() {
     SpeculativeLoads.insert(LI);
     LLVM_DEBUG(dbgs() << "LV: Found speculative load: " << *LI << "\n");
   }
+  // Support single Speculative load for now.
+  if (NonDerefLoads.size() > 1) {
+      reportVectorizationFailure("Loop contains more than one unbound access",
+                                 "TooManySpeculativeLoadInEarlyExitLoop",
+                                 ORE, TheLoop);
+      return false;
+  }
 
   [[maybe_unused]] const SCEV *SymbolicMaxBTC =
       PSE.getSymbolicMaxBackedgeTakenCount();
diff --git a/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll b/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
new file mode 100644
index 0000000000000..e35d109304274
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
@@ -0,0 +1,89 @@
+; REQUIRES: asserts
+; RUN: opt -passes=loop-vectorize -debug-only=loop-vectorize -disable-output 2>&1 -mtriple=riscv64 -mattr=+v -S %s | FileCheck %s
+
+define ptr @two_unbound_access(ptr %first, ptr %last, ptr %addr2) {
+; CHECK-LABEL: LV: Checking a loop in 'two_unbound_access'
+; CHECK:       LV: Not vectorizing: Loop contains more than one unbound access.
+entry:
+  %cond = icmp eq ptr %first, %last
+  br i1 %cond, label %return, label %for.body
+
+for.body:
+  %first.addr = phi ptr [ %first, %entry], [ %first.next, %for.inc ]
+  %match.addr = phi ptr [ %addr2, %entry ], [ %match.next, %for.inc ]
+  %1 = load i32, ptr %first.addr, align 4
+  %match.value = load i32, ptr %match.addr, align 4
+  %cmp1 = icmp eq i32 %1, %match.value
+  br i1 %cmp1, label %early.exit, label %for.inc
+
+for.inc:
+  %match.next = getelementptr inbounds nuw i8, ptr %match.addr, i64 4
+  %first.next = getelementptr inbounds i8, ptr %first.addr, i64 4
+  %exit = icmp eq ptr %first.next, %last
+  br i1 %exit, label %main.exit, label %for.body
+
+early.exit:
+  br label %return
+
+main.exit:
+  br label %return
+
+return:
+  %retval = phi ptr [ %first, %entry ], [ %last, %main.exit ], [ %first.addr, %early.exit ]
+  ret ptr %retval
+}
+
+define ptr @unbound_strided_access(ptr %first, ptr %last, i32 %value) {
+; CHECK-LABEL: LV: Checking a loop in 'unbound_strided_access'
+; CHECK:       LV: Not vectorizing: Loop contains strided unbound access.
+entry:
+  %cond = icmp eq ptr %first, %last
+  br i1 %cond, label %return, label %for.body
+
+for.body:
+  %first.addr = phi ptr [ %first, %entry ], [ %first.next, %for.inc ]
+  %1 = load i32, ptr %first.addr, align 4
+  %cond2= icmp eq i32 %1, %value
+  br i1 %cond2, label %for.end, label %for.inc
+
+for.inc:
+  %first.next = getelementptr inbounds i32, ptr %first.addr, i64 2
+  %cond3 = icmp eq ptr %first.next, %last
+  br i1 %cond3, label %for.end, label %for.body
+
+for.end:
+  %retval.ph = phi ptr [ %first.addr, %for.body ], [ %last, %for.inc ]
+  br label %return
+
+return:
+  %retval = phi ptr [ %first, %entry ], [ %retval.ph, %for.end ]
+  ret ptr %retval
+}
+
+define ptr @single_unbound_access(ptr %first, ptr %last, i32 %value) {
+; CHECK-LABEL: LV: Checking a loop in 'single_unbound_access'
+; CHECK:       LV: We can vectorize this loop!
+; CHECK-NEXT:  LV: Not vectorizing: Auto-vectorization of loops with speculative load is not supported.
+entry:
+  %cond = icmp eq ptr %first, %last
+  br i1 %cond, label %return, label %for.body
+
+for.body:
+  %first.addr = phi ptr [ %first, %entry ], [ %first.next, %for.inc ]
+  %1 = load i32, ptr %first.addr, align 4
+  %cond2= icmp eq i32 %1, %value
+  br i1 %cond2, label %for.end, label %for.inc
+
+for.inc:
+  %first.next = getelementptr inbounds i32, ptr %first.addr, i64 1
+  %cond3 = icmp eq ptr %first.next, %last
+  br i1 %cond3, label %for.end, label %for.body
+
+for.end:
+  %retval.ph = phi ptr [ %first.addr, %for.body ], [ %last, %for.inc ]
+  br label %return
+
+return:
+  %retval = phi ptr [ %first, %entry ], [ %retval.ph, %for.end ]
+  ret ptr %retval
+}

>From d3246bd1b132e63ede51b02417f37ae8f3d0cea3 Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Sat, 9 Aug 2025 15:39:30 -0700
Subject: [PATCH 4/9] clang-formatted

---
 llvm/lib/Analysis/Loads.cpp                               | 3 ++-
 .../Transforms/Vectorize/LoopVectorizationLegality.cpp    | 8 ++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index d05b89f86f554..b09dd227978b1 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -880,7 +880,8 @@ bool llvm::isReadOnlyLoopWithSafeOrSpeculativeLoads(
       if (auto *LI = dyn_cast<LoadInst>(&I)) {
         if (!isDereferenceableAndAlignedInLoop(LI, L, *SE, *DT, AC, Predicates))
           SpeculativeLoads->push_back(LI);
-      } else if (I.mayReadFromMemory() || I.mayWriteToMemory() || I.mayThrow()) {
+      } else if (I.mayReadFromMemory() || I.mayWriteToMemory() ||
+                 I.mayThrow()) {
         return false;
       }
     }
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
index b20fab14d5384..0fa3a36db1a3e 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
@@ -1791,10 +1791,10 @@ bool LoopVectorizationLegality::isVectorizableEarlyExitLoop() {
   }
   // Support single Speculative load for now.
   if (NonDerefLoads.size() > 1) {
-      reportVectorizationFailure("Loop contains more than one unbound access",
-                                 "TooManySpeculativeLoadInEarlyExitLoop",
-                                 ORE, TheLoop);
-      return false;
+    reportVectorizationFailure("Loop contains more than one unbound access",
+                               "TooManySpeculativeLoadInEarlyExitLoop", ORE,
+                               TheLoop);
+    return false;
   }
 
   [[maybe_unused]] const SCEV *SymbolicMaxBTC =

>From eb316eeff7688d577e5a061ada36f7e68fada25a Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Sat, 9 Aug 2025 15:42:06 -0700
Subject: [PATCH 5/9] trivial fix: use lower case

---
 llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
index 0fa3a36db1a3e..37e1693e256bb 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
@@ -1789,7 +1789,7 @@ bool LoopVectorizationLegality::isVectorizableEarlyExitLoop() {
     SpeculativeLoads.insert(LI);
     LLVM_DEBUG(dbgs() << "LV: Found speculative load: " << *LI << "\n");
   }
-  // Support single Speculative load for now.
+  // Support single speculative load for now.
   if (NonDerefLoads.size() > 1) {
     reportVectorizationFailure("Loop contains more than one unbound access",
                                "TooManySpeculativeLoadInEarlyExitLoop", ORE,

>From faa61124a3369bd72e0f01466102f6c22af26528 Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Sat, 9 Aug 2025 15:46:12 -0700
Subject: [PATCH 6/9] trivial fix

---
 .../Transforms/LoopVectorize/RISCV/unbound-access-legality.ll | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll b/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
index e35d109304274..a5786a0d131f7 100644
--- a/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
+++ b/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
@@ -43,7 +43,7 @@ entry:
 for.body:
   %first.addr = phi ptr [ %first, %entry ], [ %first.next, %for.inc ]
   %1 = load i32, ptr %first.addr, align 4
-  %cond2= icmp eq i32 %1, %value
+  %cond2 = icmp eq i32 %1, %value
   br i1 %cond2, label %for.end, label %for.inc
 
 for.inc:
@@ -71,7 +71,7 @@ entry:
 for.body:
   %first.addr = phi ptr [ %first, %entry ], [ %first.next, %for.inc ]
   %1 = load i32, ptr %first.addr, align 4
-  %cond2= icmp eq i32 %1, %value
+  %cond2 = icmp eq i32 %1, %value
   br i1 %cond2, label %for.end, label %for.inc
 
 for.inc:

>From b206c9f152010d330e2ec40c3822b5134b3e206f Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Mon, 11 Aug 2025 22:56:48 -0700
Subject: [PATCH 7/9] Address comment and refine TTI to check data type

---
 llvm/include/llvm/Analysis/Loads.h            |  6 ----
 .../llvm/Analysis/TargetTransformInfo.h       |  7 ++---
 .../llvm/Analysis/TargetTransformInfoImpl.h   |  6 ++--
 llvm/lib/Analysis/Loads.cpp                   | 15 ---------
 llvm/lib/Analysis/TargetTransformInfo.cpp     |  9 +++---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   | 12 +++++++
 llvm/lib/Target/RISCV/RISCVISelLowering.h     |  4 +++
 .../Target/RISCV/RISCVTargetTransformInfo.h   |  8 +++--
 .../Vectorize/LoopVectorizationLegality.cpp   | 29 ++++++++---------
 .../RISCV/unbound-access-legality.ll          | 31 ++++++++-----------
 llvm/unittests/Analysis/LoadsTest.cpp         |  5 ++-
 11 files changed, 63 insertions(+), 69 deletions(-)

diff --git a/llvm/include/llvm/Analysis/Loads.h b/llvm/include/llvm/Analysis/Loads.h
index 080757b6d1fe0..7f28afafb3500 100644
--- a/llvm/include/llvm/Analysis/Loads.h
+++ b/llvm/include/llvm/Analysis/Loads.h
@@ -85,12 +85,6 @@ LLVM_ABI bool isDereferenceableAndAlignedInLoop(
     AssumptionCache *AC = nullptr,
     SmallVectorImpl<const SCEVPredicate *> *Predicates = nullptr);
 
-/// Return true if the loop \p L cannot fault on any iteration and only
-/// contains read-only memory accesses.
-LLVM_ABI bool isDereferenceableReadOnlyLoop(
-    Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
-    SmallVectorImpl<const SCEVPredicate *> *Predicates = nullptr);
-
 /// Return true if the loop \p L cannot fault on any iteration and only
 /// contains read-only memory accesses. Also collect loads that are not
 /// guaranteed to be dereferenceable.
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index 2b8e6be92238c..0fcc5d1b3fb98 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -843,6 +843,9 @@ class TargetTransformInfo {
   /// Return true if the target supports strided load.
   LLVM_ABI bool isLegalStridedLoadStore(Type *DataType, Align Alignment) const;
 
+  /// Return true if the target supports speculative load.
+  LLVM_ABI bool isLegalSpeculativeLoad(Type *DataType, Align Alignment) const;
+
   /// Return true is the target supports interleaved access for the given vector
   /// type \p VTy, interleave factor \p Factor, alignment \p Alignment and
   /// address space \p AddrSpace.
@@ -1857,10 +1860,6 @@ class TargetTransformInfo {
   /// \returns True if the target supports scalable vectors.
   LLVM_ABI bool supportsScalableVectors() const;
 
-  /// \returns True if the target supports speculative load intrinsics (e.g.,
-  /// vp.load.ff).
-  LLVM_ABI bool supportsSpeculativeLoads() const;
-
   /// \return true when scalable vectorization is preferred.
   LLVM_ABI bool enableScalableVectorization() const;
 
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
index 1df93ecc7ec16..6aca7e1412271 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -374,6 +374,10 @@ class TargetTransformInfoImplBase {
     return false;
   }
 
+  virtual bool isLegalSpeculativeLoad(Type *DataType, Align Alignment) const {
+    return false;
+  }
+
   virtual bool isLegalInterleavedAccessType(VectorType *VTy, unsigned Factor,
                                             Align Alignment,
                                             unsigned AddrSpace) const {
@@ -1106,8 +1110,6 @@ class TargetTransformInfoImplBase {
 
   virtual bool supportsScalableVectors() const { return false; }
 
-  virtual bool supportsSpeculativeLoads() const { return false; }
-
   virtual bool enableScalableVectorization() const { return false; }
 
   virtual bool hasActiveVectorLength() const { return false; }
diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index b09dd227978b1..c39587131dac0 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -856,21 +856,6 @@ bool llvm::canReplacePointersIfEqual(const Value *From, const Value *To,
   return isPointerAlwaysReplaceable(From, To, DL);
 }
 
-bool llvm::isDereferenceableReadOnlyLoop(
-    Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
-    SmallVectorImpl<const SCEVPredicate *> *Predicates) {
-  for (BasicBlock *BB : L->blocks()) {
-    for (Instruction &I : *BB) {
-      if (auto *LI = dyn_cast<LoadInst>(&I)) {
-        if (!isDereferenceableAndAlignedInLoop(LI, L, *SE, *DT, AC, Predicates))
-          return false;
-      } else if (I.mayReadFromMemory() || I.mayWriteToMemory() || I.mayThrow())
-        return false;
-    }
-  }
-  return true;
-}
-
 bool llvm::isReadOnlyLoopWithSafeOrSpeculativeLoads(
     Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
     SmallVectorImpl<LoadInst *> *SpeculativeLoads,
diff --git a/llvm/lib/Analysis/TargetTransformInfo.cpp b/llvm/lib/Analysis/TargetTransformInfo.cpp
index 9f05e01d34781..3e94c816488fd 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -531,6 +531,11 @@ bool TargetTransformInfo::isLegalStridedLoadStore(Type *DataType,
   return TTIImpl->isLegalStridedLoadStore(DataType, Alignment);
 }
 
+bool TargetTransformInfo::isLegalSpeculativeLoad(Type *DataType,
+                                                 Align Alignment) const {
+  return TTIImpl->isLegalSpeculativeLoad(DataType, Alignment);
+}
+
 bool TargetTransformInfo::isLegalInterleavedAccessType(
     VectorType *VTy, unsigned Factor, Align Alignment,
     unsigned AddrSpace) const {
@@ -1457,10 +1462,6 @@ bool TargetTransformInfo::supportsScalableVectors() const {
   return TTIImpl->supportsScalableVectors();
 }
 
-bool TargetTransformInfo::supportsSpeculativeLoads() const {
-  return TTIImpl->supportsSpeculativeLoads();
-}
-
 bool TargetTransformInfo::enableScalableVectorization() const {
   return TTIImpl->enableScalableVectorization();
 }
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 03e54b3d395e3..192435d134e0f 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24405,6 +24405,18 @@ bool RISCVTargetLowering::isLegalStridedLoadStore(EVT DataType,
   return true;
 }
 
+bool RISCVTargetLowering::isLegalSpeculativeLoad(EVT DataType,
+                                                 Align Alignment) const {
+  if (!Subtarget.hasVInstructions())
+    return false;
+
+  EVT ScalarType = DataType.getScalarType();
+  if (!isLegalElementTypeForRVV(ScalarType))
+    return false;
+
+  return true;
+}
+
 MachineInstr *
 RISCVTargetLowering::EmitKCFICheck(MachineBasicBlock &MBB,
                                    MachineBasicBlock::instr_iterator &MBBI,
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.h b/llvm/lib/Target/RISCV/RISCVISelLowering.h
index 433b8be5c562e..a99f6fb38c8b6 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.h
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.h
@@ -425,6 +425,10 @@ class RISCVTargetLowering : public TargetLowering {
   /// alignment is legal.
   bool isLegalStridedLoadStore(EVT DataType, Align Alignment) const;
 
+  /// Return true if a speculative load of the given result type and
+  /// alignment is legal.
+  bool isLegalSpeculativeLoad(EVT DataType, Align Alignment) const;
+
   unsigned getMaxSupportedInterleaveFactor() const override { return 8; }
 
   bool fallBackToDAGISel(const Instruction &Inst) const override;
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
index 54e9c8346b6e2..ba13bdf89063a 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
@@ -110,9 +110,6 @@ class RISCVTTIImpl final : public BasicTTIImplBase<RISCVTTIImpl> {
   bool supportsScalableVectors() const override {
     return ST->hasVInstructions();
   }
-  bool supportsSpeculativeLoads() const override {
-    return ST->hasVInstructions();
-  }
   bool enableOrderedReductions() const override { return true; }
   bool enableScalableVectorization() const override {
     return ST->hasVInstructions();
@@ -327,6 +324,11 @@ class RISCVTTIImpl final : public BasicTTIImplBase<RISCVTTIImpl> {
     return TLI->isLegalStridedLoadStore(DataTypeVT, Alignment);
   }
 
+  bool isLegalSpeculativeLoad(Type *DataType, Align Alignment) const override {
+    EVT DataTypeVT = TLI->getValueType(DL, DataType);
+    return TLI->isLegalSpeculativeLoad(DataTypeVT, Alignment);
+  }
+
   bool isLegalInterleavedAccessType(VectorType *VTy, unsigned Factor,
                                     Align Alignment,
                                     unsigned AddrSpace) const override {
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
index 37e1693e256bb..1d7ec4f43311f 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
@@ -1762,40 +1762,37 @@ bool LoopVectorizationLegality::isVectorizableEarlyExitLoop() {
 
   Predicates.clear();
   SmallVector<LoadInst *, 4> NonDerefLoads;
-  bool HasSafeAccess =
-      TTI->supportsSpeculativeLoads()
-          ? isReadOnlyLoopWithSafeOrSpeculativeLoads(
-                TheLoop, PSE.getSE(), DT, AC, &NonDerefLoads, &Predicates)
-          : isDereferenceableReadOnlyLoop(TheLoop, PSE.getSE(), DT, AC,
-                                          &Predicates);
-  if (!HasSafeAccess) {
+  if (!isReadOnlyLoopWithSafeOrSpeculativeLoads(TheLoop, PSE.getSE(), DT, AC,
+                                                &NonDerefLoads, &Predicates)) {
     reportVectorizationFailure(
         "Loop may fault",
         "Cannot vectorize potentially faulting early exit loop",
         "PotentiallyFaultingEarlyExitLoop", ORE, TheLoop);
     return false;
   }
-  // Speculative loads need to be unit-stride.
+  // Check non-dereferenceable loads if any.
   for (LoadInst *LI : NonDerefLoads) {
+    // Only support unit-stride access for now.
     int Stride = isConsecutivePtr(LI->getType(), LI->getPointerOperand());
     if (Stride != 1) {
       reportVectorizationFailure("Loop contains strided unbound access",
                                  "Cannot vectorize early exit loop with "
-                                 "speculative non-unit-stride load",
+                                 "speculative strided load",
                                  "SpeculativeNonUnitStrideLoadEarlyExitLoop",
                                  ORE, TheLoop);
       return false;
     }
+    if (!TTI->isLegalSpeculativeLoad(LI->getType(), LI->getAlign())) {
+      reportVectorizationFailure("Loop may fault",
+                                 "Cannot vectorize early exit loop with "
+                                 "illegal speculative load",
+                                 "IllegalSpeculativeLoadEarlyExitLoop", ORE,
+                                 TheLoop);
+      return false;
+    }
     SpeculativeLoads.insert(LI);
     LLVM_DEBUG(dbgs() << "LV: Found speculative load: " << *LI << "\n");
   }
-  // Support single speculative load for now.
-  if (NonDerefLoads.size() > 1) {
-    reportVectorizationFailure("Loop contains more than one unbound access",
-                               "TooManySpeculativeLoadInEarlyExitLoop", ORE,
-                               TheLoop);
-    return false;
-  }
 
   [[maybe_unused]] const SCEV *SymbolicMaxBTC =
       PSE.getSymbolicMaxBackedgeTakenCount();
diff --git a/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll b/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
index a5786a0d131f7..07e64784da84b 100644
--- a/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
+++ b/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
@@ -1,35 +1,30 @@
 ; REQUIRES: asserts
 ; RUN: opt -passes=loop-vectorize -debug-only=loop-vectorize -disable-output 2>&1 -mtriple=riscv64 -mattr=+v -S %s | FileCheck %s
 
-define ptr @two_unbound_access(ptr %first, ptr %last, ptr %addr2) {
-; CHECK-LABEL: LV: Checking a loop in 'two_unbound_access'
-; CHECK:       LV: Not vectorizing: Loop contains more than one unbound access.
+define ptr @unsupported_data_type(ptr %first, ptr %last, i128 %value) {
+; CHECK-LABEL: LV: Checking a loop in 'unsupported_data_type'
+; CHECK:  LV: Not vectorizing: Loop may fault.
 entry:
   %cond = icmp eq ptr %first, %last
   br i1 %cond, label %return, label %for.body
 
 for.body:
-  %first.addr = phi ptr [ %first, %entry], [ %first.next, %for.inc ]
-  %match.addr = phi ptr [ %addr2, %entry ], [ %match.next, %for.inc ]
-  %1 = load i32, ptr %first.addr, align 4
-  %match.value = load i32, ptr %match.addr, align 4
-  %cmp1 = icmp eq i32 %1, %match.value
-  br i1 %cmp1, label %early.exit, label %for.inc
+  %first.addr = phi ptr [ %first, %entry ], [ %first.next, %for.inc ]
+  %1 = load i128, ptr %first.addr, align 4
+  %cond2 = icmp eq i128 %1, %value
+  br i1 %cond2, label %for.end, label %for.inc
 
 for.inc:
-  %match.next = getelementptr inbounds nuw i8, ptr %match.addr, i64 4
-  %first.next = getelementptr inbounds i8, ptr %first.addr, i64 4
-  %exit = icmp eq ptr %first.next, %last
-  br i1 %exit, label %main.exit, label %for.body
-
-early.exit:
-  br label %return
+  %first.next = getelementptr inbounds i128, ptr %first.addr, i64 1
+  %cond3 = icmp eq ptr %first.next, %last
+  br i1 %cond3, label %for.end, label %for.body
 
-main.exit:
+for.end:
+  %retval.ph = phi ptr [ %first.addr, %for.body ], [ %last, %for.inc ]
   br label %return
 
 return:
-  %retval = phi ptr [ %first, %entry ], [ %last, %main.exit ], [ %first.addr, %early.exit ]
+  %retval = phi ptr [ %first, %entry ], [ %retval.ph, %for.end ]
   ret ptr %retval
 }
 
diff --git a/llvm/unittests/Analysis/LoadsTest.cpp b/llvm/unittests/Analysis/LoadsTest.cpp
index c4f5b22318e34..fab2aeb745ad0 100644
--- a/llvm/unittests/Analysis/LoadsTest.cpp
+++ b/llvm/unittests/Analysis/LoadsTest.cpp
@@ -195,7 +195,10 @@ loop.end:
     assert(Header->getName() == "loop");
     Loop *L = LI.getLoopFor(Header);
 
-    return isDereferenceableReadOnlyLoop(L, &SE, &DT, &AC);
+    SmallVector<LoadInst *, 4> NonDerefLoads;
+    return isReadOnlyLoopWithSafeOrSpeculativeLoads(L, &SE, &DT, &AC,
+                                                    &NonDerefLoads) &&
+           NonDerefLoads.empty();
   };
 
   ASSERT_TRUE(IsDerefReadOnlyLoop(F1));

>From 72383ddc007db4c577d817006fed1875a48569a5 Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Wed, 20 Aug 2025 18:45:21 -0700
Subject: [PATCH 8/9] Query subtarget feature for misaligned access support

---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 192435d134e0f..25305bd10f115 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -24414,6 +24414,10 @@ bool RISCVTargetLowering::isLegalSpeculativeLoad(EVT DataType,
   if (!isLegalElementTypeForRVV(ScalarType))
     return false;
 
+  if (!Subtarget.enableUnalignedVectorMem() &&
+      Alignment < ScalarType.getStoreSize())
+    return false;
+
   return true;
 }
 

>From 0c1d007f8226ce4c321f85ad6f70c2427ca4ae53 Mon Sep 17 00:00:00 2001
From: ShihPo Hung <shihpo.hung at sifive.com>
Date: Fri, 22 Aug 2025 00:24:07 -0700
Subject: [PATCH 9/9] Add align 1 case

---
 .../RISCV/unbound-access-legality.ll          | 28 +++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll b/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
index 07e64784da84b..1570b86f91c6e 100644
--- a/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
+++ b/llvm/test/Transforms/LoopVectorize/RISCV/unbound-access-legality.ll
@@ -55,6 +55,34 @@ return:
   ret ptr %retval
 }
 
+; The alignment promise is 1 byte.
+define ptr @single_illegal_align1_access(ptr %first, ptr %last, i32 %value) {
+; CHECK-LABEL: LV: Checking a loop in 'single_illegal_align1_access'
+; CHECK:       LV: Not vectorizing: Loop may fault.
+entry:
+  %cond = icmp eq ptr %first, %last
+  br i1 %cond, label %return, label %for.body
+
+for.body:
+  %first.addr = phi ptr [ %first, %entry ], [ %first.next, %for.inc ]
+  %1 = load i32, ptr %first.addr, align 1
+  %cond2 = icmp eq i32 %1, %value
+  br i1 %cond2, label %for.end, label %for.inc
+
+for.inc:
+  %first.next = getelementptr inbounds i32, ptr %first.addr, i64 1
+  %cond3 = icmp eq ptr %first.next, %last
+  br i1 %cond3, label %for.end, label %for.body
+
+for.end:
+  %retval.ph = phi ptr [ %first.addr, %for.body ], [ %last, %for.inc ]
+  br label %return
+
+return:
+  %retval = phi ptr [ %first, %entry ], [ %retval.ph, %for.end ]
+  ret ptr %retval
+}
+
 define ptr @single_unbound_access(ptr %first, ptr %last, i32 %value) {
 ; CHECK-LABEL: LV: Checking a loop in 'single_unbound_access'
 ; CHECK:       LV: We can vectorize this loop!



More information about the llvm-commits mailing list