From f3b16d833f816d37213e6a1a839d34ceeb97d430 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Mon, 22 Dec 2025 11:20:15 -0800 Subject: [PATCH] Change how we build offload as a single Step --- bootstrap.example.toml | 3 + src/bootstrap/configure.py | 5 + src/bootstrap/src/core/build_steps/compile.rs | 22 ++- src/bootstrap/src/core/build_steps/llvm.rs | 185 ++++++++++++++++-- src/bootstrap/src/core/config/config.rs | 3 + src/bootstrap/src/core/config/toml/llvm.rs | 2 + src/bootstrap/src/lib.rs | 4 + src/bootstrap/src/utils/change_tracker.rs | 5 + 8 files changed, 215 insertions(+), 14 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 63bf50722ca0..e0cbb0c0e747 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -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. diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py index e3895d4e0a18..fda8f004eff4 100755 --- a/src/bootstrap/configure.py +++ b/src/bootstrap/configure.py @@ -120,6 +120,11 @@ o("optimize-llvm", "llvm.optimize", "build optimized LLVM") 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( diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index ed154133dec2..d6aae06d089d 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -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 @@ -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!( diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 8bf8f6f56c73..c3935d9810e9 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -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}; @@ -473,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(); @@ -917,6 +908,175 @@ fn get_var(var_base: &str, host: &str, target: &str) -> Option { .or_else(|| env::var_os(var_base)) } +#[derive(Clone)] +pub struct BuiltOmpOffload { + /// Path to the omp and offload dylibs. + offload: Vec, +} + +impl BuiltOmpOffload { + pub fn offload_paths(&self) -> Vec { + 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 folder, instead of + // using the llvm build dir. + let out_dir = builder.offload_out(target); + + 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 = 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 . + // files. + if !p.exists() { + eprintln!( + "`{p:?}` not found in `{}`. Either the build has failed or Offload 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, @@ -991,11 +1151,10 @@ impl Step for Enzyme { builder.config.update_submodule("src/tools/enzyme"); let mut cfg = cmake::Config::new(builder.src.join("src/tools/enzyme/enzyme/")); - - let mut cflags = CcFlags::default(); - // Enzyme devs maintain upstream compability, but only fix deprecations when they are about + // Enzyme devs maintain upstream compatibility, but only fix deprecations when they are about // to turn into a hard error. As such, Enzyme generates various warnings which could make it // hard to spot more relevant issues. + let mut cflags = CcFlags::default(); cflags.push_all("-Wno-deprecated"); configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), cflags, &[]); diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index a81b8ccb9140..0a329c93bc7d 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -169,6 +169,7 @@ pub struct Config { pub llvm_link_jobs: Option, pub llvm_version_suffix: Option, pub llvm_use_linker: Option, + pub llvm_clang_dir: Option, pub llvm_allow_old_toolchain: bool, pub llvm_polly: bool, pub llvm_clang: bool, @@ -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, @@ -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), diff --git a/src/bootstrap/src/core/config/toml/llvm.rs b/src/bootstrap/src/core/config/toml/llvm.rs index 9751837a8879..5f08884e4ef7 100644 --- a/src/bootstrap/src/core/config/toml/llvm.rs +++ b/src/bootstrap/src/core/config/toml/llvm.rs @@ -35,6 +35,7 @@ define_config! { allow_old_toolchain: Option = "allow-old-toolchain", offload: Option = "offload", polly: Option = "polly", + offload_clang_dir: Option = "offload-clang-dir", clang: Option = "clang", enable_warnings: Option = "enable-warnings", download_ci_llvm: Option = "download-ci-llvm", @@ -112,6 +113,7 @@ pub fn check_incompatible_options_for_ci_llvm( use_linker, allow_old_toolchain, offload, + offload_clang_dir: _, polly, clang, enable_warnings, diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index adae9a1b8b08..be459653cd65 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -975,6 +975,10 @@ impl Build { self.out.join(&*target.triple).join("enzyme") } + fn offload_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("offload") + } + fn lld_out(&self, target: TargetSelection) -> PathBuf { self.out.join(target).join("lld") } diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index d7990c2316d1..1077077cb97a 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -606,4 +606,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "New option `gcc.libgccjit-libs-dir` to specify which libgccjit.so to use per target.", }, + ChangeInfo { + change_id: 148671, + severity: ChangeSeverity::Info, + summary: "New option `llvm.offload-clang-dir` to allow building an in-tree llvm offload and openmp runtime with an external clang.", + }, ];