blob: 26e01c0e4bd70fe1c27982b1b075172f4068bb0b [file] [edit]
//===- Interpreter.cpp - Interpreter Loop 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 implements the evaluation loop for each kind of instruction.
//
//===----------------------------------------------------------------------===//
#include "Context.h"
#include "ExecutorBase.h"
#include "Library.h"
#include "Value.h"
#include "llvm/Analysis/VectorUtils.h"
#include "llvm/IR/GetElementPtrTypeIterator.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/InstVisitor.h"
#include "llvm/IR/Operator.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/Support/Allocator.h"
namespace llvm::ubi {
using namespace PatternMatch;
static 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;
}
static 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;
}
static 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;
}
/// Instruction executor using the visitor pattern.
/// Unlike the Context class that manages the global state,
/// InstExecutor only maintains the state for call frames.
class InstExecutor : public InstVisitor<InstExecutor, void>,
public ExecutorBase {
const DataLayout &DL;
std::list<Frame> CallStack;
AnyValue None;
Library Lib;
const AnyValue &getValue(Value *V) {
if (auto *C = dyn_cast<Constant>(V))
return Ctx.getConstantValue(C);
return CurrentFrame->ValueMap.at(V);
}
void setResult(Instruction &I, AnyValue V) {
if (!hasProgramExited() && !Handler.onInstructionExecuted(I, V))
setFailed();
CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
}
AnyValue computeUnOp(Type *Ty, const AnyValue &Operand,
function_ref<AnyValue(const AnyValue &)> ScalarFn) {
if (Ty->isVectorTy()) {
auto &OperandVec = Operand.asAggregate();
std::vector<AnyValue> ResVec;
ResVec.reserve(OperandVec.size());
for (const auto &Scalar : OperandVec)
ResVec.push_back(ScalarFn(Scalar));
return std::move(ResVec);
}
return ScalarFn(Operand);
}
void visitUnOp(Instruction &I,
function_ref<AnyValue(const AnyValue &)> ScalarFn) {
setResult(I, computeUnOp(I.getType(), getValue(I.getOperand(0)), ScalarFn));
}
void visitIntUnOp(Instruction &I,
function_ref<AnyValue(const APInt &)> ScalarFn) {
visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
if (Operand.isPoison())
return AnyValue::poison();
return ScalarFn(Operand.asInteger());
});
}
AnyValue
visitIntUnOpWithResult(Instruction &I,
function_ref<AnyValue(const APInt &)> ScalarFn) {
return computeUnOp(I.getType(), getValue(I.getOperand(0)),
[&](const AnyValue &Operand) -> AnyValue {
if (Operand.isPoison())
return AnyValue::poison();
return ScalarFn(Operand.asInteger());
});
}
AnyValue computeBinOp(
Type *Ty, const AnyValue &LHS, const AnyValue &RHS,
function_ref<AnyValue(const AnyValue &, const AnyValue &)> ScalarFn) {
if (Ty->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(ScalarFn(ScalarLHS, ScalarRHS));
return std::move(ResVec);
}
return ScalarFn(LHS, RHS);
}
void visitBinOp(
Instruction &I,
function_ref<AnyValue(const AnyValue &, const AnyValue &)> ScalarFn) {
setResult(I, computeBinOp(I.getType(), getValue(I.getOperand(0)),
getValue(I.getOperand(1)), ScalarFn));
}
void
visitIntBinOp(Instruction &I,
function_ref<AnyValue(const APInt &, const APInt &)> ScalarFn) {
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
if (LHS.isPoison() || RHS.isPoison())
return AnyValue::poison();
return ScalarFn(LHS.asInteger(), RHS.asInteger());
});
}
AnyValue visitIntBinOpWithResult(
Instruction &I,
function_ref<AnyValue(const APInt &, const APInt &)> ScalarFn) {
return computeBinOp(
I.getType(), getValue(I.getOperand(0)), getValue(I.getOperand(1)),
[&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
if (LHS.isPoison() || RHS.isPoison())
return AnyValue::poison();
return ScalarFn(LHS.asInteger(), RHS.asInteger());
});
}
AnyValue visitOverflowIntBinOpWithResult(
CallBase &CB,
function_ref<std::pair<APInt, bool>(const APInt &, const APInt &)>
ScalarFn) {
const AnyValue &LHS = getValue(CB.getOperand(0));
const AnyValue &RHS = getValue(CB.getOperand(1));
if (!LHS.isAggregate()) {
if (LHS.isPoison() || RHS.isPoison())
return std::vector{AnyValue::poison(), AnyValue::poison()};
auto [Res, Overflow] = ScalarFn(LHS.asInteger(), RHS.asInteger());
return std::vector{AnyValue(Res), AnyValue::boolean(Overflow)};
}
auto &LHSVec = LHS.asAggregate();
auto &RHSVec = RHS.asAggregate();
std::vector<AnyValue> ResVec;
std::vector<AnyValue> OverflowVec;
ResVec.reserve(LHSVec.size());
OverflowVec.reserve(LHSVec.size());
for (const auto &[ScalarLHS, ScalarRHS] : zip(LHSVec, RHSVec)) {
if (ScalarLHS.isPoison() || ScalarRHS.isPoison()) {
ResVec.push_back(AnyValue::poison());
OverflowVec.push_back(AnyValue::poison());
continue;
}
auto [Res, Overflow] =
ScalarFn(ScalarLHS.asInteger(), ScalarRHS.asInteger());
ResVec.push_back(AnyValue(Res));
OverflowVec.push_back(AnyValue::boolean(Overflow));
}
return std::vector{AnyValue(std::move(ResVec)),
AnyValue(std::move(OverflowVec))};
}
AnyValue
computeTriOp(Type *Ty, const AnyValue &Op1, const AnyValue &Op2,
const AnyValue &Op3,
function_ref<AnyValue(const AnyValue &, const AnyValue &,
const AnyValue &)>
ScalarFn) {
if (Ty->isVectorTy()) {
auto &Op1Vec = Op1.asAggregate();
auto &Op2Vec = Op2.asAggregate();
auto &Op3Vec = Op3.asAggregate();
std::vector<AnyValue> ResVec;
ResVec.reserve(Op1Vec.size());
for (const auto &[ScalarOp1, ScalarOp2, ScalarOp3] :
zip(Op1Vec, Op2Vec, Op3Vec))
ResVec.push_back(ScalarFn(ScalarOp1, ScalarOp2, ScalarOp3));
return std::move(ResVec);
}
return ScalarFn(Op1, Op2, Op3);
}
void visitTriOp(Instruction &I,
function_ref<AnyValue(const AnyValue &, const AnyValue &,
const AnyValue &)>
ScalarFn) {
setResult(I, computeTriOp(I.getType(), getValue(I.getOperand(0)),
getValue(I.getOperand(1)),
getValue(I.getOperand(2)), ScalarFn));
}
void visitIntTriOp(
Instruction &I,
function_ref<AnyValue(const APInt &, const APInt &, const APInt &)>
ScalarFn) {
visitTriOp(I,
[&](const AnyValue &Op1, const AnyValue &Op2,
const AnyValue &Op3) -> AnyValue {
if (Op1.isPoison() || Op2.isPoison() || Op3.isPoison())
return AnyValue::poison();
return ScalarFn(Op1.asInteger(), Op2.asInteger(),
Op3.asInteger());
});
}
AnyValue visitIntTriOpWithResult(
Instruction &I,
function_ref<AnyValue(const APInt &, const APInt &, const APInt &)>
ScalarFn) {
return computeTriOp(
I.getType(), getValue(I.getOperand(0)), getValue(I.getOperand(1)),
getValue(I.getOperand(2)),
[&](const AnyValue &Op1, const AnyValue &Op2,
const AnyValue &Op3) -> AnyValue {
if (Op1.isPoison() || Op2.isPoison() || Op3.isPoison())
return AnyValue::poison();
return ScalarFn(Op1.asInteger(), Op2.asInteger(), Op3.asInteger());
});
}
void jumpTo(Instruction &Terminator, BasicBlock *DestBB) {
if (!Handler.onBBJump(Terminator, *DestBB)) {
setFailed();
return;
}
BasicBlock *From = CurrentFrame->BB;
CurrentFrame->BB = DestBB;
CurrentFrame->PC = DestBB->begin();
// Update PHI nodes in batch to avoid the interference between PHI nodes.
// We need to store the incoming values into a temporary buffer.
// Otherwise, the incoming value may be overwritten before it is
// used by other PHI nodes.
SmallVector<std::pair<PHINode *, AnyValue>> IncomingValues;
PHINode *PHI = nullptr;
while ((PHI = dyn_cast<PHINode>(CurrentFrame->PC))) {
Value *Incoming = PHI->getIncomingValueForBlock(From);
// TODO: handle fast-math flags.
IncomingValues.emplace_back(PHI, getValue(Incoming));
++CurrentFrame->PC;
}
for (auto &[K, V] : IncomingValues)
setResult(*K, std::move(V));
}
/// Helper function to determine whether an inline asm is a no-op, which is
/// used to implement black_box style optimization blockers.
bool isNoopInlineAsm(Value *V, Type *RetTy) {
if (auto *Asm = dyn_cast<InlineAsm>(V))
return Asm->getAsmString().empty() && RetTy->isVoidTy();
return false;
}
AnyValue 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);
auto *MO = Ptr.getMemoryObject();
if (Flags.isInBounds() && (!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.
return Ptr.getWithNewAddr(NewAddr);
}
AnyValue 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 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);
}
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);
}
// Helper function to convert BooleanKind to bool. Report an immediate UB if
// a poison is found.
bool getBooleanNonPoison(BooleanKind Boolean) {
if (Boolean == BooleanKind::Poison)
reportImmediateUB("Unexpected poison boolean value");
return Boolean == BooleanKind::True;
}
public:
InstExecutor(Context &C, EventHandler &H, Function &F,
ArrayRef<AnyValue> Args, AnyValue &RetVal)
: ExecutorBase(C, H), DL(Ctx.getDataLayout()),
Lib(Ctx, Handler, DL, static_cast<ExecutorBase &>(*this)) {
CallStack.emplace_back(F, /*CallSite=*/nullptr, /*LastFrame=*/nullptr, Args,
RetVal, Ctx.getTLIImpl());
}
void visitReturnInst(ReturnInst &RI) {
if (auto *RV = RI.getReturnValue())
CurrentFrame->RetVal = getValue(RV);
CurrentFrame->State = FrameState::Exit;
if (!Handler.onInstructionExecuted(RI, None))
setFailed();
}
void visitUncondBrInst(UncondBrInst &BI) { jumpTo(BI, BI.getSuccessor()); }
void visitCondBrInst(CondBrInst &BI) {
switch (getValue(BI.getCondition()).asBoolean()) {
case BooleanKind::True:
jumpTo(BI, BI.getSuccessor(0));
return;
case BooleanKind::False:
jumpTo(BI, BI.getSuccessor(1));
return;
case BooleanKind::Poison:
reportImmediateUB("Branch on poison condition.");
return;
}
}
void visitSwitchInst(SwitchInst &SI) {
auto &Cond = getValue(SI.getCondition());
if (Cond.isPoison()) {
reportImmediateUB("Switch on poison condition.");
return;
}
for (auto &Case : SI.cases()) {
if (Case.getCaseValue()->getValue() == Cond.asInteger()) {
jumpTo(SI, Case.getCaseSuccessor());
return;
}
}
jumpTo(SI, SI.getDefaultDest());
}
void visitUnreachableInst(UnreachableInst &) {
reportImmediateUB("Unreachable code.");
}
void visitCallBrInst(CallBrInst &CI) {
if (isNoopInlineAsm(CI.getCalledOperand(), CI.getType())) {
jumpTo(CI, CI.getDefaultDest());
return;
}
Handler.onUnrecognizedInstruction(CI);
setFailed();
}
void visitIndirectBrInst(IndirectBrInst &IBI) {
auto &Target = getValue(IBI.getAddress());
if (Target.isPoison()) {
reportImmediateUB("Indirect branch on poison.");
return;
}
if (BasicBlock *DestBB = Ctx.getTargetBlock(Target.asPointer())) {
if (any_of(IBI.successors(),
[DestBB](BasicBlock *Succ) { return Succ == DestBB; }))
jumpTo(IBI, DestBB);
else
reportImmediateUB("Indirect branch on unlisted target BB.");
return;
}
reportImmediateUB("Indirect branch on invalid target BB.");
}
void returnFromCallee() {
// TODO: handle retval attributes (Attributes from known callee should be
// applied if available).
// TODO: handle metadata
auto &CB = cast<CallBase>(*CurrentFrame->PC);
CurrentFrame->CalleeArgs.clear();
AnyValue &RetVal = CurrentFrame->CalleeRetVal;
setResult(CB, std::move(RetVal));
if (auto *II = dyn_cast<InvokeInst>(&CB))
jumpTo(*II, II->getNormalDest());
else if (CurrentFrame->State == FrameState::Pending)
++CurrentFrame->PC;
}
AnyValue callIntrinsic(CallBase &CB, ArrayRef<AnyValue> Args) {
Intrinsic::ID IID = CB.getIntrinsicID();
Type *RetTy = CB.getType();
switch (IID) {
case Intrinsic::assume:
switch (Args[0].asBoolean()) {
case BooleanKind::True:
break;
case BooleanKind::False:
case BooleanKind::Poison:
reportImmediateUB("Assume on false or poison condition.");
break;
}
// TODO: handle llvm.assume with operand bundles
return AnyValue();
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end: {
auto Ptr = Args[0];
if (Ptr.isPoison())
return AnyValue();
auto *MO = Ptr.asPointer().getMemoryObject();
assert(MO && "Memory object accessed by lifetime intrinsic should be "
"always valid.");
if (IID == Intrinsic::lifetime_start) {
MO->setState(MemoryObjectState::Alive);
fill(MO->getBytes(), Byte::undef());
} else {
MO->setState(MemoryObjectState::Dead);
}
return AnyValue();
}
case Intrinsic::ssa_copy:
case Intrinsic::expect:
case Intrinsic::expect_with_probability:
return Args[0];
case Intrinsic::donothing:
return AnyValue();
case Intrinsic::vscale: {
const unsigned BitWidth = RetTy->getScalarSizeInBits();
const APInt VScale(64, Ctx.getVScale());
if (!VScale.isIntN(BitWidth))
return AnyValue::poison();
return VScale.zextOrTrunc(BitWidth);
}
case Intrinsic::abs: {
const bool IsIntMinPoison = getBooleanNonPoison(Args[1].asBoolean());
return visitIntUnOpWithResult(CB, [&](const APInt &Operand) -> AnyValue {
if (IsIntMinPoison && Operand.isMinSignedValue())
return AnyValue::poison();
return Operand.abs();
});
}
case Intrinsic::smax: {
return visitIntBinOpWithResult(
CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
return APIntOps::smax(LHS, RHS);
});
}
case Intrinsic::smin: {
return visitIntBinOpWithResult(
CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
return APIntOps::smin(LHS, RHS);
});
}
case Intrinsic::umax: {
return visitIntBinOpWithResult(
CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
return APIntOps::umax(LHS, RHS);
});
}
case Intrinsic::umin: {
return visitIntBinOpWithResult(
CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
return APIntOps::umin(LHS, RHS);
});
}
case Intrinsic::scmp:
case Intrinsic::ucmp: {
const unsigned BitWidth = RetTy->getScalarSizeInBits();
return visitIntBinOpWithResult(
CB, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
if (LHS == RHS)
return APInt::getZero(BitWidth);
if (IID == Intrinsic::scmp)
return LHS.slt(RHS) ? APInt::getAllOnes(BitWidth)
: APInt(BitWidth, 1);
return LHS.ult(RHS) ? APInt::getAllOnes(BitWidth)
: APInt(BitWidth, 1);
});
}
case Intrinsic::bitreverse: {
return visitIntUnOpWithResult(CB, [](const APInt &Operand) -> AnyValue {
return Operand.reverseBits();
});
}
case Intrinsic::bswap: {
return visitIntUnOpWithResult(CB, [](const APInt &Operand) -> AnyValue {
return Operand.byteSwap();
});
}
case Intrinsic::ctpop: {
return visitIntUnOpWithResult(CB, [](const APInt &Operand) -> AnyValue {
return APInt(Operand.getBitWidth(), Operand.popcount());
});
}
case Intrinsic::ctlz:
case Intrinsic::cttz: {
const bool IsZeroPoison = getBooleanNonPoison(Args[1].asBoolean());
return visitIntUnOpWithResult(CB, [&](const APInt &Operand) -> AnyValue {
if (IsZeroPoison && Operand.isZero())
return AnyValue::poison();
if (IID == Intrinsic::ctlz)
return APInt(Operand.getBitWidth(), Operand.countl_zero());
return APInt(Operand.getBitWidth(), Operand.countr_zero());
});
}
case Intrinsic::fshl:
case Intrinsic::fshr: {
return visitIntTriOpWithResult(
CB,
[IID](const APInt &Op1, const APInt &Op2,
const APInt &Op3) -> AnyValue {
const unsigned BitWidth = Op1.getBitWidth();
const uint64_t ShiftAmount = Op3.urem(BitWidth);
const bool IsFShr = IID == Intrinsic::fshr;
if (ShiftAmount == 0)
return IsFShr ? Op2 : Op1;
const uint64_t LShrAmount =
IsFShr ? ShiftAmount : BitWidth - ShiftAmount;
const uint64_t ShlAmount =
!IsFShr ? ShiftAmount : BitWidth - ShiftAmount;
return Op1.shl(ShlAmount) | Op2.lshr(LShrAmount);
});
}
case Intrinsic::clmul: {
return visitIntBinOpWithResult(
CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
return APIntOps::clmul(LHS, RHS);
});
}
case Intrinsic::sadd_with_overflow:
case Intrinsic::uadd_with_overflow:
case Intrinsic::ssub_with_overflow:
case Intrinsic::usub_with_overflow:
case Intrinsic::smul_with_overflow:
case Intrinsic::umul_with_overflow: {
return visitOverflowIntBinOpWithResult(
CB,
[IID](const APInt &LHS, const APInt &RHS) -> std::pair<APInt, bool> {
APInt Res;
bool Overflow = false;
switch (IID) {
case Intrinsic::sadd_with_overflow:
Res = LHS.sadd_ov(RHS, Overflow);
break;
case Intrinsic::uadd_with_overflow:
Res = LHS.uadd_ov(RHS, Overflow);
break;
case Intrinsic::ssub_with_overflow:
Res = LHS.ssub_ov(RHS, Overflow);
break;
case Intrinsic::usub_with_overflow:
Res = LHS.usub_ov(RHS, Overflow);
break;
case Intrinsic::smul_with_overflow:
Res = LHS.smul_ov(RHS, Overflow);
break;
case Intrinsic::umul_with_overflow:
Res = LHS.umul_ov(RHS, Overflow);
break;
default:
llvm_unreachable("Unexpected intrinsic ID");
}
return {Res, Overflow};
});
}
case Intrinsic::sadd_sat:
case Intrinsic::uadd_sat:
case Intrinsic::ssub_sat:
case Intrinsic::usub_sat:
case Intrinsic::sshl_sat:
case Intrinsic::ushl_sat: {
return visitIntBinOpWithResult(
CB, [IID](const APInt &LHS, const APInt &RHS) -> AnyValue {
switch (IID) {
case Intrinsic::sadd_sat:
return LHS.sadd_sat(RHS);
case Intrinsic::uadd_sat:
return LHS.uadd_sat(RHS);
case Intrinsic::ssub_sat:
return LHS.ssub_sat(RHS);
case Intrinsic::usub_sat:
return LHS.usub_sat(RHS);
case Intrinsic::sshl_sat: {
if (RHS.uge(LHS.getBitWidth()))
return AnyValue::poison();
return LHS.sshl_sat(RHS);
}
case Intrinsic::ushl_sat: {
if (RHS.uge(LHS.getBitWidth()))
return AnyValue::poison();
return LHS.ushl_sat(RHS);
}
default:
llvm_unreachable("Unexpected intrinsic ID");
}
});
}
case Intrinsic::vector_reduce_add:
case Intrinsic::vector_reduce_mul:
case Intrinsic::vector_reduce_and:
case Intrinsic::vector_reduce_or:
case Intrinsic::vector_reduce_xor:
case Intrinsic::vector_reduce_smax:
case Intrinsic::vector_reduce_smin:
case Intrinsic::vector_reduce_umax:
case Intrinsic::vector_reduce_umin: {
std::optional<APInt> Res;
for (const auto &V : Args[0].asAggregate()) {
if (V.isPoison()) {
Res.reset();
break;
}
const auto &IntV = V.asInteger();
if (!Res) {
Res = IntV;
continue;
}
switch (IID) {
case Intrinsic::vector_reduce_add:
*Res += IntV;
break;
case Intrinsic::vector_reduce_mul:
*Res *= IntV;
break;
case Intrinsic::vector_reduce_and:
*Res &= IntV;
break;
case Intrinsic::vector_reduce_or:
*Res |= IntV;
break;
case Intrinsic::vector_reduce_xor:
*Res ^= IntV;
break;
case Intrinsic::vector_reduce_smax:
*Res = APIntOps::smax(*Res, IntV);
break;
case Intrinsic::vector_reduce_smin:
*Res = APIntOps::smin(*Res, IntV);
break;
case Intrinsic::vector_reduce_umax:
*Res = APIntOps::umax(*Res, IntV);
break;
case Intrinsic::vector_reduce_umin:
*Res = APIntOps::umin(*Res, IntV);
break;
default:
llvm_unreachable("Unexpected intrinsic ID");
}
}
return Res ? *Res : AnyValue::poison();
}
case Intrinsic::vector_insert: {
if (Args[2].isPoison())
return AnyValue::poison();
const auto &Vec = Args[0].asAggregate();
const auto &SubVec = Args[1].asAggregate();
const auto &Idx = Args[2].asInteger();
const uint64_t Offset = Idx.getZExtValue();
if (Offset + SubVec.size() > Vec.size())
return AnyValue::poison();
std::vector<AnyValue> Res;
Res.reserve(Vec.size());
for (size_t I = 0; I != Vec.size(); ++I) {
if (I >= Offset && I < Offset + SubVec.size())
Res.push_back(SubVec[I - Offset]);
else
Res.push_back(Vec[I]);
}
return std::move(Res);
}
case Intrinsic::vector_extract: {
if (Args[1].isPoison())
return AnyValue::poison();
const auto &Vec = Args[0].asAggregate();
const auto &Idx = Args[1].asInteger();
const uint64_t Offset = Idx.getZExtValue();
const uint64_t DstSize =
Ctx.getEVL(cast<VectorType>(RetTy)->getElementCount());
if (Offset + DstSize > Vec.size())
return AnyValue::poison();
return std::vector(Vec.begin() + Offset, Vec.begin() + Offset + DstSize);
}
case Intrinsic::vector_reverse: {
auto Vec = Args[0].asAggregate();
std::reverse(Vec.begin(), Vec.end());
return std::move(Vec);
}
case Intrinsic::vector_deinterleave2:
case Intrinsic::vector_deinterleave3:
case Intrinsic::vector_deinterleave4:
case Intrinsic::vector_deinterleave5:
case Intrinsic::vector_deinterleave6:
case Intrinsic::vector_deinterleave7:
case Intrinsic::vector_deinterleave8: {
const unsigned Factor = getDeinterleaveIntrinsicFactor(IID);
if (Factor == 0)
llvm_unreachable("Unexpected intrinsic ID");
const auto &Vec = Args[0].asAggregate();
std::vector<std::vector<AnyValue>> Res(Factor);
for (auto &SubVec : Res)
SubVec.reserve(Vec.size() / Factor);
for (size_t I = 0, E = Vec.size(); I != E; ++I)
Res[I % Factor].push_back(Vec[I]);
std::vector<AnyValue> AggRes;
AggRes.reserve(Factor);
for (auto &SubVec : Res)
AggRes.emplace_back(std::move(SubVec));
return AnyValue(std::move(AggRes));
}
case Intrinsic::vector_interleave2:
case Intrinsic::vector_interleave3:
case Intrinsic::vector_interleave4:
case Intrinsic::vector_interleave5:
case Intrinsic::vector_interleave6:
case Intrinsic::vector_interleave7:
case Intrinsic::vector_interleave8: {
const unsigned Factor = getInterleaveIntrinsicFactor(IID);
if (Factor == 0)
llvm_unreachable("Unexpected intrinsic ID");
const auto &Vec = Args[0].asAggregate();
std::vector<AnyValue> Res;
Res.reserve(Vec.size() * Factor);
for (size_t I = 0, E = Vec.size(); I != E; ++I) {
for (unsigned J = 0; J != Factor; ++J)
Res.push_back(Args[J].asAggregate()[I]);
}
return std::move(Res);
}
case Intrinsic::vector_splice_left: {
if (Args[2].isPoison())
return AnyValue::poison();
const auto &LHS = Args[0].asAggregate();
const auto &RHS = Args[1].asAggregate();
const auto &Off = Args[2].asInteger();
const size_t Len = LHS.size();
if (Off.ugt(Len))
return AnyValue::poison();
uint64_t Offset = Off.getZExtValue();
std::vector<AnyValue> Res;
Res.reserve(Len);
for (size_t I = 0; I != Len; ++I) {
size_t Pos = I + Offset;
Res.push_back(Pos < Len ? LHS[Pos] : RHS[Pos - Len]);
}
return std::move(Res);
}
case Intrinsic::vector_splice_right: {
if (Args[2].isPoison())
return AnyValue::poison();
const auto &LHS = Args[0].asAggregate();
const auto &RHS = Args[1].asAggregate();
const auto &Off = Args[2].asInteger();
const size_t Len = LHS.size();
if (Off.ugt(Len))
return AnyValue::poison();
uint64_t Offset = Len - Off.getZExtValue();
std::vector<AnyValue> Res;
Res.reserve(Len);
for (size_t I = 0; I != Len; ++I) {
size_t Pos = I + Offset;
Res.push_back(Pos < Len ? LHS[Pos] : RHS[Pos - Len]);
}
return std::move(Res);
}
case Intrinsic::stepvector: {
std::vector<AnyValue> Res;
const uint32_t Len =
Ctx.getEVL(cast<VectorType>(RetTy)->getElementCount());
const unsigned BitWidth = RetTy->getScalarSizeInBits();
Res.reserve(Len);
for (uint64_t I = 0; I != Len; ++I) {
Res.push_back(
APInt(BitWidth, I, /*IsSigned=*/false, /*ImplicitTrunc=*/true));
}
return std::move(Res);
}
default:
Handler.onUnrecognizedInstruction(CB);
setFailed();
return AnyValue();
}
}
AnyValue callLibFunc(CallBase &CB, Function *ResolvedCallee,
ArrayRef<AnyValue> CalleeArgs) {
LibFunc LF;
// Respect nobuiltin attributes on call site.
if (CB.isNoBuiltin() ||
!CurrentFrame->TLI.getLibFunc(*ResolvedCallee, LF)) {
Handler.onUnrecognizedInstruction(CB);
setFailed();
return AnyValue();
}
if (auto LibCallRes =
Lib.executeLibcall(LF, CB.getName(), CB.getType(), CalleeArgs))
return *LibCallRes;
if (ExitInfo)
return AnyValue();
Handler.onUnrecognizedInstruction(CB);
setFailed();
return AnyValue();
}
void enterCall(CallBase &CB) {
Function *Callee = CB.getCalledFunction();
// TODO: handle parameter attributes (Attributes from known callee should be
// applied if available).
// TODO: handle byval/initializes
auto &CalleeArgs = CurrentFrame->CalleeArgs;
assert(CalleeArgs.empty() &&
"Forgot to call returnFromCallee before entering a new call.");
for (Value *Arg : CB.args())
CalleeArgs.push_back(getValue(Arg));
if (!Callee) {
Value *CalledOperand = CB.getCalledOperand();
if (isNoopInlineAsm(CalledOperand, CB.getType())) {
CurrentFrame->ResolvedCallee = nullptr;
returnFromCallee();
return;
}
if (isa<InlineAsm>(CalledOperand)) {
Handler.onUnrecognizedInstruction(CB);
setFailed();
return;
}
auto &CalleeVal = getValue(CalledOperand);
if (CalleeVal.isPoison()) {
reportImmediateUB("Indirect call through poison function pointer.");
return;
}
Callee = Ctx.getTargetFunction(CalleeVal.asPointer());
if (!Callee) {
reportImmediateUB("Indirect call through invalid function pointer.");
return;
}
if (Callee->getFunctionType() != CB.getFunctionType()) {
reportImmediateUB("Indirect call through a function pointer with "
"mismatched signature.");
return;
}
}
assert(Callee && "Expected a resolved callee function.");
assert(
Callee->getFunctionType() == CB.getFunctionType() &&
"Expected the callee function type to match the call site signature.");
CurrentFrame->ResolvedCallee = Callee;
if (Callee->isIntrinsic()) {
CurrentFrame->CalleeRetVal = callIntrinsic(CB, CalleeArgs);
returnFromCallee();
return;
} else if (Callee->isDeclaration()) {
CurrentFrame->CalleeRetVal = callLibFunc(CB, Callee, CalleeArgs);
returnFromCallee();
return;
} else {
uint32_t MaxStackDepth = Ctx.getMaxStackDepth();
if (MaxStackDepth && CallStack.size() >= MaxStackDepth) {
reportError("Maximum stack depth exceeded.");
return;
}
assert(!Callee->empty() && "Expected a defined function.");
// Suspend the current frame and push the callee frame onto the stack.
ArrayRef<AnyValue> Args = CurrentFrame->CalleeArgs;
AnyValue &RetVal = CurrentFrame->CalleeRetVal;
CurrentFrame->State = FrameState::Pending;
CallStack.emplace_back(*Callee, &CB, CurrentFrame, Args, RetVal,
Ctx.getTLIImpl());
}
}
void visitCallInst(CallInst &CI) { enterCall(CI); }
void visitInvokeInst(InvokeInst &II) {
// TODO: handle exceptions
enterCall(II);
}
void visitAdd(BinaryOperator &I) {
visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
return addNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
});
}
void visitSub(BinaryOperator &I) {
visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
return subNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
});
}
void visitMul(BinaryOperator &I) {
visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
return mulNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
});
}
void visitSDiv(BinaryOperator &I) {
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
// Priority: Immediate UB > poison > normal value
if (RHS.isPoison()) {
reportImmediateUB("Division by zero (refine RHS to 0).");
return AnyValue::poison();
}
const APInt &RHSVal = RHS.asInteger();
if (RHSVal.isZero()) {
reportImmediateUB("Division by zero.");
return AnyValue::poison();
}
if (LHS.isPoison()) {
if (RHSVal.isAllOnes())
reportImmediateUB(
"Signed division overflow (refine LHS to INT_MIN).");
return AnyValue::poison();
}
const APInt &LHSVal = LHS.asInteger();
if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
reportImmediateUB("Signed division overflow.");
return AnyValue::poison();
}
if (I.isExact()) {
APInt Q, R;
APInt::sdivrem(LHSVal, RHSVal, Q, R);
if (!R.isZero())
return AnyValue::poison();
return Q;
} else {
return LHSVal.sdiv(RHSVal);
}
});
}
void visitSRem(BinaryOperator &I) {
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
// Priority: Immediate UB > poison > normal value
if (RHS.isPoison()) {
reportImmediateUB("Division by zero (refine RHS to 0).");
return AnyValue::poison();
}
const APInt &RHSVal = RHS.asInteger();
if (RHSVal.isZero()) {
reportImmediateUB("Division by zero.");
return AnyValue::poison();
}
if (LHS.isPoison()) {
if (RHSVal.isAllOnes())
reportImmediateUB(
"Signed division overflow (refine LHS to INT_MIN).");
return AnyValue::poison();
}
const APInt &LHSVal = LHS.asInteger();
if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
reportImmediateUB("Signed division overflow.");
return AnyValue::poison();
}
return LHSVal.srem(RHSVal);
});
}
void visitUDiv(BinaryOperator &I) {
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
// Priority: Immediate UB > poison > normal value
if (RHS.isPoison()) {
reportImmediateUB("Division by zero (refine RHS to 0).");
return AnyValue::poison();
}
const APInt &RHSVal = RHS.asInteger();
if (RHSVal.isZero()) {
reportImmediateUB("Division by zero.");
return AnyValue::poison();
}
if (LHS.isPoison())
return AnyValue::poison();
const APInt &LHSVal = LHS.asInteger();
if (I.isExact()) {
APInt Q, R;
APInt::udivrem(LHSVal, RHSVal, Q, R);
if (!R.isZero())
return AnyValue::poison();
return Q;
} else {
return LHSVal.udiv(RHSVal);
}
});
}
void visitURem(BinaryOperator &I) {
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
// Priority: Immediate UB > poison > normal value
if (RHS.isPoison()) {
reportImmediateUB("Division by zero (refine RHS to 0).");
return AnyValue::poison();
}
const APInt &RHSVal = RHS.asInteger();
if (RHSVal.isZero()) {
reportImmediateUB("Division by zero.");
return AnyValue::poison();
}
if (LHS.isPoison())
return AnyValue::poison();
const APInt &LHSVal = LHS.asInteger();
return LHSVal.urem(RHSVal);
});
}
void visitTruncInst(TruncInst &Trunc) {
visitIntUnOp(Trunc, [&](const APInt &Operand) -> AnyValue {
unsigned DestBW = Trunc.getType()->getScalarSizeInBits();
if (Trunc.hasNoSignedWrap() && Operand.getSignificantBits() > DestBW)
return AnyValue::poison();
if (Trunc.hasNoUnsignedWrap() && Operand.getActiveBits() > DestBW)
return AnyValue::poison();
return Operand.trunc(DestBW);
});
}
void visitZExtInst(ZExtInst &ZExt) {
visitIntUnOp(ZExt, [&](const APInt &Operand) -> AnyValue {
uint32_t DestBW = ZExt.getDestTy()->getScalarSizeInBits();
if (ZExt.hasNonNeg() && Operand.isNegative())
return AnyValue::poison();
return Operand.zext(DestBW);
});
}
void visitSExtInst(SExtInst &SExt) {
visitIntUnOp(SExt, [&](const APInt &Operand) -> AnyValue {
uint32_t DestBW = SExt.getDestTy()->getScalarSizeInBits();
return Operand.sext(DestBW);
});
}
void visitAnd(BinaryOperator &I) {
visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
return LHS & RHS;
});
}
void visitXor(BinaryOperator &I) {
visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
return LHS ^ RHS;
});
}
void visitOr(BinaryOperator &I) {
visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
if (cast<PossiblyDisjointInst>(I).isDisjoint() && LHS.intersects(RHS))
return AnyValue::poison();
return LHS | RHS;
});
}
void visitShl(BinaryOperator &I) {
visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
if (RHS.uge(LHS.getBitWidth()))
return AnyValue::poison();
if (I.hasNoSignedWrap() && RHS.uge(LHS.getNumSignBits()))
return AnyValue::poison();
if (I.hasNoUnsignedWrap() && RHS.ugt(LHS.countl_zero()))
return AnyValue::poison();
return LHS.shl(RHS);
});
}
void visitLShr(BinaryOperator &I) {
visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
if (RHS.uge(cast<PossiblyExactOperator>(I).isExact()
? LHS.countr_zero() + 1
: LHS.getBitWidth()))
return AnyValue::poison();
return LHS.lshr(RHS);
});
}
void visitAShr(BinaryOperator &I) {
visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
if (RHS.uge(cast<PossiblyExactOperator>(I).isExact()
? LHS.countr_zero() + 1
: LHS.getBitWidth()))
return AnyValue::poison();
return LHS.ashr(RHS);
});
}
void visitICmpInst(ICmpInst &I) {
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
if (LHS.isPoison() || RHS.isPoison())
return AnyValue::poison();
// TODO: handle pointer comparison.
const APInt &LHSVal = LHS.asInteger();
const APInt &RHSVal = RHS.asInteger();
if (I.hasSameSign() && LHSVal.isNonNegative() != RHSVal.isNonNegative())
return AnyValue::poison();
return AnyValue::boolean(
ICmpInst::compare(LHSVal, RHSVal, I.getPredicate()));
});
}
void visitSelect(SelectInst &SI) {
// TODO: handle fast-math flags.
if (SI.getCondition()->getType()->isIntegerTy(1)) {
switch (getValue(SI.getCondition()).asBoolean()) {
case BooleanKind::True:
setResult(SI, getValue(SI.getTrueValue()));
return;
case BooleanKind::False:
setResult(SI, getValue(SI.getFalseValue()));
return;
case BooleanKind::Poison:
setResult(SI, AnyValue::getPoisonValue(Ctx, SI.getType()));
return;
}
}
auto &Cond = getValue(SI.getCondition()).asAggregate();
auto &TV = getValue(SI.getTrueValue()).asAggregate();
auto &FV = getValue(SI.getFalseValue()).asAggregate();
std::vector<AnyValue> Res;
size_t Len = Cond.size();
Res.reserve(Len);
for (uint32_t I = 0; I != Len; ++I) {
switch (Cond[I].asBoolean()) {
case BooleanKind::True:
Res.push_back(TV[I]);
break;
case BooleanKind::False:
Res.push_back(FV[I]);
break;
case BooleanKind::Poison:
Res.push_back(AnyValue::poison());
break;
}
}
setResult(SI, std::move(Res));
}
void visitAllocaInst(AllocaInst &AI) {
uint64_t AllocSize = Ctx.getEffectiveTypeAllocSize(AI.getAllocatedType());
if (AI.isArrayAllocation()) {
auto &Size = getValue(AI.getArraySize());
if (Size.isPoison()) {
reportImmediateUB("Alloca with poison array size.");
return;
}
if (Size.asInteger().getActiveBits() > 64) {
reportImmediateUB(
"Alloca with large array size that overflows uint64_t.");
return;
}
bool Overflowed = false;
AllocSize = SaturatingMultiply(AllocSize, Size.asInteger().getZExtValue(),
&Overflowed);
if (Overflowed) {
reportImmediateUB(
"Alloca with allocation size that overflows uint64_t.");
return;
}
}
// If it is used by llvm.lifetime.start, it should be initially dead.
bool IsInitiallyDead = any_of(AI.users(), [](User *U) {
return match(U, m_Intrinsic<Intrinsic::lifetime_start>());
});
auto Obj = Ctx.allocate(AllocSize, AI.getPointerAlignment(DL).value(),
AI.getName(), AI.getAddressSpace(),
IsInitiallyDead ? MemInitKind::Poisoned
: MemInitKind::Uninitialized,
MemAllocKind::Stack);
if (!Obj) {
reportError("Insufficient stack space.");
return;
}
CurrentFrame->Allocas.push_back(Obj);
setResult(AI, Ctx.deriveFromMemoryObject(Obj));
}
void visitGetElementPtrInst(GetElementPtrInst &GEP) {
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)),
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, Ctx.getEffectiveTypeSize(Offset),
/*isSigned=*/false, /*implicitTrunc=*/true);
if (!Scale.isZero())
ApplyScaledOffset(getValue(V), Scale);
}
setResult(GEP, std::move(Res));
}
void visitIntToPtr(IntToPtrInst &I) {
return visitUnOp(I, [&](const AnyValue &V) -> AnyValue {
if (V.isPoison())
return AnyValue::poison();
// TODO: expose provenance
// TODO: check metadata
return Pointer(V.asInteger().zextOrTrunc(
DL.getPointerSizeInBits(I.getType()->getPointerAddressSpace())));
});
}
void visitLoadInst(LoadInst &LI) {
auto RetVal =
load(getValue(LI.getPointerOperand()), LI.getAlign(), LI.getType());
// TODO: track volatile loads
// TODO: handle metadata
setResult(LI, std::move(RetVal));
}
void visitStoreInst(StoreInst &SI) {
auto &Ptr = getValue(SI.getPointerOperand());
auto &Val = getValue(SI.getValueOperand());
// TODO: track volatile stores
// TODO: handle metadata
store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType());
if (!hasProgramExited() && !Handler.onInstructionExecuted(SI, AnyValue()))
setFailed();
}
void visitInstruction(Instruction &I) {
Handler.onUnrecognizedInstruction(I);
setFailed();
}
void visitExtractValueInst(ExtractValueInst &EVI) {
auto &Res = getValue(EVI.getAggregateOperand());
const AnyValue *Pos = &Res;
for (unsigned Idx : EVI.indices())
Pos = &Pos->asAggregate()[Idx];
setResult(EVI, *Pos);
}
void visitInsertValueInst(InsertValueInst &IVI) {
AnyValue Res = getValue(IVI.getAggregateOperand());
AnyValue *Pos = &Res;
for (unsigned Idx : IVI.indices())
Pos = &Pos->asAggregate()[Idx];
*Pos = getValue(IVI.getInsertedValueOperand());
setResult(IVI, std::move(Res));
}
void visitInsertElementInst(InsertElementInst &IEI) {
auto Res = getValue(IEI.getOperand(0));
auto &ResVec = Res.asAggregate();
auto &Idx = getValue(IEI.getOperand(2));
if (Idx.isPoison() || Idx.asInteger().uge(ResVec.size())) {
setResult(IEI, AnyValue::getPoisonValue(Ctx, IEI.getType()));
return;
}
ResVec[Idx.asInteger().getZExtValue()] = getValue(IEI.getOperand(1));
setResult(IEI, std::move(Res));
}
void visitExtractElementInst(ExtractElementInst &EEI) {
auto &SrcVec = getValue(EEI.getOperand(0)).asAggregate();
auto &Idx = getValue(EEI.getOperand(1));
if (Idx.isPoison() || Idx.asInteger().uge(SrcVec.size())) {
setResult(EEI, AnyValue::getPoisonValue(Ctx, EEI.getType()));
return;
}
setResult(EEI, SrcVec[Idx.asInteger().getZExtValue()]);
}
void visitShuffleVectorInst(ShuffleVectorInst &SVI) {
auto &LHSVec = getValue(SVI.getOperand(0)).asAggregate();
auto &RHSVec = getValue(SVI.getOperand(1)).asAggregate();
uint32_t Size = cast<VectorType>(SVI.getOperand(0)->getType())
->getElementCount()
.getKnownMinValue();
std::vector<AnyValue> Res;
uint32_t DstLen = Ctx.getEVL(SVI.getType()->getElementCount());
Res.reserve(DstLen);
uint32_t Stride = SVI.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 : SVI.getShuffleMask()) {
if (Idx == PoisonMaskElem)
Res.push_back(AnyValue::poison());
else if (Idx < static_cast<int>(Size))
Res.push_back(LHSVec[Idx]);
else
Res.push_back(RHSVec[Idx - Size]);
}
}
setResult(SVI, std::move(Res));
}
void visitBitCastInst(BitCastInst &BCI) {
// The conversion is done as if the value had been stored to memory and read
// back as the target type.
SmallVector<Byte> Bytes;
Bytes.resize(Ctx.getEffectiveTypeStoreSize(BCI.getType()),
Byte::concrete(0));
Ctx.toBytes(getValue(BCI.getOperand(0)), BCI.getOperand(0)->getType(),
Bytes);
setResult(BCI, Ctx.fromBytes(Bytes, BCI.getType()));
}
void visitFreezeInst(FreezeInst &FI) {
AnyValue Val = getValue(FI.getOperand(0));
Ctx.freeze(Val, FI.getType());
setResult(FI, std::move(Val));
}
/// This function implements the main interpreter loop.
/// It handles function calls in a non-recursive manner to avoid stack
/// overflows.
ProgramExitInfo runMainLoop() {
uint32_t MaxSteps = Ctx.getMaxSteps();
uint32_t Steps = 0;
while (!hasProgramExited() && !CallStack.empty()) {
Frame &Top = CallStack.back();
CurrentFrame = &Top;
if (Top.State == FrameState::Entry) {
Handler.onFunctionEntry(Top.Func, Top.Args, Top.CallSite);
} else {
assert(Top.State == FrameState::Pending &&
"Expected to return from a callee.");
returnFromCallee();
}
Top.State = FrameState::Running;
// Interpreter loop inside a function
while (!hasProgramExited()) {
assert(Top.State == FrameState::Running &&
"Expected to be in running state.");
if (MaxSteps != 0 && Steps >= MaxSteps) {
reportError("Exceeded maximum number of execution steps.");
break;
}
++Steps;
Instruction &I = *Top.PC;
visit(&I);
if (hasProgramExited())
break;
// A function call or return has occurred.
// We need to exit the inner loop and switch to a different frame.
if (Top.State != FrameState::Running)
break;
// Otherwise, move to the next instruction if it is not a terminator.
// For terminators, the PC is updated in the visit* method.
if (!I.isTerminator())
++Top.PC;
}
if (hasProgramExited())
break;
if (Top.State == FrameState::Exit) {
assert((Top.Func.getReturnType()->isVoidTy() || !Top.RetVal.isNone()) &&
"Expected return value to be set on function exit.");
Handler.onFunctionExit(Top.Func, Top.RetVal);
// Free stack objects allocated in this frame.
for (auto &Obj : Top.Allocas)
Ctx.free(*Obj);
CallStack.pop_back();
} else {
assert(Top.State == FrameState::Pending &&
"Expected to enter a callee.");
}
}
if (!hasProgramExited())
requestProgramExit(ProgramExitInfo::ProgramExitKind::Returned);
return *getExitInfo();
}
};
ProgramExitInfo Context::runFunction(Function &F, ArrayRef<AnyValue> Args,
AnyValue &RetVal, EventHandler &Handler) {
InstExecutor Executor(*this, Handler, F, Args, RetVal);
return Executor.runMainLoop();
}
} // namespace llvm::ubi