//===-- Instrumentor.cpp - Highly configurable instrumentation pass -------===// // // 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 // //===----------------------------------------------------------------------===// // // The implementation of the Instrumentor, a highly configurable instrumentation // pass. // //===----------------------------------------------------------------------===// #include "llvm/Transforms/IPO/Instrumentor.h" #include "llvm/Transforms/IPO/InstrumentorConfigFile.h" #include "llvm/Transforms/IPO/InstrumentorStubPrinter.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/iterator.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/Demangle/Demangle.h" #include "llvm/IR/Constant.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InstrTypes.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Metadata.h" #include "llvm/IR/Module.h" #include "llvm/IR/PassManager.h" #include "llvm/IR/Verifier.h" #include "llvm/IRReader/IRReader.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Regex.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Transforms/IPO/InstrumentorUtils.h" #include "llvm/Transforms/Utils/ModuleUtils.h" #include #include #include #include #include #include #include #include using namespace llvm; using namespace llvm::instrumentor; #define DEBUG_TYPE "instrumentor" namespace { /// The user option to specify an output JSON file to write the configuration. static cl::opt OutputConfigFile( "instrumentor-write-config-file", cl::desc( "Write the instrumentor configuration into the specified JSON file"), cl::init("")); /// The user option to specify input JSON files to read the configuration from. static cl::list ConfigFiles("instrumentor-read-config-files", cl::desc("Read the instrumentor configuration from the " "specified JSON files (comma separated)"), cl::ZeroOrMore, cl::CommaSeparated); /// The user option to specify an input file to read the configuration file /// paths from. static cl::opt ConfigPathsFile( "instrumentor-read-config-paths-file", cl::desc("Read the instrumentor configuration file " "paths from the specified file (newline separated)"), cl::init("")); /// Set the debug location, if not set, after changing the insertion point of /// the IR builder \p IRB. template void ensureDbgLoc(IRBuilderTy &IRB) { if (IRB.getCurrentDebugLocation()) return; auto *BB = IRB.GetInsertBlock(); if (auto *SP = BB->getParent()->getSubprogram()) IRB.SetCurrentDebugLocation(DILocation::get(BB->getContext(), 0, 0, SP)); } /// Attempt to cast \p V to type \p Ty using only bit-preserving casts. /// This ensures that floating-point values are converted via bitcast (not /// fptosi/fptoui) to preserve their exact bit representation. template Value *tryToCast(IRBTy &IRB, Value *V, Type *Ty, const DataLayout &DL, bool AllowTruncate = false) { if (!V) return Constant::getAllOnesValue(Ty); Type *VTy = V->getType(); if (VTy == Ty) return V; if (VTy->isAggregateType()) return V; TypeSize RequestedSize = DL.getTypeSizeInBits(Ty); TypeSize ValueSize = DL.getTypeSizeInBits(VTy); bool ShouldTruncate = RequestedSize < ValueSize; if (ShouldTruncate && !AllowTruncate) return V; if (ShouldTruncate && AllowTruncate) { // First convert to integer of the same size if needed. Value *IntV = V; if (VTy->isFloatingPointTy()) IntV = IRB.CreateBitCast(V, IRB.getIntNTy(ValueSize)); return tryToCast(IRB, IRB.CreateIntCast(IntV, IRB.getIntNTy(RequestedSize), /*IsSigned=*/false), Ty, DL, AllowTruncate); } if (VTy->isPointerTy() && Ty->isPointerTy()) return IRB.CreatePointerBitCastOrAddrSpaceCast(V, Ty); if (VTy->isIntegerTy() && Ty->isIntegerTy()) return IRB.CreateIntCast(V, Ty, /*IsSigned=*/false); // Use bit-preserving casts for floating-point values: convert float to int // of the same size via bitcast, then extend/truncate the integer if needed. if (VTy->isFloatingPointTy() && Ty->isIntOrPtrTy()) { return tryToCast(IRB, IRB.CreateBitCast(V, IRB.getIntNTy(ValueSize)), Ty, DL, AllowTruncate); } // When converting int to float, never use sitofp/uitofp as they perform value // conversion, not bit-preserving cast. if (VTy->isIntegerTy() && Ty->isFloatingPointTy()) { if (ValueSize == RequestedSize) return IRB.CreateBitCast(V, Ty); return tryToCast( IRB, IRB.CreateIntCast(V, IRB.getIntNTy(RequestedSize), /*IsSigned=*/false), Ty, DL, AllowTruncate); } return IRB.CreateBitOrPointerCast(V, Ty); } /// Get a constant integer/boolean of type \p IT and value \p Val. template Constant *getCI(Type *IT, Ty Val, bool IsSigned = false) { return ConstantInt::get(IT, Val, IsSigned); } /// The core of the instrumentor pass, which instruments the module as the /// instrumentation configuration mandates. class InstrumentorImpl final { public: /// Construct an instrumentor implementation using the configuration \p IConf. InstrumentorImpl(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, Module &M) : IConf(IConf), M(M), IIRB(IIRB) {} /// Instrument the module, public entry point. bool instrument(); // Reset the state to allow reuse of the instrumentor with a different // configuration. void clear() { InstChoicesPRE.clear(); InstChoicesPOST.clear(); ParsedFunctionRegex = Regex(); } private: /// Indicate if the module should be instrumented based on the target. bool shouldInstrumentTarget(); /// Indicate if the function \p Fn should be instrumented. bool shouldInstrumentFunction(Function &Fn); bool shouldInstrumentGlobalVariable(GlobalVariable &GV); /// Instrument instruction \p I if needed, and use the argument caches in \p /// ICaches. bool instrumentInstruction(Instruction &I, InstrumentationCaches &ICaches); /// Instrument function \p Fn. bool instrumentFunction(Function &Fn); bool instrumentModule(); /// The instrumentation opportunities for instructions indexed by /// their opcode. DenseMap InstChoicesPRE, InstChoicesPOST; /// The instrumentor configuration. InstrumentationConfig &IConf; /// The function regex filter, if any. Regex ParsedFunctionRegex; /// The underlying module. Module &M; protected: /// A special IR builder that keeps track of the inserted instructions. InstrumentorIRBuilderTy &IIRB; }; } // end anonymous namespace static Regex createRegex(StringRef Str, StringRef Name, LLVMContext &Ctx) { if (!Str.empty()) { Regex RX(Str); std::string ErrMsg; if (!RX.isValid(ErrMsg)) { Ctx.diagnose(DiagnosticInfoInstrumentation( Twine("failed to parse ") + Name + " regex: " + ErrMsg, DS_Error)); return Regex(); } return RX; } return Regex(); } bool InstrumentorImpl::shouldInstrumentTarget() { const Triple &T = M.getTargetTriple(); const bool IsGPU = T.isAMDGPU() || T.isNVPTX(); bool RegexMatches = true; Regex RX = createRegex(IConf.TargetRegex->getString(), "target", IIRB.Ctx); if (RX.isValid()) RegexMatches = RX.match(T.str()); // Only instrument the module if the target has to be instrumented. return ((IsGPU && IConf.GPUEnabled->getBool()) || (!IsGPU && IConf.HostEnabled->getBool())) && RegexMatches; } bool InstrumentorImpl::shouldInstrumentFunction(Function &Fn) { if (Fn.isDeclaration()) return false; bool RegexMatches = true; if (ParsedFunctionRegex.isValid()) RegexMatches = ParsedFunctionRegex.match(Fn.getName()); return (RegexMatches && !Fn.getName().starts_with(IConf.getRTName())) || Fn.hasFnAttribute("instrument"); } bool InstrumentorImpl::shouldInstrumentGlobalVariable(GlobalVariable &GV) { return !GV.getName().starts_with("llvm.") && !GV.getName().starts_with(IConf.getRTName()); } bool InstrumentorImpl::instrumentInstruction(Instruction &I, InstrumentationCaches &ICaches) { bool Changed = false; // Skip instrumentation instructions. if (IIRB.NewInsts.contains(&I)) return Changed; // Count epochs eagerly. ++IIRB.Epoch; Value *IPtr = &I; if (auto *IO = InstChoicesPRE.lookup(I.getOpcode())) { IIRB.IRB.SetInsertPoint(&I); ensureDbgLoc(IIRB.IRB); IO->instrument(IPtr, Changed, IConf, IIRB, ICaches); } if (auto *IO = InstChoicesPOST.lookup(I.getOpcode())) { IIRB.IRB.SetInsertPoint(I.getNextNode()); ensureDbgLoc(IIRB.IRB); IO->instrument(IPtr, Changed, IConf, IIRB, ICaches); } IIRB.returnAllocas(); return Changed; } bool InstrumentorImpl::instrumentFunction(Function &Fn) { bool Changed = false; if (!shouldInstrumentFunction(Fn)) return Changed; InstrumentationCaches ICaches; SmallVector FinalTIs; ReversePostOrderTraversal RPOT(&Fn); for (auto &It : RPOT) { for (auto &I : *It) Changed |= instrumentInstruction(I, ICaches); auto *TI = It->getTerminator(); if (!TI->getNumSuccessors()) FinalTIs.push_back(TI); } Value *FPtr = &Fn; for (auto &[Name, IO] : IConf.IChoices[InstrumentationLocation::FUNCTION_PRE]) { if (!IO->Enabled) continue; // Count epochs eagerly. ++IIRB.Epoch; IIRB.IRB.SetInsertPoint( cast(FPtr)->getEntryBlock().getFirstNonPHIOrDbgOrAlloca()); ensureDbgLoc(IIRB.IRB); IO->instrument(FPtr, Changed, IConf, IIRB, ICaches); IIRB.returnAllocas(); } for (auto &[Name, IO] : IConf.IChoices[InstrumentationLocation::FUNCTION_POST]) { if (!IO->Enabled) continue; // Count epochs eagerly. ++IIRB.Epoch; for (Instruction *FinalTI : FinalTIs) { IIRB.IRB.SetInsertPoint(FinalTI); ensureDbgLoc(IIRB.IRB); IO->instrument(FPtr, Changed, IConf, IIRB, ICaches); IIRB.returnAllocas(); } } return Changed; } bool InstrumentorImpl::instrumentModule() { SmallVector Globals; Globals.reserve(M.global_size()); for (GlobalVariable &GV : M.globals()) { // llvm.metadata contains globals such as llvm.used. if (GV.getSection() == "llvm.metadata" || GV.getName() == "llvm.global_dtors" || GV.getName() == "llvm.global_ctors") continue; Globals.push_back(&GV); } auto CreateYtor = [&](bool Ctor) { Function *YtorFn = Function::Create( FunctionType::get(IIRB.VoidTy, false), GlobalValue::PrivateLinkage, IConf.getRTName(Ctor ? "ctor" : "dtor", ""), M); auto *EntryBB = BasicBlock::Create(IIRB.Ctx, "entry", YtorFn); IIRB.IRB.SetInsertPoint(EntryBB, EntryBB->begin()); ensureDbgLoc(IIRB.IRB); IIRB.IRB.CreateRetVoid(); if (Ctor) appendToGlobalCtors(M, YtorFn, 1000); else appendToGlobalDtors(M, YtorFn, 1000); return YtorFn; }; InstrumentationCaches ICaches; Function *CtorFn = nullptr, *DtorFn = nullptr; bool Changed = false; for (auto Loc : {InstrumentationLocation::MODULE_PRE, InstrumentationLocation::MODULE_POST}) { bool IsPRE = InstrumentationLocation::isPRE(Loc); Function *&YtorFn = IsPRE ? CtorFn : DtorFn; for (auto &ChoiceIt : IConf.IChoices[Loc]) { auto *IO = ChoiceIt.second; if (!IO->Enabled) continue; if (!YtorFn) { YtorFn = CreateYtor(IsPRE); Changed = true; } IIRB.IRB.SetInsertPointPastAllocas(YtorFn); ensureDbgLoc(IIRB.IRB); Value *YtorPtr = YtorFn; // Count epochs eagerly. ++IIRB.Epoch; IO->instrument(YtorPtr, Changed, IConf, IIRB, ICaches); IIRB.returnAllocas(); } } for (auto Loc : {InstrumentationLocation::GLOBAL_PRE, InstrumentationLocation::GLOBAL_POST}) { bool IsPRE = InstrumentationLocation::isPRE(Loc); Function *&YtorFn = IsPRE ? CtorFn : DtorFn; for (auto &ChoiceIt : IConf.IChoices[Loc]) { auto *IO = ChoiceIt.second; if (!IO->Enabled) continue; if (!YtorFn) { YtorFn = CreateYtor(IsPRE); Changed = true; } for (GlobalVariable *GV : Globals) { if (!shouldInstrumentGlobalVariable(*GV)) continue; if (IsPRE) IIRB.IRB.SetInsertPoint(YtorFn->getEntryBlock().getTerminator()); else IIRB.IRB.SetInsertPointPastAllocas(YtorFn); ensureDbgLoc(IIRB.IRB); Value *GVPtr = GV; // Count epochs eagerly. ++IIRB.Epoch; IO->instrument(GVPtr, Changed, IConf, IIRB, ICaches); IIRB.returnAllocas(); } } } return Changed; } bool InstrumentorImpl::instrument() { bool Changed = false; if (!shouldInstrumentTarget()) return Changed; StringRef FunctionRegexStr = IConf.FunctionRegex->getString(); ParsedFunctionRegex = createRegex(FunctionRegexStr, "function", IIRB.Ctx); // Helper to register an IO for all its opcodes. auto RegisterForAllOpcodes = [](auto &InstChoices, InstrumentationOpportunity *IO) { ArrayRef Opcodes = IO->getAllOpcodes(); // Register for all opcodes. for (unsigned Opcode : Opcodes) InstChoices[Opcode] = IO; }; for (auto &[Name, IO] : IConf.IChoices[InstrumentationLocation::INSTRUCTION_PRE]) if (IO->Enabled) RegisterForAllOpcodes(InstChoicesPRE, IO); for (auto &[Name, IO] : IConf.IChoices[InstrumentationLocation::INSTRUCTION_POST]) if (IO->Enabled) RegisterForAllOpcodes(InstChoicesPOST, IO); Changed |= instrumentModule(); for (Function &Fn : M) Changed |= instrumentFunction(Fn); return Changed; } InstrumentorPass::InstrumentorPass(IntrusiveRefCntPtr FS, InstrumentationConfig *IC, InstrumentorIRBuilderTy *IIRB) : FS(FS), UserIConf(IC), UserIIRB(IIRB) { if (!FS) this->FS = vfs::getRealFileSystem(); } PreservedAnalyses InstrumentorPass::run(Module &M, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, bool ReadConfig) { bool Changed = false; InstrumentorImpl Impl(IConf, IIRB, M); // If this is a configuration driven run, iterate over all configurations // provided by the user, if not, use the config as is and run the instrumentor // once. if (ReadConfig) readConfigPathsFile(ConfigPathsFile, ConfigFiles, IIRB.Ctx, *FS); bool MultipleConfigs = ConfigFiles.size() > 1; unsigned Idx = 0; do { std::string ConfigFile = ReadConfig && !ConfigFiles.empty() ? ConfigFiles[Idx] : ""; // Initialize the config to the base state but keep the caches around. Impl.clear(); IConf.init(IIRB); if (!readConfigFromJSON(IConf, ConfigFile, IIRB.Ctx, *FS)) continue; writeConfigToJSON(IConf, MultipleConfigs ? OutputConfigFile + "." + std::to_string(Idx) : OutputConfigFile, IIRB.Ctx); printRuntimeStub(IConf, IConf.RuntimeStubsFile->getString(), IIRB.Ctx); Changed |= Impl.instrument(); } while (++Idx < ConfigFiles.size()); if (!Changed) return PreservedAnalyses::all(); return PreservedAnalyses::none(); } PreservedAnalyses InstrumentorPass::run(Module &M, ModuleAnalysisManager &MAM) { // Only create them if the user did not provide them. std::unique_ptr IConfInt( !UserIConf ? new InstrumentationConfig() : nullptr); std::unique_ptr IIRBInt( !UserIIRB ? new InstrumentorIRBuilderTy(M) : nullptr); auto *IConf = IConfInt ? IConfInt.get() : UserIConf; auto *IIRB = IIRBInt ? IIRBInt.get() : UserIIRB; auto PA = run(M, *IConf, *IIRB, !UserIConf); assert(!verifyModule(M, &errs())); return PA; } std::unique_ptr BaseConfigurationOption::createBoolOption(InstrumentationConfig &IConf, StringRef Name, StringRef Description, bool DefaultValue) { auto BCO = std::make_unique(Name, Description, BOOLEAN); BCO->setBool(DefaultValue); IConf.addBaseChoice(BCO.get()); return BCO; } std::unique_ptr BaseConfigurationOption::createStringOption(InstrumentationConfig &IConf, StringRef Name, StringRef Description, StringRef DefaultValue) { auto BCO = std::make_unique(Name, Description, STRING); BCO->setString(DefaultValue); IConf.addBaseChoice(BCO.get()); return BCO; } void InstrumentationConfig::populate(InstrumentorIRBuilderTy &IIRB) { /// List of all instrumentation opportunities. BasePointerIO::populate(*this, IIRB); ModuleIO::populate(*this, IIRB); GlobalVarIO::populate(*this, IIRB); FunctionIO::populate(*this, IIRB); AllocaIO::populate(*this, IIRB); UnreachableIO::populate(*this, IIRB); LoadIO::populate(*this, IIRB); StoreIO::populate(*this, IIRB); CastIO::populate(*this, IIRB); NumericIO::populate(*this, IIRB); } void InstrumentationConfig::addChoice(InstrumentationOpportunity &IO, LLVMContext &Ctx) { auto *&ICPtr = IChoices[IO.getLocationKind()][IO.getName()]; if (ICPtr) { Ctx.diagnose(DiagnosticInfoInstrumentation( Twine("registered two instrumentation opportunities for the same " "location (") + ICPtr->getName() + Twine(" vs ") + IO.getName() + Twine(")"), DS_Warning)); } ICPtr = &IO; } Value * InstrumentationConfig::getBasePointerInfo(Value &V, InstrumentorIRBuilderTy &IIRB) { Function *Fn = IIRB.IRB.GetInsertBlock()->getParent(); Value *Obj; { Value *&UnderlyingObj = UnderlyingObjsMap[&V]; if (!UnderlyingObj) UnderlyingObj = const_cast(getUnderlyingObjectAggressive(&V)); Obj = UnderlyingObj; } Value *&BPI = BasePointerInfoMap[{Obj, Fn}]; if (BPI) return BPI; auto *BPIO = IChoices[InstrumentationLocation::SPECIAL_VALUE]["base_pointer_info"]; if (!BPIO || !BPIO->Enabled) { IIRB.Ctx.diagnose(DiagnosticInfoInstrumentation( "Base pointer info disabled but required, passing nullptr.", DS_Warning)); return BPI = Constant::getNullValue(BPIO->getRetTy(IIRB.Ctx)); } IRBuilderBase::InsertPointGuard IP(IIRB.IRB); if (auto *BasePtrI = dyn_cast(Obj)) { std::optional IP = BasePtrI->getInsertionPointAfterDef(); if (IP) { IIRB.IRB.SetInsertPoint(*IP); } else { IIRB.Ctx.diagnose(DiagnosticInfoInstrumentation( "Base pointer info could not be placed, passing nullptr.", DS_Warning)); return BPI = Constant::getNullValue(BPIO->getRetTy(IIRB.Ctx)); } } else if (isa(Obj) || isa(Obj)) { IIRB.IRB.SetInsertPointPastAllocas(IIRB.IRB.GetInsertBlock()->getParent()); } else { LLVM_DEBUG(Obj->dump()); llvm_unreachable("Unexpected base pointer!"); } ensureDbgLoc(IIRB.IRB); // Use fresh caches for safety, as this function may be called from // another instrumentation opportunity. bool Changed; InstrumentationCaches ICaches; BPI = BPIO->instrument(Obj, Changed, *this, IIRB, ICaches); IIRB.returnAllocas(); if (!BPI) BPI = Constant::getNullValue(BPIO->getRetTy(IIRB.Ctx)); return BPI; } Value *InstrumentationOpportunity::getIdPre(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { return getCI(&Ty, getIdFromEpoch(IIRB.Epoch)); } Value *InstrumentationOpportunity::getIdPost(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { return getCI(&Ty, -getIdFromEpoch(IIRB.Epoch), /*IsSigned=*/true); } Value *InstrumentationOpportunity::forceCast(Value &V, Type &Ty, InstrumentorIRBuilderTy &IIRB) { if (V.getType()->isVoidTy()) return Ty.isVoidTy() ? &V : Constant::getNullValue(&Ty); return tryToCast(IIRB.IRB, &V, &Ty, IIRB.IRB.GetInsertBlock()->getDataLayout()); } Value *InstrumentationOpportunity::replaceValue(Value &V, Value &NewV, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { if (V.getType()->isVoidTy()) return &V; auto *NewVCasted = &NewV; if (auto *I = dyn_cast(&NewV)) { IRBuilderBase::InsertPointGuard IPG(IIRB.IRB); IIRB.IRB.SetInsertPoint(I->getNextNode()); ensureDbgLoc(IIRB.IRB); NewVCasted = tryToCast(IIRB.IRB, &NewV, V.getType(), IIRB.DL, /*AllowTruncate=*/true); } V.replaceUsesWithIf(NewVCasted, [&](Use &U) { if (IIRB.NewInsts.lookup(cast(U.getUser())) == IIRB.Epoch) return false; return !isa(U.getUser()) && !U.getUser()->isDroppable(); }); return &V; } IRTCallDescription::IRTCallDescription(InstrumentationOpportunity &IO, Type *RetTy) : IO(IO), RetTy(RetTy) { for (auto &It : IO.IRTArgs) { if (!It.Enabled) continue; NumReplaceableArgs += bool(It.Flags & IRTArg::REPLACABLE); MightRequireIndirection |= It.Flags & IRTArg::POTENTIALLY_INDIRECT; } if (NumReplaceableArgs > 1) MightRequireIndirection = RequiresIndirection = true; } FunctionType *IRTCallDescription::createLLVMSignature( InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, const DataLayout &DL, bool ForceIndirection) { assert(((ForceIndirection && MightRequireIndirection) || (!ForceIndirection && !RequiresIndirection)) && "Wrong indirection setting!"); SmallVector ParamTypes; for (auto &It : IO.IRTArgs) { if (!It.Enabled) continue; if (!ForceIndirection || !isPotentiallyIndirect(It)) { ParamTypes.push_back(It.Ty); if (!RetTy && NumReplaceableArgs == 1 && (It.Flags & IRTArg::REPLACABLE)) RetTy = It.Ty; continue; } // The indirection pointer and the size of the value. ParamTypes.push_back(IIRB.PtrTy); if (!(It.Flags & IRTArg::INDIRECT_HAS_SIZE)) ParamTypes.push_back(IIRB.Int32Ty); } if (!RetTy) RetTy = IIRB.VoidTy; return FunctionType::get(RetTy, ParamTypes, /*isVarArg=*/false); } CallInst *IRTCallDescription::createLLVMCall(Value *&V, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, const DataLayout &DL, InstrumentationCaches &ICaches) { SmallVector CallParams; IRBuilderBase::InsertPointGuard IRP(IIRB.IRB); auto IP = IIRB.IRB.GetInsertPoint(); bool ForceIndirection = RequiresIndirection; for (auto &It : IO.IRTArgs) { if (!It.Enabled) continue; auto *&Param = ICaches.DirectArgCache[{IIRB.Epoch, IO.getName(), It.Name}]; if (!Param || It.NoCache) // Avoid passing the caches to the getter. Param = It.GetterCB(*V, *It.Ty, IConf, IIRB); assert(Param); if (Param->getType()->isVoidTy()) { Param = Constant::getNullValue(It.Ty); } else if (Param->getType()->isAggregateType() || DL.getTypeSizeInBits(Param->getType()) > DL.getTypeSizeInBits(It.Ty)) { if (!isPotentiallyIndirect(It)) { IIRB.Ctx.diagnose(DiagnosticInfoInstrumentation( Twine("indirection needed for ") + It.Name + Twine(" in ") + IO.getName() + Twine(", but not indicated. Instrumentation is skipped"), DS_Warning)); return nullptr; } ForceIndirection = true; } else { Param = tryToCast(IIRB.IRB, Param, It.Ty, DL); } CallParams.push_back(Param); } if (ForceIndirection) { Function *Fn = IIRB.IRB.GetInsertBlock()->getParent(); unsigned Offset = 0; for (auto &It : IO.IRTArgs) { if (!It.Enabled) continue; if (!isPotentiallyIndirect(It)) { ++Offset; continue; } auto *&CallParam = CallParams[Offset++]; if (!(It.Flags & IRTArg::INDIRECT_HAS_SIZE)) { CallParams.insert(&CallParam + 1, IIRB.IRB.getInt32(DL.getTypeStoreSize( CallParam->getType()))); Offset += 1; } auto *&CachedParam = ICaches.IndirectArgCache[{IIRB.Epoch, IO.getName(), It.Name}]; if (CachedParam) { CallParam = CachedParam; continue; } auto *AI = IIRB.getAlloca(Fn, CallParam->getType()); IIRB.IRB.CreateStore(CallParam, AI); CallParam = CachedParam = AI; } } if (!ForceIndirection) IIRB.IRB.SetInsertPoint(IP); ensureDbgLoc(IIRB.IRB); auto *FnTy = createLLVMSignature(IConf, IIRB, DL, ForceIndirection); auto CompleteName = IConf.getRTName(IO.IP.isPRE() ? "pre_" : "post_", IO.getName(), ForceIndirection ? "_ind" : ""); auto FC = IIRB.IRB.GetInsertBlock()->getModule()->getOrInsertFunction( CompleteName, FnTy); auto *CI = IIRB.IRB.CreateCall(FC, CallParams); CI->addFnAttr(Attribute::get(IIRB.Ctx, Attribute::WillReturn)); for (unsigned I = 0, E = IO.IRTArgs.size(); I < E; ++I) { if (!IO.IRTArgs[I].Enabled) continue; if (!isReplacable(IO.IRTArgs[I])) continue; bool IsCustomReplaceable = IO.IRTArgs[I].Flags & IRTArg::REPLACABLE_CUSTOM; Value *NewValue = FnTy->isVoidTy() || IsCustomReplaceable ? ICaches.DirectArgCache[{IIRB.Epoch, IO.getName(), IO.IRTArgs[I].Name}] : CI; assert(NewValue); if (ForceIndirection && !IsCustomReplaceable && isPotentiallyIndirect(IO.IRTArgs[I])) { auto *Q = ICaches .IndirectArgCache[{IIRB.Epoch, IO.getName(), IO.IRTArgs[I].Name}]; NewValue = IIRB.IRB.CreateLoad(V->getType(), Q); } V = IO.IRTArgs[I].SetterCB(*V, *NewValue, IConf, IIRB); } return CI; } template constexpr static Value *getValue(Ty &ValueOrUse) { if constexpr (std::is_same::value) return ValueOrUse.get(); else return static_cast(&ValueOrUse); } template static Value *createValuePack(const Range &R, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto *Fn = IIRB.IRB.GetInsertBlock()->getParent(); auto *I32Ty = IIRB.IRB.getInt32Ty(); SmallVector ConstantValues; SmallVector> Values; SmallVector Types; for (auto &RE : R) { Value *V = getValue(RE); if (!V->getType()->isSized()) continue; auto VSize = IIRB.DL.getTypeAllocSize(V->getType()); ConstantValues.push_back(getCI(I32Ty, VSize)); Types.push_back(I32Ty); ConstantValues.push_back(getCI(I32Ty, V->getType()->getTypeID())); Types.push_back(I32Ty); if (uint32_t MisAlign = VSize % 8) { Types.push_back(ArrayType::get(IIRB.Int8Ty, 8 - MisAlign)); ConstantValues.push_back(ConstantArray::getNullValue(Types.back())); } Types.push_back(V->getType()); if (auto *C = dyn_cast(V)) { ConstantValues.push_back(C); continue; } Values.push_back({V, ConstantValues.size()}); ConstantValues.push_back(Constant::getNullValue(V->getType())); } if (Types.empty()) return ConstantPointerNull::get(IIRB.PtrTy); StructType *STy = StructType::get(Fn->getContext(), Types, /*isPacked=*/true); Constant *Initializer = ConstantStruct::get(STy, ConstantValues); GlobalVariable *&GV = IConf.ConstantGlobalsCache[Initializer]; if (!GV) GV = new GlobalVariable(*Fn->getParent(), STy, false, GlobalValue::InternalLinkage, Initializer, IConf.getRTName("", "value_pack")); auto *AI = IIRB.getAlloca(Fn, STy); IIRB.IRB.CreateMemCpy(AI, AI->getAlign(), GV, MaybeAlign(GV->getAlignment()), IIRB.DL.getTypeAllocSize(STy)); for (auto [Param, Idx] : Values) { auto *Ptr = IIRB.IRB.CreateStructGEP(STy, AI, Idx); IIRB.IRB.CreateStore(Param, Ptr); } return AI; } template static void readValuePack(const Range &R, Value &Pack, InstrumentorIRBuilderTy &IIRB, function_ref SetterCB) { auto *Fn = IIRB.IRB.GetInsertBlock()->getParent(); auto &DL = Fn->getDataLayout(); SmallVector ParameterValues; unsigned Offset = 0; for (const auto &[Idx, RE] : enumerate(R)) { Value *V = getValue(RE); if (!V->getType()->isSized()) continue; Offset += 8; auto VSize = DL.getTypeAllocSize(V->getType()); auto Padding = alignTo(VSize, 8) - VSize; Offset += Padding; auto *Ptr = IIRB.IRB.CreateConstInBoundsGEP1_32(IIRB.Int8Ty, &Pack, Offset); auto *NewV = IIRB.IRB.CreateLoad(V->getType(), Ptr); SetterCB(Idx, NewV); Offset += VSize; } } /// FunctionIO /// { void FunctionIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { using namespace std::placeholders; if (UserConfig) Config = *UserConfig; bool IsPRE = getLocationKind() == InstrumentationLocation::FUNCTION_PRE; if (Config.has(PassAddress)) IRTArgs.push_back(IRTArg(IIRB.PtrTy, "address", "The function address.", IRTArg::NONE, getFunctionAddress)); if (Config.has(PassName)) IRTArgs.push_back(IRTArg(IIRB.PtrTy, "name", "The function name.", IRTArg::STRING, getFunctionName)); if (Config.has(PassNumArguments)) IRTArgs.push_back( IRTArg(IIRB.Int32Ty, "num_arguments", "Number of function arguments (without varargs).", IRTArg::NONE, std::bind(&FunctionIO::getNumArguments, this, _1, _2, _3, _4))); if (Config.has(PassArguments)) IRTArgs.push_back(IRTArg( IIRB.PtrTy, "arguments", "Description of the arguments.", (IsPRE && Config.has(ReplaceArguments) ? IRTArg::REPLACABLE_CUSTOM : IRTArg::NONE) | IRTArg::VALUE_PACK, std::bind(&FunctionIO::getArguments, this, _1, _2, _3, _4), std::bind(&FunctionIO::setArguments, this, _1, _2, _3, _4))); if (Config.has(PassIsMain)) IRTArgs.push_back(IRTArg(IIRB.Int8Ty, "is_main", "Flag to indicate it is the main function.", IRTArg::NONE, isMainFunction)); addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } Value *FunctionIO::getFunctionAddress(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &Fn = cast(V); if (Fn.isIntrinsic()) return Constant::getNullValue(&Ty); return &V; } Value *FunctionIO::getFunctionName(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &Fn = cast(V); return IConf.getGlobalString(IConf.DemangleFunctionNames->getBool() ? demangle(Fn.getName()) : Fn.getName(), IIRB); } Value *FunctionIO::getNumArguments(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &Fn = cast(V); if (!Config.ArgFilter) return getCI(&Ty, Fn.arg_size()); auto FRange = make_filter_range(Fn.args(), Config.ArgFilter); return getCI(&Ty, std::distance(FRange.begin(), FRange.end())); } Value *FunctionIO::getArguments(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &Fn = cast(V); if (!Config.ArgFilter) return createValuePack(Fn.args(), IConf, IIRB); return createValuePack(make_filter_range(Fn.args(), Config.ArgFilter), IConf, IIRB); } Value *FunctionIO::setArguments(Value &V, Value &NewV, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &Fn = cast(V); auto *AIt = Fn.arg_begin(); auto CB = [&](int Idx, Value *ReplV) { while (Config.ArgFilter && !Config.ArgFilter(*AIt)) ++AIt; Fn.getArg(Idx)->replaceUsesWithIf(ReplV, [&](Use &U) { return IIRB.NewInsts.lookup(cast(U.getUser())) != IIRB.Epoch; }); ++AIt; }; if (!Config.ArgFilter) readValuePack(Fn.args(), NewV, IIRB, CB); else readValuePack(make_filter_range(Fn.args(), Config.ArgFilter), NewV, IIRB, CB); return &Fn; } Value *FunctionIO::isMainFunction(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &Fn = cast(V); return getCI(&Ty, Fn.getName() == "main"); } static Value *getOpcode(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &I = cast(V); return getCI(&Ty, I.getOpcode()); } static Value *getTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { return getCI(&Ty, V.getType()->getTypeID()); } static Value *getSize(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &I = cast(V); auto &DL = I.getDataLayout(); return getCI(&Ty, DL.getTypeStoreSize(V.getType())); } /// UnreachableIO ///{ void UnreachableIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { if (UserConfig) Config = *UserConfig; addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } ///} /// AllocaIO ///{ void AllocaIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { if (UserConfig) Config = *UserConfig; bool IsPRE = getLocationKind() == InstrumentationLocation::INSTRUCTION_PRE; if (!IsPRE && Config.has(PassAddress)) IRTArgs.push_back( IRTArg(IIRB.PtrTy, "address", "The allocated memory address.", Config.has(ReplaceAddress) ? IRTArg::REPLACABLE : IRTArg::NONE, InstrumentationOpportunity::getValue, InstrumentationOpportunity::replaceValue)); if (Config.has(PassSize)) IRTArgs.push_back(IRTArg( IIRB.Int64Ty, "size", "The allocation size.", (IsPRE && Config.has(ReplaceSize)) ? IRTArg::REPLACABLE : IRTArg::NONE, getSize, setSize)); if (Config.has(PassAlignment)) IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "alignment", "The allocation alignment.", IRTArg::NONE, getAlignment)); addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } Value *AllocaIO::getSize(Value &V, Type &Ty, InstrumentationConfig &IO, InstrumentorIRBuilderTy &IIRB) { auto &AI = cast(V); const DataLayout &DL = AI.getDataLayout(); Value *SizeValue = nullptr; TypeSize TypeSize = DL.getTypeAllocSize(AI.getAllocatedType()); if (TypeSize.isFixed()) { SizeValue = getCI(&Ty, TypeSize.getFixedValue()); } else { auto *NullPtr = ConstantPointerNull::get(AI.getType()); SizeValue = IIRB.IRB.CreatePtrToInt( IIRB.IRB.CreateGEP(AI.getAllocatedType(), NullPtr, {IIRB.IRB.getInt32(1)}), &Ty); } if (AI.isArrayAllocation()) SizeValue = IIRB.IRB.CreateMul( SizeValue, IIRB.IRB.CreateZExtOrBitCast(AI.getArraySize(), &Ty)); return SizeValue; } Value *AllocaIO::setSize(Value &V, Value &NewV, InstrumentationConfig &IO, InstrumentorIRBuilderTy &IIRB) { auto &AI = cast(V); const DataLayout &DL = AI.getDataLayout(); auto *NewAI = IIRB.IRB.CreateAlloca(IIRB.IRB.getInt8Ty(), DL.getAllocaAddrSpace(), &NewV); NewAI->setAlignment(AI.getAlign()); AI.replaceAllUsesWith(NewAI); IIRB.eraseLater(&AI); return NewAI; } Value *AllocaIO::getAlignment(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { return getCI(&Ty, cast(V).getAlign().value()); } ///} void StoreIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { if (UserConfig) Config = *UserConfig; bool IsPRE = getLocationKind() == InstrumentationLocation::INSTRUCTION_PRE; if (Config.has(PassPointer)) { IRTArgs.push_back( IRTArg(IIRB.PtrTy, "pointer", "The accessed pointer.", ((IsPRE && Config.has(ReplacePointer)) ? IRTArg::REPLACABLE : IRTArg::NONE), getPointer, setPointer)); } if (Config.has(PassPointerAS)) { IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "pointer_as", "The address space of the accessed pointer.", IRTArg::NONE, getPointerAS)); } if (Config.has(PassBasePointerInfo)) { IRTArgs.push_back(IRTArg(IIRB.PtrTy, "base_pointer_info", "The runtime provided base pointer info.", IRTArg::NONE, getBasePointerInfo)); } if (Config.has(PassStoredValue)) { IRTArgs.push_back( IRTArg(getValueType(IIRB), "value", "The stored value.", IRTArg::POTENTIALLY_INDIRECT | (Config.has(PassStoredValueSize) ? IRTArg::INDIRECT_HAS_SIZE : IRTArg::NONE), getValue)); } if (Config.has(PassStoredValueSize)) { IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "value_size", "The size of the stored value.", IRTArg::NONE, getValueSize)); } if (Config.has(PassAlignment)) { IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "alignment", "The known access alignment.", IRTArg::NONE, getAlignment)); } if (Config.has(PassValueTypeId)) { IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "value_type_id", "The type id of the stored value.", IRTArg::NONE, getValueTypeId)); } if (Config.has(PassAtomicityOrdering)) { IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "atomicity_ordering", "The atomicity ordering of the store.", IRTArg::NONE, getAtomicityOrdering)); } if (Config.has(PassSyncScopeId)) { IRTArgs.push_back(IRTArg(IIRB.Int8Ty, "sync_scope_id", "The sync scope id of the store.", IRTArg::NONE, getSyncScopeId)); } if (Config.has(PassIsVolatile)) { IRTArgs.push_back(IRTArg(IIRB.Int8Ty, "is_volatile", "Flag indicating a volatile store.", IRTArg::NONE, isVolatile)); } addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } Value *StoreIO::getPointer(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return SI.getPointerOperand(); } Value *StoreIO::setPointer(Value &V, Value &NewV, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); SI.setOperand(SI.getPointerOperandIndex(), &NewV); return &SI; } Value *StoreIO::getPointerAS(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return getCI(&Ty, SI.getPointerAddressSpace()); } Value *StoreIO::getBasePointerInfo(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return IConf.getBasePointerInfo(*SI.getPointerOperand(), IIRB); } Value *StoreIO::getValue(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return SI.getValueOperand(); } Value *StoreIO::getValueSize(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); auto &DL = SI.getDataLayout(); return getCI(&Ty, DL.getTypeStoreSize(SI.getValueOperand()->getType())); } Value *StoreIO::getAlignment(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return getCI(&Ty, SI.getAlign().value()); } Value *StoreIO::getValueTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return getCI(&Ty, SI.getValueOperand()->getType()->getTypeID()); } Value *StoreIO::getAtomicityOrdering(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return getCI(&Ty, uint64_t(SI.getOrdering())); } Value *StoreIO::getSyncScopeId(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return getCI(&Ty, uint64_t(SI.getSyncScopeID())); } Value *StoreIO::isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &SI = cast(V); return getCI(&Ty, SI.isVolatile()); } void LoadIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { bool IsPRE = getLocationKind() == InstrumentationLocation::INSTRUCTION_PRE; if (UserConfig) Config = *UserConfig; if (Config.has(PassPointer)) { IRTArgs.push_back( IRTArg(IIRB.PtrTy, "pointer", "The accessed pointer.", ((IsPRE && Config.has(ReplacePointer)) ? IRTArg::REPLACABLE : IRTArg::NONE), getPointer, setPointer)); } if (Config.has(PassPointerAS)) { IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "pointer_as", "The address space of the accessed pointer.", IRTArg::NONE, getPointerAS)); } if (Config.has(PassBasePointerInfo)) { IRTArgs.push_back(IRTArg(IIRB.PtrTy, "base_pointer_info", "The runtime provided base pointer info.", IRTArg::NONE, getBasePointerInfo)); } if (!IsPRE && Config.has(PassValue)) { IRTArgs.push_back( IRTArg(getValueType(IIRB), "value", "The loaded value.", Config.has(ReplaceValue) ? IRTArg::REPLACABLE | IRTArg::POTENTIALLY_INDIRECT | (Config.has(PassValueSize) ? IRTArg::INDIRECT_HAS_SIZE : IRTArg::NONE) : IRTArg::NONE, getValue, Config.has(ReplaceValue) ? replaceValue : nullptr)); } if (Config.has(PassValueSize)) { IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "value_size", "The size of the loaded value.", IRTArg::NONE, getValueSize)); } if (Config.has(PassAlignment)) { IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "alignment", "The known access alignment.", IRTArg::NONE, getAlignment)); } if (Config.has(PassValueTypeId)) { IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "value_type_id", "The type id of the loaded value.", IRTArg::NONE, getValueTypeId)); } if (Config.has(PassAtomicityOrdering)) { IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "atomicity_ordering", "The atomicity ordering of the load.", IRTArg::NONE, getAtomicityOrdering)); } if (Config.has(PassSyncScopeId)) { IRTArgs.push_back(IRTArg(IIRB.Int8Ty, "sync_scope_id", "The sync scope id of the load.", IRTArg::NONE, getSyncScopeId)); } if (Config.has(PassIsVolatile)) { IRTArgs.push_back(IRTArg(IIRB.Int8Ty, "is_volatile", "Flag indicating a volatile load.", IRTArg::NONE, isVolatile)); } addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } Value *LoadIO::getPointer(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); return LI.getPointerOperand(); } Value *LoadIO::setPointer(Value &V, Value &NewV, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); LI.setOperand(LI.getPointerOperandIndex(), &NewV); return &LI; } Value *LoadIO::getPointerAS(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); return getCI(&Ty, LI.getPointerAddressSpace()); } Value *LoadIO::getBasePointerInfo(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); return IConf.getBasePointerInfo(*LI.getPointerOperand(), IIRB); } Value *LoadIO::getValue(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { return &V; } Value *LoadIO::getValueSize(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); auto &DL = LI.getDataLayout(); return getCI(&Ty, DL.getTypeStoreSize(LI.getType())); } Value *LoadIO::getAlignment(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); return getCI(&Ty, LI.getAlign().value()); } Value *LoadIO::getValueTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); return getCI(&Ty, LI.getType()->getTypeID()); } Value *LoadIO::getAtomicityOrdering(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); return getCI(&Ty, uint64_t(LI.getOrdering())); } Value *LoadIO::getSyncScopeId(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); return getCI(&Ty, uint64_t(LI.getSyncScopeID())); } Value *LoadIO::isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &LI = cast(V); return getCI(&Ty, LI.isVolatile()); } void BasePointerIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { if (UserConfig) Config = *UserConfig; if (Config.has(PassPointer)) IRTArgs.push_back(IRTArg(IIRB.PtrTy, "base_pointer", "The base pointer in question.", IRTArg::REPLACABLE, getValue, setValueNoop)); if (Config.has(PassPointerKind)) IRTArgs.push_back(IRTArg( IIRB.Int32Ty, "base_pointer_kind", "The base pointer kind (argument, global, instruction, unknown).", IRTArg::NONE, getPointerKind)); addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } Value *BasePointerIO::getPointerKind(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { if (isa(V)) return getCI(&Ty, 0); if (isa(V)) return getCI(&Ty, 1); if (isa(V)) return getCI(&Ty, 2); return getCI(&Ty, 3); } void ModuleIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { if (UserConfig) Config = *UserConfig; if (Config.has(PassName)) IRTArgs.push_back(IRTArg(IIRB.PtrTy, "module_name", "The module/translation unit name.", IRTArg::STRING, getModuleName)); if (Config.has(PassTargetTriple)) IRTArgs.push_back(IRTArg(IIRB.PtrTy, "target_triple", "The target triple.", IRTArg::STRING, getTargetTriple)); addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } Value *ModuleIO::getModuleName(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { // V is a constructor or destructor of the module we can place code in. auto &Fn = cast(V); return IConf.getGlobalString(Fn.getParent()->getName(), IIRB); } Value *ModuleIO::getTargetTriple(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { // V is a constructor or destructor of the module we can place code in. auto &Fn = cast(V); return IConf.getGlobalString(Fn.getParent()->getTargetTriple().getTriple(), IIRB); } void GlobalVarIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { if (UserConfig) Config = *UserConfig; bool IsPRE = InstrumentationLocation::isPRE(getLocationKind()); if (Config.has(PassAddress)) IRTArgs.push_back(IRTArg( IIRB.PtrTy, "address", "The address of the global (replaceable for definitions).", IsPRE && Config.has(ReplaceAddress) ? IRTArg::REPLACABLE : IRTArg::NONE, getAddress, setAddress)); if (Config.has(PassAS)) IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "address_space", "The address space of the global.", IRTArg::NONE, getAS)); if (Config.has(PassDeclaredSize)) IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "declared_size", "The size of the declared type of the global.", IRTArg::NONE, getDeclaredSize)); if (Config.has(PassAlignment)) IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "alignment", "The allocation alignment.", IRTArg::NONE, getAlignment)); if (Config.has(PassName)) IRTArgs.push_back(IRTArg(IIRB.PtrTy, "name", "The name of the global.", IRTArg::STRING, getSymbolName)); if (Config.has(PassInitialValue)) IRTArgs.push_back(IRTArg( IIRB.Int64Ty, "initial_value", "The initial value of the global.", IRTArg::POTENTIALLY_INDIRECT | IRTArg::INDIRECT_HAS_SIZE, getInitialValue)); if (Config.has(PassIsConstant)) IRTArgs.push_back(IRTArg(IIRB.Int8Ty, "is_constant", "Flag to indicate constant globals.", IRTArg::NONE, isConstant)); if (Config.has(PassIsDefinition)) IRTArgs.push_back(IRTArg(IIRB.Int8Ty, "is_definition", "Flag to indicate global definitions.", IRTArg::NONE, isDefinition)); addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } Value *GlobalVarIO::getAddress(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); if (GV.getAddressSpace()) return ConstantExpr::getAddrSpaceCast(&GV, IIRB.PtrTy); return &GV; } Value *GlobalVarIO::setAddress(Value &V, Value &NewV, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); GlobalVariable *ShadowGV = nullptr; auto ShadowName = IConf.getRTName("shadow.", GV.getName()); auto &DL = GV.getDataLayout(); if (GV.isDeclaration()) { ShadowGV = new GlobalVariable(*GV.getParent(), GV.getType(), false, GlobalVariable::WeakODRLinkage, &GV, ShadowName, &GV, GV.getThreadLocalMode(), DL.getDefaultGlobalsAddressSpace()); } else { ShadowGV = new GlobalVariable( *GV.getParent(), NewV.getType(), false, GV.getLinkage(), PoisonValue::get(NewV.getType()), ShadowName, &GV); IIRB.IRB.CreateStore(&NewV, ShadowGV); } SmallVector Worklist(make_pointer_range(GV.uses())); SmallPtrSet Done; DenseMap, Instruction *> VMap; DenseMap ConstToInstMap; DenseMap ReloadMap; auto MakeInstForConst = [&](Use &U) { Instruction *&I = ConstToInstMap[U]; if (I) return; if (U == &GV) { } else if (auto *CE = dyn_cast(U)) { I = CE->getAsInstruction(); } }; auto InsertConsts = [&](Instruction *UserI, Use &UserU) { SmallVector> Worklist; auto *&Reload = ReloadMap[UserI->getFunction()]; if (!Reload) { Reload = new LoadInst( GV.getType(), ShadowGV, GV.getName() + ".shadow_load", UserI->getFunction()->getEntryBlock().getFirstNonPHIOrDbgOrAlloca()); IIRB.NewInsts.insert({Reload, IIRB.Epoch}); } Worklist.push_back({UserI, &UserU}); while (!Worklist.empty()) { auto [I, U] = Worklist.pop_back_val(); if (*U == &GV) { U->set(ReloadMap[I->getFunction()]); continue; } if (auto *CI = ConstToInstMap[*U]) { auto *CIClone = CI->clone(); IIRB.NewInsts.insert({CIClone, IIRB.Epoch}); if (auto *PHI = dyn_cast(I)) { auto *BB = PHI->getIncomingBlock(U->getOperandNo()); CIClone->insertBefore(BB->getTerminator()->getIterator()); } else { CIClone->insertBefore(I->getIterator()); } U->set(CIClone); for (auto &CICUse : CIClone->operands()) { Worklist.push_back({CIClone, &CICUse}); } } } }; SmallPtrSet Visited; while (!Worklist.empty()) { Use *U = Worklist.pop_back_val(); if (!Done.insert(U).second) continue; MakeInstForConst(*U); auto *I = dyn_cast(U->getUser()); if (!I) { append_range(Worklist, make_pointer_range(U->getUser()->uses())); continue; } if (IIRB.NewInsts.lookup(I) == IIRB.Epoch) continue; if (isa(I)) continue; if (auto *II = dyn_cast(I)) if (II->getIntrinsicID() == Intrinsic::eh_typeid_for) continue; if (I->getParent()) InsertConsts(I, *U); } for (auto &It : ConstToInstMap) if (It.second) It.second->deleteValue(); return &V; } Value *GlobalVarIO::getAS(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); return getCI(&Ty, GV.getAddressSpace()); } Value *GlobalVarIO::getAlignment(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); return getCI(&Ty, GV.getAlignment()); } Value *GlobalVarIO::getDeclaredSize(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); auto &DL = GV.getDataLayout(); return getCI(&Ty, DL.getTypeAllocSize(GV.getValueType())); } Value *GlobalVarIO::getSymbolName(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); return IConf.getGlobalString(GV.getName(), IIRB); } Value *GlobalVarIO::getInitialValue(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); return GV.hasInitializer() ? GV.getInitializer() : Constant::getNullValue(&Ty); } Value *GlobalVarIO::isConstant(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); return getCI(&Ty, GV.isConstant()); } Value *GlobalVarIO::isDefinition(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { GlobalVariable &GV = cast(V); return getCI(&Ty, !GV.isDeclaration()); } /// CastIO /// { void CastIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { if (UserConfig) Config = *UserConfig; bool IsPRE = getLocationKind() == InstrumentationLocation::INSTRUCTION_PRE; if (Config.has(PassInput)) IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "input", "Input value of the cast.", IRTArg::POTENTIALLY_INDIRECT, getInput)); if (Config.has(PassInputTypeId)) IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "input_type_id", "The type id of the input value.", IRTArg::NONE, getInputTypeId)); if (Config.has(PassInputSize)) IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "input_size", "The size of the input value.", IRTArg::NONE, getInputSize)); if (!IsPRE && Config.has(PassResult)) IRTArgs.push_back( IRTArg(IIRB.Int64Ty, "result", "Result of the cast.", IRTArg::REPLACABLE | IRTArg::POTENTIALLY_INDIRECT, getValue, Config.has(ReplaceResult) ? replaceValue : nullptr)); if (Config.has(PassResultTypeId)) IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "result_type_id", "The type id of the result value.", IRTArg::NONE, getResultTypeId)); if (Config.has(PassResultSize)) IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "result_size", "The size of the result value.", IRTArg::NONE, getResultSize)); if (Config.has(PassOpcode)) IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "opcode", "The opcode of the cast instruction.", IRTArg::NONE, getOpcode)); addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); } Value *CastIO::getInput(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &CI = cast(V); return CI.getOperand(0); } Value *CastIO::getInputTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &CI = cast(V); return getCI(&Ty, CI.getSrcTy()->getTypeID()); } Value *CastIO::getInputSize(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &CI = cast(V); auto &DL = CI.getDataLayout(); return getCI(&Ty, DL.getTypeStoreSize(CI.getSrcTy())); } Value *CastIO::getResultTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &CI = cast(V); return getCI(&Ty, CI.getDestTy()->getTypeID()); } Value *CastIO::getResultSize(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &CI = cast(V); auto &DL = CI.getDataLayout(); return getCI(&Ty, DL.getTypeStoreSize(CI.getDestTy())); } ///} Value *NumericIO::getLeft(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &I = cast(V); return I.getOperand(0); } Value *NumericIO::getRight(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { auto &I = cast(V); if (I.getNumOperands() > 1) return I.getOperand(1); else return PoisonValue::get(&Ty); } void NumericIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) { if (UserConfig) Config = UserConfig; bool IsPRE = getLocationKind() == InstrumentationLocation::INSTRUCTION_PRE; const auto ValArgOpts = IRTArg::POTENTIALLY_INDIRECT | (Config.has(PassSize) ? IRTArg::INDIRECT_HAS_SIZE : IRTArg::NONE); if (Config.has(PassTypeId)) IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "type_id", "The operation's type id.", IRTArg::NONE, getTypeId)); if (Config.has(PassSize)) IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "size", "The operation's type size.", IRTArg::NONE, getSize)); if (Config.has(PassOpcode)) IRTArgs.push_back(IRTArg(IIRB.Int32Ty, "opcode", "The instruction opcode.", IRTArg::NONE, getOpcode)); if (Config.has(PassLeft)) IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "left", "The operation's left operand.", ValArgOpts, getLeft)); if (Config.has(PassRight)) IRTArgs.push_back(IRTArg(IIRB.Int64Ty, "right", "The operation's right operand. This value is " "poison for unary operations.", ValArgOpts, getRight)); if (!IsPRE && Config.has(PassResult)) IRTArgs.push_back( IRTArg(IIRB.Int64Ty, "result", "Result of the operation.", IRTArg::REPLACABLE | ValArgOpts, getValue, Config.has(ReplaceResult) ? replaceValue : nullptr)); addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId)); IConf.addChoice(*this, IIRB.Ctx); }