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}