jz 0.5.1 → 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.
Files changed (80) hide show
  1. package/README.md +288 -314
  2. package/bench/README.md +319 -0
  3. package/bench/bench.svg +112 -0
  4. package/cli.js +32 -24
  5. package/index.js +177 -55
  6. package/interop.js +88 -159
  7. package/jz.svg +5 -0
  8. package/jzify/arguments.js +97 -0
  9. package/jzify/bundler.js +382 -0
  10. package/jzify/classes.js +328 -0
  11. package/jzify/hoist-vars.js +177 -0
  12. package/jzify/index.js +51 -0
  13. package/jzify/names.js +37 -0
  14. package/jzify/switch.js +106 -0
  15. package/jzify/transform.js +349 -0
  16. package/layout.js +179 -0
  17. package/module/array.js +322 -153
  18. package/module/collection.js +603 -145
  19. package/module/console.js +55 -43
  20. package/module/core.js +266 -153
  21. package/module/date.js +15 -3
  22. package/module/function.js +73 -6
  23. package/module/index.js +2 -1
  24. package/module/json.js +226 -61
  25. package/module/math.js +414 -186
  26. package/module/number.js +306 -60
  27. package/module/object.js +448 -184
  28. package/module/regex.js +255 -25
  29. package/module/schema.js +24 -6
  30. package/module/simd.js +85 -0
  31. package/module/string.js +586 -220
  32. package/module/symbol.js +1 -1
  33. package/module/timer.js +9 -14
  34. package/module/typedarray.js +45 -48
  35. package/package.json +41 -12
  36. package/src/abi/index.js +39 -23
  37. package/src/abi/string.js +38 -41
  38. package/src/ast.js +460 -0
  39. package/src/autoload.js +26 -24
  40. package/src/bridge.js +111 -0
  41. package/src/compile/analyze-scans.js +661 -0
  42. package/src/compile/analyze.js +1565 -0
  43. package/src/compile/emit-assign.js +408 -0
  44. package/src/compile/emit.js +3201 -0
  45. package/src/compile/flow-types.js +103 -0
  46. package/src/{compile.js → compile/index.js} +497 -125
  47. package/src/{infer.js → compile/infer.js} +27 -98
  48. package/src/{narrow.js → compile/narrow.js} +302 -96
  49. package/src/compile/plan/advise.js +316 -0
  50. package/src/compile/plan/common.js +150 -0
  51. package/src/compile/plan/index.js +118 -0
  52. package/src/compile/plan/inline.js +679 -0
  53. package/src/compile/plan/literals.js +984 -0
  54. package/src/compile/plan/loops.js +472 -0
  55. package/src/compile/plan/scope.js +573 -0
  56. package/src/compile/program-facts.js +404 -0
  57. package/src/ctx.js +176 -58
  58. package/src/ir.js +540 -171
  59. package/src/kind-traits.js +105 -0
  60. package/src/kind.js +462 -0
  61. package/src/op-policy.js +57 -0
  62. package/src/{optimize.js → optimize/index.js} +1106 -446
  63. package/src/optimize/vectorize.js +1874 -0
  64. package/src/param-reps.js +65 -0
  65. package/src/parse.js +44 -0
  66. package/src/{prepare.js → prepare/index.js} +589 -202
  67. package/src/reps.js +115 -0
  68. package/src/resolve.js +12 -3
  69. package/src/static.js +199 -0
  70. package/src/type.js +647 -0
  71. package/src/{assemble.js → wat/assemble.js} +86 -48
  72. package/src/wat/optimize.js +3760 -0
  73. package/transform.js +21 -0
  74. package/wasi.js +47 -5
  75. package/src/analyze.js +0 -3818
  76. package/src/emit.js +0 -3040
  77. package/src/jzify.js +0 -1580
  78. package/src/plan.js +0 -2132
  79. package/src/vectorize.js +0 -1088
  80. /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
- * - ctx.core.emit['math.X'] = emitter(deps, args => WasmNode) - emitters with declarative stdlib deps
6
- * - ctx.core.stdlib['math.X'] = '(func ...)' - WAT function definitions
7
- * - ctx.core.stdlibDeps['math.X'] = ['dep'] - direct stdlib→stdlib edges (expanded transitively)
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.
@@ -13,17 +13,26 @@
13
13
  */
14
14
 
15
15
  import { typed, asF64, asI32, toI32, toNumF64, temp, arrayLoop, isLit, litVal, isPureIR } from '../src/ir.js'
16
- import { emit } from '../src/emit.js'
17
- import { emitter } from '../src/ctx.js'
18
- import { repOf } from '../src/analyze.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
- // Direct stdlib→stdlib call edges. resolveIncludes() expands these transitively,
22
- // so each emitter declares only the single stdlib it directly calls.
23
- Object.assign(ctx.core.stdlibDeps, {
24
- 'math.sin': ['math.isFinite'],
25
- 'math.cos': ['math.isFinite'],
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 call = (name, ...args) => typed(['call', `$${name}`, ...args.map(a => toNumF64(a, emit(a)))], 'f64')
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 => canon(f('f64.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,36 +237,42 @@ 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
- ctx.core.emit['math.sign'] = emitter(['math.sign'], a => call('math.sign', a))
240
+ reg('math.sign', ['math.sign'], a => fn('math.sign', a))
155
241
 
156
- // Trig
157
- ctx.core.emit['math.sin'] = emitter(['math.sin'], a => call('math.sin', a))
158
- ctx.core.emit['math.cos'] = emitter(['math.cos'], a => call('math.cos', a))
159
- ctx.core.emit['math.tan'] = emitter(['math.tan'], a => call('math.tan', a))
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
- ctx.core.emit['math.asin'] = emitter(['math.asin'], a => call('math.asin', a))
163
- ctx.core.emit['math.acos'] = emitter(['math.acos'], a => call('math.acos', a))
164
- ctx.core.emit['math.atan'] = emitter(['math.atan'], a => call('math.atan', a))
165
- ctx.core.emit['math.atan2'] = emitter(['math.atan2'], (a, b) => call('math.atan2', a, b))
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
- ctx.core.emit['math.sinh'] = emitter(['math.sinh'], a => call('math.sinh', a))
169
- ctx.core.emit['math.cosh'] = emitter(['math.cosh'], a => call('math.cosh', a))
170
- ctx.core.emit['math.tanh'] = emitter(['math.tanh'], a => call('math.tanh', a))
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
- ctx.core.emit['math.asinh'] = emitter(['math.asinh'], a => call('math.asinh', a))
174
- ctx.core.emit['math.acosh'] = emitter(['math.acosh'], a => call('math.acosh', a))
175
- ctx.core.emit['math.atanh'] = emitter(['math.atanh'], a => call('math.atanh', a))
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
- ctx.core.emit['math.exp'] = emitter(['math.exp'], a => call('math.exp', a))
179
- ctx.core.emit['math.expm1'] = emitter(['math.expm1'], a => call('math.expm1', a))
180
- ctx.core.emit['math.log'] = emitter(['math.log'], a => call('math.log', a))
181
- ctx.core.emit['math.log2'] = emitter(['math.log2'], a => call('math.log2', a))
182
- ctx.core.emit['math.log10'] = emitter(['math.log10'], a => call('math.log10', a))
183
- ctx.core.emit['math.log1p'] = emitter(['math.log1p'], a => call('math.log1p', a))
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))
184
276
 
185
277
  // Power. Constant-integer-exponent `Math.pow(x,n)` / `x ** n` (|n| ≤ POW_FOLD_MAX)
186
278
  // lower to inline square-and-multiply instead of a $math.pow call. The fold is
@@ -190,7 +282,7 @@ export default (ctx) => {
190
282
  // both the exact bits and the result sign (negative iff x<0 ∧ n odd, which is exactly
191
283
  // its `neg_base`). A program whose only pow use is folded then never pulls the
192
284
  // math.pow/exp/log stdlib. `**`'s exponent is parsed as a bare number (incl. negatives).
193
- const POW_FOLD_MAX = 8
285
+ const POW_FOLD_MAX = 16
194
286
  const get = name => ['local.get', `$${name}`]
195
287
  const constInt = b => {
196
288
  const v = typeof b === 'number' ? b
@@ -226,18 +318,52 @@ export default (ctx) => {
226
318
  const inner = typed(['block', ['result', 'f64'], ...stmts, result], 'f64')
227
319
  return (minted && !neverNaN(a, baseIR)) ? canon(inner) : inner
228
320
  }
229
- const powCall = emitter(['math.pow'], (a, b) => call('math.pow', a, b))
230
- ctx.core.emit['math.pow'] = (a, b) => {
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) => {
231
340
  const n = constInt(b)
232
- return n !== null && Math.abs(n) <= POW_FOLD_MAX ? foldPow(a, n) : powCall(a, 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'))
233
359
  }
234
- ctx.core.emit['math.pow'].deps = powCall.deps // metadata parity (tabulation/analysis)
235
- ctx.core.emit['**'] = ctx.core.emit['math.pow']
236
- ctx.core.emit['math.cbrt'] = emitter(['math.cbrt'], a => call('math.cbrt', a))
237
- ctx.core.emit['math.hypot'] = emitter(['math.hypot'], (a, b, ...rest) => {
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) => {
238
364
  if (a === undefined) return typed(['f64.const', 0], 'f64')
239
365
  if (b === undefined) return f('f64.abs', a)
240
- let r = call('math.hypot', a, b)
366
+ let r = fn('math.hypot', a, b)
241
367
  // ToNumber every rest arg too (matches min/max) — an object arg's valueOf
242
368
  // must run and may throw, which Math.hypot propagates.
243
369
  for (const x of rest) r = typed(['call', '$math.hypot', r, toNumF64(x, emit(x))], 'f64')
@@ -246,7 +372,7 @@ export default (ctx) => {
246
372
 
247
373
  // Math.sumPrecise(iterable) — exact, correctly-rounded summation (ECMA-262).
248
374
  // jz models the array case; the WAT routine sums via a fixed-point accumulator.
249
- ctx.core.emit['math.sumPrecise'] = emitter(['math.sumPrecise'], arr =>
375
+ reg('math.sumPrecise', ['math.sumPrecise'], arr =>
250
376
  typed(['call', '$math.sumPrecise', ['i64.reinterpret_f64', asF64(emit(arr))]], 'f64'))
251
377
 
252
378
  // Integer/bit operations: return i32 directly. Consumers `asF64`-rebox at
@@ -258,99 +384,170 @@ export default (ctx) => {
258
384
  ctx.core.emit['math.imul'] = (a, b) => typed(['i32.mul', toI32(emit(a)), toI32(emit(b))], 'i32')
259
385
 
260
386
  // Random
261
- ctx.core.emit['math.random'] = emitter(['math.random'], () => typed(['call', '$math.random'], 'f64'))
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
+ })
262
398
 
263
399
  // ============================================
264
400
  // WAT stdlib implementations
265
401
  // ============================================
266
402
 
267
- ctx.core.stdlib['math.sign'] = `(func $math.sign (param $x f64) (result f64)
403
+ wat('math.sign', `(func $math.sign (param $x f64) (result f64)
268
404
  ;; sign(NaN) = NaN, sign(±0) = ±0 — both pass x through unchanged.
269
405
  (if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
270
406
  (if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
271
407
  (if (result f64) (f64.gt (local.get $x) (f64.const 0.0))
272
408
  (then (f64.const 1.0))
273
- (else (f64.const -1.0))))`
274
-
275
- ctx.core.stdlib['math.sin'] = `(func $math.sin (param $x f64) (result f64)
276
- (local $n i32) (local $r f64) (local $x2 f64) (local $sign f64)
277
- ;; NaN/±Infinity NaN (avoid trapping i32.trunc on infinities).
278
- (if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (f64.const nan))))
279
- (local.set $sign (f64.const 1.0))
280
- (local.set $n (i32.trunc_f64_s (f64.floor (f64.div (local.get $x) (f64.const ${Math.PI})))))
281
- (local.set $r (f64.sub (local.get $x) (f64.mul (f64.convert_i32_s (local.get $n)) (f64.const ${Math.PI}))))
282
- (if (i32.and (local.get $n) (i32.const 1)) (then (local.set $sign (f64.const -1.0))))
283
- (if (f64.gt (local.get $r) (f64.const ${Math.PI / 2})) (then (local.set $r (f64.sub (f64.const ${Math.PI}) (local.get $r)))))
284
- (if (f64.lt (local.get $r) (f64.const 0.0)) (then
285
- (local.set $r (f64.neg (local.get $r)))
286
- (local.set $sign (f64.neg (local.get $sign)))))
287
- (local.set $x2 (f64.mul (local.get $r) (local.get $r)))
288
- (f64.mul (local.get $sign) (f64.mul (local.get $r) (f64.sub (f64.const 1.0) (f64.mul (local.get $x2)
289
- (f64.sub (f64.const 0.16666666666666666) (f64.mul (local.get $x2)
290
- (f64.sub (f64.const 0.008333333333333333) (f64.mul (local.get $x2)
291
- (f64.sub (f64.const 0.0001984126984126984) (f64.mul (local.get $x2)
292
- (f64.sub (f64.const 0.0000027557319223985893) (f64.mul (local.get $x2)
293
- (f64.const 2.505210838544172e-8))))))))))))))`
294
-
295
- ctx.core.stdlib['math.cos'] = `(func $math.cos (param $x f64) (result f64)
296
- (local $n i32) (local $r f64) (local $x2 f64) (local $sign f64)
297
- ;; NaN/±Infinity → NaN (avoid trapping i32.trunc on infinities).
298
- (if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (f64.const nan))))
299
- (local.set $sign (f64.const 1.0))
300
- (local.set $n (i32.trunc_f64_s (f64.floor (f64.div (local.get $x) (f64.const ${Math.PI})))))
301
- (local.set $r (f64.sub (local.get $x) (f64.mul (f64.convert_i32_s (local.get $n)) (f64.const ${Math.PI}))))
302
- (if (i32.and (local.get $n) (i32.const 1)) (then (local.set $sign (f64.const -1.0))))
303
- (if (f64.gt (local.get $r) (f64.const ${Math.PI / 2})) (then
304
- (local.set $r (f64.sub (f64.const ${Math.PI}) (local.get $r)))
305
- (local.set $sign (f64.neg (local.get $sign)))))
306
- (if (f64.lt (local.get $r) (f64.const 0.0)) (then (local.set $r (f64.neg (local.get $r)))))
307
- (local.set $x2 (f64.mul (local.get $r) (local.get $r)))
308
- (f64.mul (local.get $sign) (f64.sub (f64.const 1.0) (f64.mul (local.get $x2)
309
- (f64.sub (f64.const 0.5) (f64.mul (local.get $x2)
310
- (f64.sub (f64.const 0.041666666666666664) (f64.mul (local.get $x2)
311
- (f64.sub (f64.const 0.001388888888888889) (f64.mul (local.get $x2)
312
- (f64.sub (f64.const 0.0000248015873015873) (f64.mul (local.get $x2)
313
- (f64.const 2.7557319223985893e-7)))))))))))))`
314
-
315
- ctx.core.stdlib['math.tan'] = `(func $math.tan (param $x f64) (result f64)
316
- (f64.div (call $math.sin (local.get $x)) (call $math.cos (local.get $x))))`
317
-
318
- ctx.core.stdlib['math.exp'] = `(func $math.exp (param $x f64) (result f64)
319
- (local $k i32) (local $t f64) (local $t2 f64) (local $result f64) (local $pow2 f64)
320
- (if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
321
- ;; +Infinity +Infinity; finite overflow (x > 709) also rounds to +Infinity.
322
- (if (result f64) (f64.gt (local.get $x) (f64.const 709.0)) (then (f64.const inf)) (else
323
- (if (result f64) (f64.lt (local.get $x) (f64.const -745.0)) (then (f64.const 0.0)) (else
324
- (local.set $k (i32.trunc_f64_s (f64.div (local.get $x) (f64.const ${Math.LN2}))))
325
- (local.set $t (f64.sub (local.get $x) (f64.mul (f64.convert_i32_s (local.get $k)) (f64.const ${Math.LN2}))))
326
- (local.set $t2 (f64.mul (local.get $t) (local.get $t)))
327
- (local.set $result (f64.add (f64.const 1.0) (f64.add (local.get $t)
328
- (f64.mul (local.get $t2) (f64.add (f64.const 0.5)
329
- (f64.mul (local.get $t) (f64.add (f64.const 0.16666666666666666)
330
- (f64.mul (local.get $t) (f64.add (f64.const 0.041666666666666664)
331
- (f64.mul (local.get $t) (f64.add (f64.const 0.008333333333333333)
332
- (f64.mul (local.get $t) (f64.const 0.001388888888888889)))))))))))))
333
- (local.set $pow2 (f64.const 1.0))
334
- (if (i32.gt_s (local.get $k) (i32.const 0))
335
- (then (block $done (loop $loop
336
- (br_if $done (i32.le_s (local.get $k) (i32.const 0)))
337
- (local.set $pow2 (f64.mul (local.get $pow2) (f64.const 2.0)))
338
- (local.set $k (i32.sub (local.get $k) (i32.const 1)))
339
- (br $loop)))
340
- (local.set $result (f64.mul (local.get $result) (local.get $pow2))))
341
- (else (if (i32.lt_s (local.get $k) (i32.const 0))
342
- (then (block $done2 (loop $loop2
343
- (br_if $done2 (i32.ge_s (local.get $k) (i32.const 0)))
344
- (local.set $pow2 (f64.mul (local.get $pow2) (f64.const 2.0)))
345
- (local.set $k (i32.add (local.get $k) (i32.const 1)))
346
- (br $loop2)))
347
- (local.set $result (f64.div (local.get $result) (local.get $pow2)))))))
348
- (local.get $result))))))`
349
-
350
- ctx.core.stdlib['math.expm1'] = `(func $math.expm1 (param $x f64) (result f64)
351
- ;; Preserve sign of zero: expm1(±0) = ±0.
352
- (if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
353
- (f64.sub (call $math.exp (local.get $x)) (f64.const 1.0)))`
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)))))`)
354
551
 
355
552
  // log(x) via bit-level frexp + sqrt(2)-centered split + atanh series.
356
553
  // x = m * 2^k with bits-extracted k (no loop)
@@ -360,8 +557,9 @@ export default (ctx) => {
360
557
  // With 9 polynomial terms and |s|≤0.172, truncation error ≈ 2|s|·z⁹/19 ≈ 4e-17,
361
558
  // close to f64 ulp. The whole routine is branchless after edge cases.
362
559
  // Edge cases: NaN→NaN, ≤0 distinguishes 0→-Inf, <0→NaN; +Inf passes through.
363
- ctx.core.stdlib['math.log'] = `(func $math.log (param $x f64) (result f64)
560
+ wat('math.log', `(func $math.log (param $x f64) (result f64)
364
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)
365
563
  (if (f64.ne (local.get $x) (local.get $x))
366
564
  (then (return (local.get $x))))
367
565
  (if (f64.le (local.get $x) (f64.const 0.0))
@@ -392,24 +590,30 @@ export default (ctx) => {
392
590
  (then
393
591
  (local.set $m (f64.mul (local.get $m) (f64.const 0.5)))
394
592
  (local.set $k (i32.add (local.get $k) (i32.const 1)))))
395
- (local.set $s (f64.div (f64.sub (local.get $m) (f64.const 1.0))
396
- (f64.add (local.get $m) (f64.const 1.0))))
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))))
397
599
  (local.set $z (f64.mul (local.get $s) (local.get $s)))
398
- ;; Horner: 1 + z/3 + z²/5 + z³/7 + z⁴/9 + z⁵/11 + z⁶/13 + z⁷/15 + z⁸/17
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))))
399
609
  (f64.add
400
610
  (f64.mul (f64.convert_i32_s (local.get $k)) (f64.const ${Math.LN2}))
401
- (f64.mul (f64.const 2.0) (f64.mul (local.get $s) (f64.add (f64.const 1.0)
402
- (f64.mul (local.get $z) (f64.add (f64.const 0.3333333333333333)
403
- (f64.mul (local.get $z) (f64.add (f64.const 0.2)
404
- (f64.mul (local.get $z) (f64.add (f64.const 0.14285714285714285)
405
- (f64.mul (local.get $z) (f64.add (f64.const 0.1111111111111111)
406
- (f64.mul (local.get $z) (f64.add (f64.const 0.09090909090909091)
407
- (f64.mul (local.get $z) (f64.add (f64.const 0.07692307692307693)
408
- (f64.mul (local.get $z) (f64.add (f64.const 0.06666666666666667)
409
- (f64.mul (local.get $z) (f64.const 0.058823529411764705)))))))))))))))))))))`
410
-
411
- ctx.core.stdlib['math.log2'] = `(func $math.log2 (param $x f64) (result f64)
412
- (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})))`)
413
617
 
414
618
  // log10 via fdlibm's two-term decomposition: log10(x) = k*log10(2) + log10(m).
415
619
  // A plain log(x)/ln(10) double-rounds (rounding of log itself, then of the
@@ -417,7 +621,7 @@ export default (ctx) => {
417
621
  // Reducing x = m·2^k, splitting log10(2) and 1/ln(10) into hi/lo halves, and
418
622
  // keeping the bulk term (k·log10_2hi, hi·ivln10hi) carry-free recovers the
419
623
  // last ulps, so log10(10/100/1000/…) round-trips to exact integers.
420
- ctx.core.stdlib['math.log10'] = `(func $math.log10 (param $x f64) (result f64)
624
+ wat('math.log10', `(func $math.log10 (param $x f64) (result f64)
421
625
  (local $bits i64) (local $k i32) (local $m f64) (local $f f64)
422
626
  (local $hfsq f64) (local $s f64) (local $z f64) (local $w f64)
423
627
  (local $t1 f64) (local $t2 f64) (local $R f64)
@@ -481,12 +685,12 @@ export default (ctx) => {
481
685
  (local.set $w (f64.add (local.get $y) (local.get $valhi)))
482
686
  (local.set $vallo (f64.add (local.get $vallo)
483
687
  (f64.add (f64.sub (local.get $y) (local.get $w)) (local.get $valhi))))
484
- (f64.add (local.get $vallo) (local.get $w)))`
688
+ (f64.add (local.get $vallo) (local.get $w)))`)
485
689
 
486
690
  // log1p(x) via Kahan's compensated trick: with u = 1+x, log(u) loses bits when x is
487
691
  // small (because u rounds to ~1), but the ratio x/(u-1) is exactly the missing factor.
488
692
  // For u==1 (x below ulp), result is just x; preserves -0 from x=-0 path.
489
- ctx.core.stdlib['math.log1p'] = `(func $math.log1p (param $x f64) (result f64)
693
+ wat('math.log1p', `(func $math.log1p (param $x f64) (result f64)
490
694
  (local $u f64)
491
695
  ;; log1p(+Inf) = +Inf — the ratio trick below would compute Inf/Inf = NaN.
492
696
  (if (f64.eq (local.get $x) (f64.const inf)) (then (return (f64.const inf))))
@@ -495,9 +699,9 @@ export default (ctx) => {
495
699
  (then (return (local.get $x))))
496
700
  (f64.div
497
701
  (f64.mul (call $math.log (local.get $u)) (local.get $x))
498
- (f64.sub (local.get $u) (f64.const 1.0))))`
702
+ (f64.sub (local.get $u) (f64.const 1.0))))`)
499
703
 
500
- ctx.core.stdlib['math.pow'] = `(func $math.pow (param $x f64) (param $y f64) (result f64)
704
+ wat('math.pow', `(func $math.pow (param $x f64) (param $y f64) (result f64)
501
705
  (local $result f64) (local $n i32) (local $neg_base i32) (local $abs_x f64)
502
706
  ;; y == 0 -> 1 (covers pow(NaN,0), pow(±0,0), pow(±Inf,0))
503
707
  (if (f64.eq (local.get $y) (f64.const 0.0)) (then (return (f64.const 1.0))))
@@ -570,12 +774,12 @@ export default (ctx) => {
570
774
  ;; x < 0, non-integer finite y -> NaN
571
775
  (if (f64.lt (local.get $x) (f64.const 0.0))
572
776
  (then (return (f64.const nan))))
573
- (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)))))`)
574
778
 
575
779
  // fdlibm atan: 4-region argument reduction onto |r| ≤ tan(π/16), then an
576
780
  // 11-term odd polynomial split into even/odd parts. Accurate to <1 ulp —
577
781
  // the old Taylor series was ~2e-6 off near |x|=0.5. Drives asin/acos/atan2.
578
- ctx.core.stdlib['math.atan'] = `(func $math.atan (param $x f64) (result f64)
782
+ wat('math.atan', `(func $math.atan (param $x f64) (result f64)
579
783
  (local $abs_x f64) (local $id i32) (local $r f64) (local $z f64) (local $w f64)
580
784
  (local $s1 f64) (local $s2 f64) (local $ahi f64) (local $alo f64) (local $res f64)
581
785
  ;; NaN passes through unchanged.
@@ -642,19 +846,20 @@ export default (ctx) => {
642
846
  (local.set $res (f64.sub (local.get $ahi)
643
847
  (f64.sub (f64.sub (f64.mul (local.get $r) (f64.add (local.get $s1) (local.get $s2))) (local.get $alo))
644
848
  (local.get $r))))
645
- (f64.copysign (local.get $res) (local.get $x)))`
849
+ (f64.copysign (local.get $res) (local.get $x)))`)
646
850
 
647
- ctx.core.stdlib['math.asin'] = `(func $math.asin (param $x f64) (result f64)
851
+ wat('math.asin', `(func $math.asin (param $x f64) (result f64)
648
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.
649
854
  (if (result f64) (f64.gt (f64.abs (local.get $x)) (f64.const 1.0))
650
855
  (then (f64.const nan))
651
856
  (else (call $math.atan (f64.div (local.get $x)
652
- (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)))))))))`)
653
858
 
654
- ctx.core.stdlib['math.acos'] = `(func $math.acos (param $x f64) (result f64)
655
- (f64.sub (f64.const ${Math.PI / 2}) (call $math.asin (local.get $x))))`
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))))`)
656
861
 
657
- ctx.core.stdlib['math.atan2'] = `(func $math.atan2 (param $y f64) (param $x f64) (result f64)
862
+ wat('math.atan2', `(func $math.atan2 (param $y f64) (param $x f64) (result f64)
658
863
  ;; If either argument is NaN, the result is NaN (ECMA-262 21.3.2.5).
659
864
  (if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
660
865
  (if (f64.ne (local.get $y) (local.get $y)) (then (return (local.get $y))))
@@ -662,30 +867,30 @@ export default (ctx) => {
662
867
  ;; y is ±0 too: result is ±0 when x is +0, ±π when x is -0; sign taken from y.
663
868
  (if (result f64) (f64.eq (local.get $y) (f64.const 0.0))
664
869
  (then (f64.copysign
665
- (select (f64.const ${Math.PI}) (f64.const 0.0)
870
+ (select (f64.const ${PI}) (f64.const 0.0)
666
871
  (f64.lt (f64.copysign (f64.const 1.0) (local.get $x)) (f64.const 0.0)))
667
872
  (local.get $y)))
668
873
  (else
669
- (if (result f64) (f64.gt (local.get $y) (f64.const 0.0)) (then (f64.const ${Math.PI / 2})) (else (f64.neg (f64.const ${Math.PI / 2})))))))
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})))))))
670
875
  (else (if (result f64) (f64.ge (local.get $x) (f64.const 0.0))
671
876
  (then (call $math.atan (f64.div (local.get $y) (local.get $x))))
672
877
  (else (if (result f64) (f64.ge (local.get $y) (f64.const 0.0))
673
- (then (f64.add (call $math.atan (f64.div (local.get $y) (local.get $x))) (f64.const ${Math.PI})))
674
- (else (f64.sub (call $math.atan (f64.div (local.get $y) (local.get $x))) (f64.const ${Math.PI})))))))))`
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})))))))))`)
675
880
 
676
- ctx.core.stdlib['math.sinh'] = `(func $math.sinh (param $x f64) (result f64)
881
+ wat('math.sinh', `(func $math.sinh (param $x f64) (result f64)
677
882
  (local $ex f64)
678
883
  ;; Preserve sign of zero: sinh(±0) = ±0 (the f64.lt sign test below is false for -0).
679
884
  (if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
680
885
  (local.set $ex (call $math.exp (f64.abs (local.get $x))))
681
886
  (local.set $ex (f64.mul (f64.const 0.5) (f64.sub (local.get $ex) (f64.div (f64.const 1.0) (local.get $ex)))))
682
- (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))))`)
683
888
 
684
- ctx.core.stdlib['math.cosh'] = `(func $math.cosh (param $x f64) (result f64)
889
+ wat('math.cosh', `(func $math.cosh (param $x f64) (result f64)
685
890
  (local $ex f64) (local.set $ex (call $math.exp (f64.abs (local.get $x))))
686
- (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)))))`)
687
892
 
688
- ctx.core.stdlib['math.tanh'] = `(func $math.tanh (param $x f64) (result f64)
893
+ wat('math.tanh', `(func $math.tanh (param $x f64) (result f64)
689
894
  (local $e2x f64)
690
895
  ;; Preserve sign of zero: tanh(±0) = ±0 (the f64.lt sign test below is false for -0).
691
896
  (if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
@@ -693,30 +898,30 @@ export default (ctx) => {
693
898
  (then (if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.const -1.0)) (else (f64.const 1.0))))
694
899
  (else (local.set $e2x (call $math.exp (f64.mul (f64.const 2.0) (f64.abs (local.get $x)))))
695
900
  (local.set $e2x (f64.div (f64.sub (local.get $e2x) (f64.const 1.0)) (f64.add (local.get $e2x) (f64.const 1.0))))
696
- (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))))))`)
697
902
 
698
- ctx.core.stdlib['math.asinh'] = `(func $math.asinh (param $x f64) (result f64)
903
+ wat('math.asinh', `(func $math.asinh (param $x f64) (result f64)
699
904
  ;; ±Infinity and NaN pass through unchanged. (log(±Inf + sqrt(Inf²+1)) → NaN otherwise.)
700
905
  (if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (local.get $x))))
701
906
  ;; Preserve sign of zero: asinh(±0) = ±0.
702
907
  (if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
703
- (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))))))`)
704
909
 
705
- ctx.core.stdlib['math.acosh'] = `(func $math.acosh (param $x f64) (result f64)
910
+ wat('math.acosh', `(func $math.acosh (param $x f64) (result f64)
706
911
  (if (f64.eq (local.get $x) (f64.const inf)) (then (return (f64.const inf))))
707
912
  ;; acosh is defined only for x >= 1; everything below (incl. -Inf) is NaN.
708
913
  (if (result f64) (f64.lt (local.get $x) (f64.const 1.0)) (then (f64.const nan)) (else
709
- (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))))))))`)
710
915
 
711
- ctx.core.stdlib['math.atanh'] = `(func $math.atanh (param $x f64) (result f64)
916
+ wat('math.atanh', `(func $math.atanh (param $x f64) (result f64)
712
917
  ;; Preserve sign of zero: atanh(±0) = ±0.
713
918
  (if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
714
919
  ;; ±Infinity → NaN. Without this the (1+x)/(1-x) ratio is Inf/Inf, whose
715
920
  ;; sign-nondeterministic arithmetic NaN would escape non-canonical on x86.
716
921
  (if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const nan))))
717
- (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))))))`)
718
923
 
719
- ctx.core.stdlib['math.cbrt'] = `(func $math.cbrt (param $x f64) (result f64)
924
+ wat('math.cbrt', `(func $math.cbrt (param $x f64) (result f64)
720
925
  (local $y f64)
721
926
  ;; ±Infinity and NaN pass through; preserve sign of zero.
722
927
  (if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (local.get $x))))
@@ -728,31 +933,37 @@ export default (ctx) => {
728
933
  (local.set $y (call $math.pow (local.get $x) (f64.const 0.3333333333333333)))
729
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)))
730
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)))
731
- (local.get $y))))`
936
+ (local.get $y))))`)
732
937
 
733
938
  // Small finite-test helper (NaN→0, ±Inf→0, finite→1). Used by transcendental
734
939
  // functions that need to short-circuit on infinite inputs.
735
- ctx.core.stdlib['math.isFinite'] = `(func $math.isFinite (param $x f64) (result i32)
940
+ wat('math.isFinite', `(func $math.isFinite (param $x f64) (result i32)
736
941
  (i32.and
737
942
  (f64.eq (local.get $x) (local.get $x))
738
- (f64.lt (f64.abs (local.get $x)) (f64.const inf))))`
943
+ (f64.lt (f64.abs (local.get $x)) (f64.const inf))))`)
739
944
 
740
- ctx.core.stdlib['math.hypot'] = `(func $math.hypot (param $x f64) (param $y f64) (result f64)
945
+ wat('math.hypot', `(func $math.hypot (param $x f64) (param $y f64) (result f64)
741
946
  ;; Any ±Infinity argument ⇒ +Infinity, even when the other is NaN (ECMA-262 21.3.2.18).
742
947
  (if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const inf))))
743
948
  (if (f64.eq (f64.abs (local.get $y)) (f64.const inf)) (then (return (f64.const inf))))
744
- (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)))))`)
745
950
 
746
- ctx.core.stdlib['math.random'] = `(func $math.random (result f64)
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)
747
957
  (local $s i32)
748
- (local.set $s (global.get $math.rng_state))
958
+ ${rngSeedPrologue}(local.set $s (global.get $math.rng_state))
749
959
  (local.set $s (i32.xor (local.get $s) (i32.shl (local.get $s) (i32.const 13))))
750
960
  (local.set $s (i32.xor (local.get $s) (i32.shr_u (local.get $s) (i32.const 17))))
751
961
  (local.set $s (i32.xor (local.get $s) (i32.shl (local.get $s) (i32.const 5))))
752
962
  (global.set $math.rng_state (local.get $s))
753
- (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'] : [])
754
965
 
755
- ctx.core.stdlib['math.sumPrecise'] = `(func $math.sumPrecise (param $arr i64) (result f64)
966
+ wat('math.sumPrecise', `(func $math.sumPrecise (param $arr i64) (result f64)
756
967
  ;; Exact summation via a 2304-bit fixed-point accumulator (36 i64 words,
757
968
  ;; little-endian two's complement) holding sum*2^1074. Every finite f64 is an
758
969
  ;; integer multiple of 2^-1074, so the running sum carries zero rounding
@@ -911,8 +1122,25 @@ export default (ctx) => {
911
1122
  (then (f64.reinterpret_i64 (i64.shl (i64.extend_i32_u (i32.add (local.get $k) (i32.const 1023))) (i64.const 52))))
912
1123
  (else (f64.reinterpret_i64 (i64.shl (i64.const 1) (i64.extend_i32_u (i32.add (local.get $k) (i32.const 1074))))))))
913
1124
  (local.set $res (f64.mul (f64.convert_i64_u (local.get $top)) (local.get $pow)))
914
- (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)))`)
915
1126
 
916
- // Global for random state
917
- ctx.scope.globals.set('math.rng_state', '(global $math.rng_state (mut i32) (i32.const 12345))')
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
+ }
918
1146
  }