jz 0.1.1 → 0.2.1
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 +256 -90
- package/cli.js +34 -8
- package/index.js +100 -14
- package/module/array.js +396 -133
- package/module/collection.js +455 -212
- package/module/console.js +169 -88
- package/module/core.js +246 -144
- package/module/date.js +783 -0
- package/module/function.js +81 -21
- package/module/index.js +2 -1
- package/module/json.js +483 -138
- package/module/math.js +79 -39
- package/module/number.js +188 -78
- package/module/object.js +227 -47
- package/module/regex.js +33 -30
- package/module/schema.js +14 -17
- package/module/string.js +439 -235
- package/module/symbol.js +4 -3
- package/module/timer.js +89 -31
- package/module/typedarray.js +174 -44
- package/package.json +3 -10
- package/src/analyze.js +627 -133
- package/src/autoload.js +15 -7
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +658 -145
- package/src/host.js +244 -51
- package/src/ir.js +87 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1167 -4
- package/src/prepare.js +322 -75
- package/src/vectorize.js +1016 -0
- package/wasi.js +13 -0
- package/src/key.js +0 -73
- package/src/source.js +0 -76
package/module/math.js
CHANGED
|
@@ -60,13 +60,21 @@ export default (ctx) => {
|
|
|
60
60
|
ctx.core.emit['math.ceil'] = a => fInt('f64.ceil', a)
|
|
61
61
|
ctx.core.emit['math.trunc'] = a => fInt('f64.trunc', a)
|
|
62
62
|
ctx.core.emit['math.min'] = (a, b, ...rest) => {
|
|
63
|
+
if (a === undefined) return typed(['f64.const', Infinity], 'f64')
|
|
63
64
|
// Spread: Math.min(...arr) — iterate array to find min
|
|
64
65
|
if (!b && Array.isArray(a) && a[0] === '...') return emitArrayReduce('f64.min', a[1], Infinity)
|
|
65
|
-
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
|
|
66
70
|
}
|
|
67
71
|
ctx.core.emit['math.max'] = (a, b, ...rest) => {
|
|
72
|
+
if (a === undefined) return typed(['f64.const', -Infinity], 'f64')
|
|
68
73
|
if (!b && Array.isArray(a) && a[0] === '...') return emitArrayReduce('f64.max', a[1], -Infinity)
|
|
69
|
-
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
|
|
70
78
|
}
|
|
71
79
|
ctx.core.emit['math.round'] = a => fInt('f64.nearest', a)
|
|
72
80
|
ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64', asF64(emit(a))]], 'f64')
|
|
@@ -107,7 +115,16 @@ export default (ctx) => {
|
|
|
107
115
|
ctx.core.emit['math.pow'] = (a, b) => callDeps(['math.exp', 'math.log', 'math.pow'], 'math.pow', a, b)
|
|
108
116
|
ctx.core.emit['**'] = ctx.core.emit['math.pow']
|
|
109
117
|
ctx.core.emit['math.cbrt'] = a => callDeps(['math.exp', 'math.log', 'math.pow', 'math.cbrt'], 'math.cbrt', a)
|
|
110
|
-
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
|
+
}
|
|
111
128
|
|
|
112
129
|
// Integer/bit operations: return i32 directly. Consumers `asF64`-rebox at
|
|
113
130
|
// store/return boundaries; consumers staying in i32 (bit chains, i32 locals)
|
|
@@ -170,6 +187,7 @@ export default (ctx) => {
|
|
|
170
187
|
|
|
171
188
|
ctx.core.stdlib['math.exp'] = `(func $math.exp (param $x f64) (result f64)
|
|
172
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))))
|
|
173
191
|
(if (result f64) (f64.gt (local.get $x) (f64.const 709.0)) (then (f64.const 1.7976931348623157e+308)) (else
|
|
174
192
|
(if (result f64) (f64.lt (local.get $x) (f64.const -745.0)) (then (f64.const 0.0)) (else
|
|
175
193
|
(local.set $k (i32.trunc_f64_s (f64.div (local.get $x) (f64.const ${Math.LN2}))))
|
|
@@ -247,42 +265,64 @@ export default (ctx) => {
|
|
|
247
265
|
|
|
248
266
|
ctx.core.stdlib['math.pow'] = `(func $math.pow (param $x f64) (param $y f64) (result f64)
|
|
249
267
|
(local $result f64) (local $n i32) (local $neg_base i32) (local $abs_x f64)
|
|
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
|
-
|
|
285
|
-
|
|
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)))))`
|
|
286
326
|
|
|
287
327
|
ctx.core.stdlib['math.atan'] = `(func $math.atan (param $x f64) (result f64)
|
|
288
328
|
(local $x2 f64) (local $abs_x f64) (local $reduced f64)
|
package/module/number.js
CHANGED
|
@@ -9,30 +9,48 @@
|
|
|
9
9
|
* @module number
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
|
|
13
|
-
import { emit } from '../src/emit.js'
|
|
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
14
|
import { valTypeOf, VAL } from '../src/analyze.js'
|
|
15
15
|
import { inc, PTR } from '../src/ctx.js'
|
|
16
16
|
|
|
17
17
|
export default (ctx) => {
|
|
18
18
|
Object.assign(ctx.core.stdlibDeps, {
|
|
19
19
|
__mkstr: ['__alloc'],
|
|
20
|
-
__ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str'],
|
|
20
|
+
__ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str', '__toExp'],
|
|
21
21
|
__toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
|
|
22
|
-
__to_num: ['__char_at', '__str_byteLen', '__pow10'],
|
|
22
|
+
__to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
|
|
23
|
+
__to_bigint: ['__char_at', '__str_byteLen'],
|
|
23
24
|
__parseInt: ['__char_at', '__str_byteLen'],
|
|
24
25
|
})
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
// __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.
|
|
28
33
|
ctx.core.stdlib['__pow10'] = `(func $__pow10 (param $n i32) (result f64)
|
|
29
34
|
(local $r f64)
|
|
30
35
|
(local.set $r (f64.const 1))
|
|
31
|
-
(
|
|
32
|
-
(
|
|
33
|
-
|
|
34
|
-
(local.set $
|
|
35
|
-
|
|
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)))))
|
|
36
54
|
(local.get $r))`
|
|
37
55
|
|
|
38
56
|
// __itoa(val: i32, buf: i32) → i32 (digit count). Writes decimal digits to buf.
|
|
@@ -85,6 +103,17 @@ export default (ctx) => {
|
|
|
85
103
|
(if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
|
|
86
104
|
(if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
|
|
87
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)))))))
|
|
88
117
|
(local.set $buf (call $__alloc (i32.const 40)))
|
|
89
118
|
;; Sign
|
|
90
119
|
(if (f64.lt (local.get $val) (f64.const 0))
|
|
@@ -293,19 +322,17 @@ export default (ctx) => {
|
|
|
293
322
|
|
|
294
323
|
// Global isNaN/isFinite — coerce string→number first (unlike Number.isNaN/isFinite)
|
|
295
324
|
ctx.core.emit['isNaN'] = (x) => {
|
|
296
|
-
|
|
297
|
-
const v = asF64(emit(x))
|
|
325
|
+
const v = toNumF64(x, emit(x))
|
|
298
326
|
const t = temp('t')
|
|
299
327
|
return typed(['f64.ne',
|
|
300
|
-
['local.tee', `$${t}`,
|
|
328
|
+
['local.tee', `$${t}`, v],
|
|
301
329
|
['local.get', `$${t}`]], 'i32')
|
|
302
330
|
}
|
|
303
331
|
ctx.core.emit['isFinite'] = (x) => {
|
|
304
|
-
|
|
305
|
-
const v = asF64(emit(x))
|
|
332
|
+
const v = toNumF64(x, emit(x))
|
|
306
333
|
const t = temp('t')
|
|
307
334
|
return typed(['i32.and',
|
|
308
|
-
['f64.eq', ['local.tee', `$${t}`,
|
|
335
|
+
['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
309
336
|
['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]], 'i32')
|
|
310
337
|
}
|
|
311
338
|
|
|
@@ -320,15 +347,14 @@ export default (ctx) => {
|
|
|
320
347
|
}
|
|
321
348
|
|
|
322
349
|
// parseInt(str, radix) — parse string to integer
|
|
323
|
-
ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str
|
|
350
|
+
ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str i64) (param $radix i32) (result f64)
|
|
324
351
|
(local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
325
|
-
(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)))
|
|
326
354
|
;; If input is a number, just truncate
|
|
327
|
-
(if (f64.eq (local.get $
|
|
328
|
-
;; If NaN-boxed but not a string
|
|
329
|
-
(if (i32.
|
|
330
|
-
(i32.ne (call $__ptr_type (local.get $str)) (i32.const 4))
|
|
331
|
-
(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))
|
|
332
358
|
(then (return (f64.const nan))))
|
|
333
359
|
(local.set $off (call $__ptr_offset (local.get $str)))
|
|
334
360
|
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
@@ -376,19 +402,25 @@ export default (ctx) => {
|
|
|
376
402
|
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
377
403
|
(if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
|
|
378
404
|
|
|
379
|
-
ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v
|
|
405
|
+
ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v i64) (result f64)
|
|
380
406
|
(local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
381
|
-
(local $seen i32) (local $exp i32) (local $expNeg i32)
|
|
382
|
-
(local $
|
|
383
|
-
(
|
|
384
|
-
(
|
|
385
|
-
(
|
|
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))))
|
|
386
415
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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))))))
|
|
392
424
|
(local.set $len (call $__str_byteLen (local.get $v)))
|
|
393
425
|
;; Skip leading whitespace.
|
|
394
426
|
(block $ws (loop $wsl
|
|
@@ -396,6 +428,7 @@ export default (ctx) => {
|
|
|
396
428
|
(br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
|
|
397
429
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
398
430
|
(br $wsl)))
|
|
431
|
+
(local.set $wsEnd (local.get $i))
|
|
399
432
|
;; Sign.
|
|
400
433
|
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
401
434
|
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
|
|
@@ -424,45 +457,51 @@ export default (ctx) => {
|
|
|
424
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)))))
|
|
425
458
|
(local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))))
|
|
426
459
|
(return (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))))
|
|
427
|
-
;;
|
|
428
|
-
|
|
429
|
-
|
|
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)))
|
|
430
464
|
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
431
|
-
(
|
|
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
|
|
432
471
|
(i32.or
|
|
433
472
|
(i32.lt_s (local.get $c) (i32.const 48))
|
|
434
473
|
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
435
|
-
(local.set $result
|
|
436
|
-
(f64.add
|
|
437
|
-
(f64.mul (local.get $result) (f64.const 10))
|
|
438
|
-
(f64.convert_i32_s (i32.sub (local.get $c) (i32.const 48)))))
|
|
439
474
|
(local.set $seen (i32.const 1))
|
|
440
|
-
(local.set $
|
|
441
|
-
(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
(local.
|
|
448
|
-
(
|
|
449
|
-
(br_if $fracDone (i32.ge_s (local.get $i) (local.get $len)))
|
|
450
|
-
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
451
|
-
(br_if $fracDone
|
|
452
|
-
(i32.or
|
|
453
|
-
(i32.lt_s (local.get $c) (i32.const 48))
|
|
454
|
-
(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
|
|
455
484
|
(local.set $result
|
|
456
485
|
(f64.add
|
|
457
|
-
(local.get $result)
|
|
458
|
-
(f64.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
(
|
|
463
|
-
|
|
464
|
-
(
|
|
465
|
-
|
|
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)))))
|
|
466
505
|
;; Scientific notation.
|
|
467
506
|
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
468
507
|
(i32.or
|
|
@@ -487,28 +526,88 @@ export default (ctx) => {
|
|
|
487
526
|
(i32.add
|
|
488
527
|
(i32.mul (local.get $exp) (i32.const 10))
|
|
489
528
|
(i32.sub (local.get $c) (i32.const 48))))
|
|
529
|
+
(local.set $expDigits (i32.add (local.get $expDigits) (i32.const 1)))
|
|
490
530
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
491
531
|
(br $expLoop)))
|
|
492
|
-
(if (local.get $
|
|
493
|
-
(then
|
|
494
|
-
|
|
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)))))))
|
|
495
541
|
(if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
|
|
496
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
|
+
|
|
497
595
|
ctx.core.emit['Number.parseInt'] = (x, radix) => {
|
|
498
596
|
inc('__parseInt')
|
|
499
|
-
return typed(['call', '$__parseInt',
|
|
597
|
+
return typed(['call', '$__parseInt', asI64(emit(x)), radix ? asI32(emit(radix)) : ['i32.const', 0]], 'f64')
|
|
500
598
|
}
|
|
501
599
|
ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
|
|
502
600
|
ctx.core.emit['Number.parseFloat'] = (x) => {
|
|
503
601
|
inc('__to_num')
|
|
504
|
-
return typed(['call', '$__to_num',
|
|
602
|
+
return typed(['call', '$__to_num', asI64(emit(x))], 'f64')
|
|
505
603
|
}
|
|
506
604
|
ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
|
|
507
605
|
|
|
508
606
|
// Boolean(x) → truthiness (non-zero → 1, zero → 0)
|
|
509
607
|
ctx.core.emit['Boolean'] = (x) => {
|
|
608
|
+
if (x === undefined) return typed(['f64.const', 0], 'f64')
|
|
510
609
|
inc('__is_truthy')
|
|
511
|
-
const v =
|
|
610
|
+
const v = asI64(emit(x))
|
|
512
611
|
return typed(['if', ['result', 'f64'], ['call', '$__is_truthy', v], ['then', ['f64.const', 1]], ['else', ['f64.const', 0]]], 'f64')
|
|
513
612
|
}
|
|
514
613
|
|
|
@@ -561,10 +660,10 @@ export default (ctx) => {
|
|
|
561
660
|
|
|
562
661
|
// Number(x) — identity for numbers, i64→f64 conversion for BigInt
|
|
563
662
|
ctx.core.emit['Number'] = (x) => {
|
|
663
|
+
if (x === undefined) return typed(['f64.const', 0], 'f64')
|
|
564
664
|
if (valTypeOf(x) === VAL.BIGINT)
|
|
565
665
|
return typed(['f64.convert_i64_s', asI64(emit(x))], 'f64')
|
|
566
|
-
|
|
567
|
-
return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
|
|
666
|
+
return toNumF64(x, emit(x))
|
|
568
667
|
}
|
|
569
668
|
|
|
570
669
|
// BigInt(x) — f64→i64 conversion (reinterpret as BigInt-as-f64).
|
|
@@ -572,12 +671,23 @@ export default (ctx) => {
|
|
|
572
671
|
// (handles both decimal and hex string parse), then truncate.
|
|
573
672
|
ctx.core.emit['BigInt'] = (x) => {
|
|
574
673
|
const vt = valTypeOf(x)
|
|
575
|
-
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
|
+
}
|
|
576
687
|
if (vt === VAL.NUMBER)
|
|
577
688
|
return typed(['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', asF64(emit(x))]], 'f64')
|
|
578
|
-
inc('
|
|
579
|
-
return typed(['f64
|
|
580
|
-
['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')
|
|
581
691
|
}
|
|
582
692
|
|
|
583
693
|
// BigInt.asIntN(bits, bigint) — truncate to signed N-bit
|