From 3e034b3f00944707f13926be5b3df7d2b449a841 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 7 Aug 2026 15:10:07 +0300 Subject: [PATCH 01/31] rustc: Tweak the effect of `--jobs` on frontend parallelism --- compiler/rustc_session/src/config.rs | 2 +- src/doc/rustc/src/command-line-arguments.md | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 91eac6613db37..375920be616da 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1723,7 +1723,7 @@ fn parse_jobs_all( check_upper_limit(frontend, opt_name); frontend } - None => jobs.flatten(), + None => None, // default to 1 thread irrespectively of `jobs` for now }, }; let backend = match matches.opt_str("jobs-backend") { diff --git a/src/doc/rustc/src/command-line-arguments.md b/src/doc/rustc/src/command-line-arguments.md index 52dc58f65bb8b..ba4cbb01bcf52 100644 --- a/src/doc/rustc/src/command-line-arguments.md +++ b/src/doc/rustc/src/command-line-arguments.md @@ -498,8 +498,8 @@ more specific `jobs-*` options cannot specify larger values. Parallelism used by compilation stages from lexing to generation of backend IR (e.g. LLVM IR). - If `jobs-frontend` is passed, then it is used as the limit, -- otherwise if `jobs` is passed, then it is used as the limit, -- otherwise `1` is used as the limit (parallelism is disabled), this default may change. +- otherwise `1` is used as the limit (parallelism is disabled), this default may change, + but if it's changed to a larger value the limit from `jobs` will still be respected. In any case the parallelism here may be additionally limited dynamically by jobserver passed from a higher level build system like cargo. @@ -510,8 +510,7 @@ Parallelism used by compilation stages converting backend IR to object files. - If `jobs-backend` is passed, then it is used as the limit, - otherwise if `jobs` is passed, then it is used as the limit, -- otherwise `32` is used as the limit or there's no limit in case of an inherited jobserver, - this default may change. +- otherwise the number of available logical CPUs is used as the limit, this default may change. In any case the parallelism here may be additionally limited dynamically by jobserver passed from a higher level build system like cargo. From 2f9fea1dec8a0d800f32129787b3464e919b300a Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Sat, 20 Jun 2026 19:42:06 +0300 Subject: [PATCH 02/31] Constify more Iterator functions --- library/core/src/iter/traits/iterator.rs | 30 +++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index 0748274c401fc..cc4077d0ee26f 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -306,14 +306,22 @@ pub const trait Iterator { /// ``` #[inline] #[unstable(feature = "iter_advance_by", issue = "77404")] - #[rustc_non_const_trait_method] - fn advance_by(&mut self, n: usize) -> Result<(), NonZero> { + fn advance_by(&mut self, n: usize) -> Result<(), NonZero> + where + Self::Item: [const] Destruct, + { /// Helper trait to specialize `advance_by` via `try_fold` for `Sized` iterators. - trait SpecAdvanceBy { + + #[rustc_const_unstable(feature = "const_iter", issue = "92476")] + const trait SpecAdvanceBy { fn spec_advance_by(&mut self, n: usize) -> Result<(), NonZero>; } - impl SpecAdvanceBy for I { + #[rustc_const_unstable(feature = "const_iter", issue = "92476")] + const impl SpecAdvanceBy for I + where + I::Item: [const] Destruct, + { default fn spec_advance_by(&mut self, n: usize) -> Result<(), NonZero> { for i in 0..n { if self.next().is_none() { @@ -325,13 +333,17 @@ pub const trait Iterator { } } - impl SpecAdvanceBy for I { + #[rustc_const_unstable(feature = "const_iter", issue = "92476")] + const impl SpecAdvanceBy for I + where + I::Item: [const] Destruct, + { fn spec_advance_by(&mut self, n: usize) -> Result<(), NonZero> { let Some(n) = NonZero::new(n) else { return Ok(()); }; - let res = self.try_fold(n, |n, _| NonZero::new(n.get() - 1)); + let res = self.try_fold(n, const |n, _| NonZero::new(n.get() - 1)); match res { None => Ok(()), @@ -384,8 +396,10 @@ pub const trait Iterator { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_non_const_trait_method] - fn nth(&mut self, n: usize) -> Option { + fn nth(&mut self, n: usize) -> Option + where + Self::Item: [const] Destruct, + { self.advance_by(n).ok()?; self.next() } From 7b633cc303e9001faf92dc36233bf7c677ed2ffe Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Sat, 20 Jun 2026 19:43:25 +0300 Subject: [PATCH 03/31] implement const Iterator for Range --- library/core/src/iter/range.rs | 20 ++++++++++++++------ library/core/src/iter/traits/double_ended.rs | 12 ++++++++---- library/core/src/iter/traits/marker.rs | 3 ++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/library/core/src/iter/range.rs b/library/core/src/iter/range.rs index e6acf3081c890..331c367314e63 100644 --- a/library/core/src/iter/range.rs +++ b/library/core/src/iter/range.rs @@ -2,6 +2,7 @@ use super::{ FusedIterator, TrustedLen, TrustedRandomAccess, TrustedRandomAccessNoCoerce, TrustedStep, }; use crate::ascii::Char as AsciiChar; +use crate::marker::Destruct; use crate::mem; use crate::net::{Ipv4Addr, Ipv6Addr}; use crate::num::NonZero; @@ -1000,7 +1001,7 @@ macro_rules! range_incl_exact_iter_impl { } /// Specialization implementations for `Range`. -trait RangeIteratorImpl { +const trait RangeIteratorImpl { type Item; // Iterator @@ -1014,7 +1015,8 @@ trait RangeIteratorImpl { fn spec_advance_back_by(&mut self, n: usize) -> Result<(), NonZero>; } -impl RangeIteratorImpl for ops::Range { +#[rustc_const_unstable(feature = "const_iter", issue = "92476")] +const impl RangeIteratorImpl for ops::Range { type Item = A; #[inline] @@ -1094,7 +1096,8 @@ impl RangeIteratorImpl for ops::Range { } } -impl RangeIteratorImpl for ops::Range { +#[rustc_const_unstable(feature = "const_iter", issue = "92476")] +const impl RangeIteratorImpl for ops::Range { #[inline] fn spec_next(&mut self) -> Option { if self.start < self.end { @@ -1177,7 +1180,8 @@ impl RangeIteratorImpl for ops::Range { } #[stable(feature = "rust1", since = "1.0.0")] -impl Iterator for ops::Range { +#[rustc_const_unstable(feature = "const_iter", issue = "92476")] +const impl Iterator for ops::Range { type Item = A; #[inline] @@ -1230,7 +1234,10 @@ impl Iterator for ops::Range { } #[inline] - fn is_sorted(self) -> bool { + fn is_sorted(self) -> bool + where + Self: [const] Destruct, + { true } @@ -1310,7 +1317,8 @@ range_incl_exact_iter_impl! { } #[stable(feature = "rust1", since = "1.0.0")] -impl DoubleEndedIterator for ops::Range { +#[rustc_const_unstable(feature = "const_iter", issue = "92476")] +const impl DoubleEndedIterator for ops::Range { #[inline] fn next_back(&mut self) -> Option { self.spec_next_back() diff --git a/library/core/src/iter/traits/double_ended.rs b/library/core/src/iter/traits/double_ended.rs index 3df765c3da709..a7c8ec9319a07 100644 --- a/library/core/src/iter/traits/double_ended.rs +++ b/library/core/src/iter/traits/double_ended.rs @@ -185,8 +185,10 @@ pub const trait DoubleEndedIterator: [const] Iterator { /// [`Err(k)`]: Err #[inline] #[unstable(feature = "iter_advance_by", issue = "77404")] - #[rustc_non_const_trait_method] - fn advance_back_by(&mut self, n: usize) -> Result<(), NonZero> { + fn advance_back_by(&mut self, n: usize) -> Result<(), NonZero> + where + Self::Item: [const] Destruct, + { for i in 0..n { if self.next_back().is_none() { // SAFETY: `i` is always less than `n`. @@ -239,8 +241,10 @@ pub const trait DoubleEndedIterator: [const] Iterator { /// ``` #[inline] #[stable(feature = "iter_nth_back", since = "1.37.0")] - #[rustc_non_const_trait_method] - fn nth_back(&mut self, n: usize) -> Option { + fn nth_back(&mut self, n: usize) -> Option + where + Self::Item: [const] Destruct, + { if self.advance_back_by(n).is_err() { return None; } diff --git a/library/core/src/iter/traits/marker.rs b/library/core/src/iter/traits/marker.rs index 1e6704fe524a9..fd96424566bcc 100644 --- a/library/core/src/iter/traits/marker.rs +++ b/library/core/src/iter/traits/marker.rs @@ -115,4 +115,5 @@ pub unsafe trait InPlaceIterable { /// for details. Consumers are free to rely on the invariants in unsafe code. #[unstable(feature = "trusted_step", issue = "85731")] #[rustc_specialization_trait] -pub unsafe trait TrustedStep: Step + Copy {} +#[rustc_const_unstable(feature = "const_iter", issue = "92476")] +pub const unsafe trait TrustedStep: [const] Step + Copy {} From 0ce53949082b5f9992b5159387ca52ad09b1db76 Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Sat, 20 Jun 2026 19:43:57 +0300 Subject: [PATCH 04/31] Revert some const hacks --- library/core/src/slice/cmp.rs | 10 ++-------- library/core/src/slice/mod.rs | 7 ++----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/library/core/src/slice/cmp.rs b/library/core/src/slice/cmp.rs index cd3fc889ecdd5..fcdde758fa7df 100644 --- a/library/core/src/slice/cmp.rs +++ b/library/core/src/slice/cmp.rs @@ -126,14 +126,11 @@ where // Implemented as explicit indexing rather // than zipped iterators for performance reasons. // See PR https://github.com/rust-lang/rust/pull/116846 - // FIXME(const_hack): make this a `for idx in 0..len` loop. - let mut idx = 0; - while idx < len { + for idx in 0..len { // SAFETY: idx < len, so both are in-bounds and readable if unsafe { *lhs.add(idx) != *rhs.add(idx) } { return false; } - idx += 1; } true @@ -224,11 +221,8 @@ const fn chaining_impl<'l, 'r, A: PartialOrd, B, C>( let lhs = &left[..l]; let rhs = &right[..l]; - // FIXME(const-hack): revert this to `for i in 0..l` once `impl const Iterator for Range` - let mut i: usize = 0; - while i < l { + for i in 0..l { elem_chain(&lhs[i], &rhs[i])?; - i += 1; } len_chain(&left.len(), &right.len()) diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index f787b7994ba9d..6efc9e4f28a16 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -5628,11 +5628,8 @@ where // But since it can't be relied on we also have an explicit specialization for T: Copy. let len = self.len(); let src = &src[..len]; - // FIXME(const_hack): make this a `for idx in 0..self.len()` loop. - let mut idx = 0; - while idx < self.len() { - self[idx].clone_from(&src[idx]); - idx += 1; + for i in 0..len { + self[i].clone_from(&src[i]); } } } From 81792041e9a04e6a9a18e9c7023a156e907962d0 Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Sat, 20 Jun 2026 19:44:08 +0300 Subject: [PATCH 05/31] Fix tests --- tests/codegen-llvm/array-cmp.rs | 21 ++++++----- tests/ui/consts/const-for-feature-gate.rs | 2 + tests/ui/consts/const-for-feature-gate.stderr | 36 ++++++++++++++++-- tests/ui/consts/const-for.rs | 5 +-- tests/ui/consts/const-for.stderr | 20 ---------- tests/ui/consts/control-flow/loop.rs | 7 ++-- tests/ui/consts/control-flow/loop.stderr | 37 ------------------- .../ui/typeck/typeck_type_placeholder_item.rs | 1 + .../typeck_type_placeholder_item.stderr | 20 ++++++++-- 9 files changed, 68 insertions(+), 81 deletions(-) delete mode 100644 tests/ui/consts/const-for.stderr delete mode 100644 tests/ui/consts/control-flow/loop.stderr diff --git a/tests/codegen-llvm/array-cmp.rs b/tests/codegen-llvm/array-cmp.rs index 5b0a802f4097e..2eb498d08f254 100644 --- a/tests/codegen-llvm/array-cmp.rs +++ b/tests/codegen-llvm/array-cmp.rs @@ -42,6 +42,16 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool { // CHECK: %[[EQ00:.+]] = icmp eq i16 %[[A00]], %[[B00]] // CHECK-NEXT: br i1 %[[EQ00]], label %[[L01:.+]], label %[[EXIT_S:.+]] + // CHECK: [[L01]]: + // CHECK: %[[PA01:.+]] = getelementptr{{.+}}i8, ptr %a, {{i32|i64}} 2 + // CHECK: %[[PB01:.+]] = getelementptr{{.+}}i8, ptr %b, {{i32|i64}} 2 + // CHECK: %[[A01:.+]] = load i16, ptr %[[PA01]] + // CHECK: %[[B01:.+]] = load i16, ptr %[[PB01]] + // CHECK-NOT: cmp + // CHECK: %[[EQ01:.+]] = icmp eq i16 %[[A01]], %[[B01]] + // CHECK-NEXT: br i1 %[[EQ01]], label %[[L10:.+]], label %[[EXIT_U:.+]] + + // CHECK: [[L10]]: // CHECK: %[[PA10:.+]] = getelementptr{{.+}}i8, ptr %a, {{i32|i64}} 4 // CHECK: %[[PB10:.+]] = getelementptr{{.+}}i8, ptr %b, {{i32|i64}} 4 // CHECK: %[[A10:.+]] = load i16, ptr %[[PA10]] @@ -57,16 +67,7 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool { // CHECK: %[[B11:.+]] = load i16, ptr %[[PB11]] // CHECK-NOT: cmp // CHECK: %[[EQ11:.+]] = icmp eq i16 %[[A11]], %[[B11]] - // CHECK-NEXT: br i1 %[[EQ11]], label %[[DONE:.+]], label %[[EXIT_U:.+]] - - // CHECK: [[L01]]: - // CHECK: %[[PA01:.+]] = getelementptr{{.+}}i8, ptr %a, {{i32|i64}} 2 - // CHECK: %[[PB01:.+]] = getelementptr{{.+}}i8, ptr %b, {{i32|i64}} 2 - // CHECK: %[[A01:.+]] = load i16, ptr %[[PA01]] - // CHECK: %[[B01:.+]] = load i16, ptr %[[PB01]] - // CHECK-NOT: cmp - // CHECK: %[[EQ01:.+]] = icmp eq i16 %[[A01]], %[[B01]] - // CHECK-NEXT: br i1 %[[EQ01]], label %{{.+}}, label %[[EXIT_U]] + // CHECK-NEXT: br i1 %[[EQ11]], label %[[DONE:.+]], label %[[EXIT_U]] // CHECK: [[DONE]]: // LLVM22: %[[RET:.+]] = phi i1 [ %{{.+}}, %[[EXIT_S]] ], [ %{{.+}}, %[[EXIT_U]] ], [ true, %[[L11]] ] diff --git a/tests/ui/consts/const-for-feature-gate.rs b/tests/ui/consts/const-for-feature-gate.rs index b643e63c09690..1024beace3d89 100644 --- a/tests/ui/consts/const-for-feature-gate.rs +++ b/tests/ui/consts/const-for-feature-gate.rs @@ -3,7 +3,9 @@ const _: () = { for _ in 0..5 {} //~^ ERROR cannot use `for` + //~| ERROR `IntoIterator` is not yet stable //~| ERROR cannot use `for` + //~| ERROR `Iterator` is not yet stable }; fn main() {} diff --git a/tests/ui/consts/const-for-feature-gate.stderr b/tests/ui/consts/const-for-feature-gate.stderr index 29db5d24ac866..5876f1476341c 100644 --- a/tests/ui/consts/const-for-feature-gate.stderr +++ b/tests/ui/consts/const-for-feature-gate.stderr @@ -1,20 +1,48 @@ -error[E0015]: cannot use `for` loop on `std::ops::Range` in constants +error[E0658]: cannot use `for` loop on `std::ops::Range` in constants --> $DIR/const-for-feature-gate.rs:4:14 | LL | for _ in 0..5 {} | ^^^^ | = note: calls in constants are limited to constant functions, tuple structs and tuple variants + = note: see issue #143874 for more information + = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0015]: cannot use `for` loop on `std::ops::Range` in constants +error: `IntoIterator` is not yet stable as a const trait + --> $DIR/const-for-feature-gate.rs:4:14 + | +LL | for _ in 0..5 {} + | ^^^^ + | +help: add `#![feature(const_iter)]` to the crate attributes to enable + | +LL + #![feature(const_iter)] + | + +error[E0658]: cannot use `for` loop on `std::ops::Range` in constants --> $DIR/const-for-feature-gate.rs:4:14 | LL | for _ in 0..5 {} | ^^^^ | = note: calls in constants are limited to constant functions, tuple structs and tuple variants + = note: see issue #143874 for more information + = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error: aborting due to 2 previous errors +error: `Iterator` is not yet stable as a const trait + --> $DIR/const-for-feature-gate.rs:4:14 + | +LL | for _ in 0..5 {} + | ^^^^ + | +help: add `#![feature(const_iter)]` to the crate attributes to enable + | +LL + #![feature(const_iter)] + | + +error: aborting due to 4 previous errors -For more information about this error, try `rustc --explain E0015`. +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/consts/const-for.rs b/tests/ui/consts/const-for.rs index 6f7895457c53d..5b9bcff410144 100644 --- a/tests/ui/consts/const-for.rs +++ b/tests/ui/consts/const-for.rs @@ -1,9 +1,8 @@ -#![feature(const_for)] +//@ check-pass +#![feature(const_trait_impl,const_iter,const_for)] const _: () = { for _ in 0..5 {} - //~^ ERROR cannot use `for` - //~| ERROR cannot use `for` }; fn main() {} diff --git a/tests/ui/consts/const-for.stderr b/tests/ui/consts/const-for.stderr deleted file mode 100644 index d1308a8dedc85..0000000000000 --- a/tests/ui/consts/const-for.stderr +++ /dev/null @@ -1,20 +0,0 @@ -error[E0015]: cannot use `for` loop on `std::ops::Range` in constants - --> $DIR/const-for.rs:4:14 - | -LL | for _ in 0..5 {} - | ^^^^ - | - = note: calls in constants are limited to constant functions, tuple structs and tuple variants - -error[E0015]: cannot use `for` loop on `std::ops::Range` in constants - --> $DIR/const-for.rs:4:14 - | -LL | for _ in 0..5 {} - | ^^^^ - | - = note: calls in constants are limited to constant functions, tuple structs and tuple variants - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0015`. diff --git a/tests/ui/consts/control-flow/loop.rs b/tests/ui/consts/control-flow/loop.rs index b02c31c4c25b5..7da88dfd2ac89 100644 --- a/tests/ui/consts/control-flow/loop.rs +++ b/tests/ui/consts/control-flow/loop.rs @@ -1,3 +1,6 @@ +//@ check-pass +#![feature(const_iter,const_trait_impl)] + const _: () = loop { break (); }; static FOO: i32 = loop { break 4; }; @@ -51,14 +54,10 @@ const _: i32 = { let mut x = 0; for i in 0..4 { - //~^ ERROR: cannot use `for` - //~| ERROR: cannot use `for` x += i; } for i in 0..4 { - //~^ ERROR: cannot use `for` - //~| ERROR: cannot use `for` x += i; } diff --git a/tests/ui/consts/control-flow/loop.stderr b/tests/ui/consts/control-flow/loop.stderr deleted file mode 100644 index b91371f9dc218..0000000000000 --- a/tests/ui/consts/control-flow/loop.stderr +++ /dev/null @@ -1,37 +0,0 @@ -error[E0015]: cannot use `for` loop on `std::ops::Range` in constants - --> $DIR/loop.rs:53:14 - | -LL | for i in 0..4 { - | ^^^^ - | - = note: calls in constants are limited to constant functions, tuple structs and tuple variants - -error[E0015]: cannot use `for` loop on `std::ops::Range` in constants - --> $DIR/loop.rs:53:14 - | -LL | for i in 0..4 { - | ^^^^ - | - = note: calls in constants are limited to constant functions, tuple structs and tuple variants - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error[E0015]: cannot use `for` loop on `std::ops::Range` in constants - --> $DIR/loop.rs:59:14 - | -LL | for i in 0..4 { - | ^^^^ - | - = note: calls in constants are limited to constant functions, tuple structs and tuple variants - -error[E0015]: cannot use `for` loop on `std::ops::Range` in constants - --> $DIR/loop.rs:59:14 - | -LL | for i in 0..4 { - | ^^^^ - | - = note: calls in constants are limited to constant functions, tuple structs and tuple variants - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error: aborting due to 4 previous errors - -For more information about this error, try `rustc --explain E0015`. diff --git a/tests/ui/typeck/typeck_type_placeholder_item.rs b/tests/ui/typeck/typeck_type_placeholder_item.rs index 7616e391a35a9..2eda0c5863471 100644 --- a/tests/ui/typeck/typeck_type_placeholder_item.rs +++ b/tests/ui/typeck/typeck_type_placeholder_item.rs @@ -239,5 +239,6 @@ fn evens_squared(n: usize) -> _ { const _: _ = (1..10).filter(|x| x % 2 == 0).map(|x| x * x); //~^ ERROR the placeholder +//~| ERROR `Iterator` is not yet stable //~| ERROR cannot call //~| ERROR cannot call diff --git a/tests/ui/typeck/typeck_type_placeholder_item.stderr b/tests/ui/typeck/typeck_type_placeholder_item.stderr index 2772d55f953a8..469c41b286a75 100644 --- a/tests/ui/typeck/typeck_type_placeholder_item.stderr +++ b/tests/ui/typeck/typeck_type_placeholder_item.stderr @@ -678,13 +678,27 @@ LL | fn map(_: fn() -> Option<&'static T>) -> Option { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: calls in constants are limited to constant functions, tuple structs and tuple variants -error[E0015]: cannot call non-const method ` as Iterator>::filter::<{closure@$DIR/typeck_type_placeholder_item.rs:240:29: 240:32}>` in constants +error[E0658]: cannot call conditionally-const method ` as Iterator>::filter::<{closure@$DIR/typeck_type_placeholder_item.rs:240:29: 240:32}>` in constants --> $DIR/typeck_type_placeholder_item.rs:240:22 | LL | const _: _ = (1..10).filter(|x| x % 2 == 0).map(|x| x * x); | ^^^^^^^^^^^^^^^^^^^^^^ | = note: calls in constants are limited to constant functions, tuple structs and tuple variants + = note: see issue #143874 for more information + = help: add `#![feature(const_trait_impl)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: `Iterator` is not yet stable as a const trait + --> $DIR/typeck_type_placeholder_item.rs:240:14 + | +LL | const _: _ = (1..10).filter(|x| x % 2 == 0).map(|x| x * x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add `#![feature(const_iter)]` to the crate attributes to enable + | +LL + #![feature(const_iter)] + | error[E0015]: cannot call non-const method `, {closure@$DIR/typeck_type_placeholder_item.rs:240:29: 240:32}> as Iterator>::map::` in constants --> $DIR/typeck_type_placeholder_item.rs:240:45 @@ -694,7 +708,7 @@ LL | const _: _ = (1..10).filter(|x| x % 2 == 0).map(|x| x * x); | = note: calls in constants are limited to constant functions, tuple structs and tuple variants -error: aborting due to 83 previous errors +error: aborting due to 84 previous errors -Some errors have detailed explanations: E0015, E0046, E0121, E0282, E0403. +Some errors have detailed explanations: E0015, E0046, E0121, E0282, E0403, E0658. For more information about an error, try `rustc --explain E0015`. From 1b8478a11606eda041fd01e75c2f45f0bcc67e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 14 Sep 2026 15:37:33 +0200 Subject: [PATCH 06/31] yeet DeepRegionResolver --- compiler/rustc_infer/src/infer/mod.rs | 14 +++++ .../src/infer/outlives/obligations.rs | 1 - compiler/rustc_infer/src/infer/resolve.rs | 51 ------------------- .../src/solve/inspect/analyse.rs | 10 ---- .../src/traits/outlives_bounds.rs | 6 +-- .../src/traits/project.rs | 6 +-- .../rustc_traits/src/coroutine_witnesses.rs | 18 +++---- 7 files changed, 27 insertions(+), 79 deletions(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 0e3292fc0c497..9b1cc5af5e203 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1460,6 +1460,20 @@ impl<'tcx> InferCtxt<'tcx> { value.fold_with(&mut r) } + /// Where possible, replaces type/const/region variables in `value` with their final value. + /// If a type/const/region variable has not (yet) been unified, it is left as is. + /// + /// This is an idempotent operation that does not affect inference state in any way, + /// which means it's safe to call this function at will. + pub fn deeply_resolve_via_unification_table(&self, value: T) -> T + where + T: TypeFoldable>, + { + use rustc_middle::ty::InferCtxtLike; + #[allow(rustc::usage_of_type_ir_traits)] + InferCtxtLike::deeply_resolve_via_unification_table(self, value) + } + pub fn resolve_numeric_literals_with_default(&self, value: T) -> T where T: TypeFoldable>, diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 60004c709f4ac..e5a1ba3e5004e 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -335,7 +335,6 @@ impl<'tcx> InferCtxt<'tcx> { /// right before lexical region resolution. #[instrument(level = "debug", skip(self, outlives_env))] pub fn process_registered_region_obligations(&self, outlives_env: &OutlivesEnvironment<'tcx>) { - use rustc_type_ir::InferCtxtLike; assert!(!self.in_snapshot(), "cannot process registered region obligations in a snapshot"); if self.tcx.assumptions_on_binders() { diff --git a/compiler/rustc_infer/src/infer/resolve.rs b/compiler/rustc_infer/src/infer/resolve.rs index 1ced1b336c817..42877441c33c6 100644 --- a/compiler/rustc_infer/src/infer/resolve.rs +++ b/compiler/rustc_infer/src/infer/resolve.rs @@ -67,57 +67,6 @@ impl<'a, 'tcx> TypeFolder> for DeepResolverIgnoringRegions<'a, 'tcx } } -/// The region resolver resolves region variables to the variable with the -/// least variable id. It is used when normalizing projections to avoid -/// hitting the recursion limit by creating many versions of a predicate -/// for types that in the end have to unify. -/// -/// If you want to resolve type and const variables as well, call -/// [InferCtxt::deeply_resolve_ignoring_regions] first. -pub struct DeepRegionResolver<'a, 'tcx> { - infcx: &'a InferCtxt<'tcx>, -} - -impl<'a, 'tcx> DeepRegionResolver<'a, 'tcx> { - pub fn new(infcx: &'a InferCtxt<'tcx>) -> Self { - DeepRegionResolver { infcx } - } -} - -impl<'a, 'tcx> TypeFolder> for DeepRegionResolver<'a, 'tcx> { - fn cx(&self) -> TyCtxt<'tcx> { - self.infcx.tcx - } - - fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - if !t.has_infer_regions() { - t // micro-optimize -- if there is nothing in this type that this fold affects... - } else { - t.super_fold_with(self) - } - } - - fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { - match r.kind() { - ty::ReVar(vid) => self - .infcx - .inner - .borrow_mut() - .unwrap_region_constraints() - .shallow_resolve_region_var(TypeFolder::cx(self), vid), - _ => r, - } - } - - fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> { - if !ct.has_infer_regions() { - ct // micro-optimize -- if there is nothing in this const that this fold affects... - } else { - ct.super_fold_with(self) - } - } -} - /////////////////////////////////////////////////////////////////////////// // FULL TYPE RESOLUTION diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index 19d423cc0ecfd..73d17cccd3905 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -142,8 +142,6 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { fields(goal = ?self.goal.goal, steps = ?self.steps) )] pub fn instantiate_impl_args(&self, span: Span) -> ty::GenericArgsRef<'tcx> { - use rustc_middle::ty::InferCtxtLike; - let infcx = self.goal.infcx; let mut orig_values = self.goal.orig_values.clone(); @@ -166,9 +164,6 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { self.final_state, ); - // We *want* this folder to live in `rustc_type_ir`. Our best way to call into it is - // through `InferCtxtLike` and it is not defined as an inherent method on `InferCtxt`. - #[allow(rustc::usage_of_type_ir_traits)] return infcx.deeply_resolve_via_unification_table(impl_args); } inspect::ProbeStep::AddGoal(..) => {} @@ -341,8 +336,6 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { root: inspect::GoalEvaluation>, source: GoalSource, ) -> Self { - use rustc_middle::ty::InferCtxtLike; - let infcx = <&SolverDelegate<'tcx>>::from(infcx); let prev_universe = infcx.universe(); @@ -362,9 +355,6 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { depth, orig_values, prev_universe, - // We *want* this folder to live in `rustc_type_ir`. Our best way to call into it is - // through `InferCtxtLike` and it is not defined as an inherent method on `InferCtxt`. - #[allow(rustc::usage_of_type_ir_traits)] goal: infcx.deeply_resolve_via_unification_table(uncanonicalized_goal), result, final_revision, diff --git a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs index 8a464bfcf1024..fad89d17cbf44 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs @@ -1,11 +1,10 @@ use rustc_infer::infer::InferOk; use rustc_infer::infer::canonical::QueryRegionConstraint; -use rustc_infer::infer::resolve::DeepRegionResolver; use rustc_infer::traits::query::type_op::ImpliedOutlivesBounds; use rustc_macros::extension; use rustc_middle::infer::canonical::{OriginalQueryValues, QueryRegionConstraints}; pub use rustc_middle::traits::query::OutlivesBound; -use rustc_middle::ty::{self, ParamEnv, Ty, TypeFolder, TypeVisitableExt}; +use rustc_middle::ty::{self, ParamEnv, Ty, TypeVisitableExt}; use rustc_span::def_id::LocalDefId; use tracing::instrument; @@ -39,8 +38,7 @@ fn implied_outlives_bounds<'a, 'tcx>( ty: Ty<'tcx>, disable_implied_bounds_hack: bool, ) -> Vec> { - let ty = infcx.deeply_resolve_ignoring_regions(ty); - let ty = DeepRegionResolver::new(infcx).fold_ty(ty); + let ty = infcx.deeply_resolve_via_unification_table(ty); // We do not expect existential variables in implied bounds. // We may however encounter unconstrained lifetime variables diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 496ddbfebd4f0..3eda169173e38 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -7,14 +7,12 @@ use rustc_errors::ErrorGuaranteed; use rustc_hir::attrs::lang_items::LangItem; use rustc_hir::def_id::DefId; use rustc_infer::infer::DefineOpaqueTypes; -use rustc_infer::infer::resolve::DeepRegionResolver; use rustc_infer::traits::{ObligationCauseCode, PredicateObligations}; use rustc_middle::traits::select::OverflowError; use rustc_middle::traits::{BuiltinImplSource, ImplSource, ImplSourceUserDefinedData}; use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_middle::ty::{ - self, FieldInfo, Term, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, TypingMode, Unnormalized, - Upcast, + self, FieldInfo, Term, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized, Upcast, }; use rustc_middle::{bug, span_bug}; use rustc_span::sym; @@ -1326,7 +1324,7 @@ fn confirm_candidate<'cx, 'tcx>( if let Ok(Projected::Progress(progress)) = &mut result && progress.term.has_infer_regions() { - progress.term = progress.term.fold_with(&mut DeepRegionResolver::new(selcx.infcx)); + progress.term = selcx.infcx.deeply_resolve_via_unification_table(progress.term); } result diff --git a/compiler/rustc_traits/src/coroutine_witnesses.rs b/compiler/rustc_traits/src/coroutine_witnesses.rs index c3e4bfdb45e3a..624db7747d1f0 100644 --- a/compiler/rustc_traits/src/coroutine_witnesses.rs +++ b/compiler/rustc_traits/src/coroutine_witnesses.rs @@ -1,9 +1,8 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::canonical::QueryRegionConstraint; use rustc_infer::infer::canonical::query_response::make_query_region_constraints; -use rustc_infer::infer::resolve::DeepRegionResolver; use rustc_infer::traits::{Obligation, ObligationCause}; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, fold_regions}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, fold_regions}; use rustc_span::def_id::DefId; use rustc_trait_selection::traits::{ObligationCtxt, with_replaced_escaping_bound_vars}; @@ -80,13 +79,14 @@ fn compute_assumptions<'tcx>( let region_assumptions = infcx.take_registered_region_assumptions(); let region_constraints = infcx.take_and_reset_region_constraints(); - let constraints = make_query_region_constraints( - region_obligations, - ®ion_constraints, - region_assumptions, - ) - .constraints - .fold_with(&mut DeepRegionResolver::new(&infcx)); + let constraints = infcx.deeply_resolve_via_unification_table( + make_query_region_constraints( + region_obligations, + ®ion_constraints, + region_assumptions, + ) + .constraints, + ); tcx.mk_outlives_from_iter( constraints From 521c4ebd93663eb0683cb26159fb9fee6d4d0cb3 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 2 Sep 2026 13:26:19 +1000 Subject: [PATCH 07/31] Improve `build_reduced_graph_for_use_tree` arguments It makes sense to put the top-level item first, before the use tree within the item. And it makes sense to have a single function-level comment explaining this rather than inline comments at the definition and every call site. --- .../rustc_resolve/src/build_reduced_graph.rs | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index bf0a6d7ee3b49..0557099208dea 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -583,16 +583,17 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { } } + /// Note: + /// - `item` is the top-level `use` item. + /// - `use_tree` is the particular use tree within the top-level `use` item. fn build_reduced_graph_for_use_tree( &mut self, - // This particular use tree + item: &Item, use_tree: &ast::UseTree, id: NodeId, parent_prefix: &[Segment], nested: bool, list_stem: bool, - // The whole `use` item - item: &Item, vis: Visibility, root_span: Span, feed: TyCtxtFeed<'tcx, LocalDefId>, @@ -756,9 +757,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { for &(ref tree, id) in items { self.with_owner(id, None, DefKind::Use, use_tree.span(), |this, feed| { this.build_reduced_graph_for_use_tree( - // This particular use tree - tree, id, &prefix, true, false, // The whole `use` item - item, vis, root_span, feed, + item, tree, id, &prefix, true, false, vis, root_span, feed, ) }); } @@ -775,20 +774,11 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { prefix: ast::Path::from_ident(Ident::new(kw::SelfLower, new_span)), kind: ast::UseTreeKind::Simple(Some(Ident::new(kw::Underscore, new_span))), }; + let vis = Visibility::Restricted( + self.parent_scope.module.nearest_parent_mod().expect_local(), + ); self.build_reduced_graph_for_use_tree( - // This particular use tree - &tree, - id, - &prefix, - true, - true, - // The whole `use` item - item, - Visibility::Restricted( - self.parent_scope.module.nearest_parent_mod().expect_local(), - ), - root_span, - feed, + item, &tree, id, &prefix, true, true, vis, root_span, feed, ); } } @@ -835,14 +825,12 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { match item.kind { ItemKind::Use(ref use_tree) => { self.build_reduced_graph_for_use_tree( - // This particular use tree + item, use_tree, item.id, &[], false, false, - // The whole `use` item - item, vis, use_tree.span(), feed, From 605149c52338a49234aab231ef3e13659ddfaf52 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 2 Sep 2026 14:13:45 +1000 Subject: [PATCH 08/31] Remove an out-of-date comment `is_public` is not relevant here. --- compiler/rustc_resolve/src/check_unused.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 8337b2848edc3..2b4fe2cbd5c06 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -247,10 +247,9 @@ impl<'a, 'ra, 'tcx> Visitor<'a> for UnusedImportCheckVisitor<'a, 'ra, 'tcx> { fn visit_item(&mut self, item: &'a ast::Item) { self.item_span = item.span_with_attributes(); match &item.kind { - // Ignore is_public import statements because there's no way to be sure - // whether they're used or not. Also ignore imports with a dummy span - // because this means that they were generated in some fashion by the - // compiler and we don't need to consider them. + // Ignore imports with a dummy span because this means that they + // were generated in some fashion by the compiler and we don't need + // to consider them. ast::ItemKind::Use(..) if item.span.is_dummy() => return, // Use the base UseTree's NodeId as the item id // This allows the grouping of all the lints in the same item From ac9666f3241e7d776d782c306aee2c14eeffe579 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Tue, 15 Sep 2026 10:51:16 +0330 Subject: [PATCH 09/31] Add regression test for malformed RPITIT bound ICE with the new solver --- ...n-trait-malformed-bound-globally-156100.rs | 16 +++++ ...ait-malformed-bound-globally-156100.stderr | 59 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 tests/ui/traits/next-solver/rpit-in-trait-malformed-bound-globally-156100.rs create mode 100644 tests/ui/traits/next-solver/rpit-in-trait-malformed-bound-globally-156100.stderr diff --git a/tests/ui/traits/next-solver/rpit-in-trait-malformed-bound-globally-156100.rs b/tests/ui/traits/next-solver/rpit-in-trait-malformed-bound-globally-156100.rs new file mode 100644 index 0000000000000..162e291e463df --- /dev/null +++ b/tests/ui/traits/next-solver/rpit-in-trait-malformed-bound-globally-156100.rs @@ -0,0 +1,16 @@ +//@ compile-flags: -Znext-solver=globally + +// Regression test for . + +trait X { + fn into_iter(&self) -> impl Iterator { + //~^ ERROR trait takes 0 generic arguments but 1 generic argument was supplied + //~| ERROR trait takes 0 generic arguments but 1 generic argument was supplied + //~| ERROR trait takes 0 generic arguments but 1 generic argument was supplied + //~| ERROR trait takes 0 generic arguments but 1 generic argument was supplied + //~| ERROR overflow evaluating the requirement + todo!() + } +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/rpit-in-trait-malformed-bound-globally-156100.stderr b/tests/ui/traits/next-solver/rpit-in-trait-malformed-bound-globally-156100.stderr new file mode 100644 index 0000000000000..e3b4c32047af7 --- /dev/null +++ b/tests/ui/traits/next-solver/rpit-in-trait-malformed-bound-globally-156100.stderr @@ -0,0 +1,59 @@ +error[E0107]: trait takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/rpit-in-trait-malformed-bound-globally-156100.rs:6:33 + | +LL | fn into_iter(&self) -> impl Iterator { + | ^^^^^^^^ expected 0 generic arguments + | +help: turn the generic argument into an associated item binding + | +LL | fn into_iter(&self) -> impl Iterator { + | ++++++ + +error[E0107]: trait takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/rpit-in-trait-malformed-bound-globally-156100.rs:6:33 + | +LL | fn into_iter(&self) -> impl Iterator { + | ^^^^^^^^ expected 0 generic arguments + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: turn the generic argument into an associated item binding + | +LL | fn into_iter(&self) -> impl Iterator { + | ++++++ + +error[E0107]: trait takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/rpit-in-trait-malformed-bound-globally-156100.rs:6:33 + | +LL | fn into_iter(&self) -> impl Iterator { + | ^^^^^^^^ expected 0 generic arguments + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: turn the generic argument into an associated item binding + | +LL | fn into_iter(&self) -> impl Iterator { + | ++++++ + +error[E0107]: trait takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/rpit-in-trait-malformed-bound-globally-156100.rs:6:33 + | +LL | fn into_iter(&self) -> impl Iterator { + | ^^^^^^^^ expected 0 generic arguments + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: turn the generic argument into an associated item binding + | +LL | fn into_iter(&self) -> impl Iterator { + | ++++++ + +error[E0275]: overflow evaluating the requirement `impl Iterator == _` + --> $DIR/rpit-in-trait-malformed-bound-globally-156100.rs:6:28 + | +LL | fn into_iter(&self) -> impl Iterator { + | ^^^^^^^^^^^^^^^^ + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`rpit_in_trait_malformed_bound_globally_156100`) + +error: aborting due to 5 previous errors + +Some errors have detailed explanations: E0107, E0275. +For more information about an error, try `rustc --explain E0107`. From e251a4ba987d749890d92260208173536c90bd92 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 15 Sep 2026 21:43:05 +0200 Subject: [PATCH 10/31] wasm: return early on f128/i128 changes in later commits need this case out of the way --- compiler/rustc_target/src/callconv/wasm.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_target/src/callconv/wasm.rs b/compiler/rustc_target/src/callconv/wasm.rs index b84bcd5903257..434e495601596 100644 --- a/compiler/rustc_target/src/callconv/wasm.rs +++ b/compiler/rustc_target/src/callconv/wasm.rs @@ -57,19 +57,20 @@ where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { - ret.extend_integer_width_to(32); - if ret.layout.is_aggregate() && !unwrap_trivial_aggregate(cx, ret) { + // `long double`, `__int128_t` and `__uint128_t` use an indirect return + if let BackendRepr::Scalar(scalar) = ret.layout.backend_repr + && matches!( + scalar.primitive(), + Primitive::Int(Integer::I128, _) | Primitive::Float(Float::F128) + ) + { ret.make_indirect(); + return; } - // `long double`, `__int128_t` and `__uint128_t` use an indirect return - if let BackendRepr::Scalar(scalar) = ret.layout.backend_repr { - match scalar.primitive() { - Primitive::Int(Integer::I128, _) | Primitive::Float(Float::F128) => { - ret.make_indirect(); - } - _ => {} - } + ret.extend_integer_width_to(32); + if ret.layout.is_aggregate() && !unwrap_trivial_aggregate(cx, ret) { + ret.make_indirect(); } } From 9d60f8642ce0dc217254aebb3d9bddcc19eeebe5 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 15 Sep 2026 22:21:42 +0200 Subject: [PATCH 11/31] rename `unwrap_trivial_aggregate` -> `is_aggregate_for_abi` --- compiler/rustc_target/src/callconv/wasm.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_target/src/callconv/wasm.rs b/compiler/rustc_target/src/callconv/wasm.rs index 434e495601596..0ee442b879606 100644 --- a/compiler/rustc_target/src/callconv/wasm.rs +++ b/compiler/rustc_target/src/callconv/wasm.rs @@ -39,17 +39,22 @@ where found.filter(|scalar| scalar.size == layout.size) } -fn unwrap_trivial_aggregate<'a, Ty, C>(cx: &C, val: &mut ArgAbi<'a, Ty>) -> bool +/// Return whether the value should be passed as an aggregate (i.e. indirectly). +fn is_aggregate_for_abi<'a, Ty, C>(cx: &C, val: &mut ArgAbi<'a, Ty>) -> bool where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { - let Some(scalar) = singleton_scalar(cx, val.layout) else { + if !val.layout.is_aggregate() { return false; + } + + let Some(scalar) = singleton_scalar(cx, val.layout) else { + return true; }; val.cast_to(scalar); - true + false } fn classify_ret<'a, Ty, C>(cx: &C, ret: &mut ArgAbi<'a, Ty>) @@ -69,7 +74,7 @@ where } ret.extend_integer_width_to(32); - if ret.layout.is_aggregate() && !unwrap_trivial_aggregate(cx, ret) { + if is_aggregate_for_abi(cx, ret) { ret.make_indirect(); } } @@ -88,7 +93,7 @@ where return; } arg.extend_integer_width_to(32); - if arg.layout.is_aggregate() && !unwrap_trivial_aggregate(cx, arg) { + if is_aggregate_for_abi(cx, arg) { arg.make_indirect(); } } From 2c9ec83ff818f0e1cdfc2ff293f2251b16b656d8 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Wed, 19 Aug 2026 16:25:24 -0400 Subject: [PATCH 12/31] Stop building unused libompdevice code --- src/bootstrap/src/core/build_steps/dist.rs | 4 +- src/bootstrap/src/core/build_steps/llvm.rs | 170 +++++++----------- .../src/offload/installation.md | 4 + 3 files changed, 72 insertions(+), 106 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index f9c6efe35bc48..7073b334c414d 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -2890,10 +2890,10 @@ impl CommandLineStep for Offload { tarball.set_overlay(OverlayKind::Offload); tarball.is_preview(true); - let omp_offload_libdir = builder.out.join(target).join("offload").join("lib"); + let omp_offload_libdir = omp_offload.lib_dir(); for path in omp_offload.artifact_paths_with_symlink_targets() { - let relative = t!(path.strip_prefix(&omp_offload_libdir)); + let relative = t!(path.strip_prefix(omp_offload_libdir)); let destdir = target_libdir.join(relative.parent().unwrap()); tarball.add_file(path, destdir, FileType::NativeLibrary); diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 91a6ecf4d6bb9..df403175223a1 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -1215,9 +1215,15 @@ impl CommandLineStep for RustOffload { pub struct BuiltOmpOffload { /// Path to the omp and offload dylibs. offload: Vec, + /// Directory the dylibs were installed into. + lib_dir: PathBuf, } impl BuiltOmpOffload { + pub fn lib_dir(&self) -> &Path { + &self.lib_dir + } + pub fn artifact_paths_with_symlink_targets(&self) -> Vec { let mut paths = self.offload.clone(); @@ -1272,48 +1278,21 @@ impl CommandLineStep for OmpOffload { #[allow(unused)] fn run(self, builder: &Builder<'_>) -> Self::Output { if builder.config.dry_run() { - return BuiltOmpOffload { - offload: vec![builder.config.tempdir().join("llvm-offload-dry-run")], - }; + let dry_run = builder.config.tempdir().join("llvm-offload-dry-run"); + return BuiltOmpOffload { offload: vec![dry_run.clone()], lib_dir: dry_run }; } let target = self.target; let llvm_output = builder.ensure(Llvm { target }); - // Running cmake twice in the same folder is known to cause issues, like deleting existing - // binaries. We therefore write our offload artifacts into it's own folder, instead of - // using the llvm build dir. let out_dir = builder.out.join(self.target.triple).join("offload"); - let mut files = vec![]; let lib_ext = std::env::consts::DLL_EXTENSION; - files.push(out_dir.join("lib").join("libLLVMOffload").with_extension(lib_ext)); - files.push(out_dir.join("lib").join("libomp").with_extension(lib_ext)); - files.push(out_dir.join("lib").join("libomptarget").with_extension(lib_ext)); - files.push( - out_dir.join("lib").join("amdgcn-amd-amdhsa").join("libompdevice").with_extension("a"), - ); - files.push( - out_dir - .join("lib") - .join("amdgcn-amd-amdhsa") - .join("libomptarget-amdgpu") - .with_extension("bc"), - ); - files.push( - out_dir - .join("lib") - .join("nvptx64-nvidia-cuda") - .join("libompdevice") - .with_extension("a"), - ); - files.push( - out_dir - .join("lib") - .join("nvptx64-nvidia-cuda") - .join("libomptarget-nvptx") - .with_extension("bc"), - ); + let files = vec![ + out_dir.join("lib").join("libLLVMOffload").with_extension(lib_ext), + out_dir.join("lib").join("libomp").with_extension(lib_ext), + out_dir.join("lib").join("libomptarget").with_extension(lib_ext), + ]; // Offload/OpenMP are just subfolders of LLVM, so we can use the LLVM sha. static STAMP_HASH_MEMO: OnceLock = OnceLock::new(); @@ -1339,7 +1318,7 @@ impl CommandLineStep for OmpOffload { stamp.path().display() )); } - return BuiltOmpOffload { offload: files }; + return BuiltOmpOffload { offload: files, lib_dir: out_dir.join("lib") }; } trace!(?target, "(re)building offload/openmp artifacts"); @@ -1408,79 +1387,62 @@ impl CommandLineStep for OmpOffload { libstdcxx.parent().map(Path::to_path_buf) }); - // In the context of OpenMP offload, some libraries must be compiled for the gpu target, - // some for the host, and others for both. We do not perform a full cross-compilation, since - // we don't want to run rustc on a GPU. - let omp_targets = vec![target.triple.as_ref(), "amdgcn-amd-amdhsa", "nvptx64-nvidia-cuda"]; - for omp_target in omp_targets { - let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/runtimes/")); - - // If we use an external clang as opposed to building our own llvm_clang, than that clang will - // come with it's own set of default include directories, which are based on a potentially older - // LLVM. This can cause issues, so we overwrite it to include headers based on our - // `src/llvm-project` submodule instead. - let mut cflags = CcFlags::default(); - if !builder.config.llvm_clang { - let base = llvm_output.root_dir().join("include"); - let inc_dir = base.display(); - cflags.push_all(format!(" -I {inc_dir}")); - } + let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/runtimes/")); - // Logic copied from `configure_llvm` - // ThinLTO is only available when building with LLVM, enabling LLD is required. - // Apple's linker ld64 supports ThinLTO out of the box though, so don't use LLD on Darwin. - let mut ldflags = LdFlags::default(); - if builder.config.llvm_thin_lto && !target.contains("apple") { - ldflags.push_all("-fuse-ld=lld"); - } - if *omp_target == *target.triple - && let Some(dir) = &cxx_lib_dir - { - ldflags.push_all(format!("-L{}", dir.display())); - } + // If we use an external clang as opposed to building our own llvm_clang, than that clang will + // come with it's own set of default include directories, which are based on a potentially older + // LLVM. This can cause issues, so we overwrite it to include headers based on our + // `src/llvm-project` submodule instead. + let mut cflags = CcFlags::default(); + if !builder.config.llvm_clang { + let base = llvm_output.root_dir().join("include"); + let inc_dir = base.display(); + cflags.push_all(format!(" -I {inc_dir}")); + } - configure_cmake(builder, target, &mut cfg, true, ldflags, cflags, &[]); - - cfg.define("CMAKE_C_COMPILER", &clang) - .define("CMAKE_CXX_COMPILER", &clangxx) - .define("CMAKE_ASM_COMPILER", &clang); - - // Re-use the same flags as llvm to control the level of debug information - // generated for offload. - let profile = get_llvm_profile(&builder.config); - trace!(?profile); - - // FIXME(offload): Once we move from OMP to Offload (Ol) APIs, we should drop the openmp - // runtime to simplify our build. So far, these are still under development. - cfg.out_dir(&out_dir) - .profile(profile) - .env("LLVM_CONFIG_REAL", llvm_output.llvm_config()) - .define("LLVM_ENABLE_ASSERTIONS", "ON") - .define("LLVM_INCLUDE_TESTS", "OFF") - .define("OFFLOAD_INCLUDE_TESTS", "OFF") - .define("LLVM_ROOT", llvm_output.root_dir().join("build")) - .define("LLVM_DIR", llvm_output.cmake_dir()) - .define("LLVM_DEFAULT_TARGET_TRIPLE", omp_target); - if let Some(p) = offload_clang_dir.clone() { - cfg.define("Clang_DIR", p); - } + // Logic copied from `configure_llvm` + // ThinLTO is only available when building with LLVM, enabling LLD is required. + // Apple's linker ld64 supports ThinLTO out of the box though, so don't use LLD on Darwin. + let mut ldflags = LdFlags::default(); + if builder.config.llvm_thin_lto && !target.contains("apple") { + ldflags.push_all("-fuse-ld=lld"); + } - // We don't perform a full cross-compilation of rustc, therefore our target.triple - // will still be a CPU target. - if *omp_target == *target.triple { - // The offload library provides functionality which only makes sense on the host. - cfg.define("LLVM_ENABLE_RUNTIMES", "openmp;offload"); - } else { - // OpenMP provides some device libraries, so we also compile it for all gpu targets. - cfg.define("OPENMP_INSTALL_LIBDIR", Path::new("lib").join(omp_target)); - cfg.define("LLVM_USE_LINKER", "lld"); - cfg.define("LLVM_ENABLE_RUNTIMES", "openmp"); - cfg.define("CMAKE_C_COMPILER_TARGET", omp_target); - cfg.define("CMAKE_CXX_COMPILER_TARGET", omp_target); - } - cfg.build(); + if let Some(dir) = &cxx_lib_dir { + ldflags.push_all(format!("-L{}", dir.display())); + } + + configure_cmake(builder, target, &mut cfg, true, ldflags, cflags, &[]); + + cfg.define("CMAKE_C_COMPILER", &clang) + .define("CMAKE_CXX_COMPILER", &clangxx) + .define("CMAKE_ASM_COMPILER", &clang); + + // Re-use the same flags as llvm to control the level of debug information + // generated for offload. + let profile = get_llvm_profile(&builder.config); + trace!(?profile); + + // FIXME(offload): Once we move from OMP to Offload (Ol) APIs, we should drop the openmp + // runtime to simplify our build. So far, these are still under development. + cfg.out_dir(&out_dir) + .profile(profile) + .env("LLVM_CONFIG_REAL", llvm_output.llvm_config()) + .define("LLVM_ENABLE_ASSERTIONS", "ON") + .define("LLVM_INCLUDE_TESTS", "OFF") + .define("OFFLOAD_INCLUDE_TESTS", "OFF") + .define("LLVM_ROOT", llvm_output.root_dir().join("build")) + .define("LLVM_DIR", llvm_output.cmake_dir()) + .define("LLVM_DEFAULT_TARGET_TRIPLE", &*target.triple); + if let Some(p) = offload_clang_dir { + cfg.define("Clang_DIR", p); } + // The offload library provides functionality which only makes sense on the host. + cfg.define("LLVM_ENABLE_RUNTIMES", "openmp;offload"); + + cfg.build(); + t!(stamp.write()); for p in &files { @@ -1494,7 +1456,7 @@ impl CommandLineStep for OmpOffload { helpers::exit_process(1); } } - BuiltOmpOffload { offload: files } + BuiltOmpOffload { offload: files, lib_dir: out_dir.join("lib") } } } diff --git a/src/doc/rustc-dev-guide/src/offload/installation.md b/src/doc/rustc-dev-guide/src/offload/installation.md index ab8e7984d5b4a..8422d072bca3b 100644 --- a/src/doc/rustc-dev-guide/src/offload/installation.md +++ b/src/doc/rustc-dev-guide/src/offload/installation.md @@ -12,6 +12,10 @@ cd rust ./configure --enable-llvm-link-shared --release-channel=nightly --enable-llvm-assertions --enable-llvm-offload --enable-llvm-enzyme --enable-clang --enable-lld --enable-option-checking --enable-ninja --disable-docs ``` +If you would rather reuse an existing clang than build one, drop `--enable-clang` and pass +`--enable-llvm-offload-clang-dir=` +instead. It should match the (major version of the) LLVM in `src/llvm-project`. + Afterwards you can build rustc using: ```console ./x build --stage 1 library From d5a0a1612ba712382aa0f7530dc370b9cdb990ab Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Sat, 22 Aug 2026 19:38:50 -0400 Subject: [PATCH 13/31] Remove LLVM_CONFIG_REAL variables Originally introduced in the Enzyme build and coppied around since, but without effect for a few years by now. --- src/bootstrap/src/core/build_steps/llvm.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index df403175223a1..f89ccb4c2a6a8 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -1192,10 +1192,7 @@ impl CommandLineStep for RustOffload { let profile = get_llvm_profile(&builder.config); - cfg.out_dir(&out_dir) - .profile(profile) - .env("LLVM_CONFIG_REAL", llvm_output.llvm_config()) - .define("LLVM_DIR", llvm_output.cmake_dir()); + cfg.out_dir(&out_dir).profile(profile).define("LLVM_DIR", llvm_output.cmake_dir()); cfg.build(); @@ -1427,7 +1424,6 @@ impl CommandLineStep for OmpOffload { // runtime to simplify our build. So far, these are still under development. cfg.out_dir(&out_dir) .profile(profile) - .env("LLVM_CONFIG_REAL", llvm_output.llvm_config()) .define("LLVM_ENABLE_ASSERTIONS", "ON") .define("LLVM_INCLUDE_TESTS", "OFF") .define("OFFLOAD_INCLUDE_TESTS", "OFF") @@ -1585,7 +1581,6 @@ impl CommandLineStep for Enzyme { cfg.out_dir(&out_dir) .profile(profile) - .env("LLVM_CONFIG_REAL", llvm_output.llvm_config()) .define("LLVM_ENABLE_ASSERTIONS", "ON") .define("ENZYME_EXTERNAL_SHARED_LIB", "ON") .define("ENZYME_BC_LOADER", "OFF") From 9dcbee96b3adc9923e4675f01bd0c5d426a640be Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 15 Sep 2026 23:01:10 +0200 Subject: [PATCH 14/31] add `RegKind::from_primitive` --- compiler/rustc_abi/src/callconv/reg.rs | 9 +++++++++ compiler/rustc_target/src/callconv/wasm.rs | 6 +----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_abi/src/callconv/reg.rs b/compiler/rustc_abi/src/callconv/reg.rs index 745a2ecfc6159..126f5bfa4ebf6 100644 --- a/compiler/rustc_abi/src/callconv/reg.rs +++ b/compiler/rustc_abi/src/callconv/reg.rs @@ -16,6 +16,15 @@ pub enum RegKind { }, } +impl RegKind { + pub fn from_primitive(primitive: Primitive) -> Self { + match primitive { + Primitive::Int(..) | Primitive::Pointer(_) => RegKind::Integer, + Primitive::Float(_) => RegKind::Float, + } + } +} + #[cfg_attr(feature = "nightly", derive(StableHash))] #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Reg { diff --git a/compiler/rustc_target/src/callconv/wasm.rs b/compiler/rustc_target/src/callconv/wasm.rs index 0ee442b879606..2ab8085bbf6b3 100644 --- a/compiler/rustc_target/src/callconv/wasm.rs +++ b/compiler/rustc_target/src/callconv/wasm.rs @@ -15,11 +15,7 @@ where let BackendRepr::Scalar(scalar) = layout.backend_repr else { return None; }; - let kind = match scalar.primitive() { - Primitive::Int(..) | Primitive::Pointer(_) => RegKind::Integer, - Primitive::Float(_) => RegKind::Float, - }; - return Some(Reg { kind, size: layout.size }); + return Some(Reg { kind: RegKind::from_primitive(scalar.primitive()), size: layout.size }); } let mut found = None; From 2b7282088d773a82cd1870ac310dfce38883e468 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 15 Sep 2026 23:07:25 +0200 Subject: [PATCH 15/31] add `TyAbiInterface::is_enum` --- compiler/rustc_abi/src/layout/ty.rs | 8 ++++++++ compiler/rustc_middle/src/ty/layout.rs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index 27ec34f519870..3b3a58697b205 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -116,6 +116,7 @@ pub trait TyAbiInterface<'a, C>: Sized + std::fmt::Debug + std::fmt::Display { offset: Size, ) -> Option; fn is_adt(this: TyAndLayout<'a, Self>) -> bool; + fn is_enum(this: TyAndLayout<'a, Self>) -> bool; fn is_never(this: TyAndLayout<'a, Self>) -> bool; fn is_tuple(this: TyAndLayout<'a, Self>) -> bool; fn is_unit(this: TyAndLayout<'a, Self>) -> bool; @@ -200,6 +201,13 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { Ty::is_adt(self) } + pub fn is_enum(self) -> bool + where + Ty: TyAbiInterface<'a, C>, + { + Ty::is_enum(self) + } + pub fn is_never(self) -> bool where Ty: TyAbiInterface<'a, C>, diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index 7006568a2ef33..58bd0379605fd 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -1196,6 +1196,10 @@ where matches!(this.ty.kind(), ty::Adt(..)) } + fn is_enum(this: TyAndLayout<'tcx>) -> bool { + matches!(this.ty.kind(), ty::Adt(def, _) if def.is_enum()) + } + fn is_never(this: TyAndLayout<'tcx>) -> bool { matches!(this.ty.kind(), ty::Never) } From 8aff3e62f2d79bdb6650e432a1fcac4fbe4bf03b Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 15 Sep 2026 23:10:34 +0200 Subject: [PATCH 16/31] wasm: fix ABI of `repr(int)` enums with ZST fields --- compiler/rustc_target/src/callconv/wasm.rs | 34 ++++++- .../codegen-llvm/wasm-abi/singleton-scalar.rs | 92 ++++++++++++++++++- 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_target/src/callconv/wasm.rs b/compiler/rustc_target/src/callconv/wasm.rs index 2ab8085bbf6b3..3706caa6b6f44 100644 --- a/compiler/rustc_target/src/callconv/wasm.rs +++ b/compiler/rustc_target/src/callconv/wasm.rs @@ -1,6 +1,6 @@ use rustc_abi::{ - BackendRepr, Float, HasDataLayout, Integer, Primitive, Reg, RegKind, TyAbiInterface, - TyAndLayout, + BackendRepr, Float, HasDataLayout, Integer, Primitive, Reg, RegKind, TagEncoding, + TyAbiInterface, TyAndLayout, Variants, }; use crate::callconv::{ArgAbi, FnAbi}; @@ -11,13 +11,29 @@ where C: HasDataLayout, { // The base case: a single scalar is a singleton scalar. - if !layout.is_aggregate() { + if !(layout.is_aggregate() || layout.peel_transparent_wrappers(cx).is_enum()) { let BackendRepr::Scalar(scalar) = layout.backend_repr else { return None; }; return Some(Reg { kind: RegKind::from_primitive(scalar.primitive()), size: layout.size }); } + // Enums that are represented as scalars need special care: + // + // - `#[repr(u8)] enum { A, B }` is a singleton scalar + // - `#[repr(u8)] enum { A(()), B }` is not + // + // To rust their representation is the same, but clang looks at the syntax. + // Niches have custom behavior too, so `Option<&i32>` is a singleton scalar. + if let Variants::Multiple { tag, tag_encoding: TagEncoding::Direct, variants, .. } = + &layout.variants + { + if variants.iter().all(|x| x.field_offsets.is_empty()) { + return Some(Reg { kind: RegKind::from_primitive(tag.primitive()), size: layout.size }); + } + return None; + } + let mut found = None; for i in 0..layout.fields.count() { let field = layout.field(cx, i); @@ -36,12 +52,17 @@ where } /// Return whether the value should be passed as an aggregate (i.e. indirectly). +/// +/// - Enums with integer layout and variants with only zst members are passed as aggregates +/// - Aggregate wrappers around a single scalar are passed as scalars fn is_aggregate_for_abi<'a, Ty, C>(cx: &C, val: &mut ArgAbi<'a, Ty>) -> bool where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { - if !val.layout.is_aggregate() { + // An enum that is represented as an integer is not an aggregate to rust, but may still + // need to be passed as one if its variants have any (even ZST) fields. + if !(val.layout.is_aggregate() || val.layout.peel_transparent_wrappers(cx).is_enum()) { return false; } @@ -49,6 +70,11 @@ where return true; }; + // This is an enum with integer layout, no need to cast. + if !val.layout.is_aggregate() { + return false; + } + val.cast_to(scalar); false } diff --git a/tests/codegen-llvm/wasm-abi/singleton-scalar.rs b/tests/codegen-llvm/wasm-abi/singleton-scalar.rs index 9e8948c617feb..334bf14351410 100644 --- a/tests/codegen-llvm/wasm-abi/singleton-scalar.rs +++ b/tests/codegen-llvm/wasm-abi/singleton-scalar.rs @@ -3,7 +3,7 @@ //@[wasm] compile-flags: --target wasm32-unknown-unknown //@[wasip1] compile-flags: --target wasm32-wasip1 //@ needs-llvm-components: webassembly -//@ compile-flags: -Copt-level=3 -Zmerge-functions=disabled +//@ compile-flags: -Copt-level=3 -Zmerge-functions=disabled -Ctarget-feature=+simd128 #![feature(no_core, rustc_attrs, f128)] #![crate_type = "lib"] #![no_core] @@ -167,6 +167,96 @@ mod pass_i32 { ) -> ReprTransparent> { x } + + #[repr(i32)] + enum CLikeIntEnum { + A, + B, + } + + // CHECK: define{{.*}} i32 @pass_i32_c_like_enum(i32 noundef returned range(i32 0, 2) %[[ARG:.*]]) + #[unsafe(no_mangle)] + extern "C" fn pass_i32_c_like_enum(x: CLikeIntEnum) -> CLikeIntEnum { + x + } + + // CHECK: define{{.*}} i32 @pass_transparent_i32_c_like_enum(i32 noundef returned range(i32 0, 2) %[[ARG:.*]]) + #[unsafe(no_mangle)] + extern "C" fn pass_transparent_i32_c_like_enum( + x: ReprTransparent, + ) -> ReprTransparent { + x + } + + #[repr(i32)] + enum IntEnumZstStructVariants { + A(()), + B(), + } + + // Any field, even a ZST, disqualifies an enum from being passed as a scalar. + // + // CHECK: define{{.*}} void @pass_i32_enum_zst_struct_variants(ptr{{.*}}, ptr{{.*}}) + #[unsafe(no_mangle)] + extern "C" fn pass_i32_enum_zst_struct_variants( + x: IntEnumZstStructVariants, + ) -> IntEnumZstStructVariants { + x + } + + // CHECK: define{{.*}} void @pass_transparent_i32_enum_zst_struct_variants(ptr{{.*}}, ptr{{.*}}) + #[unsafe(no_mangle)] + extern "C" fn pass_transparent_i32_enum_zst_struct_variants( + x: ReprTransparent, + ) -> ReprTransparent { + x + } + + // CHECK: define{{.*}} void @pass_c_i32_enum_zst_struct_variants(ptr{{.*}}, ptr{{.*}}) + #[unsafe(no_mangle)] + extern "C" fn pass_c_i32_enum_zst_struct_variants( + x: ReprC, + ) -> ReprC { + x + } +} + +mod pass_ptr { + use super::*; + + // The layout of `Option<&T>` is guaranteed to match `*const T`. + // + // CHECK: define{{.*}} ptr @pass_option_ref(ptr{{.*}} %[[ARG:.*]]) + #[unsafe(no_mangle)] + extern "C" fn pass_option_ref(x: Option<&'static i32>) -> Option<&'static i32> { + x + } + + // CHECK: define{{.*}} ptr @pass_transparent_option_ref(ptr{{.*}} %[[ARG:.*]]) + #[unsafe(no_mangle)] + extern "C" fn pass_transparent_option_ref( + x: ReprTransparent>, + ) -> ReprTransparent> { + x + } +} + +mod pass_simd { + use super::*; + + // CHECK: define{{.*}} <4 x float> @pass_simd_f32x4(<4 x float> returned %[[ARG:.*]]) + #[unsafe(no_mangle)] + extern "C" fn pass_simd_f32x4(x: simd::f32x4) -> simd::f32x4 { + x + } + + // CHECK: define{{.*}} <4 x float> @pass_transparent_simd_f32x4(<4 x float> returned %[[ARG:.*]]) + #[unsafe(no_mangle)] + extern "C" fn pass_transparent_simd_f32x4( + x: ReprTransparent, + ) -> ReprTransparent { + x + } } mod pass_i128 { From 8420bbd952066b85af23163c6749a3211b5f6c68 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 15 Sep 2026 22:12:36 +0000 Subject: [PATCH 17/31] [rustc_pub] Expand PassMode::Cast with CastTarget Replace the opaque representation of `PassMode::Cast` with a structured `CastTarget` type that exposes the register layout used by the platform ABI. Add `Uniform`, `Reg`, and `RegKind` types so tools can inspect how arguments are mapped to registers. Add `CastTarget::size()` and `Uniform::reg_count()` helpers. Add a test covering cast on args, returns, mixed register kinds, multiple arguments, and register exhaustion causing stack spill. --- compiler/rustc_public/src/abi.rs | 78 ++++++- .../src/unstable/convert/stable/abi.rs | 67 +++++- .../rustc_public/check_abi_cast.rs | 218 ++++++++++++++++++ 3 files changed, 355 insertions(+), 8 deletions(-) create mode 100644 tests/ui-fulldeps/rustc_public/check_abi_cast.rs diff --git a/compiler/rustc_public/src/abi.rs b/compiler/rustc_public/src/abi.rs index 910f4a5745a7d..704e62e4907bc 100644 --- a/compiler/rustc_public/src/abi.rs +++ b/compiler/rustc_public/src/abi.rs @@ -54,12 +54,86 @@ pub enum PassMode { /// /// The argument has a layout abi of `ScalarPair`. Pair(Opaque, Opaque), - /// Pass the argument after casting it. - Cast { pad_i32_count: u8, cast: Opaque }, + /// Pass the argument after casting it to the given target type. + Cast { pad_i32_count: u8, cast: CastTarget }, /// Pass the argument indirectly via a hidden pointer. Indirect { attrs: Opaque, meta_attrs: Opaque, on_stack: bool }, } +/// Describes the ABI type that an argument is transmuted to for `PassMode::Cast`. +/// +/// When an argument is "cast," its raw bytes are reinterpreted as a sequence of +/// register-sized values for passing. This struct describes that target layout: +/// +/// 1. The `prefix` registers are laid out first, like fields of a `repr(C)` struct +/// (i.e., with alignment padding between them). +/// 2. After the prefix, `rest.unit` is repeated enough times to cover `rest.total`, +/// starting at `rest_offset` (or immediately after the prefix if `None`). +/// +/// For example, on x86_64 SysV a `struct { i32, f64 }` might be cast to a prefix of +/// `[Reg::i64()]` followed by a rest of `Reg::f64()` — placing the first eightbyte +/// in an integer register and the second in an SSE register. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct CastTarget { + /// Leading registers of potentially different types, laid out with `repr(C)` padding. + pub prefix: Vec, + /// The byte offset where `rest` begins, if explicitly set. + /// When `None`, `rest` starts immediately after the prefix. + pub rest_offset: Option, + /// The repeated trailing register type filling the remainder of the value. + pub rest: Uniform, +} + +impl CastTarget { + /// Return the total size of the ABI type this argument is cast to. + pub fn size(&self) -> Size { + let prefix_size: usize = self.prefix.iter().map(|r| r.size.bits()).sum(); + Size::from_bits(prefix_size + self.rest.total.bits()) + } +} + +/// An argument passed entirely in registers with the same kind (e.g., HFA/HVA on PPC64 and AArch64). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct Uniform { + /// The type of register used. + pub unit: Reg, + /// The total size of the argument, which can be: + /// * equal to `unit.size` (one scalar/vector), + /// * a multiple of `unit.size` (an array of scalar/vectors), + /// * if `unit.kind` is `Integer`, the last element can be shorter, i.e., `{ i64, i64, i32 }` + /// for 64-bit integers with a total size of 20 bytes. When the argument is actually passed, + /// this size will be rounded up to the nearest multiple of `unit.size`. + pub total: Size, + /// Whether the argument is consecutive: either all values are passed in registers, or all on + /// the stack with no additional padding between elements. + pub is_consecutive: bool, +} + +impl Uniform { + /// Return the number of registers needed to cover `total`. + pub fn reg_count(&self) -> usize { + if self.unit.size.bits() == 0 { + return 0; + } + (self.total.bits() + self.unit.size.bits() - 1) / self.unit.size.bits() + } +} + +/// A register type used in ABI calling conventions. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct Reg { + pub kind: RegKind, + pub size: Size, +} + +/// The kind of a register used in calling conventions. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] +pub enum RegKind { + Integer, + Float, + Vector, +} + /// The layout of a type, alongside the type itself. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct TyAndLayout { diff --git a/compiler/rustc_public/src/unstable/convert/stable/abi.rs b/compiler/rustc_public/src/unstable/convert/stable/abi.rs index 4bb00b4c04394..d4030a8c111e7 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/abi.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/abi.rs @@ -9,10 +9,10 @@ use rustc_public_bridge::context::CompilerCtxt; use rustc_target::callconv; use crate::abi::{ - AddressSpace, ArgAbi, CallConvention, FieldsShape, FloatLength, FnAbi, IntegerLength, - IntegerType, Layout, LayoutShape, NumScalableVectors, PassMode, Primitive, ReprFlags, - ReprOptions, Scalar, TagEncoding, TyAndLayout, ValueAbi, VariantFields, VariantsShape, - WrappingRange, + AddressSpace, ArgAbi, CallConvention, CastTarget, FieldsShape, FloatLength, FnAbi, + IntegerLength, IntegerType, Layout, LayoutShape, NumScalableVectors, PassMode, Primitive, Reg, + RegKind, ReprFlags, ReprOptions, Scalar, TagEncoding, TyAndLayout, Uniform, ValueAbi, + VariantFields, VariantsShape, WrappingRange, }; use crate::compiler_interface::BridgeTys; use crate::target::MachineSize as Size; @@ -158,7 +158,11 @@ impl<'tcx> Stable<'tcx> for CanonAbi { impl<'tcx> Stable<'tcx> for callconv::PassMode { type T = PassMode; - fn stable(&self, _: &mut Tables<'_, BridgeTys>, _: &CompilerCtxt<'_, BridgeTys>) -> Self::T { + fn stable<'cx>( + &self, + tables: &mut Tables<'cx, BridgeTys>, + cx: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { match self { callconv::PassMode::Ignore => PassMode::Ignore, callconv::PassMode::Direct(attr) => PassMode::Direct(opaque(attr)), @@ -166,7 +170,7 @@ impl<'tcx> Stable<'tcx> for callconv::PassMode { PassMode::Pair(opaque(first), opaque(second)) } callconv::PassMode::Cast { pad_i32_count, cast } => { - PassMode::Cast { pad_i32_count: *pad_i32_count, cast: opaque(cast) } + PassMode::Cast { pad_i32_count: *pad_i32_count, cast: cast.stable(tables, cx) } } callconv::PassMode::Indirect { attrs, meta_attrs, on_stack } => PassMode::Indirect { attrs: opaque(attrs), @@ -177,6 +181,57 @@ impl<'tcx> Stable<'tcx> for callconv::PassMode { } } +impl<'tcx> Stable<'tcx> for callconv::CastTarget { + type T = CastTarget; + + fn stable<'cx>( + &self, + tables: &mut Tables<'cx, BridgeTys>, + cx: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + CastTarget { + prefix: self.prefix.iter().map(|reg| reg.stable(tables, cx)).collect(), + rest_offset: self.rest_offset.map(|offset| Size::from_bits(offset.bits_usize())), + rest: self.rest.stable(tables, cx), + } + } +} + +impl<'tcx> Stable<'tcx> for callconv::Uniform { + type T = Uniform; + + fn stable<'cx>( + &self, + tables: &mut Tables<'cx, BridgeTys>, + cx: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + Uniform { + unit: self.unit.stable(tables, cx), + total: Size::from_bits(self.total.bits_usize()), + is_consecutive: self.is_consecutive, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Reg { + type T = Reg; + + fn stable<'cx>( + &self, + _: &mut Tables<'cx, BridgeTys>, + _: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + Reg { + kind: match self.kind { + rustc_abi::RegKind::Integer => RegKind::Integer, + rustc_abi::RegKind::Float => RegKind::Float, + rustc_abi::RegKind::Vector { .. } => RegKind::Vector, + }, + size: Size::from_bits(self.size.bits_usize()), + } + } +} + impl<'tcx> Stable<'tcx> for rustc_abi::FieldsShape { type T = FieldsShape; diff --git a/tests/ui-fulldeps/rustc_public/check_abi_cast.rs b/tests/ui-fulldeps/rustc_public/check_abi_cast.rs new file mode 100644 index 0000000000000..0bd4ac684066e --- /dev/null +++ b/tests/ui-fulldeps/rustc_public/check_abi_cast.rs @@ -0,0 +1,218 @@ +//@ run-pass +//! Test that `PassMode::Cast` exposes the `CastTarget` structure for arguments and returns. +//! +//! When a platform ABI requires an aggregate to be passed in registers, rustc represents +//! this as `PassMode::Cast` with a `CastTarget` describing the register layout. This test +//! verifies that the public API exposes the register kinds, sizes, and that register +//! exhaustion correctly transitions arguments from `Cast` to `Indirect { on_stack: true }`. + +//@ ignore-stage1 +//@ ignore-cross-compile +//@ ignore-remote +//@ only-x86_64-unknown-linux-gnu + +#![feature(rustc_private)] + +extern crate rustc_driver; +extern crate rustc_interface; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_public; + +use std::convert::TryFrom; +use std::io::Write; +use std::ops::ControlFlow; + +use rustc_public::abi::{CallConvention, PassMode, RegKind}; +use rustc_public::mir::mono::Instance; +use rustc_public::{CrateDef, ItemKind}; + +const CRATE_NAME: &str = "input"; + +fn test_abi_cast() -> ControlFlow<()> { + let items = rustc_public::all_local_items(); + + // Test Cast on argument: a small struct passed in registers. + let cast_arg_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_arg") + .expect("missing cast_arg"); + + let instance = Instance::try_from(*cast_arg_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + assert_eq!(abi.conv, CallConvention::C); + match &abi.args[0].mode { + PassMode::Cast { pad_i32_count, cast } => { + assert_eq!(*pad_i32_count, 0); + assert_eq!(cast.rest.unit.kind, RegKind::Integer); + assert!(cast.rest.total.bits() > 0); + } + other => panic!("Expected PassMode::Cast for struct arg, got: {:?}", other), + } + + // Test Cast on return: a small struct returned via registers. + let cast_ret_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_ret") + .expect("missing cast_ret"); + + let instance = Instance::try_from(*cast_ret_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + match &abi.ret.mode { + PassMode::Cast { pad_i32_count, cast } => { + assert_eq!(*pad_i32_count, 0); + // A 16-byte struct returned via integer registers. + assert!( + cast.rest.unit.kind == RegKind::Integer + || cast.prefix.iter().any(|r| r.kind == RegKind::Integer), + "Expected integer registers for return, got: {:?}", + cast + ); + } + other => panic!("Expected PassMode::Cast for struct return, got: {:?}", other), + } + + // Test Cast with mixed register kinds: struct with int + float fields. + let cast_mixed_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_mixed") + .expect("missing cast_mixed"); + + let instance = Instance::try_from(*cast_mixed_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + match &abi.args[0].mode { + PassMode::Cast { pad_i32_count, cast } => { + assert_eq!(*pad_i32_count, 0); + // On x86_64 SysV, a struct { i64, f64 } uses prefix [Int] + rest Sse, + // or similar split. Just verify we have register info exposed. + let has_int = cast.prefix.iter().any(|r| r.kind == RegKind::Integer) + || cast.rest.unit.kind == RegKind::Integer; + let has_float = cast.prefix.iter().any(|r| r.kind == RegKind::Float) + || cast.rest.unit.kind == RegKind::Float; + assert!( + has_int && has_float, + "Expected both integer and float registers, got: {:?}", + cast + ); + } + other => panic!("Expected PassMode::Cast for mixed struct arg, got: {:?}", other), + } + + // Test multiple cast arguments in one function. + let cast_multi_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_multi") + .expect("missing cast_multi"); + + let instance = Instance::try_from(*cast_multi_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + assert_eq!(abi.conv, CallConvention::C); + assert_eq!(abi.args.len(), 3); + // First arg: SmallStruct → Cast + assert!(matches!(&abi.args[0].mode, PassMode::Cast { .. })); + // Second arg: u64 → Direct (scalar) + assert!(matches!(&abi.args[1].mode, PassMode::Direct(_))); + // Third arg: MixedStruct → Cast with both int and float registers + match &abi.args[2].mode { + PassMode::Cast { cast, .. } => { + let has_int = cast.prefix.iter().any(|r| r.kind == RegKind::Integer) + || cast.rest.unit.kind == RegKind::Integer; + let has_float = cast.prefix.iter().any(|r| r.kind == RegKind::Float) + || cast.rest.unit.kind == RegKind::Float; + assert!(has_int && has_float, "Expected mixed registers, got: {:?}", cast); + } + other => panic!("Expected PassMode::Cast for third arg, got: {:?}", other), + } + + // Test stack spill: same type can have different PassModes when registers are exhausted. + // On x86_64 SysV, integer args use up to 6 registers (rdi, rsi, rdx, rcx, r8, r9). + // TwoWords uses 2 registers each, so the 4th one spills to the stack. + let cast_spill_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_spill") + .expect("missing cast_spill"); + + let instance = Instance::try_from(*cast_spill_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + assert_eq!(abi.conv, CallConvention::C); + assert_eq!(abi.args.len(), 4); + // First three TwoWords fit in registers (2 regs each = 6 total) → Cast + for i in 0..3 { + assert!( + matches!(&abi.args[i].mode, PassMode::Cast { .. }), + "Expected arg {} to be Cast, got: {:?}", + i, + abi.args[i].mode + ); + } + // Fourth TwoWords has no registers left → Indirect (on stack) + assert!( + matches!(&abi.args[3].mode, PassMode::Indirect { on_stack: true, .. }), + "Expected arg 3 to be Indirect on stack, got: {:?}", + abi.args[3].mode + ); + + ControlFlow::Continue(()) +} + +fn main() { + let path = "pass_mode_input.rs"; + generate_input(&path).unwrap(); + let args = &[ + "rustc".to_string(), + "-Cpanic=abort".to_string(), + "--crate-type=lib".to_string(), + "--crate-name".to_string(), + CRATE_NAME.to_string(), + path.to_string(), + ]; + run!(args, test_abi_cast).unwrap(); +} + +fn generate_input(path: &str) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + write!( + file, + r#" + #[repr(C)] + pub struct SmallStruct {{ + pub a: u8, + pub b: u16, + pub c: u32, + }} + + #[repr(C)] + pub struct TwoWords {{ + pub a: u64, + pub b: u64, + }} + + #[repr(C)] + pub struct MixedStruct {{ + pub i: i64, + pub f: f64, + }} + + pub extern "C" fn cast_arg(s: SmallStruct) -> u64 {{ + (s.a as u64) + (s.b as u64) + (s.c as u64) + }} + + pub extern "C" fn cast_ret(x: u64) -> TwoWords {{ + TwoWords {{ a: x, b: x + 1 }} + }} + + pub extern "C" fn cast_mixed(s: MixedStruct) -> f64 {{ + (s.i as f64) + s.f + }} + + pub extern "C" fn cast_multi(s: SmallStruct, x: u64, m: MixedStruct) -> u64 {{ + (s.a as u64) + x + (m.i as u64) + }} + + pub extern "C" fn cast_spill(a: TwoWords, b: TwoWords, c: TwoWords, d: TwoWords) -> u64 {{ + a.a + b.a + c.a + d.a + }} + "# + )?; + Ok(()) +} From 5a1bf7f9bcf73f2e336d56ab53e0b0d080858aac Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 15 Sep 2026 22:16:17 +0000 Subject: [PATCH 18/31] Replace Opaque with ArgAttributes in PassMode Expose argument ABI attributes through a structured type instead of opaque debug strings. ArgAttributes provides accessors for the extension mode (zero/sign-extend) and pointee alignment, which are needed by tools doing their own code generation. This removes all uses of Opaque from the abi module. --- compiler/rustc_public/src/abi.rs | 94 ++++++++++++++++--- .../src/unstable/convert/stable/abi.rs | 38 ++++++-- tests/ui-fulldeps/rustc_public/check_abi.rs | 21 ++++- 3 files changed, 125 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_public/src/abi.rs b/compiler/rustc_public/src/abi.rs index 704e62e4907bc..e505347001fc0 100644 --- a/compiler/rustc_public/src/abi.rs +++ b/compiler/rustc_public/src/abi.rs @@ -8,7 +8,7 @@ use crate::compiler_interface::with; use crate::mir::FieldIdx; use crate::target::{MachineInfo, MachineSize as Size}; use crate::ty::{Align, Ty, VariantIdx, index_impl}; -use crate::{Error, Opaque, ThreadLocalIndex, error}; +use crate::{Error, ThreadLocalIndex, error}; /// A function ABI definition. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] @@ -40,24 +40,88 @@ pub struct ArgAbi { } /// How a function argument should be passed in to the target function. +/// +/// The pass mode is determined by the platform's calling convention and the +/// argument's type layout. The same Rust type may use different pass modes +/// on different targets or when register availability changes. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum PassMode { /// Ignore the argument. /// - /// The argument is either uninhabited or a ZST. + /// The argument is either uninhabited or a ZST (zero-sized type). Ignore, - /// Pass the argument directly. + /// Pass the argument directly in a single register. + /// + /// Used for primitive types and small values that fit in one register. + Direct(ArgAttributes), + /// Pass the argument directly in two registers. /// - /// The argument has a layout abi of `Scalar` or `Vector`. - Direct(Opaque), - /// Pass a pair's elements directly in two arguments. + /// Used for types represented as a pair of values (e.g., a fat pointer + /// consisting of a data pointer and a length/vtable pointer). + Pair(ArgAttributes, ArgAttributes), + /// Pass the argument after reinterpreting it as a different register layout. /// - /// The argument has a layout abi of `ScalarPair`. - Pair(Opaque, Opaque), - /// Pass the argument after casting it to the given target type. + /// Used for aggregates (structs, tuples) that the platform ABI passes in + /// registers. The argument's bytes are reinterpreted as the register + /// sequence described by [`CastTarget`]. See its documentation for details. Cast { pad_i32_count: u8, cast: CastTarget }, - /// Pass the argument indirectly via a hidden pointer. - Indirect { attrs: Opaque, meta_attrs: Opaque, on_stack: bool }, + /// Pass the argument indirectly via a pointer. + /// + /// The caller places the value in memory and passes a pointer to it. + /// When `on_stack` is true, the value is placed at a fixed stack offset + /// rather than passed as a regular pointer argument. + Indirect { + attrs: ArgAttributes, + /// Attributes for the metadata pointer (vtable or length) of unsized arguments. + /// Only present for unsized types (e.g., `dyn Trait`, `[T]`). + meta_attrs: Option, + on_stack: bool, + }, +} + +/// Attributes of a function argument that affect its ABI. +/// +/// Not all internal compiler attributes are exposed here, as some are +/// LLVM-specific optimization hints. The internal representation is kept +/// private so it can be expanded in the future. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct ArgAttributes { + pub(crate) arg_ext: ArgExtension, + pub(crate) pointee_size: Size, + pub(crate) pointee_align: Option, +} + +impl ArgAttributes { + /// Return how this argument should be extended when passed in a register. + /// + /// Relevant for integer arguments smaller than the register width. + pub fn arg_extension(&self) -> ArgExtension { + self.arg_ext + } + + /// Return the minimum alignment of the pointee, if applicable. + /// + /// This is relevant for `PassMode::Indirect` arguments where the pointer + /// must satisfy a particular alignment. + pub fn pointee_align(&self) -> Option { + self.pointee_align + } + + /// Return the minimum dereferenceable size of the pointee, if known. + pub fn pointee_size(&self) -> Size { + self.pointee_size + } +} + +/// How a small integer argument should be extended to fill a register. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)] +pub enum ArgExtension { + /// No extension required. + None, + /// Zero-extend to the register width. + Zext, + /// Sign-extend to the register width. + Sext, } /// Describes the ABI type that an argument is transmuted to for `PassMode::Cast`. @@ -70,9 +134,9 @@ pub enum PassMode { /// 2. After the prefix, `rest.unit` is repeated enough times to cover `rest.total`, /// starting at `rest_offset` (or immediately after the prefix if `None`). /// -/// For example, on x86_64 SysV a `struct { i32, f64 }` might be cast to a prefix of -/// `[Reg::i64()]` followed by a rest of `Reg::f64()` — placing the first eightbyte -/// in an integer register and the second in an SSE register. +/// For example, on x86_64 a `struct { i32, f64 }` might be cast to a prefix of +/// `[Reg::i64()]` followed by a rest of `Reg::f64()` — placing the first 8 bytes +/// in an integer register and the second 8 bytes in a floating-point register. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct CastTarget { /// Leading registers of potentially different types, laid out with `repr(C)` padding. @@ -92,7 +156,7 @@ impl CastTarget { } } -/// An argument passed entirely in registers with the same kind (e.g., HFA/HVA on PPC64 and AArch64). +/// A sequence of registers of the same kind used to pass an argument. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] pub struct Uniform { /// The type of register used. diff --git a/compiler/rustc_public/src/unstable/convert/stable/abi.rs b/compiler/rustc_public/src/unstable/convert/stable/abi.rs index d4030a8c111e7..6a563567044b0 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/abi.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/abi.rs @@ -8,17 +8,17 @@ use rustc_public_bridge::Tables; use rustc_public_bridge::context::CompilerCtxt; use rustc_target::callconv; +use crate::IndexedVal; use crate::abi::{ - AddressSpace, ArgAbi, CallConvention, CastTarget, FieldsShape, FloatLength, FnAbi, - IntegerLength, IntegerType, Layout, LayoutShape, NumScalableVectors, PassMode, Primitive, Reg, - RegKind, ReprFlags, ReprOptions, Scalar, TagEncoding, TyAndLayout, Uniform, ValueAbi, - VariantFields, VariantsShape, WrappingRange, + AddressSpace, ArgAbi, ArgAttributes, ArgExtension, CallConvention, CastTarget, FieldsShape, + FloatLength, FnAbi, IntegerLength, IntegerType, Layout, LayoutShape, NumScalableVectors, + PassMode, Primitive, Reg, RegKind, ReprFlags, ReprOptions, Scalar, TagEncoding, TyAndLayout, + Uniform, ValueAbi, VariantFields, VariantsShape, WrappingRange, }; use crate::compiler_interface::BridgeTys; use crate::target::MachineSize as Size; use crate::ty::{Align, VariantIdx}; use crate::unstable::Stable; -use crate::{IndexedVal, opaque}; impl<'tcx> Stable<'tcx> for rustc_abi::VariantIdx { type T = VariantIdx; @@ -165,16 +165,16 @@ impl<'tcx> Stable<'tcx> for callconv::PassMode { ) -> Self::T { match self { callconv::PassMode::Ignore => PassMode::Ignore, - callconv::PassMode::Direct(attr) => PassMode::Direct(opaque(attr)), + callconv::PassMode::Direct(attr) => PassMode::Direct(attr.stable(tables, cx)), callconv::PassMode::Pair(first, second) => { - PassMode::Pair(opaque(first), opaque(second)) + PassMode::Pair(first.stable(tables, cx), second.stable(tables, cx)) } callconv::PassMode::Cast { pad_i32_count, cast } => { PassMode::Cast { pad_i32_count: *pad_i32_count, cast: cast.stable(tables, cx) } } callconv::PassMode::Indirect { attrs, meta_attrs, on_stack } => PassMode::Indirect { - attrs: opaque(attrs), - meta_attrs: opaque(meta_attrs), + attrs: attrs.stable(tables, cx), + meta_attrs: meta_attrs.map(|a| a.stable(tables, cx)), on_stack: *on_stack, }, } @@ -232,6 +232,26 @@ impl<'tcx> Stable<'tcx> for rustc_abi::Reg { } } +impl<'tcx> Stable<'tcx> for callconv::ArgAttributes { + type T = ArgAttributes; + + fn stable<'cx>( + &self, + _: &mut Tables<'cx, BridgeTys>, + _: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + ArgAttributes { + arg_ext: match self.arg_ext { + callconv::ArgExtension::None => ArgExtension::None, + callconv::ArgExtension::Zext => ArgExtension::Zext, + callconv::ArgExtension::Sext => ArgExtension::Sext, + }, + pointee_size: Size::from_bits(self.pointee_size.bits_usize()), + pointee_align: self.pointee_align.map(|a| a.bytes()), + } + } +} + impl<'tcx> Stable<'tcx> for rustc_abi::FieldsShape { type T = FieldsShape; diff --git a/tests/ui-fulldeps/rustc_public/check_abi.rs b/tests/ui-fulldeps/rustc_public/check_abi.rs index 4cf79b1ac8005..f3ebcfbbdf8ec 100644 --- a/tests/ui-fulldeps/rustc_public/check_abi.rs +++ b/tests/ui-fulldeps/rustc_public/check_abi.rs @@ -15,8 +15,8 @@ extern crate rustc_middle; extern crate rustc_public; use rustc_public::abi::{ - ArgAbi, CallConvention, FieldsShape, IntegerLength, PassMode, Primitive, Scalar, ValueAbi, - VariantsShape, + ArgAbi, ArgExtension, CallConvention, FieldsShape, IntegerLength, PassMode, Primitive, Scalar, + ValueAbi, VariantsShape, }; use rustc_public::mir::MirVisitor; use rustc_public::mir::mono::Instance; @@ -106,7 +106,13 @@ fn check_ignore(abi: &ArgAbi) { /// Check the primitive argument: `primitive: char`. fn check_primitive(abi: &ArgAbi) { assert!(abi.ty.kind().is_char()); - assert_matches!(abi.mode, PassMode::Direct(_)); + let PassMode::Direct(ref attrs) = abi.mode else { + panic!("Expected PassMode::Direct for char, got: {:?}", abi.mode); + }; + // A char (32-bit) doesn't need sign/zero extension on most platforms. + assert_eq!(attrs.arg_extension(), ArgExtension::None); + // Direct arguments are not pointers, so no pointee alignment. + assert_eq!(attrs.pointee_align(), None); let layout = abi.layout.shape(); assert!(layout.is_sized()); assert!(!layout.is_1zst()); @@ -116,7 +122,14 @@ fn check_primitive(abi: &ArgAbi) { /// Check the return value: `Result`. fn check_result(abi: &ArgAbi) { assert!(abi.ty.kind().is_enum()); - assert_matches!(abi.mode, PassMode::Indirect { .. }); + let PassMode::Indirect { ref attrs, ref meta_attrs, on_stack } = abi.mode else { + panic!("Expected PassMode::Indirect for Result, got: {:?}", abi.mode); + }; + // Indirect arguments have a pointee alignment (the pointer must be aligned). + assert!(attrs.pointee_align().is_some()); + // Result is a sized type, so no metadata pointer. + assert!(meta_attrs.is_none()); + assert!(!on_stack); let layout = abi.layout.shape(); assert!(layout.is_sized()); assert_matches!(layout.fields, FieldsShape::Arbitrary { .. }); From bd13c1d29132e3508068372e8d942824c1a78906 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 15 Sep 2026 22:19:55 +0000 Subject: [PATCH 19/31] Rename ValueAbi to ValueRepr and fix abi docs BREAKING CHANGE: `ValueAbi` is renamed to `ValueRepr` and the `LayoutShape::abi` field is renamed to `LayoutShape::value_repr`. The old name was misleading: this type does not describe how values are passed in function calls (that is `PassMode`), it is a hint for how backends should represent values (as scalars, vectors, or aggregates). This aligns with the internal rename from `Abi` to `BackendRepr`. Also fixes several doc comments that incorrectly claimed layout fields define calling behavior. --- compiler/rustc_public/src/abi.rs | 75 ++++++++++--------- .../src/unstable/convert/stable/abi.rs | 16 ++-- tests/ui-fulldeps/rustc_public/check_abi.rs | 4 +- 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_public/src/abi.rs b/compiler/rustc_public/src/abi.rs index e505347001fc0..b760ed98c7111 100644 --- a/compiler/rustc_public/src/abi.rs +++ b/compiler/rustc_public/src/abi.rs @@ -19,9 +19,11 @@ pub struct FnAbi { /// The expected return type. pub ret: ArgAbi, - /// The count of non-variadic arguments. + /// The count of declared arguments (excluding variadic and implicit arguments). /// - /// Should only be different from `args.len()` when a function is a C variadic function. + /// This may be less than `args.len()` for C variadic functions (which have + /// additional variadic arguments) or `#[track_caller]` functions (which have + /// an implicit caller location argument). pub fixed_count: u32, /// The ABI convention. @@ -44,6 +46,10 @@ pub struct ArgAbi { /// The pass mode is determined by the platform's calling convention and the /// argument's type layout. The same Rust type may use different pass modes /// on different targets or when register availability changes. +/// +/// Note: for the Rust ABI, pass modes may not correspond to any valid C +/// calling convention (e.g., using more return registers than the platform +/// C ABI allows). Further processing may be needed depending on the target. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum PassMode { /// Ignore the argument. @@ -205,7 +211,7 @@ pub struct TyAndLayout { pub layout: Layout, } -/// The layout of a type in memory. +/// The layout of a type, including its size, alignment, field offsets, and backend representation. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct LayoutShape { /// The fields location within the layout @@ -219,8 +225,8 @@ pub struct LayoutShape { /// must be taken into account. pub variants: VariantsShape, - /// The `abi` defines how this data is passed between functions. - pub abi: ValueAbi, + /// A hint for how backends should represent this type: as a scalar, vector, or aggregate. + pub value_repr: ValueRepr, /// The ABI mandated alignment in bytes. pub abi_align: Align, @@ -233,12 +239,12 @@ impl LayoutShape { /// Returns `true` if the layout corresponds to an unsized type. #[inline] pub fn is_unsized(&self) -> bool { - self.abi.is_unsized() + self.value_repr.is_unsized() } #[inline] pub fn is_sized(&self) -> bool { - !self.abi.is_unsized() + !self.value_repr.is_unsized() } /// Returns `true` if the type is sized and a 1-ZST (meaning it has size 0 and alignment 1). @@ -257,7 +263,7 @@ impl Layout { } } -/// Describes how the fields of a type are shaped in memory. +/// Describes the number and position of fields within a type's layout. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum FieldsShape { /// Scalar primitives and `!`, which never have fields. @@ -370,49 +376,54 @@ pub enum TagEncoding { }, } -/// How many scalable vectors are in a `ValueAbi::ScalableVector`? +/// The number of scalable vectors in a [`ValueRepr::ScalableVector`]. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct NumScalableVectors(pub(crate) u8); -/// Describes how values of the type are passed by target ABIs, -/// in terms of categories of C types there are ABI rules for. +/// A hint for how backends should represent values of this type. +/// +/// Distinguishes between types representable as scalars, pairs of scalars, +/// SIMD vectors, or aggregates. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] -pub enum ValueAbi { +pub enum ValueRepr { Scalar(Scalar), ScalarPair { a: Scalar, b: Scalar, b_offset: Size, }, + /// A fixed-length SIMD vector. Vector { element: Scalar, count: u64, }, + /// A scalable SIMD vector (e.g., ARM SVE). ScalableVector { element: Scalar, count: u64, number_of_vectors: NumScalableVectors, }, + /// The type is not representable as a scalar or vector (e.g., aggregates, unsized types). Aggregate { /// If true, the size is exact, otherwise it's only a lower bound. sized: bool, }, } -impl ValueAbi { +impl ValueRepr { /// Returns `true` if the layout corresponds to an unsized type. pub fn is_unsized(&self) -> bool { match *self { - ValueAbi::Scalar(_) - | ValueAbi::ScalarPair { .. } - | ValueAbi::Vector { .. } + ValueRepr::Scalar(_) + | ValueRepr::ScalarPair { .. } + | ValueRepr::Vector { .. } // FIXME(rustc_scalable_vector): Scalable vectors are `Sized` while the // `sized_hierarchy` feature is not yet fully implemented. After `sized_hierarchy` is // fully implemented, scalable vectors will remain `Sized`, they just won't be // `const Sized` - whether `is_unsized` continues to return `false` at that point will // need to be revisited and will depend on what `is_unsized` is used for. - | ValueAbi::ScalableVector { .. } => false, - ValueAbi::Aggregate { sized } => !sized, + | ValueRepr::ScalableVector { .. } => false, + ValueRepr::Aggregate { sized } => !sized, } } } @@ -429,9 +440,8 @@ pub enum Scalar { }, Union { /// Unions never have niches, so there is no `valid_range`. - /// Even for unions, we need to use the correct registers for the kind of - /// values inside the union, so we keep the `Primitive` type around. - /// It is also used to compute the size of the scalar. + /// The `Primitive` type is kept to inform the backend representation + /// and to compute the size of the scalar. value: Primitive, }, } @@ -447,23 +457,18 @@ impl Scalar { } } -/// Fundamental unit of memory access and layout. +/// A primitive scalar type: integer, float, or pointer. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize)] pub enum Primitive { - /// The `bool` is the signedness of the `Integer` type. + /// An integer type with a given length and signedness. /// - /// One would think we would not care about such details this low down, - /// but some ABIs are described in terms of C types and ISAs where the - /// integer arithmetic is done on {sign,zero}-extended registers, e.g. - /// a negative integer passed by zero-extension will appear positive in - /// the callee, and most operations on it will produce the wrong values. - Int { - length: IntegerLength, - signed: bool, - }, - Float { - length: FloatLength, - }, + /// Signedness matters because some calling conventions require small integers + /// to be sign-extended or zero-extended when passed, and using the wrong + /// extension produces incorrect values in the callee. + Int { length: IntegerLength, signed: bool }, + /// A floating-point type with a given length. + Float { length: FloatLength }, + /// A pointer in the given address space. Pointer(AddressSpace), } diff --git a/compiler/rustc_public/src/unstable/convert/stable/abi.rs b/compiler/rustc_public/src/unstable/convert/stable/abi.rs index 6a563567044b0..766c522958db7 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/abi.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/abi.rs @@ -13,7 +13,7 @@ use crate::abi::{ AddressSpace, ArgAbi, ArgAttributes, ArgExtension, CallConvention, CastTarget, FieldsShape, FloatLength, FnAbi, IntegerLength, IntegerType, Layout, LayoutShape, NumScalableVectors, PassMode, Primitive, Reg, RegKind, ReprFlags, ReprOptions, Scalar, TagEncoding, TyAndLayout, - Uniform, ValueAbi, VariantFields, VariantsShape, WrappingRange, + Uniform, ValueRepr, VariantFields, VariantsShape, WrappingRange, }; use crate::compiler_interface::BridgeTys; use crate::target::MachineSize as Size; @@ -73,7 +73,7 @@ impl<'tcx> Stable<'tcx> for rustc_abi::LayoutData Stable<'tcx> for rustc_abi::BackendLaneCount { } impl<'tcx> Stable<'tcx> for rustc_abi::BackendRepr { - type T = ValueAbi; + type T = ValueRepr; fn stable<'cx>( &self, @@ -357,26 +357,26 @@ impl<'tcx> Stable<'tcx> for rustc_abi::BackendRepr { cx: &CompilerCtxt<'cx, BridgeTys>, ) -> Self::T { match *self { - rustc_abi::BackendRepr::Scalar(scalar) => ValueAbi::Scalar(scalar.stable(tables, cx)), + rustc_abi::BackendRepr::Scalar(scalar) => ValueRepr::Scalar(scalar.stable(tables, cx)), rustc_abi::BackendRepr::ScalarPair { a: first, b: second, b_offset: second_offset } => { - ValueAbi::ScalarPair { + ValueRepr::ScalarPair { a: first.stable(tables, cx), b: second.stable(tables, cx), b_offset: second_offset.stable(tables, cx), } } - rustc_abi::BackendRepr::SimdVector { element, count } => ValueAbi::Vector { + rustc_abi::BackendRepr::SimdVector { element, count } => ValueRepr::Vector { element: element.stable(tables, cx), count: count.stable(tables, cx), }, rustc_abi::BackendRepr::SimdScalableVector { element, count, number_of_vectors } => { - ValueAbi::ScalableVector { + ValueRepr::ScalableVector { element: element.stable(tables, cx), count: count.stable(tables, cx), number_of_vectors: number_of_vectors.stable(tables, cx), } } - rustc_abi::BackendRepr::Memory { sized } => ValueAbi::Aggregate { sized }, + rustc_abi::BackendRepr::Memory { sized } => ValueRepr::Aggregate { sized }, } } } diff --git a/tests/ui-fulldeps/rustc_public/check_abi.rs b/tests/ui-fulldeps/rustc_public/check_abi.rs index f3ebcfbbdf8ec..f6c95fb745409 100644 --- a/tests/ui-fulldeps/rustc_public/check_abi.rs +++ b/tests/ui-fulldeps/rustc_public/check_abi.rs @@ -16,7 +16,7 @@ extern crate rustc_public; use rustc_public::abi::{ ArgAbi, ArgExtension, CallConvention, FieldsShape, IntegerLength, PassMode, Primitive, Scalar, - ValueAbi, VariantsShape, + ValueRepr, VariantsShape, }; use rustc_public::mir::MirVisitor; use rustc_public::mir::mono::Instance; @@ -144,7 +144,7 @@ fn check_niche(abi: &ArgAbi) { assert!(layout.is_sized()); assert_eq!(layout.size.bytes(), 1); - let ValueAbi::Scalar(scalar) = layout.abi else { unreachable!() }; + let ValueRepr::Scalar(scalar) = layout.value_repr else { unreachable!() }; assert!(scalar.has_niche(&MachineInfo::target()), "Opps: {:?}", scalar); let Scalar::Initialized { value, valid_range } = scalar else { unreachable!() }; From 0be338853e6948dc60bae5128e23468d9d57b437 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:47 +1000 Subject: [PATCH 20/31] Avoid an intermediate vec when ref-decoding to `&'tcx [T]` For !needs_drop types, this should allow decoding directly into the arena-allocated slice. For needs_drop types, the arena already collects into a SmallVec, so this avoids an intermediate conversion from Vec to SmallVec. --- compiler/rustc_middle/src/arena.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index bfaef6157d02c..aab8b5ff9584c 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -157,8 +157,10 @@ where D: TyDecoder<'tcx>, T: ArenaAllocatable<'tcx, C> + Decodable, { - let values: Vec = Decodable::decode(decoder); - decoder.interner().arena.alloc_from_iter(values) + // The decoder for slices must match the decoder for `Vec`, + // which is a `usize` length followed by that many `T`. + let len = decoder.read_usize(); + decoder.interner().arena.alloc_from_iter((0..len).map(|_| T::decode(decoder))) } macro_rules! impl_ref_decodable_into_arena { From 3c74470db46ae8c3776c72bd2fc53c89a7f3a0b3 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:47 +1000 Subject: [PATCH 21/31] Remove some unused Decodable and RefDecodable impls --- compiler/rustc_middle/src/ty/codec.rs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index 537015a2560dd..afcab030698c7 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -10,9 +10,7 @@ use std::hash::Hash; use std::intrinsics; use std::marker::{DiscriminantKind, PointeeSized}; -use rustc_abi::FieldIdx; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::Const; use rustc_serialize::{Decodable, Encodable}; use rustc_span::{Span, SpanDecoder, SpanEncoder, Spanned}; @@ -436,30 +434,6 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> } } -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List { - fn decode(decoder: &mut D) -> &'tcx Self { - let len = decoder.read_usize(); - decoder - .interner() - .mk_fields_from_iter((0..len).map::(|_| Decodable::decode(decoder))) - } -} - -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List { - fn decode(decoder: &mut D) -> &'tcx Self { - let len = decoder.read_usize(); - decoder.interner().mk_local_def_ids_from_iter( - (0..len).map::(|_| Decodable::decode(decoder)), - ) - } -} - -impl<'tcx, D: TyDecoder<'tcx>> Decodable for &'tcx ty::List { - fn decode(d: &mut D) -> Self { - RefDecodable::decode(d) - } -} - impl_decodable_via_ref! { &'tcx ty::TypeckResults<'tcx>, &'tcx ty::List>, From 8838b56460b3d241ce0e1f4452f4b3a06f7200e8 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:47 +1000 Subject: [PATCH 22/31] Migrate some RefDecodable impls to `impl_ref_decodable_into_arena!` These impls all match the impl provided by the into-arena macro. --- compiler/rustc_middle/src/arena.rs | 9 +++++++- compiler/rustc_middle/src/ty/codec.rs | 30 +-------------------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index aab8b5ff9584c..d8640471b5672 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -2,9 +2,11 @@ //! `Copy` type, and any `!Copy` type explicitly listed below. use rustc_serialize::Decodable; +use rustc_span::{Span, Spanned}; +use crate::mono::MonoItem; use crate::ty::codec::{RefDecodable, TyDecoder}; -use crate::ty::{Ty, TyCtxt}; +use crate::ty::{self, Ty, TyCtxt}; // If a type `T` supported by the arena also needs to support decoding into `&'tcx T` // backed by an arena allocation (via `RefDecodable`), add it to the list in @@ -192,9 +194,14 @@ macro_rules! impl_ref_decodable_into_arena { // // Types in this list must be `ArenaAllocatable`, either because they are `Copy` // or because they are listed in the `declare_arena!` invocation. +// +// Types in this list must also implement `Decodable` for all `D: TyDecoder<'tcx>`. impl_ref_decodable_into_arena! { // tidy-alphabetical-start (rustc_middle::middle::exported_symbols::ExportedSymbol<'tcx>, rustc_middle::middle::exported_symbols::SymbolExportInfo), + (ty::Clause<'tcx>, Span), + (ty::PolyTraitRef<'tcx>, Span), + Spanned>, rustc_ast::InlineAsmTemplatePiece, rustc_ast::tokenstream::TokenStream, rustc_data_structures::unord::UnordMap>>, diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index afcab030698c7..028541b9a51ea 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -13,11 +13,10 @@ use std::marker::{DiscriminantKind, PointeeSized}; use rustc_data_structures::fx::FxHashMap; use rustc_middle::ty::Const; use rustc_serialize::{Decodable, Encodable}; -use rustc_span::{Span, SpanDecoder, SpanEncoder, Spanned}; +use rustc_span::{SpanDecoder, SpanEncoder}; use crate::infer::canonical::{CanonicalVarKind, CanonicalVarKinds}; use crate::mir::interpret::{AllocId, ConstAllocation, CtfeProvenance}; -use crate::mono::MonoItem; use crate::ty::{self, AdtDef, GenericArgsRef, Ty, TyCtxt}; use crate::{mir, traits}; @@ -369,33 +368,6 @@ impl<'tcx, D: TyDecoder<'tcx>> Decodable for AdtDef<'tcx> { } } -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [(ty::Clause<'tcx>, Span)] { - fn decode(decoder: &mut D) -> &'tcx Self { - decoder - .interner() - .arena - .alloc_from_iter((0..decoder.read_usize()).map(|_| Decodable::decode(decoder))) - } -} - -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [(ty::PolyTraitRef<'tcx>, Span)] { - fn decode(decoder: &mut D) -> &'tcx Self { - decoder - .interner() - .arena - .alloc_from_iter((0..decoder.read_usize()).map(|_| Decodable::decode(decoder))) - } -} - -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [Spanned>] { - fn decode(decoder: &mut D) -> &'tcx Self { - decoder - .interner() - .arena - .alloc_from_iter((0..decoder.read_usize()).map(|_| Decodable::decode(decoder))) - } -} - impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { fn decode(decoder: &mut D) -> &'tcx Self { let len = decoder.read_usize(); From 82a24f236657f81e8992aad808b33cfddcd323fc Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:47 +1000 Subject: [PATCH 23/31] Move RefDecodable and related impls into a submodule The existing `codec` module contains a sea of impls that is hard to navigate. Extracting the RefDecodable parts into another file should make it easier to keep track of what is where. This commit tries to move everything as-is, so that `git diff --color-moved` can verify that nothing was changed. Subsequent commits will modify the moved code. --- compiler/rustc_middle/src/ty/codec.rs | 105 +----------------- .../src/ty/codec/ref_decodable.rs | 105 ++++++++++++++++++ 2 files changed, 110 insertions(+), 100 deletions(-) create mode 100644 compiler/rustc_middle/src/ty/codec/ref_decodable.rs diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index 028541b9a51ea..1a63c05e06ced 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -8,17 +8,19 @@ use std::hash::Hash; use std::intrinsics; -use std::marker::{DiscriminantKind, PointeeSized}; +use std::marker::DiscriminantKind; use rustc_data_structures::fx::FxHashMap; -use rustc_middle::ty::Const; use rustc_serialize::{Decodable, Encodable}; use rustc_span::{SpanDecoder, SpanEncoder}; +pub use self::ref_decodable::RefDecodable; use crate::infer::canonical::{CanonicalVarKind, CanonicalVarKinds}; +use crate::mir; use crate::mir::interpret::{AllocId, ConstAllocation, CtfeProvenance}; use crate::ty::{self, AdtDef, GenericArgsRef, Ty, TyCtxt}; -use crate::{mir, traits}; + +mod ref_decodable; /// The shorthand encoding uses an enum's variant index `usize` /// and is offset by this value so it never matches a real variant. @@ -81,23 +83,6 @@ impl<'tcx, E: TyEncoder<'tcx>> EncodableWithShorthand<'tcx, E> for ty::Predicate } } -/// Trait for decoding to a reference. -/// -/// This is a separate trait from `Decodable` so that we can implement it for -/// upstream types, such as `FxHashSet`. -/// -/// The `TyDecodable` derive macro will use this trait for fields that are -/// references (and don't use a type alias to hide that). -/// -/// `Decodable` can still be implemented in cases where `Decodable` is required -/// by a trait bound. -/// -/// Implementations of this trait will typically allocate into an arena or interner, -/// e.g. see `impl_ref_decodable_into_arena!`. -pub trait RefDecodable<'tcx, D: TyDecoder<'tcx>>: PointeeSized { - fn decode(d: &mut D) -> &'tcx Self; -} - /// Encode the given value or a previously cached shorthand. pub fn encode_with_shorthand<'tcx, E, T, M>(encoder: &mut E, value: &T, cache: M) where @@ -307,36 +292,6 @@ impl<'tcx, D: TyDecoder<'tcx>> Decodable for ty::ParamEnv<'tcx> { } } -macro_rules! impl_decodable_via_ref { - ($($t:ty,)+) => { - $(impl<'tcx, D: TyDecoder<'tcx>> Decodable for $t { - fn decode(decoder: &mut D) -> Self { - RefDecodable::decode(decoder) - } - })* - } -} - -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { - fn decode(decoder: &mut D) -> &'tcx Self { - let len = decoder.read_usize(); - decoder - .interner() - .mk_type_list_from_iter((0..len).map::, _>(|_| Decodable::decode(decoder))) - } -} - -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> - for ty::List> -{ - fn decode(decoder: &mut D) -> &'tcx Self { - let len = decoder.read_usize(); - decoder.interner().mk_poly_existential_predicates_from_iter( - (0..len).map::, _>(|_| Decodable::decode(decoder)), - ) - } -} - impl<'tcx, D: TyDecoder<'tcx>> Decodable for ty::Const<'tcx> { fn decode(decoder: &mut D) -> Self { let kind: ty::ConstKind<'tcx> = Decodable::decode(decoder); @@ -368,56 +323,6 @@ impl<'tcx, D: TyDecoder<'tcx>> Decodable for AdtDef<'tcx> { } } -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { - fn decode(decoder: &mut D) -> &'tcx Self { - let len = decoder.read_usize(); - decoder.interner().mk_bound_variable_kinds_from_iter( - (0..len).map::, _>(|_| Decodable::decode(decoder)), - ) - } -} - -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { - fn decode(decoder: &mut D) -> &'tcx Self { - let len = decoder.read_usize(); - decoder.interner().mk_patterns_from_iter( - (0..len).map::, _>(|_| Decodable::decode(decoder)), - ) - } -} - -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { - fn decode(decoder: &mut D) -> &'tcx Self { - let len = decoder.read_usize(); - decoder.interner().mk_const_list_from_iter( - (0..len).map::, _>(|_| Decodable::decode(decoder)), - ) - } -} - -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> - for ty::ListWithCachedTypeInfo> -{ - fn decode(decoder: &mut D) -> &'tcx Self { - let len = decoder.read_usize(); - decoder.interner().mk_clauses_from_iter( - (0..len).map::, _>(|_| Decodable::decode(decoder)), - ) - } -} - -impl_decodable_via_ref! { - &'tcx ty::TypeckResults<'tcx>, - &'tcx ty::List>, - &'tcx ty::List>, - &'tcx traits::ImplSource<'tcx, ()>, - &'tcx mir::Body<'tcx>, - &'tcx ty::List>, - &'tcx ty::List>, - &'tcx ty::ListWithCachedTypeInfo>, - &'tcx ty::List>, -} - #[macro_export] macro_rules! __impl_decoder_methods { ($($name:ident -> $ty:ty;)*) => { diff --git a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs new file mode 100644 index 0000000000000..ec58fc0c128f6 --- /dev/null +++ b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs @@ -0,0 +1,105 @@ +use std::marker::PointeeSized; + +use rustc_middle::ty::Const; +use rustc_serialize::Decodable; + +use crate::ty::codec::TyDecoder; +use crate::ty::{self, Ty}; +use crate::{mir, traits}; + +/// Trait for decoding to a reference. +/// +/// This is a separate trait from `Decodable` so that we can implement it for +/// upstream types, such as `FxHashSet`. +/// +/// The `TyDecodable` derive macro will use this trait for fields that are +/// references (and don't use a type alias to hide that). +/// +/// `Decodable` can still be implemented in cases where `Decodable` is required +/// by a trait bound. +/// +/// Implementations of this trait will typically allocate into an arena or interner, +/// e.g. see `impl_ref_decodable_into_arena!`. +pub trait RefDecodable<'tcx, D: TyDecoder<'tcx>>: PointeeSized { + fn decode(d: &mut D) -> &'tcx Self; +} + +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { + fn decode(decoder: &mut D) -> &'tcx Self { + let len = decoder.read_usize(); + decoder + .interner() + .mk_type_list_from_iter((0..len).map::, _>(|_| Decodable::decode(decoder))) + } +} + +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> + for ty::List> +{ + fn decode(decoder: &mut D) -> &'tcx Self { + let len = decoder.read_usize(); + decoder.interner().mk_poly_existential_predicates_from_iter( + (0..len).map::, _>(|_| Decodable::decode(decoder)), + ) + } +} + +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { + fn decode(decoder: &mut D) -> &'tcx Self { + let len = decoder.read_usize(); + decoder.interner().mk_bound_variable_kinds_from_iter( + (0..len).map::, _>(|_| Decodable::decode(decoder)), + ) + } +} + +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { + fn decode(decoder: &mut D) -> &'tcx Self { + let len = decoder.read_usize(); + decoder.interner().mk_patterns_from_iter( + (0..len).map::, _>(|_| Decodable::decode(decoder)), + ) + } +} + +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { + fn decode(decoder: &mut D) -> &'tcx Self { + let len = decoder.read_usize(); + decoder.interner().mk_const_list_from_iter( + (0..len).map::, _>(|_| Decodable::decode(decoder)), + ) + } +} + +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> + for ty::ListWithCachedTypeInfo> +{ + fn decode(decoder: &mut D) -> &'tcx Self { + let len = decoder.read_usize(); + decoder.interner().mk_clauses_from_iter( + (0..len).map::, _>(|_| Decodable::decode(decoder)), + ) + } +} + +macro_rules! impl_decodable_via_ref { + ($($t:ty,)+) => { + $(impl<'tcx, D: TyDecoder<'tcx>> Decodable for $t { + fn decode(decoder: &mut D) -> Self { + RefDecodable::decode(decoder) + } + })* + } +} + +impl_decodable_via_ref! { + &'tcx ty::TypeckResults<'tcx>, + &'tcx ty::List>, + &'tcx ty::List>, + &'tcx traits::ImplSource<'tcx, ()>, + &'tcx mir::Body<'tcx>, + &'tcx ty::List>, + &'tcx ty::List>, + &'tcx ty::ListWithCachedTypeInfo>, + &'tcx ty::List>, +} From b993427bfdcdbc9b2b66504a3dd240ad47fd2b97 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:47 +1000 Subject: [PATCH 24/31] Miscellaneous tidying in `ref_decodable` - Added and expanded comments - Renamed and clarified the impl-decodable macro - Sorted the macro list of Decodable impls --- .../src/ty/codec/ref_decodable.rs | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs index ec58fc0c128f6..160089db930c2 100644 --- a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs +++ b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs @@ -1,6 +1,5 @@ use std::marker::PointeeSized; -use rustc_middle::ty::Const; use rustc_serialize::Decodable; use crate::ty::codec::TyDecoder; @@ -9,17 +8,17 @@ use crate::{mir, traits}; /// Trait for decoding to a reference. /// -/// This is a separate trait from `Decodable` so that we can implement it for +/// This is a separate trait from [`Decodable`] so that we can easily implement it for /// upstream types, such as `FxHashSet`. /// -/// The `TyDecodable` derive macro will use this trait for fields that are -/// references (and don't use a type alias to hide that). +/// The [`TyDecodable`](rustc_macros::TyDecodable) derive macro will use this +/// trait for fields that are references (and don't use a type alias to hide that). /// -/// `Decodable` can still be implemented in cases where `Decodable` is required -/// by a trait bound. +/// [`Decodable`] can still be implemented in cases where `Decodable` is required +/// by a trait bound; see `impl_decodable_via_ref_decodable_for_local_types!` for examples. /// /// Implementations of this trait will typically allocate into an arena or interner, -/// e.g. see `impl_ref_decodable_into_arena!`. +/// e.g. see `impl_ref_decodable_into_arena!` in [`rustc_middle::arena`]. pub trait RefDecodable<'tcx, D: TyDecoder<'tcx>>: PointeeSized { fn decode(d: &mut D) -> &'tcx Self; } @@ -82,24 +81,42 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> } } -macro_rules! impl_decodable_via_ref { - ($($t:ty,)+) => { - $(impl<'tcx, D: TyDecoder<'tcx>> Decodable for $t { - fn decode(decoder: &mut D) -> Self { - RefDecodable::decode(decoder) +/// Implements [`Decodable`] for `&'tcx T`, where [`T: RefDecodable`](RefDecodable) +/// and T is defined in this crate (`rustc_middle`). +/// +/// For locally-defined types, we can use a blanket impl over any [`D: TyDecoder`](TyDecoder). +/// +/// ## Note on implementing [`Decodable`] for references to non-local types +/// +/// For references to types not defined in this crate, including slices/tuples/collections +/// of local types, [`Decodable`] cannot use a blanket impl and must be implemented for +/// specific decoders instead. +macro_rules! impl_decodable_via_ref_decodable_for_local_types { + ( + $( + &'tcx $T:ty, + )* + ) => { + $( + impl<'tcx, D: TyDecoder<'tcx>> Decodable for &'tcx $T { + fn decode(decoder: &mut D) -> Self { + RefDecodable::decode(decoder) + } } - })* + )* } } -impl_decodable_via_ref! { - &'tcx ty::TypeckResults<'tcx>, - &'tcx ty::List>, - &'tcx ty::List>, - &'tcx traits::ImplSource<'tcx, ()>, +impl_decodable_via_ref_decodable_for_local_types! { + // tidy-alphabetical-start &'tcx mir::Body<'tcx>, + &'tcx traits::ImplSource<'tcx, ()>, + &'tcx ty::List>, &'tcx ty::List>, + &'tcx ty::List>, &'tcx ty::List>, + &'tcx ty::List>, &'tcx ty::ListWithCachedTypeInfo>, - &'tcx ty::List>, + &'tcx ty::TypeckResults<'tcx>, + // tidy-alphabetical-end } From a87b21352a9038be2b5833529d1f6db049f556c9 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:48 +1000 Subject: [PATCH 25/31] Consolidate the via-RefDecodable impls in `on_disk_cache` This commit replaces the existing boilerplate impls and the existing macro with a less confusing macro. Some unused impls have been removed. --- .../rustc_middle/src/query/on_disk_cache.rs | 118 ++++++------------ .../src/ty/codec/ref_decodable.rs | 3 + 2 files changed, 40 insertions(+), 81 deletions(-) diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 1fa0aa421d584..f3984f5bec329 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -689,88 +689,44 @@ impl<'a, 'tcx> BlobDecoder for CacheDecoder<'a, 'tcx> { } } -impl<'a, 'tcx> Decodable> for &'tcx UnordSet { - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - -impl<'a, 'tcx> Decodable> - for &'tcx UnordMap>> -{ - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - -impl<'a, 'tcx> Decodable> - for &'tcx IndexVec> -{ - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - -impl<'a, 'tcx> Decodable> for &'tcx [(ty::Clause<'tcx>, Span)] { - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - -impl<'a, 'tcx> Decodable> for &'tcx [rustc_ast::InlineAsmTemplatePiece] { - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - -impl<'a, 'tcx> Decodable> for &'tcx [Spanned>] { - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - -impl<'a, 'tcx> Decodable> - for &'tcx crate::traits::specialization_graph::Graph -{ - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - -impl<'a, 'tcx> Decodable> for &'tcx rustc_ast::tokenstream::TokenStream { - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - -macro_rules! impl_ref_decoder { - (<$tcx:tt> $($ty:ty,)*) => { - $(impl<'a, $tcx> Decodable> for &$tcx [$ty] { - #[inline] - fn decode(d: &mut CacheDecoder<'a, $tcx>) -> Self { - RefDecodable::decode(d) +/// Implements [`Decodable`] for `&'tcx T`, where [`T: RefDecodable`](RefDecodable). +/// +/// Due to orphan-rule restrictions, these foreign impls cannot use a blanket +/// [`D: TyDecoder`](TyDecoder), and must instead specify a specific decoder. +/// +/// For impls on types defined in `rustc_middle`, see +/// `impl_decodable_via_ref_decodable_for_local_type!` instead. +macro_rules! impl_decodable_via_ref_decodable_for_foreign_types { + ( + $( + &'tcx $T:ty, + )* + ) => { + $( + impl<'tcx> Decodable> for &'tcx $T { + fn decode(decoder: &mut CacheDecoder<'_, 'tcx>) -> Self { + RefDecodable::decode(decoder) + } } - })* - }; -} - -impl_ref_decoder! {<'tcx> - Span, - rustc_hir::Attribute, - rustc_span::Ident, - ty::Variance, - rustc_span::def_id::DefId, - rustc_span::def_id::LocalDefId, - (rustc_middle::middle::exported_symbols::ExportedSymbol<'tcx>, rustc_middle::middle::exported_symbols::SymbolExportInfo), - rustc_middle::middle::deduced_param_attrs::DeducedParamAttrs, + )* + } +} + +impl_decodable_via_ref_decodable_for_foreign_types! { + // tidy-alphabetical-start + &'tcx IndexVec>, + &'tcx UnordMap>>, + &'tcx UnordSet, + &'tcx [( + rustc_middle::middle::exported_symbols::ExportedSymbol<'tcx>, + rustc_middle::middle::exported_symbols::SymbolExportInfo, + )], + &'tcx [(ty::Clause<'tcx>, Span)], + &'tcx [DefId], + &'tcx [Spanned>], + &'tcx [ty::Variance], + &'tcx rustc_ast::tokenstream::TokenStream, + // tidy-alphabetical-end } //- ENCODING ------------------------------------------------------------------- diff --git a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs index 160089db930c2..fd9d5af45cfc5 100644 --- a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs +++ b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs @@ -91,6 +91,8 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> /// For references to types not defined in this crate, including slices/tuples/collections /// of local types, [`Decodable`] cannot use a blanket impl and must be implemented for /// specific decoders instead. +/// +/// See invocations of `impl_decodable_via_ref_decodable_for_foreign_types!` for examples. macro_rules! impl_decodable_via_ref_decodable_for_local_types { ( $( @@ -111,6 +113,7 @@ impl_decodable_via_ref_decodable_for_local_types! { // tidy-alphabetical-start &'tcx mir::Body<'tcx>, &'tcx traits::ImplSource<'tcx, ()>, + &'tcx traits::specialization_graph::Graph, &'tcx ty::List>, &'tcx ty::List>, &'tcx ty::List>, From 8dd9b9ab5fc6672a586dc7d922e119ba5972c972 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:48 +1000 Subject: [PATCH 26/31] Remove an unused via-RefDecodable impl in rmeta decoding --- compiler/rustc_metadata/src/rmeta/decoder.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index b7a07e1d9d162..84092ffffdbab 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -667,12 +667,6 @@ impl<'a, 'tcx> Decodable> for SpanData { } } -impl<'a, 'tcx> Decodable> for &'tcx [(ty::Clause<'tcx>, Span)] { - fn decode(d: &mut MetadataDecodeContext<'a, 'tcx>) -> Self { - ty::codec::RefDecodable::decode(d) - } -} - impl Decodable for LazyValue { fn decode(decoder: &mut D) -> Self { decoder.read_lazy() From e8c03abc73a94b10afa1773be790a61f69a92aa5 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:48 +1000 Subject: [PATCH 27/31] Add a `#[diagnostic::on_unimplemented(..)]` hint to RefDecodable --- compiler/rustc_middle/src/ty/codec/ref_decodable.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs index fd9d5af45cfc5..5927a4db81648 100644 --- a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs +++ b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs @@ -19,6 +19,9 @@ use crate::{mir, traits}; /// /// Implementations of this trait will typically allocate into an arena or interner, /// e.g. see `impl_ref_decodable_into_arena!` in [`rustc_middle::arena`]. +#[diagnostic::on_unimplemented( + note = "consider adding `{Self}` to the list in `impl_ref_decodable_into_arena!`" +)] pub trait RefDecodable<'tcx, D: TyDecoder<'tcx>>: PointeeSized { fn decode(d: &mut D) -> &'tcx Self; } From a3db66c97fbbe30e22f0d934ddc7798ac7780534 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 16 Sep 2026 11:00:48 +1000 Subject: [PATCH 28/31] Remove the decoder type-param from RefDecodable It's simpler to make the `decode` method generic over `impl TyDecoder`. --- compiler/rustc_middle/src/arena.rs | 8 ++--- .../src/ty/codec/ref_decodable.rs | 32 ++++++++----------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index d8640471b5672..ee71d4d46e48f 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -172,16 +172,16 @@ macro_rules! impl_ref_decodable_into_arena { )* ) => { $( - impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for $ty { + impl<'tcx> RefDecodable<'tcx> for $ty { #[inline] - fn decode(decoder: &mut D) -> &'tcx Self { + fn decode(decoder: &mut impl TyDecoder<'tcx>) -> &'tcx Self { decode_arena_allocatable(decoder) } } - impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [$ty] { + impl<'tcx> RefDecodable<'tcx> for [$ty] { #[inline] - fn decode(decoder: &mut D) -> &'tcx Self { + fn decode(decoder: &mut impl TyDecoder<'tcx>) -> &'tcx Self { decode_arena_allocatable_slice(decoder) } } diff --git a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs index 5927a4db81648..9f37f9687da51 100644 --- a/compiler/rustc_middle/src/ty/codec/ref_decodable.rs +++ b/compiler/rustc_middle/src/ty/codec/ref_decodable.rs @@ -22,12 +22,12 @@ use crate::{mir, traits}; #[diagnostic::on_unimplemented( note = "consider adding `{Self}` to the list in `impl_ref_decodable_into_arena!`" )] -pub trait RefDecodable<'tcx, D: TyDecoder<'tcx>>: PointeeSized { - fn decode(d: &mut D) -> &'tcx Self; +pub trait RefDecodable<'tcx>: PointeeSized { + fn decode(d: &mut impl TyDecoder<'tcx>) -> &'tcx Self; } -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { - fn decode(decoder: &mut D) -> &'tcx Self { +impl<'tcx> RefDecodable<'tcx> for ty::List> { + fn decode(decoder: &mut impl TyDecoder<'tcx>) -> &'tcx Self { let len = decoder.read_usize(); decoder .interner() @@ -35,10 +35,8 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { } } -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> - for ty::List> -{ - fn decode(decoder: &mut D) -> &'tcx Self { +impl<'tcx> RefDecodable<'tcx> for ty::List> { + fn decode(decoder: &mut impl TyDecoder<'tcx>) -> &'tcx Self { let len = decoder.read_usize(); decoder.interner().mk_poly_existential_predicates_from_iter( (0..len).map::, _>(|_| Decodable::decode(decoder)), @@ -46,8 +44,8 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> } } -impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { - fn decode(decoder: &mut D) -> &'tcx Self { +impl<'tcx> RefDecodable<'tcx> for ty::List> { + fn decode(decoder: &mut impl TyDecoder<'tcx>) -> &'tcx Self { let len = decoder.read_usize(); decoder.interner().mk_bound_variable_kinds_from_iter( (0..len).map::, _>(|_| Decodable::decode(decoder)), @@ -55,8 +53,8 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> RefDecodable<'tcx, D> for ty::List> { - fn decode(decoder: &mut D) -> &'tcx Self { +impl<'tcx> RefDecodable<'tcx> for ty::List> { + fn decode(decoder: &mut impl TyDecoder<'tcx>) -> &'tcx Self { let len = decoder.read_usize(); decoder.interner().mk_patterns_from_iter( (0..len).map::, _>(|_| Decodable::decode(decoder)), @@ -64,8 +62,8 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> RefDecodable<'tcx, D> for ty::List> { - fn decode(decoder: &mut D) -> &'tcx Self { +impl<'tcx> RefDecodable<'tcx> for ty::List> { + fn decode(decoder: &mut impl TyDecoder<'tcx>) -> &'tcx Self { let len = decoder.read_usize(); decoder.interner().mk_const_list_from_iter( (0..len).map::, _>(|_| Decodable::decode(decoder)), @@ -73,10 +71,8 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> RefDecodable<'tcx, D> - for ty::ListWithCachedTypeInfo> -{ - fn decode(decoder: &mut D) -> &'tcx Self { +impl<'tcx> RefDecodable<'tcx> for ty::ListWithCachedTypeInfo> { + fn decode(decoder: &mut impl TyDecoder<'tcx>) -> &'tcx Self { let len = decoder.read_usize(); decoder.interner().mk_clauses_from_iter( (0..len).map::, _>(|_| Decodable::decode(decoder)), From 0c12d6f94b8d3d6856aad2d6294eaebae2c26ef8 Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Wed, 16 Sep 2026 06:34:21 +0300 Subject: [PATCH 29/31] Remove redundant test --- tests/ui/consts/const-for.rs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 tests/ui/consts/const-for.rs diff --git a/tests/ui/consts/const-for.rs b/tests/ui/consts/const-for.rs deleted file mode 100644 index 5b9bcff410144..0000000000000 --- a/tests/ui/consts/const-for.rs +++ /dev/null @@ -1,8 +0,0 @@ -//@ check-pass -#![feature(const_trait_impl,const_iter,const_for)] - -const _: () = { - for _ in 0..5 {} -}; - -fn main() {} From 4bfe145fcb7ffae7e252cfd073dad3ccb2cf0ee0 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 16 Sep 2026 13:29:17 +1000 Subject: [PATCH 30/31] Introduce `ast::UseTreeAndId` Currently a `NodeId` is stored in a pair with each nested use tree. This commit changes the pair to a named type `UseTreeAndId`. In most places this doesn't make much difference but in the AST visitor it gets rid of several weird special cases. --- compiler/rustc_ast/src/ast.rs | 10 ++++-- compiler/rustc_ast/src/visit.rs | 18 ++--------- compiler/rustc_ast_lowering/src/item.rs | 8 +++-- compiler/rustc_ast_lowering/src/lib.rs | 5 +-- .../rustc_ast_pretty/src/pprust/state/item.rs | 9 +++--- .../src/assert/context.rs | 16 +++++----- compiler/rustc_expand/src/expand.rs | 4 +-- compiler/rustc_lint/src/unused.rs | 10 +++--- compiler/rustc_parse/src/parser/item.rs | 4 +-- .../rustc_resolve/src/build_reduced_graph.rs | 13 ++++++-- compiler/rustc_resolve/src/check_unused.rs | 31 ++++++++++--------- compiler/rustc_resolve/src/late.rs | 4 +-- .../src/single_component_path_imports.rs | 8 ++--- .../src/unnecessary_self_imports.rs | 10 +++--- .../src/unsafe_removed_from_name.rs | 4 +-- .../clippy/clippy_utils/src/ast_utils/mod.rs | 4 ++- src/tools/rustfmt/src/imports.rs | 4 +-- 17 files changed, 84 insertions(+), 78 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 809b8b7f6a74d..5b9f3231fc744 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3336,13 +3336,12 @@ pub enum UseTreeKind { /// use foo::{bar, baz}; /// ^^^^^^^^^^ /// ``` - Nested { items: ThinVec<(UseTree, NodeId)>, span: Span }, + Nested { items: ThinVec, span: Span }, /// `use prefix::*` Glob(Span), } /// A tree of paths sharing common prefixes. -/// Used in `use` items both at top-level and inside of braces in import groups. #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct UseTree { pub prefix: Path, @@ -3384,6 +3383,13 @@ impl UseTree { } } +/// Used in nested `use` trees. +#[derive(Clone, Encodable, Decodable, Debug, Walkable)] +pub struct UseTreeAndId { + pub inner: UseTree, + pub id: NodeId, +} + /// Distinguishes between `Attribute`s that decorate items and Attributes that /// are contained as statements within items. These two cases need to be /// distinguished for pretty-printing. diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index c12f24a7eff87..4270ac0656deb 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -347,7 +347,6 @@ macro_rules! for_each_ast_visit_hook { visit_mac_call(MacCall) => walk_mac; visit_macro_def(MacroDef) => walk_macro_def; visit_mut_restriction(MutRestriction) => walk_mut_restriction; - //visit_nested_use_tree((UseTree, NodeId)) => walk_nested_use_tree; visit_param(Param) => walk_param; visit_param_bound(GenericBound, _ctxt: BoundKind) => walk_param_bound; visit_pat(Pat) => walk_pat; @@ -368,6 +367,7 @@ macro_rules! for_each_ast_visit_hook { visit_ty(Ty) => walk_ty; visit_ty_pat(TyPat) => walk_ty_pat; visit_use_tree(UseTree) => walk_use_tree; + visit_use_tree_and_id(UseTreeAndId) => walk_use_tree_and_id; visit_variant(Variant) => walk_variant; visit_variant_data(VariantData) => walk_variant_data; visit_vis(Visibility) => walk_vis; @@ -477,6 +477,7 @@ macro_rules! common_visitor_and_walkers { ThinVec, ThinVec, ThinVec, + ThinVec, // tidy-alphabetical-end } @@ -681,13 +682,6 @@ macro_rules! common_visitor_and_walkers { fn visit_stmt(&mut self, s: &$lt Stmt) -> Self::Result { walk_stmt(self, s) } - - fn visit_nested_use_tree(&mut self, use_tree: &$lt UseTree, id: NodeId) - -> Self::Result - { - try_visit!(self.visit_id(id)); - self.visit_use_tree(use_tree) - } )? // `MutVisitor`-only methods @@ -812,14 +806,6 @@ macro_rules! common_visitor_and_walkers { ) -> V::Result; } - $(impl_visitable!(|&$lt self: ThinVec<(UseTree, NodeId)>, vis: &mut V| { - for (nested_tree, nested_id) in self { - try_visit!(vis.visit_nested_use_tree(nested_tree, *nested_id)); - } - V::Result::output() - });)? - $(${ignore($mut)} impl_visitable_list!(ThinVec<(UseTree, NodeId)>,);)? - fn walk_item_inner<$($lt,)? K: WalkItemKind, V: $Visitor$(<$lt>)?>( visitor: &mut V, item: &$($lt)? $($mut)? Item, diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index b8baf21898a96..cfb7d66656f82 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -680,7 +680,8 @@ impl<'hir> LoweringContext<'_, 'hir> { let prefix = Path { segments, span }; // Add all the nested `PathListItem`s to the HIR. - for &(ref use_tree, id) in trees { + for use_tree in trees { + let id = use_tree.id; let owner_id = self.owner_id(id); // Each `use` import is an item and thus are owners of the @@ -692,7 +693,8 @@ impl<'hir> LoweringContext<'_, 'hir> { // `prefix` is lowered multiple times, but in different HIR owners. // So each segment gets renewed `HirId` with the same // `ItemLocalId` and the new owner. (See `lower_node_id`) - let kind = this.lower_use_tree(use_tree, &prefix, id, vis_span, attrs); + let kind = + this.lower_use_tree(&use_tree.inner, &prefix, id, vis_span, attrs); if !attrs.is_empty() { this.curr_owner.attrs.insert(hir::ItemLocalId::ZERO, attrs); } @@ -701,7 +703,7 @@ impl<'hir> LoweringContext<'_, 'hir> { owner_id, kind, vis_span, - span: this.lower_span(use_tree.span()), + span: this.lower_span(use_tree.inner.span()), eii: find_attr!(attrs, EiiImpl(..) | EiiDeclaration(..)), }; hir::OwnerNode::Item(this.arena.alloc(item)) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 1a351cc1420f3..20027ab68b9ea 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -674,12 +674,13 @@ fn index_ast<'tcx>( match tree.kind { UseTreeKind::Glob(_) | UseTreeKind::Simple(_) => {} UseTreeKind::Nested { items: ref nested_vec, span } => { - for &(ref nested, id) in nested_vec { + for nested in nested_vec { + let id = nested.id; self.insert(id, AstOwner::NestedUseTree(parent)); items.push(self.make_dummy(id, span, ItemKind::MacCall)); let def_id = self.owners[&id].def_id; - self.visit_item_id_use_tree(nested, def_id, items); + self.visit_item_id_use_tree(&nested.inner, def_id, items); } } } diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index ae13f627fcbe5..6ce34cecc7e97 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -911,14 +911,15 @@ impl<'a> State<'a> { } if items.is_empty() { self.word("{}"); - } else if let [(item, _)] = items.as_slice() + } else if let [item] = items.as_slice() && !item + .inner .prefix .segments .first() .is_some_and(|seg| seg.ident.name == rustc_span::symbol::kw::SelfLower) { - self.print_use_tree(item); + self.print_use_tree(&item.inner); } else { let cb = self.cbox(INDENT_UNIT); self.word("{"); @@ -926,10 +927,10 @@ impl<'a> State<'a> { let ib = self.ibox(0); for (idx, use_tree) in items.iter().enumerate() { let is_last = idx == items.len() - 1; - self.print_use_tree(&use_tree.0); + self.print_use_tree(&use_tree.inner); if !is_last { self.word(","); - if let ast::UseTreeKind::Nested { .. } = use_tree.0.kind { + if let ast::UseTreeKind::Nested { .. } = use_tree.inner.kind { self.hardbreak(); } else { self.space(); diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 80ea2e3d877fc..ef39ca049b811 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -2,7 +2,7 @@ use rustc_ast::token::{self, Delimiter, IdentIsRaw}; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::{ BinOpKind, BorrowKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, ItemKind, MacCall, MethodCall, - Mutability, Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind, + Mutability, Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeAndId, UseTreeKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; @@ -97,14 +97,12 @@ impl<'cx, 'a> Context<'cx, 'a> { /// /// use ::core::asserting::{ ... }; fn build_initial_imports(&self) -> Stmt { - let nested_tree = |this: &Self, sym| { - ( - UseTree { - prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]), - kind: UseTreeKind::Simple(None), - }, - DUMMY_NODE_ID, - ) + let nested_tree = |this: &Self, sym| UseTreeAndId { + inner: UseTree { + prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]), + kind: UseTreeKind::Simple(None), + }, + id: DUMMY_NODE_ID, }; self.cx.stmt_item( self.span, diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index c58629111ac00..fc86bdfcf4f18 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1463,8 +1463,8 @@ impl DeclaredIdents for Box { ast::UseTreeKind::Glob(_) => {} ast::UseTreeKind::Simple(_) => idents.push(ut.ident()), ast::UseTreeKind::Nested { items, .. } => { - for (ut, _) in items { - collect_use_tree_leaves(ut, idents); + for tree in items { + collect_use_tree_leaves(&tree.inner, idents); } } } diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 7452e3e08ade9..17c078615c411 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1318,17 +1318,17 @@ impl UnusedImportBraces { fn check_use_tree(&self, cx: &EarlyContext<'_>, use_tree: &ast::UseTree, item: &ast::Item) { if let ast::UseTreeKind::Nested { ref items, .. } = use_tree.kind { // Recursively check nested UseTrees - for (tree, _) in items { - self.check_use_tree(cx, tree, item); + for tree in items { + self.check_use_tree(cx, &tree.inner, item); } // Trigger the lint only if there is one nested item - let [(tree, _)] = items.as_slice() else { return }; + let [tree] = items.as_slice() else { return }; // Trigger the lint if the nested item is a non-self single item - let node_name = match tree.kind { + let node_name = match tree.inner.kind { ast::UseTreeKind::Simple(rename) => { - let orig_ident = tree.prefix.segments.last().unwrap().ident; + let orig_ident = tree.inner.prefix.segments.last().unwrap().ident; if orig_ident.name == kw::SelfLower { return; } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index d4717b88bbb14..2ab012e0ecc2f 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1368,7 +1368,7 @@ impl<'a> Parser<'a> { &mut self, use_token_span: Span, prefix: Option<&'b UsePathList<'b>>, - ) -> PResult<'a, ThinVec<(UseTree, ast::NodeId)>> { + ) -> PResult<'a, ThinVec> { self.parse_delim_comma_seq(exp!(OpenBrace), exp!(CloseBrace), |p| { p.recover_vcs_conflict_marker(); @@ -1386,7 +1386,7 @@ impl<'a> Parser<'a> { p.emit_error_attr_in_use_tree(use_token_span, prefix, use_tree.span(), attr_span); } - Ok((use_tree, DUMMY_NODE_ID)) + Ok(UseTreeAndId { inner: use_tree, id: DUMMY_NODE_ID }) }) .map(|(r, _)| r) } diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 0557099208dea..33bed2eb46c9f 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -754,10 +754,19 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { } } ast::UseTreeKind::Nested { ref items, .. } => { - for &(ref tree, id) in items { + for tree in items { + let id = tree.id; self.with_owner(id, None, DefKind::Use, use_tree.span(), |this, feed| { this.build_reduced_graph_for_use_tree( - item, tree, id, &prefix, true, false, vis, root_span, feed, + item, + &tree.inner, + id, + &prefix, + true, + false, + vis, + root_span, + feed, ) }); } diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 2b4fe2cbd5c06..fdac03a8f7163 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -148,9 +148,9 @@ impl<'a, 'ra, 'tcx> UnusedImportCheckVisitor<'a, 'ra, 'tcx> { } } - fn check_imports_as_underscore(&mut self, items: &[(ast::UseTree, ast::NodeId)]) { - for (item, id) in items { - self.check_import_as_underscore(item, *id); + fn check_imports_as_underscore(&mut self, items: &[ast::UseTreeAndId]) { + for use_tree in items { + self.check_import_as_underscore(&use_tree.inner, use_tree.id); } } @@ -275,9 +275,9 @@ impl<'a, 'ra, 'tcx> Visitor<'a> for UnusedImportCheckVisitor<'a, 'ra, 'tcx> { visit::walk_item(self, item); } - fn visit_nested_use_tree(&mut self, use_tree: &'a ast::UseTree, id: ast::NodeId) { - self.check_use_tree(use_tree, id); - visit::walk_use_tree(self, use_tree); + fn visit_use_tree_and_id(&mut self, tree: &'a ast::UseTreeAndId) { + self.check_use_tree(&tree.inner, tree.id); + visit::walk_use_tree_and_id(self, tree); } } @@ -320,8 +320,8 @@ fn calc_unused_spans( let mut used_children = 0; let mut contains_self = false; let mut previous_unused = false; - for (pos, (use_tree, use_tree_id)) in nested.iter().enumerate() { - let remove = match calc_unused_spans(unused_import, use_tree, *use_tree_id) { + for (pos, use_tree) in nested.iter().enumerate() { + let remove = match calc_unused_spans(unused_import, &use_tree.inner, use_tree.id) { UnusedSpanResult::Used => { used_children += 1; None @@ -343,10 +343,11 @@ fn calc_unused_spans( } else if pos == nested.len() - 1 || used_children > 0 { // Delete everything from the end of the last import, to delete the // previous comma - nested[pos - 1].0.hi_span().shrink_to_hi().to(use_tree.hi_span()) + nested[pos - 1].inner.hi_span().shrink_to_hi().to(use_tree.inner.hi_span()) } else { // Delete everything until the next import, to delete the trailing commas - use_tree.prefix.span.to(nested[pos + 1].0.prefix.span.shrink_to_lo()) + let inner = &nested[pos + 1].inner; + use_tree.inner.prefix.span.to(inner.prefix.span.shrink_to_lo()) }; // Try to collapse adjacent spans into a single one. This prevents all cases of @@ -358,9 +359,9 @@ fn calc_unused_spans( to_remove.push(remove_span); } } - contains_self |= use_tree.prefix == kw::SelfLower - && matches!(use_tree.kind, ast::UseTreeKind::Simple(_)) - && !unused_import.unused.contains(&use_tree_id); + contains_self |= use_tree.inner.prefix == kw::SelfLower + && matches!(use_tree.inner.kind, ast::UseTreeKind::Simple(_)) + && !unused_import.unused.contains(&use_tree.id); previous_unused = remove.is_some(); } if unused_spans.is_empty() { @@ -385,7 +386,7 @@ fn calc_unused_spans( tree_span.shrink_to_lo().to(nested .first() .unwrap() - .0 + .inner .prefix .span .shrink_to_lo()), @@ -395,7 +396,7 @@ fn calc_unused_spans( nested .last() .unwrap() - .0 + .inner .hi_span() .shrink_to_hi() .to(tree_span.shrink_to_hi()), diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 095d5131c0b60..dd1e4c0344418 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -2850,8 +2850,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } } } else if let UseTreeKind::Nested { items, .. } = &use_tree.kind { - for (use_tree, _) in items { - self.future_proof_import(use_tree); + for use_tree in items { + self.future_proof_import(&use_tree.inner); } } } diff --git a/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs index 837aefc767c8c..dfcab8c74434a 100644 --- a/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs +++ b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs @@ -209,15 +209,15 @@ impl SingleComponentPathImports { // keep track of `use {some_module, some_other_module};` usages if let UseTreeKind::Nested { items, .. } = &use_tree.kind { for tree in items { - let segments = &tree.0.prefix.segments; + let segments = &tree.inner.prefix.segments; if segments.len() == 1 - && let UseTreeKind::Simple(None) = tree.0.kind + && let UseTreeKind::Simple(None) = tree.inner.kind { let name = segments[0].ident.name; if !macros.contains(&name) { single_use_usages.push(SingleUse { name, - span: tree.0.span(), + span: tree.inner.span(), item_id: item.id, can_suggest: false, }); @@ -237,7 +237,7 @@ impl SingleComponentPathImports { // nested case such as `use self::{module1::Struct1, module2::Struct2}` if let UseTreeKind::Nested { items, .. } = &use_tree.kind { for tree in items { - let segments = &tree.0.prefix.segments; + let segments = &tree.inner.prefix.segments; if !segments.is_empty() { imports_reused_with_self.push(segments[0].ident.name); } diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_self_imports.rs b/src/tools/clippy/clippy_lints/src/unnecessary_self_imports.rs index 677a459a03c7b..cd31d728ead14 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_self_imports.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_self_imports.rs @@ -104,18 +104,18 @@ struct SelfImport<'a> { fn for_each_self_import<'a>(tree: &'a UseTree, emit_lint: impl Fn(SelfImport<'a>) + Copy) { fn inner<'a>(tree: &'a UseTree, emit_lint: impl Fn(SelfImport<'a>) + Copy, is_toplevel: bool) { if let UseTreeKind::Nested { items, .. } = &tree.kind { - if let [(self_tree, _)] = &**items - && let [self_seg] = &*self_tree.prefix.segments + if let [self_tree] = &**items + && let [self_seg] = &*self_tree.inner.prefix.segments && self_seg.ident.name == kw::SelfLower { emit_lint(SelfImport { tree, - self_tree, + self_tree: &self_tree.inner, is_toplevel, }); } else { - for (subtree, _) in &**items { - inner(subtree, emit_lint, false); + for subtree in &**items { + inner(&subtree.inner, emit_lint, false); } } } diff --git a/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs b/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs index 8756a09d56b47..20b5d36b8d5f3 100644 --- a/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs +++ b/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs @@ -52,8 +52,8 @@ fn check_use_tree(use_tree: &UseTree, cx: &EarlyContext<'_>, span: Span) { }, UseTreeKind::Simple(None) | UseTreeKind::Glob(_) => {}, UseTreeKind::Nested { ref items, .. } => { - for (use_tree, _) in items { - check_use_tree(use_tree, cx, span); + for use_tree in items { + check_use_tree(&use_tree.inner, cx, span); } }, } diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index c340c56781082..74be29dbb38c0 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -778,7 +778,9 @@ fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool { match (l, r) { (Glob(_), Glob(_)) => true, (Simple(l), Simple(r)) => both(l.as_ref(), r.as_ref(), |l, r| eq_id(*l, *r)), - (Nested { items: l, .. }, Nested { items: r, .. }) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)), + (Nested { items: l, .. }, Nested { items: r, .. }) => { + over(l, r, |l, r| eq_use_tree(&l.inner, &r.inner)) + } _ => false, } } diff --git a/src/tools/rustfmt/src/imports.rs b/src/tools/rustfmt/src/imports.rs index c5a2a5de2f175..f062eaa332d92 100644 --- a/src/tools/rustfmt/src/imports.rs +++ b/src/tools/rustfmt/src/imports.rs @@ -477,7 +477,7 @@ impl UseTree { // This needs to be done before sorting use items. let items = itemize_list( context.snippet_provider, - list.iter().map(|(tree, _)| tree), + list.iter().map(|tree| &tree.inner), "}", ",", |tree| tree.prefix.span.lo(), @@ -501,7 +501,7 @@ impl UseTree { list.iter() .zip(items) .map(|(t, list_item)| { - Self::from_ast(context, &t.0, Some(list_item), None, None, None) + Self::from_ast(context, &t.inner, Some(list_item), None, None, None) }) .collect(), ); From 3f8e2bfc76d63b52a6e3f78b3b368813f60b70b9 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Tue, 8 Sep 2026 13:57:00 +0800 Subject: [PATCH 31/31] Enable LLVM Thin LTO for LoongArch64 This PR switches the LLVM build cross-compiler from GCC to Clang and enables LLVM Thin LTO for LoongArch64 distributions. There is no significant performance difference with this change. It also reduces the size of the build artifacts: | Artifacts | Before | After | |-------------------------------------------------------|--------|-------| | rust-nightly-loongarch64-unknown-linux-gnu.tar.xz | 287M | 177M | | rust-nightly-loongarch64-unknown-linux-gnu directory | 1.6G | 849M | | rust-nightly-loongarch64-unknown-linux-musl.tar.xz | 285M | 177M | | rust-nightly-loongarch64-unknown-linux-musl directory | 1.6G | 847M | --- .../dist-loongarch64-linux/Dockerfile | 35 ++++++++++++++---- .../dist-loongarch64-musl/Dockerfile | 37 +++++++++++++++---- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/ci/docker/host-x86_64/dist-loongarch64-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-loongarch64-linux/Dockerfile index 9b1684bbd2ace..b7d281cc2dbe7 100644 --- a/src/ci/docker/host-x86_64/dist-loongarch64-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-loongarch64-linux/Dockerfile @@ -1,8 +1,14 @@ -FROM ubuntu:22.04 +FROM ubuntu:26.04 COPY scripts/cross-apt-packages.sh /scripts/ RUN sh /scripts/cross-apt-packages.sh +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + clang-22 \ + llvm-22 \ + lld-22 + COPY scripts/crosstool-ng-git.sh /scripts/ COPY scripts/crosstool-ng-sha256-20260705.diff /scripts/ RUN sh /scripts/crosstool-ng-git.sh @@ -18,13 +24,26 @@ RUN /scripts/crosstool-ng-build.sh COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh -ENV PATH=$PATH:/x-tools/loongarch64-unknown-linux-gnu/bin +ENV PATH=/usr/lib/llvm-22/bin:$PATH:/x-tools/loongarch64-unknown-linux-gnu/bin + +# --no-rosegment keeps read-only code in the first LOAD segment, matching the +# segment layout produced by GNU ld. This allows Linux to make better use of +# file-backed PMD mappings and reduces iTLB misses. +ENV CLANG_FLAGS="--target=loongarch64-unknown-linux-gnu -fuse-ld=lld \ + --gcc-toolchain=/x-tools/loongarch64-unknown-linux-gnu \ + --sysroot=/x-tools/loongarch64-unknown-linux-gnu/loongarch64-unknown-linux-gnu/sysroot \ + -Wl,--no-rosegment" -ENV CC_loongarch64_unknown_linux_gnu=loongarch64-unknown-linux-gnu-gcc \ - AR_loongarch64_unknown_linux_gnu=loongarch64-unknown-linux-gnu-ar \ - CXX_loongarch64_unknown_linux_gnu=loongarch64-unknown-linux-gnu-g++ \ - CFLAGS_loongarch64_unknown_linux_gnu="-mcmodel=medium" \ - CXXFLAGS_loongarch64_unknown_linux_gnu="-mcmodel=medium" +ENV CC=clang \ + CXX=clang++ \ + CC_loongarch64_unknown_linux_gnu=clang \ + CXX_loongarch64_unknown_linux_gnu=clang++ \ + CFLAGS_loongarch64_unknown_linux_gnu="$CLANG_FLAGS -mcmodel=medium" \ + CXXFLAGS_loongarch64_unknown_linux_gnu="$CLANG_FLAGS -mcmodel=medium" \ + AR_loongarch64_unknown_linux_gnu=llvm-ar \ + RANLIB_loongarch64_unknown_linux_gnu=llvm-ranlib \ + CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_LINKER=clang \ + CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-Clink-arg=${CLANG_FLAGS// / -Clink-arg=}" # We re-use the Linux toolchain for bare-metal, because upstream bare-metal # target support for LoongArch is only available from GCC 14+. @@ -64,6 +83,8 @@ ENV RUST_CONFIGURE_ARGS="--enable-extended \ --enable-sanitizers \ --disable-docs \ --set build.allocator=jemalloc \ + --set llvm.link-shared=true \ + --set llvm.thin-lto=true \ --set rust.lto=thin \ --set rust.codegen-units=1" diff --git a/src/ci/docker/host-x86_64/dist-loongarch64-musl/Dockerfile b/src/ci/docker/host-x86_64/dist-loongarch64-musl/Dockerfile index f9eac213e5060..582a22231b33c 100644 --- a/src/ci/docker/host-x86_64/dist-loongarch64-musl/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-loongarch64-musl/Dockerfile @@ -1,8 +1,14 @@ -FROM ubuntu:22.04 +FROM ubuntu:26.04 COPY scripts/cross-apt-packages.sh /scripts/ RUN sh /scripts/cross-apt-packages.sh +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + clang-22 \ + llvm-22 \ + lld-22 + COPY scripts/crosstool-ng-git.sh /scripts/ COPY scripts/crosstool-ng-sha256-20260705.diff /scripts/ RUN sh /scripts/crosstool-ng-git.sh @@ -18,13 +24,26 @@ RUN /scripts/crosstool-ng-build.sh COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh -ENV PATH=$PATH:/x-tools/loongarch64-unknown-linux-musl/bin - -ENV CC_loongarch64_unknown_linux_musl=loongarch64-unknown-linux-musl-gcc \ - AR_loongarch64_unknown_linux_musl=loongarch64-unknown-linux-musl-ar \ - CXX_loongarch64_unknown_linux_musl=loongarch64-unknown-linux-musl-g++ \ - CFLAGS_loongarch64_unknown_linux_musl="-mcmodel=medium" \ - CXXFLAGS_loongarch64_unknown_linux_musl="-mcmodel=medium" +ENV PATH=/usr/lib/llvm-22/bin:$PATH:/x-tools/loongarch64-unknown-linux-musl/bin + +# --no-rosegment keeps read-only code in the first LOAD segment, matching the +# segment layout produced by GNU ld. This allows Linux to make better use of +# file-backed PMD mappings and reduces iTLB misses. +ENV CLANG_FLAGS="--target=loongarch64-unknown-linux-musl -fuse-ld=lld \ + --gcc-toolchain=/x-tools/loongarch64-unknown-linux-musl \ + --sysroot=/x-tools/loongarch64-unknown-linux-musl/loongarch64-unknown-linux-musl/sysroot \ + -Wl,--no-rosegment" + +ENV CC=clang \ + CXX=clang++ \ + CC_loongarch64_unknown_linux_musl=clang \ + CXX_loongarch64_unknown_linux_musl=clang++ \ + CFLAGS_loongarch64_unknown_linux_musl="$CLANG_FLAGS -mcmodel=medium" \ + CXXFLAGS_loongarch64_unknown_linux_musl="$CLANG_FLAGS -mcmodel=medium" \ + AR_loongarch64_unknown_linux_musl=llvm-ar \ + RANLIB_loongarch64_unknown_linux_musl=llvm-ranlib \ + CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_MUSL_LINKER=clang \ + CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-Clink-arg=${CLANG_FLAGS// / -Clink-arg=}" ENV HOSTS=loongarch64-unknown-linux-musl @@ -34,6 +53,8 @@ ENV RUST_CONFIGURE_ARGS="--enable-extended \ --enable-sanitizers \ --disable-docs \ --set build.allocator=jemalloc \ + --set llvm.link-shared=true \ + --set llvm.thin-lto=true \ --set rust.lto=thin \ --set rust.codegen-units=1 \ --set target.loongarch64-unknown-linux-musl.crt-static=false \