@scriptc/cli-darwin-x64 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/THIRD_PARTY_NOTICES +1 -1
  2. package/dist/bin/scriptc +0 -0
  3. package/dist/bin/scriptc.json +1 -1
  4. package/dist/lib/llvm/bin/scriptc-llvm-codegen +0 -0
  5. package/dist/lib/llvm/package.json +1 -1
  6. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_array.o +0 -0
  7. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_bigint.o +0 -0
  8. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_cycle.o +0 -0
  9. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_json.o +0 -0
  10. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_lib.o +0 -0
  11. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_map.o +0 -0
  12. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_regex.o +0 -0
  13. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/default/scr_string.o +0 -0
  14. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_array.o +0 -0
  15. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_bigint.o +0 -0
  16. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_cycle.o +0 -0
  17. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_json.o +0 -0
  18. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_lib.o +0 -0
  19. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_map.o +0 -0
  20. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_regex.o +0 -0
  21. package/dist/lib/runtime-darwin-x64/artifacts/dev/runtime/dynamic/scr_string.o +0 -0
  22. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_array.o +0 -0
  23. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_bigint.o +0 -0
  24. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_cycle.o +0 -0
  25. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_json.o +0 -0
  26. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_lib.o +0 -0
  27. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_map.o +0 -0
  28. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_regex.o +0 -0
  29. package/dist/lib/runtime-darwin-x64/artifacts/library-dev/runtime/default/scr_string.o +0 -0
  30. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_array.o +0 -0
  31. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_bigint.o +0 -0
  32. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_cycle.o +0 -0
  33. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_json.o +0 -0
  34. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_lib.o +0 -0
  35. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_map.o +0 -0
  36. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_regex.o +0 -0
  37. package/dist/lib/runtime-darwin-x64/artifacts/library-release/runtime/default/scr_string.o +0 -0
  38. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_array.o +0 -0
  39. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_bigint.o +0 -0
  40. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_cycle.o +0 -0
  41. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_json.o +0 -0
  42. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_lib.o +0 -0
  43. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_map.o +0 -0
  44. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_regex.o +0 -0
  45. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-dev/runtime/default/scr_string.o +0 -0
  46. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_array.o +0 -0
  47. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_bigint.o +0 -0
  48. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_cycle.o +0 -0
  49. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_json.o +0 -0
  50. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_lib.o +0 -0
  51. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_map.o +0 -0
  52. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_regex.o +0 -0
  53. package/dist/lib/runtime-darwin-x64/artifacts/library-thread-release/runtime/default/scr_string.o +0 -0
  54. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_array.o +0 -0
  55. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_bigint.o +0 -0
  56. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_cycle.o +0 -0
  57. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_json.o +0 -0
  58. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_lib.o +0 -0
  59. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_map.o +0 -0
  60. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_regex.o +0 -0
  61. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/default/scr_string.o +0 -0
  62. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_array.o +0 -0
  63. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_bigint.o +0 -0
  64. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_cycle.o +0 -0
  65. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_json.o +0 -0
  66. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_lib.o +0 -0
  67. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_map.o +0 -0
  68. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_regex.o +0 -0
  69. package/dist/lib/runtime-darwin-x64/artifacts/release/runtime/dynamic/scr_string.o +0 -0
  70. package/dist/lib/runtime-darwin-x64/package.json +1 -1
  71. package/dist/lib/runtime-darwin-x64/runtime-pack.json +129 -129
  72. package/dist/lib/runtime-sources/package.json +1 -1
  73. package/dist/lib/runtime-sources/src/scr_array.c +50 -34
  74. package/dist/lib/runtime-sources/src/scr_bigint.c +15 -0
  75. package/dist/lib/runtime-sources/src/scr_cycle.c +84 -60
  76. package/dist/lib/runtime-sources/src/scr_json.c +15 -11
  77. package/dist/lib/runtime-sources/src/scr_lib.c +45 -16
  78. package/dist/lib/runtime-sources/src/scr_map.c +228 -40
  79. package/dist/lib/runtime-sources/src/scr_regex.c +47 -8
  80. package/dist/lib/runtime-sources/src/scr_runtime.h +39 -13
  81. package/dist/lib/runtime-sources/src/scr_string.c +29 -0
  82. package/package.json +1 -1
@@ -59,6 +59,16 @@ static uint64_t scr_map_hash_str(const ScrStr *k) {
59
59
  return scr_map_fnv1a((const unsigned char *)k->data, k->len);
60
60
  }
61
61
 
62
+ /* Mix the complete normalized number or identity word. Float exponents
63
+ * and aligned pointers need high bits to reach the low bucket-index bits. */
64
+ static uint64_t scr_map_hash_word(uint64_t value) {
65
+ value ^= value >> 30;
66
+ value *= UINT64_C(0xbf58476d1ce4e5b9);
67
+ value ^= value >> 27;
68
+ value *= UINT64_C(0x94d049bb133111eb);
69
+ return value ^ (value >> 31);
70
+ }
71
+
62
72
  /* ── slot packing (8-byte slots, like ScrArr) ──────────────────────────── */
63
73
 
64
74
  static uint64_t scr_map_slot_from_f64(double v) {
@@ -98,12 +108,7 @@ static uint64_t scr_map_hash_dyn(const ScrDyn *d) {
98
108
  case SCR_DYN_NULL: value = 0; break;
99
109
  case SCR_DYN_BOOL: value = d->v.b; break;
100
110
  case SCR_DYN_NUM: value = scr_map_f64_bits(d->v.num); break;
101
- case SCR_DYN_BIGINT: {
102
- ScrStr *text = scr_bigint_to_string(d->v.bigint, 10);
103
- uint64_t hash = scr_map_hash_str(text);
104
- scr_str_release(text);
105
- return hash;
106
- }
111
+ case SCR_DYN_BIGINT: return scr_bigint_hash(d->v.bigint);
107
112
  case SCR_DYN_STR: return scr_map_hash_str(d->v.str);
108
113
  case SCR_DYN_SYMBOL: value = scr_map_slot_from_ptr(d->v.symbol.value); break;
109
114
  case SCR_DYN_FUNC: value = scr_map_slot_from_ptr(d->v.fn.class_obj ? (void *)d->v.fn.class_obj : (void *)d->v.fn.clo); break;
@@ -122,7 +127,7 @@ static uint64_t scr_map_hash_dyn(const ScrDyn *d) {
122
127
  case SCR_DYN_JSVAL: value = 0; break;
123
128
  default: value = scr_map_slot_from_ptr((void *)d); break;
124
129
  }
125
- return scr_map_fnv1a((const unsigned char *)&value, sizeof value) ^ kind;
130
+ return scr_map_hash_word(value) ^ kind;
126
131
  }
127
132
 
128
133
  static bool scr_map_dyn_eq(const ScrDyn *a, const ScrDyn *b) {
@@ -135,16 +140,50 @@ static bool scr_map_dyn_eq(const ScrDyn *a, const ScrDyn *b) {
135
140
  return scr_dyn_strict_eq(a, b);
136
141
  }
137
142
 
143
+ static uint64_t scr_map_hash_union(const ScrMap *m, const ScrUnion *key) {
144
+ ScrMapKeyKind kind = (ScrMapKeyKind)m->union_keys[key->tag];
145
+ switch (kind) {
146
+ case SCR_MAP_KEY_STR: return scr_map_hash_str(scr_union_peek(key));
147
+ case SCR_MAP_KEY_BIGINT: return scr_bigint_hash(scr_union_peek(key));
148
+ case SCR_MAP_KEY_F64: return scr_map_hash_word(scr_map_f64_bits(scr_map_slot_to_f64(key->slot)));
149
+ case SCR_MAP_KEY_NULL:
150
+ case SCR_MAP_KEY_UNDEFINED: return scr_map_hash_word((uint64_t)kind);
151
+ default: return scr_map_hash_word(key->slot);
152
+ }
153
+ }
154
+
155
+ static bool scr_map_union_eq(const ScrMap *m, const ScrUnion *a, const ScrUnion *b) {
156
+ ScrMapKeyKind kind = (ScrMapKeyKind)m->union_keys[a->tag];
157
+ if (kind != (ScrMapKeyKind)m->union_keys[b->tag]) return false;
158
+ switch (kind) {
159
+ case SCR_MAP_KEY_STR: return scr_str_eq(scr_union_peek(a), scr_union_peek(b));
160
+ case SCR_MAP_KEY_BIGINT: return scr_bigint_eq(scr_union_peek(a), scr_union_peek(b));
161
+ case SCR_MAP_KEY_F64:
162
+ return scr_map_f64_bits(scr_map_slot_to_f64(a->slot)) == scr_map_f64_bits(scr_map_slot_to_f64(b->slot));
163
+ case SCR_MAP_KEY_NULL:
164
+ case SCR_MAP_KEY_UNDEFINED: return true;
165
+ default: return a->slot == b->slot;
166
+ }
167
+ }
168
+
138
169
  static uint64_t scr_map_hash_key(const ScrMap *m, uint64_t key) {
139
170
  if (m->key_kind == SCR_MAP_KEY_STR) return scr_map_hash_str((ScrStr *)scr_map_slot_to_ptr(key));
140
171
  if (m->key_kind == SCR_MAP_KEY_DYN) return scr_map_hash_dyn((ScrDyn *)scr_map_slot_to_ptr(key));
172
+ if (m->key_kind == SCR_MAP_KEY_BIGINT) return scr_bigint_hash(scr_map_slot_to_ptr(key));
173
+ if (m->key_kind == SCR_MAP_KEY_UNION_VALUE) return scr_map_hash_union(m, scr_map_slot_to_ptr(key));
141
174
  uint64_t identity = scr_map_identity(m, key);
142
- return scr_map_fnv1a((const unsigned char *)&identity, sizeof identity);
175
+ return scr_map_hash_word(identity);
143
176
  }
144
177
 
145
178
  /* Stored keys are pre-normalized, so bit equality IS SameValueZero for f64
146
179
  * keys (probe keys normalize through the same function). */
147
180
  static bool scr_map_key_eq(const ScrMap *m, uint64_t stored, uint64_t probe) {
181
+ if (m->key_kind == SCR_MAP_KEY_UNION_VALUE) {
182
+ return scr_map_union_eq(m, scr_map_slot_to_ptr(stored), scr_map_slot_to_ptr(probe));
183
+ }
184
+ if (m->key_kind == SCR_MAP_KEY_BIGINT) {
185
+ return scr_bigint_eq(scr_map_slot_to_ptr(stored), scr_map_slot_to_ptr(probe));
186
+ }
148
187
  if (m->key_kind == SCR_MAP_KEY_DYN) {
149
188
  return scr_map_dyn_eq((ScrDyn *)scr_map_slot_to_ptr(stored), (ScrDyn *)scr_map_slot_to_ptr(probe));
150
189
  }
@@ -156,18 +195,25 @@ static bool scr_map_key_eq(const ScrMap *m, uint64_t stored, uint64_t probe) {
156
195
  }
157
196
 
158
197
  /* ── lookup ────────────────────────────────────────────────────────────
159
- * Returns the LIVE entry index holding the key, or SCR_MAP_EMPTY. Probes
160
- * skip tombstoned entries (their bucket slots keep chains intact). */
161
- static size_t scr_map_find(const ScrMap *m, uint64_t hash, uint64_t key) {
198
+ * Returns the matching bucket, or the empty bucket where a new key belongs.
199
+ * A zero entry hash marks a tombstone; cached hashes reject unrelated keys
200
+ * before equality and survive table growth without hashing the keys again.
201
+ * Remap zero to one so every live entry has a nonzero hash. */
202
+ static size_t scr_map_probe(const ScrMap *m, uint64_t hash, uint64_t key) {
162
203
  if (m->nbuckets == 0) return SCR_MAP_EMPTY;
163
204
  size_t mask = m->nbuckets - 1;
164
205
  for (size_t i = hash & mask;; i = (i + 1) & mask) {
165
206
  size_t b = m->buckets[i];
166
- if (b == SCR_MAP_EMPTY) return SCR_MAP_EMPTY;
167
- if (m->entries[b].live && scr_map_key_eq(m, m->entries[b].key, key)) return b;
207
+ if (b == SCR_MAP_EMPTY) return i;
208
+ if (m->entries[b].hash == hash && scr_map_key_eq(m, m->entries[b].key, key)) return i;
168
209
  }
169
210
  }
170
211
 
212
+ static size_t scr_map_find(const ScrMap *m, uint64_t hash, uint64_t key) {
213
+ size_t slot = scr_map_probe(m, hash ? hash : 1, key);
214
+ return slot == SCR_MAP_EMPTY ? SCR_MAP_EMPTY : m->buckets[slot];
215
+ }
216
+
171
217
  /* Rebuild the bucket table (size must be a power of two >= 2 * nentries):
172
218
  * only live entries are inserted, in dense order — dead markers vanish. */
173
219
  static void scr_map_rebuild_buckets(ScrMap *m, size_t nbuckets) {
@@ -179,8 +225,8 @@ static void scr_map_rebuild_buckets(ScrMap *m, size_t nbuckets) {
179
225
  m->nbuckets = nbuckets;
180
226
  size_t mask = nbuckets - 1;
181
227
  for (size_t e = 0; e < m->nentries; e++) {
182
- if (!m->entries[e].live) continue;
183
- uint64_t hash = scr_map_hash_key(m, m->entries[e].key);
228
+ uint64_t hash = m->entries[e].hash;
229
+ if (!hash) continue;
184
230
  size_t i = hash & mask;
185
231
  while (buckets[i] != SCR_MAP_EMPTY) i = (i + 1) & mask;
186
232
  buckets[i] = e;
@@ -192,7 +238,7 @@ static void scr_map_rebuild_buckets(ScrMap *m, size_t nbuckets) {
192
238
  static void scr_map_compact(ScrMap *m) {
193
239
  size_t w = 0;
194
240
  for (size_t r = 0; r < m->nentries; r++) {
195
- if (m->entries[r].live) m->entries[w++] = m->entries[r];
241
+ if (m->entries[r].hash) m->entries[w++] = m->entries[r];
196
242
  }
197
243
  m->nentries = w;
198
244
  if (m->nbuckets > 0) scr_map_rebuild_buckets(m, m->nbuckets);
@@ -229,10 +275,15 @@ static void scr_map_reserve_append(ScrMap *m) {
229
275
 
230
276
  /* ── entry release helpers ─────────────────────────────────────────────── */
231
277
 
278
+ static bool scr_map_ref_key(ScrMapKeyKind kind) {
279
+ return kind == SCR_MAP_KEY_REF || kind == SCR_MAP_KEY_UNION_REF || kind == SCR_MAP_KEY_DYN ||
280
+ kind == SCR_MAP_KEY_BIGINT || kind == SCR_MAP_KEY_UNION_VALUE;
281
+ }
282
+
232
283
  static void scr_map_release_key(ScrMap *m, uint64_t key) {
233
284
  if (m->key_kind == SCR_MAP_KEY_STR) {
234
285
  scr_str_release((ScrStr *)scr_map_slot_to_ptr(key));
235
- } else if (m->key_kind == SCR_MAP_KEY_REF || m->key_kind == SCR_MAP_KEY_UNION_REF || m->key_kind == SCR_MAP_KEY_DYN) {
286
+ } else if (scr_map_ref_key(m->key_kind)) {
236
287
  m->key_release(scr_map_slot_to_ptr(key));
237
288
  }
238
289
  }
@@ -243,10 +294,14 @@ static void scr_map_release_val(ScrMap *m, uint64_t val) {
243
294
 
244
295
  /* ── lifecycle ─────────────────────────────────────────────────────────── */
245
296
 
297
+ void scr_map_union_keys(ScrMap *map, const uint8_t *kinds) {
298
+ map->union_keys = kinds;
299
+ }
300
+
246
301
  static void scr_map_trace(void *o, ScrTraceVisit visit, void *ctx) {
247
302
  ScrMap *m = (ScrMap *)o;
248
303
  for (size_t e = 0; e < m->nentries; e++) {
249
- if (!m->entries[e].live) continue;
304
+ if (!m->entries[e].hash) continue;
250
305
  if (m->key_trace) visit(scr_map_slot_to_ptr(m->entries[e].key), ctx);
251
306
  if (m->val_trace) visit(scr_map_slot_to_ptr(m->entries[e].val), ctx);
252
307
  }
@@ -258,7 +313,7 @@ static void scr_map_gcfree(void *o) {
258
313
  ScrMap *m = (ScrMap *)o;
259
314
  scr_weak_dispose(m);
260
315
  for (size_t e = 0; e < m->nentries; e++) {
261
- if (!m->entries[e].live) continue;
316
+ if (!m->entries[e].hash) continue;
262
317
  if (!m->key_trace) scr_map_release_key(m, m->entries[e].key);
263
318
  if (!m->val_trace) scr_map_release_val(m, m->entries[e].val);
264
319
  }
@@ -318,7 +373,7 @@ void scr_map_release(ScrMap *m) {
318
373
  scr_weak_dispose(m);
319
374
  if (m->key_trace || m->val_trace) scr_cyc_on_dead(m);
320
375
  for (size_t e = 0; e < m->nentries; e++) {
321
- if (!m->entries[e].live) continue;
376
+ if (!m->entries[e].hash) continue;
322
377
  scr_map_release_key(m, m->entries[e].key);
323
378
  scr_map_release_val(m, m->entries[e].val);
324
379
  }
@@ -344,8 +399,8 @@ double scr_map_size(const ScrMap *m) { return (double)m->nlive; }
344
399
 
345
400
  void scr_map_clear(ScrMap *m) {
346
401
  for (size_t e = 0; e < m->nentries; e++) {
347
- if (!m->entries[e].live) continue;
348
- m->entries[e].live = false;
402
+ if (!m->entries[e].hash) continue;
403
+ m->entries[e].hash = 0;
349
404
  scr_map_release_key(m, m->entries[e].key);
350
405
  scr_map_release_val(m, m->entries[e].val);
351
406
  }
@@ -363,7 +418,7 @@ void scr_map_clear(ScrMap *m) {
363
418
 
364
419
  bool scr_map_has_f64(const ScrMap *m, double key) {
365
420
  uint64_t k = scr_map_f64_bits(key);
366
- return scr_map_find(m, scr_map_fnv1a((const unsigned char *)&k, 8), k) != SCR_MAP_EMPTY;
421
+ return scr_map_find(m, scr_map_hash_word(k), k) != SCR_MAP_EMPTY;
367
422
  }
368
423
 
369
424
  bool scr_map_has_str(const ScrMap *m, const ScrStr *key) {
@@ -373,7 +428,7 @@ bool scr_map_has_str(const ScrMap *m, const ScrStr *key) {
373
428
 
374
429
  static bool scr_map_delete_found(ScrMap *m, size_t e) {
375
430
  if (e == SCR_MAP_EMPTY) return false;
376
- m->entries[e].live = false; /* bucket slot stays: probe chains intact */
431
+ m->entries[e].hash = 0; /* bucket slot stays: probe chains intact */
377
432
  m->nlive--;
378
433
  scr_map_release_key(m, m->entries[e].key);
379
434
  scr_map_release_val(m, m->entries[e].val);
@@ -382,7 +437,7 @@ static bool scr_map_delete_found(ScrMap *m, size_t e) {
382
437
 
383
438
  bool scr_map_delete_f64(ScrMap *m, double key) {
384
439
  uint64_t k = scr_map_f64_bits(key);
385
- return scr_map_delete_found(m, scr_map_find(m, scr_map_fnv1a((const unsigned char *)&k, 8), k));
440
+ return scr_map_delete_found(m, scr_map_find(m, scr_map_hash_word(k), k));
386
441
  }
387
442
 
388
443
  bool scr_map_delete_str(ScrMap *m, const ScrStr *key) {
@@ -412,18 +467,28 @@ bool scr_map_delete_ref(ScrMap *m, const void *key) {
412
467
  * insertion slot. */
413
468
 
414
469
  static void scr_map_set(ScrMap *m, uint64_t hash, uint64_t key, uint64_t val) {
415
- size_t e = scr_map_find(m, hash, key);
470
+ hash = hash ? hash : 1;
471
+ size_t slot = scr_map_probe(m, hash, key);
472
+ size_t e = slot == SCR_MAP_EMPTY ? SCR_MAP_EMPTY : m->buckets[slot];
416
473
  if (e != SCR_MAP_EMPTY) {
417
474
  uint64_t old = m->entries[e].val;
418
475
  m->entries[e].val = val; /* unlink before releasing (cycle collector) */
419
476
  scr_map_release_val(m, old);
420
477
  return;
421
478
  }
479
+ size_t old_entries = m->nentries, old_buckets = m->nbuckets;
422
480
  scr_map_reserve_append(m);
481
+ /* Growth or compaction can move the insertion bucket. Otherwise keep the
482
+ * empty slot from the first probe instead of walking the chain twice. */
483
+ if (m->nentries != old_entries || m->nbuckets != old_buckets) {
484
+ size_t mask = m->nbuckets - 1;
485
+ slot = hash & mask;
486
+ while (m->buckets[slot] != SCR_MAP_EMPTY) slot = (slot + 1) & mask;
487
+ }
423
488
  size_t idx = m->nentries++;
424
489
  m->entries[idx].key = key;
425
490
  m->entries[idx].val = val;
426
- m->entries[idx].live = true;
491
+ m->entries[idx].hash = hash;
427
492
  m->nlive++;
428
493
  if (m->key_kind == SCR_MAP_KEY_STR) {
429
494
  scr_str_retain((ScrStr *)scr_map_slot_to_ptr(key)); /* key is borrowed */
@@ -436,18 +501,21 @@ static void scr_map_set(ScrMap *m, uint64_t hash, uint64_t key, uint64_t val) {
436
501
  } else {
437
502
  m->key_retain(scr_map_slot_to_ptr(key));
438
503
  }
439
- } else if (m->key_kind == SCR_MAP_KEY_REF || m->key_kind == SCR_MAP_KEY_UNION_REF) {
504
+ } else if (m->key_kind == SCR_MAP_KEY_UNION_VALUE &&
505
+ m->union_keys[((ScrUnion *)scr_map_slot_to_ptr(key))->tag] == SCR_MAP_KEY_F64 &&
506
+ ((ScrUnion *)scr_map_slot_to_ptr(key))->slot == UINT64_C(0x8000000000000000)) {
507
+ /* Normalize stored -0 without mutating the caller's immutable box. */
508
+ ScrUnion *u = scr_map_slot_to_ptr(key);
509
+ m->entries[idx].key = scr_map_slot_from_ptr(scr_union_new_f64(u->tag, 0));
510
+ } else if (scr_map_ref_key(m->key_kind)) {
440
511
  m->key_retain(scr_map_slot_to_ptr(key)); /* key is borrowed */
441
512
  }
442
- size_t mask = m->nbuckets - 1;
443
- size_t i = hash & mask;
444
- while (m->buckets[i] != SCR_MAP_EMPTY) i = (i + 1) & mask;
445
- m->buckets[i] = idx;
513
+ m->buckets[slot] = idx;
446
514
  }
447
515
 
448
516
  static void scr_map_set_f64_key(ScrMap *m, double key, uint64_t val) {
449
517
  uint64_t k = scr_map_f64_bits(key); /* stores +0 for -0, canonical NaN */
450
- scr_map_set(m, scr_map_fnv1a((const unsigned char *)&k, 8), k, val);
518
+ scr_map_set(m, scr_map_hash_word(k), k, val);
451
519
  }
452
520
 
453
521
  static void scr_map_set_str_key(ScrMap *m, ScrStr *key, uint64_t val) {
@@ -501,7 +569,7 @@ ScrMap *scr_set_new_ref(void *(*elem_retain)(void *), void (*elem_release)(void
501
569
 
502
570
  static size_t scr_map_find_f64(const ScrMap *m, double key) {
503
571
  uint64_t k = scr_map_f64_bits(key);
504
- return scr_map_find(m, scr_map_fnv1a((const unsigned char *)&k, 8), k);
572
+ return scr_map_find(m, scr_map_hash_word(k), k);
505
573
  }
506
574
 
507
575
  static size_t scr_map_find_str(const ScrMap *m, const ScrStr *key) {
@@ -568,17 +636,63 @@ void *scr_map_get_ref_ref(const ScrMap *m, const void *key) {
568
636
  return m->val_retain(scr_map_slot_to_ptr(m->entries[e].val));
569
637
  }
570
638
 
639
+ /* Boolean keys use unboxed 0/1 slots and the same probe path. */
640
+ static size_t scr_map_find_bool(const ScrMap *m, bool key) {
641
+ uint64_t slot = key ? 1 : 0;
642
+ return scr_map_find(m, scr_map_hash_word(slot), slot);
643
+ }
644
+
645
+ bool scr_map_has_bool(const ScrMap *m, bool key) {
646
+ return scr_map_find_bool(m, key) != SCR_MAP_EMPTY;
647
+ }
648
+
649
+ bool scr_map_delete_bool(ScrMap *m, bool key) {
650
+ return scr_map_delete_found(m, scr_map_find_bool(m, key));
651
+ }
652
+
653
+ void scr_map_set_bool_f64(ScrMap *m, bool key, double value) {
654
+ scr_map_set(m, scr_map_hash_word(key ? 1 : 0), key ? 1 : 0, scr_map_slot_from_f64(value));
655
+ }
656
+
657
+ void scr_map_set_bool_bool(ScrMap *m, bool key, bool value) {
658
+ scr_map_set(m, scr_map_hash_word(key ? 1 : 0), key ? 1 : 0, value ? 1 : 0);
659
+ }
660
+
661
+ void scr_map_set_bool_ref(ScrMap *m, bool key, void *value) {
662
+ scr_map_set(m, scr_map_hash_word(key ? 1 : 0), key ? 1 : 0, scr_map_slot_from_ptr(value));
663
+ }
664
+
665
+ bool scr_map_get_bool_f64(const ScrMap *m, bool key, double *out) {
666
+ size_t entry = scr_map_find_bool(m, key);
667
+ if (entry == SCR_MAP_EMPTY) return false;
668
+ *out = scr_map_slot_to_f64(m->entries[entry].val);
669
+ return true;
670
+ }
671
+
672
+ bool scr_map_get_bool_bool(const ScrMap *m, bool key, bool *out) {
673
+ size_t entry = scr_map_find_bool(m, key);
674
+ if (entry == SCR_MAP_EMPTY) return false;
675
+ *out = m->entries[entry].val != 0;
676
+ return true;
677
+ }
678
+
679
+ void *scr_map_get_bool_ref(const ScrMap *m, bool key) {
680
+ size_t entry = scr_map_find_bool(m, key);
681
+ if (entry == SCR_MAP_EMPTY) return NULL;
682
+ return m->val_retain(scr_map_slot_to_ptr(m->entries[entry].val));
683
+ }
684
+
571
685
  /* ── iteration primitives (the forEach desugar) ────────────────────────── */
572
686
 
573
687
  double scr_map_iter_count(const ScrMap *m) { return (double)m->nentries; }
574
688
 
575
689
  bool scr_map_iter_live(const ScrMap *m, double i) {
576
690
  if (!(i >= 0) || i >= (double)m->nentries) return false;
577
- return m->entries[(size_t)i].live;
691
+ return m->entries[(size_t)i].hash;
578
692
  }
579
693
 
580
694
  static const ScrMapEntry *scr_map_iter_at(const ScrMap *m, double i) {
581
- if (!(i >= 0) || i >= (double)m->nentries || !m->entries[(size_t)i].live) {
695
+ if (!(i >= 0) || i >= (double)m->nentries || !m->entries[(size_t)i].hash) {
582
696
  scr_trap("scriptc: internal error: map iteration index out of range\n");
583
697
  }
584
698
  return &m->entries[(size_t)i];
@@ -588,6 +702,10 @@ double scr_map_iter_key_f64(const ScrMap *m, double i) {
588
702
  return scr_map_slot_to_f64(scr_map_iter_at(m, i)->key);
589
703
  }
590
704
 
705
+ bool scr_map_iter_key_bool(const ScrMap *m, double i) {
706
+ return scr_map_iter_at(m, i)->key != 0;
707
+ }
708
+
591
709
  ScrStr *scr_map_iter_key_str(const ScrMap *m, double i) {
592
710
  return scr_str_retain((ScrStr *)scr_map_slot_to_ptr(scr_map_iter_at(m, i)->key));
593
711
  }
@@ -649,6 +767,67 @@ void scr_map_dyn_set(ScrMap *map, const ScrDyn *key, const ScrDyn *value, bool s
649
767
  scr_map_release_key(map, key_slot);
650
768
  }
651
769
 
770
+ /* Copy only live entries. The source is borrowed, contains unique keys,
771
+ * and runs no user code while traversed, so cached hashes remain valid. */
772
+ ScrMap *scr_map_clone(const ScrMap *source, bool keys_only) {
773
+ ScrMap *out = scr_map_new_typed(source->key_kind, keys_only ? SCR_MAP_VAL_F64 : source->val_kind,
774
+ source->key_retain, source->key_release, source->key_trace,
775
+ keys_only ? NULL : source->val_retain, keys_only ? NULL : source->val_release,
776
+ keys_only ? NULL : source->val_trace);
777
+ out->union_keys = source->union_keys;
778
+ size_t count = source->nlive;
779
+ if (!count) return out;
780
+ if (count > SIZE_MAX / sizeof(ScrMapEntry) || count > SIZE_MAX / 2 / sizeof(size_t)) scr_map_oom();
781
+ out->entries = malloc(count * sizeof(ScrMapEntry));
782
+ if (!out->entries) scr_map_oom();
783
+ out->ecap = count;
784
+ for (size_t i = 0; i < source->nentries; i++) {
785
+ const ScrMapEntry *entry = &source->entries[i];
786
+ if (!entry->hash) continue;
787
+ ScrMapEntry *copy = &out->entries[out->nentries++];
788
+ *copy = *entry;
789
+ if (out->key_kind == SCR_MAP_KEY_STR) scr_str_retain(scr_map_slot_to_ptr(copy->key));
790
+ else if (scr_map_ref_key(out->key_kind)) out->key_retain(scr_map_slot_to_ptr(copy->key));
791
+ if (keys_only) copy->val = 0;
792
+ else if (out->val_kind == SCR_MAP_VAL_REF) out->val_retain(scr_map_slot_to_ptr(copy->val));
793
+ }
794
+ out->nlive = count;
795
+ size_t buckets = 16;
796
+ while (buckets < 2 * count) {
797
+ if (buckets > SIZE_MAX / 2 / sizeof(size_t)) scr_map_oom();
798
+ buckets *= 2;
799
+ }
800
+ scr_map_rebuild_buckets(out, buckets);
801
+ return out;
802
+ }
803
+
804
+ /* A generic view can be backed by typed slots. Its copy must accept new
805
+ * JavaScript values independently of that backing's narrower adapters. */
806
+ ScrMap *scr_map_clone_dyn(const ScrMap *source, bool keys_only) {
807
+ ScrMap *out = scr_map_new_typed(SCR_MAP_KEY_DYN, keys_only ? SCR_MAP_VAL_F64 : SCR_MAP_VAL_REF,
808
+ scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v,
809
+ keys_only ? NULL : scr_dyn_retain_v, keys_only ? NULL : scr_dyn_release_v,
810
+ keys_only ? NULL : scr_dyn_trace_v);
811
+ for (size_t i = 0; i < source->nentries; i++) {
812
+ if (!source->entries[i].hash) continue;
813
+ ScrDyn *key = scr_map_dyn_key(source, (double)i);
814
+ uint64_t value = keys_only ? 0 : scr_map_slot_from_ptr(scr_map_dyn_value(source, (double)i));
815
+ scr_map_set(out, scr_map_hash_dyn(key), scr_map_slot_from_ptr(key), value);
816
+ scr_dyn_release(key);
817
+ }
818
+ return out;
819
+ }
820
+
821
+ void scr_map_values_into(ScrMap *set, const ScrMap *source, bool checked) {
822
+ for (size_t i = 0; i < source->nentries; i++) {
823
+ if (!source->entries[i].hash) continue;
824
+ uint64_t value = checked ? scr_map_slot_from_ptr(scr_map_dyn_value(source, (double)i)) : source->entries[i].val;
825
+ if (set->key_kind == SCR_MAP_KEY_F64) value = scr_map_f64_bits(scr_map_slot_to_f64(value));
826
+ scr_map_set(set, scr_map_hash_key(set, value), value, 0);
827
+ if (checked) scr_dyn_release(scr_map_slot_to_ptr(value));
828
+ }
829
+ }
830
+
652
831
  ScrArr *scr_set_to_arr_dyn(const ScrMap *map) {
653
832
  ScrArr *out = scr_arr_new_ref(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v, map->nlive);
654
833
  for (size_t i = 0; i < map->nentries; i++) {
@@ -683,14 +862,23 @@ void scr_map_iter_exit(ScrMap *m) {
683
862
  void scr_set_add_all(ScrMap *set, ScrArr *values) {
684
863
  size_t n = values->len;
685
864
  for (size_t i = 0; i < n; i++) {
686
- if (values->elem == SCR_ELEM_STR) {
865
+ /* Packed values stay owned by the seed array throughout construction.
866
+ * Avoid taking a temporary owner only to release it after insertion.
867
+ * Sparse/missing positions retain the existing accessor semantics. */
868
+ if (i < values->cap && values->present[i] == SCR_ARR_VALUE) {
869
+ uint64_t key = values->data[i];
870
+ if (set->key_kind == SCR_MAP_KEY_F64) key = scr_map_f64_bits(scr_map_slot_to_f64(key));
871
+ scr_map_set(set, scr_map_hash_key(set, key), key, 0);
872
+ } else if (values->elem == SCR_ELEM_STR) {
687
873
  ScrStr *s = (ScrStr *)scr_arr_get_ref(values, (double)i); /* +1 */
688
874
  scr_map_set_str_f64(set, s, 0); /* borrows; retains stored copy */
689
875
  scr_str_release(s);
690
- } else if (set->key_kind == SCR_MAP_KEY_REF || set->key_kind == SCR_MAP_KEY_UNION_REF || set->key_kind == SCR_MAP_KEY_DYN) {
876
+ } else if (scr_map_ref_key(set->key_kind)) {
691
877
  void *p = scr_arr_get_ref(values, (double)i); /* +1 */
692
878
  scr_map_set_ref_f64(set, p, 0); /* borrows; retains stored copy */
693
879
  set->key_release(p);
880
+ } else if (set->key_kind == SCR_MAP_KEY_BOOL) {
881
+ scr_map_set_bool_f64(set, scr_arr_get_bool(values, (double)i), 0);
694
882
  } else {
695
883
  scr_map_set_f64_f64(set, scr_arr_get_f64(values, (double)i), 0);
696
884
  }
@@ -729,7 +917,7 @@ ScrArr *scr_map_keys_js_order(const ScrMap *m) {
729
917
  size_t nidx = 0;
730
918
  struct { uint32_t v; ScrStr *k; } *idx = NULL;
731
919
  for (size_t i = 0; i < n; i++) {
732
- if (!m->entries[i].live) continue;
920
+ if (!m->entries[i].hash) continue;
733
921
  ScrStr *k = (ScrStr *)scr_map_slot_to_ptr(m->entries[i].key);
734
922
  uint32_t v;
735
923
  if (!scr_map_key_array_index(k, &v)) continue;
@@ -750,7 +938,7 @@ ScrArr *scr_map_keys_js_order(const ScrMap *m) {
750
938
  }
751
939
  free(idx);
752
940
  for (size_t i = 0; i < n; i++) {
753
- if (!m->entries[i].live) continue;
941
+ if (!m->entries[i].hash) continue;
754
942
  ScrStr *k = (ScrStr *)scr_map_slot_to_ptr(m->entries[i].key);
755
943
  uint32_t v;
756
944
  if (scr_map_key_array_index(k, &v)) continue;
@@ -420,13 +420,6 @@ void scr_regex_reset_last_index(ScrRegex *re, double index) {
420
420
  re->last_index = index;
421
421
  }
422
422
 
423
- bool scr_regex_test(ScrRegex *re, ScrStr *s) {
424
- ScrArr *result = scr_regex_exec(s, re);
425
- bool matched = result != NULL;
426
- scr_arr_release(result);
427
- return matched;
428
- }
429
-
430
423
  static int scr_regex_start(ScrRegex *re, int len) {
431
424
  double index = re->last_index;
432
425
  if (isnan(index) || index <= 0) return 0;
@@ -434,6 +427,43 @@ static int scr_regex_start(ScrRegex *re, int len) {
434
427
  return (int)floor(index);
435
428
  }
436
429
 
430
+ /* A predicate needs only the match boundary, not the captured strings or
431
+ * result metadata. Short ASCII subjects already have the engine's one-byte
432
+ * representation. Bound that scan so a long mixed-text subject does not
433
+ * traverse an ASCII prefix twice before matching. Other subjects use exec's
434
+ * UTF-16 conversion. Register storage is bounded by alloc_count, not captures. */
435
+ bool scr_regex_test(ScrRegex *re, ScrStr *s) {
436
+ uint8_t *bc = scr_regex_bc(re);
437
+ bool ascii = s->len <= 256;
438
+ size_t i = 0;
439
+ for (; ascii && s->len - i >= sizeof(uint64_t); i += sizeof(uint64_t)) {
440
+ uint64_t word;
441
+ memcpy(&word, s->data + i, sizeof word);
442
+ if (word & UINT64_C(0x8080808080808080)) ascii = false;
443
+ }
444
+ for (; ascii && i < s->len; i++) {
445
+ if ((unsigned char)s->data[i] >= 0x80) ascii = false;
446
+ }
447
+ int len;
448
+ uint16_t *converted = ascii ? NULL : scr_to_utf16(s, &len);
449
+ const uint8_t *subject = ascii ? (const uint8_t *)s->data : (const uint8_t *)converted;
450
+ if (ascii) len = (int)s->len;
451
+ uint8_t *registers[32];
452
+ uint8_t **capture = lre_get_alloc_count(bc) <= 32 ? registers : scr_capture_alloc(bc);
453
+ bool stateful = (lre_get_flags(bc) & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) != 0;
454
+ int pos = stateful ? scr_regex_start(re, len) : 0;
455
+ int rc = pos < 0 ? 0 : lre_exec(capture, bc, subject, pos, len, ascii ? 0 : 1, lre_opaque());
456
+ if (rc < 0) {
457
+ fflush(stdout);
458
+ scr_trap("scriptc: regular expression execution failed\n");
459
+ }
460
+ if (stateful) re->last_index = rc == 1
461
+ ? (double)((capture[1] - subject) >> (ascii ? 0 : 1)) : 0;
462
+ if (capture != registers) free(capture);
463
+ free(converted);
464
+ return rc == 1;
465
+ }
466
+
437
467
  /* ── match ────────────────────────────────────────────────────────────── */
438
468
 
439
469
  /* re.exec(s): a fresh string[] of [whole match, ...captures], or NULL
@@ -1170,7 +1200,16 @@ ScrRegex *scr_regex_new(ScrStr *pattern, ScrStr *flags) {
1170
1200
  }
1171
1201
  re->rc = 1;
1172
1202
  re->source = pattern->len > 0 ? scr_str_retain(pattern) : scr_str_new("(?:)", 4);
1173
- re->flags = scr_str_retain(flags);
1203
+ /* Public flags use getter order, independently of constructor spelling.
1204
+ * Validate first so duplicates and invalid flags retain their errors. */
1205
+ char canonical[6];
1206
+ size_t flag_count = 0;
1207
+ const char *order = "gimsuy";
1208
+ for (unsigned i = 0; i < 6; i++) {
1209
+ if (seen_flags & (1u << i)) canonical[flag_count++] = order[i];
1210
+ }
1211
+ re->flags = flag_count == flags->len && memcmp(canonical, flags->data, flag_count) == 0
1212
+ ? scr_str_retain(flags) : scr_str_new(canonical, flag_count);
1174
1213
  /* Eager compile — the literal path stays lazy (its failure is an
1175
1214
  * abort; tsc already parsed those patterns). */
1176
1215
  int lre_flags = scr_lre_flags(re->flags);