diff options
author | Kevin Sala <salapenades1@llnl.gov> | 2025-06-10 12:03:23 -0700 |
---|---|---|
committer | Kevin Sala <salapenades1@llnl.gov> | 2025-07-06 22:01:14 -0700 |
commit | ee7e77f8a9a858a88697e292cf569d29bf4c8619 (patch) | |
tree | e92e30c221c718b5c49f5fa2ed46b1bd27c06318 | |
parent | 5acd5abfd92bf1be03960ec885e9b18ab30ee3f3 (diff) | |
download | llvm-users/kevinsala/instrumentor-base-pr.zip llvm-users/kevinsala/instrumentor-base-pr.tar.gz llvm-users/kevinsala/instrumentor-base-pr.tar.bz2 |
Fix most review commentsusers/kevinsala/instrumentor-base-pr
-rw-r--r-- | llvm/include/llvm/Transforms/IPO/Instrumentor.h | 519 | ||||
-rw-r--r-- | llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h | 8 | ||||
-rw-r--r-- | llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h | 172 | ||||
-rw-r--r-- | llvm/lib/Transforms/IPO/Instrumentor.cpp | 287 | ||||
-rw-r--r-- | llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp | 87 |
5 files changed, 680 insertions, 393 deletions
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h index 6fb5a06..26445d2 100644 --- a/llvm/include/llvm/Transforms/IPO/Instrumentor.h +++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h @@ -1,4 +1,4 @@ -//===- Transforms/IPO/Instrumentor.h --------------------------===// +//===- Transforms/IPO/Instrumentor.h --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -29,9 +29,9 @@ #include "llvm/Support/Allocator.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/StringSaver.h" +#include "llvm/Transforms/IPO/InstrumentorUtils.h" #include "llvm/Transforms/Utils/Instrumentation.h" -#include <bitset> #include <cstdint> #include <functional> #include <string> @@ -43,100 +43,17 @@ namespace instrumentor { struct InstrumentationConfig; struct InstrumentationOpportunity; -struct InstrumentorIRBuilderTy { - InstrumentorIRBuilderTy(Module &M, FunctionAnalysisManager &FAM) - : M(M), Ctx(M.getContext()), FAM(FAM), - IRB(Ctx, ConstantFolder(), - IRBuilderCallbackInserter( - [&](Instruction *I) { NewInsts[I] = Epoche; })) {} - - ~InstrumentorIRBuilderTy() { - for (auto *I : ToBeErased) { - if (!I->getType()->isVoidTy()) - I->replaceAllUsesWith(PoisonValue::get(I->getType())); - I->eraseFromParent(); - } - } - - /// Get a temporary alloca to communicate (large) values with the runtime. - AllocaInst *getAlloca(Function *Fn, Type *Ty, bool MatchType = false) { - const DataLayout &DL = Fn->getDataLayout(); - auto *&AllocaList = AllocaMap[{Fn, DL.getTypeAllocSize(Ty)}]; - if (!AllocaList) - AllocaList = new AllocaListTy; - AllocaInst *AI = nullptr; - for (auto *&ListAI : *AllocaList) { - if (MatchType && ListAI->getAllocatedType() != Ty) - continue; - AI = ListAI; - ListAI = *AllocaList->rbegin(); - break; - } - if (AI) - AllocaList->pop_back(); - else - AI = new AllocaInst(Ty, DL.getAllocaAddrSpace(), "", - Fn->getEntryBlock().begin()); - UsedAllocas[AI] = AllocaList; - return AI; - } - - /// Return the temporary allocas. - void returnAllocas() { - for (auto [AI, List] : UsedAllocas) - List->push_back(AI); - UsedAllocas.clear(); - } - - /// Commonly used values for IR inspection and creation. - ///{ - - Module &M; - - /// The underying LLVM context. - LLVMContext &Ctx; - - const DataLayout &DL = M.getDataLayout(); - - Type *VoidTy = Type::getVoidTy(Ctx); - Type *IntptrTy = M.getDataLayout().getIntPtrType(Ctx); - PointerType *PtrTy = PointerType::getUnqual(Ctx); - IntegerType *Int8Ty = Type::getInt8Ty(Ctx); - IntegerType *Int32Ty = Type::getInt32Ty(Ctx); - IntegerType *Int64Ty = Type::getInt64Ty(Ctx); - Constant *NullPtrVal = Constant::getNullValue(PtrTy); - ///} - - /// Mapping to remember temporary allocas for reuse. - using AllocaListTy = SmallVector<AllocaInst *>; - DenseMap<std::pair<Function *, unsigned>, AllocaListTy *> AllocaMap; - DenseMap<AllocaInst *, SmallVector<AllocaInst *> *> UsedAllocas; - - void eraseLater(Instruction *I) { ToBeErased.insert(I); } - SmallPtrSet<Instruction *, 32> ToBeErased; - - FunctionAnalysisManager &FAM; - - IRBuilder<ConstantFolder, IRBuilderCallbackInserter> IRB; - - /// Each instrumentation, i.a., of an instruction, is happening in a dedicated - /// epoche. The epoche allows to determine if instrumentation instructions - /// were already around, due to prior instrumentations, or have been - /// introduced to support the current instrumentation, i.a., compute - /// information about the current instruction. - unsigned Epoche = 0; - - /// A mapping from instrumentation instructions to the epoche they have been - /// created. - DenseMap<Instruction *, unsigned> NewInsts; -}; - +/// Callback type for getting/setting a value for a instrumented opportunity. +///{ using GetterCallbackTy = std::function<Value *( Value &, Type &, InstrumentationConfig &, InstrumentorIRBuilderTy &)>; using SetterCallbackTy = std::function<Value *( Value &, Value &, InstrumentationConfig &, InstrumentorIRBuilderTy &)>; +///} +/// Helper to represent an argument to a instrumentation runtime function. struct IRTArg { + /// Flags describing the possible properties of an argument. enum IRArgFlagTy { NONE = 0, STRING = 1 << 0, @@ -144,10 +61,10 @@ struct IRTArg { REPLACABLE_CUSTOM = 1 << 2, POTENTIALLY_INDIRECT = 1 << 3, INDIRECT_HAS_SIZE = 1 << 4, - LAST, }; + /// Construct an argument. IRTArg(Type *Ty, StringRef Name, StringRef Description, unsigned Flags, GetterCallbackTy GetterCB, SetterCallbackTy SetterCB = nullptr, bool Enabled = true, bool NoCache = false) @@ -155,49 +72,85 @@ struct IRTArg { Flags(Flags), GetterCB(std::move(GetterCB)), SetterCB(std::move(SetterCB)), NoCache(NoCache) {} + /// Whether the argument is enabled and should be passed to the function call. bool Enabled; + + /// The type of the argument. Type *Ty; + + /// A string with the name of the argument. StringRef Name; + + /// A string with the description of the argument. StringRef Description; + + /// The flags that describe the properties of the argument. Multiple flags may + /// be specified. unsigned Flags; + + /// The callback for getting the value of the argument. GetterCallbackTy GetterCB; + + /// The callback for consuming the output value of the argument. SetterCallbackTy SetterCB; - bool NoCache; -}; -struct InstrumentationCaches { - DenseMap<std::tuple<unsigned, StringRef, StringRef>, Value *> DirectArgCache; - DenseMap<std::tuple<unsigned, StringRef, StringRef>, Value *> - IndirectArgCache; + /// Whether the argument value can be cached between the PRE and POST calls. + bool NoCache; }; +/// Helper to represent an instrumentation runtime function that is related to +/// an instrumentation opportunity. struct IRTCallDescription { - IRTCallDescription(InstrumentationOpportunity &IConf, Type *RetTy = nullptr); + /// Construct an instrumentation function description linked to the \p IO + /// instrumentation opportunity and \p RetTy return type. + IRTCallDescription(InstrumentationOpportunity &IO, Type *RetTy = nullptr); + /// Create the type of the instrumentation function. FunctionType *createLLVMSignature(InstrumentationConfig &IConf, LLVMContext &Ctx, const DataLayout &DL, bool ForceIndirection); + + /// Create a call instruction that calls to the instrumentation function and + /// passes the corresponding arguments. CallInst *createLLVMCall(Value *&V, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, const DataLayout &DL, InstrumentationCaches &ICaches); + /// Return whether the \p IRTA argument can be replaced. bool isReplacable(IRTArg &IRTA) const { return (IRTA.Flags & (IRTArg::REPLACABLE | IRTArg::REPLACABLE_CUSTOM)); } + /// Return whether the function may have any indirect argument. bool isPotentiallyIndirect(IRTArg &IRTA) const { return ((IRTA.Flags & IRTArg::POTENTIALLY_INDIRECT) || ((IRTA.Flags & IRTArg::REPLACABLE) && NumReplaceableArgs > 1)); } + /// Whether the function requires indirection in some argument. bool RequiresIndirection = false; + + /// Whether any argument may require indirection. bool MightRequireIndirection = false; + + /// The number of arguments that can be replaced. unsigned NumReplaceableArgs = 0; + + /// The instrumentation opportunity which it is linked to. InstrumentationOpportunity &IO; + + /// The return type of the instrumentation function. Type *RetTy = nullptr; }; +/// Helper to represent an instrumentation location, which is composed of an +/// instrumentation opportunity type and a position. struct InstrumentationLocation { + /// The supported location kinds, which are composed of a opportunity type and + /// position. The PRE position indicates the instrumentation function call is + /// inserted before the instrumented event occurs. The POST position indicates + /// the instrumentation call is inserted after the event occurs. Some + /// opportunity types may only support one position. enum KindTy { MODULE_PRE, MODULE_POST, @@ -209,20 +162,26 @@ struct InstrumentationLocation { BASIC_BLOCK_POST, INSTRUCTION_PRE, INSTRUCTION_POST, - SPECIAL_VALUE, - Last = SPECIAL_VALUE, + Last = INSTRUCTION_POST, }; + /// Construct an instrumentation location that is not instrumenting an + /// instruction. InstrumentationLocation(KindTy Kind) : Kind(Kind) { assert(Kind != INSTRUCTION_PRE && Kind != INSTRUCTION_POST && "Opcode required!"); } + /// Construct an instrumentation location belonging to the instrumentation of + /// an instruction. InstrumentationLocation(unsigned Opcode, bool IsPRE) : Kind(IsPRE ? INSTRUCTION_PRE : INSTRUCTION_POST), Opcode(Opcode) {} + /// Return the type and position. KindTy getKind() const { return Kind; } + /// Return the string representation given a location kind. This is the string + /// used in the configuration file. static StringRef getKindStr(KindTy Kind) { switch (Kind) { case MODULE_PRE: @@ -245,11 +204,11 @@ struct InstrumentationLocation { return "instruction_pre"; case INSTRUCTION_POST: return "instruction_post"; - case SPECIAL_VALUE: - return "special_value"; } llvm_unreachable("Invalid kind!"); } + + /// Return the location kind described by a string. static KindTy getKindFromStr(StringRef S) { return StringSwitch<KindTy>(S) .Case("module_pre", MODULE_PRE) @@ -262,10 +221,10 @@ struct InstrumentationLocation { .Case("basic_block_post", BASIC_BLOCK_POST) .Case("instruction_pre", INSTRUCTION_PRE) .Case("instruction_post", INSTRUCTION_POST) - .Case("special_value", SPECIAL_VALUE) .Default(Last); } + /// Return whether a location kind is positioned before the event occurs. static bool isPRE(KindTy Kind) { switch (Kind) { case MODULE_PRE: @@ -279,13 +238,16 @@ struct InstrumentationLocation { case FUNCTION_POST: case BASIC_BLOCK_POST: case INSTRUCTION_POST: - case SPECIAL_VALUE: return false; } llvm_unreachable("Invalid kind!"); } + + /// Return whether the instrumentation location is before the event occurs. bool isPRE() const { return isPRE(Kind); } + /// Get the opcode of the instruction instrumentation location. This function + /// may not be called by a non-instruction instrumentation location. unsigned getOpcode() const { assert((Kind == INSTRUCTION_PRE || Kind == INSTRUCTION_POST) && "Expected instruction!"); @@ -293,95 +255,118 @@ struct InstrumentationLocation { } private: + /// The kind (type and position) of the instrumentation location. const KindTy Kind; + + /// The opcode for instruction instrumentation locations. const unsigned Opcode = -1; }; -struct BaseConfigurationOpportunity { +/// An option for the base configuration. +struct BaseConfigurationOption { + /// The possible types of options. enum KindTy { STRING, BOOLEAN, }; - static BaseConfigurationOpportunity *getBoolOption(InstrumentationConfig &IC, - StringRef Name, - StringRef Description, - bool B); - static BaseConfigurationOpportunity * - getStringOption(InstrumentationConfig &IC, StringRef Name, - StringRef Description, StringRef Value); + /// Create a boolean option with \p Name name, \p Description description and + /// \p DefaultValue as boolean default value. + static BaseConfigurationOption *getBoolOption(InstrumentationConfig &IC, + StringRef Name, + StringRef Description, + bool DefaultValue); + + /// Create a string option with \p Name name, \p Description description and + /// \p DefaultValue as string default value. + static BaseConfigurationOption *getStringOption(InstrumentationConfig &IC, + StringRef Name, + StringRef Description, + StringRef DefaultValue); + + /// Helper union that holds any possible option type. union ValueTy { - bool B; - int64_t I; - StringRef S; + bool Bool; + StringRef String; }; + /// Set and get of the boolean value. Only valid if it is a boolean option. + ///{ void setBool(bool B) { assert(Kind == BOOLEAN && "Not a boolean!"); - V.B = B; + Value.Bool = B; } bool getBool() const { assert(Kind == BOOLEAN && "Not a boolean!"); - return V.B; + return Value.Bool; } + ///} + + /// Set and get the string value. Only valid if it is a boolean option. + ///{ void setString(StringRef S) { assert(Kind == STRING && "Not a string!"); - V.S = S; + Value.String = S; } StringRef getString() const { assert(Kind == STRING && "Not a string!"); - return V.S; + return Value.String; } + ///} + /// The information of the option. + ///{ StringRef Name; StringRef Description; KindTy Kind; - ValueTy V = {0}; + ValueTy Value = {0}; + ///} }; -struct InstrumentorIRBuilderTy; +/// The class that contains the configuration for the instrumentor. It holds the +/// information for each instrumented opportunity, including the base +/// configuration options. Another class may inherit from this one to modify the +/// default behavior. struct InstrumentationConfig { virtual ~InstrumentationConfig() {} + /// Construct an instrumentation configuration with the base options. InstrumentationConfig() : SS(StringAllocator) { - RuntimePrefix = BaseConfigurationOpportunity::getStringOption( + RuntimePrefix = BaseConfigurationOption::getStringOption( *this, "runtime_prefix", "The runtime API prefix.", "__instrumentor_"); - TargetRegex = BaseConfigurationOpportunity::getStringOption( + TargetRegex = BaseConfigurationOption::getStringOption( *this, "target_regex", "Regular expression to be matched against the module target. " "Only targets that match this regex will be instrumented", ""); - HostEnabled = BaseConfigurationOpportunity::getBoolOption( + HostEnabled = BaseConfigurationOption::getBoolOption( *this, "host_enabled", "Instrument non-GPU targets", true); - GPUEnabled = BaseConfigurationOpportunity::getBoolOption( + GPUEnabled = BaseConfigurationOption::getBoolOption( *this, "gpu_enabled", "Instrument GPU targets", true); } - bool ReadConfig = true; - + /// Populate the instrumentation opportunities. virtual void populate(InstrumentorIRBuilderTy &IIRB); + + /// Get the runtime prefix for the instrumentation runtime functions. StringRef getRTName() const { return RuntimePrefix->getString(); } + /// Get the instrumentation function name. std::string getRTName(StringRef Prefix, StringRef Name, StringRef Suffix1 = "", StringRef Suffix2 = "") const { return (getRTName() + Prefix + Name + Suffix1 + Suffix2).str(); } - void addBaseChoice(BaseConfigurationOpportunity *BCO) { - BaseConfigurationOpportunities.push_back(BCO); + /// Add the base configuration option \p BCO into the list of base options. + void addBaseChoice(BaseConfigurationOption *BCO) { + BaseConfigurationOptions.push_back(BCO); } - SmallVector<BaseConfigurationOpportunity *> BaseConfigurationOpportunities; - - BaseConfigurationOpportunity *RuntimePrefix; - BaseConfigurationOpportunity *TargetRegex; - BaseConfigurationOpportunity *HostEnabled; - BaseConfigurationOpportunity *GPUEnabled; - EnumeratedArray<StringMap<InstrumentationOpportunity *>, - InstrumentationLocation::KindTy> - IChoices; - void addChoice(InstrumentationOpportunity &IO); + /// Register instrumentation opportunity \p IO. + void addChoice(InstrumentationOpportunity &IO, LLVMContext &Ctx); + /// Allocate an object of type \p Ty using a bump allocator and construct it + /// with the \p Args arguments. The object may not be freed manually. template <typename Ty, typename... ArgsTy> static Ty *allocate(ArgsTy &&...Args) { static SpecificBumpPtrAllocator<Ty> Allocator; @@ -390,31 +375,47 @@ struct InstrumentationConfig { return Obj; } - BumpPtrAllocator StringAllocator; - StringSaver SS; -}; + /// The list of enabled base configuration options. + SmallVector<BaseConfigurationOption *> BaseConfigurationOptions; -template <typename EnumTy> struct BaseConfigTy { - std::bitset<static_cast<int>(EnumTy::NumConfig)> Options; + /// The base configuration options. + BaseConfigurationOption *RuntimePrefix; + BaseConfigurationOption *TargetRegex; + BaseConfigurationOption *HostEnabled; + BaseConfigurationOption *GPUEnabled; - BaseConfigTy(bool Enable = true) { - if (Enable) - Options.set(); - } + /// The map registered instrumentation opportunities. The map is indexed by + /// the instrumentation location kind and then by the opportunity name. Notice + /// that an instrumentation location may have more than one instrumentation + /// opportunity registered. + EnumeratedArray<StringMap<InstrumentationOpportunity *>, + InstrumentationLocation::KindTy> + IChoices; - bool has(EnumTy Opt) const { return Options.test(static_cast<int>(Opt)); } - void set(EnumTy Opt, bool Value = true) { - Options.set(static_cast<int>(Opt), Value); - } + /// Utilities for allocating and building strings. + ///{ + BumpPtrAllocator StringAllocator; + StringSaver SS; + ///} }; +/// Base class for instrumentation opportunities. All opportunities should +/// inherit from this class and implement the virtual class members. struct InstrumentationOpportunity { - InstrumentationOpportunity(const InstrumentationLocation IP) : IP(IP) {} virtual ~InstrumentationOpportunity() {} + /// Construct an opportunity with location \p IP. + InstrumentationOpportunity(const InstrumentationLocation IP) : IP(IP) {} + + /// The instrumentation location of the opportunity. InstrumentationLocation IP; + /// The list of possible arguments for the instrumentation runtime function. + /// The order within the array determines the order of arguments. Arguments + /// may be disabled and will not be passed to the function call. SmallVector<IRTArg> IRTArgs; + + /// Whether the opportunity is enabled. bool Enabled = true; /// Helpers to cast values, pass them to the runtime, and replace them. To be @@ -425,12 +426,13 @@ struct InstrumentationOpportunity { InstrumentorIRBuilderTy &IIRB) { return forceCast(V, Ty, IIRB); } - static Value *replaceValue(Value &V, Value &NewV, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB); ///} + /// Instrument the value \p V using the configuration \p IConf, and + /// potentially, the caches \p ICaches. virtual Value *instrument(Value *&V, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, InstrumentationCaches &ICaches) { @@ -443,62 +445,89 @@ struct InstrumentationOpportunity { return CI; } + /// Get the return type for the instrumentation runtime function. virtual Type *getRetTy(LLVMContext &Ctx) const { return nullptr; } + + /// Get the name of the instrumentation opportunity. virtual StringRef getName() const = 0; + /// Get the opcode of the instruction instrumentation opportunity. Only valid + /// if it is instruction instrumentation. unsigned getOpcode() const { return IP.getOpcode(); } + + /// Get the location kind of the instrumentation opportunity. InstrumentationLocation::KindTy getLocationKind() const { return IP.getKind(); } /// An optional callback that takes the value that is about to be /// instrumented and can return false if it should be skipped. + ///{ using CallbackTy = std::function<bool(Value &)>; - CallbackTy CB = nullptr; + ///} - static Value *getIdPre(Value &V, Type &Ty, InstrumentationConfig &IConf, - InstrumentorIRBuilderTy &IIRB); - static Value *getIdPost(Value &V, Type &Ty, InstrumentationConfig &IConf, - InstrumentorIRBuilderTy &IIRB); - + /// Add arguments available in all instrumentation opportunities. void addCommonArgs(InstrumentationConfig &IConf, LLVMContext &Ctx, bool PassId) { const auto CB = IP.isPRE() ? getIdPre : getIdPost; - if (PassId) + if (PassId) { IRTArgs.push_back( IRTArg(IntegerType::getInt32Ty(Ctx), "id", "A unique ID associated with the given instrumentor call", IRTArg::NONE, CB, nullptr, true, true)); + } } - static int32_t getIdFromEpoche(uint32_t Epoche) { - static DenseMap<uint32_t, int32_t> EpocheIdMap; + /// Get the opportunity identifier for the pre and post positions. + ///{ + static Value *getIdPre(Value &V, Type &Ty, InstrumentationConfig &IConf, + InstrumentorIRBuilderTy &IIRB); + static Value *getIdPost(Value &V, Type &Ty, InstrumentationConfig &IConf, + InstrumentorIRBuilderTy &IIRB); + ///} + + /// Compute the opportunity identifier for the current instrumentation epoch + /// \p CurrentEpoch. The identifiers are assigned consecutively as the epoch + /// advances. Epochs may have no identifier assigned (e.g., because no id was + /// requested). This function always returns the same identifier when called + /// multiple times with the same epoch. + static int32_t getIdFromEpoch(uint32_t CurrentEpoch) { + static DenseMap<uint32_t, int32_t> EpochIdMap; static int32_t GlobalId = 0; - int32_t &EpochId = EpocheIdMap[Epoche]; + int32_t &EpochId = EpochIdMap[CurrentEpoch]; if (EpochId == 0) EpochId = ++GlobalId; return EpochId; } }; +/// The base instrumentation opportunity class for instruction opportunities. +/// Each instruction opportunity should inherit from this class and implement +/// the virtual class members. template <unsigned Opcode> struct InstructionIO : public InstrumentationOpportunity { - InstructionIO(bool IsPRE) - : InstrumentationOpportunity(InstrumentationLocation(Opcode, IsPRE)) {} virtual ~InstructionIO() {} - unsigned getOpcode() const { return Opcode; } + /// Construct an instruction opportunity. + InstructionIO(bool IsPRE) + : InstrumentationOpportunity(InstrumentationLocation(Opcode, IsPRE)) {} + /// Get the name of the instruction. StringRef getName() const override { return Instruction::getOpcodeName(Opcode); } }; +/// The instrumentation opportunity for store instructions. struct StoreIO : public InstructionIO<Instruction::Store> { - StoreIO(bool IsPRE) : InstructionIO(IsPRE) {} virtual ~StoreIO() {}; + /// Construct a store instruction opportunity. + StoreIO(bool IsPRE) : InstructionIO(IsPRE) {} + + /// The selector of arguments for store opportunities. + ///{ enum ConfigKind { PassPointer = 0, ReplacePointer, @@ -514,65 +543,23 @@ struct StoreIO : public InstructionIO<Instruction::Store> { NumConfig, }; + using ConfigTy = BaseConfigTy<ConfigKind>; + ConfigTy Config; + ///} + + /// Get the type of the stored value. virtual Type *getValueType(LLVMContext &Ctx) const { return IntegerType::getInt64Ty(Ctx); } - using ConfigTy = BaseConfigTy<ConfigKind>; - ConfigTy Config; - + /// Initialize the store opportunity using the instrumentation config \p IConf + /// and the user config \p UserConfig. void init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, - ConfigTy *UserConfig = nullptr) { - 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(PassStoredValue)) - IRTArgs.push_back( - IRTArg(getValueType(IIRB.Ctx), "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); - } + ConfigTy *UserConfig = nullptr); + /// Getters and setters for the arguments of the instrumentation function for + /// the store opportunity. + ///{ static Value *getPointer(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB); static Value *setPointer(Value &V, Value &NewV, InstrumentationConfig &IConf, @@ -594,7 +581,11 @@ struct StoreIO : public InstructionIO<Instruction::Store> { InstrumentorIRBuilderTy &IIRB); static Value *isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB); + ///} + /// Create the store opportunities for pre and post positions. The + /// opportunities are also initialized with the arguments for their + /// instrumentation calls. static void populate(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { for (auto IsPRE : {true, false}) { @@ -604,10 +595,15 @@ struct StoreIO : public InstructionIO<Instruction::Store> { } }; +/// The instrumentation opportunity for load instructions. struct LoadIO : public InstructionIO<Instruction::Load> { - LoadIO(bool IsPRE) : InstructionIO(IsPRE) {} virtual ~LoadIO() {}; + /// Construct a load opportunity. + LoadIO(bool IsPRE) : InstructionIO(IsPRE) {} + + /// The selector of arguments for load opportunities. + ///{ enum ConfigKind { PassPointer = 0, ReplacePointer, @@ -624,65 +620,23 @@ struct LoadIO : public InstructionIO<Instruction::Load> { NumConfig, }; + using ConfigTy = BaseConfigTy<ConfigKind>; + ConfigTy Config; + ///} + + /// Get the type of the loaded value. virtual Type *getValueType(LLVMContext &Ctx) const { return IntegerType::getInt64Ty(Ctx); } - using ConfigTy = BaseConfigTy<ConfigKind>; - ConfigTy Config; - + /// Initialize the load opportunity using the instrumentation config \p IConf + /// and the user config \p UserConfig. void init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, - ConfigTy *UserConfig = nullptr) { - 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 (!IsPRE && Config.has(PassValue)) - IRTArgs.push_back(IRTArg( - getValueType(IIRB.Ctx), "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); - } + ConfigTy *UserConfig = nullptr); + /// Getters and setters for the arguments of the instrumentation function for + /// the load opportunity. + ///{ static Value *getPointer(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB); static Value *setPointer(Value &V, Value &NewV, InstrumentationConfig &IConf, @@ -704,7 +658,9 @@ struct LoadIO : public InstructionIO<Instruction::Load> { InstrumentorIRBuilderTy &IIRB); static Value *isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB); + ///} + /// Create the store opportunities for PRE and POST positions. static void populate(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { for (auto IsPRE : {true, false}) { @@ -716,17 +672,24 @@ struct LoadIO : public InstructionIO<Instruction::Load> { } // namespace instrumentor +/// The Instrumentor pass. class InstrumentorPass : public PassInfoMixin<InstrumentorPass> { using InstrumentationConfig = instrumentor::InstrumentationConfig; using InstrumentorIRBuilderTy = instrumentor::InstrumentorIRBuilderTy; + + /// The configuration and IR builder provided by the user. InstrumentationConfig *UserIConf; InstrumentorIRBuilderTy *UserIIRB; - PreservedAnalyses run(Module &M, FunctionAnalysisManager &FAM, - InstrumentationConfig &IConf, - InstrumentorIRBuilderTy &IIRB); + PreservedAnalyses run(Module &M, InstrumentationConfig &IConf, + InstrumentorIRBuilderTy &IIRB, bool ReadConfig); public: + /// Construct an instrumentor pass that will use the instrumentation + /// configuration \p IC and the IR builder \p IIRB. If an IR builder is not + /// provided, a default builder is used. When the configuration is not + /// provided, it is read from the config file if available and otherwise a + /// default configuration is used. InstrumentorPass(InstrumentationConfig *IC = nullptr, InstrumentorIRBuilderTy *IIRB = nullptr) : UserIConf(IC), UserIIRB(IIRB) {} diff --git a/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h b/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h index 370e3b7..155df19 100644 --- a/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h +++ b/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h @@ -19,9 +19,13 @@ namespace llvm { namespace instrumentor { -void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile); +/// Write the configuration in /p IConf to the file with path \p OutputFile. +void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile, + LLVMContext &Ctx); -bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile); +/// Read the configuration from the file with path \p InputFile into /p IConf. +bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile, + LLVMContext &Ctx); } // end namespace instrumentor } // end namespace llvm diff --git a/llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h b/llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h new file mode 100644 index 0000000..c38462d --- /dev/null +++ b/llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h @@ -0,0 +1,172 @@ +//===- Transforms/IPO/InstrumentorUtils.h ---------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// General utilities for the Instrumentor pass. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_IPO_INSTRUMENTOR_UTILS_H +#define LLVM_TRANSFORMS_IPO_INSTRUMENTOR_UTILS_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" + +#include <bitset> +#include <tuple> + +namespace llvm { +namespace instrumentor { + +/// An IR builder augmented with extra information for the instrumentor pass. +/// The underlying IR builder features an insertion callback to keep track of +/// the new instructions. +struct InstrumentorIRBuilderTy { + /// Construct an IR builder for the module \p M. + InstrumentorIRBuilderTy(Module &M) + : M(M), Ctx(M.getContext()), + IRB(Ctx, ConstantFolder(), + // Save the inserted instructions in a structure. + IRBuilderCallbackInserter( + [&](Instruction *I) { NewInsts[I] = Epoch; })) {} + + /// Destroy the IR builder and remove all erasable instructions cached during + /// the process of instrumenting. + ~InstrumentorIRBuilderTy() { + for (auto *I : ErasableInstructions) { + if (!I->getType()->isVoidTy()) + I->replaceAllUsesWith(PoisonValue::get(I->getType())); + I->eraseFromParent(); + } + } + + /// Get a temporary alloca to communicate (large) values with the runtime. + AllocaInst *getAlloca(Function *Fn, Type *Ty, bool MatchType = false) { + const DataLayout &DL = Fn->getDataLayout(); + auto *&AllocaList = AllocaMap[{Fn, DL.getTypeAllocSize(Ty)}]; + if (!AllocaList) + AllocaList = new AllocaListTy; + AllocaInst *AI = nullptr; + for (auto *&ListAI : *AllocaList) { + if (MatchType && ListAI->getAllocatedType() != Ty) + continue; + AI = ListAI; + ListAI = *AllocaList->rbegin(); + break; + } + if (AI) + AllocaList->pop_back(); + else + AI = new AllocaInst(Ty, DL.getAllocaAddrSpace(), "", + Fn->getEntryBlock().begin()); + UsedAllocas[AI] = AllocaList; + return AI; + } + + /// Return the temporary allocas. + void returnAllocas() { + for (auto [AI, List] : UsedAllocas) + List->push_back(AI); + UsedAllocas.clear(); + } + + /// Save instruction \p I to be erased later. The instructions are erased when + /// the IR builder is destroyed. + void eraseLater(Instruction *I) { ErasableInstructions.insert(I); } + + /// Commonly used values for IR inspection and creation. + ///{ + Module &M; + + LLVMContext &Ctx; + + const DataLayout &DL = M.getDataLayout(); + + Type *VoidTy = Type::getVoidTy(Ctx); + Type *IntptrTy = M.getDataLayout().getIntPtrType(Ctx); + PointerType *PtrTy = PointerType::getUnqual(Ctx); + IntegerType *Int8Ty = Type::getInt8Ty(Ctx); + IntegerType *Int32Ty = Type::getInt32Ty(Ctx); + IntegerType *Int64Ty = Type::getInt64Ty(Ctx); + Constant *NullPtrVal = Constant::getNullValue(PtrTy); + ///} + + using AllocaListTy = SmallVector<AllocaInst *>; + + /// Map that holds a list of currently available allocas for a function and + /// alloca size. + DenseMap<std::pair<Function *, unsigned>, AllocaListTy *> AllocaMap; + + /// Map that holds the currently used allocas and the list where they belong. + /// Once an alloca has to be returned, it is returned directly to its list. + DenseMap<AllocaInst *, AllocaListTy *> UsedAllocas; + + /// Instructions that should be erased later. + SmallPtrSet<Instruction *, 32> ErasableInstructions; + + /// The underlying IR builder with insertion callback. + IRBuilder<ConstantFolder, IRBuilderCallbackInserter> IRB; + + /// The current epoch number. Each instrumentation, e.g., of an instruction, + /// is happening in a dedicated epoch. The epoch allows to determine if + /// instrumentation instructions were already around, due to prior + /// instrumentations, or have been introduced to support the current + /// instrumentation, e.g., compute information about the current instruction. + unsigned Epoch = 0; + + /// A mapping from instrumentation instructions to the epoch they have been + /// created. + DenseMap<Instruction *, unsigned> NewInsts; +}; + +/// Helper that represent the caches for instrumentation call arguments. The +/// value of an argument may not need to be recomputed between the pre and post +/// instrumentation calls. +struct InstrumentationCaches { + /// A cache for direct and indirect arguments. The cache is indexed by the + /// epoch, the instrumentation opportunity name and the argument name. The + /// result is a value. + DenseMap<std::tuple<unsigned, StringRef, StringRef>, Value *> DirectArgCache; + DenseMap<std::tuple<unsigned, StringRef, StringRef>, Value *> + IndirectArgCache; +}; + +/// Boolean option bitset with a compile-time number of bits to store as many +/// options as the enumeration type \p EnumTy defines. The enumeration type is +/// expected to have an ascending and consecutive values, starting at zero, and +/// the last value being artificial and named as NumConfig (i.e., the number of +/// values in the enumeration). +template <typename EnumTy> struct BaseConfigTy { + /// The bistset with as many bits as the enumeration's values. + std::bitset<static_cast<int>(EnumTy::NumConfig)> Options; + + /// Construct the option bitset with all bits set to \p Enable. If not + /// provided, all options are enabled. + BaseConfigTy(bool Enable = true) { + if (Enable) + Options.set(); + } + + /// Check if the option \p Opt is enabled. + bool has(EnumTy Opt) const { return Options.test(static_cast<int>(Opt)); } + + /// Set the boolean value of option \p Opt to \p Value. + void set(EnumTy Opt, bool Value = true) { + Options.set(static_cast<int>(Opt), Value); + } +}; + +} // namespace instrumentor +} // end namespace llvm + +#endif // LLVM_TRANSFORMS_IPO_INSTRUMENTOR_UTILS_H diff --git a/llvm/lib/Transforms/IPO/Instrumentor.cpp b/llvm/lib/Transforms/IPO/Instrumentor.cpp index 17657cf..a28a40b 100644 --- a/llvm/lib/Transforms/IPO/Instrumentor.cpp +++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp @@ -21,6 +21,7 @@ #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" @@ -42,6 +43,7 @@ #include <cstdint> #include <functional> #include <iterator> +#include <memory> #include <string> #include <system_error> #include <type_traits> @@ -51,19 +53,24 @@ using namespace llvm::instrumentor; #define DEBUG_TYPE "instrumentor" -static cl::opt<std::string> WriteJSONConfig( +namespace { + +/// The user option to specify an output JSON file to write the configuration. +static cl::opt<std::string> WriteConfigFile( "instrumentor-write-config-file", cl::desc( "Write the instrumentor configuration into the specified JSON file"), cl::init("")); -static cl::opt<std::string> ReadJSONConfig( + +/// The user option to specify an input JSON file to read the configuration. +static cl::opt<std::string> ReadConfigFile( "instrumentor-read-config-file", cl::desc( "Read the instrumentor configuration from the specified JSON file"), cl::init("")); -namespace { - +/// Set the debug location, if not set, after changing the insertion point of +/// the IR builder \p IRB. template <typename IRBuilderTy> void ensureDbgLoc(IRBuilderTy &IRB) { if (IRB.getCurrentDebugLocation()) return; @@ -72,22 +79,23 @@ template <typename IRBuilderTy> void ensureDbgLoc(IRBuilderTy &IRB) { IRB.SetCurrentDebugLocation(DILocation::get(BB->getContext(), 0, 0, SP)); } +/// Attempt to cast \p V to type \p Ty. template <typename IRBTy> Value *tryToCast(IRBTy &IRB, Value *V, Type *Ty, const DataLayout &DL, bool AllowTruncate = false) { if (!V) return Constant::getAllOnesValue(Ty); - auto *VTy = V->getType(); + Type *VTy = V->getType(); if (VTy == Ty) return V; if (VTy->isAggregateType()) return V; - auto RequestedSize = DL.getTypeSizeInBits(Ty); - auto ValueSize = DL.getTypeSizeInBits(VTy); - bool IsTruncate = RequestedSize < ValueSize; - if (IsTruncate && !AllowTruncate) + TypeSize RequestedSize = DL.getTypeSizeInBits(Ty); + TypeSize ValueSize = DL.getTypeSizeInBits(VTy); + bool ShouldTruncate = RequestedSize < ValueSize; + if (ShouldTruncate && !AllowTruncate) return V; - if (IsTruncate && AllowTruncate) + if (ShouldTruncate && AllowTruncate) return tryToCast(IRB, IRB.CreateIntCast(V, IRB.getIntNTy(RequestedSize), /*IsSigned=*/false), @@ -97,35 +105,25 @@ Value *tryToCast(IRBTy &IRB, Value *V, Type *Ty, const DataLayout &DL, if (VTy->isIntegerTy() && Ty->isIntegerTy()) return IRB.CreateIntCast(V, Ty, /*IsSigned=*/false); if (VTy->isFloatingPointTy() && Ty->isIntOrPtrTy()) { - switch (ValueSize) { - case 64: - return tryToCast(IRB, IRB.CreateBitCast(V, IRB.getInt64Ty()), Ty, DL, - AllowTruncate); - case 32: - return tryToCast(IRB, IRB.CreateBitCast(V, IRB.getInt32Ty()), Ty, DL, - AllowTruncate); - case 16: - return tryToCast(IRB, IRB.CreateBitCast(V, IRB.getInt16Ty()), Ty, DL, - AllowTruncate); - case 8: - return tryToCast(IRB, IRB.CreateBitCast(V, IRB.getInt8Ty()), Ty, DL, - AllowTruncate); - default: - llvm_unreachable("unsupported floating point size"); - } + return tryToCast(IRB, IRB.CreateBitCast(V, IRB.getIntNTy(ValueSize)), Ty, + DL, AllowTruncate); } return IRB.CreateBitOrPointerCast(V, Ty); } +/// Get a constant integer/boolean of type \p IT and value \p Val. template <typename Ty> Constant *getCI(Type *IT, Ty Val) { return ConstantInt::get(IT, Val); } +/// 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, FunctionAnalysisManager &FAM) - : IConf(IConf), M(M), FAM(FAM), IIRB(IIRB) { + Module &M) + : IConf(IConf), M(M), IIRB(IIRB) { IConf.populate(IIRB); } @@ -133,10 +131,17 @@ public: bool instrument(); 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); + /// 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); /// The instrumentation opportunities for instructions indexed by @@ -150,8 +155,6 @@ private: /// The underlying module. Module &M; - FunctionAnalysisManager &FAM; - protected: /// A special IR builder that keeps track of the inserted instructions. InstrumentorIRBuilderTy &IIRB; @@ -169,12 +172,14 @@ bool InstrumentorImpl::shouldInstrumentTarget() { llvm::Regex TargetRegex(TargetRegexStr); std::string ErrMsg; if (!TargetRegex.isValid(ErrMsg)) { - errs() << "WARNING: failed to parse target regex: " << ErrMsg << "\n"; + IIRB.Ctx.diagnose(DiagnosticInfoInstrumentation( + Twine("failed to parse target regex: ") + ErrMsg, DS_Warning)); return false; } RegexMatches = TargetRegex.match(T.str()); } + // Only instrument the module if the target has to be instrumented. return ((IsGPU && IConf.GPUEnabled->getBool()) || (!IsGPU && IConf.HostEnabled->getBool())) && RegexMatches; @@ -196,7 +201,7 @@ bool InstrumentorImpl::instrumentInstruction(Instruction &I, return Changed; // Count epochs eagerly. - ++IIRB.Epoche; + ++IIRB.Epoch; Value *IPtr = &I; if (auto *IO = InstChoicesPRE.lookup(I.getOpcode())) { @@ -247,14 +252,14 @@ bool InstrumentorImpl::instrument() { return Changed; } -PreservedAnalyses InstrumentorPass::run(Module &M, FunctionAnalysisManager &FAM, - InstrumentationConfig &IConf, - InstrumentorIRBuilderTy &IIRB) { - InstrumentorImpl Impl(IConf, IIRB, M, FAM); - if (IConf.ReadConfig && !readConfigFromJSON(IConf, ReadJSONConfig)) +PreservedAnalyses InstrumentorPass::run(Module &M, InstrumentationConfig &IConf, + InstrumentorIRBuilderTy &IIRB, + bool ReadConfig) { + InstrumentorImpl Impl(IConf, IIRB, M); + if (ReadConfig && !readConfigFromJSON(IConf, ReadConfigFile, IIRB.Ctx)) return PreservedAnalyses::all(); - writeConfigToJSON(IConf, WriteJSONConfig); + writeConfigToJSON(IConf, WriteConfigFile, IIRB.Ctx); bool Changed = Impl.instrument(); if (!Changed) @@ -263,45 +268,43 @@ PreservedAnalyses InstrumentorPass::run(Module &M, FunctionAnalysisManager &FAM, } PreservedAnalyses InstrumentorPass::run(Module &M, ModuleAnalysisManager &MAM) { - auto &FAM = MAM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager(); - InstrumentationConfig *IConf = - UserIConf ? UserIConf : new InstrumentationConfig(); - InstrumentorIRBuilderTy *IIRB = - UserIIRB ? UserIIRB : new InstrumentorIRBuilderTy(M, FAM); + // Only create them if the user did not provide them. + std::unique_ptr<InstrumentationConfig> IConfInt( + !UserIConf ? new InstrumentationConfig() : nullptr); + std::unique_ptr<InstrumentorIRBuilderTy> IIRBInt( + !UserIIRB ? new InstrumentorIRBuilderTy(M) : nullptr); - auto PA = run(M, FAM, *IConf, *IIRB); + auto *IConf = IConfInt ? IConfInt.get() : UserIConf; + auto *IIRB = IIRBInt ? IIRBInt.get() : UserIIRB; - if (!UserIIRB) - delete IIRB; - if (!UserIConf) - delete IConf; + auto PA = run(M, *IConf, *IIRB, !UserIConf); assert(!verifyModule(M, &errs())); - return PA; } -BaseConfigurationOpportunity * -BaseConfigurationOpportunity::getBoolOption(InstrumentationConfig &IConf, - StringRef Name, - StringRef Description, bool Value) { - auto *BCO = new BaseConfigurationOpportunity(); +BaseConfigurationOption * +BaseConfigurationOption::getBoolOption(InstrumentationConfig &IConf, + StringRef Name, StringRef Description, + bool DefaultValue) { + auto *BCO = new BaseConfigurationOption(); BCO->Name = Name; BCO->Description = Description; BCO->Kind = BOOLEAN; - BCO->V.B = Value; + BCO->Value.Bool = DefaultValue; IConf.addBaseChoice(BCO); return BCO; } -BaseConfigurationOpportunity *BaseConfigurationOpportunity::getStringOption( - InstrumentationConfig &IConf, StringRef Name, StringRef Description, - StringRef Value) { - auto *BCO = new BaseConfigurationOpportunity(); +BaseConfigurationOption * +BaseConfigurationOption::getStringOption(InstrumentationConfig &IConf, + StringRef Name, StringRef Description, + StringRef DefaultValue) { + auto *BCO = new BaseConfigurationOption(); BCO->Name = Name; BCO->Description = Description; BCO->Kind = STRING; - BCO->V.S = Value; + BCO->Value.String = DefaultValue; IConf.addBaseChoice(BCO); return BCO; } @@ -312,12 +315,15 @@ void InstrumentationConfig::populate(InstrumentorIRBuilderTy &IIRB) { StoreIO::populate(*this, IIRB); } -void InstrumentationConfig::addChoice(InstrumentationOpportunity &IO) { +void InstrumentationConfig::addChoice(InstrumentationOpportunity &IO, + LLVMContext &Ctx) { auto *&ICPtr = IChoices[IO.getLocationKind()][IO.getName()]; - if (ICPtr && IO.getLocationKind() != InstrumentationLocation::SPECIAL_VALUE) { - errs() << "WARNING: registered two instrumentation opportunities for the " - "same location (" - << ICPtr->getName() << " vs " << IO.getName() << ")!\n"; + 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; } @@ -325,13 +331,13 @@ void InstrumentationConfig::addChoice(InstrumentationOpportunity &IO) { Value *InstrumentationOpportunity::getIdPre(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { - return getCI(&Ty, getIdFromEpoche(IIRB.Epoche)); + return getCI(&Ty, getIdFromEpoch(IIRB.Epoch)); } Value *InstrumentationOpportunity::getIdPost(Value &V, Type &Ty, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB) { - return getCI(&Ty, -getIdFromEpoche(IIRB.Epoche)); + return getCI(&Ty, -getIdFromEpoch(IIRB.Epoch)); } Value *InstrumentationOpportunity::forceCast(Value &V, Type &Ty, @@ -357,7 +363,7 @@ Value *InstrumentationOpportunity::replaceValue(Value &V, Value &NewV, /*AllowTruncate=*/true); } V.replaceUsesWithIf(NewVCasted, [&](Use &U) { - if (IIRB.NewInsts.lookup(cast<Instruction>(U.getUser())) == IIRB.Epoche) + if (IIRB.NewInsts.lookup(cast<Instruction>(U.getUser())) == IIRB.Epoch) return false; if (isa<LifetimeIntrinsic>(U.getUser()) || U.getUser()->isDroppable()) return false; @@ -424,12 +430,10 @@ CallInst *IRTCallDescription::createLLVMCall(Value *&V, for (auto &It : IO.IRTArgs) { if (!It.Enabled) continue; - auto *&Param = ICaches.DirectArgCache[{IIRB.Epoche, IO.getName(), It.Name}]; + 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); - if (!Param) - errs() << IO.getName() << " : " << It.Name << "\n"; assert(Param); if (Param->getType()->isVoidTy()) { @@ -438,10 +442,11 @@ CallInst *IRTCallDescription::createLLVMCall(Value *&V, DL.getTypeSizeInBits(Param->getType()) > DL.getTypeSizeInBits(It.Ty)) { if (!isPotentiallyIndirect(It)) { - errs() << "WARNING: Indirection needed for " << It.Name << " of " << *V - << " in " << IO.getName() << ", but not indicated\n. Got " - << *Param << " expected " << *It.Ty - << "; instrumentation is skipped"; + 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; @@ -471,7 +476,7 @@ CallInst *IRTCallDescription::createLLVMCall(Value *&V, } auto *&CachedParam = - ICaches.IndirectArgCache[{IIRB.Epoche, IO.getName(), It.Name}]; + ICaches.IndirectArgCache[{IIRB.Epoch, IO.getName(), It.Name}]; if (CachedParam) { CallParam = CachedParam; continue; @@ -504,14 +509,15 @@ CallInst *IRTCallDescription::createLLVMCall(Value *&V, continue; bool IsCustomReplaceable = IO.IRTArgs[I].Flags & IRTArg::REPLACABLE_CUSTOM; Value *NewValue = FnTy->isVoidTy() || IsCustomReplaceable - ? ICaches.DirectArgCache[{IIRB.Epoche, IO.getName(), + ? 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.Epoche, IO.getName(), - IO.IRTArgs[I].Name}]; + 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); @@ -519,6 +525,67 @@ CallInst *IRTCallDescription::createLLVMCall(Value *&V, return CI; } +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(PassStoredValue)) { + IRTArgs.push_back( + IRTArg(getValueType(IIRB.Ctx), "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<StoreInst>(V); @@ -582,6 +649,68 @@ Value *StoreIO::isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf, 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 (!IsPRE && Config.has(PassValue)) { + IRTArgs.push_back( + IRTArg(getValueType(IIRB.Ctx), "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<LoadInst>(V); diff --git a/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp index 2f31c71..6e12399 100644 --- a/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp +++ b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp @@ -12,6 +12,7 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/IR/DiagnosticInfo.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/JSON.h" #include "llvm/Support/MemoryBuffer.h" @@ -22,16 +23,18 @@ namespace llvm { namespace instrumentor { -void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile) { +void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile, + LLVMContext &Ctx) { if (OutputFile.empty()) return; std::error_code EC; raw_fd_stream OS(OutputFile, EC); if (EC) { - errs() << "WARNING: Failed to open instrumentor configuration file for " - "writing: " - << EC.message() << "\n"; + Ctx.diagnose(DiagnosticInfoInstrumentation( + Twine("failed to open instrumentor configuration file for writing: ") + + EC.message(), + DS_Warning)); return; } @@ -40,12 +43,12 @@ void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile) { J.attributeBegin("configuration"); J.objectBegin(); - for (auto *BaseCO : IConf.BaseConfigurationOpportunities) { + for (auto *BaseCO : IConf.BaseConfigurationOptions) { switch (BaseCO->Kind) { - case BaseConfigurationOpportunity::STRING: + case BaseConfigurationOption::STRING: J.attribute(BaseCO->Name, BaseCO->getString()); break; - case BaseConfigurationOpportunity::BOOLEAN: + case BaseConfigurationOption::BOOLEAN: J.attribute(BaseCO->Name, BaseCO->getBool()); break; } @@ -89,67 +92,81 @@ void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile) { J.objectEnd(); } -bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile) { +bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile, + LLVMContext &Ctx) { if (InputFile.empty()) return true; std::error_code EC; auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(InputFile); if (std::error_code EC = BufferOrErr.getError()) { - errs() << "WARNING: Failed to open instrumentor configuration file for " - "reading: " - << EC.message() << "\n"; + Ctx.diagnose(DiagnosticInfoInstrumentation( + Twine("failed to open instrumentor configuration file for reading: ") + + EC.message(), + DS_Warning)); return false; } auto Buffer = std::move(BufferOrErr.get()); json::Path::Root NullRoot; auto Parsed = json::parse(Buffer->getBuffer()); if (!Parsed) { - errs() << "WARNING: Failed to parse the instrumentor configuration file: " - << Parsed.takeError() << "\n"; + Ctx.diagnose(DiagnosticInfoInstrumentation( + Twine("failed to parse instrumentor configuration file: ") + + toString(Parsed.takeError()), + DS_Warning)); return false; } auto *Config = Parsed->getAsObject(); if (!Config) { - errs() << "WARNING: Failed to parse the instrumentor configuration file: " - "Expected " - "an object '{ ... }'\n"; + Ctx.diagnose(DiagnosticInfoInstrumentation( + "failed to parse instrumentor configuration file, expected an object " + "'{ ... }'", + DS_Warning)); return false; } - StringMap<BaseConfigurationOpportunity *> BCOMap; - for (auto *BO : IConf.BaseConfigurationOpportunities) + StringMap<BaseConfigurationOption *> BCOMap; + for (auto *BO : IConf.BaseConfigurationOptions) BCOMap[BO->Name] = BO; SmallPtrSet<InstrumentationOpportunity *, 32> SeenIOs; for (auto &It : *Config) { auto *Obj = It.second.getAsObject(); if (!Obj) { - errs() << "WARNING: malformed JSON configuration, expected an object.\n"; + Ctx.diagnose(DiagnosticInfoInstrumentation( + "malformed JSON configuration, expected an object", DS_Warning)); continue; } if (It.first == "configuration") { for (auto &ObjIt : *Obj) { if (auto *BO = BCOMap.lookup(ObjIt.first)) { switch (BO->Kind) { - case BaseConfigurationOpportunity::STRING: + case BaseConfigurationOption::STRING: if (auto V = ObjIt.second.getAsString()) { BO->setString(IConf.SS.save(*V)); - } else - errs() << "WARNING: configuration key '" << ObjIt.first - << "' expects a string, value ignored\n"; + } else { + Ctx.diagnose(DiagnosticInfoInstrumentation( + Twine("configuration key '") + ObjIt.first.str() + + Twine("' expects a string, value ignored"), + DS_Warning)); + } break; - case BaseConfigurationOpportunity::BOOLEAN: + case BaseConfigurationOption::BOOLEAN: if (auto V = ObjIt.second.getAsBoolean()) BO->setBool(*V); - else - errs() << "WARNING: configuration key '" << ObjIt.first - << "' expects a boolean, value ignored\n"; + else { + Ctx.diagnose(DiagnosticInfoInstrumentation( + Twine("configuration key '") + ObjIt.first.str() + + Twine("' expects a boolean, value ignored"), + DS_Warning)); + } break; } } else if (!StringRef(ObjIt.first).ends_with(".description")) { - errs() << "WARNING: configuration key not found and ignored: " - << ObjIt.first << "\n"; + Ctx.diagnose(DiagnosticInfoInstrumentation( + Twine("configuration key '") + ObjIt.first.str() + + Twine("' not found and ignored"), + DS_Warning)); } } continue; @@ -160,15 +177,17 @@ bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile) { for (auto &ObjIt : *Obj) { auto *InnerObj = ObjIt.second.getAsObject(); if (!InnerObj) { - errs() - << "WARNING: malformed JSON configuration, expected an object.\n"; + Ctx.diagnose(DiagnosticInfoInstrumentation( + "malformed JSON configuration, expected an object", DS_Warning)); continue; } auto *IO = IChoiceMap.lookup(ObjIt.first); if (!IO) { - errs() << "WARNING: malformed JSON configuration, expected an object " - "matching an instrumentor choice, got " - << ObjIt.first << ".\n"; + Ctx.diagnose(DiagnosticInfoInstrumentation( + Twine("malformed JSON configuration, expected an object matching " + "an instrumentor choice, got ") + + ObjIt.first.str(), + DS_Warning)); continue; } SeenIOs.insert(IO); |