jz 0.2.1 → 0.3.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 +6 -25
- package/cli.js +19 -2
- package/index.js +14 -1
- package/module/array.js +66 -9
- package/module/collection.js +68 -35
- package/module/core.js +65 -19
- package/module/date.js +5 -0
- package/module/function.js +2 -1
- package/module/json.js +66 -8
- package/module/number.js +127 -5
- package/module/object.js +88 -1
- package/module/string.js +3 -2
- package/module/typedarray.js +7 -1
- package/package.json +3 -3
- package/src/analyze.js +16 -4
- package/src/assemble.js +544 -0
- package/src/ast.js +160 -0
- package/src/auto-config.js +120 -0
- package/src/autoload.js +4 -1
- package/src/compile.js +22 -620
- package/src/ctx.js +33 -5
- package/src/emit.js +73 -165
- package/src/host.js +12 -0
- package/src/ir.js +13 -0
- package/src/jzify.js +306 -7
- package/src/narrow.js +2 -1
- package/src/optimize.js +298 -24
- package/src/plan.js +220 -34
- package/src/prepare.js +22 -2
- package/src/vectorize.js +3 -12
package/src/ctx.js
CHANGED
|
@@ -209,6 +209,7 @@ export function reset(proto, globals) {
|
|
|
209
209
|
ctx.error = {
|
|
210
210
|
src: '',
|
|
211
211
|
loc: null,
|
|
212
|
+
node: null,
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
ctx.transform = {
|
|
@@ -259,15 +260,42 @@ export function reset(proto, globals) {
|
|
|
259
260
|
|
|
260
261
|
/** Throw with source location context. */
|
|
261
262
|
export function err(msg) {
|
|
263
|
+
let detail = msg
|
|
264
|
+
|
|
262
265
|
if (ctx.error.loc != null && ctx.error.src) {
|
|
263
266
|
const before = ctx.error.src.slice(0, ctx.error.loc)
|
|
264
267
|
const line = before.split('\n').length
|
|
265
268
|
const col = ctx.error.loc - before.lastIndexOf('\n')
|
|
266
269
|
const src = ctx.error.src.split('\n')[line - 1]
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
detail += `\n at line ${line}:${col}\n ${src}\n ${' '.repeat(col - 1)}^`
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (ctx.func.current?.name) {
|
|
274
|
+
detail += `\n in function: ${ctx.func.current.name}`
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (ctx.error.node != null) {
|
|
278
|
+
detail += `\n current AST: ${formatErrorNode(ctx.error.node)}`
|
|
271
279
|
}
|
|
272
|
-
|
|
280
|
+
|
|
281
|
+
const e = new Error(detail)
|
|
282
|
+
const stackLines = e.stack.split('\n')
|
|
283
|
+
const firstFrame = stackLines.findIndex(line => line.trimStart().startsWith('at '))
|
|
284
|
+
const frames = firstFrame >= 0 ? stackLines.slice(firstFrame) : stackLines.slice(1)
|
|
285
|
+
e.stack = `${e.name}: ${detail}\n${frames.join('\n')}`
|
|
286
|
+
throw e
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function formatErrorNode(node) {
|
|
290
|
+
const seen = new WeakSet()
|
|
291
|
+
const json = JSON.stringify(node, (_key, value) => {
|
|
292
|
+
if (typeof value === 'bigint') return `${value}n`
|
|
293
|
+
if (typeof value === 'symbol') return value.toString()
|
|
294
|
+
if (Array.isArray(value)) {
|
|
295
|
+
if (seen.has(value)) return '[Circular]'
|
|
296
|
+
seen.add(value)
|
|
297
|
+
}
|
|
298
|
+
return value
|
|
299
|
+
})
|
|
300
|
+
return json.length > 2000 ? `${json.slice(0, 2000)}...` : json
|
|
273
301
|
}
|
package/src/emit.js
CHANGED
|
@@ -23,7 +23,13 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { ctx, err, inc, PTR } from './ctx.js'
|
|
26
|
-
import { T, VAL, valTypeOf, lookupValType, extractParams, classifyParam, findFreeVars, STMT_OPS, repOf, updateRep, repOfGlobal, staticPropertyKey } from './analyze.js'
|
|
26
|
+
import { T, VAL, nonNegIntLiteral, valTypeOf, lookupValType, extractParams, classifyParam, findFreeVars, STMT_OPS, repOf, updateRep, repOfGlobal, staticPropertyKey } from './analyze.js'
|
|
27
|
+
import {
|
|
28
|
+
isReassigned, hasOwnContinue, hasOwnBreakOrContinue, containsNestedClosure,
|
|
29
|
+
containsNestedLoop, nestedSmallLoopBudget, containsDeclOf, cloneWithSubst,
|
|
30
|
+
containsKnownTypedArrayIndex, smallConstForTripCount, isTerminator,
|
|
31
|
+
MAX_SMALL_FOR_UNROLL, MAX_NESTED_FOR_UNROLL,
|
|
32
|
+
} from './ast.js'
|
|
27
33
|
import {
|
|
28
34
|
typed, asF64, asI32, asI64, asPtrOffset, asParamType, toI32, fromI64,
|
|
29
35
|
NULL_IR, nullExpr, undefExpr, MAX_CLOSURE_ARITY,
|
|
@@ -168,11 +174,6 @@ function stringLiteral(node) {
|
|
|
168
174
|
return null
|
|
169
175
|
}
|
|
170
176
|
|
|
171
|
-
function nonNegIntLiteral(node) {
|
|
172
|
-
const n = intLiteralValue(node)
|
|
173
|
-
return n != null && n >= 0 ? n : null
|
|
174
|
-
}
|
|
175
|
-
|
|
176
177
|
function emitSingleCharIndexCmp(a, b, negate = false) {
|
|
177
178
|
const leftLit = stringLiteral(a)
|
|
178
179
|
const rightLit = stringLiteral(b)
|
|
@@ -269,142 +270,6 @@ function extractRefinements(cond, out, sense = true) {
|
|
|
269
270
|
return out
|
|
270
271
|
}
|
|
271
272
|
|
|
272
|
-
/** Detect whether `name` is written to (=, +=, ++, --, etc.) anywhere within `body`.
|
|
273
|
-
* Conservative over-reject: if unsure, treat as written.
|
|
274
|
-
* `let`/`const` declarations are NOT reassignments — only the initializer expressions
|
|
275
|
-
* inside them are scanned. (Treating `let g = ...` as a write of `g` would defeat A3.) */
|
|
276
|
-
export function isReassigned(body, name) {
|
|
277
|
-
if (!Array.isArray(body)) return false
|
|
278
|
-
const op = body[0]
|
|
279
|
-
if (op === '=' || op === '+=' || op === '-=' || op === '*=' || op === '/=' || op === '%='
|
|
280
|
-
|| op === '&=' || op === '|=' || op === '^=' || op === '<<=' || op === '>>=' || op === '>>>='
|
|
281
|
-
|| op === '||=' || op === '&&=' || op === '??=') {
|
|
282
|
-
if (body[1] === name) return true
|
|
283
|
-
}
|
|
284
|
-
if ((op === '++' || op === '--') && body[1] === name) return true
|
|
285
|
-
if (op === 'let' || op === 'const') {
|
|
286
|
-
// Each decl item is either a bare name (string) or `['=', pattern, init]`.
|
|
287
|
-
// Only the init expression can contain real reassignments — recurse into it only.
|
|
288
|
-
for (let i = 1; i < body.length; i++) {
|
|
289
|
-
const d = body[i]
|
|
290
|
-
if (Array.isArray(d) && d[0] === '=' && d[2] != null && isReassigned(d[2], name)) return true
|
|
291
|
-
}
|
|
292
|
-
return false
|
|
293
|
-
}
|
|
294
|
-
for (let i = 1; i < body.length; i++) if (isReassigned(body[i], name)) return true
|
|
295
|
-
return false
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/** Does `body` contain a `continue` that targets THIS loop?
|
|
299
|
-
* A `continue` inside a nested `for`/`while`/`do` targets the inner loop, so we don't count it. */
|
|
300
|
-
function hasOwnContinue(body) {
|
|
301
|
-
if (!Array.isArray(body)) return false
|
|
302
|
-
const op = body[0]
|
|
303
|
-
if (op === 'continue') return true
|
|
304
|
-
if (op === 'for' || op === 'while' || op === 'do') return false
|
|
305
|
-
for (let i = 1; i < body.length; i++) if (hasOwnContinue(body[i])) return true
|
|
306
|
-
return false
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function hasOwnBreakOrContinue(body) {
|
|
310
|
-
if (!Array.isArray(body)) return false
|
|
311
|
-
const op = body[0]
|
|
312
|
-
if (op === 'break' || op === 'continue') return true
|
|
313
|
-
if (op === 'for' || op === 'while' || op === 'do' || op === '=>') return false
|
|
314
|
-
for (let i = 1; i < body.length; i++) if (hasOwnBreakOrContinue(body[i])) return true
|
|
315
|
-
return false
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function containsNestedClosure(body) {
|
|
319
|
-
if (!Array.isArray(body)) return false
|
|
320
|
-
if (body[0] === '=>') return true
|
|
321
|
-
for (let i = 1; i < body.length; i++) if (containsNestedClosure(body[i])) return true
|
|
322
|
-
return false
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function containsNestedLoop(body) {
|
|
326
|
-
if (!Array.isArray(body)) return false
|
|
327
|
-
const op = body[0]
|
|
328
|
-
if (op === 'for' || op === 'while' || op === 'do') return true
|
|
329
|
-
if (op === '=>') return false
|
|
330
|
-
for (let i = 1; i < body.length; i++) if (containsNestedLoop(body[i])) return true
|
|
331
|
-
return false
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function nestedSmallLoopBudget(body) {
|
|
335
|
-
if (!Array.isArray(body)) return 1
|
|
336
|
-
if (body[0] === '=>') return 1
|
|
337
|
-
if (body[0] === 'for') {
|
|
338
|
-
const [, init, cond, step, loopBody] = body
|
|
339
|
-
const n = smallConstForTripCount(init, cond, step)
|
|
340
|
-
return n == null ? MAX_NESTED_FOR_UNROLL + 1 : n * nestedSmallLoopBudget(loopBody)
|
|
341
|
-
}
|
|
342
|
-
let max = 1
|
|
343
|
-
for (let i = 1; i < body.length; i++) max = Math.max(max, nestedSmallLoopBudget(body[i]))
|
|
344
|
-
return max
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function containsDeclOf(body, name) {
|
|
348
|
-
if (!Array.isArray(body)) return false
|
|
349
|
-
const op = body[0]
|
|
350
|
-
if (op === '=>') return false
|
|
351
|
-
if (op === 'let' || op === 'const') {
|
|
352
|
-
for (let i = 1; i < body.length; i++) {
|
|
353
|
-
const d = body[i]
|
|
354
|
-
if (d === name) return true
|
|
355
|
-
if (Array.isArray(d) && d[0] === '=' && d[1] === name) return true
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
for (let i = 1; i < body.length; i++) if (containsDeclOf(body[i], name)) return true
|
|
359
|
-
return false
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function intLiteralValue(expr) {
|
|
363
|
-
let v = null
|
|
364
|
-
if (typeof expr === 'number') v = expr
|
|
365
|
-
else if (Array.isArray(expr) && expr[0] == null && typeof expr[1] === 'number') v = expr[1]
|
|
366
|
-
else if (Array.isArray(expr) && expr[0] === 'u-' && typeof expr[1] === 'number') v = -expr[1]
|
|
367
|
-
else if (typeof expr === 'string') v = repOf(expr)?.intConst ?? ctx.scope.constInts?.get(expr) ?? null
|
|
368
|
-
return v != null && Number.isInteger(v) && v >= -2147483648 && v <= 2147483647 ? v : null
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function cloneWithSubst(node, name, value) {
|
|
372
|
-
if (node === name) return [null, value]
|
|
373
|
-
if (!Array.isArray(node)) return node
|
|
374
|
-
if (node[0] === '=>') return node
|
|
375
|
-
return node.map(x => cloneWithSubst(x, name, value))
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const MAX_SMALL_FOR_UNROLL = 8
|
|
379
|
-
const MAX_NESTED_FOR_UNROLL = 64
|
|
380
|
-
|
|
381
|
-
function containsKnownTypedArrayIndex(body) {
|
|
382
|
-
if (!Array.isArray(body)) return false
|
|
383
|
-
if (body[0] === '=>') return false
|
|
384
|
-
if (body[0] === '[]' && typeof body[1] === 'string' && ctx.types.typedElem?.has(body[1])) return true
|
|
385
|
-
for (let i = 1; i < body.length; i++) if (containsKnownTypedArrayIndex(body[i])) return true
|
|
386
|
-
return false
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function smallConstForTripCount(init, cond, step) {
|
|
390
|
-
if (!Array.isArray(init) || init[0] !== 'let' || init.length !== 2) return null
|
|
391
|
-
const decl = init[1]
|
|
392
|
-
if (!Array.isArray(decl) || decl[0] !== '=' || typeof decl[1] !== 'string') return null
|
|
393
|
-
const name = decl[1]
|
|
394
|
-
const start = intLiteralValue(decl[2])
|
|
395
|
-
if (start !== 0) return null
|
|
396
|
-
|
|
397
|
-
if (!Array.isArray(cond) || cond[0] !== '<' || cond[1] !== name) return null
|
|
398
|
-
const end = intLiteralValue(cond[2])
|
|
399
|
-
if (end == null || end < 0 || end > MAX_SMALL_FOR_UNROLL) return null
|
|
400
|
-
|
|
401
|
-
const stepOk = Array.isArray(step) && (
|
|
402
|
-
(step[0] === '++' && step[1] === name) ||
|
|
403
|
-
(step[0] === '-' && Array.isArray(step[1]) && step[1][0] === '++' && step[1][1] === name && intLiteralValue(step[2]) === 1)
|
|
404
|
-
)
|
|
405
|
-
return stepOk ? end : null
|
|
406
|
-
}
|
|
407
|
-
|
|
408
273
|
function unrollSmallConstFor(init, cond, step, body) {
|
|
409
274
|
const end = smallConstForTripCount(init, cond, step)
|
|
410
275
|
if (end == null) return null
|
|
@@ -422,21 +287,23 @@ function unrollSmallConstFor(init, cond, step, body) {
|
|
|
422
287
|
return out
|
|
423
288
|
}
|
|
424
289
|
|
|
425
|
-
|
|
426
|
-
* Used for early-return refinement: after `if (!guard) return`, `guard` holds for the rest. */
|
|
427
|
-
function isTerminator(body) {
|
|
290
|
+
function canThrow(body, seen = new Set()) {
|
|
428
291
|
if (!Array.isArray(body)) return false
|
|
429
292
|
const op = body[0]
|
|
430
|
-
if (op === '
|
|
431
|
-
|
|
432
|
-
if (op === '
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
293
|
+
if (op === 'throw') return true
|
|
294
|
+
if (op === '=>') return false
|
|
295
|
+
if (op === '()') {
|
|
296
|
+
const callee = body[1]
|
|
297
|
+
if (typeof callee === 'string') {
|
|
298
|
+
const bodyName = ctx.func.directClosures?.get(callee)
|
|
299
|
+
const f = ctx.func.map?.get(bodyName || callee)
|
|
300
|
+
if (f?.body && !f.raw && !seen.has(f.name)) {
|
|
301
|
+
seen.add(f.name)
|
|
302
|
+
if (canThrow(f.body, seen)) return true
|
|
303
|
+
}
|
|
437
304
|
}
|
|
438
|
-
return false
|
|
439
305
|
}
|
|
306
|
+
for (let i = 1; i < body.length; i++) if (canThrow(body[i], seen)) return true
|
|
440
307
|
return false
|
|
441
308
|
}
|
|
442
309
|
|
|
@@ -894,6 +761,23 @@ function needsRelationalToNumber(expr, vt) {
|
|
|
894
761
|
return mayReadBoxedValue(expr)
|
|
895
762
|
}
|
|
896
763
|
|
|
764
|
+
function needsLooseEqualityToNumber(expr, vt) {
|
|
765
|
+
if (vt === VAL.STRING) return true
|
|
766
|
+
if (vt != null) return false
|
|
767
|
+
return mayReadBoxedValue(expr)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function looseNumberEq(numIR, otherNode, otherIR, negate = false) {
|
|
771
|
+
const t = temp('eq')
|
|
772
|
+
const other = typed(['local.get', `$${t}`], 'f64')
|
|
773
|
+
const cmp = ['f64.eq', asF64(numIR), toNumF64(otherNode, other)]
|
|
774
|
+
return typed(['block', ['result', 'i32'],
|
|
775
|
+
['local.set', `$${t}`, asF64(otherIR)],
|
|
776
|
+
['if', ['result', 'i32'], isNullish(other),
|
|
777
|
+
['then', ['i32.const', negate ? 1 : 0]],
|
|
778
|
+
['else', negate ? ['i32.eqz', cmp] : cmp]]], 'i32')
|
|
779
|
+
}
|
|
780
|
+
|
|
897
781
|
function mayReadBoxedValue(expr) {
|
|
898
782
|
return Array.isArray(expr) && (expr[0] === '.' || expr[0] === '[]' || expr[0] === '?.' || expr[0] === '?.[]')
|
|
899
783
|
}
|
|
@@ -966,10 +850,7 @@ export const emitter = {
|
|
|
966
850
|
';': (...args) => {
|
|
967
851
|
const out = []
|
|
968
852
|
for (const a of args) {
|
|
969
|
-
|
|
970
|
-
if (r == null) continue
|
|
971
|
-
out.push(...flat(r))
|
|
972
|
-
if (r?.type && r.type !== 'void') out.push('drop')
|
|
853
|
+
out.push(...emitFlat(a))
|
|
973
854
|
}
|
|
974
855
|
return out
|
|
975
856
|
},
|
|
@@ -1013,6 +894,8 @@ export const emitter = {
|
|
|
1013
894
|
},
|
|
1014
895
|
|
|
1015
896
|
'catch': (body, errName, handler) => {
|
|
897
|
+
if (!canThrow(body)) return emitFlat(body)
|
|
898
|
+
|
|
1016
899
|
ctx.runtime.throws = true
|
|
1017
900
|
const id = ctx.func.uniq++
|
|
1018
901
|
ctx.func.locals.set(errName, 'f64')
|
|
@@ -1031,6 +914,14 @@ export const emitter = {
|
|
|
1031
914
|
},
|
|
1032
915
|
|
|
1033
916
|
'finally': (body, cleanup) => {
|
|
917
|
+
if (!canThrow(body)) {
|
|
918
|
+
const parentStack = ctx.func.finallyStack || []
|
|
919
|
+
const activeStack = parentStack.concat([cleanup])
|
|
920
|
+
const bodyIR = withFinallyStack(activeStack, () => emitFlat(body))
|
|
921
|
+
const cleanupIR = isTerminator(body) ? [] : withFinallyStack(parentStack, () => emitFlat(cleanup))
|
|
922
|
+
return [...bodyIR, ...cleanupIR]
|
|
923
|
+
}
|
|
924
|
+
|
|
1034
925
|
ctx.runtime.throws = true
|
|
1035
926
|
const id = ctx.func.uniq++
|
|
1036
927
|
const errLocal = temp('err')
|
|
@@ -1060,13 +951,14 @@ export const emitter = {
|
|
|
1060
951
|
|
|
1061
952
|
'return': expr => {
|
|
1062
953
|
const finalizers = emitFinalizers()
|
|
954
|
+
const finalizerBlock = () => [['block', ...finalizers]]
|
|
1063
955
|
if (ctx.func.current?.results.length > 1 && Array.isArray(expr) && expr[0] === '[') {
|
|
1064
956
|
const vals = expr.slice(1).map(e => asF64(emit(e)))
|
|
1065
957
|
if (finalizers.length === 0) return typed(['return', ...vals], 'void')
|
|
1066
958
|
const names = vals.map(() => temp('ret'))
|
|
1067
959
|
return [
|
|
1068
960
|
...vals.map((v, i) => ['local.set', `$${names[i]}`, v]),
|
|
1069
|
-
...
|
|
961
|
+
...finalizerBlock(),
|
|
1070
962
|
typed(['return', ...names.map(n => ['local.get', `$${n}`])], 'void'),
|
|
1071
963
|
]
|
|
1072
964
|
}
|
|
@@ -1082,7 +974,7 @@ export const emitter = {
|
|
|
1082
974
|
const name = ty === 'i32' ? tempI32('ret') : ty === 'i64' ? tempI64('ret') : temp('ret')
|
|
1083
975
|
return [
|
|
1084
976
|
['local.set', `$${name}`, ir],
|
|
1085
|
-
...
|
|
977
|
+
...finalizerBlock(),
|
|
1086
978
|
typed(['return', ['local.get', `$${name}`]], 'void'),
|
|
1087
979
|
]
|
|
1088
980
|
}
|
|
@@ -1354,6 +1246,8 @@ export const emitter = {
|
|
|
1354
1246
|
|
|
1355
1247
|
// Compound assignments: read-modify-write with type coercion
|
|
1356
1248
|
'+=': (name, val) => {
|
|
1249
|
+
// Complex LHS (obj.prop, arr[i]) → desugar to side-effect-safe `name = name + val`
|
|
1250
|
+
if (typeof name !== 'string') return emit(['=', name, ['+', name, val]])
|
|
1357
1251
|
// String concatenation: desugar to name = name + val (+ handler knows about strings).
|
|
1358
1252
|
// Also desugar when either side has unknown type — the `+` operator picks runtime
|
|
1359
1253
|
// string/numeric dispatch (`__is_str_key`); compoundAssign would force f64.add and
|
|
@@ -1366,17 +1260,24 @@ export const emitter = {
|
|
|
1366
1260
|
},
|
|
1367
1261
|
...Object.fromEntries([
|
|
1368
1262
|
['-=', 'sub'], ['*=', 'mul'], ['/=', 'div'],
|
|
1369
|
-
].map(([op, fn]) => [op, (name, val) =>
|
|
1370
|
-
(
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1263
|
+
].map(([op, fn]) => [op, (name, val) => {
|
|
1264
|
+
if (typeof name !== 'string') return emit(['=', name, [op.slice(0, -1), name, val]])
|
|
1265
|
+
return compoundAssign(name, val,
|
|
1266
|
+
(a, b) => typed([`f64.${fn}`, a, b], 'f64'),
|
|
1267
|
+
fn === 'div' ? null : (a, b) => typed([`i32.${fn}`, a, b], 'i32')
|
|
1268
|
+
)
|
|
1269
|
+
}])),
|
|
1270
|
+
'%=': (name, val) => {
|
|
1271
|
+
if (typeof name !== 'string') return emit(['=', name, ['%', name, val]])
|
|
1272
|
+
return compoundAssign(name, val, f64rem, (a, b) => typed(['i32.rem_s', a, b], 'i32'))
|
|
1273
|
+
},
|
|
1374
1274
|
|
|
1375
1275
|
// Bitwise compound assignments: i32 normally, i64 when either operand is BigInt
|
|
1376
1276
|
...Object.fromEntries([
|
|
1377
1277
|
['&=', 'and'], ['|=', 'or'], ['^=', 'xor'],
|
|
1378
1278
|
['>>=', 'shr_s'], ['<<=', 'shl'], ['>>>=', 'shr_u'],
|
|
1379
1279
|
].map(([op, fn]) => [op, (name, val) => {
|
|
1280
|
+
if (typeof name !== 'string') return emit(['=', name, [op.slice(0, -1), name, val]])
|
|
1380
1281
|
if (valTypeOf(name) === VAL.BIGINT || valTypeOf(val) === VAL.BIGINT) {
|
|
1381
1282
|
const void_ = _expect === 'void'
|
|
1382
1283
|
const result = fromI64([`i64.${fn}`, asI64(readVar(name)), asI64(emit(val))])
|
|
@@ -1566,6 +1467,8 @@ export const emitter = {
|
|
|
1566
1467
|
// Catches `closureVar === 34` in jzified hot loops where the unknown side has no VAL.
|
|
1567
1468
|
const vta = resolveValType(a, valTypeOf, lookupValType)
|
|
1568
1469
|
const vtb = resolveValType(b, valTypeOf, lookupValType)
|
|
1470
|
+
if (vta === VAL.NUMBER && needsLooseEqualityToNumber(b, vtb)) return looseNumberEq(va, b, vb)
|
|
1471
|
+
if (vtb === VAL.NUMBER && needsLooseEqualityToNumber(a, vta)) return looseNumberEq(vb, a, va)
|
|
1569
1472
|
if (vta === VAL.NUMBER || vtb === VAL.NUMBER) return typed(['f64.eq', asF64(va), asF64(vb)], 'i32')
|
|
1570
1473
|
// Reference-equal pointer kinds (same kind, non-STRING, non-BIGINT): i64 bit equality.
|
|
1571
1474
|
// JS `==` on objects/arrays/sets/maps/etc. is pure reference equality — no content path.
|
|
@@ -1593,6 +1496,8 @@ export const emitter = {
|
|
|
1593
1496
|
if (va.type === 'i32' && vb.type === 'i32') return typed(['i32.ne', va, vb], 'i32')
|
|
1594
1497
|
const vta = resolveValType(a, valTypeOf, lookupValType)
|
|
1595
1498
|
const vtb = resolveValType(b, valTypeOf, lookupValType)
|
|
1499
|
+
if (vta === VAL.NUMBER && needsLooseEqualityToNumber(b, vtb)) return looseNumberEq(va, b, vb, true)
|
|
1500
|
+
if (vtb === VAL.NUMBER && needsLooseEqualityToNumber(a, vta)) return looseNumberEq(vb, a, va, true)
|
|
1596
1501
|
if (vta === VAL.NUMBER || vtb === VAL.NUMBER) return typed(['f64.ne', asF64(va), asF64(vb)], 'i32')
|
|
1597
1502
|
if (vta && vta === vtb && REF_EQ_KINDS.has(vta)) {
|
|
1598
1503
|
return typed(['i64.ne', ['i64.reinterpret_f64', asF64(va)], ['i64.reinterpret_f64', asF64(vb)]], 'i32')
|
|
@@ -2514,7 +2419,10 @@ function liftOptionalChain(node) {
|
|
|
2514
2419
|
*/
|
|
2515
2420
|
export function emit(node, expect) {
|
|
2516
2421
|
_expect = expect || null
|
|
2517
|
-
if (Array.isArray(node)
|
|
2422
|
+
if (Array.isArray(node)) {
|
|
2423
|
+
ctx.error.node = node
|
|
2424
|
+
if (node.loc != null) ctx.error.loc = node.loc
|
|
2425
|
+
}
|
|
2518
2426
|
if (node == null) return null
|
|
2519
2427
|
if (node === true) return typed(['i32.const', 1], 'i32')
|
|
2520
2428
|
if (node === false) return typed(['i32.const', 0], 'i32')
|
package/src/host.js
CHANGED
|
@@ -607,6 +607,18 @@ const installDefaultEnvImports = (mod, imports, state) => {
|
|
|
607
607
|
imports.env.now = (clock) =>
|
|
608
608
|
clock === 1 ? (typeof performance !== 'undefined' ? performance.now() : Date.now()) : Date.now()
|
|
609
609
|
}
|
|
610
|
+
if (envFns.has('parseFloat') && !imports.env.parseFloat) {
|
|
611
|
+
imports.env.parseFloat = (valBig) => {
|
|
612
|
+
const s = state.mem.read(i64ToF64(valBig))
|
|
613
|
+
return parseFloat(s)
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (envFns.has('parseInt') && !imports.env.parseInt) {
|
|
617
|
+
imports.env.parseInt = (valBig, radix) => {
|
|
618
|
+
const s = state.mem.read(i64ToF64(valBig))
|
|
619
|
+
return parseInt(s, radix || undefined)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
610
622
|
// host: 'js' timer wiring. Wasm calls env.setTimeout/clearTimeout; we drive
|
|
611
623
|
// callbacks back via the exported __invoke_closure trampoline (state.invoke).
|
|
612
624
|
// Each id maps to a cancel thunk so set/clear share state without tagging.
|
package/src/ir.js
CHANGED
|
@@ -688,6 +688,19 @@ export const flat = ir => {
|
|
|
688
688
|
* Reconstruct arguments with spreads inserted at correct positions.
|
|
689
689
|
* Example: normal=[a, c], spreads=[{pos:1, expr:arr}] → [a, __spread(arr), c]
|
|
690
690
|
*/
|
|
691
|
+
/** Find the index of the first body-content child in a (func ...) WAT node.
|
|
692
|
+
* Skips $name, (export …), (import …), (type …), (param …), (result …), (local …). */
|
|
693
|
+
export function findBodyStart(fn) {
|
|
694
|
+
for (let i = 2; i < fn.length; i++) {
|
|
695
|
+
const c = fn[i]
|
|
696
|
+
if (!Array.isArray(c)) continue
|
|
697
|
+
if (c[0] === 'export' || c[0] === 'import' || c[0] === 'type' ||
|
|
698
|
+
c[0] === 'param' || c[0] === 'result' || c[0] === 'local') continue
|
|
699
|
+
return i
|
|
700
|
+
}
|
|
701
|
+
return fn.length
|
|
702
|
+
}
|
|
703
|
+
|
|
691
704
|
export function reconstructArgsWithSpreads(normal, spreads) {
|
|
692
705
|
const combined = []
|
|
693
706
|
let normalIdx = 0
|