[llvm] [llubi] Add support for poison-generating/UB-implying annotations (PR #195339)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Sun May 3 07:08:39 PDT 2026


https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/195339

>From 811be6f70cec299d4829f4113319993fe6c65f54 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 2 May 2026 03:50:25 +0800
Subject: [PATCH 1/7] [llubi] Add support for poison-generating/UB-implying
 attributes and metadata

---
 llvm/tools/llubi/lib/Interpreter.cpp | 202 ++++++++++++++++++++++++++-
 1 file changed, 196 insertions(+), 6 deletions(-)

diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 6d27accd6cb93..4d29defceeed8 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -64,6 +64,82 @@ static AnyValue mulNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
   return Res;
 }
 
+/// Visit the scalar values recursively. The callback function may modify the
+/// value in-place.
+static void forEachScalarValue(AnyValue &V,
+                               function_ref<void(AnyValue &)> Visit) {
+  if (V.isNone())
+    return;
+
+  if (V.isAggregate()) {
+    for (auto &SubValue : V.asAggregate()) {
+      forEachScalarValue(SubValue, Visit);
+    }
+    return;
+  }
+
+  Visit(V);
+}
+
+static void applyRangeAttr(AnyValue &V, const ConstantRange &CR) {
+  forEachScalarValue(V, [&](AnyValue &Scalar) {
+    if (Scalar.isInteger() && !CR.contains(Scalar.asInteger()))
+      Scalar = AnyValue::poison();
+  });
+}
+
+static void applyNoFPClassAttr(AnyValue &V, FPClassTest NoFPClass) {
+  forEachScalarValue(V, [NoFPClass](AnyValue &Scalar) {
+    if (Scalar.isFloat() && (Scalar.asFloat().classify() & NoFPClass))
+      Scalar = AnyValue::poison();
+  });
+}
+
+static void applyNonNullAttr(AnyValue &V) {
+  forEachScalarValue(V, [](AnyValue &Scalar) {
+    if (Scalar.isPointer() && Scalar.asPointer().address().isZero())
+      Scalar = AnyValue::poison();
+  });
+}
+
+static void applyAlignAttr(AnyValue &V, Align Alignment) {
+  forEachScalarValue(V, [Alignment](AnyValue &Scalar) {
+    if (Scalar.isPointer() &&
+        Scalar.asPointer().address().countr_zero() >= Log2(Alignment))
+      Scalar = AnyValue::poison();
+  });
+}
+
+static bool applyNoUndefAttr(AnyValue &V) {
+  bool ContainsPoison = false;
+  forEachScalarValue(
+      V, [&](AnyValue &Scalar) { ContainsPoison |= Scalar.isPoison(); });
+  return ContainsPoison;
+}
+
+/// Assumes V is either a poison or a pointer.
+static bool applyDereferenceableBytesAttr(AnyValue &V, uint64_t Bytes,
+                                          bool OrNull) {
+  if (V.isPoison())
+    return true;
+
+  auto &Ptr = V.asPointer();
+  const APInt &PtrAddr = Ptr.address();
+  if (PtrAddr.isZero()) {
+    if (OrNull)
+      return false;
+    return true;
+  }
+  auto *MO = Ptr.getMemoryObject();
+  if (!MO)
+    return true;
+
+  // TODO: check read_provenance
+
+  return Bytes > MO->getSize() || PtrAddr.ult(MO->getAddress()) ||
+         PtrAddr.ugt(MO->getAddress() + MO->getSize() - Bytes);
+}
+
 /// Instruction executor using the visitor pattern.
 /// Unlike the Context class that manages the global state,
 /// InstExecutor only maintains the state for call frames.
@@ -462,12 +538,18 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   void returnFromCallee() {
-    // TODO: handle retval attributes (Attributes from known callee should be
-    // applied if available).
-    // TODO: handle metadata
     auto &CB = cast<CallBase>(*CurrentFrame->PC);
     CurrentFrame->CalleeArgs.clear();
     AnyValue &RetVal = CurrentFrame->CalleeRetVal;
+    if (Type *RetTy = CB.getType(); !RetTy->isVoidTy()) {
+      // Handle attributes on the return value (Attributes from resolved callee
+      // should be applied if available).
+      AttributeSet AttrsAtCallSite = CB.getRetAttributes();
+      AttributeSet AttrsAtCallee =
+          CurrentFrame->ResolvedCallee->getAttributes().getRetAttrs();
+      handleAttributes(RetTy, RetVal, AttrsAtCallSite, AttrsAtCallee);
+      handleMetadata(RetTy, RetVal, CB);
+    }
     setResult(CB, std::move(RetVal));
 
     if (auto *II = dyn_cast<InvokeInst>(&CB))
@@ -924,10 +1006,105 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return AnyValue();
   }
 
+  /// Handle both poison-generating and UB-implying attributes for parameters
+  /// and return values.
+  void handleAttributes(Type *Ty, AnyValue &V, AttributeSet AttrsAtCallSite,
+                        AttributeSet AttrsAtCallee) {
+    if (Ty->isIntOrIntVectorTy()) {
+      if (auto CRAttr = AttrsAtCallSite.getAttribute(Attribute::Range);
+          CRAttr.isValid())
+        applyRangeAttr(V, CRAttr.getRange());
+      if (auto CRAttr = AttrsAtCallee.getAttribute(Attribute::Range);
+          CRAttr.isValid())
+        applyRangeAttr(V, CRAttr.getRange());
+    }
+    if (AttributeFuncs::isNoFPClassCompatibleType(Ty)) {
+      if (auto CRAttr = AttrsAtCallSite.getAttribute(Attribute::NoFPClass);
+          CRAttr.isValid())
+        applyNoFPClassAttr(V, CRAttr.getNoFPClass());
+      if (auto CRAttr = AttrsAtCallee.getAttribute(Attribute::NoFPClass);
+          CRAttr.isValid())
+        applyNoFPClassAttr(V, CRAttr.getNoFPClass());
+    }
+    if (Ty->isPtrOrPtrVectorTy()) {
+      if (AttrsAtCallSite.hasAttribute(Attribute::NonNull) ||
+          AttrsAtCallee.hasAttribute(Attribute::NonNull))
+        applyNonNullAttr(V);
+      if (MaybeAlign Align = AttrsAtCallSite.getAlignment())
+        applyAlignAttr(V, *Align);
+      if (MaybeAlign Align = AttrsAtCallee.getAlignment())
+        applyAlignAttr(V, *Align);
+    }
+    if ((AttrsAtCallSite.hasAttribute(Attribute::NoUndef) ||
+         AttrsAtCallee.hasAttribute(Attribute::NoUndef)) &&
+        applyNoUndefAttr(V)) {
+      reportImmediateUB("The value violates noundef attribute.");
+      return;
+    }
+    if (Ty->isPointerTy()) {
+      if (uint64_t DereferenceableBytes =
+              std::max(AttrsAtCallSite.getDereferenceableBytes(),
+                       AttrsAtCallee.getDereferenceableBytes())) {
+        if (applyDereferenceableBytesAttr(V, DereferenceableBytes,
+                                          /*OrNull=*/false))
+          reportImmediateUB("The value violates dereferenceable attribute.");
+      } else if (uint64_t DereferenceableOrNullBytes =
+                     std::max(AttrsAtCallSite.getDereferenceableOrNullBytes(),
+                              AttrsAtCallee.getDereferenceableOrNullBytes())) {
+        if (applyDereferenceableBytesAttr(V, DereferenceableOrNullBytes,
+                                          /*OrNull=*/true))
+          reportImmediateUB("The value violates "
+                            "dereferenceable_or_null attribute.");
+      }
+    }
+  }
+
+  /// Handle both poison-generating and UB-implying metadata on instructions.
+  void handleMetadata(Type *Ty, AnyValue &V, Instruction &I) {
+    auto ExtractFirstIntOperand = [](const MDNode *Node) {
+      return mdconst::extract<ConstantInt>(Node->getOperand(0))->getZExtValue();
+    };
+
+    if (Ty->isIntOrIntVectorTy()) {
+      if (MDNode *Ranges = I.getMetadata(LLVMContext::MD_range))
+        applyRangeAttr(V, getConstantRangeFromMetadata(*Ranges));
+    }
+    if (AttributeFuncs::isNoFPClassCompatibleType(Ty)) {
+      if (const MDNode *NoFPClass = I.getMetadata(LLVMContext::MD_nofpclass)) {
+        applyNoFPClassAttr(
+            V, static_cast<FPClassTest>(ExtractFirstIntOperand(NoFPClass)));
+      }
+    }
+    if (Ty->isPtrOrPtrVectorTy()) {
+      if (I.hasMetadata(LLVMContext::MD_nonnull))
+        applyNonNullAttr(V);
+      if (const MDNode *Alignment = I.getMetadata(LLVMContext::MD_align))
+        applyAlignAttr(V, Align(ExtractFirstIntOperand(Alignment)));
+    }
+    if (I.hasMetadata(LLVMContext::MD_noundef) && applyNoUndefAttr(V)) {
+      reportImmediateUB("The value violates !noundef metadata.");
+      return;
+    }
+    if (Ty->isPointerTy()) {
+      if (const MDNode *DereferenceableBytes =
+              I.getMetadata(LLVMContext::MD_dereferenceable)) {
+        if (applyDereferenceableBytesAttr(
+                V, ExtractFirstIntOperand(DereferenceableBytes),
+                /*OrNull=*/false))
+          reportImmediateUB("The value violates !dereferenceable metadata.");
+      } else if (const MDNode *DereferenceableOrNullBytes =
+                     I.getMetadata(LLVMContext::MD_dereferenceable_or_null)) {
+        if (applyDereferenceableBytesAttr(
+                V, ExtractFirstIntOperand(DereferenceableOrNullBytes),
+                /*OrNull=*/true))
+          reportImmediateUB("The value violates "
+                            "!dereferenceable_or_null metadata.");
+      }
+    }
+  }
+
   void enterCall(CallBase &CB) {
     Function *Callee = CB.getCalledFunction();
-    // TODO: handle parameter attributes (Attributes from known callee should be
-    // applied if available).
     // TODO: handle byval/initializes
     auto &CalleeArgs = CurrentFrame->CalleeArgs;
     assert(CalleeArgs.empty() &&
@@ -973,6 +1150,19 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     assert(
         Callee->getFunctionType() == CB.getFunctionType() &&
         "Expected the callee function type to match the call site signature.");
+
+    // Handle parameter attributes (Attributes from resolved callee should be
+    // applied if available).
+    for (auto [I, Arg] : enumerate(CB.args())) {
+      Type *ArgTy = Arg->getType();
+      AnyValue &ArgVal = CalleeArgs[I];
+      // CallBase::paramHasAttr also checks parameter attributes at known
+      // callee. We do it explicitly to avoid duplication.
+      AttributeSet AttrsAtCallSite = CB.getParamAttributes(I);
+      AttributeSet AttrsAtCallee = Callee->getAttributes().getParamAttrs(I);
+      handleAttributes(ArgTy, ArgVal, AttrsAtCallSite, AttrsAtCallee);
+    }
+
     CurrentFrame->ResolvedCallee = Callee;
     if (Callee->isIntrinsic()) {
       CurrentFrame->CalleeRetVal = callIntrinsic(CB, CalleeArgs);
@@ -1395,7 +1585,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     auto RetVal =
         load(getValue(LI.getPointerOperand()), LI.getAlign(), LI.getType());
     // TODO: track volatile loads
-    // TODO: handle metadata
+    handleMetadata(LI.getType(), RetVal, LI);
     setResult(LI, std::move(RetVal));
   }
 

>From 24421f78149c39e47a39f325ee036e8de5477f23 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 2 May 2026 21:22:49 +0800
Subject: [PATCH 2/7] [llubi] Add support for assume operand bundles

---
 llvm/tools/llubi/lib/Interpreter.cpp | 98 ++++++++++++++++++++++++----
 1 file changed, 85 insertions(+), 13 deletions(-)

diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 4d29defceeed8..bcc069643702e 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -96,18 +96,13 @@ static void applyNoFPClassAttr(AnyValue &V, FPClassTest NoFPClass) {
 }
 
 static void applyNonNullAttr(AnyValue &V) {
-  forEachScalarValue(V, [](AnyValue &Scalar) {
-    if (Scalar.isPointer() && Scalar.asPointer().address().isZero())
-      Scalar = AnyValue::poison();
-  });
+  if (V.isPointer() && V.asPointer().address().isZero())
+    V = AnyValue::poison();
 }
 
 static void applyAlignAttr(AnyValue &V, Align Alignment) {
-  forEachScalarValue(V, [Alignment](AnyValue &Scalar) {
-    if (Scalar.isPointer() &&
-        Scalar.asPointer().address().countr_zero() >= Log2(Alignment))
-      Scalar = AnyValue::poison();
-  });
+  if (V.isPointer() && V.asPointer().address().countr_zero() < Log2(Alignment))
+    V = AnyValue::poison();
 }
 
 static bool applyNoUndefAttr(AnyValue &V) {
@@ -118,7 +113,7 @@ static bool applyNoUndefAttr(AnyValue &V) {
 }
 
 /// Assumes V is either a poison or a pointer.
-static bool applyDereferenceableBytesAttr(AnyValue &V, uint64_t Bytes,
+static bool applyDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
                                           bool OrNull) {
   if (V.isPoison())
     return true;
@@ -457,6 +452,20 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return Boolean == BooleanKind::True;
   }
 
+  uint64_t getUInt64NonPoison(const AnyValue &V) {
+    if (V.isPoison()) {
+      reportImmediateUB("Unexpected poison integer value.");
+      return 0;
+    }
+    const APInt &C = V.asInteger();
+    if (!C.isIntN(64)) {
+      reportImmediateUB("The integer value is too large.");
+      return 0;
+    }
+
+    return C.getZExtValue();
+  }
+
 public:
   InstExecutor(Context &C, EventHandler &H, Function &F,
                ArrayRef<AnyValue> Args, AnyValue &RetVal)
@@ -566,13 +575,76 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     case Intrinsic::assume:
       switch (Args[0].asBoolean()) {
       case BooleanKind::True:
+        for (unsigned Idx = 0; Idx < CB.getNumOperandBundles(); Idx++) {
+          CallBase::BundleOpInfo BOI =
+              CB.getBundleOpInfoForOperand(CB.arg_size() + Idx);
+          auto GetBundleArg = [&](uint32_t Offset) -> Value * {
+            return (CB.op_begin() + BOI.Begin + Offset)->get();
+          };
+          if (BOI.End == BOI.Begin)
+            continue;
+          Value *WasOnVal = GetBundleArg(0);
+          // Bail out on unrecognized operand bundles.
+          if (!WasOnVal->getType()->isPointerTy())
+            continue;
+          const AnyValue &WasOn = getValue(WasOnVal);
+          if (WasOn.isPoison()) {
+            reportImmediateUB("Assume on poison pointer.");
+            break;
+          }
+          const Pointer &WasOnPtr = WasOn.asPointer();
+          Attribute::AttrKind Kind =
+              Attribute::getAttrKindFromName(BOI.Tag->getKey());
+          switch (Kind) {
+          case Attribute::Alignment: {
+            // Alignment assumptions should have 2 or 3 arguments.
+            // If there are two integer arguments, use the largest power of 2
+            // that divides them as the alignment.
+            uint64_t Alignment = getUInt64NonPoison(getValue(GetBundleArg(1)));
+            if (BOI.End - BOI.Begin == 3)
+              Alignment = MinAlign(
+                  Alignment, getUInt64NonPoison(getValue(GetBundleArg(2))));
+            if (!isPowerOf2_64(Alignment)) {
+              if (!WasOn.asPointer().address().isZero())
+                reportImmediateUB("Assume on nonnull pointer with a "
+                                  "non-power-of-two alignment.");
+              break;
+            }
+            if (WasOnPtr.address().countr_zero() < Log2_64(Alignment))
+              reportImmediateUB(
+                  "The pointer address violates alignment assumption.");
+            break;
+          }
+          case Attribute::NonNull:
+            if (WasOnPtr.address().isZero())
+              reportImmediateUB(
+                  "The pointer address violates nonnull assumption.");
+            break;
+          case Attribute::Dereferenceable:
+          case Attribute::DereferenceableOrNull: {
+            uint64_t DereferenceableBytes =
+                getUInt64NonPoison(getValue(GetBundleArg(1)));
+            if (applyDereferenceableBytesAttr(
+                    WasOn, DereferenceableBytes,
+                    Kind == Attribute::DereferenceableOrNull))
+              reportImmediateUB(Kind == Attribute::DereferenceableOrNull
+                                    ? "The pointer address violates "
+                                      "dereferenceable_or_null assumption."
+                                    : "The pointer address violates "
+                                      "dereferenceable assumption.");
+            break;
+          }
+          default:
+            // TODO: handle other operand bundles like separate_storage.
+            break;
+          }
+        }
         break;
       case BooleanKind::False:
       case BooleanKind::Poison:
         reportImmediateUB() << "Assume on false or poison condition.";
         break;
       }
-      // TODO: handle llvm.assume with operand bundles
       return AnyValue();
     case Intrinsic::lifetime_start:
     case Intrinsic::lifetime_end: {
@@ -1026,7 +1098,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           CRAttr.isValid())
         applyNoFPClassAttr(V, CRAttr.getNoFPClass());
     }
-    if (Ty->isPtrOrPtrVectorTy()) {
+    if (Ty->isPointerTy()) {
       if (AttrsAtCallSite.hasAttribute(Attribute::NonNull) ||
           AttrsAtCallee.hasAttribute(Attribute::NonNull))
         applyNonNullAttr(V);
@@ -1075,7 +1147,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
             V, static_cast<FPClassTest>(ExtractFirstIntOperand(NoFPClass)));
       }
     }
-    if (Ty->isPtrOrPtrVectorTy()) {
+    if (Ty->isPointerTy()) {
       if (I.hasMetadata(LLVMContext::MD_nonnull))
         applyNonNullAttr(V);
       if (const MDNode *Alignment = I.getMetadata(LLVMContext::MD_align))

>From d4809b55acdb5ff0e436d39181dbc76c1243c0ee Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 3 May 2026 00:22:27 +0800
Subject: [PATCH 3/7] [llubi] Add tests.

---
 llvm/test/tools/llubi/attributes.ll  | 116 +++++++++++++++++++++++++++
 llvm/tools/llubi/lib/Interpreter.cpp |   9 ++-
 2 files changed, 123 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/tools/llubi/attributes.ll

diff --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
new file mode 100644
index 0000000000000..ffa7fd505476e
--- /dev/null
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -0,0 +1,116 @@
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define range(i32 0, 2) i32 @add_with_range(i32 range(i32 0, 2) %x) {
+  %add = add i32 %x, 1
+  ret i32 %add
+}
+
+define range(i32 0, 2) <4 x i32> @add_with_range_vec(<4 x i32> range(i32 0, 2) %x) {
+  %add = add <4 x i32> %x, splat(i32 1)
+  ret <4 x i32> %add
+}
+
+define nofpclass(nan) half @identity_nofpclass(half nofpclass(inf) %x) {
+  ret half %x
+}
+
+define nofpclass(nan) <4 x half> @identity_nofpclass_vec(<4 x half> nofpclass(inf) %x) {
+  ret <4 x half> %x
+}
+
+define nofpclass(nan) {<2 x half>, <2 x half>} @identity_nofpclass_agg({<2 x half>, <2 x half>} nofpclass(inf) %x) {
+  ret {<2 x half>, <2 x half>} %x
+}
+
+define nonnull ptr @gep_nonnull(ptr nonnull %p) {
+  %gep = getelementptr i8, ptr %p, i32 -1
+  ret ptr %gep
+}
+
+define ptr @gep(ptr %p) {
+  %gep = getelementptr i8, ptr %p, i32 -1
+  ret ptr %gep
+}
+
+define align 16 ptr @gep_align(ptr align 8 %p) {
+  %gep = getelementptr i8, ptr %p, i32 8
+  ret ptr %gep
+}
+
+define align 16 <4 x ptr> @gep_align_vec(<4 x ptr> align 8 %p) {
+  %gep = getelementptr i8, <4 x ptr> %p, i32 8
+  ret <4 x ptr> %gep
+}
+
+define noundef i32 @identity_noundef(i32 noundef %x) {
+  ret i32 %x
+}
+
+define noundef {i32, <2 x i32>, [2 x i32]} @identity_noundef_agg({i32, <2 x i32>, [2 x i32]} noundef %x) {
+  ret {i32, <2 x i32>, [2 x i32]} %x
+}
+
+define noundef dereferenceable(4) ptr @identity_dereferenceable(ptr noundef dereferenceable(4) %p) {
+  ret ptr %p
+}
+
+define noundef dereferenceable(1) ptr @identity_dereferenceable_single_byte(ptr noundef dereferenceable(1) %p) {
+  ret ptr %p
+}
+
+define noundef dereferenceable_or_null(1) ptr @identity_dereferenceable_or_null(ptr noundef dereferenceable_or_null(1) %p) {
+  ret ptr %p
+}
+
+define void @main() {
+  %range_valid = call i32 @add_with_range(i32 0)
+  %range_poison_input = call i32 @add_with_range(i32 poison)
+  %range_invalid_input = call i32 @add_with_range(i32 3)
+  %range_invalid_output = call i32 @add_with_range(i32 1)
+  %range_vec = call <4 x i32> @add_with_range_vec(<4 x i32> <i32 0, i32 poison, i32 3, i32 1>)
+  %range_intrinsic_valid = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 15)
+  %range_intrinsic_invalid_input = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 1500)
+  %range_intrinsic_invalid_output = call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 0)
+  %range_intrinsic_vec = call range(i32 1, 32) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> range(i32 1, 255) <i32 15, i32 1500, i32 0, i32 poison>)
+  
+  %nofpclass_valid = call half @identity_nofpclass(half 1.0)
+  %nofpclass_poison_input = call half @identity_nofpclass(half poison)
+  %nofpclass_invalid_input = call half @identity_nofpclass(half 0xH7C00)
+  %nofpclass_invalid_output = call half @identity_nofpclass(half 0xH7E00)
+  %nofpclass_vec = call <4 x half> @identity_nofpclass_vec(<4 x half> <half 1.0, half poison, half 0xH7C00, half 0xH7E00>)
+  %nofpclass_callsite_invalid_input = call half @identity_nofpclass(half nofpclass(norm) 1.0)
+  %nofpclass_callsite_invalid_output = call nofpclass(norm) half @identity_nofpclass(half 1.0)
+  %nofpclass_agg = call {<2 x half>, <2 x half>} @identity_nofpclass_agg({<2 x half>, <2 x half>} {<2 x half> <half 1.0, half poison>, <2 x half> <half 0xH7C00, half 0xH7E00>})
+
+  %alloc = alloca i32
+  %ptr_one = getelementptr i8, ptr null, i32 1
+  %nonnull_valid = call ptr @gep_nonnull(ptr %alloc)
+  %nonnull_invalid_input = call ptr @gep_nonnull(ptr null)
+  %nonnull_invalid_output = call ptr @gep_nonnull(ptr %ptr_one)
+  %nonnull_callsite_valid = call nonnull ptr @gep(ptr nonnull %alloc)
+  %nonnull_callsite_invalid_input = call ptr @gep(ptr nonnull null)
+  %nonnull_callsite_invalid_output = call nonnull ptr @gep(ptr %ptr_one)
+
+  %align_valid = call ptr @gep_align(ptr %alloc)
+  %align_invalid_input = call ptr @gep_align(ptr %ptr_one)
+  %align_invalid_output = call ptr @gep_align(ptr null)
+  %ptr_vec_1 = insertelement <4 x ptr> poison, ptr %alloc, i32 0
+  %ptr_vec_2 = insertelement <4 x ptr> %ptr_vec_1, ptr %ptr_one, i32 1
+  %ptr_vec_3 = insertelement <4 x ptr> %ptr_vec_2, ptr null, i32 2
+  %align_vec = call <4 x ptr> @gep_align_vec(<4 x ptr> %ptr_vec_3)
+  %align_valid_mixed = call align 1 ptr @gep_align(ptr align 4 %alloc)
+  %align_invalid_mixed1 = call ptr @gep_align(ptr align 1024 %alloc)
+  %align_invalid_mixed2 = call align 1024 ptr @gep_align(ptr %alloc)
+
+  %noundef_valid = call noundef i32 @identity_noundef(i32 noundef 1)
+  %noundef_valid_agg = call noundef {i32, <2 x i32>, [2 x i32]} @identity_noundef_agg({i32, <2 x i32>, [2 x i32]} noundef zeroinitializer)
+  
+  %deref_valid = call ptr @identity_dereferenceable(ptr %alloc)
+  %deref_valid_mixed = call dereferenceable(1) ptr @identity_dereferenceable(ptr dereferenceable(1) %alloc)
+  %gep = getelementptr i8, ptr %alloc, i32 3
+  %deref_valid_middle = call ptr @identity_dereferenceable_single_byte(ptr %gep)
+  %deref_or_null_valid1 = call ptr @identity_dereferenceable_or_null(ptr %alloc)
+  %deref_or_null_valid2 = call ptr @identity_dereferenceable_or_null(ptr null)
+  %deref_or_null_mixed = call dereferenceable_or_null(1) dereferenceable(1) ptr @identity_dereferenceable_or_null(ptr dereferenceable_or_null(1) dereferenceable(1) %alloc)
+  ret void
+}
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index bcc069643702e..3bea922767e7a 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -101,8 +101,10 @@ static void applyNonNullAttr(AnyValue &V) {
 }
 
 static void applyAlignAttr(AnyValue &V, Align Alignment) {
-  if (V.isPointer() && V.asPointer().address().countr_zero() < Log2(Alignment))
-    V = AnyValue::poison();
+  forEachScalarValue(V, [Alignment](AnyValue &Scalar) {
+    if (Scalar.isPointer() && Scalar.asPointer().address().countr_zero() < Log2(Alignment))
+      Scalar = AnyValue::poison();
+  });
 }
 
 static bool applyNoUndefAttr(AnyValue &V) {
@@ -1102,6 +1104,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       if (AttrsAtCallSite.hasAttribute(Attribute::NonNull) ||
           AttrsAtCallee.hasAttribute(Attribute::NonNull))
         applyNonNullAttr(V);
+    }
+    if (Ty->isPtrOrPtrVectorTy()) {
       if (MaybeAlign Align = AttrsAtCallSite.getAlignment())
         applyAlignAttr(V, *Align);
       if (MaybeAlign Align = AttrsAtCallee.getAlignment())
@@ -1150,6 +1154,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     if (Ty->isPointerTy()) {
       if (I.hasMetadata(LLVMContext::MD_nonnull))
         applyNonNullAttr(V);
+      // Unlike align attributes, !align is only defined for pointer types.
       if (const MDNode *Alignment = I.getMetadata(LLVMContext::MD_align))
         applyAlignAttr(V, Align(ExtractFirstIntOperand(Alignment)));
     }

>From b1afb7664ff504805529e41977869a4f716efa69 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 3 May 2026 00:38:54 +0800
Subject: [PATCH 4/7] [llubi] Update tests.

---
 llvm/test/tools/llubi/attributes.ll  | 216 ++++++++++++++++++++++++++-
 llvm/tools/llubi/lib/Interpreter.cpp |   6 +-
 2 files changed, 217 insertions(+), 5 deletions(-)

diff --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
index ffa7fd505476e..af593c51d7c96 100644
--- a/llvm/test/tools/llubi/attributes.ll
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
 ; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
 
 define range(i32 0, 2) i32 @add_with_range(i32 range(i32 0, 2) %x) {
@@ -62,6 +63,8 @@ define noundef dereferenceable_or_null(1) ptr @identity_dereferenceable_or_null(
   ret ptr %p
 }
 
+declare i32 @printf(ptr, ...)
+
 define void @main() {
   %range_valid = call i32 @add_with_range(i32 0)
   %range_poison_input = call i32 @add_with_range(i32 poison)
@@ -72,7 +75,7 @@ define void @main() {
   %range_intrinsic_invalid_input = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 1500)
   %range_intrinsic_invalid_output = call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 0)
   %range_intrinsic_vec = call range(i32 1, 32) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> range(i32 1, 255) <i32 15, i32 1500, i32 0, i32 poison>)
-  
+
   %nofpclass_valid = call half @identity_nofpclass(half 1.0)
   %nofpclass_poison_input = call half @identity_nofpclass(half poison)
   %nofpclass_invalid_input = call half @identity_nofpclass(half 0xH7C00)
@@ -104,7 +107,7 @@ define void @main() {
 
   %noundef_valid = call noundef i32 @identity_noundef(i32 noundef 1)
   %noundef_valid_agg = call noundef {i32, <2 x i32>, [2 x i32]} @identity_noundef_agg({i32, <2 x i32>, [2 x i32]} noundef zeroinitializer)
-  
+
   %deref_valid = call ptr @identity_dereferenceable(ptr %alloc)
   %deref_valid_mixed = call dereferenceable(1) ptr @identity_dereferenceable(ptr dereferenceable(1) %alloc)
   %gep = getelementptr i8, ptr %alloc, i32 3
@@ -112,5 +115,214 @@ define void @main() {
   %deref_or_null_valid1 = call ptr @identity_dereferenceable_or_null(ptr %alloc)
   %deref_or_null_valid2 = call ptr @identity_dereferenceable_or_null(ptr null)
   %deref_or_null_mixed = call dereferenceable_or_null(1) dereferenceable(1) ptr @identity_dereferenceable_or_null(ptr dereferenceable_or_null(1) dereferenceable(1) %alloc)
+
+  %fmt_n_out = alloca [6 x i8]
+  store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out
+  %res = call range(i32 0, 15) noundef i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6)
   ret void
 }
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: add_with_range
+; CHECK-NEXT:   i32 %x = i32 0
+; CHECK-NEXT:   %add = add i32 %x, 1 => i32 1
+; CHECK-NEXT:   ret i32 %add
+; CHECK-NEXT: Exiting function: add_with_range
+; CHECK-NEXT:   %range_valid = call i32 @add_with_range(i32 0) => i32 1
+; CHECK-NEXT: Entering function: add_with_range
+; CHECK-NEXT:   i32 %x = poison
+; CHECK-NEXT:   %add = add i32 %x, 1 => poison
+; CHECK-NEXT:   ret i32 %add
+; CHECK-NEXT: Exiting function: add_with_range
+; CHECK-NEXT:   %range_poison_input = call i32 @add_with_range(i32 poison) => poison
+; CHECK-NEXT: Entering function: add_with_range
+; CHECK-NEXT:   i32 %x = poison
+; CHECK-NEXT:   %add = add i32 %x, 1 => poison
+; CHECK-NEXT:   ret i32 %add
+; CHECK-NEXT: Exiting function: add_with_range
+; CHECK-NEXT:   %range_invalid_input = call i32 @add_with_range(i32 3) => poison
+; CHECK-NEXT: Entering function: add_with_range
+; CHECK-NEXT:   i32 %x = i32 1
+; CHECK-NEXT:   %add = add i32 %x, 1 => i32 2
+; CHECK-NEXT:   ret i32 %add
+; CHECK-NEXT: Exiting function: add_with_range
+; CHECK-NEXT:   %range_invalid_output = call i32 @add_with_range(i32 1) => poison
+; CHECK-NEXT: Entering function: add_with_range_vec
+; CHECK-NEXT:   <4 x i32> %x = { i32 0, poison, poison, i32 1 }
+; CHECK-NEXT:   %add = add <4 x i32> %x, splat (i32 1) => { i32 1, poison, poison, i32 2 }
+; CHECK-NEXT:   ret <4 x i32> %add
+; CHECK-NEXT: Exiting function: add_with_range_vec
+; CHECK-NEXT:   %range_vec = call <4 x i32> @add_with_range_vec(<4 x i32> <i32 0, i32 poison, i32 3, i32 1>) => { i32 1, poison, poison, poison }
+; CHECK-NEXT:   %range_intrinsic_valid = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 15) => i32 4
+; CHECK-NEXT:   %range_intrinsic_invalid_input = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 1500) => i32 7
+; CHECK-NEXT:   %range_intrinsic_invalid_output = call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 0) => poison
+; CHECK-NEXT:   %range_intrinsic_vec = call range(i32 1, 32) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> range(i32 1, 255) <i32 15, i32 1500, i32 0, i32 poison>) => { i32 4, i32 7, poison, poison }
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT:   half %x = half 1.000000e+00
+; CHECK-NEXT:   ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT:   %nofpclass_valid = call half @identity_nofpclass(half 0xH3C00) => half 1.000000e+00
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT:   half %x = poison
+; CHECK-NEXT:   ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT:   %nofpclass_poison_input = call half @identity_nofpclass(half poison) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT:   half %x = poison
+; CHECK-NEXT:   ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT:   %nofpclass_invalid_input = call half @identity_nofpclass(half 0xH7C00) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT:   half %x = half NaN
+; CHECK-NEXT:   ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT:   %nofpclass_invalid_output = call half @identity_nofpclass(half 0xH7E00) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass_vec
+; CHECK-NEXT:   <4 x half> %x = { half 1.000000e+00, poison, poison, half NaN }
+; CHECK-NEXT:   ret <4 x half> %x
+; CHECK-NEXT: Exiting function: identity_nofpclass_vec
+; CHECK-NEXT:   %nofpclass_vec = call <4 x half> @identity_nofpclass_vec(<4 x half> <half 0xH3C00, half poison, half 0xH7C00, half 0xH7E00>) => { half 1.000000e+00, poison, poison, poison }
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT:   half %x = poison
+; CHECK-NEXT:   ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT:   %nofpclass_callsite_invalid_input = call half @identity_nofpclass(half nofpclass(norm) 0xH3C00) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT:   half %x = half 1.000000e+00
+; CHECK-NEXT:   ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT:   %nofpclass_callsite_invalid_output = call nofpclass(norm) half @identity_nofpclass(half 0xH3C00) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass_agg
+; CHECK-NEXT:   { <2 x half>, <2 x half> } %x = { { half 1.000000e+00, poison }, { poison, half NaN } }
+; CHECK-NEXT:   ret { <2 x half>, <2 x half> } %x
+; CHECK-NEXT: Exiting function: identity_nofpclass_agg
+; CHECK-NEXT:   %nofpclass_agg = call { <2 x half>, <2 x half> } @identity_nofpclass_agg({ <2 x half>, <2 x half> } { <2 x half> <half 0xH3C00, half poison>, <2 x half> <half 0xH7C00, half 0xH7E00> }) => { { half 1.000000e+00, poison }, { poison, poison } }
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %ptr_one = getelementptr i8, ptr null, i32 1 => ptr 0x1 [dangling]
+; CHECK-NEXT: Entering function: gep_nonnull
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x7 [alloc + -1]
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_nonnull
+; CHECK-NEXT:   %nonnull_valid = call ptr @gep_nonnull(ptr %alloc) => ptr 0x7 [alloc + -1]
+; CHECK-NEXT: Entering function: gep_nonnull
+; CHECK-NEXT:   ptr %p = poison
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 -1 => poison
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_nonnull
+; CHECK-NEXT:   %nonnull_invalid_input = call ptr @gep_nonnull(ptr null) => poison
+; CHECK-NEXT: Entering function: gep_nonnull
+; CHECK-NEXT:   ptr %p = ptr 0x1 [dangling]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x0 [dangling]
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_nonnull
+; CHECK-NEXT:   %nonnull_invalid_output = call ptr @gep_nonnull(ptr %ptr_one) => poison
+; CHECK-NEXT: Entering function: gep
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x7 [alloc + -1]
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep
+; CHECK-NEXT:   %nonnull_callsite_valid = call nonnull ptr @gep(ptr nonnull %alloc) => ptr 0x7 [alloc + -1]
+; CHECK-NEXT: Entering function: gep
+; CHECK-NEXT:   ptr %p = poison
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 -1 => poison
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep
+; CHECK-NEXT:   %nonnull_callsite_invalid_input = call ptr @gep(ptr nonnull null) => poison
+; CHECK-NEXT: Entering function: gep
+; CHECK-NEXT:   ptr %p = ptr 0x1 [dangling]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x0 [dangling]
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep
+; CHECK-NEXT:   %nonnull_callsite_invalid_output = call nonnull ptr @gep(ptr %ptr_one) => poison
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x10 [alloc + 8]
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT:   %align_valid = call ptr @gep_align(ptr %alloc) => ptr 0x10 [alloc + 8]
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT:   ptr %p = poison
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 8 => poison
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT:   %align_invalid_input = call ptr @gep_align(ptr %ptr_one) => poison
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT:   ptr %p = ptr 0x0 [dangling]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x8 [dangling]
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT:   %align_invalid_output = call ptr @gep_align(ptr null) => poison
+; CHECK-NEXT:   %ptr_vec_1 = insertelement <4 x ptr> poison, ptr %alloc, i32 0 => { ptr 0x8 [alloc], poison, poison, poison }
+; CHECK-NEXT:   %ptr_vec_2 = insertelement <4 x ptr> %ptr_vec_1, ptr %ptr_one, i32 1 => { ptr 0x8 [alloc], ptr 0x1 [dangling], poison, poison }
+; CHECK-NEXT:   %ptr_vec_3 = insertelement <4 x ptr> %ptr_vec_2, ptr null, i32 2 => { ptr 0x8 [alloc], ptr 0x1 [dangling], ptr 0x0 [dangling], poison }
+; CHECK-NEXT: Entering function: gep_align_vec
+; CHECK-NEXT:   <4 x ptr> %p = { ptr 0x8 [alloc], poison, ptr 0x0 [dangling], poison }
+; CHECK-NEXT:   %gep = getelementptr i8, <4 x ptr> %p, i32 8 => { ptr 0x10 [alloc + 8], poison, ptr 0x8 [dangling], poison }
+; CHECK-NEXT:   ret <4 x ptr> %gep
+; CHECK-NEXT: Exiting function: gep_align_vec
+; CHECK-NEXT:   %align_vec = call <4 x ptr> @gep_align_vec(<4 x ptr> %ptr_vec_3) => { ptr 0x10 [alloc + 8], poison, poison, poison }
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x10 [alloc + 8]
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT:   %align_valid_mixed = call align 1 ptr @gep_align(ptr align 4 %alloc) => ptr 0x10 [alloc + 8]
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT:   ptr %p = poison
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 8 => poison
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT:   %align_invalid_mixed1 = call ptr @gep_align(ptr align 1024 %alloc) => poison
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x10 [alloc + 8]
+; CHECK-NEXT:   ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT:   %align_invalid_mixed2 = call align 1024 ptr @gep_align(ptr %alloc) => poison
+; CHECK-NEXT: Entering function: identity_noundef
+; CHECK-NEXT:   i32 %x = i32 1
+; CHECK-NEXT:   ret i32 %x
+; CHECK-NEXT: Exiting function: identity_noundef
+; CHECK-NEXT:   %noundef_valid = call noundef i32 @identity_noundef(i32 noundef 1) => i32 1
+; CHECK-NEXT: Entering function: identity_noundef_agg
+; CHECK-NEXT:   { i32, <2 x i32>, [2 x i32] } %x = { i32 0, { i32 0, i32 0 }, { i32 0, i32 0 } }
+; CHECK-NEXT:   ret { i32, <2 x i32>, [2 x i32] } %x
+; CHECK-NEXT: Exiting function: identity_noundef_agg
+; CHECK-NEXT:   %noundef_valid_agg = call noundef { i32, <2 x i32>, [2 x i32] } @identity_noundef_agg({ i32, <2 x i32>, [2 x i32] } noundef zeroinitializer) => { i32 0, { i32 0, i32 0 }, { i32 0, i32 0 } }
+; CHECK-NEXT: Entering function: identity_dereferenceable
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable
+; CHECK-NEXT:   %deref_valid = call ptr @identity_dereferenceable(ptr %alloc) => ptr 0x8 [alloc]
+; CHECK-NEXT: Entering function: identity_dereferenceable
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable
+; CHECK-NEXT:   %deref_valid_mixed = call dereferenceable(1) ptr @identity_dereferenceable(ptr dereferenceable(1) %alloc) => ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %alloc, i32 3 => ptr 0xB [alloc + 3]
+; CHECK-NEXT: Entering function: identity_dereferenceable_single_byte
+; CHECK-NEXT:   ptr %p = ptr 0xB [alloc + 3]
+; CHECK-NEXT:   ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable_single_byte
+; CHECK-NEXT:   %deref_valid_middle = call ptr @identity_dereferenceable_single_byte(ptr %gep) => ptr 0xB [alloc + 3]
+; CHECK-NEXT: Entering function: identity_dereferenceable_or_null
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
+; CHECK-NEXT:   %deref_or_null_valid1 = call ptr @identity_dereferenceable_or_null(ptr %alloc) => ptr 0x8 [alloc]
+; CHECK-NEXT: Entering function: identity_dereferenceable_or_null
+; CHECK-NEXT:   ptr %p = ptr 0x0 [dangling]
+; CHECK-NEXT:   ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
+; CHECK-NEXT:   %deref_or_null_valid2 = call ptr @identity_dereferenceable_or_null(ptr null) => ptr 0x0 [dangling]
+; CHECK-NEXT: Entering function: identity_dereferenceable_or_null
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
+; CHECK-NEXT:   %deref_or_null_mixed = call dereferenceable(1) dereferenceable_or_null(1) ptr @identity_dereferenceable_or_null(ptr dereferenceable(1) dereferenceable_or_null(1) %alloc) => ptr 0x8 [alloc]
+; CHECK-NEXT:   %fmt_n_out = alloca [6 x i8], align 1 => ptr 0xC [fmt_n_out]
+; CHECK-NEXT:   store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
+; CHECK-NEXT: N=6
+; CHECK-NEXT:   %res = call noundef range(i32 0, 15) i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6) => i32 4
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 3bea922767e7a..2ae5c4d9b3a36 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -72,9 +72,8 @@ static void forEachScalarValue(AnyValue &V,
     return;
 
   if (V.isAggregate()) {
-    for (auto &SubValue : V.asAggregate()) {
+    for (auto &SubValue : V.asAggregate())
       forEachScalarValue(SubValue, Visit);
-    }
     return;
   }
 
@@ -102,7 +101,8 @@ static void applyNonNullAttr(AnyValue &V) {
 
 static void applyAlignAttr(AnyValue &V, Align Alignment) {
   forEachScalarValue(V, [Alignment](AnyValue &Scalar) {
-    if (Scalar.isPointer() && Scalar.asPointer().address().countr_zero() < Log2(Alignment))
+    if (Scalar.isPointer() &&
+        Scalar.asPointer().address().countr_zero() < Log2(Alignment))
       Scalar = AnyValue::poison();
   });
 }

>From 7b3b1d5c4b76e35ecbe9f43eb717daf7e415f893 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 3 May 2026 01:46:23 +0800
Subject: [PATCH 5/7] [llubi] Add more tests.

---
 llvm/test/tools/llubi/assume_invalid_align.ll |  12 ++
 llvm/test/tools/llubi/assume_misalign.ll      |  12 ++
 .../tools/llubi/assume_nondereferenceable.ll  |  12 ++
 llvm/test/tools/llubi/assume_null.ll          |  10 ++
 .../tools/llubi/assume_operand_bundles.ll     |  44 +++++++
 llvm/test/tools/llubi/assume_poison.ll        |   2 +-
 llvm/test/tools/llubi/assume_poison_align.ll  |  10 ++
 ...e_dereferenceable_ub_nullary_provenance.ll |  18 +++
 .../attribute_dereferenceable_ub_oob1.ll      |  16 +++
 .../attribute_dereferenceable_ub_oob2.ll      |  18 +++
 .../attribute_dereferenceable_ub_oob3.ll      |  18 +++
 .../attribute_dereferenceable_ub_poison.ll    |  14 +++
 .../tools/llubi/attribute_noundef_agg_ub.ll   |  14 +++
 llvm/test/tools/llubi/attribute_noundef_ub.ll |  14 +++
 llvm/test/tools/llubi/metadata.ll             | 108 ++++++++++++++++++
 llvm/test/tools/llubi/metadata_noundef_ub.ll  |  14 +++
 llvm/tools/llubi/lib/Interpreter.cpp          |  16 ++-
 17 files changed, 345 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/tools/llubi/assume_invalid_align.ll
 create mode 100644 llvm/test/tools/llubi/assume_misalign.ll
 create mode 100644 llvm/test/tools/llubi/assume_nondereferenceable.ll
 create mode 100644 llvm/test/tools/llubi/assume_null.ll
 create mode 100644 llvm/test/tools/llubi/assume_operand_bundles.ll
 create mode 100644 llvm/test/tools/llubi/assume_poison_align.ll
 create mode 100644 llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
 create mode 100644 llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
 create mode 100644 llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
 create mode 100644 llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
 create mode 100644 llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
 create mode 100644 llvm/test/tools/llubi/attribute_noundef_agg_ub.ll
 create mode 100644 llvm/test/tools/llubi/attribute_noundef_ub.ll
 create mode 100644 llvm/test/tools/llubi/metadata.ll
 create mode 100644 llvm/test/tools/llubi/metadata_noundef_ub.ll

diff --git a/llvm/test/tools/llubi/assume_invalid_align.ll b/llvm/test/tools/llubi/assume_invalid_align.ll
new file mode 100644
index 0000000000000..9e75bca600e82
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_invalid_align.ll
@@ -0,0 +1,12 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %alloc = alloca i32
+  call void @llvm.assume(i1 true) ["align"(ptr %alloc, i128 18446744073709551616)]
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Immediate UB detected: The integer value is too large.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_misalign.ll b/llvm/test/tools/llubi/assume_misalign.ll
new file mode 100644
index 0000000000000..f25629d77ea29
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_misalign.ll
@@ -0,0 +1,12 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %alloc = alloca i32
+  call void @llvm.assume(i1 true) ["align"(ptr %alloc, i32 2048)]
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Immediate UB detected: The pointer address violates alignment assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_nondereferenceable.ll b/llvm/test/tools/llubi/assume_nondereferenceable.ll
new file mode 100644
index 0000000000000..ba9e0f8763e74
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_nondereferenceable.ll
@@ -0,0 +1,12 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+  %alloc = alloca i32
+  call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 2048)]
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Immediate UB detected: The pointer address violates dereferenceable{{(_or_null)?}} assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_null.ll b/llvm/test/tools/llubi/assume_null.ll
new file mode 100644
index 0000000000000..ef6247d614546
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_null.ll
@@ -0,0 +1,10 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  call void @llvm.assume(i1 true) ["nonnull"(ptr null)]
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: The pointer address violates nonnull assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_operand_bundles.ll b/llvm/test/tools/llubi/assume_operand_bundles.ll
new file mode 100644
index 0000000000000..17742a821cbf9
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_operand_bundles.ll
@@ -0,0 +1,44 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @assume_align_dynamic(ptr %p, i32 %align) {
+  call void @llvm.assume(i1 true) ["align"(ptr %p, i32 4)]
+  call void @llvm.assume(i1 true) ["align"(ptr %p, i32 %align)]
+  call void @llvm.assume(i1 true) ["align"(ptr %p, i32 %align, i32 20)]
+  ret void
+}
+
+define void @main() {
+  %alloc = alloca i32
+  call void @llvm.assume(i1 true) ["nonnull"(ptr %alloc)]
+  call void @llvm.assume(i1 true) ["cold"(), "nonnull"(ptr %alloc), "cold"()]
+  call void @assume_align_dynamic(ptr %alloc, i32 8)
+  call void @llvm.assume(i1 true) ["align"(ptr null, i32 17)]
+  call void @llvm.assume(i1 true) ["align"(ptr null, i32 0)]
+  call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 4)]
+  call void @llvm.assume(i1 true) ["dereferenceable_or_null"(ptr %alloc, i32 4)]
+  call void @llvm.assume(i1 true) ["dereferenceable_or_null"(ptr null, i32 4)]
+  call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr null, i32 4)]
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "nonnull"(ptr %alloc) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "cold"(), "nonnull"(ptr %alloc), "cold"() ]
+; CHECK-NEXT: Entering function: assume_align_dynamic
+; CHECK-NEXT:   ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT:   i32 %align = i32 8
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "align"(ptr %p, i32 4) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "align"(ptr %p, i32 %align) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "align"(ptr %p, i32 %align, i32 20) ]
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: assume_align_dynamic
+; CHECK-NEXT:   call void @assume_align_dynamic(ptr %alloc, i32 8)
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "align"(ptr null, i32 17) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "align"(ptr null, i32 0) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %alloc, i32 4) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable_or_null"(ptr %alloc, i32 4) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable_or_null"(ptr null, i32 4) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr null, i32 4) ]
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/assume_poison.ll b/llvm/test/tools/llubi/assume_poison.ll
index a33bf9224a497..b80e00482896c 100644
--- a/llvm/test/tools/llubi/assume_poison.ll
+++ b/llvm/test/tools/llubi/assume_poison.ll
@@ -8,5 +8,5 @@ define void @main() {
 ; CHECK: Entering function: main
 ; CHECK-NEXT: Stacktrace:
 ; CHECK-NEXT: #0   call void @llvm.assume(i1 poison) at @main
-; CHECK-NEXT: Immediate UB detected: Assume on false or poison condition.
+; CHECK-NEXT: Immediate UB detected: The value violates noundef attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_poison_align.ll b/llvm/test/tools/llubi/assume_poison_align.ll
new file mode 100644
index 0000000000000..6928016fa2ace
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_poison_align.ll
@@ -0,0 +1,10 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  call void @llvm.assume(i1 true) ["align"(ptr poison, i32 4)]
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Assume on poison pointer.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
new file mode 100644
index 0000000000000..91d74b814e9f3
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
@@ -0,0 +1,18 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(4) %x) {
+  ret void
+}
+
+define void @main() {
+  %ptr_storage = alloca i64
+  %p = load ptr, ptr %ptr_storage
+  call void @callee(ptr %p)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %ptr_storage = alloca i64, align 8 => ptr 0x8 [ptr_storage]
+; CHECK-NEXT:   %p = load ptr, ptr %ptr_storage, align 8 => ptr 0xE82FEEACEEB98B3E [dangling]
+; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
new file mode 100644
index 0000000000000..1f0ac195b8817
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
@@ -0,0 +1,16 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(8) %x) {
+  ret void
+}
+
+define void @main() {
+  %alloc = alloca i32
+  call void @callee(ptr %alloc)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
new file mode 100644
index 0000000000000..5d5f0e70435c0
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
@@ -0,0 +1,18 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(2) %x) {
+  ret void
+}
+
+define void @main() {
+  %alloc = alloca i32
+  %gep = getelementptr i8, ptr %alloc, i32 -1
+  call void @callee(ptr %gep)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %alloc, i32 -1 => ptr 0x7 [alloc + -1]
+; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
new file mode 100644
index 0000000000000..90e0094c3989e
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
@@ -0,0 +1,18 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(3) %x) {
+  ret void
+}
+
+define void @main() {
+  %alloc = alloca i32
+  %gep = getelementptr i8, ptr %alloc, i32 2
+  call void @callee(ptr %gep)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %alloc, i32 2 => ptr 0xA [alloc + 2]
+; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
new file mode 100644
index 0000000000000..a3abc66405f3f
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
@@ -0,0 +1,14 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(4) %x) {
+  ret void
+}
+
+define void @main() {
+  call void @callee(ptr poison)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll b/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll
new file mode 100644
index 0000000000000..f112671405f39
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll
@@ -0,0 +1,14 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @callee({i32, i32} noundef %x) {
+  ret void
+}
+
+define void @main() {
+  call void @callee({i32, i32} {i32 0, i32 poison})
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: The value violates noundef attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_noundef_ub.ll b/llvm/test/tools/llubi/attribute_noundef_ub.ll
new file mode 100644
index 0000000000000..88c4f8f7d0ef6
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_noundef_ub.ll
@@ -0,0 +1,14 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @callee(i32 noundef %x) {
+  ret void
+}
+
+define void @main() {
+  call void @callee(i32 poison)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: The value violates noundef attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/metadata.ll b/llvm/test/tools/llubi/metadata.ll
new file mode 100644
index 0000000000000..0e01a4df384be
--- /dev/null
+++ b/llvm/test/tools/llubi/metadata.ll
@@ -0,0 +1,108 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define i32 @callee() {
+  ret i32 10
+}
+
+define float @callee_fp() {
+  ret float 0.0
+}
+
+define ptr @callee_ptr(ptr %x) {
+  ret ptr %x
+}
+
+define void @main() {
+  %alloc = alloca i32
+  store i32 1, ptr %alloc
+  %range_load_valid = load i32, ptr %alloc, !noundef !{}, !range !{i32 0, i32 10}
+  %range_load_invalid = load i32, ptr %alloc, !range !{i32 2, i32 10}
+  store float 0.0, ptr %alloc
+  %nofpclass_load_valid = load float, ptr %alloc, !noundef !{}, !nofpclass !{i32 3}
+  %nofpclass_load_invalid = load float, ptr %alloc, !nofpclass !{i32 99}
+
+  ; TODO: Test dereferenceable[_or_null] after provenance support is ready
+
+  %alloc_ptr = alloca ptr
+  store ptr %alloc_ptr, ptr %alloc_ptr
+  %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, !nonnull !{}, !align !{i32 8}, !noundef !{}
+  store ptr null, ptr %alloc_ptr
+  %align_load_valid = load ptr, ptr %alloc_ptr, !align !{i32 8}, !noundef !{}
+  %nonnull_load_invalid = load ptr, ptr %alloc_ptr, !nonnull !{}
+
+  %range_call_valid = call i32 @callee(), !noundef !{}, !range !{i32 0, i32 11}
+  %range_call_invalid = call i32 @callee(), !range !{i32 0, i32 10}
+  %nofpclass_call_valid = call float @callee_fp(), !noundef !{}, !nofpclass !{i32 3}
+  %nofpclass_call_invalid = call float @callee_fp(), !nofpclass !{i32 99}
+  %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !{}, !align !{i32 8}, !noundef !{}
+  %align_call_invalid = call ptr @callee_ptr(ptr null), !align !{i32 8}, !noundef !{}
+  %nonnull_call_invalid = call ptr @callee_ptr(ptr null), !nonnull !{}
+
+  %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !{i32 8}
+  %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !{i32 8}
+  %dereferenceable_or_null_call_valid2 = call ptr @callee_ptr(ptr null), !dereferenceable_or_null !{i32 8}
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   store i32 1, ptr %alloc, align 4
+; CHECK-NEXT:   %range_load_valid = load i32, ptr %alloc, align 4, !range !0, !noundef !1 => i32 1
+; CHECK-NEXT:   %range_load_invalid = load i32, ptr %alloc, align 4, !range !2 => poison
+; CHECK-NEXT:   store float 0.000000e+00, ptr %alloc, align 4
+; CHECK-NEXT:   %nofpclass_load_valid = load float, ptr %alloc, align 4, !noundef !1, !nofpclass !3 => float 0.000000e+00
+; CHECK-NEXT:   %nofpclass_load_invalid = load float, ptr %alloc, align 4, !nofpclass !4 => poison
+; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !align !5, !noundef !1 => ptr 0x10 [dangling]
+; CHECK-NEXT:   store ptr null, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %align_load_valid = load ptr, ptr %alloc_ptr, align 8, !align !5, !noundef !1 => ptr 0x0 [dangling]
+; CHECK-NEXT:   %nonnull_load_invalid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1 => poison
+; CHECK-NEXT: Entering function: callee
+; CHECK-NEXT:   ret i32 10
+; CHECK-NEXT: Exiting function: callee
+; CHECK-NEXT:   %range_call_valid = call i32 @callee(), !range !6, !noundef !1 => i32 10
+; CHECK-NEXT: Entering function: callee
+; CHECK-NEXT:   ret i32 10
+; CHECK-NEXT: Exiting function: callee
+; CHECK-NEXT:   %range_call_invalid = call i32 @callee(), !range !0 => poison
+; CHECK-NEXT: Entering function: callee_fp
+; CHECK-NEXT:   ret float 0.000000e+00
+; CHECK-NEXT: Exiting function: callee_fp
+; CHECK-NEXT:   %nofpclass_call_valid = call float @callee_fp(), !noundef !1, !nofpclass !3 => float 0.000000e+00
+; CHECK-NEXT: Entering function: callee_fp
+; CHECK-NEXT:   ret float 0.000000e+00
+; CHECK-NEXT: Exiting function: callee_fp
+; CHECK-NEXT:   %nofpclass_call_invalid = call float @callee_fp(), !nofpclass !4 => poison
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT:   ptr %x = ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT:   %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !1, !align !5, !noundef !1 => ptr 0x10 [alloc_ptr]
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT:   ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT:   ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT:   %align_call_invalid = call ptr @callee_ptr(ptr null), !align !5, !noundef !1 => ptr 0x0 [dangling]
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT:   ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT:   ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT:   %nonnull_call_invalid = call ptr @callee_ptr(ptr null), !nonnull !1 => poison
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT:   ptr %x = ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT:   %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !5 => ptr 0x10 [alloc_ptr]
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT:   ptr %x = ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT:   %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !5 => ptr 0x10 [alloc_ptr]
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT:   ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT:   ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT:   %dereferenceable_or_null_call_valid2 = call ptr @callee_ptr(ptr null), !dereferenceable_or_null !5 => ptr 0x0 [dangling]
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/metadata_noundef_ub.ll b/llvm/test/tools/llubi/metadata_noundef_ub.ll
new file mode 100644
index 0000000000000..ec76b0e3efe95
--- /dev/null
+++ b/llvm/test/tools/llubi/metadata_noundef_ub.ll
@@ -0,0 +1,14 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %alloc = alloca i32
+  store i32 -1, ptr %alloc
+  %res = load i32, ptr %alloc, !noundef !{}, !range !{i32 0, i32 10}
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   store i32 -1, ptr %alloc, align 4
+; CHECK-NEXT: Immediate UB detected: The value violates !noundef metadata.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 2ae5c4d9b3a36..074e37c65817b 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -460,6 +460,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       return 0;
     }
     const APInt &C = V.asInteger();
+    if (C.isNegative()) {
+      reportImmediateUB("The integer value is negative.");
+      return 0;
+    }
     if (!C.isIntN(64)) {
       reportImmediateUB("The integer value is too large.");
       return 0;
@@ -578,12 +582,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       switch (Args[0].asBoolean()) {
       case BooleanKind::True:
         for (unsigned Idx = 0; Idx < CB.getNumOperandBundles(); Idx++) {
-          CallBase::BundleOpInfo BOI =
-              CB.getBundleOpInfoForOperand(CB.arg_size() + Idx);
+          OperandBundleUse OBU = CB.getOperandBundleAt(Idx);
           auto GetBundleArg = [&](uint32_t Offset) -> Value * {
-            return (CB.op_begin() + BOI.Begin + Offset)->get();
+            return OBU.Inputs[Offset];
           };
-          if (BOI.End == BOI.Begin)
+          if (OBU.Inputs.empty())
             continue;
           Value *WasOnVal = GetBundleArg(0);
           // Bail out on unrecognized operand bundles.
@@ -596,14 +599,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           }
           const Pointer &WasOnPtr = WasOn.asPointer();
           Attribute::AttrKind Kind =
-              Attribute::getAttrKindFromName(BOI.Tag->getKey());
+              Attribute::getAttrKindFromName(OBU.getTagName());
           switch (Kind) {
           case Attribute::Alignment: {
             // Alignment assumptions should have 2 or 3 arguments.
             // If there are two integer arguments, use the largest power of 2
             // that divides them as the alignment.
             uint64_t Alignment = getUInt64NonPoison(getValue(GetBundleArg(1)));
-            if (BOI.End - BOI.Begin == 3)
+            if (OBU.Inputs.size() == 3)
               Alignment = MinAlign(
                   Alignment, getUInt64NonPoison(getValue(GetBundleArg(2))));
             if (!isPowerOf2_64(Alignment)) {
@@ -1662,6 +1665,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     auto RetVal =
         load(getValue(LI.getPointerOperand()), LI.getAlign(), LI.getType());
     // TODO: track volatile loads
+    // TODO: Check undef bits when !noundef is set.
     handleMetadata(LI.getType(), RetVal, LI);
     setResult(LI, std::move(RetVal));
   }

>From 2c0039b17da474d31e025bbe0561943121cccf75 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 3 May 2026 16:25:02 +0800
Subject: [PATCH 6/7] [llubi] Update tests.

---
 llvm/test/tools/llubi/assume_invalid_align.ll |  4 +-
 llvm/test/tools/llubi/assume_misalign.ll      |  4 +-
 .../tools/llubi/assume_nondereferenceable.ll  |  4 +-
 llvm/test/tools/llubi/assume_null.ll          |  4 +-
 llvm/test/tools/llubi/assume_poison.ll        |  2 +-
 llvm/test/tools/llubi/assume_poison_align.ll  |  2 +
 ...e_dereferenceable_ub_nullary_provenance.ll |  4 +-
 .../attribute_dereferenceable_ub_oob1.ll      |  4 +-
 .../attribute_dereferenceable_ub_oob2.ll      |  4 +-
 .../attribute_dereferenceable_ub_oob3.ll      |  4 +-
 .../attribute_dereferenceable_ub_poison.ll    |  4 +-
 .../tools/llubi/attribute_noundef_agg_ub.ll   |  4 +-
 llvm/test/tools/llubi/attribute_noundef_ub.ll |  4 +-
 llvm/test/tools/llubi/attributes.ll           |  4 +-
 llvm/test/tools/llubi/metadata_noundef_ub.ll  |  4 +-
 llvm/tools/llubi/lib/Interpreter.cpp          | 70 +++++++++++--------
 16 files changed, 82 insertions(+), 44 deletions(-)

diff --git a/llvm/test/tools/llubi/assume_invalid_align.ll b/llvm/test/tools/llubi/assume_invalid_align.ll
index 9e75bca600e82..1f162a679f756 100644
--- a/llvm/test/tools/llubi/assume_invalid_align.ll
+++ b/llvm/test/tools/llubi/assume_invalid_align.ll
@@ -8,5 +8,7 @@ define void @main() {
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
-; CHECK-NEXT: Immediate UB detected: The integer value is too large.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @llvm.assume(i1 true) [ "align"(ptr %alloc, i128 18446744073709551616) ] at @main
+; CHECK-NEXT: Immediate UB detected: The integer value 18446744073709551616 is too large.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_misalign.ll b/llvm/test/tools/llubi/assume_misalign.ll
index f25629d77ea29..5f8e80a956027 100644
--- a/llvm/test/tools/llubi/assume_misalign.ll
+++ b/llvm/test/tools/llubi/assume_misalign.ll
@@ -8,5 +8,7 @@ define void @main() {
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
-; CHECK-NEXT: Immediate UB detected: The pointer address violates alignment assumption.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @llvm.assume(i1 true) [ "align"(ptr %alloc, i32 2048) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [alloc] violates align(2048) assumption.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_nondereferenceable.ll b/llvm/test/tools/llubi/assume_nondereferenceable.ll
index ba9e0f8763e74..34a4b9c84a0a6 100644
--- a/llvm/test/tools/llubi/assume_nondereferenceable.ll
+++ b/llvm/test/tools/llubi/assume_nondereferenceable.ll
@@ -8,5 +8,7 @@ define void @main() {
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
-; CHECK-NEXT: Immediate UB detected: The pointer address violates dereferenceable{{(_or_null)?}} assumption.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @llvm.assume(i1 true) [ "dereferenceable{{(_or_null)?}}"(ptr %alloc, i32 2048) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [alloc] violates dereferenceable{{(_or_null)?}}(2048) assumption.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_null.ll b/llvm/test/tools/llubi/assume_null.ll
index ef6247d614546..ef55ce54ed6b7 100644
--- a/llvm/test/tools/llubi/assume_null.ll
+++ b/llvm/test/tools/llubi/assume_null.ll
@@ -6,5 +6,7 @@ define void @main() {
   ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: The pointer address violates nonnull assumption.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @llvm.assume(i1 true) [ "nonnull"(ptr null) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x0 [dangling] violates nonnull assumption.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_poison.ll b/llvm/test/tools/llubi/assume_poison.ll
index b80e00482896c..7ab2c586fedea 100644
--- a/llvm/test/tools/llubi/assume_poison.ll
+++ b/llvm/test/tools/llubi/assume_poison.ll
@@ -8,5 +8,5 @@ define void @main() {
 ; CHECK: Entering function: main
 ; CHECK-NEXT: Stacktrace:
 ; CHECK-NEXT: #0   call void @llvm.assume(i1 poison) at @main
-; CHECK-NEXT: Immediate UB detected: The value violates noundef attribute.
+; CHECK-NEXT: Immediate UB detected: The value poison violates noundef attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_poison_align.ll b/llvm/test/tools/llubi/assume_poison_align.ll
index 6928016fa2ace..9e67964846373 100644
--- a/llvm/test/tools/llubi/assume_poison_align.ll
+++ b/llvm/test/tools/llubi/assume_poison_align.ll
@@ -6,5 +6,7 @@ define void @main() {
   ret void
 }
 ; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @llvm.assume(i1 true) [ "align"(ptr poison, i32 4) ] at @main
 ; CHECK-NEXT: Immediate UB detected: Assume on poison pointer.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
index 91d74b814e9f3..b4d585c2a71c3 100644
--- a/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
@@ -14,5 +14,7 @@ define void @main() {
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %ptr_storage = alloca i64, align 8 => ptr 0x8 [ptr_storage]
 ; CHECK-NEXT:   %p = load ptr, ptr %ptr_storage, align 8 => ptr 0xE82FEEACEEB98B3E [dangling]
-; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @callee(ptr %p) at @main
+; CHECK-NEXT: Immediate UB detected: The value ptr 0xE82FEEACEEB98B3E [dangling] violates dereferenceable{{(_or_null)?}}(4) attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
index 1f0ac195b8817..861010b1bca62 100644
--- a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
@@ -12,5 +12,7 @@ define void @main() {
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
-; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @callee(ptr %alloc) at @main
+; CHECK-NEXT: Immediate UB detected: The value ptr 0x8 [alloc] violates dereferenceable{{(_or_null)?}}(8) attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
index 5d5f0e70435c0..aabbd988f2a6c 100644
--- a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
@@ -14,5 +14,7 @@ define void @main() {
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
 ; CHECK-NEXT:   %gep = getelementptr i8, ptr %alloc, i32 -1 => ptr 0x7 [alloc + -1]
-; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @callee(ptr %gep) at @main
+; CHECK-NEXT: Immediate UB detected: The value ptr 0x7 [alloc + -1] violates dereferenceable{{(_or_null)?}}(2) attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
index 90e0094c3989e..c5402ff3864a5 100644
--- a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
@@ -14,5 +14,7 @@ define void @main() {
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
 ; CHECK-NEXT:   %gep = getelementptr i8, ptr %alloc, i32 2 => ptr 0xA [alloc + 2]
-; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @callee(ptr %gep) at @main
+; CHECK-NEXT: Immediate UB detected: The value ptr 0xA [alloc + 2] violates dereferenceable{{(_or_null)?}}(3) attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
index a3abc66405f3f..94771016c2bd9 100644
--- a/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
@@ -10,5 +10,7 @@ define void @main() {
   ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: The value violates dereferenceable{{(_or_null)?}} attribute.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @callee(ptr poison) at @main
+; CHECK-NEXT: Immediate UB detected: The value poison violates dereferenceable{{(_or_null)?}}(4) attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll b/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll
index f112671405f39..d3f722b4d12b4 100644
--- a/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll
+++ b/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll
@@ -10,5 +10,7 @@ define void @main() {
   ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: The value violates noundef attribute.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @callee({ i32, i32 } { i32 0, i32 poison }) at @main
+; CHECK-NEXT: Immediate UB detected: The value { i32 0, poison } violates noundef attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_noundef_ub.ll b/llvm/test/tools/llubi/attribute_noundef_ub.ll
index 88c4f8f7d0ef6..9eb63f8205cfc 100644
--- a/llvm/test/tools/llubi/attribute_noundef_ub.ll
+++ b/llvm/test/tools/llubi/attribute_noundef_ub.ll
@@ -10,5 +10,7 @@ define void @main() {
   ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: The value violates noundef attribute.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @callee(i32 poison) at @main
+; CHECK-NEXT: Immediate UB detected: The value poison violates noundef attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
index af593c51d7c96..1a4acfb76a0c2 100644
--- a/llvm/test/tools/llubi/attributes.ll
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -153,9 +153,9 @@ define void @main() {
 ; CHECK-NEXT: Exiting function: add_with_range_vec
 ; CHECK-NEXT:   %range_vec = call <4 x i32> @add_with_range_vec(<4 x i32> <i32 0, i32 poison, i32 3, i32 1>) => { i32 1, poison, poison, poison }
 ; CHECK-NEXT:   %range_intrinsic_valid = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 15) => i32 4
-; CHECK-NEXT:   %range_intrinsic_invalid_input = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 1500) => i32 7
+; CHECK-NEXT:   %range_intrinsic_invalid_input = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 1500) => poison
 ; CHECK-NEXT:   %range_intrinsic_invalid_output = call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 0) => poison
-; CHECK-NEXT:   %range_intrinsic_vec = call range(i32 1, 32) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> range(i32 1, 255) <i32 15, i32 1500, i32 0, i32 poison>) => { i32 4, i32 7, poison, poison }
+; CHECK-NEXT:   %range_intrinsic_vec = call range(i32 1, 32) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> range(i32 1, 255) <i32 15, i32 1500, i32 0, i32 poison>) => { i32 4, poison, poison, poison }
 ; CHECK-NEXT: Entering function: identity_nofpclass
 ; CHECK-NEXT:   half %x = half 1.000000e+00
 ; CHECK-NEXT:   ret half %x
diff --git a/llvm/test/tools/llubi/metadata_noundef_ub.ll b/llvm/test/tools/llubi/metadata_noundef_ub.ll
index ec76b0e3efe95..2d9ef4da816df 100644
--- a/llvm/test/tools/llubi/metadata_noundef_ub.ll
+++ b/llvm/test/tools/llubi/metadata_noundef_ub.ll
@@ -10,5 +10,7 @@ define void @main() {
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
 ; CHECK-NEXT:   store i32 -1, ptr %alloc, align 4
-; CHECK-NEXT: Immediate UB detected: The value violates !noundef metadata.
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   %res = load i32, ptr %alloc, align 4, !range !0, !noundef !1 at @main
+; CHECK-NEXT: Immediate UB detected: The value poison violates !noundef metadata.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 074e37c65817b..aeb2557643f13 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -456,16 +456,16 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
   uint64_t getUInt64NonPoison(const AnyValue &V) {
     if (V.isPoison()) {
-      reportImmediateUB("Unexpected poison integer value.");
+      reportImmediateUB() << "Unexpected poison integer value.";
       return 0;
     }
     const APInt &C = V.asInteger();
     if (C.isNegative()) {
-      reportImmediateUB("The integer value is negative.");
+      reportImmediateUB() << "Unexpected negative value " << C << '.';
       return 0;
     }
     if (!C.isIntN(64)) {
-      reportImmediateUB("The integer value is too large.");
+      reportImmediateUB() << "The integer value " << C << " is too large.";
       return 0;
     }
 
@@ -594,7 +594,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
             continue;
           const AnyValue &WasOn = getValue(WasOnVal);
           if (WasOn.isPoison()) {
-            reportImmediateUB("Assume on poison pointer.");
+            reportImmediateUB() << "Assume on poison pointer.";
             break;
           }
           const Pointer &WasOnPtr = WasOn.asPointer();
@@ -611,19 +611,22 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                   Alignment, getUInt64NonPoison(getValue(GetBundleArg(2))));
             if (!isPowerOf2_64(Alignment)) {
               if (!WasOn.asPointer().address().isZero())
-                reportImmediateUB("Assume on nonnull pointer with a "
-                                  "non-power-of-two alignment.");
+                reportImmediateUB() << "Assume on nonnull pointer " << WasOn
+                                    << " with a "
+                                       "non-power-of-two alignment "
+                                    << Alignment << '.';
               break;
             }
             if (WasOnPtr.address().countr_zero() < Log2_64(Alignment))
-              reportImmediateUB(
-                  "The pointer address violates alignment assumption.");
+              reportImmediateUB()
+                  << "The pointer " << WasOn << " violates align(" << Alignment
+                  << ") assumption.";
             break;
           }
           case Attribute::NonNull:
             if (WasOnPtr.address().isZero())
-              reportImmediateUB(
-                  "The pointer address violates nonnull assumption.");
+              reportImmediateUB()
+                  << "The pointer " << WasOn << " violates nonnull assumption.";
             break;
           case Attribute::Dereferenceable:
           case Attribute::DereferenceableOrNull: {
@@ -632,11 +635,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
             if (applyDereferenceableBytesAttr(
                     WasOn, DereferenceableBytes,
                     Kind == Attribute::DereferenceableOrNull))
-              reportImmediateUB(Kind == Attribute::DereferenceableOrNull
-                                    ? "The pointer address violates "
-                                      "dereferenceable_or_null assumption."
-                                    : "The pointer address violates "
-                                      "dereferenceable assumption.");
+              reportImmediateUB() << "The pointer " << WasOn << " violates "
+                                  << (Kind == Attribute::DereferenceableOrNull
+                                          ? "dereferenceable_or_null("
+                                          : "dereferenceable(")
+                                  << DereferenceableBytes << ") assumption.";
             break;
           }
           default:
@@ -1117,7 +1120,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     if ((AttrsAtCallSite.hasAttribute(Attribute::NoUndef) ||
          AttrsAtCallee.hasAttribute(Attribute::NoUndef)) &&
         applyNoUndefAttr(V)) {
-      reportImmediateUB("The value violates noundef attribute.");
+      reportImmediateUB() << "The value " << V
+                          << " violates noundef attribute.";
       return;
     }
     if (Ty->isPointerTy()) {
@@ -1126,14 +1130,18 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                        AttrsAtCallee.getDereferenceableBytes())) {
         if (applyDereferenceableBytesAttr(V, DereferenceableBytes,
                                           /*OrNull=*/false))
-          reportImmediateUB("The value violates dereferenceable attribute.");
+          reportImmediateUB()
+              << "The value " << V << " violates dereferenceable("
+              << DereferenceableBytes << ") attribute.";
       } else if (uint64_t DereferenceableOrNullBytes =
                      std::max(AttrsAtCallSite.getDereferenceableOrNullBytes(),
                               AttrsAtCallee.getDereferenceableOrNullBytes())) {
         if (applyDereferenceableBytesAttr(V, DereferenceableOrNullBytes,
                                           /*OrNull=*/true))
-          reportImmediateUB("The value violates "
-                            "dereferenceable_or_null attribute.");
+          reportImmediateUB() << "The value " << V
+                              << " violates "
+                                 "dereferenceable_or_null("
+                              << DereferenceableOrNullBytes << ") attribute.";
       }
     }
   }
@@ -1162,23 +1170,27 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         applyAlignAttr(V, Align(ExtractFirstIntOperand(Alignment)));
     }
     if (I.hasMetadata(LLVMContext::MD_noundef) && applyNoUndefAttr(V)) {
-      reportImmediateUB("The value violates !noundef metadata.");
+      reportImmediateUB() << "The value " << V
+                          << " violates !noundef metadata.";
       return;
     }
     if (Ty->isPointerTy()) {
       if (const MDNode *DereferenceableBytes =
               I.getMetadata(LLVMContext::MD_dereferenceable)) {
-        if (applyDereferenceableBytesAttr(
-                V, ExtractFirstIntOperand(DereferenceableBytes),
-                /*OrNull=*/false))
-          reportImmediateUB("The value violates !dereferenceable metadata.");
+        uint64_t Bytes = ExtractFirstIntOperand(DereferenceableBytes);
+        if (applyDereferenceableBytesAttr(V, Bytes,
+                                          /*OrNull=*/false))
+          reportImmediateUB()
+              << "The value " << V << " violates !dereferenceable !{i64 "
+              << Bytes << "} metadata.";
       } else if (const MDNode *DereferenceableOrNullBytes =
                      I.getMetadata(LLVMContext::MD_dereferenceable_or_null)) {
-        if (applyDereferenceableBytesAttr(
-                V, ExtractFirstIntOperand(DereferenceableOrNullBytes),
-                /*OrNull=*/true))
-          reportImmediateUB("The value violates "
-                            "!dereferenceable_or_null metadata.");
+        uint64_t Bytes = ExtractFirstIntOperand(DereferenceableOrNullBytes);
+        if (applyDereferenceableBytesAttr(V, Bytes,
+                                          /*OrNull=*/true))
+          reportImmediateUB()
+              << "The value " << V << " violates !dereferenceable_or_null!{i64 "
+              << Bytes << "} metadata.";
       }
     }
   }

>From 95bc8c654e2191d6e331f3380b2ac7da0e077746 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 3 May 2026 22:08:18 +0800
Subject: [PATCH 7/7] [llubi] Address review comments.

---
 llvm/test/tools/llubi/assume_invalid_align.ll |   2 +-
 llvm/test/tools/llubi/assume_null_all_ones.ll |  20 ++++
 .../tools/llubi/assume_operand_bundles.ll     |   4 +
 llvm/test/tools/llubi/metadata.ll             |  38 +++---
 llvm/tools/llubi/lib/Context.cpp              |   3 +-
 llvm/tools/llubi/lib/Interpreter.cpp          | 111 ++++++++++--------
 llvm/tools/llubi/lib/Library.cpp              |   2 +-
 llvm/tools/llubi/lib/Value.cpp                |  11 +-
 llvm/tools/llubi/lib/Value.h                  |   4 +-
 9 files changed, 123 insertions(+), 72 deletions(-)
 create mode 100644 llvm/test/tools/llubi/assume_null_all_ones.ll

diff --git a/llvm/test/tools/llubi/assume_invalid_align.ll b/llvm/test/tools/llubi/assume_invalid_align.ll
index 1f162a679f756..a29e2c819b5fd 100644
--- a/llvm/test/tools/llubi/assume_invalid_align.ll
+++ b/llvm/test/tools/llubi/assume_invalid_align.ll
@@ -10,5 +10,5 @@ define void @main() {
 ; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
 ; CHECK-NEXT: Stacktrace:
 ; CHECK-NEXT: #0   call void @llvm.assume(i1 true) [ "align"(ptr %alloc, i128 18446744073709551616) ] at @main
-; CHECK-NEXT: Immediate UB detected: The integer value 18446744073709551616 is too large.
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [alloc] violates align(18446744073709551616) assumption.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_null_all_ones.ll b/llvm/test/tools/llubi/assume_null_all_ones.ll
new file mode 100644
index 0000000000000..94dcebb600056
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_null_all_ones.ll
@@ -0,0 +1,20 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+target datalayout = "po1:64:64"
+
+define void @main() {
+  %storage = alloca ptr
+  store i64 -1, ptr %storage
+  %res = load ptr addrspace(1), ptr %storage
+  call void @llvm.assume(i1 true) ["nonnull"(ptr addrspace(1) %res)]
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %storage = alloca ptr, align 8 => ptr 0x8 [storage]
+; CHECK-NEXT:   store i64 -1, ptr %storage, align 4
+; CHECK-NEXT:   %res = load ptr addrspace(1), ptr %storage, align 8 => ptr 0xFFFFFFFFFFFFFFFF [dangling]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @llvm.assume(i1 true) [ "nonnull"(ptr addrspace(1) %res) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0xFFFFFFFFFFFFFFFF [dangling] violates nonnull assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_operand_bundles.ll b/llvm/test/tools/llubi/assume_operand_bundles.ll
index 17742a821cbf9..7e6f057115648 100644
--- a/llvm/test/tools/llubi/assume_operand_bundles.ll
+++ b/llvm/test/tools/llubi/assume_operand_bundles.ll
@@ -1,6 +1,8 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
 ; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
 
+target datalayout = "po1:64:64"
+
 define void @assume_align_dynamic(ptr %p, i32 %align) {
   call void @llvm.assume(i1 true) ["align"(ptr %p, i32 4)]
   call void @llvm.assume(i1 true) ["align"(ptr %p, i32 %align)]
@@ -18,6 +20,7 @@ define void @main() {
   call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 4)]
   call void @llvm.assume(i1 true) ["dereferenceable_or_null"(ptr %alloc, i32 4)]
   call void @llvm.assume(i1 true) ["dereferenceable_or_null"(ptr null, i32 4)]
+  call void @llvm.assume(i1 true) ["dereferenceable_or_null"(ptr addrspace(1) null, i32 4)]
   call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr null, i32 4)]
   ret void
 }
@@ -39,6 +42,7 @@ define void @main() {
 ; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %alloc, i32 4) ]
 ; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable_or_null"(ptr %alloc, i32 4) ]
 ; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable_or_null"(ptr null, i32 4) ]
+; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable_or_null"(ptr addrspace(1) null, i32 4) ]
 ; CHECK-NEXT:   call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr null, i32 4) ]
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/metadata.ll b/llvm/test/tools/llubi/metadata.ll
index 0e01a4df384be..7b4309e139e79 100644
--- a/llvm/test/tools/llubi/metadata.ll
+++ b/llvm/test/tools/llubi/metadata.ll
@@ -18,6 +18,9 @@ define void @main() {
   store i32 1, ptr %alloc
   %range_load_valid = load i32, ptr %alloc, !noundef !{}, !range !{i32 0, i32 10}
   %range_load_invalid = load i32, ptr %alloc, !range !{i32 2, i32 10}
+  %alloc_vec = alloca <8 x i32>
+  store <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>, ptr %alloc_vec
+  %range_list_load_vec = load <8 x i32>, ptr %alloc_vec, !range !{i32 3, i32 4, i32 5, i32 6, i32 7, i32 2}
   store float 0.0, ptr %alloc
   %nofpclass_load_valid = load float, ptr %alloc, !noundef !{}, !nofpclass !{i32 3}
   %nofpclass_load_invalid = load float, ptr %alloc, !nofpclass !{i32 99}
@@ -49,19 +52,22 @@ define void @main() {
 ; CHECK-NEXT:   store i32 1, ptr %alloc, align 4
 ; CHECK-NEXT:   %range_load_valid = load i32, ptr %alloc, align 4, !range !0, !noundef !1 => i32 1
 ; CHECK-NEXT:   %range_load_invalid = load i32, ptr %alloc, align 4, !range !2 => poison
+; CHECK-NEXT:   %alloc_vec = alloca <8 x i32>, align 32 => ptr 0x20 [alloc_vec]
+; CHECK-NEXT:   store <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>, ptr %alloc_vec, align 32
+; CHECK-NEXT:   %range_list_load_vec = load <8 x i32>, ptr %alloc_vec, align 32, !range !3 => { i32 0, i32 1, poison, i32 3, poison, i32 5, poison, i32 7 }
 ; CHECK-NEXT:   store float 0.000000e+00, ptr %alloc, align 4
-; CHECK-NEXT:   %nofpclass_load_valid = load float, ptr %alloc, align 4, !noundef !1, !nofpclass !3 => float 0.000000e+00
-; CHECK-NEXT:   %nofpclass_load_invalid = load float, ptr %alloc, align 4, !nofpclass !4 => poison
-; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   %nofpclass_load_valid = load float, ptr %alloc, align 4, !noundef !1, !nofpclass !4 => float 0.000000e+00
+; CHECK-NEXT:   %nofpclass_load_invalid = load float, ptr %alloc, align 4, !nofpclass !5 => poison
+; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x40 [alloc_ptr]
 ; CHECK-NEXT:   store ptr %alloc_ptr, ptr %alloc_ptr, align 8
-; CHECK-NEXT:   %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !align !5, !noundef !1 => ptr 0x10 [dangling]
+; CHECK-NEXT:   %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !align !6, !noundef !1 => ptr 0x40 [dangling]
 ; CHECK-NEXT:   store ptr null, ptr %alloc_ptr, align 8
-; CHECK-NEXT:   %align_load_valid = load ptr, ptr %alloc_ptr, align 8, !align !5, !noundef !1 => ptr 0x0 [dangling]
+; CHECK-NEXT:   %align_load_valid = load ptr, ptr %alloc_ptr, align 8, !align !6, !noundef !1 => ptr 0x0 [dangling]
 ; CHECK-NEXT:   %nonnull_load_invalid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1 => poison
 ; CHECK-NEXT: Entering function: callee
 ; CHECK-NEXT:   ret i32 10
 ; CHECK-NEXT: Exiting function: callee
-; CHECK-NEXT:   %range_call_valid = call i32 @callee(), !range !6, !noundef !1 => i32 10
+; CHECK-NEXT:   %range_call_valid = call i32 @callee(), !range !7, !noundef !1 => i32 10
 ; CHECK-NEXT: Entering function: callee
 ; CHECK-NEXT:   ret i32 10
 ; CHECK-NEXT: Exiting function: callee
@@ -69,40 +75,40 @@ define void @main() {
 ; CHECK-NEXT: Entering function: callee_fp
 ; CHECK-NEXT:   ret float 0.000000e+00
 ; CHECK-NEXT: Exiting function: callee_fp
-; CHECK-NEXT:   %nofpclass_call_valid = call float @callee_fp(), !noundef !1, !nofpclass !3 => float 0.000000e+00
+; CHECK-NEXT:   %nofpclass_call_valid = call float @callee_fp(), !noundef !1, !nofpclass !4 => float 0.000000e+00
 ; CHECK-NEXT: Entering function: callee_fp
 ; CHECK-NEXT:   ret float 0.000000e+00
 ; CHECK-NEXT: Exiting function: callee_fp
-; CHECK-NEXT:   %nofpclass_call_invalid = call float @callee_fp(), !nofpclass !4 => poison
+; CHECK-NEXT:   %nofpclass_call_invalid = call float @callee_fp(), !nofpclass !5 => poison
 ; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT:   ptr %x = ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   ptr %x = ptr 0x40 [alloc_ptr]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT:   %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !1, !align !5, !noundef !1 => ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !1, !align !6, !noundef !1 => ptr 0x40 [alloc_ptr]
 ; CHECK-NEXT: Entering function: callee_ptr
 ; CHECK-NEXT:   ptr %x = ptr 0x0 [dangling]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT:   %align_call_invalid = call ptr @callee_ptr(ptr null), !align !5, !noundef !1 => ptr 0x0 [dangling]
+; CHECK-NEXT:   %align_call_invalid = call ptr @callee_ptr(ptr null), !align !6, !noundef !1 => ptr 0x0 [dangling]
 ; CHECK-NEXT: Entering function: callee_ptr
 ; CHECK-NEXT:   ptr %x = ptr 0x0 [dangling]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
 ; CHECK-NEXT:   %nonnull_call_invalid = call ptr @callee_ptr(ptr null), !nonnull !1 => poison
 ; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT:   ptr %x = ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   ptr %x = ptr 0x40 [alloc_ptr]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT:   %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !5 => ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !6 => ptr 0x40 [alloc_ptr]
 ; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT:   ptr %x = ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   ptr %x = ptr 0x40 [alloc_ptr]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT:   %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !5 => ptr 0x10 [alloc_ptr]
+; CHECK-NEXT:   %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !6 => ptr 0x40 [alloc_ptr]
 ; CHECK-NEXT: Entering function: callee_ptr
 ; CHECK-NEXT:   ptr %x = ptr 0x0 [dangling]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT:   %dereferenceable_or_null_call_valid2 = call ptr @callee_ptr(ptr null), !dereferenceable_or_null !5 => ptr 0x0 [dangling]
+; CHECK-NEXT:   %dereferenceable_or_null_call_valid2 = call ptr @callee_ptr(ptr null), !dereferenceable_or_null !6 => ptr 0x0 [dangling]
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 2b195ac38ecfc..e591e9acc181e 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -61,8 +61,7 @@ AnyValue Context::getConstantValueImpl(Constant *C) {
     return AnyValue::getNullValue(*this, C->getType());
 
   if (isa<ConstantPointerNull>(C))
-    return Pointer::null(
-        DL.getPointerSizeInBits(C->getType()->getPointerAddressSpace()));
+    return Pointer::null(C->getType()->getPointerAddressSpace(), DL);
 
   if (auto *CI = dyn_cast<ConstantInt>(C)) {
     if (auto *VecTy = dyn_cast<VectorType>(CI->getType()))
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index aeb2557643f13..1a5e5bfe559a3 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -94,8 +94,8 @@ static void applyNoFPClassAttr(AnyValue &V, FPClassTest NoFPClass) {
   });
 }
 
-static void applyNonNullAttr(AnyValue &V) {
-  if (V.isPointer() && V.asPointer().address().isZero())
+static void applyNonNullAttr(AnyValue &V, unsigned AS, const DataLayout &DL) {
+  if (V.isPointer() && V.asPointer().isNullPtr(AS, DL))
     V = AnyValue::poison();
 }
 
@@ -107,7 +107,7 @@ static void applyAlignAttr(AnyValue &V, Align Alignment) {
   });
 }
 
-static bool applyNoUndefAttr(AnyValue &V) {
+static bool violatesNoUndefAttr(AnyValue &V) {
   bool ContainsPoison = false;
   forEachScalarValue(
       V, [&](AnyValue &Scalar) { ContainsPoison |= Scalar.isPoison(); });
@@ -115,14 +115,14 @@ static bool applyNoUndefAttr(AnyValue &V) {
 }
 
 /// Assumes V is either a poison or a pointer.
-static bool applyDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
-                                          bool OrNull) {
+static bool violatesDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
+                                             bool OrNull, unsigned AS,
+                                             const DataLayout &DL) {
   if (V.isPoison())
     return true;
 
   auto &Ptr = V.asPointer();
-  const APInt &PtrAddr = Ptr.address();
-  if (PtrAddr.isZero()) {
+  if (Ptr.isNullPtr(AS, DL)) {
     if (OrNull)
       return false;
     return true;
@@ -132,7 +132,9 @@ static bool applyDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
     return true;
 
   // TODO: check read_provenance
+  // TODO: check nofree for attributes/metadata.
 
+  const APInt &PtrAddr = Ptr.address();
   return Bytes > MO->getSize() || PtrAddr.ult(MO->getAddress()) ||
          PtrAddr.ugt(MO->getAddress() + MO->getSize() - Bytes);
 }
@@ -454,22 +456,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return Boolean == BooleanKind::True;
   }
 
-  uint64_t getUInt64NonPoison(const AnyValue &V) {
+  APInt getIntNonPoison(const AnyValue &V) {
     if (V.isPoison()) {
       reportImmediateUB() << "Unexpected poison integer value.";
-      return 0;
-    }
-    const APInt &C = V.asInteger();
-    if (C.isNegative()) {
-      reportImmediateUB() << "Unexpected negative value " << C << '.';
-      return 0;
+      return APInt::getZero(64);
     }
-    if (!C.isIntN(64)) {
-      reportImmediateUB() << "The integer value " << C << " is too large.";
-      return 0;
-    }
-
-    return C.getZExtValue();
+    return V.asInteger();
   }
 
 public:
@@ -592,6 +584,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           // Bail out on unrecognized operand bundles.
           if (!WasOnVal->getType()->isPointerTy())
             continue;
+          unsigned AS = WasOnVal->getType()->getPointerAddressSpace();
           const AnyValue &WasOn = getValue(WasOnVal);
           if (WasOn.isPoison()) {
             reportImmediateUB() << "Assume on poison pointer.";
@@ -605,36 +598,43 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
             // Alignment assumptions should have 2 or 3 arguments.
             // If there are two integer arguments, use the largest power of 2
             // that divides them as the alignment.
-            uint64_t Alignment = getUInt64NonPoison(getValue(GetBundleArg(1)));
-            if (OBU.Inputs.size() == 3)
-              Alignment = MinAlign(
-                  Alignment, getUInt64NonPoison(getValue(GetBundleArg(2))));
-            if (!isPowerOf2_64(Alignment)) {
-              if (!WasOn.asPointer().address().isZero())
+            APInt Alignment = getIntNonPoison(getValue(GetBundleArg(1)));
+            if (OBU.Inputs.size() == 3) {
+              APInt Offset = getIntNonPoison(getValue(GetBundleArg(2)));
+              if (!Alignment.isZero() || !Offset.isZero())
+                Alignment = APInt::getOneBitSet(
+                    std::max(Alignment.getBitWidth(), Offset.getBitWidth()),
+                    std::min(Alignment.countr_zero(), Offset.countr_zero()));
+            }
+            if (!Alignment.isPowerOf2()) {
+              if (!WasOnPtr.isNullPtr(AS, DL))
                 reportImmediateUB() << "Assume on nonnull pointer " << WasOn
                                     << " with a "
                                        "non-power-of-two alignment "
                                     << Alignment << '.';
               break;
             }
-            if (WasOnPtr.address().countr_zero() < Log2_64(Alignment))
+            if (WasOnPtr.address().countr_zero() < Alignment.logBase2())
               reportImmediateUB()
                   << "The pointer " << WasOn << " violates align(" << Alignment
                   << ") assumption.";
             break;
           }
           case Attribute::NonNull:
-            if (WasOnPtr.address().isZero())
+            if (WasOnPtr.isNullPtr(AS, DL))
               reportImmediateUB()
                   << "The pointer " << WasOn << " violates nonnull assumption.";
             break;
           case Attribute::Dereferenceable:
           case Attribute::DereferenceableOrNull: {
-            uint64_t DereferenceableBytes =
-                getUInt64NonPoison(getValue(GetBundleArg(1)));
-            if (applyDereferenceableBytesAttr(
-                    WasOn, DereferenceableBytes,
-                    Kind == Attribute::DereferenceableOrNull))
+            APInt DereferenceableBytes =
+                getIntNonPoison(getValue(GetBundleArg(1)));
+            // Only n > 0 implies that the pointer is dereferenceable.
+            if (!DereferenceableBytes.isStrictlyPositive())
+              break;
+            if (violatesDereferenceableBytesAttr(
+                    WasOn, DereferenceableBytes.getLimitedValue(),
+                    Kind == Attribute::DereferenceableOrNull, AS, DL))
               reportImmediateUB() << "The pointer " << WasOn << " violates "
                                   << (Kind == Attribute::DereferenceableOrNull
                                           ? "dereferenceable_or_null("
@@ -1109,7 +1109,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     if (Ty->isPointerTy()) {
       if (AttrsAtCallSite.hasAttribute(Attribute::NonNull) ||
           AttrsAtCallee.hasAttribute(Attribute::NonNull))
-        applyNonNullAttr(V);
+        applyNonNullAttr(V, Ty->getPointerAddressSpace(), DL);
     }
     if (Ty->isPtrOrPtrVectorTy()) {
       if (MaybeAlign Align = AttrsAtCallSite.getAlignment())
@@ -1119,25 +1119,26 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
     if ((AttrsAtCallSite.hasAttribute(Attribute::NoUndef) ||
          AttrsAtCallee.hasAttribute(Attribute::NoUndef)) &&
-        applyNoUndefAttr(V)) {
+        violatesNoUndefAttr(V)) {
       reportImmediateUB() << "The value " << V
                           << " violates noundef attribute.";
       return;
     }
     if (Ty->isPointerTy()) {
+      unsigned AS = Ty->getPointerAddressSpace();
       if (uint64_t DereferenceableBytes =
               std::max(AttrsAtCallSite.getDereferenceableBytes(),
                        AttrsAtCallee.getDereferenceableBytes())) {
-        if (applyDereferenceableBytesAttr(V, DereferenceableBytes,
-                                          /*OrNull=*/false))
+        if (violatesDereferenceableBytesAttr(V, DereferenceableBytes,
+                                             /*OrNull=*/false, AS, DL))
           reportImmediateUB()
               << "The value " << V << " violates dereferenceable("
               << DereferenceableBytes << ") attribute.";
       } else if (uint64_t DereferenceableOrNullBytes =
                      std::max(AttrsAtCallSite.getDereferenceableOrNullBytes(),
                               AttrsAtCallee.getDereferenceableOrNullBytes())) {
-        if (applyDereferenceableBytesAttr(V, DereferenceableOrNullBytes,
-                                          /*OrNull=*/true))
+        if (violatesDereferenceableBytesAttr(V, DereferenceableOrNullBytes,
+                                             /*OrNull=*/true, AS, DL))
           reportImmediateUB() << "The value " << V
                               << " violates "
                                  "dereferenceable_or_null("
@@ -1153,8 +1154,23 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     };
 
     if (Ty->isIntOrIntVectorTy()) {
-      if (MDNode *Ranges = I.getMetadata(LLVMContext::MD_range))
-        applyRangeAttr(V, getConstantRangeFromMetadata(*Ranges));
+      if (MDNode *Ranges = I.getMetadata(LLVMContext::MD_range)) {
+        SmallVector<ConstantRange> RangeList;
+        for (uint32_t I = 0; I < Ranges->getNumOperands(); I += 2) {
+          RangeList.emplace_back(
+              mdconst::extract<ConstantInt>(Ranges->getOperand(I))->getValue(),
+              mdconst::extract<ConstantInt>(Ranges->getOperand(I + 1))
+                  ->getValue());
+        }
+        forEachScalarValue(V, [&](AnyValue &Scalar) {
+          if (!Scalar.isInteger())
+            return;
+          for (auto &CR : RangeList)
+            if (CR.contains(Scalar.asInteger()))
+              return;
+          Scalar = AnyValue::poison();
+        });
+      }
     }
     if (AttributeFuncs::isNoFPClassCompatibleType(Ty)) {
       if (const MDNode *NoFPClass = I.getMetadata(LLVMContext::MD_nofpclass)) {
@@ -1164,30 +1180,31 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
     if (Ty->isPointerTy()) {
       if (I.hasMetadata(LLVMContext::MD_nonnull))
-        applyNonNullAttr(V);
+        applyNonNullAttr(V, Ty->getPointerAddressSpace(), DL);
       // Unlike align attributes, !align is only defined for pointer types.
       if (const MDNode *Alignment = I.getMetadata(LLVMContext::MD_align))
         applyAlignAttr(V, Align(ExtractFirstIntOperand(Alignment)));
     }
-    if (I.hasMetadata(LLVMContext::MD_noundef) && applyNoUndefAttr(V)) {
+    if (I.hasMetadata(LLVMContext::MD_noundef) && violatesNoUndefAttr(V)) {
       reportImmediateUB() << "The value " << V
                           << " violates !noundef metadata.";
       return;
     }
     if (Ty->isPointerTy()) {
+      unsigned AS = Ty->getPointerAddressSpace();
       if (const MDNode *DereferenceableBytes =
               I.getMetadata(LLVMContext::MD_dereferenceable)) {
         uint64_t Bytes = ExtractFirstIntOperand(DereferenceableBytes);
-        if (applyDereferenceableBytesAttr(V, Bytes,
-                                          /*OrNull=*/false))
+        if (violatesDereferenceableBytesAttr(V, Bytes,
+                                             /*OrNull=*/false, AS, DL))
           reportImmediateUB()
               << "The value " << V << " violates !dereferenceable !{i64 "
               << Bytes << "} metadata.";
       } else if (const MDNode *DereferenceableOrNullBytes =
                      I.getMetadata(LLVMContext::MD_dereferenceable_or_null)) {
         uint64_t Bytes = ExtractFirstIntOperand(DereferenceableOrNullBytes);
-        if (applyDereferenceableBytesAttr(V, Bytes,
-                                          /*OrNull=*/true))
+        if (violatesDereferenceableBytesAttr(V, Bytes,
+                                             /*OrNull=*/true, AS, DL))
           reportImmediateUB()
               << "The value " << V << " violates !dereferenceable_or_null!{i64 "
               << Bytes << "} metadata.";
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index a1e58e4e57f47..c68b223d2d65a 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -125,7 +125,7 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
 
   auto &Ptr = PtrVal.asPointer();
   // no-op when free is called with a null pointer.
-  if (Ptr.address().isZero())
+  if (Ptr.isNullPtr(/*AS=*/0, DL))
     return AnyValue();
 
   MemoryObject *Obj = Ptr.getMemoryObject();
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index f685e86efee20..82bf0f7b6eb22 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -31,8 +31,12 @@ void Pointer::print(raw_ostream &OS) const {
   OS << "]";
 }
 
-AnyValue Pointer::null(unsigned BitWidth) {
-  return AnyValue(Pointer(nullptr, APInt::getZero(BitWidth)));
+AnyValue Pointer::null(unsigned AS, const DataLayout &DL) {
+  return AnyValue(Pointer(nullptr, DL.getNullPtrValue(AS)));
+}
+
+bool Pointer::isNullPtr(unsigned AS, const DataLayout &DL) const {
+  return Address == DL.getNullPtrValue(AS);
 }
 
 void AnyValue::print(raw_ostream &OS) const {
@@ -250,8 +254,7 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
   if (Ty->isFloatingPointTy())
     return AnyValue(APFloat::getZero(Ty->getFltSemantics()));
   if (Ty->isPointerTy())
-    return Pointer::null(
-        Ctx.getDataLayout().getPointerSizeInBits(Ty->getPointerAddressSpace()));
+    return Pointer::null(Ty->getPointerAddressSpace(), Ctx.getDataLayout());
   if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
     uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
     return AnyValue(std::vector<AnyValue>(
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index b4686160ea8b8..dfaf5f23a15b0 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -12,6 +12,7 @@
 #include "llvm/ADT/APFloat.h"
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Type.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -102,7 +103,8 @@ class Pointer {
   Pointer getWithNewAddr(const APInt &NewAddr) const {
     return Pointer(Obj, NewAddr);
   }
-  static AnyValue null(unsigned BitWidth);
+  static AnyValue null(unsigned AS, const DataLayout &DL);
+  bool isNullPtr(unsigned AS, const DataLayout &DL) const;
   void print(raw_ostream &OS) const;
   const APInt &address() const { return Address; }
   MemoryObject *getMemoryObject() const { return Obj.get(); }



More information about the llvm-commits mailing list