[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