Skip to content

Commit c27bcd3

Browse files
Rollup merge of rust-lang#150108 - ZuseZ4:offload-build-rework, r=Kobzol
Offload: Build offload as a single Step r? ``@Kobzol`` Since it looks like we'll postpone enabling offload in CI for a bit, I factored out these improvements which we want independently. I locally tested both build options successfully, the in-tree-clang build, as well as the build where we provide a path to an external clang.
2 parents 112a274 + f3b16d8 commit c27bcd3

File tree

8 files changed

+256
-27
lines changed

8 files changed

+256
-27
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
@@ -1430,10 +1430,12 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
14301430
if builder.config.llvm_enzyme {
14311431
cargo.env("LLVM_ENZYME", "1");
14321432
}
1433+
let llvm::LlvmResult { host_llvm_config, .. } = builder.ensure(llvm::Llvm { target });
14331434
if builder.config.llvm_offload {
1435+
builder.ensure(llvm::OmpOffload { target });
14341436
cargo.env("LLVM_OFFLOAD", "1");
14351437
}
1436-
let llvm::LlvmResult { host_llvm_config, .. } = builder.ensure(llvm::Llvm { target });
1438+
14371439
cargo.env("LLVM_CONFIG", &host_llvm_config);
14381440

14391441
// Some LLVM linker flags (-L and -l) may be needed to link `rustc_llvm`. Its build script
@@ -2296,6 +2298,24 @@ impl Step for Assemble {
22962298
}
22972299
}
22982300

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

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

Lines changed: 213 additions & 26 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};
@@ -63,6 +64,25 @@ impl LlvmBuildStatus {
6364
}
6465
}
6566

67+
/// Allows each step to add C/Cxx flags which are only used for a specific cmake invocation.
68+
#[derive(Debug, Clone, Default)]
69+
struct CcFlags {
70+
/// Additional values for CMAKE_CC_FLAGS, to be added before all other values.
71+
cflags: OsString,
72+
/// Additional values for CMAKE_CXX_FLAGS, to be added before all other values.
73+
cxxflags: OsString,
74+
}
75+
76+
impl CcFlags {
77+
fn push_all(&mut self, s: impl AsRef<OsStr>) {
78+
let s = s.as_ref();
79+
self.cflags.push(" ");
80+
self.cflags.push(s);
81+
self.cxxflags.push(" ");
82+
self.cxxflags.push(s);
83+
}
84+
}
85+
6686
/// Linker flags to pass to LLVM's CMake invocation.
6787
#[derive(Debug, Clone, Default)]
6888
struct LdFlags {
@@ -454,16 +474,6 @@ impl Step for Llvm {
454474
enabled_llvm_runtimes.push("compiler-rt");
455475
}
456476

457-
// This is an experimental flag, which likely builds more than necessary.
458-
// We will optimize it when we get closer to releasing it on nightly.
459-
if builder.config.llvm_offload {
460-
enabled_llvm_runtimes.push("offload");
461-
//FIXME(ZuseZ4): LLVM intends to drop the offload dependency on openmp.
462-
//Remove this line once they achieved it.
463-
enabled_llvm_runtimes.push("openmp");
464-
enabled_llvm_projects.push("compiler-rt");
465-
}
466-
467477
if !enabled_llvm_projects.is_empty() {
468478
enabled_llvm_projects.sort();
469479
enabled_llvm_projects.dedup();
@@ -527,7 +537,7 @@ impl Step for Llvm {
527537
cfg.define("LLVM_VERSION_SUFFIX", suffix);
528538
}
529539

530-
configure_cmake(builder, target, &mut cfg, true, ldflags, &[]);
540+
configure_cmake(builder, target, &mut cfg, true, ldflags, CcFlags::default(), &[]);
531541
configure_llvm(builder, target, &mut cfg);
532542

533543
for (key, val) in &builder.config.llvm_build_config {
@@ -633,6 +643,7 @@ fn configure_cmake(
633643
cfg: &mut cmake::Config,
634644
use_compiler_launcher: bool,
635645
mut ldflags: LdFlags,
646+
ccflags: CcFlags,
636647
suppressed_compiler_flag_prefixes: &[&str],
637648
) {
638649
// Do not print installation messages for up-to-date files.
@@ -761,23 +772,21 @@ fn configure_cmake(
761772
.define("CMAKE_ASM_COMPILER", sanitize_cc(&cc));
762773

763774
cfg.build_arg("-j").build_arg(builder.jobs().to_string());
775+
let mut cflags = ccflags.cflags.clone();
764776
// FIXME(madsmtm): Allow `cmake-rs` to select flags by itself by passing
765777
// our flags via `.cflag`/`.cxxflag` instead.
766778
//
767779
// Needs `suppressed_compiler_flag_prefixes` to be gone, and hence
768780
// https://github.com/llvm/llvm-project/issues/88780 to be fixed.
769-
let mut cflags: OsString = builder
781+
for flag in builder
770782
.cc_handled_clags(target, CLang::C)
771783
.into_iter()
772784
.chain(builder.cc_unhandled_cflags(target, GitRepo::Llvm, CLang::C))
773-
.filter(|flag| {
774-
!suppressed_compiler_flag_prefixes
775-
.iter()
776-
.any(|suppressed_prefix| flag.starts_with(suppressed_prefix))
777-
})
778-
.collect::<Vec<String>>()
779-
.join(" ")
780-
.into();
785+
.filter(|flag| !suppressed_compiler_flag_prefixes.iter().any(|p| flag.starts_with(p)))
786+
{
787+
cflags.push(" ");
788+
cflags.push(flag);
789+
}
781790
if let Some(ref s) = builder.config.llvm_cflags {
782791
cflags.push(" ");
783792
cflags.push(s);
@@ -789,7 +798,8 @@ fn configure_cmake(
789798
cflags.push(format!(" --target={target}"));
790799
}
791800
cfg.define("CMAKE_C_FLAGS", cflags);
792-
let mut cxxflags: OsString = builder
801+
let mut cxxflags = ccflags.cxxflags.clone();
802+
for flag in builder
793803
.cc_handled_clags(target, CLang::Cxx)
794804
.into_iter()
795805
.chain(builder.cc_unhandled_cflags(target, GitRepo::Llvm, CLang::Cxx))
@@ -798,9 +808,10 @@ fn configure_cmake(
798808
.iter()
799809
.any(|suppressed_prefix| flag.starts_with(suppressed_prefix))
800810
})
801-
.collect::<Vec<String>>()
802-
.join(" ")
803-
.into();
811+
{
812+
cxxflags.push(" ");
813+
cxxflags.push(flag);
814+
}
804815
if let Some(ref s) = builder.config.llvm_cxxflags {
805816
cxxflags.push(" ");
806817
cxxflags.push(s);
@@ -811,6 +822,7 @@ fn configure_cmake(
811822
if builder.config.llvm_clang_cl.is_some() {
812823
cxxflags.push(format!(" --target={target}"));
813824
}
825+
814826
cfg.define("CMAKE_CXX_FLAGS", cxxflags);
815827
if let Some(ar) = builder.ar(target)
816828
&& ar.is_absolute()
@@ -896,6 +908,175 @@ fn get_var(var_base: &str, host: &str, target: &str) -> Option<OsString> {
896908
.or_else(|| env::var_os(var_base))
897909
}
898910

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

9711152
builder.config.update_submodule("src/tools/enzyme");
9721153
let mut cfg = cmake::Config::new(builder.src.join("src/tools/enzyme/enzyme/"));
973-
configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), &[]);
1154+
// Enzyme devs maintain upstream compatibility, but only fix deprecations when they are about
1155+
// to turn into a hard error. As such, Enzyme generates various warnings which could make it
1156+
// hard to spot more relevant issues.
1157+
let mut cflags = CcFlags::default();
1158+
cflags.push_all("-Wno-deprecated");
1159+
configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), cflags, &[]);
9741160

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

1093-
configure_cmake(builder, target, &mut cfg, true, ldflags, &[]);
1279+
configure_cmake(builder, target, &mut cfg, true, ldflags, CcFlags::default(), &[]);
10941280
configure_llvm(builder, target, &mut cfg);
10951281

10961282
// Re-use the same flags as llvm to control the level of debug information
@@ -1213,6 +1399,7 @@ impl Step for Sanitizers {
12131399
&mut cfg,
12141400
use_compiler_launcher,
12151401
LdFlags::default(),
1402+
CcFlags::default(),
12161403
suppressed_compiler_flag_prefixes,
12171404
);
12181405

0 commit comments

Comments
 (0)