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/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Options are passed as `jz(source, opts)` or `compile(source, opts)`. Common ones
|
|
|
68
68
|
| `imports: { mod: host }` | Wire host namespaces/functions used by `import { fn } from "mod"`; functions may be plain JS functions or `{ fn, returns }` specs. |
|
|
69
69
|
| `memory` | Pass `memory: N` to create owned memory with `N` initial pages, or pass `memory: jz.memory()` / `WebAssembly.Memory` to share memory across modules. |
|
|
70
70
|
| `host: 'js' \| 'wasi'` | Select runtime-service lowering. Default `js` uses small `env.*` imports auto-wired by `jz()`; `wasi` emits WASI Preview 1 imports for wasmtime/wasmer/deno. |
|
|
71
|
-
| `optimize` | `false`/`0` disables optimization, `1` keeps cheap size passes, `true`/`2` is the default, `3` enables aggressive experimental passes,
|
|
71
|
+
| `optimize` | `false`/`0` disables optimization, `1` keeps cheap size passes, `true`/`2` is the default, `3` enables aggressive experimental passes. String aliases `'size'` (unroll/vectorize off, tight scalar caps — smallest wasm), `'balanced'` (= default), `'speed'` (full unroll + SIMD). Object form overrides individual passes/knobs (and accepts `level:` as a number or alias base). |
|
|
72
72
|
| `strict: true` | Reject dynamic fallbacks such as unknown receiver method calls, `obj[k]`, and `for-in` instead of emitting JS-host dynamic dispatch. |
|
|
73
73
|
| `alloc: false` | Omit raw allocator exports like `_alloc`/`_clear` when compiling standalone WASM that never marshals heap values across the host boundary. |
|
|
74
74
|
| `wat: true` | `compile()` returns WAT text instead of a WASM binary. |
|
|
@@ -82,7 +82,16 @@ Options are passed as `jz(source, opts)` or `compile(source, opts)`. Common ones
|
|
|
82
82
|
|
|
83
83
|
```sh
|
|
84
84
|
# Compile
|
|
85
|
-
jz program.js
|
|
85
|
+
jz program.js # → program.wasm
|
|
86
|
+
jz program.js --wat # → program.wat
|
|
87
|
+
jz program.js -o out.wasm # custom output (- for stdout)
|
|
88
|
+
|
|
89
|
+
# Optimization level: -O0 off, -O1 size-only, -O2 default, -O3 aggressive
|
|
90
|
+
# aliases: -Os/--optimize size, -Ob/balanced, -Of/speed
|
|
91
|
+
jz program.js -O3
|
|
92
|
+
|
|
93
|
+
# Runtime-service lowering: js (default) or wasi
|
|
94
|
+
jz program.js --host wasi
|
|
86
95
|
|
|
87
96
|
# Evaluate
|
|
88
97
|
jz -e "1 + 2" # 3
|
|
@@ -91,33 +100,12 @@ jz -e "1 + 2" # 3
|
|
|
91
100
|
jz --help
|
|
92
101
|
```
|
|
93
102
|
|
|
103
|
+
Other flags: `--strict` (no auto-`jzify`, reject dynamic fallbacks), `--jzify` (transform JS → jz, no compile), `--no-alloc` (omit `_alloc`/`_clear`), `--names` (emit wasm `name` section), `--resolve` (Node.js bare-specifier resolution), `--imports <file.json>` (host import specs).
|
|
104
|
+
|
|
94
105
|
## Language
|
|
95
106
|
|
|
96
107
|
JZ is a strict functional JS subset. Built-in `jzify` transform extends support to legacy patterns.
|
|
97
108
|
|
|
98
|
-
<!--
|
|
99
|
-
```mermaid
|
|
100
|
-
%%{init: {'flowchart': {'titleTopMargin': 0, 'padding': 0, 'margin': 0}}}%%
|
|
101
|
-
flowchart TB
|
|
102
|
-
classDef plain fill:none,stroke:none,font-size:14px,font-weight:bold,padding:0px,margin:0px
|
|
103
|
-
|
|
104
|
-
subgraph JS[JS — not supported]
|
|
105
|
-
subgraph JZify[JZ + jzify]
|
|
106
|
-
subgraph JZ[JZ strict]
|
|
107
|
-
j1["let/const, arrows, default/rest params, flow, break/continue, try/catch/finally, a[]/a()/a.b, operators, strings, booleans, numbers, std, memory, host"]:::plain
|
|
108
|
-
end
|
|
109
|
-
z1["var, function, arguments, switch, new Foo(), ==, !=, instanceof"]:::plain
|
|
110
|
-
end
|
|
111
|
-
n1["async/await, Promise, generators, this, class, eval, Function, with, Proxy, Reflect, WeakMap, WeakSet, dynamic import, DOM, fetch, Intl, Node APIs"]:::plain
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
style JZ fill:#ffe0b2,stroke-width:0
|
|
115
|
-
style JZify fill:#fff9c4,stroke-width:0
|
|
116
|
-
style JS fill:#ffffff,stroke:#ccc,stroke-width:1px
|
|
117
|
-
style n1 min-width:720px
|
|
118
|
-
```
|
|
119
|
-
-->
|
|
120
|
-
|
|
121
109
|
```
|
|
122
110
|
┌────────────────────────────────────────────────────────────────────────┐
|
|
123
111
|
│ JZify │
|
|
@@ -151,6 +139,10 @@ Not supported
|
|
|
151
139
|
|
|
152
140
|
Numbers pass directly as f64, arrays of ≤ 8 elements return as plain JS arrays (multi-value). Strings, arrays, objects, and typed arrays are heap values — `inst.memory` provides read/write across the boundary:
|
|
153
141
|
|
|
142
|
+
> [!WARNING] jz objects are fixed-layout schemas (like C structs), not dynamic key bags.
|
|
143
|
+
> `memory.Object({ x: 3, y: 4 })` expects the same key order as the jz source `{ x, y }`.
|
|
144
|
+
> `{ y: 4, x: 3 }` with reversed keys will produce wrong values.
|
|
145
|
+
|
|
154
146
|
```js
|
|
155
147
|
const { exports, memory } = jz`
|
|
156
148
|
export let greet = (s) => s.length
|
|
@@ -494,7 +486,7 @@ cc program.c -o program
|
|
|
494
486
|
| [bitwise](bench/bitwise/bitwise.js) | 0.98ms<br>1.3kB | 3.76ms<br>1005 B | fails | 8.79ms<br>1.5kB | 4.86ms<br>355 B | 1.30ms | 5.20ms | 4.15ms | 1.30ms | 14.72ms |
|
|
495
487
|
| [poly](bench/poly/poly.js) | 0.27ms<br>1.4kB | 1.62ms<br>1014 B | fails | 0.73ms<br>1.3kB | 0.81ms<br>359 B | 0.57ms | 0.79ms | 0.89ms | 0.63ms | 0.60ms |
|
|
496
488
|
| [callback](bench/callback/callback.js) | 0.03ms<br>1.6kB | 0.69ms<br>828 B | fails | 1.04ms<br>1.9kB | 0.24ms<br>267 B | 0.08ms | 0.23ms | 0.01ms | 0.12ms | 1.78ms |
|
|
497
|
-
| [json](bench/json/json.js) | 0.
|
|
489
|
+
| [json](bench/json/json.js) | 0.25ms<br>10.9kB | 0.36ms<br>1.2kB | fails | — | — | 0.25ms | 1.16ms | 0.64ms | 0.65ms | 1.20ms |
|
|
498
490
|
| [watr](bench/watr/watr.js) | 1.04ms<br>169.8kB | 1.05ms<br>2.6kB | fails | — | — | — | — | — | — | — |
|
|
499
491
|
|
|
500
492
|
_Numbers from `node bench/bench.mjs` on Apple Silicon. Porffor cells were refreshed with `porf` 0.61.13; `fails` means the latest Porffor compiler/runtime did not complete that benchmark._
|
package/cli.js
CHANGED
|
@@ -11,10 +11,14 @@ import { execFileSync } from 'child_process'
|
|
|
11
11
|
import { parse } from 'subscript/feature/jessie'
|
|
12
12
|
import jz, { compile } from './index.js'
|
|
13
13
|
import jzifyFn, { codegen } from './src/jzify.js'
|
|
14
|
+
import { createRequire } from 'module'
|
|
15
|
+
|
|
16
|
+
const jzRequire = createRequire(import.meta.url)
|
|
17
|
+
const PKG = jzRequire('./package.json')
|
|
14
18
|
|
|
15
19
|
function showHelp() {
|
|
16
20
|
console.log(`
|
|
17
|
-
jz - min JS → WASM compiler
|
|
21
|
+
jz v${PKG.version} - min JS → WASM compiler
|
|
18
22
|
|
|
19
23
|
Usage:
|
|
20
24
|
jz <file.js> Compile JS to WASM (auto-jzify)
|
|
@@ -28,23 +32,38 @@ Examples:
|
|
|
28
32
|
jz program.js --wat # → program.wat
|
|
29
33
|
jz program.js -o out.wasm # custom output name
|
|
30
34
|
jz program.js -o - # write to stdout
|
|
35
|
+
jz program.js -O3 # aggressive optimization
|
|
36
|
+
jz program.js -Os # optimize for size
|
|
37
|
+
jz program.js --host wasi # emit WASI Preview 1 imports
|
|
31
38
|
jz --strict program.js # strict mode
|
|
32
39
|
jz --jzify lib.js # → lib.jz
|
|
33
40
|
jz -e "1 + 2"
|
|
34
41
|
|
|
35
42
|
Options:
|
|
36
43
|
--output, -o <file> Output file (.wat, .wasm, or - for stdout)
|
|
37
|
-
--
|
|
44
|
+
-O<n>, --optimize <n> Optimization level: 0 off, 1 size-only, 2 default,
|
|
45
|
+
3 aggressive. Aliases: -Os/size, -Ob/balanced, -Of/speed.
|
|
46
|
+
--host <js|wasi> Runtime-service lowering (default js)
|
|
47
|
+
--no-alloc Omit _alloc/_clear allocator exports (standalone wasm)
|
|
48
|
+
--names Emit wasm name section for profilers/debuggers
|
|
49
|
+
--strict Strict jz mode (no auto-transform), reject dynamic fallbacks
|
|
38
50
|
--jzify Transform JS to jz (no compilation)
|
|
39
51
|
--eval, -e Evaluate expression or file
|
|
40
52
|
--wat Output WAT text instead of binary
|
|
41
53
|
--resolve Resolve bare specifiers via Node.js module resolution
|
|
54
|
+
--imports <file> JSON file with host import specs (e.g. {"env":{"fn":{"params":2}}})
|
|
55
|
+
--version, -v Show version number
|
|
42
56
|
`)
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
async function main() {
|
|
46
60
|
const args = process.argv.slice(2)
|
|
47
61
|
|
|
62
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
63
|
+
console.log(PKG.version)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
49
68
|
showHelp()
|
|
50
69
|
return
|
|
@@ -98,15 +117,31 @@ async function handleJzify(args) {
|
|
|
98
117
|
}
|
|
99
118
|
}
|
|
100
119
|
|
|
120
|
+
// -O<n>/-Os/-Ob/-Of and --optimize <val> → value accepted by compile()'s `optimize` opt
|
|
121
|
+
const OPT_ALIAS = { s: 'size', b: 'balanced', f: 'speed' }
|
|
122
|
+
function parseOptimize(v) {
|
|
123
|
+
if (v == null) return undefined
|
|
124
|
+
if (/^\d+$/.test(v)) return +v
|
|
125
|
+
return OPT_ALIAS[v] ?? v
|
|
126
|
+
}
|
|
127
|
+
|
|
101
128
|
async function handleCompile(args) {
|
|
102
|
-
let inputFile = null, outputFile = null, wat = false, strict = false, resolveNode = false
|
|
129
|
+
let inputFile = null, outputFile = null, wat = false, strict = false, resolveNode = false, importsFile = null
|
|
130
|
+
let optimize, host, alloc = true, names = false
|
|
103
131
|
|
|
104
132
|
for (let i = 0; i < args.length; i++) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
else if (
|
|
108
|
-
else if (
|
|
109
|
-
else if (
|
|
133
|
+
const a = args[i]
|
|
134
|
+
if (a === '--output' || a === '-o') outputFile = args[++i]
|
|
135
|
+
else if (a === '--wat') wat = true
|
|
136
|
+
else if (a === '--strict') strict = true
|
|
137
|
+
else if (a === '--resolve') resolveNode = true
|
|
138
|
+
else if (a === '--imports') importsFile = args[++i]
|
|
139
|
+
else if (a === '--optimize' || a === '-O') optimize = parseOptimize(args[++i])
|
|
140
|
+
else if (/^-O.+/.test(a)) optimize = parseOptimize(a.slice(2))
|
|
141
|
+
else if (a === '--host') host = args[++i]
|
|
142
|
+
else if (a === '--no-alloc') alloc = false
|
|
143
|
+
else if (a === '--names') names = true
|
|
144
|
+
else if (!inputFile) inputFile = a
|
|
110
145
|
}
|
|
111
146
|
|
|
112
147
|
if (!inputFile) throw new Error('No input file specified')
|
|
@@ -166,10 +201,20 @@ async function handleCompile(args) {
|
|
|
166
201
|
const opts = {
|
|
167
202
|
wat,
|
|
168
203
|
jzify: !strict && !inputFile.endsWith('.jz'),
|
|
204
|
+
strict,
|
|
169
205
|
importMetaUrl: pathToFileURL(resolve(inputFile)).href,
|
|
206
|
+
...(optimize !== undefined && { optimize }),
|
|
207
|
+
...(host && { host }),
|
|
208
|
+
...(alloc === false && { alloc: false }),
|
|
209
|
+
...(names && { profileNames: true }),
|
|
170
210
|
...(Object.keys(modules).length && { modules }),
|
|
171
211
|
}
|
|
172
212
|
|
|
213
|
+
if (importsFile) {
|
|
214
|
+
const importsPath = resolve(importsFile)
|
|
215
|
+
opts.imports = JSON.parse(readFileSync(importsPath, 'utf8'))
|
|
216
|
+
}
|
|
217
|
+
|
|
173
218
|
const result = compile(code, opts)
|
|
174
219
|
|
|
175
220
|
if (outputFile === '-') {
|
package/index.js
CHANGED
|
@@ -45,6 +45,7 @@ import prepare, { GLOBALS } from './src/prepare.js'
|
|
|
45
45
|
import compile from './src/compile.js'
|
|
46
46
|
import { emitter } from './src/emit.js'
|
|
47
47
|
import { optimizeFunc, resolveOptimize } from './src/optimize.js'
|
|
48
|
+
import { detectOptimizeConfig } from './src/auto-config.js'
|
|
48
49
|
import jzify from './src/jzify.js'
|
|
49
50
|
import {
|
|
50
51
|
memory as enhanceMemory, instantiate as instantiateRuntime,
|
|
@@ -224,6 +225,16 @@ jz.compile = (code, opts = {}) => {
|
|
|
224
225
|
let parsed = time('parse', () => parse(code))
|
|
225
226
|
if (opts.jzify) parsed = time('jzify', () => jzify(parsed))
|
|
226
227
|
const ast = time('prepare', () => prepare(parsed))
|
|
228
|
+
|
|
229
|
+
// Auto-detect optimization tuning from source characteristics when the user
|
|
230
|
+
// hasn't provided any optimize option.
|
|
231
|
+
if (opts.optimize == null) {
|
|
232
|
+
const autoCfg = detectOptimizeConfig(ast, code)
|
|
233
|
+
if (Object.keys(autoCfg).length) {
|
|
234
|
+
ctx.transform.optimize = resolveOptimize(autoCfg)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
227
238
|
const module = time('compile', () => compile(ast, profiler))
|
|
228
239
|
|
|
229
240
|
// host: 'wasi' — error if the wasm would import any env.__ext_* helper. Those exist
|
|
@@ -249,8 +260,10 @@ jz.compile = (code, opts = {}) => {
|
|
|
249
260
|
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
250
261
|
if (cfg.watr) {
|
|
251
262
|
const postCfg = { ...cfg, __phase: 'post' }
|
|
263
|
+
// Build global name→type map from ctx.scope.globalTypes for promoteGlobals
|
|
264
|
+
const globalTypesMap = ctx.scope.globalTypes ? new Map([...ctx.scope.globalTypes].map(([k, v]) => [`$${k}`, v])) : null
|
|
252
265
|
time('watrReopt', () => {
|
|
253
|
-
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, postCfg)
|
|
266
|
+
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, postCfg, globalTypesMap)
|
|
254
267
|
})
|
|
255
268
|
}
|
|
256
269
|
if (opts.wat) return time('watrPrint', () => watrPrint(optimized))
|
package/module/array.js
CHANGED
|
@@ -225,7 +225,8 @@ export default (ctx) => {
|
|
|
225
225
|
'__ptr_offset',
|
|
226
226
|
...(needsArrayDynMove() ? ['__dyn_move', '__hash_new', '__ihash_set_local'] : []),
|
|
227
227
|
],
|
|
228
|
-
__arr_set_idx_ptr: ['__arr_grow', '__ptr_offset'
|
|
228
|
+
__arr_set_idx_ptr: ['__arr_grow', '__ptr_offset'],
|
|
229
|
+
__arr_push1: ['__arr_grow_known', '__ptr_offset'],
|
|
229
230
|
__typed_idx: () => ctx.features.typedarray || ctx.features.external
|
|
230
231
|
? ['__len']
|
|
231
232
|
: ['__len', '__ptr_offset'],
|
|
@@ -247,6 +248,16 @@ export default (ctx) => {
|
|
|
247
248
|
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', PTR.ARRAY]]], 'i32')
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
ctx.core.emit['new.Array'] = (len) => {
|
|
252
|
+
const n = tempI32('alen')
|
|
253
|
+
const nIR = ['local.get', `$${n}`]
|
|
254
|
+
const out = allocPtr({ type: PTR.ARRAY, len: nIR, cap: nIR, tag: 'newarr' })
|
|
255
|
+
return typed(['block', ['result', 'f64'],
|
|
256
|
+
['local.set', `$${n}`, len == null ? ['i32.const', 0] : asI32(emit(len))],
|
|
257
|
+
out.init,
|
|
258
|
+
out.ptr], 'f64')
|
|
259
|
+
}
|
|
260
|
+
|
|
250
261
|
// ARRAY-only indexed read. Inline forwarding-follow + bounds check + load — avoids
|
|
251
262
|
// the redundant double pass through __len then __ptr_offset that both follow forwarding.
|
|
252
263
|
ctx.core.stdlib['__arr_idx'] = `(func $__arr_idx (param $ptr i64) (param $i i32) (result f64)
|
|
@@ -313,8 +324,21 @@ export default (ctx) => {
|
|
|
313
324
|
return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
|
|
314
325
|
(local $t i32) (local $off i32) (local $et i32) (local $len i32) (local $aux i32)
|
|
315
326
|
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
316
|
-
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
317
327
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
328
|
+
;; ARRAY fast path: follow forwarding inline, bounds-check against header len, f64.load — no $__len call.
|
|
329
|
+
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.ARRAY})) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
330
|
+
(then
|
|
331
|
+
(block $done
|
|
332
|
+
(loop $follow
|
|
333
|
+
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
334
|
+
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
335
|
+
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
336
|
+
(br $follow)))
|
|
337
|
+
(return (if (result f64)
|
|
338
|
+
(i32.and (i32.ge_s (local.get $i) (i32.const 0)) (i32.lt_u (local.get $i) (i32.load (i32.sub (local.get $off) (i32.const 8)))))
|
|
339
|
+
(then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
340
|
+
(else (f64.const nan:${UNDEF_NAN}))))))
|
|
341
|
+
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
318
342
|
(if
|
|
319
343
|
(i32.and
|
|
320
344
|
(i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
|
|
@@ -461,13 +485,29 @@ export default (ctx) => {
|
|
|
461
485
|
(i32.load (i32.sub (local.get $base) (i32.const 8))))
|
|
462
486
|
(then
|
|
463
487
|
(local.set $p (call $__arr_grow (local.get $ptr) (i32.add (local.get $i) (i32.const 1))))
|
|
464
|
-
(call $
|
|
465
|
-
(local.
|
|
488
|
+
(local.set $base (call $__ptr_offset (i64.reinterpret_f64 (local.get $p))))
|
|
489
|
+
(i32.store (i32.sub (local.get $base) (i32.const 8)) (i32.add (local.get $i) (i32.const 1)))))
|
|
466
490
|
(f64.store
|
|
467
491
|
(i32.add (local.get $base) (i32.shl (local.get $i) (i32.const 3)))
|
|
468
492
|
(local.get $val))
|
|
469
493
|
(local.get $p))`
|
|
470
494
|
|
|
495
|
+
// Out-of-line .push(val) for known-ARRAY receivers — keeps each call site to a
|
|
496
|
+
// single call + var update instead of ~30 inlined instructions. Returns the
|
|
497
|
+
// (possibly relocated) array pointer; caller derives the new length if needed.
|
|
498
|
+
ctx.core.stdlib['__arr_push1'] = `(func $__arr_push1 (param $ptr i64) (param $val f64) (result f64)
|
|
499
|
+
(local $p f64) (local $base i32) (local $len i32)
|
|
500
|
+
(local.set $p (f64.reinterpret_i64 (local.get $ptr)))
|
|
501
|
+
(local.set $base (call $__ptr_offset (local.get $ptr)))
|
|
502
|
+
(local.set $len (i32.load (i32.sub (local.get $base) (i32.const 8))))
|
|
503
|
+
(if (i32.lt_s (i32.load (i32.sub (local.get $base) (i32.const 4))) (i32.add (local.get $len) (i32.const 1)))
|
|
504
|
+
(then
|
|
505
|
+
(local.set $p (call $__arr_grow_known (local.get $ptr) (i32.add (local.get $len) (i32.const 1))))
|
|
506
|
+
(local.set $base (call $__ptr_offset (i64.reinterpret_f64 (local.get $p))))))
|
|
507
|
+
(f64.store (i32.add (local.get $base) (i32.shl (local.get $len) (i32.const 3))) (local.get $val))
|
|
508
|
+
(i32.store (i32.sub (local.get $base) (i32.const 8)) (i32.add (local.get $len) (i32.const 1)))
|
|
509
|
+
(local.get $p))`
|
|
510
|
+
|
|
471
511
|
// === Array literal ===
|
|
472
512
|
|
|
473
513
|
ctx.core.emit['['] = (...elems) => {
|
|
@@ -725,6 +765,21 @@ export default (ctx) => {
|
|
|
725
765
|
|
|
726
766
|
// .push(val) → append, increment len, return array (possibly reallocated pointer)
|
|
727
767
|
ctx.core.emit['.push'] = (arr, ...vals) => {
|
|
768
|
+
// Out-of-line fast path: single value, named known-ARRAY receiver. One call +
|
|
769
|
+
// var update instead of ~30 inlined instructions — the dominant size cost of
|
|
770
|
+
// push-heavy code (e.g. watr's WASM emitter).
|
|
771
|
+
if (vals.length === 1 && typeof arr === 'string' && lookupValType(arr) === VAL.ARRAY) {
|
|
772
|
+
inc('__arr_push1')
|
|
773
|
+
const box = ctx.func.boxed?.get(arr)
|
|
774
|
+
const isGlobal = !box && ctx.scope.globals.has(arr) && !ctx.func.locals?.has(arr)
|
|
775
|
+
const readVar = box ? ['f64.load', ['local.get', `$${box}`]] : isGlobal ? ['global.get', `$${arr}`] : ['local.get', `$${arr}`]
|
|
776
|
+
const writeVar = v => box ? ['f64.store', ['local.get', `$${box}`], v] : isGlobal ? ['global.set', `$${arr}`, v] : ['local.set', `$${arr}`, v]
|
|
777
|
+
const vv = asF64(emit(vals[0]))
|
|
778
|
+
return typed(['block', ['result', 'f64'],
|
|
779
|
+
writeVar(['call', '$__arr_push1', ['i64.reinterpret_f64', readVar], vv]),
|
|
780
|
+
['f64.convert_i32_s', ['i32.load', ['i32.sub',
|
|
781
|
+
['call', '$__ptr_offset', ['i64.reinterpret_f64', readVar]], ['i32.const', 8]]]]], 'f64')
|
|
782
|
+
}
|
|
728
783
|
const va = asF64(emit(arr))
|
|
729
784
|
const t = temp('pp'), len = tempI32('pl')
|
|
730
785
|
|
|
@@ -779,8 +834,10 @@ export default (ctx) => {
|
|
|
779
834
|
)
|
|
780
835
|
}
|
|
781
836
|
|
|
782
|
-
// Update length header
|
|
783
|
-
|
|
837
|
+
// Update length header (write directly via the offset we already hold —
|
|
838
|
+
// skips __set_len's tag/forward dispatch), update source variable (pointer
|
|
839
|
+
// may have changed from grow), return new length
|
|
840
|
+
body.push(['i32.store', ['i32.sub', ['local.get', `$${pushBase}`], ['i32.const', 8]], ['local.get', `$${len}`]])
|
|
784
841
|
// Update the source variable if it's a named variable (so arr still points to valid memory)
|
|
785
842
|
if (typeof arr === 'string') {
|
|
786
843
|
if (ctx.func.boxed?.has(arr)) {
|
|
@@ -898,8 +955,8 @@ export default (ctx) => {
|
|
|
898
955
|
['i32.shl',
|
|
899
956
|
['i32.sub', ['i32.sub', ['local.get', `$${len}`], ['local.get', `$${s}`]], ['local.get', `$${cnt}`]],
|
|
900
957
|
['i32.const', 3]]],
|
|
901
|
-
// update length
|
|
902
|
-
['
|
|
958
|
+
// update length (write directly via the offset we already hold)
|
|
959
|
+
['i32.store', ['i32.sub', ['local.get', `$${off}`], ['i32.const', 8]], ['i32.sub', ['local.get', `$${len}`], ['local.get', `$${cnt}`]]],
|
|
903
960
|
out.ptr,
|
|
904
961
|
]
|
|
905
962
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
@@ -919,7 +976,7 @@ export default (ctx) => {
|
|
|
919
976
|
(local.get $off)
|
|
920
977
|
(i32.shl (local.get $len) (i32.const 3)))
|
|
921
978
|
(f64.store (local.get $off) (local.get $val))
|
|
922
|
-
(
|
|
979
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8)) (i32.add (local.get $len) (i32.const 1)))
|
|
923
980
|
(f64.convert_i32_s (i32.add (local.get $len) (i32.const 1))))`
|
|
924
981
|
|
|
925
982
|
// .some(fn) → return 1 if any element passes, else 0 (early exit)
|