diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index 5657620879ca1..86031a8a46342 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -1069,7 +1069,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { OperandValue::Ref(place.val) }; - OperandRef { val, layout: place.layout } + OperandRef { val, layout: place.layout, move_annotation: None } } fn write_operand_repeatedly( diff --git a/compiler/rustc_codegen_gcc/src/debuginfo.rs b/compiler/rustc_codegen_gcc/src/debuginfo.rs index 0f015cc23f52f..3979f62987f2c 100644 --- a/compiler/rustc_codegen_gcc/src/debuginfo.rs +++ b/compiler/rustc_codegen_gcc/src/debuginfo.rs @@ -19,7 +19,7 @@ use crate::context::CodegenCx; pub(super) const UNKNOWN_LINE_NUMBER: u32 = 0; pub(super) const UNKNOWN_COLUMN_NUMBER: u32 = 0; -impl<'a, 'gcc, 'tcx> DebugInfoBuilderMethods for Builder<'a, 'gcc, 'tcx> { +impl<'a, 'gcc, 'tcx> DebugInfoBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { // FIXME(eddyb) find a common convention for all of the debuginfo-related // names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.). fn dbg_var_addr( diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index 3793a1470aada..77bbdcf3ff815 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -253,7 +253,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> { ); bx.lifetime_end(llscratch, scratch_size); } - _ => { + PassMode::Pair(..) | PassMode::Direct { .. } => { OperandRef::from_immediate_or_packed_pair(bx, val, self.layout).val.store(bx, dst); } } diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index cfbb9541ecd2d..549829e74baca 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -451,7 +451,7 @@ fn report_inline_asm( llvm::DiagnosticLevel::Warning => Level::Warning, llvm::DiagnosticLevel::Note | llvm::DiagnosticLevel::Remark => Level::Note, }; - let msg = msg.strip_prefix("error: ").unwrap_or(&msg).to_string(); + let msg = msg.trim_prefix("error: ").to_string(); cgcx.diag_emitter.inline_asm_error(span, msg, level, source); } diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 49d38e05c8b06..42e03e9835e35 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -751,7 +751,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { OperandValue::Ref(place.val) }; - OperandRef { val, layout: place.layout } + OperandRef { val, layout: place.layout, move_annotation: None } } fn write_operand_repeatedly( diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index b95ad03b70e03..c2c55ee2b64f9 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -146,7 +146,7 @@ impl<'ll> Builder<'_, 'll, '_> { } } -impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> { +impl<'ll, 'tcx> DebugInfoBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { // FIXME(eddyb) find a common convention for all of the debuginfo-related // names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.). fn dbg_var_addr( @@ -284,6 +284,57 @@ impl<'ll> DebugInfoBuilderMethods for Builder<'_, 'll, '_> { llvm::set_value_name(value, name.as_bytes()); } } + + /// Annotate move/copy operations with debug info for profiling. + /// + /// This creates a temporary debug scope that makes the move/copy appear as an inlined call to + /// `compiler_move()` or `compiler_copy()`. The provided closure is executed + /// with this temporary debug location active. + /// + /// The `instance` parameter should be the monomorphized instance of the `compiler_move` or + /// `compiler_copy` function with the actual type and size. + fn with_move_annotation( + &mut self, + instance: ty::Instance<'tcx>, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + // Save the current debug location + let saved_loc = self.get_dbg_loc(); + + // Create a DIScope for the compiler_move/compiler_copy function + // We use the function's FnAbi for debug info generation + let fn_abi = self + .cx() + .tcx + .fn_abi_of_instance( + self.cx().typing_env().as_query_input((instance, ty::List::empty())), + ) + .unwrap(); + + let di_scope = self.cx().dbg_scope_fn(instance, fn_abi, None); + + // Create an inlined debug location: + // - scope: the compiler_move/compiler_copy function + // - inlined_at: the current location (where the move/copy actually occurs) + // - span: use the function's definition span + let fn_span = self.cx().tcx.def_span(instance.def_id()); + let inlined_loc = self.cx().dbg_loc(di_scope, saved_loc, fn_span); + + // Set the temporary debug location + self.set_dbg_loc(inlined_loc); + + // Execute the closure (which will generate the memcpy) + let result = f(self); + + // Restore the original debug location + if let Some(loc) = saved_loc { + self.set_dbg_loc(loc); + } else { + self.clear_dbg_loc(); + } + + result + } } /// A source code location used to generate debug information. diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 982d5cd3ac418..c8ad8f0c10d0d 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -17,6 +17,7 @@ #![feature(macro_derive)] #![feature(rustdoc_internals)] #![feature(slice_as_array)] +#![feature(trim_prefix_suffix)] #![feature(try_blocks)] // tidy-alphabetical-end diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 0d1e27a34dc5b..cce33107e2c27 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -557,9 +557,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let op = match self.locals[mir::RETURN_PLACE] { LocalRef::Operand(op) => op, LocalRef::PendingOperand => bug!("use of return before def"), - LocalRef::Place(cg_place) => { - OperandRef { val: Ref(cg_place.val), layout: cg_place.layout } - } + LocalRef::Place(cg_place) => OperandRef { + val: Ref(cg_place.val), + layout: cg_place.layout, + move_annotation: None, + }, LocalRef::UnsizedPlace(_) => bug!("return type must be sized"), }; let llslot = match op.val { @@ -1155,7 +1157,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { | (&mir::Operand::Constant(_), Ref(PlaceValue { llextra: None, .. })) => { let tmp = PlaceRef::alloca(bx, op.layout); bx.lifetime_start(tmp.val.llval, tmp.layout.size); - op.val.store(bx, tmp); + op.store_with_annotation(bx, tmp); op.val = Ref(tmp.val); lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size)); } @@ -1563,13 +1565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { }; let scratch = PlaceValue::alloca(bx, arg.layout.size, required_align); bx.lifetime_start(scratch.llval, arg.layout.size); - op.val.store(bx, scratch.with_type(arg.layout)); + op.store_with_annotation(bx, scratch.with_type(arg.layout)); lifetime_ends_after_call.push((scratch.llval, arg.layout.size)); (scratch.llval, scratch.align, true) } PassMode::Cast { .. } => { let scratch = PlaceRef::alloca(bx, arg.layout); - op.val.store(bx, scratch); + op.store_with_annotation(bx, scratch); (scratch.val.llval, scratch.val.align, true) } _ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi, false), diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 1670e2da71fb3..819abb9ae644d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -480,6 +480,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( return local(OperandRef { val: OperandValue::Pair(a, b), layout: arg.layout, + move_annotation: None, }); } _ => {} @@ -552,6 +553,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( fx.caller_location = Some(OperandRef { val: OperandValue::Immediate(bx.get_param(llarg_idx)), layout: arg.layout, + move_annotation: None, }); } diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs index 88a8e2a844cbc..5a139702f81d9 100644 --- a/compiler/rustc_codegen_ssa/src/mir/operand.rs +++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs @@ -5,12 +5,13 @@ use rustc_abi as abi; use rustc_abi::{ Align, BackendRepr, FIRST_VARIANT, FieldIdx, Primitive, Size, TagEncoding, VariantIdx, Variants, }; +use rustc_hir::LangItem; use rustc_middle::mir::interpret::{Pointer, Scalar, alloc_range}; use rustc_middle::mir::{self, ConstValue}; -use rustc_middle::ty::Ty; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; +use rustc_middle::ty::{self, Ty}; use rustc_middle::{bug, span_bug}; -use rustc_session::config::OptLevel; +use rustc_session::config::{AnnotateMoves, DebugInfo, OptLevel}; use tracing::{debug, instrument}; use super::place::{PlaceRef, PlaceValue}; @@ -131,6 +132,10 @@ pub struct OperandRef<'tcx, V> { /// The layout of value, based on its Rust type. pub layout: TyAndLayout<'tcx>, + + /// Annotation for profiler visibility of move/copy operations. + /// When set, the store operation should appear as an inlined call to this function. + pub move_annotation: Option>, } impl fmt::Debug for OperandRef<'_, V> { @@ -142,7 +147,7 @@ impl fmt::Debug for OperandRef<'_, V> { impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { pub fn zero_sized(layout: TyAndLayout<'tcx>) -> OperandRef<'tcx, V> { assert!(layout.is_zst()); - OperandRef { val: OperandValue::ZeroSized, layout } + OperandRef { val: OperandValue::ZeroSized, layout, move_annotation: None } } pub(crate) fn from_const>( @@ -180,7 +185,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { } }; - OperandRef { val, layout } + OperandRef { val, layout, move_annotation: None } } fn from_const_alloc>( @@ -214,7 +219,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { let size = s.size(bx); assert_eq!(size, layout.size, "abi::Scalar size does not match layout size"); let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout)); - OperandRef { val: OperandValue::Immediate(val), layout } + OperandRef { val: OperandValue::Immediate(val), layout, move_annotation: None } } BackendRepr::ScalarPair( a @ abi::Scalar::Initialized { .. }, @@ -235,7 +240,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { b, bx.scalar_pair_element_backend_type(layout, 1, true), ); - OperandRef { val: OperandValue::Pair(a_val, b_val), layout } + OperandRef { val: OperandValue::Pair(a_val, b_val), layout, move_annotation: None } } _ if layout.is_zst() => OperandRef::zero_sized(layout), _ => { @@ -285,6 +290,22 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { self.val.deref(layout.align.abi).with_type(layout) } + /// Store this operand into a place, applying move/copy annotation if present. + /// + /// This is the preferred method for storing operands, as it automatically + /// applies profiler annotations for tracked move/copy operations. + pub fn store_with_annotation>( + self, + bx: &mut Bx, + dest: PlaceRef<'tcx, V>, + ) { + if let Some(instance) = self.move_annotation { + bx.with_move_annotation(instance, |bx| self.val.store(bx, dest)) + } else { + self.val.store(bx, dest) + } + } + /// If this operand is a `Pair`, we return an aggregate with the two values. /// For other cases, see `immediate`. pub fn immediate_or_packed_pair>( @@ -320,7 +341,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { } else { OperandValue::Immediate(llval) }; - OperandRef { val, layout } + OperandRef { val, layout, move_annotation: None } } pub(crate) fn extract_field>( @@ -388,7 +409,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { }) }; - OperandRef { val, layout: field } + OperandRef { val, layout: field, move_annotation: None } } /// Obtain the actual discriminant of a value. @@ -828,10 +849,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> { } }, }; - OperandRef { val, layout } + OperandRef { val, layout, move_annotation: None } } } +/// Default size limit for move/copy annotations (in bytes). 64 bytes is a common size of a cache +/// line, and the assumption is that anything this size or below is very cheap to move/copy, so only +/// annotate copies larger than this. +const MOVE_ANNOTATION_DEFAULT_LIMIT: u64 = 65; + impl<'a, 'tcx, V: CodegenObject> OperandValue { /// Returns an `OperandValue` that's generally UB to use in any way. /// @@ -961,7 +987,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { abi::Variants::Single { index: vidx }, ); let layout = o.layout.for_variant(bx.cx(), vidx); - o = OperandRef { val: o.val, layout } + o = OperandRef { layout, ..o } } _ => return None, } @@ -1014,7 +1040,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { match *operand { mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => { - self.codegen_consume(bx, place.as_ref()) + let kind = match operand { + mir::Operand::Move(_) => LangItem::CompilerMove, + mir::Operand::Copy(_) => LangItem::CompilerCopy, + _ => unreachable!(), + }; + + // Check if we should annotate this move/copy for profiling + let move_annotation = self.move_copy_annotation_instance(bx, place.as_ref(), kind); + + OperandRef { move_annotation, ..self.codegen_consume(bx, place.as_ref()) } } mir::Operand::Constant(ref constant) => { @@ -1030,6 +1065,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { return OperandRef { val: OperandValue::Immediate(llval), layout: bx.layout_of(ty), + move_annotation: None, }; } } @@ -1037,4 +1073,68 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } } + + /// Creates an `Instance` for annotating a move/copy operation at codegen time. + /// + /// Returns `Some(instance)` if the operation should be annotated with debug info, `None` + /// otherwise. The instance represents a monomorphized `compiler_move` or + /// `compiler_copy` function that can be used to create debug scopes. + /// + /// There are a number of conditions that must be met for an annotation to be created, but aside + /// from the basics (annotation is enabled, we're generating debuginfo), the primary concern is + /// moves/copies which could result in a real `memcpy`. So we check for the size limit, but also + /// that the underlying representation of the type is in memory. + fn move_copy_annotation_instance( + &self, + bx: &Bx, + place: mir::PlaceRef<'tcx>, + kind: LangItem, + ) -> Option> { + let tcx = bx.tcx(); + let sess = tcx.sess; + + // Skip if we're not generating debuginfo + if sess.opts.debuginfo == DebugInfo::None { + return None; + } + + // Check if annotation is enabled and get size limit (otherwise skip) + let size_limit = match sess.opts.unstable_opts.annotate_moves { + AnnotateMoves::Disabled => return None, + AnnotateMoves::Enabled(None) => MOVE_ANNOTATION_DEFAULT_LIMIT, + AnnotateMoves::Enabled(Some(limit)) => limit, + }; + + let ty = self.monomorphized_place_ty(place); + let layout = bx.cx().layout_of(ty); + let ty_size = layout.size.bytes(); + + // Only annotate if type has a memory representation and exceeds size limit (and has a + // non-zero size) + if layout.is_zst() + || ty_size < size_limit + || !matches!(layout.backend_repr, BackendRepr::Memory { .. }) + { + return None; + } + + // Look up the DefId for compiler_move or compiler_copy lang item + let def_id = tcx.lang_items().get(kind)?; + + // Create generic args: compiler_move or compiler_copy + let size_const = ty::Const::from_target_usize(tcx, ty_size); + let generic_args = tcx.mk_args(&[ty.into(), size_const.into()]); + + // Create the Instance + let typing_env = self.mir.typing_env(tcx); + let instance = ty::Instance::expect_resolve( + tcx, + typing_env, + def_id, + generic_args, + rustc_span::DUMMY_SP, // span only used for error messages + ); + + Some(instance) + } } diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 640f7211dc994..e31dc86b926f2 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -36,7 +36,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } // FIXME: consider not copying constants through stack. (Fixable by codegen'ing // constants into `OperandValue::Ref`; why don’t we do that yet if we don’t?) - cg_operand.val.store(bx, dest); + cg_operand.store_with_annotation(bx, dest); } mir::Rvalue::Cast( @@ -50,7 +50,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // Into-coerce of a thin pointer to a wide pointer -- just // use the operand path. let temp = self.codegen_rvalue_operand(bx, rvalue); - temp.val.store(bx, dest); + temp.store_with_annotation(bx, dest); return; } @@ -70,7 +70,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { debug!("codegen_rvalue: creating ugly alloca"); let scratch = PlaceRef::alloca(bx, operand.layout); scratch.storage_live(bx); - operand.val.store(bx, scratch); + operand.store_with_annotation(bx, scratch); base::coerce_unsized_into(bx, scratch, dest); scratch.storage_dead(bx); } @@ -183,7 +183,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } else { variant_dest.project_field(bx, field_index.as_usize()) }; - op.val.store(bx, field); + op.store_with_annotation(bx, field); } } dest.codegen_set_discr(bx, variant_index); @@ -191,7 +191,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { _ => { let temp = self.codegen_rvalue_operand(bx, rvalue); - temp.val.store(bx, dest); + temp.store_with_annotation(bx, dest); } } } @@ -221,7 +221,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // Since in this path we have a place anyway, we can store or copy to it, // making sure we use the destination place's alignment even if the // source would normally have a higher one. - src.val.store(bx, dst.val.with_type(src.layout)); + src.store_with_annotation(bx, dst.val.with_type(src.layout)); } } @@ -320,7 +320,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let size = Ord::max(operand.layout.size, cast.size); let temp = PlaceValue::alloca(bx, size, align); bx.lifetime_start(temp.llval, size); - operand.val.store(bx, temp.with_type(operand.layout)); + operand.store_with_annotation(bx, temp.with_type(operand.layout)); let val = bx.load_operand(temp.with_type(cast)).val; bx.lifetime_end(temp.llval, size); val @@ -478,7 +478,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let to_backend_ty = bx.cx().immediate_backend_type(cast); if operand.layout.is_uninhabited() { let val = OperandValue::Immediate(bx.cx().const_poison(to_backend_ty)); - return OperandRef { val, layout: cast }; + return OperandRef { val, layout: cast, move_annotation: None }; } let abi::BackendRepr::Scalar(to_scalar) = cast.layout.backend_repr else { bug!("Found non-scalar for cast {cast:?}"); @@ -494,7 +494,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.codegen_transmute_operand(bx, operand, cast) } }; - OperandRef { val, layout: cast } + OperandRef { val, layout: cast, move_annotation: None } } mir::Rvalue::Ref(_, bk, place) => { @@ -525,7 +525,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { ); let val_ty = op.ty(bx.tcx(), lhs.layout.ty, rhs.layout.ty); let operand_ty = Ty::new_tup(bx.tcx(), &[val_ty, bx.tcx().types.bool]); - OperandRef { val: result, layout: bx.cx().layout_of(operand_ty) } + OperandRef { + val: result, + layout: bx.cx().layout_of(operand_ty), + move_annotation: None, + } } mir::Rvalue::BinaryOp(op, box (ref lhs, ref rhs)) => { let lhs = self.codegen_operand(bx, lhs); @@ -559,6 +563,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandRef { val: OperandValue::Immediate(llresult), layout: bx.cx().layout_of(op.ty(bx.tcx(), lhs.layout.ty, rhs.layout.ty)), + move_annotation: None, } } @@ -593,7 +598,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { val.is_expected_variant_for_type(self.cx, layout), "Made wrong variant {val:?} for type {layout:?}", ); - OperandRef { val, layout } + OperandRef { val, layout, move_annotation: None } } mir::Rvalue::Discriminant(ref place) => { @@ -604,6 +609,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandRef { val: OperandValue::Immediate(discr), layout: self.cx.layout_of(discr_ty), + move_annotation: None, } } @@ -631,6 +637,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandRef { val: OperandValue::Immediate(val), layout: self.cx.layout_of(null_op.ty(tcx)), + move_annotation: None, } } @@ -663,7 +670,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } else { bx.get_static(def_id) }; - OperandRef { val: OperandValue::Immediate(static_), layout } + OperandRef { val: OperandValue::Immediate(static_), layout, move_annotation: None } } mir::Rvalue::Use(ref operand) => self.codegen_operand(bx, operand), mir::Rvalue::Repeat(ref elem, len_const) => { @@ -675,7 +682,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let array_ty = self.monomorphize(array_ty); let array_layout = bx.layout_of(array_ty); assert!(array_layout.is_zst()); - OperandRef { val: OperandValue::ZeroSized, layout: array_layout } + OperandRef { + val: OperandValue::ZeroSized, + layout: array_layout, + move_annotation: None, + } } mir::Rvalue::Aggregate(ref kind, ref fields) => { let (variant_index, active_field_index) = match **kind { @@ -704,7 +715,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // more optimizability, if that turns out to be helpful. bx.abort(); let val = OperandValue::poison(bx, layout); - OperandRef { val, layout } + OperandRef { val, layout, move_annotation: None } } Ok(maybe_tag_value) => { if let Some((tag_field, tag_imm)) = maybe_tag_value { @@ -718,7 +729,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let operand = self.codegen_operand(bx, operand); let binder_ty = self.monomorphize(binder_ty); let layout = bx.cx().layout_of(binder_ty); - OperandRef { val: operand.val, layout } + OperandRef { val: operand.val, layout, move_annotation: None } } mir::Rvalue::CopyForDeref(_) => bug!("`CopyForDeref` in codegen"), mir::Rvalue::ShallowInitBox(..) => bug!("`ShallowInitBox` in codegen"), @@ -745,7 +756,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { "Address of place was unexpectedly {val:?} for pointee type {ty:?}", ); - OperandRef { val, layout: self.cx.layout_of(mk_ptr_ty(self.cx.tcx(), ty)) } + OperandRef { + val, + layout: self.cx.layout_of(mk_ptr_ty(self.cx.tcx(), ty)), + move_annotation: None, + } } fn codegen_scalar_binop( diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index 60296e36e0c1a..f57f9108f744b 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -37,7 +37,7 @@ pub trait BuilderMethods<'a, 'tcx>: + FnAbiOf<'tcx, FnAbiOfResult = &'tcx FnAbi<'tcx, Ty<'tcx>>> + Deref + CoverageInfoBuilderMethods<'tcx> - + DebugInfoBuilderMethods + + DebugInfoBuilderMethods<'tcx> + ArgAbiBuilderMethods<'tcx> + AbiBuilderMethods + IntrinsicCallBuilderMethods<'tcx> diff --git a/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs b/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs index a4da6c915deec..268d863dcbd2c 100644 --- a/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs +++ b/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs @@ -64,7 +64,7 @@ pub trait DebugInfoCodegenMethods<'tcx>: BackendTypes { ) -> Self::DIVariable; } -pub trait DebugInfoBuilderMethods: BackendTypes { +pub trait DebugInfoBuilderMethods<'tcx>: BackendTypes { // FIXME(eddyb) find a common convention for all of the debuginfo-related // names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.). fn dbg_var_addr( @@ -95,4 +95,18 @@ pub trait DebugInfoBuilderMethods: BackendTypes { fn clear_dbg_loc(&mut self); fn insert_reference_to_gdb_debug_scripts_section_global(&mut self); fn set_var_name(&mut self, value: Self::Value, name: &str); + + /// Hook to allow move/copy operations to be annotated for profiling. + /// + /// The `instance` parameter should be the monomorphized instance of the + /// `compiler_move` or `compiler_copy` function with the actual type and size. + /// + /// Default implementation does no annotation (just executes the closure). + fn with_move_annotation( + &mut self, + _instance: Instance<'tcx>, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + f(self) + } } diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 462ca98c21151..8353aac74fd91 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -13,6 +13,7 @@ #![feature(panic_backtrace_config)] #![feature(panic_update_hook)] #![feature(rustdoc_internals)] +#![feature(trim_prefix_suffix)] #![feature(try_blocks)] // tidy-alphabetical-end @@ -466,7 +467,7 @@ pub enum Compilation { fn handle_explain(early_dcx: &EarlyDiagCtxt, registry: Registry, code: &str, color: ColorConfig) { // Allow "E0123" or "0123" form. let upper_cased_code = code.to_ascii_uppercase(); - if let Ok(code) = upper_cased_code.strip_prefix('E').unwrap_or(&upper_cased_code).parse::() + if let Ok(code) = upper_cased_code.trim_prefix('E').parse::() && code <= ErrCode::MAX_AS_U32 && let Ok(description) = registry.try_find_description(ErrCode::from_u32(code)) { @@ -1267,7 +1268,7 @@ fn warn_on_confusing_output_filename_flag( if let Some(name) = matches.opt_str("o") && let Some(suspect) = args.iter().find(|arg| arg.starts_with("-o") && *arg != "-o") { - let filename = suspect.strip_prefix("-").unwrap_or(suspect); + let filename = suspect.trim_prefix("-"); let optgroups = config::rustc_optgroups(); let fake_args = ["optimize", "o0", "o1", "o2", "o3", "ofast", "og", "os", "oz"]; diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 9f7f4c7583412..e454ed58699c9 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -345,6 +345,10 @@ language_item_table! { EhPersonality, sym::eh_personality, eh_personality, Target::Fn, GenericRequirement::None; EhCatchTypeinfo, sym::eh_catch_typeinfo, eh_catch_typeinfo, Target::Static, GenericRequirement::None; + // Profiling markers for move/copy operations (used by -Z annotate-moves) + CompilerMove, sym::compiler_move, compiler_move_fn, Target::Fn, GenericRequirement::Exact(2); + CompilerCopy, sym::compiler_copy, compiler_copy_fn, Target::Fn, GenericRequirement::Exact(2); + OwnedBox, sym::owned_box, owned_box, Target::Struct, GenericRequirement::Minimum(1); GlobalAlloc, sym::global_alloc_ty, global_alloc_ty, Target::Struct, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index 936b02cac5bb2..74bf68362fc5f 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -1097,14 +1097,14 @@ fn check_region_bounds_on_impl_item<'tcx>( .expect("expected impl item to have generics or else we can't compare them") .span; - let mut generics_span = None; + let mut generics_span = tcx.def_span(trait_m.def_id); let mut bounds_span = vec![]; let mut where_span = None; if let Some(trait_node) = tcx.hir_get_if_local(trait_m.def_id) && let Some(trait_generics) = trait_node.generics() { - generics_span = Some(trait_generics.span); + generics_span = trait_generics.span; // FIXME: we could potentially look at the impl's bounds to not point at bounds that // *are* present in the impl. for p in trait_generics.predicates { diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index d1eb328c0e766..dbad98fd79528 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -191,7 +191,7 @@ pub(crate) struct LifetimesOrBoundsMismatchOnTrait { #[label] pub span: Span, #[label(hir_analysis_generics_label)] - pub generics_span: Option, + pub generics_span: Span, #[label(hir_analysis_where_label)] pub where_span: Option, #[label(hir_analysis_bounds_label)] diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 99a9566c74cef..b4264e9993313 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -7,6 +7,7 @@ #![feature(iter_intersperse)] #![feature(iter_order_by)] #![feature(never_type)] +#![feature(trim_prefix_suffix)] // tidy-alphabetical-end mod _match; diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 8e9c6eb046aed..7d6982cb024c3 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -2551,7 +2551,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If this is a floating point literal that ends with '.', // get rid of it to stop this from becoming a member access. - let snippet = snippet.strip_suffix('.').unwrap_or(&snippet); + let snippet = snippet.trim_suffix('.'); err.span_suggestion( lit.span, format!( diff --git a/compiler/rustc_index/Cargo.toml b/compiler/rustc_index/Cargo.toml index e46a1a7f7606f..edcd7816a60e0 100644 --- a/compiler/rustc_index/Cargo.toml +++ b/compiler/rustc_index/Cargo.toml @@ -8,7 +8,7 @@ edition = "2024" rustc_index_macros = { path = "../rustc_index_macros" } rustc_macros = { path = "../rustc_macros", optional = true } rustc_serialize = { path = "../rustc_serialize", optional = true } -smallvec = "1.8.1" +smallvec = { version = "1.8.1", optional = true } # tidy-alphabetical-end [features] @@ -17,6 +17,7 @@ default = ["nightly"] nightly = [ "dep:rustc_macros", "dep:rustc_serialize", + "dep:smallvec", "rustc_index_macros/nightly", ] rustc_randomized_layouts = [] diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 6c08b37dec083..fcba546c87b70 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -10,7 +10,7 @@ use rustc_errors::emitter::HumanReadableErrorType; use rustc_errors::{ColorConfig, registry}; use rustc_hir::attrs::NativeLibKind; use rustc_session::config::{ - AutoDiff, BranchProtection, CFGuard, Cfg, CollapseMacroDebuginfo, CoverageLevel, + AnnotateMoves, AutoDiff, BranchProtection, CFGuard, Cfg, CollapseMacroDebuginfo, CoverageLevel, CoverageOptions, DebugInfo, DumpMonoStatsFormat, ErrorOutputType, ExternEntry, ExternLocation, Externs, FmtDebug, FunctionReturn, InliningThreshold, Input, InstrumentCoverage, InstrumentXRay, LinkSelfContained, LinkerPluginLto, LocationDetail, LtoCli, MirIncludeSpans, @@ -764,6 +764,7 @@ fn test_unstable_options_tracking_hash() { // tidy-alphabetical-start tracked!(allow_features, Some(vec![String::from("lang_items")])); tracked!(always_encode_mir, true); + tracked!(annotate_moves, AnnotateMoves::Enabled(Some(1234))); tracked!(assume_incomplete_release, true); tracked!(autodiff, vec![AutoDiff::Enable, AutoDiff::NoTT]); tracked!(binary_dep_depinfo, true); diff --git a/compiler/rustc_resolve/messages.ftl b/compiler/rustc_resolve/messages.ftl index dd06c157afc7b..1569c175948e1 100644 --- a/compiler/rustc_resolve/messages.ftl +++ b/compiler/rustc_resolve/messages.ftl @@ -11,9 +11,9 @@ resolve_added_macro_use = resolve_ancestor_only = visibilities can only be restricted to ancestor modules -resolve_anonymous_lifetime_non_gat_report_error = - in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +resolve_anonymous_lifetime_non_gat_report_error = missing lifetime in associated type .label = this lifetime must come from the implemented type + .note = in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type resolve_arguments_macro_use_not_allowed = arguments to `macro_use` are not allowed here diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 0bb9909f7cbc4..eace729473e21 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -958,6 +958,8 @@ pub(crate) struct AnonymousLifetimeNonGatReportError { #[primary_span] #[label] pub(crate) lifetime: Span, + #[note] + pub(crate) decl: MultiSpan, } #[derive(Subdiagnostic)] diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 41c2e908d084d..f462ba06ce66d 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -10,6 +10,7 @@ use std::assert_matches::debug_assert_matches; use std::borrow::Cow; use std::collections::hash_map::Entry; use std::mem::{replace, swap, take}; +use std::ops::ControlFlow; use rustc_ast::visit::{ AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor, try_visit, visit_opt, walk_list, @@ -19,21 +20,21 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, DiagArgValue, ErrorGuaranteed, IntoDiagArg, StashKey, Suggestions, - pluralize, + Applicability, Diag, DiagArgValue, ErrorGuaranteed, IntoDiagArg, MultiSpan, StashKey, + Suggestions, pluralize, }; use rustc_hir::def::Namespace::{self, *}; use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::{MissingLifetimeKind, PrimTy, TraitCandidate}; use rustc_middle::middle::resolve_bound_vars::Set1; -use rustc_middle::ty::{DelegationFnSig, Visibility}; +use rustc_middle::ty::{AssocTag, DelegationFnSig, Visibility}; use rustc_middle::{bug, span_bug}; use rustc_session::config::{CrateType, ResolveDocLinks}; use rustc_session::lint; use rustc_session::parse::feature_err; use rustc_span::source_map::{Spanned, respan}; -use rustc_span::{BytePos, Ident, Span, Symbol, SyntaxContext, kw, sym}; +use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, SyntaxContext, kw, sym}; use smallvec::{SmallVec, smallvec}; use thin_vec::ThinVec; use tracing::{debug, instrument, trace}; @@ -373,11 +374,14 @@ enum LifetimeBinderKind { FnPtrType, PolyTrait, WhereBound, + // Item covers foreign items, ADTs, type aliases, trait associated items and + // trait alias associated items. Item, ConstItem, Function, Closure, ImplBlock, + // Covers only `impl` associated types. ImplAssocType, } @@ -724,6 +728,9 @@ pub(crate) struct DiagMetadata<'ast> { /// The current impl items (used to suggest). current_impl_items: Option<&'ast [Box]>, + /// The current impl items (used to suggest). + current_impl_item: Option<&'ast AssocItem>, + /// When processing impl trait currently_processing_impl_trait: Option<(TraitRef, Ty)>, @@ -1880,9 +1887,31 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ty: ty.span, }); } else { + let decl = if !trait_id.is_local() + && let Some(assoc) = self.diag_metadata.current_impl_item + && let AssocItemKind::Type(_) = assoc.kind + && let assocs = self.r.tcx.associated_items(trait_id) + && let Some(ident) = assoc.kind.ident() + && let Some(assoc) = assocs.find_by_ident_and_kind( + self.r.tcx, + ident, + AssocTag::Type, + trait_id, + ) { + let mut decl: MultiSpan = + self.r.tcx.def_span(assoc.def_id).into(); + decl.push_span_label( + self.r.tcx.def_span(trait_id), + String::new(), + ); + decl + } else { + DUMMY_SP.into() + }; let mut err = self.r.dcx().create_err( errors::AnonymousLifetimeNonGatReportError { lifetime: lifetime.ident.span, + decl, }, ); self.point_at_impl_lifetimes(&mut err, i, lifetime.ident.span); @@ -1924,17 +1953,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } fn point_at_impl_lifetimes(&mut self, err: &mut Diag<'_>, i: usize, lifetime: Span) { - let Some((rib, span)) = self.lifetime_ribs[..i] - .iter() - .rev() - .skip(1) - .filter_map(|rib| match rib.kind { + let Some((rib, span)) = + self.lifetime_ribs[..i].iter().rev().find_map(|rib| match rib.kind { LifetimeRibKind::Generics { span, kind: LifetimeBinderKind::ImplBlock, .. } => { Some((rib, span)) } _ => None, }) - .next() else { return; }; @@ -1956,11 +1981,63 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ); } } else { - err.span_label( - span, - "you could add a lifetime on the impl block, if the trait or the self type can \ - have one", - ); + struct AnonRefFinder; + impl<'ast> Visitor<'ast> for AnonRefFinder { + type Result = ControlFlow; + + fn visit_ty(&mut self, ty: &'ast ast::Ty) -> Self::Result { + if let ast::TyKind::Ref(None, mut_ty) = &ty.kind { + return ControlFlow::Break(mut_ty.ty.span.shrink_to_lo()); + } + visit::walk_ty(self, ty) + } + + fn visit_lifetime( + &mut self, + lt: &'ast ast::Lifetime, + _cx: visit::LifetimeCtxt, + ) -> Self::Result { + if lt.ident.name == kw::UnderscoreLifetime { + return ControlFlow::Break(lt.ident.span); + } + visit::walk_lifetime(self, lt) + } + } + + if let Some(ty) = &self.diag_metadata.current_self_type + && let ControlFlow::Break(sp) = AnonRefFinder.visit_ty(ty) + { + err.multipart_suggestion_verbose( + "add a lifetime to the impl block and use it in the self type and associated \ + type", + vec![ + (span, "<'a>".to_string()), + (sp, "'a ".to_string()), + (lifetime.shrink_to_hi(), "'a ".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else if let Some(item) = &self.diag_metadata.current_item + && let ItemKind::Impl(impl_) = &item.kind + && let Some(of_trait) = &impl_.of_trait + && let ControlFlow::Break(sp) = AnonRefFinder.visit_trait_ref(&of_trait.trait_ref) + { + err.multipart_suggestion_verbose( + "add a lifetime to the impl block and use it in the trait and associated type", + vec![ + (span, "<'a>".to_string()), + (sp, "'a".to_string()), + (lifetime.shrink_to_hi(), "'a ".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else { + err.span_label( + span, + "you could add a lifetime on the impl block, if the trait or the self type \ + could have one", + ); + } } } @@ -3304,6 +3381,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ) { use crate::ResolutionError::*; self.resolve_doc_links(&item.attrs, MaybeExported::ImplItem(trait_id.ok_or(&item.vis))); + let prev = self.diag_metadata.current_impl_item.take(); + self.diag_metadata.current_impl_item = Some(&item); match &item.kind { AssocItemKind::Const(box ast::ConstItem { ident, @@ -3452,6 +3531,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { panic!("unexpanded macro in resolve!") } } + self.diag_metadata.current_impl_item = prev; } fn check_trait_item( diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 57f81ebf0d81e..1d00a6b81fabc 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -2608,7 +2608,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let (span, text) = match path.segments.first() { Some(seg) if let Some(name) = seg.ident.as_str().strip_prefix("let") => { // a special case for #117894 - let name = name.strip_prefix('_').unwrap_or(name); + let name = name.trim_prefix('_'); (ident_span, format!("let {name}")) } _ => (ident_span.shrink_to_lo(), "let ".to_string()), diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index c7f65ff03f823..9f68b93520d29 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -22,6 +22,7 @@ #![feature(ptr_as_ref_unchecked)] #![feature(rustc_attrs)] #![feature(rustdoc_internals)] +#![feature(trim_prefix_suffix)] #![recursion_limit = "256"] // tidy-alphabetical-end diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 970d16313584e..872770def1775 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -400,10 +400,10 @@ fn preprocess_link(link: &str) -> Box { let link = link.split('#').next().unwrap(); let link = link.trim(); let link = link.rsplit('@').next().unwrap(); - let link = link.strip_suffix("()").unwrap_or(link); - let link = link.strip_suffix("{}").unwrap_or(link); - let link = link.strip_suffix("[]").unwrap_or(link); - let link = if link != "!" { link.strip_suffix('!').unwrap_or(link) } else { link }; + let link = link.trim_suffix("()"); + let link = link.trim_suffix("{}"); + let link = link.trim_suffix("[]"); + let link = if link != "!" { link.trim_suffix('!') } else { link }; let link = link.trim(); strip_generics_from_path(link).unwrap_or_else(|_| link.into()) } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index ce6f6434e793d..a9d96586890e4 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -262,6 +262,16 @@ pub enum AutoDiff { NoTT, } +/// The different settings that the `-Z annotate-moves` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum AnnotateMoves { + /// `-Z annotate-moves=no` (or `off`, `false` etc.) + Disabled, + /// `-Z annotate-moves` or `-Z annotate-moves=yes` (use default size limit) + /// `-Z annotate-moves=SIZE` (use specified size limit) + Enabled(Option), +} + /// Settings for `-Z instrument-xray` flag. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct InstrumentXRay { @@ -3239,13 +3249,13 @@ pub(crate) mod dep_tracking { }; use super::{ - AutoDiff, BranchProtection, CFGuard, CFProtection, CollapseMacroDebuginfo, CoverageOptions, - CrateType, DebugInfo, DebugInfoCompression, ErrorOutputType, FmtDebug, FunctionReturn, - InliningThreshold, InstrumentCoverage, InstrumentXRay, LinkerPluginLto, LocationDetail, - LtoCli, MirStripDebugInfo, NextSolverConfig, Offload, OomStrategy, OptLevel, OutFileName, - OutputType, OutputTypes, PatchableFunctionEntry, Polonius, RemapPathScopeComponents, - ResolveDocLinks, SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, - SymbolManglingVersion, WasiExecModel, + AnnotateMoves, AutoDiff, BranchProtection, CFGuard, CFProtection, CollapseMacroDebuginfo, + CoverageOptions, CrateType, DebugInfo, DebugInfoCompression, ErrorOutputType, FmtDebug, + FunctionReturn, InliningThreshold, InstrumentCoverage, InstrumentXRay, LinkerPluginLto, + LocationDetail, LtoCli, MirStripDebugInfo, NextSolverConfig, Offload, OomStrategy, + OptLevel, OutFileName, OutputType, OutputTypes, PatchableFunctionEntry, Polonius, + RemapPathScopeComponents, ResolveDocLinks, SourceFileHashAlgorithm, SplitDwarfKind, + SwitchWithOptPath, SymbolManglingVersion, WasiExecModel, }; use crate::lint; use crate::utils::NativeLib; @@ -3288,6 +3298,7 @@ pub(crate) mod dep_tracking { impl_dep_tracking_hash_via_hash!( (), + AnnotateMoves, AutoDiff, Offload, bool, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index c9d73adf31d49..893302bb1338d 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -864,6 +864,8 @@ mod desc { pub(crate) const parse_linker_features: &str = "a list of enabled (`+` prefix) and disabled (`-` prefix) features: `lld`"; pub(crate) const parse_polonius: &str = "either no value or `legacy` (the default), or `next`"; + pub(crate) const parse_annotate_moves: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc.), or a size limit in bytes"; pub(crate) const parse_stack_protector: &str = "one of (`none` (default), `basic`, `strong`, or `all`)"; pub(crate) const parse_branch_protection: &str = "a `,` separated combination of `bti`, `gcs`, `pac-ret`, (optionally with `pc`, `b-key`, `leaf` if `pac-ret` is set)"; @@ -949,6 +951,29 @@ pub mod parse { } } + pub(crate) fn parse_annotate_moves(slot: &mut AnnotateMoves, v: Option<&str>) -> bool { + let mut bslot = false; + let mut nslot = 0u64; + + *slot = match v { + // No value provided: -Z annotate-moves (enable with default limit) + None => AnnotateMoves::Enabled(None), + // Explicit boolean value provided: -Z annotate-moves=yes/no + s @ Some(_) if parse_bool(&mut bslot, s) => { + if bslot { + AnnotateMoves::Enabled(None) + } else { + AnnotateMoves::Disabled + } + } + // With numeric limit provided: -Z annotate-moves=1234 + s @ Some(_) if parse_number(&mut nslot, s) => AnnotateMoves::Enabled(Some(nslot)), + _ => return false, + }; + + true + } + /// Use this for any string option that has a static default. pub(crate) fn parse_string(slot: &mut String, v: Option<&str>) -> bool { match v { @@ -2198,6 +2223,9 @@ options! { "only allow the listed language features to be enabled in code (comma separated)"), always_encode_mir: bool = (false, parse_bool, [TRACKED], "encode MIR of all functions into the crate metadata (default: no)"), + annotate_moves: AnnotateMoves = (AnnotateMoves::Disabled, parse_annotate_moves, [TRACKED], + "emit debug info for compiler-generated move and copy operations \ + to make them visible in profilers. Can be a boolean or a size limit in bytes (default: disabled)"), assert_incr_state: Option = (None, parse_opt_string, [UNTRACKED], "assert that the incremental cache is in given state: \ either `loaded` or `not-loaded`."), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 86b6bb16835b9..f2cc68cf7f965 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -695,7 +695,9 @@ symbols! { compile_error, compiler, compiler_builtins, + compiler_copy, compiler_fence, + compiler_move, concat, concat_bytes, concat_idents, diff --git a/compiler/rustc_type_ir_macros/Cargo.toml b/compiler/rustc_type_ir_macros/Cargo.toml index 15a5557509929..14bffa403a867 100644 --- a/compiler/rustc_type_ir_macros/Cargo.toml +++ b/compiler/rustc_type_ir_macros/Cargo.toml @@ -10,6 +10,6 @@ proc-macro = true # tidy-alphabetical-start proc-macro2 = "1" quote = "1" -syn = { version = "2.0.9", features = ["full"] } +syn = { version = "2.0.9", features = ["full", "visit-mut"] } synstructure = "0.13.0" # tidy-alphabetical-end diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index f1948fc778ce2..121d26c8a3e3f 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -282,6 +282,8 @@ pub mod num; pub mod hint; pub mod intrinsics; pub mod mem; +#[unstable(feature = "profiling_marker_api", issue = "148197")] +pub mod profiling; pub mod ptr; #[unstable(feature = "ub_checks", issue = "none")] pub mod ub_checks; diff --git a/library/core/src/profiling.rs b/library/core/src/profiling.rs new file mode 100644 index 0000000000000..db4a62480a3a1 --- /dev/null +++ b/library/core/src/profiling.rs @@ -0,0 +1,33 @@ +//! Profiling markers for compiler instrumentation. + +/// Profiling marker for move operations. +/// +/// This function is never called at runtime. When `-Z annotate-moves` is enabled, +/// the compiler creates synthetic debug info that makes move operations appear as +/// calls to this function in profilers. +/// +/// The `SIZE` parameter encodes the size of the type being copied. It's the same as +/// `size_of::()`, and is only present for convenience. +#[unstable(feature = "profiling_marker_api", issue = "148197")] +#[lang = "compiler_move"] +pub fn compiler_move(_src: *const T, _dst: *mut T) { + unreachable!( + "compiler_move marks where the compiler-generated a memcpy for moves. It is never actually called." + ) +} + +/// Profiling marker for copy operations. +/// +/// This function is never called at runtime. When `-Z annotate-moves` is enabled, +/// the compiler creates synthetic debug info that makes copy operations appear as +/// calls to this function in profilers. +/// +/// The `SIZE` parameter encodes the size of the type being copied. It's the same as +/// `size_of::()`, and is only present for convenience. +#[unstable(feature = "profiling_marker_api", issue = "148197")] +#[lang = "compiler_copy"] +pub fn compiler_copy(_src: *const T, _dst: *mut T) { + unreachable!( + "compiler_copy marks where the compiler-generated a memcpy for Copies. It is never actually called." + ) +} diff --git a/src/doc/unstable-book/src/compiler-flags/annotate-moves.md b/src/doc/unstable-book/src/compiler-flags/annotate-moves.md new file mode 100644 index 0000000000000..46d8faf18fa07 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/annotate-moves.md @@ -0,0 +1,92 @@ +# `annotate-moves` + +The tracking issue for this feature is: [#148197](https://github.com/rust-lang/rust/issues/148197). + +------------------------ + + +The `-Z annotate-moves` flag enables annotation of compiler-generated +move and copy operations, making them visible in profilers and stack traces +for performance debugging. + +When enabled, the compiler manipulates debug info to make large move and copy +operations appear as if they were inlined calls to `core::profiling::compiler_move` +and `core::profiling::compiler_copy`. No actual function calls are generated - +this is purely a debug info transformation that makes expensive memory operations +visible in profilers and stack traces. + +## Syntax + +```bash +rustc -Z annotate-moves[=] +``` + +Where `` can be: +- A boolean: `true`, `false`, `yes`, `no`, `on`, `off` +- A number: size threshold in bytes (e.g., `128`) +- Omitted: enables with default threshold (65 bytes) + +## Options + +- `-Z annotate-moves` or `-Z annotate-moves=true`: Enable with default size limit +- `-Z annotate-moves=false`: Disable annotation +- `-Z annotate-moves=N`: Enable with custom size limit of N bytes + +## Examples + +```bash +# Enable annotation with default threshold (65 bytes) +rustc -Z annotate-moves main.rs + +# Enable with custom 128-byte threshold +rustc -Z annotate-moves=128 main.rs + +# Only annotate very large moves (1KB+) +rustc -Z annotate-moves=1024 main.rs + +# Explicitly disable +rustc -Z annotate-moves=false main.rs +``` + +## Behavior + +The annotation only applies to: +- Types equal or larger than the specified size threshold +- Non-immediate types (those that would generate `memcpy`) +- Operations that actually move/copy data (not ZST types) + +Stack traces will show the operations: +```text +0: memcpy +1: core::profiling::compiler_move:: +2: my_function +``` + +The `compiler_move` and `compiler_copy` functions have two generic parameters: +the type being moved/copied and its size in bytes. The size is identical to +`size_of::()`, and is present just so that it's easy to immediately tell how +large the copy is. + +Note that this requires v0 mangling to be properly encoded; legacy mangling does +not substitute these with a specific type and size. + +## Example + +```rust +#[derive(Clone)] +struct LargeData { + buffer: [u8; 1000], +} + +fn example() { + let data = LargeData { buffer: [0; 1000] }; + let copy = data.clone(); // Shows as compiler_copy in profiler + let moved = data; // Shows as compiler_move in profiler +} +``` + +## Overhead + +This has no effect on generated code; it only adds debuginfo. The overhead is +typically very small; on rustc itself, the default limit of 65 bytes adds about +0.055% to the binary size. diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 752cd2e610a54..a4d377432c914 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -264,9 +264,7 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { \ ", added_classes = added_classes.join(" "), - text = Escape( - original_text.strip_suffix('\n').unwrap_or(&original_text) - ), + text = Escape(original_text.trim_suffix('\n')), ) .into(), )); diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index c79f63fbc2032..f04f94432b808 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -185,7 +185,7 @@ impl SourceCollector<'_, '_> { }; // Remove the utf-8 BOM if any - let contents = contents.strip_prefix('\u{feff}').unwrap_or(&contents); + let contents = contents.trim_prefix('\u{feff}'); let shared = &self.cx.shared; // Create the intermediate directories diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 24087c4c4f580..e88180c3033b2 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -17,6 +17,7 @@ #![feature(iter_order_by)] #![feature(rustc_private)] #![feature(test)] +#![feature(trim_prefix_suffix)] #![warn(rustc::internal)] // tidy-alphabetical-end diff --git a/tests/codegen-llvm/annotate-moves/call-arg-scope.rs b/tests/codegen-llvm/annotate-moves/call-arg-scope.rs new file mode 100644 index 0000000000000..527ab0b09d7c8 --- /dev/null +++ b/tests/codegen-llvm/annotate-moves/call-arg-scope.rs @@ -0,0 +1,114 @@ +//@ compile-flags: -Z annotate-moves=8 -Copt-level=0 -g +// +// This test verifies that function call and return instructions use the correct debug scopes +// when passing/returning large values. The actual move/copy operations may be annotated, +// but the CALL and RETURN instructions themselves should reference the source location, +// NOT have an inlinedAt scope pointing to compiler_move/compiler_copy. + +#![crate_type = "lib"] + +#[derive(Clone, Copy)] +pub struct LargeStruct { + pub data: [u64; 20], // 160 bytes +} + +#[derive(Clone, Copy)] +pub struct MediumStruct { + pub data: [u64; 5], // 40 bytes +} + +pub struct SmallStruct { + pub x: u32, // 4 bytes +} + +// ============================================================================ +// Test 1: Single argument call +// ============================================================================ + +// CHECK-LABEL: call_arg_scope::test_call_with_single_arg +pub fn test_call_with_single_arg(s: LargeStruct) { + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#CALL1_ARG_LOC:]] + // CHECK: call {{.*}}@{{.*}}helper_single{{.*}}({{.*}}), !dbg ![[#CALL1_LOC:]] + helper_single(s); +} + +#[inline(never)] +fn helper_single(_s: LargeStruct) {} + +// ============================================================================ +// Test 2: Multiple arguments of different types +// ============================================================================ + +// CHECK-LABEL: call_arg_scope::test_call_with_multiple_args +pub fn test_call_with_multiple_args(large: LargeStruct, medium: MediumStruct, small: SmallStruct) { + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#CALL2_ARG1_LOC:]] + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#CALL2_ARG2_LOC:]] + // CHECK: call {{.*}}@{{.*}}helper_multiple{{.*}}({{.*}}), !dbg ![[#CALL2_LOC:]] + helper_multiple(large, medium, small); +} + +#[inline(never)] +fn helper_multiple(_l: LargeStruct, _m: MediumStruct, _s: SmallStruct) {} + +// ============================================================================ +// Test 3: Return value +// ============================================================================ + +// CHECK-LABEL: call_arg_scope::test_return_large_value +pub fn test_return_large_value() -> LargeStruct { + let s = LargeStruct { data: [42; 20] }; + // CHECK: ret {{.*}}, !dbg ![[#RET1_LOC:]] + s +} + +// ============================================================================ +// Test 4: Calling a function that returns a large value +// ============================================================================ + +// CHECK-LABEL: call_arg_scope::test_call_returning_large +pub fn test_call_returning_large() { + // CHECK: call {{.*}}@{{.*}}make_large_struct{{.*}}({{.*}}), !dbg ![[#CALL3_LOC:]] + let _result = make_large_struct(); +} + +#[inline(never)] +fn make_large_struct() -> LargeStruct { + LargeStruct { data: [1; 20] } +} + +// ============================================================================ +// Test 5: Mixed scenario - passing and returning large values +// ============================================================================ + +// CHECK-LABEL: call_arg_scope::test_mixed_call +pub fn test_mixed_call(input: LargeStruct) -> LargeStruct { + // CHECK: call {{.*}}@{{.*}}transform_large{{.*}}({{.*}}), !dbg ![[#CALL4_LOC:]] + transform_large(input) +} + +#[inline(never)] +fn transform_large(mut s: LargeStruct) -> LargeStruct { + s.data[0] += 1; + s +} + +// CHECK-DAG: ![[#CALL1_ARG_LOC]] = !DILocation({{.*}}scope: ![[#CALL1_ARG_SCOPE:]] +// CHECK-DAG: ![[#CALL1_ARG_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy" +// CHECK-DAG: ![[#CALL1_LOC]] = !DILocation({{.*}}scope: ![[#CALL1_SCOPE:]] +// CHECK-DAG: ![[#CALL1_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "test_call_with_single_arg" + +// CHECK-DAG: ![[#CALL2_ARG1_LOC]] = !DILocation({{.*}}scope: ![[#CALL2_ARG1_SCOPE:]] +// CHECK-DAG: ![[#CALL2_ARG1_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy" +// CHECK-DAG: ![[#CALL2_ARG2_LOC]] = !DILocation({{.*}}scope: ![[#CALL2_ARG2_SCOPE:]] +// CHECK-DAG: ![[#CALL2_ARG2_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy" +// CHECK-DAG: ![[#CALL2_LOC]] = !DILocation({{.*}}scope: ![[#CALL2_SCOPE:]] +// CHECK-DAG: ![[#CALL2_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "test_call_with_multiple_args" + +// CHECK-DAG: ![[#CALL3_LOC]] = !DILocation({{.*}}scope: ![[#CALL3_SCOPE:]] +// CHECK-DAG: ![[#CALL3_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "test_call_returning_large" + +// CHECK-DAG: ![[#CALL4_LOC]] = !DILocation({{.*}}scope: ![[#CALL4_SCOPE:]] +// CHECK-DAG: ![[#CALL4_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "test_mixed_call" + +// CHECK-DAG: ![[#RET1_LOC]] = !DILocation({{.*}}scope: ![[#RET1_SCOPE:]] +// CHECK-DAG: ![[#RET1_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "test_return_large_value" diff --git a/tests/codegen-llvm/annotate-moves/disabled.rs b/tests/codegen-llvm/annotate-moves/disabled.rs new file mode 100644 index 0000000000000..2a0e786723183 --- /dev/null +++ b/tests/codegen-llvm/annotate-moves/disabled.rs @@ -0,0 +1,35 @@ +//@ compile-flags: -Zannotate-moves=no -Copt-level=0 -g +// Test that move/copy operations are NOT annotated when the flag is disabled + +#![crate_type = "lib"] + +struct LargeStruct { + data: [u64; 20], // 160 bytes - would normally trigger annotation +} + +impl Clone for LargeStruct { + // CHECK-LABEL: ::clone + fn clone(&self) -> Self { + // Should NOT be annotated when flag is disabled + // CHECK-NOT: compiler_copy + LargeStruct { data: self.data } + } +} + +// CHECK-LABEL: disabled::test_large_copy_no_annotation +pub fn test_large_copy_no_annotation() { + let large = LargeStruct { data: [42; 20] }; + // CHECK-NOT: compiler_copy + let _copy = large.clone(); +} + +// CHECK-LABEL: disabled::test_large_move_no_annotation +pub fn test_large_move_no_annotation() { + let large = LargeStruct { data: [42; 20] }; + // CHECK-NOT: compiler_move + let _moved = large; +} + +// Verify that no compiler_move or compiler_copy annotations exist anywhere +// CHECK-NOT: compiler_move +// CHECK-NOT: compiler_copy diff --git a/tests/codegen-llvm/annotate-moves/integration.rs b/tests/codegen-llvm/annotate-moves/integration.rs new file mode 100644 index 0000000000000..5e5c2e05879bb --- /dev/null +++ b/tests/codegen-llvm/annotate-moves/integration.rs @@ -0,0 +1,192 @@ +//@ 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: ::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: ::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<[u64; 20], 160>" +// CHECK-DAG: ![[#EXPLICIT_RETURN_LOC]] = !DILocation({{.*}}scope: ![[#EXPLICIT_RETURN_SCOPE:]] +// CHECK-DAG: ![[#EXPLICIT_RETURN_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" + +// CHECK-DAG: ![[#CLONE_COPY_LOC]] = !DILocation({{.*}}scope: ![[#CLONE_COPY_SCOPE:]] +// CHECK-DAG: ![[#CLONE_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy<[u64; 20], 160>" +// CHECK-DAG: ![[#CLONE_RETURN_LOC]] = !DILocation({{.*}}scope: ![[#CLONE_RETURN_SCOPE:]] +// CHECK-DAG: ![[#CLONE_RETURN_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" + +// CHECK-DAG: ![[#ASSIGN_COPY2_LOC]] = !DILocation({{.*}}scope: ![[#ASSIGN_COPY2_SCOPE:]] +// CHECK-DAG: ![[#ASSIGN_COPY2_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" + +// CHECK-DAG: ![[#INIT_STRUCT_LOC]] = !DILocation({{.*}}scope: ![[#INIT_STRUCT_SCOPE:]] +// CHECK-DAG: ![[#INIT_STRUCT_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move" + +// CHECK-DAG: ![[#TUPLE_MOVE_LOC]] = !DILocation({{.*}}scope: ![[#TUPLE_MOVE_SCOPE:]] +// CHECK-DAG: ![[#TUPLE_MOVE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" + +// CHECK-DAG: ![[#MIXED_TUPLE_LOC]] = !DILocation({{.*}}scope: ![[#MIXED_TUPLE_SCOPE:]] +// CHECK-DAG: ![[#MIXED_TUPLE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" + +// CHECK-DAG: ![[#COPY2_LOC]] = !DILocation({{.*}}scope: ![[#COPY2_SCOPE:]] +// CHECK-DAG: ![[#COPY2_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" + +// CHECK-DAG: ![[#ARRAY_MOVE_LOC]] = !DILocation({{.*}}scope: ![[#ARRAY_MOVE_SCOPE:]] +// CHECK-DAG: ![[#ARRAY_MOVE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[alloc::string::String; 20], 480>" + +// CHECK-DAG: ![[#FIELD_MOVE_LOC]] = !DILocation({{.*}}scope: ![[#FIELD_MOVE_SCOPE:]] +// CHECK-DAG: ![[#FIELD_MOVE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" +// CHECK-DAG: ![[#FIELD_MOVE2_LOC]] = !DILocation({{.*}}scope: ![[#FIELD_MOVE2_SCOPE:]] +// CHECK-DAG: ![[#FIELD_MOVE2_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy<[u64; 20], 160>" + +// CHECK-DAG: ![[#CALL_CLONE_NONCOPY_LOC]] = !DILocation({{.*}}scope: ![[#CALL_CLONE_NONCOPY_SCOPE:]] +// CHECK-DAG: ![[#CALL_CLONE_NONCOPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" + +// CHECK-DAG: ![[#CALL_CLONE_COPY_LOC]] = !DILocation({{.*}}scope: ![[#CALL_CLONE_COPY_SCOPE:]] +// CHECK-DAG: ![[#CALL_CLONE_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u64; 20], 160>" + +// CHECK-DAG: ![[#LOCAL_COPY_LOC]] = !DILocation({{.*}}scope: ![[#LOCAL_COPY_SCOPE:]] +// CHECK-DAG: ![[#LOCAL_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy" diff --git a/tests/codegen-llvm/annotate-moves/size-limit.rs b/tests/codegen-llvm/annotate-moves/size-limit.rs new file mode 100644 index 0000000000000..fbb412de99cd2 --- /dev/null +++ b/tests/codegen-llvm/annotate-moves/size-limit.rs @@ -0,0 +1,112 @@ +//@ compile-flags: -Z annotate-moves=100 -Copt-level=0 -g +// Test that custom size limits work correctly +#![crate_type = "lib"] + +struct Struct99 { + data: [u8; 99], // just below custom 100-byte threshold +} + +const _: () = { assert!(size_of::() == 99) }; + +impl Clone for Struct99 { + // CHECK-LABEL: ::clone + fn clone(&self) -> Self { + // Should NOT be annotated since 99 < 100 + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#SZ99_COPY_LOC:]] + Struct99 { data: self.data } + } +} + +// CHECK-LABEL: size_limit::test_99_copy +pub fn test_99_copy() { + let sz99 = Struct99 { data: [42; 99] }; + let _copy = sz99.clone(); +} + +// CHECK-LABEL: size_limit::test_99_move +pub fn test_99_move() { + let sz99 = Struct99 { data: [42; 99] }; + // Should NOT be annotated + // CHECK-NOT: compiler_move + let _moved = sz99; +} + +struct Struct100 { + data: [u8; 100], // 100 bytes - equal to custom 100-byte threshold +} + +const _: () = { assert!(size_of::() == 100) }; + +impl Clone for Struct100 { + // CHECK-LABEL: ::clone + fn clone(&self) -> Self { + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#SZ100_COPY_LOC:]] + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#SZ100_RETURN_LOC:]] + Struct100 { data: self.data } + } +} + +// CHECK-LABEL: size_limit::test_100_copy +pub fn test_100_copy() { + let sz100 = Struct100 { data: [42; 100] }; + let _copy = sz100.clone(); +} + +// CHECK-LABEL: size_limit::test_100_move +pub fn test_100_move() { + let sz100 = Struct100 { data: [42; 100] }; + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#SZ100_MOVE_LOC:]] + let _moved = sz100; +} + +struct Struct101 { + data: [u8; 101], // 101 bytes - above custom 100-byte threshold +} + +const _: () = { assert!(size_of::() == 101) }; + +impl Clone for Struct101 { + // CHECK-LABEL: ::clone + fn clone(&self) -> Self { + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#SZ101_COPY_LOC:]] + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#SZ101_RETURN_LOC:]] + Struct101 { data: self.data } + } +} + +// CHECK-LABEL: size_limit::test_101_copy +pub fn test_101_copy() { + let sz101 = Struct101 { data: [42; 101] }; + let _copy = sz101.clone(); +} + +// CHECK-LABEL: size_limit::test_101_move +pub fn test_101_move() { + let sz101 = Struct101 { data: [42; 101] }; + // CHECK: call void @llvm.memcpy{{.*}}, !dbg ![[#SZ101_MOVE_LOC:]] + let _moved = sz101; +} + +// The scope for no-annotated is clone function itself +// CHECK-DAG: ![[#SZ99_COPY_LOC]] = !DILocation({{.*}}scope: ![[#SZ99_COPY_SCOPE:]] +// CHECK-DAG: ![[#SZ99_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "clone", + +// Clone itself is copy, but return is move. +// CHECK-DAG: ![[#SZ100_COPY_LOC]] = !DILocation({{.*}}scope: ![[#SZ100_COPY_SCOPE:]] +// CHECK-DAG: ![[#SZ100_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy<[u8; 100], 100>" +// CHECK-DAG: ![[#SZ100_RETURN_LOC]] = !DILocation({{.*}}scope: ![[#SZ100_RETURN_SCOPE:]] +// CHECK-DAG: ![[#SZ100_RETURN_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u8; 100], 100>" + +// Assignment is move +// CHECK-DAG: ![[#SZ100_MOVE_LOC]] = !DILocation({{.*}}scope: ![[#SZ100_MOVE_SCOPE:]] +// CHECK-DAG: ![[#SZ100_MOVE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u8; 100], 100>" + +// Clone itself is copy, but return is move. +// CHECK-DAG: ![[#SZ101_COPY_LOC]] = !DILocation({{.*}}scope: ![[#SZ101_COPY_SCOPE:]] +// CHECK-DAG: ![[#SZ101_COPY_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_copy<[u8; 101], 101>" +// CHECK-DAG: ![[#SZ101_RETURN_LOC]] = !DILocation({{.*}}scope: ![[#SZ101_RETURN_SCOPE:]] +// CHECK-DAG: ![[#SZ101_RETURN_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u8; 101], 101>" + +// Assignment is move +// CHECK-DAG: ![[#SZ101_MOVE_LOC]] = !DILocation({{.*}}scope: ![[#SZ101_MOVE_SCOPE:]] +// CHECK-DAG: ![[#SZ101_MOVE_SCOPE]] = {{(distinct )?}}!DISubprogram(name: "compiler_move<[u8; 101], 101>" diff --git a/tests/mir-opt/const_prop/invalid_constant.main.GVN.diff b/tests/mir-opt/const_prop/invalid_constant.main.GVN.diff index a4900a1ac72ea..20923d0352cd5 100644 --- a/tests/mir-opt/const_prop/invalid_constant.main.GVN.diff +++ b/tests/mir-opt/const_prop/invalid_constant.main.GVN.diff @@ -61,17 +61,11 @@ StorageDead(_1); return; } -+ } + } + -+ ALLOC0 (size: 4, align: 4) { -+ 00 00 00 00 │ .... -+ } ++ ALLOC0 (size: 4, align: 4) { .. } + -+ ALLOC1 (size: 4, align: 4) { -+ 04 00 00 00 │ .... -+ } ++ ALLOC1 (size: 4, align: 4) { .. } + -+ ALLOC2 (size: 4, align: 4) { -+ 01 00 11 00 │ .... - } ++ ALLOC2 (size: 4, align: 4) { .. } diff --git a/tests/mir-opt/const_prop/invalid_constant.rs b/tests/mir-opt/const_prop/invalid_constant.rs index b59103792bfc4..901c3721d9238 100644 --- a/tests/mir-opt/const_prop/invalid_constant.rs +++ b/tests/mir-opt/const_prop/invalid_constant.rs @@ -1,6 +1,6 @@ // skip-filecheck //@ test-mir-pass: GVN -//@ compile-flags: -Zmir-enable-passes=+RemoveZsts +//@ compile-flags: -Zmir-enable-passes=+RemoveZsts -Zdump-mir-exclude-alloc-bytes // Verify that we can pretty print invalid constants. #![feature(adt_const_params, unsized_const_params)] diff --git a/tests/mir-opt/const_prop/union.main.GVN.diff b/tests/mir-opt/const_prop/union.main.GVN.diff index 16a0432ab9013..4212a44d0a0de 100644 --- a/tests/mir-opt/const_prop/union.main.GVN.diff +++ b/tests/mir-opt/const_prop/union.main.GVN.diff @@ -34,9 +34,7 @@ StorageDead(_1); return; } -+ } -+ -+ ALLOC0 (size: 4, align: 4) { -+ 01 00 00 00 │ .... } ++ ++ ALLOC0 (size: 4, align: 4) { .. } diff --git a/tests/mir-opt/const_prop/union.rs b/tests/mir-opt/const_prop/union.rs index 9f197a1a58338..fb822ff28b2d3 100644 --- a/tests/mir-opt/const_prop/union.rs +++ b/tests/mir-opt/const_prop/union.rs @@ -1,6 +1,6 @@ //! Tests that we can propagate into places that are projections into unions //@ test-mir-pass: GVN -//@ compile-flags: -Zinline-mir +//@ compile-flags: -Zinline-mir -Zdump-mir-exclude-alloc-bytes fn val() -> u32 { 1 diff --git a/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff b/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff index 92e5ccabedf9e..e5d719cf3ca99 100644 --- a/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff +++ b/tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff @@ -107,9 +107,7 @@ StorageDead(_11); goto -> bb4; } -+ } -+ -+ ALLOC0 (size: 8, align: 4) { -+ 01 00 00 00 __ __ __ __ │ ....░░░░ } ++ ++ ALLOC0 (size: 8, align: 4) { .. } diff --git a/tests/mir-opt/gvn_loop.rs b/tests/mir-opt/gvn_loop.rs index 6e9df55a968dc..4a94d516cca23 100644 --- a/tests/mir-opt/gvn_loop.rs +++ b/tests/mir-opt/gvn_loop.rs @@ -1,4 +1,5 @@ //@ test-mir-pass: GVN +//@ compile-flags: -Zdump-mir-exclude-alloc-bytes #![crate_type = "lib"] #![feature(core_intrinsics, rustc_attrs)] diff --git a/tests/ui/annotate-moves/annotate-moves-basic.rs b/tests/ui/annotate-moves/annotate-moves-basic.rs new file mode 100644 index 0000000000000..645122113dab2 --- /dev/null +++ b/tests/ui/annotate-moves/annotate-moves-basic.rs @@ -0,0 +1,15 @@ +//@ check-pass +//@ compile-flags: -Z annotate-moves=100 + +// Test that valid annotate-moves flags are accepted + +#[derive(Clone)] +struct TestStruct { + data: [u64; 20], // 160 bytes +} + +fn main() { + let s = TestStruct { data: [42; 20] }; + let _copy = s.clone(); + let _moved = s; +} diff --git a/tests/ui/annotate-moves/annotate-moves-invalid-flag.rs b/tests/ui/annotate-moves/annotate-moves-invalid-flag.rs new file mode 100644 index 0000000000000..621b7861657ef --- /dev/null +++ b/tests/ui/annotate-moves/annotate-moves-invalid-flag.rs @@ -0,0 +1,10 @@ +//@ check-fail +//@ compile-flags: -Z annotate-moves=invalid + +// Test that invalid values for annotate-moves flag are rejected + +fn main() { + // This should fail at compile time due to invalid flag value +} + +//~? ERROR incorrect value `invalid` for unstable option `annotate-moves` diff --git a/tests/ui/annotate-moves/annotate-moves-invalid-flag.stderr b/tests/ui/annotate-moves/annotate-moves-invalid-flag.stderr new file mode 100644 index 0000000000000..5a323573a7770 --- /dev/null +++ b/tests/ui/annotate-moves/annotate-moves-invalid-flag.stderr @@ -0,0 +1,2 @@ +error: incorrect value `invalid` for unstable option `annotate-moves` - either a boolean (`yes`, `no`, `on`, `off`, etc.), or a size limit in bytes was expected + diff --git a/tests/ui/annotate-moves/annotate-moves-size-limit-invalid.rs b/tests/ui/annotate-moves/annotate-moves-size-limit-invalid.rs new file mode 100644 index 0000000000000..50a402102184a --- /dev/null +++ b/tests/ui/annotate-moves/annotate-moves-size-limit-invalid.rs @@ -0,0 +1,10 @@ +//@ check-fail +//@ compile-flags: -Z annotate-moves=-5 + +// Test that negative size limits are rejected + +fn main() { + // This should fail at compile time due to invalid negative size limit +} + +//~? ERROR incorrect value `-5` for unstable option `annotate-moves` diff --git a/tests/ui/annotate-moves/annotate-moves-size-limit-invalid.stderr b/tests/ui/annotate-moves/annotate-moves-size-limit-invalid.stderr new file mode 100644 index 0000000000000..24a3659a30a66 --- /dev/null +++ b/tests/ui/annotate-moves/annotate-moves-size-limit-invalid.stderr @@ -0,0 +1,2 @@ +error: incorrect value `-5` for unstable option `annotate-moves` - either a boolean (`yes`, `no`, `on`, `off`, etc.), or a size limit in bytes was expected + diff --git a/tests/ui/impl-header-lifetime-elision/assoc-type.rs b/tests/ui/impl-header-lifetime-elision/assoc-type.rs index 14b2ea647f190..f03a110d7dc1d 100644 --- a/tests/ui/impl-header-lifetime-elision/assoc-type.rs +++ b/tests/ui/impl-header-lifetime-elision/assoc-type.rs @@ -9,7 +9,7 @@ trait MyTrait { impl MyTrait for &i32 { type Output = &i32; - //~^ ERROR in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type + //~^ ERROR missing lifetime in associated type } impl MyTrait for &u32 { @@ -17,6 +17,19 @@ impl MyTrait for &u32 { //~^ ERROR `'_` cannot be used here } +impl<'a> MyTrait for &f64 { + type Output = &f64; + //~^ ERROR missing lifetime in associated type +} + +trait OtherTrait<'a> { + type Output; +} +impl OtherTrait<'_> for f64 { + type Output = &f64; + //~^ ERROR missing lifetime in associated type +} + // This is what you have to do: impl<'a> MyTrait for &'a f32 { type Output = &'a f32; diff --git a/tests/ui/impl-header-lifetime-elision/assoc-type.stderr b/tests/ui/impl-header-lifetime-elision/assoc-type.stderr index 72c066426bd99..201ea2d894eb7 100644 --- a/tests/ui/impl-header-lifetime-elision/assoc-type.stderr +++ b/tests/ui/impl-header-lifetime-elision/assoc-type.stderr @@ -1,10 +1,15 @@ -error: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +error: missing lifetime in associated type --> $DIR/assoc-type.rs:11:19 | -LL | impl MyTrait for &i32 { - | - you could add a lifetime on the impl block, if the trait or the self type can have one LL | type Output = &i32; | ^ this lifetime must come from the implemented type + | + = note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +help: add a lifetime to the impl block and use it in the self type and associated type + | +LL ~ impl<'a> MyTrait for &'a i32 { +LL ~ type Output = &'a i32; + | error[E0637]: `'_` cannot be used here --> $DIR/assoc-type.rs:16:20 @@ -12,6 +17,33 @@ error[E0637]: `'_` cannot be used here LL | type Output = &'_ i32; | ^^ `'_` is a reserved lifetime name -error: aborting due to 2 previous errors +error: missing lifetime in associated type + --> $DIR/assoc-type.rs:21:19 + | +LL | impl<'a> MyTrait for &f64 { + | ---- there is a named lifetime specified on the impl block you could use +LL | type Output = &f64; + | ^ this lifetime must come from the implemented type + | + = note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +help: consider using the lifetime from the impl block + | +LL | type Output = &'a f64; + | ++ + +error: missing lifetime in associated type + --> $DIR/assoc-type.rs:29:19 + | +LL | type Output = &f64; + | ^ this lifetime must come from the implemented type + | + = note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +help: add a lifetime to the impl block and use it in the trait and associated type + | +LL ~ impl<'a> OtherTrait<'a> for f64 { +LL ~ type Output = &'a f64; + | + +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0637`. diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-1.rs b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-1.rs index 5401bc4ecb873..3d02d1bb1bd80 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-1.rs +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-1.rs @@ -7,9 +7,10 @@ impl<'a> IntoIterator for &S { //~| NOTE unconstrained lifetime parameter //~| HELP consider using the named lifetime here instead of an implicit lifetime type Item = &T; - //~^ ERROR in the trait associated type + //~^ ERROR missing lifetime in associated type //~| HELP consider using the lifetime from the impl block //~| NOTE this lifetime must come from the implemented type + //~| NOTE in the trait the associated type is declared without lifetime parameters type IntoIter = std::collections::btree_map::Values<'a, i32, T>; fn into_iter(self) -> Self::IntoIter { diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-1.stderr b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-1.stderr index feac49eb0ff52..3374c76bb76b9 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-1.stderr +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-1.stderr @@ -1,4 +1,4 @@ -error: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +error: missing lifetime in associated type --> $DIR/missing-lifetime-in-assoc-type-1.rs:9:17 | LL | impl<'a> IntoIterator for &S { @@ -7,6 +7,8 @@ LL | impl<'a> IntoIterator for &S { LL | type Item = &T; | ^ this lifetime must come from the implemented type | +note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL help: consider using the lifetime from the impl block | LL | type Item = &'a T; diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-2.rs b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-2.rs index dd720f075ac46..d24aaaf8b10ed 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-2.rs +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-2.rs @@ -3,7 +3,7 @@ struct T; impl IntoIterator for &S { type Item = &T; - //~^ ERROR in the trait associated type + //~^ ERROR missing lifetime in associated type type IntoIter = std::collections::btree_map::Values<'a, i32, T>; //~^ ERROR use of undeclared lifetime name `'a` diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-2.stderr b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-2.stderr index 7a0246eaac8fd..9d9d2bc97d901 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-2.stderr +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-2.stderr @@ -1,10 +1,16 @@ -error: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +error: missing lifetime in associated type --> $DIR/missing-lifetime-in-assoc-type-2.rs:5:17 | -LL | impl IntoIterator for &S { - | - you could add a lifetime on the impl block, if the trait or the self type can have one LL | type Item = &T; | ^ this lifetime must come from the implemented type + | +note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL +help: add a lifetime to the impl block and use it in the self type and associated type + | +LL ~ impl<'a> IntoIterator for &'a S { +LL ~ type Item = &'a T; + | error[E0261]: use of undeclared lifetime name `'a` --> $DIR/missing-lifetime-in-assoc-type-2.rs:7:57 diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-3.rs b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-3.rs index 60d1f0f8fe571..cf745ab97eb3a 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-3.rs +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-3.rs @@ -3,7 +3,7 @@ struct T; impl IntoIterator for &S { type Item = &T; - //~^ ERROR in the trait associated type + //~^ ERROR missing lifetime in associated type type IntoIter = std::collections::btree_map::Values; //~^ ERROR missing lifetime specifier diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-3.stderr b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-3.stderr index 408d5bb40664d..b5811dc8ff279 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-3.stderr +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-3.stderr @@ -1,10 +1,16 @@ -error: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +error: missing lifetime in associated type --> $DIR/missing-lifetime-in-assoc-type-3.rs:5:17 | -LL | impl IntoIterator for &S { - | - you could add a lifetime on the impl block, if the trait or the self type can have one LL | type Item = &T; | ^ this lifetime must come from the implemented type + | +note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL +help: add a lifetime to the impl block and use it in the self type and associated type + | +LL ~ impl<'a> IntoIterator for &'a S { +LL ~ type Item = &'a T; + | error[E0106]: missing lifetime specifier --> $DIR/missing-lifetime-in-assoc-type-3.rs:7:56 diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-4.rs b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-4.rs index 0c99e8874c354..138f6d7bdf2f7 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-4.rs +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-4.rs @@ -3,7 +3,7 @@ struct T; impl IntoIterator for &S { type Item = &T; - //~^ ERROR in the trait associated type + //~^ ERROR missing lifetime in associated type type IntoIter<'a> = std::collections::btree_map::Values<'a, i32, T>; //~^ ERROR lifetime parameters or bounds on associated type `IntoIter` do not match the trait declaration diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-4.stderr b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-4.stderr index ebe051509aad2..a0b7ad08e8b6c 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-4.stderr +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-4.stderr @@ -1,16 +1,26 @@ -error: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +error: missing lifetime in associated type --> $DIR/missing-lifetime-in-assoc-type-4.rs:5:17 | -LL | impl IntoIterator for &S { - | - you could add a lifetime on the impl block, if the trait or the self type can have one LL | type Item = &T; | ^ this lifetime must come from the implemented type + | +note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL +help: add a lifetime to the impl block and use it in the self type and associated type + | +LL ~ impl<'a> IntoIterator for &'a S { +LL ~ type Item = &'a T; + | error[E0195]: lifetime parameters or bounds on associated type `IntoIter` do not match the trait declaration --> $DIR/missing-lifetime-in-assoc-type-4.rs:7:18 | LL | type IntoIter<'a> = std::collections::btree_map::Values<'a, i32, T>; | ^^^^ lifetimes do not match associated type in trait + | + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL + | + = note: lifetimes in impl do not match this associated type in trait error: aborting due to 2 previous errors diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-5.rs b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-5.rs index 17cca7cc9e37b..853cc6dc8e4e9 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-5.rs +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-5.rs @@ -7,9 +7,10 @@ impl<'a> IntoIterator for &'_ S { //~| NOTE unconstrained lifetime parameter //~| HELP consider using the named lifetime here instead of an implicit lifetime type Item = &T; - //~^ ERROR in the trait associated type + //~^ ERROR missing lifetime in associated type //~| HELP consider using the lifetime from the impl block //~| NOTE this lifetime must come from the implemented type + //~| NOTE in the trait the associated type is declared without lifetime parameters type IntoIter = std::collections::btree_map::Values<'a, i32, T>; fn into_iter(self) -> Self::IntoIter { diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-5.stderr b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-5.stderr index cb15bcb7d5049..d58fd8995ef9c 100644 --- a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-5.stderr +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-5.stderr @@ -1,4 +1,4 @@ -error: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +error: missing lifetime in associated type --> $DIR/missing-lifetime-in-assoc-type-5.rs:9:17 | LL | impl<'a> IntoIterator for &'_ S { @@ -7,6 +7,8 @@ LL | impl<'a> IntoIterator for &'_ S { LL | type Item = &T; | ^ this lifetime must come from the implemented type | +note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type + --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL help: consider using the lifetime from the impl block | LL | type Item = &'a T; diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-6.rs b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-6.rs new file mode 100644 index 0000000000000..b4fac575edb3b --- /dev/null +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-6.rs @@ -0,0 +1,26 @@ +//~ NOTE in the trait the associated type is declared without lifetime parameters +struct S; +struct T; + +trait Trait { + type Item; + type IntoIter; + fn into_iter(self) -> Self::IntoIter; +} + +impl<'a> Trait for &'_ S { + //~^ ERROR E0207 + //~| NOTE there is a named lifetime specified on the impl block you could use + //~| NOTE unconstrained lifetime parameter + //~| HELP consider using the named lifetime here instead of an implicit lifetime + type Item = &T; + //~^ ERROR missing lifetime in associated type + //~| HELP consider using the lifetime from the impl block + //~| NOTE this lifetime must come from the implemented type + type IntoIter = std::collections::btree_map::Values<'a, i32, T>; + + fn into_iter(self) -> Self::IntoIter { + todo!() + } +} +fn main() {} diff --git a/tests/ui/lifetimes/missing-lifetime-in-assoc-type-6.stderr b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-6.stderr new file mode 100644 index 0000000000000..6767243bf21ba --- /dev/null +++ b/tests/ui/lifetimes/missing-lifetime-in-assoc-type-6.stderr @@ -0,0 +1,30 @@ +error: missing lifetime in associated type + --> $DIR/missing-lifetime-in-assoc-type-6.rs:16:17 + | +LL | impl<'a> Trait for &'_ S { + | ---- there is a named lifetime specified on the impl block you could use +... +LL | type Item = &T; + | ^ this lifetime must come from the implemented type + | + = note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +help: consider using the lifetime from the impl block + | +LL | type Item = &'a T; + | ++ + +error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates + --> $DIR/missing-lifetime-in-assoc-type-6.rs:11:6 + | +LL | impl<'a> Trait for &'_ S { + | ^^ unconstrained lifetime parameter + | +help: consider using the named lifetime here instead of an implicit lifetime + | +LL - impl<'a> Trait for &'_ S { +LL + impl<'a> Trait for &'a S { + | + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0207`. diff --git a/tests/ui/lifetimes/no_lending_iterators.rs b/tests/ui/lifetimes/no_lending_iterators.rs index 88b8cda0898be..aa2e57ee30364 100644 --- a/tests/ui/lifetimes/no_lending_iterators.rs +++ b/tests/ui/lifetimes/no_lending_iterators.rs @@ -16,7 +16,7 @@ trait Bar { impl Bar for usize { type Item = &usize; - //~^ ERROR in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type + //~^ ERROR missing lifetime in associated type fn poke(&mut self, item: Self::Item) { self += *item; diff --git a/tests/ui/lifetimes/no_lending_iterators.stderr b/tests/ui/lifetimes/no_lending_iterators.stderr index cadba149c234d..ef90c286fc755 100644 --- a/tests/ui/lifetimes/no_lending_iterators.stderr +++ b/tests/ui/lifetimes/no_lending_iterators.stderr @@ -10,13 +10,15 @@ note: you can't create an `Iterator` that borrows each `Item` from itself, but y LL | impl Iterator for Data { | ^^^^ -error: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type +error: missing lifetime in associated type --> $DIR/no_lending_iterators.rs:18:17 | LL | impl Bar for usize { - | - you could add a lifetime on the impl block, if the trait or the self type can have one + | - you could add a lifetime on the impl block, if the trait or the self type could have one LL | type Item = &usize; | ^ this lifetime must come from the implemented type + | + = note: in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type error[E0195]: lifetime parameters or bounds on associated type `Item` do not match the trait declaration --> $DIR/no_lending_iterators.rs:27:14