Compile untyped Map and Set registries without a JavaScript engine

- Store checked values in native collections while preserving supported object identities and JavaScript key equality.
- Support collection iteration and reject reference conversions that would copy or adapt stored objects.
This commit is contained in:
Chris Tate
2026-09-28 01:12:29 -05:00
committed by GitHub
parent ec2ebb6ef4
commit f52b3b5b73
30 changed files with 392 additions and 35 deletions
+19
View File
@@ -1244,6 +1244,9 @@ ScrArr *scr_arr_flat_copy(const ScrArr *a, ScrArr *out, bool flatten) {
static bool scr_arr_ref_eq(const ScrArr *a, uint64_t slot, void *v) {
void *p = scr_slot_to_ptr(slot);
if (a->elem == SCR_ELEM_STR) return scr_str_eq((ScrStr *)p, (ScrStr *)v);
if (a->elem == SCR_ELEM_REF && a->elem_retain == scr_dyn_retain_v) {
return scr_dyn_strict_eq((ScrDyn *)p, (ScrDyn *)v);
}
return p == v;
}
@@ -1296,6 +1299,22 @@ bool scr_arr_includes_bool(ScrArr *a, bool v) {
}
bool scr_arr_includes_ref(ScrArr *a, void *v) {
if (a->elem == SCR_ELEM_REF && a->elem_retain == scr_dyn_retain_v) {
const ScrDyn *needle = (const ScrDyn *)v;
if (needle->kind == SCR_DYN_NUM && needle->v.num != needle->v.num) {
for (size_t i = 0; i < a->cap && i < a->len; i++) {
if (a->present[i] != SCR_ARR_VALUE) continue;
const ScrDyn *d = (const ScrDyn *)scr_slot_to_ptr(a->data[i]);
if (d->kind == SCR_DYN_NUM && d->v.num != d->v.num) return true;
}
for (size_t i = 0; i < a->sparse_len; i++) {
if (a->sparse[i].state != SCR_ARR_VALUE) continue;
const ScrDyn *d = (const ScrDyn *)scr_slot_to_ptr(a->sparse[i].slot);
if (d->kind == SCR_DYN_NUM && d->v.num != d->v.num) return true;
}
return false;
}
}
return scr_arr_index_of_ref(a, v) >= 0;
}
+5 -1
View File
@@ -549,7 +549,11 @@ static ScrDyn *scr_dyn_invoke_impl(
return scr_dyn_new_num(-1);
}
for (size_t i = 0; i < len; i++) {
if (scr_dyn_strict_eq(recv->v.arr.items[i], needle)) {
const ScrDyn *item = recv->v.arr.items[i];
bool nan_match = dyn_name_is(method, "includes") &&
item->kind == SCR_DYN_NUM && needle->kind == SCR_DYN_NUM &&
item->v.num != item->v.num && needle->v.num != needle->v.num;
if (nan_match || scr_dyn_strict_eq(item, needle)) {
return dyn_name_is(method, "includes") ? scr_dyn_new_bool(true) : scr_dyn_new_num((double)i);
}
}
+3 -4
View File
@@ -3076,10 +3076,9 @@ bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b) {
* — a dyn copy is a different object, which is Node's answer too. */
return a == b || scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell);
case SCR_DYN_TYPED_REF:
return a->v.typed_ref.ptr == b->v.typed_ref.ptr &&
a->v.typed_ref.type_key_len == b->v.typed_ref.type_key_len &&
memcmp(a->v.typed_ref.type_key, b->v.typed_ref.type_key,
a->v.typed_ref.type_key_len) == 0;
/* A base-class view and a derived view still name the same object.
* The compiler's type key governs checked extraction, not identity. */
return a->v.typed_ref.ptr == b->v.typed_ref.ptr;
default: return a == b;
}
}
+48 -2
View File
@@ -87,8 +87,42 @@ static uint64_t scr_map_identity(const ScrMap *m, uint64_t key) {
return key;
}
/* Checked-dynamic keys use the JavaScript value, not the temporary box.
* Typed capsules preserve a native object's identity across repeated
* crossings; numbers retain SameValueZero and strings compare by content. */
static uint64_t scr_map_hash_dyn(const ScrDyn *d) {
uint64_t value;
switch (d->kind) {
case SCR_DYN_UNDEF:
case SCR_DYN_NULL: value = 0; break;
case SCR_DYN_BOOL: value = d->v.b; break;
case SCR_DYN_NUM: value = scr_map_f64_bits(d->v.num); break;
case SCR_DYN_STR: return scr_map_hash_str(d->v.str);
case SCR_DYN_FUNC: value = scr_map_slot_from_ptr(d->v.fn.clo); break;
case SCR_DYN_HANDLE: value = scr_map_slot_from_ptr(d->v.handle.ptr); break;
case SCR_DYN_PROMISE: value = scr_map_slot_from_ptr(d->v.promise); break;
case SCR_DYN_TYPED_REF: value = scr_map_slot_from_ptr(d->v.typed_ref.ptr); break;
/* The optional island bridge exposes equality but no hash. A shared
* bucket still preserves correctness without adding an engine dependency. */
case SCR_DYN_JSVAL: value = 0; break;
default: value = scr_map_slot_from_ptr((void *)d); break;
}
return scr_map_fnv1a((const unsigned char *)&value, sizeof value) ^ (uint64_t)d->kind;
}
static bool scr_map_dyn_eq(const ScrDyn *a, const ScrDyn *b) {
if (a->kind == SCR_DYN_NUM && b->kind == SCR_DYN_NUM) {
return scr_map_f64_bits(a->v.num) == scr_map_f64_bits(b->v.num);
}
if (a->kind == SCR_DYN_TYPED_REF && b->kind == SCR_DYN_TYPED_REF) {
return a->v.typed_ref.ptr == b->v.typed_ref.ptr;
}
return scr_dyn_strict_eq(a, b);
}
static uint64_t scr_map_hash_key(const ScrMap *m, uint64_t key) {
if (m->key_kind == SCR_MAP_KEY_STR) return scr_map_hash_str((ScrStr *)scr_map_slot_to_ptr(key));
if (m->key_kind == SCR_MAP_KEY_DYN) return scr_map_hash_dyn((ScrDyn *)scr_map_slot_to_ptr(key));
uint64_t identity = scr_map_identity(m, key);
return scr_map_fnv1a((const unsigned char *)&identity, sizeof identity);
}
@@ -96,6 +130,9 @@ static uint64_t scr_map_hash_key(const ScrMap *m, uint64_t key) {
/* Stored keys are pre-normalized, so bit equality IS SameValueZero for f64
* keys (probe keys normalize through the same function). */
static bool scr_map_key_eq(const ScrMap *m, uint64_t stored, uint64_t probe) {
if (m->key_kind == SCR_MAP_KEY_DYN) {
return scr_map_dyn_eq((ScrDyn *)scr_map_slot_to_ptr(stored), (ScrDyn *)scr_map_slot_to_ptr(probe));
}
/* f64 keys are pre-normalized and REF keys are pointers, so bit equality
* IS the honest compare for both (SameValueZero; reference identity). */
if (m->key_kind != SCR_MAP_KEY_STR) return scr_map_identity(m, stored) == scr_map_identity(m, probe);
@@ -180,7 +217,7 @@ static void scr_map_reserve_append(ScrMap *m) {
static void scr_map_release_key(ScrMap *m, uint64_t key) {
if (m->key_kind == SCR_MAP_KEY_STR) {
scr_str_release((ScrStr *)scr_map_slot_to_ptr(key));
} else if (m->key_kind == SCR_MAP_KEY_REF || m->key_kind == SCR_MAP_KEY_UNION_REF) {
} else if (m->key_kind == SCR_MAP_KEY_REF || m->key_kind == SCR_MAP_KEY_UNION_REF || m->key_kind == SCR_MAP_KEY_DYN) {
m->key_release(scr_map_slot_to_ptr(key));
}
}
@@ -373,6 +410,15 @@ static void scr_map_set(ScrMap *m, uint64_t hash, uint64_t key, uint64_t val) {
m->nlive++;
if (m->key_kind == SCR_MAP_KEY_STR) {
scr_str_retain((ScrStr *)scr_map_slot_to_ptr(key)); /* key is borrowed */
} else if (m->key_kind == SCR_MAP_KEY_DYN) {
const ScrDyn *d = (ScrDyn *)scr_map_slot_to_ptr(key);
if (d->kind == SCR_DYN_NUM && d->v.num == 0) {
/* Iteration returns +0 even when the inserted key was -0. Never
* mutate the caller's shared box while normalizing a stored key. */
m->entries[idx].key = scr_map_slot_from_ptr(scr_dyn_new_num(0));
} else {
m->key_retain(scr_map_slot_to_ptr(key));
}
} else if (m->key_kind == SCR_MAP_KEY_REF || m->key_kind == SCR_MAP_KEY_UNION_REF) {
m->key_retain(scr_map_slot_to_ptr(key)); /* key is borrowed */
}
@@ -571,7 +617,7 @@ void scr_set_add_all(ScrMap *set, ScrArr *values) {
ScrStr *s = (ScrStr *)scr_arr_get_ref(values, (double)i); /* +1 */
scr_map_set_str_f64(set, s, 0); /* borrows; retains stored copy */
scr_str_release(s);
} else if (set->key_kind == SCR_MAP_KEY_REF || set->key_kind == SCR_MAP_KEY_UNION_REF) {
} else if (set->key_kind == SCR_MAP_KEY_REF || set->key_kind == SCR_MAP_KEY_UNION_REF || set->key_kind == SCR_MAP_KEY_DYN) {
void *p = scr_arr_get_ref(values, (double)i); /* +1 */
scr_map_set_ref_f64(set, p, 0); /* borrows; retains stored copy */
set->key_release(p);
+5 -3
View File
@@ -1244,8 +1244,10 @@ ScrArr *scr_regex_match_all_into(ScrStr *s, ScrRegex *re, ScrArr *indices);
/* REF keys hash/compare pointer identity and carry their own RC adapters.
* UNION_REF owns the union wrapper but hashes/compares its reference payload;
* the frontend permits only unions of reference-identity arms for this kind.
* Both key kinds serve Maps and Sets. */
typedef enum { SCR_MAP_KEY_F64, SCR_MAP_KEY_STR, SCR_MAP_KEY_REF, SCR_MAP_KEY_UNION_REF } ScrMapKeyKind;
* DYN owns a checked-value box and compares its JavaScript value using
* SameValueZero, including reference identity for object payloads.
* These key kinds serve both Maps and Sets. */
typedef enum { SCR_MAP_KEY_F64, SCR_MAP_KEY_STR, SCR_MAP_KEY_REF, SCR_MAP_KEY_UNION_REF, SCR_MAP_KEY_DYN } ScrMapKeyKind;
typedef enum { SCR_MAP_VAL_F64, SCR_MAP_VAL_BOOL, SCR_MAP_VAL_REF } ScrMapValKind;
typedef struct {
@@ -1263,7 +1265,7 @@ typedef struct ScrMap {
void *(*val_retain)(void *);
void (*val_release)(void *);
ScrTraceFn val_trace;
/* SCR_MAP_KEY_REF and SCR_MAP_KEY_UNION_REF only; NULL otherwise. */
/* SCR_MAP_KEY_REF, SCR_MAP_KEY_UNION_REF and SCR_MAP_KEY_DYN only. */
void *(*key_retain)(void *);
void (*key_release)(void *);
ScrTraceFn key_trace;
+3
View File
@@ -19,6 +19,9 @@ beforeAll(async () => {
"-o", bin,
join(testDir, "test_array.c"),
join(testDir, "../src/scr_array.c"),
// Checked-value collection equality shares the native JSON value runtime.
join(testDir, "../src/scr_json.c"),
join(testDir, "../src/scr_closure.c"),
join(testDir, "../src/scr_string.c"),
join(testDir, "../src/scr_number.c"),
join(testDir, "../src/scr_bytes.c"),
+1
View File
@@ -20,6 +20,7 @@ test("closure/box runtime: RC cascades clean under ASan + audit", async () => {
join(srcDir, "scr_closure.c"),
join(srcDir, "scr_string.c"),
join(srcDir, "scr_array.c"),
join(srcDir, "scr_json.c"),
join(srcDir, "scr_map.c"),
join(srcDir, "scr_number.c"),
join(srcDir, "scr_cycle.c"),
+2
View File
@@ -27,6 +27,8 @@ beforeAll(async () => {
// which pull in the closure/array/box machinery
join(testDir, "../src/scr_union.c"),
join(testDir, "../src/scr_array.c"),
// Checked-value collection equality shares the native JSON value runtime.
join(testDir, "../src/scr_json.c"),
join(testDir, "../src/scr_closure.c"),
join(testDir, "../src/scr_object.c"),
join(testDir, "../src/scr_bytes.c"),
+3
View File
@@ -24,6 +24,9 @@ beforeAll(async () => {
join(testDir, "../src/scr_string.c"),
join(testDir, "../src/scr_number.c"),
join(testDir, "../src/scr_array.c"),
// Checked-value collection equality shares the native JSON value runtime.
join(testDir, "../src/scr_json.c"),
join(testDir, "../src/scr_closure.c"),
join(testDir, "../src/scr_bytes.c"),
join(testDir, "../src/scr_error.c"),
join(testDir, "../src/scr_exception.c"),
+3
View File
@@ -24,6 +24,9 @@ beforeAll(async () => {
// scr_str_split returns a real string[]: the array module and its
// own dependencies join the link (the array.test.ts set).
join(testDir, "../src/scr_array.c"),
// Checked-value collection equality shares the native JSON value runtime.
join(testDir, "../src/scr_json.c"),
join(testDir, "../src/scr_closure.c"),
join(testDir, "../src/scr_bytes.c"),
join(testDir, "../src/scr_error.c"),
join(testDir, "../src/scr_exception.c"),
+3
View File
@@ -32,6 +32,9 @@ test("scr_string_to_number matches Node Number(s) on committed oracle cases", as
// scr_string.c's own link closure (the string.test.ts set):
// scr_str_split pulls the array module and its dependencies.
join(testDir, "../src/scr_array.c"),
// Checked-value collection equality shares the native JSON value runtime.
join(testDir, "../src/scr_json.c"),
join(testDir, "../src/scr_closure.c"),
join(testDir, "../src/scr_bytes.c"),
join(testDir, "../src/scr_error.c"),
join(testDir, "../src/scr_exception.c"),
+3
View File
@@ -23,6 +23,9 @@ beforeAll(async () => {
join(testDir, "../src/scr_string.c"),
join(testDir, "../src/scr_number.c"),
join(testDir, "../src/scr_array.c"),
// Checked-value collection equality shares the native JSON value runtime.
join(testDir, "../src/scr_json.c"),
join(testDir, "../src/scr_closure.c"),
join(testDir, "../src/scr_bytes.c"),
join(testDir, "../src/scr_error.c"),
join(testDir, "../src/scr_exception.c"),