From 5f9136060c6e8c0f72b571f376c462ebdb5e069c Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 25 Oct 2026 21:11:57 +0100 Subject: [PATCH 1/5] Revert "Rollup merge of #164869 - GuillaumeGomez:cg_gcc-subtree-sync, r=GuillaumeGomez" This reverts commit 36096d09e1ea05a0fd61e467b50691c50e236d3d, reversing changes made to c1ed5e8c8a2eef46cb2c953105bc30c1e2ad9141. --- .github/workflows/ci.yml | 2 + .github/workflows/rustup.yml | 2 + Cargo.lock | 8 +- Cargo.toml | 2 +- build_system/src/build.rs | 22 +++--- doc/subtree.md | 55 ++++++++++++-- libgccjit.version | 2 +- rust-toolchain.toml | 2 +- src/attributes.rs | 17 ++++- src/common.rs | 89 +++++++++++++++++------ src/consts.rs | 36 +++------ src/context.rs | 8 +- src/gcc_util.rs | 105 +++++---------------------- src/lib.rs | 7 +- tests/asm/const_global_alignment.rs | 44 ----------- tests/asm/const_str.rs | 24 ------ tests/compile/large_byte_constant.rs | 6 +- tests/failing-ui-tests.txt | 3 + 18 files changed, 193 insertions(+), 241 deletions(-) delete mode 100644 tests/asm/const_global_alignment.rs delete mode 100644 tests/asm/const_str.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66362e27ce4..0687bc11509 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,6 +116,8 @@ 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/.github/workflows/rustup.yml b/.github/workflows/rustup.yml index 0309ccbad54..57d7a39de04 100644 --- a/.github/workflows/rustup.yml +++ b/.github/workflows/rustup.yml @@ -77,6 +77,8 @@ 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 }} diff --git a/Cargo.lock b/Cargo.lock index aec0ef270bf..d94ce6575ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,18 +56,18 @@ dependencies = [ [[package]] name = "gccjit" -version = "7.3.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1820165dfb376a3bbfe8b69628d9e5a851c423e0480a666f71f560e2d0a924" +checksum = "27cd340f3c265988de3379689cbbd103c25b1f8a3d0cc73f91a49083e899d3e1" dependencies = [ "gccjit_sys", ] [[package]] name = "gccjit_sys" -version = "4.3.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6091291fc118448b91df5b662d20564020198fee46d8445a6aa0c25b6b435be" +checksum = "da2d9d74572f004908f17ec1bd0af071e5db63a12c3335c9651a6125593951c6" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 3e279961bfb..95c4c991297 100644 --- a/Cargo.toml +++ b/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.3.0", features = ["dlopen"] } +gccjit = { version = "7.2.0", features = ["dlopen"] } #gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] } # Local copy. diff --git a/build_system/src/build.rs b/build_system/src/build.rs index cc87c660520..ac076836579 100644 --- a/build_system/src/build.rs +++ b/build_system/src/build.rs @@ -6,8 +6,7 @@ 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, rustc_version_info, - walk_dir, + create_dir, get_sysroot_dir, run_command, run_command_with_output_and_env, walk_dir, }; #[derive(Default)] @@ -138,16 +137,17 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // Builds libs let mut rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); - // 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")); + // 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"); rustflags.push_str(&format!( - " --remap-path-prefix={sysroot_source_dir}=/rustc/{rustc_commit}", + " --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(), sysroot_source_dir = std::path::absolute(&sysroot_source_dir) .map_err(|error| format!( "Failed to get the absolute path of the sysroot sources: {error:?}" diff --git a/doc/subtree.md b/doc/subtree.md index dbdc09499e3..fcac399e465 100644 --- a/doc/subtree.md +++ b/doc/subtree.md @@ -1,13 +1,54 @@ -# Subtree sync procedure +# git subtree sync -`rustc_codegen_gcc` is using [Josh] to perform subtree synces with the `rust-lang/rust` repository. +`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. -[Josh]: https://josh-project.dev/docs/intro.html +### How to install a forked git-subtree -The procedure for performing subtree synchronizations is documented [here][rdg-josh]. +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: -[rdg-josh]: https://rustc-dev-guide.rust-lang.org/external-repos.html#synchronizing-a-josh-subtree +```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 +``` -## Pushing changes to `rust-lang/rust` +### Syncing with rust compiler -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. +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 diff --git a/libgccjit.version b/libgccjit.version index 0c6f40148d1..0bcc711e469 100644 --- a/libgccjit.version +++ b/libgccjit.version @@ -1 +1 @@ -e1c6e07b095d091177376a163da44039475e05e3 +37ea7cd851f04ee6a313c5067c9fde9328f876aa diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5750754596b..7d0a982195a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-10-24" +channel = "nightly-2026-10-19" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] diff --git a/src/attributes.rs b/src/attributes.rs index 925c600aee9..b6b0352d251 100644 --- a/src/attributes.rs +++ b/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, to_gcc_target_attribute_feature}; +use crate::gcc_util::to_gcc_features; /// 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,7 +192,20 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } let target_features = function_features .iter() - .filter_map(|feature| to_gcc_target_attribute_feature(feature)) + .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()) + } + }) .collect::>() .join(","); if !target_features.is_empty() { diff --git a/src/common.rs b/src/common.rs index 895bc629d63..21d92c6cc29 100644 --- a/src/common.rs +++ b/src/common.rs @@ -28,14 +28,12 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.const_uint(self.type_u16(), i as u64) } - 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()); + fn global_string(&self, string: &str) -> LValue<'gcc> { + // FIXME(antoyo): handle non-null-terminated strings. + let string = self.context.new_string_literal(string); 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. } @@ -128,6 +126,18 @@ 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 @@ -137,30 +147,65 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { /// 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> { - if let Some(&typ) = cx.byte_array_types.borrow().get(&len) { + let (element_type, count) = byte_run_shape(cx, len); + if let Some(&typ) = cx.byte_array_types.borrow().get(&(element_type, count)) { return typ; } - let typ = new_array_type(cx.context, None, cx.type_u8(), len as u64); - cx.byte_array_types.borrow_mut().insert(len, typ); + let typ = new_array_type(cx.context, None, element_type, count); + cx.byte_array_types.borrow_mut().insert((element_type, count), 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()); - // 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 + 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 .iter() - .map(|&byte| cx.context.new_rvalue_from_int(byte_type, byte as i32)) - .collect(); - cx.context.new_array_constructor(None, typ, &elements) - } + .map(|&byte| context.new_rvalue_from_int(element_type, byte as i32)) + .collect(), + }; + context.new_array_constructor(None, typ, &elements) } pub fn type_is_pointer(typ: Type<'_>) -> bool { @@ -247,7 +292,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_const_string(s); + let g = self.global_string(s); const_str_cache.insert(s.to_owned(), g); g }); diff --git a/src/consts.rs b/src/consts.rs index 6cf765a9b80..902f8ba52fa 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -64,18 +64,14 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { let cv = const_alloc_to_gcc(self, alloc); let align = alloc.inner().align; - 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; + 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); + } } - return variable; + return *variable; } let global_value = self.static_addr_of_mut(cv, align, kind); #[cfg(feature = "master")] @@ -84,7 +80,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, align)); + self.const_globals.borrow_mut().insert(cv, global_value); global_value } @@ -363,30 +359,18 @@ fn alloc_fields(cx: &CodegenCx<'_, '_>, alloc: &interpret::Allocation) -> Vec next_offset { - push_byte_runs(&mut fields, next_offset..offset); + fields.push(AllocField::Bytes { range: next_offset..offset }); } fields.push(AllocField::Pointer { offset, prov }); next_offset = offset + pointer_size; } if alloc.len() >= next_offset { - push_byte_runs(&mut fields, next_offset..alloc.len()); + fields.push(AllocField::Bytes { range: 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/src/context.rs b/src/context.rs index 39a98ede628..b57e0fb2cd2 100644 --- a/src/context.rs +++ b/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 length. + /// Cache of the array types used for runs of constant bytes, keyed by element type and count. /// /// 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>>, + pub byte_array_types: RefCell, u64), Type<'gcc>>>, /// Cache of the return types of the pairs returned in two registers, keyed by the types of the /// two scalars. @@ -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 and its alignment) - pub const_globals: RefCell, (RValue<'gcc>, Align)>>, + /// Cache of emitted const globals (value -> global) + pub const_globals: RefCell, RValue<'gcc>>>, /// 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/src/gcc_util.rs b/src/gcc_util.rs index b0442cdfb11..5ecc971830f 100644 --- a/src/gcc_util.rs +++ b/src/gcc_util.rs @@ -20,10 +20,7 @@ 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). -/// -/// 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 { +pub(crate) fn global_gcc_features(sess: &EarlySession) -> 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: // @@ -60,12 +57,6 @@ pub(crate) fn global_gcc_features(sess: &EarlySession, for_cfg: bool) -> Vec(sess: &Session) -> Context<'gcc> { context.add_command_line_option("-mavx"); } - add_feature_options(&context, &sess.target.arch, &sess.global_backend_features); + 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" + }); + } for arg in &sess.opts.cg.llvm_args { context.add_command_line_option(arg); @@ -378,80 +387,6 @@ 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/src/lib.rs b/src/lib.rs index 17034d0c195..414bff06866 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -240,7 +240,7 @@ impl CodegenBackend for GccCodegenBackend { panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths); } } - let global_backend_features = gcc_util::global_gcc_features(sess, false); + let global_backend_features = gcc_util::global_gcc_features(sess); #[cfg(feature = "master")] { @@ -253,11 +253,6 @@ 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()); diff --git a/tests/asm/const_global_alignment.rs b/tests/asm/const_global_alignment.rs deleted file mode 100644 index e4dfd4bb26d..00000000000 --- a/tests/asm/const_global_alignment.rs +++ /dev/null @@ -1,44 +0,0 @@ -//@ 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{{$}} diff --git a/tests/asm/const_str.rs b/tests/asm/const_str.rs deleted file mode 100644 index 65969a713c9..00000000000 --- a/tests/asm/const_str.rs +++ /dev/null @@ -1,24 +0,0 @@ -//@ 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/tests/compile/large_byte_constant.rs b/tests/compile/large_byte_constant.rs index 2ca3f4c2fec..b26387ecacd 100644 --- a/tests/compile/large_byte_constant.rs +++ b/tests/compile/large_byte_constant.rs @@ -1,8 +1,8 @@ -//@ memory-limit: 1G +//@ memory-limit: 2G // Compiler: -// 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. +// 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. #![crate_type = "lib"] diff --git a/tests/failing-ui-tests.txt b/tests/failing-ui-tests.txt index e35d9916247..a18eaf272fd 100644 --- a/tests/failing-ui-tests.txt +++ b/tests/failing-ui-tests.txt @@ -1,5 +1,6 @@ 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 @@ -11,6 +12,7 @@ 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 @@ -18,6 +20,7 @@ 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 1a864d51071ff1cd73cd6a47ffa02566c23f5868 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Mon, 26 Oct 2026 13:11:52 -0400 Subject: [PATCH 2/5] Prepare for merging from rust-lang/rust This updates the rust-toolchain.toml file to nightly-2026-10-26 (76456a688bc0ed897fa23a677d4ddb5b3fdb575a). --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5750754596b..16d6ab86704 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-10-24" +channel = "nightly-2026-10-26" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] From 550d82c28b8ce2df10c3d0e5ffa95bb20a3163e4 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Mon, 26 Oct 2026 13:20:04 -0400 Subject: [PATCH 3/5] Reapply "Rollup merge of #164869 - GuillaumeGomez:cg_gcc-subtree-sync, r=GuillaumeGomez" This reverts commit 5f9136060c6e8c0f72b571f376c462ebdb5e069c. --- .github/workflows/ci.yml | 2 - .github/workflows/rustup.yml | 2 - Cargo.lock | 8 +- Cargo.toml | 2 +- build_system/src/build.rs | 22 +++--- doc/subtree.md | 55 ++------------ libgccjit.version | 2 +- src/attributes.rs | 17 +---- src/common.rs | 89 ++++++----------------- src/consts.rs | 36 ++++++--- src/context.rs | 8 +- src/gcc_util.rs | 105 ++++++++++++++++++++++----- src/lib.rs | 7 +- tests/asm/const_global_alignment.rs | 44 +++++++++++ tests/asm/const_str.rs | 24 ++++++ tests/compile/large_byte_constant.rs | 6 +- tests/failing-ui-tests.txt | 3 - 17 files changed, 240 insertions(+), 192 deletions(-) create mode 100644 tests/asm/const_global_alignment.rs create mode 100644 tests/asm/const_str.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0687bc11509..66362e27ce4 100644 --- a/.github/workflows/ci.yml +++ b/.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/.github/workflows/rustup.yml b/.github/workflows/rustup.yml index 57d7a39de04..0309ccbad54 100644 --- a/.github/workflows/rustup.yml +++ b/.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 }} diff --git a/Cargo.lock b/Cargo.lock index d94ce6575ac..aec0ef270bf 100644 --- a/Cargo.lock +++ b/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/Cargo.toml b/Cargo.toml index 95c4c991297..3e279961bfb 100644 --- a/Cargo.toml +++ b/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/build_system/src/build.rs b/build_system/src/build.rs index ac076836579..cc87c660520 100644 --- a/build_system/src/build.rs +++ b/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/library"); + // 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:?}" diff --git a/doc/subtree.md b/doc/subtree.md index fcac399e465..dbdc09499e3 100644 --- a/doc/subtree.md +++ b/doc/subtree.md @@ -1,54 +1,13 @@ -# 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 -``` +[rdg-josh]: https://rustc-dev-guide.rust-lang.org/external-repos.html#synchronizing-a-josh-subtree -### Syncing with rust compiler +## Pushing changes to `rust-lang/rust` -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 +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. diff --git a/libgccjit.version b/libgccjit.version index 0bcc711e469..0c6f40148d1 100644 --- a/libgccjit.version +++ b/libgccjit.version @@ -1 +1 @@ -37ea7cd851f04ee6a313c5067c9fde9328f876aa +e1c6e07b095d091177376a163da44039475e05e3 diff --git a/src/attributes.rs b/src/attributes.rs index b6b0352d251..925c600aee9 100644 --- a/src/attributes.rs +++ b/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/src/common.rs b/src/common.rs index 21d92c6cc29..895bc629d63 100644 --- a/src/common.rs +++ b/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. } @@ -126,18 +128,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 +137,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 { @@ -292,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/src/consts.rs b/src/consts.rs index 902f8ba52fa..6cf765a9b80 100644 --- a/src/consts.rs +++ b/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 } @@ -359,18 +363,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/src/context.rs b/src/context.rs index b57e0fb2cd2..39a98ede628 100644 --- a/src/context.rs +++ b/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. @@ -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/src/gcc_util.rs b/src/gcc_util.rs index 5ecc971830f..b0442cdfb11 100644 --- a/src/gcc_util.rs +++ b/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/src/lib.rs b/src/lib.rs index 414bff06866..17034d0c195 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -240,7 +240,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")] { @@ -253,6 +253,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()); diff --git a/tests/asm/const_global_alignment.rs b/tests/asm/const_global_alignment.rs new file mode 100644 index 00000000000..e4dfd4bb26d --- /dev/null +++ b/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{{$}} diff --git a/tests/asm/const_str.rs b/tests/asm/const_str.rs new file mode 100644 index 00000000000..65969a713c9 --- /dev/null +++ b/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/tests/compile/large_byte_constant.rs b/tests/compile/large_byte_constant.rs index b26387ecacd..2ca3f4c2fec 100644 --- a/tests/compile/large_byte_constant.rs +++ b/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"] diff --git a/tests/failing-ui-tests.txt b/tests/failing-ui-tests.txt index a18eaf272fd..e35d9916247 100644 --- a/tests/failing-ui-tests.txt +++ b/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 @@ -12,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 @@ -20,7 +18,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 555dc713c7220cec54d491e206ed33e86caa3177 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Mon, 26 Oct 2026 14:36:06 -0400 Subject: [PATCH 4/5] Fix for GitHub rate-limiting while fetching the Rust repo --- build_system/src/test.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/build_system/src/test.rs b/build_system/src/test.rs index 79eae8afa14..2880108825b 100644 --- a/build_system/src/test.rs +++ b/build_system/src/test.rs @@ -559,18 +559,33 @@ fn setup_rustc(env: &mut Env, args: &TestArg) -> Result { let _ = git_clone("https://github.com/rust-lang/rust.git", Some(&rust_dir_path), false); let rust_dir: Option<&Path> = Some(&rust_dir_path); run_command(&[&"git", &"checkout", &"--", &"tests/"], rust_dir)?; - run_command_with_output_and_env(&[&"git", &"fetch"], rust_dir, Some(env))?; let rustc_commit = match rustc_version_info(env.get("RUSTC").map(|s| s.as_str()))?.commit_hash { Some(commit_hash) => commit_hash, None => return Err("Couldn't retrieve rustc commit hash".to_string()), }; if rustc_commit != "unknown" { + // GitHub rate-limits a fetch right after a full clone, so only fetch a missing commit. + let commit_object = format!("{rustc_commit}^{{commit}}"); + let has_commit = run_command_with_output_and_env_no_err( + &[&"git", &"rev-parse", &"--quiet", &"--verify", &commit_object], + rust_dir, + Some(env), + ) + .is_ok(); + if !has_commit { + run_command_with_output_and_env( + &[&"git", &"fetch", &"origin", &rustc_commit], + rust_dir, + Some(env), + )?; + } run_command_with_output_and_env( &[&"git", &"checkout", &rustc_commit], rust_dir, Some(env), )?; } else { + run_command_with_output_and_env(&[&"git", &"fetch"], rust_dir, Some(env))?; run_command_with_output_and_env(&[&"git", &"checkout"], rust_dir, Some(env))?; } From 7cca1c5fef9c7146f6d7f28a012dfe5f170d2e79 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Mon, 26 Oct 2026 17:35:04 -0400 Subject: [PATCH 5/5] Pre-clone the rustc repo in CI to avoid rate-limiting --- .github/actions/checkout-rust/action.yml | 31 ++++++++++++++++++++++++ .github/workflows/ci.yml | 4 +++ .github/workflows/failures.yml | 4 +++ .github/workflows/release.yml | 3 +++ .github/workflows/rustup.yml | 11 +++++++++ 5 files changed, 53 insertions(+) create mode 100644 .github/actions/checkout-rust/action.yml diff --git a/.github/actions/checkout-rust/action.yml b/.github/actions/checkout-rust/action.yml new file mode 100644 index 00000000000..67140523f20 --- /dev/null +++ b/.github/actions/checkout-rust/action.yml @@ -0,0 +1,31 @@ +name: Check out rust-lang/rust +description: > + Checks out the rust-lang/rust commit of the toolchain into `/build/rust`, where y.sh runs + the rustc tests. Unlike the anonymous full clone y.sh would do, this checkout is authenticated + and shallow, so GitHub does not rate-limit it. + +inputs: + path: + description: The cg_gcc directory. + default: . + +runs: + using: composite + steps: + - id: toolchain + shell: bash + working-directory: ${{ inputs.path }} + run: | + commit="$(rustc -vV | sed -n 's/^commit-hash: //p')" + if [ -z "$commit" ]; then + echo "::error::could not find the commit of the toolchain" + exit 1 + fi + echo "commit=$commit" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v7 + with: + repository: rust-lang/rust + ref: ${{ steps.toolchain.outputs.commit }} + path: ${{ inputs.path }}/build/rust + persist-credentials: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66362e27ce4..fc45d1a60f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -173,6 +173,10 @@ jobs: if: ${{ matrix.libgccjit_version.gcc == 'gcc-15-without-int128' }} run: cat tests/failing-ui-tests-without-128bit-integers.txt >> tests/failing-ui-tests.txt + - name: Check out rust-lang/rust + if: ${{ contains(matrix.commands, '-rustc') }} + uses: ./.github/actions/checkout-rust + - name: Run tests run: | ./y.sh test --release --clean --build-sysroot --no-builtins-tests ${{ matrix.commands }} diff --git a/.github/workflows/failures.yml b/.github/workflows/failures.yml index cd218ee0b2e..23411645931 100644 --- a/.github/workflows/failures.yml +++ b/.github/workflows/failures.yml @@ -100,6 +100,10 @@ jobs: if: matrix.libgccjit_version.gcc != 'libgccjit12.so' run: ./y.sh prepare + - name: Check out rust-lang/rust + if: matrix.libgccjit_version.gcc != 'libgccjit12.so' + uses: ./.github/actions/checkout-rust + - name: Run tests # FIXME: re-enable those tests for libgccjit 12. if: matrix.libgccjit_version.gcc != 'libgccjit12.so' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43885125278..cc91ea2b666 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,9 @@ jobs: - name: Add more failing tests (some panic and debuginfo tests fail) run: cat tests/failing-lto-tests.txt >> tests/failing-ui-tests.txt + - name: Check out rust-lang/rust + uses: ./.github/actions/checkout-rust + - name: Run tests run: | # FIXME(antoyo): we cannot enable LTO for stdarch tests currently because of some failing LTO tests using proc-macros. diff --git a/.github/workflows/rustup.yml b/.github/workflows/rustup.yml index 0309ccbad54..13fb592fc2b 100644 --- a/.github/workflows/rustup.yml +++ b/.github/workflows/rustup.yml @@ -111,6 +111,12 @@ jobs: git config --global user.name "User" ./y.sh prepare + - name: Check out rust-lang/rust + if: ${{ contains(matrix.commands, '-rustc') }} + uses: ./.github/actions/checkout-rust + with: + path: rust/compiler/rustc_codegen_gcc + - name: Run tests run: | ./y.sh test --release --clean --build-sysroot --no-builtins-tests ${{ matrix.commands }} @@ -252,6 +258,11 @@ jobs: git config --global user.name "User" ./y.sh prepare + - name: Check out rust-lang/rust + uses: ./.github/actions/checkout-rust + with: + path: rust/compiler/rustc_codegen_gcc + - name: Run tests run: | # Without this, `tee` masks the exit status of `y.sh test`.