From f054c8390b42fbd371c533a51cd4bd22a944af03 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Fri, 4 Sep 2026 07:05:16 +0000 Subject: [PATCH 01/30] Prepare for merging from rust-lang/rust This updates the rust-version file to 71238e21fc55e73ab3aad8c9f79fed7a47a179e1. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 7b170626619e4..386fb6f98c37f 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -da5114692c9ebe46b869488c5f34f92eb10b98c1 +71238e21fc55e73ab3aad8c9f79fed7a47a179e1 From f123df5b073d1afa9dcbbe276332901bf6541980 Mon Sep 17 00:00:00 2001 From: Yara Date: Fri, 28 Aug 2026 00:52:19 +0200 Subject: [PATCH 02/30] reflection: adds `TypeId::points_to` and `TypeId::is_mutable_pointer` --- .../src/const_eval/machine.rs | 23 ++++++++++++ .../src/const_eval/type_info.rs | 1 - .../rustc_hir_analysis/src/check/intrinsic.rs | 4 ++ compiler/rustc_span/src/symbol.rs | 2 + library/core/src/intrinsics/mod.rs | 20 ++++++++++ library/core/src/mem/type_info.rs | 37 +++++++++++++++++++ library/coretests/tests/mem/type_info.rs | 18 +++++++++ 7 files changed, 104 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 7c10dd04f39f3..562173e9ad1f5 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -612,6 +612,16 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { ecx.write_scalar(Scalar::from_bool(ty.is_signed()), dest)?; } + sym::type_id_points_mutably => { + let ty = ecx.read_type_id(&args[0])?; + let ret = if let ty::RawPtr(_, mutability) = ty.kind() { + mutability.is_mut() + } else { + false + }; + ecx.write_scalar(Scalar::from_bool(ret), dest)?; + } + sym::size_of_type_id => { let ty = ecx.read_type_id(&args[0])?; let layout = ecx.layout_of(ty)?; @@ -692,6 +702,19 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { ecx.write_type_id(frt, dest)?; } + sym::type_id_points_to => { + let ty = ecx.read_type_id(&args[0])?; + let variant_index = if let ty::RawPtr(pointee_ty, _) = ty.kind() { + let (variant, variant_place) = ecx.project_downcast_named(dest, sym::Some)?; + let field_place = ecx.project_field(&variant_place, FieldIdx::ZERO)?; + ecx.write_type_id(*pointee_ty, &field_place)?; + variant + } else { + ecx.project_downcast_named(dest, sym::None)?.0 + }; + ecx.write_discriminant(variant_index, dest)?; + } + sym::type_id_variants => { let ty = ecx.read_type_id(&args[0])?; let variants_num = ty.ty_adt_def().map(|def| def.variants().len()).unwrap_or(1); diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index f6e0208d98835..7f6f23ff6faf2 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -3,7 +3,6 @@ mod adt; use std::borrow::Cow; use rustc_abi::{ExternAbi, FieldIdx}; -use rustc_ast::Mutability; use rustc_hir::attrs::lang_items::LangItem; use rustc_middle::span_bug; use rustc_middle::ty::layout::TyAndLayout; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 30d7127ccd8fa..8e5242de82fea 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -203,7 +203,9 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::type_id_field_representing_type | sym::type_id_fields | sym::type_id_generics + | sym::type_id_points_mutably | sym::type_id_is_signed + | sym::type_id_points_to | sym::type_id_variants | sym::type_id_vtable | sym::type_name @@ -318,6 +320,8 @@ pub(crate) fn check_intrinsic_type( } sym::type_id_fields => (0, 0, vec![type_id_ty(), tcx.types.usize], tcx.types.usize), sym::type_id_is_signed => (0, 0, vec![type_id_ty()], tcx.types.bool), + sym::type_id_points_mutably => (0, 0, vec![type_id_ty()], tcx.types.bool), + sym::type_id_points_to => (0, 0, vec![type_id_ty()], Ty::new_option(tcx, type_id_ty())), sym::type_id_variants => (0, 0, vec![type_id_ty()], tcx.types.usize), sym::variant_name => (0, 0, vec![type_id_ty(), tcx.types.usize], Ty::new_static_str(tcx)), sym::variant_non_exhaustive => (0, 0, vec![type_id_ty(), tcx.types.usize], tcx.types.bool), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 768f1be1cd48d..5ee54cc39a795 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2169,7 +2169,9 @@ symbols! { type_id_field_representing_type, type_id_fields, type_id_generics, + type_id_points_mutably, type_id_is_signed, + type_id_points_to, type_id_variants, type_id_vtable, type_info, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index ca29cf2e19681..3ed3ff0518bf6 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3111,6 +3111,26 @@ pub fn non_exhaustive(_id: crate::any::TypeId) -> bool; #[rustc_comptime] pub fn type_id_generics(_id: crate::any::TypeId) -> &'static [crate::mem::type_info::Generic]; +// FIXME(reflection): Pick a consistent naming scheme for the intrinsics. Right now we got +// type_id_, _type_id and intrinsics not mentioning type_id at all. +/// Given a `TypeId` that represents a pointer this returns the `TypeId` which that pointer +/// points to. When called on anything else this returns None. +/// +/// The more user-friendly version of this intrinsic is [`core::any::TypeId::points_to`]. +#[rustc_intrinsic] +#[unstable(feature = "core_intrinsics", issue = "none")] +#[rustc_comptime] +pub fn type_id_points_to(_id: crate::any::TypeId) -> Option; + +/// Given a `TypeId` that represents a pointer returns whether that pointer is mutable. +/// When called on anything else this returns `false`. +/// +/// The more user-friendly version of this intrinsic is [`core::any::TypeId::points_mutably`]. +#[rustc_intrinsic] +#[unstable(feature = "core_intrinsics", issue = "none")] +#[rustc_comptime] +pub fn type_id_points_mutably(_id: crate::any::TypeId) -> bool; + /// Lowers in MIR to `Rvalue::Aggregate` with `AggregateKind::RawPtr`. /// /// This is used to implement functions like `slice::from_raw_parts_mut` and diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 111664775ca8d..ca4eef733a153 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -567,6 +567,43 @@ impl TypeId { pub fn generics(self) -> &'static [Generic] { intrinsics::type_id_generics(self) } + + /// Given a `TypeId` that represents a pointer this returns the `TypeId` + /// which that pointer points to. When called on anything else this returns + /// None. + /// + /// ``` + /// #![feature(type_info)] + /// use std::any::TypeId; + /// + /// assert_eq!( + /// const { TypeId::of::<*const i32>().points_to() }, + /// const { Some(TypeId::of::()) }, + /// ); + /// ``` + #[unstable(feature = "type_info", issue = "146922")] + #[rustc_const_unstable(feature = "type_info", issue = "146922")] + #[rustc_comptime] + pub fn points_to(self) -> Option { + intrinsics::type_id_points_to(self) + } + + /// Given a `TypeId` that represents a pointer returns whether that pointer is mutable. + /// When called on anything else this returns `false`. + /// + /// ``` + /// #![feature(type_info)] + /// use std::any::TypeId; + /// + /// assert!(!const { TypeId::of::<*const i32>().points_mutably() }); + /// assert!(const { TypeId::of::<*mut i32>().points_mutably() }); + /// ``` + #[unstable(feature = "type_info", issue = "146922")] + #[rustc_const_unstable(feature = "type_info", issue = "146922")] + #[rustc_comptime] + pub fn points_mutably(self) -> bool { + intrinsics::type_id_points_mutably(self) + } } /// Variant representing type ID. Representing a variant of an enum. diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index f3a69dd857aba..3f180014352d5 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -310,6 +310,12 @@ fn test_pointers() { _ => unreachable!(), } + const { + let ty = TypeId::of::<*const u8>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(!ty.points_mutably()); + } + // Mutable pointer. match const { Type::of::<*mut u64>() }.kind { TypeKind::Pointer(pointer) => { @@ -319,6 +325,12 @@ fn test_pointers() { _ => unreachable!(), } + const { + let ty = TypeId::of::<*mut u64>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(ty.points_mutably()); + } + // Wide pointer. match const { Type::of::<*const dyn Any>() }.kind { TypeKind::Pointer(pointer) => { @@ -327,6 +339,12 @@ fn test_pointers() { } _ => unreachable!(), } + + const { + let ty = TypeId::of::<*const dyn Any>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(!ty.points_mutably()); + } } #[test] From 9c36788af12390eb797a3da9748fca1c3ac33517 Mon Sep 17 00:00:00 2001 From: Yara Date: Tue, 1 Sep 2026 01:08:19 +0200 Subject: [PATCH 03/30] reflection: remove field from TypeKind::Pointer variant --- .../src/const_eval/type_info.rs | 35 ++----------------- library/core/src/mem/type_info.rs | 13 +------ library/coretests/tests/mem/type_info.rs | 29 +++------------ 3 files changed, 8 insertions(+), 69 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index 7f6f23ff6faf2..fa5f501b0b4ab 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -142,14 +142,9 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { variant } - ty::RawPtr(ty, mutability) => { - let (variant, variant_place) = + ty::RawPtr(_, _) => { + let (variant, _variant_place) = self.project_downcast_named(&field_dest, sym::Pointer)?; - let pointer_place = - self.project_field(&variant_place, FieldIdx::ZERO)?; - - self.write_pointer_type_info(pointer_place, *ty, *mutability)?; - variant } ty::Dynamic(predicates, region) => { @@ -443,30 +438,4 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { interp_ok(()) } - - pub(crate) fn write_pointer_type_info( - &mut self, - place: impl Writeable<'tcx, CtfeProvenance>, - ty: Ty<'tcx>, - mutability: Mutability, - ) -> InterpResult<'tcx> { - // Iterate over all fields of `type_info::Pointer`. - for (field_idx, field) in - place.layout().ty.ty_adt_def().unwrap().non_enum_variant().fields.iter_enumerated() - { - let field_place = self.project_field(&place, field_idx)?; - - match field.name { - // Write the `TypeId` of the pointer's inner type to the `ty` field. - sym::pointee => self.write_type_id(ty, &field_place)?, - // Write the boolean representing the pointer's mutability to the `mutable` field. - sym::mutable => { - self.write_scalar(Scalar::from_bool(mutability.is_mut()), &field_place)? - } - other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), - } - } - - interp_ok(()) - } } diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index ca4eef733a153..13bd3e99389f9 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -102,7 +102,7 @@ pub enum TypeKind { /// References. Reference(Reference), /// Pointers. - Pointer(Pointer), + Pointer, /// Function pointers. FnPtr(FnPtr), /// FIXME(#146922): add all the common types @@ -218,17 +218,6 @@ pub struct Reference { pub mutable: bool, } -/// Compile-time type information about pointers. -#[derive(Debug)] -#[non_exhaustive] -#[unstable(feature = "type_info", issue = "146922")] -pub struct Pointer { - /// The type of the value being pointed to. - pub pointee: TypeId, - /// Whether this pointer is mutable or not. - pub mutable: bool, -} - #[derive(Debug)] #[unstable(feature = "type_info", issue = "146922")] /// Function pointer, e.g. fn(u8), diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index 3f180014352d5..5a9e1a77d6564 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -301,15 +301,10 @@ fn test_references() { #[test] fn test_pointers() { - // Immutable pointer. - match const { Type::of::<*const u8>() }.kind { - TypeKind::Pointer(pointer) => { - assert_eq!(pointer.pointee, TypeId::of::()); - assert!(!pointer.mutable); - } - _ => unreachable!(), - } + use TypeKind::Pointer; + // Immutable pointer. + let Type { kind: Pointer, .. } = Type::of::<*const u8>() else { panic!() }; const { let ty = TypeId::of::<*const u8>(); assert!(ty.points_to() == Some(TypeId::of::())); @@ -317,14 +312,7 @@ fn test_pointers() { } // Mutable pointer. - match const { Type::of::<*mut u64>() }.kind { - TypeKind::Pointer(pointer) => { - assert_eq!(pointer.pointee, TypeId::of::()); - assert!(pointer.mutable); - } - _ => unreachable!(), - } - + let Type { kind: Pointer, .. } = Type::of::<*mut u64>() else { panic!() }; const { let ty = TypeId::of::<*mut u64>(); assert!(ty.points_to() == Some(TypeId::of::())); @@ -332,14 +320,7 @@ fn test_pointers() { } // Wide pointer. - match const { Type::of::<*const dyn Any>() }.kind { - TypeKind::Pointer(pointer) => { - assert_eq!(pointer.pointee, TypeId::of::()); - assert!(!pointer.mutable); - } - _ => unreachable!(), - } - + let Type { kind: Pointer, .. } = Type::of::<*const dyn Any>() else { panic!() }; const { let ty = TypeId::of::<*const dyn Any>(); assert!(ty.points_to() == Some(TypeId::of::())); From e46de86eee8bf03d2b6e93a6ad56736de17453de Mon Sep 17 00:00:00 2001 From: Yara Date: Tue, 1 Sep 2026 15:02:13 +0200 Subject: [PATCH 04/30] reflection: make `points_to` and `points_mutably` support references --- .../rustc_const_eval/src/const_eval/machine.rs | 15 ++++++++------- library/core/src/mem/type_info.rs | 8 ++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 562173e9ad1f5..8e3a94a9c8bea 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -614,12 +614,11 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { sym::type_id_points_mutably => { let ty = ecx.read_type_id(&args[0])?; - let ret = if let ty::RawPtr(_, mutability) = ty.kind() { - mutability.is_mut() - } else { - false - }; - ecx.write_scalar(Scalar::from_bool(ret), dest)?; + let is_mutable = matches!( + ty.kind(), + ty::RawPtr(_, Mutability::Mut) | &ty::Ref(_, _, Mutability::Mut) + ); + ecx.write_scalar(Scalar::from_bool(is_mutable), dest)?; } sym::size_of_type_id => { @@ -704,7 +703,9 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { sym::type_id_points_to => { let ty = ecx.read_type_id(&args[0])?; - let variant_index = if let ty::RawPtr(pointee_ty, _) = ty.kind() { + let variant_index = if let ty::RawPtr(pointee_ty, _) | ty::Ref(_, pointee_ty, _) = + ty.kind() + { let (variant, variant_place) = ecx.project_downcast_named(dest, sym::Some)?; let field_place = ecx.project_field(&variant_place, FieldIdx::ZERO)?; ecx.write_type_id(*pointee_ty, &field_place)?; diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 13bd3e99389f9..a68d6711849d5 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -566,6 +566,11 @@ impl TypeId { /// use std::any::TypeId; /// /// assert_eq!( + /// const { TypeId::of::<&i32>().points_to() }, + /// const { Some(TypeId::of::()) }, + /// ); + /// + /// assert_eq!( /// const { TypeId::of::<*const i32>().points_to() }, /// const { Some(TypeId::of::()) }, /// ); @@ -584,6 +589,9 @@ impl TypeId { /// #![feature(type_info)] /// use std::any::TypeId; /// + /// assert!(const { TypeId::of::<&mut i32>().points_mutably() }); + /// assert!(const { !TypeId::of::<&i32>().points_mutably() }); + /// /// assert!(!const { TypeId::of::<*const i32>().points_mutably() }); /// assert!(const { TypeId::of::<*mut i32>().points_mutably() }); /// ``` From f4cdf28df504687591d4d38e4960f12397bb5993 Mon Sep 17 00:00:00 2001 From: Yara Date: Tue, 1 Sep 2026 15:02:13 +0200 Subject: [PATCH 05/30] reflection: remove field from TypeKind::Reference --- .../src/const_eval/type_info.rs | 33 ++--------------- library/core/src/mem/type_info.rs | 13 +------ library/coretests/tests/mem/fn_ptr.rs | 8 ++--- library/coretests/tests/mem/type_info.rs | 35 +++++++++---------- 4 files changed, 24 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index fa5f501b0b4ab..2ac5abdd78578 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -133,13 +133,9 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { self.project_downcast_named(&field_dest, sym::Str)?; variant } - ty::Ref(_, ty, mutability) => { - let (variant, variant_place) = + ty::Ref(_, _, _) => { + let (variant, _) = self.project_downcast_named(&field_dest, sym::Reference)?; - let reference_place = - self.project_field(&variant_place, FieldIdx::ZERO)?; - self.write_reference_type_info(reference_place, *ty, *mutability)?; - variant } ty::RawPtr(_, _) => { @@ -295,31 +291,6 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { interp_ok(()) } - pub(crate) fn write_reference_type_info( - &mut self, - place: impl Writeable<'tcx, CtfeProvenance>, - ty: Ty<'tcx>, - mutability: Mutability, - ) -> InterpResult<'tcx> { - // Iterate over all fields of `type_info::Reference`. - for (field_idx, field) in - place.layout().ty.ty_adt_def().unwrap().non_enum_variant().fields.iter_enumerated() - { - let field_place = self.project_field(&place, field_idx)?; - - match field.name { - // Write the `TypeId` of the reference's inner type to the `ty` field. - sym::pointee => self.write_type_id(ty, &field_place)?, - // Write the boolean representing the reference's mutability to the `mutable` field. - sym::mutable => { - self.write_scalar(Scalar::from_bool(mutability.is_mut()), &field_place)? - } - other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), - } - } - interp_ok(()) - } - pub(crate) fn write_type_id_generics( &mut self, place: &impl Writeable<'tcx, CtfeProvenance>, diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index a68d6711849d5..1a19f60210509 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -100,7 +100,7 @@ pub enum TypeKind { /// String slice type. Str(Str), /// References. - Reference(Reference), + Reference, /// Pointers. Pointer, /// Function pointers. @@ -207,17 +207,6 @@ pub struct Str { // No additional information to provide for now. } -/// Compile-time type information about references. -#[derive(Debug)] -#[non_exhaustive] -#[unstable(feature = "type_info", issue = "146922")] -pub struct Reference { - /// The type of the value being referred to. - pub pointee: TypeId, - /// Whether this reference is mutable or not. - pub mutable: bool, -} - #[derive(Debug)] #[unstable(feature = "type_info", issue = "146922")] /// Function pointer, e.g. fn(u8), diff --git a/library/coretests/tests/mem/fn_ptr.rs b/library/coretests/tests/mem/fn_ptr.rs index 192054bcaf66b..b69478db000b7 100644 --- a/library/coretests/tests/mem/fn_ptr.rs +++ b/library/coretests/tests/mem/fn_ptr.rs @@ -43,16 +43,16 @@ fn test_ref() { if output != UNIT_TY { panic!(); } - let TypeKind::Reference(reference) = ty1.info().kind else { + let TypeKind::Reference = ty1.info().kind else { panic!(); }; - if reference.pointee != U8_TY { + if ty1.points_to().unwrap() != U8_TY { panic!(); } - let TypeKind::Reference(reference) = ty2.info().kind else { + let TypeKind::Reference = ty2.info().kind else { panic!(); }; - if reference.pointee != U8_TY { + if ty1.points_to().unwrap() != U8_TY { panic!(); } } diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index 5a9e1a77d6564..7fe592496f1a7 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -271,31 +271,30 @@ fn test_primitives() { #[test] fn test_references() { + use TypeKind::Reference; + // Immutable reference. - match const { Type::of::<&u8>() }.kind { - TypeKind::Reference(reference) => { - assert_eq!(reference.pointee, TypeId::of::()); - assert!(!reference.mutable); - } - _ => unreachable!(), + let Type { kind: Reference, .. } = Type::of::<&u8>() else { panic!() }; + const { + let ty = TypeId::of::<&u8>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(!ty.points_mutably()); } // Mutable references. - match const { Type::of::<&mut u64>() }.kind { - TypeKind::Reference(reference) => { - assert_eq!(reference.pointee, TypeId::of::()); - assert!(reference.mutable); - } - _ => unreachable!(), + let Type { kind: Reference, .. } = Type::of::<&mut u64>() else { panic!() }; + const { + let ty = TypeId::of::<&mut u64>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(ty.points_mutably()); } // Wide references. - match const { Type::of::<&dyn Any>() }.kind { - TypeKind::Reference(reference) => { - assert_eq!(reference.pointee, TypeId::of::()); - assert!(!reference.mutable); - } - _ => unreachable!(), + let Type { kind: Reference, .. } = Type::of::<&dyn Any>() else { panic!() }; + const { + let ty = TypeId::of::<&dyn Any>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(!ty.points_mutably()); } } From 98d68aa8ce1df53a07e4f68c22c9a31a56483664 Mon Sep 17 00:00:00 2001 From: Yara Date: Tue, 1 Sep 2026 23:58:45 +0200 Subject: [PATCH 06/30] reflection: adds `TypeId::function_ptr` returning FnPtr --- compiler/rustc_attr_ir/src/lang_items.rs | 3 + .../src/const_eval/machine.rs | 14 +++- .../rustc_hir_analysis/src/check/intrinsic.rs | 12 ++++ compiler/rustc_span/src/symbol.rs | 1 + library/core/src/intrinsics/mod.rs | 9 +++ library/core/src/mem/type_info.rs | 64 ++++++++++++++++++- 6 files changed, 101 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_attr_ir/src/lang_items.rs b/compiler/rustc_attr_ir/src/lang_items.rs index f2ad7abba755d..c1ad05dc8e4a8 100644 --- a/compiler/rustc_attr_ir/src/lang_items.rs +++ b/compiler/rustc_attr_ir/src/lang_items.rs @@ -465,6 +465,9 @@ language_item_table! { // Used to fallback `{float}` to `f32` when `f32: From<{float}>` From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1); FromFn, sym::from, from_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + + // Experimental lang item for `Reflection and comptime`(https://goals.rust-lang.org/2025h2/reflection-and-comptime.html) + FnPtr, sym::FnPtr, fn_ptr, Target::Struct, GenericRequirement::None; } /// The requirement imposed on the generics of a lang item diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 8e3a94a9c8bea..a26a6261251b3 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -700,7 +700,19 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { ); ecx.write_type_id(frt, dest)?; } - + sym::type_id_function_ptr => { + let ty = ecx.read_type_id(&args[0])?; + let variant_index = if let ty::FnPtr(sig, fn_header) = ty.kind() { + let (variant, variant_place) = ecx.project_downcast_named(dest, sym::Some)?; + let field_place = ecx.project_field(&variant_place, FieldIdx::ZERO)?; + let sig = sig.skip_binder(); + ecx.write_fn_ptr_type_info(field_place, &sig, fn_header)?; + variant + } else { + ecx.project_downcast_named(dest, sym::None)?.0 + }; + ecx.write_discriminant(variant_index, dest)?; + } sym::type_id_points_to => { let ty = ecx.read_type_id(&args[0])?; let variant_index = if let ty::RawPtr(pointee_ty, _) | ty::Ref(_, pointee_ty, _) = diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 8e5242de82fea..93544c383f699 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -202,6 +202,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::type_id_eq | sym::type_id_field_representing_type | sym::type_id_fields + | sym::type_id_function_ptr | sym::type_id_generics | sym::type_id_points_mutably | sym::type_id_is_signed @@ -319,6 +320,17 @@ pub(crate) fn check_intrinsic_type( (0, 0, vec![type_id_ty(), tcx.types.usize, tcx.types.usize], type_id_ty()) } sym::type_id_fields => (0, 0, vec![type_id_ty(), tcx.types.usize], tcx.types.usize), + sym::type_id_function_ptr => { + let fn_ptr = tcx.require_lang_item(LangItem::FnPtr, span); + let fn_ptr_adt_ref = tcx.adt_def(fn_ptr); + let fn_ptr_ty = Ty::new_adt(tcx, fn_ptr_adt_ref, ty::List::empty()); + + let option = tcx.require_lang_item(LangItem::Option, span); + let option_adt_ref = tcx.adt_def(option); + let option_args = tcx.mk_args(&[fn_ptr_ty.into()]); + let option_fn_ptr_ty = Ty::new_adt(tcx, option_adt_ref, option_args); + (0, 0, vec![type_id_ty()], option_fn_ptr_ty) + } sym::type_id_is_signed => (0, 0, vec![type_id_ty()], tcx.types.bool), sym::type_id_points_mutably => (0, 0, vec![type_id_ty()], tcx.types.bool), sym::type_id_points_to => (0, 0, vec![type_id_ty()], Ty::new_option(tcx, type_id_ty())), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 5ee54cc39a795..b2e2cd46a4291 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2168,6 +2168,7 @@ symbols! { type_id_eq, type_id_field_representing_type, type_id_fields, + type_id_function_ptr, type_id_generics, type_id_points_mutably, type_id_is_signed, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 3ed3ff0518bf6..52dd5cc024cb8 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3098,6 +3098,15 @@ pub fn field_representing_type_name(_frt_type_id: crate::any::TypeId) -> &'stati #[rustc_comptime] pub fn field_representing_type_offset(_frt_type_id: crate::any::TypeId) -> usize; +/// Given a `TypeId` that represents a function pointer returns an [`core::mem::type_info::FnPtr`]. +/// When called on something else this returns `None`. +/// +/// The more user-friendly version of this intrinsic is [`core::any::TypeId::function_ptr`]. +#[rustc_intrinsic] +#[unstable(feature = "core_intrinsics", issue = "none")] +#[rustc_comptime] +pub fn type_id_function_ptr(_type_id: crate::any::TypeId) -> Option; + /// Checks whether this type is non-exhaustive. #[rustc_intrinsic] #[unstable(feature = "core_intrinsics", issue = "none")] diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 1a19f60210509..abf42f5c2c802 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -106,6 +106,8 @@ pub enum TypeKind { /// Function pointers. FnPtr(FnPtr), /// FIXME(#146922): add all the common types + /// non exhaustive list: + /// - Never Other, } @@ -208,6 +210,7 @@ pub struct Str { } #[derive(Debug)] +#[lang = "FnPtr"] #[unstable(feature = "type_info", issue = "146922")] /// Function pointer, e.g. fn(u8), pub struct FnPtr { @@ -238,12 +241,36 @@ pub struct FnPtr { impl FnPtr { /// Returns the splatted function argument index, or `None` if no argument is splatted. + /// + /// e.g. in `fn overload(a: u8, #[rustc_splat] b: (f32, usize))` the index is 1, + /// and it can be called as `overload(a, 1.0, 2)`. pub const fn splatted(&self) -> Option { if self.is_splatted { Some(self.splatted_index) } else { None } } + /// Whether this function is variadic, e.g. extern "C" fn add(n: usize, mut args: ...); + pub const fn is_variadic(&self) -> Option { + if self.is_splatted { Some(self.splatted_index) } else { None } + } + /// whether this refers to an unsafe function. + pub const fn is_unsafe(&self) -> bool { + self.unsafety + } + /// Returns the application binary interface. For example extern "C". + pub const fn abi(&self) -> Abi { + self.abi + } + /// The types of the functions parameters + pub const fn inputs(&self) -> &'static [TypeId] { + self.inputs + } + /// List of the types returned by the function. For a function with no output + /// specified this returns `TypeId::of<()>`. + pub const fn output(&self) -> TypeId { + self.output + } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] #[unstable(feature = "type_info", issue = "146922")] /// Abi of [FnPtr] @@ -590,6 +617,41 @@ impl TypeId { pub fn points_mutably(self) -> bool { intrinsics::type_id_points_mutably(self) } + + /// Given a `TypeId` that represents a function pointer returns an + /// [`FnPtr`]. When called on something else this returns `None`. + /// ``` + /// #![feature(type_info)] + /// use std::any::TypeId; + /// use std::mem::type_info::{Abi, FnPtr}; + /// + /// const F: FnPtr = TypeId::of:: usize>() + /// .function_ptr() + /// .expect("TypeId of a function ptr"); + /// + /// assert!(F.inputs() == [TypeId::of::(), TypeId::of::()]); + /// assert!(F.output() == TypeId::of::()); + /// assert!(F.abi() == Abi::default()); + /// ``` + /// ``` + /// #![feature(type_info)] + /// # use std::any::TypeId; + /// # use std::mem::type_info::{Abi, FnPtr}; + /// # + /// const F: FnPtr = TypeId::of::() + /// .function_ptr() + /// .expect("TypeId of a function ptr"); + /// + /// assert!(F.inputs() == []); + /// assert!(F.output() == TypeId::of::<()>()); + /// assert!(F.abi() == Abi::default()); + /// ``` + #[unstable(feature = "type_info", issue = "146922")] + #[rustc_const_unstable(feature = "type_info", issue = "146922")] + #[rustc_comptime] + pub fn function_ptr(self) -> Option { + intrinsics::type_id_function_ptr(self) + } } /// Variant representing type ID. Representing a variant of an enum. From 06fa642f235127eb3287d887c6f9740f55da0af3 Mon Sep 17 00:00:00 2001 From: Yara Date: Fri, 4 Sep 2026 12:09:29 +0200 Subject: [PATCH 07/30] reflection: remove field from TypeKind::FnPtr --- .../src/const_eval/machine.rs | 2 +- .../src/const_eval/type_info.rs | 13 +- .../rustc_hir_analysis/src/check/intrinsic.rs | 2 +- compiler/rustc_span/src/symbol.rs | 4 +- library/core/src/mem/type_info.rs | 35 +-- library/coretests/tests/mem/fn_ptr.rs | 250 +++++------------- 6 files changed, 77 insertions(+), 229 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index a26a6261251b3..ce4c8497463c8 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -705,7 +705,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { let variant_index = if let ty::FnPtr(sig, fn_header) = ty.kind() { let (variant, variant_place) = ecx.project_downcast_named(dest, sym::Some)?; let field_place = ecx.project_field(&variant_place, FieldIdx::ZERO)?; - let sig = sig.skip_binder(); + let sig = sig.skip_binder(); // FIXME: handle lifetime bounds ecx.write_fn_ptr_type_info(field_place, &sig, fn_header)?; variant } else { diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index 2ac5abdd78578..8d93aee57a518 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -150,16 +150,9 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { self.write_dyn_trait_type_info(dyn_place, *predicates, *region)?; variant } - ty::FnPtr(sig, fn_header) => { - let (variant, variant_place) = + ty::FnPtr(_, _) => { + let (variant, _) = self.project_downcast_named(&field_dest, sym::FnPtr)?; - let fn_ptr_place = - self.project_field(&variant_place, FieldIdx::ZERO)?; - - // FIXME: handle lifetime bounds - let sig = sig.skip_binder(); - - self.write_fn_ptr_type_info(fn_ptr_place, &sig, fn_header)?; variant } ty::Foreign(_) @@ -348,7 +341,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { let field_place = self.project_field(&place, field_idx)?; match field.name { - sym::unsafety => { + sym::is_unsafe => { self.write_scalar(Scalar::from_bool(!fn_sig_kind.is_safe()), &field_place)?; } sym::abi => match fn_sig_kind.abi() { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 93544c383f699..61f72ee4b5de1 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -204,8 +204,8 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::type_id_fields | sym::type_id_function_ptr | sym::type_id_generics - | sym::type_id_points_mutably | sym::type_id_is_signed + | sym::type_id_points_mutably | sym::type_id_points_to | sym::type_id_variants | sym::type_id_vtable diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b2e2cd46a4291..87ff02d9aa58b 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1152,6 +1152,7 @@ symbols! { is, is_auto, is_splatted, + is_unsafe, is_val_statically_known, isa_attribute, isize, @@ -2170,8 +2171,8 @@ symbols! { type_id_fields, type_id_function_ptr, type_id_generics, - type_id_points_mutably, type_id_is_signed, + type_id_points_mutably, type_id_points_to, type_id_variants, type_id_vtable, @@ -2264,7 +2265,6 @@ symbols! { unsafe_no_drop_flag, unsafe_pinned, unsafe_unpin, - unsafety, unsize, unsized_const_param_ty, unsized_const_params, diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index abf42f5c2c802..1f38339a7421b 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -104,7 +104,7 @@ pub enum TypeKind { /// Pointers. Pointer, /// Function pointers. - FnPtr(FnPtr), + FnPtr, /// FIXME(#146922): add all the common types /// non exhaustive list: /// - Never @@ -214,29 +214,16 @@ pub struct Str { #[unstable(feature = "type_info", issue = "146922")] /// Function pointer, e.g. fn(u8), pub struct FnPtr { - /// Unsafety, true is unsafe - pub unsafety: bool, - - /// Abi, e.g. extern "C" - pub abi: Abi, - - /// Function inputs - pub inputs: &'static [TypeId], - - /// Function return type, default is TypeId::of::<()> - pub output: TypeId, - - /// Vardiadic function, e.g. extern "C" fn add(n: usize, mut args: ...); - pub variadic: bool, - + is_unsafe: bool, + abi: Abi, + inputs: &'static [TypeId], + output: TypeId, + variadic: bool, // FIXME(splat): should these fields be private, or merged into an Option? /// Is any function argument splatted? - pub is_splatted: bool, + is_splatted: bool, - /// The index of the splatted function argument in `inputs`, only valid if `is_splatted` is true. - /// e.g. in `fn overload(a: u8, #[rustc_splat] b: (f32, usize))` the index is 1, and it can be called - /// as `overload(a, 1.0, 2)`. - pub splatted_index: u8, + splatted_index: u8, } impl FnPtr { @@ -248,12 +235,12 @@ impl FnPtr { if self.is_splatted { Some(self.splatted_index) } else { None } } /// Whether this function is variadic, e.g. extern "C" fn add(n: usize, mut args: ...); - pub const fn is_variadic(&self) -> Option { - if self.is_splatted { Some(self.splatted_index) } else { None } + pub const fn is_variadic(&self) -> bool { + self.variadic } /// whether this refers to an unsafe function. pub const fn is_unsafe(&self) -> bool { - self.unsafety + self.is_unsafe } /// Returns the application binary interface. For example extern "C". pub const fn abi(&self) -> Abi { diff --git a/library/coretests/tests/mem/fn_ptr.rs b/library/coretests/tests/mem/fn_ptr.rs index b69478db000b7..862048e4090d4 100644 --- a/library/coretests/tests/mem/fn_ptr.rs +++ b/library/coretests/tests/mem/fn_ptr.rs @@ -3,233 +3,101 @@ use std::mem::type_info::{Abi, FnPtr, Type, TypeKind}; const STRING_TY: TypeId = const { TypeId::of::() }; const U8_TY: TypeId = const { TypeId::of::() }; -const _U8_REF_TY: TypeId = const { TypeId::of::<&u8>() }; const UNIT_TY: TypeId = const { TypeId::of::<()>() }; const TUPLE_STRING_U8_TY: TypeId = const { TypeId::of::<(String, u8)>() }; #[test] fn test_fn_ptrs() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + let f = const { TypeId::of::().function_ptr().unwrap() }; + assert_eq!(f.is_unsafe(), false); + assert_eq!(f.abi(), Abi::ExternRust); + assert_eq!(f.inputs(), &[]); + assert_eq!(f.output(), UNIT_TY); + assert_eq!(f.is_variadic(), false); + assert_eq!(f.splatted(), None); } + +#[test] +fn test_typekind() { + assert!(matches!(const { Type::of::().kind }, TypeKind::FnPtr)); + assert!(matches!(const { Type::of::().kind }, TypeKind::FnPtr)); + assert!(matches!(const { Type::of::().kind }, TypeKind::FnPtr)); + assert!(matches!(const { Type::of::().kind }, TypeKind::FnPtr)); + assert!(matches!( + const { Type::of::().kind }, + TypeKind::FnPtr + )); +} + #[test] fn test_ref() { - const { - // references are tricky because the lifetimes give the references different type ids - // so we check the pointees instead - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[ty1, ty2], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - if output != UNIT_TY { - panic!(); - } - let TypeKind::Reference = ty1.info().kind else { - panic!(); - }; - if ty1.points_to().unwrap() != U8_TY { - panic!(); - } - let TypeKind::Reference = ty2.info().kind else { - panic!(); - }; - if ty1.points_to().unwrap() != U8_TY { - panic!(); - } - } + // references are tricky because the lifetimes give the references different type ids + // so we check the pointees instead + const F: FnPtr = TypeId::of::().function_ptr().unwrap(); + assert_eq!(const { F.inputs()[0].points_to() }, Some(U8_TY)); + assert_eq!(const { F.inputs()[1].points_to() }, Some(U8_TY)); } #[test] fn test_unsafe() { - let TypeKind::FnPtr(FnPtr { - unsafety: true, - abi: Abi::ExternRust, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + assert_eq!(const { TypeId::of::().function_ptr() }.unwrap().is_unsafe(), true); } + #[test] fn test_abi() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().abi(), + Abi::ExternRust + ); - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternC, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().abi(), + Abi::ExternC + ); - let TypeKind::FnPtr(FnPtr { - unsafety: true, - abi: Abi::Named("system"), - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().abi(), + Abi::Named("system") + ); } #[test] fn test_inputs() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[ty1, ty2], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); - assert_eq!(ty1, STRING_TY); - assert_eq!(ty2, U8_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().inputs(), + [STRING_TY, U8_TY] + ); - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[ty1, ty2], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); - assert_eq!(ty1, STRING_TY); - assert_eq!(ty2, U8_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().inputs(), + [STRING_TY, U8_TY] + ); } #[test] fn test_output() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of:: u8>().kind }) - else { - panic!(); - }; - assert_eq!(output, U8_TY); + let f = const { TypeId::of:: u8>().function_ptr() }.unwrap(); + assert_eq!(f.output(), U8_TY); } #[test] fn test_variadic() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternC, - inputs: [ty1], - output, - variadic: true, - is_splatted: false, - splatted_index: _, - }) = &(const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, &UNIT_TY); - assert_eq!(*ty1, U8_TY); + let f = const { TypeId::of::().function_ptr() }.unwrap(); + assert_eq!(f.abi(), Abi::ExternC); + assert_eq!(f.inputs(), [U8_TY]); + assert_eq!(f.is_variadic(), true); } #[test] fn test_splat() { - #[rustfmt::skip] - let TypeKind::FnPtr(fn_ptr_ty) = &(const { Type::of::().kind }) else { - panic!(); - }; - let FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: [ty1], - output, - variadic: false, - is_splatted: true, - splatted_index: 0, - } = fn_ptr_ty - else { - panic!(); - }; - assert_eq!(output, &UNIT_TY); - assert_eq!(*ty1, TUPLE_STRING_U8_TY); - assert_eq!(fn_ptr_ty.splatted(), Some(0)); + let f = const { TypeId::of::().function_ptr() }.unwrap(); + assert_eq!(f.inputs(), [TUPLE_STRING_U8_TY]); + assert_eq!(f.splatted(), Some(0)); } #[test] fn test_not_splat() { - let TypeKind::FnPtr(fn_ptr_ty) = &(const { Type::of::().kind }) else { - panic!(); - }; - let FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: [ty1], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - } = fn_ptr_ty - else { - panic!(); - }; - assert_eq!(output, &UNIT_TY); - assert_eq!(*ty1, TUPLE_STRING_U8_TY); - assert_eq!(fn_ptr_ty.splatted(), None); + let f = const { TypeId::of::().function_ptr() }.unwrap(); + assert_eq!(f.inputs(), [TUPLE_STRING_U8_TY]); + assert_eq!(f.splatted(), None); } From 0238ef92794018efe672439a5f6ed22bd58bbc75 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 3 Sep 2026 16:42:07 +0200 Subject: [PATCH 08/30] change compile-flags for asm ui tests to not embed bitcode or use LTO --- tests/ui/asm/aarch64/srcloc.rs | 2 +- tests/ui/asm/inline-syntax.arm.stderr | 11 +---------- tests/ui/asm/inline-syntax.rs | 5 ++--- tests/ui/asm/riscv/riscv32e-registers.rs | 2 +- tests/ui/asm/x86_64/srcloc.rs | 2 +- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/tests/ui/asm/aarch64/srcloc.rs b/tests/ui/asm/aarch64/srcloc.rs index 91a2ef3514aee..b5e77c4023970 100644 --- a/tests/ui/asm/aarch64/srcloc.rs +++ b/tests/ui/asm/aarch64/srcloc.rs @@ -1,7 +1,7 @@ //@ add-minicore //@ build-fail //@ needs-asm-support -//@ compile-flags: --target aarch64-unknown-linux-gnu -Ccodegen-units=1 +//@ compile-flags: --target aarch64-unknown-linux-gnu -Ccodegen-units=1 -Cembed-bitcode=false -Clto=no //@ needs-llvm-components: aarch64 //@ ignore-backends: gcc #![crate_type = "lib"] diff --git a/tests/ui/asm/inline-syntax.arm.stderr b/tests/ui/asm/inline-syntax.arm.stderr index 5b193d26c8776..315f97bb09de9 100644 --- a/tests/ui/asm/inline-syntax.arm.stderr +++ b/tests/ui/asm/inline-syntax.arm.stderr @@ -6,15 +6,6 @@ note: instantiated into assembly here LL | .intel_syntax noprefix | ^ -error: unknown directive - | -note: instantiated into assembly here - --> :1:1 - | -LL | .intel_syntax noprefix - | ^ - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error: unknown directive --> $DIR/inline-syntax.rs:21:15 | @@ -87,5 +78,5 @@ note: instantiated into assembly here LL | .intel_syntax noprefix | ^ -error: aborting due to 8 previous errors +error: aborting due to 7 previous errors diff --git a/tests/ui/asm/inline-syntax.rs b/tests/ui/asm/inline-syntax.rs index 63395c1096c09..d7c9fc8972cb7 100644 --- a/tests/ui/asm/inline-syntax.rs +++ b/tests/ui/asm/inline-syntax.rs @@ -1,9 +1,9 @@ //@ add-minicore //@ revisions: x86_64 arm -//@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu -Cembed-bitcode=false -Clto=no //@[x86_64] check-pass //@[x86_64] needs-llvm-components: x86 -//@[arm] compile-flags: --target armv7-unknown-linux-gnueabihf +//@[arm] compile-flags: --target armv7-unknown-linux-gnueabihf -Cembed-bitcode=false -Clto=no //@[arm] build-fail //@[arm] needs-llvm-components: arm //@[arm] min-llvm-version: 23 @@ -49,4 +49,3 @@ global_asm!(".intel_syntax noprefix", "nop"); // Global assembly errors don't have line numbers, so no error on ARM. //[arm]~? ERROR unknown directive -//[arm]~? ERROR unknown directive diff --git a/tests/ui/asm/riscv/riscv32e-registers.rs b/tests/ui/asm/riscv/riscv32e-registers.rs index a5f4151b2c80a..77a2d92c3736b 100644 --- a/tests/ui/asm/riscv/riscv32e-registers.rs +++ b/tests/ui/asm/riscv/riscv32e-registers.rs @@ -4,7 +4,7 @@ //@ build-fail //@ revisions: riscv32e_llvm23 riscv32em_llvm23 riscv32emc_llvm23 //@ revisions: riscv32e_llvm24 riscv32em_llvm24 riscv32emc_llvm24 -//@ compile-flags: --crate-type=rlib +//@ compile-flags: --crate-type=rlib -Cembed-bitcode=false -Clto=no //@ [riscv32e_llvm23] needs-llvm-components: riscv //@ [riscv32e_llvm23] compile-flags: --target=riscv32e-unknown-none-elf //@ [riscv32e_llvm23] max-llvm-major-version: 23 diff --git a/tests/ui/asm/x86_64/srcloc.rs b/tests/ui/asm/x86_64/srcloc.rs index e73854acf1522..9bbd20340e3ab 100644 --- a/tests/ui/asm/x86_64/srcloc.rs +++ b/tests/ui/asm/x86_64/srcloc.rs @@ -1,6 +1,6 @@ //@ add-minicore //@ build-fail -//@ compile-flags: --target x86_64-unknown-linux-gnu -Ccodegen-units=1 +//@ compile-flags: --target x86_64-unknown-linux-gnu -Ccodegen-units=1 -Cembed-bitcode=false -Clto=no //@ needs-llvm-components: x86 //@ ignore-backends: gcc #![crate_type = "lib"] From fa5601dd03bd9dfabe322e2c78af832f0128960d Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 3 Sep 2026 16:05:14 +0200 Subject: [PATCH 09/30] add test for inline asm cookie reproducibility --- .../inline-asm-cookie-issue-150451.rs | 9 ++++ .../rmake.rs | 42 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/run-make/parallel-reproducible-inline-asm-cookie/inline-asm-cookie-issue-150451.rs create mode 100644 tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs diff --git a/tests/run-make/parallel-reproducible-inline-asm-cookie/inline-asm-cookie-issue-150451.rs b/tests/run-make/parallel-reproducible-inline-asm-cookie/inline-asm-cookie-issue-150451.rs new file mode 100644 index 0000000000000..be49be470091e --- /dev/null +++ b/tests/run-make/parallel-reproducible-inline-asm-cookie/inline-asm-cookie-issue-150451.rs @@ -0,0 +1,9 @@ +use std::thread; + +fn _main() { + let _t1 = thread::spawn(|| { + for _ in 0..100 { + println!("test"); + } + }); +} diff --git a/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs b/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs new file mode 100644 index 0000000000000..62ad5a46af860 --- /dev/null +++ b/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs @@ -0,0 +1,42 @@ +//@ needs-target-std +//@ ignore-cross-compile +//@ ignore-windows-gnu +// GNU Linker for Windows is non-deterministic. (from `reproducible-build-2` test in this suite) + +use std::rc::Rc; + +use run_make_support::{bin_name, is_windows_msvc, rfs, run_in_tmpdir, rustc}; + +/// Test that parallel compiler produces identical binaries. +fn main() { + const FILE_NAME: &str = "inline-asm-cookie-issue-150451"; + let bin_name = bin_name(FILE_NAME); + + let mut reference = None; + + for _ in 0..10 { + // Tmp dir as previous runs affect output binary on windows. + run_in_tmpdir(|| { + let mut rustc = rustc(); + rustc + .input(format!("{FILE_NAME}.rs")) + .arg("--crate-type=lib") + .arg("-Zthreads=3") + .arg("-Clink-dead-code=true") + .arg("-Copt-level=0") + .arg("-Cembed-bitcode=true") + .output(&bin_name); + + if is_windows_msvc() { + rustc.arg("-Clink-arg=/Brepro"); + } + + rustc.run(); + + let current = Rc::new(rfs::read(&bin_name)); + reference.get_or_insert(Rc::clone(¤t)); + + assert_eq!(Some(current), reference); + }); + } +} From 970cb98f0ea3472c8bbb9152f74025ec73e03ef7 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 30 Jul 2026 08:11:34 +0200 Subject: [PATCH 10/30] Disable inline asm line info cookies when llvm bitcode is saved or LTO is enabled The parallel frontend makes the cookies nondeterministic in their current form, resulting in nondeterministic outputs when bitcode is emitted or LTO is used. Causes minor diagnostic regression for inline asm in release builds. --- .../rustc_codegen_cranelift/src/driver/aot.rs | 1 + compiler/rustc_codegen_gcc/src/lib.rs | 1 + compiler/rustc_codegen_llvm/src/asm.rs | 55 ++++++++++++------- compiler/rustc_codegen_llvm/src/base.rs | 11 +++- compiler/rustc_codegen_llvm/src/context.rs | 3 + compiler/rustc_codegen_llvm/src/lib.rs | 3 +- compiler/rustc_codegen_ssa/src/back/write.rs | 14 ++--- compiler/rustc_codegen_ssa/src/base.rs | 22 ++++++-- .../rustc_codegen_ssa/src/traits/backend.rs | 1 + 9 files changed, 74 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index d6c25cf524a5c..e06549090226e 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -284,6 +284,7 @@ impl ExtraBackendMethods for AotDriver { &self, tcx: TyCtxt<'_>, cgu_name: Symbol, + _bitcode_needed: bool, ) -> (ModuleCodegen, u64) { let start_time = Instant::now(); diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index cbc7db8e9e23f..2fb5459a20283 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -365,6 +365,7 @@ impl ExtraBackendMethods for GccCodegenBackend { &self, tcx: TyCtxt<'_>, cgu_name: Symbol, + _bitcode_needed: bool, ) -> (ModuleCodegen, u64) { base::compile_codegen_unit( tcx, diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index 0f74f5e81d684..9960ee2ec64d0 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -10,6 +10,8 @@ use rustc_middle::mir::interpret::{PointerArithmetic, Scalar as ConstScalar}; use rustc_middle::ty::Instance; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::{bug, span_bug}; +use rustc_session::Session; +use rustc_session::config::Lto; use rustc_span::{Pos, Span, Symbol, sym}; use rustc_target::asm::*; use rustc_target::spec::HasTargetSpec; @@ -594,30 +596,45 @@ pub(crate) fn inline_asm_call<'ll>( let key = "srcloc"; let kind = bx.get_md_kind_id(key); - // `srcloc` contains one 64-bit integer for each line of assembly code, - // where the lower 32 bits hold the lo byte position and the upper 32 bits - // hold the hi byte position. - let mut srcloc = vec![]; - if dia == llvm::AsmDialect::Intel && line_spans.len() > 1 { - // LLVM inserts an extra line to add the ".intel_syntax", so add - // a dummy srcloc entry for it. - // - // Don't do this if we only have 1 line span since that may be - // due to the asm template string coming from a macro. LLVM will - // default to the first srcloc for lines that don't have an - // associated srcloc. - srcloc.push(llvm::LLVMValueAsMetadata(bx.const_u64(0))); + if allow_raw_span_inline_asm_srcloc(bx.tcx.sess, bx.bitcode_needed) { + // `srcloc` contains one 64-bit integer for each line of assembly code, + // where the lower 32 bits hold the lo byte position and the upper 32 bits + // hold the hi byte position. + let mut srcloc = vec![]; + if dia == llvm::AsmDialect::Intel && line_spans.len() > 1 { + // LLVM inserts an extra line to add the ".intel_syntax", so add + // a dummy srcloc entry for it. + // + // Don't do this if we only have 1 line span since that may be + // due to the asm template string coming from a macro. LLVM will + // default to the first srcloc for lines that don't have an + // associated srcloc. + srcloc.push(llvm::LLVMValueAsMetadata(bx.const_u64(0))); + } + srcloc.extend(line_spans.iter().map(|span| { + llvm::LLVMValueAsMetadata( + bx.const_u64(u64::from(span.lo().to_u32()) | (u64::from(span.hi().to_u32()) << 32)), + ) + })); + bx.cx.set_metadata_node(call, kind, &srcloc); } - srcloc.extend(line_spans.iter().map(|span| { - llvm::LLVMValueAsMetadata( - bx.const_u64(u64::from(span.lo().to_u32()) | (u64::from(span.hi().to_u32()) << 32)), - ) - })); - bx.cx.set_metadata_node(call, kind, &srcloc); Some(call) } +/// Whenever inline assembly bitcode is built, its `srcloc` contains the raw span numbers +/// as location cookies. This is problematic since that is nondeterministic when using +/// the parallel frontend. Even without parallelism, the cookies are meaningless in another +/// rustc session. +/// +/// Discussion about replacing the cookies with something stable: rust-lang/rust#150451 +fn allow_raw_span_inline_asm_srcloc(sess: &Session, bitcode_needed: bool) -> bool { + // even for Lto::ThinLocal, where the bitcode isn't serialized into files, the changes in + // raw span positions would reflect in the LTO module hashes, which could lead to + // nondeterminism + sess.lto() == Lto::No && !bitcode_needed +} + /// If the register is an xmm/ymm/zmm register then return its index. fn xmm_reg_index(reg: InlineAsmReg) -> Option { use X86InlineAsmReg::*; diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs index 14700266412dd..401b52318539b 100644 --- a/compiler/rustc_codegen_llvm/src/base.rs +++ b/compiler/rustc_codegen_llvm/src/base.rs @@ -64,6 +64,7 @@ pub(crate) fn iter_global_aliases(llmod: &llvm::Module) -> ValueIter<'_> { pub(crate) fn compile_codegen_unit( tcx: TyCtxt<'_>, cgu_name: Symbol, + bitcode_needed: bool, ) -> (ModuleCodegen, u64) { let start_time = Instant::now(); @@ -71,7 +72,7 @@ pub(crate) fn compile_codegen_unit( let (module, _) = tcx.dep_graph.with_task( dep_node, tcx, - || module_codegen(tcx, cgu_name), + || module_codegen(tcx, cgu_name, bitcode_needed), Some(dep_graph::hash_result), ); let time_to_codegen = start_time.elapsed(); @@ -80,7 +81,11 @@ pub(crate) fn compile_codegen_unit( // the time we needed for codegenning it. let cost = time_to_codegen.as_nanos() as u64; - fn module_codegen(tcx: TyCtxt<'_>, cgu_name: Symbol) -> ModuleCodegen { + fn module_codegen( + tcx: TyCtxt<'_>, + cgu_name: Symbol, + needs_bitcode: bool, + ) -> ModuleCodegen { let cgu = tcx.codegen_unit(cgu_name); let _prof_timer = tcx.prof.generic_activity_with_arg_recorder("codegen_module", |recorder| { @@ -90,7 +95,7 @@ pub(crate) fn compile_codegen_unit( // Instantiate monomorphizations without filling out definitions yet... let llvm_module = ModuleLlvm::new(tcx, cgu_name.as_str()); { - let mut cx = CodegenCx::new(tcx, cgu, &llvm_module); + let mut cx = CodegenCx::new(tcx, cgu, &llvm_module, needs_bitcode); // Declare and store globals shared by all offload kernels // diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 853c4bfc9ca3f..66886fc080e88 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -92,6 +92,7 @@ pub(crate) type CodegenCx<'ll, 'tcx> = GenericCx<'ll, FullCx<'ll, 'tcx>>; pub(crate) struct FullCx<'ll, 'tcx> { pub tcx: TyCtxt<'tcx>, + pub bitcode_needed: bool, pub scx: SimpleCx<'ll>, pub use_dll_storage_attrs: bool, pub tls_model: llvm::ThreadLocalMode, @@ -606,6 +607,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { tcx: TyCtxt<'tcx>, codegen_unit: &'tcx CodegenUnit<'tcx>, llvm_module: &'ll crate::ModuleLlvm, + bitcode_needed: bool, ) -> Self { // An interesting part of Windows which MSVC forces our hand on (and // apparently MinGW didn't) is the usage of `dllimport` and `dllexport` @@ -683,6 +685,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { GenericCx( FullCx { tcx, + bitcode_needed, scx: SimpleCx::new(llmod, llcx, tcx.data_layout.pointer_size()), use_dll_storage_attrs, tls_model, diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 775e1dcf2ffea..ca16d33b90256 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -112,8 +112,9 @@ impl ExtraBackendMethods for LlvmCodegenBackend { &self, tcx: TyCtxt<'_>, cgu_name: Symbol, + bitcode_needed: bool, ) -> (ModuleCodegen, u64) { - base::compile_codegen_unit(tcx, cgu_name) + base::compile_codegen_unit(tcx, cgu_name, bitcode_needed) } } diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 78cdd3e38f68c..e8b25eb4359d0 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -15,7 +15,6 @@ use rustc_errors::{ Level, MultiSpan, Style, Suggestions, catch_fatal_errors, }; use rustc_fs_util::link_or_copy; -use rustc_hir::find_attr; use rustc_incremental::{copy_cgu_workproduct_to_incr_comp_cache_dir, in_incr_comp_dir_sess}; use rustc_macros::{Decodable, Encodable}; use rustc_metadata::fs::copy_to_stdout; @@ -114,7 +113,7 @@ pub struct ModuleConfig { } impl ModuleConfig { - fn new(kind: ModuleKind, tcx: TyCtxt<'_>, no_builtins: bool) -> ModuleConfig { + pub(crate) fn new(kind: ModuleKind, tcx: TyCtxt<'_>, no_builtins: bool) -> ModuleConfig { // If it's a regular module, use `$regular`, otherwise use `$other`. // `$regular` and `$other` are evaluated lazily. macro_rules! if_regular { @@ -426,15 +425,12 @@ fn need_pre_lto_bitcode_for_incr_comp(sess: &Session) -> bool { pub(crate) fn start_async_codegen( backend: B, tcx: TyCtxt<'_>, + regular_config: Arc, + allocator_config: Arc, allocator_module: Option>, ) -> OngoingCodegen { let (coordinator_send, coordinator_receive) = channel(); - let no_builtins = find_attr!(tcx, crate, NoBuiltins); - - let regular_config = ModuleConfig::new(ModuleKind::Regular, tcx, no_builtins); - let allocator_config = ModuleConfig::new(ModuleKind::Allocator, tcx, no_builtins); - let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); let (codegen_worker_send, codegen_worker_receive) = channel(); @@ -444,8 +440,8 @@ pub(crate) fn start_async_codegen( shared_emitter, codegen_worker_send, coordinator_receive, - Arc::new(regular_config), - Arc::new(allocator_config), + regular_config, + allocator_config, allocator_module, coordinator_send.clone(), ); diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index c870d1694d068..8dd129f45cc5a 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -42,7 +42,7 @@ use tracing::{debug, info}; use crate::assert_module_sources::CguReuse; use crate::back::link::are_upstream_rust_objects_already_included; use crate::back::write::{ - ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, start_async_codegen, + ComputedLtoType, ModuleConfig, OngoingCodegen, compute_per_cgu_lto_type, start_async_codegen, submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, submit_pre_lto_module_to_llvm, }; use crate::common::{self, IntPredicate, RealPredicate, TypeKind}; @@ -52,7 +52,7 @@ use crate::mir::place::PlaceRef; use crate::traits::*; use crate::{ CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, EiiLinkageImplInfo, EiiLinkageInfo, - ModuleCodegen, diagnostics, meth, mir, + ModuleCodegen, ModuleKind, diagnostics, meth, mir, }; pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate { @@ -762,7 +762,18 @@ pub fn codegen_crate< None }; - let ongoing_codegen = start_async_codegen(backend.clone(), tcx, allocator_module); + let no_builtins = find_attr!(tcx, crate, NoBuiltins); + let regular_module_config = ModuleConfig::new(ModuleKind::Regular, tcx, no_builtins); + let bitcode_needed = regular_module_config.bitcode_needed(); + let allocator_module_config = ModuleConfig::new(ModuleKind::Allocator, tcx, no_builtins); + + let ongoing_codegen = start_async_codegen( + backend.clone(), + tcx, + Arc::new(regular_module_config), + Arc::new(allocator_module_config), + allocator_module, + ); // For better throughput during parallel processing by LLVM, we used to sort // CGUs largest to smallest. This would lead to better thread utilization @@ -822,7 +833,8 @@ pub fn codegen_crate< let start_time = Instant::now(); let pre_compiled_cgus = par_map(cgus, |(i, _)| { - let module = backend.compile_codegen_unit(tcx, codegen_units[i].name()); + let module = + backend.compile_codegen_unit(tcx, codegen_units[i].name(), bitcode_needed); (i, IntoDynSyncSend(module)) }); @@ -846,7 +858,7 @@ pub fn codegen_crate< cgu.0 } else { let start_time = Instant::now(); - let module = backend.compile_codegen_unit(tcx, cgu.name()); + let module = backend.compile_codegen_unit(tcx, cgu.name(), bitcode_needed); total_codegen_time += start_time.elapsed(); module }; diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index 11878c1f5165d..2435cca50a0f3 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -177,5 +177,6 @@ pub trait ExtraBackendMethods: Send + Sync + DynSend + DynSync { &self, tcx: TyCtxt<'_>, cgu_name: Symbol, + bitcode_needed: bool, ) -> (ModuleCodegen, u64); } From 714b06b962ffb2a6fbdf01b9622374d505acc96d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 6 Sep 2026 19:31:09 +0200 Subject: [PATCH 11/30] add test ensuring we refuse to const-eval the body of a rustc_do_not_const_check function --- .../consts/const-eval/do_not_const_check.rs | 25 +++++++++++++++++++ .../const-eval/do_not_const_check.stderr | 15 +++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/ui/consts/const-eval/do_not_const_check.rs create mode 100644 tests/ui/consts/const-eval/do_not_const_check.stderr diff --git a/tests/ui/consts/const-eval/do_not_const_check.rs b/tests/ui/consts/const-eval/do_not_const_check.rs new file mode 100644 index 0000000000000..ced2557bffd19 --- /dev/null +++ b/tests/ui/consts/const-eval/do_not_const_check.rs @@ -0,0 +1,25 @@ +//! Ensure that we refuse to run a do_not_const_check function, even if the body *would* const-check +//! at the moment. +#![feature(rustc_attrs, intrinsics)] + +#[rustc_do_not_const_check] +const fn mostly_harmless() {} + +const _: () = { + mostly_harmless(); //~ERROR: calling non-const function +}; + +// Also ensure the same happens with intrinsics. +// Here we need some intrinsic that the interpreter does *not* have a native implementation for. +// Let's hope nobody adds one... +#[rustc_intrinsic] +#[rustc_do_not_const_check] +pub const fn integer_min(a: T, b: T) -> T { + a +} + +const _: () = { + integer_min(0, 1); //~ERROR: calling non-const function +}; + +fn main() {} diff --git a/tests/ui/consts/const-eval/do_not_const_check.stderr b/tests/ui/consts/const-eval/do_not_const_check.stderr new file mode 100644 index 0000000000000..507999df218d1 --- /dev/null +++ b/tests/ui/consts/const-eval/do_not_const_check.stderr @@ -0,0 +1,15 @@ +error[E0080]: calling non-const function `mostly_harmless` + --> $DIR/do_not_const_check.rs:9:5 + | +LL | mostly_harmless(); + | ^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error[E0080]: calling non-const function `integer_min::` + --> $DIR/do_not_const_check.rs:22:5 + | +LL | integer_min(0, 1); + | ^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0080`. From eb2326af7c23acb3758d4fb7d5df68e029c3c413 Mon Sep 17 00:00:00 2001 From: pbkx <93405617+pbkx@users.noreply.github.com> Date: Fri, 19 Jun 2026 13:28:09 -0700 Subject: [PATCH 12/30] avoid spurious lifetime diagnostic in async generic case --- .../rustc_borrowck/src/region_infer/mod.rs | 35 ++++++++++++++++++- .../spurious-static-bound-issue-115376.rs | 8 +++++ .../spurious-static-bound-issue-115376.stderr | 17 +++++++++ .../unconstrained-closure-lifetime-generic.rs | 1 - ...onstrained-closure-lifetime-generic.stderr | 17 ++------- 5 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 tests/ui/async-await/spurious-static-bound-issue-115376.rs create mode 100644 tests/ui/async-await/spurious-static-bound-issue-115376.stderr diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 534cd1327bbe5..2f5793d5b3672 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -589,6 +589,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // result in basically the exact same error being reported to // the user. Avoid that. let mut deduplicate_errors = FxIndexSet::default(); + let mut failed_type_tests = Vec::new(); for type_test in &self.type_tests { debug!("check_type_test: {:?}", type_test); @@ -609,8 +610,40 @@ impl<'tcx> RegionInferenceContext<'tcx> { continue; } - // Type-test failed. Report the error. + // Type-test failed. Collect it so we can suppress redundant errors below. let erased_generic_kind = infcx.tcx.erase_and_anonymize_regions(type_test.generic_kind); + failed_type_tests.push((erased_generic_kind, type_test)); + } + + // An async body can produce both `G: 'static` and `G: 'a` type-test failures at + // the same span, as in `tests/ui/async-await/spurious-static-bound-issue-115376.rs`. + // Reporting the weaker bound adds a redundant diagnostic and suggests a lifetime + // bound that cannot fix the missing `G: 'static` requirement. Keep the `'static` + // error and suppress weaker failures for the same erased generic kind and span. + // This is a diagnostic heuristic, using the same erasure as deduplication below. + // + // Collect all failed `'static` bounds before reporting errors so suppression does + // not depend on the order of the type tests. Compare SCCs because a lower-bound + // region can be equivalent to `'static` without being `fr_static` itself. + let static_scc = self.constraint_sccs.scc(self.universal_regions().fr_static); + let static_bound_errors: FxIndexSet<_> = failed_type_tests + .iter() + .filter_map(|&(erased_generic_kind, type_test)| { + if self.constraint_sccs.scc(type_test.lower_bound) == static_scc { + Some((erased_generic_kind, type_test.span)) + } else { + None + } + }) + .collect(); + + // If `G: 'static` failed at this span, then same-span `G: 'a` failures are weaker. + for (erased_generic_kind, type_test) in failed_type_tests { + if self.constraint_sccs.scc(type_test.lower_bound) != static_scc + && static_bound_errors.contains(&(erased_generic_kind, type_test.span)) + { + continue; + } // Skip duplicate-ish errors. if deduplicate_errors.insert(( diff --git a/tests/ui/async-await/spurious-static-bound-issue-115376.rs b/tests/ui/async-await/spurious-static-bound-issue-115376.rs new file mode 100644 index 0000000000000..42cd388ab7eec --- /dev/null +++ b/tests/ui/async-await/spurious-static-bound-issue-115376.rs @@ -0,0 +1,8 @@ +//@ edition: 2021 + +async fn test(_: &u8) { + let _: &'static T; + //~^ ERROR the parameter type `T` may not live long enough +} + +fn main() {} diff --git a/tests/ui/async-await/spurious-static-bound-issue-115376.stderr b/tests/ui/async-await/spurious-static-bound-issue-115376.stderr new file mode 100644 index 0000000000000..6292823029de1 --- /dev/null +++ b/tests/ui/async-await/spurious-static-bound-issue-115376.stderr @@ -0,0 +1,17 @@ +error[E0310]: the parameter type `T` may not live long enough + --> $DIR/spurious-static-bound-issue-115376.rs:4:12 + | +LL | let _: &'static T; + | ^^^^^^^^^^ + | | + | the parameter type `T` must be valid for the static lifetime... + | ...so that the type `T` will meet its required lifetime bounds + | +help: consider adding an explicit lifetime bound + | +LL | async fn test(_: &u8) { + | +++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0310`. diff --git a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs index 4fdf5470feac6..0edabe009acdb 100644 --- a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs +++ b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs @@ -14,7 +14,6 @@ impl Foo { //~| ERROR the parameter type `impl for<'a> Fn(&'a usize) -> Box` may not live long enough //~| ERROR the parameter type `I` may not live long enough //~| ERROR the parameter type `I` may not live long enough - //~| ERROR the parameter type `I` may not live long enough //~| ERROR `f` does not live long enough } } diff --git a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr index df86ce79f09c7..cbeb8fba8d226 100644 --- a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr +++ b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr @@ -84,19 +84,6 @@ help: consider adding an explicit lifetime bound LL | pub fn ack(&mut self, f: impl for<'a> Fn(&'a usize) -> Box) { | +++++++++ -error[E0311]: the parameter type `I` may not live long enough - --> $DIR/unconstrained-closure-lifetime-generic.rs:10:35 - | -LL | pub fn ack(&mut self, f: impl for<'a> Fn(&'a usize) -> Box) { - | --------- the parameter type `I` must be valid for the anonymous lifetime defined here... -LL | self.bar = Box::new(|baz| Box::new(f(baz))); - | ^^^^^^^^^^^^^^^^ ...so that the type `I` will meet its required lifetime bounds - | -help: consider adding an explicit lifetime bound - | -LL | pub fn ack<'a, I: 'a>(&'a mut self, f: impl for<'a> Fn(&'a usize) -> Box) { - | +++ ++++ ++ - error[E0597]: `f` does not live long enough --> $DIR/unconstrained-closure-lifetime-generic.rs:10:44 | @@ -113,7 +100,7 @@ LL | } | = note: due to object lifetime defaults, `Box Fn(&'a usize) -> Box<(dyn Any + 'a)>>` actually means `Box<(dyn for<'a> Fn(&'a usize) -> Box<(dyn Any + 'a)> + 'static)>` -error: aborting due to 8 previous errors +error: aborting due to 7 previous errors -Some errors have detailed explanations: E0310, E0311, E0597. +Some errors have detailed explanations: E0310, E0597. For more information about an error, try `rustc --explain E0310`. From 0271b99df0a328988b4f87c8741271339551b037 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 7 Sep 2026 04:22:37 +0000 Subject: [PATCH 13/30] Prepare for merging from rust-lang/rust This updates the rust-version file to 32d94cc9be3f6e6c3fa1deaea9e0ab93c4980dba. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 386fb6f98c37f..18fea436747c7 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -71238e21fc55e73ab3aad8c9f79fed7a47a179e1 +32d94cc9be3f6e6c3fa1deaea9e0ab93c4980dba From 41b56aaeb81f3ef9caa214c033f77169b2a50d90 Mon Sep 17 00:00:00 2001 From: Redddy Date: Mon, 7 Sep 2026 17:33:12 +0900 Subject: [PATCH 14/30] Fix reference link name --- src/doc/rustc-dev-guide/src/tests/directives.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index 617ad701d129e..5a3ec15d00bf4 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -240,8 +240,8 @@ See also [Debuginfo tests](compiletest.md#debuginfo-tests) for directives for ig [remote testing]: running.md#running-tests-on-a-remote-machine [parallel frontend]: compiletest.md#parallel-frontend [compare modes]: ui.md#compare-modes -[`x86_64-gnu-debug`]: https://github.com/rust-lang/rust/blob/ab3dba92db355b8d97db915a2dca161a117e959c/src/ci/docker/host-x86_64/x86_64-gnu-debug/Dockerfile#L32 -[`aarch64-gnu-debug`]: https://github.com/rust-lang/rust/blob/20c909ff9cdd88d33768a4ddb8952927a675b0ad/src/ci/docker/host-aarch64/aarch64-gnu-debug/Dockerfile#L32 +[`test-x86_64-gnu-debug`]: https://github.com/rust-lang/rust/blob/ab3dba92db355b8d97db915a2dca161a117e959c/src/ci/docker/host-x86_64/x86_64-gnu-debug/Dockerfile#L32 +[`test-aarch64-gnu-debug`]: https://github.com/rust-lang/rust/blob/20c909ff9cdd88d33768a4ddb8952927a675b0ad/src/ci/docker/host-aarch64/aarch64-gnu-debug/Dockerfile#L32 ### Affecting how tests are built From 0c5a84b34fccb6d373a1fc89a736b3a78c5df961 Mon Sep 17 00:00:00 2001 From: Redddy Date: Mon, 7 Sep 2026 18:01:02 +0900 Subject: [PATCH 15/30] Update links for test-x86_64-gnu-debug and test-aarch64-gnu-debug --- src/doc/rustc-dev-guide/src/tests/directives.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index 5a3ec15d00bf4..22735c124fb43 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -240,8 +240,8 @@ See also [Debuginfo tests](compiletest.md#debuginfo-tests) for directives for ig [remote testing]: running.md#running-tests-on-a-remote-machine [parallel frontend]: compiletest.md#parallel-frontend [compare modes]: ui.md#compare-modes -[`test-x86_64-gnu-debug`]: https://github.com/rust-lang/rust/blob/ab3dba92db355b8d97db915a2dca161a117e959c/src/ci/docker/host-x86_64/x86_64-gnu-debug/Dockerfile#L32 -[`test-aarch64-gnu-debug`]: https://github.com/rust-lang/rust/blob/20c909ff9cdd88d33768a4ddb8952927a675b0ad/src/ci/docker/host-aarch64/aarch64-gnu-debug/Dockerfile#L32 +[`test-x86_64-gnu-debug`]: https://github.com/rust-lang/rust/blob/32d94cc9be3f6e6c3fa1deaea9e0ab93c4980dba/src/ci/docker/host-x86_64/test-x86_64-gnu-debug/Dockerfile#L32 +[`test-aarch64-gnu-debug`]: https://github.com/rust-lang/rust/blob/32d94cc9be3f6e6c3fa1deaea9e0ab93c4980dba/src/ci/docker/host-aarch64/test-aarch64-gnu-debug/Dockerfile#L32 ### Affecting how tests are built From 018b9ad740ebde4014322770101e1c0b6dde29e3 Mon Sep 17 00:00:00 2001 From: HigherOrderLogic <73709188+HigherOrderLogic@users.noreply.github.com> Date: Mon, 7 Sep 2026 19:37:51 +1000 Subject: [PATCH 16/30] std: remove exceed whitespace in docs --- library/std/src/thread/join_handle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/thread/join_handle.rs b/library/std/src/thread/join_handle.rs index 93dcc634d2dfa..955fd524e736b 100644 --- a/library/std/src/thread/join_handle.rs +++ b/library/std/src/thread/join_handle.rs @@ -104,7 +104,7 @@ impl JoinHandle { /// Otherwise, it fully waits for the thread to finish, including all destructors /// for thread-local variables that might be running after the main function of the thread. /// - /// In terms of [atomic memory orderings], the completion of the associated + /// In terms of [atomic memory orderings], the completion of the associated /// thread synchronizes with this function returning. In other words, all /// operations performed by that thread [happen /// before](https://doc.rust-lang.org/nomicon/atomics.html#data-accesses) all From d3cba0b9b57acfc80b5f054b20aeb3f99186d267 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 7 Sep 2026 19:55:35 +1000 Subject: [PATCH 17/30] Temporarily add a crashtest for instrumenting comptime functions This test demonstrates the existing crash, and will be migrated to a successful coverage test in a subsequent commit. Co-Authored-By: Rachel Barker --- tests/crashes/comptime.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/crashes/comptime.rs diff --git a/tests/crashes/comptime.rs b/tests/crashes/comptime.rs new file mode 100644 index 0000000000000..77fec9f509222 --- /dev/null +++ b/tests/crashes/comptime.rs @@ -0,0 +1,13 @@ +#![feature(rustc_attrs)] +//@ edition: 2024 +//@ compile-flags: -Cinstrument-coverage +//@ needs-profiler-runtime + +// Check that instrumenting a crate with a comptime function doesn't ICE. +// (The function itself doesn't need to be instrumented, and probably shouldn't be.) +// Regression test for . + +#[rustc_comptime] +fn comptime_fn() {} + +fn main() {} From 0027ee161223389c9490ffef6d07f1cc00a75385 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 6 Sep 2026 16:47:24 +1000 Subject: [PATCH 18/30] Make comptime functions ineligible for coverage Compile-time-only functions don't generate code, so instrumenting them for coverage is useless. This also avoids an ICE when trying to get the function's symbol name for an unused-function record, which can occur when instrumenting `core`. Co-Authored-By: Rachel Barker --- .../rustc_mir_transform/src/coverage/query.rs | 17 +++++++++++++++-- tests/coverage/comptime.cov-map | 10 ++++++++++ tests/coverage/comptime.coverage | 12 ++++++++++++ tests/{crashes => coverage}/comptime.rs | 2 -- 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 tests/coverage/comptime.cov-map create mode 100644 tests/coverage/comptime.coverage rename tests/{crashes => coverage}/comptime.rs (82%) diff --git a/compiler/rustc_mir_transform/src/coverage/query.rs b/compiler/rustc_mir_transform/src/coverage/query.rs index 6ffb85d7b90a8..a4d39f09b724d 100644 --- a/compiler/rustc_mir_transform/src/coverage/query.rs +++ b/compiler/rustc_mir_transform/src/coverage/query.rs @@ -1,5 +1,6 @@ use rustc_hir::attrs::CoverageAttrKind; -use rustc_hir::find_attr; +use rustc_hir::def::DefKind; +use rustc_hir::{self as hir, find_attr}; use rustc_index::bit_set::DenseBitSet; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir::coverage::{ @@ -30,11 +31,23 @@ fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { // expressions from coverage spans in enclosing MIR's, like we do for closures. (That might // be tricky if const expressions have no corresponding statements in the enclosing MIR. // Closures are carved out by their initial `Assign` statement.) - if !tcx.def_kind(def_id).is_fn_like() { + let def_kind = tcx.def_kind(def_id); + if !def_kind.is_fn_like() { trace!("InstrumentCoverage skipped for {def_id:?} (not an fn-like)"); return false; } + // Comptime functions can't exist at runtime, so instrumenting them is useless. + // This also avoids an ICE when getting the symbol name for an unused-function record + // (due to ). + // We check `def_kind` first to avoid any unexpected panics from merely asking for constness. + if matches!(def_kind, DefKind::Fn | DefKind::AssocFn) + && matches!(tcx.constness(def_id), hir::Constness::Const { always: true }) + { + trace!("InstrumentCoverage skipped for {def_id:?} (comptime)"); + return false; + } + if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::NAKED) { trace!("InstrumentCoverage skipped for {def_id:?} (`#[naked]`)"); return false; diff --git a/tests/coverage/comptime.cov-map b/tests/coverage/comptime.cov-map new file mode 100644 index 0000000000000..30f91da050f6f --- /dev/null +++ b/tests/coverage/comptime.cov-map @@ -0,0 +1,10 @@ +Function name: comptime::main +Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 01, 00, 0a, 01, 00, 0c, 00, 0d] +Number of files: 1 +- file 0 => $DIR/comptime.rs +Number of expressions: 0 +Number of file 0 mappings: 2 +- Code(Counter(0)) at (prev + 11, 1) to (start + 0, 10) +- Code(Counter(0)) at (prev + 0, 12) to (start + 0, 13) +Highest counter ID seen: c0 + diff --git a/tests/coverage/comptime.coverage b/tests/coverage/comptime.coverage new file mode 100644 index 0000000000000..1ff44169babb2 --- /dev/null +++ b/tests/coverage/comptime.coverage @@ -0,0 +1,12 @@ + LL| |#![feature(rustc_attrs)] + LL| |//@ edition: 2024 + LL| | + LL| |// Check that instrumenting a crate with a comptime function doesn't ICE. + LL| |// (The function itself doesn't need to be instrumented, and probably shouldn't be.) + LL| |// Regression test for . + LL| | + LL| |#[rustc_comptime] + LL| |fn comptime_fn() {} + LL| | + LL| 1|fn main() {} + diff --git a/tests/crashes/comptime.rs b/tests/coverage/comptime.rs similarity index 82% rename from tests/crashes/comptime.rs rename to tests/coverage/comptime.rs index 77fec9f509222..4891051b0076d 100644 --- a/tests/crashes/comptime.rs +++ b/tests/coverage/comptime.rs @@ -1,7 +1,5 @@ #![feature(rustc_attrs)] //@ edition: 2024 -//@ compile-flags: -Cinstrument-coverage -//@ needs-profiler-runtime // Check that instrumenting a crate with a comptime function doesn't ICE. // (The function itself doesn't need to be instrumented, and probably shouldn't be.) From a158531a5df2609b043f0505cf947483ee257ec9 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 6 Sep 2026 21:06:01 +0200 Subject: [PATCH 19/30] Add a regression test for trailing attributes in doctests --- .../rustdoc-ui/expect-item-after-attribute.rs | 26 ++++++ .../expect-item-after-attribute.stdout | 80 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 tests/rustdoc-ui/expect-item-after-attribute.rs create mode 100644 tests/rustdoc-ui/expect-item-after-attribute.stdout diff --git a/tests/rustdoc-ui/expect-item-after-attribute.rs b/tests/rustdoc-ui/expect-item-after-attribute.rs new file mode 100644 index 0000000000000..de78749141842 --- /dev/null +++ b/tests/rustdoc-ui/expect-item-after-attribute.rs @@ -0,0 +1,26 @@ +//@ compile-flags: --test --test-args=--test-threads=1 +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ failure-status: 101 + +//! ``` +//! #[should_panic] +//! ``` + +//! ``` +//! fn main() { +//! #[should_panic] +//! } +//! ``` + +//! ``` +//! fn main() { } +//! #[should_panic] +//! ``` + +//! ``` +//! let x = 0; #[should_panic] +//! ``` + +//! ``` +//! let x = 0; //! assert!(true); +//! ``` diff --git a/tests/rustdoc-ui/expect-item-after-attribute.stdout b/tests/rustdoc-ui/expect-item-after-attribute.stdout new file mode 100644 index 0000000000000..78b9eaea9f939 --- /dev/null +++ b/tests/rustdoc-ui/expect-item-after-attribute.stdout @@ -0,0 +1,80 @@ + +running 5 tests +test $DIR/expect-item-after-attribute.rs - (line 13) ... FAILED +test $DIR/expect-item-after-attribute.rs - (line 17) ... FAILED +test $DIR/expect-item-after-attribute.rs - (line 20) ... FAILED +test $DIR/expect-item-after-attribute.rs - (line 5) ... FAILED +test $DIR/expect-item-after-attribute.rs - (line 8) ... FAILED + +failures: + +---- $DIR/expect-item-after-attribute.rs - (line 13) stdout ---- +error: expected item after attributes + --> $DIR/expect-item-after-attribute.rs:15:1 + | +LL | #[should_panic] + | ^^^^^^^^^^^^^^^ expected an item after this + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/expect-item-after-attribute.rs - (line 17) stdout ---- +error: expected item, found keyword `let` + --> $DIR/expect-item-after-attribute.rs:18:1 + | +LL | let x = 0; #[should_panic] + | ^^^ + | | + | `let` cannot be used for global variables + | help: consider using `static` or `const` instead of `let` + | + = note: for a full list of items that can appear in modules, see + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/expect-item-after-attribute.rs - (line 20) stdout ---- +error: expected item, found keyword `let` + --> $DIR/expect-item-after-attribute.rs:21:1 + | +LL | let x = 0; //! assert!(true); + | ^^^ + | | + | `let` cannot be used for global variables + | help: consider using `static` or `const` instead of `let` + | + = note: for a full list of items that can appear in modules, see + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/expect-item-after-attribute.rs - (line 5) stdout ---- +error: expected item after attributes + --> $DIR/expect-item-after-attribute.rs:6:1 + | +LL | #[should_panic] + | ^^^^^^^^^^^^^^^ expected an item after this + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/expect-item-after-attribute.rs - (line 8) stdout ---- +error: expected statement after outer attribute + --> $DIR/expect-item-after-attribute.rs:10:5 + | +LL | #[should_panic] + | ^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/expect-item-after-attribute.rs - (line 13) + $DIR/expect-item-after-attribute.rs - (line 17) + $DIR/expect-item-after-attribute.rs - (line 20) + $DIR/expect-item-after-attribute.rs - (line 5) + $DIR/expect-item-after-attribute.rs - (line 8) + +test result: FAILED. 0 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + From eec967a01ce098a13666d766674de0a8c085355f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 7 Sep 2026 12:03:27 +0200 Subject: [PATCH 20/30] Also invalidate library when checking it if rustc has changed --- src/bootstrap/src/core/builder/cargo.rs | 4 ++-- src/bootstrap/src/utils/build_stamp.rs | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 288e147a26615..754f4a547bd74 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -1018,9 +1018,9 @@ impl Builder<'_> { // Avoid doing this during dry run as that usually means the relevant // compiler is not yet linked/copied properly. // - // Only clear out the directory if we're compiling std; otherwise, we + // Only clear out the directory if we're running Cargo on std; otherwise, we // should let Cargo take care of things for us (via depdep info) - if !self.config.dry_run() && mode == Mode::Std && cmd_kind == Kind::Build { + if !self.config.dry_run() && mode == Mode::Std { build_stamp::clear_if_dirty(self, &out_dir, &self.rustc(compiler)); } diff --git a/src/bootstrap/src/utils/build_stamp.rs b/src/bootstrap/src/utils/build_stamp.rs index 36a3d0772e5ad..f70c92667a5cf 100644 --- a/src/bootstrap/src/utils/build_stamp.rs +++ b/src/bootstrap/src/utils/build_stamp.rs @@ -114,7 +114,13 @@ pub fn clear_if_dirty(builder: &Builder<'_>, dir: &Path, input: &Path) -> bool { let stamp = BuildStamp::new(dir); let mut cleared = false; if mtime(stamp.path()) < mtime(input) { - builder.do_if_verbose(|| println!("Dirty - {}", dir.display())); + builder.do_if_verbose(|| { + println!( + "Removing dirty directory `{}` because `{}` changed", + dir.display(), + input.display(), + ) + }); let _ = fs::remove_dir_all(dir); cleared = true; } else if stamp.path().exists() { From 22506376bd6987f89d2518b8370eff4e0504d632 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 7 Sep 2026 21:26:48 +1000 Subject: [PATCH 21/30] Don't pass a redundant `scrutinee_span` to some MIR-build methods In all cases, the caller was passing `&self.thir[scrutinee_id].span`, which can just as easily be done by the callee. There's no need to add confusion by passing a separate span. --- .../rustc_mir_build/src/builder/expr/into.rs | 16 +++++----------- .../rustc_mir_build/src/builder/matches/mod.rs | 14 +++++--------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 13fbb3a2c0c3c..db9dbc1c3f482 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -55,14 +55,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Block { block: ast_block } => { this.ast_block(destination, block, ast_block, source_info) } - ExprKind::Match { scrutinee, ref arms, .. } => this.match_expr( - destination, - block, - scrutinee, - arms, - expr_span, - this.thir[scrutinee].span, - ), + ExprKind::Match { scrutinee, ref arms, .. } => { + this.match_expr(destination, block, scrutinee, arms, expr_span) + } ExprKind::If { cond, then, else_opt, if_then_scope } => { let then_span = this.thir[then].span; let then_source_info = this.source_info(then_span); @@ -303,9 +298,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Logic for `match`. let scrutinee_span = this.thir.exprs[scrutinee].span; - let scrutinee_place_builder = unpack!( - body_block = this.lower_scrutinee(body_block, scrutinee, scrutinee_span) - ); + let scrutinee_place_builder = + unpack!(body_block = this.lower_scrutinee(body_block, scrutinee)); let match_start_span = match_span.shrink_to_lo().to(scrutinee_span); diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index a520acda5e6c8..01505fb9ec8ae 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -339,10 +339,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { scrutinee_id: ExprId, arms: &[ArmId], span: Span, - scrutinee_span: Span, ) -> BlockAnd<()> { - let scrutinee_place = - unpack!(block = self.lower_scrutinee(block, scrutinee_id, scrutinee_span)); + let scrutinee_span = self.thir[scrutinee_id].span; + let scrutinee_place = unpack!(block = self.lower_scrutinee(block, scrutinee_id)); let match_start_span = span.shrink_to_lo().to(scrutinee_span); let patterns = arms @@ -378,11 +377,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { &mut self, mut block: BasicBlock, scrutinee_id: ExprId, - scrutinee_span: Span, ) -> BlockAnd> { let scrutinee_place_builder = unpack!(block = self.as_place_builder(block, scrutinee_id)); if let Some(scrutinee_place) = scrutinee_place_builder.try_to_place(self) { - let source_info = self.source_info(scrutinee_span); + let source_info = self.source_info(self.thir[scrutinee_id].span); self.cfg.push_place_mention(block, source_info, scrutinee_place); } @@ -612,9 +610,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } _ => { - let initializer = &self.thir[initializer_id]; - let place_builder = - unpack!(block = self.lower_scrutinee(block, initializer_id, initializer.span)); + let place_builder = unpack!(block = self.lower_scrutinee(block, initializer_id)); self.place_into_pattern(block, irrefutable_pat, place_builder, true) } } @@ -2334,7 +2330,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { declare_let_bindings: DeclareLetBindings, ) -> BlockAnd<()> { let expr_span = self.thir[expr_id].span; - let scrutinee = unpack!(block = self.lower_scrutinee(block, expr_id, expr_span)); + let scrutinee = unpack!(block = self.lower_scrutinee(block, expr_id)); let built_tree = self.lower_match_tree( block, expr_span, From 312d89f69b76091f9bb45503e8e68698a62a9c09 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 7 Sep 2026 13:52:07 +0200 Subject: [PATCH 22/30] further lengthen lines that are to short --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 83094681e9cc4..6d2e78258f85c 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -182,6 +182,15 @@ fn lengthen_lines(content: &str, limit: usize) -> String { new_content[new_n] = format!("{line} {}", next_line.trim_start()); new_content.remove(new_n + 1); skip_next = true; + } else { + const SEP: &str = ", "; + let Some((before_comma, after_comma)) = next_line.split_once(SEP) else { continue }; + if line.len() + before_comma.len() < limit - SEP.len() { + new_content[new_n] = format!("{line} {before_comma}{}", SEP.trim_end()); + new_n += 1; + new_content[new_n] = after_comma.to_owned(); + skip_next = true; + } } } new_content.join("\n") + "\n" @@ -325,7 +334,6 @@ fn should_pass() { } #[test] -#[ignore] fn split_on_comma() { let original = " Because of canonicalization of regions and From 767685d12a1326bf7fa516194af208db08d32b3c Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 7 Sep 2026 13:52:25 +0200 Subject: [PATCH 23/30] sembr src/queries/salsa.md --- src/doc/rustc-dev-guide/src/queries/salsa.md | 104 ++++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/queries/salsa.md b/src/doc/rustc-dev-guide/src/queries/salsa.md index dc7160edc22cd..5ab289b467ae2 100644 --- a/src/doc/rustc-dev-guide/src/queries/salsa.md +++ b/src/doc/rustc-dev-guide/src/queries/salsa.md @@ -2,10 +2,9 @@ This chapter is based on the explanation given by Niko Matsakis in this [video](https://www.youtube.com/watch?v=_muY4HjSqVw) about -[Salsa](https://github.com/salsa-rs/salsa). To find out more you may -want to watch [Salsa In More -Depth](https://www.youtube.com/watch?v=i_IhACacPRY), also by Niko -Matsakis. +[Salsa](https://github.com/salsa-rs/salsa). +To find out more you may want to watch [Salsa In More +Depth](https://www.youtube.com/watch?v=i_IhACacPRY), also by Niko Matsakis. > As of November 2022, although Salsa is inspired by (among > other things) rustc's query system, it is not used directly in rustc. It @@ -19,9 +18,9 @@ Matsakis. ## What is Salsa? -Salsa is a library for incremental recomputation. This means it allows reusing -computations that were already done in the past to increase the efficiency -of future computations. +Salsa is a library for incremental recomputation. +This means it allows reusing +computations that were already done in the past to increase the efficiency of future computations. The objectives of Salsa are: * Provide that functionality in an automatic way, so reusing old computations @@ -32,8 +31,9 @@ The objectives of Salsa are: Salsa's actual model is much richer, allowing many kinds of inputs and many different outputs. For example, integrating Salsa with an IDE could mean that the inputs could be manifests (`Cargo.toml`, `rust-toolchain.toml`), entire -source files (`foo.rs`), snippets and so on. The outputs of such an integration -could range from a binary executable, to lints, types (for example, if a user +source files (`foo.rs`), snippets and so on. +The outputs of such an integration could range from a binary executable, +to lints, types (for example, if a user selects a certain variable and wishes to see its type), completions, etc. ## How does it work? @@ -45,26 +45,27 @@ Then Salsa has to also identify intermediate, "derived" values, which are something that the library produces, but, for each derived value there's a "pure" function that computes the derived value. -For example, there might be a function `ast(x: Path) -> AST`. The produced -Abstract Syntax Tree (`AST`) isn't a final value, it's an intermediate value +For example, there might be a function `ast(x: Path) -> AST`. +The produced Abstract Syntax Tree (`AST`) isn't a final value, it's an intermediate value that the library would use for the computation. This means that when you try to compute with the library, Salsa is going to compute various derived values, and eventually read the input and produce the result for the asked computation. -In the course of computing, Salsa tracks which inputs were accessed and which -values are derived. This information is used to determine what's going to +In the course of computing, Salsa tracks which inputs were accessed and which values are derived. +This information is used to determine what's going to happen when the inputs change: are the derived values still valid? This doesn't necessarily mean that each computation downstream from the input -is going to be checked, which could be costly. Salsa only needs to check each -downstream computation until it finds one that isn't changed. At that point, it -won't check other derived computations since they wouldn't need to change. +is going to be checked, which could be costly. +Salsa only needs to check each downstream computation until it finds one that isn't changed. +At that point, it won't check other derived computations since they wouldn't need to change. -It's helpful to think about this as a graph with nodes. Each derived value -has a dependency on other values, which could themselves be either base or -derived. Base values don't have a dependency. +It's helpful to think about this as a graph with nodes. +Each derived value has a dependency on other values, which could themselves be either base or +derived. +Base values don't have a dependency. ```ignore I <- A <- C ... @@ -72,49 +73,52 @@ I <- A <- C ... J <- B <--+ ``` -When an input `I` changes, the derived value `A` could change. The derived -value `B`, which does not depend on `I`, `A`, or any value derived from `A` or -`I`, is not subject to change. Therefore, Salsa can reuse the computation done -for `B` in the past, without having to compute it again. - -The computation could also terminate early. Keeping the same graph as before, -say that input `I` has changed in some way (and input `J` hasn't), but when -computing `A` again, it's found that `A` hasn't changed from the previous -computation. This leads to an "early termination", because there's no need to -check if `C` needs to change, since both `C` direct inputs, `A` and `B`, +When an input `I` changes, the derived value `A` could change. +The derived value `B`, which does not depend on `I`, `A`, or any value derived from `A` or +`I`, is not subject to change. + Therefore, Salsa can reuse the computation done for `B` in the past, +without having to compute it again. + +The computation could also terminate early. +Keeping the same graph as before, +say that input `I` has changed in some way (and input `J` hasn't), but when computing `A` again, +it's found that `A` hasn't changed from the previous +computation. +This leads to an "early termination", because there's no need to check if `C` needs to change, +since both `C` direct inputs, `A` and `B`, haven't changed. ## Key Salsa concepts ### Query -A query is some value that Salsa can access in the course of computation. Each -query can have a number of keys (from 0 to many), and all queries have a -result, akin to functions. `0-key` queries are called "input" queries. +A query is some value that Salsa can access in the course of computation. + Each query can have a number of keys (from 0 to many), and all queries have a +result, akin to functions. + `0-key` queries are called "input" queries. ### Database The database is basically the context for the entire computation, it's meant to store Salsa's internal state, all intermediate values for each query, and -anything else that the computation might need. The database must know all the -queries the library is going to do before it can be built, but they don't need +anything else that the computation might need. +The database must know all the queries the library is going to do before it can be built, +but they don't need to be specified in the same place. -After the database is formed, it can be accessed with queries that are very -similar to functions. Since each query's result is stored in the database, when -a query is invoked `N`-times, it will return `N`-**cloned** results, without having -to recompute the query (unless the input has changed in such a way that it -warrants recomputation). +After the database is formed, it can be accessed with queries that are very similar to functions. +Since each query's result is stored in the database, when a query is invoked `N`-times, +it will return `N`-**cloned** results, without having +to recompute the query (unless the input has changed in such a way that it warrants recomputation). For each input query (`0-key`), a "set" method is generated, allowing the user to -change the output of such query, and trigger previous memoized values to be -potentially invalidated. +change the output of such query, and trigger previous memoized values to be potentially invalidated. ### Query Groups A query group is a set of queries which have been defined together as a unit. -The database is formed by combining query groups. Query groups are akin to -"Salsa modules". +The database is formed by combining query groups. +Query groups are akin to "Salsa modules". A set of queries in a query group are just a set of methods in a trait. @@ -146,8 +150,7 @@ pub trait Inputs { ``` To create a **derived** query group, one must specify which other query groups -this one depends on by specifying them as supertraits, as seen in the following -example: +this one depends on by specifying them as supertraits, as seen in the following example: ```rust,ignore /// This query group is going to contain queries that depend on derived values. @@ -162,9 +165,9 @@ pub trait Parser: Inputs { } ``` -When creating a derived query the implementation of said query must be defined -outside the trait. The definition must take a database parameter as an `impl -Trait` (or `dyn Trait`), where trait is the query group that the definition +When creating a derived query the implementation of said query must be defined outside the trait. + The definition must take a database parameter as an `impl Trait` (or `dyn Trait`), +where trait is the query group that the definition belongs to, in addition to the other keys. ```rust,ignore @@ -187,8 +190,9 @@ Eventually, after all the query groups have been defined, the database can be created by declaring a `struct`. To specify which query groups are going to be part of the database an `attribute` -(`#[salsa::database(...)]`) must be added. The argument of said `attribute` is a -list of `identifiers`, specifying the query groups **storages**. +(`#[salsa::database(...)]`) must be added. +The argument of said `attribute` is a list of `identifiers`, +specifying the query groups **storages**. ```rust,ignore ///This attribute specifies which query groups are going to be in the database From e9c8a6d0cbfae5d2ed42ad9275b4bcb30285ef4c Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 7 Sep 2026 14:06:41 +0200 Subject: [PATCH 24/30] Add more regression tests --- .../rustdoc-ui/expect-item-after-attribute.rs | 10 +++++++ .../expect-item-after-attribute.stdout | 28 +++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tests/rustdoc-ui/expect-item-after-attribute.rs b/tests/rustdoc-ui/expect-item-after-attribute.rs index de78749141842..c53395caa8efc 100644 --- a/tests/rustdoc-ui/expect-item-after-attribute.rs +++ b/tests/rustdoc-ui/expect-item-after-attribute.rs @@ -24,3 +24,13 @@ //! ``` //! let x = 0; //! assert!(true); //! ``` + +/// ``` +/// /// doc comment +/// ``` +struct Test; + +/// ``` +/// #[cfg(true)] { +/// } /// ``` +pub fn wtf() {} diff --git a/tests/rustdoc-ui/expect-item-after-attribute.stdout b/tests/rustdoc-ui/expect-item-after-attribute.stdout index 78b9eaea9f939..f853d6facf1a7 100644 --- a/tests/rustdoc-ui/expect-item-after-attribute.stdout +++ b/tests/rustdoc-ui/expect-item-after-attribute.stdout @@ -1,10 +1,12 @@ -running 5 tests +running 7 tests test $DIR/expect-item-after-attribute.rs - (line 13) ... FAILED test $DIR/expect-item-after-attribute.rs - (line 17) ... FAILED test $DIR/expect-item-after-attribute.rs - (line 20) ... FAILED test $DIR/expect-item-after-attribute.rs - (line 5) ... FAILED test $DIR/expect-item-after-attribute.rs - (line 8) ... FAILED +test $DIR/expect-item-after-attribute.rs - Test (line 28) ... FAILED +test $DIR/expect-item-after-attribute.rs - wtf (line 33) ... FAILED failures: @@ -67,6 +69,26 @@ LL | #[should_panic] error: aborting due to 1 previous error +Couldn't compile the test. +---- $DIR/expect-item-after-attribute.rs - Test (line 28) stdout ---- +error: expected item after doc comment + --> $DIR/expect-item-after-attribute.rs:29:1 + | +LL | /// doc comment + | ^^^^^^^^^^^^^^^ this doc comment doesn't document anything + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/expect-item-after-attribute.rs - wtf (line 33) stdout ---- +error: expected item after attributes + --> $DIR/expect-item-after-attribute.rs:34:1 + | +LL | #[cfg(true)] { + | ^^^^^^^^^^^^ expected an item after this + +error: aborting due to 1 previous error + Couldn't compile the test. failures: @@ -75,6 +97,8 @@ failures: $DIR/expect-item-after-attribute.rs - (line 20) $DIR/expect-item-after-attribute.rs - (line 5) $DIR/expect-item-after-attribute.rs - (line 8) + $DIR/expect-item-after-attribute.rs - Test (line 28) + $DIR/expect-item-after-attribute.rs - wtf (line 33) -test result: FAILED. 0 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME +test result: FAILED. 0 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME From 18dbb539e5d2362c2d3631f5034eac9ed2b93414 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 7 Sep 2026 14:27:56 +0200 Subject: [PATCH 25/30] improve queries/salsa.md --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 16 +++++++++++- src/doc/rustc-dev-guide/src/queries/salsa.md | 26 ++++++++++---------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 6d2e78258f85c..82c096c4bbd11 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -334,7 +334,21 @@ fn should_pass() { } #[test] -fn split_on_comma() { +#[ignore] +fn split_on_comma_of_current_line() { + let original = " +Each derived value has a dependency on other values, which could themselves be either base or +derived. +"; + let expected = " +Each derived value has a dependency on other values, +which could themselves be either base or derived. +"; + assert_eq!(expected, lengthen_lines(original, 100)) +} + +#[test] +fn split_on_comma_of_next_line() { let original = " Because of canonicalization of regions and inference variables, encountering a cycle doesn't mean that we would get an infinite proof tree. diff --git a/src/doc/rustc-dev-guide/src/queries/salsa.md b/src/doc/rustc-dev-guide/src/queries/salsa.md index 5ab289b467ae2..fcd67de72b206 100644 --- a/src/doc/rustc-dev-guide/src/queries/salsa.md +++ b/src/doc/rustc-dev-guide/src/queries/salsa.md @@ -19,8 +19,8 @@ Depth](https://www.youtube.com/watch?v=i_IhACacPRY), also by Niko Matsakis. ## What is Salsa? Salsa is a library for incremental recomputation. -This means it allows reusing -computations that were already done in the past to increase the efficiency of future computations. +This means it allows reusing computations that were already done in the past +to increase the efficiency of future computations. The objectives of Salsa are: * Provide that functionality in an automatic way, so reusing old computations @@ -30,11 +30,11 @@ The objectives of Salsa are: Salsa's actual model is much richer, allowing many kinds of inputs and many different outputs. For example, integrating Salsa with an IDE could mean that -the inputs could be manifests (`Cargo.toml`, `rust-toolchain.toml`), entire -source files (`foo.rs`), snippets and so on. +the inputs could be manifests (`Cargo.toml`, `rust-toolchain.toml`), +entire source files (`foo.rs`), snippets, and so on. The outputs of such an integration could range from a binary executable, -to lints, types (for example, if a user -selects a certain variable and wishes to see its type), completions, etc. +to lints, types (for example, if a user selects a certain variable and wishes to see its type), +completions, etc. ## How does it work? @@ -46,8 +46,8 @@ something that the library produces, but, for each derived value there's a "pure" function that computes the derived value. For example, there might be a function `ast(x: Path) -> AST`. -The produced Abstract Syntax Tree (`AST`) isn't a final value, it's an intermediate value -that the library would use for the computation. +The produced Abstract Syntax Tree (`AST`) isn't a final value; +it's an intermediate value that the library would use for the computation. This means that when you try to compute with the library, Salsa is going to compute various derived values, and eventually read the input and produce the @@ -76,7 +76,7 @@ J <- B <--+ When an input `I` changes, the derived value `A` could change. The derived value `B`, which does not depend on `I`, `A`, or any value derived from `A` or `I`, is not subject to change. - Therefore, Salsa can reuse the computation done for `B` in the past, +Therefore, Salsa can reuse the computation done for `B` in the past, without having to compute it again. The computation could also terminate early. @@ -93,9 +93,9 @@ haven't changed. ### Query A query is some value that Salsa can access in the course of computation. - Each query can have a number of keys (from 0 to many), and all queries have a +Each query can have a number of keys (from 0 to many), and all queries have a result, akin to functions. - `0-key` queries are called "input" queries. +`0-key` queries are called "input" queries. ### Database @@ -165,8 +165,8 @@ pub trait Parser: Inputs { } ``` -When creating a derived query the implementation of said query must be defined outside the trait. - The definition must take a database parameter as an `impl Trait` (or `dyn Trait`), +When creating a derived query, the implementation of said query must be defined outside the trait. +The definition must take a database parameter as an `impl Trait` (or `dyn Trait`), where trait is the query group that the definition belongs to, in addition to the other keys. From fa088e763df4ad19b120ac320c5f91b1d91eb3db Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 7 Sep 2026 14:33:12 +0200 Subject: [PATCH 26/30] sembr src/queries/query-evaluation-model-in-detail.md --- .../query-evaluation-model-in-detail.md | 101 +++++++++--------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md b/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md index c1a4373f7dac6..6461208fe2eaa 100644 --- a/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md +++ b/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md @@ -1,8 +1,8 @@ # The Query Evaluation Model in detail This chapter provides a deeper dive into the abstract model queries are built on. -It does not go into implementation details but tries to explain -the underlying logic. The examples here, therefore, have been stripped down and +It does not go into implementation details but tries to explain the underlying logic. +The examples here, therefore, have been stripped down and simplified and don't directly reflect the compilers internal APIs. ## What is a query? @@ -12,9 +12,9 @@ and queries are the way of asking the compiler questions about it, i.e. we "query" the compiler's "database" for facts. However, there's something special to this compiler database: It starts out empty -and is filled on-demand when queries are executed. Consequently, a query must -know how to compute its result if the database does not contain it yet. For -doing so, it can access other queries and certain input values that the database +and is filled on-demand when queries are executed. +Consequently, a query must know how to compute its result if the database does not contain it yet. +For doing so, it can access other queries and certain input values that the database is pre-filled with on creation. A query thus consists of the following things: @@ -26,14 +26,12 @@ A query thus consists of the following things: computed if it isn't already present in the database. As an example, the name of the `type_of` query is `type_of`, its query key is a -`DefId` identifying the item we want to know the type of, the result type is -`Ty<'tcx>`, and the provider is a function that, given the query key and access -to the rest of the database, can compute the type of the item identified by the -key. +`DefId` identifying the item we want to know the type of, the result type is `Ty<'tcx>`, +and the provider is a function that, given the query key and access +to the rest of the database, can compute the type of the item identified by the key. -So in some sense a query is just a function that maps the query key to the -corresponding result. However, we have to apply some restrictions in order for -this to be sound: +So in some sense a query is just a function that maps the query key to the corresponding result. +However, we have to apply some restrictions in order for this to be sound: - The key and result must be immutable values. - The provider function must be a pure function in the sense that for the same @@ -41,9 +39,11 @@ this to be sound: - The only parameters a provider function takes are the key and a reference to the "query context" (which provides access to the rest of the "database"). -The database is built up lazily by invoking queries. The query providers will -invoke other queries, for which the result is either already cached or computed -by calling another query provider. These query provider invocations +The database is built up lazily by invoking queries. +The query providers will invoke other queries, +for which the result is either already cached or computed +by calling another query provider. +These query provider invocations conceptually form a directed acyclic graph (DAG) at the leaves of which are input values that are already known when the query context is created. @@ -56,12 +56,12 @@ will cache the result in an internal table and, when the query is invoked with the same query key again, will return the result from the cache instead of running the provider again. -This caching is crucial for making the query engine efficient. Without -memoization the system would still be sound (that is, it would yield the same +This caching is crucial for making the query engine efficient. +Without memoization the system would still be sound (that is, it would yield the same results) but the same computations would be done over and over again. -Memoization is one of the main reasons why query providers have to be pure -functions. If calling a provider function could yield different results for +Memoization is one of the main reasons why query providers have to be pure functions. +If calling a provider function could yield different results for each invocation (because it accesses some global mutable state) then we could not memoize the result. @@ -69,8 +69,9 @@ not memoize the result. ## Input data -When the query context is created, it is still empty: No queries have been -executed, no results are cached. But the context already provides access to +When the query context is created, it is still empty: No queries have been executed, +no results are cached. +But the context already provides access to "input" data, i.e. pieces of immutable data that were computed before the context was created and that queries can access to do their computations. @@ -85,18 +86,19 @@ result from (remember, query providers only have access to other queries and the context but not any other outside state or information). For a query provider, input data and results of other queries look exactly the -same: It just tells the context "give me the value of X". Because input data -is immutable, the provider can rely on it being the same across +same: It just tells the context "give me the value of X". +Because input data is immutable, the provider can rely on it being the same across different query invocations, just as is the case for query results. ## An example execution trace of some queries -How does this DAG of query invocations come into existence? At some point -the compiler driver will create the, as yet empty, query context. It will then, -from outside of the query system, invoke the queries it needs to perform its -task. This looks something like the following: +How does this DAG of query invocations come into existence? +At some point the compiler driver will create the, as yet empty, query context. +It will then, +from outside of the query system, invoke the queries it needs to perform its task. +This looks something like the following: ```rust,ignore fn compile_crate() { @@ -124,9 +126,8 @@ fn type_check_crate_provider(tcx, _key: ()) { ``` We see that the `type_check_crate` query accesses input data -(`tcx.hir_map.list_of_items()`) and invokes other queries -(`type_check_item`). The `type_check_item` -invocations will themselves access input data and/or invoke other queries, +(`tcx.hir_map.list_of_items()`) and invokes other queries (`type_check_item`). +The `type_check_item` invocations will themselves access input data and/or invoke other queries, so that in the end the DAG of query invocations will be built up backwards from the node that was initially executed: @@ -146,8 +147,8 @@ from the node that was initially executed: ``` We also see that often a query result can be read from the cache: -`type_of(bar)` was computed for `type_check_item(foo)` so when -`type_check_item(bar)` needs it, it is already in the cache. +`type_of(bar)` was computed for `type_check_item(foo)` so when `type_check_item(bar)` needs it, +it is already in the cache. Query results stay cached in the query context as long as the context lives. So if the compiler driver invoked another query later on, the above graph @@ -157,8 +158,8 @@ would still exist and already executed queries would not have to be re-done. ## Cycles -Earlier we stated that query invocations form a DAG. However, it would be easy -to form a cyclic graph by, for example, having a query provider like the +Earlier we stated that query invocations form a DAG. +However, it would be easy to form a cyclic graph by, for example, having a query provider like the following: ```rust,ignore @@ -169,24 +170,24 @@ fn cyclic_query_provider(tcx, key) -> u32 { ``` Since query providers are regular functions, this would behave much as expected: -Evaluation would get stuck in an infinite recursion. A query like this would not -be very useful either. However, sometimes certain kinds of invalid user input -can result in queries being called in a cyclic way. The query engine includes -a check for cyclic invocations of queries with the same input arguments. +Evaluation would get stuck in an infinite recursion. +A query like this would not be very useful either. +However, sometimes certain kinds of invalid user input +can result in queries being called in a cyclic way. +The query engine includes a check for cyclic invocations of queries with the same input arguments. And, because cycles are an irrecoverable error, will abort execution with a "cycle error" message that tries to be human readable. At some point the compiler had a notion of "cycle recovery", that is, one could -"try" to execute a query and if it ended up causing a cycle, proceed in some -other fashion. However, this was later removed because it is not entirely -clear what the theoretical consequences of this are, especially regarding -incremental compilation. +"try" to execute a query and if it ended up causing a cycle, proceed in some other fashion. +However, this was later removed because it is not entirely +clear what the theoretical consequences of this are, especially regarding incremental compilation. ## "Steal" Queries -Some queries have their result wrapped in a `Steal` struct. These queries -behave exactly the same as regular with one exception: Their result is expected +Some queries have their result wrapped in a `Steal` struct. +These queries behave exactly the same as regular with one exception: Their result is expected to be "stolen" out of the cache at some point, meaning some other part of the program is taking ownership of it and the result cannot be accessed anymore. @@ -194,18 +195,18 @@ This stealing mechanism exists purely as a performance optimization because some result values are too costly to clone (e.g. the MIR of a function). It seems like result stealing would violate the condition that query results must be immutable (after all we are moving the result value out of the cache) but it is -OK as long as the mutation is not observable. This is achieved by two things: +OK as long as the mutation is not observable. +This is achieved by two things: - Before a result is stolen, we make sure to eagerly run all queries that - might ever need to read that result. This has to be done manually by calling - those queries. + might ever need to read that result. + This has to be done manually by calling those queries. - Whenever a query tries to access a stolen result, we make an ICE (Internal Compiler Error) so that such a condition cannot go unnoticed. This is not an ideal setup because of the manual intervention needed, so it -should be used sparingly and only when it is well known which queries might -access a given result. In practice, however, stealing has not turned out to be -much of a maintenance burden. +should be used sparingly and only when it is well known which queries might access a given result. +In practice, however, stealing has not turned out to be much of a maintenance burden. To summarize: "Steal queries" break some of the rules in a controlled way. There are checks in place that make sure that nothing can go silently wrong. From f4fa95009724af4ea4b9ef8dec59c4ec9508e384 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 7 Sep 2026 14:46:26 +0200 Subject: [PATCH 27/30] improve queries/query-evaluation-model-in-detail.md --- .../queries/query-evaluation-model-in-detail.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md b/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md index 6461208fe2eaa..443bcea4ee323 100644 --- a/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md +++ b/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md @@ -2,8 +2,8 @@ This chapter provides a deeper dive into the abstract model queries are built on. It does not go into implementation details but tries to explain the underlying logic. -The examples here, therefore, have been stripped down and -simplified and don't directly reflect the compilers internal APIs. +The examples here, therefore, have been stripped down and simplified, +and don't directly reflect compiler internal APIs. ## What is a query? @@ -57,7 +57,7 @@ the same query key again, will return the result from the cache instead of running the provider again. This caching is crucial for making the query engine efficient. -Without memoization the system would still be sound (that is, it would yield the same +Without memoization, the system would still be sound (that is, it would yield the same results) but the same computations would be done over and over again. Memoization is one of the main reasons why query providers have to be pure functions. @@ -71,8 +71,8 @@ not memoize the result. When the query context is created, it is still empty: No queries have been executed, no results are cached. -But the context already provides access to -"input" data, i.e. pieces of immutable data that were computed before the +But the context already provides access to "input" data, +i.e. pieces of immutable data that were computed before the context was created and that queries can access to do their computations. As of January 2021, this input data consists mainly of @@ -175,11 +175,12 @@ A query like this would not be very useful either. However, sometimes certain kinds of invalid user input can result in queries being called in a cyclic way. The query engine includes a check for cyclic invocations of queries with the same input arguments. -And, because cycles are an irrecoverable error, will abort execution with a +And, because cycles are an irrecoverable error, will abort execution with a "cycle error" message that tries to be human readable. -At some point the compiler had a notion of "cycle recovery", that is, one could -"try" to execute a query and if it ended up causing a cycle, proceed in some other fashion. +At some point the compiler had a notion of "cycle recovery". +That is, one could "try" to execute a query, +and if it ended up causing a cycle, proceed in some other fashion. However, this was later removed because it is not entirely clear what the theoretical consequences of this are, especially regarding incremental compilation. From 1ec54a77b65eebc6bf5ab12feea2ecc5fb013719 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 7 Sep 2026 14:47:14 +0200 Subject: [PATCH 28/30] sembr src/queries/incremental-compilation.md --- .../src/queries/incremental-compilation.md | 124 +++++++++--------- 1 file changed, 59 insertions(+), 65 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md b/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md index 731ff3287d9fe..ebddbe9454635 100644 --- a/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md +++ b/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md @@ -1,21 +1,20 @@ # Incremental compilation The incremental compilation scheme is, in essence, a surprisingly -simple extension to the overall query system. We'll start by describing +simple extension to the overall query system. +We'll start by describing a slightly simplified variant of the real thing – the "basic algorithm" – and then describe some possible improvements. ## The basic algorithm -The basic algorithm is -called the **red-green** algorithm[^salsa]. The high-level idea is -that, after each run of the compiler, we will save the results of all +The basic algorithm is called the **red-green** algorithm[^salsa]. +The high-level idea is that, after each run of the compiler, we will save the results of all the queries that we do, as well as the **query DAG**. The -**query DAG** is a [DAG] that indexes which queries executed which -other queries. So, for example, there would be an [edge] from a query Q1 +**query DAG** is a [DAG] that indexes which queries executed which other queries. +So, for example, there would be an [edge] from a query Q1 to another query Q2 if computing Q1 required computing Q2 (note that -because queries cannot depend on themselves, this results in a DAG and -not a general graph). +because queries cannot depend on themselves, this results in a DAG and not a general graph). [DAG]: https://en.wikipedia.org/wiki/Directed_acyclic_graph @@ -34,8 +33,8 @@ not a general graph). > but also for a specific instance of that query with given arguments. On the next run of the compiler, then, we can sometimes reuse these -query results to avoid re-executing a query. We do this by assigning -every query a **color**: +query results to avoid re-executing a query. +We do this by assigning every query a **color**: - If a query is colored **red**, that means that its result during this compilation has **changed** from the previous compilation. @@ -48,62 +47,57 @@ There are two key insights here: query Q **must** result in the same value as last time and hence need not be re-executed (or else the compiler is not deterministic). - Second, even if some inputs to a query changes, it may be that it - **still** produces the same result as the previous compilation. In - particular, the query may only use part of its input. + **still** produces the same result as the previous compilation. + In particular, the query may only use part of its input. - Therefore, after executing a query, we always check whether it - produced the same result as the previous time. **If it did,** we - can still mark the query as green, and hence avoid re-executing + produced the same result as the previous time. + **If it did,** we can still mark the query as green, and hence avoid re-executing dependent queries. ### The try-mark-green algorithm -At the core of incremental compilation is an algorithm called -"try-mark-green". It has the job of determining the color of a given -query Q (which must not have yet been executed). In cases where Q has -red inputs, determining Q's color may involve re-executing Q so that +At the core of incremental compilation is an algorithm called "try-mark-green". +It has the job of determining the color of a given query Q (which must not have yet been executed). +In cases where Q has red inputs, determining Q's color may involve re-executing Q so that we can compare its output, but if all of Q's inputs are green, then we -can conclude that Q must be green without re-executing it or inspecting -its value at all. In the compiler, this allows us to avoid -deserializing the result from disk when we don't need it, and in fact -enables us to sometimes skip *serializing* the result as well -(see the refinements section below). +can conclude that Q must be green without re-executing it or inspecting its value at all. +In the compiler, this allows us to avoid deserializing the result from disk when we don't need it, +and in fact +enables us to sometimes skip *serializing* the result as well (see the refinements section below). Try-mark-green works as follows: - First check if the query Q was executed during the previous compilation. - - If not, we can just re-execute the query as normal, and assign it the - color of red. + - If not, we can just re-execute the query as normal, and assign it the color of red. - If yes, then load the 'dependent queries' of Q. -- If there is a saved result, then we load the `reads(Q)` vector from the - query DAG. The "reads" is the set of queries that Q executed during - its execution. - - For each query R in `reads(Q)`, we recursively demand the color - of R using try-mark-green. +- If there is a saved result, then we load the `reads(Q)` vector from the query DAG. + The "reads" is the set of queries that Q executed during its execution. + - For each query R in `reads(Q)`, we recursively demand the color of R using try-mark-green. - Note: it is important that we visit each node in `reads(Q)` in same order - as they occurred in the original compilation. See [the section on the - query DAG below](#dag). - - If **any** of the nodes in `reads(Q)` wind up colored **red**, then Q is - dirty. + as they occurred in the original compilation. + See [the section on the query DAG below](#dag). + - If **any** of the nodes in `reads(Q)` wind up colored **red**, then Q is dirty. - We re-execute Q and compare the hash of its result to the hash of the result from the previous compilation. - If the hash has not changed, we can mark Q as **green** and return. - - Otherwise, **all** of the nodes in `reads(Q)` must be **green**. In that - case, we can color Q as **green** and return. + - Otherwise, **all** of the nodes in `reads(Q)` must be **green**. In that case, +we can color Q as **green** and return. ### The query DAG -The query DAG code is stored in -[`compiler/rustc_middle/src/dep_graph`][dep_graph]. Construction of the DAG is done -by instrumenting the query execution. +The query DAG code is stored in [`compiler/rustc_middle/src/dep_graph`][dep_graph]. +Construction of the DAG is done by instrumenting the query execution. -One key point is that the query DAG also tracks ordering; that is, for -each query Q, we not only track the queries that Q reads, we track the -**order** in which they were read. This allows try-mark-green to walk -those queries back in the same order. This is important because once a -subquery comes back as red, we can no longer be sure that Q will continue -along the same path as before. That is, imagine a query like this: +One key point is that the query DAG also tracks ordering; that is, for each query Q, +we not only track the queries that Q reads, we track the +**order** in which they were read. + This allows try-mark-green to walk those queries back in the same order. +This is important because once a subquery comes back as red, +we can no longer be sure that Q will continue +along the same path as before. +That is, imagine a query like this: ```rust,ignore fn main_query(tcx) { @@ -115,15 +109,15 @@ fn main_query(tcx) { } ``` -Now imagine that in the first compilation, `main_query` starts by -executing `subquery1`, and this returns true. In that case, the next -query `main_query` executes will be `subquery2`, and `subquery3` will +Now imagine that in the first compilation, `main_query` starts by executing `subquery1`, +and this returns true. +In that case, the next query `main_query` executes will be `subquery2`, and `subquery3` will not be executed at all. But now imagine that in the **next** compilation, the input has -changed such that `subquery1` returns **false**. In this case, `subquery2` -would never execute. If try-mark-green were to visit `reads(main_query)` out -of order, however, it might visit `subquery2` before `subquery1`, and hence +changed such that `subquery1` returns **false**. In this case, `subquery2` would never execute. +If try-mark-green were to visit `reads(main_query)` out of order, +however, it might visit `subquery2` before `subquery1`, and hence execute it. This can lead to ICEs and other problems in the compiler. @@ -132,28 +126,28 @@ This can lead to ICEs and other problems in the compiler. ## Improvements to the basic algorithm In the description of the basic algorithm, we said that at the end of -compilation we would save the results of all the queries that were -performed. In practice, this can be quite wasteful – many of those -results are very cheap to recompute, and serializing and deserializing -them is not a particular win. In practice, what we would do is to save -**the hashes** of all the subqueries that we performed. Then, in select cases, +compilation we would save the results of all the queries that were performed. + In practice, this can be quite wasteful – many of those results are very cheap to recompute, +and serializing and deserializing +them is not a particular win. +In practice, what we would do is to save **the hashes** of all the subqueries that we performed. +Then, in select cases, we **also** save the results. -This is why the incremental algorithm separates computing the -**color** of a node, which often does not require its value, from -computing the **result** of a node. Computing the result is done via a simple -algorithm like so: +This is why the incremental algorithm separates computing the **color** of a node, +which often does not require its value, from +computing the **result** of a node. +Computing the result is done via a simple algorithm like so: -- Check if a saved result for Q is available. If so, compute the color of Q. +- Check if a saved result for Q is available. + If so, compute the color of Q. If Q is green, deserialize and return the saved result. - Otherwise, execute Q. - - We can then compare the hash of the result and color Q as green if - it did not change. + - We can then compare the hash of the result and color Q as green if it did not change. ## Resources The initial design document can be found [here][initial-design], which expands -on the memoization details, provides more high-level overview and motivation -for this system. +on the memoization details, provides more high-level overview and motivation for this system. # Footnotes From 202362f973e56dfec7f83750904ef1372fc96d8d Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 7 Sep 2026 15:19:53 +0200 Subject: [PATCH 29/30] improve queries/incremental-compilation.md --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 8 +++++ .../src/queries/incremental-compilation.md | 34 +++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 82c096c4bbd11..92143aaa1fb87 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -359,3 +359,11 @@ encountering a cycle doesn't mean that we would get an infinite proof tree. "; assert_eq!(expected, lengthen_lines(original, 100)) } + +#[test] +#[ignore] +fn should_split() { + let original = "the queries that we do, as well as the **query DAG**. The"; + let expected = "the queries that we do, as well as the **query DAG**.\nThe\n"; + assert_eq!(expected, comply(original)); +} diff --git a/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md b/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md index ebddbe9454635..079a2b2e00a7f 100644 --- a/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md +++ b/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md @@ -19,17 +19,17 @@ because queries cannot depend on themselves, this results in a DAG and not a gen [DAG]: https://en.wikipedia.org/wiki/Directed_acyclic_graph > **NOTE**: You might think of a query as simply the definition of a query. -> A thing that you can invoke, a bit like a function, +> A thing that you can invoke, a bit like a function, > and which either returns a cached result or actually executes the code. -> +> > If that's the way you think about queries, -> it's good to know that in the following text, queries will be said to have colours. -> Keep in mind though, that here the word query also refers to a certain invocation of -> the query for a certain input. As you will read later, queries are fingerprinted based -> on their arguments. The result of a query might change when we give it one argument +> it's good to know that in the following text, queries will be said to have colours. +> Keep in mind though, that here the word query also refers to a certain invocation of +> the query for a certain input. As you will read later, queries are fingerprinted based +> on their arguments. The result of a query might change when we give it one argument > and be coloured red, while it stays the same for another argument and is thus green. -> -> In short, the word query is here not just used to mean the definition of a query, +> +> In short, the word query is here not just used to mean the definition of a query, > but also for a specific instance of that query with given arguments. On the next run of the compiler, then, we can sometimes reuse these @@ -80,8 +80,8 @@ Try-mark-green works as follows: - We re-execute Q and compare the hash of its result to the hash of the result from the previous compilation. - If the hash has not changed, we can mark Q as **green** and return. - - Otherwise, **all** of the nodes in `reads(Q)` must be **green**. In that case, -we can color Q as **green** and return. + - Otherwise, **all** of the nodes in `reads(Q)` must be **green**. + In that case, we can color Q as **green** and return. @@ -91,9 +91,9 @@ The query DAG code is stored in [`compiler/rustc_middle/src/dep_graph`][dep_grap Construction of the DAG is done by instrumenting the query execution. One key point is that the query DAG also tracks ordering; that is, for each query Q, -we not only track the queries that Q reads, we track the -**order** in which they were read. - This allows try-mark-green to walk those queries back in the same order. +we not only track the queries that Q reads; +we also track the **order** in which they were read. +This allows try-mark-green to walk those queries back in the same order. This is important because once a subquery comes back as red, we can no longer be sure that Q will continue along the same path as before. @@ -127,12 +127,10 @@ This can lead to ICEs and other problems in the compiler. In the description of the basic algorithm, we said that at the end of compilation we would save the results of all the queries that were performed. - In practice, this can be quite wasteful – many of those results are very cheap to recompute, -and serializing and deserializing -them is not a particular win. +In practice, this can be quite wasteful – many of those results are very cheap to recompute, +and serializing and deserializing them is not a particular win. In practice, what we would do is to save **the hashes** of all the subqueries that we performed. -Then, in select cases, -we **also** save the results. +Then, in select cases, we **also** save the results. This is why the incremental algorithm separates computing the **color** of a node, which often does not require its value, from From a4c14451a9c1e134bcdbc97e2a255739c20df6e8 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 7 Sep 2026 15:55:12 +0200 Subject: [PATCH 30/30] add next-solver fixmes --- compiler/rustc_type_ir/src/solve/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index d1a24e0054115..88d2184ed95b2 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -577,10 +577,12 @@ pub enum BuiltinImplSource { /// unless more specific information is necessary. Misc, /// A built-in impl for trait objects. The index is only used in winnowing. + // FIXME(-Znext-solver=no): The new solver does not need this index, remove! Object(usize), /// A built-in implementation of `Upcast` for trait objects to other trait objects. /// /// The index is only used for winnowing. + // FIXME(-Znext-solver=no): The new solver does not need this index, remove! TraitUpcasting(usize), }