jz 0.5.1 → 0.6.0
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.
- package/README.md +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +266 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +414 -186
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +586 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +589 -202
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/module/collection.js
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* @module collection
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64, allocPtr, undefExpr } from '../src/ir.js'
|
|
12
|
-
import { emit,
|
|
13
|
-
import { valTypeOf
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
11
|
+
import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64, allocPtr, undefExpr, mkPtrIR, ptrTypeEq, elemStore, elemLoad } from '../src/ir.js'
|
|
12
|
+
import { emit, deps, call } from '../src/bridge.js'
|
|
13
|
+
import { valTypeOf } from '../src/kind.js'
|
|
14
|
+
import { VAL, lookupValType } from '../src/reps.js'
|
|
15
|
+
import { hasOwnContinue, isBlockBody } from '../src/ast.js'
|
|
16
|
+
import { ctx, inc, PTR, LAYOUT, getter, declGlobal } from '../src/ctx.js'
|
|
17
|
+
import { STR_INTERN_BIT } from '../layout.js'
|
|
16
18
|
|
|
17
19
|
const SET_ENTRY = 16 // hash + key
|
|
18
20
|
const MAP_ENTRY = 24 // hash + key + value
|
|
@@ -45,6 +47,20 @@ function numConstLiteral(expr) {
|
|
|
45
47
|
const sameValueZeroEq = '(call $__same_value_zero (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
46
48
|
const strEq = '(call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
47
49
|
|
|
50
|
+
// Hash-first probe guard: each slot stores the key's hash in its low 32 bits
|
|
51
|
+
// (high 32 = insertion seq), so a collision step can reject on one i32 compare
|
|
52
|
+
// instead of a full key compare. Equal keys always have equal stored hashes
|
|
53
|
+
// (the insert wrote strHash/mapHash of that very key), so the guard never
|
|
54
|
+
// skips a true match — it only skips the ~all-of-them unequal probes that were
|
|
55
|
+
// walking shared-prefix bytes through __str_eq (31M unequal content-compares
|
|
56
|
+
// per self-host bench run before this). If-expression for short-circuit:
|
|
57
|
+
// i32.and would still evaluate the call.
|
|
58
|
+
const hashGuard = (eqExpr) =>
|
|
59
|
+
`(if (result i32) (i32.eq (i32.load (local.get $slot)) (local.get $h))
|
|
60
|
+
(then ${eqExpr}) (else (i32.const 0)))`
|
|
61
|
+
const strEqG = hashGuard(strEq)
|
|
62
|
+
const sameValueZeroEqG = hashGuard(sameValueZeroEq)
|
|
63
|
+
|
|
48
64
|
// Open-addressing probe walked additively by entrySize: avoids an i32.mul + mask per
|
|
49
65
|
// step (vs recomputing slot = off + idx*entrySize). Needs $off/$cap/$h set and $end/$slot
|
|
50
66
|
// locals declared. `idxExpr` is the first-slot index (defaults to h mod cap; cap is pow2).
|
|
@@ -55,15 +71,36 @@ const probeNext = (entrySize) =>
|
|
|
55
71
|
`(local.set $slot (i32.add (local.get $slot) (i32.const ${entrySize})))
|
|
56
72
|
(if (i32.ge_u (local.get $slot) (local.get $end)) (then (local.set $slot (local.get $off))))`
|
|
57
73
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
// Store a fresh entry's hash word, packing a monotonic insertion sequence
|
|
75
|
+
// (global $__seq) into its free high 32 bits. The hash itself only ever occupies
|
|
76
|
+
// the low 32 (always ≥2), so "empty slot ⇔ word==0" and the i32.wrap_i64
|
|
77
|
+
// home-bucket math are untouched; rehash/back-shift copy the whole word, so the
|
|
78
|
+
// sequence rides along for free. Iteration reads it back (via __coll_order) to
|
|
79
|
+
// restore JS insertion order. Emitted only on the insert-new branch — updates
|
|
80
|
+
// keep the original entry (and its sequence) in place.
|
|
81
|
+
const seqStore = `(i64.store (local.get $slot)
|
|
82
|
+
(i64.or (i64.extend_i32_u (local.get $h)) (i64.shl (i64.extend_i32_u (global.get $__seq)) (i64.const 32))))
|
|
83
|
+
(global.set $__seq (i32.add (global.get $__seq) (i32.const 1)))`
|
|
84
|
+
|
|
85
|
+
/** Generate upsert (add/set) probe for a growable collection (Set/Map). hasVal: store
|
|
86
|
+
* value at slot+16. hasExt: emit EXTERNAL fallthrough (call $__ext_set on non-matching
|
|
87
|
+
* type). Gated off → type mismatch just returns coll unchanged.
|
|
88
|
+
*
|
|
89
|
+
* The table grows at 75% load by allocating a 2× table, rehashing, and forward-marking
|
|
90
|
+
* the old header (cap=-1 sentinel, new offset at -8) — the array growth idiom. The boxed
|
|
91
|
+
* pointer the caller holds is returned UNCHANGED; future ops resolve it through
|
|
92
|
+
* __ptr_offset, which follows the chain. This is why Set/Map (held in caller locals, and
|
|
93
|
+
* possibly aliased) forward rather than remint like HASH (whose pointer lives in a single
|
|
94
|
+
* owner's propsPtr slot that genUpsertGrow can rewrite). */
|
|
61
95
|
function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt) {
|
|
62
96
|
const valParam = hasVal ? '(param $val i64) ' : ''
|
|
63
97
|
const storeVal = hasVal ? `\n (i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))` : ''
|
|
64
98
|
const onMatch = hasVal
|
|
65
99
|
? `(then\n (i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))\n (br $done))`
|
|
66
100
|
: `(then (br $done))`
|
|
101
|
+
const rehashVal = hasVal
|
|
102
|
+
? `\n (i64.store (i32.add (local.get $newslot) (i32.const 16)) (i64.load (i32.add (local.get $oldslot) (i32.const 16))))`
|
|
103
|
+
: ''
|
|
67
104
|
|
|
68
105
|
const extBranch = hasVal
|
|
69
106
|
? '(then (call $__ext_set (local.get $coll) (local.get $key) (local.get $val)) drop)'
|
|
@@ -74,15 +111,47 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
74
111
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (return (local.get $coll))))`
|
|
75
112
|
return `(func $${name} (param $coll i64) (param $key i64) ${valParam}(result i64)
|
|
76
113
|
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32)
|
|
114
|
+
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
115
|
+
(local $oldslot i32) (local $newidx i32) (local $newslot i32)
|
|
77
116
|
${typeGuard}
|
|
78
|
-
(local.set $off (
|
|
117
|
+
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
79
118
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
119
|
+
(local.set $size (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
120
|
+
;; Grow at 75% load (size*4 >= cap*3): 2× table, rehash, forward-mark old header.
|
|
121
|
+
(if (i32.ge_s (i32.mul (local.get $size) (i32.const 4)) (i32.mul (local.get $cap) (i32.const 3)))
|
|
122
|
+
(then
|
|
123
|
+
(local.set $newcap (i32.shl (local.get $cap) (i32.const 1)))
|
|
124
|
+
(local.set $newptr (call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${entrySize})))
|
|
125
|
+
(i64.store (i32.sub (local.get $newptr) (i32.const 16)) (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
126
|
+
(local.set $i (i32.const 0))
|
|
127
|
+
(block $rd (loop $rl
|
|
128
|
+
(br_if $rd (i32.ge_s (local.get $i) (local.get $cap)))
|
|
129
|
+
(local.set $oldslot (i32.add (local.get $off) (i32.mul (local.get $i) (i32.const ${entrySize}))))
|
|
130
|
+
(if (i64.ne (i64.load (local.get $oldslot)) (i64.const 0))
|
|
131
|
+
(then
|
|
132
|
+
(local.set $h (call ${hashFn} (i64.load (i32.add (local.get $oldslot) (i32.const 8)))))
|
|
133
|
+
(local.set $newidx (i32.and (local.get $h) (i32.sub (local.get $newcap) (i32.const 1))))
|
|
134
|
+
(block $ins (loop $probe2
|
|
135
|
+
(local.set $newslot (i32.add (local.get $newptr) (i32.mul (local.get $newidx) (i32.const ${entrySize}))))
|
|
136
|
+
(br_if $ins (i64.eqz (i64.load (local.get $newslot))))
|
|
137
|
+
(local.set $newidx (i32.and (i32.add (local.get $newidx) (i32.const 1)) (i32.sub (local.get $newcap) (i32.const 1))))
|
|
138
|
+
(br $probe2)))
|
|
139
|
+
(i64.store (local.get $newslot) (i64.load (local.get $oldslot)))
|
|
140
|
+
(i64.store (i32.add (local.get $newslot) (i32.const 8)) (i64.load (i32.add (local.get $oldslot) (i32.const 8))))${rehashVal}
|
|
141
|
+
(i32.store (i32.sub (local.get $newptr) (i32.const 8))
|
|
142
|
+
(i32.add (i32.load (i32.sub (local.get $newptr) (i32.const 8))) (i32.const 1)))))
|
|
143
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
144
|
+
(br $rl)))
|
|
145
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $newptr))
|
|
146
|
+
(i32.store (i32.sub (local.get $off) (i32.const 4)) (i32.const -1))
|
|
147
|
+
(local.set $off (local.get $newptr))
|
|
148
|
+
(local.set $cap (local.get $newcap))))
|
|
80
149
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
81
150
|
${probeStart(entrySize)}
|
|
82
151
|
(block $done (loop $probe
|
|
83
152
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
84
153
|
(then
|
|
85
|
-
|
|
154
|
+
${seqStore}
|
|
86
155
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${storeVal}
|
|
87
156
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
88
157
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
@@ -117,11 +186,14 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
117
186
|
: '(call $__ext_has (local.get $coll) (local.get $key))'}))
|
|
118
187
|
(else ${onEmpty}))))`
|
|
119
188
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then ${onEmpty}))`
|
|
189
|
+
// SET/MAP/HASH all grow by forward-marking the old header (genUpsert / genUpsertGrow
|
|
190
|
+
// with forward=true), so a boxed pointer may be stale → resolve through the chain.
|
|
191
|
+
const offExpr = '(call $__ptr_offset (local.get $coll))'
|
|
120
192
|
|
|
121
193
|
return `(func $${name} (param $coll i64) (param $key i64) (result ${rt})
|
|
122
194
|
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
123
195
|
${typeGuard}
|
|
124
|
-
(local.set $off
|
|
196
|
+
(local.set $off ${offExpr})
|
|
125
197
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
126
198
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
127
199
|
${probeStart(entrySize)}
|
|
@@ -135,27 +207,55 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
135
207
|
${notFound})`
|
|
136
208
|
}
|
|
137
209
|
|
|
138
|
-
/** Generate delete probe function.
|
|
210
|
+
/** Generate delete probe function. Backward-shift deletion: after removing an entry,
|
|
211
|
+
* pull back any following entry whose home slot lies outside the opened gap, so the
|
|
212
|
+
* "empty slot ⇒ end of probe chain" invariant holds without tombstones. Returns 1 if
|
|
213
|
+
* the key was present (and len decremented), 0 otherwise. Home slots are recomputed
|
|
214
|
+
* from the stored hash (low 32 bits), so no rehash of the key is needed during the shift. */
|
|
139
215
|
function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
140
216
|
return `(func $${name} (param $coll i64) (param $key i64) (result i32)
|
|
141
217
|
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
218
|
+
(local $i i32) (local $j i32) (local $k i32) (local $n i32)
|
|
142
219
|
(if (i32.ne (call $__ptr_type (local.get $coll)) (i32.const ${expectedType})) (then (return (i32.const 0))))
|
|
143
220
|
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
144
221
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
145
222
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
146
223
|
${probeStart(entrySize)}
|
|
147
|
-
(block $
|
|
148
|
-
(
|
|
149
|
-
|
|
150
|
-
(then
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
(
|
|
224
|
+
(block $found
|
|
225
|
+
(block $absent (loop $probe
|
|
226
|
+
(if (i64.eqz (i64.load (local.get $slot))) (then (br $absent)))
|
|
227
|
+
(if ${eqExpr} (then (br $found)))
|
|
228
|
+
${probeNext(entrySize)}
|
|
229
|
+
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
230
|
+
(br_if $absent (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
231
|
+
(br $probe)))
|
|
232
|
+
(return (i32.const 0)))
|
|
233
|
+
;; $slot holds the entry to remove. Walk forward; move back any entry whose home
|
|
234
|
+
;; is not cyclically within (i, j], else it would become unreachable from its home.
|
|
235
|
+
(local.set $i (local.get $slot))
|
|
236
|
+
(local.set $j (local.get $slot))
|
|
237
|
+
(block $stop (loop $shift
|
|
238
|
+
(local.set $j (i32.add (local.get $j) (i32.const ${entrySize})))
|
|
239
|
+
(if (i32.ge_u (local.get $j) (local.get $end)) (then (local.set $j (local.get $off))))
|
|
240
|
+
(br_if $stop (i64.eqz (i64.load (local.get $j))))
|
|
241
|
+
;; Empty slot ends the cluster (load < 100%). A 100%-full table has none — lookups
|
|
242
|
+
;; tolerate that via the $tries<cap bound, so delete must too: after $cap advances $j
|
|
243
|
+
;; has cycled back to the gap origin; stop and clear the final gap.
|
|
244
|
+
(local.set $n (i32.add (local.get $n) (i32.const 1)))
|
|
245
|
+
(br_if $stop (i32.ge_u (local.get $n) (local.get $cap)))
|
|
246
|
+
(local.set $k (i32.add (local.get $off)
|
|
247
|
+
(i32.mul (i32.and (i32.wrap_i64 (i64.load (local.get $j))) (i32.sub (local.get $cap) (i32.const 1))) (i32.const ${entrySize}))))
|
|
248
|
+
(if (i32.le_u (local.get $i) (local.get $j))
|
|
249
|
+
(then (br_if $shift (i32.and (i32.lt_u (local.get $i) (local.get $k)) (i32.le_u (local.get $k) (local.get $j)))))
|
|
250
|
+
(else (br_if $shift (i32.or (i32.lt_u (local.get $i) (local.get $k)) (i32.le_u (local.get $k) (local.get $j))))))
|
|
251
|
+
(memory.copy (local.get $i) (local.get $j) (i32.const ${entrySize}))
|
|
252
|
+
(local.set $i (local.get $j))
|
|
253
|
+
(br $shift)))
|
|
254
|
+
(i64.store (local.get $i) (i64.const 0))
|
|
255
|
+
(i64.store (i32.add (local.get $i) (i32.const 8)) (i64.const 0))
|
|
256
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
257
|
+
(i32.sub (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
258
|
+
(i32.const 1))`
|
|
159
259
|
}
|
|
160
260
|
|
|
161
261
|
/** Generate growable upsert. Grows table at 75% load, rehashes, then inserts.
|
|
@@ -163,7 +263,7 @@ function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
|
163
263
|
* strict=false: EXTERNAL → __ext_set, other non-HASH types → __dyn_set (global props).
|
|
164
264
|
* The non-strict fallback is critical for untyped variables (e.g. arrays from
|
|
165
265
|
* Object.create) that receive property writes — without it writes silently vanish. */
|
|
166
|
-
function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = false, hasExt = false) {
|
|
266
|
+
function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = false, hasExt = false, forward = false) {
|
|
167
267
|
const nonHashFallback = hasExt
|
|
168
268
|
? `(if (i32.eq (call $__ptr_type (local.get $obj)) (i32.const ${PTR.EXTERNAL}))
|
|
169
269
|
(then (call $__ext_set (local.get $obj) (local.get $key) (local.get $val)) drop)
|
|
@@ -209,16 +309,27 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
209
309
|
(i32.add (i32.load (i32.sub (local.get $newptr) (i32.const 8))) (i32.const 1)))))
|
|
210
310
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
211
311
|
(br $rl)))
|
|
312
|
+
${forward
|
|
313
|
+
// Forward-mark the old header (cap=-1 sentinel at -4, new offset at -8) and
|
|
314
|
+
// keep the boxed pointer the caller holds: any alias resolves through
|
|
315
|
+
// __ptr_offset. This preserves JS reference identity for a grown dict held in
|
|
316
|
+
// multiple places (e.g. ctx.core.emit), which remint cannot.
|
|
317
|
+
? `(i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $newptr))
|
|
318
|
+
(i32.store (i32.sub (local.get $off) (i32.const 4)) (i32.const -1))
|
|
212
319
|
(local.set $off (local.get $newptr))
|
|
320
|
+
(local.set $cap (local.get $newcap))`
|
|
321
|
+
// Remint: hand back a fresh boxed pointer. Only safe when a single owner
|
|
322
|
+
// (a local threaded via the return, or the global __dyn_props) is updated.
|
|
323
|
+
: `(local.set $off (local.get $newptr))
|
|
213
324
|
(local.set $cap (local.get $newcap))
|
|
214
|
-
(local.set $obj (i64.reinterpret_f64 (call $__mkptr (i32.const ${typeConst}) (i32.const 0) (local.get $newptr))))))
|
|
325
|
+
(local.set $obj (i64.reinterpret_f64 (call $__mkptr (i32.const ${typeConst}) (i32.const 0) (local.get $newptr))))`}))
|
|
215
326
|
;; Insert/update
|
|
216
327
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
217
328
|
${probeStart(entrySize)}
|
|
218
329
|
(block $done (loop $probe
|
|
219
330
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
220
331
|
(then
|
|
221
|
-
|
|
332
|
+
${seqStore}
|
|
222
333
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))
|
|
223
334
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
224
335
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
@@ -240,7 +351,7 @@ function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing
|
|
|
240
351
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
241
352
|
(i32.const ${expectedType}))
|
|
242
353
|
(then (return (i64.const ${missing}))))
|
|
243
|
-
(local.set $off (
|
|
354
|
+
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
244
355
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
245
356
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
246
357
|
${probeStart(entrySize)}
|
|
@@ -266,10 +377,12 @@ function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing
|
|
|
266
377
|
(else (return (i64.const ${missing}))))))`
|
|
267
378
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
268
379
|
(then (return (i64.const ${missing}))))`
|
|
380
|
+
// SET/MAP/HASH grow by forward-marking; a boxed pointer may be stale → follow the chain.
|
|
381
|
+
const offExpr = '(call $__ptr_offset (local.get $coll))'
|
|
269
382
|
return `(func $${name} (param $coll i64) (param $key i64) (param $h i32) (result i64)
|
|
270
383
|
(local $off i32) (local $cap i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
271
384
|
${typeGuard}
|
|
272
|
-
(local.set $off
|
|
385
|
+
(local.set $off ${offExpr})
|
|
273
386
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
274
387
|
${probeStart(entrySize)}
|
|
275
388
|
(block $done (loop $probe
|
|
@@ -291,13 +404,13 @@ function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
|
291
404
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
292
405
|
(i32.const ${expectedType}))
|
|
293
406
|
(then (return (local.get $obj))))
|
|
294
|
-
(local.set $off (
|
|
407
|
+
(local.set $off (call $__ptr_offset (local.get $obj)))
|
|
295
408
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
296
409
|
${probeStart(entrySize)}
|
|
297
410
|
(block $done (loop $probe
|
|
298
411
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
299
412
|
(then
|
|
300
|
-
|
|
413
|
+
${seqStore}
|
|
301
414
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))
|
|
302
415
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
303
416
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
@@ -317,17 +430,18 @@ export default (ctx) => {
|
|
|
317
430
|
// Feature-gated deps: EXTERNAL-dependent symbols are only pulled when features.external.
|
|
318
431
|
// Evaluated lazily at resolveIncludes() time — after emission has finalized ctx.features.
|
|
319
432
|
const ifExt = (name) => () => ctx.features.external ? [name] : []
|
|
320
|
-
|
|
433
|
+
deps({
|
|
321
434
|
__same_value_zero: ['__str_eq'],
|
|
322
435
|
__map_hash: ['__hash', '__str_hash'],
|
|
323
|
-
__set_add: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ext_set'] : ['__map_hash', '__same_value_zero'],
|
|
324
|
-
__set_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ext_has'] : ['__map_hash', '__same_value_zero'],
|
|
436
|
+
__set_add: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__alloc_hdr_n', '__ext_set'] : ['__map_hash', '__same_value_zero', '__ptr_offset', '__alloc_hdr_n'],
|
|
437
|
+
__set_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ext_has'] : ['__map_hash', '__same_value_zero', '__ptr_offset'],
|
|
325
438
|
__set_delete: ['__map_hash', '__same_value_zero'],
|
|
326
|
-
__map_set: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ext_set'] : ['__map_hash', '__same_value_zero'],
|
|
327
|
-
__map_get: () => ctx.features.external ? ['__ext_prop', '__map_set'] : ['__map_set'],
|
|
328
|
-
__map_get_h: () => ctx.features.external ? ['__ext_prop', '__same_value_zero'] : ['__same_value_zero'],
|
|
329
|
-
__map_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ext_has'] : ['__map_hash', '__same_value_zero'],
|
|
439
|
+
__map_set: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__alloc_hdr_n', '__ext_set'] : ['__map_hash', '__same_value_zero', '__ptr_offset', '__alloc_hdr_n'],
|
|
440
|
+
__map_get: () => ctx.features.external ? ['__ext_prop', '__map_set', '__ptr_offset'] : ['__map_set', '__ptr_offset'],
|
|
441
|
+
__map_get_h: () => ctx.features.external ? ['__ext_prop', '__same_value_zero', '__ptr_offset'] : ['__same_value_zero', '__ptr_offset'],
|
|
442
|
+
__map_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ext_has'] : ['__map_hash', '__same_value_zero', '__ptr_offset'],
|
|
330
443
|
__map_delete: ['__map_hash', '__same_value_zero'],
|
|
444
|
+
__map_from: ['__ptr_type', '__ptr_offset', '__len', '__typed_idx', '__map_set', '__mkptr', '__alloc_hdr_n', '__coll_order'],
|
|
331
445
|
__hash_set: () => ctx.features.external
|
|
332
446
|
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_set', '__dyn_set']
|
|
333
447
|
: ['__str_hash', '__str_eq', '__ptr_type', '__dyn_set'],
|
|
@@ -368,8 +482,15 @@ export default (ctx) => {
|
|
|
368
482
|
|
|
369
483
|
inc('__ptr_offset', '__cap')
|
|
370
484
|
|
|
485
|
+
// Monotonic insertion counter packed into each entry's hash-word high 32 bits
|
|
486
|
+
// (see seqStore). Restores JS insertion order at iteration without growing
|
|
487
|
+
// entries or touching the lookup/delete hot paths. i32: wraps after 2^32 total
|
|
488
|
+
// inserts — unreachable in practice; fresh per wasm instance.
|
|
489
|
+
if (!ctx.scope.globals.has('__seq'))
|
|
490
|
+
declGlobal('__seq', 'i32')
|
|
491
|
+
|
|
371
492
|
if (!ctx.scope.globals.has('__dyn_props'))
|
|
372
|
-
|
|
493
|
+
declGlobal('__dyn_props', 'f64')
|
|
373
494
|
// 1-slot inline cache for the global __dyn_props lookup. Hot path for
|
|
374
495
|
// metacircular workloads (watr WAT parser): ~96% of execution sits in
|
|
375
496
|
// __dyn_get_t / __ihash_get_local. Caches last-seen (off → propsPtr) at
|
|
@@ -377,15 +498,15 @@ export default (ctx) => {
|
|
|
377
498
|
// propsPtr is replaced (rehash on grow). Sentinel cache_off = -1 cannot
|
|
378
499
|
// collide with a real memory offset (always non-negative i32).
|
|
379
500
|
if (!ctx.scope.globals.has('__dyn_get_cache_off'))
|
|
380
|
-
|
|
501
|
+
declGlobal('__dyn_get_cache_off', 'i32', -1)
|
|
381
502
|
if (!ctx.scope.globals.has('__dyn_get_cache_props'))
|
|
382
|
-
|
|
503
|
+
declGlobal('__dyn_get_cache_props', 'f64')
|
|
383
504
|
// Schema name table for __dyn_get's OBJECT-schema fallback (polymorphic-receiver
|
|
384
505
|
// `.prop` access). Same declaration as json.js — defined here too so collection
|
|
385
506
|
// doesn't transitively require json. compile.js's schemaInit populates it when
|
|
386
507
|
// schema list is non-empty AND (__stringify OR __dyn_get) is included.
|
|
387
508
|
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
388
|
-
|
|
509
|
+
declGlobal('__schema_tbl', 'i32')
|
|
389
510
|
|
|
390
511
|
// __ext_* imports carry NaN-boxed pointers across the env boundary as i64
|
|
391
512
|
// (not f64) to dodge V8's f64 NaN canonicalization at the wasm↔JS edge —
|
|
@@ -455,16 +576,17 @@ export default (ctx) => {
|
|
|
455
576
|
const out = allocPtr({ type: PTR.SET, len: 0, cap: INIT_CAP, stride: SET_ENTRY, tag: 'set' })
|
|
456
577
|
return typed(['block', ['result', 'f64'], out.init, out.ptr], 'f64')
|
|
457
578
|
}
|
|
458
|
-
// new Set(iterable):
|
|
459
|
-
//
|
|
460
|
-
//
|
|
579
|
+
// new Set(iterable): __iter_arr normalizes any iterable to an index-iterable
|
|
580
|
+
// dense array (Set→keys, Map→[k,v] entries, Array/String/TypedArray pass
|
|
581
|
+
// through), so a Set/Map/Array source all seed uniformly. __set_add does
|
|
582
|
+
// SameValueZero dedup + −0 normalization. A non-iterable normalizes to a
|
|
583
|
+
// non-array value — the ptr_type guard zeroes the length so the loop is skipped.
|
|
461
584
|
//
|
|
462
|
-
// __set_add
|
|
463
|
-
//
|
|
464
|
-
// the smallest power of two
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
// and floors at INIT_CAP for the empty/short case.
|
|
585
|
+
// __set_add grows on demand, but pre-sizing the table to fit the source array
|
|
586
|
+
// skips the rehash churn of building it up from INIT_CAP. cap = 1 << (32 −
|
|
587
|
+
// clz(m−1)) with m = 2*len + INIT_CAP is the smallest power of two > 2*len:
|
|
588
|
+
// distinct entries ≤ len, so the table lands ≤50% full and never needs to grow
|
|
589
|
+
// while seeding. Floors at INIT_CAP for the empty/short case.
|
|
468
590
|
inc('__set_add', '__ptr_type', '__len', '__typed_idx')
|
|
469
591
|
const setL = temp('nss'), arrL = temp('nsa')
|
|
470
592
|
const iL = tempI32('nsi'), lenL = tempI32('nsl')
|
|
@@ -475,7 +597,7 @@ export default (ctx) => {
|
|
|
475
597
|
['i32.const', 1]]]]]
|
|
476
598
|
const out = allocPtr({ type: PTR.SET, len: 0, cap: capExpr, stride: SET_ENTRY, tag: 'set' })
|
|
477
599
|
return typed(['block', ['result', 'f64'],
|
|
478
|
-
['local.set', `$${arrL}`, asF64(emit(iterExpr))],
|
|
600
|
+
['local.set', `$${arrL}`, asF64(emit(['()', '__iter_arr', iterExpr]))],
|
|
479
601
|
['local.set', `$${lenL}`, ['i32.const', 0]],
|
|
480
602
|
['if', ['i32.eq',
|
|
481
603
|
['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${arrL}`]]],
|
|
@@ -498,28 +620,34 @@ export default (ctx) => {
|
|
|
498
620
|
['local.get', `$${setL}`]], 'f64')
|
|
499
621
|
}
|
|
500
622
|
|
|
501
|
-
ctx.core.emit['.add'] = (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
623
|
+
ctx.core.emit['.add'] = call('__set_add', 'II', 'i64')
|
|
624
|
+
|
|
625
|
+
// `.has` / `.delete` exist on BOTH Set and Map, which differ only in entry
|
|
626
|
+
// stride (16 vs 24). A receiver of unproven kind (e.g. a Map read off a nested
|
|
627
|
+
// object field like `ctx.scope.globals`) must resolve MAP vs SET at runtime:
|
|
628
|
+
// the Set probe carries a PTR.SET type guard that rejects a Map outright, so
|
|
629
|
+
// routing a Map through `__set_has`/`__set_delete` makes every lookup/delete
|
|
630
|
+
// silently report absent. Mirrors collViewDyn below; typed receivers skip it.
|
|
631
|
+
const collProbeDyn = (mapFn, setFn) => (collExpr, key) => {
|
|
632
|
+
inc(mapFn, setFn, '__ptr_type')
|
|
633
|
+
const o = temp('cp'), k = tempI64('cpk')
|
|
634
|
+
return typed(['block', ['result', 'f64'],
|
|
635
|
+
['local.set', `$${o}`, asF64(emit(collExpr))],
|
|
636
|
+
['local.set', `$${k}`, asI64(emit(key))],
|
|
637
|
+
['f64.convert_i32_s', ['if', ['result', 'i32'],
|
|
638
|
+
ptrTypeEq(['local.get', `$${o}`], PTR.MAP),
|
|
639
|
+
['then', ['call', `$${mapFn}`, ['i64.reinterpret_f64', ['local.get', `$${o}`]], ['local.get', `$${k}`]]],
|
|
640
|
+
['else', ['call', `$${setFn}`, ['i64.reinterpret_f64', ['local.get', `$${o}`]], ['local.get', `$${k}`]]]]]], 'f64')
|
|
514
641
|
}
|
|
642
|
+
ctx.core.emit['.has'] = collProbeDyn('__map_has', '__set_has')
|
|
643
|
+
ctx.core.emit['.delete'] = collProbeDyn('__map_delete', '__set_delete')
|
|
644
|
+
ctx.core.emit[`.${VAL.SET}:has`] = call('__set_has', 'II', 'i32')
|
|
645
|
+
ctx.core.emit[`.${VAL.SET}:delete`] = call('__set_delete', 'II', 'i32')
|
|
515
646
|
|
|
516
647
|
// Map.prototype.clear / Set.prototype.clear — drop every entry. `.clear` only
|
|
517
648
|
// exists on Map/Set in JS, so a single generic emitter is unambiguous; the
|
|
518
649
|
// stdlib reads the ptr tag to pick the entry stride.
|
|
519
|
-
ctx.core.emit['.clear'] = (
|
|
520
|
-
inc('__coll_clear')
|
|
521
|
-
return typed(['call', '$__coll_clear', asI64(emit(collExpr))], 'f64')
|
|
522
|
-
}
|
|
650
|
+
ctx.core.emit['.clear'] = call('__coll_clear', 'I')
|
|
523
651
|
|
|
524
652
|
// Zero every slot (so probes see empty) and reset the length header. Entry
|
|
525
653
|
// stride is 16 for a SET, 24 for a MAP — `(t == MAP) << 3` adds the 8-byte
|
|
@@ -538,41 +666,47 @@ export default (ctx) => {
|
|
|
538
666
|
(i32.store (i32.sub (local.get $off) (i32.const 8)) (i32.const 0))))
|
|
539
667
|
(f64.reinterpret_i64 (i64.const ${UNDEF_NAN})))`
|
|
540
668
|
|
|
541
|
-
ctx.core.emit['.size'] = (expr) => {
|
|
669
|
+
ctx.core.emit['.size'] = getter((expr) => {
|
|
542
670
|
return typed(['f64.convert_i32_s', ['call', '$__len', ['i64.reinterpret_f64', asF64(emit(expr))]]], 'f64')
|
|
543
|
-
}
|
|
671
|
+
})
|
|
544
672
|
|
|
545
673
|
// x instanceof Map / Set — typed-pointer predicates emitted by jzify. NaN-check
|
|
546
674
|
// first (non-pointer numbers must report false), then compare __ptr_type tag.
|
|
547
675
|
// Mirrors module/array.js's Array.isArray inline form. Result is i32 (boolean).
|
|
548
676
|
ctx.core.emit['__is_map'] = (x) => {
|
|
549
|
-
inc('__ptr_type')
|
|
550
677
|
const v = asF64(emit(x))
|
|
551
678
|
const t = temp('imap')
|
|
552
679
|
return typed(['i32.and',
|
|
553
680
|
['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
554
|
-
['
|
|
681
|
+
ptrTypeEq(['local.get', `$${t}`], PTR.MAP)], 'i32')
|
|
555
682
|
}
|
|
556
683
|
ctx.core.emit['__is_set'] = (x) => {
|
|
557
|
-
inc('__ptr_type')
|
|
558
684
|
const v = asF64(emit(x))
|
|
559
685
|
const t = temp('iset')
|
|
560
686
|
return typed(['i32.and',
|
|
561
687
|
['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
562
|
-
['
|
|
688
|
+
ptrTypeEq(['local.get', `$${t}`], PTR.SET)], 'i32')
|
|
563
689
|
}
|
|
564
690
|
|
|
565
691
|
// Generated Set probe functions
|
|
566
|
-
ctx.core.stdlib['__set_add'] = () => genUpsert('__set_add', SET_ENTRY, '$__map_hash',
|
|
567
|
-
ctx.core.stdlib['__set_has'] = () => genLookup('__set_has', SET_ENTRY, '$__map_hash',
|
|
568
|
-
ctx.core.stdlib['__set_delete'] = genDelete('__set_delete', SET_ENTRY, '$__map_hash',
|
|
692
|
+
ctx.core.stdlib['__set_add'] = () => genUpsert('__set_add', SET_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.SET, false, ctx.features.external)
|
|
693
|
+
ctx.core.stdlib['__set_has'] = () => genLookup('__set_has', SET_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.SET, false, ctx.features.external)
|
|
694
|
+
ctx.core.stdlib['__set_delete'] = genDelete('__set_delete', SET_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.SET)
|
|
569
695
|
|
|
570
696
|
// === Map ===
|
|
571
697
|
|
|
572
|
-
ctx.core.emit['new.Map'] = () => {
|
|
698
|
+
ctx.core.emit['new.Map'] = (iterExpr) => {
|
|
573
699
|
ctx.features.map = true
|
|
574
|
-
|
|
575
|
-
|
|
700
|
+
if (iterExpr == null) {
|
|
701
|
+
const out = allocPtr({ type: PTR.MAP, len: 0, cap: INIT_CAP, stride: MAP_ENTRY, tag: 'map' })
|
|
702
|
+
return typed(['block', ['result', 'f64'], out.init, out.ptr], 'f64')
|
|
703
|
+
}
|
|
704
|
+
// new Map(iterable): seed from another Map or an array of [key, value] pairs.
|
|
705
|
+
// Delegated to a stdlib helper (vs inlined like Set) — `new Map(x)` is heavily
|
|
706
|
+
// used (the compiler copies fact Maps per function), so one shared helper keeps
|
|
707
|
+
// output small. Non-Map/Array args yield an empty map (guarded in the helper).
|
|
708
|
+
inc('__map_from')
|
|
709
|
+
return typed(['call', '$__map_from', asI64(emit(iterExpr))], 'f64')
|
|
576
710
|
}
|
|
577
711
|
|
|
578
712
|
ctx.core.emit['.set'] = (mapExpr, key, val) => {
|
|
@@ -595,22 +729,131 @@ export default (ctx) => {
|
|
|
595
729
|
ctx.core.emit['.get'] = emitMapGet
|
|
596
730
|
ctx.core.emit[`.${VAL.MAP}:get`] = emitMapGet
|
|
597
731
|
|
|
598
|
-
ctx.core.emit[`.${VAL.MAP}:has`] = (
|
|
599
|
-
|
|
600
|
-
|
|
732
|
+
ctx.core.emit[`.${VAL.MAP}:has`] = call('__map_has', 'II', 'i32')
|
|
733
|
+
ctx.core.emit[`.${VAL.MAP}:delete`] = call('__map_delete', 'II', 'i32')
|
|
734
|
+
|
|
735
|
+
// Map/Set iteration views: keys() / values() / entries() materialize a dense
|
|
736
|
+
// Array snapshot (jz models iterators as arrays — for-of/spread consume them
|
|
737
|
+
// directly). Set keys===values===elements; Set entries yield [v, v] pairs.
|
|
738
|
+
// Registered per concrete type; an unproven receiver resolves SET vs MAP at
|
|
739
|
+
// runtime via `.keys`/`.values`/`.entries` below.
|
|
740
|
+
const collView = (walk) => (expr) => {
|
|
741
|
+
const t = temp('cv')
|
|
742
|
+
return typed(['block', ['result', 'f64'], ['local.set', `$${t}`, asF64(emit(expr))], walk(t)], 'f64')
|
|
601
743
|
}
|
|
602
|
-
|
|
603
|
-
ctx.core.emit[`.${VAL.MAP}:
|
|
604
|
-
|
|
605
|
-
|
|
744
|
+
ctx.core.emit[`.${VAL.MAP}:keys`] = collView(t => collKeysFromTemp(t, MAP_ENTRY, 8))
|
|
745
|
+
ctx.core.emit[`.${VAL.MAP}:values`] = collView(t => collKeysFromTemp(t, MAP_ENTRY, 16))
|
|
746
|
+
ctx.core.emit[`.${VAL.MAP}:entries`] = collView(t => collEntriesFromTemp(t, MAP_ENTRY, 8, 16))
|
|
747
|
+
ctx.core.emit[`.${VAL.SET}:keys`] = collView(t => collKeysFromTemp(t, SET_ENTRY, 8))
|
|
748
|
+
ctx.core.emit[`.${VAL.SET}:values`] = ctx.core.emit[`.${VAL.SET}:keys`]
|
|
749
|
+
ctx.core.emit[`.${VAL.SET}:entries`] = collView(t => collEntriesFromTemp(t, SET_ENTRY, 8, 8))
|
|
750
|
+
|
|
751
|
+
// Generic keys()/values()/entries() for a receiver whose collection kind isn't
|
|
752
|
+
// statically proven (e.g. a Map read off an object field): resolve MAP vs SET
|
|
753
|
+
// vs ARRAY once at runtime. ARRAY.values() is the array itself; .keys() yields
|
|
754
|
+
// indices; .entries() yields [i, el]. Any other receiver passes through.
|
|
755
|
+
const collViewDyn = (mapWalk, setWalk, arrWalk) => (expr) => {
|
|
756
|
+
inc('__ptr_type')
|
|
757
|
+
const t = temp('cd')
|
|
758
|
+
const pt = () => ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]
|
|
759
|
+
const branch = (tag, walk, rest) =>
|
|
760
|
+
['if', ['result', 'f64'], ['i32.eq', pt(), ['i32.const', tag]], ['then', walk(t)], ['else', rest]]
|
|
761
|
+
const tree = branch(PTR.MAP, mapWalk, branch(PTR.SET, setWalk,
|
|
762
|
+
branch(PTR.ARRAY, arrWalk, ['local.get', `$${t}`])))
|
|
763
|
+
return typed(['block', ['result', 'f64'], ['local.set', `$${t}`, asF64(emit(expr))], tree], 'f64')
|
|
764
|
+
}
|
|
765
|
+
ctx.core.emit['.keys'] = collViewDyn(
|
|
766
|
+
t => collKeysFromTemp(t, MAP_ENTRY, 8), t => collKeysFromTemp(t, SET_ENTRY, 8), arrIdxFromTemp)
|
|
767
|
+
ctx.core.emit['.values'] = collViewDyn(
|
|
768
|
+
t => collKeysFromTemp(t, MAP_ENTRY, 16), t => collKeysFromTemp(t, SET_ENTRY, 8), t => ['local.get', `$${t}`])
|
|
769
|
+
ctx.core.emit['.entries'] = collViewDyn(
|
|
770
|
+
t => collEntriesFromTemp(t, MAP_ENTRY, 8, 16), t => collEntriesFromTemp(t, SET_ENTRY, 8, 8), arrEntriesFromTemp)
|
|
771
|
+
|
|
772
|
+
// Map/Set forEach(cb): invoke cb(value, key) per live entry in insertion order.
|
|
773
|
+
// Map yields (value=val@16, key=key@8); Set yields (value=key@8, key@8) — the
|
|
774
|
+
// spec passes the element as both value and key. The trailing collection arg is
|
|
775
|
+
// dropped (as array/typedarray forEach drop the array arg) so we never exceed
|
|
776
|
+
// the uniform closure width (forEach autoloads array → closure floor 2). Uses
|
|
777
|
+
// the closure-call path like typedarray:forEach — forEach isn't a hot path.
|
|
778
|
+
const collForEach = (stride, valOff, keyOff) => (expr, fn) => {
|
|
779
|
+
inc('__ptr_offset', '__cap', '__len', '__coll_order')
|
|
780
|
+
const t = temp('fe'), cb = temp('fecb')
|
|
781
|
+
const off = tempI32('feo'), cap = tempI32('fec'), n = tempI32('fen')
|
|
782
|
+
const i = tempI32('fei'), ord = tempI32('fer'), slot = tempI32('fes')
|
|
783
|
+
const id = ctx.func.uniq++
|
|
784
|
+
const at = (o) => typed(['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', o]]], 'f64')
|
|
785
|
+
return typed(['block', ['result', 'f64'],
|
|
786
|
+
['local.set', `$${t}`, asF64(emit(expr))],
|
|
787
|
+
['local.set', `$${cb}`, asF64(emit(fn))],
|
|
788
|
+
['local.set', `$${n}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
789
|
+
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
790
|
+
['local.set', `$${cap}`, ['call', '$__cap', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
791
|
+
['local.set', `$${ord}`, ['call', '$__coll_order', ['local.get', `$${off}`], ['local.get', `$${cap}`], ['i32.const', stride]]],
|
|
792
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
793
|
+
['block', `$febrk${id}`, ['loop', `$feloop${id}`,
|
|
794
|
+
['br_if', `$febrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
795
|
+
['local.set', `$${slot}`, ['i32.load', ['i32.add', ['local.get', `$${ord}`],
|
|
796
|
+
['i32.shl', ['local.get', `$${i}`], ['i32.const', 2]]]]],
|
|
797
|
+
['drop', asF64(ctx.closure.call(typed(['local.get', `$${cb}`], 'f64'),
|
|
798
|
+
[at(valOff), at(keyOff)]))],
|
|
799
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
800
|
+
['br', `$feloop${id}`]]],
|
|
801
|
+
['f64.const', 0]], 'f64')
|
|
606
802
|
}
|
|
803
|
+
ctx.core.emit[`.${VAL.MAP}:forEach`] = collForEach(MAP_ENTRY, 16, 8)
|
|
804
|
+
ctx.core.emit[`.${VAL.SET}:forEach`] = collForEach(SET_ENTRY, 8, 8)
|
|
607
805
|
|
|
608
806
|
// Generated Map probe functions
|
|
609
|
-
ctx.core.stdlib['__map_set'] = () => genUpsert('__map_set', MAP_ENTRY, '$__map_hash',
|
|
610
|
-
ctx.core.stdlib['__map_get'] = () => genLookup('__map_get', MAP_ENTRY, '$__map_hash',
|
|
611
|
-
ctx.core.stdlib['__map_get_h'] = () => genLookupStrictPrehashed('__map_get_h', MAP_ENTRY,
|
|
612
|
-
ctx.core.stdlib['__map_has'] = () => genLookup('__map_has', MAP_ENTRY, '$__map_hash',
|
|
613
|
-
ctx.core.stdlib['__map_delete'] = genDelete('__map_delete', MAP_ENTRY, '$__map_hash',
|
|
807
|
+
ctx.core.stdlib['__map_set'] = () => genUpsert('__map_set', MAP_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.MAP, true, ctx.features.external)
|
|
808
|
+
ctx.core.stdlib['__map_get'] = () => genLookup('__map_get', MAP_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.MAP, true, ctx.features.external)
|
|
809
|
+
ctx.core.stdlib['__map_get_h'] = () => genLookupStrictPrehashed('__map_get_h', MAP_ENTRY, sameValueZeroEqG, PTR.MAP, UNDEF_NAN, ctx.features.external)
|
|
810
|
+
ctx.core.stdlib['__map_has'] = () => genLookup('__map_has', MAP_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.MAP, false, ctx.features.external)
|
|
811
|
+
ctx.core.stdlib['__map_delete'] = genDelete('__map_delete', MAP_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.MAP)
|
|
812
|
+
|
|
813
|
+
// new Map(iterable) seeder. Source is another Map (copy live [key,val] slots) or
|
|
814
|
+
// an array of [key,value] pairs (`new Map([["a",1],…])`); any other arg yields an
|
|
815
|
+
// empty map. Pre-sizes cap to fit (smallest pow2 > 2·n, floor INIT_CAP) so seeding
|
|
816
|
+
// never triggers a rehash. Occupied MAP slot ⇔ hash word ≠ 0 (genDelete shift-back
|
|
817
|
+
// writes 0, leaving no tombstones — matches the rehash loop's own occupancy test).
|
|
818
|
+
ctx.core.stdlib['__map_from'] = `(func $__map_from (param $src i64) (result f64)
|
|
819
|
+
(local $map i64) (local $t i32) (local $off i32) (local $cap i32)
|
|
820
|
+
(local $i i32) (local $n i32) (local $slot i32) (local $entry i64) (local $newcap i32) (local $ord i32)
|
|
821
|
+
(local.set $t (call $__ptr_type (local.get $src)))
|
|
822
|
+
(if (i32.eq (local.get $t) (i32.const ${PTR.MAP}))
|
|
823
|
+
(then
|
|
824
|
+
(local.set $off (call $__ptr_offset (local.get $src)))
|
|
825
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
826
|
+
(local.set $n (i32.load (i32.sub (local.get $off) (i32.const 8)))))
|
|
827
|
+
(else (if (i32.eq (local.get $t) (i32.const ${PTR.ARRAY}))
|
|
828
|
+
(then (local.set $n (call $__len (local.get $src)))))))
|
|
829
|
+
(local.set $newcap (i32.shl (i32.const 1)
|
|
830
|
+
(i32.sub (i32.const 32) (i32.clz
|
|
831
|
+
(i32.sub (i32.add (i32.shl (local.get $n) (i32.const 1)) (i32.const ${INIT_CAP})) (i32.const 1))))))
|
|
832
|
+
(local.set $map (i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.MAP}) (i32.const 0)
|
|
833
|
+
(call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${MAP_ENTRY})))))
|
|
834
|
+
(if (i32.eq (local.get $t) (i32.const ${PTR.MAP}))
|
|
835
|
+
(then
|
|
836
|
+
;; Copy in source insertion order so the new map enumerates identically.
|
|
837
|
+
(local.set $ord (call $__coll_order (local.get $off) (local.get $cap) (i32.const ${MAP_ENTRY})))
|
|
838
|
+
(block $dm (loop $lm
|
|
839
|
+
(br_if $dm (i32.ge_s (local.get $i) (local.get $n)))
|
|
840
|
+
(local.set $slot (i32.load (i32.add (local.get $ord) (i32.shl (local.get $i) (i32.const 2)))))
|
|
841
|
+
(local.set $map (call $__map_set (local.get $map)
|
|
842
|
+
(i64.load (i32.add (local.get $slot) (i32.const 8)))
|
|
843
|
+
(i64.load (i32.add (local.get $slot) (i32.const 16)))))
|
|
844
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
845
|
+
(br $lm))))
|
|
846
|
+
(else (if (i32.eq (local.get $t) (i32.const ${PTR.ARRAY}))
|
|
847
|
+
(then
|
|
848
|
+
(block $da (loop $la
|
|
849
|
+
(br_if $da (i32.ge_s (local.get $i) (local.get $n)))
|
|
850
|
+
(local.set $entry (i64.reinterpret_f64 (call $__typed_idx (local.get $src) (local.get $i))))
|
|
851
|
+
(local.set $map (call $__map_set (local.get $map)
|
|
852
|
+
(i64.reinterpret_f64 (call $__typed_idx (local.get $entry) (i32.const 0)))
|
|
853
|
+
(i64.reinterpret_f64 (call $__typed_idx (local.get $entry) (i32.const 1)))))
|
|
854
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
855
|
+
(br $la)))))))
|
|
856
|
+
(f64.reinterpret_i64 (local.get $map)))`
|
|
614
857
|
|
|
615
858
|
// === HASH — dynamic string-keyed object (type=7) ===
|
|
616
859
|
|
|
@@ -636,6 +879,11 @@ export default (ctx) => {
|
|
|
636
879
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
637
880
|
(br $ls))))
|
|
638
881
|
(else
|
|
882
|
+
;; canonical interned static: cached post-clamp FNV at -8 (see layout.js)
|
|
883
|
+
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
884
|
+
(i32.and (i32.ge_u (local.get $off) (i32.const 8))
|
|
885
|
+
(i32.eq (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | STR_INTERN_BIT})) (i32.const ${STR_INTERN_BIT}))))
|
|
886
|
+
(then (return (i32.load (i32.sub (local.get $off) (i32.const 8))))))
|
|
639
887
|
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.STRING})) (i32.ge_u (local.get $off) (i32.const 4)))
|
|
640
888
|
(then (local.set $len (i32.load (i32.sub (local.get $off) (i32.const 4))))))
|
|
641
889
|
;; 4-byte unrolled FNV-1a: each iter loads i32, mixes 4 bytes (little-endian) sequentially.
|
|
@@ -675,13 +923,13 @@ export default (ctx) => {
|
|
|
675
923
|
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
676
924
|
(call $__alloc_hdr_n (i32.const 0) (i32.const ${smallCap}) (i32.const ${MAP_ENTRY}))))`
|
|
677
925
|
|
|
678
|
-
ctx.core.stdlib['__hash_get_local'] = genLookupStrict('__hash_get_local', MAP_ENTRY, '$__str_hash',
|
|
679
|
-
ctx.core.stdlib['__hash_get_local_h'] = genLookupStrictPrehashed('__hash_get_local_h', MAP_ENTRY,
|
|
680
|
-
ctx.core.stdlib['__hash_set_local_h'] = genUpsertStrictPrehashed('__hash_set_local_h', MAP_ENTRY,
|
|
681
|
-
ctx.core.stdlib['__hash_set_local'] = genUpsertGrow('__hash_set_local', MAP_ENTRY, '$__str_hash',
|
|
926
|
+
ctx.core.stdlib['__hash_get_local'] = genLookupStrict('__hash_get_local', MAP_ENTRY, '$__str_hash', strEqG, PTR.HASH)
|
|
927
|
+
ctx.core.stdlib['__hash_get_local_h'] = genLookupStrictPrehashed('__hash_get_local_h', MAP_ENTRY, strEqG, PTR.HASH)
|
|
928
|
+
ctx.core.stdlib['__hash_set_local_h'] = genUpsertStrictPrehashed('__hash_set_local_h', MAP_ENTRY, strEqG, PTR.HASH)
|
|
929
|
+
ctx.core.stdlib['__hash_set_local'] = genUpsertGrow('__hash_set_local', MAP_ENTRY, '$__str_hash', strEqG, PTR.HASH, true, false, true)
|
|
682
930
|
// Tombstones an entry in a HASH (string keys). Returns 1 if found+deleted, 0 otherwise.
|
|
683
931
|
// Used as the bucket-level primitive for __dyn_del.
|
|
684
|
-
ctx.core.stdlib['__hash_del_local'] = genDelete('__hash_del_local', MAP_ENTRY, '$__str_hash',
|
|
932
|
+
ctx.core.stdlib['__hash_del_local'] = genDelete('__hash_del_local', MAP_ENTRY, '$__str_hash', strEqG, PTR.HASH)
|
|
685
933
|
// Outer __dyn_props hash: keyed by object offset (i32 as f64 bits), value is per-object props hash.
|
|
686
934
|
// Uses bit-hash + i64.eq — no string allocation for the unique integer key.
|
|
687
935
|
ctx.core.stdlib['__ihash_get_local'] = genLookupStrict('__ihash_get_local', MAP_ENTRY, '$__map_hash', '(i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))', PTR.HASH)
|
|
@@ -713,10 +961,14 @@ export default (ctx) => {
|
|
|
713
961
|
// literals — single bit-eq decides). Falls back to __str_eq when bits differ
|
|
714
962
|
// so runtime-registered schemas (e.g. JSON.parse OBJECTs whose keys are
|
|
715
963
|
// freshly heap-allocated by __jp_str) still resolve correctly.
|
|
964
|
+
// If-expression, NOT i32.or: `or` evaluates both arms, calling __str_eq even
|
|
965
|
+
// when the bit-eq already decided — that bare call per schema-key step was
|
|
966
|
+
// the hottest __str_eq producer in the self-host (the kernel includes __jp
|
|
967
|
+
// for optJSON, so the fallback arm is always compiled in).
|
|
716
968
|
const schemaKeyEq = (storedKey, userKey) => ctx.core.includes.has('__jp_obj') || ctx.core.includes.has('__jp')
|
|
717
|
-
? `(i32.
|
|
718
|
-
(
|
|
719
|
-
(call $__str_eq ${storedKey} ${userKey}))`
|
|
969
|
+
? `(if (result i32) (i64.eq ${storedKey} ${userKey})
|
|
970
|
+
(then (i32.const 1))
|
|
971
|
+
(else (call $__str_eq ${storedKey} ${userKey})))`
|
|
720
972
|
: `(i64.eq ${storedKey} ${userKey})`
|
|
721
973
|
const buildObjectSchemaArm = () => (ctx.schema.list.length > 0 || ctx.core.includes.has('__jp_obj')) ? `
|
|
722
974
|
(if (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
@@ -761,9 +1013,7 @@ export default (ctx) => {
|
|
|
761
1013
|
(local.set $idx (i32.const 0))
|
|
762
1014
|
(block $schemaSetDone (loop $schemaSetLoop
|
|
763
1015
|
(br_if $schemaSetDone (i32.ge_s (local.get $idx) (local.get $nkeys)))
|
|
764
|
-
(if (
|
|
765
|
-
(i64.load (i32.add (local.get $koff) (i32.shl (local.get $idx) (i32.const 3))))
|
|
766
|
-
(local.get $key))
|
|
1016
|
+
(if ${schemaKeyEq(`(i64.load (i32.add (local.get $koff) (i32.shl (local.get $idx) (i32.const 3))))`, `(local.get $key)`)}
|
|
767
1017
|
(then
|
|
768
1018
|
(i64.store (i32.add (local.get $off) (i32.shl (local.get $idx) (i32.const 3))) (local.get $val))
|
|
769
1019
|
(br $schemaSetDone)))
|
|
@@ -782,6 +1032,10 @@ export default (ctx) => {
|
|
|
782
1032
|
(local $props i64) (local $off i32)
|
|
783
1033
|
(local $poff i32) (local $pcap i32) (local $pend i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
784
1034
|
${buildObjectSchemaLocals()}
|
|
1035
|
+
;; Real-number receiver (f===f — pointers are NaN-boxed) has no props: bail before
|
|
1036
|
+
;; treating its bits as a heap offset (\`(5).foo\`/\`(5)[k]\` → undefined, not OOB).
|
|
1037
|
+
(if (f64.eq (f64.reinterpret_i64 (local.get $obj)) (f64.reinterpret_i64 (local.get $obj)))
|
|
1038
|
+
(then (return (i64.const ${UNDEF_NAN}))))
|
|
785
1039
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
786
1040
|
;; CLOSURE with no env (offset 0): many function refs share offset 0, so key the
|
|
787
1041
|
;; global __dyn_props hash on the function table index (negative — can't collide
|
|
@@ -827,13 +1081,23 @@ export default (ctx) => {
|
|
|
827
1081
|
(local.set $props (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
828
1082
|
(br_if $dynDone (i64.eqz (local.get $props)))
|
|
829
1083
|
(br $haveProps)))
|
|
830
|
-
;;
|
|
1084
|
+
;; HASH: a plain dict whose string keys ARE its own bucket entries — the
|
|
1085
|
+
;; receiver IS its props table, so probe it directly (a HASH never carries
|
|
1086
|
+
;; an off-16 dyn sidecar). A statically HASH-typed h[k] already inlines
|
|
1087
|
+
;; __hash_get; this path serves receivers whose HASH type is only known at
|
|
1088
|
+
;; runtime — e.g. a value read back through a function return, as in
|
|
1089
|
+
;; derive(emitter)[op]. Without it, dyn-get reads the (absent) sidecar and
|
|
1090
|
+
;; reports every key missing.
|
|
1091
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.HASH}))
|
|
1092
|
+
(then
|
|
1093
|
+
(local.set $props (local.get $obj))
|
|
1094
|
+
(br $haveProps)))
|
|
1095
|
+
;; Other header types (TYPED/SET/MAP) carry propsPtr at off-16
|
|
831
1096
|
;; directly, bypassing the global __dyn_props hash.
|
|
832
1097
|
(if (i32.and (i32.ge_u (local.get $off) (i32.const 16))
|
|
833
1098
|
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.TYPED}))
|
|
834
|
-
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.
|
|
835
|
-
|
|
836
|
-
(i32.eq (local.get $type) (i32.const ${PTR.MAP}))))))
|
|
1099
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.SET}))
|
|
1100
|
+
(i32.eq (local.get $type) (i32.const ${PTR.MAP})))))
|
|
837
1101
|
(then
|
|
838
1102
|
(local.set $props (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
839
1103
|
(br_if $dynDone (i64.eqz (local.get $props)))
|
|
@@ -856,14 +1120,17 @@ export default (ctx) => {
|
|
|
856
1120
|
(br $dynDone))
|
|
857
1121
|
(else
|
|
858
1122
|
(global.set $__dyn_get_cache_props (f64.reinterpret_i64 (local.get $props))))))))
|
|
859
|
-
(local.set $poff (
|
|
1123
|
+
(local.set $poff (call $__ptr_offset (local.get $props)))
|
|
860
1124
|
(local.set $pcap (i32.load (i32.sub (local.get $poff) (i32.const 4))))
|
|
861
1125
|
(local.set $pend (i32.add (local.get $poff) (i32.mul (local.get $pcap) (i32.const ${MAP_ENTRY}))))
|
|
862
1126
|
(local.set $slot (i32.add (local.get $poff) (i32.mul (i32.and (local.get $h) (i32.sub (local.get $pcap) (i32.const 1))) (i32.const ${MAP_ENTRY}))))
|
|
863
1127
|
(block $hdone (loop $hprobe
|
|
864
1128
|
(br_if $dynDone (i64.eqz (i64.load (local.get $slot))))
|
|
865
|
-
|
|
866
|
-
|
|
1129
|
+
;; hash-first: slot stores the key's hash in its low 32 bits — one i32
|
|
1130
|
+
;; compare rejects collision steps without walking key bytes.
|
|
1131
|
+
(if (i32.eq (i32.load (local.get $slot)) (local.get $h))
|
|
1132
|
+
(then (if (call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))
|
|
1133
|
+
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))))
|
|
867
1134
|
(local.set $slot (i32.add (local.get $slot) (i32.const ${MAP_ENTRY})))
|
|
868
1135
|
(if (i32.ge_u (local.get $slot) (local.get $pend)) (then (local.set $slot (local.get $poff))))
|
|
869
1136
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
@@ -884,6 +1151,10 @@ export default (ctx) => {
|
|
|
884
1151
|
|
|
885
1152
|
ctx.core.stdlib['__dyn_get_expr_t'] = `(func $__dyn_get_expr_t (param $obj i64) (param $key i64) (param $t i32) (result i64)
|
|
886
1153
|
(local $val i64)
|
|
1154
|
+
;; Real-number receiver → no props; its garbage tag could match HASH and hit the
|
|
1155
|
+
;; __hash_get_local fallback below (heap read at a bogus offset → OOB).
|
|
1156
|
+
(if (f64.eq (f64.reinterpret_i64 (local.get $obj)) (f64.reinterpret_i64 (local.get $obj)))
|
|
1157
|
+
(then (return (i64.const ${UNDEF_NAN}))))
|
|
887
1158
|
(local.set $val (call $__dyn_get_t (local.get $obj) (local.get $key) (local.get $t)))
|
|
888
1159
|
(if (result i64)
|
|
889
1160
|
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
@@ -891,12 +1162,15 @@ export default (ctx) => {
|
|
|
891
1162
|
(else
|
|
892
1163
|
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
893
1164
|
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
894
|
-
(else (i64.const ${
|
|
1165
|
+
(else (i64.const ${UNDEF_NAN}))))))`
|
|
895
1166
|
|
|
896
1167
|
// Prehashed variant of __dyn_get_expr_t for constant string keys: the FNV hash
|
|
897
1168
|
// is folded at compile time (strHashLiteral), so no __str_hash call at runtime.
|
|
898
1169
|
ctx.core.stdlib['__dyn_get_expr_t_h'] = `(func $__dyn_get_expr_t_h (param $obj i64) (param $key i64) (param $t i32) (param $h i32) (result i64)
|
|
899
1170
|
(local $val i64)
|
|
1171
|
+
;; Real-number receiver → no props; guard the HASH fallback OOB (see __dyn_get_expr_t).
|
|
1172
|
+
(if (f64.eq (f64.reinterpret_i64 (local.get $obj)) (f64.reinterpret_i64 (local.get $obj)))
|
|
1173
|
+
(then (return (i64.const ${UNDEF_NAN}))))
|
|
900
1174
|
(local.set $val (call $__dyn_get_t_h (local.get $obj) (local.get $key) (local.get $t) (local.get $h)))
|
|
901
1175
|
(if (result i64)
|
|
902
1176
|
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
@@ -904,7 +1178,7 @@ export default (ctx) => {
|
|
|
904
1178
|
(else
|
|
905
1179
|
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
906
1180
|
(then (call $__hash_get_local_h (local.get $obj) (local.get $key) (local.get $h)))
|
|
907
|
-
(else (i64.const ${
|
|
1181
|
+
(else (i64.const ${UNDEF_NAN}))))))`
|
|
908
1182
|
|
|
909
1183
|
// Like __dyn_get_expr but also resolves EXTERNAL host objects via __ext_prop.
|
|
910
1184
|
// Used at call sites where receiver type is statically unknown.
|
|
@@ -922,18 +1196,24 @@ export default (ctx) => {
|
|
|
922
1196
|
const extArm = ctx.features.external
|
|
923
1197
|
? `(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.EXTERNAL}))
|
|
924
1198
|
(then (call $__ext_prop (local.get $obj) (local.get $key)))
|
|
925
|
-
(else (i64.const ${
|
|
926
|
-
: `(i64.const ${
|
|
1199
|
+
(else (i64.const ${UNDEF_NAN})))`
|
|
1200
|
+
: `(i64.const ${UNDEF_NAN})`
|
|
927
1201
|
return `(func $__dyn_get_any_t (param $obj i64) (param $key i64) (param $t i32) (result i64)
|
|
928
1202
|
(local $val i64)
|
|
929
|
-
|
|
930
|
-
|
|
1203
|
+
;; A real number receiver (f===f — NaN-boxed pointers are NaN) has no dynamic
|
|
1204
|
+
;; props: \`(5).foo\` is undefined. Without this guard the bits are reinterpreted as
|
|
1205
|
+
;; a pointer and \`__dyn_get_t\` reads heap at a bogus offset → OOB for large values.
|
|
1206
|
+
(if (result i64) (f64.eq (f64.reinterpret_i64 (local.get $obj)) (f64.reinterpret_i64 (local.get $obj)))
|
|
1207
|
+
(then (i64.const ${UNDEF_NAN}))
|
|
931
1208
|
(else
|
|
932
|
-
(
|
|
933
|
-
|
|
934
|
-
(
|
|
935
|
-
|
|
936
|
-
|
|
1209
|
+
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
1210
|
+
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
1211
|
+
(else
|
|
1212
|
+
(local.set $val (call $__dyn_get_t (local.get $obj) (local.get $key) (local.get $t)))
|
|
1213
|
+
(if (result i64)
|
|
1214
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
1215
|
+
(then (local.get $val))
|
|
1216
|
+
(else ${extArm})))))))`
|
|
937
1217
|
}
|
|
938
1218
|
|
|
939
1219
|
// Prehashed variant of __dyn_get_any_t for constant string keys: the FNV hash
|
|
@@ -944,18 +1224,22 @@ export default (ctx) => {
|
|
|
944
1224
|
const extArm = ctx.features.external
|
|
945
1225
|
? `(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.EXTERNAL}))
|
|
946
1226
|
(then (call $__ext_prop (local.get $obj) (local.get $key)))
|
|
947
|
-
(else (i64.const ${
|
|
948
|
-
: `(i64.const ${
|
|
1227
|
+
(else (i64.const ${UNDEF_NAN})))`
|
|
1228
|
+
: `(i64.const ${UNDEF_NAN})`
|
|
949
1229
|
return `(func $__dyn_get_any_t_h (param $obj i64) (param $key i64) (param $t i32) (param $h i32) (result i64)
|
|
950
1230
|
(local $val i64)
|
|
951
|
-
|
|
952
|
-
|
|
1231
|
+
;; Real-number receiver → no dynamic props (see __dyn_get_any_t); guard the OOB.
|
|
1232
|
+
(if (result i64) (f64.eq (f64.reinterpret_i64 (local.get $obj)) (f64.reinterpret_i64 (local.get $obj)))
|
|
1233
|
+
(then (i64.const ${UNDEF_NAN}))
|
|
953
1234
|
(else
|
|
954
|
-
(
|
|
955
|
-
|
|
956
|
-
(
|
|
957
|
-
|
|
958
|
-
|
|
1235
|
+
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
1236
|
+
(then (call $__hash_get_local_h (local.get $obj) (local.get $key) (local.get $h)))
|
|
1237
|
+
(else
|
|
1238
|
+
(local.set $val (call $__dyn_get_t_h (local.get $obj) (local.get $key) (local.get $t) (local.get $h)))
|
|
1239
|
+
(if (result i64)
|
|
1240
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
1241
|
+
(then (local.get $val))
|
|
1242
|
+
(else ${extArm})))))))`
|
|
959
1243
|
}
|
|
960
1244
|
|
|
961
1245
|
// Hot for `node.loc = pos` patterns (e.g. watr's parser tags every nested level).
|
|
@@ -1020,11 +1304,18 @@ export default (ctx) => {
|
|
|
1020
1304
|
(if (i64.ne (local.get $props) (local.get $oldProps))
|
|
1021
1305
|
(then (i64.store (i32.sub (local.get $off) (i32.const 16)) (local.get $props))))
|
|
1022
1306
|
(return (local.get $val))))
|
|
1307
|
+
;; HASH: a plain dict — its string keys ARE its own bucket entries (there is no
|
|
1308
|
+
;; off-16 sidecar; that belongs to TYPED/SET/MAP, which have a native shape PLUS
|
|
1309
|
+
;; ad-hoc props). Write directly into the receiver, mirroring __dyn_get's HASH arm.
|
|
1310
|
+
;; __hash_set_local forwards on grow, so the caller's boxed pointer stays valid.
|
|
1311
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.HASH}))
|
|
1312
|
+
(then
|
|
1313
|
+
(drop (call $__hash_set_local (local.get $obj) (local.get $key) (local.get $val)))
|
|
1314
|
+
(return (local.get $val))))
|
|
1023
1315
|
(if (i32.and (i32.ge_u (local.get $off) (i32.const 16))
|
|
1024
1316
|
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.TYPED}))
|
|
1025
|
-
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.
|
|
1026
|
-
|
|
1027
|
-
(i32.eq (local.get $type) (i32.const ${PTR.MAP}))))))
|
|
1317
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.SET}))
|
|
1318
|
+
(i32.eq (local.get $type) (i32.const ${PTR.MAP})))))
|
|
1028
1319
|
(then
|
|
1029
1320
|
(local.set $oldProps (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
1030
1321
|
(local.set $props
|
|
@@ -1148,9 +1439,9 @@ export default (ctx) => {
|
|
|
1148
1439
|
(global.set $__dyn_props (f64.reinterpret_i64 (local.get $root))))`
|
|
1149
1440
|
|
|
1150
1441
|
// Generated HASH probe functions
|
|
1151
|
-
ctx.core.stdlib['__hash_set'] = () => genUpsertGrow('__hash_set', MAP_ENTRY, '$__str_hash',
|
|
1152
|
-
ctx.core.stdlib['__hash_get'] = () => genLookup('__hash_get', MAP_ENTRY, '$__str_hash',
|
|
1153
|
-
ctx.core.stdlib['__hash_has'] = () => genLookup('__hash_has', MAP_ENTRY, '$__str_hash',
|
|
1442
|
+
ctx.core.stdlib['__hash_set'] = () => genUpsertGrow('__hash_set', MAP_ENTRY, '$__str_hash', strEqG, PTR.HASH, false, ctx.features.external, true)
|
|
1443
|
+
ctx.core.stdlib['__hash_get'] = () => genLookup('__hash_get', MAP_ENTRY, '$__str_hash', strEqG, PTR.HASH, true, ctx.features.external)
|
|
1444
|
+
ctx.core.stdlib['__hash_has'] = () => genLookup('__hash_has', MAP_ENTRY, '$__str_hash', strEqG, PTR.HASH, false, ctx.features.external)
|
|
1154
1445
|
|
|
1155
1446
|
// === `delete obj[k]`: lift from prepare for computed-key removal ===
|
|
1156
1447
|
// Static-key `delete obj.x` / `delete obj["x"]` is rejected in prepare (fixed schema);
|
|
@@ -1170,9 +1461,14 @@ export default (ctx) => {
|
|
|
1170
1461
|
if (prop === 'length' && (objType === VAL.ARRAY || objType === VAL.TYPED || objType === VAL.STRING || objType === VAL.SET || objType === VAL.MAP))
|
|
1171
1462
|
return typed(['i32.const', 1], 'i32')
|
|
1172
1463
|
|
|
1173
|
-
const schemaIdx = typeof obj === 'string' ? ctx.schema.
|
|
1464
|
+
const schemaIdx = typeof obj === 'string' ? ctx.schema.slotOf(obj, prop) : ctx.schema.slotOf(null, prop)
|
|
1174
1465
|
if (schemaIdx >= 0) return typed(['i32.const', 1], 'i32')
|
|
1175
|
-
|
|
1466
|
+
// A schema MISS does not prove absence: an OBJECT can carry off-schema
|
|
1467
|
+
// dynamic props (`o.z = …` → __dyn_set's propsPtr), and under the self-host
|
|
1468
|
+
// kernel schema.slotOf can under-resolve even an in-schema key. Don't fold to
|
|
1469
|
+
// a static 0 — fall through to the runtime probe below, which reads the
|
|
1470
|
+
// actual property via __dyn_get (OBJECT is in `hasDynProps`) and reports
|
|
1471
|
+
// presence by non-nullish, exactly as the `.`/`[]` READ path resolves it.
|
|
1176
1472
|
}
|
|
1177
1473
|
|
|
1178
1474
|
const keyTmp = temp()
|
|
@@ -1244,13 +1540,61 @@ export default (ctx) => {
|
|
|
1244
1540
|
['local.get', `$${outTmp}`]], 'i32')
|
|
1245
1541
|
}
|
|
1246
1542
|
|
|
1543
|
+
// === iterable normalization: Set/Map → dense Array ===
|
|
1544
|
+
|
|
1545
|
+
// `for (x of coll)` and `[...coll]` iterate in *value* order, but a Set/Map
|
|
1546
|
+
// stores entries in a sparse open-addressing table (live slots scattered among
|
|
1547
|
+
// empties). Index access `coll[i]` would read raw slot words. So normalize a
|
|
1548
|
+
// Set→keys-array / Map→[k,v]-entries-array once at loop/spread setup; an Array,
|
|
1549
|
+
// String, or TypedArray is already index-iterable and passes through untouched
|
|
1550
|
+
// (no copy). `valTypeOf(['()','__iter_arr',x])` (src/kind.js) mirrors this:
|
|
1551
|
+
// Set/Map → ARRAY, everything else → x's own type, so the downstream `arr[i]`
|
|
1552
|
+
// / `.length` dispatch stays statically typed.
|
|
1553
|
+
ctx.core.emit['__iter_arr'] = (src) => {
|
|
1554
|
+
const vt = valTypeOf(src)
|
|
1555
|
+
if (vt === VAL.ARRAY || vt === VAL.STRING || vt === VAL.TYPED || vt === VAL.BUFFER)
|
|
1556
|
+
return asF64(emit(src))
|
|
1557
|
+
const t = temp('iter')
|
|
1558
|
+
const bind = ['local.set', `$${t}`, asF64(emit(src))]
|
|
1559
|
+
if (vt === VAL.SET) return typed(['block', ['result', 'f64'], bind, collKeysFromTemp(t, SET_ENTRY)], 'f64')
|
|
1560
|
+
if (vt === VAL.MAP) return typed(['block', ['result', 'f64'], bind, collEntriesFromTemp(t, MAP_ENTRY)], 'f64')
|
|
1561
|
+
// Unknown receiver: resolve the kind once at runtime (loop-invariant).
|
|
1562
|
+
inc('__ptr_type')
|
|
1563
|
+
const ptrType = () => ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]
|
|
1564
|
+
return typed(['block', ['result', 'f64'], bind,
|
|
1565
|
+
['if', ['result', 'f64'], ['i32.eq', ptrType(), ['i32.const', PTR.SET]],
|
|
1566
|
+
['then', collKeysFromTemp(t, SET_ENTRY)],
|
|
1567
|
+
['else', ['if', ['result', 'f64'], ['i32.eq', ptrType(), ['i32.const', PTR.MAP]],
|
|
1568
|
+
['then', collEntriesFromTemp(t, MAP_ENTRY)],
|
|
1569
|
+
['else', ['local.get', `$${t}`]]]]]], 'f64')
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1247
1572
|
// === for...in on dynamic objects (HASH iteration) ===
|
|
1248
1573
|
|
|
1574
|
+
// Flatten a statement/block to void IR — a self-host-robust inline of
|
|
1575
|
+
// emitVoid+emitBlockBody (see the call site in for-in for why the bridge
|
|
1576
|
+
// emitVoid can't be used here). Recurses into `{}` blocks; emits each leaf
|
|
1577
|
+
// statement in void position and drops any leftover value.
|
|
1578
|
+
const emitFlatVoid = (node) => {
|
|
1579
|
+
if (isBlockBody(node)) {
|
|
1580
|
+
if (node.length === 1) return []
|
|
1581
|
+
const inner = node[1]
|
|
1582
|
+
const stmts = Array.isArray(inner) && inner[0] === ';' ? inner.slice(1) : [inner]
|
|
1583
|
+
const out = []
|
|
1584
|
+
for (const s of stmts) if (s != null && typeof s !== 'number') out.push(...emitFlatVoid(s))
|
|
1585
|
+
return out
|
|
1586
|
+
}
|
|
1587
|
+
const ir = emit(node, 'void')
|
|
1588
|
+
if (ir == null) return []
|
|
1589
|
+
const items = Array.isArray(ir) && (typeof ir[0] === 'string' || ir[0] == null) ? [ir] : ir
|
|
1590
|
+
return ir.type && ir.type !== 'void' ? [...items, 'drop'] : items
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1249
1593
|
// for-in: iterate HASH entries, binding key string to loop variable.
|
|
1250
1594
|
// Also handles OBJECT/ARRAY/etc whose dynamic props are stored at off-16
|
|
1251
1595
|
// as a HASH (see __dyn_set). Non-HASH receivers redirect to that props HASH.
|
|
1252
1596
|
ctx.core.emit['for-in'] = (varName, src, body) => {
|
|
1253
|
-
const off = tempI32('ho'), cap = tempI32('hc')
|
|
1597
|
+
const off = tempI32('ho'), cap = tempI32('hc'), n = tempI32('hn'), ord = tempI32('hr')
|
|
1254
1598
|
const i = tempI32('hi'), slot = tempI32('hs')
|
|
1255
1599
|
const ptrI64 = tempI64('hp'), srcOff = tempI32('hso'), srcType = tempI32('hst')
|
|
1256
1600
|
if (!ctx.func.locals.has(varName)) ctx.func.locals.set(varName, 'f64')
|
|
@@ -1260,10 +1604,17 @@ export default (ctx) => {
|
|
|
1260
1604
|
const needsCont = hasOwnContinue(body)
|
|
1261
1605
|
ctx.func.stack.push({ brk, loop: needsCont ? cont : loop })
|
|
1262
1606
|
let bodyFlat
|
|
1263
|
-
|
|
1607
|
+
// NOTE: `flat(body)` (the bridge-dispatched emitVoid) miscompiles in this
|
|
1608
|
+
// self-host call context — it returns [] for a void-postfix body
|
|
1609
|
+
// (`for (k in o) n++`, lowered to `(++n)-1`), silently dropping the loop
|
|
1610
|
+
// body so the kernel-compiled for-in iterates but does nothing. The exact
|
|
1611
|
+
// same emit+flatten logic inlined here compiles correctly. emitFlatVoid
|
|
1612
|
+
// mirrors emitVoid/emitBlockBody (minus early-return refinement narrowing,
|
|
1613
|
+
// which a loop body does not need).
|
|
1614
|
+
try { bodyFlat = emitFlatVoid(body) }
|
|
1264
1615
|
finally { ctx.func.stack.pop() }
|
|
1265
1616
|
const bodyBlock = needsCont ? [['block', cont, ...bodyFlat]] : bodyFlat
|
|
1266
|
-
inc('__ptr_type')
|
|
1617
|
+
inc('__ptr_type', '__len', '__coll_order')
|
|
1267
1618
|
return [
|
|
1268
1619
|
// Save source ptr as i64
|
|
1269
1620
|
['local.set', `$${ptrI64}`, ['i64.reinterpret_f64', va]],
|
|
@@ -1282,11 +1633,16 @@ export default (ctx) => {
|
|
|
1282
1633
|
['then',
|
|
1283
1634
|
['local.set', `$${off}`, ['call', '$__ptr_offset', ['local.get', `$${ptrI64}`]]],
|
|
1284
1635
|
['local.set', `$${cap}`, ['call', '$__cap', ['local.get', `$${ptrI64}`]]],
|
|
1636
|
+
['local.set', `$${n}`, ['call', '$__len', ['local.get', `$${ptrI64}`]]],
|
|
1637
|
+
// Snapshot live slots in insertion order (JS for-in spec order). Walk
|
|
1638
|
+
// the snapshot; re-check occupancy so a key the body deletes before it
|
|
1639
|
+
// is reached is skipped rather than re-bound from an emptied slot.
|
|
1640
|
+
['local.set', `$${ord}`, ['call', '$__coll_order', ['local.get', `$${off}`], ['local.get', `$${cap}`], ['i32.const', MAP_ENTRY]]],
|
|
1285
1641
|
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1286
1642
|
['block', brk, ['loop', loop,
|
|
1287
|
-
['br_if', brk, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${
|
|
1288
|
-
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${
|
|
1289
|
-
['i32.
|
|
1643
|
+
['br_if', brk, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
1644
|
+
['local.set', `$${slot}`, ['i32.load', ['i32.add', ['local.get', `$${ord}`],
|
|
1645
|
+
['i32.shl', ['local.get', `$${i}`], ['i32.const', 2]]]]],
|
|
1290
1646
|
['if', ['i64.ne', ['i64.load', ['local.get', `$${slot}`]], ['i64.const', 0]],
|
|
1291
1647
|
['then',
|
|
1292
1648
|
['local.set', `$${varName}`, ['f64.reinterpret_i64', ['i64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]]],
|
|
@@ -1296,3 +1652,105 @@ export default (ctx) => {
|
|
|
1296
1652
|
]
|
|
1297
1653
|
}
|
|
1298
1654
|
}
|
|
1655
|
+
|
|
1656
|
+
// Walk a Set/Map backing table (bound f64 local `t`), copying one column of each
|
|
1657
|
+
// live entry into a fresh dense Array sized to the live count. `stride` is the
|
|
1658
|
+
// entry size (Set 16, Map 24); the first f64 word of each slot is the stored
|
|
1659
|
+
// hash — 0 marks an empty slot (no tombstones: delete back-shifts). `fieldOff`
|
|
1660
|
+
// picks the column: 8 = key (Set element / Map key), 16 = Map value. Mirrors
|
|
1661
|
+
// object.js's hash*FromTemp. Used by `__iter_arr` and `.keys()`/`.values()`.
|
|
1662
|
+
function collKeysFromTemp(t, stride, fieldOff = 8) {
|
|
1663
|
+
inc('__ptr_offset', '__cap', '__len', '__coll_order')
|
|
1664
|
+
const off = tempI32('cko'), cap = tempI32('ckc'), n = tempI32('ckn')
|
|
1665
|
+
const i = tempI32('cki'), ord = tempI32('ckr'), slot = tempI32('cks')
|
|
1666
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'cka' })
|
|
1667
|
+
const id = ctx.func.uniq++
|
|
1668
|
+
return ['block', ['result', 'f64'],
|
|
1669
|
+
['local.set', `$${n}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1670
|
+
out.init,
|
|
1671
|
+
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1672
|
+
['local.set', `$${cap}`, ['call', '$__cap', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1673
|
+
['local.set', `$${ord}`, ['call', '$__coll_order', ['local.get', `$${off}`], ['local.get', `$${cap}`], ['i32.const', stride]]],
|
|
1674
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1675
|
+
['block', `$ckbrk${id}`, ['loop', `$ckloop${id}`,
|
|
1676
|
+
['br_if', `$ckbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
1677
|
+
['local.set', `$${slot}`, ['i32.load', ['i32.add', ['local.get', `$${ord}`],
|
|
1678
|
+
['i32.shl', ['local.get', `$${i}`], ['i32.const', 2]]]]],
|
|
1679
|
+
elemStore(out.local, i,
|
|
1680
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', fieldOff]]]),
|
|
1681
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1682
|
+
['br', `$ckloop${id}`]]],
|
|
1683
|
+
out.ptr]
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Like collKeysFromTemp but builds 2-element pair arrays — Map/Set `entries()`
|
|
1687
|
+
// and `[...map]` yield pairs. Each live slot contributes a fresh 2-element Array
|
|
1688
|
+
// [slot+aOff, slot+bOff] boxed into the output: Map entries use (8,16) → [k,v];
|
|
1689
|
+
// Set entries use (8,8) → [v,v].
|
|
1690
|
+
function collEntriesFromTemp(t, stride, aOff = 8, bOff = 16) {
|
|
1691
|
+
inc('__ptr_offset', '__cap', '__len', '__alloc_hdr', '__coll_order')
|
|
1692
|
+
const off = tempI32('ceo'), cap = tempI32('cec'), n = tempI32('cen')
|
|
1693
|
+
const i = tempI32('cei'), ord = tempI32('cer'), slot = tempI32('ces'), pair = tempI32('cep')
|
|
1694
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'cea' })
|
|
1695
|
+
const id = ctx.func.uniq++
|
|
1696
|
+
return ['block', ['result', 'f64'],
|
|
1697
|
+
['local.set', `$${n}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1698
|
+
out.init,
|
|
1699
|
+
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1700
|
+
['local.set', `$${cap}`, ['call', '$__cap', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1701
|
+
['local.set', `$${ord}`, ['call', '$__coll_order', ['local.get', `$${off}`], ['local.get', `$${cap}`], ['i32.const', stride]]],
|
|
1702
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1703
|
+
['block', `$cebrk${id}`, ['loop', `$celoop${id}`,
|
|
1704
|
+
['br_if', `$cebrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
1705
|
+
['local.set', `$${slot}`, ['i32.load', ['i32.add', ['local.get', `$${ord}`],
|
|
1706
|
+
['i32.shl', ['local.get', `$${i}`], ['i32.const', 2]]]]],
|
|
1707
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
1708
|
+
['f64.store', ['local.get', `$${pair}`],
|
|
1709
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', aOff]]]],
|
|
1710
|
+
['f64.store', ['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]],
|
|
1711
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', bOff]]]],
|
|
1712
|
+
elemStore(out.local, i, mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])),
|
|
1713
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1714
|
+
['br', `$celoop${id}`]]],
|
|
1715
|
+
out.ptr]
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// Array.prototype.keys() → dense Array of indices [0, 1, …, len-1] as numbers.
|
|
1719
|
+
function arrIdxFromTemp(t) {
|
|
1720
|
+
inc('__len')
|
|
1721
|
+
const n = tempI32('ain'), i = tempI32('aii')
|
|
1722
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'aia' })
|
|
1723
|
+
const id = ctx.func.uniq++
|
|
1724
|
+
return ['block', ['result', 'f64'],
|
|
1725
|
+
['local.set', `$${n}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1726
|
+
out.init,
|
|
1727
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1728
|
+
['block', `$aibrk${id}`, ['loop', `$ailoop${id}`,
|
|
1729
|
+
['br_if', `$aibrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
1730
|
+
elemStore(out.local, i, ['f64.convert_i32_s', ['local.get', `$${i}`]]),
|
|
1731
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1732
|
+
['br', `$ailoop${id}`]]],
|
|
1733
|
+
out.ptr]
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// Array.prototype.entries() → dense Array of [index, element] pair arrays.
|
|
1737
|
+
function arrEntriesFromTemp(t) {
|
|
1738
|
+
inc('__len', '__ptr_offset', '__alloc_hdr')
|
|
1739
|
+
const n = tempI32('aen'), i = tempI32('aei'), src = tempI32('aes'), pair = tempI32('aep')
|
|
1740
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'aea' })
|
|
1741
|
+
const id = ctx.func.uniq++
|
|
1742
|
+
return ['block', ['result', 'f64'],
|
|
1743
|
+
['local.set', `$${n}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1744
|
+
out.init,
|
|
1745
|
+
['local.set', `$${src}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1746
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1747
|
+
['block', `$aebrk${id}`, ['loop', `$aeloop${id}`,
|
|
1748
|
+
['br_if', `$aebrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
1749
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
1750
|
+
['f64.store', ['local.get', `$${pair}`], ['f64.convert_i32_s', ['local.get', `$${i}`]]],
|
|
1751
|
+
['f64.store', ['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]], elemLoad(src, i)],
|
|
1752
|
+
elemStore(out.local, i, mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])),
|
|
1753
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1754
|
+
['br', `$aeloop${id}`]]],
|
|
1755
|
+
out.ptr]
|
|
1756
|
+
}
|