Files
rust/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs
T
Jeremy Fitzhardinge 5f29f11a4d Add -Zannotate-moves for profiler visibility of move/copy operations
This implements a new unstable compiler flag `-Zannotate-moves` that makes
move and copy operations visible in profilers by creating synthetic debug
information. This is achieved with zero runtime cost by manipulating debug
info scopes to make moves/copies appear as calls to `compiler_move<T, SIZE>`
and `compiler_copy<T, SIZE>` marker functions in profiling tools.

This allows developers to identify expensive move/copy operations in their
code using standard profiling tools, without requiring specialized tooling
or runtime instrumentation.

The implementation works at codegen time. When processing MIR operands
(`Operand::Move` and `Operand::Copy`), the codegen creates an `OperandRef`
with an optional `move_annotation` field containing an `Instance` of the
appropriate profiling marker function. When storing the operand,
`store_with_annotation()` wraps the store operation in a synthetic debug
scope that makes it appear inlined from the marker.

Two marker functions (`compiler_move` and `compiler_copy`) are defined
in `library/core/src/profiling.rs`. These are never actually called -
they exist solely as debug info anchors.

Operations are only annotated if the type:
   - Meets the size threshold (default: 65 bytes, configurable via
     `-Zannotate-moves=SIZE`)
   - Has a non-scalar backend representation (scalars use registers,
     not memcpy)

This has a very small size impact on object file size. With the default
limit it's well under 0.1%, and even with a very small limit of 8 bytes
it's still ~1.5%. This could be enabled by default.
2025-11-06 15:39:45 -08:00

113 lines
3.8 KiB
Rust

use std::ops::Range;
use rustc_abi::Size;
use rustc_middle::mir;
use rustc_middle::ty::{ExistentialTraitRef, Instance, Ty};
use rustc_span::{SourceFile, Span, Symbol};
use rustc_target::callconv::FnAbi;
use super::BackendTypes;
use crate::mir::debuginfo::{FunctionDebugContext, VariableKind};
pub trait DebugInfoCodegenMethods<'tcx>: BackendTypes {
fn create_vtable_debuginfo(
&self,
ty: Ty<'tcx>,
trait_ref: Option<ExistentialTraitRef<'tcx>>,
vtable: Self::Value,
);
/// Creates the function-specific debug context.
///
/// Returns the FunctionDebugContext for the function which holds state needed
/// for debug info creation, if it is enabled.
fn create_function_debug_context(
&self,
instance: Instance<'tcx>,
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
llfn: Self::Function,
mir: &mir::Body<'tcx>,
) -> Option<FunctionDebugContext<'tcx, Self::DIScope, Self::DILocation>>;
// FIXME(eddyb) find a common convention for all of the debuginfo-related
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
fn dbg_scope_fn(
&self,
instance: Instance<'tcx>,
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
maybe_definition_llfn: Option<Self::Function>,
) -> Self::DIScope;
fn dbg_loc(
&self,
scope: Self::DIScope,
inlined_at: Option<Self::DILocation>,
span: Span,
) -> Self::DILocation;
fn extend_scope_to_file(
&self,
scope_metadata: Self::DIScope,
file: &SourceFile,
) -> Self::DIScope;
fn debuginfo_finalize(&self);
// FIXME(eddyb) find a common convention for all of the debuginfo-related
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
fn create_dbg_var(
&self,
variable_name: Symbol,
variable_type: Ty<'tcx>,
scope_metadata: Self::DIScope,
variable_kind: VariableKind,
span: Span,
) -> Self::DIVariable;
}
pub trait DebugInfoBuilderMethods<'tcx>: BackendTypes {
// FIXME(eddyb) find a common convention for all of the debuginfo-related
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
fn dbg_var_addr(
&mut self,
dbg_var: Self::DIVariable,
dbg_loc: Self::DILocation,
variable_alloca: Self::Value,
direct_offset: Size,
// NB: each offset implies a deref (i.e. they're steps in a pointer chain).
indirect_offsets: &[Size],
// Byte range in the `dbg_var` covered by this fragment,
// if this is a fragment of a composite `DIVariable`.
fragment: &Option<Range<Size>>,
);
fn dbg_var_value(
&mut self,
dbg_var: Self::DIVariable,
dbg_loc: Self::DILocation,
value: Self::Value,
direct_offset: Size,
// NB: each offset implies a deref (i.e. they're steps in a pointer chain).
indirect_offsets: &[Size],
// Byte range in the `dbg_var` covered by this fragment,
// if this is a fragment of a composite `DIVariable`.
fragment: &Option<Range<Size>>,
);
fn set_dbg_loc(&mut self, dbg_loc: Self::DILocation);
fn clear_dbg_loc(&mut self);
fn insert_reference_to_gdb_debug_scripts_section_global(&mut self);
fn set_var_name(&mut self, value: Self::Value, name: &str);
/// Hook to allow move/copy operations to be annotated for profiling.
///
/// The `instance` parameter should be the monomorphized instance of the
/// `compiler_move` or `compiler_copy` function with the actual type and size.
///
/// Default implementation does no annotation (just executes the closure).
fn with_move_annotation<R>(
&mut self,
_instance: Instance<'tcx>,
f: impl FnOnce(&mut Self) -> R,
) -> R {
f(self)
}
}