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

Dynamic symbols on Unix #650

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
5 changes: 0 additions & 5 deletions .cargo/config.toml

This file was deleted.

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, DynNifCallbacks};

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
61 changes: 0 additions & 61 deletions rustler_mix/lib/rustler/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,69 +61,8 @@ defmodule Rustler.Compiler do
["rustup", "run", version, "cargo", "rustc"]
end

defp ensure_platform_requirements!(crate_path, config, {:unix, :darwin}) do
# We attempt to find a .cargo/config upwards from the crate_path, which
# has a target configuration for macos. If any such config exists, we
# assume that the config correctly encodes the needed linker arguments.

workspace_root = config.metadata["workspace_root"]

components =
crate_path
|> Path.relative_to(workspace_root)
|> Path.split()

{potential_config_files, _} =
Enum.map_reduce(["" | components], workspace_root, fn component, path ->
path = Path.join(path, component)
# See https://doc.rust-lang.org/cargo/reference/config.html, cargo
# accepts the config with and without a file extension of `.toml`.
file = Path.join([path, ".cargo", "config"])
file_with_extension = file <> ".toml"
{[file, file_with_extension], path}
end)

potential_config_files = List.flatten(potential_config_files)

has_macos_target_os_configuration? =
potential_config_files
|> Enum.filter(&File.exists?/1)
|> Enum.reverse()
|> Stream.map(&Toml.decode_file!/1)
|> Enum.find(&macos_target_configuration/1)

unless has_macos_target_os_configuration? do
raise """
Compiling on macOS requires special link args in order to compile
correctly.

To remove this error, please create .cargo/config.toml or .cargo/config
with the following content:

[target.'cfg(target_os = "macos")']
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]


See https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/1-Articles/executing_files.html
for more details.
"""
end
end

defp ensure_platform_requirements!(_, _, _), do: :ok

defp macos_target_configuration(toml) do
toml
|> Map.get("target", [])
|> Enum.filter(fn {key, _} ->
String.match?(key, ~r/(.*macos.*)|(.*darwin.*)/)
end)
|> Map.new()
end

defp make_no_default_features_flag(args, true), do: args
defp make_no_default_features_flag(args, false), do: args ++ ["--no-default-features"]

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"
116 changes: 62 additions & 54 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 All @@ -170,13 +175,35 @@ impl<'a> ApiBuilder for WinForwardersApiBuilder<'a> {
write!(self.0, "pub unsafe fn get_{}() -> ", name).unwrap();
write_variadic_fn_type(self.0, args, ret);
writeln!(self.0, " {{").unwrap();
writeln!(self.0, " DYN_NIF_CALLBACKS.{}.unwrap_unchecked()", name).unwrap();
writeln!(self.0, "}}\n").unwrap();
}
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,
" WIN_DYN_NIF_CALLBACKS.unchecked_unwrap().{}",
name
" filler.write(&mut self.{}, \"{}\0\");",
name, name
)
.unwrap();
writeln!(self.0, "}}\n").unwrap();
}
fn variadic_func(&mut self, ret: &str, name: &str, args: &str) {
self.func(ret, name, args);
}
fn dummy(&mut self, _name: &str) {}
}
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
36 changes: 36 additions & 0 deletions rustler_sys/src/functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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;
// ...
8 changes: 6 additions & 2 deletions rustler_sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ 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)]
#![allow(clippy::missing_safety_doc)]

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::*;
Loading
Loading