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 <[email protected]> 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, _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
