/* * fy-accel.c - YAML accelerated access methods * * Copyright (c) 2019 Pantelis Antoniou * * SPDX-License-Identifier: MIT */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "fy-parse.h" #include "fy-doc.h" #include "fy-accel.h" #include "xxhash.h" /* powers of two and the closest primes before * * pow2: 1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 * prime: 1 2 3 7 13 31 61 127 251 509 1021 2039 4093 8191 16381 32749 65521 * * pow2: 131072 262144 524288 * prime: 130657 262051 524201 */ /* 64K bucket should be enough for everybody */ static const uint32_t prime_lt_pow2[] = { 1, 2, 3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 130657, 262051, 524201 }; static inline unsigned int fy_accel_hash_to_pos(struct fy_accel *xl, const void *hash, unsigned int nbuckets) { uint64_t pos; switch (xl->hd->size) { case 1: pos = *(const uint8_t *)hash; break; case 2: assert(!((uintptr_t)hash & 1)); pos = *(const uint16_t *)hash; break; case 4: assert(!((uintptr_t)hash & 3)); pos = *(const uint32_t *)hash; break; case 8: assert(!((uintptr_t)hash & 7)); pos = *(const uint64_t *)hash; break; default: /* sigh, what ever */ pos = XXH32(hash, xl->hd->size, 0); break; } return (unsigned int)(pos % nbuckets); } static inline bool fy_accel_hash_eq(struct fy_accel *xl, const void *hash1, const void *hash2) { switch (xl->hd->size) { case 1: return *(const uint8_t *)hash1 == *(const uint8_t *)hash2; case 2: assert(!((uintptr_t)hash1 & 1)); assert(!((uintptr_t)hash2 & 1)); return *(const uint16_t *)hash1 == *(const uint16_t *)hash2; case 4: assert(!((uintptr_t)hash1 & 3)); assert(!((uintptr_t)hash2 & 3)); return *(const uint32_t *)hash1 == *(const uint32_t *)hash2; case 8: assert(!((uintptr_t)hash1 & 7)); assert(!((uintptr_t)hash2 & 7)); return *(const uint64_t *)hash1 == *(const uint64_t *)hash2; default: break; } return !memcmp(hash1, hash2, xl->hd->size); } int fy_accel_resize(struct fy_accel *xl, unsigned int min_buckets) { unsigned int next_pow2, exp, i, nbuckets, pos; struct fy_accel_entry_list *xlel; struct fy_accel_entry *xle; struct fy_accel_entry_list *buckets_new; /* get the next power of two larger or equal */ next_pow2 = 1; exp = 0; while (next_pow2 < min_buckets && exp < sizeof(prime_lt_pow2)/sizeof(prime_lt_pow2[0]) - 1) { next_pow2 <<= 1; exp++; } nbuckets = prime_lt_pow2[exp]; if (nbuckets == xl->nbuckets) return 0; buckets_new = malloc(sizeof(*buckets_new) * nbuckets); if (!buckets_new) return -1; for (i = 0, xlel = buckets_new; i < nbuckets; i++, xlel++) fy_accel_entry_list_init(xlel); if (xl->buckets) { for (i = 0, xlel = xl->buckets; i < xl->nbuckets; i++, xlel++) { while ((xle = fy_accel_entry_list_pop(xlel)) != NULL) { pos = fy_accel_hash_to_pos(xl, xle->hash, nbuckets); fy_accel_entry_list_add_tail(&buckets_new[pos], xle); } } free(xl->buckets); } xl->buckets = buckets_new; xl->nbuckets = nbuckets; xl->next_exp2 = exp; return 0; } int fy_accel_grow(struct fy_accel *xl) { unsigned int idx; if (!xl) return -1; /* should not grow indefinetely */ idx = xl->next_exp2 + 1; if (idx >= ARRAY_SIZE(prime_lt_pow2)) return -1; return fy_accel_resize(xl, prime_lt_pow2[idx]); } int fy_accel_shrink(struct fy_accel *xl) { if (!xl) return -1; /* should not shrink indefinetely */ if (xl->next_exp2 <= 0) return -1; return fy_accel_resize(xl, prime_lt_pow2[xl->next_exp2 - 1]); } int fy_accel_setup(struct fy_accel *xl, const struct fy_hash_desc *hd, void *userdata, unsigned int min_buckets) { if (!xl || !hd || !hd->size || !hd->hash) return -1; memset(xl, 0, sizeof(*xl)); xl->hd = hd; xl->userdata = userdata; xl->count = 0; return fy_accel_resize(xl, min_buckets); } void fy_accel_cleanup(struct fy_accel *xl) { unsigned int i; struct fy_accel_entry_list *xlel; struct fy_accel_entry *xle; if (!xl) return; for (i = 0, xlel = xl->buckets; i < xl->nbuckets; i++, xlel++) { while ((xle = fy_accel_entry_list_pop(xlel)) != NULL) { free(xle); assert(xl->count > 0); xl->count--; } } free(xl->buckets); } struct fy_accel_entry * fy_accel_entry_insert(struct fy_accel *xl, const void *key, const void *value) { struct fy_accel_entry *xle, *xlet; struct fy_accel_entry_list *xlel; unsigned int pos, bucket_size; int rc; if (!xl) return NULL; xle = malloc(sizeof(*xle) + xl->hd->size); if (!xle) goto err_out; rc = xl->hd->hash(xl, key, xl->userdata, xle->hash); if (rc) goto err_out; xle->key = key; xle->value = value; pos = fy_accel_hash_to_pos(xl, xle->hash, xl->nbuckets); xlel = &xl->buckets[pos]; fy_accel_entry_list_add_tail(xlel, xle); assert(xl->count < UINT_MAX); xl->count++; /* if we don't auto-resize, return */ if (xl->hd->max_bucket_grow_limit) { bucket_size = 0; for (xlet = fy_accel_entry_list_first(xlel); xlet; xlet = fy_accel_entry_next(xlel, xlet)) { bucket_size++; if (bucket_size >= xl->hd->max_bucket_grow_limit) break; } /* we don't really care whether the grow up succeeds or not */ if (bucket_size >= xl->hd->max_bucket_grow_limit) (void)fy_accel_grow(xl); } return xle; err_out: if (xle) free(xle); return NULL; } struct fy_accel_entry * fy_accel_entry_lookup(struct fy_accel *xl, const void *key) { struct fy_accel_entry_iter xli; struct fy_accel_entry *xle; xle = fy_accel_entry_iter_start(&xli, xl, key); if (xle) fy_accel_entry_iter_finish(&xli); return xle; } struct fy_accel_entry * fy_accel_entry_lookup_key_value(struct fy_accel *xl, const void *key, const void *value) { struct fy_accel_entry_iter xli; struct fy_accel_entry *xle, *xle_start; xle_start = fy_accel_entry_iter_start(&xli, xl, key); if (!xle_start) return NULL; for (xle = xle_start; xle; xle = fy_accel_entry_iter_next(&xli)) { if (xle->value == value) break; } fy_accel_entry_iter_finish(&xli); return xle; } struct fy_accel_entry * fy_accel_entry_lookup_value(struct fy_accel *xl, const void *value) { struct fy_accel_entry_list *xlel; struct fy_accel_entry *xle; unsigned int i; if (!xl) return NULL; for (i = 0, xlel = xl->buckets; i < xl->nbuckets; i++, xlel++) { for (xle = fy_accel_entry_list_first(xlel); xle; xle = fy_accel_entry_next(xlel, xle)) { if (xle->value == value) return xle; } } return NULL; } void fy_accel_entry_remove(struct fy_accel *xl, struct fy_accel_entry *xle) { unsigned int pos; if (!xl || !xle) return; pos = fy_accel_hash_to_pos(xl, xle->hash, xl->nbuckets); fy_accel_entry_list_del(&xl->buckets[pos], xle); assert(xl->count > 0); xl->count--; free(xle); } int fy_accel_insert(struct fy_accel *xl, const void *key, const void *value) { struct fy_accel_entry *xle; xle = fy_accel_entry_lookup(xl, key); if (xle) return -1; /* exists */ xle = fy_accel_entry_insert(xl, key, value); if (!xle) return -1; /* failure to insert */ return 0; } const void * fy_accel_lookup(struct fy_accel *xl, const void *key) { struct fy_accel_entry *xle; xle = fy_accel_entry_lookup(xl, key); return xle ? xle->value : NULL; } int fy_accel_remove(struct fy_accel *xl, const void *data) { struct fy_accel_entry *xle; xle = fy_accel_entry_lookup(xl, data); if (!xle) return -1; fy_accel_entry_remove(xl, xle); return 0; } struct fy_accel_entry * fy_accel_entry_iter_next_internal(struct fy_accel_entry_iter *xli) { struct fy_accel *xl; struct fy_accel_entry *xle; struct fy_accel_entry_list *xlel; const void *key; void *hash; if (!xli) return NULL; xl = xli->xl; hash = xli->hash; xlel = xli->xlel; if (!xl || !hash || !xlel) return NULL; key = xli->key; xle = !xli->xle ? fy_accel_entry_list_first(xlel) : fy_accel_entry_next(xlel, xli->xle); for (; xle; xle = fy_accel_entry_next(xlel, xle)) { if (fy_accel_hash_eq(xl, hash, xle->hash) && xl->hd->eq(xl, hash, xle->key, key, xl->userdata)) break; } return xli->xle = xle; } struct fy_accel_entry * fy_accel_entry_iter_start(struct fy_accel_entry_iter *xli, struct fy_accel *xl, const void *key) { unsigned int pos; int rc; if (!xli || !xl) return NULL; xli->xl = xl; xli->key = key; if (xl->hd->size <= sizeof(xli->hash_inline)) xli->hash = xli->hash_inline; else xli->hash = malloc(xl->hd->size); xli->xlel = NULL; if (!xli->hash) goto err_out; rc = xl->hd->hash(xl, key, xl->userdata, xli->hash); if (rc) goto err_out; pos = fy_accel_hash_to_pos(xl, xli->hash, xl->nbuckets); xli->xlel = &xl->buckets[pos]; xli->xle = NULL; return fy_accel_entry_iter_next_internal(xli); err_out: fy_accel_entry_iter_finish(xli); return NULL; } void fy_accel_entry_iter_finish(struct fy_accel_entry_iter *xli) { if (!xli) return; if (xli->hash && xli->hash != xli->hash_inline) free(xli->hash); } struct fy_accel_entry * fy_accel_entry_iter_next(struct fy_accel_entry_iter *xli) { if (!xli || !xli->xle) return NULL; return fy_accel_entry_iter_next_internal(xli); }