From fc6fa6dd541b8a5f4bdf7e9593d7b38e30e49a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 11 Sep 2026 17:50:41 +0200 Subject: [PATCH 01/21] =?UTF-8?q?Syntactically=20reject=20leading=20parent?= =?UTF-8?q?hesized=20precise=20capturing=20lists=20in=20bare=20trait=20obj?= =?UTF-8?q?ect=20types=20(`(use<=E2=80=A6>)+`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler/rustc_parse/src/parser/ty.rs | 35 ++++++++++--------- .../precise-capturing/parenthesized.rs | 10 ++++++ .../precise-capturing/parenthesized.stderr | 21 ++++++++++- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index f62f8f1765652..0ff0cf18c6d1d 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -444,14 +444,14 @@ impl<'a> Parser<'a> { /// 2. tuple type /// 3. bare trait object type where the first trait bound is parenthesized fn parse_paren_start_ty(&mut self, lo: Span, allow_plus: AllowPlus) -> PResult<'a, TyKind> { - let mut trailing_plus = false; - let (ts, trailing) = self.parse_paren_comma_seq(|p| { + let mut inside_has_trailing_plus = false; + let (ts, trailing_comma) = self.parse_paren_comma_seq(|p| { let ty = p.parse_ty()?; - trailing_plus = p.prev_token == TokenKind::Plus; + inside_has_trailing_plus = p.prev_token == TokenKind::Plus; Ok(ty) })?; - if ts.len() == 1 && matches!(trailing, Trailing::No) { + if ts.len() == 1 && matches!(trailing_comma, Trailing::No) { let ty = ts.into_iter().next().unwrap(); // Let's check if we actually have a bare trait object type where the first trait bound @@ -459,11 +459,12 @@ impl<'a> Parser<'a> { // what's contained between the parentheses resembles a *BareTraitBound*. // // For context, looking at bounds in general (see *Bound*), only trait bounds are - // allowed to be wrapped in parentheses, not however lifetime and use bounds. - let maybe_bounds = allow_plus == AllowPlus::Yes && self.token.is_like_plus(); + // allowed to be wrapped in parentheses, not however outlives and use bounds. + let outside_has_eligible_trailing_plus = + allow_plus == AllowPlus::Yes && self.token.is_like_plus(); match ty.kind { // `"(" TypePath ")" "+"` - TyKind::Path(None, path) if maybe_bounds => self + TyKind::Path(None, path) if outside_has_eligible_trailing_plus => self .finish_parsing_bare_trait_object_ty( ThinVec::new(), path, @@ -471,18 +472,18 @@ impl<'a> Parser<'a> { true, ast::Parens::Yes, ), - // `"(" BareTraitBound\TypePath | UseBound ")" "+"` + // `"(" BareTraitBound\TypePath ")" "+"` // - // * FIXME: As alluded to above, only trait bounds are meant to allow parens. - // Arguably, it's an accident that we're permitting *UseBound*s and thus types - // like `(use<>)+`. Might need a T-lang FCP to change this. - // * We're checking `!trailing_plus` to prevent us from accepting code like - // `(T+)+` or `('a+)+`. - // * While we could be looking at `('a)+` which we don't want to accept, we - // know that the `parse_ty` above has already emitted an error since the - // lifetime isn't immediately followed by a `+`. + // We actually accept outlives bounds here, too, purely to reduce diagnostic output + // for ill-formed code like `('a)+`: We know for a fact that the `parse_ty` above + // has already emitted an error for the inner `'a` as it's not followed by `+`. + // This way we indirectly suppress unhelpful follow-up diagnostics like E0178 + // ("bad `+` in type") and E0224 ("no trait bound in trait object type"). TyKind::TraitObject(mut bounds, TraitObjectSyntax::None) - if maybe_bounds && bounds.len() == 1 && !trailing_plus => + if outside_has_eligible_trailing_plus + && !inside_has_trailing_plus + && let [ast::GenericBound::Trait(_) | ast::GenericBound::Outlives(_)] = + bounds.as_slice() => { self.eat_plus(); bounds.append(&mut self.parse_generic_bounds()?); diff --git a/tests/ui/impl-trait/precise-capturing/parenthesized.rs b/tests/ui/impl-trait/precise-capturing/parenthesized.rs index e3f80fc1d9f06..6ecdf5d7ddbb3 100644 --- a/tests/ui/impl-trait/precise-capturing/parenthesized.rs +++ b/tests/ui/impl-trait/precise-capturing/parenthesized.rs @@ -5,4 +5,14 @@ fn f() -> impl Sized + (use<>) {} //~^ ERROR precise capturing lists may not be parenthesized //~| HELP remove the parentheses +#[cfg(false)] +type O = Trait + (use<>); +//~^ ERROR precise capturing lists may not be parenthesized +//~| HELP remove the parentheses + +// We once used to accidentally accept this. +#[cfg(false)] +type O = (use<>) + Trait; +//~^ ERROR expected a path on the left-hand side of `+` + fn main() {} diff --git a/tests/ui/impl-trait/precise-capturing/parenthesized.stderr b/tests/ui/impl-trait/precise-capturing/parenthesized.stderr index c97fa9972ef01..b01f02bbafa89 100644 --- a/tests/ui/impl-trait/precise-capturing/parenthesized.stderr +++ b/tests/ui/impl-trait/precise-capturing/parenthesized.stderr @@ -10,5 +10,24 @@ LL - fn f() -> impl Sized + (use<>) {} LL + fn f() -> impl Sized + use<> {} | -error: aborting due to 1 previous error +error: precise capturing lists may not be parenthesized + --> $DIR/parenthesized.rs:9:18 + | +LL | type O = Trait + (use<>); + | ^^^^^^^ + | +help: remove the parentheses + | +LL - type O = Trait + (use<>); +LL + type O = Trait + use<>; + | + +error[E0178]: expected a path on the left-hand side of `+` + --> $DIR/parenthesized.rs:15:10 + | +LL | type O = (use<>) + Trait; + | ^^^^^^^ expected a path + +error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0178`. From a9caaf2725515f6c63ab7419e536c40229479835 Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 2 Oct 2026 09:37:55 +0200 Subject: [PATCH 02/21] move everything into lint decorator --- .../src/solve/delegate.rs | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 8f2d496f95b25..fa55925687e29 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -515,27 +515,24 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< fn emit_next_solver_overflow_fcw(&self, goal: Goal<'tcx, ty::Predicate<'tcx>>, span: Span) { let tcx = self.tcx; - let goal = self.deeply_resolve_ignoring_regions(goal); - let mut visitor = OverflowedGoalChain { - span, - predicates: vec![], - recursion_limit: usize::min(16, tcx.recursion_limit().0), - }; - - // HACK: avoid computing goal chains for dependencies by relying on the fact that - // `cargo` passes `lint_cap=allow` to deps. This should mitigate some of the perf/rss - // regression when compiling crates whose deps trigger a large number of these FCWs. - if !matches!(tcx.sess.opts.lint_cap, Some(rustc_lint_defs::Level::Allow)) { - let _ = self.with_disabled_next_solver_overflow_fcw(|| { - self.visit_proof_tree(goal, &mut visitor) - }); - } - tcx.emit_node_span_lint( RECURSION_DEPTH_EXCEEDING_LIMIT, CRATE_HIR_ID, span, rustc_errors::DiagDecorator(|diag| { + // We do this in the decorator as that avoids the work + // if we don't actually emit the lint. + let goal = self.deeply_resolve_ignoring_regions(goal); + let mut visitor = OverflowedGoalChain { + span, + predicates: vec![], + recursion_limit: usize::min(16, tcx.recursion_limit().0), + }; + + let _ = self.with_disabled_next_solver_overflow_fcw(|| { + self.visit_proof_tree(goal, &mut visitor) + }); + // FIXME: share this with overflow error in fulfillment instead of duplicating. let pred_str = |pred: ty::Predicate<'tcx>| { let s = pred.to_string(); From bb356c7d4b07bafb8fc3b2d422ec4e0b5316a23a Mon Sep 17 00:00:00 2001 From: kenta-afk Date: Sat, 3 Oct 2026 00:07:02 +0900 Subject: [PATCH 03/21] updates the expect message in library/core/src/time.rs --- library/core/src/time.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 6f33fdd36e53b..9fd43e3e326e9 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -278,7 +278,7 @@ impl Duration { } else { let secs = secs .checked_add((nanos / NANOS_PER_SEC) as u64) - .expect("overflow in Duration::new"); + .expect("adding carry-over seconds from nanoseconds should not exceed u64::MAX"); let nanos = nanos % NANOS_PER_SEC; // SAFETY: nanos % NANOS_PER_SEC < NANOS_PER_SEC, therefore nanos is within the valid range Duration { secs, nanos: unsafe { Nanoseconds::new_unchecked(nanos) } } @@ -1342,7 +1342,7 @@ const impl Add for Duration { #[inline] fn add(self, rhs: Duration) -> Duration { - self.checked_add(rhs).expect("overflow when adding durations") + self.checked_add(rhs).expect("adding durations should not exceed u64::MAX") } } @@ -1362,7 +1362,7 @@ const impl Sub for Duration { #[inline] fn sub(self, rhs: Duration) -> Duration { - self.checked_sub(rhs).expect("overflow when subtracting durations") + self.checked_sub(rhs).expect("subtracting durations should not be negative") } } @@ -1382,7 +1382,7 @@ const impl Mul for Duration { #[inline] fn mul(self, rhs: u32) -> Duration { - self.checked_mul(rhs).expect("overflow when multiplying duration by scalar") + self.checked_mul(rhs).expect("multiplying duration by scalar should not exceed u64::MAX") } } @@ -1414,7 +1414,7 @@ const impl Div for Duration { #[inline] #[track_caller] fn div(self, rhs: u32) -> Duration { - self.checked_div(rhs).expect("divide by zero error when dividing duration by scalar") + self.checked_div(rhs).expect("divisor should not be zero") } } @@ -1435,20 +1435,20 @@ macro_rules! sum_durations { for entry in $iter { total_secs = - total_secs.checked_add(entry.secs).expect("overflow in iter::sum over durations"); + total_secs.checked_add(entry.secs).expect("sum over duration iterator should not overflow u64::MAX"); total_nanos = match total_nanos.checked_add(entry.nanos.as_inner() as u64) { Some(n) => n, None => { total_secs = total_secs .checked_add(total_nanos / NANOS_PER_SEC as u64) - .expect("overflow in iter::sum over durations"); + .expect("sum over duration iterator should not overflow u64::MAX"); (total_nanos % NANOS_PER_SEC as u64) + entry.nanos.as_inner() as u64 } }; } total_secs = total_secs .checked_add(total_nanos / NANOS_PER_SEC as u64) - .expect("overflow in iter::sum over durations"); + .expect("sum over duration iterator should not overflow u64::MAX"); total_nanos %= NANOS_PER_SEC as u64; Duration::new(total_secs, total_nanos as u32) }}; From 04a02fdd3478a21c5cc3fb6d03f59751239ec5e7 Mon Sep 17 00:00:00 2001 From: kenta-afk Date: Sat, 3 Oct 2026 00:14:32 +0900 Subject: [PATCH 04/21] fix: improve formatting of duration sum calculation for readability --- library/core/src/time.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 9fd43e3e326e9..207ec52ce4637 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1434,8 +1434,9 @@ macro_rules! sum_durations { let mut total_nanos: u64 = 0; for entry in $iter { - total_secs = - total_secs.checked_add(entry.secs).expect("sum over duration iterator should not overflow u64::MAX"); + total_secs = total_secs + .checked_add(entry.secs) + .expect("sum over duration iterator should not overflow u64::MAX"); total_nanos = match total_nanos.checked_add(entry.nanos.as_inner() as u64) { Some(n) => n, None => { From b6d09f95178ea7dc1d02e898cfd67cfa563bff79 Mon Sep 17 00:00:00 2001 From: lcnr Date: Sun, 4 Oct 2026 11:42:25 +0200 Subject: [PATCH 05/21] replace`fully_monomorphized` with `cx.typing_env()` --- .../rustc_codegen_cranelift/src/abi/mod.rs | 4 +- compiler/rustc_codegen_cranelift/src/base.rs | 5 +-- .../rustc_codegen_cranelift/src/common.rs | 6 +-- .../rustc_codegen_cranelift/src/constant.rs | 3 +- .../rustc_codegen_cranelift/src/inline_asm.rs | 4 +- .../src/intrinsics/mod.rs | 7 +--- compiler/rustc_codegen_cranelift/src/num.rs | 4 +- .../src/value_and_place.rs | 6 +-- compiler/rustc_codegen_gcc/src/consts.rs | 6 +-- compiler/rustc_codegen_gcc/src/int.rs | 8 ++-- .../rustc_codegen_gcc/src/intrinsic/simd.rs | 6 +-- .../src/builder/autodiff.rs | 12 ++---- compiler/rustc_codegen_llvm/src/intrinsic.rs | 4 +- compiler/rustc_codegen_ssa/src/base.rs | 2 +- compiler/rustc_codegen_ssa/src/mir/mod.rs | 2 +- .../src/builder/expr/as_rvalue.rs | 4 +- compiler/rustc_mir_build/src/builder/misc.rs | 4 +- compiler/rustc_public_bridge/src/alloc.rs | 4 +- .../src/context/helpers.rs | 5 --- .../rustc_public_bridge/src/context/impls.rs | 41 +++++-------------- 20 files changed, 52 insertions(+), 85 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/abi/mod.rs b/compiler/rustc_codegen_cranelift/src/abi/mod.rs index 7f46b19f7568f..e2446d94993a7 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/mod.rs @@ -16,7 +16,7 @@ use rustc_abi::{CanonAbi, ExternAbi, X86Call}; use rustc_codegen_ssa::base::is_call_from_compiler_builtins_to_upstream_monomorphization; use rustc_codegen_ssa::diagnostics::CompilerBuiltinsCannotCall; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::ty::layout::FnAbiOf; +use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv as _}; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{ShimKind, TypeVisitableExt}; use rustc_session::Session; @@ -441,7 +441,7 @@ pub(crate) fn codegen_terminator_call<'tcx>( let instance = if let ty::FnDef(def_id, fn_args) = *func.layout().ty.kind() { let instance = ty::Instance::expect_resolve( fx.tcx, - ty::TypingEnv::fully_monomorphized(), + fx.typing_env(), def_id, fn_args.no_bound_vars().unwrap(), source_info.span, diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index e752189130290..a1810a8d494f0 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -720,7 +720,7 @@ fn codegen_stmt<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, cur_block: Block, stmt: let func_ref = fx.get_function_ref( Instance::resolve_for_fn_ptr( fx.tcx, - ty::TypingEnv::fully_monomorphized(), + fx.typing_env(), def_id, args.no_bound_vars().unwrap(), ) @@ -771,8 +771,7 @@ fn codegen_stmt<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, cur_block: Block, stmt: fn is_wide_ptr<'tcx>(fx: &FunctionCx<'_, '_, 'tcx>, ty: Ty<'tcx>) -> bool { ty.builtin_deref(true).is_some_and(|pointee_ty| { - fx.tcx - .type_has_metadata(pointee_ty, ty::TypingEnv::fully_monomorphized()) + fx.tcx.type_has_metadata(pointee_ty, fx.typing_env()) }) } diff --git a/compiler/rustc_codegen_cranelift/src/common.rs b/compiler/rustc_codegen_cranelift/src/common.rs index 30bd2f28af53f..867843a193718 100644 --- a/compiler/rustc_codegen_cranelift/src/common.rs +++ b/compiler/rustc_codegen_cranelift/src/common.rs @@ -4,8 +4,8 @@ use rustc_abi::{Float, Integer, Primitive}; use rustc_index::IndexVec; use rustc_middle::ty::TypeFoldable; use rustc_middle::ty::layout::{ - self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers, - codegen_handle_fn_abi_err, + self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv as _, LayoutError, + LayoutOfHelpers, codegen_handle_fn_abi_err, }; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -350,7 +350,7 @@ impl<'tcx> FunctionCx<'_, '_, 'tcx> { { self.instance.instantiate_mir_and_normalize_erasing_regions( self.tcx, - ty::TypingEnv::fully_monomorphized(), + self.typing_env(), ty::EarlyBinder::bind(self.tcx, value), ) } diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index f1e4fd818ebeb..5fc02a19c0094 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -9,6 +9,7 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir::interpret::{ AllocId, GlobalAlloc, PointerArithmetic, Scalar, read_target_uint, }; +use rustc_middle::ty::layout::HasTypingEnv as _; use rustc_middle::ty::{ExistentialTraitRef, ScalarInt}; use crate::prelude::*; @@ -81,7 +82,7 @@ pub(crate) fn eval_mir_constant<'tcx>( let cv = fx.monomorphize(constant.const_); // This cannot fail because we checked all required_consts in advance. let val = cv - .eval(fx.tcx, ty::TypingEnv::fully_monomorphized(), constant.span) + .eval(fx.tcx, fx.typing_env(), constant.span) .expect("erroneous constant missed by mono item collection"); (val, cv.ty()) } diff --git a/compiler/rustc_codegen_cranelift/src/inline_asm.rs b/compiler/rustc_codegen_cranelift/src/inline_asm.rs index b2bf856e7bcee..c13af35f94108 100644 --- a/compiler/rustc_codegen_cranelift/src/inline_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/inline_asm.rs @@ -7,7 +7,7 @@ use rustc_abi::CanonAbi; use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_attr_ir::lang_items::LangItem; use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar as ConstScalar}; -use rustc_middle::ty::layout::FnAbiOf; +use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv as _}; use rustc_span::sym; use rustc_target::asm::*; use rustc_target::spec::Arch; @@ -175,7 +175,7 @@ pub(crate) fn codegen_inline_asm_terminator<'tcx>( if let ty::FnDef(def_id, args) = *const_.ty().kind() { let instance = ty::Instance::resolve_for_fn_ptr( fx.tcx, - ty::TypingEnv::fully_monomorphized(), + fx.typing_env(), def_id, args.no_bound_vars().unwrap(), ) diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs index 9c3182c944dc6..1437ea7445d24 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs @@ -22,7 +22,7 @@ use cranelift_codegen::ir::{ }; use rustc_middle::ty; use rustc_middle::ty::GenericArgsRef; -use rustc_middle::ty::layout::ValidityRequirement; +use rustc_middle::ty::layout::{HasTypingEnv as _, ValidityRequirement}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_span::{Spanned, Symbol, sym}; use rustc_target::spec::PanicStrategy; @@ -729,10 +729,7 @@ fn codegen_regular_intrinsic_call<'tcx>( if let Some(requirement) = requirement { let do_panic = !fx .tcx - .check_validity_requirement(( - requirement, - ty::TypingEnv::fully_monomorphized().as_query_input(ty), - )) + .check_validity_requirement((requirement, fx.typing_env().as_query_input(ty))) .expect("expect to have layout during codegen"); if do_panic { diff --git a/compiler/rustc_codegen_cranelift/src/num.rs b/compiler/rustc_codegen_cranelift/src/num.rs index f1c44df1f6886..317e61031b34c 100644 --- a/compiler/rustc_codegen_cranelift/src/num.rs +++ b/compiler/rustc_codegen_cranelift/src/num.rs @@ -1,5 +1,7 @@ //! Various operations on integer and floating-point numbers +use rustc_middle::ty::layout::HasTypingEnv as _; + use crate::codegen_f16_f128; use crate::prelude::*; @@ -437,7 +439,7 @@ fn codegen_ptr_binop<'tcx>( .layout() .ty .builtin_deref(true) - .map(|ty| !fx.tcx.type_has_metadata(ty, ty::TypingEnv::fully_monomorphized())) + .map(|ty| !fx.tcx.type_has_metadata(ty, fx.typing_env())) .unwrap_or(true); if is_thin_ptr { diff --git a/compiler/rustc_codegen_cranelift/src/value_and_place.rs b/compiler/rustc_codegen_cranelift/src/value_and_place.rs index 440ae9c4b812e..a4af73cbc3f20 100644 --- a/compiler/rustc_codegen_cranelift/src/value_and_place.rs +++ b/compiler/rustc_codegen_cranelift/src/value_and_place.rs @@ -693,7 +693,7 @@ impl<'tcx> CPlace<'tcx> { }; let (field_ptr, field_layout) = codegen_field(fx, base, extra, layout, field); - if fx.tcx.type_has_metadata(field_layout.ty, ty::TypingEnv::fully_monomorphized()) { + if fx.tcx.type_has_metadata(field_layout.ty, fx.typing_env()) { CPlace::for_ptr_with_extra(field_ptr, extra.unwrap(), field_layout) } else { CPlace::for_ptr(field_ptr, field_layout) @@ -808,7 +808,7 @@ impl<'tcx> CPlace<'tcx> { pub(crate) fn place_deref(self, fx: &mut FunctionCx<'_, '_, 'tcx>) -> CPlace<'tcx> { let inner_layout = fx.layout_of(self.layout().ty.builtin_deref(true).unwrap()); - if fx.tcx.type_has_metadata(inner_layout.ty, ty::TypingEnv::fully_monomorphized()) { + if fx.tcx.type_has_metadata(inner_layout.ty, fx.typing_env()) { let (addr, extra) = self.to_cvalue(fx).load_scalar_pair(fx); CPlace::for_ptr_with_extra(Pointer::new(addr), extra, inner_layout) } else { @@ -821,7 +821,7 @@ impl<'tcx> CPlace<'tcx> { fx: &mut FunctionCx<'_, '_, 'tcx>, layout: TyAndLayout<'tcx>, ) -> CValue<'tcx> { - if fx.tcx.type_has_metadata(self.layout().ty, ty::TypingEnv::fully_monomorphized()) { + if fx.tcx.type_has_metadata(self.layout().ty, fx.typing_env()) { let (ptr, extra) = self.to_ptr_unsized(); CValue::by_val_pair(ptr.get_addr(fx), extra, layout) } else { diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 0784064547e0f..c34367de422d4 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -16,8 +16,8 @@ use rustc_middle::mir::interpret::{ self, ConstAllocation, CtfeProvenance, ErrorHandled, Scalar as InterpScalar, read_target_uint, }; use rustc_middle::mono::MonoItem; -use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, Instance}; +use rustc_middle::ty::Instance; +use rustc_middle::ty::layout::{HasTypingEnv as _, LayoutOf}; use rustc_span::def_id::DefId; use rustc_span::{bug, span_bug}; @@ -251,7 +251,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { let gcc_type = if nested { self.type_i8() } else { - let ty = instance.ty(self.tcx, ty::TypingEnv::fully_monomorphized()); + let ty = instance.ty(self.tcx, self.typing_env()); self.layout_of(ty).gcc_type(self) }; diff --git a/compiler/rustc_codegen_gcc/src/int.rs b/compiler/rustc_codegen_gcc/src/int.rs index de8762d1c06b4..eaf44c35ded01 100644 --- a/compiler/rustc_codegen_gcc/src/int.rs +++ b/compiler/rustc_codegen_gcc/src/int.rs @@ -10,7 +10,8 @@ use gccjit::{ use rustc_abi::{CanonAbi, Endian, ExternAbi}; use rustc_codegen_ssa::common::{IntPredicate, TypeKind}; use rustc_codegen_ssa::traits::{BackendTypes, BaseTypeCodegenMethods, BuilderMethods, OverflowOp}; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; +use rustc_middle::ty::layout::HasTypingEnv as _; use rustc_target::callconv::{ArgAbi, ArgAttributes, FnAbi, PassMode}; use rustc_type_ir::{Interner, TyKind}; @@ -387,10 +388,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { 128 => self.tcx.types.i128, _ => unreachable!("unexpected integer size"), }; - let layout = self - .tcx - .layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(res_ty)) - .unwrap(); + let layout = self.tcx.layout_of(self.cx.typing_env().as_query_input(res_ty)).unwrap(); let arg_abi = ArgAbi { layout, mode: PassMode::Direct(ArgAttributes::new()) }; let mut fn_abi = FnAbi { diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs index 54013b6be6173..959ad826359f9 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs @@ -16,7 +16,7 @@ use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods, LayoutTy use rustc_hir as hir; use rustc_middle::mir::BinOp; use rustc_middle::ty::consts::ConstExt; -use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; +use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv as _, LayoutOf}; use rustc_middle::ty::{self, Ty}; use rustc_span::{ErrorGuaranteed, Span, Symbol, span_bug, sym}; @@ -540,7 +540,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( match *in_elem.kind() { ty::RawPtr(p_ty, _) => { let metadata = p_ty.ptr_metadata_ty(bx.tcx, |ty| { - bx.tcx.normalize_erasing_regions(ty::TypingEnv::fully_monomorphized(), ty) + bx.tcx.normalize_erasing_regions(bx.typing_env(), ty) }); require!( metadata.is_unit(), @@ -554,7 +554,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( match *out_elem.kind() { ty::RawPtr(p_ty, _) => { let metadata = p_ty.ptr_metadata_ty(bx.tcx, |ty| { - bx.tcx.normalize_erasing_regions(ty::TypingEnv::fully_monomorphized(), ty) + bx.tcx.normalize_erasing_regions(bx.typing_env(), ty) }); require!( metadata.is_unit(), diff --git a/compiler/rustc_codegen_llvm/src/builder/autodiff.rs b/compiler/rustc_codegen_llvm/src/builder/autodiff.rs index 03058ba02d9dc..3b1e36265b941 100644 --- a/compiler/rustc_codegen_llvm/src/builder/autodiff.rs +++ b/compiler/rustc_codegen_llvm/src/builder/autodiff.rs @@ -10,7 +10,7 @@ use rustc_codegen_ssa::mir::place::PlaceValue; use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods, ReturnSlot}; use rustc_data_structures::thin_vec::ThinVec; use rustc_middle::ty; -use rustc_middle::ty::{PseudoCanonicalInput, Ty, TyCtxt, TypingEnv}; +use rustc_middle::ty::{Ty, TyCtxt, TypingEnv}; use rustc_span::bug; use rustc_target::callconv::PassMode; use tracing::debug; @@ -50,12 +50,8 @@ pub(crate) fn adjust_activity_to_abi<'tcx>( if let ty::Slice(element_ty) = tail_ty.kind() { // Now we need to figure out the size of each slice element in memory to allow // safety checks and usability improvements in the backend. - let pci = PseudoCanonicalInput { - typing_env: TypingEnv::fully_monomorphized(), - value: *element_ty, - }; - let layout = tcx.layout_of(pci); + let layout = tcx.layout_of(typing_env.as_query_input(*element_ty)); let elem_size = match layout { Ok(layout) => layout.size, Err(_) => { @@ -91,9 +87,7 @@ pub(crate) fn adjust_activity_to_abi<'tcx>( } } - let pci = PseudoCanonicalInput { typing_env: TypingEnv::fully_monomorphized(), value: *ty }; - - let layout = match tcx.layout_of(pci) { + let layout = match tcx.layout_of(typing_env.as_query_input(*ty)) { Ok(layout) => layout.layout, Err(_) => { bug!("failed to compute layout for type {:?}", ty); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index da0460b2a9290..542d5fd7a088d 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -22,7 +22,7 @@ use rustc_middle::mir::BinOp; use rustc_middle::ty::consts::ConstExt; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, HasTypingEnv, LayoutOf}; use rustc_middle::ty::offload_meta::OffloadMetadata; -use rustc_middle::ty::{self, GenericArgsRef, Instance, SimdAlign, Ty, TyCtxt, TypingEnv}; +use rustc_middle::ty::{self, GenericArgsRef, Instance, SimdAlign, Ty, TyCtxt}; use rustc_session::diagnostics::feature_err; use rustc_span::{ErrorGuaranteed, Span, Symbol, bug, span_bug, sym}; use rustc_structures::CrateType; @@ -1881,7 +1881,7 @@ fn codegen_autodiff<'ll, 'tcx>( adjust_activity_to_abi( tcx, source_fn_ptr_ty, - TypingEnv::fully_monomorphized(), + bx.cx.typing_env(), &mut diff_attrs.input_activity, ); diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index f9dfd04faf2ac..e7bdb30c370dc 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -451,7 +451,7 @@ where let instance = match ty.kind() { &ty::FnDef(def_id, args) => Instance::expect_resolve( cx.tcx(), - ty::TypingEnv::fully_monomorphized(), + cx.typing_env(), def_id, args.no_bound_vars().unwrap(), expr.span, diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index aefa8356536dc..969de7a2d2172 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -222,7 +222,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( if tcx.features().ergonomic_clones() { let monomorphized_mir = instance.instantiate_mir_and_normalize_erasing_regions( tcx, - ty::TypingEnv::fully_monomorphized(), + cx.typing_env(), ty::EarlyBinder::bind(tcx, mir.clone()), ); mir = tcx.arena.alloc(optimize_use_clone::(cx, monomorphized_mir)); diff --git a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs index 09c76d6beb4a1..18dea6dafd2dd 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs @@ -736,7 +736,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Helper to get a `-1` value of the appropriate type fn neg_1_literal(&mut self, span: Span, ty: Ty<'tcx>) -> Operand<'tcx> { - let typing_env = ty::TypingEnv::fully_monomorphized(); + let typing_env = self.typing_env(); let size = self.tcx.layout_of(typing_env.as_query_input(ty)).unwrap().size; let literal = Const::from_bits(self.tcx, size.unsigned_int_max(), typing_env, ty); @@ -746,7 +746,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Helper to get the minimum value of the appropriate type fn minval_literal(&mut self, span: Span, ty: Ty<'tcx>) -> Operand<'tcx> { assert!(ty.is_signed()); - let typing_env = ty::TypingEnv::fully_monomorphized(); + let typing_env = self.typing_env(); let bits = self.tcx.layout_of(typing_env.as_query_input(ty)).unwrap().size.bits(); let n = 1 << (bits - 1); let literal = Const::from_bits(self.tcx, n, typing_env, ty); diff --git a/compiler/rustc_mir_build/src/builder/misc.rs b/compiler/rustc_mir_build/src/builder/misc.rs index 6e8e74fd4fc8d..101d07cea2ce7 100644 --- a/compiler/rustc_mir_build/src/builder/misc.rs +++ b/compiler/rustc_mir_build/src/builder/misc.rs @@ -2,7 +2,7 @@ //! kind of thing. use rustc_middle::mir::*; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use rustc_span::Span; use rustc_trait_selection::infer::InferCtxtExt; use tracing::debug; @@ -32,7 +32,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// Returns a zero literal operand for the appropriate type, works for /// bool, char and integers. pub(crate) fn zero_literal(&mut self, span: Span, ty: Ty<'tcx>) -> Operand<'tcx> { - let literal = Const::from_bits(self.tcx, 0, ty::TypingEnv::fully_monomorphized(), ty); + let literal = Const::from_bits(self.tcx, 0, self.typing_env(), ty); self.literal_operand(span, literal) } diff --git a/compiler/rustc_public_bridge/src/alloc.rs b/compiler/rustc_public_bridge/src/alloc.rs index 7e6af3425465a..675f0f4cf8671 100644 --- a/compiler/rustc_public_bridge/src/alloc.rs +++ b/compiler/rustc_public_bridge/src/alloc.rs @@ -8,6 +8,7 @@ use rustc_abi::{Size, TyAndLayout}; use rustc_middle::mir::interpret::{ AllocId, AllocInit, AllocRange, Allocation, ConstAllocation, Pointer, Scalar, alloc_range, }; +use rustc_middle::ty::layout::HasTypingEnv; use rustc_middle::ty::{Ty, layout}; use super::{CompilerCtxt, Tables}; @@ -18,8 +19,7 @@ pub fn create_ty_and_layout<'tcx, B: Bridge>( cx: &CompilerCtxt<'tcx, B>, ty: Ty<'tcx>, ) -> Result>, &'tcx layout::LayoutError<'tcx>> { - use crate::context::TypingEnvHelpers; - cx.tcx.layout_of(cx.fully_monomorphized().as_query_input(ty)) + cx.tcx.layout_of(cx.typing_env().as_query_input(ty)) } pub fn try_new_scalar<'tcx, B: Bridge>( diff --git a/compiler/rustc_public_bridge/src/context/helpers.rs b/compiler/rustc_public_bridge/src/context/helpers.rs index 21eef29e5f1cc..e5a3aa0457612 100644 --- a/compiler/rustc_public_bridge/src/context/helpers.rs +++ b/compiler/rustc_public_bridge/src/context/helpers.rs @@ -4,7 +4,6 @@ //! interface regardless of internal compiler changes. use rustc_middle::mir::interpret::AllocRange; -use rustc_middle::ty; use rustc_middle::ty::Ty; use rustc_span::def_id::DefId; @@ -12,10 +11,6 @@ pub trait TyHelpers<'tcx> { fn new_foreign(&self, def_id: DefId) -> Ty<'tcx>; } -pub trait TypingEnvHelpers<'tcx> { - fn fully_monomorphized(&self) -> ty::TypingEnv<'tcx>; -} - pub trait AllocRangeHelpers<'tcx> { fn alloc_range(&self, offset: rustc_abi::Size, size: rustc_abi::Size) -> AllocRange; } diff --git a/compiler/rustc_public_bridge/src/context/impls.rs b/compiler/rustc_public_bridge/src/context/impls.rs index f8ab16a54b451..742e863749518 100644 --- a/compiler/rustc_public_bridge/src/context/impls.rs +++ b/compiler/rustc_public_bridge/src/context/impls.rs @@ -12,7 +12,7 @@ use rustc_hir::def::DefKind; use rustc_middle::mir::interpret::{AllocId, ConstAllocation, ErrorHandled, GlobalAlloc, Scalar}; use rustc_middle::mir::{BinOp, Body, Const as MirConst, ConstValue, UnOp}; use rustc_middle::ty::consts::ConstExt; -use rustc_middle::ty::layout::{FnAbiOf, LayoutOf}; +use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv, LayoutOf}; use rustc_middle::ty::print::{ with_forced_trimmed_paths, with_no_trimmed_paths, with_resolve_crate_name, }; @@ -28,7 +28,7 @@ use rustc_span::def_id::{CrateNum, DefId, LOCAL_CRATE}; use rustc_span::{Span, Symbol}; use rustc_target::callconv::FnAbi; -use super::{AllocRangeHelpers, CompilerCtxt, TyHelpers, TypingEnvHelpers}; +use super::{AllocRangeHelpers, CompilerCtxt, TyHelpers}; use crate::builder::BodyBuilder; use crate::{Bridge, Error, Tables, filter_def_ids}; @@ -38,12 +38,6 @@ impl<'tcx, B: Bridge> TyHelpers<'tcx> for CompilerCtxt<'tcx, B> { } } -impl<'tcx, B: Bridge> TypingEnvHelpers<'tcx> for CompilerCtxt<'tcx, B> { - fn fully_monomorphized(&self) -> ty::TypingEnv<'tcx> { - ty::TypingEnv::fully_monomorphized() - } -} - impl<'tcx, B: Bridge> AllocRangeHelpers<'tcx> for CompilerCtxt<'tcx, B> { fn alloc_range( &self, @@ -458,8 +452,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { /// Evaluate constant as a target usize. pub fn eval_target_usize(&self, cnst: MirConst<'tcx>) -> Result { - use crate::context::TypingEnvHelpers; - cnst.try_eval_target_usize(self.tcx, self.fully_monomorphized()) + cnst.try_eval_target_usize(self.tcx, self.typing_env()) .ok_or_else(|| B::Error::new(format!("Const `{cnst:?}` cannot be encoded as u64"))) } @@ -471,7 +464,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { pub fn try_new_const_zst(&self, ty_internal: Ty<'tcx>) -> Result, B::Error> { let size = self .tcx - .layout_of(self.fully_monomorphized().as_query_input(ty_internal)) + .layout_of(self.typing_env().as_query_input(ty_internal)) .map_err(|err| { B::Error::new(format!( "Cannot create a zero-sized constant for type `{ty_internal}`: {err}" @@ -523,11 +516,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { value: u128, ty_internal: Ty<'tcx>, ) -> Result, B::Error> { - let size = self - .tcx - .layout_of(self.fully_monomorphized().as_query_input(ty_internal)) - .unwrap() - .size; + let size = self.tcx.layout_of(self.typing_env().as_query_input(ty_internal)).unwrap().size; let scalar = ScalarInt::try_from_uint(value, size).ok_or_else(|| { B::Error::new(format!("Value overflow: cannot convert `{value}` to `{ty_internal}`.")) })?; @@ -539,11 +528,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { value: u128, ty_internal: Ty<'tcx>, ) -> Result, B::Error> { - let size = self - .tcx - .layout_of(self.fully_monomorphized().as_query_input(ty_internal)) - .unwrap() - .size; + let size = self.tcx.layout_of(self.typing_env().as_query_input(ty_internal)).unwrap().size; let scalar = ScalarInt::try_from_uint(value, size).ok_or_else(|| { B::Error::new(format!("Value overflow: cannot convert `{value}` to `{ty_internal}`.")) })?; @@ -585,11 +570,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { /// Returns the type of given definition instantiated with the given arguments. pub fn def_ty_with_args(&self, item: DefId, args_ref: GenericArgsRef<'tcx>) -> Ty<'tcx> { let def_ty = self.tcx.type_of(item); - self.tcx.instantiate_and_normalize_erasing_regions( - args_ref, - self.fully_monomorphized(), - def_ty, - ) + self.tcx.instantiate_and_normalize_erasing_regions(args_ref, self.typing_env(), def_ty) } /// `Span` of a `DefId`. @@ -625,7 +606,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { /// Get the instance type with generic instantiations applied and lifetimes erased. pub fn instance_ty(&self, instance: ty::Instance<'tcx>) -> Ty<'tcx> { assert!(!instance.has_non_region_param(), "{instance:?} needs further instantiation"); - instance.ty(self.tcx, self.fully_monomorphized()) + instance.ty(self.tcx, self.typing_env()) } /// Get the instantiation types. @@ -694,7 +675,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { def_id: DefId, args_ref: GenericArgsRef<'tcx>, ) -> Option> { - match Instance::try_resolve(self.tcx, self.fully_monomorphized(), def_id, args_ref) { + match Instance::try_resolve(self.tcx, self.typing_env(), def_id, args_ref) { Ok(Some(instance)) => Some(instance), Ok(None) | Err(_) => None, } @@ -712,7 +693,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { def_id: DefId, args_ref: GenericArgsRef<'tcx>, ) -> Option> { - Instance::resolve_for_fn_ptr(self.tcx, self.fully_monomorphized(), def_id, args_ref) + Instance::resolve_for_fn_ptr(self.tcx, self.typing_env(), def_id, args_ref) } /// Resolve instance for a closure with the requested type. @@ -728,7 +709,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { /// Try to evaluate an instance into a constant. pub fn eval_instance(&self, instance: ty::Instance<'tcx>) -> Result { self.tcx.const_eval_instance( - self.fully_monomorphized(), + self.typing_env(), instance, self.tcx.def_span(instance.def_id()), ) From 4fb4b8f37a0c7d37fcd9f26b15988f83eb9562ef Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 23 Sep 2026 04:02:26 +0100 Subject: [PATCH 06/21] Re-introduce ResultsVisitor::visit_block_exit --- compiler/rustc_mir_dataflow/src/framework/direction.rs | 4 ++++ compiler/rustc_mir_dataflow/src/framework/visitor.rs | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs index 39bd2be9409a3..bb0685cb7ff08 100644 --- a/compiler/rustc_mir_dataflow/src/framework/direction.rs +++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs @@ -127,6 +127,8 @@ impl Direction for Backward { analysis.apply_primary_statement_effect(state, stmt, loc); vis.visit_after_primary_statement_effect(state, stmt, loc); } + + vis.visit_block_exit(state, block); } } @@ -242,5 +244,7 @@ impl Direction for Forward { vis.visit_after_early_terminator_effect(state, term, loc); analysis.apply_primary_terminator_effect(state, term, loc); vis.visit_after_primary_terminator_effect(state, term, loc); + + vis.visit_block_exit(state, block); } } diff --git a/compiler/rustc_mir_dataflow/src/framework/visitor.rs b/compiler/rustc_mir_dataflow/src/framework/visitor.rs index e4b840a73e502..5b8a3374e04d9 100644 --- a/compiler/rustc_mir_dataflow/src/framework/visitor.rs +++ b/compiler/rustc_mir_dataflow/src/framework/visitor.rs @@ -34,6 +34,13 @@ pub trait ResultsVisitor<'tcx, A> where A: Analysis<'tcx>, { + /// Called after all effects in a block have been applied in the direction + /// of the analysis. + /// + /// In a forwards analysis, `state` is from the block's end. In a backwards + /// analysis, `state` is from the block's start. + fn visit_block_exit(&mut self, _state: &A::Domain, _block: BasicBlock) {} + /// Called after the "early" effect of the given statement is applied to `state`. fn visit_after_early_statement_effect( &mut self, From 34f6848313a4038e29f503bca61e08f261c0fa2e Mon Sep 17 00:00:00 2001 From: lcnr Date: Sun, 4 Oct 2026 00:16:33 +0200 Subject: [PATCH 07/21] rigid aliases to non-rigid for fully normalized check --- compiler/rustc_hir_typeck/src/upvar.rs | 2 +- compiler/rustc_middle/src/ty/normalize_erasing_regions.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 98155d9a8d5ef..fbe20c849de9c 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -1867,7 +1867,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // Anything else would be completely captured and therefore handled already. - _ => unreachable!(), + _ => unreachable!("unexpected base_path_ty: {base_path_ty:?}",), } } diff --git a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs index 7fcabd423e778..399493e453088 100644 --- a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs +++ b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs @@ -74,10 +74,10 @@ impl<'tcx> TyCtxt<'tcx> { ) { let value = self.erase_and_anonymize_regions(value); if value.has_aliases() { - assert_eq!( - value.clone(), - value.fold_with(&mut NormalizeAfterErasingRegionsFolder { tcx: self, typing_env }) - ) + let normalized = ty::set_aliases_to_non_rigid(self, value.clone()) + .skip_normalization() + .fold_with(&mut NormalizeAfterErasingRegionsFolder { tcx: self, typing_env }); + assert_eq!(value, normalized); } } From 6ddabeb04c96427965d72314202fb14e600c620f Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 8 Oct 2026 09:27:08 +0200 Subject: [PATCH 08/21] add comment --- compiler/rustc_middle/src/ty/normalize_erasing_regions.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs index 399493e453088..c7837eeec307b 100644 --- a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs +++ b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs @@ -74,6 +74,10 @@ impl<'tcx> TyCtxt<'tcx> { ) { let value = self.erase_and_anonymize_regions(value); if value.has_aliases() { + // We want this check to also detect cases where an alias is incorrectly + // rigid in the current `TypingEnv`. This is the case if we use values + // which have been normalized in a different `TypingEnv` without + // renormalizing them. This was necessary to catch e.g. #163724. let normalized = ty::set_aliases_to_non_rigid(self, value.clone()) .skip_normalization() .fold_with(&mut NormalizeAfterErasingRegionsFolder { tcx: self, typing_env }); From 7b151cff4f8a56df4bea875a0337affcfd309147 Mon Sep 17 00:00:00 2001 From: rami3l Date: Wed, 7 Oct 2026 17:41:33 +0200 Subject: [PATCH 09/21] fix(bootstrap/darwin): fix rpath for distributed LLD --- src/bootstrap/download-ci-llvm-stamp | 2 +- src/bootstrap/src/core/build_steps/llvm.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/download-ci-llvm-stamp b/src/bootstrap/download-ci-llvm-stamp index ad3fbce51d1d2..58691539edfa3 100644 --- a/src/bootstrap/download-ci-llvm-stamp +++ b/src/bootstrap/download-ci-llvm-stamp @@ -1,4 +1,4 @@ Change this file to make users of the `download-ci-llvm` configuration download a new version of LLVM from CI, even if the LLVM submodule hasn’t changed. -Last change is for: https://github.com/rust-lang/rust/pull/161049 +Last change is for: https://github.com/rust-lang/rust/pull/163954 diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 5dae391cc4bee..4ac0f6ea85679 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -1724,7 +1724,6 @@ impl CommandLineStep for Lld { if builder.config.rpath_enabled(target) && helpers::use_host_linker(target) && llvm_output.link_shared() - && target.contains("linux") { // So we inform LLD where it can find LLVM's libraries by adding an rpath entry to the // expected parent `lib` directory. @@ -1732,7 +1731,12 @@ impl CommandLineStep for Lld { // Be careful when changing this path, we need to ensure it's quoted or escaped: // `$ORIGIN` would otherwise be expanded when the `LdFlags` are passed verbatim to // cmake. - ldflags.push_all("-Wl,-rpath,'$ORIGIN/../../../'"); + if target.contains("linux") { + ldflags.push_all("-Wl,-rpath,'$ORIGIN/../../../'"); + } + if target.contains("apple-darwin") { + ldflags.push_all("-Wl,-rpath,'@loader_path/../../../'"); + } } configure_cmake(builder, target, &mut cfg, true, ldflags, CcFlags::default(), &[]); From 86a1b2ff5a4e5d77f3440b2db5df8175492fab67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 8 Oct 2026 18:25:32 +0000 Subject: [PATCH 10/21] When mentioning that closure doesn't implement trait, point at closure ``` error[E0277]: `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` is not an iterator --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:34:14 | LL | for _ in closure {} | ^^^^^^^ `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` is not an iterator | help: the trait `Iterator` is not implemented for closure `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19 | LL | let closure = || vec![1u8].into_iter(); | ^^ = note: required for `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` to implement `IntoIterator` help: use parentheses to call this closure | LL | for _ in closure() {} | ++ ``` --- .../src/error_reporting/traits/fulfillment_errors.rs | 1 + .../adt_const_params/const_param_ty_bad.stderr | 6 +++++- tests/ui/methods/filter-relevant-fn-bounds.stderr | 6 +++++- ...closure-arg-type-mismatch-issue-45727.next.stderr | 12 ++++++++++-- .../ui/mismatched_types/closure-mismatch.next.stderr | 12 ++++++++++-- ...ed-as-arg-where-it-should-have-been-called.stderr | 6 +++++- ...uggest-calling-fn-in-for-loop-issue-161564.stderr | 12 ++++++++++-- ...e-parentheses-to-call-closure-issue-145404.stderr | 6 +++++- .../explicit-reference-cast-unrelated-leaf.stderr | 12 ++++++++++-- .../fn-pointer/bare-fn-no-impl-fn-ptr-99875.stderr | 6 +++++- 10 files changed, 66 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 3b21c5e5f19ba..bcd0d5d0bb4e2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -388,6 +388,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { { self.tcx.def_span(def.did()) } + ty::Closure(def_id, _) => self.tcx.def_span(*def_id), _ => DUMMY_SP, }; if let Some(s) = label { diff --git a/tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr b/tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr index 0237e7bb5eeab..1b442b27080a7 100644 --- a/tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr +++ b/tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr @@ -24,7 +24,11 @@ LL | check(|| {}); | | | required by a bound introduced by this call | - = help: the nightly-only, unstable trait `ConstParamTy_` is not implemented for closure `{closure@$DIR/const_param_ty_bad.rs:8:11: 8:13}` +help: the nightly-only, unstable trait `ConstParamTy_` is not implemented for closure `{closure@$DIR/const_param_ty_bad.rs:8:11: 8:13}` + --> $DIR/const_param_ty_bad.rs:8:11 + | +LL | check(|| {}); + | ^^ note: required by a bound in `check` --> $DIR/const_param_ty_bad.rs:4:18 | diff --git a/tests/ui/methods/filter-relevant-fn-bounds.stderr b/tests/ui/methods/filter-relevant-fn-bounds.stderr index 976d39a3714ca..f1676facdb2d4 100644 --- a/tests/ui/methods/filter-relevant-fn-bounds.stderr +++ b/tests/ui/methods/filter-relevant-fn-bounds.stderr @@ -20,7 +20,11 @@ LL | wrapper.do_something_wrapper(|value| ()); | | | required by a bound introduced by this call | - = help: the trait `for<'a> Output<'a>` is not implemented for closure `{closure@$DIR/filter-relevant-fn-bounds.rs:18:34: 18:41}` +help: the trait `for<'a> Output<'a>` is not implemented for closure `{closure@$DIR/filter-relevant-fn-bounds.rs:18:34: 18:41}` + --> $DIR/filter-relevant-fn-bounds.rs:18:34 + | +LL | wrapper.do_something_wrapper(|value| ()); + | ^^^^^^^ help: this trait has no implementations, consider adding one --> $DIR/filter-relevant-fn-bounds.rs:1:1 | diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr index baeff76e1ffe2..59a2255bc4fbf 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr @@ -6,7 +6,11 @@ LL | let _ = (-10..=10).find(|x: i32| x.signum() == 0); | | | required by a bound introduced by this call | - = help: the trait `for<'a> FnMut(&'a {integer})` is not implemented for closure `{closure@$DIR/closure-arg-type-mismatch-issue-45727.rs:6:29: 6:37}` +help: the trait `for<'a> FnMut(&'a {integer})` is not implemented for closure `{closure@$DIR/closure-arg-type-mismatch-issue-45727.rs:6:29: 6:37}` + --> $DIR/closure-arg-type-mismatch-issue-45727.rs:6:29 + | +LL | let _ = (-10..=10).find(|x: i32| x.signum() == 0); + | ^^^^^^^^ = note: expected a closure with signature `for<'a> fn(&'a {integer})` found a closure with signature `fn(i32)` note: required by a bound in `find` @@ -20,7 +24,11 @@ LL | let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); | | | required by a bound introduced by this call | - = help: the trait `for<'a> FnMut(&'a {integer})` is not implemented for closure `{closure@$DIR/closure-arg-type-mismatch-issue-45727.rs:9:29: 9:40}` +help: the trait `for<'a> FnMut(&'a {integer})` is not implemented for closure `{closure@$DIR/closure-arg-type-mismatch-issue-45727.rs:9:29: 9:40}` + --> $DIR/closure-arg-type-mismatch-issue-45727.rs:9:29 + | +LL | let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); + | ^^^^^^^^^^^ = note: expected a closure with signature `for<'a> fn(&'a {integer})` found a closure with signature `fn(&&&i32)` note: required by a bound in `find` diff --git a/tests/ui/mismatched_types/closure-mismatch.next.stderr b/tests/ui/mismatched_types/closure-mismatch.next.stderr index a6380b7487dad..0d6ca6d7bfca8 100644 --- a/tests/ui/mismatched_types/closure-mismatch.next.stderr +++ b/tests/ui/mismatched_types/closure-mismatch.next.stderr @@ -6,7 +6,11 @@ LL | baz(|_| ()); | | | required by a bound introduced by this call | - = help: the trait `for<'a> FnOnce(&'a ())` is not implemented for closure `{closure@$DIR/closure-mismatch.rs:12:9: 12:12}` +help: the trait `for<'a> FnOnce(&'a ())` is not implemented for closure `{closure@$DIR/closure-mismatch.rs:12:9: 12:12}` + --> $DIR/closure-mismatch.rs:12:9 + | +LL | baz(|_| ()); + | ^^^ = note: expected a closure with signature `for<'a> fn(&'a ())` found a closure with signature `fn(&())` note: required for `{closure@$DIR/closure-mismatch.rs:12:9: 12:12}` to implement `Foo` @@ -30,7 +34,11 @@ LL | baz(|x| ()); | | | required by a bound introduced by this call | - = help: the trait `for<'a> FnOnce(&'a ())` is not implemented for closure `{closure@$DIR/closure-mismatch.rs:16:9: 16:12}` +help: the trait `for<'a> FnOnce(&'a ())` is not implemented for closure `{closure@$DIR/closure-mismatch.rs:16:9: 16:12}` + --> $DIR/closure-mismatch.rs:16:9 + | +LL | baz(|x| ()); + | ^^^ = note: expected a closure with signature `for<'a> fn(&'a ())` found a closure with signature `fn(&())` note: required for `{closure@$DIR/closure-mismatch.rs:16:9: 16:12}` to implement `Foo` diff --git a/tests/ui/suggestions/fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr b/tests/ui/suggestions/fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr index 60d0263c149b4..9a01fc20342c8 100644 --- a/tests/ui/suggestions/fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr +++ b/tests/ui/suggestions/fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr @@ -24,7 +24,11 @@ LL | bar(closure); | | | required by a bound introduced by this call | - = help: the trait `T` is not implemented for closure `{closure@$DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:18:19: 18:21}` +help: the trait `T` is not implemented for closure `{closure@$DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:18:19: 18:21}` + --> $DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:18:19 + | +LL | let closure = || S; + | ^^ note: required by a bound in `bar` --> $DIR/fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:14:16 | diff --git a/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.stderr b/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.stderr index e2edaebb65c9b..4d355efc2595d 100644 --- a/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.stderr +++ b/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.stderr @@ -43,7 +43,11 @@ error[E0277]: `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:1 LL | for _ in closure {} | ^^^^^^^ `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` is not an iterator | - = help: the trait `Iterator` is not implemented for closure `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` +help: the trait `Iterator` is not implemented for closure `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` + --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19 + | +LL | let closure = || vec![1u8].into_iter(); + | ^^ = note: required for `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` to implement `IntoIterator` help: use parentheses to call this closure | @@ -56,7 +60,11 @@ error[E0277]: `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:1 LL | for _ in || vec![1u8].into_iter() {} | ^^^^^^^^^^^^^^^^^^^^^^^^ `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14: 36:16}` is not an iterator | - = help: the trait `Iterator` is not implemented for closure `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14: 36:16}` +help: the trait `Iterator` is not implemented for closure `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14: 36:16}` + --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14 + | +LL | for _ in || vec![1u8].into_iter() {} + | ^^ = note: required for `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14: 36:16}` to implement `IntoIterator` help: use parentheses to call this closure | diff --git a/tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr b/tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr index 19151a674c7f8..0e4c4afff7379 100644 --- a/tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr +++ b/tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr @@ -6,7 +6,11 @@ LL | S.call(|| "hello"); | | | required by a bound introduced by this call | - = help: the trait `std::fmt::Display` is not implemented for closure `{closure@$DIR/use-parentheses-to-call-closure-issue-145404.rs:12:12: 12:14}` +help: the trait `std::fmt::Display` is not implemented for closure `{closure@$DIR/use-parentheses-to-call-closure-issue-145404.rs:12:12: 12:14}` + --> $DIR/use-parentheses-to-call-closure-issue-145404.rs:12:12 + | +LL | S.call(|| "hello"); + | ^^ note: required by a bound in `S::call` --> $DIR/use-parentheses-to-call-closure-issue-145404.rs:8:28 | diff --git a/tests/ui/traits/explicit-reference-cast-unrelated-leaf.stderr b/tests/ui/traits/explicit-reference-cast-unrelated-leaf.stderr index 9f486cd12874f..b4bc90f0dbe31 100644 --- a/tests/ui/traits/explicit-reference-cast-unrelated-leaf.stderr +++ b/tests/ui/traits/explicit-reference-cast-unrelated-leaf.stderr @@ -6,7 +6,11 @@ LL | wrapper.do_something_wrapper(|value| ()); | | | required by a bound introduced by this call | - = help: the trait `for<'a> Output<'a>` is not implemented for closure `{closure@$DIR/explicit-reference-cast-unrelated-leaf.rs:18:34: 18:41}` +help: the trait `for<'a> Output<'a>` is not implemented for closure `{closure@$DIR/explicit-reference-cast-unrelated-leaf.rs:18:34: 18:41}` + --> $DIR/explicit-reference-cast-unrelated-leaf.rs:18:34 + | +LL | wrapper.do_something_wrapper(|value| ()); + | ^^^^^^^ help: this trait has no implementations, consider adding one --> $DIR/explicit-reference-cast-unrelated-leaf.rs:1:1 | @@ -27,7 +31,11 @@ error[E0277]: the trait bound `for<'a> _: From<<{closure@$DIR/explicit-reference LL | wrapper.do_something_wrapper(|value| ()); | ^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | - = help: the trait `for<'a> Output<'a>` is not implemented for closure `{closure@$DIR/explicit-reference-cast-unrelated-leaf.rs:18:34: 18:41}` +help: the trait `for<'a> Output<'a>` is not implemented for closure `{closure@$DIR/explicit-reference-cast-unrelated-leaf.rs:18:34: 18:41}` + --> $DIR/explicit-reference-cast-unrelated-leaf.rs:18:34 + | +LL | wrapper.do_something_wrapper(|value| ()); + | ^^^^^^^ help: this trait has no implementations, consider adding one --> $DIR/explicit-reference-cast-unrelated-leaf.rs:1:1 | diff --git a/tests/ui/traits/fn-pointer/bare-fn-no-impl-fn-ptr-99875.stderr b/tests/ui/traits/fn-pointer/bare-fn-no-impl-fn-ptr-99875.stderr index 7151302879606..e64622302731c 100644 --- a/tests/ui/traits/fn-pointer/bare-fn-no-impl-fn-ptr-99875.stderr +++ b/tests/ui/traits/fn-pointer/bare-fn-no-impl-fn-ptr-99875.stderr @@ -24,7 +24,11 @@ LL | takes(|_: Argument| -> Return { todo!() }); | | | required by a bound introduced by this call | - = help: the trait `Trait` is not implemented for closure `{closure@$DIR/bare-fn-no-impl-fn-ptr-99875.rs:16:11: 16:34}` +help: the trait `Trait` is not implemented for closure `{closure@$DIR/bare-fn-no-impl-fn-ptr-99875.rs:16:11: 16:34}` + --> $DIR/bare-fn-no-impl-fn-ptr-99875.rs:16:11 + | +LL | takes(|_: Argument| -> Return { todo!() }); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: the trait `Trait` is implemented for fn pointer `fn(Argument) -> Return` --> $DIR/bare-fn-no-impl-fn-ptr-99875.rs:9:1 | From 3e422d645fd84d0f8bfcc3d6cc0e7285ed27b34b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 8 Oct 2026 22:26:04 +0200 Subject: [PATCH 11/21] Prefer local paths over remote ones when foreign item is locally reexported --- src/librustdoc/formats/cache.rs | 1 + src/librustdoc/html/format.rs | 15 ++++++++++++- .../jump-to-def/incoherent_impl-inlined.rs | 21 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/rustdoc-html/jump-to-def/incoherent_impl-inlined.rs diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 39baca1b4c0cd..ab539302396a0 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -18,6 +18,7 @@ use crate::formats::item_type::ItemType; use crate::html::render::{IndexItem, IndexItemInfo}; use crate::visit_lib::RustdocEffectiveVisibilities; +#[derive(Debug)] pub(crate) struct PathInfo { /// Parts of the fully qualified path. So in `foo::bar::bib`, it will /// be `["foo", "bar", "bib"]`. diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 464977d762488..9236bc952b06f 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -308,7 +308,7 @@ fn print_generic_args(generic_args: &clean::GenericArgs, cx: &Context<'_>) -> im } // Possible errors when computing href link source for a `DefId` -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug)] pub(crate) enum HrefError { /// This item is known to rustdoc, but from a crate that does not have documentation generated. /// @@ -338,6 +338,7 @@ pub(crate) enum HrefError { } /// Type representing information of an `href` attribute. +#[derive(Debug)] pub(crate) struct HrefInfo { /// URL to the item page. pub(crate) url: String, @@ -530,6 +531,18 @@ fn generate_item_def_id_path( { def_id = trait_def_id; } else if let Some(new_def_id) = ty.ty_adt_def().map(|adt| adt.did()) { + // If the inferred item is available in the local path map, it means we're reexporting + // from the current crate, so better use the local path directly. + if cx.cache().paths.contains_key(&new_def_id) + && let Ok(mut info) = + href_with_jump_to_def_path_depth(new_def_id, cx, jump_to_def_path_depth, None) + { + let kind = ItemType::from_def_id(original_def_id, tcx); + // We need to append the anchor to the URL otherwise we're just linking to the + // parent item... + info.url = format!("{}#{kind}.{}", info.url, tcx.item_name(original_def_id)); + return Ok(info); + } def_id = new_def_id; maybe_have_impl_not_in_def_crate = !of_trait && !original_def_id.is_local() diff --git a/tests/rustdoc-html/jump-to-def/incoherent_impl-inlined.rs b/tests/rustdoc-html/jump-to-def/incoherent_impl-inlined.rs new file mode 100644 index 0000000000000..5f8c501e1a4a6 --- /dev/null +++ b/tests/rustdoc-html/jump-to-def/incoherent_impl-inlined.rs @@ -0,0 +1,21 @@ +//@ aux-build: incoherent_impl1.rs +//@ aux-build: incoherent_impl2.rs +//@ build-aux-docs +//@ compile-flags: -Zunstable-options --generate-link-to-definition + +#![crate_name = "foo"] + +extern crate incoherent_impl1; +extern crate incoherent_impl2; + +pub use incoherent_impl2::Error; + +//@ has 'src/foo/incoherent_impl-inlined.rs.html' +//@ has - '//pre//a[@href="../../foo/struct.Error.html#method.new"]' 'new' + +//@ has 'foo/struct.Error.html' +//@ has - '//*[@id="method.new"]' 'pub fn new() -> Error' + +fn foo() { + let x = Error::new(); +} From 2f9523bc8fa9c55d225bee3acf52d4e07c106425 Mon Sep 17 00:00:00 2001 From: June Pyle Date: Tue, 6 Oct 2026 18:34:07 -0700 Subject: [PATCH 12/21] Fix debug assert failure in `note_obligation_cause_code_inner` on the old solver and update `incorrect-skip-binder-for-item-bound` test accordingly. --- .../src/error_reporting/traits/suggestions.rs | 6 +++-- ...ect-skip-binder-for-item-bound.next.stderr | 2 +- ...rect-skip-binder-for-item-bound.old.stderr | 25 +++++++++++++++++++ .../incorrect-skip-binder-for-item-bound.rs | 5 +--- 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.old.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 76755eb481db7..5a69d4c8df384 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -4974,8 +4974,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .as_ref() .and_then(|typeck| typeck.node_type_opt(expr.hir_id)) && let Some(pred) = predicate.as_clause() - && let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder() - && self.can_eq(param_env, pred.self_ty(), expr_ty) + && let Some(pred) = pred.as_trait_clause() + && self + .infcx + .enter_forall(pred, |pred| self.can_eq(param_env, pred.self_ty(), expr_ty)) { (expr_ty, expr) } else { diff --git a/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.next.stderr b/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.next.stderr index edb3f36cd7560..13162d5ea13a5 100644 --- a/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.next.stderr +++ b/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.next.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `for<'a> u8: From<&'a ()>` is not satisfied - --> $DIR/incorrect-skip-binder-for-item-bound.rs:19:15 + --> $DIR/incorrect-skip-binder-for-item-bound.rs:16:15 | LL | fn hello() -> impl for<'a> A<'a, Assoc: Into + 'static + Copy> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `for<'a> From<&'a ()>` is not implemented for `u8` diff --git a/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.old.stderr b/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.old.stderr new file mode 100644 index 0000000000000..13162d5ea13a5 --- /dev/null +++ b/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.old.stderr @@ -0,0 +1,25 @@ +error[E0277]: the trait bound `for<'a> u8: From<&'a ()>` is not satisfied + --> $DIR/incorrect-skip-binder-for-item-bound.rs:16:15 + | +LL | fn hello() -> impl for<'a> A<'a, Assoc: Into + 'static + Copy> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `for<'a> From<&'a ()>` is not implemented for `u8` + | +help: `u8` implements trait `From` + --> $SRC_DIR/core/src/convert/num.rs:LL:COL + | + = note: `From` + ::: $SRC_DIR/core/src/convert/num.rs:LL:COL + | + = note: in this macro invocation + --> $SRC_DIR/core/src/ascii/ascii_char.rs:LL:COL + | + = note: `From` + ::: $SRC_DIR/core/src/ascii/ascii_char.rs:LL:COL + | + = note: in this macro invocation + = note: required for `&'a ()` to implement `for<'a> Into` + = note: this error originates in the macro `impl_from_bool` which comes from the expansion of the macro `into_int_impl` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.rs b/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.rs index 65a7d9442311e..fe1121af77f69 100644 --- a/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.rs +++ b/tests/ui/impl-trait/diagnostics/incorrect-skip-binder-for-item-bound.rs @@ -1,9 +1,6 @@ //@ revisions: old next //@ ignore-compare-mode-next-solver (explicit revisions) //@[next] compile-flags: -Znext-solver -//@[old] failure-status: 101 -//@[old] dont-check-compiler-stderr -//@[old] known-bug: #139381 //@ needs-rustc-debug-assertions // An ICE caused by an incorrect `skip_binder()` in diagnostics code, see #139381. @@ -17,7 +14,7 @@ impl<'a> A<'a> for () { } fn hello() -> impl for<'a> A<'a, Assoc: Into + 'static + Copy> { - //[next]~^ ERROR: the trait bound `for<'a> u8: From<&'a ()>` is not satisfied + //~^ ERROR: the trait bound `for<'a> u8: From<&'a ()>` is not satisfied () } From f774f6117d9d398dcd751cae3b452846df60e6a1 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 23 Sep 2026 04:02:26 +0100 Subject: [PATCH 13/21] Add precise MIR liveness analysis --- compiler/rustc_mir_dataflow/src/impls/mod.rs | 5 + .../src/impls/precise_liveness.rs | 596 ++++++++++++++++++ 2 files changed, 601 insertions(+) create mode 100644 compiler/rustc_mir_dataflow/src/impls/precise_liveness.rs diff --git a/compiler/rustc_mir_dataflow/src/impls/mod.rs b/compiler/rustc_mir_dataflow/src/impls/mod.rs index 1e12e41ce1fb4..faa4b0dd1694b 100644 --- a/compiler/rustc_mir_dataflow/src/impls/mod.rs +++ b/compiler/rustc_mir_dataflow/src/impls/mod.rs @@ -1,6 +1,7 @@ mod borrowed_locals; mod initialized; mod liveness; +mod precise_liveness; mod storage_liveness; pub use self::borrowed_locals::{MaybeBorrowedLocals, borrowed_locals}; @@ -11,6 +12,10 @@ pub use self::initialized::{ pub use self::liveness::{ DefUse, LivenessTransferFunction, MaybeLiveLocals, MaybeTransitiveLiveLocals, }; +pub use self::precise_liveness::{ + SplitPointEffect, SplitPointIndex, VisitAllocatedLocalsWith, VisitDeallocatedLocalsWith, + dump_liveness_matrix, liveness_matrix, +}; pub use self::storage_liveness::{ MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive, always_storage_live_locals, }; diff --git a/compiler/rustc_mir_dataflow/src/impls/precise_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/precise_liveness.rs new file mode 100644 index 0000000000000..0234c240ef304 --- /dev/null +++ b/compiler/rustc_mir_dataflow/src/impls/precise_liveness.rs @@ -0,0 +1,596 @@ +//! Computes the points where each local must have a distinct allocation. +//! +//! The result is a [`SparseIntervalMatrix`] with one row per local. Two locals +//! may share the same address only if their rows are disjoint. To model MIR +//! statements where a source operand and destination place may share an +//! address, each statement and terminator is split into an early point, where +//! operands are read, and a late point, where destinations are written. +//! +//! A local live range starts at the late point of any statement or terminator +//! that writes to it without a `Deref` projection. It ends at `StorageLive` or +//! `StorageDead`, or at the early point of a whole-local move operand or the +//! last use of that local on a control-flow path (only for locals whose address +//! is never observed). This happens at `StorageLive` because it resets a local +//! to an uninitialized state. +//! +//! `Call` terminators are handled specially: move operands are kept live +//! through the late point of the terminator so they conflict with each other +//! and with the destination place. This matches the runtime behavior where the +//! place is donated to the callee for the duration of the call. +//! +//! The analysis consists of three passes: +//! +//! 1. A backward value-liveness analysis which identifies the last point at +//! which each local is directly used in each control flow path. This is used +//! to shorten the lifetime of unborrowed locals to their last use. +//! 2. A forward allocation-liveness analysis which uses that information +//! together with writes, moves, and storage markers to compute which +//! allocations may be live on entry to each block. +//! 3. A final walk using those entry states which constructs the matrix and +//! distinguishes whether each effect happens at the early or late point of a +//! statement or terminator. + +use rustc_index::IndexVec; +use rustc_index::bit_set::DenseBitSet; +use rustc_index::interval::SparseIntervalMatrix; +use rustc_middle::mir::visit::{ + MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, VisitPlacesWith, + Visitor, +}; +use rustc_middle::mir::{self, BasicBlock, Local, Location, MirDumper, PassWhere, Place}; +use rustc_middle::ty::TyCtxt; +use tracing::trace; + +use crate::impls::{DefUse, MaybeLiveLocals, borrowed_locals}; +use crate::points::{DenseLocationMap, PointIndex}; +use crate::{Analysis, GenKill, ResultsVisitor, visit_results}; + +/// Visits locals that are implicitly allocated by a statement or terminator as +/// part of destination place evaluation. +pub struct VisitAllocatedLocalsWith(pub F); + +impl<'tcx, F> Visitor<'tcx> for VisitAllocatedLocalsWith +where + F: FnMut(Local, PlaceContext), +{ + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _: Location) { + match DefUse::for_place(*place, context) { + DefUse::Def | DefUse::PartialWrite => (self.0)(place.local, context), + DefUse::Use | DefUse::NonUse => {} + } + } +} + +/// Visits locals that are implicitly freed by being evaluated as a bare-local +/// move operand or through a storage statement. +pub struct VisitDeallocatedLocalsWith(pub F); + +impl<'tcx, F> Visitor<'tcx> for VisitDeallocatedLocalsWith +where + F: FnMut(Local, PlaceContext), +{ + fn visit_statement(&mut self, statement: &mir::Statement<'tcx>, location: Location) { + match statement.kind { + // StorageLive resets a local to the live-but-unallocated state, + // which also has the effect of freeing its allocation. + mir::StatementKind::StorageLive(local) => { + (self.0)(local, PlaceContext::NonUse(NonUseContext::StorageLive)); + } + mir::StatementKind::StorageDead(local) => { + (self.0)(local, PlaceContext::NonUse(NonUseContext::StorageDead)); + } + _ => self.super_statement(statement, location), + } + } + + fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, _: Location) { + if let mir::Operand::Move(place) = operand + && let Some(local) = place.as_local() + { + (self.0)(local, PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Backward dataflow pass +// +// This pass computes "kill points" for each local, indicating the location of +// their last use in a particular control flow branch. These are later used in +// the forward pass to end the live range of locals that are never borrowed at +// their last direct use. +// +// This pass ignores borrowed locals: we can't determine their last use from +// syntactic uses of the base local, so we conservatively treat them as always +// live in the backwards pass and don't emit kill points for them. The main +// forward pass still kills at `StorageDead` and whole-local moves, which is +// when their allocation is freed. +// +// This pass has 2 outputs: a set of kill points that mark the last use +// locations of locals and a per-block bitset indicating which locals are live +// on entry to that block. The latter acts as a set of kill points at block +// entry for all locals not in the bit set. + +struct KillPoints<'a> { + live_on_entry: IndexVec>, + kill_points_map: IndexVec, +} + +impl<'a> KillPoints<'a> { + fn compute<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + pass_name: Option<&'static str>, + points: &DenseLocationMap, + kill_points: &'a mut Vec<(Local, Location)>, + ) -> Self { + let maybe_live_locals = MaybeLiveLocals.iterate_to_fixpoint(tcx, body, pass_name); + let borrowed_locals = borrowed_locals(body); + + // Initialize all borrowed locals as live on entry. We never try to kill + // those. + let mut live_on_entry = + IndexVec::from_elem_n(borrowed_locals.clone(), body.basic_blocks.len()); + + // Collect kill points and live-on-entry states from the results of + // MaybeLiveLocals. + kill_points.clear(); + let mut visitor = KillPointsVisitor { + kill_points, + live_on_entry: &mut live_on_entry, + borrowed_locals: &borrowed_locals, + }; + visit_results( + body, + mir::traversal::reachable(body).map(|(block, _)| block), + &maybe_live_locals, + &mut visitor, + ); + trace!(?kill_points); + trace!(?live_on_entry); + + // Create a mapping of `PointIndex` to the set of killed locals at that + // location. + let mut kill_points_map = IndexVec::from_elem_n(&[][..], points.num_points()); + for chunk in kill_points.chunk_by(|a, b| a.1 == b.1) { + let point = points.point_from_location(chunk[0].1); + trace!("Kill points at {:?}: {:?}", chunk[0].1, chunk); + debug_assert!(kill_points_map[point].is_empty()); + kill_points_map[point] = chunk; + } + + Self { live_on_entry, kill_points_map } + } +} + +struct KillPointsVisitor<'a> { + kill_points: &'a mut Vec<(Local, Location)>, + live_on_entry: &'a mut IndexVec>, + borrowed_locals: &'a DenseBitSet, +} + +impl<'tcx> ResultsVisitor<'tcx, MaybeLiveLocals> for KillPointsVisitor<'_> { + fn visit_block_exit(&mut self, state: &DenseBitSet, block: BasicBlock) { + // Borrowed locals are already marked as live when live_on_entry was + // initialized. This adds the non-borrowed locals that we have + // determined are live on entry to this block. + self.live_on_entry[block].union(state); + } + + fn visit_after_early_statement_effect( + &mut self, + state: &DenseBitSet, + statement: &mir::Statement<'tcx>, + location: Location, + ) { + VisitPlacesWith(|place: Place<'tcx>, ctxt| { + // Ignore non-uses. + match ctxt { + PlaceContext::NonMutatingUse(_) | PlaceContext::MutatingUse(_) => {} + PlaceContext::NonUse(_) => return, + } + + // If a local is used in a statement but is dead after it then this + // location is a kill point. Don't emit a kill point for borrowed + // locals. + if !state.contains(place.local) && !self.borrowed_locals.contains(place.local) { + self.kill_points.push((place.local, location)); + } + }) + .visit_statement(statement, location); + } + + fn visit_after_early_terminator_effect( + &mut self, + state: &DenseBitSet, + terminator: &mir::Terminator<'tcx>, + location: Location, + ) { + VisitPlacesWith(|place: Place<'tcx>, ctxt| { + // Ignore non-uses (they don't do anything) and edge uses + // (implicitly killed though live_on_entry at the start of the + // corresponding successor). + match ctxt { + PlaceContext::MutatingUse( + MutatingUseContext::AsmOutput + | MutatingUseContext::Call + | MutatingUseContext::Yield, + ) + | PlaceContext::NonUse(_) => return, + PlaceContext::NonMutatingUse(_) | PlaceContext::MutatingUse(_) => {} + } + + // If a local is used in a terminator but is dead after it then this + // location is a kill point. Don't emit a kill point for borrowed + // locals. + if !state.contains(place.local) && !self.borrowed_locals.contains(place.local) { + self.kill_points.push((place.local, location)); + } + }) + .visit_terminator(terminator, location); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Forward dataflow pass +// +// This is a simpler version of the main pass in `liveness_matrix`. It only +// needs to determine local liveness at block edges and doesn't distinguish +// between "early" and "late" effects. However it must still preserve the +// ordering of effects: moved operands are killed before destinations are +// generated. + +struct PreciseLiveness<'a> { + kill_points: &'a KillPoints<'a>, + points: &'a DenseLocationMap, +} + +impl PreciseLiveness<'_> { + fn apply_block_start_effect(&self, state: &mut DenseBitSet, block: BasicBlock) { + // Kill any unborrowed locals that are dead at the start of this block. + // Notably this kills any dead results produced by a predecessor's + // terminator. Borrowed locals are always included in live_on_entry. + state.intersect(&self.kill_points.live_on_entry[block]); + } +} + +impl<'tcx> Analysis<'tcx> for PreciseLiveness<'_> { + type Domain = DenseBitSet; + + const NAME: &'static str = "precise_liveness"; + + fn bottom_value(&self, body: &mir::Body<'tcx>) -> DenseBitSet { + DenseBitSet::new_empty(body.local_decls.len()) + } + + fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut DenseBitSet) { + // Function arguments start out as live. + for arg in body.args_iter() { + state.gen_(arg); + } + } + + fn apply_primary_statement_effect( + &self, + state: &mut DenseBitSet, + statement: &mir::Statement<'tcx>, + location: Location, + ) { + if location.statement_index == 0 { + self.apply_block_start_effect(state, location.block); + } + + VisitDeallocatedLocalsWith(|local, _| state.kill(local)) + .visit_statement(statement, location); + VisitAllocatedLocalsWith(|local, _| state.gen_(local)).visit_statement(statement, location); + + // Apply kill points at this statement: if a variable is dead then it + // doesn't need storage. + let point = self.points.point_from_location(location); + for &(local, _) in self.kill_points.kill_points_map[point] { + state.kill(local); + } + } + + fn apply_primary_terminator_effect( + &self, + state: &mut DenseBitSet, + terminator: &mir::Terminator<'tcx>, + location: Location, + ) { + if location.statement_index == 0 { + self.apply_block_start_effect(state, location.block); + } + + VisitDeallocatedLocalsWith(|local, _| state.kill(local)) + .visit_terminator(terminator, location); + + // Gen destination places. + VisitAllocatedLocalsWith(|local, ctxt| { + // The resume place of a Yield is only evaluated after the coroutine + // is resumed. This is in contrast to call destinations and inline + // asm outputs which are evaluated before the call and therefore + // remain allocated on both normal and unwind edges. + if ctxt != PlaceContext::MutatingUse(MutatingUseContext::Yield) { + state.gen_(local); + } + }) + .visit_terminator(terminator, location); + } + + fn apply_call_return_effect( + &self, + state: &mut DenseBitSet, + _block: BasicBlock, + return_places: mir::CallReturnPlaces<'_, 'tcx>, + ) { + if let mir::CallReturnPlaces::Yield(place) = return_places + && !place.is_indirect() + { + state.gen_(place.local); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Matrix construction + +/// Different "phases" of a single MIR statement, used to describe how +/// overlapping operands are handled. +/// +/// As a general rule, source operands are read in the `Early` phase and +/// destination places are written in the `Late` phase. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SplitPointEffect { + Early = 0, + Late = 1, +} + +rustc_index::newtype_index! { + /// A `PointIndex` with the lower bit encoding early/late inside a + /// statement. + /// + /// This is used to model overlap constraints within a MIR statement: if a + /// source/destination are allowed to overlap then the source is read in + /// `SplitPointEffect::Early` and the write is done in + /// `SplitPointEffect::Late`. + #[orderable] + #[debug_format = "SplitPointIndex({})"] + pub struct SplitPointIndex {} +} + +impl SplitPointIndex { + pub fn new(point: PointIndex, effect: SplitPointEffect) -> SplitPointIndex { + let index = (point.as_u32() << 1) | (effect as u32); + SplitPointIndex::from_u32(index) + } + + pub fn point(self) -> PointIndex { + PointIndex::from_u32(self.as_u32() >> 1) + } + + pub fn effect(self) -> SplitPointEffect { + match self.as_u32() & 1 { + 0 => SplitPointEffect::Early, + 1 => SplitPointEffect::Late, + _ => unreachable!(), + } + } +} + +/// Helper type to construct a `SparseIntervalMatrix`. +struct MatrixBuilder { + matrix: SparseIntervalMatrix, + range_start: IndexVec>, + + // Track locals that have been live at any point in a block so that at the + // end of a block we don't need to iterate over all locals. This + // significantly speeds up matrix building. + maybe_live_locals: Vec, +} + +impl MatrixBuilder { + fn gen_(&mut self, local: Local, point: PointIndex, effect: SplitPointEffect) { + let split_point = SplitPointIndex::new(point, effect); + + // No-op if the local is already live. + if self.range_start[local].is_none() { + self.range_start[local] = Some(split_point); + self.maybe_live_locals.push(local); + } + } + + fn kill(&mut self, local: Local, point: PointIndex, effect: SplitPointEffect) { + let end = SplitPointIndex::new(point, effect); + + // No-op if the local is already dead. + if let Some(start) = self.range_start[local].take() { + debug_assert!(end >= start); + self.matrix.append_range(local, start..=end); + } + } + + fn kill_all(&mut self, point: PointIndex, effect: SplitPointEffect) { + while let Some(local) = self.maybe_live_locals.pop() { + self.kill(local, point, effect); + } + } + + fn kill_all_except(&mut self, except: Local, point: PointIndex, effect: SplitPointEffect) { + while let Some(local) = self.maybe_live_locals.pop() { + if local != except { + self.kill(local, point, effect); + } + } + self.maybe_live_locals.push(except); + } +} + +pub fn liveness_matrix<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + points: &DenseLocationMap, + pass_name: Option<&'static str>, +) -> SparseIntervalMatrix { + let mut kill_points_vec = vec![]; + let kill_points = KillPoints::compute(tcx, body, pass_name, points, &mut kill_points_vec); + let mut results = PreciseLiveness { kill_points: &kill_points, points } + .iterate_to_fixpoint(tcx, body, pass_name); + + let mut builder = MatrixBuilder { + matrix: SparseIntervalMatrix::new(points.num_points() * 2), + range_start: IndexVec::from_elem_n(None, body.local_decls.len()), + maybe_live_locals: Vec::new(), + }; + for (block, block_data) in body.basic_blocks.iter_enumerated() { + // We can mutate the state in-place since we're not using it any more + // after this point. + let state = &mut results.entry_states[block]; + + // Notably this kills any dead results produced by a predecessor's + // terminator. + state.intersect(&kill_points.live_on_entry[block]); + + // Gen any locals that are live at the start of the block. If this block + // only consists of a return terminator then only gen the return place + // instead. This ensures that StorageDead for all other locals are + // inserted before the return terminator. + let terminator = block_data.terminator(); + if let mir::TerminatorKind::Return = terminator.kind + && block_data.statements.is_empty() + { + if state.contains(mir::RETURN_PLACE) { + builder.gen_(mir::RETURN_PLACE, points.entry_point(block), SplitPointEffect::Early); + } + } else { + for local in state.iter() { + builder.gen_(local, points.entry_point(block), SplitPointEffect::Early); + } + } + + for (statement_index, statement) in block_data.statements.iter().enumerate() { + let location = Location { block, statement_index }; + let point = points.point_from_location(location); + + // Kill moved locals at the early point. + VisitDeallocatedLocalsWith(|local, _| { + builder.kill(local, point, SplitPointEffect::Early); + }) + .visit_statement(statement, location); + + // Kill any locals which are no longer used after this statement. + for &(local, _) in kill_points.kill_points_map[point] { + builder.kill(local, point, SplitPointEffect::Early); + } + + // Gen destination places at the late point. + VisitAllocatedLocalsWith(|local, _| { + builder.gen_(local, point, SplitPointEffect::Late); + }) + .visit_statement(statement, location); + + // Kill any dead destination places: they will only appear at the + // late point of the statement they are generated in, which is + // sufficient for determining overlap. + for &(local, _) in kill_points.kill_points_map[point] { + builder.kill(local, point, SplitPointEffect::Late); + } + } + + // If this block ends in a return terminator, end all live ranges before + // the terminator so that StorageDead statements are inserted before it. + // + // This is useful after inlining so that the lifetime of locals in the + // inlined callee don't extend past the call in the caller. + if let mir::TerminatorKind::Return = terminator.kind + && !block_data.statements.is_empty() + { + // Blocks with only a return terminator are handled above. + let location = Location { block, statement_index: block_data.statements.len() - 1 }; + let point = points.point_from_location(location); + builder.kill_all_except(mir::RETURN_PLACE, point, SplitPointEffect::Late); + } + + let location = Location { block, statement_index: block_data.statements.len() }; + let point = points.point_from_location(location); + + // Kill moved locals at the early point. + VisitDeallocatedLocalsWith(|local, _| { + builder.kill(local, point, SplitPointEffect::Early); + }) + .visit_terminator(terminator, location); + + // Kill any locals which are no longer used after this terminator. + for &(local, _) in kill_points.kill_points_map[point] { + builder.kill(local, point, SplitPointEffect::Early); + } + + // Gen destination places at the late point. + VisitAllocatedLocalsWith(|local, _| { + builder.gen_(local, point, SplitPointEffect::Late); + }) + .visit_terminator(terminator, location); + + // Move arguments to a call are treated specially: the place that they + // represent is passed directly to the callee, which means that they are + // not allowed to alias any other move operand or the destination place. + // This is represented here by extending their live range to the late + // part, making it overlap with that of the destination place. + // + // Notably, this *doesn't* apply to TailCall. + if let mir::TerminatorKind::Call { + func: _, + args, + destination: _, + target: _, + unwind: _, + call_source: _, + fn_span: _, + } = &terminator.kind + { + for arg in args { + if let mir::Operand::Move(place) = arg.node + && !place.is_indirect() + { + builder.gen_(place.local, point, SplitPointEffect::Late); + } + } + } + + // End the lifetimes of all locals at the end of the block. Successor + // blocks (which may not be contiguous in the index space!) will + // initialize the lifetimes again from their entry state. + builder.kill_all(point, SplitPointEffect::Late); + } + + builder.matrix +} + +pub fn dump_liveness_matrix<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + pass_name: &'static str, + points: &DenseLocationMap, + matrix: &SparseIntervalMatrix, +) { + let locals_live_at = |split_point| { + matrix.rows().filter(|&r| matrix.contains(r, split_point)).collect::>() + }; + + if let Some(dumper) = MirDumper::new(tcx, pass_name, body) { + let extra_data = &|pass_where, w: &mut dyn std::io::Write| { + if let PassWhere::BeforeLocation(loc) = pass_where { + let point = points.point_from_location(loc); + let split_point = SplitPointIndex::new(point, SplitPointEffect::Early); + let live = locals_live_at(split_point); + writeln!(w, " // {loc:?}-early => {live:?}")?; + let split_point = SplitPointIndex::new(point, SplitPointEffect::Late); + let live = locals_live_at(split_point); + writeln!(w, " // {loc:?}-late => {live:?}")?; + } + Ok(()) + }; + + dumper.set_extra_data(extra_data).dump_mir(body) + } +} From c645363ba0fc9bcdbad3d928582a2a74878b96d2 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 5 Oct 2026 20:59:53 +0100 Subject: [PATCH 14/21] Add dataflow tests for PreciseLiveness --- compiler/rustc_attr_ir/src/data_structures.rs | 1 + .../src/attributes/rustc_internal.rs | 4 + compiler/rustc_mir_dataflow/src/pretty.rs | 29 +- compiler/rustc_span/src/symbol.rs | 1 + .../precise_liveness.borrowed.dataflow.0.mir | 30 ++ .../precise_liveness.branch.dataflow.0.mir | 33 ++ .../precise_liveness.call.dataflow.0.mir | 39 ++ ...iveness.call_borrowed_field.dataflow.0.mir | 35 ++ ...precise_liveness.call_field.dataflow.0.mir | 21 ++ ...cise_liveness.call_indirect.dataflow.0.mir | 17 + ..._liveness.dead_destinations.dataflow.0.mir | 31 ++ ...recise_liveness.dereference.dataflow.0.mir | 14 + .../precise_liveness.fields.dataflow.0.mir | 25 ++ .../precise_liveness.indexed.dataflow.0.mir | 15 + tests/mir-opt/precise_liveness.rs | 357 ++++++++++++++++++ .../precise_liveness.storage.dataflow.0.mir | 37 ++ ...precise_liveness.unborrowed.dataflow.0.mir | 22 ++ 17 files changed, 710 insertions(+), 1 deletion(-) create mode 100644 tests/mir-opt/precise_liveness.borrowed.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.branch.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.call.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.call_borrowed_field.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.call_field.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.call_indirect.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.dead_destinations.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.dereference.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.fields.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.indexed.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.rs create mode 100644 tests/mir-opt/precise_liveness.storage.dataflow.0.mir create mode 100644 tests/mir-opt/precise_liveness.unborrowed.dataflow.0.mir diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index a1f3f840aa5a2..e60bd32570eeb 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -592,6 +592,7 @@ pub enum RustcDumpLayoutKind { #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq, Eq)] pub enum RustcMirKind { PrettyLiveLocals, + PrettyPreciseLiveness, PrettyTransitiveLiveLocals, PeekMaybeInit, PeekMaybeUninit, diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 11554b437e39b..b15d71d1b690f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -630,6 +630,10 @@ impl CombineAttributeParser for RustcMirParser { cx.expect_no_args(mi.args())?; Some(RustcMirKind::PrettyLiveLocals) } + sym::rustc_pretty_precise_liveness => { + cx.expect_no_args(mi.args())?; + Some(RustcMirKind::PrettyPreciseLiveness) + } sym::rustc_pretty_transitive_live_locals => { cx.expect_no_args(mi.args())?; Some(RustcMirKind::PrettyTransitiveLiveLocals) diff --git a/compiler/rustc_mir_dataflow/src/pretty.rs b/compiler/rustc_mir_dataflow/src/pretty.rs index b37ad708e20e0..5a159ae40bd3c 100644 --- a/compiler/rustc_mir_dataflow/src/pretty.rs +++ b/compiler/rustc_mir_dataflow/src/pretty.rs @@ -7,7 +7,11 @@ use rustc_middle::ty::TyCtxt; use crate::debuginfo::debuginfo_locals; use crate::framework::Analysis; -use crate::impls::{MaybeLiveLocals, MaybeTransitiveLiveLocals, borrowed_locals}; +use crate::impls::{ + MaybeLiveLocals, MaybeTransitiveLiveLocals, SplitPointEffect, SplitPointIndex, borrowed_locals, + liveness_matrix, +}; +use crate::points::DenseLocationMap; use crate::{ResultsVisitor, visit_results}; type ExtraDataFn = dyn Fn(PassWhere, &mut dyn io::Write) -> io::Result<()>; @@ -33,6 +37,29 @@ pub(crate) fn mir_pretty_extra_data<'tcx>( &mut Annotator { annotations: &mut annotations }, ); } + RustcMirKind::PrettyPreciseLiveness => { + let points = DenseLocationMap::new(body); + let matrix = liveness_matrix(tcx, body, &points, None); + for (block, data) in body.basic_blocks.iter_enumerated() { + for statement_index in 0..=data.statements.len() { + let location = mir::Location { block, statement_index }; + let point = points.point_from_location(location); + let locals_live_at = |effect| { + let split_point = SplitPointIndex::new(point, effect); + matrix + .rows() + .filter(|&r| matrix.contains(r, split_point)) + .collect::>() + }; + let early = locals_live_at(SplitPointEffect::Early); + annotations + .add(PassWhere::BeforeLocation(location), format!("early: {early:?}")); + let late = locals_live_at(SplitPointEffect::Late); + annotations + .add(PassWhere::BeforeLocation(location), format!("late: {late:?}")); + } + } + } RustcMirKind::PrettyTransitiveLiveLocals => { let borrowed_locals = borrowed_locals(body); let debuginfo_locals = debuginfo_locals(body); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index fa59c1c54ee89..620b57586e0cf 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1899,6 +1899,7 @@ symbols! { rustc_peek_maybe_uninit, rustc_preserve_ub_checks, rustc_pretty_live_locals, + rustc_pretty_precise_liveness, rustc_pretty_transitive_live_locals, rustc_private, rustc_proc_macro_decls, diff --git a/tests/mir-opt/precise_liveness.borrowed.dataflow.0.mir b/tests/mir-opt/precise_liveness.borrowed.dataflow.0.mir new file mode 100644 index 0000000000000..64f9513b8757a --- /dev/null +++ b/tests/mir-opt/precise_liveness.borrowed.dataflow.0.mir @@ -0,0 +1,30 @@ +// MIR for `borrowed` 0 dataflow + +fn borrowed() -> u32 { + let mut _0: u32; + let mut _1: u32; + let mut _2: u32; + let mut _3: *const u32; + let mut _4: *const u32; + + bb0: { + // early: [] + // late: [_1] + _1 = const 1_u32; + // early: [_1] + // late: [_1, _2] + _2 = const 2_u32; + // early: [_1, _2] + // late: [_1, _2, _3] + _3 = &raw const _1; + // early: [_1, _2] + // late: [_1, _2, _4] + _4 = &raw const _2; + // early: [_1, _2] + // late: [_0, _1] + _0 = Add(copy _1, move _2); + // early: [_0] + // late: [] + return; + } +} diff --git a/tests/mir-opt/precise_liveness.branch.dataflow.0.mir b/tests/mir-opt/precise_liveness.branch.dataflow.0.mir new file mode 100644 index 0000000000000..d92d560b0dab3 --- /dev/null +++ b/tests/mir-opt/precise_liveness.branch.dataflow.0.mir @@ -0,0 +1,33 @@ +// MIR for `branch` 0 dataflow + +fn branch(_1: bool) -> u32 { + let mut _0: u32; + let mut _2: u32; + + bb0: { + // early: [_1] + // late: [_1, _2] + _2 = const 1_u32; + // early: [_1, _2] + // late: [_2] + switchInt(copy _1) -> [1: bb1, otherwise: bb2]; + } + + bb1: { + // early: [_2] + // late: [_0] + _0 = move _2; + // early: [_0] + // late: [] + return; + } + + bb2: { + // early: [] + // late: [_0] + _0 = const 0_u32; + // early: [_0] + // late: [] + return; + } +} diff --git a/tests/mir-opt/precise_liveness.call.dataflow.0.mir b/tests/mir-opt/precise_liveness.call.dataflow.0.mir new file mode 100644 index 0000000000000..f5e6d06e672d4 --- /dev/null +++ b/tests/mir-opt/precise_liveness.call.dataflow.0.mir @@ -0,0 +1,39 @@ +// MIR for `call` 0 dataflow + +fn call() -> u32 { + let mut _0: u32; + let mut _1: u32; + let mut _2: u32; + let mut _3: u32; + let mut _4: *const u32; + + bb0: { + // early: [] + // late: [_1] + _1 = const 1_u32; + // early: [_1] + // late: [_1, _2] + _2 = const 2_u32; + // early: [_1, _2] + // late: [_1, _3] + _3 = callee(move _1, copy _2) -> [return: bb1, unwind: bb2]; + } + + bb1: { + // early: [_3] + // late: [_3, _4] + _4 = &raw const _3; + // early: [_3] + // late: [_0] + _0 = move _3; + // early: [_0] + // late: [] + return; + } + + bb2 (cleanup): { + // early: [_3] + // late: [_3] + resume; + } +} diff --git a/tests/mir-opt/precise_liveness.call_borrowed_field.dataflow.0.mir b/tests/mir-opt/precise_liveness.call_borrowed_field.dataflow.0.mir new file mode 100644 index 0000000000000..96ecf63a27f53 --- /dev/null +++ b/tests/mir-opt/precise_liveness.call_borrowed_field.dataflow.0.mir @@ -0,0 +1,35 @@ +// MIR for `call_borrowed_field` 0 dataflow + +fn call_borrowed_field() -> u32 { + let mut _0: u32; + let mut _1: (u32,); + let mut _2: u32; + let mut _3: *const (u32,); + + bb0: { + // early: [] + // late: [_1] + (_1.0: u32) = const 1_u32; + // early: [_1] + // late: [_1, _3] + _3 = &raw const _1; + // early: [_1] + // late: [_1, _2] + _2 = callee(move (_1.0: u32), const 0_u32) -> [return: bb1, unwind: bb2]; + } + + bb1: { + // early: [_1, _2] + // late: [_0, _1] + _0 = move _2; + // early: [_0] + // late: [] + return; + } + + bb2 (cleanup): { + // early: [_1] + // late: [_1] + resume; + } +} diff --git a/tests/mir-opt/precise_liveness.call_field.dataflow.0.mir b/tests/mir-opt/precise_liveness.call_field.dataflow.0.mir new file mode 100644 index 0000000000000..2bcd1434eeafd --- /dev/null +++ b/tests/mir-opt/precise_liveness.call_field.dataflow.0.mir @@ -0,0 +1,21 @@ +// MIR for `call_field` 0 dataflow + +fn call_field() -> u32 { + let mut _0: u32; + let mut _1: (u32,); + + bb0: { + // early: [] + // late: [_1] + (_1.0: u32) = const 1_u32; + // early: [_1] + // late: [_0, _1] + _0 = callee(move (_1.0: u32), const 0_u32) -> [return: bb1, unwind unreachable]; + } + + bb1: { + // early: [_0] + // late: [] + return; + } +} diff --git a/tests/mir-opt/precise_liveness.call_indirect.dataflow.0.mir b/tests/mir-opt/precise_liveness.call_indirect.dataflow.0.mir new file mode 100644 index 0000000000000..fff5e24358bda --- /dev/null +++ b/tests/mir-opt/precise_liveness.call_indirect.dataflow.0.mir @@ -0,0 +1,17 @@ +// MIR for `call_indirect` 0 dataflow + +fn call_indirect(_1: *const u32) -> u32 { + let mut _0: u32; + + bb0: { + // early: [_1] + // late: [_0] + _0 = callee(move (*_1), const 0_u32) -> [return: bb1, unwind unreachable]; + } + + bb1: { + // early: [_0] + // late: [] + return; + } +} diff --git a/tests/mir-opt/precise_liveness.dead_destinations.dataflow.0.mir b/tests/mir-opt/precise_liveness.dead_destinations.dataflow.0.mir new file mode 100644 index 0000000000000..9b6a304bc7184 --- /dev/null +++ b/tests/mir-opt/precise_liveness.dead_destinations.dataflow.0.mir @@ -0,0 +1,31 @@ +// MIR for `dead_destinations` 0 dataflow + +fn dead_destinations() -> u32 { + let mut _0: u32; + let mut _1: u32; + let mut _2: u32; + + bb0: { + // early: [] + // late: [_1] + _1 = const 1_u32; + // early: [] + // late: [_2] + _2 = callee(const 1_u32, const 2_u32) -> [return: bb1, unwind: bb2]; + } + + bb1: { + // early: [] + // late: [_0] + _0 = const 0_u32; + // early: [_0] + // late: [] + return; + } + + bb2 (cleanup): { + // early: [] + // late: [] + resume; + } +} diff --git a/tests/mir-opt/precise_liveness.dereference.dataflow.0.mir b/tests/mir-opt/precise_liveness.dereference.dataflow.0.mir new file mode 100644 index 0000000000000..bb00f3aaf5247 --- /dev/null +++ b/tests/mir-opt/precise_liveness.dereference.dataflow.0.mir @@ -0,0 +1,14 @@ +// MIR for `dereference` 0 dataflow + +fn dereference(_1: *mut u32) -> () { + let mut _0: (); + + bb0: { + // early: [_1] + // late: [] + (*_1) = const 1_u32; + // early: [] + // late: [] + return; + } +} diff --git a/tests/mir-opt/precise_liveness.fields.dataflow.0.mir b/tests/mir-opt/precise_liveness.fields.dataflow.0.mir new file mode 100644 index 0000000000000..2734ebac6301f --- /dev/null +++ b/tests/mir-opt/precise_liveness.fields.dataflow.0.mir @@ -0,0 +1,25 @@ +// MIR for `fields` 0 dataflow + +fn fields() -> u32 { + let mut _0: u32; + let mut _1: (u32, u32); + let mut _2: *const (u32, u32); + + bb0: { + // early: [] + // late: [_1] + (_1.0: u32) = const 1_u32; + // early: [_1] + // late: [_1] + (_1.1: u32) = const 2_u32; + // early: [_1] + // late: [_1, _2] + _2 = &raw const _1; + // early: [_1] + // late: [_0, _1] + _0 = Add(move (_1.0: u32), move (_1.1: u32)); + // early: [_0] + // late: [] + return; + } +} diff --git a/tests/mir-opt/precise_liveness.indexed.dataflow.0.mir b/tests/mir-opt/precise_liveness.indexed.dataflow.0.mir new file mode 100644 index 0000000000000..e36103a330aab --- /dev/null +++ b/tests/mir-opt/precise_liveness.indexed.dataflow.0.mir @@ -0,0 +1,15 @@ +// MIR for `indexed` 0 dataflow + +fn indexed(_1: usize) -> () { + let mut _0: (); + let mut _2: [u32; 2]; + + bb0: { + // early: [_1] + // late: [_2] + _2[_1] = const 1_u32; + // early: [] + // late: [] + return; + } +} diff --git a/tests/mir-opt/precise_liveness.rs b/tests/mir-opt/precise_liveness.rs new file mode 100644 index 0000000000000..783da35d037d0 --- /dev/null +++ b/tests/mir-opt/precise_liveness.rs @@ -0,0 +1,357 @@ +//@ needs-unwind + +#![feature(core_intrinsics, custom_mir, rustc_attrs)] +#![crate_type = "lib"] + +use std::intrinsics::mir::*; + +// A non-borrowed local dies on its last use. +// EMIT_MIR precise_liveness.unborrowed.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn unborrowed() -> u32 { + // CHECK-LABEL: fn unborrowed( + // CHECK: [[A:_[0-9]+]] = const 1_u32; + // CHECK: [[B:_[0-9]+]] = const 2_u32; + // CHECK: // early: [[[A]], [[B]]] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: _0 = Add(copy [[A]], move [[B]]); + mir! { + let a: u32; + let b: u32; + { + a = 1; + b = 2; + RET = a + Move(b); + Return() + } + } +} + +// A borrowed local only dies on move. +// EMIT_MIR precise_liveness.borrowed.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn borrowed() -> u32 { + // CHECK-LABEL: fn borrowed( + // CHECK: [[A:_[0-9]+]] = const 1_u32; + // CHECK: [[B:_[0-9]+]] = const 2_u32; + // CHECK: = &raw const [[B]]; + // CHECK-NEXT: // early: [[[A]], [[B]]] + // CHECK-NEXT: // late: [_0, [[A]]] + // CHECK-NEXT: _0 = Add(copy [[A]], move [[B]]); + mir! { + let a: u32; + let b: u32; + let a_ptr: *const u32; + let b_ptr: *const u32; + { + a = 1; + b = 2; + a_ptr = &raw const a; + b_ptr = &raw const b; + RET = a + Move(b); + Return() + } + } +} + +// StorageDead kills a lifetime, but it can be re-initialized later. +// EMIT_MIR precise_liveness.storage.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn storage() -> u32 { + // CHECK-LABEL: fn storage( + // CHECK: // early: [_0, [[BORROWED:_[0-9]+]]] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: StorageDead([[BORROWED]]); + // CHECK-NEXT: // early: [_0] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: StorageLive([[BORROWED]]); + // CHECK-NEXT: // early: [_0] + // CHECK-NEXT: // late: [_0, [[BORROWED]]] + // CHECK-NEXT: [[BORROWED]] = const 3_u32; + // CHECK-NEXT: // early: [_0, [[BORROWED]]] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: StorageDead([[BORROWED]]); + // CHECK-NEXT: // early: [_0] + // CHECK-NEXT: // late: [] + // CHECK-NEXT: return; + mir! { + let borrowed: u32; + let pointer: *const u32; + { + StorageLive(borrowed); + borrowed = 2; + pointer = &raw const borrowed; + RET = borrowed; + StorageDead(borrowed); + StorageLive(borrowed); + borrowed = 3; + StorageDead(borrowed); + Return() + } + } +} + +// Partial initialization starts a lifetime, but only a bare local move ends it. +// EMIT_MIR precise_liveness.fields.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn fields() -> u32 { + // CHECK-LABEL: fn fields( + // CHECK: // early: [] + // CHECK-NEXT: // late: [[[AGGREGATE:_[0-9]+]]] + // CHECK-NEXT: ([[AGGREGATE]].0: u32) = const 1_u32; + // CHECK: = &raw const [[AGGREGATE]]; + // CHECK-NEXT: // early: [[[AGGREGATE]]] + // CHECK-NEXT: // late: [_0, [[AGGREGATE]]] + // CHECK-NEXT: _0 = Add(move ([[AGGREGATE]].0: u32), move ([[AGGREGATE]].1: u32)); + mir! { + let aggregate: (u32, u32); + let pointer: *const (u32, u32); + { + aggregate.0 = 1; + aggregate.1 = 2; + pointer = &raw const aggregate; + RET = Move(aggregate.0) + Move(aggregate.1); + Return() + } + } +} + +unsafe extern "C-unwind" { + fn callee(a: u32, b: u32) -> u32; +} + +// Check the behavior around calls: +// - Destination is initialized at the late point. +// - Move arguments have their live range extended to the late point. +// - Copy arguments die at the early point when this is their last use. +// - Destination remains live on unwind edges. +// EMIT_MIR precise_liveness.call.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn call() -> u32 { + // CHECK-LABEL: fn call( + // CHECK: [[A:_[0-9]+]] = const 1_u32; + // CHECK: [[B:_[0-9]+]] = const 2_u32; + // CHECK-NEXT: // early: [[[A]], [[B]]] + // CHECK-NEXT: // late: [[[A]], [[DESTINATION:_[0-9]+]]] + // CHECK-NEXT: [[DESTINATION]] = callee(move [[A]], copy [[B]]) + // CHECK: = &raw const [[DESTINATION]]; + // CHECK-NEXT: // early: [[[DESTINATION]]] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: _0 = move [[DESTINATION]]; + // CHECK: (cleanup): { + // CHECK-NEXT: // early: [[[DESTINATION]]] + // CHECK-NEXT: // late: [[[DESTINATION]]] + // CHECK-NEXT: resume; + mir! { + let a: u32; + let b: u32; + let destination: u32; + let pointer: *const u32; + { + a = 1; + b = 2; + Call(destination = callee(Move(a), b), ReturnTo(done), UnwindCleanup(cleanup)) + } + done = { + pointer = &raw const destination; + RET = Move(destination); + Return() + } + cleanup(cleanup) = { + UnwindResume() + } + } +} + +// A value used on only one branch is not live on the other branch. +// EMIT_MIR precise_liveness.branch.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn branch(condition: bool) -> u32 { + // CHECK-LABEL: fn branch( + // CHECK: [[VALUE:_[0-9]+]] = const 1_u32; + // CHECK-NEXT: // early: [[[CONDITION:_[0-9]+]], [[VALUE]]] + // CHECK-NEXT: // late: [[[VALUE]]] + // CHECK-NEXT: switchInt(copy [[CONDITION]]) -> [1: [[USE:bb[0-9]+]], otherwise: [[SKIP:bb[0-9]+]]]; + // CHECK: [[USE]]: { + // CHECK-NEXT: // early: [[[VALUE]]] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: _0 = move [[VALUE]]; + // CHECK: [[SKIP]]: { + // CHECK-NEXT: // early: [] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: _0 = const 0_u32; + mir! { + let value: u32; + { + value = 1; + match condition { true => use_value, _ => skip_value } + } + use_value = { + RET = Move(value); + Return() + } + skip_value = { + RET = 0; + Return() + } + } +} + +// Dead destinations are live only at the late point of the assignment or call. +// EMIT_MIR precise_liveness.dead_destinations.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn dead_destinations() -> u32 { + // CHECK-LABEL: fn dead_destinations( + // CHECK: // early: [] + // CHECK-NEXT: // late: [[[ASSIGNMENT:_[0-9]+]]] + // CHECK-NEXT: [[ASSIGNMENT]] = const 1_u32; + // CHECK-NEXT: // early: [] + // CHECK-NEXT: // late: [[[DESTINATION:_[0-9]+]]] + // CHECK-NEXT: [[DESTINATION]] = callee(const 1_u32, const 2_u32) -> [return: [[DONE:bb[0-9]+]], unwind: [[CLEANUP:bb[0-9]+]]]; + // CHECK: [[DONE]]: { + // CHECK-NEXT: // early: [] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: _0 = const 0_u32; + // CHECK: [[CLEANUP]] (cleanup): { + // CHECK-NEXT: // early: [] + // CHECK-NEXT: // late: [] + // CHECK-NEXT: resume; + mir! { + let assignment: u32; + let destination: u32; + { + assignment = 1; + Call(destination = callee(1, 2), ReturnTo(done), UnwindCleanup(cleanup)) + } + done = { + RET = 0; + Return() + } + cleanup(cleanup) = { + UnwindResume() + } + } +} + +// An indexed write allocates the array. The index is a use, not a destination. +// EMIT_MIR precise_liveness.indexed.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn indexed(index: usize) { + // CHECK-LABEL: fn indexed( + // CHECK: // early: [[[INDEX:_[0-9]+]]] + // CHECK-NEXT: // late: [[[ARRAY:_[0-9]+]]] + // CHECK-NEXT: [[ARRAY]][[[INDEX]]] = const 1_u32; + mir! { + let array: [u32; 2]; + { + array[index] = 1; + Return() + } + } +} + +// A write through a pointer is a use of the pointer, not a def. +// EMIT_MIR precise_liveness.dereference.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn dereference(pointer: *mut u32) { + // CHECK-LABEL: fn dereference( + // CHECK: // early: [[[POINTER:_[0-9]+]]] + // CHECK-NEXT: // late: [] + // CHECK-NEXT: (*[[POINTER]]) = const 1_u32; + mir! { + { + *pointer = 1; + Return() + } + } +} + +// A field move keeps the base allocation live through the call's late point. +// EMIT_MIR precise_liveness.call_field.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn call_field() -> u32 { + // CHECK-LABEL: fn call_field( + // CHECK: ([[AGGREGATE:_[0-9]+]].0: u32) = const 1_u32; + // CHECK-NEXT: // early: [[[AGGREGATE]]] + // CHECK-NEXT: // late: [_0, [[AGGREGATE]]] + // CHECK-NEXT: _0 = callee(move ([[AGGREGATE]].0: u32), const 0_u32) + // CHECK: // early: [_0] + // CHECK-NEXT: // late: [] + // CHECK-NEXT: return; + mir! { + let aggregate: (u32,); + { + aggregate.0 = 1; + Call(RET = callee(Move(aggregate.0), 0), ReturnTo(done), UnwindUnreachable()) + } + done = { + Return() + } + } +} + +// A borrowed field's base remains live in both successors after a field move. +// EMIT_MIR precise_liveness.call_borrowed_field.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn call_borrowed_field() -> u32 { + // CHECK-LABEL: fn call_borrowed_field( + // CHECK: = &raw const [[AGGREGATE:_[0-9]+]]; + // CHECK-NEXT: // early: [[[AGGREGATE]]] + // CHECK-NEXT: // late: [[[AGGREGATE]], [[DESTINATION:_[0-9]+]]] + // CHECK-NEXT: [[DESTINATION]] = callee(move ([[AGGREGATE]].0: u32), const 0_u32) -> [return: [[DONE:bb[0-9]+]], unwind: [[CLEANUP:bb[0-9]+]]]; + // CHECK: [[DONE]]: { + // CHECK-NEXT: // early: [[[AGGREGATE]], [[DESTINATION]]] + // CHECK-NEXT: // late: [_0, [[AGGREGATE]]] + // CHECK-NEXT: _0 = move [[DESTINATION]]; + // CHECK: [[CLEANUP]] (cleanup): { + // CHECK-NEXT: // early: [[[AGGREGATE]]] + // CHECK-NEXT: // late: [[[AGGREGATE]]] + // CHECK-NEXT: resume; + mir! { + let aggregate: (u32,); + let destination: u32; + let borrow: *const (u32,); + { + aggregate.0 = 1; + borrow = &raw const aggregate; + Call(destination = callee(Move(aggregate.0), 0), ReturnTo(done), UnwindCleanup(cleanup)) + } + done = { + RET = Move(destination); + Return() + } + cleanup(cleanup) = { + UnwindResume() + } + } +} + +// An indirect move does not extend the pointer local's lifetime to the late point. +// EMIT_MIR precise_liveness.call_indirect.dataflow.0.mir +#[custom_mir(dialect = "runtime", phase = "optimized")] +#[rustc_mir(rustc_pretty_precise_liveness)] +pub fn call_indirect(pointer: *const u32) -> u32 { + // CHECK-LABEL: fn call_indirect( + // CHECK: // early: [[[POINTER:_[0-9]+]]] + // CHECK-NEXT: // late: [_0] + // CHECK-NEXT: _0 = callee(move (*[[POINTER]]), const 0_u32) + mir! { + { + Call(RET = callee(Move(*pointer), 0), ReturnTo(done), UnwindUnreachable()) + } + done = { + Return() + } + } +} diff --git a/tests/mir-opt/precise_liveness.storage.dataflow.0.mir b/tests/mir-opt/precise_liveness.storage.dataflow.0.mir new file mode 100644 index 0000000000000..56ae734839ee9 --- /dev/null +++ b/tests/mir-opt/precise_liveness.storage.dataflow.0.mir @@ -0,0 +1,37 @@ +// MIR for `storage` 0 dataflow + +fn storage() -> u32 { + let mut _0: u32; + let mut _1: u32; + let mut _2: *const u32; + + bb0: { + // early: [] + // late: [] + StorageLive(_1); + // early: [] + // late: [_1] + _1 = const 2_u32; + // early: [_1] + // late: [_1, _2] + _2 = &raw const _1; + // early: [_1] + // late: [_0, _1] + _0 = copy _1; + // early: [_0, _1] + // late: [_0] + StorageDead(_1); + // early: [_0] + // late: [_0] + StorageLive(_1); + // early: [_0] + // late: [_0, _1] + _1 = const 3_u32; + // early: [_0, _1] + // late: [_0] + StorageDead(_1); + // early: [_0] + // late: [] + return; + } +} diff --git a/tests/mir-opt/precise_liveness.unborrowed.dataflow.0.mir b/tests/mir-opt/precise_liveness.unborrowed.dataflow.0.mir new file mode 100644 index 0000000000000..4a27e7b0f9d5a --- /dev/null +++ b/tests/mir-opt/precise_liveness.unborrowed.dataflow.0.mir @@ -0,0 +1,22 @@ +// MIR for `unborrowed` 0 dataflow + +fn unborrowed() -> u32 { + let mut _0: u32; + let mut _1: u32; + let mut _2: u32; + + bb0: { + // early: [] + // late: [_1] + _1 = const 1_u32; + // early: [_1] + // late: [_1, _2] + _2 = const 2_u32; + // early: [_1, _2] + // late: [_0] + _0 = Add(copy _1, move _2); + // early: [_0] + // late: [] + return; + } +} From 9121436bb6ec2c34462bbc9d54e933bc6c759c14 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 9 Oct 2026 14:31:38 +1100 Subject: [PATCH 15/21] Replace `CanonicalVarValues` with `I::GenericArgs` in some places `CanonicalVarValues` is serving two roles. There are the places where it's a real substitution (e.g. return value of `instantiate`), and places where it's just a result never used for instantiation (e.g. `Response` and `inspect::State` and `EvalCtxt`). The latter can just be `I::GenericArgs`. Simplifications from this: - Removes some `var_values.var_values` chains. - `make_identity` can be inlined into its single caller. - `CanonicalVarValues::is_identity*` can be moved to methods of `Response`. - `CanonicalVarValues::dummy` is no longer needed. --- .../src/canonical/mod.rs | 38 ++++++--- .../src/solve/eval_ctxt/mod.rs | 15 ++-- .../src/solve/inspect/build.rs | 4 +- .../rustc_next_trait_solver/src/solve/mod.rs | 4 +- .../src/solve/trait_goals.rs | 2 +- compiler/rustc_type_ir/src/canonical.rs | 81 +------------------ compiler/rustc_type_ir/src/solve/inspect.rs | 4 +- compiler/rustc_type_ir/src/solve/mod.rs | 56 ++++++++++++- 8 files changed, 98 insertions(+), 106 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 655ab64145db5..00dfc146db50a 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -33,17 +33,17 @@ use crate::solve::{ pub mod canonicalizer; trait ResponseT { - fn var_values(&self) -> CanonicalVarValues; + fn var_values(&self) -> I::GenericArgs; } impl ResponseT for Response { - fn var_values(&self) -> CanonicalVarValues { + fn var_values(&self) -> I::GenericArgs { self.var_values } } impl ResponseT for inspect::State { - fn var_values(&self) -> CanonicalVarValues { + fn var_values(&self) -> I::GenericArgs { self.var_values } } @@ -210,7 +210,7 @@ where // We therefore instantiate the existential variable in the canonical response with the // inference variable of the input right away, which is more performant. let mut opt_values = IndexVec::from_elem_n(None, response.var_kinds.len()); - for (original_value, result_value) in iter::zip(original_values, var_values.var_values.iter()) { + for (original_value, result_value) in iter::zip(original_values, var_values.iter()) { match result_value.kind() { ty::GenericArgKind::Type(t) => { // We disable the instantiation guess for inference variables @@ -490,7 +490,7 @@ where fn unify_query_var_values( delegate: &D, original_values: &[I::GenericArg], - var_values: CanonicalVarValues, + var_values: I::GenericArgs, span: I::Span, ) where D: SolverDelegate, @@ -498,7 +498,7 @@ fn unify_query_var_values( { assert_eq!(original_values.len(), var_values.len()); - for (&orig, response) in iter::zip(original_values, var_values.var_values.iter()) { + for (&orig, response) in iter::zip(original_values, var_values.iter()) { let mut must_eq = ResponseRelating::new(&**delegate, span); must_eq.relate(orig, response).unwrap(); } @@ -564,7 +564,7 @@ where I: Interner, T: TypeFoldable, { - let var_values = CanonicalVarValues { var_values: delegate.cx().mk_args(var_values) }; + let var_values = delegate.cx().mk_args(var_values); let state = inspect::State { var_values, data }; let state = delegate.deeply_resolve_via_unification_table(state); Canonicalizer::canonicalize_response(delegate, max_input_universe, state) @@ -591,7 +591,7 @@ where delegate.create_next_universe(); } orig_values.extend( - state.value.var_values.var_values.as_slice()[orig_values.len()..] + state.value.var_values.as_slice()[orig_values.len()..] .iter() .map(|&arg| delegate.fresh_var_for_kind(arg, span, max_universe)), ); @@ -616,11 +616,31 @@ pub fn response_no_constraints_raw( var_kinds: I::CanonicalVarKinds, certainty: Certainty, ) -> CanonicalResponse { + // Given a list of canonical variables, construct a set of values which are + // the identity response. + let var_values = + cx.mk_args_from_iter(var_kinds.iter().enumerate().map(|(i, kind)| -> I::GenericArg { + match kind { + CanonicalVarKind::Ty { .. } + | CanonicalVarKind::Int + | CanonicalVarKind::Float + | CanonicalVarKind::PlaceholderTy(_) => { + Ty::new_canonical_bound(cx, ty::BoundVar::from_usize(i)).into() + } + CanonicalVarKind::Region(_) | CanonicalVarKind::PlaceholderRegion(_) => { + Region::new_canonical_bound(cx, ty::BoundVar::from_usize(i)).into() + } + CanonicalVarKind::Const(_) | CanonicalVarKind::PlaceholderConst(_) => { + Const::new_canonical_bound(cx, ty::BoundVar::from_usize(i)).into() + } + } + })); + ty::Canonical { max_universe, var_kinds, value: Response { - var_values: ty::CanonicalVarValues::make_identity(cx, var_kinds), + var_values, // FIXME: maybe we should store the "no response" version in cx, like // we do for cx.types and stuff. external_constraints: cx.mk_external_constraints(ExternalConstraintsData::new(cx)), diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 044dc89978124..f97bc0bbeb626 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -17,9 +17,9 @@ use rustc_type_ir::solve::{ RerunNonErased, RerunReason, RerunResultExt, SmallCopySet, TyOrConstInferVar, }; use rustc_type_ir::{ - self as ty, CanonicalVarValues, ClauseKind, Const, InferCtxtLike, Interner, MayBeErased, - OpaqueTypeKey, PredicateKind, PredicateProxy, Region, RegionVid, TypeFoldable, - TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, max_universe, + self as ty, ClauseKind, Const, InferCtxtLike, Interner, MayBeErased, OpaqueTypeKey, + PredicateKind, PredicateProxy, Region, RegionVid, TypeFoldable, TypeSuperVisitable, + TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, max_universe, }; use thin_vec::ThinVec; use tracing::{Level, debug, instrument, trace, warn}; @@ -129,7 +129,7 @@ where /// What kind of goal we're currently computing, see the enum definition /// for more info. current_goal_kind: CurrentGoalKind, - pub(super) var_values: CanonicalVarValues, + pub(super) var_values: I::GenericArgs, /// The highest universe index nameable by the caller. /// @@ -479,7 +479,7 @@ where max_input_universe: ty::UniverseIndex::ROOT, initial_opaque_types_storage_num_entries: Default::default(), var_kinds: Default::default(), - var_values: CanonicalVarValues::dummy(), + var_values: Default::default(), current_goal_kind: CurrentGoalKind::Misc, origin_span, tainted: Ok(()), @@ -513,6 +513,7 @@ where ) -> Result, ) -> (Result, AccessedOpaques) { let (ref delegate, input, var_values) = D::build_with_canonical(cx, &canonical_input); + let var_values = var_values.var_values; for (key, ty) in input.predefined_opaques_in_body.iter() { let prev = delegate.register_hidden_type_in_storage(key, ty, I::Span::dummy()); // It may be possible that two entries in the opaque type storage end up @@ -1707,7 +1708,7 @@ where r.retain(|(outlives, _)| !outlives.is_trivial() && unique.insert(*outlives)); } - filter_irrelevant_region_constraints(self.delegate, &var_values, &mut external_constraints); + filter_irrelevant_region_constraints(self.delegate, var_values, &mut external_constraints); let canonical = canonicalize_response( self.delegate, @@ -1826,7 +1827,7 @@ where fn filter_irrelevant_region_constraints( delegate: &D, - var_values: &CanonicalVarValues, + var_values: I::GenericArgs, external_constraints: &mut ExternalConstraintsData, ) where D: SolverDelegate, diff --git a/compiler/rustc_next_trait_solver/src/solve/inspect/build.rs b/compiler/rustc_next_trait_solver/src/solve/inspect/build.rs index 4369148baf91d..9584e16d68725 100644 --- a/compiler/rustc_next_trait_solver/src/solve/inspect/build.rs +++ b/compiler/rustc_next_trait_solver/src/solve/inspect/build.rs @@ -46,14 +46,14 @@ impl, I: Interner> ProofTreeBuilder { pub(crate) fn new_evaluation_step( &mut self, - var_values: ty::CanonicalVarValues, + var_values: I::GenericArgs, ) -> EvaluationStepBuilder { if self.is_noop() { EvaluationStepBuilder { state: None, _infcx: PhantomData } } else { EvaluationStepBuilder { state: Some(Box::new(WipEvaluationStep { - var_values: var_values.var_values.to_vec(), + var_values: var_values.to_vec(), evaluation: WipProbe { initial_num_var_values: var_values.len(), steps: vec![], diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 099c755d78427..9ed4963577458 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -62,7 +62,7 @@ fn has_no_inference_or_external_constraints( ref opaque_types, ref normalization_nested_goals, } = *response.value.external_constraints; - response.value.var_values.is_identity() + response.value.is_identity() && region_constraints.is_empty() && opaque_types.is_empty() && normalization_nested_goals.is_empty() @@ -74,7 +74,7 @@ fn has_only_region_constraints(response: ty::Canonical