Skip to content

Commit cec7008

Browse files
committed
Auto merge of #149354 - antoyo:bootstrap-config/libgccjit-libs-dir, r=Kobzol
Bootstrap config: libgccjit libs dir r? `@Kobzol`
2 parents 21ff67d + b3c1dff commit cec7008

File tree

12 files changed

+370
-83
lines changed

12 files changed

+370
-83
lines changed

bootstrap.example.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,31 @@
191191
# Currently, this is only supported for the `x86_64-unknown-linux-gnu` target.
192192
#gcc.download-ci-gcc = false
193193

194+
# Provide a directory of prebuilt libgccjit.so dylibs for given (host, target) compilation pairs.
195+
# This is useful when you want to cross-compile `rustc` to another target since GCC is not a
196+
# multi-target compiler.
197+
# You have to use a directory structure that looks like this:
198+
# `<libgccjit-libs-dir>/<host>/<target>/libgccjit.so`.
199+
# For example:
200+
#
201+
# ```
202+
# <libgccjit-libs-dir>
203+
# ├── m68k-unknown-linux-gnu
204+
# │ └── m68k-unknown-linux-gnu
205+
# │ └── libgccjit.so
206+
# └── x86_64-unknown-linux-gnu
207+
# ├── m68k-unknown-linux-gnu
208+
# │ └── libgccjit.so
209+
# └── x86_64-unknown-linux-gnu
210+
# └── libgccjit.so
211+
# ```
212+
# The directory above would allow you to cross-compile rustc from x64 to m68k
213+
#
214+
# Note that this option has priority over `gcc.download-ci-gcc`.
215+
# If you set both, bootstrap will first try to load libgccjit.so from this directory.
216+
# Only if it isn't found, it will try to download it from CI or build it locally.
217+
#gcc.libgccjit-libs-dir = "/path/to/libgccjit-libs-dir"
218+
194219
# =============================================================================
195220
# General build configuration options
196221
# =============================================================================

compiler/rustc_codegen_gcc/src/lib.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ use rustc_middle::ty::TyCtxt;
9898
use rustc_middle::util::Providers;
9999
use rustc_session::Session;
100100
use rustc_session::config::{OptLevel, OutputFilenames};
101-
use rustc_session::filesearch::make_target_lib_path;
102101
use rustc_span::Symbol;
103102
use rustc_target::spec::{Arch, RelocModel};
104103
use tempfile::TempDir;
@@ -207,18 +206,38 @@ impl CodegenBackend for GccCodegenBackend {
207206
}
208207

209208
fn init(&self, sess: &Session) {
209+
fn file_path(sysroot_path: &Path, sess: &Session) -> PathBuf {
210+
let rustlib_path =
211+
rustc_target::relative_target_rustlib_path(sysroot_path, &sess.host.llvm_target);
212+
sysroot_path
213+
.join(rustlib_path)
214+
.join("codegen-backends")
215+
.join("lib")
216+
.join(sess.target.llvm_target.as_ref())
217+
.join("libgccjit.so")
218+
}
219+
210220
// We use all_paths() instead of only path() in case the path specified by --sysroot is
211221
// invalid.
212222
// This is the case for instance in Rust for Linux where they specify --sysroot=/dev/null.
213223
for path in sess.opts.sysroot.all_paths() {
214-
let libgccjit_target_lib_file =
215-
make_target_lib_path(path, &sess.target.llvm_target).join("libgccjit.so");
224+
let libgccjit_target_lib_file = file_path(path, sess);
216225
if let Ok(true) = fs::exists(&libgccjit_target_lib_file) {
217226
load_libgccjit_if_needed(&libgccjit_target_lib_file);
218227
break;
219228
}
220229
}
221230

231+
if !gccjit::is_loaded() {
232+
let mut paths = vec![];
233+
for path in sess.opts.sysroot.all_paths() {
234+
let libgccjit_target_lib_file = file_path(path, sess);
235+
paths.push(libgccjit_target_lib_file);
236+
}
237+
238+
panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths);
239+
}
240+
222241
#[cfg(feature = "master")]
223242
{
224243
let target_cpu = target_cpu(sess);

src/bootstrap/configure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ def parse_example_config(known_args, config):
593593
top_level_keys = []
594594
comment_lines = []
595595

596-
with open(rust_dir + "/bootstrap.example.toml") as example_config:
596+
with open(rust_dir + "/bootstrap.example.toml", encoding="utf-8") as example_config:
597597
example_lines = example_config.read().split("\n")
598598
for line in example_lines:
599599
if line.count("=") >= 1 and not line.startswith("# "):

src/bootstrap/src/core/build_steps/compile.rs

Lines changed: 166 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//! goes along from the output of the previous stage.
88
99
use std::borrow::Cow;
10-
use std::collections::{HashMap, HashSet};
10+
use std::collections::{BTreeMap, HashMap, HashSet};
1111
use std::ffi::OsStr;
1212
use std::io::BufReader;
1313
use std::io::prelude::*;
@@ -19,7 +19,7 @@ use serde_derive::Deserialize;
1919
#[cfg(feature = "tracing")]
2020
use tracing::span;
2121

22-
use crate::core::build_steps::gcc::{Gcc, GccOutput, add_cg_gcc_cargo_flags};
22+
use crate::core::build_steps::gcc::{Gcc, GccOutput, GccTargetPair, add_cg_gcc_cargo_flags};
2323
use crate::core::build_steps::tool::{RustcPrivateCompilers, SourceType, copy_lld_artifacts};
2424
use crate::core::build_steps::{dist, llvm};
2525
use crate::core::builder;
@@ -1576,17 +1576,98 @@ impl Step for RustcLink {
15761576
}
15771577
}
15781578

1579+
/// Set of `libgccjit` dylibs that can be used by `cg_gcc` to compile code for a set of targets.
1580+
#[derive(Clone)]
1581+
pub struct GccDylibSet {
1582+
dylibs: BTreeMap<GccTargetPair, GccOutput>,
1583+
host_pair: GccTargetPair,
1584+
}
1585+
1586+
impl GccDylibSet {
1587+
/// Returns the libgccjit.so dylib that corresponds to a host target on which `cg_gcc` will be
1588+
/// executed, and which will target the host. So e.g. if `cg_gcc` will be executed on
1589+
/// x86_64-unknown-linux-gnu, the host dylib will be for compilation pair
1590+
/// `(x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu)`.
1591+
fn host_dylib(&self) -> &GccOutput {
1592+
self.dylibs.get(&self.host_pair).unwrap_or_else(|| {
1593+
panic!("libgccjit.so was not built for host target {}", self.host_pair)
1594+
})
1595+
}
1596+
1597+
/// Install the libgccjit dylibs to the corresponding target directories of the given compiler.
1598+
/// cg_gcc know how to search for the libgccjit dylibs in these directories, according to the
1599+
/// (host, target) pair that is being compiled by rustc and cg_gcc.
1600+
pub fn install_to(&self, builder: &Builder<'_>, compiler: Compiler) {
1601+
if builder.config.dry_run() {
1602+
return;
1603+
}
1604+
1605+
// <rustc>/lib/<host-target>/codegen-backends
1606+
let cg_sysroot = builder.sysroot_codegen_backends(compiler);
1607+
1608+
for (target_pair, libgccjit) in &self.dylibs {
1609+
assert_eq!(
1610+
target_pair.host(),
1611+
compiler.host,
1612+
"Trying to install libgccjit ({target_pair}) to a compiler with a different host ({})",
1613+
compiler.host
1614+
);
1615+
let libgccjit = libgccjit.libgccjit();
1616+
let target_filename = libgccjit.file_name().unwrap().to_str().unwrap();
1617+
1618+
// If we build libgccjit ourselves, then `libgccjit` can actually be a symlink.
1619+
// In that case, we have to resolve it first, otherwise we'd create a symlink to a
1620+
// symlink, which wouldn't work.
1621+
let actual_libgccjit_path = t!(
1622+
libgccjit.canonicalize(),
1623+
format!("Cannot find libgccjit at {}", libgccjit.display())
1624+
);
1625+
1626+
// <cg-sysroot>/lib/<target>/libgccjit.so
1627+
let dest_dir = cg_sysroot.join("lib").join(target_pair.target());
1628+
t!(fs::create_dir_all(&dest_dir));
1629+
let dst = dest_dir.join(target_filename);
1630+
builder.copy_link(&actual_libgccjit_path, &dst, FileType::NativeLibrary);
1631+
}
1632+
}
1633+
}
1634+
15791635
/// Output of the `compile::GccCodegenBackend` step.
1580-
/// It includes the path to the libgccjit library on which this backend depends.
1636+
///
1637+
/// It contains paths to all built libgccjit libraries on which this backend depends here.
15811638
#[derive(Clone)]
15821639
pub struct GccCodegenBackendOutput {
15831640
stamp: BuildStamp,
1584-
gcc: GccOutput,
1641+
dylib_set: GccDylibSet,
15851642
}
15861643

1644+
/// Builds the GCC codegen backend (`cg_gcc`).
1645+
/// The `cg_gcc` backend uses `libgccjit`, which requires a separate build for each
1646+
/// `host -> target` pair. So if you are on linux-x64 and build for linux-aarch64,
1647+
/// you will need at least:
1648+
/// - linux-x64 -> linux-x64 libgccjit (for building host code like proc macros)
1649+
/// - linux-x64 -> linux-aarch64 libgccjit (for the aarch64 target code)
1650+
///
1651+
/// We model this by having a single cg_gcc for a given host target, which contains one
1652+
/// libgccjit per (host, target) pair.
1653+
/// Note that the host target is taken from `self.compilers.target_compiler.host`.
15871654
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15881655
pub struct GccCodegenBackend {
15891656
compilers: RustcPrivateCompilers,
1657+
targets: Vec<TargetSelection>,
1658+
}
1659+
1660+
impl GccCodegenBackend {
1661+
/// Build `cg_gcc` that will run on host `H` (`compilers.target_compiler.host`) and will be
1662+
/// able to produce code target pairs (`H`, `T`) for all `T` from `targets`.
1663+
pub fn for_targets(
1664+
compilers: RustcPrivateCompilers,
1665+
mut targets: Vec<TargetSelection>,
1666+
) -> Self {
1667+
// Sort targets to improve step cache hits
1668+
targets.sort();
1669+
Self { compilers, targets }
1670+
}
15901671
}
15911672

15921673
impl Step for GccCodegenBackend {
@@ -1599,23 +1680,34 @@ impl Step for GccCodegenBackend {
15991680
}
16001681

16011682
fn make_run(run: RunConfig<'_>) {
1602-
run.builder.ensure(GccCodegenBackend {
1603-
compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target),
1604-
});
1683+
// By default, build cg_gcc that will only be able to compile native code for the given
1684+
// host target.
1685+
let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target);
1686+
run.builder.ensure(GccCodegenBackend { compilers, targets: vec![run.target] });
16051687
}
16061688

16071689
fn run(self, builder: &Builder<'_>) -> Self::Output {
1608-
let target = self.compilers.target();
1690+
let host = self.compilers.target();
16091691
let build_compiler = self.compilers.build_compiler();
16101692

16111693
let stamp = build_stamp::codegen_backend_stamp(
16121694
builder,
16131695
build_compiler,
1614-
target,
1696+
host,
16151697
&CodegenBackendKind::Gcc,
16161698
);
16171699

1618-
let gcc = builder.ensure(Gcc { target });
1700+
let dylib_set = GccDylibSet {
1701+
dylibs: self
1702+
.targets
1703+
.iter()
1704+
.map(|&target| {
1705+
let target_pair = GccTargetPair::for_target_pair(host, target);
1706+
(target_pair, builder.ensure(Gcc { target_pair }))
1707+
})
1708+
.collect(),
1709+
host_pair: GccTargetPair::for_native_build(host),
1710+
};
16191711

16201712
if builder.config.keep_stage.contains(&build_compiler.stage) {
16211713
trace!("`keep-stage` requested");
@@ -1625,29 +1717,29 @@ impl Step for GccCodegenBackend {
16251717
);
16261718
// Codegen backends are linked separately from this step today, so we don't do
16271719
// anything here.
1628-
return GccCodegenBackendOutput { stamp, gcc };
1720+
return GccCodegenBackendOutput { stamp, dylib_set };
16291721
}
16301722

16311723
let mut cargo = builder::Cargo::new(
16321724
builder,
16331725
build_compiler,
16341726
Mode::Codegen,
16351727
SourceType::InTree,
1636-
target,
1728+
host,
16371729
Kind::Build,
16381730
);
16391731
cargo.arg("--manifest-path").arg(builder.src.join("compiler/rustc_codegen_gcc/Cargo.toml"));
1640-
rustc_cargo_env(builder, &mut cargo, target);
1732+
rustc_cargo_env(builder, &mut cargo, host);
16411733

1642-
add_cg_gcc_cargo_flags(&mut cargo, &gcc);
1734+
add_cg_gcc_cargo_flags(&mut cargo, dylib_set.host_dylib());
16431735

16441736
let _guard =
1645-
builder.msg(Kind::Build, "codegen backend gcc", Mode::Codegen, build_compiler, target);
1737+
builder.msg(Kind::Build, "codegen backend gcc", Mode::Codegen, build_compiler, host);
16461738
let files = run_cargo(builder, cargo, vec![], &stamp, vec![], false, false);
16471739

16481740
GccCodegenBackendOutput {
16491741
stamp: write_codegen_backend_stamp(stamp, files, builder.config.dry_run()),
1650-
gcc,
1742+
dylib_set,
16511743
}
16521744
}
16531745

@@ -2324,12 +2416,65 @@ impl Step for Assemble {
23242416
copy_codegen_backends_to_sysroot(builder, stamp, target_compiler);
23252417
}
23262418
CodegenBackendKind::Gcc => {
2327-
let output =
2328-
builder.ensure(GccCodegenBackend { compilers: prepare_compilers() });
2419+
// We need to build cg_gcc for the host target of the compiler which we
2420+
// build here, which is `target_compiler`.
2421+
// But we also need to build libgccjit for some additional targets, in
2422+
// the most general case.
2423+
// 1. We need to build (target_compiler.host, stdlib target) libgccjit
2424+
// for all stdlibs that we build, so that cg_gcc can be used to build code
2425+
// for all those targets.
2426+
// 2. We need to build (target_compiler.host, target_compiler.host)
2427+
// libgccjit, so that the target compiler can compile host code (e.g. proc
2428+
// macros).
2429+
// 3. We need to build (target_compiler.host, host target) libgccjit
2430+
// for all *host targets* that we build, so that cg_gcc can be used to
2431+
// build a (possibly cross-compiled) stage 2+ rustc.
2432+
//
2433+
// Assume that we are on host T1 and we do a stage2 build of rustc for T2.
2434+
// We want the T2 rustc compiler to be able to use cg_gcc and build code
2435+
// for T2 (host) and T3 (target). We also want to build the stage2 compiler
2436+
// itself using cg_gcc.
2437+
// This could correspond to the following bootstrap invocation:
2438+
// `x build rustc --build T1 --host T2 --target T3 --set codegen-backends=['gcc', 'llvm']`
2439+
//
2440+
// For that, we will need the following GCC target pairs:
2441+
// 1. T1 -> T2 (to cross-compile a T2 rustc using cg_gcc running on T1)
2442+
// 2. T2 -> T2 (to build host code with the stage 2 rustc running on T2)
2443+
// 3. T2 -> T3 (to cross-compile code with the stage 2 rustc running on T2)
2444+
//
2445+
// FIXME: this set of targets is *maximal*, in reality we might need
2446+
// less libgccjits at this current build stage. Try to reduce the set of
2447+
// GCC dylibs built below by taking a look at the current stage and whether
2448+
// cg_gcc is used as the default codegen backend.
2449+
2450+
let compilers = prepare_compilers();
2451+
2452+
// The left side of the target pairs below is implied. It has to match the
2453+
// host target on which cg_gcc will run, which is the host target of
2454+
// `target_compiler`. We only pass the right side of the target pairs to
2455+
// the `GccCodegenBackend` constructor.
2456+
let mut targets = HashSet::new();
2457+
// Add all host targets, so that we are able to build host code in this
2458+
// bootstrap invocation using cg_gcc.
2459+
for target in &builder.hosts {
2460+
targets.insert(*target);
2461+
}
2462+
// Add all stdlib targets, so that the built rustc can produce code for them
2463+
for target in &builder.targets {
2464+
targets.insert(*target);
2465+
}
2466+
// Add the host target of the built rustc itself, so that it can build
2467+
// host code (e.g. proc macros) using cg_gcc.
2468+
targets.insert(compilers.target_compiler().host);
2469+
2470+
let output = builder.ensure(GccCodegenBackend::for_targets(
2471+
compilers,
2472+
targets.into_iter().collect(),
2473+
));
23292474
copy_codegen_backends_to_sysroot(builder, output.stamp, target_compiler);
2330-
// Also copy libgccjit to the library sysroot, so that it is available for
2331-
// the codegen backend.
2332-
output.gcc.install_to(builder, &rustc_libdir);
2475+
// Also copy all requires libgccjit dylibs to the corresponding
2476+
// library sysroots, so that they are available for the codegen backend.
2477+
output.dylib_set.install_to(builder, target_compiler);
23332478
}
23342479
CodegenBackendKind::Llvm | CodegenBackendKind::Custom(_) => continue,
23352480
}

src/bootstrap/src/core/build_steps/dist.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use tracing::instrument;
2121

2222
use crate::core::build_steps::compile::{get_codegen_backend_file, normalize_codegen_backend_name};
2323
use crate::core::build_steps::doc::DocumentationFormat;
24+
use crate::core::build_steps::gcc::GccTargetPair;
2425
use crate::core::build_steps::tool::{
2526
self, RustcPrivateCompilers, ToolTargetBuildMode, get_tool_target_compiler,
2627
};
@@ -2856,8 +2857,9 @@ impl Step for Gcc {
28562857

28572858
fn run(self, builder: &Builder<'_>) -> Self::Output {
28582859
let tarball = Tarball::new(builder, "gcc", &self.target.triple);
2859-
let output = builder.ensure(super::gcc::Gcc { target: self.target });
2860-
tarball.add_file(&output.libgccjit, "lib", FileType::NativeLibrary);
2860+
let output = builder
2861+
.ensure(super::gcc::Gcc { target_pair: GccTargetPair::for_native_build(self.target) });
2862+
tarball.add_file(output.libgccjit(), "lib", FileType::NativeLibrary);
28612863
tarball.generate()
28622864
}
28632865

0 commit comments

Comments
 (0)