Skip to content

Commit

Permalink
Initial attempt to use dynamically acquired symbols on Unix
Browse files Browse the repository at this point in the history
  • Loading branch information
filmor committed Aug 19, 2024
1 parent aa94424 commit 0fcf06a
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 85 deletions.
3 changes: 1 addition & 2 deletions rustler/src/codegen_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ pub use crate::wrapper::{
NIF_ENV, NIF_MAJOR_VERSION, NIF_MINOR_VERSION, NIF_TERM,
};

#[cfg(windows)]
pub use rustler_sys::{TWinDynNifCallbacks, WIN_DYN_NIF_CALLBACKS};
pub use rustler_sys::{internal_set_symbols, internal_write_symbols};

pub unsafe trait NifReturnable {
unsafe fn into_returned(self, env: Env) -> NifReturned;
Expand Down
5 changes: 3 additions & 2 deletions rustler_codegen/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,15 @@ impl From<InitMacroInput> for proc_macro2::TokenStream {
#[cfg(unix)]
#[no_mangle]
extern "C" fn nif_init() -> *const rustler::codegen_runtime::DEF_NIF_ENTRY {
unsafe { rustler::codegen_runtime::internal_write_symbols() };
#inner
}

#[cfg(windows)]
#[no_mangle]
extern "C" fn nif_init(callbacks: *mut rustler::codegen_runtime::TWinDynNifCallbacks) -> *const rustler::codegen_runtime::DEF_NIF_ENTRY {
extern "C" fn nif_init(callbacks: *mut rustler::codegen_runtime::DynNifCallbacks) -> *const rustler::codegen_runtime::DEF_NIF_ENTRY {
unsafe {
rustler::codegen_runtime::WIN_DYN_NIF_CALLBACKS = Some(*callbacks);
rustler::codegen_runtime::internal_set_symbols(*callbacks);
}

#inner
Expand Down
2 changes: 1 addition & 1 deletion rustler_sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ nif_version_2_16 = ["nif_version_2_15"]
nif_version_2_17 = ["nif_version_2_16"]

[dependencies]
unreachable = "1.0"
libloading = "0.8"

[build-dependencies]
regex-lite = "0.1"
112 changes: 60 additions & 52 deletions rustler_sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,25 @@ use std::{env, fs};
pub const MIN_SUPPORTED_VERSION: (u32, u32) = (2, 14);
pub const MAX_SUPPORTED_VERSION: (u32, u32) = (2, 17);

const SNIPPET_NAME: &str = "nif_api.snippet";
const SNIPPET_NAME: &str = "nif_api.snippet.rs";

trait ApiBuilder {
fn init(&mut self) {}
fn finish(&mut self) {}

fn func(&mut self, ret: &str, name: &str, args: &str);
fn variadic_func(&mut self, ret: &str, name: &str, args: &str);
fn dummy(&mut self, name: &str);
}

#[derive(PartialEq, Eq, Clone, Copy)]
pub enum OsFamily {
Unix,
Win,
}

pub struct GenerateOptions {
pub ulong_size: usize,
pub nif_version: (u32, u32),
pub target_family: OsFamily,
}

enum OsFamily {
Win,
Unix,
}

fn write_ret(out: &mut String, ret: &str) {
Expand All @@ -51,31 +52,25 @@ pub struct BasicApiBuilder<'a>(&'a mut String);

impl<'a> ApiBuilder for BasicApiBuilder<'a> {
fn func(&mut self, ret: &str, name: &str, args: &str) {
writeln!(self.0, "extern \"C\" {{").unwrap();
writeln!(
self.0,
" /// See [{}](http://www.erlang.org/doc/man/erl_nif.html#{}) in the Erlang docs.",
name, name
)
.unwrap();

write!(self.0, " pub fn {}({})", name, args).unwrap();
write!(self.0, " pub extern \"C\" fn {}({})", name, args).unwrap();
write_ret(self.0, ret);
writeln!(self.0, ";").unwrap();

writeln!(self.0, "}}").unwrap();
}
fn variadic_func(&mut self, ret: &str, name: &str, args: &str) {
writeln!(self.0, "extern \"C\" {{").unwrap();
writeln!(self.0, " #[doc(hidden)]").unwrap();
writeln!(self.0, " #[link_name = \"{}\"]", name).unwrap();

write!(self.0, " pub fn _{}({}, ...)", name, args).unwrap();
write!(self.0, " pub extern \"C\" fn _{}({}, ...)", name, args).unwrap();
write_ret(self.0, ret);
writeln!(self.0, ";").unwrap();

writeln!(self.0, "}}\n").unwrap();

writeln!(
self.0,
"/// See [{}](http://www.erlang.org/doc/man/erl_nif.html#{}) in the Erlang docs.",
Expand All @@ -101,22 +96,32 @@ impl<'a> ApiBuilder for BasicApiBuilder<'a> {
fn dummy(&mut self, _name: &str) {}
}

pub struct WinCallbacksApiBuilder<'a>(&'a mut String);
impl<'a> ApiBuilder for WinCallbacksApiBuilder<'a> {
pub struct CallbacksApiBuilder<'a>(&'a mut String);
impl<'a> ApiBuilder for CallbacksApiBuilder<'a> {
fn init(&mut self) {
writeln!(self.0, "#[allow(dead_code)]").unwrap();
writeln!(self.0, "#[derive(Default, Copy, Clone)]").unwrap();
writeln!(self.0, "pub struct DynNifCallbacks {{").unwrap();
}

fn finish(&mut self) {
writeln!(self.0, "}}").unwrap();
}

fn func(&mut self, ret: &str, name: &str, args: &str) {
write!(self.0, " {}: ", name).unwrap();
write!(self.0, " {}: Option<", name).unwrap();
write_fn_type(self.0, args, ret);
writeln!(self.0, ",").unwrap();
writeln!(self.0, ">,").unwrap();
}
fn variadic_func(&mut self, ret: &str, name: &str, args: &str) {
write!(self.0, " {}: ", name).unwrap();
write!(self.0, " {}: Option<", name).unwrap();
write_variadic_fn_type(self.0, args, ret);
writeln!(self.0, ",").unwrap();
writeln!(self.0, ">,").unwrap();
}
fn dummy(&mut self, name: &str) {
write!(self.0, " {}: ", name).unwrap();
write!(self.0, " {}: Option<", name).unwrap();
write_fn_type(self.0, "", "");
writeln!(self.0, ",").unwrap();
writeln!(self.0, ">,").unwrap();
}
}

Expand Down Expand Up @@ -144,7 +149,7 @@ impl<'a> ApiBuilder for WinForwardersApiBuilder<'a> {
writeln!(self.0, "{{").unwrap();
writeln!(
self.0,
" (WIN_DYN_NIF_CALLBACKS.unchecked_unwrap().{})({})",
" (DYN_NIF_CALLBACKS.{}.unwrap_unchecked())({})",
name, args_names
)
.unwrap();
Expand Down Expand Up @@ -172,7 +177,7 @@ impl<'a> ApiBuilder for WinForwardersApiBuilder<'a> {
writeln!(self.0, " {{").unwrap();
writeln!(
self.0,
" WIN_DYN_NIF_CALLBACKS.unchecked_unwrap().{}",
" DYN_NIF_CALLBACKS.{}.unwrap_unchecked()",
name
)
.unwrap();
Expand All @@ -181,6 +186,28 @@ impl<'a> ApiBuilder for WinForwardersApiBuilder<'a> {
fn dummy(&mut self, _name: &str) {}
}

pub struct WriterBuilder<'a>(&'a mut String);

impl<'a> ApiBuilder for WriterBuilder<'a> {
fn init(&mut self) {
write!(
self.0,
"impl DynNifCallbacks {{\n fn write_symbols<T: DynNifFiller>(&mut self, filler: T) {{\n"
)
.unwrap();
}
fn finish(&mut self) {
writeln!(self.0, " }}\n}}").unwrap();
}
fn func(&mut self, _ret: &str, name: &str, _args: &str) {
writeln!(self.0, " filler.write(&mut self.{}, \"{}\0\");", name, name).unwrap();
}
fn variadic_func(&mut self, ret: &str, name: &str, args: &str) {
self.func(ret, name, args);
}
fn dummy(&mut self, _name: &str) {}
}

fn generate(opts: &GenerateOptions) -> String {
let mut out = String::new();

Expand All @@ -202,31 +229,10 @@ fn generate(opts: &GenerateOptions) -> String {
)
.unwrap();

// Basic
if opts.target_family == OsFamily::Win {
writeln!(out, "#[allow(dead_code)]").unwrap();
writeln!(out, "#[derive(Copy, Clone)]").unwrap();
writeln!(out, "pub struct TWinDynNifCallbacks {{").unwrap();
build_api(&mut WinCallbacksApiBuilder(&mut out), opts);
writeln!(out, "}}").unwrap();

// The line below would be the "faithful" reproduction of the NIF Win API, but Rust
// is currently not allowing statics to be uninitialized (1.3 beta). Revisit this when
// RFC911 is implemented (or some other mechanism)
// writeln!(out, "pub static mut WIN_DYN_NIF_CALLBACKS: TWinDynNifCallbacks = unsafe {{ std::mem::uninitialized() }};\n").unwrap();

// The work-around is to use Option. The problem here is that we have to do an unwrap() for
// each API call which is extra work.
writeln!(
out,
"pub static mut WIN_DYN_NIF_CALLBACKS:Option<TWinDynNifCallbacks> = None;\n"
)
.unwrap();
build_api(&mut CallbacksApiBuilder(&mut out), opts);

build_api(&mut WinForwardersApiBuilder(&mut out), opts);
} else {
build_api(&mut BasicApiBuilder(&mut out), opts);
}
build_api(&mut WinForwardersApiBuilder(&mut out), opts);
build_api(&mut WriterBuilder(&mut out), opts);

if opts.ulong_size == 4 {
writeln!(out, "use std::os::raw::{{c_ulonglong, c_longlong}};").unwrap();
Expand Down Expand Up @@ -258,6 +264,7 @@ pub unsafe fn enif_get_uint64(env: *mut ErlNifEnv, term: ERL_NIF_TERM, ip: *mut
}

fn build_api(b: &mut dyn ApiBuilder, opts: &GenerateOptions) {
b.init();
b.func("*mut c_void", "enif_priv_data", "arg1: *mut ErlNifEnv");
b.func("*mut c_void", "enif_alloc", "size: size_t");
b.func("", "enif_free", "ptr: *mut c_void");
Expand Down Expand Up @@ -826,7 +833,7 @@ fn build_api(b: &mut dyn ApiBuilder, opts: &GenerateOptions) {
// Func("int", "enif_ioq_peek_head", "ErlNifEnv *env, ErlNifIOQueue *q, size_t *size, ERL_NIF_TERM *head"),
// Func("char*, "enif_mutex_name", "ErlNifMutex*"),
// Func("char*, "enif_cond_name", "ErlNifCond*"),
// Func("char*, "enif_rwlock_name", "ErlNifRWLock*"),
// Func("char*, "enif_rwlock_name", "ErlNifRWLock*"),buid
// Func("char*, "enif_thread_name", "ErlNifTid"),
b.dummy("dummy_enif_ioq_peek_head");
b.dummy("dummy_enif_mutex_name");
Expand Down Expand Up @@ -883,6 +890,8 @@ fn build_api(b: &mut dyn ApiBuilder, opts: &GenerateOptions) {
// handling uses the `TWinDynNifCallbacks` struct.
//
// The correct order can (currently) by derived from the `erl_nif_api_funcs.h` header.

b.finish();
}

fn get_nif_version_from_features() -> (u32, u32) {
Expand Down Expand Up @@ -936,7 +945,6 @@ fn main() {
let opts = GenerateOptions {
ulong_size,
nif_version,
target_family,
};
let api = generate(&opts);

Expand Down
37 changes: 37 additions & 0 deletions rustler_sys/src/functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use crate::nif_filler::{self, DynNifFiller};
use crate::types::*;

static mut DYN_NIF_CALLBACKS: DynNifCallbacks = unsafe {
std::mem::MaybeUninit::zeroed().assume_init()
};

pub unsafe fn internal_set_symbols(callbacks: DynNifCallbacks) {
DYN_NIF_CALLBACKS = callbacks;
}

pub unsafe fn internal_write_symbols() {
let filler = nif_filler::new();
DYN_NIF_CALLBACKS.write_symbols(filler);
}

/// See [enif_make_pid](http://erlang.org/doc/man/erl_nif.html#enif_make_pid) in the Erlang docs
pub unsafe fn enif_make_pid(_env: *mut ErlNifEnv, pid: ErlNifPid) -> ERL_NIF_TERM {
pid.pid
}

/// See [enif_compare_pids](http://erlang.org/doc/man/erl_nif.html#enif_compare_pids) in the Erlang docs
pub unsafe fn enif_compare_pids(pid1: *const ErlNifPid, pid2: *const ErlNifPid) -> c_int {
// Mimics the implementation of the enif_compare_pids macro
enif_compare((*pid1).pid, (*pid2).pid)
}

// Include the file generated by `build.rs`.
include!(concat!(env!("OUT_DIR"), "/nif_api.snippet.rs"));
// example of included content:
// extern "C" {
// pub fn enif_priv_data(arg1: *mut ErlNifEnv) -> *mut c_void;
// pub fn enif_alloc(size: size_t) -> *mut c_void;
// pub fn enif_free(ptr: *mut c_void);
// pub fn enif_is_atom(arg1: *mut ErlNifEnv, term: ERL_NIF_TERM) -> c_int;
// pub fn enif_is_binary(arg1: *mut ErlNifEnv, term: ERL_NIF_TERM) -> c_int;
// ...
7 changes: 5 additions & 2 deletions rustler_sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Low level Rust bindings to the [Erlang NIF API](http://www.erlang.org/doc/man/er
// Don't throw warnings on NIF naming conventions
#![allow(non_camel_case_types)]

pub mod rustler_sys_api;
mod functions;
mod nif_filler;
mod types;

pub use crate::rustler_sys_api::*;
pub use crate::functions::*;
pub use crate::types::*;
50 changes: 50 additions & 0 deletions rustler_sys/src/nif_filler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
pub(crate) trait DynNifFiller {
fn write<T: Copy>(&self, field: &mut Option<T>, name: &str);
}

pub struct NullNifFiller;
impl DynNifFiller for NullNifFiller {
fn write<T: Copy>(&self, _field: &mut Option<T>, _name: &str) {}
}

#[cfg(not(target_os = "windows"))]
mod internal {
use std::ffi::OsStr;

use super::DynNifFiller;
use libloading::os::unix::{Library, RTLD_GLOBAL, RTLD_NOW};

pub(crate) struct DlsymNifFiller {
lib: libloading::Library,
}

impl DlsymNifFiller {
pub(crate) fn new() -> Self {
let lib = unsafe { Library::open(None::<&OsStr>, RTLD_NOW | RTLD_GLOBAL) };
let res = DlsymNifFiller {
lib: lib.unwrap().into(),
};
res
}
}

impl DynNifFiller for DlsymNifFiller {
fn write<T: Copy>(&self, field: &mut Option<T>, name: &str) {
let symbol = unsafe { self.lib.get::<T>(name.as_bytes()).unwrap() };
*field = Some(*symbol);
}
}

pub(crate) fn new() -> impl DynNifFiller {
DlsymNifFiller::new()
}
}

#[cfg(target_os = "windows")]
mod internal {
pub fn new() -> impl DynNifFiller {
NullNifFiller
}
}

pub(crate) use internal::new;
Loading

0 comments on commit 0fcf06a

Please sign in to comment.