Skip to main content

ax_percpu_macros/
lib.rs

1//! Macros to define and access a per-CPU data structure.
2//!
3//! **DO NOT** use this crate directly. Use the [ax-percpu] crate instead.
4//!
5//! [ax-percpu]: https://docs.rs/ax-percpu
6//!
7//! ## Implementation details of the `def_percpu` macro
8//!
9//! ### Core idea
10//!
11//! The core idea is to collect all per-CPU static variables to a single section (i.e., `.percpu`), then allocate a
12//! per-CPU data area, with the size equals to the size of the `.percpu` section, for each CPU (it can be done
13//! statically or dynamically), then copy the `.percpu` section to each per-CPU data area during initialization.
14//!
15//! The address of a per-CPU static variable on a given CPU can be calculated by adding the offset of the variable
16//! (relative to the section base) to the base address of the per-CPU data area on the CPU.
17//!
18//! ### How to access the per-CPU data
19//!
20//! To access a per-CPU static variable on a given CPU, three values are needed:
21//!
22//! - The base address of the per-CPU data area on the CPU,
23//!   - which can be calculated by the base address of the whole per-CPU data area and the CPU ID,
24//!   - and then stored in a register, like `TPIDR_EL1`/`TPIDR_EL2` on AArch64, or `gs` on x86_64.
25//! - The offset of the per-CPU static variable relative to the per-CPU data area base,
26//!   - which can be calculated by assembly notations, like `offset symbol` on x86_64, or `#:abs_g0_nc:symbol` on
27//!     AArch64, or `%hi(symbol)` and `%lo(symbol)` on RISC-V.
28//! - The size of the per-CPU static variable,
29//!   - which we actually do not need to know, just give the right type to rust compiler.
30//!
31//! ### Generated code
32//!
33//! For each static variable `X` with type `T` that is defined with the `def_percpu` macro, the following items are
34//! generated:
35//!
36//! - A static variable `__PERCPU_X` with type `T` that stores the per-CPU data.
37//!
38//!   This variable is placed in the `.percpu` section. All attributes of the original static variable, as well as the
39//!   initialization expression, are preserved.
40//!
41//!   This variable is never, and should never be, accessed directly. To access the per-CPU data, the offset of the
42//!   variable is, and should be, used.
43//!
44//! - A zero-sized wrapper struct `X_WRAPPER` that is used to access the per-CPU data.
45//!
46//!   Some methods are generated in this struct to access the per-CPU data. For primitive integer types, extra methods
47//!   are generated to accelerate the access.
48//!
49//! - A static variable `X` of type `X_WRAPPER` that is used to access the per-CPU data.
50//!
51//!   This variable is always generated with the same visibility and attributes as the original static variable.
52
53use proc_macro::TokenStream;
54use proc_macro2::Span;
55use quote::{format_ident, quote};
56use syn::{Error, ItemStatic};
57
58#[cfg_attr(feature = "sp-naive", path = "naive.rs")]
59mod arch;
60
61fn compiler_error(err: Error) -> TokenStream {
62    err.to_compile_error().into()
63}
64
65#[cfg(any(feature = "sp-naive", not(feature = "custom-base")))]
66fn def_percpu_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
67    if !attr.is_empty() {
68        return compiler_error(Error::new(
69            Span::call_site(),
70            "expect an empty attribute: `#[def_percpu]`",
71        ));
72    }
73
74    let ast = syn::parse_macro_input!(item as ItemStatic);
75
76    let attrs = &ast.attrs;
77    let vis = &ast.vis;
78    let name = &ast.ident;
79    let ty = &ast.ty;
80    let init_expr = &ast.expr;
81
82    let inner_symbol_name = &format_ident!("__PERCPU_{}", name);
83    let struct_name = &format_ident!("{}_WRAPPER", name);
84
85    let ty_str = quote!(#ty).to_string();
86    let is_primitive_int = ["bool", "u8", "u16", "u32", "u64", "usize"].contains(&ty_str.as_str());
87
88    let no_preempt_guard = if cfg!(feature = "preempt") {
89        quote! { let _guard = ax_percpu::__priv::NoPreemptGuard::new(); }
90    } else {
91        quote! {}
92    };
93
94    // Do not generate `fn read_current()`, `fn write_current()`, etc for non primitive types.
95    let read_write_methods = if is_primitive_int {
96        let read_current_raw = arch::gen_read_current_raw(inner_symbol_name, ty);
97        let write_current_raw =
98            arch::gen_write_current_raw(inner_symbol_name, &format_ident!("val"), ty);
99
100        quote! {
101            /// Returns the value of the per-CPU static variable on the current CPU.
102            ///
103            /// # Safety
104            ///
105            /// Caller must ensure that preemption is disabled on the current CPU.
106            #[inline]
107            pub unsafe fn read_current_raw(&self) -> #ty {
108                unsafe { #read_current_raw }
109            }
110
111            /// Set the value of the per-CPU static variable on the current CPU.
112            ///
113            /// # Safety
114            ///
115            /// Caller must ensure that preemption is disabled on the current CPU.
116            #[inline]
117            pub unsafe fn write_current_raw(&self, val: #ty) {
118                unsafe { #write_current_raw }
119            }
120
121            /// Returns the value of the per-CPU static variable on the current CPU. Preemption will be disabled during
122            /// the call.
123            pub fn read_current(&self) -> #ty {
124                #no_preempt_guard
125                unsafe { self.read_current_raw() }
126            }
127
128            /// Set the value of the per-CPU static variable on the current CPU. Preemption will be disabled during the
129            /// call.
130            pub fn write_current(&self, val: #ty) {
131                #no_preempt_guard
132                unsafe { self.write_current_raw(val) }
133            }
134        }
135
136        // Todo: maybe add `(read|write)_remote(_raw)?` here?
137    } else {
138        quote! {}
139    };
140
141    let symbol_vma = arch::gen_symbol_vma(inner_symbol_name);
142    let offset = arch::gen_offset(inner_symbol_name);
143    let current_ptr = arch::gen_current_ptr(inner_symbol_name, ty);
144    quote! {
145        #[cfg_attr(not(target_os = "macos"), unsafe(link_section = ".percpu"))] // unimplemented on macos
146        #(#attrs)*
147        static mut #inner_symbol_name: #ty = #init_expr;
148
149        #[doc = concat!("Wrapper struct for the per-CPU data [`", stringify!(#name), "`]")]
150        #[allow(non_camel_case_types)]
151        #vis struct #struct_name {}
152
153        #(#attrs)*
154        #vis static #name: #struct_name = #struct_name {};
155
156        impl #struct_name {
157            /// Returns the virtual memory address of this per-CPU static
158            /// variable in the `.percpu` section.
159            ///
160            /// It's same across all CPUs, and also the same as `offset` if the
161            /// "non-zero-vma" feature is not enabled.
162            #[inline]
163            pub fn symbol_vma(&self) -> usize {
164                #symbol_vma
165            }
166
167            /// Returns the offset relative to the per-CPU data area base.
168            #[inline]
169            pub fn offset(&self) -> usize {
170                #offset
171            }
172
173            /// Returns the raw pointer of this per-CPU static variable on the current CPU.
174            ///
175            /// # Safety
176            ///
177            /// Caller must ensure that preemption is disabled on the current CPU.
178            #[inline]
179            pub unsafe fn current_ptr(&self) -> *const #ty {
180                unsafe { #current_ptr }
181            }
182
183            /// Returns the reference of the per-CPU static variable on the current CPU.
184            ///
185            /// # Safety
186            ///
187            /// Caller must ensure that preemption is disabled on the current CPU.
188            #[inline]
189            pub unsafe fn current_ref_raw(&self) -> &#ty {
190                unsafe { &*self.current_ptr() }
191            }
192
193            /// Returns the mutable reference of the per-CPU static variable on the current CPU.
194            ///
195            /// # Safety
196            ///
197            /// Caller must ensure that preemption is disabled on the current CPU.
198            #[inline]
199            #[allow(clippy::mut_from_ref)]
200            pub unsafe fn current_ref_mut_raw(&self) -> &mut #ty {
201                unsafe { &mut *(self.current_ptr() as *mut #ty) }
202            }
203
204            /// Manipulate the per-CPU data on the current CPU in the given closure.
205            /// Preemption will be disabled during the call.
206            pub fn with_current<F, T>(&self, f: F) -> T
207            where
208                F: FnOnce(&mut #ty) -> T,
209            {
210                #no_preempt_guard
211                f(unsafe { self.current_ref_mut_raw() })
212            }
213
214            /// Returns the raw pointer of this per-CPU static variable on the given CPU.
215            ///
216            /// # Safety
217            ///
218            /// Caller must ensure that
219            /// - the CPU ID is valid, and
220            /// - data races will not happen.
221            #[inline]
222            pub unsafe fn remote_ptr(&self, cpu_id: usize) -> *const #ty {
223                let base = ax_percpu::percpu_area_base(cpu_id);
224                let offset = #offset;
225                (base + offset) as *const #ty
226            }
227
228            /// Returns the reference of the per-CPU static variable on the given CPU.
229            ///
230            /// # Safety
231            ///
232            /// Caller must ensure that
233            /// - the CPU ID is valid, and
234            /// - data races will not happen.
235            #[inline]
236            pub unsafe fn remote_ref_raw(&self, cpu_id: usize) -> &#ty {
237                unsafe { &*self.remote_ptr(cpu_id) }
238            }
239
240            /// Returns the mutable reference of the per-CPU static variable on the given CPU.
241            ///
242            /// # Safety
243            ///
244            /// Caller must ensure that
245            /// - the CPU ID is valid, and
246            /// - data races will not happen.
247            #[inline]
248            #[allow(clippy::mut_from_ref)]
249            pub unsafe fn remote_ref_mut_raw(&self, cpu_id: usize) -> &mut #ty {
250                unsafe { &mut *(self.remote_ptr(cpu_id) as *mut #ty) }
251            }
252
253            #read_write_methods
254        }
255    }
256    .into()
257}
258/// Defines a per-CPU static variable.
259///
260/// It should be used on a `static` variable definition.
261///
262/// See the documentation of the [ax-percpu](https://docs.rs/ax-percpu) crate for more details.
263#[proc_macro_attribute]
264pub fn def_percpu(attr: TokenStream, item: TokenStream) -> TokenStream {
265    def_percpu_impl(attr, item)
266}
267
268#[doc(hidden)]
269#[cfg(not(feature = "sp-naive"))]
270#[proc_macro]
271pub fn percpu_symbol_vma(item: TokenStream) -> TokenStream {
272    let symbol = &format_ident!("{}", item.to_string());
273    let offset = arch::gen_symbol_vma(symbol);
274    quote!({ #offset }).into()
275}
276
277#[cfg(all(feature = "custom-base", not(feature = "sp-naive")))]
278fn def_percpu_impl(attr: TokenStream, input: TokenStream) -> TokenStream {
279    if !attr.is_empty() {
280        return compiler_error(Error::new(
281            Span::call_site(),
282            "expect an empty attribute: `#[def_percpu]`",
283        ));
284    }
285
286    use syn::parse_macro_input;
287
288    let ItemStatic {
289        attrs,
290        vis,
291        static_token,
292        mutability,
293        ident,
294        ty,
295        expr,
296        ..
297    } = parse_macro_input!(input as ItemStatic);
298
299    quote! {
300        #[unsafe(link_section = ".percpu")]
301        #(#attrs)*
302        #vis #static_token #mutability #ident : ax_percpu::PerCpuData<#ty> = ax_percpu::PerCpuData::new(#expr);
303    }
304    .into()
305}