[llvm] [CopyProf] Add CopyProf instrumentation passes. (PR #207385)
Snehasish Kumar via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 16 23:07:35 PDT 2026
================
@@ -0,0 +1,324 @@
+//===-- 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/Support/ErrorHandling.h"
+#include "llvm/Support/FormatVariadic.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;
+
+ // Don't instrument a function at all if it's ending with 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 flase positive reports).
+ // Skipping this function favors false negatives over false positives.
+ for (const BasicBlock &BB : F)
+ if (BB.getTerminatingMustTailCall())
+ return false;
+
+ return F.hasFnAttribute(CopyProfCtorAttr) ||
+ F.hasFnAttribute(CopyProfCopyCtorAttr) ||
+ F.hasFnAttribute(CopyProfCopyAssignAttr) ||
+ F.hasFnAttribute(CopyProfDtorAttr);
+}
+
+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;
+ if (!to_integer<size_t>(F.getFnAttribute(Attr).getValueAsString(), IntValue,
+ /*Base=*/10)) {
+ report_fatal_error(formatv("Unable to parse integer value from function "
+ "attribute value in '{0}': {1}:{2}",
+ F.getName(), Attr,
+ F.getFnAttribute(Attr).getValueAsString()));
+ }
+ 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);
+
+ LLVMContext *Ctx;
+ 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) {
+ 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).
+ 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 &&
----------------
snehasish wrote:
Can we skip stores to allocas too?
https://github.com/llvm/llvm-project/pull/207385
More information about the llvm-commits
mailing list