Skip to content

Commit 868c01b

Browse files
authored
Merge pull request #613 from filmor/discovery
Discover NIFs at startup
2 parents 3d7eb93 + 713db84 commit 868c01b

File tree

19 files changed

+146
-227
lines changed

19 files changed

+146
-227
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
See [`UPGRADE.md`](./UPGRADE.md) for additional help when upgrading to newer
99
versions.
1010

11+
## [unreleased]
12+
13+
### Added
14+
15+
### Fixed
16+
17+
### Changed
18+
19+
- NIF implementations are now discovered automatically and the respective
20+
argument of `rustler::init!` is ignored (#613)
21+
22+
### Removed
23+
24+
1125
## [0.33.0] - 2024-05-29
1226

1327
### Added

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ fn add(a: i64, b: i64) -> i64 {
5555
a + b
5656
}
5757

58-
rustler::init!("Elixir.Math", [add]);
58+
rustler::init!("Elixir.Math");
5959
```
6060

6161
#### Minimal Supported Rust Version (MSRV)

UPGRADE.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

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

5+
## 0.33 -> 0.34
6+
7+
1. NIF implementations are now discovered automatically, the respective argument
8+
in the `rustler::init!` macro should be removed. If a NIF implementation
9+
should not be exported, it must be disabled with a `#[cfg]` marker.
10+
511
## 0.32 -> 0.33
612

713
The macro changes that where already carried out in version `0.22` are now
@@ -109,7 +115,7 @@ rustler::rustler_export_nifs! {
109115
"Elixir.Math",
110116
[
111117
("add", 2, add),
112-
("long_running_operation", 0, long_running_operation, SchedulerFlags::DirtyCpu)
118+
("long_running_operation", 0, long_running_operation, SchedulerFlags::DirtyCpu)
113119
],
114120
None
115121
}
@@ -184,9 +190,9 @@ NIF called `_long_running_operation`, which used to be declared prior to Rustler
184190
rustler::rustler_export_nifs! {
185191
"Elixir.SomeNif",
186192
[
187-
// Note that the function in Rust is long_running_operation, but the NIF is exported as
188-
// _long_running_operation!
189-
("_long_running_operation", 0, long_running_operation, SchedulerFlags::DirtyCpu)
193+
// Note that the function in Rust is long_running_operation, but the NIF is exported as
194+
// _long_running_operation!
195+
("_long_running_operation", 0, long_running_operation, SchedulerFlags::DirtyCpu)
190196
],
191197
None
192198
}

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.33.0", optional = true}
2627
rustler_sys = { path = "../rustler_sys", version = "~2.4.1" }
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/src/serde/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ in a better way and allow more Erlang-y configurations).
1212
use rustler::{self, Encoder, SerdeTerm};
1313
use serde::{Serialize, Deserialize};
1414
15-
rustler::init!("Elixir.SerdeNif", [readme]);
15+
rustler::init!("Elixir.SerdeNif");
1616
1717
// NOTE: to serialize to the correct Elixir record, you MUST tell serde to
1818
// rename the variants to the full Elixir record module atom.

rustler_benchmarks/native/benchmark/src/lib.rs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,4 @@ mod nif_record;
22
mod nif_struct;
33
mod nif_various;
44

5-
rustler::init!(
6-
"Elixir.Benchmark",
7-
[
8-
nif_struct::benchmark,
9-
nif_record::benchmark,
10-
nif_various::encode_tagged_enum,
11-
nif_various::decode_tagged_enum,
12-
nif_various::decode_struct,
13-
nif_various::decode_struct_string,
14-
nif_various::decode_string,
15-
nif_various::decode_term,
16-
nif_various::void,
17-
nif_various::encode_atom,
18-
nif_various::compare_atom
19-
]
20-
);
5+
rustler::init!("Elixir.Benchmark");

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: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,51 @@
11
use proc_macro2::{Span, TokenStream};
2-
use quote::quote;
2+
use quote::{quote, quote_spanned};
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::spanned::Spanned;
5+
use syn::{Ident, Result, Token};
76

87
#[derive(Debug)]
98
pub struct InitMacroInput {
109
name: syn::Lit,
11-
funcs: syn::ExprArray,
1210
load: TokenStream,
11+
maybe_warning: TokenStream,
1312
}
1413

1514
impl Parse for InitMacroInput {
1615
fn parse(input: ParseStream) -> Result<Self> {
1716
let name = syn::Lit::parse(input)?;
18-
let _comma = <syn::Token![,]>::parse(input)?;
19-
let funcs = syn::ExprArray::parse(input)?;
17+
18+
let maybe_warning = if input.peek(syn::token::Comma) && input.peek2(syn::token::Bracket) {
19+
// peeked, must be there
20+
let _ = syn::token::Comma::parse(input).unwrap();
21+
if let Ok(funcs) = syn::ExprArray::parse(input) {
22+
quote_spanned!(funcs.span() =>
23+
#[allow(dead_code)]
24+
fn rustler_init() {
25+
#[deprecated(
26+
since = "0.34.0",
27+
note = "Passing NIF functions explicitly is deprecated and this argument is ignored, please remove it"
28+
)]
29+
#[allow(non_upper_case_globals)]
30+
const explicit_nif_functions: () = ();
31+
let _ = explicit_nif_functions;
32+
}
33+
)
34+
} else {
35+
quote!()
36+
}
37+
} else {
38+
quote!()
39+
};
40+
2041
let options = parse_expr_assigns(input);
2142
let load = extract_option(options, "load");
2243

23-
Ok(InitMacroInput { name, funcs, load })
44+
Ok(InitMacroInput {
45+
name,
46+
load,
47+
maybe_warning,
48+
})
2449
}
2550
}
2651

@@ -55,20 +80,22 @@ fn extract_option(args: Vec<syn::ExprAssign>, name: &str) -> TokenStream {
5580
impl From<InitMacroInput> for proc_macro2::TokenStream {
5681
fn from(input: InitMacroInput) -> Self {
5782
let name = input.name;
58-
let num_of_funcs = input.funcs.elems.len();
59-
let funcs = nif_funcs(input.funcs.elems);
6083
let load = input.load;
84+
let maybe_warning = input.maybe_warning;
6185

6286
let inner = quote! {
6387
static mut NIF_ENTRY: Option<rustler::codegen_runtime::DEF_NIF_ENTRY> = None;
64-
use rustler::Nif;
88+
let nif_funcs: Box<[_]> =
89+
rustler::codegen_runtime::inventory::iter::<rustler::Nif>()
90+
.map(rustler::Nif::get_def)
91+
.collect();
6592

6693
let entry = rustler::codegen_runtime::DEF_NIF_ENTRY {
6794
major: rustler::codegen_runtime::NIF_MAJOR_VERSION,
6895
minor: rustler::codegen_runtime::NIF_MINOR_VERSION,
6996
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(),
97+
num_of_funcs: nif_funcs.len() as rustler::codegen_runtime::c_int,
98+
funcs: nif_funcs.as_ptr(),
7299
load: {
73100
extern "C" fn nif_load(
74101
env: rustler::codegen_runtime::NIF_ENV,
@@ -91,12 +118,17 @@ impl From<InitMacroInput> for proc_macro2::TokenStream {
91118
};
92119

93120
unsafe {
121+
// Leak nif_funcs
122+
std::mem::forget(nif_funcs);
123+
94124
NIF_ENTRY = Some(entry);
95125
NIF_ENTRY.as_ref().unwrap()
96126
}
97127
};
98128

99129
quote! {
130+
#maybe_warning
131+
100132
#[cfg(unix)]
101133
#[no_mangle]
102134
extern "C" fn nif_init() -> *const rustler::codegen_runtime::DEF_NIF_ENTRY {
@@ -115,17 +147,3 @@ impl From<InitMacroInput> for proc_macro2::TokenStream {
115147
}
116148
}
117149
}
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-
}

0 commit comments

Comments
 (0)