From 97572317b751281282254067893f3681b427cbf6 Mon Sep 17 00:00:00 2001 From: M Joonas Pihlaja Date: Tue, 8 Feb 2022 22:14:44 +0000 Subject: [PATCH] ck_hs: Allow specifying an offset to embedded keys A common use case for hash sets is to store objects in a hash sets keyed on some embedded key within the object. The current interface requires the client to write hash and comparator functions that are specific to each object type being inserted into the hash. Alternatively, the client may store pointers to the keys in the hash set and use `container_of` to recover the actual object value. This is also quite cumbersome. This patch adds a function `ck_hs_init_from_options` that initializes a hash set from an extensible struct of options. The struct contains all the information currently passed to `ck_hs_init`, and also allows specifying a short 15 bit key offset when the hash set uses `CK_HS_MODE_OBJECT` mode. The patch sets the following invariants: - The public interface always specifies whether a parameter pointer is to a key or a value. - Hash and comparator functions are always called on pointers to keys. Fixes #152 --- doc/ck_hs_init | 58 +++++ include/ck_hs.h | 72 ++++++ regressions/ck_hs/validate/Makefile | 5 +- regressions/ck_hs/validate/hs_init_opts.c | 262 ++++++++++++++++++++++ src/ck_hs.c | 169 ++++++++++---- 5 files changed, 516 insertions(+), 50 deletions(-) create mode 100644 regressions/ck_hs/validate/hs_init_opts.c diff --git a/doc/ck_hs_init b/doc/ck_hs_init index cfcbf635..5b7b2eff 100644 --- a/doc/ck_hs_init +++ b/doc/ck_hs_init @@ -39,6 +39,29 @@ Concurrency Kit (libck, \-lck) .Fn ck_hs_compare_cb_t "const void *c1" "const void *c2" .Ft bool .Fn ck_hs_init "ck_hs_t *hs" "unsigned int mode" "ck_hs_hash_cb_t *hash_function" "ck_hs_compare_cb_t *compare" "struct ck_malloc *allocator" "unsigned long capacity" "unsigned long seed" +.Pp +.Dv struct ck_hs_init_options options = CK_HS_INIT_OPTIONS_INITIALIZER; +.Pp +.Dv #define CK_HS_INIT_OPTIONS_SIZE_V0 ... +.Pp +.Dv #define CK_HS_INIT_OPTIONS_SIZE_V1 ... +.Pp +.Bd -literal -offset +struct ck_hs_init_options { + /* v0 fields */ + uintptr_t options_size; + uintptr_t mode; + ck_hs_hash_cb_t *hash_function; + ck_hs_compare_cb_t *compare; + struct ck_malloc *allocator; + uintptr_t capacity; + uintptr_t seed; + /* v1 fields */ + uintptr_t key_offset; +}; +.Ed +.Ft bool +.Fn ck_hs_init_from_options "ck_hs_t *hs" "const struct ck_hs_init_options *options" .Sh DESCRIPTION The .Fn ck_hs_init @@ -127,6 +150,41 @@ The argument .Fa seed specifies the initial seed used by the underlying hash function. The user is free to choose a value of their choice. +.Pp +The +.Xr ck_hs_init_from_options 3 +function takes all the values required for initializing the hash set +from a ck_hs_init_options struct. The options struct's first field +.Fa options_size +is used to version the options and must be initialized with either the +size of the struct, or one of the following: +.Bl -tag -width indent +.It CK_HS_INIT_OPTIONS_SIZE_V0 +The options struct contains the fields +.Fa options_size , +.Fa mode , +.Fa hash_function , +.Fa compare , +.Fa allocator , +.Fa capacity , +and +.Fa seed . +The semantics for these fields is exactly as the corresponding arguments of +.Xr ck_hs_init 3 . +.It CK_HS_INIT_OPTIONS_SIZE_V1 +The options struct additionally contains the field +.Fa key_offset . +If this field is non-zero, then the hash set must have mode +CK_HS_MODE_OBJECT set, and the field gives the offset of an embedded +key field within the object. The offset is subsequenty added to the +object to pass the address of the embedded key to the +.Fa hash_function +and +.Fa compare +functions. +The maximum supported offset is 65535. +.El +Future versions of concurrency kit may define more option fields. .Sh RETURN VALUES Upon successful completion .Fn ck_hs_init diff --git a/include/ck_hs.h b/include/ck_hs.h index cd3e5dac..f9412f51 100644 --- a/include/ck_hs.h +++ b/include/ck_hs.h @@ -117,6 +117,77 @@ ck_hs_hash(const struct ck_hs *hs, const void *k) return hs->hf(k, hs->seed); } +/* + * An extensible struct of options for `ck_hs_init_from_options`. The + * fields in this struct must not be rearranged and ach field must + * have the same width as an uintptr_t. Adding new fields to this + * struct is forwards compatible. + */ +struct ck_hs_init_options { + /* -- V0 options start here -- */ + + /* + * The size of this options struct. This is set automatically + * by `CK_HS_INIT_OPTIONS_INITIALIZER`, or it should be set + * expicitly to the size of the smallest required version of + * the struct. Version specific sizes are given by + * `CK_HS_INIT_OPTIONS_SIZE_V` constants. + */ + uintptr_t options_size; + + /* + * Hash set mode. + */ + uintptr_t mode; + + /* + * Key hash function. + */ + ck_hs_hash_cb_t *hash_function; + + /* + * Key comparator function. + */ + ck_hs_compare_cb_t *compare; + + /* + * Allocator used for the hash set. + */ + struct ck_malloc *allocator; + + /* + * Initial capacity of the hash set. + */ + uintptr_t capacity; + + /* + * Hash function seed. + */ + uintptr_t seed; + + /* -- V1 options start here -- */ + + /* + * When mode is CK_HS_MODE_OBJECT, then the offset in bytes + * from the start of the object to the start of the key within + * the object. The hash and key comparator functions will + * then be called with the address of the embedded key rather + * than the object. + */ + uintptr_t key_offset; +}; + +/* + * The zeroth version of the options struct has only the same fields + * that `ck_hs_init` takes. + */ +#define CK_HS_INIT_OPTIONS_SIZE_V0 (7 * sizeof(uintptr_t)) + +/* The first version of the options struct adds `key_offset`. */ +#define CK_HS_INIT_OPTIONS_SIZE_V1 (8 * sizeof(uintptr_t)) + +#define CK_HS_INIT_OPTIONS_INITIALIZER { .options_size = sizeof(struct ck_hs_init_options) } + typedef void *ck_hs_apply_fn_t(void *, void *); bool ck_hs_apply(ck_hs_t *, unsigned long, const void *, ck_hs_apply_fn_t *, void *); void ck_hs_iterator_init(ck_hs_iterator_t *); @@ -126,6 +197,7 @@ bool ck_hs_move(ck_hs_t *, ck_hs_t *, ck_hs_hash_cb_t *, ck_hs_compare_cb_t *, struct ck_malloc *); bool ck_hs_init(ck_hs_t *, unsigned int, ck_hs_hash_cb_t *, ck_hs_compare_cb_t *, struct ck_malloc *, unsigned long, unsigned long); +bool ck_hs_init_from_options(ck_hs_t *, const struct ck_hs_init_options *); void ck_hs_destroy(ck_hs_t *); void *ck_hs_get(ck_hs_t *, unsigned long, const void *); bool ck_hs_put(ck_hs_t *, unsigned long, const void *); diff --git a/regressions/ck_hs/validate/Makefile b/regressions/ck_hs/validate/Makefile index a96e652a..8f3e8f34 100644 --- a/regressions/ck_hs/validate/Makefile +++ b/regressions/ck_hs/validate/Makefile @@ -1,12 +1,15 @@ .PHONY: check clean distribution -OBJECTS=serial +OBJECTS=serial hs_init_opts all: $(OBJECTS) serial: serial.c ../../../include/ck_hs.h ../../../src/ck_hs.c $(CC) $(CFLAGS) -o serial serial.c ../../../src/ck_hs.c +hs_init_opts: hs_init_opts.c ../../../include/ck_hs.h ../../../src/ck_hs.c + $(CC) $(CFLAGS) -o hs_init_opts hs_init_opts.c ../../../src/ck_hs.c + check: all ./serial diff --git a/regressions/ck_hs/validate/hs_init_opts.c b/regressions/ck_hs/validate/hs_init_opts.c new file mode 100644 index 00000000..40e4b54e --- /dev/null +++ b/regressions/ck_hs/validate/hs_init_opts.c @@ -0,0 +1,262 @@ +/* + * Copyright 2012 Samy Al Bahra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyrights + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyrights + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "../../common.h" + +/* + * We tag keys and values so that we can check that callbacks receive + * the correct kinds of arguments. + */ +#define KEY_TAG 0xAA000000 +#define VAL_TAG 0xBB000000 +#define TAGGED_KEY(key) ((key) | KEY_TAG) +#define TAGGED_VAL(val) ((val) | VAL_TAG) +#define GET_TAG(tagged) ((tagged) & 0xFF000000) + +static void * +hs_malloc(size_t r) +{ + + return malloc(r); +} + +static void +hs_free(void *p, size_t b, bool r) +{ + + (void)b; + (void)r; + free(p); + return; +} + +static struct ck_malloc my_allocator = { + .malloc = hs_malloc, + .free = hs_free +}; + +static unsigned long +hs_hash(const void *key, unsigned long seed) +{ + const unsigned long *k = key; + + assert(GET_TAG(*k) == KEY_TAG); + + (void)seed; + return *k; +} + +static bool +hs_compare(const void *previous, const void *compare) +{ + const unsigned long *a = previous; + const unsigned long *b = compare; + + assert(GET_TAG(*a) == KEY_TAG); + assert(GET_TAG(*b) == KEY_TAG); + + return *a == *b; +} + +static void * +replace_cl(void *vobj, void *cl) +{ + unsigned long *obj = vobj; + unsigned long *repl = cl; + + assert(GET_TAG(*obj) == VAL_TAG); + assert(GET_TAG(*repl) == VAL_TAG); + + return cl; +} + +/* + * Test object that contains tagged (val,key) pairs. The tags are + * used to check that the callbacks are called on the correct + * things. + */ +struct test_obj { + unsigned long val; + unsigned long key; +}; + +#define TEST_OBJ_INITIALIZER(v, k) (struct test_obj) { .val = TAGGED_VAL(v), .key = TAGGED_KEY(k) } + +static void +run_happy_path_test(void) +{ + ck_hs_t hs[1]; + struct ck_hs_init_options opts = CK_HS_INIT_OPTIONS_INITIALIZER; + struct test_obj a, b, c; + unsigned long key; + void *ptr; + + opts.mode = CK_HS_MODE_SPMC | CK_HS_MODE_OBJECT; + opts.hash_function = hs_hash; + opts.compare = hs_compare; + opts.allocator = &my_allocator; + opts.seed = 1234; + opts.key_offset = offsetof(struct test_obj, key); + opts.capacity = 8; + + if (ck_hs_init_from_options(hs, &opts) == false) + ck_error("ck_hs_init_from_options\n"); + + ck_hs_gc(hs, 0, 0); + + /* Check that the hash is the identity. */ + a = TEST_OBJ_INITIALIZER(1000, 111); + assert(hs_hash(&a.key, hs->seed) == TAGGED_KEY(111)); + + ck_hs_gc(hs, 0, 0); + + /* Add `a` with key 111 */ + a = TEST_OBJ_INITIALIZER(65, 111); + ptr = &ptr; + assert(ck_hs_set(hs, hs_hash(&a.key, hs->seed), &a, &ptr) == true); + assert(ptr == NULL); + + ck_hs_gc(hs, 0, 0); + + /* Add `b` with key 222 */ + b = TEST_OBJ_INITIALIZER(66, 222); + ptr = &ptr; + assert(ck_hs_set(hs, hs_hash(&b.key, hs->seed), &b, &ptr) == true); + assert(ptr == NULL); + + ck_hs_gc(hs, 0, 0); + + /* Remove `a` */ + key = TAGGED_KEY(111); + ptr = &ptr; + ptr = ck_hs_remove(hs, hs_hash(&key, hs->seed), &key); + assert(ptr == &a); + + ck_hs_gc(hs, 0, 0); + + /* Replace `b` with `c`. */ + c = TEST_OBJ_INITIALIZER(67, 222); + ptr = &ptr; + assert(ck_hs_set(hs, hs_hash(&c.key, hs->seed), &c, &ptr) == true); + assert(ptr == &b); + + ck_hs_gc(hs, 0, 0); + + /* + * Check that `ck_hs_apply` takes key pointers and is called + * on object pointers. The witness replaces `c` with `a`. + */ + key = TAGGED_KEY(222); + a = TEST_OBJ_INITIALIZER(65, 222); + assert(ck_hs_apply(hs, hs_hash(&key, hs->seed), &key, replace_cl, &a) == true); + + ck_hs_gc(hs, 0, 0); + + /* + * At this point `hs` only has `a` in it with key 222. + * Replace it with `b` using `ck_hs_fas`. + */ + b = TEST_OBJ_INITIALIZER(66, 222); + ptr = &ptr; + assert(ck_hs_fas(hs, hs_hash(&b.key, hs->seed), &b, &ptr)); + assert(ptr == &a); + + ck_hs_gc(hs, 0, 0); + + /* + * Check that `ck_hs_put` takes objects. + */ + c = TEST_OBJ_INITIALIZER(67, 333); + assert(ck_hs_put(hs, hs_hash(&c.key, hs->seed), &c) == true); + + ck_hs_gc(hs, 0, 0); + + /* Also `ck_hs_put_unique`. */ + a = TEST_OBJ_INITIALIZER(65, 111); + assert(ck_hs_put_unique(hs, hs_hash(&c.key, hs->seed), &a) == true); + + ck_hs_gc(hs, 0, 0); + + /* Check that `ck_hs_get` takes keys. */ + key = TAGGED_KEY(111); + assert(ck_hs_get(hs, hs_hash(&key, hs->seed), &key) == &a); + + ck_hs_gc(hs, 0, 0); + + ck_hs_destroy(hs); + + return; +} + +static void +run_invalid_opts_tests(void) +{ + ck_hs_t hs[1]; + struct ck_hs_init_options opts = CK_HS_INIT_OPTIONS_INITIALIZER; + + opts.mode = CK_HS_MODE_SPMC | CK_HS_MODE_OBJECT; + opts.hash_function = hs_hash; + opts.compare = hs_compare; + opts.allocator = &my_allocator; + opts.seed = 1234; + opts.capacity = 16; + + /* Key offset can't be too large. */ + opts.key_offset = 65536; + if (ck_hs_init_from_options(hs, &opts) == true) + ck_error("ck_hs_init_from_options succeeded with too large" + " key offset"); + + /* Mode must be OBJECT if key offset is not zero. */ + opts.mode = CK_HS_MODE_SPMC | CK_HS_MODE_DIRECT; + opts.key_offset = 8; + if (ck_hs_init_from_options(hs, &opts) == true) + ck_error("ck_hs_init_from_options succeeded non-zero key offset" + " and DIRECT mode"); + + return; +} + +int +main(void) +{ + assert(sizeof(struct ck_hs_init_options) == CK_HS_INIT_OPTIONS_SIZE_V1); + + run_happy_path_test(); + run_invalid_opts_tests(); + + return 0; +} + diff --git a/src/ck_hs.c b/src/ck_hs.c index 246bceb2..c0a9c684 100644 --- a/src/ck_hs.c +++ b/src/ck_hs.c @@ -74,6 +74,13 @@ #error "ck_hs is not supported on your platform." #endif +/* + * The key offset is stored in the high bits of the mode field in + * ck_hs_t. This is binary compatible with all existing clients + * across all architectures. + */ +#define CK_HS_MODE_KEY_OFFSET_BITS 16 + enum ck_hs_probe_behavior { CK_HS_PROBE = 0, /* Default behavior. */ CK_HS_PROBE_TOMBSTONE, /* Short-circuit on tombstone. */ @@ -329,6 +336,13 @@ ck_hs_map_bound_get(struct ck_hs_map *m, unsigned long h) return r; } +static inline const void * +ck_hs_apply_key_offset(const struct ck_hs *hs, const void *obj) +{ + unsigned int key_offset = hs->mode >> CK_HS_MODE_KEY_OFFSET_BITS; + return (const unsigned char *)obj + key_offset; +} + bool ck_hs_grow(struct ck_hs *hs, unsigned long capacity) @@ -358,7 +372,7 @@ ck_hs_grow(struct ck_hs *hs, previous = CK_HS_VMA(previous); #endif - h = hs->hf(previous, hs->seed); + h = hs->hf(ck_hs_apply_key_offset(hs, previous), hs->seed); offset = h & update->mask; i = probes = 0; @@ -431,7 +445,7 @@ ck_hs_map_probe(struct ck_hs *hs, unsigned long probe_limit, enum ck_hs_probe_behavior behavior) { - const void **bucket, **cursor, *k, *compare; + const void **bucket, **cursor, *val, *val_key, *compare_key; const void **pr = NULL; unsigned long offset, j, i, probes, opl; @@ -441,12 +455,12 @@ ck_hs_map_probe(struct ck_hs *hs, if (hs->mode & CK_HS_MODE_OBJECT) { hv = (h >> 25) & CK_HS_KEY_MASK; - compare = CK_HS_VMA(key); + compare_key = CK_HS_VMA(key); } else { - compare = key; + compare_key = key; } #else - compare = key; + compare_key = key; #endif offset = h & map->mask; @@ -465,7 +479,7 @@ ck_hs_map_probe(struct ck_hs *hs, if (probes++ == probe_limit) { if (probe_limit == opl || pr != NULL) { - k = CK_HS_EMPTY; + val = CK_HS_EMPTY; goto leave; } @@ -476,17 +490,17 @@ ck_hs_map_probe(struct ck_hs *hs, probe_limit = opl; } - k = ck_pr_load_ptr(cursor); - if (k == CK_HS_EMPTY) + val = ck_pr_load_ptr(cursor); + if (val == CK_HS_EMPTY) goto leave; - if (k == CK_HS_TOMBSTONE) { + if (val == CK_HS_TOMBSTONE) { if (pr == NULL) { pr = cursor; *n_probes = probes; if (behavior == CK_HS_PROBE_TOMBSTONE) { - k = CK_HS_EMPTY; + val = CK_HS_EMPTY; goto leave; } } @@ -496,20 +510,21 @@ ck_hs_map_probe(struct ck_hs *hs, #ifdef CK_HS_PP if (hs->mode & CK_HS_MODE_OBJECT) { - if (((uintptr_t)k >> CK_MD_VMA_BITS) != hv) + if (((uintptr_t)val >> CK_MD_VMA_BITS) != hv) continue; - k = CK_HS_VMA(k); + val = CK_HS_VMA(val); } #endif - if (k == compare) + val_key = ck_hs_apply_key_offset(hs, val); + if (val_key == compare_key) goto leave; if (hs->compare == NULL) continue; - if (hs->compare(k, key) == true) + if (hs->compare(val_key, key) == true) goto leave; } @@ -520,7 +535,7 @@ ck_hs_map_probe(struct ck_hs *hs, if (probes > probe_limit) { cursor = NULL; } else { - *object = k; + *object = val; } if (pr == NULL) @@ -531,16 +546,16 @@ ck_hs_map_probe(struct ck_hs *hs, } static inline const void * -ck_hs_marshal(unsigned int mode, const void *key, unsigned long h) +ck_hs_marshal(unsigned int mode, const void *val, unsigned long h) { #ifdef CK_HS_PP const void *insert; if (mode & CK_HS_MODE_OBJECT) { - insert = (void *)((uintptr_t)CK_HS_VMA(key) | + insert = (void *)((uintptr_t)CK_HS_VMA(val) | ((h >> 25) << CK_MD_VMA_BITS)); } else { - insert = key; + insert = val; } return insert; @@ -548,7 +563,7 @@ ck_hs_marshal(unsigned int mode, const void *key, unsigned long h) (void)mode; (void)h; - return key; + return val; #endif } @@ -585,7 +600,7 @@ ck_hs_gc(struct ck_hs *hs, unsigned long cycles, unsigned long seed) } for (i = 0; i < map->capacity; i++) { - const void **first, *object, **slot, *entry; + const void **first, *object, **slot, *entry, *entry_key; unsigned long n_probes, offset, h; entry = map->entries[(i + seed) & map->mask]; @@ -597,10 +612,11 @@ ck_hs_gc(struct ck_hs *hs, unsigned long cycles, unsigned long seed) entry = CK_HS_VMA(entry); #endif - h = hs->hf(entry, hs->seed); + entry_key = ck_hs_apply_key_offset(hs, entry); + h = hs->hf(entry_key, hs->seed); offset = h & map->mask; - slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, entry, &object, + slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, entry_key, &object, ck_hs_map_bound_get(map, h), CK_HS_PROBE); if (first != NULL) { @@ -644,22 +660,23 @@ ck_hs_gc(struct ck_hs *hs, unsigned long cycles, unsigned long seed) bool ck_hs_fas(struct ck_hs *hs, unsigned long h, - const void *key, + const void *val, void **previous) { - const void **slot, **first, *object, *insert; + const void **slot, **first, *object, *insert, *val_key; struct ck_hs_map *map = hs->map; unsigned long n_probes; *previous = NULL; - slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, key, &object, + val_key = ck_hs_apply_key_offset(hs, val); + slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, val_key, &object, ck_hs_map_bound_get(map, h), CK_HS_PROBE); /* Replacement semantics presume existence. */ if (object == NULL) return false; - insert = ck_hs_marshal(hs->mode, key, h); + insert = ck_hs_marshal(hs->mode, val, h); if (first != NULL) { ck_pr_store_ptr(first, insert); @@ -759,19 +776,20 @@ ck_hs_apply(struct ck_hs *hs, bool ck_hs_set(struct ck_hs *hs, unsigned long h, - const void *key, + const void *val, void **previous) { - const void **slot, **first, *object, *insert; + const void **slot, **first, *object, *insert, *val_key; unsigned long n_probes; struct ck_hs_map *map; + val_key = ck_hs_apply_key_offset(hs, val); *previous = NULL; restart: map = hs->map; - slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, key, &object, map->probe_limit, CK_HS_PROBE_INSERT); + slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, val_key, &object, map->probe_limit, CK_HS_PROBE_INSERT); if (slot == NULL && first == NULL) { if (ck_hs_grow(hs, map->capacity << 1) == false) return false; @@ -780,7 +798,7 @@ ck_hs_set(struct ck_hs *hs, } ck_hs_map_bound_set(map, h, n_probes); - insert = ck_hs_marshal(hs->mode, key, h); + insert = ck_hs_marshal(hs->mode, val, h); if (first != NULL) { /* If an earlier bucket was found, then store entry there. */ @@ -815,17 +833,18 @@ ck_hs_set(struct ck_hs *hs, CK_CC_INLINE static bool ck_hs_put_internal(struct ck_hs *hs, unsigned long h, - const void *key, + const void *val, enum ck_hs_probe_behavior behavior) { - const void **slot, **first, *object, *insert; + const void **slot, **first, *object, *insert, *val_key; unsigned long n_probes; struct ck_hs_map *map; + val_key = ck_hs_apply_key_offset(hs, val); restart: map = hs->map; - slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, key, &object, + slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, val_key, &object, map->probe_limit, behavior); if (slot == NULL && first == NULL) { @@ -840,10 +859,10 @@ ck_hs_put_internal(struct ck_hs *hs, return false; ck_hs_map_bound_set(map, h, n_probes); - insert = ck_hs_marshal(hs->mode, key, h); + insert = ck_hs_marshal(hs->mode, val, h); if (first != NULL) { - /* Insert key into first bucket in probe sequence. */ + /* Insert val into first bucket in probe sequence. */ ck_pr_store_ptr(first, insert); } else { /* An empty slot was found. */ @@ -857,19 +876,19 @@ ck_hs_put_internal(struct ck_hs *hs, bool ck_hs_put(struct ck_hs *hs, unsigned long h, - const void *key) + const void *val) { - return ck_hs_put_internal(hs, h, key, CK_HS_PROBE_INSERT); + return ck_hs_put_internal(hs, h, val, CK_HS_PROBE_INSERT); } bool ck_hs_put_unique(struct ck_hs *hs, unsigned long h, - const void *key) + const void *val) { - return ck_hs_put_internal(hs, h, key, CK_HS_PROBE_TOMBSTONE); + return ck_hs_put_internal(hs, h, val, CK_HS_PROBE_TOMBSTONE); } void * @@ -939,6 +958,60 @@ ck_hs_move(struct ck_hs *hs, return true; } +bool +ck_hs_init_from_options(struct ck_hs *hs, + const struct ck_hs_init_options *options) +{ + struct ck_hs init; + struct ck_hs_init_options opts = CK_HS_INIT_OPTIONS_INITIALIZER; + + /* Validate source options and their size. */ + if (options == NULL) + return false; + if (options->options_size < CK_HS_INIT_OPTIONS_SIZE_V0) + return false; + if (options->options_size > sizeof(struct ck_hs_init_options)) + return false; + + /* + * Copy the source options into our own struct to zero init + * fields not in the source options. + */ + memcpy(&opts, options, options->options_size); + + if (opts.allocator == NULL) + return false; + if (opts.allocator->malloc == NULL) + return false; + if (opts.allocator->free == NULL) + return false; + if (opts.hash_function == NULL) + return false; + if (opts.mode & CK_HS_MODE_OBJECT) { + if (opts.key_offset >= 32768) + return false; + } else { + if (opts.key_offset != 0) + return false; + } + + /* All options are ok. Make the hash set. */ + memset(&init, 0, sizeof init); + + init.m = opts.allocator; + init.mode = opts.mode; + init.mode |= opts.key_offset << CK_HS_MODE_KEY_OFFSET_BITS; + init.seed = opts.seed; + init.hf = opts.hash_function; + init.compare = opts.compare; + init.map = ck_hs_map_create(&init, opts.capacity); + if (init.map == NULL) + return false; + + *hs = init; + return true; +} + bool ck_hs_init(struct ck_hs *hs, unsigned int mode, @@ -948,16 +1021,14 @@ ck_hs_init(struct ck_hs *hs, unsigned long n_entries, unsigned long seed) { + struct ck_hs_init_options opts = CK_HS_INIT_OPTIONS_INITIALIZER; - if (m == NULL || m->malloc == NULL || m->free == NULL || hf == NULL) - return false; - - hs->m = m; - hs->mode = mode; - hs->seed = seed; - hs->hf = hf; - hs->compare = compare; + opts.mode = mode; + opts.hash_function = hf; + opts.compare = compare; + opts.allocator = m; + opts.capacity = n_entries; + opts.seed = seed; - hs->map = ck_hs_map_create(hs, n_entries); - return hs->map != NULL; + return ck_hs_init_from_options(hs, &opts); }