Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discover NIFs at startup #613

Merged
merged 7 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer
versions.

## [unreleased]

### Added

### Fixed

### Changed

- NIF implementations are now discovered automatically and the respective
argument of `rustler::init!` is ignored (#613)

### Removed


## [0.33.0] - 2024-05-29

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ fn add(a: i64, b: i64) -> i64 {
a + b
}

rustler::init!("Elixir.Math", [add]);
rustler::init!("Elixir.Math");
```

#### Minimal Supported Rust Version (MSRV)
Expand Down
14 changes: 10 additions & 4 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

This document is intended to simplify upgrading to newer versions by extending the changelog.

## 0.33 -> 0.34

1. NIF implementations are now discovered automatically, the respective argument
in the `rustler::init!` macro should be removed. If a NIF implementation
should not be exported, it must be disabled with a `#[cfg]` marker.

## 0.32 -> 0.33

The macro changes that where already carried out in version `0.22` are now
Expand Down Expand Up @@ -109,7 +115,7 @@ rustler::rustler_export_nifs! {
"Elixir.Math",
[
("add", 2, add),
("long_running_operation", 0, long_running_operation, SchedulerFlags::DirtyCpu)
("long_running_operation", 0, long_running_operation, SchedulerFlags::DirtyCpu)
],
None
}
Expand Down Expand Up @@ -184,9 +190,9 @@ NIF called `_long_running_operation`, which used to be declared prior to Rustler
rustler::rustler_export_nifs! {
"Elixir.SomeNif",
[
// Note that the function in Rust is long_running_operation, but the NIF is exported as
// _long_running_operation!
("_long_running_operation", 0, long_running_operation, SchedulerFlags::DirtyCpu)
// Note that the function in Rust is long_running_operation, but the NIF is exported as
// _long_running_operation!
("_long_running_operation", 0, long_running_operation, SchedulerFlags::DirtyCpu)
],
None
}
Expand Down
1 change: 1 addition & 0 deletions rustler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ nif_version_2_17 = ["nif_version_2_16", "rustler_sys/nif_version_2_17"]
serde = ["dep:serde"]

[dependencies]
inventory = "0.3"
rustler_codegen = { path = "../rustler_codegen", version = "0.33.0", optional = true}
rustler_sys = { path = "../rustler_sys", version = "~2.4.1" }
num-bigint = { version = "0.4", optional = true }
Expand Down
3 changes: 3 additions & 0 deletions rustler/src/codegen_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use std::fmt;

use crate::{Encoder, Env, OwnedBinary, Term};

// Re-export of inventory
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::{
Expand Down
32 changes: 22 additions & 10 deletions rustler/src/nif.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
use crate::codegen_runtime::{c_char, c_int, c_uint, DEF_NIF_FUNC, NIF_ENV, NIF_TERM};

pub trait Nif {
const NAME: *const c_char;
const ARITY: c_uint;
const FLAGS: c_uint;
const FUNC: DEF_NIF_FUNC;
const RAW_FUNC: unsafe extern "C" fn(
nif_env: NIF_ENV,
argc: c_int,
argv: *const NIF_TERM,
) -> NIF_TERM;
pub struct Nif {
pub name: *const c_char,
pub arity: c_uint,
pub flags: c_uint,
// pub func: DEF_NIF_FUNC,
pub raw_func:
unsafe extern "C" fn(nif_env: NIF_ENV, argc: c_int, argv: *const NIF_TERM) -> NIF_TERM,
}

impl Nif {
pub fn get_def(&self) -> DEF_NIF_FUNC {
DEF_NIF_FUNC {
arity: self.arity,
flags: self.flags,
function: self.raw_func,
name: self.name,
}
}
}

unsafe impl Sync for Nif {}

inventory::collect!(Nif);
2 changes: 1 addition & 1 deletion rustler/src/serde/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ in a better way and allow more Erlang-y configurations).
use rustler::{self, Encoder, SerdeTerm};
use serde::{Serialize, Deserialize};
rustler::init!("Elixir.SerdeNif", [readme]);
rustler::init!("Elixir.SerdeNif");
// NOTE: to serialize to the correct Elixir record, you MUST tell serde to
// rename the variants to the full Elixir record module atom.
Expand Down
17 changes: 1 addition & 16 deletions rustler_benchmarks/native/benchmark/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,4 @@ mod nif_record;
mod nif_struct;
mod nif_various;

rustler::init!(
"Elixir.Benchmark",
[
nif_struct::benchmark,
nif_record::benchmark,
nif_various::encode_tagged_enum,
nif_various::decode_tagged_enum,
nif_various::decode_struct,
nif_various::decode_struct_string,
nif_various::decode_string,
nif_various::decode_term,
nif_various::void,
nif_various::encode_atom,
nif_various::compare_atom
]
);
rustler::init!("Elixir.Benchmark");
1 change: 1 addition & 0 deletions rustler_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ syn = { version = "2.0", features = ["full", "extra-traits"] }
quote = "1.0"
heck = "0.5"
proc-macro2 = "1.0"
inventory = "0.3"

[dev-dependencies]
trybuild = "1.0"
72 changes: 45 additions & 27 deletions rustler_codegen/src/init.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,51 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use quote::{quote, quote_spanned};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{Expr, Ident, Result, Token};
use syn::spanned::Spanned;
use syn::{Ident, Result, Token};

#[derive(Debug)]
pub struct InitMacroInput {
name: syn::Lit,
funcs: syn::ExprArray,
load: TokenStream,
maybe_warning: TokenStream,
}

impl Parse for InitMacroInput {
fn parse(input: ParseStream) -> Result<Self> {
let name = syn::Lit::parse(input)?;
let _comma = <syn::Token![,]>::parse(input)?;
let funcs = syn::ExprArray::parse(input)?;

let maybe_warning = if input.peek(syn::token::Comma) && input.peek2(syn::token::Bracket) {
// peeked, must be there
let _ = syn::token::Comma::parse(input).unwrap();
if let Ok(funcs) = syn::ExprArray::parse(input) {
quote_spanned!(funcs.span() =>
#[allow(dead_code)]
fn rustler_init() {
#[deprecated(
since = "0.34.0",
note = "Passing NIF functions explicitly is deprecated and this argument is ignored, please remove it"
)]
#[allow(non_upper_case_globals)]
const explicit_nif_functions: () = ();
let _ = explicit_nif_functions;
}
)
} else {
quote!()
}
} else {
quote!()
};

let options = parse_expr_assigns(input);
let load = extract_option(options, "load");

Ok(InitMacroInput { name, funcs, load })
Ok(InitMacroInput {
name,
load,
maybe_warning,
})
}
}

Expand Down Expand Up @@ -55,20 +80,22 @@ fn extract_option(args: Vec<syn::ExprAssign>, name: &str) -> TokenStream {
impl From<InitMacroInput> for proc_macro2::TokenStream {
fn from(input: InitMacroInput) -> Self {
let name = input.name;
let num_of_funcs = input.funcs.elems.len();
let funcs = nif_funcs(input.funcs.elems);
let load = input.load;
let maybe_warning = input.maybe_warning;

let inner = quote! {
static mut NIF_ENTRY: Option<rustler::codegen_runtime::DEF_NIF_ENTRY> = None;
use rustler::Nif;
let nif_funcs: Box<[_]> =
rustler::codegen_runtime::inventory::iter::<rustler::Nif>()
.map(rustler::Nif::get_def)
.collect();

let entry = rustler::codegen_runtime::DEF_NIF_ENTRY {
major: rustler::codegen_runtime::NIF_MAJOR_VERSION,
minor: rustler::codegen_runtime::NIF_MINOR_VERSION,
name: concat!(#name, "\0").as_ptr() as *const rustler::codegen_runtime::c_char,
num_of_funcs: #num_of_funcs as rustler::codegen_runtime::c_int,
funcs: [#funcs].as_ptr(),
num_of_funcs: nif_funcs.len() as rustler::codegen_runtime::c_int,
funcs: nif_funcs.as_ptr(),
load: {
extern "C" fn nif_load(
env: rustler::codegen_runtime::NIF_ENV,
Expand All @@ -91,12 +118,17 @@ impl From<InitMacroInput> for proc_macro2::TokenStream {
};

unsafe {
// Leak nif_funcs
std::mem::forget(nif_funcs);

NIF_ENTRY = Some(entry);
NIF_ENTRY.as_ref().unwrap()
}
};

quote! {
#maybe_warning

#[cfg(unix)]
#[no_mangle]
extern "C" fn nif_init() -> *const rustler::codegen_runtime::DEF_NIF_ENTRY {
Expand All @@ -115,17 +147,3 @@ impl From<InitMacroInput> for proc_macro2::TokenStream {
}
}
}

fn nif_funcs(funcs: Punctuated<Expr, Comma>) -> TokenStream {
let mut tokens = TokenStream::new();

for func in funcs.iter() {
if let Expr::Path(_) = *func {
tokens.extend(quote!(#func::FUNC,));
} else {
panic!("Expected an expression, found: {}", stringify!(func));
}
}

tokens
}
2 changes: 1 addition & 1 deletion rustler_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ enum RustlerAttr {
/// true
/// }
///
/// rustler::init!("Elixir.Math", [add, sub, mul, div], load = load);
/// rustler::init!("Elixir.Math", load = load);
/// ```
#[proc_macro]
pub fn init(input: TokenStream) -> TokenStream {
Expand Down
85 changes: 39 additions & 46 deletions rustler_codegen/src/nif.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,54 +55,47 @@ pub fn transcoder_decorator(nif_attributes: NifAttributes, fun: syn::ItemFn) ->
}

quote! {
#[allow(non_camel_case_types)]
pub struct #name;

impl rustler::Nif for #name {
const NAME: *const rustler::codegen_runtime::c_char = concat!(#erl_func_name, "\0").as_ptr() as *const rustler::codegen_runtime::c_char;
const ARITY: rustler::codegen_runtime::c_uint = #arity;
const FLAGS: rustler::codegen_runtime::c_uint = #flags as rustler::codegen_runtime::c_uint;
const RAW_FUNC: unsafe extern "C" fn(
nif_env: rustler::codegen_runtime::NIF_ENV,
argc: rustler::codegen_runtime::c_int,
argv: *const rustler::codegen_runtime::NIF_TERM
) -> rustler::codegen_runtime::NIF_TERM = {
unsafe extern "C" fn nif_func(
nif_env: rustler::codegen_runtime::NIF_ENV,
argc: rustler::codegen_runtime::c_int,
argv: *const rustler::codegen_runtime::NIF_TERM
) -> rustler::codegen_runtime::NIF_TERM {
let lifetime = ();
let env = rustler::Env::new(&lifetime, nif_env);

let terms = std::slice::from_raw_parts(argv, argc as usize)
.iter()
.map(|term| rustler::Term::new(env, *term))
.collect::<Vec<rustler::Term>>();

fn wrapper<'a>(
env: rustler::Env<'a>,
args: &[rustler::Term<'a>]
) -> rustler::codegen_runtime::NifReturned {
let result: std::thread::Result<_> = std::panic::catch_unwind(move || {
#decoded_terms
#function
Ok(#name(#argument_names))
});

rustler::codegen_runtime::handle_nif_result(result, env)
rustler::codegen_runtime::inventory::submit!(
rustler::Nif {
name: concat!(#erl_func_name, "\0").as_ptr()
as *const rustler::codegen_runtime::c_char,
arity: #arity,
flags: #flags as rustler::codegen_runtime::c_uint,
raw_func: {
unsafe extern "C" fn nif_func(
nif_env: rustler::codegen_runtime::NIF_ENV,
argc: rustler::codegen_runtime::c_int,
argv: *const rustler::codegen_runtime::NIF_TERM
) -> rustler::codegen_runtime::NIF_TERM {
let lifetime = ();
let env = rustler::Env::new(&lifetime, nif_env);

let terms = std::slice::from_raw_parts(argv, argc as usize)
.iter()
.map(|term| rustler::Term::new(env, *term))
.collect::<Vec<rustler::Term>>();

fn wrapper<'a>(
env: rustler::Env<'a>,
args: &[rustler::Term<'a>]
) -> rustler::codegen_runtime::NifReturned {
let result: std::thread::Result<_> =
std::panic::catch_unwind(move || {
#decoded_terms
#function
Ok(#name(#argument_names))
});

rustler::codegen_runtime::handle_nif_result(
result, env
)
}
wrapper(env, &terms).apply(env)
}
wrapper(env, &terms).apply(env)
nif_func
}
nif_func
};
const FUNC: rustler::codegen_runtime::DEF_NIF_FUNC = rustler::codegen_runtime::DEF_NIF_FUNC {
arity: Self::ARITY,
flags: Self::FLAGS,
function: Self::RAW_FUNC,
name: Self::NAME
};
}
}
);
}
}

Expand Down
2 changes: 1 addition & 1 deletion rustler_mix/priv/templates/basic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ fn add(a: i64, b: i64) -> i64 {
a + b
}

rustler::init!("<%= native_module %>", [add]);
rustler::init!("<%= native_module %>");
1 change: 1 addition & 0 deletions rustler_sys/src/rustler_sys_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ unsafe impl Send for ErlNifEnv {}

/// See [ErlNifFunc](http://www.erlang.org/doc/man/erl_nif.html#ErlNifFunc) in the Erlang docs.
// #[allow(missing_copy_implementations)]
#[derive(Debug)]
#[repr(C)]
pub struct ErlNifFunc {
pub name: *const c_char,
Expand Down
Loading
Loading