diff --git a/api/sixtyfps-cpp/include/vtable.h b/api/sixtyfps-cpp/include/vtable.h new file mode 100644 index 000000000..2123abc77 --- /dev/null +++ b/api/sixtyfps-cpp/include/vtable.h @@ -0,0 +1,18 @@ + +template +struct VBox { + T *vtable; + void *instance; +}; + +template +struct VRef { + T *vtable; + void *instance; +}; + +template +struct VRefMut { + T *vtable; + void *instance; +} diff --git a/helper_crates/vtable/macro/macro.rs b/helper_crates/vtable/macro/macro.rs index 3cda1c59b..ef1210e3b 100644 --- a/helper_crates/vtable/macro/macro.rs +++ b/helper_crates/vtable/macro/macro.rs @@ -11,6 +11,23 @@ use quote::quote; use syn::spanned::Spanned; use syn::*; +/// Returns true if the type `ty` is "Container" +fn match_generic_type(ty: &Type, container: &str, containee: &Ident) -> bool { + if let Type::Path(pat) = ty { + if let Some(seg) = pat.path.segments.last() { + if seg.ident != container { + return false; + } + if let PathArguments::AngleBracketed(args) = &seg.arguments { + if let Some(GenericArgument::Type(Type::Path(arg))) = args.args.last() { + return Some(containee) == arg.path.get_ident(); + } + } + } + } + false +} + #[proc_macro_attribute] pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut input = parse_macro_input!(item as ItemStruct); @@ -36,17 +53,11 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { let trait_name = Ident::new(&vtable_name[..vtable_name.len() - 6], input.ident.span()); let to_name = quote::format_ident!("{}TO", trait_name); let impl_name = quote::format_ident!("{}Impl", trait_name); + let type_name = quote::format_ident!("{}Type", trait_name); let module_name = quote::format_ident!("{}_vtable_mod", trait_name); - let box_name = quote::format_ident!("{}Box", trait_name); - let ref_name = quote::format_ident!("{}Ref", trait_name); - let refmut_name = quote::format_ident!("{}RefMut", trait_name); let vtable_name = input.ident.clone(); - let ref_doc = format!("This is an equivalent to a `&'a dyn {}`", trait_name); - let refmut_doc = format!("This is an equivalent to a `&'a mut dyn {}`", trait_name); - let box_doc = format!("This is an equivalent to a `Box`", trait_name); - - let mut box_impl = None; + let mut drop_impl = None; let mut generated_trait = ItemTrait { attrs: input @@ -68,8 +79,7 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { }; let mut generated_to_fn_trait = vec![]; - let mut generated_to_fn_assoc = vec![]; - let mut generated_constructor = vec![]; + let mut generated_type_assoc_fn = vec![]; let mut vtable_ctor = vec![]; for field in &mut fields.named { @@ -98,6 +108,10 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut call_code = None; let mut self_call = None; let mut forward_code = None; + + #[derive(Default)] + struct SelfInfo {} + let mut has_self = false; for param in &f.inputs { @@ -114,51 +128,58 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { }); sig_extern.inputs.push(typed_arg.clone()); - match ¶m.ty { - Type::Ptr(TypePtr { mutability, elem, .. }) - | Type::Reference(TypeReference { mutability, elem, .. }) => { - if let Type::Path(p) = &**elem { - if let Some(pointer_to) = p.path.get_ident() { - if pointer_to == &vtable_name { - if mutability.is_some() { - return Error::new(p.span(), "VTable cannot be mutable") - .to_compile_error() - .into(); - } - if call_code.is_some() || sig.inputs.len() > 0 { - return Error::new( - p.span(), - "VTable pointer need to be the first", - ) + // check for the vtable + if let Type::Ptr(TypePtr { mutability, elem, .. }) + | Type::Reference(TypeReference { mutability, elem, .. }) = ¶m.ty + { + if let Type::Path(p) = &**elem { + if let Some(pointer_to) = p.path.get_ident() { + if pointer_to == &vtable_name { + if mutability.is_some() { + return Error::new(p.span(), "VTable cannot be mutable") .to_compile_error() .into(); - } - call_code = Some(quote!(vtable.as_ptr(),)); - continue; - } else if pointer_to == &impl_name { - if sig.inputs.len() > 0 { - return Error::new(p.span(), "Impl pointer need to be the first (with the exception of VTable)").to_compile_error().into(); - } - sig.inputs.push(FnArg::Receiver(Receiver { - attrs: param.attrs.clone(), - reference: Some(Default::default()), - mutability: mutability.clone(), - self_token: Default::default(), - })); - call_code = Some(quote!(#call_code ptr.as_ptr(),)); - let const_or_mut = mutability - .map(|x| quote!(#x)) - .unwrap_or_else(|| quote!(const)); - self_call = Some( - quote!(&#mutability (*(#arg_name as *#const_or_mut T)), ), - ); - has_self = true; - continue; } + if call_code.is_some() || sig.inputs.len() > 0 { + return Error::new( + p.span(), + "VTable pointer need to be the first", + ) + .to_compile_error() + .into(); + } + call_code = Some(quote!(self.vtable.as_ptr(),)); + continue; } } } - _ => {} + } + + // check for self + if let (true, mutability) = if match_generic_type(¶m.ty, "VRef", &vtable_name) { + (true, None) + } else if match_generic_type(¶m.ty, "VRefMut", &vtable_name) { + (true, Some(Default::default())) + } else { + (false, None) + } { + if sig.inputs.len() > 0 { + return Error::new(param.span(), "Self pointer need to be the first") + .to_compile_error() + .into(); + } + sig.inputs.push(FnArg::Receiver(Receiver { + attrs: param.attrs.clone(), + reference: Some(Default::default()), + mutability, + self_token: Default::default(), + })); + let self_ty = ¶m.ty; + let const_or_mut = mutability.map_or_else(|| quote!(const), |x| quote!(#x)); + call_code = Some(quote!(#call_code <#self_ty>::from_inner(*self),)); + self_call = Some(quote!(&#mutability (*(<#self_ty>::inner(&#arg_name).ptr.as_ptr() as *#const_or_mut T)),)); + has_self = true; + continue; } sig.inputs.push(typed_arg); call_code = Some(quote!(#call_code #arg_name,)); @@ -175,10 +196,8 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { } else { f.abi = sig_extern.abi.clone(); } - // Remove pub, if any - field.vis = Visibility::Inherited; - // FIXME!!! - field.vis = Visibility::Public(VisPublic{ pub_token: Default::default() }); + // The vtable can only be accessed in unsafe code, so it is ok if all its fields are Public + field.vis = Visibility::Public(VisPublic { pub_token: Default::default() }); let mut wrap_trait_call = None; if !has_self { @@ -186,27 +205,20 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { where_clause: Some(parse_str("where Self : Sized").unwrap()), ..Default::default() }; + + // Check if this is a constructor functions if let ReturnType::Type(_, ret) = &f.output { - if let Type::Path(ret) = &**ret { - if let Some(seg) = ret.path.segments.last() { - if let PathArguments::AngleBracketed(args) = &seg.arguments { - if let Some(GenericArgument::Type(Type::Path(arg))) = - args.args.first() - { - if let Some(arg) = arg.path.get_ident() { - // that's quite a lot of if let to get the argument of the type - if seg.ident == "Box" && arg == &impl_name { - // Consider this is a constructor, so change Box to Self - sig.output = parse_str("-> Self").unwrap(); - wrap_trait_call = Some(quote! { - let wrap_trait_call = |x| Box::from_raw(Box::into_raw(Box::new(x)) as *mut #impl_name); - wrap_trait_call - }); - } - } - } - } - } + if match_generic_type(&**ret, "VBox", &vtable_name) { + // Change VBox to Self + sig.output = parse_str("-> Self").unwrap(); + wrap_trait_call = Some(quote! { + let wrap_trait_call = |x| { + // Put the object on the heap and get a pointer to it + let ptr = core::ptr::NonNull::from(Box::leak(Box::new(x))); + VBox::<#vtable_name>::from_inner(#to_name { vtable, ptr : ptr.cast() }) + }; + wrap_trait_call + }); } } } @@ -221,46 +233,12 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { #ident:: },)); - box_impl = Some(quote! { - #[doc = #box_doc] - pub struct #box_name { - inner: #to_name, - } - impl #box_name { - /// Construct the box from raw pointer of a vtable and a corresponding pointer - pub unsafe fn from_raw( - vtable: core::ptr::NonNull<#vtable_name>, - ptr: core::ptr::NonNull<#impl_name>, - ) -> Self { - Self{inner: #to_name{vtable, ptr}} - } - - /*pub fn vtable(&self) -> & #vtable_name { - unsafe { self.inner.vtable.as_ref() } - }*/ - - pub fn as_ptr(&self) -> *mut #impl_name { - self.inner.ptr.as_ptr() - } - } - impl core::ops::Deref for #box_name { - type Target = dyn #trait_name; - fn deref(&self) -> &Self::Target { - &self.inner - } - } - impl core::ops::DerefMut for #box_name { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } - } - impl core::ops::Drop for #box_name { - fn drop(&mut self) { - #[allow(unused)] - let (vtable, ptr) = (&self.inner.vtable, &self.inner.ptr); + drop_impl = Some(quote! { + impl VTableMetaDrop for #vtable_name { + unsafe fn drop(ptr: #to_name) { // Safety: The vtable is valid and inner is a type corresponding to the vtable, // which was allocated such that drop is expected. - unsafe { (vtable.as_ref().#ident)(#call_code) } + unsafe { (ptr.vtable.as_ref().#ident)(VRefMut::from_inner(ptr)) } } } @@ -283,10 +261,8 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { block: parse( if has_self { quote!({ - #[allow(unused)] - let (vtable, ptr) = (&self.vtable, &self.ptr); // Safety: this rely on the vtable being valid, and the ptr being a valid instance for this vtable - unsafe { (vtable.as_ref().#ident)(#call_code) } + unsafe { (self.vtable.as_ref().#ident)(#call_code) } }) } else { // This should never happen: nobody should be able to access the Trait Object directly. @@ -307,57 +283,45 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { self_token: Default::default(), }), ); - if wrap_trait_call.is_some() { - sig.output = parse(quote!(-> #box_name).into()).unwrap(); - generated_constructor.push(ImplItemMethod { - attrs: vec![], - vis: generated_trait.vis.clone(), - defaultness: None, - sig, - block: parse( - quote!({ - // Safety: this rely on the vtable being valid, and the ptr being a valid instance for this vtable - unsafe { - #[allow(unused)] - let vtable = core::ptr::NonNull::from(self); - #box_name::from_raw(vtable, std::ptr::NonNull::from(Box::leak((self.#ident)(#call_code)))) - } - }) - .into(), - ) - .unwrap(), - }); - } else { - generated_to_fn_assoc.push(ImplItemMethod { - attrs: vec![], - vis: generated_trait.vis.clone(), - defaultness: None, - sig, - block: parse( - quote!({ - #[allow(unused_parens)] - #[allow(unused)] - let vtable = core::ptr::NonNull::from(self); - // Safety: this rely on the vtable being valid, and the ptr being a valid instance for this vtable - unsafe { #wrap_trait_call((vtable.as_ref().#ident)(#call_code)) } - }) - .into(), - ) - .unwrap(), - }); - } - } + sig.output = sig_extern.output.clone(); + generated_type_assoc_fn.push(ImplItemMethod { + attrs: vec![], + vis: generated_trait.vis.clone(), + defaultness: None, + sig, + block: parse( + quote!({ + #[allow(unused_parens)] + let vtable = self.vtable; + // Safety: this rely on the vtable being valid, and the ptr being a valid instance for this vtable + unsafe { #wrap_trait_call((vtable.as_ref().#ident)(#call_code)) } + }) + .into(), + ) + .unwrap(), + }); - vtable_ctor.push(quote!(#ident: { - #sig_extern { - #[allow(unused_parens)] - // This is safe since the self must be a instance of our type - unsafe { - #wrap_trait_call(T::#ident(#self_call #forward_code)) + vtable_ctor.push(quote!(#ident: { + #sig_extern { + #[allow(unused_parens)] + // This is safe since the self must be a instance of our type + unsafe { + #[allow(unused)] + let vtable = core::ptr::NonNull::new_unchecked(_0 as *mut #vtable_name); + #wrap_trait_call(T::#ident(#self_call #forward_code)) + } } - } - #ident:: - },)); + #ident:: + },)); + } else { + vtable_ctor.push(quote!(#ident: { + #sig_extern { + // This is safe since the self must be a instance of our type + unsafe { T::#ident(#self_call #forward_code) } + } + #ident:: + },)); + } } else { return Error::new(field.span(), "member must only be functions") .to_compile_error() @@ -369,13 +333,15 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { input.vis = Visibility::Public(VisPublic { pub_token: Default::default() }); /*let (fields_name, fields_type): (Vec<_>, Vec<_>) = - fields.named.iter().map(|f| (f.ident.clone().unwrap(), f.ty.clone())).unzip();*/ + fields.named.iter().map(|f| (f.ident.clone().unwrap(), f.ty.clone())).unzip();*/ let result = quote!( #[allow(non_snake_case)] /// This private module is generated by the `vtable` macro mod #module_name { + #[allow(unused)] use super::*; + use ::vtable::*; #input impl #vtable_name { @@ -385,60 +351,49 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream { #(#vtable_ctor)* } } - - /*pub unsafe fn from_raw_data(#(#fields_name : #fields_type),*) -> Self { - Self { - #(#fields_name),* - } - }*/ - - #(#generated_constructor)* - #(#generated_to_fn_assoc)* } #generated_trait - pub struct #impl_name { _private: [u8; 0] } + struct #impl_name { _private: [u8; 0] } + + /// This structure is highly unsafe, as it just has pointers. One could call trait functions + /// directly. However, it should not be possible, in safe code, to construct or to obtain a reference + /// to this structure, as it cannot be constructed safely. And none of the safe api allow accessing + /// a reference or a copy of this structure + #[doc(hidden)] #[derive(Clone, Copy)] - struct #to_name { + #[repr(C)] + pub struct #to_name { vtable: core::ptr::NonNull<#vtable_name>, ptr: core::ptr::NonNull<#impl_name>, } impl #trait_name for #to_name { #(#generated_to_fn_trait)* } - #[doc = #ref_doc] - #[derive(Clone, Copy)] - pub struct #ref_name<'a> { - inner: #to_name, - _phantom: core::marker::PhantomData<&'a #impl_name>, + #[repr(transparent)] + /// Safe wrapper around a VTable. + pub struct #type_name { + vtable: core::ptr::NonNull<#vtable_name> } - impl<'a> core::ops::Deref for #ref_name<'a> { - type Target = dyn #trait_name; - fn deref(&self) -> &Self::Target { - &self.inner - } + impl #type_name { + pub unsafe fn from_raw(vtable: core::ptr::NonNull<#vtable_name>) -> Self { + Self { vtable } + } + #(#generated_type_assoc_fn)* + } + unsafe impl VTableMeta for #vtable_name { + type Trait = dyn #trait_name; + type VTable = #vtable_name; + type TraitObject = #to_name; + unsafe fn map_to(to: &Self::TraitObject) -> &Self::Trait { to } + unsafe fn map_to_mut(to: &mut Self::TraitObject) -> &mut Self::Trait { to } } - #[doc = #refmut_doc] - pub struct #refmut_name<'a> { - inner: #to_name, - _phantom: core::marker::PhantomData<&'a *mut #impl_name>, - } - impl<'a> core::ops::Deref for #refmut_name<'a> { - type Target = dyn #trait_name; - fn deref(&self) -> &Self::Target { - &self.inner - } - } - impl<'a> core::ops::DerefMut for #refmut_name<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } - } - #box_impl + #drop_impl + } #[doc(inline)] #vis use #module_name::*; ); - // println!("{}", result); + println!("{}", result); result.into() } diff --git a/helper_crates/vtable/src/lib.rs b/helper_crates/vtable/src/lib.rs index dfb7f8508..d01917df1 100644 --- a/helper_crates/vtable/src/lib.rs +++ b/helper_crates/vtable/src/lib.rs @@ -1 +1,140 @@ +use core::ops::{Deref, DerefMut, Drop}; +//use core::ptr::NonNull; pub use vtable_macro::vtable; + +pub unsafe trait VTableMeta { + /// that's the rust trait. (e.g: `Hello`) + type Trait: ?Sized; + /// that's the vtable struct `HelloVTable` + type VTable; + + /// That's the trait object that implements the trait. + /// NOTE: the size must be 2*size_of + type TraitObject: Copy; + + /// That maps from the tait object from the trait iteself + /// (In other word, return 'to' since 'to' implements trait, + /// but we can't represent that in rust right now, hence these helper) + /// + /// Safety: the trait object need to be pointing to valid pointer / vtable + unsafe fn map_to(to: &Self::TraitObject) -> &Self::Trait; + /// Same as map_to, but mutable + unsafe fn map_to_mut(to: &mut Self::TraitObject) -> &mut Self::Trait; +} + +pub trait VTableMetaDrop: VTableMeta { + /// Safety: the traitobject need to be pointing to a valid allocated pointer + unsafe fn drop(ptr: Self::TraitObject); +} + +// These checks are not enough to ensure that this is not unsafe. +fn sanity_checks() { + debug_assert_eq!(core::mem::size_of::(), 2 * core::mem::size_of::()); +} + +#[repr(C)] +pub struct VBox { + inner: T::TraitObject, +} + +impl Deref for VBox { + type Target = T::Trait; + fn deref(&self) -> &Self::Target { + sanity_checks::(); + unsafe { T::map_to(&self.inner) } + } +} +impl DerefMut for VBox { + fn deref_mut(&mut self) -> &mut Self::Target { + sanity_checks::(); + unsafe { T::map_to_mut(&mut self.inner) } + } +} + +impl Drop for VBox { + fn drop(&mut self) { + unsafe { + T::drop(self.inner); + } + } +} + +impl VBox { + pub unsafe fn from_inner(inner: T::TraitObject) -> Self { + Self { inner } + } + pub unsafe fn inner(x: &Self) -> T::TraitObject { + x.inner + } +} + +/* +impl VBox { + /// Construct the box from raw pointer of a vtable and a corresponding pointer + pub unsafe fn from_inner( + vtable: core::ptr::NonNull<#vtable_name>, + ptr: core::ptr::NonNull<#impl_name>, + ) -> Self { + Self{inner: #to_name{vtable, ptr}} + } + + /*pub fn vtable(&self) -> & #vtable_name { + unsafe { self.inner.vtable.as_ref() } + }*/ + + /* pub fn as_ptr(&self) -> *mut #impl_name { + self.inner.ptr.as_ptr() + }*/ +} +*/ +#[derive(Clone, Copy)] +pub struct VRef<'a, T: ?Sized + VTableMeta> { + inner: T::TraitObject, + _phantom: core::marker::PhantomData<&'a T::Trait>, +} + +impl<'a, T: ?Sized + VTableMeta> Deref for VRef<'a, T> { + type Target = T::Trait; + fn deref(&self) -> &Self::Target { + sanity_checks::(); + unsafe { T::map_to(&self.inner) } + } +} + +impl<'a, T: ?Sized + VTableMeta> VRef<'a, T> { + pub unsafe fn from_inner(inner: T::TraitObject) -> Self { + Self { inner, _phantom: core::marker::PhantomData } + } + pub unsafe fn inner(x: &Self) -> T::TraitObject { + x.inner + } +} + +pub struct VRefMut<'a, T: ?Sized + VTableMeta> { + inner: T::TraitObject, + _phantom: core::marker::PhantomData<&'a mut T::Trait>, +} + +impl<'a, T: ?Sized + VTableMeta> Deref for VRefMut<'a, T> { + type Target = T::Trait; + fn deref(&self) -> &Self::Target { + sanity_checks::(); + unsafe { T::map_to(&self.inner) } + } +} + +impl<'a, T: ?Sized + VTableMeta> DerefMut for VRefMut<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + sanity_checks::(); + unsafe { T::map_to_mut(&mut self.inner) } + } +} + +impl<'a, T: ?Sized + VTableMeta> VRefMut<'a, T> { + pub unsafe fn from_inner(inner: T::TraitObject) -> Self { + Self { inner, _phantom: core::marker::PhantomData } + } + pub unsafe fn inner(x: &Self) -> T::TraitObject { + x.inner + } +} diff --git a/helper_crates/vtable/tests/test_vtable.rs b/helper_crates/vtable/tests/test_vtable.rs index 55730fb37..980fb120b 100644 --- a/helper_crates/vtable/tests/test_vtable.rs +++ b/helper_crates/vtable/tests/test_vtable.rs @@ -1,23 +1,31 @@ -use vtable::vtable; +use vtable::*; #[vtable] /// This is the actual doc struct HelloVTable { - foo: fn(*const HelloVTable, *mut HelloImpl, u32) -> u32, - - construct: fn(*const HelloVTable, u32) -> Box, - + foo: fn(VRef<'_, HelloVTable>, u32) -> u32, + foo_mut: fn(VRefMut<'_, HelloVTable>, u32) -> u32, + construct: fn(*const HelloVTable, u32) -> VBox, assoc: fn(*const HelloVTable) -> isize, - drop: fn(*const HelloVTable, *mut HelloImpl), + drop: fn(VRefMut<'_, HelloVTable>), } +#[derive(Debug)] struct SomeStruct(u32); impl Hello for SomeStruct { - fn foo(&mut self, xx: u32) -> u32 { + fn foo(&self, xx: u32) -> u32 { + println!("calling foo {} + {}", self.0, xx); self.0 + xx } + fn foo_mut(&mut self, xx: u32) -> u32 { + self.0 += xx; + self.0 + } + + fn construct(init: u32) -> Self { + println!("calling Construct {}", init); Self(init) } @@ -28,8 +36,14 @@ impl Hello for SomeStruct { #[test] fn test() { - let vt = HelloVTable::new::(); + //let vt = HelloVTable::new::(); + let mut vt = HelloVTable::new::(); + let vt = unsafe { HelloType::from_raw(std::ptr::NonNull::from(&mut vt)) }; assert_eq!(vt.assoc(), 32); let mut bx = vt.construct(89); assert_eq!(bx.foo(1), 90); + assert_eq!(bx.foo_mut(1), 91); + assert_eq!(bx.foo(1), 92); } + + diff --git a/sixtyfps_runtime/corelib/build.rs b/sixtyfps_runtime/corelib/build.rs index dfb6bc9e6..fd0a2af1c 100644 --- a/sixtyfps_runtime/corelib/build.rs +++ b/sixtyfps_runtime/corelib/build.rs @@ -26,6 +26,7 @@ fn main() { cbindgen::Builder::new() .with_config(config) .with_crate(crate_dir) + .with_header("#include ") .generate() .expect("Unable to generate bindings") .write_to_file(env::var("OUT_DIR").unwrap() + "/sixtyfps_internal.h");