Skip to main content

cubecl_macros/
lib.rs

1#![allow(clippy::large_enum_variant)]
2
3use core::panic;
4
5use error::error_into_token_stream;
6use generate::autotune::generate_autotune_key;
7use parse::{
8    cube_impl::CubeImpl,
9    cube_trait::{CubeTrait, CubeTraitImpl},
10    helpers::RemoveHelpers,
11    kernel::{KernelArgs, Launch, from_tokens},
12};
13use proc_macro::TokenStream;
14use quote::quote;
15use syn::{Item, visit_mut::VisitMut};
16
17use crate::{
18    generate::{
19        asm::generate_asm_unexpanded, assign::generate_cube_type_mut,
20        into_runtime::generate_into_runtime,
21    },
22    parse::{
23        cube_type::{generate_cube_harness, generate_cube_type},
24        derive_expand::generate_derive_expand,
25        helpers::ReplaceDefines,
26    },
27};
28
29mod error;
30mod expression;
31mod generate;
32mod operator;
33mod parse;
34mod paths;
35mod scope;
36mod statement;
37
38/// Mark a cube function, trait or implementation for expansion.
39///
40/// # Arguments
41/// * `launch` - generates a function to launch the kernel
42/// * `launch_unchecked` - generates a launch function without checks
43/// * `debug` - panics after generation to print the output to console
44/// * `create_dummy_kernel` - Generates a function to create a kernel without launching it. Used for
45///   testing.
46/// * `harness` - generates a `harness` function that runs the function on a single unit of a
47///   runtime and returns its result to the host, so a test can call `f::harness::<R>(..)` instead
48///   of writing a kernel, buffers and a read back by hand. Every parameter must be a scalar, a
49///   slice or array of scalars, a comptime scalar or a cube type that derives `CubeHarness`, and
50///   the function must return them or a tuple of them, or nothing when it writes into a mutable
51///   slice. The harness is gated:
52///   * `harness` - same as `harness(cfg(test))`
53///   * `harness(cfg(<predicate>))` - compiled under `#[cfg(<predicate>)]`. The predicate is
54///     evaluated in the crate using the macro, so `feature = "..."` names one of its features.
55///   * `harness(always)` - ungated
56///
57/// # Trait arguments
58/// * `expand_base_traits` - base traits for the expanded "second half" of a trait with methods.
59///
60/// # Example
61///
62/// ```ignored
63/// # use cubecl_macros::cube;
64/// #[cube]
65/// fn my_addition(a: u32, b: u32) -> u32 {
66///     a + b
67/// }
68/// ```
69///
70/// # Harness example
71///
72/// ```ignored
73/// #[cube(harness)]
74/// fn add<F: Float>(x: F, y: F) -> F {
75///     x + y
76/// }
77///
78/// #[test]
79/// fn adds() {
80///     assert_eq!(add::harness::<CpuRuntime, f32>(1.0, 2.0), 3.0);
81/// }
82/// ```
83#[proc_macro_attribute]
84pub fn cube(args: TokenStream, input: TokenStream) -> TokenStream {
85    match cube_impl(args, input.clone()) {
86        Ok(tokens) => tokens,
87        Err(e) => error_into_token_stream(e, input.into()).into(),
88    }
89}
90
91fn cube_impl(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
92    let mut item: Item = syn::parse(input)?;
93    let args: KernelArgs = from_tokens(args.into())?;
94
95    if args.harness.is_some() && !matches!(item, Item::Fn(_)) {
96        return Err(syn::Error::new_spanned(
97            item,
98            "`harness` is only supported on functions",
99        ));
100    }
101
102    let tokens = match item.clone() {
103        Item::Fn(kernel) => {
104            let kernel = Launch::from_item_fn(kernel, args)?;
105            RemoveHelpers.visit_item_mut(&mut item);
106            ReplaceDefines.visit_item_mut(&mut item);
107
108            let extra_allow = match kernel.func.context.is_intrinsic {
109                true => quote![#[allow(unused_variables)]],
110                false => quote![],
111            };
112
113            return Ok(TokenStream::from(quote! {
114                #[allow(dead_code, clippy::too_many_arguments)]
115                #extra_allow
116                #item
117                #kernel
118            }));
119        }
120        Item::Trait(kernel_trait) => {
121            let is_debug = args.debug.is_present();
122            let expand_trait = CubeTrait::from_item_trait(kernel_trait, args)?;
123
124            let tokens = TokenStream::from(quote! {
125                #expand_trait
126            });
127            if is_debug {
128                panic!("{tokens}");
129            }
130            return Ok(tokens);
131        }
132        Item::Impl(item_impl) => {
133            if item_impl.trait_.is_some() {
134                let mut expand_impl = CubeTraitImpl::from_item_impl(item_impl, &args)?;
135                let expand_impl = expand_impl.to_tokens_mut();
136
137                Ok(TokenStream::from(quote! {
138                    #expand_impl
139                }))
140            } else {
141                let mut expand_impl = CubeImpl::from_item_impl(item_impl, &args)?;
142                let expand_impl = expand_impl.to_tokens_mut();
143
144                Ok(TokenStream::from(quote! {
145                    #expand_impl
146                }))
147            }
148        }
149        item => Err(syn::Error::new_spanned(
150            item,
151            "`#[cube]` is only supported on traits and functions",
152        ))?,
153    };
154
155    if args.debug.is_present() {
156        match tokens {
157            Ok(tokens) => panic!("{tokens}"),
158            Err(err) => panic!("{err}"),
159        };
160    }
161
162    tokens
163}
164
165/// Derive macro to define a cube type that is launched with a kernel
166#[proc_macro_derive(CubeLaunch, attributes(cube, launch))]
167pub fn module_derive_cube_launch(input: TokenStream) -> TokenStream {
168    gen_cube_type(input, true)
169}
170
171/// Derive macro to define a cube type that is not launched
172#[proc_macro_derive(CubeType, attributes(cube, expand))]
173pub fn module_derive_cube_type(input: TokenStream) -> TokenStream {
174    gen_cube_type(input, false)
175}
176
177/// Derive macro to let a cube type cross between the host and a cube
178/// function's `harness`, as an input or an output. The type must also derive
179/// `CubeType` and `CubeLaunch`.
180///
181/// Its impls are gated like a harness, by `cfg(test)` unless another gate is
182/// given with `#[cube(harness(cfg(<predicate>)))]` or `#[cube(harness(always))]`.
183/// It must be at least as permissive as the gate of every harness using the
184/// type.
185#[proc_macro_derive(CubeHarness, attributes(cube, launch))]
186pub fn module_derive_cube_harness(input: TokenStream) -> TokenStream {
187    let parsed = syn::parse(input);
188
189    let input = match &parsed {
190        Ok(val) => val,
191        Err(err) => return err.to_compile_error().into(),
192    };
193
194    match generate_cube_harness(input) {
195        Ok(val) => val.into(),
196        Err(err) => err.to_compile_error().into(),
197    }
198}
199
200fn gen_cube_type(input: TokenStream, with_launch: bool) -> TokenStream {
201    let parsed = syn::parse(input);
202
203    let input = match &parsed {
204        Ok(val) => val,
205        Err(err) => return err.to_compile_error().into(),
206    };
207
208    match generate_cube_type(input, with_launch) {
209        Ok(val) => val.into(),
210        Err(err) => err.to_compile_error().into(),
211    }
212}
213
214/// Attribute macro to define a type that can be used as a kernel comptime
215/// argument This derive Debug, Hash, `PartialEq`, Eq, Clone, Copy
216#[proc_macro_attribute]
217pub fn derive_cube_comptime(_metadata: TokenStream, input: TokenStream) -> TokenStream {
218    let input: proc_macro2::TokenStream = input.into();
219    quote! {
220        #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
221        #input
222    }
223    .into()
224}
225
226/// Attribute macro to derive cube traits for existing structs, without redefining that struct.
227#[proc_macro_attribute]
228pub fn derive_expand(metadata: TokenStream, input: TokenStream) -> TokenStream {
229    match generate_derive_expand(input.into(), metadata.into()) {
230        Ok(val) => val.into(),
231        Err(err) => err.to_compile_error().into(),
232    }
233}
234
235/// Mark the contents of this macro as compile time values, turning off all
236/// expansion for this code and using it verbatim
237///
238/// # Example
239/// ```ignored
240/// #use cubecl_macros::cube;
241/// #fn some_rust_function(a: u32) -> u32 {}
242/// #[cube]
243/// fn do_stuff(input: u32) -> u32 {
244///     let comptime_value = comptime! { some_rust_function(3) };
245///     input + comptime_value
246/// }
247/// ```
248#[proc_macro]
249pub fn comptime(input: TokenStream) -> TokenStream {
250    let tokens: proc_macro2::TokenStream = input.into();
251    quote![{ #tokens }].into()
252}
253
254/// Mark the contents of this macro as an intrinsic, turning off all expansion
255/// for this code and calling it with the scope
256///
257/// # Example
258/// ```ignored
259/// #use cubecl_macros::cube;
260/// #[cube]
261/// fn do_stuff(input: u32) -> u32 {
262///     let comptime_value = intrinsic! { |scope| u32::elem_size(scope) };
263///     input + comptime_value
264/// }
265/// ```
266#[proc_macro]
267pub fn intrinsic(_input: TokenStream) -> TokenStream {
268    quote![{ cubecl::unexpanded!() }].into()
269}
270
271/// GPU version of [`asm`](std::arch::asm). Currently parses all the same options, but most are not
272/// applicable to GPU assembly architectures. Should validate and give proper errors at some point.
273/// Also adds a new register spec: the inferred register specifier (`_`). This is because the
274/// specifier isn't actually meaningful in PTX and is currently ignored, with constraints being
275/// inferred from the value type. The reason it's still present is because we may want to add an
276/// explicit `mem` specifier to allow fine-grained memory clobbering, or support for other assembly
277/// formats that do use different register types.
278///
279/// # Example
280/// ```ignored
281/// #use cubecl_macros::cube;
282/// #[cube]
283/// fn do_stuff(input: u32) -> u32 {
284///     let mut out: u32;
285///     gpu_asm!("some.custom.ptx {} {}", out(_) out, in(_) input)
286/// }
287/// ```
288#[proc_macro]
289pub fn gpu_asm(input: TokenStream) -> TokenStream {
290    match generate_asm_unexpanded(input.into()) {
291        Ok(val) => val.into(),
292        Err(err) => err.to_compile_error().into(),
293    }
294}
295
296/// Makes the function return a compile time value
297/// Useful in a cube trait to have a part of the trait return comptime values
298///
299/// # Example
300/// ```ignored
301/// #use cubecl_macros::cube;
302/// #[cube]
303/// fn do_stuff(#[comptime] input: u32) -> comptime_type!(u32) {
304///     input + 5   
305/// }
306/// ```
307///
308/// TODO: calling a trait method returning `comptime_type` from
309/// within another trait method does not work
310#[proc_macro]
311pub fn comptime_type(input: TokenStream) -> TokenStream {
312    let tokens: proc_macro2::TokenStream = input.into();
313    quote![ #tokens ].into()
314}
315
316/// Insert a literal comment into the kernel source code.
317///
318/// # Example
319/// ```ignored
320/// #use cubecl_macros::cube;
321/// #[cube]
322/// fn do_stuff(input: u32) -> u32 {
323///     comment!("Add five to the input");
324///     input + 5
325/// }
326/// ```
327#[proc_macro]
328pub fn comment(input: TokenStream) -> TokenStream {
329    let tokens: proc_macro2::TokenStream = input.into();
330    quote![{ #tokens }].into()
331}
332
333/// Terminate the execution of the kernel for the current unit.
334///
335/// This terminates the execution of the unit even if nested inside many
336/// functions.
337///
338/// # Example
339/// ```ignored
340/// #use cubecl_macros::cube;
341/// #[cube]
342/// fn stop_if_more_than_ten(input: u32)  {
343///     if input > 10 {
344///         terminate!();
345///     }
346/// }
347/// ```
348#[proc_macro]
349pub fn terminate(input: TokenStream) -> TokenStream {
350    let tokens: proc_macro2::TokenStream = input.into();
351    quote![{ #tokens }].into()
352}
353
354/// Implements display and initialization for autotune keys.
355///
356/// # Helper
357///
358/// Use the `#[autotune(anchor)]` helper attribute to anchor a numerical value.
359/// This groups multiple numerical values into the same bucket.
360///
361/// For now, only an exponential function is supported, and it can be modified
362/// with `exp`. By default, the base is '2' and there are no `min` or `max`
363/// provided.
364///
365/// # Example
366/// ```ignore
367/// #[derive(AutotuneKey, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
368/// pub struct OperationKey {
369///     #[autotune(name = "Batch Size")]
370///     batch_size: usize,
371///     channels: usize,
372///     #[autotune(anchor(exp(min = 16, max = 1024, base = 2)))]
373///     height: usize,
374///     #[autotune(anchor)]
375///     width: usize,
376/// }
377/// ```
378#[proc_macro_derive(AutotuneKey, attributes(autotune))]
379pub fn derive_autotune_key(input: TokenStream) -> TokenStream {
380    let input = syn::parse(input).unwrap();
381    match generate_autotune_key(input) {
382        Ok(tokens) => tokens.into(),
383        Err(e) => e.into_compile_error().into(),
384    }
385}
386
387/// Implements `IntoRuntime` for a `CubeType`
388#[proc_macro_derive(IntoRuntime, attributes(cube))]
389pub fn derive_into_runtime(input: TokenStream) -> TokenStream {
390    let input = syn::parse(input).unwrap();
391    match generate_into_runtime(&input) {
392        Ok(tokens) => tokens.into(),
393        Err(e) => e.into_compile_error().into(),
394    }
395}
396
397/// Implements mutability for a `CubeType`
398#[proc_macro_derive(CubeTypeMut, attributes(cube))]
399pub fn derive_assign(input: TokenStream) -> TokenStream {
400    let input = syn::parse(input).unwrap();
401    match generate_cube_type_mut(&input) {
402        Ok(tokens) => tokens.into(),
403        Err(e) => e.into_compile_error().into(),
404    }
405}