jz 0.1.1 → 0.2.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 +257 -90
- package/cli.js +34 -8
- package/index.js +102 -13
- package/module/array.js +396 -133
- package/module/collection.js +455 -212
- package/module/console.js +169 -88
- package/module/core.js +246 -144
- package/module/date.js +691 -0
- package/module/function.js +81 -21
- package/module/index.js +2 -1
- package/module/json.js +483 -138
- package/module/math.js +79 -39
- package/module/number.js +188 -78
- package/module/object.js +227 -47
- package/module/regex.js +33 -30
- package/module/schema.js +14 -17
- package/module/string.js +434 -235
- package/module/symbol.js +4 -3
- package/module/timer.js +89 -31
- package/module/typedarray.js +174 -44
- package/package.json +3 -10
- package/src/analyze.js +626 -132
- package/src/autoload.js +14 -6
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +634 -145
- package/src/host.js +244 -51
- package/src/ir.js +81 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1132 -4
- package/src/prepare.js +321 -75
- package/src/vectorize.js +1016 -0
- package/wasi.js +13 -0
- package/src/key.js +0 -73
- package/src/source.js +0 -76
package/src/plan.js
CHANGED
|
@@ -1,10 +1,1133 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Pre-emit compile planning: bridges prepare (AST shape) and emit (wasm bytes).
|
|
3
|
+
*
|
|
4
|
+
* # Stage contract
|
|
5
|
+
* IN: populated `ctx` from prepare.js (functions, schemas, scopes, modules)
|
|
6
|
+
* plus the prepared AST.
|
|
7
|
+
* OUT: returns a `programFacts` object; mutates `ctx` so each function has
|
|
8
|
+
* narrowed signatures, finalized global reps, and per-call decisions.
|
|
9
|
+
*
|
|
10
|
+
* # Pipeline (top-level `plan(ast)`)
|
|
11
|
+
* 1. scanGlobalValueFacts / unboxConstTypedGlobals — finalize global storage.
|
|
12
|
+
* 2. collectProgramFacts — sweep arrow bodies for typed-elem usage, key sets,
|
|
13
|
+
* loop depth, control-transfer shapes; rerun if hot inlining changes the AST.
|
|
14
|
+
* 3. materializeAutoBoxSchemas / resolveClosureWidth — settle layout decisions.
|
|
15
|
+
* 4. Whole-program narrowing (skipped on simple programs):
|
|
16
|
+
* - narrowSignatures — pick a specialization per function from call sites
|
|
17
|
+
* - specializeBimorphicTyped — split typed-elem hot paths into two variants
|
|
18
|
+
* when callers diverge between two ctors
|
|
19
|
+
* - refineDynKeys — tighten dynamic property-key sets
|
|
20
|
+
*
|
|
21
|
+
* No bytes are emitted here; emit.js consumes the planned ctx + programFacts.
|
|
22
|
+
*
|
|
23
|
+
* @module plan
|
|
24
|
+
*/
|
|
2
25
|
|
|
3
26
|
import { ctx } from './ctx.js'
|
|
4
|
-
import { VAL, valTypeOf, typedElemCtor, typedElemAux, updateGlobalRep, collectProgramFacts } from './analyze.js'
|
|
27
|
+
import { T, VAL, analyzeBody, invalidateLocalsCache, staticObjectProps, staticPropertyKey, valTypeOf, typedElemCtor, typedElemAux, updateGlobalRep, collectProgramFacts } from './analyze.js'
|
|
5
28
|
import { MAX_CLOSURE_ARITY } from './ir.js'
|
|
6
29
|
import narrowSignatures, { specializeBimorphicTyped, refineDynKeys } from './narrow.js'
|
|
7
30
|
|
|
31
|
+
const ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??='])
|
|
32
|
+
const CONTROL_TRANSFER = new Set(['return', 'throw', 'break', 'continue'])
|
|
33
|
+
const LOOP_OPS = new Set(['for', 'while', 'do', 'do-while'])
|
|
34
|
+
const SCALAR_TYPED_CTOR = 'new.Float64Array'
|
|
35
|
+
const MAX_SCALAR_TYPED_ARRAY_LEN = 32
|
|
36
|
+
const MAX_SCALAR_TYPED_LOOP_UNROLL = 16
|
|
37
|
+
const MAX_SCALAR_TYPED_NESTED_UNROLL = 128
|
|
38
|
+
|
|
39
|
+
const isSeq = node => Array.isArray(node) && node[0] === ';'
|
|
40
|
+
const blockStmts = body => {
|
|
41
|
+
if (!Array.isArray(body) || body[0] !== '{}') return null
|
|
42
|
+
const inner = body[1]
|
|
43
|
+
if (!Array.isArray(inner)) return inner == null ? [] : [inner]
|
|
44
|
+
return inner[0] === ';' ? inner.slice(1) : [inner]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const callArgs = node => {
|
|
48
|
+
if (!Array.isArray(node) || node[0] !== '()') return null
|
|
49
|
+
const raw = node[2]
|
|
50
|
+
return raw == null ? [] : (Array.isArray(raw) && raw[0] === ',') ? raw.slice(1) : [raw]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isSimpleArg = node => {
|
|
54
|
+
if (typeof node === 'string' || typeof node === 'number') return true
|
|
55
|
+
if (!Array.isArray(node)) return false
|
|
56
|
+
if (node[0] == null) return typeof node[1] === 'number'
|
|
57
|
+
if (node[0] === 'str') return typeof node[1] === 'string'
|
|
58
|
+
if (node[0] === 'u-' || (node[0] === '-' && node.length === 2)) return isSimpleArg(node[1])
|
|
59
|
+
if (['+', '-', '*', '%', '&', '|', '^', '<<', '>>', '>>>'].includes(node[0]))
|
|
60
|
+
return isSimpleArg(node[1]) && isSimpleArg(node[2])
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const scanBody = (node, fn) => {
|
|
65
|
+
if (!Array.isArray(node)) return false
|
|
66
|
+
if (fn(node)) return true
|
|
67
|
+
if (node[0] === '=>') return false
|
|
68
|
+
for (let i = 1; i < node.length; i++) if (scanBody(node[i], fn)) return true
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const loopDepth = (node, depth) => {
|
|
73
|
+
if (!Array.isArray(node)) return depth
|
|
74
|
+
if (node[0] === '=>') return depth
|
|
75
|
+
const here = LOOP_OPS.has(node[0]) ? depth + 1 : depth
|
|
76
|
+
let max = here
|
|
77
|
+
for (let i = 1; i < node.length; i++) {
|
|
78
|
+
const d = loopDepth(node[i], here)
|
|
79
|
+
if (d > max) max = d
|
|
80
|
+
}
|
|
81
|
+
return max
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const nodeSize = (node) => {
|
|
85
|
+
if (!Array.isArray(node)) return 1
|
|
86
|
+
let n = 1
|
|
87
|
+
for (let i = 1; i < node.length; i++) n += nodeSize(node[i])
|
|
88
|
+
return n
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const collectBindings = (node, out) => {
|
|
92
|
+
if (!Array.isArray(node)) return
|
|
93
|
+
const op = node[0]
|
|
94
|
+
if (op === '=>') return
|
|
95
|
+
if (op === 'let' || op === 'const') {
|
|
96
|
+
for (let i = 1; i < node.length; i++) collectBindingTarget(node[i], out)
|
|
97
|
+
}
|
|
98
|
+
for (let i = 1; i < node.length; i++) collectBindings(node[i], out)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const collectBindingTarget = (node, out) => {
|
|
102
|
+
if (typeof node === 'string') { out.add(node); return }
|
|
103
|
+
if (!Array.isArray(node)) return
|
|
104
|
+
if (node[0] === '=') collectBindingTarget(node[1], out)
|
|
105
|
+
else if (node[0] === '...' && typeof node[1] === 'string') out.add(node[1])
|
|
106
|
+
else if (node[0] === ',' || node[0] === '[]' || node[0] === '{}')
|
|
107
|
+
for (let i = 1; i < node.length; i++) collectBindingTarget(node[i], out)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const mutatesAny = (node, names) => scanBody(node, n => {
|
|
111
|
+
const op = n[0]
|
|
112
|
+
if ((op === '++' || op === '--') && typeof n[1] === 'string') return names.has(n[1])
|
|
113
|
+
return ASSIGN_OPS.has(op) && typeof n[1] === 'string' && names.has(n[1])
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const clonePlain = node => Array.isArray(node) ? node.map(clonePlain) : node
|
|
117
|
+
|
|
118
|
+
const intLit = node => {
|
|
119
|
+
if (typeof node === 'number' && Number.isInteger(node)) return node
|
|
120
|
+
if (Array.isArray(node) && node[0] == null && Number.isInteger(node[1])) return node[1]
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const constIntExpr = (node) => {
|
|
125
|
+
const lit = intLit(node)
|
|
126
|
+
if (lit != null) return lit
|
|
127
|
+
if (typeof node === 'string') return ctx.scope.constInts?.get(node) ?? null
|
|
128
|
+
if (!Array.isArray(node)) return null
|
|
129
|
+
const op = node[0]
|
|
130
|
+
if (op === 'u-') {
|
|
131
|
+
const v = constIntExpr(node[1])
|
|
132
|
+
return v == null ? null : -v
|
|
133
|
+
}
|
|
134
|
+
if (node.length !== 3) return null
|
|
135
|
+
const a = constIntExpr(node[1]), b = constIntExpr(node[2])
|
|
136
|
+
if (a == null || b == null) return null
|
|
137
|
+
if (op === '+') return a + b
|
|
138
|
+
if (op === '-') return a - b
|
|
139
|
+
if (op === '*') return a * b
|
|
140
|
+
if (op === '<<') return a << b
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const setCallArgs = (node, args) => {
|
|
145
|
+
node[2] = args.length === 0 ? null : args.length === 1 ? args[0] : [',', ...args]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const scalarArrayElems = (expr) => {
|
|
149
|
+
if (!Array.isArray(expr) || expr[0] !== '[') return null
|
|
150
|
+
const elems = expr.slice(1)
|
|
151
|
+
if (elems.some(e => e == null || (Array.isArray(e) && e[0] === '...') || !isSimpleArg(e))) return null
|
|
152
|
+
return elems
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const scalarObjectProps = (expr) => {
|
|
156
|
+
if (!Array.isArray(expr) || expr[0] !== '{}') return null
|
|
157
|
+
const props = staticObjectProps(expr.slice(1))
|
|
158
|
+
if (!props) return null
|
|
159
|
+
const seen = new Set()
|
|
160
|
+
for (let i = 0; i < props.names.length; i++) {
|
|
161
|
+
const name = props.names[i]
|
|
162
|
+
if (seen.has(name) || !isSimpleArg(props.values[i])) return null
|
|
163
|
+
seen.add(name)
|
|
164
|
+
}
|
|
165
|
+
return props
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const fixedScalarTypedArrayLen = (expr) => {
|
|
169
|
+
if (typedElemCtor(expr) !== SCALAR_TYPED_CTOR) return null
|
|
170
|
+
const args = callArgs(expr)
|
|
171
|
+
if (!args || args.length !== 1) return null
|
|
172
|
+
const len = constIntExpr(args[0])
|
|
173
|
+
return len != null && len >= 0 && len <= MAX_SCALAR_TYPED_ARRAY_LEN ? len : null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const ASSIGN_TARGET_OPS = new Set(['=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??='])
|
|
177
|
+
|
|
178
|
+
const safeScalarArrayUse = (node, name, parentOp = null) => {
|
|
179
|
+
if (typeof node === 'string') return node !== name
|
|
180
|
+
if (!Array.isArray(node)) return true
|
|
181
|
+
const op = node[0]
|
|
182
|
+
if (ASSIGN_TARGET_OPS.has(op) && node[1] === name) return false
|
|
183
|
+
if ((op === 'let' || op === 'const') && node.slice(1).some(d => d === name || (Array.isArray(d) && d[1] === name))) return false
|
|
184
|
+
if ((op === '.' || op === '?.') && node[1] === name) return node[2] === 'length'
|
|
185
|
+
if (op === '[]' && node[1] === name) return intLit(node[2]) != null
|
|
186
|
+
if (op === '...' && node[1] === name) return parentOp === '['
|
|
187
|
+
for (let i = 1; i < node.length; i++) {
|
|
188
|
+
if (!safeScalarArrayUse(node[i], name, op)) return false
|
|
189
|
+
}
|
|
190
|
+
return true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const rewriteScalarArrayUses = (node, arrays, parentOp = null) => {
|
|
194
|
+
if (!Array.isArray(node)) return node
|
|
195
|
+
const op = node[0]
|
|
196
|
+
if ((op === '.' || op === '?.') && arrays.has(node[1]) && node[2] === 'length') {
|
|
197
|
+
return [, arrays.get(node[1]).length]
|
|
198
|
+
}
|
|
199
|
+
if (op === '[]' && arrays.has(node[1])) {
|
|
200
|
+
const idx = intLit(node[2])
|
|
201
|
+
const elems = arrays.get(node[1])
|
|
202
|
+
return idx != null && idx >= 0 && idx < elems.length ? elems[idx] : [, undefined]
|
|
203
|
+
}
|
|
204
|
+
if (op === '[') {
|
|
205
|
+
const out = ['[']
|
|
206
|
+
for (let i = 1; i < node.length; i++) {
|
|
207
|
+
const item = node[i]
|
|
208
|
+
if (Array.isArray(item) && item[0] === '...' && arrays.has(item[1])) {
|
|
209
|
+
out.push(...arrays.get(item[1]))
|
|
210
|
+
} else {
|
|
211
|
+
out.push(rewriteScalarArrayUses(item, arrays, op))
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return out
|
|
215
|
+
}
|
|
216
|
+
return node.map((part, i) => i === 0 ? part : rewriteScalarArrayUses(part, arrays, op))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const safeScalarObjectUse = (node, name, keys) => {
|
|
220
|
+
if (typeof node === 'string') return node !== name
|
|
221
|
+
if (!Array.isArray(node)) return true
|
|
222
|
+
const op = node[0]
|
|
223
|
+
if (ASSIGN_TARGET_OPS.has(op) && node[1] === name) return false
|
|
224
|
+
if ((op === 'let' || op === 'const') && node.slice(1).some(d => d === name || (Array.isArray(d) && d[1] === name))) return false
|
|
225
|
+
if ((op === '.' || op === '?.') && node[1] === name) return keys.has(node[2])
|
|
226
|
+
if (op === '[]' && node[1] === name) {
|
|
227
|
+
const key = staticPropertyKey(node[2])
|
|
228
|
+
return key != null && keys.has(key)
|
|
229
|
+
}
|
|
230
|
+
if (op === '...' && node[1] === name) return false
|
|
231
|
+
for (let i = 1; i < node.length; i++) {
|
|
232
|
+
if (!safeScalarObjectUse(node[i], name, keys)) return false
|
|
233
|
+
}
|
|
234
|
+
return true
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const rewriteScalarObjectUses = (node, objects) => {
|
|
238
|
+
if (!Array.isArray(node)) return node
|
|
239
|
+
const op = node[0]
|
|
240
|
+
if ((op === '.' || op === '?.') && objects.has(node[1])) {
|
|
241
|
+
const fields = objects.get(node[1])
|
|
242
|
+
return fields.get(node[2]) ?? [, undefined]
|
|
243
|
+
}
|
|
244
|
+
if (op === '[]' && objects.has(node[1])) {
|
|
245
|
+
const key = staticPropertyKey(node[2])
|
|
246
|
+
const fields = objects.get(node[1])
|
|
247
|
+
return key != null ? (fields.get(key) ?? [, undefined]) : node
|
|
248
|
+
}
|
|
249
|
+
return node.map((part, i) => i === 0 ? part : rewriteScalarObjectUses(part, objects))
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const typedArraySlotIndex = (node, len) => {
|
|
253
|
+
const idx = constIntExpr(node)
|
|
254
|
+
return idx != null && idx >= 0 && idx < len ? idx : null
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const safeScalarTypedArrayUse = (node, name, len) => {
|
|
258
|
+
if (typeof node === 'string') return node !== name
|
|
259
|
+
if (!Array.isArray(node)) return true
|
|
260
|
+
const op = node[0]
|
|
261
|
+
if ((op === 'let' || op === 'const') && node.slice(1).some(d => d === name || (Array.isArray(d) && d[1] === name))) return false
|
|
262
|
+
if ((op === '.' || op === '?.') && node[1] === name) return node[2] === 'length'
|
|
263
|
+
if (op === '[]' && node[1] === name) return typedArraySlotIndex(node[2], len) != null
|
|
264
|
+
if ((op === '++' || op === '--') && Array.isArray(node[1]) && node[1][0] === '[]' && node[1][1] === name)
|
|
265
|
+
return typedArraySlotIndex(node[1][2], len) != null
|
|
266
|
+
if (ASSIGN_TARGET_OPS.has(op)) {
|
|
267
|
+
if (node[1] === name) return false
|
|
268
|
+
if (Array.isArray(node[1]) && node[1][0] === '[]' && node[1][1] === name) {
|
|
269
|
+
if (typedArraySlotIndex(node[1][2], len) == null) return false
|
|
270
|
+
for (let i = 2; i < node.length; i++) if (!safeScalarTypedArrayUse(node[i], name, len)) return false
|
|
271
|
+
return true
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (op === '...' && node[1] === name) return false
|
|
275
|
+
for (let i = 1; i < node.length; i++) if (!safeScalarTypedArrayUse(node[i], name, len)) return false
|
|
276
|
+
return true
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const mentionsName = (node, name) => {
|
|
280
|
+
if (typeof node === 'string') return node === name
|
|
281
|
+
if (!Array.isArray(node) || node[0] === '=>') return false
|
|
282
|
+
for (let i = 1; i < node.length; i++) if (mentionsName(node[i], name)) return true
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const rewriteScalarTypedArrayUses = (node, arrays) => {
|
|
287
|
+
if (!Array.isArray(node)) return node
|
|
288
|
+
const op = node[0]
|
|
289
|
+
const slotFor = (idxNode, entry) => {
|
|
290
|
+
const idx = typedArraySlotIndex(idxNode, entry.len)
|
|
291
|
+
return idx == null ? null : entry.slots[idx]
|
|
292
|
+
}
|
|
293
|
+
if ((op === '.' || op === '?.') && arrays.has(node[1]) && node[2] === 'length') return [null, arrays.get(node[1]).len]
|
|
294
|
+
if (op === '[]' && arrays.has(node[1])) return slotFor(node[2], arrays.get(node[1])) ?? node
|
|
295
|
+
if ((op === '++' || op === '--') && Array.isArray(node[1]) && node[1][0] === '[]' && arrays.has(node[1][1])) {
|
|
296
|
+
const slot = slotFor(node[1][2], arrays.get(node[1][1]))
|
|
297
|
+
return slot ? [op, slot] : node
|
|
298
|
+
}
|
|
299
|
+
if (ASSIGN_TARGET_OPS.has(op) && Array.isArray(node[1]) && node[1][0] === '[]' && arrays.has(node[1][1])) {
|
|
300
|
+
const slot = slotFor(node[1][2], arrays.get(node[1][1]))
|
|
301
|
+
return slot ? [op, slot, ...node.slice(2).map(part => rewriteScalarTypedArrayUses(part, arrays))] : node
|
|
302
|
+
}
|
|
303
|
+
return node.map((part, i) => i === 0 ? part : rewriteScalarTypedArrayUses(part, arrays))
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const scalarTypedArrayStores = (name, entry) =>
|
|
307
|
+
entry.slots.map((slot, i) => ['=', ['[]', name, [null, i]], slot])
|
|
308
|
+
|
|
309
|
+
const scalarTypedArrayLoads = (name, entry) =>
|
|
310
|
+
entry.slots.map((slot, i) => ['=', slot, ['[]', name, [null, i]]])
|
|
311
|
+
|
|
312
|
+
const collectScalarTypedArrayWrites = (node, name, len, out = new Set()) => {
|
|
313
|
+
if (!Array.isArray(node)) return out
|
|
314
|
+
const op = node[0]
|
|
315
|
+
const addSlot = target => {
|
|
316
|
+
if (Array.isArray(target) && target[0] === '[]' && target[1] === name) {
|
|
317
|
+
const idx = typedArraySlotIndex(target[2], len)
|
|
318
|
+
if (idx != null) out.add(idx)
|
|
319
|
+
return true
|
|
320
|
+
}
|
|
321
|
+
return false
|
|
322
|
+
}
|
|
323
|
+
if ((op === '++' || op === '--') && addSlot(node[1])) return out
|
|
324
|
+
if (ASSIGN_TARGET_OPS.has(op) && addSlot(node[1])) {
|
|
325
|
+
for (let i = 2; i < node.length; i++) collectScalarTypedArrayWrites(node[i], name, len, out)
|
|
326
|
+
return out
|
|
327
|
+
}
|
|
328
|
+
if (op !== '=>') for (let i = 1; i < node.length; i++) collectScalarTypedArrayWrites(node[i], name, len, out)
|
|
329
|
+
return out
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const scalarizeTypedArrayLiteralSeq = (seq) => {
|
|
333
|
+
if (!Array.isArray(seq) || seq[0] !== ';') return { node: seq, changed: false }
|
|
334
|
+
let changed = false
|
|
335
|
+
const stmts = seq.slice(1).map(stmt => {
|
|
336
|
+
const r = scalarizeTypedArrayLiterals(stmt)
|
|
337
|
+
changed ||= r.changed
|
|
338
|
+
return r.node
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
const candidates = new Map()
|
|
342
|
+
const mirrored = new Map()
|
|
343
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
344
|
+
const stmt = stmts[i]
|
|
345
|
+
if (!Array.isArray(stmt) || (stmt[0] !== 'let' && stmt[0] !== 'const') || stmt.length !== 2) continue
|
|
346
|
+
const decl = stmt[1]
|
|
347
|
+
if (!Array.isArray(decl) || decl[0] !== '=' || typeof decl[1] !== 'string') continue
|
|
348
|
+
const len = fixedScalarTypedArrayLen(decl[2])
|
|
349
|
+
if (len == null) continue
|
|
350
|
+
let hasSafeUse = false, hasUnsafeUse = false
|
|
351
|
+
for (let j = 0; j < stmts.length; j++) {
|
|
352
|
+
if (j === i) continue
|
|
353
|
+
if (!mentionsName(stmts[j], decl[1])) continue
|
|
354
|
+
const safe = safeScalarTypedArrayUse(stmts[j], decl[1], len)
|
|
355
|
+
hasSafeUse ||= safe
|
|
356
|
+
hasUnsafeUse ||= !safe
|
|
357
|
+
}
|
|
358
|
+
if (!hasSafeUse && hasUnsafeUse) continue
|
|
359
|
+
if (!hasUnsafeUse) candidates.set(decl[1], { index: i, len, mirrored: false })
|
|
360
|
+
else mirrored.set(decl[1], { index: i, len, mirrored: true })
|
|
361
|
+
}
|
|
362
|
+
if (!candidates.size && !mirrored.size) return { node: changed ? [';', ...stmts] : seq, changed }
|
|
363
|
+
|
|
364
|
+
const arrays = new Map()
|
|
365
|
+
for (const [name, c] of [...candidates, ...mirrored]) {
|
|
366
|
+
const slots = Array.from({ length: c.len }, (_, k) => `${name}${T}ta${ctx.func.uniq++}_${k}`)
|
|
367
|
+
arrays.set(name, { len: c.len, slots, mirrored: c.mirrored })
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const out = []
|
|
371
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
372
|
+
const entry = [...candidates.entries()].find(([, c]) => c.index === i) ||
|
|
373
|
+
[...mirrored.entries()].find(([, c]) => c.index === i)
|
|
374
|
+
if (entry) {
|
|
375
|
+
const [name] = entry
|
|
376
|
+
const arr = arrays.get(name)
|
|
377
|
+
const { slots } = arr
|
|
378
|
+
if (arr.mirrored) {
|
|
379
|
+
out.push(stmts[i])
|
|
380
|
+
if (slots.length) out.push(['let', ...slots.map(slot => ['=', slot, [null, 0]])])
|
|
381
|
+
} else if (slots.length) {
|
|
382
|
+
out.push(['let', ...slots.map(slot => ['=', slot, [null, 0]])])
|
|
383
|
+
}
|
|
384
|
+
changed = true
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
387
|
+
const unsafe = []
|
|
388
|
+
for (const [name, arr] of arrays) {
|
|
389
|
+
if (arr.mirrored && mentionsName(stmts[i], name) && !safeScalarTypedArrayUse(stmts[i], name, arr.len)) unsafe.push([name, arr])
|
|
390
|
+
}
|
|
391
|
+
if (unsafe.length) {
|
|
392
|
+
for (const [name, arr] of unsafe) out.push(...scalarTypedArrayStores(name, arr))
|
|
393
|
+
out.push(stmts[i])
|
|
394
|
+
for (const [name, arr] of unsafe) out.push(...scalarTypedArrayLoads(name, arr))
|
|
395
|
+
changed = true
|
|
396
|
+
} else {
|
|
397
|
+
out.push(rewriteScalarTypedArrayUses(stmts[i], arrays))
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return { node: [';', ...out], changed: true }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function scalarizeTypedArrayLiterals(node) {
|
|
404
|
+
if (!Array.isArray(node)) return { node, changed: false }
|
|
405
|
+
if (node[0] === '=>') return { node, changed: false }
|
|
406
|
+
if (node[0] === ';') return scalarizeTypedArrayLiteralSeq(node)
|
|
407
|
+
let changed = false
|
|
408
|
+
const out = [node[0]]
|
|
409
|
+
for (let i = 1; i < node.length; i++) {
|
|
410
|
+
const r = scalarizeTypedArrayLiterals(node[i])
|
|
411
|
+
changed ||= r.changed
|
|
412
|
+
out.push(r.node)
|
|
413
|
+
}
|
|
414
|
+
return changed ? { node: out, changed: true } : { node, changed: false }
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const stmtList = (body) => {
|
|
418
|
+
if (!Array.isArray(body)) return body == null ? [] : [body]
|
|
419
|
+
if (body[0] === '{}') return stmtList(body[1])
|
|
420
|
+
if (body[0] === ';') return body.slice(1)
|
|
421
|
+
return [body]
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const hasControlTransfer = node => scanBody(node, n => CONTROL_TRANSFER.has(n[0]))
|
|
425
|
+
|
|
426
|
+
const containsDeclOf = (body, name) => scanBody(body, n => {
|
|
427
|
+
if (n[0] !== 'let' && n[0] !== 'const') return false
|
|
428
|
+
for (let i = 1; i < n.length; i++) {
|
|
429
|
+
const d = n[i]
|
|
430
|
+
if (d === name) return true
|
|
431
|
+
if (Array.isArray(d) && d[0] === '=' && d[1] === name) return true
|
|
432
|
+
}
|
|
433
|
+
return false
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
const isReassigned = (body, name) => scanBody(body, n =>
|
|
437
|
+
(ASSIGN_OPS.has(n[0]) && n[1] === name) || ((n[0] === '++' || n[0] === '--') && n[1] === name))
|
|
438
|
+
|
|
439
|
+
const containsTypedArrayAccess = (body, names) => scanBody(body, n => n[0] === '[]' && typeof n[1] === 'string' && names.has(n[1]))
|
|
440
|
+
|
|
441
|
+
function smallScalarTypedForTrip(init, cond, step) {
|
|
442
|
+
if (!Array.isArray(init) || init[0] !== 'let' || init.length !== 2) return null
|
|
443
|
+
const decl = init[1]
|
|
444
|
+
if (!Array.isArray(decl) || decl[0] !== '=' || typeof decl[1] !== 'string') return null
|
|
445
|
+
const name = decl[1]
|
|
446
|
+
if (constIntExpr(decl[2]) !== 0) return null
|
|
447
|
+
if (!Array.isArray(cond) || cond[0] !== '<' || cond[1] !== name) return null
|
|
448
|
+
const end = constIntExpr(cond[2])
|
|
449
|
+
if (end == null || end < 0 || end > MAX_SCALAR_TYPED_LOOP_UNROLL) return null
|
|
450
|
+
const stepOk = Array.isArray(step) && ((step[0] === '++' && step[1] === name) ||
|
|
451
|
+
(step[0] === '-' && Array.isArray(step[1]) && step[1][0] === '++' && step[1][1] === name && constIntExpr(step[2]) === 1))
|
|
452
|
+
return stepOk ? { name, end } : null
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const scalarTypedLoopBudget = (body) => {
|
|
456
|
+
if (!Array.isArray(body) || body[0] === '=>') return 1
|
|
457
|
+
if (body[0] === 'for') {
|
|
458
|
+
const trip = smallScalarTypedForTrip(body[1], body[2], body[3])
|
|
459
|
+
return trip ? trip.end * scalarTypedLoopBudget(body[4]) : 1
|
|
460
|
+
}
|
|
461
|
+
let max = 1
|
|
462
|
+
for (let i = 1; i < body.length; i++) max = Math.max(max, scalarTypedLoopBudget(body[i]))
|
|
463
|
+
return max
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const unrollTypedArrayLoops = (node, names) => {
|
|
467
|
+
if (!Array.isArray(node) || node[0] === '=>') return { node, changed: false }
|
|
468
|
+
if (node[0] === ';') {
|
|
469
|
+
let changed = false
|
|
470
|
+
const out = [';']
|
|
471
|
+
for (const stmt of node.slice(1)) {
|
|
472
|
+
const r = unrollTypedArrayLoops(stmt, names)
|
|
473
|
+
changed ||= r.changed
|
|
474
|
+
if (Array.isArray(r.node) && r.node[0] === ';') out.push(...r.node.slice(1))
|
|
475
|
+
else out.push(r.node)
|
|
476
|
+
}
|
|
477
|
+
return changed ? { node: out, changed: true } : { node, changed: false }
|
|
478
|
+
}
|
|
479
|
+
if (node[0] === '{}') {
|
|
480
|
+
const r = unrollTypedArrayLoops(node[1], names)
|
|
481
|
+
return r.changed ? { node: ['{}', r.node], changed: true } : { node, changed: false }
|
|
482
|
+
}
|
|
483
|
+
if (node[0] === 'for') {
|
|
484
|
+
const trip = smallScalarTypedForTrip(node[1], node[2], node[3])
|
|
485
|
+
if (trip && containsTypedArrayAccess(node[4], names) && scalarTypedLoopBudget(node[4]) * trip.end <= MAX_SCALAR_TYPED_NESTED_UNROLL &&
|
|
486
|
+
!hasControlTransfer(node[4]) && !containsDeclOf(node[4], trip.name) && !isReassigned(node[4], trip.name)) {
|
|
487
|
+
const out = [';']
|
|
488
|
+
for (let i = 0; i < trip.end; i++) {
|
|
489
|
+
const cloned = cloneWithSubst(node[4], new Map([[trip.name, [null, i]]]), new Map())
|
|
490
|
+
const r = unrollTypedArrayLoops(cloned, names)
|
|
491
|
+
out.push(...stmtList(r.node))
|
|
492
|
+
}
|
|
493
|
+
return { node: out, changed: true }
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
let changed = false
|
|
497
|
+
const out = [node[0]]
|
|
498
|
+
for (let i = 1; i < node.length; i++) {
|
|
499
|
+
const r = unrollTypedArrayLoops(node[i], names)
|
|
500
|
+
changed ||= r.changed
|
|
501
|
+
out.push(r.node)
|
|
502
|
+
}
|
|
503
|
+
return changed ? { node: out, changed: true } : { node, changed: false }
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const fixedTypedArraysInBody = (body) => {
|
|
507
|
+
const out = new Map()
|
|
508
|
+
const walk = node => {
|
|
509
|
+
if (!Array.isArray(node) || node[0] === '=>') return
|
|
510
|
+
if (node[0] === 'let' || node[0] === 'const') {
|
|
511
|
+
for (let i = 1; i < node.length; i++) {
|
|
512
|
+
const d = node[i]
|
|
513
|
+
if (!Array.isArray(d) || d[0] !== '=' || typeof d[1] !== 'string') continue
|
|
514
|
+
const len = fixedScalarTypedArrayLen(d[2])
|
|
515
|
+
if (len != null) out.set(d[1], { len })
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
for (let i = 1; i < node.length; i++) walk(node[i])
|
|
519
|
+
}
|
|
520
|
+
walk(body)
|
|
521
|
+
return out
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const scalarTypedParamCandidates = (func, sites, fixedByFunc) => {
|
|
525
|
+
if (!sites?.length || func.exported || func.raw || !func.body || !Array.isArray(func.body) || func.body[0] !== '{}') return new Map()
|
|
526
|
+
if (scanBody(func.body, n => n[0] === 'return' || n[0] === 'throw')) return new Map()
|
|
527
|
+
const params = func.sig?.params || []
|
|
528
|
+
const cands = new Map()
|
|
529
|
+
for (let i = 0; i < params.length; i++) {
|
|
530
|
+
const pname = params[i].name
|
|
531
|
+
let len = null, ok = true
|
|
532
|
+
for (const site of sites) {
|
|
533
|
+
const arg = site.argList[i]
|
|
534
|
+
const fixed = typeof arg === 'string' ? fixedByFunc.get(site.callerFunc)?.get(arg) : null
|
|
535
|
+
if (!fixed) { ok = false; break }
|
|
536
|
+
if (len == null) len = fixed.len
|
|
537
|
+
else if (len !== fixed.len) { ok = false; break }
|
|
538
|
+
}
|
|
539
|
+
if (ok && len != null) cands.set(pname, { len })
|
|
540
|
+
}
|
|
541
|
+
if (!cands.size) return cands
|
|
542
|
+
for (const site of sites) {
|
|
543
|
+
const seen = new Set()
|
|
544
|
+
for (let i = 0; i < params.length; i++) {
|
|
545
|
+
if (!cands.has(params[i].name)) continue
|
|
546
|
+
const arg = site.argList[i]
|
|
547
|
+
if (typeof arg !== 'string' || seen.has(arg)) return new Map()
|
|
548
|
+
seen.add(arg)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return cands
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const scalarizeTypedArrayParams = (func, paramCands) => {
|
|
555
|
+
for (const [name, c] of [...paramCands]) if (!safeScalarTypedArrayUse(func.body, name, c.len)) paramCands.delete(name)
|
|
556
|
+
if (!paramCands.size) return { body: func.body, changed: false }
|
|
557
|
+
const arrays = new Map()
|
|
558
|
+
for (const [name, c] of paramCands) {
|
|
559
|
+
arrays.set(name, {
|
|
560
|
+
len: c.len,
|
|
561
|
+
slots: Array.from({ length: c.len }, (_, k) => `${name}${T}tap${ctx.func.uniq++}_${k}`),
|
|
562
|
+
})
|
|
563
|
+
}
|
|
564
|
+
const prologue = []
|
|
565
|
+
const writeback = []
|
|
566
|
+
for (const [name, { len, slots }] of arrays) {
|
|
567
|
+
if (slots.length) prologue.push(['let', ...slots.map((slot, i) => ['=', slot, ['[]', name, [null, i]]])])
|
|
568
|
+
for (const i of collectScalarTypedArrayWrites(func.body, name, len)) writeback.push(['=', ['[]', name, [null, i]], slots[i]])
|
|
569
|
+
}
|
|
570
|
+
const rewritten = stmtList(func.body).map(stmt => rewriteScalarTypedArrayUses(stmt, arrays))
|
|
571
|
+
return { body: ['{}', [';', ...prologue, ...rewritten, ...writeback]], changed: true }
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const scalarizeFunctionTypedArrays = (programFacts) => {
|
|
575
|
+
const fixedByFunc = new Map(ctx.func.list.map(func => [func, fixedTypedArraysInBody(func.body)]))
|
|
576
|
+
const sitesByCallee = new Map()
|
|
577
|
+
for (const site of programFacts.callSites) {
|
|
578
|
+
if (!site.callerFunc) continue
|
|
579
|
+
const list = sitesByCallee.get(site.callee)
|
|
580
|
+
if (list) list.push(site); else sitesByCallee.set(site.callee, [site])
|
|
581
|
+
}
|
|
582
|
+
let changed = false
|
|
583
|
+
for (const func of ctx.func.list) {
|
|
584
|
+
if (!func.body || func.raw) continue
|
|
585
|
+
const paramCands = scalarTypedParamCandidates(func, sitesByCallee.get(func.name), fixedByFunc)
|
|
586
|
+
const names = new Set([...paramCands.keys(), ...fixedByFunc.get(func).keys()])
|
|
587
|
+
if (names.size) {
|
|
588
|
+
let guard = 0
|
|
589
|
+
while (guard++ < 6) {
|
|
590
|
+
const r = unrollTypedArrayLoops(func.body, names)
|
|
591
|
+
if (!r.changed) break
|
|
592
|
+
func.body = r.node
|
|
593
|
+
changed = true
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const p = scalarizeTypedArrayParams(func, paramCands)
|
|
597
|
+
if (p.changed) { func.body = p.body; changed = true }
|
|
598
|
+
const l = scalarizeTypedArrayLiterals(func.body)
|
|
599
|
+
if (l.changed) { func.body = l.node; changed = true }
|
|
600
|
+
if (changed) invalidateLocalsCache(func.body)
|
|
601
|
+
}
|
|
602
|
+
return changed
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const scalarizeArrayLiteralSeq = (seq) => {
|
|
606
|
+
if (!Array.isArray(seq) || seq[0] !== ';') return { node: seq, changed: false }
|
|
607
|
+
let changed = false
|
|
608
|
+
const stmts = seq.slice(1).map(stmt => {
|
|
609
|
+
const r = scalarizeArrayLiterals(stmt)
|
|
610
|
+
changed ||= r.changed
|
|
611
|
+
return r.node
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
const candidates = new Map()
|
|
615
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
616
|
+
const stmt = stmts[i]
|
|
617
|
+
if (!Array.isArray(stmt) || (stmt[0] !== 'let' && stmt[0] !== 'const') || stmt.length !== 2) continue
|
|
618
|
+
const decl = stmt[1]
|
|
619
|
+
if (!Array.isArray(decl) || decl[0] !== '=' || typeof decl[1] !== 'string') continue
|
|
620
|
+
const elems = scalarArrayElems(decl[2])
|
|
621
|
+
if (!elems) continue
|
|
622
|
+
let ok = true
|
|
623
|
+
for (let j = 0; j < stmts.length && ok; j++) {
|
|
624
|
+
if (j === i) continue
|
|
625
|
+
ok = safeScalarArrayUse(stmts[j], decl[1])
|
|
626
|
+
}
|
|
627
|
+
if (!ok) continue
|
|
628
|
+
candidates.set(decl[1], { index: i, op: stmt[0], elems })
|
|
629
|
+
}
|
|
630
|
+
if (!candidates.size) return { node: changed ? [';', ...stmts] : seq, changed }
|
|
631
|
+
|
|
632
|
+
const arrays = new Map()
|
|
633
|
+
for (const [name, c] of candidates) {
|
|
634
|
+
const temps = c.elems.map((_, k) => `${name}${T}arr${ctx.func.uniq++}_${k}`)
|
|
635
|
+
arrays.set(name, temps)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const out = []
|
|
639
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
640
|
+
const entry = [...candidates.entries()].find(([, c]) => c.index === i)
|
|
641
|
+
if (entry) {
|
|
642
|
+
const [name, c] = entry
|
|
643
|
+
const temps = arrays.get(name)
|
|
644
|
+
if (temps.length) {
|
|
645
|
+
out.push([c.op, ...temps.map((tmp, k) =>
|
|
646
|
+
['=', tmp, rewriteScalarArrayUses(c.elems[k], arrays)])])
|
|
647
|
+
}
|
|
648
|
+
changed = true
|
|
649
|
+
continue
|
|
650
|
+
}
|
|
651
|
+
out.push(rewriteScalarArrayUses(stmts[i], arrays))
|
|
652
|
+
}
|
|
653
|
+
return { node: [';', ...out], changed: true }
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const scalarizeObjectLiteralSeq = (seq, escapes) => {
|
|
657
|
+
if (!Array.isArray(seq) || seq[0] !== ';') return { node: seq, changed: false }
|
|
658
|
+
let changed = false
|
|
659
|
+
const stmts = seq.slice(1).map(stmt => {
|
|
660
|
+
const r = scalarizeObjectLiterals(stmt, escapes)
|
|
661
|
+
changed ||= r.changed
|
|
662
|
+
return r.node
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
const candidates = new Map()
|
|
666
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
667
|
+
const stmt = stmts[i]
|
|
668
|
+
if (!Array.isArray(stmt) || (stmt[0] !== 'let' && stmt[0] !== 'const') || stmt.length !== 2) continue
|
|
669
|
+
const decl = stmt[1]
|
|
670
|
+
if (!Array.isArray(decl) || decl[0] !== '=' || typeof decl[1] !== 'string') continue
|
|
671
|
+
if (escapes.get(decl[1]) !== false) continue
|
|
672
|
+
const props = scalarObjectProps(decl[2])
|
|
673
|
+
if (!props) continue
|
|
674
|
+
const keys = new Set(props.names)
|
|
675
|
+
let ok = true
|
|
676
|
+
for (let j = 0; j < stmts.length && ok; j++) {
|
|
677
|
+
if (j === i) continue
|
|
678
|
+
ok = safeScalarObjectUse(stmts[j], decl[1], keys)
|
|
679
|
+
}
|
|
680
|
+
if (!ok) continue
|
|
681
|
+
candidates.set(decl[1], { index: i, op: stmt[0], props })
|
|
682
|
+
}
|
|
683
|
+
if (!candidates.size) return { node: changed ? [';', ...stmts] : seq, changed }
|
|
684
|
+
|
|
685
|
+
const objects = new Map()
|
|
686
|
+
for (const [name, c] of candidates) {
|
|
687
|
+
const fields = new Map()
|
|
688
|
+
for (let i = 0; i < c.props.names.length; i++) {
|
|
689
|
+
fields.set(c.props.names[i], `${name}${T}obj${ctx.func.uniq++}_${i}`)
|
|
690
|
+
}
|
|
691
|
+
objects.set(name, fields)
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const out = []
|
|
695
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
696
|
+
const entry = [...candidates.entries()].find(([, c]) => c.index === i)
|
|
697
|
+
if (entry) {
|
|
698
|
+
const [, c] = entry
|
|
699
|
+
const fields = objects.get(entry[0])
|
|
700
|
+
if (c.props.names.length) {
|
|
701
|
+
out.push([c.op, ...c.props.names.map((prop, k) =>
|
|
702
|
+
['=', fields.get(prop), rewriteScalarObjectUses(c.props.values[k], objects)])])
|
|
703
|
+
}
|
|
704
|
+
changed = true
|
|
705
|
+
continue
|
|
706
|
+
}
|
|
707
|
+
out.push(rewriteScalarObjectUses(stmts[i], objects))
|
|
708
|
+
}
|
|
709
|
+
return { node: [';', ...out], changed: true }
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function scalarizeObjectLiterals(node, escapes) {
|
|
713
|
+
if (!Array.isArray(node)) return { node, changed: false }
|
|
714
|
+
if (node[0] === '=>') return { node, changed: false }
|
|
715
|
+
if (node[0] === ';') return scalarizeObjectLiteralSeq(node, escapes)
|
|
716
|
+
let changed = false
|
|
717
|
+
const out = [node[0]]
|
|
718
|
+
for (let i = 1; i < node.length; i++) {
|
|
719
|
+
const r = scalarizeObjectLiterals(node[i], escapes)
|
|
720
|
+
changed ||= r.changed
|
|
721
|
+
out.push(r.node)
|
|
722
|
+
}
|
|
723
|
+
return changed ? { node: out, changed: true } : { node, changed: false }
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function scalarizeArrayLiterals(node) {
|
|
727
|
+
if (!Array.isArray(node)) return { node, changed: false }
|
|
728
|
+
if (node[0] === '=>') return { node, changed: false }
|
|
729
|
+
if (node[0] === ';') return scalarizeArrayLiteralSeq(node)
|
|
730
|
+
let changed = false
|
|
731
|
+
const out = [node[0]]
|
|
732
|
+
for (let i = 1; i < node.length; i++) {
|
|
733
|
+
const r = scalarizeArrayLiterals(node[i])
|
|
734
|
+
changed ||= r.changed
|
|
735
|
+
out.push(r.node)
|
|
736
|
+
}
|
|
737
|
+
return changed ? { node: out, changed: true } : { node, changed: false }
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const scalarizeFunctionArrayLiterals = () => {
|
|
741
|
+
let changed = false
|
|
742
|
+
for (const func of ctx.func.list) {
|
|
743
|
+
if (!func.body || func.raw) continue
|
|
744
|
+
let guard = 0
|
|
745
|
+
while (guard++ < 4) {
|
|
746
|
+
const r = scalarizeArrayLiterals(func.body)
|
|
747
|
+
if (!r.changed) break
|
|
748
|
+
func.body = r.node
|
|
749
|
+
changed = true
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return changed
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const scalarizeFunctionObjectLiterals = () => {
|
|
756
|
+
let changed = false
|
|
757
|
+
for (const func of ctx.func.list) {
|
|
758
|
+
if (!func.body || func.raw) continue
|
|
759
|
+
let guard = 0
|
|
760
|
+
while (guard++ < 4) {
|
|
761
|
+
const escapes = new Map(analyzeBody(func.body).escapes)
|
|
762
|
+
invalidateLocalsCache(func.body)
|
|
763
|
+
const r = scalarizeObjectLiterals(func.body, escapes)
|
|
764
|
+
if (!r.changed) break
|
|
765
|
+
func.body = r.node
|
|
766
|
+
changed = true
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return changed
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const cloneWithSubst = (node, subst, rename) => {
|
|
773
|
+
if (typeof node === 'string') {
|
|
774
|
+
if (subst.has(node)) return clonePlain(subst.get(node))
|
|
775
|
+
return rename.get(node) || node
|
|
776
|
+
}
|
|
777
|
+
if (!Array.isArray(node)) return node
|
|
778
|
+
const op = node[0]
|
|
779
|
+
if (op === 'str') return node.slice()
|
|
780
|
+
if (op === '.' || op === '?.') return [op, cloneWithSubst(node[1], subst, rename), node[2]]
|
|
781
|
+
if (op === ':') return [op, node[1], cloneWithSubst(node[2], subst, rename)]
|
|
782
|
+
return node.map((part, i) => i === 0 ? part : cloneWithSubst(part, subst, rename))
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Returns { prefix, value } where prefix is the substituted body statements
|
|
786
|
+
// (excluding any trailing `return X`), and value is the substituted return
|
|
787
|
+
// expression — null if void or no trailing return value.
|
|
788
|
+
const inlinedBody = (func, args) => {
|
|
789
|
+
const params = func.sig.params
|
|
790
|
+
if (args.length !== params.length || !args.every(isSimpleArg)) return null
|
|
791
|
+
const paramNames = new Set(params.map(p => p.name))
|
|
792
|
+
if (mutatesAny(func.body, paramNames)) return null
|
|
793
|
+
|
|
794
|
+
const subst = new Map()
|
|
795
|
+
for (let i = 0; i < params.length; i++) subst.set(params[i].name, args[i])
|
|
796
|
+
|
|
797
|
+
const locals = new Set()
|
|
798
|
+
collectBindings(func.body, locals)
|
|
799
|
+
for (const p of params) locals.delete(p.name)
|
|
800
|
+
|
|
801
|
+
const rename = new Map()
|
|
802
|
+
for (const name of locals) rename.set(name, `${T}inl${ctx.func.uniq++}_${name}`)
|
|
803
|
+
|
|
804
|
+
const stmts = blockStmts(func.body)
|
|
805
|
+
// Expression-bodied arrow `(c) => expr`: no statement block; the whole body
|
|
806
|
+
// *is* the return value. Treat as zero-prefix + value.
|
|
807
|
+
if (!stmts) return { prefix: [], value: cloneWithSubst(func.body, subst, rename) }
|
|
808
|
+
const last = stmts.length ? stmts[stmts.length - 1] : null
|
|
809
|
+
const isTrailingReturn = Array.isArray(last) && last[0] === 'return'
|
|
810
|
+
const prefixSrc = isTrailingReturn ? stmts.slice(0, -1) : stmts
|
|
811
|
+
const prefix = prefixSrc.map(stmt => cloneWithSubst(stmt, subst, rename))
|
|
812
|
+
const value = isTrailingReturn && last.length > 1 ? cloneWithSubst(last[1], subst, rename) : null
|
|
813
|
+
return { prefix, value }
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const isCandidateCall = (node, candidates) =>
|
|
817
|
+
Array.isArray(node) && node[0] === '()' && typeof node[1] === 'string' && candidates.has(node[1])
|
|
818
|
+
|
|
819
|
+
// Recursively substitute calls to expr-bodied candidates anywhere in `node`.
|
|
820
|
+
// Used for tiny pure-expression helpers (`isAlpha(c) => …`) that get called
|
|
821
|
+
// from expression contexts (if-conditions, ternary tests). For these the
|
|
822
|
+
// inlined body is value-only (zero prefix), so a pure substitution is safe.
|
|
823
|
+
const inlineInExpr = (node, candidates) => {
|
|
824
|
+
if (!Array.isArray(node)) return { node, changed: false }
|
|
825
|
+
if (node[0] === '=>') return { node, changed: false }
|
|
826
|
+
let changed = false
|
|
827
|
+
const next = [node[0]]
|
|
828
|
+
for (let i = 1; i < node.length; i++) {
|
|
829
|
+
const r = inlineInExpr(node[i], candidates)
|
|
830
|
+
if (r.changed) changed = true
|
|
831
|
+
next.push(r.node)
|
|
832
|
+
}
|
|
833
|
+
if (isCandidateCall(next, candidates)) {
|
|
834
|
+
const args = callArgs(next)
|
|
835
|
+
const shape = args && inlinedBody(candidates.get(next[1]), args)
|
|
836
|
+
if (shape && shape.value !== null && shape.prefix.length === 0) {
|
|
837
|
+
return { node: shape.value, changed: true }
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return { node: changed ? next : node, changed }
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const inlineInStmt = (stmt, candidates) => {
|
|
844
|
+
if (!Array.isArray(stmt)) return { node: stmt, changed: false }
|
|
845
|
+
// Statement-position call: discard return value, splice prefix in place.
|
|
846
|
+
if (isCandidateCall(stmt, candidates)) {
|
|
847
|
+
const args = callArgs(stmt)
|
|
848
|
+
const shape = args && inlinedBody(candidates.get(stmt[1]), args)
|
|
849
|
+
if (shape) return { node: ['{}', [';', ...shape.prefix]], changed: true, splice: shape.prefix }
|
|
850
|
+
}
|
|
851
|
+
// `let/const X = call(...)` with single decl: inline as prefix + decl(value).
|
|
852
|
+
if ((stmt[0] === 'let' || stmt[0] === 'const') && stmt.length === 2) {
|
|
853
|
+
const decl = stmt[1]
|
|
854
|
+
if (Array.isArray(decl) && decl[0] === '=' && typeof decl[1] === 'string' && isCandidateCall(decl[2], candidates)) {
|
|
855
|
+
const args = callArgs(decl[2])
|
|
856
|
+
const shape = args && inlinedBody(candidates.get(decl[2][1]), args)
|
|
857
|
+
if (shape && shape.value !== null) {
|
|
858
|
+
const splice = [...shape.prefix, [stmt[0], ['=', decl[1], shape.value]]]
|
|
859
|
+
return { node: ['{}', [';', ...splice]], changed: true, splice }
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
// `X = call(...)` at statement position: inline as prefix + assign(value).
|
|
864
|
+
if (stmt[0] === '=' && typeof stmt[1] === 'string' && isCandidateCall(stmt[2], candidates)) {
|
|
865
|
+
const args = callArgs(stmt[2])
|
|
866
|
+
const shape = args && inlinedBody(candidates.get(stmt[2][1]), args)
|
|
867
|
+
if (shape && shape.value !== null) {
|
|
868
|
+
const splice = [...shape.prefix, ['=', stmt[1], shape.value]]
|
|
869
|
+
return { node: ['{}', [';', ...splice]], changed: true, splice }
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const op = stmt[0]
|
|
873
|
+
if (op === ';') {
|
|
874
|
+
let changed = false
|
|
875
|
+
const next = [';']
|
|
876
|
+
for (let i = 1; i < stmt.length; i++) {
|
|
877
|
+
const r = inlineInStmt(stmt[i], candidates)
|
|
878
|
+
changed ||= r.changed
|
|
879
|
+
if (r.splice) next.push(...r.splice)
|
|
880
|
+
else next.push(r.node)
|
|
881
|
+
}
|
|
882
|
+
return changed ? { node: next, changed } : { node: stmt, changed: false }
|
|
883
|
+
}
|
|
884
|
+
if (op === '{}') {
|
|
885
|
+
const r = inlineInStmt(stmt[1], candidates)
|
|
886
|
+
return r.changed ? { node: ['{}', r.node], changed: true } : { node: stmt, changed: false }
|
|
887
|
+
}
|
|
888
|
+
if (op === 'for') {
|
|
889
|
+
const r = inlineInStmt(stmt[4], candidates)
|
|
890
|
+
return r.changed ? { node: ['for', stmt[1], stmt[2], stmt[3], r.node], changed: true } : { node: stmt, changed: false }
|
|
891
|
+
}
|
|
892
|
+
if (op === 'while') {
|
|
893
|
+
const r = inlineInStmt(stmt[2], candidates)
|
|
894
|
+
return r.changed ? { node: ['while', stmt[1], r.node], changed: true } : { node: stmt, changed: false }
|
|
895
|
+
}
|
|
896
|
+
if (op === 'if') {
|
|
897
|
+
const thenR = inlineInStmt(stmt[2], candidates)
|
|
898
|
+
const elseR = stmt.length > 3 ? inlineInStmt(stmt[3], candidates) : null
|
|
899
|
+
if (thenR.changed || elseR?.changed) return {
|
|
900
|
+
node: stmt.length > 3 ? ['if', stmt[1], thenR.node, elseR.node] : ['if', stmt[1], thenR.node],
|
|
901
|
+
changed: true,
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (op === 'try' || op === 'catch' || op === 'finally') {
|
|
905
|
+
let changed = false
|
|
906
|
+
const next = [op]
|
|
907
|
+
for (let i = 1; i < stmt.length; i++) {
|
|
908
|
+
const part = stmt[i]
|
|
909
|
+
const r = Array.isArray(part) ? inlineInStmt(part, candidates) : { node: part, changed: false }
|
|
910
|
+
changed ||= r.changed
|
|
911
|
+
next.push(r.node)
|
|
912
|
+
}
|
|
913
|
+
return changed ? { node: next, changed: true } : { node: stmt, changed: false }
|
|
914
|
+
}
|
|
915
|
+
return { node: stmt, changed: false }
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const inlineHotInternalCalls = (programFacts, ast) => {
|
|
919
|
+
const cfg = ctx.transform.optimize
|
|
920
|
+
if (cfg && cfg.sourceInline === false) return false
|
|
921
|
+
|
|
922
|
+
const fixedByFunc = new Map(ctx.func.list.map(func => [func, fixedTypedArraysInBody(func.body)]))
|
|
923
|
+
const sitesByCallee = new Map()
|
|
924
|
+
for (const cs of programFacts.callSites) {
|
|
925
|
+
const list = sitesByCallee.get(cs.callee)
|
|
926
|
+
if (list) list.push(cs); else sitesByCallee.set(cs.callee, [cs])
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const containsNode = (root, needle, inLoop = false) => {
|
|
930
|
+
if (root === needle) return inLoop
|
|
931
|
+
if (!Array.isArray(root) || root[0] === '=>') return false
|
|
932
|
+
const nextInLoop = inLoop || LOOP_OPS.has(root[0])
|
|
933
|
+
for (let i = 1; i < root.length; i++) if (containsNode(root[i], needle, nextInLoop)) return true
|
|
934
|
+
return false
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const hasFixedTypedArraySites = (func, sites) => {
|
|
938
|
+
const params = func.sig?.params || []
|
|
939
|
+
if (!sites?.length) return false
|
|
940
|
+
return sites.every(site => params.some((p, i) => {
|
|
941
|
+
const arg = site.argList[i]
|
|
942
|
+
return typeof arg === 'string' && fixedByFunc.get(site.callerFunc)?.has(arg)
|
|
943
|
+
}))
|
|
944
|
+
}
|
|
945
|
+
const hasFixedTypedArrayHotSites = (func, sites) =>
|
|
946
|
+
hasFixedTypedArraySites(func, sites) && sites.some(site => site.callerFunc?.body && containsNode(site.callerFunc.body, site.node))
|
|
947
|
+
|
|
948
|
+
const candidates = new Map()
|
|
949
|
+
for (const func of ctx.func.list) {
|
|
950
|
+
if (func.exported || func.raw || !func.body || func.rest || programFacts.valueUsed.has(func.name)) continue
|
|
951
|
+
if (func.defaults && Object.keys(func.defaults).length) continue
|
|
952
|
+
const sites = sitesByCallee.get(func.name)
|
|
953
|
+
const fixedTypedArraySite = hasFixedTypedArraySites(func, sites)
|
|
954
|
+
if (!sites || sites.length < 1 || (!fixedTypedArraySite && sites.length > 2) || sites.length > 8) continue
|
|
955
|
+
const stmts = blockStmts(func.body)
|
|
956
|
+
// Expression-bodied arrow funcs (`(c) => expr`) have no block — body IS the
|
|
957
|
+
// return value. Treat as a "tiny leaf" branch handled below; force hasLoop=false.
|
|
958
|
+
if (scanBody(func.body, n => n[0] === '=>')) continue
|
|
959
|
+
// throw/break/continue are unsupported; return is OK if it's a single
|
|
960
|
+
// trailing return (rewritten to a value at inlining time).
|
|
961
|
+
if (scanBody(func.body, n => n[0] === 'throw' || n[0] === 'break' || n[0] === 'continue')) continue
|
|
962
|
+
let returnCount = 0
|
|
963
|
+
scanBody(func.body, n => { if (n[0] === 'return') returnCount++; return false })
|
|
964
|
+
if (returnCount > 1) continue
|
|
965
|
+
if (returnCount === 1 && stmts) {
|
|
966
|
+
const last = stmts[stmts.length - 1]
|
|
967
|
+
if (!Array.isArray(last) || last[0] !== 'return') continue
|
|
968
|
+
}
|
|
969
|
+
// Either a kernel (has a loop) or a tiny leaf (no loop, no calls, small body).
|
|
970
|
+
// The leaf branch catches helpers like `isAlpha(c) => (c>=65 && c<=90) || …`
|
|
971
|
+
// that get hammered from a hot caller's loop — replacing the call with its
|
|
972
|
+
// body saves the per-iteration call+reinterpret overhead (tokenizer hot path).
|
|
973
|
+
const hasLoop = scanBody(func.body, n => LOOP_OPS.has(n[0]))
|
|
974
|
+
if (!hasLoop) {
|
|
975
|
+
if (scanBody(func.body, n => n[0] === '()')) continue
|
|
976
|
+
if (nodeSize(func.body) > 30) continue
|
|
977
|
+
}
|
|
978
|
+
if (scanBody(func.body, n => n[0] === '()' && n[1] === func.name)) continue
|
|
979
|
+
// Kernels with nested loops (depth ≥ 2) are typically large and the inner
|
|
980
|
+
// loop carries most of the cost. Inlining them into a host that V8 can't
|
|
981
|
+
// tier up (e.g. a once-called wrapper) freezes the kernel in baseline.
|
|
982
|
+
// Keep them as standalone functions so V8 wasm tier-up can warm them.
|
|
983
|
+
if (loopDepth(func.body, 0) >= 2 && !fixedTypedArraySite) continue
|
|
984
|
+
// Factory functions that allocate pointers (`new TypedArray`, `new Array`,
|
|
985
|
+
// object/array literals returned) break downstream pointer-ABI specialization
|
|
986
|
+
// when inlined: narrow.js can't trace the post-inline alias chain back to a
|
|
987
|
+
// single ctor, so the typed-array param of a callee like processCascade(x, …)
|
|
988
|
+
// stays at generic f64 ABI with __typed_idx dispatch instead of i32 + f64.load.
|
|
989
|
+
// Keeping the factory as a callable function preserves the call-site type fact.
|
|
990
|
+
if (scanBody(func.body, n => n[0] === '()' && typeof n[1] === 'string' && n[1].startsWith('new.'))) continue
|
|
991
|
+
candidates.set(func.name, func)
|
|
992
|
+
}
|
|
993
|
+
if (!candidates.size) return false
|
|
994
|
+
|
|
995
|
+
// Trivial expr-bodied candidates can be substituted at any expression position
|
|
996
|
+
// (if-condition, ternary, etc.). Stmt-bodied ones go through inlineInStmt's
|
|
997
|
+
// statement-level path which preserves prefix ordering.
|
|
998
|
+
const exprOnlyCandidates = new Map()
|
|
999
|
+
for (const [name, func] of candidates) {
|
|
1000
|
+
if (!Array.isArray(func.body) || func.body[0] !== '{}') exprOnlyCandidates.set(name, func)
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
let changed = false
|
|
1004
|
+
const exportedCandidates = new Map()
|
|
1005
|
+
for (const [name, func] of candidates) {
|
|
1006
|
+
const sites = sitesByCallee.get(name)
|
|
1007
|
+
if (hasFixedTypedArraySites(func, sites) &&
|
|
1008
|
+
!sites.some(site => site.callerFunc?.exported && site.callerFunc.body && containsNode(site.callerFunc.body, site.node))) {
|
|
1009
|
+
exportedCandidates.set(name, func)
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
for (const func of ctx.func.list) {
|
|
1013
|
+
if (!func.body || func.raw) continue
|
|
1014
|
+
// Skip exports: they're entry points usually invoked once. Inlining a
|
|
1015
|
+
// hot kernel here would put the loop into a function V8's wasm tier-up
|
|
1016
|
+
// never warms (kernel stays in baseline). Keeping the kernel as its own
|
|
1017
|
+
// callable function lets V8 promote it to TurboFan after a few calls.
|
|
1018
|
+
// Exception: fixed-size typed-array callees should inline into the exported
|
|
1019
|
+
// caller so scalar replacement can cross the call boundary and remove the
|
|
1020
|
+
// caller's heap arrays.
|
|
1021
|
+
const activeCandidates = func.exported ? exportedCandidates : candidates
|
|
1022
|
+
if (func.exported && !activeCandidates.size) continue
|
|
1023
|
+
const r = inlineInStmt(func.body, activeCandidates)
|
|
1024
|
+
let body = r.changed ? r.node : func.body
|
|
1025
|
+
let bodyChanged = r.changed
|
|
1026
|
+
if (!func.exported && exprOnlyCandidates.size) {
|
|
1027
|
+
const e = inlineInExpr(body, exprOnlyCandidates)
|
|
1028
|
+
if (e.changed) { body = e.node; bodyChanged = true }
|
|
1029
|
+
}
|
|
1030
|
+
if (bodyChanged) { func.body = body; changed = true }
|
|
1031
|
+
}
|
|
1032
|
+
if (ast) {
|
|
1033
|
+
const r = inlineInStmt(ast, candidates)
|
|
1034
|
+
if (r.changed) changed = true
|
|
1035
|
+
}
|
|
1036
|
+
return changed
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const restIndexExpr = (idx, restParams) => {
|
|
1040
|
+
const k = intLit(idx)
|
|
1041
|
+
if (k != null) return k >= 0 && k < restParams.length ? restParams[k] : [, undefined]
|
|
1042
|
+
|
|
1043
|
+
let out = [, undefined]
|
|
1044
|
+
for (let i = restParams.length - 1; i >= 0; i--) {
|
|
1045
|
+
out = ['?:', ['==', clonePlain(idx), [, i]], restParams[i], out]
|
|
1046
|
+
}
|
|
1047
|
+
return out
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const rewriteRestBody = (node, restName, restParams) => {
|
|
1051
|
+
if (typeof node === 'string') return node === restName ? { ok: false } : { ok: true, node }
|
|
1052
|
+
if (!Array.isArray(node)) return { ok: true, node }
|
|
1053
|
+
if (node[0] === 'str') return { ok: true, node: node.slice() }
|
|
1054
|
+
|
|
1055
|
+
if ((node[0] === '.' || node[0] === '?.') && node[1] === restName) {
|
|
1056
|
+
return node[2] === 'length' ? { ok: true, node: [, restParams.length] } : { ok: false }
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (node[0] === '[]' && node[1] === restName) {
|
|
1060
|
+
if (!isSimpleArg(node[2])) return { ok: false }
|
|
1061
|
+
return { ok: true, node: restIndexExpr(node[2], restParams) }
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const out = [node[0]]
|
|
1065
|
+
for (let i = 1; i < node.length; i++) {
|
|
1066
|
+
const r = rewriteRestBody(node[i], restName, restParams)
|
|
1067
|
+
if (!r.ok) return r
|
|
1068
|
+
out.push(r.node)
|
|
1069
|
+
}
|
|
1070
|
+
return { ok: true, node: out }
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const specializeFixedRestCalls = (programFacts) => {
|
|
1074
|
+
const sitesByKey = new Map()
|
|
1075
|
+
for (const site of programFacts.callSites) {
|
|
1076
|
+
const func = ctx.func.map.get(site.callee)
|
|
1077
|
+
if (!func?.rest || func.exported || func.raw || !func.body) continue
|
|
1078
|
+
if (programFacts.valueUsed.has(func.name)) continue
|
|
1079
|
+
if (func.defaults && Object.keys(func.defaults).length) continue
|
|
1080
|
+
if (site.argList.some(a => Array.isArray(a) && a[0] === '...')) continue
|
|
1081
|
+
|
|
1082
|
+
const fixedN = func.sig.params.length - 1
|
|
1083
|
+
const restN = Math.max(0, site.argList.length - fixedN)
|
|
1084
|
+
const key = `${func.name}/${restN}`
|
|
1085
|
+
const list = sitesByKey.get(key)
|
|
1086
|
+
if (list) list.push(site); else sitesByKey.set(key, [site])
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
let changed = false
|
|
1090
|
+
for (const [key, sites] of sitesByKey) {
|
|
1091
|
+
const [name, restNText] = key.split('/')
|
|
1092
|
+
const func = ctx.func.map.get(name)
|
|
1093
|
+
const restN = Number(restNText)
|
|
1094
|
+
const fixedParams = func.sig.params.slice(0, -1).map(p => ({ ...p }))
|
|
1095
|
+
const restName = func.rest
|
|
1096
|
+
const restParams = Array.from({ length: restN }, (_, i) => `${restName}${T}r${restN}_${i}`)
|
|
1097
|
+
const rewritten = rewriteRestBody(func.body, restName, restParams)
|
|
1098
|
+
if (!rewritten.ok) continue
|
|
1099
|
+
|
|
1100
|
+
const cloneName = `${name}${T}rest${restN}`
|
|
1101
|
+
if (!ctx.func.map.has(cloneName)) {
|
|
1102
|
+
const restSigParams = restParams.map(name => ({ name, type: 'f64' }))
|
|
1103
|
+
const clone = {
|
|
1104
|
+
...func,
|
|
1105
|
+
name: cloneName,
|
|
1106
|
+
exported: false,
|
|
1107
|
+
rest: null,
|
|
1108
|
+
sig: {
|
|
1109
|
+
...func.sig,
|
|
1110
|
+
params: [...fixedParams, ...restSigParams],
|
|
1111
|
+
results: [...func.sig.results],
|
|
1112
|
+
},
|
|
1113
|
+
body: rewritten.node,
|
|
1114
|
+
}
|
|
1115
|
+
delete clone.defaults
|
|
1116
|
+
ctx.func.list.push(clone)
|
|
1117
|
+
ctx.func.names.add(cloneName)
|
|
1118
|
+
ctx.func.map.set(cloneName, clone)
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const fixedN = func.sig.params.length - 1
|
|
1122
|
+
for (const site of sites) {
|
|
1123
|
+
site.node[1] = cloneName
|
|
1124
|
+
setCallArgs(site.node, site.argList.slice(0, fixedN + restN))
|
|
1125
|
+
changed = true
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return changed
|
|
1129
|
+
}
|
|
1130
|
+
|
|
8
1131
|
const scanGlobalValueFacts = (root) => {
|
|
9
1132
|
if (!root) return
|
|
10
1133
|
const stmts = Array.isArray(root) && root[0] === ';' ? root.slice(1) : [root]
|
|
@@ -89,7 +1212,12 @@ export default function plan(ast) {
|
|
|
89
1212
|
scanGlobalValueFacts(ast)
|
|
90
1213
|
unboxConstTypedGlobals()
|
|
91
1214
|
|
|
92
|
-
|
|
1215
|
+
let programFacts = collectProgramFacts(ast)
|
|
1216
|
+
if (inlineHotInternalCalls(programFacts, ast)) programFacts = collectProgramFacts(ast)
|
|
1217
|
+
if (specializeFixedRestCalls(programFacts)) programFacts = collectProgramFacts(ast)
|
|
1218
|
+
if (scalarizeFunctionArrayLiterals()) programFacts = collectProgramFacts(ast)
|
|
1219
|
+
if (scalarizeFunctionObjectLiterals()) programFacts = collectProgramFacts(ast)
|
|
1220
|
+
if (scalarizeFunctionTypedArrays(programFacts)) programFacts = collectProgramFacts(ast)
|
|
93
1221
|
ctx.types.dynKeyVars = programFacts.dynVars
|
|
94
1222
|
ctx.types.anyDynKey = programFacts.anyDyn
|
|
95
1223
|
|
|
@@ -102,4 +1230,4 @@ export default function plan(ast) {
|
|
|
102
1230
|
refineDynKeys(programFacts)
|
|
103
1231
|
|
|
104
1232
|
return programFacts
|
|
105
|
-
}
|
|
1233
|
+
}
|