Update computeGEP to implicitly truncate struct field offsets. This fixes crashes in the new test, where the field offset does not fit in the index width. Related https://github.com/llvm/llvm-project/pull/213436 PR: https://github.com/llvm/llvm-project/pull/213471
1335 lines
49 KiB
C++
1335 lines
49 KiB
C++
//===- Context.cpp - State Tracking for llubi -----------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file tracks the global states (e.g., memory) of the interpreter.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Context.h"
|
|
#include "llvm/IR/GetElementPtrTypeIterator.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/Support/MathExtras.h"
|
|
|
|
namespace llvm::ubi {
|
|
|
|
Context::Context(Module &M, const AsmParserContext *ParserContext)
|
|
: Ctx(M.getContext()), M(M), ParserContext(ParserContext),
|
|
DL(M.getDataLayout()), TLIImpl(M.getTargetTriple()) {}
|
|
|
|
Context::~Context() = default;
|
|
|
|
bool Context::initGlobalValues() {
|
|
// Register all function and block targets that may be used by indirect calls
|
|
// and branches.
|
|
for (Function &F : M) {
|
|
if (F.hasAddressTaken()) {
|
|
// TODO: Use precise alignment for function pointers if it is necessary.
|
|
auto FuncObj = allocate(0, F.getPointerAlignment(DL).value(), F.getName(),
|
|
DL.getProgramAddressSpace(), MemInitKind::Zeroed,
|
|
MemAllocKind::Global, /*IsIRGlobalValue=*/true);
|
|
if (!FuncObj)
|
|
return false;
|
|
ValidFuncTargets.try_emplace(FuncObj->getAddress(),
|
|
std::make_pair(&F, FuncObj));
|
|
FuncAddrMap.try_emplace(&F, deriveFromMemoryObject(FuncObj));
|
|
}
|
|
|
|
for (BasicBlock &BB : F) {
|
|
if (!BB.hasAddressTaken())
|
|
continue;
|
|
auto BlockObj = allocate(0, 1, BB.getName(), DL.getProgramAddressSpace(),
|
|
MemInitKind::Zeroed, MemAllocKind::BlockAddress);
|
|
if (!BlockObj)
|
|
return false;
|
|
ValidBlockTargets.try_emplace(BlockObj->getAddress(),
|
|
std::make_pair(&BB, BlockObj));
|
|
BlockAddrMap.try_emplace(&BB, deriveFromMemoryObject(BlockObj));
|
|
}
|
|
}
|
|
|
|
for (GlobalVariable &GV : M.globals()) {
|
|
Type *ValueTy = GV.getValueType();
|
|
const uint64_t Size = getEffectiveTypeAllocSize(ValueTy);
|
|
Align Alignment = GV.getPointerAlignment(DL);
|
|
auto InitKind =
|
|
GV.hasInitializer() ? MemInitKind::Zeroed : MemInitKind::Uninitialized;
|
|
const auto Obj =
|
|
allocate(Size, Alignment.value(), GV.getName(), GV.getAddressSpace(),
|
|
InitKind, MemAllocKind::Global, /*IsIRGlobalValue=*/true);
|
|
|
|
if (!Obj)
|
|
return false;
|
|
|
|
Obj->setIsConstant(GV.isConstant());
|
|
GlobalAddrMap.try_emplace(&GV, deriveFromMemoryObject(Obj));
|
|
}
|
|
|
|
for (GlobalVariable &GV : M.globals()) {
|
|
if (!GV.hasInitializer())
|
|
continue;
|
|
|
|
MemoryObject *Obj = GlobalAddrMap.at(&GV).provenance().getMemoryObject();
|
|
assert(Obj && "global pointer should have memory object provenance");
|
|
|
|
Constant *Init = GV.getInitializer();
|
|
|
|
const AnyValue *InitVal = getConstantValue(Init);
|
|
if (!InitVal)
|
|
return false;
|
|
|
|
store(*Obj, 0, *InitVal, GV.getValueType());
|
|
resetNoncacheableConstantBuffer();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
MaterializedConstant Context::getConstantValueImpl(Constant *C) {
|
|
if (isa<PoisonValue>(C))
|
|
return MaterializedConstant(AnyValue::getPoisonValue(*this, C->getType()),
|
|
/*Cacheable=*/true);
|
|
|
|
if (isa<UndefValue>(C)) {
|
|
// We treat undef as a freshly freeze poison.
|
|
auto Value = AnyValue::getPoisonValue(*this, C->getType());
|
|
freeze(Value, C->getType());
|
|
return MaterializedConstant(std::move(Value), /*Cacheable=*/false);
|
|
}
|
|
|
|
if (isa<ConstantAggregateZero>(C))
|
|
return MaterializedConstant(AnyValue::getNullValue(*this, C->getType()),
|
|
/*Cacheable=*/true);
|
|
|
|
if (isa<ConstantPointerNull>(C))
|
|
return MaterializedConstant(AnyValue::getNullValue(*this, C->getType()),
|
|
/*Cacheable=*/true);
|
|
|
|
if (auto *CI = dyn_cast<ConstantInt>(C)) {
|
|
if (auto *VecTy = dyn_cast<VectorType>(CI->getType()))
|
|
return MaterializedConstant(
|
|
std::vector<AnyValue>(getEVL(VecTy->getElementCount()),
|
|
AnyValue(CI->getValue())),
|
|
/*Cacheable=*/true);
|
|
return MaterializedConstant(CI->getValue(), /*Cacheable=*/true);
|
|
}
|
|
|
|
if (auto *CFP = dyn_cast<ConstantFP>(C)) {
|
|
if (auto *VecTy = dyn_cast<VectorType>(CFP->getType()))
|
|
return MaterializedConstant(
|
|
std::vector<AnyValue>(getEVL(VecTy->getElementCount()),
|
|
AnyValue(CFP->getValue())),
|
|
/*Cacheable=*/true);
|
|
return MaterializedConstant(CFP->getValue(), /*Cacheable=*/true);
|
|
}
|
|
|
|
if (auto *CB = dyn_cast<ConstantByte>(C)) {
|
|
if (auto *VecTy = dyn_cast<VectorType>(CB->getType()))
|
|
return MaterializedConstant(
|
|
std::vector<AnyValue>(
|
|
getEVL(VecTy->getElementCount()),
|
|
AnyValue(ByteValue(CB->getValue(), DL.isLittleEndian()))),
|
|
/*Cacheable=*/true);
|
|
return MaterializedConstant(ByteValue(CB->getValue(), DL.isLittleEndian()),
|
|
/*Cacheable=*/true);
|
|
}
|
|
|
|
if (auto *CDS = dyn_cast<ConstantDataSequential>(C)) {
|
|
std::vector<AnyValue> Elts;
|
|
Elts.reserve(CDS->getNumElements());
|
|
bool Cacheable = true;
|
|
for (uint32_t I = 0, E = CDS->getNumElements(); I != E; ++I) {
|
|
auto Elt = getConstantValue(CDS->getElementAsConstant(I));
|
|
if (!Elt)
|
|
return std::nullopt;
|
|
Cacheable &= Elt->isCacheable();
|
|
Elts.push_back(*Elt);
|
|
}
|
|
return MaterializedConstant(std::move(Elts), Cacheable);
|
|
}
|
|
|
|
if (auto *CA = dyn_cast<ConstantAggregate>(C)) {
|
|
std::vector<AnyValue> Elts;
|
|
Elts.reserve(CA->getNumOperands());
|
|
bool Cacheable = true;
|
|
for (uint32_t I = 0, E = CA->getNumOperands(); I != E; ++I) {
|
|
auto Elt = getConstantValue(CA->getOperand(I));
|
|
if (!Elt)
|
|
return std::nullopt;
|
|
Cacheable &= Elt->isCacheable();
|
|
Elts.push_back(*Elt);
|
|
}
|
|
return MaterializedConstant(std::move(Elts), Cacheable);
|
|
}
|
|
|
|
if (auto *BA = dyn_cast<BlockAddress>(C))
|
|
return MaterializedConstant(BlockAddrMap.at(BA->getBasicBlock()),
|
|
/*Cacheable=*/true);
|
|
|
|
if (auto *GV = dyn_cast<GlobalVariable>(C))
|
|
return MaterializedConstant(GlobalAddrMap.at(GV), /*Cacheable=*/true);
|
|
|
|
if (auto *F = dyn_cast<Function>(C))
|
|
return MaterializedConstant(FuncAddrMap.at(F), /*Cacheable=*/true);
|
|
|
|
if (auto *CE = dyn_cast<ConstantExpr>(C))
|
|
return evaluateConstantExpression(CE);
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
MaterializedConstant Context::evaluateConstantExpression(ConstantExpr *CE) {
|
|
unsigned Opc = CE->getOpcode();
|
|
switch (Opc) {
|
|
case Instruction::Trunc: {
|
|
const auto *Src = getConstantValue(CE->getOperand(0));
|
|
if (!Src)
|
|
return std::nullopt;
|
|
if (Src->isPoison())
|
|
return MaterializedConstant(AnyValue::poison(), Src->isCacheable());
|
|
unsigned BitWidth = CE->getType()->getScalarSizeInBits();
|
|
if (Src->isInteger())
|
|
return MaterializedConstant(Src->asInteger().trunc(BitWidth),
|
|
Src->isCacheable());
|
|
std::vector<AnyValue> Vec = Src->asAggregate();
|
|
for (auto &V : Vec) {
|
|
if (V.isInteger())
|
|
V = V.asInteger().trunc(BitWidth);
|
|
}
|
|
return MaterializedConstant(std::move(Vec), Src->isCacheable());
|
|
}
|
|
case Instruction::BitCast: {
|
|
Constant *SrcOp = CE->getOperand(0);
|
|
const auto *Src = getConstantValue(SrcOp);
|
|
if (!Src)
|
|
return std::nullopt;
|
|
SmallVector<Byte> Bytes;
|
|
Bytes.resize(getEffectiveTypeStoreSize(CE->getType()), Byte::concrete(0));
|
|
toBytes(*Src, SrcOp->getType(), Bytes);
|
|
return MaterializedConstant(fromBytes(Bytes, CE->getType()),
|
|
Src->isCacheable());
|
|
}
|
|
case Instruction::InsertElement: {
|
|
const auto *Src = getConstantValue(CE->getOperand(0));
|
|
if (!Src)
|
|
return std::nullopt;
|
|
const auto *Val = getConstantValue(CE->getOperand(1));
|
|
if (!Val)
|
|
return std::nullopt;
|
|
const auto *Idx = getConstantValue(CE->getOperand(2));
|
|
if (!Idx)
|
|
return std::nullopt;
|
|
auto &SrcVec = Src->asAggregate();
|
|
bool Cacheable =
|
|
Src->isCacheable() && Val->isCacheable() && Idx->isCacheable();
|
|
if (Idx->isPoison() || Idx->asInteger().uge(SrcVec.size()))
|
|
return MaterializedConstant(
|
|
AnyValue::getPoisonValue(*this, CE->getType()), Cacheable);
|
|
std::vector<AnyValue> ResVec = SrcVec;
|
|
ResVec[Idx->asInteger().getZExtValue()] = *Val;
|
|
return MaterializedConstant(std::move(ResVec), Cacheable);
|
|
}
|
|
case Instruction::ExtractElement: {
|
|
const auto *Src = getConstantValue(CE->getOperand(0));
|
|
if (!Src)
|
|
return std::nullopt;
|
|
const auto *Idx = getConstantValue(CE->getOperand(1));
|
|
if (!Idx)
|
|
return std::nullopt;
|
|
auto &SrcVec = Src->asAggregate();
|
|
bool Cacheable = Src->isCacheable() && Idx->isCacheable();
|
|
if (Idx->isPoison() || Idx->asInteger().uge(SrcVec.size()))
|
|
return MaterializedConstant(
|
|
AnyValue::getPoisonValue(*this, CE->getType()), Cacheable);
|
|
return MaterializedConstant(SrcVec[Idx->asInteger().getZExtValue()],
|
|
Cacheable);
|
|
}
|
|
case Instruction::ShuffleVector: {
|
|
const auto *LHS = getConstantValue(CE->getOperand(0));
|
|
if (!LHS)
|
|
return std::nullopt;
|
|
const auto *RHS = getConstantValue(CE->getOperand(1));
|
|
if (!RHS)
|
|
return std::nullopt;
|
|
auto &LHSVec = LHS->asAggregate();
|
|
auto &RHSVec = RHS->asAggregate();
|
|
uint32_t Size = cast<VectorType>(CE->getOperand(0)->getType())
|
|
->getElementCount()
|
|
.getKnownMinValue();
|
|
std::vector<AnyValue> Res;
|
|
uint32_t DstLen =
|
|
getEVL(cast<VectorType>(CE->getType())->getElementCount());
|
|
Res.reserve(DstLen);
|
|
uint32_t Stride = CE->getShuffleMask().size();
|
|
// For scalable vectors, we need to repeat the shuffle mask until we fill
|
|
// the destination vector.
|
|
for (uint32_t Off = 0; Off != DstLen; Off += Stride) {
|
|
for (int Idx : CE->getShuffleMask()) {
|
|
if (Idx == PoisonMaskElem)
|
|
Res.push_back(
|
|
AnyValue::getPoisonValue(*this, CE->getType()->getScalarType()));
|
|
else if (Idx < static_cast<int>(Size))
|
|
Res.push_back(LHSVec[Idx]);
|
|
else
|
|
Res.push_back(RHSVec[Idx - Size]);
|
|
}
|
|
}
|
|
return MaterializedConstant(std::move(Res),
|
|
LHS->isCacheable() && RHS->isCacheable());
|
|
}
|
|
case Instruction::GetElementPtr: {
|
|
// Temporary variable for reference to poison values when the subexpression
|
|
// cannot be evaluated. As the reference will be consumed immediately, we
|
|
// don't need to store them into a list.
|
|
AnyValue PoisonValue;
|
|
bool Cacheable = true;
|
|
AnyValue Res =
|
|
computeGEP(*cast<GEPOperator>(CE), [&](Value *V) -> const AnyValue & {
|
|
const auto *Val = getConstantValue(cast<Constant>(V));
|
|
if (Val) {
|
|
Cacheable &= Val->isCacheable();
|
|
return *Val;
|
|
}
|
|
PoisonValue = AnyValue::getPoisonValue(*this, V->getType());
|
|
return PoisonValue;
|
|
});
|
|
if (!PoisonValue.isNone())
|
|
return std::nullopt;
|
|
return MaterializedConstant(std::move(Res), Cacheable);
|
|
}
|
|
case Instruction::PtrToAddr:
|
|
case Instruction::PtrToInt: {
|
|
const auto *Src = getConstantValue(CE->getOperand(0));
|
|
if (!Src)
|
|
return std::nullopt;
|
|
bool Cacheable = Opc == Instruction::PtrToAddr && Src->isCacheable();
|
|
if (Src->isPoison())
|
|
return MaterializedConstant(AnyValue::poison(), Cacheable);
|
|
unsigned BitWidth = CE->getType()->getScalarSizeInBits();
|
|
if (Src->isPointer()) {
|
|
if (Opc == Instruction::PtrToInt)
|
|
exposeProvenance(Src->asPointer().provenance());
|
|
return MaterializedConstant(
|
|
Src->asPointer().address().zextOrTrunc(BitWidth), Cacheable);
|
|
}
|
|
std::vector<AnyValue> Vec = Src->asAggregate();
|
|
for (auto &V : Vec) {
|
|
if (V.isPointer()) {
|
|
if (Opc == Instruction::PtrToInt)
|
|
exposeProvenance(V.asPointer().provenance());
|
|
V = V.asPointer().address().zextOrTrunc(BitWidth);
|
|
}
|
|
}
|
|
return MaterializedConstant(std::move(Vec), Cacheable);
|
|
}
|
|
case Instruction::IntToPtr: {
|
|
const auto *Src = getConstantValue(CE->getOperand(0));
|
|
if (!Src)
|
|
return std::nullopt;
|
|
if (Src->isPoison())
|
|
return MaterializedConstant(AnyValue::poison(), /*Cacheable=*/false);
|
|
unsigned BitWidth =
|
|
DL.getPointerSizeInBits(CE->getType()->getPointerAddressSpace());
|
|
if (Src->isInteger())
|
|
return MaterializedConstant(
|
|
Pointer(getWildcardProvenance(),
|
|
Src->asInteger().zextOrTrunc(BitWidth)),
|
|
/*Cacheable=*/false);
|
|
std::vector<AnyValue> Vec = Src->asAggregate();
|
|
for (auto &V : Vec) {
|
|
if (V.isInteger())
|
|
V = Pointer(getWildcardProvenance(),
|
|
V.asInteger().zextOrTrunc(BitWidth));
|
|
}
|
|
return MaterializedConstant(std::move(Vec), /*Cacheable=*/false);
|
|
}
|
|
case Instruction::AddrSpaceCast:
|
|
return std::nullopt;
|
|
default:
|
|
assert(Instruction::isBinaryOp(Opc) && "Must be binary operator?");
|
|
const auto *LHS = getConstantValue(CE->getOperand(0));
|
|
if (!LHS)
|
|
return std::nullopt;
|
|
const auto *RHS = getConstantValue(CE->getOperand(1));
|
|
if (!RHS)
|
|
return std::nullopt;
|
|
|
|
bool HasNUW = false;
|
|
bool HasNSW = false;
|
|
if (auto *OBO = dyn_cast<OverflowingBinaryOperator>(CE)) {
|
|
HasNUW = OBO->hasNoUnsignedWrap();
|
|
HasNSW = OBO->hasNoSignedWrap();
|
|
}
|
|
|
|
auto ScalarEval = [&](const AnyValue &LHS,
|
|
const AnyValue &RHS) -> AnyValue {
|
|
if (LHS.isPoison() || RHS.isPoison())
|
|
return AnyValue::poison();
|
|
auto &LHSVal = LHS.asInteger();
|
|
auto &RHSVal = RHS.asInteger();
|
|
switch (Opc) {
|
|
case Instruction::Add:
|
|
return addNoWrap(LHSVal, RHSVal, HasNSW, HasNUW);
|
|
case Instruction::Sub:
|
|
return subNoWrap(LHSVal, RHSVal, HasNSW, HasNUW);
|
|
case Instruction::Xor:
|
|
return LHSVal ^ RHSVal;
|
|
default:
|
|
llvm_unreachable("Unsupported opcode in constant expression.");
|
|
}
|
|
};
|
|
|
|
bool Cacheable = LHS->isCacheable() && RHS->isCacheable();
|
|
|
|
if (CE->getType()->isVectorTy()) {
|
|
auto &LHSVec = LHS->asAggregate();
|
|
auto &RHSVec = RHS->asAggregate();
|
|
std::vector<AnyValue> ResVec;
|
|
ResVec.reserve(LHSVec.size());
|
|
for (const auto &[ScalarLHS, ScalarRHS] : zip(LHSVec, RHSVec))
|
|
ResVec.push_back(ScalarEval(ScalarLHS, ScalarRHS));
|
|
return MaterializedConstant(std::move(ResVec), Cacheable);
|
|
}
|
|
|
|
return MaterializedConstant(ScalarEval(*LHS, *RHS), Cacheable);
|
|
}
|
|
}
|
|
|
|
const MaterializedConstant *Context::getConstantValue(Constant *C) {
|
|
auto It = ConstCache.find(C);
|
|
if (It != ConstCache.end())
|
|
return &It->second;
|
|
|
|
MaterializedConstant Val = getConstantValueImpl(C);
|
|
if (Val.isNone())
|
|
return nullptr;
|
|
if (!Val.isCacheable()) {
|
|
assert(NoncacheableConstCount <= 1024 && "Unbounded temporary buffer.");
|
|
++NoncacheableConstCount;
|
|
return new (NoncacheableConstBuffer.Allocate())
|
|
MaterializedConstant(std::move(Val));
|
|
}
|
|
|
|
return &ConstCache.emplace(C, std::move(Val)).first->second;
|
|
}
|
|
|
|
void Context::resetNoncacheableConstantBuffer() {
|
|
NoncacheableConstBuffer.DestroyAll();
|
|
NoncacheableConstCount = 0;
|
|
}
|
|
|
|
APInt Context::getTag(uint32_t BitWidth, Provenance &Prov) {
|
|
// Nullary provenance.
|
|
if (!Prov.getMemoryObject())
|
|
return APInt::getZero(BitWidth);
|
|
// The tag is already initialized.
|
|
if (!Prov.getTag().isZero())
|
|
return Prov.getTag();
|
|
|
|
// FIXME: This doesn't work when the address space is too small.
|
|
while (true) {
|
|
APInt Tag = generateRandomAPInt(BitWidth);
|
|
if (Tag.isZero() || !TaggedProvenances.try_emplace(Tag, &Prov).second)
|
|
continue;
|
|
Prov.setTag(Tag);
|
|
Prov.getMemoryObject()->AssociatedTags.push_back(Tag);
|
|
return Tag;
|
|
}
|
|
}
|
|
|
|
AnyValue Context::fromBytes(ConstBytesView Bytes, Type *Ty,
|
|
uint32_t OffsetInBits, bool CheckPaddingBits,
|
|
bool *ContainsUndefinedBits) {
|
|
uint32_t NumBits = DL.getTypeSizeInBits(Ty).getFixedValue();
|
|
uint32_t NewOffsetInBits = OffsetInBits + NumBits;
|
|
if (CheckPaddingBits)
|
|
NewOffsetInBits = alignTo(NewOffsetInBits, 8);
|
|
bool NeedsPadding = NewOffsetInBits != OffsetInBits + NumBits;
|
|
uint32_t NumBitsToExtract = NewOffsetInBits - OffsetInBits;
|
|
uint32_t NumWords = APInt::getNumWords(NumBitsToExtract);
|
|
constexpr uint32_t WordBits = APInt::APINT_BITS_PER_WORD;
|
|
SmallVector<APInt::WordType> RawBits;
|
|
bool IsByteType = Ty->isByteTy();
|
|
// LogicalBytes is always little-endian.
|
|
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())
|
|
RawTagBits.resize(NumWords);
|
|
for (uint32_t I = 0; I < NumBitsToExtract; I += 8) {
|
|
// Try to form a 'logical' byte that represents the bits in the range
|
|
// [BitsStart, BitsEnd].
|
|
uint32_t NumBitsInByte = std::min(8U, NumBitsToExtract - I);
|
|
uint32_t BitsStart = OffsetInBits + I;
|
|
uint32_t BitsEnd = BitsStart + NumBitsInByte - 1;
|
|
Byte LogicalByte;
|
|
// Check whether it is a cross-byte access.
|
|
if (((BitsStart ^ BitsEnd) & ~7) == 0)
|
|
LogicalByte = Bytes[BitsStart / 8].lshr(BitsStart % 8);
|
|
else
|
|
LogicalByte =
|
|
Byte::fshr(Bytes[BitsStart / 8], Bytes[BitsEnd / 8], BitsStart % 8);
|
|
|
|
uint32_t Mask = (1U << NumBitsInByte) - 1;
|
|
// If any of the bits in the byte is poison, the whole value is poison.
|
|
if (!IsByteType &&
|
|
(~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask)) {
|
|
if (ContainsUndefinedBits)
|
|
*ContainsUndefinedBits = true;
|
|
OffsetInBits = NewOffsetInBits;
|
|
return AnyValue::poison();
|
|
}
|
|
uint8_t RandomBits = 0;
|
|
if (~LogicalByte.ConcreteMask & Mask) {
|
|
// This byte contains undef bits.
|
|
if (ContainsUndefinedBits)
|
|
*ContainsUndefinedBits = true;
|
|
|
|
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) {
|
|
// FIXME: Currently we treat undef bits as poison bits in the byte value,
|
|
// because keeping undef bits reintroduces undef SSA values.
|
|
// It should be fixed by completely removing undef bits from the memory.
|
|
LogicalByte.poisonBits(~LogicalByte.ConcreteMask);
|
|
LogicalBytes[I / 8] = LogicalByte;
|
|
continue;
|
|
}
|
|
|
|
uint8_t ActualBits = ((LogicalByte.Value & LogicalByte.ConcreteMask) |
|
|
(RandomBits & ~LogicalByte.ConcreteMask)) &
|
|
Mask;
|
|
RawBits[I / WordBits] |= static_cast<APInt::WordType>(ActualBits)
|
|
<< (I % WordBits);
|
|
if (IsTagValid) {
|
|
assert((LogicalByte.TagMask & LogicalByte.ConcreteMask) ==
|
|
LogicalByte.TagMask);
|
|
if ((LogicalByte.TagMask & Mask) == Mask) {
|
|
uint8_t ActualTagBits = LogicalByte.TagValue & Mask;
|
|
RawTagBits[I / WordBits] |= static_cast<APInt::WordType>(ActualTagBits)
|
|
<< (I % WordBits);
|
|
} else {
|
|
IsTagValid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
OffsetInBits = NewOffsetInBits;
|
|
|
|
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), DL.isLittleEndian());
|
|
}
|
|
|
|
APInt Bits(NumBitsToExtract, RawBits);
|
|
|
|
// Padding bits for non-byte-sized scalar types must be zero.
|
|
if (NeedsPadding) {
|
|
if (!Bits.isIntN(NumBits)) {
|
|
if (ContainsUndefinedBits)
|
|
*ContainsUndefinedBits = true;
|
|
return AnyValue::poison();
|
|
}
|
|
Bits = Bits.trunc(NumBits);
|
|
}
|
|
|
|
if (Ty->isIntegerTy())
|
|
return Bits;
|
|
if (Ty->isFloatingPointTy())
|
|
return APFloat(Ty->getFltSemantics(), Bits);
|
|
assert(Ty->isPointerTy() && "Expect a pointer type");
|
|
// Try to recover provenance from the tag.
|
|
if (IsTagValid) {
|
|
APInt Tag(NumBitsToExtract, RawTagBits);
|
|
if (auto Prov = TaggedProvenances.lookup(Tag))
|
|
return Pointer(std::move(Prov), Bits);
|
|
}
|
|
return Pointer(Bits);
|
|
}
|
|
|
|
AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
|
|
bool *ContainsUndefinedBits) {
|
|
assert(Bytes.size() == getEffectiveTypeStoreSize(Ty) &&
|
|
"Invalid byte array size for the type");
|
|
if (Ty->isIntegerTy() || Ty->isFloatingPointTy() || Ty->isPointerTy())
|
|
return fromBytes(ConstBytesView(Bytes, DL), Ty, /*OffsetInBits=*/0,
|
|
/*CheckPaddingBits=*/true, ContainsUndefinedBits);
|
|
if (Ty->isByteTy()) {
|
|
unsigned BitWidth = Ty->getByteBitWidth();
|
|
if (BitWidth & 7) {
|
|
if (!(DL.isLittleEndian() ? Bytes.back() : Bytes.front())
|
|
.areHighBitsZExtd(BitWidth & 7)) {
|
|
if (ContainsUndefinedBits)
|
|
*ContainsUndefinedBits = true;
|
|
return ByteValue::poison(BitWidth, DL.isLittleEndian());
|
|
}
|
|
}
|
|
ByteValue Res(BitWidth, Bytes, DL.isLittleEndian());
|
|
bool HasUndefinedBits = false;
|
|
for (Byte &V : Res.mutableBytes()) {
|
|
if (V.ConcreteMask != 255)
|
|
HasUndefinedBits = true;
|
|
V.poisonBits(~V.ConcreteMask);
|
|
}
|
|
if (ContainsUndefinedBits)
|
|
*ContainsUndefinedBits = HasUndefinedBits;
|
|
return AnyValue(std::move(Res));
|
|
}
|
|
|
|
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
|
|
Type *ElemTy = VecTy->getElementType();
|
|
uint32_t ElemBits = DL.getTypeSizeInBits(ElemTy).getFixedValue();
|
|
uint32_t NumElements = getEVL(VecTy->getElementCount());
|
|
// Check padding bits. <N x iM> acts as if an integer type with N * M bits.
|
|
uint32_t VecBits = ElemBits * NumElements;
|
|
uint32_t AlignedVecBits = alignTo(VecBits, 8);
|
|
ConstBytesView View(Bytes, DL);
|
|
if (VecBits != AlignedVecBits) {
|
|
const Byte &PaddingByte = View[Bytes.size() - 1];
|
|
uint32_t Mask = (~0U << (VecBits % 8)) & 255U;
|
|
// Make sure all high padding bits are zero.
|
|
if ((PaddingByte.ConcreteMask & ~PaddingByte.Value & Mask) != Mask) {
|
|
if (ContainsUndefinedBits)
|
|
*ContainsUndefinedBits = true;
|
|
return AnyValue::getPoisonValue(*this, Ty);
|
|
}
|
|
}
|
|
|
|
std::vector<AnyValue> ValVec;
|
|
ValVec.reserve(NumElements);
|
|
// For little endian element zero is put in the least significant bits of
|
|
// the integer, and for big endian element zero is put in the most
|
|
// significant bits.
|
|
for (uint32_t I = 0; I != NumElements; ++I)
|
|
ValVec.push_back(
|
|
fromBytes(View, ElemTy,
|
|
DL.isLittleEndian() ? I * ElemBits
|
|
: VecBits - ElemBits - I * ElemBits,
|
|
/*CheckPaddingBits=*/false, ContainsUndefinedBits));
|
|
return AnyValue(std::move(ValVec));
|
|
}
|
|
if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
|
|
Type *ElemTy = ArrTy->getElementType();
|
|
uint64_t Stride = getEffectiveTypeAllocSize(ElemTy);
|
|
uint64_t StoreSize = getEffectiveTypeStoreSize(ElemTy);
|
|
uint32_t NumElements = ArrTy->getNumElements();
|
|
std::vector<AnyValue> ValVec;
|
|
ValVec.reserve(NumElements);
|
|
for (uint32_t I = 0; I != NumElements; ++I)
|
|
ValVec.push_back(fromBytes(Bytes.slice(I * Stride, StoreSize), ElemTy,
|
|
ContainsUndefinedBits));
|
|
return AnyValue(std::move(ValVec));
|
|
}
|
|
if (auto *StructTy = dyn_cast<StructType>(Ty)) {
|
|
const StructLayout *Layout = DL.getStructLayout(StructTy);
|
|
std::vector<AnyValue> ValVec;
|
|
uint32_t NumElements = StructTy->getNumElements();
|
|
ValVec.reserve(NumElements);
|
|
for (uint32_t I = 0; I != NumElements; ++I) {
|
|
Type *ElemTy = StructTy->getElementType(I);
|
|
ValVec.push_back(fromBytes(
|
|
Bytes.slice(getEffectiveTypeSize(Layout->getElementOffset(I)),
|
|
getEffectiveTypeStoreSize(ElemTy)),
|
|
ElemTy, ContainsUndefinedBits));
|
|
}
|
|
return AnyValue(std::move(ValVec));
|
|
}
|
|
llvm_unreachable("Unsupported first class type.");
|
|
}
|
|
|
|
void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
|
|
MutableBytesView Bytes, bool PaddingBits) {
|
|
uint32_t NumBits = DL.getTypeSizeInBits(Ty).getFixedValue();
|
|
uint32_t NewOffsetInBits = OffsetInBits + NumBits;
|
|
if (PaddingBits)
|
|
NewOffsetInBits = alignTo(NewOffsetInBits, 8);
|
|
bool NeedsPadding = NewOffsetInBits != OffsetInBits + NumBits;
|
|
auto WriteBits = [&](const APInt &Bits, const APInt *TagBits) {
|
|
for (uint32_t I = 0, E = Bits.getBitWidth(); I < E; I += 8) {
|
|
uint32_t NumBitsInByte = std::min(8U, E - I);
|
|
uint32_t BitsStart = OffsetInBits + I;
|
|
uint32_t BitsEnd = BitsStart + NumBitsInByte - 1;
|
|
uint8_t BitsVal =
|
|
static_cast<uint8_t>(Bits.extractBitsAsZExtValue(NumBitsInByte, I));
|
|
|
|
Bytes[BitsStart / 8].writeBits(
|
|
static_cast<uint8_t>(((1U << NumBitsInByte) - 1) << (BitsStart % 8)),
|
|
static_cast<uint8_t>(BitsVal << (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].writeBits(
|
|
static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
|
|
static_cast<uint8_t>(BitsVal >> (8 - (BitsStart % 8))));
|
|
|
|
if (TagBits) {
|
|
uint8_t TagBitsVal = static_cast<uint8_t>(
|
|
TagBits->extractBitsAsZExtValue(NumBitsInByte, I));
|
|
Bytes[BitsStart / 8].writeTagBits(
|
|
static_cast<uint8_t>(((1U << NumBitsInByte) - 1)
|
|
<< (BitsStart % 8)),
|
|
static_cast<uint8_t>(TagBitsVal << (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].writeTagBits(
|
|
static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
|
|
static_cast<uint8_t>(TagBitsVal >> (8 - (BitsStart % 8))));
|
|
}
|
|
}
|
|
};
|
|
if (Val.isPoison()) {
|
|
for (uint32_t I = 0, E = NewOffsetInBits - OffsetInBits; I < E;) {
|
|
uint32_t NumBitsInByte = std::min(8 - (OffsetInBits + I) % 8, E - I);
|
|
assert(((OffsetInBits ^ (OffsetInBits + NumBitsInByte - 1)) & ~7) == 0 &&
|
|
"Across byte boundary.");
|
|
Bytes[(OffsetInBits + I) / 8].poisonBits(static_cast<uint8_t>(
|
|
((1U << NumBitsInByte) - 1) << ((OffsetInBits + I) % 8)));
|
|
I += NumBitsInByte;
|
|
}
|
|
} else if (Ty->isIntegerTy()) {
|
|
auto &Bits = Val.asInteger();
|
|
WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits) : Bits,
|
|
/*TagBits=*/nullptr);
|
|
} else if (Ty->isFloatingPointTy()) {
|
|
auto Bits = Val.asFloat().bitcastToAPInt();
|
|
WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits) : Bits,
|
|
/*TagBits=*/nullptr);
|
|
} else if (Ty->isPointerTy()) {
|
|
auto &AddressBits = Val.asPointer().address();
|
|
APInt Tag = getTag(AddressBits.getBitWidth(), Val.asPointer().provenance());
|
|
if (NeedsPadding)
|
|
Tag = Tag.zext(NewOffsetInBits - 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.");
|
|
}
|
|
}
|
|
|
|
void Context::toBytes(const AnyValue &Val, Type *Ty,
|
|
MutableArrayRef<Byte> Bytes) {
|
|
assert(Bytes.size() == getEffectiveTypeStoreSize(Ty) &&
|
|
"Invalid byte array size for the type");
|
|
if (Ty->isIntegerTy() || Ty->isFloatingPointTy() || Ty->isPointerTy()) {
|
|
toBytes(Val, Ty, /*OffsetInBits=*/0, MutableBytesView(Bytes, DL),
|
|
/*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();
|
|
uint32_t ElemBits = DL.getTypeSizeInBits(ElemTy).getFixedValue();
|
|
uint32_t NumElements = getEVL(VecTy->getElementCount());
|
|
// Zero padding bits. <N x iM> acts as if an integer type with N * M bits.
|
|
uint32_t VecBits = ElemBits * NumElements;
|
|
uint32_t AlignedVecBits = alignTo(VecBits, 8);
|
|
MutableBytesView View(Bytes, DL);
|
|
if (VecBits != AlignedVecBits) {
|
|
Byte &PaddingByte = View[Bytes.size() - 1];
|
|
uint32_t Mask = (~0U << (VecBits % 8)) & 255U;
|
|
PaddingByte.zeroBits(Mask);
|
|
}
|
|
// For little endian element zero is put in the least significant bits of
|
|
// the integer, and for big endian element zero is put in the most
|
|
// significant bits.
|
|
if (DL.isLittleEndian()) {
|
|
for (const auto &[I, Val] : enumerate(Val.asAggregate()))
|
|
toBytes(Val, ElemTy, ElemBits * I, View, /*PaddingBits=*/false);
|
|
} else {
|
|
for (const auto &[I, Val] : enumerate(reverse(Val.asAggregate())))
|
|
toBytes(Val, ElemTy, ElemBits * I, View, /*PaddingBits=*/false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Fill padding bytes due to alignment requirement.
|
|
auto FillUndefBytes = [&](uint64_t Begin, uint64_t End) {
|
|
fill(Bytes.slice(Begin, End - Begin), Byte::undef());
|
|
};
|
|
if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
|
|
Type *ElemTy = ArrTy->getElementType();
|
|
uint64_t Offset = 0;
|
|
uint64_t Stride = getEffectiveTypeAllocSize(ElemTy);
|
|
uint64_t StoreSize = getEffectiveTypeStoreSize(ElemTy);
|
|
for (const auto &SubVal : Val.asAggregate()) {
|
|
toBytes(SubVal, ElemTy, Bytes.slice(Offset, StoreSize));
|
|
FillUndefBytes(Offset + StoreSize, Offset + Stride);
|
|
Offset += Stride;
|
|
}
|
|
return;
|
|
}
|
|
if (auto *StructTy = dyn_cast<StructType>(Ty)) {
|
|
const StructLayout *Layout = DL.getStructLayout(StructTy);
|
|
uint64_t LastAccessedOffset = 0;
|
|
for (uint32_t I = 0, E = Val.asAggregate().size(); I != E; ++I) {
|
|
Type *ElemTy = StructTy->getElementType(I);
|
|
uint64_t ElemOffset = getEffectiveTypeSize(Layout->getElementOffset(I));
|
|
uint64_t ElemStoreSize = getEffectiveTypeStoreSize(ElemTy);
|
|
FillUndefBytes(LastAccessedOffset, ElemOffset);
|
|
toBytes(Val.asAggregate()[I], ElemTy,
|
|
Bytes.slice(ElemOffset, ElemStoreSize));
|
|
LastAccessedOffset = ElemOffset + ElemStoreSize;
|
|
}
|
|
FillUndefBytes(LastAccessedOffset, getEffectiveTypeStoreSize(StructTy));
|
|
return;
|
|
}
|
|
|
|
llvm_unreachable("Unsupported first class type.");
|
|
}
|
|
|
|
AnyValue Context::load(MemoryObject &MO, uint64_t Offset, Type *ValTy,
|
|
bool *ContainsUndefinedBits) {
|
|
return fromBytes(
|
|
MO.getBytes().slice(Offset, getEffectiveTypeStoreSize(ValTy)), ValTy,
|
|
ContainsUndefinedBits);
|
|
}
|
|
|
|
void Context::store(MemoryObject &MO, uint64_t Offset, const AnyValue &Val,
|
|
Type *ValTy) {
|
|
toBytes(Val, ValTy,
|
|
MO.getBytes().slice(Offset, getEffectiveTypeStoreSize(ValTy)));
|
|
}
|
|
|
|
void Context::storeRawBytes(MemoryObject &MO, uint64_t Offset, const void *Data,
|
|
uint64_t Size) {
|
|
for (uint64_t I = 0; I != Size; ++I)
|
|
MO[Offset + I] = Byte::concrete(static_cast<const uint8_t *>(Data)[I]);
|
|
}
|
|
|
|
APInt Context::generateRandomAPInt(uint32_t BitWidth) {
|
|
SmallVector<APInt::WordType> RandomWords;
|
|
uint32_t NumWords = APInt::getNumWords(BitWidth);
|
|
RandomWords.reserve(NumWords);
|
|
static_assert(decltype(Rng)::word_size >=
|
|
std::numeric_limits<APInt::WordType>::digits,
|
|
"Unexpected Rng result type.");
|
|
for (uint32_t I = 0; I != NumWords; ++I)
|
|
RandomWords.push_back(static_cast<APInt::WordType>(Rng()));
|
|
return APInt(BitWidth, RandomWords);
|
|
}
|
|
|
|
void Context::freeze(AnyValue &Val, Type *Ty) {
|
|
if (Val.isPoison()) {
|
|
uint32_t Bits = DL.getTypeSizeInBits(Ty);
|
|
APInt RandomVal = mayUseNonDeterminism() ? generateRandomAPInt(Bits)
|
|
: APInt::getZero(Bits);
|
|
if (Ty->isIntegerTy())
|
|
Val = AnyValue(RandomVal);
|
|
else if (Ty->isFloatingPointTy())
|
|
Val = AnyValue(APFloat(Ty->getFltSemantics(), RandomVal));
|
|
else if (Ty->isPointerTy())
|
|
Val = AnyValue(Pointer(RandomVal));
|
|
else
|
|
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;
|
|
}
|
|
return;
|
|
}
|
|
if (Val.isAggregate()) {
|
|
auto &SubVals = Val.asAggregate();
|
|
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
|
|
Type *ElemTy = VecTy->getElementType();
|
|
for (auto &SubVal : SubVals)
|
|
freeze(SubVal, ElemTy);
|
|
} else if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
|
|
Type *ElemTy = ArrTy->getElementType();
|
|
for (auto &SubVal : SubVals)
|
|
freeze(SubVal, ElemTy);
|
|
} else if (auto *StructTy = dyn_cast<StructType>(Ty)) {
|
|
for (uint32_t I = 0, E = SubVals.size(); I != E; ++I)
|
|
freeze(SubVals[I], StructTy->getElementType(I));
|
|
} else {
|
|
llvm_unreachable("Invalid aggregate type");
|
|
}
|
|
}
|
|
}
|
|
|
|
AnyValue Context::computePtrAdd(const Pointer &Ptr, const APInt &Offset,
|
|
GEPNoWrapFlags Flags,
|
|
AnyValue &AccumulatedOffset) {
|
|
if (Offset.isZero())
|
|
return Ptr;
|
|
APInt IndexBits = Ptr.address().trunc(Offset.getBitWidth());
|
|
auto NewIndex =
|
|
addNoWrap(IndexBits, Offset, /*HasNSW=*/false, Flags.hasNoUnsignedWrap());
|
|
if (NewIndex.isPoison())
|
|
return AnyValue::poison();
|
|
if (Flags.hasNoUnsignedSignedWrap()) {
|
|
// The successive addition of the current address, truncated to the
|
|
// pointer index type and interpreted as an unsigned number, and each
|
|
// offset, interpreted as a signed number, does not wrap the pointer index
|
|
// type.
|
|
if (Offset.isNonNegative() ? NewIndex.asInteger().ult(IndexBits)
|
|
: NewIndex.asInteger().ugt(IndexBits))
|
|
return AnyValue::poison();
|
|
}
|
|
APInt NewAddr = Ptr.address();
|
|
NewAddr.insertBits(NewIndex.asInteger(), 0);
|
|
|
|
MemoryObject *MO = nullptr;
|
|
if (Flags.isInBounds()) {
|
|
MO = checkProvenance(
|
|
Ptr, [](const Provenance &) { return true; },
|
|
/*HasSideEffect=*/false);
|
|
if (!MO || !MO->inBounds(NewAddr))
|
|
return AnyValue::poison();
|
|
}
|
|
|
|
if (!AccumulatedOffset.isPoison()) {
|
|
AccumulatedOffset =
|
|
addNoWrap(AccumulatedOffset.asInteger(), Offset,
|
|
Flags.hasNoUnsignedSignedWrap(), Flags.hasNoUnsignedWrap());
|
|
if (AccumulatedOffset.isPoison())
|
|
return AnyValue::poison();
|
|
}
|
|
|
|
// Should not expose provenance here even if the new address doesn't point
|
|
// to the original object.
|
|
auto Res = Ptr.getWithNewAddr(NewAddr);
|
|
if (MO) {
|
|
auto &Prov = Res.provenance();
|
|
if (Prov.isWildcard() && !Prov.getMemoryObject())
|
|
Res = Res.getWithNewProvenance(Prov.getWithKnownMemoryObject(*MO));
|
|
}
|
|
return Res;
|
|
}
|
|
|
|
AnyValue Context::computePtrAdd(const AnyValue &Ptr, const APInt &Offset,
|
|
GEPNoWrapFlags Flags,
|
|
AnyValue &AccumulatedOffset) {
|
|
if (Ptr.isPoison())
|
|
return AnyValue::poison();
|
|
return computePtrAdd(Ptr.asPointer(), Offset, Flags, AccumulatedOffset);
|
|
}
|
|
|
|
AnyValue Context::computeScaledPtrAdd(const AnyValue &Ptr,
|
|
const AnyValue &Index, const APInt &Scale,
|
|
GEPNoWrapFlags Flags,
|
|
AnyValue &AccumulatedOffset) {
|
|
if (Ptr.isPoison() || Index.isPoison())
|
|
return AnyValue::poison();
|
|
assert(Ptr.isPointer() && Index.isInteger() && "Unexpected type.");
|
|
if (Scale.isOne())
|
|
return computePtrAdd(Ptr, Index.asInteger(), Flags, AccumulatedOffset);
|
|
auto ScaledOffset =
|
|
mulNoWrap(Index.asInteger(), Scale, Flags.hasNoUnsignedSignedWrap(),
|
|
Flags.hasNoUnsignedWrap());
|
|
if (ScaledOffset.isPoison())
|
|
return AnyValue::poison();
|
|
return computePtrAdd(Ptr, ScaledOffset.asInteger(), Flags, AccumulatedOffset);
|
|
}
|
|
|
|
static AnyValue canonicalizeIndex(const AnyValue &Idx, unsigned IndexBitWidth,
|
|
GEPNoWrapFlags Flags) {
|
|
if (Idx.isPoison())
|
|
return AnyValue::poison();
|
|
auto &IdxInt = Idx.asInteger();
|
|
if (IdxInt.getBitWidth() == IndexBitWidth)
|
|
return Idx;
|
|
if (IdxInt.getBitWidth() > IndexBitWidth) {
|
|
if (Flags.hasNoUnsignedSignedWrap() && !IdxInt.isSignedIntN(IndexBitWidth))
|
|
return AnyValue::poison();
|
|
|
|
if (Flags.hasNoUnsignedWrap() && !IdxInt.isIntN(IndexBitWidth))
|
|
return AnyValue::poison();
|
|
|
|
return IdxInt.trunc(IndexBitWidth);
|
|
}
|
|
return IdxInt.sext(IndexBitWidth);
|
|
}
|
|
|
|
AnyValue
|
|
Context::computeGEP(GEPOperator &GEP,
|
|
function_ref<const AnyValue &(Value *V)> GetValue) {
|
|
uint32_t IndexBitWidth =
|
|
DL.getIndexSizeInBits(GEP.getType()->getPointerAddressSpace());
|
|
GEPNoWrapFlags Flags = GEP.getNoWrapFlags();
|
|
AnyValue Res = GetValue(GEP.getPointerOperand());
|
|
AnyValue AccumulatedOffset = APInt(IndexBitWidth, 0);
|
|
if (Res.isAggregate())
|
|
AccumulatedOffset =
|
|
AnyValue::getVectorSplat(AccumulatedOffset, Res.asAggregate().size());
|
|
auto ApplyScaledOffset = [&](const AnyValue &Index, const APInt &Scale) {
|
|
if (Index.isAggregate() && !Res.isAggregate()) {
|
|
Res = AnyValue::getVectorSplat(Res, Index.asAggregate().size());
|
|
AccumulatedOffset = AnyValue::getVectorSplat(AccumulatedOffset,
|
|
Index.asAggregate().size());
|
|
}
|
|
if (Index.isAggregate() && Res.isAggregate()) {
|
|
for (auto &&[ResElem, IndexElem, OffsetElem] :
|
|
zip(Res.asAggregate(), Index.asAggregate(),
|
|
AccumulatedOffset.asAggregate()))
|
|
ResElem = computeScaledPtrAdd(
|
|
ResElem, canonicalizeIndex(IndexElem, IndexBitWidth, Flags), Scale,
|
|
Flags, OffsetElem);
|
|
} else {
|
|
AnyValue CanonicalIndex = canonicalizeIndex(Index, IndexBitWidth, Flags);
|
|
if (Res.isAggregate()) {
|
|
for (auto &&[ResElem, OffsetElem] :
|
|
zip(Res.asAggregate(), AccumulatedOffset.asAggregate()))
|
|
ResElem = computeScaledPtrAdd(ResElem, CanonicalIndex, Scale, Flags,
|
|
OffsetElem);
|
|
} else {
|
|
Res = computeScaledPtrAdd(Res, CanonicalIndex, Scale, Flags,
|
|
AccumulatedOffset);
|
|
}
|
|
}
|
|
};
|
|
|
|
for (gep_type_iterator GTI = gep_type_begin(GEP), GTE = gep_type_end(GEP);
|
|
GTI != GTE; ++GTI) {
|
|
Value *V = GTI.getOperand();
|
|
|
|
// Fast path for zero offsets.
|
|
if (auto *CI = dyn_cast<ConstantInt>(V)) {
|
|
if (CI->isZero())
|
|
continue;
|
|
}
|
|
if (isa<ConstantAggregateZero>(V))
|
|
continue;
|
|
|
|
// Handle a struct index, which adds its field offset to the pointer.
|
|
if (StructType *STy = GTI.getStructTypeOrNull()) {
|
|
unsigned ElementIdx = cast<ConstantInt>(V)->getZExtValue();
|
|
const StructLayout *SL = DL.getStructLayout(STy);
|
|
// Element offset is in bytes.
|
|
ApplyScaledOffset(APInt(IndexBitWidth, SL->getElementOffset(ElementIdx),
|
|
/*isSigned=*/false, /*implicitTrunc=*/true),
|
|
APInt(IndexBitWidth, 1));
|
|
continue;
|
|
}
|
|
|
|
// Truncate if type size exceeds index space.
|
|
// TODO: Should be documented in LangRef: GEPs with nowrap flags should
|
|
// return poison when the type size exceeds index space.
|
|
TypeSize Offset = GTI.getSequentialElementStride(DL);
|
|
APInt Scale(IndexBitWidth, getEffectiveTypeSize(Offset),
|
|
/*isSigned=*/false, /*implicitTrunc=*/true);
|
|
if (!Scale.isZero())
|
|
ApplyScaledOffset(GetValue(V), Scale);
|
|
}
|
|
return Res;
|
|
}
|
|
|
|
MemoryObject::~MemoryObject() = default;
|
|
MemoryObject::MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name,
|
|
unsigned AS, MemInitKind InitKind,
|
|
MemAllocKind AllocKind, bool IsIRGlobalValue)
|
|
: Address(Addr), Size(Size), Name(Name), AS(AS),
|
|
State(InitKind != MemInitKind::Poisoned ? MemoryObjectState::Alive
|
|
: MemoryObjectState::Dead),
|
|
AllocKind(AllocKind), IsIRGlobalValue(IsIRGlobalValue) {
|
|
switch (InitKind) {
|
|
case MemInitKind::Zeroed:
|
|
Bytes.resize(Size, Byte::concrete(0));
|
|
break;
|
|
case MemInitKind::Uninitialized:
|
|
Bytes.resize(Size, Byte::undef());
|
|
break;
|
|
case MemInitKind::Poisoned:
|
|
Bytes.resize(Size, Byte::poison());
|
|
break;
|
|
}
|
|
}
|
|
|
|
IntrusiveRefCntPtr<MemoryObject>
|
|
Context::allocate(uint64_t Size, uint64_t Align, StringRef Name, unsigned AS,
|
|
MemInitKind InitKind, MemAllocKind AllocKind,
|
|
bool IsIRGlobalValue) {
|
|
// Even if the memory object is zero-sized, it still occupies a byte to obtain
|
|
// a unique address.
|
|
uint64_t AllocateSize = std::max(Size, (uint64_t)1);
|
|
if (MaxMem != 0 && SaturatingAdd(UsedMem, AllocateSize) >= MaxMem)
|
|
return nullptr;
|
|
uint64_t AlignedAddr = alignTo(AllocationBase, Align);
|
|
auto MemObj = makeIntrusiveRefCnt<MemoryObject>(
|
|
AlignedAddr, Size, Name, AS, InitKind, AllocKind, IsIRGlobalValue);
|
|
MemoryObjects[AlignedAddr] = MemObj;
|
|
// Extra padding to make sure getWildcardProvenance resolves to at most one
|
|
// memory object.
|
|
AllocationBase = AlignedAddr + AllocateSize + 1;
|
|
UsedMem += AllocateSize;
|
|
return MemObj;
|
|
}
|
|
|
|
bool Context::free(const MemoryObject &Obj) {
|
|
uint64_t Address = Obj.getAddress();
|
|
auto It = MemoryObjects.find(Address);
|
|
if (It == MemoryObjects.end() || It->second.get() != &Obj)
|
|
return false;
|
|
|
|
UsedMem -= std::max(It->second->getSize(), static_cast<uint64_t>(1));
|
|
|
|
MemoryObject &MutableObj = *It->second;
|
|
MutableObj.State = MemoryObjectState::Freed;
|
|
MutableObj.Bytes.clear();
|
|
for (const APInt &Tag : MutableObj.AssociatedTags)
|
|
TaggedProvenances.erase(Tag);
|
|
MutableObj.AssociatedTags.clear();
|
|
ExposedProvenances.erase(Address);
|
|
|
|
MemoryObjects.erase(It);
|
|
return true;
|
|
}
|
|
|
|
Pointer Context::deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj) {
|
|
assert(Obj && "Cannot determine the address space of a null memory object");
|
|
return Pointer(makeIntrusiveRefCnt<Provenance>(Obj),
|
|
APInt(DL.getPointerSizeInBits(Obj->getAddressSpace()),
|
|
Obj->getAddress()));
|
|
}
|
|
|
|
void Context::exposeProvenance(Provenance &Prov) {
|
|
if (Prov.Wildcard)
|
|
return;
|
|
MemoryObject *Obj = Prov.getMemoryObject();
|
|
if (!Obj)
|
|
return;
|
|
uint64_t Address = Obj->getAddress();
|
|
ExposedProvenanceSet &Set = ExposedProvenances[Address];
|
|
if (Set.Set.insert(&Prov).second)
|
|
Set.List.push_back({&Prov, ++ExposedProvenanceSetGeneration});
|
|
}
|
|
|
|
MemoryObject *
|
|
Context::checkProvenance(const Pointer &Ptr,
|
|
function_ref<bool(const Provenance &)> Check,
|
|
bool HasSideEffect) {
|
|
auto &Prov = Ptr.provenance();
|
|
if (!Check(Prov))
|
|
return nullptr;
|
|
// Early return for concrete provenances.
|
|
if (!Prov.Wildcard)
|
|
return Prov.Obj.get();
|
|
|
|
MemoryObject *MO = nullptr;
|
|
APInt &Mask = Prov.Wildcard->ActiveMask;
|
|
SmallVector<ExposedProvenance> *List = nullptr;
|
|
uint32_t ProvenanceCount = 0;
|
|
if (Mask.isZero()) {
|
|
// The memory object hasn't been determined.
|
|
uint64_t Addr = Ptr.address().getLimitedValue();
|
|
auto Iter = ExposedProvenances.upper_bound(Addr);
|
|
if (Iter == ExposedProvenances.begin())
|
|
return nullptr;
|
|
auto &[BaseAddress, Set] = *std::prev(Iter);
|
|
auto &Obj = MemoryObjects.at(BaseAddress);
|
|
if (!Obj->inBounds(Ptr.address()))
|
|
return nullptr;
|
|
MO = Obj.get();
|
|
// We only inspect the first N exposed provenances according to the global
|
|
// generation number of the wildcard pointer.
|
|
ProvenanceCount = std::distance(
|
|
Set.List.begin(),
|
|
upper_bound(Set.List,
|
|
ExposedProvenance{nullptr, Prov.Wildcard->Generation}));
|
|
if (HasSideEffect) {
|
|
Mask = APInt::getAllOnes(ProvenanceCount);
|
|
Prov.Wildcard->BaseAddress = BaseAddress;
|
|
}
|
|
List = &Set.List;
|
|
} else {
|
|
// We already determined the memory object in a previous memory access.
|
|
uint64_t BaseAddress = Prov.Wildcard->BaseAddress;
|
|
auto Iter = ExposedProvenances.find(BaseAddress);
|
|
// The memory object has been freed.
|
|
if (Iter == ExposedProvenances.end())
|
|
return nullptr;
|
|
MO = MemoryObjects.at(BaseAddress).get();
|
|
if (!MO->inBounds(Ptr.address()))
|
|
return nullptr;
|
|
List = &Iter->second.List;
|
|
ProvenanceCount = Mask.getBitWidth();
|
|
}
|
|
if (Prov.Obj) {
|
|
// We already determined the memory object via speculatable operations like
|
|
// gep inbounds.
|
|
if (Prov.Obj.get() != MO)
|
|
return nullptr;
|
|
}
|
|
|
|
bool Valid = false;
|
|
for (uint32_t I = 0; I != ProvenanceCount; ++I) {
|
|
assert((!HasSideEffect || !Mask.isZero()) &&
|
|
"Mask must be initialized if HasSideEffect is true.");
|
|
if (!Mask.isZero() && !Mask[I])
|
|
continue;
|
|
if (Check(*(*List)[I].Prov)) {
|
|
Valid = true;
|
|
// Early return as we don't need to update the Mask.
|
|
if (!HasSideEffect)
|
|
break;
|
|
} else if (HasSideEffect)
|
|
Mask.clearBit(I);
|
|
}
|
|
|
|
return Valid ? MO : nullptr;
|
|
}
|
|
|
|
IntrusiveRefCntPtr<Provenance> Context::getWildcardProvenance() {
|
|
// No exposed provenances.
|
|
if (ExposedProvenanceSetGeneration == 0)
|
|
return Provenance::nullary();
|
|
auto Prov = makeIntrusiveRefCnt<Provenance>(nullptr);
|
|
Prov->Wildcard =
|
|
makeIntrusiveRefCnt<WildcardProvenance>(ExposedProvenanceSetGeneration);
|
|
return Prov;
|
|
}
|
|
|
|
Function *Context::getTargetFunction(const Pointer &Ptr) {
|
|
if (Ptr.address().getActiveBits() > 64)
|
|
return nullptr;
|
|
auto It = ValidFuncTargets.find(Ptr.address().getZExtValue());
|
|
if (It == ValidFuncTargets.end())
|
|
return nullptr;
|
|
// TODO: check the provenance of pointer.
|
|
return It->second.first;
|
|
}
|
|
BasicBlock *Context::getTargetBlock(const Pointer &Ptr) {
|
|
if (Ptr.address().getActiveBits() > 64)
|
|
return nullptr;
|
|
auto It = ValidBlockTargets.find(Ptr.address().getZExtValue());
|
|
if (It == ValidBlockTargets.end())
|
|
return nullptr;
|
|
// TODO: check the provenance of pointer.
|
|
return It->second.first;
|
|
}
|
|
|
|
uint64_t Context::getEffectiveTypeAllocSize(Type *Ty) {
|
|
// FIXME: It is incorrect for overaligned scalable vector types.
|
|
return getEffectiveTypeSize(DL.getTypeAllocSize(Ty));
|
|
}
|
|
uint64_t Context::getEffectiveTypeStoreSize(Type *Ty) {
|
|
return getEffectiveTypeSize(DL.getTypeStoreSize(Ty));
|
|
}
|
|
|
|
RoundingMode Context::getCurrentRoundingMode() const {
|
|
return CurrentRoundingMode;
|
|
}
|
|
|
|
fp::ExceptionBehavior Context::getCurrentExceptionBehavior() const {
|
|
return CurrentExceptionBehavior;
|
|
}
|
|
|
|
void Context::setCurrentRoundingMode(RoundingMode RM) {
|
|
CurrentRoundingMode = RM;
|
|
}
|
|
|
|
void Context::setCurrentExceptionBehavior(fp::ExceptionBehavior EB) {
|
|
CurrentExceptionBehavior = EB;
|
|
}
|
|
|
|
bool Context::isDefaultFPEnv() const {
|
|
return isDefaultFPEnvironment(CurrentExceptionBehavior, CurrentRoundingMode);
|
|
}
|
|
|
|
UndefValueBehavior Context::getEffectiveUndefValueBehavior() const {
|
|
if (isDeterministic())
|
|
return UndefValueBehavior::Zero;
|
|
return UndefBehavior;
|
|
}
|
|
|
|
NaNPropagationBehavior Context::getEffectiveNaNPropagationBehavior() const {
|
|
if (isDeterministic())
|
|
return NaNPropagationBehavior::PreferredNaN;
|
|
return NaNBehavior;
|
|
}
|
|
|
|
bool Context::getRandomBool() {
|
|
// We use the lowest bit of the raw bits from RNG as the result:
|
|
if (mayUseNonDeterminism())
|
|
return static_cast<bool>(Rng() & 1);
|
|
return false;
|
|
}
|
|
|
|
uint64_t Context::getRandomUInt64() {
|
|
if (mayUseNonDeterminism())
|
|
return Rng();
|
|
return 0;
|
|
}
|
|
|
|
bool MemoryObject::isGlobal() const {
|
|
return AllocKind == MemAllocKind::Global;
|
|
}
|
|
|
|
bool MemoryObject::isStackAllocated() const {
|
|
return AllocKind == MemAllocKind::Stack;
|
|
}
|
|
|
|
bool MemoryObject::isHeapAllocated() const {
|
|
switch (AllocKind) {
|
|
case MemAllocKind::Global:
|
|
case MemAllocKind::BlockAddress:
|
|
case MemAllocKind::Stack:
|
|
return false;
|
|
case MemAllocKind::Malloc:
|
|
case MemAllocKind::New:
|
|
case MemAllocKind::NewArray:
|
|
return true;
|
|
}
|
|
|
|
llvm_unreachable("Unknown MemAllocKind");
|
|
}
|
|
|
|
} // namespace llvm::ubi
|