jz 0.1.0 → 0.2.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 +221 -62
- package/cli.js +34 -8
- package/index.js +133 -14
- package/module/array.js +399 -131
- package/module/collection.js +457 -212
- package/module/console.js +170 -87
- package/module/core.js +247 -143
- package/module/date.js +691 -0
- package/module/function.js +84 -23
- package/module/index.js +2 -1
- package/module/json.js +485 -138
- package/module/math.js +81 -40
- package/module/number.js +189 -83
- package/module/object.js +228 -46
- package/module/regex.js +34 -30
- package/module/schema.js +17 -18
- package/module/string.js +483 -227
- package/module/symbol.js +6 -4
- package/module/timer.js +90 -32
- package/module/typedarray.js +176 -44
- package/package.json +5 -10
- package/src/analyze.js +639 -124
- package/src/autoload.js +183 -0
- package/src/compile.js +448 -1083
- package/src/ctx.js +42 -12
- package/src/emit.js +646 -147
- package/src/host.js +273 -68
- package/src/ir.js +81 -31
- package/src/jzify.js +220 -36
- package/src/narrow.js +956 -0
- package/src/optimize.js +628 -42
- package/src/plan.js +1233 -0
- package/src/prepare.js +438 -256
- package/src/vectorize.js +1016 -0
- package/wasi.js +23 -4
package/module/collection.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* @module collection
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64, allocPtr, undefExpr } 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, LAYOUT } from '../src/ctx.js'
|
|
13
15
|
|
|
14
16
|
const SET_ENTRY = 16 // hash + key
|
|
15
17
|
const MAP_ENTRY = 24 // hash + key + value
|
|
@@ -26,8 +28,10 @@ const HASH_F64 = new Float64Array(HASH_BUF)
|
|
|
26
28
|
const HASH_U32 = new Uint32Array(HASH_BUF)
|
|
27
29
|
|
|
28
30
|
export function numHashLiteral(n) {
|
|
31
|
+
if (Object.is(n, 0) || Object.is(n, -0)) return 2
|
|
29
32
|
HASH_F64[0] = n
|
|
30
|
-
|
|
33
|
+
const h = (HASH_U32[0] ^ HASH_U32[1]) | 0
|
|
34
|
+
return h <= 1 ? (h + 2) | 0 : h
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
function numConstLiteral(expr) {
|
|
@@ -37,40 +41,39 @@ function numConstLiteral(expr) {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
// Equality expressions for probe templates
|
|
40
|
-
const
|
|
41
|
-
const strEq = '(call $__str_eq (
|
|
44
|
+
const sameValueZeroEq = '(call $__same_value_zero (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
45
|
+
const strEq = '(call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
42
46
|
|
|
43
47
|
/** Generate upsert (add/set) probe function. hasVal: store value at slot+16.
|
|
44
48
|
* hasExt: emit EXTERNAL fallthrough (call $__ext_set on non-matching type).
|
|
45
49
|
* Gated off → type mismatch just returns coll unchanged. */
|
|
46
50
|
function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt) {
|
|
47
|
-
const valParam = hasVal ? '(param $val
|
|
48
|
-
const storeVal = hasVal ? `\n (
|
|
51
|
+
const valParam = hasVal ? '(param $val i64) ' : ''
|
|
52
|
+
const storeVal = hasVal ? `\n (i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))` : ''
|
|
49
53
|
const onMatch = hasVal
|
|
50
|
-
? `(then\n (
|
|
54
|
+
? `(then\n (i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))\n (br $done))`
|
|
51
55
|
: `(then (br $done))`
|
|
52
56
|
|
|
53
57
|
const extBranch = hasVal
|
|
54
58
|
? '(then (call $__ext_set (local.get $coll) (local.get $key) (local.get $val)) drop)'
|
|
55
59
|
: '(then (nop))'
|
|
56
|
-
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $
|
|
60
|
+
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))`
|
|
57
61
|
const typeGuard = hasExt
|
|
58
62
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL})) ${extBranch}) (return (local.get $coll))))`
|
|
59
63
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (return (local.get $coll))))`
|
|
60
|
-
return `(func $${name} (param $coll
|
|
61
|
-
(local $
|
|
62
|
-
(local.set $bits (i64.reinterpret_f64 (local.get $coll)))
|
|
64
|
+
return `(func $${name} (param $coll i64) (param $key i64) ${valParam}(result i64)
|
|
65
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32)
|
|
63
66
|
${typeGuard}
|
|
64
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $
|
|
67
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
65
68
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
66
69
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
67
70
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
68
71
|
(block $done (loop $probe
|
|
69
72
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
70
|
-
(if (
|
|
73
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
71
74
|
(then
|
|
72
|
-
(
|
|
73
|
-
(
|
|
75
|
+
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
76
|
+
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${storeVal}
|
|
74
77
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
75
78
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
76
79
|
(br $done)))
|
|
@@ -85,34 +88,35 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
85
88
|
* wantValue=false: return i32 0/1 existence flag.
|
|
86
89
|
* hasExt: emit EXTERNAL fallthrough (delegate to __ext_prop/__ext_has). */
|
|
87
90
|
function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, hasExt) {
|
|
88
|
-
const rt = wantValue ? '
|
|
91
|
+
const rt = wantValue ? 'i64' : 'i32'
|
|
89
92
|
const onEmpty = wantValue
|
|
90
|
-
? `(return (
|
|
93
|
+
? `(return (i64.const ${NULL_NAN}))`
|
|
91
94
|
: '(return (i32.const 0))'
|
|
92
95
|
const onFound = wantValue
|
|
93
|
-
? '(return (
|
|
96
|
+
? '(return (i64.load (i32.add (local.get $slot) (i32.const 16))))'
|
|
94
97
|
: '(return (i32.const 1))'
|
|
95
98
|
const notFound = wantValue
|
|
96
|
-
? `(
|
|
99
|
+
? `(i64.const ${NULL_NAN})`
|
|
97
100
|
: '(i32.const 0)'
|
|
98
|
-
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $
|
|
101
|
+
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))`
|
|
99
102
|
const typeGuard = hasExt
|
|
100
103
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL}))
|
|
101
|
-
(then (return
|
|
104
|
+
(then (return ${wantValue
|
|
105
|
+
? '(call $__ext_prop (local.get $coll) (local.get $key))'
|
|
106
|
+
: '(call $__ext_has (local.get $coll) (local.get $key))'}))
|
|
102
107
|
(else ${onEmpty}))))`
|
|
103
108
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then ${onEmpty}))`
|
|
104
109
|
|
|
105
|
-
return `(func $${name} (param $coll
|
|
106
|
-
(local $
|
|
107
|
-
(local.set $bits (i64.reinterpret_f64 (local.get $coll)))
|
|
110
|
+
return `(func $${name} (param $coll i64) (param $key i64) (result ${rt})
|
|
111
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
108
112
|
${typeGuard}
|
|
109
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $
|
|
113
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
110
114
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
111
115
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
112
116
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
113
117
|
(block $done (loop $probe
|
|
114
118
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
115
|
-
(if (
|
|
119
|
+
(if (i64.eqz (i64.load (local.get $slot))) (then ${onEmpty}))
|
|
116
120
|
(if ${eqExpr} (then ${onFound}))
|
|
117
121
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
118
122
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
@@ -123,7 +127,7 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
123
127
|
|
|
124
128
|
/** Generate delete probe function. Zero out entry on match. */
|
|
125
129
|
function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
126
|
-
return `(func $${name} (param $coll
|
|
130
|
+
return `(func $${name} (param $coll i64) (param $key i64) (result i32)
|
|
127
131
|
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
128
132
|
(if (i32.ne (call $__ptr_type (local.get $coll)) (i32.const ${expectedType})) (then (return (i32.const 0))))
|
|
129
133
|
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
@@ -132,11 +136,11 @@ function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
|
132
136
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
133
137
|
(block $done (loop $probe
|
|
134
138
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
135
|
-
(if (
|
|
139
|
+
(if (i64.eqz (i64.load (local.get $slot))) (then (return (i32.const 0))))
|
|
136
140
|
(if ${eqExpr}
|
|
137
141
|
(then
|
|
138
|
-
(
|
|
139
|
-
(
|
|
142
|
+
(i64.store (local.get $slot) (i64.const 0))
|
|
143
|
+
(i64.store (i32.add (local.get $slot) (i32.const 8)) (i64.const 0))
|
|
140
144
|
(return (i32.const 1))))
|
|
141
145
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
142
146
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
@@ -163,7 +167,7 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
163
167
|
(then
|
|
164
168
|
${nonHashFallback}
|
|
165
169
|
(return (local.get $obj))))`
|
|
166
|
-
return `(func $${name} (param $obj
|
|
170
|
+
return `(func $${name} (param $obj i64) (param $key i64) (param $val i64) (result i64)
|
|
167
171
|
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32)
|
|
168
172
|
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
169
173
|
(local $oldslot i32) (local $newidx i32) (local $newslot i32)
|
|
@@ -180,41 +184,41 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
180
184
|
(block $rd (loop $rl
|
|
181
185
|
(br_if $rd (i32.ge_s (local.get $i) (local.get $cap)))
|
|
182
186
|
(local.set $oldslot (i32.add (local.get $off) (i32.mul (local.get $i) (i32.const ${entrySize}))))
|
|
183
|
-
(if (
|
|
187
|
+
(if (i64.ne (i64.load (local.get $oldslot)) (i64.const 0))
|
|
184
188
|
(then
|
|
185
|
-
(local.set $h (call ${hashFn} (
|
|
189
|
+
(local.set $h (call ${hashFn} (i64.load (i32.add (local.get $oldslot) (i32.const 8)))))
|
|
186
190
|
(local.set $newidx (i32.and (local.get $h) (i32.sub (local.get $newcap) (i32.const 1))))
|
|
187
191
|
(block $ins (loop $probe2
|
|
188
192
|
(local.set $newslot (i32.add (local.get $newptr) (i32.mul (local.get $newidx) (i32.const ${entrySize}))))
|
|
189
|
-
(br_if $ins (
|
|
193
|
+
(br_if $ins (i64.eqz (i64.load (local.get $newslot))))
|
|
190
194
|
(local.set $newidx (i32.and (i32.add (local.get $newidx) (i32.const 1)) (i32.sub (local.get $newcap) (i32.const 1))))
|
|
191
195
|
(br $probe2)))
|
|
192
|
-
(
|
|
193
|
-
(
|
|
194
|
-
(
|
|
196
|
+
(i64.store (local.get $newslot) (i64.load (local.get $oldslot)))
|
|
197
|
+
(i64.store (i32.add (local.get $newslot) (i32.const 8)) (i64.load (i32.add (local.get $oldslot) (i32.const 8))))
|
|
198
|
+
(i64.store (i32.add (local.get $newslot) (i32.const 16)) (i64.load (i32.add (local.get $oldslot) (i32.const 16))))
|
|
195
199
|
(i32.store (i32.sub (local.get $newptr) (i32.const 8))
|
|
196
200
|
(i32.add (i32.load (i32.sub (local.get $newptr) (i32.const 8))) (i32.const 1)))))
|
|
197
201
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
198
202
|
(br $rl)))
|
|
199
203
|
(local.set $off (local.get $newptr))
|
|
200
204
|
(local.set $cap (local.get $newcap))
|
|
201
|
-
(local.set $obj (call $__mkptr (i32.const ${typeConst}) (i32.const 0) (local.get $newptr)))))
|
|
205
|
+
(local.set $obj (i64.reinterpret_f64 (call $__mkptr (i32.const ${typeConst}) (i32.const 0) (local.get $newptr))))))
|
|
202
206
|
;; Insert/update
|
|
203
207
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
204
208
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
205
209
|
(block $done (loop $probe
|
|
206
210
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
207
|
-
(if (
|
|
211
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
208
212
|
(then
|
|
209
|
-
(
|
|
210
|
-
(
|
|
211
|
-
(
|
|
213
|
+
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
214
|
+
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))
|
|
215
|
+
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
212
216
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
213
217
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
214
218
|
(br $done)))
|
|
215
219
|
(if ${eqExpr}
|
|
216
220
|
(then
|
|
217
|
-
(
|
|
221
|
+
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
218
222
|
(br $done)))
|
|
219
223
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
220
224
|
(br $probe)))
|
|
@@ -222,84 +226,81 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
222
226
|
}
|
|
223
227
|
|
|
224
228
|
function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing = UNDEF_NAN) {
|
|
225
|
-
return `(func $${name} (param $coll
|
|
226
|
-
(local $
|
|
227
|
-
(local.set $bits (i64.reinterpret_f64 (local.get $coll)))
|
|
229
|
+
return `(func $${name} (param $coll i64) (param $key i64) (result i64)
|
|
230
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
228
231
|
(if (i32.ne
|
|
229
|
-
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $
|
|
232
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
230
233
|
(i32.const ${expectedType}))
|
|
231
|
-
(then (return (
|
|
232
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $
|
|
234
|
+
(then (return (i64.const ${missing}))))
|
|
235
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
233
236
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
234
237
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
235
238
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
236
239
|
(block $done (loop $probe
|
|
237
240
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
238
|
-
(if (
|
|
239
|
-
(then (return (
|
|
241
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
242
|
+
(then (return (i64.const ${missing}))))
|
|
240
243
|
(if ${eqExpr}
|
|
241
|
-
(then (return (
|
|
244
|
+
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
242
245
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
243
246
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
244
247
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
245
248
|
(br $probe)))
|
|
246
|
-
(
|
|
249
|
+
(i64.const ${missing}))`
|
|
247
250
|
}
|
|
248
251
|
|
|
249
252
|
function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing = UNDEF_NAN, hasExt = false) {
|
|
250
|
-
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $
|
|
253
|
+
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))`
|
|
251
254
|
const typeGuard = hasExt
|
|
252
255
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
253
256
|
(then
|
|
254
257
|
(if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL}))
|
|
255
258
|
(then (return (call $__ext_prop (local.get $coll) (local.get $key))))
|
|
256
|
-
(else (return (
|
|
259
|
+
(else (return (i64.const ${missing}))))))`
|
|
257
260
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
258
|
-
(then (return (
|
|
259
|
-
return `(func $${name} (param $coll
|
|
260
|
-
(local $
|
|
261
|
-
(local.set $bits (i64.reinterpret_f64 (local.get $coll)))
|
|
261
|
+
(then (return (i64.const ${missing}))))`
|
|
262
|
+
return `(func $${name} (param $coll i64) (param $key i64) (param $h i32) (result i64)
|
|
263
|
+
(local $off i32) (local $cap i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
262
264
|
${typeGuard}
|
|
263
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $
|
|
265
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
264
266
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
265
267
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
266
268
|
(block $done (loop $probe
|
|
267
269
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
268
|
-
(if (
|
|
269
|
-
(then (return (
|
|
270
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
271
|
+
(then (return (i64.const ${missing}))))
|
|
270
272
|
(if ${eqExpr}
|
|
271
|
-
(then (return (
|
|
273
|
+
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
272
274
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
273
275
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
274
276
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
275
277
|
(br $probe)))
|
|
276
|
-
(
|
|
278
|
+
(i64.const ${missing}))`
|
|
277
279
|
}
|
|
278
280
|
|
|
279
281
|
function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
280
|
-
return `(func $${name} (param $obj
|
|
281
|
-
(local $
|
|
282
|
-
(local.set $bits (i64.reinterpret_f64 (local.get $obj)))
|
|
282
|
+
return `(func $${name} (param $obj i64) (param $key i64) (param $h i32) (param $val i64) (result i64)
|
|
283
|
+
(local $off i32) (local $cap i32) (local $idx i32) (local $slot i32)
|
|
283
284
|
(if (i32.ne
|
|
284
|
-
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $
|
|
285
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
285
286
|
(i32.const ${expectedType}))
|
|
286
287
|
(then (return (local.get $obj))))
|
|
287
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $
|
|
288
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
288
289
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
289
290
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
290
291
|
(block $done (loop $probe
|
|
291
292
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
292
|
-
(if (
|
|
293
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
293
294
|
(then
|
|
294
|
-
(
|
|
295
|
-
(
|
|
296
|
-
(
|
|
295
|
+
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
296
|
+
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))
|
|
297
|
+
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
297
298
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
298
299
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
299
300
|
(br $done)))
|
|
300
301
|
(if ${eqExpr}
|
|
301
302
|
(then
|
|
302
|
-
(
|
|
303
|
+
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
303
304
|
(br $done)))
|
|
304
305
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
305
306
|
(br $probe)))
|
|
@@ -312,12 +313,16 @@ export default (ctx) => {
|
|
|
312
313
|
// Evaluated lazily at resolveIncludes() time — after emission has finalized ctx.features.
|
|
313
314
|
const ifExt = (name) => () => ctx.features.external ? [name] : []
|
|
314
315
|
Object.assign(ctx.core.stdlibDeps, {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
__same_value_zero: ['__str_eq'],
|
|
317
|
+
__map_hash: ['__hash', '__str_hash'],
|
|
318
|
+
__set_add: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ext_set'] : ['__map_hash', '__same_value_zero'],
|
|
319
|
+
__set_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ext_has'] : ['__map_hash', '__same_value_zero'],
|
|
320
|
+
__set_delete: ['__map_hash', '__same_value_zero'],
|
|
321
|
+
__map_set: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ext_set'] : ['__map_hash', '__same_value_zero'],
|
|
318
322
|
__map_get: () => ctx.features.external ? ['__ext_prop', '__map_set'] : ['__map_set'],
|
|
319
|
-
__map_get_h: () => ctx.features.external ? ['__ext_prop'] : [],
|
|
320
|
-
|
|
323
|
+
__map_get_h: () => ctx.features.external ? ['__ext_prop', '__same_value_zero'] : ['__same_value_zero'],
|
|
324
|
+
__map_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ext_has'] : ['__map_hash', '__same_value_zero'],
|
|
325
|
+
__map_delete: ['__map_hash', '__same_value_zero'],
|
|
321
326
|
__hash_set: () => ctx.features.external
|
|
322
327
|
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_set', '__dyn_set']
|
|
323
328
|
: ['__str_hash', '__str_eq', '__ptr_type', '__dyn_set'],
|
|
@@ -328,12 +333,13 @@ export default (ctx) => {
|
|
|
328
333
|
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_has']
|
|
329
334
|
: ['__str_hash', '__str_eq', '__ptr_type'],
|
|
330
335
|
__hash_new: ['__alloc_hdr'],
|
|
336
|
+
__hash_new_small: ['__alloc_hdr', '__mkptr'],
|
|
331
337
|
__hash_get_local: ['__str_hash', '__str_eq'],
|
|
332
338
|
__hash_get_local_h: ['__str_eq'],
|
|
333
339
|
__hash_set_local_h: ['__str_eq'],
|
|
334
340
|
__hash_set_local: ['__str_hash', '__str_eq'],
|
|
335
|
-
__ihash_get_local: ['
|
|
336
|
-
__ihash_set_local: ['
|
|
341
|
+
__ihash_get_local: ['__map_hash'],
|
|
342
|
+
__ihash_set_local: ['__map_hash', '__alloc_hdr', '__mkptr'],
|
|
337
343
|
__dyn_get_t: ['__ihash_get_local', '__str_hash', '__str_eq', '__is_nullish'],
|
|
338
344
|
__dyn_get: ['__dyn_get_t', '__ptr_type'],
|
|
339
345
|
__dyn_get_expr_t: ['__dyn_get_t', '__hash_get_local'],
|
|
@@ -343,7 +349,7 @@ export default (ctx) => {
|
|
|
343
349
|
? ['__dyn_get_t', '__hash_get_local', '__ext_prop']
|
|
344
350
|
: ['__dyn_get_t', '__hash_get_local'],
|
|
345
351
|
__dyn_get_or: ['__dyn_get'],
|
|
346
|
-
__dyn_set: ['__hash_new', '__ihash_get_local', '__ihash_set_local', '__hash_set_local', '__ptr_offset', '__is_nullish'],
|
|
352
|
+
__dyn_set: ['__hash_new', '__hash_new_small', '__ihash_get_local', '__ihash_set_local', '__hash_set_local', '__ptr_offset', '__is_nullish', '__str_eq'],
|
|
347
353
|
__dyn_move: ['__ihash_get_local', '__ihash_set_local', '__is_nullish'],
|
|
348
354
|
})
|
|
349
355
|
|
|
@@ -351,6 +357,16 @@ export default (ctx) => {
|
|
|
351
357
|
|
|
352
358
|
if (!ctx.scope.globals.has('__dyn_props'))
|
|
353
359
|
ctx.scope.globals.set('__dyn_props', '(global $__dyn_props (mut f64) (f64.const 0))')
|
|
360
|
+
// 1-slot inline cache for the global __dyn_props lookup. Hot path for
|
|
361
|
+
// metacircular workloads (watr WAT parser): ~96% of execution sits in
|
|
362
|
+
// __dyn_get_t / __ihash_get_local. Caches last-seen (off → propsPtr) at
|
|
363
|
+
// the top of __dyn_get_t; invalidated by __dyn_set when the same off's
|
|
364
|
+
// propsPtr is replaced (rehash on grow). Sentinel cache_off = -1 cannot
|
|
365
|
+
// collide with a real memory offset (always non-negative i32).
|
|
366
|
+
if (!ctx.scope.globals.has('__dyn_get_cache_off'))
|
|
367
|
+
ctx.scope.globals.set('__dyn_get_cache_off', '(global $__dyn_get_cache_off (mut i32) (i32.const -1))')
|
|
368
|
+
if (!ctx.scope.globals.has('__dyn_get_cache_props'))
|
|
369
|
+
ctx.scope.globals.set('__dyn_get_cache_props', '(global $__dyn_get_cache_props (mut f64) (f64.const 0))')
|
|
354
370
|
// Schema name table for __dyn_get's OBJECT-schema fallback (polymorphic-receiver
|
|
355
371
|
// `.prop` access). Same declaration as json.js — defined here too so collection
|
|
356
372
|
// doesn't transitively require json. compile.js's schemaInit populates it when
|
|
@@ -358,17 +374,61 @@ export default (ctx) => {
|
|
|
358
374
|
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
359
375
|
ctx.scope.globals.set('__schema_tbl', '(global $__schema_tbl (mut i32) (i32.const 0))')
|
|
360
376
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
377
|
+
// __ext_* imports carry NaN-boxed pointers across the env boundary as i64
|
|
378
|
+
// (not f64) to dodge V8's f64 NaN canonicalization at the wasm↔JS edge —
|
|
379
|
+
// same hazard as env.print / env.setTimeout (see module/console.js header).
|
|
380
|
+
// i32 returns (has/set) and arg shapes stay; only boxed-pointer carriers move.
|
|
381
|
+
ctx.core.stdlib['__ext_prop'] = '(import "env" "__ext_prop" (func $__ext_prop (param i64 i64) (result i64)))'
|
|
382
|
+
ctx.core.stdlib['__ext_has'] = '(import "env" "__ext_has" (func $__ext_has (param i64 i64) (result i32)))'
|
|
383
|
+
ctx.core.stdlib['__ext_set'] = '(import "env" "__ext_set" (func $__ext_set (param i64 i64 i64) (result i32)))'
|
|
384
|
+
ctx.core.stdlib['__ext_call'] = '(import "env" "__ext_call" (func $__ext_call (param i64 i64 i64) (result i64)))'
|
|
365
385
|
// Hash function: simple f64 → i32 hash
|
|
366
|
-
ctx.core.stdlib['__hash'] = `(func $__hash (param $v
|
|
386
|
+
ctx.core.stdlib['__hash'] = `(func $__hash (param $v i64) (result i32)
|
|
367
387
|
(i32.wrap_i64 (i64.xor
|
|
368
|
-
(
|
|
369
|
-
(i64.shr_u (
|
|
388
|
+
(local.get $v)
|
|
389
|
+
(i64.shr_u (local.get $v) (i64.const 32)))))`
|
|
370
390
|
inc('__hash')
|
|
371
391
|
|
|
392
|
+
ctx.core.stdlib['__same_value_zero'] = `(func $__same_value_zero (param $a i64) (param $b i64) (result i32)
|
|
393
|
+
(local $fa f64) (local $fb f64) (local $ta i32) (local $tb i32)
|
|
394
|
+
(if (result i32) (i64.eq (local.get $a) (local.get $b))
|
|
395
|
+
(then (i32.const 1))
|
|
396
|
+
(else
|
|
397
|
+
(local.set $fa (f64.reinterpret_i64 (local.get $a)))
|
|
398
|
+
(local.set $fb (f64.reinterpret_i64 (local.get $b)))
|
|
399
|
+
(if (result i32)
|
|
400
|
+
(i32.and
|
|
401
|
+
(f64.eq (local.get $fa) (local.get $fa))
|
|
402
|
+
(f64.eq (local.get $fb) (local.get $fb)))
|
|
403
|
+
(then (f64.eq (local.get $fa) (local.get $fb)))
|
|
404
|
+
(else
|
|
405
|
+
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
406
|
+
(local.set $tb (i32.wrap_i64 (i64.and (i64.shr_u (local.get $b) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
407
|
+
(if (result i32)
|
|
408
|
+
(i32.and
|
|
409
|
+
(i32.eq (local.get $ta) (i32.const ${PTR.STRING}))
|
|
410
|
+
(i32.eq (local.get $tb) (i32.const ${PTR.STRING})))
|
|
411
|
+
(then (call $__str_eq (local.get $a) (local.get $b)))
|
|
412
|
+
(else (i32.const 0))))))))`
|
|
413
|
+
|
|
414
|
+
ctx.core.stdlib['__map_hash'] = `(func $__map_hash (param $v i64) (result i32)
|
|
415
|
+
(local $f f64) (local $t i32) (local $h i32)
|
|
416
|
+
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
417
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $v) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
418
|
+
;; NaN-boxed strings carry the tag inside a NaN payload. Regular numbers
|
|
419
|
+
;; (e.g. f64.convert_i32_s offsets used as __ihash keys) can alias mantissa
|
|
420
|
+
;; bits onto the type slot — gate the str-hash dispatch on actual NaN.
|
|
421
|
+
(if (i32.and (f64.ne (local.get $f) (local.get $f))
|
|
422
|
+
(i32.eq (local.get $t) (i32.const ${PTR.STRING})))
|
|
423
|
+
(then (return (call $__str_hash (local.get $v)))))
|
|
424
|
+
(if (f64.eq (local.get $f) (f64.const 0)) (then (return (i32.const 2))))
|
|
425
|
+
(if (i32.and (i32.eq (local.get $t) (i32.const 0)) (f64.ne (local.get $f) (local.get $f)))
|
|
426
|
+
(then (return (i32.const 3))))
|
|
427
|
+
(local.set $h (call $__hash (local.get $v)))
|
|
428
|
+
(if (result i32) (i32.le_s (local.get $h) (i32.const 1))
|
|
429
|
+
(then (i32.add (local.get $h) (i32.const 2)))
|
|
430
|
+
(else (local.get $h))))`
|
|
431
|
+
|
|
372
432
|
// __map_new() → f64 — allocate empty Map (for JSON.parse, runtime creation)
|
|
373
433
|
ctx.core.stdlib['__map_new'] = `(func $__map_new (result f64)
|
|
374
434
|
(call $__mkptr (i32.const ${PTR.MAP}) (i32.const 0)
|
|
@@ -384,27 +444,27 @@ export default (ctx) => {
|
|
|
384
444
|
|
|
385
445
|
ctx.core.emit['.add'] = (setExpr, val) => {
|
|
386
446
|
inc('__set_add')
|
|
387
|
-
return typed(['call', '$__set_add',
|
|
447
|
+
return typed(['f64.reinterpret_i64', ['call', '$__set_add', asI64(emit(setExpr)), asI64(emit(val))]], 'f64')
|
|
388
448
|
}
|
|
389
449
|
|
|
390
450
|
ctx.core.emit['.has'] = (setExpr, val) => {
|
|
391
451
|
inc('__set_has')
|
|
392
|
-
return typed(['f64.convert_i32_s', ['call', '$__set_has',
|
|
452
|
+
return typed(['f64.convert_i32_s', ['call', '$__set_has', asI64(emit(setExpr)), asI64(emit(val))]], 'f64')
|
|
393
453
|
}
|
|
394
454
|
|
|
395
455
|
ctx.core.emit['.delete'] = (setExpr, val) => {
|
|
396
456
|
inc('__set_delete')
|
|
397
|
-
return typed(['f64.convert_i32_s', ['call', '$__set_delete',
|
|
457
|
+
return typed(['f64.convert_i32_s', ['call', '$__set_delete', asI64(emit(setExpr)), asI64(emit(val))]], 'f64')
|
|
398
458
|
}
|
|
399
459
|
|
|
400
460
|
ctx.core.emit['.size'] = (expr) => {
|
|
401
|
-
return typed(['f64.convert_i32_s', ['call', '$__len', asF64(emit(expr))]], 'f64')
|
|
461
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', ['i64.reinterpret_f64', asF64(emit(expr))]]], 'f64')
|
|
402
462
|
}
|
|
403
463
|
|
|
404
464
|
// Generated Set probe functions
|
|
405
|
-
ctx.core.stdlib['__set_add'] = () => genUpsert('__set_add', SET_ENTRY, '$
|
|
406
|
-
ctx.core.stdlib['__set_has'] = () => genLookup('__set_has', SET_ENTRY, '$
|
|
407
|
-
ctx.core.stdlib['__set_delete'] = genDelete('__set_delete', SET_ENTRY, '$
|
|
465
|
+
ctx.core.stdlib['__set_add'] = () => genUpsert('__set_add', SET_ENTRY, '$__map_hash', sameValueZeroEq, PTR.SET, false, ctx.features.external)
|
|
466
|
+
ctx.core.stdlib['__set_has'] = () => genLookup('__set_has', SET_ENTRY, '$__map_hash', sameValueZeroEq, PTR.SET, false, ctx.features.external)
|
|
467
|
+
ctx.core.stdlib['__set_delete'] = genDelete('__set_delete', SET_ENTRY, '$__map_hash', sameValueZeroEq, PTR.SET)
|
|
408
468
|
|
|
409
469
|
// === Map ===
|
|
410
470
|
|
|
@@ -416,7 +476,8 @@ export default (ctx) => {
|
|
|
416
476
|
|
|
417
477
|
ctx.core.emit['.set'] = (mapExpr, key, val) => {
|
|
418
478
|
inc('__map_set')
|
|
419
|
-
|
|
479
|
+
const value = val === undefined ? asI64(undefExpr()) : asI64(emit(val))
|
|
480
|
+
return typed(['f64.reinterpret_i64', ['call', '$__map_set', asI64(emit(mapExpr)), asI64(emit(key)), value]], 'f64')
|
|
420
481
|
}
|
|
421
482
|
ctx.core.emit[`.${VAL.MAP}:set`] = ctx.core.emit['.set']
|
|
422
483
|
|
|
@@ -424,19 +485,31 @@ export default (ctx) => {
|
|
|
424
485
|
const constKey = numConstLiteral(key)
|
|
425
486
|
if (constKey != null) {
|
|
426
487
|
inc('__map_get_h')
|
|
427
|
-
return typed(['call', '$__map_get_h',
|
|
488
|
+
return typed(['f64.reinterpret_i64', ['call', '$__map_get_h', asI64(emit(mapExpr)), asI64(emit(key)), ['i32.const', numHashLiteral(constKey)]]], 'f64')
|
|
428
489
|
}
|
|
429
490
|
inc('__map_get')
|
|
430
|
-
return typed(['call', '$__map_get',
|
|
491
|
+
return typed(['f64.reinterpret_i64', ['call', '$__map_get', asI64(emit(mapExpr)), asI64(emit(key))]], 'f64')
|
|
431
492
|
}
|
|
432
493
|
|
|
433
494
|
ctx.core.emit['.get'] = emitMapGet
|
|
434
495
|
ctx.core.emit[`.${VAL.MAP}:get`] = emitMapGet
|
|
435
496
|
|
|
497
|
+
ctx.core.emit[`.${VAL.MAP}:has`] = (mapExpr, key) => {
|
|
498
|
+
inc('__map_has')
|
|
499
|
+
return typed(['f64.convert_i32_s', ['call', '$__map_has', asI64(emit(mapExpr)), asI64(emit(key))]], 'f64')
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
ctx.core.emit[`.${VAL.MAP}:delete`] = (mapExpr, key) => {
|
|
503
|
+
inc('__map_delete')
|
|
504
|
+
return typed(['f64.convert_i32_s', ['call', '$__map_delete', asI64(emit(mapExpr)), asI64(emit(key))]], 'f64')
|
|
505
|
+
}
|
|
506
|
+
|
|
436
507
|
// Generated Map probe functions
|
|
437
|
-
ctx.core.stdlib['__map_set'] = () => genUpsert('__map_set', MAP_ENTRY, '$
|
|
438
|
-
ctx.core.stdlib['__map_get'] = () => genLookup('__map_get', MAP_ENTRY, '$
|
|
439
|
-
ctx.core.stdlib['__map_get_h'] = () => genLookupStrictPrehashed('__map_get_h', MAP_ENTRY,
|
|
508
|
+
ctx.core.stdlib['__map_set'] = () => genUpsert('__map_set', MAP_ENTRY, '$__map_hash', sameValueZeroEq, PTR.MAP, true, ctx.features.external)
|
|
509
|
+
ctx.core.stdlib['__map_get'] = () => genLookup('__map_get', MAP_ENTRY, '$__map_hash', sameValueZeroEq, PTR.MAP, true, ctx.features.external)
|
|
510
|
+
ctx.core.stdlib['__map_get_h'] = () => genLookupStrictPrehashed('__map_get_h', MAP_ENTRY, sameValueZeroEq, PTR.MAP, NULL_NAN, ctx.features.external)
|
|
511
|
+
ctx.core.stdlib['__map_has'] = () => genLookup('__map_has', MAP_ENTRY, '$__map_hash', sameValueZeroEq, PTR.MAP, false, ctx.features.external)
|
|
512
|
+
ctx.core.stdlib['__map_delete'] = genDelete('__map_delete', MAP_ENTRY, '$__map_hash', sameValueZeroEq, PTR.MAP)
|
|
440
513
|
|
|
441
514
|
// === HASH — dynamic string-keyed object (type=7) ===
|
|
442
515
|
|
|
@@ -444,15 +517,15 @@ export default (ctx) => {
|
|
|
444
517
|
// FNV-1a. ~95M calls in watr self-host. Inline char-fetch: hoist type/offset out of the
|
|
445
518
|
// byte loop so SSO branch uses dword shifts and STRING branch uses raw load8_u — neither
|
|
446
519
|
// calls anything per byte (vs original 1×__char_at → __ptr_type + __ptr_offset per byte).
|
|
447
|
-
ctx.core.stdlib['__str_hash'] = `(func $__str_hash (param $s
|
|
448
|
-
(local $h i32) (local $len i32) (local $lenA i32) (local $i i32) (local $t i32) (local $off i32) (local $
|
|
520
|
+
ctx.core.stdlib['__str_hash'] = `(func $__str_hash (param $s i64) (result i32)
|
|
521
|
+
(local $h i32) (local $len i32) (local $lenA i32) (local $i i32) (local $t i32) (local $off i32) (local $aux i32) (local $w i32)
|
|
449
522
|
(local.set $h (i32.const 0x811c9dc5))
|
|
450
|
-
(local.set $
|
|
451
|
-
(local.set $
|
|
452
|
-
(local.set $
|
|
453
|
-
(if (i32.eq (local.get $t) (i32.const ${PTR.
|
|
523
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $s) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
524
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $s) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
525
|
+
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $s) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
526
|
+
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.STRING})) (i32.shr_u (local.get $aux) (i32.const 14)))
|
|
454
527
|
(then
|
|
455
|
-
(local.set $len (i32.
|
|
528
|
+
(local.set $len (i32.and (local.get $aux) (i32.const 7)))
|
|
456
529
|
(block $ds (loop $ls
|
|
457
530
|
(br_if $ds (i32.ge_s (local.get $i) (local.get $len)))
|
|
458
531
|
(local.set $h (i32.mul
|
|
@@ -491,14 +564,21 @@ export default (ctx) => {
|
|
|
491
564
|
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
492
565
|
(call $__alloc_hdr (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))`
|
|
493
566
|
|
|
567
|
+
// Small initial capacity for propsPtr-style hashes (per-object dyn props).
|
|
568
|
+
// Most receivers in real code carry 0-2 dyn props; paying 8-slot up-front
|
|
569
|
+
// is wasted memory + probe-loop cache pressure. Grows to 4/8/... on demand.
|
|
570
|
+
ctx.core.stdlib['__hash_new_small'] = `(func $__hash_new_small (result f64)
|
|
571
|
+
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
572
|
+
(call $__alloc_hdr (i32.const 0) (i32.const 2) (i32.const ${MAP_ENTRY}))))`
|
|
573
|
+
|
|
494
574
|
ctx.core.stdlib['__hash_get_local'] = genLookupStrict('__hash_get_local', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH)
|
|
495
575
|
ctx.core.stdlib['__hash_get_local_h'] = genLookupStrictPrehashed('__hash_get_local_h', MAP_ENTRY, strEq, PTR.HASH)
|
|
496
576
|
ctx.core.stdlib['__hash_set_local_h'] = genUpsertStrictPrehashed('__hash_set_local_h', MAP_ENTRY, strEq, PTR.HASH)
|
|
497
577
|
ctx.core.stdlib['__hash_set_local'] = genUpsertGrow('__hash_set_local', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, true)
|
|
498
|
-
// Outer __dyn_props hash: keyed by object offset (i32 as f64), value is per-object props hash.
|
|
499
|
-
// Uses bit-hash +
|
|
500
|
-
ctx.core.stdlib['__ihash_get_local'] = genLookupStrict('__ihash_get_local', MAP_ENTRY, '$
|
|
501
|
-
ctx.core.stdlib['__ihash_set_local'] = genUpsertGrow('__ihash_set_local', MAP_ENTRY, '$
|
|
578
|
+
// Outer __dyn_props hash: keyed by object offset (i32 as f64 bits), value is per-object props hash.
|
|
579
|
+
// Uses bit-hash + i64.eq — no string allocation for the unique integer key.
|
|
580
|
+
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)
|
|
581
|
+
ctx.core.stdlib['__ihash_set_local'] = genUpsertGrow('__ihash_set_local', MAP_ENTRY, '$__map_hash', '(i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))', PTR.HASH, true)
|
|
502
582
|
|
|
503
583
|
// Inline __ptr_offset (forwarding-aware) and __hash_get_local body — dyn_get is the
|
|
504
584
|
// single hottest stdlib symbol in watr self-host (~95M calls). props returned by
|
|
@@ -511,91 +591,184 @@ export default (ctx) => {
|
|
|
511
591
|
// time but lives at runtime in the NaN-box aux bits. Gated on schema name table
|
|
512
592
|
// presence (lifted in compile.js whenever __dyn_get is included). Static-shape
|
|
513
593
|
// monomorphic OBJECTs hit the compile-time slot read path and never reach here.
|
|
514
|
-
|
|
515
|
-
|
|
594
|
+
// Wrapped in a factory: `ctx.schema.list.length` is observed at template
|
|
595
|
+
// expansion time, after all schemas have been registered. Setting the
|
|
596
|
+
// template at module-init froze hasSchemas to false and dropped the arm
|
|
597
|
+
// for any schema registered later in the compile (the common case for
|
|
598
|
+
// anonymous-literal arguments crossing call boundaries).
|
|
599
|
+
// Schema-arm key compare uses i64.eq instead of __str_eq: schema keys and
|
|
600
|
+
// the call-site key both come from the interned string pool (same NaN-box
|
|
601
|
+
// bits for identical literals), so bit-equality is correct and skips a
|
|
602
|
+
// per-iter function call. Real-world strings sharing prefix bytes are not
|
|
603
|
+
// a concern here — keys are static literals from the source program.
|
|
604
|
+
// Schema-arm key compare: i64.eq first for the static-shape case (compile-time
|
|
605
|
+
// schemas hold pool-interned keys with identical NaN-box bits as call-site
|
|
606
|
+
// literals — single bit-eq decides). Falls back to __str_eq when bits differ
|
|
607
|
+
// so runtime-registered schemas (e.g. JSON.parse OBJECTs whose keys are
|
|
608
|
+
// freshly heap-allocated by __jp_str) still resolve correctly.
|
|
609
|
+
const schemaKeyEq = (storedKey, userKey) => ctx.core.includes.has('__jp_obj') || ctx.core.includes.has('__jp')
|
|
610
|
+
? `(i32.or
|
|
611
|
+
(i64.eq ${storedKey} ${userKey})
|
|
612
|
+
(call $__str_eq ${storedKey} ${userKey}))`
|
|
613
|
+
: `(i64.eq ${storedKey} ${userKey})`
|
|
614
|
+
const buildObjectSchemaArm = () => (ctx.schema.list.length > 0 || ctx.core.includes.has('__jp_obj')) ? `
|
|
516
615
|
(if (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
517
616
|
(then
|
|
518
617
|
(if (i32.ne (global.get $__schema_tbl) (i32.const 0))
|
|
519
618
|
(then
|
|
520
619
|
(local.set $sid (i32.wrap_i64 (i64.and (i64.shr_u
|
|
521
|
-
(
|
|
522
|
-
(local.set $kbits
|
|
523
|
-
(
|
|
524
|
-
(local.set $koff (i32.wrap_i64 (i64.and (local.get $kbits) (i64.const
|
|
620
|
+
(local.get $obj) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
621
|
+
(local.set $kbits
|
|
622
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3)))))
|
|
623
|
+
(local.set $koff (i32.wrap_i64 (i64.and (local.get $kbits) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
525
624
|
(local.set $nkeys (i32.load (i32.sub (local.get $koff) (i32.const 8))))
|
|
526
625
|
(local.set $idx (i32.const 0))
|
|
527
626
|
(block $kdone (loop $kloop
|
|
528
627
|
(br_if $kdone (i32.ge_s (local.get $idx) (local.get $nkeys)))
|
|
529
|
-
(if (
|
|
530
|
-
|
|
531
|
-
(local.get $key))
|
|
532
|
-
(then (return (f64.load (i32.add (local.get $off) (i32.shl (local.get $idx) (i32.const 3)))))))
|
|
628
|
+
(if ${schemaKeyEq(`(i64.load (i32.add (local.get $koff) (i32.shl (local.get $idx) (i32.const 3))))`, `(local.get $key)`)}
|
|
629
|
+
(then (return (i64.load (i32.add (local.get $off) (i32.shl (local.get $idx) (i32.const 3)))))))
|
|
533
630
|
(local.set $idx (i32.add (local.get $idx) (i32.const 1)))
|
|
534
631
|
(br $kloop)))))))` : ''
|
|
535
|
-
const
|
|
632
|
+
const buildObjectSchemaLocals = () => (ctx.schema.list.length > 0 || ctx.core.includes.has('__jp_obj'))
|
|
536
633
|
? '(local $sid i32) (local $kbits i64) (local $koff i32) (local $nkeys i32)'
|
|
537
634
|
: ''
|
|
635
|
+
// Same lazy-gating story as buildObjectSchemaArm above — observed at
|
|
636
|
+
// template-expansion time so schemas registered later in the compile
|
|
637
|
+
// still pull the arm in.
|
|
638
|
+
const buildObjectSchemaSetLocals = () => (ctx.schema.list.length > 0 || ctx.core.includes.has('__jp_obj'))
|
|
639
|
+
? '(local $sid i32) (local $kbits i64) (local $koff i32) (local $nkeys i32) (local $idx i32)'
|
|
640
|
+
: ''
|
|
641
|
+
const buildObjectSchemaSetArm = () => (ctx.schema.list.length > 0 || ctx.core.includes.has('__jp_obj')) ? `
|
|
642
|
+
;; If a dynamic write targets an existing fixed-shape field, update the
|
|
643
|
+
;; payload slot as well as the dynamic sidecar below. Otherwise bracket
|
|
644
|
+
;; writes and later dot reads can diverge.
|
|
645
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
646
|
+
(i32.ne (global.get $__schema_tbl) (i32.const 0)))
|
|
647
|
+
(then
|
|
648
|
+
(local.set $sid (i32.wrap_i64 (i64.and (i64.shr_u
|
|
649
|
+
(local.get $obj) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
650
|
+
(local.set $kbits
|
|
651
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3)))))
|
|
652
|
+
(local.set $koff (i32.wrap_i64 (i64.and (local.get $kbits) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
653
|
+
(local.set $nkeys (i32.load (i32.sub (local.get $koff) (i32.const 8))))
|
|
654
|
+
(local.set $idx (i32.const 0))
|
|
655
|
+
(block $schemaSetDone (loop $schemaSetLoop
|
|
656
|
+
(br_if $schemaSetDone (i32.ge_s (local.get $idx) (local.get $nkeys)))
|
|
657
|
+
(if (call $__str_eq
|
|
658
|
+
(i64.load (i32.add (local.get $koff) (i32.shl (local.get $idx) (i32.const 3))))
|
|
659
|
+
(local.get $key))
|
|
660
|
+
(then
|
|
661
|
+
(i64.store (i32.add (local.get $off) (i32.shl (local.get $idx) (i32.const 3))) (local.get $val))
|
|
662
|
+
(br $schemaSetDone)))
|
|
663
|
+
(local.set $idx (i32.add (local.get $idx) (i32.const 1)))
|
|
664
|
+
(br $schemaSetLoop)))))` : ''
|
|
538
665
|
|
|
539
|
-
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj
|
|
666
|
+
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj i64) (param $key i64) (result i64)
|
|
540
667
|
(call $__dyn_get_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
541
668
|
|
|
542
|
-
ctx.core.stdlib['__dyn_get_t'] = `(func $__dyn_get_t (param $obj
|
|
543
|
-
(local $props
|
|
669
|
+
ctx.core.stdlib['__dyn_get_t'] = () => `(func $__dyn_get_t (param $obj i64) (param $key i64) (param $type i32) (result i64)
|
|
670
|
+
(local $props i64) (local $off i32)
|
|
544
671
|
(local $poff i32) (local $pcap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
545
|
-
${
|
|
546
|
-
(local.set $
|
|
547
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
672
|
+
${buildObjectSchemaLocals()}
|
|
673
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
548
674
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
549
675
|
(then
|
|
550
676
|
(block $done
|
|
551
677
|
(loop $follow
|
|
552
|
-
(br_if $done (i32.lt_u (local.get $off) (i32.const
|
|
678
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 16)))
|
|
553
679
|
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
554
680
|
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
555
681
|
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
556
682
|
(br $follow)))))
|
|
557
683
|
(block $dynDone
|
|
558
|
-
(
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
684
|
+
(block $haveProps
|
|
685
|
+
;; ARRAY: header propsPtr at $off-16 is valid only when shift hasn't
|
|
686
|
+
;; rewritten the slot with forwarding bytes. Validate via HASH tag —
|
|
687
|
+
;; rejects 0 (no props) and forwarding garbage. Misses fall through to
|
|
688
|
+
;; the global hash, where __arr_shift migrates props on first .shift().
|
|
689
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
690
|
+
(i32.ge_u (local.get $off) (i32.const 16)))
|
|
691
|
+
(then
|
|
692
|
+
(local.set $props (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
693
|
+
(br_if $haveProps (i32.eq
|
|
694
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $props) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
695
|
+
(i32.const ${PTR.HASH})))
|
|
696
|
+
(local.set $props (i64.const 0))))
|
|
697
|
+
;; OBJECT: heap-allocated (off >= __heap_start) carries propsPtr at
|
|
698
|
+
;; off-16 from __alloc_hdr. The slot is either 0 (no dyn props yet) or
|
|
699
|
+
;; a HASH — no forwarding-garbage case like ARRAY, so a bit-zero test
|
|
700
|
+
;; is enough. Static-segment objects fall through to the global hash.
|
|
701
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
702
|
+
(i32.ge_u (local.get $off) (global.get $__heap_start)))
|
|
703
|
+
(then
|
|
704
|
+
(local.set $props (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
705
|
+
(br_if $dynDone (i64.eqz (local.get $props)))
|
|
706
|
+
(br $haveProps)))
|
|
707
|
+
;; Other header types (TYPED/HASH/SET/MAP) carry propsPtr at off-16
|
|
708
|
+
;; directly, bypassing the global __dyn_props hash.
|
|
709
|
+
(if (i32.and (i32.ge_u (local.get $off) (i32.const 16))
|
|
710
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.TYPED}))
|
|
711
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.HASH}))
|
|
712
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.SET}))
|
|
713
|
+
(i32.eq (local.get $type) (i32.const ${PTR.MAP}))))))
|
|
714
|
+
(then
|
|
715
|
+
(local.set $props (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
716
|
+
(br_if $dynDone (i64.eqz (local.get $props)))
|
|
717
|
+
(br $haveProps)))
|
|
718
|
+
;; Fall back to the global __dyn_props hash (CLOSURE, shifted ARRAY,
|
|
719
|
+
;; static-segment OBJECT). 1-slot cache covers both hits and misses
|
|
720
|
+
;; (props=0 sentinel) so header-less types skip __ihash_get_local probes.
|
|
721
|
+
(br_if $dynDone (f64.eq (global.get $__dyn_props) (f64.const 0)))
|
|
722
|
+
(if (i32.eq (local.get $off) (global.get $__dyn_get_cache_off))
|
|
723
|
+
(then
|
|
724
|
+
(local.set $props (i64.reinterpret_f64 (global.get $__dyn_get_cache_props)))
|
|
725
|
+
(br_if $dynDone (i64.eqz (local.get $props))))
|
|
726
|
+
(else
|
|
727
|
+
(local.set $props (call $__ihash_get_local (i64.reinterpret_f64 (global.get $__dyn_props))
|
|
728
|
+
(i64.reinterpret_f64 (f64.convert_i32_s (local.get $off)))))
|
|
729
|
+
(global.set $__dyn_get_cache_off (local.get $off))
|
|
730
|
+
(if (call $__is_nullish (local.get $props))
|
|
731
|
+
(then
|
|
732
|
+
(global.set $__dyn_get_cache_props (f64.const 0))
|
|
733
|
+
(br $dynDone))
|
|
734
|
+
(else
|
|
735
|
+
(global.set $__dyn_get_cache_props (f64.reinterpret_i64 (local.get $props))))))))
|
|
736
|
+
(local.set $poff (i32.wrap_i64 (i64.and (local.get $props) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
564
737
|
(local.set $pcap (i32.load (i32.sub (local.get $poff) (i32.const 4))))
|
|
565
738
|
(local.set $h (call $__str_hash (local.get $key)))
|
|
566
739
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $pcap) (i32.const 1))))
|
|
567
740
|
(block $hdone (loop $hprobe
|
|
568
741
|
(local.set $slot (i32.add (local.get $poff) (i32.mul (local.get $idx) (i32.const ${MAP_ENTRY}))))
|
|
569
|
-
(br_if $dynDone (
|
|
570
|
-
(if (call $__str_eq (
|
|
571
|
-
(then (return (
|
|
742
|
+
(br_if $dynDone (i64.eqz (i64.load (local.get $slot))))
|
|
743
|
+
(if (call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))
|
|
744
|
+
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
572
745
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $pcap) (i32.const 1))))
|
|
573
746
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
574
747
|
(br_if $hdone (i32.ge_s (local.get $tries) (local.get $pcap)))
|
|
575
|
-
(br $hprobe))))${
|
|
576
|
-
(
|
|
748
|
+
(br $hprobe))))${buildObjectSchemaArm()}
|
|
749
|
+
(i64.const ${UNDEF_NAN}))`
|
|
577
750
|
|
|
578
|
-
ctx.core.stdlib['__dyn_get_or'] = `(func $__dyn_get_or (param $obj
|
|
579
|
-
(local $val
|
|
751
|
+
ctx.core.stdlib['__dyn_get_or'] = `(func $__dyn_get_or (param $obj i64) (param $key i64) (param $fallback i64) (result i64)
|
|
752
|
+
(local $val i64)
|
|
580
753
|
(local.set $val (call $__dyn_get (local.get $obj) (local.get $key)))
|
|
581
|
-
(if (result
|
|
582
|
-
(i64.eq (
|
|
754
|
+
(if (result i64)
|
|
755
|
+
(i64.eq (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
583
756
|
(then (local.get $fallback))
|
|
584
757
|
(else (local.get $val))))`
|
|
585
758
|
|
|
586
|
-
ctx.core.stdlib['__dyn_get_expr'] = `(func $__dyn_get_expr (param $obj
|
|
759
|
+
ctx.core.stdlib['__dyn_get_expr'] = `(func $__dyn_get_expr (param $obj i64) (param $key i64) (result i64)
|
|
587
760
|
(call $__dyn_get_expr_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
588
761
|
|
|
589
|
-
ctx.core.stdlib['__dyn_get_expr_t'] = `(func $__dyn_get_expr_t (param $obj
|
|
590
|
-
(local $val
|
|
762
|
+
ctx.core.stdlib['__dyn_get_expr_t'] = `(func $__dyn_get_expr_t (param $obj i64) (param $key i64) (param $t i32) (result i64)
|
|
763
|
+
(local $val i64)
|
|
591
764
|
(local.set $val (call $__dyn_get_t (local.get $obj) (local.get $key) (local.get $t)))
|
|
592
|
-
(if (result
|
|
593
|
-
(i64.ne (
|
|
765
|
+
(if (result i64)
|
|
766
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
594
767
|
(then (local.get $val))
|
|
595
768
|
(else
|
|
596
|
-
(if (result
|
|
769
|
+
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
597
770
|
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
598
|
-
(else (
|
|
771
|
+
(else (i64.const ${NULL_NAN}))))))`
|
|
599
772
|
|
|
600
773
|
// Like __dyn_get_expr but also resolves EXTERNAL host objects via __ext_prop.
|
|
601
774
|
// Used at call sites where receiver type is statically unknown.
|
|
@@ -605,24 +778,24 @@ export default (ctx) => {
|
|
|
605
778
|
// dyn_props (those are for OBJECT/ARRAY attached props), so the original __dyn_get
|
|
606
779
|
// call was always wasted work on hashes — and JSON.parse / Map-style code is the
|
|
607
780
|
// dominant HASH consumer.
|
|
608
|
-
return `(func $__dyn_get_any (param $obj
|
|
781
|
+
return `(func $__dyn_get_any (param $obj i64) (param $key i64) (result i64)
|
|
609
782
|
(call $__dyn_get_any_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
610
783
|
}
|
|
611
784
|
|
|
612
785
|
ctx.core.stdlib['__dyn_get_any_t'] = () => {
|
|
613
786
|
const extArm = ctx.features.external
|
|
614
|
-
? `(if (result
|
|
787
|
+
? `(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.EXTERNAL}))
|
|
615
788
|
(then (call $__ext_prop (local.get $obj) (local.get $key)))
|
|
616
|
-
(else (
|
|
617
|
-
: `(
|
|
618
|
-
return `(func $__dyn_get_any_t (param $obj
|
|
619
|
-
(local $val
|
|
620
|
-
(if (result
|
|
789
|
+
(else (i64.const ${NULL_NAN})))`
|
|
790
|
+
: `(i64.const ${NULL_NAN})`
|
|
791
|
+
return `(func $__dyn_get_any_t (param $obj i64) (param $key i64) (param $t i32) (result i64)
|
|
792
|
+
(local $val i64)
|
|
793
|
+
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
621
794
|
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
622
795
|
(else
|
|
623
796
|
(local.set $val (call $__dyn_get_t (local.get $obj) (local.get $key) (local.get $t)))
|
|
624
|
-
(if (result
|
|
625
|
-
(i64.ne (
|
|
797
|
+
(if (result i64)
|
|
798
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
626
799
|
(then (local.get $val))
|
|
627
800
|
(else ${extArm})))))`
|
|
628
801
|
}
|
|
@@ -631,45 +804,101 @@ export default (ctx) => {
|
|
|
631
804
|
// Defer the root insert to the end and gate it on props-ptr change: most calls hit
|
|
632
805
|
// the no-grow case where the ptr is unchanged and the root slot already points to it.
|
|
633
806
|
// __ptr_offset inlined (forwarding-aware) — only ARRAY ever has forwarding.
|
|
634
|
-
ctx.core.stdlib['__dyn_set'] = `(func $__dyn_set (param $obj
|
|
635
|
-
(local $root
|
|
636
|
-
(local $
|
|
637
|
-
(local.set $
|
|
638
|
-
(
|
|
639
|
-
|
|
640
|
-
(local.set $bits (i64.reinterpret_f64 (local.get $obj)))
|
|
641
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
642
|
-
(if (i32.eq
|
|
643
|
-
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
644
|
-
(i32.const ${PTR.ARRAY}))
|
|
807
|
+
ctx.core.stdlib['__dyn_set'] = () => `(func $__dyn_set (param $obj i64) (param $key i64) (param $val i64) (result i64)
|
|
808
|
+
(local $root i64) (local $props i64) (local $oldProps i64) (local $objKey i64)
|
|
809
|
+
(local $off i32) (local $type i32) ${buildObjectSchemaSetLocals()}
|
|
810
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
811
|
+
(local.set $type (i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
812
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
645
813
|
(then
|
|
646
814
|
(block $done
|
|
647
815
|
(loop $follow
|
|
648
|
-
(br_if $done (i32.lt_u (local.get $off) (i32.const
|
|
816
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 16)))
|
|
649
817
|
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
650
818
|
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
651
819
|
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
652
820
|
(br $follow)))))
|
|
653
|
-
|
|
821
|
+
${buildObjectSchemaSetArm()}
|
|
822
|
+
;; Header types carry propsPtr at off-16. Read/grow/write directly there;
|
|
823
|
+
;; skip the global __dyn_props hash entirely. ARRAY also uses this slot, but
|
|
824
|
+
;; only when shift hasn't overwritten it with forwarding bytes (HASH-tagged
|
|
825
|
+
;; check rejects 0 + forwarding garbage). Shifted ARRAYs fall back to the
|
|
826
|
+
;; global __dyn_props where __arr_shift has migrated their props.
|
|
827
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
828
|
+
(then
|
|
829
|
+
(if (i32.ge_u (local.get $off) (i32.const 16))
|
|
830
|
+
(then
|
|
831
|
+
(local.set $oldProps (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
832
|
+
(if (i32.or
|
|
833
|
+
(i64.eqz (local.get $oldProps))
|
|
834
|
+
(i32.eq
|
|
835
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $oldProps) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
836
|
+
(i32.const ${PTR.HASH})))
|
|
837
|
+
(then
|
|
838
|
+
(local.set $props
|
|
839
|
+
(if (result i64) (i64.eqz (local.get $oldProps))
|
|
840
|
+
(then (i64.reinterpret_f64 (call $__hash_new_small)))
|
|
841
|
+
(else (local.get $oldProps))))
|
|
842
|
+
(local.set $props (call $__hash_set_local (local.get $props) (local.get $key) (local.get $val)))
|
|
843
|
+
(if (i64.ne (local.get $props) (local.get $oldProps))
|
|
844
|
+
(then (i64.store (i32.sub (local.get $off) (i32.const 16)) (local.get $props))))
|
|
845
|
+
(return (local.get $val))))))))
|
|
846
|
+
;; OBJECT: heap-allocated (off >= __heap_start) writes propsPtr directly at
|
|
847
|
+
;; off-16. The slot is 0 (init) or HASH — no forwarding-garbage like ARRAY.
|
|
848
|
+
;; Static-segment OBJECTs fall through to the global __dyn_props.
|
|
849
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
850
|
+
(i32.ge_u (local.get $off) (global.get $__heap_start)))
|
|
851
|
+
(then
|
|
852
|
+
(local.set $oldProps (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
853
|
+
(local.set $props
|
|
854
|
+
(if (result i64) (i64.eqz (local.get $oldProps))
|
|
855
|
+
(then (i64.reinterpret_f64 (call $__hash_new_small)))
|
|
856
|
+
(else (local.get $oldProps))))
|
|
857
|
+
(local.set $props (call $__hash_set_local (local.get $props) (local.get $key) (local.get $val)))
|
|
858
|
+
(if (i64.ne (local.get $props) (local.get $oldProps))
|
|
859
|
+
(then (i64.store (i32.sub (local.get $off) (i32.const 16)) (local.get $props))))
|
|
860
|
+
(return (local.get $val))))
|
|
861
|
+
(if (i32.and (i32.ge_u (local.get $off) (i32.const 16))
|
|
862
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.TYPED}))
|
|
863
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.HASH}))
|
|
864
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.SET}))
|
|
865
|
+
(i32.eq (local.get $type) (i32.const ${PTR.MAP}))))))
|
|
866
|
+
(then
|
|
867
|
+
(local.set $oldProps (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
868
|
+
(local.set $props
|
|
869
|
+
(if (result i64) (i64.eqz (local.get $oldProps))
|
|
870
|
+
(then (i64.reinterpret_f64 (call $__hash_new_small)))
|
|
871
|
+
(else (local.get $oldProps))))
|
|
872
|
+
(local.set $props (call $__hash_set_local (local.get $props) (local.get $key) (local.get $val)))
|
|
873
|
+
(if (i64.ne (local.get $props) (local.get $oldProps))
|
|
874
|
+
(then (i64.store (i32.sub (local.get $off) (i32.const 16)) (local.get $props))))
|
|
875
|
+
(return (local.get $val))))
|
|
876
|
+
;; Fallback: non-header types use the global __dyn_props.
|
|
877
|
+
(local.set $root (i64.reinterpret_f64 (global.get $__dyn_props)))
|
|
878
|
+
(if (i64.eqz (local.get $root))
|
|
879
|
+
(then (local.set $root (i64.reinterpret_f64 (call $__hash_new)))))
|
|
880
|
+
(local.set $objKey (i64.reinterpret_f64 (f64.convert_i32_s (local.get $off))))
|
|
654
881
|
(local.set $oldProps (call $__ihash_get_local (local.get $root) (local.get $objKey)))
|
|
655
882
|
(local.set $props
|
|
656
|
-
(if (result
|
|
657
|
-
(then (call $
|
|
883
|
+
(if (result i64) (call $__is_nullish (local.get $oldProps))
|
|
884
|
+
(then (i64.reinterpret_f64 (call $__hash_new_small)))
|
|
658
885
|
(else (local.get $oldProps))))
|
|
659
886
|
(local.set $props (call $__hash_set_local (local.get $props) (local.get $key) (local.get $val)))
|
|
660
|
-
(if (i64.ne (
|
|
887
|
+
(if (i64.ne (local.get $props) (local.get $oldProps))
|
|
661
888
|
(then
|
|
662
889
|
(local.set $root (call $__ihash_set_local (local.get $root) (local.get $objKey) (local.get $props)))
|
|
663
|
-
(global.set $__dyn_props (local.get $root)))
|
|
890
|
+
(global.set $__dyn_props (f64.reinterpret_i64 (local.get $root)))
|
|
891
|
+
(if (i32.eq (local.get $off) (global.get $__dyn_get_cache_off))
|
|
892
|
+
(then (global.set $__dyn_get_cache_props (f64.reinterpret_i64 (local.get $props)))))))
|
|
664
893
|
(local.get $val))`
|
|
665
894
|
|
|
666
895
|
ctx.core.stdlib['__dyn_move'] = `(func $__dyn_move (param $oldOff i32) (param $newOff i32)
|
|
667
|
-
(local $props
|
|
896
|
+
(local $props i64) (local $root i64)
|
|
668
897
|
(if (f64.eq (global.get $__dyn_props) (f64.const 0)) (then (return)))
|
|
669
|
-
(local.set $props (call $__ihash_get_local (global.get $__dyn_props) (f64.convert_i32_s (local.get $oldOff))))
|
|
898
|
+
(local.set $props (call $__ihash_get_local (i64.reinterpret_f64 (global.get $__dyn_props)) (i64.reinterpret_f64 (f64.convert_i32_s (local.get $oldOff)))))
|
|
670
899
|
(if (call $__is_nullish (local.get $props)) (then (return)))
|
|
671
|
-
(local.set $root (call $__ihash_set_local (global.get $__dyn_props) (f64.convert_i32_s (local.get $newOff)) (local.get $props)))
|
|
672
|
-
(global.set $__dyn_props (local.get $root)))`
|
|
900
|
+
(local.set $root (call $__ihash_set_local (i64.reinterpret_f64 (global.get $__dyn_props)) (i64.reinterpret_f64 (f64.convert_i32_s (local.get $newOff))) (local.get $props)))
|
|
901
|
+
(global.set $__dyn_props (f64.reinterpret_i64 (local.get $root))))`
|
|
673
902
|
|
|
674
903
|
// Generated HASH probe functions
|
|
675
904
|
ctx.core.stdlib['__hash_set'] = () => genUpsertGrow('__hash_set', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, false, ctx.features.external)
|
|
@@ -700,10 +929,8 @@ export default (ctx) => {
|
|
|
700
929
|
const objVal = ['local.get', `$${objTmp}`]
|
|
701
930
|
const idxVal = ['local.get', `$${idxTmp}`]
|
|
702
931
|
const typeVal = ['local.get', `$${typeTmp}`]
|
|
703
|
-
const isStringKey = ['call', '$__is_str_key', keyVal]
|
|
704
|
-
const isStringLike = ['i32.
|
|
705
|
-
['i32.eq', typeVal, ['i32.const', PTR.STRING]],
|
|
706
|
-
['i32.eq', typeVal, ['i32.const', PTR.SSO]]]
|
|
932
|
+
const isStringKey = ['call', '$__is_str_key', ['i64.reinterpret_f64', keyVal]]
|
|
933
|
+
const isStringLike = ['i32.eq', typeVal, ['i32.const', PTR.STRING]]
|
|
707
934
|
const isArrayLike = ['i32.or',
|
|
708
935
|
['i32.eq', typeVal, ['i32.const', PTR.ARRAY]],
|
|
709
936
|
['i32.eq', typeVal, ['i32.const', PTR.TYPED]]]
|
|
@@ -714,9 +941,7 @@ export default (ctx) => {
|
|
|
714
941
|
['i32.eq', typeVal, ['i32.const', PTR.ARRAY]],
|
|
715
942
|
['i32.eq', typeVal, ['i32.const', PTR.TYPED]]],
|
|
716
943
|
['i32.or',
|
|
717
|
-
['i32.
|
|
718
|
-
['i32.eq', typeVal, ['i32.const', PTR.STRING]],
|
|
719
|
-
['i32.eq', typeVal, ['i32.const', PTR.SSO]]],
|
|
944
|
+
['i32.eq', typeVal, ['i32.const', PTR.STRING]],
|
|
720
945
|
['i32.or',
|
|
721
946
|
['i32.or',
|
|
722
947
|
['i32.eq', typeVal, ['i32.const', PTR.SET]],
|
|
@@ -730,7 +955,7 @@ export default (ctx) => {
|
|
|
730
955
|
['local.set', `$${objTmp}`, asF64(emit(obj))],
|
|
731
956
|
['local.set', `$${keyTmp}`, asF64(emit(key))],
|
|
732
957
|
['local.set', `$${outTmp}`, ['i32.const', 0]],
|
|
733
|
-
['local.set', `$${typeTmp}`, ['call', '$__ptr_type', objVal]],
|
|
958
|
+
['local.set', `$${typeTmp}`, ['call', '$__ptr_type', ['i64.reinterpret_f64', objVal]]],
|
|
734
959
|
['local.set', `$${idxTmp}`, ['i32.trunc_sat_f64_s', keyVal]],
|
|
735
960
|
|
|
736
961
|
['if', ['i32.and',
|
|
@@ -740,52 +965,72 @@ export default (ctx) => {
|
|
|
740
965
|
['i32.ge_s', idxVal, ['i32.const', 0]]]],
|
|
741
966
|
['then',
|
|
742
967
|
['if', isStringLike,
|
|
743
|
-
['then', ['local.set', `$${outTmp}`, ['i32.lt_u', idxVal, ['call', '$__str_byteLen', objVal]]]]],
|
|
968
|
+
['then', ['local.set', `$${outTmp}`, ['i32.lt_u', idxVal, ['call', '$__str_byteLen', ['i64.reinterpret_f64', objVal]]]]]],
|
|
744
969
|
['if', isArrayLike,
|
|
745
|
-
['then', ['local.set', `$${outTmp}`, ['i32.lt_u', idxVal, ['call', '$__len', objVal]]]]]]],
|
|
970
|
+
['then', ['local.set', `$${outTmp}`, ['i32.lt_u', idxVal, ['call', '$__len', ['i64.reinterpret_f64', objVal]]]]]]]],
|
|
746
971
|
|
|
747
972
|
['if', isStringKey,
|
|
748
973
|
['then',
|
|
749
974
|
['if', hasDynProps,
|
|
750
975
|
['then', ['local.set', `$${outTmp}`,
|
|
751
|
-
['i32.eqz', ['call', '$__is_nullish', ['call', '$__dyn_get', objVal, keyVal]]]]]]]],
|
|
976
|
+
['i32.eqz', ['call', '$__is_nullish', ['call', '$__dyn_get', ['i64.reinterpret_f64', objVal], ['i64.reinterpret_f64', keyVal]]]]]]]]],
|
|
752
977
|
|
|
753
978
|
['if', ['i32.eq', typeVal, ['i32.const', PTR.HASH]],
|
|
754
979
|
['then', ['local.set', `$${outTmp}`,
|
|
755
980
|
['if', ['result', 'i32'], isStringKey,
|
|
756
|
-
['then', ['call', '$__hash_has', objVal, keyVal]],
|
|
757
|
-
['else', ['call', '$__hash_has', objVal, ['call', '$__to_str', keyVal]]]]]]],
|
|
981
|
+
['then', ['call', '$__hash_has', ['i64.reinterpret_f64', objVal], ['i64.reinterpret_f64', keyVal]]],
|
|
982
|
+
['else', ['call', '$__hash_has', ['i64.reinterpret_f64', objVal], ['call', '$__to_str', ['i64.reinterpret_f64', keyVal]]]]]]]],
|
|
758
983
|
|
|
759
984
|
...(ctx.features.external ? [['if', ['i32.eq', typeVal, ['i32.const', PTR.EXTERNAL]],
|
|
760
|
-
['then', ['local.set', `$${outTmp}`, ['call', '$__ext_has',
|
|
985
|
+
['then', ['local.set', `$${outTmp}`, ['call', '$__ext_has',
|
|
986
|
+
['i64.reinterpret_f64', objVal], ['i64.reinterpret_f64', keyVal]]]]]] : []),
|
|
761
987
|
|
|
762
988
|
['local.get', `$${outTmp}`]], 'i32')
|
|
763
989
|
}
|
|
764
990
|
|
|
765
991
|
// === for...in on dynamic objects (HASH iteration) ===
|
|
766
992
|
|
|
767
|
-
// for-in: iterate HASH entries, binding key string to loop variable
|
|
993
|
+
// for-in: iterate HASH entries, binding key string to loop variable.
|
|
994
|
+
// Also handles OBJECT/ARRAY/etc whose dynamic props are stored at off-16
|
|
995
|
+
// as a HASH (see __dyn_set). Non-HASH receivers redirect to that props HASH.
|
|
768
996
|
ctx.core.emit['for-in'] = (varName, src, body) => {
|
|
769
997
|
const off = tempI32('ho'), cap = tempI32('hc')
|
|
770
998
|
const i = tempI32('hi'), slot = tempI32('hs')
|
|
999
|
+
const ptrI64 = tempI64('hp'), srcOff = tempI32('hso'), srcType = tempI32('hst')
|
|
771
1000
|
if (!ctx.func.locals.has(varName)) ctx.func.locals.set(varName, 'f64')
|
|
772
1001
|
const id = ctx.func.uniq++
|
|
773
1002
|
const va = asF64(emit(src))
|
|
774
1003
|
const bodyFlat = emitFlat(body)
|
|
1004
|
+
inc('__ptr_type')
|
|
775
1005
|
return [
|
|
776
|
-
|
|
777
|
-
['local.set', `$${
|
|
778
|
-
['local.set', `$${
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
['
|
|
782
|
-
['
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1006
|
+
// Save source ptr as i64
|
|
1007
|
+
['local.set', `$${ptrI64}`, ['i64.reinterpret_f64', va]],
|
|
1008
|
+
['local.set', `$${srcType}`, ['call', '$__ptr_type', ['local.get', `$${ptrI64}`]]],
|
|
1009
|
+
// If not HASH, follow off-16 to props hash (or zero if no props yet).
|
|
1010
|
+
['if', ['i32.ne', ['local.get', `$${srcType}`], ['i32.const', PTR.HASH]],
|
|
1011
|
+
['then',
|
|
1012
|
+
['local.set', `$${srcOff}`, ['call', '$__ptr_offset', ['local.get', `$${ptrI64}`]]],
|
|
1013
|
+
['if', ['i32.ge_u', ['local.get', `$${srcOff}`], ['i32.const', 16]],
|
|
1014
|
+
['then',
|
|
1015
|
+
['local.set', `$${ptrI64}`, ['i64.load', ['i32.sub', ['local.get', `$${srcOff}`], ['i32.const', 16]]]]],
|
|
1016
|
+
['else',
|
|
1017
|
+
['local.set', `$${ptrI64}`, ['i64.const', 0]]]]]],
|
|
1018
|
+
// Empty / null props: skip iteration entirely.
|
|
1019
|
+
['if', ['i64.ne', ['local.get', `$${ptrI64}`], ['i64.const', 0]],
|
|
1020
|
+
['then',
|
|
1021
|
+
['local.set', `$${off}`, ['call', '$__ptr_offset', ['local.get', `$${ptrI64}`]]],
|
|
1022
|
+
['local.set', `$${cap}`, ['call', '$__cap', ['local.get', `$${ptrI64}`]]],
|
|
1023
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1024
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
1025
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${cap}`]]],
|
|
1026
|
+
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${off}`],
|
|
1027
|
+
['i32.mul', ['local.get', `$${i}`], ['i32.const', MAP_ENTRY]]]],
|
|
1028
|
+
['if', ['i64.ne', ['i64.load', ['local.get', `$${slot}`]], ['i64.const', 0]],
|
|
1029
|
+
['then',
|
|
1030
|
+
['local.set', `$${varName}`, ['f64.reinterpret_i64', ['i64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]]],
|
|
1031
|
+
...bodyFlat]],
|
|
1032
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1033
|
+
['br', `$loop${id}`]]]]]
|
|
789
1034
|
]
|
|
790
1035
|
}
|
|
791
1036
|
}
|