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
package/src/ast.js
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Jessie/subscript AST shape helpers and walks.
|
|
3
|
+
*
|
|
4
|
+
* Cycle-free: no ctx/analyze/ir imports. Shared with abi/* and ir.js.
|
|
5
|
+
*
|
|
6
|
+
* @module ast
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Template placeholder in prepared AST (prepare.js). */
|
|
10
|
+
export const T = '\uE000'
|
|
11
|
+
|
|
12
|
+
// === Atom sentinels (shared by prepare + emit \u2014 keeps the stage boundary
|
|
13
|
+
// import-clean: emit must not reach into prepare for a constant) ===
|
|
14
|
+
|
|
15
|
+
// `null` and `undefined` are distinct NaN-box atoms (aux 1 vs 2), so they get
|
|
16
|
+
// distinct sentinels \u2014 collapsing both to one made `cond ? undefined : x`
|
|
17
|
+
// surface as `null` (the value flows through emit's symbol case, which can
|
|
18
|
+
// only carry one atom).
|
|
19
|
+
export const JZ_NULL = Symbol('null')
|
|
20
|
+
export const JZ_UNDEF = Symbol('undefined')
|
|
21
|
+
|
|
22
|
+
/** `typeof` comparison codes, keyed by the JS typeof string — negative so they
|
|
23
|
+
* can't collide with positive user-supplied PTR kinds in the same compare slot
|
|
24
|
+
* (prepare folds `typeof x == "number"` to `[op, ['typeof', x], [, TYPEOF.number]]`;
|
|
25
|
+
* emit's emitTypeofCmp and flow-types' refinements dispatch on the codes).
|
|
26
|
+
* Null-proto: prepare indexes it with arbitrary user strings — a plain literal
|
|
27
|
+
* would leak `constructor`/`toString` through the lookup. */
|
|
28
|
+
export const TYPEOF = Object.freeze(Object.assign(Object.create(null), {
|
|
29
|
+
number: -1, string: -2, undefined: -3, boolean: -4, object: -5, 'function': -6, bigint: -7,
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
// === Numeric range (shared by analyze + ir) ===
|
|
33
|
+
|
|
34
|
+
export const I32_MIN = -2147483648
|
|
35
|
+
export const I32_MAX = 2147483647
|
|
36
|
+
export const isI32 = (v) => Number.isInteger(v) && v >= I32_MIN && v <= I32_MAX && !Object.is(v, -0)
|
|
37
|
+
|
|
38
|
+
// === Statement / block-body classification ===
|
|
39
|
+
|
|
40
|
+
/** Statement operators — distinguish block bodies from object literals. */
|
|
41
|
+
export const STMT_OPS = new Set([';', 'let', 'const', 'return', 'if', 'for', 'for-in', 'while', 'break', 'continue', 'switch',
|
|
42
|
+
'=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??=',
|
|
43
|
+
'throw', 'try', 'catch', 'finally', '++', '--', '()'])
|
|
44
|
+
|
|
45
|
+
/** jzify superset: pre-lowered JS shapes before prepare strips them. */
|
|
46
|
+
export const JZ_BLOCK_OPS = new Set([...STMT_OPS, 'var', 'for-of', 'do', 'function', 'class', 'import', 'export', 'label', 'case', 'default'])
|
|
47
|
+
|
|
48
|
+
/** Valid labeled-statement bodies in jzify. */
|
|
49
|
+
export const LABEL_BODY_OPS = new Set([';', 'if', 'for', 'for-in', 'for-of', 'while', 'do', 'switch', 'try', 'throw'])
|
|
50
|
+
|
|
51
|
+
/** Statement-only ops: heads that can never be a concise arrow *value* body.
|
|
52
|
+
* A concise-body arrow with one of these can only have come from method/function
|
|
53
|
+
* shorthand the parser unwrapped (`m(){ if … }` → `['=>', p, ['if', …]]`), so it
|
|
54
|
+
* must be re-blocked. Excludes `function`/`class` (those ARE expression bodies,
|
|
55
|
+
* e.g. `() => function(){}`), assignment/update/call, and switch-internal
|
|
56
|
+
* `case`/`default`. */
|
|
57
|
+
export const STMT_ONLY_OPS = new Set([';', 'if', 'for', 'for-in', 'for-of', 'while', 'do', 'switch',
|
|
58
|
+
'return', 'break', 'continue', 'throw', 'try', 'let', 'const', 'var', 'label'])
|
|
59
|
+
|
|
60
|
+
/** Distinguish a function block body `{ … }` from an expression object literal `({a:1})`. */
|
|
61
|
+
export const isBlockBody = (body) =>
|
|
62
|
+
Array.isArray(body) && body[0] === '{}' && (body.length === 1 || STMT_OPS.has(body[1]?.[0]))
|
|
63
|
+
|
|
64
|
+
// === AST node classifiers ===
|
|
65
|
+
|
|
66
|
+
export const isLiteralStr = idx => Array.isArray(idx) && idx[0] === 'str' && typeof idx[1] === 'string'
|
|
67
|
+
export const isFuncRef = (node, funcNames) => typeof node === 'string' && funcNames.has(node)
|
|
68
|
+
|
|
69
|
+
// === Assignment / reassignment ===
|
|
70
|
+
|
|
71
|
+
/** Assignment operators — shared across analyze, plan, emit, abi. */
|
|
72
|
+
export const ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??='])
|
|
73
|
+
|
|
74
|
+
/** Detect whether `name` is written to (=, +=, ++, --, etc.) anywhere within `body`. */
|
|
75
|
+
export function isReassigned(body, name) {
|
|
76
|
+
if (!Array.isArray(body)) return false
|
|
77
|
+
const op = body[0]
|
|
78
|
+
if (ASSIGN_OPS.has(op) && body[1] === name) return true
|
|
79
|
+
if ((op === '++' || op === '--') && body[1] === name) return true
|
|
80
|
+
if (op === 'let' || op === 'const') {
|
|
81
|
+
for (let i = 1; i < body.length; i++) {
|
|
82
|
+
const d = body[i]
|
|
83
|
+
if (Array.isArray(d) && d[0] === '=' && d[2] != null && isReassigned(d[2], name)) return true
|
|
84
|
+
}
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
for (let i = 1; i < body.length; i++) if (isReassigned(body[i], name)) return true
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// A deeply-constant array literal (every element a compile-time literal), safe to
|
|
92
|
+
// allocate once and share. At emit time an array literal is `['[', e0, e1, …]` (flat
|
|
93
|
+
// elements); an INDEX access is `['[]', base, idx]` (op `[]`) — NOT a literal.
|
|
94
|
+
export function isConstLiteral(node) {
|
|
95
|
+
if (!Array.isArray(node)) return false
|
|
96
|
+
const op = node[0]
|
|
97
|
+
if (op == null) return true // [null, n] / [,bool] / [null,null] primitive
|
|
98
|
+
if (op === '[') { for (let i = 1; i < node.length; i++) if (!isConstLiteral(node[i])) return false; return true }
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Is `node` a pure reference-projection of a tainted name? (`t`, `t[i]`, `t.p`, chained) —
|
|
103
|
+
// a value that aliases into the shared literal, so it must be tracked too.
|
|
104
|
+
const isProjection = (node, tainted) => {
|
|
105
|
+
if (typeof node === 'string') return tainted.has(node)
|
|
106
|
+
return Array.isArray(node) && (node[0] === '[]' || node[0] === '.') && node.length === 3 && isProjection(node[1], tainted)
|
|
107
|
+
}
|
|
108
|
+
const baseIsTainted = (m, tainted) => isProjection(m, tainted) // m is a [] / . access node
|
|
109
|
+
|
|
110
|
+
// Could a single shared allocation of `name` be observed to differ from per-iteration
|
|
111
|
+
// allocation within `body`? It can't iff `name` and every alias derived from it by pure
|
|
112
|
+
// reference-projection is only ever READ (as a `[]`/`.` base) — never written through,
|
|
113
|
+
// method-called, or escaped (bare value, call arg, return, comparison). Then hoisting the
|
|
114
|
+
// allocation out of a loop is sound. Conservative: any use it can't classify as a read
|
|
115
|
+
// bails out (returns true = "not hoistable").
|
|
116
|
+
export function constLiteralHoistable(body, name) {
|
|
117
|
+
const decls = []
|
|
118
|
+
;(function collect(n) {
|
|
119
|
+
if (!Array.isArray(n)) return
|
|
120
|
+
if (n[0] === 'const' || n[0] === 'let')
|
|
121
|
+
for (let i = 1; i < n.length; i++) { const d = n[i]; if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string') decls.push(d) }
|
|
122
|
+
for (let i = 1; i < n.length; i++) collect(n[i])
|
|
123
|
+
})(body)
|
|
124
|
+
const tainted = new Set([name])
|
|
125
|
+
for (let changed = true; changed;) {
|
|
126
|
+
changed = false
|
|
127
|
+
for (const d of decls) if (!tainted.has(d[1]) && isProjection(d[2], tainted)) { tainted.add(d[1]); changed = true }
|
|
128
|
+
}
|
|
129
|
+
// unsafe use of any tainted name?
|
|
130
|
+
return !(function bad(node) {
|
|
131
|
+
if (typeof node === 'string') return tainted.has(node) // bare tainted value = escape
|
|
132
|
+
if (!Array.isArray(node)) return false
|
|
133
|
+
const op = node[0]
|
|
134
|
+
// decl: the bound names are bindings, not uses — only the initializer values matter
|
|
135
|
+
if (op === 'const' || op === 'let') {
|
|
136
|
+
for (let i = 1; i < node.length; i++) { const d = node[i]; if (Array.isArray(d) && d[0] === '=') { if (bad(d[2])) return true } else if (bad(d)) return true }
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
// read of a tainted base (`t[i]` / `t.p`): base is fine, only the index expr can be unsafe
|
|
140
|
+
if ((op === '[]' || op === '.') && node.length === 3 && typeof node[1] === 'string' && tainted.has(node[1]))
|
|
141
|
+
return op === '[]' ? bad(node[2]) : false
|
|
142
|
+
if (ASSIGN_OPS.has(op) && typeof node[1] === 'string' && tainted.has(node[1])) return true // reassign the binding
|
|
143
|
+
if (ASSIGN_OPS.has(op) && Array.isArray(node[1]) && (node[1][0] === '[]' || node[1][0] === '.') && baseIsTainted(node[1], tainted)) return true // write through
|
|
144
|
+
if ((op === '++' || op === '--') && typeof node[1] === 'string' && tainted.has(node[1])) return true
|
|
145
|
+
if (op === '()' && Array.isArray(node[1]) && node[1][0] === '.' && baseIsTainted(node[1], tainted)) return true // method call
|
|
146
|
+
for (let i = 1; i < node.length; i++) if (bad(node[i])) return true
|
|
147
|
+
return false
|
|
148
|
+
})(body)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Sound over-approximation: could `name`'s array length change anywhere in `body`?
|
|
152
|
+
// True if it is reassigned, has a length-mutating method called on it (push/pop/shift/
|
|
153
|
+
// unshift/splice), is assigned through (`name.x = …` / `name[i] = …`, the latter may grow),
|
|
154
|
+
// or is handed to a call as an argument (a callee might push to it). Lets a plain array's
|
|
155
|
+
// `arr.length` loop bound be hoisted when this is false (see immutableLenBound).
|
|
156
|
+
export function mutatesArrayLength(body, name) {
|
|
157
|
+
if (!Array.isArray(body)) return false
|
|
158
|
+
const op = body[0]
|
|
159
|
+
if ((ASSIGN_OPS.has(op) || op === '++' || op === '--') && body[1] === name) return true
|
|
160
|
+
// write through `name` (`name.x = …`, `name[i] = …` — index write may extend length)
|
|
161
|
+
if (ASSIGN_OPS.has(op) && Array.isArray(body[1]) && (body[1][0] === '.' || body[1][0] === '[]') && body[1][1] === name) return true
|
|
162
|
+
if (op === '()') {
|
|
163
|
+
// method call on `name` (`name.push(…)` etc.) — any method, to stay sound
|
|
164
|
+
if (Array.isArray(body[1]) && body[1][0] === '.' && body[1][1] === name) return true
|
|
165
|
+
// `name` passed as a call argument — the callee could mutate it
|
|
166
|
+
for (let i = 2; i < body.length; i++) if (body[i] === name) return true
|
|
167
|
+
}
|
|
168
|
+
for (let i = 1; i < body.length; i++) if (mutatesArrayLength(body[i], name)) return true
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Normalize a call's raw arg slot: null → [], comma-group → elems, else singleton. */
|
|
173
|
+
export function commaList(raw) {
|
|
174
|
+
if (raw == null) return []
|
|
175
|
+
return Array.isArray(raw) && raw[0] === ',' ? raw.slice(1) : [raw]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Args of a `['()', callee, raw]` node, or null when `node` is not a call. */
|
|
179
|
+
export function callArgs(node) {
|
|
180
|
+
if (!Array.isArray(node) || node[0] !== '()') return null
|
|
181
|
+
return commaList(node[2])
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Write normalized args back onto a call node. */
|
|
185
|
+
export function setCallArgs(node, args) {
|
|
186
|
+
node[2] = args.length === 0 ? null : args.length === 1 ? args[0] : [',', ...args]
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Unwrap handler/rest `args` when the sole element is a comma-group. */
|
|
190
|
+
export function spreadArgs(args) {
|
|
191
|
+
if (args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',') return args[0].slice(1)
|
|
192
|
+
return args
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const isSeq = node => Array.isArray(node) && node[0] === ';'
|
|
196
|
+
|
|
197
|
+
/** Statement list inside a block `{…}`; null when `body` is not a block. */
|
|
198
|
+
export function blockStmts(body) {
|
|
199
|
+
if (!Array.isArray(body) || body[0] !== '{}') return null
|
|
200
|
+
const inner = body[1]
|
|
201
|
+
if (!Array.isArray(inner)) return inner == null ? [] : [inner]
|
|
202
|
+
return inner[0] === ';' ? inner.slice(1) : [inner]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Flatten a block/seq/single-stmt body into a statement array. */
|
|
206
|
+
export function stmtList(body) {
|
|
207
|
+
if (!Array.isArray(body)) return body == null ? [] : [body]
|
|
208
|
+
if (body[0] === '{}') return stmtList(body[1])
|
|
209
|
+
if (body[0] === ';') return body.slice(1)
|
|
210
|
+
return [body]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Handler/rest args with comma unwrap and null drop (jzify/prepare). */
|
|
214
|
+
export function handlerArgs(args) {
|
|
215
|
+
return spreadArgs(args).filter(a => a != null)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Early-exit walk; skips into `=>` bodies by default. */
|
|
219
|
+
export function some(node, pred, { skipArrow = true } = {}) {
|
|
220
|
+
if (!Array.isArray(node)) return false
|
|
221
|
+
if (pred(node)) return true
|
|
222
|
+
if (skipArrow && node[0] === '=>') return false
|
|
223
|
+
for (let i = 1; i < node.length; i++) if (some(node[i], pred, { skipArrow })) return true
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Options for {@link refsName} / {@link refsAny}. */
|
|
228
|
+
// skipArrow (default true): stop at `=>` boundaries — matches `some()`.
|
|
229
|
+
// skipStr: don't descend into `str` literal nodes.
|
|
230
|
+
// skipBindingPositions: on `.`/`?.` recurse only the receiver; on `:` only the value.
|
|
231
|
+
|
|
232
|
+
/** Expression-position name refs: descends into `=>`, skips literal keys and `str`. */
|
|
233
|
+
export const REFS_IN_EXPR = { skipArrow: false, skipStr: true, skipBindingPositions: true }
|
|
234
|
+
|
|
235
|
+
/** True if bare identifier `name` appears anywhere in `node`. */
|
|
236
|
+
export function refsName(node, name, opts = {}) {
|
|
237
|
+
const skipArrow = opts.skipArrow !== false
|
|
238
|
+
if (typeof node === 'string') return node === name
|
|
239
|
+
if (!Array.isArray(node)) return false
|
|
240
|
+
const op = node[0]
|
|
241
|
+
if (skipArrow && op === '=>') return false
|
|
242
|
+
if (opts.skipStr && op === 'str') return false
|
|
243
|
+
if (opts.skipBindingPositions) {
|
|
244
|
+
if (op === '.' || op === '?.') return refsName(node[1], name, opts)
|
|
245
|
+
if (op === ':') return refsName(node[2], name, opts)
|
|
246
|
+
}
|
|
247
|
+
for (let i = 1; i < node.length; i++) if (refsName(node[i], name, opts)) return true
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** True if any name in `names` (Set) appears in `node`. Same options as refsName. */
|
|
252
|
+
export function refsAny(node, names, opts = {}) {
|
|
253
|
+
if (!names?.size) return false
|
|
254
|
+
if (typeof node === 'string') return names.has(node)
|
|
255
|
+
if (!Array.isArray(node)) return false
|
|
256
|
+
const op = node[0]
|
|
257
|
+
if (opts.skipArrow !== false && op === '=>') return false
|
|
258
|
+
if (opts.skipStr && op === 'str') return false
|
|
259
|
+
if (opts.skipBindingPositions) {
|
|
260
|
+
if (op === '.' || op === '?.') return refsAny(node[1], names, opts)
|
|
261
|
+
if (op === ':') return refsAny(node[2], names, opts)
|
|
262
|
+
}
|
|
263
|
+
for (let i = 1; i < node.length; i++) if (refsAny(node[i], names, opts)) return true
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const CONTROL_TRANSFER = new Set(['return', 'throw', 'break', 'continue'])
|
|
268
|
+
|
|
269
|
+
/** Does `body` contain return/throw/break/continue (not inside nested `=>`)? */
|
|
270
|
+
export function hasControlTransfer(body) {
|
|
271
|
+
if (!Array.isArray(body)) return false
|
|
272
|
+
if (CONTROL_TRANSFER.has(body[0])) return true
|
|
273
|
+
if (body[0] === '=>') return false
|
|
274
|
+
for (let i = 1; i < body.length; i++) if (hasControlTransfer(body[i])) return true
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Does `body` contain a `continue` that targets THIS loop? */
|
|
279
|
+
export function hasOwnContinue(body) {
|
|
280
|
+
if (!Array.isArray(body)) return false
|
|
281
|
+
const op = body[0]
|
|
282
|
+
if (op === 'continue') return true
|
|
283
|
+
if (op === 'for' || op === 'while' || op === 'do') return false
|
|
284
|
+
for (let i = 1; i < body.length; i++) if (hasOwnContinue(body[i])) return true
|
|
285
|
+
return false
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Does `body` contain `continue <label>` targeting the given label? Descends through nested
|
|
289
|
+
* loops (a labeled continue crosses loop boundaries) but not into closures. */
|
|
290
|
+
export function hasLabeledContinueTo(body, label) {
|
|
291
|
+
if (!Array.isArray(body)) return false
|
|
292
|
+
const op = body[0]
|
|
293
|
+
if (op === 'continue' && body[1] === label) return true
|
|
294
|
+
if (op === '=>') return false
|
|
295
|
+
for (let i = 1; i < body.length; i++) if (hasLabeledContinueTo(body[i], label)) return true
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function hasOwnBreakOrContinue(body) {
|
|
300
|
+
if (!Array.isArray(body)) return false
|
|
301
|
+
const op = body[0]
|
|
302
|
+
if (op === 'break' || op === 'continue') return true
|
|
303
|
+
if (op === 'for' || op === 'while' || op === 'do' || op === '=>') return false
|
|
304
|
+
for (let i = 1; i < body.length; i++) if (hasOwnBreakOrContinue(body[i])) return true
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// === Arrow param normalization ===
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
export function extractParams(rawParams) {
|
|
312
|
+
let p = rawParams
|
|
313
|
+
if (Array.isArray(p) && p[0] === '()') p = p[1]
|
|
314
|
+
return p == null ? [] : Array.isArray(p) ? (p[0] === ',' ? p.slice(1) : [p]) : [p]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function classifyParam(r) {
|
|
318
|
+
if (Array.isArray(r) && r[0] === '...') return { kind: 'rest', name: r[1] }
|
|
319
|
+
if (Array.isArray(r) && r[0] === '=') {
|
|
320
|
+
if (typeof r[1] === 'string') return { kind: 'default', name: r[1], defValue: r[2] }
|
|
321
|
+
return { kind: 'destruct-default', pattern: r[1], defValue: r[2] }
|
|
322
|
+
}
|
|
323
|
+
if (Array.isArray(r) && (r[0] === '[]' || r[0] === '{}')) return { kind: 'destruct', pattern: r }
|
|
324
|
+
return { kind: 'plain', name: r }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function collectParamNames(raw, out = new Set()) {
|
|
328
|
+
for (const r of raw) {
|
|
329
|
+
if (typeof r === 'string') out.add(r)
|
|
330
|
+
else if (Array.isArray(r)) {
|
|
331
|
+
if (r[0] === '=' && typeof r[1] === 'string') out.add(r[1])
|
|
332
|
+
else if (r[0] === '...' && typeof r[1] === 'string') out.add(r[1])
|
|
333
|
+
else if (r[0] === '=' && Array.isArray(r[1])) collectParamNames([r[1]], out)
|
|
334
|
+
else if (r[0] === '[]' || r[0] === '{}' || r[0] === ',') collectParamNames(r.slice(1), out)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return out
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// === Return-path queries (narrowing) ===
|
|
341
|
+
|
|
342
|
+
const collectReturnExprs = (node, out) => {
|
|
343
|
+
if (!Array.isArray(node)) return
|
|
344
|
+
const [op, ...args] = node
|
|
345
|
+
if (op === '=>') return
|
|
346
|
+
if (op === 'return') { if (args[0] != null) out.push(args[0]); return }
|
|
347
|
+
for (const a of args) collectReturnExprs(a, out)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export const alwaysReturns = (n) => {
|
|
351
|
+
if (!Array.isArray(n)) return false
|
|
352
|
+
const op = n[0]
|
|
353
|
+
if (op === '=>') return false
|
|
354
|
+
if (op === 'return' || op === 'throw') return true
|
|
355
|
+
if (op === '{}' || op === ';') return alwaysReturns(n[n.length - 1])
|
|
356
|
+
if (op === 'if') return n.length >= 4 && alwaysReturns(n[2]) && alwaysReturns(n[3])
|
|
357
|
+
return false
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export const hasBareReturn = (n) => {
|
|
361
|
+
if (!Array.isArray(n)) return false
|
|
362
|
+
if (n[0] === '=>') return false
|
|
363
|
+
if (n[0] === 'return' && n[1] == null) return true
|
|
364
|
+
return n.some(hasBareReturn)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export const returnExprs = (body) => {
|
|
368
|
+
if (isBlockBody(body)) {
|
|
369
|
+
const out = []
|
|
370
|
+
collectReturnExprs(body, out)
|
|
371
|
+
return out
|
|
372
|
+
}
|
|
373
|
+
return [body]
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// === Clone / compare / module body / bare refs ===
|
|
377
|
+
|
|
378
|
+
/** Deep-clone an AST node (arrays only; primitives pass through). */
|
|
379
|
+
export function cloneNode(node) {
|
|
380
|
+
if (node == null || typeof node !== 'object') return node
|
|
381
|
+
if (!Array.isArray(node)) return node
|
|
382
|
+
return node.map(cloneNode)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Structural equality via JSON. AST nodes are JSON-serializable except i64.const
|
|
386
|
+
* BigInt payloads (NaN-box prefixes — dcbb433 routes pointer offsets through boxed
|
|
387
|
+
* forms); the replacer stringifies those as `<n>n` (cf. formatErrorNode in ctx.js). */
|
|
388
|
+
export const bigintSafeKey = (_k, v) => typeof v === 'bigint' ? `${v}n` : v
|
|
389
|
+
export function nodeEqual(a, b) {
|
|
390
|
+
return JSON.stringify(a, bigintSafeKey) === JSON.stringify(b, bigintSafeKey)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Property entries of an object-literal AST node (`['{}', …]`). */
|
|
394
|
+
export function descriptorProps(node) {
|
|
395
|
+
if (!Array.isArray(node) || node[0] !== '{}') return null
|
|
396
|
+
const body = node[1]
|
|
397
|
+
if (body == null) return []
|
|
398
|
+
if (Array.isArray(body) && body[0] === ',') return body.slice(1)
|
|
399
|
+
return [body]
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Comma-unwrapped entries from an object-literal constructor arg list. */
|
|
403
|
+
export function objectLiteralEntries(args) {
|
|
404
|
+
const raw = args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',' ? args[0].slice(1) : args
|
|
405
|
+
return raw.filter(p => p != null)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** String literal node → string, or null. */
|
|
409
|
+
export function literalString(node) {
|
|
410
|
+
return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** Zero numeric literal node. */
|
|
414
|
+
export function isZeroLiteral(node) {
|
|
415
|
+
return Array.isArray(node) && node[0] == null && node[1] === 0
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Top-level module statements; null when `ast` is not module-shaped. */
|
|
419
|
+
export function moduleStmts(ast) {
|
|
420
|
+
if (!Array.isArray(ast)) return null
|
|
421
|
+
return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Unwrap esbuild module binding to `[name, init]`, or null. */
|
|
425
|
+
export function bindingOf(stmt) {
|
|
426
|
+
if (!Array.isArray(stmt)) return null
|
|
427
|
+
if (stmt[0] === '=' && typeof stmt[1] === 'string') return [stmt[1], stmt[2]]
|
|
428
|
+
if ((stmt[0] === 'let' || stmt[0] === 'const' || stmt[0] === 'var') && stmt.length === 2 &&
|
|
429
|
+
Array.isArray(stmt[1]) && stmt[1][0] === '=' && typeof stmt[1][1] === 'string')
|
|
430
|
+
return [stmt[1][1], stmt[1][2]]
|
|
431
|
+
return null
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Identifier refs in value positions (skip decl names, member keys, object keys). */
|
|
435
|
+
export function collectBareRefs(node, out) {
|
|
436
|
+
if (typeof node === 'string') return void out.add(node)
|
|
437
|
+
if (!Array.isArray(node)) return
|
|
438
|
+
if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var') {
|
|
439
|
+
for (let i = 1; i < node.length; i++)
|
|
440
|
+
if (Array.isArray(node[i]) && node[i][0] === '=') collectBareRefs(node[i][2], out)
|
|
441
|
+
} else if ((node[0] === '.' || node[0] === '?.') && typeof node[2] === 'string') {
|
|
442
|
+
collectBareRefs(node[1], out)
|
|
443
|
+
} else if (node[0] === ':') {
|
|
444
|
+
collectBareRefs(node[2], out)
|
|
445
|
+
} else {
|
|
446
|
+
for (let i = 1; i < node.length; i++) collectBareRefs(node[i], out)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Deep walk: `pred` on every node including bare identifiers; descends into arrows. */
|
|
451
|
+
export function someDeep(node, pred) {
|
|
452
|
+
if (pred(node)) return true
|
|
453
|
+
if (!Array.isArray(node)) return false
|
|
454
|
+
for (let i = 1; i < node.length; i++) if (someDeep(node[i], pred)) return true
|
|
455
|
+
return false
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** Alias for {@link extractParams}. */
|
|
459
|
+
export const paramList = extractParams
|
|
460
|
+
|
package/src/autoload.js
CHANGED
|
@@ -5,6 +5,10 @@ import * as mods from '../module/index.js'
|
|
|
5
5
|
|
|
6
6
|
const dict = obj => Object.assign(Object.create(null), obj)
|
|
7
7
|
|
|
8
|
+
export const MOD_ALIAS = { Number: 'number', Array: 'array', Object: 'object', Symbol: 'symbol', JSON: 'json', Date: 'date', BigInt: 'number', Error: 'core', TextEncoder: 'string', TextDecoder: 'string',
|
|
9
|
+
// SIMD intrinsic namespaces (f32x4/i32x4/f64x2/v128) all live in the `simd` module.
|
|
10
|
+
f32x4: 'simd', i32x4: 'simd', f64x2: 'simd', v128: 'simd' }
|
|
11
|
+
|
|
8
12
|
export const PROP_MODULES = Object.assign(Object.create(null), {
|
|
9
13
|
push: ['core', 'array'], pop: ['core', 'array'], shift: ['core', 'array'], unshift: ['core', 'array'],
|
|
10
14
|
splice: ['core', 'array'], reverse: ['core', 'array'], sort: ['core', 'array'], fill: ['core', 'array'],
|
|
@@ -12,7 +16,7 @@ export const PROP_MODULES = Object.assign(Object.create(null), {
|
|
|
12
16
|
forEach: ['core', 'array'], find: ['core', 'array'], findIndex: ['core', 'array'],
|
|
13
17
|
findLast: ['core', 'array'], findLastIndex: ['core', 'array'],
|
|
14
18
|
every: ['core', 'array'], some: ['core', 'array'], flat: ['core', 'array'], flatMap: ['core', 'array'],
|
|
15
|
-
join: ['core', 'array'], copyWithin: ['core', 'array'], at: ['core', 'array'],
|
|
19
|
+
join: ['core', 'array'], copyWithin: ['core', 'array'], at: ['core', 'string', 'array'],
|
|
16
20
|
charAt: ['core', 'string'], charCodeAt: ['core', 'string'], codePointAt: ['core', 'string'],
|
|
17
21
|
toUpperCase: ['core', 'string'], toLowerCase: ['core', 'string'], toLocaleLowerCase: ['core', 'string'], trim: ['core', 'string'],
|
|
18
22
|
trimStart: ['core', 'string'], trimEnd: ['core', 'string'],
|
|
@@ -46,6 +50,8 @@ export const OP_MODULES = {
|
|
|
46
50
|
'**': ['math'],
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
export const TYPED_CTORS = ['Float64Array','Float32Array','Int32Array','Uint32Array','Int16Array','Uint16Array','Int8Array','Uint8Array','BigInt64Array','BigUint64Array','ArrayBuffer','DataView']
|
|
54
|
+
|
|
49
55
|
export const CALL_MODULES = dict({
|
|
50
56
|
ArrayBuffer: ['core', 'typedarray'],
|
|
51
57
|
DataView: ['core', 'typedarray'],
|
|
@@ -75,6 +81,7 @@ export const CALL_MODULES = dict({
|
|
|
75
81
|
'Object.assign': ['core', 'object'],
|
|
76
82
|
'Object.create': ['core', 'object'],
|
|
77
83
|
'Object.defineProperty': ['core', 'object'],
|
|
84
|
+
'__object_toString': ['core', 'object', 'string'],
|
|
78
85
|
'Date.UTC': ['core', 'date'],
|
|
79
86
|
'Date.parse': ['core', 'date'],
|
|
80
87
|
'Date.now': ['core', 'console'],
|
|
@@ -84,14 +91,7 @@ export const CALL_MODULES = dict({
|
|
|
84
91
|
'String.fromCodePoint': ['core', 'string'],
|
|
85
92
|
'BigInt.asIntN': ['number'],
|
|
86
93
|
'BigInt.asUintN': ['number'],
|
|
87
|
-
'
|
|
88
|
-
'Float32Array.from': ['core', 'typedarray', 'array'],
|
|
89
|
-
'Int32Array.from': ['core', 'typedarray', 'array'],
|
|
90
|
-
'Uint32Array.from': ['core', 'typedarray', 'array'],
|
|
91
|
-
'Int16Array.from': ['core', 'typedarray', 'array'],
|
|
92
|
-
'Uint16Array.from': ['core', 'typedarray', 'array'],
|
|
93
|
-
'Int8Array.from': ['core', 'typedarray', 'array'],
|
|
94
|
-
'Uint8Array.from': ['core', 'typedarray', 'array'],
|
|
94
|
+
...Object.fromEntries(TYPED_CTORS.filter(n => n.endsWith('Array')).map(n => [`${n}.from`, ['core', 'typedarray', 'array']])),
|
|
95
95
|
'ArrayBuffer.isView': ['core', 'typedarray'],
|
|
96
96
|
// instanceof Map / Set / TypedArray predicates (synthesized by jzify).
|
|
97
97
|
'__is_map': ['core', 'collection'],
|
|
@@ -107,13 +107,15 @@ export const GENERIC_METHOD_MODULES = dict({
|
|
|
107
107
|
hasOwnProperty: ['core', 'object', 'string', 'collection'],
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
-
export const CTORS = ['Float64Array','Float32Array','Int32Array','Uint32Array','Int16Array','Uint16Array','Int8Array','Uint8Array','BigInt64Array','BigUint64Array','Set','Map','Date']
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
export const CTORS = ['Float64Array','Float32Array','Int32Array','Uint32Array','Int16Array','Uint16Array','Int8Array','Uint8Array','BigInt64Array','BigUint64Array','Set','Map','WeakSet','WeakMap','Date']
|
|
111
|
+
// WeakSet/WeakMap fold to Set/Map (the `new` handler rewrites the ctor name).
|
|
112
|
+
// jz has no GC, so weakness is unobservable; this also accepts primitive keys
|
|
113
|
+
// (real WeakMap throws TypeError) and exposes `.size`/iteration — a deliberate
|
|
114
|
+
// semantic deviation documented in README. Compilers lean on them as identity
|
|
115
|
+
// caches / cycle-detection sets and never observe the missing weak semantics.
|
|
116
|
+
export const COLLECTION_CTORS = ['Set', 'Map', 'WeakSet', 'WeakMap']
|
|
113
117
|
export const TIMER_NAMES = new Set(['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'])
|
|
114
118
|
|
|
115
|
-
export const MOD_ALIAS = { Number: 'number', Array: 'array', Object: 'object', Symbol: 'symbol', JSON: 'json', Date: 'date', BigInt: 'number', Error: 'core', TextEncoder: 'string', TextDecoder: 'string' }
|
|
116
|
-
|
|
117
119
|
const MOD_DEPS = {
|
|
118
120
|
number: ['core', 'string'],
|
|
119
121
|
string: ['core', 'number'],
|
|
@@ -127,6 +129,16 @@ const MOD_DEPS = {
|
|
|
127
129
|
regex: ['core', 'string', 'array'],
|
|
128
130
|
}
|
|
129
131
|
|
|
132
|
+
export function includeModule(name) {
|
|
133
|
+
const modName = MOD_ALIAS[name] || name
|
|
134
|
+
const init = mods[modName]
|
|
135
|
+
if (!init) return err(`Module not found: ${name}`)
|
|
136
|
+
if (ctx.module.modules[modName]) return
|
|
137
|
+
ctx.module.modules[modName] = true
|
|
138
|
+
for (const dep of MOD_DEPS[modName] || []) includeModule(dep)
|
|
139
|
+
init(ctx)
|
|
140
|
+
}
|
|
141
|
+
|
|
130
142
|
export const hasModule = name => Boolean(mods[MOD_ALIAS[name] || name])
|
|
131
143
|
|
|
132
144
|
export const includeMods = (...names) => names.forEach(includeModule)
|
|
@@ -186,13 +198,3 @@ export const includeForRuntimeCtor = name => {
|
|
|
186
198
|
else if (kind === 'array') includeMods('core', 'array')
|
|
187
199
|
return kind
|
|
188
200
|
}
|
|
189
|
-
|
|
190
|
-
export function includeModule(name) {
|
|
191
|
-
const modName = MOD_ALIAS[name] || name
|
|
192
|
-
const init = mods[modName]
|
|
193
|
-
if (!init) return err(`Module not found: ${name}`)
|
|
194
|
-
if (ctx.module.modules[modName]) return
|
|
195
|
-
ctx.module.modules[modName] = true
|
|
196
|
-
for (const dep of MOD_DEPS[modName] || []) includeModule(dep)
|
|
197
|
-
init(ctx)
|
|
198
|
-
}
|
package/src/bridge.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdlib module bridge — `module/*` imports from here, not `src/compile/emit.js`.
|
|
3
|
+
*
|
|
4
|
+
* Emit impls bind on `ctx.bridge` at reset(). Registration: `wat(name, body)`
|
|
5
|
+
* for WAT stdlib, `reg(name, deps, fn)` for emit, or `reg(name, { deps, wat, emit })`
|
|
6
|
+
* to co-register both. `method`/`call` remain sugar for simple `$stdlib` calls.
|
|
7
|
+
*
|
|
8
|
+
* @module bridge
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ctx, emitter } from './ctx.js'
|
|
12
|
+
import { typed, asF64, asI32, asI64 } from './ir.js'
|
|
13
|
+
|
|
14
|
+
export { emitter } from './ctx.js'
|
|
15
|
+
|
|
16
|
+
export const emit = (...a) => ctx.bridge.emit(...a)
|
|
17
|
+
export const flat = (...a) => ctx.bridge.flat(...a)
|
|
18
|
+
export const body = (...a) => ctx.bridge.body(...a)
|
|
19
|
+
export const bool = (...a) => ctx.bridge.bool(...a)
|
|
20
|
+
/** Index expr → i32 IR. */
|
|
21
|
+
export const idx = (...a) => ctx.bridge.idx(...a)
|
|
22
|
+
export const spread = (...a) => ctx.bridge.spread(...a)
|
|
23
|
+
|
|
24
|
+
/** Attach a pre-built handler (e.g. from method/emitter) to ctx.core.emit. */
|
|
25
|
+
export const bind = (name, handler) => {
|
|
26
|
+
ctx.core.emit[name] = handler
|
|
27
|
+
return handler
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Register a host import once, idempotent on (module, name). Stdlib modules
|
|
31
|
+
* call this from each use site without re-adding the env/wasi import. */
|
|
32
|
+
export const hostImport = (mod, name, fn) => {
|
|
33
|
+
if (ctx.module.imports.some(i => i[1] === `"${mod}"` && i[2] === `"${name}"`)) return
|
|
34
|
+
ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, fn])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** WAT stdlib→stdlib deps for `resolveIncludes()`. */
|
|
38
|
+
export const deps = (map) => Object.assign(ctx.core.stdlibDeps, map)
|
|
39
|
+
|
|
40
|
+
/** WAT stdlib body (+ optional deps edge for resolveIncludes). */
|
|
41
|
+
export const wat = (name, body, depNames = []) => {
|
|
42
|
+
ctx.core.stdlib[name] = body
|
|
43
|
+
if (depNames.length) deps({ [name]: depNames })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Emit handler; optionally co-register WAT when `depsOrOpts.wat` is set.
|
|
47
|
+
* reg(name, deps, fn) — emit only
|
|
48
|
+
* reg(name, { deps, wat, emit }) — WAT key inferred from first `__…` dep
|
|
49
|
+
* reg(name, { watKey, deps, wat, emit }) — explicit WAT key when deps differ */
|
|
50
|
+
export const reg = (name, depsOrOpts, maybeFn) => {
|
|
51
|
+
if (typeof depsOrOpts === 'object' && depsOrOpts !== null && !Array.isArray(depsOrOpts)) {
|
|
52
|
+
const o = depsOrOpts
|
|
53
|
+
const depsList = o.deps ?? []
|
|
54
|
+
if (o.wat) {
|
|
55
|
+
const watKey = o.watKey ?? depsList.find(d => d.startsWith('__')) ?? name
|
|
56
|
+
wat(watKey, o.wat, o.watDeps ?? [])
|
|
57
|
+
}
|
|
58
|
+
if (o.emit) {
|
|
59
|
+
const h = emitter(depsList, o.emit)
|
|
60
|
+
ctx.core.emit[name] = h
|
|
61
|
+
return h
|
|
62
|
+
}
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
const h = emitter(depsOrOpts, maybeFn)
|
|
66
|
+
ctx.core.emit[name] = h
|
|
67
|
+
return h
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Tag a hand-wrapped handler with `.deps` (pow/** dual lowering). */
|
|
71
|
+
export const tag = (handler, deps) => {
|
|
72
|
+
handler.deps = deps
|
|
73
|
+
return handler
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** `fast(firstArg)` → `core`, else `wrap`. Keeps wrap `.deps`. */
|
|
77
|
+
export const dual = (wrap, core, fast) => {
|
|
78
|
+
const h = (a, ...rest) => (fast(a) ? core(a, ...rest) : wrap(a, ...rest))
|
|
79
|
+
h.deps = wrap.deps
|
|
80
|
+
h.argc = wrap.argc ?? wrap.length
|
|
81
|
+
return h
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cast = { I: asI64, F: asF64, i: asI32 }
|
|
85
|
+
|
|
86
|
+
const coerce = (sig, nodes) =>
|
|
87
|
+
sig.split('').map((c, i) => cast[c](emit(nodes[i])))
|
|
88
|
+
|
|
89
|
+
const wrap = (fmt, call) => {
|
|
90
|
+
if (fmt === 'i64') return typed(['f64.reinterpret_i64', call], 'f64')
|
|
91
|
+
if (fmt === 'i32') return typed(['f64.convert_i32_s', call], 'f64')
|
|
92
|
+
return typed(call, 'f64')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** `(…args) → call($stdlib, coerced…)`. fmt: f64 · i64 · i32 */
|
|
96
|
+
export const call = (stdlib, sig, fmt = 'f64') => {
|
|
97
|
+
const h = emitter([stdlib], (...nodes) =>
|
|
98
|
+
wrap(fmt, ['call', `$${stdlib}`, ...coerce(sig, nodes)]))
|
|
99
|
+
h.argc = sig.length
|
|
100
|
+
return h
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** method `(recv, …args) → call($stdlib, …)`. sig: I · F · i per arg. */
|
|
104
|
+
export const method = (stdlib, sig, ret = 'f64') => {
|
|
105
|
+
const h = emitter([stdlib], (...nodes) => {
|
|
106
|
+
const c = ['call', `$${stdlib}`, ...coerce(sig, nodes)]
|
|
107
|
+
return typed(ret === 'i32' ? ['f64.convert_i32_s', c] : c, 'f64')
|
|
108
|
+
})
|
|
109
|
+
h.argc = sig.length
|
|
110
|
+
return h
|
|
111
|
+
}
|