From b4bcdd552cf4a9ecd80d439d4d769c01476f47bf Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Thu, 11 Sep 2025 15:08:36 -0400 Subject: [PATCH 1/8] Allow definition of a `load` NIF callback It is sometimes necessary for a NIF to have some global initialization logic in its load callback, like we already have for atom and resource registration. This commit inlines the `fine::__private__::load` function in the `FINE_INIT` macro to delay template instantiation of a new `fine::__private__::OnLoad` struct that can be overriden by a user using the `FINE_LOAD(env)` macro to provide customized initialization logic, while still registering atoms and resources. --- README.md | 30 ++++++++++++++++++ c_include/fine.hpp | 66 ++++++++++++++++++++++++++++++--------- test/c_src/finest.cpp | 5 +++ test/lib/finest/nif.ex | 2 ++ test/test/finest_test.exs | 6 ++++ 5 files changed, 95 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 55dcc85..363e4b0 100644 --- a/README.md +++ b/README.md @@ -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. + ## Prior work diff --git a/c_include/fine.hpp b/c_include/fine.hpp index 0041d3f..a97446a 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -47,7 +47,8 @@ template struct Encoder; namespace __private__ { std::vector &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 @@ -97,8 +98,7 @@ class Atom { friend struct Decoder; friend struct ::std::hash; - 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). @@ -1221,8 +1221,7 @@ class Registration { friend std::vector &__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> @@ -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 &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 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::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[]) { \ @@ -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(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, \ diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index c04ef4f..4ae674d 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -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"); diff --git a/test/lib/finest/nif.ex b/test/lib/finest/nif.ex index 6f3426a..104371a 100644 --- a/test/lib/finest/nif.ex +++ b/test/lib/finest/nif.ex @@ -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 diff --git a/test/test/finest_test.exs b/test/test/finest_test.exs index 6570c78..e9cdc2e 100644 --- a/test/test/finest_test.exs +++ b/test/test/finest_test.exs @@ -502,4 +502,10 @@ defmodule FinestTest do end end end + + describe "callbacks" do + test "load" do + assert NIF.is_loaded() + end + end end From d190c1494aba4fab0823a2ac40be402bea130d49 Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Mon, 22 Sep 2025 16:50:05 -0400 Subject: [PATCH 2/8] Rework macro to accept a function instead --- c_include/fine.hpp | 30 +++++++++++++++++------------- example/c_src/example.cpp | 4 +--- test/c_src/finest.cpp | 5 +++-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/c_include/fine.hpp b/c_include/fine.hpp index a97446a..366c3a8 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -1316,18 +1316,20 @@ template struct OnLoad { // Macros -#define FINE_LOAD(env) \ +#define FINE_LOAD(function) \ + static_assert(std::is_invocable_v, \ + "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 *); \ + static void on_load(ErlNifEnv *caller_env, void **priv_data, \ + ERL_NIF_TERM load_info) { \ + (function)(caller_env, priv_data, load_info); \ + } \ }; \ - \ - void fine::__private__:: \ - OnLoad::on_load( \ - [[maybe_unused]] ErlNifEnv *env) - -// Macros + static_assert(true, "require a semicolon after the macro") #define FINE_NIF(name, flags) \ static ERL_NIF_TERM name##_nif(ErlNifEnv *env, int argc, \ @@ -1363,16 +1365,18 @@ template struct OnLoad { auto num_funcs = static_cast(nif_funcs.size()); \ auto funcs = nif_funcs.data(); \ \ - const auto load = [](ErlNifEnv *env, void **, ERL_NIF_TERM) { \ - fine::__private__::init_atoms(env); \ + const auto load = [](ErlNifEnv *caller_env, void **priv_data, \ + ERL_NIF_TERM load_info) { \ + fine::__private__::init_atoms(caller_env); \ \ - if (!fine::__private__::init_resources(env)) { \ + if (!fine::__private__::init_resources(caller_env)) { \ return -1; \ } \ \ try { \ - fine::__private__::OnLoad< \ - fine::__private__::CallbackStatus::IMPLEMENTED>::on_load(env); \ + fine::__private__:: \ + OnLoad::on_load( \ + caller_env, priv_data, load_info); \ } catch (const std::exception &e) { \ enif_fprintf(stderr, "unhandled exception: %s\n", e.what()); \ return -1; \ diff --git a/example/c_src/example.cpp b/example/c_src/example.cpp index 86646f9..efcb7a4 100644 --- a/example/c_src/example.cpp +++ b/example/c_src/example.cpp @@ -1,8 +1,6 @@ #include -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"); diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index 4ae674d..03e2188 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -463,10 +463,11 @@ 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); } // namespace finest -FINE_LOAD(env) { finest::s_loaded = true; } - +FINE_LOAD(finest::load); FINE_INIT("Elixir.Finest.NIF"); From f19cd5ecfa8c06b8f17b3e83fa40208ef6c45f1d Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Mon, 22 Sep 2025 16:55:53 -0400 Subject: [PATCH 3/8] Update documentation --- README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 363e4b0..25ed8a6 100644 --- a/README.md +++ b/README.md @@ -609,31 +609,29 @@ used. 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 +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_NIF` will actually invoke the callbacks. +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: +using the `FINE_LOAD` macro before the `FINE_INIT` macro: ```c++ -static ThreadPool s_pool; +static std::unique_ptr s_pool; -FINE_LOAD(env) { - s_pool = ThreadPool(std::thread::hardware_concurrency()); +static void load(ErlNifEnv* caller_env, void** priv_data, fine::Term load_info) +{ + const auto thread_count = fine::decode(caller_env, load_info); + s_pool = std::make_unique(thread_count); } +FINE_LOAD(load); 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. - ## Prior work From e3a42a964e3a8d901a59e67a9ac3c07422aa44f7 Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Mon, 22 Sep 2025 17:06:27 -0400 Subject: [PATCH 4/8] Add unload callback too --- README.md | 18 ++++++++++++++++++ c_include/fine.hpp | 30 +++++++++++++++++++++++++++--- test/c_src/finest.cpp | 5 ++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 25ed8a6..9720afd 100644 --- a/README.md +++ b/README.md @@ -632,6 +632,24 @@ 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 s_pool; + +static void unload(ErlNifEnv* caller_env, void** priv_data) +{ + s_pool.stop(); +} + +FINE_UNLOAD(unload); +FINE_INIT("Elixir.MyLib.NIF); +``` + ## Prior work diff --git a/c_include/fine.hpp b/c_include/fine.hpp index 366c3a8..46e6ae3 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -1310,7 +1310,11 @@ enum class CallbackStatus { }; template struct OnLoad { - static void on_load(ErlNifEnv *) {} + static void on_load(ErlNifEnv *, void **, ERL_NIF_TERM) {} +}; + +template struct OnUnload { + static void on_unload(ErlNifEnv *, void *) {} }; } // namespace __private__ @@ -1331,6 +1335,20 @@ template struct OnLoad { }; \ static_assert(true, "require a semicolon after the macro") +#define FINE_UNLOAD(function) \ + static_assert( \ + std::is_invocable_v, \ + "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[]) { \ @@ -1366,7 +1384,7 @@ template struct OnLoad { auto funcs = nif_funcs.data(); \ \ const auto load = [](ErlNifEnv *caller_env, void **priv_data, \ - ERL_NIF_TERM load_info) { \ + ERL_NIF_TERM load_info) noexcept { \ fine::__private__::init_atoms(caller_env); \ \ if (!fine::__private__::init_resources(caller_env)) { \ @@ -1388,6 +1406,12 @@ template struct OnLoad { return 0; \ }; \ \ + const auto unload = [](ErlNifEnv *caller_env, void *priv_data) noexcept { \ + fine::__private__:: \ + OnUnload::on_unload( \ + caller_env, priv_data); \ + }; \ + \ static ErlNifEntry entry = {ERL_NIF_MAJOR_VERSION, \ ERL_NIF_MINOR_VERSION, \ name, \ @@ -1396,7 +1420,7 @@ template struct OnLoad { load, \ NULL, \ NULL, \ - NULL, \ + unload, \ ERL_NIF_VM_VARIANT, \ 1, \ sizeof(ErlNifResourceTypeInit), \ diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index 03e2188..4647292 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -463,11 +463,14 @@ 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; } +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"); From d02baeb549c4823a8693c415fc73e54cb633648b Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Tue, 30 Sep 2025 21:33:42 -0400 Subject: [PATCH 5/8] Reword callback to use `fine::Registration` While using `fine::Registration` incurs some runtime-cost, there is no longer any constraints on the order of callback registration and `FINE_INIT`. --- README.md | 24 ++------ c_include/fine.hpp | 140 +++++++++++++++++++++--------------------- test/c_src/finest.cpp | 11 ++-- 3 files changed, 82 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 9720afd..cad67f1 100644 --- a/README.md +++ b/README.md @@ -609,45 +609,33 @@ used. 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: +using the `fine::Registration::register_load` function: ```c++ static std::unique_ptr s_pool; -static void load(ErlNifEnv* caller_env, void** priv_data, fine::Term load_info) -{ +static auto load = fine::Registration::register_load([](ErlNifEnv *env, void **priv_data, fine::Term load_info) { const auto thread_count = fine::decode(caller_env, load_info); s_pool = std::make_unique(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: +using the `fine::Registration::register_unload` function: ```c++ static std::unique_ptr s_pool; -static void unload(ErlNifEnv* caller_env, void** priv_data) -{ +static auto unload = fine::Registration::register_unload([](ErlNifEnv *env, void *priv_data) noexcept { s_pool.stop(); -} - -FINE_UNLOAD(unload); -FINE_INIT("Elixir.MyLib.NIF); +}); ``` diff --git a/c_include/fine.hpp b/c_include/fine.hpp index 46e6ae3..de5dfd9 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,8 @@ template struct Encoder; namespace __private__ { std::vector &get_erl_nif_funcs(); +decltype(auto) get_erl_nif_load_callback(); +decltype(auto) get_erl_nif_unload_callback(); void init_atoms(ErlNifEnv *env); bool init_resources(ErlNifEnv *env); } // namespace __private__ @@ -1187,6 +1190,12 @@ template void raise(ErlNifEnv *env, const T &value) { // Mechanism for accumulating information via static object definitions. class Registration { public: + // A function compatible with the load callback of Erlang's NIFs. + using LoadCallback = std::function; + + // A function compatible with the unload callback of Erlang's NIFs. + using UnloadCallback = std::function; + template static Registration register_resource(const char *name) { Registration::resources.push_back({&fine::ResourcePtr::resource_type, @@ -1200,6 +1209,26 @@ class Registration { return {}; } + // Registers a load callback. + static LoadCallback register_load(LoadCallback callback) { + if (erl_nif_load_callback) { + throw std::logic_error("load callback already registered"); + } + + return Registration::erl_nif_load_callback = callback; + return callback; + } + + // Registers an unload callback. + static UnloadCallback register_unload(UnloadCallback callback) { + if (erl_nif_unload_callback) { + throw std::logic_error("unload callback already registered"); + } + + Registration::erl_nif_unload_callback = callback; + return callback; + } + private: static bool init_resources(ErlNifEnv *env) { for (const auto &[resource_type_ptr, name, dtor] : @@ -1220,6 +1249,8 @@ class Registration { } friend std::vector &__private__::get_erl_nif_funcs(); + friend decltype(auto) __private__::get_erl_nif_load_callback(); + friend decltype(auto) __private__::get_erl_nif_unload_callback(); friend bool __private__::init_resources(ErlNifEnv *env); @@ -1228,6 +1259,8 @@ class Registration { resources = {}; inline static std::vector erl_nif_funcs = {}; + inline static LoadCallback erl_nif_load_callback = {}; + inline static UnloadCallback erl_nif_unload_callback = {}; }; // NIF definitions @@ -1304,51 +1337,47 @@ inline std::vector &get_erl_nif_funcs() { return Registration::erl_nif_funcs; } -enum class CallbackStatus { - UNIMPLEMENTED, - IMPLEMENTED, -}; +inline decltype(auto) get_erl_nif_load_callback() { + return Registration::erl_nif_load_callback; +} -template struct OnLoad { - static void on_load(ErlNifEnv *, void **, ERL_NIF_TERM) {} -}; +inline decltype(auto) get_erl_nif_unload_callback() { + return Registration::erl_nif_unload_callback; +} -template struct OnUnload { - static void on_unload(ErlNifEnv *, void *) {} -}; -} // namespace __private__ +inline int load(ErlNifEnv *caller_env, void **priv_data, + ERL_NIF_TERM load_info) noexcept { + init_atoms(caller_env); -// Macros + if (!init_resources(caller_env)) { + return -1; + } -#define FINE_LOAD(function) \ - static_assert(std::is_invocable_v, \ - "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") + try { + if (fine::__private__::get_erl_nif_load_callback()) { + std::invoke(fine::__private__::get_erl_nif_load_callback(), 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; + } -#define FINE_UNLOAD(function) \ - static_assert( \ - std::is_invocable_v, \ - "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") + return 0; +} +inline void unload(ErlNifEnv *caller_env, void *priv_data) noexcept { + if (fine::__private__::get_erl_nif_unload_callback()) { + std::invoke(fine::__private__::get_erl_nif_unload_callback(), caller_env, + priv_data); + } +} +} // namespace __private__ + +// Macros #define FINE_NIF(name, flags) \ static ERL_NIF_TERM name##_nif(ErlNifEnv *env, int argc, \ const ERL_NIF_TERM argv[]) { \ @@ -1383,44 +1412,15 @@ template struct OnUnload { auto num_funcs = static_cast(nif_funcs.size()); \ auto funcs = nif_funcs.data(); \ \ - 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::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::on_unload( \ - caller_env, priv_data); \ - }; \ - \ static ErlNifEntry entry = {ERL_NIF_MAJOR_VERSION, \ ERL_NIF_MINOR_VERSION, \ name, \ num_funcs, \ funcs, \ - load, \ + fine::__private__::load, \ NULL, \ NULL, \ - unload, \ + fine::__private__::unload, \ ERL_NIF_VM_VARIANT, \ 1, \ sizeof(ErlNifResourceTypeInit), \ diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index 4647292..7596e63 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -463,14 +463,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; } + +static auto load = fine::Registration::register_load( + [](ErlNifEnv *, void **, ERL_NIF_TERM) { s_loaded = true; }); + +static auto unload = fine::Registration::register_unload( + [](ErlNifEnv *, void *) noexcept { s_loaded = false; }); 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"); From 96e42ce56a19f8db1db9d5c6e184f78e5ff36ae1 Mon Sep 17 00:00:00 2001 From: Alexis Brodeur Date: Mon, 6 Oct 2025 22:51:01 -0400 Subject: [PATCH 6/8] Final tweaks --- README.md | 4 ++-- c_include/fine.hpp | 24 ++++++++---------------- test/c_src/finest.cpp | 4 ++-- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index cad67f1..9ece5bb 100644 --- a/README.md +++ b/README.md @@ -618,7 +618,7 @@ using the `fine::Registration::register_load` function: ```c++ static std::unique_ptr s_pool; -static auto load = fine::Registration::register_load([](ErlNifEnv *env, void **priv_data, fine::Term load_info) { +static auto load_registration = fine::Registration::register_load([](ErlNifEnv *env, void **priv_data, fine::Term load_info) { const auto thread_count = fine::decode(caller_env, load_info); s_pool = std::make_unique(thread_count); }); @@ -633,7 +633,7 @@ using the `fine::Registration::register_unload` function: ```c++ static std::unique_ptr s_pool; -static auto unload = fine::Registration::register_unload([](ErlNifEnv *env, void *priv_data) noexcept { +static auto unload_registration = fine::Registration::register_unload([](ErlNifEnv *env, void *priv_data) noexcept { s_pool.stop(); }); ``` diff --git a/c_include/fine.hpp b/c_include/fine.hpp index de5dfd9..d299e04 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -48,10 +48,10 @@ template struct Encoder; namespace __private__ { std::vector &get_erl_nif_funcs(); -decltype(auto) get_erl_nif_load_callback(); -decltype(auto) get_erl_nif_unload_callback(); void init_atoms(ErlNifEnv *env); bool init_resources(ErlNifEnv *env); +int load(ErlNifEnv *, void **, ERL_NIF_TERM) noexcept; +void unload(ErlNifEnv *, void *) noexcept; } // namespace __private__ // Definitions @@ -1249,8 +1249,8 @@ class Registration { } friend std::vector &__private__::get_erl_nif_funcs(); - friend decltype(auto) __private__::get_erl_nif_load_callback(); - friend decltype(auto) __private__::get_erl_nif_unload_callback(); + friend int __private__::load(ErlNifEnv *, void **, ERL_NIF_TERM) noexcept; + friend void __private__::unload(ErlNifEnv *, void *) noexcept; friend bool __private__::init_resources(ErlNifEnv *env); @@ -1337,14 +1337,6 @@ inline std::vector &get_erl_nif_funcs() { return Registration::erl_nif_funcs; } -inline decltype(auto) get_erl_nif_load_callback() { - return Registration::erl_nif_load_callback; -} - -inline decltype(auto) get_erl_nif_unload_callback() { - return Registration::erl_nif_unload_callback; -} - inline int load(ErlNifEnv *caller_env, void **priv_data, ERL_NIF_TERM load_info) noexcept { init_atoms(caller_env); @@ -1354,8 +1346,8 @@ inline int load(ErlNifEnv *caller_env, void **priv_data, } try { - if (fine::__private__::get_erl_nif_load_callback()) { - std::invoke(fine::__private__::get_erl_nif_load_callback(), caller_env, + if (fine::Registration::erl_nif_load_callback) { + std::invoke(fine::Registration::erl_nif_load_callback, caller_env, priv_data, load_info); } } catch (const std::exception &e) { @@ -1370,8 +1362,8 @@ inline int load(ErlNifEnv *caller_env, void **priv_data, } inline void unload(ErlNifEnv *caller_env, void *priv_data) noexcept { - if (fine::__private__::get_erl_nif_unload_callback()) { - std::invoke(fine::__private__::get_erl_nif_unload_callback(), caller_env, + if (fine::Registration::erl_nif_unload_callback) { + std::invoke(fine::Registration::erl_nif_unload_callback, caller_env, priv_data); } } diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index 7596e63..f64b625 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -464,10 +464,10 @@ FINE_NIF(hash_atom, 0); static bool s_loaded = false; -static auto load = fine::Registration::register_load( +static auto load_registration = fine::Registration::register_load( [](ErlNifEnv *, void **, ERL_NIF_TERM) { s_loaded = true; }); -static auto unload = fine::Registration::register_unload( +static auto unload_registration = fine::Registration::register_unload( [](ErlNifEnv *, void *) noexcept { s_loaded = false; }); bool is_loaded(ErlNifEnv *) { return s_loaded; } From efcff88f6747f056a920f5a447a063a729cd15d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Tue, 7 Oct 2025 12:06:32 +0200 Subject: [PATCH 7/8] Update c_include/fine.hpp --- c_include/fine.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c_include/fine.hpp b/c_include/fine.hpp index d299e04..7f96a88 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -1215,7 +1215,7 @@ class Registration { throw std::logic_error("load callback already registered"); } - return Registration::erl_nif_load_callback = callback; + Registration::erl_nif_load_callback = callback; return callback; } From c44c59f23abe64b1dfdd76315e2dbb41997055ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Tue, 7 Oct 2025 12:07:57 +0200 Subject: [PATCH 8/8] Update c_include/fine.hpp --- c_include/fine.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/c_include/fine.hpp b/c_include/fine.hpp index 7f96a88..8b96d7f 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -1210,23 +1210,23 @@ class Registration { } // Registers a load callback. - static LoadCallback register_load(LoadCallback callback) { + static Registration register_load(LoadCallback callback) { if (erl_nif_load_callback) { throw std::logic_error("load callback already registered"); } Registration::erl_nif_load_callback = callback; - return callback; + return {}; } // Registers an unload callback. - static UnloadCallback register_unload(UnloadCallback callback) { + static Registration register_unload(UnloadCallback callback) { if (erl_nif_unload_callback) { throw std::logic_error("unload callback already registered"); } Registration::erl_nif_unload_callback = callback; - return callback; + return {}; } private: