mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-21 05:50:55 +08:00
Optimize keyed collection and object processing
- Speed up string-keyed collections and wide object property lookup. - Share scalable numeric-key ordering across typed and checked objects. - Preserve property, iteration, and ownership behavior across native and WASI targets.
This commit is contained in:
@@ -14354,6 +14354,18 @@
|
||||
"<repo>/tests/corpus/text-encode-into-boundaries.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/object-key-indexes.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/object-key-indexes.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/record-key-order.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/record-key-order.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+104
-52
@@ -22,6 +22,7 @@
|
||||
* serializers (and the error messages here).
|
||||
*/
|
||||
#include "scr_runtime.h"
|
||||
#include "scr_key.h"
|
||||
|
||||
static SCR_TL ScrDyn *scr_builtin_object_prototype;
|
||||
|
||||
@@ -709,6 +710,10 @@ static void scr_dyn_handle_release(void *h, ScrDynHandleTag tag);
|
||||
static bool scr_dyn_to_primitive_result_is_object(const ScrDyn *d);
|
||||
|
||||
static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
if (d->kind == SCR_DYN_OBJ) {
|
||||
free(d->v.obj.index);
|
||||
d->v.obj.index = NULL;
|
||||
}
|
||||
if (!collected) scr_dyn_release(d->prototype);
|
||||
if (!collected) scr_dyn_release(d->symbol_properties);
|
||||
if (!collected) scr_dyn_release(d->symbol_keys);
|
||||
@@ -829,6 +834,42 @@ void scr_dyn_release(ScrDyn *d) {
|
||||
scr_dyn_dispose(d, false);
|
||||
}
|
||||
|
||||
/* Small objects retain their compact linear representation. A wide object
|
||||
* owns a bucket index of entry positions; entries remain insertion ordered.
|
||||
* No key/value ownership lives here, and callbacks never retain its slots. */
|
||||
#define SCR_DYN_OBJECT_INDEX_MIN 32
|
||||
typedef struct ScrDynObjectIndex {
|
||||
size_t buckets;
|
||||
size_t slots[]; /* zero is empty; occupied slots store entry + 1 */
|
||||
} ScrDynObjectIndex;
|
||||
|
||||
static void scr_dyn_index_insert(ScrDynObjectIndex *index, const ScrDynEntry *entry, size_t position) {
|
||||
size_t slot = scr_key_hash(entry->key, entry->key_len) & (index->buckets - 1);
|
||||
while (index->slots[slot]) slot = (slot + 1) & (index->buckets - 1);
|
||||
index->slots[slot] = position + 1;
|
||||
}
|
||||
|
||||
/* Keep index allocation/probing outside compact object insertion and reads. */
|
||||
static __attribute__((noinline)) void scr_dyn_index_append(ScrDyn *object) {
|
||||
size_t count = object->v.obj.len;
|
||||
ScrDynObjectIndex *index = object->v.obj.index;
|
||||
if (!index || count > index->buckets / 2) {
|
||||
size_t buckets = index ? index->buckets : 64;
|
||||
while (count > buckets / 2) {
|
||||
if (buckets > (SIZE_MAX - sizeof *index) / sizeof(size_t) / 2) scr_json_oom();
|
||||
buckets *= 2;
|
||||
}
|
||||
free(index);
|
||||
index = calloc(1, sizeof *index + buckets * sizeof(size_t));
|
||||
if (!index) scr_json_oom();
|
||||
index->buckets = buckets;
|
||||
object->v.obj.index = index;
|
||||
for (size_t i = 0; i < count; i++) scr_dyn_index_insert(index, &object->v.obj.entries[i], i);
|
||||
} else {
|
||||
scr_dyn_index_insert(index, &object->v.obj.entries[count - 1], count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* A checked hint avoids repeating a linear walk for recurring property
|
||||
* names. Slots store indices only, never object/key pointers. Every hit
|
||||
* checks the current entry, so different shapes, deletion and reordering
|
||||
@@ -836,22 +877,38 @@ void scr_dyn_release(ScrDyn *d) {
|
||||
#define SCR_DYN_PROPERTY_HINTS 64
|
||||
static SCR_TL size_t scr_dyn_property_hints[SCR_DYN_PROPERTY_HINTS];
|
||||
|
||||
static ScrDynEntry *scr_dyn_find_linear_entry(const ScrDyn *object, const char *key, size_t length) {
|
||||
for (size_t i = 0; i < object->v.obj.len; i++) {
|
||||
ScrDynEntry *entry = &object->v.obj.entries[i];
|
||||
if (entry->key_len == length && !memcmp(entry->key, key, length)) return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static __attribute__((noinline)) ScrDynEntry *scr_dyn_find_indexed_entry(
|
||||
const ScrDyn *object, const char *key, size_t length) {
|
||||
const ScrDynObjectIndex *index = object->v.obj.index;
|
||||
size_t slot = scr_key_hash(key, length) & (index->buckets - 1);
|
||||
while (index->slots[slot]) {
|
||||
ScrDynEntry *entry = &object->v.obj.entries[index->slots[slot] - 1];
|
||||
if (entry->key_len == length && !memcmp(entry->key, key, length)) return entry;
|
||||
slot = (slot + 1) & (index->buckets - 1);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ScrDynEntry *scr_dyn_find_entry(const ScrDyn *object, const char *key, size_t length) {
|
||||
if (object->kind != SCR_DYN_OBJ) return NULL;
|
||||
if (object->v.obj.index) return scr_dyn_find_indexed_entry(object, key, length);
|
||||
size_t slot = (length + (length ? (unsigned char)key[0] * 7u : 0)) % SCR_DYN_PROPERTY_HINTS;
|
||||
size_t hint = scr_dyn_property_hints[slot];
|
||||
if (hint < object->v.obj.len) {
|
||||
ScrDynEntry *entry = &object->v.obj.entries[hint];
|
||||
if (entry->key_len == length && !memcmp(entry->key, key, length)) return entry;
|
||||
}
|
||||
for (size_t i = 0; i < object->v.obj.len; i++) {
|
||||
ScrDynEntry *entry = &object->v.obj.entries[i];
|
||||
if (entry->key_len == length && !memcmp(entry->key, key, length)) {
|
||||
scr_dyn_property_hints[slot] = i;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
ScrDynEntry *entry = scr_dyn_find_linear_entry(object, key, length);
|
||||
if (entry) scr_dyn_property_hints[slot] = (size_t)(entry - object->v.obj.entries);
|
||||
return entry;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_obj_get(const ScrDyn *d, const char *key, size_t key_len) {
|
||||
@@ -1156,20 +1213,21 @@ ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key) {
|
||||
* original spelling/storage and insertion position; the later value wins.
|
||||
* A recycled entry can reuse equal key bytes without a malloc/free pair. */
|
||||
static void scr_dyn_obj_put(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value) {
|
||||
for (size_t i = 0; i < obj->v.obj.len; i++) {
|
||||
ScrDynEntry *e = &obj->v.obj.entries[i];
|
||||
if (e->key_len == key_len && memcmp(e->key, key, key_len) == 0) {
|
||||
if (e->accessor || !e->writable) {
|
||||
scr_dyn_release(value);
|
||||
static const char message[] = "Cannot assign to read only property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
ScrDyn *old = e->value;
|
||||
e->value = value;
|
||||
scr_dyn_release(old);
|
||||
/* Construction usually appends an absent name. Avoid a read hint that
|
||||
* repeats comparisons on compact objects; wide objects use their index. */
|
||||
ScrDynEntry *existing = obj->v.obj.index ? scr_dyn_find_entry(obj, key, key_len)
|
||||
: scr_dyn_find_linear_entry(obj, key, key_len);
|
||||
if (existing) {
|
||||
if (existing->accessor || !existing->writable) {
|
||||
scr_dyn_release(value);
|
||||
static const char message[] = "Cannot assign to read only property";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
return;
|
||||
}
|
||||
ScrDyn *old = existing->value;
|
||||
existing->value = value;
|
||||
scr_dyn_release(old);
|
||||
return;
|
||||
}
|
||||
if (obj->non_extensible) {
|
||||
scr_dyn_release(value);
|
||||
@@ -1199,6 +1257,7 @@ static void scr_dyn_obj_put(ScrDyn *obj, const char *key, size_t key_len, ScrDyn
|
||||
e->writable = true;
|
||||
e->enumerable = true;
|
||||
e->configurable = true;
|
||||
if (obj->v.obj.len >= SCR_DYN_OBJECT_INDEX_MIN) scr_dyn_index_append(obj);
|
||||
}
|
||||
|
||||
/* ── dyn construction (compiler-emitted converters & overflow reads) ───── */
|
||||
@@ -6332,6 +6391,8 @@ static void scr_json_delete_member(ScrDyn *object, const ScrStr *key) {
|
||||
ScrDynEntry removed = *entry;
|
||||
memmove(entry, entry + 1, (object->v.obj.len - i - 1) * sizeof *entry);
|
||||
object->v.obj.len--;
|
||||
free(object->v.obj.index);
|
||||
object->v.obj.index = NULL;
|
||||
/* The moved tail aliases a live entry; spare slots own only their own
|
||||
* retained key bytes, never a second pointer to a surviving property. */
|
||||
memset(&object->v.obj.entries[object->v.obj.len], 0, sizeof *entry);
|
||||
@@ -7371,15 +7432,9 @@ bool scr_dyn_reflect_set(ScrDyn *target, ScrDyn *raw_key, ScrDyn *value, ScrDyn
|
||||
|
||||
/* The array-index test (ECMA: a canonical numeric string < 2^32-1). */
|
||||
static bool scr_dyn_key_is_index(const char *key, size_t len, double *out) {
|
||||
if (len == 0 || len > 10) return false;
|
||||
if (key[0] == '0' && len > 1) return false;
|
||||
double v = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (key[i] < '0' || key[i] > '9') return false;
|
||||
v = v * 10 + (key[i] - '0');
|
||||
}
|
||||
if (v > 4294967294.0) return false;
|
||||
*out = v;
|
||||
uint32_t index;
|
||||
if (!scr_key_array_index(key, len, &index)) return false;
|
||||
*out = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7397,34 +7452,31 @@ static ScrDyn *scr_dyn_objwalk_key(const char *key, size_t key_len) {
|
||||
ScrDyn *scr_dyn_obj_own_keys(const ScrDyn *v) {
|
||||
size_t n = v->v.obj.len;
|
||||
ScrDyn *keys = scr_dyn_new_arr();
|
||||
bool *is_index = malloc(n ? n * sizeof *is_index : 1);
|
||||
double *idx = malloc(n ? n * sizeof *idx : 1);
|
||||
if (!is_index || !idx) scr_trap("scriptc: out of memory\n");
|
||||
size_t index_count = 0;
|
||||
ScrKeyIndex *indices = NULL;
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
const ScrDynEntry *e = &v->v.obj.entries[i];
|
||||
is_index[i] = scr_dyn_key_is_index(e->key, e->key_len, &idx[i]);
|
||||
if (is_index[i]) index_count++;
|
||||
}
|
||||
double last = -1;
|
||||
for (size_t done = 0; done < index_count; done++) {
|
||||
size_t best = (size_t)-1;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (!is_index[i] || idx[i] <= last) continue;
|
||||
if (best == (size_t)-1 || idx[i] < idx[best]) best = i;
|
||||
const ScrDynEntry *entry = &v->v.obj.entries[i];
|
||||
uint32_t index;
|
||||
if (!scr_key_array_index(entry->key, entry->key_len, &index)) continue;
|
||||
if (!indices) {
|
||||
if (n > SIZE_MAX / sizeof *indices) scr_json_oom();
|
||||
indices = malloc(n * sizeof *indices);
|
||||
if (!indices) scr_json_oom();
|
||||
}
|
||||
if (best == (size_t)-1) break;
|
||||
last = idx[best];
|
||||
scr_dyn_arr_push(keys, scr_dyn_objwalk_key(v->v.obj.entries[best].key,
|
||||
v->v.obj.entries[best].key_len));
|
||||
indices[count++] = (ScrKeyIndex){index, i};
|
||||
}
|
||||
scr_key_index_sort(indices, count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const ScrDynEntry *entry = &v->v.obj.entries[indices[i].entry];
|
||||
scr_dyn_arr_push(keys, scr_dyn_objwalk_key(entry->key, entry->key_len));
|
||||
}
|
||||
free(indices);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (is_index[i]) continue;
|
||||
scr_dyn_arr_push(keys, scr_dyn_objwalk_key(v->v.obj.entries[i].key,
|
||||
v->v.obj.entries[i].key_len));
|
||||
const ScrDynEntry *entry = &v->v.obj.entries[i];
|
||||
uint32_t index;
|
||||
if (!scr_key_array_index(entry->key, entry->key_len, &index))
|
||||
scr_dyn_arr_push(keys, scr_dyn_objwalk_key(entry->key, entry->key_len));
|
||||
}
|
||||
free(is_index);
|
||||
free(idx);
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,41 @@ static void checked_storage(void) {
|
||||
}
|
||||
scr_dyn_release(object);
|
||||
}
|
||||
/* Cross index growth boundaries, invalidate moved entry positions, then
|
||||
* recreate the index from survivors. Repeat after pool reuse. */
|
||||
for (int round = 0; round < 4; round++) {
|
||||
ScrDyn *object = scr_dyn_new_obj();
|
||||
assert(!object->v.obj.index);
|
||||
for (int i = 0; i < 129; i++) {
|
||||
char key[16];
|
||||
int length = snprintf(key, sizeof key, "key-%d", i);
|
||||
scr_dyn_obj_set(object, key, (size_t)length, scr_dyn_new_num(i));
|
||||
assert((object->v.obj.index != NULL) == (i >= 31));
|
||||
}
|
||||
for (int i = 0; i < 129; i++) {
|
||||
char key[16];
|
||||
int length = snprintf(key, sizeof key, "key-%d", i);
|
||||
assert(scr_dyn_obj_get(object, key, (size_t)length)->v.num == i);
|
||||
if (i % 3 == 0) {
|
||||
ScrStr *name = scr_str_new(key, (size_t)length);
|
||||
scr_dyn_key_delete(object, name, true);
|
||||
scr_str_release(name);
|
||||
assert(!scr_dyn_obj_get(object, key, (size_t)length));
|
||||
}
|
||||
}
|
||||
scr_dyn_obj_set(object, "last", 4, scr_dyn_new_num(129));
|
||||
assert(object->v.obj.index);
|
||||
for (int i = 0; i < 129; i++) {
|
||||
char key[16];
|
||||
int length = snprintf(key, sizeof key, "key-%d", i);
|
||||
ScrDyn *value = scr_dyn_obj_get(object, key, (size_t)length);
|
||||
assert(i % 3 == 0 ? value == NULL : value && value->v.num == i);
|
||||
}
|
||||
scr_dyn_obj_set(object, "key-128", 7, scr_dyn_new_num(900));
|
||||
assert(object->v.obj.len == 87);
|
||||
assert(scr_dyn_obj_get(object, "key-128", 7)->v.num == 900);
|
||||
scr_dyn_release(object);
|
||||
}
|
||||
/* A direct step is authorized only by the captured builtin method. */
|
||||
ScrDyn *source = scr_dyn_new_arr();
|
||||
scr_dyn_arr_push(source, scr_dyn_new_num(7));
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/* Internal key hashing and canonical array-index ordering shared by native
|
||||
* maps and checked objects. Hashes never escape into JavaScript values. */
|
||||
#ifndef SCR_KEY_H
|
||||
#define SCR_KEY_H
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static inline uint64_t scr_key_mix(uint64_t value) {
|
||||
value ^= value >> 30;
|
||||
value *= UINT64_C(0xbf58476d1ce4e5b9);
|
||||
value ^= value >> 27;
|
||||
value *= UINT64_C(0x94d049bb133111eb);
|
||||
return value ^ (value >> 31);
|
||||
}
|
||||
|
||||
/* Unaligned loads stay inside the input. Hashing is process-local, so host
|
||||
* byte order is immaterial; equal byte strings always follow the same path. */
|
||||
static inline uint64_t scr_key_hash(const char *bytes, size_t length) {
|
||||
uint64_t hash = UINT64_C(0x9e3779b97f4a7c15) ^ length;
|
||||
size_t i = 0;
|
||||
while (length - i >= sizeof(uint64_t)) {
|
||||
uint64_t word;
|
||||
memcpy(&word, bytes + i, sizeof word);
|
||||
hash = scr_key_mix(hash ^ word);
|
||||
i += sizeof word;
|
||||
}
|
||||
uint64_t tail = 0;
|
||||
if (i < length) memcpy(&tail, bytes + i, length - i);
|
||||
return scr_key_mix(hash ^ tail);
|
||||
}
|
||||
|
||||
static inline bool scr_key_array_index(const char *key, size_t length, uint32_t *out) {
|
||||
if (length == 0 || length > 10 || (length > 1 && key[0] == '0')) return false;
|
||||
uint64_t value = 0;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (key[i] < '0' || key[i] > '9') return false;
|
||||
value = value * 10 + (unsigned)(key[i] - '0');
|
||||
}
|
||||
if (value >= UINT32_MAX) return false;
|
||||
*out = (uint32_t)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t index;
|
||||
size_t entry;
|
||||
} ScrKeyIndex;
|
||||
|
||||
static inline int scr_key_index_compare(const void *left, const void *right) {
|
||||
uint32_t a = ((const ScrKeyIndex *)left)->index;
|
||||
uint32_t b = ((const ScrKeyIndex *)right)->index;
|
||||
return (a > b) - (a < b);
|
||||
}
|
||||
|
||||
/* Keys are unique. Already ordered inputs need no sort, while arbitrary
|
||||
* numeric property insertion must not require quadratic enumeration. */
|
||||
static inline void scr_key_index_sort(ScrKeyIndex *keys, size_t count) {
|
||||
for (size_t i = 1; i < count; i++) {
|
||||
if (keys[i - 1].index > keys[i].index) {
|
||||
qsort(keys, count, sizeof *keys, scr_key_index_compare);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -15,6 +15,7 @@
|
||||
* keep the lean 1-word header and never touch the collector.
|
||||
*/
|
||||
#include "scr_runtime.h"
|
||||
#include "scr_key.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -46,27 +47,14 @@ static uint64_t scr_map_f64_bits(double k) {
|
||||
return bits;
|
||||
}
|
||||
|
||||
static uint64_t scr_map_fnv1a(const unsigned char *bytes, size_t n) {
|
||||
uint64_t h = UINT64_C(0xcbf29ce484222325);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
h ^= bytes[i];
|
||||
h *= UINT64_C(0x100000001b3);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static uint64_t scr_map_hash_str(const ScrStr *k) {
|
||||
return scr_map_fnv1a((const unsigned char *)k->data, k->len);
|
||||
return scr_key_hash(k->data, k->len);
|
||||
}
|
||||
|
||||
/* Mix the complete normalized number or identity word. Float exponents
|
||||
* and aligned pointers need high bits to reach the low bucket-index bits. */
|
||||
static uint64_t scr_map_hash_word(uint64_t value) {
|
||||
value ^= value >> 30;
|
||||
value *= UINT64_C(0xbf58476d1ce4e5b9);
|
||||
value ^= value >> 27;
|
||||
value *= UINT64_C(0x94d049bb133111eb);
|
||||
return value ^ (value >> 31);
|
||||
return scr_key_mix(value);
|
||||
}
|
||||
|
||||
/* ── slot packing (8-byte slots, like ScrArr) ──────────────────────────── */
|
||||
@@ -906,59 +894,39 @@ void scr_set_add_all(ScrMap *set, ScrArr *values) {
|
||||
/* Canonical array index test: "0".."4294967294" — digits only, no leading
|
||||
* zero (except "0" itself), value <= 2^32 - 2. JS orders these OWN keys
|
||||
* first, ascending numerically, before every other string key. */
|
||||
static bool scr_map_key_array_index(const ScrStr *k, uint32_t *out) {
|
||||
size_t n = k->len;
|
||||
if (n == 0 || n > 10) return false;
|
||||
const char *s = k->data;
|
||||
if (s[0] == '0' && n > 1) return false;
|
||||
uint64_t v = 0;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (s[i] < '0' || s[i] > '9') return false;
|
||||
v = v * 10 + (uint64_t)(s[i] - '0');
|
||||
}
|
||||
if (v > UINT64_C(4294967294)) return false;
|
||||
*out = (uint32_t)v;
|
||||
return true;
|
||||
static bool scr_map_key_array_index(const ScrStr *key, uint32_t *out) {
|
||||
return scr_key_array_index(key->data, key->len, out);
|
||||
}
|
||||
|
||||
/* The live STRING keys of `m` in JS OWN-KEY ORDER (Object.keys over the
|
||||
* index-signature overflow): integer-like keys (canonical array indices)
|
||||
* first in ascending numeric order, then the rest in insertion order.
|
||||
* Returns a fresh ScrStr* array (+1); the map is borrowed. Insertion sort
|
||||
* over the integer-like subset — overflow maps are small, and ties are
|
||||
* impossible (keys are unique). */
|
||||
/* Enumerate numeric keys in ascending order, then other keys in insertion
|
||||
* order. Scratch entries refer to stable map indices, not copied strings. */
|
||||
ScrArr *scr_map_keys_js_order(const ScrMap *m) {
|
||||
size_t n = m->nentries;
|
||||
ScrArr *out = scr_arr_new(SCR_ELEM_STR, m->nlive);
|
||||
size_t nidx = 0;
|
||||
struct { uint32_t v; ScrStr *k; } *idx = NULL;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (m->nlive > SIZE_MAX / sizeof(ScrKeyIndex)) scr_map_oom();
|
||||
ScrKeyIndex *indices = NULL;
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < m->nentries; i++) {
|
||||
if (!m->entries[i].hash) continue;
|
||||
ScrStr *k = (ScrStr *)scr_map_slot_to_ptr(m->entries[i].key);
|
||||
uint32_t v;
|
||||
if (!scr_map_key_array_index(k, &v)) continue;
|
||||
if (nidx % 16 == 0) {
|
||||
idx = realloc(idx, (nidx + 16) * sizeof *idx);
|
||||
if (!idx) scr_map_oom();
|
||||
ScrStr *key = scr_map_slot_to_ptr(m->entries[i].key);
|
||||
uint32_t index;
|
||||
if (!scr_map_key_array_index(key, &index)) continue;
|
||||
if (!indices) {
|
||||
indices = malloc(m->nlive * sizeof *indices);
|
||||
if (!indices) scr_map_oom();
|
||||
}
|
||||
size_t j = nidx++;
|
||||
while (j > 0 && idx[j - 1].v > v) {
|
||||
idx[j] = idx[j - 1];
|
||||
j--;
|
||||
}
|
||||
idx[j].v = v;
|
||||
idx[j].k = k;
|
||||
indices[count++] = (ScrKeyIndex){index, i};
|
||||
}
|
||||
for (size_t i = 0; i < nidx; i++) {
|
||||
scr_arr_push_ref(out, scr_str_retain(idx[i].k));
|
||||
scr_key_index_sort(indices, count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
ScrStr *key = scr_map_slot_to_ptr(m->entries[indices[i].entry].key);
|
||||
scr_arr_push_ref(out, scr_str_retain(key));
|
||||
}
|
||||
free(idx);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
free(indices);
|
||||
for (size_t i = 0; i < m->nentries; i++) {
|
||||
if (!m->entries[i].hash) continue;
|
||||
ScrStr *k = (ScrStr *)scr_map_slot_to_ptr(m->entries[i].key);
|
||||
uint32_t v;
|
||||
if (scr_map_key_array_index(k, &v)) continue;
|
||||
scr_arr_push_ref(out, scr_str_retain(k));
|
||||
ScrStr *key = scr_map_slot_to_ptr(m->entries[i].key);
|
||||
uint32_t index;
|
||||
if (!scr_map_key_array_index(key, &index)) scr_arr_push_ref(out, scr_str_retain(key));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -3759,6 +3759,7 @@ struct ScrDyn {
|
||||
* interfaces may request a fresh snapshot with the true arm. */
|
||||
void *source_identity;
|
||||
ScrDyn *(*source_access)(void *, bool materialize);
|
||||
struct ScrDynObjectIndex *index; /* optional wide-object lookup; owns no values */
|
||||
} obj; /* owned */
|
||||
/* SCR_DYN_FUNC: the boxed closure (owned) + its call descriptor. `sig`
|
||||
* and `name` are static compiler-emitted literals (never freed); name
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Wide checked objects preserve insertion order, descriptor behavior, and
|
||||
// lookups when entries move after deletion or are replaced by accessors.
|
||||
const object = JSON.parse("{}");
|
||||
for (let i = 95; i >= 0; i--) object[String(i)] = i * 3;
|
||||
for (const key of ["tail", "01", "4294967295", "4294967294", "-0", "", "a\0b", "é😀"]) object[key] = key;
|
||||
console.log(Object.keys(object).join("|"));
|
||||
console.log(Object.values(object).join("|"));
|
||||
for (let i = 0; i < 96; i += 3) delete object[String(i)];
|
||||
for (let i = 0; i < 96; i += 3) object[String(i)] = -i;
|
||||
object.tail = "updated";
|
||||
Object.defineProperty(object, "hidden", { value: 42, enumerable: false, configurable: true });
|
||||
Object.defineProperty(object, "31", { get() { delete object["32"]; object.added = 123; return "getter"; }, enumerable: true, configurable: true });
|
||||
console.log(Object.entries(object).map(([key, value]) => key + ":" + value).join("|"));
|
||||
console.log(Reflect.ownKeys(object).join("|"));
|
||||
console.log(object.hidden, object.missing, object["a\0b"], object["é😀"]);
|
||||
for (let i = 0; i < 96; i++) delete object[String(i)];
|
||||
console.log(Object.keys(object).join("|"));
|
||||
for (let i = 0; i < 100; i++) object["key-" + i] = i;
|
||||
Object.freeze(object);
|
||||
try { object["key-50"] = 0; } catch (error) { console.log(error.name); }
|
||||
console.log(object["key-50"], Object.keys(object).length);
|
||||
const duplicates = JSON.parse('{"32":1,"1":2,"32":3,"x":4,"x":5}');
|
||||
console.log(JSON.stringify(duplicates));
|
||||
// Recycling and cyclic destruction must discard all index storage.
|
||||
for (let round = 0; round < 40; round++) {
|
||||
const cycle = JSON.parse("{}");
|
||||
for (let i = 0; i < 80; i++) cycle["property-" + i] = round + i;
|
||||
cycle.self = cycle;
|
||||
console.log(cycle["property-79"], Object.keys(cycle).length);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
const record: Record<string, number> = {};
|
||||
for (let i = 96; i >= 0; i--) record[String(i)] = i;
|
||||
for (const key of ["word", "01", "4294967295", "4294967294", "-0", "", "a\0b", "é😀"]) record[key] = 100;
|
||||
console.log(Object.keys(record).join("|"));
|
||||
console.log(Object.values(record).join("|"));
|
||||
console.log(Object.entries(record).map(([key, value]) => key + ":" + value).join("|"));
|
||||
const map = new Map<string, number>();
|
||||
for (let length = 0; length <= 40; length++) {
|
||||
const prefix = "same-prefix-".repeat(length);
|
||||
for (const tail of ["a", "b", "\0", "é", "😀"]) map.set(prefix + tail, length);
|
||||
}
|
||||
let sum = 0;
|
||||
for (const [key, value] of map) {
|
||||
const copy = ("!" + key).slice(1);
|
||||
console.log(map.has(copy), map.get(copy), value);
|
||||
sum += map.get(copy)!;
|
||||
}
|
||||
console.log(map.size, sum);
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const key = "churn-" + i;
|
||||
map.set(key, i);
|
||||
if (i >= 7) map.delete("churn-" + (i - 7));
|
||||
}
|
||||
console.log(Array.from(map.keys()).slice(-7).join("|"));
|
||||
@@ -146,6 +146,8 @@ describe.skipIf(!zigOnPath())("wasm32-wasi differential", () => {
|
||||
"uint8array-sort-domains.ts",
|
||||
"buffer-codec-boundaries.ts",
|
||||
"text-encode-into-boundaries.ts",
|
||||
"object-key-indexes.js",
|
||||
"record-key-order.ts",
|
||||
"1612-cjs-module-globals.cjs",
|
||||
"992-fs-roundtrip.ts",
|
||||
"751-cycle-records-mutual.ts",
|
||||
|
||||
Reference in New Issue
Block a user