[llvm] [llubi] Add support for byte types (PR #200672)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 2 07:41:58 PDT 2026
https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/200672
>From 21f08f2ddbfd117079f036c02e472d19dc9492e4 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 00:53:06 +0800
Subject: [PATCH 01/11] [llubi] Add support for byte types
---
llvm/tools/llubi/lib/Context.cpp | 103 +++++++++++++++++++++++++++++--
llvm/tools/llubi/lib/Context.h | 2 +
llvm/tools/llubi/lib/Value.cpp | 77 +++++++++++++++++++++++
llvm/tools/llubi/lib/Value.h | 58 ++++++++++++++++-
4 files changed, 235 insertions(+), 5 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index dc503a788754a..34229c6808817 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -109,6 +109,14 @@ std::optional<AnyValue> Context::getConstantValueImpl(Constant *C) {
return CFP->getValue();
}
+ if (auto *CB = dyn_cast<ConstantByte>(C)) {
+ if (auto *VecTy = dyn_cast<VectorType>(CB->getType()))
+ return std::vector<AnyValue>(
+ getEVL(VecTy->getElementCount()),
+ AnyValue(ByteValue(CB->getValue(), DL.isLittleEndian())));
+ return ByteValue(CB->getValue(), DL.isLittleEndian());
+ }
+
if (auto *CDS = dyn_cast<ConstantDataSequential>(C)) {
std::vector<AnyValue> Elts;
Elts.reserve(CDS->getNumElements());
@@ -187,7 +195,13 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
uint32_t NumBitsToExtract = NewOffsetInBits - OffsetInBits;
uint32_t NumWords = APInt::getNumWords(NumBitsToExtract);
constexpr uint32_t WordBits = APInt::APINT_BITS_PER_WORD;
- SmallVector<APInt::WordType> RawBits(NumWords);
+ SmallVector<APInt::WordType> RawBits;
+ bool IsByteType = Ty->isByteTy();
+ std::vector<Byte> LogicalBytes;
+ if (IsByteType)
+ LogicalBytes.resize(divideCeil(NumBitsToExtract, 8));
+ else
+ RawBits.resize(NumWords);
bool IsTagValid = Ty->isPointerTy();
SmallVector<APInt::WordType> RawTagBits;
if (Ty->isPointerTy())
@@ -208,7 +222,8 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
uint32_t Mask = (1U << NumBitsInByte) - 1;
// If any of the bits in the byte is poison, the whole value is poison.
- if (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask) {
+ if (!IsByteType &&
+ (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask)) {
if (ContainsUndefinedBits)
*ContainsUndefinedBits = true;
OffsetInBits = NewOffsetInBits;
@@ -220,14 +235,20 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
if (ContainsUndefinedBits)
*ContainsUndefinedBits = true;
- if (getEffectiveUndefValueBehavior() ==
- UndefValueBehavior::NonDeterministic) {
+ if (!IsByteType && getEffectiveUndefValueBehavior() ==
+ UndefValueBehavior::NonDeterministic) {
// We don't use std::uniform_int_distribution here because it produces
// different results across different library implementations. Instead,
// we directly use the low bits from Rng.
RandomBits = static_cast<uint8_t>(Rng());
}
}
+
+ if (IsByteType) {
+ LogicalBytes[BitsStart / 8] = LogicalByte;
+ continue;
+ }
+
uint8_t ActualBits = ((LogicalByte.Value & LogicalByte.ConcreteMask) |
(RandomBits & ~LogicalByte.ConcreteMask)) &
Mask;
@@ -243,6 +264,19 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
}
}
}
+
+ if (IsByteType) {
+ assert(!CheckPaddingBits &&
+ "Non-vector-element cases should be handled by the fast path.");
+ if (NumBits & 7) {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << (NumBits & 7));
+ LogicalBytes.back().zeroBits(Mask);
+ }
+ if (DL.isBigEndian())
+ std::reverse(LogicalBytes.begin(), LogicalBytes.end());
+ return ByteValue(NumBits, std::move(LogicalBytes));
+ }
+
OffsetInBits = NewOffsetInBits;
APInt Bits(NumBitsToExtract, RawBits);
@@ -278,6 +312,25 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
if (Ty->isIntegerTy() || Ty->isFloatingPointTy() || Ty->isPointerTy())
return fromBytes(ConstBytesView(Bytes, DL), Ty, /*OffsetInBits=*/0,
/*CheckPaddingBits=*/true, ContainsUndefinedBits);
+ if (Ty->isByteTy()) {
+ if (ContainsUndefinedBits) {
+ for (const Byte &V : Bytes)
+ if (V.ConcreteMask != 255) {
+ *ContainsUndefinedBits = true;
+ break;
+ }
+ }
+ unsigned BitWidth = Ty->getByteBitWidth();
+ ByteValue Res(BitWidth, Bytes);
+ if (BitWidth & 7) {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+ if (DL.isLittleEndian())
+ Res.mutableBytes().back().zeroBits(Mask);
+ else
+ Res.mutableBytes().front().zeroBits(Mask);
+ }
+ return AnyValue(std::move(Res));
+ }
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
Type *ElemTy = VecTy->getElementType();
@@ -406,6 +459,27 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
WriteBits(NeedsPadding ? AddressBits.zext(NewOffsetInBits - OffsetInBits)
: AddressBits,
&Tag);
+ } else if (Ty->isByteTy()) {
+ assert(!PaddingBits &&
+ "Non-vector-element cases should be handled by the fast path.");
+ auto &ByteVal = Val.asByte();
+ ConstBytesView SrcBytes(ByteVal.bytes(), DL);
+ for (uint32_t I = 0, E = static_cast<uint32_t>(SrcBytes.size()); I != E;
+ ++I) {
+ uint32_t NumBitsInByte = std::min(8U, NumBits - I * 8);
+ uint32_t BitsStart = OffsetInBits + I * 8;
+ uint32_t BitsEnd = BitsStart + NumBitsInByte - 1;
+
+ Bytes[BitsStart / 8].writeByte(
+ static_cast<uint8_t>(((1U << NumBitsInByte) - 1) << (BitsStart % 8)),
+ SrcBytes[I].shl(BitsStart % 8));
+ // If it is a cross-byte access, write the remaining bits to the next
+ // byte.
+ if (((BitsStart ^ BitsEnd) & ~7) != 0)
+ Bytes[BitsEnd / 8].writeByte(
+ static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
+ SrcBytes[I].lshr(8 - (BitsStart % 8)));
+ }
} else {
llvm_unreachable("Unsupported scalar type.");
}
@@ -420,6 +494,13 @@ void Context::toBytes(const AnyValue &Val, Type *Ty,
/*PaddingBits=*/true);
return;
}
+ if (Ty->isByteTy()) {
+ auto &ByteVal = Val.asByte();
+ ArrayRef<Byte> SrcBytes = ByteVal.bytes();
+ assert(Bytes.size() == SrcBytes.size() && "Mismatched byte array.");
+ copy(SrcBytes, Bytes.begin());
+ return;
+ }
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
Type *ElemTy = VecTy->getElementType();
@@ -528,6 +609,20 @@ void Context::freeze(AnyValue &Val, Type *Ty) {
llvm_unreachable("Unsupported scalar type for poison value");
return;
}
+ if (Val.isByte()) {
+ for (Byte &V : Val.asMutableByte().mutableBytes()) {
+ if (V.ConcreteMask == 255)
+ continue;
+ uint8_t OldMask = V.ConcreteMask;
+ V.ConcreteMask = 255;
+ V.Value &= OldMask;
+ if (mayUseNonDeterminism())
+ V.Value |= (Rng() & 255) & ~OldMask;
+ V.TagMask &= OldMask;
+ V.TagValue &= OldMask;
+ }
+ return;
+ }
if (Val.isAggregate()) {
auto &SubVals = Val.asAggregate();
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 5daa9e816f145..b169637bd592f 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -195,6 +195,8 @@ template <typename ArrayRefT> class BytesView {
auto &operator[](uint32_t Index) {
return Bytes[IsLittleEndian ? Index : Bytes.size() - 1 - Index];
}
+
+ size_t size() const { return Bytes.size(); }
};
using ConstBytesView = BytesView<ArrayRef<Byte>>;
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 04a1aa803f66c..db0a5901eb526 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -108,6 +108,9 @@ void AnyValue::print(raw_ostream &OS) const {
case StorageKind::Pointer:
PtrVal.print(OS);
break;
+ case StorageKind::Byte:
+ ByteVal.print(OS);
+ break;
case StorageKind::Poison:
OS << "poison";
break;
@@ -137,6 +140,9 @@ void AnyValue::destroy() {
case StorageKind::Pointer:
PtrVal.~Pointer();
break;
+ case StorageKind::Byte:
+ ByteVal.~ByteValue();
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -157,6 +163,9 @@ AnyValue::AnyValue(const AnyValue &Other) : Kind(Other.Kind) {
case StorageKind::Pointer:
new (&PtrVal) Pointer(Other.PtrVal);
break;
+ case StorageKind::Byte:
+ new (&ByteVal) ByteValue(Other.ByteVal);
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -176,6 +185,9 @@ AnyValue::AnyValue(AnyValue &&Other) : Kind(Other.Kind) {
case StorageKind::Pointer:
new (&PtrVal) Pointer(std::move(Other.PtrVal));
break;
+ case StorageKind::Byte:
+ new (&ByteVal) ByteValue(std::move(Other.ByteVal));
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -201,6 +213,9 @@ AnyValue &AnyValue::operator=(const AnyValue &Other) {
case StorageKind::Pointer:
new (&PtrVal) Pointer(Other.PtrVal);
break;
+ case StorageKind::Byte:
+ new (&ByteVal) ByteValue(Other.ByteVal);
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -226,6 +241,9 @@ AnyValue &AnyValue::operator=(AnyValue &&Other) {
case StorageKind::Pointer:
new (&PtrVal) Pointer(std::move(Other.PtrVal));
break;
+ case StorageKind::Byte:
+ new (&ByteVal) ByteValue(std::move(Other.ByteVal));
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -240,6 +258,9 @@ AnyValue &AnyValue::operator=(AnyValue &&Other) {
AnyValue AnyValue::getPoisonValue(Context &Ctx, Type *Ty) {
if (Ty->isFloatingPointTy() || Ty->isIntegerTy() || Ty->isPointerTy())
return AnyValue::poison();
+ if (Ty->isByteTy())
+ return ByteValue::poison(Ty->getByteBitWidth(),
+ Ctx.getDataLayout().isLittleEndian());
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(NumElements, AnyValue::poison()));
@@ -265,6 +286,8 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
return AnyValue(APFloat::getZero(Ty->getFltSemantics()));
if (Ty->isPointerTy())
return Pointer::null(Ty->getPointerAddressSpace(), Ctx.getDataLayout());
+ if (Ty->isByteTy())
+ return ByteValue::zero(Ty->getByteBitWidth());
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(
@@ -290,4 +313,58 @@ AnyValue AnyValue::getVectorSplat(const AnyValue &Scalar, size_t NumElements) {
return AnyValue(std::vector<AnyValue>(NumElements, Scalar));
}
+ByteValue::ByteValue(const APInt &V, bool IsLittleEndian) {}
+ByteValue ByteValue::zero(uint32_t BitWidth) {
+ return ByteValue(
+ BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)));
+}
+
+ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
+ std::vector<Byte> Val(divideCeil(BitWidth, 8), Byte::poison());
+ // Zero high-bits if it is not a byte-sized value.
+ if (BitWidth & 7) {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+ if (IsLittleEndian)
+ Val.back().zeroBits(Mask);
+ else
+ Val.front().zeroBits(Mask);
+ }
+ return ByteValue(BitWidth, std::move(Val));
+}
+
+void ByteValue::print(raw_ostream &OS) const {
+ OS << 'b' << BitWidth << ' ';
+ for (const Byte &V : Val) {
+ // Try to print a byte in short form
+ if (V.ConcreteMask == 255 && V.TagMask == 0) {
+ // Concrete value without provenance.
+ OS << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
+ } else if (V.ConcreteMask == 0 && (V.Value == 0 || V.Value == 255)) {
+ // Poison/undef bytes.
+ OS << (V.Value == 0 ? "!!" : "??");
+ } else {
+ for (uint32_t I = 0; I != 8; ++I) {
+ uint32_t Mask = 1U << (8 - I);
+ if (V.ConcreteMask & Mask)
+ OS << (V.Value & Mask ? '1' : '0');
+ else
+ OS << (V.Value & Mask ? '?' : '!');
+ }
+ if (uint32_t TagMask = V.ConcreteMask & V.TagMask) {
+ // Print tags if available.
+ OS << '(';
+ for (uint32_t I = 0; I != 8; ++I) {
+ uint32_t Mask = 1U << (8 - I);
+ if (TagMask & Mask)
+ OS << (V.TagValue & Mask ? '1' : '0');
+ else
+ OS << '!';
+ }
+ OS << ')';
+ }
+ }
+ OS << ' ';
+ }
+}
+
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index 71ee212b4299e..dc15a491d20c0 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -69,6 +69,13 @@ struct Byte {
TagValue = (TagValue & ~Mask) | (Tag & Mask);
}
+ void writeByte(uint8_t Mask, const Byte &RHS) {
+ ConcreteMask = (ConcreteMask & ~Mask) | (RHS.ConcreteMask & Mask);
+ Value = (Value & ~Mask) | (RHS.Value & Mask);
+ TagMask = (TagMask & ~Mask) | (RHS.TagMask & Mask);
+ TagValue = (TagValue & ~Mask) | (RHS.TagValue & Mask);
+ }
+
/// Returns a logical byte that is part of two adjacent bytes.
/// Example with ShAmt = 5:
/// | Low | High |
@@ -89,13 +96,20 @@ struct Byte {
static_cast<uint8_t>(TagMask >> Shift),
static_cast<uint8_t>(TagValue >> Shift)};
}
+
+ Byte shl(uint8_t Shift) const {
+ return Byte{static_cast<uint8_t>(ConcreteMask << Shift),
+ static_cast<uint8_t>(Value << Shift),
+ static_cast<uint8_t>(TagMask << Shift),
+ static_cast<uint8_t>(TagValue << Shift)};
+ }
};
-// TODO: Byte
enum class StorageKind {
Integer,
Float,
Pointer,
+ Byte,
Poison,
None, // Placeholder for void type
Aggregate, // Struct, Array or Vector
@@ -164,6 +178,35 @@ class Pointer {
MemoryObject *getMemoryObject() const { return Prov->getMemoryObject(); }
};
+/// Represents a scalar byte value. If the value is not byte-sized, the high
+/// bits are zero-padded.
+class ByteValue {
+ std::vector<Byte> Val;
+ uint32_t BitWidth;
+
+public:
+ ByteValue(const APInt &V, bool IsLittleEndian);
+ /// The caller is responsible to zero high bits for non-byte-sized values.
+ ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val)
+ : Val(Val), BitWidth(BitWidth) {}
+ /// The caller is responsible to zero high bits for non-byte-sized values.
+ ByteValue(uint32_t BitWidth, std::vector<Byte> Val)
+ : Val(std::move(Val)), BitWidth(BitWidth) {}
+ ByteValue(const ByteValue &) = default;
+ ByteValue(ByteValue &&) = default;
+ ByteValue &operator=(const ByteValue &) = default;
+ ByteValue &operator=(ByteValue &&) = default;
+ ~ByteValue() = default;
+
+ static ByteValue zero(uint32_t BitWidth);
+ static ByteValue poison(uint32_t BitWidth, bool IsLittleEndian);
+
+ uint32_t getBitWidth() const { return BitWidth; }
+ ArrayRef<Byte> bytes() const { return Val; }
+ MutableArrayRef<Byte> mutableBytes() { return Val; }
+ void print(raw_ostream &OS) const;
+};
+
// Value representation for actual values of LLVM values.
// We don't model undef values here (except for byte types).
class [[nodiscard]] AnyValue {
@@ -172,6 +215,7 @@ class [[nodiscard]] AnyValue {
APInt IntVal;
APFloat FloatVal;
Pointer PtrVal;
+ ByteValue ByteVal;
std::vector<AnyValue> AggVal;
};
@@ -184,6 +228,7 @@ class [[nodiscard]] AnyValue {
AnyValue(APInt Val) : Kind(StorageKind::Integer), IntVal(std::move(Val)) {}
AnyValue(APFloat Val) : Kind(StorageKind::Float), FloatVal(std::move(Val)) {}
AnyValue(Pointer Val) : Kind(StorageKind::Pointer), PtrVal(std::move(Val)) {}
+ AnyValue(ByteValue Val) : Kind(StorageKind::Byte), ByteVal(std::move(Val)) {}
AnyValue(std::vector<AnyValue> Val)
: Kind(StorageKind::Aggregate), AggVal(std::move(Val)) {}
AnyValue(const AnyValue &Other);
@@ -205,6 +250,7 @@ class [[nodiscard]] AnyValue {
bool isInteger() const { return Kind == StorageKind::Integer; }
bool isFloat() const { return Kind == StorageKind::Float; }
bool isPointer() const { return Kind == StorageKind::Pointer; }
+ bool isByte() const { return Kind == StorageKind::Byte; }
bool isAggregate() const { return Kind == StorageKind::Aggregate; }
bool isCompatibleWith(Type *Ty) const {
@@ -241,6 +287,16 @@ class [[nodiscard]] AnyValue {
return PtrVal;
}
+ const ByteValue &asByte() const {
+ assert(Kind == StorageKind::Byte && "Expect a byte value");
+ return ByteVal;
+ }
+
+ ByteValue &asMutableByte() {
+ assert(Kind == StorageKind::Byte && "Expect a byte value");
+ return ByteVal;
+ }
+
const std::vector<AnyValue> &asAggregate() const {
assert(Kind == StorageKind::Aggregate &&
"Expect an aggregate/vector value");
>From 70693e1be875e2c0dccc2787c38381c1321d0895 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:46:15 +0800
Subject: [PATCH 02/11] [llubi] Minor fixes
---
llvm/tools/llubi/lib/Context.cpp | 14 ++++-------
llvm/tools/llubi/lib/Context.h | 4 +++-
llvm/tools/llubi/lib/Value.cpp | 35 +++++++++++++++++++---------
llvm/tools/llubi/lib/Value.h | 40 +++++++++++++++++++++++++-------
4 files changed, 63 insertions(+), 30 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 34229c6808817..37366b886a2e1 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -245,7 +245,7 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
}
if (IsByteType) {
- LogicalBytes[BitsStart / 8] = LogicalByte;
+ LogicalBytes[I / 8] = LogicalByte;
continue;
}
@@ -274,7 +274,7 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
}
if (DL.isBigEndian())
std::reverse(LogicalBytes.begin(), LogicalBytes.end());
- return ByteValue(NumBits, std::move(LogicalBytes));
+ return ByteValue(NumBits, std::move(LogicalBytes), DL.isLittleEndian());
}
OffsetInBits = NewOffsetInBits;
@@ -321,14 +321,8 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
}
}
unsigned BitWidth = Ty->getByteBitWidth();
- ByteValue Res(BitWidth, Bytes);
- if (BitWidth & 7) {
- uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
- if (DL.isLittleEndian())
- Res.mutableBytes().back().zeroBits(Mask);
- else
- Res.mutableBytes().front().zeroBits(Mask);
- }
+ ByteValue Res(BitWidth, Bytes, DL.isLittleEndian(),
+ /*ImplicitClearHighBits=*/true);
return AnyValue(std::move(Res));
}
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index b169637bd592f..2cba0183eb020 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -189,8 +189,10 @@ template <typename ArrayRefT> class BytesView {
bool IsLittleEndian;
public:
+ explicit BytesView(ArrayRefT Ref, bool IsLittleEndian)
+ : Bytes(Ref), IsLittleEndian(IsLittleEndian) {}
explicit BytesView(ArrayRefT Ref, const DataLayout &DL)
- : Bytes(Ref), IsLittleEndian(DL.isLittleEndian()) {}
+ : BytesView(Ref, DL.isLittleEndian()) {}
auto &operator[](uint32_t Index) {
return Bytes[IsLittleEndian ? Index : Bytes.size() - 1 - Index];
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index db0a5901eb526..41cac84954ae8 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -287,7 +287,8 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
if (Ty->isPointerTy())
return Pointer::null(Ty->getPointerAddressSpace(), Ctx.getDataLayout());
if (Ty->isByteTy())
- return ByteValue::zero(Ty->getByteBitWidth());
+ return ByteValue::zero(Ty->getByteBitWidth(),
+ Ctx.getDataLayout().isLittleEndian());
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(
@@ -313,10 +314,18 @@ AnyValue AnyValue::getVectorSplat(const AnyValue &Scalar, size_t NumElements) {
return AnyValue(std::vector<AnyValue>(NumElements, Scalar));
}
-ByteValue::ByteValue(const APInt &V, bool IsLittleEndian) {}
-ByteValue ByteValue::zero(uint32_t BitWidth) {
+ByteValue::ByteValue(const APInt &V, bool IsLittleEndian)
+ : BitWidth(V.getBitWidth()), IsLittleEndian(IsLittleEndian) {
+ Val.resize(divideCeil(BitWidth, 8));
+ MutableBytesView View(Val, IsLittleEndian);
+ for (uint8_t I = 0; I < BitWidth; I += 8)
+ View[I / 8] = Byte::concrete(static_cast<uint8_t>(
+ V.extractBitsAsZExtValue(std::min(BitWidth - I, 8), I)));
+}
+ByteValue ByteValue::zero(uint32_t BitWidth, bool IsLittleEndian) {
return ByteValue(
- BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)));
+ BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)),
+ IsLittleEndian);
}
ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
@@ -329,22 +338,26 @@ ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
else
Val.front().zeroBits(Mask);
}
- return ByteValue(BitWidth, std::move(Val));
+ return ByteValue(BitWidth, std::move(Val), IsLittleEndian);
}
void ByteValue::print(raw_ostream &OS) const {
OS << 'b' << BitWidth << ' ';
for (const Byte &V : Val) {
+ bool IsFullByte = (BitWidth & 7) == 0 ||
+ (IsLittleEndian ? &Val.back() : &Val.front()) != &V;
// Try to print a byte in short form
- if (V.ConcreteMask == 255 && V.TagMask == 0) {
+ if (IsFullByte && V.ConcreteMask == 255 && V.TagMask == 0) {
// Concrete value without provenance.
OS << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
- } else if (V.ConcreteMask == 0 && (V.Value == 0 || V.Value == 255)) {
+ } else if (IsFullByte && V.ConcreteMask == 0 &&
+ (V.Value == 0 || V.Value == 255)) {
// Poison/undef bytes.
OS << (V.Value == 0 ? "!!" : "??");
} else {
- for (uint32_t I = 0; I != 8; ++I) {
- uint32_t Mask = 1U << (8 - I);
+ uint32_t BitEnd = IsFullByte ? 8 : BitWidth & 7;
+ for (uint32_t I = 0; I != BitEnd; ++I) {
+ uint32_t Mask = 1U << (BitEnd - 1 - I);
if (V.ConcreteMask & Mask)
OS << (V.Value & Mask ? '1' : '0');
else
@@ -353,8 +366,8 @@ void ByteValue::print(raw_ostream &OS) const {
if (uint32_t TagMask = V.ConcreteMask & V.TagMask) {
// Print tags if available.
OS << '(';
- for (uint32_t I = 0; I != 8; ++I) {
- uint32_t Mask = 1U << (8 - I);
+ for (uint32_t I = 0; I != BitEnd; ++I) {
+ uint32_t Mask = 1U << (BitEnd - 1 - I);
if (TagMask & Mask)
OS << (V.TagValue & Mask ? '1' : '0');
else
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index dc15a491d20c0..b5ea59e034454 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -43,6 +43,7 @@ struct Byte {
void zeroBits(uint8_t Mask) {
ConcreteMask |= Mask;
Value &= ~Mask;
+ TagMask &= ~Mask;
}
void poisonBits(uint8_t Mask) {
@@ -103,6 +104,12 @@ struct Byte {
static_cast<uint8_t>(TagMask << Shift),
static_cast<uint8_t>(TagValue << Shift)};
}
+
+ bool AreHighBitsZExtd(uint8_t BitsFrom) const {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << BitsFrom);
+ return (ConcreteMask & Mask) == Mask && (Value & Mask) == 0 &&
+ (TagMask & Mask) == 0;
+ }
};
enum class StorageKind {
@@ -181,24 +188,41 @@ class Pointer {
/// Represents a scalar byte value. If the value is not byte-sized, the high
/// bits are zero-padded.
class ByteValue {
+ // The byte order is endianness-dependent.
std::vector<Byte> Val;
- uint32_t BitWidth;
+ uint32_t BitWidth : 31;
+ uint32_t IsLittleEndian : 1;
public:
ByteValue(const APInt &V, bool IsLittleEndian);
- /// The caller is responsible to zero high bits for non-byte-sized values.
- ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val)
- : Val(Val), BitWidth(BitWidth) {}
- /// The caller is responsible to zero high bits for non-byte-sized values.
- ByteValue(uint32_t BitWidth, std::vector<Byte> Val)
- : Val(std::move(Val)), BitWidth(BitWidth) {}
+ ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val, bool IsLittleEndian,
+ bool ImplicitClearHighBits = false)
+ : ByteValue(BitWidth, std::vector<Byte>(Val), IsLittleEndian,
+ ImplicitClearHighBits) {}
+ ByteValue(uint32_t BitWidth, std::vector<Byte> Val, bool IsLittleEndian,
+ bool ImplicitClearHighBits = false)
+ : Val(std::move(Val)), BitWidth(BitWidth),
+ IsLittleEndian(IsLittleEndian) {
+ if (ImplicitClearHighBits && (BitWidth & 7) != 0) {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+ if (IsLittleEndian)
+ this->Val.back().zeroBits(Mask);
+ else
+ this->Val.front().zeroBits(Mask);
+ }
+ assert(((BitWidth & 7) == 0 ||
+ ((IsLittleEndian ? this->Val.back() : this->Val.front())
+ .AreHighBitsZExtd(BitWidth & 7))) &&
+ "The caller is responsible to zero high bits for non-byte-sized "
+ "values.");
+ }
ByteValue(const ByteValue &) = default;
ByteValue(ByteValue &&) = default;
ByteValue &operator=(const ByteValue &) = default;
ByteValue &operator=(ByteValue &&) = default;
~ByteValue() = default;
- static ByteValue zero(uint32_t BitWidth);
+ static ByteValue zero(uint32_t BitWidth, bool IsLittleEndian);
static ByteValue poison(uint32_t BitWidth, bool IsLittleEndian);
uint32_t getBitWidth() const { return BitWidth; }
>From 828a21c0f2b06d82e1633b9745d71984f93f6b40 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:47:12 +0800
Subject: [PATCH 03/11] [llubi] Add tests. NFC.
---
llvm/test/tools/llubi/loadstore_be.ll | 65 +++++++++++++++++++++++++++
llvm/test/tools/llubi/loadstore_le.ll | 65 +++++++++++++++++++++++++++
2 files changed, 130 insertions(+)
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 8f2e0815f88fe..c35f536336b16 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -113,12 +113,47 @@ define void @main() {
%alloc_ptr = alloca ptr
store ptr %alloc_ptr, ptr %alloc_ptr
+ %bytes = load b64, ptr %alloc_ptr
; It should recover the provenance.
%ptr_with_provenance = load ptr, ptr %alloc_ptr
%addr_bits = load i8, ptr %alloc_ptr
store i8 %addr_bits, ptr %alloc_ptr
; The first byte is tainted. We cannot recover the provenance.
%ptr_without_provenance = load ptr, ptr %alloc_ptr
+ store b64 %bytes, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance2 = load ptr, ptr %alloc_ptr
+ %bv64b1 = bitcast b64 %bytes to <64 x b1>
+ %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse(<64 x b1> %bv64b1)
+ store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr
+ ; The bit order is incorrect. We cannot recover the provenance.
+ %ptr_without_provenance2 = load ptr, ptr %alloc_ptr
+ store b64 %bytes, ptr %alloc_ptr
+ store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr
+ %bytes_tainted = load b64, ptr %alloc_ptr
+ ; The first byte is tainted. We cannot recover the provenance.
+ %ptr_without_provenance3 = load ptr, ptr %alloc_ptr
+ %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8>
+ %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7
+ %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1>
+ %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse(<8 x b1> %first_byte_with_provenance_reversed_v8b1)
+ store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr
+ %bytes_recovered = load b64, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance3 = load ptr, ptr %alloc_ptr
+
+ %alloc_byte = alloca b32
+ store b8 127, ptr %alloc_byte
+ %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1
+ store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte
+ %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2
+ store b8 poison, ptr %gep_third_byte
+ %bytes_mixed = load b32, ptr %alloc_byte
+ store b32 u0xDEADBEEF, ptr %alloc_byte
+ %bytes_endianness = load b32, ptr %alloc_byte
+ %bytes_non_pow2 = load b28, ptr %alloc_byte
+ store b28 u0xEADBEEF, ptr %alloc_byte
+ %bytes_zextd = load b32, ptr %alloc_byte
ret void
}
@@ -204,9 +239,39 @@ define void @main() {
; CHECK-NEXT: %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
; CHECK-NEXT: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %bytes = load b64, ptr %alloc_ptr, align 8 => b64 00000000(00100110) 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100)
; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 0
; CHECK-NEXT: store i8 %addr_bits, ptr %alloc_ptr, align 1
; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT: store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %ptr_with_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %bv64b1 = bitcast b64 %bytes to <64 x b1> => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse.v64b1(<64 x b1> %bv64b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %ptr_without_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x900000000000000 [nullary]
+; CHECK-NEXT: store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT: store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %bytes_tainted = load b64, ptr %alloc_ptr, align 8 => b64 0000!!!! 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100)
+; CHECK-NEXT: %ptr_without_provenance3 = load ptr, ptr %alloc_ptr, align 8 => poison
+; CHECK-NEXT: %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8> => { b8 00001001(00110100) , b8 00000000(00010000) , b8 00000000(10101001) , b8 00000000(01010001) , b8 00000000(10011100) , b8 00000000(00111100) , b8 00000000(10001110) , b8 00000000(01100100) }
+; CHECK-NEXT: %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7 => b8 00000000(01100100)
+; CHECK-NEXT: %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1> => { b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse.v8b1(<8 x b1> %first_byte_with_provenance_reversed_v8b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) }
+; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %bytes_recovered = load b64, ptr %alloc_ptr, align 8 => b64 00000000(00100110) 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100)
+; CHECK-NEXT: %ptr_with_provenance3 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %alloc_byte = alloca b32, align 4 => ptr 0x98 [alloc_byte]
+; CHECK-NEXT: store b8 127, ptr %alloc_byte, align 1
+; CHECK-NEXT: %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1 => ptr 0x99 [alloc_byte + 1]
+; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte, align 1
+; CHECK-NEXT: %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0x9A [alloc_byte + 2]
+; CHECK-NEXT: store b8 poison, ptr %gep_third_byte, align 1
+; CHECK-NEXT: %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 7F 00000000(00100110) !! ??
+; CHECK-NEXT: store b32 -559038737, ptr %alloc_byte, align 4
+; CHECK-NEXT: %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 DE AD BE EF
+; CHECK-NEXT: %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 1110 AD BE EF
+; CHECK-NEXT: store b28 -22167825, ptr %alloc_byte, align 4
+; CHECK-NEXT: %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 0E AD BE EF
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index 3caf02c3e6eaa..1f86088c4a2ec 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -114,12 +114,47 @@ define void @main() {
%alloc_ptr = alloca ptr
store ptr %alloc_ptr, ptr %alloc_ptr
+ %bytes = load b64, ptr %alloc_ptr
; It should recover the provenance.
%ptr_with_provenance = load ptr, ptr %alloc_ptr
%addr_bits = load i8, ptr %alloc_ptr
store i8 %addr_bits, ptr %alloc_ptr
; The first byte is tainted. We cannot recover the provenance.
%ptr_without_provenance = load ptr, ptr %alloc_ptr
+ store b64 %bytes, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance2 = load ptr, ptr %alloc_ptr
+ %bv64b1 = bitcast b64 %bytes to <64 x b1>
+ %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse(<64 x b1> %bv64b1)
+ store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr
+ ; The bit order is incorrect. We cannot recover the provenance.
+ %ptr_without_provenance2 = load ptr, ptr %alloc_ptr
+ store b64 %bytes, ptr %alloc_ptr
+ store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr
+ %bytes_tainted = load b64, ptr %alloc_ptr
+ ; The first byte is tainted. We cannot recover the provenance.
+ %ptr_without_provenance3 = load ptr, ptr %alloc_ptr
+ %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8>
+ %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7
+ %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1>
+ %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse(<8 x b1> %first_byte_with_provenance_reversed_v8b1)
+ store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr
+ %bytes_recovered = load b64, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance3 = load ptr, ptr %alloc_ptr
+
+ %alloc_byte = alloca b32
+ store b8 127, ptr %alloc_byte
+ %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1
+ store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte
+ %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2
+ store b8 poison, ptr %gep_third_byte
+ %bytes_mixed = load b32, ptr %alloc_byte
+ store b32 u0xDEADBEEF, ptr %alloc_byte
+ %bytes_endianness = load b32, ptr %alloc_byte
+ %bytes_non_pow2 = load b28, ptr %alloc_byte
+ store b28 u0xEADBEEF, ptr %alloc_byte
+ %bytes_zextd = load b32, ptr %alloc_byte
ret void
}
@@ -206,9 +241,39 @@ define void @main() {
; CHECK-NEXT: %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
; CHECK-NEXT: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %bytes = load b64, ptr %alloc_ptr, align 8 => b64 10010000(00101100) 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110)
; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -112
; CHECK-NEXT: store i8 %addr_bits, ptr %alloc_ptr, align 1
; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT: store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %ptr_with_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %bv64b1 = bitcast b64 %bytes to <64 x b1> => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse.v64b1(<64 x b1> %bv64b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %ptr_without_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x900000000000000 [nullary]
+; CHECK-NEXT: store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT: store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %bytes_tainted = load b64, ptr %alloc_ptr, align 8 => b64 !!!!0000 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110)
+; CHECK-NEXT: %ptr_without_provenance3 = load ptr, ptr %alloc_ptr, align 8 => poison
+; CHECK-NEXT: %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8> => { b8 00000000(01100100) , b8 00000000(10001110) , b8 00000000(00111100) , b8 00000000(10011100) , b8 00000000(01010001) , b8 00000000(10101001) , b8 00000000(00010000) , b8 00001001(00110100) }
+; CHECK-NEXT: %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7 => b8 00001001(00110100)
+; CHECK-NEXT: %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1> => { b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse.v8b1(<8 x b1> %first_byte_with_provenance_reversed_v8b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0) }
+; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %bytes_recovered = load b64, ptr %alloc_ptr, align 8 => b64 10010000(00101100) 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110)
+; CHECK-NEXT: %ptr_with_provenance3 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %alloc_byte = alloca b32, align 4 => ptr 0x98 [alloc_byte]
+; CHECK-NEXT: store b8 127, ptr %alloc_byte, align 1
+; CHECK-NEXT: %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1 => ptr 0x99 [alloc_byte + 1]
+; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte, align 1
+; CHECK-NEXT: %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0x9A [alloc_byte + 2]
+; CHECK-NEXT: store b8 poison, ptr %gep_third_byte, align 1
+; CHECK-NEXT: %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 7F 10010000(00101100) !! ??
+; CHECK-NEXT: store b32 -559038737, ptr %alloc_byte, align 4
+; CHECK-NEXT: %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 EF BE AD DE
+; CHECK-NEXT: %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 EF BE AD 1110
+; CHECK-NEXT: store b28 -22167825, ptr %alloc_byte, align 4
+; CHECK-NEXT: %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 EF BE AD 0E
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
>From 852b079dcf1e619d25e2e765d044f5ac0dbee906 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:56:44 +0800
Subject: [PATCH 04/11] [llubi] Add tests with !noundef check. NFC.
---
llvm/test/tools/llubi/load_noundef_ub_undef.ll | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/llvm/test/tools/llubi/load_noundef_ub_undef.ll b/llvm/test/tools/llubi/load_noundef_ub_undef.ll
index bb9f5c79cf260..fdc94b602c7a2 100644
--- a/llvm/test/tools/llubi/load_noundef_ub_undef.ll
+++ b/llvm/test/tools/llubi/load_noundef_ub_undef.ll
@@ -1,5 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/i32/b32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
define void @main() {
%p = alloca i32
@@ -7,8 +8,8 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: %p = alloca i32, align 4 => ptr 0x8 [p]
+; CHECK-NEXT: %p = alloca {{i|b}}32, align 4 => ptr 0x8 [p]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 %res = load i32, ptr %p, align 4, !noundef !0 at @main <stdin>:6
+; CHECK-NEXT: #0 %res = load {{i|b}}32, ptr %p, align 4, !noundef !0 at @main <stdin>:7
; CHECK-NEXT: Immediate UB detected: The value loaded contains undefined bits.
; CHECK-NEXT: error: Execution of function 'main' failed.
>From 76578e2365b84344df80f26b441d31e944c0dc4d Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 09:11:17 +0800
Subject: [PATCH 05/11] [llubi] Handle noundef attribute for byte types
---
llvm/test/tools/llubi/attribute_noundef_ub.ll | 5 +++--
llvm/test/tools/llubi/attributes.ll | 11 +++++++++++
llvm/tools/llubi/lib/Interpreter.cpp | 14 ++++++++++++--
3 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/llvm/test/tools/llubi/attribute_noundef_ub.ll b/llvm/test/tools/llubi/attribute_noundef_ub.ll
index a8a6a1984398b..f2bd1d599b7b6 100644
--- a/llvm/test/tools/llubi/attribute_noundef_ub.ll
+++ b/llvm/test/tools/llubi/attribute_noundef_ub.ll
@@ -1,5 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/i32/b32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
define void @callee(i32 noundef %x) {
ret void
@@ -11,6 +12,6 @@ define void @main() {
}
; CHECK: Entering function: main
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 call void @callee(i32 poison) at @main <stdin>:9
-; CHECK-NEXT: Immediate UB detected: The value poison violates noundef attribute.
+; CHECK-NEXT: #0 call void @callee({{i|b}}32 poison) at @main <stdin>:10
+; CHECK-NEXT: Immediate UB detected: The value {{poison|b32 !! !! !! !!}} violates noundef attribute.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
index cc8fcf62a46f9..971063e55b904 100644
--- a/llvm/test/tools/llubi/attributes.ll
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -47,6 +47,10 @@ define noundef i32 @identity_noundef(i32 noundef %x) {
ret i32 %x
}
+define noundef b7 @identity_noundef_byte(b7 noundef %x) {
+ ret b7 %x
+}
+
define noundef {i32, <2 x i32>, [2 x i32]} @identity_noundef_agg({i32, <2 x i32>, [2 x i32]} noundef %x) {
ret {i32, <2 x i32>, [2 x i32]} %x
}
@@ -119,6 +123,8 @@ define void @main() {
%fmt_n_out = alloca [6 x i8]
store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out
%res = call range(i32 0, 15) noundef i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6)
+
+ %noundef_byte = call b7 @identity_noundef_byte(b7 -1)
ret void
}
; CHECK: Entering function: main
@@ -324,5 +330,10 @@ define void @main() {
; CHECK-NEXT: store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
; CHECK-NEXT: N=6
; CHECK-NEXT: %res = call noundef range(i32 0, 15) i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6) => i32 4
+; CHECK-NEXT: Entering function: identity_noundef_byte
+; CHECK-NEXT: b7 %x = b7 1111111
+; CHECK-NEXT: ret b7 %x
+; CHECK-NEXT: Exiting function: identity_noundef_byte
+; CHECK-NEXT: %noundef_byte = call b7 @identity_noundef_byte(b7 -1) => b7 1111111
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 81cc968535a2f..ab31bdd5ec166 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -112,8 +112,18 @@ static void applyAlignAttr(AnyValue &V, Align Alignment) {
static bool violatesNoUndefAttr(AnyValue &V) {
bool ContainsPoison = false;
- forEachScalarValue(
- V, [&](AnyValue &Scalar) { ContainsPoison |= Scalar.isPoison(); });
+ forEachScalarValue(V, [&](AnyValue &Scalar) {
+ if (Scalar.isPoison()) {
+ ContainsPoison = true;
+ return;
+ }
+ if (Scalar.isByte() && !ContainsPoison) {
+ // For non-byte-sized values, high bits are always zeroed out.
+ ContainsPoison = any_of(Scalar.asByte().bytes(), [](const Byte &V) {
+ return V.ConcreteMask != 255;
+ });
+ }
+ });
return ContainsPoison;
}
>From ea43651d23b337aabca701ef3f21ad03a532e9f6 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 09:17:06 +0800
Subject: [PATCH 06/11] [llubi] Use ImplicitClearHighBits in
`ByteValue::poison`
---
llvm/tools/llubi/lib/Value.cpp | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 41cac84954ae8..294f36a370398 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -329,16 +329,9 @@ ByteValue ByteValue::zero(uint32_t BitWidth, bool IsLittleEndian) {
}
ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
- std::vector<Byte> Val(divideCeil(BitWidth, 8), Byte::poison());
- // Zero high-bits if it is not a byte-sized value.
- if (BitWidth & 7) {
- uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
- if (IsLittleEndian)
- Val.back().zeroBits(Mask);
- else
- Val.front().zeroBits(Mask);
- }
- return ByteValue(BitWidth, std::move(Val), IsLittleEndian);
+ return ByteValue(BitWidth,
+ std::vector<Byte>(divideCeil(BitWidth, 8), Byte::poison()),
+ IsLittleEndian, /*ImplicitClearHighBits=*/true);
}
void ByteValue::print(raw_ostream &OS) const {
>From 23d4a0136bd0b8e0cbf3d7d3a38d7dde00838c17 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 21:02:21 +0800
Subject: [PATCH 07/11] [llubi] Use uint32_t
---
llvm/tools/llubi/lib/Value.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 294f36a370398..1f1d433c79003 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -318,7 +318,7 @@ ByteValue::ByteValue(const APInt &V, bool IsLittleEndian)
: BitWidth(V.getBitWidth()), IsLittleEndian(IsLittleEndian) {
Val.resize(divideCeil(BitWidth, 8));
MutableBytesView View(Val, IsLittleEndian);
- for (uint8_t I = 0; I < BitWidth; I += 8)
+ for (uint32_t I = 0; I < BitWidth; I += 8)
View[I / 8] = Byte::concrete(static_cast<uint8_t>(
V.extractBitsAsZExtValue(std::min(BitWidth - I, 8), I)));
}
>From 6737cf878be2fc79554ce9ca93a022c3e2014b11 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 22:14:10 +0800
Subject: [PATCH 08/11] [llubi] Fix compilation error.
---
llvm/tools/llubi/lib/Value.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 1f1d433c79003..11fd375f75575 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -320,7 +320,7 @@ ByteValue::ByteValue(const APInt &V, bool IsLittleEndian)
MutableBytesView View(Val, IsLittleEndian);
for (uint32_t I = 0; I < BitWidth; I += 8)
View[I / 8] = Byte::concrete(static_cast<uint8_t>(
- V.extractBitsAsZExtValue(std::min(BitWidth - I, 8), I)));
+ V.extractBitsAsZExtValue(std::min(BitWidth - I, 8U), I)));
}
ByteValue ByteValue::zero(uint32_t BitWidth, bool IsLittleEndian) {
return ByteValue(
>From 7c1cd834ed104fc66fc45ba1b4934b9919d0ef3b Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 2 Jun 2026 01:08:44 +0800
Subject: [PATCH 09/11] [llubi] Address review comments.
---
llvm/test/tools/llubi/attribute_noundef_ub.ll | 2 +-
llvm/test/tools/llubi/loadstore_be.ll | 12 ++++++++----
llvm/test/tools/llubi/loadstore_le.ll | 12 ++++++++----
llvm/tools/llubi/lib/Context.cpp | 8 ++++++--
llvm/tools/llubi/lib/Value.cpp | 4 ++--
llvm/tools/llubi/lib/Value.h | 2 ++
6 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/llvm/test/tools/llubi/attribute_noundef_ub.ll b/llvm/test/tools/llubi/attribute_noundef_ub.ll
index f2bd1d599b7b6..cab5e22df8d0a 100644
--- a/llvm/test/tools/llubi/attribute_noundef_ub.ll
+++ b/llvm/test/tools/llubi/attribute_noundef_ub.ll
@@ -13,5 +13,5 @@ define void @main() {
; CHECK: Entering function: main
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 call void @callee({{i|b}}32 poison) at @main <stdin>:10
-; CHECK-NEXT: Immediate UB detected: The value {{poison|b32 !! !! !! !!}} violates noundef attribute.
+; CHECK-NEXT: Immediate UB detected: The value {{poison|b32 0x!! 0x!! 0x!! 0x!!}} violates noundef attribute.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index c35f536336b16..f4089b08530b8 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -154,6 +154,8 @@ define void @main() {
%bytes_non_pow2 = load b28, ptr %alloc_byte
store b28 u0xEADBEEF, ptr %alloc_byte
%bytes_zextd = load b32, ptr %alloc_byte
+ store b8 255, ptr %alloc_byte
+ %bytes_zextd_poison = load b7, ptr %alloc_byte
ret void
}
@@ -267,11 +269,13 @@ define void @main() {
; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte, align 1
; CHECK-NEXT: %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0x9A [alloc_byte + 2]
; CHECK-NEXT: store b8 poison, ptr %gep_third_byte, align 1
-; CHECK-NEXT: %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 7F 00000000(00100110) !! ??
+; CHECK-NEXT: %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 0x7F 00000000(00100110) 0x!! 0x??
; CHECK-NEXT: store b32 -559038737, ptr %alloc_byte, align 4
-; CHECK-NEXT: %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 DE AD BE EF
-; CHECK-NEXT: %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 1110 AD BE EF
+; CHECK-NEXT: %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 0xDE 0xAD 0xBE 0xEF
+; CHECK-NEXT: %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 !!!! 0x!! 0x!! 0x!!
; CHECK-NEXT: store b28 -22167825, ptr %alloc_byte, align 4
-; CHECK-NEXT: %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 0E AD BE EF
+; CHECK-NEXT: %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 0x0E 0xAD 0xBE 0xEF
+; CHECK-NEXT: store b8 -1, ptr %alloc_byte, align 1
+; CHECK-NEXT: %bytes_zextd_poison = load b7, ptr %alloc_byte, align 1 => b7 !!!!!!!
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index 1f86088c4a2ec..32d5cfce65650 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -155,6 +155,8 @@ define void @main() {
%bytes_non_pow2 = load b28, ptr %alloc_byte
store b28 u0xEADBEEF, ptr %alloc_byte
%bytes_zextd = load b32, ptr %alloc_byte
+ store b8 255, ptr %alloc_byte
+ %bytes_zextd_poison = load b7, ptr %alloc_byte
ret void
}
@@ -269,11 +271,13 @@ define void @main() {
; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte, align 1
; CHECK-NEXT: %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0x9A [alloc_byte + 2]
; CHECK-NEXT: store b8 poison, ptr %gep_third_byte, align 1
-; CHECK-NEXT: %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 7F 10010000(00101100) !! ??
+; CHECK-NEXT: %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 0x7F 10010000(00101100) 0x!! 0x??
; CHECK-NEXT: store b32 -559038737, ptr %alloc_byte, align 4
-; CHECK-NEXT: %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 EF BE AD DE
-; CHECK-NEXT: %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 EF BE AD 1110
+; CHECK-NEXT: %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 0xEF 0xBE 0xAD 0xDE
+; CHECK-NEXT: %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 0x!! 0x!! 0x!! !!!!
; CHECK-NEXT: store b28 -22167825, ptr %alloc_byte, align 4
-; CHECK-NEXT: %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 EF BE AD 0E
+; CHECK-NEXT: %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 0xEF 0xBE 0xAD 0x0E
+; CHECK-NEXT: store b8 -1, ptr %alloc_byte, align 1
+; CHECK-NEXT: %bytes_zextd_poison = load b7, ptr %alloc_byte, align 1 => b7 !!!!!!!
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 37366b886a2e1..f678f25a933c2 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -321,8 +321,12 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
}
}
unsigned BitWidth = Ty->getByteBitWidth();
- ByteValue Res(BitWidth, Bytes, DL.isLittleEndian(),
- /*ImplicitClearHighBits=*/true);
+ if (BitWidth & 7) {
+ if (!(DL.isLittleEndian() ? Bytes.back() : Bytes.front())
+ .AreHighBitsZExtd(BitWidth & 7))
+ return ByteValue::poison(BitWidth, DL.isLittleEndian());
+ }
+ ByteValue Res(BitWidth, Bytes, DL.isLittleEndian());
return AnyValue(std::move(Res));
}
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 11fd375f75575..c890a2eda53b5 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -342,11 +342,11 @@ void ByteValue::print(raw_ostream &OS) const {
// Try to print a byte in short form
if (IsFullByte && V.ConcreteMask == 255 && V.TagMask == 0) {
// Concrete value without provenance.
- OS << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
+ OS << "0x" << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
} else if (IsFullByte && V.ConcreteMask == 0 &&
(V.Value == 0 || V.Value == 255)) {
// Poison/undef bytes.
- OS << (V.Value == 0 ? "!!" : "??");
+ OS << (V.Value == 0 ? "0x!!" : "0x??");
} else {
uint32_t BitEnd = IsFullByte ? 8 : BitWidth & 7;
for (uint32_t I = 0; I != BitEnd; ++I) {
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index b5ea59e034454..b9227d8597ec6 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -289,6 +289,8 @@ class [[nodiscard]] AnyValue {
return Ty->isFloatingPointTy();
case StorageKind::Pointer:
return Ty->isPointerTy();
+ case StorageKind::Byte:
+ return Ty->isByteTy();
// We don't check elements recursively.
case StorageKind::Aggregate:
return Ty->isAggregateType() || Ty->isVectorTy();
>From 09a99c0d575349c3cc6dbb32177565af185bca09 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 2 Jun 2026 01:14:42 +0800
Subject: [PATCH 10/11] [llubi] Add freeze tests. NFC.
---
llvm/test/tools/llubi/freeze.ll | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/llvm/test/tools/llubi/freeze.ll b/llvm/test/tools/llubi/freeze.ll
index c63db99b08084..6140136509c6b 100644
--- a/llvm/test/tools/llubi/freeze.ll
+++ b/llvm/test/tools/llubi/freeze.ll
@@ -16,6 +16,15 @@ define void @main() {
%arr_freeze = freeze [2 x i32] [i32 10, i32 poison]
%struct_freeze = freeze { i32, float } { i32 10, float poison }
%struct_freeze_nested = freeze { i32, { float, ptr } } { i32 poison, { float, ptr } { float 10.0, ptr poison } }
+
+ %alloca = alloca ptr
+ %byte_undef = load b8, ptr %alloca
+ store ptr %alloca, ptr %alloca
+ %byte_with_provenance = load b8, ptr %alloca
+ %vec_bytes1 = insertelement <4 x b8> <b8 0, b8 poison, b8 poison, b8 poison>, b8 %byte_undef, i32 2
+ %vec_bytes2 = insertelement <4 x b8> %vec_bytes1, b8 %byte_with_provenance, i32 3
+ %bytes = bitcast <4 x b8> %vec_bytes2 to b32
+ %bytes_freeze = freeze b32 %bytes
ret void
}
; CHECK: Entering function: main
@@ -30,5 +39,13 @@ define void @main() {
; CHECK-NEXT: %arr_freeze = freeze [2 x i32] [i32 10, i32 poison] => { i32 10, i32 -44045842 }
; CHECK-NEXT: %struct_freeze = freeze { i32, float } { i32 10, float poison } => { i32 10, float 0x042AE12F }
; CHECK-NEXT: %struct_freeze_nested = freeze { i32, { float, ptr } } { i32 poison, { float, ptr } { float 1.000000e+01, ptr poison } } => { i32 1166165736, { float 1.000000e+01, ptr 0xD79E62976F604366 [nullary] } }
+; CHECK-NEXT: %alloca = alloca ptr, align 8 => ptr 0x8 [alloca]
+; CHECK-NEXT: %byte_undef = load b8, ptr %alloca, align 1 => b8 0x??
+; CHECK-NEXT: store ptr %alloca, ptr %alloca, align 8
+; CHECK-NEXT: %byte_with_provenance = load b8, ptr %alloca, align 1 => b8 00001000(00011001)
+; CHECK-NEXT: %vec_bytes1 = insertelement <4 x b8> <b8 0, b8 poison, b8 poison, b8 poison>, b8 %byte_undef, i32 2 => { b8 0x00 , b8 0x!! , b8 0x?? , b8 0x!! }
+; CHECK-NEXT: %vec_bytes2 = insertelement <4 x b8> %vec_bytes1, b8 %byte_with_provenance, i32 3 => { b8 0x00 , b8 0x!! , b8 0x?? , b8 00001000(00011001) }
+; CHECK-NEXT: %bytes = bitcast <4 x b8> %vec_bytes2 to b32 => b32 0x00 0x!! 0x?? 00001000(00011001)
+; CHECK-NEXT: %bytes_freeze = freeze b32 %bytes => b32 0x00 0x13 0xF8 00001000(00011001)
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
>From acc49f8723b664fc0817ca844949f8a5126aebd4 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 2 Jun 2026 22:41:14 +0800
Subject: [PATCH 11/11] [llubi] Add global tests with byte type
---
llvm/test/tools/llubi/global.ll | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/llvm/test/tools/llubi/global.ll b/llvm/test/tools/llubi/global.ll
index 3cff71b21769f..c4f8c734a1a6b 100644
--- a/llvm/test/tools/llubi/global.ll
+++ b/llvm/test/tools/llubi/global.ll
@@ -1,8 +1,10 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
target datalayout = "e-p:64:64:64-i32:32:32"
@value = global i32 41
+ at value_byte = global b8 1
@value_ptr = global ptr @value
@aggregate = global { i32, [2 x i8] } { i32 7, [2 x i8] c"hi" }
@@ -12,6 +14,7 @@ define void @main() {
%ptr = load ptr, ptr @value_ptr
%updated = load i32, ptr %ptr
%aggregate_value = load { i32, [2 x i8] }, ptr @aggregate
+ %val = load i8, ptr @value_byte
ret void
}
@@ -21,5 +24,6 @@ define void @main() {
; CHECK-NEXT: %ptr = load ptr, ptr @value_ptr, align 8 => ptr 0x8 [@value]
; CHECK-NEXT: %updated = load i32, ptr %ptr, align 4 => i32 42
; CHECK-NEXT: %aggregate_value = load { i32, [2 x i8] }, ptr @aggregate, align 4 => { i32 7, { i8 104, i8 105 } }
+; CHECK-NEXT: %val = load i8, ptr @value_byte, align 1 => i8 1
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
More information about the llvm-commits
mailing list