jz 0.8.0 → 0.8.1

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/array.js CHANGED
@@ -1720,6 +1720,26 @@ export default (ctx) => {
1720
1720
  ['f64.convert_i32_s', ['local.get', `$${result}`]]], 'f64')
1721
1721
  }
1722
1722
 
1723
+ // Mirror of .indexOf scanning to the highest matching index — no early break, the last hit wins.
1724
+ // Registering it (alongside .string:lastIndexOf) is what lets lastIndexOf leave STRING_ONLY_METHODS:
1725
+ // an untyped receiver now forks string-vs-array at runtime instead of force-narrowing to string
1726
+ // (which returned -1 for every array). fromIndex is unsupported, matching .indexOf's array path.
1727
+ ctx.core.emit['.lastIndexOf'] = (arr, val) => {
1728
+ const recv = hoistArrayValue(arr)
1729
+ const vv = asF64(emit(val))
1730
+ const eq = arrEqIR(val)
1731
+ const result = tempI32('lx')
1732
+ const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
1733
+ ['if', eq(item, vv),
1734
+ ['then', ['local.set', `$${result}`, ['local.get', `$${i}`]]]]
1735
+ ])
1736
+ return typed(['block', ['result', 'f64'],
1737
+ recv.setup,
1738
+ ['local.set', `$${result}`, ['i32.const', -1]],
1739
+ ...loop,
1740
+ ['f64.convert_i32_s', ['local.get', `$${result}`]]], 'f64')
1741
+ }
1742
+
1723
1743
  // .at(i) → array element with negative index support
1724
1744
  ctx.core.emit['.array:at'] = (arr, idx) => {
1725
1745
  const vt = typeof arr === 'string' ? lookupValType(arr) : valTypeOf(arr)
package/module/math.js CHANGED
@@ -37,7 +37,7 @@ export default (ctx) => {
37
37
  'math.log2': ['math.log'],
38
38
  'math.log1p': ['math.log'],
39
39
  'math.pow': ['math.exp', 'math.log'],
40
- 'math.asin': ['math.atan'],
40
+ 'math.asin': [],
41
41
  'math.acos': ['math.asin'],
42
42
  'math.atan2': ['math.atan'],
43
43
  'math.sinh': ['math.exp'],
@@ -46,7 +46,8 @@ export default (ctx) => {
46
46
  'math.asinh': ['math.isFinite', 'math.log'],
47
47
  'math.acosh': ['math.log'],
48
48
  'math.atanh': ['math.log'],
49
- 'math.cbrt': ['math.isFinite', 'math.pow'],
49
+ 'math.cbrt': ['math.isFinite'],
50
+ 'math.fifthroot': ['math.isFinite'],
50
51
  'math.sumPrecise': ['__ptr_offset', '__len', '__alloc'],
51
52
  })
52
53
  // Helpers: all math ops take f64 and return f64. Args go through ToNumber
@@ -372,6 +373,24 @@ export default (ctx) => {
372
373
  // from exp/log); ±0.5 stays sqrt / the exact pow path (handled above).
373
374
  if (isLit(irB)) {
374
375
  const c = litVal(irB)
376
+ // Constant exponent with denominator 5 and < 5 (the sRGB / Rec.709 decode gammas 2.4, 2.2,
377
+ // and any k/5): x^(k/5) = x^p · fifthroot(x^r), p=⌊c⌋, r=5c−5p ∈ 1..4 — algebraic, no exp/log,
378
+ // ~3.6e-10 rel err over x ∈ (0,1]. Beats exp(c·log x): the pow microbench drops below V8's
379
+ // own Math.pow intrinsic. x<0 → NaN to match Math.pow on a non-integer exponent (the exp·log
380
+ // form's log(<0)=NaN). x=0/+∞/NaN all carry correctly through the power + fifthroot.
381
+ if (Number.isFinite(c) && c > 0 && c < 5 && !Number.isInteger(c) && Number.isInteger(c * 5)) {
382
+ inc('math.fifthroot')
383
+ const t = temp('pw'), g = get(t)
384
+ const ipow = (k) => k === 1 ? g : k === 2 ? ['f64.mul', g, g]
385
+ : k === 3 ? ['f64.mul', ['f64.mul', g, g], g] : ['f64.mul', ['f64.mul', g, g], ['f64.mul', g, g]] // k ∈ 1..4
386
+ const p = Math.floor(c), r = Math.round(c * 5) - p * 5
387
+ const root = ['call', '$math.fifthroot', ipow(r)]
388
+ const body = p === 0 ? root : ['f64.mul', ipow(p), root]
389
+ return typed(['block', ['result', 'f64'],
390
+ ['local.set', `$${t}`, irA],
391
+ ['if', ['result', 'f64'], ['f64.lt', g, ['f64.const', 0]],
392
+ ['then', ['f64.const', 'nan']], ['else', body]]], 'f64')
393
+ }
375
394
  if (Number.isFinite(c) && !Number.isInteger(c) && c !== 0.5 && c !== -0.5)
376
395
  return (inc('math.exp'), inc('math.log'),
377
396
  typed(['call', '$math.exp', ['f64.mul', irB, ['call', '$math.log', irA]]], 'f64'))
@@ -544,15 +563,6 @@ export default (ctx) => {
544
563
  const horner2 = (cs, v = '$r2') => cs.reduceRight((acc, c, i) =>
545
564
  i === cs.length - 1 ? splat(c)
546
565
  : `(f64x2.add ${splat(c)} (f64x2.mul (local.get ${v}) ${acc}))`, '')
547
- // fdlibm log's even/odd split, as f64x2 coefficient arrays (the scalar $math.log inlines them):
548
- // t1 = w·(L3 + w·(L5 + w·L7)), t2 = z·(L2 + w·(L4 + w·(L6 + w·L8))). Vectorized log_v reuses these.
549
- // STRING coefficients (not numbers): horner2 only ever interpolates them into the WAT
550
- // (`(f64.const ${c})`), and under self-host that `${number}` goes through jz's shortest-repr
551
- // __ftoa, which truncates to ~9 sig figs — so the kernel's log_v poly diverged from scalar
552
- // $math.log (which inlines the exact literals). As strings, the full-precision decimal is
553
- // embedded verbatim and watr parses it exactly, in both the host and the self-host kernel.
554
- const LOG_T1 = ['0.3999999999940941908', '0.2222219843214978396', '0.1531383769920937332']
555
- const LOG_T2 = ['0.6666666666666735130', '0.2857142874366239149', '0.1818357216161805012', '0.1479819860511658591']
556
566
  // Shared reduce → r ∈ [−π/2,π/2] in $r, quadrant parity in $q (branchless, 2 passes).
557
567
  const reduce2 = `
558
568
  (local.set $q (f64x2.nearest (f64x2.mul (local.get $x) ${splat(INV_PI)})))
@@ -602,7 +612,7 @@ export default (ctx) => {
602
612
  // 2^52 magic-add, identical to convert_i32_s for |k|≤1075). Any other lane (≤0/∞/NaN/denormal)
603
613
  // routes BOTH lanes to the scalar fallback → bit-exact by construction, edges never lose precision.
604
614
  wat('math.log_v', `(func $math.log_v (param $x v128) (result v128)
605
- (local $k v128) (local $m v128) (local $mask v128) (local $f v128) (local $s v128) (local $z v128) (local $w v128) (local $hfsq v128)
615
+ (local $k v128) (local $m v128) (local $mask v128) (local $s v128) (local $z v128)
606
616
  (if (result v128)
607
617
  (i64x2.all_true (v128.and
608
618
  (f64x2.ge (local.get $x) (f64x2.splat (f64.const 0x1p-1022)))
@@ -616,18 +626,20 @@ export default (ctx) => {
616
626
  (local.set $mask (f64x2.ge (local.get $m) (f64x2.splat (f64.const 1.4142135623730951))))
617
627
  (local.set $m (v128.bitselect (f64x2.mul (local.get $m) (f64x2.splat (f64.const 0.5))) (local.get $m) (local.get $mask)))
618
628
  (local.set $k (f64x2.add (local.get $k) (v128.and (local.get $mask) (f64x2.splat (f64.const 1.0)))))
619
- (local.set $f (f64x2.sub (local.get $m) (f64x2.splat (f64.const 1.0))))
620
- (local.set $s (f64x2.div (local.get $f) (f64x2.add (local.get $f) (f64x2.splat (f64.const 2.0)))))
629
+ ;; mirrors scalar $math.log op-for-op (same constants/order) bit-exact lanes
630
+ (local.set $s (f64x2.div (f64x2.sub (local.get $m) (f64x2.splat (f64.const 1.0))) (f64x2.add (local.get $m) (f64x2.splat (f64.const 1.0)))))
621
631
  (local.set $z (f64x2.mul (local.get $s) (local.get $s)))
622
- (local.set $w (f64x2.mul (local.get $z) (local.get $z)))
623
- (local.set $hfsq (f64x2.mul (f64x2.splat (f64.const 0.5)) (f64x2.mul (local.get $f) (local.get $f))))
624
632
  (f64x2.add
625
633
  (f64x2.mul (local.get $k) (f64x2.splat (f64.const ${Math.LN2})))
626
- (f64x2.add (f64x2.sub (local.get $f) (local.get $hfsq))
627
- (f64x2.mul (local.get $s) (f64x2.add (local.get $hfsq)
628
- (f64x2.add
629
- (f64x2.mul (local.get $w) ${horner2(LOG_T1, '$w')})
630
- (f64x2.mul (local.get $z) ${horner2(LOG_T2, '$w')})))))))
634
+ (f64x2.mul (f64x2.mul (f64x2.splat (f64.const 2.0)) (local.get $s))
635
+ (f64x2.add (f64x2.splat (f64.const 1.0))
636
+ (f64x2.mul (local.get $z)
637
+ (f64x2.add (f64x2.splat (f64.const 0.33333333283005556))
638
+ (f64x2.mul (local.get $z)
639
+ (f64x2.add (f64x2.splat (f64.const 0.20000059590510924))
640
+ (f64x2.mul (local.get $z)
641
+ (f64x2.add (f64x2.splat (f64.const 0.14275490984342690))
642
+ (f64x2.mul (local.get $z) (f64x2.splat (f64.const 0.11663796426848184)))))))))))))
631
643
  (else
632
644
  (f64x2.replace_lane 1
633
645
  (f64x2.splat (call $math.log (f64x2.extract_lane 0 (local.get $x))))
@@ -718,7 +730,6 @@ export default (ctx) => {
718
730
  // Edge cases: NaN→NaN, ≤0 distinguishes 0→-Inf, <0→NaN; +Inf passes through.
719
731
  wat('math.log', `(func $math.log (param $x f64) (result f64)
720
732
  (local $bits i64) (local $k i32) (local $m f64) (local $s f64) (local $z f64)
721
- (local $f f64) (local $w f64) (local $t1 f64) (local $t2 f64) (local $hfsq f64)
722
733
  (if (f64.ne (local.get $x) (local.get $x))
723
734
  (then (return (local.get $x))))
724
735
  (if (f64.le (local.get $x) (f64.const 0.0))
@@ -749,27 +760,22 @@ export default (ctx) => {
749
760
  (then
750
761
  (local.set $m (f64.mul (local.get $m) (f64.const 0.5)))
751
762
  (local.set $k (i32.add (local.get $k) (i32.const 1)))))
752
- ;; s = f/(2+f) with f = m−1 (= (m−1)/(m+1)); then the fdlibm even/odd-split
753
- ;; polynomial. Two parallel Horner chains (t1 over even powers, t2 over odd)
754
- ;; cut the dependency chain ~in half vs one 9-deep Horner more ILP, fewer
755
- ;; terms and reconstruct log(m) = f hfsq + (hfsq + t1 + t2). ~1 ulp.
756
- (local.set $f (f64.sub (local.get $m) (f64.const 1.0)))
757
- (local.set $s (f64.div (local.get $f) (f64.add (local.get $f) (f64.const 2.0))))
763
+ ;; s = (m−1)/(m+1) (|s| 3−2√2 0.172); log(m) = 2s·(1 + z·G(z)), z = s², G a degree-3
764
+ ;; minimax in z (Remez, equioscillation 5.8e-10). One short Horner replaces fdlibm's 7-term
765
+ ;; even/odd split ~40% fewer ops, max rel err 1.7e-11 (jz transcendentals target ~1e-9).
766
+ (local.set $s (f64.div (f64.sub (local.get $m) (f64.const 1.0)) (f64.add (local.get $m) (f64.const 1.0))))
758
767
  (local.set $z (f64.mul (local.get $s) (local.get $s)))
759
- (local.set $w (f64.mul (local.get $z) (local.get $z)))
760
- (local.set $t1 (f64.mul (local.get $w) (f64.add (f64.const 0.3999999999940941908)
761
- (f64.mul (local.get $w) (f64.add (f64.const 0.2222219843214978396)
762
- (f64.mul (local.get $w) (f64.const 0.1531383769920937332)))))))
763
- (local.set $t2 (f64.mul (local.get $z) (f64.add (f64.const 0.6666666666666735130)
764
- (f64.mul (local.get $w) (f64.add (f64.const 0.2857142874366239149)
765
- (f64.mul (local.get $w) (f64.add (f64.const 0.1818357216161805012)
766
- (f64.mul (local.get $w) (f64.const 0.1479819860511658591)))))))))
767
- (local.set $hfsq (f64.mul (f64.const 0.5) (f64.mul (local.get $f) (local.get $f))))
768
768
  (f64.add
769
769
  (f64.mul (f64.convert_i32_s (local.get $k)) (f64.const ${Math.LN2}))
770
- (f64.add (f64.sub (local.get $f) (local.get $hfsq))
771
- (f64.mul (local.get $s) (f64.add (local.get $hfsq)
772
- (f64.add (local.get $t1) (local.get $t2))))))))`)
770
+ (f64.mul (f64.mul (f64.const 2.0) (local.get $s))
771
+ (f64.add (f64.const 1.0)
772
+ (f64.mul (local.get $z)
773
+ (f64.add (f64.const 0.33333333283005556)
774
+ (f64.mul (local.get $z)
775
+ (f64.add (f64.const 0.20000059590510924)
776
+ (f64.mul (local.get $z)
777
+ (f64.add (f64.const 0.14275490984342690)
778
+ (f64.mul (local.get $z) (f64.const 0.11663796426848184)))))))))))))`)
773
779
 
774
780
  wat('math.log2', `(func $math.log2 (param $x f64) (result f64)
775
781
  (f64.div (call $math.log (local.get $x)) (f64.const ${Math.LN2})))`)
@@ -938,82 +944,71 @@ export default (ctx) => {
938
944
  // fdlibm atan: 4-region argument reduction onto |r| ≤ tan(π/16), then an
939
945
  // 11-term odd polynomial split into even/odd parts. Accurate to <1 ulp —
940
946
  // the old Taylor series was ~2e-6 off near |x|=0.5. Drives asin/acos/atan2.
947
+ // Fast atan: sign symmetry (work on |x|), two-stage reduction onto [0, tan(π/8)] — |x|>1 →
948
+ // π/2−atan(1/x), then t>tan(π/8) → π/8+atan((t−C)/(1+Ct)) — then a degree-5 minimax t·P(t²).
949
+ // Replaces the fdlibm 4-way / 11-term / extended-precision form (correctly-rounded but ~3× the
950
+ // ops). Max rel err 6e-10 over all of ℝ — well within jz's ~1e-9 transcendental budget. asin =
951
+ // atan(x/√(1−x²)) and acos = π/2−asin inherit it, so all three drop from ~1.6–2.3× to under V8.
941
952
  wat('math.atan', `(func $math.atan (param $x f64) (result f64)
942
- (local $abs_x f64) (local $id i32) (local $r f64) (local $z f64) (local $w f64)
943
- (local $s1 f64) (local $s2 f64) (local $ahi f64) (local $alo f64) (local $res f64)
944
- ;; NaN passes through unchanged.
953
+ (local $t f64) (local $u f64) (local $r f64) (local $off f64) (local $flip i32)
954
+ ;; NaN passes through; ±0 returns x (preserves sign of zero); ±Inf flows through (1/Inf=0 π/2).
945
955
  (if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
946
- (local.set $abs_x (f64.abs (local.get $x)))
947
- ;; |x| >= 2^66: atan saturates to ±π/2.
948
- (if (f64.ge (local.get $abs_x) (f64.const 7.378697629483821e19))
949
- (then (return (f64.copysign (f64.const 1.5707963267948966) (local.get $x)))))
950
- (if (f64.lt (local.get $abs_x) (f64.const 0.4375))
956
+ (if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
957
+ (local.set $t (f64.abs (local.get $x)))
958
+ (local.set $off (f64.const 0.0))
959
+ (local.set $flip (i32.const 0))
960
+ (if (f64.gt (local.get $t) (f64.const 1.0))
961
+ (then (local.set $t (f64.div (f64.const 1.0) (local.get $t))) (local.set $flip (i32.const 1))))
962
+ (if (f64.gt (local.get $t) (f64.const 0.41421356237309503))
951
963
  (then
952
- ;; |x| < 2^-27: atan(x) ≈ x (also preserves sign of zero).
953
- (if (f64.lt (local.get $abs_x) (f64.const 7.450580596923828e-9))
954
- (then (return (local.get $x))))
955
- (local.set $id (i32.const -1))
956
- (local.set $r (local.get $x)))
957
- (else
958
- (local.set $r (local.get $abs_x))
959
- (if (f64.lt (local.get $abs_x) (f64.const 1.1875))
960
- (then
961
- (if (f64.lt (local.get $abs_x) (f64.const 0.6875))
962
- (then ;; id=0: r = (2x-1)/(2+x)
963
- (local.set $id (i32.const 0))
964
- (local.set $r (f64.div (f64.sub (f64.mul (f64.const 2.0) (local.get $r)) (f64.const 1.0))
965
- (f64.add (f64.const 2.0) (local.get $r)))))
966
- (else ;; id=1: r = (x-1)/(x+1)
967
- (local.set $id (i32.const 1))
968
- (local.set $r (f64.div (f64.sub (local.get $r) (f64.const 1.0))
969
- (f64.add (local.get $r) (f64.const 1.0)))))))
970
- (else
971
- (if (f64.lt (local.get $abs_x) (f64.const 2.4375))
972
- (then ;; id=2: r = (x-1.5)/(1+1.5x)
973
- (local.set $id (i32.const 2))
974
- (local.set $r (f64.div (f64.sub (local.get $r) (f64.const 1.5))
975
- (f64.add (f64.const 1.0) (f64.mul (f64.const 1.5) (local.get $r))))))
976
- (else ;; id=3: r = -1/x
977
- (local.set $id (i32.const 3))
978
- (local.set $r (f64.div (f64.const -1.0) (local.get $r)))))))))
979
- (local.set $z (f64.mul (local.get $r) (local.get $r)))
980
- (local.set $w (f64.mul (local.get $z) (local.get $z)))
981
- (local.set $s1 (f64.mul (local.get $z)
982
- (f64.add (f64.const 0.3333333333333293)
983
- (f64.mul (local.get $w) (f64.add (f64.const 0.14285714272503466)
984
- (f64.mul (local.get $w) (f64.add (f64.const 0.09090887133436507)
985
- (f64.mul (local.get $w) (f64.add (f64.const 0.06661073137387531)
986
- (f64.mul (local.get $w) (f64.add (f64.const 0.049768779946159324)
987
- (f64.mul (local.get $w) (f64.const 0.016285820115365782)))))))))))))
988
- (local.set $s2 (f64.mul (local.get $w)
989
- (f64.add (f64.const -0.19999999999876483)
990
- (f64.mul (local.get $w) (f64.add (f64.const -0.11111110405462356)
991
- (f64.mul (local.get $w) (f64.add (f64.const -0.0769187620504483)
992
- (f64.mul (local.get $w) (f64.add (f64.const -0.058335701337905735)
993
- (f64.mul (local.get $w) (f64.const -0.036531572744216916)))))))))))
994
- ;; |x| < 0.4375: result = r - r*(s1+s2), sign carried by r itself.
995
- (if (i32.lt_s (local.get $id) (i32.const 0))
996
- (then (return (f64.sub (local.get $r) (f64.mul (local.get $r) (f64.add (local.get $s1) (local.get $s2)))))))
997
- ;; Reconstruct: z = atanhi[id] - ((r*(s1+s2) - atanlo[id]) - r), sign of x.
998
- (if (i32.eq (local.get $id) (i32.const 0))
999
- (then (local.set $ahi (f64.const 0.4636476090008061)) (local.set $alo (f64.const 2.2698777452961687e-17)))
1000
- (else (if (i32.eq (local.get $id) (i32.const 1))
1001
- (then (local.set $ahi (f64.const 0.7853981633974483)) (local.set $alo (f64.const 3.061616997868383e-17)))
1002
- (else (if (i32.eq (local.get $id) (i32.const 2))
1003
- (then (local.set $ahi (f64.const 0.982793723247329)) (local.set $alo (f64.const 1.3903311031230998e-17)))
1004
- (else (local.set $ahi (f64.const 1.5707963267948966)) (local.set $alo (f64.const 6.123233995736766e-17))))))))
1005
- (local.set $res (f64.sub (local.get $ahi)
1006
- (f64.sub (f64.sub (f64.mul (local.get $r) (f64.add (local.get $s1) (local.get $s2))) (local.get $alo))
1007
- (local.get $r))))
1008
- (f64.copysign (local.get $res) (local.get $x)))`)
964
+ (local.set $t (f64.div (f64.sub (local.get $t) (f64.const 0.41421356237309503))
965
+ (f64.add (f64.const 1.0) (f64.mul (f64.const 0.41421356237309503) (local.get $t)))))
966
+ (local.set $off (f64.const 0.39269908169872414))))
967
+ (local.set $u (f64.mul (local.get $t) (local.get $t)))
968
+ (local.set $r (f64.add (local.get $off)
969
+ (f64.mul (local.get $t)
970
+ (f64.add (f64.const 0.99999999939667072)
971
+ (f64.mul (local.get $u)
972
+ (f64.add (f64.const -0.33333307625846248)
973
+ (f64.mul (local.get $u)
974
+ (f64.add (f64.const 0.19998216947828790)
975
+ (f64.mul (local.get $u)
976
+ (f64.add (f64.const -0.14240083011830104)
977
+ (f64.mul (local.get $u)
978
+ (f64.add (f64.const 0.10573479828448784)
979
+ (f64.mul (local.get $u) (f64.const -0.060347904072425573))))))))))))))
980
+ (if (local.get $flip) (then (local.set $r (f64.sub (f64.const 1.5707963267948966) (local.get $r)))))
981
+ (f64.copysign (local.get $r) (local.get $x)))`)
1009
982
 
983
+ // Fast asin: small-argument poly a + a·u·R(u) (u=a²) with the standard half-angle reduction for
984
+ // |x|>0.5 — a = sqrt((1−|x|)/2) maps the singular end to the smooth domain, asin = π/2 − 2·poly.
985
+ // One sqrt only on the upper half, no atan/div. R is a degree-6 minimax on [0,0.25]; max rel err
986
+ // ~2.6e-10. Replaces asin = atan(x/√(1−x²)) (which paid a div + atan's own reductions).
1010
987
  wat('math.asin', `(func $math.asin (param $x f64) (result f64)
1011
- ;; Domain is [-1, 1]; outside it (including ±Infinity), Math.asin returns NaN.
1012
- ;; sin/cos output is clamped to [-1, 1] by sin_core/cos_core, so no tolerance needed here.
1013
- (if (result f64) (f64.gt (f64.abs (local.get $x)) (f64.const 1.0))
1014
- (then (f64.const nan))
1015
- (else (call $math.atan (f64.div (local.get $x)
1016
- (f64.sqrt (f64.sub (f64.const 1.0) (f64.mul (local.get $x) (local.get $x)))))))))`)
988
+ (local $ax f64) (local $a f64) (local $u f64) (local $r f64)
989
+ ;; |x|>1 NaN (covers ±Inf); NaN propagates (the >1 test is false, poly carries NaN through).
990
+ (if (f64.gt (f64.abs (local.get $x)) (f64.const 1.0)) (then (return (f64.const nan))))
991
+ (local.set $ax (f64.abs (local.get $x)))
992
+ (if (f64.le (local.get $ax) (f64.const 0.5))
993
+ (then (local.set $a (local.get $ax)))
994
+ (else (local.set $a (f64.sqrt (f64.mul (f64.const 0.5) (f64.sub (f64.const 1.0) (local.get $ax)))))))
995
+ (local.set $u (f64.mul (local.get $a) (local.get $a)))
996
+ (local.set $r (f64.add (local.get $a) (f64.mul (f64.mul (local.get $a) (local.get $u))
997
+ (f64.add (f64.const 0.16666666715486264)
998
+ (f64.mul (local.get $u)
999
+ (f64.add (f64.const 0.074999892151409259)
1000
+ (f64.mul (local.get $u)
1001
+ (f64.add (f64.const 0.044648555271317079)
1002
+ (f64.mul (local.get $u)
1003
+ (f64.add (f64.const 0.030259196387355945)
1004
+ (f64.mul (local.get $u)
1005
+ (f64.add (f64.const 0.023661273034955098)
1006
+ (f64.mul (local.get $u)
1007
+ (f64.add (f64.const 0.010472588920432560)
1008
+ (f64.mul (local.get $u) (f64.const 0.031028862087420162))))))))))))))))
1009
+ (if (f64.gt (local.get $ax) (f64.const 0.5))
1010
+ (then (local.set $r (f64.sub (f64.const 1.5707963267948966) (f64.mul (f64.const 2.0) (local.get $r))))))
1011
+ (f64.copysign (local.get $r) (local.get $x)))`)
1017
1012
 
1018
1013
  wat('math.acos', `(func $math.acos (param $x f64) (result f64)
1019
1014
  (f64.sub (f64.const ${HALF_PI}) (call $math.asin (local.get $x))))`)
@@ -1080,19 +1075,51 @@ export default (ctx) => {
1080
1075
  (if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const nan))))
1081
1076
  (f64.mul (f64.const 0.5) (call $math.log (f64.div (f64.add (f64.const 1.0) (local.get $x)) (f64.sub (f64.const 1.0) (local.get $x))))))`)
1082
1077
 
1078
+ // Bit-hack initial guess (divide the IEEE exponent by 3 via an integer divide of the raw bits,
1079
+ // plus a magic bias) then 3 Newton steps t = (2t + a/t²)/3 — quadratic convergence, max rel err
1080
+ // ~1e-12 over the whole f64 range. Replaces the old `pow(x,1/3)` seed: no exp/log call, ~3-4×
1081
+ // faster (the colorconv / Oklab hot path is 3 cbrt per pixel), and a program whose only
1082
+ // transcendental is cbrt no longer pulls the pow/exp/log stdlib. Not bit-identical to V8's fdlibm
1083
+ // cbrt (neither was the pow form) — jz's transcendentals are fast minimax/Newton approximations.
1083
1084
  wat('math.cbrt', `(func $math.cbrt (param $x f64) (result f64)
1084
- (local $y f64)
1085
- ;; ±Infinity and NaN pass through; preserve sign of zero.
1085
+ (local $a f64) (local $t f64) (local $s f64)
1086
+ ;; NaN / ±Infinity / ±0 pass through unchanged (sign of zero preserved).
1086
1087
  (if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (local.get $x))))
1087
1088
  (if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
1088
- (if (result f64) (f64.lt (local.get $x) (f64.const 0.0))
1089
- (then (f64.neg (call $math.cbrt (f64.neg (local.get $x)))))
1090
- (else
1091
- ;; Initial guess via pow, then Newton-Raphson: y = (2y + x/y²)/3
1092
- (local.set $y (call $math.pow (local.get $x) (f64.const 0.3333333333333333)))
1093
- (local.set $y (f64.div (f64.add (f64.mul (f64.const 2.0) (local.get $y)) (f64.div (local.get $x) (f64.mul (local.get $y) (local.get $y)))) (f64.const 3.0)))
1094
- (local.set $y (f64.div (f64.add (f64.mul (f64.const 2.0) (local.get $y)) (f64.div (local.get $x) (f64.mul (local.get $y) (local.get $y)))) (f64.const 3.0)))
1095
- (local.get $y))))`)
1089
+ (local.set $a (f64.abs (local.get $x)))
1090
+ (local.set $s (f64.const 1.0))
1091
+ ;; subnormal |x| < 2^-1022: scale up by 2^60 so the exponent split is valid; cbrt(2^60) = 2^20.
1092
+ (if (f64.lt (local.get $a) (f64.const 2.2250738585072014e-308))
1093
+ (then (local.set $a (f64.mul (local.get $a) (f64.const 1152921504606846976.0)))
1094
+ (local.set $s (f64.const 9.5367431640625e-07))))
1095
+ (local.set $t (f64.reinterpret_i64
1096
+ (i64.add (i64.div_u (i64.reinterpret_f64 (local.get $a)) (i64.const 3)) (i64.const 0x2A9F7893BF800000))))
1097
+ (local.set $t (f64.mul (f64.add (f64.add (local.get $t) (local.get $t)) (f64.div (local.get $a) (f64.mul (local.get $t) (local.get $t)))) (f64.const 0.3333333333333333)))
1098
+ (local.set $t (f64.mul (f64.add (f64.add (local.get $t) (local.get $t)) (f64.div (local.get $a) (f64.mul (local.get $t) (local.get $t)))) (f64.const 0.3333333333333333)))
1099
+ (local.set $t (f64.mul (f64.add (f64.add (local.get $t) (local.get $t)) (f64.div (local.get $a) (f64.mul (local.get $t) (local.get $t)))) (f64.const 0.3333333333333333)))
1100
+ (local.set $t (f64.mul (local.get $t) (local.get $s)))
1101
+ (if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.neg (local.get $t))) (else (local.get $t))))`)
1102
+
1103
+ // Fifth root of v ≥ 0 — same bit-hack seed (÷5 of the raw bits) + 3 Newton steps t=(4t+v/t⁴)/5.
1104
+ // Caller (constant-exponent pow with denominator 5, e.g. the sRGB 2.4 gamma) guarantees v ≥ 0.
1105
+ wat('math.fifthroot', `(func $math.fifthroot (param $v f64) (result f64)
1106
+ (local $t f64) (local $s f64) (local $q f64)
1107
+ (if (i32.eqz (call $math.isFinite (local.get $v))) (then (return (local.get $v))))
1108
+ (if (f64.eq (local.get $v) (f64.const 0.0)) (then (return (f64.const 0.0))))
1109
+ (local.set $s (f64.const 1.0))
1110
+ ;; subnormal: scale by 2^100 = (2^20)^5; fifthroot(2^100) = 2^20
1111
+ (if (f64.lt (local.get $v) (f64.const 2.2250738585072014e-308))
1112
+ (then (local.set $v (f64.mul (local.get $v) (f64.const 1.2676506002282294e30)))
1113
+ (local.set $s (f64.const 9.5367431640625e-07))))
1114
+ (local.set $t (f64.reinterpret_i64
1115
+ (i64.add (i64.div_u (i64.reinterpret_f64 (local.get $v)) (i64.const 5)) (i64.const 0x3325E66666666800))))
1116
+ (local.set $q (f64.mul (local.get $t) (local.get $t)))
1117
+ (local.set $t (f64.mul (f64.add (f64.mul (f64.const 4.0) (local.get $t)) (f64.div (local.get $v) (f64.mul (local.get $q) (local.get $q)))) (f64.const 0.2)))
1118
+ (local.set $q (f64.mul (local.get $t) (local.get $t)))
1119
+ (local.set $t (f64.mul (f64.add (f64.mul (f64.const 4.0) (local.get $t)) (f64.div (local.get $v) (f64.mul (local.get $q) (local.get $q)))) (f64.const 0.2)))
1120
+ (local.set $q (f64.mul (local.get $t) (local.get $t)))
1121
+ (local.set $t (f64.mul (f64.add (f64.mul (f64.const 4.0) (local.get $t)) (f64.div (local.get $v) (f64.mul (local.get $q) (local.get $q)))) (f64.const 0.2)))
1122
+ (f64.mul (local.get $t) (local.get $s)))`)
1096
1123
 
1097
1124
  // Small finite-test helper (NaN→0, ±Inf→0, finite→1). Used by transcendental
1098
1125
  // functions that need to short-circuit on infinite inputs.
package/module/string.js CHANGED
@@ -144,6 +144,8 @@ export default (ctx) => {
144
144
  deps({
145
145
  __str_concat: ['__to_str', '__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
146
146
  __str_concat_raw: ['__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
147
+ __str_concat_fresh: ['__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
148
+ __str_concat_raw_fresh: ['__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
147
149
  __str_append_byte: ['__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
148
150
  __str_copy: [],
149
151
  // __str_slice/_view are FUNCTION templates: resolveIncludes' auto-dep scan realizes the
@@ -674,15 +676,27 @@ export default (ctx) => {
674
676
 
675
677
  // Hoist SSO/heap dispatch for hay and ndl out of the inner byte loop. Inner
676
678
  // loop becomes (load8_u OR sso byte-extract) per side — no per-byte calls.
679
+ // Needle byte j, SSO-aware (packed-bits extract vs heap load) — lets the SIMD
680
+ // haystack scan serve SSO needles too (the common short "," / "://" needle),
681
+ // not just heap×heap. `$nsso` is loop-invariant so the branch predicts away.
682
+ const nByte = (j) => `(if (result i32) (local.get $nsso)
683
+ (then ${ssoCharWat('(local.get $ndl)', j)})
684
+ (else (i32.load8_u (i32.add (local.get $noff) ${j}))))`
677
685
  wat('__str_indexof', `(func $__str_indexof (param $hay i64) (param $ndl i64) (param $from i32) (result i32)
678
686
  (local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
679
687
  (local $hoff i32) (local $noff i32)
680
688
  (local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
689
+ (local $splat v128) (local $mask i32) (local $scanEnd i32)
681
690
  ;; The search value is ToString'd at the call site (searchArg) per 21.1.3.9 step 4 —
682
691
  ;; a known-string needle passes raw, so this helper carries no float-pulling __to_str.
683
692
  (local.set $hlen (call $__str_byteLen (local.get $hay)))
684
693
  (local.set $nlen (call $__str_byteLen (local.get $ndl)))
685
- (if (i32.eqz (local.get $nlen)) (then (return (local.get $from))))
694
+ ;; Empty needle matches at clamp(from, 0, hlen) per 21.1.3.9 step 6 (Min(Max(pos,0),len)).
695
+ (if (i32.eqz (local.get $nlen))
696
+ (then (return (select
697
+ (select (local.get $from) (local.get $hlen) (i32.lt_s (local.get $from) (local.get $hlen)))
698
+ (i32.const 0)
699
+ (i32.ge_s (local.get $from) (i32.const 0))))))
686
700
  (if (i32.gt_s (local.get $nlen) (local.get $hlen)) (then (return (i32.const -1))))
687
701
  (local.set $hoff (i32.wrap_i64 (i64.and (local.get $hay) (i64.const ${LAYOUT.OFFSET_MASK}))))
688
702
  (local.set $noff (i32.wrap_i64 (i64.and (local.get $ndl) (i64.const ${LAYOUT.OFFSET_MASK}))))
@@ -693,6 +707,83 @@ export default (ctx) => {
693
707
  (i32.wrap_i64 (i64.shr_u (local.get $ndl) (i64.const ${LAYOUT.AUX_SHIFT})))
694
708
  (i32.const ${LAYOUT.SSO_BIT})))
695
709
  (local.set $i (if (result i32) (i32.gt_s (local.get $from) (i32.const 0)) (then (local.get $from)) (else (i32.const 0))))
710
+ ;; Single-byte needle (the common s.indexOf('/') / s.includes(' ')): scan for one byte without
711
+ ;; the per-position match/j/k bookkeeping. Heap haystack gets a branchless load8_u loop (no
712
+ ;; per-byte SSO test); a 16-wide SIMD scan here would only add splat/window setup that short
713
+ ;; strings — the bulk of single-char searches — never amortize, so the scalar scan stays.
714
+ (if (i32.eq (local.get $nlen) (i32.const 1))
715
+ (then
716
+ (local.set $nb ${nByte('(i32.const 0)')})
717
+ (if (i32.eqz (local.get $hsso))
718
+ (then
719
+ (block $sd (loop $ss
720
+ (br_if $sd (i32.ge_s (local.get $i) (local.get $hlen)))
721
+ (if (i32.eq (i32.load8_u (i32.add (local.get $hoff) (local.get $i))) (local.get $nb))
722
+ (then (return (local.get $i))))
723
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
724
+ (br $ss)))
725
+ (return (i32.const -1)))
726
+ (else
727
+ (block $sd2 (loop $ss2
728
+ (br_if $sd2 (i32.ge_s (local.get $i) (local.get $hlen)))
729
+ (if (i32.eq ${ssoCharWat('(local.get $hay)', '(local.get $i)')} (local.get $nb))
730
+ (then (return (local.get $i))))
731
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
732
+ (br $ss2)))
733
+ (return (i32.const -1))))))
734
+ ;; Multi-byte needle, HEAP haystack (needle SSO or heap): a SIMD first-byte memchr — broadcast
735
+ ;; needle[0] across 16 lanes, i8x16.eq a 16-byte haystack window, and read the match-bitmask.
736
+ ;; Only positions whose first byte matches reach the branchless verify; the rare match (V8's own
737
+ ;; StringIndexOf is SIMD here) is what the scalar path lost on. The SIMD load only touches the
738
+ ;; haystack, so an SSO needle (the common short "," / "://" / "TARGET") rides this path too —
739
+ ;; its bytes are fetched SSO-aware via nByte(). scanEnd = hlen-nlen is the last viable start; the
740
+ ;; window stops at hlen-16 (a full 16-byte load stays inside the string), a scalar tail covers
741
+ ;; the rest. Only an SSO haystack (≤ MAX_SSO bytes — too short to scan 16-wide) falls through.
742
+ (if (i32.eqz (local.get $hsso))
743
+ (then
744
+ (local.set $nb ${nByte('(i32.const 0)')})
745
+ (local.set $scanEnd (i32.sub (local.get $hlen) (local.get $nlen)))
746
+ (local.set $splat (i8x16.splat (local.get $nb)))
747
+ (block $vd (loop $vo
748
+ (br_if $vd (i32.gt_s (local.get $i) (i32.sub (local.get $hlen) (i32.const 16))))
749
+ (local.set $mask (i8x16.bitmask
750
+ (i8x16.eq (v128.load (i32.add (local.get $hoff) (local.get $i))) (local.get $splat))))
751
+ (block $bd (loop $bo
752
+ (br_if $bd (i32.eqz (local.get $mask)))
753
+ (local.set $k (i32.add (local.get $i) (i32.ctz (local.get $mask)))) ;; candidate start
754
+ (br_if $bd (i32.gt_s (local.get $k) (local.get $scanEnd))) ;; positions only grow
755
+ (local.set $match (i32.const 1))
756
+ (local.set $j (i32.const 1))
757
+ (block $mn (loop $mi
758
+ (br_if $mn (i32.ge_s (local.get $j) (local.get $nlen)))
759
+ (if (i32.ne (i32.load8_u (i32.add (i32.add (local.get $hoff) (local.get $k)) (local.get $j)))
760
+ ${nByte('(local.get $j)')})
761
+ (then (local.set $match (i32.const 0)) (br $mn)))
762
+ (local.set $j (i32.add (local.get $j) (i32.const 1)))
763
+ (br $mi)))
764
+ (if (local.get $match) (then (return (local.get $k))))
765
+ (local.set $mask (i32.and (local.get $mask) (i32.sub (local.get $mask) (i32.const 1)))) ;; clear lowest match
766
+ (br $bo)))
767
+ (local.set $i (i32.add (local.get $i) (i32.const 16)))
768
+ (br $vo)))
769
+ ;; scalar tail: positions [i, scanEnd] the SIMD window couldn't cover
770
+ (block $md (loop $mo
771
+ (br_if $md (i32.gt_s (local.get $i) (local.get $scanEnd)))
772
+ (if (i32.eq (i32.load8_u (i32.add (local.get $hoff) (local.get $i))) (local.get $nb))
773
+ (then
774
+ (local.set $match (i32.const 1))
775
+ (local.set $j (i32.const 1))
776
+ (block $mn2 (loop $mi2
777
+ (br_if $mn2 (i32.ge_s (local.get $j) (local.get $nlen)))
778
+ (if (i32.ne (i32.load8_u (i32.add (i32.add (local.get $hoff) (local.get $i)) (local.get $j)))
779
+ ${nByte('(local.get $j)')})
780
+ (then (local.set $match (i32.const 0)) (br $mn2)))
781
+ (local.set $j (i32.add (local.get $j) (i32.const 1)))
782
+ (br $mi2)))
783
+ (if (local.get $match) (then (return (local.get $i))))))
784
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
785
+ (br $mo)))
786
+ (return (i32.const -1))))
696
787
  (block $done (loop $outer
697
788
  (br_if $done (i32.gt_s (local.get $i) (i32.sub (local.get $hlen) (local.get $nlen))))
698
789
  (local.set $match (i32.const 1))
@@ -1025,6 +1116,18 @@ export default (ctx) => {
1025
1116
  (global.set $__heap (local.get $newHeap))
1026
1117
  (return (f64.reinterpret_i64 (local.get $a)))))` : ''
1027
1118
 
1119
+ // Always-fresh tail: allocate a new buffer and copy both operands. Shared by the
1120
+ // bump-extend concats (reached when `a` is NOT heap-top) and the `_fresh` variants
1121
+ // (which never bump-extend at all — emit routes a NON-self-accumulating `t = s + x`
1122
+ // here so it can't mutate the live `s` the way the heap-top extend would).
1123
+ const allocCopyTail = `
1124
+ (local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
1125
+ (i32.store (local.get $off) (local.get $total))
1126
+ (local.set $off (i32.add (local.get $off) (i32.const 4)))
1127
+ (call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
1128
+ (call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
1129
+ (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
1130
+
1028
1131
  // Fused single-byte append: `buf += str[i]` lowers to this when both sides are
1029
1132
  // VAL.STRING and the rhs is a string-index. Skips __str_idx's 1-char SSO
1030
1133
  // construction and __str_concat's type-dispatch — byte goes directly from
@@ -1084,6 +1187,10 @@ export default (ctx) => {
1084
1187
  (i32.store8 (i32.add (local.get $off) (local.get $alen)) (local.get $byte))
1085
1188
  (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
1086
1189
 
1190
+ // __str_concat / __str_concat_raw bump-EXTEND `a` in place when it is the heap-top own string
1191
+ // (the O(N) accumulator path). Emit calls these ONLY when the source is a self-accumulation
1192
+ // `x = x + …` (so the mutated `a` is dead-after-reassign) or when `a` is a provably-fresh
1193
+ // module-internal temporary; a plain `t = s + x` over a live `s` routes to the _fresh twins.
1087
1194
  wat('__str_concat', `(func $__str_concat (param $a i64) (param $b i64) (result f64)
1088
1195
  (local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
1089
1196
  (local $ta i32) (local $aoff i32) (local $newHeap i32)
@@ -1096,13 +1203,7 @@ export default (ctx) => {
1096
1203
  (if (i32.eqz (local.get $total))
1097
1204
  (then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
1098
1205
  ${ssoResultFast}
1099
- ${concatFast}
1100
- (local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
1101
- (i32.store (local.get $off) (local.get $total))
1102
- (local.set $off (i32.add (local.get $off) (i32.const 4)))
1103
- (call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
1104
- (call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
1105
- (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
1206
+ ${concatFast}${allocCopyTail}`)
1106
1207
 
1107
1208
  wat('__str_concat_raw', `(func $__str_concat_raw (param $a i64) (param $b i64) (result f64)
1108
1209
  (local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
@@ -1113,13 +1214,30 @@ export default (ctx) => {
1113
1214
  (if (i32.eqz (local.get $total))
1114
1215
  (then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
1115
1216
  ${ssoResultFast}
1116
- ${concatFast}
1117
- (local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
1118
- (i32.store (local.get $off) (local.get $total))
1119
- (local.set $off (i32.add (local.get $off) (i32.const 4)))
1120
- (call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
1121
- (call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
1122
- (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
1217
+ ${concatFast}${allocCopyTail}`)
1218
+
1219
+ // Non-mutating twins: same SSO-pair fast path, but NEVER bump-extend — always alloc+copy a fresh
1220
+ // buffer, leaving `a` untouched. The default for emit's user-level `+` (any `t = s + x` where the
1221
+ // result is not assigned straight back to `s`), so string immutability holds for live operands.
1222
+ wat('__str_concat_fresh', `(func $__str_concat_fresh (param $a i64) (param $b i64) (result f64)
1223
+ (local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
1224
+ (local.set $a (call $__to_str (local.get $a)))
1225
+ (local.set $b (call $__to_str (local.get $b)))
1226
+ (local.set $alen (call $__str_byteLen (local.get $a)))
1227
+ (local.set $blen (call $__str_byteLen (local.get $b)))
1228
+ (local.set $total (i32.add (local.get $alen) (local.get $blen)))
1229
+ (if (i32.eqz (local.get $total))
1230
+ (then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
1231
+ ${ssoResultFast}${allocCopyTail}`)
1232
+
1233
+ wat('__str_concat_raw_fresh', `(func $__str_concat_raw_fresh (param $a i64) (param $b i64) (result f64)
1234
+ (local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
1235
+ (local.set $alen (call $__str_byteLen (local.get $a)))
1236
+ (local.set $blen (call $__str_byteLen (local.get $b)))
1237
+ (local.set $total (i32.add (local.get $alen) (local.get $blen)))
1238
+ (if (i32.eqz (local.get $total))
1239
+ (then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
1240
+ ${ssoResultFast}${allocCopyTail}`)
1123
1241
 
1124
1242
  wat('__str_replace', `(func $__str_replace (param $str i64) (param $search i64) (param $repl i64) (result f64)
1125
1243
  (local $idx i32) (local $slen i32)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jz",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Ahead-of-time compiler for the numeric core of JavaScript (DSP, audio, math, parsers) to lean GC-free WASM — valid jz is valid JS, no type annotations, no runtime.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -76,7 +76,7 @@
76
76
  "license": "MIT",
77
77
  "dependencies": {
78
78
  "subscript": "^10.4.17",
79
- "watr": "^4.7.1"
79
+ "watr": "^5.0.0"
80
80
  },
81
81
  "keywords": [
82
82
  "javascript",