/* * Copyright 2012-2015 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 copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * 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 #include "ck_internal.h" #ifndef CK_HS_PROBE_L1_SHIFT #define CK_HS_PROBE_L1_SHIFT 3ULL #endif /* CK_HS_PROBE_L1_SHIFT */ #define CK_HS_PROBE_L1 (1 << CK_HS_PROBE_L1_SHIFT) #define CK_HS_PROBE_L1_MASK (CK_HS_PROBE_L1 - 1) #ifndef CK_HS_PROBE_L1_DEFAULT #define CK_HS_PROBE_L1_DEFAULT CK_MD_CACHELINE #endif #define CK_HS_VMA_MASK ((uintptr_t)((1ULL << CK_MD_VMA_BITS) - 1)) #define CK_HS_VMA(x) \ ((void *)((uintptr_t)(x) & CK_HS_VMA_MASK)) #define CK_HS_EMPTY NULL #define CK_HS_TOMBSTONE ((void *)~(uintptr_t)0) #define CK_HS_G (2) #define CK_HS_G_MASK (CK_HS_G - 1) #if defined(CK_F_PR_LOAD_8) && defined(CK_F_PR_STORE_8) #define CK_HS_WORD uint8_t #define CK_HS_WORD_MAX UINT8_MAX #define CK_HS_STORE(x, y) ck_pr_store_8(x, y) #define CK_HS_LOAD(x) ck_pr_load_8(x) #elif defined(CK_F_PR_LOAD_16) && defined(CK_F_PR_STORE_16) #define CK_HS_WORD uint16_t #define CK_HS_WORD_MAX UINT16_MAX #define CK_HS_STORE(x, y) ck_pr_store_16(x, y) #define CK_HS_LOAD(x) ck_pr_load_16(x) #elif defined(CK_F_PR_LOAD_32) && defined(CK_F_PR_STORE_32) #define CK_HS_WORD uint32_t #define CK_HS_WORD_MAX UINT32_MAX #define CK_HS_STORE(x, y) ck_pr_store_32(x, y) #define CK_HS_LOAD(x) ck_pr_load_32(x) #else #error "ck_hs is not supported on your platform." #endif enum ck_hs_probe_behavior { CK_HS_PROBE = 0, /* Default behavior. */ CK_HS_PROBE_TOMBSTONE, /* Short-circuit on tombstone. */ CK_HS_PROBE_INSERT /* Short-circuit on probe bound if tombstone found. */ }; struct ck_hs_map { unsigned int generation[CK_HS_G]; unsigned int probe_maximum; unsigned long mask; unsigned long step; unsigned int probe_limit; unsigned int tombstones; unsigned long n_entries; unsigned long capacity; unsigned long size; CK_HS_WORD *probe_bound; const void **entries; }; static inline void ck_hs_map_signal(struct ck_hs_map *map, unsigned long h) { h &= CK_HS_G_MASK; ck_pr_store_uint(&map->generation[h], map->generation[h] + 1); ck_pr_fence_store(); return; } static bool _ck_hs_next(struct ck_hs *hs, struct ck_hs_map *map, struct ck_hs_iterator *i, void **key) { void *value; if (i->offset >= map->capacity) return false; do { value = CK_CC_DECONST_PTR(map->entries[i->offset]); if (value != CK_HS_EMPTY && value != CK_HS_TOMBSTONE) { #ifdef CK_HS_PP if (hs->mode & CK_HS_MODE_OBJECT) value = CK_HS_VMA(value); #else (void)hs; /* Avoid unused parameter warning. */ #endif i->offset++; *key = value; return true; } } while (++i->offset < map->capacity); return false; } void ck_hs_iterator_init(struct ck_hs_iterator *iterator) { iterator->cursor = NULL; iterator->offset = 0; iterator->map = NULL; return; } bool ck_hs_next(struct ck_hs *hs, struct ck_hs_iterator *i, void **key) { return _ck_hs_next(hs, hs->map, i, key); } bool ck_hs_next_spmc(struct ck_hs *hs, struct ck_hs_iterator *i, void **key) { struct ck_hs_map *m = i->map; if (m == NULL) { m = i->map = ck_pr_load_ptr(&hs->map); } return _ck_hs_next(hs, m, i, key); } void ck_hs_stat(struct ck_hs *hs, struct ck_hs_stat *st) { struct ck_hs_map *map = hs->map; st->n_entries = map->n_entries; st->tombstones = map->tombstones; st->probe_maximum = map->probe_maximum; return; } unsigned long ck_hs_count(struct ck_hs *hs) { return hs->map->n_entries; } static void ck_hs_map_destroy(struct ck_malloc *m, struct ck_hs_map *map, bool defer) { m->free(map, map->size, defer); return; } void ck_hs_destroy(struct ck_hs *hs) { ck_hs_map_destroy(hs->m, hs->map, false); return; } static struct ck_hs_map * ck_hs_map_create(struct ck_hs *hs, unsigned long entries) { struct ck_hs_map *map; unsigned long size, n_entries, prefix, limit; n_entries = ck_internal_power_2(entries); if (n_entries < CK_HS_PROBE_L1) n_entries = CK_HS_PROBE_L1; size = sizeof(struct ck_hs_map) + (sizeof(void *) * n_entries + CK_MD_CACHELINE - 1); if (hs->mode & CK_HS_MODE_DELETE) { prefix = sizeof(CK_HS_WORD) * n_entries; size += prefix; } else { prefix = 0; } map = hs->m->malloc(size); if (map == NULL) return NULL; map->size = size; /* We should probably use a more intelligent heuristic for default probe length. */ limit = ck_internal_max(n_entries >> (CK_HS_PROBE_L1_SHIFT + 2), CK_HS_PROBE_L1_DEFAULT); if (limit > UINT_MAX) limit = UINT_MAX; map->probe_limit = (unsigned int)limit; map->probe_maximum = 0; map->capacity = n_entries; map->step = ck_cc_ffsl(n_entries); map->mask = n_entries - 1; map->n_entries = 0; /* Align map allocation to cache line. */ map->entries = (void *)(((uintptr_t)&map[1] + prefix + CK_MD_CACHELINE - 1) & ~(CK_MD_CACHELINE - 1)); memset(map->entries, 0, sizeof(void *) * n_entries); memset(map->generation, 0, sizeof map->generation); if (hs->mode & CK_HS_MODE_DELETE) { map->probe_bound = (CK_HS_WORD *)&map[1]; memset(map->probe_bound, 0, prefix); } else { map->probe_bound = NULL; } /* Commit entries purge with respect to map publication. */ ck_pr_fence_store(); return map; } bool ck_hs_reset_size(struct ck_hs *hs, unsigned long capacity) { struct ck_hs_map *map, *previous; previous = hs->map; map = ck_hs_map_create(hs, capacity); if (map == NULL) return false; ck_pr_store_ptr(&hs->map, map); ck_hs_map_destroy(hs->m, previous, true); return true; } bool ck_hs_reset(struct ck_hs *hs) { struct ck_hs_map *previous; previous = hs->map; return ck_hs_reset_size(hs, previous->capacity); } static inline unsigned long ck_hs_map_probe_next(struct ck_hs_map *map, unsigned long offset, unsigned long h, unsigned long level, unsigned long probes) { unsigned long r, stride; r = (h >> map->step) >> level; stride = (r & ~CK_HS_PROBE_L1_MASK) << 1 | (r & CK_HS_PROBE_L1_MASK); return (offset + (probes >> CK_HS_PROBE_L1_SHIFT) + (stride | CK_HS_PROBE_L1)) & map->mask; } static inline void ck_hs_map_bound_set(struct ck_hs_map *m, unsigned long h, unsigned long n_probes) { unsigned long offset = h & m->mask; if (n_probes > m->probe_maximum) ck_pr_store_uint(&m->probe_maximum, n_probes); if (m->probe_bound != NULL && m->probe_bound[offset] < n_probes) { if (n_probes > CK_HS_WORD_MAX) n_probes = CK_HS_WORD_MAX; CK_HS_STORE(&m->probe_bound[offset], n_probes); ck_pr_fence_store(); } return; } static inline unsigned int ck_hs_map_bound_get(struct ck_hs_map *m, unsigned long h) { unsigned long offset = h & m->mask; unsigned int r = CK_HS_WORD_MAX; if (m->probe_bound != NULL) { r = CK_HS_LOAD(&m->probe_bound[offset]); if (r == CK_HS_WORD_MAX) r = ck_pr_load_uint(&m->probe_maximum); } else { r = ck_pr_load_uint(&m->probe_maximum); } return r; } bool ck_hs_grow(struct ck_hs *hs, unsigned long capacity) { struct ck_hs_map *map, *update; unsigned long k, i, j, offset, probes; const void *previous, **bucket; restart: map = hs->map; if (map->capacity > capacity) return false; update = ck_hs_map_create(hs, capacity); if (update == NULL) return false; for (k = 0; k < map->capacity; k++) { unsigned long h; previous = map->entries[k]; if (previous == CK_HS_EMPTY || previous == CK_HS_TOMBSTONE) continue; #ifdef CK_HS_PP if (hs->mode & CK_HS_MODE_OBJECT) previous = CK_HS_VMA(previous); #endif h = hs->hf(previous, hs->seed); offset = h & update->mask; i = probes = 0; for (;;) { bucket = (const void **)((uintptr_t)&update->entries[offset] & ~(CK_MD_CACHELINE - 1)); for (j = 0; j < CK_HS_PROBE_L1; j++) { const void **cursor = bucket + ((j + offset) & (CK_HS_PROBE_L1 - 1)); if (probes++ == update->probe_limit) break; if (CK_CC_LIKELY(*cursor == CK_HS_EMPTY)) { *cursor = map->entries[k]; update->n_entries++; ck_hs_map_bound_set(update, h, probes); break; } } if (j < CK_HS_PROBE_L1) break; offset = ck_hs_map_probe_next(update, offset, h, i++, probes); } if (probes > update->probe_limit) { /* * We have hit the probe limit, map needs to be even larger. */ ck_hs_map_destroy(hs->m, update, false); capacity <<= 1; goto restart; } } ck_pr_fence_store(); ck_pr_store_ptr(&hs->map, update); ck_hs_map_destroy(hs->m, map, true); return true; } static void ck_hs_map_postinsert(struct ck_hs *hs, struct ck_hs_map *map) { map->n_entries++; if ((map->n_entries << 1) > map->capacity) ck_hs_grow(hs, map->capacity << 1); return; } bool ck_hs_rebuild(struct ck_hs *hs) { return ck_hs_grow(hs, hs->map->capacity); } static const void ** ck_hs_map_probe(struct ck_hs *hs, struct ck_hs_map *map, unsigned long *n_probes, const void ***priority, unsigned long h, const void *key, const void **object, unsigned long probe_limit, enum ck_hs_probe_behavior behavior) { const void **bucket, **cursor, *k, *compare; const void **pr = NULL; unsigned long offset, j, i, probes, opl; #ifdef CK_HS_PP /* If we are storing object pointers, then we may leverage pointer packing. */ unsigned long hv = 0; if (hs->mode & CK_HS_MODE_OBJECT) { hv = (h >> 25) & CK_HS_KEY_MASK; compare = CK_HS_VMA(key); } else { compare = key; } #else compare = key; #endif offset = h & map->mask; *object = NULL; i = probes = 0; opl = probe_limit; if (behavior == CK_HS_PROBE_INSERT) probe_limit = ck_hs_map_bound_get(map, h); for (;;) { bucket = (const void **)((uintptr_t)&map->entries[offset] & ~(CK_MD_CACHELINE - 1)); for (j = 0; j < CK_HS_PROBE_L1; j++) { cursor = bucket + ((j + offset) & (CK_HS_PROBE_L1 - 1)); if (probes++ == probe_limit) { if (probe_limit == opl || pr != NULL) { k = CK_HS_EMPTY; goto leave; } /* * If no eligible slot has been found yet, continue probe * sequence with original probe limit. */ probe_limit = opl; } k = ck_pr_load_ptr(cursor); if (k == CK_HS_EMPTY) goto leave; if (k == CK_HS_TOMBSTONE) { if (pr == NULL) { pr = cursor; *n_probes = probes; if (behavior == CK_HS_PROBE_TOMBSTONE) { k = CK_HS_EMPTY; goto leave; } } continue; } #ifdef CK_HS_PP if (hs->mode & CK_HS_MODE_OBJECT) { if (((uintptr_t)k >> CK_MD_VMA_BITS) != hv) continue; k = CK_HS_VMA(k); } #endif if (k == compare) goto leave; if (hs->compare == NULL) continue; if (hs->compare(k, key) == true) goto leave; } offset = ck_hs_map_probe_next(map, offset, h, i++, probes); } leave: if (probes > probe_limit) { cursor = NULL; } else { *object = k; } if (pr == NULL) *n_probes = probes; *priority = pr; return cursor; } static inline const void * ck_hs_marshal(unsigned int mode, const void *key, unsigned long h) { #ifdef CK_HS_PP const void *insert; if (mode & CK_HS_MODE_OBJECT) { insert = (void *)((uintptr_t)CK_HS_VMA(key) | ((h >> 25) << CK_MD_VMA_BITS)); } else { insert = key; } return insert; #else (void)mode; (void)h; return key; #endif } bool ck_hs_gc(struct ck_hs *hs, unsigned long cycles, unsigned long seed) { unsigned long size = 0; unsigned long i; struct ck_hs_map *map = hs->map; unsigned int maximum; CK_HS_WORD *bounds = NULL; if (map->n_entries == 0) { ck_pr_store_uint(&map->probe_maximum, 0); if (map->probe_bound != NULL) memset(map->probe_bound, 0, sizeof(CK_HS_WORD) * map->capacity); return true; } if (cycles == 0) { maximum = 0; if (map->probe_bound != NULL) { size = sizeof(CK_HS_WORD) * map->capacity; bounds = hs->m->malloc(size); if (bounds == NULL) return false; memset(bounds, 0, size); } } else { maximum = map->probe_maximum; } for (i = 0; i < map->capacity; i++) { const void **first, *object, **slot, *entry; unsigned long n_probes, offset, h; entry = map->entries[(i + seed) & map->mask]; if (entry == CK_HS_EMPTY || entry == CK_HS_TOMBSTONE) continue; #ifdef CK_HS_PP if (hs->mode & CK_HS_MODE_OBJECT) entry = CK_HS_VMA(entry); #endif h = hs->hf(entry, hs->seed); offset = h & map->mask; slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, entry, &object, ck_hs_map_bound_get(map, h), CK_HS_PROBE); if (first != NULL) { const void *insert = ck_hs_marshal(hs->mode, entry, h); ck_pr_store_ptr(first, insert); ck_hs_map_signal(map, h); ck_pr_store_ptr(slot, CK_HS_TOMBSTONE); } if (cycles == 0) { if (n_probes > maximum) maximum = n_probes; if (n_probes > CK_HS_WORD_MAX) n_probes = CK_HS_WORD_MAX; if (bounds != NULL && n_probes > bounds[offset]) bounds[offset] = n_probes; } else if (--cycles == 0) break; } /* * The following only apply to garbage collection involving * a full scan of all entries. */ if (maximum != map->probe_maximum) ck_pr_store_uint(&map->probe_maximum, maximum); if (bounds != NULL) { for (i = 0; i < map->capacity; i++) CK_HS_STORE(&map->probe_bound[i], bounds[i]); hs->m->free(bounds, size, false); } return true; } bool ck_hs_fas(struct ck_hs *hs, unsigned long h, const void *key, void **previous) { const void **slot, **first, *object, *insert; 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, 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); if (first != NULL) { ck_pr_store_ptr(first, insert); ck_hs_map_signal(map, h); ck_pr_store_ptr(slot, CK_HS_TOMBSTONE); } else { ck_pr_store_ptr(slot, insert); } *previous = CK_CC_DECONST_PTR(object); return true; } /* * An apply function takes two arguments. The first argument is a pointer to a * pre-existing object. The second argument is a pointer to the fifth argument * passed to ck_hs_apply. If a non-NULL pointer is passed to the first argument * and the return value of the apply function is NULL, then the pre-existing * value is deleted. If the return pointer is the same as the one passed to the * apply function then no changes are made to the hash table. If the first * argument is non-NULL and the return pointer is different than that passed to * the apply function, then the pre-existing value is replaced. For * replacement, it is required that the value itself is identical to the * previous value. */ bool ck_hs_apply(struct ck_hs *hs, unsigned long h, const void *key, ck_hs_apply_fn_t *fn, void *cl) { const void **slot, **first, *object, *delta, *insert; unsigned long n_probes; struct ck_hs_map *map; restart: map = hs->map; slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, 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; goto restart; } delta = fn(CK_CC_DECONST_PTR(object), cl); if (delta == NULL) { /* * The apply function has requested deletion. If the object doesn't exist, * then exit early. */ if (CK_CC_UNLIKELY(object == NULL)) return true; /* Otherwise, mark slot as deleted. */ ck_pr_store_ptr(slot, CK_HS_TOMBSTONE); map->n_entries--; map->tombstones++; return true; } /* The apply function has not requested hash set modification so exit early. */ if (delta == object) return true; /* A modification or insertion has been requested. */ ck_hs_map_bound_set(map, h, n_probes); insert = ck_hs_marshal(hs->mode, delta, h); if (first != NULL) { /* * This follows the same semantics as ck_hs_set, please refer to that * function for documentation. */ ck_pr_store_ptr(first, insert); if (object != NULL) { ck_hs_map_signal(map, h); ck_pr_store_ptr(slot, CK_HS_TOMBSTONE); } } else { /* * If we are storing into same slot, then atomic store is sufficient * for replacement. */ ck_pr_store_ptr(slot, insert); } if (object == NULL) ck_hs_map_postinsert(hs, map); return true; } bool ck_hs_set(struct ck_hs *hs, unsigned long h, const void *key, void **previous) { const void **slot, **first, *object, *insert; unsigned long n_probes; struct ck_hs_map *map; *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); if (slot == NULL && first == NULL) { if (ck_hs_grow(hs, map->capacity << 1) == false) return false; goto restart; } ck_hs_map_bound_set(map, h, n_probes); insert = ck_hs_marshal(hs->mode, key, h); if (first != NULL) { /* If an earlier bucket was found, then store entry there. */ ck_pr_store_ptr(first, insert); /* * If a duplicate key was found, then delete it after * signaling concurrent probes to restart. Optionally, * it is possible to install tombstone after grace * period if we can guarantee earlier position of * duplicate key. */ if (object != NULL) { ck_hs_map_signal(map, h); ck_pr_store_ptr(slot, CK_HS_TOMBSTONE); } } else { /* * If we are storing into same slot, then atomic store is sufficient * for replacement. */ ck_pr_store_ptr(slot, insert); } if (object == NULL) ck_hs_map_postinsert(hs, map); *previous = CK_CC_DECONST_PTR(object); return true; } CK_CC_INLINE static bool ck_hs_put_internal(struct ck_hs *hs, unsigned long h, const void *key, enum ck_hs_probe_behavior behavior) { const void **slot, **first, *object, *insert; unsigned long n_probes; struct ck_hs_map *map; restart: map = hs->map; slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, key, &object, map->probe_limit, behavior); if (slot == NULL && first == NULL) { if (ck_hs_grow(hs, map->capacity << 1) == false) return false; goto restart; } /* Fail operation if a match was found. */ if (object != NULL) return false; ck_hs_map_bound_set(map, h, n_probes); insert = ck_hs_marshal(hs->mode, key, h); if (first != NULL) { /* Insert key into first bucket in probe sequence. */ ck_pr_store_ptr(first, insert); } else { /* An empty slot was found. */ ck_pr_store_ptr(slot, insert); } ck_hs_map_postinsert(hs, map); return true; } bool ck_hs_put(struct ck_hs *hs, unsigned long h, const void *key) { return ck_hs_put_internal(hs, h, key, CK_HS_PROBE_INSERT); } bool ck_hs_put_unique(struct ck_hs *hs, unsigned long h, const void *key) { return ck_hs_put_internal(hs, h, key, CK_HS_PROBE_TOMBSTONE); } void * ck_hs_get(struct ck_hs *hs, unsigned long h, const void *key) { const void **first, *object; struct ck_hs_map *map; unsigned long n_probes; unsigned int g, g_p, probe; unsigned int *generation; do { map = ck_pr_load_ptr(&hs->map); generation = &map->generation[h & CK_HS_G_MASK]; g = ck_pr_load_uint(generation); probe = ck_hs_map_bound_get(map, h); ck_pr_fence_load(); ck_hs_map_probe(hs, map, &n_probes, &first, h, key, &object, probe, CK_HS_PROBE); ck_pr_fence_load(); g_p = ck_pr_load_uint(generation); } while (g != g_p); return CK_CC_DECONST_PTR(object); } void * ck_hs_remove(struct ck_hs *hs, unsigned long h, const void *key) { const void **slot, **first, *object; struct ck_hs_map *map = hs->map; unsigned long n_probes; slot = ck_hs_map_probe(hs, map, &n_probes, &first, h, key, &object, ck_hs_map_bound_get(map, h), CK_HS_PROBE); if (object == NULL) return NULL; ck_pr_store_ptr(slot, CK_HS_TOMBSTONE); map->n_entries--; map->tombstones++; return CK_CC_DECONST_PTR(object); } bool ck_hs_move(struct ck_hs *hs, struct ck_hs *source, ck_hs_hash_cb_t *hf, ck_hs_compare_cb_t *compare, struct ck_malloc *m) { if (m == NULL || m->malloc == NULL || m->free == NULL || hf == NULL) return false; hs->mode = source->mode; hs->seed = source->seed; hs->map = source->map; hs->m = m; hs->hf = hf; hs->compare = compare; return true; } bool ck_hs_init(struct ck_hs *hs, unsigned int mode, ck_hs_hash_cb_t *hf, ck_hs_compare_cb_t *compare, struct ck_malloc *m, unsigned long n_entries, unsigned long seed) { 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; hs->map = ck_hs_map_create(hs, n_entries); return hs->map != NULL; }