| //===-- CopyProf.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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// This file implements the LLVM IR instrumentation passes for CopyProf. | |
| /// It adds enter/exit callbacks to C++ special member functions, and | |
| /// instruments store instructions. | |
| /// | |
| /// The basic idea of the CopyProf algorithm works like this: | |
| /// An object copy Y is made from original object X. The shadow memory | |
| /// corresponding to (and owned by) Y is marked as "copied". Any subsequent | |
| /// memory store to the memory corresponding to Y marks the shadow memory as | |
| /// "modified". When Y is destroyed and all of its corresponding shadow memory | |
| /// is marked as "copied", the object is reported as an unnecessary copy. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/Transforms/Instrumentation/CopyProf.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/IR/Attributes.h" | |
| #include "llvm/IR/DerivedTypes.h" | |
| #include "llvm/IR/Function.h" | |
| #include "llvm/IR/IRBuilder.h" | |
| #include "llvm/IR/Instruction.h" | |
| #include "llvm/IR/Instructions.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IR/PassManager.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Transforms/Utils/Instrumentation.h" | |
| #include "llvm/Transforms/Utils/ModuleUtils.h" | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| // TODO: Convert CopyProfPass and CopyProfStoresPass to module passes so that | |
| // the runtime callbacks can be cached, thus avoiding repetitive symbol table | |
| // lookups. | |
| using namespace llvm; | |
| // Names for the module c'tor to initialize the runtime, and the runtime | |
| // initialization function itself. | |
| constexpr StringRef CopyProfModuleCtorName = "copyprof.module_ctor"; | |
| constexpr StringRef CopyProfInitName = "__copyprof_init_once"; | |
| // Runtime callback function names. | |
| constexpr StringRef CopyProfCtorEnterCallbackName = | |
| "__copyprof_ctor_enter_callback"; | |
| constexpr StringRef CopyProfCtorExitCallbackName = | |
| "__copyprof_ctor_exit_callback"; | |
| constexpr StringRef CopyProfCopyCtorEnterCallbackName = | |
| "__copyprof_copy_ctor_enter_callback"; | |
| constexpr StringRef CopyProfCopyCtorExitCallbackName = | |
| "__copyprof_copy_ctor_exit_callback"; | |
| constexpr StringRef CopyProfCopyAssignOpEnterCallbackName = | |
| "__copyprof_copy_assign_op_enter_callback"; | |
| constexpr StringRef CopyProfCopyAssignOpExitCallbackName = | |
| "__copyprof_copy_assign_op_exit_callback"; | |
| constexpr StringRef CopyProfDtorEnterCallbackName = | |
| "__copyprof_dtor_enter_callback"; | |
| constexpr StringRef CopyProfDtorExitCallbackName = | |
| "__copyprof_dtor_exit_callback"; | |
| constexpr StringRef CopyProfStoreCallbackName = "__copyprof_store_callback"; | |
| // Attribute strings used by the frontend to mark special member functions. | |
| constexpr StringRef CopyProfCtorAttr = "copyprof-ctor"; | |
| constexpr StringRef CopyProfCopyCtorAttr = "copyprof-copy-ctor"; | |
| constexpr StringRef CopyProfCopyAssignAttr = "copyprof-copy-assign-op"; | |
| constexpr StringRef CopyProfDtorAttr = "copyprof-dtor"; | |
| static bool insertModuleCtor(Module &M) { | |
| bool Modified = false; | |
| getOrCreateSanitizerCtorAndInitFunctions( | |
| M, CopyProfModuleCtorName, CopyProfInitName, | |
| /*InitArgTypes=*/{}, | |
| /*InitArgs=*/{}, [&](Function *Ctor, FunctionCallee) { | |
| // Mark the ctor so it's never instrumented itself. | |
| Ctor->addFnAttr(Attribute::DisableSanitizerInstrumentation); | |
| appendToGlobalCtors(M, Ctor, 0); | |
| Modified = true; | |
| }); | |
| return Modified; | |
| } | |
| static bool isCopyProfCandidate(const Function &F) { | |
| // Must not instrument functions that are explicitly disallowed for | |
| // instrumentation, or naked functions. | |
| if (F.isDeclaration() || | |
| F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation) || | |
| F.hasFnAttribute(Attribute::Naked)) | |
| return false; | |
| if (!F.hasFnAttribute(CopyProfCtorAttr) && | |
| !F.hasFnAttribute(CopyProfCopyCtorAttr) && | |
| !F.hasFnAttribute(CopyProfCopyAssignAttr) && | |
| !F.hasFnAttribute(CopyProfDtorAttr)) | |
| return false; | |
| // Don't instrument a function at all if it ends in a tail call. | |
| // Alternatively, the exit callback could be placed before the tail call, but | |
| // that would risk missing observable side-effects needed by CopyProf to infer | |
| // memory ownership (potentially leading to false positive reports). | |
| // For example, if the tail would deallocate memory then CopyProf would be | |
| // unable to inspect that memory and the object could be misclassified as | |
| // having been unnecessarily copied. Skipping functions ending in musttail | |
| // calls therefore favors false negatives over false positives. | |
| for (const BasicBlock &BB : F) | |
| if (BB.getTerminatingMustTailCall()) | |
| return false; | |
| return true; | |
| } | |
| static bool isCopyProfStoresCandidate(const Function &F) { | |
| return !F.isDeclaration() && | |
| !F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation) && | |
| !F.hasFnAttribute(Attribute::Naked); | |
| } | |
| // Returns the object size in bytes that was stored in the given function | |
| // attribute during parsing in the frontend. | |
| static size_t getAttrValueAsInt(const Function &F, StringRef Attr) { | |
| size_t IntValue = 0; | |
| [[maybe_unused]] bool Success = | |
| to_integer<size_t>(F.getFnAttribute(Attr).getValueAsString(), IntValue, | |
| /*Base=*/10); | |
| assert(Success && | |
| "Unable to parse object size from CopyProf function attribute value."); | |
| return IntValue; | |
| } | |
| namespace { | |
| // Instruments special member functions to call into the CopyProf runtime. | |
| class CopyProf { | |
| public: | |
| explicit CopyProf(Module &M); | |
| bool instrumentFunction(Function &F); | |
| private: | |
| void insertCallback(Function &F, size_t ObjSize, unsigned NumArgs, | |
| FunctionCallee Callback, FunctionCallee ExitCallback); | |
| Type *IntPtrTy; | |
| FunctionCallee CtorEnterCallback; | |
| FunctionCallee CtorExitCallback; | |
| FunctionCallee CopyCtorEnterCallback; | |
| FunctionCallee CopyCtorExitCallback; | |
| FunctionCallee CopyAssignOpEnterCallback; | |
| FunctionCallee CopyAssignOpExitCallback; | |
| FunctionCallee DtorEnterCallback; | |
| FunctionCallee DtorExitCallback; | |
| }; | |
| // Late-stage pass that instruments store instructions after all optimizations | |
| // have run (to avoid instrumenting stores that would be eliminated). | |
| class CopyProfStores { | |
| public: | |
| explicit CopyProfStores(Module &M); | |
| bool instrumentFunction(Function &F); | |
| private: | |
| Type *IntPtrTy; | |
| FunctionCallee StoreCallback; | |
| }; | |
| } // namespace | |
| CopyProf::CopyProf(Module &M) { | |
| LLVMContext &Ctx = M.getContext(); | |
| IRBuilder<> IRB(Ctx); | |
| IntPtrTy = IRB.getIntPtrTy(M.getDataLayout()); | |
| Type *PtrTy = IRB.getPtrTy(); | |
| Type *VoidTy = IRB.getVoidTy(); | |
| // CopyProf callbacks never throw exceptions. | |
| AttributeList Attr; | |
| Attr = Attr.addFnAttribute(Ctx, Attribute::NoUnwind); | |
| CtorEnterCallback = M.getOrInsertFunction(CopyProfCtorEnterCallbackName, Attr, | |
| VoidTy, PtrTy, IntPtrTy); | |
| CtorExitCallback = M.getOrInsertFunction(CopyProfCtorExitCallbackName, Attr, | |
| VoidTy, PtrTy, IntPtrTy); | |
| CopyCtorEnterCallback = M.getOrInsertFunction( | |
| CopyProfCopyCtorEnterCallbackName, Attr, VoidTy, PtrTy, PtrTy, IntPtrTy); | |
| CopyCtorExitCallback = M.getOrInsertFunction( | |
| CopyProfCopyCtorExitCallbackName, Attr, VoidTy, PtrTy, PtrTy, IntPtrTy); | |
| CopyAssignOpEnterCallback = | |
| M.getOrInsertFunction(CopyProfCopyAssignOpEnterCallbackName, Attr, VoidTy, | |
| PtrTy, PtrTy, IntPtrTy); | |
| CopyAssignOpExitCallback = | |
| M.getOrInsertFunction(CopyProfCopyAssignOpExitCallbackName, Attr, VoidTy, | |
| PtrTy, PtrTy, IntPtrTy); | |
| DtorEnterCallback = M.getOrInsertFunction(CopyProfDtorEnterCallbackName, Attr, | |
| VoidTy, PtrTy, IntPtrTy); | |
| DtorExitCallback = M.getOrInsertFunction(CopyProfDtorExitCallbackName, Attr, | |
| VoidTy, PtrTy, IntPtrTy); | |
| } | |
| bool CopyProf::instrumentFunction(Function &F) { | |
| bool Modified = true; | |
| if (F.hasFnAttribute(CopyProfCtorAttr)) | |
| insertCallback(F, getAttrValueAsInt(F, CopyProfCtorAttr), /*NumArgs=*/1, | |
| CtorEnterCallback, CtorExitCallback); | |
| else if (F.hasFnAttribute(CopyProfCopyCtorAttr)) | |
| insertCallback(F, getAttrValueAsInt(F, CopyProfCopyCtorAttr), /*NumArgs=*/2, | |
| CopyCtorEnterCallback, CopyCtorExitCallback); | |
| else if (F.hasFnAttribute(CopyProfCopyAssignAttr)) | |
| insertCallback(F, getAttrValueAsInt(F, CopyProfCopyAssignAttr), | |
| /*NumArgs=*/2, CopyAssignOpEnterCallback, | |
| CopyAssignOpExitCallback); | |
| else if (F.hasFnAttribute(CopyProfDtorAttr)) | |
| insertCallback(F, getAttrValueAsInt(F, CopyProfDtorAttr), /*NumArgs=*/1, | |
| DtorEnterCallback, DtorExitCallback); | |
| else | |
| Modified = false; | |
| return Modified; | |
| } | |
| void CopyProf::insertCallback(Function &F, size_t ObjSize, unsigned NumArgs, | |
| FunctionCallee EntryCallback, | |
| FunctionCallee ExitCallback) { | |
| auto InsertCallback = [IntPtrTy = IntPtrTy, ObjSize, | |
| NumArgs](Function &F, InstrumentationIRBuilder &&IRB, | |
| FunctionCallee Callback) { | |
| SmallVector<Value *, 3> Args; | |
| // `this` is always the first argument to a special member function, but | |
| // copy c'tor / copy assignment operator will have the other `this` ptr | |
| // passed as their second argument. | |
| assert(NumArgs == 1 || NumArgs == 2); | |
| for (unsigned I = 0; I < NumArgs; ++I) | |
| Args.push_back(F.getArg(I)); | |
| // The last argument to the callback is the static size of the object | |
| // pointed at by `this`. | |
| Args.push_back(ConstantInt::get(IntPtrTy, ObjSize)); | |
| IRB.CreateCall(Callback, Args); | |
| }; | |
| InsertCallback( | |
| F, | |
| InstrumentationIRBuilder{&F.getEntryBlock(), | |
| F.getEntryBlock().getFirstNonPHIOrDbgOrAlloca()}, | |
| EntryCallback); | |
| for (BasicBlock &BB : F) { | |
| Instruction *Term = BB.getTerminator(); | |
| if (isa<ReturnInst>(Term) || isa<ResumeInst>(Term)) | |
| InsertCallback(F, InstrumentationIRBuilder{Term}, ExitCallback); | |
| } | |
| } | |
| CopyProfStores::CopyProfStores(Module &M) { | |
| LLVMContext &Ctx = M.getContext(); | |
| IRBuilder<> IRB(Ctx); | |
| IntPtrTy = IRB.getIntPtrTy(M.getDataLayout()); | |
| Type *PtrTy = IRB.getPtrTy(); | |
| Type *VoidTy = IRB.getVoidTy(); | |
| // CopyProf callbacks never throw exceptions. | |
| AttributeList Attr; | |
| Attr = Attr.addFnAttribute(Ctx, Attribute::NoUnwind); | |
| StoreCallback = M.getOrInsertFunction(CopyProfStoreCallbackName, Attr, VoidTy, | |
| PtrTy, IntPtrTy); | |
| } | |
| bool CopyProfStores::instrumentFunction(Function &F) { | |
| // TODO: Handle all types of memory stores (memory intrinsics, masked store | |
| // intrinsics, AtomicRMW, and AtomicCmpXchg). | |
| // TODO: Skip stores to alloca if only made of fundamental types, arrays | |
| // thereof and (possibly) class types that are trivial and aggregate. | |
| const DataLayout &DL = F.getParent()->getDataLayout(); | |
| SmallVector<StoreInst *, 16> ToInstrument; | |
| for (BasicBlock &BB : F) { | |
| for (Instruction &I : BB) { | |
| if (auto *SI = dyn_cast<StoreInst>(&I); | |
| SI != nullptr && SI->getPointerAddressSpace() == 0 && | |
| !SI->hasMetadata(LLVMContext::MD_nosanitize) && | |
| // Scalable vector stores have no compile-time-constant size so skip | |
| // them. | |
| !DL.getTypeStoreSize(SI->getValueOperand()->getType()).isScalable()) | |
| ToInstrument.push_back(SI); | |
| } | |
| } | |
| if (ToInstrument.empty()) | |
| return false; | |
| for (StoreInst *SI : ToInstrument) { | |
| uint64_t StoredSize = | |
| DL.getTypeStoreSize(SI->getValueOperand()->getType()).getFixedValue(); | |
| InstrumentationIRBuilder IRB(SI); | |
| std::array<Value *, 2> Args = {SI->getPointerOperand(), | |
| ConstantInt::get(IntPtrTy, StoredSize)}; | |
| IRB.CreateCall(StoreCallback, Args); | |
| } | |
| return true; | |
| } | |
| PreservedAnalyses CopyProfPass::run(Function &F, FunctionAnalysisManager &) { | |
| if (!isCopyProfCandidate(F)) | |
| return PreservedAnalyses::all(); | |
| CopyProf Impl(*F.getParent()); | |
| if (!Impl.instrumentFunction(F)) | |
| return PreservedAnalyses::all(); | |
| PreservedAnalyses PA; | |
| PA.preserveSet<CFGAnalyses>(); | |
| return PA; | |
| } | |
| PreservedAnalyses ModuleCopyProfPass::run(Module &M, ModuleAnalysisManager &) { | |
| return insertModuleCtor(M) ? PreservedAnalyses::none() | |
| : PreservedAnalyses::all(); | |
| } | |
| PreservedAnalyses CopyProfStoresPass::run(Function &F, | |
| FunctionAnalysisManager &) { | |
| if (!isCopyProfStoresCandidate(F)) | |
| return PreservedAnalyses::all(); | |
| CopyProfStores Impl(*F.getParent()); | |
| if (!Impl.instrumentFunction(F)) | |
| return PreservedAnalyses::all(); | |
| PreservedAnalyses PA; | |
| PA.preserveSet<CFGAnalyses>(); | |
| return PA; | |
| } |