https://github.com/OCHyams updated https://github.com/llvm/llvm-project/pull/194860
>From cd89d670e953ba41588cbfe365ca05ce39a5b8d9 Mon Sep 17 00:00:00 2001 From: Orlando Cazalet-Hyams <[email protected]> Date: Thu, 2 Apr 2026 15:49:06 +0100 Subject: [PATCH 1/6] [dyndbg][Clang] Implement nested-ELF dynamic debugging support A clone of the module is compiled without optimisations and embedded into the to-be-optimised module using embedBufferInModule, similarly to how -ffat-lto-objects and -fembed-offload-object work. That unoptimised-code object is embedded in the optimised-code object in a section called .debug_llvm_dyndbg. The optimised ELF/module may be referred to as the "outer" ELF/module, and the unoptimized one the "inner" ELF/module. The outer module holds global data referred to by both modules and all calls in the inner module are to outer module functions. To facilitate this the outer module is modified, adding external-linkage aliases for local symbols. See https://discourse.llvm.org/t/90113 for more detail (including a diagram). --- clang/include/clang/Basic/CodeGenOptions.h | 3 + clang/include/clang/Basic/DebugOptions.def | 7 ++ .../clang/Basic/DiagnosticDriverKinds.td | 4 + .../clang/Basic/DiagnosticFrontendKinds.td | 4 + clang/include/clang/Options/Options.td | 18 ++++ clang/lib/CodeGen/BackendUtil.cpp | 85 +++++++++++++++++++ clang/lib/Driver/ToolChains/Clang.cpp | 27 ++++++ clang/lib/Frontend/CompilerInvocation.cpp | 6 ++ .../DynamicDebugging/EndToEnd/X86/globalopt.c | 24 ++++++ .../EndToEnd/X86/lit.local.cfg | 4 + .../X86/obj-emission-target/lit.local.cfg | 3 + .../X86/obj-emission-target/no-target.c | 14 +++ .../DynamicDebugging/EndToEnd/X86/section.c | 13 +++ .../attr-outer-no-specialization.c | 7 ++ .../attr-outer-tail-pad-unsupported.c | 9 ++ .../DynamicDebugging/attr-outer-tail-pad.c | 10 +++ .../DynamicDebugging/compiler-used.cpp | 37 ++++++++ clang/test/DebugInfo/DynamicDebugging/embed.c | 12 +++ .../DebugInfo/DynamicDebugging/inner-attrs.c | 25 ++++++ .../DynamicDebugging/symbols-functions.cpp | 33 +++++++ .../DynamicDebugging/symbols-globals.cpp | 72 ++++++++++++++++ .../symbols-internal-comdat.cpp | 27 ++++++ clang/test/Driver/dynamic-debugging-flags.c | 28 ++++++ clang/test/Misc/warning-flags.c | 4 +- 24 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/globalopt.c create mode 100644 clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/lit.local.cfg create mode 100644 clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/obj-emission-target/lit.local.cfg create mode 100644 clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/obj-emission-target/no-target.c create mode 100644 clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c create mode 100644 clang/test/DebugInfo/DynamicDebugging/attr-outer-no-specialization.c create mode 100644 clang/test/DebugInfo/DynamicDebugging/attr-outer-tail-pad-unsupported.c create mode 100644 clang/test/DebugInfo/DynamicDebugging/attr-outer-tail-pad.c create mode 100644 clang/test/DebugInfo/DynamicDebugging/compiler-used.cpp create mode 100644 clang/test/DebugInfo/DynamicDebugging/embed.c create mode 100644 clang/test/DebugInfo/DynamicDebugging/inner-attrs.c create mode 100644 clang/test/DebugInfo/DynamicDebugging/symbols-functions.cpp create mode 100644 clang/test/DebugInfo/DynamicDebugging/symbols-globals.cpp create mode 100644 clang/test/DebugInfo/DynamicDebugging/symbols-internal-comdat.cpp create mode 100644 clang/test/Driver/dynamic-debugging-flags.c diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index c12434135a198..499736b9c36be 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -391,6 +391,9 @@ class CodeGenOptions : public CodeGenOptionsBase { /// Prefix to use for -save-temps output. std::string SaveTempsFilePrefix; + /// Prefix to use for -save-dynamic-debugging-temps output. + std::string SaveDynDbgTempsFilePrefix; + /// Name of file passed with -fcuda-include-gpubinary option to forward to /// CUDA runtime back-end for incorporating them into host-side object file. std::string CudaGpuBinaryFileName; diff --git a/clang/include/clang/Basic/DebugOptions.def b/clang/include/clang/Basic/DebugOptions.def index c9dd3f726e799..e496825d51274 100644 --- a/clang/include/clang/Basic/DebugOptions.def +++ b/clang/include/clang/Basic/DebugOptions.def @@ -62,6 +62,13 @@ ENUM_DEBUGOPT(AssignmentTrackingMode, AssignmentTrackingOpts, 2, /// Whether or not to use Key Instructions to determine breakpoint locations. DEBUGOPT(DebugKeyInstructions, 1, 0, Benign) +/// Whether or not to use the Dynamic Debugging feature. +DEBUGOPT(DynamicDebugging, 1, 0, Benign) + +/// Flag for testing: discard the dynamic debugging debug module before codegen +/// if true. +DEBUGOPT(DiscardDynamicDebuggingDebugModule, 1, 0, Benign) + DEBUGOPT(DebugColumnInfo, 1, 0, Compatible) ///< Whether or not to use column information ///< in debug info. diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index ae3eecc60fc78..dab6eb6193c38 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -409,6 +409,10 @@ def err_drv_optimization_remark_format : Error< def err_drv_no_neon_modifier : Error<"[no]neon is not accepted as modifier, please use [no]simd instead">; def err_drv_invalid_omp_target : Error<"OpenMP target is invalid: '%0'">; def err_drv_incompatible_omp_arch : Error<"OpenMP target architecture '%0' pointer size is incompatible with host '%1'">; +def err_drv_dyndbg_lto : Error<"'-fdynamic-debugging' incompatible with '-flto'">; +def err_drv_dyndbg_incompatible : Error<"'-fdynamic-debugging' incompatible with '%0'">; +def err_drv_dyndbg_ir : Error<"'-fdynamic-debugging' incompatible with IR input">; +def warn_drv_dyndbg_req_debug : Warning<"'-fdynamic-debugging' ignored: requires debug info">; def err_drv_omp_host_target_not_supported : Error< "target '%0' is not a supported OpenMP host target">; def err_drv_expecting_fopenmp_with_fopenmp_targets : Error< diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index a688b298f2bbd..7fda8b71016eb 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -557,4 +557,8 @@ def err_cir_to_cir_transform_failed : Error< def err_cir_verification_failed_pre_passes : Error< "CIR module verification error before running CIR-to-CIR passes">, DefaultFatal; + +def warn_dyndbg_unable_to_create_target : Warning< + "ignoring -fdynamic-debugging: unable to create target: '%0'">; } + diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 669b6e64725c9..23eba2adeaa43 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -5331,6 +5331,24 @@ defm key_instructions : BoolGOption<"key-instructions", " in some debuggers. DWARF only.">, BothFlags<[], [ClangOption, CLOption, CC1Option]>>, Group<g_flags_Group>; +defm dynamic_debugging : BoolOption<"f", "dynamic-debugging", + CodeGenOpts<"DynamicDebugging">, DefaultFalse, + NegFlag<SetFalse>, PosFlag<SetTrue, [], [], + "Enable Dynamic Debugging, which allows runtime switching between" + " optimized and unoptimized code in debuggers that support it." + " Specified optimization level affects only the optimized code." + " This flag inhibits interprocedural optimizations.">, + BothFlags<[], [ClangOption, CLOption, CC1Option]>>; +def save_dynamic_debugging_temps : Flag<["--"], "save-dynamic-debugging-temps">, + Visibility<[ClangOption, CLOption, CC1Option]>, + HelpText<"Compiler-debugging/testing option to save the intermediate" + " states of dynamic debugging in the same directory as the final" + " output file">; +def discard_dynamic_debugging_debug_module : Flag<["--"], "discard-dynamic-debugging-debug-module">, + MarshallingInfoFlag<CodeGenOpts<"DiscardDynamicDebuggingDebugModule">>, + Visibility<[CC1Option]>, + HelpText<"Compiler-debugging/testing option to discard the dynamic" + " debugging debug module before Clang codegen">; def headerpad__max__install__names : Joined<["-"], "headerpad_max_install_names">; def help : Flag<["-", "--"], "help">, Visibility<[ClangOption, CC1Option, CC1AsOption, diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 068b1b4c262c8..aa97e40561bc1 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -93,6 +93,7 @@ #include "llvm/Transforms/Scalar/JumpThreading.h" #include "llvm/Transforms/Utils/AssignGUID.h" #include "llvm/Transforms/Utils/Debugify.h" +#include "llvm/Transforms/Utils/DynamicDebugging.h" #include "llvm/Transforms/Utils/ModuleUtils.h" #include <limits> #include <memory> @@ -1539,6 +1540,90 @@ void clang::emitBackendOutput(CompilerInstance &CI, CodeGenOptions &CGOpts, } } + + bool EnableDynamicDebugging = CGOpts.DynamicDebugging; + // Disable dyndbg if the target isn't available as we're compiling to the + // inner module to object regardless of other options. (This may change). + if (EnableDynamicDebugging) { + std::string Error; + const llvm::Target *TheTarget = + TargetRegistry::lookupTarget(M->getTargetTriple(), Error); + if (!TheTarget) { + Diags.Report(diag::warn_dyndbg_unable_to_create_target) << Error; + EnableDynamicDebugging = false; + } + } + + if (EnableDynamicDebugging) { + /// Helper for saving the module(s) at various dyndbg stages. + auto SaveModule = [&](StringRef Name, llvm::Module &M) { + if (CGOpts.SaveDynDbgTempsFilePrefix == "") + return; + std::error_code EC; + std::string Path = + Twine(CGOpts.SaveDynDbgTempsFilePrefix + "." + Name + ".ll").str(); + raw_fd_ostream OS(Path, EC, sys::fs::OpenFlags::OF_None); + if (EC) { + // Copy -save-temps behaviour: this is a debugging option so we simply + // exit if there's an issue. + errs() << "failed to open " << Path << ": " << EC.message() << '\n'; + errs().flush(); + exit(1); + } + M.print(OS, nullptr); + }; + + // Compute a hash suffix for promoting static globals (once per TU). + std::string PromotionSuffix; + { + // LLVM's hash/hash_combine is not guaranteed to be stable. + MD5 Hash; + // Include args in the hash else preprocessor definitions used to alter + // the same source file compiled twice won't generate unique hashes. + Hash.update(CGOpts.CmdArgs); + for (auto *CU : M->debug_compile_units()) { + Hash.update(CU->getDirectory()); + Hash.update(CU->getFilename()); + } + + MD5::MD5Result Result; + Hash.final(Result); + PromotionSuffix = ".dyndbg." + utohexstr(Result.low()); + } + + SaveModule("dyndbg.0.input", *M); + // Modify M as needed and create an "unoptimized" clone. + auto UnoptM = prepareForDynamicDebugging(M, PromotionSuffix); + SaveModule("dyndbg.1.inner", *UnoptM); + + if (!CGOpts.DiscardDynamicDebuggingDebugModule) { + CodeGenOptions UnoptOpts = CGOpts; + UnoptOpts.OptimizationLevel = 0; + UnoptOpts.OptimizeSize = 0; + EmitAssemblyHelper AsmHelper(CI, UnoptOpts, UnoptM.get(), VFS); + + // Create a buffer and ostream for the inner ELF. + SmallVector<char, 0> UnoptBuf; + std::unique_ptr<llvm::raw_pwrite_stream> UnoptOS = + std::make_unique<llvm::raw_svector_ostream>(UnoptBuf); + + // Always run the full codegen pipeline (Backend_EmitObj). This causes + // assertion failures if there's no registered backend which is why we + // disable the feature if that's the case (see + // warn_dyndbg_unable_to_create_target above). + AsmHelper.emitAssembly(Backend_EmitObj, std::move(UnoptOS), BC); + assert(!UnoptBuf.empty() && "Expected emitAssembly to fill UnoptBuf"); + + // Inject the inner ELF into the outer module. + StringRef SR(UnoptBuf.data(), UnoptBuf.size()); + std::unique_ptr<MemoryBuffer> Buf = + MemoryBuffer::getMemBuffer(SR, "", false); + + llvm::embedBufferInModule(*M, *Buf, ".debug_llvm_dyndbg"); + } + SaveModule("dyndbg.2.outer", *M); + } + EmitAssemblyHelper AsmHelper(CI, CGOpts, M, VFS); AsmHelper.emitAssembly(Action, std::move(OS), BC); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 1178f625d1172..b12f378a2a908 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4887,6 +4887,30 @@ renderDebugOptions(const ToolChain &TC, const Driver &D, const llvm::Triple &T, options::OPT_gno_structor_decl_linkage_names, true)) CmdArgs.push_back("-gno-structor-decl-linkage-names"); + if (Args.hasFlag(options::OPT_fdynamic_debugging, + options::OPT_fno_dynamic_debugging, false)) { + // As this is an experimental feature we can afford to be strict about + // supported configurations. + if (!TC.getTriple().isX86()) + D.Diag(diag::err_drv_unsupported_opt_for_target) + << Args.getLastArg(options::OPT_fdynamic_debugging)->getAsString(Args) + << T.getTriple(); + if (D.isUsingLTO()) + D.Diag(diag::err_drv_dyndbg_lto); + if (DwarfFission != DwarfFissionKind::None) + D.Diag(diag::err_drv_dyndbg_incompatible) + << Args.getLastArg(options::OPT_gsplit_dwarf)->getAsString(Args); + // There's no fundamental reason why IR input should be incompatible, but + // it would add some complexity, and reducing the test matrix is valuable. + if (IRInput) + D.Diag(diag::err_drv_dyndbg_ir); + + if (!EmitDwarf) + D.Diag(diag::warn_drv_dyndbg_req_debug); + else + CmdArgs.push_back("-fdynamic-debugging"); + } + if (EmitCodeView) { CmdArgs.push_back("-gcodeview"); @@ -5603,6 +5627,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, if (Args.getLastArg(options::OPT_save_temps_EQ)) Args.AddLastArg(CmdArgs, options::OPT_save_temps_EQ); + if (Args.getLastArg(options::OPT_save_dynamic_debugging_temps)) + Args.AddLastArg(CmdArgs, options::OPT_save_dynamic_debugging_temps); + auto *MemProfArg = Args.getLastArg(options::OPT_fmemory_profile, options::OPT_fmemory_profile_EQ, options::OPT_fno_memory_profile); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 6562cb3a9b135..0e5fb61b22f6d 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1672,6 +1672,9 @@ void CompilerInvocationBase::GenerateCodeGenArgs(const CodeGenOptions &Opts, if (Opts.SaveTempsFilePrefix == OutputFile) GenerateArg(Consumer, OPT_save_temps_EQ, "obj"); + if (!Opts.SaveDynDbgTempsFilePrefix.empty()) + GenerateArg(Consumer, OPT_save_dynamic_debugging_temps); + StringRef MemProfileBasename("memprof.profraw"); if (!Opts.MemoryProfileOutput.empty()) { if (Opts.MemoryProfileOutput == MemProfileBasename) { @@ -2006,6 +2009,9 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, .Case("obj", OutputFile) .Default(llvm::sys::path::filename(OutputFile).str()); + if (Arg *A = Args.getLastArg(OPT_save_dynamic_debugging_temps)) + Opts.SaveDynDbgTempsFilePrefix = OutputFile; + // The memory profile runtime appends the pid to make this name more unique. const char *MemProfileBasename = "memprof.profraw"; if (Args.hasArg(OPT_fmemory_profile_EQ)) { diff --git a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/globalopt.c b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/globalopt.c new file mode 100644 index 0000000000000..4450bd8aae5e2 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/globalopt.c @@ -0,0 +1,24 @@ +// Check `a` and `d` have aliases while keeping their original symbols. +// GlobalOpt would usually replace these internal-linkage functions with +// their external-linkage aliases. That's currently prevented as a side effect +// of adding discardable functions to the compiler-used global. + +// RUN: %clang -cc1 %s -emit-obj -O3 -debug-info-kind=limited -fdynamic-debugging -o - -triple x86_64-unknown-unknown | llvm-nm - | FileCheck %s +// CHECK: t a +// CHECK: T a.dyndbg.[[hash:[A-Z0-9]+]] +// CHECK: T b +// CHECK: U c +// CHECK: t d +// CHECK: T d.dyndbg.[[hash]] +// CHECK: B g + +int g; +int c(); + +__attribute__((always_inline)) +static inline int d() { return c(); } + +__attribute__((always_inline)) +static int a() { return g; } + +int b() { return a() + d() ;} diff --git a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/lit.local.cfg b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/lit.local.cfg new file mode 100644 index 0000000000000..bf8bd32700a9d --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/lit.local.cfg @@ -0,0 +1,4 @@ +# These tests require x86 in order to inspect the inner object. +# FIXME: These tests are quite broad. +if "x86-registered-target" not in config.available_features: + config.unsupported = True diff --git a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/obj-emission-target/lit.local.cfg b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/obj-emission-target/lit.local.cfg new file mode 100644 index 0000000000000..d36760917851d --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/obj-emission-target/lit.local.cfg @@ -0,0 +1,3 @@ +# This test wants X86 support without aarch64 support. +if "x86-registered-target" not in config.available_features or "aarch64-registered-target" in config.available_features: + config.unsupported = True diff --git a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/obj-emission-target/no-target.c b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/obj-emission-target/no-target.c new file mode 100644 index 0000000000000..b5af3a3b7ac9c --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/obj-emission-target/no-target.c @@ -0,0 +1,14 @@ +// Check we get a warning if -fdynamic-debugging is specified for an +// unsupported target. Dynamic debugging currently emits the inner module as +// an object regardless of output flags (e.g. -emit-llvm). + +// RUN: %clang -cc1 %s -emit-llvm -debug-info-kind=limited -fdynamic-debugging -o - -triple aarch64-unknown-unknown 2>&1 | FileCheck %s --check-prefix=WITHOUT_TARGET +// WITHOUT_TARGET: warning: ignoring -fdynamic-debugging: unable to create target: 'No available targets are compatible with triple "aarch64-unknown-unknown"' +// WITHOUT_TARGET-NOT: .debug_llvm_dyndbg + +// Prevent rotten green test by checking we do see .debug_llvm_dyndbg otherwise. +// RUN: %clang -cc1 %s -emit-obj -debug-info-kind=limited -fdynamic-debugging -o - -triple x86_64-unknown-unknown 2>&1 | FileCheck %s --check-prefix=WITH_TARGET +// WITH_TARGET-NOT: ignoring -fdynamic-debugging: +// WITH_TARGET: .debug_llvm_dyndbg + +int g; diff --git a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c new file mode 100644 index 0000000000000..2f0f9a95c199a --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c @@ -0,0 +1,13 @@ +// By default LLVM gives this section SHF_EXCLUDE, which we don't want. + +// RUN: %clang -cc1 %s -emit-obj -debug-info-kind=limited -fdynamic-debugging -o - -triple x86_64-unknown-unknown | llvm-readelf --section-details - \ +// RUN: | FileCheck %s +// [Nr] Name +// CHECK: .debug_llvm_dyndbg +// Type Address Off Size ES Lk Inf Al +// CHECK-NEXT: PROGBITS 0000000000000000 {{[0-9a-z]+}} {{[0-9a-z]+}} 00 0 0 1 +// Flags +// CHECK-NEXT: [0000000000000000]: {{$}} + +int g; +int b() { return g; } diff --git a/clang/test/DebugInfo/DynamicDebugging/attr-outer-no-specialization.c b/clang/test/DebugInfo/DynamicDebugging/attr-outer-no-specialization.c new file mode 100644 index 0000000000000..94fe0b7499df0 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/attr-outer-no-specialization.c @@ -0,0 +1,7 @@ +// RUN: %clang -cc1 -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o - \ +// RUN: -emit-llvm --discard-dynamic-debugging-debug-module \ +// RUN: | FileCheck %s + +// CHECK: define dso_local i32 @f() #0 +// CHECK: attributes #0 = {{{.*}}"no-func-spec"{{.*}}} +int f() { return 0; } diff --git a/clang/test/DebugInfo/DynamicDebugging/attr-outer-tail-pad-unsupported.c b/clang/test/DebugInfo/DynamicDebugging/attr-outer-tail-pad-unsupported.c new file mode 100644 index 0000000000000..5d03e43109725 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/attr-outer-tail-pad-unsupported.c @@ -0,0 +1,9 @@ +// REQUIRES: !x86-registered-target +// This test should trip to remind that tail-padding support should be added +// for targets as dynamic debugging support is expanded. +// RUN: %clang -cc1 %s -triple %itanium_abi_triple -debug-info-kind=constructor -fdynamic-debugging -o - \ +// RUN: -emit-llvm --discard-dynamic-debugging-debug-module \ +// RUN: | FileCheck %s + +// CHECK: "tail-pad-to-size" +int f() { return 0; } diff --git a/clang/test/DebugInfo/DynamicDebugging/attr-outer-tail-pad.c b/clang/test/DebugInfo/DynamicDebugging/attr-outer-tail-pad.c new file mode 100644 index 0000000000000..045374a34b936 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/attr-outer-tail-pad.c @@ -0,0 +1,10 @@ +// RUN: %clang -cc1 %s -triple %itanium_abi_triple -debug-info-kind=constructor -fdynamic-debugging -o - \ +// RUN: -emit-llvm --discard-dynamic-debugging-debug-module \ +// RUN: | FileCheck %s --check-prefix=X86 + +/// FIXME: Add negative tests for other targets (if in doubt, don't tail-pad). + +/// Pad functions to minimum of 5 bytes for insertion of 32 rel jump. +// X86: define dso_local i32 @f() #0 +// X86: attributes #0 = {{{.*}}"tail-pad-to-size"="5"{{.*}}"tail-pad-value"="144"{{.*}}} +int f() { return 0; } diff --git a/clang/test/DebugInfo/DynamicDebugging/compiler-used.cpp b/clang/test/DebugInfo/DynamicDebugging/compiler-used.cpp new file mode 100644 index 0000000000000..25e1eec4bd84f --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/compiler-used.cpp @@ -0,0 +1,37 @@ +// RUN: %clang -cc1 -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o %t \ +// RUN: -emit-llvm --save-dynamic-debugging-temps --discard-dynamic-debugging-debug-module +// RUN: FileCheck %s --check-prefix=OUTER < %t.dyndbg.2.outer.ll + +// Test discardable symbols are added to the @llvm.compiler.used global, +// which prevents them being discarded (including by globalopt replacing) them +// with their external linkage aliases. + +// OUTER: @llvm.compiler.used = appending global [[[#]] x ptr] +// OUTER-SAME: [ +// OUTER-SAME: ptr @__cxx_global_var_init, +// OUTER-SAME: ptr @_ZL12internal_funv, +// OUTER-SAME: ptr @_Z14internal_fun_2v, +// OUTER-SAME: ptr @_GLOBAL__sub_I_compiler_used.cpp, +// OUTER-SAME: ptr @_ZL12used_by_init, +// OUTER-SAME: ptr @odrweak, +// OUTER-SAME: ptr @_ZL8internal, +// OUTER-SAME: ptr @_ZZ14internal_fun_2vE8internal +// OUTER-SAME: ], +// OUTER-SAME: section "llvm.metadata" + +// 'external' has external linkage; it's not discardable if unused, so it +// doesn't need to be added to compiler-used. +int external = 1; +// 'odrweak' and 'internal' are both discardable and may be only referenced +// from the inner module, so we must keep them. +inline int odrweak = 2; +static int internal = 1; +// 'unused_internal' has internal linkage and is unused so we don't need to +// preserve it (it isn't referenced by the outer or inner module). +static int internal_fun() { static int unused_internal = 0; return 0; } +inline int internal_fun_2() { static int internal = 0; return internal; } + +// Use those globals so they're not omitted by Clang. Don't use [[gnu::used]] +// because that populates the compiler-used global. This global itself is +// unused in user code but it _is_ used in __cxx_global_var_init. +static int used_by_init = external + odrweak + internal + internal_fun() + internal_fun_2(); diff --git a/clang/test/DebugInfo/DynamicDebugging/embed.c b/clang/test/DebugInfo/DynamicDebugging/embed.c new file mode 100644 index 0000000000000..969c2b581f913 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/embed.c @@ -0,0 +1,12 @@ +// REQUIRES: x86-registered-target +// Requires X86 as this test runs the codegen pipeline for the debug module. +// RUN: %clang -cc1 -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o %t --save-dynamic-debugging-temps +// RUN: FileCheck %s < %t.dyndbg.2.outer.ll + +// Test that a dynamic debugging section is embedded in the outer module. Note +// that !exclude is ignored by LLVM as this section's flags are chosen based +// on its name. FIXME: We could introduce new metadata like !exclude to avoid +// the special casing in LLVM. +int e() { return 0; } + +// CHECK: @llvm.embedded.object = private constant {{.*}}, section ".debug_llvm_dyndbg", align 1, !exclude diff --git a/clang/test/DebugInfo/DynamicDebugging/inner-attrs.c b/clang/test/DebugInfo/DynamicDebugging/inner-attrs.c new file mode 100644 index 0000000000000..7f7cc849d7776 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/inner-attrs.c @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple %s -Os -debug-info-kind=constructor -fdynamic-debugging -o %t \ +// RUN: -emit-llvm --save-dynamic-debugging-temps --discard-dynamic-debugging-debug-module +// RUN: FileCheck %s --check-prefix=INPUT < %t.dyndbg.0.input.ll +// RUN: FileCheck %s --check-prefix=INNER < %t.dyndbg.1.inner.ll + +/// always_inline should be removed from the inner module. +__attribute__((always_inline)) void a() { } +__attribute__((minsize)) void b() { } + +/// Confirm the input module has alwaysinline, minsize, optsize. +// INPUT: define dso_local void @a() #0 +// INPUT: define dso_local void @b() #1 +// INPUT: attributes #0 = { alwaysinline nounwind optsize " +// INPUT: attributes #1 = { minsize nounwind optsize " + +/// Check the inner module has noinline and optnone added to its copies of the +/// outer functions, removing alwaysinline, minsize, optsize. +// INNER: define dso_local void @__dyndbg.a() #0 +// INNER: define dso_local void @__dyndbg.b() #0 +// INNER: declare dso_local void @a() #1 +// INNER: declare dso_local void @b() #2 +// INNER: attributes #0 = { noinline nounwind optnone " +/// Inner's references to outer's functions keep their original attributes. +// INNER: attributes #1 = { alwaysinline nounwind optsize " +// INNER: attributes #2 = { minsize nounwind optsize " diff --git a/clang/test/DebugInfo/DynamicDebugging/symbols-functions.cpp b/clang/test/DebugInfo/DynamicDebugging/symbols-functions.cpp new file mode 100644 index 0000000000000..0a9c5a73153b5 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/symbols-functions.cpp @@ -0,0 +1,33 @@ +// RUN: %clang -cc1 -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o %t \ +// RUN: -emit-llvm --save-dynamic-debugging-temps --discard-dynamic-debugging-debug-module +// RUN: FileCheck %s --check-prefix=INNER < %t.dyndbg.1.inner.ll +// RUN: FileCheck %s --check-prefix=OUTER < %t.dyndbg.2.outer.ll + +/// Test functions get expected linkage and names in the dyndbg inner +/// and outer modules. Each outer (to-be-optimized) function should have a +/// corresponding inner (unoptimized) version. +/// +/// The internal functions get external aliases in outer. + +// OUTER: $_Z7odrweakv = comdat any +// INNER: $__dyndbg._Z7odrweakv = comdat any + +/// Outer: as input. Inner: external reference, __dyndbg copy. +void external() {} +// OUTER-DAG: define dso_local void @_Z8externalv() +// INNER-DAG: declare dso_local void @_Z8externalv() +// INNER-DAG: define dso_local void @__dyndbg._Z8externalv() + +/// Outer: add external linkage alias. Inner: external reference to alias, +/// __dyndbg copy. +[[gnu::used]] static void internal() {} +// OUTER-DAG: @_ZL8internalv.dyndbg.[[hash:[0-9A-Z]+]] = hidden alias void (), ptr @_ZL8internalv +// OUTER-DAG: define internal void @_ZL8internalv() +// INNER-DAG: declare hidden void @_ZL8internalv.dyndbg.[[hash:[0-9A-Z]+]]() +// INNER-DAG: define hidden void @__dyndbg._ZL8internalv.dyndbg.[[hash]] + +/// Outer: as input. Inner: external reference, __dyndbg copy. +[[gnu::used]] inline void odrweak() {} +// OUTER-DAG: define linkonce_odr void @_Z7odrweakv() +// INNER-DAG: declare void @_Z7odrweakv() +// INNER-DAG: define linkonce_odr void @__dyndbg._Z7odrweakv() diff --git a/clang/test/DebugInfo/DynamicDebugging/symbols-globals.cpp b/clang/test/DebugInfo/DynamicDebugging/symbols-globals.cpp new file mode 100644 index 0000000000000..be299c0262665 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/symbols-globals.cpp @@ -0,0 +1,72 @@ +// RUN: %clang -cc1 -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o %t \ +// RUN: -emit-llvm --save-dynamic-debugging-temps --discard-dynamic-debugging-debug-module +// RUN: FileCheck %s --check-prefix=INNER < %t.dyndbg.1.inner.ll +// RUN: FileCheck %s --check-prefix=OUTER < %t.dyndbg.2.outer.ll + +// Test global variables get expected linkage and names in the dyndbg inner +// and outer modules. Global data is stored in the outer module and referenced +// from the inner module. +// +// The external global variables are simply referenced by inner. +// +// The internal (static) global variables get external aliases in outer +// and those are referenced from inner. +// +// The internal functions _ZTW1d (thread-local wrapper routine for d) and +// _GLOBAL__sub_I_symbols_globals, __cxx_global_var_init are promoted too (get +// global aliases). + +int a = 1; +// OUTER-DAG: @a = global i32 1, align 4 +// INNER-DAG: @a = external global i32, align 4 + +extern int b; +// OUTER-DAG: @b = external global i32, align 4 +// INNER-DAG: @b = external global i32, align 4 + +inline int c = 2; +// OUTER-DAG: $c = comdat any +// OUTER-DAG: @c = linkonce_odr global i32 2, comdat, align 4 +// INNER-DAG: @c = external global i32, align 4 + +thread_local int d = 3; +// OUTER-DAG: @d = thread_local global i32 3, align 4 +// INNER-DAG: @d = external thread_local global i32, align 4 +// +// OUTER-DAG: $_ZTW1d = comdat any +// OUTER-DAG: define weak_odr hidden noundef ptr @_ZTW1d() #[[#]] comdat +// INNER-DAG: $__dyndbg._ZTW1d = comdat any +// INNER-DAG: declare hidden noundef ptr @_ZTW1d() +// INNER-DAG: define weak_odr hidden noundef ptr @__dyndbg._ZTW1d() #[[#]] comdat + +struct S { int a, b; float c, d; } e {0, 100, 4.f, 5.f}; +// OUTER-DAG: @e = global %struct.S { i32 0, i32 100, float 4.000000e+00, float 5.000000e+00 }, align 4 +// INNER-DAG: @e = external global %struct.S, align 4 + +inline S f {0, 100, 4.f, 5.f}; +// OUTER-DAG: $f = comdat any +// OUTER-DAG: @f = linkonce_odr global %struct.S { i32 0, i32 100, float 4.000000e+00, float 5.000000e+00 }, comdat, align 4 +// INNER-DAG: @f = external global %struct.S, align 4 + +int fun() { + static int g = 0; + return g; +} +// OUTER-DAG: @_ZZ3funvE1g = internal global i32 0, align 4 +// OUTER-DAG: @_ZZ3funvE1g.dyndbg.[[hash:[0-9A-Z]+]] = hidden alias i32, ptr @_ZZ3funvE1g +// INNER-DAG: @_ZZ3funvE1g.dyndbg.[[hash:[0-9A-Z]+]] = external hidden global i32, align 4 + +static int h = 1; +// OUTER-DAG: @_ZL1h = internal global i32 1, align 4 +// OUTER-DAG: @_ZL1h.dyndbg.[[hash]] = hidden alias i32, ptr @_ZL1h +// INNER-DAG: @_ZL1h.dyndbg.[[hash]] = external hidden global i32, align 4 + +__attribute__((nodebug)) int use = a + b + c + d + e.a + f.a + h; + +// OUTER-DAG: define internal void @__cxx_global_var_init() +// OUTER-DAG: @__cxx_global_var_init.dyndbg.[[hash]] = hidden alias void (), ptr @__cxx_global_var_init +// INNER-DAG: declare hidden void @__cxx_global_var_init.dyndbg.[[hash]]() + +// OUTER-DAG: define internal void @_GLOBAL__sub_I_symbols_globals.cpp() +// OUTER-DAG: @_GLOBAL__sub_I_symbols_globals.cpp.dyndbg.[[hash]] = hidden alias void (), ptr @_GLOBAL__sub_I_symbols_globals.cpp +// INNER-DAG: define hidden void @__dyndbg._GLOBAL__sub_I_symbols_globals.cpp.dyndbg.[[hash]]() #[[#]] diff --git a/clang/test/DebugInfo/DynamicDebugging/symbols-internal-comdat.cpp b/clang/test/DebugInfo/DynamicDebugging/symbols-internal-comdat.cpp new file mode 100644 index 0000000000000..515602b140821 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/symbols-internal-comdat.cpp @@ -0,0 +1,27 @@ +// RUN: %clang -cc1 -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o %t \ +// RUN: -emit-llvm --save-dynamic-debugging-temps --discard-dynamic-debugging-debug-module +// RUN: FileCheck %s --check-prefix=INNER < %t.dyndbg.1.inner.ll --implicit-check-not=@__dyndbg._ZN1X5weirdE +// RUN: FileCheck %s --check-prefix=OUTER < %t.dyndbg.2.outer.ll + +/// Check that internal symbols in comdats, __cxx_global_var_init in this case, +/// are not promoted (no external alias is produced). +/// +/// --implicit-check-not=@__dyndbg._ZN1X5weirdE: +/// The innner cxx_global_var_init gets a comdat, '$__dyndbg._ZN1X5weirdE', but +/// unlike the one in the outer module, this one has no associated global data. + +// OUTER: @_ZN1X5weirdE = linkonce_odr global %struct.X zeroinitializer, comdat, align 4 +// INNER: @_ZN1X5weirdE = external global %struct.X, align 4 + +// OUTER: define internal void @__cxx_global_var_init() {{.*}} comdat($_ZN1X5weirdE) +// INNER: define internal void @__dyndbg.__cxx_global_var_init() {{.*}} comdat($__dyndbg._ZN1X5weirdE) +// INNER: declare dso_local void @__cxx_global_var_init() + +struct X { +public: + X(int a) : a(a) {} + int a; + static const X weird; +}; + +inline const X X::weird = X(5); diff --git a/clang/test/Driver/dynamic-debugging-flags.c b/clang/test/Driver/dynamic-debugging-flags.c new file mode 100644 index 0000000000000..bf58a4250c63b --- /dev/null +++ b/clang/test/Driver/dynamic-debugging-flags.c @@ -0,0 +1,28 @@ +// Only support x86_64 targets initially. +// RUN: not %clang -c -target aarch64-unknown-unknown -g -fdynamic-debugging -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-TARGET-ERR +// RUN: %clang -c -target x86_64-unknown-unknown -g -fdynamic-debugging -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-OK +// CHECK-TARGET-ERR: error: unsupported option '-fdynamic-debugging' for target 'aarch64-unknown-unknown' + +// Do not support LTO initially. +// RUN: not %clang -c -target x86_64-unknown-unknown -g -fdynamic-debugging -flto -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-LTO-ERR +// RUN: not %clang -c -target x86_64-unknown-unknown -g -fdynamic-debugging -flto=full -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-LTO-ERR +// RUN: not %clang -c -target x86_64-unknown-unknown -g -fdynamic-debugging -flto=thin -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-LTO-ERR +// RUN: %clang -c -target x86_64-unknown-unknown -g -fdynamic-debugging -flto -fno-lto -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-OK +// CHECK-LTO-ERR: clang: error: '-fdynamic-debugging' incompatible with '-flto' + +// Do not support split dwarf. +// RUN: not %clang -c -target x86_64-unknown-unknown -fdynamic-debugging -g -gsplit-dwarf -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-DWO-ERR +// RUN: %clang -c -target x86_64-unknown-unknown -fdynamic-debugging -g -gsplit-dwarf -gno-split-dwarf -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-OK +// CHECK-DWO-ERR: clang: error: '-fdynamic-debugging' incompatible with '-gsplit-dwarf' + +// Do not support llvm IR input. +// RUN: not %clang -c -target x86_64-unknown-unknown -fdynamic-debugging -gsplit-dwarf -### -o /dev/null -x ir %s 2>&1 | FileCheck %s -check-prefix=CHECK-LL-ERR +// CHECK-LL-ERR: clang: error: '-fdynamic-debugging' incompatible with IR input + +// Warning - requires debug info. +// RUN: %clang -c -target x86_64-unknown-unknown -fdynamic-debugging -gsplit-dwarf -### -o /dev/null %s 2>&1 | FileCheck %s -check-prefix=CHECK-DBG-WARN +// CHECK-DBG-WARN: clang: warning: '-fdynamic-debugging' ignored: requires debug info +// CHECK-DBG-WARN-NOT: -fdynamic-debugging + +// CHECK-OK-NOT: error: +// CHECK-OK-NOT: warning: diff --git a/clang/test/Misc/warning-flags.c b/clang/test/Misc/warning-flags.c index 3dc4bb55aa69c..17128bb804964 100644 --- a/clang/test/Misc/warning-flags.c +++ b/clang/test/Misc/warning-flags.c @@ -18,7 +18,7 @@ This test serves two purposes: The list of warnings below should NEVER grow. It should gradually shrink to 0. -CHECK: Warnings without flags (56): +CHECK: Warnings without flags (58): CHECK-NEXT: ext_expected_semi_decl_list CHECK-NEXT: ext_missing_whitespace_after_macro_name @@ -42,7 +42,9 @@ CHECK-NEXT: warn_delete_array_type CHECK-NEXT: warn_double_const_requires_fp64 CHECK-NEXT: warn_drv_assuming_mfloat_abi_is CHECK-NEXT: warn_drv_clang_unsupported +CHECK-NEXT: warn_drv_dyndbg_req_debug CHECK-NEXT: warn_drv_pch_not_first_include +CHECK-NEXT: warn_dyndbg_unable_to_create_target CHECK-NEXT: warn_expected_qualified_after_typename CHECK-NEXT: warn_fe_backend_unsupported CHECK-NEXT: warn_fe_cc_log_diagnostics_failure >From ec49bc86d363d02a62862a00d21e1a5157087fa8 Mon Sep 17 00:00:00 2001 From: Orlando Cazalet-Hyams <[email protected]> Date: Wed, 29 Apr 2026 11:23:18 +0100 Subject: [PATCH 2/6] err on profile-instrumentation --- clang/include/clang/Basic/DiagnosticFrontendKinds.td | 3 +++ clang/lib/CodeGen/BackendUtil.cpp | 9 +++++++++ clang/test/DebugInfo/DynamicDebugging/profile-coverage.c | 7 +++++++ 3 files changed, 19 insertions(+) create mode 100644 clang/test/DebugInfo/DynamicDebugging/profile-coverage.c diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index 7fda8b71016eb..476cb8ecc9dd4 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -560,5 +560,8 @@ def err_cir_verification_failed_pre_passes : Error< def warn_dyndbg_unable_to_create_target : Warning< "ignoring -fdynamic-debugging: unable to create target: '%0'">; + +def err_dyndbg_no_instrumentation : Error< + "'-fdynamic-debugging' unsupported with instrumentation (PGO/code coverage)">; } diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index aa97e40561bc1..0e031f4d0bc69 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -1552,6 +1552,15 @@ void clang::emitBackendOutput(CompilerInstance &CI, CodeGenOptions &CGOpts, Diags.Report(diag::warn_dyndbg_unable_to_create_target) << Error; EnableDynamicDebugging = false; } + + // Instrumentation causes issues (parts of LLVM expect certain globals to + // have initializers). Intrinsics may already have been added to IR by now, + // so we can't just turn it off for the inner module (we'd have to strip + // them out / not clone them). TODO: Support instrumentation. + if (CGOpts.getProfileInstr() != driver::ProfileInstrKind::ProfileNone) { + Diags.Report(diag::err_dyndbg_no_instrumentation); + EnableDynamicDebugging = false; + } } if (EnableDynamicDebugging) { diff --git a/clang/test/DebugInfo/DynamicDebugging/profile-coverage.c b/clang/test/DebugInfo/DynamicDebugging/profile-coverage.c new file mode 100644 index 0000000000000..a896ea57a8b26 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/profile-coverage.c @@ -0,0 +1,7 @@ +// Instrumentation from PGO/code coverage isn't supported yet for dyndbg. +// RUN: not %clang_cc1 %s -fprofile-instrument=clang -fdynamic-debugging -emit-llvm \ +// RUN: --discard-dynamic-debugging-debug-module 2>&1 \ +// RUN: | FileCheck %s +// CHECK: error: '-fdynamic-debugging' unsupported with instrumentation (PGO/code coverage) + +int b() { return 0; } >From 062ad33388e64e9491ec9bc48d8b2dd34a9cbed6 Mon Sep 17 00:00:00 2001 From: Orlando Cazalet-Hyams <[email protected]> Date: Wed, 29 Apr 2026 12:51:31 +0100 Subject: [PATCH 3/6] [Clang] Disable dyndbg and sanitizer composition The approach taken is slightly more aggressive than needed. It doesn't allow -fsanitize=undefined -fno-sanitize=undefined for example. --- clang/lib/Driver/ToolChains/Clang.cpp | 4 ++++ clang/test/Driver/dynamic-debugging-flags.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index b12f378a2a908..1be416c528ade 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4905,6 +4905,10 @@ renderDebugOptions(const ToolChain &TC, const Driver &D, const llvm::Triple &T, if (IRInput) D.Diag(diag::err_drv_dyndbg_ir); + // Disable composition with sanitizers for now. + if (auto *San = Args.getLastArg(options::OPT_fsanitize_EQ)) + D.Diag(diag::err_drv_dyndbg_incompatible) << San->getAsString(Args); + if (!EmitDwarf) D.Diag(diag::warn_drv_dyndbg_req_debug); else diff --git a/clang/test/Driver/dynamic-debugging-flags.c b/clang/test/Driver/dynamic-debugging-flags.c index bf58a4250c63b..dedc9509eeb3d 100644 --- a/clang/test/Driver/dynamic-debugging-flags.c +++ b/clang/test/Driver/dynamic-debugging-flags.c @@ -24,5 +24,9 @@ // CHECK-DBG-WARN: clang: warning: '-fdynamic-debugging' ignored: requires debug info // CHECK-DBG-WARN-NOT: -fdynamic-debugging +// Do not support sanitizers initially. +// RUN: not %clang -fsanitize=undefined -c -target x86_64-unknown-unknown -fdynamic-debugging -### -o /dev/null -x ir %s 2>&1 | FileCheck %s -check-prefix=CHECK-SAN-ERR +// CHECK-SAN-ERR: clang: error: '-fdynamic-debugging' incompatible with '-fsanitize=undefined' + // CHECK-OK-NOT: error: // CHECK-OK-NOT: warning: >From 06dcbf9000654636f683349b31db8ea79ae9ff35 Mon Sep 17 00:00:00 2001 From: Orlando Cazalet-Hyams <[email protected]> Date: Thu, 7 May 2026 09:57:22 +0100 Subject: [PATCH 4/6] Align .debug_llvm_dyndbg to 8 bytes --- clang/lib/CodeGen/BackendUtil.cpp | 2 +- clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c | 2 +- clang/test/DebugInfo/DynamicDebugging/embed.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 0e031f4d0bc69..e31bffc7e81ee 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -1628,7 +1628,7 @@ void clang::emitBackendOutput(CompilerInstance &CI, CodeGenOptions &CGOpts, std::unique_ptr<MemoryBuffer> Buf = MemoryBuffer::getMemBuffer(SR, "", false); - llvm::embedBufferInModule(*M, *Buf, ".debug_llvm_dyndbg"); + llvm::embedBufferInModule(*M, *Buf, ".debug_llvm_dyndbg", Align(8)); } SaveModule("dyndbg.2.outer", *M); } diff --git a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c index 2f0f9a95c199a..a6ae1e74457e4 100644 --- a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c +++ b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c @@ -5,7 +5,7 @@ // [Nr] Name // CHECK: .debug_llvm_dyndbg // Type Address Off Size ES Lk Inf Al -// CHECK-NEXT: PROGBITS 0000000000000000 {{[0-9a-z]+}} {{[0-9a-z]+}} 00 0 0 1 +// CHECK-NEXT: PROGBITS 0000000000000000 {{[0-9a-z]+}} {{[0-9a-z]+}} 00 0 0 8 // Flags // CHECK-NEXT: [0000000000000000]: {{$}} diff --git a/clang/test/DebugInfo/DynamicDebugging/embed.c b/clang/test/DebugInfo/DynamicDebugging/embed.c index 969c2b581f913..a621e05378196 100644 --- a/clang/test/DebugInfo/DynamicDebugging/embed.c +++ b/clang/test/DebugInfo/DynamicDebugging/embed.c @@ -9,4 +9,4 @@ // the special casing in LLVM. int e() { return 0; } -// CHECK: @llvm.embedded.object = private constant {{.*}}, section ".debug_llvm_dyndbg", align 1, !exclude +// CHECK: @llvm.embedded.object = private constant {{.*}}, section ".debug_llvm_dyndbg", align 8, !exclude >From ed070f2c4568756ae72d754ee4dc26b9048a1e19 Mon Sep 17 00:00:00 2001 From: Orlando Cazalet-Hyams <[email protected]> Date: Wed, 22 Jul 2026 13:21:33 +0100 Subject: [PATCH 5/6] Use elf_section_properties and metadata_section_kind Review of the linker-side changes has resulted in adding a new section type SHT_LLVM_DYNDBG_ELF. These changes let us use that section without any section flags applied. --- clang/lib/CodeGen/BackendUtil.cpp | 15 ++++++++++++++- .../DynamicDebugging/EndToEnd/X86/section.c | 8 +++++--- clang/test/DebugInfo/DynamicDebugging/embed.c | 4 +++- llvm/include/llvm/Transforms/Utils/ModuleUtils.h | 12 ++++++++---- llvm/lib/Transforms/Utils/ModuleUtils.cpp | 15 +++++++++++---- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index e31bffc7e81ee..4b5da3889d8b0 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -22,6 +22,7 @@ #include "llvm/Analysis/RuntimeLibcallInfo.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/TargetTransformInfo.h" +#include "llvm/BinaryFormat/ELF.h" #include "llvm/Bitcode/BitcodeReader.h" #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/Bitcode/BitcodeWriterPass.h" @@ -1628,7 +1629,19 @@ void clang::emitBackendOutput(CompilerInstance &CI, CodeGenOptions &CGOpts, std::unique_ptr<MemoryBuffer> Buf = MemoryBuffer::getMemBuffer(SR, "", false); - llvm::embedBufferInModule(*M, *Buf, ".debug_llvm_dyndbg", Align(8)); + GlobalVariable *EmbeddedGV = + llvm::embedBufferInModule(*M, *Buf, ".debug_llvm_dyndbg", Align(8), + /*SectionExclude*/ false); + // Add ELF section properties metadata. + auto &C = M->getContext(); + auto getU32Metadata = [&C](unsigned Val) { + return ConstantAsMetadata::get(ConstantInt::get(C, APInt(32, Val))); + }; + EmbeddedGV->addMetadata( + LLVMContext::MD_elf_section_properties, + *MDTuple::get(C, + {/*sh_type*/ getU32Metadata(ELF::SHT_LLVM_DYNDBG_ELF), + /*sh_entsize*/ getU32Metadata(0)})); } SaveModule("dyndbg.2.outer", *M); } diff --git a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c index a6ae1e74457e4..4684fec6364c4 100644 --- a/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c +++ b/clang/test/DebugInfo/DynamicDebugging/EndToEnd/X86/section.c @@ -1,11 +1,13 @@ -// By default LLVM gives this section SHF_EXCLUDE, which we don't want. +// By default LLVM gives this section SHF_EXCLUDE, which we don't want. We +// expect a section named .debug_llvm_dyndbg of type LLVM_DYNDBG_ELF with +// no flags and alignment of 8 bytes. // RUN: %clang -cc1 %s -emit-obj -debug-info-kind=limited -fdynamic-debugging -o - -triple x86_64-unknown-unknown | llvm-readelf --section-details - \ // RUN: | FileCheck %s // [Nr] Name // CHECK: .debug_llvm_dyndbg -// Type Address Off Size ES Lk Inf Al -// CHECK-NEXT: PROGBITS 0000000000000000 {{[0-9a-z]+}} {{[0-9a-z]+}} 00 0 0 8 +// Type Address Off Size ES Lk Inf Al +// CHECK-NEXT: LLVM_DYNDBG_ELF 0000000000000000 {{[0-9a-z]+}} {{[0-9a-z]+}} 00 0 0 8 // Flags // CHECK-NEXT: [0000000000000000]: {{$}} diff --git a/clang/test/DebugInfo/DynamicDebugging/embed.c b/clang/test/DebugInfo/DynamicDebugging/embed.c index a621e05378196..2cb665ad0d00d 100644 --- a/clang/test/DebugInfo/DynamicDebugging/embed.c +++ b/clang/test/DebugInfo/DynamicDebugging/embed.c @@ -9,4 +9,6 @@ // the special casing in LLVM. int e() { return 0; } -// CHECK: @llvm.embedded.object = private constant {{.*}}, section ".debug_llvm_dyndbg", align 8, !exclude +// CHECK: @llvm.embedded.object = private constant {{.*}}, section ".debug_llvm_dyndbg", align 8, +// CHECK-SAME: !elf_section_properties ![[elf_props:[0-9]+]], !metadata_section_kind +// CHECK: ![[elf_props:[0-9]+]] = !{i32 1879002128, i32 0} diff --git a/llvm/include/llvm/Transforms/Utils/ModuleUtils.h b/llvm/include/llvm/Transforms/Utils/ModuleUtils.h index 4036c4e947c75..baafc078039ec 100644 --- a/llvm/include/llvm/Transforms/Utils/ModuleUtils.h +++ b/llvm/include/llvm/Transforms/Utils/ModuleUtils.h @@ -138,10 +138,14 @@ LLVM_ABI std::string getUniqueModuleId(Module *M); /// Embed the memory buffer \p Buf into the module \p M as a global using the /// specified section name. Also provide a metadata entry to identify it in the -/// module using the same section name. -LLVM_ABI void embedBufferInModule(Module &M, MemoryBufferRef Buf, - StringRef SectionName, - Align Alignment = Align(1)); +/// module using the same section name. If \p SectionExclude is true !exclude +/// is applied to the global in order to apply necessary linkage flags to +/// exclude the section from a link. If false, apply !metadata_section_kind +/// which results in no additional section linkage flags. +LLVM_ABI GlobalVariable *embedBufferInModule(Module &M, MemoryBufferRef Buf, + StringRef SectionName, + Align Alignment = Align(1), + bool SectionExclude = true); /// Lower all calls to ifuncs by replacing uses with indirect calls loaded out /// of a global table initialized in a global constructor. This will introduce diff --git a/llvm/lib/Transforms/Utils/ModuleUtils.cpp b/llvm/lib/Transforms/Utils/ModuleUtils.cpp index 2976ebf46c9b7..71e9c70cbf9a5 100644 --- a/llvm/lib/Transforms/Utils/ModuleUtils.cpp +++ b/llvm/lib/Transforms/Utils/ModuleUtils.cpp @@ -381,8 +381,10 @@ std::string llvm::getUniqueModuleId(Module *M) { return ("." + Str).str(); } -void llvm::embedBufferInModule(Module &M, MemoryBufferRef Buf, - StringRef SectionName, Align Alignment) { +GlobalVariable *llvm::embedBufferInModule(Module &M, MemoryBufferRef Buf, + StringRef SectionName, + Align Alignment, + bool SectionExclude) { // Embed the memory buffer into the module. Constant *ModuleConstant = ConstantDataArray::get( M.getContext(), ArrayRef(Buf.getBufferStart(), Buf.getBufferSize())); @@ -396,11 +398,16 @@ void llvm::embedBufferInModule(Module &M, MemoryBufferRef Buf, NamedMDNode *MD = M.getOrInsertNamedMetadata("llvm.embedded.objects"); Metadata *MDVals[] = {ConstantAsMetadata::get(GV), MDString::get(Ctx, SectionName)}; - MD->addOperand(llvm::MDNode::get(Ctx, MDVals)); - GV->setMetadata(LLVMContext::MD_exclude, llvm::MDNode::get(Ctx, {})); + + if (SectionExclude) + GV->setMetadata(LLVMContext::MD_exclude, llvm::MDNode::get(Ctx, {})); + else + GV->setMetadata(LLVMContext::MD_metadata_section_kind, + llvm::MDNode::get(Ctx, {})); appendToCompilerUsed(M, GV); + return GV; } bool llvm::lowerGlobalIFuncUsersAsGlobalCtor( >From 34a2485dffb86d8edaae70103d354f0a08c827b6 Mon Sep 17 00:00:00 2001 From: Orlando Cazalet-Hyams <[email protected]> Date: Fri, 17 Jul 2026 16:00:54 +0100 Subject: [PATCH 6/6] fixup clang patch: update tests --- .../{attr-outer-no-specialization.c => attr-outer-noipa.c} | 2 +- .../test/DebugInfo/DynamicDebugging/attr-outer-nooutline.c | 7 +++++++ clang/test/DebugInfo/DynamicDebugging/embed.c | 4 ++-- clang/test/DebugInfo/DynamicDebugging/inner-attrs.c | 6 ++++-- 4 files changed, 14 insertions(+), 5 deletions(-) rename clang/test/DebugInfo/DynamicDebugging/{attr-outer-no-specialization.c => attr-outer-noipa.c} (82%) create mode 100644 clang/test/DebugInfo/DynamicDebugging/attr-outer-nooutline.c diff --git a/clang/test/DebugInfo/DynamicDebugging/attr-outer-no-specialization.c b/clang/test/DebugInfo/DynamicDebugging/attr-outer-noipa.c similarity index 82% rename from clang/test/DebugInfo/DynamicDebugging/attr-outer-no-specialization.c rename to clang/test/DebugInfo/DynamicDebugging/attr-outer-noipa.c index 94fe0b7499df0..e728d3682563b 100644 --- a/clang/test/DebugInfo/DynamicDebugging/attr-outer-no-specialization.c +++ b/clang/test/DebugInfo/DynamicDebugging/attr-outer-noipa.c @@ -3,5 +3,5 @@ // RUN: | FileCheck %s // CHECK: define dso_local i32 @f() #0 -// CHECK: attributes #0 = {{{.*}}"no-func-spec"{{.*}}} +// CHECK: attributes #0 = {{{.*}}noipa{{.*}}} int f() { return 0; } diff --git a/clang/test/DebugInfo/DynamicDebugging/attr-outer-nooutline.c b/clang/test/DebugInfo/DynamicDebugging/attr-outer-nooutline.c new file mode 100644 index 0000000000000..d9045de521e98 --- /dev/null +++ b/clang/test/DebugInfo/DynamicDebugging/attr-outer-nooutline.c @@ -0,0 +1,7 @@ +// RUN: %clang -cc1 -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o - \ +// RUN: -emit-llvm --discard-dynamic-debugging-debug-module \ +// RUN: | FileCheck %s + +// CHECK: define dso_local i32 @f() #0 +// CHECK: attributes #0 = {{{.*}}nooutline{{.*}}} +int f() { return 0; } diff --git a/clang/test/DebugInfo/DynamicDebugging/embed.c b/clang/test/DebugInfo/DynamicDebugging/embed.c index 2cb665ad0d00d..280993ef49d17 100644 --- a/clang/test/DebugInfo/DynamicDebugging/embed.c +++ b/clang/test/DebugInfo/DynamicDebugging/embed.c @@ -1,7 +1,7 @@ // REQUIRES: x86-registered-target // Requires X86 as this test runs the codegen pipeline for the debug module. -// RUN: %clang -cc1 -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o %t --save-dynamic-debugging-temps -// RUN: FileCheck %s < %t.dyndbg.2.outer.ll +// RUN: %clang -cc1 -emit-llvm -triple %itanium_abi_triple %s -debug-info-kind=limited -fdynamic-debugging -o - \ +// RUN: | FileCheck %s // Test that a dynamic debugging section is embedded in the outer module. Note // that !exclude is ignored by LLVM as this section's flags are chosen based diff --git a/clang/test/DebugInfo/DynamicDebugging/inner-attrs.c b/clang/test/DebugInfo/DynamicDebugging/inner-attrs.c index 7f7cc849d7776..e5f3a964cd109 100644 --- a/clang/test/DebugInfo/DynamicDebugging/inner-attrs.c +++ b/clang/test/DebugInfo/DynamicDebugging/inner-attrs.c @@ -21,5 +21,7 @@ __attribute__((minsize)) void b() { } // INNER: declare dso_local void @b() #2 // INNER: attributes #0 = { noinline nounwind optnone " /// Inner's references to outer's functions keep their original attributes. -// INNER: attributes #1 = { alwaysinline nounwind optsize " -// INNER: attributes #2 = { minsize nounwind optsize " +/// Note that noipa and nooutline are added to the outer definition before +/// copying the decl to Inner. +// INNER: attributes #1 = { alwaysinline noipa nooutline nounwind optsize " +// INNER: attributes #2 = { minsize noipa nooutline nounwind optsize " _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
