[llvm] 2f910f6 - [CopyProf] Add CopyProf instrumentation passes. (#207385)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 06:11:24 PDT 2026
Author: newgre
Date: 2026-07-29T06:11:19-07:00
New Revision: 2f910f66e7e181c8c1b6ed97d44e6f4bceb6c557
URL: https://github.com/llvm/llvm-project/commit/2f910f66e7e181c8c1b6ed97d44e6f4bceb6c557
DIFF: https://github.com/llvm/llvm-project/commit/2f910f66e7e181c8c1b6ed97d44e6f4bceb6c557.diff
LOG: [CopyProf] Add CopyProf instrumentation passes. (#207385)
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).
Added:
llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
llvm/lib/Transforms/Instrumentation/CopyProf.cpp
llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
llvm/test/Instrumentation/CopyProf/no-instrumentation.ll
llvm/test/Instrumentation/CopyProf/store-instrumentation.ll
Modified:
llvm/lib/Passes/PassBuilder.cpp
llvm/lib/Passes/PassRegistry.def
llvm/lib/Transforms/Instrumentation/CMakeLists.txt
Removed:
################################################################################
diff --git a/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h b/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
new file mode 100644
index 0000000000000..5755b4ccd693b
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Instrumentation/CopyProf.h
@@ -0,0 +1,53 @@
+//===-- 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;
+ LLVM_ABI 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;
+ LLVM_ABI 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;
+ LLVM_ABI 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..61eed4cdaeeec
--- /dev/null
+++ b/llvm/lib/Transforms/Instrumentation/CopyProf.cpp
@@ -0,0 +1,327 @@
+//===-- 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;
+}
diff --git a/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll b/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
new file mode 100644
index 0000000000000..96854ecf18022
--- /dev/null
+++ b/llvm/test/Instrumentation/CopyProf/function-instrumentation.ll
@@ -0,0 +1,165 @@
+; 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 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 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:
+ %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 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
new file mode 100644
index 0000000000000..b7950771de503
--- /dev/null
+++ b/llvm/test/Instrumentation/CopyProf/no-instrumentation.ll
@@ -0,0 +1,80 @@
+; 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 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:
+ 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..f5a7ce414f850
--- /dev/null
+++ b/llvm/test/Instrumentation/CopyProf/store-instrumentation.ll
@@ -0,0 +1,113 @@
+; 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 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:
+ 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
+
More information about the llvm-commits
mailing list