Skip to main content

gpu_kernel_proc_macros/
lib.rs

1//! The procedural macros used to implement `gpu-kernel`.
2//!
3//! See the individual macros or the `gpu-kernel` crate for documentation.
4#![deny(missing_docs)]
5extern crate proc_macro;
6
7use std::path::{Path, PathBuf};
8use std::process::Command;
9use std::{env, fs};
10
11use quote::{format_ident, quote};
12use syn::{
13    FnArg, GenericArgument, GenericParam, Generics, Ident, ItemFn, Lifetime, Pat, PathArguments,
14    Safety, Type, parse_macro_input,
15};
16use toml::Table;
17use toml::map::Entry;
18
19/// If the given `lifetime` is `None` or '_, create a named lifetime, otherwise return `None`.
20fn to_explicit_lifetimes(
21    lifetime: Option<&Lifetime>,
22    name: &Ident,
23    i: u32,
24    extra_lifetimes: &mut Vec<Lifetime>,
25) -> Option<Lifetime> {
26    if let Some(lifetime) = lifetime
27        && lifetime.ident != "_"
28    {
29        return None;
30    }
31
32    // Create a new lifetime
33    let ident = format_ident!("_gpu_kernel_lifetime_{}_{}", name, i);
34    let lifetime = Lifetime::new(&format!("'{}", ident), ident.span());
35    extra_lifetimes.push(lifetime.clone());
36    Some(lifetime)
37}
38
39/// Replace all anonymous lifetimes '_ with named ones.
40/// Used as impl trait does not allow anonymous lifetimes.
41fn type_with_explicit_lifetimes(
42    ty: &Type,
43    name: &Ident,
44    i: u32,
45    extra_lifetimes: &mut Vec<Lifetime>,
46) -> proc_macro2::TokenStream {
47    if let Type::Reference(r) = ty {
48        let and = &r.and_token;
49        let lifetime = to_explicit_lifetimes(r.lifetime.as_ref(), name, i, extra_lifetimes)
50            .or_else(|| r.lifetime.clone());
51        let elem = type_with_explicit_lifetimes(&r.elem, name, i, extra_lifetimes);
52        quote! { #and #lifetime #elem }
53    } else if let Type::Path(p) = ty {
54        let mut p = p.clone();
55        if let PathArguments::AngleBracketed(args) = &mut p
56            .path
57            .segments
58            .last_mut()
59            .expect("Unexpected type without last path segment")
60            .arguments
61        {
62            for a in args.args.iter_mut() {
63                if let GenericArgument::Lifetime(lifetime) = a
64                    && let Some(l) = to_explicit_lifetimes(Some(lifetime), name, i, extra_lifetimes)
65                {
66                    *lifetime = l;
67                }
68            }
69        }
70        quote! { #p }
71    } else {
72        quote! { #ty }
73    }
74}
75
76/// Declare a function as a GPU kernel.
77///
78/// The function will be compiled for the GPU and can be launched from the CPU.
79///
80/// On the CPU side a `.launch` function is generated, taking a `&LaunchConfig`
81/// argument as the first argument, followed by all function arguments.
82///
83/// Mutable references are generally forbidden as arguments as the same
84/// arguments are passed to many, parallel executions of the function.
85///
86/// References must be to heap allocated memory or otherwise guarantee they are
87/// part of unified or managed memory, (see the [ROCm unified memory docs],
88/// `gpu-kernel` adds a global allocator that uses `hipMallocManaged()`)
89///
90/// # Unsafe/Safe Kernels
91///
92/// There are two variants to declare a kernel:
93///
94/// 1. If the kernel is marked `unsafe`, all arguments are passed through as they are defined and it
95///    is your responsibility to ensure the arguments are ok to pass.
96/// 2. If the kernel is safe (i.e. not marked `unsafe`), the `launch` function on the CPU side
97///    ensures that only valid arguments can be passed.
98///    To ensure that, a `#[kernel] fn k(arg: Ty)` gets a generated function on the CPU taking
99///    `fn launch(&self, cfg: &LaunchConfig, arg: impl SafeKernelArg<Output = Ty>)`.
100///
101/// # Safe Kernel Arguments
102///
103/// The `SafeKernelArg` trait is unsafe to implement, but it comes pre-implemented for a variety of safe types.
104///
105/// Safe types are:
106///
107/// - All primitive types like signed/unsigned integers
108/// - Pointers (these are safe to pass, but unsafe to dereference)
109/// - Slices can be passed by giving a vector or box reference as argument (`&Vec<T>` → `&[T]` where `T` is safe)
110/// - Strings can be passed by giving a string reference as argument (`&String` → `&str`)
111/// - References to any safe type can be passed by giving a box reference as argument (`&Box<T>` → `T` where `T` is safe)
112/// - If `T` is safe, the same goes for `&Box<[T>]>` → `&[T]`, `&Arc<T>` → `T`, `&GpuBox<T>` and `&GpuBox<[T]>` (see also the documentation for `GpuBox`)
113/// - `ThreadIndexedSlice` can be used to pass a mutable reference to a list where each thread gets access to an element at its thread index (`&mut Vec<T>` → `ThreadIndexedSlice<T>` where `T` is safe)
114///
115/// [ROCm unified memory docs]: https://rocm.docs.amd.com/projects/HIP/en/latest/how-to/hip_runtime_api/memory_management/unified_memory.html
116#[proc_macro_attribute]
117pub fn kernel(
118    _attr: proc_macro::TokenStream,
119    input: proc_macro::TokenStream,
120) -> proc_macro::TokenStream {
121    let func = parse_macro_input!(input as ItemFn);
122    let attrs = func.attrs;
123    let vis = func.vis;
124    let code = func.block;
125    let safety = func.sig.safety;
126    let is_unsafe = matches!(safety, Safety::Unsafe(_));
127    let orig_ident = func.sig.ident;
128    let kernel_ident = format_ident!("{}_gpu_kernel", orig_ident);
129    let kernel_struct_ident = format_ident!("GpuKernel_{}", orig_ident);
130    let inputs = func.sig.inputs;
131    let generics = func.sig.generics;
132    let where_clause = &generics.where_clause;
133    let output = func.sig.output;
134
135    assert!(
136        func.sig.asyncness.is_none(),
137        "#[kernel] `{orig_ident}` cannot be async",
138    );
139    // Forbid type generics but allow lifetimes
140    for g in &generics.params {
141        if !matches!(g, GenericParam::Lifetime(_)) {
142            panic!("#[kernel] `{orig_ident}` cannot be generic");
143        }
144    }
145    assert!(
146        func.sig.variadic.is_none(),
147        "#[kernel] `{orig_ident}` cannot be variadic"
148    );
149
150    // For the argument list, can contain impl Into
151    let mut input_tys = Vec::new();
152    // For the struct initialization `arg0`
153    let mut input_names = Vec::new();
154    // Names for the variables that save the alignment and size of each variable.
155    // Save them in extra variables as we are unable to re-query alignment after the value is moved.
156    let mut input_alignment_names = Vec::new();
157    let mut input_size_names = Vec::new();
158
159    let mut extra_lifetimes = Vec::new();
160
161    for (i, arg) in inputs.iter().enumerate() {
162        let mut name = format_ident!("_gpu_kernel_arg{i}");
163
164        match arg {
165            FnArg::Receiver(_) => {
166                panic!("#[kernel] `{orig_ident}` cannot have a `self` argument");
167            }
168            FnArg::Typed(arg) => {
169                assert!(
170                    arg.attrs.is_empty(),
171                    "#[kernel] `{orig_ident}` arg `{name}` cannot have attributes"
172                );
173                assert!(
174                    !matches!(*arg.ty, Type::ImplTrait(_)),
175                    "#[kernel] `{orig_ident}` arg `{name}` cannot be of `impl Trait` type"
176                );
177                if let Pat::Ident(ident) = &*arg.pat {
178                    name = ident.ident.clone();
179                }
180                if let Type::Reference(r) = &*arg.ty {
181                    assert!(
182                        r.mutability.is_none(),
183                        "#[kernel] `{orig_ident}` arg `{name}` cannot be a mutable reference"
184                    );
185                    assert!(
186                        !matches!(*r.elem, Type::ImplTrait(_)),
187                        "#[kernel] `{orig_ident}` arg `{name}` cannot be of `impl Trait` type"
188                    );
189                }
190                if is_unsafe {
191                    let ty = &arg.ty;
192                    input_tys.push(quote! { #ty });
193                } else {
194                    let ty = type_with_explicit_lifetimes(&arg.ty, &name, 0, &mut extra_lifetimes);
195                    input_tys.push(quote! { impl ::gpu_kernel::SafeKernelArg<Output = #ty> });
196                }
197            }
198        }
199        input_alignment_names.push(format_ident!("_gpu_kernel_align_{name}"));
200        input_size_names.push(format_ident!("_gpu_kernel_size_{name}"));
201        input_names.push(name);
202    }
203
204    let cpu_generics = if generics.lt_token.is_some() {
205        if extra_lifetimes.is_empty() {
206            quote! { #generics }
207        } else {
208            let Generics {
209                lt_token,
210                params,
211                gt_token,
212                ..
213            } = &generics;
214            quote! { #lt_token #(#extra_lifetimes),*, #params #gt_token }
215        }
216    } else {
217        quote! { <#(#extra_lifetimes),*> }
218    };
219
220    let require_safe = if is_unsafe {
221        quote!()
222    } else {
223        // The kernel is not marked as unsafe, so all arguments must implement SafeKernelArg
224        quote!(
225            #(
226                let mut #input_names = <_ as ::gpu_kernel::SafeKernelArg>::into_kernel_arg(#input_names, &gpu_kernel_launch_config);
227            )*
228        )
229    };
230
231    let safe_attrs = if is_unsafe {
232        quote!()
233    } else {
234        // Apply known safe attrs
235        quote! { #[allow(improper_ctypes_definitions, improper_gpu_kernel_arg)] }
236    };
237
238    // Assemble arguments on the CPU
239    let args;
240    let drop;
241    if input_names.len() == 1 {
242        // Fast path, just pass the argument
243        args = quote! {
244            // Move arg to make mutable
245            let mut _gpu_kernel_arg = #(#input_names)*;
246            let _gpu_kernel_args = &mut _gpu_kernel_arg;
247        };
248        drop = quote! {};
249    } else {
250        // Multiple arguments, write them to a vector one by one.
251        // We do not create a struct out of the types as that could require explicit lifetimes and
252        // we want to allow users to not specify them in the function signature.
253        args = quote! {
254            let mut _gpu_kernel_size: usize = 0;
255            #(
256                let #input_alignment_names = std::mem::align_of_val(&#input_names);
257                #[allow(clippy::size_of_ref)]
258                let #input_size_names = std::mem::size_of_val(&#input_names);
259                _gpu_kernel_size =
260                    _gpu_kernel_size.next_multiple_of(#input_alignment_names)
261                    + #input_size_names;
262            )*
263
264            let mut _gpu_kernel_args = std::vec::Vec::<std::mem::MaybeUninit<u8>>::new();
265            _gpu_kernel_args.resize(_gpu_kernel_size, std::mem::MaybeUninit::uninit());
266
267            let mut _gpu_kernel_offset: usize = 0;
268            #(
269                // Align
270                _gpu_kernel_offset = _gpu_kernel_offset.next_multiple_of(#input_alignment_names);
271
272                // Move value
273                unsafe {
274                    std::ptr::write(_gpu_kernel_args.as_mut_ptr().add(_gpu_kernel_offset) as *mut _, #input_names);
275                }
276
277                _gpu_kernel_offset += #input_size_names;
278            )*
279
280            let _gpu_kernel_args = _gpu_kernel_args.as_mut_slice();
281        };
282
283        drop = quote! {
284            _gpu_kernel_offset = 0;
285            #(
286                // Align
287                _gpu_kernel_offset = _gpu_kernel_offset.next_multiple_of(#input_alignment_names);
288
289                // Drop value (implicitly)
290                // We may be unable to name the type, so move back into original variable.
291                unsafe {
292                    #input_names = std::ptr::read(_gpu_kernel_args.as_ptr().add(_gpu_kernel_offset) as *const _);
293                }
294
295                _gpu_kernel_offset += #input_size_names;
296            )*
297        };
298    }
299
300    let output = quote! {
301        // GPU code
302
303        #[cfg(any(target_arch = "amdgpu", target_arch = "nvptx64"))]
304        #[allow(unused_imports)]
305        use ::gpu_kernel::prelude::*;
306
307        // SAFETY: Append "_gpu_kernel" to create a name that can use no_mangle
308        #[cfg(any(target_arch = "amdgpu", target_arch = "nvptx64"))]
309        #[unsafe(no_mangle)]
310        #(#attrs)*
311        #safe_attrs
312        #vis #safety extern "gpu-kernel" fn #kernel_ident #generics(#inputs) #where_clause #output
313            #code
314
315        // CPU code
316
317        #[cfg(not(any(target_arch = "amdgpu", target_arch = "nvptx64")))]
318        #[allow(non_camel_case_types)]
319        #vis struct #kernel_struct_ident(::gpu_kernel::Kernel);
320
321        #[cfg(not(any(target_arch = "amdgpu", target_arch = "nvptx64")))]
322        #[allow(non_upper_case_globals)]
323        #(#attrs)*
324        #vis static #orig_ident: std::sync::LazyLock<#kernel_struct_ident> = std::sync::LazyLock::new(|| {
325            #kernel_struct_ident(crate::KERNEL_LIB_CALLED_IN_CRATE.get_kernel(std::stringify!(#kernel_ident)))
326        });
327
328        #[cfg(not(any(target_arch = "amdgpu", target_arch = "nvptx64")))]
329        impl std::ops::Deref for #kernel_struct_ident {
330            type Target = ::gpu_kernel::Kernel;
331
332            fn deref(&self) -> &Self::Target {
333                &self.0
334            }
335        }
336
337        #[cfg(not(any(target_arch = "amdgpu", target_arch = "nvptx64")))]
338        impl #kernel_struct_ident {
339            #vis #safety fn launch #cpu_generics(&self, gpu_kernel_launch_config: &::gpu_kernel::LaunchConfig, #(mut #input_names: #input_tys),*) #where_clause {
340                #require_safe
341                #args
342                // Launch kernel
343                unsafe {
344                    self.launch_impl(gpu_kernel_launch_config, _gpu_kernel_args);
345                }
346                #drop
347            }
348        }
349    };
350
351    proc_macro::TokenStream::from(output)
352}
353
354/// The `kernel_lib!()` macro, compiling the crate in debug mode.
355///
356/// See `kernel_lib!()` for documentation.
357#[proc_macro]
358pub fn kernel_lib_impl_dbg(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
359    kernel_lib_impl(tokens, true)
360}
361
362/// The `kernel_lib!()` macro, compiling the crate in release mode.
363///
364/// See `kernel_lib!()` for documentation.
365#[proc_macro]
366pub fn kernel_lib_impl_rel(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
367    kernel_lib_impl(tokens, false)
368}
369
370/// Get RUSTFLAGS from env and cargo configs
371fn get_rustflags(env_rustflags: &str, manifest_dir: &Path, target: &str) -> String {
372    let mut all_rustflags = env_rustflags.to_string();
373    for path in manifest_dir
374        .ancestors()
375        .map(|p| p.join(".cargo"))
376        .chain(std::iter::once(
377            env::var("CARGO_HOME")
378                .map(PathBuf::from)
379                .unwrap_or_else(|_| env::home_dir().expect("$CARGO_HOME or ~ must be set")),
380        ))
381    {
382        let cargo_config_path = path.join("config.toml");
383        let config_rustflags =
384            if fs::exists(&cargo_config_path).expect("Failed to check for .cargo/config.toml") {
385                let config = fs::read_to_string(&cargo_config_path)
386                    .expect("Failed to read .cargo/config.toml");
387                let config = config
388                    .parse::<Table>()
389                    .expect("Invalid toml in .cargo/config.toml");
390                config
391                    .get("target")
392                    .and_then(|v| {
393                        v.as_table()
394                            .expect("Failed to parse .cargo/config.toml")
395                            .get(target)
396                    })
397                    .and_then(|v| {
398                        v.as_table()
399                            .expect("Failed to parse .cargo/config.toml")
400                            .get("rustflags")
401                    })
402                    .map(|v| {
403                        v.as_array()
404                            .expect("Failed to parse .cargo/config.toml")
405                            .iter()
406                            .map(|v| {
407                                v.as_str()
408                                    .expect("Failed to parse .cargo/config.toml")
409                                    .to_string()
410                            })
411                            .collect::<Vec<_>>()
412                    })
413                    .unwrap_or_default()
414            } else {
415                Vec::new()
416            };
417        // Prepend
418        let mut new_rustflags = config_rustflags.join(" ");
419        new_rustflags.push_str(&all_rustflags);
420        all_rustflags = new_rustflags;
421    }
422    all_rustflags
423}
424
425struct NewCargoToml {
426    cargo_toml: String,
427    has_gpu_feature: bool,
428}
429
430/// Modify Cargo.toml:
431/// - Insert lib.path = main.rs if lib does not exist
432/// - Set lib.crate-type = cdylib
433/// - Fixup path dependencies
434fn create_cargo_toml(
435    manifest_path: &Path,
436    manifest_dir: &Path,
437    gpu_toml_dir: &Path,
438    orig: &str,
439) -> NewCargoToml {
440    let mut cargo_toml = orig
441        .parse::<Table>()
442        .unwrap_or_else(|e| panic!("Failed to parse {}: {e}", manifest_path.display()));
443    let has_gpu_feature = cargo_toml
444        .get("features")
445        .map(|v| {
446            v.as_table()
447                .expect("features needs to be a toml table")
448                .contains_key("gpu")
449        })
450        .unwrap_or_default();
451    let has_lib = cargo_toml.contains_key("lib")
452        || fs::exists(manifest_dir.join("src").join("lib.rs")).expect("Failed to check for lib.rs");
453    let lib_config = cargo_toml
454        .entry("lib")
455        .or_insert_with(|| Table::new().into())
456        .as_table_mut()
457        .expect("lib needs to be a toml table");
458
459    // Prefix for relative paths from gpu_toml_dir to manifest_dir
460    let rel_prefix = {
461        let manifest = &manifest_dir; // Already canonicialized
462        let gpu = gpu_toml_dir
463            .canonicalize()
464            .expect("Failed to resolve $CARGO_TARGET_DIR");
465        if let Ok(rel) = gpu.strip_prefix(manifest) {
466            let diff = rel.components().count();
467            vec![".."; diff].join("/")
468        } else {
469            // Not a prefix, use an absolute path
470            manifest.display().to_string()
471        }
472    };
473
474    // Set or fixup lib path
475    match lib_config.entry("path") {
476        Entry::Vacant(e) => {
477            let path = if has_lib {
478                format!("{rel_prefix}/src/lib.rs")
479            } else {
480                format!("{rel_prefix}/src/main.rs")
481            };
482            e.insert(path.into());
483        }
484        Entry::Occupied(mut e) => {
485            // Fixup relative path
486            let path = Path::new(e.get().as_str().expect("lib path must be a toml string"));
487            if path.is_relative() {
488                let new = Path::new(&rel_prefix).join(path).display().to_string();
489                e.insert(new.into());
490            }
491        }
492    }
493
494    lib_config.insert("crate-type".into(), vec!["cdylib"].into());
495
496    // Fixup all relative dependency paths in the Cargo.toml
497    let fix_dep = |v: &mut toml::Value| {
498        if let Some(v) = v.as_table_mut()
499            && let Some(p) = v.get_mut("path")
500        {
501            let path = Path::new(p.as_str().expect("Dependency path must be a toml string"));
502            if path.is_relative() {
503                let new = Path::new(&rel_prefix).join(path).display().to_string();
504                *p = new.into();
505            }
506        }
507    };
508    let dep_keys = &["dependencies", "build-dependencies", "dev-dependencies"];
509    let fix_all_deps = |t: &mut Table| {
510        for k in dep_keys {
511            if let Some(t) = t.get_mut(*k) {
512                let t = t
513                    .as_table_mut()
514                    .unwrap_or_else(|| panic!("{k} must be a toml table"));
515                for (_, v) in t.iter_mut() {
516                    fix_dep(v);
517                }
518            }
519        }
520    };
521
522    // Either in root or in target.<something>
523    fix_all_deps(&mut cargo_toml);
524    if let Some(t) = cargo_toml.get_mut("target") {
525        let t = t.as_table_mut().expect("target must be a toml table");
526        for (_, v) in t.iter_mut() {
527            fix_all_deps(v.as_table_mut().expect("target must contain toml tables"));
528        }
529    }
530    NewCargoToml {
531        cargo_toml: cargo_toml.to_string(),
532        has_gpu_feature,
533    }
534}
535
536fn kernel_lib_impl(_: proc_macro::TokenStream, debug: bool) -> proc_macro::TokenStream {
537    #[cfg(feature = "amd")]
538    let target = "amdgcn-amd-amdhsa";
539    #[cfg(not(feature = "amd"))]
540    let target = "";
541
542    let target_env = target.replace('-', "_").to_uppercase();
543    let target_rustflags = format!("CARGO_TARGET_{target_env}_RUSTFLAGS");
544    let target_cargoflags = format!("CARGO_TARGET_{target_env}_FLAGS");
545
546    // Compile gpu crate here
547    let crate_name = env::var("CARGO_CRATE_NAME").expect("$CARGO_CRATE_NAME must be set");
548    let kernel_file = format!("{crate_name}.elf");
549    let manifest_dir =
550        PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR must be set"))
551            .canonicalize()
552            .expect("Failed to resolve $CARGO_MANIFEST_DIR");
553    let manifest_path =
554        PathBuf::from(env::var("CARGO_MANIFEST_PATH").expect("$CARGO_MANIFEST_PATH must be set"));
555    let lock_path = manifest_dir.join("Cargo.lock");
556    // Use CARGO_TARGET_DIR if set
557    let target_dir = env::var("CARGO_TARGET_DIR")
558        .map(PathBuf::from)
559        .unwrap_or_else(|_| manifest_dir.join("target"))
560        .join("gpu-kernel");
561    let kernel_path = target_dir
562        .join(target)
563        .join(if debug { "debug" } else { "release" })
564        .join(&kernel_file);
565
566    let env_rustflags = env::var(&target_rustflags).unwrap_or_default();
567    // Custom setting
568    let cargoflags = env::var(&target_cargoflags).unwrap_or_default();
569    let cargoflags = cargoflags.trim();
570
571    let all_rustflags = get_rustflags(&env_rustflags, &manifest_dir, target);
572
573    // Find important things in flags
574    let target_cpu = {
575        let i = all_rustflags.rfind("target-cpu").unwrap_or_else(|| panic!("Did not find target-cpu, make sure to set `-Ctarget-cpu=...` in ${target_rustflags}"));
576        let start = i + "target-cpu".len() + 1;
577        let end = all_rustflags[start..]
578            .find(' ')
579            .map(|i| start + i)
580            .unwrap_or(all_rustflags.len());
581        &all_rustflags[start..end]
582    };
583    // Enabled and not disabled or enabling comes later than disabling
584    #[cfg(feature = "amd")]
585    let is_wave64_enabled = all_rustflags
586        .rfind("+wavefrontsize64")
587        .map(|i| {
588            if let Some(j) = all_rustflags.rfind("-wavefrontsize64") {
589                i > j
590            } else {
591                true
592            }
593        })
594        .unwrap_or_default();
595
596    #[cfg(feature = "amd")]
597    let link_args =
598        amdgpu_device_libs_build::get_link_args(is_wave64_enabled, &target_cpu).link_args;
599    #[cfg(not(feature = "amd"))]
600    let link_args = [/* mark as used */ target_cpu];
601    let new_rustflags = link_args
602        .iter()
603        .map(|v| format!("-Clink-arg={v}"))
604        .collect::<Vec<_>>();
605
606    // Copy Cargo.toml, insert lib.path = main.rs if lib does not exist, set lib.crate-type = cdylib
607    let cargo_toml = fs::read_to_string(&manifest_path)
608        .unwrap_or_else(|e| panic!("Failed to read {}: {e}", manifest_path.display()));
609
610    let gpu_toml_dir = target_dir.clone();
611    fs::create_dir_all(&gpu_toml_dir).expect("Failed to create gpu-kernel target dir");
612    let NewCargoToml {
613        cargo_toml,
614        has_gpu_feature,
615    } = create_cargo_toml(&manifest_path, &manifest_dir, &gpu_toml_dir, &cargo_toml);
616
617    // Write new Cargo.toml
618    let gpu_toml = gpu_toml_dir.join("Cargo.toml");
619    fs::write(&gpu_toml, cargo_toml.as_bytes()).expect("Failed to write GPU Cargo.toml");
620    // Copy Cargo.lock
621    if let Err(e) = fs::copy(&lock_path, gpu_toml_dir.join("Cargo.lock")) {
622        println!("Warning: Failed to copy Cargo.lock to GPU directory ({e}), ignoring");
623    }
624
625    let mut cargo = Command::new("cargo");
626    cargo.args([
627        "build",
628        "--target",
629        target,
630        "--lib",
631        "-Zbuild-std=core,alloc",
632        "-m",
633        &gpu_toml.display().to_string(),
634        "--target-dir",
635        &target_dir.display().to_string(),
636    ]);
637    if has_gpu_feature {
638        cargo.arg("--features=gpu");
639    }
640    if !debug {
641        // Compile with panic=immediate-abort,
642        // because GPU code is often quite performance sensitive and just the
643        // existence of panic messages can slow things down considerably.
644        // E.g. the vector_add_fast example gets a speed-up of 6%.
645        cargo.args([
646            "--release",
647            "-Zpanic-immediate-abort",
648            "--config=profile.release.panic=\"immediate-abort\"",
649        ]);
650    } else {
651        // Compile always with optimizations.
652        // Compiling without optimizations can lead to crashes or compilation failures.
653        cargo.arg("--config=profile.dev.opt-level=2");
654    }
655    if !cargoflags.is_empty() {
656        for f in cargoflags.split(' ') {
657            cargo.arg(f);
658        }
659    }
660
661    cargo.env(
662        &target_rustflags,
663        format!(
664            "{env_rustflags} {} -Clinker-plugin-lto",
665            new_rustflags.join(" ")
666        ),
667    );
668    let res = cargo
669        .status()
670        .expect("Failed to run cargo to compile for GPU");
671    if !res.success() {
672        panic!("Cargo did not exit successfully, failed to compile for GPU");
673    }
674
675    let kernel_path = kernel_path.display().to_string();
676    let manifest_path = manifest_path.display().to_string();
677    let lock_path = lock_path.display().to_string();
678    let output = quote! {
679        // Changes to Cargo.toml can affect the GPU build.
680        // Dummy include to re-run the macro if it changed.
681        // Use proc_macro tracked path and env once it is stable.
682        const _: &[u8] = std::include_bytes!(#manifest_path);
683        const _: &[u8] = std::include_bytes!(#lock_path);
684        const _: std::option::Option<&str> = std::option_env!("CARGO_TARGET_DIR");
685        const _: std::option::Option<&str> = std::option_env!(#target_rustflags);
686        const _: std::option::Option<&str> = std::option_env!(#target_cargoflags);
687
688        #[doc(hidden)]
689        static GPU_KERNEL_MODULE_DATA: &[u8] = std::include_bytes!(#kernel_path);
690        // If someone forgets to call kernel_lib!() and defines a kernel, they will see
691        // an error that this is not found.
692        // Use the name to hint the user what is missing.
693        #[doc(hidden)]
694        static KERNEL_LIB_CALLED_IN_CRATE: std::sync::LazyLock<::gpu_kernel::Module> = std::sync::LazyLock::new(|| ::gpu_kernel::Module::new(GPU_KERNEL_MODULE_DATA));
695    };
696    proc_macro::TokenStream::from(output)
697}