From 2598819241d1abbea0020681f881c4918244ed47 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Sun, 22 Feb 2026 09:36:12 +0800 Subject: [PATCH 1/3] Hashtable: Extend OEISprimes[] to up to (2^64 - 59) Signed-off-by: Kang-Che Sung --- Hashtable.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Hashtable.c b/Hashtable.c index 92b2c0387..7d1d334e0 100644 --- a/Hashtable.c +++ b/Hashtable.c @@ -97,9 +97,25 @@ static const uint64_t OEISprimes[] = { 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, 134217689, 268435399, 536870909, 1073741789, 2147483647, 4294967291, -#endif #if SIZE_MAX > UINT32_MAX - 8589934583, 17179869143, 34359738337, 68719476731, 137438953447, + /* https://oeis.org/A013603 */ + ((uint64_t)1 << 33) - 9, ((uint64_t)1 << 34) - 41, + ((uint64_t)1 << 35) - 31, ((uint64_t)1 << 36) - 5, + ((uint64_t)1 << 37) - 25, ((uint64_t)1 << 38) - 45, + ((uint64_t)1 << 39) - 7, ((uint64_t)1 << 40) - 87, + ((uint64_t)1 << 41) - 21, ((uint64_t)1 << 42) - 11, + ((uint64_t)1 << 43) - 57, ((uint64_t)1 << 44) - 17, + ((uint64_t)1 << 45) - 55, ((uint64_t)1 << 46) - 21, + ((uint64_t)1 << 47) - 115, ((uint64_t)1 << 48) - 59, + ((uint64_t)1 << 49) - 81, ((uint64_t)1 << 50) - 27, + ((uint64_t)1 << 51) - 129, ((uint64_t)1 << 52) - 47, + ((uint64_t)1 << 53) - 111, ((uint64_t)1 << 54) - 33, + ((uint64_t)1 << 55) - 55, ((uint64_t)1 << 56) - 5, + ((uint64_t)1 << 57) - 13, ((uint64_t)1 << 58) - 27, + ((uint64_t)1 << 59) - 55, ((uint64_t)1 << 60) - 93, + ((uint64_t)1 << 61) - 1, ((uint64_t)1 << 62) - 57, + ((uint64_t)1 << 63) - 25, (uint64_t)-59, +#endif #endif }; From 225d30108520ab12a785518c36839bb8adf323d1 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Sun, 22 Feb 2026 09:40:37 +0800 Subject: [PATCH 2/3] Improve Hashtable buffer grow and shrink conditions * Move assertions about hash table sizes to Hashtable_isConsistent() so they can be checked in all Hashtable methods. * Slightly improve conditionals of growing and shrinking the "buckets" buffer. Specifically the calculations are now less prone to arithmetic overflow and can work with Hashtable.size value up to (SIZE_MAX / 7). (Original limit was (SIZE_MAX / 10)). * If `Hashtable.size > SIZE_MAX / sizeof(HashtableItem)`, allow the compiler to optimize out one conditional of checking overflow. (The buffer allocation would still fail at xCalloc() in that case.) * Hashtable_setSize() is now a private method. Signed-off-by: Kang-Che Sung --- Hashtable.c | 38 ++++++++++++++++++++------------------ Hashtable.h | 2 -- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Hashtable.c b/Hashtable.c index 7d1d334e0..5f65ce2c7 100644 --- a/Hashtable.c +++ b/Hashtable.c @@ -73,6 +73,11 @@ static bool Hashtable_isConsistent(const Hashtable* this) { bool res = items == this->items; if (!res) Hashtable_dump(this); + + assert(this->size > 0); + assert(this->size <= SIZE_MAX / sizeof(HashtableItem)); + assert(this->size >= this->items); + return res; } @@ -208,21 +213,18 @@ static void insert(Hashtable* this, ht_key_t key, void* value) { } } -void Hashtable_setSize(Hashtable* this, size_t size) { - +static void Hashtable_setSize(Hashtable* this, size_t size) { assert(Hashtable_isConsistent(this)); + assert(size >= this->items); - if (size <= this->items) - return; - - size_t newSize = nextPrime(size); - if (newSize == this->size) + size = nextPrime(size); + if (size == this->size) return; HashtableItem* oldBuckets = this->buckets; size_t oldSize = this->size; - this->size = newSize; + this->size = size; this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem)); this->items = 0; @@ -240,24 +242,20 @@ void Hashtable_setSize(Hashtable* this, size_t size) { } void Hashtable_put(Hashtable* this, ht_key_t key, void* value) { - assert(Hashtable_isConsistent(this)); - assert(this->size > 0); assert(value); /* grow on load-factor > 0.7 */ - if (10 * this->items > 7 * this->size) { - if (SIZE_MAX / 2 < this->size) - CRT_fatalError("Hashtable: size overflow"); + if (sizeof(HashtableItem) < 7 && SIZE_MAX / 7 < this->size) + CRT_fatalError("Hashtable: size overflow"); - Hashtable_setSize(this, 2 * this->size); - } + if (this->items >= this->size * 7 / 10) + Hashtable_setSize(this, this->size + 1); insert(this, key, value); assert(Hashtable_isConsistent(this)); assert(Hashtable_get(this, key) != NULL); - assert(this->size > this->items); } void* Hashtable_remove(Hashtable* this, ht_key_t key) { @@ -309,8 +307,12 @@ void* Hashtable_remove(Hashtable* this, ht_key_t key) { assert(Hashtable_get(this, key) == NULL); /* shrink on load-factor < 0.125 */ - if (8 * this->items < this->size) - Hashtable_setSize(this, this->size / 3); /* account for nextPrime rounding up */ + if (sizeof(HashtableItem) < 3 && SIZE_MAX / 3 < this->size) + CRT_fatalError("Hashtable: size overflow"); + + if (this->items < this->size / 8) { + Hashtable_setSize(this, this->size * 3 / 8); /* account for nextPrime rounding up */ + } return res; } diff --git a/Hashtable.h b/Hashtable.h index f51a29476..e92845954 100644 --- a/Hashtable.h +++ b/Hashtable.h @@ -29,8 +29,6 @@ void Hashtable_delete(Hashtable* this); void Hashtable_clear(Hashtable* this); -void Hashtable_setSize(Hashtable* this, size_t size); - void Hashtable_put(Hashtable* this, ht_key_t key, void* value); void* Hashtable_remove(Hashtable* this, ht_key_t key); From 7f4fa7ffe928aaa2ff51fb9de075354486a0cc7c Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Sun, 22 Feb 2026 09:45:27 +0800 Subject: [PATCH 3/3] Hashtable: Shrink nextPrime() lookup table size The lookup table now codes the difference between 2^n to the nearest prime not greater than 2^n (i.e. https://oeis.org/A013603 ). With the change of the lookup table, (2^64 - 59) has been removed. It is believed that such removal won't cause practical problems as the number is very close to SIZE_MAX and a system is unlikely to succeed in allocating a memory block _that_ huge. Signed-off-by: Kang-Che Sung --- Hashtable.c | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/Hashtable.c b/Hashtable.c index 5f65ce2c7..ce20278fa 100644 --- a/Hashtable.c +++ b/Hashtable.c @@ -10,6 +10,7 @@ in the source distribution for its full text. #include "Hashtable.h" #include +#include #include #include #include @@ -93,42 +94,26 @@ size_t Hashtable_count(const Hashtable* this) { #endif /* NDEBUG */ -/* https://oeis.org/A014234 */ -static const uint64_t OEISprimes[] = { - 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, - 16381, 32749, 65521, +static size_t nextPrime(size_t n) { + // Table of differences so that (2^m - primeDiffs[m]) is a prime. + // This is OEIS sequence https://oeis.org/A013603 except for + // entry 0 (2^0 = 1 as a non-prime special case). + static const uint8_t primeDiffs[] = { + 0, 0, 1, 1, 3, 1, 3, 1, 5, 3, 3, 9, 3, 1, 3, 19, #if SIZE_MAX > UINT16_MAX - 131071, 262139, 524287, 1048573, - 2097143, 4194301, 8388593, 16777213, 33554393, - 67108859, 134217689, 268435399, 536870909, 1073741789, - 2147483647, 4294967291, + 15, 1, 5, 1, 3, 9, 3, 15, 3, 39, 5, 39, 57, 3, 35, 1, #if SIZE_MAX > UINT32_MAX - /* https://oeis.org/A013603 */ - ((uint64_t)1 << 33) - 9, ((uint64_t)1 << 34) - 41, - ((uint64_t)1 << 35) - 31, ((uint64_t)1 << 36) - 5, - ((uint64_t)1 << 37) - 25, ((uint64_t)1 << 38) - 45, - ((uint64_t)1 << 39) - 7, ((uint64_t)1 << 40) - 87, - ((uint64_t)1 << 41) - 21, ((uint64_t)1 << 42) - 11, - ((uint64_t)1 << 43) - 57, ((uint64_t)1 << 44) - 17, - ((uint64_t)1 << 45) - 55, ((uint64_t)1 << 46) - 21, - ((uint64_t)1 << 47) - 115, ((uint64_t)1 << 48) - 59, - ((uint64_t)1 << 49) - 81, ((uint64_t)1 << 50) - 27, - ((uint64_t)1 << 51) - 129, ((uint64_t)1 << 52) - 47, - ((uint64_t)1 << 53) - 111, ((uint64_t)1 << 54) - 33, - ((uint64_t)1 << 55) - 55, ((uint64_t)1 << 56) - 5, - ((uint64_t)1 << 57) - 13, ((uint64_t)1 << 58) - 27, - ((uint64_t)1 << 59) - 55, ((uint64_t)1 << 60) - 93, - ((uint64_t)1 << 61) - 1, ((uint64_t)1 << 62) - 57, - ((uint64_t)1 << 63) - 25, (uint64_t)-59, + 5, 9, 41, 31, 5, 25, 45, 7, 87, 21, 11, 57, 17, 55, 21, 115, + 59, 81, 27, 129, 47, 111, 33, 55, 5, 13, 27, 55, 93, 1, 57, 25, #endif #endif -}; + }; -static size_t nextPrime(size_t n) { - /* on 32-bit make sure we do not return primes not fitting in size_t */ - for (size_t i = 0; i < ARRAYSIZE(OEISprimes); i++) { - if (n <= OEISprimes[i]) { - return OEISprimes[i]; + assert(sizeof(n) * CHAR_BIT <= ARRAYSIZE(primeDiffs)); + for (uint8_t shift = 3; shift < sizeof(n) * CHAR_BIT; shift++) { + size_t prime = ((size_t)1 << shift) - primeDiffs[shift]; + if (n <= prime) { + return prime; } }