[clang] [llvm] [Instrumentor] Add Alloca and Function support; stack usage example (PR #195378)
Johannes Doerfert via cfe-commits
cfe-commits at lists.llvm.org
Thu May 7 19:18:02 PDT 2026
https://github.com/jdoerfert updated https://github.com/llvm/llvm-project/pull/195378
>From 9a4824c6613372738a758d7d418ca946df097595 Mon Sep 17 00:00:00 2001
From: Johannes Doerfert <jdoerfert.llvm at gmail.com>
Date: Fri, 1 May 2026 11:01:07 -0700
Subject: [PATCH] [Instrumentor] Add Alloca and Function support; stack usage
example
This adds support for alloca instrumentation and function pre/post
instrumentation. Alloca support follows load/store support directly.
Functions require special care to determine the insertion points.
Together, we can showcase how the stack high watermark can be profiled,
see InstrumentorStackUsage.cpp.
---
.../Instrumentor/InstrumentorStackUsage.cpp | 37 +++
clang/test/Instrumentor/StackUsageRT.cpp | 59 ++++
clang/test/Instrumentor/StackUsageRT.json | 54 ++++
clang/test/Instrumentor/lit.local.cfg | 2 +
.../llvm/Transforms/IPO/Instrumentor.h | 126 +++++++-
llvm/lib/Transforms/IPO/Instrumentor.cpp | 303 +++++++++++++++++-
.../Instrumentor/alloca_and_function.ll | 56 ++++
.../Instrumentor/default_config.json | 59 ++++
8 files changed, 681 insertions(+), 15 deletions(-)
create mode 100644 clang/test/Instrumentor/InstrumentorStackUsage.cpp
create mode 100644 clang/test/Instrumentor/StackUsageRT.cpp
create mode 100644 clang/test/Instrumentor/StackUsageRT.json
create mode 100644 clang/test/Instrumentor/lit.local.cfg
create mode 100644 llvm/test/Instrumentation/Instrumentor/alloca_and_function.ll
diff --git a/clang/test/Instrumentor/InstrumentorStackUsage.cpp b/clang/test/Instrumentor/InstrumentorStackUsage.cpp
new file mode 100644
index 0000000000000..15a2714652a3f
--- /dev/null
+++ b/clang/test/Instrumentor/InstrumentorStackUsage.cpp
@@ -0,0 +1,37 @@
+// NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+// RUN: %clangxx -O0 %S/StackUsageRT.cpp -o %t.StackUsageRT.o -c
+// RUN: %clangxx -O0 -mllvm -enable-instrumentor -mllvm -instrumentor-read-config-file=%S/StackUsageRT.json %t.StackUsageRT.o -o %t %s
+// RUN: %t | FileCheck %s
+
+static void foobar(int *A, int N) {
+ int B[100];
+ for (int i = 0; i < 100; ++i) {
+ B[i] = i + N;
+ }
+ if (N-- > 0)
+ foobar(B, N);
+ for (int i = 0; i < 100; ++i) {
+ A[i] += B[i];
+ }
+}
+
+static void bar(int *A, int N) {
+ foobar(A, N);
+}
+
+int main(void) {
+ int A[100] = {0};
+ foobar(A, 4);
+ bar(A, 3);
+ foobar(A, 5);
+ foobar(A, 2);
+}
+
+// CHECK: Stack usage peaked at 2512 in
+// CHECK: - foobar(int*, int)
+// CHECK: - foobar(int*, int)
+// CHECK: - foobar(int*, int)
+// CHECK: - foobar(int*, int)
+// CHECK: - foobar(int*, int)
+// CHECK: - foobar(int*, int)
+// CHECK: - main
diff --git a/clang/test/Instrumentor/StackUsageRT.cpp b/clang/test/Instrumentor/StackUsageRT.cpp
new file mode 100644
index 0000000000000..9f2b29b8f050d
--- /dev/null
+++ b/clang/test/Instrumentor/StackUsageRT.cpp
@@ -0,0 +1,59 @@
+//===-- examples/Instrumentor/stack_usage.c - An example Instrumentor use -===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//
+//===----------------------------------------------------------------------===//
+
+#include <cstdint>
+#include <cstdio>
+#include <list>
+
+struct StackTracker {
+ std::list<char *> CallStack;
+ int64_t FunctionStackUsage = 0;
+ int64_t TotalStackUsage = 0;
+
+ std::list<char *> HighWaterMarkCallStack;
+ int64_t HighWaterMark = 0;
+
+ ~StackTracker() {
+ printf("Stack usage peaked at %lli in\n", HighWaterMark);
+ HighWaterMarkCallStack.reverse();
+ for (char *Name : HighWaterMarkCallStack)
+ printf("- %s\n", Name);
+ }
+
+ void enter(char *Name) {
+ FunctionStackUsage = 0;
+ CallStack.push_back(Name);
+ }
+ void exit(char *Name) {
+ CallStack.pop_back();
+ TotalStackUsage -= FunctionStackUsage;
+ }
+
+ void allocate(int64_t size) {
+ TotalStackUsage += size;
+ FunctionStackUsage += size;
+ if (TotalStackUsage <= HighWaterMark)
+ return;
+ HighWaterMark = TotalStackUsage;
+ HighWaterMarkCallStack = CallStack;
+ }
+};
+
+static thread_local StackTracker ST;
+
+extern "C" {
+
+void __stack_usage_pre_function(char *Name) { ST.enter(Name); }
+
+void __stack_usage_post_function(char *Name) { ST.exit(Name); }
+
+void __stack_usage_pre_alloca(int64_t size) { ST.allocate(size); }
+}
diff --git a/clang/test/Instrumentor/StackUsageRT.json b/clang/test/Instrumentor/StackUsageRT.json
new file mode 100644
index 0000000000000..491ab9cf5ea05
--- /dev/null
+++ b/clang/test/Instrumentor/StackUsageRT.json
@@ -0,0 +1,54 @@
+{
+ "configuration": {
+ "runtime_prefix": "__stack_usage_",
+ "runtime_prefix.description": "The runtime API prefix.",
+ "demangle_function_names": true,
+ "demangle_function_names.description": "Demangle functions names passed to the runtime."
+ },
+ "function_pre": {
+ "function": {
+ "enabled": true,
+ "address": false,
+ "address.description": "The function address.",
+ "name": true,
+ "name.description": "The function name.",
+ "num_arguments": false,
+ "num_arguments.description": "Number of function arguments (without varargs).",
+ "arguments": false,
+ "arguments.description": "Description of the arguments.",
+ "is_main": false,
+ "is_main.description": "Flag to indicate it is the main function.",
+ "id": false,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ }
+ },
+ "function_post": {
+ "function": {
+ "enabled": true,
+ "address": false,
+ "address.description": "The function address.",
+ "name": true,
+ "name.description": "The function name.",
+ "num_arguments": false,
+ "num_arguments.description": "Number of function arguments (without varargs).",
+ "arguments": false,
+ "arguments.description": "Description of the arguments.",
+ "is_main": false,
+ "is_main.description": "Flag to indicate it is the main function.",
+ "id": false,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ }
+ },
+ "instruction_pre": {
+ "alloca": {
+ "enabled": true,
+ "size": true,
+ "size.replace": false,
+ "size.description": "The allocation size.",
+ "alignment": false,
+ "alignment.description": "The allocation alignment.",
+ "id": false,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ }
+ }
+}
diff --git a/clang/test/Instrumentor/lit.local.cfg b/clang/test/Instrumentor/lit.local.cfg
new file mode 100644
index 0000000000000..afb6cf1a99e25
--- /dev/null
+++ b/clang/test/Instrumentor/lit.local.cfg
@@ -0,0 +1,2 @@
+config.suffixes.add(".cpp")
+config.excludes = ["StackUsageRT.cpp"]
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
index 7dfc342031579..701498bb830b9 100644
--- a/llvm/include/llvm/Transforms/IPO/Instrumentor.h
+++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
@@ -358,6 +358,9 @@ struct InstrumentationConfig {
"Regular expression to be matched against the module target. "
"Only targets that match this regex will be instrumented",
"");
+ DemangleFunctionNames = BaseConfigurationOption::createBoolOption(
+ *this, "demangle_function_names",
+ "Demangle functions names passed to the runtime.", true);
HostEnabled = BaseConfigurationOption::createBoolOption(
*this, "host_enabled", "Instrument non-GPU targets", true);
GPUEnabled = BaseConfigurationOption::createBoolOption(
@@ -394,12 +397,31 @@ struct InstrumentationConfig {
return Obj;
}
+ /// Mapping to remember global strings passed to the runtime.
+ DenseMap<StringRef, Constant *> GlobalStringsMap;
+
+ /// Mapping from constants to globals with the constant as initializer.
+ DenseMap<Constant *, GlobalVariable *> ConstantGlobalsCache;
+
+ Constant *getGlobalString(StringRef S, InstrumentorIRBuilderTy &IIRB) {
+ Constant *&V = GlobalStringsMap[SS.save(S)];
+ if (!V) {
+ auto &M = *IIRB.IRB.GetInsertBlock()->getModule();
+ V = IIRB.IRB.CreateGlobalString(
+ S, getRTName() + ".str",
+ M.getDataLayout().getDefaultGlobalsAddressSpace(), &M);
+ if (V->getType() != IIRB.IRB.getPtrTy())
+ V = ConstantExpr::getAddrSpaceCast(V, IIRB.IRB.getPtrTy());
+ }
+ return V;
+ }
/// The list of enabled base configuration options.
SmallVector<BaseConfigurationOption *> BaseConfigurationOptions;
/// The base configuration options.
std::unique_ptr<BaseConfigurationOption> RuntimePrefix;
std::unique_ptr<BaseConfigurationOption> RuntimeStubsFile;
+ std::unique_ptr<BaseConfigurationOption> DemangleFunctionNames;
std::unique_ptr<BaseConfigurationOption> TargetRegex;
std::unique_ptr<BaseConfigurationOption> HostEnabled;
std::unique_ptr<BaseConfigurationOption> GPUEnabled;
@@ -539,6 +561,94 @@ struct InstructionIO : public InstrumentationOpportunity {
}
};
+/// The instrumentation opportunity for functions.
+struct FunctionIO final : public InstrumentationOpportunity {
+ FunctionIO(bool IsPRE)
+ : InstrumentationOpportunity(
+ InstrumentationLocation(InstrumentationLocation(
+ IsPRE ? InstrumentationLocation::FUNCTION_PRE
+ : InstrumentationLocation::FUNCTION_POST))) {}
+
+ enum ConfigKind {
+ PassAddress = 0,
+ PassName,
+ PassNumArguments,
+ PassArguments,
+ ReplaceArguments,
+ PassIsMain,
+ PassId,
+ NumConfig,
+ };
+
+ struct ConfigTy final : public BaseConfigTy<ConfigKind> {
+ std::function<bool(Argument &)> ArgFilter;
+
+ ConfigTy(bool Enable = true) : BaseConfigTy(Enable) {}
+ } Config;
+
+ StringRef getName() const override { return "function"; }
+
+ void init(InstrumentationConfig &IConf, LLVMContext &Ctx,
+ ConfigTy *UserConfig = nullptr);
+
+ static Value *getFunctionAddress(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getFunctionName(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ Value *getNumArguments(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ Value *getArguments(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ Value *setArguments(Value &V, Value &NewV, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *isMainFunction(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+
+ static void populate(InstrumentationConfig &IConf, LLVMContext &Ctx) {
+ auto *PreIO = IConf.allocate<FunctionIO>(true);
+ PreIO->init(IConf, Ctx);
+ auto *PostIO = IConf.allocate<FunctionIO>(false);
+ PostIO->init(IConf, Ctx);
+ }
+};
+
+/// The instrumentation opportunity for alloca instructions.
+struct AllocaIO final : public InstructionIO<Instruction::Alloca> {
+ AllocaIO(bool IsPRE) : InstructionIO(IsPRE) {}
+
+ enum ConfigKind {
+ PassAddress = 0,
+ ReplaceAddress,
+ PassSize,
+ ReplaceSize,
+ PassAlignment,
+ PassId,
+ NumConfig,
+ };
+
+ using ConfigTy = BaseConfigTy<ConfigKind>;
+ ConfigTy Config;
+
+ void init(InstrumentationConfig &IConf, LLVMContext &Ctx,
+ ConfigTy *UserConfig = nullptr);
+
+ static Value *getSize(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *setSize(Value &V, Value &NewV, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+ static Value *getAlignment(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB);
+
+ static void populate(InstrumentationConfig &IConf, LLVMContext &Ctx) {
+ auto *PreIO = IConf.allocate<AllocaIO>(true);
+ PreIO->init(IConf, Ctx);
+ auto *PostIO = IConf.allocate<AllocaIO>(false);
+ PostIO->init(IConf, Ctx);
+ }
+};
+
/// The instrumentation opportunity for store instructions.
struct StoreIO : public InstructionIO<Instruction::Store> {
virtual ~StoreIO() {};
@@ -608,10 +718,10 @@ struct StoreIO : public InstructionIO<Instruction::Store> {
/// instrumentation calls.
static void populate(InstrumentationConfig &IConf,
InstrumentorIRBuilderTy &IIRB) {
- for (auto IsPRE : {true, false}) {
- auto *AIC = IConf.allocate<StoreIO>(IsPRE);
- AIC->init(IConf, IIRB);
- }
+ auto *PreIO = IConf.allocate<StoreIO>(true);
+ PreIO->init(IConf, IIRB);
+ auto *PostIO = IConf.allocate<StoreIO>(false);
+ PostIO->init(IConf, IIRB);
}
};
@@ -683,10 +793,10 @@ struct LoadIO : public InstructionIO<Instruction::Load> {
/// 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);
- }
+ auto *PreIO = IConf.allocate<LoadIO>(true);
+ PreIO->init(IConf, IIRB);
+ auto *PostIO = IConf.allocate<LoadIO>(false);
+ PostIO->init(IConf, IIRB);
}
};
diff --git a/llvm/lib/Transforms/IPO/Instrumentor.cpp b/llvm/lib/Transforms/IPO/Instrumentor.cpp
index 33f00be11084a..3ba1a2b00a5f8 100644
--- a/llvm/lib/Transforms/IPO/Instrumentor.cpp
+++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp
@@ -21,6 +21,7 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/iterator.h"
+#include "llvm/Demangle/Demangle.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
@@ -231,11 +232,45 @@ bool InstrumentorImpl::instrumentFunction(Function &Fn) {
return Changed;
InstrumentationCaches ICaches;
+ SmallVector<Instruction *> FinalTIs;
ReversePostOrderTraversal<Function *> RPOT(&Fn);
- for (auto &It : RPOT)
+ for (auto &It : RPOT) {
for (auto &I : *It)
Changed |= instrumentInstruction(I, ICaches);
+ auto *TI = It->getTerminator();
+ if (!TI->getNumSuccessors())
+ FinalTIs.push_back(TI);
+ }
+
+ Value *FPtr = &Fn;
+ for (auto &[Name, IO] :
+ IConf.IChoices[InstrumentationLocation::FUNCTION_PRE]) {
+ if (!IO->Enabled)
+ continue;
+ // Count epochs eagerly.
+ ++IIRB.Epoch;
+
+ IIRB.IRB.SetInsertPointPastAllocas(cast<Function>(FPtr));
+ ensureDbgLoc(IIRB.IRB);
+ IO->instrument(FPtr, IConf, IIRB, ICaches);
+ IIRB.returnAllocas();
+ }
+
+ for (auto &[Name, IO] :
+ IConf.IChoices[InstrumentationLocation::FUNCTION_POST]) {
+ if (!IO->Enabled)
+ continue;
+ // Count epochs eagerly.
+ ++IIRB.Epoch;
+
+ for (Instruction *FinalTI : FinalTIs) {
+ IIRB.IRB.SetInsertPoint(FinalTI);
+ ensureDbgLoc(IIRB.IRB);
+ IO->instrument(FPtr, IConf, IIRB, ICaches);
+ IIRB.returnAllocas();
+ }
+ }
return Changed;
}
@@ -244,12 +279,14 @@ bool InstrumentorImpl::instrument() {
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 (auto &[Name, IO] :
+ IConf.IChoices[InstrumentationLocation::INSTRUCTION_PRE])
+ if (IO->Enabled)
+ InstChoicesPRE[IO->getOpcode()] = IO;
+ for (auto &[Name, IO] :
+ IConf.IChoices[InstrumentationLocation::INSTRUCTION_POST])
+ if (IO->Enabled)
+ InstChoicesPOST[IO->getOpcode()] = IO;
for (Function &Fn : M)
Changed |= instrumentFunction(Fn);
@@ -315,6 +352,8 @@ BaseConfigurationOption::createStringOption(InstrumentationConfig &IConf,
void InstrumentationConfig::populate(InstrumentorIRBuilderTy &IIRB) {
/// List of all instrumentation opportunities.
+ FunctionIO::populate(*this, IIRB.Ctx);
+ AllocaIO::populate(*this, IIRB.Ctx);
LoadIO::populate(*this, IIRB);
StoreIO::populate(*this, IIRB);
}
@@ -525,6 +564,256 @@ CallInst *IRTCallDescription::createLLVMCall(Value *&V,
return CI;
}
+template <typename Ty> constexpr static Value *getValue(Ty &ValueOrUse) {
+ if constexpr (std::is_same<Ty, Use>::value)
+ return ValueOrUse.get();
+ else
+ return static_cast<Value *>(&ValueOrUse);
+}
+
+template <typename Range>
+static Value *createValuePack(const Range &R, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto *Fn = IIRB.IRB.GetInsertBlock()->getParent();
+ auto *I32Ty = IIRB.IRB.getInt32Ty();
+ SmallVector<Constant *> ConstantValues;
+ SmallVector<std::pair<Value *, uint32_t>> Values;
+ SmallVector<Type *> Types;
+ for (auto &RE : R) {
+ Value *V = getValue(RE);
+ if (!V->getType()->isSized())
+ continue;
+ auto VSize = IIRB.DL.getTypeAllocSize(V->getType());
+ ConstantValues.push_back(getCI(I32Ty, VSize));
+ Types.push_back(I32Ty);
+ ConstantValues.push_back(getCI(I32Ty, V->getType()->getTypeID()));
+ Types.push_back(I32Ty);
+ if (uint32_t MisAlign = VSize % 8) {
+ Types.push_back(ArrayType::get(IIRB.Int8Ty, 8 - MisAlign));
+ ConstantValues.push_back(ConstantArray::getNullValue(Types.back()));
+ }
+ Types.push_back(V->getType());
+ if (auto *C = dyn_cast<Constant>(V)) {
+ ConstantValues.push_back(C);
+ continue;
+ }
+ Values.push_back({V, ConstantValues.size()});
+ ConstantValues.push_back(Constant::getNullValue(V->getType()));
+ }
+ if (Types.empty())
+ return ConstantPointerNull::get(PointerType::getUnqual(IIRB.Ctx));
+
+ StructType *STy = StructType::get(Fn->getContext(), Types, /*isPacked=*/true);
+ Constant *Initializer = ConstantStruct::get(STy, ConstantValues);
+
+ GlobalVariable *&GV = IConf.ConstantGlobalsCache[Initializer];
+ if (!GV)
+ GV = new GlobalVariable(*Fn->getParent(), STy, false,
+ GlobalValue::InternalLinkage, Initializer,
+ IConf.getRTName("", "value_pack"));
+
+ auto *AI = IIRB.getAlloca(Fn, STy);
+ IIRB.IRB.CreateMemCpy(AI, AI->getAlign(), GV, MaybeAlign(GV->getAlignment()),
+ IIRB.DL.getTypeAllocSize(STy));
+ for (auto [Param, Idx] : Values) {
+ auto *Ptr = IIRB.IRB.CreateStructGEP(STy, AI, Idx);
+ IIRB.IRB.CreateStore(Param, Ptr);
+ }
+ return AI;
+}
+
+template <typename Range>
+static void readValuePack(const Range &R, Value &Pack,
+ InstrumentorIRBuilderTy &IIRB,
+ function_ref<void(int, Value *)> SetterCB) {
+ auto *Fn = IIRB.IRB.GetInsertBlock()->getParent();
+ auto &DL = Fn->getDataLayout();
+ SmallVector<Value *> ParameterValues;
+ unsigned Offset = 0;
+ for (const auto &[Idx, RE] : enumerate(R)) {
+ Value *V = getValue(RE);
+ if (!V->getType()->isSized())
+ continue;
+ Offset += 8;
+ auto VSize = DL.getTypeAllocSize(V->getType());
+ auto Padding = alignTo(VSize, 8) - VSize;
+ Offset += Padding;
+ auto *Ptr = IIRB.IRB.CreateConstInBoundsGEP1_32(IIRB.Int8Ty, &Pack, Offset);
+ auto *NewV = IIRB.IRB.CreateLoad(V->getType(), Ptr);
+ SetterCB(Idx, NewV);
+ Offset += VSize;
+ }
+}
+
+/// FunctionIO
+/// {
+void FunctionIO::init(InstrumentationConfig &IConf, LLVMContext &Ctx,
+ ConfigTy *UserConfig) {
+ using namespace std::placeholders;
+ if (UserConfig)
+ Config = *UserConfig;
+
+ bool IsPRE = getLocationKind() == InstrumentationLocation::FUNCTION_PRE;
+ if (Config.has(PassAddress))
+ IRTArgs.push_back(IRTArg(PointerType::getUnqual(Ctx), "address",
+ "The function address.", IRTArg::NONE,
+ getFunctionAddress));
+ if (Config.has(PassName))
+ IRTArgs.push_back(IRTArg(PointerType::getUnqual(Ctx), "name",
+ "The function name.", IRTArg::STRING,
+ getFunctionName));
+ if (Config.has(PassNumArguments))
+ IRTArgs.push_back(
+ IRTArg(IntegerType::getInt32Ty(Ctx), "num_arguments",
+ "Number of function arguments (without varargs).", IRTArg::NONE,
+ std::bind(&FunctionIO::getNumArguments, this, _1, _2, _3, _4)));
+ if (Config.has(PassArguments))
+ IRTArgs.push_back(
+ IRTArg(PointerType::getUnqual(Ctx), "arguments",
+ "Description of the arguments.",
+ IsPRE && Config.has(ReplaceArguments) ? IRTArg::REPLACABLE_CUSTOM
+ : IRTArg::NONE,
+ std::bind(&FunctionIO::getArguments, this, _1, _2, _3, _4),
+ std::bind(&FunctionIO::setArguments, this, _1, _2, _3, _4)));
+ if (Config.has(PassIsMain))
+ IRTArgs.push_back(IRTArg(IntegerType::getInt8Ty(Ctx), "is_main",
+ "Flag to indicate it is the main function.",
+ IRTArg::NONE, isMainFunction));
+ addCommonArgs(IConf, Ctx, Config.has(PassId));
+ IConf.addChoice(*this, Ctx);
+}
+
+Value *FunctionIO::getFunctionAddress(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &Fn = cast<Function>(V);
+ if (Fn.isIntrinsic())
+ return Constant::getNullValue(&Ty);
+ return &V;
+}
+Value *FunctionIO::getFunctionName(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &Fn = cast<Function>(V);
+ return IConf.getGlobalString(IConf.DemangleFunctionNames->getBool()
+ ? demangle(Fn.getName())
+ : Fn.getName(),
+ IIRB);
+}
+Value *FunctionIO::getNumArguments(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &Fn = cast<Function>(V);
+ if (!Config.ArgFilter)
+ return getCI(&Ty, Fn.arg_size());
+ auto FRange = make_filter_range(Fn.args(), Config.ArgFilter);
+ return getCI(&Ty, std::distance(FRange.begin(), FRange.end()));
+}
+Value *FunctionIO::getArguments(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &Fn = cast<Function>(V);
+ if (!Config.ArgFilter)
+ return createValuePack(Fn.args(), IConf, IIRB);
+ return createValuePack(make_filter_range(Fn.args(), Config.ArgFilter), IConf,
+ IIRB);
+}
+Value *FunctionIO::setArguments(Value &V, Value &NewV,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &Fn = cast<Function>(V);
+ auto *AIt = Fn.arg_begin();
+ auto CB = [&](int Idx, Value *ReplV) {
+ while (Config.ArgFilter && !Config.ArgFilter(*AIt))
+ ++AIt;
+ Fn.getArg(Idx)->replaceUsesWithIf(ReplV, [&](Use &U) {
+ return IIRB.NewInsts.lookup(cast<Instruction>(U.getUser())) != IIRB.Epoch;
+ });
+ ++AIt;
+ };
+ if (!Config.ArgFilter)
+ readValuePack(Fn.args(), NewV, IIRB, CB);
+ else
+ readValuePack(make_filter_range(Fn.args(), Config.ArgFilter), NewV, IIRB,
+ CB);
+ return &Fn;
+}
+Value *FunctionIO::isMainFunction(Value &V, Type &Ty,
+ InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &Fn = cast<Function>(V);
+ return getCI(&Ty, Fn.getName() == "main");
+}
+
+///}
+
+/// AllocaIO
+///{
+void AllocaIO::init(InstrumentationConfig &IConf, LLVMContext &Ctx,
+ ConfigTy *UserConfig) {
+ if (UserConfig)
+ Config = *UserConfig;
+
+ bool IsPRE = getLocationKind() == InstrumentationLocation::INSTRUCTION_PRE;
+ if (!IsPRE && Config.has(PassAddress))
+ IRTArgs.push_back(IRTArg(
+ PointerType::getUnqual(Ctx), "address", "The allocated memory address.",
+ Config.has(ReplaceAddress) ? IRTArg::REPLACABLE : IRTArg::NONE,
+ InstrumentationOpportunity::getValue,
+ InstrumentationOpportunity::replaceValue));
+ if (Config.has(PassSize))
+ IRTArgs.push_back(IRTArg(
+ IntegerType::getInt64Ty(Ctx), "size", "The allocation size.",
+ (IsPRE && Config.has(ReplaceSize)) ? IRTArg::REPLACABLE : IRTArg::NONE,
+ getSize, setSize));
+ if (Config.has(PassAlignment))
+ IRTArgs.push_back(IRTArg(IntegerType::getInt64Ty(Ctx), "alignment",
+ "The allocation alignment.", IRTArg::NONE,
+ getAlignment));
+
+ addCommonArgs(IConf, Ctx, Config.has(PassId));
+ IConf.addChoice(*this, Ctx);
+}
+
+Value *AllocaIO::getSize(Value &V, Type &Ty, InstrumentationConfig &IO,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &AI = cast<AllocaInst>(V);
+ const DataLayout &DL = AI.getDataLayout();
+ Value *SizeValue = nullptr;
+ TypeSize TypeSize = DL.getTypeAllocSize(AI.getAllocatedType());
+ if (TypeSize.isFixed()) {
+ SizeValue = getCI(&Ty, TypeSize.getFixedValue());
+ } else {
+ auto *NullPtr = ConstantPointerNull::get(AI.getType());
+ SizeValue = IIRB.IRB.CreatePtrToInt(
+ IIRB.IRB.CreateGEP(AI.getAllocatedType(), NullPtr,
+ {IIRB.IRB.getInt32(1)}),
+ &Ty);
+ }
+ if (AI.isArrayAllocation())
+ SizeValue = IIRB.IRB.CreateMul(
+ SizeValue, IIRB.IRB.CreateZExtOrBitCast(AI.getArraySize(), &Ty));
+ return SizeValue;
+}
+
+Value *AllocaIO::setSize(Value &V, Value &NewV, InstrumentationConfig &IO,
+ InstrumentorIRBuilderTy &IIRB) {
+ auto &AI = cast<AllocaInst>(V);
+ const DataLayout &DL = AI.getDataLayout();
+ auto *NewAI = IIRB.IRB.CreateAlloca(IIRB.IRB.getInt8Ty(),
+ DL.getAllocaAddrSpace(), &NewV);
+ NewAI->setAlignment(AI.getAlign());
+ AI.replaceAllUsesWith(NewAI);
+ IIRB.eraseLater(&AI);
+ return NewAI;
+}
+
+Value *AllocaIO::getAlignment(Value &V, Type &Ty, InstrumentationConfig &IConf,
+ InstrumentorIRBuilderTy &IIRB) {
+ return getCI(&Ty, cast<AllocaInst>(V).getAlign().value());
+}
+///}
+
void StoreIO::init(InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB,
ConfigTy *UserConfig) {
if (UserConfig)
diff --git a/llvm/test/Instrumentation/Instrumentor/alloca_and_function.ll b/llvm/test/Instrumentation/Instrumentor/alloca_and_function.ll
new file mode 100644
index 0000000000000..e65562bfe8caf
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/alloca_and_function.ll
@@ -0,0 +1,56 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --include-generated-funcs --version 5
+; RUN: opt < %s -passes=instrumentor -S | FileCheck %s
+
+; Check that we pack the arguments into a value_pack and unpack them again after the pre_function call.
+; Check that we replace the argument uses witht he unpacked values.
+; Check that we replace the alloca with the post_alloca returned value.
+
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+declare void @use(ptr)
+
+define float @foo(i16 %a, float %b) {
+entry:
+ %0 = alloca i16, align 16
+ store i16 %a, ptr %0
+ call void @use(ptr %0)
+ ret float %b
+}
+;.
+; CHECK: @__instrumentor_.str = private unnamed_addr constant [4 x i8] c"foo\00", align 1
+; CHECK: @__instrumentor_value_pack = internal global <{ i32, i32, [6 x i8], i16, i32, i32, [4 x i8], float }> <{ i32 2, i32 12, [6 x i8] zeroinitializer, i16 0, i32 4, i32 2, [4 x i8] zeroinitializer, float 0.000000e+00 }>
+;.
+; CHECK-LABEL: define float @foo(
+; CHECK-SAME: i16 [[A:%.*]], float [[B:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP7:%.*]] = alloca <{ i32, i32, [6 x i8], i16, i32, i32, [4 x i8], float }>, align 8
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP7]], ptr @__instrumentor_value_pack, i64 32, i1 false)
+; CHECK-NEXT: [[TMP2:%.*]] = getelementptr inbounds nuw <{ i32, i32, [6 x i8], i16, i32, i32, [4 x i8], float }>, ptr [[TMP7]], i32 0, i32 3
+; CHECK-NEXT: store i16 [[A]], ptr [[TMP2]], align 2
+; CHECK-NEXT: [[TMP9:%.*]] = getelementptr inbounds nuw <{ i32, i32, [6 x i8], i16, i32, i32, [4 x i8], float }>, ptr [[TMP7]], i32 0, i32 7
+; CHECK-NEXT: store float [[B]], ptr [[TMP9]], align 4
+; CHECK-NEXT: call void @__instrumentor_pre_function(ptr @foo, ptr @__instrumentor_.str, i32 2, ptr [[TMP7]], i8 0, i32 3) #[[ATTR1:[0-9]+]]
+; CHECK-NEXT: [[TMP3:%.*]] = getelementptr inbounds i8, ptr [[TMP7]], i32 14
+; CHECK-NEXT: [[TMP4:%.*]] = load i16, ptr [[TMP3]], align 2
+; CHECK-NEXT: [[TMP5:%.*]] = getelementptr inbounds i8, ptr [[TMP7]], i32 28
+; CHECK-NEXT: [[TMP6:%.*]] = load float, ptr [[TMP5]], align 4
+; CHECK-NEXT: [[TMP0:%.*]] = call i64 @__instrumentor_pre_alloca(i64 2, i64 16, i32 1) #[[ATTR1]]
+; CHECK-NEXT: [[TMP1:%.*]] = alloca i8, i64 [[TMP0]], align 16
+; CHECK-NEXT: [[TMP13:%.*]] = call ptr @__instrumentor_post_alloca(ptr [[TMP1]], i64 2, i64 16, i32 -1) #[[ATTR1]]
+; CHECK-NEXT: [[TMP10:%.*]] = zext i16 [[TMP4]] to i64
+; CHECK-NEXT: [[TMP14:%.*]] = call ptr @__instrumentor_pre_store(ptr [[TMP13]], i32 0, i64 [[TMP10]], i64 2, i64 2, i32 12, i32 0, i8 1, i8 0, i32 2) #[[ATTR1]]
+; CHECK-NEXT: store i16 [[TMP4]], ptr [[TMP14]], align 2
+; CHECK-NEXT: call void @__instrumentor_post_store(ptr [[TMP13]], i32 0, i64 [[TMP10]], i64 2, i64 2, i32 12, i32 0, i8 1, i8 0, i32 -2) #[[ATTR1]]
+; CHECK-NEXT: call void @use(ptr [[TMP13]])
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP7]], ptr @__instrumentor_value_pack, i64 32, i1 false)
+; CHECK-NEXT: [[TMP12:%.*]] = getelementptr inbounds nuw <{ i32, i32, [6 x i8], i16, i32, i32, [4 x i8], float }>, ptr [[TMP7]], i32 0, i32 3
+; CHECK-NEXT: store i16 [[A]], ptr [[TMP12]], align 2
+; CHECK-NEXT: [[TMP11:%.*]] = getelementptr inbounds nuw <{ i32, i32, [6 x i8], i16, i32, i32, [4 x i8], float }>, ptr [[TMP7]], i32 0, i32 7
+; CHECK-NEXT: store float [[B]], ptr [[TMP11]], align 4
+; CHECK-NEXT: call void @__instrumentor_post_function(ptr @foo, ptr @__instrumentor_.str, i32 2, ptr [[TMP7]], i8 0, i32 -4) #[[ATTR1]]
+; CHECK-NEXT: ret float [[TMP6]]
+;
+;.
+; CHECK: attributes #[[ATTR0:[0-9]+]] = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+; CHECK: attributes #[[ATTR1]] = { willreturn }
+;.
diff --git a/llvm/test/Instrumentation/Instrumentor/default_config.json b/llvm/test/Instrumentation/Instrumentor/default_config.json
index 263ab58e2566d..336dc20cfd5e0 100644
--- a/llvm/test/Instrumentation/Instrumentor/default_config.json
+++ b/llvm/test/Instrumentation/Instrumentor/default_config.json
@@ -6,11 +6,48 @@
"runtime_stubs_file.description": "The file into which runtime stubs should be written.",
"target_regex": "",
"target_regex.description": "Regular expression to be matched against the module target. Only targets that match this regex will be instrumented",
+ "demangle_function_names": true,
+ "demangle_function_names.description": "Demangle functions names passed to the runtime.",
"host_enabled": true,
"host_enabled.description": "Instrument non-GPU targets",
"gpu_enabled": true,
"gpu_enabled.description": "Instrument GPU targets"
},
+ "function_pre": {
+ "function": {
+ "enabled": true,
+ "address": true,
+ "address.description": "The function address.",
+ "name": true,
+ "name.description": "The function name.",
+ "num_arguments": true,
+ "num_arguments.description": "Number of function arguments (without varargs).",
+ "arguments": true,
+ "arguments.replace": true,
+ "arguments.description": "Description of the arguments.",
+ "is_main": true,
+ "is_main.description": "Flag to indicate it is the main function.",
+ "id": true,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ }
+ },
+ "function_post": {
+ "function": {
+ "enabled": true,
+ "address": true,
+ "address.description": "The function address.",
+ "name": true,
+ "name.description": "The function name.",
+ "num_arguments": true,
+ "num_arguments.description": "Number of function arguments (without varargs).",
+ "arguments": true,
+ "arguments.description": "Description of the arguments.",
+ "is_main": true,
+ "is_main.description": "Flag to indicate it is the main function.",
+ "id": true,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ }
+ },
"instruction_pre": {
"load": {
"enabled": true,
@@ -34,6 +71,16 @@
"id": true,
"id.description": "A unique ID associated with the given instrumentor call"
},
+ "alloca": {
+ "enabled": true,
+ "size": true,
+ "size.replace": true,
+ "size.description": "The allocation size.",
+ "alignment": true,
+ "alignment.description": "The allocation alignment.",
+ "id": true,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ },
"store": {
"enabled": true,
"pointer": true,
@@ -84,6 +131,18 @@
"id": true,
"id.description": "A unique ID associated with the given instrumentor call"
},
+ "alloca": {
+ "enabled": true,
+ "address": true,
+ "address.replace": true,
+ "address.description": "The allocated memory address.",
+ "size": true,
+ "size.description": "The allocation size.",
+ "alignment": true,
+ "alignment.description": "The allocation alignment.",
+ "id": true,
+ "id.description": "A unique ID associated with the given instrumentor call"
+ },
"store": {
"enabled": true,
"pointer": true,
More information about the cfe-commits
mailing list