jz 0.3.0 → 0.4.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 +24 -14
- package/cli.js +37 -8
- package/index.js +32 -15
- package/module/array.js +16 -7
- package/module/collection.js +28 -13
- package/module/core.js +16 -1
- package/module/json.js +8 -8
- package/module/math.js +21 -4
- package/module/object.js +104 -9
- package/module/string.js +15 -0
- package/package.json +7 -4
- package/src/analyze.js +2 -2
- package/src/assemble.js +31 -8
- package/src/codegen.js +177 -0
- package/src/compile.js +21 -12
- package/src/emit.js +6 -1
- package/src/fuse.js +159 -0
- package/src/ir.js +11 -4
- package/src/jzify.js +165 -178
- package/src/narrow.js +36 -9
- package/src/optimize.js +182 -22
- package/src/plan.js +10 -1
- package/src/prepare.js +2 -149
package/module/object.js
CHANGED
|
@@ -32,7 +32,7 @@ export default (ctx) => {
|
|
|
32
32
|
const merged = target ? ctx.schema.resolve(target) : null
|
|
33
33
|
const schemaId = merged ? ctx.schema.idOf(target) : 0
|
|
34
34
|
const cap = merged ? Math.max(1, merged.length) : 1
|
|
35
|
-
return mkPtrIR(PTR.OBJECT, schemaId, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', cap]
|
|
35
|
+
return mkPtrIR(PTR.OBJECT, schemaId, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', cap]])
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// Flatten comma-grouped props: [',', p1, p2] → [p1, p2]
|
|
@@ -82,7 +82,7 @@ export default (ctx) => {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const body = [
|
|
85
|
-
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, schema.length)]
|
|
85
|
+
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, schema.length)]]],
|
|
86
86
|
]
|
|
87
87
|
for (let i = 0; i < values.length; i++)
|
|
88
88
|
body.push(['f64.store', slotAddr(t, i), asF64(emit(values[i]))])
|
|
@@ -150,8 +150,9 @@ export default (ctx) => {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
ctx.core.emit['Object.entries'] = (obj) => {
|
|
153
|
+
if (isHashTyped(obj)) return emitHashEntries(obj)
|
|
153
154
|
const schema = resolveSchema(obj)
|
|
154
|
-
if (!schema)
|
|
155
|
+
if (!schema) return emitRuntimeEntries(obj)
|
|
155
156
|
const va = asF64(emit(obj))
|
|
156
157
|
const n = schema.length
|
|
157
158
|
const t = temp('oe'), pair = tempI32('op'), base = tempI32('eb')
|
|
@@ -160,7 +161,7 @@ export default (ctx) => {
|
|
|
160
161
|
['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]
|
|
161
162
|
for (let i = 0; i < n; i++) {
|
|
162
163
|
body.push(
|
|
163
|
-
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]
|
|
164
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
164
165
|
['f64.store', slotAddr(pair, 0), emit(['str', schema[i]])],
|
|
165
166
|
['f64.store', slotAddr(pair, 1), ['f64.load', slotAddr(base, i)]],
|
|
166
167
|
['f64.store', slotAddr(out.local, i), mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])])
|
|
@@ -185,7 +186,7 @@ export default (ctx) => {
|
|
|
185
186
|
updateRep(target, { schemaId })
|
|
186
187
|
const t = tempI32('bx'), s = temp('bs')
|
|
187
188
|
const body = [
|
|
188
|
-
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, boxedSchema.length)]
|
|
189
|
+
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, boxedSchema.length)]]],
|
|
189
190
|
['f64.store', ['local.get', `$${t}`], asF64(emit(target))],
|
|
190
191
|
]
|
|
191
192
|
const sBase = tempI32('sb')
|
|
@@ -317,7 +318,7 @@ export default (ctx) => {
|
|
|
317
318
|
const srcBase = tempI32('cb')
|
|
318
319
|
const body = [
|
|
319
320
|
['local.set', `$${s}`, asF64(emit(proto))],
|
|
320
|
-
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, n)]
|
|
321
|
+
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, n)]]],
|
|
321
322
|
['local.set', `$${srcBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]],
|
|
322
323
|
]
|
|
323
324
|
// Copy all properties from proto
|
|
@@ -378,7 +379,7 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
378
379
|
const ptr = temp('objp')
|
|
379
380
|
const src = tempI32('osp')
|
|
380
381
|
|
|
381
|
-
const body = [['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, schema.length)]
|
|
382
|
+
const body = [['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, schema.length)]]]]
|
|
382
383
|
|
|
383
384
|
// Process props in order — later props override earlier (JS semantics)
|
|
384
385
|
let srcF
|
|
@@ -462,6 +463,13 @@ function emitHashValues(obj) {
|
|
|
462
463
|
hashValuesFromTemp(t)], 'f64')
|
|
463
464
|
}
|
|
464
465
|
|
|
466
|
+
function emitHashEntries(obj) {
|
|
467
|
+
const t = temp('he')
|
|
468
|
+
return typed(['block', ['result', 'f64'],
|
|
469
|
+
['local.set', `$${t}`, asF64(emit(obj))],
|
|
470
|
+
hashEntriesFromTemp(t)], 'f64')
|
|
471
|
+
}
|
|
472
|
+
|
|
465
473
|
// Inline body of the HASH walk against an already-bound f64 local. Shared by
|
|
466
474
|
// the static-HASH path and the runtime-dispatch path so both produce the same
|
|
467
475
|
// IR shape from the same source — only difference is whether they enter from
|
|
@@ -520,6 +528,37 @@ function hashValuesFromTemp(t) {
|
|
|
520
528
|
out.ptr]
|
|
521
529
|
}
|
|
522
530
|
|
|
531
|
+
function hashEntriesFromTemp(t) {
|
|
532
|
+
inc('__ptr_offset', '__cap', '__len', '__alloc_hdr')
|
|
533
|
+
const off = tempI32('heo'), cap = tempI32('hec'), n = tempI32('hen')
|
|
534
|
+
const i = tempI32('hei'), o = tempI32('hej'), slot = tempI32('hes'), pair = tempI32('hep')
|
|
535
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'hea' })
|
|
536
|
+
const id = ctx.func.uniq++
|
|
537
|
+
return ['block', ['result', 'f64'],
|
|
538
|
+
['local.set', `$${n}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
539
|
+
out.init,
|
|
540
|
+
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
541
|
+
['local.set', `$${cap}`, ['call', '$__cap', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
542
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
543
|
+
['local.set', `$${o}`, ['i32.const', 0]],
|
|
544
|
+
['block', `$ebrk${id}`, ['loop', `$eloop${id}`,
|
|
545
|
+
['br_if', `$ebrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${cap}`]]],
|
|
546
|
+
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${off}`],
|
|
547
|
+
['i32.mul', ['local.get', `$${i}`], ['i32.const', 24]]]],
|
|
548
|
+
['if', ['f64.ne', ['f64.load', ['local.get', `$${slot}`]], ['f64.const', 0]],
|
|
549
|
+
['then',
|
|
550
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
551
|
+
['f64.store', ['local.get', `$${pair}`],
|
|
552
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]],
|
|
553
|
+
['f64.store', ['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]],
|
|
554
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 16]]]],
|
|
555
|
+
elemStore(out.local, o, mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])),
|
|
556
|
+
['local.set', `$${o}`, ['i32.add', ['local.get', `$${o}`], ['i32.const', 1]]]]],
|
|
557
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
558
|
+
['br', `$eloop${id}`]]],
|
|
559
|
+
out.ptr]
|
|
560
|
+
}
|
|
561
|
+
|
|
523
562
|
// Type-unknown receiver: bind the value, branch on ptr-type. HASH walks the
|
|
524
563
|
// probe table; OBJECT loads the schema's key array (registered statically at
|
|
525
564
|
// compile time or lazily at runtime by JSON.parse via __jp_schema_get); other
|
|
@@ -568,6 +607,26 @@ function emitRuntimeValues(obj) {
|
|
|
568
607
|
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]], 'f64')
|
|
569
608
|
}
|
|
570
609
|
|
|
610
|
+
function emitRuntimeEntries(obj) {
|
|
611
|
+
inc('__ptr_type')
|
|
612
|
+
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
613
|
+
ctx.scope.globals.set('__schema_tbl', '(global $__schema_tbl (mut i32) (i32.const 0))')
|
|
614
|
+
const t = temp('re'), tt = tempI32('ret')
|
|
615
|
+
const empty = allocPtr({ type: PTR.ARRAY, len: 0, tag: 'ree' })
|
|
616
|
+
return typed(['block', ['result', 'f64'],
|
|
617
|
+
['local.set', `$${t}`, asF64(emit(obj))],
|
|
618
|
+
['local.set', `$${tt}`, ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
619
|
+
['if', ['result', 'f64'],
|
|
620
|
+
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.HASH]],
|
|
621
|
+
['then', hashEntriesFromTemp(t)],
|
|
622
|
+
['else', ['if', ['result', 'f64'],
|
|
623
|
+
['i32.and',
|
|
624
|
+
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
625
|
+
['i32.ne', ['global.get', '$__schema_tbl'], ['i32.const', 0]]],
|
|
626
|
+
['then', objectEntriesFromTemp(t)],
|
|
627
|
+
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]], 'f64')
|
|
628
|
+
}
|
|
629
|
+
|
|
571
630
|
// Schema-keyed Object.keys: copy the schema's keys array (a jz Array of
|
|
572
631
|
// STRINGs registered in __schema_tbl[sid]) into a fresh ARRAY so callers can
|
|
573
632
|
// mutate without aliasing the schema substrate.
|
|
@@ -584,7 +643,7 @@ function objectKeysFromTemp(t) {
|
|
|
584
643
|
['i64.const', LAYOUT.OFFSET_MASK]]]],
|
|
585
644
|
['local.set', `$${n}`, ['i32.load', ['i32.sub', ['local.get', `$${src}`], ['i32.const', 8]]]],
|
|
586
645
|
['local.set', `$${out}`, ['call', '$__alloc_hdr',
|
|
587
|
-
['local.get', `$${n}`], ['local.get', `$${n}`]
|
|
646
|
+
['local.get', `$${n}`], ['local.get', `$${n}`]]],
|
|
588
647
|
['local.set', `$${i}`, ['i32.const', 0]],
|
|
589
648
|
['block', `$kbrk${id}`, ['loop', `$kloop${id}`,
|
|
590
649
|
['br_if', `$kbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
@@ -612,7 +671,7 @@ function objectValuesFromTemp(t) {
|
|
|
612
671
|
['local.set', `$${n}`, ['i32.load', ['i32.sub', ['local.get', `$${src}`], ['i32.const', 8]]]],
|
|
613
672
|
['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
614
673
|
['local.set', `$${out}`, ['call', '$__alloc_hdr',
|
|
615
|
-
['local.get', `$${n}`], ['local.get', `$${n}`]
|
|
674
|
+
['local.get', `$${n}`], ['local.get', `$${n}`]]],
|
|
616
675
|
['local.set', `$${i}`, ['i32.const', 0]],
|
|
617
676
|
['block', `$ovbrk${id}`, ['loop', `$ovloop${id}`,
|
|
618
677
|
['br_if', `$ovbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
@@ -624,3 +683,39 @@ function objectValuesFromTemp(t) {
|
|
|
624
683
|
['br', `$ovloop${id}`]]],
|
|
625
684
|
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${out}`])]
|
|
626
685
|
}
|
|
686
|
+
|
|
687
|
+
function objectEntriesFromTemp(t) {
|
|
688
|
+
inc('__alloc_hdr', '__ptr_offset')
|
|
689
|
+
const sid = tempI32('oes'), src = tempI32('oesrc'), n = tempI32('oen')
|
|
690
|
+
const base = tempI32('oebase'), out = tempI32('oeo'), i = tempI32('oei'), pair = tempI32('oep')
|
|
691
|
+
const id = ctx.func.uniq++
|
|
692
|
+
return ['block', ['result', 'f64'],
|
|
693
|
+
['local.set', `$${sid}`, ['i32.wrap_i64', ['i64.and',
|
|
694
|
+
['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', LAYOUT.AUX_SHIFT]],
|
|
695
|
+
['i64.const', LAYOUT.AUX_MASK]]]],
|
|
696
|
+
['local.set', `$${src}`, ['i32.wrap_i64', ['i64.and',
|
|
697
|
+
['i64.load', ['i32.add', ['global.get', '$__schema_tbl'], ['i32.shl', ['local.get', `$${sid}`], ['i32.const', 3]]]],
|
|
698
|
+
['i64.const', LAYOUT.OFFSET_MASK]]]],
|
|
699
|
+
['local.set', `$${n}`, ['i32.load', ['i32.sub', ['local.get', `$${src}`], ['i32.const', 8]]]],
|
|
700
|
+
['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
701
|
+
['local.set', `$${out}`, ['call', '$__alloc_hdr',
|
|
702
|
+
['local.get', `$${n}`], ['local.get', `$${n}`]]],
|
|
703
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
704
|
+
['block', `$oebrk${id}`, ['loop', `$oeloop${id}`,
|
|
705
|
+
['br_if', `$oebrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
706
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
707
|
+
['i64.store',
|
|
708
|
+
['local.get', `$${pair}`],
|
|
709
|
+
['i64.load',
|
|
710
|
+
['i32.add', ['local.get', `$${src}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
711
|
+
['f64.store',
|
|
712
|
+
['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]],
|
|
713
|
+
['f64.load',
|
|
714
|
+
['i32.add', ['local.get', `$${base}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
715
|
+
['f64.store',
|
|
716
|
+
['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]],
|
|
717
|
+
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])],
|
|
718
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
719
|
+
['br', `$oeloop${id}`]]],
|
|
720
|
+
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${out}`])]
|
|
721
|
+
}
|
package/module/string.js
CHANGED
|
@@ -752,12 +752,27 @@ export default (ctx) => {
|
|
|
752
752
|
(br $next)))
|
|
753
753
|
(f64.reinterpret_i64 (local.get $result)))`
|
|
754
754
|
|
|
755
|
+
// Empty separator: per JS spec, split into individual byte-chars
|
|
756
|
+
// ("abc".split("") -> ["a","b","c"], "".split("") -> []). Without this
|
|
757
|
+
// guard the main loop advances by plen=0 and spins forever.
|
|
755
758
|
ctx.core.stdlib['__str_split'] = `(func $__str_split (param $str i64) (param $sep i64) (result f64)
|
|
756
759
|
(local $slen i32) (local $plen i32) (local $count i32)
|
|
757
760
|
(local $i i32) (local $j i32) (local $match i32)
|
|
758
761
|
(local $arr i32) (local $piece_start i32) (local $piece_idx i32)
|
|
759
762
|
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
760
763
|
(local.set $plen (call $__str_byteLen (local.get $sep)))
|
|
764
|
+
(if (i32.eqz (local.get $plen)) (then
|
|
765
|
+
(local.set $arr (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $slen) (i32.const 3)))))
|
|
766
|
+
(i32.store (local.get $arr) (local.get $slen))
|
|
767
|
+
(i32.store (i32.add (local.get $arr) (i32.const 4)) (local.get $slen))
|
|
768
|
+
(local.set $arr (i32.add (local.get $arr) (i32.const 8)))
|
|
769
|
+
(block $de (loop $le
|
|
770
|
+
(br_if $de (i32.ge_s (local.get $i) (local.get $slen)))
|
|
771
|
+
(f64.store (i32.add (local.get $arr) (i32.shl (local.get $i) (i32.const 3)))
|
|
772
|
+
(call $__str_slice (local.get $str) (local.get $i) (i32.add (local.get $i) (i32.const 1))))
|
|
773
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
774
|
+
(br $le)))
|
|
775
|
+
(return (call $__mkptr (i32.const 1) (i32.const 0) (local.get $arr)))))
|
|
761
776
|
(local.set $count (i32.const 1))
|
|
762
777
|
(local.set $i (i32.const 0))
|
|
763
778
|
(block $d1 (loop $l1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Modern functional JS compiling to WASM",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -24,8 +24,11 @@
|
|
|
24
24
|
"test262": "node test/test262.js",
|
|
25
25
|
"test262:builtins": "node test/test262-builtins.js",
|
|
26
26
|
"bench": "node bench/bench.mjs",
|
|
27
|
+
"bench:size": "node scripts/bench-size.mjs",
|
|
27
28
|
"bench:compile": "node scripts/bench-compile.mjs",
|
|
28
|
-
"test:bench-pin": "node test/bench-pin.js"
|
|
29
|
+
"test:bench-pin": "node test/bench-pin.js",
|
|
30
|
+
"test:all": "npm test && npm run test:bench-pin",
|
|
31
|
+
"prepublishOnly": "npm run test:all"
|
|
29
32
|
},
|
|
30
33
|
"repository": {
|
|
31
34
|
"type": "git",
|
|
@@ -38,8 +41,8 @@
|
|
|
38
41
|
"author": "Dmitry Iv",
|
|
39
42
|
"license": "MIT",
|
|
40
43
|
"dependencies": {
|
|
41
|
-
"subscript": "^10.4.
|
|
42
|
-
"watr": "^4.5
|
|
44
|
+
"subscript": "^10.4.8",
|
|
45
|
+
"watr": "^4.6.5"
|
|
43
46
|
},
|
|
44
47
|
"keywords": [
|
|
45
48
|
"javascript",
|
package/src/analyze.js
CHANGED
|
@@ -37,9 +37,9 @@ export const STMT_OPS = new Set([';', 'let', 'const', 'return', 'if', 'for', 'fo
|
|
|
37
37
|
export const ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??='])
|
|
38
38
|
|
|
39
39
|
/** Distinguish a function block body `{ ... }` from an expression-bodied object literal `({a:1})`.
|
|
40
|
-
* Both share the `'{}'` op tag; blocks
|
|
40
|
+
* Both share the `'{}'` op tag; blocks start with statement ops, while object literals start with `:` or `...`. */
|
|
41
41
|
export const isBlockBody = (body) =>
|
|
42
|
-
Array.isArray(body) && body[0] === '{}' && body[1]?.[0]
|
|
42
|
+
Array.isArray(body) && body[0] === '{}' && (body.length === 1 || STMT_OPS.has(body[1]?.[0]))
|
|
43
43
|
|
|
44
44
|
/** Extract integer value from AST literal node. Returns null if not a 32-bit integer. */
|
|
45
45
|
export function intLiteralValue(expr) {
|
package/src/assemble.js
CHANGED
|
@@ -14,6 +14,25 @@
|
|
|
14
14
|
|
|
15
15
|
import { parse as parseWat } from 'watr'
|
|
16
16
|
import { ctx, inc, resolveIncludes, PTR, LAYOUT } from './ctx.js'
|
|
17
|
+
|
|
18
|
+
// Stdlib WAT templates are fixed text (or feature-keyed text from a factory) —
|
|
19
|
+
// `parseWat` of the same string always yields the same tree. Parsing is the
|
|
20
|
+
// dominant cost when a program pulls heavy stdlib (Math pow/sqrt, JSON, regex):
|
|
21
|
+
// it re-tokenizes ~KB of text every compile. Parse once per distinct resolved
|
|
22
|
+
// string, then hand out a deep clone (downstream passes mutate nodes in place).
|
|
23
|
+
// Module-level on purpose: the cache persists across compile() calls.
|
|
24
|
+
const stdlibParseCache = new Map() // resolved WAT string → pristine parsed tree
|
|
25
|
+
const cloneTemplate = (node) => {
|
|
26
|
+
if (!Array.isArray(node)) return node
|
|
27
|
+
const copy = node.map(cloneTemplate)
|
|
28
|
+
if (node.loc != null) copy.loc = node.loc
|
|
29
|
+
return copy
|
|
30
|
+
}
|
|
31
|
+
const parseTemplate = (str) => {
|
|
32
|
+
let tmpl = stdlibParseCache.get(str)
|
|
33
|
+
if (tmpl === undefined) stdlibParseCache.set(str, tmpl = parseWat(str))
|
|
34
|
+
return cloneTemplate(tmpl)
|
|
35
|
+
}
|
|
17
36
|
import { T, VAL, analyzeValTypes } from './analyze.js'
|
|
18
37
|
import { optimizeFunc, hoistConstantPool, specializeMkptr, specializePtrBase, sortStrPoolByFreq, arenaRewindModule } from './optimize.js'
|
|
19
38
|
import { emit } from './emit.js'
|
|
@@ -36,7 +55,7 @@ const heapSetIR = value => ctx.memory.shared
|
|
|
36
55
|
: ['global.set', '$__heap', value]
|
|
37
56
|
|
|
38
57
|
const ARENA_SAFE_CALLS = new Set([
|
|
39
|
-
'$__alloc', '$__alloc_hdr', '$__mkptr',
|
|
58
|
+
'$__alloc', '$__alloc_hdr', '$__alloc_hdr_n', '$__mkptr',
|
|
40
59
|
'$__ptr_offset', '$__ptr_type', '$__ptr_aux',
|
|
41
60
|
'$__len', '$__cap', '$__typed_shift', '$__typed_data',
|
|
42
61
|
])
|
|
@@ -60,7 +79,7 @@ function applyArenaRewind(func, fn, safeCallees) {
|
|
|
60
79
|
}
|
|
61
80
|
if (op === 'call') {
|
|
62
81
|
const name = node[1]
|
|
63
|
-
if (name === '$__alloc' || name === '$__alloc_hdr') hasAlloc = true
|
|
82
|
+
if (name === '$__alloc' || name === '$__alloc_hdr' || name === '$__alloc_hdr_n') hasAlloc = true
|
|
64
83
|
if (!(safeCallees ?? ARENA_SAFE_CALLS).has(name)) {
|
|
65
84
|
unsafe = true
|
|
66
85
|
return
|
|
@@ -154,7 +173,7 @@ export function buildStartFn(ast, sec, closureFuncs, compilePendingClosures) {
|
|
|
154
173
|
for (const [name, { schemaId, schema }] of ctx.schema.autoBox) {
|
|
155
174
|
inc('__alloc_hdr', '__mkptr')
|
|
156
175
|
boxInit.push(
|
|
157
|
-
['local.set', `$${bt}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, schema.length)]
|
|
176
|
+
['local.set', `$${bt}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', Math.max(1, schema.length)]]],
|
|
158
177
|
['f64.store', ['local.get', `$${bt}`],
|
|
159
178
|
ctx.func.names.has(name) ? ['f64.const', 0] : ['global.get', `$${name}`]],
|
|
160
179
|
...schema.slice(1).map((_, i) =>
|
|
@@ -195,7 +214,7 @@ export function buildStartFn(ast, sec, closureFuncs, compilePendingClosures) {
|
|
|
195
214
|
const keys = ctx.schema.list[s]
|
|
196
215
|
const n = keys.length
|
|
197
216
|
schemaInit.push(
|
|
198
|
-
['local.set', `$${sarr}`, ['call', '$__alloc_hdr', ['i32.const', n], ['i32.const', n]
|
|
217
|
+
['local.set', `$${sarr}`, ['call', '$__alloc_hdr', ['i32.const', n], ['i32.const', n]]])
|
|
199
218
|
for (let k = 0; k < n; k++)
|
|
200
219
|
schemaInit.push(
|
|
201
220
|
['f64.store', ['i32.add', ['local.get', `$${sarr}`], ['i32.const', k * 8]],
|
|
@@ -384,7 +403,7 @@ export function pullStdlib(sec) {
|
|
|
384
403
|
if (ctx.memory.shared) sec.imports.push(['import', '"env"', '"memory"', ['memory', pages]])
|
|
385
404
|
else sec.memory.push(['memory', ['export', '"memory"'], pages])
|
|
386
405
|
if (ctx.transform.alloc !== false && ctx.core._allocRawFuncs)
|
|
387
|
-
sec.funcs.push(...ctx.core._allocRawFuncs.map(
|
|
406
|
+
sec.funcs.push(...ctx.core._allocRawFuncs.map(parseTemplate))
|
|
388
407
|
}
|
|
389
408
|
|
|
390
409
|
const stdlibStr = (name) => {
|
|
@@ -394,14 +413,14 @@ export function pullStdlib(sec) {
|
|
|
394
413
|
ctx.core.extImports ??= new Set()
|
|
395
414
|
for (const name of Object.keys(ctx.core.stdlib)) {
|
|
396
415
|
if (name.startsWith('__ext_') && ctx.core.includes.has(name)) {
|
|
397
|
-
const parsed =
|
|
416
|
+
const parsed = parseTemplate(stdlibStr(name))
|
|
398
417
|
sec.extStdlib.push(parsed[0] === "module" ? parsed[1] : parsed)
|
|
399
418
|
ctx.core.extImports.add(name)
|
|
400
419
|
ctx.core.includes.delete(name)
|
|
401
420
|
}
|
|
402
421
|
}
|
|
403
422
|
for (const n of ctx.core.includes) if (!ctx.core.stdlib[n]) console.error("MISSING stdlib:", n)
|
|
404
|
-
sec.stdlib.push(...[...ctx.core.includes].map(n =>
|
|
423
|
+
sec.stdlib.push(...[...ctx.core.includes].map(n => parseTemplate(stdlibStr(n))))
|
|
405
424
|
}
|
|
406
425
|
|
|
407
426
|
export function syncImports(sec) {
|
|
@@ -449,7 +468,11 @@ export function optimizeModule(sec) {
|
|
|
449
468
|
}
|
|
450
469
|
}
|
|
451
470
|
if (!cfg || cfg.hoistConstantPool !== false)
|
|
452
|
-
hoistConstantPool([...sec.funcs, ...sec.stdlib, ...sec.start], (name, wat) =>
|
|
471
|
+
hoistConstantPool([...sec.funcs, ...sec.stdlib, ...sec.start], (name, wat) => {
|
|
472
|
+
ctx.scope.globals.set(name, wat)
|
|
473
|
+
const m = wat.match(/\(global\s+\$?\S+\s+(?:\(mut\s+)?(i32|i64|f64|f32)/)
|
|
474
|
+
if (m) ctx.scope.globalTypes.set(name, m[1])
|
|
475
|
+
})
|
|
453
476
|
|
|
454
477
|
// Second promoteGlobals pass disabled: promoting hoistConstantPool's __fc*
|
|
455
478
|
// globals regressed the watr perf micro-pin (WASM compile time increased).
|
package/src/codegen.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST → jz source codegen.
|
|
3
|
+
*
|
|
4
|
+
* Pretty-prints a jzify-transformed AST back to jz source text. CLI-only
|
|
5
|
+
* (`jz jzify file.js` → `file.jz`); the compile path consumes the AST directly
|
|
6
|
+
* and never round-trips through source.
|
|
7
|
+
*
|
|
8
|
+
* @module codegen
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const INDENT = ' '
|
|
12
|
+
const prec = { '=': 1, '+=': 1, '-=': 1, '*=': 1, '/=': 1, '%=': 1, '&=': 1, '|=': 1, '^=': 1, '>>=': 1, '<<=': 1, '>>>=': 1, '||=': 1, '&&=': 1,
|
|
13
|
+
'??': 2, '||': 3, '&&': 4, '|': 5, '^': 6, '&': 7, '===': 8, '!==': 8, '==': 8, '!=': 8,
|
|
14
|
+
'<': 9, '>': 9, '<=': 9, '>=': 9, '<<': 10, '>>': 10, '>>>': 10,
|
|
15
|
+
'+': 11, '-': 11, '*': 12, '/': 12, '%': 12, '**': 13 }
|
|
16
|
+
|
|
17
|
+
/** Wrap statement in { } if not already a block */
|
|
18
|
+
function wrapBlock(node, depth) {
|
|
19
|
+
if (Array.isArray(node) && node[0] === '{}') return codegen(node, depth)
|
|
20
|
+
return '{ ' + codegen(node, depth) + '; }'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Generate jz source from AST. Enforces semicolons. */
|
|
24
|
+
export function codegen(node, depth = 0) {
|
|
25
|
+
if (node == null) return ''
|
|
26
|
+
if (typeof node === 'number') return String(node)
|
|
27
|
+
if (typeof node === 'bigint') return node + 'n'
|
|
28
|
+
if (typeof node === 'string') return node
|
|
29
|
+
if (!Array.isArray(node)) return String(node)
|
|
30
|
+
|
|
31
|
+
const [op, ...a] = node
|
|
32
|
+
const ind = INDENT.repeat(depth), ind1 = INDENT.repeat(depth + 1)
|
|
33
|
+
|
|
34
|
+
// Literal: [, value]
|
|
35
|
+
if (op == null) return typeof a[0] === 'string' ? JSON.stringify(a[0]) : a[0] == null ? 'null' : String(a[0]) + (typeof a[0] === 'bigint' ? 'n' : '')
|
|
36
|
+
|
|
37
|
+
// Statements
|
|
38
|
+
if (op === ';') return a.map(s => codegen(s, depth)).filter(Boolean).join(';\n' + ind) + ';'
|
|
39
|
+
if (op === '{}') {
|
|
40
|
+
// Discriminate object literal / destructuring pattern from block.
|
|
41
|
+
// Object: `:` key-value, `,` of object-pattern items (id / `:` / `...` / `= default`),
|
|
42
|
+
// lone string shorthand. Empty `{}` outputs the same string either way.
|
|
43
|
+
const body = a[0]
|
|
44
|
+
const isObjItem = (n) => typeof n === 'string' ||
|
|
45
|
+
(Array.isArray(n) && (n[0] === ':' || n[0] === '...' || n[0] === 'as' ||
|
|
46
|
+
(n[0] === '=' && typeof n[1] === 'string')))
|
|
47
|
+
const isObj = body == null ? false
|
|
48
|
+
: typeof body === 'string' ? true
|
|
49
|
+
: Array.isArray(body) && (body[0] === ':' || body[0] === '...' || body[0] === 'as' ||
|
|
50
|
+
(body[0] === ',' && body.slice(1).every(isObjItem)))
|
|
51
|
+
if (isObj) {
|
|
52
|
+
if (typeof body === 'string') return '{ ' + body + ' }'
|
|
53
|
+
if (body[0] === ',') return '{ ' + body.slice(1).map(x => codegen(x)).join(', ') + ' }'
|
|
54
|
+
return '{ ' + codegen(body) + ' }'
|
|
55
|
+
}
|
|
56
|
+
// Block: body is null, a single statement, or [';', ...stmts]
|
|
57
|
+
const stmts = body == null ? [] : (Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body])
|
|
58
|
+
const rendered = stmts.map(s => codegen(s, depth + 1)).filter(Boolean).join(';\n' + ind1)
|
|
59
|
+
return '{\n' + ind1 + rendered + (rendered ? ';' : '') + '\n' + ind + '}'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Declarations
|
|
63
|
+
if (op === 'let' || op === 'const') return op + ' ' + a.map(d => codegen(d, depth)).join(', ')
|
|
64
|
+
if (op === 'export') { const inner = codegen(a[0], depth); return inner ? 'export ' + inner : '' }
|
|
65
|
+
if (op === 'default') return 'default ' + codegen(a[0], depth)
|
|
66
|
+
|
|
67
|
+
// Control flow
|
|
68
|
+
if (op === 'if') {
|
|
69
|
+
const cond = codegen(a[0]), then = wrapBlock(a[1], depth)
|
|
70
|
+
return a[2] != null
|
|
71
|
+
? 'if (' + cond + ') ' + then + ' else ' + wrapBlock(a[2], depth)
|
|
72
|
+
: 'if (' + cond + ') ' + then
|
|
73
|
+
}
|
|
74
|
+
if (op === 'while') return 'while (' + codegen(a[0]) + ') ' + wrapBlock(a[1], depth)
|
|
75
|
+
if (op === 'for') {
|
|
76
|
+
if (a.length === 2) { // ['for', head, body] — subscript shape
|
|
77
|
+
const [head, body] = a
|
|
78
|
+
if (Array.isArray(head) && (head[0] === 'of' || head[0] === 'in'))
|
|
79
|
+
return 'for (' + codegen(head[1]) + ' ' + head[0] + ' ' + codegen(head[2]) + ') ' + wrapBlock(body, depth)
|
|
80
|
+
// ['let'/'const', ['in'/'of', name, obj]] — subscript wraps var→let around in/of
|
|
81
|
+
if (Array.isArray(head) && (head[0] === 'let' || head[0] === 'const') && Array.isArray(head[1]) && (head[1][0] === 'in' || head[1][0] === 'of'))
|
|
82
|
+
return 'for (' + head[0] + ' ' + codegen(head[1][1]) + ' ' + head[1][0] + ' ' + codegen(head[1][2]) + ') ' + wrapBlock(body, depth)
|
|
83
|
+
// C-style head [';', init, cond, update] is positional — empty slots are valid,
|
|
84
|
+
// must not flow through the generic `;` joiner (which adds newlines + a trailing `;`).
|
|
85
|
+
if (Array.isArray(head) && head[0] === ';')
|
|
86
|
+
return 'for (' + (head[1] == null ? '' : codegen(head[1])) + '; ' + (head[2] == null ? '' : codegen(head[2])) + '; ' + (head[3] == null ? '' : codegen(head[3])) + ') ' + wrapBlock(body, depth)
|
|
87
|
+
return 'for (' + codegen(head) + ') ' + wrapBlock(body, depth)
|
|
88
|
+
}
|
|
89
|
+
return 'for (' + (codegen(a[0]) || '') + '; ' + (codegen(a[1]) || '') + '; ' + (codegen(a[2]) || '') + ') ' + wrapBlock(a[3], depth)
|
|
90
|
+
}
|
|
91
|
+
if (op === 'return') return 'return ' + codegen(a[0])
|
|
92
|
+
if (op === 'throw') return 'throw ' + codegen(a[0])
|
|
93
|
+
if (op === 'break') return 'break'
|
|
94
|
+
if (op === 'continue') return 'continue'
|
|
95
|
+
// catch with optional binding: ['catch', tryBlock, catchBody] or ['catch', tryBlock, paramName, catchBody]
|
|
96
|
+
if (op === 'catch') {
|
|
97
|
+
if (a.length === 3) return 'try ' + codegen(a[0], depth) + ' catch (' + a[1] + ') ' + codegen(a[2], depth)
|
|
98
|
+
return 'try ' + codegen(a[0], depth) + ' catch ' + codegen(a[1], depth)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Arrow
|
|
102
|
+
if (op === '=>') {
|
|
103
|
+
// Params: already wrapped in () by parser, or bare name
|
|
104
|
+
const p = a[0]
|
|
105
|
+
const params = Array.isArray(p) && p[0] === '()' ? codegen(p) : '(' + codegen(p) + ')'
|
|
106
|
+
const body = a[1]
|
|
107
|
+
const isBlock = Array.isArray(body) && (body[0] === '{}' || body[0] === ';' || body[0] === 'return')
|
|
108
|
+
const bodyStr = Array.isArray(body) && body[0] !== '{}' && isBlock
|
|
109
|
+
? '{ ' + codegen(body, depth) + '; }'
|
|
110
|
+
: codegen(body, depth)
|
|
111
|
+
return params + ' => ' + bodyStr
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Grouping parens / function call
|
|
115
|
+
if (op === '()') {
|
|
116
|
+
if (a.length === 1) return '(' + (a[0] == null ? '' : codegen(a[0])) + ')'
|
|
117
|
+
return codegen(a[0]) + '(' + a.slice(1).map(x => codegen(x)).join(', ') + ')'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Property access
|
|
121
|
+
if (op === '.') return codegen(a[0]) + '.' + a[1]
|
|
122
|
+
if (op === '?.') return codegen(a[0]) + '?.' + a[1]
|
|
123
|
+
if (op === '?.[]') return codegen(a[0]) + '?.[' + codegen(a[1]) + ']'
|
|
124
|
+
if (op === '?.()') return codegen(a[0]) + '?.(' + a.slice(1).map(x => codegen(x)).join(', ') + ')'
|
|
125
|
+
if (op === '[]') {
|
|
126
|
+
// Array literal: ['[]', body] (length 2 → a.length 1). body may be null (empty),
|
|
127
|
+
// a single element, or a [',', ...items] sequence.
|
|
128
|
+
if (a.length === 1) {
|
|
129
|
+
if (a[0] == null) return '[]'
|
|
130
|
+
const body = a[0]
|
|
131
|
+
if (Array.isArray(body) && body[0] === ',') return '[' + body.slice(1).map(x => codegen(x)).join(', ') + ']'
|
|
132
|
+
return '[' + codegen(body) + ']'
|
|
133
|
+
}
|
|
134
|
+
// Subscript: ['[]', obj, idx]
|
|
135
|
+
return codegen(a[0]) + '[' + codegen(a[1]) + ']'
|
|
136
|
+
}
|
|
137
|
+
if (op === ':') return codegen(a[0]) + ': ' + codegen(a[1])
|
|
138
|
+
if (op === 'str') return JSON.stringify(a[0])
|
|
139
|
+
if (op === '//') return '/' + a[0] + '/' + (a[1] || '')
|
|
140
|
+
|
|
141
|
+
// Comma
|
|
142
|
+
if (op === ',') return a.map(x => codegen(x)).join(', ')
|
|
143
|
+
// Template literal: alternating string/expr parts. String parts are [null, "str"], expr parts are AST nodes.
|
|
144
|
+
if (op === '`') return '`' + a.map(p => {
|
|
145
|
+
if (Array.isArray(p) && p[0] == null && typeof p[1] === 'string') return p[1].replace(/[`\\$]/g, c => '\\' + c)
|
|
146
|
+
return '${' + codegen(p) + '}'
|
|
147
|
+
}).join('') + '`'
|
|
148
|
+
|
|
149
|
+
// Spread
|
|
150
|
+
if (op === '...') return '...' + codegen(a[0])
|
|
151
|
+
|
|
152
|
+
// Import / export rename
|
|
153
|
+
if (op === 'import') return 'import ' + codegen(a[0])
|
|
154
|
+
if (op === 'from') return codegen(a[0]) + ' from ' + codegen(a[1])
|
|
155
|
+
if (op === 'as') return codegen(a[0]) + ' as ' + codegen(a[1])
|
|
156
|
+
|
|
157
|
+
// Unary prefix
|
|
158
|
+
if (a.length === 1) {
|
|
159
|
+
if (op === '++' || op === '--') return a[0] == null ? op : op + codegen(a[0])
|
|
160
|
+
if (op === 'typeof') return 'typeof ' + codegen(a[0])
|
|
161
|
+
if (op === 'u-') return '-' + codegen(a[0])
|
|
162
|
+
if (op === 'u+') return '+' + codegen(a[0])
|
|
163
|
+
return op + codegen(a[0])
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Postfix
|
|
167
|
+
if (a.length === 2 && a[1] === null) return codegen(a[0]) + op
|
|
168
|
+
|
|
169
|
+
// Binary
|
|
170
|
+
if (a.length === 2 && prec[op]) return codegen(a[0]) + ' ' + op + ' ' + codegen(a[1])
|
|
171
|
+
|
|
172
|
+
// Ternary
|
|
173
|
+
if (op === '?' || op === '?:') return codegen(a[0]) + ' ? ' + codegen(a[1]) + ' : ' + codegen(a[2])
|
|
174
|
+
|
|
175
|
+
// Fallback
|
|
176
|
+
return op + '(' + a.map(x => codegen(x)).join(', ') + ')'
|
|
177
|
+
}
|
package/src/compile.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
T, VAL, analyzeValTypes, analyzeIntCertain, analyzeLocals,
|
|
32
32
|
analyzePtrUnboxable, typedElemAux, invalidateLocalsCache,
|
|
33
33
|
analyzeBoxedCaptures, updateRep, inferStringParams,
|
|
34
|
+
isBlockBody,
|
|
34
35
|
} from './analyze.js'
|
|
35
36
|
import { optimizeFunc, treeshake } from './optimize.js'
|
|
36
37
|
import { emit, emitter, emitFlat, emitBody } from './emit.js'
|
|
@@ -138,6 +139,17 @@ const tcoTailRewrite = (ir, resultType) => {
|
|
|
138
139
|
return ir
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
const ensureThrowRuntime = (sec) => {
|
|
143
|
+
if (!ctx.runtime.throws) return
|
|
144
|
+
|
|
145
|
+
if (!ctx.scope.globals.has('__jz_last_err_bits'))
|
|
146
|
+
ctx.scope.globals.set('__jz_last_err_bits', '(global $__jz_last_err_bits (mut i64) (i64.const 0))')
|
|
147
|
+
if (!sec.tags.some(t => Array.isArray(t) && t[0] === 'tag' && t[1] === '$__jz_err'))
|
|
148
|
+
sec.tags.push(['tag', '$__jz_err', ['param', 'f64']])
|
|
149
|
+
if (!sec.tags.some(t => Array.isArray(t) && t[0] === 'export' && t[1] === '"__jz_last_err_bits"'))
|
|
150
|
+
sec.tags.push(['export', '"__jz_last_err_bits"', ['global', '$__jz_last_err_bits']])
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
// === Module compilation ===
|
|
142
154
|
|
|
143
155
|
const cloneRepMap = map => map ? new Map([...map].map(([k, v]) => [k, { ...v }])) : null
|
|
@@ -158,7 +170,7 @@ function analyzeFuncForEmit(func, programFacts) {
|
|
|
158
170
|
const { name, body, sig } = func
|
|
159
171
|
enterFunc(func)
|
|
160
172
|
|
|
161
|
-
const block =
|
|
173
|
+
const block = isBlockBody(body)
|
|
162
174
|
ctx.func.boxed = new Map()
|
|
163
175
|
ctx.func.repByLocal = null
|
|
164
176
|
ctx.types.typedElem = ctx.scope.globalTypedElem ? new Map(ctx.scope.globalTypedElem) : null
|
|
@@ -375,7 +387,8 @@ function emitFunc(func, funcFacts, programFacts) {
|
|
|
375
387
|
*
|
|
376
388
|
* Result rebox cases (then reinterpret to i64 at the boundary):
|
|
377
389
|
* - sig.ptrKind != null → mkPtrIR(ptrKind, ptrAux ?? 0, callIR)
|
|
378
|
-
* - sig.results[0] = i32 → f64.convert_i32_s(callIR)
|
|
390
|
+
* - sig.results[0] = i32 → f64.convert_i32_s(callIR), or `_u` when
|
|
391
|
+
* sig.unsignedResult (preserves `(x >>> 0)` ∈ [0, 2³²))
|
|
379
392
|
* - sig.results[0] = f64 → callIR (some params narrowed but result stayed f64)
|
|
380
393
|
*/
|
|
381
394
|
function synthesizeBoundaryWrappers() {
|
|
@@ -408,7 +421,7 @@ function synthesizeBoundaryWrappers() {
|
|
|
408
421
|
const ptrType = valKindToPtr(sig.ptrKind)
|
|
409
422
|
body = mkPtrIR(ptrType, sig.ptrAux ?? 0, callIR)
|
|
410
423
|
} else if (sig.results[0] === 'i32') {
|
|
411
|
-
body = ['f64.convert_i32_s', callIR]
|
|
424
|
+
body = [sig.unsignedResult ? 'f64.convert_i32_u' : 'f64.convert_i32_s', callIR]
|
|
412
425
|
} else {
|
|
413
426
|
body = callIR
|
|
414
427
|
}
|
|
@@ -485,7 +498,7 @@ function emitClosureBody(cb) {
|
|
|
485
498
|
}
|
|
486
499
|
|
|
487
500
|
// Emit body
|
|
488
|
-
const block =
|
|
501
|
+
const block = isBlockBody(cb.body)
|
|
489
502
|
let bodyIR
|
|
490
503
|
if (block) {
|
|
491
504
|
invalidateLocalsCache(cb.body)
|
|
@@ -612,7 +625,7 @@ function emitClosureBody(cb) {
|
|
|
612
625
|
['then', ['local.set', `$${restLen}`, ['i32.const', restSlots]]]])
|
|
613
626
|
fn.push(['local.set', `$${restOff}`,
|
|
614
627
|
['call', '$__alloc_hdr',
|
|
615
|
-
['local.get', `$${restLen}`], ['local.get', `$${restLen}`]
|
|
628
|
+
['local.get', `$${restLen}`], ['local.get', `$${restLen}`]]])
|
|
616
629
|
for (let i = 0; i < restSlots; i++) {
|
|
617
630
|
fn.push(['if', ['i32.gt_s', ['local.get', `$${restLen}`], ['i32.const', i]],
|
|
618
631
|
['then', ['f64.store',
|
|
@@ -775,12 +788,6 @@ export default function compile(ast, profiler) {
|
|
|
775
788
|
|
|
776
789
|
// Memory section deferred — emitted after resolveIncludes() when __alloc is needed
|
|
777
790
|
|
|
778
|
-
if (ctx.runtime.throws) {
|
|
779
|
-
ctx.scope.globals.set('__jz_last_err_bits', '(global $__jz_last_err_bits (mut i64) (i64.const 0))')
|
|
780
|
-
sec.tags.push(['tag', '$__jz_err', ['param', 'f64']])
|
|
781
|
-
sec.tags.push(['export', '"__jz_last_err_bits"', ['global', '$__jz_last_err_bits']])
|
|
782
|
-
}
|
|
783
|
-
|
|
784
791
|
if (ctx.closure.table?.length)
|
|
785
792
|
sec.table.push(['table', ['export', '"__jz_table"'], ctx.closure.table.length, 'funcref'])
|
|
786
793
|
|
|
@@ -829,6 +836,8 @@ export default function compile(ast, profiler) {
|
|
|
829
836
|
|
|
830
837
|
optimizeModule(sec)
|
|
831
838
|
|
|
839
|
+
ensureThrowRuntime(sec)
|
|
840
|
+
|
|
832
841
|
// Populate globals (after __start — const folding may update declarations)
|
|
833
842
|
sec.globals.push(...[...ctx.scope.globals.values()].filter(g => g).map(g => parseWat(g)))
|
|
834
843
|
|
|
@@ -918,7 +927,7 @@ export default function compile(ast, profiler) {
|
|
|
918
927
|
const { callCount } = treeshake(
|
|
919
928
|
[{ arr: sec.stdlib }, { arr: sec.funcs }, { arr: sec.start }],
|
|
920
929
|
[...sec.start, ...sec.elem, ...sec.customs, ...sec.extStdlib, ...sec.imports],
|
|
921
|
-
{ removeDead: !optCfg || optCfg.treeshake !== false }
|
|
930
|
+
{ removeDead: !optCfg || optCfg.treeshake !== false, globals: sec.globals }
|
|
922
931
|
)
|
|
923
932
|
|
|
924
933
|
// Reorder non-import funcs by call count: hot callees get low LEB128 indices.
|