jz 0.8.0 → 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.
- package/README.md +56 -9
- package/bench/README.md +121 -50
- package/bench/bench.svg +27 -27
- package/cli.js +3 -1
- package/dist/interop.js +1 -1
- package/dist/jz.js +7541 -6178
- package/index.js +165 -74
- package/interop.js +189 -17
- package/jzify/arguments.js +32 -1
- package/jzify/async.js +409 -0
- package/jzify/classes.js +136 -18
- package/jzify/generators.js +639 -0
- package/jzify/hoist-vars.js +73 -1
- package/jzify/index.js +123 -4
- package/jzify/names.js +1 -0
- package/jzify/transform.js +239 -1
- package/layout.js +48 -3
- package/module/array.js +318 -43
- package/module/atomics.js +144 -0
- package/module/collection.js +1429 -155
- package/module/core.js +431 -40
- package/module/date.js +142 -120
- package/module/fs.js +144 -0
- package/module/function.js +6 -3
- package/module/index.js +4 -1
- package/module/json.js +270 -49
- package/module/math.js +1032 -145
- package/module/number.js +532 -163
- package/module/object.js +353 -95
- package/module/regex.js +157 -7
- package/module/schema.js +100 -2
- package/module/string.js +428 -93
- package/module/typedarray.js +264 -72
- package/module/web.js +36 -0
- package/package.json +5 -5
- package/src/abi/string.js +82 -11
- package/src/ast.js +11 -5
- package/src/autoload.js +28 -1
- package/src/bridge.js +7 -2
- package/src/compile/analyze-scans.js +22 -7
- package/src/compile/analyze.js +136 -30
- package/src/compile/dyn-closure-tables.js +277 -0
- package/src/compile/emit-assign.js +281 -15
- package/src/compile/emit.js +1055 -70
- package/src/compile/index.js +277 -29
- package/src/compile/infer.js +42 -4
- package/src/compile/inplace-store.js +329 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/narrow.js +555 -18
- package/src/compile/peel-stencil.js +5 -0
- package/src/compile/plan/index.js +45 -3
- package/src/compile/plan/inline.js +8 -1
- package/src/compile/plan/literals.js +39 -0
- package/src/compile/plan/scope.js +430 -23
- package/src/compile/program-facts.js +732 -38
- package/src/ctx.js +64 -9
- package/src/helper-counters.js +7 -1
- package/src/ir.js +113 -5
- package/src/kind-traits.js +66 -3
- package/src/kind.js +84 -12
- package/src/op-policy.js +7 -4
- package/src/optimize/index.js +1179 -704
- package/src/optimize/recurse.js +2 -2
- package/src/optimize/vectorize.js +982 -67
- package/src/prepare/index.js +809 -67
- package/src/prepare/math-kernel.js +331 -0
- package/src/prepare/pre-eval.js +714 -0
- package/src/snapshot.js +194 -0
- package/src/type.js +1170 -56
- package/src/wat/assemble.js +403 -65
- package/src/wat/codegen.js +74 -14
- package/transform.js +113 -4
- package/wasi.js +3 -0
package/module/math.js
CHANGED
|
@@ -14,10 +14,15 @@
|
|
|
14
14
|
|
|
15
15
|
import { typed, asF64, asI32, toI32, toNumF64, temp, arrayLoop, isLit, litVal, isPureIR } from '../src/ir.js'
|
|
16
16
|
import { emit, emitter, reg, deps, dual, tag, wat, hostImport } from '../src/bridge.js'
|
|
17
|
-
import { inc, declGlobal } from '../src/ctx.js'
|
|
18
|
-
import { repOf } from '../src/reps.js'
|
|
17
|
+
import { inc, declGlobal, err } from '../src/ctx.js'
|
|
18
|
+
import { repOf, VAL } from '../src/reps.js'
|
|
19
|
+
import { valTypeOf } from '../src/kind.js'
|
|
19
20
|
|
|
20
21
|
export default (ctx) => {
|
|
22
|
+
// `**`/Math.pow kernel select — see the single authoritative comment block just above
|
|
23
|
+
// `emitPow` (below) for full crPow/approxPow semantics. Read once here; every other site
|
|
24
|
+
// (deps table, pow_core/pow_fold/pow_fold_v dual bodies) just branches on this.
|
|
25
|
+
const crPow = !!ctx.transform.optimize?.crPow
|
|
21
26
|
// Math.random seeding. DEFAULT: entropy-seeded once from the host on first use (crypto under
|
|
22
27
|
// host:'js', `random_get` under WASI), so randomness "just works" and isn't silently reproducible.
|
|
23
28
|
// `randomSeed: <n>` picks a fixed seed for a reproducible sequence; `true` forces entropy explicitly.
|
|
@@ -36,8 +41,17 @@ export default (ctx) => {
|
|
|
36
41
|
'math.expm1': ['math.exp'],
|
|
37
42
|
'math.log2': ['math.log'],
|
|
38
43
|
'math.log1p': ['math.log'],
|
|
39
|
-
'math.pow': ['math.
|
|
40
|
-
'math.
|
|
44
|
+
'math.pow': ['math.pow_core'],
|
|
45
|
+
'math.pow_core': crPow ? ['math.pow_transcend'] : ['math.pow_scalbn'],
|
|
46
|
+
'math.pow_scalbn': [],
|
|
47
|
+
// math.pow_transcend/math.pow_fold only exist (are registered as wat() templates below) when
|
|
48
|
+
// optimize.crPow is set — see the authoritative comment above emitPow. Declaring their deps
|
|
49
|
+
// unconditionally here is harmless when crPow is off: nothing ever inc()s 'math.pow_fold' in
|
|
50
|
+
// that mode (emitPow's const-exponent branch calls $math.exp/$math.log instead), so this edge
|
|
51
|
+
// is simply never traversed.
|
|
52
|
+
'math.pow_transcend': ['math.pow_scalbn'],
|
|
53
|
+
'math.pow_fold': ['math.pow_transcend'],
|
|
54
|
+
'math.asin': [],
|
|
41
55
|
'math.acos': ['math.asin'],
|
|
42
56
|
'math.atan2': ['math.atan'],
|
|
43
57
|
'math.sinh': ['math.exp'],
|
|
@@ -46,7 +60,8 @@ export default (ctx) => {
|
|
|
46
60
|
'math.asinh': ['math.isFinite', 'math.log'],
|
|
47
61
|
'math.acosh': ['math.log'],
|
|
48
62
|
'math.atanh': ['math.log'],
|
|
49
|
-
'math.cbrt': ['math.isFinite'
|
|
63
|
+
'math.cbrt': ['math.isFinite'],
|
|
64
|
+
'math.fifthroot': ['math.isFinite'],
|
|
50
65
|
'math.sumPrecise': ['__ptr_offset', '__len', '__alloc'],
|
|
51
66
|
})
|
|
52
67
|
// Helpers: all math ops take f64 and return f64. Args go through ToNumber
|
|
@@ -247,6 +262,8 @@ export default (ctx) => {
|
|
|
247
262
|
], 'f64')
|
|
248
263
|
}
|
|
249
264
|
ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64', toNumF64(a, emit(a))]], 'f64')
|
|
265
|
+
// ES2025 Math.f16round — no wasm f16 ops, so round in software (exactly).
|
|
266
|
+
reg('math.f16round', ['math.f16round'], a => fn('math.f16round', a))
|
|
250
267
|
|
|
251
268
|
// Sign
|
|
252
269
|
reg('math.sign', ['math.sign'], a => fn('math.sign', a))
|
|
@@ -349,6 +366,10 @@ export default (ctx) => {
|
|
|
349
366
|
const exp2Call = emitter(['math.exp2'], (exp) => typed(['call', '$math.exp2', toNumF64(exp, emit(exp))], 'f64'))
|
|
350
367
|
// Shared pow/** lowering.
|
|
351
368
|
const emitPow = (a, b, allowExpPos) => {
|
|
369
|
+
// BigInt ** is real JS (2n ** 3n === 8n) but unimplemented — the f64 pow
|
|
370
|
+
// pipeline would reinterpret raw i64 bits. Reject instead of silent garbage.
|
|
371
|
+
if (valTypeOf(a) === VAL.BIGINT || valTypeOf(b) === VAL.BIGINT)
|
|
372
|
+
err('BigInt exponentiation (`**`) not supported — use a multiply loop or Number(x)')
|
|
352
373
|
const n = constInt(b)
|
|
353
374
|
if (n !== null && Math.abs(n) <= POW_FOLD_MAX) return foldPow(a, n)
|
|
354
375
|
if (constNum(b) === 0.5) { const ir = typed(['f64.sqrt', toNumF64(a, emit(a))], 'f64'); return nonNegF64(ir[1]) ? ir : canon(ir) }
|
|
@@ -360,21 +381,74 @@ export default (ctx) => {
|
|
|
360
381
|
// We emit, check, and if not foldable, the emitted IR is used by the fallthrough paths.
|
|
361
382
|
const irA = toNumF64(a, emit(a)), irB = toNumF64(b, emit(b))
|
|
362
383
|
if (isLit(irA) && isLit(irB)) return typed(['f64.const', Math.pow(litVal(irA), litVal(irB))], 'f64')
|
|
363
|
-
// Constant non-integer exponent c: inline Math.pow(x,c) as
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
384
|
+
// Constant non-integer exponent c: inline Math.pow(x,c) as a fast fold instead of the
|
|
385
|
+
// general $math.pow. Skipping the ~15-branch pow special-case ladder (only the x-dependent
|
|
386
|
+
// slice — NaN/±Inf/0/negative — is needed; every y-branch is statically dead since c is a
|
|
387
|
+
// known finite non-0/1/±0.5/integer literal) + the call frame is still a per-pixel win on
|
|
388
|
+
// the gamma curves (v**0.45, a**(1/2.4)) that dominate tone-mapping, and a program whose
|
|
389
|
+
// only pow is folded this way never pulls the general $math.pow/pow_core. Integers stay on
|
|
390
|
+
// $math.pow (its square-and-multiply path is exact, not transcendental); ±0.5 stays sqrt
|
|
391
|
+
// (also exact, correctly rounded by hardware).
|
|
392
|
+
//
|
|
393
|
+
// KERNEL SELECT — `optimize.crPow` (default OFF) picks how a constant non-integer exponent
|
|
394
|
+
// lowers:
|
|
395
|
+
// OFF (DEFAULT, today's shipped behavior, bit-for-bit): exp(c·log(x)) — that IS $math.pow's
|
|
396
|
+
// own non-integer tail (the final line of $math.pow below), so it is BIT-IDENTICAL to the
|
|
397
|
+
// call for every finite x and for x ∈ {±0, +∞, NaN}: log+exp carry the edges (log(NaN)=NaN,
|
|
398
|
+
// log(0)=−∞, log(<0)=NaN, log(+∞)=+∞; exp(±∞)=∞/0). The ONE divergence is x=−∞: this
|
|
399
|
+
// yields NaN where Math.pow gives ±∞ — the same deliberate boundary trade jz already makes
|
|
400
|
+
// for `(−∞)**0.5` (see the sqrt fold above); −∞ is never a real tone-map/gamma base. The
|
|
401
|
+
// k/5-exponent gammas (sRGB/Rec.709 decode, 2.4/2.2/…) skip log/exp entirely via an
|
|
402
|
+
// UNCONDITIONAL algebraic fifthroot fold (x^(k/5) = x^p·fifthroot(x^r), p=⌊c⌋, r=5c−5p ∈
|
|
403
|
+
// 1..4, ~3.6e-10 rel err, not correctly rounded, measured worst case ~473ulp
|
|
404
|
+
// test/fifthroot-ulp.js) — this was always the plain-build behavior and stays so.
|
|
405
|
+
// ON: the constant exponent instead routes through $math.pow_fold, which shares
|
|
406
|
+
// $math.pow_transcend's two-phase Ziv dd/td kernel with the runtime-y path $math.pow_core
|
|
407
|
+
// uses when crPow is on (see $math.pow_transcend's own comment for the algorithm) —
|
|
408
|
+
// CORRECTLY ROUNDED (0 misrounds on the 5152-vector CORE-MATH-class gate,
|
|
409
|
+
// test/pow-cr.js). c needs no pre-split: the shared kernel's multiply is a twoProd-based
|
|
410
|
+
// exact product (Dekker-splits BOTH operands internally), so the call is just x and the
|
|
411
|
+
// f64.const literal c. HONEST COST: measured ~13x the default exp∘log fold's runtime on
|
|
412
|
+
// the gamma-heavy color benches (colorpq 13.6x behind V8 under crPow, vs ~1x today) —
|
|
413
|
+
// correctness has a real price, so this stays opt-in rather than default
|
|
414
|
+
// (`{ optimize: { crPow: true } }`). Under crPow, the fifthroot fast path is ALSO opt-in
|
|
415
|
+
// rather than automatic (`{ optimize: { approxPow: true } }`, default OFF): correctness
|
|
416
|
+
// wins by default once crPow has opted into the correctly-rounded kernel family — a
|
|
417
|
+
// caller who wants both speed AND crPow's runtime-y correctness sets both flags.
|
|
373
418
|
if (isLit(irB)) {
|
|
374
419
|
const c = litVal(irB)
|
|
375
|
-
|
|
420
|
+
// Finite x<0 → NaN to match Math.pow on a non-integer exponent (the exp·log form's
|
|
421
|
+
// log(<0)=NaN). x=-Infinity is its OWN case, not "negative": |x|=Infinity means Math.pow
|
|
422
|
+
// ignores the sign for a non-integer exponent (c > 0 in this branch's guard, so the result
|
|
423
|
+
// is +Infinity). x=+0/-0/+∞/NaN carry correctly through power + fifthroot.
|
|
424
|
+
const fifthrootGate = crPow ? ctx.transform.optimize?.approxPow : true
|
|
425
|
+
if (fifthrootGate && Number.isFinite(c) && c > 0 && c < 5 && !Number.isInteger(c) && Number.isInteger(c * 5)) {
|
|
426
|
+
inc('math.fifthroot')
|
|
427
|
+
const t = temp('pw'), g = get(t)
|
|
428
|
+
const ipow = (k) => k === 1 ? g : k === 2 ? ['f64.mul', g, g]
|
|
429
|
+
: k === 3 ? ['f64.mul', ['f64.mul', g, g], g] : ['f64.mul', ['f64.mul', g, g], ['f64.mul', g, g]] // k ∈ 1..4
|
|
430
|
+
const p = Math.floor(c), r = Math.round(c * 5) - p * 5
|
|
431
|
+
const root = ['call', '$math.fifthroot', ipow(r)]
|
|
432
|
+
const body = p === 0 ? root : ['f64.mul', ipow(p), root]
|
|
433
|
+
return typed(['block', ['result', 'f64'],
|
|
434
|
+
['local.set', `$${t}`, irA],
|
|
435
|
+
['if', ['result', 'f64'], ['f64.eq', g, ['f64.const', '-inf']],
|
|
436
|
+
['then', ['f64.const', 'inf']],
|
|
437
|
+
['else', ['if', ['result', 'f64'], ['f64.lt', g, ['f64.const', 0]],
|
|
438
|
+
['then', ['f64.const', 'nan']], ['else', body]]]]], 'f64')
|
|
439
|
+
}
|
|
440
|
+
if (crPow) {
|
|
441
|
+
if (Number.isFinite(c) && !Number.isInteger(c) && c !== 0.5 && c !== -0.5) {
|
|
442
|
+
inc('math.pow_fold')
|
|
443
|
+
// c needs no hi/lo pre-split: $math.pow_fold shares $math.pow_transcend's kernel,
|
|
444
|
+
// which exact-multiplies via twoProd (Dekker split done ON BOTH operands inside the
|
|
445
|
+
// kernel) rather than fdlibm's manual y1/y2 chop — so a single f64.const suffices.
|
|
446
|
+
return typed(['call', '$math.pow_fold', irA, ['f64.const', c]], 'f64')
|
|
447
|
+
}
|
|
448
|
+
} else if (Number.isFinite(c) && !Number.isInteger(c) && c !== 0.5 && c !== -0.5) {
|
|
376
449
|
return (inc('math.exp'), inc('math.log'),
|
|
377
450
|
typed(['call', '$math.exp', ['f64.mul', irB, ['call', '$math.log', irA]]], 'f64'))
|
|
451
|
+
}
|
|
378
452
|
}
|
|
379
453
|
// base 2 → dedicated 2^y (exp2 is exact for integer y, and skips exp's ×ln2/÷ln2).
|
|
380
454
|
// Every other literal base keeps $math.pow: `exp(y·ln base)` would lose ulps and,
|
|
@@ -428,6 +502,30 @@ export default (ctx) => {
|
|
|
428
502
|
// WAT stdlib implementations
|
|
429
503
|
// ============================================
|
|
430
504
|
|
|
505
|
+
// Round-to-nearest-f16 without double rounding: add-then-subtract s = 1.5·2^(52+k)
|
|
506
|
+
// makes the f64 adder itself round |x| to a multiple of the f16 quantum 2^k,
|
|
507
|
+
// ties-to-even (sum stays in s's binade, so the subtraction is exact). k comes
|
|
508
|
+
// from |x|'s exponent: eu-10 for f16 normals (eu ≥ -14), -24 in the subnormal
|
|
509
|
+
// range. Overflow boundary: |x| ≥ 65520 (= 65504 + half-ulp) → ±∞, per spec.
|
|
510
|
+
wat('math.f16round', `(func $math.f16round (param $x f64) (result f64)
|
|
511
|
+
(local $abs i64) (local $eu i32) (local $s f64)
|
|
512
|
+
(local.set $abs (i64.and (i64.reinterpret_f64 (local.get $x)) (i64.const 0x7FFFFFFFFFFFFFFF)))
|
|
513
|
+
;; NaN, ±Infinity, ±0 pass through
|
|
514
|
+
(if (i64.ge_u (local.get $abs) (i64.const 0x7FF0000000000000)) (then (return (local.get $x))))
|
|
515
|
+
(if (i64.eqz (local.get $abs)) (then (return (local.get $x))))
|
|
516
|
+
(if (f64.ge (f64.reinterpret_i64 (local.get $abs)) (f64.const 65520))
|
|
517
|
+
(then (return (f64.copysign (f64.const inf) (local.get $x)))))
|
|
518
|
+
(local.set $eu (i32.sub (i32.wrap_i64 (i64.shr_u (local.get $abs) (i64.const 52))) (i32.const 1023)))
|
|
519
|
+
(local.set $s (f64.reinterpret_i64 (i64.or
|
|
520
|
+
(i64.shl (i64.extend_i32_s (i32.add
|
|
521
|
+
(select (i32.sub (local.get $eu) (i32.const 10)) (i32.const -24)
|
|
522
|
+
(i32.ge_s (local.get $eu) (i32.const -14)))
|
|
523
|
+
(i32.const 1075))) (i64.const 52))
|
|
524
|
+
(i64.const 0x0008000000000000))))
|
|
525
|
+
(f64.copysign
|
|
526
|
+
(f64.sub (f64.add (f64.reinterpret_i64 (local.get $abs)) (local.get $s)) (local.get $s))
|
|
527
|
+
(local.get $x)))`)
|
|
528
|
+
|
|
431
529
|
wat('math.sign', `(func $math.sign (param $x f64) (result f64)
|
|
432
530
|
;; sign(NaN) = NaN, sign(±0) = ±0 — both pass x through unchanged.
|
|
433
531
|
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
@@ -449,15 +547,11 @@ export default (ctx) => {
|
|
|
449
547
|
// 2^f over the reduced range f ∈ [-0.5, 0.5] for $math.exp2 (rel. err ≤ 6e-9). Lets the
|
|
450
548
|
// base-2 power `2**y` skip the ×ln2 / ÷ln2 round-trip exp(y·ln2) pays — see $math.exp2.
|
|
451
549
|
const EXP2_C = [1, 0.6931472000619209, 0.24022650999918949, 0.05550340682450019, 0.009618048870444599, 0.0013395279077191057, 0.00015463102004723134]
|
|
452
|
-
// Range-reduction constants
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
|
|
457
|
-
// throw the reduced quadrant off (Δ≈0.2 at x≈2267). A string interpolates verbatim, so
|
|
458
|
-
// watr parses the full-precision f64 in both legs. Values are native toString's shortest
|
|
459
|
-
// round-trip reprs of Math.PI, 1/Math.PI, Math.PI/2 — byte-identical to the old output.
|
|
460
|
-
const PI = '3.141592653589793', INV_PI = '0.3183098861837907', HALF_PI = '1.5707963267948966'
|
|
550
|
+
// Range-reduction constants via plain number interpolation: `${number}` now formats
|
|
551
|
+
// through the Ryū shortest-round-trip __ftoa in BOTH legs (host and self-hosted
|
|
552
|
+
// kernel), so the full-precision f64 bakes into the WAT verbatim — the former
|
|
553
|
+
// string-literal workaround for the kernel's 9-digit dtoa is obsolete.
|
|
554
|
+
const PI = Math.PI, INV_PI = 1 / Math.PI, HALF_PI = Math.PI / 2
|
|
461
555
|
|
|
462
556
|
// Round-to-nearest reduction r = x − q·π ∈ [−π/2, π/2], in pure f64 — no int conversion,
|
|
463
557
|
// so it never traps and never saturates. A SECOND pass folds the q·π rounding error back
|
|
@@ -544,15 +638,6 @@ export default (ctx) => {
|
|
|
544
638
|
const horner2 = (cs, v = '$r2') => cs.reduceRight((acc, c, i) =>
|
|
545
639
|
i === cs.length - 1 ? splat(c)
|
|
546
640
|
: `(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
641
|
// Shared reduce → r ∈ [−π/2,π/2] in $r, quadrant parity in $q (branchless, 2 passes).
|
|
557
642
|
const reduce2 = `
|
|
558
643
|
(local.set $q (f64x2.nearest (f64x2.mul (local.get $x) ${splat(INV_PI)})))
|
|
@@ -583,6 +668,27 @@ export default (ctx) => {
|
|
|
583
668
|
(f64x2.splat (call $math.pow (f64x2.extract_lane 0 (local.get $x)) (f64x2.extract_lane 0 (local.get $y))))
|
|
584
669
|
(call $math.pow (f64x2.extract_lane 1 (local.get $x)) (f64x2.extract_lane 1 (local.get $y)))))`, ['math.pow'])
|
|
585
670
|
|
|
671
|
+
// $math.pow_fold_v — SIMD twin of $math.pow_fold, ONLY registered under optimize.crPow (that
|
|
672
|
+
// fold itself only exists then — see the authoritative comment above emitPow). Per-lane scalar
|
|
673
|
+
// repack — BIT-EXACT by construction, no cheap 2-lane polynomial for the branchy fdlibm-style
|
|
674
|
+
// dd/td kernel — and it keeps a constant-exponent-pow-bearing pixel kernel's surrounding f64x2
|
|
675
|
+
// arithmetic vectorized exactly like pow2/atan2_2/hypot_2/cbrt_v/fifthroot_v already do for
|
|
676
|
+
// their own callees. c arrives as v128 (every PPC_CALL2 arg is lifted through the generic splat
|
|
677
|
+
// path — see src/optimize/vectorize.js), but every lane holds the SAME compile-time constant,
|
|
678
|
+
// so extracting lane 0 for both scalar calls is exact. Off crPow, the vectorizer's own
|
|
679
|
+
// const-exponent lift (vectorize.js) uses $math.exp_v/$math.log_v directly instead — no mirror
|
|
680
|
+
// needed here, matching the default exp(c·log(x)) fold's own shape.
|
|
681
|
+
if (crPow) {
|
|
682
|
+
wat('math.pow_fold_v', `(func $math.pow_fold_v (param $x v128) (param $c v128) (result v128)
|
|
683
|
+
(f64x2.replace_lane 1
|
|
684
|
+
(f64x2.splat (call $math.pow_fold
|
|
685
|
+
(f64x2.extract_lane 0 (local.get $x))
|
|
686
|
+
(f64x2.extract_lane 0 (local.get $c))))
|
|
687
|
+
(call $math.pow_fold
|
|
688
|
+
(f64x2.extract_lane 1 (local.get $x))
|
|
689
|
+
(f64x2.extract_lane 1 (local.get $c)))))`, ['math.pow_fold'])
|
|
690
|
+
}
|
|
691
|
+
|
|
586
692
|
// atan2/hypot/log have no cheap 2-lane polynomial (multi-`return` fdlibm bodies), so — like pow2 —
|
|
587
693
|
// each f64x2 mirror computes both lanes with the SCALAR helper and repacks: BIT-EXACT by
|
|
588
694
|
// construction. The per-pixel-color pass only emits these when a truly-2-wide op (sin2/cos2/sqrt)
|
|
@@ -596,13 +702,24 @@ export default (ctx) => {
|
|
|
596
702
|
(f64x2.replace_lane 1
|
|
597
703
|
(f64x2.splat (call $math.hypot (f64x2.extract_lane 0 (local.get $x)) (f64x2.extract_lane 0 (local.get $y))))
|
|
598
704
|
(call $math.hypot (f64x2.extract_lane 1 (local.get $x)) (f64x2.extract_lane 1 (local.get $y)))))`, ['math.hypot'])
|
|
705
|
+
// cbrt/fifthroot: same per-lane scalar repack (their scalar bodies are branchy exponent-split +
|
|
706
|
+
// Newton, no cheap 2-lane poly). BIT-EXACT by construction. Unlocks the Oklab/OkLCh path (3 cbrt
|
|
707
|
+
// per pixel) and the sRGB/Rec.709 `x**(k/5)` gamma so their surrounding f64x2 arithmetic vectorizes.
|
|
708
|
+
wat('math.cbrt_v', `(func $math.cbrt_v (param $x v128) (result v128)
|
|
709
|
+
(f64x2.replace_lane 1
|
|
710
|
+
(f64x2.splat (call $math.cbrt (f64x2.extract_lane 0 (local.get $x))))
|
|
711
|
+
(call $math.cbrt (f64x2.extract_lane 1 (local.get $x)))))`, ['math.cbrt'])
|
|
712
|
+
wat('math.fifthroot_v', `(func $math.fifthroot_v (param $x v128) (result v128)
|
|
713
|
+
(f64x2.replace_lane 1
|
|
714
|
+
(f64x2.splat (call $math.fifthroot (f64x2.extract_lane 0 (local.get $x))))
|
|
715
|
+
(call $math.fifthroot (f64x2.extract_lane 1 (local.get $x)))))`, ['math.fifthroot'])
|
|
599
716
|
// True f64x2 log — both lanes through one fdlibm poly (≈2× over two scalar calls). The HOT path
|
|
600
717
|
// (both lanes a normal finite x>0) mirrors $math.log's normal branch op-for-op: bit-exact (the
|
|
601
718
|
// sqrt2-center conditional becomes a per-lane bitselect; the i32 exponent k becomes an f64 via the
|
|
602
719
|
// 2^52 magic-add, identical to convert_i32_s for |k|≤1075). Any other lane (≤0/∞/NaN/denormal)
|
|
603
720
|
// routes BOTH lanes to the scalar fallback → bit-exact by construction, edges never lose precision.
|
|
604
721
|
wat('math.log_v', `(func $math.log_v (param $x v128) (result v128)
|
|
605
|
-
(local $k v128) (local $m v128) (local $mask v128) (local $
|
|
722
|
+
(local $k v128) (local $m v128) (local $mask v128) (local $s v128) (local $z v128)
|
|
606
723
|
(if (result v128)
|
|
607
724
|
(i64x2.all_true (v128.and
|
|
608
725
|
(f64x2.ge (local.get $x) (f64x2.splat (f64.const 0x1p-1022)))
|
|
@@ -616,18 +733,20 @@ export default (ctx) => {
|
|
|
616
733
|
(local.set $mask (f64x2.ge (local.get $m) (f64x2.splat (f64.const 1.4142135623730951))))
|
|
617
734
|
(local.set $m (v128.bitselect (f64x2.mul (local.get $m) (f64x2.splat (f64.const 0.5))) (local.get $m) (local.get $mask)))
|
|
618
735
|
(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 $
|
|
736
|
+
;; mirrors scalar $math.log op-for-op (same constants/order) → bit-exact lanes
|
|
737
|
+
(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
738
|
(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
739
|
(f64x2.add
|
|
625
740
|
(f64x2.mul (local.get $k) (f64x2.splat (f64.const ${Math.LN2})))
|
|
626
|
-
(f64x2.
|
|
627
|
-
(f64x2.
|
|
628
|
-
(f64x2.
|
|
629
|
-
(f64x2.
|
|
630
|
-
|
|
741
|
+
(f64x2.mul (f64x2.mul (f64x2.splat (f64.const 2.0)) (local.get $s))
|
|
742
|
+
(f64x2.add (f64x2.splat (f64.const 1.0))
|
|
743
|
+
(f64x2.mul (local.get $z)
|
|
744
|
+
(f64x2.add (f64x2.splat (f64.const 0.33333333283005556))
|
|
745
|
+
(f64x2.mul (local.get $z)
|
|
746
|
+
(f64x2.add (f64x2.splat (f64.const 0.20000059590510924))
|
|
747
|
+
(f64x2.mul (local.get $z)
|
|
748
|
+
(f64x2.add (f64x2.splat (f64.const 0.14275490984342690))
|
|
749
|
+
(f64x2.mul (local.get $z) (f64x2.splat (f64.const 0.11663796426848184)))))))))))))
|
|
631
750
|
(else
|
|
632
751
|
(f64x2.replace_lane 1
|
|
633
752
|
(f64x2.splat (call $math.log (f64x2.extract_lane 0 (local.get $x))))
|
|
@@ -718,7 +837,6 @@ export default (ctx) => {
|
|
|
718
837
|
// Edge cases: NaN→NaN, ≤0 distinguishes 0→-Inf, <0→NaN; +Inf passes through.
|
|
719
838
|
wat('math.log', `(func $math.log (param $x f64) (result f64)
|
|
720
839
|
(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
840
|
(if (f64.ne (local.get $x) (local.get $x))
|
|
723
841
|
(then (return (local.get $x))))
|
|
724
842
|
(if (f64.le (local.get $x) (f64.const 0.0))
|
|
@@ -749,27 +867,22 @@ export default (ctx) => {
|
|
|
749
867
|
(then
|
|
750
868
|
(local.set $m (f64.mul (local.get $m) (f64.const 0.5)))
|
|
751
869
|
(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))))
|
|
870
|
+
;; s = (m−1)/(m+1) (|s| ≤ 3−2√2 ≈ 0.172); log(m) = 2s·(1 + z·G(z)), z = s², G a degree-3
|
|
871
|
+
;; minimax in z (Remez, equioscillation 5.8e-10). One short Horner replaces fdlibm's 7-term
|
|
872
|
+
;; even/odd split — ~40% fewer ops, max rel err 1.7e-11 (jz transcendentals target ~1e-9).
|
|
873
|
+
(local.set $s (f64.div (f64.sub (local.get $m) (f64.const 1.0)) (f64.add (local.get $m) (f64.const 1.0))))
|
|
758
874
|
(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
875
|
(f64.add
|
|
769
876
|
(f64.mul (f64.convert_i32_s (local.get $k)) (f64.const ${Math.LN2}))
|
|
770
|
-
(f64.
|
|
771
|
-
(f64.
|
|
772
|
-
(f64.
|
|
877
|
+
(f64.mul (f64.mul (f64.const 2.0) (local.get $s))
|
|
878
|
+
(f64.add (f64.const 1.0)
|
|
879
|
+
(f64.mul (local.get $z)
|
|
880
|
+
(f64.add (f64.const 0.33333333283005556)
|
|
881
|
+
(f64.mul (local.get $z)
|
|
882
|
+
(f64.add (f64.const 0.20000059590510924)
|
|
883
|
+
(f64.mul (local.get $z)
|
|
884
|
+
(f64.add (f64.const 0.14275490984342690)
|
|
885
|
+
(f64.mul (local.get $z) (f64.const 0.11663796426848184)))))))))))))`)
|
|
773
886
|
|
|
774
887
|
wat('math.log2', `(func $math.log2 (param $x f64) (result f64)
|
|
775
888
|
(f64.div (call $math.log (local.get $x)) (f64.const ${Math.LN2})))`)
|
|
@@ -860,6 +973,487 @@ export default (ctx) => {
|
|
|
860
973
|
(f64.mul (call $math.log (local.get $u)) (local.get $x))
|
|
861
974
|
(f64.sub (local.get $u) (f64.const 1.0))))`)
|
|
862
975
|
|
|
976
|
+
|
|
977
|
+
// The entire correctly-rounded kernel below (codegen helpers, breakpoint tables, and the
|
|
978
|
+
// $math.pow_transcend registration itself) is built and registered ONLY when `optimize.crPow`
|
|
979
|
+
// is set — see the authoritative crPow/approxPow comment above `emitPow` for why it's opt-in
|
|
980
|
+
// (honest cost: ~13x the old fold's runtime on gamma-heavy color kernels). Gating the whole
|
|
981
|
+
// section (not just the wat() registration) means a plain build pays zero JS-side cost for
|
|
982
|
+
// table-hex construction / codegen generation, and $math.pow_transcend never enters
|
|
983
|
+
// ctx.core.stdlib at all — so it can't accidentally leak into a default-build's includes set.
|
|
984
|
+
if (crPow) {
|
|
985
|
+
// ============================================
|
|
986
|
+
// Correctly-rounded pow: two-phase Ziv dd/td kernel
|
|
987
|
+
// ============================================
|
|
988
|
+
// $math.pow_transcend(x,y) — x>0 finite, y finite nonzero (the transcendental tail both
|
|
989
|
+
// $math.pow_core (runtime y) and $math.pow_fold (compile-time-constant y) delegate to, once
|
|
990
|
+
// their own special-case ladders rule out NaN/±Inf/±0/x<0/y==0/±1/integer-in-i32-range/y==
|
|
991
|
+
// ±0.5). Ported from a from-scratch double-double/triple-double design (NOT fdlibm's e_pow.c
|
|
992
|
+
// — that algorithm targets ~1ulp, not correct rounding, and the earlier fdlibm-ported
|
|
993
|
+
// $math.pow_core missed 8.2% of the CR vector gate; see test/pow-cr.js), using a Ziv rounding
|
|
994
|
+
// test to promote from cheap double-double (phase 1) to triple-double (phase 2) only when
|
|
995
|
+
// phase 1's own error bound can't certify the final rounding. Design + derivation fully
|
|
996
|
+
// worked out and differentially validated (5152/5152 gate vectors + 26k targeted adversarial
|
|
997
|
+
// + 150k general-random cases, 0 misrounds) in scratchpad/pow/ before this port — see
|
|
998
|
+
// pow_dd.mjs (the reference prototype every WAT line here mirrors 1:1) and its measurement
|
|
999
|
+
// scripts (measure_log2_abs.py, measure_exp2_unscaled.py) for the error-bound derivations
|
|
1000
|
+
// cited below.
|
|
1001
|
+
//
|
|
1002
|
+
// ALGORITHM (both phases share this shape, at k=2 (dd) or k=3 (td) limbs):
|
|
1003
|
+
// 1. log2(x) to k-limb precision: bit-extract x=m·2^kexp (m∈[1,2)), look up the table
|
|
1004
|
+
// breakpoint m0_j nearest m (top-8-mantissa-bit index j, LOG2_TABLE — 256 entries ×
|
|
1005
|
+
// k-limb log2(1+j/256), injected as a linear-memory data table, see injectTable in
|
|
1006
|
+
// src/wat/assemble.js — same mechanism as module/number.js's Eisel-Lemire/Ryū tables),
|
|
1007
|
+
// then log2(x) = kexp + log2(m0_j) + log2(1+r) where r=(m-m0_j)/m0_j (|r|<2^-8, so a
|
|
1008
|
+
// short Horner series converges fast — LOG_SERIES, Mercator ln(1+r) coefficients).
|
|
1009
|
+
// CANCELLATION FIX: when m>=1.5 (j>=128), regroup as (kexp+1)+(log2(m0_j)-1)+log2(1+r)
|
|
1010
|
+
// instead of kexp+log2(m0_j)+log2(1+r) — both are the same value (subtracting the exact
|
|
1011
|
+
// integer 1 from a k-limb value is lossless), but the regrouped form never lets two O(1)
|
|
1012
|
+
// quantities nearly cancel down to a near-zero log2(x) (x close to a power of 2): the
|
|
1013
|
+
// naive form loses up to ~50 bits there since the k-limb fold's error is bounded
|
|
1014
|
+
// relative to the DISCARDED O(1) input magnitude, not the tiny post-cancellation output.
|
|
1015
|
+
// 2. Multiply by y (exact-ish via twoProd, which Dekker-splits BOTH operands — no manual
|
|
1016
|
+
// y1/y2 pre-split needed, unlike fdlibm/the old $math.pow_fold's c1/c2 params).
|
|
1017
|
+
// 3. 2^L via the same shape: round to nearest integer n (f64.nearest — IEEE round-ties-to-
|
|
1018
|
+
// even, spliced back in via $math.pow_scalbn), then a 256-point sub-table (EXP2_TABLE,
|
|
1019
|
+
// 2^(idx/256) for idx∈[-128,127]) plus a short Horner series (EXP_SERIES, e^u
|
|
1020
|
+
// coefficients with ln2 powers folded in) on the doubly-reduced fraction.
|
|
1021
|
+
// 4. ROUNDING TEST: eps = |y|·LOG2_ABS_ERR[k]·ln2 + EXP2_REL_ERR[k], applied as
|
|
1022
|
+
// |result_hi|·eps to the k-limb result. LOG2_ABS_ERR is an ABSOLUTE bound on step 1's
|
|
1023
|
+
// error (measured empirically, ~uniform over x — dd 2^-77.15, td 2^-148.2, before an
|
|
1024
|
+
// 8+ bit margin), scaled by |y| because step 2 turns a fixed absolute log2(x) error
|
|
1025
|
+
// into an absolute error in L=y·log2(x) that GROWS WITH |y| — this is the term a naive
|
|
1026
|
+
// "eps=|result|*E" misses: when x is adversarially close to a power of 2 (log2(x) tiny)
|
|
1027
|
+
// and y is huge, |L| can stay modest even as |y|→huge, so bounding eps off the RESULT's
|
|
1028
|
+
// own magnitude alone silently understates the true uncertainty by a factor of
|
|
1029
|
+
// |y|·log2(x)/L. (Confirmed the hard way: x=1-2^-53, y=1e18 missed by 8 ulps under the
|
|
1030
|
+
// naive formula; 0 misses with this one, across every stress set above.) EXP2_REL_ERR
|
|
1031
|
+
// is step 3's own relative error (dd 2^-72.55, td 2^-156.9, unscaled — the final ·2^n
|
|
1032
|
+
// splice via $math.pow_scalbn is a separate, already-exact staged multiply, musl
|
|
1033
|
+
// scalbn.c, adding none). d(2^L)/dL = 2^L·ln2 converts L's absolute error to the
|
|
1034
|
+
// result's relative error, hence the ln2 factor. PHASE-1 COST: the dd Horner series
|
|
1035
|
+
// (steps 1 and 3) uses a CHEAP HYBRID — the dominant correction term is kept at full dd
|
|
1036
|
+
// precision (one extra mulExt) but the rest of the series runs in plain f64 on the
|
|
1037
|
+
// leading limb, since a fully-plain series measured only ~2^-69 (too loose for
|
|
1038
|
+
// colorpq's own PQ exponents — ~47% phase-2 escalation there) while a fully-rigorous
|
|
1039
|
+
// dd Horner chain (every term compensated) made phase 1 ~28x slower than the fdlibm
|
|
1040
|
+
// kernel it replaced. This hybrid is the measured middle ground — see powLog1pCheapGen's
|
|
1041
|
+
// and the exp2 P-series' own comments below.
|
|
1042
|
+
// 5. If phase 1 (dd) can't certify: recompute at phase 2 (td). Phase 2 is expected to
|
|
1043
|
+
// ALWAYS certify (0 uncertain-after-phase-2 cases across every validation set) — if it
|
|
1044
|
+
// doesn't, this returns its best-effort value rather than nothing (see the mission
|
|
1045
|
+
// note: an uncertain result here would mean the gate found a case beyond what
|
|
1046
|
+
// scratchpad/pow/ discovered, worth its own report, not a silent wrong answer).
|
|
1047
|
+
//
|
|
1048
|
+
// |y| > 1e20 short-circuits BEFORE any of the above: the smallest possible |log2(x)| for
|
|
1049
|
+
// finite x>0,x!=1 is ~1.6e-16 (x adjacent to 1), so |y|>1100/1.6e-16~=6.9e18 already forces
|
|
1050
|
+
// definite overflow/underflow — 1e20 keeps ~15x margin above that while staying far under
|
|
1051
|
+
// ~1.34e300, where twoProd's internal Veltkamp split (SPLITTER·y) would itself overflow to
|
|
1052
|
+
// Infinity and corrupt the multiply. x==1 is handled explicitly there too (pow_fold has no
|
|
1053
|
+
// x==1 pre-check of its own — it relies on log2(1)=0 exactly zeroing the product for ANY y,
|
|
1054
|
+
// which the main kernel already gives it, but the |y|>1e20 short-circuit bypasses the main
|
|
1055
|
+
// kernel entirely so needs its own x==1 case).
|
|
1056
|
+
const POW_LOG2_T = 8, POW_EXP2_T = 8
|
|
1057
|
+
const POW_LOG_N_DD = 9, POW_LOG_N_TD = 18, POW_EXP_N_DD = 8, POW_EXP_N_TD = 15
|
|
1058
|
+
// dd (k=2) bounds measured for the CHEAP-HYBRID phase-1 Horner below (leading correction
|
|
1059
|
+
// term at DD precision via one extra mulExt, tail terms plain-f64): worst dd log2 abs err
|
|
1060
|
+
// 2^-77.15, dd exp2 rel err 2^-72.55 over a 15k+-point sweep incl. subnormals/adversarial
|
|
1061
|
+
// near-power-of-2 x (scratchpad/pow/measure_log2_abs.py, measure_exp2_unscaled.py) — ~9
|
|
1062
|
+
// bits margin below each. An all-plain-tail version (no DD leading-correction term) measured
|
|
1063
|
+
// only 2^-68.97 / 2^-71.27 — too loose for colorpq's own PQ exponents (~47% phase-2
|
|
1064
|
+
// escalation measured there, worse than the expensive full-rigor path it replaced); this
|
|
1065
|
+
// hybrid recovers the needed precision for one extra mulExt (~35 ops) instead of the full
|
|
1066
|
+
// ~(N-1)-deep dd Horner chain (~300+ ops) it replaces. td (k=3) unchanged: phase 2 still
|
|
1067
|
+
// uses the fully-rigorous Horner (powHornerExt).
|
|
1068
|
+
const POW_LOG2_ABS_ERR = { 2: 2 ** -68, 3: 2 ** -138 }
|
|
1069
|
+
const POW_EXP2_REL_ERR = { 2: 2 ** -64, 3: 2 ** -146 }
|
|
1070
|
+
// Mercator ln(1+r) coefficients (r^1..r^18), each a 3-limb (hi,mid,lo) f64 expansion —
|
|
1071
|
+
// uniform 3-limb treatment (not just enough for dd) avoids per-coefficient precision
|
|
1072
|
+
// bookkeeping: a plain-f64 a2..a4 would itself cap the td rounding-test budget at ~2^-114
|
|
1073
|
+
// (worked by hand: a coefficient's contribution to total relative error is
|
|
1074
|
+
// a_i·r^(i-1)·(coefficient's own rel. error), and for i=2..4 with |r|<=2^-8 that leaves only
|
|
1075
|
+
// ~50-70 bits of slack from a plain double) — 3-limb coefficients remove that risk entirely
|
|
1076
|
+
// at zero extra runtime cost (dd just reads the hi limb). Generated by
|
|
1077
|
+
// scratchpad/pow/gen_tables.py (mpmath, 400-bit) — verified against the CR vector gate, not
|
|
1078
|
+
// hand-derived.
|
|
1079
|
+
const POW_LOG_SERIES = [
|
|
1080
|
+
[1, 0, 0],
|
|
1081
|
+
[-0.5, 0, 0],
|
|
1082
|
+
[0.3333333333333333, 1.850371707708594e-17, 1.0271626370065257e-33],
|
|
1083
|
+
[-0.25, 0, 0],
|
|
1084
|
+
[0.2, -1.1102230246251566e-17, 6.162975822039155e-34],
|
|
1085
|
+
[-0.16666666666666666, -9.25185853854297e-18, -5.135813185032629e-34],
|
|
1086
|
+
[0.14285714285714285, 7.93016446160826e-18, 4.4021255871708246e-34],
|
|
1087
|
+
[-0.125, 0, 0],
|
|
1088
|
+
[0.1111111111111111, 6.1679056923619804e-18, 3.423875456688419e-34],
|
|
1089
|
+
[-0.1, 5.551115123125783e-18, -3.0814879110195775e-34],
|
|
1090
|
+
[0.09090909090909091, -2.523234146875356e-18, 7.003381615953585e-35],
|
|
1091
|
+
[-0.08333333333333333, -4.625929269271485e-18, -2.5679065925163143e-34],
|
|
1092
|
+
[0.07692307692307693, -4.270088556250602e-18, 2.370375316168906e-34],
|
|
1093
|
+
[-0.07142857142857142, -3.96508223080413e-18, -2.2010627935854123e-34],
|
|
1094
|
+
[0.06666666666666667, 9.251858538542971e-19, 1.2839532962581572e-35],
|
|
1095
|
+
[-0.0625, 0, 0],
|
|
1096
|
+
[0.058823529411764705, 8.163404592832033e-19, 1.1328999672866093e-35],
|
|
1097
|
+
[-0.05555555555555555, -3.0839528461809902e-18, -1.7119377283442096e-34]]
|
|
1098
|
+
// 2^r2 coefficients (r2^0..r2^15): b_k = ln2^k/k!, so the series is directly in the reduced
|
|
1099
|
+
// fraction r2 (no separate u=r2·ln2 extended multiply needed). Same uniform-3-limb rigor.
|
|
1100
|
+
const POW_EXP_SERIES = [
|
|
1101
|
+
[1, 0, 0],
|
|
1102
|
+
[0.6931471805599453, 2.3190468138462996e-17, 5.707708438416212e-34],
|
|
1103
|
+
[0.24022650695910072, -9.493931253182876e-18, -2.4105486965696903e-34],
|
|
1104
|
+
[0.05550410866482158, -3.1658222903912804e-18, 1.1357423645400287e-34],
|
|
1105
|
+
[0.009618129107628477, 2.8324606784381e-19, 1.85284146980722e-35],
|
|
1106
|
+
[0.0013333558146428443, 1.3928059563172586e-20, -7.148318211080472e-37],
|
|
1107
|
+
[0.0001540353039338161, 1.1783618439907562e-20, 4.5910849836706486e-38],
|
|
1108
|
+
[0.000015252733804059841, -8.027446755055875e-22, -3.3547393057817446e-38],
|
|
1109
|
+
[0.000001321548679014431, -2.0162732323629023e-24, 1.2689094913973184e-40],
|
|
1110
|
+
[1.01780860092397e-7, -1.949520713756723e-24, 9.914912572246126e-41],
|
|
1111
|
+
[7.054911620801123e-9, -2.9110453965609406e-26, 1.2702853147779823e-42],
|
|
1112
|
+
[4.4455382718708116e-10, -1.2731051485060954e-26, 4.420326254448758e-43],
|
|
1113
|
+
[2.5678435993488206e-11, -3.6970912098302563e-28, 1.7132265077294294e-44],
|
|
1114
|
+
[1.3691488853904128e-12, 7.770795328665668e-29, 4.5200006429723875e-45],
|
|
1115
|
+
[6.778726354822545e-14, 5.7164033621144854e-30, 2.4988036368119357e-47],
|
|
1116
|
+
[3.1324367070884287e-15, -3.9318558140598756e-32, -2.0482463830537468e-48],
|
|
1117
|
+
[1.3570247948755148e-16, -1.057117616368963e-32, -1.512313747717571e-49]]
|
|
1118
|
+
const POW_LOG2E = [1.4426950408889634, 2.0355273740931033e-17, -1.0614659956117258e-33] // 1/ln2, 3-limb
|
|
1119
|
+
|
|
1120
|
+
// ---- WAT codegen: EFT (error-free transform) primitives, no FMA (Dekker splits) ----
|
|
1121
|
+
// A Builder accumulates a statement list (nested — if/then/else bodies build with
|
|
1122
|
+
// sub-scopes, `B.sub()`, and splice into the parent as `(then ${sub.stmts.join(' ')})`).
|
|
1123
|
+
//
|
|
1124
|
+
// REGISTER POOL (not one fresh local per intermediate value): `tmp()` used to mint a
|
|
1125
|
+
// brand-new WASM local for every single EFT micro-step — twoSum alone burns 3, twoProd 7,
|
|
1126
|
+
// and a k-limb Horner chains dozens of these per term. For $math.pow_transcend that summed
|
|
1127
|
+
// to ~7000 locals, and both the wasm engine's own compiler and jz's THIS ADD MADE
|
|
1128
|
+
// codegen/optimize passes pay for it: colorpq measured ~15x the old fdlibm kernel's time,
|
|
1129
|
+
// and a pow-using program's OWN compile time went ~0.1s -> ~4.1s. WASM locals are
|
|
1130
|
+
// function-scoped, not block-scoped, so distinct intermediates can share one physical slot
|
|
1131
|
+
// once the earlier one's last use has passed — a classic linear-scan register allocation,
|
|
1132
|
+
// done here as a two-pass token scheme instead of hand-tracking free lists at every call
|
|
1133
|
+
// site (that would be exactly as error-prone as the bug it's fixing):
|
|
1134
|
+
// PASS 1 (this Builder): `tmp()` does NOT pick a real local name. It mints a UNIQUE ID,
|
|
1135
|
+
// emits `(local.set \x01ID\x02 expr)` into the statement stream, and returns
|
|
1136
|
+
// `(local.get \x01ID\x02)` for the caller to embed in later expressions — U+0001/U+0002
|
|
1137
|
+
// control chars so a token can never collide with real WAT text or another token's digits.
|
|
1138
|
+
// Text order here IS execution order (statements append in the order they run; the one
|
|
1139
|
+
// place order gets locally inverted — a `(local.set TARGET expr)` prints TARGET before
|
|
1140
|
+
// expr's own operand reads, though expr evaluates first at runtime — only costs a missed
|
|
1141
|
+
// same-statement reuse opportunity, e.g. `x = a+a` not sharing a's slot with x; it never
|
|
1142
|
+
// causes an early free, because freeing is keyed off each id's PRECOMPUTED true last-use
|
|
1143
|
+
// position, not scan position — see powResolvePool).
|
|
1144
|
+
// PASS 2 (`powResolvePool`, called once on the fully-assembled function body): scan for
|
|
1145
|
+
// every token, resolve each id's real last use, walk the text again allocating a small
|
|
1146
|
+
// per-type register file (separate pools for f64/i32/i64 — a value can only reuse a
|
|
1147
|
+
// same-typed slot), freeing a register the instant its id's last use is seen. Mutable
|
|
1148
|
+
// locals (below) are NOT pooled — they're few, and their whole point is surviving across
|
|
1149
|
+
// sub-scopes, so they keep stable dedicated names exactly as before.
|
|
1150
|
+
const POW_TOK_1 = '\x01', POW_TOK_2 = '\x02'
|
|
1151
|
+
const powMkBuilder = (prefix, shared) => {
|
|
1152
|
+
shared ??= { n: 0, type: {}, mutDecls: [] }
|
|
1153
|
+
const stmts = []
|
|
1154
|
+
const tmp = (expr, type = 'f64') => {
|
|
1155
|
+
const id = shared.n++
|
|
1156
|
+
shared.type[id] = type
|
|
1157
|
+
const tok = `${POW_TOK_1}${id}${POW_TOK_2}`
|
|
1158
|
+
stmts.push(`(local.set ${tok} ${expr})`)
|
|
1159
|
+
return `(local.get ${tok})`
|
|
1160
|
+
}
|
|
1161
|
+
// .set returns the STATEMENT STRING (does not push itself) — a mutable local is
|
|
1162
|
+
// typically declared in one scope but assigned from several (if/then/else sub-scopes),
|
|
1163
|
+
// so the caller must explicitly `.raw()` the result onto whichever scope is active.
|
|
1164
|
+
const mutable = (base, type = 'f64') => {
|
|
1165
|
+
const name = `$${prefix}_${base}${shared.n++}`
|
|
1166
|
+
shared.mutDecls.push(`(local ${name} ${type})`)
|
|
1167
|
+
return { name, get: `(local.get ${name})`, set: (expr) => `(local.set ${name} ${expr})` }
|
|
1168
|
+
}
|
|
1169
|
+
const raw = (s) => stmts.push(s)
|
|
1170
|
+
const sub = (p) => powMkBuilder(p ?? prefix, shared)
|
|
1171
|
+
return { tmp, mutable, raw, sub, stmts, mutDecls: shared.mutDecls, type: shared.type }
|
|
1172
|
+
}
|
|
1173
|
+
// Pass 2 of the register pool (see the Builder comment above): resolve every \x01id\x02
|
|
1174
|
+
// token in `text` to a real, REUSED local name. Returns the extra `(local ...)` decls the
|
|
1175
|
+
// pool needs (concat with the mutable-local decls already collected) and the resolved text.
|
|
1176
|
+
const powResolvePool = (text, typeOf) => {
|
|
1177
|
+
const tokenRe = /local\.(set|get) \x01(\d+)\x02/g
|
|
1178
|
+
const events = []
|
|
1179
|
+
for (let m; (m = tokenRe.exec(text));) events.push({ isSet: m[1] === 'set', id: +m[2], at: m.index })
|
|
1180
|
+
const lastUse = {}
|
|
1181
|
+
for (const e of events) if (!e.isSet) lastUse[e.id] = e.at // last (highest-index) 'get' wins
|
|
1182
|
+
const free = { f64: [], i32: [], i64: [] }, next = { f64: 0, i32: 0, i64: 0 }, regOf = {}
|
|
1183
|
+
for (const e of events) {
|
|
1184
|
+
const type = typeOf[e.id]
|
|
1185
|
+
if (e.isSet) regOf[e.id] = free[type].length ? free[type].pop() : next[type]++
|
|
1186
|
+
else if (e.at === lastUse[e.id]) free[type].push(regOf[e.id])
|
|
1187
|
+
}
|
|
1188
|
+
const resolved = text.replace(/\x01(\d+)\x02/g, (_, idStr) => `$pt_${typeOf[+idStr]}_${regOf[+idStr]}`)
|
|
1189
|
+
const decls = []
|
|
1190
|
+
for (const type of ['f64', 'i32', 'i64']) for (let i = 0; i < next[type]; i++) decls.push(`(local $pt_${type}_${i} ${type})`)
|
|
1191
|
+
return { decls, resolved }
|
|
1192
|
+
}
|
|
1193
|
+
const POW_SPLITTER = '134217729' // 2^27+1, Veltkamp split constant for f64's 53-bit mantissa
|
|
1194
|
+
const powSplit = (B, a) => {
|
|
1195
|
+
const c = B.tmp(`(f64.mul (f64.const ${POW_SPLITTER}) ${a})`)
|
|
1196
|
+
const hi = B.tmp(`(f64.sub ${c} (f64.sub ${c} ${a}))`)
|
|
1197
|
+
const lo = B.tmp(`(f64.sub ${a} ${hi})`)
|
|
1198
|
+
return [hi, lo]
|
|
1199
|
+
}
|
|
1200
|
+
const powTwoSum = (B, a, b) => {
|
|
1201
|
+
const s = B.tmp(`(f64.add ${a} ${b})`)
|
|
1202
|
+
const bb = B.tmp(`(f64.sub ${s} ${a})`)
|
|
1203
|
+
const e = B.tmp(`(f64.add (f64.sub ${a} (f64.sub ${s} ${bb})) (f64.sub ${b} ${bb}))`)
|
|
1204
|
+
return [s, e]
|
|
1205
|
+
}
|
|
1206
|
+
const powTwoProd = (B, a, b) => {
|
|
1207
|
+
const p = B.tmp(`(f64.mul ${a} ${b})`)
|
|
1208
|
+
const [ah, al] = powSplit(B, a), [bh, bl] = powSplit(B, b)
|
|
1209
|
+
const t1 = B.tmp(`(f64.sub (f64.mul ${ah} ${bh}) ${p})`)
|
|
1210
|
+
const t2 = B.tmp(`(f64.add ${t1} (f64.mul ${ah} ${bl}))`)
|
|
1211
|
+
const t3 = B.tmp(`(f64.add ${t2} (f64.mul ${al} ${bh}))`)
|
|
1212
|
+
const e = B.tmp(`(f64.add ${t3} (f64.mul ${al} ${bl}))`)
|
|
1213
|
+
return [p, e]
|
|
1214
|
+
}
|
|
1215
|
+
// absorb: ripple `term` top-down into a k-limb accumulator (array of k expr-refs) via
|
|
1216
|
+
// twoSum; the final carry-out (dropped) is ~2^-53k relative to the LARGEST term folded so
|
|
1217
|
+
// far, so a chain of these absorptions gives a k-limb-equivalent (~53k-bit) result.
|
|
1218
|
+
const powAbsorb = (B, acc, term) => {
|
|
1219
|
+
const next = []
|
|
1220
|
+
let carry = term
|
|
1221
|
+
for (let j = 0; j < acc.length; j++) { const [s, e] = powTwoSum(B, acc[j], carry); next.push(s); carry = e }
|
|
1222
|
+
return next
|
|
1223
|
+
}
|
|
1224
|
+
const powFoldK = (B, terms, k) => { let acc = new Array(k).fill('(f64.const 0)'); for (const t of terms) acc = powAbsorb(B, acc, t); return acc }
|
|
1225
|
+
const powMulExtDouble = (B, A, y, k) => {
|
|
1226
|
+
const terms = []
|
|
1227
|
+
for (let i = 0; i < k; i++) {
|
|
1228
|
+
if (i === k - 1) terms.push(`(f64.mul ${A[i]} ${y})`)
|
|
1229
|
+
else { const [p, e] = powTwoProd(B, A[i], y); terms.push(p, e) }
|
|
1230
|
+
}
|
|
1231
|
+
return powFoldK(B, terms, k)
|
|
1232
|
+
}
|
|
1233
|
+
// k-limb * k-limb, triangular (drop cross terms below k-limb precision — the standard
|
|
1234
|
+
// QD-library dd_mul generalizes cleanly to k limbs this way).
|
|
1235
|
+
const powMulExt = (B, A, Bv, k) => {
|
|
1236
|
+
const terms = []
|
|
1237
|
+
for (let i = 0; i < k; i++) for (let j = 0; j < k; j++) {
|
|
1238
|
+
if (i + j >= k) continue
|
|
1239
|
+
if (i + j === k - 1) terms.push(`(f64.mul ${A[i]} ${Bv[j]})`)
|
|
1240
|
+
else { const [p, e] = powTwoProd(B, A[i], Bv[j]); terms.push(p, e) }
|
|
1241
|
+
}
|
|
1242
|
+
return powFoldK(B, terms, k)
|
|
1243
|
+
}
|
|
1244
|
+
const powAddExt = (B, A, Bv, k) => powFoldK(B, [...A, ...Bv], k)
|
|
1245
|
+
// u/v (plain doubles, u,v exact by construction at every call site — Sterbenz subtraction
|
|
1246
|
+
// against a bit-truncated table breakpoint) to k-limb precision via iterative refinement:
|
|
1247
|
+
// each pass forms the EXACT residual u-s·v (twoProd+twoSum) and divides it again, recovering
|
|
1248
|
+
// ~53 more bits per pass.
|
|
1249
|
+
const powDivExt = (B, u, v, k) => {
|
|
1250
|
+
const terms = []
|
|
1251
|
+
let rHi = u, rLo = '(f64.const 0)'
|
|
1252
|
+
for (let pass = 0; pass < k; pass++) {
|
|
1253
|
+
const s = B.tmp(`(f64.div ${rHi} ${v})`)
|
|
1254
|
+
const [p, e] = powTwoProd(B, s, v)
|
|
1255
|
+
const [t1, t1e] = powTwoSum(B, rHi, `(f64.neg ${p})`)
|
|
1256
|
+
const [t2, t2e] = powTwoSum(B, rLo, `(f64.neg ${e})`)
|
|
1257
|
+
rHi = B.tmp(`(f64.add ${t1} ${t2})`); rLo = B.tmp(`(f64.add ${t1e} ${t2e})`)
|
|
1258
|
+
terms.push(s)
|
|
1259
|
+
}
|
|
1260
|
+
return powFoldK(B, terms, k)
|
|
1261
|
+
}
|
|
1262
|
+
// Horner (highest degree first) over a k-limb variable, coefficients as 3-limb JS rows
|
|
1263
|
+
// (only the first k limbs of each are used).
|
|
1264
|
+
const powHornerExt = (B, coefRows, k, xLimbs) => {
|
|
1265
|
+
const N = coefRows.length
|
|
1266
|
+
let acc = coefRows[N - 1].slice(0, k).map(v => `(f64.const ${v})`)
|
|
1267
|
+
for (let i = N - 2; i >= 0; i--) {
|
|
1268
|
+
acc = powMulExt(B, acc, xLimbs, k)
|
|
1269
|
+
acc = powAddExt(B, acc, coefRows[i].slice(0, k).map(v => `(f64.const ${v})`), k)
|
|
1270
|
+
}
|
|
1271
|
+
return acc
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// frexp: x>0 finite, in mutable local xLoc (rescaled in-place for subnormals). Returns
|
|
1275
|
+
// {kexp (mutable i32), m (f64 expr, in [1,2)), mHi (i32 expr, high word of m's bit pattern)}.
|
|
1276
|
+
const powFrexpGen = (B, xLoc) => {
|
|
1277
|
+
const bits0 = B.tmp(`(i64.reinterpret_f64 ${xLoc.get})`, 'i64')
|
|
1278
|
+
const hi0 = B.tmp(`(i32.wrap_i64 (i64.shr_u ${bits0} (i64.const 32)))`, 'i32')
|
|
1279
|
+
const kexp = B.mutable('kexp', 'i32')
|
|
1280
|
+
B.raw(kexp.set('(i32.const 0)'))
|
|
1281
|
+
const subB = B.sub('frs')
|
|
1282
|
+
subB.raw(xLoc.set(`(f64.mul ${xLoc.get} (f64.const ${2 ** 54}))`))
|
|
1283
|
+
subB.raw(kexp.set('(i32.const -54)'))
|
|
1284
|
+
B.raw(`(if (i32.eqz (i32.shr_u ${hi0} (i32.const 20))) (then ${subB.stmts.join(' ')}))`)
|
|
1285
|
+
const bits = B.tmp(`(i64.reinterpret_f64 ${xLoc.get})`, 'i64')
|
|
1286
|
+
const hi = B.tmp(`(i32.wrap_i64 (i64.shr_u ${bits} (i64.const 32)))`, 'i32')
|
|
1287
|
+
const lo = B.tmp(`(i32.wrap_i64 ${bits})`, 'i32')
|
|
1288
|
+
B.raw(kexp.set(`(i32.add ${kexp.get} (i32.sub (i32.shr_u ${hi} (i32.const 20)) (i32.const 1023)))`))
|
|
1289
|
+
const mHi = B.tmp(`(i32.or (i32.and ${hi} (i32.const 0x800fffff)) (i32.const 0x3ff00000))`, 'i32')
|
|
1290
|
+
const m = B.tmp(`(f64.reinterpret_i64 (i64.or (i64.shl (i64.extend_i32_u ${mHi}) (i64.const 32)) (i64.extend_i32_u ${lo})))`)
|
|
1291
|
+
return { kexp, m, mHi }
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// CHEAP-HYBRID phase-1 (dd only) series evaluation: ln(1+r) = r + a2 r^2 + a3 r^3 + ... .
|
|
1295
|
+
// Mercator's series has ALL integer powers of r (unlike atanh's odd-power-only series, the
|
|
1296
|
+
// shape exp2's series shares — see below), so a plain-Horner tail's reduction variable is r
|
|
1297
|
+
// itself, NOT r^2 (an earlier version mistakenly reused the odd-series r^2 pattern here;
|
|
1298
|
+
// confirmed wrong against the mpmath oracle — it silently dropped odd-power siblings of the
|
|
1299
|
+
// r^2 term, landing ~2^-16 absolute error instead of the intended ~2^-69). a2 r^2 is kept at
|
|
1300
|
+
// DD precision (one mulExt for r^2 + one mulExtDouble by the constant) since it's the
|
|
1301
|
+
// dominant correction: a fully-plain series (a2 onward all plain f64, ~2 ops/term) measured
|
|
1302
|
+
// only ~2^-69 dd precision — too loose for colorpq's own PQ exponents (~47% phase-2
|
|
1303
|
+
// escalation measured, worse than the fully-rigorous dd Horner it was meant to replace,
|
|
1304
|
+
// which itself made phase 1 ~28x slower than the fdlibm kernel it replaced). This hybrid —
|
|
1305
|
+
// one extra dd multiply for a2 r^2, plain Horner for a3 r^3 onward (truly O(r^3), tiny) —
|
|
1306
|
+
// measured 2^-77.15 dd absolute error (scratchpad/pow/measure_log2_abs.py), recovering the
|
|
1307
|
+
// needed precision for a fraction of full rigor's ~(logNTerms-1)-deep dd Horner chain cost.
|
|
1308
|
+
const powLog1pCheapGen = (B, r, logNTerms) => {
|
|
1309
|
+
const r0 = r[0]
|
|
1310
|
+
const rSq = powMulExt(B, r, r, 2)
|
|
1311
|
+
const a2Term = powMulExtDouble(B, rSq, `(f64.const ${POW_LOG_SERIES[1][0]})`, 2)
|
|
1312
|
+
let Qtail = `(f64.const ${POW_LOG_SERIES[logNTerms - 1][0]})`
|
|
1313
|
+
for (let i = logNTerms - 2; i >= 2; i--) Qtail = B.tmp(`(f64.add (f64.mul ${Qtail} ${r0}) (f64.const ${POW_LOG_SERIES[i][0]}))`)
|
|
1314
|
+
const tail = B.tmp(`(f64.mul (f64.mul (f64.mul ${r0} ${r0}) ${r0}) ${Qtail})`)
|
|
1315
|
+
return powFoldK(B, [...r, ...a2Term, tail], 2)
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// log2(x) to k-limb precision — see the header comment for the algorithm and the
|
|
1319
|
+
// cancellation-fix rationale. tblBase: WAT expr for LOG2_TABLE's injected base address.
|
|
1320
|
+
const powLog2ExtGen = (B, xLoc, k, tblBase, logNTerms) => {
|
|
1321
|
+
const { kexp, m, mHi } = powFrexpGen(B, xLoc)
|
|
1322
|
+
const T = POW_LOG2_T
|
|
1323
|
+
const j = B.tmp(`(i32.and (i32.shr_u ${mHi} (i32.const ${20 - T})) (i32.const ${(1 << T) - 1}))`, 'i32')
|
|
1324
|
+
const maskHi = (0xfff00000 | (((1 << T) - 1) << (20 - T))) >>> 0
|
|
1325
|
+
const m0Hi = B.tmp(`(i32.and ${mHi} (i32.const ${maskHi | 0}))`, 'i32')
|
|
1326
|
+
const m0 = B.tmp(`(f64.reinterpret_i64 (i64.shl (i64.extend_i32_u ${m0Hi}) (i64.const 32)))`)
|
|
1327
|
+
const u = B.tmp(`(f64.sub ${m} ${m0})`)
|
|
1328
|
+
const r = powDivExt(B, u, m0, k)
|
|
1329
|
+
const lnP = k === 2 ? powLog1pCheapGen(B, r, logNTerms) : powMulExt(B, powHornerExt(B, POW_LOG_SERIES.slice(0, logNTerms), k, r), r, k)
|
|
1330
|
+
const log2P = powMulExt(B, lnP, POW_LOG2E.slice(0, k).map(v => `(f64.const ${v})`), k)
|
|
1331
|
+
const addr = B.tmp(`(i32.add ${tblBase} (i32.mul ${j} (i32.const 24)))`, 'i32')
|
|
1332
|
+
const tHi = B.tmp(`(f64.load offset=0 ${addr})`)
|
|
1333
|
+
const tMid = k >= 2 ? B.tmp(`(f64.load offset=8 ${addr})`) : null
|
|
1334
|
+
const tLo = k >= 3 ? B.tmp(`(f64.load offset=16 ${addr})`) : null
|
|
1335
|
+
const tableEntryRaw = [tHi, tMid, tLo].slice(0, k)
|
|
1336
|
+
const kexpAdj = B.mutable('kexpadj', 'i32')
|
|
1337
|
+
const teAdj = tableEntryRaw.map((_, i) => B.mutable('te' + i))
|
|
1338
|
+
const elseB = B.sub('lelse')
|
|
1339
|
+
elseB.raw(kexpAdj.set(kexp.get))
|
|
1340
|
+
teAdj.forEach((h, i) => elseB.raw(h.set(tableEntryRaw[i])))
|
|
1341
|
+
const thenB = B.sub('lthen')
|
|
1342
|
+
thenB.raw(kexpAdj.set(`(i32.add ${kexp.get} (i32.const 1))`))
|
|
1343
|
+
const shifted = powFoldK(thenB, [...tableEntryRaw, '(f64.const -1)'], k)
|
|
1344
|
+
shifted.forEach((h, i) => thenB.raw(teAdj[i].set(h)))
|
|
1345
|
+
B.raw(`(if (i32.ge_s ${j} (i32.const ${(1 << T) / 2})) (then ${thenB.stmts.join(' ')}) (else ${elseB.stmts.join(' ')}))`)
|
|
1346
|
+
const kexpF = B.tmp(`(f64.convert_i32_s ${kexpAdj.get})`)
|
|
1347
|
+
return powFoldK(B, [kexpF, ...teAdj.map(h => h.get), ...log2P], k)
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// 2^L to k-limb precision — Llimbs is a k-limb array. Returns {limbs: k-limb array of the
|
|
1351
|
+
// UNSCALED fractional-part result, n: i32 expr, the exponent $math.pow_scalbn splices in}.
|
|
1352
|
+
const powExp2ExtGen = (B, Llimbs, k, tblBase, expNTerms) => {
|
|
1353
|
+
const n0 = B.tmp(`(f64.nearest ${Llimbs[0]})`) // ties-to-even, IEEE roundTiesToEven
|
|
1354
|
+
const n = B.tmp(`(i32.trunc_f64_s ${n0})`, 'i32')
|
|
1355
|
+
const nF = B.tmp(`(f64.convert_i32_s ${n})`)
|
|
1356
|
+
const negNF = B.tmp(`(f64.neg ${nF})`)
|
|
1357
|
+
const rExp = powFoldK(B, [...Llimbs, negNF], k)
|
|
1358
|
+
const idxF0 = B.tmp(`(f64.nearest (f64.mul ${rExp[0]} (f64.const 256)))`)
|
|
1359
|
+
const idxI0 = B.tmp(`(i32.trunc_f64_s ${idxF0})`, 'i32')
|
|
1360
|
+
const idxLo = B.tmp(`(select (i32.const -128) ${idxI0} (i32.lt_s ${idxI0} (i32.const -128)))`, 'i32')
|
|
1361
|
+
const idx = B.tmp(`(select (i32.const 127) ${idxLo} (i32.gt_s ${idxLo} (i32.const 127)))`, 'i32')
|
|
1362
|
+
const idxF = B.tmp(`(f64.convert_i32_s ${idx})`)
|
|
1363
|
+
const negIdxOver256 = B.tmp(`(f64.neg (f64.div ${idxF} (f64.const 256)))`)
|
|
1364
|
+
const r2 = powFoldK(B, [...rExp, negIdxOver256], k)
|
|
1365
|
+
const addr = B.tmp(`(i32.add ${tblBase} (i32.mul (i32.add ${idx} (i32.const 128)) (i32.const 24)))`, 'i32')
|
|
1366
|
+
const eHi = B.tmp(`(f64.load offset=0 ${addr})`)
|
|
1367
|
+
const eMid = k >= 2 ? B.tmp(`(f64.load offset=8 ${addr})`) : null
|
|
1368
|
+
const eLo = k >= 3 ? B.tmp(`(f64.load offset=16 ${addr})`) : null
|
|
1369
|
+
const tableEntry = [eHi, eMid, eLo].slice(0, k)
|
|
1370
|
+
// CHEAP phase-1 (dd only): 2^r2 = 1 + b1*r2 + b2*r2^2+... . Unlike log's series (odd
|
|
1371
|
+
// powers of r only, naturally a series in r^2), exp2's has BOTH parities of r2, so a
|
|
1372
|
+
// plain tail Horner runs IN r2 (not r2^2). b1*r2 and b2*r2^2 are kept at DD precision
|
|
1373
|
+
// (b1*r2: one mulExt; b2*r2^2: one mulExt for r2^2 + one mulExtDouble by b2) — b1 is the
|
|
1374
|
+
// dominant correction (ln2, not O(r2) small) and b2's term needed the same DD treatment
|
|
1375
|
+
// log2's a2 did (see powLog1pCheapGen's comment: a plain-double b2 alone measured only
|
|
1376
|
+
// ~2^-71 dd precision, too loose for colorpq's own PQ exponents). b3 onward (O(r2^3),
|
|
1377
|
+
// truly small) stay a cheap plain Horner on r2's leading limb. Phase 2 (td) keeps the
|
|
1378
|
+
// fully-rigorous Horner.
|
|
1379
|
+
const P = k === 2 ? (() => {
|
|
1380
|
+
const r2_0 = r2[0]
|
|
1381
|
+
const term1 = powMulExt(B, r2, POW_EXP_SERIES[1].slice(0, 2).map(v => `(f64.const ${v})`), 2)
|
|
1382
|
+
const r2Sq = powMulExt(B, r2, r2, 2)
|
|
1383
|
+
const term2 = powMulExtDouble(B, r2Sq, `(f64.const ${POW_EXP_SERIES[2][0]})`, 2)
|
|
1384
|
+
let Qtail = `(f64.const ${POW_EXP_SERIES[expNTerms - 1][0]})`
|
|
1385
|
+
for (let i = expNTerms - 2; i >= 3; i--) Qtail = B.tmp(`(f64.add (f64.mul ${Qtail} ${r2_0}) (f64.const ${POW_EXP_SERIES[i][0]}))`)
|
|
1386
|
+
const tail = B.tmp(`(f64.mul (f64.mul (f64.mul ${r2_0} ${r2_0}) ${r2_0}) ${Qtail})`)
|
|
1387
|
+
return powFoldK(B, ['(f64.const 1)', ...term1, ...term2, tail], 2)
|
|
1388
|
+
})() : powHornerExt(B, POW_EXP_SERIES.slice(0, expNTerms), k, r2)
|
|
1389
|
+
const result = powMulExt(B, tableEntry, P, k)
|
|
1390
|
+
return { limbs: result, n }
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Assemble $math.pow_transcend's full body — see the header comment for the algorithm.
|
|
1394
|
+
const genPowTranscend = () => {
|
|
1395
|
+
const B = powMkBuilder('pt')
|
|
1396
|
+
const xLoc = { get: '(local.get $x)', set: (e) => `(local.set $x ${e})` }
|
|
1397
|
+
const yLoc = { get: '(local.get $y)' }
|
|
1398
|
+
const logTbl = '(global.get $math.pow_log2_tbl)', expTbl = '(global.get $math.pow_exp2_tbl)'
|
|
1399
|
+
B.raw(`(if (f64.gt (f64.abs ${yLoc.get}) (f64.const 1e20))
|
|
1400
|
+
(then
|
|
1401
|
+
(if (f64.eq ${xLoc.get} (f64.const 1.0)) (then (return (f64.const 1.0))))
|
|
1402
|
+
(if (i32.eq (f64.gt ${xLoc.get} (f64.const 1.0)) (f64.gt ${yLoc.get} (f64.const 0.0)))
|
|
1403
|
+
(then (return (f64.const inf)))
|
|
1404
|
+
(else (return (f64.const 0.0))))))`)
|
|
1405
|
+
const epsExpr = (k) => `(f64.add (f64.mul (f64.abs ${yLoc.get}) (f64.const ${POW_LOG2_ABS_ERR[k] * Math.LN2})) (f64.const ${POW_EXP2_REL_ERR[k]}))`
|
|
1406
|
+
const emitPhase = (k, logN, expN, isLast) => {
|
|
1407
|
+
const Bp = B.sub(`p${k}`)
|
|
1408
|
+
const logx = powLog2ExtGen(Bp, xLoc, k, logTbl, logN)
|
|
1409
|
+
const L = powMulExtDouble(Bp, logx, yLoc.get, k)
|
|
1410
|
+
Bp.raw(`(if (f64.gt ${L[0]} (f64.const 1100)) (then (return (f64.const inf))))`)
|
|
1411
|
+
Bp.raw(`(if (f64.lt ${L[0]} (f64.const -1100)) (then (return (f64.const 0.0))))`)
|
|
1412
|
+
const { limbs, n } = powExp2ExtGen(Bp, L, k, expTbl, expN)
|
|
1413
|
+
const hi = limbs[0]
|
|
1414
|
+
const loSum = limbs.length === 2 ? limbs[1] : Bp.tmp(`(f64.add ${limbs[1]} ${limbs[2]})`)
|
|
1415
|
+
const eps = Bp.tmp(`(f64.mul (f64.abs ${hi}) ${epsExpr(k)})`)
|
|
1416
|
+
const lowerU = Bp.tmp(`(f64.add ${hi} (f64.sub ${loSum} ${eps}))`)
|
|
1417
|
+
const upperU = Bp.tmp(`(f64.add ${hi} (f64.add ${loSum} ${eps}))`)
|
|
1418
|
+
const lower = Bp.tmp(`(call $math.pow_scalbn ${lowerU} ${n})`)
|
|
1419
|
+
const upper = Bp.tmp(`(call $math.pow_scalbn ${upperU} ${n})`)
|
|
1420
|
+
if (isLast) {
|
|
1421
|
+
// Phase 2: return best-effort if STILL uncertain rather than nothing — validated 0
|
|
1422
|
+
// occurrences (see header comment), so this is a documented safety net, not a live path.
|
|
1423
|
+
Bp.raw(`(if (f64.eq ${lower} ${upper}) (then (return ${lower})))`)
|
|
1424
|
+
Bp.raw(`(return (call $math.pow_scalbn (f64.add ${hi} ${loSum}) ${n}))`)
|
|
1425
|
+
} else {
|
|
1426
|
+
Bp.raw(`(if (f64.eq ${lower} ${upper}) (then (return ${lower})))`)
|
|
1427
|
+
}
|
|
1428
|
+
B.raw(Bp.stmts.join(' '))
|
|
1429
|
+
}
|
|
1430
|
+
emitPhase(2, POW_LOG_N_DD, POW_EXP_N_DD, false) // phase 1 (dd) — cheap common path
|
|
1431
|
+
emitPhase(3, POW_LOG_N_TD, POW_EXP_N_TD, true) // phase 2 (td) — rare, always returns
|
|
1432
|
+
// Pool resolution runs ONCE over the whole (both-phases) body — phase 2's pool reuses
|
|
1433
|
+
// phase 1's already-declared registers for free (phase 1 has unconditionally returned or
|
|
1434
|
+
// finished by the time phase 2's code runs, so none of its values are still live).
|
|
1435
|
+
const { decls: poolDecls, resolved } = powResolvePool(B.stmts.join(' '), B.type)
|
|
1436
|
+
return `(func $math.pow_transcend (param $x f64) (param $y f64) (result f64)
|
|
1437
|
+
${B.mutDecls.join(' ')} ${poolDecls.join(' ')}
|
|
1438
|
+
${resolved})`
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// LOG2_TABLE / EXP2_TABLE: 256 entries x 24 bytes (3 little-endian f64 limbs each) —
|
|
1442
|
+
// log2(1+j/256) for j=0..255, and 2^(j/256) for j=-128..127 respectively, computed at
|
|
1443
|
+
// 400-bit precision (scratchpad/pow/gen_table_bytes.py) and decomposed into a 3-limb
|
|
1444
|
+
// (hi,mid,lo) expansion. Injected as linear-memory data tables only when
|
|
1445
|
+
// $math.pow_transcend survives reachability pruning — same lazy-table mechanism as
|
|
1446
|
+
// module/number.js's Eisel-Lemire/Ryū tables (src/wat/assemble.js's injectTable).
|
|
1447
|
+
// Char-array + one join, not `s += chr` — the concat form allocates ~n²/2 bytes
|
|
1448
|
+
// of dead strings PER COMPILE (see module/number.js hexToBytes; these two 6 KB
|
|
1449
|
+
// tables alone cost ~38 MB per compile inside the warm self-host kernel).
|
|
1450
|
+
const powHexToBytes = (hex) => { const chars = []; for (let i = 0; i < hex.length; i += 2) chars.push(String.fromCharCode(parseInt(hex.slice(i, i + 2), 16))); return chars.join('') }
|
|
1451
|
+
ctx.runtime.powLog2Table = powHexToBytes('00000000000000000000000000000000000000000000000077ac7a6dc409773f11be24496fb6123c3b347080dd8794b85108efb650fe863f545e00ec8de32f3c1b6e1364c677b7b80c7ba9173136913f5244249b06ed323c2a1c23cc79b1b1382ad2c28596e7963fbd9f7b0776643dbc240d89569f6bd0b8860f85ba63939c3f4fd3c53da09b393c4625f31f589fceb8133413d5d11ca13fb96c05fbba7e22bc4c51f7bfdabcc438265a689430eda33f94c41fb8a513303c793aa68b0ca5de3887fd8e75d3baa63fe15b4b78039b383c47694326e7dbdeb8945149c3bf85a93f84de43ed3828483cf3f7d42fc9c7d5385fab0ab9fa4dac3ff088332c0f0e16bcabfd82b1d22bb438a05332838913af3f941a1251be8c47bcbd52d1caebcbde389b9fa29f38ebb03fb89951d1220c53bcc8f350916b06f7383d5a137e5b4bb23f6982ba324c975dbc5a236e632449ffb8c3f127dd2faab33fcf5af27697fd43bcf1f84223b5a9dd38b73b0336b807b53f962e4b42968645bcc3a726e5b1a7d0381613c9faf663b63f165198e014335f3caa60f0b586a7e4b881a2b896eebeb73ffaa11a19d7204e3cc6f7e155cfe6e4385b33466ea118b93f1793ac6d736354bc7864eca6dc37d7b8948434df1171ba3f2c3dc4e1a7bb34bcc015918a7db6d3b8baabad4042c8bb3f5f46b969b1ec583ceff73abe6978f4b8da825be3341ebd3ff9f2c83cce42533c5c0a5ab00caff838b2a57f11ec72be3ff229dcd9b5bd3c3c269c2dee50a99338a5000b0f6ac6bf3fc8bd1ae7b2ec5bbc650e64f59a00f9b8e479da8c588cc03fabd60be410766abcf915400c2b5407b92e0689b4e134c13fa5990dfe7d4bf9bb7d2ad8d3216153b87b2b5597d1dcc13f580e1e0c15a9673cb48ad47da875053940a6074b2984c23f75f7d6545da26cbcfa5e49a32e63d9381aae78e2e92ac33f4ca775c0f2514f3cce4e3bb09a6ce6b8648a9a6d14d1c33fd6b8a9e091e93fbc75ebf8dbb645deb84df783f9a976c43fb61fb2a0749c583ce9c4c7b3adeec3388a5c7a90ab1bc53ffffd1626022c623ca7ad823d61350ab988d6fb391ac0c53f5d7e7d3bfd1768bc175706c3b68be4b81613c9faf663c63f165198e014336f3caa60f0b586a7f4b87f02efd44207c73f3ebf6b7d0ee0543c8fb96547d9baf2b8df5dd0c7fea9c73f73e823be64033dbce83e96073d3dd2b8b3032fd02b4cc83fb5e2c488db6e11bc056a892605e1b1b86c2b35e8caedc83f93d99a79af8c62bcc54f06e9bdd5fb38df707e07dd8ec93f5cb9486574d6173cacc5ecec97067ab86bb82023632fca3ff0426d6cbde35abce5070ddc7ac5ed3894ecb42d5ecfca3fcae73f34e11e40bc0a755f41905ada38e9955f17cf6ecb3f1fed8b3363fb6a3cd85d40b152cdbfb8ee4dd9cdb60dcc3ffc7326669b38603c4b467ddb2794f7b8c90d773c16accc3f72b8d369cd4e6abc806de7e032de02397059324cee49cd3ffde27e4da5855bbcaa9696fa807cf7b80f48b1e33fe7cd3fbee1328cbd414ebcbcc95491b201bdb84d6a4ee70b84ce3f1e087115061b5cbc60b030556103efb8278f20395320cf3f3b63cc994add613c102efc634c070bb90a6802b916bccf3f78c253cc6cd4513ca5245261b641f9b864064da2ab2bd03f080bd6a4173a71bc2da3b9354c8316390930b0db0a79d03f1755679ac6c07b3ce15b7bbc9e99f4b88f2a547529c6d03f80f4f91638c176bcd26695fc04a30e39760bd3da0713d13fd952c3d2477b6abcfa87cdb933f80339ff08bb76a65fd13f19074f118e5f763c18de3de5e230e03870f091b205acd13f28e3b7e11ca3743c2d27cdb11d061a39138ed8f625f8d13feae6fb6934f4653c90705860f1fc0db93a070eab0744d23fffec16ad916e7fbc9d5879e10b11f0b88326b335ab8fd23fe68b905ae8c0783cee68dcd263041fb9af9a4dfc10dbd23fcf1b638fdec47b3c8c0b388be38e0b3936286b633926d33f84aac40f074779bcbfab01bd18770cb9edcda4ce2471d33f09fcb449663763bc61f47613e486f138fbdca1a0d3bbd33f2e67c98b3c9f7abc0a3c205ca8401d3949041b3b4606d43fcd4809d1bc6c7d3c2a8a0790f35e1239c44fddfe7c50d43f47ce00d897ca7abcd53ea5dd0d110eb98b1bcd4b789ad43f02250b90406d1bbc2107a84e9accb2b86afbe88038e4d43f7e15e9f4380a31bcdcfc34744a96c738b3964cfcbd2dd53f6df28739d8737f3c963cbcdfbdcb1239cb78331b0977d53f1dfbc4941fbe73bcc16aed90a11a173988d6fb391ac0d53f5d7e7d3bfd1778bc175706c3b68bf4b8ae4829b4f108d63f90e6b762fc437abc229eb942275c0139a77b67e48f51d63f6761371287dd5abc3aa3d2812e2dfeb8b8d48c24f599d63fbf2d0d1552317a3cf76c79d8b07900b9de0c9dcd21e2d63fc13dd18254e3753c9b5fdf0e6cdeedb883c1cb37162ad73ffe57d79ac9f27abc298095e3a6b60ab93cfb7ebad271d73f58dbed2a13905bbc7c586bcf6d78acb8c4aa51ac57b9d73f1973fbf4dbd8753c9ba604e122a91c39541c1663a500d83fbd6ffa045b57693c92fc8dbdb01109398e61d833bc47d83f50788d88b51f6cbcdb586a5764280e3926b2e0729c8ed83f59c9cdd666d276bc0d389238c13bf5b872c3b57346d5d83f029394b6bd3075bcace6ccd843b3eb3809171f89ba1bd93f591ea5be52d372bcbabea8db8561f5389c402705f961d93fcae648a198c27cbcd8d85468593e15392f231e3902a8d93f61ca2187b65d6a3cae856f613cdcf5b8e0259b75d6edd93f4d548ec138c97dbc6e4615f85919fab851607f0a7633da3f4f181acaefa078bcce5307c0dc1f1b39f4bef746e178da3f12255c6a81a0743cd9092cebb25310b9491f7f7918beda3f433aa7aa8d5b5ebccfaf2a3d6e78fc383464e0ef1b03db3fbbc3aeb00598793c01fb7d4ab51aef38a18238f7eb47db3f769dc041e4fa76bc1ca17fb58e54fcb87a86f8db888cdb3ffcdad1a788327abc185ec9d84c6917b93190e7e9f2d0db3fa5fb37c7f42e75bcccb6f58b4853e438e6ca246c2a15dc3fdd6b5ada1aed6fbc6cd3c85eb27dfd38565b29ad2f59dc3fccbcbb52b99f7f3c8f98615681a70839b447caf6029ddc3fefc12b2e1c4066bca759ba2e38380db97d583a92a4e0dc3fd32f8ce87a516bbc3256b5de8fea0bb97df20bc81424dd3fd2532ab56f85743cf6de402f466a19390fea32e05367dd3fadaafa661e141cbc41b70b733ebfb638b94f062262aadd3fb7115671a574123cfb166c7b12c998b84c3642d43feddd3f417c4558c6ae733cb148e84eb38906b99872093ded2fde3fab4274ae4ae44b3c4eb0ef05d239e738d254e7a16a72de3f26e2e8253ca3483cead4eb46d1abe938ce5bd147b8b4de3fedf5807972f9743c67dcd35f8f3c1db920e22873d6f6de3f05410e6a80477f3c052b89cc18e01bb941c5bc67c538df3f95a09716e48067bc6c9170aaa87df0b8cf06cb68857adf3f85d7fd3f67f368bcfeab6d06a572f3b80a6802b916bcdf3f78c253cc6cd4613ca5245261b64109b99bff839a79fddf3f1e1df432e08c71bc9c7b522da1d31db9e0647227571fe03fb3d35c08819a5bbc22e19f1dfab4f5387f99978bda3fe03fd7b302321aca793c965b13a96a1a1cb9b24ef2194760e03fe1bf9c8f8f4c7f3c7774560493da00393d707ff29c80e03ffb604507cf96843c14119e661e3d2cb9fce1f834dca0e03f7d89a14939f97fbc8c408a347a691ab9a63ad60005c1e03f97d8a6d49520863cbbd3507e50152e39117c4d7517e1e03f25fd24caabae633c59dbf2681135f9b8eac853b11301e13f39eb9b6b37c78e3c9aab6d4d3bad273907189ed3f920e13fa3e922b068a5763c11edb582d0dcf6b844e5a1fac940e13feda773b5967a88bc144f94a8b57b2ab906e095448460e13fbb9cf58f372b4e3ce14b4924263defb86a9772cf2880e13fcd14455f3aae833c64118711066328392124f3b8b79fe13f560d30d570588c3c1cdd0facd6ea23390ed0951e31bfe13f79322356ec4a6cbca4a92aa782e3efb8a6bb9c1d95dee13ff71dc7864c7a683c06826616e9c1053926810ed3e3fde13f3c11ce515790803c66b2444f756321399ad5b65b1d1de23ffe5085167aa8783c1ce5b612371907b9c82727d4413ce23f271053c3981d503ce95ac3f97f91fd38043db758515be23fd3234640a3a1853ca1ab3dd3bdddcf38f8cb85054c7ae23f7dba8e833be1453cbedcc5d20357c4b8601579f63199e23fcce24577f67c843c172f390d4111e738d17a3f4703b8e23f5d7e7d3bfd1758bc175706c3b68bd4b880135013c0d6e23f00152f8a532a893c1fdc538c184f18b9263feb7568f5e23f129369d18c27643cb276d8e56f160db9f2361b8afc13e33f5ba380201df97c3cd64c3f7a65591639a79cb46a7c32e33ff0de02fd36ca7bbc0c06dc6e9e93f038d9075732e850e33fad9c236c369367bc7f05fdde145dd13862916dfb3f6fe33f367354a8dc11803cde84334f45d82c39095d2fe0838de33f74c573e1f00d743c609be325b09519396721a0fab3abe33f837d8bdfa69a79bcc0d5ca39c6bd12b912ae9064d0c9e33f037548d90a66713c395785b1a416e5b816709f37d9e7e33fb25469eba9b88a3c2480d0cf4fca22b9bcf4388dce05e43f3155354ed01264bcdee718dd34b4e038a96a987eb023e43f13ab5274d8c1893cfdb1b510c36428396521c8247f41e43fba5e598d949384bc7b14b14bad8625b93907a2983a5fe43f4563f60700ca86bc1fec8214717c25b98725d0f2e27ce43f765b20176fbc6cbc592f4b2d8401cfb88b1bcd4b789ae43f02250b90406d2bbc2107a84e9accc2b89597e4bbfab7e43ffc88584319168f3c96bfee6e01c826b9c4ce335b6ad5e43ffd1c66fe7b9b823cc0eb11bc6f3c2a393ef3a941c7f2e43f2a41e0c21fd48d3c4aeb6c47e4ca2439f9a808871110e53fcb6e3a645b0b71bc96b1cdc5ea3a1cb90979e442492de53f872a21e126bd833c0fe501f3ef482c398e43a58c6e4ae53f2f511567cd9383bc4e72080bce8407b92cb0867b8167e53f2e0ceeafe9c58e3cb79888f79702f438349d98268284e53f524bb9163a397ebcf31c237aa74408395c8dbfa470a1e53f128e3e7941e3753cce6c6e1b142201b93514b50c4dbee53f6a77b73f304585bcf8b9740a80fc2c393c41087517dbe53fd1bddaea9170823c246ab064e3a12e39af091ef4cff7e53fa8cce84f39cd81bc3421d8c8d74e133909b131a07614e63f757150e4468c89bc39d6d6a7fea41c394830558f0b31e63f0079c785961a803c93dc40242a991939f09b71d78e4de63f5b7ae1bf03e189bc162fc6fad21e2539cc88478e006ae63f688d375e926879bc951ea4e7f0880a39876f6fc96086e63f0863be0edcc57b3cab6f8436246a02b90a0f5a9eafa2e63fcd17e4e52a13503c63d242f484abe7b8aecd5022edbee63f44d085404f15573ccaeda1d38b17fcb84a19766a19dbe63fed4ae5c0074173bc907c7c6d7869e83818c6c58b34f7e63ff11a37c3035e86bceff8b79f00162a397c6c159b3e13e73f8eec374280e96abca590e5638bf30a39b0c514ad372fe73f6b58d0c874317abc5e8018b59c37fab854074ed61f4be73f9b5717e0438d7cbc6410c80351581cb9ee3d262bf766e73fe3445acca40e79bcb2b3cc56aad4183957a6ddbfbd82e73fa9aa97841ff28e3c6a61907b62d30739200690a8739ee73f359d5b12b6997dbc9627f17d649010b9e40235f918bae73ffe3843fe6ed5533c9fcab70049eaf7b8a478a0c5add5e73f2b94a6ec8e10803c6cb1db0274ad0e3916cf822132f1e73f0aca0044996877bcc710352dacc002b9f94d6920a60ce83fcfe408da2e777bbc69d61732359a1cb97370bed50928e83fc61e9eecfd1172bc579fdd769b4b08397437ca545d43e83f3c9ef37c620c82bcbd53ce5e01582e39267bb2b0a05ee83ff6a164e8fc86603c2d47bb03261b0ab9713b7bfcd379e83fe437c2d7fd6680bc5558ec831c1104398bef064bf794e83fab46f6b1a81b803cec5de7ffc5170239a9d416af0ab0e83f66bc3a4772936ebc67c36537994b04b9be3b4b3b0ecbe83f9139815edd2583bc662c2a03fe40fab861d6230202e6e83fe7fffaf34f058ebce5c2d57efdde2ab9cd020016e600e93fd7e7827bafc02bbcf027ceedad06ceb809171f89ba1be93f591ea5be52d382bcbabea8db856105392faba06d7f36e93f91bc0893e6cd88bccbfceb62785015b9e3e284d53451e93fbdf7d66e1ed3893c9d0c9c36425429b9f6b5acd2da6be93f21b9ceeab61360bccb7764f9653bb7382f38da767186e93f2888c1bea6ac8ebc406dd1df2375283950e0b0d3f8a0e93f1a68a15a46b570bc8d6ba4053bc6ec384dceb5fa70bbe93fcd12e6cdd8f48ebc7e24784e1d692439b31050fdd9d5e93f9f201d485699893c20951ae31ca1163956e9c8ec33f0e93f8c0022383473843cf72a91eb42b429392d114cda7e0aea3fad307e44d1ce69bc702c58ab6e9ffcb877fbe7d6ba24ea3f755665fb7d4685bcd385e0ef5e3e0f391f188ef3e73eea3fb6f4ae7239c377bcac2756dcc9840539641513410659ea3f615ec42b3ff3723c050953d2b0ad0539c8202fd01573ea3fdb7d04707daa80bc5efe989afe291a3945277eb1168dea3f5fbc1e68cd5183bc2702d97631cd2039d31480f508a7ea3f83c81af28d378f3cb388bd7d6b3324393b1399acecc0ea3fa83c6a749e1e743cdf8cb825035f103933c811e7c1daea3f0446284fe95477bc3e1518d1a3add5b8d69217b588f4ea3f40053c5a9c2285bc6a3c4f1b236f16b96cc8bc26410eeb3f657bf258892c74bc9be263b24582f8b88af0f84beb27eb3fe039978dacb5813c39717c469ac9f1388c00a9348741eb3f04094138a34373bc8c5eb6705eb3183966968ff0145beb3f8af0e29f617969bca36a7364a6910bb9da32558f9474eb3f328ecd50dcd2823c24674ff4c13121b909738820068eeb3f2207b1253b3a6d3c7783398bc47a013964499eb369a7eb3f195d29bcfdf05d3c91da8d264765f0380636f257bfc0eb3fc2663452b28a8bbc0b619135c20016b96e7ec61c07daeb3f4b26fb7d48ba82bc99bb8e64fe4c15b9a764441141f3eb3f98120d122ec880bc12d26ec7b7621c39d35d7c446d0cec3f181d57696ab6883ca9fa0f5fd12323b9294866c58b25ec3fe421be5f902085bc5cfda2344d0aecb855a0e1a29c3eec3fd8db24a7c5b4793c6404f112870ff83858b6b5eb9f57ec3f21885cbb346b77bc2c8dd898b28b0839c7e191ae9570ec3f321123c6262f883ca757ad98fd1813398eb50dfa7d89ec3f45b4b56827338ebcc59293d5a4cd05b91633a9dc58a2ec3f845560c871ff7a3ceaed46e7a7e707b9f6fccc6426bbec3f650beb3ea14c84bc8c4b723d8263d1380789caa0e6d3ec3fa99d75b745de7ebc38536df498741d390352dc9e99ecec3fc21d1c8244ea34bcf1276543cc97c2389608266d3f05ed3fea3970d725c68c3c397d4a1509362639f1c3b419d81ded3f618abf846e6c7bbc52967f81e85dd1b8d5317fb26336ed3fc756dad61bbc743cbed589c694ae02392bc66545e24eed3f95b6ab8a134983bc4472bd382730d8380fea32e05367ed3fadaafa661e142cbc41b70b733ebfc6386c2a9b90b87fed3f3e7a5e000ae4813c6816c627539bdd3815663d641098ed3ff4dd5c8afc1d7ebc37fbf11ca96b16b965fba2685bb0ed3f4157db0f051086bc56bfa336022415b96cf53fab99c8ed3fc610b5aece99873c4a43f49e7ece1839a4387339cbe0ed3f076449764fa1893ce078ad79fc952b392caf8620f0f8ed3ff42a1a0fd2fb823c12457dae837b25399674af6d0811ee3f1127236d46f6743c6ad25a250dd512b940010e2e1429ee3f53bce367aaba6f3c365de723eef605393d55ae6e1341ee3f526182b3fbe46a3c726973fdd2e1f3b8ce22883c0659ee3f9bb16e6a4486853c8418f66d2bd0fb386ff87ea4ec70ee3fdbac353308d469bcbb876170a7fc0c39736a62b3c688ee3f22f8030e977785bc4ac7071b50511c393a3cee7594a0ee3fb0136e2f2ac661bc74a9d9e5ec5808b9fb88caf855b8ee3f1e6177ceb8965a3ca19ca5358cfff2b823ec8b480bd0ee3fedcfc0d5f1c3853c77915b9723432eb950a9b371b4e7ee3f22bd9f7c705b8f3c5681bfee5c86fbb8e4d3af8051ffee3ff028ad2566d87cbc75f354baab9716b93076db81e216ef3fb6af1046c9907d3c180dd176e53df5b845b87e81672eef3f944297b35d84553cad04dc0df604f6385506cf8be045ef3f08c13df95693833c866b8ceab3e007b9be36efac4d5def3f50b2c2cc7743873c26d77b68f1bc2039aeafeff0ae74ef3f7c394ed0242f74bca5acdbec3b0b0f39698cce63048cef3f333da705926b8cbc6e234642e55f2bb933c277114ea3ef3f9fd3b5e7a2a4883c99d69a522bdc26b9df44c5058cbaef3f85142fff53d77f3c37b8092cdfbef438f92a7f4cbed1ef3f0d44837388ac803cbadcada742ae0339a0d15bf1e4e8ef3fb42d1722f1ec723c88907f3167a61eb9')
|
|
1452
|
+
ctx.runtime.powExp2Table = powHexToBytes('cd3b7f669ea0e63f5664b21334dd8bbc75c1de3a3e7d25393e1775fa52b0e63f0e9d9a2cf5386a3c186d6259ba6bf938bfda0b7512c0e63f0d0bff67568962bc196c76585cefff384576d4dddccfe63f0973f1b6a97a8c3c9e7de927cb80f8b82f1a653cb2dfe63fab883c683abe5bbc78e7bb8af859ba38e53a599892efe63fb2c81a9e74b980bc90d12acab38d0ab9849451f97dffe63ff60e86250f3c78bc6e954a3f920100b9872ef466740fe73f5fa65ad444d6493ca1a2478fa89cddb8745fece8751fe73f997a8886476e71bcc3a45369796912b98ad0ea86822fe73f722cd62ca00a82bc87af0b7efb8d18b97481a5489a3fe73f3cd5656cd9a880bc68a7f317e22a2839fdcbd735bd4fe73f1c6e8a61fd47803c04b9ad3f03422b39c9674256eb5fe73fd36d3157592480bce8e519fae7f828b9096eabb12470e73ff847911677788b3c93b1e7d1c5b9eeb83f5dde4f6980e73f2d16020ab866883cf7327930424d24b9f61cac38b990e73f3eddaa62a849833c5fbd4fd609b100b98701eb7314a1e73f2f9904ee771574bcd4102d937a21d4b8dbcf76097bb1e73f88dc6884b5eb8bbc6dc00b4b750313b932c13001edc1e73fd64d16d14c128f3c03bbc26c234d2db9f086ff626ad2e73fb4b872fbdbbd813c8c6f94b65eda29b9624ecf36f3e2e73f7e7915ba025d603cdffcf827140ae73891c4918487f3e73fae1193cf117f70bcffb785360b9f1139121a3e542704e83f2b976d62867c82bc6eb1c9710d4e1d39d906d1add214e83f4d1d150d3764843c77d261654d692c3913ce4c998925e83fd83215d41d4c8dbcc1bacb65adf6e038f741b91e4c36e83fd52bdf319a9b893c8b2005ab00511e39adc723461a47e83ffbcd41a384d678bcd1ef165ce19115b9215b9f17f457e83fd016b2f848a74bbc90ee2ee7e658ea38ed92449bd968e83fbaf6d49bf8c68fbc21d98151f6161fb936a431d9ca79e83fbd47dbd2d7d2753c1fa99ec1799607b999668ad9c78ae83f3ab57cf3c294893cde85f33e28612d390f5878a4d09be83f2b20754495538d3cf40038928efd2fb9dba02a42e5ace83f274b8656f1e9863c336383a7440603b97817d6ba05bee83f6e4443fc5ecb8e3c607ef4b4f14e2e398c44b51632cfe83faae3e9325ed560bcd69d83dbb3daf3b8d966085e6ae0e83fe6b2c96f4a1187bca37c34ae62d1213936771599aef1e83f6c97e3a213cc753c6351b8d226bfc3388a2c28d0fe02e93fd23ffe85ca92853c7a400aa6ecaa2939c6ff910b5b14e93f2425582e79d68dbc4a53045285031c39e22faa53c325e93f7fdb39a65f4573bc430b22ab9a041ab9e5c5cdb03737e93fbc7eb581c75f57bcb20dac57e297f638e5985f2bb848e93f992d7d79d6c37dbc0b7e50ed82a614b90f52c8cb445ae93f39f0a5967c4b66bcbb8ba9c95370d0b8b370769add6be93f96c8197f96a54bbc2911a0e23348ec38504ede9f827de93fd1851b7c5b188dbc6f4b14d7b9ed2739a2227ae4338fe93fed784ca2daab6c3c12c377c8c22e0fb9ba07ca70f1a0e93f32e6ce91bd7381bc5f965478985300b90dfe534dbbb2e93f18d5f64d4ed88dbc314b18fee4670f3990f0a38291c4e93fbef271b0467c6c3c5c0843796b370639d5b84b1974d6e93f3382dda3be1685bc98c691ebba1905b92323e31963e8e93f6e4ce678ca24683ce0ba2b082cf9a0389ef2078d5efae93fcefaf1aacea974bc5d7888fce6f0143965e55d7b660cea3f33d51c5d495983bcfbb4514508541339bbb88eed7a1eea3f0ee78bee18668c3cc0782db72f7f2b39332d4aec9b30ea3fab36dc7d5c30863c176dc222fa472539d80a4680c942ea3f20b19f5880a78abc8391d8419e6e2db95d253eb20355ea3fe1418ddb6e2f8dbc483fd6df7afdfbb85260f48a4a67ea3f66036730560f553c750452ee9686eb3858b330139e79ea3fc763c5ca7ecb8b3c51f77631697826b9592ec153fe8bea3fa915bab267f884bc76a16b560d1f2039bffd79556b9eea3f31fdf70ec9fa803cb98c9ee36ab11839ba6e3521e5b0ea3f4545e9da319c783c71d9f0903bf40ab97af3d3bf6bc3ea3fd06ce7ca34927fbcf89676fcdb60fcb874273c3affd5ea3fe5b8b1b63bef873c842b2fd1551a23b9add35a999fe8ea3f81cc5d34cda1873cea75e63abc7f2a39fff222e64cfbea3fcd5e310ffcb284bc302f93a6d252223966b68d29070eeb3f25e4804cf5de8bbc0056c595bb1c143952899a6cce20eb3fcc56074a02dd843c9e8c92f6da8328b9fb154fb8a233eb3f08d784305e8052bcd9a4dd0ebcbaf238b149b7158446eb3f907cdfe93d766fbc6ff5f31c40e5f3b83a59e58d7259eb3fe36dbabbdf718cbcdff71d087074fcb82ac5f1296e6ceb3f6e3f8852f3a8823c6298a998eea11739475efbf2767feb3f3bac547e4f5865bc72abe18144a6fa38e44927f28c92eb3fc665cb5416728bbc672431d91e1115394a06a130b0a5eb3f2e29540ed3fc8ebc673c5091bfd1dab81f6f9ab7e0b8eb3f056269c9d1522fbc882005b899b4b1b8d2c14b901ecceb3f849e2d7ad03d723c58120e0564a1193907a2f3c369dfeb3f525bea6023262cbcff8465c2b82acbb8091ed75bc2f2eb3f739c6b3fcafd8ebcda59cdce817e02393db341612806ec3f34cafba15a8a7dbc9546df0f5dfd06b99c5285dd9b19ec3fdd4850896510713cda285912519e09392c65fad91c2dec3fd7a5c81716e586bcdee5ea370eaf05b97ad0ff5fab40ec3f0ac683e037458b3cf8f470facda6143922fbfa784754ec3fafb59324072f813cac83f5444e6317394bd1572ef167ec3fad3c48ff4d88823cb25c9d324cc41fb933c98889a87bec3f595525bebb767ebc00b57558843902b9b5e706946d8fec3f445c8048bcac613cfab800c1aaedf638dbc4515740a3ec3ff5080dd1bef277bc5a5894cc3352c4386990efdc20b7ec3fdb49e9d1cb03653c2e036b5665870d3975166d2e0fcbec3f939000860f226dbcb3b373e724040e39fac35d550bdfec3f729d82533bd87dbc4920743a07eaeab874ab5b5b15f3ec3f57ff6db8e9088abcf76dba56fe4307b97c89074a2d07ed3f9c7a794337bc8cbcf6a09d0344702eb968c9082b531bed3fee369a213656853c73cd11c6e66c29b9f2890d08872fed3f78859d717b488dbce7faa9b262daf238d6a1caeac843ed3f14165abf53db833cf7567a10bd20243987a4fbdc1858ed3f07375bd702ed723cfc3155b053b0fab8d3e662e8766ced3fa065814a7ae84f3cff759cf50117cb389883c916e380ed3fe8dfed8bc11e81bc5a76c87a4ed00eb97560ff715d95ed3fbef69abb2d058a3c14204926c50d23398532db03e6a9ed3f32b56d6900238c3c15c60e6f24f6273915833ad67cbeed3fe48b6b92f1768bbc2d61e6118f8320b960b401f321d3ed3fc318f07857da823cf31c66adde6c2cb958061c64d5e7ed3f8fba798e52a58cbce15e2b82fdf914395f9b7b3397fced3f5c4b184fcda581bcd6ef44a925722b39177d196b6711ee3f447f5cbd29b562bc2a07a59c3086033929a1f5144626ee3f96147a8127b687bc9a408c8018982bb912ee163b333bee3f8bc6fd31a4f489bc84c79e916db82839f63f8be72e50ee3f8fcca980899e733c78d2c2b32ce91139766d67243965ee3f35b72275f83f76bc18a7b58dfcbd0139834cc7fb517aee3fe28d0cca22d5823ccba9b6b057a728b940b7cd77798fee3fb154b080940881bccbb70358ae061339da90a4a2afa4ee3f93289c17239c8ebcdef3bb42f2c01fb96eca7c86f4b9ee3ff2e493222f83843ce8cb5102c40f29b9f1678e2d48cfee3f8cad11b4f3938cbc3bb444efdfb920b9108518a2aae4ee3f8d5687a48dc6813c247e07b58d1c0339275a61ee1bfaee3fb0b6a486f4c78d3c69ff29d2d56d2f392a41b61c9c0fef3f451d1865002283bc5bb0934211f823b997ba6b372b25ef3f438e0dbfa5a1833c16b57654adc624397472dd48c93aef3fde37d83e5a5a69bc10cce43f180ae43840456e5b7650ef3f8ba1d82de1d3893cf30ec8ff9b0104b9f84488793266ef3f3e3439357ba38f3c139e4c5aa426173914be9cadfd7bef3f0a3506d012bb8dbc4dfa8072cec52539893c2402d891ef3f89f679a7a82e51bc57efc3bf2493f8b8d8909e81c1a7ef3f1e93a5f35348773c51766fc360c0ed3814d59236babdef3fb68e0915736769bc329a8ad2d40c0b39f1718f2bc2d3ef3fe779659674eb523c6cc54e9396f0f238d9232a6bd9e9ef3fe3fd427403a6643cf30a13e885afeb38000000000000f03f00000000000000000000000000000000bfbc5afa1a0bf03f719f60a7b2f684bc083c3f52dd550bb93533fba93d16f03fb7cdb89a29619b3c8709d80780f42b3981023b146821f03fb64ec50f31bf82bc0bff27a73e9529396180773e9a2cf03f5d085b53839071bcd5743d0a5b0819b9ccbb112ed437f03f1ae1adee1168653ce977bd5a3d310139857f6ee81543f03f6ec977191ca390bc40404bf4fb12f9b8b154f6725f4ef03f8dd0a03a79c3843ce2af722b139c2fb9748515d3b059f03f65b475a4e2738d3c7e258d4ff95f1039891f3c0e0a65f03f97c399577bcb95bc984c6cfeade03339def6dd296b70f03f273cb1e2df918cbcab242c2e1fb41f3936a8722bd47bf03f008745543423833c6e3699508d2b09b9c89b75184587f03fff84b24bbe86613c4f416bd920580139e00766f6bd92f03fd13f0a80638096bcf83ed6f89f18043983f3c6ca3e9ef03f366131187848913c59c2fdd1458b34b919391f9bc7a9f03f381d3d876cd1853cdd230277d9f82cb90f89f96c58b5f03f0b61dc4a2ea6983c4cf7ebd69b7c36b9856ce445f1c0f03fef1cd20689f9943c8e3712c4719d0339f747722b92ccf03f714fe216dc1e903ce36f4e56ac8a3e39ec5d39233bd8f03f6a313fe44dc19bbc9c382ec1be9616b9a2d1d332ece3f03f537bc527173a403cdb9d4e9976aae5b8c5a9df5fa5eff03f1b0254bcb99d94bcfcf10371d54a3db91bd3feaf66fbf03f7bbd4ec4ed9b6bbc5942d8491febfab83e23d7283007f13fd5fd9216eb468d3ca875485b01571f39515b12d00113f13f3a9b443910c596bc2d568f988bd52939b62a5eabdb1ef13f72fb03f754a49cbc8825b0214b45f338cc316cc0bd2af13fc7a56cb314b551bc203108428f8df0b8ab04f214a836f13ff0dc48ba8f1067bcaa1ce9344e9a0c39e02da9ae9a42f13f9e36f19abf2f93bc1664c7b47bfe32b92e314f93954ef13fab44bf39e8918bbc327293d6fedd27b9518ea5c8985af13f0aabeeb96a40823c74c47952571b10b9c2c37154a466f13f321aea823bf2583c80f4f9656fecfc387b517d3cb872f13f768ad7b9419081bcf03fa16a40f22439c0bb9586d47ef13f645aace23f9e703c10929cec4cf90039ea8d8c38f98af13f6c0f97d1231091bcc5970b04f02517392f5d37582697f13f087ef185ddaa943c99a3308a6729263975cb6feb5ba3f13fe468497b4c5b8e3ce86a928361d30a391c8a13f899aff13f8092b6a485bf973cd07bbf23c4c215b9d45c0484e0bbf13f07f62e35865399bc8e710395a60c24b96b1c28952fc8f13fc9f810807709903c674ec981b87518b9aab9683187d4f13f3c64a2006e019e3c18b981082da61e392740b45ee7e0f13fdeb68c08d8fd96bc068766819f4530b91dd9fc2250edf13f8cb77b0298df91bc7574c4364d503e394dce3884c1f9f13f5caf97a024f59bbc1c06837fd78627b9d68c62883b06f23f95844a8175c78d3ca41e6fc1db8107b919a87835be12f23fc9aafc2c2d59933caac80902b46416b996dc7d91491ff23feea594947ea9823c6b10b7b3c29326b9d11279a2dd2bf23f9fd67755fb348d3c7830845221871bb93862756e7a38f23f7305c7b67eb0993ce032f59a9fd824b90a1482fb1f45f23f96a91c91cccf8a3c00703c513b4618393fa6b24fce51f23fa4f4f4be55c18a3c97f7dcafc8a9f13875ce1e71855ef23f2c1bc34aa2e1933ccd8b08766eba3539dd7ce265456bf23fd9e9409933bd823c771b463a3977123929df1d340e78f23f6ce7f9057c069e3c8b53e4ceb7d43bb98163f5e1df84f23f7e0d3f8c3a4c9abc7d2de5a2da7f363970bb9175ba91f23fbd1c402872cc82bcc4e4462d90a3d738e1de1ff59d9ef23f5512adafe812863c9046608544e50d39130fd1668aabf23fa7901619435799bcf6b7737e6dde2db990d9dad07fb8f23fa41a38d6dc0a41bcbd6d79b85f88e0382f1b77397ec5f23f2451eba6450195bcd4bf5c425c243eb90b03e4a685d2f23fd541db544702903c0793cbf8d8e91eb98915641f96dff23f98e1bcfbcf169d3c80f75c7ac6ad2a39562f3ea9afecf23f8323d5450fca713c2ad1e6de087b0d396b88bd4ad2f9f23f93da2b53553c65bc9820749c0886023915b7310afe06f33fe48231d26af4863cd9d09cf0b2b71739fdb2eeed3214f33fd1fcf3f3a359893cb5811b07eb1c29b931d84cfc7021f33f7c04188ee79c8a3ce8852b888c771b3932eaa83bb82ef33f18f3b43ce8459cbcb998083ac7783bb9ff1664b2083cf33fa65936842127933c6bfc6ceaa20634b92dfae3666249f33fa4810893755a83bcc96c7b6aaf7504b9f19f925fc556f33f28464e5cee5c8bbcf2d520e524e528b93b88dea23164f33f5eb86ca044318cbc96802341b08b2f39cba93a37a771f33fe2ea42bfea3a96bcfa6b51123e7e38394a751e23267ff33f3cb2ce9ecaf599bcf8127eed6974053966d8056dae8cf33fbd04993c8d959ebc214f40617aa72039ef40711b409af33f34298efca5a999bc7a0b91aef88f31b9f79fe534dba7f33fe3f561d636e475bc96c217ffb1b00939f46cecbf7fb5f33f18ff6fe2664c953c80bee3b2f8683d39e5a813c32dc3f33fc3295d37f8ff9ebc5a39932a3f1421b973e1ed44e5d0f33f714c288cd0e87f3c1559a64e16bac1382234124ca6def33fbc9ef01109da8a3cb78ffa68ba0828b975511cdf70ecf33fca9b8c7b63f68abc26ba49f3debc09b91c80ac0445faf33ff3f956f923d097bc0d2024373e4730b924a067c32208f43f48d0f4b6f8dd8b3ccf9544751c8b22392a2ef7210a16f43f7892301c69f35ebc1865fcea432bd3b88a460927fb23f43fdd14b3c02d4698bc80feff78d9ca31b997a850d9f531f43f99795fe3ddc781bcef5f1996c4032939d4b9843ffa3ff43f03c00497be80883cdf5849ca664929b92d896160084ef43fd080ef047a9b483c22d9e32d31acd0b832d2a742205cf43f8e1ffb82196468bcc85a5ae4395af8b857001ded416af43f768a64d14b949c3c3a1ff24f40df373937328b666d78f43f335744edf0209cbc083b3da2afb61cb9d03cc1b5a286f43ff06290b6a3c1733cc03a74aeeb1e0e39d2ae92e1e194f43fa09e495e89b283bca67223bb055c2f39ded3d7f02aa3f43f56bed1f362cb993cc7e261c776181939d7b76dea7db1f43ff097287fb82581bc2dee116a40241839272a36d5dabff43fe242ecaf97437d3c392b5c74c706ec3814c117b841cef43f5dbd0a69295e903c6778872174972bb90dddfd99b2dcf43f33786abcdbec983c439b5569c9121239ffabd8812debf43f527a5d2e7d2595bc22db115a0e772eb9a72c9d76b2f9f43fe35759d209b394bccd85b6d71faaf1b8ee31457f4108f53f5f46b7499b247a3cf8110a5f6d420fb94266cfa2da16f53fef93bd6985768fbc7d172682710ef938ef4e3fe87d25f53f71efef438d997cbce8d175164a9710b9824f9d562b34f53fad3cb11dbe7a80bc4c211f9533a70f3927adf6f4e242f53f7e5f2d196d92873caa6ba02e782611b90f925dcaa451f53f9be5edef9c688dbc93041b7791c91939d210e9dd7060f53ff0eb8e166efb90bc715c57ae29113939da27b536476ff53fad931d012cbb993cff13a65268f80fb9cfc4e2db277ef53fc4b9578a8cb990bc20aa4f1f89393db9fdc797d4128df53fe81d9a5be195823cc6e4d12ad9262ab9cc07ff27089cf53f0fe667e4cee297bc25bc1b2f770c2d39295448dd07abf53fad4746054c32963cfeda6f50ee4427b9037aa8fb11baf53f1a3e234ca1779bbc004288b1df763439b746598a26c9f53fa28669811b4b3c3c8c97545273c28e38938b999045d8f53f4356b4a8a7d69cbcfbb7d1eccf6d32394821ad156fe7f53f5ee68030f9a69b3cd6a75fb79a5f39b971ebdc20a3f6f53f92cfcde3ddea89bc7974c7fba6e1203909dc76b9e105f63f47de569b42e293bc88252eb9542c13b9f4f6cde62a15f63f274cb84a3e4b9e3cc85a3264caa305b985553ab07e24f63f97b4407ec18393bc91b9cf57e7d80539fd29191ddd33f63fe564b9be1047983cb6dd0b51212e373920c3cc344643f63f33899d753c488cbc0fc4c10040901339b78fbcfeb952f63f093ea7c9d5e39abc1a3c878aa6fd3339252255823862f63f341c598709b69bbc3b0adcf437a33439f63308c7c171f63f34616c5832878ebc25da440de8592f3973a94cd45581f63f653ef744ae38603cff043b630328efb838959eb1f490f63f5d44eb9abd04883caaebbcc8eaf828b9')
|
|
1453
|
+
|
|
1454
|
+
wat('math.pow_transcend', genPowTranscend(), ['math.pow_scalbn'])
|
|
1455
|
+
} // if (crPow)
|
|
1456
|
+
|
|
863
1457
|
wat('math.pow', `(func $math.pow (param $x f64) (param $y f64) (result f64)
|
|
864
1458
|
(local $result f64) (local $n i32) (local $neg_base i32) (local $abs_x f64)
|
|
865
1459
|
;; y == 0 -> 1 (covers pow(NaN,0), pow(±0,0), pow(±Inf,0))
|
|
@@ -933,87 +1527,348 @@ export default (ctx) => {
|
|
|
933
1527
|
;; x < 0, non-integer finite y -> NaN
|
|
934
1528
|
(if (f64.lt (local.get $x) (f64.const 0.0))
|
|
935
1529
|
(then (return (f64.const nan))))
|
|
936
|
-
|
|
1530
|
+
;; Remaining case: x > 0 finite (≠1), y finite (≠0,≠1) and not an i32-range integer.
|
|
1531
|
+
;; $math.pow_core below is a correctly-rounded fdlibm port by default (no exp/log double-
|
|
1532
|
+
;; rounding), or — under optimize.crPow — CORRECTLY ROUNDED in the stronger CORE-MATH sense
|
|
1533
|
+
;; (two-phase Ziv dd/td kernel, see $math.pow_core's own comment).
|
|
1534
|
+
(call $math.pow_core (local.get $x) (local.get $y)))`)
|
|
1535
|
+
|
|
1536
|
+
// scalbn(x, n) = x * 2^n, correctly rounded even when the result lands in the subnormal
|
|
1537
|
+
// range (a single f64.mul by a bit-constructed 2^n would double-round there). Ported from
|
|
1538
|
+
// musl's src/math/scalbn.c (MIT — https://git.musl-libc.org/cgit/musl/tree/src/math/scalbn.c,
|
|
1539
|
+
// also FreeBSD msun's scalbn.c): splitting the scale into two safe steps, each within the
|
|
1540
|
+
// exact power-of-two range, avoids that double rounding. Only reached from $math.pow_core's
|
|
1541
|
+
// subnormal-result tail, where |n| stays well under 1075 — the >1023 branch and the doubly-
|
|
1542
|
+
// nested steps are dead there but kept for fidelity with the reference.
|
|
1543
|
+
wat('math.pow_scalbn', `(func $math.pow_scalbn (param $x f64) (param $n i32) (result f64)
|
|
1544
|
+
(local $y f64)
|
|
1545
|
+
(local.set $y (local.get $x))
|
|
1546
|
+
(if (i32.gt_s (local.get $n) (i32.const 1023))
|
|
1547
|
+
(then
|
|
1548
|
+
(local.set $y (f64.mul (local.get $y) (f64.const 0x1p1023)))
|
|
1549
|
+
(local.set $n (i32.sub (local.get $n) (i32.const 1023)))
|
|
1550
|
+
(if (i32.gt_s (local.get $n) (i32.const 1023))
|
|
1551
|
+
(then
|
|
1552
|
+
(local.set $y (f64.mul (local.get $y) (f64.const 0x1p1023)))
|
|
1553
|
+
(local.set $n (i32.sub (local.get $n) (i32.const 1023)))
|
|
1554
|
+
(if (i32.gt_s (local.get $n) (i32.const 1023)) (then (local.set $n (i32.const 1023)))))))
|
|
1555
|
+
(else (if (i32.lt_s (local.get $n) (i32.const -1022))
|
|
1556
|
+
(then
|
|
1557
|
+
(local.set $y (f64.mul (local.get $y) (f64.mul (f64.const 0x1p-1022) (f64.const 0x1p53))))
|
|
1558
|
+
(local.set $n (i32.add (local.get $n) (i32.const 969))) ;; 1022-53, staged to dodge subnormal double-rounding
|
|
1559
|
+
(if (i32.lt_s (local.get $n) (i32.const -1022))
|
|
1560
|
+
(then
|
|
1561
|
+
(local.set $y (f64.mul (local.get $y) (f64.mul (f64.const 0x1p-1022) (f64.const 0x1p53))))
|
|
1562
|
+
(local.set $n (i32.add (local.get $n) (i32.const 969)))
|
|
1563
|
+
(if (i32.lt_s (local.get $n) (i32.const -1022)) (then (local.set $n (i32.const -1022))))))))))
|
|
1564
|
+
(f64.mul (local.get $y)
|
|
1565
|
+
(f64.reinterpret_i64 (i64.shl (i64.extend_i32_s (i32.add (local.get $n) (i32.const 1023))) (i64.const 52)))))`)
|
|
1566
|
+
|
|
1567
|
+
// x**y for the case the ladder above can't fast-path: x > 0 finite (≠1), y finite (≠0,≠1) and
|
|
1568
|
+
// not an i32-range integer. y==0.5 is always special-cased to hardware sqrt (correctly
|
|
1569
|
+
// rounded, cheaper than either general kernel below) regardless of crPow. Two kernels, picked
|
|
1570
|
+
// by `optimize.crPow` (see the authoritative comment above `emitPow` for the flag's full
|
|
1571
|
+
// semantics and the measured cost of switching):
|
|
1572
|
+
// OFF (DEFAULT): ported from fdlibm/FreeBSD msun's e_pow.c (Sun Microsystems, freely
|
|
1573
|
+
// licensed — https://raw.githubusercontent.com/freebsd/freebsd-src/main/lib/msun/src/
|
|
1574
|
+
// e_pow.c), the same algorithm V8's base/ieee754.cc ports for Math.pow — so this targets
|
|
1575
|
+
// bit-exactness against the host, not just low ulps (though it is "nearly rounded", not
|
|
1576
|
+
// CORE-MATH-class correctly rounded — see $math.pow_transcend for that). Trimmed to the
|
|
1577
|
+
// x>0 slice: fdlibm's sign/yisint bookkeeping for x<0 is dead weight here (x<0 already
|
|
1578
|
+
// returned NaN above).
|
|
1579
|
+
// 1. log2(x) in double-double (hi+lo): bit-extract the exponent, reduce the mantissa
|
|
1580
|
+
// around 1 or 1.5 (whichever centers it tighter), run the L1..L6 minimax on
|
|
1581
|
+
// s=(m-bp)/(m+bp). |y| ≥ 2^31 skips straight to a 1-term series, valid because the
|
|
1582
|
+
// only way such a y doesn't over/underflow outright is x within 2^-20 of 1.
|
|
1583
|
+
// 2. y*log2(x) in double-double, with an early overflow/underflow return once the
|
|
1584
|
+
// exponent product is unambiguously outside (-1075, 1024).
|
|
1585
|
+
// 3. 2^(that product): round to the nearest integer n — via the high-word bit trick
|
|
1586
|
+
// fdlibm uses, not float rounding, so the fractional remainder stays exact — evaluate
|
|
1587
|
+
// the P1..P5 minimax on the fraction, then splice n back in as a raw exponent-field
|
|
1588
|
+
// add, falling back to $math.pow_scalbn only when that add would underflow the
|
|
1589
|
+
// exponent field.
|
|
1590
|
+
// ON: delegates to the shared two-phase Ziv dd/td kernel — see $math.pow_transcend's own
|
|
1591
|
+
// comment above for the algorithm. CORE-MATH-class correctly rounded (0 misrounds on the
|
|
1592
|
+
// 5152-vector gate, test/pow-cr.js) at a measured ~13x runtime cost on gamma-heavy color
|
|
1593
|
+
// kernels (colorpq), hence opt-in rather than default.
|
|
1594
|
+
wat('math.pow_core', crPow
|
|
1595
|
+
? `(func $math.pow_core (param $x f64) (param $y f64) (result f64)
|
|
1596
|
+
(if (f64.eq (local.get $y) (f64.const 0.5))
|
|
1597
|
+
(then (return (f64.sqrt (local.get $x)))))
|
|
1598
|
+
(call $math.pow_transcend (local.get $x) (local.get $y)))`
|
|
1599
|
+
: `(func $math.pow_core (param $x f64) (param $y f64) (result f64)
|
|
1600
|
+
(local $ax f64) (local $u f64) (local $v f64) (local $w f64) (local $t f64) (local $r f64)
|
|
1601
|
+
(local $t1 f64) (local $t2 f64) (local $y1 f64) (local $p_h f64) (local $p_l f64) (local $z f64)
|
|
1602
|
+
(local $ss f64) (local $s2 f64) (local $s_h f64) (local $s_l f64) (local $t_h f64) (local $t_l f64)
|
|
1603
|
+
(local $z_h f64) (local $z_l f64) (local $bp_k f64) (local $dp_h_k f64) (local $dp_l_k f64)
|
|
1604
|
+
(local $ix i32) (local $hy i32) (local $iy i32) (local $j i32) (local $i i32) (local $k i32) (local $n i32)
|
|
1605
|
+
|
|
1606
|
+
;; y == 0.5 exactly (x > 0 here, always a valid sqrt domain): matches fdlibm/V8's own sqrt
|
|
1607
|
+
;; fast path, and f64.sqrt is correctly rounded so this can only help bit-exactness.
|
|
1608
|
+
(if (f64.eq (local.get $y) (f64.const 0.5))
|
|
1609
|
+
(then (return (f64.sqrt (local.get $x)))))
|
|
1610
|
+
|
|
1611
|
+
(local.set $ax (local.get $x))
|
|
1612
|
+
(local.set $ix (i32.wrap_i64 (i64.shr_u (i64.reinterpret_f64 (local.get $x)) (i64.const 32))))
|
|
1613
|
+
(local.set $hy (i32.wrap_i64 (i64.shr_u (i64.reinterpret_f64 (local.get $y)) (i64.const 32))))
|
|
1614
|
+
(local.set $iy (i32.and (local.get $hy) (i32.const 0x7fffffff)))
|
|
1615
|
+
|
|
1616
|
+
(if (i32.gt_u (local.get $iy) (i32.const 0x41e00000))
|
|
1617
|
+
(then
|
|
1618
|
+
;; |y| > 2^31: definite overflow/underflow unless x is within ~2^-20 of 1, in which
|
|
1619
|
+
;; case log(x) via a short series (x-x^2/2+x^3/3-x^4/4) suffices.
|
|
1620
|
+
(if (i32.gt_u (local.get $iy) (i32.const 0x43f00000))
|
|
1621
|
+
(then
|
|
1622
|
+
(if (i32.le_u (local.get $ix) (i32.const 0x3fefffff))
|
|
1623
|
+
(then (return (select (f64.const inf) (f64.const 0.0) (i32.lt_s (local.get $hy) (i32.const 0))))))
|
|
1624
|
+
(if (i32.ge_u (local.get $ix) (i32.const 0x3ff00000))
|
|
1625
|
+
(then (return (select (f64.const inf) (f64.const 0.0) (i32.gt_s (local.get $hy) (i32.const 0))))))))
|
|
1626
|
+
(if (i32.lt_u (local.get $ix) (i32.const 0x3fefffff))
|
|
1627
|
+
(then (return (select (f64.const inf) (f64.const 0.0) (i32.lt_s (local.get $hy) (i32.const 0))))))
|
|
1628
|
+
(if (i32.gt_u (local.get $ix) (i32.const 0x3ff00000))
|
|
1629
|
+
(then (return (select (f64.const inf) (f64.const 0.0) (i32.gt_s (local.get $hy) (i32.const 0))))))
|
|
1630
|
+
(local.set $t (f64.sub (local.get $ax) (f64.const 1.0)))
|
|
1631
|
+
(local.set $w (f64.mul (f64.mul (local.get $t) (local.get $t))
|
|
1632
|
+
(f64.sub (f64.const 0.5) (f64.mul (local.get $t)
|
|
1633
|
+
(f64.sub (f64.const 3.3333333333333331e-01) (f64.mul (local.get $t) (f64.const 0.25)))))))
|
|
1634
|
+
(local.set $u (f64.mul (f64.const 1.44269502162933349609e+00) (local.get $t)))
|
|
1635
|
+
(local.set $v (f64.sub (f64.mul (local.get $t) (f64.const 1.92596299112661746887e-08))
|
|
1636
|
+
(f64.mul (local.get $w) (f64.const 1.44269504088896338700e+00))))
|
|
1637
|
+
(local.set $t1 (f64.add (local.get $u) (local.get $v)))
|
|
1638
|
+
(local.set $t1 (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $t1)) (i64.const 0xffffffff00000000))))
|
|
1639
|
+
(local.set $t2 (f64.sub (local.get $v) (f64.sub (local.get $t1) (local.get $u)))))
|
|
1640
|
+
(else
|
|
1641
|
+
(local.set $n (i32.const 0))
|
|
1642
|
+
;; Subnormal x: scale into the normal range and remember the shift.
|
|
1643
|
+
(if (i32.lt_u (local.get $ix) (i32.const 0x00100000))
|
|
1644
|
+
(then
|
|
1645
|
+
(local.set $ax (f64.mul (local.get $ax) (f64.const 9007199254740992.0)))
|
|
1646
|
+
(local.set $n (i32.sub (local.get $n) (i32.const 53)))
|
|
1647
|
+
(local.set $ix (i32.wrap_i64 (i64.shr_u (i64.reinterpret_f64 (local.get $ax)) (i64.const 32))))))
|
|
1648
|
+
(local.set $n (i32.add (local.get $n) (i32.sub (i32.shr_u (local.get $ix) (i32.const 20)) (i32.const 0x3ff))))
|
|
1649
|
+
(local.set $j (i32.and (local.get $ix) (i32.const 0x000fffff)))
|
|
1650
|
+
(local.set $ix (i32.or (local.get $j) (i32.const 0x3ff00000)))
|
|
1651
|
+
;; Interval split: center the reduced mantissa on 1 (k=0, |x|<sqrt(3/2)) or 1.5
|
|
1652
|
+
;; (k=1, |x|<sqrt(3)) — whichever keeps s=(m-bp[k])/(m+bp[k]) smaller.
|
|
1653
|
+
(if (i32.le_u (local.get $j) (i32.const 0x0003988E))
|
|
1654
|
+
(then (local.set $k (i32.const 0)))
|
|
1655
|
+
(else (if (i32.lt_u (local.get $j) (i32.const 0x000BB67A))
|
|
1656
|
+
(then (local.set $k (i32.const 1)))
|
|
1657
|
+
(else
|
|
1658
|
+
(local.set $k (i32.const 0))
|
|
1659
|
+
(local.set $n (i32.add (local.get $n) (i32.const 1)))
|
|
1660
|
+
(local.set $ix (i32.sub (local.get $ix) (i32.const 0x00100000)))))))
|
|
1661
|
+
(local.set $ax (f64.reinterpret_i64
|
|
1662
|
+
(i64.or (i64.and (i64.reinterpret_f64 (local.get $ax)) (i64.const 0x00000000ffffffff))
|
|
1663
|
+
(i64.shl (i64.extend_i32_u (local.get $ix)) (i64.const 32)))))
|
|
1664
|
+
(local.set $bp_k (select (f64.const 1.5) (f64.const 1.0) (i32.eq (local.get $k) (i32.const 1))))
|
|
1665
|
+
(local.set $dp_h_k (select (f64.const 0.584962487220764160156) (f64.const 0.0) (i32.eq (local.get $k) (i32.const 1))))
|
|
1666
|
+
(local.set $dp_l_k (select (f64.const 1.35003920212974897128e-08) (f64.const 0.0) (i32.eq (local.get $k) (i32.const 1))))
|
|
1667
|
+
(local.set $u (f64.sub (local.get $ax) (local.get $bp_k)))
|
|
1668
|
+
(local.set $v (f64.div (f64.const 1.0) (f64.add (local.get $ax) (local.get $bp_k))))
|
|
1669
|
+
(local.set $ss (f64.mul (local.get $u) (local.get $v)))
|
|
1670
|
+
(local.set $s_h (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $ss)) (i64.const 0xffffffff00000000))))
|
|
1671
|
+
;; t_h ≈ (ax+bp[k]) with its low 32 bits cleared, built directly from ix's bits (half
|
|
1672
|
+
;; the exponent+mantissa, plus fdlibm's fixed per-k offsets) rather than an add+round.
|
|
1673
|
+
(local.set $t_h (f64.reinterpret_i64 (i64.shl
|
|
1674
|
+
(i64.extend_i32_u (i32.add (i32.add
|
|
1675
|
+
(i32.or (i32.shr_u (local.get $ix) (i32.const 1)) (i32.const 0x20000000))
|
|
1676
|
+
(i32.const 0x00080000))
|
|
1677
|
+
(i32.shl (local.get $k) (i32.const 18))))
|
|
1678
|
+
(i64.const 32))))
|
|
1679
|
+
(local.set $t_l (f64.sub (local.get $ax) (f64.sub (local.get $t_h) (local.get $bp_k))))
|
|
1680
|
+
(local.set $s_l (f64.mul (local.get $v)
|
|
1681
|
+
(f64.sub (f64.sub (local.get $u) (f64.mul (local.get $s_h) (local.get $t_h))) (f64.mul (local.get $s_h) (local.get $t_l)))))
|
|
1682
|
+
(local.set $s2 (f64.mul (local.get $ss) (local.get $ss)))
|
|
1683
|
+
(local.set $r (f64.mul (f64.mul (local.get $s2) (local.get $s2))
|
|
1684
|
+
(f64.add (f64.const 5.99999999999994648725e-01) (f64.mul (local.get $s2)
|
|
1685
|
+
(f64.add (f64.const 4.28571428578550184252e-01) (f64.mul (local.get $s2)
|
|
1686
|
+
(f64.add (f64.const 3.33333329818377432918e-01) (f64.mul (local.get $s2)
|
|
1687
|
+
(f64.add (f64.const 2.72728123808534006489e-01) (f64.mul (local.get $s2)
|
|
1688
|
+
(f64.add (f64.const 2.30660745775561754067e-01) (f64.mul (local.get $s2) (f64.const 2.06975017800338417784e-01)))))))))))))
|
|
1689
|
+
(local.set $r (f64.add (local.get $r) (f64.mul (local.get $s_l) (f64.add (local.get $s_h) (local.get $ss)))))
|
|
1690
|
+
(local.set $s2 (f64.mul (local.get $s_h) (local.get $s_h)))
|
|
1691
|
+
(local.set $t_h (f64.add (f64.add (f64.const 3.0) (local.get $s2)) (local.get $r)))
|
|
1692
|
+
(local.set $t_h (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $t_h)) (i64.const 0xffffffff00000000))))
|
|
1693
|
+
(local.set $t_l (f64.sub (local.get $r) (f64.sub (f64.sub (local.get $t_h) (f64.const 3.0)) (local.get $s2))))
|
|
1694
|
+
(local.set $u (f64.mul (local.get $s_h) (local.get $t_h)))
|
|
1695
|
+
(local.set $v (f64.add (f64.mul (local.get $s_l) (local.get $t_h)) (f64.mul (local.get $t_l) (local.get $ss))))
|
|
1696
|
+
(local.set $p_h (f64.add (local.get $u) (local.get $v)))
|
|
1697
|
+
(local.set $p_h (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $p_h)) (i64.const 0xffffffff00000000))))
|
|
1698
|
+
(local.set $p_l (f64.sub (local.get $v) (f64.sub (local.get $p_h) (local.get $u))))
|
|
1699
|
+
(local.set $z_h (f64.mul (f64.const 9.61796700954437255859e-01) (local.get $p_h)))
|
|
1700
|
+
(local.set $z_l (f64.add (f64.add
|
|
1701
|
+
(f64.mul (f64.const -7.02846165095275826516e-09) (local.get $p_h))
|
|
1702
|
+
(f64.mul (local.get $p_l) (f64.const 9.61796693925975554329e-01)))
|
|
1703
|
+
(local.get $dp_l_k)))
|
|
1704
|
+
(local.set $t (f64.convert_i32_s (local.get $n)))
|
|
1705
|
+
(local.set $t1 (f64.add (f64.add (f64.add (local.get $z_h) (local.get $z_l)) (local.get $dp_h_k)) (local.get $t)))
|
|
1706
|
+
(local.set $t1 (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $t1)) (i64.const 0xffffffff00000000))))
|
|
1707
|
+
(local.set $t2 (f64.sub (local.get $z_l)
|
|
1708
|
+
(f64.sub (f64.sub (f64.sub (local.get $t1) (local.get $t)) (local.get $dp_h_k)) (local.get $z_h))))))
|
|
1709
|
+
|
|
1710
|
+
;; Combine: (y1+y2)*(t1+t2) where y1 is y with its low 32 bits cleared, y2=y-y1 — a
|
|
1711
|
+
;; double-double multiply of y against log2(x).
|
|
1712
|
+
(local.set $y1 (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $y)) (i64.const 0xffffffff00000000))))
|
|
1713
|
+
(local.set $p_l (f64.add (f64.mul (f64.sub (local.get $y) (local.get $y1)) (local.get $t1)) (f64.mul (local.get $y) (local.get $t2))))
|
|
1714
|
+
(local.set $p_h (f64.mul (local.get $y1) (local.get $t1)))
|
|
1715
|
+
(local.set $z (f64.add (local.get $p_l) (local.get $p_h)))
|
|
1716
|
+
(local.set $j (i32.wrap_i64 (i64.shr_u (i64.reinterpret_f64 (local.get $z)) (i64.const 32))))
|
|
1717
|
+
(local.set $i (i32.wrap_i64 (i64.reinterpret_f64 (local.get $z))))
|
|
1718
|
+
|
|
1719
|
+
(if (i32.ge_s (local.get $j) (i32.const 0x40900000))
|
|
1720
|
+
(then
|
|
1721
|
+
(if (i32.ne (i32.or (i32.sub (local.get $j) (i32.const 0x40900000)) (local.get $i)) (i32.const 0))
|
|
1722
|
+
(then (return (f64.const inf)))
|
|
1723
|
+
(else (if (f64.gt (f64.add (local.get $p_l) (f64.const 8.0085662595372944372e-17)) (f64.sub (local.get $z) (local.get $p_h)))
|
|
1724
|
+
(then (return (f64.const inf)))))))
|
|
1725
|
+
(else (if (i32.ge_u (i32.and (local.get $j) (i32.const 0x7fffffff)) (i32.const 0x4090cc00))
|
|
1726
|
+
(then
|
|
1727
|
+
(if (i32.ne (i32.or (i32.sub (local.get $j) (i32.const 0xc090cc00)) (local.get $i)) (i32.const 0))
|
|
1728
|
+
(then (return (f64.const 0.0)))
|
|
1729
|
+
(else (if (f64.le (local.get $p_l) (f64.sub (local.get $z) (local.get $p_h)))
|
|
1730
|
+
(then (return (f64.const 0.0))))))))))
|
|
1731
|
+
|
|
1732
|
+
;; 2^(p_h+p_l): round to nearest integer n (bit trick, not float round, to keep the
|
|
1733
|
+
;; fractional remainder's low bits exact), evaluate the P1..P5 kernel on it, splice n back
|
|
1734
|
+
;; in as a raw exponent-field add.
|
|
1735
|
+
(local.set $i (i32.and (local.get $j) (i32.const 0x7fffffff)))
|
|
1736
|
+
(local.set $k (i32.sub (i32.shr_u (local.get $i) (i32.const 20)) (i32.const 0x3ff)))
|
|
1737
|
+
(local.set $n (i32.const 0))
|
|
1738
|
+
(if (i32.gt_u (local.get $i) (i32.const 0x3fe00000))
|
|
1739
|
+
(then
|
|
1740
|
+
(local.set $n (i32.add (local.get $j) (i32.shr_u (i32.const 0x00100000) (i32.add (local.get $k) (i32.const 1)))))
|
|
1741
|
+
(local.set $k (i32.sub (i32.shr_u (i32.and (local.get $n) (i32.const 0x7fffffff)) (i32.const 20)) (i32.const 0x3ff)))
|
|
1742
|
+
(local.set $t (f64.reinterpret_i64 (i64.shl
|
|
1743
|
+
(i64.extend_i32_u (i32.and (local.get $n) (i32.xor (i32.shr_u (i32.const 0x000fffff) (local.get $k)) (i32.const -1))))
|
|
1744
|
+
(i64.const 32))))
|
|
1745
|
+
(local.set $n (i32.shr_u (i32.or (i32.and (local.get $n) (i32.const 0x000fffff)) (i32.const 0x00100000)) (i32.sub (i32.const 20) (local.get $k))))
|
|
1746
|
+
(if (i32.lt_s (local.get $j) (i32.const 0)) (then (local.set $n (i32.sub (i32.const 0) (local.get $n)))))
|
|
1747
|
+
(local.set $p_h (f64.sub (local.get $p_h) (local.get $t)))))
|
|
1748
|
+
(local.set $t (f64.add (local.get $p_l) (local.get $p_h)))
|
|
1749
|
+
(local.set $t (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $t)) (i64.const 0xffffffff00000000))))
|
|
1750
|
+
(local.set $u (f64.mul (local.get $t) (f64.const 6.93147182464599609375e-01)))
|
|
1751
|
+
(local.set $v (f64.add (f64.mul (f64.sub (local.get $p_l) (f64.sub (local.get $t) (local.get $p_h))) (f64.const 6.93147180559945286227e-01))
|
|
1752
|
+
(f64.mul (local.get $t) (f64.const -1.90465429995776804525e-09))))
|
|
1753
|
+
(local.set $z (f64.add (local.get $u) (local.get $v)))
|
|
1754
|
+
(local.set $w (f64.sub (local.get $v) (f64.sub (local.get $z) (local.get $u))))
|
|
1755
|
+
(local.set $t (f64.mul (local.get $z) (local.get $z)))
|
|
1756
|
+
(local.set $t1 (f64.sub (local.get $z) (f64.mul (local.get $t)
|
|
1757
|
+
(f64.add (f64.const 1.66666666666666019037e-01) (f64.mul (local.get $t)
|
|
1758
|
+
(f64.add (f64.const -2.77777777770155933842e-03) (f64.mul (local.get $t)
|
|
1759
|
+
(f64.add (f64.const 6.61375632143793436117e-05) (f64.mul (local.get $t)
|
|
1760
|
+
(f64.add (f64.const -1.65339022054652515390e-06) (f64.mul (local.get $t) (f64.const 4.13813679705723846039e-08))))))))))))
|
|
1761
|
+
(local.set $r (f64.sub
|
|
1762
|
+
(f64.div (f64.mul (local.get $z) (local.get $t1)) (f64.sub (local.get $t1) (f64.const 2.0)))
|
|
1763
|
+
(f64.add (local.get $w) (f64.mul (local.get $z) (local.get $w)))))
|
|
1764
|
+
(local.set $z (f64.sub (f64.const 1.0) (f64.sub (local.get $r) (local.get $z))))
|
|
1765
|
+
(local.set $j (i32.wrap_i64 (i64.shr_u (i64.reinterpret_f64 (local.get $z)) (i64.const 32))))
|
|
1766
|
+
(local.set $j (i32.add (local.get $j) (i32.shl (local.get $n) (i32.const 20))))
|
|
1767
|
+
(if (result f64) (i32.le_s (i32.shr_s (local.get $j) (i32.const 20)) (i32.const 0))
|
|
1768
|
+
(then (call $math.pow_scalbn (local.get $z) (local.get $n)))
|
|
1769
|
+
(else (f64.reinterpret_i64 (i64.or (i64.and (i64.reinterpret_f64 (local.get $z)) (i64.const 0x00000000ffffffff))
|
|
1770
|
+
(i64.shl (i64.extend_i32_u (local.get $j)) (i64.const 32)))))))`,
|
|
1771
|
+
crPow ? ['math.pow_transcend'] : ['math.pow_scalbn'])
|
|
1772
|
+
|
|
1773
|
+
// $math.pow_fold — Math.pow(x, C) for a COMPILE-TIME-CONSTANT non-integer exponent C under
|
|
1774
|
+
// optimize.crPow (module/math.js's emitPow const-exponent fold, and its SIMD twin
|
|
1775
|
+
// $math.pow_fold_v above / src/optimize/vectorize.js's PPC_CALL2 entry) — see the authoritative
|
|
1776
|
+
// comment above emitPow for the flag's full semantics. Off crPow, emitPow lowers the same
|
|
1777
|
+
// constant-exponent case to exp(c·log(x)) directly (no separate wat function); this one is
|
|
1778
|
+
// registered ONLY when crPow is on. Shares $math.pow_transcend's kernel with $math.pow_core —
|
|
1779
|
+
// see that function's comment for the algorithm; c needs no hi/lo pre-split (the kernel's
|
|
1780
|
+
// multiply is twoProd-based, Dekker-splitting both operands internally). Bypasses the
|
|
1781
|
+
// $math.pow wrapper's special-case ladder, so it replicates only the x-dependent slice of it
|
|
1782
|
+
// here (NaN/±Inf/±0/x<0) — the y-dependent branches (y==0/NaN/±Inf/±1, integer y) are ALL
|
|
1783
|
+
// statically dead, since emitPow only reaches this fold when c is a finite literal that is
|
|
1784
|
+
// none of those. x==1 needs no case either: log2(1) evaluates to exactly 0 (dd) for any c
|
|
1785
|
+
// (verified by the differential test), so the result is exactly 1.0.
|
|
1786
|
+
if (crPow) {
|
|
1787
|
+
wat('math.pow_fold', `(func $math.pow_fold (param $x f64) (param $c f64) (result f64)
|
|
1788
|
+
;; NaN propagates (return x itself, preserving payload bits — same as $math.pow's own
|
|
1789
|
+
;; NaN checks: no arithmetic runs, so nothing mints a non-canonical NaN).
|
|
1790
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
1791
|
+
;; |x| == Infinity: magnitude is c>0 ? Inf : 0, UNSIGNED — c is never an odd integer here
|
|
1792
|
+
;; (emitPow's guard excludes every integer c), so the sign never flips, matching
|
|
1793
|
+
;; Math.pow(±Infinity, non-integer c) exactly.
|
|
1794
|
+
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf))
|
|
1795
|
+
(then (return (select (f64.const inf) (f64.const 0.0) (f64.gt (local.get $c) (f64.const 0.0))))))
|
|
1796
|
+
;; x == ±0: the reciprocal selection (c<0 ? Inf : 0), also unsigned for the same reason.
|
|
1797
|
+
(if (f64.eq (local.get $x) (f64.const 0.0))
|
|
1798
|
+
(then (return (select (f64.const inf) (f64.const 0.0) (f64.lt (local.get $c) (f64.const 0.0))))))
|
|
1799
|
+
;; x < 0 with non-integer c → NaN (matches $math.pow's own x<0 branch).
|
|
1800
|
+
(if (f64.lt (local.get $x) (f64.const 0.0)) (then (return (f64.const nan))))
|
|
1801
|
+
(call $math.pow_transcend (local.get $x) (local.get $c)))`, ['math.pow_transcend'])
|
|
1802
|
+
} // if (crPow)
|
|
937
1803
|
|
|
938
1804
|
// fdlibm atan: 4-region argument reduction onto |r| ≤ tan(π/16), then an
|
|
939
1805
|
// 11-term odd polynomial split into even/odd parts. Accurate to <1 ulp —
|
|
940
1806
|
// the old Taylor series was ~2e-6 off near |x|=0.5. Drives asin/acos/atan2.
|
|
1807
|
+
// Fast atan: sign symmetry (work on |x|), two-stage reduction onto [0, tan(π/8)] — |x|>1 →
|
|
1808
|
+
// π/2−atan(1/x), then t>tan(π/8) → π/8+atan((t−C)/(1+Ct)) — then a degree-5 minimax t·P(t²).
|
|
1809
|
+
// Replaces the fdlibm 4-way / 11-term / extended-precision form (correctly-rounded but ~3× the
|
|
1810
|
+
// ops). Max rel err 6e-10 over all of ℝ — well within jz's ~1e-9 transcendental budget. asin =
|
|
1811
|
+
// atan(x/√(1−x²)) and acos = π/2−asin inherit it, so all three drop from ~1.6–2.3× to under V8.
|
|
941
1812
|
wat('math.atan', `(func $math.atan (param $x f64) (result f64)
|
|
942
|
-
(local $
|
|
943
|
-
|
|
944
|
-
;; NaN passes through unchanged.
|
|
1813
|
+
(local $t f64) (local $u f64) (local $r f64) (local $off f64) (local $flip i32)
|
|
1814
|
+
;; NaN passes through; ±0 returns x (preserves sign of zero); ±Inf flows through (1/Inf=0 → π/2).
|
|
945
1815
|
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
946
|
-
(local.
|
|
947
|
-
|
|
948
|
-
(
|
|
949
|
-
|
|
950
|
-
(if (f64.
|
|
1816
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
1817
|
+
(local.set $t (f64.abs (local.get $x)))
|
|
1818
|
+
(local.set $off (f64.const 0.0))
|
|
1819
|
+
(local.set $flip (i32.const 0))
|
|
1820
|
+
(if (f64.gt (local.get $t) (f64.const 1.0))
|
|
1821
|
+
(then (local.set $t (f64.div (f64.const 1.0) (local.get $t))) (local.set $flip (i32.const 1))))
|
|
1822
|
+
(if (f64.gt (local.get $t) (f64.const 0.41421356237309503))
|
|
951
1823
|
(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)))`)
|
|
1824
|
+
(local.set $t (f64.div (f64.sub (local.get $t) (f64.const 0.41421356237309503))
|
|
1825
|
+
(f64.add (f64.const 1.0) (f64.mul (f64.const 0.41421356237309503) (local.get $t)))))
|
|
1826
|
+
(local.set $off (f64.const 0.39269908169872414))))
|
|
1827
|
+
(local.set $u (f64.mul (local.get $t) (local.get $t)))
|
|
1828
|
+
(local.set $r (f64.add (local.get $off)
|
|
1829
|
+
(f64.mul (local.get $t)
|
|
1830
|
+
(f64.add (f64.const 0.99999999939667072)
|
|
1831
|
+
(f64.mul (local.get $u)
|
|
1832
|
+
(f64.add (f64.const -0.33333307625846248)
|
|
1833
|
+
(f64.mul (local.get $u)
|
|
1834
|
+
(f64.add (f64.const 0.19998216947828790)
|
|
1835
|
+
(f64.mul (local.get $u)
|
|
1836
|
+
(f64.add (f64.const -0.14240083011830104)
|
|
1837
|
+
(f64.mul (local.get $u)
|
|
1838
|
+
(f64.add (f64.const 0.10573479828448784)
|
|
1839
|
+
(f64.mul (local.get $u) (f64.const -0.060347904072425573))))))))))))))
|
|
1840
|
+
(if (local.get $flip) (then (local.set $r (f64.sub (f64.const 1.5707963267948966) (local.get $r)))))
|
|
1841
|
+
(f64.copysign (local.get $r) (local.get $x)))`)
|
|
1009
1842
|
|
|
1843
|
+
// Fast asin: small-argument poly a + a·u·R(u) (u=a²) with the standard half-angle reduction for
|
|
1844
|
+
// |x|>0.5 — a = sqrt((1−|x|)/2) maps the singular end to the smooth domain, asin = π/2 − 2·poly.
|
|
1845
|
+
// One sqrt only on the upper half, no atan/div. R is a degree-6 minimax on [0,0.25]; max rel err
|
|
1846
|
+
// ~2.6e-10. Replaces asin = atan(x/√(1−x²)) (which paid a div + atan's own reductions).
|
|
1010
1847
|
wat('math.asin', `(func $math.asin (param $x f64) (result f64)
|
|
1011
|
-
|
|
1012
|
-
;;
|
|
1013
|
-
(if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1848
|
+
(local $ax f64) (local $a f64) (local $u f64) (local $r f64)
|
|
1849
|
+
;; |x|>1 → NaN (covers ±Inf); NaN propagates (the >1 test is false, poly carries NaN through).
|
|
1850
|
+
(if (f64.gt (f64.abs (local.get $x)) (f64.const 1.0)) (then (return (f64.const nan))))
|
|
1851
|
+
(local.set $ax (f64.abs (local.get $x)))
|
|
1852
|
+
(if (f64.le (local.get $ax) (f64.const 0.5))
|
|
1853
|
+
(then (local.set $a (local.get $ax)))
|
|
1854
|
+
(else (local.set $a (f64.sqrt (f64.mul (f64.const 0.5) (f64.sub (f64.const 1.0) (local.get $ax)))))))
|
|
1855
|
+
(local.set $u (f64.mul (local.get $a) (local.get $a)))
|
|
1856
|
+
(local.set $r (f64.add (local.get $a) (f64.mul (f64.mul (local.get $a) (local.get $u))
|
|
1857
|
+
(f64.add (f64.const 0.16666666715486264)
|
|
1858
|
+
(f64.mul (local.get $u)
|
|
1859
|
+
(f64.add (f64.const 0.074999892151409259)
|
|
1860
|
+
(f64.mul (local.get $u)
|
|
1861
|
+
(f64.add (f64.const 0.044648555271317079)
|
|
1862
|
+
(f64.mul (local.get $u)
|
|
1863
|
+
(f64.add (f64.const 0.030259196387355945)
|
|
1864
|
+
(f64.mul (local.get $u)
|
|
1865
|
+
(f64.add (f64.const 0.023661273034955098)
|
|
1866
|
+
(f64.mul (local.get $u)
|
|
1867
|
+
(f64.add (f64.const 0.010472588920432560)
|
|
1868
|
+
(f64.mul (local.get $u) (f64.const 0.031028862087420162))))))))))))))))
|
|
1869
|
+
(if (f64.gt (local.get $ax) (f64.const 0.5))
|
|
1870
|
+
(then (local.set $r (f64.sub (f64.const 1.5707963267948966) (f64.mul (f64.const 2.0) (local.get $r))))))
|
|
1871
|
+
(f64.copysign (local.get $r) (local.get $x)))`)
|
|
1017
1872
|
|
|
1018
1873
|
wat('math.acos', `(func $math.acos (param $x f64) (result f64)
|
|
1019
1874
|
(f64.sub (f64.const ${HALF_PI}) (call $math.asin (local.get $x))))`)
|
|
@@ -1080,19 +1935,51 @@ export default (ctx) => {
|
|
|
1080
1935
|
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const nan))))
|
|
1081
1936
|
(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
1937
|
|
|
1938
|
+
// Bit-hack initial guess (divide the IEEE exponent by 3 via an integer divide of the raw bits,
|
|
1939
|
+
// plus a magic bias) then 3 Newton steps t = (2t + a/t²)/3 — quadratic convergence, max rel err
|
|
1940
|
+
// ~1e-12 over the whole f64 range. Replaces the old `pow(x,1/3)` seed: no exp/log call, ~3-4×
|
|
1941
|
+
// faster (the colorconv / Oklab hot path is 3 cbrt per pixel), and a program whose only
|
|
1942
|
+
// transcendental is cbrt no longer pulls the pow/exp/log stdlib. Not bit-identical to V8's fdlibm
|
|
1943
|
+
// cbrt (neither was the pow form) — jz's transcendentals are fast minimax/Newton approximations.
|
|
1083
1944
|
wat('math.cbrt', `(func $math.cbrt (param $x f64) (result f64)
|
|
1084
|
-
(local $
|
|
1085
|
-
;; ±Infinity
|
|
1945
|
+
(local $a f64) (local $t f64) (local $s f64)
|
|
1946
|
+
;; NaN / ±Infinity / ±0 pass through unchanged (sign of zero preserved).
|
|
1086
1947
|
(if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (local.get $x))))
|
|
1087
1948
|
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
1088
|
-
(
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1949
|
+
(local.set $a (f64.abs (local.get $x)))
|
|
1950
|
+
(local.set $s (f64.const 1.0))
|
|
1951
|
+
;; subnormal |x| < 2^-1022: scale up by 2^60 so the exponent split is valid; cbrt(2^60) = 2^20.
|
|
1952
|
+
(if (f64.lt (local.get $a) (f64.const 2.2250738585072014e-308))
|
|
1953
|
+
(then (local.set $a (f64.mul (local.get $a) (f64.const 1152921504606846976.0)))
|
|
1954
|
+
(local.set $s (f64.const 9.5367431640625e-07))))
|
|
1955
|
+
(local.set $t (f64.reinterpret_i64
|
|
1956
|
+
(i64.add (i64.div_u (i64.reinterpret_f64 (local.get $a)) (i64.const 3)) (i64.const 0x2A9F7893BF800000))))
|
|
1957
|
+
(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)))
|
|
1958
|
+
(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)))
|
|
1959
|
+
(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)))
|
|
1960
|
+
(local.set $t (f64.mul (local.get $t) (local.get $s)))
|
|
1961
|
+
(if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.neg (local.get $t))) (else (local.get $t))))`)
|
|
1962
|
+
|
|
1963
|
+
// Fifth root of v ≥ 0 — same bit-hack seed (÷5 of the raw bits) + 3 Newton steps t=(4t+v/t⁴)/5.
|
|
1964
|
+
// Caller (constant-exponent pow with denominator 5, e.g. the sRGB 2.4 gamma) guarantees v ≥ 0.
|
|
1965
|
+
wat('math.fifthroot', `(func $math.fifthroot (param $v f64) (result f64)
|
|
1966
|
+
(local $t f64) (local $s f64) (local $q f64)
|
|
1967
|
+
(if (i32.eqz (call $math.isFinite (local.get $v))) (then (return (local.get $v))))
|
|
1968
|
+
(if (f64.eq (local.get $v) (f64.const 0.0)) (then (return (f64.const 0.0))))
|
|
1969
|
+
(local.set $s (f64.const 1.0))
|
|
1970
|
+
;; subnormal: scale by 2^100 = (2^20)^5; fifthroot(2^100) = 2^20
|
|
1971
|
+
(if (f64.lt (local.get $v) (f64.const 2.2250738585072014e-308))
|
|
1972
|
+
(then (local.set $v (f64.mul (local.get $v) (f64.const 1.2676506002282294e30)))
|
|
1973
|
+
(local.set $s (f64.const 9.5367431640625e-07))))
|
|
1974
|
+
(local.set $t (f64.reinterpret_i64
|
|
1975
|
+
(i64.add (i64.div_u (i64.reinterpret_f64 (local.get $v)) (i64.const 5)) (i64.const 0x3325E66666666800))))
|
|
1976
|
+
(local.set $q (f64.mul (local.get $t) (local.get $t)))
|
|
1977
|
+
(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)))
|
|
1978
|
+
(local.set $q (f64.mul (local.get $t) (local.get $t)))
|
|
1979
|
+
(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)))
|
|
1980
|
+
(local.set $q (f64.mul (local.get $t) (local.get $t)))
|
|
1981
|
+
(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)))
|
|
1982
|
+
(f64.mul (local.get $t) (local.get $s)))`)
|
|
1096
1983
|
|
|
1097
1984
|
// Small finite-test helper (NaN→0, ±Inf→0, finite→1). Used by transcendental
|
|
1098
1985
|
// functions that need to short-circuit on infinite inputs.
|