Skip to content

Commit 4df11ec

Browse files
Auto merge of #148671 - ZuseZ4:offload-bootstrap, r=<try>
Offload bootstrap
2 parents 61cc47e + e82edfe commit 4df11ec

File tree

8 files changed

+234
-11
lines changed

8 files changed

+234
-11
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 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: 57 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,60 @@ 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 src_dir = offload_install.join("lib");
2303+
let libdir = builder.sysroot_target_libdir(build_compiler, build_compiler.host);
2304+
let target_libdir =
2305+
builder.sysroot_target_libdir(target_compiler, target_compiler.host);
2306+
let lib_ext = std::env::consts::DLL_EXTENSION;
2307+
2308+
let libenzyme = format!("libLLVMOffload");
2309+
let src_lib = src_dir.join(&libenzyme).with_extension(lib_ext);
2310+
let dst_lib = libdir.join(&libenzyme).with_extension(lib_ext);
2311+
let target_dst_lib = target_libdir.join(&libenzyme).with_extension(lib_ext);
2312+
if src_lib.exists() {
2313+
builder.resolve_symlink_and_copy(&src_lib, &dst_lib);
2314+
builder.resolve_symlink_and_copy(&src_lib, &target_dst_lib);
2315+
2316+
// FIXME(offload): With LLVM-22, we should be able to drop everything below here.
2317+
let omp = format!("libomp");
2318+
let src_omp = src_dir.join(&omp).with_extension(lib_ext);
2319+
let dst_omp_lib = libdir.join(&omp).with_extension(lib_ext);
2320+
let target_omp_dst_lib = target_libdir.join(&omp).with_extension(lib_ext);
2321+
builder.resolve_symlink_and_copy(&src_omp, &dst_omp_lib);
2322+
builder.resolve_symlink_and_copy(&src_omp, &target_omp_dst_lib);
2323+
2324+
let tgt = format!("libomptarget");
2325+
let src_tgt = src_dir.join(&tgt).with_extension(lib_ext);
2326+
let dst_tgt_lib = libdir.join(&tgt).with_extension(lib_ext);
2327+
let target_tgt_dst_lib = target_libdir.join(&tgt).with_extension(lib_ext);
2328+
builder.resolve_symlink_and_copy(&src_tgt, &dst_tgt_lib);
2329+
builder.resolve_symlink_and_copy(&src_tgt, &target_tgt_dst_lib);
2330+
2331+
// The last one is slightly more tricky, since we have the same file twice, in two
2332+
// subfolders for amdgcn and nvptx64. We'll likely find two more in the future, once
2333+
// Intel and Spir-V support lands in offload.
2334+
let gpu_tgts = ["amdgcn-amd-amdhsa", "nvptx64-nvidia-cuda"];
2335+
let device = format!("libompdevice.a");
2336+
for tgt in gpu_tgts {
2337+
let dst_tgt_dir = libdir.join(&tgt);
2338+
let target_tgt_dst_dir = target_libdir.join(&tgt);
2339+
t!(fs::create_dir_all(&dst_tgt_dir));
2340+
t!(fs::create_dir_all(&target_tgt_dst_dir));
2341+
let dst_tgt_lib = dst_tgt_dir.join(&device);
2342+
let target_tgt_dst_lib = target_tgt_dst_dir.join(&device);
2343+
builder.resolve_symlink_and_copy(&src_tgt, &dst_tgt_lib);
2344+
builder.resolve_symlink_and_copy(&src_tgt, &target_tgt_dst_lib);
2345+
// FIXME(offload): copy the files within the directories as well, but figure out
2346+
// the naming scheme before.
2347+
}
2348+
}
2349+
}
2350+
}
2351+
22962352
// Build the libraries for this compiler to link to (i.e., the libraries
22972353
// it uses at runtime).
22982354
debug!(

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

Lines changed: 153 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -454,16 +454,6 @@ impl Step for Llvm {
454454
enabled_llvm_runtimes.push("compiler-rt");
455455
}
456456

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-
467457
if !enabled_llvm_projects.is_empty() {
468458
enabled_llvm_projects.sort();
469459
enabled_llvm_projects.dedup();
@@ -778,6 +768,17 @@ fn configure_cmake(
778768
.collect::<Vec<String>>()
779769
.join(" ")
780770
.into();
771+
772+
// If we use an external clang as opposed to building our own llvm_clang, than that clang will
773+
// come with it's own set of default include directories, which are based on a potentially older
774+
// LLVM. This can cause issues, so we overwrite it to include headers based on our
775+
// `src/llvm-project` submodule instead.
776+
// FIXME(offload): With LLVM-22 we hopefully won't need an external clang anymore.
777+
let base = builder.llvm_out(target).join("include");
778+
let inc_dir = base.display();
779+
if builder.config.llvm_offload && !builder.config.llvm_clang {
780+
cflags.push(format!(" -I {inc_dir}"));
781+
}
781782
if let Some(ref s) = builder.config.llvm_cflags {
782783
cflags.push(" ");
783784
cflags.push(s);
@@ -789,6 +790,7 @@ fn configure_cmake(
789790
cflags.push(format!(" --target={target}"));
790791
}
791792
cfg.define("CMAKE_C_FLAGS", cflags);
793+
792794
let mut cxxflags: OsString = builder
793795
.cc_handled_clags(target, CLang::Cxx)
794796
.into_iter()
@@ -801,6 +803,9 @@ fn configure_cmake(
801803
.collect::<Vec<String>>()
802804
.join(" ")
803805
.into();
806+
if builder.config.llvm_offload && !builder.config.llvm_clang {
807+
cxxflags.push(format!(" -I {inc_dir}"));
808+
}
804809
if let Some(ref s) = builder.config.llvm_cxxflags {
805810
cxxflags.push(" ");
806811
cxxflags.push(s);
@@ -896,6 +901,144 @@ fn get_var(var_base: &str, host: &str, target: &str) -> Option<OsString> {
896901
.or_else(|| env::var_os(var_base))
897902
}
898903

904+
// FIXME(offload): In an ideal world, we would just enable the offload runtime in our previous LLVM
905+
// build step. For now, we still depend on the openmp runtime since we use some of it's API, so we
906+
// build both. However, when building those runtimes as part of the LLVM step, then LLVM's cmake
907+
// implicitly assumes that Clang has also been build and will try to use it. In the Rust CI, we
908+
// don't always build clang (due to compile times), but instead use a slightly older external clang.
909+
// LLVM tries to remove this build dependency of offload/openmp on Clang for LLVM-22, so in the
910+
// future we might be able to integrate this step into the LLVM step. For now, we instead introduce
911+
// a Clang_DIR bootstrap option, which allows us tell CMake to use an external clang for these two
912+
// runtimes. This external clang will try to use it's own (older) include dirs when building our
913+
// in-tree LLVM submodule, which will cause build failures. To prevent those, we now also
914+
// explicitly set our include dirs.
915+
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
916+
pub struct OmpOffload {
917+
pub target: TargetSelection,
918+
}
919+
920+
impl Step for OmpOffload {
921+
type Output = PathBuf;
922+
const IS_HOST: bool = true;
923+
924+
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
925+
run.path("src/llvm-project/offload")
926+
}
927+
928+
fn make_run(run: RunConfig<'_>) {
929+
run.builder.ensure(OmpOffload { target: run.target });
930+
}
931+
932+
/// Compile OpenMP offload runtimes for `target`.
933+
#[allow(unused)]
934+
fn run(self, builder: &Builder<'_>) -> PathBuf {
935+
if builder.config.dry_run() {
936+
return builder.config.tempdir().join("llvm-offload-dry-run");
937+
}
938+
939+
let target = self.target;
940+
941+
let LlvmResult { host_llvm_config, .. } = builder.ensure(Llvm { target });
942+
943+
// Running cmake twice in the same folder is known to cause issues, like deleting existing
944+
// binaries. We therefore write our offload artifacts into it's own subfolder. We use a
945+
// subfolder, so that all the logic that processes our build artifacts (hopefully) also
946+
// automatically manages our artifacts in the subfolder.
947+
let out_dir = builder.llvm_out(target).join("offload-outdir");
948+
if std::fs::exists(&out_dir).is_ok_and(|x| !x) {
949+
std::fs::DirBuilder::new().create(&out_dir).unwrap();
950+
}
951+
952+
if !builder.config.llvm_offload
953+
|| builder.config.dry_run()
954+
|| builder.config.llvm_profile_generate
955+
|| builder.config.llvm_profile_use.is_some()
956+
{
957+
return out_dir;
958+
}
959+
960+
// Offload/OpenMP are just subfolders of LLVM, so we can use the LLVM sha.
961+
static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
962+
let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
963+
generate_smart_stamp_hash(
964+
builder,
965+
&builder.config.src.join("src/llvm-project/offload"),
966+
builder.in_tree_llvm_info.sha().unwrap_or_default(),
967+
)
968+
});
969+
let stamp = BuildStamp::new(&out_dir).with_prefix("offload").add_stamp(smart_stamp_hash);
970+
971+
trace!("checking build stamp to see if we need to rebuild offload/openmp artifacts");
972+
if stamp.is_up_to_date() {
973+
trace!(?out_dir, "offload/openmp build artifacts are up to date");
974+
if stamp.stamp().is_empty() {
975+
builder.info(
976+
"Could not determine the Offload submodule commit hash. \
977+
Assuming that an Offload rebuild is not necessary.",
978+
);
979+
builder.info(&format!(
980+
"To force Offload/OpenMP to rebuild, remove the file `{}`",
981+
stamp.path().display()
982+
));
983+
}
984+
return out_dir;
985+
}
986+
987+
trace!(?target, "(re)building offload/openmp artifacts");
988+
builder.info(&format!("Building OpenMP/Offload for {target}"));
989+
t!(stamp.remove());
990+
let _time = helpers::timeit(builder);
991+
t!(fs::create_dir_all(&out_dir));
992+
993+
builder.config.update_submodule("src/llvm-project");
994+
let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/runtimes/"));
995+
configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), &[]);
996+
997+
// Re-use the same flags as llvm to control the level of debug information
998+
// generated for offload.
999+
let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
1000+
(false, _) => "Debug",
1001+
(true, false) => "Release",
1002+
(true, true) => "RelWithDebInfo",
1003+
};
1004+
trace!(?profile);
1005+
1006+
// OpenMP/Offload builds currently (LLVM-21) still depend on Clang, although there are
1007+
// intentions to loosen this requirement for LLVM-22. If we were to
1008+
let clang_dir = if !builder.config.llvm_clang {
1009+
// We must have an external clang to use.
1010+
assert!(&builder.build.config.llvm_clang_dir.is_some());
1011+
builder.build.config.llvm_clang_dir.clone()
1012+
} else {
1013+
// No need to specify it, since we use the in-tree clang
1014+
None
1015+
};
1016+
1017+
// FIXME(offload): Once we move from OMP to Offload (Ol) APIs, we should drop the openmp
1018+
// runtime to simplify our build. We should also re-evaluate the LLVM_Root and try to get
1019+
// rid of the Clang_DIR, once we upgrade to LLVM-22.
1020+
cfg.out_dir(&out_dir)
1021+
.profile(profile)
1022+
.env("LLVM_CONFIG_REAL", &host_llvm_config)
1023+
.define("LLVM_ENABLE_ASSERTIONS", "ON")
1024+
.define("LLVM_ENABLE_RUNTIMES", "openmp;offload")
1025+
.define("LLVM_INCLUDE_TESTS", "OFF")
1026+
.define("OFFLOAD_INCLUDE_TESTS", "OFF")
1027+
.define("OPENMP_STANDALONE_BUILD", "ON")
1028+
.define("LLVM_ROOT", builder.llvm_out(target).join("build"))
1029+
.define("LLVM_DIR", builder.llvm_out(target).join("lib").join("cmake").join("llvm"));
1030+
1031+
cfg.define("LLVM_USE_LINKER", "lld");
1032+
if let Some(p) = clang_dir {
1033+
cfg.define("Clang_DIR", p);
1034+
}
1035+
cfg.build();
1036+
1037+
t!(stamp.write());
1038+
out_dir
1039+
}
1040+
}
1041+
8991042
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
9001043
pub struct Enzyme {
9011044
pub target: TargetSelection,

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

Lines changed: 7 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,
@@ -291,6 +292,7 @@ pub struct Config {
291292
pub miri_info: channel::GitInfo,
292293
pub rustfmt_info: channel::GitInfo,
293294
pub enzyme_info: channel::GitInfo,
295+
pub offload_info: channel::GitInfo,
294296
pub in_tree_llvm_info: channel::GitInfo,
295297
pub in_tree_gcc_info: channel::GitInfo,
296298

@@ -602,6 +604,7 @@ impl Config {
602604
ldflags: llvm_ldflags,
603605
use_libcxx: llvm_use_libcxx,
604606
use_linker: llvm_use_linker,
607+
clang_dir: llvm_clang_dir,
605608
allow_old_toolchain: llvm_allow_old_toolchain,
606609
offload: llvm_offload,
607610
polly: llvm_polly,
@@ -1261,6 +1264,8 @@ impl Config {
12611264
let in_tree_gcc_info = git_info(&exec_ctx, false, &src.join("src/gcc"));
12621265
let in_tree_llvm_info = git_info(&exec_ctx, false, &src.join("src/llvm-project"));
12631266
let enzyme_info = git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/enzyme"));
1267+
let offload_info =
1268+
git_info(&exec_ctx, omit_git_hash, &src.join("src/llvm-project/offload"));
12641269
let miri_info = git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/miri"));
12651270
let rust_analyzer_info =
12661271
git_info(&exec_ctx, omit_git_hash, &src.join("src/tools/rust-analyzer"));
@@ -1361,6 +1366,7 @@ impl Config {
13611366
llvm_cflags,
13621367
llvm_clang: llvm_clang.unwrap_or(false),
13631368
llvm_clang_cl,
1369+
llvm_clang_dir: llvm_clang_dir.map(PathBuf::from),
13641370
llvm_cxxflags,
13651371
llvm_enable_warnings: llvm_enable_warnings.unwrap_or(false),
13661372
llvm_enzyme: llvm_enzyme.unwrap_or(false),
@@ -1401,6 +1407,7 @@ impl Config {
14011407
musl_root: rust_musl_root.map(PathBuf::from),
14021408
ninja_in_file: llvm_ninja.unwrap_or(true),
14031409
nodejs: build_nodejs.map(PathBuf::from),
1410+
offload_info,
14041411
omit_git_hash,
14051412
on_fail: flags_on_fail,
14061413
optimized_compiler_builtins,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ define_config! {
3232
ldflags: Option<String> = "ldflags",
3333
use_libcxx: Option<bool> = "use-libcxx",
3434
use_linker: Option<String> = "use-linker",
35+
clang_dir: Option<String> = "offload-clang-dir",
3536
allow_old_toolchain: Option<bool> = "allow-old-toolchain",
3637
offload: Option<bool> = "offload",
3738
polly: Option<bool> = "polly",
@@ -110,6 +111,7 @@ pub fn check_incompatible_options_for_ci_llvm(
110111
ldflags,
111112
use_libcxx,
112113
use_linker,
114+
clang_dir: _,
113115
allow_old_toolchain,
114116
offload,
115117
polly,

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
];

src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ ENV RUST_CONFIGURE_ARGS \
9090
--set target.x86_64-unknown-linux-gnu.ranlib=/rustroot/bin/llvm-ranlib \
9191
--set llvm.thin-lto=true \
9292
--set llvm.ninja=false \
93+
--set llvm.offload=true \
94+
--set llvm.offload-clang-dir="/rustroot/lib/cmake/clang" \
9395
--set llvm.libzstd=true \
9496
--set rust.jemalloc \
9597
--set rust.bootstrap-override-lld=true \

0 commit comments

Comments
 (0)