jz 0.0.0 → 0.1.1
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/LICENSE +21 -0
- package/README.md +373 -0
- package/cli.js +163 -0
- package/index.js +247 -0
- package/module/array.js +1322 -0
- package/module/collection.js +793 -0
- package/module/console.js +192 -0
- package/module/core.js +644 -0
- package/module/function.js +181 -0
- package/module/index.js +15 -0
- package/module/json.js +506 -0
- package/module/math.js +390 -0
- package/module/number.js +601 -0
- package/module/object.js +359 -0
- package/module/regex.js +914 -0
- package/module/schema.js +106 -0
- package/module/string.js +985 -0
- package/module/symbol.js +55 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +713 -0
- package/package.json +56 -5
- package/src/analyze.js +1927 -0
- package/src/autoload.js +175 -0
- package/src/compile.js +1211 -0
- package/src/ctx.js +244 -0
- package/src/emit.js +2105 -0
- package/src/host.js +536 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +418 -0
- package/src/key.js +73 -0
- package/src/narrow.js +928 -0
- package/src/optimize.js +1352 -0
- package/src/plan.js +105 -0
- package/src/prepare.js +1534 -0
- package/src/source.js +76 -0
- package/wasi.js +80 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection module — Set, Map, HASH (dynamic string-keyed objects).
|
|
3
|
+
*
|
|
4
|
+
* Set: type=8, open addressing hash table. Entries: [hash:f64, key:f64] (16B each).
|
|
5
|
+
* Map: type=9, same but entries: [hash:f64, key:f64, val:f64] (24B each).
|
|
6
|
+
* HASH: type=7, same layout as Map but uses content-based string hash + equality.
|
|
7
|
+
*
|
|
8
|
+
* @module collection
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { typed, asF64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, allocPtr } from '../src/ir.js'
|
|
12
|
+
import { emit, emitFlat } from '../src/emit.js'
|
|
13
|
+
import { valTypeOf, lookupValType, VAL } from '../src/analyze.js'
|
|
14
|
+
import { inc, PTR } from '../src/ctx.js'
|
|
15
|
+
|
|
16
|
+
const SET_ENTRY = 16 // hash + key
|
|
17
|
+
const MAP_ENTRY = 24 // hash + key + value
|
|
18
|
+
const INIT_CAP = 8 // initial capacity (must be power of 2)
|
|
19
|
+
|
|
20
|
+
export function strHashLiteral(str) {
|
|
21
|
+
let h = 0x811c9dc5 | 0
|
|
22
|
+
for (let i = 0; i < str.length; i++) h = Math.imul(h ^ (str.charCodeAt(i) & 0xFF), 0x01000193) | 0
|
|
23
|
+
return h <= 1 ? (h + 2) | 0 : h
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const HASH_BUF = new ArrayBuffer(8)
|
|
27
|
+
const HASH_F64 = new Float64Array(HASH_BUF)
|
|
28
|
+
const HASH_U32 = new Uint32Array(HASH_BUF)
|
|
29
|
+
|
|
30
|
+
export function numHashLiteral(n) {
|
|
31
|
+
HASH_F64[0] = n
|
|
32
|
+
return (HASH_U32[0] ^ HASH_U32[1]) | 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function numConstLiteral(expr) {
|
|
36
|
+
if (typeof expr === 'number' && Number.isFinite(expr)) return expr
|
|
37
|
+
if (Array.isArray(expr) && expr[0] == null && typeof expr[1] === 'number' && Number.isFinite(expr[1])) return expr[1]
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Equality expressions for probe templates
|
|
42
|
+
const f64Eq = '(f64.eq (f64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
43
|
+
const strEq = '(call $__str_eq (f64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
44
|
+
|
|
45
|
+
/** Generate upsert (add/set) probe function. hasVal: store value at slot+16.
|
|
46
|
+
* hasExt: emit EXTERNAL fallthrough (call $__ext_set on non-matching type).
|
|
47
|
+
* Gated off → type mismatch just returns coll unchanged. */
|
|
48
|
+
function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt) {
|
|
49
|
+
const valParam = hasVal ? '(param $val f64) ' : ''
|
|
50
|
+
const storeVal = hasVal ? `\n (f64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))` : ''
|
|
51
|
+
const onMatch = hasVal
|
|
52
|
+
? `(then\n (f64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))\n (br $done))`
|
|
53
|
+
: `(then (br $done))`
|
|
54
|
+
|
|
55
|
+
const extBranch = hasVal
|
|
56
|
+
? '(then (call $__ext_set (local.get $coll) (local.get $key) (local.get $val)) drop)'
|
|
57
|
+
: '(then (nop))'
|
|
58
|
+
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))`
|
|
59
|
+
const typeGuard = hasExt
|
|
60
|
+
? `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL})) ${extBranch}) (return (local.get $coll))))`
|
|
61
|
+
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (return (local.get $coll))))`
|
|
62
|
+
return `(func $${name} (param $coll f64) (param $key f64) ${valParam}(result f64)
|
|
63
|
+
(local $bits i64) (local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32)
|
|
64
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $coll)))
|
|
65
|
+
${typeGuard}
|
|
66
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
67
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
68
|
+
(local.set $h (call ${hashFn} (local.get $key)))
|
|
69
|
+
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
70
|
+
(block $done (loop $probe
|
|
71
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
72
|
+
(if (f64.eq (f64.load (local.get $slot)) (f64.const 0))
|
|
73
|
+
(then
|
|
74
|
+
(f64.store (local.get $slot) (f64.reinterpret_i64 (i64.extend_i32_u (local.get $h))))
|
|
75
|
+
(f64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${storeVal}
|
|
76
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
77
|
+
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
78
|
+
(br $done)))
|
|
79
|
+
(if ${eqExpr} ${onMatch})
|
|
80
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
81
|
+
(br $probe)))
|
|
82
|
+
(local.get $coll))`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Generate lookup probe function.
|
|
86
|
+
* wantValue=true: return slot value, missing => NULL_NAN sentinel.
|
|
87
|
+
* wantValue=false: return i32 0/1 existence flag.
|
|
88
|
+
* hasExt: emit EXTERNAL fallthrough (delegate to __ext_prop/__ext_has). */
|
|
89
|
+
function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, hasExt) {
|
|
90
|
+
const rt = wantValue ? 'f64' : 'i32'
|
|
91
|
+
const onEmpty = wantValue
|
|
92
|
+
? `(return (f64.const nan:${NULL_NAN}))`
|
|
93
|
+
: '(return (i32.const 0))'
|
|
94
|
+
const onFound = wantValue
|
|
95
|
+
? '(return (f64.load (i32.add (local.get $slot) (i32.const 16))))'
|
|
96
|
+
: '(return (i32.const 1))'
|
|
97
|
+
const notFound = wantValue
|
|
98
|
+
? `(f64.const nan:${NULL_NAN})`
|
|
99
|
+
: '(i32.const 0)'
|
|
100
|
+
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))`
|
|
101
|
+
const typeGuard = hasExt
|
|
102
|
+
? `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL}))
|
|
103
|
+
(then (return (call $__ext_${wantValue ? 'prop' : 'has'} (local.get $coll) (local.get $key))))
|
|
104
|
+
(else ${onEmpty}))))`
|
|
105
|
+
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then ${onEmpty}))`
|
|
106
|
+
|
|
107
|
+
return `(func $${name} (param $coll f64) (param $key f64) (result ${rt})
|
|
108
|
+
(local $bits i64) (local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
109
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $coll)))
|
|
110
|
+
${typeGuard}
|
|
111
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
112
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
113
|
+
(local.set $h (call ${hashFn} (local.get $key)))
|
|
114
|
+
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
115
|
+
(block $done (loop $probe
|
|
116
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
117
|
+
(if (f64.eq (f64.load (local.get $slot)) (f64.const 0)) (then ${onEmpty}))
|
|
118
|
+
(if ${eqExpr} (then ${onFound}))
|
|
119
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
120
|
+
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
121
|
+
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
122
|
+
(br $probe)))
|
|
123
|
+
${notFound})`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Generate delete probe function. Zero out entry on match. */
|
|
127
|
+
function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
128
|
+
return `(func $${name} (param $coll f64) (param $key f64) (result i32)
|
|
129
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
130
|
+
(if (i32.ne (call $__ptr_type (local.get $coll)) (i32.const ${expectedType})) (then (return (i32.const 0))))
|
|
131
|
+
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
132
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
133
|
+
(local.set $h (call ${hashFn} (local.get $key)))
|
|
134
|
+
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
135
|
+
(block $done (loop $probe
|
|
136
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
137
|
+
(if (f64.eq (f64.load (local.get $slot)) (f64.const 0)) (then (return (i32.const 0))))
|
|
138
|
+
(if ${eqExpr}
|
|
139
|
+
(then
|
|
140
|
+
(f64.store (local.get $slot) (f64.const 0))
|
|
141
|
+
(f64.store (i32.add (local.get $slot) (i32.const 8)) (f64.const 0))
|
|
142
|
+
(return (i32.const 1))))
|
|
143
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
144
|
+
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
145
|
+
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
146
|
+
(br $probe)))
|
|
147
|
+
(i32.const 0))`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Generate growable upsert. Grows table at 75% load, rehashes, then inserts.
|
|
151
|
+
* strict=true: reject wrong type.
|
|
152
|
+
* strict=false: EXTERNAL → __ext_set, other non-HASH types → __dyn_set (global props).
|
|
153
|
+
* The non-strict fallback is critical for untyped variables (e.g. arrays from
|
|
154
|
+
* Object.create) that receive property writes — without it writes silently vanish. */
|
|
155
|
+
function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = false, hasExt = false) {
|
|
156
|
+
const nonHashFallback = hasExt
|
|
157
|
+
? `(if (i32.eq (call $__ptr_type (local.get $obj)) (i32.const ${PTR.EXTERNAL}))
|
|
158
|
+
(then (call $__ext_set (local.get $obj) (local.get $key) (local.get $val)) drop)
|
|
159
|
+
(else (call $__dyn_set (local.get $obj) (local.get $key) (local.get $val)) drop))`
|
|
160
|
+
: `(call $__dyn_set (local.get $obj) (local.get $key) (local.get $val)) drop`
|
|
161
|
+
const typeGuard = strict
|
|
162
|
+
? `(if (i32.ne (call $__ptr_type (local.get $obj)) (i32.const ${typeConst}))
|
|
163
|
+
(then (return (local.get $obj))))`
|
|
164
|
+
: `(if (i32.ne (call $__ptr_type (local.get $obj)) (i32.const ${typeConst}))
|
|
165
|
+
(then
|
|
166
|
+
${nonHashFallback}
|
|
167
|
+
(return (local.get $obj))))`
|
|
168
|
+
return `(func $${name} (param $obj f64) (param $key f64) (param $val f64) (result f64)
|
|
169
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32)
|
|
170
|
+
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
171
|
+
(local $oldslot i32) (local $newidx i32) (local $newslot i32)
|
|
172
|
+
${typeGuard}
|
|
173
|
+
(local.set $off (call $__ptr_offset (local.get $obj)))
|
|
174
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
175
|
+
(local.set $size (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
176
|
+
;; Grow if load factor > 75%: size * 4 >= cap * 3
|
|
177
|
+
(if (i32.ge_s (i32.mul (local.get $size) (i32.const 4)) (i32.mul (local.get $cap) (i32.const 3)))
|
|
178
|
+
(then
|
|
179
|
+
(local.set $newcap (i32.shl (local.get $cap) (i32.const 1)))
|
|
180
|
+
(local.set $newptr (call $__alloc_hdr (i32.const 0) (local.get $newcap) (i32.const ${entrySize})))
|
|
181
|
+
(local.set $i (i32.const 0))
|
|
182
|
+
(block $rd (loop $rl
|
|
183
|
+
(br_if $rd (i32.ge_s (local.get $i) (local.get $cap)))
|
|
184
|
+
(local.set $oldslot (i32.add (local.get $off) (i32.mul (local.get $i) (i32.const ${entrySize}))))
|
|
185
|
+
(if (f64.ne (f64.load (local.get $oldslot)) (f64.const 0))
|
|
186
|
+
(then
|
|
187
|
+
(local.set $h (call ${hashFn} (f64.load (i32.add (local.get $oldslot) (i32.const 8)))))
|
|
188
|
+
(local.set $newidx (i32.and (local.get $h) (i32.sub (local.get $newcap) (i32.const 1))))
|
|
189
|
+
(block $ins (loop $probe2
|
|
190
|
+
(local.set $newslot (i32.add (local.get $newptr) (i32.mul (local.get $newidx) (i32.const ${entrySize}))))
|
|
191
|
+
(br_if $ins (f64.eq (f64.load (local.get $newslot)) (f64.const 0)))
|
|
192
|
+
(local.set $newidx (i32.and (i32.add (local.get $newidx) (i32.const 1)) (i32.sub (local.get $newcap) (i32.const 1))))
|
|
193
|
+
(br $probe2)))
|
|
194
|
+
(f64.store (local.get $newslot) (f64.load (local.get $oldslot)))
|
|
195
|
+
(f64.store (i32.add (local.get $newslot) (i32.const 8)) (f64.load (i32.add (local.get $oldslot) (i32.const 8))))
|
|
196
|
+
(f64.store (i32.add (local.get $newslot) (i32.const 16)) (f64.load (i32.add (local.get $oldslot) (i32.const 16))))
|
|
197
|
+
(i32.store (i32.sub (local.get $newptr) (i32.const 8))
|
|
198
|
+
(i32.add (i32.load (i32.sub (local.get $newptr) (i32.const 8))) (i32.const 1)))))
|
|
199
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
200
|
+
(br $rl)))
|
|
201
|
+
(local.set $off (local.get $newptr))
|
|
202
|
+
(local.set $cap (local.get $newcap))
|
|
203
|
+
(local.set $obj (call $__mkptr (i32.const ${typeConst}) (i32.const 0) (local.get $newptr)))))
|
|
204
|
+
;; Insert/update
|
|
205
|
+
(local.set $h (call ${hashFn} (local.get $key)))
|
|
206
|
+
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
207
|
+
(block $done (loop $probe
|
|
208
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
209
|
+
(if (f64.eq (f64.load (local.get $slot)) (f64.const 0))
|
|
210
|
+
(then
|
|
211
|
+
(f64.store (local.get $slot) (f64.reinterpret_i64 (i64.extend_i32_u (local.get $h))))
|
|
212
|
+
(f64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))
|
|
213
|
+
(f64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
214
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
215
|
+
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
216
|
+
(br $done)))
|
|
217
|
+
(if ${eqExpr}
|
|
218
|
+
(then
|
|
219
|
+
(f64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
220
|
+
(br $done)))
|
|
221
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
222
|
+
(br $probe)))
|
|
223
|
+
(local.get $obj))`
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing = UNDEF_NAN) {
|
|
227
|
+
return `(func $${name} (param $coll f64) (param $key f64) (result f64)
|
|
228
|
+
(local $bits i64) (local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
229
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $coll)))
|
|
230
|
+
(if (i32.ne
|
|
231
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
232
|
+
(i32.const ${expectedType}))
|
|
233
|
+
(then (return (f64.const nan:${missing}))))
|
|
234
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
235
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
236
|
+
(local.set $h (call ${hashFn} (local.get $key)))
|
|
237
|
+
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
238
|
+
(block $done (loop $probe
|
|
239
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
240
|
+
(if (f64.eq (f64.load (local.get $slot)) (f64.const 0))
|
|
241
|
+
(then (return (f64.const nan:${missing}))))
|
|
242
|
+
(if ${eqExpr}
|
|
243
|
+
(then (return (f64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
244
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
245
|
+
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
246
|
+
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
247
|
+
(br $probe)))
|
|
248
|
+
(f64.const nan:${missing}))`
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing = UNDEF_NAN, hasExt = false) {
|
|
252
|
+
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))`
|
|
253
|
+
const typeGuard = hasExt
|
|
254
|
+
? `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
255
|
+
(then
|
|
256
|
+
(if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL}))
|
|
257
|
+
(then (return (call $__ext_prop (local.get $coll) (local.get $key))))
|
|
258
|
+
(else (return (f64.const nan:${missing}))))))`
|
|
259
|
+
: `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
260
|
+
(then (return (f64.const nan:${missing}))))`
|
|
261
|
+
return `(func $${name} (param $coll f64) (param $key f64) (param $h i32) (result f64)
|
|
262
|
+
(local $bits i64) (local $off i32) (local $cap i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
263
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $coll)))
|
|
264
|
+
${typeGuard}
|
|
265
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
266
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
267
|
+
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
268
|
+
(block $done (loop $probe
|
|
269
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
270
|
+
(if (f64.eq (f64.load (local.get $slot)) (f64.const 0))
|
|
271
|
+
(then (return (f64.const nan:${missing}))))
|
|
272
|
+
(if ${eqExpr}
|
|
273
|
+
(then (return (f64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
274
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
275
|
+
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
276
|
+
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
277
|
+
(br $probe)))
|
|
278
|
+
(f64.const nan:${missing}))`
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
282
|
+
return `(func $${name} (param $obj f64) (param $key f64) (param $h i32) (param $val f64) (result f64)
|
|
283
|
+
(local $bits i64) (local $off i32) (local $cap i32) (local $idx i32) (local $slot i32)
|
|
284
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $obj)))
|
|
285
|
+
(if (i32.ne
|
|
286
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
287
|
+
(i32.const ${expectedType}))
|
|
288
|
+
(then (return (local.get $obj))))
|
|
289
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
290
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
291
|
+
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
292
|
+
(block $done (loop $probe
|
|
293
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
294
|
+
(if (f64.eq (f64.load (local.get $slot)) (f64.const 0))
|
|
295
|
+
(then
|
|
296
|
+
(f64.store (local.get $slot) (f64.reinterpret_i64 (i64.extend_i32_u (local.get $h))))
|
|
297
|
+
(f64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))
|
|
298
|
+
(f64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
299
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
300
|
+
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
301
|
+
(br $done)))
|
|
302
|
+
(if ${eqExpr}
|
|
303
|
+
(then
|
|
304
|
+
(f64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
305
|
+
(br $done)))
|
|
306
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
307
|
+
(br $probe)))
|
|
308
|
+
(local.get $obj))`
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
export default (ctx) => {
|
|
313
|
+
// Feature-gated deps: EXTERNAL-dependent symbols are only pulled when features.external.
|
|
314
|
+
// Evaluated lazily at resolveIncludes() time — after emission has finalized ctx.features.
|
|
315
|
+
const ifExt = (name) => () => ctx.features.external ? [name] : []
|
|
316
|
+
Object.assign(ctx.core.stdlibDeps, {
|
|
317
|
+
__set_has: ifExt('__ext_has'),
|
|
318
|
+
__set_delete: [],
|
|
319
|
+
__map_set: ifExt('__ext_set'),
|
|
320
|
+
__map_get: () => ctx.features.external ? ['__ext_prop', '__map_set'] : ['__map_set'],
|
|
321
|
+
__map_get_h: () => ctx.features.external ? ['__ext_prop'] : [],
|
|
322
|
+
__map_delete: [],
|
|
323
|
+
__hash_set: () => ctx.features.external
|
|
324
|
+
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_set', '__dyn_set']
|
|
325
|
+
: ['__str_hash', '__str_eq', '__ptr_type', '__dyn_set'],
|
|
326
|
+
__hash_get: () => ctx.features.external
|
|
327
|
+
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_prop']
|
|
328
|
+
: ['__str_hash', '__str_eq', '__ptr_type'],
|
|
329
|
+
__hash_has: () => ctx.features.external
|
|
330
|
+
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_has']
|
|
331
|
+
: ['__str_hash', '__str_eq', '__ptr_type'],
|
|
332
|
+
__hash_new: ['__alloc_hdr'],
|
|
333
|
+
__hash_get_local: ['__str_hash', '__str_eq'],
|
|
334
|
+
__hash_get_local_h: ['__str_eq'],
|
|
335
|
+
__hash_set_local_h: ['__str_eq'],
|
|
336
|
+
__hash_set_local: ['__str_hash', '__str_eq'],
|
|
337
|
+
__ihash_get_local: ['__hash'],
|
|
338
|
+
__ihash_set_local: ['__hash', '__alloc_hdr', '__mkptr'],
|
|
339
|
+
__dyn_get_t: ['__ihash_get_local', '__str_hash', '__str_eq', '__is_nullish'],
|
|
340
|
+
__dyn_get: ['__dyn_get_t', '__ptr_type'],
|
|
341
|
+
__dyn_get_expr_t: ['__dyn_get_t', '__hash_get_local'],
|
|
342
|
+
__dyn_get_expr: ['__dyn_get_expr_t', '__ptr_type'],
|
|
343
|
+
__dyn_get_any: ['__dyn_get_any_t', '__ptr_type'],
|
|
344
|
+
__dyn_get_any_t: () => ctx.features.external
|
|
345
|
+
? ['__dyn_get_t', '__hash_get_local', '__ext_prop']
|
|
346
|
+
: ['__dyn_get_t', '__hash_get_local'],
|
|
347
|
+
__dyn_get_or: ['__dyn_get'],
|
|
348
|
+
__dyn_set: ['__hash_new', '__ihash_get_local', '__ihash_set_local', '__hash_set_local', '__ptr_offset', '__is_nullish'],
|
|
349
|
+
__dyn_move: ['__ihash_get_local', '__ihash_set_local', '__is_nullish'],
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
inc('__ptr_offset', '__cap')
|
|
353
|
+
|
|
354
|
+
if (!ctx.scope.globals.has('__dyn_props'))
|
|
355
|
+
ctx.scope.globals.set('__dyn_props', '(global $__dyn_props (mut f64) (f64.const 0))')
|
|
356
|
+
// Schema name table for __dyn_get's OBJECT-schema fallback (polymorphic-receiver
|
|
357
|
+
// `.prop` access). Same declaration as json.js — defined here too so collection
|
|
358
|
+
// doesn't transitively require json. compile.js's schemaInit populates it when
|
|
359
|
+
// schema list is non-empty AND (__stringify OR __dyn_get) is included.
|
|
360
|
+
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
361
|
+
ctx.scope.globals.set('__schema_tbl', '(global $__schema_tbl (mut i32) (i32.const 0))')
|
|
362
|
+
|
|
363
|
+
ctx.core.stdlib['__ext_prop'] = '(import "env" "__ext_prop" (func $__ext_prop (param f64 f64) (result f64)))'
|
|
364
|
+
ctx.core.stdlib['__ext_has'] = '(import "env" "__ext_has" (func $__ext_has (param f64 f64) (result i32)))'
|
|
365
|
+
ctx.core.stdlib['__ext_set'] = '(import "env" "__ext_set" (func $__ext_set (param f64 f64 f64) (result i32)))'
|
|
366
|
+
ctx.core.stdlib['__ext_call'] = '(import "env" "__ext_call" (func $__ext_call (param f64 f64 f64) (result f64)))'
|
|
367
|
+
// Hash function: simple f64 → i32 hash
|
|
368
|
+
ctx.core.stdlib['__hash'] = `(func $__hash (param $v f64) (result i32)
|
|
369
|
+
(i32.wrap_i64 (i64.xor
|
|
370
|
+
(i64.reinterpret_f64 (local.get $v))
|
|
371
|
+
(i64.shr_u (i64.reinterpret_f64 (local.get $v)) (i64.const 32)))))`
|
|
372
|
+
inc('__hash')
|
|
373
|
+
|
|
374
|
+
// __map_new() → f64 — allocate empty Map (for JSON.parse, runtime creation)
|
|
375
|
+
ctx.core.stdlib['__map_new'] = `(func $__map_new (result f64)
|
|
376
|
+
(call $__mkptr (i32.const ${PTR.MAP}) (i32.const 0)
|
|
377
|
+
(call $__alloc_hdr (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))`
|
|
378
|
+
|
|
379
|
+
// === Set ===
|
|
380
|
+
|
|
381
|
+
ctx.core.emit['new.Set'] = () => {
|
|
382
|
+
ctx.features.set = true
|
|
383
|
+
const out = allocPtr({ type: PTR.SET, len: 0, cap: INIT_CAP, stride: SET_ENTRY, tag: 'set' })
|
|
384
|
+
return typed(['block', ['result', 'f64'], out.init, out.ptr], 'f64')
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
ctx.core.emit['.add'] = (setExpr, val) => {
|
|
388
|
+
inc('__set_add')
|
|
389
|
+
return typed(['call', '$__set_add', asF64(emit(setExpr)), asF64(emit(val))], 'f64')
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
ctx.core.emit['.has'] = (setExpr, val) => {
|
|
393
|
+
inc('__set_has')
|
|
394
|
+
return typed(['f64.convert_i32_s', ['call', '$__set_has', asF64(emit(setExpr)), asF64(emit(val))]], 'f64')
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
ctx.core.emit['.delete'] = (setExpr, val) => {
|
|
398
|
+
inc('__set_delete')
|
|
399
|
+
return typed(['f64.convert_i32_s', ['call', '$__set_delete', asF64(emit(setExpr)), asF64(emit(val))]], 'f64')
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
ctx.core.emit['.size'] = (expr) => {
|
|
403
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', asF64(emit(expr))]], 'f64')
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Generated Set probe functions
|
|
407
|
+
ctx.core.stdlib['__set_add'] = () => genUpsert('__set_add', SET_ENTRY, '$__hash', f64Eq, PTR.SET, false, ctx.features.external)
|
|
408
|
+
ctx.core.stdlib['__set_has'] = () => genLookup('__set_has', SET_ENTRY, '$__hash', f64Eq, PTR.SET, false, ctx.features.external)
|
|
409
|
+
ctx.core.stdlib['__set_delete'] = genDelete('__set_delete', SET_ENTRY, '$__hash', f64Eq, PTR.SET)
|
|
410
|
+
|
|
411
|
+
// === Map ===
|
|
412
|
+
|
|
413
|
+
ctx.core.emit['new.Map'] = () => {
|
|
414
|
+
ctx.features.map = true
|
|
415
|
+
const out = allocPtr({ type: PTR.MAP, len: 0, cap: INIT_CAP, stride: MAP_ENTRY, tag: 'map' })
|
|
416
|
+
return typed(['block', ['result', 'f64'], out.init, out.ptr], 'f64')
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
ctx.core.emit['.set'] = (mapExpr, key, val) => {
|
|
420
|
+
inc('__map_set')
|
|
421
|
+
return typed(['call', '$__map_set', asF64(emit(mapExpr)), asF64(emit(key)), asF64(emit(val))], 'f64')
|
|
422
|
+
}
|
|
423
|
+
ctx.core.emit[`.${VAL.MAP}:set`] = ctx.core.emit['.set']
|
|
424
|
+
|
|
425
|
+
const emitMapGet = (mapExpr, key) => {
|
|
426
|
+
const constKey = numConstLiteral(key)
|
|
427
|
+
if (constKey != null) {
|
|
428
|
+
inc('__map_get_h')
|
|
429
|
+
return typed(['call', '$__map_get_h', asF64(emit(mapExpr)), asF64(emit(key)), ['i32.const', numHashLiteral(constKey)]], 'f64')
|
|
430
|
+
}
|
|
431
|
+
inc('__map_get')
|
|
432
|
+
return typed(['call', '$__map_get', asF64(emit(mapExpr)), asF64(emit(key))], 'f64')
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
ctx.core.emit['.get'] = emitMapGet
|
|
436
|
+
ctx.core.emit[`.${VAL.MAP}:get`] = emitMapGet
|
|
437
|
+
|
|
438
|
+
// Generated Map probe functions
|
|
439
|
+
ctx.core.stdlib['__map_set'] = () => genUpsert('__map_set', MAP_ENTRY, '$__hash', f64Eq, PTR.MAP, true, ctx.features.external)
|
|
440
|
+
ctx.core.stdlib['__map_get'] = () => genLookup('__map_get', MAP_ENTRY, '$__hash', f64Eq, PTR.MAP, true, ctx.features.external)
|
|
441
|
+
ctx.core.stdlib['__map_get_h'] = () => genLookupStrictPrehashed('__map_get_h', MAP_ENTRY, f64Eq, PTR.MAP, NULL_NAN, ctx.features.external)
|
|
442
|
+
|
|
443
|
+
// === HASH — dynamic string-keyed object (type=7) ===
|
|
444
|
+
|
|
445
|
+
// FNV-1a hash of string content (works on both SSO and heap strings)
|
|
446
|
+
// FNV-1a. ~95M calls in watr self-host. Inline char-fetch: hoist type/offset out of the
|
|
447
|
+
// byte loop so SSO branch uses dword shifts and STRING branch uses raw load8_u — neither
|
|
448
|
+
// calls anything per byte (vs original 1×__char_at → __ptr_type + __ptr_offset per byte).
|
|
449
|
+
ctx.core.stdlib['__str_hash'] = `(func $__str_hash (param $s f64) (result i32)
|
|
450
|
+
(local $h i32) (local $len i32) (local $lenA i32) (local $i i32) (local $t i32) (local $off i32) (local $bits i64) (local $w i32)
|
|
451
|
+
(local.set $h (i32.const 0x811c9dc5))
|
|
452
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $s)))
|
|
453
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF))))
|
|
454
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
455
|
+
(if (i32.eq (local.get $t) (i32.const ${PTR.SSO}))
|
|
456
|
+
(then
|
|
457
|
+
(local.set $len (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 32)) (i64.const 0x7FFF))))
|
|
458
|
+
(block $ds (loop $ls
|
|
459
|
+
(br_if $ds (i32.ge_s (local.get $i) (local.get $len)))
|
|
460
|
+
(local.set $h (i32.mul
|
|
461
|
+
(i32.xor (local.get $h)
|
|
462
|
+
(i32.and (i32.shr_u (local.get $off) (i32.shl (local.get $i) (i32.const 3))) (i32.const 0xFF)))
|
|
463
|
+
(i32.const 0x01000193)))
|
|
464
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
465
|
+
(br $ls))))
|
|
466
|
+
(else
|
|
467
|
+
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.STRING})) (i32.ge_u (local.get $off) (i32.const 4)))
|
|
468
|
+
(then (local.set $len (i32.load (i32.sub (local.get $off) (i32.const 4))))))
|
|
469
|
+
;; 4-byte unrolled FNV-1a: each iter loads i32, mixes 4 bytes (little-endian) sequentially.
|
|
470
|
+
(local.set $lenA (i32.and (local.get $len) (i32.const -4)))
|
|
471
|
+
(block $d4 (loop $l4
|
|
472
|
+
(br_if $d4 (i32.ge_s (local.get $i) (local.get $lenA)))
|
|
473
|
+
(local.set $w (i32.load (i32.add (local.get $off) (local.get $i))))
|
|
474
|
+
(local.set $h (i32.mul (i32.xor (local.get $h) (i32.and (local.get $w) (i32.const 0xFF))) (i32.const 0x01000193)))
|
|
475
|
+
(local.set $h (i32.mul (i32.xor (local.get $h) (i32.and (i32.shr_u (local.get $w) (i32.const 8)) (i32.const 0xFF))) (i32.const 0x01000193)))
|
|
476
|
+
(local.set $h (i32.mul (i32.xor (local.get $h) (i32.and (i32.shr_u (local.get $w) (i32.const 16)) (i32.const 0xFF))) (i32.const 0x01000193)))
|
|
477
|
+
(local.set $h (i32.mul (i32.xor (local.get $h) (i32.shr_u (local.get $w) (i32.const 24))) (i32.const 0x01000193)))
|
|
478
|
+
(local.set $i (i32.add (local.get $i) (i32.const 4)))
|
|
479
|
+
(br $l4)))
|
|
480
|
+
(block $dh (loop $lh
|
|
481
|
+
(br_if $dh (i32.ge_s (local.get $i) (local.get $len)))
|
|
482
|
+
(local.set $h (i32.mul
|
|
483
|
+
(i32.xor (local.get $h)
|
|
484
|
+
(i32.load8_u (i32.add (local.get $off) (local.get $i))))
|
|
485
|
+
(i32.const 0x01000193)))
|
|
486
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
487
|
+
(br $lh)))))
|
|
488
|
+
;; Ensure >= 2 (0=empty, 1=tombstone)
|
|
489
|
+
(if (result i32) (i32.le_s (local.get $h) (i32.const 1))
|
|
490
|
+
(then (i32.add (local.get $h) (i32.const 2))) (else (local.get $h))))`
|
|
491
|
+
|
|
492
|
+
ctx.core.stdlib['__hash_new'] = `(func $__hash_new (result f64)
|
|
493
|
+
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
494
|
+
(call $__alloc_hdr (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))`
|
|
495
|
+
|
|
496
|
+
ctx.core.stdlib['__hash_get_local'] = genLookupStrict('__hash_get_local', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH)
|
|
497
|
+
ctx.core.stdlib['__hash_get_local_h'] = genLookupStrictPrehashed('__hash_get_local_h', MAP_ENTRY, strEq, PTR.HASH)
|
|
498
|
+
ctx.core.stdlib['__hash_set_local_h'] = genUpsertStrictPrehashed('__hash_set_local_h', MAP_ENTRY, strEq, PTR.HASH)
|
|
499
|
+
ctx.core.stdlib['__hash_set_local'] = genUpsertGrow('__hash_set_local', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, true)
|
|
500
|
+
// Outer __dyn_props hash: keyed by object offset (i32 as f64), value is per-object props hash.
|
|
501
|
+
// Uses bit-hash + f64.eq — no string allocation for the unique integer key.
|
|
502
|
+
ctx.core.stdlib['__ihash_get_local'] = genLookupStrict('__ihash_get_local', MAP_ENTRY, '$__hash', f64Eq, PTR.HASH)
|
|
503
|
+
ctx.core.stdlib['__ihash_set_local'] = genUpsertGrow('__ihash_set_local', MAP_ENTRY, '$__hash', f64Eq, PTR.HASH, true)
|
|
504
|
+
|
|
505
|
+
// Inline __ptr_offset (forwarding-aware) and __hash_get_local body — dyn_get is the
|
|
506
|
+
// single hottest stdlib symbol in watr self-host (~95M calls). props returned by
|
|
507
|
+
// __ihash_get_local is always HASH (or NULL_NAN, filtered by __is_nullish), so the
|
|
508
|
+
// inlined probe skips a redundant type check + bit unboxing per call.
|
|
509
|
+
//
|
|
510
|
+
// OBJECT receivers fall back to schema-aware slot lookup when __dyn_props has no
|
|
511
|
+
// entry — covers polymorphic-receiver patterns (e.g. `let o = w?n():s()` with
|
|
512
|
+
// structurally distinct schemas) where receiver schemaId is unknown at compile
|
|
513
|
+
// time but lives at runtime in the NaN-box aux bits. Gated on schema name table
|
|
514
|
+
// presence (lifted in compile.js whenever __dyn_get is included). Static-shape
|
|
515
|
+
// monomorphic OBJECTs hit the compile-time slot read path and never reach here.
|
|
516
|
+
const hasSchemas = ctx.schema.list.length > 0
|
|
517
|
+
const objectSchemaArm = hasSchemas ? `
|
|
518
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
519
|
+
(then
|
|
520
|
+
(if (i32.ne (global.get $__schema_tbl) (i32.const 0))
|
|
521
|
+
(then
|
|
522
|
+
(local.set $sid (i32.wrap_i64 (i64.and (i64.shr_u
|
|
523
|
+
(i64.reinterpret_f64 (local.get $obj)) (i64.const 32)) (i64.const 0x7FFF))))
|
|
524
|
+
(local.set $kbits (i64.reinterpret_f64
|
|
525
|
+
(f64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))
|
|
526
|
+
(local.set $koff (i32.wrap_i64 (i64.and (local.get $kbits) (i64.const 0xFFFFFFFF))))
|
|
527
|
+
(local.set $nkeys (i32.load (i32.sub (local.get $koff) (i32.const 8))))
|
|
528
|
+
(local.set $idx (i32.const 0))
|
|
529
|
+
(block $kdone (loop $kloop
|
|
530
|
+
(br_if $kdone (i32.ge_s (local.get $idx) (local.get $nkeys)))
|
|
531
|
+
(if (call $__str_eq
|
|
532
|
+
(f64.load (i32.add (local.get $koff) (i32.shl (local.get $idx) (i32.const 3))))
|
|
533
|
+
(local.get $key))
|
|
534
|
+
(then (return (f64.load (i32.add (local.get $off) (i32.shl (local.get $idx) (i32.const 3)))))))
|
|
535
|
+
(local.set $idx (i32.add (local.get $idx) (i32.const 1)))
|
|
536
|
+
(br $kloop)))))))` : ''
|
|
537
|
+
const objectSchemaLocals = hasSchemas
|
|
538
|
+
? '(local $sid i32) (local $kbits i64) (local $koff i32) (local $nkeys i32)'
|
|
539
|
+
: ''
|
|
540
|
+
|
|
541
|
+
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj f64) (param $key f64) (result f64)
|
|
542
|
+
(call $__dyn_get_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
543
|
+
|
|
544
|
+
ctx.core.stdlib['__dyn_get_t'] = `(func $__dyn_get_t (param $obj f64) (param $key f64) (param $type i32) (result f64)
|
|
545
|
+
(local $props f64) (local $bits i64) (local $off i32)
|
|
546
|
+
(local $poff i32) (local $pcap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
547
|
+
${objectSchemaLocals}
|
|
548
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $obj)))
|
|
549
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
550
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
551
|
+
(then
|
|
552
|
+
(block $done
|
|
553
|
+
(loop $follow
|
|
554
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
|
|
555
|
+
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
556
|
+
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
557
|
+
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
558
|
+
(br $follow)))))
|
|
559
|
+
(block $dynDone
|
|
560
|
+
(br_if $dynDone (f64.eq (global.get $__dyn_props) (f64.const 0)))
|
|
561
|
+
(local.set $props (call $__ihash_get_local (global.get $__dyn_props)
|
|
562
|
+
(f64.convert_i32_s (local.get $off))))
|
|
563
|
+
(br_if $dynDone (call $__is_nullish (local.get $props)))
|
|
564
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $props)))
|
|
565
|
+
(local.set $poff (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
566
|
+
(local.set $pcap (i32.load (i32.sub (local.get $poff) (i32.const 4))))
|
|
567
|
+
(local.set $h (call $__str_hash (local.get $key)))
|
|
568
|
+
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $pcap) (i32.const 1))))
|
|
569
|
+
(block $hdone (loop $hprobe
|
|
570
|
+
(local.set $slot (i32.add (local.get $poff) (i32.mul (local.get $idx) (i32.const ${MAP_ENTRY}))))
|
|
571
|
+
(br_if $dynDone (f64.eq (f64.load (local.get $slot)) (f64.const 0)))
|
|
572
|
+
(if (call $__str_eq (f64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))
|
|
573
|
+
(then (return (f64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
574
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $pcap) (i32.const 1))))
|
|
575
|
+
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
576
|
+
(br_if $hdone (i32.ge_s (local.get $tries) (local.get $pcap)))
|
|
577
|
+
(br $hprobe))))${objectSchemaArm}
|
|
578
|
+
(f64.const nan:${UNDEF_NAN}))`
|
|
579
|
+
|
|
580
|
+
ctx.core.stdlib['__dyn_get_or'] = `(func $__dyn_get_or (param $obj f64) (param $key f64) (param $fallback f64) (result f64)
|
|
581
|
+
(local $val f64)
|
|
582
|
+
(local.set $val (call $__dyn_get (local.get $obj) (local.get $key)))
|
|
583
|
+
(if (result f64)
|
|
584
|
+
(i64.eq (i64.reinterpret_f64 (local.get $val)) (i64.const ${UNDEF_NAN}))
|
|
585
|
+
(then (local.get $fallback))
|
|
586
|
+
(else (local.get $val))))`
|
|
587
|
+
|
|
588
|
+
ctx.core.stdlib['__dyn_get_expr'] = `(func $__dyn_get_expr (param $obj f64) (param $key f64) (result f64)
|
|
589
|
+
(call $__dyn_get_expr_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
590
|
+
|
|
591
|
+
ctx.core.stdlib['__dyn_get_expr_t'] = `(func $__dyn_get_expr_t (param $obj f64) (param $key f64) (param $t i32) (result f64)
|
|
592
|
+
(local $val f64)
|
|
593
|
+
(local.set $val (call $__dyn_get_t (local.get $obj) (local.get $key) (local.get $t)))
|
|
594
|
+
(if (result f64)
|
|
595
|
+
(i64.ne (i64.reinterpret_f64 (local.get $val)) (i64.const ${UNDEF_NAN}))
|
|
596
|
+
(then (local.get $val))
|
|
597
|
+
(else
|
|
598
|
+
(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
599
|
+
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
600
|
+
(else (f64.const nan:${NULL_NAN}))))))`
|
|
601
|
+
|
|
602
|
+
// Like __dyn_get_expr but also resolves EXTERNAL host objects via __ext_prop.
|
|
603
|
+
// Used at call sites where receiver type is statically unknown.
|
|
604
|
+
// When features.external is off, collapses to __dyn_get_expr shape (no EXTERNAL probe).
|
|
605
|
+
ctx.core.stdlib['__dyn_get_any'] = () => {
|
|
606
|
+
// Fast path: HASH check first, route directly to __hash_get_local. Hashes never carry
|
|
607
|
+
// dyn_props (those are for OBJECT/ARRAY attached props), so the original __dyn_get
|
|
608
|
+
// call was always wasted work on hashes — and JSON.parse / Map-style code is the
|
|
609
|
+
// dominant HASH consumer.
|
|
610
|
+
return `(func $__dyn_get_any (param $obj f64) (param $key f64) (result f64)
|
|
611
|
+
(call $__dyn_get_any_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
ctx.core.stdlib['__dyn_get_any_t'] = () => {
|
|
615
|
+
const extArm = ctx.features.external
|
|
616
|
+
? `(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.EXTERNAL}))
|
|
617
|
+
(then (call $__ext_prop (local.get $obj) (local.get $key)))
|
|
618
|
+
(else (f64.const nan:${NULL_NAN})))`
|
|
619
|
+
: `(f64.const nan:${NULL_NAN})`
|
|
620
|
+
return `(func $__dyn_get_any_t (param $obj f64) (param $key f64) (param $t i32) (result f64)
|
|
621
|
+
(local $val f64)
|
|
622
|
+
(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
623
|
+
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
624
|
+
(else
|
|
625
|
+
(local.set $val (call $__dyn_get_t (local.get $obj) (local.get $key) (local.get $t)))
|
|
626
|
+
(if (result f64)
|
|
627
|
+
(i64.ne (i64.reinterpret_f64 (local.get $val)) (i64.const ${UNDEF_NAN}))
|
|
628
|
+
(then (local.get $val))
|
|
629
|
+
(else ${extArm})))))`
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Hot for `node.loc = pos` patterns (e.g. watr's parser tags every nested level).
|
|
633
|
+
// Defer the root insert to the end and gate it on props-ptr change: most calls hit
|
|
634
|
+
// the no-grow case where the ptr is unchanged and the root slot already points to it.
|
|
635
|
+
// __ptr_offset inlined (forwarding-aware) — only ARRAY ever has forwarding.
|
|
636
|
+
ctx.core.stdlib['__dyn_set'] = `(func $__dyn_set (param $obj f64) (param $key f64) (param $val f64) (result f64)
|
|
637
|
+
(local $root f64) (local $props f64) (local $oldProps f64) (local $objKey f64)
|
|
638
|
+
(local $bits i64) (local $off i32)
|
|
639
|
+
(local.set $root (global.get $__dyn_props))
|
|
640
|
+
(if (f64.eq (local.get $root) (f64.const 0))
|
|
641
|
+
(then (local.set $root (call $__hash_new))))
|
|
642
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $obj)))
|
|
643
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
644
|
+
(if (i32.eq
|
|
645
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
646
|
+
(i32.const ${PTR.ARRAY}))
|
|
647
|
+
(then
|
|
648
|
+
(block $done
|
|
649
|
+
(loop $follow
|
|
650
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
|
|
651
|
+
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
652
|
+
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
653
|
+
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
654
|
+
(br $follow)))))
|
|
655
|
+
(local.set $objKey (f64.convert_i32_s (local.get $off)))
|
|
656
|
+
(local.set $oldProps (call $__ihash_get_local (local.get $root) (local.get $objKey)))
|
|
657
|
+
(local.set $props
|
|
658
|
+
(if (result f64) (call $__is_nullish (local.get $oldProps))
|
|
659
|
+
(then (call $__hash_new))
|
|
660
|
+
(else (local.get $oldProps))))
|
|
661
|
+
(local.set $props (call $__hash_set_local (local.get $props) (local.get $key) (local.get $val)))
|
|
662
|
+
(if (i64.ne (i64.reinterpret_f64 (local.get $props)) (i64.reinterpret_f64 (local.get $oldProps)))
|
|
663
|
+
(then
|
|
664
|
+
(local.set $root (call $__ihash_set_local (local.get $root) (local.get $objKey) (local.get $props)))
|
|
665
|
+
(global.set $__dyn_props (local.get $root))))
|
|
666
|
+
(local.get $val))`
|
|
667
|
+
|
|
668
|
+
ctx.core.stdlib['__dyn_move'] = `(func $__dyn_move (param $oldOff i32) (param $newOff i32)
|
|
669
|
+
(local $props f64) (local $root f64)
|
|
670
|
+
(if (f64.eq (global.get $__dyn_props) (f64.const 0)) (then (return)))
|
|
671
|
+
(local.set $props (call $__ihash_get_local (global.get $__dyn_props) (f64.convert_i32_s (local.get $oldOff))))
|
|
672
|
+
(if (call $__is_nullish (local.get $props)) (then (return)))
|
|
673
|
+
(local.set $root (call $__ihash_set_local (global.get $__dyn_props) (f64.convert_i32_s (local.get $newOff)) (local.get $props)))
|
|
674
|
+
(global.set $__dyn_props (local.get $root)))`
|
|
675
|
+
|
|
676
|
+
// Generated HASH probe functions
|
|
677
|
+
ctx.core.stdlib['__hash_set'] = () => genUpsertGrow('__hash_set', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, false, ctx.features.external)
|
|
678
|
+
ctx.core.stdlib['__hash_get'] = () => genLookup('__hash_get', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, true, ctx.features.external)
|
|
679
|
+
ctx.core.stdlib['__hash_has'] = () => genLookup('__hash_has', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, false, ctx.features.external)
|
|
680
|
+
|
|
681
|
+
// === `in` operator: key in obj → HASH key existence check ===
|
|
682
|
+
ctx.core.emit['in'] = (key, obj) => {
|
|
683
|
+
const objType = typeof obj === 'string' ? lookupValType(obj) : valTypeOf(obj)
|
|
684
|
+
|
|
685
|
+
if (Array.isArray(key) && key[0] === 'str') {
|
|
686
|
+
const prop = key[1]
|
|
687
|
+
if (prop === 'length' && (objType === VAL.ARRAY || objType === VAL.TYPED || objType === VAL.STRING || objType === VAL.SET || objType === VAL.MAP))
|
|
688
|
+
return typed(['i32.const', 1], 'i32')
|
|
689
|
+
|
|
690
|
+
const schemaIdx = typeof obj === 'string' ? ctx.schema.find(obj, prop) : ctx.schema.find(null, prop)
|
|
691
|
+
if (schemaIdx >= 0) return typed(['i32.const', 1], 'i32')
|
|
692
|
+
if (objType === VAL.OBJECT) return typed(['i32.const', 0], 'i32')
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const keyTmp = temp()
|
|
696
|
+
const objTmp = temp()
|
|
697
|
+
const idxTmp = tempI32('in_idx')
|
|
698
|
+
const typeTmp = tempI32('in_type')
|
|
699
|
+
const outTmp = tempI32('in_out')
|
|
700
|
+
|
|
701
|
+
const keyVal = ['local.get', `$${keyTmp}`]
|
|
702
|
+
const objVal = ['local.get', `$${objTmp}`]
|
|
703
|
+
const idxVal = ['local.get', `$${idxTmp}`]
|
|
704
|
+
const typeVal = ['local.get', `$${typeTmp}`]
|
|
705
|
+
const isStringKey = ['call', '$__is_str_key', keyVal]
|
|
706
|
+
const isStringLike = ['i32.or',
|
|
707
|
+
['i32.eq', typeVal, ['i32.const', PTR.STRING]],
|
|
708
|
+
['i32.eq', typeVal, ['i32.const', PTR.SSO]]]
|
|
709
|
+
const isArrayLike = ['i32.or',
|
|
710
|
+
['i32.eq', typeVal, ['i32.const', PTR.ARRAY]],
|
|
711
|
+
['i32.eq', typeVal, ['i32.const', PTR.TYPED]]]
|
|
712
|
+
const hasDynProps = ['i32.or',
|
|
713
|
+
['i32.eq', typeVal, ['i32.const', PTR.OBJECT]],
|
|
714
|
+
['i32.or',
|
|
715
|
+
['i32.or',
|
|
716
|
+
['i32.eq', typeVal, ['i32.const', PTR.ARRAY]],
|
|
717
|
+
['i32.eq', typeVal, ['i32.const', PTR.TYPED]]],
|
|
718
|
+
['i32.or',
|
|
719
|
+
['i32.or',
|
|
720
|
+
['i32.eq', typeVal, ['i32.const', PTR.STRING]],
|
|
721
|
+
['i32.eq', typeVal, ['i32.const', PTR.SSO]]],
|
|
722
|
+
['i32.or',
|
|
723
|
+
['i32.or',
|
|
724
|
+
['i32.eq', typeVal, ['i32.const', PTR.SET]],
|
|
725
|
+
['i32.eq', typeVal, ['i32.const', PTR.MAP]]],
|
|
726
|
+
['i32.eq', typeVal, ['i32.const', PTR.CLOSURE]]]]]]
|
|
727
|
+
|
|
728
|
+
inc('__ptr_type', '__len', '__str_byteLen', '__hash_has', '__is_str_key', '__to_str', '__dyn_get', '__is_nullish')
|
|
729
|
+
if (ctx.features.external) inc('__ext_has')
|
|
730
|
+
|
|
731
|
+
return typed(['block', ['result', 'i32'],
|
|
732
|
+
['local.set', `$${objTmp}`, asF64(emit(obj))],
|
|
733
|
+
['local.set', `$${keyTmp}`, asF64(emit(key))],
|
|
734
|
+
['local.set', `$${outTmp}`, ['i32.const', 0]],
|
|
735
|
+
['local.set', `$${typeTmp}`, ['call', '$__ptr_type', objVal]],
|
|
736
|
+
['local.set', `$${idxTmp}`, ['i32.trunc_sat_f64_s', keyVal]],
|
|
737
|
+
|
|
738
|
+
['if', ['i32.and',
|
|
739
|
+
['f64.eq', keyVal, keyVal],
|
|
740
|
+
['i32.and',
|
|
741
|
+
['f64.eq', ['f64.convert_i32_s', idxVal], keyVal],
|
|
742
|
+
['i32.ge_s', idxVal, ['i32.const', 0]]]],
|
|
743
|
+
['then',
|
|
744
|
+
['if', isStringLike,
|
|
745
|
+
['then', ['local.set', `$${outTmp}`, ['i32.lt_u', idxVal, ['call', '$__str_byteLen', objVal]]]]],
|
|
746
|
+
['if', isArrayLike,
|
|
747
|
+
['then', ['local.set', `$${outTmp}`, ['i32.lt_u', idxVal, ['call', '$__len', objVal]]]]]]],
|
|
748
|
+
|
|
749
|
+
['if', isStringKey,
|
|
750
|
+
['then',
|
|
751
|
+
['if', hasDynProps,
|
|
752
|
+
['then', ['local.set', `$${outTmp}`,
|
|
753
|
+
['i32.eqz', ['call', '$__is_nullish', ['call', '$__dyn_get', objVal, keyVal]]]]]]]],
|
|
754
|
+
|
|
755
|
+
['if', ['i32.eq', typeVal, ['i32.const', PTR.HASH]],
|
|
756
|
+
['then', ['local.set', `$${outTmp}`,
|
|
757
|
+
['if', ['result', 'i32'], isStringKey,
|
|
758
|
+
['then', ['call', '$__hash_has', objVal, keyVal]],
|
|
759
|
+
['else', ['call', '$__hash_has', objVal, ['call', '$__to_str', keyVal]]]]]]],
|
|
760
|
+
|
|
761
|
+
...(ctx.features.external ? [['if', ['i32.eq', typeVal, ['i32.const', PTR.EXTERNAL]],
|
|
762
|
+
['then', ['local.set', `$${outTmp}`, ['call', '$__ext_has', objVal, keyVal]]]]] : []),
|
|
763
|
+
|
|
764
|
+
['local.get', `$${outTmp}`]], 'i32')
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// === for...in on dynamic objects (HASH iteration) ===
|
|
768
|
+
|
|
769
|
+
// for-in: iterate HASH entries, binding key string to loop variable
|
|
770
|
+
ctx.core.emit['for-in'] = (varName, src, body) => {
|
|
771
|
+
const off = tempI32('ho'), cap = tempI32('hc')
|
|
772
|
+
const i = tempI32('hi'), slot = tempI32('hs')
|
|
773
|
+
if (!ctx.func.locals.has(varName)) ctx.func.locals.set(varName, 'f64')
|
|
774
|
+
const id = ctx.func.uniq++
|
|
775
|
+
const va = asF64(emit(src))
|
|
776
|
+
const bodyFlat = emitFlat(body)
|
|
777
|
+
return [
|
|
778
|
+
['local.set', `$${off}`, ['call', '$__ptr_offset', va]],
|
|
779
|
+
['local.set', `$${cap}`, ['call', '$__cap', va]],
|
|
780
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
781
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
782
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${cap}`]]],
|
|
783
|
+
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${off}`],
|
|
784
|
+
['i32.mul', ['local.get', `$${i}`], ['i32.const', MAP_ENTRY]]]],
|
|
785
|
+
['if', ['f64.ne', ['f64.load', ['local.get', `$${slot}`]], ['f64.const', 0]],
|
|
786
|
+
['then',
|
|
787
|
+
['local.set', `$${varName}`, ['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]],
|
|
788
|
+
...bodyFlat]],
|
|
789
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
790
|
+
['br', `$loop${id}`]]]
|
|
791
|
+
]
|
|
792
|
+
}
|
|
793
|
+
}
|