Skip to content

Commit

Permalink
Type erased hash_slot_fn that depends only on key types (and hash fun…
Browse files Browse the repository at this point in the history
…ction).

PiperOrigin-RevId: 603148301
Change-Id: Ie2e5702995c9e1ef4d5aaab23bc89a1eb5007a86
  • Loading branch information
Abseil Team authored and copybara-github committed Jan 31, 2024
1 parent 780bfc1 commit 4c7e7c7
Show file tree
Hide file tree
Showing 16 changed files with 214 additions and 16 deletions.
4 changes: 4 additions & 0 deletions absl/container/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ cc_library(
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":container_memory",
":hash_function_defaults",
":node_slot_policy",
":raw_hash_set",
Expand Down Expand Up @@ -504,6 +505,7 @@ cc_test(
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":container_memory",
":hash_policy_traits",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
Expand Down Expand Up @@ -714,6 +716,7 @@ cc_binary(
tags = ["benchmark"],
visibility = ["//visibility:private"],
deps = [
":container_memory",
":hash_function_defaults",
":raw_hash_set",
"//absl/base:raw_logging_internal",
Expand Down Expand Up @@ -753,6 +756,7 @@ cc_test(
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":container_memory",
":raw_hash_set",
":tracked",
"//absl/base:config",
Expand Down
3 changes: 3 additions & 0 deletions absl/container/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ absl_cc_library(
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::container_memory
absl::core_headers
absl::hash_function_defaults
absl::node_slot_policy
Expand Down Expand Up @@ -560,6 +561,7 @@ absl_cc_test(
COPTS
${ABSL_TEST_COPTS}
DEPS
absl::container_memory
absl::hash_policy_traits
GTest::gmock_main
)
Expand Down Expand Up @@ -776,6 +778,7 @@ absl_cc_test(
${ABSL_TEST_COPTS}
DEPS
absl::config
absl::container_memory
absl::raw_hash_set
absl::tracked
GTest::gmock_main
Expand Down
7 changes: 7 additions & 0 deletions absl/container/flat_hash_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,13 @@ struct FlatHashMapPolicy {
std::forward<Args>(args)...);
}

template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
return memory_internal::IsLayoutCompatible<K, V>::value
? &TypeErasedApplyToSlotFn<Hash, K>
: nullptr;
}

static size_t space_used(const slot_type*) { return 0; }

static std::pair<const K, V>& element(slot_type* slot) { return slot->value; }
Expand Down
6 changes: 6 additions & 0 deletions absl/container/flat_hash_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#ifndef ABSL_CONTAINER_FLAT_HASH_SET_H_
#define ABSL_CONTAINER_FLAT_HASH_SET_H_

#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
Expand Down Expand Up @@ -492,6 +493,11 @@ struct FlatHashSetPolicy {
}

static size_t space_used(const T*) { return 0; }

template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
return &TypeErasedApplyToSlotFn<Hash, T>;
}
};
} // namespace container_internal

Expand Down
20 changes: 20 additions & 0 deletions absl/container/internal/container_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,26 @@ struct map_slot_policy {
}
};

// Type erased function for computing hash of the slot.
using HashSlotFn = size_t (*)(const void* hash_fn, void* slot);

// Type erased function to apply `Fn` to data inside of the `slot`.
// The data is expected to have type `T`.
template <class Fn, class T>
size_t TypeErasedApplyToSlotFn(const void* fn, void* slot) {
const auto* f = static_cast<const Fn*>(fn);
return (*f)(*static_cast<const T*>(slot));
}

// Type erased function to apply `Fn` to data inside of the `*slot_ptr`.
// The data is expected to have type `T`.
template <class Fn, class T>
size_t TypeErasedDerefAndApplyToSlotFn(const void* fn, void* slot_ptr) {
const auto* f = static_cast<const Fn*>(fn);
const T* slot = *static_cast<const T**>(slot_ptr);
return (*f)(*slot);
}

} // namespace container_internal
ABSL_NAMESPACE_END
} // namespace absl
Expand Down
14 changes: 14 additions & 0 deletions absl/container/internal/container_memory_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,20 @@ TEST(MapSlotPolicy, DestroyReturnsTrue) {
}
}

TEST(ApplyTest, TypeErasedApplyToSlotFn) {
size_t x = 7;
auto fn = [](size_t v) { return v * 2; };
EXPECT_EQ((TypeErasedApplyToSlotFn<decltype(fn), size_t>(&fn, &x)), 14);
}

TEST(ApplyTest, TypeErasedDerefAndApplyToSlotFn) {
size_t x = 7;
auto fn = [](size_t v) { return v * 2; };
size_t* x_ptr = &x;
EXPECT_EQ(
(TypeErasedDerefAndApplyToSlotFn<decltype(fn), size_t>(&fn, &x_ptr)), 14);
}

} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
Expand Down
35 changes: 35 additions & 0 deletions absl/container/internal/hash_policy_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,41 @@ struct hash_policy_traits : common_policy_traits<Policy> {
static auto value(T* elem) -> decltype(P::value(elem)) {
return P::value(elem);
}

using HashSlotFn = size_t (*)(const void* hash_fn, void* slot);

template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
// get_hash_slot_fn may return nullptr to signal that non type erased function
// should be used. GCC warns against comparing function address with nullptr.
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
// silent error: the address of * will never be NULL [-Werror=address]
#pragma GCC diagnostic ignored "-Waddress"
#endif
return Policy::template get_hash_slot_fn<Hash>() == nullptr
? &hash_slot_fn_non_type_erased<Hash>
: Policy::template get_hash_slot_fn<Hash>();
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
}

private:
template <class Hash>
struct HashElement {
template <class K, class... Args>
size_t operator()(const K& key, Args&&...) const {
return h(key);
}
const Hash& h;
};

template <class Hash>
static size_t hash_slot_fn_non_type_erased(const void* hash_fn, void* slot) {
return Policy::apply(HashElement<Hash>{*static_cast<const Hash*>(hash_fn)},
Policy::element(static_cast<slot_type*>(slot)));
}
};

} // namespace container_internal
Expand Down
64 changes: 64 additions & 0 deletions absl/container/internal/hash_policy_traits_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@

#include "absl/container/internal/hash_policy_traits.h"

#include <cstddef>
#include <functional>
#include <memory>
#include <new>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/container/internal/container_memory.h"

namespace absl {
ABSL_NAMESPACE_BEGIN
Expand All @@ -42,6 +44,11 @@ struct PolicyWithoutOptionalOps {
static int apply(int v) { return apply_impl(v); }
static std::function<int(int)> apply_impl;
static std::function<Slot&(Slot*)> value;

template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
return nullptr;
}
};

std::function<int(int)> PolicyWithoutOptionalOps::apply_impl;
Expand Down Expand Up @@ -74,6 +81,63 @@ TEST_F(Test, value) {
EXPECT_EQ(&b, &hash_policy_traits<PolicyWithoutOptionalOps>::value(&a));
}

struct Hash {
size_t operator()(Slot a) const { return static_cast<size_t>(a) * 5; }
};

struct PolicyNoHashFn {
using slot_type = Slot;
using key_type = Slot;
using init_type = Slot;

static size_t* apply_called_count;

static Slot& element(Slot* slot) { return *slot; }
template <typename Fn>
static size_t apply(const Fn& fn, int v) {
++(*apply_called_count);
return fn(v);
}

template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
return nullptr;
}
};

size_t* PolicyNoHashFn::apply_called_count;

struct PolicyCustomHashFn : PolicyNoHashFn {
template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
return &TypeErasedApplyToSlotFn<Hash, int>;
}
};

TEST(HashTest, PolicyNoHashFn_get_hash_slot_fn) {
size_t apply_called_count = 0;
PolicyNoHashFn::apply_called_count = &apply_called_count;

Hash hasher;
Slot value = 7;
auto* fn = hash_policy_traits<PolicyNoHashFn>::get_hash_slot_fn<Hash>();
EXPECT_NE(fn, nullptr);
EXPECT_EQ(fn(&hasher, &value), hasher(value));
EXPECT_EQ(apply_called_count, 1);
}

TEST(HashTest, PolicyCustomHashFn_get_hash_slot_fn) {
size_t apply_called_count = 0;
PolicyNoHashFn::apply_called_count = &apply_called_count;

Hash hasher;
Slot value = 7;
auto* fn = hash_policy_traits<PolicyCustomHashFn>::get_hash_slot_fn<Hash>();
EXPECT_EQ(fn, PolicyCustomHashFn::get_hash_slot_fn<Hash>());
EXPECT_EQ(fn(&hasher, &value), hasher(value));
EXPECT_EQ(apply_called_count, 0);
}

} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
Expand Down
4 changes: 2 additions & 2 deletions absl/container/internal/raw_hash_set.cc
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ static inline void* PrevSlot(void* slot, size_t slot_size) {
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size);
}

void DropDeletesWithoutResize(CommonFields& common,
void DropDeletesWithoutResize(CommonFields& common, const void* hash_fn,
const PolicyFunctions& policy, void* tmp_space) {
void* set = &common;
void* slot_array = common.slot_array();
Expand Down Expand Up @@ -175,7 +175,7 @@ void DropDeletesWithoutResize(CommonFields& common,
++i, slot_ptr = NextSlot(slot_ptr, slot_size)) {
assert(slot_ptr == SlotAddress(slot_array, i, slot_size));
if (!IsDeleted(ctrl[i])) continue;
const size_t hash = (*hasher)(set, slot_ptr);
const size_t hash = (*hasher)(hash_fn, slot_ptr);
const FindInfo target = find_first_non_full(common, hash);
const size_t new_i = target.offset;
total_probe_length += target.probe_length;
Expand Down
15 changes: 4 additions & 11 deletions absl/container/internal/raw_hash_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -1802,7 +1802,7 @@ struct PolicyFunctions {
size_t slot_size;

// Returns the hash of the pointed-to slot.
size_t (*hash_slot)(void* set, void* slot);
size_t (*hash_slot)(const void* hash_fn, void* slot);

// Transfer the contents of src_slot to dst_slot.
void (*transfer)(void* set, void* dst_slot, void* src_slot);
Expand Down Expand Up @@ -1847,7 +1847,7 @@ ABSL_ATTRIBUTE_NOINLINE void TransferRelocatable(void*, void* dst, void* src) {
}

// Type-erased version of raw_hash_set::drop_deletes_without_resize.
void DropDeletesWithoutResize(CommonFields& common,
void DropDeletesWithoutResize(CommonFields& common, const void* hash_fn,
const PolicyFunctions& policy, void* tmp_space);

// A SwissTable.
Expand Down Expand Up @@ -2989,7 +2989,7 @@ class raw_hash_set {
inline void drop_deletes_without_resize() {
// Stack-allocate space for swapping elements.
alignas(slot_type) unsigned char tmp[sizeof(slot_type)];
DropDeletesWithoutResize(common(), GetPolicyFunctions(), tmp);
DropDeletesWithoutResize(common(), &hash_ref(), GetPolicyFunctions(), tmp);
}

// Called whenever the table *might* need to conditionally grow.
Expand Down Expand Up @@ -3238,13 +3238,6 @@ class raw_hash_set {
return settings_.template get<3>();
}

// Make type-specific functions for this type's PolicyFunctions struct.
static size_t hash_slot_fn(void* set, void* slot) {
auto* h = static_cast<raw_hash_set*>(set);
return PolicyTraits::apply(
HashElement{h->hash_ref()},
PolicyTraits::element(static_cast<slot_type*>(slot)));
}
static void transfer_slot_fn(void* set, void* dst, void* src) {
auto* h = static_cast<raw_hash_set*>(set);
h->transfer(static_cast<slot_type*>(dst), static_cast<slot_type*>(src));
Expand All @@ -3266,7 +3259,7 @@ class raw_hash_set {
static const PolicyFunctions& GetPolicyFunctions() {
static constexpr PolicyFunctions value = {
sizeof(slot_type),
&raw_hash_set::hash_slot_fn,
PolicyTraits::template get_hash_slot_fn<hasher>(),
PolicyTraits::transfer_uses_memcpy()
? TransferRelocatable<sizeof(slot_type)>
: &raw_hash_set::transfer_slot_fn,
Expand Down
8 changes: 7 additions & 1 deletion absl/container/internal/raw_hash_set_allocator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/raw_hash_set.h"
#include "absl/container/internal/tracked.h"

Expand Down Expand Up @@ -133,7 +134,7 @@ class CheckedAlloc {
};

struct Identity {
int32_t operator()(int32_t v) const { return v; }
size_t operator()(int32_t v) const { return static_cast<size_t>(v); }
};

struct Policy {
Expand Down Expand Up @@ -178,6 +179,11 @@ struct Policy {
}

static slot_type& element(slot_type* slot) { return *slot; }

template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
return nullptr;
}
};

template <int Spec>
Expand Down
11 changes: 11 additions & 0 deletions absl/container/internal/raw_hash_set_benchmark.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <vector>

#include "absl/base/internal/raw_logging.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_function_defaults.h"
#include "absl/container/internal/raw_hash_set.h"
#include "absl/strings/str_format.h"
Expand Down Expand Up @@ -59,6 +60,11 @@ struct IntPolicy {
static auto apply(F&& f, int64_t x) -> decltype(std::forward<F>(f)(x, x)) {
return std::forward<F>(f)(x, x);
}

template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
return nullptr;
}
};

class StringPolicy {
Expand Down Expand Up @@ -117,6 +123,11 @@ class StringPolicy {
return apply_impl(std::forward<F>(f),
PairArgs(std::forward<Args>(args)...));
}

template <class Hash>
static constexpr HashSlotFn get_hash_slot_fn() {
return nullptr;
}
};

struct StringHash : container_internal::hash_default_hash<absl::string_view> {
Expand Down
Loading

0 comments on commit 4c7e7c7

Please sign in to comment.