2250 lines
91 KiB
C++
2250 lines
91 KiB
C++
//===-- NumericalStabilitySanitizer.cpp -----------------------------------===//
|
|
//
|
|
// 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 contains the instrumentation pass for the numerical sanitizer.
|
|
// Conceptually the pass injects shadow computations using higher precision
|
|
// types and inserts consistency checks. For details see the paper
|
|
// https://arxiv.org/abs/2102.12782.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/Transforms/Instrumentation/NumericalStabilitySanitizer.h"
|
|
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Analysis/TargetLibraryInfo.h"
|
|
#include "llvm/Analysis/ValueTracking.h"
|
|
#include "llvm/IR/DataLayout.h"
|
|
#include "llvm/IR/Function.h"
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
#include "llvm/IR/Intrinsics.h"
|
|
#include "llvm/IR/LLVMContext.h"
|
|
#include "llvm/IR/MDBuilder.h"
|
|
#include "llvm/IR/Metadata.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/IR/Type.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/Regex.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
|
|
#include "llvm/Transforms/Utils/Instrumentation.h"
|
|
#include "llvm/Transforms/Utils/Local.h"
|
|
#include "llvm/Transforms/Utils/ModuleUtils.h"
|
|
|
|
#include <cstdint>
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "nsan"
|
|
|
|
STATISTIC(NumInstrumentedFTLoads,
|
|
"Number of instrumented floating-point loads");
|
|
|
|
STATISTIC(NumInstrumentedFTCalls,
|
|
"Number of instrumented floating-point calls");
|
|
STATISTIC(NumInstrumentedFTRets,
|
|
"Number of instrumented floating-point returns");
|
|
STATISTIC(NumInstrumentedFTStores,
|
|
"Number of instrumented floating-point stores");
|
|
STATISTIC(NumInstrumentedNonFTStores,
|
|
"Number of instrumented non floating-point stores");
|
|
STATISTIC(
|
|
NumInstrumentedNonFTMemcpyStores,
|
|
"Number of instrumented non floating-point stores with memcpy semantics");
|
|
STATISTIC(NumInstrumentedFCmp, "Number of instrumented fcmps");
|
|
|
|
// Using smaller shadow types types can help improve speed. For example, `dlq`
|
|
// is 3x slower to 5x faster in opt mode and 2-6x faster in dbg mode compared to
|
|
// `dqq`.
|
|
static cl::opt<std::string> ClShadowMapping(
|
|
"nsan-shadow-type-mapping", cl::init("dqq"),
|
|
cl::desc("One shadow type id for each of `float`, `double`, `long double`. "
|
|
"`d`,`l`,`q`,`e` mean double, x86_fp80, fp128 (quad) and "
|
|
"ppc_fp128 (extended double) respectively. The default is to "
|
|
"shadow `float` as `double`, and `double` and `x86_fp80` as "
|
|
"`fp128`"),
|
|
cl::Hidden);
|
|
|
|
static cl::opt<bool>
|
|
ClInstrumentFCmp("nsan-instrument-fcmp", cl::init(true),
|
|
cl::desc("Instrument floating-point comparisons"),
|
|
cl::Hidden);
|
|
|
|
static cl::opt<std::string> ClCheckFunctionsFilter(
|
|
"check-functions-filter",
|
|
cl::desc("Only emit checks for arguments of functions "
|
|
"whose names match the given regular expression"),
|
|
cl::value_desc("regex"));
|
|
|
|
static cl::opt<bool> ClTruncateFCmpEq(
|
|
"nsan-truncate-fcmp-eq", cl::init(true),
|
|
cl::desc(
|
|
"This flag controls the behaviour of fcmp equality comparisons."
|
|
"For equality comparisons such as `x == 0.0f`, we can perform the "
|
|
"shadow check in the shadow (`x_shadow == 0.0) == (x == 0.0f)`) or app "
|
|
" domain (`(trunc(x_shadow) == 0.0f) == (x == 0.0f)`). This helps "
|
|
"catch the case when `x_shadow` is accurate enough (and therefore "
|
|
"close enough to zero) so that `trunc(x_shadow)` is zero even though "
|
|
"both `x` and `x_shadow` are not"),
|
|
cl::Hidden);
|
|
|
|
// When there is external, uninstrumented code writing to memory, the shadow
|
|
// memory can get out of sync with the application memory. Enabling this flag
|
|
// emits consistency checks for loads to catch this situation.
|
|
// When everything is instrumented, this is not strictly necessary because any
|
|
// load should have a corresponding store, but can help debug cases when the
|
|
// framework did a bad job at tracking shadow memory modifications by failing on
|
|
// load rather than store.
|
|
// TODO: provide a way to resume computations from the FT value when the load
|
|
// is inconsistent. This ensures that further computations are not polluted.
|
|
static cl::opt<bool> ClCheckLoads("nsan-check-loads",
|
|
cl::desc("Check floating-point load"),
|
|
cl::Hidden);
|
|
|
|
static cl::opt<bool> ClCheckStores("nsan-check-stores", cl::init(true),
|
|
cl::desc("Check floating-point stores"),
|
|
cl::Hidden);
|
|
|
|
static cl::opt<bool> ClCheckRet("nsan-check-ret", cl::init(true),
|
|
cl::desc("Check floating-point return values"),
|
|
cl::Hidden);
|
|
|
|
// LLVM may store constant floats as bitcasted ints.
|
|
// It's not really necessary to shadow such stores,
|
|
// if the shadow value is unknown the framework will re-extend it on load
|
|
// anyway. Moreover, because of size collisions (e.g. bf16 vs f16) it is
|
|
// impossible to determine the floating-point type based on the size.
|
|
// However, for debugging purposes it can be useful to model such stores.
|
|
static cl::opt<bool> ClPropagateNonFTConstStoresAsFT(
|
|
"nsan-propagate-non-ft-const-stores-as-ft",
|
|
cl::desc(
|
|
"Propagate non floating-point const stores as floating point values."
|
|
"For debugging purposes only"),
|
|
cl::Hidden);
|
|
|
|
constexpr StringLiteral kNsanModuleCtorName("nsan.module_ctor");
|
|
constexpr StringLiteral kNsanInitName("__nsan_init");
|
|
|
|
// The following values must be kept in sync with the runtime.
|
|
constexpr int kShadowScale = 2;
|
|
constexpr int kMaxVectorWidth = 8;
|
|
constexpr int kMaxNumArgs = 128;
|
|
constexpr int kMaxShadowTypeSizeBytes = 16; // fp128
|
|
|
|
namespace {
|
|
|
|
// Defines the characteristics (type id, type, and floating-point semantics)
|
|
// attached for all possible shadow types.
|
|
class ShadowTypeConfig {
|
|
public:
|
|
static std::unique_ptr<ShadowTypeConfig> fromNsanTypeId(char TypeId);
|
|
|
|
// The LLVM Type corresponding to the shadow type.
|
|
virtual Type *getType(LLVMContext &Context) const = 0;
|
|
|
|
// The nsan type id of the shadow type (`d`, `l`, `q`, ...).
|
|
virtual char getNsanTypeId() const = 0;
|
|
|
|
virtual ~ShadowTypeConfig() = default;
|
|
};
|
|
|
|
template <char NsanTypeId>
|
|
class ShadowTypeConfigImpl : public ShadowTypeConfig {
|
|
public:
|
|
char getNsanTypeId() const override { return NsanTypeId; }
|
|
static constexpr const char kNsanTypeId = NsanTypeId;
|
|
};
|
|
|
|
// `double` (`d`) shadow type.
|
|
class F64ShadowConfig : public ShadowTypeConfigImpl<'d'> {
|
|
Type *getType(LLVMContext &Context) const override {
|
|
return Type::getDoubleTy(Context);
|
|
}
|
|
};
|
|
|
|
// `x86_fp80` (`l`) shadow type: X86 long double.
|
|
class F80ShadowConfig : public ShadowTypeConfigImpl<'l'> {
|
|
Type *getType(LLVMContext &Context) const override {
|
|
return Type::getX86_FP80Ty(Context);
|
|
}
|
|
};
|
|
|
|
// `fp128` (`q`) shadow type.
|
|
class F128ShadowConfig : public ShadowTypeConfigImpl<'q'> {
|
|
Type *getType(LLVMContext &Context) const override {
|
|
return Type::getFP128Ty(Context);
|
|
}
|
|
};
|
|
|
|
// `ppc_fp128` (`e`) shadow type: IBM extended double with 106 bits of mantissa.
|
|
class PPC128ShadowConfig : public ShadowTypeConfigImpl<'e'> {
|
|
Type *getType(LLVMContext &Context) const override {
|
|
return Type::getPPC_FP128Ty(Context);
|
|
}
|
|
};
|
|
|
|
// Creates a ShadowTypeConfig given its type id.
|
|
std::unique_ptr<ShadowTypeConfig>
|
|
ShadowTypeConfig::fromNsanTypeId(const char TypeId) {
|
|
switch (TypeId) {
|
|
case F64ShadowConfig::kNsanTypeId:
|
|
return std::make_unique<F64ShadowConfig>();
|
|
case F80ShadowConfig::kNsanTypeId:
|
|
return std::make_unique<F80ShadowConfig>();
|
|
case F128ShadowConfig::kNsanTypeId:
|
|
return std::make_unique<F128ShadowConfig>();
|
|
case PPC128ShadowConfig::kNsanTypeId:
|
|
return std::make_unique<PPC128ShadowConfig>();
|
|
}
|
|
report_fatal_error("nsan: invalid shadow type id '" + Twine(TypeId) + "'");
|
|
}
|
|
|
|
// An enum corresponding to shadow value types. Used as indices in arrays, so
|
|
// not an `enum class`.
|
|
enum FTValueType { kFloat, kDouble, kLongDouble, kNumValueTypes };
|
|
|
|
// If `FT` corresponds to a primitive FTValueType, return it.
|
|
static std::optional<FTValueType> ftValueTypeFromType(Type *FT) {
|
|
if (FT->isFloatTy())
|
|
return kFloat;
|
|
if (FT->isDoubleTy())
|
|
return kDouble;
|
|
if (FT->isX86_FP80Ty())
|
|
return kLongDouble;
|
|
return {};
|
|
}
|
|
|
|
// Returns the LLVM type for an FTValueType.
|
|
static Type *typeFromFTValueType(FTValueType VT, LLVMContext &Context) {
|
|
switch (VT) {
|
|
case kFloat:
|
|
return Type::getFloatTy(Context);
|
|
case kDouble:
|
|
return Type::getDoubleTy(Context);
|
|
case kLongDouble:
|
|
return Type::getX86_FP80Ty(Context);
|
|
case kNumValueTypes:
|
|
return nullptr;
|
|
}
|
|
llvm_unreachable("Unhandled FTValueType enum");
|
|
}
|
|
|
|
// Returns the type name for an FTValueType.
|
|
static const char *typeNameFromFTValueType(FTValueType VT) {
|
|
switch (VT) {
|
|
case kFloat:
|
|
return "float";
|
|
case kDouble:
|
|
return "double";
|
|
case kLongDouble:
|
|
return "longdouble";
|
|
case kNumValueTypes:
|
|
return nullptr;
|
|
}
|
|
llvm_unreachable("Unhandled FTValueType enum");
|
|
}
|
|
|
|
// A specific mapping configuration of application type to shadow type for nsan
|
|
// (see -nsan-shadow-mapping flag).
|
|
class MappingConfig {
|
|
public:
|
|
explicit MappingConfig(LLVMContext &C) : Context(C) {
|
|
if (ClShadowMapping.size() != 3)
|
|
report_fatal_error("Invalid nsan mapping: " + Twine(ClShadowMapping));
|
|
unsigned ShadowTypeSizeBits[kNumValueTypes];
|
|
for (int VT = 0; VT < kNumValueTypes; ++VT) {
|
|
auto Config = ShadowTypeConfig::fromNsanTypeId(ClShadowMapping[VT]);
|
|
if (!Config)
|
|
report_fatal_error("Failed to get ShadowTypeConfig for " +
|
|
Twine(ClShadowMapping[VT]));
|
|
const unsigned AppTypeSize =
|
|
typeFromFTValueType(static_cast<FTValueType>(VT), Context)
|
|
->getScalarSizeInBits();
|
|
const unsigned ShadowTypeSize =
|
|
Config->getType(Context)->getScalarSizeInBits();
|
|
// Check that the shadow type size is at most kShadowScale times the
|
|
// application type size, so that shadow memory compoutations are valid.
|
|
if (ShadowTypeSize > kShadowScale * AppTypeSize)
|
|
report_fatal_error("Invalid nsan mapping f" + Twine(AppTypeSize) +
|
|
"->f" + Twine(ShadowTypeSize) +
|
|
": The shadow type size should be at most " +
|
|
Twine(kShadowScale) +
|
|
" times the application type size");
|
|
ShadowTypeSizeBits[VT] = ShadowTypeSize;
|
|
Configs[VT] = std::move(Config);
|
|
}
|
|
|
|
// Check that the mapping is monotonous. This is required because if one
|
|
// does an fpextend of `float->long double` in application code, nsan is
|
|
// going to do an fpextend of `shadow(float) -> shadow(long double)` in
|
|
// shadow code. This will fail in `qql` mode, since nsan would be
|
|
// fpextending `f128->long`, which is invalid.
|
|
// TODO: Relax this.
|
|
if (ShadowTypeSizeBits[kFloat] > ShadowTypeSizeBits[kDouble] ||
|
|
ShadowTypeSizeBits[kDouble] > ShadowTypeSizeBits[kLongDouble])
|
|
report_fatal_error("Invalid nsan mapping: { float->f" +
|
|
Twine(ShadowTypeSizeBits[kFloat]) + "; double->f" +
|
|
Twine(ShadowTypeSizeBits[kDouble]) +
|
|
"; long double->f" +
|
|
Twine(ShadowTypeSizeBits[kLongDouble]) + " }");
|
|
}
|
|
|
|
const ShadowTypeConfig &byValueType(FTValueType VT) const {
|
|
assert(VT < FTValueType::kNumValueTypes && "invalid value type");
|
|
return *Configs[VT];
|
|
}
|
|
|
|
// Returns the extended shadow type for a given application type.
|
|
Type *getExtendedFPType(Type *FT) const {
|
|
if (const auto VT = ftValueTypeFromType(FT))
|
|
return Configs[*VT]->getType(Context);
|
|
if (FT->isVectorTy()) {
|
|
auto *VecTy = cast<VectorType>(FT);
|
|
// TODO: add support for scalable vector types.
|
|
if (VecTy->isScalableTy())
|
|
return nullptr;
|
|
Type *ExtendedScalar = getExtendedFPType(VecTy->getElementType());
|
|
return ExtendedScalar
|
|
? VectorType::get(ExtendedScalar, VecTy->getElementCount())
|
|
: nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
private:
|
|
LLVMContext &Context;
|
|
std::unique_ptr<ShadowTypeConfig> Configs[FTValueType::kNumValueTypes];
|
|
};
|
|
|
|
// The memory extents of a type specifies how many elements of a given
|
|
// FTValueType needs to be stored when storing this type.
|
|
struct MemoryExtents {
|
|
FTValueType ValueType;
|
|
uint64_t NumElts;
|
|
};
|
|
|
|
static MemoryExtents getMemoryExtentsOrDie(Type *FT) {
|
|
if (const auto VT = ftValueTypeFromType(FT))
|
|
return {*VT, 1};
|
|
if (auto *VecTy = dyn_cast<VectorType>(FT)) {
|
|
const auto ScalarExtents = getMemoryExtentsOrDie(VecTy->getElementType());
|
|
return {ScalarExtents.ValueType,
|
|
ScalarExtents.NumElts * VecTy->getElementCount().getFixedValue()};
|
|
}
|
|
llvm_unreachable("invalid value type");
|
|
}
|
|
|
|
// The location of a check. Passed as parameters to runtime checking functions.
|
|
class CheckLoc {
|
|
public:
|
|
// Creates a location that references an application memory location.
|
|
static CheckLoc makeStore(Value *Address) {
|
|
CheckLoc Result(kStore);
|
|
Result.Address = Address;
|
|
return Result;
|
|
}
|
|
static CheckLoc makeLoad(Value *Address) {
|
|
CheckLoc Result(kLoad);
|
|
Result.Address = Address;
|
|
return Result;
|
|
}
|
|
|
|
// Creates a location that references an argument, given by id.
|
|
static CheckLoc makeArg(int ArgId) {
|
|
CheckLoc Result(kArg);
|
|
Result.ArgId = ArgId;
|
|
return Result;
|
|
}
|
|
|
|
// Creates a location that references the return value of a function.
|
|
static CheckLoc makeRet() { return CheckLoc(kRet); }
|
|
|
|
// Creates a location that references a vector insert.
|
|
static CheckLoc makeInsert() { return CheckLoc(kInsert); }
|
|
|
|
// Returns the CheckType of location this refers to, as an integer-typed LLVM
|
|
// IR value.
|
|
Value *getType(LLVMContext &C) const {
|
|
return ConstantInt::get(Type::getInt32Ty(C), static_cast<int>(CheckTy));
|
|
}
|
|
|
|
// Returns a CheckType-specific value representing details of the location
|
|
// (e.g. application address for loads or stores), as an `IntptrTy`-typed LLVM
|
|
// IR value.
|
|
Value *getValue(Type *IntptrTy, IRBuilder<> &Builder) const {
|
|
switch (CheckTy) {
|
|
case kUnknown:
|
|
llvm_unreachable("unknown type");
|
|
case kRet:
|
|
case kInsert:
|
|
return ConstantInt::get(IntptrTy, 0);
|
|
case kArg:
|
|
return ConstantInt::get(IntptrTy, ArgId);
|
|
case kLoad:
|
|
case kStore:
|
|
return Builder.CreatePtrToInt(Address, IntptrTy);
|
|
}
|
|
llvm_unreachable("Unhandled CheckType enum");
|
|
}
|
|
|
|
private:
|
|
// Must be kept in sync with the runtime,
|
|
// see compiler-rt/lib/nsan/nsan_stats.h
|
|
enum CheckType {
|
|
kUnknown = 0,
|
|
kRet,
|
|
kArg,
|
|
kLoad,
|
|
kStore,
|
|
kInsert,
|
|
};
|
|
explicit CheckLoc(CheckType CheckTy) : CheckTy(CheckTy) {}
|
|
|
|
Value *Address = nullptr;
|
|
const CheckType CheckTy;
|
|
int ArgId = -1;
|
|
};
|
|
|
|
// A map of LLVM IR values to shadow LLVM IR values.
|
|
class ValueToShadowMap {
|
|
public:
|
|
explicit ValueToShadowMap(const MappingConfig &Config) : Config(Config) {}
|
|
|
|
ValueToShadowMap(const ValueToShadowMap &) = delete;
|
|
ValueToShadowMap &operator=(const ValueToShadowMap &) = delete;
|
|
|
|
// Sets the shadow value for a value. Asserts that the value does not already
|
|
// have a value.
|
|
void setShadow(Value &V, Value &Shadow) {
|
|
[[maybe_unused]] const bool Inserted = Map.try_emplace(&V, &Shadow).second;
|
|
LLVM_DEBUG({
|
|
if (!Inserted) {
|
|
if (auto *I = dyn_cast<Instruction>(&V))
|
|
errs() << I->getFunction()->getName() << ": ";
|
|
errs() << "duplicate shadow (" << &V << "): ";
|
|
V.dump();
|
|
}
|
|
});
|
|
assert(Inserted && "duplicate shadow");
|
|
}
|
|
|
|
// Returns true if the value already has a shadow (including if the value is a
|
|
// constant). If true, calling getShadow() is valid.
|
|
bool hasShadow(Value *V) const { return isa<Constant>(V) || Map.contains(V); }
|
|
|
|
// Returns the shadow value for a given value. Asserts that the value has
|
|
// a shadow value. Lazily creates shadows for constant values.
|
|
Value *getShadow(Value *V) const {
|
|
if (Constant *C = dyn_cast<Constant>(V))
|
|
return getShadowConstant(C);
|
|
return Map.find(V)->second;
|
|
}
|
|
|
|
bool empty() const { return Map.empty(); }
|
|
|
|
private:
|
|
// Extends a constant application value to its shadow counterpart.
|
|
APFloat extendConstantFP(APFloat CV, const fltSemantics &To) const {
|
|
bool LosesInfo = false;
|
|
CV.convert(To, APFloatBase::rmTowardZero, &LosesInfo);
|
|
return CV;
|
|
}
|
|
|
|
// Returns the shadow constant for the given application constant.
|
|
Constant *getShadowConstant(Constant *C) const {
|
|
if (UndefValue *U = dyn_cast<UndefValue>(C)) {
|
|
return UndefValue::get(Config.getExtendedFPType(U->getType()));
|
|
}
|
|
if (ConstantFP *CFP = dyn_cast<ConstantFP>(C)) {
|
|
// Floating-point constants.
|
|
Type *Ty = Config.getExtendedFPType(CFP->getType());
|
|
return ConstantFP::get(
|
|
Ty, extendConstantFP(CFP->getValueAPF(),
|
|
Ty->getScalarType()->getFltSemantics()));
|
|
}
|
|
// Vector, array, or aggregate constants.
|
|
if (C->getType()->isVectorTy()) {
|
|
SmallVector<Constant *, 8> Elements;
|
|
for (int I = 0, E = cast<VectorType>(C->getType())
|
|
->getElementCount()
|
|
.getFixedValue();
|
|
I < E; ++I)
|
|
Elements.push_back(getShadowConstant(C->getAggregateElement(I)));
|
|
return ConstantVector::get(Elements);
|
|
}
|
|
llvm_unreachable("unimplemented");
|
|
}
|
|
|
|
const MappingConfig &Config;
|
|
DenseMap<Value *, Value *> Map;
|
|
};
|
|
|
|
class NsanMemOpFn {
|
|
public:
|
|
NsanMemOpFn(Module &M, ArrayRef<StringRef> Sized, StringRef Fallback,
|
|
size_t NumArgs);
|
|
FunctionCallee getFunctionFor(uint64_t MemOpSize) const;
|
|
FunctionCallee getFallback() const;
|
|
|
|
private:
|
|
SmallVector<FunctionCallee> Funcs;
|
|
size_t NumSizedFuncs;
|
|
};
|
|
|
|
NsanMemOpFn::NsanMemOpFn(Module &M, ArrayRef<StringRef> Sized,
|
|
StringRef Fallback, size_t NumArgs) {
|
|
LLVMContext &Ctx = M.getContext();
|
|
AttributeList Attr;
|
|
Attr = Attr.addFnAttribute(Ctx, Attribute::NoUnwind);
|
|
Type *PtrTy = PointerType::getUnqual(Ctx);
|
|
Type *VoidTy = Type::getVoidTy(Ctx);
|
|
IntegerType *IntptrTy = M.getDataLayout().getIntPtrType(Ctx);
|
|
FunctionType *SizedFnTy = nullptr;
|
|
|
|
NumSizedFuncs = Sized.size();
|
|
|
|
// First entry is fallback function
|
|
if (NumArgs == 3) {
|
|
Funcs.push_back(
|
|
M.getOrInsertFunction(Fallback, Attr, VoidTy, PtrTy, PtrTy, IntptrTy));
|
|
SizedFnTy = FunctionType::get(VoidTy, {PtrTy, PtrTy}, false);
|
|
} else if (NumArgs == 2) {
|
|
Funcs.push_back(
|
|
M.getOrInsertFunction(Fallback, Attr, VoidTy, PtrTy, IntptrTy));
|
|
SizedFnTy = FunctionType::get(VoidTy, {PtrTy}, false);
|
|
} else {
|
|
llvm_unreachable("Unexpected value of sized functions arguments");
|
|
}
|
|
|
|
for (size_t i = 0; i < NumSizedFuncs; ++i)
|
|
Funcs.push_back(M.getOrInsertFunction(Sized[i], SizedFnTy, Attr));
|
|
}
|
|
|
|
FunctionCallee NsanMemOpFn::getFunctionFor(uint64_t MemOpSize) const {
|
|
// Now `getFunctionFor` operates on `Funcs` of size 4 (at least) and the
|
|
// following code assumes that the number of functions in `Func` is sufficient
|
|
assert(NumSizedFuncs >= 3 && "Unexpected number of sized functions");
|
|
|
|
size_t Idx =
|
|
MemOpSize == 4 ? 1 : (MemOpSize == 8 ? 2 : (MemOpSize == 16 ? 3 : 0));
|
|
|
|
return Funcs[Idx];
|
|
}
|
|
|
|
FunctionCallee NsanMemOpFn::getFallback() const { return Funcs[0]; }
|
|
|
|
/// Instantiating NumericalStabilitySanitizer inserts the nsan runtime library
|
|
/// API function declarations into the module if they don't exist already.
|
|
/// Instantiating ensures the __nsan_init function is in the list of global
|
|
/// constructors for the module.
|
|
class NumericalStabilitySanitizer {
|
|
public:
|
|
NumericalStabilitySanitizer(Module &M);
|
|
bool sanitizeFunction(Function &F, const TargetLibraryInfo &TLI);
|
|
|
|
private:
|
|
bool instrumentMemIntrinsic(MemIntrinsic *MI);
|
|
void maybeAddSuffixForNsanInterface(CallBase *CI);
|
|
bool addrPointsToConstantData(Value *Addr);
|
|
void maybeCreateShadowValue(Instruction &Root, const TargetLibraryInfo &TLI,
|
|
ValueToShadowMap &Map);
|
|
Value *createShadowValueWithOperandsAvailable(Instruction &Inst,
|
|
const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map);
|
|
PHINode *maybeCreateShadowPhi(PHINode &Phi, const TargetLibraryInfo &TLI);
|
|
void createShadowArguments(Function &F, const TargetLibraryInfo &TLI,
|
|
ValueToShadowMap &Map);
|
|
|
|
void populateShadowStack(CallBase &CI, const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map);
|
|
|
|
void propagateShadowValues(Instruction &Inst, const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map);
|
|
Value *emitCheck(Value *V, Value *ShadowV, IRBuilder<> &Builder,
|
|
CheckLoc Loc);
|
|
Value *emitCheckInternal(Value *V, Value *ShadowV, IRBuilder<> &Builder,
|
|
CheckLoc Loc);
|
|
void emitFCmpCheck(FCmpInst &FCmp, const ValueToShadowMap &Map);
|
|
|
|
// Value creation handlers.
|
|
Value *handleLoad(LoadInst &Load, Type *VT, Type *ExtendedVT);
|
|
Value *handleCallBase(CallBase &Call, Type *VT, Type *ExtendedVT,
|
|
const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map, IRBuilder<> &Builder);
|
|
Value *maybeHandleKnownCallBase(CallBase &Call, Type *VT, Type *ExtendedVT,
|
|
const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map,
|
|
IRBuilder<> &Builder);
|
|
Value *handleTrunc(const FPTruncInst &Trunc, Type *VT, Type *ExtendedVT,
|
|
const ValueToShadowMap &Map, IRBuilder<> &Builder);
|
|
Value *handleExt(const FPExtInst &Ext, Type *VT, Type *ExtendedVT,
|
|
const ValueToShadowMap &Map, IRBuilder<> &Builder);
|
|
|
|
// Value propagation handlers.
|
|
void propagateFTStore(StoreInst &Store, Type *VT, Type *ExtendedVT,
|
|
const ValueToShadowMap &Map);
|
|
void propagateNonFTStore(StoreInst &Store, Type *VT,
|
|
const ValueToShadowMap &Map);
|
|
|
|
const DataLayout &DL;
|
|
LLVMContext &Context;
|
|
MappingConfig Config;
|
|
IntegerType *IntptrTy = nullptr;
|
|
|
|
// TODO: Use std::array instead?
|
|
FunctionCallee NsanGetShadowPtrForStore[FTValueType::kNumValueTypes] = {};
|
|
FunctionCallee NsanGetShadowPtrForLoad[FTValueType::kNumValueTypes] = {};
|
|
FunctionCallee NsanCheckValue[FTValueType::kNumValueTypes] = {};
|
|
FunctionCallee NsanFCmpFail[FTValueType::kNumValueTypes] = {};
|
|
|
|
NsanMemOpFn NsanCopyFns;
|
|
NsanMemOpFn NsanSetUnknownFns;
|
|
|
|
FunctionCallee NsanGetRawShadowTypePtr;
|
|
FunctionCallee NsanGetRawShadowPtr;
|
|
GlobalValue *NsanShadowRetTag = nullptr;
|
|
|
|
Type *NsanShadowRetType = nullptr;
|
|
GlobalValue *NsanShadowRetPtr = nullptr;
|
|
|
|
GlobalValue *NsanShadowArgsTag = nullptr;
|
|
|
|
Type *NsanShadowArgsType = nullptr;
|
|
GlobalValue *NsanShadowArgsPtr = nullptr;
|
|
|
|
std::optional<Regex> CheckFunctionsFilter;
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
PreservedAnalyses
|
|
NumericalStabilitySanitizerPass::run(Module &M, ModuleAnalysisManager &MAM) {
|
|
getOrCreateSanitizerCtorAndInitFunctions(
|
|
M, kNsanModuleCtorName, kNsanInitName, /*InitArgTypes=*/{},
|
|
/*InitArgs=*/{},
|
|
// This callback is invoked when the functions are created the first
|
|
// time. Hook them into the global ctors list in that case:
|
|
[&](Function *Ctor, FunctionCallee) { appendToGlobalCtors(M, Ctor, 0); });
|
|
|
|
NumericalStabilitySanitizer Nsan(M);
|
|
auto &FAM = MAM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
|
|
for (Function &F : M)
|
|
Nsan.sanitizeFunction(F, FAM.getResult<TargetLibraryAnalysis>(F));
|
|
|
|
return PreservedAnalyses::none();
|
|
}
|
|
|
|
static GlobalValue *createThreadLocalGV(const char *Name, Module &M, Type *Ty) {
|
|
return M.getOrInsertGlobal(Name, Ty, [&M, Ty, Name] {
|
|
return new GlobalVariable(M, Ty, false, GlobalVariable::ExternalLinkage,
|
|
nullptr, Name, nullptr,
|
|
GlobalVariable::InitialExecTLSModel);
|
|
});
|
|
}
|
|
|
|
NumericalStabilitySanitizer::NumericalStabilitySanitizer(Module &M)
|
|
: DL(M.getDataLayout()), Context(M.getContext()), Config(Context),
|
|
NsanCopyFns(M, {"__nsan_copy_4", "__nsan_copy_8", "__nsan_copy_16"},
|
|
"__nsan_copy_values", /*NumArgs=*/3),
|
|
NsanSetUnknownFns(M,
|
|
{"__nsan_set_value_unknown_4",
|
|
"__nsan_set_value_unknown_8",
|
|
"__nsan_set_value_unknown_16"},
|
|
"__nsan_set_value_unknown", /*NumArgs=*/2) {
|
|
IntptrTy = DL.getIntPtrType(Context);
|
|
Type *PtrTy = PointerType::getUnqual(Context);
|
|
Type *Int32Ty = Type::getInt32Ty(Context);
|
|
Type *Int1Ty = Type::getInt1Ty(Context);
|
|
Type *VoidTy = Type::getVoidTy(Context);
|
|
|
|
AttributeList Attr;
|
|
Attr = Attr.addFnAttribute(Context, Attribute::NoUnwind);
|
|
// Initialize the runtime values (functions and global variables).
|
|
for (int I = 0; I < kNumValueTypes; ++I) {
|
|
const FTValueType VT = static_cast<FTValueType>(I);
|
|
const char *VTName = typeNameFromFTValueType(VT);
|
|
Type *VTTy = typeFromFTValueType(VT, Context);
|
|
|
|
// Load/store.
|
|
const std::string GetterPrefix =
|
|
std::string("__nsan_get_shadow_ptr_for_") + VTName;
|
|
NsanGetShadowPtrForStore[VT] = M.getOrInsertFunction(
|
|
GetterPrefix + "_store", Attr, PtrTy, PtrTy, IntptrTy);
|
|
NsanGetShadowPtrForLoad[VT] = M.getOrInsertFunction(
|
|
GetterPrefix + "_load", Attr, PtrTy, PtrTy, IntptrTy);
|
|
|
|
// Check.
|
|
const auto &ShadowConfig = Config.byValueType(VT);
|
|
Type *ShadowTy = ShadowConfig.getType(Context);
|
|
NsanCheckValue[VT] =
|
|
M.getOrInsertFunction(std::string("__nsan_internal_check_") + VTName +
|
|
"_" + ShadowConfig.getNsanTypeId(),
|
|
Attr, Int32Ty, VTTy, ShadowTy, Int32Ty, IntptrTy);
|
|
NsanFCmpFail[VT] = M.getOrInsertFunction(
|
|
std::string("__nsan_fcmp_fail_") + VTName + "_" +
|
|
ShadowConfig.getNsanTypeId(),
|
|
Attr, VoidTy, VTTy, VTTy, ShadowTy, ShadowTy, Int32Ty, Int1Ty, Int1Ty);
|
|
}
|
|
|
|
// TODO: Add attributes nofree, nosync, readnone, readonly,
|
|
NsanGetRawShadowTypePtr = M.getOrInsertFunction(
|
|
"__nsan_internal_get_raw_shadow_type_ptr", Attr, PtrTy, PtrTy);
|
|
NsanGetRawShadowPtr = M.getOrInsertFunction(
|
|
"__nsan_internal_get_raw_shadow_ptr", Attr, PtrTy, PtrTy);
|
|
|
|
NsanShadowRetTag = createThreadLocalGV("__nsan_shadow_ret_tag", M, IntptrTy);
|
|
|
|
NsanShadowRetType = ArrayType::get(Type::getInt8Ty(Context),
|
|
kMaxVectorWidth * kMaxShadowTypeSizeBytes);
|
|
NsanShadowRetPtr =
|
|
createThreadLocalGV("__nsan_shadow_ret_ptr", M, NsanShadowRetType);
|
|
|
|
NsanShadowArgsTag =
|
|
createThreadLocalGV("__nsan_shadow_args_tag", M, IntptrTy);
|
|
|
|
NsanShadowArgsType =
|
|
ArrayType::get(Type::getInt8Ty(Context),
|
|
kMaxVectorWidth * kMaxNumArgs * kMaxShadowTypeSizeBytes);
|
|
|
|
NsanShadowArgsPtr =
|
|
createThreadLocalGV("__nsan_shadow_args_ptr", M, NsanShadowArgsType);
|
|
|
|
if (!ClCheckFunctionsFilter.empty()) {
|
|
Regex R = Regex(ClCheckFunctionsFilter);
|
|
std::string RegexError;
|
|
assert(R.isValid(RegexError));
|
|
CheckFunctionsFilter = std::move(R);
|
|
}
|
|
}
|
|
|
|
// Returns true if the given LLVM Value points to constant data (typically, a
|
|
// global variable reference).
|
|
bool NumericalStabilitySanitizer::addrPointsToConstantData(Value *Addr) {
|
|
// If this is a GEP, just analyze its pointer operand.
|
|
if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(Addr))
|
|
Addr = GEP->getPointerOperand();
|
|
|
|
if (GlobalVariable *GV = dyn_cast<GlobalVariable>(Addr))
|
|
return GV->isConstant();
|
|
return false;
|
|
}
|
|
|
|
// This instruments the function entry to create shadow arguments.
|
|
// Pseudocode:
|
|
// if (this_fn_ptr == __nsan_shadow_args_tag) {
|
|
// s(arg0) = LOAD<sizeof(arg0)>(__nsan_shadow_args);
|
|
// s(arg1) = LOAD<sizeof(arg1)>(__nsan_shadow_args + sizeof(arg0));
|
|
// ...
|
|
// __nsan_shadow_args_tag = 0;
|
|
// } else {
|
|
// s(arg0) = fext(arg0);
|
|
// s(arg1) = fext(arg1);
|
|
// ...
|
|
// }
|
|
void NumericalStabilitySanitizer::createShadowArguments(
|
|
Function &F, const TargetLibraryInfo &TLI, ValueToShadowMap &Map) {
|
|
assert(!F.getIntrinsicID() && "found a definition of an intrinsic");
|
|
|
|
// Do not bother if there are no FP args.
|
|
if (all_of(F.args(), [this](const Argument &Arg) {
|
|
return Config.getExtendedFPType(Arg.getType()) == nullptr;
|
|
}))
|
|
return;
|
|
|
|
IRBuilder<> Builder(&F.getEntryBlock(), F.getEntryBlock().getFirstNonPHIIt());
|
|
// The function has shadow args if the shadow args tag matches the function
|
|
// address.
|
|
Value *HasShadowArgs = Builder.CreateICmpEQ(
|
|
Builder.CreateLoad(IntptrTy, NsanShadowArgsTag, /*isVolatile=*/false),
|
|
Builder.CreatePtrToInt(&F, IntptrTy));
|
|
|
|
unsigned ShadowArgsOffsetBytes = 0;
|
|
for (Argument &Arg : F.args()) {
|
|
Type *VT = Arg.getType();
|
|
Type *ExtendedVT = Config.getExtendedFPType(VT);
|
|
if (ExtendedVT == nullptr)
|
|
continue; // Not an FT value.
|
|
Value *L = Builder.CreateAlignedLoad(
|
|
ExtendedVT,
|
|
Builder.CreateConstGEP2_64(NsanShadowArgsType, NsanShadowArgsPtr, 0,
|
|
ShadowArgsOffsetBytes),
|
|
Align(1), /*isVolatile=*/false);
|
|
Value *Shadow = Builder.CreateSelect(HasShadowArgs, L,
|
|
Builder.CreateFPExt(&Arg, ExtendedVT));
|
|
Map.setShadow(Arg, *Shadow);
|
|
TypeSize SlotSize = DL.getTypeStoreSize(ExtendedVT);
|
|
assert(!SlotSize.isScalable() && "unsupported");
|
|
ShadowArgsOffsetBytes += SlotSize;
|
|
}
|
|
Builder.CreateStore(ConstantInt::get(IntptrTy, 0), NsanShadowArgsTag);
|
|
}
|
|
|
|
// Returns true if the instrumentation should emit code to check arguments
|
|
// before a function call.
|
|
static bool shouldCheckArgs(CallBase &CI, const TargetLibraryInfo &TLI,
|
|
const std::optional<Regex> &CheckFunctionsFilter) {
|
|
|
|
Function *Fn = CI.getCalledFunction();
|
|
|
|
if (CheckFunctionsFilter) {
|
|
// Skip checking args of indirect calls.
|
|
if (Fn == nullptr)
|
|
return false;
|
|
if (CheckFunctionsFilter->match(Fn->getName()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
if (Fn == nullptr)
|
|
return true; // Always check args of indirect calls.
|
|
|
|
// Never check nsan functions, the user called them for a reason.
|
|
if (Fn->getName().starts_with("__nsan_"))
|
|
return false;
|
|
|
|
const auto ID = Fn->getIntrinsicID();
|
|
LibFunc LFunc = LibFunc::NumLibFuncs;
|
|
// Always check args of unknown functions.
|
|
if (ID == Intrinsic::ID() && !TLI.getLibFunc(*Fn, LFunc))
|
|
return true;
|
|
|
|
// Do not check args of an `fabs` call that is used for a comparison.
|
|
// This is typically used for `fabs(a-b) < tolerance`, where what matters is
|
|
// the result of the comparison, which is already caught be the fcmp checks.
|
|
if (ID == Intrinsic::fabs || LFunc == LibFunc_fabsf ||
|
|
LFunc == LibFunc_fabs || LFunc == LibFunc_fabsl)
|
|
for (const auto &U : CI.users())
|
|
if (isa<CmpInst>(U))
|
|
return false;
|
|
|
|
return true; // Default is check.
|
|
}
|
|
|
|
// Populates the shadow call stack (which contains shadow values for every
|
|
// floating-point parameter to the function).
|
|
void NumericalStabilitySanitizer::populateShadowStack(
|
|
CallBase &CI, const TargetLibraryInfo &TLI, const ValueToShadowMap &Map) {
|
|
// Do not create a shadow stack for inline asm.
|
|
if (CI.isInlineAsm())
|
|
return;
|
|
|
|
// Do not bother if there are no FP args.
|
|
if (all_of(CI.operands(), [this](const Value *Arg) {
|
|
return Config.getExtendedFPType(Arg->getType()) == nullptr;
|
|
}))
|
|
return;
|
|
|
|
IRBuilder<> Builder(&CI);
|
|
SmallVector<Value *, 8> ArgShadows;
|
|
const bool ShouldCheckArgs = shouldCheckArgs(CI, TLI, CheckFunctionsFilter);
|
|
for (auto [ArgIdx, Arg] : enumerate(CI.operands())) {
|
|
if (Config.getExtendedFPType(Arg->getType()) == nullptr)
|
|
continue; // Not an FT value.
|
|
Value *ArgShadow = Map.getShadow(Arg);
|
|
ArgShadows.push_back(ShouldCheckArgs ? emitCheck(Arg, ArgShadow, Builder,
|
|
CheckLoc::makeArg(ArgIdx))
|
|
: ArgShadow);
|
|
}
|
|
|
|
// Do not create shadow stacks for intrinsics/known lib funcs.
|
|
if (Function *Fn = CI.getCalledFunction()) {
|
|
LibFunc LFunc;
|
|
if (Fn->isIntrinsic() || TLI.getLibFunc(*Fn, LFunc))
|
|
return;
|
|
}
|
|
|
|
// Set the shadow stack tag.
|
|
Builder.CreateStore(CI.getCalledOperand(), NsanShadowArgsTag);
|
|
TypeSize ShadowArgsOffsetBytes = TypeSize::getFixed(0);
|
|
|
|
unsigned ShadowArgId = 0;
|
|
for (const Value *Arg : CI.operands()) {
|
|
Type *VT = Arg->getType();
|
|
Type *ExtendedVT = Config.getExtendedFPType(VT);
|
|
if (ExtendedVT == nullptr)
|
|
continue; // Not an FT value.
|
|
Builder.CreateAlignedStore(
|
|
ArgShadows[ShadowArgId++],
|
|
Builder.CreateConstGEP2_64(NsanShadowArgsType, NsanShadowArgsPtr, 0,
|
|
ShadowArgsOffsetBytes),
|
|
Align(1), /*isVolatile=*/false);
|
|
TypeSize SlotSize = DL.getTypeStoreSize(ExtendedVT);
|
|
assert(!SlotSize.isScalable() && "unsupported");
|
|
ShadowArgsOffsetBytes += SlotSize;
|
|
}
|
|
}
|
|
|
|
// Internal part of emitCheck(). Returns a value that indicates whether
|
|
// computation should continue with the shadow or resume by re-fextending the
|
|
// value.
|
|
enum class ContinuationType { // Keep in sync with runtime.
|
|
ContinueWithShadow = 0,
|
|
ResumeFromValue = 1,
|
|
};
|
|
|
|
Value *NumericalStabilitySanitizer::emitCheckInternal(Value *V, Value *ShadowV,
|
|
IRBuilder<> &Builder,
|
|
CheckLoc Loc) {
|
|
// Do not emit checks for constant values, this is redundant.
|
|
if (isa<Constant>(V))
|
|
return ConstantInt::get(
|
|
Builder.getInt32Ty(),
|
|
static_cast<int>(ContinuationType::ContinueWithShadow));
|
|
|
|
Type *Ty = V->getType();
|
|
if (const auto VT = ftValueTypeFromType(Ty))
|
|
return Builder.CreateCall(
|
|
NsanCheckValue[*VT],
|
|
{V, ShadowV, Loc.getType(Context), Loc.getValue(IntptrTy, Builder)});
|
|
|
|
if (Ty->isVectorTy()) {
|
|
auto *VecTy = cast<VectorType>(Ty);
|
|
// We currently skip scalable vector types in MappingConfig,
|
|
// thus we should not encounter any such types here.
|
|
assert(!VecTy->isScalableTy() &&
|
|
"Scalable vector types are not supported yet");
|
|
Value *CheckResult = nullptr;
|
|
for (int I = 0, E = VecTy->getElementCount().getFixedValue(); I < E; ++I) {
|
|
// We resume if any element resumes. Another option would be to create a
|
|
// vector shuffle with the array of ContinueWithShadow, but that is too
|
|
// complex.
|
|
Value *ExtractV = Builder.CreateExtractElement(V, I);
|
|
Value *ExtractShadowV = Builder.CreateExtractElement(ShadowV, I);
|
|
Value *ComponentCheckResult =
|
|
emitCheckInternal(ExtractV, ExtractShadowV, Builder, Loc);
|
|
CheckResult = CheckResult
|
|
? Builder.CreateOr(CheckResult, ComponentCheckResult)
|
|
: ComponentCheckResult;
|
|
}
|
|
return CheckResult;
|
|
}
|
|
if (Ty->isArrayTy()) {
|
|
Value *CheckResult = nullptr;
|
|
for (auto I : seq(Ty->getArrayNumElements())) {
|
|
Value *ExtractV = Builder.CreateExtractElement(V, I);
|
|
Value *ExtractShadowV = Builder.CreateExtractElement(ShadowV, I);
|
|
Value *ComponentCheckResult =
|
|
emitCheckInternal(ExtractV, ExtractShadowV, Builder, Loc);
|
|
CheckResult = CheckResult
|
|
? Builder.CreateOr(CheckResult, ComponentCheckResult)
|
|
: ComponentCheckResult;
|
|
}
|
|
return CheckResult;
|
|
}
|
|
if (Ty->isStructTy()) {
|
|
Value *CheckResult = nullptr;
|
|
for (auto I : seq(Ty->getStructNumElements())) {
|
|
if (Config.getExtendedFPType(Ty->getStructElementType(I)) == nullptr)
|
|
continue; // Only check FT values.
|
|
Value *ExtractV = Builder.CreateExtractValue(V, I);
|
|
Value *ExtractShadowV = Builder.CreateExtractElement(ShadowV, I);
|
|
Value *ComponentCheckResult =
|
|
emitCheckInternal(ExtractV, ExtractShadowV, Builder, Loc);
|
|
CheckResult = CheckResult
|
|
? Builder.CreateOr(CheckResult, ComponentCheckResult)
|
|
: ComponentCheckResult;
|
|
}
|
|
if (!CheckResult)
|
|
return ConstantInt::get(
|
|
Builder.getInt32Ty(),
|
|
static_cast<int>(ContinuationType::ContinueWithShadow));
|
|
return CheckResult;
|
|
}
|
|
|
|
llvm_unreachable("not implemented");
|
|
}
|
|
|
|
// Inserts a runtime check of V against its shadow value ShadowV.
|
|
// We check values whenever they escape: on return, call, stores, and
|
|
// insertvalue.
|
|
// Returns the shadow value that should be used to continue the computations,
|
|
// depending on the answer from the runtime.
|
|
// TODO: Should we check on select ? phi ?
|
|
Value *NumericalStabilitySanitizer::emitCheck(Value *V, Value *ShadowV,
|
|
IRBuilder<> &Builder,
|
|
CheckLoc Loc) {
|
|
// Do not emit checks for constant values, this is redundant.
|
|
if (isa<Constant>(V))
|
|
return ShadowV;
|
|
|
|
if (Instruction *Inst = dyn_cast<Instruction>(V)) {
|
|
Function *F = Inst->getFunction();
|
|
if (CheckFunctionsFilter && !CheckFunctionsFilter->match(F->getName())) {
|
|
return ShadowV;
|
|
}
|
|
}
|
|
|
|
Value *CheckResult = emitCheckInternal(V, ShadowV, Builder, Loc);
|
|
Value *ICmpEQ = Builder.CreateICmpEQ(
|
|
CheckResult,
|
|
ConstantInt::get(Builder.getInt32Ty(),
|
|
static_cast<int>(ContinuationType::ResumeFromValue)));
|
|
return Builder.CreateSelect(
|
|
ICmpEQ, Builder.CreateFPExt(V, Config.getExtendedFPType(V->getType())),
|
|
ShadowV);
|
|
}
|
|
|
|
// Inserts a check that fcmp on shadow values are consistent with that on base
|
|
// values.
|
|
void NumericalStabilitySanitizer::emitFCmpCheck(FCmpInst &FCmp,
|
|
const ValueToShadowMap &Map) {
|
|
if (!ClInstrumentFCmp)
|
|
return;
|
|
|
|
Function *F = FCmp.getFunction();
|
|
if (CheckFunctionsFilter && !CheckFunctionsFilter->match(F->getName()))
|
|
return;
|
|
|
|
Value *LHS = FCmp.getOperand(0);
|
|
if (Config.getExtendedFPType(LHS->getType()) == nullptr)
|
|
return;
|
|
Value *RHS = FCmp.getOperand(1);
|
|
|
|
// Split the basic block. On mismatch, we'll jump to the new basic block with
|
|
// a call to the runtime for error reporting.
|
|
BasicBlock *FCmpBB = FCmp.getParent();
|
|
BasicBlock *NextBB = FCmpBB->splitBasicBlock(FCmp.getNextNode());
|
|
// Remove the newly created terminator unconditional branch.
|
|
FCmpBB->back().eraseFromParent();
|
|
BasicBlock *FailBB =
|
|
BasicBlock::Create(Context, "", FCmpBB->getParent(), NextBB);
|
|
|
|
// Create the shadow fcmp and comparison between the fcmps.
|
|
IRBuilder<> FCmpBuilder(FCmpBB);
|
|
FCmpBuilder.SetCurrentDebugLocation(FCmp.getDebugLoc());
|
|
Value *ShadowLHS = Map.getShadow(LHS);
|
|
Value *ShadowRHS = Map.getShadow(RHS);
|
|
// See comment on ClTruncateFCmpEq.
|
|
if (FCmp.isEquality() && ClTruncateFCmpEq) {
|
|
Type *Ty = ShadowLHS->getType();
|
|
ShadowLHS = FCmpBuilder.CreateFPExt(
|
|
FCmpBuilder.CreateFPTrunc(ShadowLHS, LHS->getType()), Ty);
|
|
ShadowRHS = FCmpBuilder.CreateFPExt(
|
|
FCmpBuilder.CreateFPTrunc(ShadowRHS, RHS->getType()), Ty);
|
|
}
|
|
Value *ShadowFCmp =
|
|
FCmpBuilder.CreateFCmp(FCmp.getPredicate(), ShadowLHS, ShadowRHS);
|
|
Value *OriginalAndShadowFcmpMatch =
|
|
FCmpBuilder.CreateICmpEQ(&FCmp, ShadowFCmp);
|
|
|
|
if (OriginalAndShadowFcmpMatch->getType()->isVectorTy()) {
|
|
// If we have a vector type, `OriginalAndShadowFcmpMatch` is a vector of i1,
|
|
// where an element is true if the corresponding elements in original and
|
|
// shadow are the same. We want all elements to be 1.
|
|
OriginalAndShadowFcmpMatch =
|
|
FCmpBuilder.CreateAndReduce(OriginalAndShadowFcmpMatch);
|
|
}
|
|
|
|
// Use MDBuilder(*C).createLikelyBranchWeights() because "match" is the common
|
|
// case.
|
|
FCmpBuilder.CreateCondBr(OriginalAndShadowFcmpMatch, NextBB, FailBB,
|
|
MDBuilder(Context).createLikelyBranchWeights());
|
|
|
|
// Fill in FailBB.
|
|
IRBuilder<> FailBuilder(FailBB);
|
|
FailBuilder.SetCurrentDebugLocation(FCmp.getDebugLoc());
|
|
|
|
const auto EmitFailCall = [this, &FCmp, &FCmpBuilder,
|
|
&FailBuilder](Value *L, Value *R, Value *ShadowL,
|
|
Value *ShadowR, Value *Result,
|
|
Value *ShadowResult) {
|
|
Type *FT = L->getType();
|
|
FunctionCallee *Callee = nullptr;
|
|
if (FT->isFloatTy()) {
|
|
Callee = &(NsanFCmpFail[kFloat]);
|
|
} else if (FT->isDoubleTy()) {
|
|
Callee = &(NsanFCmpFail[kDouble]);
|
|
} else if (FT->isX86_FP80Ty()) {
|
|
// TODO: make NsanFCmpFailLongDouble work.
|
|
Callee = &(NsanFCmpFail[kDouble]);
|
|
L = FailBuilder.CreateFPTrunc(L, Type::getDoubleTy(Context));
|
|
R = FailBuilder.CreateFPTrunc(L, Type::getDoubleTy(Context));
|
|
} else {
|
|
llvm_unreachable("not implemented");
|
|
}
|
|
FailBuilder.CreateCall(*Callee, {L, R, ShadowL, ShadowR,
|
|
ConstantInt::get(FCmpBuilder.getInt32Ty(),
|
|
FCmp.getPredicate()),
|
|
Result, ShadowResult});
|
|
};
|
|
if (LHS->getType()->isVectorTy()) {
|
|
for (int I = 0, E = cast<VectorType>(LHS->getType())
|
|
->getElementCount()
|
|
.getFixedValue();
|
|
I < E; ++I) {
|
|
Value *ExtractLHS = FailBuilder.CreateExtractElement(LHS, I);
|
|
Value *ExtractRHS = FailBuilder.CreateExtractElement(RHS, I);
|
|
Value *ExtractShaodwLHS = FailBuilder.CreateExtractElement(ShadowLHS, I);
|
|
Value *ExtractShaodwRHS = FailBuilder.CreateExtractElement(ShadowRHS, I);
|
|
Value *ExtractFCmp = FailBuilder.CreateExtractElement(&FCmp, I);
|
|
Value *ExtractShadowFCmp =
|
|
FailBuilder.CreateExtractElement(ShadowFCmp, I);
|
|
EmitFailCall(ExtractLHS, ExtractRHS, ExtractShaodwLHS, ExtractShaodwRHS,
|
|
ExtractFCmp, ExtractShadowFCmp);
|
|
}
|
|
} else {
|
|
EmitFailCall(LHS, RHS, ShadowLHS, ShadowRHS, &FCmp, ShadowFCmp);
|
|
}
|
|
FailBuilder.CreateBr(NextBB);
|
|
|
|
++NumInstrumentedFCmp;
|
|
}
|
|
|
|
// Creates a shadow phi value for any phi that defines a value of FT type.
|
|
PHINode *NumericalStabilitySanitizer::maybeCreateShadowPhi(
|
|
PHINode &Phi, const TargetLibraryInfo &TLI) {
|
|
Type *VT = Phi.getType();
|
|
Type *ExtendedVT = Config.getExtendedFPType(VT);
|
|
if (ExtendedVT == nullptr)
|
|
return nullptr; // Not an FT value.
|
|
// The phi operands are shadow values and are not available when the phi is
|
|
// created. They will be populated in a final phase, once all shadow values
|
|
// have been created.
|
|
PHINode *Shadow = PHINode::Create(ExtendedVT, Phi.getNumIncomingValues());
|
|
Shadow->insertAfter(Phi.getIterator());
|
|
return Shadow;
|
|
}
|
|
|
|
Value *NumericalStabilitySanitizer::handleLoad(LoadInst &Load, Type *VT,
|
|
Type *ExtendedVT) {
|
|
IRBuilder<> Builder(Load.getNextNode());
|
|
Builder.SetCurrentDebugLocation(Load.getDebugLoc());
|
|
if (addrPointsToConstantData(Load.getPointerOperand())) {
|
|
// No need to look into the shadow memory, the value is a constant. Just
|
|
// convert from FT to 2FT.
|
|
return Builder.CreateFPExt(&Load, ExtendedVT);
|
|
}
|
|
|
|
// if (%shadowptr == &)
|
|
// %shadow = fpext %v
|
|
// else
|
|
// %shadow = load (ptrcast %shadow_ptr))
|
|
// Considered options here:
|
|
// - Have `NsanGetShadowPtrForLoad` return a fixed address
|
|
// &__nsan_unknown_value_shadow_address that is valid to load from, and
|
|
// use a select. This has the advantage that the generated IR is simpler.
|
|
// - Have `NsanGetShadowPtrForLoad` return nullptr. Because `select` does
|
|
// not short-circuit, dereferencing the returned pointer is no longer an
|
|
// option, have to split and create a separate basic block. This has the
|
|
// advantage of being easier to debug because it crashes if we ever mess
|
|
// up.
|
|
|
|
const auto Extents = getMemoryExtentsOrDie(VT);
|
|
Value *ShadowPtr = Builder.CreateCall(
|
|
NsanGetShadowPtrForLoad[Extents.ValueType],
|
|
{Load.getPointerOperand(), ConstantInt::get(IntptrTy, Extents.NumElts)});
|
|
++NumInstrumentedFTLoads;
|
|
|
|
// Split the basic block.
|
|
BasicBlock *LoadBB = Load.getParent();
|
|
BasicBlock *NextBB = LoadBB->splitBasicBlock(Builder.GetInsertPoint());
|
|
// Create the two options for creating the shadow value.
|
|
BasicBlock *ShadowLoadBB =
|
|
BasicBlock::Create(Context, "", LoadBB->getParent(), NextBB);
|
|
BasicBlock *FExtBB =
|
|
BasicBlock::Create(Context, "", LoadBB->getParent(), NextBB);
|
|
|
|
// Replace the newly created terminator unconditional branch by a conditional
|
|
// branch to one of the options.
|
|
{
|
|
LoadBB->back().eraseFromParent();
|
|
IRBuilder<> LoadBBBuilder(LoadBB); // The old builder has been invalidated.
|
|
LoadBBBuilder.SetCurrentDebugLocation(Load.getDebugLoc());
|
|
LoadBBBuilder.CreateCondBr(LoadBBBuilder.CreateIsNull(ShadowPtr), FExtBB,
|
|
ShadowLoadBB);
|
|
}
|
|
|
|
// Fill in ShadowLoadBB.
|
|
IRBuilder<> ShadowLoadBBBuilder(ShadowLoadBB);
|
|
ShadowLoadBBBuilder.SetCurrentDebugLocation(Load.getDebugLoc());
|
|
Value *ShadowLoad = ShadowLoadBBBuilder.CreateAlignedLoad(
|
|
ExtendedVT, ShadowPtr, Align(1), Load.isVolatile());
|
|
if (ClCheckLoads) {
|
|
ShadowLoad = emitCheck(&Load, ShadowLoad, ShadowLoadBBBuilder,
|
|
CheckLoc::makeLoad(Load.getPointerOperand()));
|
|
}
|
|
ShadowLoadBBBuilder.CreateBr(NextBB);
|
|
|
|
// Fill in FExtBB.
|
|
IRBuilder<> FExtBBBuilder(FExtBB);
|
|
FExtBBBuilder.SetCurrentDebugLocation(Load.getDebugLoc());
|
|
Value *FExt = FExtBBBuilder.CreateFPExt(&Load, ExtendedVT);
|
|
FExtBBBuilder.CreateBr(NextBB);
|
|
|
|
// The shadow value come from any of the options.
|
|
IRBuilder<> NextBBBuilder(&*NextBB->begin());
|
|
NextBBBuilder.SetCurrentDebugLocation(Load.getDebugLoc());
|
|
PHINode *ShadowPhi = NextBBBuilder.CreatePHI(ExtendedVT, 2);
|
|
ShadowPhi->addIncoming(ShadowLoad, ShadowLoadBB);
|
|
ShadowPhi->addIncoming(FExt, FExtBB);
|
|
return ShadowPhi;
|
|
}
|
|
|
|
Value *NumericalStabilitySanitizer::handleTrunc(const FPTruncInst &Trunc,
|
|
Type *VT, Type *ExtendedVT,
|
|
const ValueToShadowMap &Map,
|
|
IRBuilder<> &Builder) {
|
|
Value *OrigSource = Trunc.getOperand(0);
|
|
Type *OrigSourceTy = OrigSource->getType();
|
|
Type *ExtendedSourceTy = Config.getExtendedFPType(OrigSourceTy);
|
|
|
|
// When truncating:
|
|
// - (A) If the source has a shadow, we truncate from the shadow, else we
|
|
// truncate from the original source.
|
|
// - (B) If the shadow of the source is larger than the shadow of the dest,
|
|
// we still need a truncate. Else, the shadow of the source is the same
|
|
// type as the shadow of the dest (because mappings are non-decreasing), so
|
|
// we don't need to emit a truncate.
|
|
// Examples,
|
|
// with a mapping of {f32->f64;f64->f80;f80->f128}
|
|
// fptrunc double %1 to float -> fptrunc x86_fp80 s(%1) to double
|
|
// fptrunc x86_fp80 %1 to float -> fptrunc fp128 s(%1) to double
|
|
// fptrunc fp128 %1 to float -> fptrunc fp128 %1 to double
|
|
// fptrunc x86_fp80 %1 to double -> x86_fp80 s(%1)
|
|
// fptrunc fp128 %1 to double -> fptrunc fp128 %1 to x86_fp80
|
|
// fptrunc fp128 %1 to x86_fp80 -> fp128 %1
|
|
// with a mapping of {f32->f64;f64->f128;f80->f128}
|
|
// fptrunc double %1 to float -> fptrunc fp128 s(%1) to double
|
|
// fptrunc x86_fp80 %1 to float -> fptrunc fp128 s(%1) to double
|
|
// fptrunc fp128 %1 to float -> fptrunc fp128 %1 to double
|
|
// fptrunc x86_fp80 %1 to double -> fp128 %1
|
|
// fptrunc fp128 %1 to double -> fp128 %1
|
|
// fptrunc fp128 %1 to x86_fp80 -> fp128 %1
|
|
// with a mapping of {f32->f32;f64->f32;f80->f64}
|
|
// fptrunc double %1 to float -> float s(%1)
|
|
// fptrunc x86_fp80 %1 to float -> fptrunc double s(%1) to float
|
|
// fptrunc fp128 %1 to float -> fptrunc fp128 %1 to float
|
|
// fptrunc x86_fp80 %1 to double -> fptrunc double s(%1) to float
|
|
// fptrunc fp128 %1 to double -> fptrunc fp128 %1 to float
|
|
// fptrunc fp128 %1 to x86_fp80 -> fptrunc fp128 %1 to double
|
|
|
|
// See (A) above.
|
|
Value *Source = ExtendedSourceTy ? Map.getShadow(OrigSource) : OrigSource;
|
|
Type *SourceTy = ExtendedSourceTy ? ExtendedSourceTy : OrigSourceTy;
|
|
// See (B) above.
|
|
if (SourceTy == ExtendedVT)
|
|
return Source;
|
|
|
|
return Builder.CreateFPTrunc(Source, ExtendedVT);
|
|
}
|
|
|
|
Value *NumericalStabilitySanitizer::handleExt(const FPExtInst &Ext, Type *VT,
|
|
Type *ExtendedVT,
|
|
const ValueToShadowMap &Map,
|
|
IRBuilder<> &Builder) {
|
|
Value *OrigSource = Ext.getOperand(0);
|
|
Type *OrigSourceTy = OrigSource->getType();
|
|
Type *ExtendedSourceTy = Config.getExtendedFPType(OrigSourceTy);
|
|
// When extending:
|
|
// - (A) If the source has a shadow, we extend from the shadow, else we
|
|
// extend from the original source.
|
|
// - (B) If the shadow of the dest is larger than the shadow of the source,
|
|
// we still need an extend. Else, the shadow of the source is the same
|
|
// type as the shadow of the dest (because mappings are non-decreasing), so
|
|
// we don't need to emit an extend.
|
|
// Examples,
|
|
// with a mapping of {f32->f64;f64->f80;f80->f128}
|
|
// fpext half %1 to float -> fpext half %1 to double
|
|
// fpext half %1 to double -> fpext half %1 to x86_fp80
|
|
// fpext half %1 to x86_fp80 -> fpext half %1 to fp128
|
|
// fpext float %1 to double -> double s(%1)
|
|
// fpext float %1 to x86_fp80 -> fpext double s(%1) to fp128
|
|
// fpext double %1 to x86_fp80 -> fpext x86_fp80 s(%1) to fp128
|
|
// with a mapping of {f32->f64;f64->f128;f80->f128}
|
|
// fpext half %1 to float -> fpext half %1 to double
|
|
// fpext half %1 to double -> fpext half %1 to fp128
|
|
// fpext half %1 to x86_fp80 -> fpext half %1 to fp128
|
|
// fpext float %1 to double -> fpext double s(%1) to fp128
|
|
// fpext float %1 to x86_fp80 -> fpext double s(%1) to fp128
|
|
// fpext double %1 to x86_fp80 -> fp128 s(%1)
|
|
// with a mapping of {f32->f32;f64->f32;f80->f64}
|
|
// fpext half %1 to float -> fpext half %1 to float
|
|
// fpext half %1 to double -> fpext half %1 to float
|
|
// fpext half %1 to x86_fp80 -> fpext half %1 to double
|
|
// fpext float %1 to double -> s(%1)
|
|
// fpext float %1 to x86_fp80 -> fpext float s(%1) to double
|
|
// fpext double %1 to x86_fp80 -> fpext float s(%1) to double
|
|
|
|
// See (A) above.
|
|
Value *Source = ExtendedSourceTy ? Map.getShadow(OrigSource) : OrigSource;
|
|
Type *SourceTy = ExtendedSourceTy ? ExtendedSourceTy : OrigSourceTy;
|
|
// See (B) above.
|
|
if (SourceTy == ExtendedVT)
|
|
return Source;
|
|
|
|
return Builder.CreateFPExt(Source, ExtendedVT);
|
|
}
|
|
|
|
namespace {
|
|
// TODO: This should be tablegen-ed.
|
|
struct KnownIntrinsic {
|
|
struct WidenedIntrinsic {
|
|
const char *NarrowName;
|
|
Intrinsic::ID ID; // wide id.
|
|
using FnTypeFactory = FunctionType *(*)(LLVMContext &);
|
|
FnTypeFactory MakeFnTy;
|
|
};
|
|
|
|
static const char *get(LibFunc LFunc);
|
|
|
|
// Given an intrinsic with an `FT` argument, try to find a wider intrinsic
|
|
// that applies the same operation on the shadow argument.
|
|
// Options are:
|
|
// - pass in the ID and full function type,
|
|
// - pass in the name, which includes the function type through mangling.
|
|
static const WidenedIntrinsic *widen(StringRef Name);
|
|
|
|
private:
|
|
struct LFEntry {
|
|
LibFunc LFunc;
|
|
const char *IntrinsicName;
|
|
};
|
|
static const LFEntry kLibfuncIntrinsics[];
|
|
|
|
static const WidenedIntrinsic kWidenedIntrinsics[];
|
|
};
|
|
} // namespace
|
|
|
|
static FunctionType *makeDoubleDouble(LLVMContext &C) {
|
|
return FunctionType::get(Type::getDoubleTy(C), {Type::getDoubleTy(C)}, false);
|
|
}
|
|
|
|
static FunctionType *makeX86FP80X86FP80(LLVMContext &C) {
|
|
return FunctionType::get(Type::getX86_FP80Ty(C), {Type::getX86_FP80Ty(C)},
|
|
false);
|
|
}
|
|
|
|
static FunctionType *makeDoubleDoubleI32(LLVMContext &C) {
|
|
return FunctionType::get(Type::getDoubleTy(C),
|
|
{Type::getDoubleTy(C), Type::getInt32Ty(C)}, false);
|
|
}
|
|
|
|
static FunctionType *makeX86FP80X86FP80I32(LLVMContext &C) {
|
|
return FunctionType::get(Type::getX86_FP80Ty(C),
|
|
{Type::getX86_FP80Ty(C), Type::getInt32Ty(C)},
|
|
false);
|
|
}
|
|
|
|
static FunctionType *makeDoubleDoubleDouble(LLVMContext &C) {
|
|
return FunctionType::get(Type::getDoubleTy(C),
|
|
{Type::getDoubleTy(C), Type::getDoubleTy(C)}, false);
|
|
}
|
|
|
|
static FunctionType *makeX86FP80X86FP80X86FP80(LLVMContext &C) {
|
|
return FunctionType::get(Type::getX86_FP80Ty(C),
|
|
{Type::getX86_FP80Ty(C), Type::getX86_FP80Ty(C)},
|
|
false);
|
|
}
|
|
|
|
static FunctionType *makeDoubleDoubleDoubleDouble(LLVMContext &C) {
|
|
return FunctionType::get(
|
|
Type::getDoubleTy(C),
|
|
{Type::getDoubleTy(C), Type::getDoubleTy(C), Type::getDoubleTy(C)},
|
|
false);
|
|
}
|
|
|
|
static FunctionType *makeX86FP80X86FP80X86FP80X86FP80(LLVMContext &C) {
|
|
return FunctionType::get(
|
|
Type::getX86_FP80Ty(C),
|
|
{Type::getX86_FP80Ty(C), Type::getX86_FP80Ty(C), Type::getX86_FP80Ty(C)},
|
|
false);
|
|
}
|
|
|
|
const KnownIntrinsic::WidenedIntrinsic KnownIntrinsic::kWidenedIntrinsics[] = {
|
|
// TODO: Right now we ignore vector intrinsics.
|
|
// This is hard because we have to model the semantics of the intrinsics,
|
|
// e.g. llvm.x86.sse2.min.sd means extract first element, min, insert back.
|
|
// Intrinsics that take any non-vector FT types:
|
|
// NOTE: Right now because of
|
|
// https://github.com/llvm/llvm-project/issues/44744
|
|
// for f128 we need to use makeX86FP80X86FP80 (go to a lower precision and
|
|
// come back).
|
|
{"llvm.sqrt.f32", Intrinsic::sqrt, makeDoubleDouble},
|
|
{"llvm.sqrt.f64", Intrinsic::sqrt, makeX86FP80X86FP80},
|
|
{"llvm.sqrt.f80", Intrinsic::sqrt, makeX86FP80X86FP80},
|
|
{"llvm.powi.f32", Intrinsic::powi, makeDoubleDoubleI32},
|
|
{"llvm.powi.f64", Intrinsic::powi, makeX86FP80X86FP80I32},
|
|
{"llvm.powi.f80", Intrinsic::powi, makeX86FP80X86FP80I32},
|
|
{"llvm.sin.f32", Intrinsic::sin, makeDoubleDouble},
|
|
{"llvm.sin.f64", Intrinsic::sin, makeX86FP80X86FP80},
|
|
{"llvm.sin.f80", Intrinsic::sin, makeX86FP80X86FP80},
|
|
{"llvm.cos.f32", Intrinsic::cos, makeDoubleDouble},
|
|
{"llvm.cos.f64", Intrinsic::cos, makeX86FP80X86FP80},
|
|
{"llvm.cos.f80", Intrinsic::cos, makeX86FP80X86FP80},
|
|
{"llvm.pow.f32", Intrinsic::pow, makeDoubleDoubleDouble},
|
|
{"llvm.pow.f64", Intrinsic::pow, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.pow.f80", Intrinsic::pow, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.exp.f32", Intrinsic::exp, makeDoubleDouble},
|
|
{"llvm.exp.f64", Intrinsic::exp, makeX86FP80X86FP80},
|
|
{"llvm.exp.f80", Intrinsic::exp, makeX86FP80X86FP80},
|
|
{"llvm.exp2.f32", Intrinsic::exp2, makeDoubleDouble},
|
|
{"llvm.exp2.f64", Intrinsic::exp2, makeX86FP80X86FP80},
|
|
{"llvm.exp2.f80", Intrinsic::exp2, makeX86FP80X86FP80},
|
|
{"llvm.log.f32", Intrinsic::log, makeDoubleDouble},
|
|
{"llvm.log.f64", Intrinsic::log, makeX86FP80X86FP80},
|
|
{"llvm.log.f80", Intrinsic::log, makeX86FP80X86FP80},
|
|
{"llvm.log10.f32", Intrinsic::log10, makeDoubleDouble},
|
|
{"llvm.log10.f64", Intrinsic::log10, makeX86FP80X86FP80},
|
|
{"llvm.log10.f80", Intrinsic::log10, makeX86FP80X86FP80},
|
|
{"llvm.log2.f32", Intrinsic::log2, makeDoubleDouble},
|
|
{"llvm.log2.f64", Intrinsic::log2, makeX86FP80X86FP80},
|
|
{"llvm.log2.f80", Intrinsic::log2, makeX86FP80X86FP80},
|
|
{"llvm.fma.f32", Intrinsic::fma, makeDoubleDoubleDoubleDouble},
|
|
|
|
{"llvm.fmuladd.f32", Intrinsic::fmuladd, makeDoubleDoubleDoubleDouble},
|
|
|
|
{"llvm.fma.f64", Intrinsic::fma, makeX86FP80X86FP80X86FP80X86FP80},
|
|
|
|
{"llvm.fmuladd.f64", Intrinsic::fma, makeX86FP80X86FP80X86FP80X86FP80},
|
|
|
|
{"llvm.fma.f80", Intrinsic::fma, makeX86FP80X86FP80X86FP80X86FP80},
|
|
{"llvm.fabs.f32", Intrinsic::fabs, makeDoubleDouble},
|
|
{"llvm.fabs.f64", Intrinsic::fabs, makeX86FP80X86FP80},
|
|
{"llvm.fabs.f80", Intrinsic::fabs, makeX86FP80X86FP80},
|
|
{"llvm.minnum.f32", Intrinsic::minnum, makeDoubleDoubleDouble},
|
|
{"llvm.minnum.f64", Intrinsic::minnum, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.minnum.f80", Intrinsic::minnum, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.maxnum.f32", Intrinsic::maxnum, makeDoubleDoubleDouble},
|
|
{"llvm.maxnum.f64", Intrinsic::maxnum, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.maxnum.f80", Intrinsic::maxnum, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.minimum.f32", Intrinsic::minimum, makeDoubleDoubleDouble},
|
|
{"llvm.minimum.f64", Intrinsic::minimum, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.minimum.f80", Intrinsic::minimum, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.maximum.f32", Intrinsic::maximum, makeDoubleDoubleDouble},
|
|
{"llvm.maximum.f64", Intrinsic::maximum, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.maximum.f80", Intrinsic::maximum, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.copysign.f32", Intrinsic::copysign, makeDoubleDoubleDouble},
|
|
{"llvm.copysign.f64", Intrinsic::copysign, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.copysign.f80", Intrinsic::copysign, makeX86FP80X86FP80X86FP80},
|
|
{"llvm.floor.f32", Intrinsic::floor, makeDoubleDouble},
|
|
{"llvm.floor.f64", Intrinsic::floor, makeX86FP80X86FP80},
|
|
{"llvm.floor.f80", Intrinsic::floor, makeX86FP80X86FP80},
|
|
{"llvm.ceil.f32", Intrinsic::ceil, makeDoubleDouble},
|
|
{"llvm.ceil.f64", Intrinsic::ceil, makeX86FP80X86FP80},
|
|
{"llvm.ceil.f80", Intrinsic::ceil, makeX86FP80X86FP80},
|
|
{"llvm.trunc.f32", Intrinsic::trunc, makeDoubleDouble},
|
|
{"llvm.trunc.f64", Intrinsic::trunc, makeX86FP80X86FP80},
|
|
{"llvm.trunc.f80", Intrinsic::trunc, makeX86FP80X86FP80},
|
|
{"llvm.rint.f32", Intrinsic::rint, makeDoubleDouble},
|
|
{"llvm.rint.f64", Intrinsic::rint, makeX86FP80X86FP80},
|
|
{"llvm.rint.f80", Intrinsic::rint, makeX86FP80X86FP80},
|
|
{"llvm.nearbyint.f32", Intrinsic::nearbyint, makeDoubleDouble},
|
|
{"llvm.nearbyint.f64", Intrinsic::nearbyint, makeX86FP80X86FP80},
|
|
{"llvm.nearbyin80f64", Intrinsic::nearbyint, makeX86FP80X86FP80},
|
|
{"llvm.round.f32", Intrinsic::round, makeDoubleDouble},
|
|
{"llvm.round.f64", Intrinsic::round, makeX86FP80X86FP80},
|
|
{"llvm.round.f80", Intrinsic::round, makeX86FP80X86FP80},
|
|
{"llvm.lround.f32", Intrinsic::lround, makeDoubleDouble},
|
|
{"llvm.lround.f64", Intrinsic::lround, makeX86FP80X86FP80},
|
|
{"llvm.lround.f80", Intrinsic::lround, makeX86FP80X86FP80},
|
|
{"llvm.llround.f32", Intrinsic::llround, makeDoubleDouble},
|
|
{"llvm.llround.f64", Intrinsic::llround, makeX86FP80X86FP80},
|
|
{"llvm.llround.f80", Intrinsic::llround, makeX86FP80X86FP80},
|
|
{"llvm.lrint.f32", Intrinsic::lrint, makeDoubleDouble},
|
|
{"llvm.lrint.f64", Intrinsic::lrint, makeX86FP80X86FP80},
|
|
{"llvm.lrint.f80", Intrinsic::lrint, makeX86FP80X86FP80},
|
|
{"llvm.llrint.f32", Intrinsic::llrint, makeDoubleDouble},
|
|
{"llvm.llrint.f64", Intrinsic::llrint, makeX86FP80X86FP80},
|
|
{"llvm.llrint.f80", Intrinsic::llrint, makeX86FP80X86FP80},
|
|
};
|
|
|
|
const KnownIntrinsic::LFEntry KnownIntrinsic::kLibfuncIntrinsics[] = {
|
|
{LibFunc_sqrtf, "llvm.sqrt.f32"},
|
|
{LibFunc_sqrt, "llvm.sqrt.f64"},
|
|
{LibFunc_sqrtl, "llvm.sqrt.f80"},
|
|
{LibFunc_sinf, "llvm.sin.f32"},
|
|
{LibFunc_sin, "llvm.sin.f64"},
|
|
{LibFunc_sinl, "llvm.sin.f80"},
|
|
{LibFunc_cosf, "llvm.cos.f32"},
|
|
{LibFunc_cos, "llvm.cos.f64"},
|
|
{LibFunc_cosl, "llvm.cos.f80"},
|
|
{LibFunc_powf, "llvm.pow.f32"},
|
|
{LibFunc_pow, "llvm.pow.f64"},
|
|
{LibFunc_powl, "llvm.pow.f80"},
|
|
{LibFunc_expf, "llvm.exp.f32"},
|
|
{LibFunc_exp, "llvm.exp.f64"},
|
|
{LibFunc_expl, "llvm.exp.f80"},
|
|
{LibFunc_exp2f, "llvm.exp2.f32"},
|
|
{LibFunc_exp2, "llvm.exp2.f64"},
|
|
{LibFunc_exp2l, "llvm.exp2.f80"},
|
|
{LibFunc_logf, "llvm.log.f32"},
|
|
{LibFunc_log, "llvm.log.f64"},
|
|
{LibFunc_logl, "llvm.log.f80"},
|
|
{LibFunc_log10f, "llvm.log10.f32"},
|
|
{LibFunc_log10, "llvm.log10.f64"},
|
|
{LibFunc_log10l, "llvm.log10.f80"},
|
|
{LibFunc_log2f, "llvm.log2.f32"},
|
|
{LibFunc_log2, "llvm.log2.f64"},
|
|
{LibFunc_log2l, "llvm.log2.f80"},
|
|
{LibFunc_fabsf, "llvm.fabs.f32"},
|
|
{LibFunc_fabs, "llvm.fabs.f64"},
|
|
{LibFunc_fabsl, "llvm.fabs.f80"},
|
|
{LibFunc_copysignf, "llvm.copysign.f32"},
|
|
{LibFunc_copysign, "llvm.copysign.f64"},
|
|
{LibFunc_copysignl, "llvm.copysign.f80"},
|
|
{LibFunc_floorf, "llvm.floor.f32"},
|
|
{LibFunc_floor, "llvm.floor.f64"},
|
|
{LibFunc_floorl, "llvm.floor.f80"},
|
|
{LibFunc_fmaxf, "llvm.maxnum.f32"},
|
|
{LibFunc_fmax, "llvm.maxnum.f64"},
|
|
{LibFunc_fmaxl, "llvm.maxnum.f80"},
|
|
{LibFunc_fminf, "llvm.minnum.f32"},
|
|
{LibFunc_fmin, "llvm.minnum.f64"},
|
|
{LibFunc_fminl, "llvm.minnum.f80"},
|
|
{LibFunc_ceilf, "llvm.ceil.f32"},
|
|
{LibFunc_ceil, "llvm.ceil.f64"},
|
|
{LibFunc_ceill, "llvm.ceil.f80"},
|
|
{LibFunc_truncf, "llvm.trunc.f32"},
|
|
{LibFunc_trunc, "llvm.trunc.f64"},
|
|
{LibFunc_truncl, "llvm.trunc.f80"},
|
|
{LibFunc_rintf, "llvm.rint.f32"},
|
|
{LibFunc_rint, "llvm.rint.f64"},
|
|
{LibFunc_rintl, "llvm.rint.f80"},
|
|
{LibFunc_nearbyintf, "llvm.nearbyint.f32"},
|
|
{LibFunc_nearbyint, "llvm.nearbyint.f64"},
|
|
{LibFunc_nearbyintl, "llvm.nearbyint.f80"},
|
|
{LibFunc_roundf, "llvm.round.f32"},
|
|
{LibFunc_round, "llvm.round.f64"},
|
|
{LibFunc_roundl, "llvm.round.f80"},
|
|
};
|
|
|
|
const char *KnownIntrinsic::get(LibFunc LFunc) {
|
|
for (const auto &E : kLibfuncIntrinsics) {
|
|
if (E.LFunc == LFunc)
|
|
return E.IntrinsicName;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const KnownIntrinsic::WidenedIntrinsic *KnownIntrinsic::widen(StringRef Name) {
|
|
for (const auto &E : kWidenedIntrinsics) {
|
|
if (E.NarrowName == Name)
|
|
return &E;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Returns the name of the LLVM intrinsic corresponding to the given function.
|
|
static const char *getIntrinsicFromLibfunc(Function &Fn, Type *VT,
|
|
const TargetLibraryInfo &TLI) {
|
|
LibFunc LFunc;
|
|
if (!TLI.getLibFunc(Fn, LFunc))
|
|
return nullptr;
|
|
|
|
if (const char *Name = KnownIntrinsic::get(LFunc))
|
|
return Name;
|
|
|
|
LLVM_DEBUG(errs() << "TODO: LibFunc: " << TLI.getName(LFunc) << "\n");
|
|
return nullptr;
|
|
}
|
|
|
|
// Try to handle a known function call.
|
|
Value *NumericalStabilitySanitizer::maybeHandleKnownCallBase(
|
|
CallBase &Call, Type *VT, Type *ExtendedVT, const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map, IRBuilder<> &Builder) {
|
|
Function *Fn = Call.getCalledFunction();
|
|
if (Fn == nullptr)
|
|
return nullptr;
|
|
|
|
Intrinsic::ID WidenedId = Intrinsic::ID();
|
|
FunctionType *WidenedFnTy = nullptr;
|
|
if (const auto ID = Fn->getIntrinsicID()) {
|
|
const auto *Widened = KnownIntrinsic::widen(Fn->getName());
|
|
if (Widened) {
|
|
WidenedId = Widened->ID;
|
|
WidenedFnTy = Widened->MakeFnTy(Context);
|
|
} else {
|
|
// If we don't know how to widen the intrinsic, we have no choice but to
|
|
// call the non-wide version on a truncated shadow and extend again
|
|
// afterwards.
|
|
WidenedId = ID;
|
|
WidenedFnTy = Fn->getFunctionType();
|
|
}
|
|
} else if (const char *Name = getIntrinsicFromLibfunc(*Fn, VT, TLI)) {
|
|
// We might have a call to a library function that we can replace with a
|
|
// wider Intrinsic.
|
|
const auto *Widened = KnownIntrinsic::widen(Name);
|
|
assert(Widened && "make sure KnownIntrinsic entries are consistent");
|
|
WidenedId = Widened->ID;
|
|
WidenedFnTy = Widened->MakeFnTy(Context);
|
|
} else {
|
|
// This is not a known library function or intrinsic.
|
|
return nullptr;
|
|
}
|
|
|
|
// Check that the widened intrinsic is valid.
|
|
SmallVector<Intrinsic::IITDescriptor, 8> Table;
|
|
getIntrinsicInfoTableEntries(WidenedId, Table);
|
|
SmallVector<Type *, 4> ArgTys;
|
|
ArrayRef<Intrinsic::IITDescriptor> TableRef = Table;
|
|
[[maybe_unused]] Intrinsic::MatchIntrinsicTypesResult MatchResult =
|
|
Intrinsic::matchIntrinsicSignature(WidenedFnTy, TableRef, ArgTys);
|
|
assert(MatchResult == Intrinsic::MatchIntrinsicTypes_Match &&
|
|
"invalid widened intrinsic");
|
|
// For known intrinsic functions, we create a second call to the same
|
|
// intrinsic with a different type.
|
|
SmallVector<Value *, 4> Args;
|
|
// The last operand is the intrinsic itself, skip it.
|
|
for (unsigned I = 0, E = Call.getNumOperands() - 1; I < E; ++I) {
|
|
Value *Arg = Call.getOperand(I);
|
|
Type *OrigArgTy = Arg->getType();
|
|
Type *IntrinsicArgTy = WidenedFnTy->getParamType(I);
|
|
if (OrigArgTy == IntrinsicArgTy) {
|
|
Args.push_back(Arg); // The arg is passed as is.
|
|
continue;
|
|
}
|
|
Type *ShadowArgTy = Config.getExtendedFPType(Arg->getType());
|
|
assert(ShadowArgTy &&
|
|
"don't know how to get the shadow value for a non-FT");
|
|
Value *Shadow = Map.getShadow(Arg);
|
|
if (ShadowArgTy == IntrinsicArgTy) {
|
|
// The shadow is the right type for the intrinsic.
|
|
assert(Shadow->getType() == ShadowArgTy);
|
|
Args.push_back(Shadow);
|
|
continue;
|
|
}
|
|
// There is no intrinsic with his level of precision, truncate the shadow.
|
|
Args.push_back(Builder.CreateFPTrunc(Shadow, IntrinsicArgTy));
|
|
}
|
|
Value *IntrinsicCall = Builder.CreateIntrinsic(WidenedId, ArgTys, Args);
|
|
return WidenedFnTy->getReturnType() == ExtendedVT
|
|
? IntrinsicCall
|
|
: Builder.CreateFPExt(IntrinsicCall, ExtendedVT);
|
|
}
|
|
|
|
// Handle a CallBase, i.e. a function call, an inline asm sequence, or an
|
|
// invoke.
|
|
Value *NumericalStabilitySanitizer::handleCallBase(CallBase &Call, Type *VT,
|
|
Type *ExtendedVT,
|
|
const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map,
|
|
IRBuilder<> &Builder) {
|
|
// We cannot look inside inline asm, just expand the result again.
|
|
if (Call.isInlineAsm())
|
|
return Builder.CreateFPExt(&Call, ExtendedVT);
|
|
|
|
// Intrinsics and library functions (e.g. sin, exp) are handled
|
|
// specifically, because we know their semantics and can do better than
|
|
// blindly calling them (e.g. compute the sinus in the actual shadow domain).
|
|
if (Value *V =
|
|
maybeHandleKnownCallBase(Call, VT, ExtendedVT, TLI, Map, Builder))
|
|
return V;
|
|
|
|
// If the return tag matches that of the called function, read the extended
|
|
// return value from the shadow ret ptr. Else, just extend the return value.
|
|
Value *L =
|
|
Builder.CreateLoad(IntptrTy, NsanShadowRetTag, /*isVolatile=*/false);
|
|
Value *HasShadowRet = Builder.CreateICmpEQ(
|
|
L, Builder.CreatePtrToInt(Call.getCalledOperand(), IntptrTy));
|
|
|
|
Value *ShadowRetVal = Builder.CreateLoad(
|
|
ExtendedVT,
|
|
Builder.CreateConstGEP2_64(NsanShadowRetType, NsanShadowRetPtr, 0, 0),
|
|
/*isVolatile=*/false);
|
|
Value *Shadow = Builder.CreateSelect(HasShadowRet, ShadowRetVal,
|
|
Builder.CreateFPExt(&Call, ExtendedVT));
|
|
++NumInstrumentedFTCalls;
|
|
return Shadow;
|
|
}
|
|
|
|
// Creates a shadow value for the given FT value. At that point all operands are
|
|
// guaranteed to be available.
|
|
Value *NumericalStabilitySanitizer::createShadowValueWithOperandsAvailable(
|
|
Instruction &Inst, const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map) {
|
|
Type *VT = Inst.getType();
|
|
Type *ExtendedVT = Config.getExtendedFPType(VT);
|
|
assert(ExtendedVT != nullptr && "trying to create a shadow for a non-FT");
|
|
|
|
if (auto *Load = dyn_cast<LoadInst>(&Inst))
|
|
return handleLoad(*Load, VT, ExtendedVT);
|
|
|
|
if (auto *Call = dyn_cast<CallInst>(&Inst)) {
|
|
// Insert after the call.
|
|
BasicBlock::iterator It(Inst);
|
|
IRBuilder<> Builder(Call->getParent(), ++It);
|
|
Builder.SetCurrentDebugLocation(Call->getDebugLoc());
|
|
return handleCallBase(*Call, VT, ExtendedVT, TLI, Map, Builder);
|
|
}
|
|
|
|
if (auto *Invoke = dyn_cast<InvokeInst>(&Inst)) {
|
|
// The Invoke terminates the basic block, create a new basic block in
|
|
// between the successful invoke and the next block.
|
|
BasicBlock *InvokeBB = Invoke->getParent();
|
|
BasicBlock *NextBB = Invoke->getNormalDest();
|
|
BasicBlock *NewBB =
|
|
BasicBlock::Create(Context, "", NextBB->getParent(), NextBB);
|
|
Inst.replaceSuccessorWith(NextBB, NewBB);
|
|
|
|
IRBuilder<> Builder(NewBB);
|
|
Builder.SetCurrentDebugLocation(Invoke->getDebugLoc());
|
|
Value *Shadow = handleCallBase(*Invoke, VT, ExtendedVT, TLI, Map, Builder);
|
|
Builder.CreateBr(NextBB);
|
|
NewBB->replaceSuccessorsPhiUsesWith(InvokeBB, NewBB);
|
|
return Shadow;
|
|
}
|
|
|
|
IRBuilder<> Builder(Inst.getNextNode());
|
|
Builder.SetCurrentDebugLocation(Inst.getDebugLoc());
|
|
|
|
if (auto *Trunc = dyn_cast<FPTruncInst>(&Inst))
|
|
return handleTrunc(*Trunc, VT, ExtendedVT, Map, Builder);
|
|
if (auto *Ext = dyn_cast<FPExtInst>(&Inst))
|
|
return handleExt(*Ext, VT, ExtendedVT, Map, Builder);
|
|
|
|
if (auto *UnaryOp = dyn_cast<UnaryOperator>(&Inst))
|
|
return Builder.CreateUnOp(UnaryOp->getOpcode(),
|
|
Map.getShadow(UnaryOp->getOperand(0)));
|
|
|
|
if (auto *BinOp = dyn_cast<BinaryOperator>(&Inst))
|
|
return Builder.CreateBinOp(BinOp->getOpcode(),
|
|
Map.getShadow(BinOp->getOperand(0)),
|
|
Map.getShadow(BinOp->getOperand(1)));
|
|
|
|
if (isa<UIToFPInst>(&Inst) || isa<SIToFPInst>(&Inst)) {
|
|
auto *Cast = cast<CastInst>(&Inst);
|
|
return Builder.CreateCast(Cast->getOpcode(), Cast->getOperand(0),
|
|
ExtendedVT);
|
|
}
|
|
|
|
if (auto *S = dyn_cast<SelectInst>(&Inst))
|
|
return Builder.CreateSelect(S->getCondition(),
|
|
Map.getShadow(S->getTrueValue()),
|
|
Map.getShadow(S->getFalseValue()));
|
|
|
|
if (auto *Freeze = dyn_cast<FreezeInst>(&Inst))
|
|
return Builder.CreateFreeze(Map.getShadow(Freeze->getOperand(0)));
|
|
|
|
if (auto *Extract = dyn_cast<ExtractElementInst>(&Inst))
|
|
return Builder.CreateExtractElement(
|
|
Map.getShadow(Extract->getVectorOperand()), Extract->getIndexOperand());
|
|
|
|
if (auto *Insert = dyn_cast<InsertElementInst>(&Inst))
|
|
return Builder.CreateInsertElement(Map.getShadow(Insert->getOperand(0)),
|
|
Map.getShadow(Insert->getOperand(1)),
|
|
Insert->getOperand(2));
|
|
|
|
if (auto *Shuffle = dyn_cast<ShuffleVectorInst>(&Inst))
|
|
return Builder.CreateShuffleVector(Map.getShadow(Shuffle->getOperand(0)),
|
|
Map.getShadow(Shuffle->getOperand(1)),
|
|
Shuffle->getShuffleMask());
|
|
// TODO: We could make aggregate object first class citizens. For now we
|
|
// just extend the extracted value.
|
|
if (auto *Extract = dyn_cast<ExtractValueInst>(&Inst))
|
|
return Builder.CreateFPExt(Extract, ExtendedVT);
|
|
|
|
if (auto *BC = dyn_cast<BitCastInst>(&Inst))
|
|
return Builder.CreateFPExt(BC, ExtendedVT);
|
|
|
|
report_fatal_error("Unimplemented support for " +
|
|
Twine(Inst.getOpcodeName()));
|
|
}
|
|
|
|
// Creates a shadow value for an instruction that defines a value of FT type.
|
|
// FT operands that do not already have shadow values are created recursively.
|
|
// The DFS is guaranteed to not loop as phis and arguments already have
|
|
// shadows.
|
|
void NumericalStabilitySanitizer::maybeCreateShadowValue(
|
|
Instruction &Root, const TargetLibraryInfo &TLI, ValueToShadowMap &Map) {
|
|
Type *VT = Root.getType();
|
|
Type *ExtendedVT = Config.getExtendedFPType(VT);
|
|
if (ExtendedVT == nullptr)
|
|
return; // Not an FT value.
|
|
|
|
if (Map.hasShadow(&Root))
|
|
return; // Shadow already exists.
|
|
|
|
assert(!isa<PHINode>(Root) && "phi nodes should already have shadows");
|
|
|
|
std::vector<Instruction *> DfsStack(1, &Root);
|
|
while (!DfsStack.empty()) {
|
|
// Ensure that all operands to the instruction have shadows before
|
|
// proceeding.
|
|
Instruction *I = DfsStack.back();
|
|
// The shadow for the instruction might have been created deeper in the DFS,
|
|
// see `forward_use_with_two_uses` test.
|
|
if (Map.hasShadow(I)) {
|
|
DfsStack.pop_back();
|
|
continue;
|
|
}
|
|
|
|
bool MissingShadow = false;
|
|
for (Value *Op : I->operands()) {
|
|
Type *VT = Op->getType();
|
|
if (!Config.getExtendedFPType(VT))
|
|
continue; // Not an FT value.
|
|
if (Map.hasShadow(Op))
|
|
continue; // Shadow is already available.
|
|
MissingShadow = true;
|
|
DfsStack.push_back(cast<Instruction>(Op));
|
|
}
|
|
if (MissingShadow)
|
|
continue; // Process operands and come back to this instruction later.
|
|
|
|
// All operands have shadows. Create a shadow for the current value.
|
|
Value *Shadow = createShadowValueWithOperandsAvailable(*I, TLI, Map);
|
|
Map.setShadow(*I, *Shadow);
|
|
DfsStack.pop_back();
|
|
}
|
|
}
|
|
|
|
// A floating-point store needs its value and type written to shadow memory.
|
|
void NumericalStabilitySanitizer::propagateFTStore(
|
|
StoreInst &Store, Type *VT, Type *ExtendedVT, const ValueToShadowMap &Map) {
|
|
Value *StoredValue = Store.getValueOperand();
|
|
IRBuilder<> Builder(&Store);
|
|
Builder.SetCurrentDebugLocation(Store.getDebugLoc());
|
|
const auto Extents = getMemoryExtentsOrDie(VT);
|
|
Value *ShadowPtr = Builder.CreateCall(
|
|
NsanGetShadowPtrForStore[Extents.ValueType],
|
|
{Store.getPointerOperand(), ConstantInt::get(IntptrTy, Extents.NumElts)});
|
|
|
|
Value *StoredShadow = Map.getShadow(StoredValue);
|
|
if (!Store.getParent()->getParent()->hasOptNone()) {
|
|
// Only check stores when optimizing, because non-optimized code generates
|
|
// too many stores to the stack, creating false positives.
|
|
if (ClCheckStores) {
|
|
StoredShadow = emitCheck(StoredValue, StoredShadow, Builder,
|
|
CheckLoc::makeStore(Store.getPointerOperand()));
|
|
++NumInstrumentedFTStores;
|
|
}
|
|
}
|
|
|
|
Builder.CreateAlignedStore(StoredShadow, ShadowPtr, Align(1),
|
|
Store.isVolatile());
|
|
}
|
|
|
|
// A non-ft store needs to invalidate shadow memory. Exceptions are:
|
|
// - memory transfers of floating-point data through other pointer types (llvm
|
|
// optimization passes transform `*(float*)a = *(float*)b` into
|
|
// `*(i32*)a = *(i32*)b` ). These have the same semantics as memcpy.
|
|
// - Writes of FT-sized constants. LLVM likes to do float stores as bitcasted
|
|
// ints. Note that this is not really necessary because if the value is
|
|
// unknown the framework will re-extend it on load anyway. It just felt
|
|
// easier to debug tests with vectors of FTs.
|
|
void NumericalStabilitySanitizer::propagateNonFTStore(
|
|
StoreInst &Store, Type *VT, const ValueToShadowMap &Map) {
|
|
Value *PtrOp = Store.getPointerOperand();
|
|
IRBuilder<> Builder(Store.getNextNode());
|
|
Builder.SetCurrentDebugLocation(Store.getDebugLoc());
|
|
Value *Dst = PtrOp;
|
|
TypeSize SlotSize = DL.getTypeStoreSize(VT);
|
|
assert(!SlotSize.isScalable() && "unsupported");
|
|
const auto LoadSizeBytes = SlotSize.getFixedValue();
|
|
Value *ValueSize = Constant::getIntegerValue(
|
|
IntptrTy, APInt(IntptrTy->getPrimitiveSizeInBits(), LoadSizeBytes));
|
|
|
|
++NumInstrumentedNonFTStores;
|
|
Value *StoredValue = Store.getValueOperand();
|
|
if (LoadInst *Load = dyn_cast<LoadInst>(StoredValue)) {
|
|
// TODO: Handle the case when the value is from a phi.
|
|
// This is a memory transfer with memcpy semantics. Copy the type and
|
|
// value from the source. Note that we cannot use __nsan_copy_values()
|
|
// here, because that will not work when there is a write to memory in
|
|
// between the load and the store, e.g. in the case of a swap.
|
|
Type *ShadowTypeIntTy = Type::getIntNTy(Context, 8 * LoadSizeBytes);
|
|
Type *ShadowValueIntTy =
|
|
Type::getIntNTy(Context, 8 * kShadowScale * LoadSizeBytes);
|
|
IRBuilder<> LoadBuilder(Load->getNextNode());
|
|
Builder.SetCurrentDebugLocation(Store.getDebugLoc());
|
|
Value *LoadSrc = Load->getPointerOperand();
|
|
// Read the shadow type and value at load time. The type has the same size
|
|
// as the FT value, the value has twice its size.
|
|
// TODO: cache them to avoid re-creating them when a load is used by
|
|
// several stores. Maybe create them like the FT shadows when a load is
|
|
// encountered.
|
|
Value *RawShadowType = LoadBuilder.CreateAlignedLoad(
|
|
ShadowTypeIntTy,
|
|
LoadBuilder.CreateCall(NsanGetRawShadowTypePtr, {LoadSrc}), Align(1),
|
|
/*isVolatile=*/false);
|
|
Value *RawShadowValue = LoadBuilder.CreateAlignedLoad(
|
|
ShadowValueIntTy,
|
|
LoadBuilder.CreateCall(NsanGetRawShadowPtr, {LoadSrc}), Align(1),
|
|
/*isVolatile=*/false);
|
|
|
|
// Write back the shadow type and value at store time.
|
|
Builder.CreateAlignedStore(
|
|
RawShadowType, Builder.CreateCall(NsanGetRawShadowTypePtr, {Dst}),
|
|
Align(1),
|
|
/*isVolatile=*/false);
|
|
Builder.CreateAlignedStore(RawShadowValue,
|
|
Builder.CreateCall(NsanGetRawShadowPtr, {Dst}),
|
|
Align(1),
|
|
/*isVolatile=*/false);
|
|
|
|
++NumInstrumentedNonFTMemcpyStores;
|
|
return;
|
|
}
|
|
// ClPropagateNonFTConstStoresAsFT is by default false.
|
|
if (Constant *C; ClPropagateNonFTConstStoresAsFT &&
|
|
(C = dyn_cast<Constant>(StoredValue))) {
|
|
// This might be a fp constant stored as an int. Bitcast and store if it has
|
|
// appropriate size.
|
|
Type *BitcastTy = nullptr; // The FT type to bitcast to.
|
|
if (auto *CInt = dyn_cast<ConstantInt>(C)) {
|
|
switch (CInt->getType()->getScalarSizeInBits()) {
|
|
case 32:
|
|
BitcastTy = Type::getFloatTy(Context);
|
|
break;
|
|
case 64:
|
|
BitcastTy = Type::getDoubleTy(Context);
|
|
break;
|
|
case 80:
|
|
BitcastTy = Type::getX86_FP80Ty(Context);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (auto *CDV = dyn_cast<ConstantDataVector>(C)) {
|
|
const int NumElements =
|
|
cast<VectorType>(CDV->getType())->getElementCount().getFixedValue();
|
|
switch (CDV->getType()->getScalarSizeInBits()) {
|
|
case 32:
|
|
BitcastTy =
|
|
VectorType::get(Type::getFloatTy(Context), NumElements, false);
|
|
break;
|
|
case 64:
|
|
BitcastTy =
|
|
VectorType::get(Type::getDoubleTy(Context), NumElements, false);
|
|
break;
|
|
case 80:
|
|
BitcastTy =
|
|
VectorType::get(Type::getX86_FP80Ty(Context), NumElements, false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (BitcastTy) {
|
|
const MemoryExtents Extents = getMemoryExtentsOrDie(BitcastTy);
|
|
Value *ShadowPtr = Builder.CreateCall(
|
|
NsanGetShadowPtrForStore[Extents.ValueType],
|
|
{PtrOp, ConstantInt::get(IntptrTy, Extents.NumElts)});
|
|
// Bitcast the integer value to the appropriate FT type and extend to 2FT.
|
|
Type *ExtVT = Config.getExtendedFPType(BitcastTy);
|
|
Value *Shadow =
|
|
Builder.CreateFPExt(Builder.CreateBitCast(C, BitcastTy), ExtVT);
|
|
Builder.CreateAlignedStore(Shadow, ShadowPtr, Align(1),
|
|
Store.isVolatile());
|
|
return;
|
|
}
|
|
}
|
|
// All other stores just reset the shadow value to unknown.
|
|
Builder.CreateCall(NsanSetUnknownFns.getFallback(), {Dst, ValueSize});
|
|
}
|
|
|
|
void NumericalStabilitySanitizer::propagateShadowValues(
|
|
Instruction &Inst, const TargetLibraryInfo &TLI,
|
|
const ValueToShadowMap &Map) {
|
|
if (auto *Store = dyn_cast<StoreInst>(&Inst)) {
|
|
Value *StoredValue = Store->getValueOperand();
|
|
Type *VT = StoredValue->getType();
|
|
Type *ExtendedVT = Config.getExtendedFPType(VT);
|
|
if (ExtendedVT == nullptr)
|
|
return propagateNonFTStore(*Store, VT, Map);
|
|
return propagateFTStore(*Store, VT, ExtendedVT, Map);
|
|
}
|
|
|
|
if (auto *FCmp = dyn_cast<FCmpInst>(&Inst)) {
|
|
emitFCmpCheck(*FCmp, Map);
|
|
return;
|
|
}
|
|
|
|
if (auto *CB = dyn_cast<CallBase>(&Inst)) {
|
|
maybeAddSuffixForNsanInterface(CB);
|
|
if (CallInst *CI = dyn_cast<CallInst>(&Inst))
|
|
maybeMarkSanitizerLibraryCallNoBuiltin(CI, &TLI);
|
|
if (MemIntrinsic *MI = dyn_cast<MemIntrinsic>(&Inst)) {
|
|
instrumentMemIntrinsic(MI);
|
|
return;
|
|
}
|
|
populateShadowStack(*CB, TLI, Map);
|
|
return;
|
|
}
|
|
|
|
if (auto *RetInst = dyn_cast<ReturnInst>(&Inst)) {
|
|
if (!ClCheckRet)
|
|
return;
|
|
|
|
Value *RV = RetInst->getReturnValue();
|
|
if (RV == nullptr)
|
|
return; // This is a `ret void`.
|
|
Type *VT = RV->getType();
|
|
Type *ExtendedVT = Config.getExtendedFPType(VT);
|
|
if (ExtendedVT == nullptr)
|
|
return; // Not an FT ret.
|
|
Value *RVShadow = Map.getShadow(RV);
|
|
IRBuilder<> Builder(RetInst);
|
|
|
|
RVShadow = emitCheck(RV, RVShadow, Builder, CheckLoc::makeRet());
|
|
++NumInstrumentedFTRets;
|
|
// Store tag.
|
|
Value *FnAddr =
|
|
Builder.CreatePtrToInt(Inst.getParent()->getParent(), IntptrTy);
|
|
Builder.CreateStore(FnAddr, NsanShadowRetTag);
|
|
// Store value.
|
|
Value *ShadowRetValPtr =
|
|
Builder.CreateConstGEP2_64(NsanShadowRetType, NsanShadowRetPtr, 0, 0);
|
|
Builder.CreateStore(RVShadow, ShadowRetValPtr);
|
|
return;
|
|
}
|
|
|
|
if (InsertValueInst *Insert = dyn_cast<InsertValueInst>(&Inst)) {
|
|
Value *V = Insert->getOperand(1);
|
|
Type *VT = V->getType();
|
|
Type *ExtendedVT = Config.getExtendedFPType(VT);
|
|
if (ExtendedVT == nullptr)
|
|
return;
|
|
IRBuilder<> Builder(Insert);
|
|
emitCheck(V, Map.getShadow(V), Builder, CheckLoc::makeInsert());
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Moves fast math flags from the function to individual instructions, and
|
|
// removes the attribute from the function.
|
|
// TODO: Make this controllable with a flag.
|
|
static void moveFastMathFlags(Function &F,
|
|
std::vector<Instruction *> &Instructions) {
|
|
FastMathFlags FMF;
|
|
#define MOVE_FLAG(attr, setter) \
|
|
if (F.getFnAttribute(attr).getValueAsString() == "true") { \
|
|
F.removeFnAttr(attr); \
|
|
FMF.set##setter(); \
|
|
}
|
|
MOVE_FLAG("unsafe-fp-math", Fast)
|
|
MOVE_FLAG("no-infs-fp-math", NoInfs)
|
|
MOVE_FLAG("no-nans-fp-math", NoNaNs)
|
|
MOVE_FLAG("no-signed-zeros-fp-math", NoSignedZeros)
|
|
#undef MOVE_FLAG
|
|
|
|
for (Instruction *I : Instructions)
|
|
if (isa<FPMathOperator>(I))
|
|
I->setFastMathFlags(FMF);
|
|
}
|
|
|
|
bool NumericalStabilitySanitizer::sanitizeFunction(
|
|
Function &F, const TargetLibraryInfo &TLI) {
|
|
if (!F.hasFnAttribute(Attribute::SanitizeNumericalStability) ||
|
|
F.isDeclaration())
|
|
return false;
|
|
|
|
// This is required to prevent instrumenting call to __nsan_init from within
|
|
// the module constructor.
|
|
if (F.getName() == kNsanModuleCtorName)
|
|
return false;
|
|
|
|
// The instrumentation maintains:
|
|
// - for each IR value `v` of floating-point (or vector floating-point) type
|
|
// FT, a shadow IR value `s(v)` with twice the precision 2FT (e.g.
|
|
// double for float and f128 for double).
|
|
// - A shadow memory, which stores `s(v)` for any `v` that has been stored,
|
|
// along with a shadow memory tag, which stores whether the value in the
|
|
// corresponding shadow memory is valid. Note that this might be
|
|
// incorrect if a non-instrumented function stores to memory, or if
|
|
// memory is stored to through a char pointer.
|
|
// - A shadow stack, which holds `s(v)` for any floating-point argument `v`
|
|
// of a call to an instrumented function. This allows
|
|
// instrumented functions to retrieve the shadow values for their
|
|
// arguments.
|
|
// Because instrumented functions can be called from non-instrumented
|
|
// functions, the stack needs to include a tag so that the instrumented
|
|
// function knows whether shadow values are available for their
|
|
// parameters (i.e. whether is was called by an instrumented function).
|
|
// When shadow arguments are not available, they have to be recreated by
|
|
// extending the precision of the non-shadow arguments to the non-shadow
|
|
// value. Non-instrumented functions do not modify (or even know about) the
|
|
// shadow stack. The shadow stack pointer is __nsan_shadow_args. The shadow
|
|
// stack tag is __nsan_shadow_args_tag. The tag is any unique identifier
|
|
// for the function (we use the address of the function). Both variables
|
|
// are thread local.
|
|
// Example:
|
|
// calls shadow stack tag shadow stack
|
|
// =======================================================================
|
|
// non_instrumented_1() 0 0
|
|
// |
|
|
// v
|
|
// instrumented_2(float a) 0 0
|
|
// |
|
|
// v
|
|
// instrumented_3(float b, double c) &instrumented_3 s(b),s(c)
|
|
// |
|
|
// v
|
|
// instrumented_4(float d) &instrumented_4 s(d)
|
|
// |
|
|
// v
|
|
// non_instrumented_5(float e) &non_instrumented_5 s(e)
|
|
// |
|
|
// v
|
|
// instrumented_6(float f) &non_instrumented_5 s(e)
|
|
//
|
|
// On entry, instrumented_2 checks whether the tag corresponds to its
|
|
// function ptr.
|
|
// Note that functions reset the tag to 0 after reading shadow parameters.
|
|
// This ensures that the function does not erroneously read invalid data if
|
|
// called twice in the same stack, once from an instrumented function and
|
|
// once from an uninstrumented one. For example, in the following example,
|
|
// resetting the tag in (A) ensures that (B) does not reuse the same the
|
|
// shadow arguments (which would be incorrect).
|
|
// instrumented_1(float a)
|
|
// |
|
|
// v
|
|
// instrumented_2(float b) (A)
|
|
// |
|
|
// v
|
|
// non_instrumented_3()
|
|
// |
|
|
// v
|
|
// instrumented_2(float b) (B)
|
|
//
|
|
// - A shadow return slot. Any function that returns a floating-point value
|
|
// places a shadow return value in __nsan_shadow_ret_val. Again, because
|
|
// we might be calling non-instrumented functions, this value is guarded
|
|
// by __nsan_shadow_ret_tag marker indicating which instrumented function
|
|
// placed the value in __nsan_shadow_ret_val, so that the caller can check
|
|
// that this corresponds to the callee. Both variables are thread local.
|
|
//
|
|
// For example, in the following example, the instrumentation in
|
|
// `instrumented_1` rejects the shadow return value from `instrumented_3`
|
|
// because is is not tagged as expected (`&instrumented_3` instead of
|
|
// `non_instrumented_2`):
|
|
//
|
|
// instrumented_1()
|
|
// |
|
|
// v
|
|
// float non_instrumented_2()
|
|
// |
|
|
// v
|
|
// float instrumented_3()
|
|
//
|
|
// Calls of known math functions (sin, cos, exp, ...) are duplicated to call
|
|
// their overload on the shadow type.
|
|
|
|
// Collect all instructions before processing, as creating shadow values
|
|
// creates new instructions inside the function.
|
|
std::vector<Instruction *> OriginalInstructions;
|
|
for (BasicBlock &BB : F)
|
|
for (Instruction &Inst : BB)
|
|
OriginalInstructions.emplace_back(&Inst);
|
|
|
|
moveFastMathFlags(F, OriginalInstructions);
|
|
ValueToShadowMap ValueToShadow(Config);
|
|
|
|
// In the first pass, we create shadow values for all FT function arguments
|
|
// and all phis. This ensures that the DFS of the next pass does not have
|
|
// any loops.
|
|
std::vector<PHINode *> OriginalPhis;
|
|
createShadowArguments(F, TLI, ValueToShadow);
|
|
for (Instruction *I : OriginalInstructions) {
|
|
if (PHINode *Phi = dyn_cast<PHINode>(I)) {
|
|
if (PHINode *Shadow = maybeCreateShadowPhi(*Phi, TLI)) {
|
|
OriginalPhis.push_back(Phi);
|
|
ValueToShadow.setShadow(*Phi, *Shadow);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create shadow values for all instructions creating FT values.
|
|
for (Instruction *I : OriginalInstructions)
|
|
maybeCreateShadowValue(*I, TLI, ValueToShadow);
|
|
|
|
// Propagate shadow values across stores, calls and rets.
|
|
for (Instruction *I : OriginalInstructions)
|
|
propagateShadowValues(*I, TLI, ValueToShadow);
|
|
|
|
// The last pass populates shadow phis with shadow values.
|
|
for (PHINode *Phi : OriginalPhis) {
|
|
PHINode *ShadowPhi = cast<PHINode>(ValueToShadow.getShadow(Phi));
|
|
for (unsigned I : seq(Phi->getNumOperands())) {
|
|
Value *V = Phi->getOperand(I);
|
|
Value *Shadow = ValueToShadow.getShadow(V);
|
|
BasicBlock *IncomingBB = Phi->getIncomingBlock(I);
|
|
// For some instructions (e.g. invoke), we create the shadow in a separate
|
|
// block, different from the block where the original value is created.
|
|
// In that case, the shadow phi might need to refer to this block instead
|
|
// of the original block.
|
|
// Note that this can only happen for instructions as constant shadows are
|
|
// always created in the same block.
|
|
ShadowPhi->addIncoming(Shadow, IncomingBB);
|
|
}
|
|
}
|
|
|
|
return !ValueToShadow.empty();
|
|
}
|
|
|
|
static uint64_t GetMemOpSize(Value *V) {
|
|
uint64_t OpSize = 0;
|
|
if (Constant *C = dyn_cast<Constant>(V)) {
|
|
auto *CInt = dyn_cast<ConstantInt>(C);
|
|
if (CInt && CInt->getValue().getBitWidth() <= 64)
|
|
OpSize = CInt->getValue().getZExtValue();
|
|
}
|
|
|
|
return OpSize;
|
|
}
|
|
|
|
// Instrument the memory intrinsics so that they properly modify the shadow
|
|
// memory.
|
|
bool NumericalStabilitySanitizer::instrumentMemIntrinsic(MemIntrinsic *MI) {
|
|
IRBuilder<> Builder(MI);
|
|
if (auto *M = dyn_cast<MemSetInst>(MI)) {
|
|
FunctionCallee SetUnknownFn =
|
|
NsanSetUnknownFns.getFunctionFor(GetMemOpSize(M->getArgOperand(2)));
|
|
if (SetUnknownFn.getFunctionType()->getNumParams() == 1)
|
|
Builder.CreateCall(SetUnknownFn, {/*Address=*/M->getArgOperand(0)});
|
|
else
|
|
Builder.CreateCall(SetUnknownFn,
|
|
{/*Address=*/M->getArgOperand(0),
|
|
/*Size=*/Builder.CreateIntCast(M->getArgOperand(2),
|
|
IntptrTy, false)});
|
|
|
|
} else if (auto *M = dyn_cast<MemTransferInst>(MI)) {
|
|
FunctionCallee CopyFn =
|
|
NsanCopyFns.getFunctionFor(GetMemOpSize(M->getArgOperand(2)));
|
|
|
|
if (CopyFn.getFunctionType()->getNumParams() == 2)
|
|
Builder.CreateCall(CopyFn, {/*Destination=*/M->getArgOperand(0),
|
|
/*Source=*/M->getArgOperand(1)});
|
|
else
|
|
Builder.CreateCall(CopyFn, {/*Destination=*/M->getArgOperand(0),
|
|
/*Source=*/M->getArgOperand(1),
|
|
/*Size=*/
|
|
Builder.CreateIntCast(M->getArgOperand(2),
|
|
IntptrTy, false)});
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NumericalStabilitySanitizer::maybeAddSuffixForNsanInterface(CallBase *CI) {
|
|
Function *Fn = CI->getCalledFunction();
|
|
if (Fn == nullptr)
|
|
return;
|
|
|
|
if (!Fn->getName().starts_with("__nsan_"))
|
|
return;
|
|
|
|
if (Fn->getName() == "__nsan_dump_shadow_mem") {
|
|
assert(CI->arg_size() == 4 &&
|
|
"invalid prototype for __nsan_dump_shadow_mem");
|
|
// __nsan_dump_shadow_mem requires an extra parameter with the dynamic
|
|
// configuration:
|
|
// (shadow_type_id_for_long_double << 16) | (shadow_type_id_for_double << 8)
|
|
// | shadow_type_id_for_double
|
|
const uint64_t shadow_value_type_ids =
|
|
(static_cast<size_t>(Config.byValueType(kLongDouble).getNsanTypeId())
|
|
<< 16) |
|
|
(static_cast<size_t>(Config.byValueType(kDouble).getNsanTypeId())
|
|
<< 8) |
|
|
static_cast<size_t>(Config.byValueType(kFloat).getNsanTypeId());
|
|
CI->setArgOperand(3, ConstantInt::get(IntptrTy, shadow_value_type_ids));
|
|
}
|
|
}
|