diff options
author | David Robillard <d@drobilla.net> | 2021-09-11 01:28:39 -0400 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2022-01-28 21:57:07 -0500 |
commit | 56cceb103dc633d6af957472945e792187a5dd4e (patch) | |
tree | 154a7b228f4f2bfda422d16feb9863d8a6d6992a | |
parent | eb804125430e3445e85c423b28e1c41346772ed0 (diff) | |
download | serd-56cceb103dc633d6af957472945e792187a5dd4e.tar.gz serd-56cceb103dc633d6af957472945e792187a5dd4e.tar.bz2 serd-56cceb103dc633d6af957472945e792187a5dd4e.zip |
Update zix and make it a proper subproject
-rw-r--r-- | .gitmodules | 4 | ||||
-rw-r--r-- | meson.build | 9 | ||||
-rw-r--r-- | src/describe.c | 10 | ||||
-rw-r--r-- | src/env.c | 1 | ||||
-rw-r--r-- | src/model.c | 15 | ||||
-rw-r--r-- | src/nodes.c | 2 | ||||
-rw-r--r-- | src/zix/btree.c | 975 | ||||
-rw-r--r-- | src/zix/btree.h | 213 | ||||
-rw-r--r-- | src/zix/common.h | 137 | ||||
-rw-r--r-- | src/zix/digest.c | 233 | ||||
-rw-r--r-- | src/zix/digest.h | 109 | ||||
-rw-r--r-- | src/zix/hash.c | 348 | ||||
-rw-r--r-- | src/zix/hash.h | 329 | ||||
m--------- | subprojects/zix | 0 | ||||
-rw-r--r-- | subprojects/zix.wrap | 5 |
15 files changed, 33 insertions, 2357 deletions
diff --git a/.gitmodules b/.gitmodules index 17929c01..3f314507 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "subprojects/exess"] path = subprojects/exess url = ../exess.git + +[submodule "subprojects/zix"] + path = subprojects/zix + url = ../zix.git diff --git a/meson.build b/meson.build index da14eb27..7d1d3c84 100644 --- a/meson.build +++ b/meson.build @@ -118,9 +118,6 @@ sources = [ 'src/uri.c', 'src/world.c', 'src/writer.c', - 'src/zix/btree.c', - 'src/zix/digest.c', - 'src/zix/hash.c', ] # System libraries @@ -170,6 +167,10 @@ library_args += ['-DSERD_MAJOR_VERSION=@0@'.format(serd_major_version), '-DSERD_MINOR_VERSION=@0@'.format(serd_minor_version), '-DSERD_MICRO_VERSION=@0@'.format(serd_micro_version)] +zix_dep = dependency('zix-0', + version: '>= 0.0.1', + fallback: ['zix', 'zix_dep']) + exess_dep = dependency('exess-0', version: '>= 0.0.1', fallback: ['exess', 'exess_dep']) @@ -181,7 +182,7 @@ libserd = build_target( version: meson.project_version(), include_directories: include_directories('include', 'src'), c_args: c_warnings + platform_args + library_args, - dependencies: [exess_dep, m_dep], + dependencies: [exess_dep, m_dep, zix_dep], gnu_symbol_visibility: 'hidden', install: true, target_type: library_type) diff --git a/src/describe.c b/src/describe.c index 4a087222..695217fd 100644 --- a/src/describe.c +++ b/src/describe.c @@ -295,10 +295,12 @@ serd_describe_range(const SerdCursor* const range, assert(sink); - SerdStatus st = SERD_SUCCESS; - SerdCursor copy = *range; - ZixHash* const list_subjects = zix_hash_new(identity, ptr_hash, ptr_equals); - DescribeContext ctx = {range->model, sink, list_subjects, flags}; + SerdStatus st = SERD_SUCCESS; + SerdCursor copy = *range; + ZixHash* const list_subjects = + zix_hash_new(NULL, identity, ptr_hash, ptr_equals); + + DescribeContext ctx = {range->model, sink, list_subjects, flags}; st = write_pretty_range(&ctx, 0, ©, NULL, (flags & SERD_NO_TYPE_FIRST)); @@ -44,6 +44,7 @@ SerdEnv* serd_env_new(SerdWorld* const world, const SerdStringView base_uri) { assert(world); + (void)world; SerdEnv* env = (SerdEnv*)calloc(1, sizeof(struct SerdEnvImpl)); diff --git a/src/model.c b/src/model.c index 0cf3b842..4e79fa77 100644 --- a/src/model.c +++ b/src/model.c @@ -73,7 +73,7 @@ serd_model_add_index(SerdModel* const model, const SerdStatementOrder order) const unsigned* const ordering = orderings[order]; const ZixComparator comparator = serd_model_index_comparator(model, order); - model->indices[order] = zix_btree_new(comparator, ordering); + model->indices[order] = zix_btree_new(NULL, comparator, ordering); ZixStatus zst = model->indices[order] ? ZIX_STATUS_SUCCESS : ZIX_STATUS_ERROR; @@ -103,7 +103,7 @@ serd_model_drop_index(SerdModel* const model, const SerdStatementOrder order) return SERD_BAD_CALL; } - zix_btree_free(model->indices[order], NULL); + zix_btree_free(model->indices[order], NULL, NULL); model->indices[order] = NULL; return SERD_SUCCESS; } @@ -246,6 +246,13 @@ serd_model_drop_statement(SerdModel* const model, serd_statement_free(statement); } +static void +destroy_tree_statement(void* ptr, const void* user_data) +{ + (void)user_data; + serd_statement_free((SerdStatement*)ptr); +} + void serd_model_free(SerdModel* const model) { @@ -255,11 +262,11 @@ serd_model_free(SerdModel* const model) // Free all statements (which are owned by the default index) ZixBTree* const default_index = model->indices[model->default_order]; - zix_btree_clear(default_index, (ZixDestroyFunc)serd_statement_free); + zix_btree_clear(default_index, destroy_tree_statement, NULL); // Free indices themselves for (unsigned i = 0u; i < N_STATEMENT_ORDERS; ++i) { - zix_btree_free(model->indices[i], NULL); + zix_btree_free(model->indices[i], NULL, NULL); } serd_nodes_free(model->nodes); diff --git a/src/nodes.c b/src/nodes.c index b821c004..529af688 100644 --- a/src/nodes.c +++ b/src/nodes.c @@ -202,7 +202,7 @@ serd_nodes_new(void) { SerdNodes* const nodes = (SerdNodes*)calloc(1, sizeof(SerdNodes)); - nodes->hash = zix_hash_new(nodes_key, nodes_hash, nodes_equal); + nodes->hash = zix_hash_new(NULL, nodes_key, nodes_hash, nodes_equal); return nodes; } diff --git a/src/zix/btree.c b/src/zix/btree.c deleted file mode 100644 index 56dfa98c..00000000 --- a/src/zix/btree.c +++ /dev/null @@ -1,975 +0,0 @@ -/* - Copyright 2011-2021 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include "zix/btree.h" - -#include <assert.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> - -// #define ZIX_BTREE_SORTED_CHECK 1 - -// Define ZixShort as an integer type half the size of a pointer -#if UINTPTR_MAX >= UINT32_MAX -typedef uint32_t ZixShort; -#else -typedef uint16_t ZixShort; -#endif - -#ifndef ZIX_BTREE_PAGE_SIZE -# define ZIX_BTREE_PAGE_SIZE 4096u -#endif - -#define ZIX_BTREE_NODE_SPACE (ZIX_BTREE_PAGE_SIZE - 2u * sizeof(ZixShort)) -#define ZIX_BTREE_LEAF_VALS ((ZIX_BTREE_NODE_SPACE / sizeof(void*)) - 1u) -#define ZIX_BTREE_INODE_VALS (ZIX_BTREE_LEAF_VALS / 2u) - -struct ZixBTreeImpl { - ZixBTreeNode* root; - ZixComparator cmp; - const void* cmp_data; - size_t size; -}; - -struct ZixBTreeNodeImpl { - ZixShort is_leaf; - ZixShort n_vals; - - union { - struct { - void* vals[ZIX_BTREE_LEAF_VALS]; - } leaf; - - struct { - void* vals[ZIX_BTREE_INODE_VALS]; - ZixBTreeNode* children[ZIX_BTREE_INODE_VALS + 1u]; - } inode; - } data; -}; - -#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112l) || \ - (defined(__cplusplus) && __cplusplus >= 201103L) -static_assert(sizeof(ZixBTreeNode) == ZIX_BTREE_PAGE_SIZE, ""); -#endif - -static ZixBTreeNode* -zix_btree_node_new(const bool leaf) -{ -#if !((defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112l) || \ - (defined(__cplusplus) && __cplusplus >= 201103L)) - assert(sizeof(ZixBTreeNode) <= ZIX_BTREE_PAGE_SIZE); - assert(sizeof(ZixBTreeNode) >= - ZIX_BTREE_PAGE_SIZE - 2u * sizeof(ZixBTreeNode*)); -#endif - - ZixBTreeNode* const node = (ZixBTreeNode*)malloc(sizeof(ZixBTreeNode)); - if (node) { - node->is_leaf = leaf; - node->n_vals = 0u; - } - - return node; -} - -ZIX_PURE_FUNC -static ZixBTreeNode* -zix_btree_child(const ZixBTreeNode* const node, const unsigned i) -{ - assert(!node->is_leaf); - assert(i <= ZIX_BTREE_INODE_VALS); - return node->data.inode.children[i]; -} - -ZixBTree* -zix_btree_new(const ZixComparator cmp, const void* const cmp_data) -{ - ZixBTree* const t = (ZixBTree*)malloc(sizeof(ZixBTree)); - if (!t) { - return NULL; - } - - if (!(t->root = zix_btree_node_new(true))) { - free(t); - return NULL; - } - - t->cmp = cmp; - t->cmp_data = cmp_data; - t->size = 0; - - return t; -} - -static void -zix_btree_free_children(ZixBTree* const t, - ZixBTreeNode* const n, - ZixDestroyFunc destroy) -{ - if (!n->is_leaf) { - for (ZixShort i = 0; i < n->n_vals + 1u; ++i) { - zix_btree_free_children(t, zix_btree_child(n, i), destroy); - free(zix_btree_child(n, i)); - } - } - - if (destroy) { - if (n->is_leaf) { - for (ZixShort i = 0u; i < n->n_vals; ++i) { - destroy(n->data.leaf.vals[i]); - } - } else { - for (ZixShort i = 0u; i < n->n_vals; ++i) { - destroy(n->data.inode.vals[i]); - } - } - } -} - -void -zix_btree_free(ZixBTree* const t, ZixDestroyFunc destroy) -{ - if (t) { - zix_btree_clear(t, destroy); - free(t->root); - free(t); - } -} - -void -zix_btree_clear(ZixBTree* const t, ZixDestroyFunc destroy) -{ - zix_btree_free_children(t, t->root, destroy); - - memset(t->root, 0, sizeof(ZixBTreeNode)); - t->root->is_leaf = true; - t->size = 0u; -} - -size_t -zix_btree_size(const ZixBTree* const t) -{ - return t->size; -} - -static ZixShort -zix_btree_max_vals(const ZixBTreeNode* const node) -{ - return node->is_leaf ? ZIX_BTREE_LEAF_VALS : ZIX_BTREE_INODE_VALS; -} - -static ZixShort -zix_btree_min_vals(const ZixBTreeNode* const node) -{ - return (ZixShort)(((zix_btree_max_vals(node) + 1u) / 2u) - 1u); -} - -/// Shift pointers in `array` of length `n` right starting at `i` -static void -zix_btree_ainsert(void** const array, - const unsigned n, - const unsigned i, - void* const e) -{ - memmove(array + i + 1, array + i, (n - i) * sizeof(e)); - array[i] = e; -} - -/// Erase element `i` in `array` of length `n` and return erased element -static void* -zix_btree_aerase(void** const array, const unsigned n, const unsigned i) -{ - void* const ret = array[i]; - memmove(array + i, array + i + 1, (n - i) * sizeof(ret)); - return ret; -} - -/// Split lhs, the i'th child of `n`, into two nodes -static ZixBTreeNode* -zix_btree_split_child(ZixBTreeNode* const n, - const unsigned i, - ZixBTreeNode* const lhs) -{ - assert(lhs->n_vals == zix_btree_max_vals(lhs)); - assert(n->n_vals < ZIX_BTREE_INODE_VALS); - assert(i < n->n_vals + 1U); - assert(zix_btree_child(n, i) == lhs); - - const ZixShort max_n_vals = zix_btree_max_vals(lhs); - ZixBTreeNode* rhs = zix_btree_node_new(lhs->is_leaf); - if (!rhs) { - return NULL; - } - - // LHS and RHS get roughly half, less the middle value which moves up - lhs->n_vals = max_n_vals / 2U; - rhs->n_vals = (ZixShort)(max_n_vals - lhs->n_vals - 1); - - if (lhs->is_leaf) { - // Copy large half from LHS to new RHS node - memcpy(rhs->data.leaf.vals, - lhs->data.leaf.vals + lhs->n_vals + 1, - rhs->n_vals * sizeof(void*)); - - // Move middle value up to parent - zix_btree_ainsert( - n->data.inode.vals, n->n_vals, i, lhs->data.leaf.vals[lhs->n_vals]); - } else { - // Copy large half from LHS to new RHS node - memcpy(rhs->data.inode.vals, - lhs->data.inode.vals + lhs->n_vals + 1, - rhs->n_vals * sizeof(void*)); - memcpy(rhs->data.inode.children, - lhs->data.inode.children + lhs->n_vals + 1, - (rhs->n_vals + 1U) * sizeof(ZixBTreeNode*)); - - // Move middle value up to parent - zix_btree_ainsert( - n->data.inode.vals, n->n_vals, i, lhs->data.inode.vals[lhs->n_vals]); - } - - // Insert new RHS node in parent at position i - zix_btree_ainsert((void**)n->data.inode.children, ++n->n_vals, i + 1U, rhs); - - return rhs; -} - -#ifdef ZIX_BTREE_SORTED_CHECK -/// Check that `n` is sorted with respect to search key `e` -static bool -zix_btree_node_is_sorted_with_respect_to(const ZixComparator compare, - const void* const compare_user_data, - void* const* const values, - const unsigned n_values, - const void* const key) -{ - if (n_values <= 1u) { - return true; - } - - int cmp = compare(values[0], key, compare_user_data); - for (unsigned i = 1u; i < n_values; ++i) { - const int next_cmp = compare(values[i], key, compare_user_data); - if ((cmp >= 0 && next_cmp < 0) || (cmp > 0 && next_cmp <= 0)) { - return false; - } - - cmp = next_cmp; - } - - return true; -} -#endif - -static unsigned -zix_btree_find_value(const ZixComparator compare, - const void* const compare_user_data, - void* const* const values, - const unsigned n_values, - const void* const key, - bool* const equal) -{ - unsigned first = 0u; - unsigned count = n_values; - - while (count > 0u) { - const unsigned half = count >> 1u; - const unsigned i = first + half; - void* const value = values[i]; - const int cmp = compare(value, key, compare_user_data); - - if (!cmp) { - *equal = true; - return i; - } - - if (cmp < 0) { - first += half + 1u; - count -= half + 1u; - } else { - count = half; - } - } - - assert(first == n_values || compare(values[first], key, compare_user_data)); - *equal = false; - return first; -} - -static unsigned -zix_btree_find_pattern(const ZixComparator compare_key, - const void* const compare_key_user_data, - void* const* const values, - const unsigned n_values, - const void* const key, - bool* const equal) -{ -#ifdef ZIX_BTREE_SORTED_CHECK - assert(zix_btree_node_is_sorted_with_respect_to( - compare_key, compare_key_user_data, values, n_values, key)); -#endif - - unsigned first = 0u; - unsigned count = n_values; - - while (count > 0u) { - const unsigned half = count >> 1u; - const unsigned i = first + half; - void* const value = values[i]; - const int cmp = compare_key(value, key, compare_key_user_data); - - if (cmp == 0) { - // Found a match, but keep searching for the leftmost one - *equal = true; - count = half; - - } else if (cmp < 0) { - // Search right half - first += half + 1u; - count -= half + 1u; - - } else { - // Search left half - count = half; - } - } - - assert(!*equal || - (compare_key(values[first], key, compare_key_user_data) == 0 && - (first == 0u || - (compare_key(values[first - 1u], key, compare_key_user_data) < 0)))); - - return first; -} - -/// Convenience wrapper to find a value in an internal node -static unsigned -zix_btree_inode_find(const ZixBTree* const t, - const ZixBTreeNode* const n, - const void* const e, - bool* const equal) -{ - assert(!n->is_leaf); - - return zix_btree_find_value( - t->cmp, t->cmp_data, n->data.inode.vals, n->n_vals, e, equal); -} - -/// Convenience wrapper to find a value in a leaf node -static unsigned -zix_btree_leaf_find(const ZixBTree* const t, - const ZixBTreeNode* const n, - const void* const e, - bool* const equal) -{ - assert(n->is_leaf); - - return zix_btree_find_value( - t->cmp, t->cmp_data, n->data.leaf.vals, n->n_vals, e, equal); -} - -static inline bool -zix_btree_can_remove_from(const ZixBTreeNode* const n) -{ - assert(n->n_vals >= zix_btree_min_vals(n)); - return n->n_vals > zix_btree_min_vals(n); -} - -static inline bool -zix_btree_is_full(const ZixBTreeNode* const n) -{ - assert(n->n_vals <= zix_btree_max_vals(n)); - return n->n_vals == zix_btree_max_vals(n); -} - -static ZixStatus -zix_btree_grow_up(ZixBTree* const t) -{ - ZixBTreeNode* const new_root = zix_btree_node_new(false); - if (!new_root) { - return ZIX_STATUS_NO_MEM; - } - - // Set old root as the only child of the new root - new_root->data.inode.children[0] = t->root; - - // Split the old root to get two balanced siblings - zix_btree_split_child(new_root, 0, t->root); - t->root = new_root; - - return ZIX_STATUS_SUCCESS; -} - -ZixStatus -zix_btree_insert(ZixBTree* const t, void* const e) -{ - ZixStatus st = ZIX_STATUS_SUCCESS; - - // Grow up if necessary to ensure the root is not full - if (zix_btree_is_full(t->root)) { - if ((st = zix_btree_grow_up(t))) { - return st; - } - } - - // Walk down from the root until we reach a suitable leaf - ZixBTreeNode* node = t->root; - while (!node->is_leaf) { - // Search for the value in this node - bool equal = false; - const unsigned i = zix_btree_inode_find(t, node, e, &equal); - if (equal) { - return ZIX_STATUS_EXISTS; - } - - // Value not in this node, but may be in the ith child - ZixBTreeNode* child = node->data.inode.children[i]; - if (zix_btree_is_full(child)) { - // The child is full, split it before continuing - ZixBTreeNode* const rhs = zix_btree_split_child(node, i, child); - if (!rhs) { - return ZIX_STATUS_NO_MEM; - } - - // Compare with new split value to determine which side to use - const int cmp = t->cmp(node->data.inode.vals[i], e, t->cmp_data); - if (cmp < 0) { - child = rhs; // Split value is less than the new value, move right - } else if (cmp == 0) { - return ZIX_STATUS_EXISTS; // Split value is exactly the value to insert - } - } - - // Descend to child node and continue - node = child; - } - - // Search for the value in the leaf - bool equal = false; - const unsigned i = zix_btree_leaf_find(t, node, e, &equal); - if (equal) { - return ZIX_STATUS_EXISTS; - } - - // The value is not in the tree, insert into the leaf - zix_btree_ainsert(node->data.leaf.vals, node->n_vals++, i, e); - ++t->size; - return ZIX_STATUS_SUCCESS; -} - -static void -zix_btree_iter_set_frame(ZixBTreeIter* const ti, - ZixBTreeNode* const n, - const ZixShort i) -{ - ti->nodes[ti->level] = n; - ti->indexes[ti->level] = (uint16_t)i; -} - -static void -zix_btree_iter_push(ZixBTreeIter* const ti, - ZixBTreeNode* const n, - const ZixShort i) -{ - assert(ti->level < ZIX_BTREE_MAX_HEIGHT); - ++ti->level; - ti->nodes[ti->level] = n; - ti->indexes[ti->level] = (uint16_t)i; -} - -static void -zix_btree_iter_pop(ZixBTreeIter* const ti) -{ - assert(ti->level > 0u); - ti->nodes[ti->level] = NULL; - ti->indexes[ti->level] = 0u; - --ti->level; -} - -/// Enlarge left child by stealing a value from its right sibling -static ZixBTreeNode* -zix_btree_rotate_left(ZixBTreeNode* const parent, const unsigned i) -{ - ZixBTreeNode* const lhs = zix_btree_child(parent, i); - ZixBTreeNode* const rhs = zix_btree_child(parent, i + 1); - - assert(lhs->is_leaf == rhs->is_leaf); - - if (lhs->is_leaf) { - // Move parent value to end of LHS - lhs->data.leaf.vals[lhs->n_vals++] = parent->data.inode.vals[i]; - - // Move first value in RHS to parent - parent->data.inode.vals[i] = - zix_btree_aerase(rhs->data.leaf.vals, rhs->n_vals, 0); - } else { - // Move parent value to end of LHS - lhs->data.inode.vals[lhs->n_vals++] = parent->data.inode.vals[i]; - - // Move first value in RHS to parent - parent->data.inode.vals[i] = - zix_btree_aerase(rhs->data.inode.vals, rhs->n_vals, 0); - - // Move first child pointer from RHS to end of LHS - lhs->data.inode.children[lhs->n_vals] = (ZixBTreeNode*)zix_btree_aerase( - (void**)rhs->data.inode.children, rhs->n_vals, 0); - } - - --rhs->n_vals; - - return lhs; -} - -/// Enlarge a child by stealing a value from its left sibling -static ZixBTreeNode* -zix_btree_rotate_right(ZixBTreeNode* const parent, const unsigned i) -{ - ZixBTreeNode* const lhs = zix_btree_child(parent, i - 1); - ZixBTreeNode* const rhs = zix_btree_child(parent, i); - - assert(lhs->is_leaf == rhs->is_leaf); - - if (lhs->is_leaf) { - // Prepend parent value to RHS - zix_btree_ainsert( - rhs->data.leaf.vals, rhs->n_vals++, 0, parent->data.inode.vals[i - 1]); - - // Move last value from LHS to parent - parent->data.inode.vals[i - 1] = lhs->data.leaf.vals[--lhs->n_vals]; - } else { - // Prepend parent value to RHS - zix_btree_ainsert( - rhs->data.inode.vals, rhs->n_vals++, 0, parent->data.inode.vals[i - 1]); - - // Move last child pointer from LHS and prepend to RHS - zix_btree_ainsert((void**)rhs->data.inode.children, - rhs->n_vals, - 0, - lhs->data.inode.children[lhs->n_vals]); - - // Move last value from LHS to parent - parent->data.inode.vals[i - 1] = lhs->data.inode.vals[--lhs->n_vals]; - } - - return rhs; -} - -/// Move n[i] down, merge the left and right child, return the merged node -static ZixBTreeNode* -zix_btree_merge(ZixBTree* const t, ZixBTreeNode* const n, const unsigned i) -{ - ZixBTreeNode* const lhs = zix_btree_child(n, i); - ZixBTreeNode* const rhs = zix_btree_child(n, i + 1); - - assert(lhs->is_leaf == rhs->is_leaf); - assert(lhs->n_vals + rhs->n_vals < zix_btree_max_vals(lhs)); - - // Move parent value to end of LHS - if (lhs->is_leaf) { - lhs->data.leaf.vals[lhs->n_vals++] = - zix_btree_aerase(n->data.inode.vals, n->n_vals, i); - } else { - lhs->data.inode.vals[lhs->n_vals++] = - zix_btree_aerase(n->data.inode.vals, n->n_vals, i); - } - - // Erase corresponding child pointer (to RHS) in parent - zix_btree_aerase((void**)n->data.inode.children, n->n_vals, i + 1U); - - // Add everything from RHS to end of LHS - if (lhs->is_leaf) { - memcpy(lhs->data.leaf.vals + lhs->n_vals, - rhs->data.leaf.vals, - rhs->n_vals * sizeof(void*)); - } else { - memcpy(lhs->data.inode.vals + lhs->n_vals, - rhs->data.inode.vals, - rhs->n_vals * sizeof(void*)); - memcpy(lhs->data.inode.children + lhs->n_vals, - rhs->data.inode.children, - (rhs->n_vals + 1U) * sizeof(void*)); - } - - lhs->n_vals = (ZixShort)(lhs->n_vals + rhs->n_vals); - - if (--n->n_vals == 0) { - // Root is now empty, replace it with its only child - assert(n == t->root); - t->root = lhs; - free(n); - } - - free(rhs); - return lhs; -} - -/// Remove and return the min value from the subtree rooted at `n` -static void* -zix_btree_remove_min(ZixBTree* const t, ZixBTreeNode* n) -{ - assert(zix_btree_can_remove_from(n)); - - while (!n->is_leaf) { - ZixBTreeNode* const* const children = n->data.inode.children; - - n = zix_btree_can_remove_from(children[0]) ? children[0] - : zix_btree_can_remove_from(children[1]) ? zix_btree_rotate_left(n, 0) - : zix_btree_merge(t, n, 0); - } - - return zix_btree_aerase(n->data.leaf.vals, --n->n_vals, 0); -} - -/// Remove and return the max value from the subtree rooted at `n` -static void* -zix_btree_remove_max(ZixBTree* const t, ZixBTreeNode* n) -{ - assert(zix_btree_can_remove_from(n)); - - while (!n->is_leaf) { - ZixBTreeNode* const* const children = n->data.inode.children; - - const unsigned y = n->n_vals - 1u; - const unsigned z = n->n_vals; - - n = zix_btree_can_remove_from(children[z]) ? children[z] - : zix_btree_can_remove_from(children[y]) ? zix_btree_rotate_right(n, z) - : zix_btree_merge(t, n, y); - } - - return n->data.leaf.vals[--n->n_vals]; -} - -static ZixBTreeNode* -zix_btree_fatten_child(ZixBTree* const t, ZixBTreeIter* const iter) -{ - ZixBTreeNode* const n = iter->nodes[iter->level]; - const ZixShort i = iter->indexes[iter->level]; - - assert(!n->is_leaf); - ZixBTreeNode* const* const children = n->data.inode.children; - - if (i > 0 && zix_btree_can_remove_from(children[i - 1u])) { - return zix_btree_rotate_right(n, i); // Steal a key from left sibling - } - - if (i < n->n_vals && zix_btree_can_remove_from(children[i + 1u])) { - return zix_btree_rotate_left(n, i); // Steal a key from right sibling - } - - // Both child's siblings are minimal, merge them - - if (i == n->n_vals) { - --iter->indexes[iter->level]; - return zix_btree_merge(t, n, i - 1u); // Merge last two children - } - - return zix_btree_merge(t, n, i); // Merge left and right siblings -} - -/// Replace the ith value in `n` with one from a child if possible -static ZixStatus -zix_btree_replace_value(ZixBTree* const t, - ZixBTreeNode* const n, - const unsigned i, - void** const out) -{ - ZixBTreeNode* const lhs = zix_btree_child(n, i); - ZixBTreeNode* const rhs = zix_btree_child(n, i + 1); - if (!zix_btree_can_remove_from(lhs) && !zix_btree_can_remove_from(rhs)) { - return ZIX_STATUS_NOT_FOUND; - } - - // Stash the value for the caller before it is replaced - *out = n->data.inode.vals[i]; - - n->data.inode.vals[i] = - // Left child has more values, steal its largest - (lhs->n_vals > rhs->n_vals) ? zix_btree_remove_max(t, lhs) - - // Right child has more values, steal its smallest - : (rhs->n_vals > lhs->n_vals) ? zix_btree_remove_min(t, rhs) - - // Children are balanced, use index parity as a low-bias tie breaker - : (i & 1u) ? zix_btree_remove_max(t, lhs) - : zix_btree_remove_min(t, rhs); - - return ZIX_STATUS_SUCCESS; -} - -ZixStatus -zix_btree_remove(ZixBTree* const t, - const void* const e, - void** const out, - ZixBTreeIter* const next) -{ - ZixBTreeNode* n = t->root; - ZixBTreeIter* ti = next; - ZixStatus st = ZIX_STATUS_SUCCESS; - - *ti = zix_btree_end_iter; - - /* To remove in a single walk down, the tree is adjusted along the way so - that the current node always has at least one more value than the - minimum. This ensures that there is always room to remove, without - having to merge nodes again on a traversal back up. */ - - if (!n->is_leaf && n->n_vals == 1u && - !zix_btree_can_remove_from(n->data.inode.children[0u]) && - !zix_btree_can_remove_from(n->data.inode.children[1u])) { - // Root has only two children, both minimal, merge them into a new root - n = zix_btree_merge(t, n, 0); - } - - while (!n->is_leaf) { - assert(n == t->root || zix_btree_can_remove_from(n)); - - // Search for the value in the current node and update the iterator - bool equal = false; - const unsigned i = zix_btree_inode_find(t, n, e, &equal); - - zix_btree_iter_set_frame(ti, n, i); - - if (equal) { - // Found in internal node - if (!(st = zix_btree_replace_value(t, n, i, out))) { - // Replaced hole with a value from a direct child - --t->size; - return st; - } - - // Both preceding and succeeding child are minimal, merge and continue - n = zix_btree_merge(t, n, i); - - } else { - // Not found in internal node, is in the ith child if anywhere - n = zix_btree_can_remove_from(zix_btree_child(n, i)) - ? zix_btree_child(n, i) - : zix_btree_fatten_child(t, ti); - } - - ++ti->level; - } - - // We're at the leaf the value may be in, search for the value in it - bool equal = false; - const unsigned i = zix_btree_leaf_find(t, n, e, &equal); - - if (!equal) { // Not found in tree - *ti = zix_btree_end_iter; - return ZIX_STATUS_NOT_FOUND; - } - - // Erase from leaf node - *out = zix_btree_aerase(n->data.leaf.vals, --n->n_vals, i); - - // Update next iterator - if (n->n_vals == 0u) { - // Removed the last element in the tree - assert(n == t->root); - assert(t->size == 1u); - *ti = zix_btree_end_iter; - } else if (i == n->n_vals) { - // Removed the largest element in this leaf, increment to the next - zix_btree_iter_set_frame(ti, n, i - 1); - zix_btree_iter_increment(ti); - } else { - zix_btree_iter_set_frame(ti, n, i); - } - - --t->size; - return ZIX_STATUS_SUCCESS; -} - -ZixStatus -zix_btree_find(const ZixBTree* const t, - const void* const e, - ZixBTreeIter* const ti) -{ - ZixBTreeNode* n = t->root; - - *ti = zix_btree_end_iter; - - while (!n->is_leaf) { - bool equal = false; - const unsigned i = zix_btree_inode_find(t, n, e, &equal); - - zix_btree_iter_set_frame(ti, n, i); - - if (equal) { - return ZIX_STATUS_SUCCESS; - } - - ++ti->level; - n = zix_btree_child(n, i); - } - - bool equal = false; - const unsigned i = zix_btree_leaf_find(t, n, e, &equal); - if (equal) { - zix_btree_iter_set_frame(ti, n, i); - return ZIX_STATUS_SUCCESS; - } - - *ti = zix_btree_end_iter; - return ZIX_STATUS_NOT_FOUND; -} - -ZixStatus -zix_btree_lower_bound(const ZixBTree* const t, - const ZixComparator compare, - const void* const compare_user_data, - const void* const key, - ZixBTreeIter* const ti) -{ - *ti = zix_btree_end_iter; - - ZixBTreeNode* n = t->root; // Current node - uint16_t found_level = 0u; // Lowest level a match was found at - bool found = false; // True if a match was ever found - - // Search down until we reach a leaf - while (!n->is_leaf) { - bool equal = false; - const unsigned i = zix_btree_find_pattern( - compare, compare_user_data, n->data.inode.vals, n->n_vals, key, &equal); - - zix_btree_iter_set_frame(ti, n, i); - if (equal) { - found_level = ti->level; - found = true; - } - - ++ti->level; - n = zix_btree_child(n, i); - } - - bool equal = false; - const unsigned i = zix_btree_find_pattern( - compare, compare_user_data, n->data.leaf.vals, n->n_vals, key, &equal); - - zix_btree_iter_set_frame(ti, n, i); - if (equal) { - return ZIX_STATUS_SUCCESS; - } - - if (ti->indexes[ti->level] == ti->nodes[ti->level]->n_vals) { - if (found) { - // Found on a previous level but went too far - ti->level = found_level; - } else { - // Reached end (key is greater than everything in tree) - *ti = zix_btree_end_iter; - } - } - - return ZIX_STATUS_SUCCESS; -} - -void* -zix_btree_get(const ZixBTreeIter ti) -{ - const ZixBTreeNode* const node = ti.nodes[ti.level]; - const unsigned index = ti.indexes[ti.level]; - - assert(node); - assert(index < node->n_vals); - - return node->is_leaf ? node->data.leaf.vals[index] - : node->data.inode.vals[index]; -} - -ZixBTreeIter -zix_btree_begin(const ZixBTree* const t) -{ - ZixBTreeIter iter = zix_btree_end_iter; - - if (t->size > 0u) { - ZixBTreeNode* n = t->root; - zix_btree_iter_set_frame(&iter, n, 0u); - - while (!n->is_leaf) { - n = zix_btree_child(n, 0); - zix_btree_iter_push(&iter, n, 0u); - } - } - - return iter; -} - -ZixBTreeIter -zix_btree_end(const ZixBTree* const t) -{ - (void)t; - - return zix_btree_end_iter; -} - -bool -zix_btree_iter_equals(const ZixBTreeIter lhs, const ZixBTreeIter rhs) -{ - const size_t indexes_size = (lhs.level + 1u) * sizeof(uint16_t); - - return (lhs.level == rhs.level) && (lhs.nodes[0] == rhs.nodes[0]) && - (!lhs.nodes[0] || !memcmp(lhs.indexes, rhs.indexes, indexes_size)); -} - -ZixStatus -zix_btree_iter_increment(ZixBTreeIter* const i) -{ - assert(!zix_btree_iter_is_end(*i)); - - // Move to the next value in the current node - const uint16_t index = ++i->indexes[i->level]; - - if (i->nodes[i->level]->is_leaf) { - // Leaf, move up if necessary until we're not at the end of the node - while (i->indexes[i->level] >= i->nodes[i->level]->n_vals) { - if (i->level == 0) { - // End of root, end of tree - i->nodes[0] = NULL; - return ZIX_STATUS_REACHED_END; - } - - // At end of internal node, move up - zix_btree_iter_pop(i); - } - - } else { - // Internal node, move down to next child - const ZixBTreeNode* const node = i->nodes[i->level]; - ZixBTreeNode* const child = node->data.inode.children[index]; - - zix_btree_iter_push(i, child, 0u); - - // Move down and left until we hit a leaf - while (!i->nodes[i->level]->is_leaf) { - zix_btree_iter_push(i, i->nodes[i->level]->data.inode.children[0], 0u); - } - } - - return ZIX_STATUS_SUCCESS; -} - -ZixBTreeIter -zix_btree_iter_next(const ZixBTreeIter iter) -{ - ZixBTreeIter next = iter; - - zix_btree_iter_increment(&next); - - return next; -} diff --git a/src/zix/btree.h b/src/zix/btree.h deleted file mode 100644 index 3576789c..00000000 --- a/src/zix/btree.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - Copyright 2011-2020 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef ZIX_BTREE_H -#define ZIX_BTREE_H - -#include "zix/common.h" - -#include <stdbool.h> -#include <stddef.h> -#include <stdint.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/** - @addtogroup zix - @{ - @name BTree - @{ -*/ - -/** - The maximum height of a ZixBTree. - - This is exposed because it determines the size of iterators, which are - statically sized so they can used on the stack. The usual degree (or - "fanout") of a B-Tree is high enough that a relatively short tree can - contain many elements. With the default page size of 4 KiB, the default - height of 6 is enough to store trillions. -*/ -#ifndef ZIX_BTREE_MAX_HEIGHT -# define ZIX_BTREE_MAX_HEIGHT 6u -#endif - -/// A B-Tree -typedef struct ZixBTreeImpl ZixBTree; - -/// A B-Tree node (opaque) -typedef struct ZixBTreeNodeImpl ZixBTreeNode; - -/** - An iterator over a B-Tree. - - Note that modifying the tree invalidates all iterators. - - The contents of this type are considered an implementation detail and should - not be used directly by clients. They are nevertheless exposed here so that - iterators can be allocated on the stack. -*/ -typedef struct { - ZixBTreeNode* nodes[ZIX_BTREE_MAX_HEIGHT]; ///< Parallel node pointer stack - uint16_t indexes[ZIX_BTREE_MAX_HEIGHT]; ///< Parallel child index stack - uint16_t level; ///< Current level in stack -} ZixBTreeIter; - -/// A static end iterator for convenience -static const ZixBTreeIter zix_btree_end_iter = { - {NULL, NULL, NULL, NULL, NULL, NULL}, - {0u, 0u, 0u, 0u, 0u, 0u}, - 0u}; - -/** - Create a new (empty) B-Tree. - - The given comparator must be a total ordering and is used to internally - organize the tree and look for values exactly. - - Searching can be done with a custom comparator that supports wildcards, see - zix_btree_lower_bound() for details. -*/ -ZIX_API -ZixBTree* -zix_btree_new(ZixComparator cmp, const void* cmp_data); - -/** - Free `t` and all the nodes it contains. - - @param destroy Function to call once for every value in the tree. This can - be used to free values if they are dynamically allocated. -*/ -ZIX_API -void -zix_btree_free(ZixBTree* t, ZixDestroyFunc destroy); - -/** - Clear everything from `t`, leaving it empty. - - @param destroy Function called exactly once for every value in the tree, - just before that value is removed from the tree. -*/ -ZIX_API -void -zix_btree_clear(ZixBTree* t, ZixDestroyFunc destroy); - -/// Return the number of elements in `t` -ZIX_PURE_API -size_t -zix_btree_size(const ZixBTree* t); - -/// Insert the element `e` into `t` -ZIX_API -ZixStatus -zix_btree_insert(ZixBTree* t, void* e); - -/** - Remove the value `e` from `t`. - - @param t Tree to remove from. - - @param e Value to remove. - - @param out Set to point to the removed pointer (which may not equal `e`). - - @param next On successful return, set to point at the value that immediately - followed `e`. -*/ -ZIX_API -ZixStatus -zix_btree_remove(ZixBTree* t, const void* e, void** out, ZixBTreeIter* next); - -/** - Set `ti` to an element exactly equal to `e` in `t`. - - If no such item exists, `ti` is set to the end. -*/ -ZIX_API -ZixStatus -zix_btree_find(const ZixBTree* t, const void* e, ZixBTreeIter* ti); - -/** - Set `ti` to the smallest element in `t` that is not less than `e`. - - The given comparator must be compatible with the tree comparator, that is, - any two values must have the same ordering according to both. Within this - constraint, it may implement fuzzier searching by handling special search - key values, for example with wildcards. - - If the search key `e` compares equal to many values in the tree, then `ti` - will be set to the least such element. - - The comparator is always called with an actual value in the tree as the - first argument, and `key` as the second argument. -*/ -ZIX_API -ZixStatus -zix_btree_lower_bound(const ZixBTree* t, - ZixComparator compare, - const void* compare_user_data, - const void* key, - ZixBTreeIter* ti); - -/// Return the data at the given position in the tree -ZIX_PURE_API -void* -zix_btree_get(ZixBTreeIter ti); - -/// Return an iterator to the first (smallest) element in `t` -ZIX_PURE_API -ZixBTreeIter -zix_btree_begin(const ZixBTree* t); - -/// Return an iterator to the end of `t` (one past the last element) -ZIX_CONST_API -ZixBTreeIter -zix_btree_end(const ZixBTree* t); - -/// Return true iff `lhs` is equal to `rhs` -ZIX_PURE_API -bool -zix_btree_iter_equals(ZixBTreeIter lhs, ZixBTreeIter rhs); - -/// Return true iff `i` is an iterator at the end of a tree -static inline bool -zix_btree_iter_is_end(const ZixBTreeIter i) -{ - return i.level == 0 && !i.nodes[0]; -} - -/// Increment `i` to point to the next element in the tree -ZIX_API -ZixStatus -zix_btree_iter_increment(ZixBTreeIter* i); - -/// Return an iterator one past `iter` -ZIX_PURE_API -ZixBTreeIter -zix_btree_iter_next(ZixBTreeIter iter); - -/** - @} - @} -*/ - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* ZIX_BTREE_H */ diff --git a/src/zix/common.h b/src/zix/common.h deleted file mode 100644 index 02f00b45..00000000 --- a/src/zix/common.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - Copyright 2016-2020 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef ZIX_COMMON_H -#define ZIX_COMMON_H - -#include <stdbool.h> - -/** - @addtogroup zix - @{ -*/ - -/** @cond */ -#ifndef ZIX_API -# if defined(_WIN32) && !defined(ZIX_STATIC) && defined(ZIX_INTERNAL) -# define ZIX_API __declspec(dllexport) -# elif defined(_WIN32) && !defined(ZIX_STATIC) -# define ZIX_API __declspec(dllimport) -# elif defined(__GNUC__) -# define ZIX_API __attribute__((visibility("default"))) -# else -# define ZIX_API -# endif -#endif - -#ifdef __GNUC__ -# define ZIX_PURE_FUNC __attribute__((pure)) -# define ZIX_CONST_FUNC __attribute__((const)) -# define ZIX_MALLOC_FUNC __attribute__((malloc)) -#else -# define ZIX_PURE_FUNC -# define ZIX_CONST_FUNC -# define ZIX_MALLOC_FUNC -#endif - -#define ZIX_PURE_API \ - ZIX_API \ - ZIX_PURE_FUNC - -#define ZIX_CONST_API \ - ZIX_API \ - ZIX_CONST_FUNC - -#define ZIX_MALLOC_API \ - ZIX_API \ - ZIX_MALLOC_FUNC - -/** @endcond */ - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __GNUC__ -# define ZIX_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) -#else -# define ZIX_LOG_FUNC(fmt, arg1) -#endif - -// Unused parameter macro to suppresses warnings and make it impossible to use -#if defined(__cplusplus) -# define ZIX_UNUSED(name) -#elif defined(__GNUC__) -# define ZIX_UNUSED(name) name##_unused __attribute__((__unused__)) -#else -# define ZIX_UNUSED(name) name -#endif - -typedef enum { - ZIX_STATUS_SUCCESS, - ZIX_STATUS_ERROR, - ZIX_STATUS_NO_MEM, - ZIX_STATUS_NOT_FOUND, - ZIX_STATUS_EXISTS, - ZIX_STATUS_BAD_ARG, - ZIX_STATUS_BAD_PERMS, - ZIX_STATUS_REACHED_END -} ZixStatus; - -static inline const char* -zix_strerror(const ZixStatus status) -{ - switch (status) { - case ZIX_STATUS_SUCCESS: - return "Success"; - case ZIX_STATUS_ERROR: - return "Unknown error"; - case ZIX_STATUS_NO_MEM: - return "Out of memory"; - case ZIX_STATUS_NOT_FOUND: - return "Not found"; - case ZIX_STATUS_EXISTS: - return "Exists"; - case ZIX_STATUS_BAD_ARG: - return "Bad argument"; - case ZIX_STATUS_BAD_PERMS: - return "Bad permissions"; - case ZIX_STATUS_REACHED_END: - return "Reached end"; - } - return "Unknown error"; -} - -/// Function for comparing two elements -typedef int (*ZixComparator)(const void* a, - const void* b, - const void* user_data); - -/// Function for testing equality of two elements -typedef bool (*ZixEqualFunc)(const void* a, const void* b); - -/// Function to destroy an element -typedef void (*ZixDestroyFunc)(void* ptr); - -/** - @} -*/ - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* ZIX_COMMON_H */ diff --git a/src/zix/digest.c b/src/zix/digest.c deleted file mode 100644 index dcfadf8f..00000000 --- a/src/zix/digest.c +++ /dev/null @@ -1,233 +0,0 @@ -/* - Copyright 2012-2021 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include "zix/digest.h" - -#include <assert.h> -#include <stdint.h> -#include <string.h> - -#if defined(__clang__) && __clang_major__ >= 12 -# define FALLTHROUGH() __attribute__((fallthrough)) -#elif defined(__GNUC__) && !defined(__clang__) -# define FALLTHROUGH() __attribute__((fallthrough)) -#else -# define FALLTHROUGH() -#endif - -/* - 64-bit hash: Essentially fasthash64, implemented here in an aligned/padded - and a general UB-free variant. -*/ - -static inline uint64_t -mix64(uint64_t h) -{ - h ^= h >> 23u; - h *= 0x2127599BF4325C37ull; - h ^= h >> 47u; - return h; -} - -uint64_t -zix_digest64(const uint64_t seed, const void* const key, const size_t len) -{ - static const uint64_t m = 0x880355F21E6D1965ull; - - const uint64_t* const blocks = (const uint64_t*)key; - const size_t n_blocks = len / sizeof(uint64_t); - - uint64_t h = seed ^ (len * m); - for (size_t i = 0u; i < n_blocks; ++i) { - h ^= mix64(blocks[i]); - h *= m; - } - - const uint8_t* const tail = (const unsigned char*)(blocks + n_blocks); - uint64_t v = 0u; - - switch (len & 7u) { - case 7: - v |= (uint64_t)tail[6] << 48u; - FALLTHROUGH(); - case 6: - v |= (uint64_t)tail[5] << 40u; - FALLTHROUGH(); - case 5: - v |= (uint64_t)tail[4] << 32u; - FALLTHROUGH(); - case 4: - v |= (uint64_t)tail[3] << 24u; - FALLTHROUGH(); - case 3: - v |= (uint64_t)tail[2] << 16u; - FALLTHROUGH(); - case 2: - v |= (uint64_t)tail[1] << 8u; - FALLTHROUGH(); - case 1: - v |= (uint64_t)tail[0]; - - h ^= mix64(v); - h *= m; - } - - return mix64(h); -} - -uint64_t -zix_digest64_aligned(const uint64_t seed, const void* const key, size_t len) -{ - static const uint64_t m = 0x880355F21E6D1965ull; - - assert((uintptr_t)key % sizeof(uint64_t) == 0u); - assert(len % sizeof(uint64_t) == 0u); - - const uint64_t* const blocks = (const uint64_t*)key; - const size_t n_blocks = len / sizeof(uint64_t); - uint64_t h = seed ^ (len * m); - - for (size_t i = 0u; i < n_blocks; ++i) { - h ^= mix64(blocks[i]); - h *= m; - } - - return mix64(h); -} - -/* - 32-bit hash: Essentially murmur3, reimplemented here in an aligned/padded and - a general UB-free variant. -*/ - -/** - Rotate left by some count of bits. - - This is recognized by any halfway decent compiler and compiled to a single - instruction on architectures that have one. -*/ -static inline uint32_t -rotl32(const uint32_t val, const uint32_t bits) -{ - return ((val << bits) | (val >> (32 - bits))); -} - -static inline uint32_t -mix32(uint32_t h) -{ - h ^= h >> 16u; - h *= 0x85EBCA6Bu; - h ^= h >> 13u; - h *= 0xC2B2AE35u; - h ^= h >> 16u; - return h; -} - -uint32_t -zix_digest32(const uint32_t seed, const void* const key, const size_t len) -{ - static const uint32_t c1 = 0xCC9E2D51u; - static const uint32_t c2 = 0x1B873593u; - - // Process as many 32-bit blocks as possible - const size_t n_blocks = len / sizeof(uint32_t); - const uint8_t* data = (const uint8_t*)key; - const uint8_t* const blocks_end = data + (n_blocks * sizeof(uint32_t)); - uint32_t h = seed; - for (; data != blocks_end; data += sizeof(uint32_t)) { - uint32_t k = 0u; - memcpy(&k, data, sizeof(uint32_t)); - - k *= c1; - k = rotl32(k, 15); - k *= c2; - - h ^= k; - h = rotl32(h, 13); - h = h * 5u + 0xE6546B64u; - } - - // Process any trailing bytes - uint32_t k = 0u; - switch (len & 3u) { - case 3u: - k ^= (uint32_t)data[2u] << 16u; - FALLTHROUGH(); - case 2u: - k ^= (uint32_t)data[1u] << 8u; - FALLTHROUGH(); - case 1u: - k ^= (uint32_t)data[0u]; - - k *= c1; - k = rotl32(k, 15u); - k *= c2; - h ^= k; - } - - return mix32(h ^ (uint32_t)len); -} - -uint32_t -zix_digest32_aligned(const uint32_t seed, - const void* const key, - const size_t len) -{ - static const uint32_t c1 = 0xCC9E2D51u; - static const uint32_t c2 = 0x1B873593u; - - assert((uintptr_t)key % sizeof(uint32_t) == 0u); - assert(len % sizeof(uint32_t) == 0u); - - const uint32_t* const blocks = (const uint32_t*)key; - const size_t n_blocks = len / sizeof(uint32_t); - uint32_t h = seed; - for (size_t i = 0u; i < n_blocks; ++i) { - uint32_t k = blocks[i]; - - k *= c1; - k = rotl32(k, 15); - k *= c2; - - h ^= k; - h = rotl32(h, 13); - h = h * 5u + 0xE6546B64u; - } - - return mix32(h ^ (uint32_t)len); -} - -// Native word size wrapper - -size_t -zix_digest(const size_t seed, const void* const buf, const size_t len) -{ -#if UINTPTR_MAX >= UINT64_MAX - return zix_digest64(seed, buf, len); -#else - return zix_digest32(seed, buf, len); -#endif -} - -size_t -zix_digest_aligned(const size_t seed, const void* const buf, const size_t len) -{ -#if UINTPTR_MAX >= UINT64_MAX - return zix_digest64_aligned(seed, buf, len); -#else - return zix_digest32_aligned(seed, buf, len); -#endif -} diff --git a/src/zix/digest.h b/src/zix/digest.h deleted file mode 100644 index 6df70024..00000000 --- a/src/zix/digest.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - Copyright 2012-2021 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef ZIX_DIGEST_H -#define ZIX_DIGEST_H - -#include "zix/common.h" - -#include <stddef.h> -#include <stdint.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/** - @addtogroup zix - @{ - @name Digest - Functions to generate a short "digest" of data with minimal collisions. - - These are good general-purpose hash functions for indexing arbitrary data, - but are not necessarily stable across platforms and should never be used for - cryptographic purposes. - @{ -*/ - -/** - Return a 32-bit hash of a buffer. - - This can be used for any size or alignment. -*/ -ZIX_PURE_API -uint32_t -zix_digest32(uint32_t seed, const void* buf, size_t len); - -/** - Return a 32-bit hash of an aligned buffer. - - Both the buffer and size must be aligned to 32 bits. For data that fits - these requirements, this is equivalent to, but faster than, zix_digest32(). -*/ -ZIX_PURE_API -uint32_t -zix_digest32_aligned(uint32_t seed, const void* buf, size_t len); - -/** - Return a 64-bit hash of a buffer. - - This can be used for any size or alignment. -*/ -ZIX_PURE_API -uint64_t -zix_digest64(uint64_t seed, const void* buf, size_t len); - -/** - Return a 64-bit hash of an aligned buffer. - - Both the buffer and size must be aligned to 64 bits. For data that fits - these requirements, this is equivalent to, but faster than, zix_digest64(). -*/ -ZIX_PURE_API -uint64_t -zix_digest64_aligned(uint64_t seed, const void* buf, size_t len); - -/** - Return a pointer-sized hash of a buffer. - - This can be used for any size or alignment. - - Internally, this simply dispatches to zix_digest32() or zix_digest64() as - appropriate. -*/ -ZIX_PURE_API -size_t -zix_digest(size_t seed, const void* buf, size_t len); - -/** - Return a pointer-sized hash of an aligned buffer. - - Both the buffer and size must be aligned to the pointer size. For data that - fits these requirements, this is equivalent to, but faster than, - zix_digest(). - - Internally, this simply dispatches to zix_digest32_aligned() or - zix_digest64_aligned() as appropriate. -*/ -ZIX_PURE_API -size_t -zix_digest_aligned(size_t seed, const void* buf, size_t len); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* ZIX_DIGEST_H */ diff --git a/src/zix/hash.c b/src/zix/hash.c deleted file mode 100644 index 8837378e..00000000 --- a/src/zix/hash.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - Copyright 2011-2021 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include "zix/hash.h" - -#include <assert.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> - -typedef struct ZixHashEntry { - ZixHashCode hash; ///< Non-folded hash value - ZixHashRecord* value; ///< Pointer to user-owned record -} ZixHashEntry; - -struct ZixHashImpl { - ZixKeyFunc key_func; ///< User-provided key accessor - ZixHashFunc hash_func; ///< User-provided hashing function - ZixKeyEqualFunc equal_func; ///< User-provided equality comparison function - size_t count; ///< Number of records stored in the table - size_t mask; ///< Bit mask for fast modulo (n_entries - 1) - size_t n_entries; ///< Power of two table size - ZixHashEntry* entries; ///< Pointer to dynamically allocated table -}; - -static const size_t min_n_entries = 4u; -static const size_t tombstone = 0xDEADu; - -ZixHash* -zix_hash_new(const ZixKeyFunc key_func, - const ZixHashFunc hash_func, - const ZixKeyEqualFunc equal_func) -{ - ZixHash* const hash = (ZixHash*)malloc(sizeof(ZixHash)); - if (!hash) { - return NULL; - } - - hash->key_func = key_func; - hash->hash_func = hash_func; - hash->equal_func = equal_func; - hash->count = 0u; - hash->n_entries = min_n_entries; - hash->mask = hash->n_entries - 1u; - - hash->entries = (ZixHashEntry*)calloc(hash->n_entries, sizeof(ZixHashEntry)); - if (!hash->entries) { - free(hash); - return NULL; - } - - return hash; -} - -void -zix_hash_free(ZixHash* const hash) -{ - if (hash) { - free(hash->entries); - free(hash); - } -} - -ZixHashIter -zix_hash_begin(const ZixHash* const hash) -{ - return hash->entries[0u].value ? 0u : zix_hash_next(hash, 0u); -} - -ZixHashIter -zix_hash_end(const ZixHash* const hash) -{ - return hash->n_entries; -} - -ZixHashRecord* -zix_hash_get(const ZixHash* hash, const ZixHashIter i) -{ - assert(i < hash->n_entries); - - return hash->entries[i].value; -} - -ZixHashIter -zix_hash_next(const ZixHash* const hash, ZixHashIter i) -{ - do { - ++i; - } while (i < hash->n_entries && !hash->entries[i].value); - - return i; -} - -size_t -zix_hash_size(const ZixHash* const hash) -{ - return hash->count; -} - -static inline size_t -fold_hash(const ZixHashCode h_nomod, const size_t mask) -{ - return h_nomod & mask; -} - -static inline bool -is_tombstone(const ZixHashEntry* const entry) -{ - return !entry->value && entry->hash == tombstone; -} - -static inline bool -is_empty(const ZixHashEntry* const entry) -{ - return !entry->value && !entry->hash; -} - -static inline bool -is_match(const ZixHash* const hash, - const ZixHashCode code, - const size_t entry_index, - ZixKeyEqualFunc predicate, - const void* const user_data) -{ - const ZixHashEntry* const entry = &hash->entries[entry_index]; - - return entry->value && entry->hash == code && - predicate(hash->key_func(entry->value), user_data); -} - -static inline size_t -next_index(const ZixHash* const hash, const size_t i) -{ - return (i == hash->mask) ? 0u : (i + 1u); -} - -static inline ZixHashIter -find_entry(const ZixHash* const hash, - const ZixHashKey* const key, - const size_t h, - const ZixHashCode code) -{ - size_t i = h; - - while (!is_match(hash, code, i, hash->equal_func, key) && - !is_empty(&hash->entries[i])) { - i = next_index(hash, i); - } - - return i; -} - -static ZixStatus -rehash(ZixHash* const hash, const size_t old_n_entries) -{ - ZixHashEntry* const old_entries = hash->entries; - const size_t new_n_entries = hash->n_entries; - - // Replace the array in the hash first so we can use find_entry() normally - hash->entries = (ZixHashEntry*)calloc(new_n_entries, sizeof(ZixHashEntry)); - if (!hash->entries) { - return ZIX_STATUS_NO_MEM; - } - - // Reinsert every element into the new array - for (size_t i = 0u; i < old_n_entries; ++i) { - ZixHashEntry* const entry = &old_entries[i]; - - if (entry->value) { - assert(hash->mask == hash->n_entries - 1u); - const size_t new_h = fold_hash(entry->hash, hash->mask); - const size_t new_i = find_entry(hash, entry->value, new_h, entry->hash); - - hash->entries[new_i] = *entry; - } - } - - free(old_entries); - return ZIX_STATUS_SUCCESS; -} - -static ZixStatus -grow(ZixHash* const hash) -{ - const size_t old_n_entries = hash->n_entries; - - hash->n_entries <<= 1u; - hash->mask = hash->n_entries - 1u; - - return rehash(hash, old_n_entries); -} - -static ZixStatus -shrink(ZixHash* const hash) -{ - if (hash->n_entries > min_n_entries) { - const size_t old_n_entries = hash->n_entries; - - hash->n_entries >>= 1u; - hash->mask = hash->n_entries - 1u; - - return rehash(hash, old_n_entries); - } - - return ZIX_STATUS_SUCCESS; -} - -ZixHashIter -zix_hash_find(const ZixHash* const hash, const ZixHashKey* const key) -{ - const ZixHashCode h_nomod = hash->hash_func(key); - const size_t h = fold_hash(h_nomod, hash->mask); - const ZixHashIter i = find_entry(hash, key, h, h_nomod); - - return is_empty(&hash->entries[i]) ? hash->n_entries : i; -} - -ZixHashRecord* -zix_hash_find_record(const ZixHash* const hash, const ZixHashKey* const key) -{ - const ZixHashCode h_nomod = hash->hash_func(key); - const size_t h = fold_hash(h_nomod, hash->mask); - - return hash->entries[find_entry(hash, key, h, h_nomod)].value; -} - -ZixHashInsertPlan -zix_hash_plan_insert_prehashed(const ZixHash* const hash, - const ZixHashCode code, - const ZixKeyMatchFunc predicate, - const void* const user_data) -{ - // Calculate an ideal initial position - ZixHashInsertPlan pos = {code, fold_hash(code, hash->mask)}; - - // Search for a free position starting at the ideal one - const size_t start_index = pos.index; - size_t first_tombstone = SIZE_MAX; - while (!is_empty(&hash->entries[pos.index])) { - if (is_match(hash, code, pos.index, predicate, user_data)) { - return pos; - } - - if (first_tombstone == SIZE_MAX && - is_tombstone(&hash->entries[pos.index])) { - first_tombstone = pos.index; // Remember the first/best free index - } - - if ((pos.index = next_index(hash, pos.index)) == start_index) { - break; - } - } - - // If we found a tombstone before an empty slot, place the new element there - pos.index = first_tombstone < SIZE_MAX ? first_tombstone : pos.index; - assert(!hash->entries[pos.index].value); - - return pos; -} - -ZixHashInsertPlan -zix_hash_plan_insert(const ZixHash* const hash, const ZixHashKey* const key) -{ - return zix_hash_plan_insert_prehashed( - hash, hash->hash_func(key), hash->equal_func, key); -} - -ZixHashRecord* -zix_hash_record_at(const ZixHash* const hash, const ZixHashInsertPlan position) -{ - return hash->entries[position.index].value; -} - -ZixStatus -zix_hash_insert_at(ZixHash* const hash, - const ZixHashInsertPlan position, - ZixHashRecord* const record) -{ - if (hash->entries[position.index].value) { - return ZIX_STATUS_EXISTS; - } - - // Set entry to new value - ZixHashEntry* const entry = &hash->entries[position.index]; - assert(!entry->value); - entry->hash = position.code; - entry->value = record; - - // Update size and rehash if we exceeded the maximum load - const size_t max_load = hash->n_entries / 2u + hash->n_entries / 8u; - if (++hash->count >= max_load) { - return grow(hash); - } - - return ZIX_STATUS_SUCCESS; -} - -ZixStatus -zix_hash_insert(ZixHash* const hash, ZixHashRecord* const record) -{ - const ZixHashKey* const key = hash->key_func(record); - const ZixHashInsertPlan position = zix_hash_plan_insert(hash, key); - - return zix_hash_insert_at(hash, position, record); -} - -ZixStatus -zix_hash_erase(ZixHash* const hash, - const ZixHashIter i, - ZixHashRecord** const removed) -{ - // Replace entry with a tombstone - *removed = hash->entries[i].value; - hash->entries[i].hash = tombstone; - hash->entries[i].value = NULL; - - // Decrease element count and rehash if necessary - --hash->count; - if (hash->count < hash->n_entries / 4u) { - return shrink(hash); - } - - return ZIX_STATUS_SUCCESS; -} - -ZixStatus -zix_hash_remove(ZixHash* const hash, - const ZixHashKey* const key, - ZixHashRecord** const removed) -{ - const ZixHashIter i = zix_hash_find(hash, key); - - return i == hash->n_entries ? ZIX_STATUS_NOT_FOUND - : zix_hash_erase(hash, i, removed); -} diff --git a/src/zix/hash.h b/src/zix/hash.h deleted file mode 100644 index f08d267b..00000000 --- a/src/zix/hash.h +++ /dev/null @@ -1,329 +0,0 @@ -/* - Copyright 2011-2021 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef ZIX_HASH_H -#define ZIX_HASH_H - -#include "zix/common.h" - -#include <stdbool.h> -#include <stddef.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/** - @addtogroup zix - @{ - @name Hash - @{ -*/ - -// ZIX_HASH_KEY_TYPE can be defined to make the API more type-safe -#if defined(ZIX_HASH_KEY_TYPE) -typedef ZIX_HASH_KEY_TYPE ZixHashKey; -#else -typedef void ZixHashKey; -#endif - -// ZIX_HASH_RECORD_TYPE can be defined to make the API more type-safe -#if defined(ZIX_HASH_RECORD_TYPE) -typedef ZIX_HASH_RECORD_TYPE ZixHashRecord; -#else -typedef void ZixHashRecord; -#endif - -// ZIX_HASH_SEARCH_DATA_TYPE can be defined to make the API more type-safe -#if defined(ZIX_HASH_SEARCH_DATA_TYPE) -typedef ZIX_HASH_SEARCH_DATA_TYPE ZixHashSearchData; -#else -typedef void ZixHashSearchData; -#endif - -/** - A hash table. - - This is an open addressing hash table that stores pointers to arbitrary user - data. Internally, everything is stored in a single flat array that is - resized when necessary. - - The single user-provided pointer that is stored in the table is called a - "record". A record contains a "key", which is accessed via a user-provided - function. This design supports storing large records (which may be - expensive to allocate/construct) without requiring an entire record to - search. Simple atomic values can be stored by providing a trivial identity - function as a key function. - - The table uses power of 2 sizes with a growth factor of 2, so that hash - values can be folded into an array index using bitwise AND as a fast modulo. - This means that only the necessary low bits of the hash value will be used, - so the hash function must be well-balanced within this range. More or less - any good modern hash algorithm will be fine, but beware, for example, hash - functions that assume they are targeting a table with a prime size. - - Since this doubles and halves in size, it may not be an optimal choice if - memory reuse is a priority. A growth factor of 1.5 with fast range - reduction may be a better choice there, at the cost of requiring 128-bit - arithmetic on 64-bit platforms, and indexing operations being slightly more - expensive. -*/ -typedef struct ZixHashImpl ZixHash; - -/// A full hash code for a key which is not folded down to the table size -typedef size_t ZixHashCode; - -/** - An iterator to an entry in a hash table. - - This is really just an index, but should be considered opaque to the user - and only used via the provided API and equality comparison. -*/ -typedef size_t ZixHashIter; - -/// User function for computing the hash of a key -typedef ZixHashCode (*ZixHashFunc)(const ZixHashKey* key); - -/// User function for determining if two keys are truly equal -typedef bool (*ZixKeyEqualFunc)(const ZixHashKey* a, const ZixHashKey* b); - -/// User function for determining if a key matches in a custom search -typedef bool (*ZixKeyMatchFunc)(const ZixHashKey* key, - const ZixHashSearchData* user_data); - -/// User function for getting the key of a record -typedef const ZixHashKey* (*ZixKeyFunc)(const ZixHashRecord* record); - -/** - A "plan" (position) to insert a record in a hash table. - - This contains everything necessary to insert a record, except the record - itself. It is exposed to split up insertion so that records only need to be - allocated if an existing record is not found, but the contents should be - considered opaque to the user. -*/ -typedef struct { - ZixHashCode code; ///< Hash code used to find this position - ZixHashIter index; ///< Index into hash table -} ZixHashInsertPlan; - -/** - Create a new hash table. - - @param key_func A function to retrieve the key from a record. - @param hash_func The key hashing function. - @param equal_func A function to test keys for equality. -*/ -ZIX_API -ZixHash* -zix_hash_new(ZixKeyFunc key_func, - ZixHashFunc hash_func, - ZixKeyEqualFunc equal_func); - -/// Free `hash` -ZIX_API -void -zix_hash_free(ZixHash* hash); - -/// Return an iterator to the first record in a hash, or the end if it is empty -ZIX_PURE_API -ZixHashIter -zix_hash_begin(const ZixHash* hash); - -/// Return an iterator one past the last possible record in a hash -ZIX_PURE_API -ZixHashIter -zix_hash_end(const ZixHash* hash); - -/// Return the record pointed to by an iterator -ZIX_PURE_API -ZixHashRecord* -zix_hash_get(const ZixHash* hash, ZixHashIter i); - -/// Return an iterator that has been advanced to the next record in a hash -ZIX_PURE_API -ZixHashIter -zix_hash_next(const ZixHash* hash, ZixHashIter i); - -/// Return the number of elements in a hash -ZIX_PURE_API -size_t -zix_hash_size(const ZixHash* hash); - -/** - Find the best position to insert a record with the given key. - - This searches the hash table and returns either the position of an existing - equivalent record, or the best available position to insert a new one. The - difference can be determined with zix_hash_record_at(). - - If the returned position is not occupied, then it is valid to insert a - record with this key using zix_hash_insert_at() until the hash table is - modified (which invalidates the position). -*/ -ZIX_API -ZixHashInsertPlan -zix_hash_plan_insert(const ZixHash* hash, const ZixHashKey* key); - -/** - Find the best position to insert a record with a custom search. - - This is an advanced low-level version of zix_hash_plan_insert(). It allows - a precalculated hash code to be given, along with a custom search predicate. - These must be compatible with the functions that manage the hash table: - trivial usage would be to pass the equality function used by the hash and - the key to search for. - - This is exposed to make it possible to avoid constructing a key at all when - potentially inserting. For example, if keys are structures with a fixed - header and a variably-sized body, and you have a separate header and body - this can be used to find an insert position without having to allocate a - contiguous key. - - Note that care must be taken when using this function: improper use can - corrupt the hash table. The hash code given must be correct for the key to - be inserted, and the predicate must return true only if the key it is called - with (the first argument) matches the key to be inserted. -*/ -ZIX_API -ZixHashInsertPlan -zix_hash_plan_insert_prehashed(const ZixHash* hash, - ZixHashCode code, - ZixKeyMatchFunc predicate, - const ZixHashSearchData* user_data); - -/** - Return the record at the given position, or null. - - This is used to determine if a position returned from zix_hash_plan_insert() - can be used to insert a new record, or to access the existing matching - record. -*/ -ZIX_PURE_API -ZixHashRecord* -zix_hash_record_at(const ZixHash* hash, ZixHashInsertPlan position); - -/** - Insert a record at a specific position. - - The position must have been found with an earlier call to - zix_hash_plan_insert(), and no modifications must have been made to the hash - table since. - - @param hash The hash table. - - @param position The position for the new record. - - @param record The record to insert which, on success, can now be considered - owned by the hash table. - - @return ZIX_STATUS_SUCCESS, ZIX_STATUS_EXISTS if a record already exists at - this position, or ZIX_STATUS_NO_MEM if growing the hash table failed. -*/ -ZIX_API -ZixStatus -zix_hash_insert_at(ZixHash* hash, - ZixHashInsertPlan position, - ZixHashRecord* record); - -/** - Insert a record. - - This is a trivial wrapper for zix_hash_plan_insert() and - zix_hash_insert_at() that is more convenient when record construction is not - expensive. - - @param hash The hash table. - - @param record The record to insert which, on success, can now be considered - owned by the hash table. - - @return ZIX_STATUS_SUCCESS, ZIX_STATUS_EXISTS, or ZIX_STATUS_NO_MEM. -*/ -ZIX_API -ZixStatus -zix_hash_insert(ZixHash* hash, ZixHashRecord* record); - -/** - Erase a record at a specific position. - - @param hash The hash table to remove the record from. - - @param i Iterator to the record to remove. This must be a valid iterator - from an earlier call to zix_hash_find(), that is, the hash table must not - have been modified since. - - @param removed Set to the removed record, or null. - - @return ZIX_STATUS_SUCCES or ZIX_STATUS_BAD_ARG if `i` does not point at a - removable record. -*/ -ZIX_API -ZixStatus -zix_hash_erase(ZixHash* hash, ZixHashIter i, ZixHashRecord** removed); - -/** - Remove a record. - - @param hash The hash table. - @param key The key of the record to remove. - @param removed Set to the removed record, or null. - @return ZIX_STATUS_SUCCES or ZIX_STATUS_NOT_FOUND. -*/ -ZIX_API -ZixStatus -zix_hash_remove(ZixHash* hash, const ZixHashKey* key, ZixHashRecord** removed); - -/** - Find the position of a record with a given key. - - @param hash The hash table to search. - - @param key The key of the desired record. - - @return An iterator to the matching record, or the end iterator if no such - record exists. -*/ -ZIX_API -ZixHashIter -zix_hash_find(const ZixHash* hash, const ZixHashKey* key); - -/** - Find a record with a given key. - - This is essentially the same as zix_hash_find(), but returns a pointer to - the record for convenience. - - @param hash The hash table to search. - - @param key The key of the desired record. - - @return A pointer to the matching record, of null if no such record exists. -*/ -ZIX_API -ZixHashRecord* -zix_hash_find_record(const ZixHash* hash, const ZixHashKey* key); - -/** - @} - @} -*/ - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* ZIX_HASH_H */ diff --git a/subprojects/zix b/subprojects/zix new file mode 160000 +Subproject 335fe063ea9b98d8d94cb60e63a08eeb5303f0d diff --git a/subprojects/zix.wrap b/subprojects/zix.wrap new file mode 100644 index 00000000..cbbba4df --- /dev/null +++ b/subprojects/zix.wrap @@ -0,0 +1,5 @@ +[wrap-git] +url = https://gitlab.com/drobilla/zix.git +push-url = git@gitlab.com:drobilla/zix.git +revision = main +depth = 1 |