| //===- 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/ADT/STLExtras.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/Analysis/VectorUtils.h" |
| #include "llvm/IR/InlineAsm.h" |
| #include "llvm/IR/InstVisitor.h" |
| #include "llvm/IR/Intrinsics.h" |
| #include "llvm/IR/Operator.h" |
| #include "llvm/IR/PatternMatch.h" |
| #include "llvm/Support/Allocator.h" |
| #include "llvm/TargetParser/Triple.h" |
| |
| #include <cassert> |
| #include <cstring> |
| #include <limits> |
| |
| namespace llvm::ubi { |
| |
| using namespace PatternMatch; |
| |
| /// Visit the scalar values recursively. The callback function may modify the |
| /// value in-place. |
| static void forEachScalarValue(AnyValue &V, |
| function_ref<void(AnyValue &)> Visit) { |
| if (V.isNone()) |
| return; |
| |
| if (V.isAggregate()) { |
| for (auto &SubValue : V.asAggregate()) |
| forEachScalarValue(SubValue, Visit); |
| return; |
| } |
| |
| Visit(V); |
| } |
| |
| static void applyRangeAttr(AnyValue &V, const ConstantRange &CR) { |
| forEachScalarValue(V, [&](AnyValue &Scalar) { |
| if (Scalar.isInteger() && !CR.contains(Scalar.asInteger())) |
| Scalar = AnyValue::poison(); |
| }); |
| } |
| |
| static void applyNoFPClassAttr(AnyValue &V, FPClassTest NoFPClass) { |
| forEachScalarValue(V, [NoFPClass](AnyValue &Scalar) { |
| if (Scalar.isFloat() && (Scalar.asFloat().classify() & NoFPClass)) |
| Scalar = AnyValue::poison(); |
| }); |
| } |
| |
| static void applyNonNullAttr(AnyValue &V, unsigned AS, const DataLayout &DL) { |
| if (V.isPointer() && V.asPointer().isNullPtr(AS, DL)) |
| V = AnyValue::poison(); |
| } |
| |
| static void applyAlignAttr(AnyValue &V, Align Alignment) { |
| forEachScalarValue(V, [Alignment](AnyValue &Scalar) { |
| if (Scalar.isPointer() && |
| Scalar.asPointer().address().countr_zero() < Log2(Alignment)) |
| Scalar = AnyValue::poison(); |
| }); |
| } |
| |
| static bool violatesNoUndefAttr(AnyValue &V) { |
| bool ContainsPoison = false; |
| forEachScalarValue(V, [&](AnyValue &Scalar) { |
| if (Scalar.isPoison()) { |
| ContainsPoison = true; |
| return; |
| } |
| if (Scalar.isByte() && !ContainsPoison) { |
| // For non-byte-sized values, high bits are always zeroed out. |
| ContainsPoison = any_of(Scalar.asByte().bytes(), [](const Byte &V) { |
| return V.ConcreteMask != 255; |
| }); |
| } |
| }); |
| return ContainsPoison; |
| } |
| |
| /// Assumes V is either a poison or a pointer. |
| static bool violatesDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes, |
| bool OrNull, unsigned AS, |
| Context &Ctx) { |
| if (V.isPoison()) |
| return true; |
| |
| auto &Ptr = V.asPointer(); |
| if (Ptr.isNullPtr(AS, Ctx.getDataLayout())) { |
| if (OrNull) |
| return false; |
| return true; |
| } |
| |
| auto *MO = Ctx.checkProvenance(Ptr, [&](const Provenance &) { |
| // TODO: check read_provenance |
| // TODO: check nofree for attributes/metadata. |
| return true; |
| }); |
| if (!MO) |
| return true; |
| |
| const APInt &PtrAddr = Ptr.address(); |
| return Bytes > MO->getSize() || PtrAddr.ult(MO->getAddress()) || |
| PtrAddr.ugt(MO->getAddress() + MO->getSize() - Bytes); |
| } |
| |
| /// 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; |
| std::list<AnyValue> UnsupportedConstantValues; |
| Library Lib; |
| |
| const AnyValue &getValue(Value *V) { |
| if (auto *C = dyn_cast<Constant>(V)) { |
| if (const AnyValue *Val = Ctx.getConstantValue(C)) |
| return *Val; |
| reportError() << "Unsupported constant: " << *C << "."; |
| UnsupportedConstantValues.push_back( |
| AnyValue::getPoisonValue(Ctx, C->getType())); |
| return UnsupportedConstantValues.back(); |
| } |
| if (isa<MetadataAsValue>(V)) |
| return None; |
| return CurrentFrame->ValueMap.at(V); |
| } |
| |
| void setResult(Instruction &I, AnyValue V) { |
| if (!hasProgramExited() && !Handler.onInstructionExecuted(I, V)) |
| setFailed(); |
| if (hasProgramExited()) |
| return; |
| assert(V.isCompatibleWith(I.getType()) && "Unexpected value storage kind."); |
| if (!V.isNone()) |
| CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V)); |
| } |
| |
| APFloat handleDenormal(APFloat Val, DenormalMode::DenormalModeKind Mode, |
| bool IsInput) { |
| if (!Val.isDenormal()) |
| return Val; |
| if (IsInput) { |
| // Non-deterministically choose between flushing or preserving the |
| // denormal value. |
| if (Ctx.getRandomBool()) |
| return Val; |
| } |
| if (Mode == DenormalMode::PositiveZero) |
| return APFloat::getZero(Val.getSemantics(), false); |
| if (Mode == DenormalMode::PreserveSign) |
| return APFloat::getZero(Val.getSemantics(), Val.isNegative()); |
| // Default case for IEEE, Dynamic, and Invalid |
| // Currently we treat Dynamic the same as IEEE, since we don't support |
| // changing the mode at this point. |
| return Val; |
| } |
| |
| AnyValue handleFMFFlags(AnyValue Val, FastMathFlags FMF, bool IsInput) { |
| if (Val.isPoison()) |
| return AnyValue::poison(); |
| |
| if (Val.isAggregate()) { |
| std::vector<AnyValue> ResVec; |
| ResVec.reserve(Val.asAggregate().size()); |
| for (const auto &A : Val.asAggregate()) |
| ResVec.push_back(handleFMFFlags(A, FMF, IsInput)); |
| return AnyValue(ResVec); |
| } |
| |
| const APFloat &APVal = Val.asFloat(); |
| if (FMF.noNaNs() && APVal.isNaN()) |
| return AnyValue::poison(); |
| if (FMF.noInfs() && APVal.isInfinity()) |
| return AnyValue::poison(); |
| if (IsInput && FMF.noSignedZeros() && APVal.isZero()) |
| return AnyValue(APFloat::getZero( |
| APVal.getSemantics(), APVal.isNegative() ^ Ctx.getRandomBool())); |
| return Val; |
| } |
| |
| void addNaNCandidate(SmallVectorImpl<APFloat> &Candidates, |
| APFloat Candidate) { |
| if (any_of(Candidates, [&](const APFloat &Existing) { |
| return Existing.bitwiseIsEqual(Candidate); |
| })) |
| return; |
| Candidates.push_back(std::move(Candidate)); |
| } |
| |
| APFloat pickNaNCandidate(ArrayRef<APFloat> Candidates) { |
| assert(!Candidates.empty() && "Need at least one NaN candidate."); |
| return Candidates[Ctx.getRandomUInt64() % Candidates.size()]; |
| } |
| |
| APInt getRandomNaNPayload(const fltSemantics &Sem) { |
| const unsigned NumBits = Sem.precision - 1; |
| SmallVector<APInt::WordType, 2> RandomWords; |
| const unsigned NumWords = APInt::getNumWords(NumBits); |
| RandomWords.reserve(NumWords); |
| for (unsigned I = 0; I != NumWords; ++I) |
| RandomWords.push_back(Ctx.getRandomUInt64()); |
| return APInt(NumBits, RandomWords); |
| } |
| |
| bool isPreferredNaN(const APFloat &Val) { |
| assert(Val.isNaN() && "Expected NaN."); |
| const APFloat Preferred = |
| APFloat::getQNaN(Val.getSemantics(), Val.isNegative()); |
| return Val.bitwiseIsEqual(Preferred); |
| } |
| |
| bool wasmMayProduceExtraNaNPayload(ArrayRef<const APFloat *> Inputs) { |
| for (const APFloat *Input : Inputs) { |
| if (!Input->isNaN()) |
| continue; |
| if (Input->isSignaling() || !isPreferredNaN(*Input)) |
| return true; |
| } |
| return false; |
| } |
| |
| APFloat propagateInputNaN(const APFloat &InputNaN, const fltSemantics &DstSem, |
| bool QuietingMode, bool FlipSign) { |
| APFloat Res = InputNaN; |
| bool LosesInfo; |
| Res.convert(DstSem, APFloat::rmNearestTiesToEven, &LosesInfo); |
| if (FlipSign) |
| Res.changeSign(); |
| if (QuietingMode && Res.isSignaling()) |
| Res = Res.makeQuiet(); |
| return Res; |
| } |
| |
| APFloat maybeQuietSNaN(APFloat Val) const { |
| if (Val.isSignaling() && Ctx.getRandomBool()) |
| return Val.makeQuiet(); |
| return Val; |
| } |
| |
| APFloat maxnumWithSNaNQuieting(const APFloat &LHS, const APFloat &RHS) { |
| return maxnum(maybeQuietSNaN(LHS), maybeQuietSNaN(RHS)); |
| } |
| |
| APFloat minnumWithSNaNQuieting(const APFloat &LHS, const APFloat &RHS) { |
| return minnum(maybeQuietSNaN(LHS), maybeQuietSNaN(RHS)); |
| } |
| |
| void addPropagatedNaNCandidates(SmallVectorImpl<APFloat> &Candidates, |
| ArrayRef<const APFloat *> Inputs, |
| const fltSemantics &DstSem, bool QuietingMode, |
| bool SignChoice) { |
| for (const APFloat *Input : Inputs) { |
| if (!Input->isNaN()) |
| continue; |
| addNaNCandidate(Candidates, propagateInputNaN(*Input, DstSem, |
| QuietingMode, SignChoice)); |
| } |
| } |
| |
| void addTargetSpecificNaNCandidates(SmallVectorImpl<APFloat> &Candidates, |
| const APFloat &Result, |
| ArrayRef<const APFloat *> Inputs, |
| bool SignChoice) { |
| const Triple &TT = Ctx.getTargetTriple(); |
| if (TT.isWasm()) { |
| if (!wasmMayProduceExtraNaNPayload(Inputs)) |
| return; |
| APInt Payload = getRandomNaNPayload(Result.getSemantics()); |
| addNaNCandidate(Candidates, APFloat::getQNaN(Result.getSemantics(), |
| SignChoice, &Payload)); |
| return; |
| } |
| |
| if (TT.isSPARC32() || TT.isSPARC64()) { |
| APInt Payload = APInt::getAllOnes(Result.getSemantics().precision - 1); |
| addNaNCandidate(Candidates, APFloat::getQNaN(Result.getSemantics(), |
| SignChoice, &Payload)); |
| } |
| } |
| |
| APFloat applyNaNPropagation(const APFloat &Result, |
| ArrayRef<const APFloat *> Inputs) { |
| if (!Result.isNaN()) |
| return Result; |
| |
| const NaNPropagationBehavior Choice = |
| Ctx.getEffectiveNaNPropagationBehavior(); |
| const bool SignChoice = Ctx.getRandomBool(); |
| const fltSemantics &ResultSem = Result.getSemantics(); |
| auto PreferredNaN = [&]() { |
| return APFloat::getQNaN(ResultSem, SignChoice); |
| }; |
| |
| SmallVector<APFloat, 4> Candidates; |
| switch (Choice) { |
| case NaNPropagationBehavior::PreferredNaN: |
| return PreferredNaN(); |
| case NaNPropagationBehavior::QuietingNaN: |
| addPropagatedNaNCandidates(Candidates, Inputs, ResultSem, |
| /*QuietingMode=*/true, SignChoice); |
| return Candidates.empty() ? PreferredNaN() : pickNaNCandidate(Candidates); |
| case NaNPropagationBehavior::UnchangedNaN: |
| addPropagatedNaNCandidates(Candidates, Inputs, ResultSem, |
| /*QuietingMode=*/false, SignChoice); |
| return Candidates.empty() ? PreferredNaN() : pickNaNCandidate(Candidates); |
| case NaNPropagationBehavior::TargetSpecificNaN: |
| addTargetSpecificNaNCandidates(Candidates, Result, Inputs, SignChoice); |
| return Candidates.empty() ? PreferredNaN() : pickNaNCandidate(Candidates); |
| case NaNPropagationBehavior::NonDeterministic: |
| addNaNCandidate(Candidates, PreferredNaN()); |
| addPropagatedNaNCandidates(Candidates, Inputs, ResultSem, |
| /*QuietingMode=*/true, SignChoice); |
| addPropagatedNaNCandidates(Candidates, Inputs, ResultSem, |
| /*QuietingMode=*/false, SignChoice); |
| addTargetSpecificNaNCandidates(Candidates, Result, Inputs, SignChoice); |
| return pickNaNCandidate(Candidates); |
| } |
| llvm_unreachable("Unhandled NaN propagation behavior."); |
| } |
| |
| 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()); |
| }); |
| } |
| |
| void visitBitwiseFPUnOp(Instruction &I, |
| function_ref<APFloat(const APFloat &)> ScalarFn) { |
| setResult(I, visitBitwiseFPUnOpWithResult( |
| I.getType(), cast<FPMathOperator>(I).getFastMathFlags(), |
| getValue(I.getOperand(0)), ScalarFn)); |
| } |
| |
| AnyValue |
| visitIntUnOpWithResult(Type *RetTy, const AnyValue &Operand, |
| function_ref<AnyValue(const APInt &)> ScalarFn) { |
| return computeUnOp(RetTy, Operand, |
| [&](const AnyValue &OperandInner) -> AnyValue { |
| if (OperandInner.isPoison()) |
| return AnyValue::poison(); |
| return ScalarFn(OperandInner.asInteger()); |
| }); |
| } |
| |
| AnyValue visitBitwiseFPUnOpWithResult( |
| Type *RetTy, const FastMathFlags &FMF, const AnyValue &Operand, |
| function_ref<APFloat(const APFloat &)> ScalarFn) { |
| return computeUnOp( |
| RetTy, Operand, [&](const AnyValue &OperandInner) -> AnyValue { |
| if (OperandInner.isPoison()) |
| return AnyValue::poison(); |
| |
| // We don't flush denormals here since bitwise floating-point |
| // operations only manipulate on certain bits of the operand. |
| |
| AnyValue ValidatedOperand = |
| handleFMFFlags(OperandInner, FMF, /*IsInput=*/true); |
| if (ValidatedOperand.isPoison()) |
| return ValidatedOperand; |
| |
| APFloat Result = ScalarFn(ValidatedOperand.asFloat()); |
| |
| return handleFMFFlags(Result, FMF, /*IsInput=*/false); |
| }); |
| } |
| |
| 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()); |
| }); |
| } |
| |
| void visitFPBinOp( |
| Instruction &I, |
| function_ref<APFloat(const APFloat &, const APFloat &)> ScalarFn) { |
| setResult(I, visitFPBinOpWithResult( |
| I.getType(), cast<FPMathOperator>(I).getFastMathFlags(), |
| getValue(I.getOperand(0)), getValue(I.getOperand(1)), |
| ScalarFn)); |
| } |
| |
| AnyValue visitIntBinOpWithResult( |
| Type *RetTy, const AnyValue &LHS, const AnyValue &RHS, |
| function_ref<AnyValue(const APInt &, const APInt &)> ScalarFn) { |
| return computeBinOp( |
| RetTy, LHS, RHS, |
| [&](const AnyValue &LHSInner, const AnyValue &RHSInner) -> AnyValue { |
| if (LHSInner.isPoison() || RHSInner.isPoison()) |
| return AnyValue::poison(); |
| return ScalarFn(LHSInner.asInteger(), RHSInner.asInteger()); |
| }); |
| } |
| |
| AnyValue visitOverflowIntBinOpWithResult( |
| Type *RetTy, const AnyValue &LHS, const AnyValue &RHS, |
| function_ref<std::pair<APInt, bool>(const APInt &, const APInt &)> |
| ScalarFn) { |
| if (!LHS.isAggregate()) { |
| if (LHS.isPoison() || RHS.isPoison()) |
| return std::vector<AnyValue>{AnyValue::poison(), AnyValue::poison()}; |
| auto [Res, Overflow] = ScalarFn(LHS.asInteger(), RHS.asInteger()); |
| return std::vector<AnyValue>{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>{AnyValue(std::move(ResVec)), |
| AnyValue(std::move(OverflowVec))}; |
| } |
| |
| AnyValue visitFPBinOpWithResult( |
| Type *RetTy, const FastMathFlags &FMF, const AnyValue &LHS, |
| const AnyValue &RHS, |
| function_ref<APFloat(const APFloat &, const APFloat &)> ScalarFn) { |
| DenormalMode DenormMode = getCurrentDenormalMode(RetTy); |
| |
| if (!Ctx.isDefaultFPEnv()) |
| reportImmediateUB() << "Non-constrained floating-point operation assumes " |
| "default floating-point environment"; |
| |
| return computeBinOp( |
| RetTy, LHS, RHS, |
| [&](const AnyValue &LHSInner, const AnyValue &RHSInner) -> AnyValue { |
| if (LHSInner.isPoison() || RHSInner.isPoison()) |
| return AnyValue::poison(); |
| |
| AnyValue ValidatedLHS = |
| handleFMFFlags(LHSInner, FMF, /*IsInput=*/true); |
| AnyValue ValidatedRHS = |
| handleFMFFlags(RHSInner, FMF, /*IsInput=*/true); |
| if (ValidatedLHS.isPoison()) |
| return ValidatedLHS; |
| if (ValidatedRHS.isPoison()) |
| return ValidatedRHS; |
| |
| // Flush input denormals |
| APFloat FLHS = handleDenormal(ValidatedLHS.asFloat(), |
| DenormMode.Input, /*IsInput=*/true); |
| APFloat FRHS = handleDenormal(ValidatedRHS.asFloat(), |
| DenormMode.Input, /*IsInput=*/true); |
| |
| APFloat RawResult = ScalarFn(FLHS, FRHS); |
| |
| // Flush output denormals and handle fast-math flags. |
| AnyValue FResult = handleFMFFlags( |
| handleDenormal(RawResult, DenormMode.Output, /*IsInput=*/false), |
| FMF, |
| /*IsInput=*/false); |
| |
| if (FResult.isPoison()) |
| return FResult; |
| |
| APFloat Result = FResult.asFloat(); |
| return applyNaNPropagation(Result, {&FLHS, &FRHS}); |
| }); |
| } |
| |
| 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( |
| Type *RetTy, const AnyValue &Op1, const AnyValue &Op2, |
| const AnyValue &Op3, |
| function_ref<AnyValue(const APInt &, const APInt &, const APInt &)> |
| ScalarFn) { |
| return computeTriOp( |
| RetTy, Op1, Op2, Op3, |
| [&](const AnyValue &Op1Inner, const AnyValue &Op2Inner, |
| const AnyValue &Op3Inner) -> AnyValue { |
| if (Op1Inner.isPoison() || Op2Inner.isPoison() || Op3Inner.isPoison()) |
| return AnyValue::poison(); |
| return ScalarFn(Op1Inner.asInteger(), Op2Inner.asInteger(), |
| Op3Inner.asInteger()); |
| }); |
| } |
| |
| AnyValue visitFPTriOpWithResult( |
| Type *RetTy, const FastMathFlags &FMF, const AnyValue &Op1, |
| const AnyValue &Op2, const AnyValue &Op3, |
| function_ref<APFloat(const APFloat &, const APFloat &, const APFloat &)> |
| ScalarFn) { |
| DenormalMode DenormMode = getCurrentDenormalMode(RetTy); |
| |
| if (!Ctx.isDefaultFPEnv()) |
| reportImmediateUB() << "Non-constrained floating-point operation assumes " |
| "default floating-point environment"; |
| |
| return computeTriOp( |
| RetTy, Op1, Op2, Op3, |
| [&](const AnyValue &Op1Inner, const AnyValue &Op2Inner, |
| const AnyValue &Op3Inner) -> AnyValue { |
| if (Op1Inner.isPoison() || Op2Inner.isPoison() || Op3Inner.isPoison()) |
| return AnyValue::poison(); |
| |
| AnyValue ValidatedOp1 = |
| handleFMFFlags(Op1Inner, FMF, /*IsInput=*/true); |
| AnyValue ValidatedOp2 = |
| handleFMFFlags(Op2Inner, FMF, /*IsInput=*/true); |
| AnyValue ValidatedOp3 = |
| handleFMFFlags(Op3Inner, FMF, /*IsInput=*/true); |
| if (ValidatedOp1.isPoison()) |
| return ValidatedOp1; |
| if (ValidatedOp2.isPoison()) |
| return ValidatedOp2; |
| if (ValidatedOp3.isPoison()) |
| return ValidatedOp3; |
| |
| // Flush input denormals |
| APFloat FOp1 = handleDenormal(ValidatedOp1.asFloat(), |
| DenormMode.Input, /*IsInput=*/true); |
| APFloat FOp2 = handleDenormal(ValidatedOp2.asFloat(), |
| DenormMode.Input, /*IsInput=*/true); |
| APFloat FOp3 = handleDenormal(ValidatedOp3.asFloat(), |
| DenormMode.Input, /*IsInput=*/true); |
| |
| APFloat RawResult = ScalarFn(FOp1, FOp2, FOp3); |
| |
| // Flush output denormals and handle fast-math flags. |
| AnyValue FResult = handleFMFFlags( |
| handleDenormal(RawResult, DenormMode.Output, /*IsInput=*/false), |
| FMF, |
| /*IsInput=*/false); |
| |
| if (FResult.isPoison()) |
| return FResult; |
| |
| APFloat Result = FResult.asFloat(); |
| return applyNaNPropagation(Result, {&FOp1, &FOp2, &FOp3}); |
| }); |
| } |
| |
| 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))) { |
| AnyValue IncomingVal = getValue(PHI->getIncomingValueForBlock(From)); |
| |
| // Fast-math flags validation |
| if (isa<FPMathOperator>(PHI)) { |
| FastMathFlags FMF = PHI->getFastMathFlags(); |
| if (FMF.any()) |
| IncomingVal = |
| handleFMFFlags(std::move(IncomingVal), FMF, /*IsInput=*/true); |
| } |
| |
| IncomingValues.emplace_back(PHI, IncomingVal); |
| ++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; |
| } |
| |
| DenormalMode getCurrentDenormalMode(Type *Ty) { |
| return CurrentFrame->Func.getDenormalMode( |
| Ty->getScalarType()->getFltSemantics()); |
| } |
| |
| // 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; |
| } |
| |
| APInt getIntNonPoison(const AnyValue &V) { |
| if (V.isPoison()) { |
| reportImmediateUB() << "Unexpected poison integer value."; |
| return APInt::getZero(64); |
| } |
| return V.asInteger(); |
| } |
| |
| AnyValue callMemTransferIntrinsic(CallBase &CB, ArrayRef<AnyValue> Args, |
| Intrinsic::ID IID) { |
| const AnyValue &Dest = Args[0]; |
| const AnyValue &Src = Args[1]; |
| const AnyValue &Length = Args[2]; |
| // TODO: Handle isvolatile argument. |
| if (Length.isPoison()) { |
| reportImmediateUB() << "Memory transfer intrinsic with poison length."; |
| return AnyValue(); |
| } |
| |
| const APInt &LengthInt = Args[2].asInteger(); |
| if (LengthInt.getActiveBits() > 64) { |
| reportImmediateUB() |
| << "Memory transfer intrinsic length overflows uint64_t."; |
| return AnyValue(); |
| } |
| |
| const uint64_t Len = LengthInt.getZExtValue(); |
| if (Len == 0) |
| return AnyValue(); |
| |
| if (Dest.isPoison()) { |
| reportImmediateUB() |
| << "Memory transfer intrinsic with poison destination pointer."; |
| return AnyValue(); |
| } |
| |
| if (Src.isPoison()) { |
| reportImmediateUB() |
| << "Memory transfer intrinsic with poison source pointer."; |
| return AnyValue(); |
| } |
| |
| const Pointer &DstPtr = Dest.asPointer(); |
| const Pointer &SrcPtr = Src.asPointer(); |
| |
| Align DstAlign = CB.getParamAlign(0).valueOrOne(); |
| Align SrcAlign = CB.getParamAlign(1).valueOrOne(); |
| |
| auto [SrcMO, SrcOffset] = |
| verifyMemAccess(SrcPtr, Len, SrcAlign, /*IsStore=*/false); |
| if (!SrcMO) |
| return AnyValue(); |
| |
| auto [DstMO, DstOffset] = |
| verifyMemAccess(DstPtr, Len, DstAlign, /*IsStore=*/true); |
| if (!DstMO) |
| return AnyValue(); |
| |
| if (IID == Intrinsic::memcpy || IID == Intrinsic::memcpy_inline) { |
| if (SrcMO == DstMO && SrcOffset != DstOffset) { |
| const uint64_t SrcEnd = SrcOffset + Len; |
| const uint64_t DstEnd = DstOffset + Len; |
| if (SrcOffset < DstEnd && DstOffset < SrcEnd) { |
| reportImmediateUB() |
| << "memcpy with overlapping source and destination."; |
| return AnyValue(); |
| } |
| } |
| } |
| |
| MutableArrayRef<Byte> DstBytes = DstMO->getBytes().slice(DstOffset, Len); |
| ArrayRef<Byte> SrcBytes = SrcMO->getBytes().slice(SrcOffset, Len); |
| std::memmove(DstBytes.data(), SrcBytes.data(), Len * sizeof(Byte)); |
| return AnyValue(); |
| } |
| |
| AnyValue callMemSetIntrinsic(CallBase &CB, ArrayRef<AnyValue> Args) { |
| const AnyValue &Dest = Args[0]; |
| const AnyValue &Val = Args[1]; |
| const AnyValue &Length = Args[2]; |
| |
| if (Length.isPoison()) { |
| reportImmediateUB() << "memset called with poison length."; |
| return AnyValue(); |
| } |
| |
| const APInt &LengthInt = Length.asInteger(); |
| if (LengthInt.getActiveBits() > 64) { |
| reportImmediateUB() << "memset called with length overflows uint64_t."; |
| return AnyValue(); |
| } |
| |
| const uint64_t Len = LengthInt.getZExtValue(); |
| if (Len == 0) |
| return AnyValue(); |
| |
| if (Dest.isPoison()) { |
| reportImmediateUB() << "memset called with poison destination pointer."; |
| return AnyValue(); |
| } |
| |
| const Pointer &DstPtr = Dest.asPointer(); |
| |
| Align DstAlign = CB.getParamAlign(0).valueOrOne(); |
| auto [DstMO, DstOffset] = |
| verifyMemAccess(DstPtr, Len, DstAlign, /*IsStore=*/true); |
| if (!DstMO) |
| return AnyValue(); |
| |
| Byte FillByte = Val.isPoison() |
| ? Byte::poison() |
| : Byte::concrete(Val.asInteger().getZExtValue()); |
| fill(DstMO->getBytes().slice(DstOffset, Len), FillByte); |
| return AnyValue(); |
| } |
| |
| static BooleanKind getMaskLane(const AnyValue &Mask, size_t I) { |
| return Mask.asAggregate()[I].asBoolean(); |
| } |
| |
| AnyValue callExperimentalVectorHistogramIntrinsic(CallBase &CB, |
| ArrayRef<AnyValue> Args, |
| Intrinsic::ID IID) { |
| struct LaneUpdate { |
| MemoryObject *MO; |
| uint64_t Offset; |
| uint64_t Count; |
| AnyValue Old; |
| AnyValue New; |
| }; |
| |
| const auto &Ptrs = Args[0].asAggregate(); |
| const AnyValue &Update = Args[1]; |
| const AnyValue &Mask = Args[2]; |
| Type *ElemTy = CB.getArgOperand(1)->getType(); |
| const uint64_t AccessSize = Ctx.getEffectiveTypeStoreSize(ElemTy); |
| |
| SmallVector<LaneUpdate, 8> Lanes; |
| Lanes.reserve(Ptrs.size()); |
| for (size_t I = 0, E = Ptrs.size(); I != E; ++I) { |
| switch (getMaskLane(Mask, I)) { |
| case BooleanKind::False: |
| continue; |
| case BooleanKind::Poison: |
| reportImmediateUB() |
| << "Poison mask lane in experimental vector histogram intrinsic."; |
| return AnyValue(); |
| case BooleanKind::True: |
| break; |
| } |
| |
| if (Ptrs[I].isPoison()) { |
| reportImmediateUB() << "Poison pointer lane in experimental vector " |
| "histogram intrinsic."; |
| return AnyValue(); |
| } |
| |
| auto [MO, Offset] = |
| verifyMemAccess(Ptrs[I].asPointer(), AccessSize, Align(1), |
| /*IsStore=*/true); |
| if (!MO) |
| return AnyValue(); |
| |
| Lanes.push_back({MO, Offset, 0, AnyValue(), AnyValue()}); |
| } |
| |
| for (LaneUpdate &Lane : Lanes) { |
| Lane.Count = count_if(Lanes, [&](const LaneUpdate &Other) { |
| return Other.MO == Lane.MO && Other.Offset == Lane.Offset; |
| }); |
| Lane.Old = Ctx.load(*Lane.MO, Lane.Offset, ElemTy); |
| } |
| |
| for (LaneUpdate &Lane : Lanes) { |
| const AnyValue &Old = Lane.Old; |
| AnyValue &New = Lane.New; |
| |
| if (Old.isPoison() || Update.isPoison()) { |
| New = AnyValue::poison(); |
| } else { |
| const APInt &OldInt = Old.asInteger(); |
| const APInt &UpdateInt = Update.asInteger(); |
| |
| switch (IID) { |
| case Intrinsic::experimental_vector_histogram_add: |
| New = OldInt + UpdateInt * APInt(UpdateInt.getBitWidth(), Lane.Count, |
| /*isSigned=*/false, |
| /*implicitTrunc=*/true); |
| break; |
| case Intrinsic::experimental_vector_histogram_uadd_sat: { |
| APInt Acc = OldInt; |
| for (uint64_t I = 0; I != Lane.Count; ++I) |
| Acc = Acc.uadd_sat(UpdateInt); |
| New = Acc; |
| break; |
| } |
| case Intrinsic::experimental_vector_histogram_umax: |
| New = APIntOps::umax(OldInt, UpdateInt); |
| break; |
| case Intrinsic::experimental_vector_histogram_umin: |
| New = APIntOps::umin(OldInt, UpdateInt); |
| break; |
| default: |
| llvm_unreachable("Unexpected histogram intrinsic ID"); |
| } |
| } |
| } |
| |
| for (const LaneUpdate &Lane : Lanes) |
| Ctx.store(*Lane.MO, Lane.Offset, Lane.New, ElemTy); |
| |
| return AnyValue(); |
| } |
| |
| 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); |
| else |
| CurrentFrame->RetVal = AnyValue(); |
| 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() { |
| auto &CB = cast<CallBase>(*CurrentFrame->PC); |
| CurrentFrame->CalleeArgs.clear(); |
| AnyValue &RetVal = CurrentFrame->CalleeRetVal; |
| if (Type *RetTy = CB.getType(); !RetTy->isVoidTy()) { |
| // Handle attributes on the return value (Attributes from resolved callee |
| // should be applied if available). |
| AttributeSet AttrsAtCallSite = CB.getRetAttributes(); |
| AttributeSet AttrsAtCallee = |
| CurrentFrame->ResolvedCallee->getAttributes().getRetAttrs(); |
| handleAttributes(RetTy, RetVal, AttrsAtCallSite, AttrsAtCallee); |
| handleMetadata(RetTy, RetVal, CB); |
| } |
| setResult(CB, std::move(RetVal)); |
| |
| for (auto &ByValArg : CurrentFrame->CalleeByValArgs) |
| Ctx.free(*ByValArg); |
| CurrentFrame->CalleeByValArgs.clear(); |
| |
| 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(); |
| const FastMathFlags FMF = CB.getFastMathFlagsOrNone(); |
| |
| switch (IID) { |
| case Intrinsic::assume: |
| switch (Args[0].asBoolean()) { |
| case BooleanKind::True: |
| for (unsigned Idx = 0; Idx < CB.getNumOperandBundles(); Idx++) { |
| OperandBundleUse OBU = CB.getOperandBundleAt(Idx); |
| auto GetBundleArg = [&](uint32_t Offset) -> Value * { |
| return OBU.Inputs[Offset]; |
| }; |
| if (OBU.Inputs.empty()) |
| continue; |
| Value *WasOnVal = GetBundleArg(0); |
| // Bail out on unrecognized operand bundles. |
| if (!WasOnVal->getType()->isPointerTy()) |
| continue; |
| unsigned AS = WasOnVal->getType()->getPointerAddressSpace(); |
| const AnyValue &WasOn = getValue(WasOnVal); |
| if (WasOn.isPoison()) { |
| reportImmediateUB() << "Assume on poison pointer."; |
| break; |
| } |
| const Pointer &WasOnPtr = WasOn.asPointer(); |
| Attribute::AttrKind Kind = |
| Attribute::getAttrKindFromName(OBU.getTagName()); |
| switch (Kind) { |
| case Attribute::Alignment: { |
| // Alignment assumptions should have 2 or 3 arguments. |
| APInt Alignment = getIntNonPoison(getValue(GetBundleArg(1))); |
| APInt CheckedAddr = WasOnPtr.address(); |
| if (OBU.Inputs.size() == 3) { |
| APInt Offset = getIntNonPoison(getValue(GetBundleArg(2))); |
| CheckedAddr -= Offset.sextOrTrunc(CheckedAddr.getBitWidth()); |
| } |
| if (!Alignment.isPowerOf2()) { |
| if (!CheckedAddr.isZero()) |
| reportImmediateUB() << "Assume on pointer " << WasOn |
| << " with a nonzero adjusted address and a " |
| "non-power-of-two alignment " |
| << Alignment << '.'; |
| break; |
| } |
| if (CheckedAddr.countr_zero() < Alignment.logBase2()) |
| reportImmediateUB() |
| << "The pointer " << WasOn << " violates align(" << Alignment |
| << ") assumption."; |
| break; |
| } |
| case Attribute::NonNull: |
| if (WasOnPtr.isNullPtr(AS, DL)) |
| reportImmediateUB() |
| << "The pointer " << WasOn << " violates nonnull assumption."; |
| break; |
| case Attribute::Dereferenceable: |
| case Attribute::DereferenceableOrNull: { |
| APInt DereferenceableBytes = |
| getIntNonPoison(getValue(GetBundleArg(1))); |
| // Only n > 0 implies that the pointer is dereferenceable. |
| if (DereferenceableBytes.isZero()) |
| break; |
| if (violatesDereferenceableBytesAttr( |
| WasOn, DereferenceableBytes.getLimitedValue(), |
| Kind == Attribute::DereferenceableOrNull, AS, Ctx)) |
| reportImmediateUB() << "The pointer " << WasOn << " violates " |
| << (Kind == Attribute::DereferenceableOrNull |
| ? "dereferenceable_or_null(" |
| : "dereferenceable(") |
| << DereferenceableBytes << ") assumption."; |
| break; |
| } |
| default: |
| // TODO: handle other operand bundles like separate_storage. |
| break; |
| } |
| } |
| break; |
| case BooleanKind::False: |
| case BooleanKind::Poison: |
| reportImmediateUB() << "Assume on false or poison condition."; |
| break; |
| } |
| return AnyValue(); |
| case Intrinsic::lifetime_start: |
| case Intrinsic::lifetime_end: { |
| auto Ptr = Args[0]; |
| if (Ptr.isPoison()) |
| return AnyValue(); |
| auto *MO = Ctx.checkProvenance(Ptr.asPointer(), |
| [](const Provenance &) { return true; }); |
| 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 { |
| fill(MO->getBytes(), Byte::poison()); |
| 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( |
| RetTy, Args[0], [&](const APInt &Operand) -> AnyValue { |
| if (IsIntMinPoison && Operand.isMinSignedValue()) |
| return AnyValue::poison(); |
| return Operand.abs(); |
| }); |
| } |
| case Intrinsic::smax: { |
| return visitIntBinOpWithResult( |
| RetTy, Args[0], Args[1], |
| [](const APInt &LHS, const APInt &RHS) -> AnyValue { |
| return APIntOps::smax(LHS, RHS); |
| }); |
| } |
| case Intrinsic::smin: { |
| return visitIntBinOpWithResult( |
| RetTy, Args[0], Args[1], |
| [](const APInt &LHS, const APInt &RHS) -> AnyValue { |
| return APIntOps::smin(LHS, RHS); |
| }); |
| } |
| case Intrinsic::umax: { |
| return visitIntBinOpWithResult( |
| RetTy, Args[0], Args[1], |
| [](const APInt &LHS, const APInt &RHS) -> AnyValue { |
| return APIntOps::umax(LHS, RHS); |
| }); |
| } |
| case Intrinsic::umin: { |
| return visitIntBinOpWithResult( |
| RetTy, Args[0], Args[1], |
| [](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( |
| RetTy, Args[0], Args[1], |
| [&](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(RetTy, Args[0], |
| [](const APInt &Operand) -> AnyValue { |
| return Operand.reverseBits(); |
| }); |
| } |
| case Intrinsic::bswap: { |
| return visitIntUnOpWithResult( |
| RetTy, Args[0], |
| [](const APInt &Operand) -> AnyValue { return Operand.byteSwap(); }); |
| } |
| case Intrinsic::ctpop: { |
| return visitIntUnOpWithResult( |
| RetTy, Args[0], [](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( |
| RetTy, Args[0], [&](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( |
| RetTy, Args[0], Args[1], Args[2], |
| [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( |
| RetTy, Args[0], Args[1], |
| [](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( |
| RetTy, Args[0], Args[1], |
| [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( |
| RetTy, Args[0], Args[1], |
| [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: { |
| assert(!Args[2].isPoison() && |
| "Verifier should reject poison vector_insert immarg."); |
| const auto &Vec = Args[0].asAggregate(); |
| const auto &SubVec = Args[1].asAggregate(); |
| const auto &Idx = Args[2].asInteger(); |
| auto EC = |
| cast<VectorType>(CB.getArgOperand(1)->getType())->getElementCount(); |
| const uint64_t RawOffset = Idx.getZExtValue(); |
| const uint32_t MinSize = EC.getKnownMinValue(); |
| assert(RawOffset % MinSize == 0 && |
| "Verifier should reject misaligned vector_insert index."); |
| const uint64_t Chunk = RawOffset / MinSize; |
| const uint64_t EVL = Ctx.getEVL(EC); |
| if (Chunk > std::numeric_limits<uint64_t>::max() / EVL) |
| return AnyValue::getPoisonValue(Ctx, RetTy); |
| const uint64_t Offset = Chunk * EVL; |
| if (Offset > Vec.size() || SubVec.size() > Vec.size() - Offset) |
| return AnyValue::getPoisonValue(Ctx, RetTy); |
| 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: { |
| assert(!Args[1].isPoison() && |
| "Verifier should reject poison vector_extract immarg."); |
| const auto &Vec = Args[0].asAggregate(); |
| const auto &Idx = Args[1].asInteger(); |
| auto EC = cast<VectorType>(RetTy)->getElementCount(); |
| const uint64_t RawOffset = Idx.getZExtValue(); |
| const uint32_t MinSize = EC.getKnownMinValue(); |
| assert(RawOffset % MinSize == 0 && |
| "Verifier should reject misaligned vector_extract index."); |
| const uint64_t Chunk = RawOffset / MinSize; |
| const uint64_t EVL = Ctx.getEVL(EC); |
| if (Chunk > std::numeric_limits<uint64_t>::max() / EVL) |
| return AnyValue::getPoisonValue(Ctx, RetTy); |
| const uint64_t Offset = Chunk * EVL; |
| if (Offset > Vec.size() || EVL > Vec.size() - Offset) |
| return AnyValue::getPoisonValue(Ctx, RetTy); |
| return std::vector<AnyValue>(Vec.begin() + Offset, |
| Vec.begin() + Offset + EVL); |
| } |
| 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::getPoisonValue(Ctx, RetTy); |
| 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::getPoisonValue(Ctx, RetTy); |
| 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::getPoisonValue(Ctx, RetTy); |
| 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::getPoisonValue(Ctx, RetTy); |
| 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); |
| } |
| case Intrinsic::vector_reduce_fadd: |
| case Intrinsic::vector_reduce_fmul: |
| case Intrinsic::vector_reduce_fmaximum: |
| case Intrinsic::vector_reduce_fminimum: { |
| const auto DenormMode = getCurrentDenormalMode(RetTy); |
| const bool HasStart = IID == Intrinsic::vector_reduce_fadd || |
| IID == Intrinsic::vector_reduce_fmul; |
| const AnyValue &Vector = HasStart ? Args[1] : Args[0]; |
| std::optional<APFloat> Res; |
| if (HasStart) { |
| if (Args[0].isPoison()) |
| return AnyValue::poison(); |
| const AnyValue ValidatedStart = |
| handleFMFFlags(Args[0], FMF, /*IsInput=*/true); |
| if (ValidatedStart.isPoison()) |
| return AnyValue::poison(); |
| Res = handleDenormal(ValidatedStart.asFloat(), DenormMode.Input, |
| /*IsInput=*/true); |
| } |
| for (const auto &V : Vector.asAggregate()) { |
| if (V.isPoison()) |
| return AnyValue::poison(); |
| const AnyValue ValidatedOp = handleFMFFlags(V, FMF, /*IsInput=*/true); |
| if (ValidatedOp.isPoison()) |
| return AnyValue::poison(); |
| APFloat Op = handleDenormal(ValidatedOp.asFloat(), DenormMode.Input, |
| /*IsInput=*/true); |
| if (!Res) { |
| Res = std::move(Op); |
| continue; |
| } |
| switch (IID) { |
| case Intrinsic::vector_reduce_fadd: |
| *Res = *Res + Op; |
| break; |
| case Intrinsic::vector_reduce_fmul: |
| *Res = *Res * Op; |
| break; |
| case Intrinsic::vector_reduce_fmaximum: |
| *Res = maximum(*Res, Op); |
| break; |
| case Intrinsic::vector_reduce_fminimum: |
| *Res = minimum(*Res, Op); |
| break; |
| default: |
| llvm_unreachable("Unexpected intrinsic ID"); |
| } |
| } |
| assert(Res.has_value()); |
| const AnyValue ValidatedRes = |
| handleFMFFlags(*Res, FMF, /*IsInput=*/false); |
| if (ValidatedRes.isPoison()) |
| return AnyValue::poison(); |
| const APFloat FRes = |
| handleDenormal(ValidatedRes.asFloat(), DenormMode.Output, |
| /*IsInput=*/false); |
| SmallVector<const APFloat *, 8> InputVec; |
| InputVec.reserve(Vector.asAggregate().size()); |
| transform( |
| Vector.asAggregate(), std::back_inserter(InputVec), |
| [](const AnyValue &V) -> const APFloat * { return &V.asFloat(); }); |
| return applyNaNPropagation(FRes, InputVec); |
| } |
| case Intrinsic::vector_reduce_fmax: |
| case Intrinsic::vector_reduce_fmin: { |
| const auto DenormMode = getCurrentDenormalMode(RetTy); |
| const auto &Vector = Args[0].asAggregate(); |
| SmallVector<APFloat, 8> InputFloats; |
| SmallVector<const APFloat *, 8> InputVec; |
| InputFloats.reserve(Vector.size()); |
| InputVec.reserve(Vector.size()); |
| for (const auto &V : Vector) { |
| if (V.isPoison()) |
| return AnyValue::poison(); |
| const AnyValue ValidatedOp = handleFMFFlags(V, FMF, /*IsInput=*/true); |
| if (ValidatedOp.isPoison()) |
| return AnyValue::poison(); |
| InputFloats.push_back(handleDenormal(ValidatedOp.asFloat(), |
| DenormMode.Input, |
| /*IsInput=*/true)); |
| InputVec.push_back(&InputFloats.back()); |
| } |
| assert(!InputVec.empty()); |
| SmallVector<APFloat, 8> Worklist(InputFloats); |
| const bool HasSNaN = |
| any_of(InputVec, [](const APFloat *V) { return V->isSignaling(); }); |
| while (Worklist.size() > 1) { |
| size_t LHSIdx = 0; |
| size_t RHSIdx = 1; |
| if (HasSNaN) { |
| LHSIdx = Ctx.getRandomUInt64() % Worklist.size(); |
| RHSIdx = Ctx.getRandomUInt64() % (Worklist.size() - 1); |
| if (RHSIdx >= LHSIdx) |
| ++RHSIdx; |
| } |
| |
| APFloat Res = |
| IID == Intrinsic::vector_reduce_fmax |
| ? maxnumWithSNaNQuieting(Worklist[LHSIdx], Worklist[RHSIdx]) |
| : minnumWithSNaNQuieting(Worklist[LHSIdx], Worklist[RHSIdx]); |
| if (LHSIdx < RHSIdx) |
| std::swap(LHSIdx, RHSIdx); |
| Worklist.erase(Worklist.begin() + LHSIdx); |
| Worklist.erase(Worklist.begin() + RHSIdx); |
| Worklist.push_back(std::move(Res)); |
| } |
| |
| AnyValue ValidatedRes = |
| handleFMFFlags(Worklist.front(), FMF, /*IsInput=*/false); |
| if (ValidatedRes.isPoison()) |
| return AnyValue::poison(); |
| APFloat FRes = handleDenormal(ValidatedRes.asFloat(), DenormMode.Output, |
| /*IsInput=*/false); |
| |
| return applyNaNPropagation(FRes, InputVec); |
| } |
| case Intrinsic::fabs: { |
| return visitBitwiseFPUnOpWithResult( |
| RetTy, FMF, Args[0], |
| [](const APFloat &Operand) -> APFloat { return abs(Operand); }); |
| } |
| case Intrinsic::fma: { |
| return visitFPTriOpWithResult( |
| RetTy, FMF, Args[0], Args[1], Args[2], |
| [](const APFloat &Op1, const APFloat &Op2, |
| const APFloat &Op3) -> APFloat { |
| auto Res = Op1; |
| Res.fusedMultiplyAdd(Op2, Op3, RoundingMode::NearestTiesToEven); |
| return Res; |
| }); |
| } |
| case Intrinsic::fmuladd: { |
| return visitFPTriOpWithResult( |
| RetTy, FMF, Args[0], Args[1], Args[2], |
| [&](const APFloat &Op1, const APFloat &Op2, |
| const APFloat &Op3) -> APFloat { |
| if (Ctx.fuseMultiplyAdd()) { |
| auto Res = Op1; |
| Res.fusedMultiplyAdd(Op2, Op3, RoundingMode::NearestTiesToEven); |
| return Res; |
| } |
| return Op1 * Op2 + Op3; |
| }); |
| } |
| case Intrinsic::is_fpclass: { |
| const FPClassTest Mask = |
| static_cast<FPClassTest>(Args[1].asInteger().getZExtValue()); |
| return computeUnOp(RetTy, Args[0], [&](const AnyValue &Op) -> AnyValue { |
| if (Op.isPoison()) |
| return AnyValue::poison(); |
| return AnyValue::boolean( |
| static_cast<bool>(Op.asFloat().classify() & Mask)); |
| }); |
| } |
| case Intrinsic::copysign: { |
| return computeBinOp( |
| RetTy, Args[0], Args[1], |
| [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue { |
| if (LHS.isPoison() || RHS.isPoison()) |
| return AnyValue::poison(); |
| const AnyValue ValidatedLHS = |
| handleFMFFlags(LHS, FMF, /*IsInput=*/true); |
| const AnyValue ValidatedRHS = |
| handleFMFFlags(RHS, FMF, /*IsInput=*/true); |
| if (ValidatedLHS.isPoison() || ValidatedRHS.isPoison()) |
| return AnyValue::poison(); |
| |
| return handleFMFFlags(APFloat::copySign(ValidatedLHS.asFloat(), |
| ValidatedRHS.asFloat()), |
| FMF, /*IsInput=*/false); |
| }); |
| } |
| case Intrinsic::maxnum: |
| case Intrinsic::minnum: |
| case Intrinsic::maximum: |
| case Intrinsic::minimum: |
| case Intrinsic::maximumnum: |
| case Intrinsic::minimumnum: { |
| return visitFPBinOpWithResult( |
| RetTy, FMF, Args[0], Args[1], |
| [&](const APFloat &LHS, const APFloat &RHS) -> APFloat { |
| switch (IID) { |
| case Intrinsic::maximum: |
| return maximum(LHS, RHS); |
| case Intrinsic::minimum: |
| return minimum(LHS, RHS); |
| case Intrinsic::maximumnum: |
| return maximumnum(LHS, RHS); |
| case Intrinsic::minimumnum: |
| return minimumnum(LHS, RHS); |
| case Intrinsic::maxnum: |
| return maxnumWithSNaNQuieting(LHS, RHS); |
| case Intrinsic::minnum: |
| return minnumWithSNaNQuieting(LHS, RHS); |
| default: |
| llvm_unreachable("Unexpected intrinsic ID"); |
| } |
| }); |
| } |
| case Intrinsic::fptosi_sat: |
| case Intrinsic::fptoui_sat: { |
| const auto BitWidth = RetTy->getScalarSizeInBits(); |
| return computeUnOp(RetTy, Args[0], [&](const AnyValue &Op) -> AnyValue { |
| if (Op.isPoison()) |
| return AnyValue::poison(); |
| const APFloat &Operand = Op.asFloat(); |
| APSInt V(BitWidth, IID == Intrinsic::fptoui_sat); |
| [[maybe_unused]] bool IsExact; |
| Operand.convertToInteger(V, APFloat::rmTowardZero, &IsExact); |
| return V; |
| }); |
| } |
| case Intrinsic::memcpy: |
| case Intrinsic::memcpy_inline: |
| case Intrinsic::memmove: |
| return callMemTransferIntrinsic(CB, Args, IID); |
| case Intrinsic::memset: |
| case Intrinsic::memset_inline: |
| return callMemSetIntrinsic(CB, Args); |
| case Intrinsic::experimental_noalias_scope_decl: |
| // FIXME: Not implemented yet. Currently it acts as a noop. |
| return AnyValue(); |
| case Intrinsic::experimental_cttz_elts: { |
| auto *IsZeroPoisonC = cast<ConstantInt>(CB.getArgOperand(1)); |
| const bool IsZeroPoison = IsZeroPoisonC->isOne(); |
| |
| const auto &Vec = Args[0].asAggregate(); |
| const unsigned RetBW = RetTy->getIntegerBitWidth(); |
| |
| if (!isUIntN(RetBW, Vec.size())) |
| return AnyValue::poison(); |
| |
| uint64_t Count = 0; |
| for (const AnyValue &V : Vec) { |
| if (V.isPoison()) |
| return AnyValue::poison(); |
| if (!V.asInteger().isZero()) |
| break; |
| ++Count; |
| } |
| |
| if (Count == Vec.size() && IsZeroPoison) |
| return AnyValue::poison(); |
| return APInt(RetBW, Count); |
| } |
| case Intrinsic::experimental_get_vector_length: { |
| auto *VFC = cast<ConstantInt>(CB.getArgOperand(1)); |
| auto *ScalableC = cast<ConstantInt>(CB.getArgOperand(2)); |
| |
| if (Args[0].isPoison()) |
| return AnyValue::poison(); |
| |
| const APInt &Cnt = Args[0].asInteger(); |
| const uint64_t VF = VFC->getZExtValue(); |
| const bool Scalable = ScalableC->isOne(); |
| |
| const uint64_t MaxLanes = Ctx.getEVL(ElementCount::get(VF, Scalable)); |
| |
| uint64_t Res = 0; |
| if (!Cnt.isZero()) { |
| if (Cnt.getActiveBits() <= 64 && Cnt.getZExtValue() <= MaxLanes) { |
| Res = Cnt.getZExtValue(); |
| } else { |
| APInt Max(Cnt.getBitWidth(), MaxLanes); |
| APInt NumIters = |
| APIntOps::RoundingUDiv(Cnt, Max, APInt::Rounding::UP); |
| uint64_t Lower = |
| APIntOps::RoundingUDiv(Cnt, NumIters, APInt::Rounding::UP) |
| .getZExtValue(); |
| uint64_t Range = MaxLanes - Lower + 1; |
| Res = Lower + Ctx.getRandomUInt64() % Range; |
| } |
| } |
| |
| if (isIntN(32, Res)) |
| return APInt(32, Res); |
| return AnyValue::poison(); |
| } |
| |
| case Intrinsic::experimental_vector_extract_last_active: { |
| const auto &Data = Args[0].asAggregate(); |
| const AnyValue &Mask = Args[1]; |
| |
| for (size_t I = Data.size(); I != 0; --I) { |
| switch (getMaskLane(Mask, I - 1)) { |
| case BooleanKind::True: |
| return Data[I - 1]; |
| case BooleanKind::False: |
| break; |
| case BooleanKind::Poison: |
| return AnyValue::poison(); |
| } |
| } |
| |
| return Args[2]; |
| } |
| |
| case Intrinsic::experimental_vector_compress: { |
| const auto &Val = Args[0].asAggregate(); |
| const AnyValue &Mask = Args[1]; |
| const auto &Passthru = Args[2].asAggregate(); |
| |
| std::vector<AnyValue> Res; |
| Res.reserve(Val.size()); |
| |
| for (size_t I = 0, E = Val.size(); I != E; ++I) { |
| switch (getMaskLane(Mask, I)) { |
| case BooleanKind::True: |
| Res.push_back(Val[I]); |
| break; |
| case BooleanKind::False: |
| break; |
| case BooleanKind::Poison: |
| return AnyValue::getPoisonValue(Ctx, RetTy); |
| } |
| } |
| |
| for (size_t I = Res.size(), E = Val.size(); I != E; ++I) |
| Res.push_back(Passthru[I]); |
| return std::move(Res); |
| } |
| |
| case Intrinsic::experimental_vector_match: { |
| const auto &Search = Args[0].asAggregate(); |
| const auto &Needles = Args[1].asAggregate(); |
| const auto &Mask = Args[2].asAggregate(); |
| |
| std::vector<AnyValue> Res; |
| Res.reserve(Search.size()); |
| |
| for (size_t I = 0, E = Search.size(); I != E; ++I) { |
| switch (Mask[I].asBoolean()) { |
| case BooleanKind::False: |
| Res.push_back(AnyValue::boolean(false)); |
| continue; |
| case BooleanKind::Poison: |
| Res.push_back(AnyValue::poison()); |
| continue; |
| case BooleanKind::True: |
| break; |
| } |
| |
| if (Search[I].isPoison()) { |
| Res.push_back(AnyValue::poison()); |
| continue; |
| } |
| |
| bool Found = false; |
| bool SawPoison = false; |
| for (const AnyValue &Needle : Needles) { |
| if (Needle.isPoison()) { |
| SawPoison = true; |
| break; |
| } |
| if (Search[I].asInteger() == Needle.asInteger()) |
| Found = true; |
| } |
| |
| if (SawPoison) |
| Res.push_back(AnyValue::poison()); |
| else |
| Res.push_back(AnyValue::boolean(Found)); |
| } |
| |
| return std::move(Res); |
| } |
| case Intrinsic::experimental_vector_histogram_add: |
| case Intrinsic::experimental_vector_histogram_uadd_sat: |
| case Intrinsic::experimental_vector_histogram_umax: |
| case Intrinsic::experimental_vector_histogram_umin: |
| return callExperimentalVectorHistogramIntrinsic(CB, Args, IID); |
| 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(); |
| } |
| |
| /// Handle both poison-generating and UB-implying attributes for parameters |
| /// and return values. |
| void handleAttributes(Type *Ty, AnyValue &V, AttributeSet AttrsAtCallSite, |
| AttributeSet AttrsAtCallee) { |
| if (Ty->isIntOrIntVectorTy()) { |
| if (auto CRAttr = AttrsAtCallSite.getAttribute(Attribute::Range); |
| CRAttr.isValid()) |
| applyRangeAttr(V, CRAttr.getRange()); |
| if (auto CRAttr = AttrsAtCallee.getAttribute(Attribute::Range); |
| CRAttr.isValid()) |
| applyRangeAttr(V, CRAttr.getRange()); |
| } |
| if (AttributeFuncs::isNoFPClassCompatibleType(Ty)) { |
| if (auto CRAttr = AttrsAtCallSite.getAttribute(Attribute::NoFPClass); |
| CRAttr.isValid()) |
| applyNoFPClassAttr(V, CRAttr.getNoFPClass()); |
| if (auto CRAttr = AttrsAtCallee.getAttribute(Attribute::NoFPClass); |
| CRAttr.isValid()) |
| applyNoFPClassAttr(V, CRAttr.getNoFPClass()); |
| } |
| if (Ty->isPointerTy()) { |
| if (AttrsAtCallSite.hasAttribute(Attribute::NonNull) || |
| AttrsAtCallee.hasAttribute(Attribute::NonNull)) |
| applyNonNullAttr(V, Ty->getPointerAddressSpace(), DL); |
| } |
| if (Ty->isPtrOrPtrVectorTy()) { |
| if (MaybeAlign Align = AttrsAtCallSite.getAlignment()) |
| applyAlignAttr(V, *Align); |
| if (MaybeAlign Align = AttrsAtCallee.getAlignment()) |
| applyAlignAttr(V, *Align); |
| } |
| if ((AttrsAtCallSite.hasAttribute(Attribute::NoUndef) || |
| AttrsAtCallee.hasAttribute(Attribute::NoUndef)) && |
| violatesNoUndefAttr(V)) { |
| reportImmediateUB() << "The value " << V |
| << " violates noundef attribute."; |
| return; |
| } |
| if (Ty->isPointerTy()) { |
| unsigned AS = Ty->getPointerAddressSpace(); |
| if (uint64_t DereferenceableBytes = |
| std::max(AttrsAtCallSite.getDereferenceableBytes(), |
| AttrsAtCallee.getDereferenceableBytes())) { |
| if (violatesDereferenceableBytesAttr(V, DereferenceableBytes, |
| /*OrNull=*/false, AS, Ctx)) |
| reportImmediateUB() |
| << "The value " << V << " violates dereferenceable(" |
| << DereferenceableBytes << ") attribute."; |
| } else if (uint64_t DereferenceableOrNullBytes = |
| std::max(AttrsAtCallSite.getDereferenceableOrNullBytes(), |
| AttrsAtCallee.getDereferenceableOrNullBytes())) { |
| if (violatesDereferenceableBytesAttr(V, DereferenceableOrNullBytes, |
| /*OrNull=*/true, AS, Ctx)) |
| reportImmediateUB() << "The value " << V |
| << " violates " |
| "dereferenceable_or_null(" |
| << DereferenceableOrNullBytes << ") attribute."; |
| } |
| } |
| } |
| |
| /// Handle both poison-generating and UB-implying metadata on instructions. |
| void handleMetadata(Type *Ty, AnyValue &V, Instruction &I) { |
| auto ExtractFirstIntOperand = [](const MDNode *Node) { |
| return mdconst::extract<ConstantInt>(Node->getOperand(0))->getZExtValue(); |
| }; |
| |
| if (Ty->isIntOrIntVectorTy()) { |
| if (MDNode *Ranges = I.getMetadata(LLVMContext::MD_range)) { |
| SmallVector<ConstantRange> RangeList; |
| for (uint32_t I = 0; I < Ranges->getNumOperands(); I += 2) { |
| RangeList.emplace_back( |
| mdconst::extract<ConstantInt>(Ranges->getOperand(I))->getValue(), |
| mdconst::extract<ConstantInt>(Ranges->getOperand(I + 1)) |
| ->getValue()); |
| } |
| forEachScalarValue(V, [&](AnyValue &Scalar) { |
| if (!Scalar.isInteger()) |
| return; |
| for (auto &CR : RangeList) |
| if (CR.contains(Scalar.asInteger())) |
| return; |
| Scalar = AnyValue::poison(); |
| }); |
| } |
| } |
| if (AttributeFuncs::isNoFPClassCompatibleType(Ty)) { |
| if (const MDNode *NoFPClass = I.getMetadata(LLVMContext::MD_nofpclass)) { |
| applyNoFPClassAttr( |
| V, static_cast<FPClassTest>(ExtractFirstIntOperand(NoFPClass))); |
| } |
| } |
| if (Ty->isPointerTy()) { |
| if (I.hasMetadata(LLVMContext::MD_nonnull)) |
| applyNonNullAttr(V, Ty->getPointerAddressSpace(), DL); |
| // Unlike align attributes, !align is only defined for pointer types. |
| if (const MDNode *Alignment = I.getMetadata(LLVMContext::MD_align)) |
| applyAlignAttr(V, Align(ExtractFirstIntOperand(Alignment))); |
| } |
| if (I.hasMetadata(LLVMContext::MD_noundef) && violatesNoUndefAttr(V)) { |
| reportImmediateUB() << "The value " << V |
| << " violates !noundef metadata."; |
| return; |
| } |
| if (Ty->isPointerTy()) { |
| unsigned AS = Ty->getPointerAddressSpace(); |
| if (const MDNode *DereferenceableBytes = |
| I.getMetadata(LLVMContext::MD_dereferenceable)) { |
| uint64_t Bytes = ExtractFirstIntOperand(DereferenceableBytes); |
| if (violatesDereferenceableBytesAttr(V, Bytes, |
| /*OrNull=*/false, AS, Ctx)) |
| reportImmediateUB() |
| << "The value " << V << " violates !dereferenceable !{i64 " |
| << Bytes << "} metadata."; |
| } else if (const MDNode *DereferenceableOrNullBytes = |
| I.getMetadata(LLVMContext::MD_dereferenceable_or_null)) { |
| uint64_t Bytes = ExtractFirstIntOperand(DereferenceableOrNullBytes); |
| if (violatesDereferenceableBytesAttr(V, Bytes, |
| /*OrNull=*/true, AS, Ctx)) |
| reportImmediateUB() |
| << "The value " << V << " violates !dereferenceable_or_null!{i64 " |
| << Bytes << "} metadata."; |
| } |
| } |
| } |
| |
| void enterCall(CallBase &CB) { |
| Function *Callee = CB.getCalledFunction(); |
| // TODO: handle 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. Expected: " |
| << *CB.getFunctionType() |
| << ", Actual: " << *Callee->getFunctionType(); |
| return; |
| } |
| } |
| |
| assert(Callee && "Expected a resolved callee function."); |
| assert( |
| Callee->getFunctionType() == CB.getFunctionType() && |
| "Expected the callee function type to match the call site signature."); |
| |
| // Handle parameter attributes (Attributes from resolved callee should be |
| // applied if available). |
| for (auto [I, Arg] : enumerate(CB.args())) { |
| Type *ArgTy = Arg->getType(); |
| AnyValue &ArgVal = CalleeArgs[I]; |
| |
| // CallBase::paramHasAttr also checks parameter attributes at known |
| // callee. We do it explicitly to avoid duplication. |
| AttributeSet AttrsAtCallSite = CB.getParamAttributes(I); |
| AttributeSet AttrsAtCallee = Callee->getAttributes().getParamAttrs(I); |
| |
| if (ArgTy->isPointerTy()) { |
| auto *ByValTy = AttrsAtCallSite.getByValType(); |
| auto *ByValTyFromCallee = AttrsAtCallee.getByValType(); |
| if (ByValTy != ByValTyFromCallee) { |
| reportImmediateUB() |
| << "Mismatched byval attribute between callee and callsite."; |
| return; |
| } |
| if (ByValTy) { |
| if (ArgVal.isPoison()) { |
| reportImmediateUB() << "Invalid poison byval pointer argument."; |
| return; |
| } |
| |
| uint64_t Size = Ctx.getEffectiveTypeAllocSize(ByValTy); |
| MaybeAlign AllocAlign = AttrsAtCallSite.getAlignment(); |
| // Ignore the alignment at the callsite when it is set on the callee. |
| if (MaybeAlign CalleeAlign = AttrsAtCallee.getAlignment()) |
| AllocAlign = CalleeAlign; |
| if (!AllocAlign.has_value()) { |
| // If the alignment is not specified, we use the default ABI |
| // alignment. This is the default behavior of |
| // TargetLoweringBase::getByValTypeAlignment. |
| AllocAlign = DL.getABITypeAlign(ByValTy); |
| } |
| assert(I < Callee->arg_size() && |
| "Byval pointers cannot be passed via variadic arguments."); |
| auto Obj = Ctx.allocate( |
| Size, AllocAlign->value(), Callee->getArg(I)->getName(), |
| ArgTy->getPointerAddressSpace(), MemInitKind::Uninitialized, |
| MemAllocKind::Stack); |
| if (!Obj) { |
| reportError() |
| << "Insufficient stack space for byval pointer argument."; |
| return; |
| } |
| if (auto [MO, Offset] = verifyMemAccess( |
| ArgVal.asPointer(), Size, |
| std::max(AllocAlign.value(), |
| AttrsAtCallSite.getAlignment().valueOrOne()), |
| /*IsStore=*/false); |
| MO) |
| copy(MO->getBytes().slice(Offset, Size), Obj->getBytes().begin()); |
| else |
| return; |
| CurrentFrame->CalleeByValArgs.push_back(Obj); |
| ArgVal = Ctx.deriveFromMemoryObject(std::move(Obj)); |
| } |
| } |
| handleAttributes(ArgTy, ArgVal, AttrsAtCallSite, AttrsAtCallee); |
| } |
| |
| 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. LHS: " << LHSVal |
| << ", RHS: " << RHSVal; |
| 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 visitFAdd(BinaryOperator &I) { |
| visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat { |
| APFloat Res = LHS; |
| Res.add(RHS, APFloat::rmNearestTiesToEven); |
| return Res; |
| }); |
| } |
| |
| void visitFSub(BinaryOperator &I) { |
| visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat { |
| APFloat Res = LHS; |
| Res.subtract(RHS, APFloat::rmNearestTiesToEven); |
| return Res; |
| }); |
| } |
| |
| void visitFMul(BinaryOperator &I) { |
| visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat { |
| APFloat Res = LHS; |
| Res.multiply(RHS, APFloat::rmNearestTiesToEven); |
| return Res; |
| }); |
| } |
| |
| void visitFDiv(BinaryOperator &I) { |
| visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat { |
| APFloat Res = LHS; |
| Res.divide(RHS, APFloat::rmNearestTiesToEven); |
| return Res; |
| }); |
| } |
| |
| void visitFRem(BinaryOperator &I) { |
| visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat { |
| APFloat Res = LHS; |
| Res.mod(RHS); |
| return Res; |
| }); |
| } |
| |
| void visitFNeg(UnaryOperator &I) { |
| visitBitwiseFPUnOp( |
| I, [](const APFloat &Operand) -> APFloat { return -Operand; }); |
| } |
| |
| 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 visitFPExtInst(FPExtInst &FPExt) { visitFPConvInst(FPExt); } |
| |
| void visitFPTruncInst(FPTruncInst &FPTrunc) { visitFPConvInst(FPTrunc); } |
| |
| void visitFPConvInst(Instruction &I) { |
| if (!Ctx.isDefaultFPEnv()) |
| reportImmediateUB() << "Non-constrained floating-point operation assumes " |
| "default floating-point environment"; |
| |
| const fltSemantics &DstSem = |
| I.getType()->getScalarType()->getFltSemantics(); |
| |
| visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue { |
| if (Operand.isPoison()) |
| return AnyValue::poison(); |
| |
| FastMathFlags FMF = cast<FPMathOperator>(I).getFastMathFlags(); |
| DenormalMode DenormMode = |
| getCurrentDenormalMode(I.getOperand(0)->getType()); |
| |
| auto ValidatedOperand = handleFMFFlags(Operand, FMF, /*IsInput=*/true); |
| if (ValidatedOperand.isPoison()) |
| return ValidatedOperand; |
| |
| APFloat FOperand = handleDenormal(ValidatedOperand.asFloat(), |
| DenormMode.Input, /*IsInput=*/true); |
| APFloat SourceNaN = FOperand; |
| |
| bool LosesInfo; |
| FOperand.convert(DstSem, Ctx.getCurrentRoundingMode(), &LosesInfo); |
| |
| if (auto ValidateRes = handleFMFFlags(FOperand, FMF, /*IsInput=*/false); |
| ValidateRes.isPoison()) |
| return ValidateRes; |
| |
| FOperand = handleDenormal(std::move(FOperand), DenormMode.Output, true); |
| |
| return AnyValue(applyNaNPropagation(FOperand, {&SourceNaN})); |
| }); |
| } |
| |
| void visitFPToSIInst(FPToSIInst &FPToSI) { |
| visitFPToIntInst(FPToSI, /*IsUnsigned=*/false); |
| } |
| |
| void visitFPToUIInst(FPToUIInst &FPToUI) { |
| visitFPToIntInst(FPToUI, /*IsUnsigned=*/true); |
| } |
| |
| void visitFPToIntInst(Instruction &I, bool IsUnsigned) { |
| // Note: We DO NOT use CurrentRoundingMode here. |
| // Language specs require truncation towards zero for FP-to-Int conversions. |
| visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue { |
| if (Operand.isPoison()) |
| return AnyValue::poison(); |
| |
| APSInt Res(I.getType()->getScalarSizeInBits(), /*isUnsigned=*/IsUnsigned); |
| bool IsExact; |
| APFloat::opStatus Status = Operand.asFloat().convertToInteger( |
| Res, APFloat::rmTowardZero, &IsExact); |
| |
| if (Status == APFloat::opInvalidOp) |
| return AnyValue::poison(); |
| |
| return AnyValue(Res); |
| }); |
| } |
| |
| void visitSIToFPInst(SIToFPInst &SIToFP) { |
| visitIntToFPInst(SIToFP, /*IsSigned=*/true); |
| } |
| |
| void visitUIToFPInst(UIToFPInst &UIToFP) { |
| visitIntToFPInst(UIToFP, /*IsSigned=*/false); |
| } |
| |
| void visitIntToFPInst(Instruction &I, bool IsSigned) { |
| const fltSemantics &DstSem = |
| I.getType()->getScalarType()->getFltSemantics(); |
| |
| visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue { |
| if (Operand.isPoison()) |
| return AnyValue::poison(); |
| |
| APInt IOperand = Operand.asInteger(); |
| |
| if (isa<UIToFPInst>(I) && I.hasNonNeg() && IOperand.isNegative()) |
| return AnyValue::poison(); |
| |
| APFloat Res(DstSem); |
| |
| Res.convertFromAPInt(Operand.asInteger(), /*IsSigned=*/IsSigned, |
| Ctx.getCurrentRoundingMode()); |
| |
| return AnyValue(Res); |
| }); |
| } |
| |
| 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(LHS.getBitWidth()) || |
| (cast<PossiblyExactOperator>(I).isExact() && |
| RHS.ugt(LHS.countr_zero()))) |
| return AnyValue::poison(); |
| return LHS.lshr(RHS); |
| }); |
| } |
| |
| void visitAShr(BinaryOperator &I) { |
| visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue { |
| if (RHS.uge(LHS.getBitWidth()) || |
| (cast<PossiblyExactOperator>(I).isExact() && |
| RHS.ugt(LHS.countr_zero()))) |
| 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(); |
| const APInt &LHSVal = |
| LHS.isPointer() ? LHS.asPointer().address() : LHS.asInteger(); |
| const APInt &RHSVal = |
| RHS.isPointer() ? RHS.asPointer().address() : RHS.asInteger(); |
| if (I.hasSameSign() && LHSVal.isNonNegative() != RHSVal.isNonNegative()) |
| return AnyValue::poison(); |
| return AnyValue::boolean( |
| ICmpInst::compare(LHSVal, RHSVal, I.getPredicate())); |
| }); |
| } |
| |
| void visitFCmpInst(FCmpInst &I) { |
| DenormalMode DenormMode = |
| getCurrentDenormalMode(I.getOperand(0)->getType()); |
| FastMathFlags FMF = I.getFastMathFlags(); |
| |
| visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue { |
| if (LHS.isPoison() || RHS.isPoison()) |
| return AnyValue::poison(); |
| |
| if (auto ValidateRes = handleFMFFlags(LHS, FMF, /*IsInput=*/true); |
| ValidateRes.isPoison()) |
| return ValidateRes; |
| if (auto ValidateRes = handleFMFFlags(RHS, FMF, /*IsInput=*/true); |
| ValidateRes.isPoison()) |
| return ValidateRes; |
| |
| APFloat FLHS = |
| handleDenormal(LHS.asFloat(), DenormMode.Input, /*IsInput=*/true); |
| APFloat FRHS = |
| handleDenormal(RHS.asFloat(), DenormMode.Input, /*IsInput=*/true); |
| |
| return AnyValue::boolean(FCmpInst::compare(FLHS, FRHS, I.getPredicate())); |
| }); |
| } |
| |
| void visitSelect(SelectInst &SI) { |
| AnyValue Res; |
| |
| if (SI.getCondition()->getType()->isIntegerTy(1)) { |
| switch (getValue(SI.getCondition()).asBoolean()) { |
| case BooleanKind::True: |
| Res = getValue(SI.getTrueValue()); |
| break; |
| case BooleanKind::False: |
| Res = getValue(SI.getFalseValue()); |
| break; |
| case BooleanKind::Poison: |
| Res = AnyValue::getPoisonValue(Ctx, SI.getType()); |
| break; |
| } |
| } else { |
| auto &Cond = getValue(SI.getCondition()).asAggregate(); |
| auto &TV = getValue(SI.getTrueValue()).asAggregate(); |
| auto &FV = getValue(SI.getFalseValue()).asAggregate(); |
| std::vector<AnyValue> ResVec; |
| size_t Len = Cond.size(); |
| ResVec.reserve(Len); |
| for (uint32_t I = 0; I != Len; ++I) { |
| switch (Cond[I].asBoolean()) { |
| case BooleanKind::True: |
| ResVec.push_back(TV[I]); |
| break; |
| case BooleanKind::False: |
| ResVec.push_back(FV[I]); |
| break; |
| case BooleanKind::Poison: |
| ResVec.push_back( |
| AnyValue::getPoisonValue(Ctx, SI.getType()->getScalarType())); |
| break; |
| } |
| } |
| Res = AnyValue(std::move(ResVec)); |
| } |
| |
| // Handle fast-math flags |
| if (auto *FPMO = dyn_cast<FPMathOperator>(&SI)) { |
| if (FastMathFlags FMF = FPMO->getFastMathFlags(); FMF.any()) |
| Res = handleFMFFlags(std::move(Res), FMF, /*IsInput=*/true); |
| } |
| |
| 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. Size: " |
| << Size.asInteger(); |
| return; |
| } |
| bool Overflowed = false; |
| AllocSize = SaturatingMultiply(AllocSize, Size.asInteger().getZExtValue(), |
| &Overflowed); |
| if (Overflowed) { |
| reportImmediateUB() |
| << "Alloca with allocation size that overflows uint64_t. Size: " |
| << Size.asInteger(); |
| 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) { |
| setResult(GEP, Ctx.computeGEP(cast<GEPOperator>(GEP), |
| [this](Value *V) -> const AnyValue & { |
| return getValue(V); |
| })); |
| } |
| |
| void visitPtrToInt(PtrToIntInst &I) { |
| unsigned BitWidth = I.getType()->getScalarSizeInBits(); |
| return visitUnOp(I, [this, BitWidth](const AnyValue &V) -> AnyValue { |
| if (V.isPoison()) |
| return AnyValue::poison(); |
| Ctx.exposeProvenance(V.asPointer().provenance()); |
| return V.asPointer().address().zextOrTrunc(BitWidth); |
| }); |
| } |
| |
| void visitIntToPtr(IntToPtrInst &I) { |
| return visitUnOp(I, [&](const AnyValue &V) -> AnyValue { |
| if (V.isPoison()) |
| return AnyValue::poison(); |
| auto Prov = Ctx.getWildcardProvenance(); |
| // TODO: check metadata |
| return Pointer(std::move(Prov), |
| V.asInteger().zextOrTrunc(DL.getPointerSizeInBits( |
| I.getType()->getPointerAddressSpace()))); |
| }); |
| } |
| |
| void visitPtrToAddr(PtrToAddrInst &I) { |
| unsigned BitWidth = I.getType()->getScalarSizeInBits(); |
| return visitUnOp(I, [&](const AnyValue &V) -> AnyValue { |
| if (V.isPoison()) |
| return AnyValue::poison(); |
| return V.asPointer().address().trunc(BitWidth); |
| }); |
| } |
| |
| void visitLoadInst(LoadInst &LI) { |
| auto RetVal = load(getValue(LI.getPointerOperand()), LI.getAlign(), |
| LI.getType(), LI.hasMetadata(LLVMContext::MD_noundef)); |
| // TODO: track volatile loads |
| handleMetadata(LI.getType(), RetVal, LI); |
| 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::getPoisonValue(Ctx, SVI.getType()->getScalarType())); |
| 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); |
| Ctx.resetNoncacheableConstantBuffer(); |
| 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 |