From 840ff78d1680e26e11444de09dc4f4ba03234956 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:46:25 +0000 Subject: [PATCH 01/25] library: optimize truncate method --- library/alloc/src/collections/vec_deque/mod.rs | 4 ++-- library/alloc/src/string.rs | 11 ++++++----- library/alloc/src/vec/mod.rs | 17 ++++++++--------- library/alloc/src/wtf8/mod.rs | 11 ++++++----- library/std/src/ffi/os_str.rs | 13 +++++++------ 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index d91b35c1f6077..8f5928d3a0a28 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1449,8 +1449,8 @@ impl VecDeque { // Safe because: // // * Any slice passed to `drop_in_place` is valid; the second case has - // `len <= front.len()` and returning on `len > self.len()` ensures - // `begin <= back.len()` in the first case + // `len <= front.len()` and returning on `len >= self.len()` ensures + // `begin < back.len()` in the first case // * The head of the VecDeque is moved before calling `drop_in_place`, // so no value is dropped twice if `drop_in_place` panics unsafe { diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 1d9a4347a9616..37315d26fdf19 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -1457,8 +1457,8 @@ impl String { /// Shortens this `String` to the specified length. /// - /// If `new_len` is greater than or equal to the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// Note that this method has no effect on the allocated capacity /// of the string @@ -1480,10 +1480,11 @@ impl String { #[stable(feature = "rust1", since = "1.0.0")] #[track_caller] pub fn truncate(&mut self, new_len: usize) { - if new_len <= self.len() { - assert!(self.is_char_boundary(new_len)); - self.vec.truncate(new_len) + if new_len >= self.len() { + return; } + assert!(self.is_char_boundary(new_len)); + self.vec.truncate(new_len) } /// Removes the last character from the string buffer and returns it. diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index d2b7837d98fa6..3375b63f01510 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -1790,8 +1790,8 @@ impl Vec { /// assert_eq!(vec, [1, 2]); /// ``` /// - /// No truncation occurs when `len` is greater than the vector's current - /// length: + /// No truncation occurs when `len` is greater than or equal to the + /// vector's current length: /// /// ``` /// let mut vec = vec![1, 2, 3]; @@ -1817,16 +1817,13 @@ impl Vec { // This is safe because: // - // * the slice passed to `drop_in_place` is valid; the `len > self.len` + // * the slice passed to `drop_in_place` is valid; the `len >= self.len` // case avoids creating an invalid slice, and // * the `len` of the vector is shrunk before calling `drop_in_place`, // such that no value will be dropped twice in case `drop_in_place` // were to panic once (if it panics twice, the program aborts). unsafe { - // Note: It's intentional that this is `>` and not `>=`. - // Changing it to `>=` has negative performance - // implications in some cases. See #78884 for more. - if len > self.len { + if len >= self.len { return; } let remaining_len = self.len - len; @@ -3151,7 +3148,8 @@ impl Vec { /// calling the closure `f`. The return values from `f` will end up /// in the `Vec` in the order they have been generated. /// - /// If `new_len` is less than `len`, the `Vec` is simply truncated. + /// If `new_len` is less than or equal to `len`, the `Vec` is simply + /// truncated. /// /// This method uses a closure to create new values on every push. If /// you'd rather [`Clone`] a given value, use [`Vec::resize`]. If you @@ -3501,7 +3499,8 @@ impl Vec { /// /// If `new_len` is greater than `len`, the `Vec` is extended by the /// difference, with each additional slot filled with `value`. - /// If `new_len` is less than `len`, the `Vec` is simply truncated. + /// If `new_len` is less than or equal to `len`, the `Vec` is simply + /// truncated. /// /// This method requires `T` to implement [`Clone`], /// in order to be able to clone the passed value. diff --git a/library/alloc/src/wtf8/mod.rs b/library/alloc/src/wtf8/mod.rs index 394c41bf36727..4138770a5e9f1 100644 --- a/library/alloc/src/wtf8/mod.rs +++ b/library/alloc/src/wtf8/mod.rs @@ -342,18 +342,19 @@ impl Wtf8Buf { /// Shortens a string to the specified length. /// - /// If `new_len` is greater than the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// # Panics /// /// Panics if `new_len` does not lie on a code point boundary. #[inline] pub fn truncate(&mut self, new_len: usize) { - if new_len <= self.len() { - assert!(self.is_code_point_boundary(new_len)); - self.bytes.truncate(new_len) + if new_len >= self.len() { + return; } + assert!(self.is_code_point_boundary(new_len)); + self.bytes.truncate(new_len) } /// Consumes the WTF-8 string and tries to convert it to a vec of bytes. diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index be606ae69d6d9..c3ae2b9e2f522 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -576,8 +576,8 @@ impl OsString { /// Truncate the `OsString` to the specified length. /// - /// If `new_len` is greater than the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// # Panics /// @@ -586,11 +586,12 @@ impl OsString { #[inline] #[unstable(feature = "os_string_truncate", issue = "133262")] pub fn truncate(&mut self, len: usize) { - if len <= self.len() { - self.as_os_str().inner.check_public_boundary(len); - // SAFETY: The length was just checked to be at a valid boundary. - unsafe { self.inner.truncate_unchecked(len) }; + if len >= self.len() { + return; } + self.as_os_str().inner.check_public_boundary(len); + // SAFETY: The length was just checked to be at a valid boundary. + unsafe { self.inner.truncate_unchecked(len) }; } /// Provides plumbing to `Vec::extend_from_slice` without giving full From dfa6584d17920bc1b66a7c0fbb86ae922b74c903 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 18 Oct 2026 11:48:03 +0200 Subject: [PATCH 02/25] ping me for layout invariant changes --- triagebot.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/triagebot.toml b/triagebot.toml index fe827ebdb653e..b53bbdc323de8 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1103,6 +1103,10 @@ cc = ["@BoxyUwU"] message = "Some changes occurred in `rustc_ty_utils::consts.rs`" cc = ["@BoxyUwU"] +[mentions."compiler/rustc_ty_utils/src/layout/invariant.rs"] +message = "Layout invariants are being changed" +cc = ["@RalfJung"] + [mentions."compiler/rustc_trait_selection/src/solve/"] message = "Some changes occurred to the core trait solver" cc = ["@rust-lang/initiative-trait-system-refactor"] From 0fc7ab9a42ef759205f5ea55f1eb760753e18d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 19 Oct 2026 14:27:45 +0200 Subject: [PATCH 03/25] Update subtree sync docs --- compiler/rustc_codegen_gcc/doc/subtree.md | 55 +++-------------------- 1 file changed, 5 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_codegen_gcc/doc/subtree.md b/compiler/rustc_codegen_gcc/doc/subtree.md index fcac399e46542..00b5d377816fd 100644 --- a/compiler/rustc_codegen_gcc/doc/subtree.md +++ b/compiler/rustc_codegen_gcc/doc/subtree.md @@ -1,54 +1,9 @@ -# git subtree sync +# Subtree sync procedure -`rustc_codegen_gcc` is a subtree of the rust compiler. As such, it needs to be -synced from time to time to ensure changes that happened on their side are also -included on our side. +`rustc_codegen_gcc` is using [Josh] to perform subtree synces with the `rust-lang/rust` repository. -### How to install a forked git-subtree +[Josh]: https://josh-project.dev/docs/intro.html -Using git-subtree with `rustc` requires a patched git to make it work. -The PR that is needed is [here](https://github.com/gitgitgadget/git/pull/493). -Use the following instructions to install it: +The procedure for performing subtree synchronizations is documented [here][rdg-josh]. -```bash -git clone git@github.com:tqc/git.git -cd git -git checkout tqc/subtree -make -make install -cd contrib/subtree -make -cp git-subtree ~/bin -``` - -### Syncing with rust compiler - -Do a sync with this command: - -```bash -PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name -cd ../rustc_codegen_gcc -git checkout master -git pull -git checkout sync_branch_name -git merge master -``` - -To send the changes to the rust repo: - -```bash -cd ../rust -git pull origin master -git checkout -b subtree-update_cg_gcc_YYYY-MM-DD -PATH="$HOME/bin:$PATH" ~/bin/git-subtree pull --prefix=compiler/rustc_codegen_gcc/ https://github.com/rust-lang/rustc_codegen_gcc.git master -# Don't forget to update the `gcc` submodule to the same version as the -# one in `rustc_codegen_gcc/libgccjit.version`. -git push - -# Immediately merge the merge commit into cg_gcc to prevent merge conflicts when syncing from rust-lang/rust later. -PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name -``` - -FIXME: write a script that does the above. - -https://rust-lang.zulipchat.com/#narrow/stream/301329-t-devtools/topic/subtree.20madness/near/258877725 +[rdg-josh]: https://rustc-dev-guide.rust-lang.org/external-repos.html#synchronizing-a-josh-subtree From 4f3cf9712cf98c35509fad730eb87477a9de83cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 19 Oct 2026 16:54:01 +0200 Subject: [PATCH 04/25] Document updating the `gcc` submodule --- compiler/rustc_codegen_gcc/doc/subtree.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_codegen_gcc/doc/subtree.md b/compiler/rustc_codegen_gcc/doc/subtree.md index 00b5d377816fd..dbdc09499e30d 100644 --- a/compiler/rustc_codegen_gcc/doc/subtree.md +++ b/compiler/rustc_codegen_gcc/doc/subtree.md @@ -7,3 +7,7 @@ The procedure for performing subtree synchronizations is documented [here][rdg-josh]. [rdg-josh]: https://rustc-dev-guide.rust-lang.org/external-repos.html#synchronizing-a-josh-subtree + +## Pushing changes to `rust-lang/rust` + +If the used GCC commit (recorded in `libgccjit.version`) changed in `rustc_codegen_gcc`, then you will have to update the `gcc` submodule in the `rust-lang/rust` repository in the push sync PR. The CI in `rust-lang/rust` should ensure that these two commit references match. You can simply update the `gcc` submodule commit and push it to your push sync PR once it has been created. From 750d8d3f103e92118a1e19f2185de2b8a19212e8 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 20 Oct 2026 08:49:34 -0400 Subject: [PATCH 05/25] Remove /tmp workaround now that rustdoc fixed the disk usage issue --- compiler/rustc_codegen_gcc/.github/workflows/ci.yml | 2 -- compiler/rustc_codegen_gcc/.github/workflows/rustup.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml index 0687bc1150907..66362e27ce4da 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml @@ -116,8 +116,6 @@ jobs: - name: Set env run: | echo "workspace="$GITHUB_WORKSPACE >> $GITHUB_ENV - # /tmp is a small tmpfs, and rustdoc keeps every compiled doctest there until the run ends. - echo "TMPDIR=$RUNNER_TEMP" >> $GITHUB_ENV #- name: Cache rust repository ## We only clone the rust repository for rustc tests diff --git a/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml b/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml index 57d7a39de049a..0309ccbad5481 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml @@ -77,8 +77,6 @@ jobs: - name: Set env run: | echo "workspace="$GITHUB_WORKSPACE >> $GITHUB_ENV - # /tmp is a small tmpfs, and rustdoc keeps every compiled doctest there until the run ends. - echo "TMPDIR=$RUNNER_TEMP" >> $GITHUB_ENV - name: Prepare if: ${{ matrix.run_cargo_tests }} From 6e8e699e1cfb4797b6039f83a7aac0951ddb54c0 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 20 Oct 2026 18:00:25 -0400 Subject: [PATCH 06/25] Use correct alignment for global variables --- compiler/rustc_codegen_gcc/src/consts.rs | 20 +++++---- compiler/rustc_codegen_gcc/src/context.rs | 4 +- .../tests/asm/const_global_alignment.rs | 44 +++++++++++++++++++ 3 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 902f8ba52faaa..b5a4dcf87bbd6 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -64,14 +64,18 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { let cv = const_alloc_to_gcc(self, alloc); let align = alloc.inner().align; - if let Some(variable) = self.const_globals.borrow().get(&cv) { - if let Some(global_variable) = self.global_lvalues.borrow().get(variable) { - let alignment = align.bits() as i32; - if alignment > global_variable.get_alignment() { - global_variable.set_alignment(alignment); - } + if let Some(&mut (variable, ref mut global_align)) = + self.const_globals.borrow_mut().get_mut(&cv) + { + // `set_alignment` replaces the alignment, which is on the global's type and thus not + // reported by `get_alignment`: only raise it, or a smaller use would lower it. + if align > *global_align + && let Some(global_variable) = self.global_lvalues.borrow().get(&variable) + { + global_variable.set_alignment(align.bytes() as i32); + *global_align = align; } - return *variable; + return variable; } let global_value = self.static_addr_of_mut(cv, align, kind); #[cfg(feature = "master")] @@ -80,7 +84,7 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { .get(&global_value) .expect("`static_addr_of_mut` did not add the global to `self.global_lvalues`") .global_set_readonly(); - self.const_globals.borrow_mut().insert(cv, global_value); + self.const_globals.borrow_mut().insert(cv, (global_value, align)); global_value } diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index b57e0fb2cd23a..8eaa155ab8be1 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -126,8 +126,8 @@ pub struct CodegenCx<'gcc, 'tcx> { /// Local holding the return value of the defined functions returning it in memory. pub functions_with_indirect_return: RefCell, LValue<'gcc>>>, - /// Cache of emitted const globals (value -> global) - pub const_globals: RefCell, RValue<'gcc>>>, + /// Cache of emitted const globals (value -> global and its alignment) + pub const_globals: RefCell, (RValue<'gcc>, Align)>>, /// Map from the address of a global variable (rvalue) to the global variable itself (lvalue). /// FIXME(antoyo): remove when the rustc API is fixed. diff --git a/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs b/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs new file mode 100644 index 0000000000000..e4dfd4bb26dd0 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs @@ -0,0 +1,44 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu + +#![crate_type = "lib"] + +// Constants with identical bytes share one global, which needs the largest alignment among +// their uses: lowering it to a later, smaller one or reading it as bits fails these checks. + +// CHECK: "A_WIDE": +// CHECK-NEXT: .quad "[[WIDE_THEN_BYTES:global\.[0-9]+]]" +#[no_mangle] +pub static A_WIDE: &[u128; 4] = &[0x09090909_09090909_09090909_09090909; 4]; + +// CHECK: "B_BYTES": +// CHECK-NEXT: .quad "[[WIDE_THEN_BYTES]]" +#[no_mangle] +pub static B_BYTES: &[u8; 64] = &[9; 64]; + +// CHECK: "C_BYTES": +// CHECK-NEXT: .quad "[[BYTES_THEN_WIDE:global\.[0-9]+]]" +#[no_mangle] +pub static C_BYTES: &[u8; 64] = &[8; 64]; + +// CHECK: "D_WIDE": +// CHECK-NEXT: .quad "[[BYTES_THEN_WIDE]]" +#[no_mangle] +pub static D_WIDE: &[u128; 4] = &[0x08080808_08080808_08080808_08080808; 4]; + +// CHECK: "E_BYTES": +// CHECK-NEXT: .quad "[[BYTES_THEN_HALVES:global\.[0-9]+]]" +#[no_mangle] +pub static E_BYTES: &[u8; 4] = &[3; 4]; + +// CHECK: "F_HALVES": +// CHECK-NEXT: .quad "[[BYTES_THEN_HALVES]]" +#[no_mangle] +pub static F_HALVES: &[u16; 2] = &[0x0303; 2]; + +// CHECK: .section .rodata.[[WIDE_THEN_BYTES]],"a" +// CHECK-NEXT: .align {{16|32|64}}{{$}} +// CHECK: .section .rodata.[[BYTES_THEN_WIDE]],"a" +// CHECK-NEXT: .align 16{{$}} +// CHECK: .section .rodata.[[BYTES_THEN_HALVES]],"a" +// CHECK-NEXT: .align 2{{$}} From a4f5320342eccb1036cf3c38e006c550d4041e6f Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 20 Oct 2026 13:59:58 -0400 Subject: [PATCH 07/25] Remove integer packing workaround and replace by the new_array_from_bytes API to reduce RAM usage --- compiler/rustc_codegen_gcc/Cargo.lock | 8 +- compiler/rustc_codegen_gcc/Cargo.toml | 2 +- compiler/rustc_codegen_gcc/libgccjit.version | 2 +- compiler/rustc_codegen_gcc/src/common.rs | 79 ++++--------------- compiler/rustc_codegen_gcc/src/consts.rs | 16 +++- compiler/rustc_codegen_gcc/src/context.rs | 4 +- .../tests/compile/large_byte_constant.rs | 6 +- 7 files changed, 41 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_codegen_gcc/Cargo.lock b/compiler/rustc_codegen_gcc/Cargo.lock index d94ce6575ac0e..aec0ef270bf73 100644 --- a/compiler/rustc_codegen_gcc/Cargo.lock +++ b/compiler/rustc_codegen_gcc/Cargo.lock @@ -56,18 +56,18 @@ dependencies = [ [[package]] name = "gccjit" -version = "7.2.0" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27cd340f3c265988de3379689cbbd103c25b1f8a3d0cc73f91a49083e899d3e1" +checksum = "ea1820165dfb376a3bbfe8b69628d9e5a851c423e0480a666f71f560e2d0a924" dependencies = [ "gccjit_sys", ] [[package]] name = "gccjit_sys" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2d9d74572f004908f17ec1bd0af071e5db63a12c3335c9651a6125593951c6" +checksum = "a6091291fc118448b91df5b662d20564020198fee46d8445a6aa0c25b6b435be" dependencies = [ "libc", ] diff --git a/compiler/rustc_codegen_gcc/Cargo.toml b/compiler/rustc_codegen_gcc/Cargo.toml index 95c4c991297fa..3e279961bfbe0 100644 --- a/compiler/rustc_codegen_gcc/Cargo.toml +++ b/compiler/rustc_codegen_gcc/Cargo.toml @@ -20,7 +20,7 @@ default = ["master"] [dependencies] object = { version = "0.39.0", default-features = false, features = ["std", "read"] } tempfile = "3.20" -gccjit = { version = "7.2.0", features = ["dlopen"] } +gccjit = { version = "7.3.0", features = ["dlopen"] } #gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] } # Local copy. diff --git a/compiler/rustc_codegen_gcc/libgccjit.version b/compiler/rustc_codegen_gcc/libgccjit.version index 0bcc711e4696b..0c6f40148d1e5 100644 --- a/compiler/rustc_codegen_gcc/libgccjit.version +++ b/compiler/rustc_codegen_gcc/libgccjit.version @@ -1 +1 @@ -37ea7cd851f04ee6a313c5067c9fde9328f876aa +e1c6e07b095d091177376a163da44039475e05e3 diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 21d92c6cc2936..948d2df6b92a5 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -126,18 +126,6 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } } -/// The element type and element count of the array used to represent a run of `len` constant bytes. -/// -/// Larger integers are used where possible: this reduces the number of rvalues, which is a -/// significant memory saving on constant-heavy crates. -fn byte_run_shape<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> (Type<'gcc>, u64) { - match len % 8 { - 0 => (cx.context.new_type::(), len as u64 / 8), - 4 => (cx.context.new_type::(), len as u64 / 4), - _ => (cx.context.new_type::(), len as u64), - } -} - /// The type [`bytes_in_context`] gives a run of `len` constant bytes. /// /// Exposed separately so that the type of a constant allocation can be computed before any of its @@ -147,65 +135,30 @@ fn byte_run_shape<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> (Type<'gcc>, u6 /// Two equal-but-distinct array types would key [`CodegenCx::type_struct`] differently and so /// produce two distinct anonymous structs, and libgccjit compares struct types by identity. pub fn bytes_type_in_context<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> Type<'gcc> { - let (element_type, count) = byte_run_shape(cx, len); - if let Some(&typ) = cx.byte_array_types.borrow().get(&(element_type, count)) { + if let Some(&typ) = cx.byte_array_types.borrow().get(&len) { return typ; } - let typ = new_array_type(cx.context, None, element_type, count); - cx.byte_array_types.borrow_mut().insert((element_type, count), typ); + let typ = new_array_type(cx.context, None, cx.type_u8(), len as u64); + cx.byte_array_types.borrow_mut().insert(len, typ); typ } -// FIXME(FractalFir): Consider using `global_set_initializer` instead. Before this is done, we need to confirm that -// `global_set_initializer` is more memory efficient than the current solution. -// `global_set_initializer` calls `global_set_initializer_rvalue` under the hood - does it generate an array of rvalues, -// or is it using a more efficient representation? pub fn bytes_in_context<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, bytes: &[u8]) -> RValue<'gcc> { let typ = bytes_type_in_context(cx, bytes.len()); - let (element_type, _) = byte_run_shape(cx, bytes.len()); - let context = &cx.context; - // Since we are representing arbitrary byte runs as integers, we need to follow the target - // endianness. - let endian = cx.sess().target.options.endian; - let elements: Vec<_> = match bytes.len() % 8 { - 0 => { - let (arrays, remainder) = bytes.as_chunks::<8>(); - debug_assert!(remainder.is_empty()); - arrays - .iter() - .map(|&arr| { - context.new_rvalue_from_long( - element_type, - match endian { - rustc_abi::Endian::Little => u64::from_le_bytes(arr) as i64, - rustc_abi::Endian::Big => u64::from_be_bytes(arr) as i64, - }, - ) - }) - .collect() - } - 4 => { - let (arrays, remainder) = bytes.as_chunks::<4>(); - debug_assert!(remainder.is_empty()); - arrays - .iter() - .map(|&arr| { - context.new_rvalue_from_int( - element_type, - match endian { - rustc_abi::Endian::Little => u32::from_le_bytes(arr) as i32, - rustc_abi::Endian::Big => u32::from_be_bytes(arr) as i32, - }, - ) - }) - .collect() - } - _ => bytes + // Raw bytes take far less RAM in libgccjit than one rvalue per array element. + #[cfg(feature = "master")] + { + cx.context.new_array_from_bytes(None, typ, bytes) + } + #[cfg(not(feature = "master"))] + { + let byte_type = cx.type_u8(); + let elements: Vec<_> = bytes .iter() - .map(|&byte| context.new_rvalue_from_int(element_type, byte as i32)) - .collect(), - }; - context.new_array_constructor(None, typ, &elements) + .map(|&byte| cx.context.new_rvalue_from_int(byte_type, byte as i32)) + .collect(); + cx.context.new_array_constructor(None, typ, &elements) + } } pub fn type_is_pointer(typ: Type<'_>) -> bool { diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 902f8ba52faaa..752f04906a2c8 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -359,18 +359,30 @@ fn alloc_fields(cx: &CodegenCx<'_, '_>, alloc: &interpret::Allocation) -> Vec next_offset { - fields.push(AllocField::Bytes { range: next_offset..offset }); + push_byte_runs(&mut fields, next_offset..offset); } fields.push(AllocField::Pointer { offset, prov }); next_offset = offset + pointer_size; } if alloc.len() >= next_offset { - fields.push(AllocField::Bytes { range: next_offset..alloc.len() }); + push_byte_runs(&mut fields, next_offset..alloc.len()); } fields } +/// `gcc_jit_context_new_array_from_bytes` rejects runs of `i32::MAX` bytes or more. +const MAX_BYTE_RUN_LEN: usize = i32::MAX as usize - 1; + +fn push_byte_runs(fields: &mut Vec, range: Range) { + let mut start = range.start; + while range.end - start > MAX_BYTE_RUN_LEN { + fields.push(AllocField::Bytes { range: start..start + MAX_BYTE_RUN_LEN }); + start += MAX_BYTE_RUN_LEN; + } + fields.push(AllocField::Bytes { range: start..range.end }); +} + /// The type [`const_alloc_to_gcc`] gives `alloc`, computed without building any rvalue. /// /// This lets `predefine_static` declare a static's global with the type its initializer will have, diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index b57e0fb2cd23a..3cb12a4c2e56c 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -97,11 +97,11 @@ pub struct CodegenCx<'gcc, 'tcx> { /// Cache of the anonymous struct types. pub struct_types: RefCell, Type<'gcc>>>, - /// Cache of the array types used for runs of constant bytes, keyed by element type and count. + /// Cache of the array types used for runs of constant bytes, keyed by length. /// /// libgccjit mints a fresh type on every `new_array_type`, and struct types are keyed on their /// field types, so without this two equal byte runs would yield two distinct anonymous structs. - pub byte_array_types: RefCell, u64), Type<'gcc>>>, + pub byte_array_types: RefCell>>, /// Cache of the return types of the pairs returned in two registers, keyed by the types of the /// two scalars. diff --git a/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs b/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs index b26387ecacdb6..2ca3f4c2fec36 100644 --- a/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs +++ b/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs @@ -1,8 +1,8 @@ -//@ memory-limit: 2G +//@ memory-limit: 1G // Compiler: -// An 8 MiB constant must compile within 2 GiB of address space. A backend that builds one rvalue -// per byte of a constant needs more than 6 GiB for it and runs out of memory. +// An 8 MiB constant must compile within 1 GiB of address space. A backend that builds one rvalue +// per 8 bytes of a constant needs about 1.4 GiB for it and runs out of memory. #![crate_type = "lib"] From aea90b8d92e3d608e975bae4ee319c92590b06a8 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 22 Oct 2026 18:13:32 +0000 Subject: [PATCH 08/25] Update dependency jinja2 to v3.1.6 [SECURITY] --- .../host-x86_64/test-pr-check-1/reuse-requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt b/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt index 8784e18864b23..231e06ba0a00c 100644 --- a/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt +++ b/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt @@ -24,9 +24,9 @@ chardet==5.2.0 \ # via # binaryornot # python-debian -jinja2==3.1.4 \ - --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ - --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d +Jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 # via reuse license-expression==30.3.0 \ --hash=sha256:1295406f736b4f395ff069aec1cebfad53c0fcb3cf57df0f5ec58fc7b905aea5 \ From f65e06b0198e1034f77eeef17b5e1de592b79f64 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 23 Oct 2026 18:27:10 +0200 Subject: [PATCH 09/25] Fix sysroot path for ui tests --- compiler/rustc_codegen_gcc/build_system/src/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_gcc/build_system/src/build.rs b/compiler/rustc_codegen_gcc/build_system/src/build.rs index ac076836579ac..9a4e98d3bb24d 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/build.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/build.rs @@ -140,7 +140,7 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // Record the sysroot sources under the path the `rust-src` component uses, which is where // rustc looks for them to turn a sysroot span into `/rustc/$hash`. Without this, ui tests // print the build path where they expect `$SRC_DIR`. - let sysroot_source_dir = lib_path.join("rustlib/src/rust/library"); + let sysroot_source_dir = lib_path.join("rustlib/src/rust"); rustflags.push_str(&format!( " --remap-path-prefix={library_dir}={sysroot_source_dir}", library_dir = std::path::absolute(&library_dir) From 41be5c0ed3e3374e814e2fd959d43c1b6f15236e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 21 Oct 2026 15:41:55 +0200 Subject: [PATCH 10/25] Make const strings readonly and remove the pointer indirection --- compiler/rustc_codegen_gcc/src/common.rs | 10 ++++---- .../rustc_codegen_gcc/tests/asm/const_str.rs | 24 +++++++++++++++++++ .../tests/failing-ui-tests.txt | 2 -- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 compiler/rustc_codegen_gcc/tests/asm/const_str.rs diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 948d2df6b92a5..895bc629d63ce 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -28,12 +28,14 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.const_uint(self.type_u16(), i as u64) } - fn global_string(&self, string: &str) -> LValue<'gcc> { - // FIXME(antoyo): handle non-null-terminated strings. - let string = self.context.new_string_literal(string); + fn global_const_string(&self, string: &str) -> LValue<'gcc> { + // The bytes of a Rust string are not NUL-terminated and can contain NULs. + let string = bytes_in_context(self, string.as_bytes()); let sym = self.generate_local_symbol_name("str"); let global = self.declare_private_global(&sym, self.val_ty(string)); global.global_set_initializer_rvalue(string); + #[cfg(feature = "master")] + global.global_set_readonly(); global // FIXME(antoyo): set linkage. } @@ -245,7 +247,7 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { fn const_str(&self, s: &str) -> (RValue<'gcc>, RValue<'gcc>) { let mut const_str_cache = self.const_str_cache.borrow_mut(); let str_global = const_str_cache.get(s).copied().unwrap_or_else(|| { - let g = self.global_string(s); + let g = self.global_const_string(s); const_str_cache.insert(s.to_owned(), g); g }); diff --git a/compiler/rustc_codegen_gcc/tests/asm/const_str.rs b/compiler/rustc_codegen_gcc/tests/asm/const_str.rs new file mode 100644 index 0000000000000..65969a713c996 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/const_str.rs @@ -0,0 +1,24 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -Copt-level=2 + +#![crate_type = "lib"] +#![allow(invalid_value)] + +// The panic messages of the `assert_zero_valid` and `assert_inhabited` intrinsics are built with +// `const_str`, which passes the address of its global as the string data. The global must +// therefore hold the bytes themselves (not a pointer to a string literal), without a trailing +// NUL so that its size matches the length passed along, and it must be read-only. + +// CHECK-LABEL: "zeroed_ref": +// CHECK: mov esi, 57 +// CHECK: lea rdi, "[[REF_STR:str\.[0-9]+]]"[rip] +#[unsafe(no_mangle)] +pub fn zeroed_ref() -> &'static u8 { + unsafe { std::mem::zeroed() } +} + +// CHECK: .section .rodata.[[REF_STR]],"a" +// CHECK: .size "[[REF_STR]]", 57 +// CHECK-NEXT: "[[REF_STR]]": +// CHECK-NEXT: .ascii "attempted to zero-initialize type `&u8`, which is invalid" diff --git a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt index a18eaf272fd07..84a0bbd93f07f 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt @@ -1,6 +1,5 @@ tests/ui/asm/may_unwind.rs tests/ui/asm/x86_64/may_unwind.rs -tests/ui/intrinsics/panic-uninitialized-zeroed.rs tests/ui/simd/issue-17170.rs tests/ui/simd/issue-39720.rs tests/ui/process/println-with-broken-pipe.rs @@ -20,7 +19,6 @@ tests/ui/explicit-tail-calls/become-indirect-return.rs tests/ui/explicit-tail-calls/indirect.rs tests/ui/abi/rust-tail-cc.rs tests/ui/abi/rust-preserve-none-cc.rs -tests/ui/extern/extern-types-field-offset.rs tests/ui/scalable-vectors/debuginfo-does-not-spill.rs tests/ui/scalable-vectors/debuginfo-no-opt-llvm-ice.rs tests/ui/scalable-vectors/transmute.rs From 2452094904eae4e6928ad99c51e4b9ea664f3286 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 20 Oct 2026 12:10:56 -0400 Subject: [PATCH 11/25] Fix to use target features from a target spec --- compiler/rustc_codegen_gcc/src/attributes.rs | 17 +-- compiler/rustc_codegen_gcc/src/gcc_util.rs | 105 +++++++++++++++---- compiler/rustc_codegen_gcc/src/lib.rs | 7 +- 3 files changed, 93 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/attributes.rs b/compiler/rustc_codegen_gcc/src/attributes.rs index b6b0352d2516c..925c600aee936 100644 --- a/compiler/rustc_codegen_gcc/src/attributes.rs +++ b/compiler/rustc_codegen_gcc/src/attributes.rs @@ -20,7 +20,7 @@ use crate::base; use crate::context::CodegenCx; #[cfg(feature = "master")] use crate::gcc_util::to_gcc_aarch64_extension; -use crate::gcc_util::to_gcc_features; +use crate::gcc_util::{to_gcc_features, to_gcc_target_attribute_feature}; /// Checks if the function `instance` is recursively inline. /// Returns `false` if a functions is guaranteed to be non-recursive, and `true` if it *might* be recursive. @@ -192,20 +192,7 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } let target_features = function_features .iter() - .filter_map(|feature| { - // FIXME(antoyo): support soft-float. - if feature.contains("soft-float") { - return None; - } - - if feature.starts_with('-') { - Some(format!("no{}", feature)) - } else if let Some(stripped) = feature.strip_prefix('+') { - Some(stripped.to_string()) - } else { - Some(feature.to_string()) - } - }) + .filter_map(|feature| to_gcc_target_attribute_feature(feature)) .collect::>() .join(","); if !target_features.is_empty() { diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index 5ecc971830f20..b0442cdfb11cc 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -20,7 +20,10 @@ fn gcc_features_by_flags(sess: &EarlySession, features: &mut Vec) { /// The list of GCC features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`, /// `--target` and similar). -pub(crate) fn global_gcc_features(sess: &EarlySession) -> Vec { +/// +/// If `for_cfg` is `true`, only the features implied by `--target` are included, to configure the +/// target info that populates [`rustc_codegen_ssa::TargetConfig`]. +pub(crate) fn global_gcc_features(sess: &EarlySession, for_cfg: bool) -> Vec { // Features that come earlier are overridden by conflicting features later in the string. // Typically we'll want more explicit settings to override the implicit ones, so: // @@ -57,6 +60,12 @@ pub(crate) fn global_gcc_features(sess: &EarlySession) -> Vec { // Features implied by an implicit or explicit `--target`. target_features::target_spec_to_backend_features(sess, &mut extend_backend_features); + // `internal_target_features` applies -Ctarget-features itself, and GCC has no options for the + // features implied by other flags (e.g. retpoline), which would break the target info. + if for_cfg { + return features; + } + // -Ctarget-features target_features::flag_to_backend_features(sess, extend_backend_features); @@ -267,25 +276,7 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { context.add_command_line_option("-mavx"); } - if sess.target.arch == Arch::AArch64 { - // Unlike LLVM, GCC enables outline atomics by default, but their libgcc helpers are - // missing on targets without this feature, like the Linux kernel. Only AArch64 has it. - let outline_atomics = sess - .global_backend_features - .iter() - .rev() - .find_map(|feature| match feature.as_str() { - "outline-atomics" | "+outline-atomics" => Some(true), - "-outline-atomics" => Some(false), - _ => None, - }) - .unwrap_or(false); - context.add_command_line_option(if outline_atomics { - "-moutline-atomics" - } else { - "-mno-outline-atomics" - }); - } + add_feature_options(&context, &sess.target.arch, &sess.global_backend_features); for arg in &sess.opts.cg.llvm_args { context.add_command_line_option(arg); @@ -387,6 +378,80 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { context } +/// Add the options for the global `features` that are not applied with function attributes. +fn add_feature_options(context: &Context<'_>, arch: &Arch, features: &[String]) { + if *arch == Arch::AArch64 { + // Unlike LLVM, GCC enables outline atomics by default, but their libgcc helpers are + // missing on targets without this feature, like the Linux kernel. Only AArch64 has it. + let outline_atomics = + feature_setting(features, "outline-atomics") == FeatureSetting::Enabled; + context.add_command_line_option(if outline_atomics { + "-moutline-atomics" + } else { + "-mno-outline-atomics" + }); + + // Rust's `neon` covers both FP and SIMD, so without it (e.g. on softfloat targets like the + // Linux kernel) GCC must not touch those registers at all. + if feature_setting(features, "neon") == FeatureSetting::Disabled { + context.add_command_line_option("-mgeneral-regs-only"); + } + } +} + +/// Configure the target info `context` with the global `features`, like codegen does. +#[cfg(feature = "master")] +pub fn add_target_info_feature_options(context: &Context<'_>, arch: &Arch, features: &[String]) { + add_feature_options(context, arch, features); + // Codegen applies these as function attributes, which the target info does not see. Of the + // arches using these attributes, libgccjit only has target info for x86, not PowerPC. + if matches!(arch, Arch::X86 | Arch::X86_64) { + for feature in + features.iter().filter_map(|feature| to_gcc_target_attribute_feature(feature)) + { + context.add_command_line_option(format!("-m{}", feature)); + } + } +} + +/// The spelling of a global `feature` in a GCC `target` attribute, e.g. `no-sse` for `-sse`. +pub fn to_gcc_target_attribute_feature(feature: &str) -> Option { + // FIXME(antoyo): support soft-float. + if feature.contains("soft-float") { + return None; + } + + if feature.starts_with('-') { + Some(format!("no{}", feature)) + } else if let Some(stripped) = feature.strip_prefix('+') { + Some(stripped.to_string()) + } else { + Some(feature.to_string()) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum FeatureSetting { + Enabled, + Disabled, + /// No global feature mentions it. + Unspecified, +} + +/// The last setting of `feature` in `features`. +fn feature_setting(features: &[String], feature: &str) -> FeatureSetting { + features + .iter() + .rev() + .find_map(|global_feature| match global_feature.strip_prefix('-') { + Some(disabled) if disabled == feature => Some(FeatureSetting::Disabled), + Some(_) => None, + None => (global_feature.strip_prefix('+').unwrap_or(global_feature) == feature) + .then_some(FeatureSetting::Enabled), + }) + .unwrap_or(FeatureSetting::Unspecified) +} + pub fn add_pic_option<'gcc>(context: &Context<'gcc>, relocation_model: RelocModel) { match relocation_model { rustc_target::spec::RelocModel::Static => { diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 0e44966d9b85d..d1d46db68ea67 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -238,7 +238,7 @@ impl CodegenBackend for GccCodegenBackend { panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths); } } - let global_backend_features = gcc_util::global_gcc_features(sess); + let global_backend_features = gcc_util::global_gcc_features(sess, false); #[cfg(feature = "master")] { @@ -251,6 +251,11 @@ impl CodegenBackend for GccCodegenBackend { if target_cpu != "generic" { context.add_command_line_option(format!("-march={}", target_cpu)); } + gcc_util::add_target_info_feature_options( + &context, + &sess.target.arch, + &gcc_util::global_gcc_features(sess, true), + ); // libgccjit always passes -fPIC, which some targets reject (e.g. MIPS without -mabicalls). gcc_util::add_pic_option(&context, sess.relocation_model()); From e034607236cef544bef110f5f2dd7d7a85348f6a Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 25 Jul 2026 18:50:17 +0200 Subject: [PATCH 12/25] specialize str.escape_default().{to_string, collect::}() and Extend for String too --- library/alloc/src/lib.rs | 2 + library/alloc/src/string.rs | 178 ++++++++++++++++++++++++++- library/alloctests/benches/str.rs | 4 + library/alloctests/benches/string.rs | 12 ++ library/alloctests/tests/string.rs | 11 ++ library/core/src/str/iter.rs | 12 ++ 6 files changed, 213 insertions(+), 6 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 6c2d5d68d787c..aefe43aa13ef2 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -92,6 +92,7 @@ #![feature(allocator_ext)] #![feature(array_into_iter_constructors)] #![feature(ascii_char)] +#![feature(ascii_char_variants)] #![feature(async_fn_traits)] #![feature(async_iterator)] #![feature(borrowed_buf_init)] @@ -156,6 +157,7 @@ #![feature(pattern)] #![feature(pin_coerce_unsized_trait)] #![feature(ptr_alignment_type)] +#![feature(ptr_cast_array)] #![feature(ptr_metadata)] #![feature(random)] #![feature(raw_os_error_ty)] diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index bb8301e53d34f..95c661649373b 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -42,11 +42,15 @@ #![stable(feature = "rust1", since = "1.0.0")] +#[cfg(not(no_global_oom_handling))] +use core::ascii; use core::error::Error; use core::iter::FusedIterator; #[cfg(not(no_global_oom_handling))] use core::iter::from_fn; #[cfg(not(no_global_oom_handling))] +use core::mem::MaybeUninit; +#[cfg(not(no_global_oom_handling))] use core::num::Saturating; #[cfg(not(no_global_oom_handling))] use core::ops::Add; @@ -2448,12 +2452,35 @@ impl Clone for String { #[stable(feature = "rust1", since = "1.0.0")] impl FromIterator for String { fn from_iter>(iter: I) -> String { - let mut buf = String::new(); - buf.extend(iter); + FromCharIterSpec::to_string_spec(iter.into_iter()) + } +} + +#[cfg(not(no_global_oom_handling))] +trait FromCharIterSpec: Iterator { + fn to_string_spec(self) -> String; +} + +#[cfg(not(no_global_oom_handling))] +impl FromCharIterSpec for T +where + T: Iterator, +{ + default fn to_string_spec(self) -> String { + let mut buf = String::with_capacity(self.size_hint().0); + buf.extend(self); buf } } +#[cfg(not(no_global_oom_handling))] +impl FromCharIterSpec for core::str::EscapeDefault<'_> { + fn to_string_spec(self) -> String { + // go through the ToString specialization + self.to_string() + } +} + #[cfg(not(no_global_oom_handling))] #[stable(feature = "string_from_iter_by_ref", since = "1.17.0")] impl<'a> FromIterator<&'a char> for String { @@ -2549,10 +2576,7 @@ impl<'a> FromIterator<&'a core::ascii::Char> for String { #[stable(feature = "rust1", since = "1.0.0")] impl Extend for String { fn extend>(&mut self, iter: I) { - let iterator = iter.into_iter(); - let (lower_bound, _) = iterator.size_hint(); - self.reserve(lower_bound); - iterator.for_each(move |c| self.push(c)); + SpecExtendChars::spec_extend_into(iter, self); } #[inline] @@ -2566,6 +2590,28 @@ impl Extend for String { } } +#[cfg(not(no_global_oom_handling))] +trait SpecExtendChars { + fn spec_extend_into(self, s: &mut String); +} + +#[cfg(not(no_global_oom_handling))] +impl> SpecExtendChars for T { + default fn spec_extend_into(self, target: &mut String) { + let iterator = self.into_iter(); + let (lower_bound, _) = iterator.size_hint(); + target.reserve(lower_bound); + iterator.for_each(move |c| target.push(c)); + } +} + +#[cfg(not(no_global_oom_handling))] +impl SpecExtendChars for core::str::EscapeDefault<'_> { + fn spec_extend_into(self, target: &mut String) { + target.extend_from_escape_default(self); + } +} + #[cfg(not(no_global_oom_handling))] #[stable(feature = "extend_ref", since = "1.2.0")] impl<'a> Extend<&'a char> for String { @@ -3160,6 +3206,126 @@ impl SpecToString for fmt::Arguments<'_> { } } +#[cfg(not(no_global_oom_handling))] +impl SpecToString for core::str::EscapeDefault<'_> { + fn spec_to_string(&self) -> String { + let mut s = String::new(); + s.extend_from_escape_default(self.clone()); + s + } +} + +#[cfg(not(no_global_oom_handling))] +impl String { + #[inline] + fn extend_from_escape_default(&mut self, escaper: core::str::EscapeDefault<'_>) { + let (front, middle, tail) = escaper.into_parts(); + let lengths = front.as_ref().map_or_default(|f| f.size_hint().0) + + middle.as_ref().map_or_default(|m| m.size_hint().0) + + tail.as_ref().map_or_default(|t| t.size_hint().0); + self.reserve(lengths); + + if let Some(front) = front { + self.extend(front); + } + if let Some(middle) = middle { + /// Ensures a spare capacity of N bytes and then passes that as MaybeUninit + /// slice to a closure that returns the bytes written, then + /// updates the length. + /// + /// # Safety: + /// + /// The caller must initialize the number of bytes returned by the closure. + #[inline] + unsafe fn with_spare( + bytes: &mut Vec, + then: impl FnOnce(&mut [MaybeUninit; N]) -> usize, + ) { + const { + assert!(core::mem::size_of::() == 1); + } + bytes.reserve(N); + let old_len = bytes.len(); + // SAFETY: We reserved enough space for the requested number of bytes. + unsafe { + let spare: &mut [MaybeUninit; N] = bytes + .spare_capacity_mut() + .as_mut_ptr() + .cast::() + .cast_uninit() + .cast_array::() + .as_mut_unchecked(); + let written = then(spare); + bytes.set_len(old_len + written); + } + } + + for c in middle { + match c { + '\'' | '\"' | '\\' => { + self.push('\\'); + self.push(c); + } + '\x20'..='\x7e' => self.push(c), + '\t' => { + self.push('\\'); + self.push('t'); + } + '\r' => { + self.push('\\'); + self.push('r'); + } + '\n' => { + self.push('\\'); + self.push('n'); + } + c => { + // SAFETY: We only let the function write ascii chars, so it remains valid utf8. + // and it returns the number of bytes written, so so with_spare ensures the rest for us. + unsafe { + with_spare::<10, ascii::Char>(self.as_mut_vec(), |spare| { + escape_unicode_into(spare, c) + }); + } + } + } + } + } + + if let Some(tail) = tail { + self.extend(tail); + } + } +} + +#[cfg(not(no_global_oom_handling))] +#[inline] +// adapted from core::escape::escape_unicode, but it's left-aligned instead of right-aligned. +fn escape_unicode_into(output: &mut [MaybeUninit; 10], c: char) -> usize { + const HEX_DIGITS: [ascii::Char; 16] = *b"0123456789abcdef".as_ascii().unwrap(); + + let c = c as u32; + // OR-ing `1` ensures that for `c == 0` the code computes that + // one digit should be printed. + let prefix_skip = (c | 1).leading_zeros() as usize / 4 - 2; + + // Always write all digits, but for shorter representations the unused + // digits get overwritten by later writes. + output[3usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 20) & 15) as usize]); + output[4usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 16) & 15) as usize]); + output[5usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 12) & 15) as usize]); + output[6usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 8) & 15) as usize]); + output[7usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 4) & 15) as usize]); + output[8usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[(c & 15) as usize]); + + output[0].write(ascii::Char::ReverseSolidus); + output[1].write(ascii::Char::SmallU); + output[2].write(ascii::Char::LeftCurlyBracket); + output[9 - prefix_skip].write(ascii::Char::RightCurlyBracket); + + 10 - prefix_skip +} + #[stable(feature = "rust1", since = "1.0.0")] impl AsRef for String { #[inline] diff --git a/library/alloctests/benches/str.rs b/library/alloctests/benches/str.rs index 98c7c5413caef..68bdfbed8c8b5 100644 --- a/library/alloctests/benches/str.rs +++ b/library/alloctests/benches/str.rs @@ -1,5 +1,9 @@ use test::{Bencher, black_box}; +#[allow(unused)] // this is a symlink to core benches, we don't use everything here +#[path = "../../coretests/benches/str/corpora.rs"] +pub(super) mod corpora; + #[bench] fn char_iterator(b: &mut Bencher) { let s = "ศไทย中华Việt Nam; Mary had a little lamb, Little lamb"; diff --git a/library/alloctests/benches/string.rs b/library/alloctests/benches/string.rs index 0bbec12e4fdc6..75caf722d723d 100644 --- a/library/alloctests/benches/string.rs +++ b/library/alloctests/benches/string.rs @@ -159,3 +159,15 @@ fn bench_insert_str_long(b: &mut Bencher) { x }) } + +#[bench] +fn bench_from_escape_default_ascii(b: &mut Bencher) { + let s = super::str::corpora::en::LARGE; + b.iter(|| black_box(s).escape_default().to_string()) +} + +#[bench] +fn bench_from_escape_default_multibyte(b: &mut Bencher) { + let s = super::str::corpora::emoji::LARGE; + b.iter(|| black_box(s).escape_default().to_string()) +} diff --git a/library/alloctests/tests/string.rs b/library/alloctests/tests/string.rs index 08eb1855a4824..9c632b4e3a2f9 100644 --- a/library/alloctests/tests/string.rs +++ b/library/alloctests/tests/string.rs @@ -956,3 +956,14 @@ fn test_str_concat() { let s: String = format!("{a}{b}"); assert_eq!(s.as_bytes()[9], 'd' as u8); } + +// alloc has a specialization for EscapeDefault's ToString impl, which isn't covered by core tests. +#[test] +fn test_from_escaped_str() { + assert_eq!("abcABC012".escape_default().to_string(), "abcABC012"); + assert_eq!("\t\n\"".escape_default().to_string(), r#"\t\n\""#); + assert_eq!("\u{10FFFF}".escape_default().to_string(), "\\u{10ffff}"); + assert_eq!("\u{0}".escape_default().to_string(), "\\u{0}"); + assert_eq!("\u{1}".escape_default().to_string(), "\\u{1}"); + assert_eq!("\u{100}".escape_default().to_string(), "\\u{100}"); +} diff --git a/library/core/src/str/iter.rs b/library/core/src/str/iter.rs index 60b9e81aaeff9..657d894d38878 100644 --- a/library/core/src/str/iter.rs +++ b/library/core/src/str/iter.rs @@ -1589,6 +1589,18 @@ pub struct EscapeDefault<'a> { pub(super) inner: FlatMap, char_mod::EscapeDefault, CharEscapeDefault>, } +impl<'a> EscapeDefault<'a> { + /// Disassembles the iterator into its internal state. + /// Used by alloc for an optimized to_string impl. + #[unstable(feature = "std_internals", issue = "none")] + pub fn into_parts( + self, + ) -> (Option>, Option>, Option>) + { + self.inner.into_parts() + } +} + /// The return type of [`str::escape_unicode`]. #[stable(feature = "str_escape", since = "1.34.0")] #[derive(Clone, Debug)] From 195573d6fa578eec2d8cc05b6060e3b7eff1547f Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 25 Jul 2026 18:51:08 +0200 Subject: [PATCH 13/25] partially revert "Optimize escape_string_symbol()" This reverts commit bfe8ccb68d88b5cc2007d8cfbc3f365abfae4fd0. --- compiler/rustc_ast/src/util/literal.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index b5d8fc821ae0e..2b264a6843837 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -1,6 +1,5 @@ //! Code related to parsing literals. -use std::fmt::Write as _; use std::{ascii, fmt, str}; use rustc_literal_escaper::{ @@ -38,19 +37,8 @@ pub fn escape_string_symbol(symbol: Symbol) -> Symbol { let mut escaped = String::with_capacity(s.len() + 1); escaped.push_str(prefix); - // Don't use escape_default() here, because using it is slower than escaping manually. - for c in suffix.chars() { - match c { - '\t' => escaped.push_str("\\t"), - '\r' => escaped.push_str("\\r"), - '\n' => escaped.push_str("\\n"), - '\\' => escaped.push_str("\\\\"), - '\'' => escaped.push_str("\\'"), - '\"' => escaped.push_str("\\\""), - '\x20'..='\x7e' => escaped.push(c), - c => write!(escaped, "\\u{{{:x}}}", c as u32).unwrap(), - } - } + escaped.extend(suffix.escape_default()); + Symbol::intern(&escaped) } From ab2464ae738e1e97211f0e161b868fd6eeb920eb Mon Sep 17 00:00:00 2001 From: Yash Aggarwal <68061195+yashaggarwal85d@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:56:28 +0000 Subject: [PATCH 14/25] Add regression test for closure input inference through fn pointer args --- .../closure-input-in-fn-ptr-arg-162109.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/ui/coercion/fudge-inference/closure-input-in-fn-ptr-arg-162109.rs diff --git a/tests/ui/coercion/fudge-inference/closure-input-in-fn-ptr-arg-162109.rs b/tests/ui/coercion/fudge-inference/closure-input-in-fn-ptr-arg-162109.rs new file mode 100644 index 0000000000000..3a7faeb4ac4e0 --- /dev/null +++ b/tests/ui/coercion/fudge-inference/closure-input-in-fn-ptr-arg-162109.rs @@ -0,0 +1,18 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +// A regression test for https://github.com/rust-lang/rust/issues/162109. + +struct Foo(I, fn(I) -> O); + +impl Foo { + fn new(_: I, _: fn(I) -> O) -> Self { + loop {} + } +} + +fn main() { + let _: Foo<_, u8> = Foo::new(&1u8, |input| *input); +} From cbf6e4a2b8441e17aa6c74aa2918a5be332dd7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 24 Oct 2026 16:45:30 +0200 Subject: [PATCH 15/25] Do not checkout Cargo for bootstrap tests --- src/bootstrap/src/core/build_steps/test.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index f329f61c100c8..563c38f52fb7a 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -4155,9 +4155,6 @@ impl CommandLineStep for Bootstrap { let build_compiler = builder.compiler(0, host); let record_failed_tests = builder.ensure(SetupFailedTestsFile); - // Some tests require cargo submodule to be present. - builder.sess.require_submodule("src/tools/cargo", None); - let mut cargo = tool::prepare_tool_cargo( builder, build_compiler, From 59a2d735f5aba67ab7f0010d2f9355b0a3fe0cc7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 23 Oct 2026 21:32:28 +0200 Subject: [PATCH 16/25] Fix path setup for panic messages --- .../build_system/src/build.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_codegen_gcc/build_system/src/build.rs b/compiler/rustc_codegen_gcc/build_system/src/build.rs index 9a4e98d3bb24d..cc87c660520d8 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/build.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/build.rs @@ -6,7 +6,8 @@ use std::path::{Path, PathBuf}; use crate::config::{Channel, ConfigInfo}; use crate::utils::{ - create_dir, get_sysroot_dir, run_command, run_command_with_output_and_env, walk_dir, + create_dir, get_sysroot_dir, run_command, run_command_with_output_and_env, rustc_version_info, + walk_dir, }; #[derive(Default)] @@ -137,17 +138,16 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // Builds libs let mut rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); - // Record the sysroot sources under the path the `rust-src` component uses, which is where - // rustc looks for them to turn a sysroot span into `/rustc/$hash`. Without this, ui tests - // print the build path where they expect `$SRC_DIR`. - let sysroot_source_dir = lib_path.join("rustlib/src/rust"); + // Remap both source spans and embedded panic locations to the standard virtual path. + let sysroot_source_dir = start_dir.join("sysroot_src"); + let rustc_commit = rustc_version_info(env.get("RUSTC").map(String::as_str))? + .commit_hash + .ok_or_else(|| "Couldn't retrieve rustc commit hash".to_string())?; + // Cargo passes workspace-relative filenames, including those used by panic locations. + // Also: the `==` is intentional! The first `=` is "FROM" and the second `=` is for "TO". + rustflags.push_str(&format!(" --remap-path-prefix==/rustc/{rustc_commit}/library")); rustflags.push_str(&format!( - " --remap-path-prefix={library_dir}={sysroot_source_dir}", - library_dir = std::path::absolute(&library_dir) - .map_err(|error| format!( - "Failed to get the absolute path of the sysroot sources: {error:?}" - ))? - .display(), + " --remap-path-prefix={sysroot_source_dir}=/rustc/{rustc_commit}", sysroot_source_dir = std::path::absolute(&sysroot_source_dir) .map_err(|error| format!( "Failed to get the absolute path of the sysroot sources: {error:?}" From bd872a031ecd117e1d1385e0678eb0d2c71e3446 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 24 Oct 2026 17:08:16 +0200 Subject: [PATCH 17/25] Remove non-failing ui test from the failing ui tests list --- compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt index 84a0bbd93f07f..e35d9916247f1 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt @@ -11,7 +11,6 @@ tests/ui/process/nofile-limit.rs tests/ui/attributes/fn-align-dyn.rs tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs tests/ui/statics/const_generics.rs -tests/ui/thir-print/offset_of.rs tests/ui/asm/x86_64/global_asm_escape.rs tests/ui/lto/all-crates.rs tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs From e02f4fd1871478349435d3279ba54caccd8f069f Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Sat, 24 Oct 2026 11:12:54 -0400 Subject: [PATCH 18/25] Prepare for merging from rust-lang/rust This updates the rust-toolchain.toml file to nightly-2026-10-24 (48f4b05538b8eca412137042d9647a61e692c6b3). --- compiler/rustc_codegen_gcc/rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_gcc/rust-toolchain.toml b/compiler/rustc_codegen_gcc/rust-toolchain.toml index 7d0a982195aa0..5750754596ba2 100644 --- a/compiler/rustc_codegen_gcc/rust-toolchain.toml +++ b/compiler/rustc_codegen_gcc/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-10-19" +channel = "nightly-2026-10-24" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] From ed29f42d74c9029442e6e6611b1c194a68cfc3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 24 Oct 2026 19:07:29 +0200 Subject: [PATCH 19/25] Fix skipping library build with `--keep-stage[-std]` --- src/bootstrap/src/core/builder/cargo.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 98cfd58198a6c..27de3ec60b96b 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -1026,7 +1026,19 @@ impl Builder<'_> { // // Only clear out the directory if we're running Cargo on std; otherwise, we // should let Cargo take care of things for us (via depdep info) - if !self.config.dry_run() && mode == Mode::Std { + // + // When --keep-stage[-std] is passed, two things can happen: + // - Either this function is not invoked on `std` at all, because the corresponding Std step + // checks the flag and doesn't invoke Cargo for std at all. + // - This function is invoked, in which case it will (most likely) do a no-op stdlib + // rebuild. + // + // In the second situation, we must ensure that we will not clear the standard library + // output directory, otherwise --keep-stage[-std] will of course break. + // Note that the compiler stage corresponds to the standard library stage. + let keep_previously_built_std = self.config.keep_stage.contains(&compiler.stage) + || self.config.keep_stage_std.contains(&compiler.stage); + if !self.config.dry_run() && mode == Mode::Std && !keep_previously_built_std { build_stamp::clear_if_dirty(self, &out_dir, &self.rustc(compiler)); } From cdcaab758eea35ec838bf513bd1a0147764c396b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 24 Oct 2026 19:52:44 +0200 Subject: [PATCH 20/25] Update gcc submodule --- src/gcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gcc b/src/gcc index 37ea7cd851f04..e1c6e07b095d0 160000 --- a/src/gcc +++ b/src/gcc @@ -1 +1 @@ -Subproject commit 37ea7cd851f04ee6a313c5067c9fde9328f876aa +Subproject commit e1c6e07b095d091177376a163da44039475e05e3 From bb4f948aa911bf400bc201a8a63817efa7ae72b1 Mon Sep 17 00:00:00 2001 From: Hanna Kruppe Date: Sat, 24 Oct 2026 19:08:53 +0200 Subject: [PATCH 21/25] cg_llvm: clarify Send / Sync story for modules * Remove unnecessary and unsound Sync impl for ModuleLlvm * Explain why the Send impl is fine * turn a OnceLock into OnceCell to avoid giving the impression Sync might be needed here --- .../rustc_codegen_llvm/src/back/llvm_backend.rs | 16 +++++++++------- compiler/rustc_codegen_ssa/src/traits/write.rs | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index 385b785233a75..c2fd8a79407f0 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -2,12 +2,12 @@ //! and related traits for the LLVM codegen backend. use std::any::Any; +use std::cell::OnceCell; use std::ffi::CStr; use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::path::PathBuf; use std::ptr; -use std::sync::OnceLock; use rustc_ast::expand::allocator::AllocatorMethod; use rustc_codegen_ssa::back::lto::ThinModule; @@ -451,13 +451,15 @@ pub struct ModuleLlvm { /// Saved ThinLTO buffer for embedding in the object file. /// - /// This is currently a `OnceLock` to avoid aliasing problems when writing to it + /// This is currently a `OnceCell` to avoid aliasing problems when writing to it /// from code that also wants to pass around a shared ref to `ModuleCodegen`. - pub(crate) thin_lto_buffer: OnceLock>, + pub(crate) thin_lto_buffer: OnceCell>, } +// An LLVM context can only be used by one thread at a time. But we can move the entire context and +// all objects inside it to another thread (needed for parallel codegen). This is sound because +// `ModuleLlvm` owns the context and only allows borrowing LLVM objects tied to the context. unsafe impl Send for ModuleLlvm {} -unsafe impl Sync for ModuleLlvm {} /// Shared helper to call [`llvm::LLVMContextCreate`] and then configure /// [`llvm::LLVMContextSetDiscardValueNames`]. @@ -478,7 +480,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(create_target_machine(tcx, mod_name)), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } @@ -491,7 +493,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(create_informational_target_machine(tcx.sess)), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } @@ -512,7 +514,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(tm), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs index 4c0df97a41985..36bc0a68d882e 100644 --- a/compiler/rustc_codegen_ssa/src/traits/write.rs +++ b/compiler/rustc_codegen_ssa/src/traits/write.rs @@ -14,7 +14,7 @@ use crate::back::write::{ use crate::{CompiledModule, ModuleCodegen}; pub trait WriteBackendMethods: Clone + 'static { - type Module: Send + Sync; + type Module: Send; type TargetMachine; type ModuleBuffer: ModuleBufferMethods; type ThinData: Send + Sync; From 127a5d989f4bd10e31f40b421e501e56413fea4c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 19 Aug 2026 17:13:54 +0200 Subject: [PATCH 22/25] rustdoc: Correctly handle reexports when `--document-private-items` is used --- src/librustdoc/passes/strip_priv_imports.rs | 9 +++++++-- src/librustdoc/passes/strip_private.rs | 10 +++++++--- src/librustdoc/passes/stripper.rs | 2 ++ src/librustdoc/visit_ast.rs | 12 ++++++++++-- .../glob-private-document-private-items.rs | 16 ++++++++-------- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs index 9a432c3bad343..7c68f0091342f 100644 --- a/src/librustdoc/passes/strip_priv_imports.rs +++ b/src/librustdoc/passes/strip_priv_imports.rs @@ -12,6 +12,11 @@ pub(super) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) - } let is_json_output = cx.is_json_output(); - ImportStripper { tcx: cx.tcx, is_json_output, document_hidden: cx.document_hidden() } - .fold_crate(krate) + ImportStripper { + tcx: cx.tcx, + is_json_output, + document_hidden: cx.document_hidden(), + document_private: cx.document_private(), + } + .fold_crate(krate) } diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs index 54c8e9daedd17..e99e488ea227c 100644 --- a/src/librustdoc/passes/strip_private.rs +++ b/src/librustdoc/passes/strip_private.rs @@ -25,9 +25,13 @@ pub(super) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> is_json_output, tcx: cx.tcx, }; - krate = - ImportStripper { tcx: cx.tcx, is_json_output, document_hidden: cx.document_hidden() } - .fold_crate(stripper.fold_crate(krate)); + krate = ImportStripper { + tcx: cx.tcx, + is_json_output, + document_hidden: cx.document_hidden(), + document_private: cx.document_private(), + } + .fold_crate(stripper.fold_crate(krate)); } // strip all impls referencing private items diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 4c6d62917ab24..45bfb519cf4f5 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -268,6 +268,7 @@ pub(crate) struct ImportStripper<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, pub(crate) is_json_output: bool, pub(crate) document_hidden: bool, + pub(crate) document_private: bool, } impl ImportStripper<'_> { @@ -290,6 +291,7 @@ impl DocFolder for ImportStripper<'_> { debug!("ImportStripper: stripping {:?}", i.name); None } + clean::ImportItem(imp) if self.document_private => Some(self.fold_item_recur(i)), // clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None, clean::ExternCrateItem { .. } | clean::ImportItem(..) if i.visibility(self.tcx) != Some(Visibility::Public) => diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index df55626ffb05d..33b0334e3fd5d 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -490,7 +490,15 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let tcx = self.cx.tcx; let def_id = item.owner_id.to_def_id(); - let is_pub = tcx.visibility(def_id).is_public(); + // Explanations: if the item is public, nothing more to check. If not, then we check if + // we're documenting private item. If we are, then we want all items, except if it's a + // `use`. In this case, we want to check if it's a reexport. + let is_pub = tcx.visibility(def_id).is_public() + || (self.cx.document_private() + && (!matches!(item.kind, hir::ItemKind::Use(..)) + || def_id.as_local().is_some_and(|def_id| { + tcx.visibility(def_id).is_accessible_from(CRATE_DEF_ID, tcx) + }))); if is_pub { self.store_path(item.owner_id.to_def_id()); @@ -592,7 +600,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // If there was a private module in the current path then don't bother inlining // anything as it will probably be stripped anyway. - if is_pub && self.inside_public_path { + if is_pub && (self.inside_public_path || self.cx.document_private()) { let please_inline = if let Some(res_did) = res.opt_def_id() && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG)) { diff --git a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs index 4ad36da843f80..601eb711ae33d 100644 --- a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs +++ b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs @@ -17,14 +17,14 @@ pub use mod1::*; //@ has foo/index.html //@ hasraw - "mod1" //@ hasraw - "Mod1Public" -//@ !hasraw - "Mod1Private" -//@ !hasraw - "mod2" +//@ hasraw - "Mod1Private" +//@ hasraw - "mod2" //@ hasraw - "Mod2Public" -//@ !hasraw - "Mod2Private" +//@ hasraw - "Mod2Private" //@ has foo/struct.Mod1Public.html -//@ !has foo/struct.Mod1Private.html +//@ has foo/struct.Mod1Private.html //@ has foo/struct.Mod2Public.html -//@ !has foo/struct.Mod2Private.html +//@ has foo/struct.Mod2Private.html //@ has foo/mod1/index.html //@ hasraw - "mod2" @@ -43,6 +43,6 @@ pub use mod1::*; //@ has foo/mod1/mod2/struct.Mod2Public.html //@ has foo/mod1/mod2/struct.Mod2Private.html -//@ !has foo/mod2/index.html -//@ !has foo/mod2/struct.Mod2Public.html -//@ !has foo/mod2/struct.Mod2Private.html +//@ has foo/mod2/index.html +//@ has foo/mod2/struct.Mod2Public.html +//@ has foo/mod2/struct.Mod2Private.html From f87a2daa727df902a0ec452763076e921e92b370 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 19 Aug 2026 17:15:35 +0200 Subject: [PATCH 23/25] Add rustdoc-html regression test for reexports when `--document-private-items` is used --- .../document-private-reexports.rs | 52 +++++++++++++++++++ .../glob-private-document-private-items.rs | 8 +-- 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 tests/rustdoc-html/inline_local/document-private-reexports.rs diff --git a/tests/rustdoc-html/inline_local/document-private-reexports.rs b/tests/rustdoc-html/inline_local/document-private-reexports.rs new file mode 100644 index 0000000000000..c0953e7678716 --- /dev/null +++ b/tests/rustdoc-html/inline_local/document-private-reexports.rs @@ -0,0 +1,52 @@ +// Test ensures that when we use the `--document-private-items` option, reexports +// work the same as for "public" items. +// Regression test for . + +//@ compile-flags: --document-private-items + +#![crate_name = "foo"] + +//@ has 'foo/index.html' +// The four reexports, the `not_hidden` and `foofoo` (private) modules. +//@ count - '//*[@class="item-table"]/dt' 6 +//@ has - '//*[@class="item-table"]/dt/a[@href="not_hidden/index.html"]' 'not_hidden' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Top.html"]' 'Top' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.NotHidden.html"]' 'NotHidden' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Hidden2.html"]' 'Hidden2' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.X.html"]' 'X' +//@ has - '//*[@class="item-table"]/dt/a[@href="foofoo/index.html"]' 'foofoo' + +#[doc(hidden)] +pub(crate) struct TopHidden; + +#[doc(inline)] +pub(crate) use TopHidden as Top; + +#[doc(hidden)] +mod hidden { + pub(crate) struct NotHidden; +} + +pub(crate) use self::hidden::NotHidden; +// Even though there is no `pub` of any kind, we display it because we're at the +// crate root. +use self::hidden::NotHidden as X; + +mod not_hidden { + #[doc(hidden)] + pub(crate) struct Hidden; +} + +#[doc(inline)] +pub(crate) use self::not_hidden::Hidden as Hidden2; + +//@ has 'foo/foofoo/index.html' +//@ count - '//*[@class="item-table"]/dt' 1 +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Something.html"]' 'Something' +mod foofoo { + // Since there is no `pub` of any kind, we don't display it, even if there is `doc(inline)`. + #[doc(inline)] + use crate::not_hidden::Hidden; + #[doc(inline)] + pub(crate) use crate::not_hidden::Hidden as Something; +} diff --git a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs index 601eb711ae33d..a85ed41fa5ddf 100644 --- a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs +++ b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs @@ -30,12 +30,12 @@ pub use mod1::*; //@ hasraw - "mod2" //@ hasraw - "Mod1Public" //@ hasraw - "Mod1Private" -//@ !hasraw - "Mod2Public" -//@ !hasraw - "Mod2Private" +//@ hasraw - "Mod2Public" +//@ hasraw - "Mod2Private" //@ has foo/mod1/struct.Mod1Public.html //@ has foo/mod1/struct.Mod1Private.html -//@ !has foo/mod1/struct.Mod2Public.html -//@ !has foo/mod1/struct.Mod2Private.html +//@ has foo/mod1/struct.Mod2Public.html +//@ has foo/mod1/struct.Mod2Private.html //@ has foo/mod1/mod2/index.html //@ hasraw - "Mod2Public" From 072ded864312c7b052946ea2d8720b6db111f4f9 Mon Sep 17 00:00:00 2001 From: Shun Sakai Date: Sun, 25 Oct 2026 09:25:21 +0900 Subject: [PATCH 24/25] Add FIXME comments to `from_ascii_bytes_radix_impl` --- library/core/src/num/nonzero.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index c97e6f4c45395..d296d0ceb12b3 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1506,10 +1506,12 @@ macro_rules! nonzero_integer { #[inline] const fn from_ascii_bytes_radix_impl(src: &[u8], radix: u32) -> Result { + // FIXME(const-hack): This should use `?` again, once it's `const` let n = match <$Int>::from_ascii_bytes_radix_impl(src, radix) { Ok(n) => n, Err(err) => return Err(err), }; + // FIXME(const-hack): This should use `Option::ok_or` again, once it's `const` if let Some(n) = Self::new(n) { Ok(n) } else { From dfa3e66f7344e39da3bd6c9c58acb14ebd286db1 Mon Sep 17 00:00:00 2001 From: Samuel Thibault Date: Sun, 25 Oct 2026 04:50:40 +0000 Subject: [PATCH 25/25] Test(lib/sync): Fix `test_rwlock_max_readers` for 32bit architectures * Test(lib/sync): Fix `test_rwlock_max_readers` for 32bit architectures 962e9e2aab0d ("Fix `test_rwlock_max_readers` for x86 Win7") detected the issue but fixed it only for windows. As mentioned in the comment above the test, with the queue implementation, the counter for the number of threads has 4 bits reserved for management. On 64bit platforms, there are still ample room for u32::MAX readers, but on 32b platforms, only 2^28-1 readers can fit. We can still test u32::MAX readers on 64b platforms, thus keeping the min with usize::MAX >> 4. Fixes 83a7a4ad74ad ("Clarified docs in std::sync::RwLock and added a test that checks if we can reach max reader") Signed-off-by: Samuel Thibault * reformat --- library/std/tests/sync/rwlock.rs | 57 ++++++++++++++++---------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/library/std/tests/sync/rwlock.rs b/library/std/tests/sync/rwlock.rs index 311866dab4733..9715411a9a692 100644 --- a/library/std/tests/sync/rwlock.rs +++ b/library/std/tests/sync/rwlock.rs @@ -905,35 +905,36 @@ fn test_rwlock_max_readers() { let mut read_lock_ctr: u32 = 0; let rwlock: RwLock = RwLock::new(0); - const MAX_READERS: u32 = - cfg_select! { - miri => 100, - any( - all(target_os = "windows", not(target_vendor = "win7")), - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "openbsd", - target_os = "dragonfly", - target_os = "fuchsia", - all(target_family = "wasm", target_feature = "atomics"), - target_os = "hermit", - target_os = "motor", - ) => (1 << 30) - 2, - any( - target_family = "unix", - all(target_os = "windows", target_vendor = "win7", target_pointer_width = "64"), - all(target_vendor = "fortanix", target_env = "sgx"), - target_os = "xous", - target_os = "teeos", - ) => u32::MAX, - // Otherwise a form of deadlock is observed. - all(target_os = "windows", target_vendor = "win7", target_pointer_width = "32") => { - (1 << 28) - 1 + const MAX_READERS: u32 = cfg_select! { + miri => 100, + any( + all(target_os = "windows", not(target_vendor = "win7")), + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "openbsd", + target_os = "dragonfly", + target_os = "fuchsia", + all(target_family = "wasm", target_feature = "atomics"), + target_os = "hermit", + target_os = "motor", + ) => (1 << 30) - 2, + any( + target_family = "unix", + all(target_os = "windows", target_vendor = "win7"), + all(target_vendor = "fortanix", target_env = "sgx"), + target_os = "xous", + target_os = "teeos", + ) => { + if usize::BITS - 4 > u32::BITS { + u32::MAX + } else { + (usize::MAX >> 4) as u32 } - target_os = "solid_asp3" => (1 << 30), - _ => u32::MAX, - }; + } + target_os = "solid_asp3" => (1 << 30), + _ => u32::MAX, + }; while read_lock_ctr < MAX_READERS { let lock = rwlock.read();