Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4d5755f
Add libgccjit-libs-dir config
antoyo Nov 8, 2025
aeafb51
Do not build or download libgccjit if using libgccjit-libs-dir
antoyo Nov 8, 2025
68240b2
Formatting
antoyo Nov 26, 2025
9999b5f
Fix tidy
antoyo Nov 26, 2025
4796e49
Add new config in example
antoyo Nov 26, 2025
8c677b5
Fix clippy warning
antoyo Nov 26, 2025
5ae7550
Remove mut
antoyo Nov 28, 2025
c84f43c
When we cannot load libgccjit.so, show the paths that were tried
antoyo Dec 11, 2025
b08bac2
TO REVERT: Do not remove the libgccjit.so for other targets from the …
antoyo Dec 11, 2025
1b55922
Add missing target field
antoyo Dec 11, 2025
599127c
Fix path to libgccjit.so
antoyo Dec 11, 2025
b056d3e
Document new gcc.download-ci-gcc option
antoyo Dec 11, 2025
9336a4b
Add missing gcc. prefix
antoyo Dec 11, 2025
a0b3cb3
Add new ChangeInfo for the new option gcc.libgccjit-libs-dir
antoyo Dec 11, 2025
200f782
Address review
antoyo Dec 11, 2025
dc178b5
Fix clippy warnings
antoyo Dec 11, 2025
0ded41b
Revert back flags shuffling
antoyo Dec 12, 2025
989f4e0
Revert back debugging stuff
antoyo Dec 12, 2025
3838fcf
Do not use `GccCiMode` for the libs directory
Kobzol Dec 12, 2025
58721a9
Explicitly model that a single `cg_gcc` might need a bunch of `libgcc…
Kobzol Dec 12, 2025
c15d000
Include cg_gcc host target in the list of targets for which we prepar…
Kobzol Dec 12, 2025
7ce220d
Use different sysroot structure
Kobzol Dec 12, 2025
4804055
Fix Clippy
Kobzol Dec 12, 2025
3a374a8
Load the correct libgccjit.so in rustc_codegen_gcc
antoyo Dec 12, 2025
d50630d
Explicitly model GCC target pairs
Kobzol Dec 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions bootstrap.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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-libs-dir>/<host>/<target>/libgccjit.so`.
# For example:
#
# ```
# <libgccjit-libs-dir>
# ├── 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
# =============================================================================
Expand Down
25 changes: 22 additions & 3 deletions compiler/rustc_codegen_gcc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
182 changes: 161 additions & 21 deletions src/bootstrap/src/core/build_steps/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! goes along from the output of the previous stage.

use std::borrow::Cow;
use std::collections::HashSet;
use std::collections::{BTreeMap, HashSet};
use std::ffi::OsStr;
use std::io::BufReader;
use std::io::prelude::*;
Expand All @@ -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;
Expand Down Expand Up @@ -1576,17 +1576,94 @@ 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<GccTargetPair, GccOutput>,
host_pair: GccTargetPair,
}

impl GccDylibSet {
/// Returns the libgccjit.so dylib that corresponds to a host target on which `cg_gcc` will be
/// executed.
fn host_dylib(&self) -> &GccOutput {
self.dylibs.get(&self.host_pair).unwrap_or_else(|| {
panic!("libgccjit.so was not build for host target {}", self.host_pair)
})
}

/// Install the libgccjit dylibs to the corresponding target directories of the given compiler.
pub fn install_to(&self, builder: &Builder<'_>, compiler: Compiler) {
if builder.config.dry_run() {
return;
}

// <rustc>/lib/<host-target>/codegen-backends
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember @bjorn3 had some concerns about a similar directory structure.
@bjorn3: Could you please confirm that this won't result in having multiple directories in /usr/lib with different target triples when doing a global install?

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())
);

// <cg-sysroot>/lib/<target>/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.build_compiler.host`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GccCodegenBackend {
compilers: RustcPrivateCompilers,
targets: Vec<TargetSelection>,
}

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<TargetSelection>,
) -> Self {
// Sort targets to improve step cache hits
targets.sort();
Self { compilers, targets }
}
}

impl Step for GccCodegenBackend {
Expand All @@ -1599,23 +1676,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_cross_build(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");
Expand All @@ -1625,29 +1713,29 @@ 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(
builder,
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,
}
}

Expand Down Expand Up @@ -2324,12 +2412,64 @@ 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)
//
// Note that this set of targets is *maximal*, in reality we might need
// less libgccjits at this current build stage. So below we try to determine
// which target pairs we actually need at this stage.

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,
}
Expand Down
6 changes: 4 additions & 2 deletions src/bootstrap/src/core/build_steps/dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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()
}

Expand Down
Loading
Loading