diff --git a/README.md b/README.md index 6574678..1dbb54c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,9 @@ NIFs in C++. - STL compatible Erlang-backend mutex and rwlock. +- Compatible with STL container allocators and polymorphic memory + resources. + ## Motivation Some projects make extensive use of NIFs, where using the C API results @@ -131,28 +134,28 @@ auto message = fine::decode(env, term); Fine provides implementations for the following types: -| Type | Encoder | Decoder | -| ------------------------------------ | ------- | ------- | -| `fine::Term` | x | x | -| `int64_t` | x | x | -| `uint64_t` | x | x | -| `double` | x | x | -| `bool` | x | x | -| `ErlNifPid` | x | x | -| `ErlNifBinary` | x | x | -| `std::string_view` | x | x | -| `std::string` | x | x | -| `fine::Atom` | x | x | -| `std::nullopt_t` | x | | -| `std::optional` | x | x | -| `std::variant` | x | x | -| `std::tuple` | x | x | -| `std::vector` | x | x | -| `std::map` | x | x | -| `fine::ResourcePtr` | x | x | -| `T` with [struct metadata](#structs) | x | x | -| `fine::Ok` | x | | -| `fine::Error` | x | | +| C++ Type | Encoder | Decoder | Elixir Type | +| ------------------------------------ | ------- | ------- | --------------------------- | +| `fine::Term` | x | x | `term` | +| `int64_t` | x | x | `integer` | +| `uint64_t` | x | x | `non_neg_integer` | +| `double` | x | x | `float` | +| `bool` | x | x | `boolean` | +| `ErlNifPid` | x | x | `pid` | +| `ErlNifBinary` | x | x | `binary` | +| `std::string_view` | x | x | `binary` | +| `std::string` | x | x | `binary` | +| `fine::Atom` | x | x | `atom` | +| `std::nullopt_t` | x | | `nil` | +| `std::optional` | x | x | `a \| nil` | +| `std::variant` | x | x | `a \| b \| ... \| c` | +| `std::tuple` | x | x | `{a, b, ..., c}` | +| `std::vector` | x | x | `list(a)` | +| `std::map` | x | x | `%{k => v}` | +| `fine::ResourcePtr` | x | x | `reference` | +| `T` with [struct metadata](#structs) | x | x | `%a{}` | +| `fine::Ok` | x | | `{:ok, ...}` | +| `fine::Error` | x | | `{:error, ...}` | > #### ERL_NIF_TERM {: .warning} > @@ -557,6 +560,38 @@ const char* my_object__name(struct my_object*); fine::SharedMutex my_object_rwlock("my_lib", "my_object", my_object__name(my_object)); ``` +## Allocators + +For compatibility with the STL, fine supports stateless allocators when +decoding values, while also supporting stateful allocators when encoding +values. The following shows how a custom `MyAllocator` allocator and +`my_memory_resource` memory resource can be used in conjunction with fine: + +```c++ +template +struct MyAllocator { ... }; + +std::pmr::memory_resource* my_memory_resource = ...; + +std::vector> repeat_string( + ErlNifEnv *, + std::basic_string, MyAllocator> + string, + std::uint64_t repeat) { + std::vector> strings; + + for (std::uint64_t i = 0; i != repeat; ++i) { + strings.emplace_back(std::pmr::string(string, my_memory_resource)); + } + + return strings; +} +FINE_NIF(repeat_string, 0); +``` + +Attempting to decode STL containers making use of `std::pmr::polymorphic_allocator` +will result in the `std::pmr::get_default_resource()` memory resource being +used. diff --git a/include/fine.hpp b/include/fine.hpp index 4c64469..31b96a4 100644 --- a/include/fine.hpp +++ b/include/fine.hpp @@ -304,8 +304,7 @@ Term make_resource_binary(ErlNifEnv *env, ResourcePtr resource, // // 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) { +inline 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) { @@ -432,9 +431,12 @@ template <> struct Decoder { } }; -template <> struct Decoder { - static std::string decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { - return std::string(fine::decode(env, term)); +template +struct Decoder, Alloc>> { + using string = std::basic_string, Alloc>; + + static string decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { + return string(fine::decode(env, term)); } }; @@ -521,15 +523,16 @@ template struct Decoder> { } }; -template struct Decoder> { - static std::vector decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { +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; + std::vector vector; vector.reserve(length); auto list = term; @@ -537,7 +540,7 @@ template struct Decoder> { ERL_NIF_TERM head, tail; while (enif_get_list_cell(env, list, &head, &tail)) { auto elem = fine::decode(env, head); - vector.push_back(elem); + vector.emplace_back(std::move(elem)); list = tail; } @@ -545,11 +548,13 @@ template struct Decoder> { } }; -template struct Decoder> { - static std::map decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { - auto map = std::map(); +template +struct Decoder> { + static std::map decode(ErlNifEnv *env, + const ERL_NIF_TERM &term) { + std::map map; - ERL_NIF_TERM key, value; + ERL_NIF_TERM key_term, value_term; ErlNifMapIterator iter; if (!enif_map_iterator_create(env, term, &iter, ERL_NIF_MAP_ITERATOR_FIRST)) { @@ -559,8 +564,12 @@ template struct Decoder> { // 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); + while (enif_map_iterator_get_pair(env, &iter, &key_term, &value_term)) { + auto key = fine::decode(env, key_term); + auto value = fine::decode(env, value_term); + + map.insert_or_assign(std::move(key), std::move(value)); + enif_map_iterator_next(env, &iter); } @@ -705,8 +714,11 @@ template <> struct Encoder { } }; -template <> struct Encoder { - static ERL_NIF_TERM encode(ErlNifEnv *env, const std::string &string) { +template +struct Encoder, Alloc>> { + static ERL_NIF_TERM + encode(ErlNifEnv *env, + const std::basic_string, Alloc> &string) { return fine::encode(env, string); } }; @@ -775,8 +787,9 @@ template struct Encoder> { } }; -template struct Encoder> { - static ERL_NIF_TERM encode(ErlNifEnv *env, const std::vector &vector) { +template struct Encoder> { + static ERL_NIF_TERM encode(ErlNifEnv *env, + const std::vector &vector) { auto terms = std::vector(); terms.reserve(vector.size()); @@ -789,8 +802,10 @@ template struct Encoder> { } }; -template struct Encoder> { - static ERL_NIF_TERM encode(ErlNifEnv *env, const std::map &map) { +template +struct Encoder> { + static ERL_NIF_TERM encode(ErlNifEnv *env, + const std::map &map) { auto keys = std::vector(); auto values = std::vector(); diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index ddb3cd2..ae96802 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -76,6 +77,44 @@ struct ExError { static constexpr auto is_exception = true; }; +template struct Allocator { + using value_type = std::decay_t; + + Allocator() noexcept = default; + + template Allocator(const Allocator &) noexcept {} + + value_type *allocate(std::size_t n, const void *hint = nullptr) { + (void)hint; + + void *ptr = enif_alloc(sizeof(T) * n); + if (ptr == nullptr) { + throw std::bad_alloc(); + } + return reinterpret_cast(ptr); + } + + void deallocate(value_type *ptr, std::size_t n) { + (void)n; + + enif_free(ptr); + } + + template void construct(U *p, Args &&...args) { + new (p) U(std::forward(args)...); + } + + template void destruct(U *p) { std::destroy_at(p); } + + friend bool operator==(const Allocator &, const Allocator &) noexcept { + return true; + } + + friend bool operator!=(const Allocator &, const Allocator &) noexcept { + return false; + } +}; + int64_t add(ErlNifEnv *, int64_t x, int64_t y) { return x + y; } FINE_NIF(add, 0); @@ -295,6 +334,25 @@ std::nullopt_t shared_mutex_shared_lock_test(ErlNifEnv *) { } FINE_NIF(shared_mutex_shared_lock_test, 0); +template using NifVector = std::vector>; + +template +using NifBasicString = std::basic_string, Allocator>; + +using NifString = NifBasicString; + +NifVector allocators(ErlNifEnv *, NifString string, + std::uint64_t repeat) { + NifVector strings; + + for (std::uint64_t i = 0; i != repeat; ++i) { + strings.emplace_back(string); + } + + return strings; +} +FINE_NIF(allocators, 0); + } // namespace finest FINE_INIT("Elixir.Finest.NIF"); diff --git a/test/lib/finest/nif.ex b/test/lib/finest/nif.ex index 371ade0..b9c57b7 100644 --- a/test/lib/finest/nif.ex +++ b/test/lib/finest/nif.ex @@ -56,5 +56,7 @@ defmodule Finest.NIF do def shared_mutex_unique_lock_test(), do: err!() def shared_mutex_shared_lock_test(), do: err!() + def allocators(_string, _repeat), 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 dc3bc9b..dd0bd1b 100644 --- a/test/test/finest_test.exs +++ b/test/test/finest_test.exs @@ -307,4 +307,11 @@ defmodule FinestTest do NIF.shared_mutex_shared_lock_test() end end + + describe "allocators" do + test "allocators" do + assert NIF.allocators("abc", 16) == + ["abc"] |> Stream.cycle() |> Stream.take(16) |> Enum.to_list() + end + end end