jz 0.8.1 → 0.9.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.
Files changed (72) hide show
  1. package/README.md +56 -9
  2. package/bench/README.md +121 -50
  3. package/bench/bench.svg +27 -27
  4. package/cli.js +3 -1
  5. package/dist/interop.js +1 -1
  6. package/dist/jz.js +7541 -6271
  7. package/index.js +165 -74
  8. package/interop.js +189 -17
  9. package/jzify/arguments.js +32 -1
  10. package/jzify/async.js +409 -0
  11. package/jzify/classes.js +136 -18
  12. package/jzify/generators.js +639 -0
  13. package/jzify/hoist-vars.js +73 -1
  14. package/jzify/index.js +123 -4
  15. package/jzify/names.js +1 -0
  16. package/jzify/transform.js +239 -1
  17. package/layout.js +48 -3
  18. package/module/array.js +299 -44
  19. package/module/atomics.js +144 -0
  20. package/module/collection.js +1429 -155
  21. package/module/core.js +431 -40
  22. package/module/date.js +142 -120
  23. package/module/fs.js +144 -0
  24. package/module/function.js +6 -3
  25. package/module/index.js +4 -1
  26. package/module/json.js +270 -49
  27. package/module/math.js +892 -32
  28. package/module/number.js +532 -163
  29. package/module/object.js +353 -95
  30. package/module/regex.js +157 -7
  31. package/module/schema.js +100 -2
  32. package/module/string.js +301 -84
  33. package/module/typedarray.js +264 -72
  34. package/module/web.js +36 -0
  35. package/package.json +5 -5
  36. package/src/abi/string.js +71 -5
  37. package/src/ast.js +11 -5
  38. package/src/autoload.js +28 -1
  39. package/src/bridge.js +7 -2
  40. package/src/compile/analyze-scans.js +22 -7
  41. package/src/compile/analyze.js +136 -30
  42. package/src/compile/dyn-closure-tables.js +277 -0
  43. package/src/compile/emit-assign.js +281 -15
  44. package/src/compile/emit.js +979 -54
  45. package/src/compile/index.js +271 -29
  46. package/src/compile/infer.js +34 -3
  47. package/src/compile/inplace-store.js +329 -0
  48. package/src/compile/narrow.js +543 -15
  49. package/src/compile/peel-stencil.js +5 -0
  50. package/src/compile/plan/index.js +45 -3
  51. package/src/compile/plan/inline.js +8 -1
  52. package/src/compile/plan/literals.js +39 -0
  53. package/src/compile/plan/scope.js +430 -23
  54. package/src/compile/program-facts.js +732 -38
  55. package/src/ctx.js +64 -9
  56. package/src/helper-counters.js +7 -1
  57. package/src/ir.js +113 -5
  58. package/src/kind-traits.js +66 -3
  59. package/src/kind.js +84 -12
  60. package/src/op-policy.js +7 -4
  61. package/src/optimize/index.js +1060 -750
  62. package/src/optimize/recurse.js +2 -2
  63. package/src/optimize/vectorize.js +972 -67
  64. package/src/prepare/index.js +792 -63
  65. package/src/prepare/math-kernel.js +331 -0
  66. package/src/prepare/pre-eval.js +714 -0
  67. package/src/snapshot.js +194 -0
  68. package/src/type.js +1170 -56
  69. package/src/wat/assemble.js +403 -65
  70. package/src/wat/codegen.js +74 -14
  71. package/transform.js +113 -4
  72. package/wasi.js +3 -0
package/module/number.js CHANGED
@@ -10,12 +10,12 @@
10
10
  */
11
11
 
12
12
  import { typed, asF64, asI32, asI64, toI32, toNumF64, NULL_NAN, UNDEF_NAN, FALSE_NAN, TRUE_NAN, temp, tempI32, tempI64, ptrTypeEq, truthyIR } from '../src/ir.js'
13
- import { ssoBitI64Hex } from '../layout.js'
14
- import { emit, bool, deps, reg, hostImport } from '../src/bridge.js'
13
+ import { ssoBitI64Hex, ptrNanHex } from '../layout.js'
14
+ import { emit, bool, deps, reg } from '../src/bridge.js'
15
15
  import { isReassigned } from '../src/ast.js'
16
16
  import { valTypeOf } from '../src/kind.js'
17
17
  import { VAL } from '../src/reps.js'
18
- import { inc, PTR, LAYOUT } from '../src/ctx.js'
18
+ import { inc, PTR, LAYOUT, declGlobal } from '../src/ctx.js'
19
19
 
20
20
  // ─── Shared decimal-number parsing fragments ────────────────────────────────
21
21
  // `__to_num` (Number coercion) and `__parseFloat` both scan a StrDecimalLiteral
@@ -89,10 +89,16 @@ const DEC_SIGNIFICAND = `
89
89
  (if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1)))))
90
90
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
91
91
  (br $numLoop)))
92
- ;; Accumulate the significand in an i64 (exact to 18 decimal digits,
93
- ;; since 10^18 < 2^63) and convert to f64 once at the end a single
94
- ;; correctly-rounded i64->f64 step instead of lossy per-digit f64 math.
95
- (if (i32.lt_s (local.get $sigDigits) (i32.const 18))
92
+ ;; Accumulate the significand in an i64, UNSIGNED-exact to 19 decimal
93
+ ;; digits (2^64-1 1.8e19): the first 18 always fit; the 19th joins only
94
+ ;; when mant*10+9 cannot overflow (mant (2^64-1-9)/10). Rounding by
95
+ ;; increment only ever applies from the 20th digit on — pre-rounding the
96
+ ;; 19th double-rounded 19-digit literals (parseFloat('1152921504606847359')
97
+ ;; came out 2 ulp off; the EL consumer is unsigned throughout).
98
+ (if (i32.or
99
+ (i32.lt_s (local.get $sigDigits) (i32.const 18))
100
+ (i32.and (i32.eq (local.get $sigDigits) (i32.const 18))
101
+ (i64.le_u (local.get $mant) (i64.const 1844674407370955160))))
96
102
  (then
97
103
  (local.set $mant
98
104
  (i64.add
@@ -146,11 +152,28 @@ const sciExponent = (tail) => `
146
152
 
147
153
  // Apply the accumulated base-10 exponent to $result via __pow10.
148
154
  // Used as fallback when EL cannot determine a unique rounding.
155
+ // Deep exponents split in two steps so the intermediate stays finite/normal:
156
+ // 10^324 overflows f64 (__pow10 → Infinity) and x/Infinity flushed every
157
+ // |exp10| > 308 literal to 0 — including min-normal 2.2250738585072014e-308
158
+ // (17 mantissa digits → decExp −324) and the 5e-324 min subnormal. Dividing
159
+ // by 10^(|e|−308) FIRST keeps the intermediate normal; the final /10^308
160
+ // rounds once into the (sub)normal target. Symmetric split on overflow-side
161
+ // exponents (mant digits can drag decExp past +308 while the value is finite).
149
162
  const POW10_SCALE = `
150
163
  (if (i32.gt_s (local.get $decExp) (i32.const 0))
151
- (then (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))
164
+ (then
165
+ (if (i32.gt_s (local.get $decExp) (i32.const 308))
166
+ (then
167
+ (local.set $result (f64.mul (local.get $result) (call $__pow10 (i32.sub (local.get $decExp) (i32.const 308)))))
168
+ (local.set $result (f64.mul (local.get $result) (call $__pow10 (i32.const 308)))))
169
+ (else (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))))
152
170
  (if (i32.lt_s (local.get $decExp) (i32.const 0))
153
- (then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))`
171
+ (then
172
+ (if (i32.lt_s (local.get $decExp) (i32.const -308))
173
+ (then
174
+ (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const -308) (local.get $decExp)))))
175
+ (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.const 308)))))
176
+ (else (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))))`
154
177
 
155
178
  // Eisel-Lemire correctly-rounded decimal-to-f64.
156
179
  // Used in place of FINISH_SIGNIFICAND + POW10_SCALE. Keeps $mant as i64 until after
@@ -207,17 +230,17 @@ const DEC_TO_F64_WAT = `(func $__dec_to_f64
207
230
  (local.set $mBits (i32.sub (i32.const 64) (i32.wrap_i64 (i64.clz (local.get $mant)))))
208
231
  ;; Normalize mant to 64-bit: w = mant << (64 - mBits)
209
232
  (local.set $w (i64.shl (local.get $mant) (i64.extend_i32_u (i32.sub (i32.const 64) (local.get $mBits)))))
210
- ;; Range checks: the table is TRIMMED to exp10 in [-65..65] (covers every
211
- ;; real-world / source / JSON constant; fft/synth's coefficients are ~10^-23).
212
- ;; Outside that, return NaN caller falls back to POW10_SCALE (the pre-EL path,
213
- ;; ~1 ULP for moderate exponents, exact only near the f64 limits no real literal
214
- ;; reaches). This keeps the data segment ~2KB instead of ~10KB on every program
215
- ;; that does an untyped numeric coercion (which pulls __to_num).
216
- (if (i32.or (i32.lt_s (local.get $exp10) (i32.const -65))
217
- (i32.gt_s (local.get $exp10) (i32.const 65)))
233
+ ;; Full-range table: exp10 in [-342..308] every finite/subnormal f64 decimal
234
+ ;; parses correctly rounded (the trimmed [-65..65] table sent deep exponents to
235
+ ;; the POW10_SCALE fallback, whose double rounding missed the subnormal boundary
236
+ ;; by 1 ulp and whose 10^|e|>308 scale factor overflowed to Infinity, flushing
237
+ ;; min-normal-adjacent literals to 0 watr.wasm's float_literals leg). ~10.4KB
238
+ ;; data on programs that pull __to_num; correctness owns the trade.
239
+ (if (i32.or (i32.lt_s (local.get $exp10) (i32.const -342))
240
+ (i32.gt_s (local.get $exp10) (i32.const 308)))
218
241
  (then (return (f64.const nan))))
219
- ;; Load 128-bit table entry for exp10: tbl = $__el_tbl + (exp10 + 65) * 16
220
- (local.set $tbl (i32.add (global.get $__el_tbl) (i32.shl (i32.add (local.get $exp10) (i32.const 65)) (i32.const 4))))
242
+ ;; Load 128-bit table entry for exp10: tbl = $__el_tbl + (exp10 + 342) * 16
243
+ (local.set $tbl (i32.add (global.get $__el_tbl) (i32.shl (i32.add (local.get $exp10) (i32.const 342)) (i32.const 4))))
221
244
  (local.set $tblLo (i64.load (local.get $tbl)))
222
245
  (local.set $tblHi (i64.load (i32.add (local.get $tbl) (i32.const 8))))
223
246
  ;; ─── Two-product: (prodHi:prodLo) = w × tblHi + (w × tblLo >> 64) ──────────
@@ -363,19 +386,40 @@ const DEC_TO_F64_WAT = `(func $__dec_to_f64
363
386
  (i64.shl (i64.extend_i32_u (local.get $biased)) (i64.const 52))
364
387
  (local.get $mant52))))`
365
388
 
389
+ // Hex → byte-string via char-array + one join. NOT `s += chr` in a loop: that
390
+ // allocates Σ1..n ≈ n²/2 bytes of dead strings (54 MB for the 10.4 KB EL table),
391
+ // and setup runs PER COMPILE inside the self-host kernel — a warm no-_clear
392
+ // instance exhausted its heap after ~70 compiles. join is linear: ~n boxes of
393
+ // array + 1-char strings plus the result.
394
+ const hexToBytes = (hex) => {
395
+ const chars = []
396
+ for (let i = 0; i < hex.length; i += 2) chars.push(String.fromCharCode(parseInt(hex.slice(i, i + 2), 16)))
397
+ return chars.join('')
398
+ }
399
+
366
400
  export default (ctx) => {
367
401
  deps({
368
402
  __mkstr: ['__alloc'],
369
- __ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str', '__toExp'],
370
- __i32_to_str: ['__itoa', '__mkstr'],
403
+ // own edge: __static_str's body calls $__mkstr without it the helper
404
+ // rides the self-host-unreliable auto-scan (test/selfhost-includes.js)
405
+ __static_str: ['__mkstr'],
406
+ __ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str', '__ftoa_shortest'],
407
+ __ftoa_shortest: ['__mkstr', '__static_str', '__alloc', '__itoa', '__ryu_mulshift', '__ryu_pow5', '__ryu_pow5div'],
408
+ __ryu_pow5: ['__ryu_mulhi'],
409
+ __ryu_mulshift: ['__ryu_mulhi'],
410
+ __ryu_mulhi: [],
411
+ __ryu_pow5div: [],
412
+ __i32_to_str: ['__itoa_s', '__mkstr'],
413
+ __itoa_s: ['__itoa'],
414
+ __ilen: [],
371
415
  __toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
372
416
  __radix_str: ['__mkstr'],
373
417
  __num_radix: ['__ftoa', '__mkstr'],
374
418
  __to_num: ['__char_at', '__str_byteLen', '__pow10', '__dec_to_f64', '__to_str', '__skipws', '__ptr_aux'],
375
419
  __skipws: ['__char_at', '__strws'],
376
420
  __to_bigint: ['__char_at', '__str_byteLen', '__num_to_bigint'],
377
- __parseInt: ['__char_at', '__str_byteLen'],
378
- __parseFloat: ['__char_at', '__str_byteLen', '__pow10', '__dec_to_f64', '__to_str'],
421
+ __parseInt: ['__char_at', '__str_byteLen', '__skipws', '__to_str'],
422
+ __parseFloat: ['__char_at', '__str_byteLen', '__pow10', '__dec_to_f64', '__to_str', '__skipws'],
379
423
  })
380
424
 
381
425
 
@@ -428,25 +472,52 @@ export default (ctx) => {
428
472
  ${reverseBytesWat()}
429
473
  (local.get $len))`
430
474
 
475
+ // __ilen(val: i32) → i32 — exact byte length of ToString(val): sign + decimal
476
+ // digits over the unsigned magnitude (INT_MIN negates to itself; read unsigned the
477
+ // ladder still lands on 10). Must agree with __itoa_s byte-for-byte: the fused
478
+ // concat emitters alloc from __ilen totals and render with __itoa_s at the cursor —
479
+ // a one-byte disagreement is heap corruption (pinned differentially in test/strings.js).
480
+ ctx.core.stdlib['__ilen'] = `(func $__ilen (param $val i32) (result i32)
481
+ (local $u i32) (local $n i32)
482
+ (local.set $u (local.get $val))
483
+ (if (i32.lt_s (local.get $val) (i32.const 0))
484
+ (then
485
+ (local.set $u (i32.sub (i32.const 0) (local.get $val)))
486
+ (local.set $n (i32.const 1))))
487
+ (if (i32.lt_u (local.get $u) (i32.const 10)) (then (return (i32.add (local.get $n) (i32.const 1)))))
488
+ (if (i32.lt_u (local.get $u) (i32.const 100)) (then (return (i32.add (local.get $n) (i32.const 2)))))
489
+ (if (i32.lt_u (local.get $u) (i32.const 1000)) (then (return (i32.add (local.get $n) (i32.const 3)))))
490
+ (if (i32.lt_u (local.get $u) (i32.const 10000)) (then (return (i32.add (local.get $n) (i32.const 4)))))
491
+ (if (i32.lt_u (local.get $u) (i32.const 100000)) (then (return (i32.add (local.get $n) (i32.const 5)))))
492
+ (if (i32.lt_u (local.get $u) (i32.const 1000000)) (then (return (i32.add (local.get $n) (i32.const 6)))))
493
+ (if (i32.lt_u (local.get $u) (i32.const 10000000)) (then (return (i32.add (local.get $n) (i32.const 7)))))
494
+ (if (i32.lt_u (local.get $u) (i32.const 100000000)) (then (return (i32.add (local.get $n) (i32.const 8)))))
495
+ (if (i32.lt_u (local.get $u) (i32.const 1000000000)) (then (return (i32.add (local.get $n) (i32.const 9)))))
496
+ (i32.add (local.get $n) (i32.const 10)))`
497
+
498
+ // __itoa_s(val: i32, buf: i32) → i32 (bytes written) — signed decimal render at
499
+ // buf: '-' + digits over the unsigned magnitude. The render core shared by
500
+ // __i32_to_str (temp-string ToString) and the fused concat emitters (render
501
+ // directly at the destination cursor — no temp string, no copy).
502
+ ctx.core.stdlib['__itoa_s'] = `(func $__itoa_s (param $val i32) (param $buf i32) (result i32)
503
+ (if (i32.lt_s (local.get $val) (i32.const 0))
504
+ (then
505
+ (i32.store8 (local.get $buf) (i32.const 45)) ;; '-'
506
+ ;; magnitude as unsigned: negate via 0 - val (INT_MIN maps to itself, __itoa reads unsigned)
507
+ (return (i32.add
508
+ (call $__itoa (i32.sub (i32.const 0) (local.get $val)) (i32.add (local.get $buf) (i32.const 1)))
509
+ (i32.const 1)))))
510
+ (call $__itoa (local.get $val) (local.get $buf)))`
511
+
431
512
  // __i32_to_str(val: i32) → f64 (NaN-boxed string) — ToString for a value the
432
513
  // compiler proved is a signed i32. The whole point is to bypass __ftoa's float
433
514
  // machinery (shortest-repr search, __toExp, __pow10): a known integer renders with
434
- // just __itoa over its magnitude plus a sign byte. Lets `"id " + (n|0)` and integer
515
+ // just __itoa_s over a scratch buffer. Lets `"id " + (n|0)` and integer
435
516
  // templates skip the ~2 KB float formatter the generic ToString hard-pulls.
436
517
  ctx.core.stdlib['__i32_to_str'] = `(func $__i32_to_str (param $val i32) (result f64)
437
- (local $buf i32) (local $len i32) (local $u i32)
518
+ (local $buf i32)
438
519
  (local.set $buf (call $__alloc (i32.const 12)))
439
- (if (i32.lt_s (local.get $val) (i32.const 0))
440
- (then
441
- (i32.store8 (local.get $buf) (i32.const 45)) ;; '-'
442
- ;; magnitude as unsigned: negate via 0 - val (INT_MIN maps to itself, read u below)
443
- (local.set $u (i32.sub (i32.const 0) (local.get $val)))
444
- (local.set $len (call $__itoa (local.get $u) (i32.add (local.get $buf) (i32.const 1))))
445
- (return (call $__mkstr (local.get $buf) (i32.add (local.get $len) (i32.const 1)))))
446
- (else
447
- (local.set $len (call $__itoa (local.get $val) (local.get $buf)))
448
- (return (call $__mkstr (local.get $buf) (local.get $len)))))
449
- (f64.const 0))`
520
+ (call $__mkstr (local.get $buf) (call $__itoa_s (local.get $val) (local.get $buf))))`
450
521
 
451
522
  // __radix_str(val: i64, radix: i32) → f64 (NaN-boxed string)
452
523
  // Signed integer → radix string for BigInt.prototype.toString(radix). Digits go
@@ -554,11 +625,12 @@ export default (ctx) => {
554
625
  // Hot (~60M calls in watr self-host via __ftoa). bulk memory.copy is ~10× faster than
555
626
  // a hand-rolled byte loop (wasm2c lowers it to memcpy under PGO+LTO).
556
627
  ctx.core.stdlib['__mkstr'] = `(func $__mkstr (param $buf i32) (param $len i32) (result f64)
557
- (local $off i32) (local $i i32) (local $packed i32) (local $b i32)
558
- ;; SSO fast path: ≤4 ASCII bytes pack into the pointer with no allocation, so a
628
+ (local $off i32) (local $i i32) (local $packed i64) (local $b i32)
629
+ ;; SSO fast path: ≤6 ASCII bytes pack into the pointer with no allocation, so a
559
630
  ;; number-format result doesn't displace a heap-top accumulator — keeping the
560
631
  ;; canonical \`s += n.toString(r)\` builder O(n) via the bump-extend path.
561
- (if (i32.le_u (local.get $len) (i32.const 4))
632
+ ;; ≤6-ASCII⇒SSO is also the string-module INVARIANT __str_eq relies on.
633
+ (if (i32.le_u (local.get $len) (i32.const 6))
562
634
  (then
563
635
  (block $heap
564
636
  (loop $pk
@@ -566,11 +638,16 @@ export default (ctx) => {
566
638
  (then
567
639
  (local.set $b (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
568
640
  (br_if $heap (i32.ge_u (local.get $b) (i32.const 0x80)))
569
- ;; 7-bit ASCII SSO: char i at bit i*7 (chars fit the offset for len ≤4); len at aux bits 10-12.
570
- (local.set $packed (i32.or (local.get $packed) (i32.shl (local.get $b) (i32.mul (local.get $i) (i32.const 7)))))
641
+ ;; 7-bit ASCII SSO: char i at payload bit i*7; len at payload bits 42-44.
642
+ (local.set $packed (i64.or (local.get $packed)
643
+ (i64.shl (i64.extend_i32_u (local.get $b)) (i64.mul (i64.extend_i32_u (local.get $i)) (i64.const 7)))))
571
644
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
572
645
  (br $pk))))
573
- (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.shl (local.get $len) (i32.const 10))) (local.get $packed))))))
646
+ (return (f64.reinterpret_i64 (i64.or
647
+ (i64.or
648
+ (i64.const ${ptrNanHex(PTR.STRING, LAYOUT.SSO_BIT)})
649
+ (i64.shl (i64.extend_i32_u (local.get $len)) (i64.const 42)))
650
+ (local.get $packed)))))))
574
651
  (local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
575
652
  (i32.store (local.get $off) (local.get $len))
576
653
  (local.set $off (i32.add (local.get $off) (i32.const 4)))
@@ -590,17 +667,10 @@ export default (ctx) => {
590
667
  (if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
591
668
  (if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
592
669
  (if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
593
- ;; ES spec: |x| >= 1e21 or 0 < |x| < 1e-6 exponential notation (default mode only).
594
- ;; __toExp clamps the digit count so its scaled mantissa fits an unsigned i32.
595
- ;; Fewer digits than ECMAScript shortest-repr ideal, but valid output.
670
+ ;; Default mode: ES-exact shortest round-trip digits + notation (Ryū core)
671
+ ;; the rest of this function serves mode 1 (toFixed/toPrecision) only.
596
672
  (if (i32.eqz (local.get $mode))
597
- (then
598
- (if (f64.ge (f64.abs (local.get $val)) (f64.const 1e21))
599
- (then (return (call $__toExp (local.get $val) (i32.const 8) (i32.const 1)))))
600
- (if (i32.and
601
- (f64.gt (f64.abs (local.get $val)) (f64.const 0))
602
- (f64.lt (f64.abs (local.get $val)) (f64.const 1e-6)))
603
- (then (return (call $__toExp (local.get $val) (i32.const 8) (i32.const 1)))))))
673
+ (then (return (call $__ftoa_shortest (local.get $val)))))
604
674
  (local.set $buf (call $__alloc (i32.const 40)))
605
675
  ;; Sign
606
676
  (if (f64.lt (local.get $val) (f64.const 0))
@@ -610,9 +680,6 @@ export default (ctx) => {
610
680
  (if (local.get $neg)
611
681
  (then (i32.store8 (local.get $buf) (i32.const 45))
612
682
  (local.set $pos (i32.const 1))))
613
- ;; Default mode: auto-select precision (up to 9 digits, must fit i32 when scaled)
614
- (if (i32.eqz (local.get $mode))
615
- (then (local.set $prec (i32.const 9))))
616
683
  ;; Round and scale to integer: scaled = nearest(val * 10^prec).
617
684
  ;; NOTE: toFixed/toPrecision round ties-to-even here (f64.nearest), which differs from
618
685
  ;; JS's round-half-away-from-zero on exact halves like (2.5).toFixed(0) → '2' vs '3'.
@@ -634,25 +701,7 @@ export default (ctx) => {
634
701
  (then
635
702
  (local.set $int (i32.trunc_f64_u (f64.div (local.get $scaled) (local.get $scale))))
636
703
  (local.set $frac (i32.trunc_f64_u (f64.sub (local.get $scaled)
637
- (f64.mul (f64.convert_i32_u (local.get $int)) (local.get $scale)))))
638
- ;; Default mode, fit loop reduced prec to 0: the rounded integer is ready, but the
639
- ;; original val may still have a fractional part that was discarded. Recover it:
640
- ;; frac_f = val - trunc(val); since frac_f ∈ [0,1), frac_f*10^9 < 10^9 < 2^31 — safe.
641
- (if (i32.and (i32.eqz (local.get $mode)) (i32.eqz (local.get $prec)))
642
- (then
643
- (local.set $abs (f64.sub (local.get $val) (f64.trunc (local.get $val))))
644
- (if (f64.gt (local.get $abs) (f64.const 0))
645
- (then
646
- ;; $int was taken from f64.nearest(val), which rounds .5+ UP (999999999.9 → 1e9),
647
- ;; but $abs/$frac below derive from f64.trunc(val). Re-derive $int from the same
648
- ;; trunc so integer and fraction agree — else String(999999999.9) → "1000000000.9".
649
- (local.set $int (i32.trunc_f64_u (f64.trunc (local.get $val))))
650
- (local.set $prec (i32.const 9))
651
- (local.set $scale (call $__pow10 (i32.const 9)))
652
- ;; round: trunc_u(x+0.5) == floor(x+0.5) for the positive frac scale
653
- (local.set $frac (i32.trunc_f64_u (f64.add
654
- (f64.mul (local.get $abs) (f64.const 1000000000))
655
- (f64.const 0.5)))))))))
704
+ (f64.mul (f64.convert_i32_u (local.get $int)) (local.get $scale))))))
656
705
  (else
657
706
  (local.set $int (i32.const 0))
658
707
  (local.set $frac (i32.const 0))
@@ -682,43 +731,6 @@ export default (ctx) => {
682
731
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
683
732
  (local.set $j (i32.sub (local.get $j) (i32.const 1)))
684
733
  (br $rl)))
685
- ;; Default mode: emit fractional part if val has one (large-int path skipped it before).
686
- ;; frac_f = val - trunc(val); since frac_f ∈ [0,1), frac_f*10^9 < 10^9 < 2^31 — safe.
687
- (if (i32.eqz (local.get $mode))
688
- (then
689
- (local.set $abs (f64.sub (local.get $val) (f64.trunc (local.get $val))))
690
- (if (f64.gt (local.get $abs) (f64.const 0))
691
- (then
692
- ;; round: trunc_u(x+0.5) == floor(x+0.5) for the positive frac scale
693
- (local.set $frac (i32.trunc_f64_u (f64.add
694
- (f64.mul (local.get $abs) (f64.const 1000000000))
695
- (f64.const 0.5))))
696
- (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
697
- (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
698
- ;; 9 fractional digits from $frac, high-to-low
699
- (local.set $i (i32.const 8))
700
- (block $fd2 (loop $fl2
701
- (br_if $fd2 (i32.lt_s (local.get $i) (i32.const 0)))
702
- (local.set $j (i32.div_u (local.get $frac) (i32.trunc_f64_u (call $__pow10 (local.get $i)))))
703
- (i32.store8 (i32.add (local.get $buf) (local.get $pos))
704
- (i32.add (i32.const 48) (i32.rem_u (local.get $j) (i32.const 10))))
705
- (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
706
- (local.set $i (i32.sub (local.get $i) (i32.const 1)))
707
- (br $fl2)))
708
- ;; Strip trailing zeros
709
- (block $sz2 (loop $sl2
710
- (br_if $sz2 (i32.le_s (local.get $pos) (i32.const 0)))
711
- (br_if $sz2 (i32.ne
712
- (i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1))))
713
- (i32.const 48)))
714
- (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))
715
- (br $sl2)))
716
- ;; Strip trailing dot
717
- (if (i32.and (i32.gt_s (local.get $pos) (i32.const 0))
718
- (i32.eq
719
- (i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1))))
720
- (i32.const 46)))
721
- (then (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))))))))
722
734
  (return (call $__mkstr (local.get $buf) (local.get $pos)))))
723
735
  ;; Write integer part
724
736
  (local.set $ilen (call $__itoa (local.get $int) (i32.add (local.get $buf) (local.get $pos))))
@@ -737,19 +749,6 @@ export default (ctx) => {
737
749
  (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
738
750
  (local.set $i (i32.sub (local.get $i) (i32.const 1)))
739
751
  (br $fl)))))
740
- ;; Default mode: strip trailing zeros and dot — only when a fractional part was emitted.
741
- ;; Gating on $prec>0 prevents stripping zeros from the integer part (e.g. 1079623680 → 107962368)
742
- ;; for values where auto-fit reduced prec to 0 because the scaled integer wouldn't fit i32.
743
- (if (i32.and (i32.eqz (local.get $mode)) (i32.gt_s (local.get $prec) (i32.const 0)))
744
- (then
745
- (block $sd (loop $sl
746
- (br_if $sd (i32.le_s (local.get $pos) (i32.const 0)))
747
- (br_if $sd (i32.ne (i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1)))) (i32.const 48)))
748
- (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))
749
- (br $sl)))
750
- (if (i32.and (i32.gt_s (local.get $pos) (i32.const 0))
751
- (i32.eq (i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1)))) (i32.const 46)))
752
- (then (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))))))
753
752
  (call $__mkstr (local.get $buf) (local.get $pos)))`
754
753
 
755
754
  // __toExp(val: f64, prec: i32, strip: i32) → f64 (NaN-boxed string)
@@ -837,7 +836,12 @@ export default (ctx) => {
837
836
 
838
837
  // __static_str(id: i32) → f64 — create heap string from data segment
839
838
  // 0=NaN 1=Infinity 2=-Infinity 3=true 4=false 5=null 6=undefined 7=[Array] 8=[Object]
840
- ctx.core.stdlib['__static_str'] = `(func $__static_str (param $id i32) (result f64)
839
+ // Thunked: shared memory has no active data segment at 0 — the static-string
840
+ // region is memory.init'd into __alloc'd space at start (compile/index.js) and
841
+ // reads rebase off $__staticBase. Owned memory keeps absolute offsets (base 0).
842
+ ctx.core.stdlib['__static_str'] = () => {
843
+ if (ctx.memory.shared && !ctx.scope.globals.has('__staticBase')) declGlobal('__staticBase', 'i32')
844
+ return `(func $__static_str (param $id i32) (result f64)
841
845
  (local $src i32) (local $len i32)
842
846
  (local.set $src (i32.const 0)) (local.set $len (i32.const 0))
843
847
  (if (i32.eqz (local.get $id)) (then (local.set $len (i32.const 3))))
@@ -849,11 +853,16 @@ export default (ctx) => {
849
853
  (if (i32.eq (local.get $id) (i32.const 6)) (then (local.set $src (i32.const 33)) (local.set $len (i32.const 9))))
850
854
  (if (i32.eq (local.get $id) (i32.const 7)) (then (local.set $src (i32.const 42)) (local.set $len (i32.const 7))))
851
855
  (if (i32.eq (local.get $id) (i32.const 8)) (then (local.set $src (i32.const 49)) (local.set $len (i32.const 8))))
852
- (call $__mkstr (local.get $src) (local.get $len)))`
856
+ (if (i32.eq (local.get $id) (i32.const 9)) (then (local.set $src (i32.const 57)) (local.set $len (i32.const 2))))
857
+ (if (i32.eq (local.get $id) (i32.const 10)) (then (local.set $src (i32.const 59)) (local.set $len (i32.const 9))))
858
+ (if (i32.eq (local.get $id) (i32.const 11)) (then (local.set $src (i32.const 68)) (local.set $len (i32.const 9))))
859
+ (call $__mkstr ${ctx.memory.shared ? '(i32.add (global.get $__staticBase) (local.get $src))' : '(local.get $src)'} (local.get $len)))`
860
+ }
853
861
 
854
862
  // R: Static strings seeded at address 0. Compile.js strips if __static_str unused.
855
863
  // 0=NaN 1=Infinity 2=-Infinity 3=true 4=false 5=null 6=undefined 7=[Array] 8=[Object]
856
- const staticStr = 'NaNInfinity-Infinitytruefalsenullundefined[Array][Object]'
864
+ // 9=ok 10=not-equal 11=timed-out (Atomics.wait results, module/atomics.js)
865
+ const staticStr = 'NaNInfinity-Infinitytruefalsenullundefined[Array][Object]oknot-equaltimed-out'
857
866
  ctx.runtime.staticDataLen = staticStr.length
858
867
  ctx.runtime.data = (ctx.runtime.data || '') + staticStr
859
868
 
@@ -865,18 +874,380 @@ export default (ctx) => {
865
874
  // 2096-byte data segment out of modules that don't do decimal→f64 conversion.
866
875
  // TRIMMED to exp10 in [-65..65] (see __dec_to_f64 range check). Each entry: 8
867
876
  // bytes tblLo (LE) + 8 bytes tblHi (LE), for exp10 = -65 + i.
868
- const EL_TABLE_HEX = 'e9b4c29f1247e998eaba94ea52bbcc862462b347d798233fa5e939a527ea7fa8ad3aa0190d7fec8e0e64888eb1e49fd2ac24043068cf5319893e15f9eeeea383d72d053c42c3a85f2b8e5ab7aaea8ca44d7906cb12f49237b63131655525b0cdd00be4be8bd8bbe211bf3e5f55178e80c40e9daeaece6a5bd66e0eb72a9db1a07552445a5a8245f28b0ad2647504dec81267d5f0f0e2d6ee2e8d06be928515fb6b608596d64d46553d18c4b67b73ed9c86b8263c4ce197aa4c1e75a45ad028c4a866304b9fd93dd5df65924d710433f52940fe8e03a846e5ab7f7bd0c6e23f9933d0bd72045298de965f9a8478db8fbf40446d8f85663e967cf7c0a556d273efa84aa4791300e7ddad9a98277663a895525d0d5818c0605559c17eb1537c12bba6b4106e1ef0b8aaaf71de9d681bd7e9e870ca041396b3ca0d07ab6221712692220dfdc5977b603dd1c855bb690db0b66a507cb77d9ab88c053b2b2ac4105ce442b2ad928e60f377e3045b9a7a8ab98ed31e5937b238f0551cc6f14019ed67b288662fc5de466c6ba3372e915fe801df15a03d3b4bac2323c6e2bcba3b31618b1a080d0a5e97ecab771b6ca98a7d39ae214a908c35bde7965522c753eddcc7d9542eda7741d6507e75755c5414ea1c88e9b9d0d5d10be5ddd2927369992424aa64e8444bc64e5e958777d0c3bf2dadd43e110bef3bf15abdb44a62da973cec848ed5cdea8aadb1ec61ddfad0bd4b27a6f24a81a5ed18de67ba943945ad1eb1cfd7ce708794cfea80f4fc434b2cb3ce818d024da9798325a131fc145ef75f42a23043a01358e46e093e3b9a35f5f7d2cafc5388186e9dca8b0dca0083f2b587fd7d3455cf64a25e77487ee091b7d1749e9d812a03fe4a3695da9d5876250612c60422f583bddd833a51c5eed3ae8796f742357972966a92c4523b7544cd14be9a9382170f3c05b775278a9295009a6dc13863dd128bc62453b12cf7ba8000c9f1035ecaeb16fcf6d3ee7bda7450a01d9784f5bca61cbbf488ea1a11926408e5bce5326cd0e3e9312ba56195b67d4a1eeccf9f43622e32ff3a075d1d928eee9293c287d4fab9febe0949b4a43632aa77b8b3a9897968be2e4c5be14dc4be9495e6100af64b01379d0fd9acb03af77c1d90948cf39ec18484530fd85c0935dc24b4b96fb006f2a56528130eb44b42132ee1d3452e44b7873ff9cb88506f09ccbc8c48d73915a5698ff7feaa24cb0bffebaf1b4d885a0e4473b5bed5edbdcefee6db303095f8880a683197a5b436415f70893d7cba362b0dc2fdfcce61841177ccab4c1b69047690323dbc427ae5d594bfd60fb1c1c2499a3fa6b5696caf05bd3786531d7233dc80cf0f2384471b47acc5a7a8a44e401361c3d32b6519e25817b7d1e9263108ac1c5a643bdf4f8d976e1283a3703d0ad7a3703d0ad7a3703d0ad7a3cccccccccccccccccccccccccccccccc00000000000000000000000000000080000000000000000000000000000000a0000000000000000000000000000000c8000000000000000000000000000000fa0000000000000000000000000000409c000000000000000000000000000050c3000000000000000000000000000024f4000000000000000000000000008096980000000000000000000000000020bcbe00000000000000000000000000286bee00000000000000000000000000f9029500000000000000000000000040b743ba00000000000000000000000010a5d4e80000000000000000000000002ae78491000000000000000000000080f420e6b50000000000000000000000a031a95fe3000000000000000000000004bfc91b8e0000000000000000000000c52ebca2b10000000000000000000040763a6b0bde00000000000000000000e8890423c78a0000000000000000000062acc5eb78ad000000000000000000807a17b726d7d800000000000000000090ac6e32788687000000000000000000b4570a3f1668a9000000000000000000a1edccce1bc2d30000000000000000a0841440615159840000000000000000c8a51990b9a56fa500000000000000003a0f20f4278fcbce0000000000000040840994f878393f810000000000000050e50bb936d7078fa100000000000000a4de4e6704cdc9f2c9000000000000004d96228145407c6ffc00000000000020f09db5702ba8adc59d000000000000286c05e34c36121937c500000000000032c7c61be0c356df84f60000000000407f3c5c116c3a960b139a0000000000109f4bb31507c97bce97c00000000000d4861e20db48bb1ac2bdf00000000080441413f4880db55099769600000000a055d91731eb50e2a43f14bc0000000008abcf5dfd25e51a8e4f19eb00000000e5caa15abe37cfd0b8d1ef92000000409e3d4af1ad05030527c6abb7000000d005cd9c6d19c743c6b0b796e5000000a2230082e46f5cea7bce327e8f0000808a2c80a2dd8bf3e41a82bf5db3000020ad37200bd56e309ea1622f35e0000034cc22f4264545de02a59d3d218c0000417f2bb17096d695430e058d29af0040115f76dd0c3c4c7bd45146f0f3da00c86afb690a88a50fcd24f32b76d888007a457a040dea8e5300eeefb6930eab80d8d6984590a4726880e9aba438d2d55047867f2bdaa64741f071eb6663a38524d9675fb6909099516c4ea6403c0ca76dcf41f7e3b4f4ff6507e2cf504bcfd0a421897a0ef1f8bf9f44ed81128f81820d6a2b19522df7afc7956822d7f221a39044769fa6f8f49b39bb02eb8c6feacbb4d55347d036f202086ac325700be5fe9065942c4262d70145229a1726274f9ff57eb9b7d23a4d42d6aa809deff022c7b2dea7658789e0d28bd5e0842badebf82feb889ff455cc6377850c333b4c939bfb256bc7716bbf3cd5a6cfff491f78c27aef45394e46ef8b8a90c37f1c2716f3'
869
- // Pre-decode the EL table bytes once and stash in ctx.runtime.
877
+ const EL_TABLE_HEX = '3f3ba10629aa3f115ad63b92d653f4ee07c524a459cac74af865651b66b4589549f62d0df0bc795d76bf3ea27fe1aebadc7379102c2cd8f4536fce8adf995ae969e84b8a9b1b07799405c1b62ba0d89184e2de6c82e24897f94671a436c84eb6259b1608231b1bfdb7988d4d447ae2e3f7200ee5f5f030fe727f78b06a8c6d8e35a9515e332dbdbd4f9f965c85ef08b28213e63580782cad2347bcb3662b8bde31ccaf2150cb3b4c76ac553020fb168b3dbf1b2a24be4adf93176b3ce8b9dcad0dafa234ad6d1dd778dd854b62e853d968ade5408c6472866baa336f3d71d487c2181f51affd0e68069500cb8c8dc9a9f2de66251bbd120248bac0fdeff03bd4574b60f730b64b016d7498fe9576a5842d5e3835bda39e4188913e7e3bd4cea5b9758682ac4c0652ea35ce5d4a8942cf930994d1ebef4373b2e1a07ace958981f80bf9c5e6eb14101f1a491942fbeba1f64e7777e0261ad4a6609b9f12fa66cab422559598b02089d038824797b800fdb035555d5f6eb4558263b18c5e73209e1d83aa34f78921eb62bcdd2f3690a8c5e423d50175ece9a57b2bd5bb43b412f76e362521c933b2472d3b6555aab06b9a0a846e69bbc09e99f889beead49c06c10d25ca43ea7006c0762c6e250a4448f128575e6a92060438cadb6457862acd96f2ecf504370805c6bc123eed277580bc2e6833c6444a86f76b978de87192a0eb1d21e0fb6aeeb37aa37e5831875b44936429d8ba05ea60594c9eaefd687215b8bd338e298724b96fdf451a3d03cf1ae656e0f879d4b6d3a5ab6b300662c1d08f6c18779889a4488f9686bc87baf1c4b387de94feabcd1a333ca8ab29292eb6e0140b1d7f8bc0f09f25490bbad9dc718cd94de45eaef0ec076f1b8e2810548eaf50619df6d92ce8c94aa2b13214e971dbd25c223a081c31be6e05af9fac31278906f4aa480a63bd6dcac69ac717fe70ab08b1d5daccbb2c097d7881b99d3d4dd6a58ec50860f5bb254eebf0938246f0854ef2f60ab8f22aaf2126ed3823586ca7e1aeb40d66aff51aaa6f28072c6e47d14ded90c89f8dd950ca457984dba4cc82a028b5ba07f10fe53c97976512ce7fa3c87262a949ed531e0c7dfdfe96c15fcc7a0fbb139ce8e8254fdcbcbefcb177fface9548c6191b177b10936f73dcfaa9f17246aefb9f59dd51d8c03750d8395c71dad446b2873054b256f44d2d0e37af932ec0a43f967e34e77c56a8362ceec9b3fa7cd93f7419c22d5764524fb01e8c20f11c1787552436b8ad456ed7902a2f3a9aa786b89130a83d64456348c41459853d556c66b98cc230cd66b41ef9156bea88aecb786bebf2c8fcbc6116b36eceda9d6f33214d7f77b393f1ceb02a2b39453ccb03fd9ccf5da074fe3a5838ae0b968ff9c8f0f40b3d1c9225c8f24ad58e8a11fc2b909081023be9599d9366c37918aa732280c0ad4ab2dfbff8f444785b56c513f328f0cc916f9f9ffb31599e6e2e392677fd9a73dae3bfc7f90ad1fd08d9c7741dfcf11cd994afb9ff4982744b183d511d7435640401dfac7317f3195dd72256b66ea35284852fc1c7fef3e7d8acfee0500654332da663be45eab8e1cad826a07403ed4be90404a9d3656b263d891a204e8a644775a684e22e2754f3e8736cb05a2d015157102e2aa5a53e30da9033e87ca445b5a0d839a5531285c51d3c28694fe0a7958e89180d51e99d9128472a839be4d976e62b6e08a66ff8f17a58f12c82d213d0afbe3982d40ff735dce990b9dbc3466e67c8e7f1c887f68fa80804ec4ebc1ff1f1c729f236a9f0239a12062b566b2ff27a34e87ac44474387c9a8ba62009ffff14b22a9d7151914e9fba9b43d60c33f776fb5c9a6ad8fac719dd3214d38b40f55cb227c1099b317cec4486a6046a1532a7e2b9b547fa09d01f66d42fccb4474da2efbe0944f8402c1990853fbfe551191fa39197a63254331c0ca27ba7eab553579889f58bcee933df0de58342f8b55c14bb563b735757c2696166f01fbedaab19ea23c2583921bb0bbdccac179a9155e46cb8bee2377229ceac91e19ec89cdfa0b5f1775768a95a1927b661f67ec80f9ce365d1214edfa49b71a40e78027e1b78284f41659a8791ce5108890b0b8ecb2d1d258ae3709cc318f15aab4dce6a71f8607ef99850b3ffeb29ad4e193e091a767c96a0067cecebddfe0246d5c2cbbc8e0bd42600041a1d68b186e8873f7e9fa586d5378409149ccae9e896a5075a439af48689690f55b7fda03964252c906846d2d015e7a79998f88833bd3a67b08e5c87881f5d8d77fb3aa640a88909a4a1efbd6e132cfcd5f60d57f06559aa0eef25c26cd7fa1e03b5c851e48eac048aa2ff46fc0dfc9d84ab3a626da24f1da943bf18bb057fc8e1d60d05808b7d6083dc57657ceb65d79123c826eca640c4b8c7654ed8124b51717cba209fd7dcf5d2f94a968a26da2dddc7dcb4c7c5d43353bf9d3020b090b15545dfeaf6d1a4a01c57bc4e1a6e5268d54fa9e1b09a19c41b69a359a109f70b0e9b8c6624bc903d26301c3c0d4c68c1c2467f81dcf5d4263dee079f844fcd79176409be442f512fc1559983656fb4d369410c29d93b2177b5b6f3ec42b7ae143b994f2429ccfee2c9905a75a5bec6ccaf39c975383832a78ffc65031722708bd3084bd2864243556bff8a4bd4e314aec3ce5ec99be36e195771b8736d15eae13460f943f6e84597b55e2288485f699981713b9cf89e52fdaea1a33e52674c07edd57e72176ef5dc8d2f03f4f9848386fea9690a9536b757a07ed0f63be5a060ba5bcb49428c6125949e8d3fb6df1c74dceebe15cd9bbabd72d7164bde4f69cf060338db3cfaa964d798dbdec9d34c42c3980b0a08355fca0d7f0ec67c541f57747a0dc4472b59dc48616f4601b49f9aa2ce489d5ce22c575281c3139629bb7d5375dac8b826b369332637dc73a8225cb8574d7973103029cff5daebc6471f79ed3a886fcfd8302837ff5d9ebbd4db5860853a87bfd24c363df72d0662da162a8ca67d26d1ef7599ecb474260bca43da9de808308e674f085bed95278eb0d8d531661a48b1f926c272e906756665170e85b79cdb653dba3d81cba00f6df324671d96b80a428d2cc0ea4e880f397bf97cdcf86a0cdb2068012cd2261f07daffdc083a8c8815f082057806b796c5d1b3db1a4d2fab03b05743630e3cb631a31c6eea6c39c9c8a061144fcdbbefc60bd77aa90f4c3442d481555fb92ee3bb9ac15d5b4f1f44a1c4d2d15dd1b75c5f38b2d051117995d63a0785ad462d2b6f0ee7846d55cbf347cc8167189fb86e4ac2a17980a34efa04d3daee6355dd40eac7a0e9f86809509a1cc5960837489125719d246a8e0ba4bc93f7038a4d12bd7ac9f8658d298e9cfdd2746a306637b06cc23547783ff9142d5b1174cc83b1a08bf2c2955647fb6934a9e1d5fbaca20caee77736a3d1fe49cee82727bb47e543ef52a886286938e43aa234f9a619ee98db2352afb6738b2d494ece200fa0564311fc3f4f981c6de04ddd38d40bc83de7ef3f9383c113c8b45d448b150ab24965e7038478b150bae57099bdd24d6ad3b768c0619eeda8dd9d6e5800ad7a54ce5c917a4cfd4a8f8874c1f21cd4ccf9f5ebc1d8d030ad3f6a91f67690020c347762b657084cc8774d473e04100f4d9ec293b3fc6d2dfd4c88490585200711068f409cf77c7170afba5b4ee66408d148271ccc255b99dcc79cf30554048d84cf1c6bf99d593e21fac817c6a505a0ea0adb82f00cb38db2717a21c85e4f01108d9a63bc0fd06d2f19cca63a61d6d164a8f904a30bd88462e44fdfe8732044e8e599a2e3e7615ec9c4a9efd293f85e1f1ef40bacdd31a2744ddc57cf48ee659ee2bd128c188e1309554f7ce581930f874bb82b978f58c3edd949a01af1f3c36526ae3e7d632308e143ac1c19a27cbc3e644dca18c3fbcb19988f1b9c0f85e3a10ab29e5b7a7150f60f596e7f0b6f648d41574dea511db12b8b2bc21ad64345b491b11560fd6911766dfeb34ecbe00d90db1ca95c925bbce9f6b9342a7ee404f515d3dfb3bef69c28746b812512a11a3a5b40cfa0a6b04b32958e6ab72baea85e7f047dce6c2e20f1af78f560f69656721ed5993a073db93e0f4b32c53c33ec1696830b88850d2b818f2e0fb133ac71842411e73557283734f978cfa9808f99e92d1e5cfea4e645023bdaf39bf4ab746f745df83a5627d246cacdb83b78e328cba8b6b72a75dce96c34b896465323f2fa96e064f11f5817cb49eabbdfefe0e7b530ac8a25572a29b6186d6365f5fe92c7406bd8575874501fd13860437b7233811482ce752e99641fc98a7c504a52c86155af7a0a7a3fc513b7fd1fb22e7db734d989ac448e63d1385ef82baebe0d2d0603ec1f5da5f0d5866aba3a826990705f98d31b3d1b710ee3f96cc52707f494677f1fd1fc6e594e9cfbbff33a6efed8beab6fed39b0ffdf161d59fc08f6be92ea564fec882537c6ebacac7b073c6a37acefd3d7b63681b0a69bdf94e085ca60ca1be062d3e2151a661169c620af3cf4f496e48b88d69e50ffa1bc3faccefc3a3db895a26f1c3de93f8e2f31ce0755a462996f8b7763a6b5cdb6d98235813f197b3bbf665140986335289be2c2e58ed7da06a747f598b67c0a62beedc1c57b44ea4c2a8ef17b7403848db9413e46c61624df392ebdde450461a12ba171dc8f9ba20b07766151ee5d7a096e82e121ddc7414ce0a60cd32ef86245e91ba5624139299810db880ffaaa8adb5b5696ced97f6ffe110e660bfd5121923e3c163f41efa3f8dca8f9c97c5abeff58db27cb1a6f88f30bdb383fdb6966b73b1dedb5dd0f6b37caca0e4bc647c46d0dd6ba93a427af0cd6be40ef6be0d2ca28ac653c9d2986cc1869d92b32e11b74aadb7a87b07bfc771e84477607ad5649dd87249ad64d71c47118b4a7c6c055f6287cf9bd83d0de498d52d5d9bc7c6f63aa9c3c24e8d101dff4a7934827978b489d3ba3951582a72dfcecb60f14bcb103684288865eeb44e97c2feb8ed1efe9443a532eafe2962223d733e27a9a63d7a94ce5f523f5a7d35060887b8298866cc1c81f726cfb0dcc207caa826342a80ff63a1b5f002dd93b389fc5230c13460ffbcc9e2ac43d47820acbb677cf141383f2cfc0d4caa844b944bd5c0ed362983a79b9d11dfd4655e799e0a31a984f3639102c5d5164affb517464d7dd365f0bc3543f6454e8ebfd1ce4b502ea43f169601ea99d6e1712f86c25ee4398dcf9bfb8164c04c5a4ebb2773765d8870c3827aa27df06ff810d5f8076a3a5526ba918c854e968b36550af7890489eaaf28b6ef26e2bb2e84eacc74ac452be5dbb2a3abb0daea9d921200c98b0b3b6fc94f466baec89244371740bb6ece09cbbbe31706da7ab715051d106a0a42ccbdaadc9d879059e52d23124a8246a99fb6eaa9c254fa578ff9ab96dc22989347646554f3e9f82db3f756bc932b7e7859bd7e29702477f9df5ab6553cdb4eeb5736ef19c676eafb8bf1236b0b9222e6ed036ba07714e5faaeedec458e36ab5fe9c4858895599eb9da14b4eb1802cbdb119b5375fdf702b48819a1269fc2bd52d681a8d2fcb503e1aa5f49f046336de74ba252077ca34499d5db2d560c40a4706fa593842de6ca7f8552b96b0f50cd4ccb8eb8e5b89fbddfa6a7a74613a400207eb2261fa707ad97d0c8280c8c6600d48e2f7873c824cc5e82fa320f2f800089723b5690fa2d7ff6a2b9ffd23aa0402b4fca6b3479f91eb4cba8bf8749c810f6e2bc8681d7b726a1fec9d7f42d7dcad90d36f4b0e632b8249fbb0d72791c3d509143315da03fe6edc62a91ce97634ca475947d7488cf5fa9f8ba1ae13ebeaf86c97cce48b5e1db699b686199cead5be8fb1b029b22da5244c2c3b93f429972e2faa2c241ab9067d5f21ad467c99f87cddca519096bba60c59720c9c1bb87e900540f60cb05e9b8b6bd683bb2aae923012913383e47236724ed2165af0a72b6a0f90be3860c76c03694693e5b8d0ee408f8ce9ba88f937044b9040eb230121d0bb6c2c29273b88c95e7c2486f5e2bf2c6b1b9b93b48f377bd90f31a0b36b6ae381e28a84a1af0d5ecb4b0e18dc363dac6253252dd206c0b28e20ead385a7e489c575f538a942307598d51d8c6f09d5a832d37e8ac79ec48afb0658ef86c4531e4f844221898271bdbdcff581b64cb9e8e1b6b150fbff8f0088a3f2f223d7e4672e2c5dad2ee362d8bac0fbb6acc1dd80e5b779187aa84f8add7e9b4c29f1247e998eaba94ea52bbcc862462b347d798233fa5e939a527ea7fa8ad3aa0190d7fec8e0e64888eb1e49fd2ac24043068cf5319893e15f9eeeea383d72d053c42c3a85f2b8e5ab7aaea8ca44d7906cb12f49237b63131655525b0cdd00be4be8bd8bbe211bf3e5f55178e80c40e9daeaece6a5bd66e0eb72a9db1a07552445a5a8245f28b0ad2647504dec81267d5f0f0e2d6ee2e8d06be928515fb6b608596d64d46553d18c4b67b73ed9c86b8263c4ce197aa4c1e75a45ad028c4a866304b9fd93dd5df65924d710433f52940fe8e03a846e5ab7f7bd0c6e23f9933d0bd72045298de965f9a8478db8fbf40446d8f85663e967cf7c0a556d273efa84aa4791300e7ddad9a98277663a895525d0d5818c0605559c17eb1537c12bba6b4106e1ef0b8aaaf71de9d681bd7e9e870ca041396b3ca0d07ab6221712692220dfdc5977b603dd1c855bb690db0b66a507cb77d9ab88c053b2b2ac4105ce442b2ad928e60f377e3045b9a7a8ab98ed31e5937b238f0551cc6f14019ed67b288662fc5de466c6ba3372e915fe801df15a03d3b4bac2323c6e2bcba3b31618b1a080d0a5e97ecab771b6ca98a7d39ae214a908c35bde7965522c753eddcc7d9542eda7741d6507e75755c5414ea1c88e9b9d0d5d10be5ddd2927369992424aa64e8444bc64e5e958777d0c3bf2dadd43e110bef3bf15abdb44a62da973cec848ed5cdea8aadb1ec61ddfad0bd4b27a6f24a81a5ed18de67ba943945ad1eb1cfd7ce708794cfea80f4fc434b2cb3ce818d024da9798325a131fc145ef75f42a23043a01358e46e093e3b9a35f5f7d2cafc5388186e9dca8b0dca0083f2b587fd7d3455cf64a25e77487ee091b7d1749e9d812a03fe4a3695da9d5876250612c60422f583bddd833a51c5eed3ae8796f742357972966a92c4523b7544cd14be9a9382170f3c05b775278a9295009a6dc13863dd128bc62453b12cf7ba8000c9f1035ecaeb16fcf6d3ee7bda7450a01d9784f5bca61cbbf488ea1a11926408e5bce5326cd0e3e9312ba56195b67d4a1eeccf9f43622e32ff3a075d1d928eee9293c287d4fab9febe0949b4a43632aa77b8b3a9897968be2e4c5be14dc4be9495e6100af64b01379d0fd9acb03af77c1d90948cf39ec18484530fd85c0935dc24b4b96fb006f2a56528130eb44b42132ee1d3452e44b7873ff9cb88506f09ccbc8c48d73915a5698ff7feaa24cb0bffebaf1b4d885a0e4473b5bed5edbdcefee6db303095f8880a683197a5b436415f70893d7cba362b0dc2fdfcce61841177ccab4c1b69047690323dbc427ae5d594bfd60fb1c1c2499a3fa6b5696caf05bd3786531d7233dc80cf0f2384471b47acc5a7a8a44e401361c3d32b6519e25817b7d1e9263108ac1c5a643bdf4f8d976e1283a3703d0ad7a3703d0ad7a3703d0ad7a3cccccccccccccccccccccccccccccccc00000000000000000000000000000080000000000000000000000000000000a0000000000000000000000000000000c8000000000000000000000000000000fa0000000000000000000000000000409c000000000000000000000000000050c3000000000000000000000000000024f4000000000000000000000000008096980000000000000000000000000020bcbe00000000000000000000000000286bee00000000000000000000000000f9029500000000000000000000000040b743ba00000000000000000000000010a5d4e80000000000000000000000002ae78491000000000000000000000080f420e6b50000000000000000000000a031a95fe3000000000000000000000004bfc91b8e0000000000000000000000c52ebca2b10000000000000000000040763a6b0bde00000000000000000000e8890423c78a0000000000000000000062acc5eb78ad000000000000000000807a17b726d7d800000000000000000090ac6e32788687000000000000000000b4570a3f1668a9000000000000000000a1edccce1bc2d30000000000000000a0841440615159840000000000000000c8a51990b9a56fa500000000000000003a0f20f4278fcbce0000000000000040840994f878393f810000000000000050e50bb936d7078fa100000000000000a4de4e6704cdc9f2c9000000000000004d96228145407c6ffc00000000000020f09db5702ba8adc59d000000000000286c05e34c36121937c500000000000032c7c61be0c356df84f60000000000407f3c5c116c3a960b139a0000000000109f4bb31507c97bce97c00000000000d4861e20db48bb1ac2bdf00000000080441413f4880db55099769600000000a055d91731eb50e2a43f14bc0000000008abcf5dfd25e51a8e4f19eb00000000e5caa15abe37cfd0b8d1ef92000000409e3d4af1ad05030527c6abb7000000d005cd9c6d19c743c6b0b796e5000000a2230082e46f5cea7bce327e8f0000808a2c80a2dd8bf3e41a82bf5db3000020ad37200bd56e309ea1622f35e0000034cc22f4264545de02a59d3d218c0000417f2bb17096d695430e058d29af0040115f76dd0c3c4c7bd45146f0f3da00c86afb690a88a50fcd24f32b76d888007a457a040dea8e5300eeefb6930eab80d8d6984590a4726880e9aba438d2d55047867f2bdaa64741f071eb6663a38524d9675fb6909099516c4ea6403c0ca76dcf41f7e3b4f4ff6507e2cf504bcfd0a421897a0ef1f8bf9f44ed81128f81820d6a2b19522df7afc7956822d7f221a39044769fa6f8f49b39bb02eb8c6feacbb4d55347d036f202086ac325700be5fe9065942c4262d70145229a1726274f9ff57eb9b7d23a4d42d6aa809deff022c7b2dea7658789e0d28bd5e0842badebf82feb889ff455cc6377850c333b4c939bfb256bc7716bbf3cd5a6cfff491f78c27aef45394e46ef8b8a90c37f1c2716f3acb5cbe3f08b7597563adacf71d8ed9717a3be1cedee523decc8d0438e4ee9bddd4bee63a8aaa74c27fbc4d431a263ed6aef743ea9cae88ff81cfb245f455e94442b128e53fde2b336e439eeb6d675b916b69671a8bcdb60445dc8a9644cd3e7cd31fe46e95589bc4a3a1deabe0fe49041bebd9863abab6bdd88a4a4ae131db5d12ded7e3c9696c614abcd4d9a5864e2a23c54cfe51d1efcec8aa07060b77e8dcb4b29435fa5253ba8adc88c3865deb0be9ef313b70eef4912d9faaf86fe15dd3743786c3269356eabc7fc2d14bf2d8a045496077fc3c24996f97b39d92eb9ac06e97bc95e7433dcfbf7da878f7ae7d7a371ed3dbb28a069fddae8b499acf0860cce680dea3208c4bc112322c0d7aca89001c390a43f0af52bd6ab2ab00dd8d2fae079dac6672679db65ab1a8e08c78338591891b8017057523f56a1b1cab8a4866f5eb526024ced26cfab095efde6cdb4055b3158814f5478610bc65a5eb08021c7b13dae616369d6398e77f175dca0e9381ecd193abc034cc871d56d9313c923c76540a048ab045f3ace4a497858fb769c3f28640deb627be4c0ce2d4b179d94834f32bdd0a53b9a1d7142f91d5dc47964e37eec448fca00650d93776574f5cb1e4ecf138b997e205fe8bb6abf68997ea621c3d8ed3f9ee876e26a45efc2bf1e10eaf34ee9cfc5a2149bc516abb3ef124a7258d1f1a1bbe5ec803bee4ad09597dc8eae456e8a2a1f2861caa95d44bbbd93321ad7092df52672f93c147515ea569c5f7026263c5958e71ba62c694d926c83770cb02f8b6f2ee1a2cf77c3e0b64764950f9cfb6d0b7a998bc355f498e4ac5ebd8941bd2447ec3f379ab598df8e57b62cec91eced58e70fc500e37e97b2ede33767b667292fe153f6c09b5e3ddf74ee8200d2e079bd6cf49958215b868b11aaa3800659d8ec8771c0aee9f167ae9594cc20486f0ee8e98d701a64ee01dadddc7f148d050931b2588690fe34418815d49f59f0464bbddeeea7343e8251aa1ac90770ac189e6c96ead1c1cde2e5d4b0dd04c66bcfe2039e322399c0ad0f851c1586b74683db8445ff6bbf309953a6639a6765186412e616ff46ef7c7fe8cf7ec0603f8f7ecb4f6e5f8c15ae4ff1819df0380f335ebee34977ef9a99a36da2c52c07d3bff5ad5c1c55ab01800c09cbf6f7c8c72f73d973632a1602a04fcbfdfa9adddcfde767287eda4d01c4119f9eb8011554fde181b21d51a10135d646c626421aa97c5a221f65a50942c28bd8f75869b0e98d7875335f0746695957e79aae831c64b1d65200378997c32f2da1c19aa423bd5d8c67c0846b7db47b7809f2e0463696bab740f83263ce504deb459798d8c33ba9e550b6fffb01a5206617bdbeceb48a131fe5a3ff7a42cea83f5dec3701b1366c336fc6df8ce980c947ba9384415d4447000bb817f023e1bbd9a8b8e591741559c00da61dec6cd92a10d3e62fdb68ad3798c8879213e4c71aea4390fb11c39845beba297718dd79a1e454b47ad6f3fed66d29f4945e54d8c91d6ae10c66585fa6e499181dbb34279e52e28c8f7f2ef7cf5dc05ee4e901b145e71ab0731ffaf4437570765d64421d17a121dca8531c794a49066aba7e4972ae049589926863179ddb870469dedb0eda45faabb6423c5d84d2a94503d6929250d7f8d6b2a945ba92238a0bc2c59b5b92865b861e14d76877ac6c8e32b782f23668f2a726d90c4395d70732ff6423af4402efd1b807e849bde6447f1f1f76ed6a613583a609629c6c20165fe7a6d3a8c5b902a40f8c7ac387a8db36a1900813376803cd89972cda544949c2645ae56b222122806cbdb710aa9bdbf2fdb0de066ba92aa0c7ace5949482926f3d5d96c8c55335c8f9171fba392377cb8cf4bb3ab7a842fafb6e531404762affd778b58472a9699cba4a68198513f5fe0dd7e225cf1384c3695dc25f6658b27ed18c5befc21865f4617ad9fb3f772fef023899d5792fbf98fad8cffa0f55fbaa0386ff4a58fbeebe38cf83f9532aba958467bf5d2ebaaaee8361f27b745a94ddb2a097fa5cb42a95e4f9ee9a1171f994df883d39746175ba5db8aa0156cd377a17eb8c47d1b912e93ab30ac155e062acee12b8cc22b4ab9109604d316b987b57aa17e67f2ba116b60bb8a0fd857e5aed949ddf5f76499ce3077384be138f58147dc2ebfbe9ad418ec88f25aed8b26e591cb3e67a6419d2b1bbf3aed98e5fca6fe35fa099bd9f46de54580d48b97bde25ee3b0480d623ec8a6aae109aa71a56afe94a0520cc2ca7ad04da948051a12b1ba49d0628fff710d942085df0d244fb9086220479ff9aaa87534a74ac07163a35282b4557bf4195a9e85c9197899b8842f275162d2f92fad311dabafe35619569b7092e7c5d9b7c849590697e83b9fa43258c39db34c29ba5bbf4035ee467f9942eef0712c2b202cff578c2baeee01b1d7df5444bb9af6181321773692ad96264dc32169ea71bbaa1fedccf03758f7b7d93bf9b8591a228ca3ed4c3445273da5c78af02e735cbb2fca764fa6a1388083aabad61b001bfef9dd0fdb84518aa8a0816197a1cc2ae6bc5453d27579e54ad8a5b9f98a3729ac6f64b8678f6e254ac3699633fa687203c9adda716b41b6a57847f3ccf8fa928cbc0d5511ca1a2446d659f0bc3f3d3f2fdf025b3b1a4e54a649f43e75978c4b79e96ee1fde0d9f5d3d8714617096b56546bceaa755d106b50ca959790cfc22ff57ebf288d54224f1a709d8cb87dd75ff16932feb8a536ded110ccebee95453bfdcb7faa56da8c868168f812e242a28efd3e5bc8744697d016ef9109d561a7975a48faca995c3dc81c9375544ec60d7928db317147bf453e2bb856a5527398df770e08eeccc78746d95936295b843b89a468cb2270097d1c87a38bbbaa654664158af9e31c0fc057b99066a69d0e9bf512edb031ff8bde3ec1f44e24122f217f3fc88c32676ad1ce827d55ad2aaeedd2f3cab74b0d3d823e2718af186556ad53b0bd6494e8467562d87f6567475626505c785db616501acf828b46cd112bbbec638a752babe01d73633e1c785d7696ef806d1733417614602c0ec9cb32602455ba48290015df9d702f0278460b04216724da3f441b4f78d03ec31a5785cd39bce20cc7152a1757104677ece9633c8420229ff86d384e9c662000f413e20bd69a1799f6808e6a3787bc052d14d682cc40958c7828adfcc569a70a745618237350c2ef991b60b407660a688cb7cb142a1c7bc9b35a40ed093f8cf6afedb5d9389f9abc2434d12c4b8f68305fe5235f8ebf756f34a708b7a337a72c3de53217bf35a16985c4c2e59c0184f74d6a8e959b0f11bbe73df796ff0de62110c1364701ceea2eda82bac4556cbdd8ae78b3ec6d1d48594923617d72b3e956de12ece37064aa7b93704ddccb68dfac899bac1c5871c11e8a2220a4092989c1da01499dbd4b10a914bab0cd0b6be0325c8597f124a5e4db51dd60f8464ae442e3a301f97dcb5a0e2d2e589d2feecea5c247e73dea971a48d475f2c873ea82574ad5d1056148e0db11977f7284e122fd11875946b99f150dd6faa9ad9706bbd822fc93ce3ff96528a0b5501104dc66c637bfb0bdcbf3ce7ac4eaa0154e0f7473c5afa0ed3ef0b21d8710a8134ecfaac65785ce9e375a714870d4da141a739187f96b3e35c53d1d9a850a009121148de1e7ca01c34a84510d3320446ab0aed4a934de49120892bea833f8517564da81df8605db6686bb6e4a48e669dab60122536b9f4e34206e41dce1960426b7c2bd7c1f378cee983aed2801ff812865bf64cb2301742e4245a07a127b69767f233e0defc9c521dae3049c9b1a37d01ef4098163c44a7a4d97c9bfb4e86ee6095281f8ea58ae806082e419de2272ab9baf2a6f14eada2088a7991c4dbb1746769af10aea258cb8aecd7b5f529efa8e0a16dcaac6517bfd6f3a69199f32ad3580a09fd173fdd6eccb010f6bfb0f507ef4c4bfcdd8e948affdc94f3ef8ef9641510afbd4ad99cb61f0a3df895f137be1ad41a6d9d0f44a4a74c4c76bbedc56d218961c88413558dd15fdf53eab49be4b4f53cfd322c55f8e29b6b7492a1c21d22338cbc3f776ab6db828611b74a33a5ea3fafab0f1505a49223e8d5e40e40a7f2874dcb292d83a63b16b1058f121051efe9203e74f82390ca5b1dc7b21654256b24a94d91f62c34bdb2e478df8e54f7c2b689d01a1a9c40b6ef8eab8bb129b57324ac84a120c3d0a3ab7296ae1e74a2902dd7e5c9e8f3c48c560f3cda9288657a7ca62f7e7118fb1796896588b7eafe981b90bbdd8ddef99dfbeb7eaa65a53e7f22742a5531567885faa61ed55f27878f95883ad5de356b935c28338537f168f3ba2a898a560346b873f27fa6852d43b069752b2d2c8457a610ef1fd073fc290e62293b9c9bb2f6676af513828f7bb491baf34983425ff401c5f298a2739a2136a9701c2413777142762f3fcb1001aa83d38c23edd7d40dd353fb0efeaa404a32043836f406a5e863145dc99ed5d0dc3e05c643b148cee27c59b47bc60a05948e86b794ddda811bdc6fa11af826831c19b4f27cca283191e9e5a4109bf0a3631f612f1cfd727df5631fced4c1ec8c3c67393b63bccfdcf23ca7014af213d885e00305bed501ca178608416e97184ea7d844862d4b82bc9da74ad149bd9e21d10ed6e7f8dda22b85519d459cec03b542c9e590bbca453bf35282abe1934362933b1f756a3d170ab0e76216dab8d43a780a6712c50c9d0c9ca1fb9b10e7c5248b66802bfb27e28701457d616a90f6ed2d8060f6f9b1dae94196dcf984b4736939a0f873785e5164d2bb5338a6e1e8e123647b480bdbb27e635534e3078d62da2c3d9a1ace915f5ebc6a01dc49b0fb1078cc40a14176f7756bc501535cdc9d0acb7fc804e9a9ba29631be1b3b98944cdbd9ffa45635429f43b62d92028ac9540ad4779177ca933f1caba0f2932d75d48ccccab8eed49c0d6bed4a9597f86745affbf56f2685c708cee4914301fa81131ff6fec2e83738c2f6a5c19fc26d2ab7effc553fd31c8b75dc2d98f5d5883555e7fb7a87c3eba25f532d0f3742ea4eb355fe5d21bce286fb23fc430123acdb3815bcf63d1807985cfa77a5e4b44801f6232c3bc05e1d766c35119365e55a0a7fafef32b47d98d4034a69fc3b56ac851b9fef0f6984fb150c18f87346385fad3339f569abfd16ed2d8b9d4005e939cc80047ec802f860a074fe8098135b8c3fac0582761bb27cdc862624ce142a6f49c7897b81cd53880bd7dbdcfcce9e798c356bde6630a47e02cddac0340e421bf74ac6ce0fccc581878149804505deaeec8eb430c1e80370fcb0cdf02527a5295bae6548f256005d3fdcf9683e618a7ba69202af32eb8c647fd837c2420df50e94154fa571d33dc4c7ed2cd16748bd29152e9f8ade43f13e01d47811c512e47b6a62377d9dd0f1858e598a163e5f9d8e34876eaa7ea090f578fff445e2f9c678e'
878
+ // Pre-decode the EL table bytes and stash in ctx.runtime.
870
879
  // src/compile/index.js appends elTable to ctx.runtime.data only when
871
880
  // __dec_to_f64 is actually pulled in via deps (lazy, keeps small modules clean).
872
- let elTableBytes = ''
873
- for (let i = 0; i < EL_TABLE_HEX.length; i += 2)
874
- elTableBytes += String.fromCharCode(parseInt(EL_TABLE_HEX.slice(i, i + 2), 16))
875
- ctx.runtime.elTable = elTableBytes
881
+ ctx.runtime.elTable = hexToBytes(EL_TABLE_HEX)
876
882
 
877
883
  // Register the stdlib function (no data appended here — see compile/index.js hook)
878
884
  ctx.core.stdlib['__dec_to_f64'] = DEC_TO_F64_WAT
879
885
 
886
+ // === Ryū shortest round-trip float→decimal (Adams, PLDI'18) ===
887
+ // Digit core for the default String(number)/template/JSON.stringify path: the
888
+ // unique shortest digit string that parses back to the same f64 bits, with ES
889
+ // Number::toString notation rules applied on top. Ported from the reference
890
+ // d2s.c (github.com/ulfjack/ryu, Apache-2.0/Boost-1.0), full-table variant.
891
+ //
892
+ // Size-optimized table (reference RYU_OPTIMIZE_SIZE, d2s_small_table.h):
893
+ // instead of all 618 required 128-bit powers of 5 (~9.7KB), store every 26th
894
+ // power (plus 5^0..5^25 as u64 and 2-bit rounding-error offsets) and rebuild
895
+ // any entry with one extra 64×128 multiply per call ($__ryu_pow5) — verified
896
+ // entry-exact against the full reference tables for all 618 indices.
897
+ // Seed layout at $__ryu_tbl (828 bytes, LE):
898
+ // +0 DOUBLE_POW5_INV_SPLIT2[15] (lo,hi u64 pairs — 1/5^(26k), 126-bit)
899
+ // +240 DOUBLE_POW5_SPLIT2[13] (lo,hi u64 pairs — 5^(26k), 125-bit)
900
+ // +448 DOUBLE_POW5_TABLE[26] (5^0..5^25 as u64)
901
+ // +656 POW5_INV_OFFSETS[22] (u32 bitmaps, 2 bits per index)
902
+ // +744 POW5_OFFSETS[21] (u32 bitmaps, 2 bits per index)
903
+ const RYU_SEED_HEX = '01000000000000000000000000000020345065c05fc9a652bb13cbaec440c21806c8df7100d5a87cf56f0fda58fc27136e4756357d24206502c7e768e48ca41de9e60268d7cd39617977fcc2405bef16798cde43ffa751f991f3b278f5bdbe11e857e9d6e8bee87bb054ac8f848d751bea23a499e9f9d38bb7a3714061da3e15cee33ecb73f948088c97b427d51b7010a2bfefb9eb8532154db44db49bbb6f1996b6076cf8e7eead36d9b4f59135ae13222218af4e6a684d91daaa3d4f40741e9fbd9ee006a1c09857c2a7fda40e90170e7d497173e3208fb220d87605143b12853d7434811343b0ad297a5f27f4351c000000000000000000000000000000100000000000000000b9340332b7f4ad1410db1ab30892540e0d307d951447ba1a66088f4d26adc66df598bf85e2b74511ca96853d92bd1debfca11860dcef52163c92ae220bb8c1b4839d2d5b0562da1c304c7e8f4e8bb25b16f4529f8b56a512fbd4827643ed8af08fe7f9311565191850f19bd94a13eeb4284cf0a686c1251f035fc270cb9e4916e642889c44eb2014b0650836ad6ea58585f0ca14e2fd031a0b899979d5b13d09d8da973a35ebcf10ac363f5e73bb38cf3e6752fa44afba150100000000000000050000000000000019000000000000007d000000000000007102000000000000350c000000000000093d0000000000002d31010000000000e1f505000000000065cd1d0000000000f902950000000000dd0ee90200000000514a8d0e000000009573c24800000000e941cc6b010000008d49fd1a07000000c16ff28623000000c52ebca2b1000000d9e9ac2d780300003d9160e45811000031d6e275bc560000f52e6e4daeb10100c9ea268367780800ed95c28f055a2a00a1edccce1bc2d30025a4000a8bca220454455454455505040010041014044000000001405555154154040000440001000000004041000044504445505400555554556551004000400100000100050100115451515455550500154150000004401001040500000000000000000000000000000000000000000000004095596959555554551555555604051541105455404551554440455044505555450040004040044496655556554540455451411540559155555555405105010000'
904
+ ctx.runtime.ryuTable = hexToBytes(RYU_SEED_HEX)
905
+
906
+ // 64×64 → high 64 bits, via 32-bit limb products (wasm has no mul-high).
907
+ ctx.core.stdlib['__ryu_mulhi'] = `(func $__ryu_mulhi (param $a i64) (param $b i64) (result i64)
908
+ (local $a0 i64) (local $a1 i64) (local $b0 i64) (local $b1 i64) (local $mid i64) (local $mid2 i64)
909
+ (local.set $a0 (i64.and (local.get $a) (i64.const 0xFFFFFFFF)))
910
+ (local.set $a1 (i64.shr_u (local.get $a) (i64.const 32)))
911
+ (local.set $b0 (i64.and (local.get $b) (i64.const 0xFFFFFFFF)))
912
+ (local.set $b1 (i64.shr_u (local.get $b) (i64.const 32)))
913
+ (local.set $mid (i64.add (i64.mul (local.get $a1) (local.get $b0))
914
+ (i64.shr_u (i64.mul (local.get $a0) (local.get $b0)) (i64.const 32))))
915
+ (local.set $mid2 (i64.add (i64.mul (local.get $a0) (local.get $b1))
916
+ (i64.and (local.get $mid) (i64.const 0xFFFFFFFF))))
917
+ (i64.add (i64.add (i64.mul (local.get $a1) (local.get $b1))
918
+ (i64.shr_u (local.get $mid) (i64.const 32)))
919
+ (i64.shr_u (local.get $mid2) (i64.const 32))))`
920
+
921
+ // (m × entry) >> j for a 128-bit table entry at $tbl (lo, hi LE u64 pair);
922
+ // 64 < j < 128, result proven to fit u64 (ryu d2s_intrinsics.h).
923
+ ctx.core.stdlib['__ryu_mulshift'] = `(func $__ryu_mulshift (param $m i64) (param $tbl i32) (param $j i32) (result i64)
924
+ (local $hi i64) (local $h0 i64) (local $sum i64) (local $high1 i64) (local $d i64)
925
+ (local.set $hi (i64.load (i32.add (local.get $tbl) (i32.const 8))))
926
+ (local.set $h0 (call $__ryu_mulhi (local.get $m) (i64.load (local.get $tbl))))
927
+ (local.set $sum (i64.add (local.get $h0) (i64.mul (local.get $m) (local.get $hi))))
928
+ (local.set $high1 (i64.add (call $__ryu_mulhi (local.get $m) (local.get $hi))
929
+ (i64.extend_i32_u (i64.lt_u (local.get $sum) (local.get $h0)))))
930
+ (local.set $d (i64.extend_i32_u (i32.sub (local.get $j) (i32.const 64))))
931
+ (i64.or (i64.shr_u (local.get $sum) (local.get $d))
932
+ (i64.shl (local.get $high1) (i64.sub (i64.const 64) (local.get $d)))))`
933
+
934
+ // Rebuild the 128-bit power-of-5 entry for index $i into 16 bytes at $out:
935
+ // seed × 5^offset, shifted back into the 125/126-bit window, plus the stored
936
+ // 2-bit rounding offset (reference double_computePow5/double_computeInvPow5).
937
+ ctx.core.stdlib['__ryu_pow5'] = () => {
938
+ if (!ctx.scope.globals.has('__ryu_tbl')) declGlobal('__ryu_tbl', 'i32')
939
+ return `(func $__ryu_pow5 (param $i i32) (param $inv i32) (param $out i32) (result i32)
940
+ (local $base i32) (local $base2 i32) (local $off i32) (local $mul i32)
941
+ (local $mlo i64) (local $mhi i64) (local $m i64) (local $a i64)
942
+ (local $lo0 i64) (local $hi0 i64) (local $lo2 i64) (local $hi2 i64)
943
+ (local $delta i64) (local $sLo i64) (local $sHi i64) (local $tLo i64) (local $tHi i64)
944
+ (local $rLo i64) (local $rHi i64) (local $e i64)
945
+ (if (local.get $inv)
946
+ (then
947
+ (local.set $base (i32.div_u (i32.add (local.get $i) (i32.const 25)) (i32.const 26)))
948
+ (local.set $base2 (i32.mul (local.get $base) (i32.const 26)))
949
+ (local.set $off (i32.sub (local.get $base2) (local.get $i)))
950
+ (local.set $mul (i32.add (global.get $__ryu_tbl) (i32.shl (local.get $base) (i32.const 4)))))
951
+ (else
952
+ (local.set $base (i32.div_u (local.get $i) (i32.const 26)))
953
+ (local.set $base2 (i32.mul (local.get $base) (i32.const 26)))
954
+ (local.set $off (i32.sub (local.get $i) (local.get $base2)))
955
+ (local.set $mul (i32.add (i32.add (global.get $__ryu_tbl) (i32.const 240)) (i32.shl (local.get $base) (i32.const 4))))))
956
+ (local.set $mlo (i64.load (local.get $mul)))
957
+ (local.set $mhi (i64.load (i32.add (local.get $mul) (i32.const 8))))
958
+ (if (i32.eqz (local.get $off))
959
+ (then
960
+ (i64.store (local.get $out) (local.get $mlo))
961
+ (i64.store (i32.add (local.get $out) (i32.const 8)) (local.get $mhi))
962
+ (return (local.get $out))))
963
+ (local.set $m (i64.load (i32.add (i32.add (global.get $__ryu_tbl) (i32.const 448)) (i32.shl (local.get $off) (i32.const 3)))))
964
+ ;; b0 = m·(mulLo - inv); b2 = m·mulHi (inv subtracts 1 per the reference)
965
+ (local.set $a (i64.sub (local.get $mlo) (i64.extend_i32_u (local.get $inv))))
966
+ (local.set $lo0 (i64.mul (local.get $m) (local.get $a)))
967
+ (local.set $hi0 (call $__ryu_mulhi (local.get $m) (local.get $a)))
968
+ (local.set $lo2 (i64.mul (local.get $m) (local.get $mhi)))
969
+ (local.set $hi2 (call $__ryu_mulhi (local.get $m) (local.get $mhi)))
970
+ ;; delta = |pow5bits(i) - pow5bits(base2)| ∈ (0, 64)
971
+ (local.set $delta (i64.extend_i32_u (i32.sub
972
+ (i32.shr_u (i32.mul (select (local.get $base2) (local.get $i) (local.get $inv)) (i32.const 1217359)) (i32.const 19))
973
+ (i32.shr_u (i32.mul (select (local.get $i) (local.get $base2) (local.get $inv)) (i32.const 1217359)) (i32.const 19)))))
974
+ ;; (b0 >> delta) + (b2 << (64-delta)) as a 128-bit sum
975
+ (local.set $sLo (i64.or (i64.shr_u (local.get $lo0) (local.get $delta))
976
+ (i64.shl (local.get $hi0) (i64.sub (i64.const 64) (local.get $delta)))))
977
+ (local.set $sHi (i64.shr_u (local.get $hi0) (local.get $delta)))
978
+ (local.set $tLo (i64.shl (local.get $lo2) (i64.sub (i64.const 64) (local.get $delta))))
979
+ (local.set $tHi (i64.or (i64.shl (local.get $hi2) (i64.sub (i64.const 64) (local.get $delta)))
980
+ (i64.shr_u (local.get $lo2) (local.get $delta))))
981
+ (local.set $rLo (i64.add (local.get $sLo) (local.get $tLo)))
982
+ (local.set $rHi (i64.add (i64.add (local.get $sHi) (local.get $tHi))
983
+ (i64.extend_i32_u (i64.lt_u (local.get $rLo) (local.get $sLo)))))
984
+ ;; + stored 2-bit error (+1 more for the inverse table)
985
+ (local.set $e (i64.add
986
+ (i64.and (i64.extend_i32_u (i32.shr_u
987
+ (i32.load (i32.add (i32.add (global.get $__ryu_tbl) (select (i32.const 656) (i32.const 744) (local.get $inv)))
988
+ (i32.shl (i32.div_u (local.get $i) (i32.const 16)) (i32.const 2))))
989
+ (i32.shl (i32.rem_u (local.get $i) (i32.const 16)) (i32.const 1)))) (i64.const 3))
990
+ (i64.extend_i32_u (local.get $inv))))
991
+ (local.set $a (i64.add (local.get $rLo) (local.get $e)))
992
+ (local.set $rHi (i64.add (local.get $rHi) (i64.extend_i32_u (i64.lt_u (local.get $a) (local.get $rLo)))))
993
+ (i64.store (local.get $out) (local.get $a))
994
+ (i64.store (i32.add (local.get $out) (i32.const 8)) (local.get $rHi))
995
+ (local.get $out))`
996
+ }
997
+
998
+ // divisible by 5^p?
999
+ ctx.core.stdlib['__ryu_pow5div'] = `(func $__ryu_pow5div (param $v i64) (param $p i32) (result i32)
1000
+ (block $out (loop $l
1001
+ (br_if $out (i32.le_s (local.get $p) (i32.const 0)))
1002
+ (if (i64.ne (i64.rem_u (local.get $v) (i64.const 5)) (i64.const 0)) (then (return (i32.const 0))))
1003
+ (local.set $v (i64.div_u (local.get $v) (i64.const 5)))
1004
+ (local.set $p (i32.sub (local.get $p) (i32.const 1)))
1005
+ (br $l)))
1006
+ (i32.const 1))`
1007
+
1008
+ // Shortest-round-trip ToString(number). Transcribes d2s.c's d2d (same local
1009
+ // names where possible: mv/vp/vr/vm, q, e10, acceptBounds≡$even) and renders
1010
+ // per ES Number::toString: n = e10+len is the decimal-point position; k≤n≤21
1011
+ // plain integer, 0<n≤21 embedded point, -6<n≤0 leading zeros, else d.dddde±k.
1012
+ ctx.core.stdlib['__ftoa_shortest'] = () => `(func $__ftoa_shortest (param $val f64) (result f64)
1013
+ (local $bits i64) (local $ieeeM i64) (local $ieeeE i32)
1014
+ (local $e2 i32) (local $m2 i64) (local $even i32) (local $mmShift i64) (local $mv i64)
1015
+ (local $vr i64) (local $vp i64) (local $vm i64) (local $h0 i64) (local $t i64) (local $d10 i64)
1016
+ (local $e10 i32) (local $q i32) (local $k i32) (local $sh i32) (local $tbl i32)
1017
+ (local $vmTZ i32) (local $vrTZ i32) (local $removed i32) (local $last i32) (local $roundUp i32)
1018
+ (local $out i64) (local $buf i32) (local $scr i32) (local $pos i32) (local $olen i32) (local $n i32) (local $i i32)
1019
+ (if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
1020
+ (if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
1021
+ (if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
1022
+ (local.set $buf (call $__alloc (i32.const 96)))
1023
+ (local.set $scr (i32.add (local.get $buf) (i32.const 64)))
1024
+ (if (f64.eq (local.get $val) (f64.const 0))
1025
+ (then
1026
+ (i32.store8 (local.get $buf) (i32.const 48))
1027
+ (return (call $__mkstr (local.get $buf) (i32.const 1)))))
1028
+ (local.set $bits (i64.reinterpret_f64 (local.get $val)))
1029
+ (if (i64.lt_s (local.get $bits) (i64.const 0))
1030
+ (then
1031
+ (i32.store8 (local.get $buf) (i32.const 45))
1032
+ (local.set $pos (i32.const 1))))
1033
+ (local.set $ieeeM (i64.and (local.get $bits) (i64.const 0xFFFFFFFFFFFFF)))
1034
+ (local.set $ieeeE (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 52)) (i64.const 0x7FF))))
1035
+ ;; m2·2^e2 = |val|·2^-2 — two extra bits for the halfway-boundary math
1036
+ (if (i32.eqz (local.get $ieeeE))
1037
+ (then
1038
+ (local.set $e2 (i32.const -1076))
1039
+ (local.set $m2 (local.get $ieeeM)))
1040
+ (else
1041
+ (local.set $e2 (i32.sub (local.get $ieeeE) (i32.const 1077)))
1042
+ (local.set $m2 (i64.or (i64.const 0x10000000000000) (local.get $ieeeM)))))
1043
+ (local.set $even (i64.eqz (i64.and (local.get $m2) (i64.const 1))))
1044
+ (local.set $mv (i64.shl (local.get $m2) (i64.const 2)))
1045
+ (local.set $mmShift (i64.extend_i32_u (i32.or
1046
+ (i64.ne (local.get $ieeeM) (i64.const 0))
1047
+ (i32.le_s (local.get $ieeeE) (i32.const 1)))))
1048
+ (if (i32.ge_s (local.get $e2) (i32.const 0))
1049
+ (then
1050
+ ;; q = log10Pow2(e2) - (e2 > 3); shift = -e2 + q + 125 + (pow5bits(q)-1)
1051
+ (local.set $q (i32.sub
1052
+ (i32.shr_u (i32.mul (local.get $e2) (i32.const 78913)) (i32.const 18))
1053
+ (i32.gt_s (local.get $e2) (i32.const 3))))
1054
+ (local.set $e10 (local.get $q))
1055
+ (local.set $sh (i32.add
1056
+ (i32.add (i32.sub (local.get $q) (local.get $e2)) (i32.const 125))
1057
+ (i32.shr_u (i32.mul (local.get $q) (i32.const 1217359)) (i32.const 19))))
1058
+ (local.set $tbl (call $__ryu_pow5 (local.get $q) (i32.const 1) (i32.add (local.get $buf) (i32.const 48))))
1059
+ (local.set $vr (call $__ryu_mulshift (local.get $mv) (local.get $tbl) (local.get $sh)))
1060
+ (local.set $vp (call $__ryu_mulshift (i64.add (local.get $mv) (i64.const 2)) (local.get $tbl) (local.get $sh)))
1061
+ (local.set $vm (call $__ryu_mulshift (i64.sub (i64.sub (local.get $mv) (i64.const 1)) (local.get $mmShift)) (local.get $tbl) (local.get $sh)))
1062
+ (if (i32.le_u (local.get $q) (i32.const 21))
1063
+ (then
1064
+ (if (i64.eqz (i64.rem_u (local.get $mv) (i64.const 5)))
1065
+ (then (local.set $vrTZ (call $__ryu_pow5div (local.get $mv) (local.get $q))))
1066
+ (else
1067
+ (if (local.get $even)
1068
+ (then (local.set $vmTZ (call $__ryu_pow5div (i64.sub (i64.sub (local.get $mv) (i64.const 1)) (local.get $mmShift)) (local.get $q))))
1069
+ (else (local.set $vp (i64.sub (local.get $vp)
1070
+ (i64.extend_i32_u (call $__ryu_pow5div (i64.add (local.get $mv) (i64.const 2)) (local.get $q))))))))))))
1071
+ (else
1072
+ ;; q = log10Pow5(-e2) - (-e2 > 1); i = -e2-q (in $k); shift = q - (pow5bits(i)-125)
1073
+ (local.set $q (i32.sub
1074
+ (i32.shr_u (i32.mul (i32.sub (i32.const 0) (local.get $e2)) (i32.const 732923)) (i32.const 20))
1075
+ (i32.gt_s (i32.sub (i32.const 0) (local.get $e2)) (i32.const 1))))
1076
+ (local.set $e10 (i32.add (local.get $q) (local.get $e2)))
1077
+ (local.set $k (i32.sub (i32.sub (i32.const 0) (local.get $e2)) (local.get $q)))
1078
+ (local.set $sh (i32.add (i32.sub (local.get $q)
1079
+ (i32.add (i32.shr_u (i32.mul (local.get $k) (i32.const 1217359)) (i32.const 19)) (i32.const 1)))
1080
+ (i32.const 125)))
1081
+ (local.set $tbl (call $__ryu_pow5 (local.get $k) (i32.const 0) (i32.add (local.get $buf) (i32.const 48))))
1082
+ (local.set $vr (call $__ryu_mulshift (local.get $mv) (local.get $tbl) (local.get $sh)))
1083
+ (local.set $vp (call $__ryu_mulshift (i64.add (local.get $mv) (i64.const 2)) (local.get $tbl) (local.get $sh)))
1084
+ (local.set $vm (call $__ryu_mulshift (i64.sub (i64.sub (local.get $mv) (i64.const 1)) (local.get $mmShift)) (local.get $tbl) (local.get $sh)))
1085
+ (if (i32.le_u (local.get $q) (i32.const 1))
1086
+ (then
1087
+ (local.set $vrTZ (i32.const 1))
1088
+ (if (local.get $even)
1089
+ (then (local.set $vmTZ (i64.eq (local.get $mmShift) (i64.const 1))))
1090
+ (else (local.set $vp (i64.sub (local.get $vp) (i64.const 1))))))
1091
+ (else
1092
+ (if (i32.lt_u (local.get $q) (i32.const 63))
1093
+ (then (local.set $vrTZ (i64.eqz (i64.and (local.get $mv)
1094
+ (i64.sub (i64.shl (i64.const 1) (i64.extend_i32_u (local.get $q))) (i64.const 1)))))))))))
1095
+ ;; shortest digits within [vm, vp]
1096
+ (if (i32.or (local.get $vmTZ) (local.get $vrTZ))
1097
+ (then
1098
+ ;; rare general path: tracks trailing zeros for exact ties
1099
+ (block $g1 (loop $gl1
1100
+ (local.set $t (i64.div_u (local.get $vp) (i64.const 10)))
1101
+ (local.set $d10 (i64.div_u (local.get $vm) (i64.const 10)))
1102
+ (br_if $g1 (i64.le_u (local.get $t) (local.get $d10)))
1103
+ (local.set $vmTZ (i32.and (local.get $vmTZ)
1104
+ (i64.eqz (i64.sub (local.get $vm) (i64.mul (local.get $d10) (i64.const 10))))))
1105
+ (local.set $vrTZ (i32.and (local.get $vrTZ) (i32.eqz (local.get $last))))
1106
+ (local.set $h0 (i64.div_u (local.get $vr) (i64.const 10)))
1107
+ (local.set $last (i32.wrap_i64 (i64.sub (local.get $vr) (i64.mul (local.get $h0) (i64.const 10)))))
1108
+ (local.set $vr (local.get $h0))
1109
+ (local.set $vp (local.get $t))
1110
+ (local.set $vm (local.get $d10))
1111
+ (local.set $removed (i32.add (local.get $removed) (i32.const 1)))
1112
+ (br $gl1)))
1113
+ (if (local.get $vmTZ)
1114
+ (then (block $g2 (loop $gl2
1115
+ (local.set $d10 (i64.div_u (local.get $vm) (i64.const 10)))
1116
+ (br_if $g2 (i64.ne (i64.sub (local.get $vm) (i64.mul (local.get $d10) (i64.const 10))) (i64.const 0)))
1117
+ (local.set $vrTZ (i32.and (local.get $vrTZ) (i32.eqz (local.get $last))))
1118
+ (local.set $h0 (i64.div_u (local.get $vr) (i64.const 10)))
1119
+ (local.set $last (i32.wrap_i64 (i64.sub (local.get $vr) (i64.mul (local.get $h0) (i64.const 10)))))
1120
+ (local.set $vr (local.get $h0))
1121
+ (local.set $vp (i64.div_u (local.get $vp) (i64.const 10)))
1122
+ (local.set $vm (local.get $d10))
1123
+ (local.set $removed (i32.add (local.get $removed) (i32.const 1)))
1124
+ (br $gl2)))))
1125
+ ;; exact .5 tail rounds to even
1126
+ (if (i32.and (i32.and (local.get $vrTZ) (i32.eq (local.get $last) (i32.const 5)))
1127
+ (i64.eqz (i64.and (local.get $vr) (i64.const 1))))
1128
+ (then (local.set $last (i32.const 4))))
1129
+ (local.set $out (i64.add (local.get $vr) (i64.extend_i32_u (i32.or
1130
+ (i32.and (i64.eq (local.get $vr) (local.get $vm))
1131
+ (i32.or (i32.eqz (local.get $even)) (i32.eqz (local.get $vmTZ))))
1132
+ (i32.ge_s (local.get $last) (i32.const 5)))))))
1133
+ (else
1134
+ ;; common fast path (~99.3%): two digits at a time first
1135
+ (local.set $t (i64.div_u (local.get $vp) (i64.const 100)))
1136
+ (local.set $d10 (i64.div_u (local.get $vm) (i64.const 100)))
1137
+ (if (i64.gt_u (local.get $t) (local.get $d10))
1138
+ (then
1139
+ (local.set $h0 (i64.div_u (local.get $vr) (i64.const 100)))
1140
+ (local.set $roundUp (i64.ge_u (i64.sub (local.get $vr) (i64.mul (local.get $h0) (i64.const 100))) (i64.const 50)))
1141
+ (local.set $vr (local.get $h0))
1142
+ (local.set $vp (local.get $t))
1143
+ (local.set $vm (local.get $d10))
1144
+ (local.set $removed (i32.add (local.get $removed) (i32.const 2)))))
1145
+ (block $f1 (loop $fl1
1146
+ (local.set $t (i64.div_u (local.get $vp) (i64.const 10)))
1147
+ (local.set $d10 (i64.div_u (local.get $vm) (i64.const 10)))
1148
+ (br_if $f1 (i64.le_u (local.get $t) (local.get $d10)))
1149
+ (local.set $h0 (i64.div_u (local.get $vr) (i64.const 10)))
1150
+ (local.set $roundUp (i64.ge_u (i64.sub (local.get $vr) (i64.mul (local.get $h0) (i64.const 10))) (i64.const 5)))
1151
+ (local.set $vr (local.get $h0))
1152
+ (local.set $vp (local.get $t))
1153
+ (local.set $vm (local.get $d10))
1154
+ (local.set $removed (i32.add (local.get $removed) (i32.const 1)))
1155
+ (br $fl1)))
1156
+ (local.set $out (i64.add (local.get $vr) (i64.extend_i32_u
1157
+ (i32.or (i64.eq (local.get $vr) (local.get $vm)) (local.get $roundUp)))))))
1158
+ (local.set $e10 (i32.add (local.get $e10) (local.get $removed)))
1159
+ ;; digits, least-significant first, into scratch
1160
+ (local.set $t (local.get $out))
1161
+ (block $dd (loop $dl
1162
+ (local.set $h0 (i64.div_u (local.get $t) (i64.const 10)))
1163
+ (i32.store8 (i32.add (local.get $scr) (local.get $olen))
1164
+ (i32.add (i32.const 48) (i32.wrap_i64 (i64.sub (local.get $t) (i64.mul (local.get $h0) (i64.const 10))))))
1165
+ (local.set $olen (i32.add (local.get $olen) (i32.const 1)))
1166
+ (local.set $t (local.get $h0))
1167
+ (br_if $dd (i64.eqz (local.get $t)))
1168
+ (br $dl)))
1169
+ ;; ES notation: n = decimal-point position
1170
+ (local.set $n (i32.add (local.get $e10) (local.get $olen)))
1171
+ (if (i32.and (i32.le_s (local.get $olen) (local.get $n)) (i32.le_s (local.get $n) (i32.const 21)))
1172
+ (then
1173
+ (local.set $i (i32.const 0))
1174
+ (block $b1d (loop $b1l
1175
+ (br_if $b1d (i32.ge_s (local.get $i) (local.get $olen)))
1176
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
1177
+ (i32.load8_u (i32.add (local.get $scr) (i32.sub (i32.sub (local.get $olen) (i32.const 1)) (local.get $i)))))
1178
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1179
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1180
+ (br $b1l)))
1181
+ (block $z1d (loop $z1l
1182
+ (br_if $z1d (i32.ge_s (local.get $i) (local.get $n)))
1183
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 48))
1184
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1185
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1186
+ (br $z1l))))
1187
+ (else (if (i32.and (i32.gt_s (local.get $n) (i32.const 0)) (i32.le_s (local.get $n) (i32.const 21)))
1188
+ (then
1189
+ (local.set $i (i32.const 0))
1190
+ (block $b2d (loop $b2l
1191
+ (br_if $b2d (i32.ge_s (local.get $i) (local.get $olen)))
1192
+ (if (i32.eq (local.get $i) (local.get $n))
1193
+ (then
1194
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
1195
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))))
1196
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
1197
+ (i32.load8_u (i32.add (local.get $scr) (i32.sub (i32.sub (local.get $olen) (i32.const 1)) (local.get $i)))))
1198
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1199
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1200
+ (br $b2l))))
1201
+ (else (if (i32.and (i32.gt_s (local.get $n) (i32.const -6)) (i32.le_s (local.get $n) (i32.const 0)))
1202
+ (then
1203
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 48))
1204
+ (i32.store8 (i32.add (local.get $buf) (i32.add (local.get $pos) (i32.const 1))) (i32.const 46))
1205
+ (local.set $pos (i32.add (local.get $pos) (i32.const 2)))
1206
+ (local.set $i (i32.const 0))
1207
+ (block $z3d (loop $z3l
1208
+ (br_if $z3d (i32.ge_s (local.get $i) (i32.sub (i32.const 0) (local.get $n))))
1209
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 48))
1210
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1211
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1212
+ (br $z3l)))
1213
+ (local.set $i (i32.const 0))
1214
+ (block $b3d (loop $b3l
1215
+ (br_if $b3d (i32.ge_s (local.get $i) (local.get $olen)))
1216
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
1217
+ (i32.load8_u (i32.add (local.get $scr) (i32.sub (i32.sub (local.get $olen) (i32.const 1)) (local.get $i)))))
1218
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1219
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1220
+ (br $b3l))))
1221
+ (else
1222
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
1223
+ (i32.load8_u (i32.add (local.get $scr) (i32.sub (local.get $olen) (i32.const 1)))))
1224
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1225
+ (if (i32.gt_s (local.get $olen) (i32.const 1))
1226
+ (then
1227
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
1228
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1229
+ (local.set $i (i32.const 1))
1230
+ (block $b4d (loop $b4l
1231
+ (br_if $b4d (i32.ge_s (local.get $i) (local.get $olen)))
1232
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
1233
+ (i32.load8_u (i32.add (local.get $scr) (i32.sub (i32.sub (local.get $olen) (i32.const 1)) (local.get $i)))))
1234
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1235
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1236
+ (br $b4l)))))
1237
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 101))
1238
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1239
+ (local.set $n (i32.sub (local.get $n) (i32.const 1)))
1240
+ (if (i32.lt_s (local.get $n) (i32.const 0))
1241
+ (then
1242
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 45))
1243
+ (local.set $n (i32.sub (i32.const 0) (local.get $n))))
1244
+ (else (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 43))))
1245
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
1246
+ (local.set $pos (i32.add (local.get $pos)
1247
+ (call $__itoa (local.get $n) (i32.add (local.get $buf) (local.get $pos)))))))))))
1248
+ (call $__mkstr (local.get $buf) (local.get $pos)))`
1249
+
1250
+
880
1251
  // === Number constants ===
881
1252
 
882
1253
  // Each folds to inline (f64.const …), no stdlib dep. Written out (not a table
@@ -951,21 +1322,30 @@ export default (ctx) => {
951
1322
  (local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
952
1323
  (local $digit i32) (local $seen i32) (local $f f64)
953
1324
  (local $acc i64) (local $rad i64) (local $ovf i32) (local $exp i32) (local $sticky i32) (local $k i32) (local $e i32)
1325
+ ;; Invalid radix (nonzero and outside 2..36 after ToInt32) → NaN regardless of input.
1326
+ (if (i32.and (i32.ne (local.get $radix) (i32.const 0))
1327
+ (i32.or (i32.lt_s (local.get $radix) (i32.const 2)) (i32.gt_s (local.get $radix) (i32.const 36))))
1328
+ (then (return (f64.const nan))))
954
1329
  (local.set $f (f64.reinterpret_i64 (local.get $str)))
955
- ;; If input is a number, just truncate
956
- (if (f64.eq (local.get $f) (local.get $f)) (then (return (f64.trunc (local.get $f)))))
1330
+ ;; Number input takes ToString like any other value. In the plain-decimal range
1331
+ ;; (finite, 1e-6 |x| < 1e21, or ±0) ToString has no exponent, so parsing its
1332
+ ;; leading digits IS trunc — keep that as the fast path. Outside it ("1e+21",
1333
+ ;; "1e-7", "Infinity") route through the real formatter: parseInt(1e21) is 1,
1334
+ ;; parseInt(Infinity) is NaN.
1335
+ (if (f64.eq (local.get $f) (local.get $f)) (then
1336
+ ;; ±0 → +0: ToString(-0) is "0", no sign survives.
1337
+ (if (f64.eq (local.get $f) (f64.const 0)) (then (return (f64.const 0))))
1338
+ (if (i32.and (f64.lt (f64.abs (local.get $f)) (f64.const 1e21))
1339
+ (f64.ge (f64.abs (local.get $f)) (f64.const 0.000001)))
1340
+ (then (return (f64.trunc (local.get $f)))))
1341
+ (local.set $str (call $__to_str (local.get $str)))))
957
1342
  ;; If NaN-boxed but not a string → return NaN
958
1343
  (if (i32.ne (call $__ptr_type (local.get $str)) (i32.const 4))
959
1344
  (then (return (f64.const nan))))
960
1345
  (local.set $off (call $__ptr_offset (local.get $str)))
961
1346
  (local.set $len (call $__str_byteLen (local.get $str)))
962
- (local.set $i (i32.const 0))
963
- ;; Skip whitespace
964
- (block $ws (loop $wsl
965
- (br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
966
- (br_if $ws (i32.gt_s (call $__char_at (local.get $str) (local.get $i)) (i32.const 32)))
967
- (local.set $i (i32.add (local.get $i) (i32.const 1)))
968
- (br $wsl)))
1347
+ ;; Skip StrWhiteSpace (UTF-8-decoded: NBSP, LS/PS, Zs — not just ASCII).
1348
+ (local.set $i (call $__skipws (local.get $str) (i32.const 0) (local.get $len)))
969
1349
  ;; Sign
970
1350
  (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
971
1351
  (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 45)))
@@ -1309,12 +1689,8 @@ export default (ctx) => {
1309
1689
  (then (return (f64.const nan))))))
1310
1690
  (local.set $len (call $__str_byteLen (local.get $v)))
1311
1691
  ${SBASE_INIT}
1312
- ;; Skip leading whitespace.
1313
- (block $ws (loop $wsl
1314
- (br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
1315
- (br_if $ws (i32.gt_s ${chAt('(local.get $i)')} (i32.const 32)))
1316
- (local.set $i (i32.add (local.get $i) (i32.const 1)))
1317
- (br $wsl)))
1692
+ ;; Skip leading StrWhiteSpace (UTF-8-decoded: NBSP, LS/PS, Zs — not just ASCII).
1693
+ (local.set $i (call $__skipws (local.get $v) (i32.const 0) (local.get $len)))
1318
1694
  ;; Sign.
1319
1695
  (if (i32.eq ${chAtSafe('(local.get $i)')} (i32.const 45))
1320
1696
  (then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
@@ -1339,28 +1715,21 @@ export default (ctx) => {
1339
1715
  // (string already, number/object ToPrimitive) stay out of scope per the runner.
1340
1716
  const strInputI64 = (x) => valTypeOf(x) === VAL.BOOL ? asI64(bool(x)) : asI64(emit(x))
1341
1717
 
1718
+ // Native for EVERY host (formerly host-imported off-wasi): the wasm-side
1719
+ // parsers are exact (u64 round-once accumulation / Eisel-Lemire), while a
1720
+ // host round-trip re-decodes the string box in the embedder — the bench
1721
+ // runner's decoder mishandled SSO lanes and slice views, silently corrupting
1722
+ // watr's parseInt(hex) calls. Self-contained also means browsers/shells
1723
+ // need no env.parseInt/parseFloat import at all.
1342
1724
  ctx.core.emit['Number.parseInt'] = (x, radix) => {
1343
- if (ctx.transform.host === 'wasi') {
1344
- inc('__parseInt')
1345
- const radixIR = radix == null ? ['i32.const', 0] : toI32(toNumF64(radix, emit(radix)))
1346
- return typed(['call', '$__parseInt', strInputI64(x), radixIR], 'f64')
1347
- }
1348
- needParseInt()
1725
+ inc('__parseInt')
1349
1726
  const radixIR = radix == null ? ['i32.const', 0] : toI32(toNumF64(radix, emit(radix)))
1350
1727
  return typed(['call', '$__parseInt', strInputI64(x), radixIR], 'f64')
1351
1728
  }
1352
1729
  ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
1353
- const needParseFloat = () => hostImport('env', 'parseFloat',
1354
- ['func', '$__parseFloat', ['param', 'i64'], ['result', 'f64']])
1355
- const needParseInt = () => hostImport('env', 'parseInt',
1356
- ['func', '$__parseInt', ['param', 'i64'], ['param', 'i32'], ['result', 'f64']])
1357
1730
 
1358
1731
  ctx.core.emit['Number.parseFloat'] = (x) => {
1359
- if (ctx.transform.host === 'wasi') {
1360
- inc('__parseFloat')
1361
- return typed(['call', '$__parseFloat', strInputI64(x)], 'f64')
1362
- }
1363
- needParseFloat()
1732
+ inc('__parseFloat')
1364
1733
  return typed(['call', '$__parseFloat', strInputI64(x)], 'f64')
1365
1734
  }
1366
1735
  ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']