1
0
mirror of git://jb55.com/damus synced 2024-09-16 02:03:45 +00:00
damus/nostrdb/flatcc/refmap.c
2023-08-28 08:17:25 -07:00

249 lines
7.0 KiB
C

/*
* Optional file that can be included in runtime library to support DAG
* cloning with the builder and may also be used for custom purposes
* standalone. See also comments in `flatcc/flatcc_builder.h`.
*
* Note that dynamic construction takes place and that large offset
* vectors might consume significant space if there are not many shared
* references. In the basic use case no allocation takes place because a
* few references can be held using only a small stack allocated hash
* table.
*/
#include <stdlib.h>
#include <string.h>
#include "flatcc_rtconfig.h"
#include "flatcc_refmap.h"
#include "flatcc_alloc.h"
#include "flatcc_assert.h"
#define _flatcc_refmap_calloc FLATCC_CALLOC
#define _flatcc_refmap_free FLATCC_FREE
/* Can be used as a primitive defense against collision attacks. */
#ifdef FLATCC_HASH_SEED
#define _flatcc_refmap_seed FLATCC_HASH_SEED
#else
#define _flatcc_refmap_seed 0x2f693b52
#endif
static inline size_t _flatcc_refmap_above_load_factor(size_t count, size_t buckets)
{
static const size_t d = 256;
static const size_t n = (size_t)((FLATCC_REFMAP_LOAD_FACTOR) * 256.0f);
return count >= buckets * n / d;
}
#define _flatcc_refmap_probe(k, i, N) ((k + i) & N)
void flatcc_refmap_clear(flatcc_refmap_t *refmap)
{
if (refmap->table && refmap->table != refmap->min_table) {
_flatcc_refmap_free(refmap->table);
}
flatcc_refmap_init(refmap);
}
static inline size_t _flatcc_refmap_hash(const void *src)
{
/* MurmurHash3 64-bit finalizer */
uint64_t x;
x = (uint64_t)((size_t)src) ^ _flatcc_refmap_seed;
x ^= x >> 33;
x *= 0xff51afd7ed558ccdULL;
x ^= x >> 33;
x *= 0xc4ceb9fe1a85ec53ULL;
x ^= x >> 33;
return (size_t)x;
}
void flatcc_refmap_reset(flatcc_refmap_t *refmap)
{
if (refmap->count) {
memset(refmap->table, 0, sizeof(refmap->table[0]) * refmap->buckets);
}
refmap->count = 0;
}
/*
* Technically resize also supports shrinking which may be useful for
* adapations, but the current hash table never deletes individual items.
*/
int flatcc_refmap_resize(flatcc_refmap_t *refmap, size_t count)
{
const size_t min_buckets = sizeof(refmap->min_table) / sizeof(refmap->min_table[0]);
size_t i;
size_t buckets;
size_t buckets_old;
struct flatcc_refmap_item *T_old;
if (count < refmap->count) {
count = refmap->count;
}
buckets = min_buckets;
while (_flatcc_refmap_above_load_factor(count, buckets)) {
buckets *= 2;
}
if (refmap->buckets == buckets) {
return 0;
}
T_old = refmap->table;
buckets_old = refmap->buckets;
if (buckets == min_buckets) {
memset(refmap->min_table, 0, sizeof(refmap->min_table));
refmap->table = refmap->min_table;
} else {
refmap->table = _flatcc_refmap_calloc(buckets, sizeof(refmap->table[0]));
if (refmap->table == 0) {
refmap->table = T_old;
FLATCC_ASSERT(0); /* out of memory */
return -1;
}
}
refmap->buckets = buckets;
refmap->count = 0;
for (i = 0; i < buckets_old; ++i) {
if (T_old[i].src) {
flatcc_refmap_insert(refmap, T_old[i].src, T_old[i].ref);
}
}
if (T_old && T_old != refmap->min_table) {
_flatcc_refmap_free(T_old);
}
return 0;
}
flatcc_refmap_ref_t flatcc_refmap_insert(flatcc_refmap_t *refmap, const void *src, flatcc_refmap_ref_t ref)
{
struct flatcc_refmap_item *T;
size_t N, i, j, k;
if (src == 0) return ref;
if (_flatcc_refmap_above_load_factor(refmap->count, refmap->buckets)) {
if (flatcc_refmap_resize(refmap, refmap->count * 2)) {
return flatcc_refmap_not_found; /* alloc failed */
}
}
T = refmap->table;
N = refmap->buckets - 1;
k = _flatcc_refmap_hash(src);
i = 0;
j = _flatcc_refmap_probe(k, i, N);
while (T[j].src) {
if (T[j].src == src) {
return T[j].ref = ref;
}
++i;
j = _flatcc_refmap_probe(k, i, N);
}
++refmap->count;
T[j].src = src;
return T[j].ref = ref;
}
flatcc_refmap_ref_t flatcc_refmap_find(flatcc_refmap_t *refmap, const void *src)
{
struct flatcc_refmap_item *T;
size_t N, i, j, k;
if (refmap->count == 0) {
return flatcc_refmap_not_found;
}
T = refmap->table;
N = refmap->buckets - 1;
k = _flatcc_refmap_hash(src);
i = 0;
j = _flatcc_refmap_probe(k, i, N);
while (T[j].src) {
if (T[j].src == src) return T[j].ref;
++i;
j = _flatcc_refmap_probe(k, i, N);
}
return flatcc_refmap_not_found;
}
/*
* To run test from project root:
*
* cc -D FLATCC_REFMAP_TEST -I include src/runtime/refmap.c -o test_refmap && ./test_refmap
*
*/
#ifdef FLATCC_REFMAP_TEST
#include <stdio.h>
#ifndef FLATCC_REFMAP_H
#include "flatcc_refmap.h"
#endif
#define test(x) do { if (!(x)) { fprintf(stderr, "%02d: refmap test failed\n", __LINE__); exit(-1); } } while (0)
#define test_start() fprintf(stderr, "starting refmap test ...\n")
#define test_ok() fprintf(stderr, "refmap test succeeded\n")
int main()
{
int i;
int data[1000];
int a = 1;
int b = 2;
int c = 3;
flatcc_refmap_t refmap;
flatcc_refmap_init(&refmap);
test(flatcc_refmap_find(&refmap, &a) == flatcc_refmap_not_found);
test(flatcc_refmap_find(&refmap, &b) == flatcc_refmap_not_found);
test(flatcc_refmap_find(&refmap, &c) == flatcc_refmap_not_found);
test(flatcc_refmap_find(&refmap, 0) == flatcc_refmap_not_found);
test(flatcc_refmap_find(&refmap, &a) == 0);
test(flatcc_refmap_insert(&refmap, &a, 42) == 42);
test(flatcc_refmap_find(&refmap, &a) == 42);
test(flatcc_refmap_find(&refmap, &b) == flatcc_refmap_not_found);
test(flatcc_refmap_find(&refmap, &c) == flatcc_refmap_not_found);
test(flatcc_refmap_insert(&refmap, &a, 42) == 42);
test(flatcc_refmap_find(&refmap, &a) == 42);
test(refmap.count == 1);
test(flatcc_refmap_insert(&refmap, &a, 43) == 43);
test(flatcc_refmap_find(&refmap, &a) == 43);
test(refmap.count == 1);
test(flatcc_refmap_insert(&refmap, &b, -10) == -10);
test(flatcc_refmap_insert(&refmap, &c, 100) == 100);
test(refmap.count == 3);
test(flatcc_refmap_find(&refmap, &a) == 43);
test(flatcc_refmap_find(&refmap, &b) == -10);
test(flatcc_refmap_find(&refmap, &c) == 100);
test(flatcc_refmap_insert(&refmap, 0, 1000) == 1000);
test(flatcc_refmap_find(&refmap, 0) == 0);
test(refmap.count == 3);
test(flatcc_refmap_insert(&refmap, &b, 0) == 0);
test(flatcc_refmap_find(&refmap, &b) == 0);
test(refmap.count == 3);
flatcc_refmap_reset(&refmap);
test(refmap.count == 0);
test(refmap.buckets > 0);
for (i = 0; i < 1000; ++i) {
test(flatcc_refmap_insert(&refmap, data + i, i + 42) == i + 42);
}
test(refmap.count == 1000);
for (i = 0; i < 1000; ++i) {
test(flatcc_refmap_find(&refmap, data + i) == i + 42);
}
flatcc_refmap_clear(&refmap);
test(refmap.count == 0);
test(refmap.buckets == 0);
test_ok();
return 0;
}
#endif /* FLATCC_REFMAP_TEST */