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

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 4 11:36:11 PDT 2026


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

>From f762d55fc2b71c8dbe34f88c8643ff54876be040 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/17] [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 e89f7a43b45c6..3b2d91c6d821d 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -126,6 +126,14 @@ MaterializedConstant Context::getConstantValueImpl(Constant *C) {
     return MaterializedConstant(CFP->getValue(), /*Cacheable=*/true);
   }
 
+  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());
@@ -412,7 +420,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())
@@ -433,7 +447,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;
@@ -445,14 +460,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;
@@ -468,6 +489,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);
@@ -503,6 +537,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();
@@ -631,6 +684,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.");
   }
@@ -645,6 +719,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();
@@ -753,6 +834,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 097f0d1e0ab8b..da70c9f23e474 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -197,6 +197,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 3f1c2567cc51d..6bee90b580571 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -118,6 +118,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;
@@ -147,6 +150,9 @@ void AnyValue::destroy() {
   case StorageKind::Pointer:
     PtrVal.~Pointer();
     break;
+  case StorageKind::Byte:
+    ByteVal.~ByteValue();
+    break;
   case StorageKind::Poison:
   case StorageKind::None:
     break;
@@ -167,6 +173,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;
@@ -186,6 +195,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;
@@ -211,6 +223,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;
@@ -236,6 +251,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;
@@ -250,6 +268,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()));
@@ -275,6 +296,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>(
@@ -300,4 +323,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 77e428813a832..0bb94b5bb6386 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -79,6 +79,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       |
@@ -99,13 +106,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
@@ -220,6 +234,35 @@ class Pointer {
   Provenance &provenance() const { return *Prov; }
 };
 
+/// 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 {
@@ -228,6 +271,7 @@ class [[nodiscard]] AnyValue {
     APInt IntVal;
     APFloat FloatVal;
     Pointer PtrVal;
+    ByteValue ByteVal;
     std::vector<AnyValue> AggVal;
   };
 
@@ -240,6 +284,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);
@@ -261,6 +306,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 {
@@ -297,6 +343,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 3dd0b60c3487d2b3f75cfe49ca65e7c5215fb3e1 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/17] [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 3b2d91c6d821d..e3e334bb6b854 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -470,7 +470,7 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
     }
 
     if (IsByteType) {
-      LogicalBytes[BitsStart / 8] = LogicalByte;
+      LogicalBytes[I / 8] = LogicalByte;
       continue;
     }
 
@@ -499,7 +499,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;
@@ -546,14 +546,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 da70c9f23e474..571be140cc83f 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -191,8 +191,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 6bee90b580571..8d37c3587ce72 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -297,7 +297,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>(
@@ -323,10 +324,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) {
@@ -339,22 +348,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
@@ -363,8 +376,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 0bb94b5bb6386..ae2347c1cb0a4 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -53,6 +53,7 @@ struct Byte {
   void zeroBits(uint8_t Mask) {
     ConcreteMask |= Mask;
     Value &= ~Mask;
+    TagMask &= ~Mask;
   }
 
   void poisonBits(uint8_t Mask) {
@@ -113,6 +114,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 {
@@ -237,24 +244,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 b9a8280469882044f3fd6cd6ca749ebe3f2cfc40 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/17] [llubi] Add tests. NFC.

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

diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 8f1f851ff13c2..b15bef03fdcfa 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
 }
@@ -208,5 +243,39 @@ define void @main() {
 ; 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 0xC0 [nullary]
+; 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 9ad92ac6e75e0..4e6348a6eed75 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
 }
@@ -210,5 +245,39 @@ define void @main() {
 ; CHECK-NEXT:   %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -64
 ; CHECK-NEXT:   store i8 %addr_bits, ptr %alloc_ptr, align 1
 ; CHECK-NEXT:   %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [nullary]
+; 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 89756b06beb31df311a772d4aefb83f450e6ca44 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/17] [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 0ed718c25ec91f709ad741ecfded1c8243dd2048 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/17] [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 87057ab816ba7..931924b443643 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 d734a9962e6d9..8bc2aa843cd7a 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -78,8 +78,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 e4e8ceb4a641d1fbaf9df7791e97a1bf076ad814 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/17] [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 8d37c3587ce72..f3feb272d9e20 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -339,16 +339,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 51826ef3c1194d753fcb32f6b5405d7dd057042f 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/17] [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 f3feb272d9e20..c75349cf3d4fb 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -328,7 +328,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 c197139f0bc6c2378b74e581c4a371c91260c585 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/17] [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 c75349cf3d4fb..60accc25a8b19 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -330,7 +330,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 5ac66e3e9f00eebd5b7f375461ad27d1c5bb6ef7 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/17] [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 b15bef03fdcfa..6bf7050603546 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
 }
@@ -271,11 +273,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 4e6348a6eed75..d31555fedb5b5 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
 }
@@ -273,11 +275,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 e3e334bb6b854..51d4a4f66073c 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -546,8 +546,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 60accc25a8b19..5a811d67f04f5 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -352,11 +352,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 ae2347c1cb0a4..3505fb79579e6 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -345,6 +345,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 0bdbeee1cf7b0aa5099012ae373a546d7a5da51a 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/17] [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 35dc028bf801686f1819981a29780107d06377da 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/17] [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

>From 9f744ef10924abd11e9b90e9a2fc6b3723c80b2e Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 4 Jun 2026 01:55:35 +0800
Subject: [PATCH 12/17] [llubi] Add bitcast tests. NFC.

---
 llvm/test/tools/llubi/bitcast_be.ll | 7 +++++++
 llvm/test/tools/llubi/bitcast_le.ll | 7 +++++++
 2 files changed, 14 insertions(+)

diff --git a/llvm/test/tools/llubi/bitcast_be.ll b/llvm/test/tools/llubi/bitcast_be.ll
index f5cd5d3c9dd24..9b962534c3052 100644
--- a/llvm/test/tools/llubi/bitcast_be.ll
+++ b/llvm/test/tools/llubi/bitcast_be.ll
@@ -29,6 +29,10 @@ entry:
 
   %bitcast_intvec2floatvec = bitcast <2 x i32> <i32 1, i32 2> to <4 x half>
   %bitcast_floatvec2int = bitcast <4 x half> <half 1.0, half 2.0, half 3.0, half 4.0> to i64
+
+  %bitcast_ptr2b64 = bitcast ptr %ptr to b64
+  %bitcast_b642i64 = bitcast b64 %bitcast_ptr2b64 to i64
+  %bitcast_b642ptr = bitcast b64 %bitcast_ptr2b64 to ptr
   ret void
 }
 ; CHECK: Entering function: main
@@ -54,5 +58,8 @@ entry:
 ; CHECK-NEXT:   %bitcast_vec2vec_weird = bitcast <8 x i3> <i3 0, i3 1, i3 2, i3 3, i3 -4, i3 -3, i3 -2, i3 -1> to <3 x i8> => { i8 5, i8 57, i8 119 }
 ; CHECK-NEXT:   %bitcast_intvec2floatvec = bitcast <2 x i32> <i32 1, i32 2> to <4 x half> => { half 0.000000e+00, half 5.960460e-08, half 0.000000e+00, half 1.192090e-07 }
 ; CHECK-NEXT:   %bitcast_floatvec2int = bitcast <4 x half> <half 1.000000e+00, half 2.000000e+00, half 3.000000e+00, half 4.000000e+00> to i64 => i64 4323526012127167488
+; CHECK-NEXT:   %bitcast_ptr2b64 = bitcast ptr %ptr to b64 => b64 00000000(00101000) 00000000(11101000) 00000000(00110111) 00000000(11000101) 00000000(11001011) 00000000(01000001) 00000000(11011100) 00001000(00111110) 
+; CHECK-NEXT:   %bitcast_b642i64 = bitcast b64 %bitcast_ptr2b64 to i64 => i64 8
+; CHECK-NEXT:   %bitcast_b642ptr = bitcast b64 %bitcast_ptr2b64 to ptr => ptr 0x8 [ptr]
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/bitcast_le.ll b/llvm/test/tools/llubi/bitcast_le.ll
index e949d6655aeb0..7151fc6364020 100644
--- a/llvm/test/tools/llubi/bitcast_le.ll
+++ b/llvm/test/tools/llubi/bitcast_le.ll
@@ -29,6 +29,10 @@ entry:
 
   %bitcast_intvec2floatvec = bitcast <2 x i32> <i32 1, i32 2> to <4 x half>
   %bitcast_floatvec2int = bitcast <4 x half> <half 1.0, half 2.0, half 3.0, half 4.0> to i64
+
+  %bitcast_ptr2b64 = bitcast ptr %ptr to b64
+  %bitcast_b642i64 = bitcast b64 %bitcast_ptr2b64 to i64
+  %bitcast_b642ptr = bitcast b64 %bitcast_ptr2b64 to ptr
   ret void
 }
 ; CHECK: Entering function: main
@@ -54,5 +58,8 @@ entry:
 ; CHECK-NEXT:   %bitcast_vec2vec_weird = bitcast <8 x i3> <i3 0, i3 1, i3 2, i3 3, i3 -4, i3 -3, i3 -2, i3 -1> to <3 x i8> => { i8 -120, i8 -58, i8 -6 }
 ; CHECK-NEXT:   %bitcast_intvec2floatvec = bitcast <2 x i32> <i32 1, i32 2> to <4 x half> => { half 5.960460e-08, half 0.000000e+00, half 1.192090e-07, half 0.000000e+00 }
 ; CHECK-NEXT:   %bitcast_floatvec2int = bitcast <4 x half> <half 1.000000e+00, half 2.000000e+00, half 3.000000e+00, half 4.000000e+00> to i64 => i64 4899988963420290048
+; CHECK-NEXT:   %bitcast_ptr2b64 = bitcast ptr %ptr to b64 => b64 00001000(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) 
+; CHECK-NEXT:   %bitcast_b642i64 = bitcast b64 %bitcast_ptr2b64 to i64 => i64 8
+; CHECK-NEXT:   %bitcast_b642ptr = bitcast b64 %bitcast_ptr2b64 to ptr => ptr 0x8 [ptr]
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main

>From 50edd96c4c3f2ae3033b94ec94333a6e372e1cc5 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 16 Jun 2026 23:45:41 +0800
Subject: [PATCH 13/17] [llubi] Update check lines after rebasing

---
 llvm/test/tools/llubi/loadstore_be.ll | 28 ++++++++++------------
 llvm/test/tools/llubi/loadstore_le.ll | 34 ++++++++++++---------------
 2 files changed, 27 insertions(+), 35 deletions(-)

diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 6bf7050603546..d8de9fbe32968 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -241,37 +241,33 @@ 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 0xC0 [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) 11000000(00101100) 
 ; CHECK-NEXT:   %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [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 0xC0 [nullary]
-; 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:   %ptr_with_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [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 1(0) , b1 0(1) , b1 0(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 0(0) , b1 0(1) , b1 1(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:   %ptr_without_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x300000000000000 [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:   %bytes_tainted = load b64, ptr %alloc_ptr, align 8 => b64 0000!!!! 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 11000000(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:   %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8> => { b8 00000011(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:   %bytes_recovered = load b64, ptr %alloc_ptr, align 8 => b64 00000000(00100110) 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 11000000(00101100) 
+; CHECK-NEXT:   %ptr_with_provenance3 = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [alloc_ptr]
+; CHECK-NEXT:   %alloc_byte = alloca b32, align 4 => ptr 0xCC [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:   %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1 => ptr 0xCD [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:   %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0xCE [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 0x7F 00000000(00100110) 0x!! 0x?? 
 ; CHECK-NEXT:   store b32 -559038737, ptr %alloc_byte, align 4
diff --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index d31555fedb5b5..13cd6a415bee8 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -243,39 +243,35 @@ 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 0xC0 [alloc_ptr]
 ; CHECK-NEXT:   store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT:   %bytes = load b64, ptr %alloc_ptr, align 8 => b64 11000000(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 0xC0 [alloc_ptr]
 ; CHECK-NEXT:   %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -64
 ; CHECK-NEXT:   store i8 %addr_bits, ptr %alloc_ptr, align 1
 ; CHECK-NEXT:   %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [nullary]
-; 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:   %ptr_with_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [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 0(0) , b1 0(1) , b1 1(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 1(0) , b1 0(1) , b1 0(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:   %ptr_without_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x300000000000000 [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:   %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 00000011(00110100)  }
+; CHECK-NEXT:   %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7 => b8 00000011(00110100) 
+; CHECK-NEXT:   %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1> => { b1 1(0) , b1 1(0) , b1 0(1) , b1 0(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 0(0) , b1 0(1) , b1 1(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:   %bytes_recovered = load b64, ptr %alloc_ptr, align 8 => b64 11000000(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 0xC0 [alloc_ptr]
+; CHECK-NEXT:   %alloc_byte = alloca b32, align 4 => ptr 0xCC [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:   %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1 => ptr 0xCD [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:   %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0xCE [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 0x7F 10010000(00101100) 0x!! 0x?? 
+; CHECK-NEXT:   %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 0x7F 11000000(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 0xEF 0xBE 0xAD 0xDE 
 ; CHECK-NEXT:   %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 0x!! 0x!! 0x!! !!!! 

>From 1452fead5fc3318a3fa2c6ebd432e911b7f12e6e Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 16 Jun 2026 23:51:08 +0800
Subject: [PATCH 14/17] [llubi] Pre-commit problematic cases. NFC.

---
 llvm/test/tools/llubi/bytes_undef.ll | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 llvm/test/tools/llubi/bytes_undef.ll

diff --git a/llvm/test/tools/llubi/bytes_undef.ll b/llvm/test/tools/llubi/bytes_undef.ll
new file mode 100644
index 0000000000000..befda937f284c
--- /dev/null
+++ b/llvm/test/tools/llubi/bytes_undef.ll
@@ -0,0 +1,21 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+; byte values don't contain undef bits.
+
+define void @main() {
+  %undef = alloca b8
+  %load_undef1 = load b8, ptr %undef
+  %load_undef2 = load b8, ptr %undef
+  %bitcast_undef1 = bitcast b8 %load_undef1 to i8
+  %bitcast_undef2 = bitcast b8 %load_undef1 to i8
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %undef = alloca b8, align 1 => ptr 0x8 [undef]
+; CHECK-NEXT:   %load_undef1 = load b8, ptr %undef, align 1 => b8 0x?? 
+; CHECK-NEXT:   %load_undef2 = load b8, ptr %undef, align 1 => b8 0x?? 
+; CHECK-NEXT:   %bitcast_undef1 = bitcast b8 %load_undef1 to i8 => i8 62
+; CHECK-NEXT:   %bitcast_undef2 = bitcast b8 %load_undef1 to i8 => i8 -117
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main

>From e196ce642ffec915e109f630378f8734ce1cface Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Wed, 17 Jun 2026 00:13:33 +0800
Subject: [PATCH 15/17] [llubi] Freeze undef bits in byte value

---
 llvm/test/tools/llubi/bytes_undef.ll  |  6 ++---
 llvm/test/tools/llubi/freeze.ll       | 12 +++++-----
 llvm/test/tools/llubi/loadstore_be.ll |  2 +-
 llvm/test/tools/llubi/loadstore_le.ll |  2 +-
 llvm/tools/llubi/lib/Context.cpp      | 33 +++++++++++++++++++--------
 llvm/tools/llubi/lib/Value.cpp        | 15 +++++++-----
 6 files changed, 43 insertions(+), 27 deletions(-)

diff --git a/llvm/test/tools/llubi/bytes_undef.ll b/llvm/test/tools/llubi/bytes_undef.ll
index befda937f284c..6bf51ca69d2b1 100644
--- a/llvm/test/tools/llubi/bytes_undef.ll
+++ b/llvm/test/tools/llubi/bytes_undef.ll
@@ -13,9 +13,9 @@ define void @main() {
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %undef = alloca b8, align 1 => ptr 0x8 [undef]
-; CHECK-NEXT:   %load_undef1 = load b8, ptr %undef, align 1 => b8 0x?? 
-; CHECK-NEXT:   %load_undef2 = load b8, ptr %undef, align 1 => b8 0x?? 
+; CHECK-NEXT:   %load_undef1 = load b8, ptr %undef, align 1 => b8 0x3E 
+; CHECK-NEXT:   %load_undef2 = load b8, ptr %undef, align 1 => b8 0x8B 
 ; CHECK-NEXT:   %bitcast_undef1 = bitcast b8 %load_undef1 to i8 => i8 62
-; CHECK-NEXT:   %bitcast_undef2 = bitcast b8 %load_undef1 to i8 => i8 -117
+; CHECK-NEXT:   %bitcast_undef2 = bitcast b8 %load_undef1 to i8 => i8 62
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/freeze.ll b/llvm/test/tools/llubi/freeze.ll
index 6140136509c6b..c85a1458dd668 100644
--- a/llvm/test/tools/llubi/freeze.ll
+++ b/llvm/test/tools/llubi/freeze.ll
@@ -40,12 +40,12 @@ define void @main() {
 ; 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:   %byte_undef = load b8, ptr %alloca, align 1 => b8 0x19 
 ; 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:   %byte_with_provenance = load b8, ptr %alloca, align 1 => b8 00001000(00010011) 
+; 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 0x19 , b8 0x!!  }
+; CHECK-NEXT:   %vec_bytes2 = insertelement <4 x b8> %vec_bytes1, b8 %byte_with_provenance, i32 3 => { b8 0x00 , b8 0x!! , b8 0x19 , b8 00001000(00010011)  }
+; CHECK-NEXT:   %bytes = bitcast <4 x b8> %vec_bytes2 to b32 => b32 0x00 0x!! 0x19 00001000(00010011) 
+; CHECK-NEXT:   %bytes_freeze = freeze b32 %bytes => b32 0x00 0xF8 0x19 00001000(00010011) 
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index d8de9fbe32968..81d995ef69f48 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -269,7 +269,7 @@ 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 0xCE [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 0x7F 00000000(00100110) 0x!! 0x?? 
+; CHECK-NEXT:   %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 0x7F 00000000(00100110) 0x!! 0x2F 
 ; CHECK-NEXT:   store b32 -559038737, ptr %alloc_byte, align 4
 ; 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!! 
diff --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index 13cd6a415bee8..0d5893cc0a0ce 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -271,7 +271,7 @@ 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 0xCE [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 0x7F 11000000(00101100) 0x!! 0x?? 
+; CHECK-NEXT:   %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 0x7F 11000000(00101100) 0x!! 0x2F 
 ; CHECK-NEXT:   store b32 -559038737, ptr %alloc_byte, align 4
 ; 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!! !!!! 
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 51d4a4f66073c..d35bc2fdb7845 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -460,8 +460,8 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
       if (ContainsUndefinedBits)
         *ContainsUndefinedBits = true;
 
-      if (!IsByteType && getEffectiveUndefValueBehavior() ==
-                             UndefValueBehavior::NonDeterministic) {
+      if (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.
@@ -470,6 +470,8 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
     }
 
     if (IsByteType) {
+      LogicalByte.writeBits(~LogicalByte.ConcreteMask & LogicalByte.Value,
+                            RandomBits);
       LogicalBytes[I / 8] = LogicalByte;
       continue;
     }
@@ -538,20 +540,31 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
     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();
     if (BitWidth & 7) {
       if (!(DL.isLittleEndian() ? Bytes.back() : Bytes.front())
-               .AreHighBitsZExtd(BitWidth & 7))
+               .AreHighBitsZExtd(BitWidth & 7)) {
+        if (ContainsUndefinedBits)
+          *ContainsUndefinedBits = true;
         return ByteValue::poison(BitWidth, DL.isLittleEndian());
+      }
     }
     ByteValue Res(BitWidth, Bytes, DL.isLittleEndian());
+    bool HasUndefinedBits = false;
+    for (Byte &V : Res.mutableBytes()) {
+      if (V.ConcreteMask != 255)
+        HasUndefinedBits = true;
+      uint8_t UndefBits = ~V.ConcreteMask & V.Value;
+      if (UndefBits == 0)
+        continue;
+      uint8_t RandomBits = 0;
+      if (getEffectiveUndefValueBehavior() ==
+          UndefValueBehavior::NonDeterministic)
+        RandomBits = static_cast<uint8_t>(Rng());
+      V.writeBits(UndefBits, RandomBits);
+    }
+    if (ContainsUndefinedBits)
+      *ContainsUndefinedBits = HasUndefinedBits;
     return AnyValue(std::move(Res));
   }
 
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 5a811d67f04f5..e0192e6ce2a24 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -353,18 +353,21 @@ void ByteValue::print(raw_ostream &OS) const {
     if (IsFullByte && V.ConcreteMask == 255 && V.TagMask == 0) {
       // Concrete value without provenance.
       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 ? "0x!!" : "0x??");
+    } else if (IsFullByte && V.ConcreteMask == 0) {
+      assert(V.Value == 0 && "Byte values don't contain undef bits.");
+      // Poison bytes.
+      OS << "0x!!";
     } else {
       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
-          OS << (V.Value & Mask ? '?' : '!');
+        else {
+          assert((V.Value & Mask) == 0 &&
+                 "Byte values don't contain undef bits.");
+          OS << '!';
+        }
       }
       if (uint32_t TagMask = V.ConcreteMask & V.TagMask) {
         // Print tags if available.

>From 6db6840fed0137e63ce0ff46df146061c07589b8 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 5 Jul 2026 02:19:14 +0800
Subject: [PATCH 16/17] [llubi] Use MaterializedConstant

---
 llvm/tools/llubi/lib/Context.cpp | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index d35bc2fdb7845..55c6022a9250c 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -128,10 +128,13 @@ MaterializedConstant Context::getConstantValueImpl(Constant *C) {
 
   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());
+      return MaterializedConstant(
+          std::vector<AnyValue>(
+              getEVL(VecTy->getElementCount()),
+              AnyValue(ByteValue(CB->getValue(), DL.isLittleEndian()))),
+          /*Cacheable=*/true);
+    return MaterializedConstant(ByteValue(CB->getValue(), DL.isLittleEndian()),
+                                /*Cacheable=*/true);
   }
 
   if (auto *CDS = dyn_cast<ConstantDataSequential>(C)) {

>From bee1572c85641f2d1df711d1289f46394d229620 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 5 Jul 2026 02:19:26 +0800
Subject: [PATCH 17/17] [llubi] Update OffsetInBits

---
 llvm/tools/llubi/lib/Context.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 55c6022a9250c..47a011a795019 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -495,6 +495,8 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
     }
   }
 
+  OffsetInBits = NewOffsetInBits;
+
   if (IsByteType) {
     assert(!CheckPaddingBits &&
            "Non-vector-element cases should be handled by the fast path.");
@@ -507,8 +509,6 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
     return ByteValue(NumBits, std::move(LogicalBytes), DL.isLittleEndian());
   }
 
-  OffsetInBits = NewOffsetInBits;
-
   APInt Bits(NumBitsToExtract, RawBits);
 
   // Padding bits for non-byte-sized scalar types must be zero.



More information about the llvm-commits mailing list