jz 0.2.0 → 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 +8 -28
- package/cli.js +19 -2
- package/index.js +14 -4
- package/module/array.js +66 -9
- package/module/collection.js +68 -35
- package/module/core.js +65 -19
- package/module/date.js +104 -7
- 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 +8 -2
- package/module/typedarray.js +7 -1
- package/package.json +3 -3
- package/src/analyze.js +18 -6
- package/src/assemble.js +544 -0
- package/src/ast.js +160 -0
- package/src/auto-config.js +120 -0
- package/src/autoload.js +6 -3
- package/src/compile.js +22 -620
- package/src/ctx.js +33 -5
- package/src/emit.js +97 -165
- package/src/host.js +12 -0
- package/src/ir.js +19 -0
- package/src/jzify.js +306 -7
- package/src/narrow.js +2 -1
- package/src/optimize.js +298 -24
- package/src/plan.js +258 -37
- package/src/prepare.js +23 -2
- package/src/vectorize.js +3 -12
package/module/object.js
CHANGED
|
@@ -134,8 +134,9 @@ export default (ctx) => {
|
|
|
134
134
|
ctx.core.emit[`.${VAL.CLOSURE}:hasOwnProperty`] = ctx.core.emit['.hasOwnProperty']
|
|
135
135
|
|
|
136
136
|
ctx.core.emit['Object.values'] = (obj) => {
|
|
137
|
+
if (isHashTyped(obj)) return emitHashValues(obj)
|
|
137
138
|
const schema = resolveSchema(obj)
|
|
138
|
-
if (!schema)
|
|
139
|
+
if (!schema) return emitRuntimeValues(obj)
|
|
139
140
|
const va = asF64(emit(obj))
|
|
140
141
|
const n = schema.length
|
|
141
142
|
const t = temp('ov'), base = tempI32('vb')
|
|
@@ -225,6 +226,10 @@ export default (ctx) => {
|
|
|
225
226
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
226
227
|
}
|
|
227
228
|
|
|
229
|
+
ctx.core.emit['Object.defineProperty'] = () => {
|
|
230
|
+
err('Object.defineProperty descriptor semantics are outside jz scope; jzify only folds static bundler export helpers')
|
|
231
|
+
}
|
|
232
|
+
|
|
228
233
|
// Object.fromEntries(arr) → creates HASH from array of [key, value] pairs
|
|
229
234
|
ctx.core.emit['Object.fromEntries'] = (arr) => {
|
|
230
235
|
inc('__hash_new', '__hash_set')
|
|
@@ -450,6 +455,13 @@ function emitHashKeys(obj) {
|
|
|
450
455
|
hashKeysFromTemp(t)], 'f64')
|
|
451
456
|
}
|
|
452
457
|
|
|
458
|
+
function emitHashValues(obj) {
|
|
459
|
+
const t = temp('hv')
|
|
460
|
+
return typed(['block', ['result', 'f64'],
|
|
461
|
+
['local.set', `$${t}`, asF64(emit(obj))],
|
|
462
|
+
hashValuesFromTemp(t)], 'f64')
|
|
463
|
+
}
|
|
464
|
+
|
|
453
465
|
// Inline body of the HASH walk against an already-bound f64 local. Shared by
|
|
454
466
|
// the static-HASH path and the runtime-dispatch path so both produce the same
|
|
455
467
|
// IR shape from the same source — only difference is whether they enter from
|
|
@@ -481,6 +493,33 @@ function hashKeysFromTemp(t) {
|
|
|
481
493
|
out.ptr]
|
|
482
494
|
}
|
|
483
495
|
|
|
496
|
+
function hashValuesFromTemp(t) {
|
|
497
|
+
inc('__ptr_offset', '__cap', '__len')
|
|
498
|
+
const off = tempI32('hvo'), cap = tempI32('hvc'), n = tempI32('hvn')
|
|
499
|
+
const i = tempI32('hvi'), o = tempI32('hvj'), slot = tempI32('hvs')
|
|
500
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'hva' })
|
|
501
|
+
const id = ctx.func.uniq++
|
|
502
|
+
return ['block', ['result', 'f64'],
|
|
503
|
+
['local.set', `$${n}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
504
|
+
out.init,
|
|
505
|
+
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
506
|
+
['local.set', `$${cap}`, ['call', '$__cap', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
507
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
508
|
+
['local.set', `$${o}`, ['i32.const', 0]],
|
|
509
|
+
['block', `$vbrk${id}`, ['loop', `$vloop${id}`,
|
|
510
|
+
['br_if', `$vbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${cap}`]]],
|
|
511
|
+
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${off}`],
|
|
512
|
+
['i32.mul', ['local.get', `$${i}`], ['i32.const', 24]]]],
|
|
513
|
+
['if', ['f64.ne', ['f64.load', ['local.get', `$${slot}`]], ['f64.const', 0]],
|
|
514
|
+
['then',
|
|
515
|
+
elemStore(out.local, o,
|
|
516
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 16]]]),
|
|
517
|
+
['local.set', `$${o}`, ['i32.add', ['local.get', `$${o}`], ['i32.const', 1]]]]],
|
|
518
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
519
|
+
['br', `$vloop${id}`]]],
|
|
520
|
+
out.ptr]
|
|
521
|
+
}
|
|
522
|
+
|
|
484
523
|
// Type-unknown receiver: bind the value, branch on ptr-type. HASH walks the
|
|
485
524
|
// probe table; OBJECT loads the schema's key array (registered statically at
|
|
486
525
|
// compile time or lazily at runtime by JSON.parse via __jp_schema_get); other
|
|
@@ -509,6 +548,26 @@ function emitRuntimeKeys(obj) {
|
|
|
509
548
|
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]], 'f64')
|
|
510
549
|
}
|
|
511
550
|
|
|
551
|
+
function emitRuntimeValues(obj) {
|
|
552
|
+
inc('__ptr_type')
|
|
553
|
+
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
554
|
+
ctx.scope.globals.set('__schema_tbl', '(global $__schema_tbl (mut i32) (i32.const 0))')
|
|
555
|
+
const t = temp('rv'), tt = tempI32('rvt')
|
|
556
|
+
const empty = allocPtr({ type: PTR.ARRAY, len: 0, tag: 'rve' })
|
|
557
|
+
return typed(['block', ['result', 'f64'],
|
|
558
|
+
['local.set', `$${t}`, asF64(emit(obj))],
|
|
559
|
+
['local.set', `$${tt}`, ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
560
|
+
['if', ['result', 'f64'],
|
|
561
|
+
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.HASH]],
|
|
562
|
+
['then', hashValuesFromTemp(t)],
|
|
563
|
+
['else', ['if', ['result', 'f64'],
|
|
564
|
+
['i32.and',
|
|
565
|
+
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
566
|
+
['i32.ne', ['global.get', '$__schema_tbl'], ['i32.const', 0]]],
|
|
567
|
+
['then', objectValuesFromTemp(t)],
|
|
568
|
+
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]], 'f64')
|
|
569
|
+
}
|
|
570
|
+
|
|
512
571
|
// Schema-keyed Object.keys: copy the schema's keys array (a jz Array of
|
|
513
572
|
// STRINGs registered in __schema_tbl[sid]) into a fresh ARRAY so callers can
|
|
514
573
|
// mutate without aliasing the schema substrate.
|
|
@@ -537,3 +596,31 @@ function objectKeysFromTemp(t) {
|
|
|
537
596
|
['br', `$kloop${id}`]]],
|
|
538
597
|
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${out}`])]
|
|
539
598
|
}
|
|
599
|
+
|
|
600
|
+
function objectValuesFromTemp(t) {
|
|
601
|
+
inc('__alloc_hdr', '__ptr_offset')
|
|
602
|
+
const sid = tempI32('ovs'), src = tempI32('ovsrc'), n = tempI32('ovn')
|
|
603
|
+
const base = tempI32('ovbase'), out = tempI32('ovo'), i = tempI32('ovi')
|
|
604
|
+
const id = ctx.func.uniq++
|
|
605
|
+
return ['block', ['result', 'f64'],
|
|
606
|
+
['local.set', `$${sid}`, ['i32.wrap_i64', ['i64.and',
|
|
607
|
+
['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', LAYOUT.AUX_SHIFT]],
|
|
608
|
+
['i64.const', LAYOUT.AUX_MASK]]]],
|
|
609
|
+
['local.set', `$${src}`, ['i32.wrap_i64', ['i64.and',
|
|
610
|
+
['i64.load', ['i32.add', ['global.get', '$__schema_tbl'], ['i32.shl', ['local.get', `$${sid}`], ['i32.const', 3]]]],
|
|
611
|
+
['i64.const', LAYOUT.OFFSET_MASK]]]],
|
|
612
|
+
['local.set', `$${n}`, ['i32.load', ['i32.sub', ['local.get', `$${src}`], ['i32.const', 8]]]],
|
|
613
|
+
['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
614
|
+
['local.set', `$${out}`, ['call', '$__alloc_hdr',
|
|
615
|
+
['local.get', `$${n}`], ['local.get', `$${n}`], ['i32.const', 8]]],
|
|
616
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
617
|
+
['block', `$ovbrk${id}`, ['loop', `$ovloop${id}`,
|
|
618
|
+
['br_if', `$ovbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
619
|
+
['f64.store',
|
|
620
|
+
['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]],
|
|
621
|
+
['f64.load',
|
|
622
|
+
['i32.add', ['local.get', `$${base}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
623
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
624
|
+
['br', `$ovloop${id}`]]],
|
|
625
|
+
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${out}`])]
|
|
626
|
+
}
|
package/module/string.js
CHANGED
|
@@ -883,11 +883,12 @@ export default (ctx) => {
|
|
|
883
883
|
|
|
884
884
|
ctx.core.emit['.string:slice'] = (str, start, end) => {
|
|
885
885
|
inc('__str_slice')
|
|
886
|
-
|
|
886
|
+
const startIR = start == null ? ['i32.const', 0] : asI32(emit(start))
|
|
887
|
+
if (end != null) return typed(['call', '$__str_slice', asI64(emit(str)), startIR, asI32(emit(end))], 'f64')
|
|
887
888
|
const t = temp('t')
|
|
888
889
|
return typed(['block', ['result', 'f64'],
|
|
889
890
|
['local.set', `$${t}`, asF64(emit(str))],
|
|
890
|
-
['call', '$__str_slice', ['i64.reinterpret_f64', ['local.get', `$${t}`]],
|
|
891
|
+
['call', '$__str_slice', ['i64.reinterpret_f64', ['local.get', `$${t}`]], startIR,
|
|
891
892
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
892
893
|
}
|
|
893
894
|
|
|
@@ -959,6 +960,11 @@ export default (ctx) => {
|
|
|
959
960
|
return typed(['call', '$__str_case', asI64(emit(str)), ['i32.const', 65], ['i32.const', 90], ['i32.const', 32]], 'f64')
|
|
960
961
|
}
|
|
961
962
|
|
|
963
|
+
// Locale-specific casing needs ICU/CLDR data. jz intentionally has no
|
|
964
|
+
// runtime, so this follows the existing ASCII-only lowercase helper and
|
|
965
|
+
// ignores optional locale arguments.
|
|
966
|
+
ctx.core.emit['.toLocaleLowerCase'] = ctx.core.emit['.toLowerCase']
|
|
967
|
+
|
|
962
968
|
// Byte-wise variant of String.prototype.localeCompare. Returns -1/0/1 from
|
|
963
969
|
// an unsigned byte-by-byte compare with shorter-string-sorts-first tiebreak.
|
|
964
970
|
// NOT locale-aware: real localeCompare is ICU-driven (CLDR collation, case
|
package/module/typedarray.js
CHANGED
|
@@ -587,8 +587,14 @@ export default (ctx) => {
|
|
|
587
587
|
}
|
|
588
588
|
return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
|
|
589
589
|
(local $off i32) (local $et i32) (local $len i32) (local $aux i32)
|
|
590
|
-
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
591
590
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
591
|
+
;; ARRAY fast path: __ptr_offset already followed any forwarding — read header len + f64.load, no $__len call.
|
|
592
|
+
(if (i32.and (i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.ARRAY})) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
593
|
+
(then (return (if (result f64)
|
|
594
|
+
(i32.and (i32.ge_s (local.get $i) (i32.const 0)) (i32.lt_u (local.get $i) (i32.load (i32.sub (local.get $off) (i32.const 8)))))
|
|
595
|
+
(then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
596
|
+
(else (f64.const nan:${UNDEF_NAN}))))))
|
|
597
|
+
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
592
598
|
(if
|
|
593
599
|
(i32.and
|
|
594
600
|
(i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.TYPED}))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Modern functional JS compiling to WASM",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"author": "Dmitry Iv",
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"subscript": "^10.4.
|
|
42
|
-
"watr": "^4.5.
|
|
41
|
+
"subscript": "^10.4.7",
|
|
42
|
+
"watr": "^4.5.3"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
45
45
|
"javascript",
|
package/src/analyze.js
CHANGED
|
@@ -33,11 +33,27 @@ export const STMT_OPS = new Set([';', 'let', 'const', 'return', 'if', 'for', 'fo
|
|
|
33
33
|
'=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??=',
|
|
34
34
|
'throw', 'try', 'catch', 'finally', '++', '--', '()'])
|
|
35
35
|
|
|
36
|
+
/** Assignment operators — shared across analyze, plan, emit. */
|
|
37
|
+
export const ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??='])
|
|
38
|
+
|
|
36
39
|
/** Distinguish a function block body `{ ... }` from an expression-bodied object literal `({a:1})`.
|
|
37
40
|
* Both share the `'{}'` op tag; blocks have a non-`':'` first child (object literals start with key:val pairs). */
|
|
38
41
|
export const isBlockBody = (body) =>
|
|
39
42
|
Array.isArray(body) && body[0] === '{}' && body[1]?.[0] !== ':'
|
|
40
43
|
|
|
44
|
+
/** Extract integer value from AST literal node. Returns null if not a 32-bit integer. */
|
|
45
|
+
export function intLiteralValue(expr) {
|
|
46
|
+
let v = null
|
|
47
|
+
if (typeof expr === 'number') v = expr
|
|
48
|
+
else if (Array.isArray(expr) && expr[0] == null && typeof expr[1] === 'number') v = expr[1]
|
|
49
|
+
else if (Array.isArray(expr) && expr[0] === 'u-' && typeof expr[1] === 'number') v = -expr[1]
|
|
50
|
+
else if (typeof expr === 'string') v = repOf(expr)?.intConst ?? ctx.scope.constInts?.get(expr) ?? null
|
|
51
|
+
return v != null && Number.isInteger(v) && v >= -2147483648 && v <= 2147483647 ? v : null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Non-negative integer literal — used for string/typed-array index bounds. */
|
|
55
|
+
export const nonNegIntLiteral = (node) => { const n = intLiteralValue(node); return n != null && n >= 0 ? n : null }
|
|
56
|
+
|
|
41
57
|
/** Collect all `return X` expressions (X != null) from a function body, skipping nested arrow funcs.
|
|
42
58
|
* Pushes into `out`. Non-returning paths are silently skipped — pair with `alwaysReturns` if total
|
|
43
59
|
* coverage matters, or with `hasBareReturn` to detect `return;` (undef result). */
|
|
@@ -359,7 +375,7 @@ export function valTypeOf(expr) {
|
|
|
359
375
|
if (method === 'add' || method === 'delete') return VAL.SET
|
|
360
376
|
if (method === 'set') return VAL.MAP
|
|
361
377
|
// String-returning methods
|
|
362
|
-
if (['toUpperCase', 'toLowerCase', 'trim', 'trimStart', 'trimEnd',
|
|
378
|
+
if (['toUpperCase', 'toLowerCase', 'toLocaleLowerCase', 'trim', 'trimStart', 'trimEnd',
|
|
363
379
|
'repeat', 'padStart', 'padEnd', 'replace', 'replaceAll', 'charAt', 'substring'].includes(method)) return VAL.STRING
|
|
364
380
|
if (method === 'split') return VAL.ARRAY
|
|
365
381
|
// slice/concat preserve caller type (string.slice → string, array.slice → array)
|
|
@@ -1263,7 +1279,7 @@ export function invalidateLocalsCache(body) {
|
|
|
1263
1279
|
// concat) that strings share with arrays/typed-arrays.
|
|
1264
1280
|
const STRING_ONLY_METHODS = new Set([
|
|
1265
1281
|
'charCodeAt', 'charAt', 'codePointAt', 'startsWith', 'endsWith',
|
|
1266
|
-
'toUpperCase', 'toLowerCase', 'normalize', 'localeCompare',
|
|
1282
|
+
'toUpperCase', 'toLowerCase', 'toLocaleLowerCase', 'normalize', 'localeCompare',
|
|
1267
1283
|
'padStart', 'padEnd', 'repeat', 'trimStart', 'trimEnd', 'trim',
|
|
1268
1284
|
'matchAll', 'match', 'replace', 'replaceAll', 'split',
|
|
1269
1285
|
])
|
|
@@ -1892,8 +1908,6 @@ export function analyzePtrUnboxable(body, locals, boxed) {
|
|
|
1892
1908
|
for (const a of args) collect(a)
|
|
1893
1909
|
}
|
|
1894
1910
|
|
|
1895
|
-
const ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=',
|
|
1896
|
-
'<<=', '>>=', '>>>=', '||=', '&&=', '??='])
|
|
1897
1911
|
const NULL_CMP_OPS = new Set(['==', '!=', '===', '!=='])
|
|
1898
1912
|
|
|
1899
1913
|
function check(node) {
|
|
@@ -2035,8 +2049,6 @@ export function findFreeVars(node, bound, free, scope) {
|
|
|
2035
2049
|
for (const a of args) findFreeVars(a, bound, free, scope)
|
|
2036
2050
|
}
|
|
2037
2051
|
|
|
2038
|
-
const ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??='])
|
|
2039
|
-
|
|
2040
2052
|
/** Check if any of the given variable names are assigned anywhere in the AST. */
|
|
2041
2053
|
export function findMutations(node, names, mutated) {
|
|
2042
2054
|
if (node == null || typeof node !== 'object' || !Array.isArray(node)) return
|