From 4d52fe93a207098976f78bcd95bf8477f8a10131 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Wed, 15 Jul 2026 18:13:56 +0300 Subject: [PATCH 1/8] Add 3-pass compilation to support generics and remove `no_mangle` attr --- Cargo.lock | 2 + compiler/rustc_builtin_macros/src/offload.rs | 40 +- compiler/rustc_codegen_llvm/src/back/write.rs | 14 +- compiler/rustc_codegen_llvm/src/intrinsic.rs | 6 +- .../src/back/symbol_export.rs | 94 ++++- compiler/rustc_codegen_ssa/src/back/write.rs | 33 +- compiler/rustc_codegen_ssa/src/base.rs | 20 +- compiler/rustc_middle/src/mono.rs | 6 + compiler/rustc_monomorphize/Cargo.toml | 2 + compiler/rustc_monomorphize/src/collector.rs | 102 ++++- .../rustc_monomorphize/src/diagnostics.rs | 14 + compiler/rustc_monomorphize/src/lib.rs | 1 + .../src/offload_manifest.rs | 396 ++++++++++++++++++ .../rustc_monomorphize/src/partitioning.rs | 10 + compiler/rustc_session/src/config.rs | 6 + compiler/rustc_session/src/options.rs | 17 +- compiler/rustc_symbol_mangling/src/lib.rs | 104 +++-- .../codegen-llvm/gpu_offload/control_flow.rs | 8 +- tests/codegen-llvm/gpu_offload/slice_host.rs | 4 +- tests/pretty/offload/offload_kernel.device.pp | 1 - tests/pretty/offload/offload_kernel.host.pp | 2 +- 21 files changed, 792 insertions(+), 90 deletions(-) create mode 100644 compiler/rustc_monomorphize/src/offload_manifest.rs diff --git a/Cargo.lock b/Cargo.lock index ce3fd04faf3bb..3ddb0c6981318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4528,12 +4528,14 @@ name = "rustc_monomorphize" version = "0.0.0" dependencies = [ "rustc_abi", + "rustc_ast", "rustc_data_structures", "rustc_errors", "rustc_hir", "rustc_index", "rustc_macros", "rustc_middle", + "rustc_serialize", "rustc_session", "rustc_span", "rustc_symbol_mangling", diff --git a/compiler/rustc_builtin_macros/src/offload.rs b/compiler/rustc_builtin_macros/src/offload.rs index e47ccc0d85f7d..96cb9372d3e09 100644 --- a/compiler/rustc_builtin_macros/src/offload.rs +++ b/compiler/rustc_builtin_macros/src/offload.rs @@ -1,6 +1,6 @@ +use rustc_ast::ast; use rustc_ast::token::{Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree}; -use rustc_ast::{AttrItem, ast}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_session::config::Offload; use rustc_span::{DUMMY_SP, Ident, Span, sym}; @@ -9,7 +9,12 @@ use thin_vec::thin_vec; use crate::diagnostics; fn compile_for_device(ecx: &mut ExtCtxt<'_>) -> bool { - ecx.sess.opts.unstable_opts.offload.contains(&Offload::Device) + ecx.sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, Offload::Device | Offload::DeviceWithManifest(_))) } fn outer_normal_attr(normal: &Box, id: ast::AttrId, span: Span) -> ast::Attribute { @@ -45,7 +50,6 @@ fn extract_fn( /// This expands to the host-side function: /// /// ``` -/// #[unsafe(no_mangle)] /// #[inline(never)] /// fn foo(_: &[f32], _: &[f32], _: *mut f32) { /// ::core::panicking::panic("not implemented") @@ -56,7 +60,6 @@ fn extract_fn( /// /// ``` /// #[rustc_offload_kernel] -/// #[unsafe(no_mangle)] /// unsafe extern "gpu-kernel" fn foo(a: &[f32], b: &[f32], c: *mut f32) { /// *c = a[0] + b[0]; /// } @@ -110,24 +113,9 @@ pub(crate) fn expand_kernel( span, ); - // unsafe(no_mangle) attr - let unsafe_item = AttrItem { - unsafety: ast::Safety::Unsafe(span), - path: ast::Path::from_ident(Ident::new(sym::no_mangle, span)), - args: ast::AttrArgs::Empty, - span, - }; - - let no_mangle_attr = Box::new(ast::NormalAttr { item: unsafe_item, tokens: None }); - let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); - let unsafe_no_mangle = outer_normal_attr(&no_mangle_attr, new_id, span); - let device_item = { - let mut item = ecx.item( - span, - thin_vec![rustc_offload_kernel, unsafe_no_mangle], - ast::ItemKind::Fn(device_fn), - ); + let mut item = + ecx.item(span, thin_vec![rustc_offload_kernel.clone()], ast::ItemKind::Fn(device_fn)); item.vis = vis.clone(); Annotatable::Item(item) }; @@ -187,12 +175,12 @@ pub(crate) fn expand_kernel( let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); let inline_never = outer_normal_attr(&inline_never_attr, new_id, span); - let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); - let unsafe_no_mangle = outer_normal_attr(&no_mangle_attr, new_id, span); - let host_item = { - let mut item = - ecx.item(span, thin_vec![unsafe_no_mangle, inline_never], ast::ItemKind::Fn(host_fn)); + let mut item = ecx.item( + span, + thin_vec![rustc_offload_kernel, inline_never], + ast::ItemKind::Fn(host_fn), + ); item.vis = vis.clone(); Annotatable::Item(item) }; diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 400a9ce89df8b..80d5ff31c730e 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -742,7 +742,12 @@ pub(crate) unsafe fn llvm_optimize( llvm::set_value_name(new_fn, &name); } - if cgcx.target_is_like_gpu && config.offload.contains(&config::Offload::Device) { + if cgcx.target_is_like_gpu + && config + .offload + .iter() + .any(|o| matches!(o, config::Offload::Device | config::Offload::DeviceWithManifest(_))) + { let cx = SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size); for func in cx.get_functions() { @@ -813,7 +818,12 @@ pub(crate) unsafe fn llvm_optimize( ) }; - if cgcx.target_is_like_gpu && config.offload.contains(&config::Offload::Device) { + if cgcx.target_is_like_gpu + && config + .offload + .iter() + .any(|o| matches!(o, config::Offload::Device | config::Offload::DeviceWithManifest(_))) + { let device_path = cgcx.output_filenames.path(OutputType::Object); let device_dir = device_path.parent().unwrap(); let device_out = device_dir.join("device.bin"); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 4ae85af897527..4c54b60c6d48a 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -26,7 +26,9 @@ use rustc_session::config::CrateType; use rustc_session::diagnostics::feature_err; use rustc_session::lint::builtin::DEPRECATED_LLVM_INTRINSIC; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; -use rustc_symbol_mangling::{mangle_internal_symbol, symbol_name_for_instance_in_crate}; +use rustc_symbol_mangling::{ + mangle_internal_symbol, mangle_offload_export, symbol_name_for_instance_in_crate, +}; use rustc_target::callconv::PassMode; use rustc_target::spec::Arch; use tracing::debug; @@ -1850,7 +1852,7 @@ fn codegen_offload<'ll, 'tcx>( _ => panic!("unparsable"), }; let args = get_args_from_tuple(bx, args[4], fn_target); - let target_symbol = symbol_name_for_instance_in_crate(tcx, fn_target, LOCAL_CRATE); + let target_symbol = mangle_offload_export(tcx, fn_target); let sig = tcx.fn_sig(fn_target.def_id()).skip_binder(); let sig = tcx.instantiate_bound_regions_with_erased(sig); diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 941e9d28fc7e1..bdff48ced55b6 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -3,6 +3,7 @@ use std::collections::hash_map::Entry::*; use rustc_abi::{CanonAbi, X86Call}; use rustc_ast::expand::allocator::{AllocatorKind, NO_ALLOC_SHIM_IS_UNSTABLE, global_fn_name}; use rustc_crate_store::CrateDepKind; +use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::unord::UnordMap; use rustc_hir as hir; use rustc_hir::def::DefKind; @@ -19,7 +20,7 @@ use rustc_middle::ty::{ use rustc_middle::util::Providers; use rustc_session::config::CrateType; use rustc_span::Span; -use rustc_symbol_mangling::mangle_internal_symbol; +use rustc_symbol_mangling::{is_offload_kernel, mangle_internal_symbol}; use rustc_target::spec::{Arch, Os, TlsModel}; use tracing::debug; @@ -244,6 +245,51 @@ pub fn exported_non_generic_symbols_helper<'tcx>( )); } + let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { + matches!( + o, + rustc_session::config::Offload::DeviceWithManifest(_) + | rustc_session::config::Offload::Device + ) + }); + if is_device_offload { + let crate_items = tcx.hir_crate_items(()); + let mut seen: rustc_data_structures::fx::FxHashSet = symbols + .iter() + .filter_map(|(s, _)| match s { + ExportedSymbol::NonGeneric(d) => Some(*d), + _ => None, + }) + .collect(); + + let mut try_emit_offload_kernel = |def_id: DefId, seen: &mut FxHashSet| { + if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { + return; + } + if !tcx.generics_of(def_id).requires_monomorphization(tcx) + && is_offload_kernel(tcx.codegen_fn_attrs(def_id)) + && seen.insert(def_id) + { + symbols.push(( + ExportedSymbol::NonGeneric(def_id), + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Text, + used: false, + rustc_std_internal_symbol: false, + }, + )); + } + }; + + for id in crate_items.free_items() { + try_emit_offload_kernel(id.owner_id.to_def_id(), &mut seen); + } + for id in crate_items.impl_items() { + try_emit_offload_kernel(id.owner_id.to_def_id(), &mut seen); + } + } + // Sort so we get a stable incr. comp. hash. symbols.sort_by_cached_key(|s| s.0.symbol_name_for_local_instance(tcx)); @@ -260,7 +306,16 @@ fn exported_generic_symbols_provider_local<'tcx>( let mut symbols: Vec<_> = vec![]; - if tcx.local_crate_exports_generics() { + let export_generics = tcx.local_crate_exports_generics(); + let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { + matches!( + o, + rustc_session::config::Offload::DeviceWithManifest(_) + | rustc_session::config::Offload::Device + ) + }); + + if export_generics || is_device_offload { use rustc_hir::attrs::Linkage; use rustc_middle::mono::{MonoItem, Visibility}; use rustc_middle::ty::InstanceKind; @@ -306,6 +361,14 @@ fn exported_generic_symbols_provider_local<'tcx>( }) }; + let is_offload_instance = |mono_item: &MonoItem<'tcx>| { + if let MonoItem::Fn(instance) = mono_item { + is_offload_kernel(tcx.codegen_fn_attrs(instance.def_id())) + } else { + false + } + }; + // The symbols created in this loop are sorted below it #[allow(rustc::potential_query_instability)] for (mono_item, data) in cgus.iter().flat_map(|cgu| cgu.items().iter()) { @@ -321,7 +384,9 @@ fn exported_generic_symbols_provider_local<'tcx>( continue; } - if !tcx.sess.opts.share_generics() { + let item_is_offload = is_offload_instance(mono_item); + + if !item_is_offload && !tcx.sess.opts.share_generics() { if tcx.codegen_fn_attrs(mono_item.def_id()).inline == rustc_hir::attrs::InlineAttr::Never { @@ -338,15 +403,22 @@ fn exported_generic_symbols_provider_local<'tcx>( MonoItem::Fn(Instance { def: InstanceKind::Item(def), args }) => { let has_generics = args.non_erasable_generics().next().is_some(); - let should_export = - has_generics && is_instantiable_downstream(Some(def), &args); + let should_export = if item_is_offload { + has_generics + } else { + has_generics && is_instantiable_downstream(Some(def), &args) + }; if should_export { let symbol = ExportedSymbol::Generic(def, args); symbols.push(( symbol, SymbolExportInfo { - level: SymbolExportLevel::Rust, + level: if item_is_offload { + SymbolExportLevel::C + } else { + SymbolExportLevel::Rust + }, kind: SymbolExportKind::Text, used: false, rustc_std_internal_symbol: false, @@ -561,11 +633,19 @@ fn symbol_export_level(tcx: TyCtxt<'_>, sym_def_id: DefId) -> SymbolExportLevel // are not considered for export let codegen_fn_attrs = tcx.codegen_fn_attrs(sym_def_id); let is_extern = codegen_fn_attrs.contains_extern_indicator(); + let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { + matches!( + o, + rustc_session::config::Offload::DeviceWithManifest(_) + | rustc_session::config::Offload::Device + ) + }); + let is_offload = is_device_offload && is_offload_kernel(codegen_fn_attrs); let std_internal = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL); let eii = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM); - if is_extern && !std_internal && !eii { + if (is_extern && !std_internal && !eii) || is_offload { let target = &tcx.sess.target.llvm_target; // WebAssembly cannot export data symbols, so reduce their export level // FIXME(jdonszelmann) don't do a substring match here. diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 2eaceb68a67a2..46ac35cb7dbc5 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -464,6 +464,29 @@ pub(crate) fn start_async_codegen( } } +/// Create an `OngoingCodegen` that has no coordinator thread and will finish +/// immediately when joined. This is used for the offload host-metadata pass, +/// that only need to run the monomorphization collector. +pub(crate) fn empty_ongoing_codegen( + backend: B, + tcx: TyCtxt<'_>, +) -> OngoingCodegen { + let (coordinator_send, _) = channel::>(); + let (codegen_worker_send, codegen_worker_receive) = channel(); + drop(codegen_worker_send); + + let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); + drop(shared_emitter); + + OngoingCodegen { + backend, + codegen_worker_receive, + shared_emitter_main, + coordinator: Coordinator { sender: coordinator_send, future: None, phantom: PhantomData }, + output_filenames: Arc::clone(tcx.output_filenames(())), + } +} + fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( sess: &Session, incr_comp_session: Option<&IncrCompSession>, @@ -2112,7 +2135,15 @@ pub struct Coordinator { impl Coordinator { fn join(mut self) -> std::thread::Result, ()>> { - self.future.take().unwrap().join() + if let Some(future) = self.future.take() { + future.join() + } else { + // Used for passes that do not codegen anything (e.g. the offload host-metadata pass). + Ok(Ok(MaybeLtoModules::NoLto(CompiledModules { + modules: vec![], + allocator_module: None, + }))) + } } } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 0468e3de18d8b..d615c8daab550 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -41,8 +41,9 @@ use tracing::{debug, info}; use crate::assert_module_sources::CguReuse; use crate::back::link::are_upstream_rust_objects_already_included; use crate::back::write::{ - ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, start_async_codegen, - submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, submit_pre_lto_module_to_llvm, + ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, empty_ongoing_codegen, + start_async_codegen, submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, + submit_pre_lto_module_to_llvm, }; use crate::common::{self, IntPredicate, RealPredicate, TypeKind}; use crate::meth::load_vtable; @@ -723,6 +724,21 @@ pub fn codegen_crate< tcx.dcx().emit_fatal(diagnostics::CpuRequired); } + // A `HostMetadata` pass only exists to collect the set of generic kernel instantiations + // required by the host and write the offload manifest. + let is_host_metadata = tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, rustc_session::config::Offload::HostMetadata(_))); + + if is_host_metadata { + let _ = tcx.collect_and_partition_mono_items(()); + return empty_ongoing_codegen(backend, tcx); + } + if let Some(target_cpu) = &tcx.sess.opts.cg.target_cpu && tcx.sess.target.unsupported_cpus.contains(&target_cpu.into()) { diff --git a/compiler/rustc_middle/src/mono.rs b/compiler/rustc_middle/src/mono.rs index dc9a94f79aa0f..1d15844f4ca96 100644 --- a/compiler/rustc_middle/src/mono.rs +++ b/compiler/rustc_middle/src/mono.rs @@ -160,6 +160,12 @@ impl<'tcx> MonoItem<'tcx> { return InstantiationMode::GloballyShared { may_conflict: false }; } + // Offload kernels are looked up by symbol name at runtime by the host. + // They must be emitted exactly once with external linkage. + if codegen_fn_attrs.flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) { + return InstantiationMode::GloballyShared { may_conflict: false }; + } + // This is technically a heuristic even though it's in the "not a heuristic" part of // instantiation mode selection. // It is surely possible to untangle this; the root problem is that the way we instantiate diff --git a/compiler/rustc_monomorphize/Cargo.toml b/compiler/rustc_monomorphize/Cargo.toml index 58ccf77903bab..c45232b2565a7 100644 --- a/compiler/rustc_monomorphize/Cargo.toml +++ b/compiler/rustc_monomorphize/Cargo.toml @@ -6,12 +6,14 @@ edition = "2024" [dependencies] # tidy-alphabetical-start rustc_abi = { path = "../rustc_abi" } +rustc_ast = { path = "../rustc_ast" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } rustc_macros = { path = "../rustc_macros" } rustc_middle = { path = "../rustc_middle" } +rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_symbol_mangling = { path = "../rustc_symbol_mangling" } diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 1ee6e0506dcdd..eb5741f68746f 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -831,8 +831,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { }; match terminator.kind { - mir::TerminatorKind::Call { ref func, .. } - | mir::TerminatorKind::TailCall { ref func, .. } => { + mir::TerminatorKind::Call { ref func, ref args, .. } + | mir::TerminatorKind::TailCall { ref func, ref args, .. } => { let callee_ty = func.ty(self.body, tcx); // *Before* monomorphizing, record that we already handled this mention. self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty)); @@ -865,7 +865,17 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { !force_indirect_call, source, &mut self.used_items, - ) + ); + + // TODO(Sa4dUs): check why it only collects non generic fns + if let ty::FnDef(def_id, _) = *callee_ty.kind() + && self.tcx.is_intrinsic(def_id, rustc_span::sym::offload) + && let Some(kernel) = args.first() + { + let kernel_ty = kernel.node.ty(self.body, self.tcx); + let kernel_ty = self.monomorphize(kernel_ty); + visit_fn_use(self.tcx, kernel_ty, false, source, &mut self.used_items); + } } mir::TerminatorKind::Drop { ref place, .. } => { let ty = place.ty(self.body, self.tcx).ty; @@ -1475,6 +1485,27 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec { + for instance in instances { + if instance.def_id().is_local() { + roots.push(dummy_spanned(MonoItem::Fn(instance))); + } + } + } + Err(e) => { + tcx.dcx().emit_err(crate::diagnostics::OffloadManifestReadError { + path: manifest_path.clone(), + err: e.to_string(), + }); + } + } + } + { let entry_fn = tcx.entry_fn(()); @@ -1499,6 +1530,39 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec( state.visited.into_inner().into_sorted(&mut hcx, true) }); + // Write out the offload manifest of required generic kernel instantiations. + if let Some(path) = tcx.sess.opts.unstable_opts.offload.iter().find_map(|o| { + if let rustc_session::config::Offload::HostMetadata(p) = o { Some(p) } else { None } + }) { + let instances: Vec> = mono_items + .iter() + .filter_map(|item| { + if let MonoItem::Fn(instance) = item { + if tcx + .codegen_fn_attrs(instance.def_id()) + .flags + .contains(CodegenFnAttrFlags::OFFLOAD_KERNEL) + { + Some(*instance) + } else { + None + } + } else { + None + } + }) + .collect(); + if let Err(e) = + crate::offload_manifest::write_manifest(std::path::Path::new(path), tcx, &instances) + { + tcx.dcx().emit_fatal(crate::diagnostics::OffloadManifestWriteError { + path: path.clone(), + err: e.to_string(), + }); + } + } + (mono_items, state.usage_map.into_inner()) } diff --git a/compiler/rustc_monomorphize/src/diagnostics.rs b/compiler/rustc_monomorphize/src/diagnostics.rs index 27705a9837ad3..df4b54abc2258 100644 --- a/compiler/rustc_monomorphize/src/diagnostics.rs +++ b/compiler/rustc_monomorphize/src/diagnostics.rs @@ -50,6 +50,20 @@ pub(crate) struct CouldntDumpMonoStats { pub error: String, } +#[derive(Diagnostic)] +#[diag("could not write offload monomorphization manifest to `{$path}`: {$err}")] +pub(crate) struct OffloadManifestWriteError { + pub path: String, + pub err: String, +} + +#[derive(Diagnostic)] +#[diag("could not read offload monomorphization manifest from `{$path}`: {$err}")] +pub(crate) struct OffloadManifestReadError { + pub path: String, + pub err: String, +} + #[derive(Diagnostic)] #[diag("the above error was encountered while instantiating `{$kind} {$instance}`")] pub(crate) struct EncounteredErrorWhileInstantiating<'tcx> { diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index c72ee9dd23393..a15ebe33651c6 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -16,6 +16,7 @@ mod collector; mod diagnostics; mod graph_checks; mod mono_checks; +mod offload_manifest; mod partitioning; mod util; diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload_manifest.rs new file mode 100644 index 0000000000000..adfbebc435568 --- /dev/null +++ b/compiler/rustc_monomorphize/src/offload_manifest.rs @@ -0,0 +1,396 @@ +//! Offload manifest: communicates required generic kernel instantiations +//! between host-metadata and device compilation passes. +//! +//! Uses `TyEncoder`/`TyDecoder` to serialize `ty::Instance`. DefIds are +//! encoded as (crate name, DefPath) pairs for stability. + +use std::fs; + +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::sync::Lock; +use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE}; +use rustc_middle::ty::codec::{TyDecoder, TyEncoder}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_serialize::opaque::{FileEncoder, MemDecoder}; +use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; +use rustc_span::{ + BlobDecoder, BytePos, ByteSymbol, Pos, Span, SpanDecoder, SpanEncoder, Symbol, SyntaxContext, +}; + +pub(crate) struct OffloadManifestEncoder<'a, 'tcx> { + encoder: FileEncoder<'a>, + type_shorthands: FxHashMap, usize>, + predicate_shorthands: FxHashMap, usize>, + tcx: TyCtxt<'tcx>, +} + +impl<'a, 'tcx> OffloadManifestEncoder<'a, 'tcx> { + pub(crate) fn new(path: &'a std::path::Path, tcx: TyCtxt<'tcx>) -> std::io::Result { + let encoder = FileEncoder::new(path)?; + Ok(OffloadManifestEncoder { + encoder, + type_shorthands: FxHashMap::default(), + predicate_shorthands: FxHashMap::default(), + tcx, + }) + } + + pub(crate) fn finish(mut self) -> std::io::Result<()> { + self.encoder.finish().map(|_| ()).map_err(|(_, e)| e) + } +} + +impl<'a, 'tcx> Encoder for OffloadManifestEncoder<'a, 'tcx> { + fn emit_usize(&mut self, v: usize) { + self.encoder.emit_usize(v); + } + fn emit_u128(&mut self, v: u128) { + self.encoder.emit_u128(v); + } + fn emit_u64(&mut self, v: u64) { + self.encoder.emit_u64(v); + } + fn emit_u32(&mut self, v: u32) { + self.encoder.emit_u32(v); + } + fn emit_u16(&mut self, v: u16) { + self.encoder.emit_u16(v); + } + fn emit_u8(&mut self, v: u8) { + self.encoder.emit_u8(v); + } + fn emit_isize(&mut self, v: isize) { + self.encoder.emit_isize(v); + } + fn emit_i128(&mut self, v: i128) { + self.encoder.emit_i128(v); + } + fn emit_i64(&mut self, v: i64) { + self.encoder.emit_i64(v); + } + fn emit_i32(&mut self, v: i32) { + self.encoder.emit_i32(v); + } + fn emit_i16(&mut self, v: i16) { + self.encoder.emit_i16(v); + } + fn emit_i8(&mut self, v: i8) { + self.encoder.emit_i8(v); + } + fn emit_raw_bytes(&mut self, v: &[u8]) { + self.encoder.emit_raw_bytes(v); + } +} + +impl<'a, 'tcx> SpanEncoder for OffloadManifestEncoder<'a, 'tcx> { + fn encode_span(&mut self, _span: Span) { + // Spans are not needed in the manifest, encode a dummy span. + self.emit_usize(0); + self.emit_usize(0); + self.emit_u32(0); + } + + fn encode_symbol(&mut self, sym: rustc_span::Symbol) { + sym.as_str().encode(self); + } + + fn encode_byte_symbol(&mut self, byte_sym: ByteSymbol) { + let bytes = byte_sym.as_byte_str(); + debug_assert!( + bytes.is_empty(), + "ByteSymbols with content are not expected in offload manifests" + ); + self.emit_usize(bytes.len()); + self.emit_raw_bytes(bytes.as_ref()) + } + + fn encode_expn_id(&mut self, _expn_id: rustc_span::ExpnId) { + self.emit_u32(0); + } + + fn encode_syntax_context(&mut self, _syntax_context: SyntaxContext) { + self.emit_u32(0); + } + + fn encode_crate_num(&mut self, crate_num: rustc_span::def_id::CrateNum) { + crate_num.as_u32().encode(self); + } + + fn encode_def_index(&mut self, def_index: rustc_span::def_id::DefIndex) { + def_index.as_u32().encode(self); + } + + fn encode_def_id(&mut self, def_id: rustc_span::def_id::DefId) { + let crate_name = self.tcx.crate_name(def_id.krate); + let def_path = self.tcx.def_path(def_id); + crate_name.encode(self); + def_path.to_string_no_crate_verbose().encode(self); + } +} + +impl<'a, 'tcx> TyEncoder<'tcx> for OffloadManifestEncoder<'a, 'tcx> { + const CLEAR_CROSS_CRATE: bool = true; + + fn position(&self) -> usize { + self.encoder.position() + } + + fn type_shorthands(&mut self) -> &mut FxHashMap, usize> { + &mut self.type_shorthands + } + + fn predicate_shorthands(&mut self) -> &mut FxHashMap, usize> { + &mut self.predicate_shorthands + } + + fn encode_alloc_id(&mut self, _alloc_id: &rustc_middle::mir::interpret::AllocId) { + // AllocIds are not expected in the manifest. + } +} + +const UNRESOLVED_DEF_ID: DefId = DefId { + krate: rustc_span::def_id::CrateNum::MAX, + index: rustc_span::def_id::DefIndex::from_u32(0), +}; + +/// Decoder used to read the offload monomorphization manifest. +pub(crate) struct OffloadManifestDecoder<'a, 'tcx> { + decoder: MemDecoder<'a>, + type_shorthands: Lock>>, + #[allow(dead_code)] + predicate_shorthands: Lock>>, + tcx: TyCtxt<'tcx>, + /// Map from (crate name, DefPath string) to DefId, used to resolve DefIds + /// across compilation sessions where StableCrateId differs. + def_path_map: Lock>>, +} + +impl<'a, 'tcx> OffloadManifestDecoder<'a, 'tcx> { + pub(crate) fn new(data: &'a [u8], tcx: TyCtxt<'tcx>) -> Result { + let decoder = MemDecoder::new(data, 0)?; + Ok(OffloadManifestDecoder { + decoder, + type_shorthands: Lock::new(FxHashMap::default()), + predicate_shorthands: Lock::new(FxHashMap::default()), + tcx, + def_path_map: Lock::new(None), + }) + } + + /// (crate name, DefPath) -> DefId map for resolving cross-session DefIds. + fn get_or_build_def_path_map(&self) -> FxHashMap<(Symbol, String), DefId> { + let mut guard = self.def_path_map.lock(); + if let Some(map) = guard.as_ref() { + return map.clone(); + } + let map = Self::build_def_path_map(self.tcx); + *guard = Some(map.clone()); + map + } + + /// Build a (crate name, DefPath) -> DefId map. Owns the format details. + fn build_def_path_map(tcx: TyCtxt<'tcx>) -> FxHashMap<(Symbol, String), DefId> { + let mut map: FxHashMap<(Symbol, String), DefId> = FxHashMap::default(); + + let local_crate_name = tcx.crate_name(LOCAL_CRATE); + let krate_items = tcx.hir_crate_items(()); + let local_def_ids = krate_items + .free_items() + .map(|id| id.owner_id.to_def_id()) + .chain(krate_items.trait_items().map(|id| id.owner_id.to_def_id())) + .chain(krate_items.impl_items().map(|id| id.owner_id.to_def_id())) + .chain(krate_items.foreign_items().map(|id| id.owner_id.to_def_id())); + for item_id in local_def_ids { + let def_id = item_id; + let def_path = tcx.def_path(def_id); + map.insert((local_crate_name, def_path.to_string_no_crate_verbose()), def_id); + } + + for &cnum in tcx.crates(()) { + if cnum == LOCAL_CRATE { + continue; + } + let crate_name = tcx.crate_name(cnum); + let num_defs = tcx.num_extern_def_ids(cnum); + for i in 0..num_defs { + let def_id = DefId { krate: cnum, index: DefIndex::from_usize(i) }; + let def_path = tcx.def_path(def_id); + map.entry((crate_name, def_path.to_string_no_crate_verbose())).or_insert(def_id); + } + } + + map + } +} + +impl<'a, 'tcx> Decoder for OffloadManifestDecoder<'a, 'tcx> { + fn read_usize(&mut self) -> usize { + self.decoder.read_usize() + } + fn read_u128(&mut self) -> u128 { + self.decoder.read_u128() + } + fn read_u64(&mut self) -> u64 { + self.decoder.read_u64() + } + fn read_u32(&mut self) -> u32 { + self.decoder.read_u32() + } + fn read_u16(&mut self) -> u16 { + self.decoder.read_u16() + } + fn read_u8(&mut self) -> u8 { + self.decoder.read_u8() + } + fn read_isize(&mut self) -> isize { + self.decoder.read_isize() + } + fn read_i128(&mut self) -> i128 { + self.decoder.read_i128() + } + fn read_i64(&mut self) -> i64 { + self.decoder.read_i64() + } + fn read_i32(&mut self) -> i32 { + self.decoder.read_i32() + } + fn read_i16(&mut self) -> i16 { + self.decoder.read_i16() + } + fn read_i8(&mut self) -> i8 { + self.decoder.read_i8() + } + fn read_raw_bytes(&mut self, len: usize) -> &[u8] { + self.decoder.read_raw_bytes(len) + } + fn peek_byte(&self) -> u8 { + self.decoder.peek_byte() + } + fn position(&self) -> usize { + self.decoder.position() + } +} + +impl<'a, 'tcx> BlobDecoder for OffloadManifestDecoder<'a, 'tcx> { + fn decode_symbol(&mut self) -> rustc_span::Symbol { + let s: String = Decodable::decode(self); + rustc_span::Symbol::intern(&s) + } + + fn decode_byte_symbol(&mut self) -> ByteSymbol { + let len = self.read_usize(); + let bytes = self.read_raw_bytes(len); + ByteSymbol::intern(bytes) + } + + fn decode_def_index(&mut self) -> rustc_span::def_id::DefIndex { + let v = self.read_u32(); + rustc_span::def_id::DefIndex::from_u32(v) + } +} + +impl<'a, 'tcx> SpanDecoder for OffloadManifestDecoder<'a, 'tcx> { + fn decode_span(&mut self) -> Span { + let lo = self.read_usize(); + let hi = self.read_usize(); + let _ctxt = self.read_u32(); + Span::new(BytePos::from_usize(lo), BytePos::from_usize(hi), SyntaxContext::root(), None) + } + + fn decode_expn_id(&mut self) -> rustc_span::ExpnId { + let _ = self.read_u32(); + rustc_span::ExpnId::root() + } + + fn decode_syntax_context(&mut self) -> SyntaxContext { + let _ = self.read_u32(); + SyntaxContext::root() + } + + fn decode_crate_num(&mut self) -> rustc_span::def_id::CrateNum { + let v = self.read_u32(); + rustc_span::def_id::CrateNum::from_u32(v) + } + + fn decode_def_id(&mut self) -> rustc_span::def_id::DefId { + let crate_name: String = Decodable::decode(self); + let crate_name = Symbol::intern(&crate_name); + let def_path_str: String = Decodable::decode(self); + let map = self.get_or_build_def_path_map(); + map.get(&(crate_name, def_path_str)).copied().unwrap_or(UNRESOLVED_DEF_ID) + } + + fn decode_attr_id(&mut self) -> rustc_ast::AttrId { + self.tcx.dcx().fatal("AttrIds are not expected in offload manifests"); + } +} + +impl<'a, 'tcx> TyDecoder<'tcx> for OffloadManifestDecoder<'a, 'tcx> { + const CLEAR_CROSS_CRATE: bool = true; + + fn interner(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn cached_ty_for_shorthand(&mut self, shorthand: usize, or_insert_with: F) -> Ty<'tcx> + where + F: FnOnce(&mut Self) -> Ty<'tcx>, + { + if let Some(ty) = self.type_shorthands.lock().get(&shorthand) { + return *ty; + } + let ty = or_insert_with(self); + self.type_shorthands.lock().insert(shorthand, ty); + ty + } + + fn with_position(&mut self, pos: usize, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + let new_decoder = self.decoder.split_at(pos); + let old_decoder = std::mem::replace(&mut self.decoder, new_decoder); + let result = f(self); + self.decoder = old_decoder; + result + } + + fn decode_alloc_id(&mut self) -> rustc_middle::mir::interpret::AllocId { + self.tcx.dcx().fatal("AllocIds are not expected in offload manifests"); + } +} + +/// Write a list of offload kernel instances to the manifest file. +pub(crate) fn write_manifest<'tcx>( + path: &std::path::Path, + tcx: TyCtxt<'tcx>, + instances: &[ty::Instance<'tcx>], +) -> std::io::Result<()> { + let mut encoder = OffloadManifestEncoder::new(path, tcx)?; + instances.encode(&mut encoder); + encoder.finish() +} + +/// Read a list of offload kernel instances from the manifest file. +pub(crate) fn read_manifest<'tcx>( + path: &std::path::Path, + tcx: TyCtxt<'tcx>, +) -> std::io::Result>> { + let data = fs::read(path)?; + let mut decoder = OffloadManifestDecoder::new(&data, tcx) + .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid manifest"))?; + + let payload_len = decoder.decoder.len() - decoder.position(); + if payload_len == 0 { + return Ok(Vec::new()); + } + + let instances: Vec> = Decodable::decode(&mut decoder); + + let instances: Vec<_> = instances + .into_iter() + .filter(|instance| instance.def_id().krate != rustc_span::def_id::CrateNum::MAX) + .collect(); + + Ok(instances) +} diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs index cdd18654f0930..1b3e411312ca6 100644 --- a/compiler/rustc_monomorphize/src/partitioning.rs +++ b/compiler/rustc_monomorphize/src/partitioning.rs @@ -844,6 +844,16 @@ fn mono_item_visibility<'tcx>( | InstanceKind::Shim(ShimKind::FnPtrAddr(..)) => return Visibility::Hidden, }; + let attrs = tcx.codegen_fn_attrs(def_id); + if attrs.flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) { + *can_be_internalized = false; + return default_visibility( + tcx, + def_id, + instance.args.non_erasable_generics().next().is_some(), + ); + } + // Both the `start_fn` lang item and `main` itself should not be exported, // so we give them with `Hidden` visibility but these symbols are // only referenced from the actual `main` symbol which we unfortunately diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 0303081e2c627..d424991f2921f 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -198,10 +198,16 @@ pub enum CoverageLevel { pub enum Offload { /// Entry point for `std::offload`, enables kernel compilation for a gpu device Device, + /// Like `Device`, but reads a manifest of required generic kernel instantiations + /// produced by a previous `HostMetadata` pass. + DeviceWithManifest(String), /// Second step in the offload pipeline, generates the host code to call kernels. Host(String), /// Test is similar to Host, but allows testing without a device artifact. Test, + /// First step in the offload pipeline: compile for the host but only emit a manifest of + /// kernel instantiations required by the host code. + HostMetadata(String), } /// The different settings that the `-Z codegen-emit-retag` flag can have. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 20d1ff55eab6e..65a94bc9e314c 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -819,8 +819,7 @@ mod desc { "a comma-separated list of strings, with elements beginning with + or -"; pub(crate) const parse_pointer_authentication_list_with_polarity: &str = "a comma-separated list of options, each of the form `+` or `-`, where `` is one of: `aarch64-jump-table-hardening`, `auth-traps`, `calls`, `elf-got`, `function-pointer-type-discrimination`, `indirect-gotos`, `init-fini`, `init-fini-address-discrimination`, `intrinsics`, `return-addresses`, `typeinfo-vt-ptr-discrimination`, `vt-ptr-addr-discrimination` or `vt-ptr-type-discrimination`"; pub(crate) const parse_autodiff: &str = "a comma separated list of settings: `Enable`, `PrintSteps`, `PrintTA`, `PrintTAFn`, `PrintAA`, `PrintPerf`, `PrintModBefore`, `PrintModAfter`, `PrintModFinal`, `PrintPasses`, `NoPostopt`, `LooseTypes`, `Inline`, `NoTT`"; - pub(crate) const parse_offload: &str = - "a comma separated list of settings: `Host=`, `Device`, `Test`"; + pub(crate) const parse_offload: &str = "a comma separated list of settings: `Host=`, `HostMetadata=`, `Device`, `DeviceWithManifest=`, `Test`"; pub(crate) const parse_comma_list: &str = "a comma-separated list of strings"; pub(crate) const parse_opt_comma_list: &str = parse_comma_list; pub(crate) const parse_number: &str = "a number"; @@ -1514,6 +1513,13 @@ pub mod parse { return false; } } + "HostMetadata" => { + if let Some(p) = arg { + Offload::HostMetadata(p.to_string()) + } else { + return false; + } + } "Device" => { if let Some(_) = arg { // Device does not accept a value @@ -1521,6 +1527,13 @@ pub mod parse { } Offload::Device } + "DeviceWithManifest" => { + if let Some(p) = arg { + Offload::DeviceWithManifest(p.to_string()) + } else { + return false; + } + } "Test" => { if let Some(_) = arg { // Test does not accept a value diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index 482848578a81b..a6b7485223f11 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -106,6 +106,13 @@ pub mod test; pub use v0::{mangle_cgu, mangle_internal_symbol}; +/// Offload kernels need custom v0 symbol treatment because the host +/// and device compilation passes run with different `stable_crate_id`s +/// so they cannot rely on the regular export-hash path. +pub fn is_offload_kernel(attrs: &CodegenFnAttrs) -> bool { + attrs.flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) +} + /// This function computes the symbol name for the given `instance` and the /// given instantiating crate. That is, if you know that instance X is /// instantiated in crate Y, this is the symbol name this instance would have. @@ -121,6 +128,18 @@ pub fn provide(providers: &mut Providers) { *providers = Providers { symbol_name: symbol_name_provider, ..*providers }; } +/// Compute the v0 symbol name for an offload kernel instance. Forces +/// `is_exportable: true` to omit the `stable_crate_id` disambiguator +/// (which differs between host and device passes). +pub fn mangle_offload_export<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> String { + let instantiating_crate = if is_generic(instance) { + Some(instance.upstream_monomorphization(tcx).unwrap_or(LOCAL_CRATE)) + } else { + None + }; + v0::mangle(tcx, instance, instantiating_crate, true) +} + // The `symbol_name` query provides the symbol name for calling a given // instance from the local crate. In particular, it will also look up the // correct symbol name of instances from upstream crates. @@ -293,45 +312,54 @@ fn compute_symbol_name<'tcx>( tcx.symbol_mangling_version(mangling_version_crate) }; - let symbol = match tcx.is_exportable(def_id) { - true => format!( - "{}.{}", - v0::mangle(tcx, instance, instantiating_crate, true), - export::compute_hash_of_export_fn(tcx, instance) - ), - false => match mangling_version { - SymbolManglingVersion::Legacy => { - let mangled_name = legacy::mangle(tcx, instance, instantiating_crate); - - let mangled_name_too_long = { - // The PDB debug info format cannot store mangled symbol names for which its - // internal record exceeds u16::MAX bytes, a limit multiple Rust projects have been - // hitting due to the verbosity of legacy name mangling. Depending on the linker version - // in use, such symbol names can lead to linker crashes or incomprehensible linker error - // about a limit being hit. - // Mangle those symbols with v0 mangling instead, which gives us more room to breathe - // as v0 mangling is more compact. - // Empirical testing has shown the limit for the symbol name to be 65521 bytes; use - // 65000 bytes to leave some room for prefixes / suffixes as well as unknown scenarios - // with a different limit. - const MAX_SYMBOL_LENGTH: usize = 65000; - - tcx.sess.target.uses_pdb_debuginfo() && mangled_name.len() > MAX_SYMBOL_LENGTH - }; - - if mangled_name_too_long { - v0::mangle(tcx, instance, instantiating_crate, false) - } else { - mangled_name + // Offload kernels must omit the stable_crate_id disambiguator because + // host and device passes have different stable_crate_ids. + let is_offload_kernel = + tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::OFFLOAD_KERNEL); + let symbol = if is_offload_kernel { + v0::mangle(tcx, instance, instantiating_crate, true) + } else { + match tcx.is_exportable(def_id) { + true => format!( + "{}.{}", + v0::mangle(tcx, instance, instantiating_crate, true), + export::compute_hash_of_export_fn(tcx, instance) + ), + false => match mangling_version { + SymbolManglingVersion::Legacy => { + let mangled_name = legacy::mangle(tcx, instance, instantiating_crate); + + let mangled_name_too_long = { + // The PDB debug info format cannot store mangled symbol names for which its + // internal record exceeds u16::MAX bytes, a limit multiple Rust projects have been + // hitting due to the verbosity of legacy name mangling. Depending on the linker version + // in use, such symbol names can lead to linker crashes or incomprehensible linker error + // about a limit being hit. + // Mangle those symbols with v0 mangling instead, which gives us more room to breathe + // as v0 mangling is more compact. + // Empirical testing has shown the limit for the symbol name to be 65521 bytes; use + // 65000 bytes to leave some room for prefixes / suffixes as well as unknown scenarios + // with a different limit. + const MAX_SYMBOL_LENGTH: usize = 65000; + + tcx.sess.target.uses_pdb_debuginfo() + && mangled_name.len() > MAX_SYMBOL_LENGTH + }; + + if mangled_name_too_long { + v0::mangle(tcx, instance, instantiating_crate, false) + } else { + mangled_name + } } - } - SymbolManglingVersion::V0 => v0::mangle(tcx, instance, instantiating_crate, false), - SymbolManglingVersion::Hashed => { - hashed::mangle(tcx, instance, instantiating_crate, || { - v0::mangle(tcx, instance, instantiating_crate, false) - }) - } - }, + SymbolManglingVersion::V0 => v0::mangle(tcx, instance, instantiating_crate, false), + SymbolManglingVersion::Hashed => { + hashed::mangle(tcx, instance, instantiating_crate, || { + v0::mangle(tcx, instance, instantiating_crate, false) + }) + } + }, + } }; debug_assert!( diff --git a/tests/codegen-llvm/gpu_offload/control_flow.rs b/tests/codegen-llvm/gpu_offload/control_flow.rs index 605a6f08843c3..da997de53a428 100644 --- a/tests/codegen-llvm/gpu_offload/control_flow.rs +++ b/tests/codegen-llvm/gpu_offload/control_flow.rs @@ -10,6 +10,8 @@ #![feature(core_intrinsics)] #![no_main] +// CHECK: @.offload_sizes.[[K:[^ ]*foo]] = private unnamed_addr constant + // CHECK: define{{( dso_local)?}} void @main() // CHECK-NOT: define // CHECK: %.offload_baseptrs = alloca [1 x ptr], align 8 @@ -18,9 +20,9 @@ // CHECK: br label %bb3 // CHECK-NOT define // CHECK: bb3 -// CHECK: call void @__tgt_target_data_begin_mapper(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 1, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull @.offload_sizes.foo, ptr nonnull @.offload_maptypes.foo.begin, ptr null, ptr null) -// CHECK: = call i32 @__tgt_target_kernel(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 256, i32 32, ptr nonnull @.foo.region_id, ptr nonnull %kernel_args) -// CHECK-NEXT: call void @__tgt_target_data_end_mapper(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 1, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull @.offload_sizes.foo, ptr nonnull @.offload_maptypes.foo.end, ptr null, ptr null) +// CHECK: call void @__tgt_target_data_begin_mapper(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 1, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull @.offload_sizes.[[K]], ptr nonnull @.offload_maptypes.[[K]].begin, ptr null, ptr null) +// CHECK: = call i32 @__tgt_target_kernel(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 256, i32 32, ptr nonnull @.[[K]].region_id, ptr nonnull %kernel_args) +// CHECK-NEXT: call void @__tgt_target_data_end_mapper(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 1, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull @.offload_sizes.[[K]], ptr nonnull @.offload_maptypes.[[K]].end, ptr null, ptr null) #[unsafe(no_mangle)] unsafe fn main() { let A = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; diff --git a/tests/codegen-llvm/gpu_offload/slice_host.rs b/tests/codegen-llvm/gpu_offload/slice_host.rs index 0f27821ef765c..dfc7ec545630c 100644 --- a/tests/codegen-llvm/gpu_offload/slice_host.rs +++ b/tests/codegen-llvm/gpu_offload/slice_host.rs @@ -18,10 +18,10 @@ // CHECK: define{{( dso_local)?}} void @main() // CHECK: %.offload_sizes = alloca [2 x i64], align 8 -// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %.offload_sizes, ptr {{.*}} @.offload_sizes.foo, i64 16, i1 false) +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %.offload_sizes, ptr {{.*}} @.offload_sizes.[[K]], i64 16, i1 false) // CHECK: store i64 16, ptr %.offload_sizes, align 8 // CHECK: call void @__tgt_target_data_begin_mapper(ptr nonnull @anon.[[ID]].1, i64 -1, i32 2, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull %.offload_sizes, ptr nonnull @.offload_maptypes.[[K]].begin, ptr null, ptr null) -// CHECK: call i32 @__tgt_target_kernel(ptr nonnull @anon.[[ID]].1, i64 -1, i32 1, i32 1, ptr nonnull @.foo.region_id, ptr nonnull %kernel_args) +// CHECK: call i32 @__tgt_target_kernel(ptr nonnull @anon.[[ID]].1, i64 -1, i32 1, i32 1, ptr nonnull @.[[K]].region_id, ptr nonnull %kernel_args) // CHECK-NEXT: call void @__tgt_target_data_end_mapper(ptr nonnull @anon.[[ID]].1, i64 -1, i32 2, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull %.offload_sizes, ptr nonnull @.offload_maptypes.[[K]].end, ptr null, ptr null) #[unsafe(no_mangle)] diff --git a/tests/pretty/offload/offload_kernel.device.pp b/tests/pretty/offload/offload_kernel.device.pp index 9c8e6edaf2ed3..6f6c4e9693ddf 100644 --- a/tests/pretty/offload/offload_kernel.device.pp +++ b/tests/pretty/offload/offload_kernel.device.pp @@ -18,7 +18,6 @@ use std::offload::offload_kernel; #[rustc_offload_kernel] -#[unsafe(no_mangle)] unsafe extern "gpu-kernel" fn foo(a: &[f32], b: &[f32], c: *mut f32) { *c = a[0] + b[0]; } diff --git a/tests/pretty/offload/offload_kernel.host.pp b/tests/pretty/offload/offload_kernel.host.pp index cf60ee9f8138b..c35cf9474f709 100644 --- a/tests/pretty/offload/offload_kernel.host.pp +++ b/tests/pretty/offload/offload_kernel.host.pp @@ -17,7 +17,7 @@ use std::offload::offload_kernel; -#[unsafe(no_mangle)] +#[rustc_offload_kernel] #[inline(never)] fn foo(_: &[f32], _: &[f32], _: *mut f32) { From a5a9f8cf4f8923efc976c1561e408bb30eb57183 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Tue, 21 Jul 2026 20:12:30 +0300 Subject: [PATCH 2/8] Remove codegen and move manifest out of query --- .../src/back/symbol_export.rs | 10 +--- compiler/rustc_codegen_ssa/src/back/write.rs | 23 ---------- compiler/rustc_codegen_ssa/src/base.rs | 20 +------- compiler/rustc_interface/src/passes.rs | 18 +++++++- .../src/middle/codegen_fn_attrs.rs | 2 + compiler/rustc_middle/src/mono.rs | 6 --- compiler/rustc_monomorphize/src/collector.rs | 33 ------------- compiler/rustc_monomorphize/src/lib.rs | 4 ++ .../src/offload_manifest.rs | 46 +++++++++++++++++++ 9 files changed, 71 insertions(+), 91 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index bdff48ced55b6..6109231edb5f9 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -633,19 +633,11 @@ fn symbol_export_level(tcx: TyCtxt<'_>, sym_def_id: DefId) -> SymbolExportLevel // are not considered for export let codegen_fn_attrs = tcx.codegen_fn_attrs(sym_def_id); let is_extern = codegen_fn_attrs.contains_extern_indicator(); - let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { - matches!( - o, - rustc_session::config::Offload::DeviceWithManifest(_) - | rustc_session::config::Offload::Device - ) - }); - let is_offload = is_device_offload && is_offload_kernel(codegen_fn_attrs); let std_internal = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL); let eii = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM); - if (is_extern && !std_internal && !eii) || is_offload { + if is_extern && !std_internal && !eii { let target = &tcx.sess.target.llvm_target; // WebAssembly cannot export data symbols, so reduce their export level // FIXME(jdonszelmann) don't do a substring match here. diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 46ac35cb7dbc5..bd1683b3acc34 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -464,29 +464,6 @@ pub(crate) fn start_async_codegen( } } -/// Create an `OngoingCodegen` that has no coordinator thread and will finish -/// immediately when joined. This is used for the offload host-metadata pass, -/// that only need to run the monomorphization collector. -pub(crate) fn empty_ongoing_codegen( - backend: B, - tcx: TyCtxt<'_>, -) -> OngoingCodegen { - let (coordinator_send, _) = channel::>(); - let (codegen_worker_send, codegen_worker_receive) = channel(); - drop(codegen_worker_send); - - let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); - drop(shared_emitter); - - OngoingCodegen { - backend, - codegen_worker_receive, - shared_emitter_main, - coordinator: Coordinator { sender: coordinator_send, future: None, phantom: PhantomData }, - output_filenames: Arc::clone(tcx.output_filenames(())), - } -} - fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( sess: &Session, incr_comp_session: Option<&IncrCompSession>, diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index d615c8daab550..0468e3de18d8b 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -41,9 +41,8 @@ use tracing::{debug, info}; use crate::assert_module_sources::CguReuse; use crate::back::link::are_upstream_rust_objects_already_included; use crate::back::write::{ - ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, empty_ongoing_codegen, - start_async_codegen, submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, - submit_pre_lto_module_to_llvm, + ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, start_async_codegen, + submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, submit_pre_lto_module_to_llvm, }; use crate::common::{self, IntPredicate, RealPredicate, TypeKind}; use crate::meth::load_vtable; @@ -724,21 +723,6 @@ pub fn codegen_crate< tcx.dcx().emit_fatal(diagnostics::CpuRequired); } - // A `HostMetadata` pass only exists to collect the set of generic kernel instantiations - // required by the host and write the offload manifest. - let is_host_metadata = tcx - .sess - .opts - .unstable_opts - .offload - .iter() - .any(|o| matches!(o, rustc_session::config::Offload::HostMetadata(_))); - - if is_host_metadata { - let _ = tcx.collect_and_partition_mono_items(()); - return empty_ongoing_codegen(backend, tcx); - } - if let Some(target_cpu) = &tcx.sess.opts.cg.target_cpu && tcx.sess.target.unsupported_cpus.contains(&target_cpu.into()) { diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 0e5bf519fab18..d8e5243148847 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1313,11 +1313,25 @@ pub(crate) fn start_codegen<'tcx>( let metadata = rustc_metadata::fs::encode_and_write_metadata(tcx); + let is_host_metadata = tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, rustc_session::config::Offload::HostMetadata(_))); + let codegen = tcx.sess.time("codegen_crate", || { - if tcx.sess.opts.unstable_opts.no_codegen || !tcx.sess.opts.output_types.should_codegen() { - // Skip crate items and just output metadata in -Z no-codegen mode. + if tcx.sess.opts.unstable_opts.no_codegen + || !tcx.sess.opts.output_types.should_codegen() + || is_host_metadata + { tcx.sess.dcx().abort_if_errors(); + if is_host_metadata { + rustc_monomorphize::write_host_metadata_offload_manifest(tcx); + } + // Linker::link will skip join_codegen in case of a CodegenResults Any value. Box::new(CompiledModules { modules: vec![], allocator_module: None }) } else { diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs index b6ae4a98a34e3..ef9043101dbbb 100644 --- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -295,6 +295,8 @@ impl CodegenFnAttrs { // note: for these we do also set a symbol name so technically also handled by the // condition below. However, I think that regardless these should be treated as extern. || self.flags.contains(CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM) + // `#[rustc_offload_kernel]`: this item is an externally-launched kernel entry point. + || self.flags.contains(CodegenFnAttrFlags::OFFLOAD_KERNEL) || self.symbol_name.is_some() || match self.linkage { // These are private, so make sure we don't try to consider diff --git a/compiler/rustc_middle/src/mono.rs b/compiler/rustc_middle/src/mono.rs index 1d15844f4ca96..dc9a94f79aa0f 100644 --- a/compiler/rustc_middle/src/mono.rs +++ b/compiler/rustc_middle/src/mono.rs @@ -160,12 +160,6 @@ impl<'tcx> MonoItem<'tcx> { return InstantiationMode::GloballyShared { may_conflict: false }; } - // Offload kernels are looked up by symbol name at runtime by the host. - // They must be emitted exactly once with external linkage. - if codegen_fn_attrs.flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) { - return InstantiationMode::GloballyShared { may_conflict: false }; - } - // This is technically a heuristic even though it's in the "not a heuristic" part of // instantiation mode selection. // It is surely possible to untangle this; the root problem is that the way we instantiate diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index eb5741f68746f..69e532fd1e2df 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -867,7 +867,6 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { &mut self.used_items, ); - // TODO(Sa4dUs): check why it only collects non generic fns if let ty::FnDef(def_id, _) = *callee_ty.kind() && self.tcx.is_intrinsic(def_id, rustc_span::sym::offload) && let Some(kernel) = args.first() @@ -1925,38 +1924,6 @@ pub(crate) fn collect_crate_mono_items<'tcx>( state.visited.into_inner().into_sorted(&mut hcx, true) }); - // Write out the offload manifest of required generic kernel instantiations. - if let Some(path) = tcx.sess.opts.unstable_opts.offload.iter().find_map(|o| { - if let rustc_session::config::Offload::HostMetadata(p) = o { Some(p) } else { None } - }) { - let instances: Vec> = mono_items - .iter() - .filter_map(|item| { - if let MonoItem::Fn(instance) = item { - if tcx - .codegen_fn_attrs(instance.def_id()) - .flags - .contains(CodegenFnAttrFlags::OFFLOAD_KERNEL) - { - Some(*instance) - } else { - None - } - } else { - None - } - }) - .collect(); - if let Err(e) = - crate::offload_manifest::write_manifest(std::path::Path::new(path), tcx, &instances) - { - tcx.dcx().emit_fatal(crate::diagnostics::OffloadManifestWriteError { - path: path.clone(), - err: e.to_string(), - }); - } - } - (mono_items, state.usage_map.into_inner()) } diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index a15ebe33651c6..a7d1119dd064c 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -20,6 +20,10 @@ mod offload_manifest; mod partitioning; mod util; +// Exposed so `rustc_codegen_ssa::base::codegen_crate` can trigger the +// host-metadata manifest write. +pub use offload_manifest::write_host_metadata_offload_manifest; + fn custom_coerce_unsize_info<'tcx>( tcx: TyCtxtAt<'tcx>, source_ty: Ty<'tcx>, diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload_manifest.rs index adfbebc435568..60bf8373f8445 100644 --- a/compiler/rustc_monomorphize/src/offload_manifest.rs +++ b/compiler/rustc_monomorphize/src/offload_manifest.rs @@ -9,6 +9,8 @@ use std::fs; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::Lock; use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE}; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::mono::MonoItem; use rustc_middle::ty::codec::{TyDecoder, TyEncoder}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_serialize::opaque::{FileEncoder, MemDecoder}; @@ -371,6 +373,50 @@ pub(crate) fn write_manifest<'tcx>( encoder.finish() } +/// Write out the offload host-metadata manifest for `mono_items`. No-op unless +/// the session was invoked with `-Zoffload=HostMetadata=`. +pub fn write_host_metadata_offload_manifest<'tcx>(tcx: TyCtxt<'tcx>) { + let Some(path) = tcx.sess.opts.unstable_opts.offload.iter().find_map(|o| { + if let rustc_session::config::Offload::HostMetadata(p) = o { Some(p) } else { None } + }) else { + return; + }; + + let partitions = tcx.collect_and_partition_mono_items(()); + let mono_items: Vec> = partitions + .codegen_units + .iter() + .flat_map(|cgu| cgu.items().iter()) + .map(|(item, _)| *item) + .collect(); + + let instances: Vec> = mono_items + .iter() + .filter_map(|item| { + if let MonoItem::Fn(instance) = item { + if tcx + .codegen_fn_attrs(instance.def_id()) + .flags + .contains(CodegenFnAttrFlags::OFFLOAD_KERNEL) + { + Some(*instance) + } else { + None + } + } else { + None + } + }) + .collect(); + + if let Err(e) = write_manifest(std::path::Path::new(path), tcx, &instances) { + tcx.dcx().emit_fatal(crate::diagnostics::OffloadManifestWriteError { + path: path.clone(), + err: e.to_string(), + }); + } +} + /// Read a list of offload kernel instances from the manifest file. pub(crate) fn read_manifest<'tcx>( path: &std::path::Path, From 3daa8b3c7076275e41e3ccd52e7c1aa199ff36f5 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Thu, 23 Jul 2026 19:29:00 +0300 Subject: [PATCH 3/8] fix --- compiler/rustc_codegen_ssa/src/back/write.rs | 10 +--------- compiler/rustc_monomorphize/src/offload_manifest.rs | 13 +++++++++---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index bd1683b3acc34..2eaceb68a67a2 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -2112,15 +2112,7 @@ pub struct Coordinator { impl Coordinator { fn join(mut self) -> std::thread::Result, ()>> { - if let Some(future) = self.future.take() { - future.join() - } else { - // Used for passes that do not codegen anything (e.g. the offload host-metadata pass). - Ok(Ok(MaybeLtoModules::NoLto(CompiledModules { - modules: vec![], - allocator_module: None, - }))) - } + self.future.take().unwrap().join() } } diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload_manifest.rs index 60bf8373f8445..f3848832e1755 100644 --- a/compiler/rustc_monomorphize/src/offload_manifest.rs +++ b/compiler/rustc_monomorphize/src/offload_manifest.rs @@ -330,10 +330,6 @@ impl<'a, 'tcx> SpanDecoder for OffloadManifestDecoder<'a, 'tcx> { impl<'a, 'tcx> TyDecoder<'tcx> for OffloadManifestDecoder<'a, 'tcx> { const CLEAR_CROSS_CRATE: bool = true; - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - fn cached_ty_for_shorthand(&mut self, shorthand: usize, or_insert_with: F) -> Ty<'tcx> where F: FnOnce(&mut Self) -> Ty<'tcx>, @@ -362,6 +358,15 @@ impl<'a, 'tcx> TyDecoder<'tcx> for OffloadManifestDecoder<'a, 'tcx> { } } +impl<'a, 'tcx> rustc_middle::ty::InternerDecoder for OffloadManifestDecoder<'a, 'tcx> { + type Interner = TyCtxt<'tcx>; + + #[inline] + fn interner(&self) -> Self::Interner { + self.tcx + } +} + /// Write a list of offload kernel instances to the manifest file. pub(crate) fn write_manifest<'tcx>( path: &std::path::Path, From 1920a16080af8e3b8bac09006625c4d3a2488c3e Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Fri, 24 Jul 2026 16:44:41 +0300 Subject: [PATCH 4/8] ci fix --- compiler/rustc_symbol_mangling/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index a6b7485223f11..93db093f630bd 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -314,8 +314,8 @@ fn compute_symbol_name<'tcx>( // Offload kernels must omit the stable_crate_id disambiguator because // host and device passes have different stable_crate_ids. - let is_offload_kernel = - tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::OFFLOAD_KERNEL); + let is_offload_kernel = tcx.def_kind(def_id).has_codegen_attrs() + && tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::OFFLOAD_KERNEL); let symbol = if is_offload_kernel { v0::mangle(tcx, instance, instantiating_crate, true) } else { From 30dd3d499b566c4bb24cbbd197f0016e586c2a14 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Fri, 24 Jul 2026 19:25:39 +0300 Subject: [PATCH 5/8] minor fixes --- compiler/rustc_monomorphize/src/collector.rs | 3 ++- .../rustc_monomorphize/src/offload_manifest.rs | 18 ++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 69e532fd1e2df..401a441a939ea 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -232,7 +232,7 @@ use rustc_middle::ty::{ use rustc_middle::util::Providers; use rustc_middle::{bug, span_bug}; use rustc_session::config::{DebugInfo, EntryFnType}; -use rustc_span::{DUMMY_SP, Span, Spanned, dummy_spanned, respan}; +use rustc_span::{DUMMY_SP, Span, Spanned, Symbol, dummy_spanned, respan}; use tracing::{debug, instrument, trace}; use crate::diagnostics::{ @@ -1488,6 +1488,7 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec { for instance in instances { diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload_manifest.rs index f3848832e1755..d307282a3f9b1 100644 --- a/compiler/rustc_monomorphize/src/offload_manifest.rs +++ b/compiler/rustc_monomorphize/src/offload_manifest.rs @@ -8,7 +8,7 @@ use std::fs; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::Lock; -use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE}; +use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE, StableCrateId}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mono::MonoItem; use rustc_middle::ty::codec::{TyDecoder, TyEncoder}; @@ -115,7 +115,7 @@ impl<'a, 'tcx> SpanEncoder for OffloadManifestEncoder<'a, 'tcx> { } fn encode_crate_num(&mut self, crate_num: rustc_span::def_id::CrateNum) { - crate_num.as_u32().encode(self); + self.tcx.stable_crate_id(crate_num).encode(self); } fn encode_def_index(&mut self, def_index: rustc_span::def_id::DefIndex) { @@ -310,8 +310,8 @@ impl<'a, 'tcx> SpanDecoder for OffloadManifestDecoder<'a, 'tcx> { } fn decode_crate_num(&mut self) -> rustc_span::def_id::CrateNum { - let v = self.read_u32(); - rustc_span::def_id::CrateNum::from_u32(v) + let stable_id: StableCrateId = Decodable::decode(self); + self.tcx.stable_crate_id_to_crate_num(stable_id) } fn decode_def_id(&mut self) -> rustc_span::def_id::DefId { @@ -431,17 +431,7 @@ pub(crate) fn read_manifest<'tcx>( let mut decoder = OffloadManifestDecoder::new(&data, tcx) .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid manifest"))?; - let payload_len = decoder.decoder.len() - decoder.position(); - if payload_len == 0 { - return Ok(Vec::new()); - } - let instances: Vec> = Decodable::decode(&mut decoder); - let instances: Vec<_> = instances - .into_iter() - .filter(|instance| instance.def_id().krate != rustc_span::def_id::CrateNum::MAX) - .collect(); - Ok(instances) } From 55c63da34578d02154d887842310fae685406159 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Wed, 5 Aug 2026 18:48:25 +0300 Subject: [PATCH 6/8] Add tests remove extra mode and other fixes --- compiler/rustc_builtin_macros/src/offload.rs | 7 +--- compiler/rustc_codegen_llvm/src/back/write.rs | 10 +---- compiler/rustc_codegen_llvm/src/intrinsic.rs | 2 +- .../src/back/symbol_export.rs | 28 +++++++------- compiler/rustc_interface/src/tests.rs | 2 +- compiler/rustc_monomorphize/src/collector.rs | 19 +++++++++- .../src/offload_manifest.rs | 3 +- compiler/rustc_session/src/config.rs | 8 ++-- compiler/rustc_session/src/options.rs | 17 ++------- .../offload-generic-manifest/generic.rs | 12 ++++++ .../offload-generic-manifest/rmake.rs | 37 +++++++++++++++++++ tests/ui/offload/duplicate_kernel.rs | 20 ++++++++++ tests/ui/offload/duplicate_kernel.stderr | 8 ++++ 13 files changed, 124 insertions(+), 49 deletions(-) create mode 100644 tests/run-make/offload-generic-manifest/generic.rs create mode 100644 tests/run-make/offload-generic-manifest/rmake.rs create mode 100644 tests/ui/offload/duplicate_kernel.rs create mode 100644 tests/ui/offload/duplicate_kernel.stderr diff --git a/compiler/rustc_builtin_macros/src/offload.rs b/compiler/rustc_builtin_macros/src/offload.rs index 96cb9372d3e09..006332b8c40c9 100644 --- a/compiler/rustc_builtin_macros/src/offload.rs +++ b/compiler/rustc_builtin_macros/src/offload.rs @@ -9,12 +9,7 @@ use thin_vec::thin_vec; use crate::diagnostics; fn compile_for_device(ecx: &mut ExtCtxt<'_>) -> bool { - ecx.sess - .opts - .unstable_opts - .offload - .iter() - .any(|o| matches!(o, Offload::Device | Offload::DeviceWithManifest(_))) + ecx.sess.opts.unstable_opts.offload.iter().any(|o| matches!(o, Offload::Device(_))) } fn outer_normal_attr(normal: &Box, id: ast::AttrId, span: Span) -> ast::Attribute { diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 80d5ff31c730e..66b51d9184a79 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -743,10 +743,7 @@ pub(crate) unsafe fn llvm_optimize( } if cgcx.target_is_like_gpu - && config - .offload - .iter() - .any(|o| matches!(o, config::Offload::Device | config::Offload::DeviceWithManifest(_))) + && config.offload.iter().any(|o| matches!(o, config::Offload::Device(_))) { let cx = SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size); @@ -819,10 +816,7 @@ pub(crate) unsafe fn llvm_optimize( }; if cgcx.target_is_like_gpu - && config - .offload - .iter() - .any(|o| matches!(o, config::Offload::Device | config::Offload::DeviceWithManifest(_))) + && config.offload.iter().any(|o| matches!(o, config::Offload::Device(_))) { let device_path = cgcx.output_filenames.path(OutputType::Object); let device_dir = device_path.parent().unwrap(); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 4c54b60c6d48a..ba11ef29fb536 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1854,7 +1854,7 @@ fn codegen_offload<'ll, 'tcx>( let args = get_args_from_tuple(bx, args[4], fn_target); let target_symbol = mangle_offload_export(tcx, fn_target); - let sig = tcx.fn_sig(fn_target.def_id()).skip_binder(); + let sig = tcx.fn_sig(fn_target.def_id()).instantiate(tcx, fn_target.args).skip_norm_wip(); let sig = tcx.instantiate_bound_regions_with_erased(sig); let inputs = sig.inputs(); diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 6109231edb5f9..cc102c636f4ae 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -245,13 +245,13 @@ pub fn exported_non_generic_symbols_helper<'tcx>( )); } - let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { - matches!( - o, - rustc_session::config::Offload::DeviceWithManifest(_) - | rustc_session::config::Offload::Device - ) - }); + let is_device_offload = tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, rustc_session::config::Offload::Device(_))); if is_device_offload { let crate_items = tcx.hir_crate_items(()); let mut seen: rustc_data_structures::fx::FxHashSet = symbols @@ -307,13 +307,13 @@ fn exported_generic_symbols_provider_local<'tcx>( let mut symbols: Vec<_> = vec![]; let export_generics = tcx.local_crate_exports_generics(); - let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { - matches!( - o, - rustc_session::config::Offload::DeviceWithManifest(_) - | rustc_session::config::Offload::Device - ) - }); + let is_device_offload = tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, rustc_session::config::Offload::Device(_))); if export_generics || is_device_offload { use rustc_hir::attrs::Linkage; diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 548ee3f4b8e7b..24c7ff8484a5e 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -857,7 +857,7 @@ fn test_unstable_options_tracking_hash() { tracked!(no_profiler_runtime, true); tracked!(no_trait_vptr, true); tracked!(no_unique_section_names, true); - tracked!(offload, vec![Offload::Device]); + tracked!(offload, vec![Offload::Device(String::new())]); tracked!(on_broken_pipe, OnBrokenPipe::Kill); tracked!(osx_rpath_install_name, true); tracked!(packed_bundled_libs, true); diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 401a441a939ea..44c602dafc9eb 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -1486,7 +1486,13 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec, mode: MonoItemCollectionStrategy) -> Vec(tcx: TyCtxt<'tcx>) { let Some(path) = tcx.sess.opts.unstable_opts.offload.iter().find_map(|o| { if let rustc_session::config::Offload::HostMetadata(p) = o { Some(p) } else { None } }) else { - return; + bug!("HostMetadata path not found; caller should have checked"); }; let partitions = tcx.collect_and_partition_mono_items(()); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d424991f2921f..1abc122ba8a11 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -197,10 +197,10 @@ pub enum CoverageLevel { #[derive(Clone, PartialEq, Hash, Debug, Encodable, Decodable)] pub enum Offload { /// Entry point for `std::offload`, enables kernel compilation for a gpu device - Device, - /// Like `Device`, but reads a manifest of required generic kernel instantiations - /// produced by a previous `HostMetadata` pass. - DeviceWithManifest(String), + /// Reads a manifest of required generic kernel instantiations + /// produced by a previous `HostMetadata` pass. An empty manifest + /// means all kernel instantiations are discovered via monomorphization. + Device(String), /// Second step in the offload pipeline, generates the host code to call kernels. Host(String), /// Test is similar to Host, but allows testing without a device artifact. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 65a94bc9e314c..3c3f1d69fe2c1 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -819,7 +819,7 @@ mod desc { "a comma-separated list of strings, with elements beginning with + or -"; pub(crate) const parse_pointer_authentication_list_with_polarity: &str = "a comma-separated list of options, each of the form `+` or `-`, where `` is one of: `aarch64-jump-table-hardening`, `auth-traps`, `calls`, `elf-got`, `function-pointer-type-discrimination`, `indirect-gotos`, `init-fini`, `init-fini-address-discrimination`, `intrinsics`, `return-addresses`, `typeinfo-vt-ptr-discrimination`, `vt-ptr-addr-discrimination` or `vt-ptr-type-discrimination`"; pub(crate) const parse_autodiff: &str = "a comma separated list of settings: `Enable`, `PrintSteps`, `PrintTA`, `PrintTAFn`, `PrintAA`, `PrintPerf`, `PrintModBefore`, `PrintModAfter`, `PrintModFinal`, `PrintPasses`, `NoPostopt`, `LooseTypes`, `Inline`, `NoTT`"; - pub(crate) const parse_offload: &str = "a comma separated list of settings: `Host=`, `HostMetadata=`, `Device`, `DeviceWithManifest=`, `Test`"; + pub(crate) const parse_offload: &str = "a comma separated list of settings: `Host=`, `HostMetadata=`, `Device` (empty manifest) or `Device=`, `Test`"; pub(crate) const parse_comma_list: &str = "a comma-separated list of strings"; pub(crate) const parse_opt_comma_list: &str = parse_comma_list; pub(crate) const parse_number: &str = "a number"; @@ -1521,18 +1521,9 @@ pub mod parse { } } "Device" => { - if let Some(_) = arg { - // Device does not accept a value - return false; - } - Offload::Device - } - "DeviceWithManifest" => { - if let Some(p) = arg { - Offload::DeviceWithManifest(p.to_string()) - } else { - return false; - } + // Without an argument, `Device` uses an empty manifest and all kernel + // instantiations are discovered via monomorphization. + Offload::Device(arg.unwrap_or_default().to_string()) } "Test" => { if let Some(_) = arg { diff --git a/tests/run-make/offload-generic-manifest/generic.rs b/tests/run-make/offload-generic-manifest/generic.rs new file mode 100644 index 0000000000000..eb356ad05c574 --- /dev/null +++ b/tests/run-make/offload-generic-manifest/generic.rs @@ -0,0 +1,12 @@ +#![feature(core_intrinsics, rustc_attrs)] +#![allow(internal_features)] +#![cfg_attr(device, no_main)] + +#[rustc_offload_kernel] +fn kernel(x: T) {} + +#[cfg(not(device))] +fn main() { + core::intrinsics::offload::<_, _, ()>(kernel::, [1, 1, 1], [1, 1, 1], 0, (0.0f32,)); + core::intrinsics::offload::<_, _, ()>(kernel::, [1, 1, 1], [1, 1, 1], 0, (0i32,)); +} diff --git a/tests/run-make/offload-generic-manifest/rmake.rs b/tests/run-make/offload-generic-manifest/rmake.rs new file mode 100644 index 0000000000000..29603196d35c7 --- /dev/null +++ b/tests/run-make/offload-generic-manifest/rmake.rs @@ -0,0 +1,37 @@ +// Tests the offload manifest pipeline for generic kernels + +use run_make_support::rustc; +use run_make_support::symbols::object_contains_any_symbol_substring; + +fn main() { + rustc() + .input("generic.rs") + .arg("-Zunstable-options") + .arg("-Zoffload=HostMetadata=generic.manifest") + .arg("-Clto=fat") + .emit("metadata") + .run(); + + rustc() + .input("generic.rs") + .cfg("device") + .arg("-Zunstable-options") + .arg("-Zoffload=Device=generic.manifest") + .arg("-Clto=fat") + .emit("obj") + .run(); + + assert!(object_contains_any_symbol_substring("generic.o", &["6kernelfEB2_"])); + assert!(object_contains_any_symbol_substring("generic.o", &["6kernellEB2_"])); + + rustc() + .input("generic.rs") + .cfg("device") + .arg("-Zunstable-options") + .arg("-Zoffload=Device") + .arg("-Clto=fat") + .emit("obj") + .run(); + + assert!(!object_contains_any_symbol_substring("generic.o", &["6kernel"])); +} diff --git a/tests/ui/offload/duplicate_kernel.rs b/tests/ui/offload/duplicate_kernel.rs new file mode 100644 index 0000000000000..66f8b4c7a5445 --- /dev/null +++ b/tests/ui/offload/duplicate_kernel.rs @@ -0,0 +1,20 @@ +//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat --crate-name collision_kernels_a +//@ build-fail + +// An offload kernel whose mangled symbol collides with another item in the +// same crate must be rejected, just like any other symbol collision. + +#![feature(core_intrinsics, rustc_attrs)] +#![allow(internal_features)] + +#[allow(non_snake_case)] +#[no_mangle] +pub fn _RNvC19collision_kernels_a6kernel(_x: f32) {} + +#[rustc_offload_kernel] +fn kernel(_x: f32) {} //~ ERROR symbol `_RNvC19collision_kernels_a6kernel` is already defined + +fn main() { + _RNvC19collision_kernels_a6kernel(0.0); + core::intrinsics::offload::<_, _, ()>(kernel, [1, 1, 1], [1, 1, 1], 0, (0.0f32,)); +} diff --git a/tests/ui/offload/duplicate_kernel.stderr b/tests/ui/offload/duplicate_kernel.stderr new file mode 100644 index 0000000000000..28b0aca940246 --- /dev/null +++ b/tests/ui/offload/duplicate_kernel.stderr @@ -0,0 +1,8 @@ +error: symbol `_RNvC19collision_kernels_a6kernel` is already defined + --> $DIR/duplicate_kernel.rs:15:1 + | +LL | fn kernel(_x: f32) {} + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 4227e93fd2b094171eefa59d374c97e1ecfa1bbd Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Wed, 12 Aug 2026 18:01:32 +0300 Subject: [PATCH 7/8] Some fixes and error when missing manifest --- compiler/rustc_monomorphize/Cargo.toml | 5 ++ compiler/rustc_monomorphize/src/collector.rs | 15 +++++- .../rustc_monomorphize/src/diagnostics.rs | 13 ++++++ compiler/rustc_monomorphize/src/lib.rs | 4 +- .../manifest.rs} | 0 .../rustc_monomorphize/src/offload/mod.rs | 46 +++++++++++++++++++ compiler/rustc_session/src/config.rs | 8 ++-- .../offload-generic-manifest/rmake.rs | 11 +++-- tests/ui/offload/duplicate_kernel.rs | 2 +- .../generic_kernel_not_instantiated.rs | 16 +++++++ .../generic_kernel_not_instantiated.stderr | 10 ++++ 11 files changed, 118 insertions(+), 12 deletions(-) rename compiler/rustc_monomorphize/src/{offload_manifest.rs => offload/manifest.rs} (100%) create mode 100644 compiler/rustc_monomorphize/src/offload/mod.rs create mode 100644 tests/ui/offload/generic_kernel_not_instantiated.rs create mode 100644 tests/ui/offload/generic_kernel_not_instantiated.stderr diff --git a/compiler/rustc_monomorphize/Cargo.toml b/compiler/rustc_monomorphize/Cargo.toml index c45232b2565a7..8846de7a9bf81 100644 --- a/compiler/rustc_monomorphize/Cargo.toml +++ b/compiler/rustc_monomorphize/Cargo.toml @@ -22,3 +22,8 @@ serde = "1" serde_json = "1" tracing = "0.1" # tidy-alphabetical-end + +[features] +# tidy-alphabetical-start +llvm_offload = [] +# tidy-alphabetical-end diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 44c602dafc9eb..5d7820df622b7 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -231,7 +231,7 @@ use rustc_middle::ty::{ }; use rustc_middle::util::Providers; use rustc_middle::{bug, span_bug}; -use rustc_session::config::{DebugInfo, EntryFnType}; +use rustc_session::config::{DebugInfo, EntryFnType, Offload}; use rustc_span::{DUMMY_SP, Span, Spanned, Symbol, dummy_spanned, respan}; use tracing::{debug, instrument, trace}; @@ -1495,7 +1495,7 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec { for instance in instances { if instance.def_id().is_local() { @@ -1942,6 +1942,17 @@ pub(crate) fn collect_crate_mono_items<'tcx>( state.visited.into_inner().into_sorted(&mut hcx, true) }); + if tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, Offload::Device(p) if p.is_empty())) + { + crate::offload::check_offload_kernels_instantiated(tcx, &mono_items); + } + (mono_items, state.usage_map.into_inner()) } diff --git a/compiler/rustc_monomorphize/src/diagnostics.rs b/compiler/rustc_monomorphize/src/diagnostics.rs index df4b54abc2258..51ce633f55a54 100644 --- a/compiler/rustc_monomorphize/src/diagnostics.rs +++ b/compiler/rustc_monomorphize/src/diagnostics.rs @@ -64,6 +64,19 @@ pub(crate) struct OffloadManifestReadError { pub err: String, } +#[derive(Diagnostic)] +#[diag("generic offload kernel `{$def_path}` is not instantiated")] +#[help( + "with `-Zoffload=Device` (without a manifest), generic kernels are only discovered via \ + monomorphization; if this kernel is called from host code, pass \ + `-Zoffload=Device=`, using the manifest written by `-Zoffload=HostMetadata=`" +)] +pub(crate) struct GenericKernelNotInstantiated { + #[primary_span] + pub span: Span, + pub def_path: String, +} + #[derive(Diagnostic)] #[diag("the above error was encountered while instantiating `{$kind} {$instance}`")] pub(crate) struct EncounteredErrorWhileInstantiating<'tcx> { diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index a7d1119dd064c..79abdee53eda4 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -16,13 +16,13 @@ mod collector; mod diagnostics; mod graph_checks; mod mono_checks; -mod offload_manifest; +mod offload; mod partitioning; mod util; // Exposed so `rustc_codegen_ssa::base::codegen_crate` can trigger the // host-metadata manifest write. -pub use offload_manifest::write_host_metadata_offload_manifest; +pub use offload::manifest::write_host_metadata_offload_manifest; fn custom_coerce_unsize_info<'tcx>( tcx: TyCtxtAt<'tcx>, diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload/manifest.rs similarity index 100% rename from compiler/rustc_monomorphize/src/offload_manifest.rs rename to compiler/rustc_monomorphize/src/offload/manifest.rs diff --git a/compiler/rustc_monomorphize/src/offload/mod.rs b/compiler/rustc_monomorphize/src/offload/mod.rs new file mode 100644 index 0000000000000..a4dca281e0122 --- /dev/null +++ b/compiler/rustc_monomorphize/src/offload/mod.rs @@ -0,0 +1,46 @@ +pub(crate) mod manifest; + +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::DefId; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::mono::MonoItem; +use rustc_middle::ty::TyCtxt; + +pub(crate) fn check_offload_kernels_instantiated<'tcx>( + tcx: TyCtxt<'tcx>, + mono_items: &[MonoItem<'tcx>], +) { + let instantiated: FxHashSet = mono_items + .iter() + .filter_map(|item| match item { + MonoItem::Fn(instance) => Some(instance.def_id()), + MonoItem::Static(def_id) => Some(*def_id), + _ => None, + }) + .collect(); + + let crate_items = tcx.hir_crate_items(()); + let check = |def_id: DefId| { + if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) + || !tcx.generics_of(def_id).requires_monomorphization(tcx) + || !tcx.codegen_fn_attrs(def_id).flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) + || instantiated.contains(&def_id) + { + return; + } + tcx.dcx().emit_err(crate::diagnostics::GenericKernelNotInstantiated { + span: tcx.def_span(def_id), + def_path: tcx.def_path_str(def_id), + }); + }; + for id in crate_items.free_items() { + check(id.owner_id.to_def_id()); + } + for id in crate_items.impl_items() { + check(id.owner_id.to_def_id()); + } + for id in crate_items.trait_items() { + check(id.owner_id.to_def_id()); + } +} diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 1abc122ba8a11..efbffa8c0e486 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -196,12 +196,14 @@ pub enum CoverageLevel { // The different settings that the `-Z offload` flag can have. #[derive(Clone, PartialEq, Hash, Debug, Encodable, Decodable)] pub enum Offload { - /// Entry point for `std::offload`, enables kernel compilation for a gpu device + /// Second step in the offload pipeline, enables kernel compilation for a gpu device /// Reads a manifest of required generic kernel instantiations /// produced by a previous `HostMetadata` pass. An empty manifest - /// means all kernel instantiations are discovered via monomorphization. + /// means there are no generic kernels at all, or that generic kernels are only + /// called from non-generic device entry points and never from the host, so we + /// don't need to track their instantiations. Device(String), - /// Second step in the offload pipeline, generates the host code to call kernels. + /// Third step in the offload pipeline, generates the host code to call kernels. Host(String), /// Test is similar to Host, but allows testing without a device artifact. Test, diff --git a/tests/run-make/offload-generic-manifest/rmake.rs b/tests/run-make/offload-generic-manifest/rmake.rs index 29603196d35c7..54907fabf8ee4 100644 --- a/tests/run-make/offload-generic-manifest/rmake.rs +++ b/tests/run-make/offload-generic-manifest/rmake.rs @@ -8,6 +8,7 @@ fn main() { .input("generic.rs") .arg("-Zunstable-options") .arg("-Zoffload=HostMetadata=generic.manifest") + .arg("-Csymbol-mangling-version=v0") .arg("-Clto=fat") .emit("metadata") .run(); @@ -17,6 +18,7 @@ fn main() { .cfg("device") .arg("-Zunstable-options") .arg("-Zoffload=Device=generic.manifest") + .arg("-Csymbol-mangling-version=v0") .arg("-Clto=fat") .emit("obj") .run(); @@ -24,14 +26,15 @@ fn main() { assert!(object_contains_any_symbol_substring("generic.o", &["6kernelfEB2_"])); assert!(object_contains_any_symbol_substring("generic.o", &["6kernellEB2_"])); - rustc() + let p = rustc() .input("generic.rs") .cfg("device") .arg("-Zunstable-options") .arg("-Zoffload=Device") + .arg("-Csymbol-mangling-version=v0") .arg("-Clto=fat") .emit("obj") - .run(); - - assert!(!object_contains_any_symbol_substring("generic.o", &["6kernel"])); + .run_fail(); + assert!(p.stderr_utf8().contains("generic offload kernel")); + assert!(p.stderr_utf8().contains("is not instantiated")); } diff --git a/tests/ui/offload/duplicate_kernel.rs b/tests/ui/offload/duplicate_kernel.rs index 66f8b4c7a5445..0410fe0690d47 100644 --- a/tests/ui/offload/duplicate_kernel.rs +++ b/tests/ui/offload/duplicate_kernel.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat --crate-name collision_kernels_a +//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat -Csymbol-mangling-version=v0 --crate-name collision_kernels_a //@ build-fail // An offload kernel whose mangled symbol collides with another item in the diff --git a/tests/ui/offload/generic_kernel_not_instantiated.rs b/tests/ui/offload/generic_kernel_not_instantiated.rs new file mode 100644 index 0000000000000..2df058758024b --- /dev/null +++ b/tests/ui/offload/generic_kernel_not_instantiated.rs @@ -0,0 +1,16 @@ +//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat -Csymbol-mangling-version=v0 +//@ build-fail + +// A generic offload kernel that is never called from host code (and hence +// never monomorphized) cannot be discovered without a manifest: with +// `-Zoffload=Device` (no manifest path), the compiler relies on +// monomorphization to find kernels, so it must reject the kernel rather than +// silently emit no device code for it. + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +#[rustc_offload_kernel] +fn kernel(x: T) {} //~ ERROR generic offload kernel `kernel` is not instantiated + +fn main() {} diff --git a/tests/ui/offload/generic_kernel_not_instantiated.stderr b/tests/ui/offload/generic_kernel_not_instantiated.stderr new file mode 100644 index 0000000000000..ab7c066d09b82 --- /dev/null +++ b/tests/ui/offload/generic_kernel_not_instantiated.stderr @@ -0,0 +1,10 @@ +error: generic offload kernel `kernel` is not instantiated + --> $DIR/generic_kernel_not_instantiated.rs:14:1 + | +LL | fn kernel(x: T) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: with `-Zoffload=Device` (without a manifest), generic kernels are only discovered via monomorphization; if this kernel is called from host code, pass `-Zoffload=Device=`, using the manifest written by `-Zoffload=HostMetadata=` + +error: aborting due to 1 previous error + From b1b0f5e7b4fcedfdab14de37a2c8f06118868bd6 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Wed, 12 Aug 2026 18:48:12 +0300 Subject: [PATCH 8/8] fix --- compiler/rustc_codegen_llvm/src/lib.rs | 2 +- tests/run-make/offload-generic-manifest/rmake.rs | 2 ++ tests/ui/offload/check_config.rs | 2 +- tests/ui/offload/duplicate_kernel.rs | 4 +++- tests/ui/offload/duplicate_kernel.stderr | 2 +- tests/ui/offload/generic_kernel_not_instantiated.rs | 4 +++- tests/ui/offload/generic_kernel_not_instantiated.stderr | 2 +- 7 files changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 1f0e583709592..552a91ffee071 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -375,7 +375,7 @@ impl CodegenBackend for LlvmCodegenBackend { fn codegen_crate<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Box { use rustc_session::config::Offload; - if tcx.sess.opts.unstable_opts.offload.contains(&Offload::Device) + if tcx.sess.opts.unstable_opts.offload.iter().any(|o| matches!(o, Offload::Device(_))) || tcx.sess.opts.unstable_opts.offload.iter().any(|o| matches!(o, Offload::Host(_))) { match llvm::RustOffloadWrapper::get_or_init(&tcx.sess.opts.sysroot) { diff --git a/tests/run-make/offload-generic-manifest/rmake.rs b/tests/run-make/offload-generic-manifest/rmake.rs index 54907fabf8ee4..01b09ff87d2fa 100644 --- a/tests/run-make/offload-generic-manifest/rmake.rs +++ b/tests/run-make/offload-generic-manifest/rmake.rs @@ -1,3 +1,5 @@ +//@ needs-offload + // Tests the offload manifest pipeline for generic kernels use run_make_support::rustc; diff --git a/tests/ui/offload/check_config.rs b/tests/ui/offload/check_config.rs index 69afe65a308b4..ff145f420e482 100644 --- a/tests/ui/offload/check_config.rs +++ b/tests/ui/offload/check_config.rs @@ -1,6 +1,6 @@ //@ revisions: pass fail //@ no-prefer-dynamic -//@ needs-enzyme +//@ needs-offload //@[pass] build-pass //@[fail] build-fail //@[pass] compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat --emit=metadata diff --git a/tests/ui/offload/duplicate_kernel.rs b/tests/ui/offload/duplicate_kernel.rs index 0410fe0690d47..abde76137a37c 100644 --- a/tests/ui/offload/duplicate_kernel.rs +++ b/tests/ui/offload/duplicate_kernel.rs @@ -1,5 +1,6 @@ //@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat -Csymbol-mangling-version=v0 --crate-name collision_kernels_a //@ build-fail +//@ needs-offload // An offload kernel whose mangled symbol collides with another item in the // same crate must be rejected, just like any other symbol collision. @@ -12,7 +13,8 @@ pub fn _RNvC19collision_kernels_a6kernel(_x: f32) {} #[rustc_offload_kernel] -fn kernel(_x: f32) {} //~ ERROR symbol `_RNvC19collision_kernels_a6kernel` is already defined +fn kernel(_x: f32) {} +//~^ ERROR symbol `_RNvC19collision_kernels_a6kernel` is already defined fn main() { _RNvC19collision_kernels_a6kernel(0.0); diff --git a/tests/ui/offload/duplicate_kernel.stderr b/tests/ui/offload/duplicate_kernel.stderr index 28b0aca940246..bb0f21b1f1cc3 100644 --- a/tests/ui/offload/duplicate_kernel.stderr +++ b/tests/ui/offload/duplicate_kernel.stderr @@ -1,5 +1,5 @@ error: symbol `_RNvC19collision_kernels_a6kernel` is already defined - --> $DIR/duplicate_kernel.rs:15:1 + --> $DIR/duplicate_kernel.rs:16:1 | LL | fn kernel(_x: f32) {} | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/offload/generic_kernel_not_instantiated.rs b/tests/ui/offload/generic_kernel_not_instantiated.rs index 2df058758024b..36f4f8c667a54 100644 --- a/tests/ui/offload/generic_kernel_not_instantiated.rs +++ b/tests/ui/offload/generic_kernel_not_instantiated.rs @@ -1,5 +1,6 @@ //@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat -Csymbol-mangling-version=v0 //@ build-fail +//@ needs-offload // A generic offload kernel that is never called from host code (and hence // never monomorphized) cannot be discovered without a manifest: with @@ -11,6 +12,7 @@ #![allow(internal_features)] #[rustc_offload_kernel] -fn kernel(x: T) {} //~ ERROR generic offload kernel `kernel` is not instantiated +fn kernel(x: T) {} +//~^ ERROR generic offload kernel `kernel` is not instantiated fn main() {} diff --git a/tests/ui/offload/generic_kernel_not_instantiated.stderr b/tests/ui/offload/generic_kernel_not_instantiated.stderr index ab7c066d09b82..60af2e9b3b92c 100644 --- a/tests/ui/offload/generic_kernel_not_instantiated.stderr +++ b/tests/ui/offload/generic_kernel_not_instantiated.stderr @@ -1,5 +1,5 @@ error: generic offload kernel `kernel` is not instantiated - --> $DIR/generic_kernel_not_instantiated.rs:14:1 + --> $DIR/generic_kernel_not_instantiated.rs:15:1 | LL | fn kernel(x: T) {} | ^^^^^^^^^^^^^^^^^^^^^^^^