From 95c38b2bbaa666c52268902c3dff108615cfa63d Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Sat, 8 Nov 2025 11:36:01 -0500 Subject: [PATCH 1/6] Add libgccjit-libs-dir config --- bootstrap.example.toml | 25 +++ src/bootstrap/src/core/build_steps/compile.rs | 187 ++++++++++++++++-- src/bootstrap/src/core/build_steps/dist.rs | 6 +- src/bootstrap/src/core/build_steps/gcc.rs | 155 ++++++++++----- src/bootstrap/src/core/build_steps/test.rs | 7 +- src/bootstrap/src/core/config/config.rs | 7 +- src/bootstrap/src/core/config/toml/gcc.rs | 1 + src/bootstrap/src/lib.rs | 4 - 8 files changed, 313 insertions(+), 79 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 4e850810a30a9..63bf50722ca09 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -191,6 +191,31 @@ # Currently, this is only supported for the `x86_64-unknown-linux-gnu` target. #gcc.download-ci-gcc = false +# Provide a directory of prebuilt libgccjit.so dylibs for given (host, target) compilation pairs. +# This is useful when you want to cross-compile `rustc` to another target since GCC is not a +# multi-target compiler. +# You have to use a directory structure that looks like this: +# `///libgccjit.so`. +# For example: +# +# ``` +# +# ├── m68k-unknown-linux-gnu +# │ └── m68k-unknown-linux-gnu +# │ └── libgccjit.so +# └── x86_64-unknown-linux-gnu +# ├── m68k-unknown-linux-gnu +# │ └── libgccjit.so +# └── x86_64-unknown-linux-gnu +# └── libgccjit.so +# ``` +# The directory above would allow you to cross-compile rustc from x64 to m68k +# +# Note that this option has priority over `gcc.download-ci-gcc`. +# If you set both, bootstrap will first try to load libgccjit.so from this directory. +# Only if it isn't found, it will try to download it from CI or build it locally. +#gcc.libgccjit-libs-dir = "/path/to/libgccjit-libs-dir" + # ============================================================================= # General build configuration options # ============================================================================= diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 82cc25f123bc9..9adf889def002 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -7,7 +7,7 @@ //! goes along from the output of the previous stage. use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::ffi::OsStr; use std::io::BufReader; use std::io::prelude::*; @@ -19,7 +19,7 @@ use serde_derive::Deserialize; #[cfg(feature = "tracing")] use tracing::span; -use crate::core::build_steps::gcc::{Gcc, GccOutput, add_cg_gcc_cargo_flags}; +use crate::core::build_steps::gcc::{Gcc, GccOutput, GccTargetPair, add_cg_gcc_cargo_flags}; use crate::core::build_steps::tool::{RustcPrivateCompilers, SourceType, copy_lld_artifacts}; use crate::core::build_steps::{dist, llvm}; use crate::core::builder; @@ -1576,17 +1576,98 @@ impl Step for RustcLink { } } +/// Set of `libgccjit` dylibs that can be used by `cg_gcc` to compile code for a set of targets. +#[derive(Clone)] +pub struct GccDylibSet { + dylibs: BTreeMap, + host_pair: GccTargetPair, +} + +impl GccDylibSet { + /// Returns the libgccjit.so dylib that corresponds to a host target on which `cg_gcc` will be + /// executed, and which will target the host. So e.g. if `cg_gcc` will be executed on + /// x86_64-unknown-linux-gnu, the host dylib will be for compilation pair + /// `(x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu)`. + fn host_dylib(&self) -> &GccOutput { + self.dylibs.get(&self.host_pair).unwrap_or_else(|| { + panic!("libgccjit.so was not built for host target {}", self.host_pair) + }) + } + + /// Install the libgccjit dylibs to the corresponding target directories of the given compiler. + /// cg_gcc know how to search for the libgccjit dylibs in these directories, according to the + /// (host, target) pair that is being compiled by rustc and cg_gcc. + pub fn install_to(&self, builder: &Builder<'_>, compiler: Compiler) { + if builder.config.dry_run() { + return; + } + + // /lib//codegen-backends + let cg_sysroot = builder.sysroot_codegen_backends(compiler); + + for (target_pair, libgccjit) in &self.dylibs { + assert_eq!( + target_pair.host(), + compiler.host, + "Trying to install libgccjit ({target_pair}) to a compiler with a different host ({})", + compiler.host + ); + let libgccjit = libgccjit.libgccjit(); + let target_filename = libgccjit.file_name().unwrap().to_str().unwrap(); + + // If we build libgccjit ourselves, then `libgccjit` can actually be a symlink. + // In that case, we have to resolve it first, otherwise we'd create a symlink to a + // symlink, which wouldn't work. + let actual_libgccjit_path = t!( + libgccjit.canonicalize(), + format!("Cannot find libgccjit at {}", libgccjit.display()) + ); + + // /lib//libgccjit.so + let dest_dir = cg_sysroot.join("lib").join(target_pair.target()); + t!(fs::create_dir_all(&dest_dir)); + let dst = dest_dir.join(target_filename); + builder.copy_link(&actual_libgccjit_path, &dst, FileType::NativeLibrary); + } + } +} + /// Output of the `compile::GccCodegenBackend` step. -/// It includes the path to the libgccjit library on which this backend depends. +/// +/// It contains paths to all built libgccjit libraries on which this backend depends here. #[derive(Clone)] pub struct GccCodegenBackendOutput { stamp: BuildStamp, - gcc: GccOutput, + dylib_set: GccDylibSet, } +/// Builds the GCC codegen backend (`cg_gcc`). +/// The `cg_gcc` backend uses `libgccjit`, which requires a separate build for each +/// `host -> target` pair. So if you are on linux-x64 and build for linux-aarch64, +/// you will need at least: +/// - linux-x64 -> linux-x64 libgccjit (for building host code like proc macros) +/// - linux-x64 -> linux-aarch64 libgccjit (for the aarch64 target code) +/// +/// We model this by having a single cg_gcc for a given host target, which contains one +/// libgccjit per (host, target) pair. +/// Note that the host target is taken from `self.compilers.target_compiler.host`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GccCodegenBackend { compilers: RustcPrivateCompilers, + targets: Vec, +} + +impl GccCodegenBackend { + /// Build `cg_gcc` that will run on host `H` (`compilers.target_compiler.host`) and will be + /// able to produce code target pairs (`H`, `T`) for all `T` from `targets`. + pub fn for_targets( + compilers: RustcPrivateCompilers, + mut targets: Vec, + ) -> Self { + // Sort targets to improve step cache hits + targets.sort(); + Self { compilers, targets } + } } impl Step for GccCodegenBackend { @@ -1599,23 +1680,34 @@ impl Step for GccCodegenBackend { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(GccCodegenBackend { - compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target), - }); + // By default, build cg_gcc that will only be able to compile native code for the given + // host target. + let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target); + run.builder.ensure(GccCodegenBackend { compilers, targets: vec![run.target] }); } fn run(self, builder: &Builder<'_>) -> Self::Output { - let target = self.compilers.target(); + let host = self.compilers.target(); let build_compiler = self.compilers.build_compiler(); let stamp = build_stamp::codegen_backend_stamp( builder, build_compiler, - target, + host, &CodegenBackendKind::Gcc, ); - let gcc = builder.ensure(Gcc { target }); + let dylib_set = GccDylibSet { + dylibs: self + .targets + .iter() + .map(|&target| { + let target_pair = GccTargetPair::for_target_pair(host, target); + (target_pair, builder.ensure(Gcc { target_pair })) + }) + .collect(), + host_pair: GccTargetPair::for_native_build(host), + }; if builder.config.keep_stage.contains(&build_compiler.stage) { trace!("`keep-stage` requested"); @@ -1625,7 +1717,7 @@ impl Step for GccCodegenBackend { ); // Codegen backends are linked separately from this step today, so we don't do // anything here. - return GccCodegenBackendOutput { stamp, gcc }; + return GccCodegenBackendOutput { stamp, dylib_set }; } let mut cargo = builder::Cargo::new( @@ -1633,21 +1725,21 @@ impl Step for GccCodegenBackend { build_compiler, Mode::Codegen, SourceType::InTree, - target, + host, Kind::Build, ); cargo.arg("--manifest-path").arg(builder.src.join("compiler/rustc_codegen_gcc/Cargo.toml")); - rustc_cargo_env(builder, &mut cargo, target); + rustc_cargo_env(builder, &mut cargo, host); - add_cg_gcc_cargo_flags(&mut cargo, &gcc); + add_cg_gcc_cargo_flags(&mut cargo, dylib_set.host_dylib()); let _guard = - builder.msg(Kind::Build, "codegen backend gcc", Mode::Codegen, build_compiler, target); + builder.msg(Kind::Build, "codegen backend gcc", Mode::Codegen, build_compiler, host); let files = run_cargo(builder, cargo, vec![], &stamp, vec![], false, false); GccCodegenBackendOutput { stamp: write_codegen_backend_stamp(stamp, files, builder.config.dry_run()), - gcc, + dylib_set, } } @@ -2324,12 +2416,65 @@ impl Step for Assemble { copy_codegen_backends_to_sysroot(builder, stamp, target_compiler); } CodegenBackendKind::Gcc => { - let output = - builder.ensure(GccCodegenBackend { compilers: prepare_compilers() }); + // We need to build cg_gcc for the host target of the compiler which we + // build here, which is `target_compiler`. + // But we also need to build libgccjit for some additional targets, in + // the most general case. + // 1. We need to build (target_compiler.host, stdlib target) libgccjit + // for all stdlibs that we build, so that cg_gcc can be used to build code + // for all those targets. + // 2. We need to build (target_compiler.host, target_compiler.host) + // libgccjit, so that the target compiler can compile host code (e.g. proc + // macros). + // 3. We need to build (target_compiler.host, host target) libgccjit + // for all *host targets* that we build, so that cg_gcc can be used to + // build a (possibly cross-compiled) stage 2+ rustc. + // + // Assume that we are on host T1 and we do a stage2 build of rustc for T2. + // We want the T2 rustc compiler to be able to use cg_gcc and build code + // for T2 (host) and T3 (target). We also want to build the stage2 compiler + // itself using cg_gcc. + // This could correspond to the following bootstrap invocation: + // `x build rustc --build T1 --host T2 --target T3 --set codegen-backends=['gcc', 'llvm']` + // + // For that, we will need the following GCC target pairs: + // 1. T1 -> T2 (to cross-compile a T2 rustc using cg_gcc running on T1) + // 2. T2 -> T2 (to build host code with the stage 2 rustc running on T2) + // 3. T2 -> T3 (to cross-compile code with the stage 2 rustc running on T2) + // + // FIXME: this set of targets is *maximal*, in reality we might need + // less libgccjits at this current build stage. Try to reduce the set of + // GCC dylibs built below by taking a look at the current stage and whether + // cg_gcc is used as the default codegen backend. + + let compilers = prepare_compilers(); + + // The left side of the target pairs below is implied. It has to match the + // host target on which cg_gcc will run, which is the host target of + // `target_compiler`. We only pass the right side of the target pairs to + // the `GccCodegenBackend` constructor. + let mut targets = HashSet::new(); + // Add all host targets, so that we are able to build host code in this + // bootstrap invocation using cg_gcc. + for target in &builder.hosts { + targets.insert(*target); + } + // Add all stdlib targets, so that the built rustc can produce code for them + for target in &builder.targets { + targets.insert(*target); + } + // Add the host target of the built rustc itself, so that it can build + // host code (e.g. proc macros) using cg_gcc. + targets.insert(compilers.target_compiler().host); + + let output = builder.ensure(GccCodegenBackend::for_targets( + compilers, + targets.into_iter().collect(), + )); copy_codegen_backends_to_sysroot(builder, output.stamp, target_compiler); - // Also copy libgccjit to the library sysroot, so that it is available for - // the codegen backend. - output.gcc.install_to(builder, &rustc_libdir); + // Also copy all requires libgccjit dylibs to the corresponding + // library sysroots, so that they are available for the codegen backend. + output.dylib_set.install_to(builder, target_compiler); } CodegenBackendKind::Llvm | CodegenBackendKind::Custom(_) => continue, } diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index caf0af35e401d..cbbdc7e024195 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -21,6 +21,7 @@ use tracing::instrument; use crate::core::build_steps::compile::{get_codegen_backend_file, normalize_codegen_backend_name}; use crate::core::build_steps::doc::DocumentationFormat; +use crate::core::build_steps::gcc::GccTargetPair; use crate::core::build_steps::tool::{ self, RustcPrivateCompilers, ToolTargetBuildMode, get_tool_target_compiler, }; @@ -2856,8 +2857,9 @@ impl Step for Gcc { fn run(self, builder: &Builder<'_>) -> Self::Output { let tarball = Tarball::new(builder, "gcc", &self.target.triple); - let output = builder.ensure(super::gcc::Gcc { target: self.target }); - tarball.add_file(&output.libgccjit, "lib", FileType::NativeLibrary); + let output = builder + .ensure(super::gcc::Gcc { target_pair: GccTargetPair::for_native_build(self.target) }); + tarball.add_file(output.libgccjit(), "lib", FileType::NativeLibrary); tarball.generate() } diff --git a/src/bootstrap/src/core/build_steps/gcc.rs b/src/bootstrap/src/core/build_steps/gcc.rs index fc87c48f17b68..9cc5d0ed71b16 100644 --- a/src/bootstrap/src/core/build_steps/gcc.rs +++ b/src/bootstrap/src/core/build_steps/gcc.rs @@ -8,49 +8,69 @@ //! GCC and compiler-rt are essentially just wired up to everything else to //! ensure that they're always in place if needed. +use std::fmt::{Display, Formatter}; use std::fs; use std::path::{Path, PathBuf}; use std::sync::OnceLock; -use crate::FileType; use crate::core::builder::{Builder, Cargo, Kind, RunConfig, ShouldRun, Step}; use crate::core::config::TargetSelection; use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash}; use crate::utils::exec::command; use crate::utils::helpers::{self, t}; +/// GCC cannot cross-compile from a single binary to multiple targets. +/// So we need to have a separate GCC dylib for each (host, target) pair. +/// We represent this explicitly using this struct. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct GccTargetPair { + /// Target on which the libgccjit.so dylib will be executed. + host: TargetSelection, + /// Target for which the libgccjit.so dylib will generate assembly. + target: TargetSelection, +} + +impl GccTargetPair { + /// Create a target pair for a GCC that will run on `target` and generate assembly for `target`. + pub fn for_native_build(target: TargetSelection) -> Self { + Self { host: target, target } + } + + /// Create a target pair for a GCC that will run on `host` and generate assembly for `target`. + /// This may be cross-compilation if `host != target`. + pub fn for_target_pair(host: TargetSelection, target: TargetSelection) -> Self { + Self { host, target } + } + + pub fn host(&self) -> TargetSelection { + self.host + } + + pub fn target(&self) -> TargetSelection { + self.target + } +} + +impl Display for GccTargetPair { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} -> {}", self.host, self.target) + } +} + #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Gcc { - pub target: TargetSelection, + pub target_pair: GccTargetPair, } #[derive(Clone)] pub struct GccOutput { - pub libgccjit: PathBuf, - target: TargetSelection, + /// Path to a built or downloaded libgccjit. + libgccjit: PathBuf, } impl GccOutput { - /// Install the required libgccjit library file(s) to the specified `path`. - pub fn install_to(&self, builder: &Builder<'_>, directory: &Path) { - if builder.config.dry_run() { - return; - } - - let target_filename = self.libgccjit.file_name().unwrap().to_str().unwrap().to_string(); - - // If we build libgccjit ourselves, then `self.libgccjit` can actually be a symlink. - // In that case, we have to resolve it first, otherwise we'd create a symlink to a symlink, - // which wouldn't work. - let actual_libgccjit_path = t!( - self.libgccjit.canonicalize(), - format!("Cannot find libgccjit at {}", self.libgccjit.display()) - ); - - let dest_dir = directory.join("rustlib").join(self.target).join("lib"); - t!(fs::create_dir_all(&dest_dir)); - let dst = dest_dir.join(target_filename); - builder.copy_link(&actual_libgccjit_path, &dst, FileType::NativeLibrary); + pub fn libgccjit(&self) -> &Path { + &self.libgccjit } } @@ -64,33 +84,38 @@ impl Step for Gcc { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Gcc { target: run.target }); + // By default, we build libgccjit that can do native compilation (no cross-compilation) + // on a given target. + run.builder + .ensure(Gcc { target_pair: GccTargetPair { host: run.target, target: run.target } }); } /// Compile GCC (specifically `libgccjit`) for `target`. fn run(self, builder: &Builder<'_>) -> Self::Output { - let target = self.target; + let target_pair = self.target_pair; // If GCC has already been built, we avoid building it again. - let metadata = match get_gcc_build_status(builder, target) { - GccBuildStatus::AlreadyBuilt(path) => return GccOutput { libgccjit: path, target }, + let metadata = match get_gcc_build_status(builder, target_pair) { + GccBuildStatus::AlreadyBuilt(path) => return GccOutput { libgccjit: path }, GccBuildStatus::ShouldBuild(m) => m, }; - let _guard = builder.msg_unstaged(Kind::Build, "GCC", target); + let action = Kind::Build.description(); + let msg = format!("{action} GCC for {target_pair}"); + let _guard = builder.group(&msg); t!(metadata.stamp.remove()); let _time = helpers::timeit(builder); let libgccjit_path = libgccjit_built_path(&metadata.install_dir); if builder.config.dry_run() { - return GccOutput { libgccjit: libgccjit_path, target }; + return GccOutput { libgccjit: libgccjit_path }; } - build_gcc(&metadata, builder, target); + build_gcc(&metadata, builder, target_pair); t!(metadata.stamp.write()); - GccOutput { libgccjit: libgccjit_path, target } + GccOutput { libgccjit: libgccjit_path } } } @@ -111,15 +136,27 @@ pub enum GccBuildStatus { /// are available for the given target. /// Returns a path to the libgccjit.so file. #[cfg(not(test))] -fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option { +fn try_download_gcc(builder: &Builder<'_>, target_pair: GccTargetPair) -> Option { use build_helper::git::PathFreshness; // Try to download GCC from CI if configured and available if !matches!(builder.config.gcc_ci_mode, crate::core::config::GccCiMode::DownloadFromCi) { return None; } - if target != "x86_64-unknown-linux-gnu" { - eprintln!("GCC CI download is only available for the `x86_64-unknown-linux-gnu` target"); + + // We currently do not support downloading CI GCC if the host/target pair doesn't match. + if target_pair.host != target_pair.target { + eprintln!( + "GCC CI download is not available when the host ({}) does not equal the compilation target ({}).", + target_pair.host, target_pair.target + ); + return None; + } + + if target_pair.host != "x86_64-unknown-linux-gnu" { + eprintln!( + "GCC CI download is only available for the `x86_64-unknown-linux-gnu` host/target" + ); return None; } let source = detect_gcc_freshness( @@ -132,7 +169,7 @@ fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option { // Download from upstream CI - let root = ci_gcc_root(&builder.config, target); + let root = ci_gcc_root(&builder.config, target_pair.target); let gcc_stamp = BuildStamp::new(&root).with_prefix("gcc").add_stamp(&upstream); if !gcc_stamp.is_up_to_date() && !builder.config.dry_run() { builder.config.download_ci_gcc(&upstream, &root); @@ -158,7 +195,7 @@ fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option, _target: TargetSelection) -> Option { +fn try_download_gcc(_builder: &Builder<'_>, _target_pair: GccTargetPair) -> Option { None } @@ -167,11 +204,28 @@ fn try_download_gcc(_builder: &Builder<'_>, _target: TargetSelection) -> Option< /// /// It's used to avoid busting caches during x.py check -- if we've already built /// GCC, it's fine for us to not try to avoid doing so. -pub fn get_gcc_build_status(builder: &Builder<'_>, target: TargetSelection) -> GccBuildStatus { - if let Some(path) = try_download_gcc(builder, target) { +pub fn get_gcc_build_status(builder: &Builder<'_>, target_pair: GccTargetPair) -> GccBuildStatus { + // Prefer taking externally provided prebuilt libgccjit dylib + if let Some(dir) = &builder.config.libgccjit_libs_dir { + // The dir structure should be ///libgccjit.so + let host_dir = dir.join(target_pair.host); + let path = host_dir.join(target_pair.target).join("libgccjit.so"); + if path.exists() { + return GccBuildStatus::AlreadyBuilt(path); + } else { + builder.info(&format!( + "libgccjit.so for `{target_pair}` was not found at `{}`", + path.display() + )); + } + } + + // If not available, try to download from CI + if let Some(path) = try_download_gcc(builder, target_pair) { return GccBuildStatus::AlreadyBuilt(path); } + // If not available, try to build (or use already built libgccjit from disk) static STAMP_HASH_MEMO: OnceLock = OnceLock::new(); let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| { generate_smart_stamp_hash( @@ -185,8 +239,8 @@ pub fn get_gcc_build_status(builder: &Builder<'_>, target: TargetSelection) -> G builder.config.update_submodule("src/gcc"); let root = builder.src.join("src/gcc"); - let out_dir = builder.gcc_out(target).join("build"); - let install_dir = builder.gcc_out(target).join("install"); + let out_dir = gcc_out(builder, target_pair).join("build"); + let install_dir = gcc_out(builder, target_pair).join("install"); let stamp = BuildStamp::new(&out_dir).with_prefix("gcc").add_stamp(smart_stamp_hash); @@ -215,15 +269,20 @@ pub fn get_gcc_build_status(builder: &Builder<'_>, target: TargetSelection) -> G GccBuildStatus::ShouldBuild(Meta { stamp, out_dir, install_dir, root }) } +fn gcc_out(builder: &Builder<'_>, pair: GccTargetPair) -> PathBuf { + builder.out.join(pair.host).join("gcc").join(pair.target) +} + /// Returns the path to a libgccjit.so file in the install directory of GCC. fn libgccjit_built_path(install_dir: &Path) -> PathBuf { install_dir.join("lib/libgccjit.so") } -fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target: TargetSelection) { - if builder.build.cc_tool(target).is_like_clang() - || builder.build.cxx_tool(target).is_like_clang() - { +fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target_pair: GccTargetPair) { + // Target on which libgccjit.so will be executed. Here we will generate a dylib with + // instructions for that target. + let host = target_pair.host; + if builder.build.cc_tool(host).is_like_clang() || builder.build.cxx_tool(host).is_like_clang() { panic!( "Attempting to build GCC using Clang, which is known to misbehave. Please use GCC as the host C/C++ compiler. " ); @@ -241,7 +300,7 @@ fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target: TargetSelection) { // builds. // Therefore, we first copy the whole source directory to the build directory, and perform the // build from there. - let src_dir = builder.gcc_out(target).join("src"); + let src_dir = gcc_out(builder, target_pair).join("src"); if src_dir.exists() { builder.remove_dir(&src_dir); } @@ -259,7 +318,7 @@ fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target: TargetSelection) { .arg("--disable-multilib") .arg(format!("--prefix={}", install_dir.display())); - let cc = builder.build.cc(target).display().to_string(); + let cc = builder.build.cc(host).display().to_string(); let cc = builder .build .config @@ -268,7 +327,7 @@ fn build_gcc(metadata: &Meta, builder: &Builder<'_>, target: TargetSelection) { .map_or_else(|| cc.clone(), |ccache| format!("{ccache} {cc}")); configure_cmd.env("CC", cc); - if let Ok(ref cxx) = builder.build.cxx(target) { + if let Ok(ref cxx) = builder.build.cxx(host) { let cxx = cxx.display().to_string(); let cxx = builder .build diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 9f8bb4dea54fd..05aa2521b2e0a 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -16,7 +16,7 @@ use build_helper::exit; use crate::core::build_steps::compile::{Std, run_cargo}; use crate::core::build_steps::doc::{DocumentationFormat, prepare_doc_compiler}; -use crate::core::build_steps::gcc::{Gcc, add_cg_gcc_cargo_flags}; +use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget; @@ -3956,7 +3956,7 @@ impl Step for CodegenGCC { let compilers = self.compilers; let target = self.target; - let gcc = builder.ensure(Gcc { target }); + let gcc = builder.ensure(Gcc { target_pair: GccTargetPair::for_native_build(target) }); builder.ensure( compile::Std::new(compilers.build_compiler(), target) @@ -3997,12 +3997,13 @@ impl Step for CodegenGCC { .arg("--use-backend") .arg("gcc") .arg("--gcc-path") - .arg(gcc.libgccjit.parent().unwrap()) + .arg(gcc.libgccjit().parent().unwrap()) .arg("--out-dir") .arg(builder.stage_out(compilers.build_compiler(), Mode::Codegen).join("cg_gcc")) .arg("--release") .arg("--mini-tests") .arg("--std-tests"); + cargo.args(builder.config.test_args()); cargo.into_cmd().run(builder); diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 2f493658ec0ec..a81b8ccb9140a 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -188,6 +188,7 @@ pub struct Config { // gcc codegen options pub gcc_ci_mode: GccCiMode, + pub libgccjit_libs_dir: Option, // rust codegen options pub rust_optimize: RustOptimize, @@ -620,7 +621,10 @@ impl Config { vendor: dist_vendor, } = toml.dist.unwrap_or_default(); - let Gcc { download_ci_gcc: gcc_download_ci_gcc } = toml.gcc.unwrap_or_default(); + let Gcc { + download_ci_gcc: gcc_download_ci_gcc, + libgccjit_libs_dir: gcc_libgccjit_libs_dir, + } = toml.gcc.unwrap_or_default(); if rust_bootstrap_override_lld.is_some() && rust_bootstrap_override_lld_legacy.is_some() { panic!( @@ -1346,6 +1350,7 @@ impl Config { keep_stage: flags_keep_stage, keep_stage_std: flags_keep_stage_std, libdir: install_libdir.map(PathBuf::from), + libgccjit_libs_dir: gcc_libgccjit_libs_dir, library_docs_private_items: build_library_docs_private_items.unwrap_or(false), lld_enabled, lldb: build_lldb.map(PathBuf::from), diff --git a/src/bootstrap/src/core/config/toml/gcc.rs b/src/bootstrap/src/core/config/toml/gcc.rs index 9ea697edf159e..94d15a9baaff9 100644 --- a/src/bootstrap/src/core/config/toml/gcc.rs +++ b/src/bootstrap/src/core/config/toml/gcc.rs @@ -15,5 +15,6 @@ define_config! { #[derive(Default)] struct Gcc { download_ci_gcc: Option = "download-ci-gcc", + libgccjit_libs_dir: Option = "libgccjit-libs-dir", } } diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index a31eb0c1c8012..adae9a1b8b081 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -975,10 +975,6 @@ impl Build { self.out.join(&*target.triple).join("enzyme") } - fn gcc_out(&self, target: TargetSelection) -> PathBuf { - self.out.join(&*target.triple).join("gcc") - } - fn lld_out(&self, target: TargetSelection) -> PathBuf { self.out.join(target).join("lld") } From a9511b9b49f2118e8176609389bf84a404ca15ac Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Thu, 11 Dec 2025 11:15:15 -0500 Subject: [PATCH 2/6] When we cannot load libgccjit.so, show the paths that were tried --- compiler/rustc_codegen_gcc/src/lib.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index a77239e23b4e5..96d3a0024f418 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -98,7 +98,6 @@ use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; use rustc_session::Session; use rustc_session::config::{OptLevel, OutputFilenames}; -use rustc_session::filesearch::make_target_lib_path; use rustc_span::Symbol; use rustc_target::spec::{Arch, RelocModel}; use tempfile::TempDir; @@ -207,18 +206,38 @@ impl CodegenBackend for GccCodegenBackend { } fn init(&self, sess: &Session) { + fn file_path(sysroot_path: &Path, sess: &Session) -> PathBuf { + let rustlib_path = + rustc_target::relative_target_rustlib_path(sysroot_path, &sess.host.llvm_target); + sysroot_path + .join(rustlib_path) + .join("codegen-backends") + .join("lib") + .join(sess.target.llvm_target.as_ref()) + .join("libgccjit.so") + } + // We use all_paths() instead of only path() in case the path specified by --sysroot is // invalid. // This is the case for instance in Rust for Linux where they specify --sysroot=/dev/null. for path in sess.opts.sysroot.all_paths() { - let libgccjit_target_lib_file = - make_target_lib_path(path, &sess.target.llvm_target).join("libgccjit.so"); + let libgccjit_target_lib_file = file_path(path, sess); if let Ok(true) = fs::exists(&libgccjit_target_lib_file) { load_libgccjit_if_needed(&libgccjit_target_lib_file); break; } } + if !gccjit::is_loaded() { + let mut paths = vec![]; + for path in sess.opts.sysroot.all_paths() { + let libgccjit_target_lib_file = file_path(path, sess); + paths.push(libgccjit_target_lib_file); + } + + panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths); + } + #[cfg(feature = "master")] { let target_cpu = target_cpu(sess); From 15e3955f3a6e38cf8cd4f678b588609c0617b2a1 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Thu, 11 Dec 2025 14:26:57 -0500 Subject: [PATCH 3/6] Document new gcc.download-ci-gcc option --- .../src/tests/codegen-backend-tests/cg_gcc.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/tests/codegen-backend-tests/cg_gcc.md b/src/doc/rustc-dev-guide/src/tests/codegen-backend-tests/cg_gcc.md index 4325cc58797f8..7a1c5b9a98c03 100644 --- a/src/doc/rustc-dev-guide/src/tests/codegen-backend-tests/cg_gcc.md +++ b/src/doc/rustc-dev-guide/src/tests/codegen-backend-tests/cg_gcc.md @@ -77,6 +77,26 @@ will be downloaded from CI or built locally. The default value is `true`, which will download GCC from CI if there are no local changes to the GCC sources and the given host target is available on CI. +## Providing your own GCC + +There are cases where you will want to provide yourself the `libgccjit.so` file. +One such case is when you want to cross-compile `rustc` to another target since GCC is not a multi-target compiler. +To support this use case, there is the bootstrap option `gcc.libgccjit-libs-dir`. +This option override `gcc.download-ci-gcc`, meaning `libgccjit.so` won't be downloaded or built locally by bootstrap. +The directory structure of this directory is `//libgccjit.so`, for instance: + +``` +. +├── m68k-unknown-linux-gnu +│ └── m68k-unknown-linux-gnu +│ └── libgccjit.so +└── x86_64-unknown-linux-gnu + ├── m68k-unknown-linux-gnu + │ └── libgccjit.so + └── x86_64-unknown-linux-gnu + └── libgccjit.so +``` + ## Running tests of the backend itself In addition to running the compiler's test suites using the GCC codegen backend, you can also run the test suite of the backend itself. From 8f035402e074a285af3407b2989234bb7081d7ce Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Thu, 11 Dec 2025 14:27:29 -0500 Subject: [PATCH 4/6] Add new ChangeInfo for the new option gcc.libgccjit-libs-dir --- src/bootstrap/src/utils/change_tracker.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 37ad5f09897fa..d7990c2316d15 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -601,4 +601,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "New options `rust.rustflags` for all targets and per-target `rustflags` that will pass specified flags to rustc for all stages. Target-specific flags override global `rust.rustflags` ones.", }, + ChangeInfo { + change_id: 149354, + severity: ChangeSeverity::Info, + summary: "New option `gcc.libgccjit-libs-dir` to specify which libgccjit.so to use per target.", + }, ]; From b98a91e6ca1a5ab38eef810922b54405f0a2a5b8 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Sun, 14 Dec 2025 08:09:34 -0500 Subject: [PATCH 5/6] Error out if a GCC cross-compiler cannot be found --- src/bootstrap/src/core/build_steps/gcc.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bootstrap/src/core/build_steps/gcc.rs b/src/bootstrap/src/core/build_steps/gcc.rs index 9cc5d0ed71b16..648a2bea41819 100644 --- a/src/bootstrap/src/core/build_steps/gcc.rs +++ b/src/bootstrap/src/core/build_steps/gcc.rs @@ -217,6 +217,15 @@ pub fn get_gcc_build_status(builder: &Builder<'_>, target_pair: GccTargetPair) - "libgccjit.so for `{target_pair}` was not found at `{}`", path.display() )); + + if target_pair.host != target_pair.target || target_pair.host != builder.host_target { + eprintln!( + "info: libgccjit.so for `{target_pair}` was not found at `{}`", + path.display() + ); + eprintln!("error: we do not support downloading or building a GCC cross-compiler"); + std::process::exit(1); + } } } From b3c1dff330f82827e823e8e7b8572562e8d5dc56 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Mon, 15 Dec 2025 14:46:15 -0500 Subject: [PATCH 6/6] Open bootstrap.example.toml with the utf-8 encoding --- src/bootstrap/configure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py index 1915986be28c5..e3895d4e0a189 100755 --- a/src/bootstrap/configure.py +++ b/src/bootstrap/configure.py @@ -593,7 +593,7 @@ def parse_example_config(known_args, config): top_level_keys = [] comment_lines = [] - with open(rust_dir + "/bootstrap.example.toml") as example_config: + with open(rust_dir + "/bootstrap.example.toml", encoding="utf-8") as example_config: example_lines = example_config.read().split("\n") for line in example_lines: if line.count("=") >= 1 and not line.startswith("# "):