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

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 2 07:41:58 PDT 2026


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

>From 21f08f2ddbfd117079f036c02e472d19dc9492e4 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 01/11] [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 dc503a788754a..34229c6808817 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -109,6 +109,14 @@ std::optional<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());
@@ -187,7 +195,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())
@@ -208,7 +222,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;
@@ -220,14 +235,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;
@@ -243,6 +264,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);
@@ -278,6 +312,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();
@@ -406,6 +459,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.");
   }
@@ -420,6 +494,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();
@@ -528,6 +609,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 5daa9e816f145..b169637bd592f 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -195,6 +195,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 04a1aa803f66c..db0a5901eb526 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -108,6 +108,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;
@@ -137,6 +140,9 @@ void AnyValue::destroy() {
   case StorageKind::Pointer:
     PtrVal.~Pointer();
     break;
+  case StorageKind::Byte:
+    ByteVal.~ByteValue();
+    break;
   case StorageKind::Poison:
   case StorageKind::None:
     break;
@@ -157,6 +163,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;
@@ -176,6 +185,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;
@@ -201,6 +213,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;
@@ -226,6 +241,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;
@@ -240,6 +258,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()));
@@ -265,6 +286,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>(
@@ -290,4 +313,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 71ee212b4299e..dc15a491d20c0 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; }
 
   bool isCompatibleWith(Type *Ty) const {
@@ -241,6 +287,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 70693e1be875e2c0dccc2787c38381c1321d0895 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 02/11] [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 34229c6808817..37366b886a2e1 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -245,7 +245,7 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
     }
 
     if (IsByteType) {
-      LogicalBytes[BitsStart / 8] = LogicalByte;
+      LogicalBytes[I / 8] = LogicalByte;
       continue;
     }
 
@@ -274,7 +274,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;
@@ -321,14 +321,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 b169637bd592f..2cba0183eb020 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -189,8 +189,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 db0a5901eb526..41cac84954ae8 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -287,7 +287,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>(
@@ -313,10 +314,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) {
@@ -329,22 +338,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
@@ -353,8 +366,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 dc15a491d20c0..b5ea59e034454 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 828a21c0f2b06d82e1633b9745d71984f93f6b40 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 03/11] [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 852b079dcf1e619d25e2e765d044f5ac0dbee906 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 04/11] [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 76578e2365b84344df80f26b441d31e944c0dc4d 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 05/11] [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 81cc968535a2f..ab31bdd5ec166 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 ea43651d23b337aabca701ef3f21ad03a532e9f6 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 06/11] [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 41cac84954ae8..294f36a370398 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -329,16 +329,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 23d4a0136bd0b8e0cbf3d7d3a38d7dde00838c17 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 07/11] [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 294f36a370398..1f1d433c79003 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -318,7 +318,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)));
 }

>From 6737cf878be2fc79554ce9ca93a022c3e2014b11 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 22:14:10 +0800
Subject: [PATCH 08/11] [llubi] Fix compilation error.

---
 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 1f1d433c79003..11fd375f75575 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -320,7 +320,7 @@ ByteValue::ByteValue(const APInt &V, bool IsLittleEndian)
   MutableBytesView View(Val, IsLittleEndian);
   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)));
+        V.extractBitsAsZExtValue(std::min(BitWidth - I, 8U), I)));
 }
 ByteValue ByteValue::zero(uint32_t BitWidth, bool IsLittleEndian) {
   return ByteValue(

>From 7c1cd834ed104fc66fc45ba1b4934b9919d0ef3b Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 2 Jun 2026 01:08:44 +0800
Subject: [PATCH 09/11] [llubi] Address review comments.

---
 llvm/test/tools/llubi/attribute_noundef_ub.ll |  2 +-
 llvm/test/tools/llubi/loadstore_be.ll         | 12 ++++++++----
 llvm/test/tools/llubi/loadstore_le.ll         | 12 ++++++++----
 llvm/tools/llubi/lib/Context.cpp              |  8 ++++++--
 llvm/tools/llubi/lib/Value.cpp                |  4 ++--
 llvm/tools/llubi/lib/Value.h                  |  2 ++
 6 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/llvm/test/tools/llubi/attribute_noundef_ub.ll b/llvm/test/tools/llubi/attribute_noundef_ub.ll
index f2bd1d599b7b6..cab5e22df8d0a 100644
--- a/llvm/test/tools/llubi/attribute_noundef_ub.ll
+++ b/llvm/test/tools/llubi/attribute_noundef_ub.ll
@@ -13,5 +13,5 @@ define void @main() {
 ; CHECK: Entering function: main
 ; CHECK-NEXT: Stacktrace:
 ; 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: Immediate UB detected: The value {{poison|b32 0x!! 0x!! 0x!! 0x!!}} violates noundef attribute.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index c35f536336b16..f4089b08530b8 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -154,6 +154,8 @@ define void @main() {
   %bytes_non_pow2 = load b28, ptr %alloc_byte
   store b28 u0xEADBEEF, ptr %alloc_byte
   %bytes_zextd = load b32, ptr %alloc_byte
+  store b8 255, ptr %alloc_byte
+  %bytes_zextd_poison = load b7, ptr %alloc_byte
 
   ret void
 }
@@ -267,11 +269,13 @@ define void @main() {
 ; 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:   %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 0x7F 00000000(00100110) 0x!! 0x?? 
 ; 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:   %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 0xDE 0xAD 0xBE 0xEF 
+; CHECK-NEXT:   %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 !!!! 0x!! 0x!! 0x!! 
 ; 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:   %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 0x0E 0xAD 0xBE 0xEF 
+; CHECK-NEXT:   store b8 -1, ptr %alloc_byte, align 1
+; CHECK-NEXT:   %bytes_zextd_poison = load b7, ptr %alloc_byte, align 1 => b7 !!!!!!! 
 ; 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 1f86088c4a2ec..32d5cfce65650 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -155,6 +155,8 @@ define void @main() {
   %bytes_non_pow2 = load b28, ptr %alloc_byte
   store b28 u0xEADBEEF, ptr %alloc_byte
   %bytes_zextd = load b32, ptr %alloc_byte
+  store b8 255, ptr %alloc_byte
+  %bytes_zextd_poison = load b7, ptr %alloc_byte
 
   ret void
 }
@@ -269,11 +271,13 @@ define void @main() {
 ; 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:   %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 0x7F 10010000(00101100) 0x!! 0x?? 
 ; 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:   %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 0xEF 0xBE 0xAD 0xDE 
+; CHECK-NEXT:   %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 0x!! 0x!! 0x!! !!!! 
 ; 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:   %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 0xEF 0xBE 0xAD 0x0E 
+; CHECK-NEXT:   store b8 -1, ptr %alloc_byte, align 1
+; CHECK-NEXT:   %bytes_zextd_poison = load b7, ptr %alloc_byte, align 1 => b7 !!!!!!! 
 ; 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 37366b886a2e1..f678f25a933c2 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -321,8 +321,12 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
         }
     }
     unsigned BitWidth = Ty->getByteBitWidth();
-    ByteValue Res(BitWidth, Bytes, DL.isLittleEndian(),
-                  /*ImplicitClearHighBits=*/true);
+    if (BitWidth & 7) {
+      if (!(DL.isLittleEndian() ? Bytes.back() : Bytes.front())
+               .AreHighBitsZExtd(BitWidth & 7))
+        return ByteValue::poison(BitWidth, DL.isLittleEndian());
+    }
+    ByteValue Res(BitWidth, Bytes, DL.isLittleEndian());
     return AnyValue(std::move(Res));
   }
 
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 11fd375f75575..c890a2eda53b5 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -342,11 +342,11 @@ void ByteValue::print(raw_ostream &OS) const {
     // Try to print a byte in short form
     if (IsFullByte && V.ConcreteMask == 255 && V.TagMask == 0) {
       // Concrete value without provenance.
-      OS << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
+      OS << "0x" << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
     } else if (IsFullByte && V.ConcreteMask == 0 &&
                (V.Value == 0 || V.Value == 255)) {
       // Poison/undef bytes.
-      OS << (V.Value == 0 ? "!!" : "??");
+      OS << (V.Value == 0 ? "0x!!" : "0x??");
     } else {
       uint32_t BitEnd = IsFullByte ? 8 : BitWidth & 7;
       for (uint32_t I = 0; I != BitEnd; ++I) {
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index b5ea59e034454..b9227d8597ec6 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -289,6 +289,8 @@ class [[nodiscard]] AnyValue {
       return Ty->isFloatingPointTy();
     case StorageKind::Pointer:
       return Ty->isPointerTy();
+    case StorageKind::Byte:
+      return Ty->isByteTy();
     // We don't check elements recursively.
     case StorageKind::Aggregate:
       return Ty->isAggregateType() || Ty->isVectorTy();

>From 09a99c0d575349c3cc6dbb32177565af185bca09 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 2 Jun 2026 01:14:42 +0800
Subject: [PATCH 10/11] [llubi] Add freeze tests. NFC.

---
 llvm/test/tools/llubi/freeze.ll | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/llvm/test/tools/llubi/freeze.ll b/llvm/test/tools/llubi/freeze.ll
index c63db99b08084..6140136509c6b 100644
--- a/llvm/test/tools/llubi/freeze.ll
+++ b/llvm/test/tools/llubi/freeze.ll
@@ -16,6 +16,15 @@ define void @main() {
   %arr_freeze = freeze [2 x i32] [i32 10, i32 poison]
   %struct_freeze = freeze { i32, float } { i32 10, float poison }
   %struct_freeze_nested = freeze { i32, { float, ptr } } { i32 poison, { float, ptr } { float 10.0, ptr poison } }
+
+  %alloca = alloca ptr
+  %byte_undef = load b8, ptr %alloca
+  store ptr %alloca, ptr %alloca
+  %byte_with_provenance = load b8, ptr %alloca
+  %vec_bytes1 = insertelement <4 x b8> <b8 0, b8 poison, b8 poison, b8 poison>, b8 %byte_undef, i32 2
+  %vec_bytes2 = insertelement <4 x b8> %vec_bytes1, b8 %byte_with_provenance, i32 3
+  %bytes = bitcast <4 x b8> %vec_bytes2 to b32
+  %bytes_freeze = freeze b32 %bytes
   ret void
 }
 ; CHECK: Entering function: main
@@ -30,5 +39,13 @@ define void @main() {
 ; CHECK-NEXT:   %arr_freeze = freeze [2 x i32] [i32 10, i32 poison] => { i32 10, i32 -44045842 }
 ; CHECK-NEXT:   %struct_freeze = freeze { i32, float } { i32 10, float poison } => { i32 10, float 0x042AE12F }
 ; CHECK-NEXT:   %struct_freeze_nested = freeze { i32, { float, ptr } } { i32 poison, { float, ptr } { float 1.000000e+01, ptr poison } } => { i32 1166165736, { float 1.000000e+01, ptr 0xD79E62976F604366 [nullary] } }
+; CHECK-NEXT:   %alloca = alloca ptr, align 8 => ptr 0x8 [alloca]
+; CHECK-NEXT:   %byte_undef = load b8, ptr %alloca, align 1 => b8 0x?? 
+; CHECK-NEXT:   store ptr %alloca, ptr %alloca, align 8
+; CHECK-NEXT:   %byte_with_provenance = load b8, ptr %alloca, align 1 => b8 00001000(00011001) 
+; CHECK-NEXT:   %vec_bytes1 = insertelement <4 x b8> <b8 0, b8 poison, b8 poison, b8 poison>, b8 %byte_undef, i32 2 => { b8 0x00 , b8 0x!! , b8 0x?? , b8 0x!!  }
+; CHECK-NEXT:   %vec_bytes2 = insertelement <4 x b8> %vec_bytes1, b8 %byte_with_provenance, i32 3 => { b8 0x00 , b8 0x!! , b8 0x?? , b8 00001000(00011001)  }
+; CHECK-NEXT:   %bytes = bitcast <4 x b8> %vec_bytes2 to b32 => b32 0x00 0x!! 0x?? 00001000(00011001) 
+; CHECK-NEXT:   %bytes_freeze = freeze b32 %bytes => b32 0x00 0x13 0xF8 00001000(00011001) 
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main

>From acc49f8723b664fc0817ca844949f8a5126aebd4 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 2 Jun 2026 22:41:14 +0800
Subject: [PATCH 11/11] [llubi] Add global tests with byte type

---
 llvm/test/tools/llubi/global.ll | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/llvm/test/tools/llubi/global.ll b/llvm/test/tools/llubi/global.ll
index 3cff71b21769f..c4f8c734a1a6b 100644
--- a/llvm/test/tools/llubi/global.ll
+++ b/llvm/test/tools/llubi/global.ll
@@ -1,8 +1,10 @@
+; 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 = "e-p:64:64:64-i32:32:32"
 
 @value = global i32 41
+ at value_byte = global b8 1
 @value_ptr = global ptr @value
 @aggregate = global { i32, [2 x i8] } { i32 7, [2 x i8] c"hi" }
 
@@ -12,6 +14,7 @@ define void @main() {
   %ptr = load ptr, ptr @value_ptr
   %updated = load i32, ptr %ptr
   %aggregate_value = load { i32, [2 x i8] }, ptr @aggregate
+  %val = load i8, ptr @value_byte
   ret void
 }
 
@@ -21,5 +24,6 @@ define void @main() {
 ; CHECK-NEXT:   %ptr = load ptr, ptr @value_ptr, align 8 => ptr 0x8 [@value]
 ; CHECK-NEXT:   %updated = load i32, ptr %ptr, align 4 => i32 42
 ; CHECK-NEXT:   %aggregate_value = load { i32, [2 x i8] }, ptr @aggregate, align 4 => { i32 7, { i8 104, i8 105 } }
+; CHECK-NEXT:   %val = load i8, ptr @value_byte, align 1 => i8 1
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main



More information about the llvm-commits mailing list