Skip to main content

dtact_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4    FnArg, ItemFn, Lit, Meta, Token, parse::Parse, parse::ParseStream, parse_macro_input,
5    punctuated::Punctuated,
6};
7
8struct TaskArgs {
9    priority: String,
10    affinity: String,
11    kind: String,
12    stack: String,
13    switcher: String,
14}
15
16impl Parse for TaskArgs {
17    fn parse(input: ParseStream) -> syn::Result<Self> {
18        let vars = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
19        let mut priority = "Normal".to_string();
20        let mut affinity = "SameCore".to_string();
21        let mut kind = "Compute".to_string();
22        let mut stack = "2M".to_string();
23        let mut switcher = "CrossThreadFloat".to_string();
24
25        for var in vars {
26            if let Meta::NameValue(nv) = var {
27                if nv.path.is_ident("priority") {
28                    if let syn::Expr::Lit(syn::ExprLit {
29                        lit: Lit::Str(s), ..
30                    }) = nv.value
31                    {
32                        priority = s.value();
33                    }
34                } else if nv.path.is_ident("affinity") {
35                    if let syn::Expr::Lit(syn::ExprLit {
36                        lit: Lit::Str(s), ..
37                    }) = nv.value
38                    {
39                        affinity = s.value();
40                    }
41                } else if nv.path.is_ident("kind") {
42                    if let syn::Expr::Lit(syn::ExprLit {
43                        lit: Lit::Str(s), ..
44                    }) = nv.value
45                    {
46                        kind = s.value();
47                    }
48                } else if nv.path.is_ident("stack") {
49                    if let syn::Expr::Lit(syn::ExprLit {
50                        lit: Lit::Str(s), ..
51                    }) = nv.value
52                    {
53                        stack = s.value();
54                    }
55                } else if nv.path.is_ident("switcher")
56                    && let syn::Expr::Lit(syn::ExprLit {
57                        lit: Lit::Str(s), ..
58                    }) = nv.value
59                {
60                    switcher = s.value();
61                }
62            }
63        }
64
65        Ok(TaskArgs {
66            priority,
67            affinity,
68            kind,
69            stack,
70            switcher,
71        })
72    }
73}
74
75#[proc_macro_attribute]
76pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
77    let args = parse_macro_input!(args as TaskArgs);
78    let mut input = parse_macro_input!(item as ItemFn);
79
80    let fn_name = &input.sig.ident;
81    let priority = &args.priority;
82    let affinity = &args.affinity;
83    let kind = &args.kind;
84    let stack = &args.stack;
85
86    let metadata_mod = syn::Ident::new(&format!("dtact_metadata_{}", fn_name), fn_name.span());
87    let priority_ident = syn::Ident::new(priority, fn_name.span());
88    let affinity_ident = syn::Ident::new(affinity, fn_name.span());
89    let kind_ident = syn::Ident::new(kind, fn_name.span());
90    let switcher = &args.switcher;
91    let switcher_ident = syn::Ident::new(switcher, fn_name.span());
92
93    let return_type = match &input.sig.output {
94        syn::ReturnType::Default => quote! { () },
95        syn::ReturnType::Type(_, ty) => quote! { #ty },
96    };
97
98    input.sig.asyncness = None;
99    input.sig.output = syn::parse2(quote! {
100        -> dtact::api::TaskFuture<impl std::future::Future<Output = #return_type> + Send + 'static, dtact::#switcher_ident>
101    }).unwrap();
102
103    let vis = &input.vis;
104    let attrs = &input.attrs;
105    let sig = &input.sig;
106    let body = &input.block;
107
108    let expanded = quote! {
109        #(#attrs)*
110        #vis #sig {
111            let fut = async move #body;
112            dtact::api::TaskFuture {
113                future: fut,
114                priority: dtact::Priority::#priority_ident,
115                affinity: dtact::topology::Affinity::#affinity_ident,
116                kind: dtact::WorkloadKind::#kind_ident,
117                _marker: std::marker::PhantomData,
118            }
119        }
120
121        pub mod #metadata_mod {
122            pub const PRIORITY: dtact::Priority = dtact::Priority::#priority_ident;
123            pub const AFFINITY: dtact::topology::Affinity = dtact::topology::Affinity::#affinity_ident;
124            pub const KIND: dtact::WorkloadKind = dtact::WorkloadKind::#kind_ident;
125            pub const STACK_SIZE: &'static str = #stack;
126            pub type SWITCHER = dtact::#switcher_ident;
127        }
128    };
129
130    TokenStream::from(expanded)
131}
132
133#[proc_macro_attribute]
134pub fn export_async(_args: TokenStream, item: TokenStream) -> TokenStream {
135    let input = parse_macro_input!(item as ItemFn);
136    let fn_name = &input.sig.ident;
137    let wrapper_name = syn::Ident::new(&format!("dtact_export_{}", fn_name), fn_name.span());
138
139    let mut c_params = Vec::new();
140    let mut call_args = Vec::new();
141
142    for input in &input.sig.inputs {
143        if let FnArg::Typed(pat_type) = input {
144            let pat = &pat_type.pat;
145            let ty = &pat_type.ty;
146            c_params.push(quote! { #pat: #ty });
147            call_args.push(quote! { #pat });
148        } else {
149            panic!("export_async does not support 'self' parameters");
150        }
151    }
152
153    let expanded = quote! {
154        #input
155
156        #[unsafe(no_mangle)]
157        pub extern "C" fn #wrapper_name(#(#c_params),*) -> dtact::dtact_handle_t {
158            dtact::spawn(#fn_name(#(#call_args),*))
159        }
160    };
161
162    TokenStream::from(expanded)
163}
164
165#[proc_macro_attribute]
166pub fn export_fiber(_args: TokenStream, item: TokenStream) -> TokenStream {
167    let input = parse_macro_input!(item as ItemFn);
168    let fn_name = &input.sig.ident;
169    let wrapper_name = syn::Ident::new(&format!("dtact_export_fiber_{}", fn_name), fn_name.span());
170
171    let mut c_params = Vec::new();
172    let mut call_args = Vec::new();
173
174    for input in &input.sig.inputs {
175        if let FnArg::Typed(pat_type) = input {
176            let pat = &pat_type.pat;
177            let ty = &pat_type.ty;
178            c_params.push(quote! { #pat: #ty });
179            call_args.push(quote! { #pat });
180        } else {
181            panic!("export_fiber does not support 'self' parameters");
182        }
183    }
184
185    let expanded = quote! {
186        #input
187
188        #[unsafe(no_mangle)]
189        pub extern "C" fn #wrapper_name(#(#c_params),*) -> dtact::dtact_handle_t {
190            dtact::api::fiber::spawn_with_stack("2M", move || {
191                #fn_name(#(#call_args),*);
192            })
193        }
194    };
195
196    TokenStream::from(expanded)
197}
198
199struct InitArgs {
200    topology: String,
201    safety: String,
202    workers: usize,
203    capacity: u32,
204    stack: usize,
205    numa: usize,
206}
207
208impl Parse for InitArgs {
209    fn parse(input: ParseStream) -> syn::Result<Self> {
210        let vars = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
211        let mut topology = "P2PMesh".to_string();
212        let mut safety = "Safety1".to_string();
213        let mut workers = 0;
214        let mut capacity = 4096;
215        let mut stack = 512 * 1024;
216        let mut numa = 0;
217
218        for var in vars {
219            if let Meta::NameValue(nv) = var {
220                if nv.path.is_ident("topology") {
221                    if let syn::Expr::Lit(syn::ExprLit {
222                        lit: Lit::Str(s), ..
223                    }) = &nv.value
224                    {
225                        topology = s.value();
226                    }
227                } else if nv.path.is_ident("safety") {
228                    if let syn::Expr::Lit(syn::ExprLit {
229                        lit: Lit::Str(s), ..
230                    }) = &nv.value
231                    {
232                        safety = s.value();
233                    }
234                } else if nv.path.is_ident("workers") {
235                    if let syn::Expr::Lit(syn::ExprLit {
236                        lit: Lit::Int(i), ..
237                    }) = &nv.value
238                    {
239                        workers = i.base10_parse()?;
240                    }
241                } else if nv.path.is_ident("capacity") {
242                    if let syn::Expr::Lit(syn::ExprLit {
243                        lit: Lit::Int(i), ..
244                    }) = &nv.value
245                    {
246                        capacity = i.base10_parse()?;
247                    }
248                } else if nv.path.is_ident("stack") {
249                    if let syn::Expr::Lit(syn::ExprLit {
250                        lit: Lit::Int(i), ..
251                    }) = &nv.value
252                    {
253                        stack = i.base10_parse()?;
254                    }
255                } else if nv.path.is_ident("numa")
256                    && let syn::Expr::Lit(syn::ExprLit {
257                        lit: Lit::Int(i), ..
258                    }) = &nv.value
259                {
260                    numa = i.base10_parse()?;
261                }
262            }
263        }
264        Ok(InitArgs {
265            topology,
266            safety,
267            workers,
268            capacity,
269            stack,
270            numa,
271        })
272    }
273}
274
275#[proc_macro_attribute]
276pub fn dtact_init(args: TokenStream, item: TokenStream) -> TokenStream {
277    let args = parse_macro_input!(args as InitArgs);
278    let input = parse_macro_input!(item as ItemFn);
279
280    let topology = &args.topology;
281    let safety = &args.safety;
282    let workers = args.workers;
283    let capacity = args.capacity;
284    let stack = args.stack;
285    let numa = args.numa;
286
287    let topology_ident = syn::Ident::new(topology, input.sig.ident.span());
288    let safety_ident = syn::Ident::new(safety, input.sig.ident.span());
289    let autostart_fn_name = syn::Ident::new("dtact_autostart", input.sig.ident.span());
290
291    let attrs = &input.attrs;
292    let vis = &input.vis;
293    let sig = &input.sig;
294    let block = &input.block;
295
296    let expanded = quote! {
297        #[unsafe(no_mangle)]
298        extern "C" fn #autostart_fn_name() {
299            let runtime = dtact::GLOBAL_RUNTIME.get_or_init(|| {
300                let mut workers_count = #workers;
301                if workers_count == 0 {
302                    workers_count = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
303                }
304
305                let scheduler = dtact::dta_scheduler::DtaScheduler::new(
306                    workers_count,
307                    dtact::dta_scheduler::TopologyMode::#topology_ident
308                );
309
310                let pool = dtact::memory_management::ContextPool::new(
311                    #capacity,
312                    #stack,
313                    dtact::memory_management::SafetyLevel::#safety_ident,
314                    #numa
315                ).expect("DTA-V3 Hardware Initialization Failed");
316
317                dtact::Runtime {
318                    scheduler,
319                    pool,
320                    started: core::sync::atomic::AtomicBool::new(false),
321                    shutdown: core::sync::atomic::AtomicBool::new(false),
322                }
323            });
324            runtime.start();
325        }
326
327        #(#attrs)* #vis #sig {
328            #autostart_fn_name();
329            #block
330        }
331    };
332
333    TokenStream::from(expanded)
334}
335
336/// Parsed `name = value` attribute args shared by [`dtact_io_init`],
337/// [`fs_init`], and [`process_init`] — all three accept the same five
338/// knobs (`workers`, `buffer_pool_size`, `chunk_size`, `ring_depth`,
339/// `pin_cpus`) and each emits a call to its module's raw five-knob init
340/// function in that module's canonical `(workers, ring_depth,
341/// buffer_pool_size, chunk_size, pin_cpus)` argument order.
342struct IoInitArgs {
343    workers: usize,
344    buffer_pool_size: usize,
345    chunk_size: usize,
346    pin_cpus: Vec<usize>,
347    ring_depth: u32,
348}
349
350impl Parse for IoInitArgs {
351    fn parse(input: ParseStream) -> syn::Result<Self> {
352        let vars = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
353        let mut workers = 1;
354        let mut buffer_pool_size = 65536;
355        let mut chunk_size = 4096;
356        let mut pin_cpus = Vec::new();
357        let mut ring_depth = 256;
358
359        for var in vars {
360            if let Meta::NameValue(nv) = var {
361                if nv.path.is_ident("workers") {
362                    if let syn::Expr::Lit(syn::ExprLit {
363                        lit: Lit::Int(i), ..
364                    }) = &nv.value
365                    {
366                        workers = i.base10_parse()?;
367                    }
368                } else if nv.path.is_ident("buffer_pool_size") {
369                    if let syn::Expr::Lit(syn::ExprLit {
370                        lit: Lit::Int(i), ..
371                    }) = &nv.value
372                    {
373                        buffer_pool_size = i.base10_parse()?;
374                    }
375                } else if nv.path.is_ident("chunk_size") {
376                    if let syn::Expr::Lit(syn::ExprLit {
377                        lit: Lit::Int(i), ..
378                    }) = &nv.value
379                    {
380                        chunk_size = i.base10_parse()?;
381                    }
382                } else if nv.path.is_ident("ring_depth") {
383                    if let syn::Expr::Lit(syn::ExprLit {
384                        lit: Lit::Int(i), ..
385                    }) = &nv.value
386                    {
387                        ring_depth = i.base10_parse()?;
388                    }
389                } else if nv.path.is_ident("pin_cpus")
390                    && let syn::Expr::Array(syn::ExprArray { elems, .. }) = &nv.value
391                {
392                    for elem in elems {
393                        if let syn::Expr::Lit(syn::ExprLit {
394                            lit: Lit::Int(i), ..
395                        }) = elem
396                        {
397                            pin_cpus.push(i.base10_parse()?);
398                        }
399                    }
400                }
401            }
402        }
403        Ok(IoInitArgs {
404            workers,
405            buffer_pool_size,
406            chunk_size,
407            pin_cpus,
408            ring_depth,
409        })
410    }
411}
412
413/// Configures and starts whichever native `io` backend is active
414/// (io_uring on Linux, IOCP on Windows, kqueue/mio elsewhere, or the
415/// `tokio` wrapper — see `dtact_util::io::init_runtime`) before the
416/// wrapped `main`/entry point runs. Takes the same five knobs as
417/// [`fs_init`]/[`process_init`] (`workers`, `buffer_pool_size`,
418/// `chunk_size`, `pin_cpus`, `ring_depth`), the "power user" counterpart
419/// to the plain `dtact_util::io::init(workers)` convenience function.
420#[proc_macro_attribute]
421pub fn dtact_io_init(args: TokenStream, item: TokenStream) -> TokenStream {
422    let args = parse_macro_input!(args as IoInitArgs);
423    let input = parse_macro_input!(item as ItemFn);
424
425    let workers = args.workers;
426    let buffer_pool_size = args.buffer_pool_size;
427    let chunk_size = args.chunk_size;
428    let ring_depth = args.ring_depth;
429    let pin_cpus = &args.pin_cpus;
430
431    let attrs = &input.attrs;
432    let vis = &input.vis;
433    let sig = &input.sig;
434    let block = &input.block;
435
436    let expanded = quote! {
437        #(#attrs)* #vis #sig {
438            dtact_util::init_runtime(
439                #workers,
440                #ring_depth,
441                #buffer_pool_size,
442                #chunk_size,
443                &[#(#pin_cpus),*],
444            );
445            #block
446        }
447    };
448
449    TokenStream::from(expanded)
450}
451
452/// Sibling to [`dtact_io_init`] for the `fs` module: configures and starts
453/// whichever native fs backend is active (io_uring slot pool on Linux,
454/// IOCP slot pool on Windows, thread pool elsewhere — see
455/// `dtact_util::fs::init_fs`) before the wrapped `main`/entry point runs.
456/// Takes the same five knobs as `dtact_io_init` (`workers`,
457/// `buffer_pool_size`, `chunk_size`, `pin_cpus`, `ring_depth`); `ring_depth`
458/// is what actually sizes the preallocated op-slot pool for fs ops.
459#[proc_macro_attribute]
460pub fn fs_init(args: TokenStream, item: TokenStream) -> TokenStream {
461    let args = parse_macro_input!(args as IoInitArgs);
462    let input = parse_macro_input!(item as ItemFn);
463
464    let workers = args.workers;
465    let buffer_pool_size = args.buffer_pool_size;
466    let chunk_size = args.chunk_size;
467    let ring_depth = args.ring_depth;
468    let pin_cpus = &args.pin_cpus;
469
470    let attrs = &input.attrs;
471    let vis = &input.vis;
472    let sig = &input.sig;
473    let block = &input.block;
474
475    let expanded = quote! {
476        #(#attrs)* #vis #sig {
477            dtact_util::fs::init_fs(
478                #workers,
479                #ring_depth,
480                #buffer_pool_size,
481                #chunk_size,
482                &[#(#pin_cpus),*],
483            );
484            #block
485        }
486    };
487
488    TokenStream::from(expanded)
489}
490
491/// Sibling to [`fs_init`] for the `process` module: starts the
492/// dtact-process blocking-thread pool before the wrapped `main`/entry
493/// point runs. Same five knobs for call-site parity with `fs_init`/
494/// `dtact_io_init`; only `workers` is actually consulted by
495/// `dtact_util::process::init_process` today (see that function's doc
496/// for why the ring/buffer-pool knobs don't apply to a thread-pool-
497/// bridged backend).
498#[proc_macro_attribute]
499pub fn process_init(args: TokenStream, item: TokenStream) -> TokenStream {
500    let args = parse_macro_input!(args as IoInitArgs);
501    let input = parse_macro_input!(item as ItemFn);
502
503    let workers = args.workers;
504    let buffer_pool_size = args.buffer_pool_size;
505    let chunk_size = args.chunk_size;
506    let ring_depth = args.ring_depth;
507    let pin_cpus = &args.pin_cpus;
508
509    let attrs = &input.attrs;
510    let vis = &input.vis;
511    let sig = &input.sig;
512    let block = &input.block;
513
514    let expanded = quote! {
515        #(#attrs)* #vis #sig {
516            dtact_util::process::init_process(
517                #workers,
518                #ring_depth,
519                #buffer_pool_size,
520                #chunk_size,
521                &[#(#pin_cpus),*],
522            );
523            #block
524        }
525    };
526
527    TokenStream::from(expanded)
528}