@scriptc/cli-darwin-arm64 0.2.3 → 0.2.5

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 (139) 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/declarations/scriptc-node-fallback.d.ts +915 -215
  5. package/dist/lib/declarations/scriptc-overrides.d.ts +5 -1
  6. package/dist/lib/declarations/scriptc.d.ts +22 -2
  7. package/dist/lib/llvm/bin/scriptc-llvm-codegen +0 -0
  8. package/dist/lib/llvm/package.json +1 -1
  9. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_array.o +0 -0
  10. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_bigint.o +0 -0
  11. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_bytes.o +0 -0
  12. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_bytes_io.o +0 -0
  13. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_copying.o +0 -0
  14. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_cycle.o +0 -0
  15. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_json.o +0 -0
  16. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_lib.o +0 -0
  17. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_map.o +0 -0
  18. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_number.o +0 -0
  19. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_regex.o +0 -0
  20. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/default/scr_string.o +0 -0
  21. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_array.o +0 -0
  22. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_bigint.o +0 -0
  23. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_bytes.o +0 -0
  24. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_bytes_io.o +0 -0
  25. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_copying.o +0 -0
  26. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_cycle.o +0 -0
  27. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_json.o +0 -0
  28. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_lib.o +0 -0
  29. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_map.o +0 -0
  30. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_number.o +0 -0
  31. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_regex.o +0 -0
  32. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic/scr_string.o +0 -0
  33. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/dynamic-text-decoder-legacy/scr_bytes.o +0 -0
  34. package/dist/lib/runtime-darwin-arm64/artifacts/dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  35. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_array.o +0 -0
  36. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_bigint.o +0 -0
  37. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_bytes.o +0 -0
  38. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_bytes_io.o +0 -0
  39. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_copying.o +0 -0
  40. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_cycle.o +0 -0
  41. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_json.o +0 -0
  42. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_lib.o +0 -0
  43. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_map.o +0 -0
  44. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_number.o +0 -0
  45. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_regex.o +0 -0
  46. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/default/scr_string.o +0 -0
  47. package/dist/lib/runtime-darwin-arm64/artifacts/library-dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  48. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_array.o +0 -0
  49. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_bigint.o +0 -0
  50. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_bytes.o +0 -0
  51. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_bytes_io.o +0 -0
  52. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_copying.o +0 -0
  53. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_cycle.o +0 -0
  54. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_json.o +0 -0
  55. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_lib.o +0 -0
  56. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_map.o +0 -0
  57. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_number.o +0 -0
  58. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_regex.o +0 -0
  59. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/default/scr_string.o +0 -0
  60. package/dist/lib/runtime-darwin-arm64/artifacts/library-release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  61. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_array.o +0 -0
  62. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_bigint.o +0 -0
  63. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_bytes.o +0 -0
  64. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_bytes_io.o +0 -0
  65. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_copying.o +0 -0
  66. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_cycle.o +0 -0
  67. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_json.o +0 -0
  68. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_lib.o +0 -0
  69. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_map.o +0 -0
  70. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_number.o +0 -0
  71. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_regex.o +0 -0
  72. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/default/scr_string.o +0 -0
  73. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-dev/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  74. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_array.o +0 -0
  75. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_bigint.o +0 -0
  76. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_bytes.o +0 -0
  77. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_bytes_io.o +0 -0
  78. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_copying.o +0 -0
  79. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_cycle.o +0 -0
  80. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_json.o +0 -0
  81. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_lib.o +0 -0
  82. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_map.o +0 -0
  83. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_number.o +0 -0
  84. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_regex.o +0 -0
  85. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/default/scr_string.o +0 -0
  86. package/dist/lib/runtime-darwin-arm64/artifacts/library-thread-release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  87. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_array.o +0 -0
  88. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_bigint.o +0 -0
  89. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_bytes.o +0 -0
  90. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_bytes_io.o +0 -0
  91. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_copying.o +0 -0
  92. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_cycle.o +0 -0
  93. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_json.o +0 -0
  94. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_lib.o +0 -0
  95. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_map.o +0 -0
  96. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_number.o +0 -0
  97. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_regex.o +0 -0
  98. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_string.o +0 -0
  99. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/default/scr_util_compare.o +0 -0
  100. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_array.o +0 -0
  101. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_bigint.o +0 -0
  102. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_bytes.o +0 -0
  103. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_bytes_io.o +0 -0
  104. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_copying.o +0 -0
  105. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_cycle.o +0 -0
  106. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_json.o +0 -0
  107. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_lib.o +0 -0
  108. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_map.o +0 -0
  109. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_number.o +0 -0
  110. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_regex.o +0 -0
  111. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_string.o +0 -0
  112. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic/scr_util_compare.o +0 -0
  113. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/dynamic-text-decoder-legacy/scr_bytes.o +0 -0
  114. package/dist/lib/runtime-darwin-arm64/artifacts/release/runtime/text-decoder-legacy/scr_bytes.o +0 -0
  115. package/dist/lib/runtime-darwin-arm64/package.json +1 -1
  116. package/dist/lib/runtime-darwin-arm64/runtime-pack.json +213 -213
  117. package/dist/lib/runtime-sources/package.json +1 -1
  118. package/dist/lib/runtime-sources/src/scr_array.c +317 -40
  119. package/dist/lib/runtime-sources/src/scr_async.test.ts +16 -6
  120. package/dist/lib/runtime-sources/src/scr_bigint.c +15 -0
  121. package/dist/lib/runtime-sources/src/scr_bytes.c +437 -216
  122. package/dist/lib/runtime-sources/src/scr_bytes.test.c +85 -0
  123. package/dist/lib/runtime-sources/src/scr_bytes_io.c +0 -34
  124. package/dist/lib/runtime-sources/src/scr_copying.c +12 -2
  125. package/dist/lib/runtime-sources/src/scr_cycle.c +84 -60
  126. package/dist/lib/runtime-sources/src/scr_json.c +494 -161
  127. package/dist/lib/runtime-sources/src/scr_json.test.c +323 -1
  128. package/dist/lib/runtime-sources/src/scr_json.test.ts +58 -25
  129. package/dist/lib/runtime-sources/src/scr_key.h +68 -0
  130. package/dist/lib/runtime-sources/src/scr_lib.c +177 -89
  131. package/dist/lib/runtime-sources/src/scr_map.c +268 -96
  132. package/dist/lib/runtime-sources/src/scr_number.c +2 -4
  133. package/dist/lib/runtime-sources/src/scr_numeric.h +25 -0
  134. package/dist/lib/runtime-sources/src/scr_numeric.test.c +37 -0
  135. package/dist/lib/runtime-sources/src/scr_numeric.test.ts +33 -0
  136. package/dist/lib/runtime-sources/src/scr_regex.c +255 -145
  137. package/dist/lib/runtime-sources/src/scr_runtime.h +76 -19
  138. package/dist/lib/runtime-sources/src/scr_string.c +184 -42
  139. package/package.json +1 -1
@@ -15,6 +15,7 @@
15
15
  * keep the lean 1-word header and never touch the collector.
16
16
  */
17
17
  #include "scr_runtime.h"
18
+ #include "scr_key.h"
18
19
 
19
20
  #include <stdio.h>
20
21
  #include <stdlib.h>
@@ -46,17 +47,14 @@ static uint64_t scr_map_f64_bits(double k) {
46
47
  return bits;
47
48
  }
48
49
 
49
- static uint64_t scr_map_fnv1a(const unsigned char *bytes, size_t n) {
50
- uint64_t h = UINT64_C(0xcbf29ce484222325);
51
- for (size_t i = 0; i < n; i++) {
52
- h ^= bytes[i];
53
- h *= UINT64_C(0x100000001b3);
54
- }
55
- return h;
50
+ static uint64_t scr_map_hash_str(const ScrStr *k) {
51
+ return scr_key_hash(k->data, k->len);
56
52
  }
57
53
 
58
- static uint64_t scr_map_hash_str(const ScrStr *k) {
59
- return scr_map_fnv1a((const unsigned char *)k->data, k->len);
54
+ /* Mix the complete normalized number or identity word. Float exponents
55
+ * and aligned pointers need high bits to reach the low bucket-index bits. */
56
+ static uint64_t scr_map_hash_word(uint64_t value) {
57
+ return scr_key_mix(value);
60
58
  }
61
59
 
62
60
  /* ── slot packing (8-byte slots, like ScrArr) ──────────────────────────── */
@@ -98,12 +96,7 @@ static uint64_t scr_map_hash_dyn(const ScrDyn *d) {
98
96
  case SCR_DYN_NULL: value = 0; break;
99
97
  case SCR_DYN_BOOL: value = d->v.b; break;
100
98
  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
- }
99
+ case SCR_DYN_BIGINT: return scr_bigint_hash(d->v.bigint);
107
100
  case SCR_DYN_STR: return scr_map_hash_str(d->v.str);
108
101
  case SCR_DYN_SYMBOL: value = scr_map_slot_from_ptr(d->v.symbol.value); break;
109
102
  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 +115,7 @@ static uint64_t scr_map_hash_dyn(const ScrDyn *d) {
122
115
  case SCR_DYN_JSVAL: value = 0; break;
123
116
  default: value = scr_map_slot_from_ptr((void *)d); break;
124
117
  }
125
- return scr_map_fnv1a((const unsigned char *)&value, sizeof value) ^ kind;
118
+ return scr_map_hash_word(value) ^ kind;
126
119
  }
127
120
 
128
121
  static bool scr_map_dyn_eq(const ScrDyn *a, const ScrDyn *b) {
@@ -135,16 +128,50 @@ static bool scr_map_dyn_eq(const ScrDyn *a, const ScrDyn *b) {
135
128
  return scr_dyn_strict_eq(a, b);
136
129
  }
137
130
 
131
+ static uint64_t scr_map_hash_union(const ScrMap *m, const ScrUnion *key) {
132
+ ScrMapKeyKind kind = (ScrMapKeyKind)m->union_keys[key->tag];
133
+ switch (kind) {
134
+ case SCR_MAP_KEY_STR: return scr_map_hash_str(scr_union_peek(key));
135
+ case SCR_MAP_KEY_BIGINT: return scr_bigint_hash(scr_union_peek(key));
136
+ case SCR_MAP_KEY_F64: return scr_map_hash_word(scr_map_f64_bits(scr_map_slot_to_f64(key->slot)));
137
+ case SCR_MAP_KEY_NULL:
138
+ case SCR_MAP_KEY_UNDEFINED: return scr_map_hash_word((uint64_t)kind);
139
+ default: return scr_map_hash_word(key->slot);
140
+ }
141
+ }
142
+
143
+ static bool scr_map_union_eq(const ScrMap *m, const ScrUnion *a, const ScrUnion *b) {
144
+ ScrMapKeyKind kind = (ScrMapKeyKind)m->union_keys[a->tag];
145
+ if (kind != (ScrMapKeyKind)m->union_keys[b->tag]) return false;
146
+ switch (kind) {
147
+ case SCR_MAP_KEY_STR: return scr_str_eq(scr_union_peek(a), scr_union_peek(b));
148
+ case SCR_MAP_KEY_BIGINT: return scr_bigint_eq(scr_union_peek(a), scr_union_peek(b));
149
+ case SCR_MAP_KEY_F64:
150
+ return scr_map_f64_bits(scr_map_slot_to_f64(a->slot)) == scr_map_f64_bits(scr_map_slot_to_f64(b->slot));
151
+ case SCR_MAP_KEY_NULL:
152
+ case SCR_MAP_KEY_UNDEFINED: return true;
153
+ default: return a->slot == b->slot;
154
+ }
155
+ }
156
+
138
157
  static uint64_t scr_map_hash_key(const ScrMap *m, uint64_t key) {
139
158
  if (m->key_kind == SCR_MAP_KEY_STR) return scr_map_hash_str((ScrStr *)scr_map_slot_to_ptr(key));
140
159
  if (m->key_kind == SCR_MAP_KEY_DYN) return scr_map_hash_dyn((ScrDyn *)scr_map_slot_to_ptr(key));
160
+ if (m->key_kind == SCR_MAP_KEY_BIGINT) return scr_bigint_hash(scr_map_slot_to_ptr(key));
161
+ if (m->key_kind == SCR_MAP_KEY_UNION_VALUE) return scr_map_hash_union(m, scr_map_slot_to_ptr(key));
141
162
  uint64_t identity = scr_map_identity(m, key);
142
- return scr_map_fnv1a((const unsigned char *)&identity, sizeof identity);
163
+ return scr_map_hash_word(identity);
143
164
  }
144
165
 
145
166
  /* Stored keys are pre-normalized, so bit equality IS SameValueZero for f64
146
167
  * keys (probe keys normalize through the same function). */
147
168
  static bool scr_map_key_eq(const ScrMap *m, uint64_t stored, uint64_t probe) {
169
+ if (m->key_kind == SCR_MAP_KEY_UNION_VALUE) {
170
+ return scr_map_union_eq(m, scr_map_slot_to_ptr(stored), scr_map_slot_to_ptr(probe));
171
+ }
172
+ if (m->key_kind == SCR_MAP_KEY_BIGINT) {
173
+ return scr_bigint_eq(scr_map_slot_to_ptr(stored), scr_map_slot_to_ptr(probe));
174
+ }
148
175
  if (m->key_kind == SCR_MAP_KEY_DYN) {
149
176
  return scr_map_dyn_eq((ScrDyn *)scr_map_slot_to_ptr(stored), (ScrDyn *)scr_map_slot_to_ptr(probe));
150
177
  }
@@ -156,16 +183,34 @@ static bool scr_map_key_eq(const ScrMap *m, uint64_t stored, uint64_t probe) {
156
183
  }
157
184
 
158
185
  /* ── 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) {
186
+ * Returns the matching bucket, or the empty bucket where a new key belongs.
187
+ * A zero entry hash marks a tombstone; cached hashes reject unrelated keys
188
+ * before equality and survive table growth without hashing the keys again.
189
+ * Remap zero to one so every live entry has a nonzero hash. */
190
+ static size_t scr_map_probe(const ScrMap *m, uint64_t hash, uint64_t key) {
162
191
  if (m->nbuckets == 0) return SCR_MAP_EMPTY;
163
192
  size_t mask = m->nbuckets - 1;
164
193
  for (size_t i = hash & mask;; i = (i + 1) & mask) {
165
194
  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;
195
+ if (b == SCR_MAP_EMPTY) return i;
196
+ if (m->entries[b].hash == hash && scr_map_key_eq(m, m->entries[b].key, key)) return i;
197
+ }
198
+ }
199
+
200
+ #define SCR_MAP_LINEAR_LIMIT 4
201
+
202
+ /* Small collections keep only their ordered entries. Cached hashes make
203
+ * the bounded scan cheap without allocating a separate bucket table. */
204
+ static size_t scr_map_find(const ScrMap *m, uint64_t hash, uint64_t key) {
205
+ hash = hash ? hash : 1;
206
+ if (m->nbuckets == 0) {
207
+ for (size_t e = 0; e < m->nentries; e++) {
208
+ if (m->entries[e].hash == hash && scr_map_key_eq(m, m->entries[e].key, key)) return e;
209
+ }
210
+ return SCR_MAP_EMPTY;
168
211
  }
212
+ size_t slot = scr_map_probe(m, hash, key);
213
+ return slot == SCR_MAP_EMPTY ? SCR_MAP_EMPTY : m->buckets[slot];
169
214
  }
170
215
 
171
216
  /* Rebuild the bucket table (size must be a power of two >= 2 * nentries):
@@ -179,8 +224,8 @@ static void scr_map_rebuild_buckets(ScrMap *m, size_t nbuckets) {
179
224
  m->nbuckets = nbuckets;
180
225
  size_t mask = nbuckets - 1;
181
226
  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);
227
+ uint64_t hash = m->entries[e].hash;
228
+ if (!hash) continue;
184
229
  size_t i = hash & mask;
185
230
  while (buckets[i] != SCR_MAP_EMPTY) i = (i + 1) & mask;
186
231
  buckets[i] = e;
@@ -192,7 +237,7 @@ static void scr_map_rebuild_buckets(ScrMap *m, size_t nbuckets) {
192
237
  static void scr_map_compact(ScrMap *m) {
193
238
  size_t w = 0;
194
239
  for (size_t r = 0; r < m->nentries; r++) {
195
- if (m->entries[r].live) m->entries[w++] = m->entries[r];
240
+ if (m->entries[r].hash) m->entries[w++] = m->entries[r];
196
241
  }
197
242
  m->nentries = w;
198
243
  if (m->nbuckets > 0) scr_map_rebuild_buckets(m, m->nbuckets);
@@ -202,12 +247,15 @@ static void scr_map_compact(ScrMap *m) {
202
247
  * reuse their storage) and grows otherwise; while an iteration is active it
203
248
  * ONLY grows — indices must stay stable under callback mutation. */
204
249
  static void scr_map_reserve_append(ScrMap *m) {
205
- if (m->nentries < m->ecap && m->nbuckets >= 2 * (m->nentries + 1)) return;
206
- if (m->iter_depth == 0 && m->nlive <= m->nentries / 2 && m->nentries > 0) {
250
+ if (m->nentries < m->ecap &&
251
+ ((m->nbuckets == 0 && m->nentries < SCR_MAP_LINEAR_LIMIT) ||
252
+ m->nbuckets >= 2 * (m->nentries + 1))) return;
253
+ if (m->iter_depth == 0 && m->nentries > 0 &&
254
+ (m->nbuckets == 0 ? m->nlive < m->nentries : m->nlive <= m->nentries / 2)) {
207
255
  scr_map_compact(m);
208
256
  }
209
257
  if (m->nentries == m->ecap) {
210
- size_t cap = m->ecap ? m->ecap : 8;
258
+ size_t cap = m->ecap ? m->ecap : 4;
211
259
  while (cap < m->nentries + 1) {
212
260
  if (cap > SIZE_MAX / 2 / sizeof(ScrMapEntry)) scr_map_oom();
213
261
  cap *= 2;
@@ -217,6 +265,7 @@ static void scr_map_reserve_append(ScrMap *m) {
217
265
  m->entries = entries;
218
266
  m->ecap = cap;
219
267
  }
268
+ if (m->nbuckets == 0 && m->nentries < SCR_MAP_LINEAR_LIMIT) return;
220
269
  if (m->nbuckets < 2 * (m->nentries + 1)) {
221
270
  size_t nbuckets = m->nbuckets ? m->nbuckets : 16;
222
271
  while (nbuckets < 2 * (m->nentries + 1)) {
@@ -229,10 +278,15 @@ static void scr_map_reserve_append(ScrMap *m) {
229
278
 
230
279
  /* ── entry release helpers ─────────────────────────────────────────────── */
231
280
 
281
+ static bool scr_map_ref_key(ScrMapKeyKind kind) {
282
+ return kind == SCR_MAP_KEY_REF || kind == SCR_MAP_KEY_UNION_REF || kind == SCR_MAP_KEY_DYN ||
283
+ kind == SCR_MAP_KEY_BIGINT || kind == SCR_MAP_KEY_UNION_VALUE;
284
+ }
285
+
232
286
  static void scr_map_release_key(ScrMap *m, uint64_t key) {
233
287
  if (m->key_kind == SCR_MAP_KEY_STR) {
234
288
  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) {
289
+ } else if (scr_map_ref_key(m->key_kind)) {
236
290
  m->key_release(scr_map_slot_to_ptr(key));
237
291
  }
238
292
  }
@@ -243,10 +297,14 @@ static void scr_map_release_val(ScrMap *m, uint64_t val) {
243
297
 
244
298
  /* ── lifecycle ─────────────────────────────────────────────────────────── */
245
299
 
300
+ void scr_map_union_keys(ScrMap *map, const uint8_t *kinds) {
301
+ map->union_keys = kinds;
302
+ }
303
+
246
304
  static void scr_map_trace(void *o, ScrTraceVisit visit, void *ctx) {
247
305
  ScrMap *m = (ScrMap *)o;
248
306
  for (size_t e = 0; e < m->nentries; e++) {
249
- if (!m->entries[e].live) continue;
307
+ if (!m->entries[e].hash) continue;
250
308
  if (m->key_trace) visit(scr_map_slot_to_ptr(m->entries[e].key), ctx);
251
309
  if (m->val_trace) visit(scr_map_slot_to_ptr(m->entries[e].val), ctx);
252
310
  }
@@ -258,7 +316,7 @@ static void scr_map_gcfree(void *o) {
258
316
  ScrMap *m = (ScrMap *)o;
259
317
  scr_weak_dispose(m);
260
318
  for (size_t e = 0; e < m->nentries; e++) {
261
- if (!m->entries[e].live) continue;
319
+ if (!m->entries[e].hash) continue;
262
320
  if (!m->key_trace) scr_map_release_key(m, m->entries[e].key);
263
321
  if (!m->val_trace) scr_map_release_val(m, m->entries[e].val);
264
322
  }
@@ -318,7 +376,7 @@ void scr_map_release(ScrMap *m) {
318
376
  scr_weak_dispose(m);
319
377
  if (m->key_trace || m->val_trace) scr_cyc_on_dead(m);
320
378
  for (size_t e = 0; e < m->nentries; e++) {
321
- if (!m->entries[e].live) continue;
379
+ if (!m->entries[e].hash) continue;
322
380
  scr_map_release_key(m, m->entries[e].key);
323
381
  scr_map_release_val(m, m->entries[e].val);
324
382
  }
@@ -344,8 +402,8 @@ double scr_map_size(const ScrMap *m) { return (double)m->nlive; }
344
402
 
345
403
  void scr_map_clear(ScrMap *m) {
346
404
  for (size_t e = 0; e < m->nentries; e++) {
347
- if (!m->entries[e].live) continue;
348
- m->entries[e].live = false;
405
+ if (!m->entries[e].hash) continue;
406
+ m->entries[e].hash = 0;
349
407
  scr_map_release_key(m, m->entries[e].key);
350
408
  scr_map_release_val(m, m->entries[e].val);
351
409
  }
@@ -363,7 +421,7 @@ void scr_map_clear(ScrMap *m) {
363
421
 
364
422
  bool scr_map_has_f64(const ScrMap *m, double key) {
365
423
  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;
424
+ return scr_map_find(m, scr_map_hash_word(k), k) != SCR_MAP_EMPTY;
367
425
  }
368
426
 
369
427
  bool scr_map_has_str(const ScrMap *m, const ScrStr *key) {
@@ -373,7 +431,7 @@ bool scr_map_has_str(const ScrMap *m, const ScrStr *key) {
373
431
 
374
432
  static bool scr_map_delete_found(ScrMap *m, size_t e) {
375
433
  if (e == SCR_MAP_EMPTY) return false;
376
- m->entries[e].live = false; /* bucket slot stays: probe chains intact */
434
+ m->entries[e].hash = 0; /* bucket slot stays: probe chains intact */
377
435
  m->nlive--;
378
436
  scr_map_release_key(m, m->entries[e].key);
379
437
  scr_map_release_val(m, m->entries[e].val);
@@ -382,7 +440,7 @@ static bool scr_map_delete_found(ScrMap *m, size_t e) {
382
440
 
383
441
  bool scr_map_delete_f64(ScrMap *m, double key) {
384
442
  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));
443
+ return scr_map_delete_found(m, scr_map_find(m, scr_map_hash_word(k), k));
386
444
  }
387
445
 
388
446
  bool scr_map_delete_str(ScrMap *m, const ScrStr *key) {
@@ -412,18 +470,28 @@ bool scr_map_delete_ref(ScrMap *m, const void *key) {
412
470
  * insertion slot. */
413
471
 
414
472
  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);
473
+ hash = hash ? hash : 1;
474
+ size_t slot = scr_map_probe(m, hash, key);
475
+ size_t e = m->nbuckets == 0 ? scr_map_find(m, hash, key) : m->buckets[slot];
416
476
  if (e != SCR_MAP_EMPTY) {
417
477
  uint64_t old = m->entries[e].val;
418
478
  m->entries[e].val = val; /* unlink before releasing (cycle collector) */
419
479
  scr_map_release_val(m, old);
420
480
  return;
421
481
  }
482
+ size_t old_entries = m->nentries, old_buckets = m->nbuckets;
422
483
  scr_map_reserve_append(m);
484
+ /* Growth or compaction can move the insertion bucket. Otherwise keep the
485
+ * empty slot from the first probe instead of walking the chain twice. */
486
+ if (m->nbuckets && (m->nentries != old_entries || m->nbuckets != old_buckets)) {
487
+ size_t mask = m->nbuckets - 1;
488
+ slot = hash & mask;
489
+ while (m->buckets[slot] != SCR_MAP_EMPTY) slot = (slot + 1) & mask;
490
+ }
423
491
  size_t idx = m->nentries++;
424
492
  m->entries[idx].key = key;
425
493
  m->entries[idx].val = val;
426
- m->entries[idx].live = true;
494
+ m->entries[idx].hash = hash;
427
495
  m->nlive++;
428
496
  if (m->key_kind == SCR_MAP_KEY_STR) {
429
497
  scr_str_retain((ScrStr *)scr_map_slot_to_ptr(key)); /* key is borrowed */
@@ -436,18 +504,21 @@ static void scr_map_set(ScrMap *m, uint64_t hash, uint64_t key, uint64_t val) {
436
504
  } else {
437
505
  m->key_retain(scr_map_slot_to_ptr(key));
438
506
  }
439
- } else if (m->key_kind == SCR_MAP_KEY_REF || m->key_kind == SCR_MAP_KEY_UNION_REF) {
507
+ } else if (m->key_kind == SCR_MAP_KEY_UNION_VALUE &&
508
+ m->union_keys[((ScrUnion *)scr_map_slot_to_ptr(key))->tag] == SCR_MAP_KEY_F64 &&
509
+ ((ScrUnion *)scr_map_slot_to_ptr(key))->slot == UINT64_C(0x8000000000000000)) {
510
+ /* Normalize stored -0 without mutating the caller's immutable box. */
511
+ ScrUnion *u = scr_map_slot_to_ptr(key);
512
+ m->entries[idx].key = scr_map_slot_from_ptr(scr_union_new_f64(u->tag, 0));
513
+ } else if (scr_map_ref_key(m->key_kind)) {
440
514
  m->key_retain(scr_map_slot_to_ptr(key)); /* key is borrowed */
441
515
  }
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;
516
+ if (m->nbuckets) m->buckets[slot] = idx;
446
517
  }
447
518
 
448
519
  static void scr_map_set_f64_key(ScrMap *m, double key, uint64_t val) {
449
520
  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);
521
+ scr_map_set(m, scr_map_hash_word(k), k, val);
451
522
  }
452
523
 
453
524
  static void scr_map_set_str_key(ScrMap *m, ScrStr *key, uint64_t val) {
@@ -501,7 +572,7 @@ ScrMap *scr_set_new_ref(void *(*elem_retain)(void *), void (*elem_release)(void
501
572
 
502
573
  static size_t scr_map_find_f64(const ScrMap *m, double key) {
503
574
  uint64_t k = scr_map_f64_bits(key);
504
- return scr_map_find(m, scr_map_fnv1a((const unsigned char *)&k, 8), k);
575
+ return scr_map_find(m, scr_map_hash_word(k), k);
505
576
  }
506
577
 
507
578
  static size_t scr_map_find_str(const ScrMap *m, const ScrStr *key) {
@@ -568,17 +639,63 @@ void *scr_map_get_ref_ref(const ScrMap *m, const void *key) {
568
639
  return m->val_retain(scr_map_slot_to_ptr(m->entries[e].val));
569
640
  }
570
641
 
642
+ /* Boolean keys use unboxed 0/1 slots and the same probe path. */
643
+ static size_t scr_map_find_bool(const ScrMap *m, bool key) {
644
+ uint64_t slot = key ? 1 : 0;
645
+ return scr_map_find(m, scr_map_hash_word(slot), slot);
646
+ }
647
+
648
+ bool scr_map_has_bool(const ScrMap *m, bool key) {
649
+ return scr_map_find_bool(m, key) != SCR_MAP_EMPTY;
650
+ }
651
+
652
+ bool scr_map_delete_bool(ScrMap *m, bool key) {
653
+ return scr_map_delete_found(m, scr_map_find_bool(m, key));
654
+ }
655
+
656
+ void scr_map_set_bool_f64(ScrMap *m, bool key, double value) {
657
+ scr_map_set(m, scr_map_hash_word(key ? 1 : 0), key ? 1 : 0, scr_map_slot_from_f64(value));
658
+ }
659
+
660
+ void scr_map_set_bool_bool(ScrMap *m, bool key, bool value) {
661
+ scr_map_set(m, scr_map_hash_word(key ? 1 : 0), key ? 1 : 0, value ? 1 : 0);
662
+ }
663
+
664
+ void scr_map_set_bool_ref(ScrMap *m, bool key, void *value) {
665
+ scr_map_set(m, scr_map_hash_word(key ? 1 : 0), key ? 1 : 0, scr_map_slot_from_ptr(value));
666
+ }
667
+
668
+ bool scr_map_get_bool_f64(const ScrMap *m, bool key, double *out) {
669
+ size_t entry = scr_map_find_bool(m, key);
670
+ if (entry == SCR_MAP_EMPTY) return false;
671
+ *out = scr_map_slot_to_f64(m->entries[entry].val);
672
+ return true;
673
+ }
674
+
675
+ bool scr_map_get_bool_bool(const ScrMap *m, bool key, bool *out) {
676
+ size_t entry = scr_map_find_bool(m, key);
677
+ if (entry == SCR_MAP_EMPTY) return false;
678
+ *out = m->entries[entry].val != 0;
679
+ return true;
680
+ }
681
+
682
+ void *scr_map_get_bool_ref(const ScrMap *m, bool key) {
683
+ size_t entry = scr_map_find_bool(m, key);
684
+ if (entry == SCR_MAP_EMPTY) return NULL;
685
+ return m->val_retain(scr_map_slot_to_ptr(m->entries[entry].val));
686
+ }
687
+
571
688
  /* ── iteration primitives (the forEach desugar) ────────────────────────── */
572
689
 
573
690
  double scr_map_iter_count(const ScrMap *m) { return (double)m->nentries; }
574
691
 
575
692
  bool scr_map_iter_live(const ScrMap *m, double i) {
576
693
  if (!(i >= 0) || i >= (double)m->nentries) return false;
577
- return m->entries[(size_t)i].live;
694
+ return m->entries[(size_t)i].hash;
578
695
  }
579
696
 
580
697
  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) {
698
+ if (!(i >= 0) || i >= (double)m->nentries || !m->entries[(size_t)i].hash) {
582
699
  scr_trap("scriptc: internal error: map iteration index out of range\n");
583
700
  }
584
701
  return &m->entries[(size_t)i];
@@ -588,6 +705,10 @@ double scr_map_iter_key_f64(const ScrMap *m, double i) {
588
705
  return scr_map_slot_to_f64(scr_map_iter_at(m, i)->key);
589
706
  }
590
707
 
708
+ bool scr_map_iter_key_bool(const ScrMap *m, double i) {
709
+ return scr_map_iter_at(m, i)->key != 0;
710
+ }
711
+
591
712
  ScrStr *scr_map_iter_key_str(const ScrMap *m, double i) {
592
713
  return scr_str_retain((ScrStr *)scr_map_slot_to_ptr(scr_map_iter_at(m, i)->key));
593
714
  }
@@ -649,6 +770,68 @@ void scr_map_dyn_set(ScrMap *map, const ScrDyn *key, const ScrDyn *value, bool s
649
770
  scr_map_release_key(map, key_slot);
650
771
  }
651
772
 
773
+ /* Copy only live entries. The source is borrowed, contains unique keys,
774
+ * and runs no user code while traversed, so cached hashes remain valid. */
775
+ ScrMap *scr_map_clone(const ScrMap *source, bool keys_only) {
776
+ ScrMap *out = scr_map_new_typed(source->key_kind, keys_only ? SCR_MAP_VAL_F64 : source->val_kind,
777
+ source->key_retain, source->key_release, source->key_trace,
778
+ keys_only ? NULL : source->val_retain, keys_only ? NULL : source->val_release,
779
+ keys_only ? NULL : source->val_trace);
780
+ out->union_keys = source->union_keys;
781
+ size_t count = source->nlive;
782
+ if (!count) return out;
783
+ if (count > SIZE_MAX / sizeof(ScrMapEntry) || count > SIZE_MAX / 2 / sizeof(size_t)) scr_map_oom();
784
+ out->entries = malloc(count * sizeof(ScrMapEntry));
785
+ if (!out->entries) scr_map_oom();
786
+ out->ecap = count;
787
+ for (size_t i = 0; i < source->nentries; i++) {
788
+ const ScrMapEntry *entry = &source->entries[i];
789
+ if (!entry->hash) continue;
790
+ ScrMapEntry *copy = &out->entries[out->nentries++];
791
+ *copy = *entry;
792
+ if (out->key_kind == SCR_MAP_KEY_STR) scr_str_retain(scr_map_slot_to_ptr(copy->key));
793
+ else if (scr_map_ref_key(out->key_kind)) out->key_retain(scr_map_slot_to_ptr(copy->key));
794
+ if (keys_only) copy->val = 0;
795
+ else if (out->val_kind == SCR_MAP_VAL_REF) out->val_retain(scr_map_slot_to_ptr(copy->val));
796
+ }
797
+ out->nlive = count;
798
+ if (count <= SCR_MAP_LINEAR_LIMIT) return out;
799
+ size_t buckets = 16;
800
+ while (buckets < 2 * count) {
801
+ if (buckets > SIZE_MAX / 2 / sizeof(size_t)) scr_map_oom();
802
+ buckets *= 2;
803
+ }
804
+ scr_map_rebuild_buckets(out, buckets);
805
+ return out;
806
+ }
807
+
808
+ /* A generic view can be backed by typed slots. Its copy must accept new
809
+ * JavaScript values independently of that backing's narrower adapters. */
810
+ ScrMap *scr_map_clone_dyn(const ScrMap *source, bool keys_only) {
811
+ ScrMap *out = scr_map_new_typed(SCR_MAP_KEY_DYN, keys_only ? SCR_MAP_VAL_F64 : SCR_MAP_VAL_REF,
812
+ scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v,
813
+ keys_only ? NULL : scr_dyn_retain_v, keys_only ? NULL : scr_dyn_release_v,
814
+ keys_only ? NULL : scr_dyn_trace_v);
815
+ for (size_t i = 0; i < source->nentries; i++) {
816
+ if (!source->entries[i].hash) continue;
817
+ ScrDyn *key = scr_map_dyn_key(source, (double)i);
818
+ uint64_t value = keys_only ? 0 : scr_map_slot_from_ptr(scr_map_dyn_value(source, (double)i));
819
+ scr_map_set(out, scr_map_hash_dyn(key), scr_map_slot_from_ptr(key), value);
820
+ scr_dyn_release(key);
821
+ }
822
+ return out;
823
+ }
824
+
825
+ void scr_map_values_into(ScrMap *set, const ScrMap *source, bool checked) {
826
+ for (size_t i = 0; i < source->nentries; i++) {
827
+ if (!source->entries[i].hash) continue;
828
+ uint64_t value = checked ? scr_map_slot_from_ptr(scr_map_dyn_value(source, (double)i)) : source->entries[i].val;
829
+ if (set->key_kind == SCR_MAP_KEY_F64) value = scr_map_f64_bits(scr_map_slot_to_f64(value));
830
+ scr_map_set(set, scr_map_hash_key(set, value), value, 0);
831
+ if (checked) scr_dyn_release(scr_map_slot_to_ptr(value));
832
+ }
833
+ }
834
+
652
835
  ScrArr *scr_set_to_arr_dyn(const ScrMap *map) {
653
836
  ScrArr *out = scr_arr_new_ref(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v, map->nlive);
654
837
  for (size_t i = 0; i < map->nentries; i++) {
@@ -683,14 +866,23 @@ void scr_map_iter_exit(ScrMap *m) {
683
866
  void scr_set_add_all(ScrMap *set, ScrArr *values) {
684
867
  size_t n = values->len;
685
868
  for (size_t i = 0; i < n; i++) {
686
- if (values->elem == SCR_ELEM_STR) {
869
+ /* Packed values stay owned by the seed array throughout construction.
870
+ * Avoid taking a temporary owner only to release it after insertion.
871
+ * Sparse/missing positions retain the existing accessor semantics. */
872
+ if (i < values->cap && values->present[i] == SCR_ARR_VALUE) {
873
+ uint64_t key = values->data[i];
874
+ if (set->key_kind == SCR_MAP_KEY_F64) key = scr_map_f64_bits(scr_map_slot_to_f64(key));
875
+ scr_map_set(set, scr_map_hash_key(set, key), key, 0);
876
+ } else if (values->elem == SCR_ELEM_STR) {
687
877
  ScrStr *s = (ScrStr *)scr_arr_get_ref(values, (double)i); /* +1 */
688
878
  scr_map_set_str_f64(set, s, 0); /* borrows; retains stored copy */
689
879
  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) {
880
+ } else if (scr_map_ref_key(set->key_kind)) {
691
881
  void *p = scr_arr_get_ref(values, (double)i); /* +1 */
692
882
  scr_map_set_ref_f64(set, p, 0); /* borrows; retains stored copy */
693
883
  set->key_release(p);
884
+ } else if (set->key_kind == SCR_MAP_KEY_BOOL) {
885
+ scr_map_set_bool_f64(set, scr_arr_get_bool(values, (double)i), 0);
694
886
  } else {
695
887
  scr_map_set_f64_f64(set, scr_arr_get_f64(values, (double)i), 0);
696
888
  }
@@ -702,59 +894,39 @@ void scr_set_add_all(ScrMap *set, ScrArr *values) {
702
894
  /* Canonical array index test: "0".."4294967294" — digits only, no leading
703
895
  * zero (except "0" itself), value <= 2^32 - 2. JS orders these OWN keys
704
896
  * first, ascending numerically, before every other string key. */
705
- static bool scr_map_key_array_index(const ScrStr *k, uint32_t *out) {
706
- size_t n = k->len;
707
- if (n == 0 || n > 10) return false;
708
- const char *s = k->data;
709
- if (s[0] == '0' && n > 1) return false;
710
- uint64_t v = 0;
711
- for (size_t i = 0; i < n; i++) {
712
- if (s[i] < '0' || s[i] > '9') return false;
713
- v = v * 10 + (uint64_t)(s[i] - '0');
714
- }
715
- if (v > UINT64_C(4294967294)) return false;
716
- *out = (uint32_t)v;
717
- return true;
897
+ static bool scr_map_key_array_index(const ScrStr *key, uint32_t *out) {
898
+ return scr_key_array_index(key->data, key->len, out);
718
899
  }
719
900
 
720
- /* The live STRING keys of `m` in JS OWN-KEY ORDER (Object.keys over the
721
- * index-signature overflow): integer-like keys (canonical array indices)
722
- * first in ascending numeric order, then the rest in insertion order.
723
- * Returns a fresh ScrStr* array (+1); the map is borrowed. Insertion sort
724
- * over the integer-like subset — overflow maps are small, and ties are
725
- * impossible (keys are unique). */
901
+ /* Enumerate numeric keys in ascending order, then other keys in insertion
902
+ * order. Scratch entries refer to stable map indices, not copied strings. */
726
903
  ScrArr *scr_map_keys_js_order(const ScrMap *m) {
727
- size_t n = m->nentries;
728
904
  ScrArr *out = scr_arr_new(SCR_ELEM_STR, m->nlive);
729
- size_t nidx = 0;
730
- struct { uint32_t v; ScrStr *k; } *idx = NULL;
731
- for (size_t i = 0; i < n; i++) {
732
- if (!m->entries[i].live) continue;
733
- ScrStr *k = (ScrStr *)scr_map_slot_to_ptr(m->entries[i].key);
734
- uint32_t v;
735
- if (!scr_map_key_array_index(k, &v)) continue;
736
- if (nidx % 16 == 0) {
737
- idx = realloc(idx, (nidx + 16) * sizeof *idx);
738
- if (!idx) scr_map_oom();
905
+ if (m->nlive > SIZE_MAX / sizeof(ScrKeyIndex)) scr_map_oom();
906
+ ScrKeyIndex *indices = NULL;
907
+ size_t count = 0;
908
+ for (size_t i = 0; i < m->nentries; i++) {
909
+ if (!m->entries[i].hash) continue;
910
+ ScrStr *key = scr_map_slot_to_ptr(m->entries[i].key);
911
+ uint32_t index;
912
+ if (!scr_map_key_array_index(key, &index)) continue;
913
+ if (!indices) {
914
+ indices = malloc(m->nlive * sizeof *indices);
915
+ if (!indices) scr_map_oom();
739
916
  }
740
- size_t j = nidx++;
741
- while (j > 0 && idx[j - 1].v > v) {
742
- idx[j] = idx[j - 1];
743
- j--;
744
- }
745
- idx[j].v = v;
746
- idx[j].k = k;
917
+ indices[count++] = (ScrKeyIndex){index, i};
747
918
  }
748
- for (size_t i = 0; i < nidx; i++) {
749
- scr_arr_push_ref(out, scr_str_retain(idx[i].k));
919
+ scr_key_index_sort(indices, count);
920
+ for (size_t i = 0; i < count; i++) {
921
+ ScrStr *key = scr_map_slot_to_ptr(m->entries[indices[i].entry].key);
922
+ scr_arr_push_ref(out, scr_str_retain(key));
750
923
  }
751
- free(idx);
752
- for (size_t i = 0; i < n; i++) {
753
- if (!m->entries[i].live) continue;
754
- ScrStr *k = (ScrStr *)scr_map_slot_to_ptr(m->entries[i].key);
755
- uint32_t v;
756
- if (scr_map_key_array_index(k, &v)) continue;
757
- scr_arr_push_ref(out, scr_str_retain(k));
924
+ free(indices);
925
+ for (size_t i = 0; i < m->nentries; i++) {
926
+ if (!m->entries[i].hash) continue;
927
+ ScrStr *key = scr_map_slot_to_ptr(m->entries[i].key);
928
+ uint32_t index;
929
+ if (!scr_map_key_array_index(key, &index)) scr_arr_push_ref(out, scr_str_retain(key));
758
930
  }
759
931
  return out;
760
932
  }
@@ -14,6 +14,7 @@
14
14
  * and the 1M-double fuzz gate (packages/runtime/test/gen-number-cases.mjs).
15
15
  */
16
16
  #include "scr_runtime.h"
17
+ #include "scr_numeric.h"
17
18
 
18
19
  #include <math.h>
19
20
  #include <stdint.h>
@@ -27,10 +28,7 @@
27
28
  /* ECMA ToUint32, shared by bitwise operators and split's limit: non-finite
28
29
  * values become zero, finite values truncate toward zero and wrap mod 2^32. */
29
30
  uint32_t scr_to_uint32(double d) {
30
- if (!isfinite(d)) return 0;
31
- double t = fmod(trunc(d), 4294967296.0);
32
- if (t < 0) t += 4294967296.0;
33
- return (uint32_t)t;
31
+ return scr_numeric_to_u32(d);
34
32
  }
35
33
 
36
34
  /* ── bitwise operators ─────────────────────────────────────────────────
@@ -0,0 +1,25 @@
1
+ /* Internal numeric coercions shared by scalar operators and typed storage. */
2
+ #ifndef SCR_NUMERIC_H
3
+ #define SCR_NUMERIC_H
4
+ #include <float.h>
5
+ #include <stdint.h>
6
+ #include <string.h>
7
+
8
+ _Static_assert(sizeof(double) == sizeof(uint64_t) && DBL_MANT_DIG == 53 && DBL_MAX_EXP == 1024,
9
+ "numeric coercions require binary64 doubles");
10
+
11
+ /* ToUint32 truncates toward zero and keeps the low 32 bits. Reading the
12
+ * binary64 exponent also handles non-finite values without floating modulo.
13
+ * Subnormals have magnitude below one; exponents >= 84 have no low bits. */
14
+ static inline uint32_t scr_numeric_to_u32(double value) {
15
+ uint64_t bits;
16
+ memcpy(&bits, &value, sizeof bits);
17
+ unsigned exponent = (unsigned)((bits >> 52) & 0x7ff);
18
+ if (exponent < 1023 || exponent >= 1023 + 84) return 0;
19
+ unsigned shift = exponent - 1023;
20
+ uint64_t significand = (bits & UINT64_C(0x000fffffffffffff)) | UINT64_C(0x0010000000000000);
21
+ uint32_t integer = shift >= 52 ? (uint32_t)(significand << (shift - 52))
22
+ : (uint32_t)(significand >> (52 - shift));
23
+ return bits >> 63 ? UINT32_C(0) - integer : integer;
24
+ }
25
+ #endif