https://github.com/jdoerfert updated https://github.com/llvm/llvm-project/pull/195378
>From a4305765027e4f0555e4d9af7ceba8322ad720c1 Mon Sep 17 00:00:00 2001 From: Johannes Doerfert <[email protected]> Date: Mon, 11 May 2026 14:17:35 -0700 Subject: [PATCH 1/2] [Instrumentor] Use the pass builder's FileSystem for reading files In the IO sandbox, the old read calls caused the CI to fail. This changes uses the PassBuilder's FileSystem the same way other passes read files from disk (during CI). --- .../llvm/Transforms/IPO/Instrumentor.h | 10 ++++++--- .../Transforms/IPO/InstrumentorConfigFile.h | 2 +- llvm/lib/Passes/PassBuilderPipelines.cpp | 4 ++-- llvm/lib/Transforms/IPO/Instrumentor.cpp | 13 +++++++++++- .../Transforms/IPO/InstrumentorConfigFile.cpp | 21 ++++++++++++++----- 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h index 7dfc342031579..331bbc259532e 100644 --- a/llvm/include/llvm/Transforms/IPO/Instrumentor.h +++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h @@ -15,6 +15,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/EnumeratedArray.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" @@ -697,6 +698,9 @@ class InstrumentorPass : public RequiredPassInfoMixin<InstrumentorPass> { using InstrumentationConfig = instrumentor::InstrumentationConfig; using InstrumentorIRBuilderTy = instrumentor::InstrumentorIRBuilderTy; + /// File system to be used for read operations. + IntrusiveRefCntPtr<vfs::FileSystem> FS; + /// The configuration and IR builder provided by the user. InstrumentationConfig *UserIConf; InstrumentorIRBuilderTy *UserIIRB; @@ -710,9 +714,9 @@ class InstrumentorPass : public RequiredPassInfoMixin<InstrumentorPass> { /// 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) {} + InstrumentorPass(IntrusiveRefCntPtr<vfs::FileSystem> FS = nullptr, + InstrumentationConfig *IC = nullptr, + InstrumentorIRBuilderTy *IIRB = nullptr); PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM); }; diff --git a/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h b/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h index cae68f4b34f08..28efa77d772c4 100644 --- a/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h +++ b/llvm/include/llvm/Transforms/IPO/InstrumentorConfigFile.h @@ -25,7 +25,7 @@ void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile, /// Read the configuration from the file with path \p InputFile into /p IConf. bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile, - LLVMContext &Ctx); + LLVMContext &Ctx, vfs::FileSystem &FS); } // end namespace instrumentor } // end namespace llvm diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp index 2d2867b9b84d1..b96f5626734d3 100644 --- a/llvm/lib/Passes/PassBuilderPipelines.cpp +++ b/llvm/lib/Passes/PassBuilderPipelines.cpp @@ -1648,7 +1648,7 @@ PassBuilder::buildModuleOptimizationPipeline(OptimizationLevel Level, // Run the Instrumentor pass late. if (EnableInstrumentor) - MPM.addPass(InstrumentorPass()); + MPM.addPass(InstrumentorPass(FS)); // Split out cold code. Splitting is done late to avoid hiding context from // other optimizations and inadvertently regressing performance. The tradeoff @@ -2429,7 +2429,7 @@ PassBuilder::buildO0DefaultPipeline(OptimizationLevel Level, invokeOptimizerLastEPCallbacks(MPM, Level, Phase); if (EnableInstrumentor) - MPM.addPass(InstrumentorPass()); + MPM.addPass(InstrumentorPass(FS)); if (isLTOPreLink(Phase)) addRequiredLTOPreLinkPasses(MPM); diff --git a/llvm/lib/Transforms/IPO/Instrumentor.cpp b/llvm/lib/Transforms/IPO/Instrumentor.cpp index 33f00be11084a..eefa90b83742e 100644 --- a/llvm/lib/Transforms/IPO/Instrumentor.cpp +++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp @@ -16,6 +16,7 @@ #include "llvm/Transforms/IPO/InstrumentorStubPrinter.h" #include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" @@ -42,6 +43,8 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Regex.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" #include <cassert> #include <cstdint> @@ -257,11 +260,19 @@ bool InstrumentorImpl::instrument() { return Changed; } +InstrumentorPass::InstrumentorPass(IntrusiveRefCntPtr<vfs::FileSystem> FS, + InstrumentationConfig *IC, + InstrumentorIRBuilderTy *IIRB) + : FS(FS), UserIConf(IC), UserIIRB(IIRB) { + if (!FS) + this->FS = vfs::getRealFileSystem(); +} + PreservedAnalyses InstrumentorPass::run(Module &M, InstrumentationConfig &IConf, InstrumentorIRBuilderTy &IIRB, bool ReadConfig) { InstrumentorImpl Impl(IConf, IIRB, M); - if (ReadConfig && !readConfigFromJSON(IConf, ReadConfigFile, IIRB.Ctx)) + if (ReadConfig && !readConfigFromJSON(IConf, ReadConfigFile, IIRB.Ctx, *FS)) return PreservedAnalyses::all(); writeConfigToJSON(IConf, WriteConfigFile, IIRB.Ctx); diff --git a/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp index a2707193da1a7..7ee800a5c96a0 100644 --- a/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp +++ b/llvm/lib/Transforms/IPO/InstrumentorConfigFile.cpp @@ -20,9 +20,21 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/StringSaver.h" +#include "llvm/Support/VirtualFileSystem.h" #include <string> +using namespace llvm; + +static Expected<std::unique_ptr<MemoryBuffer>> +setupMemoryBuffer(const Twine &Filename, vfs::FileSystem &FS) { + auto BufferOrErr = Filename.str() == "-" ? MemoryBuffer::getSTDIN() + : FS.getBufferForFile(Filename); + if (std::error_code EC = BufferOrErr.getError()) + return errorCodeToError(EC); + return std::move(BufferOrErr.get()); +} + namespace llvm { namespace instrumentor { @@ -96,16 +108,15 @@ void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile, } bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile, - LLVMContext &Ctx) { + LLVMContext &Ctx, vfs::FileSystem &FS) { if (InputFile.empty()) return true; - std::error_code EC; - auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(InputFile); - if (std::error_code EC = BufferOrErr.getError()) { + auto BufferOrErr = setupMemoryBuffer(InputFile, FS); + if (Error E = BufferOrErr.takeError()) { Ctx.diagnose(DiagnosticInfoInstrumentation( Twine("failed to open instrumentor configuration file for reading: ") + - EC.message(), + toString(std::move(E)), DS_Warning)); return false; } >From 200de6c30bcc90bcd30f419b8183ac61050e1977 Mon Sep 17 00:00:00 2001 From: Johannes Doerfert <[email protected]> Date: Fri, 1 May 2026 11:01:07 -0700 Subject: [PATCH 2/2] [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..90ef4faeddf6f --- /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 331bbc259532e..cec4c69120ee4 100644 --- a/llvm/include/llvm/Transforms/IPO/Instrumentor.h +++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h @@ -359,6 +359,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( @@ -395,12 +398,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; @@ -540,6 +562,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() {}; @@ -609,10 +719,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); } }; @@ -684,10 +794,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 eefa90b83742e..9095e38a04d05 100644 --- a/llvm/lib/Transforms/IPO/Instrumentor.cpp +++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp @@ -22,6 +22,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" @@ -234,11 +235,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; } @@ -247,12 +282,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); @@ -326,6 +363,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); } @@ -536,6 +575,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, _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
