ostd_macros/lib.rs
1// SPDX-License-Identifier: MPL-2.0
2
3#![feature(proc_macro_span)]
4
5use proc_macro::TokenStream;
6use quote::quote;
7use rand::{Rng, distr::Alphanumeric};
8use syn::{Expr, Ident, ItemFn, parse_macro_input};
9
10/// A macro attribute to mark the kernel entry point.
11///
12/// # Example
13///
14/// ```ignore
15/// #![no_std]
16///
17/// use ostd::prelude::*;
18///
19/// #[ostd::main]
20/// pub fn main() {
21/// println!("hello world");
22/// }
23/// ```
24#[proc_macro_attribute]
25pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
26 let main_fn = parse_macro_input!(item as ItemFn);
27 let main_fn_name = &main_fn.sig.ident;
28
29 quote!(
30 #[cfg(not(ktest))]
31 // SAFETY: The name does not collide with other symbols.
32 #[unsafe(no_mangle)]
33 extern "Rust" fn __ostd_main() -> ! {
34 let _: () = #main_fn_name();
35
36 ostd::task::Task::yield_now();
37 unreachable!("`yield_now` in the boot context should not return");
38 }
39
40 #[expect(unused)]
41 #main_fn
42 )
43 .into()
44}
45
46/// A macro attribute for the unit test kernel entry point.
47///
48/// This macro is used for internal OSDK implementation. Do not use it
49/// directly.
50#[proc_macro_attribute]
51pub fn test_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
52 let main_fn = parse_macro_input!(item as ItemFn);
53 let main_fn_name = &main_fn.sig.ident;
54
55 quote!(
56 // SAFETY: The name does not collide with other symbols.
57 #[unsafe(no_mangle)]
58 extern "Rust" fn __ostd_main() -> ! {
59 let _: () = #main_fn_name();
60
61 ostd::task::Task::yield_now();
62 unreachable!("`yield_now` in the boot context should not return");
63 }
64
65 #main_fn
66 )
67 .into()
68}
69
70/// A macro attribute for the global frame allocator.
71///
72/// The attributed static variable will be used to provide frame allocation
73/// for the kernel.
74///
75/// # Example
76///
77/// ```ignore
78/// use core::alloc::Layout;
79/// use ostd::{mm::{frame::GlobalFrameAllocator, Paddr}, global_frame_allocator};
80///
81/// // Of course it won't work because all allocations will fail.
82/// // It's just an example.
83/// #[global_frame_allocator]
84/// static ALLOCATOR: MyFrameAllocator = MyFrameAllocator;
85///
86/// struct MyFrameAllocator;
87///
88/// impl GlobalFrameAllocator for MyFrameAllocator {
89/// fn alloc(&self, _layout: Layout) -> Option<Paddr> { None }
90/// fn dealloc(&self, _paddr: Paddr, _size: usize) {}
91/// }
92/// ```
93#[proc_macro_attribute]
94pub fn global_frame_allocator(_attr: TokenStream, item: TokenStream) -> TokenStream {
95 // Make a `static __GLOBAL_FRAME_ALLOCATOR_REF: &'static dyn GlobalFrameAllocator`
96 // That points to the annotated static variable.
97 let item = parse_macro_input!(item as syn::ItemStatic);
98 let static_name = &item.ident;
99
100 quote!(
101 // SAFETY: The name does not collide with other symbols.
102 #[unsafe(no_mangle)]
103 static __GLOBAL_FRAME_ALLOCATOR_REF: &'static dyn ostd::mm::frame::GlobalFrameAllocator = &#static_name;
104 #item
105 )
106 .into()
107}
108
109/// A macro attribute to register the global heap allocator.
110///
111/// The attributed static variable will be used to provide heap allocation
112/// for the kernel.
113///
114/// This attribute is not to be confused with Rust's built-in
115/// [`global_allocator`] attribute, which applies to a static variable
116/// implementing the unsafe `GlobalAlloc` trait. In contrast, the
117/// [`macro@global_heap_allocator`] attribute does not require the heap allocator to
118/// implement an unsafe trait. [`macro@global_heap_allocator`] eventually relies on
119/// [`global_allocator`] to customize Rust's heap allocator.
120///
121/// # Example
122///
123/// ```ignore
124/// use core::alloc::{AllocError, Layout};
125/// use ostd::{mm::heap::{GlobalHeapAllocator, HeapSlot}, global_heap_allocator};
126///
127/// // Of course it won't work and all allocations will fail.
128/// // It's just an example.
129/// #[global_heap_allocator]
130/// static ALLOCATOR: MyHeapAllocator = MyHeapAllocator;
131///
132/// struct MyHeapAllocator;
133///
134/// impl GlobalHeapAllocator for MyHeapAllocator {
135/// fn alloc(&self, _layout: Layout) -> Result<HeapSlot, AllocError> { None }
136/// fn dealloc(&self, _slot: HeapSlot) -> Result<(), AllocError> {}
137/// }
138/// ```
139#[proc_macro_attribute]
140pub fn global_heap_allocator(_attr: TokenStream, item: TokenStream) -> TokenStream {
141 // Make a `static __GLOBAL_HEAP_ALLOCATOR_REF: &'static dyn GlobalHeapAllocator`
142 // That points to the annotated static variable.
143 let item = parse_macro_input!(item as syn::ItemStatic);
144 let static_name = &item.ident;
145
146 quote!(
147 // SAFETY: The name does not collide with other symbols.
148 #[unsafe(no_mangle)]
149 static __GLOBAL_HEAP_ALLOCATOR_REF: &'static dyn ostd::mm::heap::GlobalHeapAllocator = &#static_name;
150 #item
151 )
152 .into()
153}
154
155/// A macro attribute to map allocation layouts to slot sizes and types.
156///
157/// In OSTD, both slab slots and large slots are used to serve heap allocations.
158/// Slab slots must come from slabs of fixed sizes, while large slots can be
159/// allocated by frame allocation, with sizes being multiples of pages.
160/// OSTD must know the user's decision on the size and type of a slot to serve
161/// an allocation with a given layout.
162///
163/// This macro should be used to annotate a function that maps a layout to the
164/// slot size and the type. The function should return `None` if the layout is
165/// not supported.
166///
167/// The annotated function should be idempotent, meaning the result should be the
168/// same for the same layout. OSDK enforces this by only allowing the function
169/// to be `const`.
170#[proc_macro_attribute]
171pub fn global_heap_allocator_slot_map(_attr: TokenStream, item: TokenStream) -> TokenStream {
172 // Rewrite the input `const fn __any_name__(layout: Layout) -> Option<SlotInfo> { ... }` to
173 // `const extern "Rust" fn __GLOBAL_HEAP_SLOT_INFO_FROM_LAYOUT(layout: Layout) -> Option<SlotInfo> { ... }`.
174 // Reject if the input is not a `const fn`.
175 let item = parse_macro_input!(item as syn::ItemFn);
176 assert!(
177 item.sig.constness.is_some(),
178 "the annotated function must be `const`"
179 );
180
181 quote!(
182 /// SAFETY: The name does not collide with other symbols.
183 #[unsafe(export_name = "__GLOBAL_HEAP_SLOT_INFO_FROM_LAYOUT")]
184 #item
185 )
186 .into()
187}
188
189/// A macro attribute for the panic handler.
190///
191/// The attributed function will be used to override OSTD's default
192/// implementation of Rust's `#[panic_handler]`. The function takes a single
193/// parameter of type `&core::panic::PanicInfo` and does not return.
194#[proc_macro_attribute]
195pub fn panic_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
196 let handler_fn = parse_macro_input!(item as ItemFn);
197 let handler_fn_name = &handler_fn.sig.ident;
198
199 quote!(
200 #[cfg(not(ktest))]
201 // SAFETY: The name does not collide with other symbols.
202 #[unsafe(no_mangle)]
203 extern "Rust" fn __ostd_panic_handler(info: &core::panic::PanicInfo) -> ! {
204 #handler_fn_name(info);
205 }
206
207 #[expect(unused)]
208 #handler_fn
209 )
210 .into()
211}
212
213/// A macro attribute for the panic handler.
214///
215/// This macro is used for internal OSDK implementation. Do not use it
216/// directly.
217#[proc_macro_attribute]
218pub fn test_panic_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
219 let handler_fn = parse_macro_input!(item as ItemFn);
220 let handler_fn_name = &handler_fn.sig.ident;
221
222 quote!(
223 // SAFETY: The name does not collide with other symbols.
224 #[unsafe(no_mangle)]
225 extern "Rust" fn __ostd_panic_handler(info: &core::panic::PanicInfo) -> ! {
226 #handler_fn_name(info);
227 }
228
229 #handler_fn
230 )
231 .into()
232}
233
234/// The test attribute macro to mark a test function.
235///
236/// # Example
237///
238/// For crates other than ostd,
239/// this macro can be used in the following form.
240///
241/// ```ignore
242/// use ostd::prelude::*;
243///
244/// #[ktest]
245/// fn test_fn() {
246/// assert_eq!(1 + 1, 2);
247/// }
248/// ```
249///
250/// For ostd crate itself,
251/// this macro can be used in the form
252///
253/// ```ignore
254/// use crate::prelude::*;
255///
256/// #[ktest]
257/// fn test_fn() {
258/// assert_eq!(1 + 1, 2);
259/// }
260/// ```
261#[proc_macro_attribute]
262pub fn ktest(_attr: TokenStream, item: TokenStream) -> TokenStream {
263 // Assuming that the item has type `fn() -> ()`, otherwise panics.
264 let input = parse_macro_input!(item as ItemFn);
265 assert!(
266 input.sig.inputs.is_empty(),
267 "ostd::test function should have no arguments"
268 );
269 assert!(
270 matches!(input.sig.output, syn::ReturnType::Default),
271 "ostd::test function should return `()`"
272 );
273
274 // Generate a random identifier to avoid name conflicts.
275 let fn_id: String = rand::rng()
276 .sample_iter(&Alphanumeric)
277 .take(8)
278 .map(char::from)
279 .collect();
280
281 let fn_name = &input.sig.ident;
282 let fn_ktest_item_name = Ident::new(
283 &format!("{}_ktest_item_{}", &input.sig.ident, &fn_id),
284 proc_macro2::Span::call_site(),
285 );
286
287 let is_should_panic_attr = |attr: &&syn::Attribute| {
288 attr.path()
289 .segments
290 .iter()
291 .any(|segment| segment.ident == "should_panic")
292 };
293 let mut attr_iter = input.attrs.iter();
294 let should_panic = attr_iter.find(is_should_panic_attr);
295 let (should_panic, expectation) = match should_panic {
296 Some(attr) => {
297 assert!(
298 !attr_iter.any(|attr: &syn::Attribute| is_should_panic_attr(&attr)),
299 "multiple `should_panic` attributes"
300 );
301 match &attr.meta {
302 syn::Meta::List(l) => {
303 let arg_err_message = "`should_panic` attribute should only have zero or one `expected` argument, with the format of `expected = \"<panic message>\"`";
304 let expected_assign =
305 syn::parse2::<syn::ExprAssign>(l.tokens.clone()).expect(arg_err_message);
306 let Expr::Lit(s) = *expected_assign.right else {
307 panic!("{}", arg_err_message);
308 };
309 let syn::Lit::Str(expectation) = s.lit else {
310 panic!("{}", arg_err_message);
311 };
312 (true, Some(expectation))
313 }
314 _ => (true, None),
315 }
316 }
317 None => (false, None),
318 };
319 let expectation_tokens = if let Some(s) = expectation {
320 quote! {
321 Some(#s)
322 }
323 } else {
324 quote! {
325 None
326 }
327 };
328
329 let package_name = std::env::var("CARGO_PKG_NAME").unwrap();
330 let span = proc_macro2::Span::call_site();
331 let line = span.start().line;
332 let col = span.start().column;
333
334 let register_ktest_item = if package_name.as_str() == "ostd" {
335 quote! {
336 #[cfg(ktest)]
337 #[used]
338 // SAFETY: This is properly handled in the linker script.
339 #[unsafe(link_section = ".ktest_array")]
340 static #fn_ktest_item_name: ostd_test::KtestItem = ostd_test::KtestItem::new(
341 #fn_name,
342 (#should_panic, #expectation_tokens),
343 ostd_test::KtestItemInfo {
344 module_path: module_path!(),
345 fn_name: stringify!(#fn_name),
346 package: #package_name,
347 source: file!(),
348 line: #line,
349 col: #col,
350 },
351 );
352 }
353 } else {
354 quote! {
355 #[cfg(ktest)]
356 #[used]
357 // SAFETY: This is properly handled in the linker script.
358 #[unsafe(link_section = ".ktest_array")]
359 static #fn_ktest_item_name: ostd::ktest::KtestItem = ostd::ktest::KtestItem::new(
360 #fn_name,
361 (#should_panic, #expectation_tokens),
362 ostd::ktest::KtestItemInfo {
363 module_path: module_path!(),
364 fn_name: stringify!(#fn_name),
365 package: #package_name,
366 source: file!(),
367 line: #line,
368 col: #col,
369 },
370 );
371 }
372 };
373
374 let output = quote! {
375 #input
376
377 #register_ktest_item
378 };
379
380 TokenStream::from(output)
381}