diff --git a/Cargo.toml b/Cargo.toml index 8295cab3..53bf4f1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "iceoryx2-bb/trait-tests", "iceoryx2-ffi/ffi", + "iceoryx2-ffi/ffi-macros", "iceoryx2-cal", "iceoryx2", @@ -66,7 +67,8 @@ iceoryx2-pal-configuration = { version = "0.3.0", path = "iceoryx2-pal/configura iceoryx2-cal = { version = "0.3.0", path = "iceoryx2-cal" } -iceoryx2-ffi = { version = "0.3.0", path = "iceoryx2-ffi" } +iceoryx2-ffi = { version = "0.3.0", path = "iceoryx2-ffi/ffi" } +iceoryx2-ffi-macros = { version = "0.3.0", path = "iceoryx2-ffi/ffi-macros" } iceoryx2 = { version = "0.3.0", path = "iceoryx2/" } diff --git a/iceoryx2-ffi/ffi-macros/Cargo.toml b/iceoryx2-ffi/ffi-macros/Cargo.toml new file mode 100644 index 00000000..a8fc1882 --- /dev/null +++ b/iceoryx2-ffi/ffi-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "iceoryx2-ffi-macros" +description = "iceoryx2: [internal] helper proc-macros for ffi" +categories = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +keywords = { workspace = true } +license = { workspace = true } +readme = { workspace = true } +repository = { workspace = true } +rust-version = { workspace = true } +version = { workspace = true } + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { workspace = true } diff --git a/iceoryx2-ffi/ffi-macros/src/lib.rs b/iceoryx2-ffi/ffi-macros/src/lib.rs new file mode 100644 index 00000000..3e635168 --- /dev/null +++ b/iceoryx2-ffi/ffi-macros/src/lib.rs @@ -0,0 +1,216 @@ +// Copyright (c) 2024 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro::TokenStream; +use proc_macro2::TokenTree; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, punctuated::Punctuated, DeriveInput, LitStr, Meta, Token}; + +#[proc_macro_attribute] +pub fn iceoryx2_ffi(args: TokenStream, input: TokenStream) -> TokenStream { + let Args { rust_type: my_type } = parse_attribute_args(args); + + // Parse the input tokens into a syntax tree + let my_struct = parse_macro_input!(input as DeriveInput); + + let mut has_repr_c = false; + for attr in &my_struct.attrs { + if attr.path().is_ident("repr") { + let nested = attr + .parse_args_with(Punctuated::::parse_terminated) + .expect("'repr' should have at least one argument"); + for meta in nested { + match meta { + // #[repr(C)] + Meta::Path(path) if path.is_ident("C") => { + has_repr_c = true; + } + _ => (), + } + } + } + } + + if !has_repr_c { + panic!( + "The 'repr(C)' attribute is missing from '{}'!", + &my_struct.ident.to_string() + ); + } + + // Get the name of the struct we are generating code + let struct_name = &my_struct.ident; + let stripped_struct_name = struct_name + .to_string() + .strip_prefix("iox2_") + .expect("The struct must have an 'iox2_' prefix") + .strip_suffix("_t") + .expect("The struct must have a '_t' suffix") + .to_string(); + + // Define all the names we need + let struct_storage_name = format_ident!("iox2_{}_storage_t", stripped_struct_name); + let _struct_h_t_name = format_ident!("iox2_{}_h_t", stripped_struct_name); + let _struct_h_name = format_ident!("iox2_{}_h", stripped_struct_name); + let _struct_ref_h_t_name = format_ident!("iox2_{}_ref_h_t", stripped_struct_name); + let _struct_ref_h_name = format_ident!("iox2_{}_ref_h", stripped_struct_name); + + // NOTE: cbindgen does not play well with adding new structs or fields to existing structs; + // this code is kept for reference + + // // Add the additional fields to the struct + // match &mut my_struct.data { + // syn::Data::Struct(ref mut struct_data) => match &mut struct_data.fields { + // syn::Fields::Named(fields) => { + // fields.named.push( + // syn::Field::parse_named + // .parse2(quote! { value: #struct_storage_name }) + // .unwrap(), + // ); + // fields.named.push( + // syn::Field::parse_named + // .parse2(quote! { deleter: fn(*mut #struct_name) }) + // .unwrap(), + // ); + // } + // _ => (), + // }, + // _ => panic!("#### `iceoryx_ffi` has to be used with structs "), + // }; + + let expanded = quote! { + impl #struct_storage_name { + const fn assert_storage_layout() { + static_assert_ge::< + { ::std::mem::align_of::<#struct_storage_name>() }, + { ::std::mem::align_of::>() }, + >(); + static_assert_ge::< + { ::std::mem::size_of::<#struct_storage_name>() }, + { ::std::mem::size_of::>() }, + >(); + } + + fn init(&mut self, value: #my_type) { + #struct_storage_name::assert_storage_layout(); + + unsafe { &mut *(self as *mut Self).cast::<::std::mem::MaybeUninit>>() } + .write(Some(value)); + } + + unsafe fn as_option_mut(&mut self) -> &mut Option<#my_type> { + &mut *(self as *mut Self).cast::>() + } + + unsafe fn as_option_ref(&self) -> &Option<#my_type> { + &*(self as *const Self).cast::>() + } + + unsafe fn as_mut(&mut self) -> &mut #my_type { + self.as_option_mut().as_mut().unwrap() + } + + unsafe fn as_ref(&self) -> &#my_type { + self.as_option_ref().as_ref().unwrap() + } + } + + // this is the struct which is annotated with '#[iceoryx2_ffi(Type)]' + #my_struct + + impl #struct_name { + pub(crate) fn take(&mut self) -> Option<#my_type> { + unsafe { self.value.as_option_mut().take() } + } + + pub(crate) fn set(&mut self, value: #my_type) { + unsafe { *self.value.as_option_mut() = Some(value) } + } + + fn alloc() -> *mut #struct_name { + unsafe { ::std::alloc::alloc(::std::alloc::Layout::new::<#struct_name>()) as _ } + } + + fn dealloc(storage: *mut #struct_name) { + unsafe { + ::std::alloc::dealloc(storage as _, ::core::alloc::Layout::new::<#struct_name>()) + } + } + } + + #[cfg(test)] + mod test_generated { + use super::*; + + #[test] + fn assert_storage_size() { + // all const functions; if it compiles, the storage size is sufficient + const _STORAGE_LAYOUT_CHECK: () = #struct_storage_name::assert_storage_layout(); + } + } + }; + + // eprintln!("#### DEBUG\n{}", expanded); + TokenStream::from(expanded) +} + +struct Args { + rust_type: syn::Type, +} + +fn parse_attribute_args(args: TokenStream) -> Args { + let args = proc_macro2::TokenStream::from(args); + + let args = args.into_iter().collect::>(); + + let attribute_format = "Format must be '#[iceoryx2_ffi(Type)]'"; + if args.len() != 1 { + panic!("Invalid attribute definition! {}", attribute_format); + } + + let rust_type = match &args[0] { + TokenTree::Ident(my_type) => LitStr::new(&my_type.to_string(), my_type.span()) + .parse::() + .expect("Valid type"), + _ => panic!("Invalid type argument! {}", attribute_format), + }; + + // NOTE: this code is kept for reference if more arguments are added to the attribute + + // match (&args[1], &args[3], &args[5], &args[7]) { + // (TokenTree::Punct(a), TokenTree::Punct(b), TokenTree::Punct(c), TokenTree::Punct(d)) + // if a.as_char() == ',' + // && b.as_char() == '=' + // && c.as_char() == ',' + // && d.as_char() == '=' => + // { + // () + // } + // _ => panic!("Invalid format! {}", attribute_format), + // } + // + // let size = match (&args[2], &args[4]) { + // (TokenTree::Ident(key), TokenTree::Literal(value)) if key.to_string() == "size" => { + // >::into(value.clone()) + // } + // _ => panic!("Invalid 'size' argument! {}", attribute_format), + // }; + // + // let alignment = match (&args[6], &args[8]) { + // (TokenTree::Ident(key), TokenTree::Literal(value)) if key.to_string() == "alignment" => { + // >::into(value.clone()) + // } + // _ => panic!("Invalid 'alignment' argument! {}", attribute_format), + // }; + + Args { rust_type } +} diff --git a/iceoryx2-ffi/ffi/Cargo.toml b/iceoryx2-ffi/ffi/Cargo.toml index 3f3600ac..ddcb9662 100644 --- a/iceoryx2-ffi/ffi/Cargo.toml +++ b/iceoryx2-ffi/ffi/Cargo.toml @@ -26,6 +26,7 @@ iceoryx2 = { workspace = true } iceoryx2-bb-container = { workspace = true } iceoryx2-bb-elementary = { workspace = true } iceoryx2-bb-log = { workspace = true } +iceoryx2-ffi-macros = { workspace = true } [dev-dependencies] iceoryx2-bb-testing = { workspace = true }