Commit bbb6f68

Browse files
committed
Auto merge of #147803 - jsgf:move-copy-codegen, r=madsmtm,saethlin
Add -Zannotate-moves for profiler visibility of move/copy operations (codegen) **Note:** this is an alternative implementation of #147206; rather than being a MIR transform, it adds the annotations closer to codegen. It's functionally the same but the implementation is lower impact and it could be more correct. --- 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: - We're generating debug info and the feature is enabled. - Meets the size threshold (default: 65 bytes, configurable via `-Zannotate-moves=SIZE`), and is non-zero - Has a memory representation 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.
2 parents 4e0baae + 5f29f11 commit bbb6f68

28 files changed

Lines changed: 897 additions & 48 deletions

File tree

‎compiler/rustc_codegen_gcc/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
10691069
OperandValue::Ref(place.val)
10701070
};
10711071

1072-
OperandRef{ val,layout: place.layout}
1072+
OperandRef{ val,layout: place.layout,move_annotation:None}
10731073
}
10741074

10751075
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_gcc/src/debuginfo.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::context::CodegenCx;
1919
pub(super)constUNKNOWN_LINE_NUMBER:u32 = 0;
2020
pub(super)constUNKNOWN_COLUMN_NUMBER:u32 = 0;
2121

22-
impl<'a,'gcc,'tcx>DebugInfoBuilderMethodsforBuilder<'a,'gcc,'tcx>{
22+
impl<'a,'gcc,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'a,'gcc,'tcx>{
2323
// FIXME(eddyb) find a common convention for all of the debuginfo-related
2424
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
2525
fndbg_var_addr(

‎compiler/rustc_codegen_llvm/src/abi.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
253253
);
254254
bx.lifetime_end(llscratch, scratch_size);
255255
}
256-
_ => {
256+
PassMode::Pair(..) | PassMode::Direct{ .. } => {
257257
OperandRef::from_immediate_or_packed_pair(bx, val,self.layout).val.store(bx, dst);
258258
}
259259
}

‎compiler/rustc_codegen_llvm/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
751751
OperandValue::Ref(place.val)
752752
};
753753

754-
OperandRef{ val,layout: place.layout}
754+
OperandRef{ val,layout: place.layout,move_annotation:None}
755755
}
756756

757757
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_llvm/src/debuginfo/mod.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> {
146146
}
147147
}
148148

149-
impl<'ll>DebugInfoBuilderMethodsforBuilder<'_,'ll,'_>{
149+
impl<'ll,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'_,'ll,'tcx>{
150150
// FIXME(eddyb) find a common convention for all of the debuginfo-related
151151
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
152152
fndbg_var_addr(
@@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
284284
llvm::set_value_name(value, name.as_bytes());
285285
}
286286
}
287+
288+
/// Annotate move/copy operations with debug info for profiling.
289+
///
290+
/// This creates a temporary debug scope that makes the move/copy appear as an inlined call to
291+
/// `compiler_move<T, SIZE>()` or `compiler_copy<T, SIZE>()`. The provided closure is executed
292+
/// with this temporary debug location active.
293+
///
294+
/// The `instance` parameter should be the monomorphized instance of the `compiler_move` or
295+
/// `compiler_copy` function with the actual type and size.
296+
fnwith_move_annotation<R>(
297+
&mutself,
298+
instance: ty::Instance<'tcx>,
299+
f:implFnOnce(&mutSelf) -> R,
300+
) -> R{
301+
// Save the current debug location
302+
let saved_loc = self.get_dbg_loc();
303+
304+
// Create a DIScope for the compiler_move/compiler_copy function
305+
// We use the function's FnAbi for debug info generation
306+
let fn_abi = self
307+
.cx()
308+
.tcx
309+
.fn_abi_of_instance(
310+
self.cx().typing_env().as_query_input((instance, ty::List::empty())),
311+
)
312+
.unwrap();
313+
314+
let di_scope = self.cx().dbg_scope_fn(instance, fn_abi,None);
315+
316+
// Create an inlined debug location:
317+
// - scope: the compiler_move/compiler_copy function
318+
// - inlined_at: the current location (where the move/copy actually occurs)
319+
// - span: use the function's definition span
320+
let fn_span = self.cx().tcx.def_span(instance.def_id());
321+
let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span);
322+
323+
// Set the temporary debug location
324+
self.set_dbg_loc(inlined_loc);
325+
326+
// Execute the closure (which will generate the memcpy)
327+
let result = f(self);
328+
329+
// Restore the original debug location
330+
ifletSome(loc) = saved_loc {
331+
self.set_dbg_loc(loc);
332+
}else{
333+
self.clear_dbg_loc();
334+
}
335+
336+
result
337+
}
287338
}
288339

289340
/// A source code location used to generate debug information.

‎compiler/rustc_codegen_ssa/src/mir/block.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
557557
let op = matchself.locals[mir::RETURN_PLACE]{
558558
LocalRef::Operand(op) => op,
559559
LocalRef::PendingOperand => bug!("use of return before def"),
560-
LocalRef::Place(cg_place) => {
561-
OperandRef{val:Ref(cg_place.val),layout: cg_place.layout}
562-
}
560+
LocalRef::Place(cg_place) => OperandRef{
561+
val:Ref(cg_place.val),
562+
layout: cg_place.layout,
563+
move_annotation:None,
564+
},
563565
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
564566
};
565567
let llslot = match op.val{
@@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11551157
| (&mir::Operand::Constant(_),Ref(PlaceValue{llextra:None, .. })) => {
11561158
let tmp = PlaceRef::alloca(bx, op.layout);
11571159
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
1158-
op.val.store(bx, tmp);
1160+
op.store_with_annotation(bx, tmp);
11591161
op.val = Ref(tmp.val);
11601162
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
11611163
}
@@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15631565
};
15641566
let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align);
15651567
bx.lifetime_start(scratch.llval, arg.layout.size);
1566-
op.val.store(bx, scratch.with_type(arg.layout));
1568+
op.store_with_annotation(bx, scratch.with_type(arg.layout));
15671569
lifetime_ends_after_call.push((scratch.llval, arg.layout.size));
15681570
(scratch.llval, scratch.align,true)
15691571
}
15701572
PassMode::Cast{ .. } => {
15711573
let scratch = PlaceRef::alloca(bx, arg.layout);
1572-
op.val.store(bx, scratch);
1574+
op.store_with_annotation(bx, scratch);
15731575
(scratch.val.llval, scratch.val.align,true)
15741576
}
15751577
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi,false),

‎compiler/rustc_codegen_ssa/src/mir/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
480480
returnlocal(OperandRef{
481481
val:OperandValue::Pair(a, b),
482482
layout: arg.layout,
483+
move_annotation:None,
483484
});
484485
}
485486
_ => {}
@@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
552553
fx.caller_location = Some(OperandRef{
553554
val:OperandValue::Immediate(bx.get_param(llarg_idx)),
554555
layout: arg.layout,
556+
move_annotation:None,
555557
});
556558
}
557559

‎compiler/rustc_codegen_ssa/src/mir/operand.rs‎

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use rustc_abi as abi;
55
use rustc_abi::{
66
Align,BackendRepr,FIRST_VARIANT,FieldIdx,Primitive,Size,TagEncoding,VariantIdx,Variants,
77
};
8+
use rustc_hir::LangItem;
89
use rustc_middle::mir::interpret::{Pointer,Scalar, alloc_range};
910
use rustc_middle::mir::{self,ConstValue};
10-
use rustc_middle::ty::Ty;
1111
use rustc_middle::ty::layout::{LayoutOf,TyAndLayout};
12+
use rustc_middle::ty::{self,Ty};
1213
use rustc_middle::{bug, span_bug};
13-
use rustc_session::config::OptLevel;
14+
use rustc_session::config::{AnnotateMoves,DebugInfo,OptLevel};
1415
use tracing::{debug, instrument};
1516

1617
usesuper::place::{PlaceRef,PlaceValue};
@@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> {
131132

132133
/// The layout of value, based on its Rust type.
133134
publayout:TyAndLayout<'tcx>,
135+
136+
/// Annotation for profiler visibility of move/copy operations.
137+
/// When set, the store operation should appear as an inlined call to this function.
138+
pubmove_annotation:Option<ty::Instance<'tcx>>,
134139
}
135140

136141
impl<V:CodegenObject> fmt::DebugforOperandRef<'_,V>{
@@ -142,7 +147,7 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
142147
impl<'a,'tcx,V:CodegenObject>OperandRef<'tcx,V>{
143148
pubfnzero_sized(layout:TyAndLayout<'tcx>) -> OperandRef<'tcx,V>{
144149
assert!(layout.is_zst());
145-
OperandRef{val:OperandValue::ZeroSized, layout }
150+
OperandRef{val:OperandValue::ZeroSized, layout,move_annotation:None}
146151
}
147152

148153
pub(crate)fnfrom_const<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
180185
}
181186
};
182187

183-
OperandRef{ val, layout }
188+
OperandRef{ val, layout,move_annotation:None}
184189
}
185190

186191
fnfrom_const_alloc<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
214219
let size = s.size(bx);
215220
assert_eq!(size, layout.size,"abi::Scalar size does not match layout size");
216221
let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout));
217-
OperandRef{val:OperandValue::Immediate(val), layout }
222+
OperandRef{val:OperandValue::Immediate(val), layout,move_annotation:None}
218223
}
219224
BackendRepr::ScalarPair(
220225
a @ abi::Scalar::Initialized{ .. },
@@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
235240
b,
236241
bx.scalar_pair_element_backend_type(layout,1,true),
237242
);
238-
OperandRef{val:OperandValue::Pair(a_val, b_val), layout }
243+
OperandRef{val:OperandValue::Pair(a_val, b_val), layout,move_annotation:None}
239244
}
240245
_ if layout.is_zst() => OperandRef::zero_sized(layout),
241246
_ => {
@@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
285290
self.val.deref(layout.align.abi).with_type(layout)
286291
}
287292

293+
/// Store this operand into a place, applying move/copy annotation if present.
294+
///
295+
/// This is the preferred method for storing operands, as it automatically
296+
/// applies profiler annotations for tracked move/copy operations.
297+
pubfnstore_with_annotation<Bx:BuilderMethods<'a,'tcx,Value = V>>(
298+
self,
299+
bx:&mutBx,
300+
dest:PlaceRef<'tcx,V>,
301+
){
302+
ifletSome(instance) = self.move_annotation{
303+
bx.with_move_annotation(instance, |bx| self.val.store(bx, dest))
304+
}else{
305+
self.val.store(bx, dest)
306+
}
307+
}
308+
288309
/// If this operand is a `Pair`, we return an aggregate with the two values.
289310
/// For other cases, see `immediate`.
290311
pubfnimmediate_or_packed_pair<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
320341
}else{
321342
OperandValue::Immediate(llval)
322343
};
323-
OperandRef{ val, layout }
344+
OperandRef{ val, layout,move_annotation:None}
324345
}
325346

326347
pub(crate)fnextract_field<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
388409
})
389410
};
390411

391-
OperandRef{ val,layout: field }
412+
OperandRef{ val,layout: field,move_annotation:None}
392413
}
393414

394415
/// Obtain the actual discriminant of a value.
@@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
828849
}
829850
},
830851
};
831-
OperandRef{ val, layout }
852+
OperandRef{ val, layout,move_annotation:None}
832853
}
833854
}
834855

856+
/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache
857+
/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only
858+
/// annotate copies larger than this.
859+
constMOVE_ANNOTATION_DEFAULT_LIMIT:u64 = 65;
860+
835861
impl<'a,'tcx,V:CodegenObject>OperandValue<V>{
836862
/// Returns an `OperandValue` that's generally UB to use in any way.
837863
///
@@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
961987
abi::Variants::Single{ index: vidx },
962988
);
963989
let layout = o.layout.for_variant(bx.cx(), vidx);
964-
o = OperandRef{val: o.val, layout}
990+
o = OperandRef{layout, ..o}
965991
}
966992
_ => returnNone,
967993
}
@@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10141040

10151041
match*operand {
10161042
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
1017-
self.codegen_consume(bx, place.as_ref())
1043+
let kind = match operand {
1044+
mir::Operand::Move(_) => LangItem::CompilerMove,
1045+
mir::Operand::Copy(_) => LangItem::CompilerCopy,
1046+
_ => unreachable!(),
1047+
};
1048+
1049+
// Check if we should annotate this move/copy for profiling
1050+
let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind);
1051+
1052+
OperandRef{ move_annotation, ..self.codegen_consume(bx, place.as_ref())}
10181053
}
10191054

10201055
mir::Operand::Constant(ref constant) => {
@@ -1030,11 +1065,76 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10301065
returnOperandRef{
10311066
val:OperandValue::Immediate(llval),
10321067
layout: bx.layout_of(ty),
1068+
move_annotation:None,
10331069
};
10341070
}
10351071
}
10361072
self.eval_mir_constant_to_operand(bx, constant)
10371073
}
10381074
}
10391075
}
1076+
1077+
/// Creates an `Instance` for annotating a move/copy operation at codegen time.
1078+
///
1079+
/// Returns `Some(instance)` if the operation should be annotated with debug info, `None`
1080+
/// otherwise. The instance represents a monomorphized `compiler_move<T, SIZE>` or
1081+
/// `compiler_copy<T, SIZE>` function that can be used to create debug scopes.
1082+
///
1083+
/// There are a number of conditions that must be met for an annotation to be created, but aside
1084+
/// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is
1085+
/// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also
1086+
/// that the underlying representation of the type is in memory.
1087+
fnmove_copy_annotation_instance(
1088+
&self,
1089+
bx:&Bx,
1090+
place: mir::PlaceRef<'tcx>,
1091+
kind:LangItem,
1092+
) -> Option<ty::Instance<'tcx>>{
1093+
let tcx = bx.tcx();
1094+
let sess = tcx.sess;
1095+
1096+
// Skip if we're not generating debuginfo
1097+
if sess.opts.debuginfo == DebugInfo::None{
1098+
returnNone;
1099+
}
1100+
1101+
// Check if annotation is enabled and get size limit (otherwise skip)
1102+
let size_limit = match sess.opts.unstable_opts.annotate_moves{
1103+
AnnotateMoves::Disabled => returnNone,
1104+
AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT,
1105+
AnnotateMoves::Enabled(Some(limit)) => limit,
1106+
};
1107+
1108+
let ty = self.monomorphized_place_ty(place);
1109+
let layout = bx.cx().layout_of(ty);
1110+
let ty_size = layout.size.bytes();
1111+
1112+
// Only annotate if type has a memory representation and exceeds size limit (and has a
1113+
// non-zero size)
1114+
if layout.is_zst()
1115+
|| ty_size < size_limit
1116+
|| !matches!(layout.backend_repr,BackendRepr::Memory{ .. })
1117+
{
1118+
returnNone;
1119+
}
1120+
1121+
// Look up the DefId for compiler_move or compiler_copy lang item
1122+
let def_id = tcx.lang_items().get(kind)?;
1123+
1124+
// Create generic args: compiler_move<T, SIZE> or compiler_copy<T, SIZE>
1125+
let size_const = ty::Const::from_target_usize(tcx, ty_size);
1126+
let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]);
1127+
1128+
// Create the Instance
1129+
let typing_env = self.mir.typing_env(tcx);
1130+
let instance = ty::Instance::expect_resolve(
1131+
tcx,
1132+
typing_env,
1133+
def_id,
1134+
generic_args,
1135+
rustc_span::DUMMY_SP,// span only used for error messages
1136+
);
1137+
1138+
Some(instance)
1139+
}
10401140
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit bbb6f68

Browse files
committed
Auto merge of #147803 - jsgf:move-copy-codegen, r=madsmtm,saethlin
Add -Zannotate-moves for profiler visibility of move/copy operations (codegen) **Note:** this is an alternative implementation of #147206; rather than being a MIR transform, it adds the annotations closer to codegen. It's functionally the same but the implementation is lower impact and it could be more correct. --- 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: - We're generating debug info and the feature is enabled. - Meets the size threshold (default: 65 bytes, configurable via `-Zannotate-moves=SIZE`), and is non-zero - Has a memory representation 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.
2 parents 4e0baae + 5f29f11 commit bbb6f68

28 files changed

Lines changed: 897 additions & 48 deletions

File tree

‎compiler/rustc_codegen_gcc/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
10691069
OperandValue::Ref(place.val)
10701070
};
10711071

1072-
OperandRef{ val,layout: place.layout}
1072+
OperandRef{ val,layout: place.layout,move_annotation:None}
10731073
}
10741074

10751075
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_gcc/src/debuginfo.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::context::CodegenCx;
1919
pub(super)constUNKNOWN_LINE_NUMBER:u32 = 0;
2020
pub(super)constUNKNOWN_COLUMN_NUMBER:u32 = 0;
2121

22-
impl<'a,'gcc,'tcx>DebugInfoBuilderMethodsforBuilder<'a,'gcc,'tcx>{
22+
impl<'a,'gcc,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'a,'gcc,'tcx>{
2323
// FIXME(eddyb) find a common convention for all of the debuginfo-related
2424
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
2525
fndbg_var_addr(

‎compiler/rustc_codegen_llvm/src/abi.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
253253
);
254254
bx.lifetime_end(llscratch, scratch_size);
255255
}
256-
_ => {
256+
PassMode::Pair(..) | PassMode::Direct{ .. } => {
257257
OperandRef::from_immediate_or_packed_pair(bx, val,self.layout).val.store(bx, dst);
258258
}
259259
}

‎compiler/rustc_codegen_llvm/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
751751
OperandValue::Ref(place.val)
752752
};
753753

754-
OperandRef{ val,layout: place.layout}
754+
OperandRef{ val,layout: place.layout,move_annotation:None}
755755
}
756756

757757
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_llvm/src/debuginfo/mod.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> {
146146
}
147147
}
148148

149-
impl<'ll>DebugInfoBuilderMethodsforBuilder<'_,'ll,'_>{
149+
impl<'ll,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'_,'ll,'tcx>{
150150
// FIXME(eddyb) find a common convention for all of the debuginfo-related
151151
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
152152
fndbg_var_addr(
@@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
284284
llvm::set_value_name(value, name.as_bytes());
285285
}
286286
}
287+
288+
/// Annotate move/copy operations with debug info for profiling.
289+
///
290+
/// This creates a temporary debug scope that makes the move/copy appear as an inlined call to
291+
/// `compiler_move<T, SIZE>()` or `compiler_copy<T, SIZE>()`. The provided closure is executed
292+
/// with this temporary debug location active.
293+
///
294+
/// The `instance` parameter should be the monomorphized instance of the `compiler_move` or
295+
/// `compiler_copy` function with the actual type and size.
296+
fnwith_move_annotation<R>(
297+
&mutself,
298+
instance: ty::Instance<'tcx>,
299+
f:implFnOnce(&mutSelf) -> R,
300+
) -> R{
301+
// Save the current debug location
302+
let saved_loc = self.get_dbg_loc();
303+
304+
// Create a DIScope for the compiler_move/compiler_copy function
305+
// We use the function's FnAbi for debug info generation
306+
let fn_abi = self
307+
.cx()
308+
.tcx
309+
.fn_abi_of_instance(
310+
self.cx().typing_env().as_query_input((instance, ty::List::empty())),
311+
)
312+
.unwrap();
313+
314+
let di_scope = self.cx().dbg_scope_fn(instance, fn_abi,None);
315+
316+
// Create an inlined debug location:
317+
// - scope: the compiler_move/compiler_copy function
318+
// - inlined_at: the current location (where the move/copy actually occurs)
319+
// - span: use the function's definition span
320+
let fn_span = self.cx().tcx.def_span(instance.def_id());
321+
let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span);
322+
323+
// Set the temporary debug location
324+
self.set_dbg_loc(inlined_loc);
325+
326+
// Execute the closure (which will generate the memcpy)
327+
let result = f(self);
328+
329+
// Restore the original debug location
330+
ifletSome(loc) = saved_loc {
331+
self.set_dbg_loc(loc);
332+
}else{
333+
self.clear_dbg_loc();
334+
}
335+
336+
result
337+
}
287338
}
288339

289340
/// A source code location used to generate debug information.

‎compiler/rustc_codegen_ssa/src/mir/block.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
557557
let op = matchself.locals[mir::RETURN_PLACE]{
558558
LocalRef::Operand(op) => op,
559559
LocalRef::PendingOperand => bug!("use of return before def"),
560-
LocalRef::Place(cg_place) => {
561-
OperandRef{val:Ref(cg_place.val),layout: cg_place.layout}
562-
}
560+
LocalRef::Place(cg_place) => OperandRef{
561+
val:Ref(cg_place.val),
562+
layout: cg_place.layout,
563+
move_annotation:None,
564+
},
563565
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
564566
};
565567
let llslot = match op.val{
@@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11551157
| (&mir::Operand::Constant(_),Ref(PlaceValue{llextra:None, .. })) => {
11561158
let tmp = PlaceRef::alloca(bx, op.layout);
11571159
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
1158-
op.val.store(bx, tmp);
1160+
op.store_with_annotation(bx, tmp);
11591161
op.val = Ref(tmp.val);
11601162
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
11611163
}
@@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15631565
};
15641566
let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align);
15651567
bx.lifetime_start(scratch.llval, arg.layout.size);
1566-
op.val.store(bx, scratch.with_type(arg.layout));
1568+
op.store_with_annotation(bx, scratch.with_type(arg.layout));
15671569
lifetime_ends_after_call.push((scratch.llval, arg.layout.size));
15681570
(scratch.llval, scratch.align,true)
15691571
}
15701572
PassMode::Cast{ .. } => {
15711573
let scratch = PlaceRef::alloca(bx, arg.layout);
1572-
op.val.store(bx, scratch);
1574+
op.store_with_annotation(bx, scratch);
15731575
(scratch.val.llval, scratch.val.align,true)
15741576
}
15751577
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi,false),

‎compiler/rustc_codegen_ssa/src/mir/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
480480
returnlocal(OperandRef{
481481
val:OperandValue::Pair(a, b),
482482
layout: arg.layout,
483+
move_annotation:None,
483484
});
484485
}
485486
_ => {}
@@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
552553
fx.caller_location = Some(OperandRef{
553554
val:OperandValue::Immediate(bx.get_param(llarg_idx)),
554555
layout: arg.layout,
556+
move_annotation:None,
555557
});
556558
}
557559

‎compiler/rustc_codegen_ssa/src/mir/operand.rs‎

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use rustc_abi as abi;
55
use rustc_abi::{
66
Align,BackendRepr,FIRST_VARIANT,FieldIdx,Primitive,Size,TagEncoding,VariantIdx,Variants,
77
};
8+
use rustc_hir::LangItem;
89
use rustc_middle::mir::interpret::{Pointer,Scalar, alloc_range};
910
use rustc_middle::mir::{self,ConstValue};
10-
use rustc_middle::ty::Ty;
1111
use rustc_middle::ty::layout::{LayoutOf,TyAndLayout};
12+
use rustc_middle::ty::{self,Ty};
1213
use rustc_middle::{bug, span_bug};
13-
use rustc_session::config::OptLevel;
14+
use rustc_session::config::{AnnotateMoves,DebugInfo,OptLevel};
1415
use tracing::{debug, instrument};
1516

1617
usesuper::place::{PlaceRef,PlaceValue};
@@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> {
131132

132133
/// The layout of value, based on its Rust type.
133134
publayout:TyAndLayout<'tcx>,
135+
136+
/// Annotation for profiler visibility of move/copy operations.
137+
/// When set, the store operation should appear as an inlined call to this function.
138+
pubmove_annotation:Option<ty::Instance<'tcx>>,
134139
}
135140

136141
impl<V:CodegenObject> fmt::DebugforOperandRef<'_,V>{
@@ -142,7 +147,7 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
142147
impl<'a,'tcx,V:CodegenObject>OperandRef<'tcx,V>{
143148
pubfnzero_sized(layout:TyAndLayout<'tcx>) -> OperandRef<'tcx,V>{
144149
assert!(layout.is_zst());
145-
OperandRef{val:OperandValue::ZeroSized, layout }
150+
OperandRef{val:OperandValue::ZeroSized, layout,move_annotation:None}
146151
}
147152

148153
pub(crate)fnfrom_const<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
180185
}
181186
};
182187

183-
OperandRef{ val, layout }
188+
OperandRef{ val, layout,move_annotation:None}
184189
}
185190

186191
fnfrom_const_alloc<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
214219
let size = s.size(bx);
215220
assert_eq!(size, layout.size,"abi::Scalar size does not match layout size");
216221
let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout));
217-
OperandRef{val:OperandValue::Immediate(val), layout }
222+
OperandRef{val:OperandValue::Immediate(val), layout,move_annotation:None}
218223
}
219224
BackendRepr::ScalarPair(
220225
a @ abi::Scalar::Initialized{ .. },
@@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
235240
b,
236241
bx.scalar_pair_element_backend_type(layout,1,true),
237242
);
238-
OperandRef{val:OperandValue::Pair(a_val, b_val), layout }
243+
OperandRef{val:OperandValue::Pair(a_val, b_val), layout,move_annotation:None}
239244
}
240245
_ if layout.is_zst() => OperandRef::zero_sized(layout),
241246
_ => {
@@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
285290
self.val.deref(layout.align.abi).with_type(layout)
286291
}
287292

293+
/// Store this operand into a place, applying move/copy annotation if present.
294+
///
295+
/// This is the preferred method for storing operands, as it automatically
296+
/// applies profiler annotations for tracked move/copy operations.
297+
pubfnstore_with_annotation<Bx:BuilderMethods<'a,'tcx,Value = V>>(
298+
self,
299+
bx:&mutBx,
300+
dest:PlaceRef<'tcx,V>,
301+
){
302+
ifletSome(instance) = self.move_annotation{
303+
bx.with_move_annotation(instance, |bx| self.val.store(bx, dest))
304+
}else{
305+
self.val.store(bx, dest)
306+
}
307+
}
308+
288309
/// If this operand is a `Pair`, we return an aggregate with the two values.
289310
/// For other cases, see `immediate`.
290311
pubfnimmediate_or_packed_pair<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
320341
}else{
321342
OperandValue::Immediate(llval)
322343
};
323-
OperandRef{ val, layout }
344+
OperandRef{ val, layout,move_annotation:None}
324345
}
325346

326347
pub(crate)fnextract_field<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
388409
})
389410
};
390411

391-
OperandRef{ val,layout: field }
412+
OperandRef{ val,layout: field,move_annotation:None}
392413
}
393414

394415
/// Obtain the actual discriminant of a value.
@@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
828849
}
829850
},
830851
};
831-
OperandRef{ val, layout }
852+
OperandRef{ val, layout,move_annotation:None}
832853
}
833854
}
834855

856+
/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache
857+
/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only
858+
/// annotate copies larger than this.
859+
constMOVE_ANNOTATION_DEFAULT_LIMIT:u64 = 65;
860+
835861
impl<'a,'tcx,V:CodegenObject>OperandValue<V>{
836862
/// Returns an `OperandValue` that's generally UB to use in any way.
837863
///
@@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
961987
abi::Variants::Single{ index: vidx },
962988
);
963989
let layout = o.layout.for_variant(bx.cx(), vidx);
964-
o = OperandRef{val: o.val, layout}
990+
o = OperandRef{layout, ..o}
965991
}
966992
_ => returnNone,
967993
}
@@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10141040

10151041
match*operand {
10161042
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
1017-
self.codegen_consume(bx, place.as_ref())
1043+
let kind = match operand {
1044+
mir::Operand::Move(_) => LangItem::CompilerMove,
1045+
mir::Operand::Copy(_) => LangItem::CompilerCopy,
1046+
_ => unreachable!(),
1047+
};
1048+
1049+
// Check if we should annotate this move/copy for profiling
1050+
let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind);
1051+
1052+
OperandRef{ move_annotation, ..self.codegen_consume(bx, place.as_ref())}
10181053
}
10191054

10201055
mir::Operand::Constant(ref constant) => {
@@ -1030,11 +1065,76 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10301065
returnOperandRef{
10311066
val:OperandValue::Immediate(llval),
10321067
layout: bx.layout_of(ty),
1068+
move_annotation:None,
10331069
};
10341070
}
10351071
}
10361072
self.eval_mir_constant_to_operand(bx, constant)
10371073
}
10381074
}
10391075
}
1076+
1077+
/// Creates an `Instance` for annotating a move/copy operation at codegen time.
1078+
///
1079+
/// Returns `Some(instance)` if the operation should be annotated with debug info, `None`
1080+
/// otherwise. The instance represents a monomorphized `compiler_move<T, SIZE>` or
1081+
/// `compiler_copy<T, SIZE>` function that can be used to create debug scopes.
1082+
///
1083+
/// There are a number of conditions that must be met for an annotation to be created, but aside
1084+
/// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is
1085+
/// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also
1086+
/// that the underlying representation of the type is in memory.
1087+
fnmove_copy_annotation_instance(
1088+
&self,
1089+
bx:&Bx,
1090+
place: mir::PlaceRef<'tcx>,
1091+
kind:LangItem,
1092+
) -> Option<ty::Instance<'tcx>>{
1093+
let tcx = bx.tcx();
1094+
let sess = tcx.sess;
1095+
1096+
// Skip if we're not generating debuginfo
1097+
if sess.opts.debuginfo == DebugInfo::None{
1098+
returnNone;
1099+
}
1100+
1101+
// Check if annotation is enabled and get size limit (otherwise skip)
1102+
let size_limit = match sess.opts.unstable_opts.annotate_moves{
1103+
AnnotateMoves::Disabled => returnNone,
1104+
AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT,
1105+
AnnotateMoves::Enabled(Some(limit)) => limit,
1106+
};
1107+
1108+
let ty = self.monomorphized_place_ty(place);
1109+
let layout = bx.cx().layout_of(ty);
1110+
let ty_size = layout.size.bytes();
1111+
1112+
// Only annotate if type has a memory representation and exceeds size limit (and has a
1113+
// non-zero size)
1114+
if layout.is_zst()
1115+
|| ty_size < size_limit
1116+
|| !matches!(layout.backend_repr,BackendRepr::Memory{ .. })
1117+
{
1118+
returnNone;
1119+
}
1120+
1121+
// Look up the DefId for compiler_move or compiler_copy lang item
1122+
let def_id = tcx.lang_items().get(kind)?;
1123+
1124+
// Create generic args: compiler_move<T, SIZE> or compiler_copy<T, SIZE>
1125+
let size_const = ty::Const::from_target_usize(tcx, ty_size);
1126+
let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]);
1127+
1128+
// Create the Instance
1129+
let typing_env = self.mir.typing_env(tcx);
1130+
let instance = ty::Instance::expect_resolve(
1131+
tcx,
1132+
typing_env,
1133+
def_id,
1134+
generic_args,
1135+
rustc_span::DUMMY_SP,// span only used for error messages
1136+
);
1137+
1138+
Some(instance)
1139+
}
10401140
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit bbb6f68

Browse files
committed
Auto merge of #147803 - jsgf:move-copy-codegen, r=madsmtm,saethlin
Add -Zannotate-moves for profiler visibility of move/copy operations (codegen) **Note:** this is an alternative implementation of #147206; rather than being a MIR transform, it adds the annotations closer to codegen. It's functionally the same but the implementation is lower impact and it could be more correct. --- 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: - We're generating debug info and the feature is enabled. - Meets the size threshold (default: 65 bytes, configurable via `-Zannotate-moves=SIZE`), and is non-zero - Has a memory representation 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.
2 parents 4e0baae + 5f29f11 commit bbb6f68

28 files changed

Lines changed: 897 additions & 48 deletions

File tree

‎compiler/rustc_codegen_gcc/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
10691069
OperandValue::Ref(place.val)
10701070
};
10711071

1072-
OperandRef{ val,layout: place.layout}
1072+
OperandRef{ val,layout: place.layout,move_annotation:None}
10731073
}
10741074

10751075
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_gcc/src/debuginfo.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::context::CodegenCx;
1919
pub(super)constUNKNOWN_LINE_NUMBER:u32 = 0;
2020
pub(super)constUNKNOWN_COLUMN_NUMBER:u32 = 0;
2121

22-
impl<'a,'gcc,'tcx>DebugInfoBuilderMethodsforBuilder<'a,'gcc,'tcx>{
22+
impl<'a,'gcc,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'a,'gcc,'tcx>{
2323
// FIXME(eddyb) find a common convention for all of the debuginfo-related
2424
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
2525
fndbg_var_addr(

‎compiler/rustc_codegen_llvm/src/abi.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
253253
);
254254
bx.lifetime_end(llscratch, scratch_size);
255255
}
256-
_ => {
256+
PassMode::Pair(..) | PassMode::Direct{ .. } => {
257257
OperandRef::from_immediate_or_packed_pair(bx, val,self.layout).val.store(bx, dst);
258258
}
259259
}

‎compiler/rustc_codegen_llvm/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
751751
OperandValue::Ref(place.val)
752752
};
753753

754-
OperandRef{ val,layout: place.layout}
754+
OperandRef{ val,layout: place.layout,move_annotation:None}
755755
}
756756

757757
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_llvm/src/debuginfo/mod.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> {
146146
}
147147
}
148148

149-
impl<'ll>DebugInfoBuilderMethodsforBuilder<'_,'ll,'_>{
149+
impl<'ll,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'_,'ll,'tcx>{
150150
// FIXME(eddyb) find a common convention for all of the debuginfo-related
151151
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
152152
fndbg_var_addr(
@@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
284284
llvm::set_value_name(value, name.as_bytes());
285285
}
286286
}
287+
288+
/// Annotate move/copy operations with debug info for profiling.
289+
///
290+
/// This creates a temporary debug scope that makes the move/copy appear as an inlined call to
291+
/// `compiler_move<T, SIZE>()` or `compiler_copy<T, SIZE>()`. The provided closure is executed
292+
/// with this temporary debug location active.
293+
///
294+
/// The `instance` parameter should be the monomorphized instance of the `compiler_move` or
295+
/// `compiler_copy` function with the actual type and size.
296+
fnwith_move_annotation<R>(
297+
&mutself,
298+
instance: ty::Instance<'tcx>,
299+
f:implFnOnce(&mutSelf) -> R,
300+
) -> R{
301+
// Save the current debug location
302+
let saved_loc = self.get_dbg_loc();
303+
304+
// Create a DIScope for the compiler_move/compiler_copy function
305+
// We use the function's FnAbi for debug info generation
306+
let fn_abi = self
307+
.cx()
308+
.tcx
309+
.fn_abi_of_instance(
310+
self.cx().typing_env().as_query_input((instance, ty::List::empty())),
311+
)
312+
.unwrap();
313+
314+
let di_scope = self.cx().dbg_scope_fn(instance, fn_abi,None);
315+
316+
// Create an inlined debug location:
317+
// - scope: the compiler_move/compiler_copy function
318+
// - inlined_at: the current location (where the move/copy actually occurs)
319+
// - span: use the function's definition span
320+
let fn_span = self.cx().tcx.def_span(instance.def_id());
321+
let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span);
322+
323+
// Set the temporary debug location
324+
self.set_dbg_loc(inlined_loc);
325+
326+
// Execute the closure (which will generate the memcpy)
327+
let result = f(self);
328+
329+
// Restore the original debug location
330+
ifletSome(loc) = saved_loc {
331+
self.set_dbg_loc(loc);
332+
}else{
333+
self.clear_dbg_loc();
334+
}
335+
336+
result
337+
}
287338
}
288339

289340
/// A source code location used to generate debug information.

‎compiler/rustc_codegen_ssa/src/mir/block.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
557557
let op = matchself.locals[mir::RETURN_PLACE]{
558558
LocalRef::Operand(op) => op,
559559
LocalRef::PendingOperand => bug!("use of return before def"),
560-
LocalRef::Place(cg_place) => {
561-
OperandRef{val:Ref(cg_place.val),layout: cg_place.layout}
562-
}
560+
LocalRef::Place(cg_place) => OperandRef{
561+
val:Ref(cg_place.val),
562+
layout: cg_place.layout,
563+
move_annotation:None,
564+
},
563565
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
564566
};
565567
let llslot = match op.val{
@@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11551157
| (&mir::Operand::Constant(_),Ref(PlaceValue{llextra:None, .. })) => {
11561158
let tmp = PlaceRef::alloca(bx, op.layout);
11571159
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
1158-
op.val.store(bx, tmp);
1160+
op.store_with_annotation(bx, tmp);
11591161
op.val = Ref(tmp.val);
11601162
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
11611163
}
@@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15631565
};
15641566
let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align);
15651567
bx.lifetime_start(scratch.llval, arg.layout.size);
1566-
op.val.store(bx, scratch.with_type(arg.layout));
1568+
op.store_with_annotation(bx, scratch.with_type(arg.layout));
15671569
lifetime_ends_after_call.push((scratch.llval, arg.layout.size));
15681570
(scratch.llval, scratch.align,true)
15691571
}
15701572
PassMode::Cast{ .. } => {
15711573
let scratch = PlaceRef::alloca(bx, arg.layout);
1572-
op.val.store(bx, scratch);
1574+
op.store_with_annotation(bx, scratch);
15731575
(scratch.val.llval, scratch.val.align,true)
15741576
}
15751577
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi,false),

‎compiler/rustc_codegen_ssa/src/mir/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
480480
returnlocal(OperandRef{
481481
val:OperandValue::Pair(a, b),
482482
layout: arg.layout,
483+
move_annotation:None,
483484
});
484485
}
485486
_ => {}
@@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
552553
fx.caller_location = Some(OperandRef{
553554
val:OperandValue::Immediate(bx.get_param(llarg_idx)),
554555
layout: arg.layout,
556+
move_annotation:None,
555557
});
556558
}
557559

‎compiler/rustc_codegen_ssa/src/mir/operand.rs‎

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use rustc_abi as abi;
55
use rustc_abi::{
66
Align,BackendRepr,FIRST_VARIANT,FieldIdx,Primitive,Size,TagEncoding,VariantIdx,Variants,
77
};
8+
use rustc_hir::LangItem;
89
use rustc_middle::mir::interpret::{Pointer,Scalar, alloc_range};
910
use rustc_middle::mir::{self,ConstValue};
10-
use rustc_middle::ty::Ty;
1111
use rustc_middle::ty::layout::{LayoutOf,TyAndLayout};
12+
use rustc_middle::ty::{self,Ty};
1213
use rustc_middle::{bug, span_bug};
13-
use rustc_session::config::OptLevel;
14+
use rustc_session::config::{AnnotateMoves,DebugInfo,OptLevel};
1415
use tracing::{debug, instrument};
1516

1617
usesuper::place::{PlaceRef,PlaceValue};
@@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> {
131132

132133
/// The layout of value, based on its Rust type.
133134
publayout:TyAndLayout<'tcx>,
135+
136+
/// Annotation for profiler visibility of move/copy operations.
137+
/// When set, the store operation should appear as an inlined call to this function.
138+
pubmove_annotation:Option<ty::Instance<'tcx>>,
134139
}
135140

136141
impl<V:CodegenObject> fmt::DebugforOperandRef<'_,V>{
@@ -142,7 +147,7 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
142147
impl<'a,'tcx,V:CodegenObject>OperandRef<'tcx,V>{
143148
pubfnzero_sized(layout:TyAndLayout<'tcx>) -> OperandRef<'tcx,V>{
144149
assert!(layout.is_zst());
145-
OperandRef{val:OperandValue::ZeroSized, layout }
150+
OperandRef{val:OperandValue::ZeroSized, layout,move_annotation:None}
146151
}
147152

148153
pub(crate)fnfrom_const<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
180185
}
181186
};
182187

183-
OperandRef{ val, layout }
188+
OperandRef{ val, layout,move_annotation:None}
184189
}
185190

186191
fnfrom_const_alloc<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
214219
let size = s.size(bx);
215220
assert_eq!(size, layout.size,"abi::Scalar size does not match layout size");
216221
let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout));
217-
OperandRef{val:OperandValue::Immediate(val), layout }
222+
OperandRef{val:OperandValue::Immediate(val), layout,move_annotation:None}
218223
}
219224
BackendRepr::ScalarPair(
220225
a @ abi::Scalar::Initialized{ .. },
@@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
235240
b,
236241
bx.scalar_pair_element_backend_type(layout,1,true),
237242
);
238-
OperandRef{val:OperandValue::Pair(a_val, b_val), layout }
243+
OperandRef{val:OperandValue::Pair(a_val, b_val), layout,move_annotation:None}
239244
}
240245
_ if layout.is_zst() => OperandRef::zero_sized(layout),
241246
_ => {
@@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
285290
self.val.deref(layout.align.abi).with_type(layout)
286291
}
287292

293+
/// Store this operand into a place, applying move/copy annotation if present.
294+
///
295+
/// This is the preferred method for storing operands, as it automatically
296+
/// applies profiler annotations for tracked move/copy operations.
297+
pubfnstore_with_annotation<Bx:BuilderMethods<'a,'tcx,Value = V>>(
298+
self,
299+
bx:&mutBx,
300+
dest:PlaceRef<'tcx,V>,
301+
){
302+
ifletSome(instance) = self.move_annotation{
303+
bx.with_move_annotation(instance, |bx| self.val.store(bx, dest))
304+
}else{
305+
self.val.store(bx, dest)
306+
}
307+
}
308+
288309
/// If this operand is a `Pair`, we return an aggregate with the two values.
289310
/// For other cases, see `immediate`.
290311
pubfnimmediate_or_packed_pair<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
320341
}else{
321342
OperandValue::Immediate(llval)
322343
};
323-
OperandRef{ val, layout }
344+
OperandRef{ val, layout,move_annotation:None}
324345
}
325346

326347
pub(crate)fnextract_field<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
388409
})
389410
};
390411

391-
OperandRef{ val,layout: field }
412+
OperandRef{ val,layout: field,move_annotation:None}
392413
}
393414

394415
/// Obtain the actual discriminant of a value.
@@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
828849
}
829850
},
830851
};
831-
OperandRef{ val, layout }
852+
OperandRef{ val, layout,move_annotation:None}
832853
}
833854
}
834855

856+
/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache
857+
/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only
858+
/// annotate copies larger than this.
859+
constMOVE_ANNOTATION_DEFAULT_LIMIT:u64 = 65;
860+
835861
impl<'a,'tcx,V:CodegenObject>OperandValue<V>{
836862
/// Returns an `OperandValue` that's generally UB to use in any way.
837863
///
@@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
961987
abi::Variants::Single{ index: vidx },
962988
);
963989
let layout = o.layout.for_variant(bx.cx(), vidx);
964-
o = OperandRef{val: o.val, layout}
990+
o = OperandRef{layout, ..o}
965991
}
966992
_ => returnNone,
967993
}
@@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10141040

10151041
match*operand {
10161042
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
1017-
self.codegen_consume(bx, place.as_ref())
1043+
let kind = match operand {
1044+
mir::Operand::Move(_) => LangItem::CompilerMove,
1045+
mir::Operand::Copy(_) => LangItem::CompilerCopy,
1046+
_ => unreachable!(),
1047+
};
1048+
1049+
// Check if we should annotate this move/copy for profiling
1050+
let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind);
1051+
1052+
OperandRef{ move_annotation, ..self.codegen_consume(bx, place.as_ref())}
10181053
}
10191054

10201055
mir::Operand::Constant(ref constant) => {
@@ -1030,11 +1065,76 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10301065
returnOperandRef{
10311066
val:OperandValue::Immediate(llval),
10321067
layout: bx.layout_of(ty),
1068+
move_annotation:None,
10331069
};
10341070
}
10351071
}
10361072
self.eval_mir_constant_to_operand(bx, constant)
10371073
}
10381074
}
10391075
}
1076+
1077+
/// Creates an `Instance` for annotating a move/copy operation at codegen time.
1078+
///
1079+
/// Returns `Some(instance)` if the operation should be annotated with debug info, `None`
1080+
/// otherwise. The instance represents a monomorphized `compiler_move<T, SIZE>` or
1081+
/// `compiler_copy<T, SIZE>` function that can be used to create debug scopes.
1082+
///
1083+
/// There are a number of conditions that must be met for an annotation to be created, but aside
1084+
/// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is
1085+
/// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also
1086+
/// that the underlying representation of the type is in memory.
1087+
fnmove_copy_annotation_instance(
1088+
&self,
1089+
bx:&Bx,
1090+
place: mir::PlaceRef<'tcx>,
1091+
kind:LangItem,
1092+
) -> Option<ty::Instance<'tcx>>{
1093+
let tcx = bx.tcx();
1094+
let sess = tcx.sess;
1095+
1096+
// Skip if we're not generating debuginfo
1097+
if sess.opts.debuginfo == DebugInfo::None{
1098+
returnNone;
1099+
}
1100+
1101+
// Check if annotation is enabled and get size limit (otherwise skip)
1102+
let size_limit = match sess.opts.unstable_opts.annotate_moves{
1103+
AnnotateMoves::Disabled => returnNone,
1104+
AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT,
1105+
AnnotateMoves::Enabled(Some(limit)) => limit,
1106+
};
1107+
1108+
let ty = self.monomorphized_place_ty(place);
1109+
let layout = bx.cx().layout_of(ty);
1110+
let ty_size = layout.size.bytes();
1111+
1112+
// Only annotate if type has a memory representation and exceeds size limit (and has a
1113+
// non-zero size)
1114+
if layout.is_zst()
1115+
|| ty_size < size_limit
1116+
|| !matches!(layout.backend_repr,BackendRepr::Memory{ .. })
1117+
{
1118+
returnNone;
1119+
}
1120+
1121+
// Look up the DefId for compiler_move or compiler_copy lang item
1122+
let def_id = tcx.lang_items().get(kind)?;
1123+
1124+
// Create generic args: compiler_move<T, SIZE> or compiler_copy<T, SIZE>
1125+
let size_const = ty::Const::from_target_usize(tcx, ty_size);
1126+
let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]);
1127+
1128+
// Create the Instance
1129+
let typing_env = self.mir.typing_env(tcx);
1130+
let instance = ty::Instance::expect_resolve(
1131+
tcx,
1132+
typing_env,
1133+
def_id,
1134+
generic_args,
1135+
rustc_span::DUMMY_SP,// span only used for error messages
1136+
);
1137+
1138+
Some(instance)
1139+
}
10401140
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit bbb6f68

Browse files
committed
Auto merge of #147803 - jsgf:move-copy-codegen, r=madsmtm,saethlin
Add -Zannotate-moves for profiler visibility of move/copy operations (codegen) **Note:** this is an alternative implementation of #147206; rather than being a MIR transform, it adds the annotations closer to codegen. It's functionally the same but the implementation is lower impact and it could be more correct. --- 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: - We're generating debug info and the feature is enabled. - Meets the size threshold (default: 65 bytes, configurable via `-Zannotate-moves=SIZE`), and is non-zero - Has a memory representation 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.
2 parents 4e0baae + 5f29f11 commit bbb6f68

28 files changed

Lines changed: 897 additions & 48 deletions

File tree

‎compiler/rustc_codegen_gcc/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
10691069
OperandValue::Ref(place.val)
10701070
};
10711071

1072-
OperandRef{ val,layout: place.layout}
1072+
OperandRef{ val,layout: place.layout,move_annotation:None}
10731073
}
10741074

10751075
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_gcc/src/debuginfo.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::context::CodegenCx;
1919
pub(super)constUNKNOWN_LINE_NUMBER:u32 = 0;
2020
pub(super)constUNKNOWN_COLUMN_NUMBER:u32 = 0;
2121

22-
impl<'a,'gcc,'tcx>DebugInfoBuilderMethodsforBuilder<'a,'gcc,'tcx>{
22+
impl<'a,'gcc,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'a,'gcc,'tcx>{
2323
// FIXME(eddyb) find a common convention for all of the debuginfo-related
2424
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
2525
fndbg_var_addr(

‎compiler/rustc_codegen_llvm/src/abi.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
253253
);
254254
bx.lifetime_end(llscratch, scratch_size);
255255
}
256-
_ => {
256+
PassMode::Pair(..) | PassMode::Direct{ .. } => {
257257
OperandRef::from_immediate_or_packed_pair(bx, val,self.layout).val.store(bx, dst);
258258
}
259259
}

‎compiler/rustc_codegen_llvm/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
751751
OperandValue::Ref(place.val)
752752
};
753753

754-
OperandRef{ val,layout: place.layout}
754+
OperandRef{ val,layout: place.layout,move_annotation:None}
755755
}
756756

757757
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_llvm/src/debuginfo/mod.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> {
146146
}
147147
}
148148

149-
impl<'ll>DebugInfoBuilderMethodsforBuilder<'_,'ll,'_>{
149+
impl<'ll,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'_,'ll,'tcx>{
150150
// FIXME(eddyb) find a common convention for all of the debuginfo-related
151151
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
152152
fndbg_var_addr(
@@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
284284
llvm::set_value_name(value, name.as_bytes());
285285
}
286286
}
287+
288+
/// Annotate move/copy operations with debug info for profiling.
289+
///
290+
/// This creates a temporary debug scope that makes the move/copy appear as an inlined call to
291+
/// `compiler_move<T, SIZE>()` or `compiler_copy<T, SIZE>()`. The provided closure is executed
292+
/// with this temporary debug location active.
293+
///
294+
/// The `instance` parameter should be the monomorphized instance of the `compiler_move` or
295+
/// `compiler_copy` function with the actual type and size.
296+
fnwith_move_annotation<R>(
297+
&mutself,
298+
instance: ty::Instance<'tcx>,
299+
f:implFnOnce(&mutSelf) -> R,
300+
) -> R{
301+
// Save the current debug location
302+
let saved_loc = self.get_dbg_loc();
303+
304+
// Create a DIScope for the compiler_move/compiler_copy function
305+
// We use the function's FnAbi for debug info generation
306+
let fn_abi = self
307+
.cx()
308+
.tcx
309+
.fn_abi_of_instance(
310+
self.cx().typing_env().as_query_input((instance, ty::List::empty())),
311+
)
312+
.unwrap();
313+
314+
let di_scope = self.cx().dbg_scope_fn(instance, fn_abi,None);
315+
316+
// Create an inlined debug location:
317+
// - scope: the compiler_move/compiler_copy function
318+
// - inlined_at: the current location (where the move/copy actually occurs)
319+
// - span: use the function's definition span
320+
let fn_span = self.cx().tcx.def_span(instance.def_id());
321+
let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span);
322+
323+
// Set the temporary debug location
324+
self.set_dbg_loc(inlined_loc);
325+
326+
// Execute the closure (which will generate the memcpy)
327+
let result = f(self);
328+
329+
// Restore the original debug location
330+
ifletSome(loc) = saved_loc {
331+
self.set_dbg_loc(loc);
332+
}else{
333+
self.clear_dbg_loc();
334+
}
335+
336+
result
337+
}
287338
}
288339

289340
/// A source code location used to generate debug information.

‎compiler/rustc_codegen_ssa/src/mir/block.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
557557
let op = matchself.locals[mir::RETURN_PLACE]{
558558
LocalRef::Operand(op) => op,
559559
LocalRef::PendingOperand => bug!("use of return before def"),
560-
LocalRef::Place(cg_place) => {
561-
OperandRef{val:Ref(cg_place.val),layout: cg_place.layout}
562-
}
560+
LocalRef::Place(cg_place) => OperandRef{
561+
val:Ref(cg_place.val),
562+
layout: cg_place.layout,
563+
move_annotation:None,
564+
},
563565
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
564566
};
565567
let llslot = match op.val{
@@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11551157
| (&mir::Operand::Constant(_),Ref(PlaceValue{llextra:None, .. })) => {
11561158
let tmp = PlaceRef::alloca(bx, op.layout);
11571159
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
1158-
op.val.store(bx, tmp);
1160+
op.store_with_annotation(bx, tmp);
11591161
op.val = Ref(tmp.val);
11601162
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
11611163
}
@@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15631565
};
15641566
let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align);
15651567
bx.lifetime_start(scratch.llval, arg.layout.size);
1566-
op.val.store(bx, scratch.with_type(arg.layout));
1568+
op.store_with_annotation(bx, scratch.with_type(arg.layout));
15671569
lifetime_ends_after_call.push((scratch.llval, arg.layout.size));
15681570
(scratch.llval, scratch.align,true)
15691571
}
15701572
PassMode::Cast{ .. } => {
15711573
let scratch = PlaceRef::alloca(bx, arg.layout);
1572-
op.val.store(bx, scratch);
1574+
op.store_with_annotation(bx, scratch);
15731575
(scratch.val.llval, scratch.val.align,true)
15741576
}
15751577
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi,false),

‎compiler/rustc_codegen_ssa/src/mir/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
480480
returnlocal(OperandRef{
481481
val:OperandValue::Pair(a, b),
482482
layout: arg.layout,
483+
move_annotation:None,
483484
});
484485
}
485486
_ => {}
@@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
552553
fx.caller_location = Some(OperandRef{
553554
val:OperandValue::Immediate(bx.get_param(llarg_idx)),
554555
layout: arg.layout,
556+
move_annotation:None,
555557
});
556558
}
557559

‎compiler/rustc_codegen_ssa/src/mir/operand.rs‎

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use rustc_abi as abi;
55
use rustc_abi::{
66
Align,BackendRepr,FIRST_VARIANT,FieldIdx,Primitive,Size,TagEncoding,VariantIdx,Variants,
77
};
8+
use rustc_hir::LangItem;
89
use rustc_middle::mir::interpret::{Pointer,Scalar, alloc_range};
910
use rustc_middle::mir::{self,ConstValue};
10-
use rustc_middle::ty::Ty;
1111
use rustc_middle::ty::layout::{LayoutOf,TyAndLayout};
12+
use rustc_middle::ty::{self,Ty};
1213
use rustc_middle::{bug, span_bug};
13-
use rustc_session::config::OptLevel;
14+
use rustc_session::config::{AnnotateMoves,DebugInfo,OptLevel};
1415
use tracing::{debug, instrument};
1516

1617
usesuper::place::{PlaceRef,PlaceValue};
@@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> {
131132

132133
/// The layout of value, based on its Rust type.
133134
publayout:TyAndLayout<'tcx>,
135+
136+
/// Annotation for profiler visibility of move/copy operations.
137+
/// When set, the store operation should appear as an inlined call to this function.
138+
pubmove_annotation:Option<ty::Instance<'tcx>>,
134139
}
135140

136141
impl<V:CodegenObject> fmt::DebugforOperandRef<'_,V>{
@@ -142,7 +147,7 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
142147
impl<'a,'tcx,V:CodegenObject>OperandRef<'tcx,V>{
143148
pubfnzero_sized(layout:TyAndLayout<'tcx>) -> OperandRef<'tcx,V>{
144149
assert!(layout.is_zst());
145-
OperandRef{val:OperandValue::ZeroSized, layout }
150+
OperandRef{val:OperandValue::ZeroSized, layout,move_annotation:None}
146151
}
147152

148153
pub(crate)fnfrom_const<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
180185
}
181186
};
182187

183-
OperandRef{ val, layout }
188+
OperandRef{ val, layout,move_annotation:None}
184189
}
185190

186191
fnfrom_const_alloc<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
214219
let size = s.size(bx);
215220
assert_eq!(size, layout.size,"abi::Scalar size does not match layout size");
216221
let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout));
217-
OperandRef{val:OperandValue::Immediate(val), layout }
222+
OperandRef{val:OperandValue::Immediate(val), layout,move_annotation:None}
218223
}
219224
BackendRepr::ScalarPair(
220225
a @ abi::Scalar::Initialized{ .. },
@@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
235240
b,
236241
bx.scalar_pair_element_backend_type(layout,1,true),
237242
);
238-
OperandRef{val:OperandValue::Pair(a_val, b_val), layout }
243+
OperandRef{val:OperandValue::Pair(a_val, b_val), layout,move_annotation:None}
239244
}
240245
_ if layout.is_zst() => OperandRef::zero_sized(layout),
241246
_ => {
@@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
285290
self.val.deref(layout.align.abi).with_type(layout)
286291
}
287292

293+
/// Store this operand into a place, applying move/copy annotation if present.
294+
///
295+
/// This is the preferred method for storing operands, as it automatically
296+
/// applies profiler annotations for tracked move/copy operations.
297+
pubfnstore_with_annotation<Bx:BuilderMethods<'a,'tcx,Value = V>>(
298+
self,
299+
bx:&mutBx,
300+
dest:PlaceRef<'tcx,V>,
301+
){
302+
ifletSome(instance) = self.move_annotation{
303+
bx.with_move_annotation(instance, |bx| self.val.store(bx, dest))
304+
}else{
305+
self.val.store(bx, dest)
306+
}
307+
}
308+
288309
/// If this operand is a `Pair`, we return an aggregate with the two values.
289310
/// For other cases, see `immediate`.
290311
pubfnimmediate_or_packed_pair<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
320341
}else{
321342
OperandValue::Immediate(llval)
322343
};
323-
OperandRef{ val, layout }
344+
OperandRef{ val, layout,move_annotation:None}
324345
}
325346

326347
pub(crate)fnextract_field<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
388409
})
389410
};
390411

391-
OperandRef{ val,layout: field }
412+
OperandRef{ val,layout: field,move_annotation:None}
392413
}
393414

394415
/// Obtain the actual discriminant of a value.
@@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
828849
}
829850
},
830851
};
831-
OperandRef{ val, layout }
852+
OperandRef{ val, layout,move_annotation:None}
832853
}
833854
}
834855

856+
/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache
857+
/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only
858+
/// annotate copies larger than this.
859+
constMOVE_ANNOTATION_DEFAULT_LIMIT:u64 = 65;
860+
835861
impl<'a,'tcx,V:CodegenObject>OperandValue<V>{
836862
/// Returns an `OperandValue` that's generally UB to use in any way.
837863
///
@@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
961987
abi::Variants::Single{ index: vidx },
962988
);
963989
let layout = o.layout.for_variant(bx.cx(), vidx);
964-
o = OperandRef{val: o.val, layout}
990+
o = OperandRef{layout, ..o}
965991
}
966992
_ => returnNone,
967993
}
@@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10141040

10151041
match*operand {
10161042
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
1017-
self.codegen_consume(bx, place.as_ref())
1043+
let kind = match operand {
1044+
mir::Operand::Move(_) => LangItem::CompilerMove,
1045+
mir::Operand::Copy(_) => LangItem::CompilerCopy,
1046+
_ => unreachable!(),
1047+
};
1048+
1049+
// Check if we should annotate this move/copy for profiling
1050+
let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind);
1051+
1052+
OperandRef{ move_annotation, ..self.codegen_consume(bx, place.as_ref())}
10181053
}
10191054

10201055
mir::Operand::Constant(ref constant) => {
@@ -1030,11 +1065,76 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10301065
returnOperandRef{
10311066
val:OperandValue::Immediate(llval),
10321067
layout: bx.layout_of(ty),
1068+
move_annotation:None,
10331069
};
10341070
}
10351071
}
10361072
self.eval_mir_constant_to_operand(bx, constant)
10371073
}
10381074
}
10391075
}
1076+
1077+
/// Creates an `Instance` for annotating a move/copy operation at codegen time.
1078+
///
1079+
/// Returns `Some(instance)` if the operation should be annotated with debug info, `None`
1080+
/// otherwise. The instance represents a monomorphized `compiler_move<T, SIZE>` or
1081+
/// `compiler_copy<T, SIZE>` function that can be used to create debug scopes.
1082+
///
1083+
/// There are a number of conditions that must be met for an annotation to be created, but aside
1084+
/// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is
1085+
/// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also
1086+
/// that the underlying representation of the type is in memory.
1087+
fnmove_copy_annotation_instance(
1088+
&self,
1089+
bx:&Bx,
1090+
place: mir::PlaceRef<'tcx>,
1091+
kind:LangItem,
1092+
) -> Option<ty::Instance<'tcx>>{
1093+
let tcx = bx.tcx();
1094+
let sess = tcx.sess;
1095+
1096+
// Skip if we're not generating debuginfo
1097+
if sess.opts.debuginfo == DebugInfo::None{
1098+
returnNone;
1099+
}
1100+
1101+
// Check if annotation is enabled and get size limit (otherwise skip)
1102+
let size_limit = match sess.opts.unstable_opts.annotate_moves{
1103+
AnnotateMoves::Disabled => returnNone,
1104+
AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT,
1105+
AnnotateMoves::Enabled(Some(limit)) => limit,
1106+
};
1107+
1108+
let ty = self.monomorphized_place_ty(place);
1109+
let layout = bx.cx().layout_of(ty);
1110+
let ty_size = layout.size.bytes();
1111+
1112+
// Only annotate if type has a memory representation and exceeds size limit (and has a
1113+
// non-zero size)
1114+
if layout.is_zst()
1115+
|| ty_size < size_limit
1116+
|| !matches!(layout.backend_repr,BackendRepr::Memory{ .. })
1117+
{
1118+
returnNone;
1119+
}
1120+
1121+
// Look up the DefId for compiler_move or compiler_copy lang item
1122+
let def_id = tcx.lang_items().get(kind)?;
1123+
1124+
// Create generic args: compiler_move<T, SIZE> or compiler_copy<T, SIZE>
1125+
let size_const = ty::Const::from_target_usize(tcx, ty_size);
1126+
let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]);
1127+
1128+
// Create the Instance
1129+
let typing_env = self.mir.typing_env(tcx);
1130+
let instance = ty::Instance::expect_resolve(
1131+
tcx,
1132+
typing_env,
1133+
def_id,
1134+
generic_args,
1135+
rustc_span::DUMMY_SP,// span only used for error messages
1136+
);
1137+
1138+
Some(instance)
1139+
}
10401140
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit bbb6f68

Browse files
committed
Auto merge of #147803 - jsgf:move-copy-codegen, r=madsmtm,saethlin
Add -Zannotate-moves for profiler visibility of move/copy operations (codegen) **Note:** this is an alternative implementation of #147206; rather than being a MIR transform, it adds the annotations closer to codegen. It's functionally the same but the implementation is lower impact and it could be more correct. --- 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: - We're generating debug info and the feature is enabled. - Meets the size threshold (default: 65 bytes, configurable via `-Zannotate-moves=SIZE`), and is non-zero - Has a memory representation 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.
2 parents 4e0baae + 5f29f11 commit bbb6f68

28 files changed

Lines changed: 897 additions & 48 deletions

File tree

‎compiler/rustc_codegen_gcc/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
10691069
OperandValue::Ref(place.val)
10701070
};
10711071

1072-
OperandRef{ val,layout: place.layout}
1072+
OperandRef{ val,layout: place.layout,move_annotation:None}
10731073
}
10741074

10751075
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_gcc/src/debuginfo.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::context::CodegenCx;
1919
pub(super)constUNKNOWN_LINE_NUMBER:u32 = 0;
2020
pub(super)constUNKNOWN_COLUMN_NUMBER:u32 = 0;
2121

22-
impl<'a,'gcc,'tcx>DebugInfoBuilderMethodsforBuilder<'a,'gcc,'tcx>{
22+
impl<'a,'gcc,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'a,'gcc,'tcx>{
2323
// FIXME(eddyb) find a common convention for all of the debuginfo-related
2424
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
2525
fndbg_var_addr(

‎compiler/rustc_codegen_llvm/src/abi.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
253253
);
254254
bx.lifetime_end(llscratch, scratch_size);
255255
}
256-
_ => {
256+
PassMode::Pair(..) | PassMode::Direct{ .. } => {
257257
OperandRef::from_immediate_or_packed_pair(bx, val,self.layout).val.store(bx, dst);
258258
}
259259
}

‎compiler/rustc_codegen_llvm/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
751751
OperandValue::Ref(place.val)
752752
};
753753

754-
OperandRef{ val,layout: place.layout}
754+
OperandRef{ val,layout: place.layout,move_annotation:None}
755755
}
756756

757757
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_llvm/src/debuginfo/mod.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> {
146146
}
147147
}
148148

149-
impl<'ll>DebugInfoBuilderMethodsforBuilder<'_,'ll,'_>{
149+
impl<'ll,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'_,'ll,'tcx>{
150150
// FIXME(eddyb) find a common convention for all of the debuginfo-related
151151
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
152152
fndbg_var_addr(
@@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
284284
llvm::set_value_name(value, name.as_bytes());
285285
}
286286
}
287+
288+
/// Annotate move/copy operations with debug info for profiling.
289+
///
290+
/// This creates a temporary debug scope that makes the move/copy appear as an inlined call to
291+
/// `compiler_move<T, SIZE>()` or `compiler_copy<T, SIZE>()`. The provided closure is executed
292+
/// with this temporary debug location active.
293+
///
294+
/// The `instance` parameter should be the monomorphized instance of the `compiler_move` or
295+
/// `compiler_copy` function with the actual type and size.
296+
fnwith_move_annotation<R>(
297+
&mutself,
298+
instance: ty::Instance<'tcx>,
299+
f:implFnOnce(&mutSelf) -> R,
300+
) -> R{
301+
// Save the current debug location
302+
let saved_loc = self.get_dbg_loc();
303+
304+
// Create a DIScope for the compiler_move/compiler_copy function
305+
// We use the function's FnAbi for debug info generation
306+
let fn_abi = self
307+
.cx()
308+
.tcx
309+
.fn_abi_of_instance(
310+
self.cx().typing_env().as_query_input((instance, ty::List::empty())),
311+
)
312+
.unwrap();
313+
314+
let di_scope = self.cx().dbg_scope_fn(instance, fn_abi,None);
315+
316+
// Create an inlined debug location:
317+
// - scope: the compiler_move/compiler_copy function
318+
// - inlined_at: the current location (where the move/copy actually occurs)
319+
// - span: use the function's definition span
320+
let fn_span = self.cx().tcx.def_span(instance.def_id());
321+
let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span);
322+
323+
// Set the temporary debug location
324+
self.set_dbg_loc(inlined_loc);
325+
326+
// Execute the closure (which will generate the memcpy)
327+
let result = f(self);
328+
329+
// Restore the original debug location
330+
ifletSome(loc) = saved_loc {
331+
self.set_dbg_loc(loc);
332+
}else{
333+
self.clear_dbg_loc();
334+
}
335+
336+
result
337+
}
287338
}
288339

289340
/// A source code location used to generate debug information.

‎compiler/rustc_codegen_ssa/src/mir/block.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
557557
let op = matchself.locals[mir::RETURN_PLACE]{
558558
LocalRef::Operand(op) => op,
559559
LocalRef::PendingOperand => bug!("use of return before def"),
560-
LocalRef::Place(cg_place) => {
561-
OperandRef{val:Ref(cg_place.val),layout: cg_place.layout}
562-
}
560+
LocalRef::Place(cg_place) => OperandRef{
561+
val:Ref(cg_place.val),
562+
layout: cg_place.layout,
563+
move_annotation:None,
564+
},
563565
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
564566
};
565567
let llslot = match op.val{
@@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11551157
| (&mir::Operand::Constant(_),Ref(PlaceValue{llextra:None, .. })) => {
11561158
let tmp = PlaceRef::alloca(bx, op.layout);
11571159
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
1158-
op.val.store(bx, tmp);
1160+
op.store_with_annotation(bx, tmp);
11591161
op.val = Ref(tmp.val);
11601162
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
11611163
}
@@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15631565
};
15641566
let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align);
15651567
bx.lifetime_start(scratch.llval, arg.layout.size);
1566-
op.val.store(bx, scratch.with_type(arg.layout));
1568+
op.store_with_annotation(bx, scratch.with_type(arg.layout));
15671569
lifetime_ends_after_call.push((scratch.llval, arg.layout.size));
15681570
(scratch.llval, scratch.align,true)
15691571
}
15701572
PassMode::Cast{ .. } => {
15711573
let scratch = PlaceRef::alloca(bx, arg.layout);
1572-
op.val.store(bx, scratch);
1574+
op.store_with_annotation(bx, scratch);
15731575
(scratch.val.llval, scratch.val.align,true)
15741576
}
15751577
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi,false),

‎compiler/rustc_codegen_ssa/src/mir/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
480480
returnlocal(OperandRef{
481481
val:OperandValue::Pair(a, b),
482482
layout: arg.layout,
483+
move_annotation:None,
483484
});
484485
}
485486
_ => {}
@@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
552553
fx.caller_location = Some(OperandRef{
553554
val:OperandValue::Immediate(bx.get_param(llarg_idx)),
554555
layout: arg.layout,
556+
move_annotation:None,
555557
});
556558
}
557559

‎compiler/rustc_codegen_ssa/src/mir/operand.rs‎

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use rustc_abi as abi;
55
use rustc_abi::{
66
Align,BackendRepr,FIRST_VARIANT,FieldIdx,Primitive,Size,TagEncoding,VariantIdx,Variants,
77
};
8+
use rustc_hir::LangItem;
89
use rustc_middle::mir::interpret::{Pointer,Scalar, alloc_range};
910
use rustc_middle::mir::{self,ConstValue};
10-
use rustc_middle::ty::Ty;
1111
use rustc_middle::ty::layout::{LayoutOf,TyAndLayout};
12+
use rustc_middle::ty::{self,Ty};
1213
use rustc_middle::{bug, span_bug};
13-
use rustc_session::config::OptLevel;
14+
use rustc_session::config::{AnnotateMoves,DebugInfo,OptLevel};
1415
use tracing::{debug, instrument};
1516

1617
usesuper::place::{PlaceRef,PlaceValue};
@@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> {
131132

132133
/// The layout of value, based on its Rust type.
133134
publayout:TyAndLayout<'tcx>,
135+
136+
/// Annotation for profiler visibility of move/copy operations.
137+
/// When set, the store operation should appear as an inlined call to this function.
138+
pubmove_annotation:Option<ty::Instance<'tcx>>,
134139
}
135140

136141
impl<V:CodegenObject> fmt::DebugforOperandRef<'_,V>{
@@ -142,7 +147,7 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
142147
impl<'a,'tcx,V:CodegenObject>OperandRef<'tcx,V>{
143148
pubfnzero_sized(layout:TyAndLayout<'tcx>) -> OperandRef<'tcx,V>{
144149
assert!(layout.is_zst());
145-
OperandRef{val:OperandValue::ZeroSized, layout }
150+
OperandRef{val:OperandValue::ZeroSized, layout,move_annotation:None}
146151
}
147152

148153
pub(crate)fnfrom_const<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
180185
}
181186
};
182187

183-
OperandRef{ val, layout }
188+
OperandRef{ val, layout,move_annotation:None}
184189
}
185190

186191
fnfrom_const_alloc<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
214219
let size = s.size(bx);
215220
assert_eq!(size, layout.size,"abi::Scalar size does not match layout size");
216221
let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout));
217-
OperandRef{val:OperandValue::Immediate(val), layout }
222+
OperandRef{val:OperandValue::Immediate(val), layout,move_annotation:None}
218223
}
219224
BackendRepr::ScalarPair(
220225
a @ abi::Scalar::Initialized{ .. },
@@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
235240
b,
236241
bx.scalar_pair_element_backend_type(layout,1,true),
237242
);
238-
OperandRef{val:OperandValue::Pair(a_val, b_val), layout }
243+
OperandRef{val:OperandValue::Pair(a_val, b_val), layout,move_annotation:None}
239244
}
240245
_ if layout.is_zst() => OperandRef::zero_sized(layout),
241246
_ => {
@@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
285290
self.val.deref(layout.align.abi).with_type(layout)
286291
}
287292

293+
/// Store this operand into a place, applying move/copy annotation if present.
294+
///
295+
/// This is the preferred method for storing operands, as it automatically
296+
/// applies profiler annotations for tracked move/copy operations.
297+
pubfnstore_with_annotation<Bx:BuilderMethods<'a,'tcx,Value = V>>(
298+
self,
299+
bx:&mutBx,
300+
dest:PlaceRef<'tcx,V>,
301+
){
302+
ifletSome(instance) = self.move_annotation{
303+
bx.with_move_annotation(instance, |bx| self.val.store(bx, dest))
304+
}else{
305+
self.val.store(bx, dest)
306+
}
307+
}
308+
288309
/// If this operand is a `Pair`, we return an aggregate with the two values.
289310
/// For other cases, see `immediate`.
290311
pubfnimmediate_or_packed_pair<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
320341
}else{
321342
OperandValue::Immediate(llval)
322343
};
323-
OperandRef{ val, layout }
344+
OperandRef{ val, layout,move_annotation:None}
324345
}
325346

326347
pub(crate)fnextract_field<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
388409
})
389410
};
390411

391-
OperandRef{ val,layout: field }
412+
OperandRef{ val,layout: field,move_annotation:None}
392413
}
393414

394415
/// Obtain the actual discriminant of a value.
@@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
828849
}
829850
},
830851
};
831-
OperandRef{ val, layout }
852+
OperandRef{ val, layout,move_annotation:None}
832853
}
833854
}
834855

856+
/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache
857+
/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only
858+
/// annotate copies larger than this.
859+
constMOVE_ANNOTATION_DEFAULT_LIMIT:u64 = 65;
860+
835861
impl<'a,'tcx,V:CodegenObject>OperandValue<V>{
836862
/// Returns an `OperandValue` that's generally UB to use in any way.
837863
///
@@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
961987
abi::Variants::Single{ index: vidx },
962988
);
963989
let layout = o.layout.for_variant(bx.cx(), vidx);
964-
o = OperandRef{val: o.val, layout}
990+
o = OperandRef{layout, ..o}
965991
}
966992
_ => returnNone,
967993
}
@@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10141040

10151041
match*operand {
10161042
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
1017-
self.codegen_consume(bx, place.as_ref())
1043+
let kind = match operand {
1044+
mir::Operand::Move(_) => LangItem::CompilerMove,
1045+
mir::Operand::Copy(_) => LangItem::CompilerCopy,
1046+
_ => unreachable!(),
1047+
};
1048+
1049+
// Check if we should annotate this move/copy for profiling
1050+
let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind);
1051+
1052+
OperandRef{ move_annotation, ..self.codegen_consume(bx, place.as_ref())}
10181053
}
10191054

10201055
mir::Operand::Constant(ref constant) => {
@@ -1030,11 +1065,76 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10301065
returnOperandRef{
10311066
val:OperandValue::Immediate(llval),
10321067
layout: bx.layout_of(ty),
1068+
move_annotation:None,
10331069
};
10341070
}
10351071
}
10361072
self.eval_mir_constant_to_operand(bx, constant)
10371073
}
10381074
}
10391075
}
1076+
1077+
/// Creates an `Instance` for annotating a move/copy operation at codegen time.
1078+
///
1079+
/// Returns `Some(instance)` if the operation should be annotated with debug info, `None`
1080+
/// otherwise. The instance represents a monomorphized `compiler_move<T, SIZE>` or
1081+
/// `compiler_copy<T, SIZE>` function that can be used to create debug scopes.
1082+
///
1083+
/// There are a number of conditions that must be met for an annotation to be created, but aside
1084+
/// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is
1085+
/// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also
1086+
/// that the underlying representation of the type is in memory.
1087+
fnmove_copy_annotation_instance(
1088+
&self,
1089+
bx:&Bx,
1090+
place: mir::PlaceRef<'tcx>,
1091+
kind:LangItem,
1092+
) -> Option<ty::Instance<'tcx>>{
1093+
let tcx = bx.tcx();
1094+
let sess = tcx.sess;
1095+
1096+
// Skip if we're not generating debuginfo
1097+
if sess.opts.debuginfo == DebugInfo::None{
1098+
returnNone;
1099+
}
1100+
1101+
// Check if annotation is enabled and get size limit (otherwise skip)
1102+
let size_limit = match sess.opts.unstable_opts.annotate_moves{
1103+
AnnotateMoves::Disabled => returnNone,
1104+
AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT,
1105+
AnnotateMoves::Enabled(Some(limit)) => limit,
1106+
};
1107+
1108+
let ty = self.monomorphized_place_ty(place);
1109+
let layout = bx.cx().layout_of(ty);
1110+
let ty_size = layout.size.bytes();
1111+
1112+
// Only annotate if type has a memory representation and exceeds size limit (and has a
1113+
// non-zero size)
1114+
if layout.is_zst()
1115+
|| ty_size < size_limit
1116+
|| !matches!(layout.backend_repr,BackendRepr::Memory{ .. })
1117+
{
1118+
returnNone;
1119+
}
1120+
1121+
// Look up the DefId for compiler_move or compiler_copy lang item
1122+
let def_id = tcx.lang_items().get(kind)?;
1123+
1124+
// Create generic args: compiler_move<T, SIZE> or compiler_copy<T, SIZE>
1125+
let size_const = ty::Const::from_target_usize(tcx, ty_size);
1126+
let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]);
1127+
1128+
// Create the Instance
1129+
let typing_env = self.mir.typing_env(tcx);
1130+
let instance = ty::Instance::expect_resolve(
1131+
tcx,
1132+
typing_env,
1133+
def_id,
1134+
generic_args,
1135+
rustc_span::DUMMY_SP,// span only used for error messages
1136+
);
1137+
1138+
Some(instance)
1139+
}
10401140
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit bbb6f68

Browse files
committed
Auto merge of #147803 - jsgf:move-copy-codegen, r=madsmtm,saethlin
Add -Zannotate-moves for profiler visibility of move/copy operations (codegen) **Note:** this is an alternative implementation of #147206; rather than being a MIR transform, it adds the annotations closer to codegen. It's functionally the same but the implementation is lower impact and it could be more correct. --- 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: - We're generating debug info and the feature is enabled. - Meets the size threshold (default: 65 bytes, configurable via `-Zannotate-moves=SIZE`), and is non-zero - Has a memory representation 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.
2 parents 4e0baae + 5f29f11 commit bbb6f68

28 files changed

Lines changed: 897 additions & 48 deletions

File tree

‎compiler/rustc_codegen_gcc/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
10691069
OperandValue::Ref(place.val)
10701070
};
10711071

1072-
OperandRef{ val,layout: place.layout}
1072+
OperandRef{ val,layout: place.layout,move_annotation:None}
10731073
}
10741074

10751075
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_gcc/src/debuginfo.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::context::CodegenCx;
1919
pub(super)constUNKNOWN_LINE_NUMBER:u32 = 0;
2020
pub(super)constUNKNOWN_COLUMN_NUMBER:u32 = 0;
2121

22-
impl<'a,'gcc,'tcx>DebugInfoBuilderMethodsforBuilder<'a,'gcc,'tcx>{
22+
impl<'a,'gcc,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'a,'gcc,'tcx>{
2323
// FIXME(eddyb) find a common convention for all of the debuginfo-related
2424
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
2525
fndbg_var_addr(

‎compiler/rustc_codegen_llvm/src/abi.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
253253
);
254254
bx.lifetime_end(llscratch, scratch_size);
255255
}
256-
_ => {
256+
PassMode::Pair(..) | PassMode::Direct{ .. } => {
257257
OperandRef::from_immediate_or_packed_pair(bx, val,self.layout).val.store(bx, dst);
258258
}
259259
}

‎compiler/rustc_codegen_llvm/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
751751
OperandValue::Ref(place.val)
752752
};
753753

754-
OperandRef{ val,layout: place.layout}
754+
OperandRef{ val,layout: place.layout,move_annotation:None}
755755
}
756756

757757
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_llvm/src/debuginfo/mod.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> {
146146
}
147147
}
148148

149-
impl<'ll>DebugInfoBuilderMethodsforBuilder<'_,'ll,'_>{
149+
impl<'ll,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'_,'ll,'tcx>{
150150
// FIXME(eddyb) find a common convention for all of the debuginfo-related
151151
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
152152
fndbg_var_addr(
@@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
284284
llvm::set_value_name(value, name.as_bytes());
285285
}
286286
}
287+
288+
/// Annotate move/copy operations with debug info for profiling.
289+
///
290+
/// This creates a temporary debug scope that makes the move/copy appear as an inlined call to
291+
/// `compiler_move<T, SIZE>()` or `compiler_copy<T, SIZE>()`. The provided closure is executed
292+
/// with this temporary debug location active.
293+
///
294+
/// The `instance` parameter should be the monomorphized instance of the `compiler_move` or
295+
/// `compiler_copy` function with the actual type and size.
296+
fnwith_move_annotation<R>(
297+
&mutself,
298+
instance: ty::Instance<'tcx>,
299+
f:implFnOnce(&mutSelf) -> R,
300+
) -> R{
301+
// Save the current debug location
302+
let saved_loc = self.get_dbg_loc();
303+
304+
// Create a DIScope for the compiler_move/compiler_copy function
305+
// We use the function's FnAbi for debug info generation
306+
let fn_abi = self
307+
.cx()
308+
.tcx
309+
.fn_abi_of_instance(
310+
self.cx().typing_env().as_query_input((instance, ty::List::empty())),
311+
)
312+
.unwrap();
313+
314+
let di_scope = self.cx().dbg_scope_fn(instance, fn_abi,None);
315+
316+
// Create an inlined debug location:
317+
// - scope: the compiler_move/compiler_copy function
318+
// - inlined_at: the current location (where the move/copy actually occurs)
319+
// - span: use the function's definition span
320+
let fn_span = self.cx().tcx.def_span(instance.def_id());
321+
let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span);
322+
323+
// Set the temporary debug location
324+
self.set_dbg_loc(inlined_loc);
325+
326+
// Execute the closure (which will generate the memcpy)
327+
let result = f(self);
328+
329+
// Restore the original debug location
330+
ifletSome(loc) = saved_loc {
331+
self.set_dbg_loc(loc);
332+
}else{
333+
self.clear_dbg_loc();
334+
}
335+
336+
result
337+
}
287338
}
288339

289340
/// A source code location used to generate debug information.

‎compiler/rustc_codegen_ssa/src/mir/block.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
557557
let op = matchself.locals[mir::RETURN_PLACE]{
558558
LocalRef::Operand(op) => op,
559559
LocalRef::PendingOperand => bug!("use of return before def"),
560-
LocalRef::Place(cg_place) => {
561-
OperandRef{val:Ref(cg_place.val),layout: cg_place.layout}
562-
}
560+
LocalRef::Place(cg_place) => OperandRef{
561+
val:Ref(cg_place.val),
562+
layout: cg_place.layout,
563+
move_annotation:None,
564+
},
563565
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
564566
};
565567
let llslot = match op.val{
@@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11551157
| (&mir::Operand::Constant(_),Ref(PlaceValue{llextra:None, .. })) => {
11561158
let tmp = PlaceRef::alloca(bx, op.layout);
11571159
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
1158-
op.val.store(bx, tmp);
1160+
op.store_with_annotation(bx, tmp);
11591161
op.val = Ref(tmp.val);
11601162
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
11611163
}
@@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15631565
};
15641566
let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align);
15651567
bx.lifetime_start(scratch.llval, arg.layout.size);
1566-
op.val.store(bx, scratch.with_type(arg.layout));
1568+
op.store_with_annotation(bx, scratch.with_type(arg.layout));
15671569
lifetime_ends_after_call.push((scratch.llval, arg.layout.size));
15681570
(scratch.llval, scratch.align,true)
15691571
}
15701572
PassMode::Cast{ .. } => {
15711573
let scratch = PlaceRef::alloca(bx, arg.layout);
1572-
op.val.store(bx, scratch);
1574+
op.store_with_annotation(bx, scratch);
15731575
(scratch.val.llval, scratch.val.align,true)
15741576
}
15751577
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi,false),

‎compiler/rustc_codegen_ssa/src/mir/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
480480
returnlocal(OperandRef{
481481
val:OperandValue::Pair(a, b),
482482
layout: arg.layout,
483+
move_annotation:None,
483484
});
484485
}
485486
_ => {}
@@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
552553
fx.caller_location = Some(OperandRef{
553554
val:OperandValue::Immediate(bx.get_param(llarg_idx)),
554555
layout: arg.layout,
556+
move_annotation:None,
555557
});
556558
}
557559

‎compiler/rustc_codegen_ssa/src/mir/operand.rs‎

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use rustc_abi as abi;
55
use rustc_abi::{
66
Align,BackendRepr,FIRST_VARIANT,FieldIdx,Primitive,Size,TagEncoding,VariantIdx,Variants,
77
};
8+
use rustc_hir::LangItem;
89
use rustc_middle::mir::interpret::{Pointer,Scalar, alloc_range};
910
use rustc_middle::mir::{self,ConstValue};
10-
use rustc_middle::ty::Ty;
1111
use rustc_middle::ty::layout::{LayoutOf,TyAndLayout};
12+
use rustc_middle::ty::{self,Ty};
1213
use rustc_middle::{bug, span_bug};
13-
use rustc_session::config::OptLevel;
14+
use rustc_session::config::{AnnotateMoves,DebugInfo,OptLevel};
1415
use tracing::{debug, instrument};
1516

1617
usesuper::place::{PlaceRef,PlaceValue};
@@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> {
131132

132133
/// The layout of value, based on its Rust type.
133134
publayout:TyAndLayout<'tcx>,
135+
136+
/// Annotation for profiler visibility of move/copy operations.
137+
/// When set, the store operation should appear as an inlined call to this function.
138+
pubmove_annotation:Option<ty::Instance<'tcx>>,
134139
}
135140

136141
impl<V:CodegenObject> fmt::DebugforOperandRef<'_,V>{
@@ -142,7 +147,7 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
142147
impl<'a,'tcx,V:CodegenObject>OperandRef<'tcx,V>{
143148
pubfnzero_sized(layout:TyAndLayout<'tcx>) -> OperandRef<'tcx,V>{
144149
assert!(layout.is_zst());
145-
OperandRef{val:OperandValue::ZeroSized, layout }
150+
OperandRef{val:OperandValue::ZeroSized, layout,move_annotation:None}
146151
}
147152

148153
pub(crate)fnfrom_const<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
180185
}
181186
};
182187

183-
OperandRef{ val, layout }
188+
OperandRef{ val, layout,move_annotation:None}
184189
}
185190

186191
fnfrom_const_alloc<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
214219
let size = s.size(bx);
215220
assert_eq!(size, layout.size,"abi::Scalar size does not match layout size");
216221
let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout));
217-
OperandRef{val:OperandValue::Immediate(val), layout }
222+
OperandRef{val:OperandValue::Immediate(val), layout,move_annotation:None}
218223
}
219224
BackendRepr::ScalarPair(
220225
a @ abi::Scalar::Initialized{ .. },
@@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
235240
b,
236241
bx.scalar_pair_element_backend_type(layout,1,true),
237242
);
238-
OperandRef{val:OperandValue::Pair(a_val, b_val), layout }
243+
OperandRef{val:OperandValue::Pair(a_val, b_val), layout,move_annotation:None}
239244
}
240245
_ if layout.is_zst() => OperandRef::zero_sized(layout),
241246
_ => {
@@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
285290
self.val.deref(layout.align.abi).with_type(layout)
286291
}
287292

293+
/// Store this operand into a place, applying move/copy annotation if present.
294+
///
295+
/// This is the preferred method for storing operands, as it automatically
296+
/// applies profiler annotations for tracked move/copy operations.
297+
pubfnstore_with_annotation<Bx:BuilderMethods<'a,'tcx,Value = V>>(
298+
self,
299+
bx:&mutBx,
300+
dest:PlaceRef<'tcx,V>,
301+
){
302+
ifletSome(instance) = self.move_annotation{
303+
bx.with_move_annotation(instance, |bx| self.val.store(bx, dest))
304+
}else{
305+
self.val.store(bx, dest)
306+
}
307+
}
308+
288309
/// If this operand is a `Pair`, we return an aggregate with the two values.
289310
/// For other cases, see `immediate`.
290311
pubfnimmediate_or_packed_pair<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
320341
}else{
321342
OperandValue::Immediate(llval)
322343
};
323-
OperandRef{ val, layout }
344+
OperandRef{ val, layout,move_annotation:None}
324345
}
325346

326347
pub(crate)fnextract_field<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
388409
})
389410
};
390411

391-
OperandRef{ val,layout: field }
412+
OperandRef{ val,layout: field,move_annotation:None}
392413
}
393414

394415
/// Obtain the actual discriminant of a value.
@@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
828849
}
829850
},
830851
};
831-
OperandRef{ val, layout }
852+
OperandRef{ val, layout,move_annotation:None}
832853
}
833854
}
834855

856+
/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache
857+
/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only
858+
/// annotate copies larger than this.
859+
constMOVE_ANNOTATION_DEFAULT_LIMIT:u64 = 65;
860+
835861
impl<'a,'tcx,V:CodegenObject>OperandValue<V>{
836862
/// Returns an `OperandValue` that's generally UB to use in any way.
837863
///
@@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
961987
abi::Variants::Single{ index: vidx },
962988
);
963989
let layout = o.layout.for_variant(bx.cx(), vidx);
964-
o = OperandRef{val: o.val, layout}
990+
o = OperandRef{layout, ..o}
965991
}
966992
_ => returnNone,
967993
}
@@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10141040

10151041
match*operand {
10161042
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
1017-
self.codegen_consume(bx, place.as_ref())
1043+
let kind = match operand {
1044+
mir::Operand::Move(_) => LangItem::CompilerMove,
1045+
mir::Operand::Copy(_) => LangItem::CompilerCopy,
1046+
_ => unreachable!(),
1047+
};
1048+
1049+
// Check if we should annotate this move/copy for profiling
1050+
let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind);
1051+
1052+
OperandRef{ move_annotation, ..self.codegen_consume(bx, place.as_ref())}
10181053
}
10191054

10201055
mir::Operand::Constant(ref constant) => {
@@ -1030,11 +1065,76 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10301065
returnOperandRef{
10311066
val:OperandValue::Immediate(llval),
10321067
layout: bx.layout_of(ty),
1068+
move_annotation:None,
10331069
};
10341070
}
10351071
}
10361072
self.eval_mir_constant_to_operand(bx, constant)
10371073
}
10381074
}
10391075
}
1076+
1077+
/// Creates an `Instance` for annotating a move/copy operation at codegen time.
1078+
///
1079+
/// Returns `Some(instance)` if the operation should be annotated with debug info, `None`
1080+
/// otherwise. The instance represents a monomorphized `compiler_move<T, SIZE>` or
1081+
/// `compiler_copy<T, SIZE>` function that can be used to create debug scopes.
1082+
///
1083+
/// There are a number of conditions that must be met for an annotation to be created, but aside
1084+
/// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is
1085+
/// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also
1086+
/// that the underlying representation of the type is in memory.
1087+
fnmove_copy_annotation_instance(
1088+
&self,
1089+
bx:&Bx,
1090+
place: mir::PlaceRef<'tcx>,
1091+
kind:LangItem,
1092+
) -> Option<ty::Instance<'tcx>>{
1093+
let tcx = bx.tcx();
1094+
let sess = tcx.sess;
1095+
1096+
// Skip if we're not generating debuginfo
1097+
if sess.opts.debuginfo == DebugInfo::None{
1098+
returnNone;
1099+
}
1100+
1101+
// Check if annotation is enabled and get size limit (otherwise skip)
1102+
let size_limit = match sess.opts.unstable_opts.annotate_moves{
1103+
AnnotateMoves::Disabled => returnNone,
1104+
AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT,
1105+
AnnotateMoves::Enabled(Some(limit)) => limit,
1106+
};
1107+
1108+
let ty = self.monomorphized_place_ty(place);
1109+
let layout = bx.cx().layout_of(ty);
1110+
let ty_size = layout.size.bytes();
1111+
1112+
// Only annotate if type has a memory representation and exceeds size limit (and has a
1113+
// non-zero size)
1114+
if layout.is_zst()
1115+
|| ty_size < size_limit
1116+
|| !matches!(layout.backend_repr,BackendRepr::Memory{ .. })
1117+
{
1118+
returnNone;
1119+
}
1120+
1121+
// Look up the DefId for compiler_move or compiler_copy lang item
1122+
let def_id = tcx.lang_items().get(kind)?;
1123+
1124+
// Create generic args: compiler_move<T, SIZE> or compiler_copy<T, SIZE>
1125+
let size_const = ty::Const::from_target_usize(tcx, ty_size);
1126+
let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]);
1127+
1128+
// Create the Instance
1129+
let typing_env = self.mir.typing_env(tcx);
1130+
let instance = ty::Instance::expect_resolve(
1131+
tcx,
1132+
typing_env,
1133+
def_id,
1134+
generic_args,
1135+
rustc_span::DUMMY_SP,// span only used for error messages
1136+
);
1137+
1138+
Some(instance)
1139+
}
10401140
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit bbb6f68

Browse files
committed
Auto merge of #147803 - jsgf:move-copy-codegen, r=madsmtm,saethlin
Add -Zannotate-moves for profiler visibility of move/copy operations (codegen) **Note:** this is an alternative implementation of #147206; rather than being a MIR transform, it adds the annotations closer to codegen. It's functionally the same but the implementation is lower impact and it could be more correct. --- 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: - We're generating debug info and the feature is enabled. - Meets the size threshold (default: 65 bytes, configurable via `-Zannotate-moves=SIZE`), and is non-zero - Has a memory representation 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.
2 parents 4e0baae + 5f29f11 commit bbb6f68

28 files changed

Lines changed: 897 additions & 48 deletions

File tree

‎compiler/rustc_codegen_gcc/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
10691069
OperandValue::Ref(place.val)
10701070
};
10711071

1072-
OperandRef{ val,layout: place.layout}
1072+
OperandRef{ val,layout: place.layout,move_annotation:None}
10731073
}
10741074

10751075
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_gcc/src/debuginfo.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::context::CodegenCx;
1919
pub(super)constUNKNOWN_LINE_NUMBER:u32 = 0;
2020
pub(super)constUNKNOWN_COLUMN_NUMBER:u32 = 0;
2121

22-
impl<'a,'gcc,'tcx>DebugInfoBuilderMethodsforBuilder<'a,'gcc,'tcx>{
22+
impl<'a,'gcc,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'a,'gcc,'tcx>{
2323
// FIXME(eddyb) find a common convention for all of the debuginfo-related
2424
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
2525
fndbg_var_addr(

‎compiler/rustc_codegen_llvm/src/abi.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
253253
);
254254
bx.lifetime_end(llscratch, scratch_size);
255255
}
256-
_ => {
256+
PassMode::Pair(..) | PassMode::Direct{ .. } => {
257257
OperandRef::from_immediate_or_packed_pair(bx, val,self.layout).val.store(bx, dst);
258258
}
259259
}

‎compiler/rustc_codegen_llvm/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
751751
OperandValue::Ref(place.val)
752752
};
753753

754-
OperandRef{ val,layout: place.layout}
754+
OperandRef{ val,layout: place.layout,move_annotation:None}
755755
}
756756

757757
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_llvm/src/debuginfo/mod.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> {
146146
}
147147
}
148148

149-
impl<'ll>DebugInfoBuilderMethodsforBuilder<'_,'ll,'_>{
149+
impl<'ll,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'_,'ll,'tcx>{
150150
// FIXME(eddyb) find a common convention for all of the debuginfo-related
151151
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
152152
fndbg_var_addr(
@@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
284284
llvm::set_value_name(value, name.as_bytes());
285285
}
286286
}
287+
288+
/// Annotate move/copy operations with debug info for profiling.
289+
///
290+
/// This creates a temporary debug scope that makes the move/copy appear as an inlined call to
291+
/// `compiler_move<T, SIZE>()` or `compiler_copy<T, SIZE>()`. The provided closure is executed
292+
/// with this temporary debug location active.
293+
///
294+
/// The `instance` parameter should be the monomorphized instance of the `compiler_move` or
295+
/// `compiler_copy` function with the actual type and size.
296+
fnwith_move_annotation<R>(
297+
&mutself,
298+
instance: ty::Instance<'tcx>,
299+
f:implFnOnce(&mutSelf) -> R,
300+
) -> R{
301+
// Save the current debug location
302+
let saved_loc = self.get_dbg_loc();
303+
304+
// Create a DIScope for the compiler_move/compiler_copy function
305+
// We use the function's FnAbi for debug info generation
306+
let fn_abi = self
307+
.cx()
308+
.tcx
309+
.fn_abi_of_instance(
310+
self.cx().typing_env().as_query_input((instance, ty::List::empty())),
311+
)
312+
.unwrap();
313+
314+
let di_scope = self.cx().dbg_scope_fn(instance, fn_abi,None);
315+
316+
// Create an inlined debug location:
317+
// - scope: the compiler_move/compiler_copy function
318+
// - inlined_at: the current location (where the move/copy actually occurs)
319+
// - span: use the function's definition span
320+
let fn_span = self.cx().tcx.def_span(instance.def_id());
321+
let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span);
322+
323+
// Set the temporary debug location
324+
self.set_dbg_loc(inlined_loc);
325+
326+
// Execute the closure (which will generate the memcpy)
327+
let result = f(self);
328+
329+
// Restore the original debug location
330+
ifletSome(loc) = saved_loc {
331+
self.set_dbg_loc(loc);
332+
}else{
333+
self.clear_dbg_loc();
334+
}
335+
336+
result
337+
}
287338
}
288339

289340
/// A source code location used to generate debug information.

‎compiler/rustc_codegen_ssa/src/mir/block.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
557557
let op = matchself.locals[mir::RETURN_PLACE]{
558558
LocalRef::Operand(op) => op,
559559
LocalRef::PendingOperand => bug!("use of return before def"),
560-
LocalRef::Place(cg_place) => {
561-
OperandRef{val:Ref(cg_place.val),layout: cg_place.layout}
562-
}
560+
LocalRef::Place(cg_place) => OperandRef{
561+
val:Ref(cg_place.val),
562+
layout: cg_place.layout,
563+
move_annotation:None,
564+
},
563565
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
564566
};
565567
let llslot = match op.val{
@@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11551157
| (&mir::Operand::Constant(_),Ref(PlaceValue{llextra:None, .. })) => {
11561158
let tmp = PlaceRef::alloca(bx, op.layout);
11571159
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
1158-
op.val.store(bx, tmp);
1160+
op.store_with_annotation(bx, tmp);
11591161
op.val = Ref(tmp.val);
11601162
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
11611163
}
@@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15631565
};
15641566
let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align);
15651567
bx.lifetime_start(scratch.llval, arg.layout.size);
1566-
op.val.store(bx, scratch.with_type(arg.layout));
1568+
op.store_with_annotation(bx, scratch.with_type(arg.layout));
15671569
lifetime_ends_after_call.push((scratch.llval, arg.layout.size));
15681570
(scratch.llval, scratch.align,true)
15691571
}
15701572
PassMode::Cast{ .. } => {
15711573
let scratch = PlaceRef::alloca(bx, arg.layout);
1572-
op.val.store(bx, scratch);
1574+
op.store_with_annotation(bx, scratch);
15731575
(scratch.val.llval, scratch.val.align,true)
15741576
}
15751577
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi,false),

‎compiler/rustc_codegen_ssa/src/mir/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
480480
returnlocal(OperandRef{
481481
val:OperandValue::Pair(a, b),
482482
layout: arg.layout,
483+
move_annotation:None,
483484
});
484485
}
485486
_ => {}
@@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
552553
fx.caller_location = Some(OperandRef{
553554
val:OperandValue::Immediate(bx.get_param(llarg_idx)),
554555
layout: arg.layout,
556+
move_annotation:None,
555557
});
556558
}
557559

‎compiler/rustc_codegen_ssa/src/mir/operand.rs‎

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use rustc_abi as abi;
55
use rustc_abi::{
66
Align,BackendRepr,FIRST_VARIANT,FieldIdx,Primitive,Size,TagEncoding,VariantIdx,Variants,
77
};
8+
use rustc_hir::LangItem;
89
use rustc_middle::mir::interpret::{Pointer,Scalar, alloc_range};
910
use rustc_middle::mir::{self,ConstValue};
10-
use rustc_middle::ty::Ty;
1111
use rustc_middle::ty::layout::{LayoutOf,TyAndLayout};
12+
use rustc_middle::ty::{self,Ty};
1213
use rustc_middle::{bug, span_bug};
13-
use rustc_session::config::OptLevel;
14+
use rustc_session::config::{AnnotateMoves,DebugInfo,OptLevel};
1415
use tracing::{debug, instrument};
1516

1617
usesuper::place::{PlaceRef,PlaceValue};
@@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> {
131132

132133
/// The layout of value, based on its Rust type.
133134
publayout:TyAndLayout<'tcx>,
135+
136+
/// Annotation for profiler visibility of move/copy operations.
137+
/// When set, the store operation should appear as an inlined call to this function.
138+
pubmove_annotation:Option<ty::Instance<'tcx>>,
134139
}
135140

136141
impl<V:CodegenObject> fmt::DebugforOperandRef<'_,V>{
@@ -142,7 +147,7 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
142147
impl<'a,'tcx,V:CodegenObject>OperandRef<'tcx,V>{
143148
pubfnzero_sized(layout:TyAndLayout<'tcx>) -> OperandRef<'tcx,V>{
144149
assert!(layout.is_zst());
145-
OperandRef{val:OperandValue::ZeroSized, layout }
150+
OperandRef{val:OperandValue::ZeroSized, layout,move_annotation:None}
146151
}
147152

148153
pub(crate)fnfrom_const<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
180185
}
181186
};
182187

183-
OperandRef{ val, layout }
188+
OperandRef{ val, layout,move_annotation:None}
184189
}
185190

186191
fnfrom_const_alloc<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
214219
let size = s.size(bx);
215220
assert_eq!(size, layout.size,"abi::Scalar size does not match layout size");
216221
let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout));
217-
OperandRef{val:OperandValue::Immediate(val), layout }
222+
OperandRef{val:OperandValue::Immediate(val), layout,move_annotation:None}
218223
}
219224
BackendRepr::ScalarPair(
220225
a @ abi::Scalar::Initialized{ .. },
@@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
235240
b,
236241
bx.scalar_pair_element_backend_type(layout,1,true),
237242
);
238-
OperandRef{val:OperandValue::Pair(a_val, b_val), layout }
243+
OperandRef{val:OperandValue::Pair(a_val, b_val), layout,move_annotation:None}
239244
}
240245
_ if layout.is_zst() => OperandRef::zero_sized(layout),
241246
_ => {
@@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
285290
self.val.deref(layout.align.abi).with_type(layout)
286291
}
287292

293+
/// Store this operand into a place, applying move/copy annotation if present.
294+
///
295+
/// This is the preferred method for storing operands, as it automatically
296+
/// applies profiler annotations for tracked move/copy operations.
297+
pubfnstore_with_annotation<Bx:BuilderMethods<'a,'tcx,Value = V>>(
298+
self,
299+
bx:&mutBx,
300+
dest:PlaceRef<'tcx,V>,
301+
){
302+
ifletSome(instance) = self.move_annotation{
303+
bx.with_move_annotation(instance, |bx| self.val.store(bx, dest))
304+
}else{
305+
self.val.store(bx, dest)
306+
}
307+
}
308+
288309
/// If this operand is a `Pair`, we return an aggregate with the two values.
289310
/// For other cases, see `immediate`.
290311
pubfnimmediate_or_packed_pair<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
320341
}else{
321342
OperandValue::Immediate(llval)
322343
};
323-
OperandRef{ val, layout }
344+
OperandRef{ val, layout,move_annotation:None}
324345
}
325346

326347
pub(crate)fnextract_field<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
388409
})
389410
};
390411

391-
OperandRef{ val,layout: field }
412+
OperandRef{ val,layout: field,move_annotation:None}
392413
}
393414

394415
/// Obtain the actual discriminant of a value.
@@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
828849
}
829850
},
830851
};
831-
OperandRef{ val, layout }
852+
OperandRef{ val, layout,move_annotation:None}
832853
}
833854
}
834855

856+
/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache
857+
/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only
858+
/// annotate copies larger than this.
859+
constMOVE_ANNOTATION_DEFAULT_LIMIT:u64 = 65;
860+
835861
impl<'a,'tcx,V:CodegenObject>OperandValue<V>{
836862
/// Returns an `OperandValue` that's generally UB to use in any way.
837863
///
@@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
961987
abi::Variants::Single{ index: vidx },
962988
);
963989
let layout = o.layout.for_variant(bx.cx(), vidx);
964-
o = OperandRef{val: o.val, layout}
990+
o = OperandRef{layout, ..o}
965991
}
966992
_ => returnNone,
967993
}
@@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10141040

10151041
match*operand {
10161042
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
1017-
self.codegen_consume(bx, place.as_ref())
1043+
let kind = match operand {
1044+
mir::Operand::Move(_) => LangItem::CompilerMove,
1045+
mir::Operand::Copy(_) => LangItem::CompilerCopy,
1046+
_ => unreachable!(),
1047+
};
1048+
1049+
// Check if we should annotate this move/copy for profiling
1050+
let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind);
1051+
1052+
OperandRef{ move_annotation, ..self.codegen_consume(bx, place.as_ref())}
10181053
}
10191054

10201055
mir::Operand::Constant(ref constant) => {
@@ -1030,11 +1065,76 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10301065
returnOperandRef{
10311066
val:OperandValue::Immediate(llval),
10321067
layout: bx.layout_of(ty),
1068+
move_annotation:None,
10331069
};
10341070
}
10351071
}
10361072
self.eval_mir_constant_to_operand(bx, constant)
10371073
}
10381074
}
10391075
}
1076+
1077+
/// Creates an `Instance` for annotating a move/copy operation at codegen time.
1078+
///
1079+
/// Returns `Some(instance)` if the operation should be annotated with debug info, `None`
1080+
/// otherwise. The instance represents a monomorphized `compiler_move<T, SIZE>` or
1081+
/// `compiler_copy<T, SIZE>` function that can be used to create debug scopes.
1082+
///
1083+
/// There are a number of conditions that must be met for an annotation to be created, but aside
1084+
/// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is
1085+
/// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also
1086+
/// that the underlying representation of the type is in memory.
1087+
fnmove_copy_annotation_instance(
1088+
&self,
1089+
bx:&Bx,
1090+
place: mir::PlaceRef<'tcx>,
1091+
kind:LangItem,
1092+
) -> Option<ty::Instance<'tcx>>{
1093+
let tcx = bx.tcx();
1094+
let sess = tcx.sess;
1095+
1096+
// Skip if we're not generating debuginfo
1097+
if sess.opts.debuginfo == DebugInfo::None{
1098+
returnNone;
1099+
}
1100+
1101+
// Check if annotation is enabled and get size limit (otherwise skip)
1102+
let size_limit = match sess.opts.unstable_opts.annotate_moves{
1103+
AnnotateMoves::Disabled => returnNone,
1104+
AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT,
1105+
AnnotateMoves::Enabled(Some(limit)) => limit,
1106+
};
1107+
1108+
let ty = self.monomorphized_place_ty(place);
1109+
let layout = bx.cx().layout_of(ty);
1110+
let ty_size = layout.size.bytes();
1111+
1112+
// Only annotate if type has a memory representation and exceeds size limit (and has a
1113+
// non-zero size)
1114+
if layout.is_zst()
1115+
|| ty_size < size_limit
1116+
|| !matches!(layout.backend_repr,BackendRepr::Memory{ .. })
1117+
{
1118+
returnNone;
1119+
}
1120+
1121+
// Look up the DefId for compiler_move or compiler_copy lang item
1122+
let def_id = tcx.lang_items().get(kind)?;
1123+
1124+
// Create generic args: compiler_move<T, SIZE> or compiler_copy<T, SIZE>
1125+
let size_const = ty::Const::from_target_usize(tcx, ty_size);
1126+
let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]);
1127+
1128+
// Create the Instance
1129+
let typing_env = self.mir.typing_env(tcx);
1130+
let instance = ty::Instance::expect_resolve(
1131+
tcx,
1132+
typing_env,
1133+
def_id,
1134+
generic_args,
1135+
rustc_span::DUMMY_SP,// span only used for error messages
1136+
);
1137+
1138+
Some(instance)
1139+
}
10401140
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit bbb6f68

Browse files
committed
Auto merge of #147803 - jsgf:move-copy-codegen, r=madsmtm,saethlin
Add -Zannotate-moves for profiler visibility of move/copy operations (codegen) **Note:** this is an alternative implementation of #147206; rather than being a MIR transform, it adds the annotations closer to codegen. It's functionally the same but the implementation is lower impact and it could be more correct. --- 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: - We're generating debug info and the feature is enabled. - Meets the size threshold (default: 65 bytes, configurable via `-Zannotate-moves=SIZE`), and is non-zero - Has a memory representation 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.
2 parents 4e0baae + 5f29f11 commit bbb6f68

28 files changed

Lines changed: 897 additions & 48 deletions

File tree

‎compiler/rustc_codegen_gcc/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
10691069
OperandValue::Ref(place.val)
10701070
};
10711071

1072-
OperandRef{ val,layout: place.layout}
1072+
OperandRef{ val,layout: place.layout,move_annotation:None}
10731073
}
10741074

10751075
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_gcc/src/debuginfo.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::context::CodegenCx;
1919
pub(super)constUNKNOWN_LINE_NUMBER:u32 = 0;
2020
pub(super)constUNKNOWN_COLUMN_NUMBER:u32 = 0;
2121

22-
impl<'a,'gcc,'tcx>DebugInfoBuilderMethodsforBuilder<'a,'gcc,'tcx>{
22+
impl<'a,'gcc,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'a,'gcc,'tcx>{
2323
// FIXME(eddyb) find a common convention for all of the debuginfo-related
2424
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
2525
fndbg_var_addr(

‎compiler/rustc_codegen_llvm/src/abi.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
253253
);
254254
bx.lifetime_end(llscratch, scratch_size);
255255
}
256-
_ => {
256+
PassMode::Pair(..) | PassMode::Direct{ .. } => {
257257
OperandRef::from_immediate_or_packed_pair(bx, val,self.layout).val.store(bx, dst);
258258
}
259259
}

‎compiler/rustc_codegen_llvm/src/builder.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
751751
OperandValue::Ref(place.val)
752752
};
753753

754-
OperandRef{ val,layout: place.layout}
754+
OperandRef{ val,layout: place.layout,move_annotation:None}
755755
}
756756

757757
fnwrite_operand_repeatedly(

‎compiler/rustc_codegen_llvm/src/debuginfo/mod.rs‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> {
146146
}
147147
}
148148

149-
impl<'ll>DebugInfoBuilderMethodsforBuilder<'_,'ll,'_>{
149+
impl<'ll,'tcx>DebugInfoBuilderMethods<'tcx>forBuilder<'_,'ll,'tcx>{
150150
// FIXME(eddyb) find a common convention for all of the debuginfo-related
151151
// names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
152152
fndbg_var_addr(
@@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> {
284284
llvm::set_value_name(value, name.as_bytes());
285285
}
286286
}
287+
288+
/// Annotate move/copy operations with debug info for profiling.
289+
///
290+
/// This creates a temporary debug scope that makes the move/copy appear as an inlined call to
291+
/// `compiler_move<T, SIZE>()` or `compiler_copy<T, SIZE>()`. The provided closure is executed
292+
/// with this temporary debug location active.
293+
///
294+
/// The `instance` parameter should be the monomorphized instance of the `compiler_move` or
295+
/// `compiler_copy` function with the actual type and size.
296+
fnwith_move_annotation<R>(
297+
&mutself,
298+
instance: ty::Instance<'tcx>,
299+
f:implFnOnce(&mutSelf) -> R,
300+
) -> R{
301+
// Save the current debug location
302+
let saved_loc = self.get_dbg_loc();
303+
304+
// Create a DIScope for the compiler_move/compiler_copy function
305+
// We use the function's FnAbi for debug info generation
306+
let fn_abi = self
307+
.cx()
308+
.tcx
309+
.fn_abi_of_instance(
310+
self.cx().typing_env().as_query_input((instance, ty::List::empty())),
311+
)
312+
.unwrap();
313+
314+
let di_scope = self.cx().dbg_scope_fn(instance, fn_abi,None);
315+
316+
// Create an inlined debug location:
317+
// - scope: the compiler_move/compiler_copy function
318+
// - inlined_at: the current location (where the move/copy actually occurs)
319+
// - span: use the function's definition span
320+
let fn_span = self.cx().tcx.def_span(instance.def_id());
321+
let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span);
322+
323+
// Set the temporary debug location
324+
self.set_dbg_loc(inlined_loc);
325+
326+
// Execute the closure (which will generate the memcpy)
327+
let result = f(self);
328+
329+
// Restore the original debug location
330+
ifletSome(loc) = saved_loc {
331+
self.set_dbg_loc(loc);
332+
}else{
333+
self.clear_dbg_loc();
334+
}
335+
336+
result
337+
}
287338
}
288339

289340
/// A source code location used to generate debug information.

‎compiler/rustc_codegen_ssa/src/mir/block.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
557557
let op = matchself.locals[mir::RETURN_PLACE]{
558558
LocalRef::Operand(op) => op,
559559
LocalRef::PendingOperand => bug!("use of return before def"),
560-
LocalRef::Place(cg_place) => {
561-
OperandRef{val:Ref(cg_place.val),layout: cg_place.layout}
562-
}
560+
LocalRef::Place(cg_place) => OperandRef{
561+
val:Ref(cg_place.val),
562+
layout: cg_place.layout,
563+
move_annotation:None,
564+
},
563565
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
564566
};
565567
let llslot = match op.val{
@@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11551157
| (&mir::Operand::Constant(_),Ref(PlaceValue{llextra:None, .. })) => {
11561158
let tmp = PlaceRef::alloca(bx, op.layout);
11571159
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
1158-
op.val.store(bx, tmp);
1160+
op.store_with_annotation(bx, tmp);
11591161
op.val = Ref(tmp.val);
11601162
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
11611163
}
@@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15631565
};
15641566
let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align);
15651567
bx.lifetime_start(scratch.llval, arg.layout.size);
1566-
op.val.store(bx, scratch.with_type(arg.layout));
1568+
op.store_with_annotation(bx, scratch.with_type(arg.layout));
15671569
lifetime_ends_after_call.push((scratch.llval, arg.layout.size));
15681570
(scratch.llval, scratch.align,true)
15691571
}
15701572
PassMode::Cast{ .. } => {
15711573
let scratch = PlaceRef::alloca(bx, arg.layout);
1572-
op.val.store(bx, scratch);
1574+
op.store_with_annotation(bx, scratch);
15731575
(scratch.val.llval, scratch.val.align,true)
15741576
}
15751577
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi,false),

‎compiler/rustc_codegen_ssa/src/mir/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
480480
returnlocal(OperandRef{
481481
val:OperandValue::Pair(a, b),
482482
layout: arg.layout,
483+
move_annotation:None,
483484
});
484485
}
485486
_ => {}
@@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
552553
fx.caller_location = Some(OperandRef{
553554
val:OperandValue::Immediate(bx.get_param(llarg_idx)),
554555
layout: arg.layout,
556+
move_annotation:None,
555557
});
556558
}
557559

‎compiler/rustc_codegen_ssa/src/mir/operand.rs‎

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use rustc_abi as abi;
55
use rustc_abi::{
66
Align,BackendRepr,FIRST_VARIANT,FieldIdx,Primitive,Size,TagEncoding,VariantIdx,Variants,
77
};
8+
use rustc_hir::LangItem;
89
use rustc_middle::mir::interpret::{Pointer,Scalar, alloc_range};
910
use rustc_middle::mir::{self,ConstValue};
10-
use rustc_middle::ty::Ty;
1111
use rustc_middle::ty::layout::{LayoutOf,TyAndLayout};
12+
use rustc_middle::ty::{self,Ty};
1213
use rustc_middle::{bug, span_bug};
13-
use rustc_session::config::OptLevel;
14+
use rustc_session::config::{AnnotateMoves,DebugInfo,OptLevel};
1415
use tracing::{debug, instrument};
1516

1617
usesuper::place::{PlaceRef,PlaceValue};
@@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> {
131132

132133
/// The layout of value, based on its Rust type.
133134
publayout:TyAndLayout<'tcx>,
135+
136+
/// Annotation for profiler visibility of move/copy operations.
137+
/// When set, the store operation should appear as an inlined call to this function.
138+
pubmove_annotation:Option<ty::Instance<'tcx>>,
134139
}
135140

136141
impl<V:CodegenObject> fmt::DebugforOperandRef<'_,V>{
@@ -142,7 +147,7 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
142147
impl<'a,'tcx,V:CodegenObject>OperandRef<'tcx,V>{
143148
pubfnzero_sized(layout:TyAndLayout<'tcx>) -> OperandRef<'tcx,V>{
144149
assert!(layout.is_zst());
145-
OperandRef{val:OperandValue::ZeroSized, layout }
150+
OperandRef{val:OperandValue::ZeroSized, layout,move_annotation:None}
146151
}
147152

148153
pub(crate)fnfrom_const<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
180185
}
181186
};
182187

183-
OperandRef{ val, layout }
188+
OperandRef{ val, layout,move_annotation:None}
184189
}
185190

186191
fnfrom_const_alloc<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
214219
let size = s.size(bx);
215220
assert_eq!(size, layout.size,"abi::Scalar size does not match layout size");
216221
let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout));
217-
OperandRef{val:OperandValue::Immediate(val), layout }
222+
OperandRef{val:OperandValue::Immediate(val), layout,move_annotation:None}
218223
}
219224
BackendRepr::ScalarPair(
220225
a @ abi::Scalar::Initialized{ .. },
@@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
235240
b,
236241
bx.scalar_pair_element_backend_type(layout,1,true),
237242
);
238-
OperandRef{val:OperandValue::Pair(a_val, b_val), layout }
243+
OperandRef{val:OperandValue::Pair(a_val, b_val), layout,move_annotation:None}
239244
}
240245
_ if layout.is_zst() => OperandRef::zero_sized(layout),
241246
_ => {
@@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
285290
self.val.deref(layout.align.abi).with_type(layout)
286291
}
287292

293+
/// Store this operand into a place, applying move/copy annotation if present.
294+
///
295+
/// This is the preferred method for storing operands, as it automatically
296+
/// applies profiler annotations for tracked move/copy operations.
297+
pubfnstore_with_annotation<Bx:BuilderMethods<'a,'tcx,Value = V>>(
298+
self,
299+
bx:&mutBx,
300+
dest:PlaceRef<'tcx,V>,
301+
){
302+
ifletSome(instance) = self.move_annotation{
303+
bx.with_move_annotation(instance, |bx| self.val.store(bx, dest))
304+
}else{
305+
self.val.store(bx, dest)
306+
}
307+
}
308+
288309
/// If this operand is a `Pair`, we return an aggregate with the two values.
289310
/// For other cases, see `immediate`.
290311
pubfnimmediate_or_packed_pair<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
320341
}else{
321342
OperandValue::Immediate(llval)
322343
};
323-
OperandRef{ val, layout }
344+
OperandRef{ val, layout,move_annotation:None}
324345
}
325346

326347
pub(crate)fnextract_field<Bx:BuilderMethods<'a,'tcx,Value = V>>(
@@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
388409
})
389410
};
390411

391-
OperandRef{ val,layout: field }
412+
OperandRef{ val,layout: field,move_annotation:None}
392413
}
393414

394415
/// Obtain the actual discriminant of a value.
@@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
828849
}
829850
},
830851
};
831-
OperandRef{ val, layout }
852+
OperandRef{ val, layout,move_annotation:None}
832853
}
833854
}
834855

856+
/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache
857+
/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only
858+
/// annotate copies larger than this.
859+
constMOVE_ANNOTATION_DEFAULT_LIMIT:u64 = 65;
860+
835861
impl<'a,'tcx,V:CodegenObject>OperandValue<V>{
836862
/// Returns an `OperandValue` that's generally UB to use in any way.
837863
///
@@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
961987
abi::Variants::Single{ index: vidx },
962988
);
963989
let layout = o.layout.for_variant(bx.cx(), vidx);
964-
o = OperandRef{val: o.val, layout}
990+
o = OperandRef{layout, ..o}
965991
}
966992
_ => returnNone,
967993
}
@@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10141040

10151041
match*operand {
10161042
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
1017-
self.codegen_consume(bx, place.as_ref())
1043+
let kind = match operand {
1044+
mir::Operand::Move(_) => LangItem::CompilerMove,
1045+
mir::Operand::Copy(_) => LangItem::CompilerCopy,
1046+
_ => unreachable!(),
1047+
};
1048+
1049+
// Check if we should annotate this move/copy for profiling
1050+
let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind);
1051+
1052+
OperandRef{ move_annotation, ..self.codegen_consume(bx, place.as_ref())}
10181053
}
10191054

10201055
mir::Operand::Constant(ref constant) => {
@@ -1030,11 +1065,76 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10301065
returnOperandRef{
10311066
val:OperandValue::Immediate(llval),
10321067
layout: bx.layout_of(ty),
1068+
move_annotation:None,
10331069
};
10341070
}
10351071
}
10361072
self.eval_mir_constant_to_operand(bx, constant)
10371073
}
10381074
}
10391075
}
1076+
1077+
/// Creates an `Instance` for annotating a move/copy operation at codegen time.
1078+
///
1079+
/// Returns `Some(instance)` if the operation should be annotated with debug info, `None`
1080+
/// otherwise. The instance represents a monomorphized `compiler_move<T, SIZE>` or
1081+
/// `compiler_copy<T, SIZE>` function that can be used to create debug scopes.
1082+
///
1083+
/// There are a number of conditions that must be met for an annotation to be created, but aside
1084+
/// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is
1085+
/// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also
1086+
/// that the underlying representation of the type is in memory.
1087+
fnmove_copy_annotation_instance(
1088+
&self,
1089+
bx:&Bx,
1090+
place: mir::PlaceRef<'tcx>,
1091+
kind:LangItem,
1092+
) -> Option<ty::Instance<'tcx>>{
1093+
let tcx = bx.tcx();
1094+
let sess = tcx.sess;
1095+
1096+
// Skip if we're not generating debuginfo
1097+
if sess.opts.debuginfo == DebugInfo::None{
1098+
returnNone;
1099+
}
1100+
1101+
// Check if annotation is enabled and get size limit (otherwise skip)
1102+
let size_limit = match sess.opts.unstable_opts.annotate_moves{
1103+
AnnotateMoves::Disabled => returnNone,
1104+
AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT,
1105+
AnnotateMoves::Enabled(Some(limit)) => limit,
1106+
};
1107+
1108+
let ty = self.monomorphized_place_ty(place);
1109+
let layout = bx.cx().layout_of(ty);
1110+
let ty_size = layout.size.bytes();
1111+
1112+
// Only annotate if type has a memory representation and exceeds size limit (and has a
1113+
// non-zero size)
1114+
if layout.is_zst()
1115+
|| ty_size < size_limit
1116+
|| !matches!(layout.backend_repr,BackendRepr::Memory{ .. })
1117+
{
1118+
returnNone;
1119+
}
1120+
1121+
// Look up the DefId for compiler_move or compiler_copy lang item
1122+
let def_id = tcx.lang_items().get(kind)?;
1123+
1124+
// Create generic args: compiler_move<T, SIZE> or compiler_copy<T, SIZE>
1125+
let size_const = ty::Const::from_target_usize(tcx, ty_size);
1126+
let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]);
1127+
1128+
// Create the Instance
1129+
let typing_env = self.mir.typing_env(tcx);
1130+
let instance = ty::Instance::expect_resolve(
1131+
tcx,
1132+
typing_env,
1133+
def_id,
1134+
generic_args,
1135+
rustc_span::DUMMY_SP,// span only used for error messages
1136+
);
1137+
1138+
Some(instance)
1139+
}
10401140
}

0 commit comments

Comments
 (0)