Skip to content

Commit

Permalink
common+common-proc: initial (untested) re-implementation of ser2mem
Browse files Browse the repository at this point in the history
  • Loading branch information
Qix- committed Jul 29, 2024
1 parent f94cd73 commit afa9768
Show file tree
Hide file tree
Showing 12 changed files with 584 additions and 6 deletions.
2 changes: 1 addition & 1 deletion clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
doc-valid-idents = ["..", "x86_64", "AArch64", "ARMv8"]
doc-valid-idents = ["..", "x86_64", "AArch64", "ARMv8", "Ser2Mem"]
21 changes: 21 additions & 0 deletions oro-common-proc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ mod enum_as;
mod enum_iterator;
mod gdb_autoload;
mod paste;
mod ser2mem;

pub(crate) mod syn_ex;

/// Derive macro for the `EnumIterator` trait.
///
Expand Down Expand Up @@ -83,3 +86,21 @@ pub fn enum_as_u32(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
pub fn gdb_autoload_inline(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
self::gdb_autoload::gdb_autoload_inline(input)
}

/// Automatically derives seralization for a structure to be deserialized via a pointer
/// cast.
///
/// Used exclusively by the boot protocol; its use should not be used for anything else.
///
/// **This macro only works within the `oro_common` crate.** It is not intended to be
/// used outside of the boot protocol, and thus won't compile in any other crate due to
/// the usage of the `crate` keyword. It is not advisable to try to work around this;
/// `ser2mem` is very unsafe and has a lot of restrictions, and there's no guarantee that
/// all of those restrictions are checked by the proc macros or implementations.
#[proc_macro_derive(Ser2Mem)]
pub fn derive_ser2mem(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
match self::ser2mem::derive_ser2mem(input) {
Ok(is) => is,
Err(e) => e.to_compile_error().into(),
}
}
249 changes: 249 additions & 0 deletions oro-common-proc/src/ser2mem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
//! Implements the `#[derive(Ser2Mem)]` derive proc macro.
//!
//! This macro generates implementations of the serializer
//! used to write Rust structures directly to another point
//! in memory, linearly, with proper alignment and padding
//! in order to be read by the kernel via a simple virtual
//! memory mapping and pointer cast.
//!
//! It's use is exclusively for the boot protocol and should
//! not be used for anything else.
#![allow(clippy::missing_docs_in_private_items)]

use crate::syn_ex::{ReprArgs, ReprAttribute};
use quote::quote;
use syn::{Data, DataEnum, DataStruct, DeriveInput, Error, Ident};

pub fn derive_ser2mem(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenStream> {
let input: DeriveInput = syn::parse(input)?;

let repr_args = match input.repr_args() {
Ok(ra) => ra,
Err(e) => {
return Err(Error::new_spanned(
input,
format!(
"Ser2Mem requires a #[repr(...)] attribute, but one could not be parsed: {e}",
),
));
}
};

if repr_args.packed {
return Err(Error::new(
repr_args.span,
"Ser2Mem does not support packed structs",
));
}

if !input.generics.params.is_empty() {
return Err(Error::new_spanned(
input.generics.params,
"Ser2Mem does not support generic types",
));
}

match input.data {
Data::Enum(d) => derive_ser2mem_enum(&input.ident, &d, &repr_args),
Data::Struct(d) => derive_ser2mem_struct(&input.ident, &d, &repr_args),
Data::Union(_) => {
Err(Error::new_spanned(
input,
"Ser2Mem cannot be derived for unions",
))
}
}
}

fn derive_ser2mem_enum(
ident: &Ident,
data: &DataEnum,
repr_args: &ReprArgs,
) -> syn::Result<proc_macro::TokenStream> {
// SAFETY(qix-): There isn't any *technical* reason in terms of implementing the serialization
// SAFETY(qix-): why these checks are needed but it keeps the boot protocol types consistent,
// SAFETY(qix-): especially over time.
if let Some(abi) = &repr_args.abi {
match ["u8", "u16", "u32", "u64"]
.into_iter()
.find_map(|x| if abi == x { Some(abi.clone()) } else { None })
{
Some(abi) => abi,
None => {
return Err(Error::new_spanned(
abi,
"Ser2Mem enums require a #[repr(u8|u16|u32|u64)] attribute, but an \
unsupported base type was given",
));
}
}
} else {
return Err(Error::new(
repr_args.span,
"Ser2Mem enums require a #[repr(u8|u16|u32|u64)] attribute with a supported base \
type, but no ABI was given",
));
};

for variant in &data.variants {
if !variant.fields.is_empty() {
return Err(Error::new_spanned(
variant,
"Ser2Mem can only be derived for unit variant enums",
));
}

if variant.discriminant.is_none() {
return Err(Error::new_spanned(
variant,
"Ser2Mem can only be derived for enums with explicit discriminants",
));
}
}

Ok(quote! {
const _: () = {
#[automatically_derived]
unsafe impl crate::ser2mem::Serialize for #ident {
type Output = Self;

#[inline]
unsafe fn serialize<S: crate::ser2mem::Serializer>(self, _s: &mut S) -> Result<Self::Output, S::Error> {
Ok(self)
}
}
};
}.into())
}

#[allow(clippy::too_many_lines)]
fn derive_ser2mem_struct(
ident: &Ident,
data: &DataStruct,
repr_args: &ReprArgs,
) -> syn::Result<proc_macro::TokenStream> {
if let Some(abi) = &repr_args.abi {
if abi != "C" {
return Err(Error::new_spanned(
abi,
"Ser2Mem structs require a #[repr(C, ...)] ABI specifier, but a different ABI was \
given",
));
}
} else {
return Err(Error::new(
repr_args.span,
"Ser2Mem structs require a #[repr(...)] attribute with a C ABI specifier, but no ABI \
was given",
));
}

let proxy_ident = Ident::new(&format!("{ident}Proxy"), ident.span());
let mut needs_lifetime = false;

let mut proxy_fields = vec![];
let mut field_serializations = vec![];
let mut field_writes = vec![];

for field in &data.fields {
let ident = field.ident.as_ref().ok_or_else(|| {
Error::new_spanned(field, "Ser2Mem can only be derived for named fields")
})?;

let ty = &field.ty;

// If the type is a static const reference to an array of T...
let proxy_ty = if let syn::Type::Reference(syn::TypeReference {
elem,
lifetime,
mutability,
..
}) = ty
{
if mutability.is_some() {
return Err(Error::new_spanned(
ty,
"Ser2Mem reference fields must be immutable",
));
}

if lifetime.is_none() || matches!(lifetime, Some(lt) if lt.ident != "static") {
return Err(Error::new_spanned(
ty,
"Ser2Mem reference fields must be 'static",
));
}

if let syn::Type::Slice(syn::TypeSlice { elem, .. }) = (*elem).as_ref() {
// We need to pass in a lifetime since we use `DynIter` in the proxy struct.
needs_lifetime = true;
quote! {
crate::ser2mem::DynIter<'iter, <#elem as crate::ser2mem::Proxy>::Proxy<'iter>>
}
} else {
quote! {
#elem
}
}
} else {
quote! {
#ty
}
};

let temp_ident = Ident::new(&format!("{ident}__out"), ident.span());

proxy_fields.push(quote! {
pub #ident: #proxy_ty,
});

field_serializations.push(quote! {
let #temp_ident = self.#ident.serialize(s)?;
});

field_writes.push(quote! {
#ident: #temp_ident,
});
}

let lt = if needs_lifetime {
quote! { <'iter> }
} else {
quote! {}
};

Ok(quote! {
const _: () = {
#[automatically_derived]
pub struct #proxy_ident #lt {
#(#proxy_fields)*
}

#[automatically_derived]
impl crate::ser2mem::Proxy for #ident {
type Proxy<'iter> = #proxy_ident #lt;
}

#[automatically_derived]
unsafe impl #lt crate::ser2mem::Serialize for #proxy_ident #lt {
type Output = &'static #ident;

#[allow(non_snake_case)]
unsafe fn serialize<S: crate::ser2mem::Serializer>(self, s: &mut S) -> Result<Self::Output, S::Error> {
let layout = ::core::alloc::Layout::new::<#ident>();

#(#field_serializations)*

let base = s.align_to(layout.align())?;

s.write(#ident {
#(#field_writes)*
})?;

Ok(&*base.cast())
}
}
};
}
.into())
}
33 changes: 33 additions & 0 deletions oro-common-proc/src/syn_ex/attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Utilities for working with attributes on certain [`syn`] types.
/// Allows high-level querying and interacting with attributes on
/// certain [`syn`] types.
pub trait Attributes {
/// Returns an attribute given its name. The name can be
/// a path, such as `foo::bar`.
///
/// Returns `None` if the attribute is not present.
fn get_attribute(&self, name: &str) -> Option<&syn::Attribute>;
}

impl Attributes for syn::DeriveInput {
fn get_attribute(&self, name: &str) -> Option<&syn::Attribute> {
let name_segments = name.split("::").collect::<Vec<_>>();
self.attrs.iter().find(|attr| {
let mut name_segments = name_segments.iter();
let mut attr_segments = attr.path().segments.iter();

loop {
match (name_segments.next(), attr_segments.next()) {
(Some(name), Some(attr)) => {
if attr.ident != name {
return false;
}
}
(None, Some(_)) | (Some(_), None) => return false,
(None, None) => return true,
}
}
})
}
}
10 changes: 10 additions & 0 deletions oro-common-proc/src/syn_ex/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Provides extension traits and implementations for common, high-level
//! operations on [`syn`] types.
mod attr;
mod repr;

pub use self::{
attr::Attributes,
repr::{ReprArgs, ReprAttribute},
};
Loading

0 comments on commit afa9768

Please sign in to comment.