From a1cf8a2367faeb6d109d7057924cfdee7cf34bba Mon Sep 17 00:00:00 2001 From: Sonny Scroggin Date: Fri, 18 Oct 2019 09:08:03 -0500 Subject: [PATCH 1/2] NIF Codegen Rework Signed-off-by: Sonny Scroggin --- README.md | 24 +- rustler/Cargo.toml | 2 +- rustler/src/codegen_runtime.rs | 41 +++- rustler/src/env.rs | 2 +- rustler/src/export.rs | 22 +- rustler/src/lib.rs | 11 +- rustler/src/nif.rs | 13 ++ rustler/src/return.rs | 1 + rustler/src/types/atom.rs | 3 + rustler_codegen/Cargo.toml | 2 +- rustler_codegen/src/init.rs | 112 ++++++++++ rustler_codegen/src/lib.rs | 101 ++++++--- rustler_codegen/src/nif.rs | 205 ++++++++++++++++++ rustler_mix/priv/templates/basic/src/lib.rs | 27 +-- rustler_mix/test.sh | 34 +-- rustler_tests/native/rustler_test/src/lib.rs | 136 +++++------- .../native/rustler_test/src/test_atom.rs | 23 +- .../native/rustler_test/src/test_binary.rs | 53 ++--- .../native/rustler_test/src/test_codegen.rs | 74 +++---- .../native/rustler_test/src/test_dirty.rs | 23 +- .../native/rustler_test/src/test_env.rs | 29 ++- .../native/rustler_test/src/test_list.rs | 16 +- .../native/rustler_test/src/test_map.rs | 25 +-- .../rustler_test/src/test_primitives.rs | 39 ++-- .../native/rustler_test/src/test_range.rs | 9 +- .../native/rustler_test/src/test_resource.rs | 45 ++-- .../native/rustler_test/src/test_term.rs | 27 ++- .../native/rustler_test/src/test_thread.rs | 16 +- rustler_tests/test/atom_test.exs | 6 +- rustler_tests/test/term_test.exs | 2 +- 30 files changed, 722 insertions(+), 401 deletions(-) create mode 100644 rustler/src/nif.rs create mode 100644 rustler_codegen/src/init.rs create mode 100644 rustler_codegen/src/nif.rs diff --git a/README.md b/README.md index da777aaf..b0f1a6d2 100644 --- a/README.md +++ b/README.md @@ -35,28 +35,12 @@ NOTE: If you have previously used Rustler, you need to run `mix archive.uninstal This is the code for a minimal NIF that adds two numbers and returns the result. ```rust -use rustler::{Encoder, Env, Error, Term}; - -mod atoms { - rustler::atoms! { - ok, - } +#[rustler::nif] +fn add(a: i64, b: i64) -> i64 { + a + b } -rustler::init!( - "Elixir.Math", - [ - ("add", 2, add) - ], - None -); - -fn add<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result, Error> { - let a: i64 = args[0].decode()?; - let b: i64 = args[1].decode()?; - - Ok((atoms::ok(), a + b).encode(env)) -} +rustler::init!("Elixir.Math", [add]); ``` #### Supported nif_version diff --git a/rustler/Cargo.toml b/rustler/Cargo.toml index 7b3311b2..89014707 100644 --- a/rustler/Cargo.toml +++ b/rustler/Cargo.toml @@ -13,9 +13,9 @@ derive = ["rustler_codegen"] alternative_nif_init_name = [] [dependencies] +lazy_static = "1.4" rustler_codegen = { path = "../rustler_codegen", version = "0.21.0", optional = true } rustler_sys = { path = "../rustler_sys", version = "~2.0" } -lazy_static = "1.4" [build-dependencies] lazy_static = "1.4" diff --git a/rustler/src/codegen_runtime.rs b/rustler/src/codegen_runtime.rs index 987ba45a..31124c96 100644 --- a/rustler/src/codegen_runtime.rs +++ b/rustler/src/codegen_runtime.rs @@ -1,10 +1,11 @@ //! Functions used by runtime generated code. Should not be used. use std::ffi::CString; +use std::fmt; use crate::{Env, Term}; -// Names used by the `init!` macro or other generated code. +// Names used by the `rustler::init!` macro or other generated code. pub use crate::wrapper::exception::raise_exception; pub use crate::wrapper::{ c_int, c_void, get_nif_resource_type_init_size, DEF_NIF_ENTRY, DEF_NIF_FUNC, @@ -17,6 +18,7 @@ pub use rustler_sys::{TWinDynNifCallbacks, WIN_DYN_NIF_CALLBACKS}; pub unsafe trait NifReturnable { unsafe fn as_returned(self, env: Env) -> NifReturned; } + unsafe impl NifReturnable for T where T: crate::Encoder, @@ -25,6 +27,7 @@ where NifReturned::Term(self.encode(env).as_c_arg()) } } + unsafe impl NifReturnable for Result where T: crate::Encoder, @@ -48,6 +51,7 @@ pub enum NifReturned { args: Vec, }, } + impl NifReturned { pub unsafe fn apply(self, env: Env) -> NIF_TERM { match self { @@ -73,6 +77,17 @@ impl NifReturned { } } +impl fmt::Debug for NifReturned { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + NifReturned::BadArg => write!(fmt, "{{error, badarg}}"), + NifReturned::Term(ref s) => write!(fmt, "{{ok, {}}}", s), + NifReturned::Raise(ref s) => write!(fmt, "throw({})", s), + NifReturned::Reschedule { .. } => write!(fmt, "reschedule()"), + } + } +} + /// # Unsafe /// /// This takes arguments, including raw pointers, that must be correct. @@ -94,3 +109,27 @@ pub unsafe fn handle_nif_init_call( 0 } } + +pub fn handle_nif_result( + result: std::thread::Result>, + env: Env, +) -> NifReturned +where + T: NifReturnable, +{ + unsafe { + match result { + Ok(res) => match res { + Ok(res) => NifReturnable::as_returned(res, env), + Err(err) => NifReturnable::as_returned(err, env), + }, + Err(err) => match err.downcast::() { + Ok(ty) => NifReturned::Term(ty.apply(env)), + Err(_) => { + let term = crate::types::atom::nif_panicked().as_c_arg(); + NifReturned::Raise(term) + } + }, + } + } +} diff --git a/rustler/src/env.rs b/rustler/src/env.rs index fdd2e6f0..62fe65ed 100644 --- a/rustler/src/env.rs +++ b/rustler/src/env.rs @@ -23,7 +23,7 @@ pub struct Env<'a> { /// Two environments are equal if they're the same `NIF_ENV` value. /// -/// A `Env<'a>` is equal to a `Env<'b>` iff `'a` and `'b` are the same lifetime. +/// A `Env<'a>` is equal to a `Env<'b>` if and only if `'a` and `'b` are the same lifetime. impl<'a, 'b> PartialEq> for Env<'a> { fn eq(&self, other: &Env<'b>) -> bool { self.env == other.env diff --git a/rustler/src/export.rs b/rustler/src/export.rs index f3fb75ea..3ebf6be8 100644 --- a/rustler/src/export.rs +++ b/rustler/src/export.rs @@ -15,23 +15,16 @@ /// The third argument is an `Option bool>`. If this is /// `Some`, the function will execute when the NIF is first loaded by the BEAM. #[macro_export] -#[deprecated(since = "0.22.0", note = "Please use `init!` instead.")] +#[deprecated(since = "0.22.0", note = "Please use `rustler::init!` instead.")] macro_rules! rustler_export_nifs { - ($name:expr, [$( $exported_nif:tt ),+,], $on_load:expr) => { - $crate::init!($name, [$( $exported_nif ),*], $on_load); - }; -} - -#[macro_export] -macro_rules! init { // Strip trailing comma. ($name:expr, [$( $exported_nif:tt ),+,], $on_load:expr) => { - $crate::init!($name, [$( $exported_nif ),*], $on_load); + $crate::rustler_export_nifs!($name, [$( $exported_nif ),*], $on_load); }; ($name:expr, [$( $exported_nif:tt ),*], $on_load:expr) => { static mut NIF_ENTRY: Option<$crate::codegen_runtime::DEF_NIF_ENTRY> = None; - $crate::init!(internal_platform_init, ({ + $crate::rustler_export_nifs!(internal_platform_init, ({ // TODO: If an unwrap ever happens, we will unwind right into C! Fix this! extern "C" fn nif_load( @@ -45,7 +38,7 @@ macro_rules! init { } const FUN_ENTRIES: &'static [$crate::codegen_runtime::DEF_NIF_FUNC] = &[ - $($crate::init!(internal_item_init, $exported_nif)),* + $($crate::rustler_export_nifs!(internal_item_init, $exported_nif)),* ]; let entry = $crate::codegen_runtime::DEF_NIF_ENTRY { @@ -69,7 +62,7 @@ macro_rules! init { }; (internal_item_init, ($nif_name:expr, $nif_arity:expr, $nif_fun:path)) => { - $crate::init!(internal_item_init, ($nif_name, $nif_arity, $nif_fun, $crate::schedule::SchedulerFlags::Normal)) + $crate::rustler_export_nifs!(internal_item_init, ($nif_name, $nif_arity, $nif_fun, $crate::schedule::SchedulerFlags::Normal)) }; (internal_item_init, ($nif_name:expr, $nif_arity:expr, $nif_fun:path, $nif_flag:expr)) => { $crate::codegen_runtime::DEF_NIF_FUNC { @@ -82,12 +75,9 @@ macro_rules! init { argv: *const $crate::codegen_runtime::NIF_TERM) -> $crate::codegen_runtime::NIF_TERM { unsafe { - $crate::init!( + $crate::rustler_export_nifs!( internal_handle_nif_call, ($nif_fun, $nif_arity, env, argc, argv)) } - //unsafe { - // $crate::codegen_runtime::handle_nif_call($nif_fun, $nif_arity, env, argc, argv) - //} } nif_func }, diff --git a/rustler/src/lib.rs b/rustler/src/lib.rs index 1562cfb1..9593bce5 100644 --- a/rustler/src/lib.rs +++ b/rustler/src/lib.rs @@ -25,7 +25,8 @@ #[macro_use(enif_snprintf)] extern crate rustler_sys; -mod wrapper; +#[doc(hidden)] +pub mod wrapper; #[doc(hidden)] pub mod codegen_runtime; @@ -62,7 +63,13 @@ pub use crate::error::Error; pub mod r#return; pub use crate::r#return::Return; +#[doc(hidden)] +mod nif; +pub use nif::Nif; + pub type NifResult = Result; #[cfg(feature = "derive")] -pub use rustler_codegen::{NifMap, NifRecord, NifStruct, NifTuple, NifUnitEnum, NifUntaggedEnum}; +pub use rustler_codegen::{ + init, nif, NifMap, NifRecord, NifStruct, NifTuple, NifUnitEnum, NifUntaggedEnum, +}; diff --git a/rustler/src/nif.rs b/rustler/src/nif.rs new file mode 100644 index 00000000..3d9342fa --- /dev/null +++ b/rustler/src/nif.rs @@ -0,0 +1,13 @@ +use crate::codegen_runtime::{c_int, DEF_NIF_FUNC, NIF_ENV, NIF_TERM}; + +pub trait Nif { + const NAME: *const u8; + const ARITY: u32; + const FLAGS: u32; + 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; +} diff --git a/rustler/src/return.rs b/rustler/src/return.rs index a9210c13..af299292 100644 --- a/rustler/src/return.rs +++ b/rustler/src/return.rs @@ -6,6 +6,7 @@ pub enum Return<'a> { Term(Term<'a>), Error(Error), } + unsafe impl<'b> NifReturnable for Return<'b> { unsafe fn as_returned(self, env: Env) -> NifReturned { match self { diff --git a/rustler/src/types/atom.rs b/rustler/src/types/atom.rs index 8618098e..17bfc7ce 100644 --- a/rustler/src/types/atom.rs +++ b/rustler/src/types/atom.rs @@ -269,6 +269,9 @@ macro_rules! rustler_atoms { } atoms! { + /// The `nif_panicked` atom. + nif_panicked, + /// The `nil` atom. nil, diff --git a/rustler_codegen/Cargo.toml b/rustler_codegen/Cargo.toml index 047e578d..9edf0ccf 100644 --- a/rustler_codegen/Cargo.toml +++ b/rustler_codegen/Cargo.toml @@ -12,7 +12,7 @@ name = "rustler_codegen" proc_macro = true [dependencies] -syn = { version = "1.0.5", features = ["derive"] } +syn = { version = "1.0.5", features = ["full", "extra-traits"] } quote = "1.0" heck = "0.3" proc-macro2 = "1.0" diff --git a/rustler_codegen/src/init.rs b/rustler_codegen/src/init.rs new file mode 100644 index 00000000..438658cc --- /dev/null +++ b/rustler_codegen/src/init.rs @@ -0,0 +1,112 @@ +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::token::Comma; +use syn::{Expr, Ident, Result, Token}; + +#[derive(Debug)] +pub struct InitMacroInput { + module: syn::Lit, + funcs: syn::ExprArray, + on_load: Option, +} + +impl Parse for InitMacroInput { + fn parse(input: ParseStream) -> Result { + let module = syn::Lit::parse(input)?; + let _comma = ::parse(input)?; + let funcs = syn::ExprArray::parse(input)?; + let on_load = if input.peek(Token![,]) { + let _comma = ::parse(input)?; + Some(TokenStream::parse(input)?) + } else { + let none = Ident::new("None", Span::call_site()); + Some(quote!(#none)) + }; + + Ok(InitMacroInput { + module, + funcs, + on_load, + }) + } +} + +impl Into for InitMacroInput { + fn into(self) -> proc_macro2::TokenStream { + let name = self.module; + let num_of_funcs = self.funcs.elems.len(); + let funcs = nif_funcs(self.funcs.elems); + let on_load = self.on_load; + + let inner = quote! { + static mut NIF_ENTRY: Option = None; + use rustler::Nif; + + 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 u8, + num_of_funcs: #num_of_funcs as rustler::codegen_runtime::c_int, + funcs: [#funcs].as_ptr(), + load: { + extern "C" fn nif_load( + env: rustler::codegen_runtime::NIF_ENV, + _priv_data: *mut *mut rustler::codegen_runtime::c_void, + load_info: rustler::codegen_runtime::NIF_TERM + ) -> rustler::codegen_runtime::c_int { + unsafe { + // TODO: If an unwrap ever happens, we will unwind right into C! Fix this! + rustler::codegen_runtime::handle_nif_init_call(#on_load, env, load_info) + } + } + Some(nif_load) + }, + reload: None, + upgrade: None, + unload: None, + vm_variant: b"beam.vanilla\0".as_ptr(), + options: 0, + sizeof_ErlNifResourceTypeInit: rustler::codegen_runtime::get_nif_resource_type_init_size(), + }; + + unsafe { + NIF_ENTRY = Some(entry); + NIF_ENTRY.as_ref().unwrap() + } + }; + + quote! { + #[cfg(unix)] + #[no_mangle] + pub extern "C" fn nif_init() -> *const rustler::codegen_runtime::DEF_NIF_ENTRY { + #inner + } + + #[cfg(windows)] + #[no_mangle] + pub extern "C" fn nif_init(callbacks: *mut rustler::codegen_runtime::TWinDynNifCallbacks) -> *const rustler::codegen_runtime::DEF_NIF_ENTRY { + unsafe { + rustler::codegen_runtime::WIN_DYN_NIF_CALLBACKS = Some(*callbacks); + } + + #inner + } + } + } +} + +fn nif_funcs(funcs: Punctuated) -> 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 +} diff --git a/rustler_codegen/src/lib.rs b/rustler_codegen/src/lib.rs index cede4d68..451e6cef 100644 --- a/rustler_codegen/src/lib.rs +++ b/rustler_codegen/src/lib.rs @@ -11,9 +11,10 @@ extern crate syn; extern crate quote; mod context; - mod ex_struct; +mod init; mod map; +mod nif; mod record; mod tuple; mod unit_enum; @@ -27,11 +28,61 @@ enum RustlerAttr { Tag(String), } +/// Implementation of a Native Implementated Function (NIF) macro that lets the user annotate +/// a function that will be wrapped in higer-level NIF implementation. +/// +/// ```ignore +/// #[rustler::nif] +/// fn add(a: i64, b: i64) -> i64 { +/// a + b +/// } +/// +/// #[rustler::nif] +/// fn add(a: i64, b: i64) -> i64 { +/// a - b +/// } +/// +/// #[rustler::nif] +/// fn mul(a: i64, b: i64) -> i64 { +/// a * b +/// } +/// +/// #[rustler::nif] +/// fn div(a: i64, b: i64) -> i64 { +/// a / b +/// } +/// +/// rustler::init!("Elixir.Math", [add, sub, mul, div], Some(load)); +/// ``` +#[proc_macro] +pub fn init(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as init::InitMacroInput); + let output: proc_macro2::TokenStream = input.into(); + output.into() +} + +/// Implementation of a Native Implementated Function (NIF) macro that lets the user annotate +/// a function that will be wrapped in higer-level NIF implementation. +/// +/// ```ignore +/// #[nif] +/// fn add(a: i64, b: i64) -> i64 { +/// a + b +/// } +/// ``` +#[proc_macro_attribute] +pub fn nif(args: TokenStream, input: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let input = syn::parse_macro_input!(input as syn::ItemFn); + + nif::transcoder_decorator(args, input).into() +} + /// Implementation of the `NifStruct` macro that lets the user annotate a struct that will /// be translated directly from an Elixir struct to a Rust struct. For example, the following /// struct, annotated as such: /// -/// ```text +/// ```ignore /// #[derive(Debug, NifStruct)] /// #[module = "AddStruct"] /// struct AddStruct { @@ -42,9 +93,9 @@ enum RustlerAttr { /// /// This would be translated by Rustler into: /// -/// ```text +/// ```elixir /// defmodule AddStruct do -/// defstruct lhs: 0, rhs: 0 +/// defstruct lhs: 0, rhs: 0 /// end /// ``` #[proc_macro_derive(NifStruct, attributes(module, rustler))] @@ -57,7 +108,7 @@ pub fn nif_struct(input: TokenStream) -> TokenStream { /// struct can be encoded or decoded from an Elixir map. For example, the following struct /// annotated as such: /// -/// ```text +/// ```ignore /// #[derive(NifMap)] /// struct AddMap { /// lhs: i32, @@ -68,7 +119,7 @@ pub fn nif_struct(input: TokenStream) -> TokenStream { /// Given the values 33 and 21 for this struct, this would result, when encoded, in an elixir /// map with two elements like: /// -/// ```text +/// ```elixir /// %{lhs: 33, rhs: 21} /// ``` #[proc_macro_derive(NifMap, attributes(rustler))] @@ -81,7 +132,7 @@ pub fn nif_map(input: TokenStream) -> TokenStream { /// struct can be encoded or decoded from an Elixir tuple. For example, the following struct /// annotated as such: /// -/// ```text +/// ```ignore /// #[derive(NifTuple)] /// struct AddTuple { /// lhs: i32, @@ -92,7 +143,7 @@ pub fn nif_map(input: TokenStream) -> TokenStream { /// Given the values 33 and 21 for this struct, this would result, when encoded, in an elixir /// tuple with two elements like: /// -/// ```text +/// ```elixir /// {33, 21} /// ``` /// @@ -107,7 +158,7 @@ pub fn nif_tuple(input: TokenStream) -> TokenStream { /// be translated directly from an Elixir struct to a Rust struct. For example, the following /// struct, annotated as such: /// -/// ```text +/// ```ignore /// #[derive(Debug, NifRecord)] /// #[tag = "record"] /// struct AddRecord { @@ -118,10 +169,10 @@ pub fn nif_tuple(input: TokenStream) -> TokenStream { /// /// This would be translated by Rustler into: /// -/// ```text +/// ```elixir /// defmodule AddRecord do -/// import Record -/// defrecord :record, [lhs: 1, rhs: 2] +/// import Record +/// defrecord :record, [lhs: 1, rhs: 2] /// end /// ``` #[proc_macro_derive(NifRecord, attributes(tag, rustler))] @@ -133,7 +184,7 @@ pub fn nif_record(input: TokenStream) -> TokenStream { /// Implementation of the `NifUnitEnum` macro that lets the user annotate an enum with a unit type /// that will generate elixir atoms when encoded /// -/// ```text +/// ```ignore /// #[derive(NifUnitEnum)] /// enum UnitEnum { /// FooBar, @@ -143,11 +194,11 @@ pub fn nif_record(input: TokenStream) -> TokenStream { /// /// An example usage in elixir would look like the following. /// -/// ```text +/// ```elixir /// test "unit enum transcoder" do -/// assert :foo_bar == RustlerTest.unit_enum_echo(:foo_bar) -/// assert :baz == RustlerTest.unit_enum_echo(:baz) -/// assert :invalid_variant == RustlerTest.unit_enum_echo(:somethingelse) +/// assert :foo_bar == RustlerTest.unit_enum_echo(:foo_bar) +/// assert :baz == RustlerTest.unit_enum_echo(:baz) +/// assert :invalid_variant == RustlerTest.unit_enum_echo(:somethingelse) /// end /// ``` /// @@ -163,7 +214,7 @@ pub fn nif_unit_enum(input: TokenStream) -> TokenStream { /// generate elixir values when decoded. This can be used for rust enums that contain data and /// will generate a value based on the kind of data encoded. For example from the test code: /// -/// ```text +/// ```ignore /// #[derive(NifUntaggedEnum)] /// enum UntaggedEnum { /// Foo(u32), @@ -179,13 +230,13 @@ pub fn nif_unit_enum(input: TokenStream) -> TokenStream { /// /// This can be used from elixir in the following manner. /// -/// ```text -/// test "untagged enum transcoder" do -/// assert 123 == RustlerTest.untagged_enum_echo(123) -/// assert "Hello" == RustlerTest.untagged_enum_echo("Hello") -/// assert %AddStruct{lhs: 45, rhs: 123} = RustlerTest.untagged_enum_echo(%AddStruct{lhs: 45, rhs: 123}) -/// assert :invalid_variant == RustlerTest.untagged_enum_echo([1,2,3,4]) -/// end +/// ```elixir +/// test "untagged enum transcoder" do +/// assert 123 == RustlerTest.untagged_enum_echo(123) +/// assert "Hello" == RustlerTest.untagged_enum_echo("Hello") +/// assert %AddStruct{lhs: 45, rhs: 123} = RustlerTest.untagged_enum_echo(%AddStruct{lhs: 45, rhs: 123}) +/// assert :invalid_variant == RustlerTest.untagged_enum_echo([1,2,3,4]) +/// end /// ``` /// /// Note that the type of elixir return is dependent on the data in the enum and the actual enum diff --git a/rustler_codegen/src/nif.rs b/rustler_codegen/src/nif.rs new file mode 100644 index 00000000..c501d70e --- /dev/null +++ b/rustler_codegen/src/nif.rs @@ -0,0 +1,205 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::punctuated::Punctuated; +use syn::token::Comma; + +pub fn transcoder_decorator(args: syn::AttributeArgs, fun: syn::ItemFn) -> TokenStream { + let sig = &fun.sig; + let name = &sig.ident; + let inputs = &sig.inputs; + let flags = schedule_flag(args.to_owned()); + let function = fun.to_owned().into_token_stream(); + let arity = arity(inputs.clone()); + let decoded_terms = extract_inputs(inputs.clone()); + let argument_names = create_function_params(inputs.clone()); + let erl_func_name = extract_attr_value(args.clone(), "name") + .map(|ref n| syn::Ident::new(n, Span::call_site())) + .unwrap_or_else(|| name.clone()); + + quote! { + #[allow(non_camel_case_types)] + pub struct #name; + + impl rustler::Nif for #name { + const NAME: *const u8 = concat!(stringify!(#erl_func_name), "\0").as_ptr() as *const u8; + const ARITY: u32 = #arity; + const FLAGS: u32 = #flags as u32; + 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::>(); + + 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) + } + 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 + }; + } + } +} + +fn schedule_flag(args: syn::AttributeArgs) -> TokenStream { + let mut tokens = TokenStream::new(); + + let valid = ["DirtyCpu", "DirtyIo", "Normal"]; + + let flag = match extract_attr_value(args, "schedule") { + Some(value) => { + if valid.contains(&value.as_str()) { + syn::Ident::new(value.as_str(), Span::call_site()) + } else { + panic!("Invalid schedule option `{}`", value); + } + } + None => syn::Ident::new("Normal", Span::call_site()), + }; + + tokens.extend(quote! { rustler::SchedulerFlags::#flag }); + tokens +} + +fn extract_attr_value(args: syn::AttributeArgs, name: &str) -> Option { + use syn::{Lit, Meta, MetaNameValue, NestedMeta}; + + for arg in args.iter() { + if let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) = arg { + if path.is_ident(name) { + if let Lit::Str(lit) = lit { + return Some(lit.value()); + } + } + } + } + + None +} + +fn extract_inputs(inputs: Punctuated) -> TokenStream { + let mut tokens = TokenStream::new(); + let mut idx = 0 as usize; + + for item in inputs.iter() { + if let syn::FnArg::Typed(ref typed) = item { + let name = &typed.pat; + + match &*typed.ty { + syn::Type::Reference(typ) => { + let decoder = quote! { + let #name: #typ = match args[#idx].decode() { + Ok(value) => value, + Err(err) => return Err(err) + }; + }; + + tokens.extend(decoder); + } + syn::Type::Path(syn::TypePath { path, .. }) => { + let typ = &typed.ty; + let ident = path.segments.last().unwrap().ident.to_string(); + + match ident.as_ref() { + "Env" => { + continue; + } + "Term" => { + let arg = quote! { + let #name: #typ = args[#idx]; + }; + + tokens.extend(arg); + } + _ => { + let decoder = quote! { + let #name: #typ = match args[#idx].decode() { + Ok(value) => value, + Err(err) => return Err(err) + }; + }; + + tokens.extend(decoder); + } + } + } + other => { + panic!("unsupported input given: {:?}", other); + } + } + } else { + panic!("unsupported input given: {:?}", stringify!(&item)); + }; + idx += 1; + } + + tokens +} + +fn create_function_params(inputs: Punctuated) -> TokenStream { + let mut tokens = TokenStream::new(); + + for item in inputs.iter() { + let name = if let syn::FnArg::Typed(ref typed) = item { + &typed.pat + } else { + panic!("unsupported input given: {:?}", stringify!(&item)); + }; + + tokens.extend(quote!(#name,)); + } + + tokens +} + +fn arity(inputs: Punctuated) -> u32 { + let mut arity: u32 = 0; + + for (i, item) in inputs.iter().enumerate() { + if let syn::FnArg::Typed(ref typed) = item { + if let syn::Type::Path(syn::TypePath { path, .. }) = &*typed.ty { + let ident = path.segments.last().unwrap().ident.to_string(); + + if i == 0 && ident == "Env" { + continue; + } + + if ident == "Env" { + panic!("Env must be the first argument in NIF functions"); + } + }; + } else { + panic!("unsupported input given: {:?}", stringify!(&item)); + }; + arity += 1; + } + + arity +} diff --git a/rustler_mix/priv/templates/basic/src/lib.rs b/rustler_mix/priv/templates/basic/src/lib.rs index 9533de2f..923cf822 100644 --- a/rustler_mix/priv/templates/basic/src/lib.rs +++ b/rustler_mix/priv/templates/basic/src/lib.rs @@ -1,25 +1,6 @@ -use rustler::{Encoder, Env, Error, Term}; - -mod atoms { - rustler::atoms! { - ok, - // error, - // __true__ = "true", - // __false__ = "false" - } -} - -rustler::init! { - "<%= native_module %>", - [ - ("add", 2, add) - ], - None +#[rustler::nif] +fn add(a: i64, b: i64) -> i64 { + a + b } -fn add<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result, Error> { - let num1: i64 = args[0].decode()?; - let num2: i64 = args[1].decode()?; - - Ok((atoms::ok(), num1 + num2).encode(env)) -} +rustler::init!("<%= native_module %>", [add]); diff --git a/rustler_mix/test.sh b/rustler_mix/test.sh index ac7533bf..f33c7943 100755 --- a/rustler_mix/test.sh +++ b/rustler_mix/test.sh @@ -30,7 +30,7 @@ cd $tmp mix new test_rustler_mix cd test_rustler_mix -cat >mix.exs <<_ +cat >mix.exs <mix.exs <<_ +cat >mix.exs < lib/rustler_mix_test.ex -cat >test/rustler_mix_test_test.exs <<_ +cat >test/rustler_mix_test_test.exs <(env: Env<'a>, _load_info: Term<'a>) -> bool { +fn load(env: rustler::Env, _: rustler::Term) -> bool { test_resource::on_load(env); - test_atom::on_load(env); true } diff --git a/rustler_tests/native/rustler_test/src/test_atom.rs b/rustler_tests/native/rustler_test/src/test_atom.rs index 3942daea..5cdd64fd 100644 --- a/rustler_tests/native/rustler_test/src/test_atom.rs +++ b/rustler_tests/native/rustler_test/src/test_atom.rs @@ -1,28 +1,27 @@ -use rustler::types::{atom::Atom, binary::Binary}; -use rustler::{Env, NifResult, Term}; +use rustler::{Atom, Binary, Env, NifResult, Term}; mod atoms { rustler::atoms! { ok } } -pub fn on_load(_env: Env) {} - -pub fn atom_to_string<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - args[0].atom_to_string() +#[rustler::nif] +pub fn atom_to_string(atom: Term) -> NifResult { + atom.atom_to_string() } -pub fn atom_equals_ok<'a>(_env: Env<'a>, args: &[Term<'a>]) -> bool { - atoms::ok() == args[0] +#[rustler::nif] +pub fn atom_equals_ok(atom: Atom) -> bool { + atoms::ok() == atom } -pub fn binary_to_atom<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let binary: Binary = args[0].decode()?; +#[rustler::nif] +pub fn binary_to_atom(env: Env, binary: Binary) -> NifResult { let atom = Atom::from_bytes(env, binary.as_slice())?; Ok(atom) } -pub fn binary_to_existing_atom<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let binary: Binary = args[0].decode()?; +#[rustler::nif] +pub fn binary_to_existing_atom(env: Env, binary: Binary) -> NifResult> { let atom = Atom::try_from_bytes(env, binary.as_slice())?; Ok(atom) } diff --git a/rustler_tests/native/rustler_test/src/test_binary.rs b/rustler_tests/native/rustler_test/src/test_binary.rs index 46bb9096..d41d6c9f 100644 --- a/rustler_tests/native/rustler_test/src/test_binary.rs +++ b/rustler_tests/native/rustler_test/src/test_binary.rs @@ -3,35 +3,33 @@ use std::io::Write; use rustler::types::binary::{Binary, OwnedBinary}; use rustler::{Env, Error, NifResult, Term}; -pub fn make_shorter_subbinary<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let binary: Binary = args[0].decode()?; +#[rustler::nif] +pub fn make_shorter_subbinary(binary: Binary) -> NifResult { let length: usize = binary.as_slice().len(); - Ok(binary.make_subbinary(1, length - 2)?) + binary.make_subbinary(1, length - 2) } -pub fn parse_integer<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let str_num: &str = args[0].decode()?; - let num: i64 = match ::std::str::FromStr::from_str(str_num) { - Ok(num) => num, - Err(_) => return Err(Error::BadArg), - }; - Ok(num) +#[rustler::nif] +pub fn parse_integer(string: &str) -> NifResult { + std::str::FromStr::from_str(string).map_err(|_| Error::BadArg) } -pub fn binary_new<'a>(env: Env<'a>, _args: &[Term<'a>]) -> NifResult> { +#[rustler::nif] +pub fn binary_new(env: Env) -> Binary { let mut binary = OwnedBinary::new(4).unwrap(); binary.as_mut_slice().write_all(&[1, 2, 3, 4]).unwrap(); - Ok(binary.release(env)) + binary.release(env) } -pub fn unowned_to_owned<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let in_binary: Binary = args[0].decode()?; - let mut copied = in_binary.to_owned().unwrap(); +#[rustler::nif] +pub fn unowned_to_owned<'a>(env: Env<'a>, binary: Binary<'a>) -> NifResult> { + let mut copied = binary.to_owned().unwrap(); copied.as_mut_slice()[0] = 1; Ok(copied.release(env)) } -pub fn realloc_shrink<'a>(env: Env<'a>, _args: &[Term<'a>]) -> NifResult> { +#[rustler::nif] +pub fn realloc_shrink(env: Env) -> Binary { let mut binary = OwnedBinary::new(8).unwrap(); binary .as_mut_slice() @@ -40,27 +38,24 @@ pub fn realloc_shrink<'a>(env: Env<'a>, _args: &[Term<'a>]) -> NifResult(env: Env<'a>, _args: &[Term<'a>]) -> NifResult> { +#[rustler::nif] +pub fn realloc_grow(env: Env) -> Binary { let mut binary = OwnedBinary::new(4).unwrap(); binary.as_mut_slice().write_all(&[1, 2, 3, 4]).unwrap(); binary.realloc_or_copy(5); binary.as_mut_slice()[4] = 5; - Ok(binary.release(env)) + binary.release(env) } -pub fn encode_string<'a>(_env: Env<'a>, _args: &[Term<'a>]) -> NifResult<(String, &'static str)> { - Ok(("first".to_string(), "second")) +#[rustler::nif] +pub fn encode_string() -> (String, &'static str) { + ("first".to_string(), "second") } -pub fn decode_iolist<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let input: Binary = args[0].decode_as_binary()?; - Ok(input) +#[rustler::nif] +pub fn decode_iolist(binary: Term) -> NifResult { + binary.decode_as_binary() } - -// OwnedBinary::new -// OwnedBinary::from_unowned -// OwnedBinary::realloc -// OwnedBinary::realloc_or_copy diff --git a/rustler_tests/native/rustler_test/src/test_codegen.rs b/rustler_tests/native/rustler_test/src/test_codegen.rs index 12d6c2aa..e1b4c7a6 100644 --- a/rustler_tests/native/rustler_test/src/test_codegen.rs +++ b/rustler_tests/native/rustler_test/src/test_codegen.rs @@ -1,5 +1,5 @@ use rustler::types::truthy::Truthy; -use rustler::{Encoder, Env, NifResult, Term}; +use rustler::Encoder; use rustler::{NifMap, NifRecord, NifStruct, NifTuple, NifUnitEnum, NifUntaggedEnum}; #[derive(NifTuple)] @@ -8,9 +8,9 @@ pub struct AddTuple { rhs: i32, } -pub fn tuple_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let tuple: AddTuple = args[0].decode()?; - Ok(tuple) +#[rustler::nif] +pub fn tuple_echo(tuple: AddTuple) -> AddTuple { + tuple } #[derive(NifRecord)] @@ -22,9 +22,9 @@ pub struct AddRecord { rhs: i32, } -pub fn record_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let record: AddRecord = args[0].decode()?; - Ok(record) +#[rustler::nif] +pub fn record_echo(record: AddRecord) -> AddRecord { + record } #[derive(NifMap)] @@ -33,9 +33,9 @@ pub struct AddMap { rhs: i32, } -pub fn map_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let map: AddMap = args[0].decode()?; - Ok(map) +#[rustler::nif] +pub fn map_echo(map: AddMap) -> AddMap { + map } #[derive(Debug, NifStruct)] @@ -46,9 +46,9 @@ pub struct AddStruct { rhs: i32, } -pub fn struct_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let add_struct: AddStruct = args[0].decode()?; - Ok(add_struct) +#[rustler::nif] +pub fn struct_echo(add_struct: AddStruct) -> AddStruct { + add_struct } #[derive(NifUnitEnum)] @@ -57,9 +57,9 @@ pub enum UnitEnum { Baz, } -pub fn unit_enum_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let unit_enum: UnitEnum = args[0].decode()?; - Ok(unit_enum) +#[rustler::nif] +pub fn unit_enum_echo(unit_enum: UnitEnum) -> UnitEnum { + unit_enum } #[derive(NifUntaggedEnum)] @@ -70,9 +70,9 @@ pub enum UntaggedEnum { Bool(bool), } -pub fn untagged_enum_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let untagged_enum: UntaggedEnum = args[0].decode()?; - Ok(untagged_enum) +#[rustler::nif] +pub fn untagged_enum_echo(untagged_enum: UntaggedEnum) -> UntaggedEnum { + untagged_enum } #[derive(NifUntaggedEnum)] @@ -81,47 +81,41 @@ pub enum UntaggedEnumWithTruthy { Truthy(Truthy), } -pub fn untagged_enum_with_truthy<'a>( - _env: Env<'a>, - args: &[Term<'a>], -) -> NifResult { - let untagged_enum: UntaggedEnumWithTruthy = args[0].decode()?; - Ok(untagged_enum) +#[rustler::nif] +pub fn untagged_enum_with_truthy(untagged_enum: UntaggedEnumWithTruthy) -> UntaggedEnumWithTruthy { + untagged_enum } #[derive(NifTuple)] pub struct Newtype(i64); -pub fn newtype_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let newtype: Newtype = args[0].decode()?; - Ok(newtype) +#[rustler::nif] +pub fn newtype_echo(newtype: Newtype) -> Newtype { + newtype } #[derive(NifTuple)] pub struct TupleStruct(i64, i64, i64); -pub fn tuplestruct_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let tuplestruct: TupleStruct = args[0].decode()?; - Ok(tuplestruct) +#[rustler::nif] +pub fn tuplestruct_echo(tuplestruct: TupleStruct) -> TupleStruct { + tuplestruct } #[derive(NifRecord)] #[tag = "newtype"] pub struct NewtypeRecord(i64); -pub fn newtype_record_echo<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let newtype: NewtypeRecord = args[0].decode()?; - Ok(newtype) +#[rustler::nif] +pub fn newtype_record_echo(newtype: NewtypeRecord) -> NewtypeRecord { + newtype } #[derive(NifRecord)] #[tag = "tuplestruct"] pub struct TupleStructRecord(i64, i64, i64); -pub fn tuplestruct_record_echo<'a>( - _env: Env<'a>, - args: &[Term<'a>], -) -> NifResult { - let tuplestruct: TupleStructRecord = args[0].decode()?; - Ok(tuplestruct) +#[rustler::nif] +pub fn tuplestruct_record_echo(tuplestruct: TupleStructRecord) -> TupleStructRecord { + tuplestruct } diff --git a/rustler_tests/native/rustler_test/src/test_dirty.rs b/rustler_tests/native/rustler_test/src/test_dirty.rs index 1c7bbcc4..65082718 100644 --- a/rustler_tests/native/rustler_test/src/test_dirty.rs +++ b/rustler_tests/native/rustler_test/src/test_dirty.rs @@ -1,25 +1,24 @@ -use rustler::{Atom, Env, Term}; - -use std::time; +use rustler::Atom; +use std::time::Duration; mod atoms { - rustler::atoms! { - ok, - } + rustler::atoms! { ok } } // TODO: Make these realistic -pub fn dirty_cpu<'a>(_env: Env<'a>, _: &[Term<'a>]) -> Atom { - let duration = time::Duration::from_millis(100); - ::std::thread::sleep(duration); +#[rustler::nif(schedule = "DirtyCpu")] +pub fn dirty_cpu() -> Atom { + let duration = Duration::from_millis(100); + std::thread::sleep(duration); atoms::ok() } -pub fn dirty_io<'a>(_env: Env<'a>, _: &[Term<'a>]) -> Atom { - let duration = time::Duration::from_millis(100); - ::std::thread::sleep(duration); +#[rustler::nif(schedule = "DirtyIo")] +pub fn dirty_io() -> Atom { + let duration = Duration::from_millis(100); + std::thread::sleep(duration); atoms::ok() } diff --git a/rustler_tests/native/rustler_test/src/test_env.rs b/rustler_tests/native/rustler_test/src/test_env.rs index af9ad1a0..97a998f4 100644 --- a/rustler_tests/native/rustler_test/src/test_env.rs +++ b/rustler_tests/native/rustler_test/src/test_env.rs @@ -2,22 +2,21 @@ use rustler::env::{OwnedEnv, SavedTerm}; use rustler::types::atom; use rustler::types::list::ListIterator; use rustler::types::pid::Pid; -use rustler::{Encoder, Env, NifResult, Term}; +use rustler::{Atom, Encoder, Env, NifResult, Term}; use std::thread; // Send a message to several PIDs. -pub fn send_all<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let pids: Vec = args[0].decode()?; - let message = args[1]; - +#[rustler::nif] +pub fn send_all<'a>(env: Env<'a>, pids: Vec, msg: Term<'a>) -> Term<'a> { for pid in pids { - env.send(&pid, message); + env.send(&pid, msg); } - Ok(message) + msg } -pub fn sublists<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { +#[rustler::nif] +pub fn sublists<'a>(env: Env<'a>, list: Term<'a>) -> NifResult { // This is a "threaded NIF": it spawns a thread that sends a message back // to the calling thread later. let pid = env.pid(); @@ -25,14 +24,14 @@ pub fn sublists<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { // Our worker thread will need an environment. We can't ship `env` to the // other thread, because the Erlang VM is going to tear it down as soon as // we return from this NIF. So we use an `OwnedEnv`. - let mut my_env = OwnedEnv::new(); + let mut owned_env = OwnedEnv::new(); // Start by taking the argument (which should be a list), and copying it - // into `my_env`, and reversing it. We can use `my_env.save()` to save + // into `owned_env`, and reversing it. We can use `owned_env.save()` to save // terms in a form that doesn't have a lifetime parameter. - let saved_reversed_list = my_env.run(|env| -> NifResult { - let list_arg = args[0].in_env(env); - Ok(my_env.save(list_arg.list_reverse()?)) + let saved_reversed_list = owned_env.run(|env| -> NifResult { + let list_arg = list.in_env(env); + Ok(owned_env.save(list_arg.list_reverse()?)) })?; // Start the worker thread. This `move` closure takes ownership of both @@ -40,7 +39,7 @@ pub fn sublists<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { thread::spawn(move || { // Use `.send()` to get a `Env` from our `OwnedEnv`, // run some rust code, and finally send the result back to `pid`. - my_env.send_and_clear(&pid, |env| { + owned_env.send_and_clear(&pid, |env| { let result: NifResult = (|| { let reversed_list = saved_reversed_list.load(env); let iter: ListIterator = reversed_list.decode()?; @@ -65,5 +64,5 @@ pub fn sublists<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { }); }); - Ok(atom::ok().to_term(env)) + Ok(atom::ok()) } diff --git a/rustler_tests/native/rustler_test/src/test_list.rs b/rustler_tests/native/rustler_test/src/test_list.rs index 3f80682c..0c4ea442 100644 --- a/rustler_tests/native/rustler_test/src/test_list.rs +++ b/rustler_tests/native/rustler_test/src/test_list.rs @@ -1,18 +1,16 @@ -use rustler::types::list::ListIterator; -use rustler::{Encoder, Env, Error, NifResult, Term}; - -pub fn sum_list<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let iter: ListIterator = args[0].decode()?; +use rustler::{Error, ListIterator, NifResult}; +#[rustler::nif] +pub fn sum_list(iter: ListIterator) -> NifResult { let res: Result, Error> = iter.map(|x| x.decode::()).collect(); match res { - Ok(result) => Ok(result.iter().sum::().encode(env)), + Ok(result) => Ok(result.iter().sum::()), Err(err) => Err(err), } } -pub fn make_list<'a>(env: Env<'a>, _args: &[Term<'a>]) -> NifResult> { - let list = vec![1, 2, 3]; - Ok(list.encode(env)) +#[rustler::nif] +pub fn make_list() -> Vec { + vec![1, 2, 3] } diff --git a/rustler_tests/native/rustler_test/src/test_map.rs b/rustler_tests/native/rustler_test/src/test_map.rs index a40fc32d..0e7b16b1 100644 --- a/rustler_tests/native/rustler_test/src/test_map.rs +++ b/rustler_tests/native/rustler_test/src/test_map.rs @@ -2,19 +2,16 @@ use rustler::types::map::MapIterator; use rustler::types::tuple::make_tuple; use rustler::{Encoder, Env, NifResult, Term}; -pub fn sum_map_values<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let iter: MapIterator = args[0].decode()?; - +#[rustler::nif] +pub fn sum_map_values(iter: MapIterator) -> NifResult { let res: NifResult> = iter.map(|(_key, value)| value.decode::()).collect(); let nums = res?; - let total: i64 = nums.into_iter().sum(); - Ok(total) + Ok(nums.into_iter().sum()) } -pub fn map_entries_sorted<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let iter: MapIterator = args[0].decode()?; - +#[rustler::nif] +pub fn map_entries_sorted<'a>(env: Env<'a>, iter: MapIterator<'a>) -> NifResult>> { let mut vec = vec![]; for (key, value) in iter { let key_string = key.decode::()?; @@ -26,12 +23,14 @@ pub fn map_entries_sorted<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let keys: Vec = args[0].decode()?; - let values: Vec = args[1].decode()?; - +#[rustler::nif] +pub fn map_from_arrays<'a>( + env: Env<'a>, + keys: Vec>, + values: Vec>, +) -> NifResult> { Term::map_from_arrays(env, &keys, &values) } diff --git a/rustler_tests/native/rustler_test/src/test_primitives.rs b/rustler_tests/native/rustler_test/src/test_primitives.rs index dc7d3933..922c452b 100644 --- a/rustler_tests/native/rustler_test/src/test_primitives.rs +++ b/rustler_tests/native/rustler_test/src/test_primitives.rs @@ -1,35 +1,28 @@ -use rustler::{Encoder, Env, NifResult, Term}; - -pub fn add_u32<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let lhs: u32 = args[0].decode()?; - let rhs: u32 = args[1].decode()?; - - Ok((lhs + rhs).encode(env)) +#[rustler::nif] +pub fn add_u32(a: u32, b: u32) -> u32 { + a + b } -pub fn add_i32<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let lhs: i32 = args[0].decode()?; - let rhs: i32 = args[1].decode()?; - Ok((lhs + rhs).encode(env)) +#[rustler::nif] +pub fn add_i32(a: i32, b: i32) -> i32 { + a + b } -pub fn echo_u8<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let num: u8 = args[0].decode()?; - Ok(num.encode(env)) +#[rustler::nif] +pub fn echo_u8(n: u8) -> u8 { + n } -pub fn option_inc<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let opt: Option = args[0].decode()?; - let incremented = opt.map(|num| num + 1.0); - Ok(incremented.encode(env)) +#[rustler::nif] +pub fn option_inc(opt: Option) -> Option { + opt.map(|num| num + 1.0) } -pub fn result_to_int<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let result: Result = args[0].decode()?; - let int_result = match result { +#[rustler::nif] +pub fn result_to_int(res: Result) -> Result { + match res { Ok(true) => Ok(1), Ok(false) => Ok(0), Err(errstr) => Err(format!("{}{}", errstr, errstr)), - }; - Ok(int_result.encode(env)) + } } diff --git a/rustler_tests/native/rustler_test/src/test_range.rs b/rustler_tests/native/rustler_test/src/test_range.rs index 2441b295..5470134e 100644 --- a/rustler_tests/native/rustler_test/src/test_range.rs +++ b/rustler_tests/native/rustler_test/src/test_range.rs @@ -1,9 +1,6 @@ -use rustler::{Encoder, Env, Error, Term}; use std::ops::RangeInclusive; -pub fn sum_range<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result, Error> { - let range: RangeInclusive = args[0].decode()?; - - let total: i64 = range.sum(); - Ok(total.encode(env)) +#[rustler::nif] +pub fn sum_range(range: RangeInclusive) -> i64 { + range.sum() } diff --git a/rustler_tests/native/rustler_test/src/test_resource.rs b/rustler_tests/native/rustler_test/src/test_resource.rs index f2b2e817..a21e9be8 100644 --- a/rustler_tests/native/rustler_test/src/test_resource.rs +++ b/rustler_tests/native/rustler_test/src/test_resource.rs @@ -1,13 +1,13 @@ -use rustler::{Encoder, Env, NifResult, ResourceArc, Term}; +use rustler::{Env, ResourceArc}; use std::sync::RwLock; -struct TestResource { +pub struct TestResource { test_field: RwLock, } /// This one is designed to look more like pointer data, to increase the /// chance of segfaults if the implementation is wrong. -struct ImmutableResource { +pub struct ImmutableResource { a: u32, b: u32, } @@ -18,27 +18,24 @@ pub fn on_load(env: Env) -> bool { true } -pub fn resource_make<'a>(env: Env<'a>, _args: &[Term<'a>]) -> NifResult> { - let data = TestResource { +#[rustler::nif] +pub fn resource_make() -> ResourceArc { + ResourceArc::new(TestResource { test_field: RwLock::new(0), - }; - let resource = ResourceArc::new(data); - - Ok(resource.encode(env)) + }) } -pub fn resource_set_integer_field<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let resource: ResourceArc = args[0].decode()?; +#[rustler::nif] +pub fn resource_set_integer_field(resource: ResourceArc, n: i32) -> &'static str { let mut test_field = resource.test_field.write().unwrap(); - *test_field = args[1].decode()?; + *test_field = n; - Ok("ok".encode(env)) + "ok" } -pub fn resource_get_integer_field<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let resource: ResourceArc = args[0].decode()?; - let test_field = resource.test_field.read().unwrap(); - Ok(test_field.encode(env)) +#[rustler::nif] +pub fn resource_get_integer_field(resource: ResourceArc) -> i32 { + *resource.test_field.read().unwrap() } use std::sync::atomic::{AtomicUsize, Ordering}; @@ -62,13 +59,13 @@ impl Drop for ImmutableResource { } } -pub fn resource_make_immutable<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let u: u32 = args[0].decode()?; - Ok(ResourceArc::new(ImmutableResource::new(u)).encode(env)) +#[rustler::nif] +pub fn resource_make_immutable(u: u32) -> ResourceArc { + ResourceArc::new(ImmutableResource::new(u)) } -/// Count how many instances of `ImmutableResource` are currently alive globally. -pub fn resource_immutable_count<'a>(env: Env<'a>, _args: &[Term<'a>]) -> NifResult> { - let n = COUNT.load(Ordering::SeqCst) as u32; - Ok(n.encode(env)) +// Count how many instances of `ImmutableResource` are currently alive globally. +#[rustler::nif] +pub fn resource_immutable_count() -> u32 { + COUNT.load(Ordering::SeqCst) as u32 } diff --git a/rustler_tests/native/rustler_test/src/test_term.rs b/rustler_tests/native/rustler_test/src/test_term.rs index 9d052085..e23eb4db 100644 --- a/rustler_tests/native/rustler_test/src/test_term.rs +++ b/rustler_tests/native/rustler_test/src/test_term.rs @@ -1,14 +1,7 @@ -use rustler::Atom; -use rustler::{Env, NifResult, Term}; +use rustler::{Atom, Term}; use std::cmp::Ordering; use std::io::Write; -pub fn term_debug<'a>(_env: Env<'a>, args: &[Term<'a>]) -> NifResult { - let mut bytes: Vec = Vec::new(); - write!(&mut bytes, "{:?}", args[0]).expect("debug formatting should succeed"); - Ok(String::from_utf8_lossy(&bytes).to_string()) -} - mod atoms { rustler::atoms! { equal, @@ -17,11 +10,21 @@ mod atoms { } } -pub fn term_eq<'a>(_env: Env<'a>, args: &[Term<'a>]) -> bool { - args[0] == args[1] +#[rustler::nif] +pub fn term_debug(term: Term) -> String { + let mut bytes: Vec = Vec::new(); + write!(&mut bytes, "{:?}", term).expect("debug formatting should succeed"); + String::from_utf8_lossy(&bytes).to_string() } -pub fn term_cmp<'a>(_env: Env<'a>, args: &[Term<'a>]) -> Atom { - match Ord::cmp(&args[0], &args[1]) { + +#[rustler::nif] +pub fn term_eq<'a>(a: Term<'a>, b: Term<'a>) -> bool { + a == b +} + +#[rustler::nif] +pub fn term_cmp<'a>(a: Term<'a>, b: Term<'a>) -> Atom { + match Ord::cmp(&a, &b) { Ordering::Equal => atoms::equal(), Ordering::Less => atoms::less(), Ordering::Greater => atoms::greater(), diff --git a/rustler_tests/native/rustler_test/src/test_thread.rs b/rustler_tests/native/rustler_test/src/test_thread.rs index 374231c0..2cf853a2 100644 --- a/rustler_tests/native/rustler_test/src/test_thread.rs +++ b/rustler_tests/native/rustler_test/src/test_thread.rs @@ -1,9 +1,9 @@ use rustler::thread; use rustler::types::atom; -use rustler::{Encoder, Env, NifResult, Term}; -use std; +use rustler::{Atom, Encoder, Env}; -pub fn threaded_fac<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { +#[rustler::nif] +pub fn threaded_fac(env: Env, n: u64) -> Atom { // Multiply two numbers; panic on overflow. In Rust, the `*` operator wraps (rather than // panicking) in release builds. A test depends on this panicking, so we make sure it panics in // all builds. The test also checks the panic message. @@ -11,18 +11,16 @@ pub fn threaded_fac<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> a.checked_mul(b).expect("threaded_fac: integer overflow") } - let n: u64 = args[0].decode()?; thread::spawn::(env, move |thread_env| { let result = (1..=n).fold(1, mul); result.encode(thread_env) }); - Ok(atom::ok().to_term(env)) + atom::ok() } -pub fn threaded_sleep<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { - let msec: u64 = args[0].decode()?; - +#[rustler::nif] +pub fn threaded_sleep(env: Env, msec: u64) -> Atom { let q = msec / 1000; let r = (msec % 1000) as u32; thread::spawn::(env, move |thread_env| { @@ -30,5 +28,5 @@ pub fn threaded_sleep<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult msec.encode(thread_env) }); - Ok(atom::ok().to_term(env)) + atom::ok() } diff --git a/rustler_tests/test/atom_test.exs b/rustler_tests/test/atom_test.exs index 2ac970a7..72e1e01a 100644 --- a/rustler_tests/test/atom_test.exs +++ b/rustler_tests/test/atom_test.exs @@ -13,7 +13,7 @@ defmodule RustlerTest.AtomTest do test "binary to existing atom" do assert RustlerTest.binary_to_existing_atom("test_atom_nonexisting") == nil - RustlerTest.binary_to_atom("test_atom_nonexisting") + assert RustlerTest.binary_to_atom("test_atom_nonexisting") assert RustlerTest.binary_to_existing_atom("test_atom_nonexisting") != nil end @@ -21,9 +21,9 @@ defmodule RustlerTest.AtomTest do assert catch_error(RustlerTest.atom_to_string("already a string")) == :badarg end - test "term equals ok" do + test "atom equals ok" do assert RustlerTest.atom_equals_ok(:ok) refute RustlerTest.atom_equals_ok(:fish) - refute RustlerTest.atom_equals_ok("ok") + assert catch_error(RustlerTest.atom_equals_ok("ok")) == :badarg end end diff --git a/rustler_tests/test/term_test.exs b/rustler_tests/test/term_test.exs index fb78c687..6c8a1908 100644 --- a/rustler_tests/test/term_test.exs +++ b/rustler_tests/test/term_test.exs @@ -12,7 +12,7 @@ defmodule RustlerTest.TermTest do assert RustlerTest.term_debug(Enum.to_list(0..5)) == "[0,1,2,3,4,5]" assert RustlerTest.term_debug(Enum.to_list(0..1000)) == "[#{Enum.join(0..1000, ",")}]" - # assert RustlerTest.term_debug([[[[]],[]],[[]],[]]) == "[[[[]],[]],[[]],[]]" + assert RustlerTest.term_debug([[[[]], []], [[]], []]) == "[[[[]],[]],[[]],[]]" sues = Enum.map(1..500, fn i -> %{name: "Aunt Sue", id: i} end) sue_strs = Enum.map(1..500, fn i -> "\#{id=>#{i},name=><<\"Aunt Sue\">>}" end) From eaf72ca10bf9bc3a82e42a7c51cdeebd46a6dbfe Mon Sep 17 00:00:00 2001 From: Sonny Scroggin Date: Wed, 6 Nov 2019 21:14:32 -0600 Subject: [PATCH 2/2] Use named option for `load` --- rustler_codegen/src/init.rs | 55 +++++++++++++------- rustler_tests/native/rustler_test/src/lib.rs | 2 +- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/rustler_codegen/src/init.rs b/rustler_codegen/src/init.rs index 438658cc..c3fb4486 100644 --- a/rustler_codegen/src/init.rs +++ b/rustler_codegen/src/init.rs @@ -7,38 +7,57 @@ use syn::{Expr, Ident, Result, Token}; #[derive(Debug)] pub struct InitMacroInput { - module: syn::Lit, + name: syn::Lit, funcs: syn::ExprArray, - on_load: Option, + load: TokenStream, } impl Parse for InitMacroInput { fn parse(input: ParseStream) -> Result { - let module = syn::Lit::parse(input)?; + let name = syn::Lit::parse(input)?; let _comma = ::parse(input)?; let funcs = syn::ExprArray::parse(input)?; - let on_load = if input.peek(Token![,]) { - let _comma = ::parse(input)?; - Some(TokenStream::parse(input)?) - } else { - let none = Ident::new("None", Span::call_site()); - Some(quote!(#none)) - }; + let options = parse_expr_assigns(input); + let load = extract_option(options, "load"); + + Ok(InitMacroInput { name, funcs, load }) + } +} + +fn parse_expr_assigns(input: ParseStream) -> Vec { + let mut vec = Vec::new(); + + while let Ok(_) = ::parse(input) { + match syn::ExprAssign::parse(input) { + Ok(expr) => vec.push(expr), + Err(err) => panic!("{} (i.e. `load = load`)", err), + } + } + vec +} - Ok(InitMacroInput { - module, - funcs, - on_load, - }) +fn extract_option(args: Vec, name: &str) -> TokenStream { + for syn::ExprAssign { left, right, .. } in args.into_iter() { + if let syn::Expr::Path(syn::ExprPath { path, .. }) = &*left { + if let Some(ident) = path.get_ident() { + if *ident == name { + let value = *right.clone(); + return quote!(Some(#value)); + } + } + } } + + let none = Ident::new("None", Span::call_site()); + quote!(#none) } impl Into for InitMacroInput { fn into(self) -> proc_macro2::TokenStream { - let name = self.module; + let name = self.name; let num_of_funcs = self.funcs.elems.len(); let funcs = nif_funcs(self.funcs.elems); - let on_load = self.on_load; + let load = self.load; let inner = quote! { static mut NIF_ENTRY: Option = None; @@ -58,7 +77,7 @@ impl Into for InitMacroInput { ) -> rustler::codegen_runtime::c_int { unsafe { // TODO: If an unwrap ever happens, we will unwind right into C! Fix this! - rustler::codegen_runtime::handle_nif_init_call(#on_load, env, load_info) + rustler::codegen_runtime::handle_nif_init_call(#load, env, load_info) } } Some(nif_load) diff --git a/rustler_tests/native/rustler_test/src/lib.rs b/rustler_tests/native/rustler_test/src/lib.rs index 381177e5..c0863b8c 100644 --- a/rustler_tests/native/rustler_test/src/lib.rs +++ b/rustler_tests/native/rustler_test/src/lib.rs @@ -63,7 +63,7 @@ rustler::init!( test_dirty::dirty_io, test_range::sum_range, ], - Some(load) + load = load ); fn load(env: rustler::Env, _: rustler::Term) -> bool {