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/bench/bench.svg +14 -14
- package/dist/jz.js +897 -804
- package/module/array.js +20 -0
- package/module/math.js +148 -121
- package/module/string.js +133 -15
- package/package.json +2 -2
- package/src/abi/string.js +11 -6
- package/src/compile/emit.js +77 -17
- package/src/compile/index.js +6 -0
- package/src/compile/infer.js +8 -1
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/narrow.js +12 -3
- package/src/optimize/index.js +166 -1
- package/src/optimize/vectorize.js +10 -0
- package/src/prepare/index.js +18 -5
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': [
|
|
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'
|
|
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 $
|
|
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
|
-
|
|
620
|
-
(local.set $s (f64x2.div (local.get $
|
|
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.
|
|
627
|
-
(f64x2.
|
|
628
|
-
(f64x2.
|
|
629
|
-
(f64x2.
|
|
630
|
-
|
|
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 =
|
|
753
|
-
;;
|
|
754
|
-
;;
|
|
755
|
-
|
|
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.
|
|
771
|
-
(f64.
|
|
772
|
-
(f64.
|
|
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 $
|
|
943
|
-
|
|
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.
|
|
947
|
-
|
|
948
|
-
(
|
|
949
|
-
|
|
950
|
-
(if (f64.
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
(
|
|
958
|
-
(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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
|
-
|
|
1012
|
-
;;
|
|
1013
|
-
(if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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 $
|
|
1085
|
-
;; ±Infinity
|
|
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
|
-
(
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
(
|
|
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.
|
|
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": "^
|
|
79
|
+
"watr": "^5.0.0"
|
|
80
80
|
},
|
|
81
81
|
"keywords": [
|
|
82
82
|
"javascript",
|