Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions bootstrap.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@
# Whether to build LLVM with support for it's gpu offload runtime.
#llvm.offload = false

# Absolute path to the directory containing ClangConfig.cmake
#llvm.offload-clang-dir = ""

# When true, link libstdc++ statically into the rustc_llvm.
# This is useful if you don't want to use the dynamic version of that
# library provided by LLVM.
Expand Down
5 changes: 5 additions & 0 deletions src/bootstrap/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ def v(*args):
o("llvm-assertions", "llvm.assertions", "build LLVM with assertions")
o("llvm-enzyme", "llvm.enzyme", "build LLVM with enzyme")
o("llvm-offload", "llvm.offload", "build LLVM with gpu offload support")
o(
"llvm-offload-clang-dir",
"llvm.offload-clang-dir",
"pass the absolute directory of ClangConfig.cmake",
)
o("llvm-plugins", "llvm.plugins", "build LLVM with plugin interface")
o("debug-assertions", "rust.debug-assertions", "build with debugging assertions")
o(
Expand Down
22 changes: 21 additions & 1 deletion src/bootstrap/src/core/build_steps/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1427,10 +1427,12 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
if builder.config.llvm_enzyme {
cargo.env("LLVM_ENZYME", "1");
}
let llvm::LlvmResult { host_llvm_config, .. } = builder.ensure(llvm::Llvm { target });
if builder.config.llvm_offload {
builder.ensure(llvm::OmpOffload { target });
cargo.env("LLVM_OFFLOAD", "1");
}
let llvm::LlvmResult { host_llvm_config, .. } = builder.ensure(llvm::Llvm { target });

cargo.env("LLVM_CONFIG", &host_llvm_config);

// Some LLVM linker flags (-L and -l) may be needed to link `rustc_llvm`. Its build script
Expand Down Expand Up @@ -2293,6 +2295,24 @@ impl Step for Assemble {
}
}

if builder.config.llvm_offload && !builder.config.dry_run() {
debug!("`llvm_offload` requested");
let offload_install = builder.ensure(llvm::OmpOffload { target: build_compiler.host });
if let Some(_llvm_config) = builder.llvm_config(builder.config.host_target) {
let target_libdir =
builder.sysroot_target_libdir(target_compiler, target_compiler.host);
for p in offload_install.offload_paths() {
let libname = p.file_name().unwrap();
let dst_lib = target_libdir.join(libname);
builder.resolve_symlink_and_copy(&p, &dst_lib);
}
// FIXME(offload): Add amdgcn-amd-amdhsa and nvptx64-nvidia-cuda folder
// This one is slightly more tricky, since we have the same file twice, in two
// subfolders for amdgcn and nvptx64. We'll likely find two more in the future, once
// Intel and Spir-V support lands in offload.
}
}

// Build the libraries for this compiler to link to (i.e., the libraries
// it uses at runtime).
debug!(
Expand Down
223 changes: 210 additions & 13 deletions src/bootstrap/src/core/build_steps/llvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use std::{env, fs};

use build_helper::exit;
use build_helper::git::PathFreshness;

use crate::core::builder::{Builder, RunConfig, ShouldRun, Step, StepMetadata};
Expand Down Expand Up @@ -74,6 +75,25 @@ struct LdFlags {
module: OsString,
}

/// Allows each step to add C/Cxx flags which are only used for a specific cmake invocation.
#[derive(Debug, Clone, Default)]
struct CcFlags {
///
cflags: OsString,
///
cxxflags: OsString,
}

impl CcFlags {
fn push_all(&mut self, s: impl AsRef<OsStr>) {
let s = s.as_ref();
self.cflags.push(" ");
self.cflags.push(s);
self.cxxflags.push(" ");
self.cxxflags.push(s);
}
}

impl LdFlags {
fn push_all(&mut self, s: impl AsRef<OsStr>) {
let s = s.as_ref();
Expand Down Expand Up @@ -454,16 +474,6 @@ impl Step for Llvm {
enabled_llvm_runtimes.push("compiler-rt");
}

// This is an experimental flag, which likely builds more than necessary.
// We will optimize it when we get closer to releasing it on nightly.
if builder.config.llvm_offload {
enabled_llvm_runtimes.push("offload");
//FIXME(ZuseZ4): LLVM intends to drop the offload dependency on openmp.
//Remove this line once they achieved it.
enabled_llvm_runtimes.push("openmp");
enabled_llvm_projects.push("compiler-rt");
}

if !enabled_llvm_projects.is_empty() {
enabled_llvm_projects.sort();
enabled_llvm_projects.dedup();
Expand Down Expand Up @@ -527,7 +537,7 @@ impl Step for Llvm {
cfg.define("LLVM_VERSION_SUFFIX", suffix);
}

configure_cmake(builder, target, &mut cfg, true, ldflags, &[]);
configure_cmake(builder, target, &mut cfg, true, ldflags, CcFlags::default(), &[]);
configure_llvm(builder, target, &mut cfg);

for (key, val) in &builder.config.llvm_build_config {
Expand Down Expand Up @@ -633,6 +643,7 @@ fn configure_cmake(
cfg: &mut cmake::Config,
use_compiler_launcher: bool,
mut ldflags: LdFlags,
ccflags: CcFlags,
suppressed_compiler_flag_prefixes: &[&str],
) {
// Do not print installation messages for up-to-date files.
Expand Down Expand Up @@ -778,6 +789,11 @@ fn configure_cmake(
.collect::<Vec<String>>()
.join(" ")
.into();
let mut tmp = ccflags.cflags.clone();
tmp.push(" ");
tmp.push(cflags);
cflags = tmp;

if let Some(ref s) = builder.config.llvm_cflags {
cflags.push(" ");
cflags.push(s);
Expand All @@ -789,6 +805,7 @@ fn configure_cmake(
cflags.push(format!(" --target={target}"));
}
cfg.define("CMAKE_C_FLAGS", cflags);

let mut cxxflags: OsString = builder
.cc_handled_clags(target, CLang::Cxx)
.into_iter()
Expand All @@ -801,6 +818,10 @@ fn configure_cmake(
.collect::<Vec<String>>()
.join(" ")
.into();
let mut tmp = ccflags.cxxflags.clone();
tmp.push(" ");
tmp.push(cxxflags);
cxxflags = tmp;
if let Some(ref s) = builder.config.llvm_cxxflags {
cxxflags.push(" ");
cxxflags.push(s);
Expand Down Expand Up @@ -896,6 +917,179 @@ fn get_var(var_base: &str, host: &str, target: &str) -> Option<OsString> {
.or_else(|| env::var_os(var_base))
}

#[derive(Clone)]
pub struct BuiltOmpOffload {
/// Path to the omp and offload dylibs.
offload: Vec<PathBuf>,
}

impl BuiltOmpOffload {
pub fn offload_paths(&self) -> Vec<PathBuf> {
self.offload.clone()
}
}

// FIXME(offload): In an ideal world, we would just enable the offload runtime in our previous LLVM
// build step. For now, we still depend on the openmp runtime since we use some of it's API, so we
// build both. However, when building those runtimes as part of the LLVM step, then LLVM's cmake
// implicitly assumes that Clang has also been build and will try to use it. In the Rust CI, we
// don't always build clang (due to compile times), but instead use a slightly older external clang.
// LLVM tries to remove this build dependency of offload/openmp on Clang for LLVM-22, so in the
// future we might be able to integrate this step into the LLVM step. For now, we instead introduce
// a Clang_DIR bootstrap option, which allows us tell CMake to use an external clang for these two
// runtimes. This external clang will try to use it's own (older) include dirs when building our
// in-tree LLVM submodule, which will cause build failures. To prevent those, we now also
// explicitly set our include dirs.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct OmpOffload {
pub target: TargetSelection,
}

impl Step for OmpOffload {
type Output = BuiltOmpOffload;
const IS_HOST: bool = true;

fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/llvm-project/offload")
}

fn make_run(run: RunConfig<'_>) {
run.builder.ensure(OmpOffload { target: run.target });
}

/// Compile OpenMP offload runtimes for `target`.
#[allow(unused)]
fn run(self, builder: &Builder<'_>) -> Self::Output {
if builder.config.dry_run() {
return BuiltOmpOffload {
offload: vec![builder.config.tempdir().join("llvm-offload-dry-run")],
};
}
let target = self.target;

let LlvmResult { host_llvm_config, llvm_cmake_dir } =
builder.ensure(Llvm { target: self.target });

// Running cmake twice in the same folder is known to cause issues, like deleting existing
// binaries. We therefore write our offload artifacts into it's own subfolder. We use a
// subfolder, so that all the logic that processes our build artifacts (hopefully) also
// automatically manages our artifacts in the subfolder.
let out_dir = builder.llvm_out(target).join("offload-outdir");
Copy link
Member

Choose a reason for hiding this comment

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

Actually, I think that we should use a different build directory, and handle the copying manually in the Assemble step. We should not rely on other code copying the offload stuff for us. Partly also because we will want to have it as a separate component, and not put the offload things into e.g. the rustc or llvm-tools component by accident.

if std::fs::exists(&out_dir).is_ok_and(|x| !x) {
std::fs::create_dir_all(&out_dir).unwrap();
}

let mut files = vec![];
let lib_ext = std::env::consts::DLL_EXTENSION;
files.push(out_dir.join("lib").join("libLLVMOffload").with_extension(lib_ext));
files.push(out_dir.join("lib").join("libomp").with_extension(lib_ext));
files.push(out_dir.join("lib").join("libomptarget").with_extension(lib_ext));

// Offload/OpenMP are just subfolders of LLVM, so we can use the LLVM sha.
static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
generate_smart_stamp_hash(
builder,
&builder.config.src.join("src/llvm-project/offload"),
builder.in_tree_llvm_info.sha().unwrap_or_default(),
)
});
let stamp = BuildStamp::new(&out_dir).with_prefix("offload").add_stamp(smart_stamp_hash);

trace!("checking build stamp to see if we need to rebuild offload/openmp artifacts");
if stamp.is_up_to_date() {
trace!(?out_dir, "offload/openmp build artifacts are up to date");
if stamp.stamp().is_empty() {
builder.info(
"Could not determine the Offload submodule commit hash. \
Assuming that an Offload rebuild is not necessary.",
);
builder.info(&format!(
"To force Offload/OpenMP to rebuild, remove the file `{}`",
stamp.path().display()
));
}
return BuiltOmpOffload { offload: files };
}

trace!(?target, "(re)building offload/openmp artifacts");
builder.info(&format!("Building OpenMP/Offload for {target}"));
t!(stamp.remove());
let _time = helpers::timeit(builder);
t!(fs::create_dir_all(&out_dir));

builder.config.update_submodule("src/llvm-project");
let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/runtimes/"));

// If we use an external clang as opposed to building our own llvm_clang, than that clang will
// come with it's own set of default include directories, which are based on a potentially older
// LLVM. This can cause issues, so we overwrite it to include headers based on our
// `src/llvm-project` submodule instead.
// FIXME(offload): With LLVM-22 we hopefully won't need an external clang anymore.
let mut cflags = CcFlags::default();
if !builder.config.llvm_clang {
let base = builder.llvm_out(target).join("include");
let inc_dir = base.display();
cflags.push_all(format!(" -I {inc_dir}"));
}

configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), cflags, &[]);

// Re-use the same flags as llvm to control the level of debug information
// generated for offload.
let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
(false, _) => "Debug",
(true, false) => "Release",
(true, true) => "RelWithDebInfo",
};
trace!(?profile);

// OpenMP/Offload builds currently (LLVM-21) still depend on Clang, although there are
// intentions to loosen this requirement for LLVM-22. If we were to
let clang_dir = if !builder.config.llvm_clang {
// We must have an external clang to use.
assert!(&builder.build.config.llvm_clang_dir.is_some());
builder.build.config.llvm_clang_dir.clone()
} else {
// No need to specify it, since we use the in-tree clang
None
};

// FIXME(offload): Once we move from OMP to Offload (Ol) APIs, we should drop the openmp
// runtime to simplify our build. We should also re-evaluate the LLVM_Root and try to get
// rid of the Clang_DIR, once we upgrade to LLVM-22.
cfg.out_dir(&out_dir)
.profile(profile)
.env("LLVM_CONFIG_REAL", &host_llvm_config)
.define("LLVM_ENABLE_ASSERTIONS", "ON")
.define("LLVM_ENABLE_RUNTIMES", "openmp;offload")
.define("LLVM_INCLUDE_TESTS", "OFF")
.define("OFFLOAD_INCLUDE_TESTS", "OFF")
.define("OPENMP_STANDALONE_BUILD", "ON")
.define("LLVM_ROOT", builder.llvm_out(target).join("build"))
.define("LLVM_DIR", llvm_cmake_dir);
if let Some(p) = clang_dir {
cfg.define("Clang_DIR", p);
}
cfg.build();

t!(stamp.write());

for p in &files {
// At this point, `out_dir` should contain the built libEnzyme-<LLVM-version>.<dylib-ext>
// file.
if !p.exists() {
eprintln!(
"`{p:?}` not found in `{}`. Either the build has failed or Enzyme was built with a wrong version of LLVM",
out_dir.display()
);
exit!(1);
}
}
BuiltOmpOffload { offload: files }
}
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Enzyme {
pub target: TargetSelection,
Expand Down Expand Up @@ -970,7 +1164,9 @@ impl Step for Enzyme {

builder.config.update_submodule("src/tools/enzyme");
let mut cfg = cmake::Config::new(builder.src.join("src/tools/enzyme/enzyme/"));
configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), &[]);
let mut cflags = CcFlags::default();
cflags.push_all("-Wno-deprecated");
configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), cflags, &[]);

// Re-use the same flags as llvm to control the level of debug information
// generated by Enzyme.
Expand Down Expand Up @@ -1090,7 +1286,7 @@ impl Step for Lld {
ldflags.push_all("-Wl,-rpath,'$ORIGIN/../../../'");
}

configure_cmake(builder, target, &mut cfg, true, ldflags, &[]);
configure_cmake(builder, target, &mut cfg, true, ldflags, CcFlags::default(), &[]);
configure_llvm(builder, target, &mut cfg);

// Re-use the same flags as llvm to control the level of debug information
Expand Down Expand Up @@ -1213,6 +1409,7 @@ impl Step for Sanitizers {
&mut cfg,
use_compiler_launcher,
LdFlags::default(),
CcFlags::default(),
suppressed_compiler_flag_prefixes,
);

Expand Down
3 changes: 3 additions & 0 deletions src/bootstrap/src/core/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ pub struct Config {
pub llvm_link_jobs: Option<u32>,
pub llvm_version_suffix: Option<String>,
pub llvm_use_linker: Option<String>,
pub llvm_clang_dir: Option<PathBuf>,
pub llvm_allow_old_toolchain: bool,
pub llvm_polly: bool,
pub llvm_clang: bool,
Expand Down Expand Up @@ -604,6 +605,7 @@ impl Config {
use_linker: llvm_use_linker,
allow_old_toolchain: llvm_allow_old_toolchain,
offload: llvm_offload,
offload_clang_dir: llvm_clang_dir,
polly: llvm_polly,
clang: llvm_clang,
enable_warnings: llvm_enable_warnings,
Expand Down Expand Up @@ -1361,6 +1363,7 @@ impl Config {
llvm_cflags,
llvm_clang: llvm_clang.unwrap_or(false),
llvm_clang_cl,
llvm_clang_dir: llvm_clang_dir.map(PathBuf::from),
llvm_cxxflags,
llvm_enable_warnings: llvm_enable_warnings.unwrap_or(false),
llvm_enzyme: llvm_enzyme.unwrap_or(false),
Expand Down
Loading