[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