[llvm] 7b4175c - [Instrumentor] Add Instrumentor pass (#138958)
via llvm-commits
llvm-commits at lists.llvm.org
Wed May 6 06:06:19 PDT 2026
Author: Kevin Sala Penades
Date: 2026-05-06T06:06:13-07:00
New Revision: 7b4175c17f16f6597e5c80a017ea2f8aa1b09fcb
URL: https://github.com/llvm/llvm-project/commit/7b4175c17f16f6597e5c80a017ea2f8aa1b09fcb
DIFF: https://github.com/llvm/llvm-project/commit/7b4175c17f16f6597e5c80a017ea2f8aa1b09fcb.diff
LOG: [Instrumentor] Add Instrumentor pass (#138958)
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.
The default JSON config file can be found in:
`llvm/test/Instrumentation/Instrumentor/default_config.json`. More
information about Instrumentor in the
[RFC](https://discourse.llvm.org/t/rfc-introducing-instrumentor-easily-customizable-code-instrumentation/86020).
This is only a squash commit of several contributions to the
Instrumentor. The authors and contributors of this pass are:
- Johannes Doerfert @jdoerfert
- Kevin Sala @kevinsala
- Ivan Radanov Ivanov @ivanradanov
- Ethan Luis McDonough @EthanLuisMcDonough
---------
Co-authored-by: Johannes Doerfert <johannes at jdoerfert.de>
Co-authored-by: Ivan Radanov Ivanov <ivanov.i.aa at m.titech.ac.jp>
Co-authored-by: Ethan Luis McDonough <ethanluismcdonough at gmail.com>
Added:
llvm/include/llvm/Transforms/IPO/Instrumentor.h
llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h
llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h
llvm/lib/Transforms/IPO/Instrumentor.cpp
llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp
llvm/test/Instrumentation/Instrumentor/counters.ll
llvm/test/Instrumentation/Instrumentor/counters_config.json
llvm/test/Instrumentation/Instrumentor/custom_config.json
llvm/test/Instrumentation/Instrumentor/default_config.json
llvm/test/Instrumentation/Instrumentor/load_store.ll
llvm/test/Instrumentation/Instrumentor/load_store_args.ll
llvm/test/Instrumentation/Instrumentor/load_store_config.json
llvm/test/Instrumentation/Instrumentor/load_store_noreplace.ll
llvm/test/Instrumentation/Instrumentor/load_store_noreplace_config.json
llvm/test/Instrumentation/Instrumentor/read_config.ll
llvm/test/Instrumentation/Instrumentor/write_config.ll
Modified:
llvm/lib/Passes/PassBuilder.cpp
llvm/lib/Passes/PassBuilderPipelines.cpp
llvm/lib/Passes/PassRegistry.def
llvm/lib/Transforms/IPO/CMakeLists.txt
Removed:
################################################################################
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
new file mode 100644
index 0000000000000..c115bea24bb27
--- /dev/null
+++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
@@ -0,0 +1,707 @@
+//===-- Instrumentor.h - Highly configurable instrumentation pass ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// The Instrumentor, 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/IPO/InstrumentorUtils.h"
+#include "llvm/Transforms/Utils/Instrumentation.h"
+
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <tuple>
+
+namespace llvm {
+namespace instrumentor {
+
+struct InstrumentationConfig;
+struct InstrumentationOpportunity;
+
+/// 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 an instrumentation runtime function.
+struct IRTArg {
+ /// Flags describing the possible properties of an argument.
+ enum IRArgFlagTy {
+ NONE = 0,
+ STRING = 1 << 0,
+ REPLACABLE = 1 << 1,
+ 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)
+ : Enabled(Enabled), Ty(Ty), Name(Name), Description(Description),
+ 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;
+
+ /// 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 {
+ /// 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,
+ InstrumentorIRBuilderTy &IIRB,
+ 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,
+ GLOBAL_PRE,
+ GLOBAL_POST,
+ FUNCTION_PRE,
+ FUNCTION_POST,
+ BASIC_BLOCK_PRE,
+ BASIC_BLOCK_POST,
+ INSTRUCTION_PRE,
+ INSTRUCTION_POST,
+ 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:
+ 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";
+ }
+ 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)
+ .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)
+ .Default(Last);
+ }
+
+ /// Return whether a location kind is positioned before the event occurs.
+ 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:
+ 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!");
+ return Opcode;
+ }
+
+private:
+ /// The kind (type and position) of the instrumentation location.
+ const KindTy Kind;
+
+ /// The opcode for instruction instrumentation locations.
+ const unsigned Opcode = -1;
+};
+
+/// An option for the base configuration.
+struct BaseConfigurationOption {
+ /// The possible types of options.
+ enum KindTy {
+ STRING,
+ BOOLEAN,
+ };
+
+ /// Create a boolean option with \p Name name, \p Description description and
+ /// \p DefaultValue as boolean default value.
+ static BaseConfigurationOption *createBoolOption(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 *createStringOption(InstrumentationConfig &IC,
+ StringRef Name,
+ StringRef Description,
+ StringRef DefaultValue);
+
+ /// Helper union that holds any possible option type.
+ union ValueTy {
+ 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!");
+ Value.Bool = B;
+ }
+ bool getBool() const {
+ assert(Kind == BOOLEAN && "Not a boolean!");
+ 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!");
+ Value.String = S;
+ }
+ StringRef getString() const {
+ assert(Kind == STRING && "Not a string!");
+ return Value.String;
+ }
+ ///}
+
+ /// The information of the option.
+ ///{
+ StringRef Name;
+ StringRef Description;
+ KindTy Kind;
+ ValueTy Value = {0};
+ ///}
+
+ /// Construct a base configuration option.
+ BaseConfigurationOption(StringRef Name, StringRef Desc, KindTy Kind)
+ : Name(Name), Description(Desc), Kind(Kind) {}
+};
+
+/// 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 = BaseConfigurationOption::createStringOption(
+ *this, "runtime_prefix", "The runtime API prefix.", "__instrumentor_");
+ TargetRegex = BaseConfigurationOption::createStringOption(
+ *this, "target_regex",
+ "Regular expression to be matched against the module target. "
+ "Only targets that match this regex will be instrumented",
+ "");
+ HostEnabled = BaseConfigurationOption::createBoolOption(
+ *this, "host_enabled", "Instrument non-GPU targets", true);
+ GPUEnabled = BaseConfigurationOption::createBoolOption(
+ *this, "gpu_enabled", "Instrument GPU targets", 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();
+ }
+
+ /// Add the base configuration option \p BCO into the list of base options.
+ void addBaseChoice(BaseConfigurationOption *BCO) {
+ BaseConfigurationOptions.push_back(BCO);
+ }
+
+ /// 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;
+ Ty *Obj = Allocator.Allocate();
+ new (Obj) Ty(std::forward<ArgsTy>(Args)...);
+ return Obj;
+ }
+
+ /// The list of enabled base configuration options.
+ SmallVector<BaseConfigurationOption *> BaseConfigurationOptions;
+
+ /// The base configuration options.
+ BaseConfigurationOption *RuntimePrefix;
+ BaseConfigurationOption *TargetRegex;
+ BaseConfigurationOption *HostEnabled;
+ BaseConfigurationOption *GPUEnabled;
+
+ /// 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;
+
+ /// 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 {
+ 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
+ /// 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);
+ ///}
+
+ /// 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) {
+ 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;
+ }
+
+ /// 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;
+ ///}
+
+ /// Add arguments available in all instrumentation opportunities.
+ 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));
+ }
+ }
+
+ /// 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 = 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 {
+ virtual ~InstructionIO() {}
+
+ /// 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> {
+ virtual ~StoreIO() {};
+
+ /// Construct a store instruction opportunity.
+ StoreIO(bool IsPRE) : InstructionIO(IsPRE) {}
+
+ /// The selector of arguments for store opportunities.
+ ///{
+ enum ConfigKind {
+ PassPointer = 0,
+ ReplacePointer,
+ PassPointerAS,
+ PassStoredValue,
+ PassStoredValueSize,
+ PassAlignment,
+ PassValueTypeId,
+ PassAtomicityOrdering,
+ PassSyncScopeId,
+ PassIsVolatile,
+ PassId,
+ NumConfig,
+ };
+
+ using ConfigTy = BaseConfigTy<ConfigKind>;
+ ConfigTy Config;
+ ///}
+
+ /// Get the type of the stored value.
+ virtual Type *getValueType(InstrumentorIRBuilderTy &IIRB) const {
+ return IIRB.Int64Ty;
+ }
+
+ /// 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);
+
+ /// 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,
+ 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);
+ ///}
+
+ /// 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}) {
+ auto *AIC = IConf.allocate<StoreIO>(IsPRE);
+ AIC->init(IConf, IIRB);
+ }
+ }
+};
+
+/// The instrumentation opportunity for load instructions.
+struct LoadIO : public InstructionIO<Instruction::Load> {
+ virtual ~LoadIO() {};
+
+ /// Construct a load opportunity.
+ LoadIO(bool IsPRE) : InstructionIO(IsPRE) {}
+
+ /// The selector of arguments for load opportunities.
+ ///{
+ enum ConfigKind {
+ PassPointer = 0,
+ ReplacePointer,
+ PassPointerAS,
+ PassValue,
+ ReplaceValue,
+ PassValueSize,
+ PassAlignment,
+ PassValueTypeId,
+ PassAtomicityOrdering,
+ PassSyncScopeId,
+ PassIsVolatile,
+ PassId,
+ NumConfig,
+ };
+
+ using ConfigTy = BaseConfigTy<ConfigKind>;
+ ConfigTy Config;
+ ///}
+
+ /// Get the type of the loaded value.
+ virtual Type *getValueType(InstrumentorIRBuilderTy &IIRB) const {
+ return IIRB.Int64Ty;
+ }
+
+ /// 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);
+
+ /// 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,
+ 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);
+ ///}
+
+ /// Create the store opportunities for PRE and POST positions.
+ static void populate(InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ for (auto IsPRE : {true, false}) {
+ auto *AIC = IConf.allocate<LoadIO>(IsPRE);
+ AIC->init(IConf, IIRB);
+ }
+ }
+};
+
+} // 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, 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) {}
+
+ 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..cae68f4b34f08
--- /dev/null
+++ b/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h
@@ -0,0 +1,33 @@
+//===-- 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 {
+
+/// Write the configuration in /p IConf to the file with path \p OutputFile.
+void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile,
+ LLVMContext &Ctx);
+
+/// 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
+
+#endif // LLVM_TRANSFORMS_IPO_INSTRUMENTOR_CONFIGFILE_H
diff --git a/llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h b/llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h
new file mode 100644
index 0000000000000..220ec4e61613a
--- /dev/null
+++ b/llvm/include/llvm/Transforms/IPO/InstrumentorUtils.h
@@ -0,0 +1,170 @@
+//===-- 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);
+ PointerType *PtrTy = PointerType::get(Ctx, 0);
+ IntegerType *Int8Ty = Type::getInt8Ty(Ctx);
+ IntegerType *Int32Ty = Type::getInt32Ty(Ctx);
+ IntegerType *Int64Ty = Type::getInt64Ty(Ctx);
+ ///}
+
+ 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/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 35ef547b36e80..16a935f23e367 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -231,6 +231,7 @@
#include "llvm/Transforms/IPO/HotColdSplitting.h"
#include "llvm/Transforms/IPO/IROutliner.h"
#include "llvm/Transforms/IPO/InferFunctionAttrs.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 319ea2f3a69a1..2d2867b9b84d1 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -64,6 +64,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"
@@ -221,6 +222,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"));
+
static cl::opt<bool>
EnableDFAJumpThreading("enable-dfa-jump-thread",
cl::desc("Enable DFA jump threading"),
@@ -1641,6 +1646,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.
@@ -2419,6 +2428,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 b05b1d3a4a8f5..9edb30fedd867 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -102,6 +102,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..cb10a8986a99f
--- /dev/null
+++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp
@@ -0,0 +1,767 @@
+//===-- Instrumentor.cpp - Highly configurable instrumentation pass -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// The implementation of the Instrumentor, a highly configurable instrumentation
+// pass.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/IPO/Instrumentor.h"
+#include "llvm/Transforms/IPO/InstrumentorConfigFile.h"
+
+#include "llvm/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/DiagnosticInfo.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstrTypes.h"
+#include "llvm/IR/Instruction.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Intrinsics.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/IR/Verifier.h"
+#include "llvm/IRReader/IRReader.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/Regex.h"
+
+#include <cassert>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <system_error>
+#include <type_traits>
+
+using namespace llvm;
+using namespace llvm::instrumentor;
+
+#define DEBUG_TYPE "instrumentor"
+
+namespace {
+
+/// The user option to specify an output JSON file to write the configuration.
+static cl::opt<std::string> WriteConfigFile(
+ "instrumentor-write-config-file",
+ cl::desc(
+ "Write the instrumentor configuration into the specified JSON file"),
+ cl::init(""));
+
+/// 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(""));
+
+/// 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;
+ auto *BB = IRB.GetInsertBlock();
+ if (auto *SP = BB->getParent()->getSubprogram())
+ IRB.SetCurrentDebugLocation(DILocation::get(BB->getContext(), 0, 0, SP));
+}
+
+/// Attempt to cast \p V to type \p Ty.
+template <typename IRBTy>
+Value *tryToCast(IRBTy &IRB, Value *V, Type *Ty, const DataLayout &DL,
+ bool AllowTruncate = false) {
+ if (!V)
+ return Constant::getAllOnesValue(Ty);
+ Type *VTy = V->getType();
+ if (VTy == Ty)
+ return V;
+ if (VTy->isAggregateType())
+ return V;
+ TypeSize RequestedSize = DL.getTypeSizeInBits(Ty);
+ TypeSize ValueSize = DL.getTypeSizeInBits(VTy);
+ bool ShouldTruncate = RequestedSize < ValueSize;
+ if (ShouldTruncate && !AllowTruncate)
+ return V;
+ if (ShouldTruncate && AllowTruncate)
+ 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()) {
+ 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, bool IsSigned = false) {
+ return ConstantInt::get(IT, Val, IsSigned);
+}
+
+/// The core of the instrumentor pass, which instruments the module as the
+/// instrumentation configuration mandates.
+class InstrumentorImpl final {
+public:
+ /// Construct an instrumentor implementation using the configuration \p IConf.
+ InstrumentorImpl(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB,
+ Module &M)
+ : IConf(IConf), M(M), IIRB(IIRB) {
+ IConf.populate(IIRB);
+ }
+
+ /// Instrument the module, public entry point.
+ 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
+ /// their opcode.
+ DenseMap<unsigned, InstrumentationOpportunity *> InstChoicesPRE,
+ InstChoicesPOST;
+
+ /// The instrumentor configuration.
+ InstrumentationConfig &IConf;
+
+ /// The underlying module.
+ Module &M;
+
+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)) {
+ 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;
+}
+
+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.Epoch;
+
+ 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.getNextNode());
+ 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, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB,
+ bool ReadConfig) {
+ InstrumentorImpl Impl(IConf, IIRB, M);
+ if (ReadConfig && !readConfigFromJSON(IConf, ReadConfigFile, IIRB.Ctx))
+ return PreservedAnalyses::all();
+
+ writeConfigToJSON(IConf, WriteConfigFile, IIRB.Ctx);
+
+ bool Changed = Impl.instrument();
+ if (!Changed)
+ return PreservedAnalyses::all();
+ return PreservedAnalyses::none();
+}
+
+PreservedAnalyses InstrumentorPass::run(Module &M, ModuleAnalysisManager &MAM) {
+ // Only create them if the user did not provide them.
+ std::unique_ptr<InstrumentationConfig> IConfInt(
+ !UserIConf ? new InstrumentationConfig() : nullptr);
+ std::unique_ptr<InstrumentorIRBuilderTy> IIRBInt(
+ !UserIIRB ? new InstrumentorIRBuilderTy(M) : nullptr);
+
+ auto *IConf = IConfInt ? IConfInt.get() : UserIConf;
+ auto *IIRB = IIRBInt ? IIRBInt.get() : UserIIRB;
+
+ auto PA = run(M, *IConf, *IIRB, !UserIConf);
+
+ assert(!verifyModule(M, &errs()));
+ return PA;
+}
+
+BaseConfigurationOption *
+BaseConfigurationOption::createBoolOption(InstrumentationConfig &IConf,
+ StringRef Name, StringRef Description,
+ bool DefaultValue) {
+ auto *BCO = new BaseConfigurationOption(Name, Description, BOOLEAN);
+ BCO->setBool(DefaultValue);
+ IConf.addBaseChoice(BCO);
+ return BCO;
+}
+
+BaseConfigurationOption *BaseConfigurationOption::createStringOption(
+ InstrumentationConfig &IConf, StringRef Name, StringRef Description,
+ StringRef DefaultValue) {
+ auto *BCO = new BaseConfigurationOption(Name, Description, STRING);
+ BCO->setString(DefaultValue);
+ 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,
+ LLVMContext &Ctx) {
+ auto *&ICPtr = IChoices[IO.getLocationKind()][IO.getName()];
+ if (ICPtr) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("registered two instrumentation opportunities for the same "
+ "location (") +
+ ICPtr->getName() + Twine(" vs ") + IO.getName() + Twine(")"),
+ DS_Warning));
+ }
+ ICPtr = &IO;
+}
+
+Value *InstrumentationOpportunity::getIdPre(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ return getCI(&Ty, getIdFromEpoch(IIRB.Epoch));
+}
+
+Value *InstrumentationOpportunity::getIdPost(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ return getCI(&Ty, -getIdFromEpoch(IIRB.Epoch), /*IsSigned=*/true);
+}
+
+Value *InstrumentationOpportunity::forceCast(Value &V, Type &Ty,
+ InstrumentorIRBuilderTy &IIRB) {
+ if (V.getType()->isVoidTy())
+ return Ty.isVoidTy() ? &V : Constant::getNullValue(&Ty);
+ return tryToCast(IIRB.IRB, &V, &Ty,
+ IIRB.IRB.GetInsertBlock()->getDataLayout());
+}
+
+Value *InstrumentationOpportunity::replaceValue(Value &V, Value &NewV,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ if (V.getType()->isVoidTy())
+ return &V;
+
+ auto *NewVCasted = &NewV;
+ if (auto *I = dyn_cast<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.Epoch)
+ return false;
+ return !isa<LifetimeIntrinsic>(U.getUser()) && !U.getUser()->isDroppable();
+ });
+
+ return &V;
+}
+
+IRTCallDescription::IRTCallDescription(InstrumentationOpportunity &IO,
+ Type *RetTy)
+ : IO(IO), RetTy(RetTy) {
+ for (auto &It : IO.IRTArgs) {
+ if (!It.Enabled)
+ continue;
+ NumReplaceableArgs += bool(It.Flags & IRTArg::REPLACABLE);
+ MightRequireIndirection |= It.Flags & IRTArg::POTENTIALLY_INDIRECT;
+ }
+ if (NumReplaceableArgs > 1)
+ MightRequireIndirection = RequiresIndirection = true;
+}
+
+FunctionType *IRTCallDescription::createLLVMSignature(
+ InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB,
+ const DataLayout &DL, bool ForceIndirection) {
+ assert(((ForceIndirection && MightRequireIndirection) ||
+ (!ForceIndirection && !RequiresIndirection)) &&
+ "Wrong indirection setting!");
+
+ SmallVector<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(IIRB.PtrTy);
+ if (!(It.Flags & IRTArg::INDIRECT_HAS_SIZE))
+ ParamTypes.push_back(IIRB.Int32Ty);
+ }
+ if (!RetTy)
+ RetTy = IIRB.VoidTy;
+
+ return FunctionType::get(RetTy, ParamTypes, /*isVarArg=*/false);
+}
+
+CallInst *IRTCallDescription::createLLVMCall(Value *&V,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB,
+ const DataLayout &DL,
+ InstrumentationCaches &ICaches) {
+ SmallVector<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.Epoch, IO.getName(), It.Name}];
+ if (!Param || It.NoCache)
+ // Avoid passing the caches to the getter.
+ Param = It.GetterCB(*V, *It.Ty, IConf, IIRB);
+ assert(Param);
+
+ if (Param->getType()->isVoidTy()) {
+ Param = Constant::getNullValue(It.Ty);
+ } else if (Param->getType()->isAggregateType() ||
+ DL.getTypeSizeInBits(Param->getType()) >
+ DL.getTypeSizeInBits(It.Ty)) {
+ if (!isPotentiallyIndirect(It)) {
+ IIRB.Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("indirection needed for ") + It.Name + Twine(" in ") +
+ IO.getName() +
+ Twine(", but not indicated. Instrumentation is skipped"),
+ DS_Warning));
+ return nullptr;
+ }
+ ForceIndirection = true;
+ } else {
+ Param = tryToCast(IIRB.IRB, Param, It.Ty, DL);
+ }
+ CallParams.push_back(Param);
+ }
+
+ if (ForceIndirection) {
+ Function *Fn = IIRB.IRB.GetInsertBlock()->getParent();
+
+ unsigned Offset = 0;
+ for (auto &It : IO.IRTArgs) {
+ if (!It.Enabled)
+ continue;
+
+ if (!isPotentiallyIndirect(It)) {
+ ++Offset;
+ continue;
+ }
+ auto *&CallParam = CallParams[Offset++];
+ if (!(It.Flags & IRTArg::INDIRECT_HAS_SIZE)) {
+ CallParams.insert(&CallParam + 1, IIRB.IRB.getInt32(DL.getTypeStoreSize(
+ CallParam->getType())));
+ Offset += 1;
+ }
+
+ auto *&CachedParam =
+ ICaches.IndirectArgCache[{IIRB.Epoch, IO.getName(), It.Name}];
+ if (CachedParam) {
+ CallParam = CachedParam;
+ continue;
+ }
+
+ auto *AI = IIRB.getAlloca(Fn, CallParam->getType());
+ IIRB.IRB.CreateStore(CallParam, AI);
+ CallParam = CachedParam = AI;
+ }
+ }
+
+ if (!ForceIndirection)
+ IIRB.IRB.SetInsertPoint(IP);
+ ensureDbgLoc(IIRB.IRB);
+
+ auto *FnTy = createLLVMSignature(IConf, IIRB, DL, ForceIndirection);
+ auto CompleteName =
+ IConf.getRTName(IO.IP.isPRE() ? "pre_" : "post_", IO.getName(),
+ ForceIndirection ? "_ind" : "");
+ auto FC = IIRB.IRB.GetInsertBlock()->getModule()->getOrInsertFunction(
+ CompleteName, FnTy);
+ auto *CI = IIRB.IRB.CreateCall(FC, CallParams);
+ CI->addFnAttr(Attribute::get(IIRB.Ctx, Attribute::WillReturn));
+
+ for (unsigned I = 0, E = IO.IRTArgs.size(); I < E; ++I) {
+ if (!IO.IRTArgs[I].Enabled)
+ continue;
+ if (!isReplacable(IO.IRTArgs[I]))
+ continue;
+ bool IsCustomReplaceable = IO.IRTArgs[I].Flags & IRTArg::REPLACABLE_CUSTOM;
+ Value *NewValue = FnTy->isVoidTy() || IsCustomReplaceable
+ ? ICaches.DirectArgCache[{IIRB.Epoch, IO.getName(),
+ IO.IRTArgs[I].Name}]
+ : CI;
+ assert(NewValue);
+ if (ForceIndirection && !IsCustomReplaceable &&
+ isPotentiallyIndirect(IO.IRTArgs[I])) {
+ auto *Q =
+ ICaches
+ .IndirectArgCache[{IIRB.Epoch, IO.getName(), IO.IRTArgs[I].Name}];
+ NewValue = IIRB.IRB.CreateLoad(V->getType(), Q);
+ }
+ V = IO.IRTArgs[I].SetterCB(*V, *NewValue, IConf, IIRB);
+ }
+ return CI;
+}
+
+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), "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);
+ 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());
+}
+
+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), "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);
+ 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..a2707193da1a7
--- /dev/null
+++ b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp
@@ -0,0 +1,225 @@
+//===-- InstrumentorConfigFile.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
+//
+//===----------------------------------------------------------------------===//
+//
+// The implementation of the utilities for the Instrumentor JSON configuration
+// file.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/IPO/Instrumentor.h"
+
+#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"
+#include "llvm/Support/StringSaver.h"
+
+#include <string>
+
+namespace llvm {
+namespace instrumentor {
+
+void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile,
+ LLVMContext &Ctx) {
+ if (OutputFile.empty())
+ return;
+
+ std::error_code EC;
+ raw_fd_stream OS(OutputFile, EC);
+ if (EC) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("failed to open instrumentor configuration file for writing: ") +
+ EC.message(),
+ DS_Warning));
+ return;
+ }
+
+ json::OStream J(OS, 2);
+ J.objectBegin();
+
+ J.attributeBegin("configuration");
+ J.objectBegin();
+ for (auto *BaseCO : IConf.BaseConfigurationOptions) {
+ switch (BaseCO->Kind) {
+ case BaseConfigurationOption::STRING:
+ J.attribute(BaseCO->Name, BaseCO->getString());
+ break;
+ case BaseConfigurationOption::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,
+ LLVMContext &Ctx) {
+ if (InputFile.empty())
+ return true;
+
+ std::error_code EC;
+ auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(InputFile);
+ if (std::error_code EC = BufferOrErr.getError()) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("failed to open instrumentor configuration file for reading: ") +
+ EC.message(),
+ DS_Warning));
+ return false;
+ }
+ auto Buffer = std::move(BufferOrErr.get());
+ json::Path::Root NullRoot;
+ auto Parsed = json::parse(Buffer->getBuffer());
+ if (!Parsed) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("failed to parse instrumentor configuration file: ") +
+ toString(Parsed.takeError()),
+ DS_Warning));
+ return false;
+ }
+ auto *Config = Parsed->getAsObject();
+ if (!Config) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ "failed to parse instrumentor configuration file, expected an object "
+ "'{ ... }'",
+ DS_Warning));
+ return false;
+ }
+
+ 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) {
+ 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 BaseConfigurationOption::STRING:
+ if (auto V = ObjIt.second.getAsString()) {
+ BO->setString(IConf.SS.save(*V));
+ } else {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("configuration key '") + ObjIt.first.str() +
+ Twine("' expects a string, value ignored"),
+ DS_Warning));
+ }
+ break;
+ case BaseConfigurationOption::BOOLEAN:
+ if (auto V = ObjIt.second.getAsBoolean())
+ BO->setBool(*V);
+ 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")) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("configuration key '") + ObjIt.first.str() +
+ Twine("' not found and ignored"),
+ DS_Warning));
+ }
+ }
+ continue;
+ }
+
+ auto &IChoiceMap =
+ IConf.IChoices[InstrumentationLocation::getKindFromStr(It.first)];
+ for (auto &ObjIt : *Obj) {
+ auto *InnerObj = ObjIt.second.getAsObject();
+ if (!InnerObj) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ "malformed JSON configuration, expected an object", DS_Warning));
+ continue;
+ }
+ auto *IO = IChoiceMap.lookup(ObjIt.first);
+ if (!IO) {
+ Ctx.diagnose(DiagnosticInfoInstrumentation(
+ Twine("malformed JSON configuration, expected an object matching "
+ "an instrumentor choice, got ") +
+ ObjIt.first.str(),
+ DS_Warning));
+ 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
More information about the llvm-commits
mailing list