jz 0.5.1 → 0.6.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 +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +266 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +414 -186
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +586 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +589 -202
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* @module assemble
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
import { ctx, inc, resolveIncludes, err, PTR, LAYOUT } from '
|
|
15
|
+
import parseWat from 'watr/parse'
|
|
16
|
+
import { ctx, inc, resolveIncludes, err, PTR, LAYOUT, HEAP, declGlobal } from '../ctx.js'
|
|
17
17
|
|
|
18
18
|
// Stdlib WAT templates are fixed text (or feature-keyed text from a factory) —
|
|
19
19
|
// `parseWat` of the same string always yields the same tree. Parsing is the
|
|
@@ -33,10 +33,12 @@ const parseTemplate = (str) => {
|
|
|
33
33
|
if (tmpl === undefined) stdlibParseCache.set(str, tmpl = parseWat(str))
|
|
34
34
|
return cloneTemplate(tmpl)
|
|
35
35
|
}
|
|
36
|
-
import { T
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
36
|
+
import { T } from '../ast.js'
|
|
37
|
+
import { analyzeValTypes, analyzeBody } from '../compile/analyze.js'
|
|
38
|
+
import { VAL } from '../reps.js'
|
|
39
|
+
import { optimizeFunc, collectVolatileGlobals, collectReachableGlobalWrites, hoistGlobalPtrOffset, stablePtrGlobalNames, hoistConstantPool, specializeMkptr, specializePtrBase, sortStrPoolByFreq, arenaRewindModule } from '../optimize/index.js'
|
|
40
|
+
import { emit } from '../compile/emit.js'
|
|
41
|
+
import { mkPtrIR, MAX_CLOSURE_ARITY, MEM_OPS, findBodyStart } from '../ir.js'
|
|
40
42
|
|
|
41
43
|
// NaN-prefix top-13-bits as BigInt — used by the static-prefix-strip pass
|
|
42
44
|
const NAN_PREFIX = BigInt(LAYOUT.NAN_PREFIX)
|
|
@@ -46,16 +48,16 @@ const TAG_SHIFT_BIG = BigInt(LAYOUT.TAG_SHIFT)
|
|
|
46
48
|
const AUX_SHIFT_BIG = BigInt(LAYOUT.AUX_SHIFT)
|
|
47
49
|
const SSO_BIT_BIG = BigInt(LAYOUT.SSO_BIT)
|
|
48
50
|
|
|
49
|
-
// memory[
|
|
51
|
+
// memory[HEAP.PTR_ADDR] holds the heap pointer only for shared memory (wasm globals are
|
|
50
52
|
// per-instance — see module/core.js comment). Non-shared memory uses $__heap.
|
|
51
53
|
const heapUsesMem = () => ctx.memory.shared
|
|
52
54
|
|
|
53
55
|
const heapGetIR = () => heapUsesMem()
|
|
54
|
-
? ['i32.load', ['i32.const',
|
|
56
|
+
? ['i32.load', ['i32.const', HEAP.PTR_ADDR]]
|
|
55
57
|
: ['global.get', '$__heap']
|
|
56
58
|
|
|
57
59
|
const heapSetIR = value => heapUsesMem()
|
|
58
|
-
? ['i32.store', ['i32.const',
|
|
60
|
+
? ['i32.store', ['i32.const', HEAP.PTR_ADDR], value]
|
|
59
61
|
: ['global.set', '$__heap', value]
|
|
60
62
|
|
|
61
63
|
const ARENA_SAFE_CALLS = new Set([
|
|
@@ -134,8 +136,20 @@ export function buildStartFn(ast, sec, closureFuncs, compilePendingClosures) {
|
|
|
134
136
|
ctx.func.locals = new Map()
|
|
135
137
|
ctx.func.localReps = null
|
|
136
138
|
ctx.func.boxed = new Map()
|
|
139
|
+
ctx.func.cellTypes = new Set()
|
|
137
140
|
ctx.func.stack = []
|
|
138
141
|
ctx.func.current = { params: [], results: [] }
|
|
142
|
+
// Reserve prepare-generated temp names (for-of `arrVar`/`idx`/`len`,
|
|
143
|
+
// destructure scratch, …) in the __start frame so emit's temp()/tempI32()
|
|
144
|
+
// skip them — the same pre-seed analyzeFuncForEmit gives every function frame.
|
|
145
|
+
// Only T-sentinel names: they're always __start locals (user module-scope
|
|
146
|
+
// bindings become globals and can't contain T). Without this, prepare's
|
|
147
|
+
// `${T}arr${n}` collides with an emit-time tempI32('arr') at the same uniq,
|
|
148
|
+
// declaring the array pointer's local i32 and corrupting it via convert_i32_s.
|
|
149
|
+
const seedGeneratedLocals = (body) => {
|
|
150
|
+
for (const [n, t] of analyzeBody(body).locals)
|
|
151
|
+
if (n.includes(T) && !ctx.func.locals.has(n)) ctx.func.locals.set(n, t)
|
|
152
|
+
}
|
|
139
153
|
analyzeValTypes(ast)
|
|
140
154
|
const normalizeIR = ir => !ir?.length ? [] : Array.isArray(ir[0]) ? ir : [ir]
|
|
141
155
|
|
|
@@ -143,9 +157,11 @@ export function buildStartFn(ast, sec, closureFuncs, compilePendingClosures) {
|
|
|
143
157
|
if (ctx.module.moduleInits) {
|
|
144
158
|
for (const mi of ctx.module.moduleInits) {
|
|
145
159
|
analyzeValTypes(mi)
|
|
160
|
+
seedGeneratedLocals(mi)
|
|
146
161
|
moduleInits.push(...normalizeIR(emit(mi)))
|
|
147
162
|
}
|
|
148
163
|
}
|
|
164
|
+
seedGeneratedLocals(ast)
|
|
149
165
|
const init = emit(ast)
|
|
150
166
|
|
|
151
167
|
// Module-scope object literals can create closure bodies while `emit(ast)`
|
|
@@ -342,8 +358,12 @@ export function finalizeClosureTable(sec) {
|
|
|
342
358
|
}
|
|
343
359
|
for (const fn of sec.funcs) { scan(fn); if (indirectUsed) break }
|
|
344
360
|
if (!indirectUsed) for (const fn of sec.start) scan(fn)
|
|
345
|
-
|
|
346
|
-
|
|
361
|
+
// stdlib values are mixed: WAT-template strings + lazy generator functions.
|
|
362
|
+
// Only the string templates can carry a literal `call_indirect`; a typeof
|
|
363
|
+
// guard skips the generators (where `.includes` is meaningless — and on a jz
|
|
364
|
+
// closure receiver would read the closure pointer as a string, out of bounds).
|
|
365
|
+
if (!indirectUsed) for (const tpl of Object.values(ctx.core.stdlib)) {
|
|
366
|
+
if (typeof tpl === 'string' && tpl.includes('call_indirect')) { indirectUsed = true; break }
|
|
347
367
|
}
|
|
348
368
|
if (indirectUsed) {
|
|
349
369
|
if (!ctx.closure.table) ctx.closure.table = []
|
|
@@ -409,7 +429,11 @@ export function pullStdlib(sec) {
|
|
|
409
429
|
const needsMemory = [...ctx.core.includes].some(n => ctx.core.stdlib[n] && MEM_OPS.test(ctx.core.stdlib[n]))
|
|
410
430
|
if (!needsMemory) ctx.scope.globals.delete('__heap')
|
|
411
431
|
if (needsMemory && ctx.module.modules.core) {
|
|
412
|
-
for (const fn of ['__alloc', '__alloc_hdr', '__clear'])
|
|
432
|
+
for (const fn of ['__alloc', '__alloc_hdr', '__clear']) ctx.core.includes.add(fn)
|
|
433
|
+
// Late-add of allocators may pull in transitive deps (__alloc → __memgrow,
|
|
434
|
+
// etc.) that the initial resolveIncludes did not yet see; re-resolve.
|
|
435
|
+
// No-op when the alloc trio was already present.
|
|
436
|
+
resolveIncludes()
|
|
413
437
|
const pages = ctx.memory.pages || 1
|
|
414
438
|
if (ctx.memory.shared) sec.imports.push(['import', '"env"', '"memory"', ['memory', pages]])
|
|
415
439
|
else sec.memory.push(['memory', ['export', '"memory"'], pages])
|
|
@@ -443,31 +467,34 @@ export function syncImports(sec) {
|
|
|
443
467
|
/**
|
|
444
468
|
* Phase: whole-module + per-function optimization passes.
|
|
445
469
|
*/
|
|
446
|
-
export function optimizeModule(sec) {
|
|
470
|
+
export function optimizeModule(sec, profiler) {
|
|
471
|
+
const t = profiler?.time ? (name, fn) => profiler.time(`optMod:${name}`, fn) : (_, fn) => fn()
|
|
447
472
|
const cfg = ctx.transform.optimize
|
|
448
|
-
if (!cfg || cfg.specializeMkptr !== false)
|
|
449
|
-
specializeMkptr([...sec.funcs, ...sec.stdlib, ...sec.start], wat => sec.stdlib.push(parseWat(wat)), parseWat)
|
|
450
|
-
if (!cfg || cfg.specializePtrBase !== false)
|
|
451
|
-
specializePtrBase([...sec.funcs, ...sec.stdlib, ...sec.start], wat => sec.stdlib.push(parseWat(wat)), parseWat)
|
|
452
|
-
if (ctx.runtime.strPool && (!cfg || cfg.sortStrPoolByFreq !== false)) {
|
|
473
|
+
if (!cfg || cfg.specializeMkptr !== false) t('specializeMkptr', () =>
|
|
474
|
+
specializeMkptr([...sec.funcs, ...sec.stdlib, ...sec.start], wat => sec.stdlib.push(parseWat(wat)), parseWat))
|
|
475
|
+
if (!cfg || cfg.specializePtrBase !== false) t('specializePtrBase', () =>
|
|
476
|
+
specializePtrBase([...sec.funcs, ...sec.stdlib, ...sec.start], wat => sec.stdlib.push(parseWat(wat)), parseWat))
|
|
477
|
+
if (ctx.runtime.strPool && (!cfg || cfg.sortStrPoolByFreq !== false)) t('sortStrPool', () => {
|
|
453
478
|
const poolRef = { pool: ctx.runtime.strPool }
|
|
454
479
|
sortStrPoolByFreq([...sec.funcs, ...sec.stdlib, ...sec.start], poolRef, ctx.runtime.strPoolDedup)
|
|
455
480
|
ctx.runtime.strPool = poolRef.pool
|
|
456
|
-
}
|
|
457
|
-
//
|
|
458
|
-
// (e.g., __schema_tbl, __strBase). Parses the WAT string to infer i32/f64/i64.
|
|
459
|
-
if (ctx.scope.globals) {
|
|
460
|
-
for (const [name, wat] of ctx.scope.globals) {
|
|
461
|
-
if (!wat || ctx.scope.globalTypes.has(name)) continue
|
|
462
|
-
const m = wat.match(/\(global\s+\$?\S+\s+(?:\(mut\s+)?(i32|i64|f64|f32)/)
|
|
463
|
-
if (m) ctx.scope.globalTypes.set(name, m[1])
|
|
464
|
-
}
|
|
465
|
-
}
|
|
481
|
+
})
|
|
482
|
+
// (globalTypes backfill gone: declGlobal sets the type at declaration.)
|
|
466
483
|
// Build global name→type map from ctx.scope.globalTypes (keys without $) for promoteGlobals
|
|
467
484
|
const globalTypesMap = ctx.scope.globalTypes ? new Map([...ctx.scope.globalTypes].map(([k, v]) => [`$${k}`, v])) : null
|
|
468
485
|
const allFuncs = [...sec.funcs, ...sec.stdlib, ...sec.start]
|
|
469
|
-
const volatileGlobals = collectVolatileGlobals(allFuncs)
|
|
470
|
-
|
|
486
|
+
const volatileGlobals = t('volatileGlobals', () => collectVolatileGlobals(allFuncs))
|
|
487
|
+
const reachableWrites = t('reachableWrites', () => collectReachableGlobalWrites(allFuncs))
|
|
488
|
+
// Offset-hoist BEFORE promoteGlobals (inside optimizeFunc): value-promoting a
|
|
489
|
+
// stable-pointee global to a $_pg local would destroy the global.get pattern
|
|
490
|
+
// this pass matches, reverting rfft/diffusion to per-iteration resolves. After
|
|
491
|
+
// the hoist, the surviving global.get count is 1 (the entry snap) — naturally
|
|
492
|
+
// below promoteGlobals' threshold, so the two passes compose either way.
|
|
493
|
+
if (!cfg || cfg.hoistGlobalPtrOffset !== false) t('hoistGlobalPtr', () => {
|
|
494
|
+
const stable = stablePtrGlobalNames()
|
|
495
|
+
if (stable.size) for (const s of allFuncs) hoistGlobalPtrOffset(s, stable, reachableWrites)
|
|
496
|
+
})
|
|
497
|
+
t('optimizeFuncs', () => { for (const s of allFuncs) optimizeFunc(s, cfg, globalTypesMap, volatileGlobals, 'pre', reachableWrites) })
|
|
471
498
|
if (!cfg || cfg.arenaRewind !== false) {
|
|
472
499
|
const safeCallees = arenaRewindModule([...sec.funcs, ...sec.stdlib, ...sec.start])
|
|
473
500
|
const fnByName = new Map()
|
|
@@ -481,11 +508,7 @@ export function optimizeModule(sec) {
|
|
|
481
508
|
}
|
|
482
509
|
}
|
|
483
510
|
if (!cfg || cfg.hoistConstantPool !== false)
|
|
484
|
-
hoistConstantPool([...sec.funcs, ...sec.stdlib, ...sec.start], (name,
|
|
485
|
-
ctx.scope.globals.set(name, wat)
|
|
486
|
-
const m = wat.match(/\(global\s+\$?\S+\s+(?:\(mut\s+)?(i32|i64|f64|f32)/)
|
|
487
|
-
if (m) ctx.scope.globalTypes.set(name, m[1])
|
|
488
|
-
})
|
|
511
|
+
hoistConstantPool([...sec.funcs, ...sec.stdlib, ...sec.start], (name, lit) => declGlobal(name, 'f64', lit))
|
|
489
512
|
|
|
490
513
|
// Second promoteGlobals pass disabled: promoting hoistConstantPool's __fc*
|
|
491
514
|
// globals regressed the watr perf micro-pin (WASM compile time increased).
|
|
@@ -502,12 +525,8 @@ export function optimizeModule(sec) {
|
|
|
502
525
|
const heapBase = (dataLen + 7) & ~7
|
|
503
526
|
// Non-shared memory always carries a $__heap global — start it past the
|
|
504
527
|
// static data so the bump allocator never overwrites a literal.
|
|
505
|
-
|
|
506
|
-
ctx.scope.
|
|
507
|
-
if (ctx.scope.globals.has('__heap_start')) {
|
|
508
|
-
ctx.scope.globals.set('__heap_start', `(global $__heap_start (mut i32) (i32.const ${heapBase}))`)
|
|
509
|
-
ctx.scope.globalTypes.set('__heap_start', 'i32')
|
|
510
|
-
}
|
|
528
|
+
declGlobal('__heap', 'i32', heapBase, { export: '__heap' })
|
|
529
|
+
if (ctx.scope.globals.has('__heap_start')) declGlobal('__heap_start', 'i32', heapBase)
|
|
511
530
|
for (const s of sec.stdlib)
|
|
512
531
|
if (s[0] === 'func' && s[1] === '$__clear')
|
|
513
532
|
for (let i = 2; i < s.length; i++)
|
|
@@ -528,18 +547,33 @@ export function stripStaticDataPrefix(sec) {
|
|
|
528
547
|
for (let i = 0; i < data.length; i++) buf[i] = data.charCodeAt(i)
|
|
529
548
|
const dv = new DataView(buf.buffer)
|
|
530
549
|
if (ctx.runtime.staticPtrSlots) {
|
|
550
|
+
// u32-half reads/writes — DataView's BigInt accessors are unfaithful in the
|
|
551
|
+
// self-host kernel; the offset lives entirely in the LE low word and the
|
|
552
|
+
// tag/aux fields entirely in the high word, so plain number math suffices.
|
|
531
553
|
for (const slotOff of ctx.runtime.staticPtrSlots) {
|
|
532
554
|
if (slotOff < prefix) continue
|
|
533
|
-
const
|
|
534
|
-
if (((
|
|
535
|
-
const ty =
|
|
555
|
+
const hi = dv.getUint32(slotOff + 4, true)
|
|
556
|
+
if (((hi >>> 16) & 0xFFF8) !== LAYOUT.NAN_PREFIX) continue
|
|
557
|
+
const ty = (hi >>> 15) & 15
|
|
536
558
|
if (!SHIFTABLE.has(ty)) continue
|
|
537
|
-
if (ty === PTR.STRING && ((
|
|
538
|
-
const off =
|
|
559
|
+
if (ty === PTR.STRING && ((hi >>> (LAYOUT.AUX_SHIFT - 32)) & LAYOUT.SSO_BIT)) continue
|
|
560
|
+
const off = dv.getUint32(slotOff, true)
|
|
539
561
|
if (off < prefix) continue
|
|
540
|
-
|
|
541
|
-
|
|
562
|
+
dv.setUint32(slotOff, off - prefix, true)
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// The intern index (buildInternTable) stores raw static-string ptrs as u32
|
|
566
|
+
// slots — shift each occupied slot like every other static reference, and
|
|
567
|
+
// re-declare the (already-declared) base global at its post-strip position.
|
|
568
|
+
if (ctx.runtime.internTable) {
|
|
569
|
+
const { base, size } = ctx.runtime.internTable
|
|
570
|
+
for (let i = 0; i < size; i++) {
|
|
571
|
+
const slot = base + i * 8 + 4
|
|
572
|
+
const off = dv.getUint32(slot, true)
|
|
573
|
+
if (off >= prefix) dv.setUint32(slot, off - prefix, true)
|
|
542
574
|
}
|
|
575
|
+
ctx.runtime.internTable.base = base - prefix
|
|
576
|
+
declGlobal('__internBase', 'i32', base - prefix, { mut: false })
|
|
543
577
|
}
|
|
544
578
|
let s = ''
|
|
545
579
|
for (let i = prefix; i < buf.length; i++) s += String.fromCharCode(buf[i])
|
|
@@ -559,6 +593,10 @@ export function stripStaticDataPrefix(sec) {
|
|
|
559
593
|
Array.isArray(child[3]) && child[3][0] === 'i32.const' &&
|
|
560
594
|
typeof child[3][1] === 'number' && (child[3][1] & LAYOUT.SSO_BIT)
|
|
561
595
|
if (!isSsoString) child[4][1] -= prefix
|
|
596
|
+
} else if (typeof child[0] === 'string' && child[0].endsWith('.store') &&
|
|
597
|
+
Array.isArray(child[1]) && child[1][0] === 'i32.const' &&
|
|
598
|
+
typeof child[1][1] === 'number' && child[1][1] >= prefix) {
|
|
599
|
+
child[1][1] -= prefix
|
|
562
600
|
} else if (child[0] === 'f64.const' &&
|
|
563
601
|
typeof child[1] === 'string' && child[1].startsWith('nan:0x')) {
|
|
564
602
|
const bits = BigInt(child[1].slice(4)) | 0x7FF0000000000000n
|