jz 0.5.0 → 0.6.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 +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +281 -142
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +461 -185
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +591 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +600 -205
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -2997
- package/src/jzify.js +0 -1553
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/module/math.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Math module - Math.sin, Math.cos, Math.sqrt, Math.PI, etc.
|
|
3
3
|
*
|
|
4
4
|
* Module API:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
5
|
+
* - reg('math.X', deps, args => WasmNode) — emit handler + declarative stdlib deps
|
|
6
|
+
* - wat('math.X', `(func …)`) — WAT stdlib via bridge
|
|
7
|
+
* - deps({ 'math.X': ['dep'] }) - WAT stdlib→stdlib deps (expanded transitively)
|
|
8
8
|
*
|
|
9
9
|
* Prepare resolves Math.sin(x) → ['()', 'math.sin', x]
|
|
10
10
|
* Compile looks up ctx.core.emit['math.sin'] and calls it.
|
|
@@ -12,18 +12,27 @@
|
|
|
12
12
|
* @module math
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { typed, asF64, asI32, toI32, toNumF64, temp, arrayLoop, isLit, litVal } from '../src/ir.js'
|
|
16
|
-
import { emit } from '../src/
|
|
17
|
-
import {
|
|
18
|
-
import { repOf } from '../src/
|
|
15
|
+
import { typed, asF64, asI32, toI32, toNumF64, temp, arrayLoop, isLit, litVal, isPureIR } from '../src/ir.js'
|
|
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'
|
|
19
19
|
|
|
20
20
|
export default (ctx) => {
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// Math.random seeding. DEFAULT: entropy-seeded once from the host on first use (crypto under
|
|
22
|
+
// host:'js', `random_get` under WASI), so randomness "just works" and isn't silently reproducible.
|
|
23
|
+
// `randomSeed: <n>` picks a fixed seed for a reproducible sequence; `true` forces entropy explicitly.
|
|
24
|
+
// Either way jz emits the randomness syscall only when `Math.random` is actually used.
|
|
25
|
+
const rngEntropy = ctx.transform.randomSeed === undefined || ctx.transform.randomSeed === true
|
|
26
|
+
const rngSeedConst = typeof ctx.transform.randomSeed === 'number'
|
|
27
|
+
? ((ctx.transform.randomSeed >>> 0) || 1) // xorshift dies on 0 → floor at 1
|
|
28
|
+
: 12345
|
|
29
|
+
deps({
|
|
30
|
+
'math.sin': ['math.sin_core'],
|
|
31
|
+
'math.cos': ['math.cos_core'],
|
|
32
|
+
'math.sin_core': [],
|
|
33
|
+
'math.cos_core': [],
|
|
26
34
|
'math.tan': ['math.sin', 'math.cos'],
|
|
35
|
+
'math.exp': ['math.exp2'],
|
|
27
36
|
'math.expm1': ['math.exp'],
|
|
28
37
|
'math.log2': ['math.log'],
|
|
29
38
|
'math.log1p': ['math.log'],
|
|
@@ -40,7 +49,6 @@ export default (ctx) => {
|
|
|
40
49
|
'math.cbrt': ['math.isFinite', 'math.pow'],
|
|
41
50
|
'math.sumPrecise': ['__ptr_offset', '__len', '__alloc'],
|
|
42
51
|
})
|
|
43
|
-
|
|
44
52
|
// Helpers: all math ops take f64 and return f64. Args go through ToNumber
|
|
45
53
|
// (toNumF64) — ECMA Math methods coerce each argument, so null→0, undefined→NaN.
|
|
46
54
|
const f = (op, a) => typed([op, toNumF64(a, emit(a))], 'f64')
|
|
@@ -56,13 +64,72 @@ export default (ctx) => {
|
|
|
56
64
|
}
|
|
57
65
|
return false
|
|
58
66
|
}
|
|
67
|
+
// Emit-time fast path: call $math.sin_core/$math.cos_core directly instead of
|
|
68
|
+
// the $math.sin/$math.cos wrappers. sin_core still runs the isFinite guard
|
|
69
|
+
// (finite operands can overflow to ±Inf); this only saves a call indirection.
|
|
70
|
+
const isBoundName = (name) =>
|
|
71
|
+
typeof name === 'string' && (
|
|
72
|
+
repOf(name) != null ||
|
|
73
|
+
ctx.func.locals?.has(name) ||
|
|
74
|
+
ctx.func.current?.params?.some(p => p.name === name)
|
|
75
|
+
)
|
|
76
|
+
const isSinCoreFastPath = (src) => {
|
|
77
|
+
if (src == null) return false
|
|
78
|
+
if (typeof src === 'number') return Number.isFinite(src)
|
|
79
|
+
if (typeof src === 'string') return isIntCertain(src) || isBoundName(src)
|
|
80
|
+
if (!Array.isArray(src)) return false
|
|
81
|
+
const op = src[0]
|
|
82
|
+
// jz source literals: [null, n], [, bool], [null, null]
|
|
83
|
+
if (op == null && src.length === 2) {
|
|
84
|
+
const v = src[1]
|
|
85
|
+
if (typeof v === 'number') return Number.isFinite(v)
|
|
86
|
+
if (typeof v === 'boolean') return true
|
|
87
|
+
if (v == null) return true
|
|
88
|
+
}
|
|
89
|
+
if (op === 'literal') {
|
|
90
|
+
const v = src[1]
|
|
91
|
+
return typeof v === 'number' && Number.isFinite(v)
|
|
92
|
+
}
|
|
93
|
+
if (op === 'global' || op === 'param') return true
|
|
94
|
+
if (op === '()') {
|
|
95
|
+
const fn = src[1]
|
|
96
|
+
if (fn === 'math.PI' || fn === 'math.E' || fn === 'math.LN2' || fn === 'math.SQRT2') return true
|
|
97
|
+
if (typeof fn === 'string' && fn.startsWith('math.')) {
|
|
98
|
+
const finiteOps = new Set([
|
|
99
|
+
'math.abs', 'math.floor', 'math.ceil', 'math.trunc', 'math.round', 'math.sqrt',
|
|
100
|
+
'math.sin', 'math.cos', 'math.tan', 'math.imul', 'math.clz32',
|
|
101
|
+
])
|
|
102
|
+
if (finiteOps.has(fn)) return src.slice(2).every(isSinCoreFastPath)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (op === '|' && src.length === 3) return isSinCoreFastPath(src[1]) && isSinCoreFastPath(src[2])
|
|
106
|
+
if (op === '%' || op === '&' || op === '^' || op === '<<' || op === '>>' || op === '>>>') {
|
|
107
|
+
return src.slice(1).every(isSinCoreFastPath)
|
|
108
|
+
}
|
|
109
|
+
if (op === '+' || op === '-' || op === '*' || op === '**') {
|
|
110
|
+
return src.slice(1).every(isSinCoreFastPath)
|
|
111
|
+
}
|
|
112
|
+
if (op === '/') return isSinCoreFastPath(src[1]) && isSinCoreFastPath(src[2])
|
|
113
|
+
if (op === '?:' && src.length === 4) return isSinCoreFastPath(src[2]) && isSinCoreFastPath(src[3])
|
|
114
|
+
if ((op === '&&' || op === '||') && src.length === 3) return isSinCoreFastPath(src[1]) && isSinCoreFastPath(src[2])
|
|
115
|
+
if (op === '!' && src.length === 2) return isSinCoreFastPath(src[1])
|
|
116
|
+
if (op === '[]' && src.length === 3) {
|
|
117
|
+
// Chord tables / semitone indices — index is int-shaped, element is a small int.
|
|
118
|
+
return isSinCoreFastPath(src[2]) || isIntCertain(src[2])
|
|
119
|
+
}
|
|
120
|
+
if (op === '.' && src.length === 3 && src[2] === 'length') {
|
|
121
|
+
return typeof src[1] === 'string' || isSinCoreFastPath(src[1])
|
|
122
|
+
}
|
|
123
|
+
if (op === '.' && src.length === 3) return isIntCertain(src)
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
59
126
|
const fInt = (op, a) => isIntCertain(a) ? asF64(emit(a)) : f(op, a)
|
|
60
127
|
// ECMA Math methods perform ToNumber on each argument. toNumF64 short-circuits
|
|
61
128
|
// for known-number nodes, and routes everything else through __to_num so null→0,
|
|
62
129
|
// undefined→NaN, and strings get parsed. Without this, raw NaN-boxed pointers
|
|
63
130
|
// (null/undefined/strings) would propagate through math.log etc. and surface
|
|
64
131
|
// as the original null/undefined sentinel after decode.
|
|
65
|
-
const
|
|
132
|
+
const fn = (name, ...args) => typed(['call', `$${name}`, ...args.map(a => toNumF64(a, emit(a)))], 'f64')
|
|
66
133
|
|
|
67
134
|
// Canonicalize a possibly-NaN f64 result. A wasm arithmetic op that mints a
|
|
68
135
|
// fresh NaN (f64.sqrt of a negative, f64.min/max with a NaN operand) leaves
|
|
@@ -80,6 +147,25 @@ export default (ctx) => {
|
|
|
80
147
|
['f64.ne', ['local.get', `$${t}`], ['local.get', `$${t}`]]]], 'f64')
|
|
81
148
|
}
|
|
82
149
|
|
|
150
|
+
// sqrt(x) needs no NaN-canon when its argument is provably ≥ 0 with no spurious NaN:
|
|
151
|
+
// the result is then a normal non-negative f64 (or +0 / +inf, or a propagated input
|
|
152
|
+
// NaN that is already canonical), never a freshly-minted ±NaN. A sum of pure squares
|
|
153
|
+
// — the vector-length idiom sqrt(x*x + y*y + …) — is exactly that: every term is ≥ +0
|
|
154
|
+
// (even ±0·±0 = +0, ±inf² = +inf), the sum never cancels to inf−inf, and any NaN can
|
|
155
|
+
// only come from a NaN input (already the canonical number-NaN). So a distance /
|
|
156
|
+
// normalize loop sheds the per-sqrt select + f64.ne + local on its critical path.
|
|
157
|
+
// `pure` excludes call/load/tee, so two structurally-equal operands of a `*` really
|
|
158
|
+
// are the same value (a genuine square), not two side-effecting evaluations.
|
|
159
|
+
const pureF64 = (n) => !Array.isArray(n) ? true :
|
|
160
|
+
(n[0] === 'local.get' || n[0] === 'global.get' || n[0] === 'f64.const' || n[0] === 'i32.const' || n[0] === 'i64.const') ? true :
|
|
161
|
+
(n[0] === 'f64.add' || n[0] === 'f64.sub' || n[0] === 'f64.mul' || n[0] === 'f64.div' || n[0] === 'f64.neg' || n[0] === 'f64.abs' || n[0] === 'f64.convert_i32_s' || n[0] === 'f64.convert_i32_u') ? n.slice(1).every(pureF64) : false
|
|
162
|
+
const sameIR = (a, b) => Array.isArray(a) !== Array.isArray(b) ? false : !Array.isArray(a) ? a === b : (a.length === b.length && a.every((x, i) => sameIR(x, b[i])))
|
|
163
|
+
const nonNegF64 = (n) => !Array.isArray(n) ? false :
|
|
164
|
+
n[0] === 'f64.mul' ? (pureF64(n[1]) && sameIR(n[1], n[2])) : // x·x ≥ 0
|
|
165
|
+
n[0] === 'f64.add' ? (nonNegF64(n[1]) && nonNegF64(n[2])) : // (≥0) + (≥0) ≥ 0
|
|
166
|
+
n[0] === 'f64.const' ? (typeof n[1] === 'number' && n[1] >= 0) : false
|
|
167
|
+
const sqrtIR = (a) => { const ir = f('f64.sqrt', a); return nonNegF64(ir[1]) ? ir : canon(ir) }
|
|
168
|
+
|
|
83
169
|
// Constants
|
|
84
170
|
ctx.core.emit['math.PI'] = () => typed(['f64.const', Math.PI], 'f64')
|
|
85
171
|
ctx.core.emit['math.E'] = () => typed(['f64.const', Math.E], 'f64')
|
|
@@ -105,7 +191,7 @@ export default (ctx) => {
|
|
|
105
191
|
// Built-in WASM ops. sqrt/min/max mint a fresh NaN (sqrt of a negative, min/max
|
|
106
192
|
// with a NaN operand) whose sign is platform-nondeterministic — `canon` folds it
|
|
107
193
|
// back to the canonical pattern. abs/floor/ceil/trunc never produce a new NaN.
|
|
108
|
-
ctx.core.emit['math.sqrt'] = a =>
|
|
194
|
+
ctx.core.emit['math.sqrt'] = a => sqrtIR(a)
|
|
109
195
|
ctx.core.emit['math.abs'] = a => f('f64.abs', a)
|
|
110
196
|
ctx.core.emit['math.floor'] = a => fInt('f64.floor', a)
|
|
111
197
|
ctx.core.emit['math.ceil'] = a => fInt('f64.ceil', a)
|
|
@@ -151,45 +237,133 @@ export default (ctx) => {
|
|
|
151
237
|
ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64', toNumF64(a, emit(a))]], 'f64')
|
|
152
238
|
|
|
153
239
|
// Sign
|
|
154
|
-
|
|
240
|
+
reg('math.sign', ['math.sign'], a => fn('math.sign', a))
|
|
155
241
|
|
|
156
|
-
// Trig
|
|
157
|
-
ctx.core.emit['math.sin'] =
|
|
158
|
-
|
|
159
|
-
|
|
242
|
+
// Trig — isSinCoreFastPath skips the $math.sin/$math.cos wrapper call.
|
|
243
|
+
ctx.core.emit['math.sin'] = dual(
|
|
244
|
+
emitter(['math.sin'], a => fn('math.sin', a)),
|
|
245
|
+
emitter(['math.sin_core'], a => fn('math.sin_core', a)),
|
|
246
|
+
isSinCoreFastPath)
|
|
247
|
+
ctx.core.emit['math.cos'] = dual(
|
|
248
|
+
emitter(['math.cos'], a => fn('math.cos', a)),
|
|
249
|
+
emitter(['math.cos_core'], a => fn('math.cos_core', a)),
|
|
250
|
+
isSinCoreFastPath)
|
|
251
|
+
reg('math.tan', ['math.tan'], a => fn('math.tan', a))
|
|
160
252
|
|
|
161
253
|
// Inverse trig
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
254
|
+
reg('math.asin', ['math.asin'], a => fn('math.asin', a))
|
|
255
|
+
reg('math.acos', ['math.acos'], a => fn('math.acos', a))
|
|
256
|
+
reg('math.atan', ['math.atan'], a => fn('math.atan', a))
|
|
257
|
+
reg('math.atan2', ['math.atan2'], (a, b) => fn('math.atan2', a, b))
|
|
166
258
|
|
|
167
259
|
// Hyperbolic
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
260
|
+
reg('math.sinh', ['math.sinh'], a => fn('math.sinh', a))
|
|
261
|
+
reg('math.cosh', ['math.cosh'], a => fn('math.cosh', a))
|
|
262
|
+
reg('math.tanh', ['math.tanh'], a => fn('math.tanh', a))
|
|
171
263
|
|
|
172
264
|
// Inverse hyperbolic
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
265
|
+
reg('math.asinh', ['math.asinh'], a => fn('math.asinh', a))
|
|
266
|
+
reg('math.acosh', ['math.acosh'], a => fn('math.acosh', a))
|
|
267
|
+
reg('math.atanh', ['math.atanh'], a => fn('math.atanh', a))
|
|
176
268
|
|
|
177
269
|
// Exponential and logarithmic
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// Power
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
270
|
+
reg('math.exp', ['math.exp'], a => fn('math.exp', a))
|
|
271
|
+
reg('math.expm1', ['math.expm1'], a => fn('math.expm1', a))
|
|
272
|
+
reg('math.log', ['math.log'], a => fn('math.log', a))
|
|
273
|
+
reg('math.log2', ['math.log2'], a => fn('math.log2', a))
|
|
274
|
+
reg('math.log10', ['math.log10'], a => fn('math.log10', a))
|
|
275
|
+
reg('math.log1p', ['math.log1p'], a => fn('math.log1p', a))
|
|
276
|
+
|
|
277
|
+
// Power. Constant-integer-exponent `Math.pow(x,n)` / `x ** n` (|n| ≤ POW_FOLD_MAX)
|
|
278
|
+
// lower to inline square-and-multiply instead of a $math.pow call. The fold is
|
|
279
|
+
// bit-identical to $math.pow's integer fast path: that path runs the same LSB-first
|
|
280
|
+
// square-and-multiply, and an f64 product's magnitude is the rounded product of the
|
|
281
|
+
// operand magnitudes regardless of sign — so multiplying the *signed* base reproduces
|
|
282
|
+
// both the exact bits and the result sign (negative iff x<0 ∧ n odd, which is exactly
|
|
283
|
+
// its `neg_base`). A program whose only pow use is folded then never pulls the
|
|
284
|
+
// math.pow/exp/log stdlib. `**`'s exponent is parsed as a bare number (incl. negatives).
|
|
285
|
+
const POW_FOLD_MAX = 16
|
|
286
|
+
const get = name => ['local.get', `$${name}`]
|
|
287
|
+
const constInt = b => {
|
|
288
|
+
const v = typeof b === 'number' ? b
|
|
289
|
+
: (Array.isArray(b) && b.length === 2 && b[0] == null && typeof b[1] === 'number') ? b[1]
|
|
290
|
+
: null
|
|
291
|
+
return v != null && Number.isInteger(v) ? v : null
|
|
292
|
+
}
|
|
293
|
+
const foldPow = (a, n) => {
|
|
294
|
+
const baseIR = toNumF64(a, emit(a))
|
|
295
|
+
// pow(x,0) === 1 for every x (NaN/±0/±Inf included). Keep the base's side
|
|
296
|
+
// effects (a call, a throwing valueOf), discard its value, yield 1.
|
|
297
|
+
if (n === 0) return isPureIR(baseIR)
|
|
298
|
+
? typed(['f64.const', 1], 'f64')
|
|
299
|
+
: typed(['block', ['result', 'f64'], ['drop', baseIR], ['f64.const', 1]], 'f64')
|
|
300
|
+
const b = temp('pw')
|
|
301
|
+
const stmts = [['local.set', `$${b}`, baseIR]]
|
|
302
|
+
// square-and-multiply, LSB-first — mirrors $math.pow's loop association exactly,
|
|
303
|
+
// so the rounding tree (and thus the last bit) matches.
|
|
304
|
+
let sq = b, res = null, minted = false
|
|
305
|
+
for (let m = Math.abs(n); m > 0; m >>= 1) {
|
|
306
|
+
if (m & 1) {
|
|
307
|
+
if (res === null) res = sq // lowest set bit: result := this square (skip ×1)
|
|
308
|
+
else { const r = temp('pw'); stmts.push(['local.set', `$${r}`, ['f64.mul', get(res), get(sq)]]); res = r; minted = true }
|
|
309
|
+
}
|
|
310
|
+
if (m >> 1) { const s = temp('pw'); stmts.push(['local.set', `$${s}`, ['f64.mul', get(sq), get(sq)]]); sq = s; minted = true }
|
|
311
|
+
}
|
|
312
|
+
let result = get(res)
|
|
313
|
+
if (n < 0) { result = ['f64.div', ['f64.const', 1], result]; minted = true } // y<0 → reciprocal, as $math.pow does
|
|
314
|
+
// A NaN minted by f64.mul/div has a platform-nondeterministic sign; jz's value
|
|
315
|
+
// model requires the one canonical number-NaN, so `canon` folds it back. Skip when
|
|
316
|
+
// the base provably can't be NaN (same test min/max uses) or when no op was minted
|
|
317
|
+
// (|n|=1 hands the base straight through, already canonical).
|
|
318
|
+
const inner = typed(['block', ['result', 'f64'], ...stmts, result], 'f64')
|
|
319
|
+
return (minted && !neverNaN(a, baseIR)) ? canon(inner) : inner
|
|
320
|
+
}
|
|
321
|
+
const constNum = b => typeof b === 'number' ? b
|
|
322
|
+
: (Array.isArray(b) && b.length === 2 && b[0] == null && typeof b[1] === 'number') ? b[1]
|
|
323
|
+
: null
|
|
324
|
+
// `x ** 0.5` folds to f64.sqrt instead of the exp/log $math.pow call — saves the
|
|
325
|
+
// whole pow/exp/log stdlib (the headline `dist` example drops from ~1.0kB to 70B)
|
|
326
|
+
// and runs at hardware-sqrt speed. f64.sqrt is correctly-rounded, so for every
|
|
327
|
+
// normal input it is bit-identical to V8's `Math.pow(x, 0.5)`, and it agrees with
|
|
328
|
+
// jz's own `Math.sqrt(x)` by construction (mirrors the math.sqrt emit: always
|
|
329
|
+
// canon, since a negative finite base yields a NaN whose sign needs canonicalizing).
|
|
330
|
+
// Two exotic inputs follow sqrt rather than Math.pow semantics — a deliberate
|
|
331
|
+
// trade in the same class as jz's other boundary divergences: `(-0) ** 0.5` is -0
|
|
332
|
+
// (Math.pow: +0; and -0 === 0), `(-Infinity) ** 0.5` is NaN (Math.pow: +Infinity).
|
|
333
|
+
// `** -0.5` is intentionally NOT folded: 1/sqrt double-rounds and loses the last
|
|
334
|
+
// ULP vs Math.pow's single rounding, so it keeps the exact $math.pow path.
|
|
335
|
+
const powCall = emitter(['math.pow'], (a, b) => fn('math.pow', a, b))
|
|
336
|
+
// base-2 power → dedicated $math.exp2(y) (skips exp's ×ln2 / ÷ln2 round-trip)
|
|
337
|
+
const exp2Call = emitter(['math.exp2'], (exp) => typed(['call', '$math.exp2', toNumF64(exp, emit(exp))], 'f64'))
|
|
338
|
+
// Shared pow/** lowering.
|
|
339
|
+
const emitPow = (a, b, allowExpPos) => {
|
|
340
|
+
const n = constInt(b)
|
|
341
|
+
if (n !== null && Math.abs(n) <= POW_FOLD_MAX) return foldPow(a, n)
|
|
342
|
+
if (constNum(b) === 0.5) { const ir = typed(['f64.sqrt', toNumF64(a, emit(a))], 'f64'); return nonNegF64(ir[1]) ? ir : canon(ir) }
|
|
343
|
+
// Both args are compile-time constants: evaluate now, emit f64.const.
|
|
344
|
+
// Catches pow(2, -2/12) where the arithmetic folds emit f64.const for both sides.
|
|
345
|
+
const ca = constNum(a), cb = constNum(b)
|
|
346
|
+
if (ca !== null && cb !== null) return typed(['f64.const', Math.pow(ca, cb)], 'f64')
|
|
347
|
+
// IR-level fold: peek at emitted IR for both args — e.g. -2/12 emits f64.const -0.1666.
|
|
348
|
+
// We emit, check, and if not foldable, the emitted IR is used by the fallthrough paths.
|
|
349
|
+
const irA = toNumF64(a, emit(a)), irB = toNumF64(b, emit(b))
|
|
350
|
+
if (isLit(irA) && isLit(irB)) return typed(['f64.const', Math.pow(litVal(irA), litVal(irB))], 'f64')
|
|
351
|
+
// base 2 → dedicated 2^y (exp2 is exact for integer y, and skips exp's ×ln2/÷ln2).
|
|
352
|
+
// Every other literal base keeps $math.pow: `exp(y·ln base)` would lose ulps and,
|
|
353
|
+
// worse, miss Math.pow's integer-exponent semantics — e.g. `16 ** flen` with a
|
|
354
|
+
// runtime-integer flen must reproduce the exact square-and-multiply value (2⁵² for
|
|
355
|
+
// flen=13), which only $math.pow's integer fast path delivers.
|
|
356
|
+
if (allowExpPos && isLit(irA) && litVal(irA) === 2 && n === null)
|
|
357
|
+
return (inc('math.exp2'), typed(['call', '$math.exp2', irB], 'f64'))
|
|
358
|
+
return (inc('math.pow'), typed(['call', '$math.pow', irA, irB], 'f64'))
|
|
359
|
+
}
|
|
360
|
+
ctx.core.emit['math.pow'] = tag((a, b) => emitPow(a, b, true), powCall.deps)
|
|
361
|
+
ctx.core.emit['**'] = tag((a, b) => emitPow(a, b, true), powCall.deps)
|
|
362
|
+
reg('math.cbrt', ['math.cbrt'], a => fn('math.cbrt', a))
|
|
363
|
+
reg('math.hypot', ['math.hypot'], (a, b, ...rest) => {
|
|
190
364
|
if (a === undefined) return typed(['f64.const', 0], 'f64')
|
|
191
365
|
if (b === undefined) return f('f64.abs', a)
|
|
192
|
-
let r =
|
|
366
|
+
let r = fn('math.hypot', a, b)
|
|
193
367
|
// ToNumber every rest arg too (matches min/max) — an object arg's valueOf
|
|
194
368
|
// must run and may throw, which Math.hypot propagates.
|
|
195
369
|
for (const x of rest) r = typed(['call', '$math.hypot', r, toNumF64(x, emit(x))], 'f64')
|
|
@@ -198,7 +372,7 @@ export default (ctx) => {
|
|
|
198
372
|
|
|
199
373
|
// Math.sumPrecise(iterable) — exact, correctly-rounded summation (ECMA-262).
|
|
200
374
|
// jz models the array case; the WAT routine sums via a fixed-point accumulator.
|
|
201
|
-
|
|
375
|
+
reg('math.sumPrecise', ['math.sumPrecise'], arr =>
|
|
202
376
|
typed(['call', '$math.sumPrecise', ['i64.reinterpret_f64', asF64(emit(arr))]], 'f64'))
|
|
203
377
|
|
|
204
378
|
// Integer/bit operations: return i32 directly. Consumers `asF64`-rebox at
|
|
@@ -210,99 +384,170 @@ export default (ctx) => {
|
|
|
210
384
|
ctx.core.emit['math.imul'] = (a, b) => typed(['i32.mul', toI32(emit(a)), toI32(emit(b))], 'i32')
|
|
211
385
|
|
|
212
386
|
// Random
|
|
213
|
-
|
|
387
|
+
reg('math.random', ['math.random'], () => {
|
|
388
|
+
// Entropy mode: pull the host randomness syscall on demand (only when
|
|
389
|
+
// Math.random is actually used) — env.rngSeed (JS host) or WASI random_get.
|
|
390
|
+
if (rngEntropy) {
|
|
391
|
+
if (ctx.transform.host === 'wasi')
|
|
392
|
+
hostImport('wasi_snapshot_preview1', 'random_get', ['func', '$__random_get', ['param', 'i32'], ['param', 'i32'], ['result', 'i32']])
|
|
393
|
+
else
|
|
394
|
+
hostImport('env', 'rngSeed', ['func', '$__env_rng_seed', ['result', 'i32']])
|
|
395
|
+
}
|
|
396
|
+
return typed(['call', '$math.random'], 'f64')
|
|
397
|
+
})
|
|
214
398
|
|
|
215
399
|
// ============================================
|
|
216
400
|
// WAT stdlib implementations
|
|
217
401
|
// ============================================
|
|
218
402
|
|
|
219
|
-
|
|
403
|
+
wat('math.sign', `(func $math.sign (param $x f64) (result f64)
|
|
220
404
|
;; sign(NaN) = NaN, sign(±0) = ±0 — both pass x through unchanged.
|
|
221
405
|
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
222
406
|
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
223
407
|
(if (result f64) (f64.gt (local.get $x) (f64.const 0.0))
|
|
224
408
|
(then (f64.const 1.0))
|
|
225
|
-
(else (f64.const -1.0))))`
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
(local $
|
|
272
|
-
(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
(local.set $
|
|
277
|
-
(local.set $
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
(local.get $
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
(f64.
|
|
409
|
+
(else (f64.const -1.0))))`)
|
|
410
|
+
|
|
411
|
+
// sin/cos over the folded range [0, π/2] use a 5-term MINIMAX polynomial in x² (Horner
|
|
412
|
+
// form, generated below). It beats the prior 6-term Taylor on both counts: one fewer
|
|
413
|
+
// multiply (faster — the floatbeat synth is sin-bound), and lower error (sin ≤ 1.9e-8,
|
|
414
|
+
// cos ≤ 1.3e-7 vs Taylor's ~6e-8 / ~5e-7) — minimax spreads error evenly across the range
|
|
415
|
+
// instead of piling unused precision near 0. Coeffs fit by scripts/minimax-trig.mjs.
|
|
416
|
+
const horner = (cs, v) => cs.reduceRight((acc, c, i) =>
|
|
417
|
+
i === cs.length - 1 ? `(f64.const ${c})`
|
|
418
|
+
: `(f64.add (f64.const ${c}) (f64.mul (local.get ${v}) ${acc}))`, '')
|
|
419
|
+
const SIN_C = [1, -0.16666660296130772, 0.008333091744946387, -0.00019811771757028443, 0.000002611054662215034]
|
|
420
|
+
const COS_C = [1, -0.4999993043717576, 0.04166402742354027, -0.0013856638518363177, 0.00002321737177898552]
|
|
421
|
+
// 2^f over the reduced range f ∈ [-0.5, 0.5] for $math.exp2 (rel. err ≤ 6e-9). Lets the
|
|
422
|
+
// base-2 power `2**y` skip the ×ln2 / ÷ln2 round-trip exp(y·ln2) pays — see $math.exp2.
|
|
423
|
+
const EXP2_C = [1, 0.6931472000619209, 0.24022650999918949, 0.05550340682450019, 0.009618048870444599, 0.0013395279077191057, 0.00015463102004723134]
|
|
424
|
+
// Range-reduction constants embedded as exact round-trip decimal STRINGS, not
|
|
425
|
+
// `${Math.PI}`/`${1/Math.PI}` number interpolation. These multiply the (possibly
|
|
426
|
+
// astronomically large) argument, so any lost digit wrecks large-arg reduction. Under
|
|
427
|
+
// self-host the kernel formats `${number}` through __ftoa — a 9-significant-digit dtoa
|
|
428
|
+
// (module/number.js) — which would bake "3.14159265"/"0.318309886" into the WAT and
|
|
429
|
+
// throw the reduced quadrant off (Δ≈0.2 at x≈2267). A string interpolates verbatim, so
|
|
430
|
+
// watr parses the full-precision f64 in both legs. Values are native toString's shortest
|
|
431
|
+
// round-trip reprs of Math.PI, 1/Math.PI, Math.PI/2 — byte-identical to the old output.
|
|
432
|
+
const PI = '3.141592653589793', INV_PI = '0.3183098861837907', HALF_PI = '1.5707963267948966'
|
|
433
|
+
|
|
434
|
+
// Round-to-nearest reduction r = x − q·π ∈ [−π/2, π/2], in pure f64 — no int conversion,
|
|
435
|
+
// so it never traps and never saturates. A SECOND pass folds the q·π rounding error back
|
|
436
|
+
// in, keeping r bounded even for astronomically large x where the first pass loses all
|
|
437
|
+
// precision: Math.sin must return a value in [−1,1] for every finite input, not Inf/garbage.
|
|
438
|
+
// For |x| ≲ 1e15 the second pass is a no-op (q2 = 0) and the result is bit-identical to a
|
|
439
|
+
// single reduction. The odd poly r·P(r²) handles r<0 on its own (sin is odd); the sign is the
|
|
440
|
+
// parity of the total quotient, taken in f64 as q − 2·round(q/2). ×(1/π) avoids a divide.
|
|
441
|
+
// ±Infinity and NaN must return NaN. Guard before reduction instead of relying on
|
|
442
|
+
// Inf−Inf·π to mint one: that arithmetic NaN has platform-dependent bits and can
|
|
443
|
+
// escape as a non-canonical NaN-box on x86/Linux.
|
|
444
|
+
// The second reduction pass only corrects an r that the first pass left outside
|
|
445
|
+
// [−π/2, π/2]; for in-range r, q2 is 0 and the pass is a no-op, so gating it on
|
|
446
|
+
// |r| > π/2 is bit-identical for all finite inputs while sparing the common case
|
|
447
|
+
// ~6 ops. Both are generic wins for every sin/cos/tan/exp-via-trig call site.
|
|
448
|
+
wat('math.sin_core', `(func $math.sin_core (param $x f64) (result f64)
|
|
449
|
+
(local $q f64) (local $q2 f64) (local $r f64) (local $r2 f64)
|
|
450
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (f64.const nan))))
|
|
451
|
+
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const nan))))
|
|
452
|
+
;; |x| ≤ 2⁻²⁷: sin(x) = x to within a fraction of an ulp, and returning x preserves the
|
|
453
|
+
;; sign of ±0 (the range reduction below would turn -0 into +0: -0 − (-0·π) = +0).
|
|
454
|
+
(if (f64.lt (f64.abs (local.get $x)) (f64.const ${2 ** -27})) (then (return (local.get $x))))
|
|
455
|
+
(local.set $q (f64.nearest (f64.mul (local.get $x) (f64.const ${INV_PI}))))
|
|
456
|
+
(local.set $r (f64.sub (local.get $x) (f64.mul (local.get $q) (f64.const ${PI}))))
|
|
457
|
+
(if (f64.gt (f64.abs (local.get $r)) (f64.const ${HALF_PI}))
|
|
458
|
+
(then
|
|
459
|
+
(local.set $q2 (f64.nearest (f64.mul (local.get $r) (f64.const ${INV_PI}))))
|
|
460
|
+
(local.set $r (f64.sub (local.get $r) (f64.mul (local.get $q2) (f64.const ${PI}))))
|
|
461
|
+
(local.set $q (f64.add (local.get $q) (local.get $q2)))))
|
|
462
|
+
(local.set $q (f64.sub (local.get $q) (f64.mul (f64.const 2) (f64.nearest (f64.mul (local.get $q) (f64.const 0.5))))))
|
|
463
|
+
(local.set $r2 (f64.mul (local.get $r) (local.get $r)))
|
|
464
|
+
(local.set $r (f64.mul (local.get $r) ${horner(SIN_C, '$r2')}))
|
|
465
|
+
;; Negate for odd quasiperiods
|
|
466
|
+
(if (f64.gt (f64.abs (local.get $q)) (f64.const 0.5)) (then (local.set $r (f64.neg (local.get $r)))))
|
|
467
|
+
;; Clamp to [-1, 1]: polynomial approximation can overshoot by ~1e-8 near peaks.
|
|
468
|
+
;; Branchless (f64.min/f64.max) avoids branch misprediction near peaks.
|
|
469
|
+
(f64.min (f64.max (local.get $r) (f64.const -1.0)) (f64.const 1.0)))`)
|
|
470
|
+
|
|
471
|
+
wat('math.sin', `(func $math.sin (param $x f64) (result f64)
|
|
472
|
+
(call $math.sin_core (local.get $x)))`)
|
|
473
|
+
|
|
474
|
+
wat('math.cos_core', `(func $math.cos_core (param $x f64) (result f64)
|
|
475
|
+
(local $q f64) (local $q2 f64) (local $r f64) (local $r2 f64)
|
|
476
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (f64.const nan))))
|
|
477
|
+
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const nan))))
|
|
478
|
+
(local.set $q (f64.nearest (f64.mul (local.get $x) (f64.const ${INV_PI}))))
|
|
479
|
+
(local.set $r (f64.sub (local.get $x) (f64.mul (local.get $q) (f64.const ${PI}))))
|
|
480
|
+
(if (f64.gt (f64.abs (local.get $r)) (f64.const ${HALF_PI}))
|
|
481
|
+
(then
|
|
482
|
+
(local.set $q2 (f64.nearest (f64.mul (local.get $r) (f64.const ${INV_PI}))))
|
|
483
|
+
(local.set $r (f64.sub (local.get $r) (f64.mul (local.get $q2) (f64.const ${PI}))))
|
|
484
|
+
(local.set $q (f64.add (local.get $q) (local.get $q2)))))
|
|
485
|
+
(local.set $q (f64.sub (local.get $q) (f64.mul (f64.const 2) (f64.nearest (f64.mul (local.get $q) (f64.const 0.5))))))
|
|
486
|
+
(local.set $r2 (f64.mul (local.get $r) (local.get $r)))
|
|
487
|
+
(local.set $r ${horner(COS_C, '$r2')})
|
|
488
|
+
;; Negate for odd quasiperiods
|
|
489
|
+
(if (f64.gt (f64.abs (local.get $q)) (f64.const 0.5)) (then (local.set $r (f64.neg (local.get $r)))))
|
|
490
|
+
;; Clamp to [-1, 1]: polynomial approximation can overshoot by ~1e-8 near peaks.
|
|
491
|
+
;; Branchless (f64.min/f64.max) avoids branch misprediction near peaks.
|
|
492
|
+
(f64.min (f64.max (local.get $r) (f64.const -1.0)) (f64.const 1.0)))`)
|
|
493
|
+
|
|
494
|
+
wat('math.cos', `(func $math.cos (param $x f64) (result f64)
|
|
495
|
+
(call $math.cos_core (local.get $x)))`)
|
|
496
|
+
|
|
497
|
+
wat('math.tan', `(func $math.tan (param $x f64) (result f64)
|
|
498
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (f64.const nan))))
|
|
499
|
+
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const nan))))
|
|
500
|
+
(f64.div (call $math.sin (local.get $x)) (call $math.cos (local.get $x))))`)
|
|
501
|
+
|
|
502
|
+
// e^x = 2^(x·log2 e) — defer to the faster $math.exp2 (one multiply, no division, and
|
|
503
|
+
// exp2's NaN/overflow/underflow guards cover exp's). Accurate to exp2's ~6e-9, better
|
|
504
|
+
// than the old 7-term Taylor, and it shares one code path with `2**`.
|
|
505
|
+
wat('math.exp', `(func $math.exp (param $x f64) (result f64)
|
|
506
|
+
(call $math.exp2 (f64.mul (local.get $x) (f64.const ${Math.LOG2E}))))`)
|
|
507
|
+
|
|
508
|
+
// 2^y, the dedicated base-2 power. `2**y` lowers here instead of exp(y·ln2): no ×ln2
|
|
509
|
+
// (so no reciprocal cancellation against exp's ÷ln2), a poly over the tighter [-0.5,0.5],
|
|
510
|
+
// and the same O(1) IEEE-exponent build of 2^k. ~6e-9 rel. error — well inside tolerance.
|
|
511
|
+
wat('math.exp2', `(func $math.exp2 (param $y f64) (result f64)
|
|
512
|
+
(local $k i32) (local $f f64) (local $k2 i32) (local $p f64)
|
|
513
|
+
(if (f64.ne (local.get $y) (local.get $y)) (then (return (local.get $y))))
|
|
514
|
+
(if (result f64) (f64.gt (local.get $y) (f64.const 1024.0)) (then (f64.const inf)) (else
|
|
515
|
+
(if (result f64) (f64.lt (local.get $y) (f64.const -1075.0)) (then (f64.const 0.0)) (else
|
|
516
|
+
(local.set $k (i32.trunc_f64_s (f64.nearest (local.get $y))))
|
|
517
|
+
(local.set $f (f64.sub (local.get $y) (f64.convert_i32_s (local.get $k))))
|
|
518
|
+
(local.set $p ${horner(EXP2_C, '$f')})
|
|
519
|
+
;; 2^k via a single IEEE-exponent build for the normal range (the hot path); the
|
|
520
|
+
;; two-factor split (2^k2 · 2^(k−k2)) is only needed at the denormal/overflow edges.
|
|
521
|
+
;; For normal k both are bit-identical (powers of two multiply exactly) — free speedup.
|
|
522
|
+
(if (result f64)
|
|
523
|
+
(i32.and (i32.gt_s (local.get $k) (i32.const -1023)) (i32.lt_s (local.get $k) (i32.const 1024)))
|
|
524
|
+
(then (f64.mul (local.get $p)
|
|
525
|
+
(f64.reinterpret_i64 (i64.shl (i64.extend_i32_s (i32.add (local.get $k) (i32.const 1023))) (i64.const 52)))))
|
|
526
|
+
(else
|
|
527
|
+
(local.set $k2 (i32.shr_s (local.get $k) (i32.const 1)))
|
|
528
|
+
(f64.mul (f64.mul (local.get $p)
|
|
529
|
+
(f64.reinterpret_i64 (i64.shl (i64.extend_i32_s (i32.add (local.get $k2) (i32.const 1023))) (i64.const 52))))
|
|
530
|
+
(f64.reinterpret_i64 (i64.shl (i64.extend_i32_s (i32.add (i32.sub (local.get $k) (local.get $k2)) (i32.const 1023))) (i64.const 52)))))))))))`)
|
|
531
|
+
|
|
532
|
+
// Maclaurin coefficients 1/1!…1/8! for e^x−1 = x·(1 + x/2! + x²/3! + …), Horner-nested
|
|
533
|
+
// (built with an explicit right-to-left fold so the parens stay balanced — and so the
|
|
534
|
+
// builder uses only constructs the self-host kernel can compile: Array.reduceRight is
|
|
535
|
+
// not in jz's runtime, so under the kernel it returns undefined and that token lands
|
|
536
|
+
// verbatim in the emitted WAT).
|
|
537
|
+
const expm1Coef = [1, 1 / 2, 1 / 6, 1 / 24, 1 / 120, 1 / 720, 1 / 5040, 1 / 40320]
|
|
538
|
+
let expm1Series = ''
|
|
539
|
+
for (let i = expm1Coef.length - 1; i >= 0; i--)
|
|
540
|
+
expm1Series = expm1Series
|
|
541
|
+
? `(f64.add (f64.const ${expm1Coef[i]}) (f64.mul (local.get $x) ${expm1Series}))`
|
|
542
|
+
: `(f64.const ${expm1Coef[i]})`
|
|
543
|
+
wat('math.expm1', `(func $math.expm1 (param $x f64) (result f64)
|
|
544
|
+
;; expm1(x) = e^x − 1. For |x| < 0.5 sum the series directly: there e^x is within ~1.6
|
|
545
|
+
;; of 1, so exp(x)−1 cancels the leading digits (the prior naive form lost up to ~11%
|
|
546
|
+
;; near 0); the series doesn't, and the leading x·(…) preserves the sign of ±0. Larger
|
|
547
|
+
;; |x| has no cancellation, so exp(x)−1 is accurate.
|
|
548
|
+
(if (result f64) (f64.lt (f64.abs (local.get $x)) (f64.const 0.5))
|
|
549
|
+
(then (f64.mul (local.get $x) ${expm1Series}))
|
|
550
|
+
(else (f64.sub (call $math.exp (local.get $x)) (f64.const 1.0)))))`)
|
|
306
551
|
|
|
307
552
|
// log(x) via bit-level frexp + sqrt(2)-centered split + atanh series.
|
|
308
553
|
// x = m * 2^k with bits-extracted k (no loop)
|
|
@@ -312,8 +557,9 @@ export default (ctx) => {
|
|
|
312
557
|
// With 9 polynomial terms and |s|≤0.172, truncation error ≈ 2|s|·z⁹/19 ≈ 4e-17,
|
|
313
558
|
// close to f64 ulp. The whole routine is branchless after edge cases.
|
|
314
559
|
// Edge cases: NaN→NaN, ≤0 distinguishes 0→-Inf, <0→NaN; +Inf passes through.
|
|
315
|
-
|
|
560
|
+
wat('math.log', `(func $math.log (param $x f64) (result f64)
|
|
316
561
|
(local $bits i64) (local $k i32) (local $m f64) (local $s f64) (local $z f64)
|
|
562
|
+
(local $f f64) (local $w f64) (local $t1 f64) (local $t2 f64) (local $hfsq f64)
|
|
317
563
|
(if (f64.ne (local.get $x) (local.get $x))
|
|
318
564
|
(then (return (local.get $x))))
|
|
319
565
|
(if (f64.le (local.get $x) (f64.const 0.0))
|
|
@@ -344,24 +590,30 @@ export default (ctx) => {
|
|
|
344
590
|
(then
|
|
345
591
|
(local.set $m (f64.mul (local.get $m) (f64.const 0.5)))
|
|
346
592
|
(local.set $k (i32.add (local.get $k) (i32.const 1)))))
|
|
347
|
-
|
|
348
|
-
|
|
593
|
+
;; s = f/(2+f) with f = m−1 (= (m−1)/(m+1)); then the fdlibm even/odd-split
|
|
594
|
+
;; polynomial. Two parallel Horner chains (t1 over even powers, t2 over odd)
|
|
595
|
+
;; cut the dependency chain ~in half vs one 9-deep Horner — more ILP, fewer
|
|
596
|
+
;; terms — and reconstruct log(m) = f − hfsq + s·(hfsq + t1 + t2). ~1 ulp.
|
|
597
|
+
(local.set $f (f64.sub (local.get $m) (f64.const 1.0)))
|
|
598
|
+
(local.set $s (f64.div (local.get $f) (f64.add (local.get $f) (f64.const 2.0))))
|
|
349
599
|
(local.set $z (f64.mul (local.get $s) (local.get $s)))
|
|
350
|
-
|
|
600
|
+
(local.set $w (f64.mul (local.get $z) (local.get $z)))
|
|
601
|
+
(local.set $t1 (f64.mul (local.get $w) (f64.add (f64.const 0.3999999999940941908)
|
|
602
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.2222219843214978396)
|
|
603
|
+
(f64.mul (local.get $w) (f64.const 0.1531383769920937332)))))))
|
|
604
|
+
(local.set $t2 (f64.mul (local.get $z) (f64.add (f64.const 0.6666666666666735130)
|
|
605
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.2857142874366239149)
|
|
606
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.1818357216161805012)
|
|
607
|
+
(f64.mul (local.get $w) (f64.const 0.1479819860511658591)))))))))
|
|
608
|
+
(local.set $hfsq (f64.mul (f64.const 0.5) (f64.mul (local.get $f) (local.get $f))))
|
|
351
609
|
(f64.add
|
|
352
610
|
(f64.mul (f64.convert_i32_s (local.get $k)) (f64.const ${Math.LN2}))
|
|
353
|
-
(f64.
|
|
354
|
-
(f64.mul (local.get $
|
|
355
|
-
(f64.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
(f64.mul (local.get $z) (f64.add (f64.const 0.07692307692307693)
|
|
360
|
-
(f64.mul (local.get $z) (f64.add (f64.const 0.06666666666666667)
|
|
361
|
-
(f64.mul (local.get $z) (f64.const 0.058823529411764705)))))))))))))))))))))`
|
|
362
|
-
|
|
363
|
-
ctx.core.stdlib['math.log2'] = `(func $math.log2 (param $x f64) (result f64)
|
|
364
|
-
(f64.div (call $math.log (local.get $x)) (f64.const ${Math.LN2})))`
|
|
611
|
+
(f64.add (f64.sub (local.get $f) (local.get $hfsq))
|
|
612
|
+
(f64.mul (local.get $s) (f64.add (local.get $hfsq)
|
|
613
|
+
(f64.add (local.get $t1) (local.get $t2))))))))`)
|
|
614
|
+
|
|
615
|
+
wat('math.log2', `(func $math.log2 (param $x f64) (result f64)
|
|
616
|
+
(f64.div (call $math.log (local.get $x)) (f64.const ${Math.LN2})))`)
|
|
365
617
|
|
|
366
618
|
// log10 via fdlibm's two-term decomposition: log10(x) = k*log10(2) + log10(m).
|
|
367
619
|
// A plain log(x)/ln(10) double-rounds (rounding of log itself, then of the
|
|
@@ -369,7 +621,7 @@ export default (ctx) => {
|
|
|
369
621
|
// Reducing x = m·2^k, splitting log10(2) and 1/ln(10) into hi/lo halves, and
|
|
370
622
|
// keeping the bulk term (k·log10_2hi, hi·ivln10hi) carry-free recovers the
|
|
371
623
|
// last ulps, so log10(10/100/1000/…) round-trips to exact integers.
|
|
372
|
-
|
|
624
|
+
wat('math.log10', `(func $math.log10 (param $x f64) (result f64)
|
|
373
625
|
(local $bits i64) (local $k i32) (local $m f64) (local $f f64)
|
|
374
626
|
(local $hfsq f64) (local $s f64) (local $z f64) (local $w f64)
|
|
375
627
|
(local $t1 f64) (local $t2 f64) (local $R f64)
|
|
@@ -433,12 +685,12 @@ export default (ctx) => {
|
|
|
433
685
|
(local.set $w (f64.add (local.get $y) (local.get $valhi)))
|
|
434
686
|
(local.set $vallo (f64.add (local.get $vallo)
|
|
435
687
|
(f64.add (f64.sub (local.get $y) (local.get $w)) (local.get $valhi))))
|
|
436
|
-
(f64.add (local.get $vallo) (local.get $w)))`
|
|
688
|
+
(f64.add (local.get $vallo) (local.get $w)))`)
|
|
437
689
|
|
|
438
690
|
// log1p(x) via Kahan's compensated trick: with u = 1+x, log(u) loses bits when x is
|
|
439
691
|
// small (because u rounds to ~1), but the ratio x/(u-1) is exactly the missing factor.
|
|
440
692
|
// For u==1 (x below ulp), result is just x; preserves -0 from x=-0 path.
|
|
441
|
-
|
|
693
|
+
wat('math.log1p', `(func $math.log1p (param $x f64) (result f64)
|
|
442
694
|
(local $u f64)
|
|
443
695
|
;; log1p(+Inf) = +Inf — the ratio trick below would compute Inf/Inf = NaN.
|
|
444
696
|
(if (f64.eq (local.get $x) (f64.const inf)) (then (return (f64.const inf))))
|
|
@@ -447,9 +699,9 @@ export default (ctx) => {
|
|
|
447
699
|
(then (return (local.get $x))))
|
|
448
700
|
(f64.div
|
|
449
701
|
(f64.mul (call $math.log (local.get $u)) (local.get $x))
|
|
450
|
-
(f64.sub (local.get $u) (f64.const 1.0))))`
|
|
702
|
+
(f64.sub (local.get $u) (f64.const 1.0))))`)
|
|
451
703
|
|
|
452
|
-
|
|
704
|
+
wat('math.pow', `(func $math.pow (param $x f64) (param $y f64) (result f64)
|
|
453
705
|
(local $result f64) (local $n i32) (local $neg_base i32) (local $abs_x f64)
|
|
454
706
|
;; y == 0 -> 1 (covers pow(NaN,0), pow(±0,0), pow(±Inf,0))
|
|
455
707
|
(if (f64.eq (local.get $y) (f64.const 0.0)) (then (return (f64.const 1.0))))
|
|
@@ -522,12 +774,12 @@ export default (ctx) => {
|
|
|
522
774
|
;; x < 0, non-integer finite y -> NaN
|
|
523
775
|
(if (f64.lt (local.get $x) (f64.const 0.0))
|
|
524
776
|
(then (return (f64.const nan))))
|
|
525
|
-
(call $math.exp (f64.mul (local.get $y) (call $math.log (local.get $x)))))`
|
|
777
|
+
(call $math.exp (f64.mul (local.get $y) (call $math.log (local.get $x)))))`)
|
|
526
778
|
|
|
527
779
|
// fdlibm atan: 4-region argument reduction onto |r| ≤ tan(π/16), then an
|
|
528
780
|
// 11-term odd polynomial split into even/odd parts. Accurate to <1 ulp —
|
|
529
781
|
// the old Taylor series was ~2e-6 off near |x|=0.5. Drives asin/acos/atan2.
|
|
530
|
-
|
|
782
|
+
wat('math.atan', `(func $math.atan (param $x f64) (result f64)
|
|
531
783
|
(local $abs_x f64) (local $id i32) (local $r f64) (local $z f64) (local $w f64)
|
|
532
784
|
(local $s1 f64) (local $s2 f64) (local $ahi f64) (local $alo f64) (local $res f64)
|
|
533
785
|
;; NaN passes through unchanged.
|
|
@@ -594,19 +846,20 @@ export default (ctx) => {
|
|
|
594
846
|
(local.set $res (f64.sub (local.get $ahi)
|
|
595
847
|
(f64.sub (f64.sub (f64.mul (local.get $r) (f64.add (local.get $s1) (local.get $s2))) (local.get $alo))
|
|
596
848
|
(local.get $r))))
|
|
597
|
-
(f64.copysign (local.get $res) (local.get $x)))`
|
|
849
|
+
(f64.copysign (local.get $res) (local.get $x)))`)
|
|
598
850
|
|
|
599
|
-
|
|
851
|
+
wat('math.asin', `(func $math.asin (param $x f64) (result f64)
|
|
600
852
|
;; Domain is [-1, 1]; outside it (including ±Infinity), Math.asin returns NaN.
|
|
853
|
+
;; sin/cos output is clamped to [-1, 1] by sin_core/cos_core, so no tolerance needed here.
|
|
601
854
|
(if (result f64) (f64.gt (f64.abs (local.get $x)) (f64.const 1.0))
|
|
602
855
|
(then (f64.const nan))
|
|
603
856
|
(else (call $math.atan (f64.div (local.get $x)
|
|
604
|
-
(f64.sqrt (f64.sub (f64.const 1.0) (f64.mul (local.get $x) (local.get $x)))))))))`
|
|
857
|
+
(f64.sqrt (f64.sub (f64.const 1.0) (f64.mul (local.get $x) (local.get $x)))))))))`)
|
|
605
858
|
|
|
606
|
-
|
|
607
|
-
(f64.sub (f64.const ${
|
|
859
|
+
wat('math.acos', `(func $math.acos (param $x f64) (result f64)
|
|
860
|
+
(f64.sub (f64.const ${HALF_PI}) (call $math.asin (local.get $x))))`)
|
|
608
861
|
|
|
609
|
-
|
|
862
|
+
wat('math.atan2', `(func $math.atan2 (param $y f64) (param $x f64) (result f64)
|
|
610
863
|
;; If either argument is NaN, the result is NaN (ECMA-262 21.3.2.5).
|
|
611
864
|
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
612
865
|
(if (f64.ne (local.get $y) (local.get $y)) (then (return (local.get $y))))
|
|
@@ -614,30 +867,30 @@ export default (ctx) => {
|
|
|
614
867
|
;; y is ±0 too: result is ±0 when x is +0, ±π when x is -0; sign taken from y.
|
|
615
868
|
(if (result f64) (f64.eq (local.get $y) (f64.const 0.0))
|
|
616
869
|
(then (f64.copysign
|
|
617
|
-
(select (f64.const ${
|
|
870
|
+
(select (f64.const ${PI}) (f64.const 0.0)
|
|
618
871
|
(f64.lt (f64.copysign (f64.const 1.0) (local.get $x)) (f64.const 0.0)))
|
|
619
872
|
(local.get $y)))
|
|
620
873
|
(else
|
|
621
|
-
(if (result f64) (f64.gt (local.get $y) (f64.const 0.0)) (then (f64.const ${
|
|
874
|
+
(if (result f64) (f64.gt (local.get $y) (f64.const 0.0)) (then (f64.const ${HALF_PI})) (else (f64.neg (f64.const ${HALF_PI})))))))
|
|
622
875
|
(else (if (result f64) (f64.ge (local.get $x) (f64.const 0.0))
|
|
623
876
|
(then (call $math.atan (f64.div (local.get $y) (local.get $x))))
|
|
624
877
|
(else (if (result f64) (f64.ge (local.get $y) (f64.const 0.0))
|
|
625
|
-
(then (f64.add (call $math.atan (f64.div (local.get $y) (local.get $x))) (f64.const ${
|
|
626
|
-
(else (f64.sub (call $math.atan (f64.div (local.get $y) (local.get $x))) (f64.const ${
|
|
878
|
+
(then (f64.add (call $math.atan (f64.div (local.get $y) (local.get $x))) (f64.const ${PI})))
|
|
879
|
+
(else (f64.sub (call $math.atan (f64.div (local.get $y) (local.get $x))) (f64.const ${PI})))))))))`)
|
|
627
880
|
|
|
628
|
-
|
|
881
|
+
wat('math.sinh', `(func $math.sinh (param $x f64) (result f64)
|
|
629
882
|
(local $ex f64)
|
|
630
883
|
;; Preserve sign of zero: sinh(±0) = ±0 (the f64.lt sign test below is false for -0).
|
|
631
884
|
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
632
885
|
(local.set $ex (call $math.exp (f64.abs (local.get $x))))
|
|
633
886
|
(local.set $ex (f64.mul (f64.const 0.5) (f64.sub (local.get $ex) (f64.div (f64.const 1.0) (local.get $ex)))))
|
|
634
|
-
(if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.neg (local.get $ex))) (else (local.get $ex))))`
|
|
887
|
+
(if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.neg (local.get $ex))) (else (local.get $ex))))`)
|
|
635
888
|
|
|
636
|
-
|
|
889
|
+
wat('math.cosh', `(func $math.cosh (param $x f64) (result f64)
|
|
637
890
|
(local $ex f64) (local.set $ex (call $math.exp (f64.abs (local.get $x))))
|
|
638
|
-
(f64.mul (f64.const 0.5) (f64.add (local.get $ex) (f64.div (f64.const 1.0) (local.get $ex)))))`
|
|
891
|
+
(f64.mul (f64.const 0.5) (f64.add (local.get $ex) (f64.div (f64.const 1.0) (local.get $ex)))))`)
|
|
639
892
|
|
|
640
|
-
|
|
893
|
+
wat('math.tanh', `(func $math.tanh (param $x f64) (result f64)
|
|
641
894
|
(local $e2x f64)
|
|
642
895
|
;; Preserve sign of zero: tanh(±0) = ±0 (the f64.lt sign test below is false for -0).
|
|
643
896
|
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
@@ -645,30 +898,30 @@ export default (ctx) => {
|
|
|
645
898
|
(then (if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.const -1.0)) (else (f64.const 1.0))))
|
|
646
899
|
(else (local.set $e2x (call $math.exp (f64.mul (f64.const 2.0) (f64.abs (local.get $x)))))
|
|
647
900
|
(local.set $e2x (f64.div (f64.sub (local.get $e2x) (f64.const 1.0)) (f64.add (local.get $e2x) (f64.const 1.0))))
|
|
648
|
-
(if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.neg (local.get $e2x))) (else (local.get $e2x))))))`
|
|
901
|
+
(if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.neg (local.get $e2x))) (else (local.get $e2x))))))`)
|
|
649
902
|
|
|
650
|
-
|
|
903
|
+
wat('math.asinh', `(func $math.asinh (param $x f64) (result f64)
|
|
651
904
|
;; ±Infinity and NaN pass through unchanged. (log(±Inf + sqrt(Inf²+1)) → NaN otherwise.)
|
|
652
905
|
(if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (local.get $x))))
|
|
653
906
|
;; Preserve sign of zero: asinh(±0) = ±0.
|
|
654
907
|
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
655
|
-
(call $math.log (f64.add (local.get $x) (f64.sqrt (f64.add (f64.mul (local.get $x) (local.get $x)) (f64.const 1.0))))))`
|
|
908
|
+
(call $math.log (f64.add (local.get $x) (f64.sqrt (f64.add (f64.mul (local.get $x) (local.get $x)) (f64.const 1.0))))))`)
|
|
656
909
|
|
|
657
|
-
|
|
910
|
+
wat('math.acosh', `(func $math.acosh (param $x f64) (result f64)
|
|
658
911
|
(if (f64.eq (local.get $x) (f64.const inf)) (then (return (f64.const inf))))
|
|
659
912
|
;; acosh is defined only for x >= 1; everything below (incl. -Inf) is NaN.
|
|
660
913
|
(if (result f64) (f64.lt (local.get $x) (f64.const 1.0)) (then (f64.const nan)) (else
|
|
661
|
-
(call $math.log (f64.add (local.get $x) (f64.sqrt (f64.sub (f64.mul (local.get $x) (local.get $x)) (f64.const 1.0))))))))`
|
|
914
|
+
(call $math.log (f64.add (local.get $x) (f64.sqrt (f64.sub (f64.mul (local.get $x) (local.get $x)) (f64.const 1.0))))))))`)
|
|
662
915
|
|
|
663
|
-
|
|
916
|
+
wat('math.atanh', `(func $math.atanh (param $x f64) (result f64)
|
|
664
917
|
;; Preserve sign of zero: atanh(±0) = ±0.
|
|
665
918
|
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
666
919
|
;; ±Infinity → NaN. Without this the (1+x)/(1-x) ratio is Inf/Inf, whose
|
|
667
920
|
;; sign-nondeterministic arithmetic NaN would escape non-canonical on x86.
|
|
668
921
|
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const nan))))
|
|
669
|
-
(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))))))`
|
|
922
|
+
(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))))))`)
|
|
670
923
|
|
|
671
|
-
|
|
924
|
+
wat('math.cbrt', `(func $math.cbrt (param $x f64) (result f64)
|
|
672
925
|
(local $y f64)
|
|
673
926
|
;; ±Infinity and NaN pass through; preserve sign of zero.
|
|
674
927
|
(if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (local.get $x))))
|
|
@@ -680,31 +933,37 @@ export default (ctx) => {
|
|
|
680
933
|
(local.set $y (call $math.pow (local.get $x) (f64.const 0.3333333333333333)))
|
|
681
934
|
(local.set $y (f64.div (f64.add (f64.mul (f64.const 2.0) (local.get $y)) (f64.div (local.get $x) (f64.mul (local.get $y) (local.get $y)))) (f64.const 3.0)))
|
|
682
935
|
(local.set $y (f64.div (f64.add (f64.mul (f64.const 2.0) (local.get $y)) (f64.div (local.get $x) (f64.mul (local.get $y) (local.get $y)))) (f64.const 3.0)))
|
|
683
|
-
(local.get $y))))`
|
|
936
|
+
(local.get $y))))`)
|
|
684
937
|
|
|
685
938
|
// Small finite-test helper (NaN→0, ±Inf→0, finite→1). Used by transcendental
|
|
686
939
|
// functions that need to short-circuit on infinite inputs.
|
|
687
|
-
|
|
940
|
+
wat('math.isFinite', `(func $math.isFinite (param $x f64) (result i32)
|
|
688
941
|
(i32.and
|
|
689
942
|
(f64.eq (local.get $x) (local.get $x))
|
|
690
|
-
(f64.lt (f64.abs (local.get $x)) (f64.const inf))))`
|
|
943
|
+
(f64.lt (f64.abs (local.get $x)) (f64.const inf))))`)
|
|
691
944
|
|
|
692
|
-
|
|
945
|
+
wat('math.hypot', `(func $math.hypot (param $x f64) (param $y f64) (result f64)
|
|
693
946
|
;; Any ±Infinity argument ⇒ +Infinity, even when the other is NaN (ECMA-262 21.3.2.18).
|
|
694
947
|
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const inf))))
|
|
695
948
|
(if (f64.eq (f64.abs (local.get $y)) (f64.const inf)) (then (return (f64.const inf))))
|
|
696
|
-
(f64.sqrt (f64.add (f64.mul (local.get $x) (local.get $x)) (f64.mul (local.get $y) (local.get $y)))))`
|
|
949
|
+
(f64.sqrt (f64.add (f64.mul (local.get $x) (local.get $x)) (f64.mul (local.get $y) (local.get $y)))))`)
|
|
697
950
|
|
|
698
|
-
|
|
951
|
+
// xorshift32 → [0,1). In entropy mode a one-shot prologue replaces the fixed
|
|
952
|
+
// initial state with host entropy on first call (branch is well-predicted after).
|
|
953
|
+
const rngSeedPrologue = rngEntropy ? `(if (i32.eqz (global.get $math.rng_seeded))
|
|
954
|
+
(then (global.set $math.rng_state (call $__rng_seed)) (global.set $math.rng_seeded (i32.const 1))))
|
|
955
|
+
` : ``
|
|
956
|
+
wat('math.random', `(func $math.random (result f64)
|
|
699
957
|
(local $s i32)
|
|
700
|
-
(local.set $s (global.get $math.rng_state))
|
|
958
|
+
${rngSeedPrologue}(local.set $s (global.get $math.rng_state))
|
|
701
959
|
(local.set $s (i32.xor (local.get $s) (i32.shl (local.get $s) (i32.const 13))))
|
|
702
960
|
(local.set $s (i32.xor (local.get $s) (i32.shr_u (local.get $s) (i32.const 17))))
|
|
703
961
|
(local.set $s (i32.xor (local.get $s) (i32.shl (local.get $s) (i32.const 5))))
|
|
704
962
|
(global.set $math.rng_state (local.get $s))
|
|
705
|
-
(f64.div (f64.convert_i32_u (i32.and (local.get $s) (i32.const 0x7FFFFFFF))) (f64.const 2147483647.0)))
|
|
963
|
+
(f64.div (f64.convert_i32_u (i32.and (local.get $s) (i32.const 0x7FFFFFFF))) (f64.const 2147483647.0)))`,
|
|
964
|
+
rngEntropy ? ['__rng_seed'] : [])
|
|
706
965
|
|
|
707
|
-
|
|
966
|
+
wat('math.sumPrecise', `(func $math.sumPrecise (param $arr i64) (result f64)
|
|
708
967
|
;; Exact summation via a 2304-bit fixed-point accumulator (36 i64 words,
|
|
709
968
|
;; little-endian two's complement) holding sum*2^1074. Every finite f64 is an
|
|
710
969
|
;; integer multiple of 2^-1074, so the running sum carries zero rounding
|
|
@@ -863,8 +1122,25 @@ export default (ctx) => {
|
|
|
863
1122
|
(then (f64.reinterpret_i64 (i64.shl (i64.extend_i32_u (i32.add (local.get $k) (i32.const 1023))) (i64.const 52))))
|
|
864
1123
|
(else (f64.reinterpret_i64 (i64.shl (i64.const 1) (i64.extend_i32_u (i32.add (local.get $k) (i32.const 1074))))))))
|
|
865
1124
|
(local.set $res (f64.mul (f64.convert_i64_u (local.get $top)) (local.get $pow)))
|
|
866
|
-
(select (f64.neg (local.get $res)) (local.get $res) (local.get $resultNeg)))`
|
|
1125
|
+
(select (f64.neg (local.get $res)) (local.get $res) (local.get $resultNeg)))`)
|
|
867
1126
|
|
|
868
|
-
// Global for random state
|
|
869
|
-
|
|
1127
|
+
// Global for random state — seeded with the fixed constant (deterministic) or,
|
|
1128
|
+
// in entropy mode, overwritten from the host on first Math.random() call.
|
|
1129
|
+
declGlobal('math.rng_state', 'i32', rngSeedConst)
|
|
1130
|
+
if (rngEntropy) {
|
|
1131
|
+
declGlobal('math.rng_seeded', 'i32')
|
|
1132
|
+
// One i32 of host entropy, floored at 1 (xorshift32 is dead at state 0).
|
|
1133
|
+
wat('__rng_seed', ctx.transform.host === 'wasi'
|
|
1134
|
+
? `(func $__rng_seed (result i32)
|
|
1135
|
+
(local $buf i32) (local $s i32)
|
|
1136
|
+
(local.set $buf (call $__alloc (i32.const 4)))
|
|
1137
|
+
(drop (call $__random_get (local.get $buf) (i32.const 4)))
|
|
1138
|
+
(local.set $s (i32.load (local.get $buf)))
|
|
1139
|
+
(select (local.get $s) (i32.const 1) (local.get $s)))`
|
|
1140
|
+
: `(func $__rng_seed (result i32)
|
|
1141
|
+
(local $s i32)
|
|
1142
|
+
(local.set $s (call $__env_rng_seed))
|
|
1143
|
+
(select (local.get $s) (i32.const 1) (local.get $s)))`,
|
|
1144
|
+
ctx.transform.host === 'wasi' ? ['__alloc'] : [])
|
|
1145
|
+
}
|
|
870
1146
|
}
|