Add bitwise key check to skip janet_equals calls (#1894)

* Add bitwise key check to skip janet_equals calls

* Skip janet_equals for identity-comparing keys

Since symbols, keywords, non-0 numbers, bools, tables, buffers, arrays, funcs and fibers are only equal by identity, we now fail them all if the bitwise check fails instead of passing them to `janet_equals`, so lookups only call `janet_equals` for strings, tuples, structs, abstract values and 0

* Skip traversal in janet_equals for non tuples and structs

---------

Co-authored-by: Alex Alejandre <alex@alexalejandre.com>
This commit is contained in:
veqqq
2026-09-30 21:10:12 -05:00
committed by GitHub
co-authored by Alex Alejandre
parent 28f35518ea
commit 1d917e6813
4 changed files with 52 additions and 22 deletions
+2 -2
View File
@@ -53,10 +53,10 @@ const JanetKV *janet_struct_find(const JanetKV *st, Janet key) {
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_equals(st[i].key, key))
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_equals(st[i].key, key))
if (janet_checktype(st[i].key, JANET_NIL) || janet_key_equals(st[i].key, key))
return st + i;
return NULL;
}
+2 -2
View File
@@ -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_equals(kv->key, key)) {
} else if (janet_key_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_equals(kv->key, key)) {
} else if (janet_key_equals(kv->key, key)) {
return buckets + i;
}
}
+26
View File
@@ -69,6 +69,32 @@ uint32_t janet_hash_mix(uint32_t input, uint32_t more);
#define janet_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1))
/* janet_bitwise_same is true when x and y are the same type and bits,
* guarded for the nanboxed implementation */
#if defined(JANET_NANBOX_64) || defined(JANET_NANBOX_32)
#define janet_bitwise_same(x, y) (janet_u64(x) == janet_u64(y))
#else
#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);
+22 -18
View File
@@ -246,27 +246,27 @@ static int janet_compare_abstract(JanetAbstract xx, JanetAbstract yy) {
return xt->compare(xx, yy);
}
int janet_equals(Janet x, Janet y) {
/* Compare two values of the same type that are not tuples or structs */
static int janet_equals_leaf(Janet x, Janet y) {
switch (janet_type(x)) {
case JANET_NUMBER:
return janet_unwrap_number(x) == janet_unwrap_number(y);
case JANET_STRING:
return janet_string_equal(janet_unwrap_string(x), janet_unwrap_string(y));
case JANET_ABSTRACT:
return !janet_compare_abstract(janet_unwrap_abstract(x), janet_unwrap_abstract(y));
default:
return janet_bitwise_same(x, y);
}
}
static int janet_equals_containers(Janet x, Janet y) {
janet_vm.traversal = janet_vm.traversal_base;
do {
if (janet_type(x) != janet_type(y)) return 0;
switch (janet_type(x)) {
case JANET_NIL:
break;
case JANET_BOOLEAN:
if (janet_unwrap_boolean(x) != janet_unwrap_boolean(y)) return 0;
break;
case JANET_NUMBER:
if (janet_unwrap_number(x) != janet_unwrap_number(y)) return 0;
break;
case JANET_STRING:
if (!janet_string_equal(janet_unwrap_string(x), janet_unwrap_string(y))) return 0;
break;
case JANET_ABSTRACT:
if (janet_compare_abstract(janet_unwrap_abstract(x), janet_unwrap_abstract(y))) return 0;
break;
default:
if (janet_unwrap_pointer(x) != janet_unwrap_pointer(y)) return 0;
if (!janet_equals_leaf(x, y)) return 0;
break;
case JANET_TUPLE: {
const Janet *t1 = janet_unwrap_tuple(x);
@@ -278,7 +278,6 @@ int janet_equals(Janet x, Janet y) {
push_traversal_node(janet_tuple_head(t1), janet_tuple_head(t2), 0);
break;
}
break;
case JANET_STRUCT: {
const JanetKV *s1 = janet_unwrap_struct(x);
const JanetKV *s2 = janet_unwrap_struct(y);
@@ -290,12 +289,17 @@ int janet_equals(Janet x, Janet y) {
push_traversal_node(janet_struct_head(s1), janet_struct_head(s2), 0);
break;
}
break;
}
} while (!traversal_next(&x, &y));
return 1;
}
int janet_equals(Janet x, Janet y) {
if (janet_type(x) != janet_type(y)) return 0;
if (janet_checktypes(x, JANET_TFLAG_TUPLE | JANET_TFLAG_STRUCT)) return janet_equals_containers(x, y);
return janet_equals_leaf(x, y);
}
static uint64_t murmur64(uint64_t h) {
h ^= h >> 33;
h *= 0xff51afd7ed558ccdUL;