Skip to content

Commit 2708c3d

Browse files
committed
Add enif_hash support
This is related to #10, and will allow `fine::Atom`s and `fine::Term`s to be used as `std::map`, and `std::unordered_map` keys.
1 parent d0e7a1c commit 2708c3d

File tree

4 files changed

+69
-0
lines changed

4 files changed

+69
-0
lines changed

c_include/fine.hpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ template <typename T> ERL_NIF_TERM encode(ErlNifEnv *env, const T &value);
4444
template <typename T, typename SFINAE = void> struct Decoder;
4545
template <typename T, typename SFINAE = void> struct Encoder;
4646

47+
enum class HashAlgorithm {
48+
INTERNAL = ERL_NIF_INTERNAL_HASH,
49+
PHASH2 = ERL_NIF_PHASH2,
50+
};
51+
52+
namespace __private__ {
53+
template <typename T> struct Hasher;
54+
}
55+
4756
namespace __private__ {
4857
std::vector<ErlNifFunc> &get_erl_nif_funcs();
4958
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info);
@@ -90,6 +99,7 @@ class Atom {
9099
}
91100

92101
friend struct Encoder<Atom>;
102+
friend struct __private__::Hasher<Atom>;
93103

94104
friend int __private__::load(ErlNifEnv *env, void **priv_data,
95105
ERL_NIF_TERM load_info);
@@ -1071,6 +1081,34 @@ inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
10711081
}
10721082
} // namespace __private__
10731083

1084+
// Hash
1085+
namespace __private__ {
1086+
template <> struct Hasher<Atom> {
1087+
static std::uint64_t hash(HashAlgorithm algorithm, const Atom &atom,
1088+
std::uint64_t salt = 0) noexcept {
1089+
return enif_hash(static_cast<ErlNifHash>(algorithm), *atom.term, salt);
1090+
}
1091+
};
1092+
1093+
template <> struct Hasher<Term> {
1094+
static std::uint64_t hash(HashAlgorithm algorithm, const Term &term,
1095+
std::uint64_t salt = 0) noexcept {
1096+
return enif_hash(static_cast<ErlNifHash>(algorithm), term, salt);
1097+
}
1098+
};
1099+
} // namespace __private__
1100+
1101+
template <HashAlgorithm A = HashAlgorithm::INTERNAL, typename T>
1102+
inline static std::uint64_t hash(const T &value, std::uint64_t salt = 0) {
1103+
return __private__::Hasher<T>::hash(A, value, salt);
1104+
}
1105+
1106+
template <typename T>
1107+
inline static std::uint64_t hash(HashAlgorithm algorithm, const T &value,
1108+
std::uint64_t salt = 0) {
1109+
return __private__::Hasher<T>::hash(algorithm, value, salt);
1110+
}
1111+
10741112
// Macros
10751113

10761114
#define FINE_NIF(name, flags) \
@@ -1128,4 +1166,18 @@ inline int load(ErlNifEnv *env, void **, ERL_NIF_TERM) {
11281166

11291167
} // namespace fine
11301168

1169+
namespace std {
1170+
template <> struct hash<::fine::Term> {
1171+
size_t operator()(const ::fine::Term &term) noexcept {
1172+
return ::fine::hash(term);
1173+
}
1174+
};
1175+
1176+
template <> struct hash<::fine::Atom> {
1177+
size_t operator()(const ::fine::Term &term) noexcept {
1178+
return ::fine::hash(term);
1179+
}
1180+
};
1181+
} // namespace std
1182+
11311183
#endif

test/c_src/finest.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,13 @@ std::nullopt_t shared_mutex_shared_lock_test(ErlNifEnv *) {
325325
}
326326
FINE_NIF(shared_mutex_shared_lock_test, 0);
327327

328+
std::uint64_t hash_test(ErlNifEnv *, fine::Term term) noexcept {
329+
// Ensure the use of PHASH2. INTERNAL is not guaranteed to be stable across
330+
// ERTS instances, even less so ERTS versions.
331+
return fine::hash<fine::HashAlgorithm::PHASH2>(term);
332+
}
333+
FINE_NIF(hash_test, 0);
334+
328335
} // namespace finest
329336

330337
FINE_INIT("Elixir.Finest.NIF");

test/lib/finest/nif.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,7 @@ defmodule Finest.NIF do
5959
def shared_mutex_unique_lock_test(), do: err!()
6060
def shared_mutex_shared_lock_test(), do: err!()
6161

62+
def hash_test(_term), do: err!()
63+
6264
defp err!(), do: :erlang.nif_error(:not_loaded)
6365
end

test/test/finest_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,12 @@ defmodule FinestTest do
313313
NIF.shared_mutex_shared_lock_test()
314314
end
315315
end
316+
317+
describe "hash" do
318+
test "phash2" do
319+
for elem <- [42, "fine", ["it", %{"should" => {"just", "work"}}]] do
320+
assert NIF.hash_test(elem) == :erlang.phash2(elem)
321+
end
322+
end
323+
end
316324
end

0 commit comments

Comments
 (0)