Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,36 @@ 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_NIF`, since the formers use template
specializations that are instantiated by the latter. If the order is reverse,
there is no guarantee that `FINE_NIF` 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:

```c++
static ThreadPool s_pool;

FINE_LOAD(env) {
s_pool = ThreadPool(std::thread::hardware_concurrency());
}

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

`FINE_LOAD` takes an identifier that will become the name of the variable
storing the `ErlNifEnv *`. While the NIF load callback returns an `int` to
designate success or failure, `FINE_LOAD` will instead check for a thrown
exception to determine success or failure.

<!-- Docs -->

## Prior work
Expand Down
66 changes: 52 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,41 @@ 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);

if (!Registration::init_resources(env)) {
return -1;
}
enum class CallbackStatus {
UNIMPLEMENTED,
IMPLEMENTED,
};

return 0;
}
template <CallbackStatus> struct OnLoad {
static void on_load(ErlNifEnv *) {}
};
} // namespace __private__

// Macros

#define FINE_LOAD(env) \
template <> \
struct fine::__private__::OnLoad< \
fine::__private__::CallbackStatus::IMPLEMENTED> { \
static void on_load(ErlNifEnv *); \
}; \
\
void fine::__private__:: \
OnLoad<fine::__private__::CallbackStatus::IMPLEMENTED>::on_load( \
[[maybe_unused]] ErlNifEnv *env)

// Macros

#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 +1362,28 @@ 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 *env, void **, ERL_NIF_TERM) { \
fine::__private__::init_atoms(env); \
\
if (!fine::__private__::init_resources(env)) { \
return -1; \
} \
\
try { \
fine::__private__::OnLoad< \
fine::__private__::CallbackStatus::IMPLEMENTED>::on_load(env); \
} 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; \
}; \
\
static ErlNifEntry entry = {ERL_NIF_MAJOR_VERSION, \
ERL_NIF_MINOR_VERSION, \
name, \
Expand Down
5 changes: 5 additions & 0 deletions test/c_src/finest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,11 @@ std::uint64_t hash_atom(ErlNifEnv *, fine::Atom atom) noexcept {
}
FINE_NIF(hash_atom, 0);

static bool s_loaded = false;
bool is_loaded(ErlNifEnv *) { return s_loaded; }
FINE_NIF(is_loaded, 0);
} // namespace finest

FINE_LOAD(env) { finest::s_loaded = true; }

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