Merge branch 'main' into rkyv-0.8-migration

This commit is contained in:
bunnie 2024-09-23 22:32:34 +08:00 committed by GitHub
commit 67e0004f73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 267 additions and 39 deletions

View file

@ -1,7 +1,7 @@
[package]
edition = "2021"
name = "flatipc-derive"
version = "0.1.0"
version = "0.1.2"
authors = ["Sean Cross <sean@xobs.io>"]
description = "Custom derive for traits from the flatipc crate"
license = "BSD-2-Clause OR Apache-2.0 OR MIT"
@ -11,8 +11,8 @@ repository = "https://github.com/betrusted/xous-flatipc"
proc-macro = true
[dependencies]
proc-macro2 = "*"
quote = "*"
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["parsing", "extra-traits"] }
[features]

View file

@ -4,12 +4,22 @@ use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
fn ast_hash(ast: &syn::DeriveInput) -> u32 {
fn ast_hash(ast: &syn::DeriveInput) -> usize {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
ast.hash(&mut hasher);
let full_hash = hasher.finish();
((full_hash >> 32) as u32) ^ (full_hash as u32)
#[cfg(target_pointer_width = "64")]
{
full_hash as usize
}
#[cfg(target_pointer_width = "32")]
{
(((full_hash >> 32) as u32) ^ (full_hash as u32)) as usize
}
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
compile_error!("Unsupported target_pointer_width");
}
#[proc_macro_derive(IpcSafe)]
@ -52,11 +62,10 @@ fn derive_ipc_inner(ast: DeriveInput) -> Result<proc_macro2::TokenStream, proc_m
syn::Data::Union(r#union) => generate_transmittable_checks_union(&ast, r#union)?,
};
let padded_version = generate_padded_version(&ast)?;
let ipc_struct = generate_ipc_struct(&ast)?;
Ok(quote! {
#transmittable_checks
#padded_version
#ipc_struct
})
}
@ -222,10 +231,10 @@ fn generate_transmittable_checks_union(
})
}
fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream, proc_macro2::TokenStream> {
fn generate_ipc_struct(ast: &DeriveInput) -> Result<proc_macro2::TokenStream, proc_macro2::TokenStream> {
let visibility = ast.vis.clone();
let ident = ast.ident.clone();
let padded_ident = format_ident!("Ipc{}", ast.ident);
let ipc_ident = format_ident!("Ipc{}", ast.ident);
let ident_size = quote! { core::mem::size_of::< #ident >() };
let padded_size = quote! { (#ident_size + (4096 - 1)) & !(4096 - 1) };
let padding_size = quote! { #padded_size - #ident_size };
@ -286,73 +295,101 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
}
};
let memory_messages = if cfg!(feature = "xous") {
quote! {
fn from_memory_message<'a>(msg: &'a xous::MemoryMessage) -> Option<&'a Self> {
if msg.buf.len() < core::mem::size_of::< #ipc_ident >() {
return None;
}
let signature = msg.offset.map(|offset| offset.get()).unwrap_or_default();
if signature != #hash {
return None;
}
unsafe { Some(&*(msg.buf.as_ptr() as *const #ipc_ident)) }
}
fn from_memory_message_mut<'a>(msg: &'a mut xous::MemoryMessage) -> Option<&'a mut Self> {
if msg.buf.len() < core::mem::size_of::< #ipc_ident >() {
return None;
}
let signature = msg.offset.map(|offset| offset.get()).unwrap_or_default();
if signature != #hash {
return None;
}
unsafe { Some(&mut *(msg.buf.as_mut_ptr() as *mut #ipc_ident)) }
}
}
} else {
quote! {}
};
Ok(quote! {
#[repr(C, align(4096))]
#visibility struct #padded_ident {
#visibility struct #ipc_ident {
original: #ident,
padding: [u8; #padding_size],
}
impl core::ops::Deref for #padded_ident {
impl core::ops::Deref for #ipc_ident {
type Target = #ident ;
fn deref(&self) -> &Self::Target {
&self.original
}
}
impl core::ops::DerefMut for #padded_ident {
impl core::ops::DerefMut for #ipc_ident {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.original
}
}
impl flatipc::IntoIpc for #ident {
type IpcType = #padded_ident;
type IpcType = #ipc_ident;
fn into_ipc(self) -> Self::IpcType {
#padded_ident {
#ipc_ident {
original: self,
padding: [0; #padding_size],
}
}
}
impl flatipc::Ipc for #padded_ident {
unsafe impl flatipc::Ipc for #ipc_ident {
type Original = #ident ;
fn from_slice<'a>(data: &'a [u8], signature: usize) -> Option<&'a Self> {
if data.len() < core::mem::size_of::< #padded_ident >() {
if data.len() < core::mem::size_of::< #ipc_ident >() {
return None;
}
if signature as u32 != #hash {
if signature != #hash {
return None;
}
unsafe { Some(&*(data.as_ptr() as *const u8 as *const #padded_ident)) }
unsafe { Some(&*(data.as_ptr() as *const u8 as *const #ipc_ident)) }
}
unsafe fn from_buffer_unchecked<'a>(data: &'a [u8]) -> &'a Self {
&*(data.as_ptr() as *const u8 as *const #padded_ident)
&*(data.as_ptr() as *const u8 as *const #ipc_ident)
}
fn from_slice_mut<'a>(data: &'a mut [u8], signature: usize) -> Option<&'a mut Self> {
if data.len() < core::mem::size_of::< #padded_ident >() {
if data.len() < core::mem::size_of::< #ipc_ident >() {
return None;
}
if signature as u32 != #hash {
if signature != #hash {
return None;
}
unsafe { Some(&mut *(data.as_mut_ptr() as *mut u8 as *mut #padded_ident)) }
unsafe { Some(&mut *(data.as_mut_ptr() as *mut u8 as *mut #ipc_ident)) }
}
unsafe fn from_buffer_mut_unchecked<'a>(data: &'a mut [u8]) -> &'a mut Self {
unsafe { &mut *(data.as_mut_ptr() as *mut u8 as *mut #padded_ident) }
unsafe { &mut *(data.as_mut_ptr() as *mut u8 as *mut #ipc_ident) }
}
fn lend(&self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> {
let signature = self.signature() as usize;
let signature = self.signature();
let data = unsafe {
core::slice::from_raw_parts(
self as *const #padded_ident as *const u8,
core::mem::size_of::< #padded_ident >(),
self as *const #ipc_ident as *const u8,
core::mem::size_of::< #ipc_ident >(),
)
};
#lend
@ -360,11 +397,11 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
}
fn try_lend(&self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> {
let signature = self.signature() as usize;
let signature = self.signature();
let data = unsafe {
core::slice::from_raw_parts(
self as *const #padded_ident as *const u8,
core::mem::size_of::< #padded_ident >(),
self as *const #ipc_ident as *const u8,
core::mem::size_of::< #ipc_ident >(),
)
};
#try_lend
@ -372,10 +409,10 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
}
fn lend_mut(&mut self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> {
let signature = self.signature() as usize;
let signature = self.signature();
let mut data = unsafe {
core::slice::from_raw_parts_mut(
self as *mut #padded_ident as *mut u8,
self as *mut #ipc_ident as *mut u8,
#padded_size,
)
};
@ -384,10 +421,10 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
}
fn try_lend_mut(&mut self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> {
let signature = self.signature() as usize;
let signature = self.signature();
let mut data = unsafe {
core::slice::from_raw_parts_mut(
self as *mut #padded_ident as *mut u8,
self as *mut #ipc_ident as *mut u8,
#padded_size,
)
};
@ -407,9 +444,11 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
self.original
}
fn signature(&self) -> u32 {
fn signature(&self) -> usize {
#hash
}
#memory_messages
}
})
}

View file

@ -1,17 +1,129 @@
//! Xous supports sending Messages from Clients to Servers. If a message is
//! a `MemoryMessage`, then the Server may respond by updating the buffer
//! with a response message and returning the buffer.
//!
//! A number of serialization options are available, ranging from sending
//! raw arrays of bytes all the way to sending full Protobuf messages.
//!
//! This crate takes a middle road and allows for sending rich Rust types
//! such as full enums and structs without doing extensive checks. This is
//! based on the theory that in normal operating systems, ABIs do not contain
//! any sort of verification, and it is undefined behaviour to send a malformed
//! request to a loaded module.
//!
//! An object can be made into an IPC object by implementing the `Ipc` trait.
//! The primary method of doing this is by adding `#[derive(flatipc::Ipc)]`
//! to the definition. Such an object may be included in both the Client and
//! the Server so that they share the same view of the object.
//!
//! Any object may be made into an IPC object provided it follows the following
//! requirements:
//!
//! - **The object is `#[repr(C)]`** - This is required to ensure the objecty
//! has a well-defined layout in memory. Other representations may shift
//! depending on optimizations.
//! - **The object only contains fields that are `IpcSafe`** - This trait is
//! implemented on primitive types that are able to be sent across an IPC
//! boundary. This includes all integers, floats, and booleans. It also
//! includes arrays of `IpcSafe` types, `Option<T>` where `T` is `IpcSafe`,
//! and `Result<T, E>` where `T` and `E` are `IpcSafe`. Pointers and
//! references are not `IpcSafe` and may not be used.
//!
//! When deriving `Ipc`, a new type will be created with the same name as
//! the original type prefixed with `Ipc`. For example, if you derive `Ipc`
//! on a type named `Foo`, the new type will be named `IpcFoo`.
//!
//! Objects that implement `Ipc` must be page-aligned and must be a full multiple
//! of a page size in length. This is to ensure that the object can be mapped
//! transparently between the Client and the Server without dragging any extra
//! memory along with it.
//!
//! `Ipc` objects implement `Deref` and `DerefMut` to the original object, so
//! they can be used in place of the original object in most cases by derefencing
//! the `Ipc` object.
//!
//! Because `String` and `Vec` contain pointers, they are not `IpcSafe`. As such,
//! replacements are made available in this crate that are `IpcSafe`.
//!
//! <br>
//!
//! # Example of a Derived Ipc Object
//!
//! ```ignore
//! #[repr(C)]
//! #[derive(flatipc::Ipc)]
//! pub struct Foo {
//! a: u32,
//! b: u64,
//! }
//!
//! // Connect to an example server
//! let conn = xous::connect(xous::SID::from_bytes(b"example---server").unwrap())?;
//!
//! // Construct our object
//! let foo = Foo { a: 1, b: 1234567890 };
//!
//! // Create an IPC representation of the original object, consuming
//! // the original object in the process.
//! let mut foo_ipc = foo.into_ipc();
//!
//! // Lend the object to the server with opcode 42.
//! foo_ipc.lend(conn, 42)?;
//!
//! // Note that we can still access the original object.
//! println!("a: {}", foo_ipc.a);
//!
//! // When we're done with the IPC object, we can get the original object back.
//! let foo = foo_ipc.into_original();
//! ```
//!
//! # Example of Server Usage
//!
//! ```ignore
//! // Ideally this comes from a common `API` file that both the
//! // client and server share.
//! #[repr(C)]
//! #[derive(flatipc::Ipc)]
//! pub struct Foo {
//! a: u32,
//! b: u64,
//! }
//!
//! let mut msg_opt = None;
//! let mut server = xous::create_server_with_sid(b"example---server").unwrap();
//! loop {
//! let envelope = xous::reply_and_receive_next(server, &mut msg_opt).unwrap();
//! let Some(msg) = msg_opt else { continue };
//!
//! // Take the memory portion of the message, continuing if it's not a memory message.
//! let Some(msg_memory) = msg.memory_message() else { continue };
//!
//! // Turn the `MemoryMessage` into an `IpcFoo` object. Note that this is the `Ipc`-prefixed
//! // version of the original object. If the object is not the correct type, `None` will be
//! // returned and the message will be returned to the sender in ghe next loop.
//! let Some(foo) = IpcFoo::from_memory_message(msg_slice, signature) else { continue };
//!
//! // Do something with the object.
//! }
//! ```
/// An object is Sendable if it is guaranteed to be flat and contains no pointers.
/// This trait can be placed on objects that have invalid representations such as
/// bools (which can only be 0 or 1) but it is up to the implementer to ensure that
/// the correct object arrives on the other side.
pub unsafe trait IpcSafe {}
// Enable calling this crate as `flatipc` in tests.
extern crate self as flatipc;
// Allow doing `#[derive(flatipc::Ipc)]` instead of `#[derive(flatipc_derive::Ipc)]`
pub use flatipc_derive::{Ipc, IpcSafe};
#[cfg(feature = "xous")]
mod backend {
pub use xous::Error;
pub use xous::CID;
}
#[cfg(not(feature = "xous"))]
mod backend {
pub mod mock;
@ -56,8 +168,12 @@ where
{
}
pub trait Ipc {
/// What this memory message is a representation of.
/// An object that can be sent across an IPC boundary, and can be reconstituted
/// on the other side without copying. An object with this trait must be page-aligned,
/// must be a multiple of the page size in length, and must not contain any pointers.
pub unsafe trait Ipc {
/// What this memory message is a representation of. This is used to turn
/// this object back into the original object.
type Original;
/// Create an Ipc variant from the original object. Succeeds only if
@ -87,7 +203,8 @@ pub trait Ipc {
/// Consume the memory version and return the original object.
fn into_original(self) -> Self::Original;
/// Lend the buffer to the specified server.
/// Lend the buffer to the specified server. The connection should already be
/// open and the server should be ready to receive the buffer.
fn lend(&self, connection: CID, opcode: usize) -> Result<(), backend::Error>;
/// Try to lend the buffer to the specified server, returning an error
@ -104,9 +221,23 @@ pub trait Ipc {
/// Return the signature of this memory message. Useful for verifying
/// that the correct message is being received.
fn signature(&self) -> u32;
fn signature(&self) -> usize;
#[cfg(feature = "xous")]
/// Build an `Ipc` object from a `xous::MemoryMessage`. Verifies the signature and
/// returns `None` if there is no match.
fn from_memory_message<'a>(msg: &'a xous::MemoryMessage) -> Option<&'a Self>;
#[cfg(feature = "xous")]
/// Build a mutable `Ipc` object from a mutable `xous::MemoryMessage`. Verifies the
/// signature and returns `None` if there is no match. The returned object has a
/// lifetime that's tied to the `MemoryMessage`.
fn from_memory_message_mut<'a>(msg: &'a mut xous::MemoryMessage) -> Option<&'a mut Self>;
}
/// Objects that have `IntoIpc` may be turned into an object that can be passed
/// across an IPC barrier. This consumes the object and returns a new object that
/// may be dereferenced to the original object.
pub trait IntoIpc {
type IpcType;
fn into_ipc(self) -> Self::IpcType;

View file

@ -0,0 +1,58 @@
use std::sync::{LazyLock, Mutex};
pub struct Server {
lend: Box<dyn Send + Fn(usize, usize, usize, &[u8]) -> (usize, usize)>,
lend_mut: Box<dyn Send + Fn(usize, usize, usize, &mut [u8]) -> (usize, usize)>,
}
impl Server {
pub fn new(
lend: Box<dyn Send + Fn(usize, usize, usize, &[u8]) -> (usize, usize)>,
lend_mut: Box<dyn Send + Fn(usize, usize, usize, &mut [u8]) -> (usize, usize)>,
) -> Self {
Server { lend, lend_mut }
}
}
pub struct IpcMachine {
servers: Vec<Server>,
}
pub(crate) static IPC_MACHINE: LazyLock<Mutex<IpcMachine>> =
LazyLock::new(|| Mutex::new(IpcMachine::new()));
impl IpcMachine {
fn new() -> Self {
IpcMachine {
servers: Vec::new(),
}
}
pub fn add_server(&mut self, server: Server) -> usize {
let server_id = self.servers.len();
self.servers.push(server);
server_id
}
pub fn lend(
&self,
server_id: usize,
opcode: usize,
a: usize,
b: usize,
data: &[u8],
) -> (usize, usize) {
(self.servers[server_id].lend)(opcode, a, b, data)
}
pub fn lend_mut(
&self,
server_id: usize,
opcode: usize,
a: usize,
b: usize,
data: &mut [u8],
) -> (usize, usize) {
(self.servers[server_id].lend_mut)(opcode, a, b, data)
}
}