jz 0.1.0 → 0.2.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 +221 -62
- package/cli.js +34 -8
- package/index.js +133 -14
- package/module/array.js +399 -131
- package/module/collection.js +457 -212
- package/module/console.js +170 -87
- package/module/core.js +247 -143
- package/module/date.js +691 -0
- package/module/function.js +84 -23
- package/module/index.js +2 -1
- package/module/json.js +485 -138
- package/module/math.js +81 -40
- package/module/number.js +189 -83
- package/module/object.js +228 -46
- package/module/regex.js +34 -30
- package/module/schema.js +17 -18
- package/module/string.js +483 -227
- package/module/symbol.js +6 -4
- package/module/timer.js +90 -32
- package/module/typedarray.js +176 -44
- package/package.json +5 -10
- package/src/analyze.js +639 -124
- package/src/autoload.js +183 -0
- package/src/compile.js +448 -1083
- package/src/ctx.js +42 -12
- package/src/emit.js +646 -147
- package/src/host.js +273 -68
- package/src/ir.js +81 -31
- package/src/jzify.js +220 -36
- package/src/narrow.js +956 -0
- package/src/optimize.js +628 -42
- package/src/plan.js +1233 -0
- package/src/prepare.js +438 -256
- package/src/vectorize.js +1016 -0
- package/wasi.js +23 -4
package/module/math.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* @module math
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { typed, asF64, asI32, temp, arrayLoop } from '../src/ir.js'
|
|
17
|
+
import { emit } from '../src/emit.js'
|
|
17
18
|
import { inc } from '../src/ctx.js'
|
|
18
19
|
import { repOf } from '../src/analyze.js'
|
|
19
20
|
|
|
@@ -59,13 +60,21 @@ export default (ctx) => {
|
|
|
59
60
|
ctx.core.emit['math.ceil'] = a => fInt('f64.ceil', a)
|
|
60
61
|
ctx.core.emit['math.trunc'] = a => fInt('f64.trunc', a)
|
|
61
62
|
ctx.core.emit['math.min'] = (a, b, ...rest) => {
|
|
63
|
+
if (a === undefined) return typed(['f64.const', Infinity], 'f64')
|
|
62
64
|
// Spread: Math.min(...arr) — iterate array to find min
|
|
63
65
|
if (!b && Array.isArray(a) && a[0] === '...') return emitArrayReduce('f64.min', a[1], Infinity)
|
|
64
|
-
return
|
|
66
|
+
if (b === undefined) return typed(['f64.min', asF64(emit(a)), ['f64.const', Infinity]], 'f64')
|
|
67
|
+
let r = f2('f64.min', a, b)
|
|
68
|
+
for (const x of rest) r = typed(['f64.min', r, asF64(emit(x))], 'f64')
|
|
69
|
+
return r
|
|
65
70
|
}
|
|
66
71
|
ctx.core.emit['math.max'] = (a, b, ...rest) => {
|
|
72
|
+
if (a === undefined) return typed(['f64.const', -Infinity], 'f64')
|
|
67
73
|
if (!b && Array.isArray(a) && a[0] === '...') return emitArrayReduce('f64.max', a[1], -Infinity)
|
|
68
|
-
return
|
|
74
|
+
if (b === undefined) return typed(['f64.max', asF64(emit(a)), ['f64.const', -Infinity]], 'f64')
|
|
75
|
+
let r = f2('f64.max', a, b)
|
|
76
|
+
for (const x of rest) r = typed(['f64.max', r, asF64(emit(x))], 'f64')
|
|
77
|
+
return r
|
|
69
78
|
}
|
|
70
79
|
ctx.core.emit['math.round'] = a => fInt('f64.nearest', a)
|
|
71
80
|
ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64', asF64(emit(a))]], 'f64')
|
|
@@ -106,7 +115,16 @@ export default (ctx) => {
|
|
|
106
115
|
ctx.core.emit['math.pow'] = (a, b) => callDeps(['math.exp', 'math.log', 'math.pow'], 'math.pow', a, b)
|
|
107
116
|
ctx.core.emit['**'] = ctx.core.emit['math.pow']
|
|
108
117
|
ctx.core.emit['math.cbrt'] = a => callDeps(['math.exp', 'math.log', 'math.pow', 'math.cbrt'], 'math.cbrt', a)
|
|
109
|
-
ctx.core.emit['math.hypot'] = (a, b) =>
|
|
118
|
+
ctx.core.emit['math.hypot'] = (a, b, ...rest) => {
|
|
119
|
+
if (a === undefined) return typed(['f64.const', 0], 'f64')
|
|
120
|
+
if (b === undefined) return f('f64.abs', a)
|
|
121
|
+
let r = call('math.hypot', a, b)
|
|
122
|
+
for (const x of rest) {
|
|
123
|
+
inc('math.hypot')
|
|
124
|
+
r = typed(['call', '$math.hypot', r, asF64(emit(x))], 'f64')
|
|
125
|
+
}
|
|
126
|
+
return r
|
|
127
|
+
}
|
|
110
128
|
|
|
111
129
|
// Integer/bit operations: return i32 directly. Consumers `asF64`-rebox at
|
|
112
130
|
// store/return boundaries; consumers staying in i32 (bit chains, i32 locals)
|
|
@@ -169,6 +187,7 @@ export default (ctx) => {
|
|
|
169
187
|
|
|
170
188
|
ctx.core.stdlib['math.exp'] = `(func $math.exp (param $x f64) (result f64)
|
|
171
189
|
(local $k i32) (local $t f64) (local $t2 f64) (local $result f64) (local $pow2 f64)
|
|
190
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
172
191
|
(if (result f64) (f64.gt (local.get $x) (f64.const 709.0)) (then (f64.const 1.7976931348623157e+308)) (else
|
|
173
192
|
(if (result f64) (f64.lt (local.get $x) (f64.const -745.0)) (then (f64.const 0.0)) (else
|
|
174
193
|
(local.set $k (i32.trunc_f64_s (f64.div (local.get $x) (f64.const ${Math.LN2}))))
|
|
@@ -246,42 +265,64 @@ export default (ctx) => {
|
|
|
246
265
|
|
|
247
266
|
ctx.core.stdlib['math.pow'] = `(func $math.pow (param $x f64) (param $y f64) (result f64)
|
|
248
267
|
(local $result f64) (local $n i32) (local $neg_base i32) (local $abs_x f64)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
268
|
+
;; y == 0 -> 1 (covers pow(NaN,0), pow(±0,0), pow(±Inf,0))
|
|
269
|
+
(if (f64.eq (local.get $y) (f64.const 0.0)) (then (return (f64.const 1.0))))
|
|
270
|
+
;; y is NaN -> NaN
|
|
271
|
+
(if (f64.ne (local.get $y) (local.get $y)) (then (return (local.get $y))))
|
|
272
|
+
;; x is NaN -> NaN
|
|
273
|
+
(if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
|
|
274
|
+
;; y is ±Infinity
|
|
275
|
+
(if (f64.eq (f64.abs (local.get $y)) (f64.const inf))
|
|
276
|
+
(then
|
|
277
|
+
(local.set $abs_x (f64.abs (local.get $x)))
|
|
278
|
+
(if (f64.eq (local.get $abs_x) (f64.const 1.0))
|
|
279
|
+
(then (return (f64.div (f64.const 0.0) (f64.const 0.0)))))
|
|
280
|
+
(if (i32.eq (f64.gt (local.get $abs_x) (f64.const 1.0))
|
|
281
|
+
(f64.gt (local.get $y) (f64.const 0.0)))
|
|
282
|
+
(then (return (f64.const inf)))
|
|
283
|
+
(else (return (f64.const 0.0))))))
|
|
284
|
+
;; x == 1 -> 1 (after y=±Inf check, so 1**Inf already returned NaN)
|
|
285
|
+
(if (f64.eq (local.get $x) (f64.const 1.0)) (then (return (f64.const 1.0))))
|
|
286
|
+
;; y == 1 -> x (preserves -0 for (-0)**1)
|
|
287
|
+
(if (f64.eq (local.get $y) (f64.const 1.0)) (then (return (local.get $x))))
|
|
288
|
+
;; integer fast path: y integer in i32 range. Binary exponentiation is
|
|
289
|
+
;; O(log |n|) so the bound only matters for i32.trunc_f64_s safety.
|
|
290
|
+
;; Also covers ±Infinity x: abs_x stays Inf through the loop, 1/Inf=0,
|
|
291
|
+
;; with neg_base (x<0 && odd y) producing -0 — required for (-Inf)**-odd.
|
|
292
|
+
;; Runs before the x==0 fallback so (-0)**oddInt correctly returns ∓0/∓Inf.
|
|
293
|
+
(if (i32.and
|
|
294
|
+
(f64.eq (f64.nearest (local.get $y)) (local.get $y))
|
|
295
|
+
(f64.lt (f64.abs (local.get $y)) (f64.const 2147483648.0)))
|
|
296
|
+
(then
|
|
297
|
+
(local.set $abs_x (f64.abs (local.get $x)))
|
|
298
|
+
;; copysign(1, x) gives -1 for any x with sign bit set (incl. -0); f64.lt picks that up.
|
|
299
|
+
(local.set $neg_base (i32.and (f64.lt (f64.copysign (f64.const 1.0) (local.get $x)) (f64.const 0.0))
|
|
300
|
+
(i32.and (i32.trunc_f64_s (local.get $y)) (i32.const 1))))
|
|
301
|
+
(local.set $n (i32.trunc_f64_s (f64.abs (local.get $y))))
|
|
302
|
+
(local.set $result (f64.const 1.0))
|
|
303
|
+
(block $done
|
|
304
|
+
(loop $loop
|
|
305
|
+
(br_if $done (i32.le_s (local.get $n) (i32.const 0)))
|
|
306
|
+
(if (i32.and (local.get $n) (i32.const 1))
|
|
307
|
+
(then (local.set $result (f64.mul (local.get $result) (local.get $abs_x)))))
|
|
308
|
+
(local.set $abs_x (f64.mul (local.get $abs_x) (local.get $abs_x)))
|
|
309
|
+
(local.set $n (i32.shr_s (local.get $n) (i32.const 1)))
|
|
310
|
+
(br $loop)))
|
|
311
|
+
(if (f64.lt (local.get $y) (f64.const 0.0))
|
|
312
|
+
(then (local.set $result (f64.div (f64.const 1.0) (local.get $result)))))
|
|
313
|
+
(if (local.get $neg_base)
|
|
314
|
+
(then (local.set $result (f64.neg (local.get $result)))))
|
|
315
|
+
(return (local.get $result))))
|
|
316
|
+
;; x == 0 with non-integer y -> y<0 ? Infinity : 0 (sign-of-zero only matters for integer y, handled above)
|
|
317
|
+
(if (f64.eq (local.get $x) (f64.const 0.0))
|
|
318
|
+
(then
|
|
319
|
+
(if (f64.lt (local.get $y) (f64.const 0.0))
|
|
320
|
+
(then (return (f64.const inf)))
|
|
321
|
+
(else (return (f64.const 0.0))))))
|
|
322
|
+
;; x < 0, non-integer finite y -> NaN
|
|
323
|
+
(if (f64.lt (local.get $x) (f64.const 0.0))
|
|
324
|
+
(then (return (f64.div (f64.const 0.0) (f64.const 0.0)))))
|
|
325
|
+
(call $math.exp (f64.mul (local.get $y) (call $math.log (local.get $x)))))`
|
|
285
326
|
|
|
286
327
|
ctx.core.stdlib['math.atan'] = `(func $math.atan (param $x f64) (result f64)
|
|
287
328
|
(local $x2 f64) (local $abs_x f64) (local $reduced f64)
|
package/module/number.js
CHANGED
|
@@ -9,28 +9,48 @@
|
|
|
9
9
|
* @module number
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { typed, asF64, asI32, asI64, toNumF64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
|
|
13
|
+
import { emit, isReassigned } from '../src/emit.js'
|
|
14
|
+
import { valTypeOf, VAL } from '../src/analyze.js'
|
|
13
15
|
import { inc, PTR } from '../src/ctx.js'
|
|
14
16
|
|
|
15
17
|
export default (ctx) => {
|
|
16
18
|
Object.assign(ctx.core.stdlibDeps, {
|
|
17
19
|
__mkstr: ['__alloc'],
|
|
18
|
-
__ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str'],
|
|
20
|
+
__ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str', '__toExp'],
|
|
19
21
|
__toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
|
|
20
|
-
__to_num: ['__char_at', '__str_byteLen', '__pow10'],
|
|
22
|
+
__to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
|
|
23
|
+
__to_bigint: ['__char_at', '__str_byteLen'],
|
|
21
24
|
__parseInt: ['__char_at', '__str_byteLen'],
|
|
22
25
|
})
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
// __pow10(n: i32) → f64 — compute 10^n via
|
|
28
|
+
// __pow10(n: i32) → f64 — compute 10^n via binary decomposition.
|
|
29
|
+
// Naive iterative `r *= 10` accumulates O(n) ULPs of rounding drift —
|
|
30
|
+
// 1e308 came out 1 ULP low, breaking parseFloat round-trip at the f64 edge.
|
|
31
|
+
// Bit-decomposition multiplies at most 9 precomputed powers (10^1 .. 10^256),
|
|
32
|
+
// so accumulated error stays at O(log n) ULPs.
|
|
26
33
|
ctx.core.stdlib['__pow10'] = `(func $__pow10 (param $n i32) (result f64)
|
|
27
34
|
(local $r f64)
|
|
28
35
|
(local.set $r (f64.const 1))
|
|
29
|
-
(
|
|
30
|
-
(
|
|
31
|
-
|
|
32
|
-
(local.set $
|
|
33
|
-
|
|
36
|
+
(if (i32.and (local.get $n) (i32.const 1))
|
|
37
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 10)))))
|
|
38
|
+
(if (i32.and (local.get $n) (i32.const 2))
|
|
39
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 100)))))
|
|
40
|
+
(if (i32.and (local.get $n) (i32.const 4))
|
|
41
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 10000)))))
|
|
42
|
+
(if (i32.and (local.get $n) (i32.const 8))
|
|
43
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 1e8)))))
|
|
44
|
+
(if (i32.and (local.get $n) (i32.const 16))
|
|
45
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 1e16)))))
|
|
46
|
+
(if (i32.and (local.get $n) (i32.const 32))
|
|
47
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 1e32)))))
|
|
48
|
+
(if (i32.and (local.get $n) (i32.const 64))
|
|
49
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 1e64)))))
|
|
50
|
+
(if (i32.and (local.get $n) (i32.const 128))
|
|
51
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 1e128)))))
|
|
52
|
+
(if (i32.and (local.get $n) (i32.const 256))
|
|
53
|
+
(then (local.set $r (f64.mul (local.get $r) (f64.const 1e256)))))
|
|
34
54
|
(local.get $r))`
|
|
35
55
|
|
|
36
56
|
// __itoa(val: i32, buf: i32) → i32 (digit count). Writes decimal digits to buf.
|
|
@@ -83,6 +103,17 @@ export default (ctx) => {
|
|
|
83
103
|
(if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
|
|
84
104
|
(if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
|
|
85
105
|
(if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
|
|
106
|
+
;; ES spec: |x| >= 1e21 or 0 < |x| < 1e-6 → exponential notation (default mode only).
|
|
107
|
+
;; Cap prec at 9: mantissa fits i32, avoiding trunc_f64_u trap. Fewer digits than
|
|
108
|
+
;; ECMAScript shortest-repr ideal, but valid output for very large/small numbers.
|
|
109
|
+
(if (i32.eqz (local.get $mode))
|
|
110
|
+
(then
|
|
111
|
+
(if (f64.ge (f64.abs (local.get $val)) (f64.const 1e21))
|
|
112
|
+
(then (return (call $__toExp (local.get $val) (i32.const 9)))))
|
|
113
|
+
(if (i32.and
|
|
114
|
+
(f64.gt (f64.abs (local.get $val)) (f64.const 0))
|
|
115
|
+
(f64.lt (f64.abs (local.get $val)) (f64.const 1e-6)))
|
|
116
|
+
(then (return (call $__toExp (local.get $val) (i32.const 9)))))))
|
|
86
117
|
(local.set $buf (call $__alloc (i32.const 40)))
|
|
87
118
|
;; Sign
|
|
88
119
|
(if (f64.lt (local.get $val) (f64.const 0))
|
|
@@ -291,19 +322,17 @@ export default (ctx) => {
|
|
|
291
322
|
|
|
292
323
|
// Global isNaN/isFinite — coerce string→number first (unlike Number.isNaN/isFinite)
|
|
293
324
|
ctx.core.emit['isNaN'] = (x) => {
|
|
294
|
-
|
|
295
|
-
const v = asF64(emit(x))
|
|
325
|
+
const v = toNumF64(x, emit(x))
|
|
296
326
|
const t = temp('t')
|
|
297
327
|
return typed(['f64.ne',
|
|
298
|
-
['local.tee', `$${t}`,
|
|
328
|
+
['local.tee', `$${t}`, v],
|
|
299
329
|
['local.get', `$${t}`]], 'i32')
|
|
300
330
|
}
|
|
301
331
|
ctx.core.emit['isFinite'] = (x) => {
|
|
302
|
-
|
|
303
|
-
const v = asF64(emit(x))
|
|
332
|
+
const v = toNumF64(x, emit(x))
|
|
304
333
|
const t = temp('t')
|
|
305
334
|
return typed(['i32.and',
|
|
306
|
-
['f64.eq', ['local.tee', `$${t}`,
|
|
335
|
+
['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
307
336
|
['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]], 'i32')
|
|
308
337
|
}
|
|
309
338
|
|
|
@@ -318,15 +347,14 @@ export default (ctx) => {
|
|
|
318
347
|
}
|
|
319
348
|
|
|
320
349
|
// parseInt(str, radix) — parse string to integer
|
|
321
|
-
ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str
|
|
350
|
+
ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str i64) (param $radix i32) (result f64)
|
|
322
351
|
(local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
323
|
-
(local $result f64) (local $digit i32) (local $seen i32)
|
|
352
|
+
(local $result f64) (local $digit i32) (local $seen i32) (local $f f64)
|
|
353
|
+
(local.set $f (f64.reinterpret_i64 (local.get $str)))
|
|
324
354
|
;; If input is a number, just truncate
|
|
325
|
-
(if (f64.eq (local.get $
|
|
326
|
-
;; If NaN-boxed but not a string
|
|
327
|
-
(if (i32.
|
|
328
|
-
(i32.ne (call $__ptr_type (local.get $str)) (i32.const 4))
|
|
329
|
-
(i32.ne (call $__ptr_type (local.get $str)) (i32.const 5)))
|
|
355
|
+
(if (f64.eq (local.get $f) (local.get $f)) (then (return (f64.trunc (local.get $f)))))
|
|
356
|
+
;; If NaN-boxed but not a string → return NaN
|
|
357
|
+
(if (i32.ne (call $__ptr_type (local.get $str)) (i32.const 4))
|
|
330
358
|
(then (return (f64.const nan))))
|
|
331
359
|
(local.set $off (call $__ptr_offset (local.get $str)))
|
|
332
360
|
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
@@ -374,19 +402,25 @@ export default (ctx) => {
|
|
|
374
402
|
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
375
403
|
(if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
|
|
376
404
|
|
|
377
|
-
ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v
|
|
405
|
+
ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v i64) (result f64)
|
|
378
406
|
(local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
379
|
-
(local $seen i32) (local $exp i32) (local $expNeg i32)
|
|
380
|
-
(local $
|
|
381
|
-
(
|
|
382
|
-
(
|
|
383
|
-
(
|
|
407
|
+
(local $seen i32) (local $exp i32) (local $expNeg i32) (local $expDigits i32)
|
|
408
|
+
(local $dot i32) (local $sigDigits i32) (local $decExp i32) (local $dropped i32) (local $round i32)
|
|
409
|
+
(local $wsEnd i32)
|
|
410
|
+
(local $result f64) (local $f f64)
|
|
411
|
+
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
412
|
+
(if (f64.eq (local.get $f) (local.get $f)) (then (return (local.get $f))))
|
|
413
|
+
(if (i64.eq (local.get $v) (i64.const ${NULL_NAN})) (then (return (f64.const 0))))
|
|
414
|
+
(if (i64.eq (local.get $v) (i64.const ${UNDEF_NAN})) (then (return (f64.const nan))))
|
|
384
415
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
416
|
+
;; Non-string values go through ToString per JS spec, then re-check the
|
|
417
|
+
;; type in case ToString itself returned a non-string sentinel.
|
|
418
|
+
(if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
|
|
419
|
+
(then
|
|
420
|
+
(local.set $v (call $__to_str (local.get $v)))
|
|
421
|
+
(local.set $t (call $__ptr_type (local.get $v)))
|
|
422
|
+
(if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
|
|
423
|
+
(then (return (f64.const nan))))))
|
|
390
424
|
(local.set $len (call $__str_byteLen (local.get $v)))
|
|
391
425
|
;; Skip leading whitespace.
|
|
392
426
|
(block $ws (loop $wsl
|
|
@@ -394,6 +428,7 @@ export default (ctx) => {
|
|
|
394
428
|
(br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
|
|
395
429
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
396
430
|
(br $wsl)))
|
|
431
|
+
(local.set $wsEnd (local.get $i))
|
|
397
432
|
;; Sign.
|
|
398
433
|
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
399
434
|
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
|
|
@@ -422,45 +457,51 @@ export default (ctx) => {
|
|
|
422
457
|
(then (local.set $result (f64.add (f64.mul (local.get $result) (f64.const 16)) (f64.convert_i32_s (i32.sub (local.get $c) (i32.const 55)))))
|
|
423
458
|
(local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))))
|
|
424
459
|
(return (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))))
|
|
425
|
-
;;
|
|
426
|
-
|
|
427
|
-
|
|
460
|
+
;; Decimal significand. Keep 17 significant decimal digits, track the
|
|
461
|
+
;; base-10 exponent for skipped digits, and round once before pow10 scaling.
|
|
462
|
+
(block $numDone (loop $numLoop
|
|
463
|
+
(br_if $numDone (i32.ge_s (local.get $i) (local.get $len)))
|
|
428
464
|
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
429
|
-
(
|
|
465
|
+
(if (i32.and (i32.eq (local.get $c) (i32.const 46)) (i32.eqz (local.get $dot)))
|
|
466
|
+
(then
|
|
467
|
+
(local.set $dot (i32.const 1))
|
|
468
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
469
|
+
(br $numLoop)))
|
|
470
|
+
(br_if $numDone
|
|
430
471
|
(i32.or
|
|
431
472
|
(i32.lt_s (local.get $c) (i32.const 48))
|
|
432
473
|
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
433
|
-
(local.set $result
|
|
434
|
-
(f64.add
|
|
435
|
-
(f64.mul (local.get $result) (f64.const 10))
|
|
436
|
-
(f64.convert_i32_s (i32.sub (local.get $c) (i32.const 48)))))
|
|
437
474
|
(local.set $seen (i32.const 1))
|
|
438
|
-
(local.set $
|
|
439
|
-
(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
(local.
|
|
446
|
-
(
|
|
447
|
-
(br_if $fracDone (i32.ge_s (local.get $i) (local.get $len)))
|
|
448
|
-
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
449
|
-
(br_if $fracDone
|
|
450
|
-
(i32.or
|
|
451
|
-
(i32.lt_s (local.get $c) (i32.const 48))
|
|
452
|
-
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
475
|
+
(local.set $c (i32.sub (local.get $c) (i32.const 48)))
|
|
476
|
+
(if (i32.and (i32.eqz (local.get $sigDigits)) (i32.eqz (local.get $c)))
|
|
477
|
+
(then
|
|
478
|
+
(if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1)))))
|
|
479
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
480
|
+
(br $numLoop)))
|
|
481
|
+
(if (i32.lt_s (local.get $sigDigits)
|
|
482
|
+
(if (result i32) (local.get $dot) (then (i32.const 16)) (else (i32.const 17))))
|
|
483
|
+
(then
|
|
453
484
|
(local.set $result
|
|
454
485
|
(f64.add
|
|
455
|
-
(local.get $result)
|
|
456
|
-
(f64.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
(
|
|
461
|
-
|
|
462
|
-
(
|
|
463
|
-
|
|
486
|
+
(f64.mul (local.get $result) (f64.const 10))
|
|
487
|
+
(f64.convert_i32_s (local.get $c))))
|
|
488
|
+
(local.set $sigDigits (i32.add (local.get $sigDigits) (i32.const 1)))
|
|
489
|
+
(if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1))))))
|
|
490
|
+
(else
|
|
491
|
+
(if (i32.eqz (local.get $dropped))
|
|
492
|
+
(then (if (i32.ge_s (local.get $c) (i32.const 5)) (then (local.set $round (i32.const 1))))))
|
|
493
|
+
(local.set $dropped (i32.const 1))
|
|
494
|
+
(if (i32.eqz (local.get $dot)) (then (local.set $decExp (i32.add (local.get $decExp) (i32.const 1)))))))
|
|
495
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
496
|
+
(br $numLoop)))
|
|
497
|
+
;; No digits seen: empty/whitespace-only string coerces to 0; non-numeric
|
|
498
|
+
;; content (sign-only, "abc", etc.) coerces to NaN.
|
|
499
|
+
(if (i32.eqz (local.get $seen))
|
|
500
|
+
(then
|
|
501
|
+
(if (i32.ge_s (local.get $wsEnd) (local.get $len))
|
|
502
|
+
(then (return (f64.const 0))))
|
|
503
|
+
(return (f64.const nan))))
|
|
504
|
+
(if (local.get $round) (then (local.set $result (f64.add (local.get $result) (f64.const 1)))))
|
|
464
505
|
;; Scientific notation.
|
|
465
506
|
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
466
507
|
(i32.or
|
|
@@ -485,28 +526,88 @@ export default (ctx) => {
|
|
|
485
526
|
(i32.add
|
|
486
527
|
(i32.mul (local.get $exp) (i32.const 10))
|
|
487
528
|
(i32.sub (local.get $c) (i32.const 48))))
|
|
529
|
+
(local.set $expDigits (i32.add (local.get $expDigits) (i32.const 1)))
|
|
488
530
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
489
531
|
(br $expLoop)))
|
|
490
|
-
(if (local.get $
|
|
491
|
-
(then
|
|
492
|
-
|
|
532
|
+
(if (local.get $expDigits)
|
|
533
|
+
(then
|
|
534
|
+
(if (local.get $expNeg)
|
|
535
|
+
(then (local.set $decExp (i32.sub (local.get $decExp) (local.get $exp))))
|
|
536
|
+
(else (local.set $decExp (i32.add (local.get $decExp) (local.get $exp)))))))))
|
|
537
|
+
(if (i32.gt_s (local.get $decExp) (i32.const 0))
|
|
538
|
+
(then (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))
|
|
539
|
+
(if (i32.lt_s (local.get $decExp) (i32.const 0))
|
|
540
|
+
(then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))
|
|
493
541
|
(if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
|
|
494
542
|
|
|
543
|
+
ctx.core.stdlib['__to_bigint'] = `(func $__to_bigint (param $v i64) (result f64)
|
|
544
|
+
(local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
545
|
+
(local $radix i32) (local $digit i32) (local $seen i32) (local $result i64) (local $f f64)
|
|
546
|
+
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
547
|
+
(if (f64.eq (local.get $f) (local.get $f))
|
|
548
|
+
(then (return (f64.reinterpret_i64 (i64.trunc_sat_f64_s (local.get $f))))))
|
|
549
|
+
(local.set $t (call $__ptr_type (local.get $v)))
|
|
550
|
+
(if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
|
|
551
|
+
(then (return (f64.reinterpret_i64 (i64.const 0)))))
|
|
552
|
+
(local.set $len (call $__str_byteLen (local.get $v)))
|
|
553
|
+
(block $ws (loop $wsl
|
|
554
|
+
(br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
|
|
555
|
+
(br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
|
|
556
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
557
|
+
(br $wsl)))
|
|
558
|
+
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
559
|
+
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
|
|
560
|
+
(then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
561
|
+
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
562
|
+
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
|
|
563
|
+
(then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
564
|
+
(local.set $radix (i32.const 10))
|
|
565
|
+
(if (i32.and
|
|
566
|
+
(i32.lt_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
|
|
567
|
+
(i32.and (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 48))
|
|
568
|
+
(i32.or (i32.eq (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 120))
|
|
569
|
+
(i32.eq (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 88)))))
|
|
570
|
+
(then (local.set $radix (i32.const 16)) (local.set $i (i32.add (local.get $i) (i32.const 2)))))
|
|
571
|
+
(block $done (loop $lp
|
|
572
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $len)))
|
|
573
|
+
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
574
|
+
(local.set $digit (i32.const -1))
|
|
575
|
+
(if (i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
|
|
576
|
+
(then (local.set $digit (i32.sub (local.get $c) (i32.const 48)))))
|
|
577
|
+
(if (i32.and (i32.ge_s (local.get $c) (i32.const 97)) (i32.le_s (local.get $c) (i32.const 122)))
|
|
578
|
+
(then (local.set $digit (i32.sub (local.get $c) (i32.const 87)))))
|
|
579
|
+
(if (i32.and (i32.ge_s (local.get $c) (i32.const 65)) (i32.le_s (local.get $c) (i32.const 90)))
|
|
580
|
+
(then (local.set $digit (i32.sub (local.get $c) (i32.const 55)))))
|
|
581
|
+
(br_if $done (i32.or (i32.lt_s (local.get $digit) (i32.const 0)) (i32.ge_s (local.get $digit) (local.get $radix))))
|
|
582
|
+
(local.set $seen (i32.const 1))
|
|
583
|
+
(local.set $result
|
|
584
|
+
(i64.add
|
|
585
|
+
(i64.mul (local.get $result) (i64.extend_i32_s (local.get $radix)))
|
|
586
|
+
(i64.extend_i32_s (local.get $digit))))
|
|
587
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
588
|
+
(br $lp)))
|
|
589
|
+
(if (i32.eqz (local.get $seen)) (then (return (f64.reinterpret_i64 (i64.const 0)))))
|
|
590
|
+
(f64.reinterpret_i64
|
|
591
|
+
(if (result i64) (local.get $neg)
|
|
592
|
+
(then (i64.sub (i64.const 0) (local.get $result)))
|
|
593
|
+
(else (local.get $result)))))`
|
|
594
|
+
|
|
495
595
|
ctx.core.emit['Number.parseInt'] = (x, radix) => {
|
|
496
596
|
inc('__parseInt')
|
|
497
|
-
return typed(['call', '$__parseInt',
|
|
597
|
+
return typed(['call', '$__parseInt', asI64(emit(x)), radix ? asI32(emit(radix)) : ['i32.const', 0]], 'f64')
|
|
498
598
|
}
|
|
499
599
|
ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
|
|
500
600
|
ctx.core.emit['Number.parseFloat'] = (x) => {
|
|
501
601
|
inc('__to_num')
|
|
502
|
-
return typed(['call', '$__to_num',
|
|
602
|
+
return typed(['call', '$__to_num', asI64(emit(x))], 'f64')
|
|
503
603
|
}
|
|
504
604
|
ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
|
|
505
605
|
|
|
506
606
|
// Boolean(x) → truthiness (non-zero → 1, zero → 0)
|
|
507
607
|
ctx.core.emit['Boolean'] = (x) => {
|
|
608
|
+
if (x === undefined) return typed(['f64.const', 0], 'f64')
|
|
508
609
|
inc('__is_truthy')
|
|
509
|
-
const v =
|
|
610
|
+
const v = asI64(emit(x))
|
|
510
611
|
return typed(['if', ['result', 'f64'], ['call', '$__is_truthy', v], ['then', ['f64.const', 1]], ['else', ['f64.const', 0]]], 'f64')
|
|
511
612
|
}
|
|
512
613
|
|
|
@@ -557,18 +658,12 @@ export default (ctx) => {
|
|
|
557
658
|
['i32.const', 1]]]]], 'f64')
|
|
558
659
|
}
|
|
559
660
|
|
|
560
|
-
ctx.core.emit['String'] = (x) => {
|
|
561
|
-
inc('__ftoa')
|
|
562
|
-
if (Array.isArray(x) && x[0] === 'str') return emit(x)
|
|
563
|
-
return typed(['call', '$__ftoa', asF64(emit(x)), ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
564
|
-
}
|
|
565
|
-
|
|
566
661
|
// Number(x) — identity for numbers, i64→f64 conversion for BigInt
|
|
567
662
|
ctx.core.emit['Number'] = (x) => {
|
|
663
|
+
if (x === undefined) return typed(['f64.const', 0], 'f64')
|
|
568
664
|
if (valTypeOf(x) === VAL.BIGINT)
|
|
569
665
|
return typed(['f64.convert_i64_s', asI64(emit(x))], 'f64')
|
|
570
|
-
|
|
571
|
-
return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
|
|
666
|
+
return toNumF64(x, emit(x))
|
|
572
667
|
}
|
|
573
668
|
|
|
574
669
|
// BigInt(x) — f64→i64 conversion (reinterpret as BigInt-as-f64).
|
|
@@ -576,12 +671,23 @@ export default (ctx) => {
|
|
|
576
671
|
// (handles both decimal and hex string parse), then truncate.
|
|
577
672
|
ctx.core.emit['BigInt'] = (x) => {
|
|
578
673
|
const vt = valTypeOf(x)
|
|
579
|
-
if (vt === VAL.BIGINT)
|
|
674
|
+
if (vt === VAL.BIGINT) {
|
|
675
|
+
if (typeof x === 'bigint' || (typeof x === 'string' && !isReassigned(ctx.func.body, x))) return emit(x)
|
|
676
|
+
inc('__to_bigint', '__ptr_type')
|
|
677
|
+
const t = temp('bi')
|
|
678
|
+
return typed(['block', ['result', 'f64'],
|
|
679
|
+
['local.set', `$${t}`, asF64(emit(x))],
|
|
680
|
+
['if', ['result', 'f64'], ['f64.eq', ['local.get', `$${t}`], ['local.get', `$${t}`]],
|
|
681
|
+
['then', ['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', ['local.get', `$${t}`]]]],
|
|
682
|
+
['else', ['if', ['result', 'f64'],
|
|
683
|
+
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', PTR.STRING]],
|
|
684
|
+
['then', ['call', '$__to_bigint', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
685
|
+
['else', ['local.get', `$${t}`]]]]]], 'f64')
|
|
686
|
+
}
|
|
580
687
|
if (vt === VAL.NUMBER)
|
|
581
688
|
return typed(['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', asF64(emit(x))]], 'f64')
|
|
582
|
-
inc('
|
|
583
|
-
return typed(['f64
|
|
584
|
-
['i64.trunc_sat_f64_s', ['call', '$__to_num', asF64(emit(x))]]], 'f64')
|
|
689
|
+
inc('__to_bigint')
|
|
690
|
+
return typed(['call', '$__to_bigint', asI64(emit(x))], 'f64')
|
|
585
691
|
}
|
|
586
692
|
|
|
587
693
|
// BigInt.asIntN(bits, bigint) — truncate to signed N-bit
|