From 820ad0780c32f4d33685eb644c6a31c7d4db4737 Mon Sep 17 00:00:00 2001 From: veqqq <78571825+veqqq@users.noreply.github.com> Date: Mon, 5 Oct 2026 20:34:41 -0600 Subject: [PATCH] Decide once per lookup if janet_equals needed (#1903) As key types can't change during lookup, we only need to check them once. The check is now inlined in util.h, so structs and tables can use the same implementation. (Structs had a simpler copy of the table implementation before, now they just ignore the table specific parts as they lack deleted buckets) Co-authored-by: Alex Alejandre --- src/core/struct.c | 16 +++------------ src/core/table.c | 4 ++-- src/core/util.c | 10 +++++----- src/core/util.h | 51 +++++++++++++++++++++++++++++------------------ 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/src/core/struct.c b/src/core/struct.c index 077e7536..fc5d61f6 100644 --- a/src/core/struct.c +++ b/src/core/struct.c @@ -46,19 +46,9 @@ JanetKV *janet_struct_begin(int32_t count) { return st; } -/* Find an item in a struct without looking for prototypes. Should be similar to janet_dict_find, but - * specialized to structs (slightly more compact). */ -const JanetKV *janet_struct_find(const JanetKV *st, Janet key) { - int32_t cap = janet_struct_capacity(st); - int32_t index = janet_maphash(cap, janet_hash(key)); - int32_t i; - for (i = index; i < cap; i++) - if (janet_checktype(st[i].key, JANET_NIL) || janet_key_equals(st[i].key, key)) - return st + i; - for (i = 0; i < index; i++) - if (janet_checktype(st[i].key, JANET_NIL) || janet_key_equals(st[i].key, key)) - return st + i; - return NULL; +/* Find an item in a struct without looking for prototypes. */ + const JanetKV *janet_struct_find(const JanetKV *st, Janet key) { + return janet_dict_find(st, janet_struct_capacity(st), key); } /* Put a kv pair into a struct that has not yet been fully constructed. diff --git a/src/core/table.c b/src/core/table.c index 2466c458..421392aa 100644 --- a/src/core/table.c +++ b/src/core/table.c @@ -216,8 +216,8 @@ void janet_table_put(JanetTable *t, Janet key, Janet value) { } else { if (NULL == bucket || 2 * (t->count + t->deleted + 1) > t->capacity) { janet_table_rehash(t, janet_tablen(2 * t->count + 2)); + bucket = janet_table_find(t, key); } - bucket = janet_table_find(t, key); if (janet_checktype(bucket->value, JANET_BOOLEAN)) --t->deleted; bucket->key = key; @@ -235,8 +235,8 @@ static void janet_table_put_no_overwrite(JanetTable *t, Janet key, Janet value) return; if (NULL == bucket || 2 * (t->count + t->deleted + 1) > t->capacity) { janet_table_rehash(t, janet_tablen(2 * t->count + 2)); + bucket = janet_table_find(t, key); } - bucket = janet_table_find(t, key); if (janet_checktype(bucket->value, JANET_BOOLEAN)) --t->deleted; bucket->key = key; diff --git a/src/core/util.c b/src/core/util.c index d31007f9..1e6b1ac7 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -287,9 +287,9 @@ void safe_memcpy(void *dest, const void *src, size_t len) { memcpy(dest, src, len); } -/* Helper to find a value in a Janet struct or table. Returns the bucket - * containing the key, or the first empty bucket if there is no such key. */ -const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key) { +/* Helper to find a value in a Janet struct or table like janet_dict_find, for keys + * equaling stored key that is not bitwise identical to it. */ +const JanetKV *janet_dict_find_deep(const JanetKV *buckets, int32_t cap, Janet key) { int32_t index = janet_maphash(cap, janet_hash(key)); int32_t i; const JanetKV *first_bucket = NULL; @@ -302,7 +302,7 @@ const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key) { } else if (NULL == first_bucket) { first_bucket = kv; } - } else if (janet_key_equals(kv->key, key)) { + } else if (janet_bitwise_same(kv->key, key) || janet_equals(kv->key, key)) { return buckets + i; } } @@ -315,7 +315,7 @@ const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key) { } else if (NULL == first_bucket) { first_bucket = kv; } - } else if (janet_key_equals(kv->key, key)) { + } else if (janet_bitwise_same(kv->key, key) || janet_equals(kv->key, key)) { return buckets + i; } } diff --git a/src/core/util.h b/src/core/util.h index 0d95d062..3dcbf25b 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -77,24 +77,6 @@ uint32_t janet_hash_mix(uint32_t input, uint32_t more); #define janet_bitwise_same(x, y) ((x).type == (y).type && janet_u64(x) == janet_u64(y)) #endif -/* Compare a stored key with a lookup key. Most types are equal only when bitwise identical, so - * janet_equals only runs for zero (since 0.0 and -0.0 are equal), strings, tuples, structs and abstract - * types. NaN is never stored as a key, so we skip it. */ -static inline int janet_key_equals(Janet stored, Janet key) { - if (janet_bitwise_same(stored, key)) return 1; - switch (janet_type(key)) { - case JANET_NUMBER: - return janet_unwrap_number(key) == 0 && janet_equals(stored, key); - case JANET_STRING: - case JANET_TUPLE: - case JANET_STRUCT: - case JANET_ABSTRACT: - return janet_equals(stored, key); - default: - return 0; - } -} - int janet_valid_utf8(const uint8_t *str, int32_t len); int janet_is_symbol_char(uint8_t c); @@ -113,7 +95,38 @@ void safe_memcpy(void *dest, const void *src, size_t len); void janet_buffer_push_types(JanetBuffer *buffer, int types); -const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key); +const JanetKV *janet_dict_find_deep(const JanetKV *buckets, int32_t cap, Janet key); + +/* Helper to find a value in a Janet struct or table. Returns the bucket + * containing the key, or the first empty bucket if there is no such key. + * Most types are equal only when bitwise identical, so we can probe them + * without calling janet_equals. Zero (since 0.0 and -0.0 are equal), strings, + * tuples, structs and abstract types go to janet_dict_find_deep, nil too + * as it's bitwise identical to an empty bucket's key. NaN is never + * stored as a key. */ +static inline const JanetKV *janet_dict_find(const JanetKV *buckets, int32_t cap, Janet key) { + int32_t hash; + if (janet_checktype(key, JANET_KEYWORD) || janet_checktype(key, JANET_SYMBOL)) { + hash = janet_string_hash(janet_unwrap_string(key)); + } else if (janet_checktypes(key, JANET_TFLAG_NIL | JANET_TFLAG_STRING | JANET_TFLAG_TUPLE | JANET_TFLAG_STRUCT | JANET_TFLAG_ABSTRACT) || + (janet_checktype(key, JANET_NUMBER) && janet_unwrap_number(key) == 0)) { + return janet_dict_find_deep(buckets, cap, key); + } else { + hash = janet_hash(key); + } + uint32_t mask = (uint32_t) cap - 1; + uint32_t i = (uint32_t) hash & mask; + const JanetKV *first_bucket = NULL; + for (int32_t n = 0; n < cap; n++, i = (i + 1) & mask) { + const JanetKV *kv = buckets + i; + if (janet_bitwise_same(kv->key, key)) return kv; + if (janet_checktype(kv->key, JANET_NIL)) { + if (janet_checktype(kv->value, JANET_NIL)) return kv; + if (NULL == first_bucket) first_bucket = kv; + } + } + return first_bucket; +} void janet_memempty(JanetKV *mem, int32_t count);