[llvm] [llubi] Add support for byte types (PR #200672)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 1 06:02:45 PDT 2026
https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/200672
>From a3e9eec739fa6afd05a6da786ee2669365c7f26d Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 00:53:06 +0800
Subject: [PATCH 1/7] [llubi] Add support for byte types
---
llvm/tools/llubi/lib/Context.cpp | 103 +++++++++++++++++++++++++++++--
llvm/tools/llubi/lib/Context.h | 2 +
llvm/tools/llubi/lib/Value.cpp | 77 +++++++++++++++++++++++
llvm/tools/llubi/lib/Value.h | 58 ++++++++++++++++-
4 files changed, 235 insertions(+), 5 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 69cc52545adc6..a87d66fce1638 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -77,6 +77,14 @@ AnyValue Context::getConstantValueImpl(Constant *C) {
return CFP->getValue();
}
+ if (auto *CB = dyn_cast<ConstantByte>(C)) {
+ if (auto *VecTy = dyn_cast<VectorType>(CB->getType()))
+ return std::vector<AnyValue>(
+ getEVL(VecTy->getElementCount()),
+ AnyValue(ByteValue(CB->getValue(), DL.isLittleEndian())));
+ return ByteValue(CB->getValue(), DL.isLittleEndian());
+ }
+
if (auto *CDS = dyn_cast<ConstantDataSequential>(C)) {
std::vector<AnyValue> Elts;
Elts.reserve(CDS->getNumElements());
@@ -140,7 +148,13 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
uint32_t NumBitsToExtract = NewOffsetInBits - OffsetInBits;
uint32_t NumWords = APInt::getNumWords(NumBitsToExtract);
constexpr uint32_t WordBits = APInt::APINT_BITS_PER_WORD;
- SmallVector<APInt::WordType> RawBits(NumWords);
+ SmallVector<APInt::WordType> RawBits;
+ bool IsByteType = Ty->isByteTy();
+ std::vector<Byte> LogicalBytes;
+ if (IsByteType)
+ LogicalBytes.resize(divideCeil(NumBitsToExtract, 8));
+ else
+ RawBits.resize(NumWords);
bool IsTagValid = Ty->isPointerTy();
SmallVector<APInt::WordType> RawTagBits;
if (Ty->isPointerTy())
@@ -161,7 +175,8 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
uint32_t Mask = (1U << NumBitsInByte) - 1;
// If any of the bits in the byte is poison, the whole value is poison.
- if (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask) {
+ if (!IsByteType &&
+ (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask)) {
if (ContainsUndefinedBits)
*ContainsUndefinedBits = true;
OffsetInBits = NewOffsetInBits;
@@ -173,14 +188,20 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
if (ContainsUndefinedBits)
*ContainsUndefinedBits = true;
- if (getEffectiveUndefValueBehavior() ==
- UndefValueBehavior::NonDeterministic) {
+ if (!IsByteType && getEffectiveUndefValueBehavior() ==
+ UndefValueBehavior::NonDeterministic) {
// We don't use std::uniform_int_distribution here because it produces
// different results across different library implementations. Instead,
// we directly use the low bits from Rng.
RandomBits = static_cast<uint8_t>(Rng());
}
}
+
+ if (IsByteType) {
+ LogicalBytes[BitsStart / 8] = LogicalByte;
+ continue;
+ }
+
uint8_t ActualBits = ((LogicalByte.Value & LogicalByte.ConcreteMask) |
(RandomBits & ~LogicalByte.ConcreteMask)) &
Mask;
@@ -196,6 +217,19 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
}
}
}
+
+ if (IsByteType) {
+ assert(!CheckPaddingBits &&
+ "Non-vector-element cases should be handled by the fast path.");
+ if (NumBits & 7) {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << (NumBits & 7));
+ LogicalBytes.back().zeroBits(Mask);
+ }
+ if (DL.isBigEndian())
+ std::reverse(LogicalBytes.begin(), LogicalBytes.end());
+ return ByteValue(NumBits, std::move(LogicalBytes));
+ }
+
OffsetInBits = NewOffsetInBits;
APInt Bits(NumBitsToExtract, RawBits);
@@ -231,6 +265,25 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
if (Ty->isIntegerTy() || Ty->isFloatingPointTy() || Ty->isPointerTy())
return fromBytes(ConstBytesView(Bytes, DL), Ty, /*OffsetInBits=*/0,
/*CheckPaddingBits=*/true, ContainsUndefinedBits);
+ if (Ty->isByteTy()) {
+ if (ContainsUndefinedBits) {
+ for (const Byte &V : Bytes)
+ if (V.ConcreteMask != 255) {
+ *ContainsUndefinedBits = true;
+ break;
+ }
+ }
+ unsigned BitWidth = Ty->getByteBitWidth();
+ ByteValue Res(BitWidth, Bytes);
+ if (BitWidth & 7) {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+ if (DL.isLittleEndian())
+ Res.mutableBytes().back().zeroBits(Mask);
+ else
+ Res.mutableBytes().front().zeroBits(Mask);
+ }
+ return AnyValue(std::move(Res));
+ }
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
Type *ElemTy = VecTy->getElementType();
@@ -359,6 +412,27 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
WriteBits(NeedsPadding ? AddressBits.zext(NewOffsetInBits - OffsetInBits)
: AddressBits,
&Tag);
+ } else if (Ty->isByteTy()) {
+ assert(!PaddingBits &&
+ "Non-vector-element cases should be handled by the fast path.");
+ auto &ByteVal = Val.asByte();
+ ConstBytesView SrcBytes(ByteVal.bytes(), DL);
+ for (uint32_t I = 0, E = static_cast<uint32_t>(SrcBytes.size()); I != E;
+ ++I) {
+ uint32_t NumBitsInByte = std::min(8U, NumBits - I * 8);
+ uint32_t BitsStart = OffsetInBits + I * 8;
+ uint32_t BitsEnd = BitsStart + NumBitsInByte - 1;
+
+ Bytes[BitsStart / 8].writeByte(
+ static_cast<uint8_t>(((1U << NumBitsInByte) - 1) << (BitsStart % 8)),
+ SrcBytes[I].shl(BitsStart % 8));
+ // If it is a cross-byte access, write the remaining bits to the next
+ // byte.
+ if (((BitsStart ^ BitsEnd) & ~7) != 0)
+ Bytes[BitsEnd / 8].writeByte(
+ static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
+ SrcBytes[I].lshr(8 - (BitsStart % 8)));
+ }
} else {
llvm_unreachable("Unsupported scalar type.");
}
@@ -373,6 +447,13 @@ void Context::toBytes(const AnyValue &Val, Type *Ty,
/*PaddingBits=*/true);
return;
}
+ if (Ty->isByteTy()) {
+ auto &ByteVal = Val.asByte();
+ ArrayRef<Byte> SrcBytes = ByteVal.bytes();
+ assert(Bytes.size() == SrcBytes.size() && "Mismatched byte array.");
+ copy(SrcBytes, Bytes.begin());
+ return;
+ }
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
Type *ElemTy = VecTy->getElementType();
@@ -481,6 +562,20 @@ void Context::freeze(AnyValue &Val, Type *Ty) {
llvm_unreachable("Unsupported scalar type for poison value");
return;
}
+ if (Val.isByte()) {
+ for (Byte &V : Val.asMutableByte().mutableBytes()) {
+ if (V.ConcreteMask == 255)
+ continue;
+ uint8_t OldMask = V.ConcreteMask;
+ V.ConcreteMask = 255;
+ V.Value &= OldMask;
+ if (mayUseNonDeterminism())
+ V.Value |= (Rng() & 255) & ~OldMask;
+ V.TagMask &= OldMask;
+ V.TagValue &= OldMask;
+ }
+ return;
+ }
if (Val.isAggregate()) {
auto &SubVals = Val.asAggregate();
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 78d99f2cc5f13..8d6252649e510 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -190,6 +190,8 @@ template <typename ArrayRefT> class BytesView {
auto &operator[](uint32_t Index) {
return Bytes[IsLittleEndian ? Index : Bytes.size() - 1 - Index];
}
+
+ size_t size() const { return Bytes.size(); }
};
using ConstBytesView = BytesView<ArrayRef<Byte>>;
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index fe8e0f3cff330..9b2afa9926fe3 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -106,6 +106,9 @@ void AnyValue::print(raw_ostream &OS) const {
case StorageKind::Pointer:
PtrVal.print(OS);
break;
+ case StorageKind::Byte:
+ ByteVal.print(OS);
+ break;
case StorageKind::Poison:
OS << "poison";
break;
@@ -135,6 +138,9 @@ void AnyValue::destroy() {
case StorageKind::Pointer:
PtrVal.~Pointer();
break;
+ case StorageKind::Byte:
+ ByteVal.~ByteValue();
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -155,6 +161,9 @@ AnyValue::AnyValue(const AnyValue &Other) : Kind(Other.Kind) {
case StorageKind::Pointer:
new (&PtrVal) Pointer(Other.PtrVal);
break;
+ case StorageKind::Byte:
+ new (&ByteVal) ByteValue(Other.ByteVal);
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -174,6 +183,9 @@ AnyValue::AnyValue(AnyValue &&Other) : Kind(Other.Kind) {
case StorageKind::Pointer:
new (&PtrVal) Pointer(std::move(Other.PtrVal));
break;
+ case StorageKind::Byte:
+ new (&ByteVal) ByteValue(std::move(Other.ByteVal));
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -199,6 +211,9 @@ AnyValue &AnyValue::operator=(const AnyValue &Other) {
case StorageKind::Pointer:
new (&PtrVal) Pointer(Other.PtrVal);
break;
+ case StorageKind::Byte:
+ new (&ByteVal) ByteValue(Other.ByteVal);
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -224,6 +239,9 @@ AnyValue &AnyValue::operator=(AnyValue &&Other) {
case StorageKind::Pointer:
new (&PtrVal) Pointer(std::move(Other.PtrVal));
break;
+ case StorageKind::Byte:
+ new (&ByteVal) ByteValue(std::move(Other.ByteVal));
+ break;
case StorageKind::Poison:
case StorageKind::None:
break;
@@ -238,6 +256,9 @@ AnyValue &AnyValue::operator=(AnyValue &&Other) {
AnyValue AnyValue::getPoisonValue(Context &Ctx, Type *Ty) {
if (Ty->isFloatingPointTy() || Ty->isIntegerTy() || Ty->isPointerTy())
return AnyValue::poison();
+ if (Ty->isByteTy())
+ return ByteValue::poison(Ty->getByteBitWidth(),
+ Ctx.getDataLayout().isLittleEndian());
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(NumElements, AnyValue::poison()));
@@ -263,6 +284,8 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
return AnyValue(APFloat::getZero(Ty->getFltSemantics()));
if (Ty->isPointerTy())
return Pointer::null(Ty->getPointerAddressSpace(), Ctx.getDataLayout());
+ if (Ty->isByteTy())
+ return ByteValue::zero(Ty->getByteBitWidth());
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(
@@ -288,4 +311,58 @@ AnyValue AnyValue::getVectorSplat(const AnyValue &Scalar, size_t NumElements) {
return AnyValue(std::vector<AnyValue>(NumElements, Scalar));
}
+ByteValue::ByteValue(const APInt &V, bool IsLittleEndian) {}
+ByteValue ByteValue::zero(uint32_t BitWidth) {
+ return ByteValue(
+ BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)));
+}
+
+ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
+ std::vector<Byte> Val(divideCeil(BitWidth, 8), Byte::poison());
+ // Zero high-bits if it is not a byte-sized value.
+ if (BitWidth & 7) {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+ if (IsLittleEndian)
+ Val.back().zeroBits(Mask);
+ else
+ Val.front().zeroBits(Mask);
+ }
+ return ByteValue(BitWidth, std::move(Val));
+}
+
+void ByteValue::print(raw_ostream &OS) const {
+ OS << 'b' << BitWidth << ' ';
+ for (const Byte &V : Val) {
+ // Try to print a byte in short form
+ if (V.ConcreteMask == 255 && V.TagMask == 0) {
+ // Concrete value without provenance.
+ OS << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
+ } else if (V.ConcreteMask == 0 && (V.Value == 0 || V.Value == 255)) {
+ // Poison/undef bytes.
+ OS << (V.Value == 0 ? "!!" : "??");
+ } else {
+ for (uint32_t I = 0; I != 8; ++I) {
+ uint32_t Mask = 1U << (8 - I);
+ if (V.ConcreteMask & Mask)
+ OS << (V.Value & Mask ? '1' : '0');
+ else
+ OS << (V.Value & Mask ? '?' : '!');
+ }
+ if (uint32_t TagMask = V.ConcreteMask & V.TagMask) {
+ // Print tags if available.
+ OS << '(';
+ for (uint32_t I = 0; I != 8; ++I) {
+ uint32_t Mask = 1U << (8 - I);
+ if (TagMask & Mask)
+ OS << (V.TagValue & Mask ? '1' : '0');
+ else
+ OS << '!';
+ }
+ OS << ')';
+ }
+ }
+ OS << ' ';
+ }
+}
+
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index 60e656342f116..ea023aa6920e5 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -69,6 +69,13 @@ struct Byte {
TagValue = (TagValue & ~Mask) | (Tag & Mask);
}
+ void writeByte(uint8_t Mask, const Byte &RHS) {
+ ConcreteMask = (ConcreteMask & ~Mask) | (RHS.ConcreteMask & Mask);
+ Value = (Value & ~Mask) | (RHS.Value & Mask);
+ TagMask = (TagMask & ~Mask) | (RHS.TagMask & Mask);
+ TagValue = (TagValue & ~Mask) | (RHS.TagValue & Mask);
+ }
+
/// Returns a logical byte that is part of two adjacent bytes.
/// Example with ShAmt = 5:
/// | Low | High |
@@ -89,13 +96,20 @@ struct Byte {
static_cast<uint8_t>(TagMask >> Shift),
static_cast<uint8_t>(TagValue >> Shift)};
}
+
+ Byte shl(uint8_t Shift) const {
+ return Byte{static_cast<uint8_t>(ConcreteMask << Shift),
+ static_cast<uint8_t>(Value << Shift),
+ static_cast<uint8_t>(TagMask << Shift),
+ static_cast<uint8_t>(TagValue << Shift)};
+ }
};
-// TODO: Byte
enum class StorageKind {
Integer,
Float,
Pointer,
+ Byte,
Poison,
None, // Placeholder for void type
Aggregate, // Struct, Array or Vector
@@ -164,6 +178,35 @@ class Pointer {
MemoryObject *getMemoryObject() const { return Prov->getMemoryObject(); }
};
+/// Represents a scalar byte value. If the value is not byte-sized, the high
+/// bits are zero-padded.
+class ByteValue {
+ std::vector<Byte> Val;
+ uint32_t BitWidth;
+
+public:
+ ByteValue(const APInt &V, bool IsLittleEndian);
+ /// The caller is responsible to zero high bits for non-byte-sized values.
+ ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val)
+ : Val(Val), BitWidth(BitWidth) {}
+ /// The caller is responsible to zero high bits for non-byte-sized values.
+ ByteValue(uint32_t BitWidth, std::vector<Byte> Val)
+ : Val(std::move(Val)), BitWidth(BitWidth) {}
+ ByteValue(const ByteValue &) = default;
+ ByteValue(ByteValue &&) = default;
+ ByteValue &operator=(const ByteValue &) = default;
+ ByteValue &operator=(ByteValue &&) = default;
+ ~ByteValue() = default;
+
+ static ByteValue zero(uint32_t BitWidth);
+ static ByteValue poison(uint32_t BitWidth, bool IsLittleEndian);
+
+ uint32_t getBitWidth() const { return BitWidth; }
+ ArrayRef<Byte> bytes() const { return Val; }
+ MutableArrayRef<Byte> mutableBytes() { return Val; }
+ void print(raw_ostream &OS) const;
+};
+
// Value representation for actual values of LLVM values.
// We don't model undef values here (except for byte types).
class [[nodiscard]] AnyValue {
@@ -172,6 +215,7 @@ class [[nodiscard]] AnyValue {
APInt IntVal;
APFloat FloatVal;
Pointer PtrVal;
+ ByteValue ByteVal;
std::vector<AnyValue> AggVal;
};
@@ -184,6 +228,7 @@ class [[nodiscard]] AnyValue {
AnyValue(APInt Val) : Kind(StorageKind::Integer), IntVal(std::move(Val)) {}
AnyValue(APFloat Val) : Kind(StorageKind::Float), FloatVal(std::move(Val)) {}
AnyValue(Pointer Val) : Kind(StorageKind::Pointer), PtrVal(std::move(Val)) {}
+ AnyValue(ByteValue Val) : Kind(StorageKind::Byte), ByteVal(std::move(Val)) {}
AnyValue(std::vector<AnyValue> Val)
: Kind(StorageKind::Aggregate), AggVal(std::move(Val)) {}
AnyValue(const AnyValue &Other);
@@ -205,6 +250,7 @@ class [[nodiscard]] AnyValue {
bool isInteger() const { return Kind == StorageKind::Integer; }
bool isFloat() const { return Kind == StorageKind::Float; }
bool isPointer() const { return Kind == StorageKind::Pointer; }
+ bool isByte() const { return Kind == StorageKind::Byte; }
bool isAggregate() const { return Kind == StorageKind::Aggregate; }
const APInt &asInteger() const {
@@ -222,6 +268,16 @@ class [[nodiscard]] AnyValue {
return PtrVal;
}
+ const ByteValue &asByte() const {
+ assert(Kind == StorageKind::Byte && "Expect a byte value");
+ return ByteVal;
+ }
+
+ ByteValue &asMutableByte() {
+ assert(Kind == StorageKind::Byte && "Expect a byte value");
+ return ByteVal;
+ }
+
const std::vector<AnyValue> &asAggregate() const {
assert(Kind == StorageKind::Aggregate &&
"Expect an aggregate/vector value");
>From 8cd012c3458cd54c9112148dfb950dd44b110b51 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:46:15 +0800
Subject: [PATCH 2/7] [llubi] Minor fixes
---
llvm/tools/llubi/lib/Context.cpp | 14 ++++-------
llvm/tools/llubi/lib/Context.h | 4 +++-
llvm/tools/llubi/lib/Value.cpp | 35 +++++++++++++++++++---------
llvm/tools/llubi/lib/Value.h | 40 +++++++++++++++++++++++++-------
4 files changed, 63 insertions(+), 30 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index a87d66fce1638..6388bfc1aefa5 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -198,7 +198,7 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
}
if (IsByteType) {
- LogicalBytes[BitsStart / 8] = LogicalByte;
+ LogicalBytes[I / 8] = LogicalByte;
continue;
}
@@ -227,7 +227,7 @@ AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
}
if (DL.isBigEndian())
std::reverse(LogicalBytes.begin(), LogicalBytes.end());
- return ByteValue(NumBits, std::move(LogicalBytes));
+ return ByteValue(NumBits, std::move(LogicalBytes), DL.isLittleEndian());
}
OffsetInBits = NewOffsetInBits;
@@ -274,14 +274,8 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
}
}
unsigned BitWidth = Ty->getByteBitWidth();
- ByteValue Res(BitWidth, Bytes);
- if (BitWidth & 7) {
- uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
- if (DL.isLittleEndian())
- Res.mutableBytes().back().zeroBits(Mask);
- else
- Res.mutableBytes().front().zeroBits(Mask);
- }
+ ByteValue Res(BitWidth, Bytes, DL.isLittleEndian(),
+ /*ImplicitClearHighBits=*/true);
return AnyValue(std::move(Res));
}
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 8d6252649e510..3769a61a137fe 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -184,8 +184,10 @@ template <typename ArrayRefT> class BytesView {
bool IsLittleEndian;
public:
+ explicit BytesView(ArrayRefT Ref, bool IsLittleEndian)
+ : Bytes(Ref), IsLittleEndian(IsLittleEndian) {}
explicit BytesView(ArrayRefT Ref, const DataLayout &DL)
- : Bytes(Ref), IsLittleEndian(DL.isLittleEndian()) {}
+ : BytesView(Ref, DL.isLittleEndian()) {}
auto &operator[](uint32_t Index) {
return Bytes[IsLittleEndian ? Index : Bytes.size() - 1 - Index];
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 9b2afa9926fe3..e1dc6ca6d1ec1 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -285,7 +285,8 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
if (Ty->isPointerTy())
return Pointer::null(Ty->getPointerAddressSpace(), Ctx.getDataLayout());
if (Ty->isByteTy())
- return ByteValue::zero(Ty->getByteBitWidth());
+ return ByteValue::zero(Ty->getByteBitWidth(),
+ Ctx.getDataLayout().isLittleEndian());
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(
@@ -311,10 +312,18 @@ AnyValue AnyValue::getVectorSplat(const AnyValue &Scalar, size_t NumElements) {
return AnyValue(std::vector<AnyValue>(NumElements, Scalar));
}
-ByteValue::ByteValue(const APInt &V, bool IsLittleEndian) {}
-ByteValue ByteValue::zero(uint32_t BitWidth) {
+ByteValue::ByteValue(const APInt &V, bool IsLittleEndian)
+ : BitWidth(V.getBitWidth()), IsLittleEndian(IsLittleEndian) {
+ Val.resize(divideCeil(BitWidth, 8));
+ MutableBytesView View(Val, IsLittleEndian);
+ for (uint8_t I = 0; I < BitWidth; I += 8)
+ View[I / 8] = Byte::concrete(static_cast<uint8_t>(
+ V.extractBitsAsZExtValue(std::min(BitWidth - I, 8), I)));
+}
+ByteValue ByteValue::zero(uint32_t BitWidth, bool IsLittleEndian) {
return ByteValue(
- BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)));
+ BitWidth, std::vector<Byte>(divideCeil(BitWidth, 8), Byte::concrete(0)),
+ IsLittleEndian);
}
ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
@@ -327,22 +336,26 @@ ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
else
Val.front().zeroBits(Mask);
}
- return ByteValue(BitWidth, std::move(Val));
+ return ByteValue(BitWidth, std::move(Val), IsLittleEndian);
}
void ByteValue::print(raw_ostream &OS) const {
OS << 'b' << BitWidth << ' ';
for (const Byte &V : Val) {
+ bool IsFullByte = (BitWidth & 7) == 0 ||
+ (IsLittleEndian ? &Val.back() : &Val.front()) != &V;
// Try to print a byte in short form
- if (V.ConcreteMask == 255 && V.TagMask == 0) {
+ if (IsFullByte && V.ConcreteMask == 255 && V.TagMask == 0) {
// Concrete value without provenance.
OS << hexdigit(V.Value >> 4) << hexdigit(V.Value & 15);
- } else if (V.ConcreteMask == 0 && (V.Value == 0 || V.Value == 255)) {
+ } else if (IsFullByte && V.ConcreteMask == 0 &&
+ (V.Value == 0 || V.Value == 255)) {
// Poison/undef bytes.
OS << (V.Value == 0 ? "!!" : "??");
} else {
- for (uint32_t I = 0; I != 8; ++I) {
- uint32_t Mask = 1U << (8 - I);
+ uint32_t BitEnd = IsFullByte ? 8 : BitWidth & 7;
+ for (uint32_t I = 0; I != BitEnd; ++I) {
+ uint32_t Mask = 1U << (BitEnd - 1 - I);
if (V.ConcreteMask & Mask)
OS << (V.Value & Mask ? '1' : '0');
else
@@ -351,8 +364,8 @@ void ByteValue::print(raw_ostream &OS) const {
if (uint32_t TagMask = V.ConcreteMask & V.TagMask) {
// Print tags if available.
OS << '(';
- for (uint32_t I = 0; I != 8; ++I) {
- uint32_t Mask = 1U << (8 - I);
+ for (uint32_t I = 0; I != BitEnd; ++I) {
+ uint32_t Mask = 1U << (BitEnd - 1 - I);
if (TagMask & Mask)
OS << (V.TagValue & Mask ? '1' : '0');
else
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index ea023aa6920e5..28ed8771e72e6 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -43,6 +43,7 @@ struct Byte {
void zeroBits(uint8_t Mask) {
ConcreteMask |= Mask;
Value &= ~Mask;
+ TagMask &= ~Mask;
}
void poisonBits(uint8_t Mask) {
@@ -103,6 +104,12 @@ struct Byte {
static_cast<uint8_t>(TagMask << Shift),
static_cast<uint8_t>(TagValue << Shift)};
}
+
+ bool AreHighBitsZExtd(uint8_t BitsFrom) const {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << BitsFrom);
+ return (ConcreteMask & Mask) == Mask && (Value & Mask) == 0 &&
+ (TagMask & Mask) == 0;
+ }
};
enum class StorageKind {
@@ -181,24 +188,41 @@ class Pointer {
/// Represents a scalar byte value. If the value is not byte-sized, the high
/// bits are zero-padded.
class ByteValue {
+ // The byte order is endianness-dependent.
std::vector<Byte> Val;
- uint32_t BitWidth;
+ uint32_t BitWidth : 31;
+ uint32_t IsLittleEndian : 1;
public:
ByteValue(const APInt &V, bool IsLittleEndian);
- /// The caller is responsible to zero high bits for non-byte-sized values.
- ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val)
- : Val(Val), BitWidth(BitWidth) {}
- /// The caller is responsible to zero high bits for non-byte-sized values.
- ByteValue(uint32_t BitWidth, std::vector<Byte> Val)
- : Val(std::move(Val)), BitWidth(BitWidth) {}
+ ByteValue(uint32_t BitWidth, ArrayRef<Byte> Val, bool IsLittleEndian,
+ bool ImplicitClearHighBits = false)
+ : ByteValue(BitWidth, std::vector<Byte>(Val), IsLittleEndian,
+ ImplicitClearHighBits) {}
+ ByteValue(uint32_t BitWidth, std::vector<Byte> Val, bool IsLittleEndian,
+ bool ImplicitClearHighBits = false)
+ : Val(std::move(Val)), BitWidth(BitWidth),
+ IsLittleEndian(IsLittleEndian) {
+ if (ImplicitClearHighBits && (BitWidth & 7) != 0) {
+ uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
+ if (IsLittleEndian)
+ this->Val.back().zeroBits(Mask);
+ else
+ this->Val.front().zeroBits(Mask);
+ }
+ assert(((BitWidth & 7) == 0 ||
+ ((IsLittleEndian ? this->Val.back() : this->Val.front())
+ .AreHighBitsZExtd(BitWidth & 7))) &&
+ "The caller is responsible to zero high bits for non-byte-sized "
+ "values.");
+ }
ByteValue(const ByteValue &) = default;
ByteValue(ByteValue &&) = default;
ByteValue &operator=(const ByteValue &) = default;
ByteValue &operator=(ByteValue &&) = default;
~ByteValue() = default;
- static ByteValue zero(uint32_t BitWidth);
+ static ByteValue zero(uint32_t BitWidth, bool IsLittleEndian);
static ByteValue poison(uint32_t BitWidth, bool IsLittleEndian);
uint32_t getBitWidth() const { return BitWidth; }
>From ec6dbeaf0faca3252765c976e1f3908c66b9726d Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:47:12 +0800
Subject: [PATCH 3/7] [llubi] Add tests. NFC.
---
llvm/test/tools/llubi/loadstore_be.ll | 65 +++++++++++++++++++++++++++
llvm/test/tools/llubi/loadstore_le.ll | 65 +++++++++++++++++++++++++++
2 files changed, 130 insertions(+)
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 8f2e0815f88fe..c35f536336b16 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -113,12 +113,47 @@ define void @main() {
%alloc_ptr = alloca ptr
store ptr %alloc_ptr, ptr %alloc_ptr
+ %bytes = load b64, ptr %alloc_ptr
; It should recover the provenance.
%ptr_with_provenance = load ptr, ptr %alloc_ptr
%addr_bits = load i8, ptr %alloc_ptr
store i8 %addr_bits, ptr %alloc_ptr
; The first byte is tainted. We cannot recover the provenance.
%ptr_without_provenance = load ptr, ptr %alloc_ptr
+ store b64 %bytes, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance2 = load ptr, ptr %alloc_ptr
+ %bv64b1 = bitcast b64 %bytes to <64 x b1>
+ %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse(<64 x b1> %bv64b1)
+ store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr
+ ; The bit order is incorrect. We cannot recover the provenance.
+ %ptr_without_provenance2 = load ptr, ptr %alloc_ptr
+ store b64 %bytes, ptr %alloc_ptr
+ store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr
+ %bytes_tainted = load b64, ptr %alloc_ptr
+ ; The first byte is tainted. We cannot recover the provenance.
+ %ptr_without_provenance3 = load ptr, ptr %alloc_ptr
+ %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8>
+ %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7
+ %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1>
+ %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse(<8 x b1> %first_byte_with_provenance_reversed_v8b1)
+ store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr
+ %bytes_recovered = load b64, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance3 = load ptr, ptr %alloc_ptr
+
+ %alloc_byte = alloca b32
+ store b8 127, ptr %alloc_byte
+ %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1
+ store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte
+ %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2
+ store b8 poison, ptr %gep_third_byte
+ %bytes_mixed = load b32, ptr %alloc_byte
+ store b32 u0xDEADBEEF, ptr %alloc_byte
+ %bytes_endianness = load b32, ptr %alloc_byte
+ %bytes_non_pow2 = load b28, ptr %alloc_byte
+ store b28 u0xEADBEEF, ptr %alloc_byte
+ %bytes_zextd = load b32, ptr %alloc_byte
ret void
}
@@ -204,9 +239,39 @@ define void @main() {
; CHECK-NEXT: %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
; CHECK-NEXT: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %bytes = load b64, ptr %alloc_ptr, align 8 => b64 00000000(00100110) 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100)
; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 0
; CHECK-NEXT: store i8 %addr_bits, ptr %alloc_ptr, align 1
; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT: store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %ptr_with_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %bv64b1 = bitcast b64 %bytes to <64 x b1> => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse.v64b1(<64 x b1> %bv64b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %ptr_without_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x900000000000000 [nullary]
+; CHECK-NEXT: store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT: store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %bytes_tainted = load b64, ptr %alloc_ptr, align 8 => b64 0000!!!! 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100)
+; CHECK-NEXT: %ptr_without_provenance3 = load ptr, ptr %alloc_ptr, align 8 => poison
+; CHECK-NEXT: %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8> => { b8 00001001(00110100) , b8 00000000(00010000) , b8 00000000(10101001) , b8 00000000(01010001) , b8 00000000(10011100) , b8 00000000(00111100) , b8 00000000(10001110) , b8 00000000(01100100) }
+; CHECK-NEXT: %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7 => b8 00000000(01100100)
+; CHECK-NEXT: %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1> => { b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse.v8b1(<8 x b1> %first_byte_with_provenance_reversed_v8b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) }
+; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %bytes_recovered = load b64, ptr %alloc_ptr, align 8 => b64 00000000(00100110) 00000000(01110001) 00000000(00111100) 00000000(00111001) 00000000(10001010) 00000000(10010101) 00000000(00001000) 10010000(00101100)
+; CHECK-NEXT: %ptr_with_provenance3 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %alloc_byte = alloca b32, align 4 => ptr 0x98 [alloc_byte]
+; CHECK-NEXT: store b8 127, ptr %alloc_byte, align 1
+; CHECK-NEXT: %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1 => ptr 0x99 [alloc_byte + 1]
+; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte, align 1
+; CHECK-NEXT: %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0x9A [alloc_byte + 2]
+; CHECK-NEXT: store b8 poison, ptr %gep_third_byte, align 1
+; CHECK-NEXT: %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 7F 00000000(00100110) !! ??
+; CHECK-NEXT: store b32 -559038737, ptr %alloc_byte, align 4
+; CHECK-NEXT: %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 DE AD BE EF
+; CHECK-NEXT: %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 1110 AD BE EF
+; CHECK-NEXT: store b28 -22167825, ptr %alloc_byte, align 4
+; CHECK-NEXT: %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 0E AD BE EF
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index 3caf02c3e6eaa..1f86088c4a2ec 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -114,12 +114,47 @@ define void @main() {
%alloc_ptr = alloca ptr
store ptr %alloc_ptr, ptr %alloc_ptr
+ %bytes = load b64, ptr %alloc_ptr
; It should recover the provenance.
%ptr_with_provenance = load ptr, ptr %alloc_ptr
%addr_bits = load i8, ptr %alloc_ptr
store i8 %addr_bits, ptr %alloc_ptr
; The first byte is tainted. We cannot recover the provenance.
%ptr_without_provenance = load ptr, ptr %alloc_ptr
+ store b64 %bytes, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance2 = load ptr, ptr %alloc_ptr
+ %bv64b1 = bitcast b64 %bytes to <64 x b1>
+ %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse(<64 x b1> %bv64b1)
+ store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr
+ ; The bit order is incorrect. We cannot recover the provenance.
+ %ptr_without_provenance2 = load ptr, ptr %alloc_ptr
+ store b64 %bytes, ptr %alloc_ptr
+ store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr
+ %bytes_tainted = load b64, ptr %alloc_ptr
+ ; The first byte is tainted. We cannot recover the provenance.
+ %ptr_without_provenance3 = load ptr, ptr %alloc_ptr
+ %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8>
+ %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7
+ %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1>
+ %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse(<8 x b1> %first_byte_with_provenance_reversed_v8b1)
+ store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr
+ %bytes_recovered = load b64, ptr %alloc_ptr
+ ; It should recover the provenance.
+ %ptr_with_provenance3 = load ptr, ptr %alloc_ptr
+
+ %alloc_byte = alloca b32
+ store b8 127, ptr %alloc_byte
+ %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1
+ store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte
+ %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2
+ store b8 poison, ptr %gep_third_byte
+ %bytes_mixed = load b32, ptr %alloc_byte
+ store b32 u0xDEADBEEF, ptr %alloc_byte
+ %bytes_endianness = load b32, ptr %alloc_byte
+ %bytes_non_pow2 = load b28, ptr %alloc_byte
+ store b28 u0xEADBEEF, ptr %alloc_byte
+ %bytes_zextd = load b32, ptr %alloc_byte
ret void
}
@@ -206,9 +241,39 @@ define void @main() {
; CHECK-NEXT: %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
; CHECK-NEXT: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %bytes = load b64, ptr %alloc_ptr, align 8 => b64 10010000(00101100) 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110)
; CHECK-NEXT: %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
; CHECK-NEXT: %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -112
; CHECK-NEXT: store i8 %addr_bits, ptr %alloc_ptr, align 1
; CHECK-NEXT: %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT: store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %ptr_with_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %bv64b1 = bitcast b64 %bytes to <64 x b1> => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: %bv64b1_reversed = call <64 x b1> @llvm.vector.reverse.v64b1(<64 x b1> %bv64b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(0) , b1 0(1) , b1 0(0) , b1 0(0) , b1 0(0) , b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: store <64 x b1> %bv64b1_reversed, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %ptr_without_provenance2 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x900000000000000 [nullary]
+; CHECK-NEXT: store b64 %bytes, ptr %alloc_ptr, align 8
+; CHECK-NEXT: store <2 x b4> <b4 0, b4 poison>, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %bytes_tainted = load b64, ptr %alloc_ptr, align 8 => b64 !!!!0000 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110)
+; CHECK-NEXT: %ptr_without_provenance3 = load ptr, ptr %alloc_ptr, align 8 => poison
+; CHECK-NEXT: %bv8b8_reversed = bitcast <64 x b1> %bv64b1_reversed to <8 x b8> => { b8 00000000(01100100) , b8 00000000(10001110) , b8 00000000(00111100) , b8 00000000(10011100) , b8 00000000(01010001) , b8 00000000(10101001) , b8 00000000(00010000) , b8 00001001(00110100) }
+; CHECK-NEXT: %first_byte_with_provenance_reversed = extractelement <8 x b8> %bv8b8_reversed, i32 7 => b8 00001001(00110100)
+; CHECK-NEXT: %first_byte_with_provenance_reversed_v8b1 = bitcast b8 %first_byte_with_provenance_reversed to <8 x b1> => { b1 1(0) , b1 0(0) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(1) , b1 0(0) , b1 0(0) }
+; CHECK-NEXT: %first_byte_with_provenance_v8b1 = call <8 x b1> @llvm.vector.reverse.v8b1(<8 x b1> %first_byte_with_provenance_reversed_v8b1) => { b1 0(0) , b1 0(0) , b1 0(1) , b1 0(1) , b1 1(0) , b1 0(1) , b1 0(0) , b1 1(0) }
+; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %alloc_ptr, align 1
+; CHECK-NEXT: %bytes_recovered = load b64, ptr %alloc_ptr, align 8 => b64 10010000(00101100) 00000000(00001000) 00000000(10010101) 00000000(10001010) 00000000(00111001) 00000000(00111100) 00000000(01110001) 00000000(00100110)
+; CHECK-NEXT: %ptr_with_provenance3 = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT: %alloc_byte = alloca b32, align 4 => ptr 0x98 [alloc_byte]
+; CHECK-NEXT: store b8 127, ptr %alloc_byte, align 1
+; CHECK-NEXT: %gep_second_byte = getelementptr i8, ptr %alloc_byte, i64 1 => ptr 0x99 [alloc_byte + 1]
+; CHECK-NEXT: store <8 x b1> %first_byte_with_provenance_v8b1, ptr %gep_second_byte, align 1
+; CHECK-NEXT: %gep_third_byte = getelementptr i8, ptr %alloc_byte, i64 2 => ptr 0x9A [alloc_byte + 2]
+; CHECK-NEXT: store b8 poison, ptr %gep_third_byte, align 1
+; CHECK-NEXT: %bytes_mixed = load b32, ptr %alloc_byte, align 4 => b32 7F 10010000(00101100) !! ??
+; CHECK-NEXT: store b32 -559038737, ptr %alloc_byte, align 4
+; CHECK-NEXT: %bytes_endianness = load b32, ptr %alloc_byte, align 4 => b32 EF BE AD DE
+; CHECK-NEXT: %bytes_non_pow2 = load b28, ptr %alloc_byte, align 4 => b28 EF BE AD 1110
+; CHECK-NEXT: store b28 -22167825, ptr %alloc_byte, align 4
+; CHECK-NEXT: %bytes_zextd = load b32, ptr %alloc_byte, align 4 => b32 EF BE AD 0E
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
>From 7b3dc59fdc72516cf5ec08912a9b48ec7335fdec Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 08:56:44 +0800
Subject: [PATCH 4/7] [llubi] Add tests with !noundef check. NFC.
---
llvm/test/tools/llubi/load_noundef_ub_undef.ll | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/llvm/test/tools/llubi/load_noundef_ub_undef.ll b/llvm/test/tools/llubi/load_noundef_ub_undef.ll
index bb9f5c79cf260..fdc94b602c7a2 100644
--- a/llvm/test/tools/llubi/load_noundef_ub_undef.ll
+++ b/llvm/test/tools/llubi/load_noundef_ub_undef.ll
@@ -1,5 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/i32/b32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
define void @main() {
%p = alloca i32
@@ -7,8 +8,8 @@ define void @main() {
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: %p = alloca i32, align 4 => ptr 0x8 [p]
+; CHECK-NEXT: %p = alloca {{i|b}}32, align 4 => ptr 0x8 [p]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 %res = load i32, ptr %p, align 4, !noundef !0 at @main <stdin>:6
+; CHECK-NEXT: #0 %res = load {{i|b}}32, ptr %p, align 4, !noundef !0 at @main <stdin>:7
; CHECK-NEXT: Immediate UB detected: The value loaded contains undefined bits.
; CHECK-NEXT: error: Execution of function 'main' failed.
>From 26e4dbf0a20a627d80e901907a7b52eaaddec188 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 09:11:17 +0800
Subject: [PATCH 5/7] [llubi] Handle noundef attribute for byte types
---
llvm/test/tools/llubi/attribute_noundef_ub.ll | 5 +++--
llvm/test/tools/llubi/attributes.ll | 11 +++++++++++
llvm/tools/llubi/lib/Interpreter.cpp | 14 ++++++++++++--
3 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/llvm/test/tools/llubi/attribute_noundef_ub.ll b/llvm/test/tools/llubi/attribute_noundef_ub.ll
index a8a6a1984398b..f2bd1d599b7b6 100644
--- a/llvm/test/tools/llubi/attribute_noundef_ub.ll
+++ b/llvm/test/tools/llubi/attribute_noundef_ub.ll
@@ -1,5 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/i32/b32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
define void @callee(i32 noundef %x) {
ret void
@@ -11,6 +12,6 @@ define void @main() {
}
; CHECK: Entering function: main
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 call void @callee(i32 poison) at @main <stdin>:9
-; CHECK-NEXT: Immediate UB detected: The value poison violates noundef attribute.
+; CHECK-NEXT: #0 call void @callee({{i|b}}32 poison) at @main <stdin>:10
+; CHECK-NEXT: Immediate UB detected: The value {{poison|b32 !! !! !! !!}} violates noundef attribute.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
index cc8fcf62a46f9..971063e55b904 100644
--- a/llvm/test/tools/llubi/attributes.ll
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -47,6 +47,10 @@ define noundef i32 @identity_noundef(i32 noundef %x) {
ret i32 %x
}
+define noundef b7 @identity_noundef_byte(b7 noundef %x) {
+ ret b7 %x
+}
+
define noundef {i32, <2 x i32>, [2 x i32]} @identity_noundef_agg({i32, <2 x i32>, [2 x i32]} noundef %x) {
ret {i32, <2 x i32>, [2 x i32]} %x
}
@@ -119,6 +123,8 @@ define void @main() {
%fmt_n_out = alloca [6 x i8]
store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out
%res = call range(i32 0, 15) noundef i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6)
+
+ %noundef_byte = call b7 @identity_noundef_byte(b7 -1)
ret void
}
; CHECK: Entering function: main
@@ -324,5 +330,10 @@ define void @main() {
; CHECK-NEXT: store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
; CHECK-NEXT: N=6
; CHECK-NEXT: %res = call noundef range(i32 0, 15) i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6) => i32 4
+; CHECK-NEXT: Entering function: identity_noundef_byte
+; CHECK-NEXT: b7 %x = b7 1111111
+; CHECK-NEXT: ret b7 %x
+; CHECK-NEXT: Exiting function: identity_noundef_byte
+; CHECK-NEXT: %noundef_byte = call b7 @identity_noundef_byte(b7 -1) => b7 1111111
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 3c21f8ff175f7..0cba8121245a8 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -112,8 +112,18 @@ static void applyAlignAttr(AnyValue &V, Align Alignment) {
static bool violatesNoUndefAttr(AnyValue &V) {
bool ContainsPoison = false;
- forEachScalarValue(
- V, [&](AnyValue &Scalar) { ContainsPoison |= Scalar.isPoison(); });
+ forEachScalarValue(V, [&](AnyValue &Scalar) {
+ if (Scalar.isPoison()) {
+ ContainsPoison = true;
+ return;
+ }
+ if (Scalar.isByte() && !ContainsPoison) {
+ // For non-byte-sized values, high bits are always zeroed out.
+ ContainsPoison = any_of(Scalar.asByte().bytes(), [](const Byte &V) {
+ return V.ConcreteMask != 255;
+ });
+ }
+ });
return ContainsPoison;
}
>From 2b097c3a12a92284e3ace710c4716198ccec3ab2 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 09:17:06 +0800
Subject: [PATCH 6/7] [llubi] Use ImplicitClearHighBits in `ByteValue::poison`
---
llvm/tools/llubi/lib/Value.cpp | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index e1dc6ca6d1ec1..2dc3fea6387b4 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -327,16 +327,9 @@ ByteValue ByteValue::zero(uint32_t BitWidth, bool IsLittleEndian) {
}
ByteValue ByteValue::poison(uint32_t BitWidth, bool IsLittleEndian) {
- std::vector<Byte> Val(divideCeil(BitWidth, 8), Byte::poison());
- // Zero high-bits if it is not a byte-sized value.
- if (BitWidth & 7) {
- uint8_t Mask = static_cast<uint8_t>((~0U) << (BitWidth & 7));
- if (IsLittleEndian)
- Val.back().zeroBits(Mask);
- else
- Val.front().zeroBits(Mask);
- }
- return ByteValue(BitWidth, std::move(Val), IsLittleEndian);
+ return ByteValue(BitWidth,
+ std::vector<Byte>(divideCeil(BitWidth, 8), Byte::poison()),
+ IsLittleEndian, /*ImplicitClearHighBits=*/true);
}
void ByteValue::print(raw_ostream &OS) const {
>From ed48a0626caf38d3f7c1795a920b05e6d4b2f981 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 1 Jun 2026 21:02:21 +0800
Subject: [PATCH 7/7] [llubi] Use uint32_t
---
llvm/tools/llubi/lib/Value.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 2dc3fea6387b4..f258b73d560ba 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -316,7 +316,7 @@ ByteValue::ByteValue(const APInt &V, bool IsLittleEndian)
: BitWidth(V.getBitWidth()), IsLittleEndian(IsLittleEndian) {
Val.resize(divideCeil(BitWidth, 8));
MutableBytesView View(Val, IsLittleEndian);
- for (uint8_t I = 0; I < BitWidth; I += 8)
+ for (uint32_t I = 0; I < BitWidth; I += 8)
View[I / 8] = Byte::concrete(static_cast<uint8_t>(
V.extractBitsAsZExtValue(std::min(BitWidth - I, 8), I)));
}
More information about the llvm-commits
mailing list