Skip to content

Commit ac1585f

Browse files
committed
Change how we build offload as a single Step
1 parent e8a6312 commit ac1585f

File tree

8 files changed

+233
-13
lines changed

8 files changed

+233
-13
lines changed

bootstrap.example.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@
106106
# Whether to build LLVM with support for it's gpu offload runtime.
107107
#llvm.offload = false
108108

109+
# Absolute path to the directory containing ClangConfig.cmake
110+
#llvm.offload-clang-dir = ""
111+
109112
# When true, link libstdc++ statically into the rustc_llvm.
110113
# This is useful if you don't want to use the dynamic version of that
111114
# library provided by LLVM.

src/bootstrap/configure.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ def v(*args):
120120
o("llvm-assertions", "llvm.assertions", "build LLVM with assertions")
121121
o("llvm-enzyme", "llvm.enzyme", "build LLVM with enzyme")
122122
o("llvm-offload", "llvm.offload", "build LLVM with gpu offload support")
123+
o(
124+
"llvm-offload-clang-dir",
125+
"llvm.offload-clang-dir",
126+
"pass the absolute directory of ClangConfig.cmake",
127+
)
123128
o("llvm-plugins", "llvm.plugins", "build LLVM with plugin interface")
124129
o("debug-assertions", "rust.debug-assertions", "build with debugging assertions")
125130
o(

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1427,10 +1427,12 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
14271427
if builder.config.llvm_enzyme {
14281428
cargo.env("LLVM_ENZYME", "1");
14291429
}
1430+
let llvm::LlvmResult { host_llvm_config, .. } = builder.ensure(llvm::Llvm { target });
14301431
if builder.config.llvm_offload {
1432+
builder.ensure(llvm::OmpOffload { target });
14311433
cargo.env("LLVM_OFFLOAD", "1");
14321434
}
1433-
let llvm::LlvmResult { host_llvm_config, .. } = builder.ensure(llvm::Llvm { target });
1435+
14341436
cargo.env("LLVM_CONFIG", &host_llvm_config);
14351437

14361438
// 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 {
22932295
}
22942296
}
22952297

2298+
if builder.config.llvm_offload && !builder.config.dry_run() {
2299+
debug!("`llvm_offload` requested");
2300+
let offload_install = builder.ensure(llvm::OmpOffload { target: build_compiler.host });
2301+
if let Some(_llvm_config) = builder.llvm_config(builder.config.host_target) {
2302+
let target_libdir =
2303+
builder.sysroot_target_libdir(target_compiler, target_compiler.host);
2304+
for p in offload_install.offload_paths() {
2305+
let libname = p.file_name().unwrap();
2306+
let dst_lib = target_libdir.join(libname);
2307+
builder.resolve_symlink_and_copy(&p, &dst_lib);
2308+
}
2309+
// FIXME(offload): Add amdgcn-amd-amdhsa and nvptx64-nvidia-cuda folder
2310+
// This one is slightly more tricky, since we have the same file twice, in two
2311+
// subfolders for amdgcn and nvptx64. We'll likely find two more in the future, once
2312+
// Intel and Spir-V support lands in offload.
2313+
}
2314+
}
2315+
22962316
// Build the libraries for this compiler to link to (i.e., the libraries
22972317
// it uses at runtime).
22982318
debug!(

src/bootstrap/src/core/build_steps/llvm.rs

Lines changed: 190 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::path::{Path, PathBuf};
1414
use std::sync::OnceLock;
1515
use std::{env, fs};
1616

17+
use build_helper::exit;
1718
use build_helper::git::PathFreshness;
1819

1920
use crate::core::builder::{Builder, RunConfig, ShouldRun, Step, StepMetadata};
@@ -93,6 +94,25 @@ struct LdFlags {
9394
module: OsString,
9495
}
9596

97+
/// Allows each step to add C/Cxx flags which are only used for a specific cmake invocation.
98+
#[derive(Debug, Clone, Default)]
99+
struct CcFlags {
100+
/// Additional values for CMAKE_CC_FLAGS, to be added before all other values.
101+
cflags: OsString,
102+
/// Additional values for CMAKE_CXX_FLAGS, to be added before all other values.
103+
cxxflags: OsString,
104+
}
105+
106+
impl CcFlags {
107+
fn push_all(&mut self, s: impl AsRef<OsStr>) {
108+
let s = s.as_ref();
109+
self.cflags.push(" ");
110+
self.cflags.push(s);
111+
self.cxxflags.push(" ");
112+
self.cxxflags.push(s);
113+
}
114+
}
115+
96116
impl LdFlags {
97117
fn push_all(&mut self, s: impl AsRef<OsStr>) {
98118
let s = s.as_ref();
@@ -473,16 +493,6 @@ impl Step for Llvm {
473493
enabled_llvm_runtimes.push("compiler-rt");
474494
}
475495

476-
// This is an experimental flag, which likely builds more than necessary.
477-
// We will optimize it when we get closer to releasing it on nightly.
478-
if builder.config.llvm_offload {
479-
enabled_llvm_runtimes.push("offload");
480-
//FIXME(ZuseZ4): LLVM intends to drop the offload dependency on openmp.
481-
//Remove this line once they achieved it.
482-
enabled_llvm_runtimes.push("openmp");
483-
enabled_llvm_projects.push("compiler-rt");
484-
}
485-
486496
if !enabled_llvm_projects.is_empty() {
487497
enabled_llvm_projects.sort();
488498
enabled_llvm_projects.dedup();
@@ -917,6 +927,175 @@ fn get_var(var_base: &str, host: &str, target: &str) -> Option<OsString> {
917927
.or_else(|| env::var_os(var_base))
918928
}
919929

930+
#[derive(Clone)]
931+
pub struct BuiltOmpOffload {
932+
/// Path to the omp and offload dylibs.
933+
offload: Vec<PathBuf>,
934+
}
935+
936+
impl BuiltOmpOffload {
937+
pub fn offload_paths(&self) -> Vec<PathBuf> {
938+
self.offload.clone()
939+
}
940+
}
941+
942+
// FIXME(offload): In an ideal world, we would just enable the offload runtime in our previous LLVM
943+
// build step. For now, we still depend on the openmp runtime since we use some of it's API, so we
944+
// build both. However, when building those runtimes as part of the LLVM step, then LLVM's cmake
945+
// implicitly assumes that Clang has also been build and will try to use it. In the Rust CI, we
946+
// don't always build clang (due to compile times), but instead use a slightly older external clang.
947+
// LLVM tries to remove this build dependency of offload/openmp on Clang for LLVM-22, so in the
948+
// future we might be able to integrate this step into the LLVM step. For now, we instead introduce
949+
// a Clang_DIR bootstrap option, which allows us tell CMake to use an external clang for these two
950+
// runtimes. This external clang will try to use it's own (older) include dirs when building our
951+
// in-tree LLVM submodule, which will cause build failures. To prevent those, we now also
952+
// explicitly set our include dirs.
953+
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
954+
pub struct OmpOffload {
955+
pub target: TargetSelection,
956+
}
957+
958+
impl Step for OmpOffload {
959+
type Output = BuiltOmpOffload;
960+
const IS_HOST: bool = true;
961+
962+
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
963+
run.path("src/llvm-project/offload")
964+
}
965+
966+
fn make_run(run: RunConfig<'_>) {
967+
run.builder.ensure(OmpOffload { target: run.target });
968+
}
969+
970+
/// Compile OpenMP offload runtimes for `target`.
971+
#[allow(unused)]
972+
fn run(self, builder: &Builder<'_>) -> Self::Output {
973+
if builder.config.dry_run() {
974+
return BuiltOmpOffload {
975+
offload: vec![builder.config.tempdir().join("llvm-offload-dry-run")],
976+
};
977+
}
978+
let target = self.target;
979+
980+
let LlvmResult { host_llvm_config, llvm_cmake_dir } =
981+
builder.ensure(Llvm { target: self.target });
982+
983+
// Running cmake twice in the same folder is known to cause issues, like deleting existing
984+
// binaries. We therefore write our offload artifacts into it's own folder, instead of
985+
// using the llvm build dir.
986+
let out_dir = builder.offload_out(target);
987+
988+
let mut files = vec![];
989+
let lib_ext = std::env::consts::DLL_EXTENSION;
990+
files.push(out_dir.join("lib").join("libLLVMOffload").with_extension(lib_ext));
991+
files.push(out_dir.join("lib").join("libomp").with_extension(lib_ext));
992+
files.push(out_dir.join("lib").join("libomptarget").with_extension(lib_ext));
993+
994+
// Offload/OpenMP are just subfolders of LLVM, so we can use the LLVM sha.
995+
static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
996+
let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
997+
generate_smart_stamp_hash(
998+
builder,
999+
&builder.config.src.join("src/llvm-project/offload"),
1000+
builder.in_tree_llvm_info.sha().unwrap_or_default(),
1001+
)
1002+
});
1003+
let stamp = BuildStamp::new(&out_dir).with_prefix("offload").add_stamp(smart_stamp_hash);
1004+
1005+
trace!("checking build stamp to see if we need to rebuild offload/openmp artifacts");
1006+
if stamp.is_up_to_date() {
1007+
trace!(?out_dir, "offload/openmp build artifacts are up to date");
1008+
if stamp.stamp().is_empty() {
1009+
builder.info(
1010+
"Could not determine the Offload submodule commit hash. \
1011+
Assuming that an Offload rebuild is not necessary.",
1012+
);
1013+
builder.info(&format!(
1014+
"To force Offload/OpenMP to rebuild, remove the file `{}`",
1015+
stamp.path().display()
1016+
));
1017+
}
1018+
return BuiltOmpOffload { offload: files };
1019+
}
1020+
1021+
trace!(?target, "(re)building offload/openmp artifacts");
1022+
builder.info(&format!("Building OpenMP/Offload for {target}"));
1023+
t!(stamp.remove());
1024+
let _time = helpers::timeit(builder);
1025+
t!(fs::create_dir_all(&out_dir));
1026+
1027+
builder.config.update_submodule("src/llvm-project");
1028+
let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/runtimes/"));
1029+
1030+
// If we use an external clang as opposed to building our own llvm_clang, than that clang will
1031+
// come with it's own set of default include directories, which are based on a potentially older
1032+
// LLVM. This can cause issues, so we overwrite it to include headers based on our
1033+
// `src/llvm-project` submodule instead.
1034+
// FIXME(offload): With LLVM-22 we hopefully won't need an external clang anymore.
1035+
let mut cflags = CcFlags::default();
1036+
if !builder.config.llvm_clang {
1037+
let base = builder.llvm_out(target).join("include");
1038+
let inc_dir = base.display();
1039+
cflags.push_all(format!(" -I {inc_dir}"));
1040+
}
1041+
1042+
configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), cflags, &[]);
1043+
1044+
// Re-use the same flags as llvm to control the level of debug information
1045+
// generated for offload.
1046+
let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
1047+
(false, _) => "Debug",
1048+
(true, false) => "Release",
1049+
(true, true) => "RelWithDebInfo",
1050+
};
1051+
trace!(?profile);
1052+
1053+
// OpenMP/Offload builds currently (LLVM-21) still depend on Clang, although there are
1054+
// intentions to loosen this requirement for LLVM-22. If we were to
1055+
let clang_dir = if !builder.config.llvm_clang {
1056+
// We must have an external clang to use.
1057+
assert!(&builder.build.config.llvm_clang_dir.is_some());
1058+
builder.build.config.llvm_clang_dir.clone()
1059+
} else {
1060+
// No need to specify it, since we use the in-tree clang
1061+
None
1062+
};
1063+
1064+
// FIXME(offload): Once we move from OMP to Offload (Ol) APIs, we should drop the openmp
1065+
// runtime to simplify our build. We should also re-evaluate the LLVM_Root and try to get
1066+
// rid of the Clang_DIR, once we upgrade to LLVM-22.
1067+
cfg.out_dir(&out_dir)
1068+
.profile(profile)
1069+
.env("LLVM_CONFIG_REAL", &host_llvm_config)
1070+
.define("LLVM_ENABLE_ASSERTIONS", "ON")
1071+
.define("LLVM_ENABLE_RUNTIMES", "openmp;offload")
1072+
.define("LLVM_INCLUDE_TESTS", "OFF")
1073+
.define("OFFLOAD_INCLUDE_TESTS", "OFF")
1074+
.define("OPENMP_STANDALONE_BUILD", "ON")
1075+
.define("LLVM_ROOT", builder.llvm_out(target).join("build"))
1076+
.define("LLVM_DIR", llvm_cmake_dir);
1077+
if let Some(p) = clang_dir {
1078+
cfg.define("Clang_DIR", p);
1079+
}
1080+
cfg.build();
1081+
1082+
t!(stamp.write());
1083+
1084+
for p in &files {
1085+
// At this point, `out_dir` should contain the built <offload-filename>.<dylib-ext>
1086+
// files.
1087+
if !p.exists() {
1088+
eprintln!(
1089+
"`{p:?}` not found in `{}`. Either the build has failed or Offload was built with a wrong version of LLVM",
1090+
out_dir.display()
1091+
);
1092+
exit!(1);
1093+
}
1094+
}
1095+
BuiltOmpOffload { offload: files }
1096+
}
1097+
}
1098+
9201099
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
9211100
pub struct Enzyme {
9221101
pub target: TargetSelection,
@@ -991,11 +1170,10 @@ impl Step for Enzyme {
9911170

9921171
builder.config.update_submodule("src/tools/enzyme");
9931172
let mut cfg = cmake::Config::new(builder.src.join("src/tools/enzyme/enzyme/"));
994-
995-
let mut cflags = CcFlags::default();
9961173
// Enzyme devs maintain upstream compability, but only fix deprecations when they are about
9971174
// to turn into a hard error. As such, Enzyme generates various warnings which could make it
9981175
// hard to spot more relevant issues.
1176+
let mut cflags = CcFlags::default();
9991177
cflags.push_all("-Wno-deprecated");
10001178
configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), cflags, &[]);
10011179

src/bootstrap/src/core/config/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ pub struct Config {
169169
pub llvm_link_jobs: Option<u32>,
170170
pub llvm_version_suffix: Option<String>,
171171
pub llvm_use_linker: Option<String>,
172+
pub llvm_clang_dir: Option<PathBuf>,
172173
pub llvm_allow_old_toolchain: bool,
173174
pub llvm_polly: bool,
174175
pub llvm_clang: bool,
@@ -604,6 +605,7 @@ impl Config {
604605
use_linker: llvm_use_linker,
605606
allow_old_toolchain: llvm_allow_old_toolchain,
606607
offload: llvm_offload,
608+
offload_clang_dir: llvm_clang_dir,
607609
polly: llvm_polly,
608610
clang: llvm_clang,
609611
enable_warnings: llvm_enable_warnings,
@@ -1361,6 +1363,7 @@ impl Config {
13611363
llvm_cflags,
13621364
llvm_clang: llvm_clang.unwrap_or(false),
13631365
llvm_clang_cl,
1366+
llvm_clang_dir: llvm_clang_dir.map(PathBuf::from),
13641367
llvm_cxxflags,
13651368
llvm_enable_warnings: llvm_enable_warnings.unwrap_or(false),
13661369
llvm_enzyme: llvm_enzyme.unwrap_or(false),

src/bootstrap/src/core/config/toml/llvm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ define_config! {
3535
allow_old_toolchain: Option<bool> = "allow-old-toolchain",
3636
offload: Option<bool> = "offload",
3737
polly: Option<bool> = "polly",
38+
offload_clang_dir: Option<String> = "offload-clang-dir",
3839
clang: Option<bool> = "clang",
3940
enable_warnings: Option<bool> = "enable-warnings",
4041
download_ci_llvm: Option<StringOrBool> = "download-ci-llvm",
@@ -112,6 +113,7 @@ pub fn check_incompatible_options_for_ci_llvm(
112113
use_linker,
113114
allow_old_toolchain,
114115
offload,
116+
offload_clang_dir: _,
115117
polly,
116118
clang,
117119
enable_warnings,

src/bootstrap/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,10 @@ impl Build {
975975
self.out.join(&*target.triple).join("enzyme")
976976
}
977977

978+
fn offload_out(&self, target: TargetSelection) -> PathBuf {
979+
self.out.join(&*target.triple).join("offload")
980+
}
981+
978982
fn lld_out(&self, target: TargetSelection) -> PathBuf {
979983
self.out.join(target).join("lld")
980984
}

src/bootstrap/src/utils/change_tracker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,4 +606,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
606606
severity: ChangeSeverity::Info,
607607
summary: "New option `gcc.libgccjit-libs-dir` to specify which libgccjit.so to use per target.",
608608
},
609+
ChangeInfo {
610+
change_id: 148671,
611+
severity: ChangeSeverity::Info,
612+
summary: "New option `llvm.offload-clang-dir` to allow building an in-tree llvm offload and openmp runtime with an external clang.",
613+
},
609614
];

0 commit comments

Comments
 (0)