diff --git a/rustler/src/codegen_runtime.rs b/rustler/src/codegen_runtime.rs index 027ba15b..5ed8f39b 100644 --- a/rustler/src/codegen_runtime.rs +++ b/rustler/src/codegen_runtime.rs @@ -6,13 +6,14 @@ use std::fmt; use crate::{Encoder, Env, OwnedBinary, Term}; // Re-export of inventory +pub use crate::resource::ResourceRegistration; pub use inventory; // Names used by the `rustler::init!` macro or other generated code. pub use crate::wrapper::exception::raise_exception; pub use crate::wrapper::{ c_char, c_int, c_uint, c_void, get_nif_resource_type_init_size, DEF_NIF_ENTRY, DEF_NIF_FUNC, - MUTABLE_NIF_RESOURCE_HANDLE, NIF_ENV, NIF_MAJOR_VERSION, NIF_MINOR_VERSION, NIF_TERM, + NIF_ENV, NIF_MAJOR_VERSION, NIF_MINOR_VERSION, NIF_TERM, }; #[cfg(windows)] diff --git a/rustler/src/lib.rs b/rustler/src/lib.rs index ba3ec239..1263fc12 100644 --- a/rustler/src/lib.rs +++ b/rustler/src/lib.rs @@ -45,8 +45,8 @@ pub use crate::types::{ #[cfg(feature = "big_integer")] pub use crate::types::BigInt; -pub mod resource; -pub use crate::resource::ResourceArc; +mod resource; +pub use crate::resource::{Monitor, MonitorResource, Resource, ResourceArc}; #[doc(hidden)] pub mod dynamic; diff --git a/rustler/src/resource.rs b/rustler/src/resource/handle.rs similarity index 51% rename from rustler/src/resource.rs rename to rustler/src/resource/handle.rs index ee6ebff0..2937187f 100644 --- a/rustler/src/resource.rs +++ b/rustler/src/resource/handle.rs @@ -1,152 +1,12 @@ -//! Support for storing Rust data in Erlang terms. -//! -//! A NIF resource allows you to safely store Rust structs in a term, and therefore keep it across -//! NIF calls. The struct will be automatically dropped when the BEAM GC decides that there are no -//! more references to the resource. - -use std::any::TypeId; -use std::collections::HashMap; use std::ops::Deref; use std::ptr; -use std::sync::OnceLock; -use std::{ffi::CString, mem}; - -use super::{Binary, Decoder, Encoder, Env, Error, NifResult, Term}; -use crate::resource::resource::open_resource_type; -pub use crate::wrapper::{ - c_void, resource, NifResourceFlags, MUTABLE_NIF_RESOURCE_HANDLE, NIF_ENV, NIF_RESOURCE_TYPE, -}; - -#[derive(Debug)] -pub struct ResourceRegistration { - name: &'static str, - get_type_id: fn() -> TypeId, - destructor: unsafe extern "C" fn(_env: NIF_ENV, handle: MUTABLE_NIF_RESOURCE_HANDLE), -} -inventory::collect!(ResourceRegistration); - -static mut RESOURCE_TYPES: OnceLock> = OnceLock::new(); - -fn get_resource_type() -> Option { - let map = unsafe { RESOURCE_TYPES.get()? }; - map.get(&TypeId::of::()) - .map(|ptr| *ptr as NIF_RESOURCE_TYPE) -} - -impl ResourceRegistration { - pub const fn new(name: &'static str) -> Self { - Self { - name, - destructor: resource_destructor::, - get_type_id: TypeId::of::, - } - } - - pub fn initialize(env: Env) { - for reg in inventory::iter::() { - reg.register(env); - } - } - - pub fn register(&self, env: Env) { - let res: Option = unsafe { - open_resource_type( - env.as_c_arg(), - CString::new(self.name).unwrap().as_bytes_with_nul(), - Some(self.destructor), - NIF_RESOURCE_FLAGS::ERL_NIF_RT_CREATE, - ) - }; - - let type_id = (self.get_type_id)(); - - unsafe { - RESOURCE_TYPES.get_or_init(Default::default); - RESOURCE_TYPES - .get_mut() - .unwrap() - .insert(type_id, res.unwrap() as usize); - } - } -} -/// Re-export a type used by the `resource!` macro. -#[doc(hidden)] -pub use crate::wrapper::NIF_RESOURCE_FLAGS; +use rustler_sys::c_void; -#[doc(hidden)] -pub trait ResourceType: Sized + Send + Sync + 'static { - fn get_resource_type() -> Option { - get_resource_type::() - } -} - -impl<'a> Term<'a> { - unsafe fn get_resource_ptrs(&self) -> Option<(*const c_void, *mut T)> { - let typ = T::get_resource_type()?; - let res = resource::get_resource(self.get_env().as_c_arg(), self.as_c_arg(), typ)?; - Some((res, align_alloced_mem_for_struct::(res) as *mut T)) - } - - pub fn get_resource(&self) -> Option<&'a T> { - unsafe { self.get_resource_ptrs().map(|(_, ptr)| &*ptr) } - } +use crate::{Binary, Decoder, Encoder, Env, Error, NifResult, Term}; - pub unsafe fn get_mut_resource(&self) -> Option<&'a mut T> { - self.get_resource_ptrs().map(|(_, ptr)| &mut *ptr) - } -} - -impl Encoder for ResourceArc -where - T: ResourceType, -{ - fn encode<'a>(&self, env: Env<'a>) -> Term<'a> { - self.as_term(env) - } -} -impl<'a, T> Decoder<'a> for ResourceArc -where - T: ResourceType + 'a, -{ - fn decode(term: Term<'a>) -> NifResult { - ResourceArc::from_term(term) - } -} - -impl<'a, T> Decoder<'a> for &'a T -where - T: ResourceType + 'a, -{ - fn decode(term: Term<'a>) -> NifResult { - term.get_resource().ok_or(Error::BadArg) - } -} - -/// Drop a T that lives in an Erlang resource. (erlang_nif-sys requires us to declare this -/// function safe, but it is of course thoroughly unsafe!) -pub unsafe extern "C" fn resource_destructor( - _env: NIF_ENV, - handle: MUTABLE_NIF_RESOURCE_HANDLE, -) { - unsafe { - let aligned = align_alloced_mem_for_struct::(handle); - let res = aligned as *mut T; - ptr::read(res); - } -} - -fn get_alloc_size_struct() -> usize { - mem::size_of::() + mem::align_of::() -} - -/// Given a pointer `ptr` to an allocation of `get_alloc_size_struct::()` bytes, return the -/// first aligned pointer within the allocation where a `T` may be stored. -/// Unsafe: `ptr` must point to a large enough allocation and not be null. -unsafe fn align_alloced_mem_for_struct(ptr: *const c_void) -> *const c_void { - let offset = mem::align_of::() - ((ptr as usize) % mem::align_of::()); - ptr.add(offset) -} +use super::traits::{Resource, ResourceExt}; +use super::util::{align_alloced_mem_for_struct, get_alloc_size_struct}; /// A reference to a resource of type `T`. /// @@ -159,19 +19,19 @@ unsafe fn align_alloced_mem_for_struct(ptr: *const c_void) -> *const c_void { /// convert back and forth between the two using `Encoder` and `Decoder`. pub struct ResourceArc where - T: ResourceType, + T: Resource, { raw: *const c_void, inner: *mut T, } // Safe because T is `Sync` and `Send`. -unsafe impl Send for ResourceArc where T: ResourceType {} -unsafe impl Sync for ResourceArc where T: ResourceType {} +unsafe impl Send for ResourceArc where T: Resource {} +unsafe impl Sync for ResourceArc where T: Resource {} impl ResourceArc where - T: ResourceType, + T: Resource, { /// Makes a new ResourceArc from the given type. Note that the type must have /// ResourceType implemented for it. See module documentation for info on this. @@ -255,7 +115,7 @@ where impl Deref for ResourceArc where - T: ResourceType, + T: Resource, { type Target = T; @@ -266,7 +126,7 @@ where impl Clone for ResourceArc where - T: ResourceType, + T: Resource, { /// Cloning a `ResourceArc` simply increments the reference count for the /// resource. The `T` value is not cloned. @@ -281,7 +141,7 @@ where impl Drop for ResourceArc where - T: ResourceType, + T: Resource, { /// When a `ResourceArc` is dropped, the reference count is decremented. If /// there are no other references to the resource, the `T` value is dropped. @@ -294,13 +154,19 @@ where } } -#[macro_export] -macro_rules! resource { - ($struct_name:ty, $env: ident) => {{ - impl $crate::resource::ResourceType for $struct_name {} - - let tuple = rustler::resource::ResourceRegistration::new::<$struct_name>( - stringify!(#name) - ).register($env); - }}; +impl Encoder for ResourceArc +where + T: Resource, +{ + fn encode<'a>(&self, env: Env<'a>) -> Term<'a> { + self.as_term(env) + } +} +impl<'a, T> Decoder<'a> for ResourceArc +where + T: Resource + 'a, +{ + fn decode(term: Term<'a>) -> NifResult { + ResourceArc::from_term(term) + } } diff --git a/rustler/src/resource/mod.rs b/rustler/src/resource/mod.rs new file mode 100644 index 00000000..91b724ca --- /dev/null +++ b/rustler/src/resource/mod.rs @@ -0,0 +1,71 @@ +//! Support for storing Rust data in Erlang terms. +//! +//! A NIF resource allows you to safely store Rust structs in a term, and therefore keep it across +//! NIF calls. The struct will be automatically dropped when the BEAM GC decides that there are no +//! more references to the resource. + +mod handle; +mod monitor; +mod registration; +mod traits; +mod util; + +use std::mem::MaybeUninit; + +use super::{Decoder, Error, NifResult, Term}; + +pub use handle::ResourceArc; +pub use monitor::Monitor; +pub use registration::ResourceRegistration; +use rustler_sys::c_void; +use traits::ResourceExt; +pub use traits::{MonitorResource, Resource}; +use util::align_alloced_mem_for_struct; + +impl<'a> Term<'a> { + unsafe fn get_resource_ptrs(&self) -> Option<(*const c_void, *mut T)> { + let typ = T::get_resource_type()?; + let mut ret_obj = MaybeUninit::uninit(); + let res = rustler_sys::enif_get_resource( + self.get_env().as_c_arg(), + self.as_c_arg(), + typ, + ret_obj.as_mut_ptr(), + ); + + if res == 0 { + None + } else { + let res = ret_obj.assume_init(); + Some((res, align_alloced_mem_for_struct::(res) as *mut T)) + } + } + + pub fn get_resource(&self) -> Option<&'a T> { + unsafe { self.get_resource_ptrs().map(|(_, ptr)| &*ptr) } + } + + pub unsafe fn get_mut_resource(&self) -> Option<&'a mut T> { + self.get_resource_ptrs().map(|(_, ptr)| &mut *ptr) + } +} + +impl<'a, T> Decoder<'a> for &'a T +where + T: Resource + 'a, +{ + fn decode(term: Term<'a>) -> NifResult { + term.get_resource().ok_or(Error::BadArg) + } +} + +#[macro_export] +macro_rules! resource { + ($struct_name:ty, $env: ident) => {{ + impl $crate::Resource for $struct_name {} + + let tuple = $crate::codegen_runtime::ResourceRegistration::new::<$struct_name>( + stringify!(#name) + ).register($env); + }}; +} diff --git a/rustler/src/resource/monitor.rs b/rustler/src/resource/monitor.rs new file mode 100644 index 00000000..14c634c2 --- /dev/null +++ b/rustler/src/resource/monitor.rs @@ -0,0 +1,22 @@ +use rustler_sys::ErlNifMonitor; + +#[derive(Clone)] +pub struct Monitor { + inner: ErlNifMonitor, +} + +impl Monitor { + pub fn as_c_arg(&self) -> &ErlNifMonitor { + &self.inner + } + + pub fn from_c_arg(erl_nif_mon: ErlNifMonitor) -> Self { + Monitor { inner: erl_nif_mon } + } +} + +impl PartialEq for Monitor { + fn eq(&self, other: &Self) -> bool { + unsafe { rustler_sys::enif_compare_monitors(&self.inner, &other.inner) == 0 } + } +} diff --git a/rustler/src/resource/registration.rs b/rustler/src/resource/registration.rs new file mode 100644 index 00000000..a87fe5bd --- /dev/null +++ b/rustler/src/resource/registration.rs @@ -0,0 +1,117 @@ +use super::traits; +use super::util::{align_alloced_mem_for_struct, as_ptr}; +use crate::{Env, LocalPid, Monitor, MonitorResource, Resource}; +use rustler_sys::{ + c_char, c_void, ErlNifEnv, ErlNifMonitor, ErlNifPid, ErlNifResourceDown, ErlNifResourceDtor, + ErlNifResourceFlags, ErlNifResourceType, ErlNifResourceTypeInit, +}; +use std::any::TypeId; +use std::ffi::CString; +use std::mem::MaybeUninit; +use std::ptr; + +#[derive(Debug)] +pub struct ResourceRegistration { + name: &'static str, + get_type_id: fn() -> TypeId, + init: ErlNifResourceTypeInit, +} + +unsafe impl Sync for ResourceRegistration {} + +inventory::collect!(ResourceRegistration); + +impl ResourceRegistration { + pub const fn new(name: &'static str) -> Self { + let init = ErlNifResourceTypeInit { + dtor: if std::mem::needs_drop::() { + resource_destructor:: as *const ErlNifResourceDtor + } else { + ptr::null() + }, + stop: ptr::null(), + down: ptr::null(), + members: 1, + dyncall: ptr::null(), + }; + Self { + name, + init, + get_type_id: TypeId::of::, + } + } + + pub const fn add_down_callback(self) -> Self { + Self { + init: ErlNifResourceTypeInit { + down: resource_down:: as *const ErlNifResourceDown, + ..self.init + }, + ..self + } + } + + pub fn initialize(env: Env) { + for reg in inventory::iter::() { + reg.register(env); + } + } + + pub fn register(&self, env: Env) { + let res: Option<*const ErlNifResourceType> = unsafe { + open_resource_type( + env.as_c_arg(), + CString::new(self.name).unwrap().as_bytes_with_nul(), + self.init, + ErlNifResourceFlags::ERL_NIF_RT_CREATE, + ) + }; + + let type_id = (self.get_type_id)(); + unsafe { traits::register_resource_type(type_id, res.unwrap()) } + } +} + +/// Drop a T that lives in an Erlang resource +unsafe extern "C" fn resource_destructor(_env: *mut ErlNifEnv, handle: *mut c_void) { + let aligned = align_alloced_mem_for_struct::(handle); + ptr::drop_in_place(aligned as *mut T); +} + +unsafe extern "C" fn resource_down( + env: *mut ErlNifEnv, + obj: *mut c_void, + pid: *const ErlNifPid, + mon: *const ErlNifMonitor, +) { + let env = Env::new(&env, env); + let aligned = align_alloced_mem_for_struct::(obj); + let res = &*(aligned as *const T); + let pid = LocalPid::from_c_arg(*pid); + let mon = Monitor::from_c_arg(*mon); + + res.down(env, pid, mon); +} + +pub unsafe fn open_resource_type( + env: *mut ErlNifEnv, + name: &[u8], + init: ErlNifResourceTypeInit, + flags: ErlNifResourceFlags, +) -> Option<*const ErlNifResourceType> { + // Panic if name is not null-terminated. + assert_eq!(name.last().cloned(), Some(0u8)); + + let name_p = name.as_ptr() as *const c_char; + + let res = { + let mut tried = MaybeUninit::uninit(); + rustler_sys::enif_open_resource_type_x(env, name_p, &init, flags, tried.as_mut_ptr()) + }; + + if res.is_null() { + None + } else { + Some(res) + } +} diff --git a/rustler/src/resource/traits.rs b/rustler/src/resource/traits.rs new file mode 100644 index 00000000..acacfa03 --- /dev/null +++ b/rustler/src/resource/traits.rs @@ -0,0 +1,34 @@ +use std::any::TypeId; +use std::collections::HashMap; +use std::sync::OnceLock; + +use crate::{Env, LocalPid, Monitor}; + +type NifResourcePtr = *const rustler_sys::ErlNifResourceType; + +static mut RESOURCE_TYPES: OnceLock> = OnceLock::new(); + +pub(crate) unsafe fn register_resource_type(type_id: TypeId, resource_type: NifResourcePtr) { + RESOURCE_TYPES.get_or_init(Default::default); + RESOURCE_TYPES + .get_mut() + .unwrap() + .insert(type_id, resource_type as usize); +} + +pub trait Resource: Sized + Send + Sync + 'static {} + +#[doc(hidden)] +pub(crate) trait ResourceExt: 'static { + fn get_resource_type() -> Option { + let map = unsafe { RESOURCE_TYPES.get()? }; + map.get(&TypeId::of::()) + .map(|ptr| *ptr as NifResourcePtr) + } +} + +impl ResourceExt for T {} + +pub trait MonitorResource: 'static { + fn down<'a>(&'a self, env: Env<'a>, pid: LocalPid, monitor: Monitor); +} diff --git a/rustler/src/resource/util.rs b/rustler/src/resource/util.rs new file mode 100644 index 00000000..3bde50f7 --- /dev/null +++ b/rustler/src/resource/util.rs @@ -0,0 +1,21 @@ +use rustler_sys::c_void; +use std::mem; + +pub fn get_alloc_size_struct() -> usize { + mem::size_of::() + mem::align_of::() +} + +/// Given a pointer `ptr` to an allocation of `get_alloc_size_struct::()` bytes, return the +/// first aligned pointer within the allocation where a `T` may be stored. +/// Unsafe: `ptr` must point to a large enough allocation and not be null. +pub unsafe fn align_alloced_mem_for_struct(ptr: *const c_void) -> *const c_void { + let offset = mem::align_of::() - ((ptr as usize) % mem::align_of::()); + ptr.add(offset) +} + +pub fn as_ptr(opt: Option) -> *const T { + match opt { + Some(t) => &t as *const T, + None => std::ptr::null(), + } +} diff --git a/rustler/src/wrapper.rs b/rustler/src/wrapper.rs index 4efac11d..1e5f8cda 100644 --- a/rustler/src/wrapper.rs +++ b/rustler/src/wrapper.rs @@ -15,7 +15,6 @@ pub mod exception; pub mod list; pub mod map; pub mod pid; -pub mod resource; pub mod term; pub mod tuple; @@ -37,20 +36,12 @@ pub fn get_nif_resource_type_init_size() -> usize { std::mem::size_of::() } -pub type NIF_RESOURCE_HANDLE = *const c_void; -pub type MUTABLE_NIF_RESOURCE_HANDLE = *mut c_void; - -pub type NifResourceDtor = - unsafe extern "C" fn(r_env: NIF_ENV, obj: MUTABLE_NIF_RESOURCE_HANDLE) -> (); -pub type NifResourceFlags = rustler_sys::ErlNifResourceFlags; - pub enum NIF_ERROR { BAD_ARG, } pub type DEF_NIF_FUNC = rustler_sys::ErlNifFunc; pub type DEF_NIF_ENTRY = rustler_sys::ErlNifEntry; -pub use rustler_sys::ErlNifResourceFlags as NIF_RESOURCE_FLAGS; pub use rustler_sys::NIF_MAJOR_VERSION; pub use rustler_sys::NIF_MINOR_VERSION; diff --git a/rustler/src/wrapper/resource.rs b/rustler/src/wrapper/resource.rs deleted file mode 100644 index 2975b966..00000000 --- a/rustler/src/wrapper/resource.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::wrapper::{ - NifResourceDtor, NifResourceFlags, NIF_ENV, NIF_RESOURCE_HANDLE, NIF_RESOURCE_TYPE, NIF_TERM, -}; - -use rustler_sys::c_char; - -use std::mem::MaybeUninit; -use std::ptr; - -pub unsafe fn open_resource_type( - env: NIF_ENV, - name: &[u8], - dtor: Option, - flags: NifResourceFlags, -) -> Option { - // Panic if name is not null-terminated. - assert_eq!(name.last().cloned(), Some(0u8)); - - // Currently unused as per erlang nif documentation - let module_p: *const c_char = ptr::null(); - let name_p = name.as_ptr() as *const c_char; - let res = { - let mut tried = MaybeUninit::uninit(); - rustler_sys::enif_open_resource_type(env, module_p, name_p, dtor, flags, tried.as_mut_ptr()) - }; - - if res.is_null() { - None - } else { - Some(res) - } -} - -// Functionally incomplete -pub unsafe fn get_resource( - env: NIF_ENV, - term: NIF_TERM, - typ: NIF_RESOURCE_TYPE, -) -> Option { - let mut ret_obj = MaybeUninit::uninit(); - let res = rustler_sys::enif_get_resource(env, term, typ, ret_obj.as_mut_ptr()); - - if res == 0 { - None - } else { - Some(ret_obj.assume_init()) - } -} diff --git a/rustler_codegen/src/init.rs b/rustler_codegen/src/init.rs index cfc803c2..d10771ab 100644 --- a/rustler_codegen/src/init.rs +++ b/rustler_codegen/src/init.rs @@ -104,7 +104,7 @@ impl From for proc_macro2::TokenStream { ) -> rustler::codegen_runtime::c_int { unsafe { let env = rustler::Env::new(&env, env); - rustler::resource::ResourceRegistration::initialize(env); + rustler::codegen_runtime::ResourceRegistration::initialize(env); // TODO: If an unwrap ever happens, we will unwind right into C! Fix this! let load_info = rustler::Term::new(env, load_info); rustler::codegen_runtime::handle_nif_init_call( diff --git a/rustler_codegen/src/resource.rs b/rustler_codegen/src/resource.rs index d87c1297..411e585b 100644 --- a/rustler_codegen/src/resource.rs +++ b/rustler_codegen/src/resource.rs @@ -9,9 +9,10 @@ pub fn transcoder_decorator(ast: &syn::DeriveInput) -> TokenStream { let name_s = name.to_string(); quote! { - impl rustler::resource::ResourceType for #name {} + impl rustler::Resource for #name {} + rustler::codegen_runtime::inventory::submit!( - rustler::resource::ResourceRegistration::new::<#name>( + rustler::codegen_runtime::ResourceRegistration::new::<#name>( #name_s ) ); diff --git a/rustler_sys/src/rustler_sys_api.rs b/rustler_sys/src/rustler_sys_api.rs index 1d7f3473..67086122 100644 --- a/rustler_sys/src/rustler_sys_api.rs +++ b/rustler_sys/src/rustler_sys_api.rs @@ -147,11 +147,11 @@ pub type ErlNifResourceDynCall = #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct ErlNifResourceTypeInit { - dtor: *const ErlNifResourceDtor, - stop: *const ErlNifResourceStop, // at ERL_NIF_SELECT_STOP event - down: *const ErlNifResourceDown, // enif_monitor_process - members: c_int, - dyncall: *const ErlNifResourceDynCall, + pub dtor: *const ErlNifResourceDtor, + pub stop: *const ErlNifResourceStop, // at ERL_NIF_SELECT_STOP event + pub down: *const ErlNifResourceDown, // enif_monitor_process + pub members: c_int, + pub dyncall: *const ErlNifResourceDynCall, } /// See [ErlNifSelectFlags](http://erlang.org/doc/man/erl_nif.html#ErlNifSelectFlags) in the Erlang docs.