Skip to content
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
# ```
Comment on lines +201 to +211
Copy link
Member

Choose a reason for hiding this comment

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

maybe python does not like these?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we need to add encoding="utf-8" to the call to open?

Copy link
Member

Choose a reason for hiding this comment

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

Let's try it.

Copy link
Member

Choose a reason for hiding this comment

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

Let's try it.

# 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
2 changes: 1 addition & 1 deletion src/bootstrap/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("# "):
Expand Down
187 changes: 166 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::{HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, 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,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 {
Expand All @@ -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");
Expand All @@ -1625,29 +1717,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 +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,
}
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