[llvm] [WIP][llubi] Add support for poison-generating/UB-implying attributes and metadata (PR #195339)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Sat May 2 06:23:07 PDT 2026


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

>From 137005da1c1ab2e4392a1c0995f365b675f2505b 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/2] [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 1f68051c617a0..7adc8fdf01513 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.
@@ -463,12 +539,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))
@@ -913,10 +995,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() &&
@@ -959,6 +1136,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);
@@ -1378,7 +1568,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 84d273dd0b674d4d1a9032557674d5a88d6f96b7 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/2] [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 7adc8fdf01513..43e87594a4126 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;
@@ -458,6 +453,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)
@@ -567,13 +576,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: {
@@ -1015,7 +1087,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);
@@ -1064,7 +1136,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))



More information about the llvm-commits mailing list