jz 0.1.1 → 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 +257 -90
- package/cli.js +34 -8
- package/index.js +102 -13
- package/module/array.js +396 -133
- package/module/collection.js +455 -212
- package/module/console.js +169 -88
- package/module/core.js +246 -144
- package/module/date.js +691 -0
- package/module/function.js +81 -21
- package/module/index.js +2 -1
- package/module/json.js +483 -138
- package/module/math.js +79 -39
- package/module/number.js +188 -78
- package/module/object.js +227 -47
- package/module/regex.js +33 -30
- package/module/schema.js +14 -17
- package/module/string.js +434 -235
- package/module/symbol.js +4 -3
- package/module/timer.js +89 -31
- package/module/typedarray.js +174 -44
- package/package.json +3 -10
- package/src/analyze.js +626 -132
- package/src/autoload.js +14 -6
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +634 -145
- package/src/host.js +244 -51
- package/src/ir.js +81 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1132 -4
- package/src/prepare.js +321 -75
- package/src/vectorize.js +1016 -0
- package/wasi.js +13 -0
- package/src/key.js +0 -73
- package/src/source.js +0 -76
package/module/collection.js
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* @module collection
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { typed, asF64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, allocPtr } from '../src/ir.js'
|
|
11
|
+
import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64, allocPtr, undefExpr } from '../src/ir.js'
|
|
12
12
|
import { emit, emitFlat } from '../src/emit.js'
|
|
13
13
|
import { valTypeOf, lookupValType, VAL } from '../src/analyze.js'
|
|
14
|
-
import { inc, PTR } from '../src/ctx.js'
|
|
14
|
+
import { inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
15
15
|
|
|
16
16
|
const SET_ENTRY = 16 // hash + key
|
|
17
17
|
const MAP_ENTRY = 24 // hash + key + value
|
|
@@ -28,8 +28,10 @@ const HASH_F64 = new Float64Array(HASH_BUF)
|
|
|
28
28
|
const HASH_U32 = new Uint32Array(HASH_BUF)
|
|
29
29
|
|
|
30
30
|
export function numHashLiteral(n) {
|
|
31
|
+
if (Object.is(n, 0) || Object.is(n, -0)) return 2
|
|
31
32
|
HASH_F64[0] = n
|
|
32
|
-
|
|
33
|
+
const h = (HASH_U32[0] ^ HASH_U32[1]) | 0
|
|
34
|
+
return h <= 1 ? (h + 2) | 0 : h
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
function numConstLiteral(expr) {
|
|
@@ -39,40 +41,39 @@ function numConstLiteral(expr) {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
// Equality expressions for probe templates
|
|
42
|
-
const
|
|
43
|
-
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))'
|
|
44
46
|
|
|
45
47
|
/** Generate upsert (add/set) probe function. hasVal: store value at slot+16.
|
|
46
48
|
* hasExt: emit EXTERNAL fallthrough (call $__ext_set on non-matching type).
|
|
47
49
|
* Gated off → type mismatch just returns coll unchanged. */
|
|
48
50
|
function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt) {
|
|
49
|
-
const valParam = hasVal ? '(param $val
|
|
50
|
-
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))` : ''
|
|
51
53
|
const onMatch = hasVal
|
|
52
|
-
? `(then\n (
|
|
54
|
+
? `(then\n (i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))\n (br $done))`
|
|
53
55
|
: `(then (br $done))`
|
|
54
56
|
|
|
55
57
|
const extBranch = hasVal
|
|
56
58
|
? '(then (call $__ext_set (local.get $coll) (local.get $key) (local.get $val)) drop)'
|
|
57
59
|
: '(then (nop))'
|
|
58
|
-
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})))`
|
|
59
61
|
const typeGuard = hasExt
|
|
60
62
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL})) ${extBranch}) (return (local.get $coll))))`
|
|
61
63
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (return (local.get $coll))))`
|
|
62
|
-
return `(func $${name} (param $coll
|
|
63
|
-
(local $
|
|
64
|
-
(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)
|
|
65
66
|
${typeGuard}
|
|
66
|
-
(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}))))
|
|
67
68
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
68
69
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
69
70
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
70
71
|
(block $done (loop $probe
|
|
71
72
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
72
|
-
(if (
|
|
73
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
73
74
|
(then
|
|
74
|
-
(
|
|
75
|
-
(
|
|
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}
|
|
76
77
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
77
78
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
78
79
|
(br $done)))
|
|
@@ -87,34 +88,35 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
87
88
|
* wantValue=false: return i32 0/1 existence flag.
|
|
88
89
|
* hasExt: emit EXTERNAL fallthrough (delegate to __ext_prop/__ext_has). */
|
|
89
90
|
function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, hasExt) {
|
|
90
|
-
const rt = wantValue ? '
|
|
91
|
+
const rt = wantValue ? 'i64' : 'i32'
|
|
91
92
|
const onEmpty = wantValue
|
|
92
|
-
? `(return (
|
|
93
|
+
? `(return (i64.const ${NULL_NAN}))`
|
|
93
94
|
: '(return (i32.const 0))'
|
|
94
95
|
const onFound = wantValue
|
|
95
|
-
? '(return (
|
|
96
|
+
? '(return (i64.load (i32.add (local.get $slot) (i32.const 16))))'
|
|
96
97
|
: '(return (i32.const 1))'
|
|
97
98
|
const notFound = wantValue
|
|
98
|
-
? `(
|
|
99
|
+
? `(i64.const ${NULL_NAN})`
|
|
99
100
|
: '(i32.const 0)'
|
|
100
|
-
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})))`
|
|
101
102
|
const typeGuard = hasExt
|
|
102
103
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL}))
|
|
103
|
-
(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))'}))
|
|
104
107
|
(else ${onEmpty}))))`
|
|
105
108
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then ${onEmpty}))`
|
|
106
109
|
|
|
107
|
-
return `(func $${name} (param $coll
|
|
108
|
-
(local $
|
|
109
|
-
(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)
|
|
110
112
|
${typeGuard}
|
|
111
|
-
(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}))))
|
|
112
114
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
113
115
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
114
116
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
115
117
|
(block $done (loop $probe
|
|
116
118
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
117
|
-
(if (
|
|
119
|
+
(if (i64.eqz (i64.load (local.get $slot))) (then ${onEmpty}))
|
|
118
120
|
(if ${eqExpr} (then ${onFound}))
|
|
119
121
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
120
122
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
@@ -125,7 +127,7 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
125
127
|
|
|
126
128
|
/** Generate delete probe function. Zero out entry on match. */
|
|
127
129
|
function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
128
|
-
return `(func $${name} (param $coll
|
|
130
|
+
return `(func $${name} (param $coll i64) (param $key i64) (result i32)
|
|
129
131
|
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
130
132
|
(if (i32.ne (call $__ptr_type (local.get $coll)) (i32.const ${expectedType})) (then (return (i32.const 0))))
|
|
131
133
|
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
@@ -134,11 +136,11 @@ function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
|
134
136
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
135
137
|
(block $done (loop $probe
|
|
136
138
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
137
|
-
(if (
|
|
139
|
+
(if (i64.eqz (i64.load (local.get $slot))) (then (return (i32.const 0))))
|
|
138
140
|
(if ${eqExpr}
|
|
139
141
|
(then
|
|
140
|
-
(
|
|
141
|
-
(
|
|
142
|
+
(i64.store (local.get $slot) (i64.const 0))
|
|
143
|
+
(i64.store (i32.add (local.get $slot) (i32.const 8)) (i64.const 0))
|
|
142
144
|
(return (i32.const 1))))
|
|
143
145
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
144
146
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
@@ -165,7 +167,7 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
165
167
|
(then
|
|
166
168
|
${nonHashFallback}
|
|
167
169
|
(return (local.get $obj))))`
|
|
168
|
-
return `(func $${name} (param $obj
|
|
170
|
+
return `(func $${name} (param $obj i64) (param $key i64) (param $val i64) (result i64)
|
|
169
171
|
(local $off i32) (local $cap i32) (local $h i32) (local $idx i32) (local $slot i32)
|
|
170
172
|
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
171
173
|
(local $oldslot i32) (local $newidx i32) (local $newslot i32)
|
|
@@ -182,41 +184,41 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
182
184
|
(block $rd (loop $rl
|
|
183
185
|
(br_if $rd (i32.ge_s (local.get $i) (local.get $cap)))
|
|
184
186
|
(local.set $oldslot (i32.add (local.get $off) (i32.mul (local.get $i) (i32.const ${entrySize}))))
|
|
185
|
-
(if (
|
|
187
|
+
(if (i64.ne (i64.load (local.get $oldslot)) (i64.const 0))
|
|
186
188
|
(then
|
|
187
|
-
(local.set $h (call ${hashFn} (
|
|
189
|
+
(local.set $h (call ${hashFn} (i64.load (i32.add (local.get $oldslot) (i32.const 8)))))
|
|
188
190
|
(local.set $newidx (i32.and (local.get $h) (i32.sub (local.get $newcap) (i32.const 1))))
|
|
189
191
|
(block $ins (loop $probe2
|
|
190
192
|
(local.set $newslot (i32.add (local.get $newptr) (i32.mul (local.get $newidx) (i32.const ${entrySize}))))
|
|
191
|
-
(br_if $ins (
|
|
193
|
+
(br_if $ins (i64.eqz (i64.load (local.get $newslot))))
|
|
192
194
|
(local.set $newidx (i32.and (i32.add (local.get $newidx) (i32.const 1)) (i32.sub (local.get $newcap) (i32.const 1))))
|
|
193
195
|
(br $probe2)))
|
|
194
|
-
(
|
|
195
|
-
(
|
|
196
|
-
(
|
|
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))))
|
|
197
199
|
(i32.store (i32.sub (local.get $newptr) (i32.const 8))
|
|
198
200
|
(i32.add (i32.load (i32.sub (local.get $newptr) (i32.const 8))) (i32.const 1)))))
|
|
199
201
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
200
202
|
(br $rl)))
|
|
201
203
|
(local.set $off (local.get $newptr))
|
|
202
204
|
(local.set $cap (local.get $newcap))
|
|
203
|
-
(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))))))
|
|
204
206
|
;; Insert/update
|
|
205
207
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
206
208
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
207
209
|
(block $done (loop $probe
|
|
208
210
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
209
|
-
(if (
|
|
211
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
210
212
|
(then
|
|
211
|
-
(
|
|
212
|
-
(
|
|
213
|
-
(
|
|
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))
|
|
214
216
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
215
217
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
216
218
|
(br $done)))
|
|
217
219
|
(if ${eqExpr}
|
|
218
220
|
(then
|
|
219
|
-
(
|
|
221
|
+
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
220
222
|
(br $done)))
|
|
221
223
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
222
224
|
(br $probe)))
|
|
@@ -224,84 +226,81 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing = UNDEF_NAN) {
|
|
227
|
-
return `(func $${name} (param $coll
|
|
228
|
-
(local $
|
|
229
|
-
(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)
|
|
230
231
|
(if (i32.ne
|
|
231
|
-
(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})))
|
|
232
233
|
(i32.const ${expectedType}))
|
|
233
|
-
(then (return (
|
|
234
|
-
(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}))))
|
|
235
236
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
236
237
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
237
238
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
238
239
|
(block $done (loop $probe
|
|
239
240
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
240
|
-
(if (
|
|
241
|
-
(then (return (
|
|
241
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
242
|
+
(then (return (i64.const ${missing}))))
|
|
242
243
|
(if ${eqExpr}
|
|
243
|
-
(then (return (
|
|
244
|
+
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
244
245
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
245
246
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
246
247
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
247
248
|
(br $probe)))
|
|
248
|
-
(
|
|
249
|
+
(i64.const ${missing}))`
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing = UNDEF_NAN, hasExt = false) {
|
|
252
|
-
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})))`
|
|
253
254
|
const typeGuard = hasExt
|
|
254
255
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
255
256
|
(then
|
|
256
257
|
(if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL}))
|
|
257
258
|
(then (return (call $__ext_prop (local.get $coll) (local.get $key))))
|
|
258
|
-
(else (return (
|
|
259
|
+
(else (return (i64.const ${missing}))))))`
|
|
259
260
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
260
|
-
(then (return (
|
|
261
|
-
return `(func $${name} (param $coll
|
|
262
|
-
(local $
|
|
263
|
-
(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)
|
|
264
264
|
${typeGuard}
|
|
265
|
-
(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}))))
|
|
266
266
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
267
267
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
268
268
|
(block $done (loop $probe
|
|
269
269
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
270
|
-
(if (
|
|
271
|
-
(then (return (
|
|
270
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
271
|
+
(then (return (i64.const ${missing}))))
|
|
272
272
|
(if ${eqExpr}
|
|
273
|
-
(then (return (
|
|
273
|
+
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
274
274
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
275
275
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
276
276
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
277
277
|
(br $probe)))
|
|
278
|
-
(
|
|
278
|
+
(i64.const ${missing}))`
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
282
|
-
return `(func $${name} (param $obj
|
|
283
|
-
(local $
|
|
284
|
-
(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)
|
|
285
284
|
(if (i32.ne
|
|
286
|
-
(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})))
|
|
287
286
|
(i32.const ${expectedType}))
|
|
288
287
|
(then (return (local.get $obj))))
|
|
289
|
-
(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}))))
|
|
290
289
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
291
290
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1))))
|
|
292
291
|
(block $done (loop $probe
|
|
293
292
|
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
294
|
-
(if (
|
|
293
|
+
(if (i64.eqz (i64.load (local.get $slot)))
|
|
295
294
|
(then
|
|
296
|
-
(
|
|
297
|
-
(
|
|
298
|
-
(
|
|
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))
|
|
299
298
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
300
299
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
301
300
|
(br $done)))
|
|
302
301
|
(if ${eqExpr}
|
|
303
302
|
(then
|
|
304
|
-
(
|
|
303
|
+
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
305
304
|
(br $done)))
|
|
306
305
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $cap) (i32.const 1))))
|
|
307
306
|
(br $probe)))
|
|
@@ -314,12 +313,16 @@ export default (ctx) => {
|
|
|
314
313
|
// Evaluated lazily at resolveIncludes() time — after emission has finalized ctx.features.
|
|
315
314
|
const ifExt = (name) => () => ctx.features.external ? [name] : []
|
|
316
315
|
Object.assign(ctx.core.stdlibDeps, {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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'],
|
|
320
322
|
__map_get: () => ctx.features.external ? ['__ext_prop', '__map_set'] : ['__map_set'],
|
|
321
|
-
__map_get_h: () => ctx.features.external ? ['__ext_prop'] : [],
|
|
322
|
-
|
|
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'],
|
|
323
326
|
__hash_set: () => ctx.features.external
|
|
324
327
|
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_set', '__dyn_set']
|
|
325
328
|
: ['__str_hash', '__str_eq', '__ptr_type', '__dyn_set'],
|
|
@@ -330,12 +333,13 @@ export default (ctx) => {
|
|
|
330
333
|
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_has']
|
|
331
334
|
: ['__str_hash', '__str_eq', '__ptr_type'],
|
|
332
335
|
__hash_new: ['__alloc_hdr'],
|
|
336
|
+
__hash_new_small: ['__alloc_hdr', '__mkptr'],
|
|
333
337
|
__hash_get_local: ['__str_hash', '__str_eq'],
|
|
334
338
|
__hash_get_local_h: ['__str_eq'],
|
|
335
339
|
__hash_set_local_h: ['__str_eq'],
|
|
336
340
|
__hash_set_local: ['__str_hash', '__str_eq'],
|
|
337
|
-
__ihash_get_local: ['
|
|
338
|
-
__ihash_set_local: ['
|
|
341
|
+
__ihash_get_local: ['__map_hash'],
|
|
342
|
+
__ihash_set_local: ['__map_hash', '__alloc_hdr', '__mkptr'],
|
|
339
343
|
__dyn_get_t: ['__ihash_get_local', '__str_hash', '__str_eq', '__is_nullish'],
|
|
340
344
|
__dyn_get: ['__dyn_get_t', '__ptr_type'],
|
|
341
345
|
__dyn_get_expr_t: ['__dyn_get_t', '__hash_get_local'],
|
|
@@ -345,7 +349,7 @@ export default (ctx) => {
|
|
|
345
349
|
? ['__dyn_get_t', '__hash_get_local', '__ext_prop']
|
|
346
350
|
: ['__dyn_get_t', '__hash_get_local'],
|
|
347
351
|
__dyn_get_or: ['__dyn_get'],
|
|
348
|
-
__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'],
|
|
349
353
|
__dyn_move: ['__ihash_get_local', '__ihash_set_local', '__is_nullish'],
|
|
350
354
|
})
|
|
351
355
|
|
|
@@ -353,6 +357,16 @@ export default (ctx) => {
|
|
|
353
357
|
|
|
354
358
|
if (!ctx.scope.globals.has('__dyn_props'))
|
|
355
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))')
|
|
356
370
|
// Schema name table for __dyn_get's OBJECT-schema fallback (polymorphic-receiver
|
|
357
371
|
// `.prop` access). Same declaration as json.js — defined here too so collection
|
|
358
372
|
// doesn't transitively require json. compile.js's schemaInit populates it when
|
|
@@ -360,17 +374,61 @@ export default (ctx) => {
|
|
|
360
374
|
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
361
375
|
ctx.scope.globals.set('__schema_tbl', '(global $__schema_tbl (mut i32) (i32.const 0))')
|
|
362
376
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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)))'
|
|
367
385
|
// Hash function: simple f64 → i32 hash
|
|
368
|
-
ctx.core.stdlib['__hash'] = `(func $__hash (param $v
|
|
386
|
+
ctx.core.stdlib['__hash'] = `(func $__hash (param $v i64) (result i32)
|
|
369
387
|
(i32.wrap_i64 (i64.xor
|
|
370
|
-
(
|
|
371
|
-
(i64.shr_u (
|
|
388
|
+
(local.get $v)
|
|
389
|
+
(i64.shr_u (local.get $v) (i64.const 32)))))`
|
|
372
390
|
inc('__hash')
|
|
373
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
|
+
|
|
374
432
|
// __map_new() → f64 — allocate empty Map (for JSON.parse, runtime creation)
|
|
375
433
|
ctx.core.stdlib['__map_new'] = `(func $__map_new (result f64)
|
|
376
434
|
(call $__mkptr (i32.const ${PTR.MAP}) (i32.const 0)
|
|
@@ -386,27 +444,27 @@ export default (ctx) => {
|
|
|
386
444
|
|
|
387
445
|
ctx.core.emit['.add'] = (setExpr, val) => {
|
|
388
446
|
inc('__set_add')
|
|
389
|
-
return typed(['call', '$__set_add',
|
|
447
|
+
return typed(['f64.reinterpret_i64', ['call', '$__set_add', asI64(emit(setExpr)), asI64(emit(val))]], 'f64')
|
|
390
448
|
}
|
|
391
449
|
|
|
392
450
|
ctx.core.emit['.has'] = (setExpr, val) => {
|
|
393
451
|
inc('__set_has')
|
|
394
|
-
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')
|
|
395
453
|
}
|
|
396
454
|
|
|
397
455
|
ctx.core.emit['.delete'] = (setExpr, val) => {
|
|
398
456
|
inc('__set_delete')
|
|
399
|
-
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')
|
|
400
458
|
}
|
|
401
459
|
|
|
402
460
|
ctx.core.emit['.size'] = (expr) => {
|
|
403
|
-
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')
|
|
404
462
|
}
|
|
405
463
|
|
|
406
464
|
// Generated Set probe functions
|
|
407
|
-
ctx.core.stdlib['__set_add'] = () => genUpsert('__set_add', SET_ENTRY, '$
|
|
408
|
-
ctx.core.stdlib['__set_has'] = () => genLookup('__set_has', SET_ENTRY, '$
|
|
409
|
-
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)
|
|
410
468
|
|
|
411
469
|
// === Map ===
|
|
412
470
|
|
|
@@ -418,7 +476,8 @@ export default (ctx) => {
|
|
|
418
476
|
|
|
419
477
|
ctx.core.emit['.set'] = (mapExpr, key, val) => {
|
|
420
478
|
inc('__map_set')
|
|
421
|
-
|
|
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')
|
|
422
481
|
}
|
|
423
482
|
ctx.core.emit[`.${VAL.MAP}:set`] = ctx.core.emit['.set']
|
|
424
483
|
|
|
@@ -426,19 +485,31 @@ export default (ctx) => {
|
|
|
426
485
|
const constKey = numConstLiteral(key)
|
|
427
486
|
if (constKey != null) {
|
|
428
487
|
inc('__map_get_h')
|
|
429
|
-
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')
|
|
430
489
|
}
|
|
431
490
|
inc('__map_get')
|
|
432
|
-
return typed(['call', '$__map_get',
|
|
491
|
+
return typed(['f64.reinterpret_i64', ['call', '$__map_get', asI64(emit(mapExpr)), asI64(emit(key))]], 'f64')
|
|
433
492
|
}
|
|
434
493
|
|
|
435
494
|
ctx.core.emit['.get'] = emitMapGet
|
|
436
495
|
ctx.core.emit[`.${VAL.MAP}:get`] = emitMapGet
|
|
437
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
|
+
|
|
438
507
|
// Generated Map probe functions
|
|
439
|
-
ctx.core.stdlib['__map_set'] = () => genUpsert('__map_set', MAP_ENTRY, '$
|
|
440
|
-
ctx.core.stdlib['__map_get'] = () => genLookup('__map_get', MAP_ENTRY, '$
|
|
441
|
-
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)
|
|
442
513
|
|
|
443
514
|
// === HASH — dynamic string-keyed object (type=7) ===
|
|
444
515
|
|
|
@@ -446,15 +517,15 @@ export default (ctx) => {
|
|
|
446
517
|
// FNV-1a. ~95M calls in watr self-host. Inline char-fetch: hoist type/offset out of the
|
|
447
518
|
// byte loop so SSO branch uses dword shifts and STRING branch uses raw load8_u — neither
|
|
448
519
|
// 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
|
|
450
|
-
(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)
|
|
451
522
|
(local.set $h (i32.const 0x811c9dc5))
|
|
452
|
-
(local.set $
|
|
453
|
-
(local.set $
|
|
454
|
-
(local.set $
|
|
455
|
-
(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)))
|
|
456
527
|
(then
|
|
457
|
-
(local.set $len (i32.
|
|
528
|
+
(local.set $len (i32.and (local.get $aux) (i32.const 7)))
|
|
458
529
|
(block $ds (loop $ls
|
|
459
530
|
(br_if $ds (i32.ge_s (local.get $i) (local.get $len)))
|
|
460
531
|
(local.set $h (i32.mul
|
|
@@ -493,14 +564,21 @@ export default (ctx) => {
|
|
|
493
564
|
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
494
565
|
(call $__alloc_hdr (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))`
|
|
495
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
|
+
|
|
496
574
|
ctx.core.stdlib['__hash_get_local'] = genLookupStrict('__hash_get_local', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH)
|
|
497
575
|
ctx.core.stdlib['__hash_get_local_h'] = genLookupStrictPrehashed('__hash_get_local_h', MAP_ENTRY, strEq, PTR.HASH)
|
|
498
576
|
ctx.core.stdlib['__hash_set_local_h'] = genUpsertStrictPrehashed('__hash_set_local_h', MAP_ENTRY, strEq, PTR.HASH)
|
|
499
577
|
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 +
|
|
502
|
-
ctx.core.stdlib['__ihash_get_local'] = genLookupStrict('__ihash_get_local', MAP_ENTRY, '$
|
|
503
|
-
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)
|
|
504
582
|
|
|
505
583
|
// Inline __ptr_offset (forwarding-aware) and __hash_get_local body — dyn_get is the
|
|
506
584
|
// single hottest stdlib symbol in watr self-host (~95M calls). props returned by
|
|
@@ -513,91 +591,184 @@ export default (ctx) => {
|
|
|
513
591
|
// time but lives at runtime in the NaN-box aux bits. Gated on schema name table
|
|
514
592
|
// presence (lifted in compile.js whenever __dyn_get is included). Static-shape
|
|
515
593
|
// monomorphic OBJECTs hit the compile-time slot read path and never reach here.
|
|
516
|
-
|
|
517
|
-
|
|
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')) ? `
|
|
518
615
|
(if (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
519
616
|
(then
|
|
520
617
|
(if (i32.ne (global.get $__schema_tbl) (i32.const 0))
|
|
521
618
|
(then
|
|
522
619
|
(local.set $sid (i32.wrap_i64 (i64.and (i64.shr_u
|
|
523
|
-
(
|
|
524
|
-
(local.set $kbits
|
|
525
|
-
(
|
|
526
|
-
(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}))))
|
|
527
624
|
(local.set $nkeys (i32.load (i32.sub (local.get $koff) (i32.const 8))))
|
|
528
625
|
(local.set $idx (i32.const 0))
|
|
529
626
|
(block $kdone (loop $kloop
|
|
530
627
|
(br_if $kdone (i32.ge_s (local.get $idx) (local.get $nkeys)))
|
|
531
|
-
(if (
|
|
532
|
-
|
|
533
|
-
(local.get $key))
|
|
534
|
-
(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)))))))
|
|
535
630
|
(local.set $idx (i32.add (local.get $idx) (i32.const 1)))
|
|
536
631
|
(br $kloop)))))))` : ''
|
|
537
|
-
const
|
|
632
|
+
const buildObjectSchemaLocals = () => (ctx.schema.list.length > 0 || ctx.core.includes.has('__jp_obj'))
|
|
538
633
|
? '(local $sid i32) (local $kbits i64) (local $koff i32) (local $nkeys i32)'
|
|
539
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)))))` : ''
|
|
540
665
|
|
|
541
|
-
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)
|
|
542
667
|
(call $__dyn_get_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
543
668
|
|
|
544
|
-
ctx.core.stdlib['__dyn_get_t'] = `(func $__dyn_get_t (param $obj
|
|
545
|
-
(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)
|
|
546
671
|
(local $poff i32) (local $pcap i32) (local $h i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
547
|
-
${
|
|
548
|
-
(local.set $
|
|
549
|
-
(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}))))
|
|
550
674
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
551
675
|
(then
|
|
552
676
|
(block $done
|
|
553
677
|
(loop $follow
|
|
554
|
-
(br_if $done (i32.lt_u (local.get $off) (i32.const
|
|
678
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 16)))
|
|
555
679
|
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
556
680
|
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
557
681
|
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
558
682
|
(br $follow)))))
|
|
559
683
|
(block $dynDone
|
|
560
|
-
(
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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}))))
|
|
566
737
|
(local.set $pcap (i32.load (i32.sub (local.get $poff) (i32.const 4))))
|
|
567
738
|
(local.set $h (call $__str_hash (local.get $key)))
|
|
568
739
|
(local.set $idx (i32.and (local.get $h) (i32.sub (local.get $pcap) (i32.const 1))))
|
|
569
740
|
(block $hdone (loop $hprobe
|
|
570
741
|
(local.set $slot (i32.add (local.get $poff) (i32.mul (local.get $idx) (i32.const ${MAP_ENTRY}))))
|
|
571
|
-
(br_if $dynDone (
|
|
572
|
-
(if (call $__str_eq (
|
|
573
|
-
(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))))))
|
|
574
745
|
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.sub (local.get $pcap) (i32.const 1))))
|
|
575
746
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
576
747
|
(br_if $hdone (i32.ge_s (local.get $tries) (local.get $pcap)))
|
|
577
|
-
(br $hprobe))))${
|
|
578
|
-
(
|
|
748
|
+
(br $hprobe))))${buildObjectSchemaArm()}
|
|
749
|
+
(i64.const ${UNDEF_NAN}))`
|
|
579
750
|
|
|
580
|
-
ctx.core.stdlib['__dyn_get_or'] = `(func $__dyn_get_or (param $obj
|
|
581
|
-
(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)
|
|
582
753
|
(local.set $val (call $__dyn_get (local.get $obj) (local.get $key)))
|
|
583
|
-
(if (result
|
|
584
|
-
(i64.eq (
|
|
754
|
+
(if (result i64)
|
|
755
|
+
(i64.eq (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
585
756
|
(then (local.get $fallback))
|
|
586
757
|
(else (local.get $val))))`
|
|
587
758
|
|
|
588
|
-
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)
|
|
589
760
|
(call $__dyn_get_expr_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
590
761
|
|
|
591
|
-
ctx.core.stdlib['__dyn_get_expr_t'] = `(func $__dyn_get_expr_t (param $obj
|
|
592
|
-
(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)
|
|
593
764
|
(local.set $val (call $__dyn_get_t (local.get $obj) (local.get $key) (local.get $t)))
|
|
594
|
-
(if (result
|
|
595
|
-
(i64.ne (
|
|
765
|
+
(if (result i64)
|
|
766
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
596
767
|
(then (local.get $val))
|
|
597
768
|
(else
|
|
598
|
-
(if (result
|
|
769
|
+
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
599
770
|
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
600
|
-
(else (
|
|
771
|
+
(else (i64.const ${NULL_NAN}))))))`
|
|
601
772
|
|
|
602
773
|
// Like __dyn_get_expr but also resolves EXTERNAL host objects via __ext_prop.
|
|
603
774
|
// Used at call sites where receiver type is statically unknown.
|
|
@@ -607,24 +778,24 @@ export default (ctx) => {
|
|
|
607
778
|
// dyn_props (those are for OBJECT/ARRAY attached props), so the original __dyn_get
|
|
608
779
|
// call was always wasted work on hashes — and JSON.parse / Map-style code is the
|
|
609
780
|
// dominant HASH consumer.
|
|
610
|
-
return `(func $__dyn_get_any (param $obj
|
|
781
|
+
return `(func $__dyn_get_any (param $obj i64) (param $key i64) (result i64)
|
|
611
782
|
(call $__dyn_get_any_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
612
783
|
}
|
|
613
784
|
|
|
614
785
|
ctx.core.stdlib['__dyn_get_any_t'] = () => {
|
|
615
786
|
const extArm = ctx.features.external
|
|
616
|
-
? `(if (result
|
|
787
|
+
? `(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.EXTERNAL}))
|
|
617
788
|
(then (call $__ext_prop (local.get $obj) (local.get $key)))
|
|
618
|
-
(else (
|
|
619
|
-
: `(
|
|
620
|
-
return `(func $__dyn_get_any_t (param $obj
|
|
621
|
-
(local $val
|
|
622
|
-
(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}))
|
|
623
794
|
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
624
795
|
(else
|
|
625
796
|
(local.set $val (call $__dyn_get_t (local.get $obj) (local.get $key) (local.get $t)))
|
|
626
|
-
(if (result
|
|
627
|
-
(i64.ne (
|
|
797
|
+
(if (result i64)
|
|
798
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
628
799
|
(then (local.get $val))
|
|
629
800
|
(else ${extArm})))))`
|
|
630
801
|
}
|
|
@@ -633,45 +804,101 @@ export default (ctx) => {
|
|
|
633
804
|
// Defer the root insert to the end and gate it on props-ptr change: most calls hit
|
|
634
805
|
// the no-grow case where the ptr is unchanged and the root slot already points to it.
|
|
635
806
|
// __ptr_offset inlined (forwarding-aware) — only ARRAY ever has forwarding.
|
|
636
|
-
ctx.core.stdlib['__dyn_set'] = `(func $__dyn_set (param $obj
|
|
637
|
-
(local $root
|
|
638
|
-
(local $
|
|
639
|
-
(local.set $
|
|
640
|
-
(
|
|
641
|
-
|
|
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}))
|
|
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}))
|
|
647
813
|
(then
|
|
648
814
|
(block $done
|
|
649
815
|
(loop $follow
|
|
650
|
-
(br_if $done (i32.lt_u (local.get $off) (i32.const
|
|
816
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 16)))
|
|
651
817
|
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
652
818
|
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
653
819
|
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
654
820
|
(br $follow)))))
|
|
655
|
-
|
|
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))))
|
|
656
881
|
(local.set $oldProps (call $__ihash_get_local (local.get $root) (local.get $objKey)))
|
|
657
882
|
(local.set $props
|
|
658
|
-
(if (result
|
|
659
|
-
(then (call $
|
|
883
|
+
(if (result i64) (call $__is_nullish (local.get $oldProps))
|
|
884
|
+
(then (i64.reinterpret_f64 (call $__hash_new_small)))
|
|
660
885
|
(else (local.get $oldProps))))
|
|
661
886
|
(local.set $props (call $__hash_set_local (local.get $props) (local.get $key) (local.get $val)))
|
|
662
|
-
(if (i64.ne (
|
|
887
|
+
(if (i64.ne (local.get $props) (local.get $oldProps))
|
|
663
888
|
(then
|
|
664
889
|
(local.set $root (call $__ihash_set_local (local.get $root) (local.get $objKey) (local.get $props)))
|
|
665
|
-
(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)))))))
|
|
666
893
|
(local.get $val))`
|
|
667
894
|
|
|
668
895
|
ctx.core.stdlib['__dyn_move'] = `(func $__dyn_move (param $oldOff i32) (param $newOff i32)
|
|
669
|
-
(local $props
|
|
896
|
+
(local $props i64) (local $root i64)
|
|
670
897
|
(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))))
|
|
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)))))
|
|
672
899
|
(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)))`
|
|
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))))`
|
|
675
902
|
|
|
676
903
|
// Generated HASH probe functions
|
|
677
904
|
ctx.core.stdlib['__hash_set'] = () => genUpsertGrow('__hash_set', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, false, ctx.features.external)
|
|
@@ -702,10 +929,8 @@ export default (ctx) => {
|
|
|
702
929
|
const objVal = ['local.get', `$${objTmp}`]
|
|
703
930
|
const idxVal = ['local.get', `$${idxTmp}`]
|
|
704
931
|
const typeVal = ['local.get', `$${typeTmp}`]
|
|
705
|
-
const isStringKey = ['call', '$__is_str_key', keyVal]
|
|
706
|
-
const isStringLike = ['i32.
|
|
707
|
-
['i32.eq', typeVal, ['i32.const', PTR.STRING]],
|
|
708
|
-
['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]]
|
|
709
934
|
const isArrayLike = ['i32.or',
|
|
710
935
|
['i32.eq', typeVal, ['i32.const', PTR.ARRAY]],
|
|
711
936
|
['i32.eq', typeVal, ['i32.const', PTR.TYPED]]]
|
|
@@ -716,9 +941,7 @@ export default (ctx) => {
|
|
|
716
941
|
['i32.eq', typeVal, ['i32.const', PTR.ARRAY]],
|
|
717
942
|
['i32.eq', typeVal, ['i32.const', PTR.TYPED]]],
|
|
718
943
|
['i32.or',
|
|
719
|
-
['i32.
|
|
720
|
-
['i32.eq', typeVal, ['i32.const', PTR.STRING]],
|
|
721
|
-
['i32.eq', typeVal, ['i32.const', PTR.SSO]]],
|
|
944
|
+
['i32.eq', typeVal, ['i32.const', PTR.STRING]],
|
|
722
945
|
['i32.or',
|
|
723
946
|
['i32.or',
|
|
724
947
|
['i32.eq', typeVal, ['i32.const', PTR.SET]],
|
|
@@ -732,7 +955,7 @@ export default (ctx) => {
|
|
|
732
955
|
['local.set', `$${objTmp}`, asF64(emit(obj))],
|
|
733
956
|
['local.set', `$${keyTmp}`, asF64(emit(key))],
|
|
734
957
|
['local.set', `$${outTmp}`, ['i32.const', 0]],
|
|
735
|
-
['local.set', `$${typeTmp}`, ['call', '$__ptr_type', objVal]],
|
|
958
|
+
['local.set', `$${typeTmp}`, ['call', '$__ptr_type', ['i64.reinterpret_f64', objVal]]],
|
|
736
959
|
['local.set', `$${idxTmp}`, ['i32.trunc_sat_f64_s', keyVal]],
|
|
737
960
|
|
|
738
961
|
['if', ['i32.and',
|
|
@@ -742,52 +965,72 @@ export default (ctx) => {
|
|
|
742
965
|
['i32.ge_s', idxVal, ['i32.const', 0]]]],
|
|
743
966
|
['then',
|
|
744
967
|
['if', isStringLike,
|
|
745
|
-
['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]]]]]],
|
|
746
969
|
['if', isArrayLike,
|
|
747
|
-
['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]]]]]]]],
|
|
748
971
|
|
|
749
972
|
['if', isStringKey,
|
|
750
973
|
['then',
|
|
751
974
|
['if', hasDynProps,
|
|
752
975
|
['then', ['local.set', `$${outTmp}`,
|
|
753
|
-
['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]]]]]]]]],
|
|
754
977
|
|
|
755
978
|
['if', ['i32.eq', typeVal, ['i32.const', PTR.HASH]],
|
|
756
979
|
['then', ['local.set', `$${outTmp}`,
|
|
757
980
|
['if', ['result', 'i32'], isStringKey,
|
|
758
|
-
['then', ['call', '$__hash_has', objVal, keyVal]],
|
|
759
|
-
['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]]]]]]]],
|
|
760
983
|
|
|
761
984
|
...(ctx.features.external ? [['if', ['i32.eq', typeVal, ['i32.const', PTR.EXTERNAL]],
|
|
762
|
-
['then', ['local.set', `$${outTmp}`, ['call', '$__ext_has',
|
|
985
|
+
['then', ['local.set', `$${outTmp}`, ['call', '$__ext_has',
|
|
986
|
+
['i64.reinterpret_f64', objVal], ['i64.reinterpret_f64', keyVal]]]]]] : []),
|
|
763
987
|
|
|
764
988
|
['local.get', `$${outTmp}`]], 'i32')
|
|
765
989
|
}
|
|
766
990
|
|
|
767
991
|
// === for...in on dynamic objects (HASH iteration) ===
|
|
768
992
|
|
|
769
|
-
// 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.
|
|
770
996
|
ctx.core.emit['for-in'] = (varName, src, body) => {
|
|
771
997
|
const off = tempI32('ho'), cap = tempI32('hc')
|
|
772
998
|
const i = tempI32('hi'), slot = tempI32('hs')
|
|
999
|
+
const ptrI64 = tempI64('hp'), srcOff = tempI32('hso'), srcType = tempI32('hst')
|
|
773
1000
|
if (!ctx.func.locals.has(varName)) ctx.func.locals.set(varName, 'f64')
|
|
774
1001
|
const id = ctx.func.uniq++
|
|
775
1002
|
const va = asF64(emit(src))
|
|
776
1003
|
const bodyFlat = emitFlat(body)
|
|
1004
|
+
inc('__ptr_type')
|
|
777
1005
|
return [
|
|
778
|
-
|
|
779
|
-
['local.set', `$${
|
|
780
|
-
['local.set', `$${
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
['
|
|
784
|
-
['
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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}`]]]]]
|
|
791
1034
|
]
|
|
792
1035
|
}
|
|
793
1036
|
}
|