From 52d436e777b28c165b2b9ff745b1155861e0905f Mon Sep 17 00:00:00 2001 From: Scott Young Date: Sun, 29 Mar 2026 01:13:50 -0400 Subject: [PATCH] debuginfo: emit DW_TAG_call_site entries on optimized builds --- .../rustc_codegen_llvm/src/debuginfo/mod.rs | 9 +++++++- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 3 ++- .../debuginfo-callsite-flag-noopt.rs | 23 +++++++++++++++++++ tests/codegen-llvm/debuginfo-callsite-flag.rs | 21 +++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/codegen-llvm/debuginfo-callsite-flag-noopt.rs create mode 100644 tests/codegen-llvm/debuginfo-callsite-flag.rs diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index 580b7a89484d4..a5ca5ed44a3a1 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -182,6 +182,10 @@ impl<'ll, 'tcx> DebugInfoBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { } if self.sess().opts.optimize != config::OptLevel::No { spflags |= DISPFlags::SPFlagOptimized; + // Emit DW_TAG_call_site entries for optimized builds, matching + // Clang's behavior. At -O0 no tail-call optimization occurs, so + // the debugger can reconstruct the call stack without them. + flags |= DIFlags::FlagAllCallsDescribed; } if let Some((id, _)) = tcx.entry_fn(()) { if id == def_id { @@ -193,6 +197,9 @@ impl<'ll, 'tcx> DebugInfoBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { // LLVM LTO can't unify type definitions when a child DIE is a full subprogram definition. // When we use this `decl` below, the subprogram definition gets created at the CU level // with a DW_AT_specification pointing back to the type's declaration. + // FlagAllCallsDescribed cannot appear on the method declaration DIE + // because it has no body, which LLVM's verifier rejects. + let decl_flags = flags & !DIFlags::FlagAllCallsDescribed; let decl = is_method.then(|| unsafe { llvm::LLVMRustDIBuilderCreateMethod( DIB(self), @@ -204,7 +211,7 @@ impl<'ll, 'tcx> DebugInfoBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { file_metadata, loc.line, function_type_metadata, - flags, + decl_flags, spflags & !DISPFlags::SPFlagDefinition, template_parameters, ) diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index bd770d286851b..4d06559093937 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -806,6 +806,7 @@ pub(crate) mod debuginfo { const FlagNonTrivial = (1 << 26); const FlagBigEndian = (1 << 27); const FlagLittleEndian = (1 << 28); + const FlagAllCallsDescribed = (1 << 29); } } diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 3b8e6f6415365..becd07ae39505 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -811,6 +811,7 @@ ASSERT_DIFLAG_VALUE(FlagThunk, 1 << 25); ASSERT_DIFLAG_VALUE(FlagNonTrivial, 1 << 26); ASSERT_DIFLAG_VALUE(FlagBigEndian, 1 << 27); ASSERT_DIFLAG_VALUE(FlagLittleEndian, 1 << 28); +static_assert(DINode::DIFlags::FlagAllCallsDescribed == (1 << 29)); ASSERT_DIFLAG_VALUE(FlagIndirectVirtualBase, (1 << 2) | (1 << 5)); #undef ASSERT_DIFLAG_VALUE @@ -823,7 +824,7 @@ ASSERT_DIFLAG_VALUE(FlagIndirectVirtualBase, (1 << 2) | (1 << 5)); // to copying each bit/subvalue. static DINode::DIFlags fromRust(LLVMDIFlags Flags) { // Check that all set bits are covered by the static assertions above. - const unsigned UNKNOWN_BITS = (1 << 31) | (1 << 30) | (1 << 29) | (1 << 21); + const unsigned UNKNOWN_BITS = (1 << 31) | (1 << 30) | (1 << 21); if (Flags & UNKNOWN_BITS) { report_fatal_error("bad LLVMDIFlags"); } diff --git a/tests/codegen-llvm/debuginfo-callsite-flag-noopt.rs b/tests/codegen-llvm/debuginfo-callsite-flag-noopt.rs new file mode 100644 index 0000000000000..d7e9930074daf --- /dev/null +++ b/tests/codegen-llvm/debuginfo-callsite-flag-noopt.rs @@ -0,0 +1,23 @@ +// Check that DIFlagAllCallsDescribed is NOT set in unoptimized builds. +// At -O0 no tail-call optimization occurs, so the debugger can +// reconstruct the call stack without DW_TAG_call_site entries. + +//@ ignore-msvc (CodeView does not use DIFlagAllCallsDescribed) +//@ compile-flags: -C debuginfo=2 -C opt-level=0 -C no-prepopulate-passes + +#![crate_type = "lib"] + +// CHECK: {{.*}}DISubprogram{{.*}}name: "foo" +// CHECK-NOT: DIFlagAllCallsDescribed + +#[no_mangle] +#[inline(never)] +pub fn foo(x: i32) -> i32 { + bar(x + 1) +} + +#[no_mangle] +#[inline(never)] +pub fn bar(x: i32) -> i32 { + x * 2 +} diff --git a/tests/codegen-llvm/debuginfo-callsite-flag.rs b/tests/codegen-llvm/debuginfo-callsite-flag.rs new file mode 100644 index 0000000000000..04e99eba34613 --- /dev/null +++ b/tests/codegen-llvm/debuginfo-callsite-flag.rs @@ -0,0 +1,21 @@ +// Check that DIFlagAllCallsDescribed is set on subprogram definitions +// in optimized builds, so LLVM emits DW_TAG_call_site entries. + +//@ ignore-msvc (CodeView does not use DIFlagAllCallsDescribed) +//@ compile-flags: -C debuginfo=2 -C opt-level=1 -C no-prepopulate-passes + +#![crate_type = "lib"] + +// CHECK: {{.*}}DISubprogram{{.*}}name: "foo"{{.*}}DIFlagAllCallsDescribed{{.*}} + +#[no_mangle] +#[inline(never)] +pub fn foo(x: i32) -> i32 { + bar(x + 1) +} + +#[no_mangle] +#[inline(never)] +pub fn bar(x: i32) -> i32 { + x * 2 +}