From 9d291cbaab5e7c0f857ff19186187c99069def77 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Fri, 4 Sep 2026 00:35:20 -0400 Subject: [PATCH] Simplify mono entry-point computation for GPU Offload `std::offload` previously compiled too much code for the GPU. We first have a Host (CPU) compilation pass, in which we walk all `offload` intrinsics that launch a Kernel. We create a Manifest, and if we ever launch a generic kernel, than we add the kernel along with it's needed instantiations. If the launched kernel wasn't generic, we would skip it and not add it to the Manifest. During the following Device (GPU) compilation pass, we'd then add add all those generic kernels to the mono collector and force their instantiations. We'd also walk all functions and check if any non-generic functions would have a `rustc_offload_kernel` attribute, in which case we also add them to our collector. We'd also codegen all the other mono roots. After this PR, we simply write all launched Kernels into the Manifest, including the non-generic ones. When compiling for the GPU Device, we now drop all other mono roots and only add the ones from our Manifest. This makes compiling core under build-std extremely fast. To make core usage more reliable, `-Zoffload=Device` now also implies `-Zcross-crate-inline-threshold=always`, so functions will always be available. Our more aggressive mono roots computation makes it cheap. --- compiler/rustc_monomorphize/src/collector.rs | 52 ++------------ compiler/rustc_session/src/config.rs | 7 ++ .../offload-device-manifest-roots/dep.rs | 13 ++++ .../offload-device-manifest-roots/host.rs | 33 +++++++++ .../offload-device-manifest-roots/rmake.rs | 70 +++++++++++++++++++ 5 files changed, 127 insertions(+), 48 deletions(-) create mode 100644 tests/run-make/offload-device-manifest-roots/dep.rs create mode 100644 tests/run-make/offload-device-manifest-roots/host.rs create mode 100644 tests/run-make/offload-device-manifest-roots/rmake.rs diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 5d7820df622b7..86830343f1ea2 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -1484,7 +1484,9 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec, mode: MonoItemCollectionStrategy) -> Vec, mode: MonoItemCollectionStrategy) -> Vec bool { + ll.lines().any(|line| line.starts_with("define") && line.contains(name)) +} + +fn main() { + // Host build of the dependency, so the metadata pass can resolve `dep::helper`. + rustc().input("dep.rs").run(); + + // Pass 1: Our manifest now includes both generic and non-generic kernels that were launched. + rustc() + .input("host.rs") + .extern_("dep", "libdep.rlib") + .arg("-Zunstable-options") + .arg("-Zoffload=HostMetadata=kernels.manifest") + .arg("-Csymbol-mangling-version=v0") + .arg("-Clto=fat") + .emit("metadata") + .run(); + + // Pass 2a: the dependency for the device. It reads a manifest naming kernels in a crate it + // has never heard of, has no launched kernels of its own, and so has nothing to codegen. + // FIXME(offload): In the future, we should add better errorhandling here. It's fine to not find + // the kernels mentioned in the manifest if this is just a dep. However, if the Manifest entry + // names this crate and the path to the Kernel does not resolve, then we should error. The + // decoder should be able to tell the difference between both cases. + rustc() + .input("dep.rs") + .arg("-Zunstable-options") + .arg("-Zoffload=Device=kernels.manifest") + .arg("-Csymbol-mangling-version=v0") + .codegen_units(1) + .emit("link,llvm-ir") + .out_dir("device") + .run(); + if Path::new("device/dep.ll").exists() { + let dep_ll = rfs::read_to_string("device/dep.ll"); + assert!(!defines(&dep_ll, "helper"), "`dep::helper` was codegened in its own crate"); + } + + // Pass 2b: the kernel crate for the device. + rustc() + .input("host.rs") + .extern_("dep", "device/libdep.rlib") + .arg("-Zunstable-options") + .arg("-Zoffload=Device=kernels.manifest") + .arg("-Csymbol-mangling-version=v0") + .arg("-Clto=fat") + .codegen_units(1) + .emit("llvm-ir") + .out_dir("device") + .run(); + let ll = rfs::read_to_string("device/host.ll"); + assert!(defines(&ll, "launched"), "the launched kernel is missing"); + assert!(defines(&ll, "helper"), "`dep::helper` was not instantiated in the kernel crate"); + assert!(!defines(&ll, "dormant"), "an unlaunched kernel was compiled"); + assert!(!defines(&ll, "plain_pub"), "a function no kernel reaches was compiled"); + // The entry point would be a root under the usual rules; on the device it is not launched. + assert!(!defines(&ll, "main"), "the host entry point was compiled for the device"); +}