Skip to content
Open
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
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,52 @@ Attempting to decode STL containers making use of `std::pmr::polymorphic_allocat
will result in the `std::pmr::get_default_resource()` memory resource being
used.

## Global Callbacks

The Erlang NIF API allows the creation of global callbacks. This section
describes how to leverage these callbacks while using Fine.

Callbacks MUST be used before `FINE_INIT`, since the formers use template
specializations that are instantiated by the latter. If the order is reverse,
there is no guarantee that `FINE_INIT` will actually invoke the callbacks.

### Load

The NIF load callback is called by the ERTS when the NIFs are being loaded by
`:erlang.load_nif/2`. Fine allows customizing the behavior of the load callback
using the `FINE_LOAD` macro before the `FINE_INIT` macro:

```c++
static std::unique_ptr<ThreadPool> s_pool;

static void load(ErlNifEnv* caller_env, void** priv_data, fine::Term load_info)
{
const auto thread_count = fine::decode<std::uint64_t>(caller_env, load_info);
s_pool = std::make_unique<FixedThreadPool>(thread_count);
}

FINE_LOAD(load);
FINE_INIT("Elixir.MyLib.NIF");
```

### Unload

The NIF unload callback is called by the ERTS when the NIFs are being unloaded
from the runtime. Fine allows customizing the behavior of the unload callback
using the `FINE_UNLOAD` macro before the `FINE_INIT` macro:

```c++
static std::unique_ptr<ThreadPool> s_pool;

static void unload(ErlNifEnv* caller_env, void** priv_data)
{
s_pool.stop();
}

FINE_UNLOAD(unload);
FINE_INIT("Elixir.MyLib.NIF);
```

<!-- Docs -->

## Prior work
Expand Down
94 changes: 80 additions & 14 deletions c_include/fine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ template <typename T, typename SFINAE = void> struct Encoder;

namespace __private__ {
std::vector<ErlNifFunc> &get_erl_nif_funcs();
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info);
void init_atoms(ErlNifEnv *env);
bool init_resources(ErlNifEnv *env);
} // namespace __private__

// Definitions
Expand Down Expand Up @@ -97,8 +98,7 @@ class Atom {
friend struct Decoder<Atom>;
friend struct ::std::hash<Atom>;

friend int __private__::load(ErlNifEnv *env, void **priv_data,
ERL_NIF_TERM load_info);
friend void __private__::init_atoms(ErlNifEnv *env);

// We accumulate all globally defined atom objects and create the
// terms upfront as part of init (called from the NIF load callback).
Expand Down Expand Up @@ -1221,8 +1221,7 @@ class Registration {

friend std::vector<ErlNifFunc> &__private__::get_erl_nif_funcs();

friend int __private__::load(ErlNifEnv *env, void **priv_data,
ERL_NIF_TERM load_info);
friend bool __private__::init_resources(ErlNifEnv *env);

inline static std::vector<std::tuple<ErlNifResourceType **, const char *,
void (*)(ErlNifEnv *, void *)>>
Expand Down Expand Up @@ -1295,23 +1294,61 @@ constexpr unsigned int nif_arity(Ret (*)(Args...)) {
}

namespace __private__ {
void init_atoms(ErlNifEnv *env) { fine::Atom::init_atoms(env); }

bool init_resources(ErlNifEnv *env) {
return fine::Registration::init_resources(env);
}

inline std::vector<ErlNifFunc> &get_erl_nif_funcs() {
return Registration::erl_nif_funcs;
}

inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
Atom::init_atoms(env);
enum class CallbackStatus {
UNIMPLEMENTED,
IMPLEMENTED,
};

if (!Registration::init_resources(env)) {
return -1;
}
template <CallbackStatus> struct OnLoad {
static void on_load(ErlNifEnv *, void **, ERL_NIF_TERM) {}
};

return 0;
}
template <CallbackStatus> struct OnUnload {
static void on_unload(ErlNifEnv *, void *) {}
};
} // namespace __private__

// Macros

#define FINE_LOAD(function) \
static_assert(std::is_invocable_v<decltype((function)), ErlNifEnv *, \
void **, ERL_NIF_TERM>, \
"function must have the signature: void(*)(ErlNifEnv* " \
"caller_env, void** priv_data, fine::Term load_info)"); \
template <> \
struct fine::__private__::OnLoad< \
fine::__private__::CallbackStatus::IMPLEMENTED> { \
static void on_load(ErlNifEnv *caller_env, void **priv_data, \
ERL_NIF_TERM load_info) { \
(function)(caller_env, priv_data, load_info); \
} \
}; \
static_assert(true, "require a semicolon after the macro")

#define FINE_UNLOAD(function) \
static_assert( \
std::is_invocable_v<decltype((function)), ErlNifEnv *, void *>, \
"function must have the signature: void(*)(ErlNifEnv* " \
"caller_env, void* priv_data)"); \
template <> \
struct fine::__private__::OnUnload< \
fine::__private__::CallbackStatus::IMPLEMENTED> { \
static void on_unload(ErlNifEnv *caller_env, void *priv_data) { \
(function)(caller_env, priv_data); \
} \
}; \
static_assert(true, "require a semicolon after the macro")

#define FINE_NIF(name, flags) \
static ERL_NIF_TERM name##_nif(ErlNifEnv *env, int argc, \
const ERL_NIF_TERM argv[]) { \
Expand Down Expand Up @@ -1345,7 +1382,36 @@ inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
auto &nif_funcs = fine::__private__::get_erl_nif_funcs(); \
auto num_funcs = static_cast<int>(nif_funcs.size()); \
auto funcs = nif_funcs.data(); \
auto load = fine::__private__::load; \
\
const auto load = [](ErlNifEnv *caller_env, void **priv_data, \
ERL_NIF_TERM load_info) noexcept { \
fine::__private__::init_atoms(caller_env); \
\
if (!fine::__private__::init_resources(caller_env)) { \
return -1; \
} \
\
try { \
fine::__private__:: \
OnLoad<fine::__private__::CallbackStatus::IMPLEMENTED>::on_load( \
caller_env, priv_data, load_info); \
} catch (const std::exception &e) { \
enif_fprintf(stderr, "unhandled exception: %s\n", e.what()); \
return -1; \
} catch (...) { \
enif_fprintf(stderr, "unhandled throw\n"); \
return -1; \
} \
\
return 0; \
}; \
\
const auto unload = [](ErlNifEnv *caller_env, void *priv_data) noexcept { \
fine::__private__:: \
OnUnload<fine::__private__::CallbackStatus::IMPLEMENTED>::on_unload( \
caller_env, priv_data); \
}; \
\
static ErlNifEntry entry = {ERL_NIF_MAJOR_VERSION, \
ERL_NIF_MINOR_VERSION, \
name, \
Expand All @@ -1354,7 +1420,7 @@ inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
load, \
NULL, \
NULL, \
NULL, \
unload, \
ERL_NIF_VM_VARIANT, \
1, \
sizeof(ErlNifResourceTypeInit), \
Expand Down
4 changes: 1 addition & 3 deletions example/c_src/example.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#include <fine.hpp>

int64_t add(ErlNifEnv *env, int64_t x, int64_t y) {
return x + y;
}
int64_t add(ErlNifEnv *env, int64_t x, int64_t y) { return x + y; }

FINE_NIF(add, 0);
FINE_INIT("Elixir.Example");
9 changes: 9 additions & 0 deletions test/c_src/finest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,15 @@ std::uint64_t hash_atom(ErlNifEnv *, fine::Atom atom) noexcept {
}
FINE_NIF(hash_atom, 0);

static bool s_loaded = false;
static void load(ErlNifEnv *, void **, fine::Term) { s_loaded = true; }

bool is_loaded(ErlNifEnv *) { return s_loaded; }
FINE_NIF(is_loaded, 0);

static void unload(ErlNifEnv *, void *) {}
} // namespace finest

FINE_LOAD(finest::load);
FINE_UNLOAD(finest::unload);
FINE_INIT("Elixir.Finest.NIF");
2 changes: 2 additions & 0 deletions test/lib/finest/nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,7 @@ defmodule Finest.NIF do
def hash_term(_term), do: err!()
def hash_atom(_term), do: err!()

def is_loaded(), do: err!()

defp err!(), do: :erlang.nif_error(:not_loaded)
end
6 changes: 6 additions & 0 deletions test/test/finest_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -502,4 +502,10 @@ defmodule FinestTest do
end
end
end

describe "callbacks" do
test "load" do
assert NIF.is_loaded()
end
end
end