Skip to content

Commit

Permalink
feat(neon): Create functions from closures
Browse files Browse the repository at this point in the history
  • Loading branch information
kjvalencik committed Oct 20, 2021
1 parent ddf5888 commit fc0e01b
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 71 deletions.
10 changes: 10 additions & 0 deletions crates/neon-runtime/src/napi/bindings/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ mod napi4 {
#[cfg(feature = "napi-5")]
mod napi5 {
use super::super::types::*;
use std::ffi::c_void;

generate!(
extern "C" {
Expand All @@ -298,6 +299,15 @@ mod napi5 {
fn get_date_value(env: Env, value: Value, result: *mut f64) -> Status;

fn is_date(env: Env, value: Value, result: *mut bool) -> Status;

fn add_finalizer(
env: Env,
js_object: Value,
native_object: *mut c_void,
finalize_cb: Finalize,
finalize_hint: *mut c_void,
result: Ref,
) -> Status;
}
);
}
Expand Down
15 changes: 0 additions & 15 deletions crates/neon-runtime/src/napi/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,6 @@ use smallvec::{smallvec, SmallVec};
use crate::napi::bindings as napi;
use crate::raw::{Env, FunctionCallbackInfo, Local};

#[repr(C)]
pub struct CCallback {
pub static_callback: *mut c_void,
pub dynamic_callback: *mut c_void,
}

impl Default for CCallback {
fn default() -> Self {
CCallback {
static_callback: null_mut(),
dynamic_callback: null_mut(),
}
}
}

pub unsafe fn is_construct(env: Env, info: FunctionCallbackInfo) -> bool {
let mut target: MaybeUninit<Local> = MaybeUninit::zeroed();

Expand Down
78 changes: 65 additions & 13 deletions crates/neon-runtime/src/napi/fun.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,81 @@
//! Facilities for working with JS functions.
use std::mem::MaybeUninit;
use std::os::raw::c_void;
use std::ptr::null;
use std::ptr;

use crate::call::CCallback;
use crate::napi::bindings as napi;
use crate::raw::{Env, Local};

/// Mutates the `out` argument provided to refer to a newly created `v8::Function`. Returns
/// `false` if the value couldn't be created.
pub unsafe fn new(out: &mut Local, env: Env, callback: CCallback) -> bool {
pub unsafe fn new<F>(env: Env, name: &str, callback: F) -> Result<Local, napi::Status>
where
F: Fn(Env, napi::CallbackInfo) -> Local + Send + 'static,
{
let mut out = MaybeUninit::uninit();
let data = Box::into_raw(Box::new(callback));
let status = napi::create_function(
env,
null(),
0,
Some(std::mem::transmute(callback.static_callback)),
callback.dynamic_callback,
out as *mut Local,
name.as_ptr().cast(),
name.len(),
Some(call_boxed::<F>),
data.cast(),
out.as_mut_ptr(),
);

status == napi::Status::Ok
if status == napi::Status::PendingException {
Box::from_raw(data);

return Err(status);
}

assert_eq!(status, napi::Status::Ok);

let out = out.assume_init();

#[cfg(feature = "napi-5")]
{
unsafe extern "C" fn drop_function<F>(
_env: Env,
_finalize_data: *mut c_void,
finalize_hint: *mut c_void,
) {
Box::from_raw(finalize_hint.cast::<F>());
}

let status = napi::add_finalizer(
env,
out,
ptr::null_mut(),
Some(drop_function::<F>),
data.cast(),
ptr::null_mut(),
);

assert_eq!(status, napi::Status::Ok);
}

Ok(out)
}

pub unsafe fn get_dynamic_callback(_env: Env, data: *mut c_void) -> *mut c_void {
data
unsafe extern "C" fn call_boxed<F>(env: Env, info: napi::CallbackInfo) -> Local
where
F: Fn(Env, napi::CallbackInfo) -> Local + Send + 'static,
{
let mut data = MaybeUninit::uninit();
let status = napi::get_cb_info(
env,
info,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
data.as_mut_ptr(),
);

assert_eq!(status, napi::Status::Ok);

let callback = &*data.assume_init().cast::<F>();

callback(env, info)
}

pub unsafe fn call(
Expand Down
7 changes: 7 additions & 0 deletions src/context/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ pub struct Env(raw::Isolate);
#[derive(Clone, Copy)]
pub struct Env(raw::Env);

#[cfg(feature = "napi-1")]
impl From<raw::Env> for Env {
fn from(env: raw::Env) -> Self {
Self(env)
}
}

thread_local! {
#[allow(unused)]
pub(crate) static IS_RUNNING: RefCell<bool> = RefCell::new(false);
Expand Down
24 changes: 24 additions & 0 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ use std;
use std::cell::RefCell;
use std::convert::Into;
use std::marker::PhantomData;
#[cfg(feature = "legacy-runtime")]
use std::os::raw::c_void;
use std::panic::UnwindSafe;

Expand All @@ -198,6 +199,15 @@ pub(crate) struct CallbackInfo<'a> {
}

impl CallbackInfo<'_> {
#[cfg(feature = "napi-1")]
pub unsafe fn new(info: raw::FunctionCallbackInfo) -> Self {
Self {
info,
_lifetime: PhantomData,
}
}

#[cfg(feature = "legacy-runtime")]
pub fn data(&self, env: Env) -> *mut c_void {
unsafe {
let mut raw_data: *mut c_void = std::mem::zeroed();
Expand All @@ -206,6 +216,7 @@ impl CallbackInfo<'_> {
}
}

#[cfg(feature = "legacy-runtime")]
pub unsafe fn with_cx<T: This, U, F: for<'a> FnOnce(CallContext<'a, T>) -> U>(
&self,
env: Env,
Expand Down Expand Up @@ -694,6 +705,7 @@ impl<'a> ModuleContext<'a> {
Scope::with(env, |scope| f(ModuleContext { scope, exports }))
}

#[cfg(not(feature = "napi-5"))]
/// Convenience method for exporting a Neon function from a module.
pub fn export_function<T: Value>(
&mut self,
Expand All @@ -705,6 +717,18 @@ impl<'a> ModuleContext<'a> {
Ok(())
}

#[cfg(feature = "napi-5")]
/// Convenience method for exporting a Neon function from a module.
pub fn export_function<F, V>(&mut self, key: &str, f: F) -> NeonResult<()>
where
F: Fn(FunctionContext) -> JsResult<V> + Send + 'static,
V: Value,
{
let value = JsFunction::new(self, f)?.upcast::<JsValue>();
self.exports.set(self, key, value)?;
Ok(())
}

#[cfg(feature = "legacy-runtime")]
/// Convenience method for exporting a Neon class constructor from a module.
pub fn export_class<T: Class>(&mut self, key: &str) -> NeonResult<()> {
Expand Down
58 changes: 17 additions & 41 deletions src/types/internal.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use super::Value;
use crate::context::internal::Env;
use crate::context::{CallbackInfo, FunctionContext};
#[cfg(feature = "legacy-runtime")]
use crate::context::CallbackInfo;
#[cfg(feature = "legacy-runtime")]
use crate::context::FunctionContext;
#[cfg(feature = "legacy-runtime")]
use crate::result::JsResult;
use crate::types::error::convert_panics;
use crate::types::{Handle, JsObject, Managed};
use crate::types::{Handle, Managed};
use neon_runtime;
#[cfg(feature = "legacy-runtime")]
use neon_runtime::call::CCallback;
use neon_runtime::raw;
use std::mem;
#[cfg(feature = "legacy-runtime")]
use std::os::raw::c_void;

pub trait ValueInternal: Managed + 'static {
Expand All @@ -28,47 +32,24 @@ pub trait ValueInternal: Managed + 'static {
}
}

#[cfg(feature = "legacy-runtime")]
#[repr(C)]
pub struct FunctionCallback<T: Value>(pub fn(FunctionContext) -> JsResult<T>);

#[cfg(feature = "legacy-runtime")]
impl<T: Value> Callback<()> for FunctionCallback<T> {
extern "C" fn invoke(env: Env, info: CallbackInfo<'_>) {
unsafe {
info.with_cx::<JsObject, _, _>(env, |cx| {
let data = info.data(env);
let dynamic_callback: fn(FunctionContext) -> JsResult<T> =
mem::transmute(neon_runtime::fun::get_dynamic_callback(env.to_raw(), data));
if let Ok(value) = convert_panics(env, || dynamic_callback(cx)) {
info.set_return(value);
}
})
}
}
use crate::types::error::convert_panics;
use crate::types::JsObject;

fn into_ptr(self) -> *mut c_void {
self.0 as *mut _
}
}

#[cfg(feature = "napi-1")]
impl<T: Value> Callback<raw::Local> for FunctionCallback<T> {
extern "C" fn invoke(env: Env, info: CallbackInfo<'_>) -> raw::Local {
unsafe {
info.with_cx::<JsObject, _, _>(env, |cx| {
let data = info.data(env);
let dynamic_callback: fn(FunctionContext) -> JsResult<T> =
mem::transmute(neon_runtime::fun::get_dynamic_callback(env.to_raw(), data));
let dynamic_callback: fn(FunctionContext) -> JsResult<T> = std::mem::transmute(
neon_runtime::fun::get_dynamic_callback(env.to_raw(), data),
);
if let Ok(value) = convert_panics(env, || dynamic_callback(cx)) {
value.to_raw()
} else {
// We do not have a Js Value to return, most likely due to an exception.
// If we are in a throwing state, constructing a Js Value would be invalid.
// While not explicitly written, the N-API documentation includes many examples
// of returning `NULL` when a native function does not return a value.
// Note, `raw::Local` in this context is a type alias for `*mut napi_value` and not a struct
// https://nodejs.org/api/n-api.html#n_api_napi_create_function
std::ptr::null_mut()
info.set_return(value);
}
})
}
Expand All @@ -83,6 +64,7 @@ impl<T: Value> Callback<raw::Local> for FunctionCallback<T> {
/// This type makes it possible to export a dynamically computed Rust function
/// as a pair of 1) a raw pointer to the dynamically computed function, and 2)
/// a static function that knows how to transmute that raw pointer and call it.
#[cfg(feature = "legacy-runtime")]
pub(crate) trait Callback<T: Clone + Copy + Sized>: Sized {
/// Extracts the computed Rust function and invokes it. The Neon runtime
/// ensures that the computed function is provided as the extra data field,
Expand All @@ -91,8 +73,6 @@ pub(crate) trait Callback<T: Clone + Copy + Sized>: Sized {

/// See `invoke`. This is used by the non-n-api implementation, so that every impl for this
/// trait doesn't need to provide two versions of `invoke`.
#[cfg(feature = "legacy-runtime")]
#[doc(hidden)]
extern "C" fn invoke_compat(info: CallbackInfo<'_>) -> T {
Self::invoke(Env::current(), info)
}
Expand All @@ -103,12 +83,8 @@ pub(crate) trait Callback<T: Clone + Copy + Sized>: Sized {
/// Exports the callback as a pair consisting of the static `Self::invoke`
/// method and the computed callback, both converted to raw void pointers.
fn into_c_callback(self) -> CCallback {
#[cfg(feature = "napi-1")]
let invoke = Self::invoke;
#[cfg(feature = "legacy-runtime")]
let invoke = Self::invoke_compat;
CCallback {
static_callback: unsafe { mem::transmute(invoke as usize) },
static_callback: unsafe { std::mem::transmute(Self::invoke_compat as usize) },
dynamic_callback: self.into_ptr(),
}
}
Expand Down
Loading

0 comments on commit fc0e01b

Please sign in to comment.