diff --git a/clang/test/Driver/pass-plugin-entrypoints.c b/clang/test/Driver/pass-plugin-entrypoints.c new file mode 100644 index 0000000000000..1eb44ee1e8d4d --- /dev/null +++ b/clang/test/Driver/pass-plugin-entrypoints.c @@ -0,0 +1,41 @@ +// REQUIRES: pass-plugins +// UNSUPPORTED: system-windows + +// Default entry-point is Pipeline-EarlySimplification +// +// RUN: %clang -O0 -fpass-plugin=%pass_plugin_reference \ +// RUN: -S -emit-llvm -Xclang -fdebug-pass-manager %s -o /dev/null 2>&1 | \ +// RUN: FileCheck --check-prefix=EP-EARLY %s +// +// RUN: %clang -O2 -fpass-plugin=%pass_plugin_reference \ +// RUN: -S -emit-llvm -Xclang -fdebug-pass-manager %s -o /dev/null 2>&1 | \ +// RUN: FileCheck --check-prefix=EP-EARLY %s +// +// RUN: %clang -c -flto=full -O2 -fpass-plugin=%pass_plugin_reference \ +// RUN: -Xclang -fdebug-pass-manager %s -o /dev/null 2>&1 | \ +// RUN: FileCheck --check-prefix=EP-EARLY %s +// +// RUN: %clang -c -flto=thin -O2 -fpass-plugin=%pass_plugin_reference \ +// RUN: -Xclang -fdebug-pass-manager %s -o /dev/null 2>&1 | \ +// RUN: FileCheck --check-prefix=EP-EARLY %s +// +// EP-EARLY: Running pass: InstrumentorPass +// EP-EARLY: Running pass: AlwaysInlinerPass + +// Pass doesn't run if default entry-point is disabled +// RUN: env registerPipelineEarlySimplificationEPCallback=Off \ +// RUN: %clang -fpass-plugin=%pass_plugin_reference -S -emit-llvm \ +// RUN: -Xclang -fdebug-pass-manager %s -o /dev/null 2>&1 | FileCheck %s +// +// CHECK-NOT: Running pass: InstrumentorPass + +// Pass runs twice if we add entry-point Opt-Early +// RUN: env registerOptimizerEarlyEPCallback=On \ +// RUN: %clang -fpass-plugin=%pass_plugin_reference -S -emit-llvm \ +// RUN: -Xclang -fdebug-pass-manager %s -o /dev/null 2>&1 | FileCheck --check-prefix=OPT-EARLY %s +// +// OPT-EARLY: Running pass: InstrumentorPass +// OPT-EARLY: Running pass: AlwaysInlinerPass +// OPT-EARLY: Running pass: InstrumentorPass + +int main() { return 0; } diff --git a/clang/test/Driver/pass-plugin-params.c b/clang/test/Driver/pass-plugin-params.c new file mode 100644 index 0000000000000..03661f3dc7233 --- /dev/null +++ b/clang/test/Driver/pass-plugin-params.c @@ -0,0 +1,13 @@ +// REQUIRES: pass-plugins +// UNSUPPORTED: system-windows + +// FIXME: This is supposed to work, but right now it doesn't. We need the extra -load like below. +// RUN: not %clang -fsyntax-only -fpass-plugin=%pass_plugin_reference \ +// RUN: -mllvm -instrumentor-write-config-file=%t_cfg.json %s 2>&1 | FileCheck %s +// +// RUN: %clang -fsyntax-only -fpass-plugin=%pass_plugin_reference -Xclang -load -Xclang %pass_plugin_reference \ +// RUN: -mllvm -instrumentor-write-config-file=%t_cfg.json %s +// +// CHECK: Unknown command line argument + +int main() { return 0; } diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py index 1957bb1715eb6..da360e52c7241 100644 --- a/clang/test/lit.cfg.py +++ b/clang/test/lit.cfg.py @@ -264,6 +264,13 @@ def have_host_clang_repl_cuda(): if config.clang_default_pie_on_linux: config.available_features.add("default-pie-on-linux") +# Run end-to-end tests if the reference pass-plugin exists in LLVM +plugin_name = f"{config.llvm_plugin_prefix}ReferencePlugin{config.llvm_plugin_ext}" +plugin_shlib = os.path.join(config.llvm_shlib_dir, plugin_name) +if os.path.exists(plugin_shlib): + config.available_features.add("pass-plugins") + config.substitutions.append(("%pass_plugin_reference", plugin_shlib)) + # Set available features we allow tests to conditionalize on. # if config.clang_default_cxx_stdlib != "": diff --git a/clang/test/lit.site.cfg.py.in b/clang/test/lit.site.cfg.py.in index 176cf644badcc..7cef6ba341e0c 100644 --- a/clang/test/lit.site.cfg.py.in +++ b/clang/test/lit.site.cfg.py.in @@ -47,6 +47,7 @@ config.have_llvm_driver = @LLVM_TOOL_LLVM_DRIVER_BUILD@ config.spirv_tools_tests = @LLVM_INCLUDE_SPIRV_TOOLS_TESTS@ config.substitutions.append(("%llvm-version-major", "@LLVM_VERSION_MAJOR@")) config.has_key_instructions = @LLVM_EXPERIMENTAL_KEY_INSTRUCTIONS@ +config.llvm_plugin_prefix = "@CMAKE_SHARED_LIBRARY_PREFIX@" import lit.llvm lit.llvm.initialize(lit_config, config) diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py index 143cc3817bd08..2e2dd3c4ae6a8 100644 --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -700,3 +700,10 @@ def host_unwind_supports_jit(): if config.has_logf128: config.available_features.add("has_logf128") + +# Run tests with opt if the reference pass-plugin exists +plugin_name = f"{config.llvm_plugin_prefix}ReferencePlugin{config.llvm_plugin_ext}" +plugin_shlib = os.path.join(config.llvm_shlib_dir, plugin_name) +if os.path.exists(plugin_shlib): + config.available_features.add("pass-plugins") + config.substitutions.append(("%pass_plugin_reference", plugin_shlib)) diff --git a/llvm/test/lit.site.cfg.py.in b/llvm/test/lit.site.cfg.py.in index 893e2cbd4f62b..0394044b0b580 100644 --- a/llvm/test/lit.site.cfg.py.in +++ b/llvm/test/lit.site.cfg.py.in @@ -11,6 +11,7 @@ config.llvm_lib_dir = lit_config.substitute(path(r"@LLVM_LIBS_DIR@")) config.llvm_shlib_dir = lit_config.substitute(path(r"@SHLIBDIR@")) config.llvm_shlib_ext = "@SHLIBEXT@" config.llvm_plugin_ext = "@LLVM_PLUGIN_EXT@" +config.llvm_plugin_prefix = "@CMAKE_SHARED_LIBRARY_PREFIX@" config.llvm_exe_ext = "@EXEEXT@" config.lit_tools_dir = path(r"@LLVM_LIT_TOOLS_DIR@") config.errc_messages = "@LLVM_LIT_ERRC_MESSAGES@" diff --git a/llvm/test/tools/plugins-shlib/alloca.ll b/llvm/test/tools/plugins-shlib/alloca.ll new file mode 100644 index 0000000000000..81297b4343380 --- /dev/null +++ b/llvm/test/tools/plugins-shlib/alloca.ll @@ -0,0 +1,63 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 +; RUN: opt < %s -load-pass-plugin=%pass_plugin_reference -passes=instrumentor -S | FileCheck %s +; REQUIRES: pass-plugins + +target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + +define ptr @foo() { +; CHECK-LABEL: define ptr @foo() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[A:%.*]] = alloca ptr, align 8 +; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_alloca(ptr [[A]], i64 8, i64 8) +; CHECK-NEXT: ret ptr [[TMP0]] +; +entry: + %a = alloca ptr + ret ptr %a +} +define ptr @bar(i1 %c) { +; CHECK-LABEL: define ptr @bar( +; CHECK-SAME: i1 [[C:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, i32 5, align 4 +; CHECK-NEXT: [[TMP0:%.*]] = call ptr @__instrumentor_alloca(ptr [[B]], i64 20, i64 4) +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_alloca(ptr [[A]], i64 4, i64 4) +; CHECK-NEXT: [[S:%.*]] = select i1 [[C]], ptr [[TMP1]], ptr [[TMP0]] +; CHECK-NEXT: ret ptr [[S]] +; +entry: + %a = alloca i32 + %b = alloca i32, i32 5 + %s = select i1 %c, ptr %a, ptr %b + ret ptr %s +} +define ptr @baz(i32 %v) { +; CHECK-LABEL: define ptr @baz( +; CHECK-SAME: i32 [[V:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[A:%.*]] = alloca ptr, i32 [[V]], align 8 +; CHECK-NEXT: [[TMP0:%.*]] = zext i32 [[V]] to i64 +; CHECK-NEXT: [[TMP2:%.*]] = mul i64 8, [[TMP0]] +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @__instrumentor_alloca(ptr [[A]], i64 [[TMP2]], i64 8) +; CHECK-NEXT: ret ptr [[TMP1]] +; +entry: + %a = alloca ptr, i32 %v + ret ptr %a +} +define ptr @fizz() { +; CHECK-LABEL: define ptr @fizz() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[A:%.*]] = alloca , align 8 +; CHECK-NEXT: [[TMP1:%.*]] = getelementptr , ptr [[A]], i32 1 +; CHECK-NEXT: [[TMP2:%.*]] = ptrtoint ptr [[TMP1]] to i64 +; CHECK-NEXT: [[TMP0:%.*]] = ptrtoint ptr [[A]] to i64 +; CHECK-NEXT: [[TMP3:%.*]] = sub i64 [[TMP2]], [[TMP0]] +; CHECK-NEXT: [[TMP4:%.*]] = call ptr @__instrumentor_alloca(ptr [[A]], i64 [[TMP3]], i64 8) +; CHECK-NEXT: ret ptr [[TMP4]] +; +entry: + %a = alloca + ret ptr %a +} diff --git a/llvm/test/tools/plugins-shlib/default_config.json b/llvm/test/tools/plugins-shlib/default_config.json new file mode 100644 index 0000000000000..587931254a049 --- /dev/null +++ b/llvm/test/tools/plugins-shlib/default_config.json @@ -0,0 +1,13 @@ +{ + "Base": { + "RuntimeName": "__instrumentor_", + "PrintRuntimeSignatures": true + }, + "Alloca": { + "Instrument": true, + "Value": true, + "AllocationSize": true, + "Alignment": true, + "ReplaceValue": true + } +} \ No newline at end of file diff --git a/llvm/test/tools/plugins-shlib/just_calls_config.json b/llvm/test/tools/plugins-shlib/just_calls_config.json new file mode 100644 index 0000000000000..4f1608318fc71 --- /dev/null +++ b/llvm/test/tools/plugins-shlib/just_calls_config.json @@ -0,0 +1,14 @@ +{ + "Base": { + "RuntimeName": "__just_calls_", + "PrintRuntimeSignatures": false + }, + "Alloca": { + "Instrument": true, + "Value": false, + "AllocationSize": false, + "Alignment": false, + "ArraySize": false, + "ReplaceValue": false + } +} diff --git a/llvm/test/tools/plugins-shlib/read_config.ll b/llvm/test/tools/plugins-shlib/read_config.ll new file mode 100644 index 0000000000000..b320cc85d739b --- /dev/null +++ b/llvm/test/tools/plugins-shlib/read_config.ll @@ -0,0 +1,22 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 +; RUN: opt < %s -load-pass-plugin=%pass_plugin_reference -passes=instrumentor -instrumentor-read-config-file=%S/just_calls_config.json -S | FileCheck %s + +target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + +define ptr @bar(i1 %c) { +; CHECK-LABEL: define ptr @bar( +; CHECK-SAME: i1 [[C:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, i32 5, align 4 +; CHECK-NEXT: call void @__just_calls_alloca() +; CHECK-NEXT: call void @__just_calls_alloca() +; CHECK-NEXT: [[S:%.*]] = select i1 [[C]], ptr [[A]], ptr [[B]] +; CHECK-NEXT: ret ptr [[S]] +; +entry: + %a = alloca i32 + %b = alloca i32, i32 5 + %s = select i1 %c, ptr %a, ptr %b + ret ptr %s +} diff --git a/llvm/test/tools/plugins-shlib/write_config.ll b/llvm/test/tools/plugins-shlib/write_config.ll new file mode 100644 index 0000000000000..459344876ce60 --- /dev/null +++ b/llvm/test/tools/plugins-shlib/write_config.ll @@ -0,0 +1,3 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 +; RUN: opt < %s -load-pass-plugin=%pass_plugin_reference -passes=instrumentor -instrumentor-write-config-file=%t.json +; RUN: diff %t.json %S/default_config.json diff --git a/llvm/tools/plugins-shlib/CMakeLists.txt b/llvm/tools/plugins-shlib/CMakeLists.txt new file mode 100644 index 0000000000000..76e32046c8f5e --- /dev/null +++ b/llvm/tools/plugins-shlib/CMakeLists.txt @@ -0,0 +1,7 @@ +add_llvm_library(ReferencePlugin SHARED INSTALL_WITH_TOOLCHAIN + Instrumentor.cpp + ReferencePlugin.cpp + + DEPENDS + intrinsics_gen +) diff --git a/llvm/tools/plugins-shlib/Instrumentor.cpp b/llvm/tools/plugins-shlib/Instrumentor.cpp new file mode 100644 index 0000000000000..dac6c601f3e6e --- /dev/null +++ b/llvm/tools/plugins-shlib/Instrumentor.cpp @@ -0,0 +1,341 @@ +//===-- Instrumentor.cpp - Highly configurable instrumentation pass -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +#include "Instrumentor.h" + +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/CFG.h" +#include "llvm/IR/ConstantFolder.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace llvm; + +#define DEBUG_TYPE "instrumentor" + +cl::opt WriteJSONConfig( + "instrumentor-write-config-file", + cl::desc( + "Write the instrumentor configuration into the specified JSON file"), + cl::init("")); +cl::opt ReadJSONConfig( + "instrumentor-read-config-file", + cl::desc( + "Read the instrumentor configuration from the specified JSON file"), + cl::init("")); + +namespace { + +template +void dumpObject(json::OStream &J, Targs... Fargs) {} + +void writeInstrumentorConfig(InstrumentorConfig &IC) { + if (WriteJSONConfig.empty()) + return; + + std::error_code EC; + raw_fd_stream OS(WriteJSONConfig, EC); + if (EC) { + errs() << "WARNING: Failed to open instrumentor configuration file for " + "writing: " + << EC.message() << "\n"; + return; + } + + json::OStream J(OS, 2); + J.objectBegin(); + +#define SECTION_START(SECTION, CLASS) \ + J.attributeBegin(#SECTION); \ + J.objectBegin(); +#define CONFIG_INTERNAL(SECTION, TYPE, NAME, DEFAULT_VALUE) +#define CONFIG(SECTION, TYPE, NAME, DEFAULT_VALUE) \ + J.attribute(#NAME, IC.SECTION.NAME); +#define SECTION_END(SECTION) \ + J.objectEnd(); \ + J.attributeEnd(); + +#include "InstrumentorConfig.def" + + J.objectEnd(); +} + +bool readInstrumentorConfigFromJSON(InstrumentorConfig &IC) { + if (ReadJSONConfig.empty()) + return true; + + std::error_code EC; + auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(ReadJSONConfig); + if (std::error_code EC = BufferOrErr.getError()) { + errs() << "WARNING: Failed to open instrumentor configuration file for " + "reading: " + << EC.message() << "\n"; + return false; + } + auto Buffer = std::move(BufferOrErr.get()); + json::Path::Root NullRoot; + auto Parsed = json::parse(Buffer->getBuffer()); + if (!Parsed) { + errs() << "WARNING: Failed to parse the instrumentor configuration file: " + << Parsed.takeError() << "\n"; + return false; + } + auto *Config = Parsed->getAsObject(); + if (!Config) { + errs() << "WARNING: Failed to parse the instrumentor configuration file: " + "Expected " + "an object '{ ... }'\n"; + return false; + } + + auto End = Config->end(), It = Config->begin(); + +#define CONFIG(SECTION, TYPE, NAME, DEFAULT_VALUE) \ + It = Config->find(#SECTION); \ + if (It != End) { \ + if (auto *InstObj = It->second.getAsObject()) { \ + if (auto *Val = InstObj->get(#NAME)) { \ + if (!json::fromJSON(*Val, IC.SECTION.NAME, NullRoot)) \ + errs() << "WARNING: Failed to read " #SECTION "." #NAME " as " #TYPE \ + << "\n"; \ + } \ + } \ + } + +#define SECTION_START(SECTION, CLASS) +#define CONFIG_INTERNAL(SECTION, TYPE, NAME, DEFAULT_VALUE) +#define SECTION_END(SECTION) + +#include "InstrumentorConfig.def" + + return true; +} + +raw_ostream &printAsCType(raw_ostream &OS, Type *T) { + if (T->isPointerTy()) + return OS << "void* "; + if (T->isIntegerTy()) + return OS << "int" << T->getIntegerBitWidth() << "_t "; + return OS << *T << " "; +} + +class InstrumentorImpl final { +public: + InstrumentorImpl(const InstrumentorConfig &IC, Module &M) + : IC(IC), M(M), Ctx(M.getContext()), + IRB(Ctx, ConstantFolder(), + IRBuilderCallbackInserter( + [&](Instruction *I) { NewInsts[I] = Epoche; })) {} + + /// Instrument the module, public entry point. + bool instrument(); + +private: + bool shouldInstrumentFunction(Function *Fn); + bool instrumentFunction(Function &Fn); + bool instrument(AllocaInst &I); + + template Constant *getCI(Type *IT, Ty Val) { + return ConstantInt::get(IT, Val); + } + + std::string getRTName(StringRef Suffix) { + return (IC.Base.RuntimeName + Suffix).str(); + } + + DenseMap InstrumentationFunctions; + FunctionCallee getCallee(Instruction &I, SmallVectorImpl &RTArgTypes, + SmallVectorImpl &RTArgNames, + Type *RT = nullptr) { + FunctionCallee &FC = InstrumentationFunctions[I.getOpcode()]; + if (!FC.getFunctionType()) { + FC = M.getOrInsertFunction( + getRTName(I.getOpcodeName()), + FunctionType::get(RT ? RT : VoidTy, RTArgTypes, /*IsVarArgs*/ false)); + + if (IC.Base.PrintRuntimeSignatures) { + printAsCType(outs(), FC.getFunctionType()->getReturnType()); + outs() << FC.getCallee()->getName() << "("; + auto *FT = FC.getFunctionType(); + for (int I = 0, E = RTArgNames.size(); I != E; ++I) { + if (I != 0) + outs() << ", "; + printAsCType(outs(), FT->getParamType(I)) << RTArgNames[I]; + } + outs() << ");\n"; + } + } + return FC; + } + + /// Each instrumentation, i.a., of an instruction, is happening in a dedicated + /// epoche. The epoche allows to determine if instrumentation instructions + /// were already around, due to prior instrumentations, or have been + /// introduced to support the current instrumentation, i.a., compute + /// information about the current instruction. + unsigned Epoche = 0; + + /// A mapping from instrumentation instructions to the epoche they have been + /// created. + DenseMap NewInsts; + + /// The instrumentor configuration. + const InstrumentorConfig &IC; + + /// The module and the LLVM context. + Module &M; + LLVMContext &Ctx; + + /// A special IR builder that keeps track of the inserted instructions. + IRBuilder IRB; + + /// Commonly used values for IR inspection and creation. + ///{ + + const DataLayout &DL = M.getDataLayout(); + + Type *VoidTy = Type::getVoidTy(Ctx); + Type *IntptrTy = M.getDataLayout().getIntPtrType(Ctx); + PointerType *PtrTy = PointerType::getUnqual(Ctx); + IntegerType *Int8Ty = Type::getInt8Ty(Ctx); + IntegerType *Int32Ty = Type::getInt32Ty(Ctx); + IntegerType *Int64Ty = Type::getInt64Ty(Ctx); + ///} +}; + +} // end anonymous namespace + +bool InstrumentorImpl::shouldInstrumentFunction(Function *Fn) { + if (!Fn || Fn->isDeclaration()) + return false; + return true; +} + +bool InstrumentorImpl::instrument(AllocaInst &I) { + if (IC.Alloca.CB && !IC.Alloca.CB(I)) + return false; + + Instruction *IP = I.getNextNode(); + while (isa(IP)) + IP = IP->getNextNode(); + IRB.SetInsertPoint(IP); + + SmallVector RTArgTypes; + SmallVector RTArgs; + SmallVector RTArgNames; + + if (IC.Alloca.Value) { + auto *ArgTy = PtrTy; + RTArgTypes.push_back(ArgTy); + RTArgs.push_back(IRB.CreatePointerBitCastOrAddrSpaceCast(&I, ArgTy)); + RTArgNames.push_back("Value"); + } + + if (IC.Alloca.AllocationSize) { + auto *ArgTy = Int64Ty; + RTArgTypes.push_back(ArgTy); + Value *SizeValue = nullptr; + TypeSize TypeSize = DL.getTypeAllocSize(I.getAllocatedType()); + if (TypeSize.isFixed()) + SizeValue = getCI(ArgTy, TypeSize.getFixedValue()); + if (!SizeValue) { + auto *LHS = IRB.CreatePtrToInt( + IRB.CreateGEP(I.getAllocatedType(), &I, {getCI(Int32Ty, 1)}), ArgTy); + SizeValue = IRB.CreateSub(LHS, IRB.CreatePtrToInt(&I, ArgTy)); + } + if (I.isArrayAllocation()) + SizeValue = IRB.CreateMul( + SizeValue, IRB.CreateZExtOrBitCast(I.getArraySize(), ArgTy)); + RTArgs.push_back(SizeValue); + RTArgNames.push_back("AllocationSize"); + } + + if (IC.Alloca.Alignment) { + auto *ArgTy = Int64Ty; + RTArgTypes.push_back(ArgTy); + RTArgs.push_back(getCI(ArgTy, I.getAlign().value())); + RTArgNames.push_back("Alignment"); + } + + Type *RetTy = IC.Alloca.ReplaceValue ? PtrTy : nullptr; + FunctionCallee FC = getCallee(I, RTArgTypes, RTArgNames, RetTy); + auto *CI = IRB.CreateCall(FC, RTArgs); + if (IC.Alloca.ReplaceValue) + I.replaceUsesWithIf( + IRB.CreatePointerBitCastOrAddrSpaceCast(CI, I.getType()), [&](Use &U) { + return NewInsts.lookup(cast(U.getUser())) != Epoche; + }); + + return true; +} + +bool InstrumentorImpl::instrumentFunction(Function &Fn) { + bool Changed = false; + if (!shouldInstrumentFunction(&Fn)) + return Changed; + + ReversePostOrderTraversal RPOT(&Fn); + for (auto &It : RPOT) { + for (auto &I : *It) { + // Skip instrumentation instructions. + if (NewInsts.contains(&I)) + continue; + + // Count epochs eagerly. + ++Epoche; + + switch (I.getOpcode()) { + case Instruction::Alloca: + if (IC.Alloca.Instrument) + instrument(cast(I)); + break; + default: + break; + } + } + } + + return Changed; +} + +bool InstrumentorImpl::instrument() { + bool Changed = false; + + for (Function &Fn : M) + Changed |= instrumentFunction(Fn); + + return Changed; +} + +PreservedAnalyses InstrumentorPass::run(Module &M, ModuleAnalysisManager &MAM) { + InstrumentorImpl Impl(IC, M); + if (!readInstrumentorConfigFromJSON(IC)) + return PreservedAnalyses::all(); + writeInstrumentorConfig(IC); + if (!Impl.instrument()) + return PreservedAnalyses::all(); + assert(!verifyModule(M, &errs())); + return PreservedAnalyses::none(); +} diff --git a/llvm/tools/plugins-shlib/Instrumentor.h b/llvm/tools/plugins-shlib/Instrumentor.h new file mode 100644 index 0000000000000..bb017cc96d554 --- /dev/null +++ b/llvm/tools/plugins-shlib/Instrumentor.h @@ -0,0 +1,56 @@ +//===- Transforms/Instrumentation/Instrumentor.h --------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// A highly configurable instrumentation pass. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_PLUGINS_SHLIB_INSTRUMENTOR_H +#define LLVM_TOOLS_PLUGINS_SHLIB_INSTRUMENTOR_H + +#include "llvm/IR/Instruction.h" +#include "llvm/IR/PassManager.h" + +#include + +namespace llvm { + +/// Configuration for the Instrumentor. First generic configuration, followed by +/// the selection of what instruction classes and instructions should be +/// instrumented and how. +struct InstrumentorConfig { + + /// An optional callback that takes the instruction that is about to be + /// instrumented and can return false if it should be skipped. + using CallbackTy = std::function; + +#define SECTION_START(SECTION, CLASS) struct { + +#define CONFIG_INTERNAL(SECTION, TYPE, NAME, DEFAULT_VALUE) \ + TYPE NAME = DEFAULT_VALUE; + +#define CONFIG(SECTION, TYPE, NAME, DEFAULT_VALUE) TYPE NAME = DEFAULT_VALUE; + +#define SECTION_END(SECTION) \ + } \ + SECTION; + +#include "InstrumentorConfig.def" +}; + +class InstrumentorPass : public PassInfoMixin { + InstrumentorConfig IC; + +public: + InstrumentorPass(InstrumentorConfig IC = InstrumentorConfig{}) : IC(IC) {} + + PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM); +}; +} // end namespace llvm + +#endif // LLVM_TOOLS_PLUGINS_SHLIB_INSTRUMENTOR_H diff --git a/llvm/tools/plugins-shlib/InstrumentorConfig.def b/llvm/tools/plugins-shlib/InstrumentorConfig.def new file mode 100644 index 0000000000000..1d45919608b32 --- /dev/null +++ b/llvm/tools/plugins-shlib/InstrumentorConfig.def @@ -0,0 +1,56 @@ +//===- Transforms/Instrumentation/InstrumentorConfig.def ------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// + +// No include guards + +/// Base configuration +///{ +SECTION_START(Base, ) + +/// The base name which defines the runtime call names, i.a., +/// (...) +CONFIG(Base, std::string, RuntimeName, "__instrumentor_") + +/// Print the signatures of all used runtime functions. +CONFIG(Base, bool, PrintRuntimeSignatures, true) + +SECTION_END(Base) +///} + +/// AllocaInst +///{ +SECTION_START(Alloca, AllocaInst) + +/// Should allocas be instrumented. +CONFIG(Alloca, bool, Instrument, true) + +/// Selection of information passed to the runtime. +///{ +/// The actual allocated pointer. +CONFIG(Alloca, bool, Value, /* PtrTy */ true) +/// The size of the entire allocation. +CONFIG(Alloca, bool, AllocationSize, /* I64 */ true) +/// The minimal alignment requested statically. +CONFIG(Alloca, bool, Alignment, /* I64 */ true) +///} + +/// Should the value be replaced by the runtime call result. +CONFIG(Alloca, bool, ReplaceValue, true) + +/// Optional callback, see CallbackTy. +CONFIG_INTERNAL(Alloca, CallbackTy, CB, nullptr) + +SECTION_END(Alloca) +///} + +#undef SECTION_START +#undef CONFIG +#undef CONFIG_INTERNAL +#undef SECTION_END diff --git a/llvm/tools/plugins-shlib/ReferencePlugin.cpp b/llvm/tools/plugins-shlib/ReferencePlugin.cpp new file mode 100644 index 0000000000000..2dad9f61c6590 --- /dev/null +++ b/llvm/tools/plugins-shlib/ReferencePlugin.cpp @@ -0,0 +1,86 @@ +//===- tools/plugins-shlib/ReferencePlugin.cpp ----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Instrumentor.h" + +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" + +#include +#include +#include +#include + +using namespace llvm; + +static std::optional getEnv(const std::string &Var) { + const char *Val = std::getenv(Var.c_str()); + if (!Val) + return std::nullopt; + return std::string(Val); +} + +static bool getEnvBool(const std::string &VarName, bool Default = false) { + if (auto ValOpt = getEnv(VarName)) { + std::string V = *ValOpt; + std::transform(V.begin(), V.end(), V.begin(), ::tolower); + + if (V == "1" || V == "true" || V == "yes" || V == "on") + return true; + if (V == "0" || V == "false" || V == "no" || V == "off") + return false; + } + return Default; +} + +static void registerCallbacks(PassBuilder &PB) { + // Entry-points for module passes + if (getEnvBool("registerPipelineStartEPCallback")) + PB.registerPipelineStartEPCallback( + [](ModulePassManager &MPM, OptimizationLevel Opt) { + MPM.addPass(InstrumentorPass()); + return true; + }); + if (getEnvBool("registerPipelineEarlySimplificationEPCallback", true)) + PB.registerPipelineEarlySimplificationEPCallback( + [](ModulePassManager &MPM, OptimizationLevel Opt, + ThinOrFullLTOPhase Phase) { + MPM.addPass(InstrumentorPass()); + return true; + }); + if (getEnvBool("registerOptimizerEarlyEPCallback")) + PB.registerOptimizerEarlyEPCallback([](ModulePassManager &MPM, + OptimizationLevel Opt, + ThinOrFullLTOPhase Phase) { + MPM.addPass(InstrumentorPass()); + return true; + }); + if (getEnvBool("registerOptimizerLastEPCallback")) + PB.registerOptimizerLastEPCallback([](ModulePassManager &MPM, + OptimizationLevel Opt, + ThinOrFullLTOPhase Phase) { + MPM.addPass(InstrumentorPass()); + return true; + }); + if (getEnvBool("registerPipelineParsingCallback"), true) + PB.registerPipelineParsingCallback( + [](StringRef Name, ModulePassManager &PM, + ArrayRef) { + if (Name == "instrumentor") { + PM.addPass(InstrumentorPass()); + return true; + } + return false; + }); +} + +extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK +llvmGetPassPluginInfo() { + return {LLVM_PLUGIN_API_VERSION, "ReferencePlugin", LLVM_VERSION_STRING, + registerCallbacks}; +}