mirror of
https://github.com/rust-lang/rust.git
synced 2025-12-02 12:39:01 +00:00
This implements a new unstable compiler flag `-Zannotate-moves` that makes
move and copy operations visible in profilers by creating synthetic debug
information. This is achieved with zero runtime cost by manipulating debug
info scopes to make moves/copies appear as calls to `compiler_move<T, SIZE>`
and `compiler_copy<T, SIZE>` marker functions in profiling tools.
This allows developers to identify expensive move/copy operations in their
code using standard profiling tools, without requiring specialized tooling
or runtime instrumentation.
The implementation works at codegen time. When processing MIR operands
(`Operand::Move` and `Operand::Copy`), the codegen creates an `OperandRef`
with an optional `move_annotation` field containing an `Instance` of the
appropriate profiling marker function. When storing the operand,
`store_with_annotation()` wraps the store operation in a synthetic debug
scope that makes it appear inlined from the marker.
Two marker functions (`compiler_move` and `compiler_copy`) are defined
in `library/core/src/profiling.rs`. These are never actually called -
they exist solely as debug info anchors.
Operations are only annotated if the type:
- Meets the size threshold (default: 65 bytes, configurable via
`-Zannotate-moves=SIZE`)
- Has a non-scalar backend representation (scalars use registers,
not memcpy)
This has a very small size impact on object file size. With the default
limit it's well under 0.1%, and even with a very small limit of 8 bytes
it's still ~1.5%. This could be enabled by default.
193 lines
8.1 KiB
Rust
193 lines
8.1 KiB
Rust
//@ compile-flags: -Z annotate-moves=1 -Copt-level=0 -g
|
|
|
|
#![crate_type = "lib"]
|
|
|
|
// Test with large array (non-struct type, Copy)
|
|
type LargeArray = [u64; 20]; // 160 bytes
|
|
|
|
#[derive(Clone, Default)]
|
|
struct NonCopyU64(u64);
|
|
|
|
// Test with Copy implementation
|
|
#[derive(Copy)]
|
|
struct ExplicitCopy {
|
|
data: [u64; 20], // 160 bytes
|
|
}
|
|
|
|
impl Clone for ExplicitCopy {
|
|
// CHECK-LABEL: <integration::ExplicitCopy as core::clone::Clone>::clone
|
|
fn clone(&self) -> Self {
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#EXPLICIT_COPY_LOC:]]
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#EXPLICIT_RETURN_LOC:]]
|
|
Self { data: self.data }
|
|
}
|
|
}
|
|
|
|
// Test with hand-implemented Clone (non-Copy)
|
|
struct NonCopyStruct {
|
|
data: [u64; 20], // 160 bytes
|
|
}
|
|
|
|
impl Clone for NonCopyStruct {
|
|
// CHECK-LABEL: <integration::NonCopyStruct as core::clone::Clone>::clone
|
|
fn clone(&self) -> Self {
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#CLONE_COPY_LOC:]]
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#CLONE_RETURN_LOC:]]
|
|
NonCopyStruct { data: self.data }
|
|
}
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_pure_assignment_move
|
|
pub fn test_pure_assignment_move() {
|
|
let arr: LargeArray = [42; 20];
|
|
// Arrays are initialized with a loop
|
|
// CHECK-NOT: call void @llvm.memcpy{{.*}}, !dbg ![[#]]
|
|
let _moved = arr;
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_pure_assignment_copy
|
|
pub fn test_pure_assignment_copy() {
|
|
let s = ExplicitCopy { data: [42; 20] };
|
|
// Arrays are initialized with a loop
|
|
// CHECK-NOT: call void @llvm.memcpy{{.*}}, !dbg ![[#]]
|
|
let _copied = s;
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#ASSIGN_COPY2_LOC:]]
|
|
let _copied_2 = s;
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct InitializeStruct {
|
|
field1: String,
|
|
field2: String,
|
|
field3: String,
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_init_struct
|
|
pub fn test_init_struct() {
|
|
let mut s = InitializeStruct::default();
|
|
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#INIT_STRUCT_LOC:]]
|
|
s = InitializeStruct {
|
|
field1: String::from("Hello"),
|
|
field2: String::from("from"),
|
|
field3: String::from("Rust"),
|
|
};
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_tuple_of_scalars
|
|
pub fn test_tuple_of_scalars() {
|
|
// Tuple of scalars (even if large) may use scalar-pair repr, so may not be annotated
|
|
let t: (u64, u64, u64, u64) = (1, 2, 3, 4); // 32 bytes
|
|
// Copied with explicit stores
|
|
// CHECK-NOT: call void @llvm.memcpy{{.*}}, !dbg ![[#]]
|
|
let _moved = t;
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_tuple_of_structs
|
|
pub fn test_tuple_of_structs() {
|
|
let s1 = NonCopyStruct { data: [1; 20] };
|
|
let s2 = NonCopyStruct { data: [2; 20] };
|
|
let tuple = (s1, s2); // Large tuple containing structs (320 bytes)
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#TUPLE_MOVE_LOC:]]
|
|
let _moved = tuple;
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_tuple_mixed
|
|
pub fn test_tuple_mixed() {
|
|
let s = NonCopyStruct { data: [1; 20] };
|
|
let tuple = (42u64, s); // Mixed tuple (168 bytes: 8 for u64 + 160 for struct)
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#MIXED_TUPLE_LOC:]]
|
|
let _moved = tuple;
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_explicit_copy_assignment
|
|
pub fn test_explicit_copy_assignment() {
|
|
let c1 = ExplicitCopy { data: [1; 20] };
|
|
// Initialized with loop
|
|
// CHECK-NOT: call void @llvm.memcpy{{.*}}, !dbg ![[#]]
|
|
let c2 = c1;
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#COPY2_LOC:]]
|
|
let _c3 = c1; // Can still use c1 (it was copied)
|
|
let _ = c2;
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_array_move
|
|
pub fn test_array_move() {
|
|
let arr: [String; 20] = std::array::from_fn(|i| i.to_string());
|
|
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#ARRAY_MOVE_LOC:]]
|
|
let _moved = arr;
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_array_in_struct_field
|
|
pub fn test_array_in_struct_field() {
|
|
let s = NonCopyStruct { data: [1; 20] };
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#FIELD_MOVE_LOC:]]
|
|
let data = s.data; // Move array field out of struct
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#FIELD_MOVE2_LOC:]]
|
|
let _moved = data;
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_clone_noncopy
|
|
pub fn test_clone_noncopy() {
|
|
let s = NonCopyStruct { data: [1; 20] };
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#CALL_CLONE_NONCOPY_LOC:]]
|
|
let _cloned = s.clone(); // The copy happens inside the clone() impl above
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_clone_explicit_copy
|
|
pub fn test_clone_explicit_copy() {
|
|
let c = ExplicitCopy { data: [1; 20] };
|
|
// Derived Clone on Copy type - the copy happens inside the generated clone impl
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#CALL_CLONE_COPY_LOC:]]
|
|
let _cloned = c.clone();
|
|
}
|
|
|
|
// CHECK-LABEL: integration::test_copy_ref
|
|
pub fn test_copy_ref(x: &ExplicitCopy) {
|
|
// CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#LOCAL_COPY_LOC:]]
|
|
let _local = *x;
|
|
}
|
|
|
|
// CHECK-DAG: ![[#EXPLICIT_COPY_LOC]] = !DILocation({{.*}}scope: ![[#EXPLICIT_COPY_SCOPE:]]
|
|
// CHECK-DAG: ![[#EXPLICIT_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
// CHECK-DAG: ![[#EXPLICIT_RETURN_LOC]] = !DILocation({{.*}}scope: ![[#EXPLICIT_RETURN_SCOPE:]]
|
|
// CHECK-DAG: ![[#EXPLICIT_RETURN_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#CLONE_COPY_LOC]] = !DILocation({{.*}}scope: ![[#CLONE_COPY_SCOPE:]]
|
|
// CHECK-DAG: ![[#CLONE_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
// CHECK-DAG: ![[#CLONE_RETURN_LOC]] = !DILocation({{.*}}scope: ![[#CLONE_RETURN_SCOPE:]]
|
|
// CHECK-DAG: ![[#CLONE_RETURN_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#ASSIGN_COPY2_LOC]] = !DILocation({{.*}}scope: ![[#ASSIGN_COPY2_SCOPE:]]
|
|
// CHECK-DAG: ![[#ASSIGN_COPY2_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#INIT_STRUCT_LOC]] = !DILocation({{.*}}scope: ![[#INIT_STRUCT_SCOPE:]]
|
|
// CHECK-DAG: ![[#INIT_STRUCT_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<alloc::string::String,{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#TUPLE_MOVE_LOC]] = !DILocation({{.*}}scope: ![[#TUPLE_MOVE_SCOPE:]]
|
|
// CHECK-DAG: ![[#TUPLE_MOVE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#MIXED_TUPLE_LOC]] = !DILocation({{.*}}scope: ![[#MIXED_TUPLE_SCOPE:]]
|
|
// CHECK-DAG: ![[#MIXED_TUPLE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#COPY2_LOC]] = !DILocation({{.*}}scope: ![[#COPY2_SCOPE:]]
|
|
// CHECK-DAG: ![[#COPY2_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#ARRAY_MOVE_LOC]] = !DILocation({{.*}}scope: ![[#ARRAY_MOVE_SCOPE:]]
|
|
// CHECK-DAG: ![[#ARRAY_MOVE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)alloc::string::String[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#FIELD_MOVE_LOC]] = !DILocation({{.*}}scope: ![[#FIELD_MOVE_SCOPE:]]
|
|
// CHECK-DAG: ![[#FIELD_MOVE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
// CHECK-DAG: ![[#FIELD_MOVE2_LOC]] = !DILocation({{.*}}scope: ![[#FIELD_MOVE2_SCOPE:]]
|
|
// CHECK-DAG: ![[#FIELD_MOVE2_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#CALL_CLONE_NONCOPY_LOC]] = !DILocation({{.*}}scope: ![[#CALL_CLONE_NONCOPY_SCOPE:]]
|
|
// CHECK-DAG: ![[#CALL_CLONE_NONCOPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#CALL_CLONE_COPY_LOC]] = !DILocation({{.*}}scope: ![[#CALL_CLONE_COPY_SCOPE:]]
|
|
// CHECK-DAG: ![[#CALL_CLONE_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<{{(array\$<|\[)u64[,;].*}},{{ *[0-9]+}}>"
|
|
|
|
// CHECK-DAG: ![[#LOCAL_COPY_LOC]] = !DILocation({{.*}}scope: ![[#LOCAL_COPY_SCOPE:]]
|
|
// CHECK-DAG: ![[#LOCAL_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy<integration::ExplicitCopy,{{ *[0-9]+}}>"
|