Skip to content

Commit

Permalink
doc(neon-runtime): Add documentation from dynamic loading review comm…
Browse files Browse the repository at this point in the history
…ents
  • Loading branch information
kjvalencik committed Dec 2, 2020
1 parent 1c0a540 commit ccd508d
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 19 deletions.
2 changes: 1 addition & 1 deletion crates/neon-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ neon-sys = { version = "=0.5.3", path = "../neon-sys", optional = true }
smallvec = "1.4.2"

[dev-dependencies]
nodejs-sys = "0.7.0" # Dev dependency for easy copying
nodejs-sys = "0.7.0" # Not strictly needed; just here for easy manual copying

[features]
default = []
Expand Down
130 changes: 112 additions & 18 deletions crates/neon-runtime/src/napi/bindings/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,105 @@
//! # FFI bindings to N-API symbols
//!
//! These types are manually copied from bindings generated from `bindgen`. To
//! update, use the following approach:
//!
//! * Run `cargo test --manifest-path=crates/neon-runtime/Cargo.toml --features napi`
//! at least once to install `nodejs-sys`
//! * Open the generated bindings at `target/release/build/nodejs-sys-*/out/bindings.rs`
//! * Copy the types needed into `types.rs` and `functions.rs`
//! * Modify to match Rust naming conventions:
//! - Remove `napi_` prefixes
//! - Use `PascalCase` for types
//! - Rename types that match a reserved word
/// Constructs the name of a N-API symbol as a string from a function identifier
/// E.g., `get_undefined` becomes `"napi_get_undefined"`
macro_rules! napi_name {
// Explicitly replace identifiers that have been renamed from the N-API
// symbol because they would match a reserved word.
(typeof_value) => {
"napi_typeof"
};
// Default case: Stringify the identifier and prefix with `napi_`
($name:ident) => {
concat!("napi_", stringify!($name))
};
}

/// Generate dynamic bindings to N-API symbols from definitions in an
/// block `extern "C"`.
///
/// * A single global mutable struct holds references to the N-API functions
/// * The global `Napi` struct is initialized with stubs that panic if called
/// * A `load` function is generated that loads the N-API symbols from the
/// host process and replaces the global struct with real implementations
/// * `load` should be called exactly once before using any N-API functions
/// * Wrapper functions are generated to delegate to fields in the `Napi` struct
///
/// Sample input:
///
/// ```
/// extern "C" {
/// fn get_undefined(env: Env, result: *mut Value) -> Status;
/// /* Additional functions may be included */
/// }
/// ```
///
/// Generated output:
///
/// ```
/// // Each field is a pointer to a N-API function
/// pub(crate) struct Napi {
/// get_undefined: unsafe extern "C" fn(env: Env, result: *mut Value) -> Status,
/// /* ... repeat for each N-API function */
/// }
///
/// // Defines a panic function that is called if symbols have not been loaded
/// #[inline(never)]
/// fn panic_load<T>() -> T {
/// panic!("Must load N-API bindings")
/// }
///
/// // Mutable global instance of the Napi struct
/// // Initialized with stubs of N-API methods that panic
/// static mut NAPI: Napi = {
/// // Stubs are defined in a block to prevent naming conflicts with wrappers
/// unsafe extern "C" fn get_undefined(_: Env, _: *mut Value) -> Status {
/// panic_load()
/// }
/// /* ... repeat for each N-API function */
///
/// Napi {
/// get_undefined,
/// /* ... repeat for each N-API function */
/// }
/// };
///
/// // Load N-API symbols from the host process
/// // # Safety: Must only be called once
/// pub(crate) unsafe fn load() -> Result<(), libloading::Error> {
/// // Load the host process as a library
/// let host = Library::this();
/// #[cfg(windows)]
/// // On Windows, the host process might not be a library
/// let host = host?;
///
/// NAPI = Napi {
/// // Load each N-API symbol
/// get_undefined: *host.get("napi_get_undefined".as_bytes())?,
/// /* ... repeat for each N-API function */
/// };
///
/// Ok(())
/// }
///
/// // Each N-API function has wrapper for easy calling. These calls are optimized
/// // to a single pointer dereference.
/// #[inline]
/// pub(crate) unsafe fn get_undefined(env: Env, result: *mut Value) -> Status {
/// (NAPI.get_undefined)(env, result)
/// }
/// ```
macro_rules! generate {
(extern "C" {
$(fn $name:ident($($param:ident: $ptype:ty$(,)?)*) -> $rtype:ty;)+
Expand All @@ -19,6 +112,25 @@ macro_rules! generate {
)*
}

#[inline(never)]
fn panic_load<T>() -> T {
panic!("Must load N-API bindings")
}

static mut NAPI: Napi = {
$(
unsafe extern "C" fn $name($(_: $ptype,)*) -> $rtype {
panic_load()
}
)*

Napi {
$(
$name,
)*
}
};

pub(crate) unsafe fn load() -> Result<(), libloading::Error> {
let host = Library::this();
#[cfg(windows)]
Expand All @@ -39,24 +151,6 @@ macro_rules! generate {
(NAPI.$name)($($param,)*)
}
)*

fn panic_load<T>() -> T {
panic!("Must load N-API bindings")
}

static mut NAPI: Napi = {
$(
unsafe extern "C" fn $name($(_: $ptype,)*) -> $rtype {
panic_load()
}
)*

Napi {
$(
$name,
)*
}
};
};
}

Expand Down

0 comments on commit ccd508d

Please sign in to comment.