https://github.com/newgre updated 
https://github.com/llvm/llvm-project/pull/225905

>From 606319129e6cd63ac46fa3a4c24caee81ad23212 Mon Sep 17 00:00:00 2001
From: Jan Newger <[email protected]>
Date: Wed, 24 Jun 2026 10:36:24 +0000
Subject: [PATCH 1/2] [CopyProf] Clang frontend and driver integration.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This wires the existing passes and runtime up to the Clang driver so CopyProf
can actually be enabled from the command line.

Adds two flags:
- `-fcopyprof` — to enable CopyProf.
- `-fcopyprof-static-size-threshold=<N>` — minimum C++ object size in bytes
  to enable instrumentation.

RFC: 
https://discourse.llvm.org/t/rfc-copysanitizer-csan-detecting-unneccessary-object-copies-at-runtime/91038
Previous PR: #223458
---
 clang/include/clang/Basic/CodeGenOptions.def  |  2 +
 clang/include/clang/Driver/SanitizerArgs.h    |  2 +
 clang/include/clang/Options/Options.td        | 12 +++
 clang/lib/CodeGen/BackendUtil.cpp             | 18 +++++
 clang/lib/CodeGen/CodeGenFunction.cpp         | 32 ++++++++
 clang/lib/Driver/SanitizerArgs.cpp            |  3 +
 clang/lib/Driver/ToolChains/Clang.cpp         |  8 ++
 clang/lib/Driver/ToolChains/CommonArgs.cpp    |  2 +
 clang/runtime/CMakeLists.txt                  |  2 +-
 clang/test/CodeGen/copyprof.cpp               | 34 ++++++++
 .../CodeGen/no-skipped-passes-O0-opt-bisect.c |  2 +
 clang/test/CodeGenCXX/copyprof-attrs-move.cpp | 37 +++++++++
 clang/test/CodeGenCXX/copyprof-attrs.cpp      | 80 +++++++++++++++++++
 clang/test/Driver/fcopyprof.cpp               | 34 ++++++++
 14 files changed, 267 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CodeGen/copyprof.cpp
 create mode 100644 clang/test/CodeGenCXX/copyprof-attrs-move.cpp
 create mode 100644 clang/test/CodeGenCXX/copyprof-attrs.cpp
 create mode 100644 clang/test/Driver/fcopyprof.cpp

diff --git a/clang/include/clang/Basic/CodeGenOptions.def 
b/clang/include/clang/Basic/CodeGenOptions.def
index dd9590d406d5e1..10d9a4e3a6a82c 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -298,6 +298,8 @@ CODEGENOPT(SanitizeCfiICallNormalizeIntegers, 1, 0, Benign) 
///< Normalize integ
 CODEGENOPT(SanitizeCfiCanonicalJumpTables, 1, 0, Benign) ///< Make jump table 
symbols canonical
                                                          ///< instead of 
creating a local jump table.
 CODEGENOPT(SanitizeKcfiArity, 1, 0, Benign) ///< Embed arity in KCFI patchable 
function prefix
+CODEGENOPT(CopyProf, 1, 0, Benign) ///< Enable CopyProf instrumentation
+VALUE_CODEGENOPT(CopyProfStaticSizeThreshold, 32, 16, Benign) ///< Set 
threshold for CopyProf instrumentation
 CODEGENOPT(SanitizeCoverageType, 2, 0, Benign) ///< Type of sanitizer coverage
                                                ///< instrumentation.
 CODEGENOPT(SanitizeCoverageIndirectCalls, 1, 0, Benign) ///< Enable sanitizer 
coverage
diff --git a/clang/include/clang/Driver/SanitizerArgs.h 
b/clang/include/clang/Driver/SanitizerArgs.h
index 62f154bae3ffd3..d7823d71b58601 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -78,6 +78,7 @@ class SanitizerArgs {
   // True if cross-dso CFI support if provided by the system (i.e. Android).
   bool ImplicitCfiRuntime = false;
   bool NeedsMemProfRt = false;
+  bool NeedsCopyProfRt = false;
   bool HwasanUseAliases = false;
   llvm::AsanDetectStackUseAfterReturnMode AsanUseAfterReturn =
       llvm::AsanDetectStackUseAfterReturnMode::Invalid;
@@ -113,6 +114,7 @@ class SanitizerArgs {
            !Sanitizers.has(SanitizerKind::Address) &&
            !Sanitizers.has(SanitizerKind::HWAddress);
   }
+  bool needsCopyProfRt() const { return NeedsCopyProfRt; }
   bool needsFuzzerInterceptors() const;
   bool needsUbsanRt() const;
   bool needsUbsanCXXRt() const;
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 15b2196b68e76e..1bf7092e37d3d1 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2662,6 +2662,18 @@ def fmemory_profile_use_EQ : Joined<["-"], 
"fmemory-profile-use=">,
     HelpText<"Use memory profile for profile-guided memory optimization">,
     MarshallingInfoString<CodeGenOpts<"MemoryProfileUsePath">>;
 
+defm copyprof : BoolFOption<"copyprof",
+  CodeGenOpts<"CopyProf">, DefaultFalse,
+  PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
+  NegFlag<SetFalse, [], [ClangOption], "Disable">,
+  BothFlags<[], [ClangOption], " copy profiling">>;
+def fcopyprof_static_size_threshold_EQ
+    : Joined<["-"], "fcopyprof-static-size-threshold=">,
+      Group<f_Group>, Visibility<[ClangOption, CC1Option]>,
+      MetaVarName<"<N>">,
+      HelpText<"Minimum C++ object size to enable CopyProf instrumentation 
(default: 16).">,
+      MarshallingInfoInt<CodeGenOpts<"CopyProfStaticSizeThreshold">, "16">;
+
 // Begin sanitizer flags. These should all be core options exposed in all 
driver
 // modes.
 let Visibility = [ClangOption, CC1Option, CLOption] in {
diff --git a/clang/lib/CodeGen/BackendUtil.cpp 
b/clang/lib/CodeGen/BackendUtil.cpp
index 8103b72cf4e3a2..3a02922d592beb 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -74,6 +74,7 @@
 #include "llvm/Transforms/Instrumentation/AddressSanitizer.h"
 #include "llvm/Transforms/Instrumentation/AddressSanitizerOptions.h"
 #include "llvm/Transforms/Instrumentation/BoundsChecking.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"
@@ -1059,6 +1060,23 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
               MPM.addPass(InferFunctionAttrsPass());
             }
           });
+
+      if (CodeGenOpts.CopyProf) {
+        // Early pass: insert callbacks into special member functions before 
the
+        // inliner removes function boundaries.
+        PB.registerPipelineEarlySimplificationEPCallback(
+            [](ModulePassManager &MPM, OptimizationLevel, ThinOrFullLTOPhase) {
+              MPM.addPass(createModuleToFunctionPassAdaptor(CopyProfPass()));
+              MPM.addPass(ModuleCopyProfPass());
+            });
+        // Late pass: to reduce runtime overhead, instrument stores only after
+        // optimizations have been run so only useful stores are instrumented.
+        PB.registerOptimizerLastEPCallback([](ModulePassManager &MPM,
+                                              OptimizationLevel,
+                                              ThinOrFullLTOPhase) {
+          MPM.addPass(createModuleToFunctionPassAdaptor(CopyProfStoresPass()));
+        });
+      }
     }
 
     if (std::optional<GCOVOptions> Options =
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp 
b/clang/lib/CodeGen/CodeGenFunction.cpp
index 745423945ca838..effd7283854205 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -40,6 +40,7 @@
 #include "clang/CodeGenUtils/FunctionUtils.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Dominators.h"
@@ -1053,6 +1054,37 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, 
QualType RetTy,
     }
   }
 
+  // Annotate C++ special member functions so that CopyProfPass can instrument
+  // them, provided the object is at least as large as the size threshold.
+  if (CGM.getCodeGenOpts().CopyProf) {
+    const auto *MD = dyn_cast_or_null<CXXMethodDecl>(D);
+    const auto *CD = MD ? dyn_cast<CXXConstructorDecl>(MD) : nullptr;
+    // It's either some sort of c'tor (but not any move function), or either of
+    // copy assignment operator / d'tor.
+    bool IsCandidate =
+        CD ? !(CD->isMoveConstructor() || CD->isMoveAssignmentOperator())
+           : MD &&
+                 (isa<CXXDestructorDecl>(MD) || 
MD->isCopyAssignmentOperator());
+    if (IsCandidate) {
+      // A special member function always has an implicit object parameter, so
+      // its type is guaranteed to be complete here.
+      CharUnits ObjSize =
+          
getContext().getTypeSizeInChars(MD->getFunctionObjectParameterType());
+      if (ObjSize.getQuantity() >=
+          CGM.getCodeGenOpts().CopyProfStaticSizeThreshold) {
+        std::string ObjSizeStr = llvm::utostr(ObjSize.getQuantity());
+        if (CD)
+          Fn->addFnAttr(CD->isCopyConstructor() ? "copyprof-copy-ctor"
+                                                : "copyprof-ctor",
+                        ObjSizeStr);
+        else if (isa<CXXDestructorDecl>(MD))
+          Fn->addFnAttr("copyprof-dtor", ObjSizeStr);
+        else
+          Fn->addFnAttr("copyprof-copy-assign-op", ObjSizeStr);
+      }
+    }
+  }
+
   // If we're in C++ mode and the function name is "main", it is guaranteed
   // to be norecurse by the standard (3.6.1.3 "The function main shall not be
   // used within a program").
diff --git a/clang/lib/Driver/SanitizerArgs.cpp 
b/clang/lib/Driver/SanitizerArgs.cpp
index 778cde8285aaf1..438283c60c0ba2 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -1345,6 +1345,9 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
                                 options::OPT_fmemory_profile_EQ,
                                 options::OPT_fno_memory_profile, false);
 
+  NeedsCopyProfRt =
+      Args.hasFlag(options::OPT_fcopyprof, options::OPT_fno_copyprof, false);
+
   // Finally, initialize the set of available and recoverable sanitizers.
   Sanitizers.Mask |= Kinds;
   RecoverableSanitizers.Mask |= RecoverableKinds;
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 6636a5fd6e6551..1b13be2aca3b9f 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -5645,6 +5645,14 @@ void Clang::ConstructJob(Compilation &C, const JobAction 
&JA,
     MemProfUseArg->render(Args, CmdArgs);
   }
 
+  auto *CopyProfArg =
+      Args.getLastArg(options::OPT_fcopyprof, options::OPT_fno_copyprof);
+  if (CopyProfArg &&
+      !CopyProfArg->getOption().matches(options::OPT_fno_copyprof)) {
+    CopyProfArg->render(Args, CmdArgs);
+    Args.AddLastArg(CmdArgs, options::OPT_fcopyprof_static_size_threshold_EQ);
+  }
+
   // Embed-bitcode option.
   // Only white-listed flags below are allowed to be embedded.
   if (C.getDriver().embedBitcodeInObject() && !IsUsingLTO &&
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp 
b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 445eb4ccfbfa72..a2d508b34c5fc0 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1792,6 +1792,8 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain 
&TC,
     StaticRuntimes.push_back("dfsan");
   if (SanArgs.needsLsanRt())
     StaticRuntimes.push_back("lsan");
+  if (SanArgs.needsCopyProfRt())
+    StaticRuntimes.push_back("copyprof");
   if (SanArgs.needsMsanRt()) {
     StaticRuntimes.push_back("msan");
     if (SanArgs.linkCXXRuntimes())
diff --git a/clang/runtime/CMakeLists.txt b/clang/runtime/CMakeLists.txt
index ff2605b23d25b0..f543bba2f27f30 100644
--- a/clang/runtime/CMakeLists.txt
+++ b/clang/runtime/CMakeLists.txt
@@ -122,7 +122,7 @@ if(LLVM_BUILD_EXTERNAL_COMPILER_RT AND EXISTS 
${COMPILER_RT_SRC_ROOT}/)
                            COMPONENT compiler-rt)
 
   # Add top-level targets that build specific compiler-rt runtimes.
-  set(COMPILER_RT_RUNTIMES fuzzer asan builtins dfsan lsan msan profile tsan 
tysan ubsan ubsan-minimal)
+  set(COMPILER_RT_RUNTIMES fuzzer asan builtins copyprof dfsan lsan msan 
profile tsan tysan ubsan ubsan-minimal)
   foreach(runtime ${COMPILER_RT_RUNTIMES})
     get_ext_project_build_command(build_runtime_cmd ${runtime})
     add_custom_target(${runtime}
diff --git a/clang/test/CodeGen/copyprof.cpp b/clang/test/CodeGen/copyprof.cpp
new file mode 100644
index 00000000000000..3c06b14e6df772
--- /dev/null
+++ b/clang/test/CodeGen/copyprof.cpp
@@ -0,0 +1,34 @@
+// Test that CopyProf instrumentation passes are invoked at -O0 and -O2,
+// and are not re-run during ThinLTO postlink backend compilation.
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -fcopyprof %s 
-fdebug-pass-manager -emit-llvm -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=INSTRUMENT
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fcopyprof %s 
-fdebug-pass-manager -emit-llvm -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=INSTRUMENT
+
+// INSTRUMENT: Running pass: CopyProfPass on
+// INSTRUMENT: Running pass: ModuleCopyProfPass on [module]
+// INSTRUMENT: Running pass: CopyProfStoresPass on
+
+// Test ThinLTO prelink vs postlink:
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fcopyprof -flto=thin 
-emit-llvm-bc %s -o %t.bc
+// RUN: llvm-lto -thinlto -o %t %t.bc
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fcopyprof 
-fthinlto-index=%t.thinlto.bc -fdebug-pass-manager -emit-obj -x ir %t.bc -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=POSTLINK
+
+// POSTLINK-NOT: Running pass: CopyProfPass
+// POSTLINK-NOT: Running pass: ModuleCopyProfPass
+// POSTLINK-NOT: Running pass: CopyProfStoresPass
+
+struct Foo {
+  long a[2];
+  Foo() : a{0, 0} {}
+  Foo(const Foo &other) {
+    a[0] = other.a[0];
+    a[1] = other.a[1];
+  }
+  ~Foo() {}
+};
+
+int main() {
+  Foo f1;
+  Foo f2(f1);
+  return 0;
+}
diff --git a/clang/test/CodeGen/no-skipped-passes-O0-opt-bisect.c 
b/clang/test/CodeGen/no-skipped-passes-O0-opt-bisect.c
index 6d3e6359d37c77..5ed2ec377c96c0 100644
--- a/clang/test/CodeGen/no-skipped-passes-O0-opt-bisect.c
+++ b/clang/test/CodeGen/no-skipped-passes-O0-opt-bisect.c
@@ -11,6 +11,7 @@
 // RUN: %clang_cc1 -triple x86_64-linux-gnu -O0 %s -fdebug-pass-manager 
-emit-llvm -o /dev/null -fsanitize-coverage-trace-pc-guard 2>&1 | FileCheck %s
 // RUN: %clang_cc1 -triple x86_64-linux-gnu -O0 %s -fdebug-pass-manager 
-emit-llvm -o /dev/null -fsanitize=numerical 2>&1 | FileCheck %s
 // RUN: %clang_cc1 -triple x86_64-linux-gnu -O0 %s -fdebug-pass-manager 
-emit-llvm -o /dev/null -fmemory-profile 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -O0 %s -fdebug-pass-manager 
-emit-llvm -o /dev/null -fcopyprof 2>&1 | FileCheck %s
 
 // RUN: %clang_cc1 -triple x86_64-linux-gnu -mllvm -opt-bisect-limit=0 %s 
-fdebug-pass-manager -emit-llvm -o /dev/null 2>&1 | FileCheck %s
 // RUN: %clang_cc1 -triple x86_64-linux-gnu -mllvm -opt-bisect-limit=0 %s 
-fdebug-pass-manager -emit-llvm -o /dev/null -fcoroutines 2>&1 | FileCheck %s
@@ -22,6 +23,7 @@
 // RUN: %clang_cc1 -triple x86_64-linux-gnu -mllvm -opt-bisect-limit=0 %s 
-fdebug-pass-manager -emit-llvm -o /dev/null -fsanitize=dataflow 2>&1 | 
FileCheck %s
 // RUN: %clang_cc1 -triple x86_64-linux-gnu -mllvm -opt-bisect-limit=0 %s 
-fdebug-pass-manager -emit-llvm -o /dev/null -fsanitize-coverage-trace-pc-guard 
2>&1 | FileCheck %s
 // RUN: %clang_cc1 -triple x86_64-linux-gnu -mllvm -opt-bisect-limit=0 %s 
-fdebug-pass-manager -emit-llvm -o /dev/null -fmemory-profile 2>&1 | FileCheck 
%s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -mllvm -opt-bisect-limit=0 %s 
-fdebug-pass-manager -emit-llvm -o /dev/null -fcopyprof 2>&1 | FileCheck %s
 
 // CHECK-NOT: Skipping pass
 
diff --git a/clang/test/CodeGenCXX/copyprof-attrs-move.cpp 
b/clang/test/CodeGenCXX/copyprof-attrs-move.cpp
new file mode 100644
index 00000000000000..998a4f32ca59f5
--- /dev/null
+++ b/clang/test/CodeGenCXX/copyprof-attrs-move.cpp
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm 
-disable-llvm-passes -fcopyprof -fcopyprof-static-size-threshold=32 %s -o - | 
FileCheck %s
+
+// Assert that move constructors and move assignment operators are never 
annotated by CopyProf.
+
+struct S {
+  long a[4];
+  S();
+  S(const S &);
+  S(S &&);
+  S &operator=(const S &);
+  S &operator=(S &&);
+  ~S();
+};
+static_assert(sizeof(S) == 32);
+
+S::S() {}
+S::S(const S &) {}
+S::S(S &&) {}
+S &S::operator=(const S &) { return *this; }
+S &S::operator=(S &&) { return *this; }
+S::~S() {}
+
+// CHECK: define {{.*}} @_ZN1SC1Ev({{.*}}#[[CTOR:[0-9]+]]
+// CHECK: define {{.*}} @_ZN1SC1ERKS_({{.*}}#[[COPY_CTOR:[0-9]+]]
+// CHECK: define {{.*}} @_ZN1SC1EOS_({{.*}}#[[MOVE:[0-9]+]]
+// CHECK: define {{.*}} @_ZN1SaSERKS_({{.*}}#[[COPY_ASSIGN:[0-9]+]]
+// CHECK: define {{.*}} @_ZN1SaSEOS_({{.*}}#[[MOVE]]
+// CHECK: define {{.*}} @_ZN1SD1Ev({{.*}}#[[DTOR:[0-9]+]]
+
+// CHECK: attributes #[[CTOR]] = {{{.*}}"copyprof-ctor"="32"{{.*}}}
+// CHECK: attributes #[[COPY_CTOR]] = {{{.*}}"copyprof-copy-ctor"="32"{{.*}}}
+// The move constructor and the move assignment operator share this attribute
+// group, and it must not carry any CopyProf annotation.
+// CHECK: attributes #[[MOVE]] =
+// CHECK-NOT: "copyprof-
+// CHECK: attributes #[[COPY_ASSIGN]] = 
{{{.*}}"copyprof-copy-assign-op"="32"{{.*}}}
+// CHECK: attributes #[[DTOR]] = {{{.*}}"copyprof-dtor"="32"{{.*}}}
diff --git a/clang/test/CodeGenCXX/copyprof-attrs.cpp 
b/clang/test/CodeGenCXX/copyprof-attrs.cpp
new file mode 100644
index 00000000000000..b2f569036b3436
--- /dev/null
+++ b/clang/test/CodeGenCXX/copyprof-attrs.cpp
@@ -0,0 +1,80 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm 
-disable-llvm-passes -fcopyprof -fcopyprof-static-size-threshold=16 %s -o - | 
FileCheck %s --check-prefix=T16
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm 
-disable-llvm-passes -fcopyprof -fcopyprof-static-size-threshold=8 %s -o - | 
FileCheck %s --check-prefix=T8
+
+// Asserts that special member functions are annotated depending on whether 
the object size is above the static size threshold.
+
+struct Small {
+  long a;
+  Small() : a(0) {}
+  Small(const Small &other) : a(other.a) {}
+  Small &operator=(const Small &other) {
+    a = other.a;
+    return *this;
+  }
+  ~Small() {}
+};
+static_assert(sizeof(Small) == 8);
+
+struct Large {
+  long a[2];
+  Large() : a{0, 0} {}
+  Large(const Large &other) {
+    a[0] = other.a[0];
+    a[1] = other.a[1];
+  }
+  Large &operator=(const Large &other) {
+    a[0] = other.a[0];
+    a[1] = other.a[1];
+    return *this;
+  }
+  ~Large() {}
+};
+static_assert(sizeof(Large) == 16);
+
+void test() {
+  Small s1;
+  Small s2(s1);
+  s1 = s2;
+
+  Large l1;
+  Large l2(l1);
+  l1 = l2;
+}
+
+// Threshold 16: `Small` must not be annotated but `Large` must be.
+
+// T16: define {{.*}} @_ZN5SmallC1Ev({{.*}}#[[SMALL:[0-9]+]]
+// T16: define {{.*}} @_ZN5SmallC1ERKS_({{.*}}#[[SMALL]]
+// T16: define {{.*}} @_ZN5SmallaSERKS_({{.*}}#[[SMALL]]
+// T16: define {{.*}} @_ZN5LargeC1Ev({{.*}}#[[LARGE_CTOR:[0-9]+]]
+// T16: define {{.*}} @_ZN5LargeC1ERKS_({{.*}}#[[LARGE_COPY_CTOR:[0-9]+]]
+// T16: define {{.*}} @_ZN5LargeaSERKS_({{.*}}#[[LARGE_ASSIGN:[0-9]+]]
+// T16: define {{.*}} @_ZN5LargeD1Ev({{.*}}#[[LARGE_DTOR:[0-9]+]]
+// T16: define {{.*}} @_ZN5SmallD1Ev({{.*}}#[[SMALL]]
+
+// T16: attributes #[[SMALL]] =
+// T16-NOT: "copyprof-
+// T16: attributes #[[LARGE_CTOR]] = {{{.*}}"copyprof-ctor"="16"{{.*}}}
+// T16: attributes #[[LARGE_COPY_CTOR]] = 
{{{.*}}"copyprof-copy-ctor"="16"{{.*}}}
+// T16: attributes #[[LARGE_ASSIGN]] = 
{{{.*}}"copyprof-copy-assign-op"="16"{{.*}}}
+// T16: attributes #[[LARGE_DTOR]] = {{{.*}}"copyprof-dtor"="16"{{.*}}}
+
+// Threshold 8: both structs are annotated.
+
+// T8: define {{.*}} @_ZN5SmallC1Ev({{.*}}#[[SMALL_CTOR:[0-9]+]]
+// T8: define {{.*}} @_ZN5SmallC1ERKS_({{.*}}#[[SMALL_COPY_CTOR:[0-9]+]]
+// T8: define {{.*}} @_ZN5SmallaSERKS_({{.*}}#[[SMALL_ASSIGN:[0-9]+]]
+// T8: define {{.*}} @_ZN5LargeC1Ev({{.*}}#[[LARGE_CTOR8:[0-9]+]]
+// T8: define {{.*}} @_ZN5LargeC1ERKS_({{.*}}#[[LARGE_COPY_CTOR8:[0-9]+]]
+// T8: define {{.*}} @_ZN5LargeaSERKS_({{.*}}#[[LARGE_ASSIGN8:[0-9]+]]
+// T8: define {{.*}} @_ZN5LargeD1Ev({{.*}}#[[LARGE_DTOR8:[0-9]+]]
+// T8: define {{.*}} @_ZN5SmallD1Ev({{.*}}#[[SMALL_DTOR:[0-9]+]]
+
+// T8: attributes #[[SMALL_CTOR]] = {{{.*}}"copyprof-ctor"="8"{{.*}}}
+// T8: attributes #[[SMALL_COPY_CTOR]] = {{{.*}}"copyprof-copy-ctor"="8"{{.*}}}
+// T8: attributes #[[SMALL_ASSIGN]] = 
{{{.*}}"copyprof-copy-assign-op"="8"{{.*}}}
+// T8: attributes #[[LARGE_CTOR8]] = {{{.*}}"copyprof-ctor"="16"{{.*}}}
+// T8: attributes #[[LARGE_COPY_CTOR8]] = 
{{{.*}}"copyprof-copy-ctor"="16"{{.*}}}
+// T8: attributes #[[LARGE_ASSIGN8]] = 
{{{.*}}"copyprof-copy-assign-op"="16"{{.*}}}
+// T8: attributes #[[LARGE_DTOR8]] = {{{.*}}"copyprof-dtor"="16"{{.*}}}
+// T8: attributes #[[SMALL_DTOR]] = {{{.*}}"copyprof-dtor"="8"{{.*}}}
diff --git a/clang/test/Driver/fcopyprof.cpp b/clang/test/Driver/fcopyprof.cpp
new file mode 100644
index 00000000000000..2ac70d3349116e
--- /dev/null
+++ b/clang/test/Driver/fcopyprof.cpp
@@ -0,0 +1,34 @@
+// Test driver flags for CopyProf instrumentation and runtime linking.
+
+// Basic C and C++ invocations:
+// RUN: %clang --target=x86_64-linux-gnu -fcopyprof %s -### 2>&1 | FileCheck %s
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof %s -### 2>&1 | FileCheck 
%s
+// CHECK: "-cc1" {{.*}} "-fcopyprof"
+// CHECK: ld{{.*}}libclang_rt.copyprof
+
+// Re-enabling after -fno-copyprof:
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof -fno-copyprof -fcopyprof 
%s -### 2>&1 | FileCheck %s
+
+// Custom static size threshold (and last-flag-wins behavior):
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof 
-fcopyprof-static-size-threshold=32 %s -### 2>&1 | FileCheck %s 
--check-prefix=THRESHOLD32
+// THRESHOLD32: "-cc1" {{.*}} "-fcopyprof" 
"-fcopyprof-static-size-threshold=32"
+// THRESHOLD32: ld{{.*}}libclang_rt.copyprof
+
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof 
-fcopyprof-static-size-threshold=32 -fcopyprof-static-size-threshold=64 %s -### 
2>&1 | FileCheck %s --check-prefix=THRESHOLD64
+// THRESHOLD64: "-cc1" {{.*}} "-fcopyprof" 
"-fcopyprof-static-size-threshold=64"
+// THRESHOLD64-NOT: "-fcopyprof-static-size-threshold=32"
+
+// Disabling CopyProf via -fno-copyprof (ensuring threshold is also 
suppressed):
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof -fno-copyprof %s -### 
2>&1 | FileCheck %s --check-prefix=OFF
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof 
-fcopyprof-static-size-threshold=32 -fno-copyprof %s -### 2>&1 | FileCheck %s 
--check-prefix=OFF
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof-static-size-threshold=32 
%s -### 2>&1 | FileCheck %s --check-prefix=OFF
+// OFF-NOT: "-fcopyprof"
+// OFF-NOT: "-fcopyprof-static-size-threshold"
+// OFF-NOT: libclang_rt.copyprof
+
+// Shared libraries (-shared) and relocatable links (-r) compile with 
-fcopyprof
+// but must not link the static CopyProf runtime archive:
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof -shared %s -### 2>&1 | 
FileCheck %s --check-prefix=NO-STATIC-RT
+// RUN: %clangxx --target=x86_64-linux-gnu -fcopyprof -r %s -### 2>&1 | 
FileCheck %s --check-prefix=NO-STATIC-RT
+// NO-STATIC-RT: "-cc1" {{.*}} "-fcopyprof"
+// NO-STATIC-RT-NOT: libclang_rt.copyprof

>From e8cb78e0acdacad0f20a2881e92ca5366b00a793 Mon Sep 17 00:00:00 2001
From: Jan Newger <[email protected]>
Date: Fri, 25 Sep 2026 10:45:56 +0000
Subject: [PATCH 2/2] fixup! [CopyProf] Clang frontend and driver integration.

---
 clang/include/clang/Options/Options.td |  2 +-
 clang/lib/CodeGen/CodeGenFunction.cpp  | 44 ++++++++++++--------------
 clang/test/CodeGen/copyprof.cpp        |  1 +
 3 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 1bf7092e37d3d1..457c55630a3715 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2671,7 +2671,7 @@ def fcopyprof_static_size_threshold_EQ
     : Joined<["-"], "fcopyprof-static-size-threshold=">,
       Group<f_Group>, Visibility<[ClangOption, CC1Option]>,
       MetaVarName<"<N>">,
-      HelpText<"Minimum C++ object size to enable CopyProf instrumentation 
(default: 16).">,
+      HelpText<"Minimum C++ object size in bytes to enable CopyProf 
instrumentation (default: 16).">,
       MarshallingInfoInt<CodeGenOpts<"CopyProfStaticSizeThreshold">, "16">;
 
 // Begin sanitizer flags. These should all be core options exposed in all 
driver
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp 
b/clang/lib/CodeGen/CodeGenFunction.cpp
index effd7283854205..b524021b44b814 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -1057,30 +1057,26 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, 
QualType RetTy,
   // Annotate C++ special member functions so that CopyProfPass can instrument
   // them, provided the object is at least as large as the size threshold.
   if (CGM.getCodeGenOpts().CopyProf) {
-    const auto *MD = dyn_cast_or_null<CXXMethodDecl>(D);
-    const auto *CD = MD ? dyn_cast<CXXConstructorDecl>(MD) : nullptr;
-    // It's either some sort of c'tor (but not any move function), or either of
-    // copy assignment operator / d'tor.
-    bool IsCandidate =
-        CD ? !(CD->isMoveConstructor() || CD->isMoveAssignmentOperator())
-           : MD &&
-                 (isa<CXXDestructorDecl>(MD) || 
MD->isCopyAssignmentOperator());
-    if (IsCandidate) {
-      // A special member function always has an implicit object parameter, so
-      // its type is guaranteed to be complete here.
-      CharUnits ObjSize =
-          
getContext().getTypeSizeInChars(MD->getFunctionObjectParameterType());
-      if (ObjSize.getQuantity() >=
-          CGM.getCodeGenOpts().CopyProfStaticSizeThreshold) {
-        std::string ObjSizeStr = llvm::utostr(ObjSize.getQuantity());
-        if (CD)
-          Fn->addFnAttr(CD->isCopyConstructor() ? "copyprof-copy-ctor"
-                                                : "copyprof-ctor",
-                        ObjSizeStr);
-        else if (isa<CXXDestructorDecl>(MD))
-          Fn->addFnAttr("copyprof-dtor", ObjSizeStr);
-        else
-          Fn->addFnAttr("copyprof-copy-assign-op", ObjSizeStr);
+    if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(D)) {
+      StringRef Attr;
+      if (const auto *CD = dyn_cast<CXXConstructorDecl>(MD)) {
+        if (!CD->isMoveConstructor())
+          Attr =
+              CD->isCopyConstructor() ? "copyprof-copy-ctor" : "copyprof-ctor";
+      } else if (isa<CXXDestructorDecl>(MD)) {
+        Attr = "copyprof-dtor";
+      } else if (MD->isCopyAssignmentOperator()) {
+        Attr = "copyprof-copy-assign-op";
+      }
+      if (!Attr.empty()) {
+        // Finally, add the object size in bytes to the annotation.
+        // A special member function always has an implicit object parameter, 
so
+        // its type is guaranteed to be complete here.
+        CharUnits ObjSize = getContext().getTypeSizeInChars(
+            MD->getFunctionObjectParameterType());
+        if (ObjSize.getQuantity() >=
+            CGM.getCodeGenOpts().CopyProfStaticSizeThreshold)
+          Fn->addFnAttr(Attr, llvm::utostr(ObjSize.getQuantity()));
       }
     }
   }
diff --git a/clang/test/CodeGen/copyprof.cpp b/clang/test/CodeGen/copyprof.cpp
index 3c06b14e6df772..926db01effac39 100644
--- a/clang/test/CodeGen/copyprof.cpp
+++ b/clang/test/CodeGen/copyprof.cpp
@@ -1,6 +1,7 @@
 // Test that CopyProf instrumentation passes are invoked at -O0 and -O2,
 // and are not re-run during ThinLTO postlink backend compilation.
 
+// REQUIRES: x86-registered-target
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -fcopyprof %s 
-fdebug-pass-manager -emit-llvm -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=INSTRUMENT
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fcopyprof %s 
-fdebug-pass-manager -emit-llvm -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=INSTRUMENT
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to