[llvm] [llubi] Add support for byte types (PR #200672)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 1 06:02:45 PDT 2026


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

>From a3e9eec739fa6afd05a6da786ee2669365c7f26d Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 00:53:06 +0800
Subject: [PATCH 1/7] [llubi] Add support for byte types

---
 llvm/tools/llubi/lib/Context.cpp | 103 +++++++++++++++++++++++++++++--
 llvm/tools/llubi/lib/Context.h   |   2 +
 llvm/tools/llubi/lib/Value.cpp   |  77 +++++++++++++++++++++++
 llvm/tools/llubi/lib/Value.h     |  58 ++++++++++++++++-
 4 files changed, 235 insertions(+), 5 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 69cc52545adc6..a87d66fce1638 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -77,6 +77,14 @@ AnyValue Context::getConstantValueImpl(Constant *C) {
     return CFP->getValue();
   }
 
+  if (auto *CB = dyn_cast<ConstantByte>(C)) {
+    if (auto *VecTy = dyn_cast<VectorType>(CB->getType()))
+      return std::vector<AnyValue>(
+          getEVL(VecTy->getElementCount()),
+          AnyValue(ByteValue(CB->getValue(), DL.isLittleEndian())));
+    return ByteValue(CB->getValue(), DL.isLittleEndian());
+  }
+
   if (auto *CDS = dyn_cast<ConstantDataSequential>(C)) {
     std::vector<AnyValue> Elts;
     Elts.reserve(CDS->getNumElements());
@@ -140,7 +148,13 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
   uint32_t NumBitsToExtract = NewOffsetInBits - OffsetInBits;
   uint32_t NumWords = APInt::getNumWords(NumBitsToExtract);
   constexpr uint32_t WordBits = APInt::APINT_BITS_PER_WORD;
-  SmallVector<APInt::WordType> RawBits(NumWords);
+  SmallVector<APInt::WordType> RawBits;
+  bool IsByteType = Ty->isByteTy();
+  std::vector<Byte> LogicalBytes;
+  if (IsByteType)
+    LogicalBytes.resize(divideCeil(NumBitsToExtract, 8));
+  else
+    RawBits.resize(NumWords);
   bool IsTagValid = Ty->isPointerTy();
   SmallVector<APInt::WordType> RawTagBits;
   if (Ty->isPointerTy())
@@ -161,7 +175,8 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
 
     uint32_t Mask = (1U << NumBitsInByte) - 1;
     // If any of the bits in the byte is poison, the whole value is poison.
-    if (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask) {
+    if (!IsByteType &&
+        (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask)) {
       if (ContainsUndefinedBits)
         *ContainsUndefinedBits = true;
       OffsetInBits = NewOffsetInBits;
@@ -173,14 +188,20 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
       if (ContainsUndefinedBits)
         *ContainsUndefinedBits = true;
 
-      if (getEffectiveUndefValueBehavior() ==
-          UndefValueBehavior::NonDeterministic) {
+      if (!IsByteType && getEffectiveUndefValueBehavior() ==
+                             UndefValueBehavior::NonDeterministic) {
         // We don't use std::uniform_int_distribution here because it produces
         // different results across different library implementations. Instead,
         // we directly use the low bits from Rng.
         RandomBits = static_cast<uint8_t>(Rng());
       }
     }
+
+    if (IsByteType) {
+      LogicalBytes[BitsStart / 8] = LogicalByte;
+      continue;
+    }
+
     uint8_t ActualBits = ((LogicalByte.Value & LogicalByte.ConcreteMask) |
                           (RandomBits & ~LogicalByte.ConcreteMask)) &
                          Mask;
@@ -196,6 +217,19 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
       }
     }
   }
+
+  if (IsByteType) {
+    assert(!CheckPaddingBits &&
+           "Non-vector-element cases should be handled by the fast path.");
+    if (NumBits & 7) {
+      uint8_t Mask = static_cast<uint8_t>((~0U) << (NumBits & 7));
+      LogicalBytes.back().zeroBits(Mask);
+    }
+    if (DL.isBigEndian())
+      std::reverse(LogicalBytes.begin(), LogicalBytes.end());
+    return ByteValue(NumBits, std::move(LogicalBytes));
+  }
+
   OffsetInBits = NewOffsetInBits;
 
   APInt Bits(NumBitsToExtract, RawBits);
@@ -231,6 +265,25 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
   if (Ty->isIntegerTy() || Ty->isFloatingPointTy() || Ty->isPointerTy())
     return fromBytes(ConstBytesView(Bytes, DL), Ty, /*OffsetInBits=*/0,
                      /*CheckPaddingBits=*/true, ContainsUndefinedBits);
+  if (Ty->isByteTy()) {
+    if (ContainsUndefinedBits) {
+      for (const Byte &V : Bytes)
+        if (V.ConcreteMask != 255) {
+          *ContainsUndefinedBits = true;
+          break;
+        }
+    }
+    unsigned BitWidth = Ty->getByteBitWidth();
+    ByteValue Res(BitWidth, Bytes);
+    if (BitWidth & 7) {
+      uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+      if (DL.isLittleEndian())
+        Res.mutableBytes().back().zeroBits(Mask);
+      else
+        Res.mutableBytes().front().zeroBits(Mask);
+    }
+    return AnyValue(std::move(Res));
+  }
 
   if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
     Type *ElemTy = VecTy->getElementType();
@@ -359,6 +412,27 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
     WriteBits(NeedsPadding ? AddressBits.zext(NewOffsetInBits - OffsetInBits)
                            : AddressBits,
               &Tag);
+  } else if (Ty->isByteTy()) {
+    assert(!PaddingBits &&
+           "Non-vector-element cases should be handled by the fast path.");
+    auto &ByteVal = Val.asByte();
+    ConstBytesView SrcBytes(ByteVal.bytes(), DL);
+    for (uint32_t I = 0, E = static_cast<uint32_t>(SrcBytes.size()); I != E;
+         ++I) {
+      uint32_t NumBitsInByte = std::min(8U, NumBits - I * 8);
+      uint32_t BitsStart = OffsetInBits + I * 8;
+      uint32_t BitsEnd = BitsStart + NumBitsInByte - 1;
+
+      Bytes[BitsStart / 8].writeByte(
+          static_cast<uint8_t>(((1U << NumBitsInByte) - 1) << (BitsStart % 8)),
+          SrcBytes[I].shl(BitsStart % 8));
+      // If it is a cross-byte access, write the remaining bits to the next
+      // byte.
+      if (((BitsStart ^ BitsEnd) & ~7) != 0)
+        Bytes[BitsEnd / 8].writeByte(
+            static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
+            SrcBytes[I].lshr(8 - (BitsStart % 8)));
+    }
   } else {
     llvm_unreachable("Unsupported scalar type.");
   }
@@ -373,6 +447,13 @@ void Context::toBytes(const AnyValue &Val, Type *Ty,
             /*PaddingBits=*/true);
     return;
   }
+  if (Ty->isByteTy()) {
+    auto &ByteVal = Val.asByte();
+    ArrayRef<Byte> SrcBytes = ByteVal.bytes();
+    assert(Bytes.size() == SrcBytes.size() && "Mismatched byte array.");
+    copy(SrcBytes, Bytes.begin());
+    return;
+  }
 
   if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
     Type *ElemTy = VecTy->getElementType();
@@ -481,6 +562,20 @@ void Context::freeze(AnyValue &Val, Type *Ty) {
       llvm_unreachable("Unsupported scalar type for poison value");
     return;
   }
+  if (Val.isByte()) {
+    for (Byte &V : Val.asMutableByte().mutableBytes()) {
+      if (V.ConcreteMask == 255)
+        continue;
+      uint8_t OldMask = V.ConcreteMask;
+      V.ConcreteMask = 255;
+      V.Value &= OldMask;
+      if (mayUseNonDeterminism())
+        V.Value |= (Rng() & 255) & ~OldMask;
+      V.TagMask &= OldMask;
+      V.TagValue &= OldMask;
+    }
+    return;
+  }
   if (Val.isAggregate()) {
     auto &SubVals = Val.asAggregate();
     if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 78d99f2cc5f13..8d6252649e510 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -190,6 +190,8 @@ template <typename ArrayRefT> class BytesView {
   auto &operator[](uint32_t Index) {
     return Bytes[IsLittleEndian ? Index : Bytes.size() - 1 - Index];
   }
+
+  size_t size() const { return Bytes.size(); }
 };
 
 using ConstBytesView = BytesView<ArrayRef<Byte>>;
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index fe8e0f3cff330..9b2afa9926fe3 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -106,6 +106,9 @@ void AnyValue::print(raw_ostream &OS) const {
   case StorageKind::Pointer:
     PtrVal.print(OS);
     break;
+  case StorageKind::Byte:
+    ByteVal.print(OS);
+    break;
   case StorageKind::Poison:
     OS << "poison";
     break;
@@ -135,6 +138,9 @@ void AnyValue::destroy() {
   case StorageKind::Pointer:
     PtrVal.~Pointer();
     break;
+  case StorageKind::Byte:
+    ByteVal.~ByteValue();
+    break;
   case StorageKind::Poison:
   case StorageKind::None:
     break;
@@ -155,6 +161,9 @@ AnyValue::AnyValue(const AnyValue &Other) : Kind(Other.Kind) {
   case StorageKind::Pointer:
     new (&PtrVal) Pointer(Other.PtrVal);
     break;
+  case StorageKind::Byte:
+    new (&ByteVal) ByteValue(Other.ByteVal);
+    break;
   case StorageKind::Poison:
   case StorageKind::None:
     break;
@@ -174,6 +183,9 @@ AnyValue::AnyValue(AnyValue &&Other) : Kind(Other.Kind) {
   case StorageKind::Pointer:
     new (&PtrVal) Pointer(std::move(Other.PtrVal));
     break;
+  case StorageKind::Byte:
+    new (&ByteVal) ByteValue(std::move(Other.ByteVal));
+    break;
   case StorageKind::Poison:
   case StorageKind::None:
     break;
@@ -199,6 +211,9 @@ AnyValue &AnyValue::operator=(const AnyValue &Other) {
   case StorageKind::Pointer:
     new (&PtrVal) Pointer(Other.PtrVal);
     break;
+  case StorageKind::Byte:
+    new (&ByteVal) ByteValue(Other.ByteVal);
+    break;
   case StorageKind::Poison:
   case StorageKind::None:
     break;
@@ -224,6 +239,9 @@ AnyValue &AnyValue::operator=(AnyValue &&Other) {
   case StorageKind::Pointer:
     new (&PtrVal) Pointer(std::move(Other.PtrVal));
     break;
+  case StorageKind::Byte:
+    new (&ByteVal) ByteValue(std::move(Other.ByteVal));
+    break;
   case StorageKind::Poison:
   case StorageKind::None:
     break;
@@ -238,6 +256,9 @@ AnyValue &AnyValue::operator=(AnyValue &&Other) {
 AnyValue AnyValue::getPoisonValue(Context &Ctx, Type *Ty) {
   if (Ty->isFloatingPointTy() || Ty->isIntegerTy() || Ty->isPointerTy())
     return AnyValue::poison();
+  if (Ty->isByteTy())
+    return ByteValue::poison(Ty->getByteBitWidth(),
+                             Ctx.getDataLayout().isLittleEndian());
   if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
     uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
     return AnyValue(std::vector<AnyValue>(NumElements, AnyValue::poison()));
@@ -263,6 +284,8 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
     return AnyValue(APFloat::getZero(Ty->getFltSemantics()));
   if (Ty->isPointerTy())
     return Pointer::null(Ty->getPointerAddressSpace(), Ctx.getDataLayout());
+  if (Ty->isByteTy())
+    return ByteValue::zero(Ty->getByteBitWidth());
   if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
     uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
     return AnyValue(std::vector<AnyValue>(
@@ -288,4 +311,58 @@ AnyValue AnyValue::getVectorSplat(const AnyValue &Scalar, size_t NumElements) {
   return AnyValue(std::vector<AnyValue>(NumElements, Scalar));
 }
 
+ByteValue::ByteValue(const APInt &V, bool IsLittleEndian) {}
+ByteValue ByteValue::zero(uint32_t BitWidth) {
+  return ByteValue(
+      BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)));
+}
+
+ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
+  std::vector<Byte> Val(divideCeil(BitWidth, 8), Byte::poison());
+  // Zero high-bits if it is not a byte-sized value.
+  if (BitWidth & 7) {
+    uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+    if (IsLittleEndian)
+      Val.back().zeroBits(Mask);
+    else
+      Val.front().zeroBits(Mask);
+  }
+  return ByteValue(BitWidth, std::move(Val));
+}
+
+void ByteValue::print(raw_ostream &OS) const {
+  OS << 'b' << BitWidth << ' ';
+  for (const Byte &V : Val) {
+    // Try to print a byte in short form
+    if (V.ConcreteMask == 255 && V.TagMask == 0) {
+      // Concrete value without provenance.
+      OS << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
+    } else if (V.ConcreteMask == 0 && (V.Value == 0 || V.Value == 255)) {
+      // Poison/undef bytes.
+      OS << (V.Value == 0 ? "!!" : "??");
+    } else {
+      for (uint32_t I = 0; I != 8; ++I) {
+        uint32_t Mask = 1U << (8 - I);
+        if (V.ConcreteMask & Mask)
+          OS << (V.Value & Mask ? '1' : '0');
+        else
+          OS << (V.Value & Mask ? '?' : '!');
+      }
+      if (uint32_t TagMask = V.ConcreteMask & V.TagMask) {
+        // Print tags if available.
+        OS << '(';
+        for (uint32_t I = 0; I != 8; ++I) {
+          uint32_t Mask = 1U << (8 - I);
+          if (TagMask & Mask)
+            OS << (V.TagValue & Mask ? '1' : '0');
+          else
+            OS << '!';
+        }
+        OS << ')';
+      }
+    }
+    OS << ' ';
+  }
+}
+
 } // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index 60e656342f116..ea023aa6920e5 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -69,6 +69,13 @@ struct Byte {
     TagValue = (TagValue & ~Mask) | (Tag & Mask);
   }
 
+  void writeByte(uint8_t Mask, const Byte &RHS) {
+    ConcreteMask = (ConcreteMask & ~Mask) | (RHS.ConcreteMask & Mask);
+    Value = (Value & ~Mask) | (RHS.Value & Mask);
+    TagMask = (TagMask & ~Mask) | (RHS.TagMask & Mask);
+    TagValue = (TagValue & ~Mask) | (RHS.TagValue & Mask);
+  }
+
   /// Returns a logical byte that is part of two adjacent bytes.
   /// Example with ShAmt = 5:
   ///     |       Low       |      High       |
@@ -89,13 +96,20 @@ struct Byte {
                 static_cast<uint8_t>(TagMask >> Shift),
                 static_cast<uint8_t>(TagValue >> Shift)};
   }
+
+  Byte shl(uint8_t Shift) const {
+    return Byte{static_cast<uint8_t>(ConcreteMask << Shift),
+                static_cast<uint8_t>(Value << Shift),
+                static_cast<uint8_t>(TagMask << Shift),
+                static_cast<uint8_t>(TagValue << Shift)};
+  }
 };
 
-// TODO: Byte
 enum class StorageKind {
   Integer,
   Float,
   Pointer,
+  Byte,
   Poison,
   None,      // Placeholder for void type
   Aggregate, // Struct, Array or Vector
@@ -164,6 +178,35 @@ class Pointer {
   MemoryObject *getMemoryObject() const { return Prov->getMemoryObject(); }
 };
 
+/// Represents a scalar byte value. If the value is not byte-sized, the high
+/// bits are zero-padded.
+class ByteValue {
+  std::vector<Byte> Val;
+  uint32_t BitWidth;
+
+public:
+  ByteValue(const APInt &V, bool IsLittleEndian);
+  /// The caller is responsible to zero high bits for non-byte-sized values.
+  ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val)
+      : Val(Val), BitWidth(BitWidth) {}
+  /// The caller is responsible to zero high bits for non-byte-sized values.
+  ByteValue(uint32_t BitWidth, std::vector<Byte> Val)
+      : Val(std::move(Val)), BitWidth(BitWidth) {}
+  ByteValue(const ByteValue &) = default;
+  ByteValue(ByteValue &&) = default;
+  ByteValue &operator=(const ByteValue &) = default;
+  ByteValue &operator=(ByteValue &&) = default;
+  ~ByteValue() = default;
+
+  static ByteValue zero(uint32_t BitWidth);
+  static ByteValue poison(uint32_t BitWidth, bool IsLittleEndian);
+
+  uint32_t getBitWidth() const { return BitWidth; }
+  ArrayRef<Byte> bytes() const { return Val; }
+  MutableArrayRef<Byte> mutableBytes() { return Val; }
+  void print(raw_ostream &OS) const;
+};
+
 // Value representation for actual values of LLVM values.
 // We don't model undef values here (except for byte types).
 class [[nodiscard]] AnyValue {
@@ -172,6 +215,7 @@ class [[nodiscard]] AnyValue {
     APInt IntVal;
     APFloat FloatVal;
     Pointer PtrVal;
+    ByteValue ByteVal;
     std::vector<AnyValue> AggVal;
   };
 
@@ -184,6 +228,7 @@ class [[nodiscard]] AnyValue {
   AnyValue(APInt Val) : Kind(StorageKind::Integer), IntVal(std::move(Val)) {}
   AnyValue(APFloat Val) : Kind(StorageKind::Float), FloatVal(std::move(Val)) {}
   AnyValue(Pointer Val) : Kind(StorageKind::Pointer), PtrVal(std::move(Val)) {}
+  AnyValue(ByteValue Val) : Kind(StorageKind::Byte), ByteVal(std::move(Val)) {}
   AnyValue(std::vector<AnyValue> Val)
       : Kind(StorageKind::Aggregate), AggVal(std::move(Val)) {}
   AnyValue(const AnyValue &Other);
@@ -205,6 +250,7 @@ class [[nodiscard]] AnyValue {
   bool isInteger() const { return Kind == StorageKind::Integer; }
   bool isFloat() const { return Kind == StorageKind::Float; }
   bool isPointer() const { return Kind == StorageKind::Pointer; }
+  bool isByte() const { return Kind == StorageKind::Byte; }
   bool isAggregate() const { return Kind == StorageKind::Aggregate; }
 
   const APInt &asInteger() const {
@@ -222,6 +268,16 @@ class [[nodiscard]] AnyValue {
     return PtrVal;
   }
 
+  const ByteValue &asByte() const {
+    assert(Kind == StorageKind::Byte && "Expect a byte value");
+    return ByteVal;
+  }
+
+  ByteValue &asMutableByte() {
+    assert(Kind == StorageKind::Byte && "Expect a byte value");
+    return ByteVal;
+  }
+
   const std::vector<AnyValue> &asAggregate() const {
     assert(Kind == StorageKind::Aggregate &&
            "Expect an aggregate/vector value");

>From 8cd012c3458cd54c9112148dfb950dd44b110b51 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:46:15 +0800
Subject: [PATCH 2/7] [llubi] Minor fixes

---
 llvm/tools/llubi/lib/Context.cpp | 14 ++++-------
 llvm/tools/llubi/lib/Context.h   |  4 +++-
 llvm/tools/llubi/lib/Value.cpp   | 35 +++++++++++++++++++---------
 llvm/tools/llubi/lib/Value.h     | 40 +++++++++++++++++++++++++-------
 4 files changed, 63 insertions(+), 30 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index a87d66fce1638..6388bfc1aefa5 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -198,7 +198,7 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
     }
 
     if (IsByteType) {
-      LogicalBytes[BitsStart / 8] = LogicalByte;
+      LogicalBytes[I / 8] = LogicalByte;
       continue;
     }
 
@@ -227,7 +227,7 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
     }
     if (DL.isBigEndian())
       std::reverse(LogicalBytes.begin(), LogicalBytes.end());
-    return ByteValue(NumBits, std::move(LogicalBytes));
+    return ByteValue(NumBits, std::move(LogicalBytes), DL.isLittleEndian());
   }
 
   OffsetInBits = NewOffsetInBits;
@@ -274,14 +274,8 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
         }
     }
     unsigned BitWidth = Ty->getByteBitWidth();
-    ByteValue Res(BitWidth, Bytes);
-    if (BitWidth & 7) {
-      uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
-      if (DL.isLittleEndian())
-        Res.mutableBytes().back().zeroBits(Mask);
-      else
-        Res.mutableBytes().front().zeroBits(Mask);
-    }
+    ByteValue Res(BitWidth, Bytes, DL.isLittleEndian(),
+                  /*ImplicitClearHighBits=*/true);
     return AnyValue(std::move(Res));
   }
 
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 8d6252649e510..3769a61a137fe 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -184,8 +184,10 @@ template <typename ArrayRefT> class BytesView {
   bool IsLittleEndian;
 
 public:
+  explicit BytesView(ArrayRefT Ref, bool IsLittleEndian)
+      : Bytes(Ref), IsLittleEndian(IsLittleEndian) {}
   explicit BytesView(ArrayRefT Ref, const DataLayout &DL)
-      : Bytes(Ref), IsLittleEndian(DL.isLittleEndian()) {}
+      : BytesView(Ref, DL.isLittleEndian()) {}
 
   auto &operator[](uint32_t Index) {
     return Bytes[IsLittleEndian ? Index : Bytes.size() - 1 - Index];
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 9b2afa9926fe3..e1dc6ca6d1ec1 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -285,7 +285,8 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
   if (Ty->isPointerTy())
     return Pointer::null(Ty->getPointerAddressSpace(), Ctx.getDataLayout());
   if (Ty->isByteTy())
-    return ByteValue::zero(Ty->getByteBitWidth());
+    return ByteValue::zero(Ty->getByteBitWidth(),
+                           Ctx.getDataLayout().isLittleEndian());
   if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
     uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
     return AnyValue(std::vector<AnyValue>(
@@ -311,10 +312,18 @@ AnyValue AnyValue::getVectorSplat(const AnyValue &Scalar, size_t NumElements) {
   return AnyValue(std::vector<AnyValue>(NumElements, Scalar));
 }
 
-ByteValue::ByteValue(const APInt &V, bool IsLittleEndian) {}
-ByteValue ByteValue::zero(uint32_t BitWidth) {
+ByteValue::ByteValue(const APInt &V, bool IsLittleEndian)
+    : BitWidth(V.getBitWidth()), IsLittleEndian(IsLittleEndian) {
+  Val.resize(divideCeil(BitWidth, 8));
+  MutableBytesView View(Val, IsLittleEndian);
+  for (uint8_t I = 0; I < BitWidth; I += 8)
+    View[I / 8] = Byte::concrete(static_cast<uint8_t>(
+        V.extractBitsAsZExtValue(std::min(BitWidth - I, 8), I)));
+}
+ByteValue ByteValue::zero(uint32_t BitWidth, bool IsLittleEndian) {
   return ByteValue(
-      BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)));
+      BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)),
+      IsLittleEndian);
 }
 
 ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
@@ -327,22 +336,26 @@ ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
     else
       Val.front().zeroBits(Mask);
   }
-  return ByteValue(BitWidth, std::move(Val));
+  return ByteValue(BitWidth, std::move(Val), IsLittleEndian);
 }
 
 void ByteValue::print(raw_ostream &OS) const {
   OS << 'b' << BitWidth << ' ';
   for (const Byte &V : Val) {
+    bool IsFullByte = (BitWidth & 7) == 0 ||
+                      (IsLittleEndian ? &Val.back() : &Val.front()) != &V;
     // Try to print a byte in short form
-    if (V.ConcreteMask == 255 && V.TagMask == 0) {
+    if (IsFullByte && V.ConcreteMask == 255 && V.TagMask == 0) {
       // Concrete value without provenance.
       OS << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
-    } else if (V.ConcreteMask == 0 && (V.Value == 0 || V.Value == 255)) {
+    } else if (IsFullByte && V.ConcreteMask == 0 &&
+               (V.Value == 0 || V.Value == 255)) {
       // Poison/undef bytes.
       OS << (V.Value == 0 ? "!!" : "??");
     } else {
-      for (uint32_t I = 0; I != 8; ++I) {
-        uint32_t Mask = 1U << (8 - I);
+      uint32_t BitEnd = IsFullByte ? 8 : BitWidth & 7;
+      for (uint32_t I = 0; I != BitEnd; ++I) {
+        uint32_t Mask = 1U << (BitEnd - 1 - I);
         if (V.ConcreteMask & Mask)
           OS << (V.Value & Mask ? '1' : '0');
         else
@@ -351,8 +364,8 @@ void ByteValue::print(raw_ostream &OS) const {
       if (uint32_t TagMask = V.ConcreteMask & V.TagMask) {
         // Print tags if available.
         OS << '(';
-        for (uint32_t I = 0; I != 8; ++I) {
-          uint32_t Mask = 1U << (8 - I);
+        for (uint32_t I = 0; I != BitEnd; ++I) {
+          uint32_t Mask = 1U << (BitEnd - 1 - I);
           if (TagMask & Mask)
             OS << (V.TagValue & Mask ? '1' : '0');
           else
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index ea023aa6920e5..28ed8771e72e6 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -43,6 +43,7 @@ struct Byte {
   void zeroBits(uint8_t Mask) {
     ConcreteMask |= Mask;
     Value &= ~Mask;
+    TagMask &= ~Mask;
   }
 
   void poisonBits(uint8_t Mask) {
@@ -103,6 +104,12 @@ struct Byte {
                 static_cast<uint8_t>(TagMask << Shift),
                 static_cast<uint8_t>(TagValue << Shift)};
   }
+
+  bool AreHighBitsZExtd(uint8_t BitsFrom) const {
+    uint8_t Mask = static_cast<uint8_t>((~0U) << BitsFrom);
+    return (ConcreteMask & Mask) == Mask && (Value & Mask) == 0 &&
+           (TagMask & Mask) == 0;
+  }
 };
 
 enum class StorageKind {
@@ -181,24 +188,41 @@ class Pointer {
 /// Represents a scalar byte value. If the value is not byte-sized, the high
 /// bits are zero-padded.
 class ByteValue {
+  // The byte order is endianness-dependent.
   std::vector<Byte> Val;
-  uint32_t BitWidth;
+  uint32_t BitWidth : 31;
+  uint32_t IsLittleEndian : 1;
 
 public:
   ByteValue(const APInt &V, bool IsLittleEndian);
-  /// The caller is responsible to zero high bits for non-byte-sized values.
-  ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val)
-      : Val(Val), BitWidth(BitWidth) {}
-  /// The caller is responsible to zero high bits for non-byte-sized values.
-  ByteValue(uint32_t BitWidth, std::vector<Byte> Val)
-      : Val(std::move(Val)), BitWidth(BitWidth) {}
+  ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val, bool IsLittleEndian,
+            bool ImplicitClearHighBits = false)
+      : ByteValue(BitWidth, std::vector<Byte>(Val), IsLittleEndian,
+                  ImplicitClearHighBits) {}
+  ByteValue(uint32_t BitWidth, std::vector<Byte> Val, bool IsLittleEndian,
+            bool ImplicitClearHighBits = false)
+      : Val(std::move(Val)), BitWidth(BitWidth),
+        IsLittleEndian(IsLittleEndian) {
+    if (ImplicitClearHighBits && (BitWidth & 7) != 0) {
+      uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+      if (IsLittleEndian)
+        this->Val.back().zeroBits(Mask);
+      else
+        this->Val.front().zeroBits(Mask);
+    }
+    assert(((BitWidth & 7) == 0 ||
+            ((IsLittleEndian ? this->Val.back() : this->Val.front())
+                 .AreHighBitsZExtd(BitWidth & 7))) &&
+           "The caller is responsible to zero high bits for non-byte-sized "
+           "values.");
+  }
   ByteValue(const ByteValue &) = default;
   ByteValue(ByteValue &&) = default;
   ByteValue &operator=(const ByteValue &) = default;
   ByteValue &operator=(ByteValue &&) = default;
   ~ByteValue() = default;
 
-  static ByteValue zero(uint32_t BitWidth);
+  static ByteValue zero(uint32_t BitWidth, bool IsLittleEndian);
   static ByteValue poison(uint32_t BitWidth, bool IsLittleEndian);
 
   uint32_t getBitWidth() const { return BitWidth; }

>From ec6dbeaf0faca3252765c976e1f3908c66b9726d Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:47:12 +0800
Subject: [PATCH 3/7] [llubi] Add tests. NFC.

---
 llvm/test/tools/llubi/loadstore_be.ll | 65 +++++++++++++++++++++++++++
 llvm/test/tools/llubi/loadstore_le.ll | 65 +++++++++++++++++++++++++++
 2 files changed, 130 insertions(+)

diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 8f2e0815f88fe..c35f536336b16 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -113,12 +113,47 @@ define void @main() {
 
   %alloc_ptr = alloca ptr
   store ptr %alloc_ptr, ptr %alloc_ptr
+  %bytes = load b64, ptr %alloc_ptr
   ; It should recover the provenance.
   %ptr_with_provenance = load ptr, ptr %alloc_ptr
   %addr_bits = load i8, ptr %alloc_ptr
   store i8 %addr_bits, ptr %alloc_ptr
   ; The first byte is tainted. We cannot recover the provenance.
   %ptr_without_provenance = load ptr, ptr %alloc_ptr
+  store b64 %bytes, ptr %alloc_ptr
+  ; It should recover the provenance.
+  %ptr_with_provenance2 = load ptr, ptr %alloc_ptr
+  %bv64b1 = bitcast b64 %bytes to <64 x b1>
+  %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse(<64 x b1> %bv64b1)
+  store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr
+  ; The bit order is incorrect. We cannot recover the provenance.
+  %ptr_without_provenance2 = load ptr, ptr %alloc_ptr
+  store b64 %bytes, ptr %alloc_ptr
+  store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr
+  %bytes_tainted = load b64, ptr %alloc_ptr
+  ; The first byte is tainted. We cannot recover the provenance.
+  %ptr_without_provenance3 = load ptr, ptr %alloc_ptr
+  %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8>
+  %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7
+  %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1>
+  %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse(<8 x b1> %first_byte_with_provenance_reversed_v8b1)
+  store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr
+  %bytes_recovered = load b64, ptr %alloc_ptr
+  ; It should recover the provenance.
+  %ptr_with_provenance3 = load ptr, ptr %alloc_ptr
+
+  %alloc_byte = alloca b32
+  store b8 127, ptr %alloc_byte
+  %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1
+  store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte
+  %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2
+  store b8 poison, ptr %gep_third_byte
+  %bytes_mixed = load b32, ptr %alloc_byte
+  store b32 u0xDEADBEEF, ptr %alloc_byte
+  %bytes_endianness = load b32, ptr %alloc_byte
+  %bytes_non_pow2 = load b28, ptr %alloc_byte
+  store b28 u0xEADBEEF, ptr %alloc_byte
+  %bytes_zextd = load b32, ptr %alloc_byte
 
   ret void
 }
@@ -204,9 +239,39 @@ define void @main() {
 ; CHECK-NEXT:   %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
 ; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
 ; CHECK-NEXT:   store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %bytes = load b64, ptr %alloc_ptr, align 8 => b64 00000000(00100110) 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100) 
 ; CHECK-NEXT:   %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
 ; CHECK-NEXT:   %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 0
 ; CHECK-NEXT:   store i8 %addr_bits, ptr %alloc_ptr, align 1
 ; CHECK-NEXT:   %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT:   store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %ptr_with_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT:   %bv64b1 = bitcast b64 %bytes to <64 x b1> => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0)  }
+; CHECK-NEXT:   %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse.v64b1(<64 x b1> %bv64b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0)  }
+; CHECK-NEXT:   store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %ptr_without_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x900000000000000 [nullary]
+; CHECK-NEXT:   store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr, align 1
+; CHECK-NEXT:   %bytes_tainted = load b64, ptr %alloc_ptr, align 8 => b64 0000!!!! 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100) 
+; CHECK-NEXT:   %ptr_without_provenance3 = load ptr, ptr %alloc_ptr, align 8 => poison
+; CHECK-NEXT:   %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8> => { b8 00001001(00110100) , b8 00000000(00010000) , b8 00000000(10101001) , b8 00000000(01010001) , b8 00000000(10011100) , b8 00000000(00111100) , b8 00000000(10001110) , b8 00000000(01100100)  }
+; CHECK-NEXT:   %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7 => b8 00000000(01100100) 
+; CHECK-NEXT:   %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1> => { b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0)  }
+; CHECK-NEXT:   %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse.v8b1(<8 x b1> %first_byte_with_provenance_reversed_v8b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0)  }
+; CHECK-NEXT:   store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr, align 1
+; CHECK-NEXT:   %bytes_recovered = load b64, ptr %alloc_ptr, align 8 => b64 00000000(00100110) 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100) 
+; CHECK-NEXT:   %ptr_with_provenance3 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT:   %alloc_byte = alloca b32, align 4 => ptr 0x98 [alloc_byte]
+; CHECK-NEXT:   store b8 127, ptr %alloc_byte, align 1
+; CHECK-NEXT:   %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1 => ptr 0x99 [alloc_byte + 1]
+; CHECK-NEXT:   store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte, align 1
+; CHECK-NEXT:   %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0x9A [alloc_byte + 2]
+; CHECK-NEXT:   store b8 poison, ptr %gep_third_byte, align 1
+; CHECK-NEXT:   %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 7F 00000000(00100110) !! ?? 
+; CHECK-NEXT:   store b32 -559038737, ptr %alloc_byte, align 4
+; CHECK-NEXT:   %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 DE AD BE EF 
+; CHECK-NEXT:   %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 1110 AD BE EF 
+; CHECK-NEXT:   store b28 -22167825, ptr %alloc_byte, align 4
+; CHECK-NEXT:   %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 0E AD BE EF 
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index 3caf02c3e6eaa..1f86088c4a2ec 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -114,12 +114,47 @@ define void @main() {
 
   %alloc_ptr = alloca ptr
   store ptr %alloc_ptr, ptr %alloc_ptr
+  %bytes = load b64, ptr %alloc_ptr
   ; It should recover the provenance.
   %ptr_with_provenance = load ptr, ptr %alloc_ptr
   %addr_bits = load i8, ptr %alloc_ptr
   store i8 %addr_bits, ptr %alloc_ptr
   ; The first byte is tainted. We cannot recover the provenance.
   %ptr_without_provenance = load ptr, ptr %alloc_ptr
+  store b64 %bytes, ptr %alloc_ptr
+  ; It should recover the provenance.
+  %ptr_with_provenance2 = load ptr, ptr %alloc_ptr
+  %bv64b1 = bitcast b64 %bytes to <64 x b1>
+  %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse(<64 x b1> %bv64b1)
+  store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr
+  ; The bit order is incorrect. We cannot recover the provenance.
+  %ptr_without_provenance2 = load ptr, ptr %alloc_ptr
+  store b64 %bytes, ptr %alloc_ptr
+  store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr
+  %bytes_tainted = load b64, ptr %alloc_ptr
+  ; The first byte is tainted. We cannot recover the provenance.
+  %ptr_without_provenance3 = load ptr, ptr %alloc_ptr
+  %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8>
+  %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7
+  %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1>
+  %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse(<8 x b1> %first_byte_with_provenance_reversed_v8b1)
+  store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr
+  %bytes_recovered = load b64, ptr %alloc_ptr
+  ; It should recover the provenance.
+  %ptr_with_provenance3 = load ptr, ptr %alloc_ptr
+
+  %alloc_byte = alloca b32
+  store b8 127, ptr %alloc_byte
+  %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1
+  store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte
+  %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2
+  store b8 poison, ptr %gep_third_byte
+  %bytes_mixed = load b32, ptr %alloc_byte
+  store b32 u0xDEADBEEF, ptr %alloc_byte
+  %bytes_endianness = load b32, ptr %alloc_byte
+  %bytes_non_pow2 = load b28, ptr %alloc_byte
+  store b28 u0xEADBEEF, ptr %alloc_byte
+  %bytes_zextd = load b32, ptr %alloc_byte
 
   ret void
 }
@@ -206,9 +241,39 @@ define void @main() {
 ; CHECK-NEXT:   %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
 ; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
 ; CHECK-NEXT:   store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %bytes = load b64, ptr %alloc_ptr, align 8 => b64 10010000(00101100) 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110) 
 ; CHECK-NEXT:   %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
 ; CHECK-NEXT:   %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -112
 ; CHECK-NEXT:   store i8 %addr_bits, ptr %alloc_ptr, align 1
 ; CHECK-NEXT:   %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT:   store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %ptr_with_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT:   %bv64b1 = bitcast b64 %bytes to <64 x b1> => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0)  }
+; CHECK-NEXT:   %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse.v64b1(<64 x b1> %bv64b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0)  }
+; CHECK-NEXT:   store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %ptr_without_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x900000000000000 [nullary]
+; CHECK-NEXT:   store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr, align 1
+; CHECK-NEXT:   %bytes_tainted = load b64, ptr %alloc_ptr, align 8 => b64 !!!!0000 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110) 
+; CHECK-NEXT:   %ptr_without_provenance3 = load ptr, ptr %alloc_ptr, align 8 => poison
+; CHECK-NEXT:   %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8> => { b8 00000000(01100100) , b8 00000000(10001110) , b8 00000000(00111100) , b8 00000000(10011100) , b8 00000000(01010001) , b8 00000000(10101001) , b8 00000000(00010000) , b8 00001001(00110100)  }
+; CHECK-NEXT:   %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7 => b8 00001001(00110100) 
+; CHECK-NEXT:   %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1> => { b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0)  }
+; CHECK-NEXT:   %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse.v8b1(<8 x b1> %first_byte_with_provenance_reversed_v8b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0)  }
+; CHECK-NEXT:   store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr, align 1
+; CHECK-NEXT:   %bytes_recovered = load b64, ptr %alloc_ptr, align 8 => b64 10010000(00101100) 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110) 
+; CHECK-NEXT:   %ptr_with_provenance3 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT:   %alloc_byte = alloca b32, align 4 => ptr 0x98 [alloc_byte]
+; CHECK-NEXT:   store b8 127, ptr %alloc_byte, align 1
+; CHECK-NEXT:   %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1 => ptr 0x99 [alloc_byte + 1]
+; CHECK-NEXT:   store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte, align 1
+; CHECK-NEXT:   %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0x9A [alloc_byte + 2]
+; CHECK-NEXT:   store b8 poison, ptr %gep_third_byte, align 1
+; CHECK-NEXT:   %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 7F 10010000(00101100) !! ?? 
+; CHECK-NEXT:   store b32 -559038737, ptr %alloc_byte, align 4
+; CHECK-NEXT:   %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 EF BE AD DE 
+; CHECK-NEXT:   %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 EF BE AD 1110 
+; CHECK-NEXT:   store b28 -22167825, ptr %alloc_byte, align 4
+; CHECK-NEXT:   %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 EF BE AD 0E 
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main

>From 7b3dc59fdc72516cf5ec08912a9b48ec7335fdec Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:56:44 +0800
Subject: [PATCH 4/7] [llubi] Add tests with !noundef check. NFC.

---
 llvm/test/tools/llubi/load_noundef_ub_undef.ll | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/llvm/test/tools/llubi/load_noundef_ub_undef.ll b/llvm/test/tools/llubi/load_noundef_ub_undef.ll
index bb9f5c79cf260..fdc94b602c7a2 100644
--- a/llvm/test/tools/llubi/load_noundef_ub_undef.ll
+++ b/llvm/test/tools/llubi/load_noundef_ub_undef.ll
@@ -1,5 +1,6 @@
 ; 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
+; RUN: sed 's/i32/b32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
 
 define void @main() {
   %p = alloca i32
@@ -7,8 +8,8 @@ define void @main() {
   ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT:   %p = alloca i32, align 4 => ptr 0x8 [p]
+; CHECK-NEXT:   %p = alloca {{i|b}}32, align 4 => ptr 0x8 [p]
 ; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0   %res = load i32, ptr %p, align 4, !noundef !0 at @main <stdin>:6
+; CHECK-NEXT: #0   %res = load {{i|b}}32, ptr %p, align 4, !noundef !0 at @main <stdin>:7
 ; CHECK-NEXT: Immediate UB detected: The value loaded contains undefined bits.
 ; CHECK-NEXT: error: Execution of function 'main' failed.

>From 26e4dbf0a20a627d80e901907a7b52eaaddec188 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 09:11:17 +0800
Subject: [PATCH 5/7] [llubi] Handle noundef attribute for byte types

---
 llvm/test/tools/llubi/attribute_noundef_ub.ll |  5 +++--
 llvm/test/tools/llubi/attributes.ll           | 11 +++++++++++
 llvm/tools/llubi/lib/Interpreter.cpp          | 14 ++++++++++++--
 3 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/llvm/test/tools/llubi/attribute_noundef_ub.ll b/llvm/test/tools/llubi/attribute_noundef_ub.ll
index a8a6a1984398b..f2bd1d599b7b6 100644
--- a/llvm/test/tools/llubi/attribute_noundef_ub.ll
+++ b/llvm/test/tools/llubi/attribute_noundef_ub.ll
@@ -1,5 +1,6 @@
 ; 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
+; RUN: sed 's/i32/b32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
 
 define void @callee(i32 noundef %x) {
   ret void
@@ -11,6 +12,6 @@ define void @main() {
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0   call void @callee(i32 poison) at @main <stdin>:9
-; CHECK-NEXT: Immediate UB detected: The value poison violates noundef attribute.
+; CHECK-NEXT: #0   call void @callee({{i|b}}32 poison) at @main <stdin>:10
+; CHECK-NEXT: Immediate UB detected: The value {{poison|b32 !! !! !! !!}} 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 cc8fcf62a46f9..971063e55b904 100644
--- a/llvm/test/tools/llubi/attributes.ll
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -47,6 +47,10 @@ define noundef i32 @identity_noundef(i32 noundef %x) {
   ret i32 %x
 }
 
+define noundef b7 @identity_noundef_byte(b7 noundef %x) {
+  ret b7 %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
 }
@@ -119,6 +123,8 @@ define void @main() {
   %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)
+
+  %noundef_byte = call b7 @identity_noundef_byte(b7 -1)
   ret void
 }
 ; CHECK: Entering function: main
@@ -324,5 +330,10 @@ define void @main() {
 ; 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: Entering function: identity_noundef_byte
+; CHECK-NEXT:   b7 %x = b7 1111111 
+; CHECK-NEXT:   ret b7 %x
+; CHECK-NEXT: Exiting function: identity_noundef_byte
+; CHECK-NEXT:   %noundef_byte = call b7 @identity_noundef_byte(b7 -1) => b7 1111111 
 ; 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 3c21f8ff175f7..0cba8121245a8 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -112,8 +112,18 @@ static void applyAlignAttr(AnyValue &V, Align Alignment) {
 
 static bool violatesNoUndefAttr(AnyValue &V) {
   bool ContainsPoison = false;
-  forEachScalarValue(
-      V, [&](AnyValue &Scalar) { ContainsPoison |= Scalar.isPoison(); });
+  forEachScalarValue(V, [&](AnyValue &Scalar) {
+    if (Scalar.isPoison()) {
+      ContainsPoison = true;
+      return;
+    }
+    if (Scalar.isByte() && !ContainsPoison) {
+      // For non-byte-sized values, high bits are always zeroed out.
+      ContainsPoison = any_of(Scalar.asByte().bytes(), [](const Byte &V) {
+        return V.ConcreteMask != 255;
+      });
+    }
+  });
   return ContainsPoison;
 }
 

>From 2b097c3a12a92284e3ace710c4716198ccec3ab2 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 09:17:06 +0800
Subject: [PATCH 6/7] [llubi] Use ImplicitClearHighBits in `ByteValue::poison`

---
 llvm/tools/llubi/lib/Value.cpp | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index e1dc6ca6d1ec1..2dc3fea6387b4 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -327,16 +327,9 @@ ByteValue ByteValue::zero(uint32_t BitWidth, bool IsLittleEndian) {
 }
 
 ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
-  std::vector<Byte> Val(divideCeil(BitWidth, 8), Byte::poison());
-  // Zero high-bits if it is not a byte-sized value.
-  if (BitWidth & 7) {
-    uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
-    if (IsLittleEndian)
-      Val.back().zeroBits(Mask);
-    else
-      Val.front().zeroBits(Mask);
-  }
-  return ByteValue(BitWidth, std::move(Val), IsLittleEndian);
+  return ByteValue(BitWidth,
+                   std::vector<Byte>(divideCeil(BitWidth, 8), Byte::poison()),
+                   IsLittleEndian, /*ImplicitClearHighBits=*/true);
 }
 
 void ByteValue::print(raw_ostream &OS) const {

>From ed48a0626caf38d3f7c1795a920b05e6d4b2f981 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 21:02:21 +0800
Subject: [PATCH 7/7] [llubi] Use uint32_t

---
 llvm/tools/llubi/lib/Value.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 2dc3fea6387b4..f258b73d560ba 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -316,7 +316,7 @@ ByteValue::ByteValue(const APInt &V, bool IsLittleEndian)
     : BitWidth(V.getBitWidth()), IsLittleEndian(IsLittleEndian) {
   Val.resize(divideCeil(BitWidth, 8));
   MutableBytesView View(Val, IsLittleEndian);
-  for (uint8_t I = 0; I < BitWidth; I += 8)
+  for (uint32_t I = 0; I < BitWidth; I += 8)
     View[I / 8] = Byte::concrete(static_cast<uint8_t>(
         V.extractBitsAsZExtValue(std::min(BitWidth - I, 8), I)));
 }



More information about the llvm-commits mailing list