diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 3021f52d15962..1ceebc1f89e07 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -2325,6 +2325,17 @@ impl CommandLineStep for Assemble { let dst_lib = target_libdir.join(libname); builder.resolve_symlink_and_copy(&p, &dst_lib); } + + for &gpu_target in llvm::GPU_TARGETS { + let gpu_libc = + builder.ensure(llvm::GpuLibc { target: build_compiler.host, gpu_target }); + let dst_dir = target_libdir.join(gpu_target); + t!(fs::create_dir_all(&dst_dir)); + for p in gpu_libc.paths() { + let dst_lib = dst_dir.join(p.file_name().unwrap()); + builder.copy_link(p, &dst_lib, FileType::NativeLibrary); + } + } } } diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index b3a6817b5d467..fbab16bd73b61 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -2883,6 +2883,10 @@ impl CommandLineStep for Offload { let omp_offload = builder.ensure(llvm::OmpOffload { target }); let rust_offload = builder.ensure(llvm::RustOffload { target }); + let gpu_libcs: Vec<_> = llvm::GPU_TARGETS + .iter() + .map(|&gpu_target| (gpu_target, builder.ensure(llvm::GpuLibc { target, gpu_target }))) + .collect(); if builder.config.dry_run() { return None; @@ -2903,6 +2907,13 @@ impl CommandLineStep for Offload { tarball.add_file(path, destdir, FileType::NativeLibrary); } + for (gpu_target, gpu_libc) in &gpu_libcs { + let destdir = target_libdir.join(gpu_target); + for path in gpu_libc.paths() { + tarball.add_file(path, &destdir, FileType::NativeLibrary); + } + } + tarball.add_file(rust_offload.rust_offload_path(), target_libdir, FileType::NativeLibrary); Some(tarball.generate()) diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 030617b0db56a..1bf4353ef467c 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -1402,27 +1402,7 @@ impl CommandLineStep for OmpOffload { // we built our own clang based on the llvm submodule first, this always works. The // alternative is that the user sets the offload_clang_dir path, in which case they hopefully point // to a suitable clang, otherwise the build will fail. - let clang_bin_dir = if builder.config.llvm_clang { - llvm_output.llvm_config().parent().map(Path::to_path_buf) - } else { - // We expect the following (default) structure of the offload_clang_dir: - // /lib/cmake/clang, with a ClangConfig.cmake inside. - // The clang binary is located in /bin, so we go up three levels to find it. - // This hardcodes the ClangConfig.cmake logic, which isn't great, so we filter for the - // binary and error if we can't find it (presumably because LLVM build layout changed?). - offload_clang_dir - .as_deref() - .and_then(|dir| dir.ancestors().nth(3)) - .map(|prefix| prefix.join("bin")) - } - .filter(|dir| dir.join(exe("clang", target)).exists()); - - let Some(clang_bin_dir) = clang_bin_dir else { - eprintln!( - "Building Offload requires a clang binary. Please either set `llvm.offload-clang-dir` or enable `llvm.clang` to build it." - ); - helpers::exit_process(1); - }; + let clang_bin_dir = offload_clang_bin_dir(builder, target, &llvm_output); let clang = clang_bin_dir.join(exe("clang", target)); let clangxx = clang_bin_dir.join(exe("clang++", target)); @@ -1519,6 +1499,199 @@ impl CommandLineStep for OmpOffload { } } +fn offload_clang_cmake_dir(builder: &Builder<'_>) -> Option { + if !builder.config.llvm_clang { + // We must have an external clang to use. + builder.config.offload_clang_dir.clone() + } else { + // No need to specify it, since we use the in-tree clang + None + } +} + +fn offload_clang_bin_dir( + builder: &Builder<'_>, + target: TargetSelection, + llvm_output: &LlvmOutput, +) -> PathBuf { + let clang_bin_dir = if builder.config.llvm_clang { + llvm_output.llvm_config().parent().map(Path::to_path_buf) + } else { + // We expect the following (default) structure of the offload_clang_dir: + // /lib/cmake/clang, with a ClangConfig.cmake inside. + // The clang binary is located in /bin, so we go up three levels to find it. + // This hardcodes the ClangConfig.cmake logic, which isn't great, so we filter for the + // binary and error if we can't find it (presumably because LLVM build layout changed?). + builder + .config + .offload_clang_dir + .as_deref() + .and_then(|dir| dir.ancestors().nth(3)) + .map(|prefix| prefix.join("bin")) + } + .filter(|dir| dir.join(exe("clang", target)).exists()); + + let Some(clang_bin_dir) = clang_bin_dir else { + eprintln!( + "Building Offload requires a clang binary. Please either set `llvm.offload-clang-dir` or enable `llvm.clang` to build it." + ); + helpers::exit_process(1); + }; + clang_bin_dir +} + +// FIXME(offload): add intel-spirv target once there is a rustc target for it. +pub const GPU_TARGETS: &[&str] = &["amdgcn-amd-amdhsa", "nvptx64-nvidia-cuda"]; + +#[derive(Clone)] +pub struct BuiltGpuLibc { + libs: Vec, +} + +impl BuiltGpuLibc { + pub fn paths(&self) -> &[PathBuf] { + &self.libs + } +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct GpuLibc { + pub target: TargetSelection, + pub gpu_target: &'static str, +} + +impl CommandLineStep for GpuLibc { + type Output = BuiltGpuLibc; + const IS_HOST: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.alias("gpu-libc") + } + + fn make_run(run: RunConfig<'_>) { + for &gpu_target in GPU_TARGETS { + run.builder.ensure(GpuLibc { target: run.target, gpu_target }); + } + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + let target = self.target; + let gpu_target = self.gpu_target; + + let out_dir = builder.offload_out(target).join(gpu_target); + let lib_dir = out_dir.join("lib").join(gpu_target); + let files = vec![lib_dir.join("libc.a"), lib_dir.join("libm.a"), lib_dir.join("crt1.o")]; + + if builder.config.dry_run() { + return BuiltGpuLibc { libs: files }; + } + + let llvm_output = builder.ensure(Llvm { target }); + + 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/libc"), + builder.in_tree_llvm_info.sha().unwrap_or_default(), + ) + }); + let stamp = BuildStamp::new(&out_dir).with_prefix("gpu-libc").add_stamp(smart_stamp_hash); + + trace!("checking build stamp to see if we need to rebuild the gpu libc"); + if stamp.is_up_to_date() { + trace!(?out_dir, "gpu libc build artifacts are up to date"); + return BuiltGpuLibc { libs: files }; + } + + trace!(?target, ?gpu_target, "(re)building the gpu libc"); + let _guard = builder.msg_unstaged(Kind::Build, "gpu-libc", target); + t!(stamp.remove()); + let _time = helpers::timeit(builder); + t!(fs::create_dir_all(&out_dir)); + + builder.config.update_submodule("src/llvm-project"); + + let clang_bin_dir = offload_clang_bin_dir(builder, target, &llvm_output); + let clang = clang_bin_dir.join(exe("clang", target)); + let clangxx = clang_bin_dir.join(exe("clang++", target)); + + let mut program_dirs = vec![clang_bin_dir.clone()]; + if !clang_bin_dir.join(exe("ld.lld", target)).exists() { + program_dirs.push(builder.ensure(Lld { target }).join("bin")); + } + let program_flags = + program_dirs.iter().map(|d| format!("-B{}", d.display())).collect::>().join(" "); + + let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/runtimes/")); + + configure_cmake( + builder, + target, + &mut cfg, + true, + LdFlags::default(), + CcFlags::default(), + &[], + ); + + let profile = get_llvm_profile(&builder.config); + trace!(?profile); + + let llvm_version_major = get_llvm_version_major(builder, llvm_output.llvm_config()); + + if llvm_output.link_shared() { + let mut dylib_path = vec![llvm_output.root_dir().join("lib")]; + dylib_path.extend(helpers::dylib_path()); + cfg.env(helpers::dylib_path_var(), t!(env::join_paths(dylib_path))); + } + + cfg.out_dir(&out_dir) + .profile(profile) + .define("LLVM_ENABLE_RUNTIMES", "libc") + .define("LLVM_LIBC_FULL_BUILD", "ON") + .define("LLVM_DEFAULT_TARGET_TRIPLE", gpu_target) + .define("LLVM_RUNTIMES_TARGET", gpu_target) + .define("LLVM_ENABLE_PER_TARGET_RUNTIME_DIR", "ON") + .define("LLVM_ENABLE_ASSERTIONS", "ON") + .define("LLVM_INCLUDE_TESTS", "OFF") + .define("LLVM_ROOT", llvm_output.root_dir().join("build")) + .define("LLVM_DIR", llvm_output.cmake_dir()) + .define("LIBC_NAMESPACE", format!("__llvm_libc_{llvm_version_major}")) + .define("CMAKE_C_COMPILER", &clang) + .define("CMAKE_CXX_COMPILER", &clangxx) + // We already require a suitable clang to build this step. Every working toolchain + // should build ar/ranlib along with clang, so we just directly specify them, to avoid + // relying on a (potentially incompatible) host ar/ranlib. + .define("CMAKE_AR", clang_bin_dir.join(exe("llvm-ar", target))) + .define("CMAKE_RANLIB", clang_bin_dir.join(exe("llvm-ranlib", target))) + .define("CMAKE_C_COMPILER_TARGET", gpu_target) + .define("CMAKE_CXX_COMPILER_TARGET", gpu_target) + .define("CMAKE_C_COMPILER_WORKS", "ON") + .define("CMAKE_CXX_COMPILER_WORKS", "ON") + .define("CMAKE_ASM_COMPILER_WORKS", "ON") + .define("CMAKE_C_FLAGS", &program_flags) + .define("CMAKE_CXX_FLAGS", &program_flags); + + if let Some(p) = offload_clang_cmake_dir(builder) { + cfg.define("Clang_DIR", p); + } + + cfg.build(); + + for p in &files { + if !p.exists() { + eprintln!("Failed to build libc-for-gpu file: {p:?}, {}", out_dir.display()); + helpers::exit_process(1); + } + } + + t!(stamp.write()); + + BuiltGpuLibc { libs: files } + } +} + #[derive(Clone)] pub struct BuiltEnzyme { /// Path to the libEnzyme dylib. diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 1359733089eee..0acd39d587863 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -806,6 +806,7 @@ impl<'a> Builder<'a> { llvm::Lld, llvm::Enzyme, llvm::RustOffload, + llvm::GpuLibc, llvm::CrtBeginEnd, llvm::Tpde, tool::RustdocGUITest, @@ -1553,6 +1554,14 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler self.ensure(llvm::Llvm { target: self.host_target }).llvm_config().to_owned() } + /// Root output directory of the OpenMP/Offload runtimes for `target` + /// + /// Deliberately not under `llvm_output_dir`, since running cmake twice in the same folder is + /// known to cause issues, like deleting existing binaries. + pub fn offload_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(target).join("offload") + } + /// Updates all submodules, and exits with an error if submodule /// management is disabled and the submodule does not exist. pub fn require_and_update_all_submodules(&self) { diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile index 911bb168d2489..fe771c29f7ff3 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile @@ -80,6 +80,10 @@ RUN ./build-zstd.sh COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh +RUN yum install -y python3-pip && \ + pip3 install --no-cache-dir pyyaml==6.0.1 && \ + yum clean all + ENV PGO_HOST=x86_64-unknown-linux-gnu ENV HOSTS=x86_64-unknown-linux-gnu diff --git a/src/doc/rustc-dev-guide/src/offload/installation.md b/src/doc/rustc-dev-guide/src/offload/installation.md index 2bb7ea98f6d5b..aa6250fea43f7 100644 --- a/src/doc/rustc-dev-guide/src/offload/installation.md +++ b/src/doc/rustc-dev-guide/src/offload/installation.md @@ -21,8 +21,7 @@ cd rust If you would rather reuse an existing clang than build one, drop `--enable-clang` and pass `--enable-llvm-offload-clang-dir=` -instead. -It should match the (major version of the) LLVM in `src/llvm-project`. +instead. It should match the (major version of the) LLVM in `src/llvm-project`. Afterwards you can build rustc using: ```console