jz 0.4.0 → 0.5.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 +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +251 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +551 -128
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +540 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +662 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +786 -94
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +822 -150
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
- package/src/fuse.js +0 -159
package/module/math.js
CHANGED
|
@@ -2,10 +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'] = (args
|
|
5
|
+
* - ctx.core.emit['math.X'] = emitter(deps, args => WasmNode) - emitters with declarative stdlib deps
|
|
6
6
|
* - ctx.core.stdlib['math.X'] = '(func ...)' - WAT function definitions
|
|
7
|
-
* - ctx.
|
|
8
|
-
* - include('math.X') - marks stdlib for inclusion (called by emitters)
|
|
7
|
+
* - ctx.core.stdlibDeps['math.X'] = ['dep'] - direct stdlib→stdlib edges (expanded transitively)
|
|
9
8
|
*
|
|
10
9
|
* Prepare resolves Math.sin(x) → ['()', 'math.sin', x]
|
|
11
10
|
* Compile looks up ctx.core.emit['math.sin'] and calls it.
|
|
@@ -13,23 +12,73 @@
|
|
|
13
12
|
* @module math
|
|
14
13
|
*/
|
|
15
14
|
|
|
16
|
-
import { typed, asF64, asI32, toI32, temp, arrayLoop } from '../src/ir.js'
|
|
15
|
+
import { typed, asF64, asI32, toI32, toNumF64, temp, arrayLoop, isLit, litVal } from '../src/ir.js'
|
|
17
16
|
import { emit } from '../src/emit.js'
|
|
18
|
-
import {
|
|
17
|
+
import { emitter } from '../src/ctx.js'
|
|
19
18
|
import { repOf } from '../src/analyze.js'
|
|
20
19
|
|
|
21
20
|
export default (ctx) => {
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
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'],
|
|
26
|
+
'math.tan': ['math.sin', 'math.cos'],
|
|
27
|
+
'math.expm1': ['math.exp'],
|
|
28
|
+
'math.log2': ['math.log'],
|
|
29
|
+
'math.log1p': ['math.log'],
|
|
30
|
+
'math.pow': ['math.exp', 'math.log'],
|
|
31
|
+
'math.asin': ['math.atan'],
|
|
32
|
+
'math.acos': ['math.asin'],
|
|
33
|
+
'math.atan2': ['math.atan'],
|
|
34
|
+
'math.sinh': ['math.exp'],
|
|
35
|
+
'math.cosh': ['math.exp'],
|
|
36
|
+
'math.tanh': ['math.exp'],
|
|
37
|
+
'math.asinh': ['math.isFinite', 'math.log'],
|
|
38
|
+
'math.acosh': ['math.log'],
|
|
39
|
+
'math.atanh': ['math.log'],
|
|
40
|
+
'math.cbrt': ['math.isFinite', 'math.pow'],
|
|
41
|
+
'math.sumPrecise': ['__ptr_offset', '__len', '__alloc'],
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Helpers: all math ops take f64 and return f64. Args go through ToNumber
|
|
45
|
+
// (toNumF64) — ECMA Math methods coerce each argument, so null→0, undefined→NaN.
|
|
46
|
+
const f = (op, a) => typed([op, toNumF64(a, emit(a))], 'f64')
|
|
25
47
|
// floor/ceil/trunc/round are no-ops on integer-valued operands. When the
|
|
26
48
|
// arg is a local whose every def is integer-valued (intCertain lattice),
|
|
27
|
-
// skip the wasm op and just hand back the operand cast to f64.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
// skip the wasm op and just hand back the operand cast to f64. Same elision
|
|
50
|
+
// fires for schema-field reads `o.x` when every observed write to that slot
|
|
51
|
+
// is integer-shaped (ctx.schema.slotIntCertainAt).
|
|
52
|
+
const isIntCertain = a => {
|
|
53
|
+
if (typeof a === 'string') return repOf(a)?.intCertain === true
|
|
54
|
+
if (Array.isArray(a) && a[0] === '.' && typeof a[1] === 'string' && typeof a[2] === 'string') {
|
|
55
|
+
return ctx.schema.slotIntCertainAt?.(a[1], a[2]) === true
|
|
56
|
+
}
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
const fInt = (op, a) => isIntCertain(a) ? asF64(emit(a)) : f(op, a)
|
|
60
|
+
// ECMA Math methods perform ToNumber on each argument. toNumF64 short-circuits
|
|
61
|
+
// for known-number nodes, and routes everything else through __to_num so null→0,
|
|
62
|
+
// undefined→NaN, and strings get parsed. Without this, raw NaN-boxed pointers
|
|
63
|
+
// (null/undefined/strings) would propagate through math.log etc. and surface
|
|
64
|
+
// as the original null/undefined sentinel after decode.
|
|
65
|
+
const call = (name, ...args) => typed(['call', `$${name}`, ...args.map(a => toNumF64(a, emit(a)))], 'f64')
|
|
66
|
+
|
|
67
|
+
// Canonicalize a possibly-NaN f64 result. A wasm arithmetic op that mints a
|
|
68
|
+
// fresh NaN (f64.sqrt of a negative, f64.min/max with a NaN operand) leaves
|
|
69
|
+
// the sign bit nondeterministic — x86 yields the negative NaN 0xFFF8.., ARM
|
|
70
|
+
// the positive 0x7FF8... jz's carrier reserves 0x7FF8.. as THE number-NaN;
|
|
71
|
+
// a negative-NaN number is bit-identical to a negative BigInt and corrupts
|
|
72
|
+
// untyped === / typeof. So fold any NaN back to canonical where one is born.
|
|
73
|
+
const canon = (node) => {
|
|
74
|
+
const t = temp('cn')
|
|
75
|
+
return typed(['block', ['result', 'f64'],
|
|
76
|
+
['local.set', `$${t}`, node],
|
|
77
|
+
['select',
|
|
78
|
+
['f64.const', 'nan'],
|
|
79
|
+
['local.get', `$${t}`],
|
|
80
|
+
['f64.ne', ['local.get', `$${t}`], ['local.get', `$${t}`]]]], 'f64')
|
|
81
|
+
}
|
|
33
82
|
|
|
34
83
|
// Constants
|
|
35
84
|
ctx.core.emit['math.PI'] = () => typed(['f64.const', Math.PI], 'f64')
|
|
@@ -53,38 +102,45 @@ export default (ctx) => {
|
|
|
53
102
|
['local.get', `$${acc}`]], 'f64')
|
|
54
103
|
}
|
|
55
104
|
|
|
56
|
-
// Built-in WASM ops
|
|
57
|
-
|
|
105
|
+
// Built-in WASM ops. sqrt/min/max mint a fresh NaN (sqrt of a negative, min/max
|
|
106
|
+
// with a NaN operand) whose sign is platform-nondeterministic — `canon` folds it
|
|
107
|
+
// 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))
|
|
58
109
|
ctx.core.emit['math.abs'] = a => f('f64.abs', a)
|
|
59
110
|
ctx.core.emit['math.floor'] = a => fInt('f64.floor', a)
|
|
60
111
|
ctx.core.emit['math.ceil'] = a => fInt('f64.ceil', a)
|
|
61
112
|
ctx.core.emit['math.trunc'] = a => fInt('f64.trunc', a)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (a === undefined) return typed(['f64.const',
|
|
73
|
-
|
|
74
|
-
if (b
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
113
|
+
// Math.min/max fold their operands with a wasm op. f64.min/max PROPAGATE a
|
|
114
|
+
// NaN but never MINT one, so `canon` is needed only when an operand could
|
|
115
|
+
// itself be NaN. An operand provably never is when it's an intCertain local/
|
|
116
|
+
// slot, a non-NaN numeric literal, or an i32-typed carrier (`x|0`, compares,
|
|
117
|
+
// lengths). When every operand qualifies, drop `canon` — erasing its cost
|
|
118
|
+
// from the common integer-clamp idiom Math.min(idx, len) / Math.max(x|0, lo).
|
|
119
|
+
const neverNaN = (src, v) =>
|
|
120
|
+
isIntCertain(src) || (typeof src === 'number' && src === src) ||
|
|
121
|
+
(v.type === 'i32' && v.ptrKind == null) || (isLit(v) && litVal(v) === litVal(v))
|
|
122
|
+
const minmax = (op, ident) => (a, b, ...rest) => {
|
|
123
|
+
if (a === undefined) return typed(['f64.const', ident], 'f64')
|
|
124
|
+
// Spread: Math.min(...arr) — array contents unknown, keep canon
|
|
125
|
+
if (!b && Array.isArray(a) && a[0] === '...') return canon(emitArrayReduce(op, a[1], ident))
|
|
126
|
+
const src = b === undefined ? [a] : [a, b, ...rest]
|
|
127
|
+
const ev = src.map(x => emit(x))
|
|
128
|
+
let r = typed([op, toNumF64(src[0], ev[0]),
|
|
129
|
+
b === undefined ? ['f64.const', ident] : toNumF64(src[1], ev[1])], 'f64')
|
|
130
|
+
for (let i = 2; i < src.length; i++) r = typed([op, r, toNumF64(src[i], ev[i])], 'f64')
|
|
131
|
+
return src.every((s, i) => neverNaN(s, ev[i])) ? r : canon(r)
|
|
78
132
|
}
|
|
133
|
+
ctx.core.emit['math.min'] = minmax('f64.min', Infinity)
|
|
134
|
+
ctx.core.emit['math.max'] = minmax('f64.max', -Infinity)
|
|
79
135
|
// f64.nearest is roundTiesToEven; JS Math.round is roundTiesToward+∞. They agree
|
|
80
136
|
// everywhere except exact half-integers n+0.5 with n even (nearest→n, JS→n+1).
|
|
81
137
|
// Detect that one case — `nearest(x) === x - 0.5` — and bump by one. (The −0.5→−0
|
|
82
138
|
// and 0.49999…94→0 edges already match `f64.nearest`.)
|
|
83
139
|
ctx.core.emit['math.round'] = a => {
|
|
84
|
-
if (
|
|
140
|
+
if (isIntCertain(a)) return asF64(emit(a))
|
|
85
141
|
const t = temp('rnd'), n = temp('rnd')
|
|
86
142
|
return typed(['block', ['result', 'f64'],
|
|
87
|
-
['local.set', `$${t}`,
|
|
143
|
+
['local.set', `$${t}`, toNumF64(a, emit(a))],
|
|
88
144
|
['local.set', `$${n}`, ['f64.nearest', ['local.get', `$${t}`]]],
|
|
89
145
|
['select',
|
|
90
146
|
['f64.add', ['local.get', `$${n}`], ['f64.const', 1]],
|
|
@@ -92,54 +148,58 @@ export default (ctx) => {
|
|
|
92
148
|
['f64.eq', ['local.get', `$${n}`], ['f64.sub', ['local.get', `$${t}`], ['f64.const', 0.5]]]],
|
|
93
149
|
], 'f64')
|
|
94
150
|
}
|
|
95
|
-
ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64',
|
|
151
|
+
ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64', toNumF64(a, emit(a))]], 'f64')
|
|
96
152
|
|
|
97
153
|
// Sign
|
|
98
|
-
ctx.core.emit['math.sign'] = a => call('math.sign', a)
|
|
154
|
+
ctx.core.emit['math.sign'] = emitter(['math.sign'], a => call('math.sign', a))
|
|
99
155
|
|
|
100
156
|
// Trig
|
|
101
|
-
ctx.core.emit['math.sin'] = a => call('math.sin', a)
|
|
102
|
-
ctx.core.emit['math.cos'] = a => call('math.cos', a)
|
|
103
|
-
ctx.core.emit['math.tan'] =
|
|
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))
|
|
104
160
|
|
|
105
161
|
// Inverse trig
|
|
106
|
-
ctx.core.emit['math.asin'] =
|
|
107
|
-
ctx.core.emit['math.acos'] =
|
|
108
|
-
ctx.core.emit['math.atan'] = a => call('math.atan', a)
|
|
109
|
-
ctx.core.emit['math.atan2'] = (a, b) =>
|
|
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))
|
|
110
166
|
|
|
111
167
|
// Hyperbolic
|
|
112
|
-
ctx.core.emit['math.sinh'] =
|
|
113
|
-
ctx.core.emit['math.cosh'] =
|
|
114
|
-
ctx.core.emit['math.tanh'] =
|
|
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))
|
|
115
171
|
|
|
116
172
|
// Inverse hyperbolic
|
|
117
|
-
ctx.core.emit['math.asinh'] =
|
|
118
|
-
ctx.core.emit['math.acosh'] =
|
|
119
|
-
ctx.core.emit['math.atanh'] =
|
|
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))
|
|
120
176
|
|
|
121
177
|
// Exponential and logarithmic
|
|
122
|
-
ctx.core.emit['math.exp'] = a => call('math.exp', a)
|
|
123
|
-
ctx.core.emit['math.expm1'] =
|
|
124
|
-
ctx.core.emit['math.log'] = a => call('math.log', a)
|
|
125
|
-
ctx.core.emit['math.log2'] =
|
|
126
|
-
ctx.core.emit['math.log10'] =
|
|
127
|
-
ctx.core.emit['math.log1p'] =
|
|
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))
|
|
128
184
|
|
|
129
185
|
// Power
|
|
130
|
-
ctx.core.emit['math.pow'] = (
|
|
186
|
+
ctx.core.emit['math.pow'] = emitter(['math.pow'], (a, b) => call('math.pow', a, b))
|
|
131
187
|
ctx.core.emit['**'] = ctx.core.emit['math.pow']
|
|
132
|
-
ctx.core.emit['math.cbrt'] =
|
|
133
|
-
ctx.core.emit['math.hypot'] = (a, b, ...rest) => {
|
|
188
|
+
ctx.core.emit['math.cbrt'] = emitter(['math.cbrt'], a => call('math.cbrt', a))
|
|
189
|
+
ctx.core.emit['math.hypot'] = emitter(['math.hypot'], (a, b, ...rest) => {
|
|
134
190
|
if (a === undefined) return typed(['f64.const', 0], 'f64')
|
|
135
191
|
if (b === undefined) return f('f64.abs', a)
|
|
136
192
|
let r = call('math.hypot', a, b)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
193
|
+
// ToNumber every rest arg too (matches min/max) — an object arg's valueOf
|
|
194
|
+
// must run and may throw, which Math.hypot propagates.
|
|
195
|
+
for (const x of rest) r = typed(['call', '$math.hypot', r, toNumF64(x, emit(x))], 'f64')
|
|
141
196
|
return r
|
|
142
|
-
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Math.sumPrecise(iterable) — exact, correctly-rounded summation (ECMA-262).
|
|
200
|
+
// jz models the array case; the WAT routine sums via a fixed-point accumulator.
|
|
201
|
+
ctx.core.emit['math.sumPrecise'] = emitter(['math.sumPrecise'], arr =>
|
|
202
|
+
typed(['call', '$math.sumPrecise', ['i64.reinterpret_f64', asF64(emit(arr))]], 'f64'))
|
|
143
203
|
|
|
144
204
|
// Integer/bit operations: return i32 directly. Consumers `asF64`-rebox at
|
|
145
205
|
// store/return boundaries; consumers staying in i32 (bit chains, i32 locals)
|
|
@@ -150,21 +210,24 @@ export default (ctx) => {
|
|
|
150
210
|
ctx.core.emit['math.imul'] = (a, b) => typed(['i32.mul', toI32(emit(a)), toI32(emit(b))], 'i32')
|
|
151
211
|
|
|
152
212
|
// Random
|
|
153
|
-
ctx.core.emit['math.random'] = (
|
|
213
|
+
ctx.core.emit['math.random'] = emitter(['math.random'], () => typed(['call', '$math.random'], 'f64'))
|
|
154
214
|
|
|
155
215
|
// ============================================
|
|
156
216
|
// WAT stdlib implementations
|
|
157
217
|
// ============================================
|
|
158
218
|
|
|
159
219
|
ctx.core.stdlib['math.sign'] = `(func $math.sign (param $x f64) (result f64)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
220
|
+
;; sign(NaN) = NaN, sign(±0) = ±0 — both pass x through unchanged.
|
|
221
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
222
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
223
|
+
(if (result f64) (f64.gt (local.get $x) (f64.const 0.0))
|
|
224
|
+
(then (f64.const 1.0))
|
|
225
|
+
(else (f64.const -1.0))))`
|
|
165
226
|
|
|
166
227
|
ctx.core.stdlib['math.sin'] = `(func $math.sin (param $x f64) (result f64)
|
|
167
228
|
(local $n i32) (local $r f64) (local $x2 f64) (local $sign f64)
|
|
229
|
+
;; NaN/±Infinity → NaN (avoid trapping i32.trunc on infinities).
|
|
230
|
+
(if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (f64.const nan))))
|
|
168
231
|
(local.set $sign (f64.const 1.0))
|
|
169
232
|
(local.set $n (i32.trunc_f64_s (f64.floor (f64.div (local.get $x) (f64.const ${Math.PI})))))
|
|
170
233
|
(local.set $r (f64.sub (local.get $x) (f64.mul (f64.convert_i32_s (local.get $n)) (f64.const ${Math.PI}))))
|
|
@@ -183,6 +246,8 @@ export default (ctx) => {
|
|
|
183
246
|
|
|
184
247
|
ctx.core.stdlib['math.cos'] = `(func $math.cos (param $x f64) (result f64)
|
|
185
248
|
(local $n i32) (local $r f64) (local $x2 f64) (local $sign f64)
|
|
249
|
+
;; NaN/±Infinity → NaN (avoid trapping i32.trunc on infinities).
|
|
250
|
+
(if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (f64.const nan))))
|
|
186
251
|
(local.set $sign (f64.const 1.0))
|
|
187
252
|
(local.set $n (i32.trunc_f64_s (f64.floor (f64.div (local.get $x) (f64.const ${Math.PI})))))
|
|
188
253
|
(local.set $r (f64.sub (local.get $x) (f64.mul (f64.convert_i32_s (local.get $n)) (f64.const ${Math.PI}))))
|
|
@@ -205,7 +270,8 @@ export default (ctx) => {
|
|
|
205
270
|
ctx.core.stdlib['math.exp'] = `(func $math.exp (param $x f64) (result f64)
|
|
206
271
|
(local $k i32) (local $t f64) (local $t2 f64) (local $result f64) (local $pow2 f64)
|
|
207
272
|
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
208
|
-
|
|
273
|
+
;; +Infinity → +Infinity; finite overflow (x > 709) also rounds to +Infinity.
|
|
274
|
+
(if (result f64) (f64.gt (local.get $x) (f64.const 709.0)) (then (f64.const inf)) (else
|
|
209
275
|
(if (result f64) (f64.lt (local.get $x) (f64.const -745.0)) (then (f64.const 0.0)) (else
|
|
210
276
|
(local.set $k (i32.trunc_f64_s (f64.div (local.get $x) (f64.const ${Math.LN2}))))
|
|
211
277
|
(local.set $t (f64.sub (local.get $x) (f64.mul (f64.convert_i32_s (local.get $k)) (f64.const ${Math.LN2}))))
|
|
@@ -234,34 +300,54 @@ export default (ctx) => {
|
|
|
234
300
|
(local.get $result))))))`
|
|
235
301
|
|
|
236
302
|
ctx.core.stdlib['math.expm1'] = `(func $math.expm1 (param $x f64) (result f64)
|
|
303
|
+
;; Preserve sign of zero: expm1(±0) = ±0.
|
|
304
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
237
305
|
(f64.sub (call $math.exp (local.get $x)) (f64.const 1.0)))`
|
|
238
306
|
|
|
307
|
+
// log(x) via bit-level frexp + sqrt(2)-centered split + atanh series.
|
|
308
|
+
// x = m * 2^k with bits-extracted k (no loop)
|
|
309
|
+
// if m >= sqrt(2): m /= 2, k += 1 so m ∈ [sqrt(2)/2, sqrt(2)) ≈ [0.707, 1.414)
|
|
310
|
+
// s = (m-1)/(m+1) |s| ≤ 0.172
|
|
311
|
+
// log(x) = k·ln(2) + 2s·(1 + s²/3 + s⁴/5 + ... + s¹⁶/17)
|
|
312
|
+
// With 9 polynomial terms and |s|≤0.172, truncation error ≈ 2|s|·z⁹/19 ≈ 4e-17,
|
|
313
|
+
// close to f64 ulp. The whole routine is branchless after edge cases.
|
|
314
|
+
// Edge cases: NaN→NaN, ≤0 distinguishes 0→-Inf, <0→NaN; +Inf passes through.
|
|
239
315
|
ctx.core.stdlib['math.log'] = `(func $math.log (param $x f64) (result f64)
|
|
240
|
-
(local $k i32) (local $
|
|
316
|
+
(local $bits i64) (local $k i32) (local $m f64) (local $s f64) (local $z f64)
|
|
241
317
|
(if (f64.ne (local.get $x) (local.get $x))
|
|
242
318
|
(then (return (local.get $x))))
|
|
243
319
|
(if (f64.le (local.get $x) (f64.const 0.0))
|
|
244
|
-
(then
|
|
245
|
-
|
|
246
|
-
|
|
320
|
+
(then
|
|
321
|
+
(if (f64.eq (local.get $x) (f64.const 0.0))
|
|
322
|
+
(then (return (f64.const -inf))))
|
|
323
|
+
(return (f64.const nan))))
|
|
247
324
|
(if (f64.eq (local.get $x) (f64.const inf))
|
|
248
325
|
(then (return (local.get $x))))
|
|
249
326
|
(local.set $k (i32.const 0))
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
(local.set $
|
|
255
|
-
(local.set $k (i32.
|
|
256
|
-
|
|
257
|
-
(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
327
|
+
;; Normalize denormals (exponent=0): scale by 2^54 and remember the shift,
|
|
328
|
+
;; so the bit-extracted exponent below is meaningful for every finite x > 0.
|
|
329
|
+
(if (f64.lt (local.get $x) (f64.const 0x1p-1022))
|
|
330
|
+
(then
|
|
331
|
+
(local.set $x (f64.mul (local.get $x) (f64.const 0x1p54)))
|
|
332
|
+
(local.set $k (i32.const -54))))
|
|
333
|
+
;; frexp via bit twiddling: k = ((bits >> 52) & 0x7ff) - 1023, then force exp=1023 so m ∈ [1,2).
|
|
334
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $x)))
|
|
335
|
+
(local.set $k (i32.add (local.get $k) (i32.sub
|
|
336
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 52)) (i64.const 0x7ff)))
|
|
337
|
+
(i32.const 1023))))
|
|
338
|
+
(local.set $m (f64.reinterpret_i64
|
|
339
|
+
(i64.or
|
|
340
|
+
(i64.and (local.get $bits) (i64.const 0x000fffffffffffff))
|
|
341
|
+
(i64.const 0x3ff0000000000000))))
|
|
342
|
+
;; Center on sqrt(2) to shrink |s| from 1/3 down to ~0.172.
|
|
343
|
+
(if (f64.ge (local.get $m) (f64.const 1.4142135623730951))
|
|
344
|
+
(then
|
|
345
|
+
(local.set $m (f64.mul (local.get $m) (f64.const 0.5)))
|
|
346
|
+
(local.set $k (i32.add (local.get $k) (i32.const 1)))))
|
|
347
|
+
(local.set $s (f64.div (f64.sub (local.get $m) (f64.const 1.0))
|
|
348
|
+
(f64.add (local.get $m) (f64.const 1.0))))
|
|
264
349
|
(local.set $z (f64.mul (local.get $s) (local.get $s)))
|
|
350
|
+
;; Horner: 1 + z/3 + z²/5 + z³/7 + z⁴/9 + z⁵/11 + z⁶/13 + z⁷/15 + z⁸/17
|
|
265
351
|
(f64.add
|
|
266
352
|
(f64.mul (f64.convert_i32_s (local.get $k)) (f64.const ${Math.LN2}))
|
|
267
353
|
(f64.mul (f64.const 2.0) (f64.mul (local.get $s) (f64.add (f64.const 1.0)
|
|
@@ -269,16 +355,99 @@ export default (ctx) => {
|
|
|
269
355
|
(f64.mul (local.get $z) (f64.add (f64.const 0.2)
|
|
270
356
|
(f64.mul (local.get $z) (f64.add (f64.const 0.14285714285714285)
|
|
271
357
|
(f64.mul (local.get $z) (f64.add (f64.const 0.1111111111111111)
|
|
272
|
-
(f64.mul (local.get $z) (f64.const 0.09090909090909091)
|
|
358
|
+
(f64.mul (local.get $z) (f64.add (f64.const 0.09090909090909091)
|
|
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)))))))))))))))))))))`
|
|
273
362
|
|
|
274
363
|
ctx.core.stdlib['math.log2'] = `(func $math.log2 (param $x f64) (result f64)
|
|
275
364
|
(f64.div (call $math.log (local.get $x)) (f64.const ${Math.LN2})))`
|
|
276
365
|
|
|
366
|
+
// log10 via fdlibm's two-term decomposition: log10(x) = k*log10(2) + log10(m).
|
|
367
|
+
// A plain log(x)/ln(10) double-rounds (rounding of log itself, then of the
|
|
368
|
+
// divide), so exact powers of ten drift — log10(1000) lands on 2.9999…996.
|
|
369
|
+
// Reducing x = m·2^k, splitting log10(2) and 1/ln(10) into hi/lo halves, and
|
|
370
|
+
// keeping the bulk term (k·log10_2hi, hi·ivln10hi) carry-free recovers the
|
|
371
|
+
// last ulps, so log10(10/100/1000/…) round-trips to exact integers.
|
|
277
372
|
ctx.core.stdlib['math.log10'] = `(func $math.log10 (param $x f64) (result f64)
|
|
278
|
-
(
|
|
279
|
-
|
|
373
|
+
(local $bits i64) (local $k i32) (local $m f64) (local $f f64)
|
|
374
|
+
(local $hfsq f64) (local $s f64) (local $z f64) (local $w f64)
|
|
375
|
+
(local $t1 f64) (local $t2 f64) (local $R f64)
|
|
376
|
+
(local $hi f64) (local $lo f64) (local $dk f64)
|
|
377
|
+
(local $valhi f64) (local $vallo f64) (local $y f64)
|
|
378
|
+
;; Special values: NaN→NaN, x≤0 → (-inf for 0, NaN for negative), +inf→+inf.
|
|
379
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
380
|
+
(if (f64.le (local.get $x) (f64.const 0.0))
|
|
381
|
+
(then
|
|
382
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (f64.const -inf))))
|
|
383
|
+
(return (f64.const nan))))
|
|
384
|
+
(if (f64.eq (local.get $x) (f64.const inf)) (then (return (local.get $x))))
|
|
385
|
+
;; Normalize subnormals so the bit-extracted exponent is meaningful.
|
|
386
|
+
(local.set $k (i32.const 0))
|
|
387
|
+
(if (f64.lt (local.get $x) (f64.const 0x1p-1022))
|
|
388
|
+
(then
|
|
389
|
+
(local.set $x (f64.mul (local.get $x) (f64.const 0x1p54)))
|
|
390
|
+
(local.set $k (i32.const -54))))
|
|
391
|
+
;; frexp: k += exponent, m = mantissa forced into [1,2).
|
|
392
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $x)))
|
|
393
|
+
(local.set $k (i32.add (local.get $k) (i32.sub
|
|
394
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 52)) (i64.const 0x7ff)))
|
|
395
|
+
(i32.const 1023))))
|
|
396
|
+
(local.set $m (f64.reinterpret_i64
|
|
397
|
+
(i64.or (i64.and (local.get $bits) (i64.const 0x000fffffffffffff))
|
|
398
|
+
(i64.const 0x3ff0000000000000))))
|
|
399
|
+
;; Center on sqrt(2): m ∈ [sqrt2/2, sqrt2) keeps the kernel argument small.
|
|
400
|
+
(if (f64.ge (local.get $m) (f64.const 1.4142135623730951))
|
|
401
|
+
(then
|
|
402
|
+
(local.set $m (f64.mul (local.get $m) (f64.const 0.5)))
|
|
403
|
+
(local.set $k (i32.add (local.get $k) (i32.const 1)))))
|
|
404
|
+
;; log(m) kernel: f - hfsq + s*(hfsq+R), s = f/(2+f), polynomial in s².
|
|
405
|
+
(local.set $f (f64.sub (local.get $m) (f64.const 1.0)))
|
|
406
|
+
(local.set $hfsq (f64.mul (f64.const 0.5) (f64.mul (local.get $f) (local.get $f))))
|
|
407
|
+
(local.set $s (f64.div (local.get $f) (f64.add (f64.const 2.0) (local.get $f))))
|
|
408
|
+
(local.set $z (f64.mul (local.get $s) (local.get $s)))
|
|
409
|
+
(local.set $w (f64.mul (local.get $z) (local.get $z)))
|
|
410
|
+
(local.set $t1 (f64.mul (local.get $w) (f64.add (f64.const 0.3999999999940942)
|
|
411
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.22222198432149792)
|
|
412
|
+
(f64.mul (local.get $w) (f64.const 0.15313837699209373)))))))
|
|
413
|
+
(local.set $t2 (f64.mul (local.get $z) (f64.add (f64.const 0.6666666666666735)
|
|
414
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.2857142874366239)
|
|
415
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.1818357216161805)
|
|
416
|
+
(f64.mul (local.get $w) (f64.const 0.14798198605116586)))))))))
|
|
417
|
+
(local.set $R (f64.add (local.get $t2) (local.get $t1)))
|
|
418
|
+
;; hi = high 32 bits of (f - hfsq); lo = the carry-free remainder.
|
|
419
|
+
(local.set $hi (f64.sub (local.get $f) (local.get $hfsq)))
|
|
420
|
+
(local.set $hi (f64.reinterpret_i64
|
|
421
|
+
(i64.and (i64.reinterpret_f64 (local.get $hi)) (i64.const 0xffffffff00000000))))
|
|
422
|
+
(local.set $lo (f64.add
|
|
423
|
+
(f64.sub (f64.sub (local.get $f) (local.get $hi)) (local.get $hfsq))
|
|
424
|
+
(f64.mul (local.get $s) (f64.add (local.get $hfsq) (local.get $R)))))
|
|
425
|
+
;; Combine with k·log10(2): bulk in val_hi, corrections in val_lo.
|
|
426
|
+
(local.set $valhi (f64.mul (local.get $hi) (f64.const 0.4342944818781689)))
|
|
427
|
+
(local.set $dk (f64.convert_i32_s (local.get $k)))
|
|
428
|
+
(local.set $y (f64.mul (local.get $dk) (f64.const 0.30102999566361177)))
|
|
429
|
+
(local.set $vallo (f64.add (f64.add
|
|
430
|
+
(f64.mul (local.get $dk) (f64.const 3.694239077158931e-13))
|
|
431
|
+
(f64.mul (f64.add (local.get $lo) (local.get $hi)) (f64.const 2.5082946711645275e-11)))
|
|
432
|
+
(f64.mul (local.get $lo) (f64.const 0.4342944818781689))))
|
|
433
|
+
(local.set $w (f64.add (local.get $y) (local.get $valhi)))
|
|
434
|
+
(local.set $vallo (f64.add (local.get $vallo)
|
|
435
|
+
(f64.add (f64.sub (local.get $y) (local.get $w)) (local.get $valhi))))
|
|
436
|
+
(f64.add (local.get $vallo) (local.get $w)))`
|
|
437
|
+
|
|
438
|
+
// log1p(x) via Kahan's compensated trick: with u = 1+x, log(u) loses bits when x is
|
|
439
|
+
// small (because u rounds to ~1), but the ratio x/(u-1) is exactly the missing factor.
|
|
440
|
+
// For u==1 (x below ulp), result is just x; preserves -0 from x=-0 path.
|
|
280
441
|
ctx.core.stdlib['math.log1p'] = `(func $math.log1p (param $x f64) (result f64)
|
|
281
|
-
(
|
|
442
|
+
(local $u f64)
|
|
443
|
+
;; log1p(+Inf) = +Inf — the ratio trick below would compute Inf/Inf = NaN.
|
|
444
|
+
(if (f64.eq (local.get $x) (f64.const inf)) (then (return (f64.const inf))))
|
|
445
|
+
(local.set $u (f64.add (f64.const 1.0) (local.get $x)))
|
|
446
|
+
(if (f64.eq (local.get $u) (f64.const 1.0))
|
|
447
|
+
(then (return (local.get $x))))
|
|
448
|
+
(f64.div
|
|
449
|
+
(f64.mul (call $math.log (local.get $u)) (local.get $x))
|
|
450
|
+
(f64.sub (local.get $u) (f64.const 1.0))))`
|
|
282
451
|
|
|
283
452
|
ctx.core.stdlib['math.pow'] = `(func $math.pow (param $x f64) (param $y f64) (result f64)
|
|
284
453
|
(local $result f64) (local $n i32) (local $neg_base i32) (local $abs_x f64)
|
|
@@ -293,7 +462,7 @@ export default (ctx) => {
|
|
|
293
462
|
(then
|
|
294
463
|
(local.set $abs_x (f64.abs (local.get $x)))
|
|
295
464
|
(if (f64.eq (local.get $abs_x) (f64.const 1.0))
|
|
296
|
-
(then (return (f64.
|
|
465
|
+
(then (return (f64.const nan))))
|
|
297
466
|
(if (i32.eq (f64.gt (local.get $abs_x) (f64.const 1.0))
|
|
298
467
|
(f64.gt (local.get $y) (f64.const 0.0)))
|
|
299
468
|
(then (return (f64.const inf)))
|
|
@@ -330,6 +499,20 @@ export default (ctx) => {
|
|
|
330
499
|
(if (local.get $neg_base)
|
|
331
500
|
(then (local.set $result (f64.neg (local.get $result)))))
|
|
332
501
|
(return (local.get $result))))
|
|
502
|
+
;; x is ±Infinity with |y| >= 2^31 (the i32 fast path above handles smaller y):
|
|
503
|
+
;; magnitude is Inf for y>0, 0 for y<0; sign is negative only when x is -Inf
|
|
504
|
+
;; and y is an odd integer. Odd-ness is tested in f64 (y, y/2 both integral)
|
|
505
|
+
;; to avoid an i32.trunc trap on |y| beyond i32 range.
|
|
506
|
+
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf))
|
|
507
|
+
(then
|
|
508
|
+
(local.set $result
|
|
509
|
+
(select (f64.const inf) (f64.const 0.0) (f64.gt (local.get $y) (f64.const 0.0))))
|
|
510
|
+
(if (i32.and (f64.lt (local.get $x) (f64.const 0.0))
|
|
511
|
+
(i32.and (f64.eq (f64.nearest (local.get $y)) (local.get $y))
|
|
512
|
+
(f64.ne (f64.nearest (f64.mul (local.get $y) (f64.const 0.5)))
|
|
513
|
+
(f64.mul (local.get $y) (f64.const 0.5)))))
|
|
514
|
+
(then (local.set $result (f64.neg (local.get $result)))))
|
|
515
|
+
(return (local.get $result))))
|
|
333
516
|
;; x == 0 with non-integer y -> y<0 ? Infinity : 0 (sign-of-zero only matters for integer y, handled above)
|
|
334
517
|
(if (f64.eq (local.get $x) (f64.const 0.0))
|
|
335
518
|
(then
|
|
@@ -338,41 +521,85 @@ export default (ctx) => {
|
|
|
338
521
|
(else (return (f64.const 0.0))))))
|
|
339
522
|
;; x < 0, non-integer finite y -> NaN
|
|
340
523
|
(if (f64.lt (local.get $x) (f64.const 0.0))
|
|
341
|
-
(then (return (f64.
|
|
524
|
+
(then (return (f64.const nan))))
|
|
342
525
|
(call $math.exp (f64.mul (local.get $y) (call $math.log (local.get $x)))))`
|
|
343
526
|
|
|
527
|
+
// fdlibm atan: 4-region argument reduction onto |r| ≤ tan(π/16), then an
|
|
528
|
+
// 11-term odd polynomial split into even/odd parts. Accurate to <1 ulp —
|
|
529
|
+
// the old Taylor series was ~2e-6 off near |x|=0.5. Drives asin/acos/atan2.
|
|
344
530
|
ctx.core.stdlib['math.atan'] = `(func $math.atan (param $x f64) (result f64)
|
|
345
|
-
(local $
|
|
531
|
+
(local $abs_x f64) (local $id i32) (local $r f64) (local $z f64) (local $w f64)
|
|
532
|
+
(local $s1 f64) (local $s2 f64) (local $ahi f64) (local $alo f64) (local $res f64)
|
|
533
|
+
;; NaN passes through unchanged.
|
|
534
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
346
535
|
(local.set $abs_x (f64.abs (local.get $x)))
|
|
347
|
-
|
|
536
|
+
;; |x| >= 2^66: atan saturates to ±π/2.
|
|
537
|
+
(if (f64.ge (local.get $abs_x) (f64.const 7.378697629483821e19))
|
|
538
|
+
(then (return (f64.copysign (f64.const 1.5707963267948966) (local.get $x)))))
|
|
539
|
+
(if (f64.lt (local.get $abs_x) (f64.const 0.4375))
|
|
348
540
|
(then
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
(
|
|
541
|
+
;; |x| < 2^-27: atan(x) ≈ x (also preserves sign of zero).
|
|
542
|
+
(if (f64.lt (local.get $abs_x) (f64.const 7.450580596923828e-9))
|
|
543
|
+
(then (return (local.get $x))))
|
|
544
|
+
(local.set $id (i32.const -1))
|
|
545
|
+
(local.set $r (local.get $x)))
|
|
352
546
|
(else
|
|
353
|
-
(
|
|
547
|
+
(local.set $r (local.get $abs_x))
|
|
548
|
+
(if (f64.lt (local.get $abs_x) (f64.const 1.1875))
|
|
354
549
|
(then
|
|
355
|
-
(
|
|
356
|
-
|
|
550
|
+
(if (f64.lt (local.get $abs_x) (f64.const 0.6875))
|
|
551
|
+
(then ;; id=0: r = (2x-1)/(2+x)
|
|
552
|
+
(local.set $id (i32.const 0))
|
|
553
|
+
(local.set $r (f64.div (f64.sub (f64.mul (f64.const 2.0) (local.get $r)) (f64.const 1.0))
|
|
554
|
+
(f64.add (f64.const 2.0) (local.get $r)))))
|
|
555
|
+
(else ;; id=1: r = (x-1)/(x+1)
|
|
556
|
+
(local.set $id (i32.const 1))
|
|
557
|
+
(local.set $r (f64.div (f64.sub (local.get $r) (f64.const 1.0))
|
|
558
|
+
(f64.add (local.get $r) (f64.const 1.0)))))))
|
|
357
559
|
(else
|
|
358
|
-
(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
(f64.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
560
|
+
(if (f64.lt (local.get $abs_x) (f64.const 2.4375))
|
|
561
|
+
(then ;; id=2: r = (x-1.5)/(1+1.5x)
|
|
562
|
+
(local.set $id (i32.const 2))
|
|
563
|
+
(local.set $r (f64.div (f64.sub (local.get $r) (f64.const 1.5))
|
|
564
|
+
(f64.add (f64.const 1.0) (f64.mul (f64.const 1.5) (local.get $r))))))
|
|
565
|
+
(else ;; id=3: r = -1/x
|
|
566
|
+
(local.set $id (i32.const 3))
|
|
567
|
+
(local.set $r (f64.div (f64.const -1.0) (local.get $r)))))))))
|
|
568
|
+
(local.set $z (f64.mul (local.get $r) (local.get $r)))
|
|
569
|
+
(local.set $w (f64.mul (local.get $z) (local.get $z)))
|
|
570
|
+
(local.set $s1 (f64.mul (local.get $z)
|
|
571
|
+
(f64.add (f64.const 0.3333333333333293)
|
|
572
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.14285714272503466)
|
|
573
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.09090887133436507)
|
|
574
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.06661073137387531)
|
|
575
|
+
(f64.mul (local.get $w) (f64.add (f64.const 0.049768779946159324)
|
|
576
|
+
(f64.mul (local.get $w) (f64.const 0.016285820115365782)))))))))))))
|
|
577
|
+
(local.set $s2 (f64.mul (local.get $w)
|
|
578
|
+
(f64.add (f64.const -0.19999999999876483)
|
|
579
|
+
(f64.mul (local.get $w) (f64.add (f64.const -0.11111110405462356)
|
|
580
|
+
(f64.mul (local.get $w) (f64.add (f64.const -0.0769187620504483)
|
|
581
|
+
(f64.mul (local.get $w) (f64.add (f64.const -0.058335701337905735)
|
|
582
|
+
(f64.mul (local.get $w) (f64.const -0.036531572744216916)))))))))))
|
|
583
|
+
;; |x| < 0.4375: result = r - r*(s1+s2), sign carried by r itself.
|
|
584
|
+
(if (i32.lt_s (local.get $id) (i32.const 0))
|
|
585
|
+
(then (return (f64.sub (local.get $r) (f64.mul (local.get $r) (f64.add (local.get $s1) (local.get $s2)))))))
|
|
586
|
+
;; Reconstruct: z = atanhi[id] - ((r*(s1+s2) - atanlo[id]) - r), sign of x.
|
|
587
|
+
(if (i32.eq (local.get $id) (i32.const 0))
|
|
588
|
+
(then (local.set $ahi (f64.const 0.4636476090008061)) (local.set $alo (f64.const 2.2698777452961687e-17)))
|
|
589
|
+
(else (if (i32.eq (local.get $id) (i32.const 1))
|
|
590
|
+
(then (local.set $ahi (f64.const 0.7853981633974483)) (local.set $alo (f64.const 3.061616997868383e-17)))
|
|
591
|
+
(else (if (i32.eq (local.get $id) (i32.const 2))
|
|
592
|
+
(then (local.set $ahi (f64.const 0.982793723247329)) (local.set $alo (f64.const 1.3903311031230998e-17)))
|
|
593
|
+
(else (local.set $ahi (f64.const 1.5707963267948966)) (local.set $alo (f64.const 6.123233995736766e-17))))))))
|
|
594
|
+
(local.set $res (f64.sub (local.get $ahi)
|
|
595
|
+
(f64.sub (f64.sub (f64.mul (local.get $r) (f64.add (local.get $s1) (local.get $s2))) (local.get $alo))
|
|
596
|
+
(local.get $r))))
|
|
597
|
+
(f64.copysign (local.get $res) (local.get $x)))`
|
|
372
598
|
|
|
373
599
|
ctx.core.stdlib['math.asin'] = `(func $math.asin (param $x f64) (result f64)
|
|
600
|
+
;; Domain is [-1, 1]; outside it (including ±Infinity), Math.asin returns NaN.
|
|
374
601
|
(if (result f64) (f64.gt (f64.abs (local.get $x)) (f64.const 1.0))
|
|
375
|
-
(then (f64.const
|
|
602
|
+
(then (f64.const nan))
|
|
376
603
|
(else (call $math.atan (f64.div (local.get $x)
|
|
377
604
|
(f64.sqrt (f64.sub (f64.const 1.0) (f64.mul (local.get $x) (local.get $x)))))))))`
|
|
378
605
|
|
|
@@ -380,9 +607,18 @@ export default (ctx) => {
|
|
|
380
607
|
(f64.sub (f64.const ${Math.PI / 2}) (call $math.asin (local.get $x))))`
|
|
381
608
|
|
|
382
609
|
ctx.core.stdlib['math.atan2'] = `(func $math.atan2 (param $y f64) (param $x f64) (result f64)
|
|
610
|
+
;; If either argument is NaN, the result is NaN (ECMA-262 21.3.2.5).
|
|
611
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
612
|
+
(if (f64.ne (local.get $y) (local.get $y)) (then (return (local.get $y))))
|
|
383
613
|
(if (result f64) (f64.eq (local.get $x) (f64.const 0.0)) (then
|
|
384
|
-
|
|
385
|
-
|
|
614
|
+
;; y is ±0 too: result is ±0 when x is +0, ±π when x is -0; sign taken from y.
|
|
615
|
+
(if (result f64) (f64.eq (local.get $y) (f64.const 0.0))
|
|
616
|
+
(then (f64.copysign
|
|
617
|
+
(select (f64.const ${Math.PI}) (f64.const 0.0)
|
|
618
|
+
(f64.lt (f64.copysign (f64.const 1.0) (local.get $x)) (f64.const 0.0)))
|
|
619
|
+
(local.get $y)))
|
|
620
|
+
(else
|
|
621
|
+
(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})))))))
|
|
386
622
|
(else (if (result f64) (f64.ge (local.get $x) (f64.const 0.0))
|
|
387
623
|
(then (call $math.atan (f64.div (local.get $y) (local.get $x))))
|
|
388
624
|
(else (if (result f64) (f64.ge (local.get $y) (f64.const 0.0))
|
|
@@ -391,6 +627,8 @@ export default (ctx) => {
|
|
|
391
627
|
|
|
392
628
|
ctx.core.stdlib['math.sinh'] = `(func $math.sinh (param $x f64) (result f64)
|
|
393
629
|
(local $ex f64)
|
|
630
|
+
;; Preserve sign of zero: sinh(±0) = ±0 (the f64.lt sign test below is false for -0).
|
|
631
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
394
632
|
(local.set $ex (call $math.exp (f64.abs (local.get $x))))
|
|
395
633
|
(local.set $ex (f64.mul (f64.const 0.5) (f64.sub (local.get $ex) (f64.div (f64.const 1.0) (local.get $ex)))))
|
|
396
634
|
(if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.neg (local.get $ex))) (else (local.get $ex))))`
|
|
@@ -401,6 +639,8 @@ export default (ctx) => {
|
|
|
401
639
|
|
|
402
640
|
ctx.core.stdlib['math.tanh'] = `(func $math.tanh (param $x f64) (result f64)
|
|
403
641
|
(local $e2x f64)
|
|
642
|
+
;; Preserve sign of zero: tanh(±0) = ±0 (the f64.lt sign test below is false for -0).
|
|
643
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
404
644
|
(if (result f64) (f64.gt (f64.abs (local.get $x)) (f64.const 22.0))
|
|
405
645
|
(then (if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.const -1.0)) (else (f64.const 1.0))))
|
|
406
646
|
(else (local.set $e2x (call $math.exp (f64.mul (f64.const 2.0) (f64.abs (local.get $x)))))
|
|
@@ -408,29 +648,51 @@ export default (ctx) => {
|
|
|
408
648
|
(if (result f64) (f64.lt (local.get $x) (f64.const 0.0)) (then (f64.neg (local.get $e2x))) (else (local.get $e2x))))))`
|
|
409
649
|
|
|
410
650
|
ctx.core.stdlib['math.asinh'] = `(func $math.asinh (param $x f64) (result f64)
|
|
651
|
+
;; ±Infinity and NaN pass through unchanged. (log(±Inf + sqrt(Inf²+1)) → NaN otherwise.)
|
|
652
|
+
(if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (local.get $x))))
|
|
653
|
+
;; Preserve sign of zero: asinh(±0) = ±0.
|
|
654
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
411
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))))))`
|
|
412
656
|
|
|
413
657
|
ctx.core.stdlib['math.acosh'] = `(func $math.acosh (param $x f64) (result f64)
|
|
414
|
-
(if (
|
|
658
|
+
(if (f64.eq (local.get $x) (f64.const inf)) (then (return (f64.const inf))))
|
|
659
|
+
;; acosh is defined only for x >= 1; everything below (incl. -Inf) is NaN.
|
|
660
|
+
(if (result f64) (f64.lt (local.get $x) (f64.const 1.0)) (then (f64.const nan)) (else
|
|
415
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))))))))`
|
|
416
662
|
|
|
417
663
|
ctx.core.stdlib['math.atanh'] = `(func $math.atanh (param $x f64) (result f64)
|
|
664
|
+
;; Preserve sign of zero: atanh(±0) = ±0.
|
|
665
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
666
|
+
;; ±Infinity → NaN. Without this the (1+x)/(1-x) ratio is Inf/Inf, whose
|
|
667
|
+
;; sign-nondeterministic arithmetic NaN would escape non-canonical on x86.
|
|
668
|
+
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const nan))))
|
|
418
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))))))`
|
|
419
670
|
|
|
420
671
|
ctx.core.stdlib['math.cbrt'] = `(func $math.cbrt (param $x f64) (result f64)
|
|
421
672
|
(local $y f64)
|
|
673
|
+
;; ±Infinity and NaN pass through; preserve sign of zero.
|
|
674
|
+
(if (i32.eqz (call $math.isFinite (local.get $x))) (then (return (local.get $x))))
|
|
675
|
+
(if (f64.eq (local.get $x) (f64.const 0.0)) (then (return (local.get $x))))
|
|
422
676
|
(if (result f64) (f64.lt (local.get $x) (f64.const 0.0))
|
|
423
677
|
(then (f64.neg (call $math.cbrt (f64.neg (local.get $x)))))
|
|
424
|
-
(else
|
|
425
|
-
|
|
426
|
-
(
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
678
|
+
(else
|
|
679
|
+
;; Initial guess via pow, then Newton-Raphson: y = (2y + x/y²)/3
|
|
680
|
+
(local.set $y (call $math.pow (local.get $x) (f64.const 0.3333333333333333)))
|
|
681
|
+
(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
|
+
(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))))`
|
|
684
|
+
|
|
685
|
+
// Small finite-test helper (NaN→0, ±Inf→0, finite→1). Used by transcendental
|
|
686
|
+
// functions that need to short-circuit on infinite inputs.
|
|
687
|
+
ctx.core.stdlib['math.isFinite'] = `(func $math.isFinite (param $x f64) (result i32)
|
|
688
|
+
(i32.and
|
|
689
|
+
(f64.eq (local.get $x) (local.get $x))
|
|
690
|
+
(f64.lt (f64.abs (local.get $x)) (f64.const inf))))`
|
|
432
691
|
|
|
433
692
|
ctx.core.stdlib['math.hypot'] = `(func $math.hypot (param $x f64) (param $y f64) (result f64)
|
|
693
|
+
;; Any ±Infinity argument ⇒ +Infinity, even when the other is NaN (ECMA-262 21.3.2.18).
|
|
694
|
+
(if (f64.eq (f64.abs (local.get $x)) (f64.const inf)) (then (return (f64.const inf))))
|
|
695
|
+
(if (f64.eq (f64.abs (local.get $y)) (f64.const inf)) (then (return (f64.const inf))))
|
|
434
696
|
(f64.sqrt (f64.add (f64.mul (local.get $x) (local.get $x)) (f64.mul (local.get $y) (local.get $y)))))`
|
|
435
697
|
|
|
436
698
|
ctx.core.stdlib['math.random'] = `(func $math.random (result f64)
|
|
@@ -442,6 +704,167 @@ export default (ctx) => {
|
|
|
442
704
|
(global.set $math.rng_state (local.get $s))
|
|
443
705
|
(f64.div (f64.convert_i32_u (i32.and (local.get $s) (i32.const 0x7FFFFFFF))) (f64.const 2147483647.0)))`
|
|
444
706
|
|
|
707
|
+
ctx.core.stdlib['math.sumPrecise'] = `(func $math.sumPrecise (param $arr i64) (result f64)
|
|
708
|
+
;; Exact summation via a 2304-bit fixed-point accumulator (36 i64 words,
|
|
709
|
+
;; little-endian two's complement) holding sum*2^1074. Every finite f64 is an
|
|
710
|
+
;; integer multiple of 2^-1074, so the running sum carries zero rounding
|
|
711
|
+
;; error; a single ties-to-even rounding at the end yields the result.
|
|
712
|
+
(local $base i32) (local $n i32) (local $i i32) (local $acc i32) (local $addr i32) (local $j i32)
|
|
713
|
+
(local $b i64) (local $exp i32) (local $sig i64) (local $shift i32) (local $wi i32) (local $bo i32)
|
|
714
|
+
(local $lo i64) (local $hi i64) (local $loW i64) (local $hiW i64) (local $ext i64) (local $neg i32)
|
|
715
|
+
(local $carry i32) (local $old i64) (local $s i64) (local $s2 i64) (local $addend i64)
|
|
716
|
+
(local $sawNaN i32) (local $posInf i32) (local $negInf i32) (local $allNegZero i32)
|
|
717
|
+
(local $L i32) (local $word i64) (local $resultNeg i32)
|
|
718
|
+
(local $rwi i32) (local $rbo i32) (local $top i64) (local $roundBit i64) (local $sticky i32) (local $k i32)
|
|
719
|
+
(local $pow f64) (local $res f64)
|
|
720
|
+
;; allocate + zero 36 i64 words
|
|
721
|
+
(local.set $acc (call $__alloc (i32.const 288)))
|
|
722
|
+
(local.set $j (i32.const 0))
|
|
723
|
+
(block $zdone (loop $zero
|
|
724
|
+
(br_if $zdone (i32.ge_u (local.get $j) (i32.const 288)))
|
|
725
|
+
(i64.store (i32.add (local.get $acc) (local.get $j)) (i64.const 0))
|
|
726
|
+
(local.set $j (i32.add (local.get $j) (i32.const 8)))
|
|
727
|
+
(br $zero)))
|
|
728
|
+
(local.set $allNegZero (i32.const 1))
|
|
729
|
+
(local.set $base (call $__ptr_offset (local.get $arr)))
|
|
730
|
+
(local.set $n (call $__len (local.get $arr)))
|
|
731
|
+
;; accumulate every element
|
|
732
|
+
(local.set $i (i32.const 0))
|
|
733
|
+
(block $idone (loop $iter
|
|
734
|
+
(br_if $idone (i32.ge_u (local.get $i) (local.get $n)))
|
|
735
|
+
(block $next
|
|
736
|
+
(local.set $b (i64.load (i32.add (local.get $base) (i32.shl (local.get $i) (i32.const 3)))))
|
|
737
|
+
(local.set $exp (i32.wrap_i64 (i64.and (i64.shr_u (local.get $b) (i64.const 52)) (i64.const 0x7ff))))
|
|
738
|
+
(local.set $sig (i64.and (local.get $b) (i64.const 0xfffffffffffff)))
|
|
739
|
+
(local.set $neg (i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const 63))))
|
|
740
|
+
;; NaN / +-Infinity
|
|
741
|
+
(if (i32.eq (local.get $exp) (i32.const 0x7ff))
|
|
742
|
+
(then
|
|
743
|
+
(if (i64.ne (local.get $sig) (i64.const 0))
|
|
744
|
+
(then (local.set $sawNaN (i32.const 1)))
|
|
745
|
+
(else (if (local.get $neg)
|
|
746
|
+
(then (local.set $negInf (i32.const 1)))
|
|
747
|
+
(else (local.set $posInf (i32.const 1))))))
|
|
748
|
+
(br $next)))
|
|
749
|
+
;; -0 tracking: any element not bit-identical to -0 clears allNegZero
|
|
750
|
+
(if (i64.ne (local.get $b) (i64.const 0x8000000000000000))
|
|
751
|
+
(then (local.set $allNegZero (i32.const 0))))
|
|
752
|
+
;; +-0 contributes nothing
|
|
753
|
+
(if (i32.and (i32.eqz (local.get $exp)) (i64.eqz (local.get $sig)))
|
|
754
|
+
(then (br $next)))
|
|
755
|
+
;; significand + bit shift: normal adds the implicit bit, shift=exp-1; subnormal shift=0
|
|
756
|
+
(if (i32.eqz (local.get $exp))
|
|
757
|
+
(then (local.set $shift (i32.const 0)))
|
|
758
|
+
(else
|
|
759
|
+
(local.set $sig (i64.or (local.get $sig) (i64.const 0x10000000000000)))
|
|
760
|
+
(local.set $shift (i32.sub (local.get $exp) (i32.const 1)))))
|
|
761
|
+
(local.set $wi (i32.shr_u (local.get $shift) (i32.const 6)))
|
|
762
|
+
(local.set $bo (i32.and (local.get $shift) (i32.const 63)))
|
|
763
|
+
(local.set $lo (i64.shl (local.get $sig) (i64.extend_i32_u (local.get $bo))))
|
|
764
|
+
(local.set $hi (if (result i64) (i32.eqz (local.get $bo))
|
|
765
|
+
(then (i64.const 0))
|
|
766
|
+
(else (i64.shr_u (local.get $sig) (i64.extend_i32_u (i32.sub (i32.const 64) (local.get $bo)))))))
|
|
767
|
+
;; subtraction of a negative element = adding (~M)+1 with sign-extension ext=-1
|
|
768
|
+
(local.set $ext (i64.extend_i32_s (i32.sub (i32.const 0) (local.get $neg))))
|
|
769
|
+
(local.set $loW (i64.xor (local.get $lo) (local.get $ext)))
|
|
770
|
+
(local.set $hiW (i64.xor (local.get $hi) (local.get $ext)))
|
|
771
|
+
(local.set $carry (local.get $neg))
|
|
772
|
+
(local.set $j (local.get $wi))
|
|
773
|
+
(block $adone (loop $add
|
|
774
|
+
(br_if $adone (i32.ge_u (local.get $j) (i32.const 36)))
|
|
775
|
+
(local.set $addend (select (local.get $loW)
|
|
776
|
+
(select (local.get $hiW) (local.get $ext)
|
|
777
|
+
(i32.eq (local.get $j) (i32.add (local.get $wi) (i32.const 1))))
|
|
778
|
+
(i32.eq (local.get $j) (local.get $wi))))
|
|
779
|
+
(local.set $addr (i32.add (local.get $acc) (i32.shl (local.get $j) (i32.const 3))))
|
|
780
|
+
(local.set $old (i64.load (local.get $addr)))
|
|
781
|
+
(local.set $s (i64.add (local.get $old) (local.get $addend)))
|
|
782
|
+
(local.set $s2 (i64.add (local.get $s) (i64.extend_i32_u (local.get $carry))))
|
|
783
|
+
(local.set $carry (i32.or (i64.lt_u (local.get $s) (local.get $old)) (i64.lt_u (local.get $s2) (local.get $s))))
|
|
784
|
+
(i64.store (local.get $addr) (local.get $s2))
|
|
785
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
786
|
+
(br $add))))
|
|
787
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
788
|
+
(br $iter)))
|
|
789
|
+
;; special results
|
|
790
|
+
(if (local.get $sawNaN) (then (return (f64.const nan))))
|
|
791
|
+
(if (i32.and (local.get $posInf) (local.get $negInf)) (then (return (f64.const nan))))
|
|
792
|
+
(if (local.get $posInf) (then (return (f64.const inf))))
|
|
793
|
+
(if (local.get $negInf) (then (return (f64.neg (f64.const inf)))))
|
|
794
|
+
;; sign of accumulator = top bit of word 35; negate the magnitude if set
|
|
795
|
+
(local.set $resultNeg (i32.wrap_i64 (i64.shr_u (i64.load (i32.add (local.get $acc) (i32.const 280))) (i64.const 63))))
|
|
796
|
+
(if (local.get $resultNeg) (then
|
|
797
|
+
(local.set $j (i32.const 0))
|
|
798
|
+
(local.set $carry (i32.const 1))
|
|
799
|
+
(block $ndone (loop $negl
|
|
800
|
+
(br_if $ndone (i32.ge_u (local.get $j) (i32.const 36)))
|
|
801
|
+
(local.set $addr (i32.add (local.get $acc) (i32.shl (local.get $j) (i32.const 3))))
|
|
802
|
+
(local.set $old (i64.xor (i64.load (local.get $addr)) (i64.const -1)))
|
|
803
|
+
(local.set $s (i64.add (local.get $old) (i64.extend_i32_u (local.get $carry))))
|
|
804
|
+
(local.set $carry (i64.lt_u (local.get $s) (local.get $old)))
|
|
805
|
+
(i64.store (local.get $addr) (local.get $s))
|
|
806
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
807
|
+
(br $negl)))))
|
|
808
|
+
;; bit length L (scan words high -> low)
|
|
809
|
+
(local.set $L (i32.const 0))
|
|
810
|
+
(local.set $j (i32.const 35))
|
|
811
|
+
(block $ldone (loop $lscan
|
|
812
|
+
(local.set $word (i64.load (i32.add (local.get $acc) (i32.shl (local.get $j) (i32.const 3)))))
|
|
813
|
+
(if (i64.ne (local.get $word) (i64.const 0))
|
|
814
|
+
(then
|
|
815
|
+
(local.set $L (i32.sub (i32.add (i32.mul (local.get $j) (i32.const 64)) (i32.const 64))
|
|
816
|
+
(i32.wrap_i64 (i64.clz (local.get $word)))))
|
|
817
|
+
(br $ldone)))
|
|
818
|
+
(br_if $ldone (i32.eqz (local.get $j)))
|
|
819
|
+
(local.set $j (i32.sub (local.get $j) (i32.const 1)))
|
|
820
|
+
(br $lscan)))
|
|
821
|
+
;; sum is exactly zero: -0 for empty input or an all-(-0) list, else +0
|
|
822
|
+
(if (i32.eqz (local.get $L)) (then
|
|
823
|
+
(return (if (result f64) (i32.or (i32.eqz (local.get $n)) (local.get $allNegZero))
|
|
824
|
+
(then (f64.reinterpret_i64 (i64.const 0x8000000000000000)))
|
|
825
|
+
(else (f64.const 0))))))
|
|
826
|
+
;; magnitude fits in 53 bits: exact, scale by 2^-1074 (reinterpret of i64 1)
|
|
827
|
+
(if (i32.le_u (local.get $L) (i32.const 53)) (then
|
|
828
|
+
(local.set $res (f64.mul (f64.convert_i64_u (i64.load (local.get $acc))) (f64.reinterpret_i64 (i64.const 1))))
|
|
829
|
+
(return (select (f64.neg (local.get $res)) (local.get $res) (local.get $resultNeg)))))
|
|
830
|
+
;; round to nearest f64 (ties-to-even). top 53 bits start at bit L-53.
|
|
831
|
+
(local.set $wi (i32.shr_u (i32.sub (local.get $L) (i32.const 53)) (i32.const 6)))
|
|
832
|
+
(local.set $bo (i32.and (i32.sub (local.get $L) (i32.const 53)) (i32.const 63)))
|
|
833
|
+
(local.set $top (i64.shr_u (i64.load (i32.add (local.get $acc) (i32.shl (local.get $wi) (i32.const 3)))) (i64.extend_i32_u (local.get $bo))))
|
|
834
|
+
(if (i32.ne (local.get $bo) (i32.const 0)) (then
|
|
835
|
+
(local.set $top (i64.or (local.get $top)
|
|
836
|
+
(i64.shl (i64.load (i32.add (local.get $acc) (i32.shl (i32.add (local.get $wi) (i32.const 1)) (i32.const 3))))
|
|
837
|
+
(i64.extend_i32_u (i32.sub (i32.const 64) (local.get $bo))))))))
|
|
838
|
+
(local.set $top (i64.and (local.get $top) (i64.const 0x1fffffffffffff)))
|
|
839
|
+
;; round bit at L-54, sticky = OR of every lower bit
|
|
840
|
+
(local.set $rwi (i32.shr_u (i32.sub (local.get $L) (i32.const 54)) (i32.const 6)))
|
|
841
|
+
(local.set $rbo (i32.and (i32.sub (local.get $L) (i32.const 54)) (i32.const 63)))
|
|
842
|
+
(local.set $roundBit (i64.and (i64.shr_u (i64.load (i32.add (local.get $acc) (i32.shl (local.get $rwi) (i32.const 3)))) (i64.extend_i32_u (local.get $rbo))) (i64.const 1)))
|
|
843
|
+
(local.set $sticky (i32.const 0))
|
|
844
|
+
(local.set $j (i32.const 0))
|
|
845
|
+
(block $sdone (loop $sscan
|
|
846
|
+
(br_if $sdone (i32.ge_u (local.get $j) (local.get $rwi)))
|
|
847
|
+
(if (i64.ne (i64.load (i32.add (local.get $acc) (i32.shl (local.get $j) (i32.const 3)))) (i64.const 0))
|
|
848
|
+
(then (local.set $sticky (i32.const 1))))
|
|
849
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
850
|
+
(br $sscan)))
|
|
851
|
+
(if (i64.ne (i64.and (i64.load (i32.add (local.get $acc) (i32.shl (local.get $rwi) (i32.const 3))))
|
|
852
|
+
(i64.sub (i64.shl (i64.const 1) (i64.extend_i32_u (local.get $rbo))) (i64.const 1)))
|
|
853
|
+
(i64.const 0))
|
|
854
|
+
(then (local.set $sticky (i32.const 1))))
|
|
855
|
+
(if (i32.and (i64.eq (local.get $roundBit) (i64.const 1))
|
|
856
|
+
(i32.or (local.get $sticky) (i32.wrap_i64 (i64.and (local.get $top) (i64.const 1)))))
|
|
857
|
+
(then (local.set $top (i64.add (local.get $top) (i64.const 1)))))
|
|
858
|
+
;; result = top * 2^k where k is the exponent of top's low bit
|
|
859
|
+
(local.set $k (i32.sub (local.get $L) (i32.const 1127)))
|
|
860
|
+
(if (i32.ge_s (local.get $k) (i32.const 1024)) (then
|
|
861
|
+
(return (select (f64.neg (f64.const inf)) (f64.const inf) (local.get $resultNeg)))))
|
|
862
|
+
(local.set $pow (if (result f64) (i32.ge_s (local.get $k) (i32.const -1022))
|
|
863
|
+
(then (f64.reinterpret_i64 (i64.shl (i64.extend_i32_u (i32.add (local.get $k) (i32.const 1023))) (i64.const 52))))
|
|
864
|
+
(else (f64.reinterpret_i64 (i64.shl (i64.const 1) (i64.extend_i32_u (i32.add (local.get $k) (i32.const 1074))))))))
|
|
865
|
+
(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)))`
|
|
867
|
+
|
|
445
868
|
// Global for random state
|
|
446
869
|
ctx.scope.globals.set('math.rng_state', '(global $math.rng_state (mut i32) (i32.const 12345))')
|
|
447
870
|
}
|