[util] Update constexpr hash to be FNV-1a

The constexpr_hash.h header has fallen out of use. As constexpr hashing
will be used for IDs with the service locator protocol, update these
hashes to be 32 and 64 bit FNV-1a, and replace the _h user-defined
literal with _id (a 64-bit hash), and _id8 (a 32-bit hash folded down to
8 bits). These are now in the util/hash.h header along with the runtime
hash functions.
This commit is contained in:
Justin C. Miller
2022-02-22 00:20:00 -08:00
parent 63265728d4
commit 2640cea175
6 changed files with 94 additions and 70 deletions

View File

@@ -9,7 +9,6 @@ module("util",
public_headers = [
"util/basic_types.h",
"util/bip_buffer.h",
"util/constexpr_hash.h",
"util/counted.h",
"util/deque.h",
"util/enum_bitfields.h",

View File

@@ -2,6 +2,8 @@
/// file basic_types.h
/// Type properties that would normally come from <type_traits>
#include <stdint.h>
namespace types {
template <bool B, typename T = void> struct enable_if {};
@@ -59,5 +61,16 @@ template<typename B1> struct conjunction<B1> : B1 {};
template<typename B1, typename... Bn>
struct conjunction<B1, Bn...> : conditional<bool(B1::value), conjunction<Bn...>, B1>::type {};
template <unsigned N> struct sized_uint_type {};
template <> struct sized_uint_type<8> { using type = uint8_t; };
template <> struct sized_uint_type<16> { using type = uint16_t; };
template <> struct sized_uint_type<32> { using type = uint32_t; };
template <> struct sized_uint_type<64> { using type = uint64_t; };
template <unsigned N> struct sized_uint {
static constexpr uint64_t mask = ((1<<N)-1);
static constexpr unsigned bits = N;
using type = typename sized_uint_type<N>::type;
};
} // namespace types

View File

@@ -1,42 +0,0 @@
#pragma once
/// \file constexpr_hash.h
/// A complile-time hashing function
#include <stdint.h>
#include <stddef.h>
namespace util {
constexpr static const uint8_t pearson_hash_table[256] = {
0x76,0x07,0xbe,0x47,0xcf,0x41,0x0a,0xe8,0x01,0x5c,0x9f,0xc5,0x24,0x63,0x9a,0x85,
0x39,0x2c,0xe2,0x34,0xb9,0xf2,0xae,0x40,0x10,0x90,0x94,0xd1,0x98,0x2d,0x16,0xfd,
0xc6,0x48,0x0d,0xce,0x74,0x43,0x28,0xf9,0x61,0x12,0xd0,0xcd,0xd8,0xd7,0xa8,0x78,
0x73,0x70,0xcc,0x1e,0x17,0xa7,0x87,0x38,0x68,0x91,0xc1,0x04,0x3f,0xf5,0xde,0xa3,
0x8a,0xe5,0x9b,0xec,0x97,0xd5,0x71,0x4a,0x20,0xca,0xc8,0xc4,0x83,0x53,0xe7,0x7b,
0x64,0x31,0x06,0xe0,0x7a,0xb6,0x52,0x8c,0xba,0x58,0xcb,0xb5,0x37,0x51,0x59,0xa1,
0x11,0xe3,0x5a,0xdb,0xe1,0x6d,0x46,0x62,0xaf,0xbd,0x57,0xb8,0x0e,0xf4,0xdd,0xa6,
0x45,0xf8,0x35,0x42,0x56,0xdf,0xad,0x80,0xb2,0x0b,0x5b,0xd4,0x86,0xb3,0xf0,0xc9,
0x3c,0xa5,0xc0,0x8e,0x55,0x77,0xeb,0x36,0x79,0xab,0x4c,0x25,0xed,0xa9,0x75,0x8f,
0xee,0xc2,0x72,0x8b,0x60,0x2a,0xfa,0x32,0xe9,0xda,0x03,0x1b,0x27,0x69,0x18,0x9e,
0x88,0x96,0x54,0x81,0x30,0x22,0x7c,0x4f,0xc7,0xef,0x5d,0xa4,0x67,0x44,0xc3,0x99,
0xbb,0xd3,0x8d,0x65,0xb1,0x82,0x09,0x1a,0x13,0xd9,0x9c,0x4d,0xb0,0xfc,0xac,0xbc,
0x6a,0x29,0x95,0x19,0x92,0xaa,0x49,0x7d,0x3b,0xfb,0x50,0xb7,0xf3,0x5e,0x3e,0x6b,
0x3a,0x14,0x2b,0xb4,0xfe,0xe6,0x93,0x23,0xd6,0x1f,0xd2,0x0c,0x1d,0x9d,0x6c,0x66,
0x1c,0x89,0xbf,0xf6,0xff,0x6f,0x84,0x6e,0x2e,0xea,0x21,0xf7,0x7f,0x33,0xf1,0xe4,
0x3d,0x0f,0x05,0x08,0x4e,0xa2,0xa0,0x2f,0xdc,0x00,0x5f,0x15,0x7e,0x02,0x4b,0x26
};
constexpr inline uint8_t pearson_hash_8(const char *s, uint8_t inv) {
return (*s) ? pearson_hash_8(s + 1, pearson_hash_table[inv ^ *s]) : inv;
}
constexpr inline uint32_t djb_hash_32(const char *s, int off = 0) {
return !s[off] ? 5381 : (djb_hash_32(s, off+1)*33) ^ s[off];
}
} // namespace util
constexpr inline uint8_t operator "" _h (const char *s, size_t len) {
return util::pearson_hash_8(s, static_cast<uint8_t>(len & 0xff));
}

View File

@@ -4,40 +4,68 @@
#include <stddef.h>
#include <stdint.h>
#include <util/basic_types.h>
namespace util {
namespace fnv1a {
constexpr uint64_t fnv_64_prime = 0x100000001b3ull;
constexpr uint64_t fnv1a_64_init = 0xcbf29ce484222325ull;
constexpr uint32_t val_32 = 0x811c9dc5;
constexpr uint32_t prime_32 = 0x1000193;
constexpr uint64_t val_64 = 0xcbf29ce484222325;
constexpr uint64_t prime_64 = 0x100000001b3;
/// Return the FNV-1a hash of the given 0-terminated string.
inline uint64_t hash_string(char const *s, uint64_t init = 0) {
if (!init) init = fnv1a_64_init;
while(s && *s) {
init ^= static_cast<uint64_t>(*s++);
init *= fnv_64_prime;
}
return init;
/// Evaluate the 32-bit constexpr FNV-1a hash of the given 0-terminated string.
inline constexpr uint32_t hash32_const(const char* const str, uint32_t value = val_32) {
return (str[0] == '\0') ? value :
hash32_const(&str[1], (value ^ uint32_t(str[0])) * prime_32);
}
/// Return the FNV-1a hash of the given buffer.
inline uint64_t hash_buffer(const void *v, size_t len, uint64_t init = 0) {
/// Evaluate the 64-bit constexpr FNV-1a hash of the given 0-terminated string.
inline constexpr uint64_t hash64_const(const char* const str, uint64_t value = val_64) {
return (str[0] == '\0') ? value :
hash64_const(&str[1], (value ^ uint64_t(str[0])) * prime_64);
}
/// Return the 64-bit FNV-1a hash of the given 0-terminated string.
inline uint64_t hash64_string(char const *s, uint64_t value = val_64) {
while(s && *s) {
value ^= static_cast<uint64_t>(*s++);
value *= prime_64;
}
return value;
}
/// Return the 64-bit FNV-1a hash of the given buffer.
inline uint64_t hash64(void const * const v, size_t len, uint64_t value = val_64) {
uint8_t const *p = reinterpret_cast<uint8_t const*>(v);
uint8_t const *end = p + len;
if (!init) init = fnv1a_64_init;
while(p < end) {
init ^= static_cast<uint64_t>(*p++);
init *= fnv_64_prime;
value ^= static_cast<uint64_t>(*p++);
value *= prime_64;
}
return init;
return value;
}
} // namespace fnv1a
template <unsigned N>
constexpr inline typename types::sized_uint<N>::type hash_fold(typename types::sized_uint<N*2>::type value) {
return (value >> types::sized_uint<N>::bits) ^ (value & types::sized_uint<N>::mask);
}
template <unsigned N>
constexpr inline typename types::sized_uint<N>::type hash_fold(typename types::sized_uint<N*4>::type value) {
typename types::sized_uint<N*2>::type value2 = hash_fold<N*2>(value);
return (value2 >> types::sized_uint<N>::bits) ^ (value2 & types::sized_uint<N>::mask);
}
template <typename T>
inline uint64_t hash(const T &v) {
return hash_buffer(reinterpret_cast<const void*>(&v), sizeof(T));
}
inline uint64_t hash(const T &v) { return fnv1a::hash64(reinterpret_cast<const void*>(&v), sizeof(T)); }
template <> inline uint64_t hash<uint64_t>(const uint64_t &i) { return i; }
template <> inline uint64_t hash<const char *>(const char * const &s) { return hash_string(s); }
template <> inline uint64_t hash<const char *>(const char * const &s) { return fnv1a::hash64_string(s); }
} // namespace util
constexpr inline uint8_t operator "" _id (const char *s, size_t len) { return util::fnv1a::hash64_const(s); }
constexpr inline uint8_t operator "" _id8 (const char *s, size_t len) { return util::hash_fold<8>(util::fnv1a::hash32_const(s)); }

View File

@@ -9,7 +9,6 @@
#include <j6/syscalls.h>
#include <j6/sysconf.h>
#include <j6/types.h>
#include <util/constexpr_hash.h>
#include "io.h"
#include "serial.h"

View File

@@ -1,4 +1,4 @@
#include <util/constexpr_hash.h>
#include <util/hash.h>
#include "test_case.h"
class hash_tests :
@@ -6,19 +6,46 @@ class hash_tests :
{
};
TEST_CASE( hash_tests, equality_test )
TEST_CASE( hash_tests, equality_test64 )
{
const unsigned hash1 = static_cast<unsigned>("hash1!"_h);
const auto hash1 = static_cast<unsigned>("hash1!"_id);
CHECK( hash1 == 210, "hash gave unexpected value");
const unsigned hash2 = static_cast<unsigned>("hash1!"_h);
const auto hash2 = static_cast<unsigned>("hash1!"_id);
CHECK(hash1 == hash2, "hashes of equal strings should be equal");
const unsigned hash3 = static_cast<unsigned>("not hash1!"_h);
const auto hash3 = static_cast<unsigned>("not hash1!"_id);
CHECK(hash1 != hash3, "hashes of different strings should not be equal");
CHECK(hash3 == 37, "hash gave unexpected value");
const unsigned hash4 = static_cast<unsigned>("another thing that's longer"_h);
const auto hash4 = static_cast<unsigned>("another thing that's longer"_id);
CHECK(hash1 != hash4, "hashes of different strings should not be equal");
CHECK(hash4 == 212, "hash gave unexpected value");
}
TEST_CASE( hash_tests, equality_test8 )
{
const uint8_t type_ids[] = {
"system"_id8,
"event"_id8,
"channel"_id8,
"endpoint"_id8,
"mailbox"_id8,
"vma"_id8,
"process"_id8,
"thread"_id8
};
constexpr unsigned id_count = sizeof(type_ids) / sizeof(type_ids[0]);
const uint8_t system = "system"_id8;
CHECK_BARE( type_ids[0] == system );
for (unsigned i = 0; i < id_count; ++i) {
for (unsigned j = i + 1; j < id_count; ++j) {
CHECK_BARE( type_ids[i] != type_ids[j] );
}
}
}