use quote::{format_ident, quote};
use syn::{Ident, Type};
fn macos_unimplemented(item: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
quote! {
#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))]
{ #item }
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
{ unimplemented!("per-CPU variables are not supported on x86_64 macOS") }
}
}
pub fn gen_symbol_vma(symbol: &Ident) -> proc_macro2::TokenStream {
let slow_addr = quote! {
unsafe { ::core::ptr::addr_of!(#symbol) as usize }
};
let fast_addr = quote! {
unsafe {
let value: usize;
#[cfg(target_arch = "x86_64")]
::core::arch::asm!(
"mov {0:e}, offset {VAR}", out(reg) value,
VAR = sym #symbol,
);
#[cfg(target_arch = "aarch64")]
::core::arch::asm!(
"movz {0}, #:abs_g0_nc:{VAR}", out(reg) value,
VAR = sym #symbol,
);
#[cfg(target_arch = "arm")]
{
let mut offset: u32;
::core::arch::asm!(
"movw {0}, #:lower16:{VAR}", "movt {0}, #:upper16:{VAR}",
out(reg) offset,
VAR = sym #symbol,
);
value = offset as usize;
}
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
::core::arch::asm!(
"lui {0}, %hi({VAR})",
"addi {0}, {0}, %lo({VAR})", out(reg) value,
VAR = sym #symbol,
);
#[cfg(any(target_arch = "loongarch64"))]
::core::arch::asm!(
"lu12i.w {0}, %abs_hi20({VAR})",
"ori {0}, {0}, %abs_lo12({VAR})", out(reg) value,
VAR = sym #symbol,
);
value
}
};
quote! {
{
#[cfg(not(target_os = "macos"))]
{ #fast_addr }
#[cfg(target_os = "macos")]
{ #slow_addr }
}
}
}
pub fn gen_offset(symbol: &Ident) -> proc_macro2::TokenStream {
#[cfg(feature = "non-zero-vma")]
{
let symbol_vma = gen_symbol_vma(symbol);
let symbol_vma_percpu_load_start = gen_symbol_vma(&format_ident!("_percpu_load_start"));
quote! {
{
extern "C" { static _percpu_load_start: u8; }
(#symbol_vma) - (#symbol_vma_percpu_load_start)
}
}
}
#[cfg(not(feature = "non-zero-vma"))]
{
gen_symbol_vma(symbol)
}
}
pub fn gen_current_ptr(symbol: &Ident, ty: &Type) -> proc_macro2::TokenStream {
let aarch64_tpidr = if cfg!(feature = "arm-el2") {
"TPIDR_EL2"
} else {
"TPIDR_EL1"
};
let aarch64_asm = format!("mrs {{}}, {aarch64_tpidr}");
macos_unimplemented(quote! {
let base: usize;
#[cfg(target_arch = "x86_64")]
{
::core::arch::asm!(
"mov {0}, gs:[offset __PERCPU_SELF_PTR]",
"add {0}, offset {VAR}",
out(reg) base,
VAR = sym #symbol,
);
base as *const #ty
}
#[cfg(not(target_arch = "x86_64"))]
{
#[cfg(target_arch = "aarch64")]
::core::arch::asm!(#aarch64_asm, out(reg) base);
#[cfg(target_arch = "arm")]
::core::arch::asm!("mrc p15, 0, {}, c13, c0, 4", out(reg) base);
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
::core::arch::asm!("mv {}, gp", out(reg) base);
#[cfg(any(target_arch = "loongarch64"))]
::core::arch::asm!("move {}, $r21", out(reg) base);
(base + self.symbol_vma()) as *const #ty
}
})
}
pub fn gen_read_current_raw(symbol: &Ident, ty: &Type) -> proc_macro2::TokenStream {
let ty_str = quote!(#ty).to_string();
let rv64_op = match ty_str.as_str() {
"u8" | "bool" => "lbu",
"u16" => "lhu",
"u32" => "lwu",
"u64" | "usize" => "ld",
_ => unreachable!(),
};
let rv64_asm = quote! {
::core::arch::asm!(
"lui {0}, %hi({VAR})",
"add {0}, {0}, gp",
concat!(#rv64_op, " {0}, %lo({VAR})({0})"),
out(reg) value,
VAR = sym #symbol,
)
};
let la64_op = match ty_str.as_str() {
"u8" | "bool" => "ldx.bu",
"u16" => "ldx.hu",
"u32" => "ldx.wu",
"u64" | "usize" => "ldx.d",
_ => unreachable!(),
};
let la64_asm = quote! {
::core::arch::asm!(
"lu12i.w {0}, %abs_hi20({VAR})",
"ori {0}, {0}, %abs_lo12({VAR})",
concat!(#la64_op, " {0}, {0}, $r21"),
out(reg) value,
VAR = sym #symbol,
)
};
let (x64_asm, x64_reg) = if ["bool", "u8"].contains(&ty_str.as_str()) {
(
"mov {0}, byte ptr gs:[offset {VAR}]".into(),
format_ident!("reg_byte"),
)
} else {
let (x64_mod, x64_ptr) = match ty_str.as_str() {
"u16" => ("x", "word"),
"u32" => ("e", "dword"),
"u64" | "usize" => ("r", "qword"),
_ => unreachable!(),
};
(
format!("mov {{0:{x64_mod}}}, {x64_ptr} ptr gs:[offset {{VAR}}]"),
format_ident!("reg"),
)
};
let x64_asm = quote! {
::core::arch::asm!(#x64_asm, out(#x64_reg) value, VAR = sym #symbol)
};
let gen_code = |asm_stmt| {
if ty_str.as_str() == "bool" {
quote! {
let value: u8;
#asm_stmt;
value != 0
}
} else {
quote! {
let value: #ty;
#asm_stmt;
value
}
}
};
let rv64_code = gen_code(rv64_asm);
let la64_code = gen_code(la64_asm);
let x64_code = gen_code(x64_asm);
macos_unimplemented(quote! {
#[cfg(target_arch = "riscv64")]
{ #rv64_code }
#[cfg(target_arch = "loongarch64")]
{ #la64_code }
#[cfg(target_arch = "x86_64")]
{ #x64_code }
#[cfg(not(any(target_arch = "riscv64", target_arch = "loongarch64", target_arch = "x86_64")))]
{ *self.current_ptr() }
})
}
pub fn gen_write_current_raw(symbol: &Ident, val: &Ident, ty: &Type) -> proc_macro2::TokenStream {
let ty_str = quote!(#ty).to_string();
let ty_fixup = if ty_str.as_str() == "bool" {
format_ident!("u8")
} else {
format_ident!("{}", ty_str)
};
let rv64_op = match ty_str.as_str() {
"u8" | "bool" => "sb",
"u16" => "sh",
"u32" => "sw",
"u64" | "usize" => "sd",
_ => unreachable!(),
};
let rv64_code = quote! {
::core::arch::asm!(
"lui {0}, %hi({VAR})",
"add {0}, {0}, gp",
concat!(#rv64_op, " {1}, %lo({VAR})({0})"),
out(reg) _,
in(reg) #val as #ty_fixup,
VAR = sym #symbol,
);
};
let la64_op = match ty_str.as_str() {
"u8" | "bool" => "stx.b",
"u16" => "stx.h",
"u32" => "stx.w",
"u64" | "usize" => "stx.d",
_ => unreachable!(),
};
let la64_code = quote! {
::core::arch::asm!(
"lu12i.w {0}, %abs_hi20({VAR})",
"ori {0}, {0}, %abs_lo12({VAR})",
concat!(#la64_op, " {1}, {0}, $r21"),
out(reg) _,
in(reg) #val as #ty_fixup,
VAR = sym #symbol,
);
};
let (x64_asm, x64_reg) = if ["bool", "u8"].contains(&ty_str.as_str()) {
(
"mov byte ptr gs:[offset {VAR}], {0}".into(),
format_ident!("reg_byte"),
)
} else {
let (x64_mod, x64_ptr) = match ty_str.as_str() {
"u16" => ("x", "word"),
"u32" => ("e", "dword"),
"u64" | "usize" => ("r", "qword"),
_ => unreachable!(),
};
(
format!("mov {x64_ptr} ptr gs:[offset {{VAR}}], {{0:{x64_mod}}}"),
format_ident!("reg"),
)
};
let x64_code = quote! {
::core::arch::asm!(#x64_asm, in(#x64_reg) #val as #ty_fixup, VAR = sym #symbol)
};
macos_unimplemented(quote! {
#[cfg(target_arch = "riscv64")]
{ #rv64_code }
#[cfg(target_arch = "loongarch64")]
{ #la64_code }
#[cfg(target_arch = "x86_64")]
{ #x64_code }
#[cfg(not(any(target_arch = "riscv64", target_arch = "loongarch64", target_arch = "x86_64")))]
{ *(self.current_ptr() as *mut #ty) = #val }
})
}