blob: b0f90aab2f1b59693a0402f904eea3ff5c963fe8 [file]
//===--- Value.h - Value Representation for llubi ---------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_TOOLS_LLUBI_VALUE_H
#define LLVM_TOOLS_LLUBI_VALUE_H
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/raw_ostream.h"
namespace llvm::ubi {
class MemoryObject;
class Context;
class AnyValue;
/// Representation of a byte in memory.
/// How to interpret the byte per bit:
/// - If the concrete mask bit is 0, the bit is either undef or poison. The
/// value bit indicates whether it is undef.
/// - If the concrete mask bit is 1, the bit is a concrete value. The value bit
/// stores the concrete bit value. The tag mask bit indicates whether it is a
/// pointer bit, and the tag value bit is used for provenance tracking of
/// pointers.
///
/// Note that the idealized interpreter would store a full pointer tag for every
/// single pointer bit, as well as the position of that bit in the pointer. The
/// provenance is preserved as the bit is copied around, and when a sequence of
/// bytes is eventually converted back to a pointer, all bits must be in the
/// original order and have the same provenance. However, that would be
/// prohibitively expensive. So instead, we rely on randomized ptr-sized tags.
/// This means that if bits get reordered, or if bits from different pointers
/// get mixed, then the result is unlikely to be a valid tag.
struct Byte {
uint8_t ConcreteMask;
uint8_t Value;
uint8_t TagMask; // A mask to indicate which bits are pointer bits.
uint8_t TagValue; // For each pointer bit, the corresponding bit of the tag
// for provenance tracking.
static Byte poison() { return Byte{0, 0, 0, 0}; }
static Byte undef() { return Byte{0, 255, 0, 0}; }
static Byte concrete(uint8_t Val) { return Byte{255, Val, 0, 0}; }
void zeroBits(uint8_t Mask) {
ConcreteMask |= Mask;
Value &= ~Mask;
TagMask &= ~Mask;
}
void poisonBits(uint8_t Mask) {
ConcreteMask &= ~Mask;
Value &= ~Mask;
TagMask &= ~Mask;
}
void undefBits(uint8_t Mask) {
ConcreteMask &= ~Mask;
Value |= Mask;
TagMask &= ~Mask;
}
void writeBits(uint8_t Mask, uint8_t Val) {
ConcreteMask |= Mask;
Value = (Value & ~Mask) | (Val & Mask);
TagMask &= ~Mask;
}
void writeTagBits(uint8_t Mask, uint8_t Tag) {
assert(
(ConcreteMask & Mask) == Mask &&
"Please ensure pointer bits are concrete before calling writeTagBits.");
TagMask |= Mask;
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 |
/// LSB | 0 1 0 1 0 1 0 1 | 0 0 0 0 1 1 1 1 | MSB
/// Result = | 1 0 1 0 0 0 0 1 |
static Byte fshr(const Byte &Low, const Byte &High, uint32_t ShAmt) {
return Byte{
static_cast<uint8_t>((Low.ConcreteMask | (High.ConcreteMask << 8)) >>
ShAmt),
static_cast<uint8_t>((Low.Value | (High.Value << 8)) >> ShAmt),
static_cast<uint8_t>((Low.TagMask | (High.TagMask << 8)) >> ShAmt),
static_cast<uint8_t>((Low.TagValue | (High.TagValue << 8)) >> ShAmt)};
}
Byte lshr(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)};
}
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)};
}
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 {
Integer,
Float,
Pointer,
Byte,
Poison,
None, // Placeholder for void type
Aggregate, // Struct, Array or Vector
};
/// Tri-state boolean value.
enum class BooleanKind { False, True, Poison };
/// A set of previously exposed provenances. It is originally yielded by
/// inttoptr, and shared by pointers derived from the result.
///
/// Each capability check may invalidate some provenances. If we cannot
/// pick one, it is UB. That is, from the angelic non-determinism view,
/// we cannot pick a provenance to make the program reach this point.
///
/// For efficiency, this class has different forms in two stages:
/// 1. Before any memory access is performed, ActiveMask is set to zero and
/// Generation represents the global generation number of the snapshot.
/// 2. After a memory access is performed, we can determine exactly one memory
/// object to be accessed (address ranges are distinct). In this case,
/// BaseAddress is set and ActiveMask is non-zero. ActiveMask represents the
/// validity of the first N exposed provenances associated with the memory
/// object. The bitwidth N is the number of provenances in the list with
/// List[I].Generation <= WildcardProvenance::Generation (The generation field
/// in the list is monotonically increasing). That is, we can only access
/// through exposed provenances before inttoptr executes. Note that if
/// ActiveMask becomes zero again, UB must be triggered.
class WildcardProvenance : public RefCountedBase<WildcardProvenance> {
APInt ActiveMask;
union {
uint64_t Generation;
uint64_t BaseAddress;
};
friend class Context;
public:
explicit WildcardProvenance(uint64_t Generation)
: ActiveMask(), Generation(Generation) {}
};
/// Components of a pointer excluding address. They are shared between pointer
/// values, as most of operations don't change the provenance.
/// Each node will be assigned a unique, pointer-sized tag, which is used to
/// represent the pointer in the memory.
/// The provenance can be either concrete or wildcard, as determined by the
/// cases below:
/// Obj Wildcard State
/// Null Null Invalid
/// Null NonNull Wildcard
/// NonNull Null Concrete
/// NonNull NonNull Wildcard (associated with a specific MO)
class Provenance : public RefCountedBase<Provenance> {
// TODO: store reference to the provenance of the pointer it is derived from
// The underlying memory object. It can be null for invalid or dangling
// pointers. Besides, for pointers with wildcard provenance, it can be null
// until the memory object is resolved by gep inbounds.
IntrusiveRefCntPtr<MemoryObject> Obj;
// A tag is a randomly generated unique identifier to recover the provenance
// of a pointer. The length of tag is equal to the store size of the pointer
// type, in bits. It may produce false negatives in some corner cases. But in
// real practice the false negative rate should be negligible.
// A zero tag is invalid.
APInt Tag;
// Null if it is concrete.
IntrusiveRefCntPtr<WildcardProvenance> Wildcard;
// TODO: modeling nofree
// TODO: modeling captures
// TODO: modeling inrange(Start, End) attribute
const APInt &getTag() const { return Tag; }
void setTag(const APInt &T) { Tag = T; }
friend class Context;
public:
Provenance(IntrusiveRefCntPtr<MemoryObject> Obj) : Obj(std::move(Obj)) {}
static IntrusiveRefCntPtr<Provenance> nullary();
IntrusiveRefCntPtr<Provenance> getWithKnownMemoryObject(MemoryObject &Obj);
MemoryObject *getMemoryObject() const { return Obj.get(); }
bool isWildcard() const { return Wildcard != nullptr; }
};
class Pointer {
// The provenance of the pointer.
IntrusiveRefCntPtr<Provenance> Prov;
// The address of the pointer. The bit width is determined by
// DataLayout::getPointerSizeInBits.
APInt Address;
public:
explicit Pointer(const APInt &Address)
: Prov(Provenance::nullary()), Address(Address) {}
explicit Pointer(IntrusiveRefCntPtr<Provenance> Prov, const APInt &Address)
: Prov(std::move(Prov)), Address(Address) {
assert(this->Prov && "Invalid provenance.");
}
Pointer getWithNewAddr(const APInt &NewAddr) const {
return Pointer(Prov, NewAddr);
}
Pointer getWithNewProvenance(IntrusiveRefCntPtr<Provenance> NewProv) const {
return Pointer(NewProv, Address);
}
static AnyValue null(unsigned AS, const DataLayout &DL);
bool isNullPtr(unsigned AS, const DataLayout &DL) const;
void print(raw_ostream &OS) const;
const APInt &address() const { return Address; }
Provenance &provenance() const { return *Prov; }
};
/// Represents a scalar byte value. If the value is not byte-sized, the high
/// bits are zero-padded.
class ByteValue {
// The byte order is endianness-dependent.
std::vector<Byte> Val;
uint32_t BitWidth : 31;
uint32_t IsLittleEndian : 1;
public:
ByteValue(const APInt &V, bool IsLittleEndian);
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, bool IsLittleEndian);
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(Context &Ctx, 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 {
StorageKind Kind;
union {
APInt IntVal;
APFloat FloatVal;
Pointer PtrVal;
ByteValue ByteVal;
std::vector<AnyValue> AggVal;
};
struct PoisonTag {};
void destroy();
public:
AnyValue() : Kind(StorageKind::None) {}
explicit AnyValue(PoisonTag) : Kind(StorageKind::Poison) {}
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);
AnyValue(AnyValue &&Other);
AnyValue &operator=(const AnyValue &);
AnyValue &operator=(AnyValue &&);
~AnyValue() { destroy(); }
void print(Context &Ctx, raw_ostream &OS) const;
static AnyValue poison() { return AnyValue(PoisonTag{}); }
static AnyValue boolean(bool Val) { return AnyValue(APInt(1, Val)); }
static AnyValue getPoisonValue(Context &Ctx, Type *Ty);
static AnyValue getNullValue(Context &Ctx, Type *Ty);
static AnyValue getVectorSplat(const AnyValue &Scalar, size_t NumElements);
bool isNone() const { return Kind == StorageKind::None; }
bool isPoison() const { return Kind == StorageKind::Poison; }
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 {
switch (Kind) {
case StorageKind::None:
return Ty->isVoidTy();
case StorageKind::Poison:
return Ty->isFloatingPointTy() || Ty->isIntegerTy() || Ty->isPointerTy();
case StorageKind::Integer:
return Ty->isIntegerTy();
case StorageKind::Float:
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();
}
llvm_unreachable("Unhandled storage kind.");
}
const APInt &asInteger() const {
assert(Kind == StorageKind::Integer && "Expect an integer value");
return IntVal;
}
const APFloat &asFloat() const {
assert(Kind == StorageKind::Float && "Expect a float value");
return FloatVal;
}
const Pointer &asPointer() const {
assert(Kind == StorageKind::Pointer && "Expect a pointer value");
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");
return AggVal;
}
std::vector<AnyValue> &asAggregate() {
assert(Kind == StorageKind::Aggregate &&
"Expect an aggregate/vector value");
return AggVal;
}
// Helper function for C++ 17 structured bindings.
template <size_t I> const AnyValue &get() const {
assert(Kind == StorageKind::Aggregate &&
"Expect an aggregate/vector value");
assert(I < AggVal.size() && "Index out of bounds");
return AggVal[I];
}
BooleanKind asBoolean() const {
if (isPoison())
return BooleanKind::Poison;
return asInteger().isZero() ? BooleanKind::False : BooleanKind::True;
}
};
class AnyValuePrinter {
Context &Ctx;
raw_ostream &OS;
public:
AnyValuePrinter(Context &Ctx, raw_ostream &OS) : Ctx(Ctx), OS(OS) {}
AnyValuePrinter &operator<<(const AnyValue &V) {
V.print(Ctx, OS);
return *this;
}
template <typename T> AnyValuePrinter &operator<<(const T &Val) {
OS << Val;
return *this;
}
operator raw_ostream &() { return OS; }
};
inline raw_ostream &operator<<(raw_ostream &OS, const Pointer &P) {
P.print(OS);
return OS;
}
inline AnyValue addNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
bool HasNUW) {
APInt Res = LHS + RHS;
if (HasNUW && Res.ult(RHS))
return AnyValue::poison();
if (HasNSW && LHS.isNonNegative() == RHS.isNonNegative() &&
LHS.isNonNegative() != Res.isNonNegative())
return AnyValue::poison();
return Res;
}
inline AnyValue subNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
bool HasNUW) {
APInt Res = LHS - RHS;
if (HasNUW && Res.ugt(LHS))
return AnyValue::poison();
if (HasNSW && LHS.isNonNegative() != RHS.isNonNegative() &&
LHS.isNonNegative() != Res.isNonNegative())
return AnyValue::poison();
return Res;
}
inline AnyValue mulNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
bool HasNUW) {
bool Overflow = false;
APInt Res = LHS.smul_ov(RHS, Overflow);
if (HasNSW && Overflow)
return AnyValue::poison();
if (HasNUW) {
(void)LHS.umul_ov(RHS, Overflow);
if (Overflow)
return AnyValue::poison();
}
return Res;
}
} // namespace llvm::ubi
#endif