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
