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 uninitialized storage for all per-CPU static
12//! variables in the `.percpu.template.storage` input section. A separate initializer table describes
13//! how to construct each value at its final runtime address after image
14//! relocation. This is required for Rust values whose bytes cannot legally be
15//! duplicated into another allocation.
16//!
17//! The address of a per-CPU static variable on a given CPU can be calculated by adding the offset of the variable
18//! (relative to the section base) to the base address of the per-CPU data area on the CPU.
19//!
20//! ### How to access the per-CPU data
21//!
22//! To access a per-CPU static variable on a given CPU, three values are needed:
23//!
24//! - The runtime base of the current CPU's data area,
25//!   - which is read through the architecture capability owned by `cpu-local`.
26//! - The offset of the per-CPU static variable relative to the per-CPU data area base,
27//!   - calculated by ordinary Rust integer arithmetic from the linked template base.
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 `MaybeUninit` static variable `__PERCPU_X` in `.percpu.template.storage` that
37//!   reserves the per-CPU storage. Primitive values use their matching atomic representation so
38//!   hard-IRQ re-entry does not make safe reads and writes data-racy; objects
39//!   retain `T` directly.
40//!
41//!   This variable is placed in the `.percpu.template` section. All attributes of the original static variable, as well as the
42//!   initialization expression, are preserved. The expression is retained as
43//!   a Rust `const` and instantiated independently in every runtime CPU area.
44//!
45//!   This variable is never, and should never be, accessed directly. To access the per-CPU data, the offset of the
46//!   variable is, and should be, used.
47//!
48//! - A typed initializer registration in `.percpu.init`. Its descriptor
49//!   thunk is consumed only after the final image relocation and resolves the
50//!   storage symbol to a checked template-relative scalar offset. Rust cannot
51//!   encode this subtraction directly in a static integer: separate statics
52//!   are distinct const-eval allocations, even when the linker later places
53//!   them in one output section.
54//!
55//! - A zero-sized descriptor type `X_WRAPPER` that selects the appropriate
56//!   object or primitive access surface from `ax-percpu`.
57//!
58//! - A static variable `X` of type `X_WRAPPER` that is used to access the per-CPU data.
59//!
60//!   This variable is always generated with the same visibility and attributes as the original static variable.
61
62use proc_macro::TokenStream;
63use proc_macro2::Span;
64use quote::{format_ident, quote};
65use syn::{Attribute, Error, ItemStatic};
66
67mod address;
68
69fn compiler_error(err: Error) -> TokenStream {
70    err.to_compile_error().into()
71}
72
73fn def_percpu_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
74    if !attr.is_empty() {
75        return compiler_error(Error::new(
76            Span::call_site(),
77            "expect an empty attribute: `#[def_percpu]`",
78        ));
79    }
80
81    let ast = syn::parse_macro_input!(item as ItemStatic);
82
83    let attrs = &ast.attrs;
84    let vis = &ast.vis;
85    let name = &ast.ident;
86    let ty = &ast.ty;
87    let init_expr = &ast.expr;
88
89    let inner_symbol_name = &format_ident!("__PERCPU_{}", name);
90    let alignment_descriptor_name = &format_ident!("__PERCPU_{}_ALIGNMENT", name);
91    let initial_value_name = &format_ident!("__PERCPU_{}_INITIAL_VALUE", name);
92    let initializer_name = &format_ident!("__PERCPU_{}_INITIALIZE", name);
93    let descriptor_name = &format_ident!("__PERCPU_{}_DESCRIBE", name);
94    let registration_name = &format_ident!("__PERCPU_{}_REGISTRATION", name);
95    let symbol_provider_name = &format_ident!("__PERCPU_{}_SYMBOL", name);
96    let struct_name = &format_ident!("{}_WRAPPER", name);
97    let conditional_attrs = conditional_attributes(attrs);
98
99    let ty_str = quote!(#ty).to_string();
100    let is_primitive_int = ["bool", "u8", "u16", "u32", "u64", "usize"].contains(&ty_str.as_str());
101
102    let (access_trait, storage_type, initial_value) = if is_primitive_int {
103        let atomic_ty = match ty_str.as_str() {
104            "bool" => quote!(::core::sync::atomic::AtomicBool),
105            "u8" => quote!(::core::sync::atomic::AtomicU8),
106            "u16" => quote!(::core::sync::atomic::AtomicU16),
107            "u32" => quote!(::core::sync::atomic::AtomicU32),
108            "u64" => quote!(::core::sync::atomic::AtomicU64),
109            "usize" => quote!(::core::sync::atomic::AtomicUsize),
110            _ => unreachable!("primitive type classification must stay exhaustive"),
111        };
112        (
113            quote!(ax_percpu::__priv::PerCpuPrimitiveSymbol),
114            atomic_ty.clone(),
115            quote!(#atomic_ty::new(#init_expr)),
116        )
117    } else {
118        (
119            quote!(ax_percpu::__priv::PerCpuObjectSymbol),
120            quote!(#ty),
121            quote!(#init_expr),
122        )
123    };
124
125    let storage_definition = quote! {
126        static mut #inner_symbol_name: ::core::mem::MaybeUninit<#storage_type> =
127            ::core::mem::MaybeUninit::uninit();
128    };
129
130    let offset = address::gen_offset(inner_symbol_name);
131    let current_ptr_pinned =
132        address::gen_current_ptr_pinned(inner_symbol_name, &format_ident!("pin"), ty);
133    let remote_ptr = address::gen_remote_ptr(inner_symbol_name, &format_ident!("area"), ty);
134    let initialization = quote! {
135            #(#conditional_attrs)*
136            #[allow(non_upper_case_globals)]
137            const #initial_value_name: #storage_type = #initial_value;
138
139            #(#conditional_attrs)*
140            #[allow(non_snake_case)]
141            unsafe extern "C" fn #initializer_name(destination: *mut u8) {
142                let destination = destination.cast::<::core::mem::MaybeUninit<#storage_type>>();
143                // SAFETY: ax-percpu validates this record's offset, size, and
144                // alignment against every exclusively owned runtime area before
145                // invoking the typed initializer exactly once for that area.
146                unsafe {
147                    destination.write(::core::mem::MaybeUninit::new(#initial_value_name));
148                }
149            }
150
151            #(#conditional_attrs)*
152            #[allow(non_snake_case)]
153            unsafe extern "C" fn #descriptor_name() -> ax_percpu::__priv::PerCpuInitDescriptor {
154                let storage_address =
155                    ::core::ptr::addr_of!(#inner_symbol_name).cast::<u8>() as usize;
156                // SAFETY: every scalar is derived from this exact generated
157                // MaybeUninit<Storage> object and its matching typed writer.
158                unsafe {
159                    ax_percpu::__priv::PerCpuInitDescriptor::new(
160                        storage_address,
161                        ::core::mem::size_of::<#storage_type>(),
162                        ::core::mem::align_of::<#storage_type>(),
163                        #initializer_name,
164                    )
165                }
166            }
167
168            #(#conditional_attrs)*
169            #[cfg_attr(
170                not(target_os = "macos"),
171                unsafe(link_section = ".percpu.init")
172            )]
173            #[used]
174            static #registration_name: ax_percpu::__priv::PerCpuInitRegistration =
175                // SAFETY: the generated descriptor thunk is immutable,
176                // deterministic, final-image resident, and always describes
177                // the same generated storage and initializer.
178                unsafe { ax_percpu::__priv::PerCpuInitRegistration::new(#descriptor_name) };
179    };
180
181    quote! {
182        #[cfg_attr(
183            not(target_os = "macos"),
184            unsafe(link_section = ".percpu.align")
185        )]
186        #[used]
187        static #alignment_descriptor_name: usize = ::core::mem::align_of::<#storage_type>();
188
189        #[cfg_attr(
190            not(target_os = "macos"),
191            unsafe(link_section = ".percpu.template.storage")
192        )]
193        #(#attrs)*
194        #storage_definition
195
196        #initialization
197
198        #[doc(hidden)]
199        #[allow(non_camel_case_types)]
200        #(#conditional_attrs)*
201        #vis struct #symbol_provider_name;
202
203        // SAFETY: every pointer is derived from this one typed template symbol
204        // and the registered CPU-area layout.
205        #(#conditional_attrs)*
206        unsafe impl ax_percpu::__priv::PerCpuSymbol<#ty> for #symbol_provider_name {
207            #[inline]
208            fn offset() -> usize {
209                #offset
210            }
211
212            #[inline]
213            fn current_ptr(pin: &ax_percpu::CpuPin<'_>) -> ::core::ptr::NonNull<#ty> {
214                #current_ptr_pinned
215            }
216
217            #[inline]
218            fn remote_ptr(area: ax_percpu::PerCpuArea) -> ::core::ptr::NonNull<#ty> {
219                #remote_ptr
220            }
221        }
222
223        // SAFETY: the marker selects the access surface matching the exact
224        // storage representation emitted above.
225        #(#conditional_attrs)*
226        unsafe impl #access_trait<#ty> for #symbol_provider_name {}
227
228        #[doc = concat!("Wrapper type for the per-CPU data [`", stringify!(#name), "`]")]
229        #[allow(non_camel_case_types)]
230        #(#conditional_attrs)*
231        #vis type #struct_name = ax_percpu::PerCpu<#ty, #symbol_provider_name>;
232
233        #(#attrs)*
234        #vis static #name: #struct_name = ax_percpu::PerCpu::new();
235    }
236    .into()
237}
238
239fn conditional_attributes(attrs: &[Attribute]) -> Vec<&Attribute> {
240    attrs
241        .iter()
242        .filter(|attribute| {
243            attribute.path().is_ident("cfg") || attribute.path().is_ident("cfg_attr")
244        })
245        .collect()
246}
247/// Defines a per-CPU static variable.
248///
249/// It should be used on a `static` variable definition.
250///
251/// See the documentation of the [ax-percpu](https://docs.rs/ax-percpu) crate for more details.
252#[proc_macro_attribute]
253pub fn def_percpu(attr: TokenStream, item: TokenStream) -> TokenStream {
254    def_percpu_impl(attr, item)
255}