Auto merge of #149354 - antoyo:bootstrap-config/libgccjit-libs-dir, r=Kobzol

Bootstrap config: libgccjit libs dir

r? `@Kobzol`
This commit is contained in:
bors 2025-12-15 23:11:45 +00:00
commit cec70080fd
12 changed files with 370 additions and 83 deletions

View file

@ -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
# =============================================================================

View file

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

View file

@ -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("# "):

View file

@ -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<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, 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;
}
// <rustc>/lib/<host-target>/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())
);
// <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.target_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 {
@ -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,
}

View file

@ -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()
}

View file

@ -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<PathBuf> {
fn try_download_gcc(builder: &Builder<'_>, target_pair: GccTargetPair) -> Option<PathBuf> {
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<Pa
match source {
PathFreshness::LastModifiedUpstream { upstream } => {
// 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<Pa
}
#[cfg(test)]
fn try_download_gcc(_builder: &Builder<'_>, _target: TargetSelection) -> Option<PathBuf> {
fn try_download_gcc(_builder: &Builder<'_>, _target_pair: GccTargetPair) -> Option<PathBuf> {
None
}
@ -167,11 +204,37 @@ 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 <root>/<host>/<target>/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 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);
}
}
}
// 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<String> = OnceLock::new();
let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
generate_smart_stamp_hash(
@ -185,8 +248,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 +278,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 +309,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 +327,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 +336,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

View file

@ -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;
@ -3960,7 +3960,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)
@ -4001,12 +4001,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);

View file

@ -188,6 +188,7 @@ pub struct Config {
// gcc codegen options
pub gcc_ci_mode: GccCiMode,
pub libgccjit_libs_dir: Option<PathBuf>,
// 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),

View file

@ -15,5 +15,6 @@ define_config! {
#[derive(Default)]
struct Gcc {
download_ci_gcc: Option<bool> = "download-ci-gcc",
libgccjit_libs_dir: Option<PathBuf> = "libgccjit-libs-dir",
}
}

View file

@ -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")
}

View file

@ -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.",
},
];

View file

@ -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 `<host>/<target>/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.