jz 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -26
- package/cli.js +53 -8
- package/index.js +14 -1
- package/module/array.js +66 -9
- package/module/collection.js +68 -35
- package/module/core.js +65 -19
- package/module/date.js +5 -0
- package/module/function.js +2 -1
- package/module/json.js +66 -8
- package/module/number.js +127 -5
- package/module/object.js +88 -1
- package/module/string.js +3 -2
- package/module/typedarray.js +7 -1
- package/package.json +3 -3
- package/src/analyze.js +16 -4
- package/src/assemble.js +544 -0
- package/src/ast.js +160 -0
- package/src/auto-config.js +120 -0
- package/src/autoload.js +4 -1
- package/src/compile.js +22 -620
- package/src/ctx.js +33 -5
- package/src/emit.js +73 -165
- package/src/host.js +12 -0
- package/src/ir.js +13 -0
- package/src/jzify.js +348 -9
- package/src/narrow.js +2 -1
- package/src/optimize.js +298 -24
- package/src/plan.js +220 -34
- package/src/prepare.js +22 -2
- package/src/vectorize.js +3 -12
package/module/collection.js
CHANGED
|
@@ -44,6 +44,16 @@ function numConstLiteral(expr) {
|
|
|
44
44
|
const sameValueZeroEq = '(call $__same_value_zero (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
45
45
|
const strEq = '(call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
46
46
|
|
|
47
|
+
// Open-addressing probe walked additively by entrySize: avoids an i32.mul + mask per
|
|
48
|
+
// step (vs recomputing slot = off + idx*entrySize). Needs $off/$cap/$h set and $end/$slot
|
|
49
|
+
// locals declared. `idxExpr` is the first-slot index (defaults to h mod cap; cap is pow2).
|
|
50
|
+
const probeStart = (entrySize, idxExpr = '(i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1)))') =>
|
|
51
|
+
`(local.set $end (i32.add (local.get $off) (i32.mul (local.get $cap) (i32.const ${entrySize}))))
|
|
52
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul ${idxExpr} (i32.const ${entrySize}))))`
|
|
53
|
+
const probeNext = (entrySize) =>
|
|
54
|
+
`(local.set $slot (i32.add (local.get $slot) (i32.const ${entrySize})))
|
|
55
|
+
(if (i32.ge_u (local.get $slot) (local.get $end)) (then (local.set $slot (local.get $off))))`
|
|
56
|
+
|
|
47
57
|
/** Generate upsert (add/set) probe function. hasVal: store value at slot+16.
|
|
48
58
|
* hasExt: emit EXTERNAL fallthrough (call $__ext_set on non-matching type).
|
|
49
59
|
* Gated off → type mismatch just returns coll unchanged. */
|
|
@@ -62,14 +72,13 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
62
72
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL})) ${extBranch}) (return (local.get $coll))))`
|
|
63
73
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (return (local.get $coll))))`
|
|
64
74
|
return `(func $${name} (param $coll i64) (param $key i64) ${valParam}(result i64)
|
|
65
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
75
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32)
|
|
66
76
|
${typeGuard}
|
|
67
77
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
68
78
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
69
79
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
70
|
-
|
|
80
|
+
${probeStart(entrySize)}
|
|
71
81
|
(block $done (loop $probe
|
|
72
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
73
82
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
74
83
|
(then
|
|
75
84
|
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
@@ -78,7 +87,7 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
78
87
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
79
88
|
(br $done)))
|
|
80
89
|
(if ${eqExpr} ${onMatch})
|
|
81
|
-
|
|
90
|
+
${probeNext(entrySize)}
|
|
82
91
|
(br $probe)))
|
|
83
92
|
(local.get $coll))`
|
|
84
93
|
}
|
|
@@ -108,17 +117,16 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
108
117
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then ${onEmpty}))`
|
|
109
118
|
|
|
110
119
|
return `(func $${name} (param $coll i64) (param $key i64) (result ${rt})
|
|
111
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
120
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
112
121
|
${typeGuard}
|
|
113
122
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
114
123
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
115
124
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
116
|
-
|
|
125
|
+
${probeStart(entrySize)}
|
|
117
126
|
(block $done (loop $probe
|
|
118
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
119
127
|
(if (i64.eqz (i64.load (local.get $slot))) (then ${onEmpty}))
|
|
120
128
|
(if ${eqExpr} (then ${onFound}))
|
|
121
|
-
|
|
129
|
+
${probeNext(entrySize)}
|
|
122
130
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
123
131
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
124
132
|
(br $probe)))
|
|
@@ -128,21 +136,20 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
128
136
|
/** Generate delete probe function. Zero out entry on match. */
|
|
129
137
|
function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
130
138
|
return `(func $${name} (param $coll i64) (param $key i64) (result i32)
|
|
131
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
139
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
132
140
|
(if (i32.ne (call $__ptr_type (local.get $coll)) (i32.const ${expectedType})) (then (return (i32.const 0))))
|
|
133
141
|
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
134
142
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
135
143
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
136
|
-
|
|
144
|
+
${probeStart(entrySize)}
|
|
137
145
|
(block $done (loop $probe
|
|
138
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
139
146
|
(if (i64.eqz (i64.load (local.get $slot))) (then (return (i32.const 0))))
|
|
140
147
|
(if ${eqExpr}
|
|
141
148
|
(then
|
|
142
149
|
(i64.store (local.get $slot) (i64.const 0))
|
|
143
150
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (i64.const 0))
|
|
144
151
|
(return (i32.const 1))))
|
|
145
|
-
|
|
152
|
+
${probeNext(entrySize)}
|
|
146
153
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
147
154
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
148
155
|
(br $probe)))
|
|
@@ -168,7 +175,7 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
168
175
|
${nonHashFallback}
|
|
169
176
|
(return (local.get $obj))))`
|
|
170
177
|
return `(func $${name} (param $obj i64) (param $key i64) (param $val i64) (result i64)
|
|
171
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
178
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32)
|
|
172
179
|
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
173
180
|
(local $oldslot i32) (local $newidx i32) (local $newslot i32)
|
|
174
181
|
${typeGuard}
|
|
@@ -205,9 +212,8 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
205
212
|
(local.set $obj (i64.reinterpret_f64 (call $__mkptr (i32.const ${typeConst}) (i32.const 0) (local.get $newptr))))))
|
|
206
213
|
;; Insert/update
|
|
207
214
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
208
|
-
|
|
215
|
+
${probeStart(entrySize)}
|
|
209
216
|
(block $done (loop $probe
|
|
210
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
211
217
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
212
218
|
(then
|
|
213
219
|
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
@@ -220,14 +226,14 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
220
226
|
(then
|
|
221
227
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
222
228
|
(br $done)))
|
|
223
|
-
|
|
229
|
+
${probeNext(entrySize)}
|
|
224
230
|
(br $probe)))
|
|
225
231
|
(local.get $obj))`
|
|
226
232
|
}
|
|
227
233
|
|
|
228
234
|
function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing = UNDEF_NAN) {
|
|
229
235
|
return `(func $${name} (param $coll i64) (param $key i64) (result i64)
|
|
230
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
236
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
231
237
|
(if (i32.ne
|
|
232
238
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
233
239
|
(i32.const ${expectedType}))
|
|
@@ -235,14 +241,13 @@ function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing
|
|
|
235
241
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
236
242
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
237
243
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
238
|
-
|
|
244
|
+
${probeStart(entrySize)}
|
|
239
245
|
(block $done (loop $probe
|
|
240
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
241
246
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
242
247
|
(then (return (i64.const ${missing}))))
|
|
243
248
|
(if ${eqExpr}
|
|
244
249
|
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
245
|
-
|
|
250
|
+
${probeNext(entrySize)}
|
|
246
251
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
247
252
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
248
253
|
(br $probe)))
|
|
@@ -260,18 +265,17 @@ function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing
|
|
|
260
265
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
261
266
|
(then (return (i64.const ${missing}))))`
|
|
262
267
|
return `(func $${name} (param $coll i64) (param $key i64) (param $h i32) (result i64)
|
|
263
|
-
(local $off i32) (local $cap i32) (local $
|
|
268
|
+
(local $off i32) (local $cap i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
264
269
|
${typeGuard}
|
|
265
270
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
266
271
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
267
|
-
|
|
272
|
+
${probeStart(entrySize)}
|
|
268
273
|
(block $done (loop $probe
|
|
269
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
270
274
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
271
275
|
(then (return (i64.const ${missing}))))
|
|
272
276
|
(if ${eqExpr}
|
|
273
277
|
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
274
|
-
|
|
278
|
+
${probeNext(entrySize)}
|
|
275
279
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
276
280
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
277
281
|
(br $probe)))
|
|
@@ -280,16 +284,15 @@ function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing
|
|
|
280
284
|
|
|
281
285
|
function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
282
286
|
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 $
|
|
287
|
+
(local $off i32) (local $cap i32) (local $end i32) (local $slot i32)
|
|
284
288
|
(if (i32.ne
|
|
285
289
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
286
290
|
(i32.const ${expectedType}))
|
|
287
291
|
(then (return (local.get $obj))))
|
|
288
292
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
289
293
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
290
|
-
|
|
294
|
+
${probeStart(entrySize)}
|
|
291
295
|
(block $done (loop $probe
|
|
292
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
293
296
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
294
297
|
(then
|
|
295
298
|
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
@@ -302,7 +305,7 @@ function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
|
302
305
|
(then
|
|
303
306
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
304
307
|
(br $done)))
|
|
305
|
-
|
|
308
|
+
${probeNext(entrySize)}
|
|
306
309
|
(br $probe)))
|
|
307
310
|
(local.get $obj))`
|
|
308
311
|
}
|
|
@@ -340,9 +343,11 @@ export default (ctx) => {
|
|
|
340
343
|
__hash_set_local: ['__str_hash', '__str_eq'],
|
|
341
344
|
__ihash_get_local: ['__map_hash'],
|
|
342
345
|
__ihash_set_local: ['__map_hash', '__alloc_hdr', '__mkptr'],
|
|
343
|
-
__dyn_get_t: ['
|
|
346
|
+
__dyn_get_t: ['__dyn_get_t_h', '__str_hash'],
|
|
347
|
+
__dyn_get_t_h: ['__ihash_get_local', '__str_eq', '__is_nullish'],
|
|
344
348
|
__dyn_get: ['__dyn_get_t', '__ptr_type'],
|
|
345
349
|
__dyn_get_expr_t: ['__dyn_get_t', '__hash_get_local'],
|
|
350
|
+
__dyn_get_expr_t_h: ['__dyn_get_t_h', '__hash_get_local_h'],
|
|
346
351
|
__dyn_get_expr: ['__dyn_get_expr_t', '__ptr_type'],
|
|
347
352
|
__dyn_get_any: ['__dyn_get_any_t', '__ptr_type'],
|
|
348
353
|
__dyn_get_any_t: () => ctx.features.external
|
|
@@ -666,11 +671,22 @@ export default (ctx) => {
|
|
|
666
671
|
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj i64) (param $key i64) (result i64)
|
|
667
672
|
(call $__dyn_get_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
668
673
|
|
|
669
|
-
|
|
674
|
+
// Thin wrapper: hash the key once, delegate to the prehashed body. Constant-key
|
|
675
|
+
// call sites bypass this and call $__dyn_get_t_h directly with strHashLiteral().
|
|
676
|
+
ctx.core.stdlib['__dyn_get_t'] = `(func $__dyn_get_t (param $obj i64) (param $key i64) (param $type i32) (result i64)
|
|
677
|
+
(call $__dyn_get_t_h (local.get $obj) (local.get $key) (local.get $type) (call $__str_hash (local.get $key))))`
|
|
678
|
+
|
|
679
|
+
ctx.core.stdlib['__dyn_get_t_h'] = () => `(func $__dyn_get_t_h (param $obj i64) (param $key i64) (param $type i32) (param $h i32) (result i64)
|
|
670
680
|
(local $props i64) (local $off i32)
|
|
671
|
-
(local $poff i32) (local $pcap i32) (local $
|
|
681
|
+
(local $poff i32) (local $pcap i32) (local $pend i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
672
682
|
${buildObjectSchemaLocals()}
|
|
673
683
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
684
|
+
;; CLOSURE with no env (offset 0): many function refs share offset 0, so key the
|
|
685
|
+
;; global __dyn_props hash on the function table index (negative — can't collide
|
|
686
|
+
;; with real heap/data offsets). Closures *with* env keep their unique env ptr.
|
|
687
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.CLOSURE})) (i32.eqz (local.get $off)))
|
|
688
|
+
(then (local.set $off (i32.sub (i32.const -1)
|
|
689
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK})))))))
|
|
674
690
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
675
691
|
(then
|
|
676
692
|
(block $done
|
|
@@ -735,14 +751,14 @@ export default (ctx) => {
|
|
|
735
751
|
(global.set $__dyn_get_cache_props (f64.reinterpret_i64 (local.get $props))))))))
|
|
736
752
|
(local.set $poff (i32.wrap_i64 (i64.and (local.get $props) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
737
753
|
(local.set $pcap (i32.load (i32.sub (local.get $poff) (i32.const 4))))
|
|
738
|
-
(local.set $
|
|
739
|
-
(local.set $
|
|
754
|
+
(local.set $pend (i32.add (local.get $poff) (i32.mul (local.get $pcap) (i32.const ${MAP_ENTRY}))))
|
|
755
|
+
(local.set $slot (i32.add (local.get $poff) (i32.mul (i32.and (local.get $h) (i32.sub (local.get $pcap) (i32.const 1))) (i32.const ${MAP_ENTRY}))))
|
|
740
756
|
(block $hdone (loop $hprobe
|
|
741
|
-
(local.set $slot (i32.add (local.get $poff) (i32.mul (local.get $idx) (i32.const ${MAP_ENTRY}))))
|
|
742
757
|
(br_if $dynDone (i64.eqz (i64.load (local.get $slot))))
|
|
743
758
|
(if (call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))
|
|
744
759
|
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
745
|
-
(local.set $
|
|
760
|
+
(local.set $slot (i32.add (local.get $slot) (i32.const ${MAP_ENTRY})))
|
|
761
|
+
(if (i32.ge_u (local.get $slot) (local.get $pend)) (then (local.set $slot (local.get $poff))))
|
|
746
762
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
747
763
|
(br_if $hdone (i32.ge_s (local.get $tries) (local.get $pcap)))
|
|
748
764
|
(br $hprobe))))${buildObjectSchemaArm()}
|
|
@@ -770,6 +786,19 @@ export default (ctx) => {
|
|
|
770
786
|
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
771
787
|
(else (i64.const ${NULL_NAN}))))))`
|
|
772
788
|
|
|
789
|
+
// Prehashed variant of __dyn_get_expr_t for constant string keys: the FNV hash
|
|
790
|
+
// is folded at compile time (strHashLiteral), so no __str_hash call at runtime.
|
|
791
|
+
ctx.core.stdlib['__dyn_get_expr_t_h'] = `(func $__dyn_get_expr_t_h (param $obj i64) (param $key i64) (param $t i32) (param $h i32) (result i64)
|
|
792
|
+
(local $val i64)
|
|
793
|
+
(local.set $val (call $__dyn_get_t_h (local.get $obj) (local.get $key) (local.get $t) (local.get $h)))
|
|
794
|
+
(if (result i64)
|
|
795
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
796
|
+
(then (local.get $val))
|
|
797
|
+
(else
|
|
798
|
+
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
799
|
+
(then (call $__hash_get_local_h (local.get $obj) (local.get $key) (local.get $h)))
|
|
800
|
+
(else (i64.const ${NULL_NAN}))))))`
|
|
801
|
+
|
|
773
802
|
// Like __dyn_get_expr but also resolves EXTERNAL host objects via __ext_prop.
|
|
774
803
|
// Used at call sites where receiver type is statically unknown.
|
|
775
804
|
// When features.external is off, collapses to __dyn_get_expr shape (no EXTERNAL probe).
|
|
@@ -809,6 +838,10 @@ export default (ctx) => {
|
|
|
809
838
|
(local $off i32) (local $type i32) ${buildObjectSchemaSetLocals()}
|
|
810
839
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
811
840
|
(local.set $type (i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
841
|
+
;; CLOSURE with no env (offset 0): key __dyn_props on the function table index — see __dyn_get_t.
|
|
842
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.CLOSURE})) (i32.eqz (local.get $off)))
|
|
843
|
+
(then (local.set $off (i32.sub (i32.const -1)
|
|
844
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK})))))))
|
|
812
845
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
813
846
|
(then
|
|
814
847
|
(block $done
|
package/module/core.js
CHANGED
|
@@ -173,16 +173,25 @@ export default (ctx) => {
|
|
|
173
173
|
} else {
|
|
174
174
|
// Own memory: heap offset in a global, auto-grow when needed
|
|
175
175
|
ctx.scope.globals.set('__heap', '(global $__heap (mut i32) (i32.const 1024))')
|
|
176
|
+
// Bump allocator with geometric growth. Growing one page at a time turns a
|
|
177
|
+
// long-running embedding (e.g. watr called thousands of times) into O(n²) —
|
|
178
|
+
// each memory.grow may relocate and copy the whole heap. So when we must
|
|
179
|
+
// grow, request at least the current size (≥2× total) in one shot; only on
|
|
180
|
+
// hitting the declared maximum do we fall back to the bare minimum.
|
|
176
181
|
ctx.core.stdlib['__alloc'] = `(func $__alloc (param $bytes i32) (result i32)
|
|
177
|
-
(local $ptr i32) (local $next i32)
|
|
182
|
+
(local $ptr i32) (local $next i32) (local $cur i32) (local $need i32)
|
|
178
183
|
(local.set $ptr (global.get $__heap))
|
|
179
184
|
;; Align next allocation to 8 bytes
|
|
180
185
|
(local.set $next (i32.and (i32.add (i32.add (local.get $ptr) (local.get $bytes)) (i32.const 7)) (i32.const -8)))
|
|
181
|
-
|
|
182
|
-
(if (i32.gt_u (local.get $next) (
|
|
183
|
-
(then
|
|
184
|
-
(i32.shr_u (i32.add (i32.sub (local.get $next) (
|
|
185
|
-
(i32.
|
|
186
|
+
(local.set $cur (i32.shl (memory.size) (i32.const 16)))
|
|
187
|
+
(if (i32.gt_u (local.get $next) (local.get $cur))
|
|
188
|
+
(then
|
|
189
|
+
(local.set $need (i32.shr_u (i32.add (i32.sub (local.get $next) (local.get $cur)) (i32.const 65535)) (i32.const 16)))
|
|
190
|
+
(if (i32.lt_u (local.get $need) (memory.size)) (then (local.set $need (memory.size))))
|
|
191
|
+
(if (i32.eq (memory.grow (local.get $need)) (i32.const -1))
|
|
192
|
+
(then (if (i32.eq (memory.grow
|
|
193
|
+
(i32.shr_u (i32.add (i32.sub (local.get $next) (local.get $cur)) (i32.const 65535)) (i32.const 16)))
|
|
194
|
+
(i32.const -1)) (then (unreachable)))))))
|
|
186
195
|
(global.set $__heap (local.get $next))
|
|
187
196
|
(local.get $ptr))`
|
|
188
197
|
ctx.core.stdlib['__clear'] = `(func $__clear
|
|
@@ -404,9 +413,15 @@ export default (ctx) => {
|
|
|
404
413
|
return ['call', '$__ptr_type', receiver]
|
|
405
414
|
}
|
|
406
415
|
|
|
407
|
-
function emitDynGetExprTyped(base, key, vt) {
|
|
408
|
-
inc('__dyn_get_expr_t')
|
|
416
|
+
function emitDynGetExprTyped(base, key, vt, prop) {
|
|
409
417
|
const receiver = asI64(base?.type ? base : typed(base, 'f64'))
|
|
418
|
+
// Constant string key: fold the FNV hash at compile time and call the
|
|
419
|
+
// prehashed body — no __str_hash on every access.
|
|
420
|
+
if (typeof prop === 'string') {
|
|
421
|
+
inc('__dyn_get_expr_t_h')
|
|
422
|
+
return typed(['f64.reinterpret_i64', ['call', '$__dyn_get_expr_t_h', receiver, key, emitTypeTag(receiver, vt), ['i32.const', strHashLiteral(prop)]]], 'f64')
|
|
423
|
+
}
|
|
424
|
+
inc('__dyn_get_expr_t')
|
|
410
425
|
return typed(['f64.reinterpret_i64', ['call', '$__dyn_get_expr_t', receiver, key, emitTypeTag(receiver, vt)]], 'f64')
|
|
411
426
|
}
|
|
412
427
|
|
|
@@ -486,7 +501,7 @@ export default (ctx) => {
|
|
|
486
501
|
if (typeof obj === 'string') {
|
|
487
502
|
const vt = lookupValType(obj)
|
|
488
503
|
if (usesDynProps(vt)) {
|
|
489
|
-
return emitDynGetExprTyped(va, key, vt)
|
|
504
|
+
return emitDynGetExprTyped(va, key, vt, prop)
|
|
490
505
|
}
|
|
491
506
|
if (vt === VAL.HASH) {
|
|
492
507
|
return emitHashGetLocalConst(va, key, prop)
|
|
@@ -495,12 +510,12 @@ export default (ctx) => {
|
|
|
495
510
|
// at off-16 (set by __dyn_set). __hash_get assumes HASH bucket layout
|
|
496
511
|
// and would mis-read OBJECT memory.
|
|
497
512
|
if (vt === VAL.OBJECT) {
|
|
498
|
-
return emitDynGetExprTyped(va, key, vt)
|
|
513
|
+
return emitDynGetExprTyped(va, key, vt, prop)
|
|
499
514
|
}
|
|
500
515
|
if (vt == null) {
|
|
501
516
|
// In WASI mode, values are always JSON-derived (never PTR.EXTERNAL host objects).
|
|
502
517
|
// Skip the external branch and dispatch through the typed HASH/OBJECT path.
|
|
503
|
-
if (ctx.transform.host === 'wasi') return emitDynGetExprTyped(va, key, vt)
|
|
518
|
+
if (ctx.transform.host === 'wasi') return emitDynGetExprTyped(va, key, vt, prop)
|
|
504
519
|
ctx.features.external = true
|
|
505
520
|
return emitDynGetAnyTyped(va, key, vt)
|
|
506
521
|
}
|
|
@@ -598,7 +613,8 @@ export default (ctx) => {
|
|
|
598
613
|
|
|
599
614
|
// Module-registered property emitter (.size, etc.)
|
|
600
615
|
const propKey = `.${prop}`
|
|
601
|
-
|
|
616
|
+
const propEmitter = ctx.core.emit[propKey]
|
|
617
|
+
if (propEmitter && propEmitter.length <= 1) return propEmitter(obj)
|
|
602
618
|
|
|
603
619
|
return emitPropAccess(emit(obj), obj, prop)
|
|
604
620
|
}
|
|
@@ -618,7 +634,7 @@ export default (ctx) => {
|
|
|
618
634
|
if (typeof obj === 'string') {
|
|
619
635
|
const objType = lookupValType(obj)
|
|
620
636
|
if (usesDynProps(objType)) {
|
|
621
|
-
access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
|
|
637
|
+
access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType, prop)
|
|
622
638
|
} else if (objType === VAL.HASH) {
|
|
623
639
|
access = emitHashGetLocalConst(['local.get', `$${t}`], asI64(emit(['str', prop])), prop)
|
|
624
640
|
} else if (objType == null) {
|
|
@@ -626,7 +642,7 @@ export default (ctx) => {
|
|
|
626
642
|
// In JS host mode use __dyn_get_any_t but don't force features.external here
|
|
627
643
|
// since ?.prop short-circuits on nullish (EXTERNAL arm is dead unless already on).
|
|
628
644
|
access = ctx.transform.host === 'wasi'
|
|
629
|
-
? emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
|
|
645
|
+
? emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType, prop)
|
|
630
646
|
: emitDynGetAnyTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
|
|
631
647
|
} else {
|
|
632
648
|
inc('__hash_get', '__str_hash', '__str_eq')
|
|
@@ -636,12 +652,20 @@ export default (ctx) => {
|
|
|
636
652
|
if (valTypeOf(obj) === VAL.HASH) {
|
|
637
653
|
access = emitHashGetLocalConst(['local.get', `$${t}`], asI64(emit(['str', prop])), prop)
|
|
638
654
|
} else {
|
|
639
|
-
access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), valTypeOf(obj))
|
|
655
|
+
access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), valTypeOf(obj), prop)
|
|
640
656
|
}
|
|
641
657
|
}
|
|
642
658
|
}
|
|
643
659
|
}
|
|
644
|
-
|
|
660
|
+
// Use local.set + local.get (not local.tee inside guard) because isNullish
|
|
661
|
+
// inlines the null/undefined check as (i32.or (i64.eq X NULL) (i64.eq X UNDEF)),
|
|
662
|
+
// which duplicates X — a local.tee(block(call $sideEffect)) would run twice.
|
|
663
|
+
return typed(['block', ['result', 'f64'],
|
|
664
|
+
['local.set', `$${t}`, va],
|
|
665
|
+
['if', ['result', 'f64'],
|
|
666
|
+
notNullish(typed(['local.get', `$${t}`], 'f64')),
|
|
667
|
+
['then', access],
|
|
668
|
+
['else', ['f64.const', `nan:${UNDEF_NAN}`]]]], 'f64')
|
|
645
669
|
}
|
|
646
670
|
|
|
647
671
|
// Optional index: arr?.[i] → null if arr is null, else arr[i]
|
|
@@ -656,7 +680,15 @@ export default (ctx) => {
|
|
|
656
680
|
if (!ctx.types.typedElem) ctx.types.typedElem = new Map()
|
|
657
681
|
ctx.types.typedElem.set(t, ctx.types.typedElem.get(arr))
|
|
658
682
|
}
|
|
659
|
-
|
|
683
|
+
// Use local.set + local.get (not local.tee inside guard) because isNullish
|
|
684
|
+
// inlines the null/undefined check as (i32.or (i64.eq X NULL) (i64.eq X UNDEF)),
|
|
685
|
+
// which duplicates X — a local.tee(block(call $sideEffect)) would run twice.
|
|
686
|
+
return typed(['block', ['result', 'f64'],
|
|
687
|
+
['local.set', `$${t}`, va],
|
|
688
|
+
['if', ['result', 'f64'],
|
|
689
|
+
notNullish(typed(['local.get', `$${t}`], 'f64')),
|
|
690
|
+
['then', asF64(ctx.core.emit['[]'](t, idx))],
|
|
691
|
+
['else', ['f64.const', `nan:${UNDEF_NAN}`]]]], 'f64')
|
|
660
692
|
}
|
|
661
693
|
|
|
662
694
|
// Optional call: fn?.(...args) → null if fn is null, else call fn
|
|
@@ -674,7 +706,14 @@ export default (ctx) => {
|
|
|
674
706
|
const vt = typeof recv === 'string' ? repOf(recv)?.val : valTypeOf(recv)
|
|
675
707
|
if (vt) updateRep(t, { val: vt })
|
|
676
708
|
const callResult = methodEmit(t, ...args)
|
|
677
|
-
|
|
709
|
+
// Use local.set + local.get (not local.tee inside guard) because isNullish
|
|
710
|
+
// inlines the null/undefined check which duplicates the expression.
|
|
711
|
+
return typed(['block', ['result', 'f64'],
|
|
712
|
+
['local.set', `$${t}`, va],
|
|
713
|
+
['if', ['result', 'f64'],
|
|
714
|
+
notNullish(typed(['local.get', `$${t}`], 'f64')),
|
|
715
|
+
['then', asF64(callResult)],
|
|
716
|
+
['else', ['f64.const', `nan:${UNDEF_NAN}`]]]], 'f64')
|
|
678
717
|
}
|
|
679
718
|
}
|
|
680
719
|
const t = temp()
|
|
@@ -682,7 +721,14 @@ export default (ctx) => {
|
|
|
682
721
|
// If nullish → return NULL_NAN, else call via fn.call
|
|
683
722
|
if (!ctx.closure.call) err('Optional call requires fn module')
|
|
684
723
|
const callResult = ctx.closure.call(typed(['local.get', `$${t}`], 'f64'), args)
|
|
685
|
-
|
|
724
|
+
// Use local.set + local.get (not local.tee inside guard) because isNullish
|
|
725
|
+
// inlines the null/undefined check which duplicates the expression.
|
|
726
|
+
return typed(['block', ['result', 'f64'],
|
|
727
|
+
['local.set', `$${t}`, va],
|
|
728
|
+
['if', ['result', 'f64'],
|
|
729
|
+
notNullish(typed(['local.get', `$${t}`], 'f64')),
|
|
730
|
+
['then', asF64(callResult)],
|
|
731
|
+
['else', ['f64.const', `nan:${UNDEF_NAN}`]]]], 'f64')
|
|
686
732
|
}
|
|
687
733
|
|
|
688
734
|
// typeof: returns JS-style string. Reachable results are number/undefined/string/function/symbol/object
|
package/module/date.js
CHANGED
|
@@ -76,6 +76,11 @@ export default (ctx) => {
|
|
|
76
76
|
], 'f64')
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
ctx.core.emit['Date.parse'] = (value) => {
|
|
80
|
+
inc('__date_from_value')
|
|
81
|
+
return typed(['call', '$__date_from_value', ['i64.reinterpret_f64', asF64(emit(value))]], 'f64')
|
|
82
|
+
}
|
|
83
|
+
|
|
79
84
|
// ── Core algorithms ───────────────────────────────────────────────────────
|
|
80
85
|
|
|
81
86
|
ctx.core.stdlib['__date_days_from_year'] = `(func $__date_days_from_year (param $y f64) (result f64)
|
package/module/function.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { typed, asF64, asI32, mkPtrIR, temp, tempI32, MAX_CLOSURE_ARITY, UNDEF_NAN } from '../src/ir.js'
|
|
14
|
-
import { emit
|
|
14
|
+
import { emit } from '../src/emit.js'
|
|
15
|
+
import { isReassigned } from '../src/ast.js'
|
|
15
16
|
import { T, lookupValType, repOf, findFreeVars } from '../src/analyze.js'
|
|
16
17
|
import { PTR, LAYOUT, inc, err } from '../src/ctx.js'
|
|
17
18
|
|
package/module/json.js
CHANGED
|
@@ -50,7 +50,8 @@ export default (ctx) => {
|
|
|
50
50
|
__jput_str: ['__char_at', '__str_byteLen'],
|
|
51
51
|
__jp: ['__jp_val', '__jp_str', '__jp_num', '__jp_arr', '__jp_obj', '__sso_char', '__ptr_aux', '__ptr_type', '__ptr_offset', '__str_byteLen'],
|
|
52
52
|
__jp_val: ['__jp_str', '__jp_num', '__jp_arr', '__jp_obj'],
|
|
53
|
-
__jp_str: ['__sso_char', '__char_at', '__str_byteLen'],
|
|
53
|
+
__jp_str: ['__sso_char', '__char_at', '__str_byteLen', '__hex4', '__utf8_enc'],
|
|
54
|
+
__hex4: ['__hex1'],
|
|
54
55
|
__jp_num: ['__pow10'],
|
|
55
56
|
__jp_arr: ['__jp_val'],
|
|
56
57
|
__jp_obj: ['__jp_val', '__jp_str', '__jp_schema_get', '__alloc_hdr', '__mkptr'],
|
|
@@ -319,8 +320,45 @@ export default (ctx) => {
|
|
|
319
320
|
// SSO byte packing for ≤4-char ASCII keys, and FNV-1a hash. The hash is
|
|
320
321
|
// stashed in $__jp_keyh so __jp_obj can use the prehashed insert and skip
|
|
321
322
|
// a redundant __str_hash call.
|
|
323
|
+
// Hex nibble: '0'-'9' / 'a'-'f' / 'A'-'F' → 0..15; anything else → 0 (lenient).
|
|
324
|
+
ctx.core.stdlib['__hex1'] = `(func $__hex1 (param $c i32) (result i32)
|
|
325
|
+
(if (i32.le_u (i32.sub (local.get $c) (i32.const 48)) (i32.const 9))
|
|
326
|
+
(then (return (i32.sub (local.get $c) (i32.const 48)))))
|
|
327
|
+
(if (i32.le_u (i32.sub (i32.or (local.get $c) (i32.const 0x20)) (i32.const 97)) (i32.const 5))
|
|
328
|
+
(then (return (i32.sub (i32.or (local.get $c) (i32.const 0x20)) (i32.const 87)))))
|
|
329
|
+
(i32.const 0))`
|
|
330
|
+
|
|
331
|
+
// Read 4 hex bytes at absolute address $p → 16-bit value.
|
|
332
|
+
ctx.core.stdlib['__hex4'] = `(func $__hex4 (param $p i32) (result i32)
|
|
333
|
+
(i32.or (i32.or (i32.or
|
|
334
|
+
(i32.shl (call $__hex1 (i32.load8_u (local.get $p))) (i32.const 12))
|
|
335
|
+
(i32.shl (call $__hex1 (i32.load8_u (i32.add (local.get $p) (i32.const 1)))) (i32.const 8)))
|
|
336
|
+
(i32.shl (call $__hex1 (i32.load8_u (i32.add (local.get $p) (i32.const 2)))) (i32.const 4)))
|
|
337
|
+
(call $__hex1 (i32.load8_u (i32.add (local.get $p) (i32.const 3))))))`
|
|
338
|
+
|
|
339
|
+
// Encode code point $cp as UTF-8 at $off; returns bytes written (1-4).
|
|
340
|
+
ctx.core.stdlib['__utf8_enc'] = `(func $__utf8_enc (param $off i32) (param $cp i32) (result i32)
|
|
341
|
+
(if (i32.lt_u (local.get $cp) (i32.const 0x80))
|
|
342
|
+
(then (i32.store8 (local.get $off) (local.get $cp)) (return (i32.const 1))))
|
|
343
|
+
(if (i32.lt_u (local.get $cp) (i32.const 0x800))
|
|
344
|
+
(then
|
|
345
|
+
(i32.store8 (local.get $off) (i32.or (i32.const 0xC0) (i32.shr_u (local.get $cp) (i32.const 6))))
|
|
346
|
+
(i32.store8 (i32.add (local.get $off) (i32.const 1)) (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))))
|
|
347
|
+
(return (i32.const 2))))
|
|
348
|
+
(if (i32.lt_u (local.get $cp) (i32.const 0x10000))
|
|
349
|
+
(then
|
|
350
|
+
(i32.store8 (local.get $off) (i32.or (i32.const 0xE0) (i32.shr_u (local.get $cp) (i32.const 12))))
|
|
351
|
+
(i32.store8 (i32.add (local.get $off) (i32.const 1)) (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))))
|
|
352
|
+
(i32.store8 (i32.add (local.get $off) (i32.const 2)) (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))))
|
|
353
|
+
(return (i32.const 3))))
|
|
354
|
+
(i32.store8 (local.get $off) (i32.or (i32.const 0xF0) (i32.shr_u (local.get $cp) (i32.const 18))))
|
|
355
|
+
(i32.store8 (i32.add (local.get $off) (i32.const 1)) (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 12)) (i32.const 0x3F))))
|
|
356
|
+
(i32.store8 (i32.add (local.get $off) (i32.const 2)) (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))))
|
|
357
|
+
(i32.store8 (i32.add (local.get $off) (i32.const 3)) (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))))
|
|
358
|
+
(i32.const 4))`
|
|
359
|
+
|
|
322
360
|
ctx.core.stdlib['__jp_str'] = `(func $__jp_str (result f64)
|
|
323
|
-
(local $start i32) (local $ch i32) (local $len i32) (local $off i32) (local $i i32) (local $simple i32) (local $sso i32) (local $h i32)
|
|
361
|
+
(local $start i32) (local $ch i32) (local $len i32) (local $off i32) (local $i i32) (local $simple i32) (local $sso i32) (local $h i32) (local $cp i32)
|
|
324
362
|
(local.set $start (global.get $__jppos))
|
|
325
363
|
(local.set $simple (i32.const 1))
|
|
326
364
|
(local.set $h (i32.const 0x811c9dc5))
|
|
@@ -382,12 +420,32 @@ export default (ctx) => {
|
|
|
382
420
|
${ADV(1)}
|
|
383
421
|
(local.set $ch ${PEEK})
|
|
384
422
|
${ADV(1)}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
423
|
+
(if (i32.eq (local.get $ch) (i32.const 117)) ;; \\uXXXX
|
|
424
|
+
(then
|
|
425
|
+
(local.set $cp (call $__hex4 (i32.add (global.get $__jpstr) (global.get $__jppos))))
|
|
426
|
+
${ADV(4)}
|
|
427
|
+
;; High surrogate immediately followed by \\uXXXX low surrogate → combine.
|
|
428
|
+
(if (i32.and
|
|
429
|
+
(i32.eq (i32.and (local.get $cp) (i32.const 0xFC00)) (i32.const 0xD800))
|
|
430
|
+
(i32.and (i32.eq ${PEEK} (i32.const 92))
|
|
431
|
+
(i32.eq (i32.load8_u (i32.add (global.get $__jpstr) (i32.add (global.get $__jppos) (i32.const 1)))) (i32.const 117))))
|
|
432
|
+
(then
|
|
433
|
+
${ADV(2)}
|
|
434
|
+
(local.set $i (call $__hex4 (i32.add (global.get $__jpstr) (global.get $__jppos))))
|
|
435
|
+
${ADV(4)}
|
|
436
|
+
(local.set $cp (i32.add (i32.const 0x10000)
|
|
437
|
+
(i32.or (i32.shl (i32.and (local.get $cp) (i32.const 0x3FF)) (i32.const 10))
|
|
438
|
+
(i32.and (local.get $i) (i32.const 0x3FF)))))))
|
|
439
|
+
(local.set $len (i32.add (local.get $len)
|
|
440
|
+
(call $__utf8_enc (i32.add (local.get $off) (local.get $len)) (local.get $cp))))
|
|
441
|
+
(br $l2))
|
|
442
|
+
(else
|
|
443
|
+
;; Decode simple escape: n→10 t→9 r→13 b→8 f→12, else literal char.
|
|
444
|
+
(if (i32.eq (local.get $ch) (i32.const 110)) (then (local.set $ch (i32.const 10))))
|
|
445
|
+
(if (i32.eq (local.get $ch) (i32.const 116)) (then (local.set $ch (i32.const 9))))
|
|
446
|
+
(if (i32.eq (local.get $ch) (i32.const 114)) (then (local.set $ch (i32.const 13))))
|
|
447
|
+
(if (i32.eq (local.get $ch) (i32.const 98)) (then (local.set $ch (i32.const 8))))
|
|
448
|
+
(if (i32.eq (local.get $ch) (i32.const 102)) (then (local.set $ch (i32.const 12)))))))
|
|
391
449
|
(else ${ADV(1)}))
|
|
392
450
|
(i32.store8 (i32.add (local.get $off) (local.get $len)) (local.get $ch))
|
|
393
451
|
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|