use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{Attribute, Error, ItemStatic};
mod address;
fn compiler_error(err: Error) -> TokenStream {
err.to_compile_error().into()
}
fn def_percpu_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
if !attr.is_empty() {
return compiler_error(Error::new(
Span::call_site(),
"expect an empty attribute: `#[def_percpu]`",
));
}
let ast = syn::parse_macro_input!(item as ItemStatic);
let attrs = &ast.attrs;
let vis = &ast.vis;
let name = &ast.ident;
let ty = &ast.ty;
let init_expr = &ast.expr;
let inner_symbol_name = &format_ident!("__PERCPU_{}", name);
let alignment_descriptor_name = &format_ident!("__PERCPU_{}_ALIGNMENT", name);
let initial_value_name = &format_ident!("__PERCPU_{}_INITIAL_VALUE", name);
let initializer_name = &format_ident!("__PERCPU_{}_INITIALIZE", name);
let descriptor_name = &format_ident!("__PERCPU_{}_DESCRIBE", name);
let registration_name = &format_ident!("__PERCPU_{}_REGISTRATION", name);
let symbol_provider_name = &format_ident!("__PERCPU_{}_SYMBOL", name);
let struct_name = &format_ident!("{}_WRAPPER", name);
let conditional_attrs = conditional_attributes(attrs);
let ty_str = quote!(#ty).to_string();
let is_primitive_int = ["bool", "u8", "u16", "u32", "u64", "usize"].contains(&ty_str.as_str());
let (access_trait, storage_type, initial_value) = if is_primitive_int {
let atomic_ty = match ty_str.as_str() {
"bool" => quote!(::core::sync::atomic::AtomicBool),
"u8" => quote!(::core::sync::atomic::AtomicU8),
"u16" => quote!(::core::sync::atomic::AtomicU16),
"u32" => quote!(::core::sync::atomic::AtomicU32),
"u64" => quote!(::core::sync::atomic::AtomicU64),
"usize" => quote!(::core::sync::atomic::AtomicUsize),
_ => unreachable!("primitive type classification must stay exhaustive"),
};
(
quote!(ax_percpu::__priv::PerCpuPrimitiveSymbol),
atomic_ty.clone(),
quote!(#atomic_ty::new(#init_expr)),
)
} else {
(
quote!(ax_percpu::__priv::PerCpuObjectSymbol),
quote!(#ty),
quote!(#init_expr),
)
};
let storage_definition = quote! {
static mut #inner_symbol_name: ::core::mem::MaybeUninit<#storage_type> =
::core::mem::MaybeUninit::uninit();
};
let offset = address::gen_offset(inner_symbol_name);
let current_ptr_pinned =
address::gen_current_ptr_pinned(inner_symbol_name, &format_ident!("pin"), ty);
let remote_ptr = address::gen_remote_ptr(inner_symbol_name, &format_ident!("area"), ty);
let initialization = quote! {
#(#conditional_attrs)*
#[allow(non_upper_case_globals)]
const #initial_value_name: #storage_type = #initial_value;
#(#conditional_attrs)*
#[allow(non_snake_case)]
unsafe extern "C" fn #initializer_name(destination: *mut u8) {
let destination = destination.cast::<::core::mem::MaybeUninit<#storage_type>>();
unsafe {
destination.write(::core::mem::MaybeUninit::new(#initial_value_name));
}
}
#(#conditional_attrs)*
#[allow(non_snake_case)]
unsafe extern "C" fn #descriptor_name() -> ax_percpu::__priv::PerCpuInitDescriptor {
let storage_address =
::core::ptr::addr_of!(#inner_symbol_name).cast::<u8>() as usize;
unsafe {
ax_percpu::__priv::PerCpuInitDescriptor::new(
storage_address,
::core::mem::size_of::<#storage_type>(),
::core::mem::align_of::<#storage_type>(),
#initializer_name,
)
}
}
#(#conditional_attrs)*
#[cfg_attr(
not(target_os = "macos"),
unsafe(link_section = ".percpu.init")
)]
#[used]
static #registration_name: ax_percpu::__priv::PerCpuInitRegistration =
unsafe { ax_percpu::__priv::PerCpuInitRegistration::new(#descriptor_name) };
};
quote! {
#[cfg_attr(
not(target_os = "macos"),
unsafe(link_section = ".percpu.align")
)]
#[used]
static #alignment_descriptor_name: usize = ::core::mem::align_of::<#storage_type>();
#[cfg_attr(
not(target_os = "macos"),
unsafe(link_section = ".percpu.template.storage")
)]
#(#attrs)*
#storage_definition
#initialization
#[doc(hidden)]
#[allow(non_camel_case_types)]
#(#conditional_attrs)*
#vis struct #symbol_provider_name;
#(#conditional_attrs)*
unsafe impl ax_percpu::__priv::PerCpuSymbol<#ty> for #symbol_provider_name {
#[inline]
fn offset() -> usize {
#offset
}
#[inline]
fn current_ptr(pin: &ax_percpu::CpuPin<'_>) -> ::core::ptr::NonNull<#ty> {
#current_ptr_pinned
}
#[inline]
fn remote_ptr(area: ax_percpu::PerCpuArea) -> ::core::ptr::NonNull<#ty> {
#remote_ptr
}
}
#(#conditional_attrs)*
unsafe impl #access_trait<#ty> for #symbol_provider_name {}
#[doc = concat!("Wrapper type for the per-CPU data [`", stringify!(#name), "`]")]
#[allow(non_camel_case_types)]
#(#conditional_attrs)*
#vis type #struct_name = ax_percpu::PerCpu<#ty, #symbol_provider_name>;
#(#attrs)*
#vis static #name: #struct_name = ax_percpu::PerCpu::new();
}
.into()
}
fn conditional_attributes(attrs: &[Attribute]) -> Vec<&Attribute> {
attrs
.iter()
.filter(|attribute| {
attribute.path().is_ident("cfg") || attribute.path().is_ident("cfg_attr")
})
.collect()
}
#[proc_macro_attribute]
pub fn def_percpu(attr: TokenStream, item: TokenStream) -> TokenStream {
def_percpu_impl(attr, item)
}