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}