[llvm] [Instrumentor] Add Instrumentor pass (PR #138958)
Kevin Sala Penades via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 11 16:19:56 PDT 2025
https://github.com/kevinsala updated https://github.com/llvm/llvm-project/pull/138958
>From 5acd5abfd92bf1be03960ec885e9b18ab30ee3f3 Mon Sep 17 00:00:00 2001
From: Kevin Sala <salapenades1 at llnl.gov>
Date: Wed, 7 May 2025 13:34:19 -0700
Subject: [PATCH 1/2] [Instrumentor] Add Instrumentor pass
This commit adds the basic infrastructure for the Instrumentor
pass, which allows instrumenting code in a simple and
customizable way. This commit adds support for instrumenting
load and store instructions. The Instrumentor can be configured
with a JSON file that describes what should be instrumented, or
can be used programmatically from another pass.
This is only a squash commit of several contributions to the
Instrumentor. The following are the authors and contributors of
this pass:
Co-authored-by: Johannes Doerfert <johannes at jdoerfert.de>
Co-authored-by: Kevin Sala <salapenades1 at llnl.gov>
Co-authored-by: Ivan Radanov Ivanov <ivanov.i.aa at m.titech.ac.jp>
Co-authored-by: Ethan Luis McDonough <ethanluismcdonough at gmail.com>
---
.../llvm/Transforms/IPO/Instrumentor.h | 739 ++++++++++++++++++
.../Transforms/IPO/InstrumentorConfigFile.h | 29 +
llvm/lib/Passes/PassBuilder.cpp | 1 +
llvm/lib/Passes/PassBuilderPipelines.cpp | 12 +
llvm/lib/Passes/PassRegistry.def | 1 +
llvm/lib/Transforms/IPO/CMakeLists.txt | 3 +
llvm/lib/Transforms/IPO/Instrumentor.cpp | 645 +++++++++++++++
.../Transforms/IPO/InstrumentorConfigFile.cpp | 203 +++++
.../Instrumentation/Instrumentor/counters.ll | 23 +
.../Instrumentor/counters_config.json | 25 +
.../Instrumentor/custom_config.json | 14 +
.../Instrumentor/default_config.json | 109 +++
.../Instrumentor/load_store.ll | 217 +++++
.../Instrumentor/load_store_args.ll | 226 ++++++
.../Instrumentor/load_store_config.json | 107 +++
.../Instrumentor/load_store_noreplace.ll | 208 +++++
.../load_store_noreplace_config.json | 107 +++
.../Instrumentor/read_config.ll | 26 +
.../Instrumentor/write_config.ll | 3 +
19 files changed, 2698 insertions(+)
create mode 100644 llvm/include/llvm/Transforms/IPO/Instrumentor.h
create mode 100644 llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h
create mode 100644 llvm/lib/Transforms/IPO/Instrumentor.cpp
create mode 100644 llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp
create mode 100644 llvm/test/Instrumentation/Instrumentor/counters.ll
create mode 100644 llvm/test/Instrumentation/Instrumentor/counters_config.json
create mode 100644 llvm/test/Instrumentation/Instrumentor/custom_config.json
create mode 100644 llvm/test/Instrumentation/Instrumentor/default_config.json
create mode 100644 llvm/test/Instrumentation/Instrumentor/load_store.ll
create mode 100644 llvm/test/Instrumentation/Instrumentor/load_store_args.ll
create mode 100644 llvm/test/Instrumentation/Instrumentor/load_store_config.json
create mode 100644 llvm/test/Instrumentation/Instrumentor/load_store_noreplace.ll
create mode 100644 llvm/test/Instrumentation/Instrumentor/load_store_noreplace_config.json
create mode 100644 llvm/test/Instrumentation/Instrumentor/read_config.ll
create mode 100644 llvm/test/Instrumentation/Instrumentor/write_config.ll
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
new file mode 100644
index 0000000000000..6fb5a06305096
--- /dev/null
+++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
@@ -0,0 +1,739 @@
+//===- 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.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// A highly configurable instrumentation pass.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_IPO_INSTRUMENTOR_H
+#define LLVM_TRANSFORMS_IPO_INSTRUMENTOR_H
+
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/EnumeratedArray.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/Instruction.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/StringSaver.h"
+#include "llvm/Transforms/Utils/Instrumentation.h"
+
+#include <bitset>
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <tuple>
+
+namespace llvm {
+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;
+};
+
+using GetterCallbackTy = std::function<Value *(
+ Value &, Type &, InstrumentationConfig &, InstrumentorIRBuilderTy &)>;
+using SetterCallbackTy = std::function<Value *(
+ Value &, Value &, InstrumentationConfig &, InstrumentorIRBuilderTy &)>;
+
+struct IRTArg {
+ enum IRArgFlagTy {
+ NONE = 0,
+ STRING = 1 << 0,
+ REPLACABLE = 1 << 1,
+ REPLACABLE_CUSTOM = 1 << 2,
+ POTENTIALLY_INDIRECT = 1 << 3,
+ INDIRECT_HAS_SIZE = 1 << 4,
+
+ LAST,
+ };
+
+ IRTArg(Type *Ty, StringRef Name, StringRef Description, unsigned Flags,
+ GetterCallbackTy GetterCB, SetterCallbackTy SetterCB = nullptr,
+ bool Enabled = true, bool NoCache = false)
+ : Enabled(Enabled), Ty(Ty), Name(Name), Description(Description),
+ Flags(Flags), GetterCB(std::move(GetterCB)),
+ SetterCB(std::move(SetterCB)), NoCache(NoCache) {}
+
+ bool Enabled;
+ Type *Ty;
+ StringRef Name;
+ StringRef Description;
+ unsigned Flags;
+ GetterCallbackTy GetterCB;
+ SetterCallbackTy SetterCB;
+ bool NoCache;
+};
+
+struct InstrumentationCaches {
+ DenseMap<std::tuple<unsigned, StringRef, StringRef>, Value *> DirectArgCache;
+ DenseMap<std::tuple<unsigned, StringRef, StringRef>, Value *>
+ IndirectArgCache;
+};
+
+struct IRTCallDescription {
+ IRTCallDescription(InstrumentationOpportunity &IConf, Type *RetTy = nullptr);
+
+ FunctionType *createLLVMSignature(InstrumentationConfig &IConf,
+ LLVMContext &Ctx, const DataLayout &DL,
+ bool ForceIndirection);
+ CallInst *createLLVMCall(Value *&V, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB, const DataLayout &DL,
+ InstrumentationCaches &ICaches);
+
+ bool isReplacable(IRTArg &IRTA) const {
+ return (IRTA.Flags & (IRTArg::REPLACABLE | IRTArg::REPLACABLE_CUSTOM));
+ }
+
+ bool isPotentiallyIndirect(IRTArg &IRTA) const {
+ return ((IRTA.Flags & IRTArg::POTENTIALLY_INDIRECT) ||
+ ((IRTA.Flags & IRTArg::REPLACABLE) && NumReplaceableArgs > 1));
+ }
+
+ bool RequiresIndirection = false;
+ bool MightRequireIndirection = false;
+ unsigned NumReplaceableArgs = 0;
+ InstrumentationOpportunity &IO;
+ Type *RetTy = nullptr;
+};
+
+struct InstrumentationLocation {
+ enum KindTy {
+ MODULE_PRE,
+ MODULE_POST,
+ GLOBAL_PRE,
+ GLOBAL_POST,
+ FUNCTION_PRE,
+ FUNCTION_POST,
+ BASIC_BLOCK_PRE,
+ BASIC_BLOCK_POST,
+ INSTRUCTION_PRE,
+ INSTRUCTION_POST,
+ SPECIAL_VALUE,
+ Last = SPECIAL_VALUE,
+ };
+
+ InstrumentationLocation(KindTy Kind) : Kind(Kind) {
+ assert(Kind != INSTRUCTION_PRE && Kind != INSTRUCTION_POST &&
+ "Opcode required!");
+ }
+
+ InstrumentationLocation(unsigned Opcode, bool IsPRE)
+ : Kind(IsPRE ? INSTRUCTION_PRE : INSTRUCTION_POST), Opcode(Opcode) {}
+
+ KindTy getKind() const { return Kind; }
+
+ static StringRef getKindStr(KindTy Kind) {
+ switch (Kind) {
+ case MODULE_PRE:
+ return "module_pre";
+ case MODULE_POST:
+ return "module_post";
+ case GLOBAL_PRE:
+ return "global_pre";
+ case GLOBAL_POST:
+ return "global_post";
+ case FUNCTION_PRE:
+ return "function_pre";
+ case FUNCTION_POST:
+ return "function_post";
+ case BASIC_BLOCK_PRE:
+ return "basic_block_pre";
+ case BASIC_BLOCK_POST:
+ return "basic_block_post";
+ case INSTRUCTION_PRE:
+ return "instruction_pre";
+ case INSTRUCTION_POST:
+ return "instruction_post";
+ case SPECIAL_VALUE:
+ return "special_value";
+ }
+ llvm_unreachable("Invalid kind!");
+ }
+ static KindTy getKindFromStr(StringRef S) {
+ return StringSwitch<KindTy>(S)
+ .Case("module_pre", MODULE_PRE)
+ .Case("module_post", MODULE_POST)
+ .Case("global_pre", GLOBAL_PRE)
+ .Case("global_post", GLOBAL_POST)
+ .Case("function_pre", FUNCTION_PRE)
+ .Case("function_post", FUNCTION_POST)
+ .Case("basic_block_pre", BASIC_BLOCK_PRE)
+ .Case("basic_block_post", BASIC_BLOCK_POST)
+ .Case("instruction_pre", INSTRUCTION_PRE)
+ .Case("instruction_post", INSTRUCTION_POST)
+ .Case("special_value", SPECIAL_VALUE)
+ .Default(Last);
+ }
+
+ static bool isPRE(KindTy Kind) {
+ switch (Kind) {
+ case MODULE_PRE:
+ case GLOBAL_PRE:
+ case FUNCTION_PRE:
+ case BASIC_BLOCK_PRE:
+ case INSTRUCTION_PRE:
+ return true;
+ case MODULE_POST:
+ case GLOBAL_POST:
+ case FUNCTION_POST:
+ case BASIC_BLOCK_POST:
+ case INSTRUCTION_POST:
+ case SPECIAL_VALUE:
+ return false;
+ }
+ llvm_unreachable("Invalid kind!");
+ }
+ bool isPRE() const { return isPRE(Kind); }
+
+ unsigned getOpcode() const {
+ assert((Kind == INSTRUCTION_PRE || Kind == INSTRUCTION_POST) &&
+ "Expected instruction!");
+ return Opcode;
+ }
+
+private:
+ const KindTy Kind;
+ const unsigned Opcode = -1;
+};
+
+struct BaseConfigurationOpportunity {
+ 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);
+ union ValueTy {
+ bool B;
+ int64_t I;
+ StringRef S;
+ };
+
+ void setBool(bool B) {
+ assert(Kind == BOOLEAN && "Not a boolean!");
+ V.B = B;
+ }
+ bool getBool() const {
+ assert(Kind == BOOLEAN && "Not a boolean!");
+ return V.B;
+ }
+ void setString(StringRef S) {
+ assert(Kind == STRING && "Not a string!");
+ V.S = S;
+ }
+ StringRef getString() const {
+ assert(Kind == STRING && "Not a string!");
+ return V.S;
+ }
+
+ StringRef Name;
+ StringRef Description;
+ KindTy Kind;
+ ValueTy V = {0};
+};
+
+struct InstrumentorIRBuilderTy;
+struct InstrumentationConfig {
+ virtual ~InstrumentationConfig() {}
+
+ InstrumentationConfig() : SS(StringAllocator) {
+ RuntimePrefix = BaseConfigurationOpportunity::getStringOption(
+ *this, "runtime_prefix", "The runtime API prefix.", "__instrumentor_");
+ TargetRegex = BaseConfigurationOpportunity::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(
+ *this, "host_enabled", "Instrument non-GPU targets", true);
+ GPUEnabled = BaseConfigurationOpportunity::getBoolOption(
+ *this, "gpu_enabled", "Instrument GPU targets", true);
+ }
+
+ bool ReadConfig = true;
+
+ virtual void populate(InstrumentorIRBuilderTy &IIRB);
+ StringRef getRTName() const { return RuntimePrefix->getString(); }
+
+ 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);
+ }
+ SmallVector<BaseConfigurationOpportunity *> BaseConfigurationOpportunities;
+
+ BaseConfigurationOpportunity *RuntimePrefix;
+ BaseConfigurationOpportunity *TargetRegex;
+ BaseConfigurationOpportunity *HostEnabled;
+ BaseConfigurationOpportunity *GPUEnabled;
+
+ EnumeratedArray<StringMap<InstrumentationOpportunity *>,
+ InstrumentationLocation::KindTy>
+ IChoices;
+ void addChoice(InstrumentationOpportunity &IO);
+
+ template <typename Ty, typename... ArgsTy>
+ static Ty *allocate(ArgsTy &&...Args) {
+ static SpecificBumpPtrAllocator<Ty> Allocator;
+ Ty *Obj = Allocator.Allocate();
+ new (Obj) Ty(std::forward<ArgsTy>(Args)...);
+ return Obj;
+ }
+
+ BumpPtrAllocator StringAllocator;
+ StringSaver SS;
+};
+
+template <typename EnumTy> struct BaseConfigTy {
+ std::bitset<static_cast<int>(EnumTy::NumConfig)> Options;
+
+ BaseConfigTy(bool Enable = true) {
+ if (Enable)
+ Options.set();
+ }
+
+ 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);
+ }
+};
+
+struct InstrumentationOpportunity {
+ InstrumentationOpportunity(const InstrumentationLocation IP) : IP(IP) {}
+ virtual ~InstrumentationOpportunity() {}
+
+ InstrumentationLocation IP;
+
+ SmallVector<IRTArg> IRTArgs;
+ bool Enabled = true;
+
+ /// Helpers to cast values, pass them to the runtime, and replace them. To be
+ /// used as part of the getter/setter of a InstrumentationOpportunity.
+ ///{
+ static Value *forceCast(Value &V, Type &Ty, InstrumentorIRBuilderTy &IIRB);
+ static Value *getValue(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ return forceCast(V, Ty, IIRB);
+ }
+
+ static Value *replaceValue(Value &V, Value &NewV,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ ///}
+
+ virtual Value *instrument(Value *&V, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB,
+ InstrumentationCaches &ICaches) {
+ if (CB && !CB(*V))
+ return nullptr;
+
+ const DataLayout &DL = IIRB.IRB.GetInsertBlock()->getDataLayout();
+ IRTCallDescription IRTCallDesc(*this, getRetTy(V->getContext()));
+ auto *CI = IRTCallDesc.createLLVMCall(V, IConf, IIRB, DL, ICaches);
+ return CI;
+ }
+
+ virtual Type *getRetTy(LLVMContext &Ctx) const { return nullptr; }
+ virtual StringRef getName() const = 0;
+
+ unsigned getOpcode() const { return IP.getOpcode(); }
+ 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);
+
+ void addCommonArgs(InstrumentationConfig &IConf, LLVMContext &Ctx,
+ bool PassId) {
+ const auto CB = IP.isPRE() ? getIdPre : getIdPost;
+ 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;
+ static int32_t GlobalId = 0;
+ int32_t &EpochId = EpocheIdMap[Epoche];
+ if (EpochId == 0)
+ EpochId = ++GlobalId;
+ return EpochId;
+ }
+};
+
+template <unsigned Opcode>
+struct InstructionIO : public InstrumentationOpportunity {
+ InstructionIO(bool IsPRE)
+ : InstrumentationOpportunity(InstrumentationLocation(Opcode, IsPRE)) {}
+ virtual ~InstructionIO() {}
+
+ unsigned getOpcode() const { return Opcode; }
+
+ StringRef getName() const override {
+ return Instruction::getOpcodeName(Opcode);
+ }
+};
+
+struct StoreIO : public InstructionIO<Instruction::Store> {
+ StoreIO(bool IsPRE) : InstructionIO(IsPRE) {}
+ virtual ~StoreIO() {};
+
+ enum ConfigKind {
+ PassPointer = 0,
+ ReplacePointer,
+ PassPointerAS,
+ PassStoredValue,
+ PassStoredValueSize,
+ PassAlignment,
+ PassValueTypeId,
+ PassAtomicityOrdering,
+ PassSyncScopeId,
+ PassIsVolatile,
+ PassId,
+ NumConfig,
+ };
+
+ virtual Type *getValueType(LLVMContext &Ctx) const {
+ return IntegerType::getInt64Ty(Ctx);
+ }
+
+ using ConfigTy = BaseConfigTy<ConfigKind>;
+ ConfigTy Config;
+
+ 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);
+ }
+
+ static Value *getPointer(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *setPointer(Value &V, Value &NewV, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getPointerAS(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getValue(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getValueSize(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getAlignment(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getValueTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getAtomicityOrdering(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getSyncScopeId(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+
+ static void populate(InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ for (auto IsPRE : {true, false}) {
+ auto *AIC = IConf.allocate<StoreIO>(IsPRE);
+ AIC->init(IConf, IIRB);
+ }
+ }
+};
+
+struct LoadIO : public InstructionIO<Instruction::Load> {
+ LoadIO(bool IsPRE) : InstructionIO(IsPRE) {}
+ virtual ~LoadIO() {};
+
+ enum ConfigKind {
+ PassPointer = 0,
+ ReplacePointer,
+ PassPointerAS,
+ PassValue,
+ ReplaceValue,
+ PassValueSize,
+ PassAlignment,
+ PassValueTypeId,
+ PassAtomicityOrdering,
+ PassSyncScopeId,
+ PassIsVolatile,
+ PassId,
+ NumConfig,
+ };
+
+ virtual Type *getValueType(LLVMContext &Ctx) const {
+ return IntegerType::getInt64Ty(Ctx);
+ }
+
+ using ConfigTy = BaseConfigTy<ConfigKind>;
+ ConfigTy Config;
+
+ 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);
+ }
+
+ static Value *getPointer(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *setPointer(Value &V, Value &NewV, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getPointerAS(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getValue(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getValueSize(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getAlignment(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getValueTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getAtomicityOrdering(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getSyncScopeId(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+
+ static void populate(InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ for (auto IsPRE : {true, false}) {
+ auto *AIC = IConf.allocate<LoadIO>(IsPRE);
+ AIC->init(IConf, IIRB);
+ }
+ }
+};
+
+} // namespace instrumentor
+
+class InstrumentorPass : public PassInfoMixin<InstrumentorPass> {
+ using InstrumentationConfig = instrumentor::InstrumentationConfig;
+ using InstrumentorIRBuilderTy = instrumentor::InstrumentorIRBuilderTy;
+ InstrumentationConfig *UserIConf;
+ InstrumentorIRBuilderTy *UserIIRB;
+
+ PreservedAnalyses run(Module &M, FunctionAnalysisManager &FAM,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+
+public:
+ InstrumentorPass(InstrumentationConfig *IC = nullptr,
+ InstrumentorIRBuilderTy *IIRB = nullptr)
+ : UserIConf(IC), UserIIRB(IIRB) {}
+
+ PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_IPO_INSTRUMENTOR_H
diff --git a/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h b/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h
new file mode 100644
index 0000000000000..370e3b7303b34
--- /dev/null
+++ b/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h
@@ -0,0 +1,29 @@
+//===- Transforms/IPO/InstrumentorConfigFile.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Utilities for the Instrumentor JSON configuration file.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_IPO_INSTRUMENTOR_CONFIGFILE_H
+#define LLVM_TRANSFORMS_IPO_INSTRUMENTOR_CONFIGFILE_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Transforms/IPO/Instrumentor.h"
+
+namespace llvm {
+namespace instrumentor {
+
+void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile);
+
+bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile);
+
+} // end namespace instrumentor
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_IPO_INSTRUMENTOR_CONFIGFILE_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 7740f622ede7c..064afdec3da68 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -221,6 +221,7 @@
#include "llvm/Transforms/IPO/IROutliner.h"
#include "llvm/Transforms/IPO/InferFunctionAttrs.h"
#include "llvm/Transforms/IPO/Inliner.h"
+#include "llvm/Transforms/IPO/Instrumentor.h"
#include "llvm/Transforms/IPO/Internalize.h"
#include "llvm/Transforms/IPO/LoopExtractor.h"
#include "llvm/Transforms/IPO/LowerTypeTests.h"
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index f172271be09ab..228891ecf9aa6 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -63,6 +63,7 @@
#include "llvm/Transforms/IPO/IROutliner.h"
#include "llvm/Transforms/IPO/InferFunctionAttrs.h"
#include "llvm/Transforms/IPO/Inliner.h"
+#include "llvm/Transforms/IPO/Instrumentor.h"
#include "llvm/Transforms/IPO/LowerTypeTests.h"
#include "llvm/Transforms/IPO/MemProfContextDisambiguation.h"
#include "llvm/Transforms/IPO/MergeFunctions.h"
@@ -212,6 +213,10 @@ static cl::opt<bool> EnableLoopFlatten("enable-loop-flatten", cl::init(false),
cl::Hidden,
cl::desc("Enable the LoopFlatten Pass"));
+static cl::opt<bool>
+ EnableInstrumentor("enable-instrumentor", cl::init(false), cl::Hidden,
+ cl::desc("Enable the Instrumentor Pass"));
+
// Experimentally allow loop header duplication. This should allow for better
// optimization at Oz, since loop-idiom recognition can then recognize things
// like memcpy. If this ends up being useful for many targets, we should drop
@@ -1594,6 +1599,10 @@ PassBuilder::buildModuleOptimizationPipeline(OptimizationLevel Level,
invokeOptimizerLastEPCallbacks(MPM, Level, LTOPhase);
+ // Run the Instrumentor pass late.
+ if (EnableInstrumentor)
+ MPM.addPass(InstrumentorPass());
+
// Split out cold code. Splitting is done late to avoid hiding context from
// other optimizations and inadvertently regressing performance. The tradeoff
// is that this has a higher code size cost than splitting early.
@@ -2306,6 +2315,9 @@ PassBuilder::buildO0DefaultPipeline(OptimizationLevel Level,
invokeOptimizerLastEPCallbacks(MPM, Level, Phase);
+ if (EnableInstrumentor)
+ MPM.addPass(InstrumentorPass());
+
if (isLTOPreLink(Phase))
addRequiredLTOPreLinkPasses(MPM);
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index d5d1b2173da69..666233a398f8a 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -94,6 +94,7 @@ MODULE_PASS("instrprof", InstrProfilingLoweringPass())
MODULE_PASS("ctx-instr-lower", PGOCtxProfLoweringPass())
MODULE_PASS("print<ctx-prof-analysis>", CtxProfAnalysisPrinterPass(errs()))
MODULE_PASS("invalidate<all>", InvalidateAllAnalysesPass())
+MODULE_PASS("instrumentor", InstrumentorPass())
MODULE_PASS("iroutliner", IROutlinerPass())
MODULE_PASS("jmc-instrumenter", JMCInstrumenterPass())
MODULE_PASS("lower-emutls", LowerEmuTLSPass())
diff --git a/llvm/lib/Transforms/IPO/CMakeLists.txt b/llvm/lib/Transforms/IPO/CMakeLists.txt
index 1c4ee0336d4db..824dff527a672 100644
--- a/llvm/lib/Transforms/IPO/CMakeLists.txt
+++ b/llvm/lib/Transforms/IPO/CMakeLists.txt
@@ -27,6 +27,8 @@ add_llvm_component_library(LLVMipo
IROutliner.cpp
InferFunctionAttrs.cpp
Inliner.cpp
+ Instrumentor.cpp
+ InstrumentorConfigFile.cpp
Internalize.cpp
LoopExtractor.cpp
LowerTypeTests.cpp
@@ -62,6 +64,7 @@ add_llvm_component_library(LLVMipo
BitReader
BitWriter
Core
+ Demangle
FrontendOpenMP
InstCombine
IRReader
diff --git a/llvm/lib/Transforms/IPO/Instrumentor.cpp b/llvm/lib/Transforms/IPO/Instrumentor.cpp
new file mode 100644
index 0000000000000..17657cfae8fb5
--- /dev/null
+++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp
@@ -0,0 +1,645 @@
+//===-- 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
+//
+//===----------------------------------------------------------------------===//
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/IPO/Instrumentor.h"
+#include "llvm/Transforms/IPO/InstrumentorConfigFile.h"
+
+#include "llvm/ADT/PostOrderIterator.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/IR/Constant.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DebugInfoMetadata.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 <cassert>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <string>
+#include <system_error>
+#include <type_traits>
+
+using namespace llvm;
+using namespace llvm::instrumentor;
+
+#define DEBUG_TYPE "instrumentor"
+
+static cl::opt<std::string> WriteJSONConfig(
+ "instrumentor-write-config-file",
+ cl::desc(
+ "Write the instrumentor configuration into the specified JSON file"),
+ cl::init(""));
+static cl::opt<std::string> ReadJSONConfig(
+ "instrumentor-read-config-file",
+ cl::desc(
+ "Read the instrumentor configuration from the specified JSON file"),
+ cl::init(""));
+
+namespace {
+
+template <typename IRBuilderTy> 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));
+}
+
+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();
+ 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)
+ return V;
+ if (IsTruncate && AllowTruncate)
+ return tryToCast(IRB,
+ IRB.CreateIntCast(V, 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);
+ 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 IRB.CreateBitOrPointerCast(V, Ty);
+}
+
+template <typename Ty> Constant *getCI(Type *IT, Ty Val) {
+ return ConstantInt::get(IT, Val);
+}
+
+class InstrumentorImpl final {
+public:
+ InstrumentorImpl(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB,
+ Module &M, FunctionAnalysisManager &FAM)
+ : IConf(IConf), M(M), FAM(FAM), IIRB(IIRB) {
+ IConf.populate(IIRB);
+ }
+
+ /// Instrument the module, public entry point.
+ bool instrument();
+
+private:
+ bool shouldInstrumentTarget();
+ bool shouldInstrumentFunction(Function &Fn);
+
+ bool instrumentInstruction(Instruction &I, InstrumentationCaches &ICaches);
+ bool instrumentFunction(Function &Fn);
+
+ /// The instrumentation opportunities for instructions indexed by
+ /// their opcode.
+ DenseMap<unsigned, InstrumentationOpportunity *> InstChoicesPRE,
+ InstChoicesPOST;
+
+ /// The instrumentor configuration.
+ InstrumentationConfig &IConf;
+
+ /// The underlying module.
+ Module &M;
+
+ FunctionAnalysisManager &FAM;
+
+protected:
+ /// A special IR builder that keeps track of the inserted instructions.
+ InstrumentorIRBuilderTy &IIRB;
+};
+
+} // end anonymous namespace
+
+bool InstrumentorImpl::shouldInstrumentTarget() {
+ const Triple &T = M.getTargetTriple();
+ const bool IsGPU = T.isAMDGPU() || T.isNVPTX();
+
+ bool RegexMatches = true;
+ const auto TargetRegexStr = IConf.TargetRegex->getString();
+ if (!TargetRegexStr.empty()) {
+ llvm::Regex TargetRegex(TargetRegexStr);
+ std::string ErrMsg;
+ if (!TargetRegex.isValid(ErrMsg)) {
+ errs() << "WARNING: failed to parse target regex: " << ErrMsg << "\n";
+ return false;
+ }
+ RegexMatches = TargetRegex.match(T.str());
+ }
+
+ return ((IsGPU && IConf.GPUEnabled->getBool()) ||
+ (!IsGPU && IConf.HostEnabled->getBool())) &&
+ RegexMatches;
+}
+
+bool InstrumentorImpl::shouldInstrumentFunction(Function &Fn) {
+ if (Fn.isDeclaration())
+ return false;
+ return !Fn.getName().starts_with(IConf.getRTName()) ||
+ Fn.hasFnAttribute("instrument");
+}
+
+bool InstrumentorImpl::instrumentInstruction(Instruction &I,
+ InstrumentationCaches &ICaches) {
+ bool Changed = false;
+
+ // Skip instrumentation instructions.
+ if (IIRB.NewInsts.contains(&I))
+ return Changed;
+
+ // Count epochs eagerly.
+ ++IIRB.Epoche;
+
+ Value *IPtr = &I;
+ if (auto *IO = InstChoicesPRE.lookup(I.getOpcode())) {
+ IIRB.IRB.SetInsertPoint(&I);
+ ensureDbgLoc(IIRB.IRB);
+ Changed |= bool(IO->instrument(IPtr, IConf, IIRB, ICaches));
+ }
+
+ if (auto *IO = InstChoicesPOST.lookup(I.getOpcode())) {
+ IIRB.IRB.SetInsertPoint(I.getNextNonDebugInstruction());
+ ensureDbgLoc(IIRB.IRB);
+ Changed |= bool(IO->instrument(IPtr, IConf, IIRB, ICaches));
+ }
+ IIRB.returnAllocas();
+
+ return Changed;
+};
+
+bool InstrumentorImpl::instrumentFunction(Function &Fn) {
+ bool Changed = false;
+ if (!shouldInstrumentFunction(Fn))
+ return Changed;
+
+ InstrumentationCaches ICaches;
+ ReversePostOrderTraversal<Function *> RPOT(&Fn);
+ for (auto &It : RPOT)
+ for (auto &I : *It)
+ Changed |= instrumentInstruction(I, ICaches);
+
+ return Changed;
+}
+
+bool InstrumentorImpl::instrument() {
+ bool Changed = false;
+ if (!shouldInstrumentTarget())
+ return Changed;
+
+ for (auto &It : IConf.IChoices[InstrumentationLocation::INSTRUCTION_PRE])
+ if (It.second->Enabled)
+ InstChoicesPRE[It.second->getOpcode()] = It.second;
+ for (auto &It : IConf.IChoices[InstrumentationLocation::INSTRUCTION_POST])
+ if (It.second->Enabled)
+ InstChoicesPOST[It.second->getOpcode()] = It.second;
+
+ for (Function &Fn : M)
+ Changed |= instrumentFunction(Fn);
+
+ 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))
+ return PreservedAnalyses::all();
+
+ writeConfigToJSON(IConf, WriteJSONConfig);
+
+ bool Changed = Impl.instrument();
+ if (!Changed)
+ return PreservedAnalyses::all();
+ return PreservedAnalyses::none();
+}
+
+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);
+
+ auto PA = run(M, FAM, *IConf, *IIRB);
+
+ if (!UserIIRB)
+ delete IIRB;
+ if (!UserIConf)
+ delete IConf;
+
+ assert(!verifyModule(M, &errs()));
+
+ return PA;
+}
+
+BaseConfigurationOpportunity *
+BaseConfigurationOpportunity::getBoolOption(InstrumentationConfig &IConf,
+ StringRef Name,
+ StringRef Description, bool Value) {
+ auto *BCO = new BaseConfigurationOpportunity();
+ BCO->Name = Name;
+ BCO->Description = Description;
+ BCO->Kind = BOOLEAN;
+ BCO->V.B = Value;
+ IConf.addBaseChoice(BCO);
+ return BCO;
+}
+
+BaseConfigurationOpportunity *BaseConfigurationOpportunity::getStringOption(
+ InstrumentationConfig &IConf, StringRef Name, StringRef Description,
+ StringRef Value) {
+ auto *BCO = new BaseConfigurationOpportunity();
+ BCO->Name = Name;
+ BCO->Description = Description;
+ BCO->Kind = STRING;
+ BCO->V.S = Value;
+ IConf.addBaseChoice(BCO);
+ return BCO;
+}
+
+void InstrumentationConfig::populate(InstrumentorIRBuilderTy &IIRB) {
+ /// List of all instrumentation opportunities.
+ LoadIO::populate(*this, IIRB);
+ StoreIO::populate(*this, IIRB);
+}
+
+void InstrumentationConfig::addChoice(InstrumentationOpportunity &IO) {
+ 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";
+ }
+ ICPtr = &IO;
+}
+
+Value *InstrumentationOpportunity::getIdPre(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ return getCI(&Ty, getIdFromEpoche(IIRB.Epoche));
+}
+
+Value *InstrumentationOpportunity::getIdPost(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ return getCI(&Ty, -getIdFromEpoche(IIRB.Epoche));
+}
+
+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<Instruction>(&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<Instruction>(U.getUser())) == IIRB.Epoche)
+ return false;
+ if (isa<LifetimeIntrinsic>(U.getUser()) || U.getUser()->isDroppable())
+ return false;
+ return true;
+ });
+
+ 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,
+ LLVMContext &Ctx, const DataLayout &DL,
+ bool ForceIndirection) {
+ assert(((ForceIndirection && MightRequireIndirection) ||
+ (!ForceIndirection && !RequiresIndirection)) &&
+ "Wrong indirection setting!");
+
+ SmallVector<Type *> 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(PointerType::get(Ctx, 0));
+ if (!(It.Flags & IRTArg::INDIRECT_HAS_SIZE))
+ ParamTypes.push_back(IntegerType::getInt32Ty(Ctx));
+ }
+ if (!RetTy)
+ RetTy = Type::getVoidTy(Ctx);
+
+ return FunctionType::get(RetTy, ParamTypes, /*isVarArg=*/false);
+}
+
+CallInst *IRTCallDescription::createLLVMCall(Value *&V,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB,
+ const DataLayout &DL,
+ InstrumentationCaches &ICaches) {
+ SmallVector<Value *> 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.Epoche, 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()) {
+ Param = Constant::getNullValue(It.Ty);
+ } else if (Param->getType()->isAggregateType() ||
+ 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";
+ 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.Epoche, 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, V->getContext(), 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.Epoche, 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}];
+ NewValue = IIRB.IRB.CreateLoad(V->getType(), Q);
+ }
+ V = IO.IRTArgs[I].SetterCB(*V, *NewValue, IConf, IIRB);
+ }
+ return CI;
+}
+
+Value *StoreIO::getPointer(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(V);
+ return SI.getPointerOperand();
+}
+
+Value *StoreIO::setPointer(Value &V, Value &NewV, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(V);
+ SI.setOperand(SI.getPointerOperandIndex(), &NewV);
+ return &SI;
+}
+
+Value *StoreIO::getPointerAS(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(V);
+ return getCI(&Ty, SI.getPointerAddressSpace());
+}
+
+Value *StoreIO::getValue(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(V);
+ return SI.getValueOperand();
+}
+
+Value *StoreIO::getValueSize(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(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<StoreInst>(V);
+ return getCI(&Ty, SI.getAlign().value());
+}
+
+Value *StoreIO::getValueTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(V);
+ return getCI(&Ty, SI.getValueOperand()->getType()->getTypeID());
+}
+
+Value *StoreIO::getAtomicityOrdering(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(V);
+ return getCI(&Ty, uint64_t(SI.getOrdering()));
+}
+
+Value *StoreIO::getSyncScopeId(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(V);
+ return getCI(&Ty, uint64_t(SI.getSyncScopeID()));
+}
+
+Value *StoreIO::isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &SI = cast<StoreInst>(V);
+ return getCI(&Ty, SI.isVolatile());
+}
+
+Value *LoadIO::getPointer(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &LI = cast<LoadInst>(V);
+ return LI.getPointerOperand();
+}
+
+Value *LoadIO::setPointer(Value &V, Value &NewV, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &LI = cast<LoadInst>(V);
+ LI.setOperand(LI.getPointerOperandIndex(), &NewV);
+ return &LI;
+}
+
+Value *LoadIO::getPointerAS(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &LI = cast<LoadInst>(V);
+ return getCI(&Ty, LI.getPointerAddressSpace());
+}
+
+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<LoadInst>(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<LoadInst>(V);
+ return getCI(&Ty, LI.getAlign().value());
+}
+
+Value *LoadIO::getValueTypeId(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &LI = cast<LoadInst>(V);
+ return getCI(&Ty, LI.getType()->getTypeID());
+}
+
+Value *LoadIO::getAtomicityOrdering(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &LI = cast<LoadInst>(V);
+ return getCI(&Ty, uint64_t(LI.getOrdering()));
+}
+
+Value *LoadIO::getSyncScopeId(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &LI = cast<LoadInst>(V);
+ return getCI(&Ty, uint64_t(LI.getSyncScopeID()));
+}
+
+Value *LoadIO::isVolatile(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &LI = cast<LoadInst>(V);
+ return getCI(&Ty, LI.isVolatile());
+}
diff --git a/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp
new file mode 100644
index 0000000000000..2f31c714ddf89
--- /dev/null
+++ b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp
@@ -0,0 +1,203 @@
+//===-- InstrumentorStubPrinter.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/IPO/Instrumentor.h"
+
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/StringSaver.h"
+
+#include <string>
+
+namespace llvm {
+namespace instrumentor {
+
+void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile) {
+ 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";
+ return;
+ }
+
+ json::OStream J(OS, 2);
+ J.objectBegin();
+
+ J.attributeBegin("configuration");
+ J.objectBegin();
+ for (auto *BaseCO : IConf.BaseConfigurationOpportunities) {
+ switch (BaseCO->Kind) {
+ case BaseConfigurationOpportunity::STRING:
+ J.attribute(BaseCO->Name, BaseCO->getString());
+ break;
+ case BaseConfigurationOpportunity::BOOLEAN:
+ J.attribute(BaseCO->Name, BaseCO->getBool());
+ break;
+ }
+ if (!BaseCO->Description.empty())
+ J.attribute(std::string(BaseCO->Name) + ".description",
+ BaseCO->Description);
+ }
+ J.objectEnd();
+ J.attributeEnd();
+
+ for (unsigned KindVal = 0; KindVal <= InstrumentationLocation::Last;
+ ++KindVal) {
+ auto Kind = InstrumentationLocation::KindTy(KindVal);
+
+ auto &KindChoices = IConf.IChoices[Kind];
+ if (KindChoices.empty())
+ continue;
+
+ J.attributeBegin(InstrumentationLocation::getKindStr(Kind));
+ J.objectBegin();
+ for (auto &ChoiceIt : KindChoices) {
+ J.attributeBegin(ChoiceIt.getKey());
+ J.objectBegin();
+ J.attribute("enabled", ChoiceIt.second->Enabled);
+ for (auto &ArgIt : ChoiceIt.second->IRTArgs) {
+ J.attribute(ArgIt.Name, ArgIt.Enabled);
+ if ((ArgIt.Flags & IRTArg::REPLACABLE) ||
+ (ArgIt.Flags & IRTArg::REPLACABLE_CUSTOM))
+ J.attribute(std::string(ArgIt.Name) + ".replace", true);
+ if (!ArgIt.Description.empty())
+ J.attribute(std::string(ArgIt.Name) + ".description",
+ ArgIt.Description);
+ }
+ J.objectEnd();
+ J.attributeEnd();
+ }
+ J.objectEnd();
+ J.attributeEnd();
+ }
+
+ J.objectEnd();
+}
+
+bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile) {
+ 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";
+ 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";
+ return false;
+ }
+ auto *Config = Parsed->getAsObject();
+ if (!Config) {
+ errs() << "WARNING: Failed to parse the instrumentor configuration file: "
+ "Expected "
+ "an object '{ ... }'\n";
+ return false;
+ }
+
+ StringMap<BaseConfigurationOpportunity *> BCOMap;
+ for (auto *BO : IConf.BaseConfigurationOpportunities)
+ 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";
+ continue;
+ }
+ if (It.first == "configuration") {
+ for (auto &ObjIt : *Obj) {
+ if (auto *BO = BCOMap.lookup(ObjIt.first)) {
+ switch (BO->Kind) {
+ case BaseConfigurationOpportunity::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";
+ break;
+ case BaseConfigurationOpportunity::BOOLEAN:
+ if (auto V = ObjIt.second.getAsBoolean())
+ BO->setBool(*V);
+ else
+ errs() << "WARNING: configuration key '" << ObjIt.first
+ << "' expects a boolean, value ignored\n";
+ break;
+ }
+ } else if (!StringRef(ObjIt.first).ends_with(".description")) {
+ errs() << "WARNING: configuration key not found and ignored: "
+ << ObjIt.first << "\n";
+ }
+ }
+ continue;
+ }
+
+ auto &IChoiceMap =
+ IConf.IChoices[InstrumentationLocation::getKindFromStr(It.first)];
+ for (auto &ObjIt : *Obj) {
+ auto *InnerObj = ObjIt.second.getAsObject();
+ if (!InnerObj) {
+ errs()
+ << "WARNING: malformed JSON configuration, expected an object.\n";
+ 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";
+ continue;
+ }
+ SeenIOs.insert(IO);
+ StringMap<bool> ValueMap, ReplaceMap;
+ for (auto &InnerObjIt : *InnerObj) {
+ auto Name = StringRef(InnerObjIt.first);
+ if (Name.consume_back(".replace"))
+ ReplaceMap[Name] = InnerObjIt.second.getAsBoolean().value_or(false);
+ else
+ ValueMap[Name] = InnerObjIt.second.getAsBoolean().value_or(false);
+ }
+ IO->Enabled = ValueMap["enabled"];
+ for (auto &IRArg : IO->IRTArgs) {
+ IRArg.Enabled = ValueMap[IRArg.Name];
+ if (!ReplaceMap.lookup(IRArg.Name)) {
+ IRArg.Flags &= ~IRTArg::REPLACABLE;
+ IRArg.Flags &= ~IRTArg::REPLACABLE_CUSTOM;
+ }
+ }
+ }
+ }
+
+ for (auto &IChoiceMap : IConf.IChoices)
+ for (auto &It : IChoiceMap)
+ if (!SeenIOs.count(It.second))
+ It.second->Enabled = false;
+
+ return true;
+}
+
+} // end namespace instrumentor
+} // end namespace llvm
diff --git a/llvm/test/Instrumentation/Instrumentor/counters.ll b/llvm/test/Instrumentation/Instrumentor/counters.ll
new file mode 100644
index 0000000000000..81512b6a054d6
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/counters.ll
@@ -0,0 +1,23 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instrumentor -instrumentor-read-config-file=%S/counters_config.json -S | FileCheck %s
+
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define i32 @foo() {
+; CHECK-LABEL: define i32 @foo() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP1:%.*]] = alloca i32, align 4
+; CHECK-NEXT: call void @__instrumentor_pre_store() #[[ATTR0:[0-9]+]]
+; CHECK-NEXT: store i32 0, ptr [[TMP1]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_store(i32 -1) #[[ATTR0]]
+; CHECK-NEXT: call void @__instrumentor_pre_load(i32 2) #[[ATTR0]]
+; CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[TMP1]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_load(i32 -2) #[[ATTR0]]
+; CHECK-NEXT: ret i32 [[TMP2]]
+;
+entry:
+ %0 = alloca i32, align 4
+ store i32 0, ptr %0, align 4
+ %2 = load i32, ptr %0, align 4
+ ret i32 %2
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/counters_config.json b/llvm/test/Instrumentation/Instrumentor/counters_config.json
new file mode 100644
index 0000000000000..542c62cd48d5d
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/counters_config.json
@@ -0,0 +1,25 @@
+{
+ "configuration": {
+ "runtime_prefix": "__instrumentor_"
+ },
+ "instruction_pre": {
+ "load": {
+ "enabled": true,
+ "id": true
+ },
+ "store": {
+ "enabled": true,
+ "id": false
+ }
+ },
+ "instruction_post": {
+ "load": {
+ "enabled": true,
+ "id": true
+ },
+ "store": {
+ "enabled": true,
+ "id": true
+ }
+ }
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/custom_config.json b/llvm/test/Instrumentation/Instrumentor/custom_config.json
new file mode 100644
index 0000000000000..856fbf37eb990
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/custom_config.json
@@ -0,0 +1,14 @@
+{
+ "configuration": {
+ "runtime_prefix": "__custom_",
+ "runtime_prefix.description": "The runtime API prefix."
+ },
+ "instruction_post": {
+ "load": {
+ "enabled": true
+ },
+ "store": {
+ "enabled": true
+ }
+ }
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/default_config.json b/llvm/test/Instrumentation/Instrumentor/default_config.json
new file mode 100644
index 0000000000000..2765d5c0116d2
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/default_config.json
@@ -0,0 +1,109 @@
+{
+ "configuration": {
+ "runtime_prefix": "__instrumentor_",
+ "runtime_prefix.description": "The runtime API prefix.",
+ "target_regex": "",
+ "target_regex.description": "Regular expression to be matched against the module target. Only targets that match this regex will be instrumented",
+ "host_enabled": true,
+ "host_enabled.description": "Instrument non-GPU targets",
+ "gpu_enabled": true,
+ "gpu_enabled.description": "Instrument GPU targets"
+ },
+ "instruction_pre": {
+ "load": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.replace": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "value_size": true,
+ "value_size.description": "The size of the loaded value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the loaded value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the load.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the load.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile load.",
+ "id": true,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ },
+ "store": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.replace": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "value": true,
+ "value.description": "The stored value.",
+ "value_size": true,
+ "value_size.description": "The size of the stored value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the stored value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the store.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the store.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile store.",
+ "id": true,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ }
+ },
+ "instruction_post": {
+ "load": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "value": true,
+ "value.replace": true,
+ "value.description": "The loaded value.",
+ "value_size": true,
+ "value_size.description": "The size of the loaded value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the loaded value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the load.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the load.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile load.",
+ "id": true,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ },
+ "store": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "value": true,
+ "value.description": "The stored value.",
+ "value_size": true,
+ "value_size.description": "The size of the stored value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the stored value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the store.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the store.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile store.",
+ "id": true,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ }
+ }
+}
\ No newline at end of file
diff --git a/llvm/test/Instrumentation/Instrumentor/load_store.ll b/llvm/test/Instrumentation/Instrumentor/load_store.ll
new file mode 100644
index 0000000000000..461bd2bc62e64
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/load_store.ll
@@ -0,0 +1,217 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instrumentor -instrumentor-read-config-file=%S/load_store_config.json -S | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+
+
+define noundef zeroext i1 @_Z15store_load_boolPb(ptr captures(none) noundef initializes((0, 1)) %A) {
+; CHECK-LABEL: define noundef zeroext i1 @_Z15store_load_boolPb(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 1)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 1, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i8 1, ptr [[TMP0]], align 1
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 1, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 1
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load i8, ptr [[TMP1]], align 1
+; CHECK-NEXT: [[TMP3:%.*]] = zext i8 [[TMP2]] to i64
+; CHECK-NEXT: [[TMP4:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP3]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = trunc i64 [[TMP4]] to i8
+; CHECK-NEXT: [[LOADEDV:%.*]] = trunc nuw i8 [[TMP5]] to i1
+; CHECK-NEXT: ret i1 [[LOADEDV]]
+;
+entry:
+ store i8 1, ptr %A, align 1
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 1
+ %0 = load i8, ptr %arrayidx, align 1
+ %loadedv = trunc nuw i8 %0 to i1
+ ret i1 %loadedv
+}
+
+
+define noundef signext i8 @_Z15store_load_charPc(ptr captures(none) noundef initializes((0, 1)) %A) {
+; CHECK-LABEL: define noundef signext i8 @_Z15store_load_charPc(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 1)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 1, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i8 1, ptr [[TMP0]], align 1
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 1, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 1
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load i8, ptr [[TMP1]], align 1
+; CHECK-NEXT: [[TMP3:%.*]] = zext i8 [[TMP2]] to i64
+; CHECK-NEXT: [[TMP4:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP3]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = trunc i64 [[TMP4]] to i8
+; CHECK-NEXT: ret i8 [[TMP5]]
+;
+entry:
+ store i8 1, ptr %A, align 1
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 1
+ %0 = load i8, ptr %arrayidx, align 1
+ ret i8 %0
+}
+
+
+define noundef signext i16 @_Z16store_load_shortPs(ptr captures(none) noundef initializes((0, 2)) %A) {
+; CHECK-LABEL: define noundef signext i16 @_Z16store_load_shortPs(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 2)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 2, i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i16 2, ptr [[TMP0]], align 2
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 2, i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 2
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load i16, ptr [[TMP1]], align 2
+; CHECK-NEXT: [[TMP3:%.*]] = zext i16 [[TMP2]] to i64
+; CHECK-NEXT: [[TMP4:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP3]], i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = trunc i64 [[TMP4]] to i16
+; CHECK-NEXT: ret i16 [[TMP5]]
+;
+entry:
+ store i16 2, ptr %A, align 2
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 2
+ %0 = load i16, ptr %arrayidx, align 2
+ ret i16 %0
+}
+
+
+define noundef i32 @_Z14store_load_intPi(ptr captures(none) noundef initializes((0, 4)) %A) {
+; CHECK-LABEL: define noundef i32 @_Z14store_load_intPi(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 4)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 3, i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i32 3, ptr [[TMP0]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 3, i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[TMP3:%.*]] = zext i32 [[TMP2]] to i64
+; CHECK-NEXT: [[TMP4:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP3]], i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = trunc i64 [[TMP4]] to i32
+; CHECK-NEXT: ret i32 [[TMP5]]
+;
+entry:
+ store i32 3, ptr %A, align 4
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 4
+ %0 = load i32, ptr %arrayidx, align 4
+ ret i32 %0
+}
+
+
+define noundef i64 @_Z15store_load_longPl(ptr captures(none) noundef initializes((0, 8)) %A) {
+; CHECK-LABEL: define noundef i64 @_Z15store_load_longPl(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 8)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 4, i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i64 4, ptr [[TMP0]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 4, i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 8
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load i64, ptr [[TMP1]], align 8
+; CHECK-NEXT: [[TMP3:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP2]], i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret i64 [[TMP3]]
+;
+entry:
+ store i64 4, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 8
+ %0 = load i64, ptr %arrayidx, align 8
+ ret i64 %0
+}
+
+
+define noundef i128 @_Z20store_load_long_longPx(ptr captures(none) noundef initializes((0, 16)) %A) {
+; CHECK-LABEL: define noundef i128 @_Z20store_load_long_longPx(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 16)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = alloca i128, align 16
+; CHECK-NEXT: store i128 5, ptr [[TMP0]], align 16
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i128 5, ptr [[TMP1]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 16
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load i128, ptr [[TMP2]], align 8
+; CHECK-NEXT: store i128 [[TMP3]], ptr [[TMP0]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_load_ind(ptr [[ARRAYIDX]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP4:%.*]] = load i128, ptr [[TMP0]], align 16
+; CHECK-NEXT: ret i128 [[TMP4]]
+;
+entry:
+ store i128 5, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 16
+ %0 = load i128, ptr %arrayidx, align 8
+ ret i128 %0
+}
+
+
+define noundef float @_Z16store_load_floatPf(ptr captures(none) noundef initializes((0, 4)) %A) {
+; CHECK-LABEL: define noundef float @_Z16store_load_floatPf(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 4)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 1086324736, i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store float 6.000000e+00, ptr [[TMP0]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 1086324736, i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load float, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[TMP3:%.*]] = bitcast float [[TMP2]] to i32
+; CHECK-NEXT: [[TMP4:%.*]] = zext i32 [[TMP3]] to i64
+; CHECK-NEXT: [[TMP5:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP4]], i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP6:%.*]] = trunc i64 [[TMP5]] to i32
+; CHECK-NEXT: [[TMP7:%.*]] = bitcast i32 [[TMP6]] to float
+; CHECK-NEXT: ret float [[TMP7]]
+;
+entry:
+ store float 6.000000e+00, ptr %A, align 4
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 4
+ %0 = load float, ptr %arrayidx, align 4
+ ret float %0
+}
+
+
+define noundef double @_Z17store_load_doublePd(ptr captures(none) noundef initializes((0, 8)) %A) {
+; CHECK-LABEL: define noundef double @_Z17store_load_doublePd(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 8)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 4619567317775286272, i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store double 7.000000e+00, ptr [[TMP0]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 4619567317775286272, i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 8
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load double, ptr [[TMP1]], align 8
+; CHECK-NEXT: [[TMP3:%.*]] = bitcast double [[TMP2]] to i64
+; CHECK-NEXT: [[TMP4:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP3]], i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = bitcast i64 [[TMP4]] to double
+; CHECK-NEXT: ret double [[TMP5]]
+;
+entry:
+ store double 7.000000e+00, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 8
+ %0 = load double, ptr %arrayidx, align 8
+ ret double %0
+}
+
+
+define noundef x86_fp80 @_Z22store_load_long_doublePe(ptr captures(none) noundef initializes((0, 10)) %A) {
+; CHECK-LABEL: define noundef x86_fp80 @_Z22store_load_long_doublePe(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 10)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = alloca x86_fp80, align 16
+; CHECK-NEXT: store x86_fp80 0xK40028000000000000000, ptr [[TMP0]], align 16
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store x86_fp80 0xK40028000000000000000, ptr [[TMP1]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 16
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load x86_fp80, ptr [[TMP2]], align 16
+; CHECK-NEXT: store x86_fp80 [[TMP3]], ptr [[TMP0]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_load_ind(ptr [[ARRAYIDX]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP4:%.*]] = load x86_fp80, ptr [[TMP0]], align 16
+; CHECK-NEXT: ret x86_fp80 [[TMP4]]
+;
+entry:
+ store x86_fp80 0xK40028000000000000000, ptr %A, align 16
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 16
+ %0 = load x86_fp80, ptr %arrayidx, align 16
+ ret x86_fp80 %0
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/load_store_args.ll b/llvm/test/Instrumentation/Instrumentor/load_store_args.ll
new file mode 100644
index 0000000000000..ad4c1b8d0fc70
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/load_store_args.ll
@@ -0,0 +1,226 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instrumentor -instrumentor-read-config-file=%S/load_store_config.json -S | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+
+
+define noundef zeroext i1 @_Z15store_load_boolPbb(ptr captures(none) noundef initializes((0, 1)) %A, i1 noundef zeroext %Val) {
+; CHECK-LABEL: define noundef zeroext i1 @_Z15store_load_boolPbb(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 1)) [[A:%.*]], i1 noundef zeroext [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[STOREDV:%.*]] = zext i1 [[VAL]] to i8
+; CHECK-NEXT: [[TMP0:%.*]] = zext i8 [[STOREDV]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i8 [[STOREDV]], ptr [[TMP1]], align 1
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 1
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load i8, ptr [[TMP2]], align 1
+; CHECK-NEXT: [[TMP4:%.*]] = zext i8 [[TMP3]] to i64
+; CHECK-NEXT: [[TMP5:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP4]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP7:%.*]] = trunc i64 [[TMP5]] to i8
+; CHECK-NEXT: [[LOADEDV2:%.*]] = trunc nuw i8 [[TMP7]] to i1
+; CHECK-NEXT: ret i1 [[LOADEDV2]]
+;
+entry:
+ %storedv = zext i1 %Val to i8
+ store i8 %storedv, ptr %A, align 1
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 1
+ %0 = load i8, ptr %arrayidx, align 1
+ %loadedv2 = trunc nuw i8 %0 to i1
+ ret i1 %loadedv2
+}
+
+
+define noundef signext i8 @_Z15store_load_charPcc(ptr captures(none) noundef initializes((0, 1)) %A, i8 noundef signext %Val) {
+; CHECK-LABEL: define noundef signext i8 @_Z15store_load_charPcc(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 1)) [[A:%.*]], i8 noundef signext [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = zext i8 [[VAL]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i8 [[VAL]], ptr [[TMP1]], align 1
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 1
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load i8, ptr [[TMP2]], align 1
+; CHECK-NEXT: [[TMP4:%.*]] = zext i8 [[TMP3]] to i64
+; CHECK-NEXT: [[TMP5:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP4]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP7:%.*]] = trunc i64 [[TMP5]] to i8
+; CHECK-NEXT: ret i8 [[TMP7]]
+;
+entry:
+ store i8 %Val, ptr %A, align 1
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 1
+ %0 = load i8, ptr %arrayidx, align 1
+ ret i8 %0
+}
+
+
+define noundef signext i16 @_Z16store_load_shortPss(ptr captures(none) noundef initializes((0, 2)) %A, i16 noundef signext %Val) {
+; CHECK-LABEL: define noundef signext i16 @_Z16store_load_shortPss(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 2)) [[A:%.*]], i16 noundef signext [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[VAL]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i16 [[VAL]], ptr [[TMP1]], align 2
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 2
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load i16, ptr [[TMP2]], align 2
+; CHECK-NEXT: [[TMP4:%.*]] = zext i16 [[TMP3]] to i64
+; CHECK-NEXT: [[TMP5:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP4]], i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP7:%.*]] = trunc i64 [[TMP5]] to i16
+; CHECK-NEXT: ret i16 [[TMP7]]
+;
+entry:
+ store i16 %Val, ptr %A, align 2
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 2
+ %0 = load i16, ptr %arrayidx, align 2
+ ret i16 %0
+}
+
+
+define noundef i32 @_Z14store_load_intPii(ptr captures(none) noundef initializes((0, 4)) %A, i32 noundef %Val) {
+; CHECK-LABEL: define noundef i32 @_Z14store_load_intPii(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 4)) [[A:%.*]], i32 noundef [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = zext i32 [[VAL]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i32 [[VAL]], ptr [[TMP1]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[TMP4:%.*]] = zext i32 [[TMP3]] to i64
+; CHECK-NEXT: [[TMP5:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP4]], i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP7:%.*]] = trunc i64 [[TMP5]] to i32
+; CHECK-NEXT: ret i32 [[TMP7]]
+;
+entry:
+ store i32 %Val, ptr %A, align 4
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 4
+ %0 = load i32, ptr %arrayidx, align 4
+ ret i32 %0
+}
+
+
+define noundef i64 @_Z15store_load_longPll(ptr captures(none) noundef initializes((0, 8)) %A, i64 noundef %Val) {
+; CHECK-LABEL: define noundef i64 @_Z15store_load_longPll(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 8)) [[A:%.*]], i64 noundef [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 [[VAL]], i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i64 [[VAL]], ptr [[TMP0]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 [[VAL]], i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 8
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load i64, ptr [[TMP1]], align 8
+; CHECK-NEXT: [[TMP4:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP2]], i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret i64 [[TMP4]]
+;
+entry:
+ store i64 %Val, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 8
+ %0 = load i64, ptr %arrayidx, align 8
+ ret i64 %0
+}
+
+
+define noundef i128 @_Z20store_load_long_longPxx(ptr captures(none) noundef initializes((0, 16)) %A, i128 noundef %Val) {
+; CHECK-LABEL: define noundef i128 @_Z20store_load_long_longPxx(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 16)) [[A:%.*]], i128 noundef [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = alloca i128, align 16
+; CHECK-NEXT: store i128 [[VAL]], ptr [[TMP0]], align 16
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i128 [[VAL]], ptr [[TMP1]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 16
+; CHECK-NEXT: [[TMP4:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load i128, ptr [[TMP4]], align 8
+; CHECK-NEXT: store i128 [[TMP3]], ptr [[TMP0]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_load_ind(ptr [[ARRAYIDX]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load i128, ptr [[TMP0]], align 16
+; CHECK-NEXT: ret i128 [[TMP2]]
+;
+entry:
+ store i128 %Val, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 16
+ %0 = load i128, ptr %arrayidx, align 8
+ ret i128 %0
+}
+
+
+define noundef float @_Z16store_load_floatPff(ptr captures(none) noundef initializes((0, 4)) %A, float noundef %Val) {
+; CHECK-LABEL: define noundef float @_Z16store_load_floatPff(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 4)) [[A:%.*]], float noundef [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = bitcast float [[VAL]] to i32
+; CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 [[TMP1]], i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store float [[VAL]], ptr [[TMP2]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 [[TMP1]], i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4
+; CHECK-NEXT: [[TMP3:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP4:%.*]] = load float, ptr [[TMP3]], align 4
+; CHECK-NEXT: [[TMP5:%.*]] = bitcast float [[TMP4]] to i32
+; CHECK-NEXT: [[TMP6:%.*]] = zext i32 [[TMP5]] to i64
+; CHECK-NEXT: [[TMP7:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP6]], i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP8:%.*]] = trunc i64 [[TMP7]] to i32
+; CHECK-NEXT: [[TMP10:%.*]] = bitcast i32 [[TMP8]] to float
+; CHECK-NEXT: ret float [[TMP10]]
+;
+entry:
+ store float %Val, ptr %A, align 4
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 4
+ %0 = load float, ptr %arrayidx, align 4
+ ret float %0
+}
+
+
+define noundef double @_Z17store_load_doublePdd(ptr captures(none) noundef initializes((0, 8)) %A, double noundef %Val) {
+; CHECK-LABEL: define noundef double @_Z17store_load_doublePdd(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 8)) [[A:%.*]], double noundef [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = bitcast double [[VAL]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store double [[VAL]], ptr [[TMP1]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 [[TMP0]], i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 8
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load double, ptr [[TMP2]], align 8
+; CHECK-NEXT: [[TMP4:%.*]] = bitcast double [[TMP3]] to i64
+; CHECK-NEXT: [[TMP5:%.*]] = call i64 @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP4]], i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP7:%.*]] = bitcast i64 [[TMP5]] to double
+; CHECK-NEXT: ret double [[TMP7]]
+;
+entry:
+ store double %Val, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 8
+ %0 = load double, ptr %arrayidx, align 8
+ ret double %0
+}
+
+
+define noundef x86_fp80 @_Z22store_load_long_doublePee(ptr captures(none) noundef initializes((0, 10)) %A, x86_fp80 noundef %Val) {
+; CHECK-LABEL: define noundef x86_fp80 @_Z22store_load_long_doublePee(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 10)) [[A:%.*]], x86_fp80 noundef [[VAL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = alloca x86_fp80, align 16
+; CHECK-NEXT: store x86_fp80 [[VAL]], ptr [[TMP0]], align 16
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_pre_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store x86_fp80 [[VAL]], ptr [[TMP1]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 16
+; CHECK-NEXT: [[TMP4:%.*]] = call ptr @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load x86_fp80, ptr [[TMP4]], align 16
+; CHECK-NEXT: store x86_fp80 [[TMP3]], ptr [[TMP0]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_load_ind(ptr [[ARRAYIDX]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP2:%.*]] = load x86_fp80, ptr [[TMP0]], align 16
+; CHECK-NEXT: ret x86_fp80 [[TMP2]]
+;
+entry:
+ store x86_fp80 %Val, ptr %A, align 16
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 16
+ %0 = load x86_fp80, ptr %arrayidx, align 16
+ ret x86_fp80 %0
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/load_store_config.json b/llvm/test/Instrumentation/Instrumentor/load_store_config.json
new file mode 100644
index 0000000000000..b96a02a4e09ea
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/load_store_config.json
@@ -0,0 +1,107 @@
+{
+ "configuration": {
+ "runtime_prefix": "__instrumentor_",
+ "runtime_prefix.description": "The runtime API prefix.",
+ "runtime_stubs_file": "rt.c",
+ "runtime_stubs_file.description": "The file into which runtime stubs should be written.",
+ "demangle_function_names": true,
+ "demangle_function_names.description": "Demangle functions names passed to the runtime."
+ },
+ "instruction_pre": {
+ "load": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.replace": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "base_pointer_info": false,
+ "base_pointer_info.description": "The runtime provided base pointer info.",
+ "value_size": true,
+ "value_size.description": "The size of the loaded value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the loaded value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the load.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the load.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile load."
+ },
+ "store": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.replace": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "base_pointer_info": false,
+ "base_pointer_info.description": "The runtime provided base pointer info.",
+ "value": true,
+ "value.description": "The stored value.",
+ "value_size": true,
+ "value_size.description": "The size of the stored value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the stored value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the store.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the store.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile store."
+ }
+ },
+ "instruction_post": {
+ "load": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "base_pointer_info": false,
+ "base_pointer_info.description": "The runtime provided base pointer info.",
+ "value": true,
+ "value.replace": true,
+ "value.description": "The loaded value.",
+ "value_size": true,
+ "value_size.description": "The size of the loaded value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the loaded value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the load.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the load.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile load."
+ },
+ "store": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "base_pointer_info": false,
+ "base_pointer_info.description": "The runtime provided base pointer info.",
+ "value": true,
+ "value.description": "The stored value.",
+ "value_size": true,
+ "value_size.description": "The size of the stored value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the stored value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the store.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the store.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile store."
+ }
+ }
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/load_store_noreplace.ll b/llvm/test/Instrumentation/Instrumentor/load_store_noreplace.ll
new file mode 100644
index 0000000000000..5edb8d2e8f892
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/load_store_noreplace.ll
@@ -0,0 +1,208 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instrumentor -instrumentor-read-config-file=%S/load_store_noreplace_config.json -S | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+
+
+define noundef zeroext i1 @_Z15store_load_boolPb(ptr captures(none) noundef initializes((0, 1)) %A) {
+; CHECK-LABEL: define noundef zeroext i1 @_Z15store_load_boolPb(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 1)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @__instrumentor_pre_store(ptr [[A]], i32 0, i64 1, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i8 1, ptr [[A]], align 1
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 1, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 1
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = load i8, ptr [[ARRAYIDX]], align 1
+; CHECK-NEXT: [[TMP1:%.*]] = zext i8 [[TMP5]] to i64
+; CHECK-NEXT: call void @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP1]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[LOADEDV:%.*]] = trunc nuw i8 [[TMP5]] to i1
+; CHECK-NEXT: ret i1 [[LOADEDV]]
+;
+entry:
+ store i8 1, ptr %A, align 1
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 1
+ %0 = load i8, ptr %arrayidx, align 1
+ %loadedv = trunc nuw i8 %0 to i1
+ ret i1 %loadedv
+}
+
+
+define noundef signext i8 @_Z15store_load_charPc(ptr captures(none) noundef initializes((0, 1)) %A) {
+; CHECK-LABEL: define noundef signext i8 @_Z15store_load_charPc(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 1)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @__instrumentor_pre_store(ptr [[A]], i32 0, i64 1, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i8 1, ptr [[A]], align 1
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 1, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 1
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = load i8, ptr [[ARRAYIDX]], align 1
+; CHECK-NEXT: [[TMP1:%.*]] = zext i8 [[TMP5]] to i64
+; CHECK-NEXT: call void @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP1]], i64 1, i64 1, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret i8 [[TMP5]]
+;
+entry:
+ store i8 1, ptr %A, align 1
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 1
+ %0 = load i8, ptr %arrayidx, align 1
+ ret i8 %0
+}
+
+
+define noundef signext i16 @_Z16store_load_shortPs(ptr captures(none) noundef initializes((0, 2)) %A) {
+; CHECK-LABEL: define noundef signext i16 @_Z16store_load_shortPs(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 2)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @__instrumentor_pre_store(ptr [[A]], i32 0, i64 2, i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i16 2, ptr [[A]], align 2
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 2, i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 2
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = load i16, ptr [[ARRAYIDX]], align 2
+; CHECK-NEXT: [[TMP1:%.*]] = zext i16 [[TMP5]] to i64
+; CHECK-NEXT: call void @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP1]], i64 2, i64 2, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret i16 [[TMP5]]
+;
+entry:
+ store i16 2, ptr %A, align 2
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 2
+ %0 = load i16, ptr %arrayidx, align 2
+ ret i16 %0
+}
+
+
+define noundef i32 @_Z14store_load_intPi(ptr captures(none) noundef initializes((0, 4)) %A) {
+; CHECK-LABEL: define noundef i32 @_Z14store_load_intPi(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 4)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @__instrumentor_pre_store(ptr [[A]], i32 0, i64 3, i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i32 3, ptr [[A]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 3, i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+; CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[TMP5]] to i64
+; CHECK-NEXT: call void @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP1]], i64 4, i64 4, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret i32 [[TMP5]]
+;
+entry:
+ store i32 3, ptr %A, align 4
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 4
+ %0 = load i32, ptr %arrayidx, align 4
+ ret i32 %0
+}
+
+
+define noundef i64 @_Z15store_load_longPl(ptr captures(none) noundef initializes((0, 8)) %A) {
+; CHECK-LABEL: define noundef i64 @_Z15store_load_longPl(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 8)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @__instrumentor_pre_store(ptr [[A]], i32 0, i64 4, i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i64 4, ptr [[A]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 4, i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 8
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP3:%.*]] = load i64, ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP3]], i64 8, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret i64 [[TMP3]]
+;
+entry:
+ store i64 4, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 8
+ %0 = load i64, ptr %arrayidx, align 8
+ ret i64 %0
+}
+
+
+define noundef i128 @_Z20store_load_long_longPx(ptr captures(none) noundef initializes((0, 16)) %A) {
+; CHECK-LABEL: define noundef i128 @_Z20store_load_long_longPx(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 16)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = alloca i128, align 16
+; CHECK-NEXT: store i128 5, ptr [[TMP0]], align 16
+; CHECK-NEXT: call void @__instrumentor_pre_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store i128 5, ptr [[A]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 16
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP4:%.*]] = load i128, ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT: store i128 [[TMP4]], ptr [[TMP0]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_load_ind(ptr [[ARRAYIDX]], i32 0, ptr [[TMP0]], i64 16, i64 8, i32 12, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret i128 [[TMP4]]
+;
+entry:
+ store i128 5, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 16
+ %0 = load i128, ptr %arrayidx, align 8
+ ret i128 %0
+}
+
+
+define noundef float @_Z16store_load_floatPf(ptr captures(none) noundef initializes((0, 4)) %A) {
+; CHECK-LABEL: define noundef float @_Z16store_load_floatPf(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 4)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @__instrumentor_pre_store(ptr [[A]], i32 0, i64 1086324736, i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store float 6.000000e+00, ptr [[A]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 1086324736, i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP7:%.*]] = load float, ptr [[ARRAYIDX]], align 4
+; CHECK-NEXT: [[TMP1:%.*]] = bitcast float [[TMP7]] to i32
+; CHECK-NEXT: [[TMP2:%.*]] = zext i32 [[TMP1]] to i64
+; CHECK-NEXT: call void @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP2]], i64 4, i64 4, i32 2, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret float [[TMP7]]
+;
+entry:
+ store float 6.000000e+00, ptr %A, align 4
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 4
+ %0 = load float, ptr %arrayidx, align 4
+ ret float %0
+}
+
+
+define noundef double @_Z17store_load_doublePd(ptr captures(none) noundef initializes((0, 8)) %A) {
+; CHECK-LABEL: define noundef double @_Z17store_load_doublePd(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 8)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @__instrumentor_pre_store(ptr [[A]], i32 0, i64 4619567317775286272, i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store double 7.000000e+00, ptr [[A]], align 8
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[A]], i32 0, i64 4619567317775286272, i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 8
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP5:%.*]] = load double, ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT: [[TMP1:%.*]] = bitcast double [[TMP5]] to i64
+; CHECK-NEXT: call void @__instrumentor_post_load(ptr [[ARRAYIDX]], i32 0, i64 [[TMP1]], i64 8, i64 8, i32 3, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret double [[TMP5]]
+;
+entry:
+ store double 7.000000e+00, ptr %A, align 8
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 8
+ %0 = load double, ptr %arrayidx, align 8
+ ret double %0
+}
+
+
+define noundef x86_fp80 @_Z22store_load_long_doublePe(ptr captures(none) noundef initializes((0, 10)) %A) {
+; CHECK-LABEL: define noundef x86_fp80 @_Z22store_load_long_doublePe(
+; CHECK-SAME: ptr noundef captures(none) initializes((0, 10)) [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = alloca x86_fp80, align 16
+; CHECK-NEXT: store x86_fp80 0xK40028000000000000000, ptr [[TMP0]], align 16
+; CHECK-NEXT: call void @__instrumentor_pre_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: store x86_fp80 0xK40028000000000000000, ptr [[A]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_store_ind(ptr [[A]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 16
+; CHECK-NEXT: call void @__instrumentor_pre_load(ptr [[ARRAYIDX]], i32 0, i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: [[TMP4:%.*]] = load x86_fp80, ptr [[ARRAYIDX]], align 16
+; CHECK-NEXT: store x86_fp80 [[TMP4]], ptr [[TMP0]], align 16
+; CHECK-NEXT: call void @__instrumentor_post_load_ind(ptr [[ARRAYIDX]], i32 0, ptr [[TMP0]], i64 10, i64 16, i32 4, i32 0, i8 1, i8 0)
+; CHECK-NEXT: ret x86_fp80 [[TMP4]]
+;
+entry:
+ store x86_fp80 0xK40028000000000000000, ptr %A, align 16
+ %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 16
+ %0 = load x86_fp80, ptr %arrayidx, align 16
+ ret x86_fp80 %0
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/load_store_noreplace_config.json b/llvm/test/Instrumentation/Instrumentor/load_store_noreplace_config.json
new file mode 100644
index 0000000000000..f59de1c010adf
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/load_store_noreplace_config.json
@@ -0,0 +1,107 @@
+{
+ "configuration": {
+ "runtime_prefix": "__instrumentor_",
+ "runtime_prefix.description": "The runtime API prefix.",
+ "runtime_stubs_file": "rt.c",
+ "runtime_stubs_file.description": "The file into which runtime stubs should be written.",
+ "demangle_function_names": true,
+ "demangle_function_names.description": "Demangle functions names passed to the runtime."
+ },
+ "instruction_pre": {
+ "load": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.replace": false,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "base_pointer_info": false,
+ "base_pointer_info.description": "The runtime provided base pointer info.",
+ "value_size": true,
+ "value_size.description": "The size of the loaded value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the loaded value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the load.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the load.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile load."
+ },
+ "store": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.replace": false,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "base_pointer_info": false,
+ "base_pointer_info.description": "The runtime provided base pointer info.",
+ "value": true,
+ "value.description": "The stored value.",
+ "value_size": true,
+ "value_size.description": "The size of the stored value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the stored value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the store.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the store.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile store."
+ }
+ },
+ "instruction_post": {
+ "load": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "base_pointer_info": false,
+ "base_pointer_info.description": "The runtime provided base pointer info.",
+ "value": true,
+ "value.replace": false,
+ "value.description": "The loaded value.",
+ "value_size": true,
+ "value_size.description": "The size of the loaded value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the loaded value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the load.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the load.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile load."
+ },
+ "store": {
+ "enabled": true,
+ "pointer": true,
+ "pointer.description": "The accessed pointer.",
+ "pointer_as": true,
+ "pointer_as.description": "The address space of the accessed pointer.",
+ "base_pointer_info": false,
+ "base_pointer_info.description": "The runtime provided base pointer info.",
+ "value": true,
+ "value.description": "The stored value.",
+ "value_size": true,
+ "value_size.description": "The size of the stored value.",
+ "alignment": true,
+ "alignment.description": "The known access alignment.",
+ "value_type_id": true,
+ "value_type_id.description": "The type id of the stored value.",
+ "atomicity_ordering": true,
+ "atomicity_ordering.description": "The atomicity ordering of the store.",
+ "sync_scope_id": true,
+ "sync_scope_id.description": "The sync scope id of the store.",
+ "is_volatile": true,
+ "is_volatile.description": "Flag indicating a volatile store."
+ }
+ }
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/read_config.ll b/llvm/test/Instrumentation/Instrumentor/read_config.ll
new file mode 100644
index 0000000000000..519093cef72e2
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/read_config.ll
@@ -0,0 +1,26 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instrumentor -instrumentor-read-config-file=%S/custom_config.json -S | FileCheck %s
+
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define i32 @bar(i1 %c) {
+; CHECK-LABEL: define i32 @bar(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
+; CHECK-NEXT: [[B:%.*]] = alloca i32, i32 5, align 4
+; CHECK-NEXT: [[S:%.*]] = select i1 [[C]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[S]], align 4
+; CHECK-NEXT: call void @__custom_post_load() #[[ATTR0:[0-9]+]]
+; CHECK-NEXT: store i32 [[V]], ptr [[S]], align 4
+; CHECK-NEXT: call void @__custom_post_store() #[[ATTR0]]
+; CHECK-NEXT: ret i32 [[V]]
+;
+entry:
+ %a = alloca i32
+ %b = alloca i32, i32 5
+ %s = select i1 %c, ptr %a, ptr %b
+ %v = load i32, ptr %s
+ store i32 %v, ptr %s
+ ret i32 %v
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/write_config.ll b/llvm/test/Instrumentation/Instrumentor/write_config.ll
new file mode 100644
index 0000000000000..ab6d0599279d2
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/write_config.ll
@@ -0,0 +1,3 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instrumentor -instrumentor-write-config-file=%t.json
+; RUN: diff %t.json %S/default_config.json
>From 3f03cb400b0c8d357f04f92832cf1b239ae0853a Mon Sep 17 00:00:00 2001
From: Kevin Sala <salapenades1 at llnl.gov>
Date: Tue, 10 Jun 2025 12:03:23 -0700
Subject: [PATCH 2/2] Fix most review comments
---
.../llvm/Transforms/IPO/Instrumentor.h | 519 ++++++++----------
.../Transforms/IPO/InstrumentorConfigFile.h | 8 +-
.../llvm/Transforms/IPO/InstrumentorUtils.h | 172 ++++++
llvm/lib/Transforms/IPO/Instrumentor.cpp | 287 +++++++---
.../Transforms/IPO/InstrumentorConfigFile.cpp | 87 +--
5 files changed, 680 insertions(+), 393 deletions(-)
create mode 100644 llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
index 6fb5a06305096..26445d221d00f 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 370e3b7303b34..155df1931d32c 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 0000000000000..c38462dc58015
--- /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 17657cfae8fb5..ba5ca6dbf4db0 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 @@ class InstrumentorImpl final {
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 @@ class InstrumentorImpl final {
/// 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 2f31c714ddf89..949ca3f9ffa2a 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 writing: ") +
+ 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);
More information about the llvm-commits
mailing list