[llvm] [CopyProf] Add CopyProf instrumentation passes. (PR #207385)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 00:47:45 PDT 2026
https://github.com/newgre updated https://github.com/llvm/llvm-project/pull/207385
>From 8d1201d17174529b4848845d5e0f356e7475ab07 Mon Sep 17 00:00:00 2001
From: Jan Newger <jannewger at gmail.com>
Date: Tue, 30 Jun 2026 10:53:09 +0000
Subject: [PATCH 1/5] [CopyProf] Add CopyProf instrumentation passes.
This patch introduces the instrumentation passes and corresponding tests
for CopyProf, a profiling tool designed to identify unnecessary object
copies in C++ applications.
RFC at
https://discourse.llvm.org/t/rfc-copysanitizer-csan-detecting-unneccessary-object-copies-at-runtime/91038.
Three passes are added:
- CopyProfPass inserts enter/exit callback around special member
functions.
- CopyPRofStoresPass instruments store instructions to track memory
modifications.
- ModuleCopyProfPass inserts a module constructor to initialize the
CopyProf runtime at program startup (will be added later).
---
.../Transforms/Instrumentation/CopyProf.h | 52 +++
llvm/lib/Passes/PassBuilder.cpp | 1 +
llvm/lib/Passes/PassRegistry.def | 3 +
.../Transforms/Instrumentation/CMakeLists.txt | 1 +
.../Transforms/Instrumentation/CopyProf.cpp | 297 ++++++++++++++++++
.../CopyProf/function-instrumentation.ll | 135 ++++++++
.../CopyProf/no-instrumentation.ll | 66 ++++
.../CopyProf/store-instrumentation.ll | 101 ++++++
8 files changed, 656 insertions(+)
create mode 100644 llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
create mode 100644 llvm/lib/Transforms/Instrumentation/CopyProf.cpp
create mode 100644 llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
create mode 100644 llvm/test/Instrumentation/CopyProf/no-instrumentation.ll
create mode 100644 llvm/test/Instrumentation/CopyProf/store-instrumentation.ll
diff --git a/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h b/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
new file mode 100644
index 0000000000000..73c49024c52f5
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
@@ -0,0 +1,52 @@
+//===-- CopyProf.h ----------------------------------------------*- C++ -*-===//
+//
+// 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 declares the instrumentation passes for CopyProf that insert
+// callbacks into special member functions, and add store instrumentation.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_INSTRUMENTATION_COPYPROF_H
+#define LLVM_TRANSFORMS_INSTRUMENTATION_COPYPROF_H
+
+#include "llvm/IR/PassManager.h"
+namespace llvm {
+
+// Early-stage pass that instruments special member functions to call into the
+// CopyProf runtime.
+class CopyProfPass : public PassInfoMixin<CopyProfPass> {
+public:
+ CopyProfPass() = default;
+ PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+
+ static bool isRequired() { return true; }
+};
+
+// Module-level pass that inserts the CopyProf runtime initialization
+// constructor and hooks it into @llvm.global_ctors.
+class ModuleCopyProfPass : public PassInfoMixin<ModuleCopyProfPass> {
+public:
+ ModuleCopyProfPass() = default;
+ PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+
+ static bool isRequired() { return true; }
+};
+
+// Late-stage pass that instruments store instructions to detect whether an
+// object copy has been modified before it is destructed.
+class CopyProfStoresPass : public PassInfoMixin<CopyProfStoresPass> {
+public:
+ CopyProfStoresPass() = default;
+ PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+
+ static bool isRequired() { return true; }
+};
+
+} // namespace llvm
+
+#endif // LLVM_TRANSFORMS_INSTRUMENTATION_COPYPROF_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 4bc9e0c74c539..b8b5507447fd4 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -262,6 +262,7 @@
#include "llvm/Transforms/Instrumentation/BoundsChecking.h"
#include "llvm/Transforms/Instrumentation/CGProfile.h"
#include "llvm/Transforms/Instrumentation/ControlHeightReduction.h"
+#include "llvm/Transforms/Instrumentation/CopyProf.h"
#include "llvm/Transforms/Instrumentation/DataFlowSanitizer.h"
#include "llvm/Transforms/Instrumentation/GCOVProfiler.h"
#include "llvm/Transforms/Instrumentation/HWAddressSanitizer.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index bb5814f377f6b..cba01aab49963 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -63,6 +63,7 @@ MODULE_PASS("called-value-propagation", CalledValuePropagationPass())
MODULE_PASS("canonicalize-aliases", CanonicalizeAliasesPass())
MODULE_PASS("check-debugify", NewPMCheckDebugifyPass())
MODULE_PASS("constmerge", ConstantMergePass())
+MODULE_PASS("copyprof-module", ModuleCopyProfPass())
MODULE_PASS("coro-cleanup", CoroCleanupPass())
MODULE_PASS("coro-early", CoroEarlyPass())
MODULE_PASS("lower-comment-string", LowerCommentStringPass())
@@ -433,6 +434,8 @@ FUNCTION_PASS("codegenprepare", CodeGenPreparePass(*TM))
FUNCTION_PASS("complex-deinterleaving", ComplexDeinterleavingPass(*TM))
FUNCTION_PASS("consthoist", ConstantHoistingPass())
FUNCTION_PASS("constraint-elimination", ConstraintEliminationPass())
+FUNCTION_PASS("copyprof", CopyProfPass())
+FUNCTION_PASS("copyprof-stores", CopyProfStoresPass())
FUNCTION_PASS("coro-elide", CoroElidePass())
FUNCTION_PASS("correlated-propagation", CorrelatedValuePropagationPass())
FUNCTION_PASS("count-visits", CountVisitsPass())
diff --git a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
index 80576c61fd80c..7d2e658acb5c1 100644
--- a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
+++ b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
@@ -4,6 +4,7 @@ add_llvm_component_library(LLVMInstrumentation
BoundsChecking.cpp
CGProfile.cpp
ControlHeightReduction.cpp
+ CopyProf.cpp
DataFlowSanitizer.cpp
GCOVProfiling.cpp
BlockCoverageInference.cpp
diff --git a/llvm/lib/Transforms/Instrumentation/CopyProf.cpp b/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
new file mode 100644
index 0000000000000..6efc318b20195
--- /dev/null
+++ b/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
@@ -0,0 +1,297 @@
+//===-- 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) {
+ appendToGlobalCtors(M, Ctor, 0);
+ Modified = true;
+ });
+ return Modified;
+}
+
+// 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) {
+ // Must not instrument our own module c'tor or functions that are explicitly
+ // disallowed for instrumentation.
+ if (F.isDeclaration() ||
+ F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation) ||
+ F.getName() == CopyProfModuleCtorName)
+ return false;
+
+ 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) {
+ if (F.isDeclaration() ||
+ F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation) ||
+ F.getName() == CopyProfModuleCtorName)
+ return false;
+
+ // TODO: handle all types of memory stores (memory intrinsics, masked store
+ // intrinsics, AtomicRMW, and AtomicCmpXchg).
+ 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))
+ ToInstrument.push_back(SI);
+ }
+ }
+ if (ToInstrument.empty())
+ return false;
+
+ const DataLayout &DL = F.getParent()->getDataLayout();
+ for (StoreInst *SI : ToInstrument) {
+ uint64_t StoredSize = DL.getTypeStoreSize(SI->getValueOperand()->getType());
+ 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 &) {
+ 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 &) {
+ CopyProfStores Impl(*F.getParent());
+ if (!Impl.instrumentFunction(F))
+ return PreservedAnalyses::all();
+ PreservedAnalyses PA;
+ PA.preserveSet<CFGAnalyses>();
+ return PA;
+}
diff --git a/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll b/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
new file mode 100644
index 0000000000000..dd69dd7914250
--- /dev/null
+++ b/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
@@ -0,0 +1,135 @@
+; Tests basic CopyProf instrumentation of special member functions.
+;
+; RUN: opt < %s -passes='function(copyprof),module(copyprof-module)' -S | FileCheck %s
+
+target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
+target triple = "x86_64-unknown-linux-gnu"
+
+;; Verifies that the module constructor and global ctors are set up.
+; CHECK: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }]
+; CHECK-SAME: i32 0, ptr @copyprof.module_ctor
+
+;; Tests constructor instrumentation (1-arg: this).
+define void @ctor(ptr %this) "copyprof-ctor"="24" {
+entry:
+ %field = getelementptr i8, ptr %this, i64 8
+ store i32 0, ptr %field
+ ret void
+}
+; CHECK-LABEL: define void @ctor(ptr %this)
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @__copyprof_ctor_enter_callback(ptr %this, i64 24)
+; CHECK-NOT: call void @__copyprof_ctor_enter_callback
+; CHECK: call void @__copyprof_ctor_exit_callback(ptr %this, i64 24)
+; CHECK-NEXT: ret void
+
+;; Tests copy constructor instrumentation (2-arg: this, other).
+define void @copy_ctor(ptr %this, ptr %other) "copyprof-copy-ctor"="16" {
+entry:
+ %val = load i32, ptr %other
+ store i32 %val, ptr %this
+ ret void
+}
+; CHECK-LABEL: define void @copy_ctor(ptr %this, ptr %other)
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @__copyprof_copy_ctor_enter_callback(ptr %this, ptr %other, i64 16)
+; CHECK-NOT: call void @__copyprof_copy_ctor_enter_callback
+; CHECK: call void @__copyprof_copy_ctor_exit_callback(ptr %this, ptr %other, i64 16)
+; CHECK-NEXT: ret void
+
+;; Tests copy assignment operator instrumentation (2-arg: this, other).
+define void @copy_assign(ptr %this, ptr %other) "copyprof-copy-assign-op"="32" {
+entry:
+ %val = load i64, ptr %other
+ store i64 %val, ptr %this
+ ret void
+}
+; CHECK-LABEL: define void @copy_assign(ptr %this, ptr %other)
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @__copyprof_copy_assign_op_enter_callback(ptr %this, ptr %other, i64 32)
+; CHECK-NOT: call void @__copyprof_copy_assign_op_enter_callback
+; CHECK: call void @__copyprof_copy_assign_op_exit_callback(ptr %this, ptr %other, i64 32)
+; CHECK-NEXT: ret void
+
+;; Tests destructor instrumentation (1-arg: this).
+define void @dtor(ptr %this) "copyprof-dtor"="24" {
+entry:
+ ret void
+}
+; CHECK-LABEL: define void @dtor(ptr %this)
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @__copyprof_dtor_enter_callback(ptr %this, i64 24)
+; CHECK-NOT: call void @__copyprof_dtor_enter_callback
+; CHECK: call void @__copyprof_dtor_exit_callback(ptr %this, i64 24)
+; CHECK-NEXT: ret void
+
+;; Tests that exit callbacks are inserted before multiple return instructions.
+define void @ctor_multi_ret(ptr %this, i1 %cond) "copyprof-ctor"="8" {
+entry:
+ br i1 %cond, label %then, label %else
+then:
+ ret void
+else:
+ ret void
+}
+; CHECK-LABEL: define void @ctor_multi_ret(ptr %this, i1 %cond)
+; CHECK: call void @__copyprof_ctor_enter_callback(ptr %this, i64 8)
+; CHECK-NOT: call void @__copyprof_ctor_enter_callback
+; CHECK: then:
+; CHECK: call void @__copyprof_ctor_exit_callback(ptr %this, i64 8)
+; CHECK-NEXT: ret void
+; CHECK: else:
+; CHECK: call void @__copyprof_ctor_exit_callback(ptr %this, i64 8)
+; CHECK-NEXT: ret void
+
+;; Tests that exit callbacks are inserted before resume instructions
+;; (exception unwinding).
+define void @ctor_with_resume(ptr %this) "copyprof-ctor"="8" personality ptr @__gxx_personality_v0 {
+entry:
+ invoke void @may_throw() to label %cont unwind label %lpad
+cont:
+ ret void
+lpad:
+ %lp = landingpad { ptr, i32 } cleanup
+ resume { ptr, i32 } %lp
+}
+
+declare void @may_throw()
+declare i32 @__gxx_personality_v0(...)
+
+; CHECK-LABEL: define void @ctor_with_resume(ptr %this)
+; CHECK: call void @__copyprof_ctor_enter_callback(ptr %this, i64 8)
+; CHECK: cont:
+; CHECK: call void @__copyprof_ctor_exit_callback(ptr %this, i64 8)
+; CHECK-NEXT: ret void
+; CHECK: lpad:
+; CHECK: call void @__copyprof_ctor_exit_callback(ptr %this, i64 8)
+; CHECK-NEXT: resume
+
+;; Tests that entry callbacks are inserted after alloca instructions.
+define void @ctor_with_alloca(ptr %this) "copyprof-ctor"="8" {
+entry:
+ %tmp = alloca i32
+ store i32 0, ptr %tmp
+ ret void
+}
+; CHECK-LABEL: define void @ctor_with_alloca(ptr %this)
+; CHECK: %tmp = alloca i32
+; CHECK-NEXT: call void @__copyprof_ctor_enter_callback(ptr %this, i64 8)
+; CHECK: call void @__copyprof_ctor_exit_callback(ptr %this, i64 8)
+; CHECK-NEXT: ret void
+
+;; Tests that no exit callback is inserted before unreachable terminators.
+define void @ctor_with_unreachable(ptr %this) "copyprof-ctor"="8" {
+entry:
+ call void @may_throw()
+ unreachable
+}
+; CHECK-LABEL: define void @ctor_with_unreachable(ptr %this)
+; CHECK: call void @__copyprof_ctor_enter_callback(ptr %this, i64 8)
+; CHECK-NOT: call void @__copyprof_ctor_exit_callback
+; CHECK: unreachable
+
+;; Verifies that the module constructor calls the init function.
+; CHECK: define internal void @copyprof.module_ctor()
+; CHECK: call void @__copyprof_init()
diff --git a/llvm/test/Instrumentation/CopyProf/no-instrumentation.ll b/llvm/test/Instrumentation/CopyProf/no-instrumentation.ll
new file mode 100644
index 0000000000000..d56f990547f8f
--- /dev/null
+++ b/llvm/test/Instrumentation/CopyProf/no-instrumentation.ll
@@ -0,0 +1,66 @@
+; Tests that CopyProf skips functions that should not be instrumented.
+;
+; RUN: opt < %s -passes='function(copyprof)' -S | FileCheck %s
+; RUN: opt < %s -passes='function(copyprof-stores)' -S | FileCheck %s --check-prefix=STORES
+
+target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
+target triple = "x86_64-unknown-linux-gnu"
+
+;; Tests that a function without copyprof attributes is not instrumented.
+define void @no_attrs(ptr %this) {
+entry:
+ ret void
+}
+; CHECK-LABEL: define void @no_attrs(ptr %this)
+; CHECK-NOT: call void @__copyprof_
+; CHECK: ret void
+; STORES-LABEL: define void @no_attrs(ptr %this)
+; STORES-NOT: call void @__copyprof_store_callback
+; STORES: ret void
+
+;; Tests that instrumentation is skipped when disable_sanitizer_instrumentation is present.
+define void @disabled_ctor(ptr %this) "copyprof-ctor"="8" disable_sanitizer_instrumentation {
+entry:
+ ret void
+}
+; CHECK-LABEL: define void @disabled_ctor(ptr %this)
+; CHECK-NOT: call void @__copyprof_
+; CHECK: ret void
+
+;; Tests that store instrumentation is skipped when disable_sanitizer_instrumentation is present.
+define void @disabled_ctor_with_store(ptr %this) "copyprof-ctor"="8" disable_sanitizer_instrumentation {
+entry:
+ store i32 0, ptr %this
+ ret void
+}
+; CHECK-LABEL: define void @disabled_ctor_with_store(ptr %this)
+; CHECK-NOT: call void @__copyprof_
+; CHECK: ret void
+; STORES-LABEL: define void @disabled_ctor_with_store(ptr %this)
+; STORES-NOT: call void @__copyprof_store_callback
+; STORES: store i32 0, ptr %this
+; STORES-NEXT: ret void
+
+;; Tests that store instrumentation is skipped for regular functions when disable_sanitizer_instrumentation
+;; is present.
+define void @disabled_stores(ptr %a) disable_sanitizer_instrumentation {
+entry:
+ store i32 42, ptr %a
+ ret void
+}
+; STORES-LABEL: define void @disabled_stores(ptr %a)
+; STORES-NOT: call void @__copyprof_store_callback
+; STORES: store i32 42, ptr %a
+; STORES-NEXT: ret void
+
+;; Tests that the module constructor itself is not instrumented by either pass.
+define internal void @copyprof.module_ctor() {
+entry:
+ ret void
+}
+; CHECK-LABEL: define internal void @copyprof.module_ctor()
+; CHECK-NOT: call void @__copyprof_
+; CHECK: ret void
+; STORES-LABEL: define internal void @copyprof.module_ctor()
+; STORES-NOT: call void @__copyprof_store_callback
+; STORES: ret void
diff --git a/llvm/test/Instrumentation/CopyProf/store-instrumentation.ll b/llvm/test/Instrumentation/CopyProf/store-instrumentation.ll
new file mode 100644
index 0000000000000..0370a26073292
--- /dev/null
+++ b/llvm/test/Instrumentation/CopyProf/store-instrumentation.ll
@@ -0,0 +1,101 @@
+; Tests CopyProf store instrumentation.
+;
+; RUN: opt < %s -passes='function(copyprof-stores)' -S | FileCheck %s
+
+target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
+target triple = "x86_64-unknown-linux-gnu"
+
+;; Tests that a simple i32 store is instrumented.
+define void @test_store_i32(ptr %a) {
+entry:
+ store i32 42, ptr %a, align 4
+ ret void
+}
+; CHECK-LABEL: define void @test_store_i32(ptr %a)
+; CHECK: call void @__copyprof_store_callback(ptr %a, i64 4)
+; CHECK-NEXT: store i32 42, ptr %a
+
+;; Tests that an i64 store is instrumented with the correct size.
+define void @test_store_i64(ptr %a) {
+entry:
+ store i64 100, ptr %a, align 8
+ ret void
+}
+; CHECK-LABEL: define void @test_store_i64(ptr %a)
+; CHECK: call void @__copyprof_store_callback(ptr %a, i64 8)
+; CHECK-NEXT: store i64 100, ptr %a
+
+;; Tests that multiple stores in the same function are all instrumented.
+define void @test_multiple_stores(ptr %a, ptr %b) {
+entry:
+ store i32 1, ptr %a, align 4
+ store i32 2, ptr %b, align 4
+ ret void
+}
+; CHECK-LABEL: define void @test_multiple_stores(ptr %a, ptr %b)
+; CHECK: call void @__copyprof_store_callback(ptr %a, i64 4)
+; CHECK-NEXT: store i32 1, ptr %a
+; CHECK: call void @__copyprof_store_callback(ptr %b, i64 4)
+; CHECK-NEXT: store i32 2, ptr %b
+
+;; Tests that a function with no stores is not modified.
+define i32 @test_no_stores(ptr %a) {
+entry:
+ %val = load i32, ptr %a
+ ret i32 %val
+}
+; CHECK-LABEL: define i32 @test_no_stores(ptr %a)
+; CHECK-NOT: call void @__copyprof_store_callback
+; CHECK: ret i32
+
+;; Tests that stores to non-default address spaces are not instrumented.
+define void @test_addrspace_store(ptr addrspace(1) %a) {
+entry:
+ store i32 42, ptr addrspace(1) %a, align 4
+ ret void
+}
+; CHECK-LABEL: define void @test_addrspace_store(ptr addrspace(1) %a)
+; CHECK-NOT: call void @__copyprof_store_callback
+; CHECK: store i32 42, ptr addrspace(1) %a
+; CHECK-NEXT: ret void
+
+;; Tests that a vector store is instrumented with the correct aggregate size.
+define void @test_store_vector(ptr %a) {
+entry:
+ store <4 x i32> zeroinitializer, ptr %a, align 16
+ ret void
+}
+; CHECK-LABEL: define void @test_store_vector(ptr %a)
+; CHECK: call void @__copyprof_store_callback(ptr %a, i64 16)
+; CHECK-NEXT: store <4 x i32> zeroinitializer, ptr %a
+
+;; Tests that an aggregate store is instrumented with the correct size.
+define void @test_store_struct(ptr %a) {
+entry:
+ store <{ i32, i16, i8 }> zeroinitializer, ptr %a
+ ret void
+}
+; CHECK-LABEL: define void @test_store_struct(ptr %a)
+; CHECK: call void @__copyprof_store_callback(ptr %a, i64 7)
+; CHECK-NEXT: store <{ i32, i16, i8 }> zeroinitializer, ptr %a
+
+;; Tests that volatile stores are instrumented.
+define void @test_store_volatile(ptr %a) {
+entry:
+ store volatile i32 42, ptr %a, align 4
+ ret void
+}
+; CHECK-LABEL: define void @test_store_volatile(ptr %a)
+; CHECK: call void @__copyprof_store_callback(ptr %a, i64 4)
+; CHECK-NEXT: store volatile i32 42, ptr %a
+
+;; Tests that atomic stores are instrumented.
+define void @test_store_atomic(ptr %a) {
+entry:
+ store atomic i32 42, ptr %a monotonic, align 4
+ ret void
+}
+; CHECK-LABEL: define void @test_store_atomic(ptr %a)
+; CHECK: call void @__copyprof_store_callback(ptr %a, i64 4)
+; CHECK-NEXT: store atomic i32 42, ptr %a monotonic
+
>From 1ca49ae8119971239393f5260237f10ee4318305 Mon Sep 17 00:00:00 2001
From: Jan Newger <jannewger at gmail.com>
Date: Fri, 3 Jul 2026 16:52:32 +0000
Subject: [PATCH 2/5] fixup! [CopyProf] Add CopyProf instrumentation passes.
---
llvm/include/llvm/Transforms/Instrumentation/CopyProf.h | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h b/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
index 73c49024c52f5..5755b4ccd693b 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
@@ -15,6 +15,7 @@
#define LLVM_TRANSFORMS_INSTRUMENTATION_COPYPROF_H
#include "llvm/IR/PassManager.h"
+
namespace llvm {
// Early-stage pass that instruments special member functions to call into the
@@ -22,7 +23,7 @@ namespace llvm {
class CopyProfPass : public PassInfoMixin<CopyProfPass> {
public:
CopyProfPass() = default;
- PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+ LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
static bool isRequired() { return true; }
};
@@ -32,7 +33,7 @@ class CopyProfPass : public PassInfoMixin<CopyProfPass> {
class ModuleCopyProfPass : public PassInfoMixin<ModuleCopyProfPass> {
public:
ModuleCopyProfPass() = default;
- PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+ LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
static bool isRequired() { return true; }
};
@@ -42,7 +43,7 @@ class ModuleCopyProfPass : public PassInfoMixin<ModuleCopyProfPass> {
class CopyProfStoresPass : public PassInfoMixin<CopyProfStoresPass> {
public:
CopyProfStoresPass() = default;
- PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+ LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
static bool isRequired() { return true; }
};
>From 305b631ca8d028fda3cc400152851a6db545a422 Mon Sep 17 00:00:00 2001
From: Jan Newger <jannewger at gmail.com>
Date: Sun, 5 Jul 2026 22:18:47 +0200
Subject: [PATCH 3/5] fixup! [CopyProf] Add CopyProf instrumentation passes.
Skip adding callbacks in musttail functions, skip naked functions,
more robust module-ctor exclusion, skip scalable-vector store instrumentation,
avoid adding CopyProf declarations to each module even if no instrumentation
emitted.
---
.../Transforms/Instrumentation/CopyProf.cpp | 57 ++++++++++++++-----
.../CopyProf/function-instrumentation.ll | 18 +++++-
.../CopyProf/no-instrumentation.ll | 14 +++++
.../CopyProf/store-instrumentation.ll | 12 ++++
4 files changed, 85 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Transforms/Instrumentation/CopyProf.cpp b/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
index 6efc318b20195..553d3aef7c8ec 100644
--- a/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
+++ b/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
@@ -82,12 +82,43 @@ static bool insertModuleCtor(Module &M) {
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) {
@@ -170,13 +201,6 @@ CopyProf::CopyProf(Module &M) {
}
bool CopyProf::instrumentFunction(Function &F) {
- // Must not instrument our own module c'tor or functions that are explicitly
- // disallowed for instrumentation.
- if (F.isDeclaration() ||
- F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation) ||
- F.getName() == CopyProfModuleCtorName)
- return false;
-
bool Modified = true;
if (F.hasFnAttribute(CopyProfCtorAttr))
insertCallback(F, getAttrValueAsInt(F, CopyProfCtorAttr), /*NumArgs=*/1,
@@ -242,28 +266,27 @@ CopyProfStores::CopyProfStores(Module &M) {
}
bool CopyProfStores::instrumentFunction(Function &F) {
- if (F.isDeclaration() ||
- F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation) ||
- F.getName() == CopyProfModuleCtorName)
- return false;
-
// 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 &&
- !SI->hasMetadata(LLVMContext::MD_nosanitize))
+ !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;
- const DataLayout &DL = F.getParent()->getDataLayout();
for (StoreInst *SI : ToInstrument) {
- uint64_t StoredSize = DL.getTypeStoreSize(SI->getValueOperand()->getType());
+ uint64_t StoredSize =
+ DL.getTypeStoreSize(SI->getValueOperand()->getType()).getFixedValue();
InstrumentationIRBuilder IRB(SI);
std::array<Value *, 2> Args = {SI->getPointerOperand(),
ConstantInt::get(IntPtrTy, StoredSize)};
@@ -273,6 +296,8 @@ bool CopyProfStores::instrumentFunction(Function &F) {
}
PreservedAnalyses CopyProfPass::run(Function &F, FunctionAnalysisManager &) {
+ if (!isCopyProfCandidate(F))
+ return PreservedAnalyses::all();
CopyProf Impl(*F.getParent());
if (!Impl.instrumentFunction(F))
return PreservedAnalyses::all();
@@ -288,6 +313,8 @@ PreservedAnalyses ModuleCopyProfPass::run(Module &M, ModuleAnalysisManager &) {
PreservedAnalyses CopyProfStoresPass::run(Function &F,
FunctionAnalysisManager &) {
+ if (!isCopyProfStoresCandidate(F))
+ return PreservedAnalyses::all();
CopyProfStores Impl(*F.getParent());
if (!Impl.instrumentFunction(F))
return PreservedAnalyses::all();
diff --git a/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll b/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
index dd69dd7914250..d1a8b667c123a 100644
--- a/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
+++ b/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
@@ -106,6 +106,20 @@ declare i32 @__gxx_personality_v0(...)
; CHECK: call void @__copyprof_ctor_exit_callback(ptr %this, i64 8)
; CHECK-NEXT: resume
+;; Tests that a function containing a musttail call is skipped entirely.
+declare void @tail_callee(ptr)
+
+define void @ctor_with_musttail(ptr %this) "copyprof-ctor"="8" {
+entry:
+ musttail call void @tail_callee(ptr %this)
+ ret void
+}
+; CHECK-LABEL: define void @ctor_with_musttail(ptr %this)
+; CHECK-NEXT: entry:
+; CHECK-NEXT: musttail call void @tail_callee(ptr %this)
+; CHECK-NEXT: ret void
+; CHECK-NOT: call void @__copyprof_
+
;; Tests that entry callbacks are inserted after alloca instructions.
define void @ctor_with_alloca(ptr %this) "copyprof-ctor"="8" {
entry:
@@ -130,6 +144,8 @@ entry:
; CHECK-NOT: call void @__copyprof_ctor_exit_callback
; CHECK: unreachable
-;; Verifies that the module constructor calls the init function.
+;; Verifies that the module constructor calls the init function and is marked so
+;; that it's never instrumented itself.
; CHECK: define internal void @copyprof.module_ctor()
; CHECK: call void @__copyprof_init()
+; CHECK: disable_sanitizer_instrumentation
diff --git a/llvm/test/Instrumentation/CopyProf/no-instrumentation.ll b/llvm/test/Instrumentation/CopyProf/no-instrumentation.ll
index d56f990547f8f..b7950771de503 100644
--- a/llvm/test/Instrumentation/CopyProf/no-instrumentation.ll
+++ b/llvm/test/Instrumentation/CopyProf/no-instrumentation.ll
@@ -53,6 +53,20 @@ entry:
; STORES: store i32 42, ptr %a
; STORES-NEXT: ret void
+;; Tests that instrumentation is skipped for naked functions.
+define void @naked_ctor_with_store(ptr %this) "copyprof-ctor"="8" naked {
+entry:
+ store i32 0, ptr null
+ ret void
+}
+; CHECK-LABEL: define void @naked_ctor_with_store(ptr %this)
+; CHECK-NOT: call void @__copyprof_
+; CHECK: ret void
+; STORES-LABEL: define void @naked_ctor_with_store(ptr %this)
+; STORES-NOT: call void @__copyprof_store_callback
+; STORES: store i32 0, ptr null
+; STORES-NEXT: ret void
+
;; Tests that the module constructor itself is not instrumented by either pass.
define internal void @copyprof.module_ctor() {
entry:
diff --git a/llvm/test/Instrumentation/CopyProf/store-instrumentation.ll b/llvm/test/Instrumentation/CopyProf/store-instrumentation.ll
index 0370a26073292..f5a7ce414f850 100644
--- a/llvm/test/Instrumentation/CopyProf/store-instrumentation.ll
+++ b/llvm/test/Instrumentation/CopyProf/store-instrumentation.ll
@@ -59,6 +59,18 @@ entry:
; CHECK: store i32 42, ptr addrspace(1) %a
; CHECK-NEXT: ret void
+;; Tests that scalable vector stores are skipped as they have no
+;; compile-time-constant store size.
+define void @test_store_scalable_vector(ptr %a) {
+entry:
+ store <vscale x 4 x i32> zeroinitializer, ptr %a, align 16
+ ret void
+}
+; CHECK-LABEL: define void @test_store_scalable_vector(ptr %a)
+; CHECK-NOT: call void @__copyprof_store_callback
+; CHECK: store <vscale x 4 x i32> zeroinitializer, ptr %a
+; CHECK-NEXT: ret void
+
;; Tests that a vector store is instrumented with the correct aggregate size.
define void @test_store_vector(ptr %a) {
entry:
>From 6f0696f68ce352ba4b16c6a275e2a2d4bcf3fefe Mon Sep 17 00:00:00 2001
From: Jan Newger <jannewger at gmail.com>
Date: Fri, 24 Jul 2026 12:45:20 +0000
Subject: [PATCH 4/5] fixup! fixup! [CopyProf] Add CopyProf instrumentation
passes.
---
.../Transforms/Instrumentation/CopyProf.cpp | 45 ++++++++++---------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/llvm/lib/Transforms/Instrumentation/CopyProf.cpp b/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
index 553d3aef7c8ec..61eed4cdaeeec 100644
--- a/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
+++ b/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
@@ -32,8 +32,6 @@
#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>
@@ -98,19 +96,25 @@ static bool isCopyProfCandidate(const Function &F) {
F.hasFnAttribute(Attribute::Naked))
return false;
- // Don't instrument a function at all if it's ending with a tail call.
+ 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 flase positive reports).
- // Skipping this function favors false negatives over false positives.
+ // 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 F.hasFnAttribute(CopyProfCtorAttr) ||
- F.hasFnAttribute(CopyProfCopyCtorAttr) ||
- F.hasFnAttribute(CopyProfCopyAssignAttr) ||
- F.hasFnAttribute(CopyProfDtorAttr);
+ return true;
}
static bool isCopyProfStoresCandidate(const Function &F) {
@@ -123,13 +127,11 @@ static bool isCopyProfStoresCandidate(const Function &F) {
// 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()));
- }
+ [[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;
}
@@ -145,7 +147,6 @@ class CopyProf {
void insertCallback(Function &F, size_t ObjSize, unsigned NumArgs,
FunctionCallee Callback, FunctionCallee ExitCallback);
- LLVMContext *Ctx;
Type *IntPtrTy;
FunctionCallee CtorEnterCallback;
FunctionCallee CtorExitCallback;
@@ -172,14 +173,14 @@ class CopyProfStores {
} // namespace
CopyProf::CopyProf(Module &M) {
- Ctx = &M.getContext();
- IRBuilder<> IRB(*Ctx);
+ 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);
+ Attr = Attr.addFnAttribute(Ctx, Attribute::NoUnwind);
CtorEnterCallback = M.getOrInsertFunction(CopyProfCtorEnterCallbackName, Attr,
VoidTy, PtrTy, IntPtrTy);
CtorExitCallback = M.getOrInsertFunction(CopyProfCtorExitCallbackName, Attr,
@@ -266,8 +267,10 @@ CopyProfStores::CopyProfStores(Module &M) {
}
bool CopyProfStores::instrumentFunction(Function &F) {
- // TODO: handle all types of memory stores (memory intrinsics, masked store
+ // 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) {
>From f630810d53b1fffd4796ee8286a94d62d8afe9f1 Mon Sep 17 00:00:00 2001
From: Jan Newger <jannewger at gmail.com>
Date: Fri, 24 Jul 2026 16:37:23 +0000
Subject: [PATCH 5/5] fixup! fixup! fixup! [CopyProf] Add CopyProf
instrumentation passes.
---
.../CopyProf/function-instrumentation.ll | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll b/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
index d1a8b667c123a..96854ecf18022 100644
--- a/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
+++ b/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
@@ -120,6 +120,20 @@ entry:
; CHECK-NEXT: ret void
; CHECK-NOT: call void @__copyprof_
+;; Tests that a function containing an ordinary tail call is instrumented normally,
+;; with the exit callback inserted between the tail call and return instructions.
+define void @ctor_with_tail(ptr %this) "copyprof-ctor"="8" {
+entry:
+ tail call void @tail_callee(ptr %this)
+ ret void
+}
+; CHECK-LABEL: define void @ctor_with_tail(ptr %this)
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @__copyprof_ctor_enter_callback(ptr %this, i64 8)
+; CHECK-NEXT: tail call void @tail_callee(ptr %this)
+; CHECK-NEXT: call void @__copyprof_ctor_exit_callback(ptr %this, i64 8)
+; CHECK-NEXT: ret void
+
;; Tests that entry callbacks are inserted after alloca instructions.
define void @ctor_with_alloca(ptr %this) "copyprof-ctor"="8" {
entry:
More information about the llvm-commits
mailing list