From 786777fe4bb1e62088c8cdab24bb6e926d4c1235 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sat, 7 Feb 2026 12:37:00 +0100 Subject: [PATCH 01/14] Fix an ICE in the vtable iteration for a trait reference in const eval when a supertrait not implemented compiler/rustc_trait_selection/src/traits/vtable.rs@`vtable_entries`: The impossible predicates check in `vtable_entries` used `instantiate_own` which only includes the method's own where-clauses, not the parent trait's bounds. Replace it with `instantiate_and_check_impossible_predicates` which also checks the trait ref itself, so unsatisfied supertrait bounds are caught and the method is marked `Vacant` instead of ICEing. --- compiler/rustc_trait_selection/src/traits/vtable.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/vtable.rs b/compiler/rustc_trait_selection/src/traits/vtable.rs index e3e65150a6d6c..9858e0112f4d3 100644 --- a/compiler/rustc_trait_selection/src/traits/vtable.rs +++ b/compiler/rustc_trait_selection/src/traits/vtable.rs @@ -13,7 +13,7 @@ use rustc_span::DUMMY_SP; use smallvec::{SmallVec, smallvec}; use tracing::debug; -use crate::traits::{impossible_predicates, is_vtable_safe_method}; +use crate::traits::is_vtable_safe_method; #[derive(Clone, Debug)] pub enum VtblSegment<'tcx> { @@ -283,11 +283,7 @@ fn vtable_entries<'tcx>( // do not hold for this particular set of type parameters. // Note that this method could then never be called, so we // do not want to try and codegen it, in that case (see #23435). - let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, args); - if impossible_predicates( - tcx, - predicates.map(|(predicate, _)| predicate.skip_norm_wip()).collect(), - ) { + if tcx.instantiate_and_check_impossible_predicates((def_id, args)) { debug!("vtable_entries: predicates do not hold"); return VtblEntry::Vacant; } From 35416455cb3f7ac4842cf1504a09f3f0298ca122 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Sat, 7 Feb 2026 12:42:59 +0100 Subject: [PATCH 02/14] Move tests from tests/crashes to tests/ui now that they don't ICE --- tests/crashes/137190-2.rs | 18 ------------- tests/crashes/137190-3.rs | 10 -------- .../vtable-impossible-predicates-async.rs} | 6 ++++- .../vtable-unsatisfied-supertrait-generics.rs | 25 +++++++++++++++++++ ...ble-unsatisfied-supertrait-generics.stderr | 20 +++++++++++++++ .../coercion/vtable-unsatisfied-supertrait.rs | 16 ++++++++++++ .../vtable-unsatisfied-supertrait.stderr | 20 +++++++++++++++ 7 files changed, 86 insertions(+), 29 deletions(-) delete mode 100644 tests/crashes/137190-2.rs delete mode 100644 tests/crashes/137190-3.rs rename tests/{crashes/135470.rs => ui/coercion/vtable-impossible-predicates-async.rs} (79%) create mode 100644 tests/ui/coercion/vtable-unsatisfied-supertrait-generics.rs create mode 100644 tests/ui/coercion/vtable-unsatisfied-supertrait-generics.stderr create mode 100644 tests/ui/coercion/vtable-unsatisfied-supertrait.rs create mode 100644 tests/ui/coercion/vtable-unsatisfied-supertrait.stderr diff --git a/tests/crashes/137190-2.rs b/tests/crashes/137190-2.rs deleted file mode 100644 index 0c68b5aa4a518..0000000000000 --- a/tests/crashes/137190-2.rs +++ /dev/null @@ -1,18 +0,0 @@ -//@ known-bug: #137190 -trait Supertrait { - fn method(&self) {} -} - -trait Trait

: Supertrait<()> {} - -impl

Trait

for () {} - -const fn upcast

(x: &dyn Trait

) -> &dyn Supertrait<()> { - x -} - -const fn foo() -> &'static dyn Supertrait<()> { - upcast::<()>(&()) -} - -const _: &'static dyn Supertrait<()> = foo(); diff --git a/tests/crashes/137190-3.rs b/tests/crashes/137190-3.rs deleted file mode 100644 index 88ae88e11bcdb..0000000000000 --- a/tests/crashes/137190-3.rs +++ /dev/null @@ -1,10 +0,0 @@ -//@ known-bug: #137190 -trait Supertrait { - fn method(&self) {} -} - -trait Trait: Supertrait {} - -impl Trait for () {} - -const _: &dyn Supertrait = &() as &dyn Trait as &dyn Supertrait; diff --git a/tests/crashes/135470.rs b/tests/ui/coercion/vtable-impossible-predicates-async.rs similarity index 79% rename from tests/crashes/135470.rs rename to tests/ui/coercion/vtable-impossible-predicates-async.rs index efa017b5457cf..fe6aa9843fc61 100644 --- a/tests/crashes/135470.rs +++ b/tests/ui/coercion/vtable-impossible-predicates-async.rs @@ -1,4 +1,8 @@ -//@ known-bug: #135470 +// Regression test for #135470. +// Verify that we don't ICE when building vtable entries +// for a blanket impl involving async and impossible predicates. + +//@ check-pass //@ compile-flags: -Copt-level=0 //@ edition: 2021 diff --git a/tests/ui/coercion/vtable-unsatisfied-supertrait-generics.rs b/tests/ui/coercion/vtable-unsatisfied-supertrait-generics.rs new file mode 100644 index 0000000000000..41e603fc0a0a3 --- /dev/null +++ b/tests/ui/coercion/vtable-unsatisfied-supertrait-generics.rs @@ -0,0 +1,25 @@ +// Regression test for #137190. +// Variant of vtable-unsatisfied-supertrait.rs with generic traits. +// Verify that we don't ICE when building vtable entries +// for a generic trait whose supertrait is not implemented. + +//@ compile-flags: --crate-type lib + +trait Supertrait { + fn method(&self) {} +} + +trait Trait

: Supertrait<()> {} + +impl

Trait

for () {} +//~^ ERROR the trait bound `(): Supertrait<()>` is not satisfied + +const fn upcast

(x: &dyn Trait

) -> &dyn Supertrait<()> { + x +} + +const fn foo() -> &'static dyn Supertrait<()> { + upcast::<()>(&()) +} + +const _: &'static dyn Supertrait<()> = foo(); diff --git a/tests/ui/coercion/vtable-unsatisfied-supertrait-generics.stderr b/tests/ui/coercion/vtable-unsatisfied-supertrait-generics.stderr new file mode 100644 index 0000000000000..a485d2d539534 --- /dev/null +++ b/tests/ui/coercion/vtable-unsatisfied-supertrait-generics.stderr @@ -0,0 +1,20 @@ +error[E0277]: the trait bound `(): Supertrait<()>` is not satisfied + --> $DIR/vtable-unsatisfied-supertrait-generics.rs:14:22 + | +LL | impl

Trait

for () {} + | ^^ the trait `Supertrait<()>` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/vtable-unsatisfied-supertrait-generics.rs:8:1 + | +LL | trait Supertrait { + | ^^^^^^^^^^^^^^^^^^^ +note: required by a bound in `Trait` + --> $DIR/vtable-unsatisfied-supertrait-generics.rs:12:17 + | +LL | trait Trait

: Supertrait<()> {} + | ^^^^^^^^^^^^^^ required by this bound in `Trait` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/coercion/vtable-unsatisfied-supertrait.rs b/tests/ui/coercion/vtable-unsatisfied-supertrait.rs new file mode 100644 index 0000000000000..26a83d2fb0661 --- /dev/null +++ b/tests/ui/coercion/vtable-unsatisfied-supertrait.rs @@ -0,0 +1,16 @@ +// Regression test for #137190. +// Verify that we don't ICE when building vtable entries +// for a trait whose supertrait is not implemented. + +//@ compile-flags: --crate-type lib + +trait Supertrait { + fn method(&self) {} +} + +trait Trait: Supertrait {} + +impl Trait for () {} +//~^ ERROR the trait bound `(): Supertrait` is not satisfied + +const _: &dyn Supertrait = &() as &dyn Trait as &dyn Supertrait; diff --git a/tests/ui/coercion/vtable-unsatisfied-supertrait.stderr b/tests/ui/coercion/vtable-unsatisfied-supertrait.stderr new file mode 100644 index 0000000000000..7df2c95c7facc --- /dev/null +++ b/tests/ui/coercion/vtable-unsatisfied-supertrait.stderr @@ -0,0 +1,20 @@ +error[E0277]: the trait bound `(): Supertrait` is not satisfied + --> $DIR/vtable-unsatisfied-supertrait.rs:13:16 + | +LL | impl Trait for () {} + | ^^ the trait `Supertrait` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/vtable-unsatisfied-supertrait.rs:7:1 + | +LL | trait Supertrait { + | ^^^^^^^^^^^^^^^^ +note: required by a bound in `Trait` + --> $DIR/vtable-unsatisfied-supertrait.rs:11:14 + | +LL | trait Trait: Supertrait {} + | ^^^^^^^^^^ required by this bound in `Trait` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From f8618d0de595e8e454eddd810a1abeeb51bac5d9 Mon Sep 17 00:00:00 2001 From: "Mark Z. Ding" Date: Mon, 11 May 2026 22:49:17 -0400 Subject: [PATCH 03/14] Report AsyncFn traits in async closure lifetime note --- .../src/diagnostics/region_name.rs | 41 +++++++++++-------- .../async-closures/not-lending.stderr | 4 +- ...sue-74072-lifetime-name-annotations.stderr | 4 +- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs index 963f902b71fcb..a00d39eea12d3 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs @@ -111,9 +111,12 @@ impl RegionName { | RegionNameSource::NamedEarlyParamRegion(span) => { diag.span_label(*span, format!("lifetime `{self}` defined here")); } - RegionNameSource::SynthesizedFreeEnvRegion(span, note) => { + RegionNameSource::SynthesizedFreeEnvRegion(span, closure_trait) => { diag.span_label(*span, format!("lifetime `{self}` represents this closure's body")); - diag.note(*note); + diag.note(format!( + "closure implements `{closure_trait}`, so references to captured variables \ + can't escape the closure" + )); } RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::CannotMatchHirTy( span, @@ -326,9 +329,15 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { ty::LateParamRegionKind::ClosureEnv => { let def_ty = self.regioncx.universal_regions().defining_ty; - let closure_kind = match def_ty { - DefiningTy::Closure(_, args) => args.as_closure().kind(), - DefiningTy::CoroutineClosure(_, args) => args.as_coroutine_closure().kind(), + let (is_lending_coroutine_closure, closure_kind) = match def_ty { + DefiningTy::Closure(_, args) => (false, args.as_closure().kind()), + DefiningTy::CoroutineClosure(_, args) => { + let args = args.as_coroutine_closure(); + ( + !args.tupled_upvars_ty().is_ty_var() && args.has_self_borrows(), + args.kind(), + ) + } _ => { // Can't have BrEnv in functions, constants or coroutines. bug!("BrEnv outside of closure."); @@ -340,23 +349,19 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { bug!("Closure is not defined by a closure expr"); }; let region_name = self.synthesize_region_name(); - let note = match closure_kind { - ty::ClosureKind::Fn => { - "closure implements `Fn`, so references to captured variables \ - can't escape the closure" - } - ty::ClosureKind::FnMut => { - "closure implements `FnMut`, so references to captured variables \ - can't escape the closure" - } - ty::ClosureKind::FnOnce => { - bug!("BrEnv in a `FnOnce` closure"); - } + let closure_trait = match (is_lending_coroutine_closure, closure_kind) { + (false, kind) => kind.as_str(), + (true, ty::ClosureKind::Fn) => "AsyncFn", + (true, ty::ClosureKind::FnMut) => "AsyncFnMut", + (true, ty::ClosureKind::FnOnce) => "AsyncFnOnce", }; Some(RegionName { name: region_name, - source: RegionNameSource::SynthesizedFreeEnvRegion(fn_decl_span, note), + source: RegionNameSource::SynthesizedFreeEnvRegion( + fn_decl_span, + closure_trait, + ), }) } diff --git a/tests/ui/async-await/async-closures/not-lending.stderr b/tests/ui/async-await/async-closures/not-lending.stderr index f0028129caa02..fb941502d3646 100644 --- a/tests/ui/async-await/async-closures/not-lending.stderr +++ b/tests/ui/async-await/async-closures/not-lending.stderr @@ -7,7 +7,7 @@ LL | let x = async move || -> &String { &s }; | | return type of async closure `{async closure body@$DIR/not-lending.rs:12:42: 12:48}` contains a lifetime `'2` | lifetime `'1` represents this closure's body | - = note: closure implements `Fn`, so references to captured variables can't escape the closure + = note: closure implements `AsyncFn`, so references to captured variables can't escape the closure error: lifetime may not live long enough --> $DIR/not-lending.rs:16:31 @@ -18,7 +18,7 @@ LL | let x = async move || { &s }; | | return type of async closure `{async closure body@$DIR/not-lending.rs:16:31: 16:37}` contains a lifetime `'2` | lifetime `'1` represents this closure's body | - = note: closure implements `Fn`, so references to captured variables can't escape the closure + = note: closure implements `AsyncFn`, so references to captured variables can't escape the closure error: aborting due to 2 previous errors diff --git a/tests/ui/async-await/issue-74072-lifetime-name-annotations.stderr b/tests/ui/async-await/issue-74072-lifetime-name-annotations.stderr index e1f268116fc54..c479adfa56d7e 100644 --- a/tests/ui/async-await/issue-74072-lifetime-name-annotations.stderr +++ b/tests/ui/async-await/issue-74072-lifetime-name-annotations.stderr @@ -39,7 +39,7 @@ LL | | y LL | | })() | |_____^ returning this value requires that `'1` must outlive `'2` | - = note: closure implements `FnMut`, so references to captured variables can't escape the closure + = note: closure implements `AsyncFnMut`, so references to captured variables can't escape the closure error[E0716]: temporary value dropped while borrowed --> $DIR/issue-74072-lifetime-name-annotations.rs:13:5 @@ -88,7 +88,7 @@ LL | | y LL | | })() | |_____^ returning this value requires that `'1` must outlive `'2` | - = note: closure implements `FnMut`, so references to captured variables can't escape the closure + = note: closure implements `AsyncFnMut`, so references to captured variables can't escape the closure error[E0716]: temporary value dropped while borrowed --> $DIR/issue-74072-lifetime-name-annotations.rs:23:5 From b6422f74b1fc8cc52ac42d993cec880a8661ff06 Mon Sep 17 00:00:00 2001 From: Soowon Jeong Date: Thu, 7 May 2026 14:13:23 +0900 Subject: [PATCH 04/14] Equate captures-by-ref ty to an error type when args reference errors Upvar inference unconditionally unifies `tupled_upvars_ty` (via `demand_suptype` near the bottom of `analyze_closure`), but skipped unifying `coroutine_captures_by_ref_ty` whenever the closure args already referenced an error. That left the captures-by-ref inference variable unconstrained for tainted coroutine-closures, breaking the invariant `has_self_borrows` relies on (`FnPtr` or `Error`). If the body contains an unresolved `as _` cast, e.g. ```rust //@ edition:2021 fn needs_fn_mut(x: impl FnMut() -> T) { needs_fn_mut(async || x as _) } ``` selection later reaches `has_self_borrows` and trips its `_ => panic!()` arm. Always equate `coroutine_captures_by_ref_ty` in the error path -- to `Ty::new_error(guar)` -- so the invariant holds and downstream consumers can rely on the inference variable being resolved. --- compiler/rustc_hir_typeck/src/upvar.rs | 176 ++++++++++-------- .../cast-as-infer-self-borrows.rs | 10 + .../cast-as-infer-self-borrows.stderr | 9 + 3 files changed, 115 insertions(+), 80 deletions(-) create mode 100644 tests/ui/async-await/async-closures/cast-as-infer-self-borrows.rs create mode 100644 tests/ui/async-await/async-closures/cast-as-infer-self-borrows.stderr diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index dfdc7c3d8df0a..9d2a513988f5e 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -378,92 +378,108 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // For coroutine-closures, we additionally must compute the // `coroutine_captures_by_ref_ty` type, which is used to generate the by-ref // version of the coroutine-closure's output coroutine. - if let UpvarArgs::CoroutineClosure(args) = args - && !args.references_error() - { - let closure_env_region: ty::Region<'_> = ty::Region::new_bound( - self.tcx, - ty::INNERMOST, - ty::BoundRegion { var: ty::BoundVar::ZERO, kind: ty::BoundRegionKind::ClosureEnv }, - ); - - let num_args = args - .as_coroutine_closure() - .coroutine_closure_sig() - .skip_binder() - .tupled_inputs_ty - .tuple_fields() - .len(); - let typeck_results = self.typeck_results.borrow(); + // + // If the args already reference an error, computing the by-ref upvar + // tuple may itself reach malformed types. We still equate the + // `coroutine_captures_by_ref_ty` inference variable to an error type + // so downstream consumers (e.g. `has_self_borrows`) can rely on it + // being resolved to either an `FnPtr` or `Error` rather than remaining + // an unconstrained inference variable. + if let UpvarArgs::CoroutineClosure(args) = args { + if let Some(guar) = args.error_reported().err() { + self.demand_eqtype( + span, + args.as_coroutine_closure().coroutine_captures_by_ref_ty(), + Ty::new_error(self.tcx, guar), + ); + } else { + let closure_env_region: ty::Region<'_> = ty::Region::new_bound( + self.tcx, + ty::INNERMOST, + ty::BoundRegion { + var: ty::BoundVar::ZERO, + kind: ty::BoundRegionKind::ClosureEnv, + }, + ); - let tupled_upvars_ty_for_borrow = Ty::new_tup_from_iter( - self.tcx, - ty::analyze_coroutine_closure_captures( - typeck_results.closure_min_captures_flattened(closure_def_id), - typeck_results - .closure_min_captures_flattened( - self.tcx.coroutine_for_closure(closure_def_id).expect_local(), - ) - // Skip the captures that are just moving the closure's args - // into the coroutine. These are always by move, and we append - // those later in the `CoroutineClosureSignature` helper functions. - .skip(num_args), - |(_, parent_capture), (_, child_capture)| { - // This is subtle. See documentation on function. - let needs_ref = should_reborrow_from_env_of_parent_coroutine_closure( - parent_capture, - child_capture, - ); + let num_args = args + .as_coroutine_closure() + .coroutine_closure_sig() + .skip_binder() + .tupled_inputs_ty + .tuple_fields() + .len(); + let typeck_results = self.typeck_results.borrow(); - let upvar_ty = child_capture.place.ty(); - let capture = child_capture.info.capture_kind; - // Not all upvars are captured by ref, so use - // `apply_capture_kind_on_capture_ty` to ensure that we - // compute the right captured type. - apply_capture_kind_on_capture_ty( - self.tcx, - upvar_ty, - capture, - if needs_ref { - closure_env_region - } else { - self.tcx.lifetimes.re_erased - }, - ) - }, - ), - ); - let coroutine_captures_by_ref_ty = Ty::new_fn_ptr( - self.tcx, - ty::Binder::bind_with_vars( - self.tcx.mk_fn_sig_safe_rust_abi([], tupled_upvars_ty_for_borrow), - self.tcx.mk_bound_variable_kinds(&[ty::BoundVariableKind::Region( - ty::BoundRegionKind::ClosureEnv, - )]), - ), - ); - self.demand_eqtype( - span, - args.as_coroutine_closure().coroutine_captures_by_ref_ty(), - coroutine_captures_by_ref_ty, - ); + let tupled_upvars_ty_for_borrow = Ty::new_tup_from_iter( + self.tcx, + ty::analyze_coroutine_closure_captures( + typeck_results.closure_min_captures_flattened(closure_def_id), + typeck_results + .closure_min_captures_flattened( + self.tcx.coroutine_for_closure(closure_def_id).expect_local(), + ) + // Skip the captures that are just moving the closure's args + // into the coroutine. These are always by move, and we append + // those later in the `CoroutineClosureSignature` helper functions. + .skip(num_args), + |(_, parent_capture), (_, child_capture)| { + // This is subtle. See documentation on function. + let needs_ref = should_reborrow_from_env_of_parent_coroutine_closure( + parent_capture, + child_capture, + ); - // Additionally, we can now constrain the coroutine's kind type. - // - // We only do this if `infer_kind`, because if we have constrained - // the kind from closure signature inference, the kind inferred - // for the inner coroutine may actually be more restrictive. - if infer_kind { - let ty::Coroutine(_, coroutine_args) = - *self.typeck_results.borrow().expr_ty(body.value).kind() - else { - bug!(); - }; + let upvar_ty = child_capture.place.ty(); + let capture = child_capture.info.capture_kind; + // Not all upvars are captured by ref, so use + // `apply_capture_kind_on_capture_ty` to ensure that we + // compute the right captured type. + apply_capture_kind_on_capture_ty( + self.tcx, + upvar_ty, + capture, + if needs_ref { + closure_env_region + } else { + self.tcx.lifetimes.re_erased + }, + ) + }, + ), + ); + let coroutine_captures_by_ref_ty = Ty::new_fn_ptr( + self.tcx, + ty::Binder::bind_with_vars( + self.tcx.mk_fn_sig_safe_rust_abi([], tupled_upvars_ty_for_borrow), + self.tcx.mk_bound_variable_kinds(&[ty::BoundVariableKind::Region( + ty::BoundRegionKind::ClosureEnv, + )]), + ), + ); self.demand_eqtype( span, - coroutine_args.as_coroutine().kind_ty(), - Ty::from_coroutine_closure_kind(self.tcx, closure_kind), + args.as_coroutine_closure().coroutine_captures_by_ref_ty(), + coroutine_captures_by_ref_ty, ); + + // Additionally, we can now constrain the coroutine's kind type. + // + // We only do this if `infer_kind`, because if we have constrained + // the kind from closure signature inference, the kind inferred + // for the inner coroutine may actually be more restrictive. + if infer_kind { + let ty::Coroutine(_, coroutine_args) = + *self.typeck_results.borrow().expr_ty(body.value).kind() + else { + bug!(); + }; + self.demand_eqtype( + span, + coroutine_args.as_coroutine().kind_ty(), + Ty::from_coroutine_closure_kind(self.tcx, closure_kind), + ); + } } } diff --git a/tests/ui/async-await/async-closures/cast-as-infer-self-borrows.rs b/tests/ui/async-await/async-closures/cast-as-infer-self-borrows.rs new file mode 100644 index 0000000000000..a172aaf93acd0 --- /dev/null +++ b/tests/ui/async-await/async-closures/cast-as-infer-self-borrows.rs @@ -0,0 +1,10 @@ +//@ edition:2021 + +// Regression test for #155999. + +fn needs_fn_mut(x: impl FnMut() -> T) { + needs_fn_mut(async || x as _) + //~^ ERROR type annotations needed +} + +fn main() {} diff --git a/tests/ui/async-await/async-closures/cast-as-infer-self-borrows.stderr b/tests/ui/async-await/async-closures/cast-as-infer-self-borrows.stderr new file mode 100644 index 0000000000000..9dbf29f92abe9 --- /dev/null +++ b/tests/ui/async-await/async-closures/cast-as-infer-self-borrows.stderr @@ -0,0 +1,9 @@ +error[E0282]: type annotations needed + --> $DIR/cast-as-infer-self-borrows.rs:6:32 + | +LL | needs_fn_mut(async || x as _) + | ^ cannot infer type + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0282`. From bb5f1c86e21b1cd0c71a9b37efbbe36966e2091b Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Tue, 26 May 2026 09:18:31 +0100 Subject: [PATCH 05/14] `Lift_Generic` can lift generic parameters --- compiler/rustc_type_ir/src/binder.rs | 19 +----- compiler/rustc_type_ir_macros/src/lib.rs | 81 +++++++++++++++++++----- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index de3e04626f823..2085d0883045f 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -27,7 +27,7 @@ use crate::{self as ty, DebruijnIndex, Interner, UniverseIndex, Unnormalized}; /// /// `Decodable` and `Encodable` are implemented for `Binder` using the `impl_binder_encode_decode!` macro. #[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner, T)] -#[derive(GenericTypeVisitable)] +#[derive(GenericTypeVisitable, Lift_Generic)] #[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] pub struct Binder { value: T, @@ -36,23 +36,6 @@ pub struct Binder { impl Eq for Binder {} -// FIXME: We manually derive `Lift` because the `derive(Lift_Generic)` doesn't -// understand how to turn `T` to `T::Lifted` in the output `type Lifted`. -impl Lift for Binder -where - T: Lift, - I::BoundVarKinds: Lift, -{ - type Lifted = Binder; - - fn lift_to_interner(self, cx: U) -> Self::Lifted { - Binder { - value: self.value.lift_to_interner(cx), - bound_vars: self.bound_vars.lift_to_interner(cx), - } - } -} - #[cfg(feature = "nightly")] macro_rules! impl_binder_encode_decode { ($($t:ty),+ $(,)?) => { diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index 7ea8bd384e3cf..f8a849f7230e9 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -10,13 +10,18 @@ decl_derive!( [TypeFoldable_Generic, attributes(type_foldable)] => type_foldable_derive ); decl_derive!( - [Lift_Generic] => lift_derive + [Lift_Generic, attributes(lift)] => lift_derive ); #[cfg(not(feature = "nightly"))] decl_derive!( [GenericTypeVisitable] => customizable_type_visitable_derive ); +struct LiftedTy { + ty: syn::Type, + generic_parameter_bounds: Vec, +} + fn has_ignore_attr(attrs: &[Attribute], name: &'static str, meta: &'static str) -> bool { let mut ignored = false; attrs.iter().for_each(|attr| { @@ -158,15 +163,32 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { s.add_impl_generic(parse_quote! { J }); s.add_where_predicate(parse_quote! { J: Interner }); + let generic_parameters = + s.ast().generics.type_params().map(|ty| ty.ident.clone()).collect::>(); + let mut wc = vec![]; s.bind_with(|_| synstructure::BindStyle::Move); let body_fold = s.each_variant(|vi| { let bindings = vi.bindings(); vi.construct(|field, index| { let ty = field.ty.clone(); - let lifted_ty = lift(ty.clone()); - wc.push(parse_quote! { #ty: ::rustc_type_ir::lift::Lift }); let bind = &bindings[index]; + // Allow field to be ignored from lift + if has_ignore_attr(&field.attrs, "lift", "identity") { + return bind.to_token_stream(); + } + + let lifted = lift(ty.clone(), &generic_parameters); + + for param in lifted.generic_parameter_bounds { + wc.push(parse_quote! { #param: ::rustc_type_ir::lift::Lift }); + } + + if !is_type_param(&ty, &generic_parameters) { + let lifted_ty = lifted.ty; + wc.push(parse_quote! { #ty: ::rustc_type_ir::lift::Lift }); + } + quote! { #bind.lift_to_interner(interner) } @@ -179,7 +201,8 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { let (_, ty_generics, _) = s.ast().generics.split_for_impl(); let name = s.ast().ident.clone(); let self_ty: syn::Type = parse_quote! { #name #ty_generics }; - let lifted_ty = lift(self_ty); + let lifted = lift(self_ty, &generic_parameters); + let lifted_ty = lifted.ty; s.bound_impl( quote!(::rustc_type_ir::lift::Lift), @@ -196,24 +219,52 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { ) } -fn lift(mut ty: syn::Type) -> syn::Type { - struct ItoJ; - impl VisitMut for ItoJ { +fn is_type_param(ty: &syn::Type, generic_parameters: &[syn::Ident]) -> bool { + if let syn::Type::Path(ty) = ty + && ty.path.segments.len() == 1 + && let Some(segment) = ty.path.segments.first() + { + matches!(segment.arguments, syn::PathArguments::None) + && generic_parameters.iter().any(|param| segment.ident == *param) + } else { + false + } +} + +fn lift(mut ty: syn::Type, generic_parameters: &[syn::Ident]) -> LiftedTy { + struct ItoJ<'a> { + generic_parameters: &'a [syn::Ident], + generic_parameter_bounds: Vec, + } + + impl VisitMut for ItoJ<'_> { fn visit_type_path_mut(&mut self, i: &mut syn::TypePath) { if i.qself.is_none() { - if let Some(first) = i.path.segments.first_mut() - && first.ident == "I" - { - *first = parse_quote! { J }; + let segments_len = i.path.segments.len(); + if let Some(first) = i.path.segments.first_mut() { + // Turn paths from `I` into `J` + if first.ident == "I" { + *first = parse_quote! { J }; + } else if segments_len == 1 + && matches!(first.arguments, syn::PathArguments::None) + && self.generic_parameters.iter().any(|param| first.ident == *param) + { + let ident = first.ident.clone(); + if !self.generic_parameter_bounds.iter().any(|param| *param == ident) { + self.generic_parameter_bounds.push(ident.clone()); + } + + *i = parse_quote! { <#ident as ::rustc_type_ir::lift::Lift>::Lifted }; + return; + } } } syn::visit_mut::visit_type_path_mut(self, i); } } - - ItoJ.visit_type_mut(&mut ty); - - ty + let mut visitor = ItoJ { generic_parameters, generic_parameter_bounds: Vec::new() }; + visitor.visit_type_mut(&mut ty); + LiftedTy { ty, generic_parameter_bounds: visitor.generic_parameter_bounds } } #[cfg(not(feature = "nightly"))] From 2837932e7b79d7be27c1708197f6dd4f926daf39 Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Tue, 26 May 2026 15:31:23 +0100 Subject: [PATCH 06/14] Add comment explaining why we need a `Lift` bound for all fields --- compiler/rustc_type_ir_macros/src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index f8a849f7230e9..e4a70648c07ac 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -184,6 +184,20 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { wc.push(parse_quote! { #param: ::rustc_type_ir::lift::Lift }); } + // `lift(ty, ...)` rewrites any bare generic params inside the + // field type to their lifted associated type, e.g; + // `T` becomes `>::Lifted`. + // + // That, however, does not imply that the field type itself + // implements `Lift`. The generated body calls `lift_to_interner` + // on each field value, so Rust needs a `Lift` impl for that + // value's complete declared type, not just for generic parameters + // that appear somewhere inside it. For non parameter fields, the + // implementation must also produce the exact mapped field type: + // + // Example: + // `I::DefId: Lift` + // `Binder: Lift>::Lifted>>` if !is_type_param(&ty, &generic_parameters) { let lifted_ty = lifted.ty; wc.push(parse_quote! { #ty: ::rustc_type_ir::lift::Lift }); @@ -219,6 +233,8 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { ) } +/// Returns true only for bare generic parameters like `T`, not paths such as +/// `I::Ty`, `Vec`, or `Binder` fn is_type_param(ty: &syn::Type, generic_parameters: &[syn::Ident]) -> bool { if let syn::Type::Path(ty) = ty && ty.path.segments.len() == 1 From 91a328f74b6739fd5090a2136ae79a26859e2c83 Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Wed, 27 May 2026 13:45:37 +0100 Subject: [PATCH 07/14] Can lift structs with a field of `PhantomData` --- compiler/rustc_type_ir/src/binder.rs | 33 +++--------------------- compiler/rustc_type_ir/src/predicate.rs | 17 +----------- compiler/rustc_type_ir/src/ty_kind.rs | 10 ++----- compiler/rustc_type_ir_macros/src/lib.rs | 30 ++++++++++++++++----- 4 files changed, 30 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index 2085d0883045f..dbd8beed2ed26 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -14,7 +14,6 @@ use tracing::instrument; use crate::data_structures::SsoHashSet; use crate::fold::{FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable}; use crate::inherent::*; -use crate::lift::Lift; use crate::visit::{Flags, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor}; use crate::{self as ty, DebruijnIndex, Interner, UniverseIndex, Unnormalized}; @@ -949,12 +948,13 @@ pub enum BoundVarIndexKind { /// identified by both a universe, as well as a name residing within that universe. Distinct bound /// regions/types/consts within the same universe simply have an unknown relationship to one #[derive_where(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash; I: Interner, T)] -#[derive(TypeVisitable_Generic, TypeFoldable_Generic, GenericTypeVisitable)] +#[derive(TypeVisitable_Generic, TypeFoldable_Generic, GenericTypeVisitable, Lift_Generic)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext) )] pub struct Placeholder { + #[lift(identity)] pub universe: UniverseIndex, pub bound: T, #[type_foldable(identity)] @@ -972,21 +972,6 @@ impl fmt::Debug for ty::Placeholder { } } -impl Lift for Placeholder -where - T: Lift, -{ - type Lifted = Placeholder; - - fn lift_to_interner(self, cx: U) -> Self::Lifted { - Placeholder { - universe: self.universe, - bound: self.bound.lift_to_interner(cx), - _tcx: PhantomData, - } - } -} - #[derive_where(Clone, Copy, PartialEq, Eq, Hash; I: Interner)] #[derive(Lift_Generic, GenericTypeVisitable)] #[cfg_attr( @@ -1157,27 +1142,17 @@ impl PlaceholderRegion { } #[derive_where(Clone, Copy, PartialEq, Eq, Hash; I: Interner)] -#[derive(GenericTypeVisitable)] +#[derive(GenericTypeVisitable, Lift_Generic)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext) )] pub struct BoundTy { + #[lift(identity)] pub var: ty::BoundVar, pub kind: BoundTyKind, } -impl Lift for BoundTy -where - BoundTyKind: Lift>, -{ - type Lifted = BoundTy; - - fn lift_to_interner(self, cx: U) -> Self::Lifted { - BoundTy { var: self.var, kind: self.kind.lift_to_interner(cx) } - } -} - impl fmt::Debug for ty::BoundTy { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.kind { diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index 301cf7dbf1087..20b02a31596bc 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -9,7 +9,6 @@ use rustc_type_ir_macros::{ }; use crate::inherent::*; -use crate::lift::Lift; use crate::upcast::{Upcast, UpcastFrom}; use crate::visit::TypeVisitableExt as _; use crate::{self as ty, AliasTyKind, Interner}; @@ -17,7 +16,7 @@ use crate::{self as ty, AliasTyKind, Interner}; /// `A: 'region` #[derive_where(Clone, Hash, PartialEq, Debug; I: Interner, A)] #[derive_where(Copy; I: Interner, A: Copy)] -#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( feature = "nightly", derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext) @@ -26,20 +25,6 @@ pub struct OutlivesPredicate(pub A, pub I::Region); impl Eq for OutlivesPredicate {} -// FIXME: We manually derive `Lift` because the `derive(Lift_Generic)` doesn't -// understand how to turn `A` to `A::Lifted` in the output `type Lifted`. -impl Lift for OutlivesPredicate -where - A: Lift, - I::Region: Lift, -{ - type Lifted = OutlivesPredicate; - - fn lift_to_interner(self, cx: U) -> Self::Lifted { - OutlivesPredicate(self.0.lift_to_interner(cx), self.1.lift_to_interner(cx)) - } -} - /// `'a == 'b`. /// For the rationale behind having this instead of a pair of bidirectional /// `'a: 'b` and `'b: 'a`, see diff --git a/compiler/rustc_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index 6f3cea27cafdb..2014ec094524f 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -764,7 +764,7 @@ impl Eq for TypeAndMut {} /// Contains the packed non-type fields of a function signature. // FIXME(splat): add the splatted argument index as a u16 #[derive_where(Copy, Clone, PartialEq, Eq, Hash; I: Interner)] -#[derive(TypeVisitable_Generic, TypeFoldable_Generic)] +#[derive(TypeVisitable_Generic, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext) @@ -772,6 +772,7 @@ impl Eq for TypeAndMut {} pub struct FnSigKind { /// Holds the c_variadic and safety bitflags, and 6 bits for the `ExternAbi` variant and unwind /// flag. + #[lift(identity)] #[type_visitable(ignore)] #[type_foldable(identity)] flags: u8, @@ -780,13 +781,6 @@ pub struct FnSigKind { _marker: PhantomData I>, } -impl crate::lift::Lift for FnSigKind { - type Lifted = FnSigKind; - fn lift_to_interner(self, _cx: J) -> Self::Lifted { - FnSigKind { flags: self.flags, _marker: PhantomData } - } -} - impl fmt::Debug for FnSigKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_tuple("FnSigKind"); diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index e4a70648c07ac..463ed14aa1bff 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -178,6 +178,12 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { return bind.to_token_stream(); } + if is_type_phantom(&ty) { + return quote! { + PhantomData + }; + } + let lifted = lift(ty.clone(), &generic_parameters); for param in lifted.generic_parameter_bounds { @@ -233,20 +239,30 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { ) } -/// Returns true only for bare generic parameters like `T`, not paths such as -/// `I::Ty`, `Vec`, or `Binder` -fn is_type_param(ty: &syn::Type, generic_parameters: &[syn::Ident]) -> bool { +fn get_first_path_segment(ty: &syn::Type) -> Option<&syn::PathSegment> { if let syn::Type::Path(ty) = ty && ty.path.segments.len() == 1 - && let Some(segment) = ty.path.segments.first() { - matches!(segment.arguments, syn::PathArguments::None) - && generic_parameters.iter().any(|param| segment.ident == *param) + ty.path.segments.first() } else { - false + None } } +/// Returns true only for bare generic parameters like `T`, not paths such as +/// `I::Ty`, `Vec`, or `Binder` +fn is_type_param(ty: &syn::Type, generic_parameters: &[syn::Ident]) -> bool { + get_first_path_segment(ty).is_some_and(|segment| { + matches!(segment.arguments, syn::PathArguments::None) + && generic_parameters.iter().any(|param| segment.ident == *param) + }) +} + +/// Return if the type is `PhantomData` +fn is_type_phantom(ty: &syn::Type) -> bool { + get_first_path_segment(ty).is_some_and(|segment| segment.ident == "PhantomData") +} + fn lift(mut ty: syn::Type, generic_parameters: &[syn::Ident]) -> LiftedTy { struct ItoJ<'a> { generic_parameters: &'a [syn::Ident], From 82c1c877ef2beb2b5911c337f5ffbe9c03294da1 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 2 Jun 2026 13:59:11 +0200 Subject: [PATCH 08/14] Eagerly decide whether relaxed bounds are allowed or not --- compiler/rustc_ast_lowering/src/item.rs | 11 ++++++++--- compiler/rustc_ast_lowering/src/lib.rs | 21 ++++++--------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 70f55d67c7a16..5fa614841a513 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1986,7 +1986,7 @@ impl<'hir> LoweringContext<'_, 'hir> { bounds: &[GenericBound], colon_span: Option, parent_span: Span, - rbp: RelaxedBoundPolicy<'_>, + rbp: RelaxedBoundPolicy, itctx: ImplTraitContext, origin: PredicateOrigin, ) -> Option> { @@ -2061,8 +2061,13 @@ impl<'hir> LoweringContext<'_, 'hir> { bounded_ty, bounds, }) => { - let rbp = if bound_generic_params.is_empty() { - RelaxedBoundPolicy::AllowedIfOnTyParam(bounded_ty.id, params) + let rbp = if bound_generic_params.is_empty() + && let Some(res) = + self.get_partial_res(bounded_ty.id).and_then(|r| r.full_res()) + && let Res::Def(DefKind::TyParam, def_id) = res + && params.iter().any(|p| def_id == self.local_def_id(p.id).to_def_id()) + { + RelaxedBoundPolicy::Allowed } else { RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::LateBoundVarsInScope) }; diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 4045f08c053ed..95d3ab7a5c5a2 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -318,9 +318,8 @@ impl<'tcx> ResolverAstLowering<'tcx> { /// Relaxed bounds should only be allowed in places where we later /// (namely during HIR ty lowering) perform *sized elaboration*. #[derive(Clone, Copy, Debug)] -enum RelaxedBoundPolicy<'a> { +enum RelaxedBoundPolicy { Allowed, - AllowedIfOnTyParam(NodeId, &'a [ast::GenericParam]), Forbidden(RelaxedBoundForbiddenReason), } @@ -1955,7 +1954,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bound( &mut self, tpb: &GenericBound, - rbp: RelaxedBoundPolicy<'_>, + rbp: RelaxedBoundPolicy, itctx: ImplTraitContext, ) -> hir::GenericBound<'hir> { match tpb { @@ -2193,7 +2192,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_poly_trait_ref( &mut self, PolyTraitRef { bound_generic_params, modifiers, trait_ref, span, parens: _ }: &PolyTraitRef, - rbp: RelaxedBoundPolicy<'_>, + rbp: RelaxedBoundPolicy, itctx: ImplTraitContext, ) -> hir::PolyTraitRef<'hir> { let bound_generic_params = @@ -2217,7 +2216,7 @@ impl<'hir> LoweringContext<'_, 'hir> { &self, trait_ref: hir::TraitRef<'_>, span: Span, - rbp: RelaxedBoundPolicy<'_>, + rbp: RelaxedBoundPolicy, ) { // Even though feature `more_maybe_bounds` enables the user to relax all default bounds // other than `Sized` in a lot more positions (thereby bypassing the given policy), we don't @@ -2230,14 +2229,6 @@ impl<'hir> LoweringContext<'_, 'hir> { match rbp { RelaxedBoundPolicy::Allowed => return, - RelaxedBoundPolicy::AllowedIfOnTyParam(id, params) => { - if let Some(res) = self.get_partial_res(id).and_then(|r| r.full_res()) - && let Res::Def(DefKind::TyParam, def_id) = res - && params.iter().any(|p| def_id == self.local_def_id(p.id).to_def_id()) - { - return; - } - } RelaxedBoundPolicy::Forbidden(reason) => { let gate = |context, subject| { let extended = self.tcx.features().more_maybe_bounds(); @@ -2299,7 +2290,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bounds( &mut self, bounds: &[GenericBound], - rbp: RelaxedBoundPolicy<'_>, + rbp: RelaxedBoundPolicy, itctx: ImplTraitContext, ) -> hir::GenericBounds<'hir> { self.arena.alloc_from_iter(self.lower_param_bounds_mut(bounds, rbp, itctx)) @@ -2308,7 +2299,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bounds_mut( &mut self, bounds: &[GenericBound], - rbp: RelaxedBoundPolicy<'_>, + rbp: RelaxedBoundPolicy, itctx: ImplTraitContext, ) -> impl Iterator> { bounds.iter().map(move |bound| self.lower_param_bound(bound, rbp, itctx)) From 876d5f376f83cfe36c4f3025ff85f8d9aae2e843 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 2 Jun 2026 17:20:22 +0200 Subject: [PATCH 09/14] Rename and document a relaxed bound reason --- compiler/rustc_ast_lowering/src/item.rs | 2 +- compiler/rustc_ast_lowering/src/lib.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 5fa614841a513..784d82aab391f 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -2069,7 +2069,7 @@ impl<'hir> LoweringContext<'_, 'hir> { { RelaxedBoundPolicy::Allowed } else { - RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::LateBoundVarsInScope) + RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::WhereBound) }; hir::WherePredicateKind::BoundPredicate(hir::WhereBoundPredicate { bound_generic_params: self.lower_generic_params( diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 95d3ab7a5c5a2..72b1d0642001d 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -329,7 +329,9 @@ enum RelaxedBoundForbiddenReason { SuperTrait, TraitAlias, AssocTyBounds, - LateBoundVarsInScope, + /// We do not allow where bounds doing relaxed bounds, + /// except if it's for generic parameters of the current item. + WhereBound, } /// Context of `impl Trait` in code, which determines whether it is allowed in an HIR subtree, @@ -2268,7 +2270,7 @@ impl<'hir> LoweringContext<'_, 'hir> { return; } RelaxedBoundForbiddenReason::AssocTyBounds - | RelaxedBoundForbiddenReason::LateBoundVarsInScope => {} + | RelaxedBoundForbiddenReason::WhereBound => {} }; } } From a55c892b5b6124e769906752747b7c542e4a9291 Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Wed, 3 Jun 2026 13:58:12 +0100 Subject: [PATCH 10/14] Simplify generic Lift bounds --- compiler/rustc_middle/src/ty/context.rs | 17 +++++-- .../rustc_middle/src/ty/structural_impls.rs | 10 +++- compiler/rustc_type_ir/src/interner.rs | 46 +++++++++++++++++++ compiler/rustc_type_ir/src/lift.rs | 6 +++ compiler/rustc_type_ir/src/predicate.rs | 3 ++ compiler/rustc_type_ir_macros/src/lib.rs | 34 ++------------ 6 files changed, 82 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index e756277b92d76..ee176cd7914b4 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1659,16 +1659,20 @@ macro_rules! nop_lift { macro_rules! nop_list_lift { ($set:ident; $ty:ty => $lifted:ty) => { - impl<'a, 'tcx> Lift> for &'a List<$ty> { - type Lifted = &'tcx List<$lifted>; + nop_list_lift! { $set: List; $ty => $lifted } + }; + // Allows defining own list type + ($set:ident: $list:ident; $ty:ty => $lifted:ty) => { + impl<'a, 'tcx> Lift> for &'a $list<$ty> { + type Lifted = &'tcx $list<$lifted>; fn lift_to_interner(self, tcx: TyCtxt<'tcx>) -> Self::Lifted { // Assert that the set has the right type. if false { - let _x: &InternedSet<'tcx, List<$lifted>> = &tcx.interners.$set; + let _x: &InternedSet<'tcx, $list<$lifted>> = &tcx.interners.$set; } if self.is_empty() { - return List::empty(); + return $list::empty(); } assert!(tcx.interners.$set.contains_pointer_to(&InternedInSet(self))); // SAFETY: we just checked that `self` is interned and therefore is valid for the @@ -1690,10 +1694,15 @@ nop_lift! { layout; Layout<'a> => Layout<'tcx> } nop_lift! { valtree; ValTree<'a> => ValTree<'tcx> } nop_list_lift! { type_lists; Ty<'a> => Ty<'tcx> } +nop_list_lift! { clauses: ListWithCachedTypeInfo; Clause<'a> => Clause<'tcx> } nop_list_lift! { poly_existential_predicates; PolyExistentialPredicate<'a> => PolyExistentialPredicate<'tcx> } nop_list_lift! { bound_variable_kinds; ty::BoundVariableKind<'a> => ty::BoundVariableKind<'tcx> } +nop_list_lift! { patterns; Pattern<'a> => Pattern<'tcx> } +nop_list_lift! { + outlives; ty::ArgOutlivesPredicate<'a> => ty::ArgOutlivesPredicate<'tcx> +} // This is the impl for `&'a GenericArgs<'a>`. nop_list_lift! { args; GenericArg<'a> => GenericArg<'tcx> } diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index 08a8491bab6c5..a92a407133e32 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -184,7 +184,6 @@ impl<'tcx> fmt::Debug for Region<'tcx> { // For things for which the type library provides traversal implementations // for all Interners, we only need to provide a Lift implementation. TrivialLiftImpls! { - (), bool, usize, u64, @@ -197,6 +196,7 @@ TrivialLiftImpls! { rustc_abi::Size, rustc_hir::Safety, rustc_middle::mir::ConstValue, + rustc_span::Symbol, rustc_type_ir::BoundConstness, rustc_type_ir::PredicatePolarity, // tidy-alphabetical-end @@ -269,6 +269,14 @@ TrivialTypeTraversalAndLiftImpls! { /////////////////////////////////////////////////////////////////////////// // Lift implementations +impl<'a, 'tcx> Lift> for ty::ParamEnv<'a> { + type Lifted = ty::ParamEnv<'tcx>; + + fn lift_to_interner(self, tcx: TyCtxt<'tcx>) -> Self::Lifted { + ty::ParamEnv::new(tcx.lift(self.caller_bounds())) + } +} + impl<'tcx, T: Lift>> Lift> for Option { type Lifted = Option; fn lift_to_interner(self, tcx: TyCtxt<'tcx>) -> Self::Lifted { diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 09def0212a153..df700fdfe3122 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -469,6 +469,52 @@ pub trait Interner: fn item_name(self, item_index: Self::DefId) -> Self::Symbol; } +macro_rules! declare_lift_into { + ($($assoc:ident),* $(,)?) => { + /// An interner whose associated types can be lifted into another interner `J`. + /// + /// These are associated type bounds rather than `where` clauses so a caller with + /// `I: LiftInto` can rely on the individual associated type `Lift` bounds being + /// implied. + pub trait LiftInto: Interner<$($assoc: crate::lift::Lift,)*> + where + J: Interner, + {} + + impl LiftInto for I + where + J: Interner, + I: Interner<$($assoc: crate::lift::Lift,)*>, + {} + }; +} + +declare_lift_into! { + BoundVarKinds, + Const, + DefId, + FreeConstAliasId, + FreeTyAliasId, + GenericArg, + GenericArgs, + InherentAssocConstId, + InherentAssocTyId, + OpaqueTyId, + ParamEnv, + PatList, + Region, + RegionAssumptions, + Symbol, + Term, + TraitAssocConstId, + TraitAssocTermId, + TraitAssocTyId, + TraitId, + Ty, + Tys, + UnevaluatedConstId, +} + /// Imagine you have a function `F: FnOnce(&[T]) -> R`, plus an iterator `iter` /// that produces `T` items. You could combine them with /// `f(&iter.collect::>())`, but this requires allocating memory for the diff --git a/compiler/rustc_type_ir/src/lift.rs b/compiler/rustc_type_ir/src/lift.rs index 739d3a8512329..f281825803e9e 100644 --- a/compiler/rustc_type_ir/src/lift.rs +++ b/compiler/rustc_type_ir/src/lift.rs @@ -19,3 +19,9 @@ pub trait Lift: std::fmt::Debug { type Lifted: std::fmt::Debug; fn lift_to_interner(self, cx: I) -> Self::Lifted; } + +impl Lift for () { + type Lifted = (); + + fn lift_to_interner(self, _: I) -> Self::Lifted {} +} diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index 20b02a31596bc..6c8fc8991a353 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -195,6 +195,7 @@ pub struct TraitPredicate { /// If polarity is Negative: we are proving that a negative impl of this trait /// exists. (Note that coherence also checks whether negative impls of supertraits /// exist via a series of predicates.) + #[lift(identity)] pub polarity: PredicatePolarity, } @@ -990,6 +991,7 @@ impl fmt::Debug for NormalizesTo { )] pub struct HostEffectPredicate { pub trait_ref: ty::TraitRef, + #[lift(identity)] pub constness: BoundConstness, } @@ -1035,6 +1037,7 @@ impl ty::Binder> { derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext) )] pub struct SubtypePredicate { + #[lift(identity)] pub a_is_expected: bool, pub a: I::Ty, pub b: I::Ty, diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index 463ed14aa1bff..f90737dd0889b 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -159,9 +159,9 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { } s.add_bounds(synstructure::AddBounds::None); - s.add_where_predicate(parse_quote! { I: Interner }); s.add_impl_generic(parse_quote! { J }); s.add_where_predicate(parse_quote! { J: Interner }); + s.add_where_predicate(parse_quote! { I: ::rustc_type_ir::LiftInto }); let generic_parameters = s.ast().generics.type_params().map(|ty| ty.ident.clone()).collect::>(); @@ -186,29 +186,14 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { let lifted = lift(ty.clone(), &generic_parameters); + // Field types involving ordinary generic parameters still need + // explicit bounds for those parameters, e.g. `Binder` needs + // `T: Lift` so its own derived `Lift` impl applies. Interner + // associated types are covered by `I: LiftInto`. for param in lifted.generic_parameter_bounds { wc.push(parse_quote! { #param: ::rustc_type_ir::lift::Lift }); } - // `lift(ty, ...)` rewrites any bare generic params inside the - // field type to their lifted associated type, e.g; - // `T` becomes `>::Lifted`. - // - // That, however, does not imply that the field type itself - // implements `Lift`. The generated body calls `lift_to_interner` - // on each field value, so Rust needs a `Lift` impl for that - // value's complete declared type, not just for generic parameters - // that appear somewhere inside it. For non parameter fields, the - // implementation must also produce the exact mapped field type: - // - // Example: - // `I::DefId: Lift` - // `Binder: Lift>::Lifted>>` - if !is_type_param(&ty, &generic_parameters) { - let lifted_ty = lifted.ty; - wc.push(parse_quote! { #ty: ::rustc_type_ir::lift::Lift }); - } - quote! { #bind.lift_to_interner(interner) } @@ -249,15 +234,6 @@ fn get_first_path_segment(ty: &syn::Type) -> Option<&syn::PathSegment> { } } -/// Returns true only for bare generic parameters like `T`, not paths such as -/// `I::Ty`, `Vec`, or `Binder` -fn is_type_param(ty: &syn::Type, generic_parameters: &[syn::Ident]) -> bool { - get_first_path_segment(ty).is_some_and(|segment| { - matches!(segment.arguments, syn::PathArguments::None) - && generic_parameters.iter().any(|param| segment.ident == *param) - }) -} - /// Return if the type is `PhantomData` fn is_type_phantom(ty: &syn::Type) -> bool { get_first_path_segment(ty).is_some_and(|segment| segment.ident == "PhantomData") From 57da2f6ab2edef8427a101ac2e6d74a110363769 Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Thu, 4 Jun 2026 13:43:06 +0100 Subject: [PATCH 11/14] Add documentation comment for `lift_derive`, move `PhantomData` check so we still collect generic bounds from the type --- compiler/rustc_type_ir_macros/src/lib.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_type_ir_macros/src/lib.rs b/compiler/rustc_type_ir_macros/src/lib.rs index f90737dd0889b..9d576c3deacee 100644 --- a/compiler/rustc_type_ir_macros/src/lib.rs +++ b/compiler/rustc_type_ir_macros/src/lib.rs @@ -149,6 +149,18 @@ fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::Toke ) } +/// `Lift_Generic` is specialised for structs/enums parameterised by an interner +/// `I: Interner`. It derives `Lift` by rewriting interner associated types +/// from `I::Assoc` to `J::Assoc`. The required associated type lift bounds are +/// supplied by `I: LiftInto`. +/// +/// Ordinary generic parameters still get explicit `Lift` bounds. Interner +/// independent fields must either implement `Lift` manually or use +/// `#[lift(identity)]`. +/// +/// `PhantomData` is a special case that occurs enough in the code base to be +/// handled here directly. We collect any generic bounds from the type then +/// produce another `PhantomData`. fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { if let syn::Data::Union(_) = s.ast().data { panic!("cannot derive on union") @@ -178,12 +190,6 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { return bind.to_token_stream(); } - if is_type_phantom(&ty) { - return quote! { - PhantomData - }; - } - let lifted = lift(ty.clone(), &generic_parameters); // Field types involving ordinary generic parameters still need @@ -194,6 +200,12 @@ fn lift_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { wc.push(parse_quote! { #param: ::rustc_type_ir::lift::Lift }); } + if is_type_phantom(&ty) { + return quote! { + PhantomData + }; + } + quote! { #bind.lift_to_interner(interner) } From cb09a42d7ecc84d6791c74c6283fd7b8c6006cad Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 7 May 2026 16:01:48 +0200 Subject: [PATCH 12/14] Emit nofree attribute Treat the semantics of pointee.size as "dereferenceable-at-point" and always specify the size. Instead, use a separate NoFree attribute to determine whether dereferenceability extends to the whole function. Then in the LLVM backend, only actually emit dereferenceable if nofree is also set, as dereferenceable currently implies nofree. In addition, explicitly emit the nofree attribute, which will help when LLVM switches dereferenceable to be at-point. --- compiler/rustc_codegen_llvm/src/abi.rs | 10 +++- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 3 + compiler/rustc_middle/src/ty/layout.rs | 29 ++-------- compiler/rustc_target/src/callconv/mod.rs | 13 +++-- compiler/rustc_ty_utils/src/abi.rs | 55 ++++++++++++++----- tests/codegen-llvm/addr-of-mutate.rs | 6 +- tests/codegen-llvm/drop-in-place-noalias.rs | 2 +- tests/codegen-llvm/function-arguments.rs | 48 ++++++++-------- tests/codegen-llvm/llvm-writable.rs | 8 +-- .../loongarch-abi/loongarch64-lp64d-abi.rs | 4 +- tests/codegen-llvm/packed.rs | 4 +- tests/codegen-llvm/slice-iter-nonnull.rs | 4 +- tests/ui/abi/c-zst.powerpc-linux.stderr | 2 +- tests/ui/abi/c-zst.s390x-linux.stderr | 2 +- tests/ui/abi/c-zst.sparc64-linux.stderr | 2 +- .../ui/abi/c-zst.x86_64-pc-windows-gnu.stderr | 2 +- tests/ui/abi/debug.generic.stderr | 6 +- tests/ui/abi/debug.loongarch64.stderr | 6 +- tests/ui/abi/debug.riscv64.stderr | 6 +- tests/ui/abi/pass-indirectly-attr.stderr | 2 +- .../pass-by-value-abi.aarch64.stderr | 2 +- .../pass-by-value-abi.x86_64.stderr | 6 +- 23 files changed, 124 insertions(+), 99 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index dcde960258a51..66c6cd1ecbfe9 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -38,12 +38,16 @@ trait ArgAttributesExt { const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] = [(ArgAttribute::InReg, llvm::AttributeKind::InReg)]; -const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 5] = [ +const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [ (ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias), (ArgAttribute::NonNull, llvm::AttributeKind::NonNull), (ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly), (ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef), (ArgAttribute::Writable, llvm::AttributeKind::Writable), + // Our internal NoFree attribute still allows deallocation of zero-size allocations. However, + // these don't render any bytes non-dereferenceable, so it's still fine to apply LLVM NoFree + // for them. + (ArgAttribute::NoFree, llvm::AttributeKind::NoFree), ]; const CAPTURES_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 3] = [ @@ -75,7 +79,9 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&' // Only apply remaining attributes when optimizing if cx.sess().opts.optimize != config::OptLevel::No { let deref = this.pointee_size.bytes(); - if deref != 0 { + // dereferenceable in LLVM currently implies nofree, so only emit dereferenceable if nofree + // is also set. + if deref != 0 && regular.contains(ArgAttribute::NoFree) { if regular.contains(ArgAttribute::NonNull) { attrs.push(llvm::CreateDereferenceableAttr(cx.llcx, deref)); } else { diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 9ecf5afcbcb35..36d5bf8362fa0 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -294,6 +294,7 @@ pub(crate) enum AttributeKind { SanitizeRealtimeNonblocking = 47, SanitizeRealtimeBlocking = 48, Convergent = 49, + NoFree = 50, } /// LLVMIntPredicate diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 91bb1c9733630..bfaa00dc0ebaa 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -383,6 +383,7 @@ enum class LLVMRustAttributeKind { SanitizeRealtimeNonblocking = 47, SanitizeRealtimeBlocking = 48, Convergent = 49, + NoFree = 50, }; static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) { @@ -481,6 +482,8 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) { return Attribute::SanitizeRealtimeBlocking; case LLVMRustAttributeKind::Convergent: return Attribute::Convergent; + case LLVMRustAttributeKind::NoFree: + return Attribute::NoFree; } report_fatal_error("bad LLVMRustAttributeKind"); } diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index e38e5393d967d..4efd06a9326d0 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -979,33 +979,19 @@ where } ty::Ref(_, ty, mt) if offset.bytes() == 0 => { tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| { - let (size, kind); - match mt { + let kind = match mt { hir::Mutability::Not => { let frozen = optimize && ty.is_freeze(tcx, typing_env); - - // Non-frozen shared references are not necessarily dereferenceable for the entire duration of the function - // (see ) - // (if we had "dereferenceable on entry", we could support this) - size = if frozen { layout.size } else { Size::ZERO }; - - kind = PointerKind::SharedRef { frozen }; + PointerKind::SharedRef { frozen } } hir::Mutability::Mut => { let unpin = optimize && ty.is_unpin(tcx, typing_env) && ty.is_unsafe_unpin(tcx, typing_env); - - // Mutable references to potentially self-referential types are not - // necessarily dereferenceable for the entire duration of the function - // (see ) - // (if we had "dereferenceable on entry", we could support this) - size = if unpin { layout.size } else { Size::ZERO }; - - kind = PointerKind::MutableRef { unpin }; + PointerKind::MutableRef { unpin } } }; - PointeeInfo { safe: Some(kind), size, align: layout.align.abi } + PointeeInfo { safe: Some(kind), size: layout.size, align: layout.align.abi } }) } @@ -1021,12 +1007,7 @@ where && pointee.is_unsafe_unpin(tcx, typing_env), global: this.ty.is_box_global(tcx), }), - - // `Box` are not necessarily dereferenceable for the entire duration of the function as - // they can be deallocated at any time. - // (if we had "dereferenceable on entry", we could support this) - size: Size::ZERO, - + size: layout.size, align: layout.align.abi, }) } diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index e6eb68c1fe10d..0e7e180854d28 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -122,6 +122,11 @@ mod attr_impl { const InReg = 1 << 6; const NoUndef = 1 << 7; const Writable = 1 << 8; + /// It is UB for this pointer or any pointer derived from it to be used for + /// deallocation (except for zero-sized deallocation) while the function is + /// executing. Only valid on arguments (including return values that are passed + /// indirectly as arguments). + const NoFree = 1 << 9; } } rustc_data_structures::external_bitflags_debug! { ArgAttribute } @@ -143,9 +148,8 @@ pub enum ArgExtension { pub struct ArgAttributes { pub regular: ArgAttribute, pub arg_ext: ArgExtension, - /// The minimum size of the pointee, guaranteed to be valid for the duration of the whole call - /// (corresponding to LLVM's dereferenceable_or_null attributes, i.e., it is okay for this to be - /// set on a null pointer, but all non-null pointers must be dereferenceable). + /// If the pointer is not null, the minimum dereferenceable size of the pointee, at the time of + /// function entry (for arguments) or function return (for return values). pub pointee_size: Size, /// The minimum alignment of the pointee, if any. pub pointee_align: Option, @@ -408,7 +412,8 @@ impl<'a, Ty> ArgAbi<'a, Ty> { .set(ArgAttribute::NoAlias) .set(ArgAttribute::CapturesAddress) .set(ArgAttribute::NonNull) - .set(ArgAttribute::NoUndef); + .set(ArgAttribute::NoUndef) + .set(ArgAttribute::NoFree); attrs.pointee_size = layout.size; attrs.pointee_align = Some(layout.align.abi); diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index 02739ef6a4135..e782557d126bf 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -356,13 +356,7 @@ fn arg_attrs_for_rust_scalar<'tcx>( Some(pointee.align.min(cx.tcx().sess.target.max_reliable_alignment())); } - // LLVM dereferenceable attribute has unclear semantics on the return type, - // they seem to be "dereferenceable until the end of the program", which is - // generally, not valid for references. See - // - if !is_return { - attrs.pointee_size = pointee.size; - }; + attrs.pointee_size = pointee.size; if let Some(kind) = pointee.safe { // The aliasing rules for `Box` are still not decided, but currently we emit @@ -407,6 +401,26 @@ fn arg_attrs_for_rust_scalar<'tcx>( } } + // NoFree is not valid on return values. If it were, it would mean something like + // "will not be freed until the end of the program", which is generally not valid for + // references. + let no_free = !is_return + && match kind { + // Non-frozen shared references are not necessarily dereferenceable for the + // entire duration of the function + // (see ). + PointerKind::SharedRef { frozen } => frozen, + // Mutable references to potentially self-referential types are not necessarily + // dereferenceable for the entire duration of the function + // (see ). + PointerKind::MutableRef { unpin } => unpin, + // Box may be deallocated during execution of the function. + PointerKind::Box { .. } => false, + }; + if no_free { + attrs.set(ArgAttribute::NoFree); + } + if matches!(kind, PointerKind::SharedRef { frozen: true }) && !is_return { attrs.set(ArgAttribute::ReadOnly); attrs.set(ArgAttribute::CapturesReadOnly); @@ -423,11 +437,18 @@ fn fn_abi_sanity_check<'tcx>( fn_abi: &FnAbi<'tcx, Ty<'tcx>>, spec_abi: ExternAbi, ) { + fn fn_arg_attrs_sanity_check(attrs: &ArgAttributes, is_ret: bool) { + if attrs.regular.contains(ArgAttribute::NoFree) { + assert!(!is_ret, "NoFree not valid on return values"); + } + } + fn fn_arg_sanity_check<'tcx>( cx: &LayoutCx<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>, spec_abi: ExternAbi, arg: &ArgAbi<'tcx, Ty<'tcx>>, + is_ret: bool, ) { let tcx = cx.tcx(); @@ -452,7 +473,7 @@ fn fn_abi_sanity_check<'tcx>( PassMode::Ignore => { assert!(arg.layout.is_zst()); } - PassMode::Direct(_) => { + PassMode::Direct(attrs) => { // Here the Rust type is used to determine the actual ABI, so we have to be very // careful. Scalar/Vector is fine, since backends will generally use // `layout.backend_repr` and ignore everything else. We should just reject @@ -482,8 +503,9 @@ fn fn_abi_sanity_check<'tcx>( ); } } + fn_arg_attrs_sanity_check(attrs, is_ret); } - PassMode::Pair(_, _) => { + PassMode::Pair(attrs1, attrs2) => { // Similar to `Direct`, we need to make sure that backends use `layout.backend_repr` // and ignore the rest of the layout. assert!( @@ -491,19 +513,23 @@ fn fn_abi_sanity_check<'tcx>( "PassMode::Pair for type {}", arg.layout.ty ); + fn_arg_attrs_sanity_check(attrs1, is_ret); + fn_arg_attrs_sanity_check(attrs2, is_ret); } PassMode::Cast { .. } => { // `Cast` means "transmute to `CastType`"; that only makes sense for sized types. assert!(arg.layout.is_sized()); } - PassMode::Indirect { meta_attrs: None, .. } => { + PassMode::Indirect { meta_attrs: None, attrs, .. } => { // No metadata, must be sized. // Conceptually, unsized arguments must be copied around, which requires dynamically // determining their size, which we cannot do without metadata. Consult // t-opsem before removing this check. assert!(arg.layout.is_sized()); + // Indirect returns are arguments from an ABI perspective. + fn_arg_attrs_sanity_check(attrs, false); } - PassMode::Indirect { meta_attrs: Some(_), on_stack, .. } => { + PassMode::Indirect { meta_attrs: Some(meta_attrs), attrs, on_stack } => { // With metadata. Must be unsized and not on the stack. assert!(arg.layout.is_unsized() && !on_stack); // Also, must not be `extern` type. @@ -515,14 +541,17 @@ fn fn_abi_sanity_check<'tcx>( // t-opsem before removing this check. panic!("unsized arguments must not be `extern` types"); } + // Indirect returns are arguments from an ABI perspective. + fn_arg_attrs_sanity_check(attrs, false); + fn_arg_attrs_sanity_check(meta_attrs, false); } } } for arg in fn_abi.args.iter() { - fn_arg_sanity_check(cx, fn_abi, spec_abi, arg); + fn_arg_sanity_check(cx, fn_abi, spec_abi, arg, false); } - fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret); + fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret, true); } #[tracing::instrument( diff --git a/tests/codegen-llvm/addr-of-mutate.rs b/tests/codegen-llvm/addr-of-mutate.rs index d1939391b25de..def73bf8190d7 100644 --- a/tests/codegen-llvm/addr-of-mutate.rs +++ b/tests/codegen-llvm/addr-of-mutate.rs @@ -5,7 +5,7 @@ // Test for the absence of `readonly` on the argument when it is mutated via `&raw const`. // See . -// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias noundef align 1{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable(128) %x) +// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias nofree noundef align 1{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable(128) %x) #[no_mangle] pub fn foo(x: [u8; 128]) -> u8 { let ptr = core::ptr::addr_of!(x).cast_mut(); @@ -15,7 +15,7 @@ pub fn foo(x: [u8; 128]) -> u8 { x[0] } -// CHECK: i1 @second(ptr{{( dead_on_return)?}} noalias noundef align {{[0-9]+}}{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b) +// CHECK: i1 @second(ptr{{( dead_on_return)?}} noalias nofree noundef align {{[0-9]+}}{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b) #[no_mangle] pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool { let b_bool_ptr = core::ptr::addr_of!(a_ptr_and_b.1.1).cast_mut(); @@ -24,7 +24,7 @@ pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool { } // If going through a deref (and there are no other mutating accesses), then `readonly` is fine. -// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias noundef readonly align {{[0-9]+}}{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b) +// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias nofree noundef readonly align {{[0-9]+}}{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b) #[no_mangle] pub unsafe fn third(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool { let b_bool_ptr = core::ptr::addr_of!((*a_ptr_and_b.0).1).cast_mut(); diff --git a/tests/codegen-llvm/drop-in-place-noalias.rs b/tests/codegen-llvm/drop-in-place-noalias.rs index bff2f52781f23..86e0caa1b287d 100644 --- a/tests/codegen-llvm/drop-in-place-noalias.rs +++ b/tests/codegen-llvm/drop-in-place-noalias.rs @@ -7,7 +7,7 @@ use std::marker::PhantomPinned; -// CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructUnpin{{.*}}(ptr noalias noundef align 4 dereferenceable(12) %{{.+}}) +// CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructUnpin{{.*}}(ptr noalias nofree noundef align 4 dereferenceable(12) %{{.+}}) // CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructNotUnpin{{.*}}(ptr noundef nonnull align 4 %{{.+}}) diff --git a/tests/codegen-llvm/function-arguments.rs b/tests/codegen-llvm/function-arguments.rs index 6eab9a58b47ad..0832778a6ed93 100644 --- a/tests/codegen-llvm/function-arguments.rs +++ b/tests/codegen-llvm/function-arguments.rs @@ -80,7 +80,7 @@ pub fn option_nonzero_int(x: Option>) -> Option> { x } -// CHECK: @readonly_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) +// CHECK: @readonly_borrow(ptr noalias nofree noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn readonly_borrow(_: &i32) {} @@ -91,12 +91,12 @@ pub fn readonly_borrow_ret() -> &'static i32 { loop {} } -// CHECK: @static_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) +// CHECK: @static_borrow(ptr noalias nofree noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) // static borrow may be captured #[no_mangle] pub fn static_borrow(_: &'static i32) {} -// CHECK: @named_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) +// CHECK: @named_borrow(ptr noalias nofree noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) // borrow with named lifetime may be captured #[no_mangle] pub fn named_borrow<'r>(_: &'r i32) {} @@ -106,12 +106,12 @@ pub fn named_borrow<'r>(_: &'r i32) {} #[no_mangle] pub fn unsafe_borrow(_: &UnsafeInner) {} -// CHECK: @mutable_unsafe_borrow(ptr noalias noundef align 2 dereferenceable(2) %_1) +// CHECK: @mutable_unsafe_borrow(ptr noalias nofree noundef align 2 dereferenceable(2) %_1) // ... unless this is a mutable borrow, those never alias #[no_mangle] pub fn mutable_unsafe_borrow(_: &mut UnsafeInner) {} -// CHECK: @mutable_borrow(ptr noalias noundef align 4 dereferenceable(4) %_1) +// CHECK: @mutable_borrow(ptr noalias nofree noundef align 4 dereferenceable(4) %_1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn mutable_borrow(_: &mut i32) {} @@ -129,25 +129,25 @@ pub fn mutable_borrow_ret() -> &'static mut i32 { // . pub fn mutable_notunpin_borrow(_: &mut NotUnpin) {} -// CHECK: @notunpin_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) +// CHECK: @notunpin_borrow(ptr noalias nofree noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) // But `&NotUnpin` behaves perfectly normal. #[no_mangle] pub fn notunpin_borrow(_: &NotUnpin) {} -// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias noundef readonly align 4{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable(32) %_1) +// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias nofree noundef readonly align 4{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable(32) %_1) #[no_mangle] pub fn indirect_struct(_: S) {} -// CHECK: @borrowed_struct(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(32) %_1) +// CHECK: @borrowed_struct(ptr noalias nofree noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(32) %_1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn borrowed_struct(_: &S) {} -// CHECK: @option_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable_or_null(4) %_x) +// CHECK: @option_borrow(ptr noalias nofree noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable_or_null(4) %_x) #[no_mangle] pub fn option_borrow(_x: Option<&i32>) {} -// CHECK: @option_borrow_mut(ptr noalias noundef align 4 dereferenceable_or_null(4) %_x) +// CHECK: @option_borrow_mut(ptr noalias nofree noundef align 4 dereferenceable_or_null(4) %_x) #[no_mangle] pub fn option_borrow_mut(_x: Option<&mut i32>) {} @@ -170,7 +170,7 @@ pub fn _box(x: Box) -> Box { // With a custom allocator, it should *not* have `noalias`. (See // for why.) The second argument is the allocator, // which is a reference here that still carries `noalias` as usual. -// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %x.1) +// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias nofree noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %x.1) #[no_mangle] pub fn _box_custom(x: Box) { drop(x) @@ -182,7 +182,7 @@ pub fn notunpin_box(x: Box) -> Box { x } -// CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias noundef{{( writable)?}} sret([32 x i8]) align 4{{( captures\(none\))?}} dereferenceable(32){{( %_0)?}}) +// CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias nofree noundef{{( writable)?}} sret([32 x i8]) align 4{{( captures\(none\))?}} dereferenceable(32){{( %_0)?}}) #[no_mangle] pub fn struct_return() -> S { S { _field: [0, 0, 0, 0, 0, 0, 0, 0] } @@ -194,14 +194,14 @@ pub fn struct_return() -> S { pub fn helper(_: usize) {} // CHECK: @slice( -// CHECK-SAME: ptr noalias noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %_1.0, +// CHECK-SAME: ptr noalias nofree noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %_1.0, // CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn slice(_: &[u8]) {} // CHECK: @mutable_slice( -// CHECK-SAME: ptr noalias noundef nonnull %_1.0, +// CHECK-SAME: ptr noalias nofree noundef nonnull %_1.0, // CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1) // FIXME #25759 This should also have `nocapture` #[no_mangle] @@ -219,13 +219,13 @@ pub fn unsafe_slice(_: &[UnsafeInner]) {} pub fn raw_slice(_: *const [u8]) {} // CHECK: @str( -// CHECK-SAME: ptr noalias noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %_1.0, +// CHECK-SAME: ptr noalias nofree noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %_1.0, // CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn str(_: &[u8]) {} -// CHECK: @trait_borrow(ptr noundef nonnull %_1.0, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}) %_1.1) +// CHECK: @trait_borrow(ptr noundef nonnull %_1.0, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}) %_1.1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn trait_borrow(_: &dyn Drop) {} @@ -238,31 +238,31 @@ pub fn option_trait_borrow(x: Option<&dyn Drop>) {} #[no_mangle] pub fn option_trait_borrow_mut(x: Option<&mut dyn Drop>) {} -// CHECK: @trait_raw(ptr noundef %_1.0, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}) %_1.1) +// CHECK: @trait_raw(ptr noundef %_1.0, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}) %_1.1) #[no_mangle] pub fn trait_raw(_: *const dyn Drop) {} // Ensure that `Box` gets `noalias` when the right traits are present, but removing *either* `Unpin` // or `UnsafeUnpin` is enough to lose the attribute. -// CHECK: @trait_box(ptr noalias noundef nonnull{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) +// CHECK: @trait_box(ptr noalias noundef nonnull{{( %0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) #[no_mangle] pub fn trait_box(_: Box) {} -// CHECK: @trait_box_pin1(ptr noundef nonnull{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) +// CHECK: @trait_box_pin1(ptr noundef nonnull{{( %0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) #[no_mangle] pub fn trait_box_pin1(_: Box) {} -// CHECK: @trait_box_pin2(ptr noundef nonnull{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) +// CHECK: @trait_box_pin2(ptr noundef nonnull{{( %0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) #[no_mangle] pub fn trait_box_pin2(_: Box) {} // Same for mutable references (with a non-zero minimal size so that we also see the // `dereferenceable` disappear). -// CHECK: @trait_mutref(ptr noalias noundef align 4 dereferenceable(4){{( %_1.0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) +// CHECK: @trait_mutref(ptr noalias nofree noundef align 4 dereferenceable(4){{( %_1.0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) #[no_mangle] pub fn trait_mutref(_: &mut (i32, dyn Drop + Unpin + UnsafeUnpin)) {} -// CHECK: @trait_mutref_pin1(ptr noundef nonnull align 4{{( %_1.0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) +// CHECK: @trait_mutref_pin1(ptr noundef nonnull align 4{{( %_1.0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) #[no_mangle] pub fn trait_mutref_pin1(_: &mut (i32, dyn Drop + Unpin)) {} -// CHECK: @trait_mutref_pin2(ptr noundef nonnull align 4{{( %_1.0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) +// CHECK: @trait_mutref_pin2(ptr noundef nonnull align 4{{( %_1.0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) #[no_mangle] pub fn trait_mutref_pin2(_: &mut (i32, dyn Drop + UnsafeUnpin)) {} @@ -275,7 +275,7 @@ pub fn trait_option( } // CHECK: { ptr, [[USIZE]] } @return_slice( -// CHECK-SAME: ptr noalias noundef nonnull readonly align 2{{( captures\(address, read_provenance\))?}} %x.0, +// CHECK-SAME: ptr noalias nofree noundef nonnull readonly align 2{{( captures\(address, read_provenance\))?}} %x.0, // CHECK-SAME: [[USIZE]] noundef range({{i32 0, 1073741824|i64 0, 4611686018427387904}}) %x.1) #[no_mangle] pub fn return_slice(x: &[u16]) -> &[u16] { diff --git a/tests/codegen-llvm/llvm-writable.rs b/tests/codegen-llvm/llvm-writable.rs index ea245fc3a6e7a..0c72fb8a1dd9f 100644 --- a/tests/codegen-llvm/llvm-writable.rs +++ b/tests/codegen-llvm/llvm-writable.rs @@ -4,15 +4,15 @@ #![crate_type = "lib"] #![feature(rustc_attrs, unsafe_pinned)] -// CHECK: @mutable_borrow(ptr noalias noundef writable align 4 dereferenceable(4) %_1) +// CHECK: @mutable_borrow(ptr noalias nofree noundef writable align 4 dereferenceable(4) %_1) #[no_mangle] pub fn mutable_borrow(_: &mut i32) {} -// CHECK: @mutable_unsafe_borrow(ptr noalias noundef writable align 2 dereferenceable(2) %_1) +// CHECK: @mutable_unsafe_borrow(ptr noalias nofree noundef writable align 2 dereferenceable(2) %_1) #[no_mangle] pub fn mutable_unsafe_borrow(_: &mut std::cell::UnsafeCell) {} -// CHECK: @option_borrow_mut(ptr noalias noundef writable align 4 dereferenceable_or_null(4) %_1) +// CHECK: @option_borrow_mut(ptr noalias nofree noundef writable align 4 dereferenceable_or_null(4) %_1) #[no_mangle] pub fn option_borrow_mut(_: Option<&mut i32>) {} @@ -24,7 +24,7 @@ pub fn box_moved(_: Box) {} #[no_mangle] pub fn unsafe_pinned_borrow_mut(_: &mut std::pin::UnsafePinned) {} -// CHECK: @mutable_borrow_no_writable(ptr noalias noundef align 4 dereferenceable(4) %_1) +// CHECK: @mutable_borrow_no_writable(ptr noalias nofree noundef align 4 dereferenceable(4) %_1) #[no_mangle] #[rustc_no_writable] pub fn mutable_borrow_no_writable(_: &mut i32) {} diff --git a/tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs b/tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs index 4c61224ac6d8f..68f1267c2a444 100644 --- a/tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs +++ b/tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs @@ -256,11 +256,11 @@ pub struct IntDoubleInt { c: i32, } -// CHECK: define void @f_int_double_int_s_arg(ptr{{( dead_on_return)?}} noalias noundef align 8{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable(24) %a) +// CHECK: define void @f_int_double_int_s_arg(ptr{{( dead_on_return)?}} noalias nofree noundef align 8{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable(24) %a) #[no_mangle] pub extern "C" fn f_int_double_int_s_arg(a: IntDoubleInt) {} -// CHECK: define void @f_ret_int_double_int_s(ptr{{( dead_on_unwind)?}} noalias noundef{{( writable)?}} sret([24 x i8]) align 8{{( captures\(address\))?}} dereferenceable(24) %_0) +// CHECK: define void @f_ret_int_double_int_s(ptr{{( dead_on_unwind)?}} noalias nofree noundef{{( writable)?}} sret([24 x i8]) align 8{{( captures\(address\))?}} dereferenceable(24) %_0) #[no_mangle] pub extern "C" fn f_ret_int_double_int_s() -> IntDoubleInt { IntDoubleInt { a: 1, b: 2., c: 3 } diff --git a/tests/codegen-llvm/packed.rs b/tests/codegen-llvm/packed.rs index 6f62719282eac..84e2b0897c09c 100644 --- a/tests/codegen-llvm/packed.rs +++ b/tests/codegen-llvm/packed.rs @@ -52,7 +52,7 @@ pub struct BigPacked2 { #[no_mangle] pub fn call_pkd1(f: fn() -> Array) -> BigPacked1 { // CHECK: [[ALLOCA:%[_a-z0-9]+]] = alloca [32 x i8] - // CHECK: call void %{{.*}}(ptr{{( captures(none))?}} noalias{{( nocapture)?}} noundef sret{{.*}} dereferenceable(32) [[ALLOCA]]) + // CHECK: call void %{{.*}}(ptr{{( captures(none))?}} noalias{{( nocapture)?}} nofree noundef sret{{.*}} dereferenceable(32) [[ALLOCA]]) // CHECK: call void @llvm.memcpy.{{.*}}(ptr align 1 %{{.*}}, ptr align 4 %{{.*}}, i{{[0-9]+}} 32, i1 false) // check that calls whose destination is a field of a packed struct // go through an alloca rather than calling the function with an @@ -64,7 +64,7 @@ pub fn call_pkd1(f: fn() -> Array) -> BigPacked1 { #[no_mangle] pub fn call_pkd2(f: fn() -> Array) -> BigPacked2 { // CHECK: [[ALLOCA:%[_a-z0-9]+]] = alloca [32 x i8] - // CHECK: call void %{{.*}}(ptr{{( captures(none))?}} noalias{{( nocapture)?}} noundef sret{{.*}} dereferenceable(32) [[ALLOCA]]) + // CHECK: call void %{{.*}}(ptr{{( captures(none))?}} noalias{{( nocapture)?}} nofree noundef sret{{.*}} dereferenceable(32) [[ALLOCA]]) // CHECK: call void @llvm.memcpy.{{.*}}(ptr align 2 %{{.*}}, ptr align 4 %{{.*}}, i{{[0-9]+}} 32, i1 false) // check that calls whose destination is a field of a packed struct // go through an alloca rather than calling the function with an diff --git a/tests/codegen-llvm/slice-iter-nonnull.rs b/tests/codegen-llvm/slice-iter-nonnull.rs index 6b8416662314f..8ef313f2d6715 100644 --- a/tests/codegen-llvm/slice-iter-nonnull.rs +++ b/tests/codegen-llvm/slice-iter-nonnull.rs @@ -51,7 +51,7 @@ pub fn slice_iter_next_back<'a>(it: &mut std::slice::Iter<'a, u32>) -> Option<&' // attribute is there, and confirms adding the assume back doesn't do anything. // CHECK-LABEL: @slice_iter_new -// CHECK-SAME: (ptr noalias noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1) +// CHECK-SAME: (ptr noalias nofree noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1) #[no_mangle] pub fn slice_iter_new(slice: &[u32]) -> std::slice::Iter<'_, u32> { // CHECK-NOT: slice @@ -66,7 +66,7 @@ pub fn slice_iter_new(slice: &[u32]) -> std::slice::Iter<'_, u32> { } // CHECK-LABEL: @slice_iter_mut_new -// CHECK-SAME: (ptr noalias noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1) +// CHECK-SAME: (ptr noalias nofree noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1) #[no_mangle] pub fn slice_iter_mut_new(slice: &mut [u32]) -> std::slice::IterMut<'_, u32> { // CHECK-NOT: slice diff --git a/tests/ui/abi/c-zst.powerpc-linux.stderr b/tests/ui/abi/c-zst.powerpc-linux.stderr index f297aa984dd2e..edea2d5772280 100644 --- a/tests/ui/abi/c-zst.powerpc-linux.stderr +++ b/tests/ui/abi/c-zst.powerpc-linux.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some( diff --git a/tests/ui/abi/c-zst.s390x-linux.stderr b/tests/ui/abi/c-zst.s390x-linux.stderr index f297aa984dd2e..edea2d5772280 100644 --- a/tests/ui/abi/c-zst.s390x-linux.stderr +++ b/tests/ui/abi/c-zst.s390x-linux.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some( diff --git a/tests/ui/abi/c-zst.sparc64-linux.stderr b/tests/ui/abi/c-zst.sparc64-linux.stderr index f297aa984dd2e..edea2d5772280 100644 --- a/tests/ui/abi/c-zst.sparc64-linux.stderr +++ b/tests/ui/abi/c-zst.sparc64-linux.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some( diff --git a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr index f297aa984dd2e..edea2d5772280 100644 --- a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr +++ b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some( diff --git a/tests/ui/abi/debug.generic.stderr b/tests/ui/abi/debug.generic.stderr index b154c3fa201e5..125150f2c2e3f 100644 --- a/tests/ui/abi/debug.generic.stderr +++ b/tests/ui/abi/debug.generic.stderr @@ -487,7 +487,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(32 bytes), pointee_align: Some( @@ -560,7 +560,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(128 bytes), pointee_align: Some( @@ -966,7 +966,7 @@ error: fn_abi_of(assoc_test) = FnAbi { }, mode: Direct( ArgAttributes { - regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef, + regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef | NoFree, arg_ext: None, pointee_size: Size(2 bytes), pointee_align: Some( diff --git a/tests/ui/abi/debug.loongarch64.stderr b/tests/ui/abi/debug.loongarch64.stderr index 68bcd736e47ce..5f05174c12760 100644 --- a/tests/ui/abi/debug.loongarch64.stderr +++ b/tests/ui/abi/debug.loongarch64.stderr @@ -487,7 +487,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(32 bytes), pointee_align: Some( @@ -560,7 +560,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(128 bytes), pointee_align: Some( @@ -966,7 +966,7 @@ error: fn_abi_of(assoc_test) = FnAbi { }, mode: Direct( ArgAttributes { - regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef, + regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef | NoFree, arg_ext: None, pointee_size: Size(2 bytes), pointee_align: Some( diff --git a/tests/ui/abi/debug.riscv64.stderr b/tests/ui/abi/debug.riscv64.stderr index 68bcd736e47ce..5f05174c12760 100644 --- a/tests/ui/abi/debug.riscv64.stderr +++ b/tests/ui/abi/debug.riscv64.stderr @@ -487,7 +487,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(32 bytes), pointee_align: Some( @@ -560,7 +560,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(128 bytes), pointee_align: Some( @@ -966,7 +966,7 @@ error: fn_abi_of(assoc_test) = FnAbi { }, mode: Direct( ArgAttributes { - regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef, + regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef | NoFree, arg_ext: None, pointee_size: Size(2 bytes), pointee_align: Some( diff --git a/tests/ui/abi/pass-indirectly-attr.stderr b/tests/ui/abi/pass-indirectly-attr.stderr index d8cc39cb2e4e3..7c4b0240e9f31 100644 --- a/tests/ui/abi/pass-indirectly-attr.stderr +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -40,7 +40,7 @@ error: fn_abi_of(extern_c) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(1 bytes), pointee_align: Some( diff --git a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr index 584416f58f861..45edd7bc0e0ee 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(take_va_list) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(32 bytes), pointee_align: Some( diff --git a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr index b5e0e8589af16..1e203b93e66b3 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(take_va_list) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(24 bytes), pointee_align: Some( @@ -105,7 +105,7 @@ error: fn_abi_of(take_va_list_sysv64) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(24 bytes), pointee_align: Some( @@ -185,7 +185,7 @@ error: fn_abi_of(take_va_list_win64) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(24 bytes), pointee_align: Some( From 8832c4246281182267ead51698c2eb1614ce2b11 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:45:55 +0200 Subject: [PATCH 13/14] Refactor/expand rustc_attr_parsing docs --- .../rustc_attr_parsing/src/attributes/mod.rs | 8 +++-- compiler/rustc_attr_parsing/src/context.rs | 4 +++ compiler/rustc_attr_parsing/src/interface.rs | 1 + compiler/rustc_attr_parsing/src/lib.rs | 30 +++++++------------ compiler/rustc_attr_parsing/src/parser.rs | 11 +++++++ 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 1ad5c18f64293..73662bb4a28b2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -1,3 +1,5 @@ +//! Traits for parsing attributes. +//! //! This module defines traits for attribute parsers, little state machines that recognize and parse //! attributes out of a longer list of attributes. The main trait is called [`AttributeParser`]. //! You can find more docs about [`AttributeParser`]s on the trait itself. @@ -7,9 +9,11 @@ //! Specifically, you might not care about managing the state of your [`AttributeParser`] //! state machine yourself. In this case you can choose to implement: //! -//! - [`SingleAttributeParser`](crate::attributes::SingleAttributeParser): makes it easy to implement an attribute which should error if it +//! - [`NoArgsAttributeParser`]: used for implementing an attribute that appears only once and +//! accepts no arguments +//! - [`SingleAttributeParser`]: makes it easy to implement an attribute which should error if it //! appears more than once in a list of attributes -//! - [`CombineAttributeParser`](crate::attributes::CombineAttributeParser): makes it easy to implement an attribute which should combine the +//! - [`CombineAttributeParser`]: makes it easy to implement an attribute which should combine the //! contents of attributes, if an attribute appear multiple times in a list //! //! Attributes should be added to `crate::context::ATTRIBUTE_PARSERS` to be parsed. diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 8d56717be48aa..3ef8c6665e950 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -1,3 +1,4 @@ +//! Context given to attribute parsers when parsing. use std::cell::RefCell; use std::collections::BTreeMap; use std::collections::btree_map::Entry; @@ -846,6 +847,9 @@ impl ShouldEmit { } } +/// The interface for issuing argument parsing related diagnostics. +/// +/// It can be obtained through the [`adcx`](AcceptContext::adcx) method on [`AcceptContext`]. pub(crate) struct AttributeDiagnosticContext<'a, 'f, 'sess> { ctx: &'a mut AcceptContext<'f, 'sess>, custom_suggestions: Vec, diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 6e63bf2d78fdc..cf5a722f0529e 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -1,3 +1,4 @@ +//! API for other crates to parse attributes themselves. use std::convert::identity; #[cfg(debug_assertions)] use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 640817a876eea..98b7bfe8b216e 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -2,9 +2,9 @@ //! //! ## Architecture //! This crate is part of a series of crates and modules that handle attribute processing. -//! - [rustc_hir::attrs](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/index.html): Defines the data structures that store parsed attributes -//! - [rustc_attr_parsing](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_attr_parsing/index.html): This crate, handles the parsing of attributes -//! - (planned) rustc_attr_validation: Will handle attribute validation, logic currently handled in `rustc_passes` +//! - [`rustc_hir::attrs`]: Defines the data structures that store parsed attributes +//! - `rustc_attr_parsing`: This crate, handles the parsing of attributes +//! - [`rustc_passes::check_attr`] handles attribute validation that cannot be done in this crate //! //! The separation between data structures and parsing follows the principle of separation of concerns. //! Data structures (`rustc_hir::attrs`) define what attributes look like after parsing. @@ -13,7 +13,7 @@ //! the parsing logic, making the codebase more modular and maintainable. //! //! ## Background -//! Previously, the compiler had a single attribute definition (`ast::Attribute`) with parsing and +//! Previously, the compiler had a single attribute definition ([`ast::Attribute`]) with parsing and //! validation scattered throughout the codebase. This was reorganized for better maintainability //! (see [#131229](https://github.com/rust-lang/rust/issues/131229)). //! @@ -61,7 +61,7 @@ //! `#[stable(...)]` and `#[unstable()]` cannot occur together, and both semantically define //! a "stability" of an item. So, the stability attribute has an //! [`AttributeParser`](attributes::AttributeParser) that recognizes both the `#[stable()]` -//! and `#[unstable()]` syntactic attributes, and at the end produce a single +//! and `#[unstable()]` syntactic attributes, and at the end produces a single //! [`AttributeKind::Stability`](rustc_hir::attrs::AttributeKind::Stability). //! //! When multiple instances of the same attribute are allowed, they're combined into a single @@ -82,6 +82,9 @@ //! However, sometimes an attributes' parsed form is needed before the HIR is constructed. //! This is referred to as "early" attribute parsing, //! and is performed using the `parse_limited_*` family of functions on `AttributeParser`. +//! +//! [`ast::Attribute`]: rustc_ast::ast::Attribute +//! [`rustc_passes::check_attr`]: ../rustc_passes/check_attr/index.html // tidy-alphabetical-start #![feature(decl_macro)] @@ -91,24 +94,13 @@ // tidy-alphabetical-end #[macro_use] -/// All the individual attribute parsers for each of rustc's built-in attributes. mod attributes; - -/// All the important types given to attribute parsers when parsing -pub(crate) mod context; - -/// Code that other crates interact with, to actually parse a list (or sometimes single) -/// attribute. -mod interface; - -/// Despite this entire module called attribute parsing and the term being a little overloaded, -/// in this module the code lives that actually breaks up tokenstreams into semantic pieces of attributes, -/// like lists or name-value pairs. -pub mod parser; - mod check_cfg; +mod context; mod early_parsed; mod errors; +mod interface; +pub mod parser; mod safety; mod session_diagnostics; mod stability; diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 820400813f910..4d714fccc3f61 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -1,3 +1,8 @@ +//! Parsing of attribute arguments. +//! +//! Depending on the attribute parser, an [`ArgParser`] can be used to parse the arguments given to +//! an attribute. See its documentation for more information. +//! //! This is in essence an (improved) duplicate of `rustc_ast/attr/mod.rs`. //! That module is intended to be deleted in its entirety. //! @@ -89,6 +94,12 @@ impl> Display for PathParser

{ } } +/// Used for parsing attribute arguments. +/// +/// See also [`AttributeDiagnosticContext`], which is the preferred interface for issuing argument +/// parsing related diagnostics. +/// +/// [`AttributeDiagnosticContext`]: crate::context::AttributeDiagnosticContext #[derive(Debug)] #[must_use] pub enum ArgParser { From d4e6f26f93d348f8cba6eb176ddcb2db83630840 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 4 Jun 2026 23:53:12 +0900 Subject: [PATCH 14/14] rustc_target: Use rustc_abi instead of cfg_abi to detect powerpcspe --- compiler/rustc_target/src/asm/mod.rs | 4 ++-- compiler/rustc_target/src/asm/powerpc.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_target/src/asm/mod.rs b/compiler/rustc_target/src/asm/mod.rs index 14fef2880ff68..44e52c9a7e976 100644 --- a/compiler/rustc_target/src/asm/mod.rs +++ b/compiler/rustc_target/src/asm/mod.rs @@ -5,7 +5,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Symbol; -use crate::spec::{Arch, CfgAbi, RelocModel, Target}; +use crate::spec::{Arch, RelocModel, Target}; pub struct ModifierInfo { pub modifier: char, @@ -1001,7 +1001,7 @@ impl InlineAsmClobberAbi { _ => Err(&["C", "system", "efiapi"]), }, InlineAsmArch::PowerPC | InlineAsmArch::PowerPC64 => match name { - "C" | "system" => Ok(if target.cfg_abi == CfgAbi::Spe { + "C" | "system" => Ok(if powerpc::is_spe(target) { InlineAsmClobberAbi::PowerPCSPE } else { InlineAsmClobberAbi::PowerPC diff --git a/compiler/rustc_target/src/asm/powerpc.rs b/compiler/rustc_target/src/asm/powerpc.rs index a09b93c64e0e0..4f478fe8c257c 100644 --- a/compiler/rustc_target/src/asm/powerpc.rs +++ b/compiler/rustc_target/src/asm/powerpc.rs @@ -4,7 +4,7 @@ use rustc_data_structures::fx::FxIndexSet; use rustc_span::Symbol; use super::{InlineAsmArch, InlineAsmType, ModifierInfo}; -use crate::spec::{CfgAbi, RelocModel, Target}; +use crate::spec::{CfgAbi, RelocModel, RustcAbi, Target}; def_reg_class! { PowerPC PowerPCInlineAsmRegClass { @@ -115,6 +115,10 @@ fn reserved_v20to31( } } +pub(crate) fn is_spe(target: &Target) -> bool { + target.rustc_abi == Some(RustcAbi::PowerPcSpe) +} + fn spe_acc_target_check( _arch: InlineAsmArch, _reloc_model: RelocModel, @@ -122,11 +126,7 @@ fn spe_acc_target_check( target: &Target, _is_clobber: bool, ) -> Result<(), &'static str> { - if target.cfg_abi == CfgAbi::Spe { - Ok(()) - } else { - Err("spe_acc is only available on spe targets") - } + if is_spe(target) { Ok(()) } else { Err("spe_acc is only available on spe targets") } } def_regs! {