Skip to content

Commit ef76ce2

Browse files
committed
Discover NIFs at startup
This is a proof-of-concept for a mechanism that does away with the need to list all NIF functions in the `rustler::init!` invocation. A similar mechanism will be required and implemented for resource type registrations. - Convert `Nif` to a `struct` - Use `inventory` to register the `Nif` instances - In `rustler::init!`, ignore the passed functions and instead use the registered `Nif` instances Next steps: - See whether `linkme` is not a better option (creates the array directly at build-time, no need to leak) - Implement resource registration using this mechanism - See if we can move the macros to `rustler`
1 parent b882d51 commit ef76ce2

File tree

9 files changed

+93
-95
lines changed

9 files changed

+93
-95
lines changed

rustler/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ nif_version_2_17 = ["nif_version_2_16", "rustler_sys/nif_version_2_17"]
2222
serde = ["dep:serde"]
2323

2424
[dependencies]
25+
inventory = "0.3"
2526
rustler_codegen = { path = "../rustler_codegen", version = "0.32.1", optional = true}
2627
rustler_sys = { path = "../rustler_sys", version = "~2.4.0" }
2728
num-bigint = { version = "0.4", optional = true }

rustler/src/codegen_runtime.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use std::fmt;
55

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

8+
// Re-export of inventory
9+
pub use inventory;
10+
811
// Names used by the `rustler::init!` macro or other generated code.
912
pub use crate::wrapper::exception::raise_exception;
1013
pub use crate::wrapper::{

rustler/src/nif.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
use crate::codegen_runtime::{c_char, c_int, c_uint, DEF_NIF_FUNC, NIF_ENV, NIF_TERM};
22

3-
pub trait Nif {
4-
const NAME: *const c_char;
5-
const ARITY: c_uint;
6-
const FLAGS: c_uint;
7-
const FUNC: DEF_NIF_FUNC;
8-
const RAW_FUNC: unsafe extern "C" fn(
9-
nif_env: NIF_ENV,
10-
argc: c_int,
11-
argv: *const NIF_TERM,
12-
) -> NIF_TERM;
3+
pub struct Nif {
4+
pub name: *const c_char,
5+
pub arity: c_uint,
6+
pub flags: c_uint,
7+
// pub func: DEF_NIF_FUNC,
8+
pub raw_func:
9+
unsafe extern "C" fn(nif_env: NIF_ENV, argc: c_int, argv: *const NIF_TERM) -> NIF_TERM,
1310
}
11+
12+
impl Nif {
13+
pub fn get_def(&self) -> DEF_NIF_FUNC {
14+
DEF_NIF_FUNC {
15+
arity: self.arity,
16+
flags: self.flags,
17+
function: self.raw_func,
18+
name: self.name
19+
}
20+
}
21+
}
22+
23+
unsafe impl Sync for Nif {}
24+
25+
inventory::collect!(Nif);

rustler_codegen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ syn = { version = "2.0", features = ["full", "extra-traits"] }
1717
quote = "1.0"
1818
heck = "0.5"
1919
proc-macro2 = "1.0"
20+
inventory = "0.3"
2021

2122
[dev-dependencies]
2223
trybuild = "1.0"

rustler_codegen/src/init.rs

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
use proc_macro2::{Span, TokenStream};
22
use quote::quote;
33
use syn::parse::{Parse, ParseStream};
4-
use syn::punctuated::Punctuated;
5-
use syn::token::Comma;
6-
use syn::{Expr, Ident, Result, Token};
4+
use syn::{Ident, Result, Token};
75

86
#[derive(Debug)]
97
pub struct InitMacroInput {
108
name: syn::Lit,
11-
funcs: syn::ExprArray,
129
load: TokenStream,
1310
}
1411

1512
impl Parse for InitMacroInput {
1613
fn parse(input: ParseStream) -> Result<Self> {
1714
let name = syn::Lit::parse(input)?;
1815
let _comma = <syn::Token![,]>::parse(input)?;
19-
let funcs = syn::ExprArray::parse(input)?;
16+
let _funcs = syn::ExprArray::parse(input)?;
2017
let options = parse_expr_assigns(input);
2118
let load = extract_option(options, "load");
2219

23-
Ok(InitMacroInput { name, funcs, load })
20+
Ok(InitMacroInput { name, load })
2421
}
2522
}
2623

@@ -55,20 +52,21 @@ fn extract_option(args: Vec<syn::ExprAssign>, name: &str) -> TokenStream {
5552
impl From<InitMacroInput> for proc_macro2::TokenStream {
5653
fn from(input: InitMacroInput) -> Self {
5754
let name = input.name;
58-
let num_of_funcs = input.funcs.elems.len();
59-
let funcs = nif_funcs(input.funcs.elems);
6055
let load = input.load;
6156

6257
let inner = quote! {
6358
static mut NIF_ENTRY: Option<rustler::codegen_runtime::DEF_NIF_ENTRY> = None;
64-
use rustler::Nif;
59+
let nif_funcs: Box<[_]> =
60+
rustler::codegen_runtime::inventory::iter::<rustler::Nif>()
61+
.map(rustler::Nif::get_def)
62+
.collect();
6563

6664
let entry = rustler::codegen_runtime::DEF_NIF_ENTRY {
6765
major: rustler::codegen_runtime::NIF_MAJOR_VERSION,
6866
minor: rustler::codegen_runtime::NIF_MINOR_VERSION,
6967
name: concat!(#name, "\0").as_ptr() as *const rustler::codegen_runtime::c_char,
70-
num_of_funcs: #num_of_funcs as rustler::codegen_runtime::c_int,
71-
funcs: [#funcs].as_ptr(),
68+
num_of_funcs: nif_funcs.len() as rustler::codegen_runtime::c_int,
69+
funcs: nif_funcs.as_ptr(),
7270
load: {
7371
extern "C" fn nif_load(
7472
env: rustler::codegen_runtime::NIF_ENV,
@@ -91,6 +89,9 @@ impl From<InitMacroInput> for proc_macro2::TokenStream {
9189
};
9290

9391
unsafe {
92+
// Leak nif_funcs
93+
std::mem::forget(nif_funcs);
94+
9495
NIF_ENTRY = Some(entry);
9596
NIF_ENTRY.as_ref().unwrap()
9697
}
@@ -115,17 +116,3 @@ impl From<InitMacroInput> for proc_macro2::TokenStream {
115116
}
116117
}
117118
}
118-
119-
fn nif_funcs(funcs: Punctuated<Expr, Comma>) -> TokenStream {
120-
let mut tokens = TokenStream::new();
121-
122-
for func in funcs.iter() {
123-
if let Expr::Path(_) = *func {
124-
tokens.extend(quote!(#func::FUNC,));
125-
} else {
126-
panic!("Expected an expression, found: {}", stringify!(func));
127-
}
128-
}
129-
130-
tokens
131-
}

rustler_codegen/src/nif.rs

Lines changed: 39 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -55,54 +55,47 @@ pub fn transcoder_decorator(nif_attributes: NifAttributes, fun: syn::ItemFn) ->
5555
}
5656

5757
quote! {
58-
#[allow(non_camel_case_types)]
59-
pub struct #name;
60-
61-
impl rustler::Nif for #name {
62-
const NAME: *const rustler::codegen_runtime::c_char = concat!(#erl_func_name, "\0").as_ptr() as *const rustler::codegen_runtime::c_char;
63-
const ARITY: rustler::codegen_runtime::c_uint = #arity;
64-
const FLAGS: rustler::codegen_runtime::c_uint = #flags as rustler::codegen_runtime::c_uint;
65-
const RAW_FUNC: unsafe extern "C" fn(
66-
nif_env: rustler::codegen_runtime::NIF_ENV,
67-
argc: rustler::codegen_runtime::c_int,
68-
argv: *const rustler::codegen_runtime::NIF_TERM
69-
) -> rustler::codegen_runtime::NIF_TERM = {
70-
unsafe extern "C" fn nif_func(
71-
nif_env: rustler::codegen_runtime::NIF_ENV,
72-
argc: rustler::codegen_runtime::c_int,
73-
argv: *const rustler::codegen_runtime::NIF_TERM
74-
) -> rustler::codegen_runtime::NIF_TERM {
75-
let lifetime = ();
76-
let env = rustler::Env::new(&lifetime, nif_env);
77-
78-
let terms = std::slice::from_raw_parts(argv, argc as usize)
79-
.iter()
80-
.map(|term| rustler::Term::new(env, *term))
81-
.collect::<Vec<rustler::Term>>();
82-
83-
fn wrapper<'a>(
84-
env: rustler::Env<'a>,
85-
args: &[rustler::Term<'a>]
86-
) -> rustler::codegen_runtime::NifReturned {
87-
let result: std::thread::Result<_> = std::panic::catch_unwind(move || {
88-
#decoded_terms
89-
#function
90-
Ok(#name(#argument_names))
91-
});
92-
93-
rustler::codegen_runtime::handle_nif_result(result, env)
58+
rustler::codegen_runtime::inventory::submit!(
59+
rustler::Nif {
60+
name: concat!(#erl_func_name, "\0").as_ptr()
61+
as *const rustler::codegen_runtime::c_char,
62+
arity: #arity,
63+
flags: #flags as rustler::codegen_runtime::c_uint,
64+
raw_func: {
65+
unsafe extern "C" fn nif_func(
66+
nif_env: rustler::codegen_runtime::NIF_ENV,
67+
argc: rustler::codegen_runtime::c_int,
68+
argv: *const rustler::codegen_runtime::NIF_TERM
69+
) -> rustler::codegen_runtime::NIF_TERM {
70+
let lifetime = ();
71+
let env = rustler::Env::new(&lifetime, nif_env);
72+
73+
let terms = std::slice::from_raw_parts(argv, argc as usize)
74+
.iter()
75+
.map(|term| rustler::Term::new(env, *term))
76+
.collect::<Vec<rustler::Term>>();
77+
78+
fn wrapper<'a>(
79+
env: rustler::Env<'a>,
80+
args: &[rustler::Term<'a>]
81+
) -> rustler::codegen_runtime::NifReturned {
82+
let result: std::thread::Result<_> =
83+
std::panic::catch_unwind(move || {
84+
#decoded_terms
85+
#function
86+
Ok(#name(#argument_names))
87+
});
88+
89+
rustler::codegen_runtime::handle_nif_result(
90+
result, env
91+
)
92+
}
93+
wrapper(env, &terms).apply(env)
9494
}
95-
wrapper(env, &terms).apply(env)
95+
nif_func
9696
}
97-
nif_func
98-
};
99-
const FUNC: rustler::codegen_runtime::DEF_NIF_FUNC = rustler::codegen_runtime::DEF_NIF_FUNC {
100-
arity: Self::ARITY,
101-
flags: Self::FLAGS,
102-
function: Self::RAW_FUNC,
103-
name: Self::NAME
104-
};
105-
}
97+
}
98+
);
10699
}
107100
}
108101

rustler_sys/src/rustler_sys_api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ unsafe impl Send for ErlNifEnv {}
3939

4040
/// See [ErlNifFunc](http://www.erlang.org/doc/man/erl_nif.html#ErlNifFunc) in the Erlang docs.
4141
// #[allow(missing_copy_implementations)]
42+
#[derive(Debug)]
4243
#[repr(C)]
4344
pub struct ErlNifFunc {
4445
pub name: *const c_char,

rustler_tests/native/rustler_serde_test/src/lib.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ use rustler::{types::tuple, Encoder, Env, NifResult, SerdeTerm, Term};
1616
init! {
1717
"Elixir.SerdeRustlerTests",
1818
[
19-
// json
20-
json::decode_json,
21-
json::decode_json_dirty,
22-
json::encode_json_compact,
23-
json::encode_json_compact_dirty,
24-
json::encode_json_pretty,
25-
json::encode_json_pretty_dirty,
26-
27-
// tests
28-
readme,
29-
test::test,
30-
transcode,
31-
transcode_dirty,
19+
// // json
20+
// json::decode_json,
21+
// json::decode_json_dirty,
22+
// json::encode_json_compact,
23+
// json::encode_json_compact_dirty,
24+
// json::encode_json_pretty,
25+
// json::encode_json_pretty_dirty,
26+
//
27+
// // tests
28+
// readme,
29+
// test::test,
30+
// transcode,
31+
// transcode_dirty,
3232
]
3333
}
3434

rustler_tests/native/rustler_test/src/test_resource.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub fn resource_make_binaries(
109109
)
110110
}
111111

112-
#[rustler::nif]
112+
// #[rustler::nif]
113113
pub fn resource_make_binary_from_vec(env: Env, resource: ResourceArc<WithBinaries>) -> Binary {
114114
resource.make_binary(env, |w| &w.b)
115115
}

0 commit comments

Comments
 (0)