diff --git a/.gitmodules b/.gitmodules index 2b2315dfe..762ebcf1f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,5 +1,5 @@ -[submodule "crates/mun_abi/c"] - path = crates/mun_abi/c +[submodule "crates/mun_abi/ffi"] + path = crates/mun_abi/ffi url = https://github.com/mun-lang/abi-c.git [submodule "crates/mun_runtime_capi/ffi"] path = crates/mun_runtime_capi/ffi diff --git a/crates/mun_abi/c b/crates/mun_abi/c deleted file mode 160000 index 4bccc48f4..000000000 --- a/crates/mun_abi/c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4bccc48f4c782fa06c1ae911b2f7bf36e587280c diff --git a/crates/mun_abi/cbindgen.toml b/crates/mun_abi/cbindgen.toml new file mode 100644 index 000000000..9b1ecfc22 --- /dev/null +++ b/crates/mun_abi/cbindgen.toml @@ -0,0 +1,18 @@ +language = "C" +cpp_compat = true + +include_guard = "MUN_ABI_H_" +include_version = true +sys_includes = ["stdint.h"] +no_includes = true + +line_length = 100 +tab_width = 4 + +[export] +include = ["AssemblyInfo", "StructInfo"] +prefix = "Mun" +renaming_overrides_prefixing = true + +[export.rename] +"ABI_VERSION" = "MUN_ABI_VERSION" diff --git a/crates/mun_abi/ffi b/crates/mun_abi/ffi new file mode 160000 index 000000000..4e6b5421c --- /dev/null +++ b/crates/mun_abi/ffi @@ -0,0 +1 @@ +Subproject commit 4e6b5421c058e489af886b447c8c13ad4cd04a1c diff --git a/crates/mun_abi/src/assembly_info.rs b/crates/mun_abi/src/assembly_info.rs new file mode 100644 index 000000000..ee871094d --- /dev/null +++ b/crates/mun_abi/src/assembly_info.rs @@ -0,0 +1,59 @@ +use crate::{DispatchTable, ModuleInfo}; +use std::{ffi::CStr, os::raw::c_char, slice, str}; + +/// Represents an assembly declaration. +#[repr(C)] +pub struct AssemblyInfo { + /// Symbols of the top-level module + pub symbols: ModuleInfo, + /// Dispatch table + pub dispatch_table: DispatchTable, + /// Paths to assembly dependencies + pub(crate) dependencies: *const *const c_char, + /// Number of dependencies + pub num_dependencies: u32, +} + +impl AssemblyInfo { + /// Returns an iterator over the assembly's dependencies. + pub fn dependencies(&self) -> impl Iterator { + let dependencies = if self.num_dependencies == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(self.dependencies, self.num_dependencies as usize) } + }; + + dependencies + .iter() + .map(|d| unsafe { str::from_utf8_unchecked(CStr::from_ptr(*d).to_bytes()) }) + } +} + +unsafe impl Send for AssemblyInfo {} +unsafe impl Sync for AssemblyInfo {} + +#[cfg(test)] +mod tests { + use crate::test_utils::{ + fake_assembly_info, fake_dispatch_table, fake_module_info, FAKE_DEPENDENCY, + FAKE_MODULE_PATH, + }; + use std::ffi::CString; + + #[test] + fn test_assembly_info_dependencies() { + let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); + let module = fake_module_info(&module_path, &[], &[]); + + let dispatch_table = fake_dispatch_table(&[], &mut []); + + let dependency = CString::new(FAKE_DEPENDENCY).expect("Invalid fake dependency."); + let dependencies = &[dependency.as_ptr()]; + let assembly = fake_assembly_info(module, dispatch_table, dependencies); + + assert_eq!(assembly.dependencies().count(), dependencies.len()); + for (lhs, rhs) in assembly.dependencies().zip([FAKE_DEPENDENCY].iter()) { + assert_eq!(lhs, *rhs) + } + } +} diff --git a/crates/mun_abi/src/autogen.rs b/crates/mun_abi/src/autogen.rs deleted file mode 100644 index 9a96aeace..000000000 --- a/crates/mun_abi/src/autogen.rs +++ /dev/null @@ -1,552 +0,0 @@ -//! Generated file, do not edit by hand, see `crate/ra_tools/src/codegen` - -/* automatically generated by rust-bindgen 0.54.1 */ - -#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] -use crate::{StructMemoryKind, TypeGroup}; - -#[doc = " Represents a globally unique identifier (GUID)."] -#[doc = ""] -#[doc = " GUIDs are generated by taking the MD5 hash of a type's name."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct Guid { - #[doc = " 16-byte MD5 hash"] - pub b: [u8; 16usize], -} -#[test] -fn bindgen_test_layout_Guid() { - assert_eq!( - ::std::mem::size_of::(), - 16usize, - concat!("Size of: ", stringify!(Guid)) - ); - assert_eq!( - ::std::mem::align_of::(), - 1usize, - concat!("Alignment of ", stringify!(Guid)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).b as *const _ as usize }, - 0usize, - concat!("Offset of field: ", stringify!(Guid), "::", stringify!(b)) - ); -} -#[doc = " Represents the type declaration for a value type."] -#[doc = ""] -#[doc = " TODO: add support for structs, polymorphism, enumerations, type parameters, generic type definitions, and constructed generic types."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Debug)] -pub struct TypeInfo { - #[doc = " Type GUID"] - pub guid: Guid, - #[doc = " Type name"] - pub name: *const ::std::os::raw::c_char, - #[doc = " The exact size of the type in bits without any padding"] - pub size_in_bits: u32, - #[doc = " The alignment of the type"] - pub alignment: u8, - #[doc = " Type group"] - pub group: TypeGroup, -} -#[test] -fn bindgen_test_layout_TypeInfo() { - assert_eq!( - ::std::mem::size_of::(), - 32usize, - concat!("Size of: ", stringify!(TypeInfo)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(TypeInfo)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).guid as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(TypeInfo), - "::", - stringify!(guid) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).name as *const _ as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(TypeInfo), - "::", - stringify!(name) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).size_in_bits as *const _ as usize }, - 24usize, - concat!( - "Offset of field: ", - stringify!(TypeInfo), - "::", - stringify!(size_in_bits) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).alignment as *const _ as usize }, - 28usize, - concat!( - "Offset of field: ", - stringify!(TypeInfo), - "::", - stringify!(alignment) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).group as *const _ as usize }, - 29usize, - concat!( - "Offset of field: ", - stringify!(TypeInfo), - "::", - stringify!(group) - ) - ); -} -#[doc = " Represents a function signature."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Clone, Debug)] -pub struct FunctionSignature { - #[doc = " Argument types"] - pub arg_types: *const *const TypeInfo, - #[doc = " Optional return type"] - pub return_type: *const TypeInfo, - #[doc = " Number of argument types"] - pub num_arg_types: u16, -} -#[test] -fn bindgen_test_layout_FunctionSignature() { - assert_eq!( - ::std::mem::size_of::(), - 24usize, - concat!("Size of: ", stringify!(FunctionSignature)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(FunctionSignature)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).arg_types as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(FunctionSignature), - "::", - stringify!(arg_types) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).return_type as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(FunctionSignature), - "::", - stringify!(return_type) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).num_arg_types as *const _ as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(FunctionSignature), - "::", - stringify!(num_arg_types) - ) - ); -} -#[doc = " Represents a function prototype. A function prototype contains the name,"] -#[doc = " type signature, but not an implementation."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Clone, Debug)] -pub struct FunctionPrototype { - #[doc = " Function name"] - pub name: *const ::std::os::raw::c_char, - #[doc = " The type signature of the function"] - pub signature: FunctionSignature, -} -#[test] -fn bindgen_test_layout_FunctionPrototype() { - assert_eq!( - ::std::mem::size_of::(), - 32usize, - concat!("Size of: ", stringify!(FunctionPrototype)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(FunctionPrototype)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).name as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(FunctionPrototype), - "::", - stringify!(name) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).signature as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(FunctionPrototype), - "::", - stringify!(signature) - ) - ); -} -#[doc = " Represents a function definition. A function definition contains the name,"] -#[doc = " type signature, and a pointer to the implementation."] -#[doc = ""] -#[doc = " `fn_ptr` can be used to call the declared function."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Clone, Debug)] -pub struct FunctionDefinition { - #[doc = " Function prototype"] - pub prototype: FunctionPrototype, - #[doc = " Function pointer"] - pub fn_ptr: *const ::std::os::raw::c_void, -} -#[test] -fn bindgen_test_layout_FunctionDefinition() { - assert_eq!( - ::std::mem::size_of::(), - 40usize, - concat!("Size of: ", stringify!(FunctionDefinition)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(FunctionDefinition)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).prototype as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(FunctionDefinition), - "::", - stringify!(prototype) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).fn_ptr as *const _ as usize }, - 32usize, - concat!( - "Offset of field: ", - stringify!(FunctionDefinition), - "::", - stringify!(fn_ptr) - ) - ); -} -#[doc = " Represents a struct declaration."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Clone, Debug)] -pub struct StructInfo { - #[doc = " Struct fields' names"] - pub field_names: *const *const ::std::os::raw::c_char, - #[doc = " Struct fields' information"] - pub field_types: *const *const TypeInfo, - #[doc = " Struct fields' offsets"] - pub field_offsets: *const u16, - #[doc = " Number of fields"] - pub num_fields: u16, - #[doc = " Struct memory kind"] - pub memory_kind: StructMemoryKind, -} -#[test] -fn bindgen_test_layout_StructInfo() { - assert_eq!( - ::std::mem::size_of::(), - 32usize, - concat!("Size of: ", stringify!(StructInfo)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(StructInfo)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).field_names as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(StructInfo), - "::", - stringify!(field_names) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).field_types as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(StructInfo), - "::", - stringify!(field_types) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).field_offsets as *const _ as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(StructInfo), - "::", - stringify!(field_offsets) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).num_fields as *const _ as usize }, - 24usize, - concat!( - "Offset of field: ", - stringify!(StructInfo), - "::", - stringify!(num_fields) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).memory_kind as *const _ as usize }, - 26usize, - concat!( - "Offset of field: ", - stringify!(StructInfo), - "::", - stringify!(memory_kind) - ) - ); -} -#[doc = " Represents a module declaration."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Debug)] -pub struct ModuleInfo { - #[doc = " Module path"] - pub path: *const ::std::os::raw::c_char, - #[doc = " Module functions"] - pub functions: *const FunctionDefinition, - #[doc = " Number of module functions"] - pub num_functions: u32, - #[doc = " Module types"] - pub types: *const *const TypeInfo, - #[doc = " Number of module types"] - pub num_types: u32, -} -#[test] -fn bindgen_test_layout_ModuleInfo() { - assert_eq!( - ::std::mem::size_of::(), - 40usize, - concat!("Size of: ", stringify!(ModuleInfo)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(ModuleInfo)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).path as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(ModuleInfo), - "::", - stringify!(path) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).functions as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(ModuleInfo), - "::", - stringify!(functions) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).num_functions as *const _ as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(ModuleInfo), - "::", - stringify!(num_functions) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).types as *const _ as usize }, - 24usize, - concat!( - "Offset of field: ", - stringify!(ModuleInfo), - "::", - stringify!(types) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).num_types as *const _ as usize }, - 32usize, - concat!( - "Offset of field: ", - stringify!(ModuleInfo), - "::", - stringify!(num_types) - ) - ); -} -#[doc = " Represents a function dispatch table. This is used for runtime linking."] -#[doc = ""] -#[doc = " Function signatures and pointers are stored separately for cache efficiency."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Debug)] -pub struct DispatchTable { - #[doc = " Function signatures"] - pub prototypes: *const FunctionPrototype, - #[doc = " Function pointers"] - pub fn_ptrs: *mut *const ::std::os::raw::c_void, - #[doc = " Number of functions"] - pub num_entries: u32, -} -#[test] -fn bindgen_test_layout_DispatchTable() { - assert_eq!( - ::std::mem::size_of::(), - 24usize, - concat!("Size of: ", stringify!(DispatchTable)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(DispatchTable)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).prototypes as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(DispatchTable), - "::", - stringify!(prototypes) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).fn_ptrs as *const _ as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(DispatchTable), - "::", - stringify!(fn_ptrs) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).num_entries as *const _ as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(DispatchTable), - "::", - stringify!(num_entries) - ) - ); -} -#[doc = " Represents an assembly declaration."] -#[doc = ""] -#[doc = "
"] -#[repr(C)] -#[derive(Debug)] -pub struct AssemblyInfo { - #[doc = " Symbols of the top-level module"] - pub symbols: ModuleInfo, - #[doc = " Dispatch table"] - pub dispatch_table: DispatchTable, - #[doc = " Paths to assembly dependencies"] - pub dependencies: *const *const ::std::os::raw::c_char, - #[doc = " Number of dependencies"] - pub num_dependencies: u32, -} -#[test] -fn bindgen_test_layout_AssemblyInfo() { - assert_eq!( - ::std::mem::size_of::(), - 80usize, - concat!("Size of: ", stringify!(AssemblyInfo)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(AssemblyInfo)) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).symbols as *const _ as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(AssemblyInfo), - "::", - stringify!(symbols) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).dispatch_table as *const _ as usize }, - 40usize, - concat!( - "Offset of field: ", - stringify!(AssemblyInfo), - "::", - stringify!(dispatch_table) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).dependencies as *const _ as usize }, - 64usize, - concat!( - "Offset of field: ", - stringify!(AssemblyInfo), - "::", - stringify!(dependencies) - ) - ); - assert_eq!( - unsafe { &(*(::std::ptr::null::())).num_dependencies as *const _ as usize }, - 72usize, - concat!( - "Offset of field: ", - stringify!(AssemblyInfo), - "::", - stringify!(num_dependencies) - ) - ); -} diff --git a/crates/mun_abi/src/autogen_impl.rs b/crates/mun_abi/src/autogen_impl.rs deleted file mode 100644 index cde280b1e..000000000 --- a/crates/mun_abi/src/autogen_impl.rs +++ /dev/null @@ -1,923 +0,0 @@ -use crate::prelude::*; - -use std::convert::TryInto; -use std::ffi::{c_void, CStr}; -use std::fmt::Formatter; -use std::marker::{Send, Sync}; -use std::mem; -use std::str; -use std::{fmt, slice}; - -impl TypeInfo { - /// Returns the type's name. - pub fn name(&self) -> &str { - unsafe { str::from_utf8_unchecked(CStr::from_ptr(self.name).to_bytes()) } - } - - /// Retrieves the type's struct information, if available. - pub fn as_struct(&self) -> Option<&StructInfo> { - if self.group.is_struct() { - let ptr = (self as *const TypeInfo).cast::(); - let ptr = ptr.wrapping_add(mem::size_of::()); - let offset = ptr.align_offset(mem::align_of::()); - let ptr = ptr.wrapping_add(offset); - Some(unsafe { &*ptr.cast::() }) - } else { - None - } - } - - /// Returns the size of the type in bits - pub fn size_in_bits(&self) -> usize { - self.size_in_bits - .try_into() - .expect("cannot convert size in bits to platform size") - } - - /// Returns the size of the type in bytes - pub fn size_in_bytes(&self) -> usize { - ((self.size_in_bits + 7) / 8) - .try_into() - .expect("cannot covert size in bytes to platform size") - } - - /// Returns the alignment of the type in bytes - pub fn alignment(&self) -> usize { - self.alignment - .try_into() - .expect("cannot convert alignment to platform size") - } -} - -impl fmt::Display for TypeInfo { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.name()) - } -} - -impl PartialEq for TypeInfo { - fn eq(&self, other: &Self) -> bool { - self.guid == other.guid - } -} - -impl std::hash::Hash for TypeInfo { - fn hash(&self, state: &mut H) { - self.guid.hash(state); - } -} - -unsafe impl Send for TypeInfo {} -unsafe impl Sync for TypeInfo {} - -impl FunctionSignature { - /// Returns the function's arguments' types. - pub fn arg_types(&self) -> &[&TypeInfo] { - if self.num_arg_types == 0 { - &[] - } else { - unsafe { - slice::from_raw_parts( - self.arg_types.cast::<&TypeInfo>(), - self.num_arg_types as usize, - ) - } - } - } - - /// Returns the function's return type - pub fn return_type(&self) -> Option<&TypeInfo> { - unsafe { self.return_type.as_ref() } - } -} - -impl fmt::Display for FunctionSignature { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "fn(")?; - for (i, arg) in self.arg_types().iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{}", arg)?; - } - write!(f, ")")?; - if let Some(ret_type) = self.return_type() { - write!(f, ":{}", ret_type)? - } - Ok(()) - } -} - -impl PartialEq for FunctionSignature { - fn eq(&self, other: &Self) -> bool { - self.return_type() == other.return_type() - && self.arg_types().len() == other.arg_types().len() - && self - .arg_types() - .iter() - .zip(other.arg_types().iter()) - .all(|(a, b)| PartialEq::eq(a, b)) - } -} - -impl Eq for FunctionSignature {} - -unsafe impl Send for FunctionSignature {} -unsafe impl Sync for FunctionSignature {} - -impl FunctionPrototype { - /// Returns the function's name. - pub fn name(&self) -> &str { - unsafe { str::from_utf8_unchecked(CStr::from_ptr(self.name).to_bytes()) } - } -} - -impl fmt::Display for FunctionPrototype { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "fn {}(", self.name())?; - for (i, arg) in self.signature.arg_types().iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{}", arg)?; - } - write!(f, ")")?; - if let Some(ret_type) = self.signature.return_type() { - write!(f, ":{}", ret_type)? - } - Ok(()) - } -} - -unsafe impl Send for FunctionPrototype {} -unsafe impl Sync for FunctionPrototype {} - -unsafe impl Send for FunctionDefinition {} -unsafe impl Sync for FunctionDefinition {} - -impl StructInfo { - /// Returns the struct's field names. - pub fn field_names(&self) -> impl Iterator { - let field_names = if self.num_fields == 0 { - &[] - } else { - unsafe { slice::from_raw_parts(self.field_names, self.num_fields as usize) } - }; - - field_names - .iter() - .map(|n| unsafe { str::from_utf8_unchecked(CStr::from_ptr(*n).to_bytes()) }) - } - - /// Returns the struct's field types. - pub fn field_types(&self) -> &[&TypeInfo] { - if self.num_fields == 0 { - &[] - } else { - unsafe { - slice::from_raw_parts( - self.field_types.cast::<&TypeInfo>(), - self.num_fields as usize, - ) - } - } - } - - /// Returns the struct's field offsets. - pub fn field_offsets(&self) -> &[u16] { - if self.num_fields == 0 { - &[] - } else { - unsafe { slice::from_raw_parts(self.field_offsets, self.num_fields as usize) } - } - } - - /// Returns the index of the field matching the specified `field_name`. - pub fn find_field_index( - type_name: &str, - struct_info: &StructInfo, - field_name: &str, - ) -> Result { - struct_info - .field_names() - .enumerate() - .find(|(_, name)| *name == field_name) - .map(|(idx, _)| idx) - .ok_or_else(|| { - format!( - "Struct `{}` does not contain field `{}`.", - type_name, field_name - ) - }) - } -} - -impl ModuleInfo { - /// Returns the module's full path. - pub fn path(&self) -> &str { - unsafe { str::from_utf8_unchecked(CStr::from_ptr(self.path).to_bytes()) } - } - - // /// Finds the type's fields that match `filter`. - // pub fn find_fields(&self, filter: fn(&&FieldInfo) -> bool) -> impl Iterator { - // self.fields.iter().map(|f| *f).filter(filter) - // } - - // /// Retrieves the type's field with the specified `name`, if it exists. - // pub fn get_field(&self, name: &str) -> Option<&FieldInfo> { - // self.fields.iter().find(|f| f.name == name).map(|f| *f) - // } - - // /// Retrieves the type's fields. - // pub fn get_fields(&self) -> impl Iterator { - // self.fields.iter().map(|f| *f) - // } - - /// Returns the module's functions. - pub fn functions(&self) -> &[FunctionDefinition] { - if self.num_functions == 0 { - &[] - } else { - unsafe { slice::from_raw_parts(self.functions, self.num_functions as usize) } - } - } - - /// Returns the module's types. - pub fn types(&self) -> &[&TypeInfo] { - if self.num_types == 0 { - &[] - } else { - unsafe { - slice::from_raw_parts(self.types.cast::<&TypeInfo>(), self.num_types as usize) - } - } - } -} - -unsafe impl Send for ModuleInfo {} -unsafe impl Sync for ModuleInfo {} - -impl DispatchTable { - /// Returns an iterator over pairs of mutable function pointers and signatures. - pub fn iter_mut(&mut self) -> impl Iterator { - if self.num_entries == 0 { - (&mut []).iter_mut().zip((&[]).iter()) - } else { - let ptrs = - unsafe { slice::from_raw_parts_mut(self.fn_ptrs, self.num_entries as usize) }; - let signatures = - unsafe { slice::from_raw_parts(self.prototypes, self.num_entries as usize) }; - - ptrs.iter_mut().zip(signatures.iter()) - } - } - - /// Returns an iterator over pairs of function pointers and signatures. - pub fn iter(&self) -> impl Iterator { - if self.num_entries == 0 { - (&[]).iter().zip((&[]).iter()) - } else { - let ptrs = - unsafe { slice::from_raw_parts_mut(self.fn_ptrs, self.num_entries as usize) }; - let signatures = - unsafe { slice::from_raw_parts(self.prototypes, self.num_entries as usize) }; - - ptrs.iter().zip(signatures.iter()) - } - } - - /// Returns mutable functions pointers. - pub fn ptrs_mut(&mut self) -> &mut [*const c_void] { - if self.num_entries == 0 { - &mut [] - } else { - unsafe { slice::from_raw_parts_mut(self.fn_ptrs, self.num_entries as usize) } - } - } - - /// Returns function prototypes. - pub fn prototypes(&self) -> &[FunctionPrototype] { - if self.num_entries == 0 { - &[] - } else { - unsafe { slice::from_raw_parts(self.prototypes, self.num_entries as usize) } - } - } - - /// Returns a function pointer, without doing bounds checking. - /// - /// This is generally not recommended, use with caution! Calling this method with an - /// out-of-bounds index is _undefined behavior_ even if the resulting reference is not used. - /// For a safe alternative see [get_ptr](#method.get_ptr). - /// - /// # Safety - /// - /// The `idx` is not bounds checked and should therefor be used with care. - pub unsafe fn get_ptr_unchecked(&self, idx: u32) -> *const c_void { - *self.fn_ptrs.offset(idx as isize) - } - - /// Returns a function pointer at the given index, or `None` if out of bounds. - pub fn get_ptr(&self, idx: u32) -> Option<*const c_void> { - if idx < self.num_entries { - Some(unsafe { self.get_ptr_unchecked(idx) }) - } else { - None - } - } - - /// Returns a mutable reference to a function pointer, without doing bounds checking. - /// - /// This is generally not recommended, use with caution! Calling this method with an - /// out-of-bounds index is _undefined behavior_ even if the resulting reference is not used. - /// For a safe alternative see [get_ptr_mut](#method.get_ptr_mut). - /// - /// # Safety - /// - /// The `idx` is not bounds checked and should therefor be used with care. - pub unsafe fn get_ptr_unchecked_mut(&mut self, idx: u32) -> &mut *const c_void { - &mut *self.fn_ptrs.offset(idx as isize) - } - - /// Returns a mutable reference to a function pointer at the given index, or `None` if out of - /// bounds. - pub fn get_ptr_mut(&mut self, idx: u32) -> Option<&mut *const c_void> { - if idx < self.num_entries { - Some(unsafe { self.get_ptr_unchecked_mut(idx) }) - } else { - None - } - } -} - -impl AssemblyInfo { - /// Returns an iterator over the assembly's dependencies. - pub fn dependencies(&self) -> impl Iterator { - let dependencies = if self.num_dependencies == 0 { - &[] - } else { - unsafe { slice::from_raw_parts(self.dependencies, self.num_dependencies as usize) } - }; - - dependencies - .iter() - .map(|d| unsafe { str::from_utf8_unchecked(CStr::from_ptr(*d).to_bytes()) }) - } -} - -unsafe impl Send for AssemblyInfo {} -unsafe impl Sync for AssemblyInfo {} - -#[cfg(test)] -mod tests { - use super::*; - use std::ffi::CString; - use std::os::raw::c_char; - use std::ptr; - - /// A dummy struct for initializing a struct's `TypeInfo` - #[allow(dead_code)] - struct StructTypeInfo { - type_info: TypeInfo, - struct_info: StructInfo, - } - - fn fake_type_info(name: &CStr, group: TypeGroup, size: u32, alignment: u8) -> TypeInfo { - TypeInfo { - guid: FAKE_TYPE_GUID, - name: name.as_ptr(), - size_in_bits: size, - alignment, - group, - } - } - - fn fake_struct_type_info( - name: &CStr, - struct_info: StructInfo, - size: u32, - alignment: u8, - ) -> StructTypeInfo { - StructTypeInfo { - type_info: fake_type_info(name, TypeGroup::StructTypes, size, alignment), - struct_info, - } - } - - const FAKE_TYPE_GUID: Guid = Guid { - b: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - }; - - const FAKE_TYPE_NAME: &str = "type-name"; - const FAKE_FIELD_NAME: &str = "field-name"; - - #[test] - fn test_type_info_name() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - assert_eq!(type_info.name(), FAKE_TYPE_NAME); - } - - #[test] - fn test_type_info_size_alignment() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 24, 8); - - assert_eq!(type_info.size_in_bits(), 24); - assert_eq!(type_info.size_in_bytes(), 3); - assert_eq!(type_info.alignment(), 8); - } - - #[test] - fn test_type_info_group_fundamental() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_group = TypeGroup::FundamentalTypes; - let type_info = fake_type_info(&type_name, type_group, 1, 1); - - assert_eq!(type_info.group, type_group); - assert!(type_info.group.is_fundamental()); - assert!(!type_info.group.is_struct()); - } - - #[test] - fn test_type_info_group_struct() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_group = TypeGroup::StructTypes; - let type_info = fake_type_info(&type_name, type_group, 1, 1); - - assert_eq!(type_info.group, type_group); - assert!(type_info.group.is_struct()); - assert!(!type_info.group.is_fundamental()); - } - - #[test] - fn test_type_info_eq() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - assert_eq!(type_info, type_info); - } - - fn fake_fn_signature( - arg_types: &[&TypeInfo], - return_type: Option<&TypeInfo>, - ) -> FunctionSignature { - FunctionSignature { - arg_types: arg_types.as_ptr().cast::<*const TypeInfo>(), - return_type: return_type.map_or(ptr::null(), |t| t as *const TypeInfo), - num_arg_types: arg_types.len() as u16, - } - } - - fn fake_fn_prototype( - name: &CStr, - arg_types: &[&TypeInfo], - return_type: Option<&TypeInfo>, - ) -> FunctionPrototype { - FunctionPrototype { - name: name.as_ptr(), - signature: fake_fn_signature(arg_types, return_type), - } - } - - const FAKE_FN_NAME: &str = "fn-name"; - - #[test] - fn test_fn_prototype_name() { - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_signature = fake_fn_prototype(&fn_name, &[], None); - - assert_eq!(fn_signature.name(), FAKE_FN_NAME); - } - - #[test] - fn test_fn_signature_arg_types_none() { - let arg_types = &[]; - let fn_signature = fake_fn_signature(arg_types, None); - - assert_eq!(fn_signature.arg_types(), arg_types); - } - - #[test] - fn test_fn_signature_arg_types_some() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let arg_types = &[&type_info]; - let fn_signature = fake_fn_signature(arg_types, None); - - assert_eq!(fn_signature.arg_types(), arg_types); - } - - #[test] - fn test_fn_signature_return_type_none() { - let return_type = None; - let fn_signature = fake_fn_signature(&[], return_type); - - assert_eq!(fn_signature.return_type(), return_type); - } - - #[test] - fn test_fn_signature_return_type_some() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_signature = fake_fn_signature(&[], return_type); - - assert_eq!(fn_signature.return_type(), return_type); - } - - fn fake_struct_info( - field_names: &[*const c_char], - field_types: &[&TypeInfo], - field_offsets: &[u16], - memory_kind: StructMemoryKind, - ) -> StructInfo { - assert!(field_names.len() == field_types.len()); - assert!(field_types.len() == field_offsets.len()); - - StructInfo { - field_names: field_names.as_ptr(), - field_types: field_types.as_ptr().cast::<*const TypeInfo>(), - field_offsets: field_offsets.as_ptr(), - num_fields: field_names.len() as u16, - memory_kind, - } - } - - #[test] - fn test_struct_info_fields_none() { - let field_names = &[]; - let field_types = &[]; - let field_offsets = &[]; - let struct_info = - fake_struct_info(field_names, field_types, field_offsets, Default::default()); - - assert_eq!(struct_info.field_names().count(), 0); - assert_eq!(struct_info.field_types(), field_types); - assert_eq!(struct_info.field_offsets(), field_offsets); - } - - #[test] - fn test_struct_info_fields_some() { - let field_name = CString::new(FAKE_FIELD_NAME).expect("Invalid fake field name."); - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let field_names = &[field_name.as_ptr()]; - let field_types = &[&type_info]; - let field_offsets = &[1]; - let struct_info = - fake_struct_info(field_names, field_types, field_offsets, Default::default()); - - for (lhs, rhs) in struct_info.field_names().zip([FAKE_FIELD_NAME].iter()) { - assert_eq!(lhs, *rhs) - } - assert_eq!(struct_info.field_types(), field_types); - assert_eq!(struct_info.field_offsets(), field_offsets); - } - - #[test] - fn test_struct_info_memory_kind_gc() { - let struct_memory_kind = StructMemoryKind::GC; - let struct_info = fake_struct_info(&[], &[], &[], struct_memory_kind.clone()); - - assert_eq!(struct_info.memory_kind, struct_memory_kind); - } - - #[test] - fn test_struct_info_memory_kind_value() { - let struct_memory_kind = StructMemoryKind::Value; - let struct_info = fake_struct_info(&[], &[], &[], struct_memory_kind.clone()); - - assert_eq!(struct_info.memory_kind, struct_memory_kind); - } - - fn fake_module_info( - path: &CStr, - functions: &[FunctionDefinition], - types: &[&TypeInfo], - ) -> ModuleInfo { - ModuleInfo { - path: path.as_ptr(), - functions: functions.as_ptr(), - num_functions: functions.len() as u32, - types: types.as_ptr().cast::<*const TypeInfo>(), - num_types: types.len() as u32, - } - } - - const FAKE_MODULE_PATH: &str = "path::to::module"; - const FAKE_STRUCT_NAME: &str = "StructName"; - - #[test] - fn test_module_info_path() { - let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); - let module = fake_module_info(&module_path, &[], &[]); - - assert_eq!(module.path(), FAKE_MODULE_PATH); - } - - #[test] - fn test_module_info_types_none() { - let functions = &[]; - let types = &[]; - let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); - let module = fake_module_info(&module_path, functions, types); - - assert_eq!(module.functions().len(), functions.len()); - assert_eq!(module.types().len(), types.len()); - } - - #[test] - fn test_module_info_types_some() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let fn_info = FunctionDefinition { - prototype: fn_prototype, - fn_ptr: ptr::null(), - }; - let functions = &[fn_info]; - - let struct_name = CString::new(FAKE_STRUCT_NAME).expect("Invalid fake struct name"); - let struct_info = fake_struct_info(&[], &[], &[], Default::default()); - let struct_type_info = fake_struct_type_info(&struct_name, struct_info, 1, 1); - let types = &[unsafe { mem::transmute(&struct_type_info) }]; - - let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); - let module = fake_module_info(&module_path, functions, types); - - let result_functions = module.functions(); - assert_eq!(result_functions.len(), functions.len()); - for (lhs, rhs) in result_functions.iter().zip(functions.iter()) { - assert_eq!(lhs.fn_ptr, rhs.fn_ptr); - assert_eq!(lhs.prototype.name(), rhs.prototype.name()); - assert_eq!( - lhs.prototype.signature.arg_types(), - rhs.prototype.signature.arg_types() - ); - assert_eq!( - lhs.prototype.signature.return_type(), - rhs.prototype.signature.return_type() - ); - } - - let result_types: &[&TypeInfo] = module.types(); - assert_eq!(result_types.len(), types.len()); - for (lhs, rhs) in result_types.iter().zip(types.iter()) { - assert_eq!(lhs, rhs); - assert_eq!(lhs.name(), rhs.name()); - assert_eq!(lhs.group, rhs.group); - if lhs.group == TypeGroup::StructTypes { - let lhs_struct = lhs.as_struct().unwrap(); - let rhs_struct = rhs.as_struct().unwrap(); - assert_eq!(lhs_struct.field_types(), rhs_struct.field_types()); - } - } - } - - fn fake_dispatch_table( - fn_prototypes: &[FunctionPrototype], - fn_ptrs: &mut [*const c_void], - ) -> DispatchTable { - assert_eq!(fn_prototypes.len(), fn_ptrs.len()); - - DispatchTable { - prototypes: fn_prototypes.as_ptr(), - fn_ptrs: fn_ptrs.as_mut_ptr(), - num_entries: fn_prototypes.len() as u32, - } - } - - #[test] - fn test_dispatch_table_iter_mut_none() { - let signatures = &[]; - let fn_ptrs = &mut []; - let mut dispatch_table = fake_dispatch_table(signatures, fn_ptrs); - - let iter = fn_ptrs.iter_mut().zip(signatures.iter()); - assert_eq!(dispatch_table.iter_mut().count(), iter.count()); - } - - #[test] - fn test_dispatch_table_iter_mut_some() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototypes = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); - - let iter = fn_ptrs.iter_mut().zip(prototypes.iter()); - assert_eq!(dispatch_table.iter_mut().count(), iter.len()); - - for (lhs, rhs) in dispatch_table.iter_mut().zip(iter) { - assert_eq!(lhs.0, rhs.0); - assert_eq!(lhs.1.name(), rhs.1.name()); - assert_eq!(lhs.1.signature.arg_types(), rhs.1.signature.arg_types()); - assert_eq!(lhs.1.signature.return_type(), rhs.1.signature.return_type()); - } - } - - #[test] - fn test_dispatch_table_ptrs_mut_none() { - let signatures = &[]; - let fn_ptrs = &mut []; - let mut dispatch_table = fake_dispatch_table(signatures, fn_ptrs); - - assert_eq!(dispatch_table.ptrs_mut().len(), 0); - } - - #[test] - fn test_dispatch_table_ptrs_mut_some() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototypes = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); - - let result = dispatch_table.ptrs_mut(); - assert_eq!(result.len(), fn_ptrs.len()); - for (lhs, rhs) in result.iter().zip(fn_ptrs.iter()) { - assert_eq!(lhs, rhs); - } - } - - #[test] - fn test_dispatch_table_signatures_none() { - let signatures = &[]; - let fn_ptrs = &mut []; - let dispatch_table = fake_dispatch_table(signatures, fn_ptrs); - - assert_eq!(dispatch_table.prototypes().len(), 0); - } - - #[test] - fn test_dispatch_table_signatures_some() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototypes = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - let dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); - - let result = dispatch_table.prototypes(); - assert_eq!(result.len(), prototypes.len()); - for (lhs, rhs) in result.iter().zip(prototypes.iter()) { - assert_eq!(lhs.name(), rhs.name()); - assert_eq!(lhs.signature.arg_types(), rhs.signature.arg_types()); - assert_eq!(lhs.signature.return_type(), rhs.signature.return_type()); - } - } - - #[test] - fn test_dispatch_table_get_ptr_unchecked() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototypes = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - - let dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); - assert_eq!(unsafe { dispatch_table.get_ptr_unchecked(0) }, fn_ptrs[0]); - } - - #[test] - fn test_dispatch_table_get_ptr_none() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototype = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - - let dispatch_table = fake_dispatch_table(prototype, fn_ptrs); - assert_eq!(dispatch_table.get_ptr(1), None); - } - - #[test] - fn test_dispatch_table_get_ptr_some() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototypes = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - - let dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); - assert_eq!(dispatch_table.get_ptr(0), Some(fn_ptrs[0])); - } - - #[test] - fn test_dispatch_table_get_ptr_unchecked_mut() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototypes = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - - let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); - assert_eq!( - unsafe { dispatch_table.get_ptr_unchecked_mut(0) }, - &mut fn_ptrs[0] - ); - } - - #[test] - fn test_dispatch_table_get_ptr_mut_none() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototypes = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - - let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); - assert_eq!(dispatch_table.get_ptr_mut(1), None); - } - - #[test] - fn test_dispatch_table_get_ptr_mut_some() { - let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); - let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); - - let return_type = Some(&type_info); - let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); - let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); - - let prototypes = &[fn_prototype]; - let fn_ptrs = &mut [ptr::null()]; - - let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); - assert_eq!(dispatch_table.get_ptr_mut(0), Some(&mut fn_ptrs[0])); - } - - fn fake_assembly_info( - symbols: ModuleInfo, - dispatch_table: DispatchTable, - dependencies: &[*const c_char], - ) -> AssemblyInfo { - AssemblyInfo { - symbols, - dispatch_table, - dependencies: dependencies.as_ptr(), - num_dependencies: dependencies.len() as u32, - } - } - - const FAKE_DEPENDENCY: &str = "path/to/dependency.dylib"; - - #[test] - fn test_assembly_info_dependencies() { - let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); - let module = fake_module_info(&module_path, &[], &[]); - - let dispatch_table = fake_dispatch_table(&[], &mut []); - - let dependency = CString::new(FAKE_DEPENDENCY).expect("Invalid fake dependency."); - let dependencies = &[dependency.as_ptr()]; - let assembly = fake_assembly_info(module, dispatch_table, dependencies); - - assert_eq!(assembly.dependencies().count(), dependencies.len()); - for (lhs, rhs) in assembly.dependencies().zip([FAKE_DEPENDENCY].iter()) { - assert_eq!(lhs, *rhs) - } - } -} diff --git a/crates/mun_abi/src/dispatch_table.rs b/crates/mun_abi/src/dispatch_table.rs new file mode 100644 index 000000000..339ff72f6 --- /dev/null +++ b/crates/mun_abi/src/dispatch_table.rs @@ -0,0 +1,312 @@ +use crate::FunctionPrototype; +use std::{ffi::c_void, slice}; + +/// Represents a function dispatch table. This is used for runtime linking. +/// +/// Function signatures and pointers are stored separately for cache efficiency. +#[repr(C)] +pub struct DispatchTable { + /// Function signatures + pub(crate) prototypes: *const FunctionPrototype, + /// Function pointers + pub(crate) fn_ptrs: *mut *const c_void, + /// Number of functions + pub num_entries: u32, +} + +impl DispatchTable { + /// Returns an iterator over pairs of mutable function pointers and signatures. + pub fn iter_mut(&mut self) -> impl Iterator { + if self.num_entries == 0 { + (&mut []).iter_mut().zip((&[]).iter()) + } else { + let ptrs = + unsafe { slice::from_raw_parts_mut(self.fn_ptrs, self.num_entries as usize) }; + let signatures = + unsafe { slice::from_raw_parts(self.prototypes, self.num_entries as usize) }; + + ptrs.iter_mut().zip(signatures.iter()) + } + } + + /// Returns an iterator over pairs of function pointers and signatures. + pub fn iter(&self) -> impl Iterator { + if self.num_entries == 0 { + (&[]).iter().zip((&[]).iter()) + } else { + let ptrs = + unsafe { slice::from_raw_parts_mut(self.fn_ptrs, self.num_entries as usize) }; + let signatures = + unsafe { slice::from_raw_parts(self.prototypes, self.num_entries as usize) }; + + ptrs.iter().zip(signatures.iter()) + } + } + + /// Returns mutable functions pointers. + pub fn ptrs_mut(&mut self) -> &mut [*const c_void] { + if self.num_entries == 0 { + &mut [] + } else { + unsafe { slice::from_raw_parts_mut(self.fn_ptrs, self.num_entries as usize) } + } + } + + /// Returns function prototypes. + pub fn prototypes(&self) -> &[FunctionPrototype] { + if self.num_entries == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(self.prototypes, self.num_entries as usize) } + } + } + + /// Returns a function pointer, without doing bounds checking. + /// + /// This is generally not recommended, use with caution! Calling this method with an + /// out-of-bounds index is _undefined behavior_ even if the resulting reference is not used. + /// For a safe alternative see [get_ptr](#method.get_ptr). + /// + /// # Safety + /// + /// The `idx` is not bounds checked and should therefor be used with care. + pub unsafe fn get_ptr_unchecked(&self, idx: u32) -> *const c_void { + *self.fn_ptrs.offset(idx as isize) + } + + /// Returns a function pointer at the given index, or `None` if out of bounds. + pub fn get_ptr(&self, idx: u32) -> Option<*const c_void> { + if idx < self.num_entries { + Some(unsafe { self.get_ptr_unchecked(idx) }) + } else { + None + } + } + + /// Returns a mutable reference to a function pointer, without doing bounds checking. + /// + /// This is generally not recommended, use with caution! Calling this method with an + /// out-of-bounds index is _undefined behavior_ even if the resulting reference is not used. + /// For a safe alternative see [get_ptr_mut](#method.get_ptr_mut). + /// + /// # Safety + /// + /// The `idx` is not bounds checked and should therefor be used with care. + pub unsafe fn get_ptr_unchecked_mut(&mut self, idx: u32) -> &mut *const c_void { + &mut *self.fn_ptrs.offset(idx as isize) + } + + /// Returns a mutable reference to a function pointer at the given index, or `None` if out of + /// bounds. + pub fn get_ptr_mut(&mut self, idx: u32) -> Option<&mut *const c_void> { + if idx < self.num_entries { + Some(unsafe { self.get_ptr_unchecked_mut(idx) }) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + test_utils::{ + fake_dispatch_table, fake_fn_prototype, fake_type_info, FAKE_FN_NAME, FAKE_TYPE_NAME, + }, + TypeGroup, + }; + use std::{ffi::CString, ptr}; + + #[test] + fn test_dispatch_table_iter_mut_none() { + let signatures = &[]; + let fn_ptrs = &mut []; + let mut dispatch_table = fake_dispatch_table(signatures, fn_ptrs); + + let iter = fn_ptrs.iter_mut().zip(signatures.iter()); + assert_eq!(dispatch_table.iter_mut().count(), iter.count()); + } + + #[test] + fn test_dispatch_table_iter_mut_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototypes = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); + + let iter = fn_ptrs.iter_mut().zip(prototypes.iter()); + assert_eq!(dispatch_table.iter_mut().count(), iter.len()); + + for (lhs, rhs) in dispatch_table.iter_mut().zip(iter) { + assert_eq!(lhs.0, rhs.0); + assert_eq!(lhs.1.name(), rhs.1.name()); + assert_eq!(lhs.1.signature.arg_types(), rhs.1.signature.arg_types()); + assert_eq!(lhs.1.signature.return_type(), rhs.1.signature.return_type()); + } + } + + #[test] + fn test_dispatch_table_ptrs_mut_none() { + let signatures = &[]; + let fn_ptrs = &mut []; + let mut dispatch_table = fake_dispatch_table(signatures, fn_ptrs); + + assert_eq!(dispatch_table.ptrs_mut().len(), 0); + } + + #[test] + fn test_dispatch_table_ptrs_mut_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototypes = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); + + let result = dispatch_table.ptrs_mut(); + assert_eq!(result.len(), fn_ptrs.len()); + for (lhs, rhs) in result.iter().zip(fn_ptrs.iter()) { + assert_eq!(lhs, rhs); + } + } + + #[test] + fn test_dispatch_table_signatures_none() { + let signatures = &[]; + let fn_ptrs = &mut []; + let dispatch_table = fake_dispatch_table(signatures, fn_ptrs); + + assert_eq!(dispatch_table.prototypes().len(), 0); + } + + #[test] + fn test_dispatch_table_signatures_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototypes = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + let dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); + + let result = dispatch_table.prototypes(); + assert_eq!(result.len(), prototypes.len()); + for (lhs, rhs) in result.iter().zip(prototypes.iter()) { + assert_eq!(lhs.name(), rhs.name()); + assert_eq!(lhs.signature.arg_types(), rhs.signature.arg_types()); + assert_eq!(lhs.signature.return_type(), rhs.signature.return_type()); + } + } + + #[test] + fn test_dispatch_table_get_ptr_unchecked() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototypes = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + + let dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); + assert_eq!(unsafe { dispatch_table.get_ptr_unchecked(0) }, fn_ptrs[0]); + } + + #[test] + fn test_dispatch_table_get_ptr_none() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototype = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + + let dispatch_table = fake_dispatch_table(prototype, fn_ptrs); + assert_eq!(dispatch_table.get_ptr(1), None); + } + + #[test] + fn test_dispatch_table_get_ptr_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototypes = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + + let dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); + assert_eq!(dispatch_table.get_ptr(0), Some(fn_ptrs[0])); + } + + #[test] + fn test_dispatch_table_get_ptr_unchecked_mut() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototypes = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + + let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); + assert_eq!( + unsafe { dispatch_table.get_ptr_unchecked_mut(0) }, + &mut fn_ptrs[0] + ); + } + + #[test] + fn test_dispatch_table_get_ptr_mut_none() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototypes = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + + let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); + assert_eq!(dispatch_table.get_ptr_mut(1), None); + } + + #[test] + fn test_dispatch_table_get_ptr_mut_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let prototypes = &[fn_prototype]; + let fn_ptrs = &mut [ptr::null()]; + + let mut dispatch_table = fake_dispatch_table(prototypes, fn_ptrs); + assert_eq!(dispatch_table.get_ptr_mut(0), Some(&mut fn_ptrs[0])); + } +} diff --git a/crates/mun_abi/src/function_info.rs b/crates/mun_abi/src/function_info.rs index 700ce824d..5d5dbb2fb 100644 --- a/crates/mun_abi/src/function_info.rs +++ b/crates/mun_abi/src/function_info.rs @@ -1,7 +1,46 @@ -use crate::{ - FunctionDefinition, FunctionPrototype, FunctionSignature, HasStaticTypeInfo, TypeInfo, +use crate::{HasStaticTypeInfo, TypeInfo}; +use std::{ + ffi::{c_void, CStr, CString}, + fmt::{self, Formatter}, + os::raw::c_char, + ptr, slice, str, }; -use std::{ffi::CString, ptr}; + +/// Represents a function definition. A function definition contains the name, type signature, and +/// a pointer to the implementation. +/// +/// `fn_ptr` can be used to call the declared function. +#[repr(C)] +#[derive(Clone)] +pub struct FunctionDefinition { + /// Function prototype + pub prototype: FunctionPrototype, + /// Function pointer + pub fn_ptr: *const c_void, +} + +/// Represents a function prototype. A function prototype contains the name, type signature, but +/// not an implementation. +#[repr(C)] +#[derive(Clone)] +pub struct FunctionPrototype { + /// Function name + pub name: *const c_char, + /// The type signature of the function + pub signature: FunctionSignature, +} + +/// Represents a function signature. +#[repr(C)] +#[derive(Clone)] +pub struct FunctionSignature { + /// Argument types + pub(crate) arg_types: *const *const TypeInfo, + /// Optional return type + pub(crate) return_type: *const TypeInfo, + /// Number of argument types + pub num_arg_types: u16, +} /// Owned storage for C-style `FunctionDefinition`. pub struct FunctionDefinitionStorage { @@ -9,6 +48,91 @@ pub struct FunctionDefinitionStorage { _type_infos: Vec<&'static TypeInfo>, } +unsafe impl Send for FunctionDefinition {} +unsafe impl Sync for FunctionDefinition {} + +impl FunctionPrototype { + /// Returns the function's name. + pub fn name(&self) -> &str { + unsafe { str::from_utf8_unchecked(CStr::from_ptr(self.name).to_bytes()) } + } +} + +impl fmt::Display for FunctionPrototype { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "fn {}(", self.name())?; + for (i, arg) in self.signature.arg_types().iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + write!(f, ")")?; + if let Some(ret_type) = self.signature.return_type() { + write!(f, ":{}", ret_type)? + } + Ok(()) + } +} + +unsafe impl Send for FunctionPrototype {} +unsafe impl Sync for FunctionPrototype {} + +impl FunctionSignature { + /// Returns the function's arguments' types. + pub fn arg_types(&self) -> &[&TypeInfo] { + if self.num_arg_types == 0 { + &[] + } else { + unsafe { + slice::from_raw_parts( + self.arg_types.cast::<&TypeInfo>(), + self.num_arg_types as usize, + ) + } + } + } + + /// Returns the function's return type + pub fn return_type(&self) -> Option<&TypeInfo> { + unsafe { self.return_type.as_ref() } + } +} + +impl fmt::Display for FunctionSignature { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "fn(")?; + for (i, arg) in self.arg_types().iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + write!(f, ")")?; + if let Some(ret_type) = self.return_type() { + write!(f, ":{}", ret_type)? + } + Ok(()) + } +} + +impl PartialEq for FunctionSignature { + fn eq(&self, other: &Self) -> bool { + self.return_type() == other.return_type() + && self.arg_types().len() == other.arg_types().len() + && self + .arg_types() + .iter() + .zip(other.arg_types().iter()) + .all(|(a, b)| PartialEq::eq(a, b)) + } +} + +impl Eq for FunctionSignature {} + +unsafe impl Send for FunctionSignature {} +unsafe impl Sync for FunctionSignature {} + impl FunctionDefinitionStorage { /// Constructs a new `FunctionDefinition`, the data of which is stored in a /// `FunctionDefinitionStorage`. @@ -16,7 +140,7 @@ impl FunctionDefinitionStorage { name: &str, args: &[&'static TypeInfo], ret: Option<&'static TypeInfo>, - fn_ptr: *const std::ffi::c_void, + fn_ptr: *const c_void, ) -> (FunctionDefinition, FunctionDefinitionStorage) { let name = CString::new(name).unwrap(); let type_infos: Vec<&'static TypeInfo> = args.iter().copied().collect(); @@ -102,3 +226,60 @@ into_function_info_impl! { extern "C" fn(A, B, C, D, E, F, G, H, I) -> R; extern "C" fn(A, B, C, D, E, F, G, H, I, J) -> R; } + +#[cfg(test)] +mod tests { + use crate::{ + test_utils::{ + fake_fn_prototype, fake_fn_signature, fake_type_info, FAKE_FN_NAME, FAKE_TYPE_NAME, + }, + TypeGroup, + }; + use std::ffi::CString; + + #[test] + fn test_fn_prototype_name() { + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_signature = fake_fn_prototype(&fn_name, &[], None); + + assert_eq!(fn_signature.name(), FAKE_FN_NAME); + } + + #[test] + fn test_fn_signature_arg_types_none() { + let arg_types = &[]; + let fn_signature = fake_fn_signature(arg_types, None); + + assert_eq!(fn_signature.arg_types(), arg_types); + } + + #[test] + fn test_fn_signature_arg_types_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let arg_types = &[&type_info]; + let fn_signature = fake_fn_signature(arg_types, None); + + assert_eq!(fn_signature.arg_types(), arg_types); + } + + #[test] + fn test_fn_signature_return_type_none() { + let return_type = None; + let fn_signature = fake_fn_signature(&[], return_type); + + assert_eq!(fn_signature.return_type(), return_type); + } + + #[test] + fn test_fn_signature_return_type_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_signature = fake_fn_signature(&[], return_type); + + assert_eq!(fn_signature.return_type(), return_type); + } +} diff --git a/crates/mun_abi/src/lib.rs b/crates/mun_abi/src/lib.rs index 08a817170..fb7c0df14 100644 --- a/crates/mun_abi/src/lib.rs +++ b/crates/mun_abi/src/lib.rs @@ -4,30 +4,38 @@ //! Runtime. #![warn(missing_docs)] -// Bindings can be manually generated by running `cargo gen-abi`. -mod autogen; -mod autogen_impl; +// C bindings can be manually generated by running `cargo gen-abi`. +mod assembly_info; +mod dispatch_table; mod function_info; +mod module_info; mod static_type_map; +mod struct_info; mod type_info; -pub use autogen::*; -pub use function_info::{FunctionDefinitionStorage, IntoFunctionDefinition}; -pub use type_info::HasStaticTypeInfo; +#[cfg(test)] +mod test_utils; + +pub use assembly_info::AssemblyInfo; +pub use dispatch_table::DispatchTable; +pub use function_info::{ + FunctionDefinition, FunctionDefinitionStorage, FunctionPrototype, FunctionSignature, + IntoFunctionDefinition, +}; +pub use module_info::ModuleInfo; +pub use struct_info::{StructInfo, StructMemoryKind}; +pub use type_info::{HasStaticTypeInfo, TypeGroup, TypeInfo}; /// The Mun ABI prelude /// /// The *prelude* contains imports that are used almost every time. pub mod prelude { - pub use crate::autogen::*; - pub use crate::{ - HasStaticTypeInfo, IntoFunctionDefinition, Privacy, StructMemoryKind, TypeGroup, - }; + pub use crate::{HasStaticTypeInfo, IntoFunctionDefinition, StructMemoryKind, TypeGroup}; } -/// Defines the current abi version +/// Defines the current ABI version #[allow(clippy::zero_prefixed_literal)] -pub const ABI_VERSION: u32 = 00_02_00; +pub const ABI_VERSION: u32 = 00_03_00; /// Defines the name for the `get_info` function pub const GET_INFO_FN_NAME: &str = "get_info"; /// Defines the name for the `get_version` function @@ -35,35 +43,10 @@ pub const GET_VERSION_FN_NAME: &str = "get_version"; /// Defines the name for the `set_allocator_handle` function pub const SET_ALLOCATOR_HANDLE_FN_NAME: &str = "set_allocator_handle"; -/// Represents the kind of memory management a struct uses. -#[repr(u8)] -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum StructMemoryKind { - /// A garbage collected struct is allocated on the heap and uses reference semantics when passed - /// around. - GC, - - /// A value struct is allocated on the stack and uses value semantics when passed around. - /// - /// NOTE: When a value struct is used in an external API, a wrapper is created that _pins_ the - /// value on the heap. The heap-allocated value needs to be *manually deallocated*! - Value, -} - -impl Default for StructMemoryKind { - fn default() -> Self { - StructMemoryKind::GC - } -} - -impl From for u64 { - fn from(kind: StructMemoryKind) -> Self { - match kind { - StructMemoryKind::GC => 0, - StructMemoryKind::Value => 1, - } - } -} +/// Represents a globally unique identifier (GUID). +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Guid(pub [u8; 16]); /// Represents the privacy level of modules, functions, or variables. #[repr(u8)] @@ -75,30 +58,4 @@ pub enum Privacy { Private = 1, } -/// Represents a group of types that illicit the same characteristics. -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum TypeGroup { - /// Fundamental types (i.e. `()`, `bool`, `float`, `int`, etc.) - FundamentalTypes = 0, - /// Struct types (i.e. record, tuple, or unit structs) - StructTypes = 1, -} - -impl TypeGroup { - /// Returns whether this is a fundamental type. - pub fn is_fundamental(self) -> bool { - match self { - TypeGroup::FundamentalTypes => true, - _ => false, - } - } - - /// Returns whether this is a struct type. - pub fn is_struct(self) -> bool { - match self { - TypeGroup::StructTypes => true, - _ => false, - } - } -} +// TODO: Fix leakage of pointer types in struct fields due to integration tests and test utils diff --git a/crates/mun_abi/src/module_info.rs b/crates/mun_abi/src/module_info.rs new file mode 100644 index 000000000..7fb1ef34d --- /dev/null +++ b/crates/mun_abi/src/module_info.rs @@ -0,0 +1,145 @@ +use crate::{FunctionDefinition, TypeInfo}; +use std::{ffi::CStr, os::raw::c_char, slice, str}; + +/// Represents a module declaration. +#[repr(C)] +pub struct ModuleInfo { + /// Module path + pub(crate) path: *const c_char, + /// Module functions + pub(crate) functions: *const FunctionDefinition, + /// Module types + pub(crate) types: *const *const TypeInfo, + /// Number of module functions + pub num_functions: u32, + /// Number of module types + pub num_types: u32, +} + +impl ModuleInfo { + /// Returns the module's full path. + pub fn path(&self) -> &str { + unsafe { str::from_utf8_unchecked(CStr::from_ptr(self.path).to_bytes()) } + } + + // /// Finds the type's fields that match `filter`. + // pub fn find_fields(&self, filter: fn(&&FieldInfo) -> bool) -> impl Iterator { + // self.fields.iter().map(|f| *f).filter(filter) + // } + + // /// Retrieves the type's field with the specified `name`, if it exists. + // pub fn get_field(&self, name: &str) -> Option<&FieldInfo> { + // self.fields.iter().find(|f| f.name == name).map(|f| *f) + // } + + // /// Retrieves the type's fields. + // pub fn get_fields(&self) -> impl Iterator { + // self.fields.iter().map(|f| *f) + // } + + /// Returns the module's functions. + pub fn functions(&self) -> &[FunctionDefinition] { + if self.num_functions == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(self.functions, self.num_functions as usize) } + } + } + + /// Returns the module's types. + pub fn types(&self) -> &[&TypeInfo] { + if self.num_types == 0 { + &[] + } else { + unsafe { + slice::from_raw_parts(self.types.cast::<&TypeInfo>(), self.num_types as usize) + } + } + } +} + +unsafe impl Send for ModuleInfo {} +unsafe impl Sync for ModuleInfo {} + +#[cfg(test)] +mod tests { + use crate::{ + test_utils::{ + fake_fn_prototype, fake_module_info, fake_struct_info, fake_struct_type_info, + fake_type_info, FAKE_FN_NAME, FAKE_MODULE_PATH, FAKE_STRUCT_NAME, FAKE_TYPE_NAME, + }, + FunctionDefinition, TypeGroup, TypeInfo, + }; + use std::{ffi::CString, mem, ptr}; + + #[test] + fn test_module_info_path() { + let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); + let module = fake_module_info(&module_path, &[], &[]); + + assert_eq!(module.path(), FAKE_MODULE_PATH); + } + + #[test] + fn test_module_info_types_none() { + let functions = &[]; + let types = &[]; + let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); + let module = fake_module_info(&module_path, functions, types); + + assert_eq!(module.functions().len(), functions.len()); + assert_eq!(module.types().len(), types.len()); + } + + #[test] + fn test_module_info_types_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let return_type = Some(&type_info); + let fn_name = CString::new(FAKE_FN_NAME).expect("Invalid fake fn name."); + let fn_prototype = fake_fn_prototype(&fn_name, &[], return_type); + + let fn_info = FunctionDefinition { + prototype: fn_prototype, + fn_ptr: ptr::null(), + }; + let functions = &[fn_info]; + + let struct_name = CString::new(FAKE_STRUCT_NAME).expect("Invalid fake struct name"); + let struct_info = fake_struct_info(&[], &[], &[], Default::default()); + let struct_type_info = fake_struct_type_info(&struct_name, struct_info, 1, 1); + let types = &[unsafe { mem::transmute(&struct_type_info) }]; + + let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); + let module = fake_module_info(&module_path, functions, types); + + let result_functions = module.functions(); + assert_eq!(result_functions.len(), functions.len()); + for (lhs, rhs) in result_functions.iter().zip(functions.iter()) { + assert_eq!(lhs.fn_ptr, rhs.fn_ptr); + assert_eq!(lhs.prototype.name(), rhs.prototype.name()); + assert_eq!( + lhs.prototype.signature.arg_types(), + rhs.prototype.signature.arg_types() + ); + assert_eq!( + lhs.prototype.signature.return_type(), + rhs.prototype.signature.return_type() + ); + } + + let result_types: &[&TypeInfo] = module.types(); + assert_eq!(result_types.len(), types.len()); + for (lhs, rhs) in result_types.iter().zip(types.iter()) { + assert_eq!(lhs, rhs); + assert_eq!(lhs.name(), rhs.name()); + assert_eq!(lhs.group, rhs.group); + if lhs.group == TypeGroup::StructTypes { + let lhs_struct = lhs.as_struct().unwrap(); + let rhs_struct = rhs.as_struct().unwrap(); + assert_eq!(lhs_struct.field_types(), rhs_struct.field_types()); + } + } + } +} diff --git a/crates/mun_abi/src/struct_info.rs b/crates/mun_abi/src/struct_info.rs new file mode 100644 index 000000000..f618e377b --- /dev/null +++ b/crates/mun_abi/src/struct_info.rs @@ -0,0 +1,170 @@ +use crate::TypeInfo; +use std::{ffi::CStr, os::raw::c_char, slice, str}; + +/// Represents a struct declaration. +#[repr(C)] +pub struct StructInfo { + /// Struct fields' names + pub field_names: *const *const c_char, + /// Struct fields' information + pub(crate) field_types: *const *const TypeInfo, + /// Struct fields' offsets + pub(crate) field_offsets: *const u16, + // TODO: Field accessibility levels + // const MunPrivacy_t *field_privacies, + /// Number of fields + pub(crate) num_fields: u16, + // TODO: Add struct accessibility level + /// Struct memory kind + pub memory_kind: StructMemoryKind, +} + +/// Represents the kind of memory management a struct uses. +#[repr(u8)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StructMemoryKind { + /// A garbage collected struct is allocated on the heap and uses reference semantics when passed + /// around. + GC, + + /// A value struct is allocated on the stack and uses value semantics when passed around. + /// + /// NOTE: When a value struct is used in an external API, a wrapper is created that _pins_ the + /// value on the heap. The heap-allocated value needs to be *manually deallocated*! + Value, +} + +impl StructInfo { + /// Returns the struct's field names. + pub fn field_names(&self) -> impl Iterator { + let field_names = if self.num_fields == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(self.field_names, self.num_fields as usize) } + }; + + field_names + .iter() + .map(|n| unsafe { str::from_utf8_unchecked(CStr::from_ptr(*n).to_bytes()) }) + } + + /// Returns the struct's field types. + pub fn field_types(&self) -> &[&TypeInfo] { + if self.num_fields == 0 { + &[] + } else { + unsafe { + slice::from_raw_parts( + self.field_types.cast::<&TypeInfo>(), + self.num_fields as usize, + ) + } + } + } + + /// Returns the struct's field offsets. + pub fn field_offsets(&self) -> &[u16] { + if self.num_fields == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(self.field_offsets, self.num_fields as usize) } + } + } + + /// Returns the number of struct fields. + pub fn num_fields(&self) -> usize { + self.num_fields.into() + } + + /// Returns the index of the field matching the specified `field_name`. + pub fn find_field_index( + type_name: &str, + struct_info: &StructInfo, + field_name: &str, + ) -> Result { + struct_info + .field_names() + .enumerate() + .find(|(_, name)| *name == field_name) + .map(|(idx, _)| idx) + .ok_or_else(|| { + format!( + "Struct `{}` does not contain field `{}`.", + type_name, field_name + ) + }) + } +} + +impl Default for StructMemoryKind { + fn default() -> Self { + StructMemoryKind::GC + } +} + +impl From for u64 { + fn from(kind: StructMemoryKind) -> Self { + match kind { + StructMemoryKind::GC => 0, + StructMemoryKind::Value => 1, + } + } +} + +#[cfg(test)] +mod tests { + use super::StructMemoryKind; + use crate::{ + test_utils::{fake_struct_info, fake_type_info, FAKE_FIELD_NAME, FAKE_TYPE_NAME}, + TypeGroup, + }; + use std::ffi::CString; + + #[test] + fn test_struct_info_fields_none() { + let field_names = &[]; + let field_types = &[]; + let field_offsets = &[]; + let struct_info = + fake_struct_info(field_names, field_types, field_offsets, Default::default()); + + assert_eq!(struct_info.field_names().count(), 0); + assert_eq!(struct_info.field_types(), field_types); + assert_eq!(struct_info.field_offsets(), field_offsets); + } + + #[test] + fn test_struct_info_fields_some() { + let field_name = CString::new(FAKE_FIELD_NAME).expect("Invalid fake field name."); + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + let field_names = &[field_name.as_ptr()]; + let field_types = &[&type_info]; + let field_offsets = &[1]; + let struct_info = + fake_struct_info(field_names, field_types, field_offsets, Default::default()); + + for (lhs, rhs) in struct_info.field_names().zip([FAKE_FIELD_NAME].iter()) { + assert_eq!(lhs, *rhs) + } + assert_eq!(struct_info.field_types(), field_types); + assert_eq!(struct_info.field_offsets(), field_offsets); + } + + #[test] + fn test_struct_info_memory_kind_gc() { + let struct_memory_kind = StructMemoryKind::GC; + let struct_info = fake_struct_info(&[], &[], &[], struct_memory_kind.clone()); + + assert_eq!(struct_info.memory_kind, struct_memory_kind); + } + + #[test] + fn test_struct_info_memory_kind_value() { + let struct_memory_kind = StructMemoryKind::Value; + let struct_info = fake_struct_info(&[], &[], &[], struct_memory_kind.clone()); + + assert_eq!(struct_info.memory_kind, struct_memory_kind); + } +} diff --git a/crates/mun_abi/src/test_utils.rs b/crates/mun_abi/src/test_utils.rs new file mode 100644 index 000000000..e87c4243e --- /dev/null +++ b/crates/mun_abi/src/test_utils.rs @@ -0,0 +1,126 @@ +use crate::{ + AssemblyInfo, DispatchTable, FunctionDefinition, FunctionPrototype, FunctionSignature, Guid, + ModuleInfo, StructInfo, StructMemoryKind, TypeGroup, TypeInfo, +}; +use std::{ + ffi::{c_void, CStr}, + os::raw::c_char, + ptr, +}; + +pub(crate) const FAKE_TYPE_GUID: Guid = + Guid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); +pub(crate) const FAKE_DEPENDENCY: &str = "path/to/dependency.munlib"; +pub(crate) const FAKE_FIELD_NAME: &str = "field_name"; +pub(crate) const FAKE_FN_NAME: &str = "fn_name"; +pub(crate) const FAKE_MODULE_PATH: &str = "path::to::module"; +pub(crate) const FAKE_STRUCT_NAME: &str = "StructName"; +pub(crate) const FAKE_TYPE_NAME: &str = "TypeName"; + +/// A dummy struct for initializing a struct's `TypeInfo` +pub(crate) struct StructTypeInfo { + _type_info: TypeInfo, + _struct_info: StructInfo, +} + +pub(crate) fn fake_assembly_info( + symbols: ModuleInfo, + dispatch_table: DispatchTable, + dependencies: &[*const c_char], +) -> AssemblyInfo { + AssemblyInfo { + symbols, + dispatch_table, + dependencies: dependencies.as_ptr(), + num_dependencies: dependencies.len() as u32, + } +} + +pub(crate) fn fake_dispatch_table( + fn_prototypes: &[FunctionPrototype], + fn_ptrs: &mut [*const c_void], +) -> DispatchTable { + assert_eq!(fn_prototypes.len(), fn_ptrs.len()); + + DispatchTable { + prototypes: fn_prototypes.as_ptr(), + fn_ptrs: fn_ptrs.as_mut_ptr(), + num_entries: fn_prototypes.len() as u32, + } +} + +pub(crate) fn fake_fn_signature( + arg_types: &[&TypeInfo], + return_type: Option<&TypeInfo>, +) -> FunctionSignature { + FunctionSignature { + arg_types: arg_types.as_ptr().cast::<*const TypeInfo>(), + return_type: return_type.map_or(ptr::null(), |t| t as *const TypeInfo), + num_arg_types: arg_types.len() as u16, + } +} + +pub(crate) fn fake_fn_prototype( + name: &CStr, + arg_types: &[&TypeInfo], + return_type: Option<&TypeInfo>, +) -> FunctionPrototype { + FunctionPrototype { + name: name.as_ptr(), + signature: fake_fn_signature(arg_types, return_type), + } +} + +pub(crate) fn fake_module_info( + path: &CStr, + functions: &[FunctionDefinition], + types: &[&TypeInfo], +) -> ModuleInfo { + ModuleInfo { + path: path.as_ptr(), + functions: functions.as_ptr(), + num_functions: functions.len() as u32, + types: types.as_ptr().cast::<*const TypeInfo>(), + num_types: types.len() as u32, + } +} + +pub(crate) fn fake_struct_info( + field_names: &[*const c_char], + field_types: &[&TypeInfo], + field_offsets: &[u16], + memory_kind: StructMemoryKind, +) -> StructInfo { + assert!(field_names.len() == field_types.len()); + assert!(field_types.len() == field_offsets.len()); + + StructInfo { + field_names: field_names.as_ptr(), + field_types: field_types.as_ptr().cast::<*const TypeInfo>(), + field_offsets: field_offsets.as_ptr(), + num_fields: field_names.len() as u16, + memory_kind, + } +} + +pub(crate) fn fake_struct_type_info( + name: &CStr, + struct_info: StructInfo, + size: u32, + alignment: u8, +) -> StructTypeInfo { + StructTypeInfo { + _type_info: fake_type_info(name, TypeGroup::StructTypes, size, alignment), + _struct_info: struct_info, + } +} + +pub(crate) fn fake_type_info(name: &CStr, group: TypeGroup, size: u32, alignment: u8) -> TypeInfo { + TypeInfo { + guid: FAKE_TYPE_GUID, + name: name.as_ptr(), + size_in_bits: size, + alignment, + group, + } +} diff --git a/crates/mun_abi/src/type_info.rs b/crates/mun_abi/src/type_info.rs index 666205595..9c683bf43 100644 --- a/crates/mun_abi/src/type_info.rs +++ b/crates/mun_abi/src/type_info.rs @@ -1,8 +1,123 @@ -use crate::{static_type_map::StaticTypeMap, Guid, TypeGroup, TypeInfo}; +use crate::{static_type_map::StaticTypeMap, Guid, StructInfo}; use once_cell::sync::OnceCell; -use std::convert::TryInto; -use std::ffi::{CStr, CString}; -use std::sync::Once; +use std::{ + convert::TryInto, + ffi::{CStr, CString}, + fmt::{self, Formatter}, + mem, + os::raw::c_char, + str, + sync::Once, +}; + +/// Represents the type declaration for a value type. +/// +/// TODO: add support for polymorphism, enumerations, type parameters, generic type definitions, and +/// constructed generic types. +#[repr(C)] +#[derive(Debug)] +pub struct TypeInfo { + /// Type GUID + pub guid: Guid, + /// Type name + pub name: *const c_char, + /// The exact size of the type in bits without any padding + pub(crate) size_in_bits: u32, + /// The alignment of the type + pub(crate) alignment: u8, + /// Type group + pub group: TypeGroup, +} + +/// Represents a group of types that illicit the same characteristics. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum TypeGroup { + /// Fundamental types (i.e. `()`, `bool`, `float`, `int`, etc.) + FundamentalTypes = 0, + /// Struct types (i.e. record, tuple, or unit structs) + StructTypes = 1, +} + +impl TypeInfo { + /// Returns the type's name. + pub fn name(&self) -> &str { + unsafe { str::from_utf8_unchecked(CStr::from_ptr(self.name).to_bytes()) } + } + + /// Retrieves the type's struct information, if available. + pub fn as_struct(&self) -> Option<&StructInfo> { + if self.group.is_struct() { + let ptr = (self as *const TypeInfo).cast::(); + let ptr = ptr.wrapping_add(mem::size_of::()); + let offset = ptr.align_offset(mem::align_of::()); + let ptr = ptr.wrapping_add(offset); + Some(unsafe { &*ptr.cast::() }) + } else { + None + } + } + + /// Returns the size of the type in bits + pub fn size_in_bits(&self) -> usize { + self.size_in_bits + .try_into() + .expect("cannot convert size in bits to platform size") + } + + /// Returns the size of the type in bytes + pub fn size_in_bytes(&self) -> usize { + ((self.size_in_bits + 7) / 8) + .try_into() + .expect("cannot covert size in bytes to platform size") + } + + /// Returns the alignment of the type in bytes + pub fn alignment(&self) -> usize { + self.alignment + .try_into() + .expect("cannot convert alignment to platform size") + } +} + +impl fmt::Display for TypeInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + +impl PartialEq for TypeInfo { + fn eq(&self, other: &Self) -> bool { + self.guid == other.guid + } +} + +impl std::hash::Hash for TypeInfo { + fn hash(&self, state: &mut H) { + self.guid.hash(state); + } +} + +unsafe impl Send for TypeInfo {} +unsafe impl Sync for TypeInfo {} + +impl TypeGroup { + /// Returns whether this is a fundamental type. + pub fn is_fundamental(self) -> bool { + match self { + TypeGroup::FundamentalTypes => true, + _ => false, + } + } + + /// Returns whether this is a struct type. + pub fn is_struct(self) -> bool { + match self { + TypeGroup::StructTypes => true, + _ => false, + } + } +} /// A trait that defines that for a type we can statically return a `TypeInfo`. pub trait HasStaticTypeInfo { @@ -41,9 +156,7 @@ impl HasStaticTypeInfo for *const T { &map.call_once::(|| { let name = CString::new(format!("*const {}", T::type_name().to_str().unwrap())).unwrap(); - let guid = Guid { - b: md5::compute(&name.as_bytes()).0, - }; + let guid = Guid(md5::compute(&name.as_bytes()).0); let name_ptr = name.as_ptr(); ( name, @@ -79,9 +192,7 @@ impl HasStaticTypeInfo for *mut T { &map.call_once::(|| { let name = CString::new(format!("*mut {}", T::type_name().to_str().unwrap())).unwrap(); - let guid = Guid { - b: md5::compute(&name.as_bytes()).0, - }; + let guid = Guid(md5::compute(&name.as_bytes()).0); let name_ptr = name.as_ptr(); ( name, @@ -116,7 +227,7 @@ macro_rules! impl_basic_type_info { .get_or_init(|| CString::new(format!("core::{}", stringify!($ty))).unwrap()); TypeInfo { - guid: Guid{ b: md5::compute(&type_info_name.as_bytes()).0 }, + guid: Guid(md5::compute(&type_info_name.as_bytes()).0), name: type_info_name.as_ptr(), group: TypeGroup::FundamentalTypes, size_in_bits: (std::mem::size_of::<$ty>() * 8) @@ -187,10 +298,60 @@ impl HasStaticTypeInfo for isize { #[cfg(test)] mod tests { - use super::HasStaticTypeInfoName; + use super::{HasStaticTypeInfoName, TypeGroup}; + use crate::test_utils::{fake_type_info, FAKE_TYPE_NAME}; + use std::ffi::CString; + + #[test] + fn test_type_info_name() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + assert_eq!(type_info.name(), FAKE_TYPE_NAME); + } + + #[test] + fn test_type_info_size_alignment() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 24, 8); + + assert_eq!(type_info.size_in_bits(), 24); + assert_eq!(type_info.size_in_bytes(), 3); + assert_eq!(type_info.alignment(), 8); + } + + #[test] + fn test_type_info_group_fundamental() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_group = TypeGroup::FundamentalTypes; + let type_info = fake_type_info(&type_name, type_group, 1, 1); + + assert_eq!(type_info.group, type_group); + assert!(type_info.group.is_fundamental()); + assert!(!type_info.group.is_struct()); + } + + #[test] + fn test_type_info_group_struct() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_group = TypeGroup::StructTypes; + let type_info = fake_type_info(&type_name, type_group, 1, 1); + + assert_eq!(type_info.group, type_group); + assert!(type_info.group.is_struct()); + assert!(!type_info.group.is_fundamental()); + } + + #[test] + fn test_type_info_eq() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name, TypeGroup::FundamentalTypes, 1, 1); + + assert_eq!(type_info, type_info); + } #[test] - fn ptr_test() { + fn test_ptr() { let ty = <*const std::ffi::c_void>::type_name(); assert_eq!(ty.to_str().unwrap(), "*const core::void"); diff --git a/crates/mun_codegen/Cargo.toml b/crates/mun_codegen/Cargo.toml index f0e448e11..c4150806b 100644 --- a/crates/mun_codegen/Cargo.toml +++ b/crates/mun_codegen/Cargo.toml @@ -34,7 +34,11 @@ rev = "4448bcd" features = ["llvm7-0"] [dev-dependencies] +abi = { path = "../mun_abi", package = "mun_abi" } insta = "0.16" +libloader = { path = "../mun_libloader", package = "mun_libloader" } +mun_test = { path = "../mun_test" } +runtime = { path = "../mun_runtime", package = "mun_runtime" } [build-dependencies] semver = "0.9.0" diff --git a/crates/mun_codegen/src/ir/types.rs b/crates/mun_codegen/src/ir/types.rs index 4e0cd0d19..47d22ed19 100644 --- a/crates/mun_codegen/src/ir/types.rs +++ b/crates/mun_codegen/src/ir/types.rs @@ -1,11 +1,11 @@ use crate::value::{AsValue, IrValueContext, SizedValueType, TransparentValue, Value}; -use mun_codegen_macros::{AsValue, TestIsAbiCompatible}; +use mun_codegen_macros::AsValue; impl TransparentValue for abi::Guid { type Target = [u8; 16]; fn as_target_value(&self, context: &IrValueContext) -> Value { - self.b.as_value(context) + self.0.as_value(context) } } @@ -33,9 +33,8 @@ impl TransparentValue for abi::StructMemoryKind { } } -#[derive(AsValue, TestIsAbiCompatible)] +#[derive(AsValue)] #[ir_name = "struct.MunTypeInfo"] -#[abi_type(abi::TypeInfo)] pub struct TypeInfo { pub guid: abi::Guid, pub name: Value<*const u8>, @@ -44,34 +43,30 @@ pub struct TypeInfo { pub group: abi::TypeGroup, } -#[derive(AsValue, TestIsAbiCompatible)] +#[derive(AsValue)] #[ir_name = "struct.MunFunctionSignature"] -#[abi_type(abi::FunctionSignature)] pub struct FunctionSignature { pub arg_types: Value<*const *const TypeInfo>, pub return_type: Value<*const TypeInfo>, pub num_arg_types: u16, } -#[derive(AsValue, TestIsAbiCompatible)] +#[derive(AsValue)] #[ir_name = "struct.MunFunctionPrototype"] -#[abi_type(abi::FunctionPrototype)] pub struct FunctionPrototype { pub name: Value<*const u8>, pub signature: FunctionSignature, } -#[derive(AsValue, TestIsAbiCompatible)] +#[derive(AsValue)] #[ir_name = "struct.MunFunctionDefinition"] -#[abi_type(abi::FunctionDefinition)] pub struct FunctionDefinition { pub prototype: FunctionPrototype, pub fn_ptr: Value<*const fn()>, } -#[derive(AsValue, TestIsAbiCompatible)] +#[derive(AsValue)] #[ir_name = "struct.MunStructInfo"] -#[abi_type(abi::StructInfo)] pub struct StructInfo { pub field_names: Value<*const *const u8>, pub field_types: Value<*const *const TypeInfo>, @@ -80,35 +75,29 @@ pub struct StructInfo { pub memory_kind: abi::StructMemoryKind, } -#[derive(AsValue, TestIsAbiCompatible)] +#[derive(AsValue)] #[ir_name = "struct.MunModuleInfo"] -#[abi_type(abi::ModuleInfo)] pub struct ModuleInfo { pub path: Value<*const u8>, pub functions: Value<*const FunctionDefinition>, - pub num_functions: u32, pub types: Value<*const *const TypeInfo>, + pub num_functions: u32, pub num_types: u32, } -#[derive(AsValue, TestIsAbiCompatible)] +#[derive(AsValue)] #[ir_name = "struct.MunDispatchTable"] -#[abi_type(abi::DispatchTable)] pub struct DispatchTable { pub prototypes: Value<*const FunctionPrototype>, pub fn_ptrs: Value<*mut *const fn()>, pub num_entries: u32, } -#[derive(AsValue, TestIsAbiCompatible)] +#[derive(AsValue)] #[ir_name = "struct.MunAssemblyInfo"] -#[abi_type(abi::AssemblyInfo)] pub struct AssemblyInfo { pub symbols: ModuleInfo, pub dispatch_table: DispatchTable, pub dependencies: Value<*const *const u8>, pub num_dependencies: u32, } - -#[cfg(test)] -mod test; diff --git a/crates/mun_codegen/src/ir/types/test.rs b/crates/mun_codegen/src/ir/types/test.rs deleted file mode 100644 index acd223780..000000000 --- a/crates/mun_codegen/src/ir/types/test.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::value::{ConcreteValueType, Value}; - -/// A trait implemented by a type to check if its values match its corresponding abi type (T). This -/// trait can be derived. e.g.: -/// -/// ```ignore,rust -/// #[derive(AsValue, TestIsAbiCompatible)] -/// #[ir_name = "struct.MunTypeInfo"] -/// #[abi_type(abi::TypeInfo)] -/// pub struct TypeInfo { } -/// ``` -/// -/// The procedural macro calls for every field: -/// -/// ```ignore,rust -/// self::test::AbiTypeHelper::from_value(&abi_value.) -/// .ir_type::<>() -/// .assert_compatible(, , ); -/// ``` -pub trait TestIsAbiCompatible { - /// Runs a test to see if the implementor is compatible with the specified abi type. - fn test(abi_value: &T); -} - -/// A trait that is implemented if a type is ABI compatible with `T`. -pub trait IsAbiCompatible {} - -/// A trait indicating that a type is *not* compatible. This is a helper trait used to determine -/// if a type is ABI compatible at runtime. By default this trait is implemented for all types. -pub trait IsNotAbiCompatible { - fn assert_compatible(&self, ir_type: &str, abi_type: &str, field_name: &str) { - panic!( - "the field '{}' on type '{}' is not compatible with the ABI version of the struct ({})", - field_name, ir_type, abi_type - ); - } -} -impl IsNotAbiCompatible for T {} - -/// Helper structs that allows extracting the type of a value. -pub struct AbiTypeHelper(std::marker::PhantomData); -pub struct AbiAndIrTypeHelper(std::marker::PhantomData, std::marker::PhantomData); - -impl AbiTypeHelper { - pub fn from_value(_: &T) -> Self { - Self(Default::default()) - } - - pub fn ir_type(&self) -> AbiAndIrTypeHelper { - AbiAndIrTypeHelper(Default::default(), Default::default()) - } -} - -impl> AbiAndIrTypeHelper { - pub fn assert_compatible(&self, _ir_type: &str, _abi_type: &str, _field_name: &str) {} -} - -impl IsAbiCompatible for u8 {} -impl IsAbiCompatible for u16 {} -impl IsAbiCompatible for u32 {} -impl IsAbiCompatible for abi::Guid {} -impl IsAbiCompatible for abi::TypeGroup {} -impl IsAbiCompatible for abi::StructMemoryKind {} -impl IsAbiCompatible<*const ::std::os::raw::c_char> for *const u8 {} -impl IsAbiCompatible<*const ::std::os::raw::c_void> for *const fn() {} -impl> IsAbiCompatible<*const S> for *const T {} -impl> IsAbiCompatible<*mut S> for *mut T {} -impl IsAbiCompatible for Value where T: IsAbiCompatible {} - -#[test] -#[cfg(test)] -fn test_type_info_abi_compatible() { - let abi_type = abi::TypeInfo { - guid: abi::Guid { - b: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - }, - name: std::ptr::null(), - size_in_bits: 0, - alignment: 0, - group: abi::TypeGroup::FundamentalTypes, - }; - - super::TypeInfo::test(&abi_type); -} - -#[test] -#[cfg(test)] -fn test_function_signature_abi_compatible() { - let abi_type = abi::FunctionSignature { - arg_types: std::ptr::null(), - return_type: std::ptr::null(), - num_arg_types: 0, - }; - - super::FunctionSignature::test(&abi_type); -} - -#[test] -#[cfg(test)] -fn test_function_prototype_abi_compatible() { - let abi_type = abi::FunctionPrototype { - name: std::ptr::null(), - signature: abi::FunctionSignature { - arg_types: std::ptr::null(), - return_type: std::ptr::null(), - num_arg_types: 0, - }, - }; - - super::FunctionPrototype::test(&abi_type); -} - -#[test] -#[cfg(test)] -fn test_function_definition_abi_compatible() { - let abi_type = abi::FunctionDefinition { - prototype: abi::FunctionPrototype { - name: std::ptr::null(), - signature: abi::FunctionSignature { - arg_types: std::ptr::null(), - return_type: std::ptr::null(), - num_arg_types: 0, - }, - }, - fn_ptr: std::ptr::null(), - }; - - super::FunctionDefinition::test(&abi_type); -} - -#[test] -#[cfg(test)] -fn test_struct_info_abi_compatible() { - let abi_type = abi::StructInfo { - field_names: std::ptr::null(), - field_types: std::ptr::null(), - field_offsets: std::ptr::null(), - num_fields: 0, - memory_kind: abi::StructMemoryKind::Value, - }; - - super::StructInfo::test(&abi_type); -} - -#[test] -#[cfg(test)] -fn test_module_info_abi_compatible() { - let abi_type = abi::ModuleInfo { - path: std::ptr::null(), - functions: std::ptr::null(), - num_functions: 0, - types: std::ptr::null(), - num_types: 0, - }; - - super::ModuleInfo::test(&abi_type); -} - -#[test] -#[cfg(test)] -fn test_dispatch_table_abi_compatible() { - let abi_type = abi::DispatchTable { - prototypes: std::ptr::null(), - fn_ptrs: std::ptr::null_mut(), - num_entries: 0, - }; - - super::DispatchTable::test(&abi_type); -} - -#[test] -#[cfg(test)] -fn test_assembly_info_abi_compatible() { - let abi_type = abi::AssemblyInfo { - symbols: abi::ModuleInfo { - path: std::ptr::null(), - functions: std::ptr::null(), - num_functions: 0, - types: std::ptr::null(), - num_types: 0, - }, - dispatch_table: abi::DispatchTable { - prototypes: std::ptr::null(), - fn_ptrs: std::ptr::null_mut(), - num_entries: 0, - }, - dependencies: std::ptr::null(), - num_dependencies: 0, - }; - - super::AssemblyInfo::test(&abi_type); -} diff --git a/crates/mun_codegen/src/type_info.rs b/crates/mun_codegen/src/type_info.rs index 60344b2b8..3f2c69f21 100644 --- a/crates/mun_codegen/src/type_info.rs +++ b/crates/mun_codegen/src/type_info.rs @@ -66,7 +66,7 @@ pub struct TypeInfo { impl Hash for TypeInfo { fn hash(&self, state: &mut H) { - state.write(&self.guid.b) + state.write(&self.guid.0) } } @@ -92,9 +92,7 @@ impl TypeInfo { pub fn new_fundamental>(name: S, type_size: TypeSize) -> TypeInfo { TypeInfo { name: name.as_ref().to_string(), - guid: Guid { - b: md5::compute(name.as_ref()).0, - }, + guid: Guid(md5::compute(name.as_ref()).0), group: TypeGroup::FundamentalTypes, size: type_size, } @@ -122,9 +120,7 @@ impl TypeInfo { ) }; Self { - guid: Guid { - b: md5::compute(&guid_string).0, - }, + guid: Guid(md5::compute(&guid_string).0), name, group: TypeGroup::StructTypes(s), size: type_size, diff --git a/crates/mun_codegen/tests/abi.rs b/crates/mun_codegen/tests/abi.rs new file mode 100644 index 000000000..6f4d6c804 --- /dev/null +++ b/crates/mun_codegen/tests/abi.rs @@ -0,0 +1,165 @@ +use abi::{StructMemoryKind, TypeGroup, ABI_VERSION}; +use libloader::MunLibrary; +use mun_test::CompileTestDriver; +use runtime::ReturnTypeReflection; +use std::mem; + +// TODO: add integration test for ModuleInfo's path +#[test] +fn test_abi_compatibility() { + let fn_name = "foo"; + let fn_name2 = "bar"; + let struct_name = "Foo"; + let struct_name2 = "Bar"; + let driver = CompileTestDriver::new(&format!( + r#" + pub fn {fn_name}(_: f64) -> i32 {{ 0 }} + pub fn {fn_name2}() {{ }} + + pub struct {struct_name}(f64, f64); + pub struct(value) {struct_name2} {{ a: i32, b: i32 }}; + "#, + fn_name = fn_name, + fn_name2 = fn_name2, + struct_name = struct_name, + struct_name2 = struct_name2, + )); + + // Assert that all library functions are exposed + let lib = MunLibrary::new(driver.lib_path()).expect("Failed to load generated Mun library."); + + assert_eq!(ABI_VERSION, lib.get_abi_version()); + + let lib_info = lib.get_info(); + + // Dependency compatibility + assert_eq!(lib_info.num_dependencies, 0); + // TODO: verify dependencies ABI + + assert_eq!(lib_info.dispatch_table.num_entries, 0); + // TODO: verify dispatch table ABI + + let module_info = &lib_info.symbols; + assert_eq!(module_info.path(), ""); + assert_eq!(module_info.num_functions, 2); + + let fn_def = get_function_info(module_info, fn_name); + test_function_args(fn_def, &[(f64::type_name(), f64::type_guid())]); + test_function_return_type_some::(fn_def); + + let fn_def2 = get_function_info(module_info, fn_name2); + test_function_args(fn_def2, &[]); + test_function_return_type_none(fn_def2); + + struct Foo(f64, f64); + test_struct_info::( + &lib_info.symbols, + struct_name, + &["0", "1"], + StructMemoryKind::GC, + ); + + struct Bar { + _a: i32, + _b: i32, + }; + test_struct_info::( + &lib_info.symbols, + struct_name2, + &["a", "b"], + StructMemoryKind::Value, + ); + + fn get_function_info<'m>( + module_info: &'m abi::ModuleInfo, + fn_name: &str, + ) -> &'m abi::FunctionDefinition { + module_info + .functions() + .iter() + .find(|f| f.prototype.name() == fn_name) + .expect(&format!( + "Failed to retrieve function definition '{}'", + fn_name + )) + } + + fn test_function_args(fn_def: &abi::FunctionDefinition, args: &[(&str, abi::Guid)]) { + assert_eq!( + usize::from(fn_def.prototype.signature.num_arg_types), + args.len() + ); + + for (idx, (arg_name, arg_guid)) in args.iter().enumerate() { + let fn_arg_type = fn_def + .prototype + .signature + .arg_types() + .get(idx) + .expect(&format!( + "Function '{}' should have an argument.", + fn_def.prototype.name() + )); + + assert_eq!(fn_arg_type.guid, *arg_guid); + assert_eq!(fn_arg_type.name(), *arg_name); + } + } + + #[allow(dead_code)] + fn test_function_return_type_none(fn_def: &abi::FunctionDefinition) { + assert!( + fn_def.prototype.signature.return_type().is_none(), + format!( + "Function '{}' should not have a return type.", + fn_def.prototype.name(), + ) + ); + } + + fn test_function_return_type_some(fn_def: &abi::FunctionDefinition) { + let fn_return_type = fn_def.prototype.signature.return_type().expect(&format!( + "Function '{}' should have a return type.", + fn_def.prototype.name() + )); + assert_eq!(fn_return_type.guid, R::type_guid()); + assert_eq!(fn_return_type.name(), R::type_name()); + } + + fn test_struct_info( + module_info: &abi::ModuleInfo, + struct_name: &str, + field_names: &[&str], + memory_kind: StructMemoryKind, + ) { + let type_info = module_info + .types() + .iter() + .find(|ty| ty.name() == struct_name) + .expect(&format!("Failed to retrieve struct '{}'", struct_name)); + + assert_eq!(type_info.name(), struct_name); + assert_eq!(type_info.size_in_bits(), 8 * mem::size_of::()); + assert_eq!(type_info.size_in_bytes(), mem::size_of::()); + assert_eq!(type_info.alignment(), mem::align_of::()); + assert_eq!(type_info.group, TypeGroup::StructTypes); + + let struct_info = type_info.as_struct().expect("Expected a struct"); + assert_eq!(struct_info.num_fields(), field_names.len()); + for (lhs, rhs) in struct_info.field_names().zip(field_names) { + assert_eq!(lhs, *rhs); + } + for field_type in struct_info.field_types().iter() { + assert_eq!(field_type.guid, F::type_guid()); + assert_eq!(field_type.name(), F::type_name()); + } + + let mut offset = 0; + for field_offset in struct_info.field_offsets() { + assert_eq!(usize::from(*field_offset), offset); + offset += mem::size_of::(); + } + + assert_eq!(struct_info.memory_kind, memory_kind); + } +} diff --git a/crates/mun_memory/tests/diff/util.rs b/crates/mun_memory/tests/diff/util.rs index 5cc2f080e..495855585 100644 --- a/crates/mun_memory/tests/diff/util.rs +++ b/crates/mun_memory/tests/diff/util.rs @@ -6,17 +6,13 @@ use mun_memory::{ use std::alloc::Layout; pub const STRUCT1_NAME: &str = "struct1"; -pub const STRUCT1_GUID: abi::Guid = abi::Guid { - b: [ - 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, - ], -}; +pub const STRUCT1_GUID: abi::Guid = abi::Guid([ + 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, +]); pub const STRUCT2_NAME: &str = "struct2"; -pub const STRUCT2_GUID: abi::Guid = abi::Guid { - b: [ - 150, 140, 130, 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0, - ], -}; +pub const STRUCT2_GUID: abi::Guid = abi::Guid([ + 150, 140, 130, 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0, +]); #[derive(Clone, Debug, Eq, PartialEq)] pub struct StructInfo { diff --git a/crates/mun_runtime/src/adt.rs b/crates/mun_runtime/src/adt.rs index eec6123f2..551f2126f 100644 --- a/crates/mun_runtime/src/adt.rs +++ b/crates/mun_runtime/src/adt.rs @@ -207,9 +207,7 @@ impl<'r> ReturnTypeReflection for StructRef<'r> { fn type_guid() -> abi::Guid { // TODO: Once `const_fn` lands, replace this with a const md5 hash static GUID: OnceCell = OnceCell::new(); - *GUID.get_or_init(|| abi::Guid { - b: md5::compute(::type_name()).0, - }) + *GUID.get_or_init(|| abi::Guid(md5::compute(::type_name()).0)) } } diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index b6f3e359e..316c2da86 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -262,6 +262,19 @@ impl Runtime { self.dispatch_table.get_fn(function_name) } + /// Retrieves the type definition corresponding to `type_name`, if available. + pub fn get_type_info(&self, type_name: &str) -> Option<&abi::TypeInfo> { + for assembly in self.assemblies.values() { + for type_info in assembly.info().symbols.types().iter() { + if type_info.name() == type_name { + return Some(type_info); + } + } + } + + None + } + /// Updates the state of the runtime. This includes checking for file changes, and reloading /// compiled assemblies. pub fn update(&mut self) -> bool { diff --git a/crates/mun_runtime/src/reflection.rs b/crates/mun_runtime/src/reflection.rs index 1f7b351fe..f5650c260 100644 --- a/crates/mun_runtime/src/reflection.rs +++ b/crates/mun_runtime/src/reflection.rs @@ -128,9 +128,7 @@ impl ReturnTypeReflection for () { fn type_guid() -> abi::Guid { // TODO: Once `const_fn` lands, replace this with a const md5 hash static GUID: OnceCell = OnceCell::new(); - *GUID.get_or_init(|| abi::Guid { - b: md5::compute(Self::type_name()).0, - }) + *GUID.get_or_init(|| abi::Guid(md5::compute(Self::type_name()).0)) } } impl<'t> Marshal<'t> for () { diff --git a/crates/mun_runtime/tests/marshalling.rs b/crates/mun_runtime/tests/marshalling.rs index 4e223eb7c..3524ad5e6 100644 --- a/crates/mun_runtime/tests/marshalling.rs +++ b/crates/mun_runtime/tests/marshalling.rs @@ -273,8 +273,7 @@ fn compiler_valid_utf8() { ); if let Some(s) = arg_type.as_struct() { - let field_names = - unsafe { slice::from_raw_parts(s.field_names, s.num_fields as usize) }; + let field_names = unsafe { slice::from_raw_parts(s.field_names, s.num_fields()) }; for field_name in field_names { assert_eq!( @@ -285,9 +284,18 @@ fn compiler_valid_utf8() { } } assert_eq!( - unsafe { CStr::from_ptr((*foo_func.prototype.signature.return_type).name) } - .to_str() - .is_ok(), + unsafe { + CStr::from_ptr( + foo_func + .prototype + .signature + .return_type() + .expect("Missing return type") + .name, + ) + } + .to_str() + .is_ok(), true ); } diff --git a/crates/mun_runtime_capi/ffi b/crates/mun_runtime_capi/ffi index ac61e9902..966514e82 160000 --- a/crates/mun_runtime_capi/ffi +++ b/crates/mun_runtime_capi/ffi @@ -1 +1 @@ -Subproject commit ac61e99025ece3a8105bf95a7a1ff70506f74bd3 +Subproject commit 966514e820aaa7bbf9ad79faa5d3377f3a32d09a diff --git a/crates/tools/Cargo.toml b/crates/tools/Cargo.toml index cba9037eb..4f24fe857 100644 --- a/crates/tools/Cargo.toml +++ b/crates/tools/Cargo.toml @@ -10,5 +10,4 @@ clap = "2.32.0" anyhow = "1.0.31" ron = "0.4.2" cbindgen = "= 0.14.2" -bindgen = "0.54" difference = "2.0" diff --git a/crates/tools/src/abi.rs b/crates/tools/src/abi.rs index e4ab3654b..3ae577387 100644 --- a/crates/tools/src/abi.rs +++ b/crates/tools/src/abi.rs @@ -1,67 +1,16 @@ -use crate::{project_root, reformat, update, Result}; -use anyhow::anyhow; +use crate::{project_root, update, Result}; use teraron::Mode; pub const ABI_DIR: &str = "crates/mun_abi"; -use bindgen::{self, callbacks::EnumVariantValue, callbacks::ParseCallbacks}; - -#[derive(Debug)] -struct RemoveVendorName; - -impl ParseCallbacks for RemoveVendorName { - fn enum_variant_name( - &self, - enum_name: Option<&str>, - original_variant_name: &str, - _variant_value: EnumVariantValue, - ) -> Option { - Some( - original_variant_name - .trim_start_matches(enum_name.unwrap_or("")) - .to_string(), - ) - } - - fn item_name(&self, original_item_name: &str) -> Option { - if original_item_name == "MunPrivacy_t" { - Some("Privacy".to_string()) - } else if original_item_name == "MunTypeGroup_t" { - Some("TypeGroup".to_string()) - } else if original_item_name == "MunStructMemoryKind_t" { - Some("StructMemoryKind".to_string()) - } else { - Some(original_item_name.trim_start_matches("Mun").to_string()) - } - } -} - /// Generates the FFI bindings for the Mun ABI pub fn generate(mode: Mode) -> Result<()> { let crate_dir = project_root().join(ABI_DIR); - let output_file_path = crate_dir.join("src/autogen.rs"); - let input_file_path = crate_dir.join("c/include/mun_abi.h"); + let file_path = crate_dir.join("ffi/include/mun_abi.h"); - let input_file_str = input_file_path - .to_str() - .ok_or_else(|| anyhow!("could not create path to mun_abi.h"))?; - let bindings = bindgen::Builder::default() - .header(input_file_str) - .whitelist_type("Mun.*") - // Remove type aliasing on Linux - .blacklist_type("__uint8_t") - .blacklist_type("__uint16_t") - .blacklist_type("__uint32_t") - .blacklist_type("__uint64_t") - .parse_callbacks(Box::new(RemoveVendorName)) - // FIXME: Prevent double derivation of Copy and Debug attributes on Windows - .derive_copy(false) - .derive_debug(false) - .raw_line("#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]") - .raw_line("use crate::{StructMemoryKind, TypeGroup};") - .generate() - .map_err(|_| anyhow!("Unable to generate bindings from 'mun_abi.h'"))?; + let mut file_contents = Vec::::new(); + cbindgen::generate(crate_dir)?.write(&mut file_contents); - let file_contents = reformat(bindings.to_string())?; - update(&output_file_path, &file_contents, mode) + let file_contents = String::from_utf8(file_contents)?; + update(&file_path, &file_contents, mode) }