| //===-- 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"; |
| |
| // 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; |
| } |