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/module/core.js CHANGED
@@ -173,16 +173,25 @@ export default (ctx) => {
173
173
  } else {
174
174
  // Own memory: heap offset in a global, auto-grow when needed
175
175
  ctx.scope.globals.set('__heap', '(global $__heap (mut i32) (i32.const 1024))')
176
+ // Bump allocator with geometric growth. Growing one page at a time turns a
177
+ // long-running embedding (e.g. watr called thousands of times) into O(n²) —
178
+ // each memory.grow may relocate and copy the whole heap. So when we must
179
+ // grow, request at least the current size (≥2× total) in one shot; only on
180
+ // hitting the declared maximum do we fall back to the bare minimum.
176
181
  ctx.core.stdlib['__alloc'] = `(func $__alloc (param $bytes i32) (result i32)
177
- (local $ptr i32) (local $next i32)
182
+ (local $ptr i32) (local $next i32) (local $cur i32) (local $need i32)
178
183
  (local.set $ptr (global.get $__heap))
179
184
  ;; Align next allocation to 8 bytes
180
185
  (local.set $next (i32.and (i32.add (i32.add (local.get $ptr) (local.get $bytes)) (i32.const 7)) (i32.const -8)))
181
- ;; Grow memory if needed (each page = 65536 bytes)
182
- (if (i32.gt_u (local.get $next) (i32.mul (memory.size) (i32.const 65536)))
183
- (then (if (i32.eq (memory.grow
184
- (i32.shr_u (i32.add (i32.sub (local.get $next) (i32.mul (memory.size) (i32.const 65536))) (i32.const 65535)) (i32.const 16)))
185
- (i32.const -1)) (then (unreachable)))))
186
+ (local.set $cur (i32.shl (memory.size) (i32.const 16)))
187
+ (if (i32.gt_u (local.get $next) (local.get $cur))
188
+ (then
189
+ (local.set $need (i32.shr_u (i32.add (i32.sub (local.get $next) (local.get $cur)) (i32.const 65535)) (i32.const 16)))
190
+ (if (i32.lt_u (local.get $need) (memory.size)) (then (local.set $need (memory.size))))
191
+ (if (i32.eq (memory.grow (local.get $need)) (i32.const -1))
192
+ (then (if (i32.eq (memory.grow
193
+ (i32.shr_u (i32.add (i32.sub (local.get $next) (local.get $cur)) (i32.const 65535)) (i32.const 16)))
194
+ (i32.const -1)) (then (unreachable)))))))
186
195
  (global.set $__heap (local.get $next))
187
196
  (local.get $ptr))`
188
197
  ctx.core.stdlib['__clear'] = `(func $__clear
@@ -404,9 +413,15 @@ export default (ctx) => {
404
413
  return ['call', '$__ptr_type', receiver]
405
414
  }
406
415
 
407
- function emitDynGetExprTyped(base, key, vt) {
408
- inc('__dyn_get_expr_t')
416
+ function emitDynGetExprTyped(base, key, vt, prop) {
409
417
  const receiver = asI64(base?.type ? base : typed(base, 'f64'))
418
+ // Constant string key: fold the FNV hash at compile time and call the
419
+ // prehashed body — no __str_hash on every access.
420
+ if (typeof prop === 'string') {
421
+ inc('__dyn_get_expr_t_h')
422
+ return typed(['f64.reinterpret_i64', ['call', '$__dyn_get_expr_t_h', receiver, key, emitTypeTag(receiver, vt), ['i32.const', strHashLiteral(prop)]]], 'f64')
423
+ }
424
+ inc('__dyn_get_expr_t')
410
425
  return typed(['f64.reinterpret_i64', ['call', '$__dyn_get_expr_t', receiver, key, emitTypeTag(receiver, vt)]], 'f64')
411
426
  }
412
427
 
@@ -486,7 +501,7 @@ export default (ctx) => {
486
501
  if (typeof obj === 'string') {
487
502
  const vt = lookupValType(obj)
488
503
  if (usesDynProps(vt)) {
489
- return emitDynGetExprTyped(va, key, vt)
504
+ return emitDynGetExprTyped(va, key, vt, prop)
490
505
  }
491
506
  if (vt === VAL.HASH) {
492
507
  return emitHashGetLocalConst(va, key, prop)
@@ -495,12 +510,12 @@ export default (ctx) => {
495
510
  // at off-16 (set by __dyn_set). __hash_get assumes HASH bucket layout
496
511
  // and would mis-read OBJECT memory.
497
512
  if (vt === VAL.OBJECT) {
498
- return emitDynGetExprTyped(va, key, vt)
513
+ return emitDynGetExprTyped(va, key, vt, prop)
499
514
  }
500
515
  if (vt == null) {
501
516
  // In WASI mode, values are always JSON-derived (never PTR.EXTERNAL host objects).
502
517
  // Skip the external branch and dispatch through the typed HASH/OBJECT path.
503
- if (ctx.transform.host === 'wasi') return emitDynGetExprTyped(va, key, vt)
518
+ if (ctx.transform.host === 'wasi') return emitDynGetExprTyped(va, key, vt, prop)
504
519
  ctx.features.external = true
505
520
  return emitDynGetAnyTyped(va, key, vt)
506
521
  }
@@ -598,7 +613,8 @@ export default (ctx) => {
598
613
 
599
614
  // Module-registered property emitter (.size, etc.)
600
615
  const propKey = `.${prop}`
601
- if (ctx.core.emit[propKey]) return ctx.core.emit[propKey](obj)
616
+ const propEmitter = ctx.core.emit[propKey]
617
+ if (propEmitter && propEmitter.length <= 1) return propEmitter(obj)
602
618
 
603
619
  return emitPropAccess(emit(obj), obj, prop)
604
620
  }
@@ -618,7 +634,7 @@ export default (ctx) => {
618
634
  if (typeof obj === 'string') {
619
635
  const objType = lookupValType(obj)
620
636
  if (usesDynProps(objType)) {
621
- access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
637
+ access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType, prop)
622
638
  } else if (objType === VAL.HASH) {
623
639
  access = emitHashGetLocalConst(['local.get', `$${t}`], asI64(emit(['str', prop])), prop)
624
640
  } else if (objType == null) {
@@ -626,7 +642,7 @@ export default (ctx) => {
626
642
  // In JS host mode use __dyn_get_any_t but don't force features.external here
627
643
  // since ?.prop short-circuits on nullish (EXTERNAL arm is dead unless already on).
628
644
  access = ctx.transform.host === 'wasi'
629
- ? emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
645
+ ? emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType, prop)
630
646
  : emitDynGetAnyTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
631
647
  } else {
632
648
  inc('__hash_get', '__str_hash', '__str_eq')
@@ -636,12 +652,20 @@ export default (ctx) => {
636
652
  if (valTypeOf(obj) === VAL.HASH) {
637
653
  access = emitHashGetLocalConst(['local.get', `$${t}`], asI64(emit(['str', prop])), prop)
638
654
  } else {
639
- access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), valTypeOf(obj))
655
+ access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), valTypeOf(obj), prop)
640
656
  }
641
657
  }
642
658
  }
643
659
  }
644
- return emitNullishGuarded(['local.tee', `$${t}`, va], access)
660
+ // Use local.set + local.get (not local.tee inside guard) because isNullish
661
+ // inlines the null/undefined check as (i32.or (i64.eq X NULL) (i64.eq X UNDEF)),
662
+ // which duplicates X — a local.tee(block(call $sideEffect)) would run twice.
663
+ return typed(['block', ['result', 'f64'],
664
+ ['local.set', `$${t}`, va],
665
+ ['if', ['result', 'f64'],
666
+ notNullish(typed(['local.get', `$${t}`], 'f64')),
667
+ ['then', access],
668
+ ['else', ['f64.const', `nan:${UNDEF_NAN}`]]]], 'f64')
645
669
  }
646
670
 
647
671
  // Optional index: arr?.[i] → null if arr is null, else arr[i]
@@ -656,7 +680,15 @@ export default (ctx) => {
656
680
  if (!ctx.types.typedElem) ctx.types.typedElem = new Map()
657
681
  ctx.types.typedElem.set(t, ctx.types.typedElem.get(arr))
658
682
  }
659
- return emitNullishGuarded(['local.tee', `$${t}`, va], asF64(ctx.core.emit['[]'](t, idx)))
683
+ // Use local.set + local.get (not local.tee inside guard) because isNullish
684
+ // inlines the null/undefined check as (i32.or (i64.eq X NULL) (i64.eq X UNDEF)),
685
+ // which duplicates X — a local.tee(block(call $sideEffect)) would run twice.
686
+ return typed(['block', ['result', 'f64'],
687
+ ['local.set', `$${t}`, va],
688
+ ['if', ['result', 'f64'],
689
+ notNullish(typed(['local.get', `$${t}`], 'f64')),
690
+ ['then', asF64(ctx.core.emit['[]'](t, idx))],
691
+ ['else', ['f64.const', `nan:${UNDEF_NAN}`]]]], 'f64')
660
692
  }
661
693
 
662
694
  // Optional call: fn?.(...args) → null if fn is null, else call fn
@@ -674,7 +706,14 @@ export default (ctx) => {
674
706
  const vt = typeof recv === 'string' ? repOf(recv)?.val : valTypeOf(recv)
675
707
  if (vt) updateRep(t, { val: vt })
676
708
  const callResult = methodEmit(t, ...args)
677
- return emitNullishGuarded(['local.tee', `$${t}`, va], asF64(callResult))
709
+ // Use local.set + local.get (not local.tee inside guard) because isNullish
710
+ // inlines the null/undefined check which duplicates the expression.
711
+ return typed(['block', ['result', 'f64'],
712
+ ['local.set', `$${t}`, va],
713
+ ['if', ['result', 'f64'],
714
+ notNullish(typed(['local.get', `$${t}`], 'f64')),
715
+ ['then', asF64(callResult)],
716
+ ['else', ['f64.const', `nan:${UNDEF_NAN}`]]]], 'f64')
678
717
  }
679
718
  }
680
719
  const t = temp()
@@ -682,7 +721,14 @@ export default (ctx) => {
682
721
  // If nullish → return NULL_NAN, else call via fn.call
683
722
  if (!ctx.closure.call) err('Optional call requires fn module')
684
723
  const callResult = ctx.closure.call(typed(['local.get', `$${t}`], 'f64'), args)
685
- return emitNullishGuarded(['local.tee', `$${t}`, va], asF64(callResult))
724
+ // Use local.set + local.get (not local.tee inside guard) because isNullish
725
+ // inlines the null/undefined check which duplicates the expression.
726
+ return typed(['block', ['result', 'f64'],
727
+ ['local.set', `$${t}`, va],
728
+ ['if', ['result', 'f64'],
729
+ notNullish(typed(['local.get', `$${t}`], 'f64')),
730
+ ['then', asF64(callResult)],
731
+ ['else', ['f64.const', `nan:${UNDEF_NAN}`]]]], 'f64')
686
732
  }
687
733
 
688
734
  // typeof: returns JS-style string. Reachable results are number/undefined/string/function/symbol/object
package/module/date.js CHANGED
@@ -76,6 +76,11 @@ export default (ctx) => {
76
76
  ], 'f64')
77
77
  }
78
78
 
79
+ ctx.core.emit['Date.parse'] = (value) => {
80
+ inc('__date_from_value')
81
+ return typed(['call', '$__date_from_value', ['i64.reinterpret_f64', asF64(emit(value))]], 'f64')
82
+ }
83
+
79
84
  // ── Core algorithms ───────────────────────────────────────────────────────
80
85
 
81
86
  ctx.core.stdlib['__date_days_from_year'] = `(func $__date_days_from_year (param $y f64) (result f64)
@@ -11,7 +11,8 @@
11
11
  */
12
12
 
13
13
  import { typed, asF64, asI32, mkPtrIR, temp, tempI32, MAX_CLOSURE_ARITY, UNDEF_NAN } from '../src/ir.js'
14
- import { emit, isReassigned } from '../src/emit.js'
14
+ import { emit } from '../src/emit.js'
15
+ import { isReassigned } from '../src/ast.js'
15
16
  import { T, lookupValType, repOf, findFreeVars } from '../src/analyze.js'
16
17
  import { PTR, LAYOUT, inc, err } from '../src/ctx.js'
17
18
 
package/module/json.js CHANGED
@@ -50,7 +50,8 @@ export default (ctx) => {
50
50
  __jput_str: ['__char_at', '__str_byteLen'],
51
51
  __jp: ['__jp_val', '__jp_str', '__jp_num', '__jp_arr', '__jp_obj', '__sso_char', '__ptr_aux', '__ptr_type', '__ptr_offset', '__str_byteLen'],
52
52
  __jp_val: ['__jp_str', '__jp_num', '__jp_arr', '__jp_obj'],
53
- __jp_str: ['__sso_char', '__char_at', '__str_byteLen'],
53
+ __jp_str: ['__sso_char', '__char_at', '__str_byteLen', '__hex4', '__utf8_enc'],
54
+ __hex4: ['__hex1'],
54
55
  __jp_num: ['__pow10'],
55
56
  __jp_arr: ['__jp_val'],
56
57
  __jp_obj: ['__jp_val', '__jp_str', '__jp_schema_get', '__alloc_hdr', '__mkptr'],
@@ -319,8 +320,45 @@ export default (ctx) => {
319
320
  // SSO byte packing for ≤4-char ASCII keys, and FNV-1a hash. The hash is
320
321
  // stashed in $__jp_keyh so __jp_obj can use the prehashed insert and skip
321
322
  // a redundant __str_hash call.
323
+ // Hex nibble: '0'-'9' / 'a'-'f' / 'A'-'F' → 0..15; anything else → 0 (lenient).
324
+ ctx.core.stdlib['__hex1'] = `(func $__hex1 (param $c i32) (result i32)
325
+ (if (i32.le_u (i32.sub (local.get $c) (i32.const 48)) (i32.const 9))
326
+ (then (return (i32.sub (local.get $c) (i32.const 48)))))
327
+ (if (i32.le_u (i32.sub (i32.or (local.get $c) (i32.const 0x20)) (i32.const 97)) (i32.const 5))
328
+ (then (return (i32.sub (i32.or (local.get $c) (i32.const 0x20)) (i32.const 87)))))
329
+ (i32.const 0))`
330
+
331
+ // Read 4 hex bytes at absolute address $p → 16-bit value.
332
+ ctx.core.stdlib['__hex4'] = `(func $__hex4 (param $p i32) (result i32)
333
+ (i32.or (i32.or (i32.or
334
+ (i32.shl (call $__hex1 (i32.load8_u (local.get $p))) (i32.const 12))
335
+ (i32.shl (call $__hex1 (i32.load8_u (i32.add (local.get $p) (i32.const 1)))) (i32.const 8)))
336
+ (i32.shl (call $__hex1 (i32.load8_u (i32.add (local.get $p) (i32.const 2)))) (i32.const 4)))
337
+ (call $__hex1 (i32.load8_u (i32.add (local.get $p) (i32.const 3))))))`
338
+
339
+ // Encode code point $cp as UTF-8 at $off; returns bytes written (1-4).
340
+ ctx.core.stdlib['__utf8_enc'] = `(func $__utf8_enc (param $off i32) (param $cp i32) (result i32)
341
+ (if (i32.lt_u (local.get $cp) (i32.const 0x80))
342
+ (then (i32.store8 (local.get $off) (local.get $cp)) (return (i32.const 1))))
343
+ (if (i32.lt_u (local.get $cp) (i32.const 0x800))
344
+ (then
345
+ (i32.store8 (local.get $off) (i32.or (i32.const 0xC0) (i32.shr_u (local.get $cp) (i32.const 6))))
346
+ (i32.store8 (i32.add (local.get $off) (i32.const 1)) (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))))
347
+ (return (i32.const 2))))
348
+ (if (i32.lt_u (local.get $cp) (i32.const 0x10000))
349
+ (then
350
+ (i32.store8 (local.get $off) (i32.or (i32.const 0xE0) (i32.shr_u (local.get $cp) (i32.const 12))))
351
+ (i32.store8 (i32.add (local.get $off) (i32.const 1)) (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))))
352
+ (i32.store8 (i32.add (local.get $off) (i32.const 2)) (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))))
353
+ (return (i32.const 3))))
354
+ (i32.store8 (local.get $off) (i32.or (i32.const 0xF0) (i32.shr_u (local.get $cp) (i32.const 18))))
355
+ (i32.store8 (i32.add (local.get $off) (i32.const 1)) (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 12)) (i32.const 0x3F))))
356
+ (i32.store8 (i32.add (local.get $off) (i32.const 2)) (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))))
357
+ (i32.store8 (i32.add (local.get $off) (i32.const 3)) (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))))
358
+ (i32.const 4))`
359
+
322
360
  ctx.core.stdlib['__jp_str'] = `(func $__jp_str (result f64)
323
- (local $start i32) (local $ch i32) (local $len i32) (local $off i32) (local $i i32) (local $simple i32) (local $sso i32) (local $h i32)
361
+ (local $start i32) (local $ch i32) (local $len i32) (local $off i32) (local $i i32) (local $simple i32) (local $sso i32) (local $h i32) (local $cp i32)
324
362
  (local.set $start (global.get $__jppos))
325
363
  (local.set $simple (i32.const 1))
326
364
  (local.set $h (i32.const 0x811c9dc5))
@@ -382,12 +420,32 @@ export default (ctx) => {
382
420
  ${ADV(1)}
383
421
  (local.set $ch ${PEEK})
384
422
  ${ADV(1)}
385
- ;; Decode escape: n→10 t→9 r→13 b→8 f→12, else literal
386
- (if (i32.eq (local.get $ch) (i32.const 110)) (then (local.set $ch (i32.const 10))))
387
- (if (i32.eq (local.get $ch) (i32.const 116)) (then (local.set $ch (i32.const 9))))
388
- (if (i32.eq (local.get $ch) (i32.const 114)) (then (local.set $ch (i32.const 13))))
389
- (if (i32.eq (local.get $ch) (i32.const 98)) (then (local.set $ch (i32.const 8))))
390
- (if (i32.eq (local.get $ch) (i32.const 102)) (then (local.set $ch (i32.const 12)))))
423
+ (if (i32.eq (local.get $ch) (i32.const 117)) ;; \\uXXXX
424
+ (then
425
+ (local.set $cp (call $__hex4 (i32.add (global.get $__jpstr) (global.get $__jppos))))
426
+ ${ADV(4)}
427
+ ;; High surrogate immediately followed by \\uXXXX low surrogate → combine.
428
+ (if (i32.and
429
+ (i32.eq (i32.and (local.get $cp) (i32.const 0xFC00)) (i32.const 0xD800))
430
+ (i32.and (i32.eq ${PEEK} (i32.const 92))
431
+ (i32.eq (i32.load8_u (i32.add (global.get $__jpstr) (i32.add (global.get $__jppos) (i32.const 1)))) (i32.const 117))))
432
+ (then
433
+ ${ADV(2)}
434
+ (local.set $i (call $__hex4 (i32.add (global.get $__jpstr) (global.get $__jppos))))
435
+ ${ADV(4)}
436
+ (local.set $cp (i32.add (i32.const 0x10000)
437
+ (i32.or (i32.shl (i32.and (local.get $cp) (i32.const 0x3FF)) (i32.const 10))
438
+ (i32.and (local.get $i) (i32.const 0x3FF)))))))
439
+ (local.set $len (i32.add (local.get $len)
440
+ (call $__utf8_enc (i32.add (local.get $off) (local.get $len)) (local.get $cp))))
441
+ (br $l2))
442
+ (else
443
+ ;; Decode simple escape: n→10 t→9 r→13 b→8 f→12, else literal char.
444
+ (if (i32.eq (local.get $ch) (i32.const 110)) (then (local.set $ch (i32.const 10))))
445
+ (if (i32.eq (local.get $ch) (i32.const 116)) (then (local.set $ch (i32.const 9))))
446
+ (if (i32.eq (local.get $ch) (i32.const 114)) (then (local.set $ch (i32.const 13))))
447
+ (if (i32.eq (local.get $ch) (i32.const 98)) (then (local.set $ch (i32.const 8))))
448
+ (if (i32.eq (local.get $ch) (i32.const 102)) (then (local.set $ch (i32.const 12)))))))
391
449
  (else ${ADV(1)}))
392
450
  (i32.store8 (i32.add (local.get $off) (local.get $len)) (local.get $ch))
393
451
  (local.set $len (i32.add (local.get $len) (i32.const 1)))
package/module/number.js CHANGED
@@ -10,7 +10,8 @@
10
10
  */
11
11
 
12
12
  import { typed, asF64, asI32, asI64, toNumF64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
13
- import { emit, isReassigned } from '../src/emit.js'
13
+ import { emit } from '../src/emit.js'
14
+ import { isReassigned } from '../src/ast.js'
14
15
  import { valTypeOf, VAL } from '../src/analyze.js'
15
16
  import { inc, PTR } from '../src/ctx.js'
16
17
 
@@ -22,6 +23,7 @@ export default (ctx) => {
22
23
  __to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
23
24
  __to_bigint: ['__char_at', '__str_byteLen'],
24
25
  __parseInt: ['__char_at', '__str_byteLen'],
26
+ __parseFloat: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
25
27
  })
26
28
 
27
29
 
@@ -346,7 +348,6 @@ export default (ctx) => {
346
348
  ['f64.eq', ['local.get', `$${t}`], ['f64.trunc', ['local.get', `$${t}`]]]], 'i32')
347
349
  }
348
350
 
349
- // parseInt(str, radix) — parse string to integer
350
351
  ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str i64) (param $radix i32) (result f64)
351
352
  (local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
352
353
  (local $result f64) (local $digit i32) (local $seen i32) (local $f f64)
@@ -592,14 +593,135 @@ export default (ctx) => {
592
593
  (then (i64.sub (i64.const 0) (local.get $result)))
593
594
  (else (local.get $result)))))`
594
595
 
596
+ ctx.core.stdlib['__parseFloat'] = `(func $__parseFloat (param $v i64) (result f64)
597
+ (local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
598
+ (local $seen i32) (local $exp i32) (local $expNeg i32) (local $expDigits i32)
599
+ (local $dot i32) (local $sigDigits i32) (local $decExp i32) (local $dropped i32) (local $round i32)
600
+ (local $result f64) (local $f f64)
601
+ (local.set $f (f64.reinterpret_i64 (local.get $v)))
602
+ (if (f64.eq (local.get $f) (local.get $f)) (then (return (local.get $f))))
603
+ (local.set $t (call $__ptr_type (local.get $v)))
604
+ ;; parseFloat first applies ToString, then parses the longest decimal prefix.
605
+ ;; Unlike Number(), empty strings and non-decimal prefixes produce NaN/0
606
+ ;; according to the consumed prefix rather than whole-string numeric coercion.
607
+ (if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
608
+ (then
609
+ (local.set $v (call $__to_str (local.get $v)))
610
+ (local.set $t (call $__ptr_type (local.get $v)))
611
+ (if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
612
+ (then (return (f64.const nan))))))
613
+ (local.set $len (call $__str_byteLen (local.get $v)))
614
+ ;; Skip leading whitespace.
615
+ (block $ws (loop $wsl
616
+ (br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
617
+ (br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
618
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
619
+ (br $wsl)))
620
+ ;; Sign.
621
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
622
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
623
+ (then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
624
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
625
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
626
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
627
+ ;; Decimal significand. Keep 17 significant decimal digits, track the
628
+ ;; base-10 exponent for skipped digits, and round once before pow10 scaling.
629
+ (block $numDone (loop $numLoop
630
+ (br_if $numDone (i32.ge_s (local.get $i) (local.get $len)))
631
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
632
+ (if (i32.and (i32.eq (local.get $c) (i32.const 46)) (i32.eqz (local.get $dot)))
633
+ (then
634
+ (local.set $dot (i32.const 1))
635
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
636
+ (br $numLoop)))
637
+ (br_if $numDone
638
+ (i32.or
639
+ (i32.lt_s (local.get $c) (i32.const 48))
640
+ (i32.gt_s (local.get $c) (i32.const 57))))
641
+ (local.set $seen (i32.const 1))
642
+ (local.set $c (i32.sub (local.get $c) (i32.const 48)))
643
+ (if (i32.and (i32.eqz (local.get $sigDigits)) (i32.eqz (local.get $c)))
644
+ (then
645
+ (if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1)))))
646
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
647
+ (br $numLoop)))
648
+ (if (i32.lt_s (local.get $sigDigits)
649
+ (if (result i32) (local.get $dot) (then (i32.const 16)) (else (i32.const 17))))
650
+ (then
651
+ (local.set $result
652
+ (f64.add
653
+ (f64.mul (local.get $result) (f64.const 10))
654
+ (f64.convert_i32_s (local.get $c))))
655
+ (local.set $sigDigits (i32.add (local.get $sigDigits) (i32.const 1)))
656
+ (if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1))))))
657
+ (else
658
+ (if (i32.eqz (local.get $dropped))
659
+ (then (if (i32.ge_s (local.get $c) (i32.const 5)) (then (local.set $round (i32.const 1))))))
660
+ (local.set $dropped (i32.const 1))
661
+ (if (i32.eqz (local.get $dot)) (then (local.set $decExp (i32.add (local.get $decExp) (i32.const 1)))))))
662
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
663
+ (br $numLoop)))
664
+ (if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
665
+ (if (local.get $round) (then (local.set $result (f64.add (local.get $result) (f64.const 1)))))
666
+ ;; Scientific notation.
667
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
668
+ (i32.or
669
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 101))
670
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 69))))
671
+ (then
672
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
673
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
674
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
675
+ (then (local.set $expNeg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
676
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
677
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
678
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
679
+ (block $expDone (loop $expLoop
680
+ (br_if $expDone (i32.ge_s (local.get $i) (local.get $len)))
681
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
682
+ (br_if $expDone
683
+ (i32.or
684
+ (i32.lt_s (local.get $c) (i32.const 48))
685
+ (i32.gt_s (local.get $c) (i32.const 57))))
686
+ (local.set $exp
687
+ (i32.add
688
+ (i32.mul (local.get $exp) (i32.const 10))
689
+ (i32.sub (local.get $c) (i32.const 48))))
690
+ (local.set $expDigits (i32.add (local.get $expDigits) (i32.const 1)))
691
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
692
+ (br $expLoop)))
693
+ (if (local.get $expDigits)
694
+ (then
695
+ (if (local.get $expNeg)
696
+ (then (local.set $decExp (i32.sub (local.get $decExp) (local.get $exp))))
697
+ (else (local.set $decExp (i32.add (local.get $decExp) (local.get $exp)))))))))
698
+ (if (i32.gt_s (local.get $decExp) (i32.const 0))
699
+ (then (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))
700
+ (if (i32.lt_s (local.get $decExp) (i32.const 0))
701
+ (then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))
702
+ (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
703
+
595
704
  ctx.core.emit['Number.parseInt'] = (x, radix) => {
596
- inc('__parseInt')
705
+ needParseInt()
597
706
  return typed(['call', '$__parseInt', asI64(emit(x)), radix ? asI32(emit(radix)) : ['i32.const', 0]], 'f64')
598
707
  }
599
708
  ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
709
+ const addImportOnce = (ctx, mod, name, fn) => {
710
+ if (ctx.module.imports.some(i => i[1] === `"${mod}"` && i[2] === `"${name}"`)) return
711
+ ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, fn])
712
+ }
713
+ const needParseFloat = () => addImportOnce(ctx, 'env', 'parseFloat',
714
+ ['func', '$__parseFloat', ['param', 'i64'], ['result', 'f64']])
715
+ const needParseInt = () => addImportOnce(ctx, 'env', 'parseInt',
716
+ ['func', '$__parseInt', ['param', 'i64'], ['param', 'i32'], ['result', 'f64']])
717
+
600
718
  ctx.core.emit['Number.parseFloat'] = (x) => {
601
- inc('__to_num')
602
- return typed(['call', '$__to_num', asI64(emit(x))], 'f64')
719
+ if (ctx.transform.host === 'wasi') {
720
+ inc('__parseFloat')
721
+ return typed(['call', '$__parseFloat', asI64(emit(x))], 'f64')
722
+ }
723
+ needParseFloat()
724
+ return typed(['call', '$__parseFloat', asI64(emit(x))], 'f64')
603
725
  }
604
726
  ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
605
727
 
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
 
@@ -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.1",
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",