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/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) err('Object.values requires object with known 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
- if (end != null) return typed(['call', '$__str_slice', asI64(emit(str)), asI32(emit(start)), asI32(emit(end))], 'f64')
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}`]], asI32(emit(start)),
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
@@ -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.2.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.2",
42
- "watr": "^4.5.1"
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