A lot of files were missing copyright field in the header and
the Blender Foundation contributed to them in a sense of bug
fixing and general maintenance.
This change makes it explicit that those files are at least
partially copyrighted by the Blender Foundation.
Note that this does not make it so the Blender Foundation is
the only holder of the copyright in those files, and developers
who do not have a signed contract with the foundation still
hold the copyright as well.
Another aspect of this change is using SPDX format for the
header. We already used it for the license specification,
and now we state it for the copyright as well, following the
FAQ:
https://reuse.software/faq/
400 lines
9.1 KiB
C
400 lines
9.1 KiB
C
/* SPDX-FileCopyrightText: 2008 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bli
|
|
*
|
|
* A light stack-friendly hash library, it uses stack space for relatively small,
|
|
* fixed size hash tables but falls back to heap memory once the stack limits reached
|
|
* (#SMSTACKSIZE).
|
|
*
|
|
* based on a doubling hashing approach (non-chaining) which uses more buckets than entries
|
|
* stepping over buckets when two keys share the same hash so any key can find a free bucket.
|
|
*
|
|
* See: https://en.wikipedia.org/wiki/Double_hashing
|
|
*
|
|
* \warning This should _only_ be used for small hashes
|
|
* where allocating a hash every time is unacceptable.
|
|
* Otherwise #GHash should be used instead.
|
|
*
|
|
* #SmallHashEntry.key
|
|
* - `SMHASH_KEY_UNUSED` means the key in the cell has not been initialized.
|
|
*
|
|
* #SmallHashEntry.val
|
|
* - `SMHASH_CELL_UNUSED` means this cell is inside a key series.
|
|
* - `SMHASH_CELL_FREE` means this cell terminates a key series.
|
|
*
|
|
* Note that the values and keys are often pointers or index values,
|
|
* use the maximum values to avoid real pointers colliding with magic numbers.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "BLI_sys_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLI_smallhash.h"
|
|
|
|
#include "BLI_strict_flags.h"
|
|
|
|
#define SMHASH_KEY_UNUSED ((uintptr_t)(UINTPTR_MAX - 0))
|
|
#define SMHASH_CELL_FREE ((void *)(UINTPTR_MAX - 1))
|
|
#define SMHASH_CELL_UNUSED ((void *)(UINTPTR_MAX - 2))
|
|
|
|
/* typically this re-assigns 'h' */
|
|
#define SMHASH_NEXT(h, hoff) \
|
|
(CHECK_TYPE_INLINE(&(h), uint *), \
|
|
CHECK_TYPE_INLINE(&(hoff), uint *), \
|
|
((h) + (((hoff) = ((hoff)*2) + 1), (hoff))))
|
|
|
|
/* nothing uses BLI_smallhash_remove yet */
|
|
// #define USE_REMOVE
|
|
|
|
BLI_INLINE bool smallhash_val_is_used(const void *val)
|
|
{
|
|
#ifdef USE_REMOVE
|
|
return !ELEM(val, SMHASH_CELL_FREE, SMHASH_CELL_UNUSED);
|
|
#else
|
|
return (val != SMHASH_CELL_FREE);
|
|
#endif
|
|
}
|
|
|
|
extern const uint BLI_ghash_hash_sizes[];
|
|
#define hashsizes BLI_ghash_hash_sizes
|
|
|
|
BLI_INLINE uint smallhash_key(const uintptr_t key)
|
|
{
|
|
return (uint)key;
|
|
}
|
|
|
|
/**
|
|
* Check if the number of items in the smallhash is large enough to require more buckets.
|
|
*/
|
|
BLI_INLINE bool smallhash_test_expand_buckets(const uint nentries, const uint nbuckets)
|
|
{
|
|
/* (approx * 1.5) */
|
|
return (nentries + (nentries >> 1)) > nbuckets;
|
|
}
|
|
|
|
BLI_INLINE void smallhash_init_empty(SmallHash *sh)
|
|
{
|
|
uint i;
|
|
|
|
for (i = 0; i < sh->nbuckets; i++) {
|
|
sh->buckets[i].key = SMHASH_KEY_UNUSED;
|
|
sh->buckets[i].val = SMHASH_CELL_FREE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Increase initial bucket size to match a reserved amount.
|
|
*/
|
|
BLI_INLINE void smallhash_buckets_reserve(SmallHash *sh, const uint nentries_reserve)
|
|
{
|
|
while (smallhash_test_expand_buckets(nentries_reserve, sh->nbuckets)) {
|
|
sh->nbuckets = hashsizes[++sh->cursize];
|
|
}
|
|
}
|
|
|
|
BLI_INLINE SmallHashEntry *smallhash_lookup(const SmallHash *sh, const uintptr_t key)
|
|
{
|
|
SmallHashEntry *e;
|
|
uint h = smallhash_key(key);
|
|
uint hoff = 1;
|
|
|
|
BLI_assert(key != SMHASH_KEY_UNUSED);
|
|
|
|
/* NOTE: there are always more buckets than entries,
|
|
* so we know there will always be a free bucket if the key isn't found. */
|
|
for (e = &sh->buckets[h % sh->nbuckets]; e->val != SMHASH_CELL_FREE;
|
|
h = SMHASH_NEXT(h, hoff), e = &sh->buckets[h % sh->nbuckets])
|
|
{
|
|
if (e->key == key) {
|
|
/* should never happen because unused keys are zero'd */
|
|
BLI_assert(e->val != SMHASH_CELL_UNUSED);
|
|
return e;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
BLI_INLINE SmallHashEntry *smallhash_lookup_first_free(SmallHash *sh, const uintptr_t key)
|
|
{
|
|
SmallHashEntry *e;
|
|
uint h = smallhash_key(key);
|
|
uint hoff = 1;
|
|
|
|
for (e = &sh->buckets[h % sh->nbuckets]; smallhash_val_is_used(e->val);
|
|
h = SMHASH_NEXT(h, hoff), e = &sh->buckets[h % sh->nbuckets])
|
|
{
|
|
/* pass */
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
BLI_INLINE void smallhash_resize_buckets(SmallHash *sh, const uint nbuckets)
|
|
{
|
|
SmallHashEntry *buckets_old = sh->buckets;
|
|
const uint nbuckets_old = sh->nbuckets;
|
|
const bool was_alloc = (buckets_old != sh->buckets_stack);
|
|
uint i = 0;
|
|
|
|
BLI_assert(sh->nbuckets != nbuckets);
|
|
if (nbuckets <= SMSTACKSIZE) {
|
|
const size_t size = sizeof(*buckets_old) * nbuckets_old;
|
|
buckets_old = alloca(size);
|
|
memcpy(buckets_old, sh->buckets, size);
|
|
|
|
sh->buckets = sh->buckets_stack;
|
|
}
|
|
else {
|
|
sh->buckets = MEM_mallocN(sizeof(*sh->buckets) * nbuckets, __func__);
|
|
}
|
|
|
|
sh->nbuckets = nbuckets;
|
|
|
|
smallhash_init_empty(sh);
|
|
|
|
for (i = 0; i < nbuckets_old; i++) {
|
|
if (smallhash_val_is_used(buckets_old[i].val)) {
|
|
SmallHashEntry *e = smallhash_lookup_first_free(sh, buckets_old[i].key);
|
|
e->key = buckets_old[i].key;
|
|
e->val = buckets_old[i].val;
|
|
}
|
|
}
|
|
|
|
if (was_alloc) {
|
|
MEM_freeN(buckets_old);
|
|
}
|
|
}
|
|
|
|
void BLI_smallhash_init_ex(SmallHash *sh, const uint nentries_reserve)
|
|
{
|
|
/* assume 'sh' is uninitialized */
|
|
|
|
sh->nentries = 0;
|
|
sh->cursize = 2;
|
|
sh->nbuckets = hashsizes[sh->cursize];
|
|
|
|
sh->buckets = sh->buckets_stack;
|
|
|
|
if (nentries_reserve) {
|
|
smallhash_buckets_reserve(sh, nentries_reserve);
|
|
|
|
if (sh->nbuckets > SMSTACKSIZE) {
|
|
sh->buckets = MEM_mallocN(sizeof(*sh->buckets) * sh->nbuckets, __func__);
|
|
}
|
|
}
|
|
|
|
smallhash_init_empty(sh);
|
|
}
|
|
|
|
void BLI_smallhash_init(SmallHash *sh)
|
|
{
|
|
BLI_smallhash_init_ex(sh, 0);
|
|
}
|
|
|
|
void BLI_smallhash_release(SmallHash *sh)
|
|
{
|
|
if (sh->buckets != sh->buckets_stack) {
|
|
MEM_freeN(sh->buckets);
|
|
}
|
|
}
|
|
|
|
void BLI_smallhash_insert(SmallHash *sh, uintptr_t key, void *item)
|
|
{
|
|
SmallHashEntry *e;
|
|
|
|
BLI_assert(key != SMHASH_KEY_UNUSED);
|
|
BLI_assert(smallhash_val_is_used(item));
|
|
BLI_assert(BLI_smallhash_haskey(sh, key) == false);
|
|
|
|
if (UNLIKELY(smallhash_test_expand_buckets(++sh->nentries, sh->nbuckets))) {
|
|
smallhash_resize_buckets(sh, hashsizes[++sh->cursize]);
|
|
}
|
|
|
|
e = smallhash_lookup_first_free(sh, key);
|
|
e->key = key;
|
|
e->val = item;
|
|
}
|
|
|
|
bool BLI_smallhash_reinsert(SmallHash *sh, uintptr_t key, void *item)
|
|
{
|
|
SmallHashEntry *e = smallhash_lookup(sh, key);
|
|
if (e) {
|
|
e->val = item;
|
|
return false;
|
|
}
|
|
|
|
BLI_smallhash_insert(sh, key, item);
|
|
return true;
|
|
}
|
|
|
|
#ifdef USE_REMOVE
|
|
bool BLI_smallhash_remove(SmallHash *sh, uintptr_t key)
|
|
{
|
|
SmallHashEntry *e = smallhash_lookup(sh, key);
|
|
|
|
if (e) {
|
|
e->key = SMHASH_KEY_UNUSED;
|
|
e->val = SMHASH_CELL_UNUSED;
|
|
sh->nentries--;
|
|
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void *BLI_smallhash_lookup(const SmallHash *sh, uintptr_t key)
|
|
{
|
|
SmallHashEntry *e = smallhash_lookup(sh, key);
|
|
|
|
return e ? e->val : NULL;
|
|
}
|
|
|
|
void **BLI_smallhash_lookup_p(const SmallHash *sh, uintptr_t key)
|
|
{
|
|
SmallHashEntry *e = smallhash_lookup(sh, key);
|
|
|
|
return e ? &e->val : NULL;
|
|
}
|
|
|
|
bool BLI_smallhash_haskey(const SmallHash *sh, uintptr_t key)
|
|
{
|
|
SmallHashEntry *e = smallhash_lookup(sh, key);
|
|
|
|
return (e != NULL);
|
|
}
|
|
|
|
int BLI_smallhash_len(const SmallHash *sh)
|
|
{
|
|
return (int)sh->nentries;
|
|
}
|
|
|
|
BLI_INLINE SmallHashEntry *smallhash_iternext(SmallHashIter *iter, uintptr_t *key)
|
|
{
|
|
while (iter->i < iter->sh->nbuckets) {
|
|
if (smallhash_val_is_used(iter->sh->buckets[iter->i].val)) {
|
|
if (key) {
|
|
*key = iter->sh->buckets[iter->i].key;
|
|
}
|
|
|
|
return &iter->sh->buckets[iter->i++];
|
|
}
|
|
|
|
iter->i++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void *BLI_smallhash_iternext(SmallHashIter *iter, uintptr_t *key)
|
|
{
|
|
SmallHashEntry *e = smallhash_iternext(iter, key);
|
|
|
|
return e ? e->val : NULL;
|
|
}
|
|
|
|
void **BLI_smallhash_iternext_p(SmallHashIter *iter, uintptr_t *key)
|
|
{
|
|
SmallHashEntry *e = smallhash_iternext(iter, key);
|
|
|
|
return e ? &e->val : NULL;
|
|
}
|
|
|
|
void *BLI_smallhash_iternew(const SmallHash *sh, SmallHashIter *iter, uintptr_t *key)
|
|
{
|
|
iter->sh = sh;
|
|
iter->i = 0;
|
|
|
|
return BLI_smallhash_iternext(iter, key);
|
|
}
|
|
|
|
void **BLI_smallhash_iternew_p(const SmallHash *sh, SmallHashIter *iter, uintptr_t *key)
|
|
{
|
|
iter->sh = sh;
|
|
iter->i = 0;
|
|
|
|
return BLI_smallhash_iternext_p(iter, key);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Debugging & Introspection
|
|
* \{ */
|
|
|
|
/* NOTE(@ideasman42): useful for debugging but may not be intended for general use. */
|
|
#if 0
|
|
void BLI_smallhash_print(SmallHash *sh)
|
|
{
|
|
uint i, linecol = 79, c = 0;
|
|
|
|
printf("{");
|
|
for (i = 0; i < sh->nbuckets; i++) {
|
|
if (sh->buckets[i].val == SMHASH_CELL_UNUSED) {
|
|
printf("--u-");
|
|
}
|
|
else if (sh->buckets[i].val == SMHASH_CELL_FREE) {
|
|
printf("--f-");
|
|
}
|
|
else {
|
|
printf("%2x", (uint)sh->buckets[i].key);
|
|
}
|
|
|
|
if (i != sh->nbuckets - 1) {
|
|
printf(", ");
|
|
}
|
|
|
|
c += 6;
|
|
|
|
if (c >= linecol) {
|
|
printf("\n ");
|
|
c = 0;
|
|
}
|
|
}
|
|
|
|
fflush(stdout);
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
double BLI_smallhash_calc_quality(SmallHash *sh)
|
|
{
|
|
uint64_t sum = 0;
|
|
uint i;
|
|
|
|
if (sh->nentries == 0) {
|
|
return -1.0;
|
|
}
|
|
|
|
for (i = 0; i < sh->nbuckets; i++) {
|
|
if (sh->buckets[i].key != SMHASH_KEY_UNUSED) {
|
|
uint64_t count = 0;
|
|
SmallHashEntry *e, *e_final = &sh->buckets[i];
|
|
uint h = smallhash_key(e_final->key);
|
|
uint hoff = 1;
|
|
|
|
for (e = &sh->buckets[h % sh->nbuckets]; e != e_final;
|
|
h = SMHASH_NEXT(h, hoff), e = &sh->buckets[h % sh->nbuckets])
|
|
{
|
|
count += 1;
|
|
}
|
|
|
|
sum += count;
|
|
}
|
|
}
|
|
return ((double)(sh->nentries + sum) / (double)sh->nentries);
|
|
}
|
|
#endif
|
|
|
|
/** \} */
|