#ifndef FINE_HPP #define FINE_HPP #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_MSVC_LANG) #define CPP_VERSION _MSVC_LANG #else #define CPP_VERSION __cplusplus #endif #if CPP_VERSION < 201703L #error "elixir-nx/fine only supports C++ 17 and later" #endif #if ERL_NIF_MAJOR_VERSION > 2 || \ (ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 17) #define FINE_ERL_NIF_CHAR_ENCODING ERL_NIF_UTF8 #else #define FINE_ERL_NIF_CHAR_ENCODING ERL_NIF_LATIN1 #endif namespace fine { // Forward declarations template T decode(ErlNifEnv *env, const ERL_NIF_TERM &term); template ERL_NIF_TERM encode(ErlNifEnv *env, const T &value); template struct Decoder; template struct Encoder; namespace __private__ { std::vector &get_erl_nif_funcs(); int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info); } // namespace __private__ // Definitions namespace __private__ { inline ERL_NIF_TERM make_atom(ErlNifEnv *env, const char *msg) { ERL_NIF_TERM atom; if (enif_make_existing_atom(env, msg, &atom, FINE_ERL_NIF_CHAR_ENCODING)) { return atom; } else { return enif_make_atom(env, msg); } } } // namespace __private__ // A representation of an atom term. class Atom { public: Atom(std::string name) : name(name), term(std::nullopt) { if (!Atom::initialized) { Atom::atoms.push_back(this); } } std::string to_string() const { return this->name; } bool operator==(const Atom &other) const { return this->name == other.name; } bool operator==(const char *other) const { return this->name == other; } bool operator<(const Atom &other) const { return this->name < other.name; } private: static void init_atoms(ErlNifEnv *env) { for (auto atom : Atom::atoms) { atom->term = fine::__private__::make_atom(env, atom->name.c_str()); } Atom::atoms.clear(); Atom::initialized = true; } friend struct Encoder; friend int __private__::load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info); // We accumulate all globally defined atom objects and create the // terms upfront as part of init (called from the NIF load callback). inline static std::vector atoms = {}; inline static bool initialized = false; std::string name; std::optional term; }; namespace __private__::atoms { inline auto ok = Atom("ok"); inline auto error = Atom("error"); inline auto nil = Atom("nil"); inline auto true_ = Atom("true"); inline auto false_ = Atom("false"); inline auto __struct__ = Atom("__struct__"); inline auto __exception__ = Atom("__exception__"); inline auto message = Atom("message"); inline auto ElixirArgumentError = Atom("Elixir.ArgumentError"); inline auto ElixirRuntimeError = Atom("Elixir.RuntimeError"); } // namespace __private__::atoms // Represents any term. // // This type should be used instead of ERL_NIF_TERM in the NIF signature // and encode/decode APIs. class Term { // ERL_NIF_TERM is typedef-ed as an integer type. At the moment of // writing it is unsigned long int. This means that we cannot define // separate Decoder and Decoder, // (which could potentially match uint64_t). The same applies to // Encoder. For this reason we need a wrapper object for terms, so // they can be unambiguously distinguished. We define implicit // bidirectional conversion between Term and ERL_NIF_TERM, so that // Term is effectively just a typing tag for decoder and encoder // (and the nif signature). public: Term() {} Term(const ERL_NIF_TERM &term) : term(term) {} operator ERL_NIF_TERM() const { return this->term; } private: ERL_NIF_TERM term; }; // Represents a `:ok` tagged tuple, useful as a NIF result. template class Ok { public: Ok(const Args &...items) : items(items...) {} private: friend struct Encoder>; std::tuple items; }; // Represents a `:error` tagged tuple, useful as a NIF result. template class Error { public: Error(const Args &...items) : items(items...) {} private: friend struct Encoder>; std::tuple items; }; namespace __private__ { template struct ResourceWrapper { T resource; bool initialized; static void dtor(ErlNifEnv *env, void *ptr) { auto resource_wrapper = reinterpret_cast *>(ptr); if (resource_wrapper->initialized) { if constexpr (has_destructor::value) { resource_wrapper->resource.destructor(env); } resource_wrapper->resource.~T(); } } template struct has_destructor : std::false_type {}; template struct has_destructor< U, typename std::enable_if().destructor(std::declval())), void>::value>::type> : std::true_type {}; }; } // namespace __private__ // A smart pointer that retains ownership of a resource object. template class ResourcePtr { // For more context see [1] and [2]. // // [1]: https://stackoverflow.com/a/3279550 // [2]: https://stackoverflow.com/a/5695855 public: // Make default constructor public, so that classes with ResourcePtr // field can also have default constructor. ResourcePtr() : ptr(nullptr) {} ResourcePtr(const ResourcePtr &other) : ptr(other.ptr) { if (this->ptr != nullptr) { enif_keep_resource(reinterpret_cast(this->ptr)); } } ResourcePtr(ResourcePtr &&other) : ResourcePtr() { swap(other, *this); } ~ResourcePtr() { if (this->ptr != nullptr) { enif_release_resource(reinterpret_cast(this->ptr)); } } ResourcePtr &operator=(ResourcePtr other) { swap(*this, other); return *this; } T &operator*() const { return this->ptr->resource; } T *operator->() const { return &this->ptr->resource; } T *get() const { return &this->ptr->resource; } friend void swap(ResourcePtr &left, ResourcePtr &right) { using std::swap; swap(left.ptr, right.ptr); } private: // This constructor assumes the pointer is already accounted for in // the resource reference count. Since it is private, we guarantee // this in all the callers. ResourcePtr(__private__::ResourceWrapper *ptr) : ptr(ptr) {} // Friend functions that use the resource_type static member or the // private constructor. template friend ResourcePtr make_resource(Args &&...args); friend class Registration; friend struct Decoder>; inline static ErlNifResourceType *resource_type = nullptr; __private__::ResourceWrapper *ptr; }; // Allocates a new resource object, invoking its constructor with the // given arguments. template ResourcePtr make_resource(Args &&...args) { auto type = ResourcePtr::resource_type; if (type == nullptr) { throw std::runtime_error( "calling make_resource with unexpected type. Make sure" " to register your resource type with the FINE_RESOURCE macro"); } void *allocation_ptr = enif_alloc_resource(type, sizeof(__private__::ResourceWrapper)); auto resource_wrapper = reinterpret_cast<__private__::ResourceWrapper *>(allocation_ptr); // We create ResourcePtr right away, to make sure the resource is // properly released in case the constructor below throws auto resource = ResourcePtr(resource_wrapper); // We use a wrapper struct with an extra field to track if the // resource has actually been initialized. This way if the constructor // below throws, we can skip the destructor calls in the Erlang dtor resource_wrapper->initialized = false; // Invoke the constructor with prefect forwarding to initialize the // object at the VM-allocated memory new (&resource_wrapper->resource) T(std::forward(args)...); resource_wrapper->initialized = true; return resource; } // Creates a binary term pointing to the given buffer. // // The buffer is managed by the resource object and should be deallocated // once the resource is destroyed. template Term make_resource_binary(ErlNifEnv *env, ResourcePtr resource, const char *data, size_t size) { return enif_make_resource_binary( env, reinterpret_cast(resource.get()), data, size); } // Creates a binary term copying data from the given buffer. // // This is useful when returning large binary from a NIF and the source // buffer does not outlive the return. inline fine::Term make_new_binary(ErlNifEnv *env, const char *data, size_t size) { ERL_NIF_TERM term; auto term_data = enif_make_new_binary(env, size, &term); if (term_data == nullptr) { throw std::runtime_error( "make_new_binary failed, failed to allocate new binary"); } memcpy(term_data, data, size); return term; } // Decodes the given Erlang term as a value of the specified type. // // The given type must have a specialized Decoder implementation. template T decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { return Decoder::decode(env, term); } // Encodes the given value as a Erlang term. // // The value type must have a specialized Encoder implementation. template ERL_NIF_TERM encode(ErlNifEnv *env, const T &value) { return Encoder::encode(env, value); } // We want decode to return the value, and since the argument types // are always the same, we need template specialization, so that the // caller can explicitly specify the desired type. However, in order // to implement decode for a type such as std::vector we need // partial specialization, and that is not supported for functions. // To solve this, we specialize a struct instead and have the decode // logic in a static member function. // // In case of encode, the argument type differs, so we could use // function overloading. That said, we pick struct specialization as // well for consistency with decode. This approach also prevents from // implicit argument conversion, which is arguably good in this case, // as it makes the encoding explicit. template struct Decoder {}; template struct Encoder {}; template <> struct Decoder { static Term decode(ErlNifEnv *, const ERL_NIF_TERM &term) { return Term(term); } }; template <> struct Decoder { static int64_t decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { int64_t integer; if (!enif_get_int64(env, term, reinterpret_cast(&integer))) { throw std::invalid_argument("decode failed, expected an integer"); } return integer; } }; template <> struct Decoder { static uint64_t decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { uint64_t integer; if (!enif_get_uint64(env, term, reinterpret_cast(&integer))) { throw std::invalid_argument( "decode failed, expected an unsigned integer"); } return integer; } }; template <> struct Decoder { static double decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { double number; if (!enif_get_double(env, term, &number)) { throw std::invalid_argument("decode failed, expected a float"); } return number; } }; template <> struct Decoder { static bool decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { char atom_string[6]; auto length = enif_get_atom(env, term, atom_string, 6, ERL_NIF_LATIN1); if (length == 5 && strcmp(atom_string, "true") == 0) { return true; } if (length == 6 && strcmp(atom_string, "false") == 0) { return false; } throw std::invalid_argument("decode failed, expected a boolean"); } }; template <> struct Decoder { static ErlNifPid decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { ErlNifPid pid; if (!enif_is_pid(env, term)) { throw std::invalid_argument("decode failed, expected a local pid"); } if (!enif_get_local_pid(env, term, &pid)) { // If the term is a PID and it is not local, it means it's a remote PID. throw std::invalid_argument( "decode failed, expected a local pid, but got a remote one. NIFs can " "only send messages to local PIDs and remote PIDs cannot be decoded"); } return pid; } }; template <> struct Decoder { static ErlNifBinary decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { ErlNifBinary binary; if (!enif_inspect_binary(env, term, &binary)) { throw std::invalid_argument("decode failed, expected a binary"); } return binary; } }; template <> struct Decoder { static std::string_view decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { auto binary = fine::decode(env, term); return std::string_view(reinterpret_cast(binary.data), binary.size); } }; template <> struct Decoder { static std::string decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { return std::string(fine::decode(env, term)); } }; template <> struct Decoder { static Atom decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { unsigned int length; if (!enif_get_atom_length(env, term, &length, FINE_ERL_NIF_CHAR_ENCODING)) { throw std::invalid_argument("decode failed, expected an atom"); } auto buffer = std::make_unique(length + 1); // Note that enif_get_atom writes the NULL byte at the end if (!enif_get_atom(env, term, buffer.get(), length + 1, FINE_ERL_NIF_CHAR_ENCODING)) { throw std::invalid_argument("decode failed, expected an atom"); } return Atom(std::string(buffer.get(), length)); } }; template struct Decoder> { static std::optional decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { char atom_string[4]; if (enif_get_atom(env, term, atom_string, 4, ERL_NIF_LATIN1) == 4) { if (strcmp(atom_string, "nil") == 0) { return std::nullopt; } } return fine::decode(env, term); } }; template struct Decoder> { static std::variant decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { return do_decode(env, term); } private: template static std::variant do_decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { try { return fine::decode(env, term); } catch (const std::invalid_argument &) { if constexpr (sizeof...(Rest) > 0) { return do_decode(env, term); } else { throw std::invalid_argument( "decode failed, none of the variant types could be decoded"); } } } }; template struct Decoder> { static std::tuple decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { constexpr auto expected_size = sizeof...(Args); int size; const ERL_NIF_TERM *terms; if (!enif_get_tuple(env, term, &size, &terms)) { throw std::invalid_argument("decode failed, expected a tuple"); } if (size != expected_size) { throw std::invalid_argument("decode failed, expected tuple to have " + std::to_string(expected_size) + " elements, but had " + std::to_string(size)); } return do_decode(env, terms, std::make_index_sequence()); } private: template static std::tuple do_decode(ErlNifEnv *env, const ERL_NIF_TERM *terms, std::index_sequence) { return std::make_tuple(fine::decode(env, terms[Indices])...); } }; template struct Decoder> { static std::vector decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { unsigned int length; if (!enif_get_list_length(env, term, &length)) { throw std::invalid_argument("decode failed, expected a list"); } std::vector vector; vector.reserve(length); auto list = term; ERL_NIF_TERM head, tail; while (enif_get_list_cell(env, list, &head, &tail)) { auto elem = fine::decode(env, head); vector.push_back(elem); list = tail; } return vector; } }; template struct Decoder> { static std::map decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { auto map = std::map(); ERL_NIF_TERM key, value; ErlNifMapIterator iter; if (!enif_map_iterator_create(env, term, &iter, ERL_NIF_MAP_ITERATOR_FIRST)) { throw std::invalid_argument("decode failed, expected a map"); } // Define RAII cleanup for the iterator auto cleanup = IterCleanup{env, iter}; while (enif_map_iterator_get_pair(env, &iter, &key, &value)) { map[fine::decode(env, key)] = fine::decode(env, value); enif_map_iterator_next(env, &iter); } return map; } private: struct IterCleanup { ErlNifEnv *env; ErlNifMapIterator iter; ~IterCleanup() { enif_map_iterator_destroy(env, &iter); } }; }; template struct Decoder> { static ResourcePtr decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { void *ptr; auto type = ResourcePtr::resource_type; if (!enif_get_resource(env, term, type, &ptr)) { throw std::invalid_argument( "decode failed, expected a resource reference"); } enif_keep_resource(ptr); return ResourcePtr( reinterpret_cast<__private__::ResourceWrapper *>(ptr)); } }; template struct Decoder> { static T decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { ERL_NIF_TERM struct_value; if (!enif_get_map_value(env, term, encode(env, __private__::atoms::__struct__), &struct_value)) { throw std::invalid_argument("decode failed, expected a struct"); } // Make sure __struct__ matches const auto &struct_atom = *T::module; if (enif_compare(struct_value, encode(env, struct_atom)) != 0) { throw std::invalid_argument("decode failed, expected a " + struct_atom.to_string() + " struct"); } T ex_struct; constexpr auto fields = T::fields(); std::apply( [&](auto... field) { (set_field(env, term, ex_struct, std::get<0>(field), std::get<1>(field)), ...); }, fields); return ex_struct; } private: template static void set_field(ErlNifEnv *env, ERL_NIF_TERM term, T &ex_struct, U T::*field_ptr, const Atom *atom) { ERL_NIF_TERM value; if (!enif_get_map_value(env, term, encode(env, *atom), &value)) { throw std::invalid_argument( "decode failed, expected the struct to have " + atom->to_string() + " field"); } ex_struct.*(field_ptr) = fine::decode(env, value); } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *, const Term &term) { return term; } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const int64_t &integer) { return enif_make_int64(env, integer); } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const uint64_t &integer) { return enif_make_uint64(env, integer); } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const double &number) { return enif_make_double(env, number); } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const bool &boolean) { return fine::encode(env, boolean ? __private__::atoms::true_ : __private__::atoms::false_); } }; // enif_make_pid is a macro that does a cast (const ERL_NIF_TERM) // and GCC complains that the cast is ignored, so we ignore this // specific warning explicitly here. #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wignored-qualifiers" #endif template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const ErlNifPid &pid) { return enif_make_pid(env, &pid); } }; #ifdef __GNUC__ #pragma GCC diagnostic pop #endif template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const ErlNifBinary &binary) { return enif_make_binary(env, const_cast(&binary)); } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::string_view &string) { ERL_NIF_TERM term; auto data = enif_make_new_binary(env, string.length(), &term); if (data == nullptr) { throw std::runtime_error("encode failed, failed to allocate new binary"); } memcpy(data, string.data(), string.length()); return term; } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::string &string) { return fine::encode(env, string); } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const Atom &atom) { if (atom.term) { return atom.term.value(); } else { return fine::__private__::make_atom(env, atom.name.c_str()); } } }; template <> struct Encoder { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::nullopt_t &) { return fine::encode(env, __private__::atoms::nil); } }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::optional &optional) { if (optional) { return fine::encode(env, optional.value()); } else { return fine::encode(env, __private__::atoms::nil); } } }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::variant &variant) { return do_encode(env, variant); } private: template static ERL_NIF_TERM do_encode(ErlNifEnv *env, const std::variant &variant) { if (auto value = std::get_if(&variant)) { return fine::encode(env, *value); } if constexpr (sizeof...(Rest) > 0) { return do_encode(env, variant); } else { throw std::runtime_error("unreachable"); } } }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::tuple &tuple) { return do_encode(env, tuple, std::make_index_sequence()); } private: template static ERL_NIF_TERM do_encode(ErlNifEnv *env, const std::tuple &tuple, std::index_sequence) { constexpr auto size = sizeof...(Args); return enif_make_tuple(env, size, fine::encode(env, std::get(tuple))...); } }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::vector &vector) { auto terms = std::vector(); terms.reserve(vector.size()); for (const auto &item : vector) { terms.push_back(fine::encode(env, item)); } return enif_make_list_from_array(env, terms.data(), static_cast(terms.size())); } }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::map &map) { auto keys = std::vector(); auto values = std::vector(); for (const auto &[key, value] : map) { keys.push_back(fine::encode(env, key)); values.push_back(fine::encode(env, value)); } ERL_NIF_TERM map_term; if (!enif_make_map_from_arrays(env, keys.data(), values.data(), keys.size(), &map_term)) { throw std::runtime_error("encode failed, failed to make a map"); } return map_term; } }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const ResourcePtr &resource) { return enif_make_resource(env, reinterpret_cast(resource.get())); } }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const T &ex_struct) { const auto &struct_atom = *T::module; constexpr auto fields = T::fields(); constexpr auto is_exception = get_is_exception(); constexpr auto num_fields = std::tuple_size::value; constexpr auto num_extra_fields = is_exception ? 2 : 1; ERL_NIF_TERM keys[num_extra_fields + num_fields]; ERL_NIF_TERM values[num_extra_fields + num_fields]; keys[0] = fine::encode(env, __private__::atoms::__struct__); values[0] = fine::encode(env, struct_atom); if constexpr (is_exception) { keys[1] = fine::encode(env, __private__::atoms::__exception__); values[1] = fine::encode(env, __private__::atoms::true_); } put_key_values(env, ex_struct, keys + num_extra_fields, values + num_extra_fields, std::make_index_sequence()); ERL_NIF_TERM map; if (!enif_make_map_from_arrays(env, keys, values, num_extra_fields + num_fields, &map)) { throw std::runtime_error("encode failed, failed to make a map"); } return map; } private: template static void put_key_values(ErlNifEnv *env, const T &ex_struct, ERL_NIF_TERM keys[], ERL_NIF_TERM values[], std::index_sequence) { constexpr auto fields = T::fields(); std::apply( [&](auto... field) { ((keys[Indices] = fine::encode(env, *std::get<1>(field)), values[Indices] = fine::encode(env, ex_struct.*(std::get<0>(field)))), ...); }, fields); } static constexpr bool get_is_exception() { if constexpr (has_is_exception::value) { return T::is_exception; } else { return false; } } template struct has_is_exception : std::false_type {}; template struct has_is_exception> : std::true_type {}; }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const Ok &ok) { auto tag = __private__::atoms::ok; if constexpr (sizeof...(Args) > 0) { return fine::encode(env, std::tuple_cat(std::tuple(tag), ok.items)); } else { return fine::encode(env, tag); } } }; template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const Error &error) { auto tag = __private__::atoms::error; if constexpr (sizeof...(Args) > 0) { return fine::encode(env, std::tuple_cat(std::tuple(tag), error.items)); } else { return fine::encode(env, tag); } } }; namespace __private__ { class ExceptionError : public std::exception { public: ERL_NIF_TERM reason; ExceptionError(ERL_NIF_TERM reason) : reason(reason) {} const char *what() const noexcept { return "erlang exception raised"; } }; } // namespace __private__ // Raises an Elixir exception with the given value as reason. template void raise(ErlNifEnv *env, const T &value) { auto term = encode(env, value); throw __private__::ExceptionError(term); } // Mechanism for accumulating information via static object definitions. class Registration { public: template static Registration register_resource(const char *name) { Registration::resources.push_back({&fine::ResourcePtr::resource_type, name, __private__::ResourceWrapper::dtor}); return {}; } static Registration register_nif(ErlNifFunc erl_nif_func) { Registration::erl_nif_funcs.push_back(erl_nif_func); return {}; } private: static bool init_resources(ErlNifEnv *env) { for (const auto &[resource_type_ptr, name, dtor] : Registration::resources) { auto flags = ERL_NIF_RT_CREATE; auto type = enif_open_resource_type(env, NULL, name, dtor, flags, NULL); if (type) { *resource_type_ptr = type; } else { return false; } } Registration::resources.clear(); return true; } friend std::vector &__private__::get_erl_nif_funcs(); friend int __private__::load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info); inline static std::vector> resources = {}; inline static std::vector erl_nif_funcs = {}; }; // NIF definitions namespace __private__ { inline ERL_NIF_TERM raise_error_with_message(ErlNifEnv *env, Atom module, std::string message) { ERL_NIF_TERM keys[3] = {fine::encode(env, __private__::atoms::__struct__), fine::encode(env, __private__::atoms::__exception__), fine::encode(env, __private__::atoms::message)}; ERL_NIF_TERM values[3] = { fine::encode(env, module), fine::encode(env, __private__::atoms::true_), fine::encode(env, message), }; ERL_NIF_TERM map; if (!enif_make_map_from_arrays(env, keys, values, 3, &map)) { return enif_raise_exception(env, encode(env, message)); } return enif_raise_exception(env, map); } template ERL_NIF_TERM nif_impl(ErlNifEnv *env, const ERL_NIF_TERM argv[], Return (*fun)(ErlNifEnv *, Args...), std::index_sequence) { try { auto result = fun(env, decode(env, argv[Indices])...); return encode(env, result); } catch (const ExceptionError &error) { return enif_raise_exception(env, error.reason); } catch (const std::invalid_argument &error) { return raise_error_with_message( env, __private__::atoms::ElixirArgumentError, error.what()); } catch (const std::runtime_error &error) { return raise_error_with_message(env, __private__::atoms::ElixirRuntimeError, error.what()); } catch (...) { return raise_error_with_message(env, __private__::atoms::ElixirRuntimeError, "unknown exception thrown within NIF"); } } } // namespace __private__ template ERL_NIF_TERM nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[], Return (*fun)(ErlNifEnv *, Args...)) { const auto num_args = sizeof...(Args); if (num_args != argc) { return enif_raise_exception( env, encode(env, std::string("wrong number of arguments"))); } return __private__::nif_impl(env, argv, fun, std::make_index_sequence()); } template constexpr unsigned int nif_arity(Ret (*)(Args...)) { return sizeof...(Args) - 1; } namespace __private__ { 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; } return 0; } } // namespace __private__ // Macros #define FINE_NIF(name, flags) \ static ERL_NIF_TERM name##_nif(ErlNifEnv *env, int argc, \ const ERL_NIF_TERM argv[]) { \ return fine::nif(env, argc, argv, name); \ } \ auto __nif_registration_##name = fine::Registration::register_nif( \ {#name, fine::nif_arity(name), name##_nif, flags}); \ static_assert(true, "require a semicolon after the macro") // Note that we use static, in case FINE_REASOURCE is used in another // translation unit on the same line. #define FINE_RESOURCE(class_name) \ static auto __FINE_CONCAT__(__resource_registration_, __LINE__) = \ fine::Registration::register_resource(#class_name); \ static_assert(true, "require a semicolon after the macro") // An extra level of indirection is necessary to make sure __LINE__ // is expanded before concatenation. #define __FINE_CONCAT__(a, b) __FINE_CONCAT_IMPL__(a, b) #define __FINE_CONCAT_IMPL__(a, b) a##b // This is a modified version of ERL_NIF_INIT that points to the // registered NIF functions and also sets the load callback. #define FINE_INIT(name) \ ERL_NIF_INIT_PROLOGUE \ ERL_NIF_INIT_GLOB \ ERL_NIF_INIT_DECL(NAME); \ ERL_NIF_INIT_DECL(NAME) { \ 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; \ static ErlNifEntry entry = {ERL_NIF_MAJOR_VERSION, \ ERL_NIF_MINOR_VERSION, \ name, \ num_funcs, \ funcs, \ load, \ NULL, \ NULL, \ NULL, \ ERL_NIF_VM_VARIANT, \ 1, \ sizeof(ErlNifResourceTypeInit), \ ERL_NIF_MIN_ERTS_VERSION}; \ ERL_NIF_INIT_BODY; \ return &entry; \ } \ ERL_NIF_INIT_EPILOGUE \ static_assert(true, "require a semicolon after the macro") } // namespace fine #endif