jz 0.5.1 → 0.7.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 +315 -318
- package/bench/README.md +369 -0
- package/bench/bench.svg +102 -0
- package/cli.js +104 -30
- package/dist/interop.js +1 -0
- package/dist/jz.js +6024 -0
- package/index.d.ts +126 -0
- package/index.js +362 -84
- package/interop.js +159 -186
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +184 -0
- package/module/array.js +377 -176
- package/module/collection.js +639 -145
- package/module/console.js +55 -43
- package/module/core.js +264 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +551 -187
- package/module/number.js +327 -60
- package/module/object.js +474 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +117 -0
- package/module/string.js +621 -226
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +459 -73
- package/package.json +58 -14
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +29 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +824 -0
- package/src/compile/analyze.js +1600 -0
- package/src/compile/emit-assign.js +411 -0
- package/src/compile/emit.js +3512 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +778 -128
- package/src/{infer.js → compile/infer.js} +62 -98
- package/src/compile/loop-divmod.js +155 -0
- package/src/{narrow.js → compile/narrow.js} +409 -106
- package/src/compile/peel-stencil.js +261 -0
- package/src/compile/plan/advise.js +370 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +122 -0
- package/src/compile/plan/inline.js +682 -0
- package/src/compile/plan/literals.js +1199 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +649 -0
- package/src/compile/program-facts.js +423 -0
- package/src/ctx.js +220 -64
- package/src/ir.js +589 -172
- package/src/kind-traits.js +132 -0
- package/src/kind.js +524 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1432 -473
- package/src/optimize/vectorize.js +4660 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +649 -205
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +116 -0
- package/src/resolve.js +12 -3
- package/src/static.js +208 -0
- package/src/type.js +651 -0
- package/src/{assemble.js → wat/assemble.js} +275 -55
- package/src/wat/optimize.js +3938 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/module/number.js
CHANGED
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* @module number
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { typed, asF64, asI32, asI64, toI32, toNumF64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
12
|
+
import { typed, asF64, asI32, asI64, toI32, toNumF64, NULL_NAN, UNDEF_NAN, FALSE_NAN, TRUE_NAN, temp, tempI32, tempI64, ptrTypeEq, truthyIR } from '../src/ir.js'
|
|
13
|
+
import { ssoBitI64Hex } from '../layout.js'
|
|
14
|
+
import { emit, bool, deps, reg, hostImport } from '../src/bridge.js'
|
|
15
|
+
import { isReassigned } from '../src/ast.js'
|
|
16
|
+
import { valTypeOf } from '../src/kind.js'
|
|
17
|
+
import { VAL } from '../src/reps.js'
|
|
18
|
+
import { inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
16
19
|
|
|
17
20
|
// ─── Shared decimal-number parsing fragments ────────────────────────────────
|
|
18
21
|
// `__to_num` (Number coercion) and `__parseFloat` both scan a StrDecimalLiteral
|
|
@@ -34,9 +37,23 @@ import { inc, PTR } from '../src/ctx.js'
|
|
|
34
37
|
// Callers MUST declare `$sbase i32`, set it once after `$v` is final and a
|
|
35
38
|
// confirmed string, and only pass indices proven `< $len` (`__char_at` would
|
|
36
39
|
// otherwise return its OOB 0; the inline load has no such guard).
|
|
40
|
+
const SSO_BIT_I64 = ssoBitI64Hex()
|
|
37
41
|
const SBASE_INIT = '(local.set $sbase (i32.wrap_i64 (i64.and (local.get $v) (i64.const 4294967295))))'
|
|
42
|
+
|
|
43
|
+
/** In-place byte reversal of buf[i..j] (WAT fragment). __itoa/__radix_str emit the
|
|
44
|
+
* least-significant digit first, then flip the run. Caller pre-sets `j` to the last
|
|
45
|
+
* index and leaves `i` at 0; `tmp` is scratch. Labels $rev/$revl are block-local. */
|
|
46
|
+
const reverseBytesWat = (buf = '$buf', i = '$i', j = '$j', tmp = '$tmp') =>
|
|
47
|
+
`(block $rev (loop $revl
|
|
48
|
+
(br_if $rev (i32.ge_s (local.get ${i}) (local.get ${j})))
|
|
49
|
+
(local.set ${tmp} (i32.load8_u (i32.add (local.get ${buf}) (local.get ${i}))))
|
|
50
|
+
(i32.store8 (i32.add (local.get ${buf}) (local.get ${i})) (i32.load8_u (i32.add (local.get ${buf}) (local.get ${j}))))
|
|
51
|
+
(i32.store8 (i32.add (local.get ${buf}) (local.get ${j})) (local.get ${tmp}))
|
|
52
|
+
(local.set ${i} (i32.add (local.get ${i}) (i32.const 1)))
|
|
53
|
+
(local.set ${j} (i32.sub (local.get ${j}) (i32.const 1)))
|
|
54
|
+
(br $revl)))`
|
|
38
55
|
const chAt = idx => `(if (result i32)
|
|
39
|
-
(i64.eqz (i64.and (local.get $v) (i64.const
|
|
56
|
+
(i64.eqz (i64.and (local.get $v) (i64.const ${SSO_BIT_I64})))
|
|
40
57
|
(then (i32.load8_u (i32.add (local.get $sbase) ${idx})))
|
|
41
58
|
(else (call $__char_at (local.get $v) ${idx})))`
|
|
42
59
|
|
|
@@ -135,10 +152,13 @@ const POW10_SCALE = `
|
|
|
135
152
|
(then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))`
|
|
136
153
|
|
|
137
154
|
export default (ctx) => {
|
|
138
|
-
|
|
155
|
+
deps({
|
|
139
156
|
__mkstr: ['__alloc'],
|
|
140
157
|
__ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str', '__toExp'],
|
|
158
|
+
__i32_to_str: ['__itoa', '__mkstr'],
|
|
141
159
|
__toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
|
|
160
|
+
__radix_str: ['__mkstr'],
|
|
161
|
+
__num_radix: ['__ftoa', '__mkstr'],
|
|
142
162
|
__to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str', '__skipws', '__ptr_aux'],
|
|
143
163
|
__skipws: ['__char_at', '__strws'],
|
|
144
164
|
__to_bigint: ['__char_at', '__str_byteLen', '__num_to_bigint'],
|
|
@@ -193,22 +213,151 @@ export default (ctx) => {
|
|
|
193
213
|
(br $l)))
|
|
194
214
|
;; Reverse
|
|
195
215
|
(local.set $j (i32.sub (local.get $len) (i32.const 1)))
|
|
196
|
-
|
|
197
|
-
|
|
216
|
+
${reverseBytesWat()}
|
|
217
|
+
(local.get $len))`
|
|
218
|
+
|
|
219
|
+
// __i32_to_str(val: i32) → f64 (NaN-boxed string) — ToString for a value the
|
|
220
|
+
// compiler proved is a signed i32. The whole point is to bypass __ftoa's float
|
|
221
|
+
// machinery (shortest-repr search, __toExp, __pow10): a known integer renders with
|
|
222
|
+
// just __itoa over its magnitude plus a sign byte. Lets `"id " + (n|0)` and integer
|
|
223
|
+
// templates skip the ~2 KB float formatter the generic ToString hard-pulls.
|
|
224
|
+
ctx.core.stdlib['__i32_to_str'] = `(func $__i32_to_str (param $val i32) (result f64)
|
|
225
|
+
(local $buf i32) (local $len i32) (local $u i32)
|
|
226
|
+
(local.set $buf (call $__alloc (i32.const 12)))
|
|
227
|
+
(if (i32.lt_s (local.get $val) (i32.const 0))
|
|
228
|
+
(then
|
|
229
|
+
(i32.store8 (local.get $buf) (i32.const 45)) ;; '-'
|
|
230
|
+
;; magnitude as unsigned: negate via 0 - val (INT_MIN maps to itself, read u below)
|
|
231
|
+
(local.set $u (i32.sub (i32.const 0) (local.get $val)))
|
|
232
|
+
(local.set $len (call $__itoa (local.get $u) (i32.add (local.get $buf) (i32.const 1))))
|
|
233
|
+
(return (call $__mkstr (local.get $buf) (i32.add (local.get $len) (i32.const 1)))))
|
|
234
|
+
(else
|
|
235
|
+
(local.set $len (call $__itoa (local.get $val) (local.get $buf)))
|
|
236
|
+
(return (call $__mkstr (local.get $buf) (local.get $len)))))
|
|
237
|
+
(f64.const 0))`
|
|
238
|
+
|
|
239
|
+
// __radix_str(val: i64, radix: i32) → f64 (NaN-boxed string)
|
|
240
|
+
// Signed integer → radix string for BigInt.prototype.toString(radix). Digits go
|
|
241
|
+
// 0-9 then a-z (lowercase, per spec); magnitude is taken unsigned so i64.MIN
|
|
242
|
+
// (whose two's-complement negation is itself) formats correctly via div_u/rem_u.
|
|
243
|
+
ctx.core.stdlib['__radix_str'] = `(func $__radix_str (param $val i64) (param $radix i32) (result f64)
|
|
244
|
+
(local $buf i32) (local $pos i32) (local $neg i32) (local $mag i64) (local $r i64)
|
|
245
|
+
(local $dg i32) (local $i i32) (local $j i32) (local $tmp i32)
|
|
246
|
+
(local.set $buf (call $__alloc (i32.const 72)))
|
|
247
|
+
(local.set $r (i64.extend_i32_s (local.get $radix)))
|
|
248
|
+
(if (i64.eqz (local.get $val))
|
|
249
|
+
(then (i32.store8 (local.get $buf) (i32.const 48)) (return (call $__mkstr (local.get $buf) (i32.const 1)))))
|
|
250
|
+
(local.set $mag (local.get $val))
|
|
251
|
+
(if (i64.lt_s (local.get $val) (i64.const 0))
|
|
252
|
+
(then (local.set $neg (i32.const 1)) (local.set $mag (i64.sub (i64.const 0) (local.get $val)))))
|
|
253
|
+
(block $mb (loop $ml
|
|
254
|
+
(br_if $mb (i64.eqz (local.get $mag)))
|
|
255
|
+
(local.set $dg (i32.wrap_i64 (i64.rem_u (local.get $mag) (local.get $r))))
|
|
256
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
257
|
+
(select (i32.add (local.get $dg) (i32.const 48)) (i32.add (local.get $dg) (i32.const 87)) (i32.lt_s (local.get $dg) (i32.const 10))))
|
|
258
|
+
(local.set $mag (i64.div_u (local.get $mag) (local.get $r)))
|
|
259
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
260
|
+
(br $ml)))
|
|
261
|
+
(if (local.get $neg)
|
|
262
|
+
(then (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 45))
|
|
263
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))))
|
|
264
|
+
(local.set $j (i32.sub (local.get $pos) (i32.const 1)))
|
|
265
|
+
${reverseBytesWat()}
|
|
266
|
+
(call $__mkstr (local.get $buf) (local.get $pos)))`
|
|
267
|
+
|
|
268
|
+
// __num_radix(val: f64, radix: i32) → f64 (NaN-boxed string)
|
|
269
|
+
// Number.prototype.toString(radix) for radix != 10. Non-finite values defer to
|
|
270
|
+
// __ftoa ("NaN"/"Infinity"/"-Infinity"). Integer part uses exact i64 division
|
|
271
|
+
// (magnitude bounded by jz's i64 BigInt domain); the fraction multiplies out in
|
|
272
|
+
// f64, capped at 100 digits (radix fractions are implementation-defined precision).
|
|
273
|
+
//
|
|
274
|
+
// Per 21.1.3.6: radix must be an integer in [2, 36] — otherwise RangeError.
|
|
275
|
+
// The check sits at the canonical entry point so the const-radix fold path
|
|
276
|
+
// (which still calls here for any non-10 constant) and the dynamic-radix
|
|
277
|
+
// branch are validated uniformly. `(throw $__jz_err …)` is picked up by
|
|
278
|
+
// ensureThrowRuntime via stdlib scan, so callers do not need to flag throws.
|
|
279
|
+
ctx.core.stdlib['__num_radix'] = `(func $__num_radix (param $val f64) (param $radix i32) (result f64)
|
|
280
|
+
(local $buf i32) (local $pos i32) (local $neg i32) (local $iv i64) (local $r i64) (local $rf f64)
|
|
281
|
+
(local $int f64) (local $frac f64) (local $dg i32) (local $i i32) (local $j i32) (local $tmp i32) (local $fn i32) (local $rv f64)
|
|
282
|
+
(if (i32.or (i32.lt_s (local.get $radix) (i32.const 2)) (i32.gt_s (local.get $radix) (i32.const 36)))
|
|
283
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
284
|
+
(if (i32.or (f64.ne (local.get $val) (local.get $val)) (f64.eq (f64.abs (local.get $val)) (f64.const inf)))
|
|
285
|
+
(then (return (call $__ftoa (local.get $val) (i32.const 0) (i32.const 0)))))
|
|
286
|
+
(local.set $buf (call $__alloc (i32.const 180)))
|
|
287
|
+
(local.set $r (i64.extend_i32_s (local.get $radix)))
|
|
288
|
+
(local.set $rf (f64.convert_i32_s (local.get $radix)))
|
|
289
|
+
(if (f64.lt (local.get $val) (f64.const 0))
|
|
290
|
+
(then (local.set $neg (i32.const 1)) (local.set $val (f64.neg (local.get $val)))))
|
|
291
|
+
(local.set $int (f64.floor (local.get $val)))
|
|
292
|
+
(local.set $frac (f64.sub (local.get $val) (local.get $int)))
|
|
293
|
+
(local.set $iv (i64.trunc_sat_f64_u (local.get $int)))
|
|
294
|
+
(if (i64.eqz (local.get $iv))
|
|
295
|
+
(then (i32.store8 (local.get $buf) (i32.const 48)) (local.set $pos (i32.const 1)))
|
|
296
|
+
(else
|
|
297
|
+
(block $ib (loop $il
|
|
298
|
+
(br_if $ib (i64.eqz (local.get $iv)))
|
|
299
|
+
(local.set $dg (i32.wrap_i64 (i64.rem_u (local.get $iv) (local.get $r))))
|
|
300
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
301
|
+
(select (i32.add (local.get $dg) (i32.const 48)) (i32.add (local.get $dg) (i32.const 87)) (i32.lt_s (local.get $dg) (i32.const 10))))
|
|
302
|
+
(local.set $iv (i64.div_u (local.get $iv) (local.get $r)))
|
|
303
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
304
|
+
(br $il)))))
|
|
305
|
+
(if (local.get $neg)
|
|
306
|
+
(then (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 45))
|
|
307
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))))
|
|
308
|
+
(local.set $j (i32.sub (local.get $pos) (i32.const 1)))
|
|
309
|
+
(block $rb (loop $rl
|
|
310
|
+
(br_if $rb (i32.ge_s (local.get $i) (local.get $j)))
|
|
198
311
|
(local.set $tmp (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
|
|
199
|
-
(i32.store8 (i32.add (local.get $buf) (local.get $i))
|
|
200
|
-
(i32.load8_u (i32.add (local.get $buf) (local.get $j))))
|
|
312
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $i)) (i32.load8_u (i32.add (local.get $buf) (local.get $j))))
|
|
201
313
|
(i32.store8 (i32.add (local.get $buf) (local.get $j)) (local.get $tmp))
|
|
202
314
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
203
315
|
(local.set $j (i32.sub (local.get $j) (i32.const 1)))
|
|
204
316
|
(br $rl)))
|
|
205
|
-
(local.get $
|
|
317
|
+
(if (f64.gt (local.get $frac) (f64.const 0))
|
|
318
|
+
(then
|
|
319
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
|
|
320
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
321
|
+
(block $fb (loop $fl
|
|
322
|
+
(br_if $fb (f64.le (local.get $frac) (f64.const 0)))
|
|
323
|
+
(br_if $fb (i32.ge_s (local.get $fn) (i32.const 100)))
|
|
324
|
+
(local.set $frac (f64.mul (local.get $frac) (local.get $rf)))
|
|
325
|
+
(local.set $dg (i32.trunc_f64_s (f64.floor (local.get $frac))))
|
|
326
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
327
|
+
(select (i32.add (local.get $dg) (i32.const 48)) (i32.add (local.get $dg) (i32.const 87)) (i32.lt_s (local.get $dg) (i32.const 10))))
|
|
328
|
+
(local.set $frac (f64.sub (local.get $frac) (f64.floor (local.get $frac))))
|
|
329
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
330
|
+
(local.set $fn (i32.add (local.get $fn) (i32.const 1)))
|
|
331
|
+
(br $fl)))))
|
|
332
|
+
${!ctx.memory.shared ? `
|
|
333
|
+
;; When __mkstr packed an SSO result it allocated nothing, so the digit scratch
|
|
334
|
+
;; ($buf, at heap top) is dead — reclaim it so a heap-top accumulator stays on
|
|
335
|
+
;; top and \`s += n.toString(r)\` bump-extends instead of reallocating (O(n)).
|
|
336
|
+
(local.set $rv (call $__mkstr (local.get $buf) (local.get $pos)))
|
|
337
|
+
(if (i32.and (i32.wrap_i64 (i64.shr_u (i64.reinterpret_f64 (local.get $rv)) (i64.const ${LAYOUT.AUX_SHIFT}))) (i32.const ${LAYOUT.SSO_BIT}))
|
|
338
|
+
(then (global.set $__heap (local.get $buf))))
|
|
339
|
+
(local.get $rv)` : `(call $__mkstr (local.get $buf) (local.get $pos))`})`
|
|
206
340
|
|
|
207
341
|
// __mkstr(buf: i32, len: i32) → f64 — copy scratch buffer to heap string.
|
|
208
342
|
// Hot (~60M calls in watr self-host via __ftoa). bulk memory.copy is ~10× faster than
|
|
209
343
|
// a hand-rolled byte loop (wasm2c lowers it to memcpy under PGO+LTO).
|
|
210
344
|
ctx.core.stdlib['__mkstr'] = `(func $__mkstr (param $buf i32) (param $len i32) (result f64)
|
|
211
|
-
(local $off i32)
|
|
345
|
+
(local $off i32) (local $i i32) (local $packed i32) (local $b i32)
|
|
346
|
+
;; SSO fast path: ≤4 ASCII bytes pack into the pointer with no allocation, so a
|
|
347
|
+
;; number-format result doesn't displace a heap-top accumulator — keeping the
|
|
348
|
+
;; canonical \`s += n.toString(r)\` builder O(n) via the bump-extend path.
|
|
349
|
+
(if (i32.le_u (local.get $len) (i32.const 4))
|
|
350
|
+
(then
|
|
351
|
+
(block $heap
|
|
352
|
+
(loop $pk
|
|
353
|
+
(if (i32.lt_u (local.get $i) (local.get $len))
|
|
354
|
+
(then
|
|
355
|
+
(local.set $b (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
|
|
356
|
+
(br_if $heap (i32.ge_u (local.get $b) (i32.const 0x80)))
|
|
357
|
+
(local.set $packed (i32.or (local.get $packed) (i32.shl (local.get $b) (i32.shl (local.get $i) (i32.const 3)))))
|
|
358
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
359
|
+
(br $pk))))
|
|
360
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $len)) (local.get $packed))))))
|
|
212
361
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
|
|
213
362
|
(i32.store (local.get $off) (local.get $len))
|
|
214
363
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
@@ -251,7 +400,12 @@ export default (ctx) => {
|
|
|
251
400
|
;; Default mode: auto-select precision (up to 9 digits, must fit i32 when scaled)
|
|
252
401
|
(if (i32.eqz (local.get $mode))
|
|
253
402
|
(then (local.set $prec (i32.const 9))))
|
|
254
|
-
;; Round and scale to integer: scaled = nearest(val * 10^prec)
|
|
403
|
+
;; Round and scale to integer: scaled = nearest(val * 10^prec).
|
|
404
|
+
;; NOTE: toFixed/toPrecision round ties-to-even here (f64.nearest), which differs from
|
|
405
|
+
;; JS's round-half-away-from-zero on exact halves like (2.5).toFixed(0) → '2' vs '3'.
|
|
406
|
+
;; A naive floor(x+0.5) "fixes" those but breaks values like 1.45 (whose ×10 rounds up
|
|
407
|
+
;; to 14.5 in f64, giving '1.5' vs JS '1.4'); bit-exact toFixed needs the exact-decimal
|
|
408
|
+
;; algorithm. Documented as a known difference rather than trading one error for another.
|
|
255
409
|
(local.set $scale (call $__pow10 (local.get $prec)))
|
|
256
410
|
(local.set $scaled (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
|
|
257
411
|
;; If scaled doesn't fit i32, reduce precision until it does (min prec=0)
|
|
@@ -267,19 +421,40 @@ export default (ctx) => {
|
|
|
267
421
|
(then
|
|
268
422
|
(local.set $int (i32.trunc_f64_u (f64.div (local.get $scaled) (local.get $scale))))
|
|
269
423
|
(local.set $frac (i32.trunc_f64_u (f64.sub (local.get $scaled)
|
|
270
|
-
(f64.mul (f64.convert_i32_u (local.get $int)) (local.get $scale)))))
|
|
424
|
+
(f64.mul (f64.convert_i32_u (local.get $int)) (local.get $scale)))))
|
|
425
|
+
;; Default mode, fit loop reduced prec to 0: the rounded integer is ready, but the
|
|
426
|
+
;; original val may still have a fractional part that was discarded. Recover it:
|
|
427
|
+
;; frac_f = val - trunc(val); since frac_f ∈ [0,1), frac_f*10^9 < 10^9 < 2^31 — safe.
|
|
428
|
+
(if (i32.and (i32.eqz (local.get $mode)) (i32.eqz (local.get $prec)))
|
|
429
|
+
(then
|
|
430
|
+
(local.set $abs (f64.sub (local.get $val) (f64.trunc (local.get $val))))
|
|
431
|
+
(if (f64.gt (local.get $abs) (f64.const 0))
|
|
432
|
+
(then
|
|
433
|
+
;; $int was taken from f64.nearest(val), which rounds .5+ UP (999999999.9 → 1e9),
|
|
434
|
+
;; but $abs/$frac below derive from f64.trunc(val). Re-derive $int from the same
|
|
435
|
+
;; trunc so integer and fraction agree — else String(999999999.9) → "1000000000.9".
|
|
436
|
+
(local.set $int (i32.trunc_f64_u (f64.trunc (local.get $val))))
|
|
437
|
+
(local.set $prec (i32.const 9))
|
|
438
|
+
(local.set $scale (call $__pow10 (i32.const 9)))
|
|
439
|
+
;; round: trunc_u(x+0.5) == floor(x+0.5) for the positive frac scale
|
|
440
|
+
(local.set $frac (i32.trunc_f64_u (f64.add
|
|
441
|
+
(f64.mul (local.get $abs) (f64.const 1000000000))
|
|
442
|
+
(f64.const 0.5)))))))))
|
|
271
443
|
(else
|
|
272
444
|
(local.set $int (i32.const 0))
|
|
273
445
|
(local.set $frac (i32.const 0))
|
|
274
446
|
(local.set $prec (i32.const 0))
|
|
275
447
|
(local.set $abs (f64.trunc (local.get $val)))
|
|
276
|
-
;; Write large integer digits reversed
|
|
448
|
+
;; Write large integer digits reversed.
|
|
449
|
+
;; Clamp digit to [0,9]: f64 precision loss for large values can make the naive
|
|
450
|
+
;; subtraction (abs - trunc(abs/10)*10) go slightly negative → i32.trunc_f64_u trap.
|
|
277
451
|
(local.set $ilen (local.get $pos))
|
|
278
452
|
(block $ld (loop $ll
|
|
279
453
|
(br_if $ld (f64.lt (local.get $abs) (f64.const 1)))
|
|
280
454
|
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
281
|
-
(i32.add (i32.const 48) (i32.trunc_f64_u (f64.
|
|
282
|
-
(f64.
|
|
455
|
+
(i32.add (i32.const 48) (i32.trunc_f64_u (f64.max (f64.const 0) (f64.min (f64.const 9)
|
|
456
|
+
(f64.nearest (f64.sub (local.get $abs)
|
|
457
|
+
(f64.mul (f64.trunc (f64.div (local.get $abs) (f64.const 10))) (f64.const 10)))))))))
|
|
283
458
|
(local.set $abs (f64.trunc (f64.div (local.get $abs) (f64.const 10))))
|
|
284
459
|
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
285
460
|
(br $ll)))
|
|
@@ -294,6 +469,43 @@ export default (ctx) => {
|
|
|
294
469
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
295
470
|
(local.set $j (i32.sub (local.get $j) (i32.const 1)))
|
|
296
471
|
(br $rl)))
|
|
472
|
+
;; Default mode: emit fractional part if val has one (large-int path skipped it before).
|
|
473
|
+
;; frac_f = val - trunc(val); since frac_f ∈ [0,1), frac_f*10^9 < 10^9 < 2^31 — safe.
|
|
474
|
+
(if (i32.eqz (local.get $mode))
|
|
475
|
+
(then
|
|
476
|
+
(local.set $abs (f64.sub (local.get $val) (f64.trunc (local.get $val))))
|
|
477
|
+
(if (f64.gt (local.get $abs) (f64.const 0))
|
|
478
|
+
(then
|
|
479
|
+
;; round: trunc_u(x+0.5) == floor(x+0.5) for the positive frac scale
|
|
480
|
+
(local.set $frac (i32.trunc_f64_u (f64.add
|
|
481
|
+
(f64.mul (local.get $abs) (f64.const 1000000000))
|
|
482
|
+
(f64.const 0.5))))
|
|
483
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
|
|
484
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
485
|
+
;; 9 fractional digits from $frac, high-to-low
|
|
486
|
+
(local.set $i (i32.const 8))
|
|
487
|
+
(block $fd2 (loop $fl2
|
|
488
|
+
(br_if $fd2 (i32.lt_s (local.get $i) (i32.const 0)))
|
|
489
|
+
(local.set $j (i32.div_u (local.get $frac) (i32.trunc_f64_u (call $__pow10 (local.get $i)))))
|
|
490
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
491
|
+
(i32.add (i32.const 48) (i32.rem_u (local.get $j) (i32.const 10))))
|
|
492
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
493
|
+
(local.set $i (i32.sub (local.get $i) (i32.const 1)))
|
|
494
|
+
(br $fl2)))
|
|
495
|
+
;; Strip trailing zeros
|
|
496
|
+
(block $sz2 (loop $sl2
|
|
497
|
+
(br_if $sz2 (i32.le_s (local.get $pos) (i32.const 0)))
|
|
498
|
+
(br_if $sz2 (i32.ne
|
|
499
|
+
(i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1))))
|
|
500
|
+
(i32.const 48)))
|
|
501
|
+
(local.set $pos (i32.sub (local.get $pos) (i32.const 1)))
|
|
502
|
+
(br $sl2)))
|
|
503
|
+
;; Strip trailing dot
|
|
504
|
+
(if (i32.and (i32.gt_s (local.get $pos) (i32.const 0))
|
|
505
|
+
(i32.eq
|
|
506
|
+
(i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1))))
|
|
507
|
+
(i32.const 46)))
|
|
508
|
+
(then (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))))))))
|
|
297
509
|
(return (call $__mkstr (local.get $buf) (local.get $pos)))))
|
|
298
510
|
;; Write integer part
|
|
299
511
|
(local.set $ilen (call $__itoa (local.get $int) (i32.add (local.get $buf) (local.get $pos))))
|
|
@@ -365,7 +577,7 @@ export default (ctx) => {
|
|
|
365
577
|
(local.set $val (f64.mul (local.get $val) (f64.const 10)))
|
|
366
578
|
(local.set $exp (i32.sub (local.get $exp) (i32.const 1)))
|
|
367
579
|
(br $l2)))))
|
|
368
|
-
;; Scale to integer mantissa: nearest(val * 10^prec)
|
|
580
|
+
;; Scale to integer mantissa: nearest(val * 10^prec). Ties-to-even (see __ftoa note).
|
|
369
581
|
(local.set $scale (call $__pow10 (local.get $prec)))
|
|
370
582
|
(local.set $mantissa (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
|
|
371
583
|
;; Rounding overflow (e.g. 9.95 → 1000 when prec=1, scale=10)
|
|
@@ -441,7 +653,7 @@ export default (ctx) => {
|
|
|
441
653
|
ctx.core.emit['Number.MIN_VALUE'] = () => typed(['f64.const', 5e-324], 'f64')
|
|
442
654
|
ctx.core.emit['Number.POSITIVE_INFINITY'] = () => typed(['f64.const', Infinity], 'f64')
|
|
443
655
|
ctx.core.emit['Number.NEGATIVE_INFINITY'] = () => typed(['f64.const', -Infinity], 'f64')
|
|
444
|
-
ctx.core.emit['Number.NaN'] = () => typed(['f64.const',
|
|
656
|
+
ctx.core.emit['Number.NaN'] = () => typed(['f64.const', 'nan'], 'f64') // `nan` token, not raw NaN (survives self-host IR marshalling — see emitNum)
|
|
445
657
|
|
|
446
658
|
// === Number static methods ===
|
|
447
659
|
|
|
@@ -501,7 +713,8 @@ export default (ctx) => {
|
|
|
501
713
|
|
|
502
714
|
ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str i64) (param $radix i32) (result f64)
|
|
503
715
|
(local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
504
|
-
(local $
|
|
716
|
+
(local $digit i32) (local $seen i32) (local $f f64)
|
|
717
|
+
(local $acc i64) (local $rad i64) (local $ovf i32) (local $exp i32) (local $sticky i32) (local $k i32) (local $e i32)
|
|
505
718
|
(local.set $f (f64.reinterpret_i64 (local.get $str)))
|
|
506
719
|
;; If input is a number, just truncate
|
|
507
720
|
(if (f64.eq (local.get $f) (local.get $f)) (then (return (f64.trunc (local.get $f)))))
|
|
@@ -524,16 +737,21 @@ export default (ctx) => {
|
|
|
524
737
|
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
525
738
|
(i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 43)))
|
|
526
739
|
(then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
527
|
-
;; 0x prefix → radix 16
|
|
528
|
-
(if (i32.and (i32.eqz (local.get $radix))
|
|
740
|
+
;; 0x prefix → radix 16 (stripped when radix is unspecified OR explicitly 16, per JS)
|
|
741
|
+
(if (i32.and (i32.or (i32.eqz (local.get $radix)) (i32.eq (local.get $radix) (i32.const 16)))
|
|
529
742
|
(i32.and (i32.le_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
|
|
530
743
|
(i32.and (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 48))
|
|
531
744
|
(i32.or (i32.eq (call $__char_at (local.get $str) (i32.add (local.get $i) (i32.const 1))) (i32.const 120))
|
|
532
745
|
(i32.eq (call $__char_at (local.get $str) (i32.add (local.get $i) (i32.const 1))) (i32.const 88))))))
|
|
533
746
|
(then (local.set $radix (i32.const 16)) (local.set $i (i32.add (local.get $i) (i32.const 2)))))
|
|
534
747
|
(if (i32.eqz (local.get $radix)) (then (local.set $radix (i32.const 10))))
|
|
535
|
-
;;
|
|
536
|
-
(local.
|
|
748
|
+
;; Power-of-two radix → exact bit width per digit (lets the >2^64 path round once).
|
|
749
|
+
(if (i32.eqz (i32.and (local.get $radix) (i32.sub (local.get $radix) (i32.const 1))))
|
|
750
|
+
(then (local.set $k (i32.ctz (local.get $radix)))))
|
|
751
|
+
(local.set $rad (i64.extend_i32_u (local.get $radix)))
|
|
752
|
+
;; Parse digits — accumulate EXACTLY in u64 (round-once via convert at the end, matching JS for
|
|
753
|
+
;; any value < 2^64). On u64 overflow, freeze the high bits and track the dropped magnitude
|
|
754
|
+
;; (exp) + a sticky bit so the final round-to-f64 still matches round-once for power-of-two radix.
|
|
537
755
|
(block $done (loop $lp
|
|
538
756
|
(br_if $done (i32.ge_s (local.get $i) (local.get $len)))
|
|
539
757
|
(local.set $c (call $__char_at (local.get $str) (local.get $i)))
|
|
@@ -547,12 +765,41 @@ export default (ctx) => {
|
|
|
547
765
|
(then (local.set $digit (i32.sub (local.get $c) (i32.const 55)))))
|
|
548
766
|
(br_if $done (i32.or (i32.lt_s (local.get $digit) (i32.const 0)) (i32.ge_s (local.get $digit) (local.get $radix))))
|
|
549
767
|
(local.set $seen (i32.const 1))
|
|
550
|
-
(
|
|
768
|
+
(if (i32.eqz (local.get $ovf))
|
|
769
|
+
(then
|
|
770
|
+
;; acc*radix + digit, exact while it stays within unsigned 64-bit
|
|
771
|
+
(if (i64.le_u (local.get $acc) (i64.div_u (i64.sub (i64.const -1) (i64.extend_i32_u (local.get $digit))) (local.get $rad)))
|
|
772
|
+
(then (local.set $acc (i64.add (i64.mul (local.get $acc) (local.get $rad)) (i64.extend_i32_u (local.get $digit)))))
|
|
773
|
+
(else
|
|
774
|
+
(local.set $ovf (i32.const 1))
|
|
775
|
+
;; non-power-of-two radix: continue round-each in f64 from the exact seed (keeps magnitude)
|
|
776
|
+
(if (i32.eqz (local.get $k)) (then (local.set $f (f64.convert_i64_u (local.get $acc)))))))))
|
|
777
|
+
(if (local.get $ovf)
|
|
778
|
+
(then
|
|
779
|
+
(if (local.get $k)
|
|
780
|
+
(then ;; power-of-two: this digit and all later ones sit below the frozen high bits
|
|
781
|
+
(local.set $exp (i32.add (local.get $exp) (local.get $k)))
|
|
782
|
+
(if (local.get $digit) (then (local.set $sticky (i32.const 1)))))
|
|
783
|
+
(else ;; other radix: round-each f64 — ≤1 ULP past 2^53, but only for >2^64 values
|
|
784
|
+
(local.set $f (f64.add (f64.mul (local.get $f) (f64.convert_i32_u (local.get $radix))) (f64.convert_i32_u (local.get $digit))))))))
|
|
551
785
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
552
786
|
(br $lp)))
|
|
553
787
|
;; No digits consumed → NaN
|
|
554
788
|
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
555
|
-
|
|
789
|
+
;; Produce the f64. No overflow → exact round-once convert. Overflow+power-of-two → round the
|
|
790
|
+
;; frozen high bits (sticky-injected to break ties correctly) and scale by 2^exp. Overflow on
|
|
791
|
+
;; another radix → $f already holds the round-each result.
|
|
792
|
+
(if (i32.eqz (local.get $ovf))
|
|
793
|
+
(then (local.set $f (f64.convert_i64_u (local.get $acc))))
|
|
794
|
+
(else (if (local.get $k)
|
|
795
|
+
(then
|
|
796
|
+
(if (local.get $sticky) (then (local.set $acc (i64.or (local.get $acc) (i64.const 1)))))
|
|
797
|
+
(local.set $f (f64.convert_i64_u (local.get $acc)))
|
|
798
|
+
(local.set $e (i32.add (local.get $exp) (i32.const 1023)))
|
|
799
|
+
(if (i32.ge_s (local.get $e) (i32.const 2047))
|
|
800
|
+
(then (local.set $f (f64.const inf)))
|
|
801
|
+
(else (local.set $f (f64.mul (local.get $f) (f64.reinterpret_i64 (i64.shl (i64.extend_i32_u (local.get $e)) (i64.const 52)))))))))))
|
|
802
|
+
(if (result f64) (local.get $neg) (then (f64.neg (local.get $f))) (else (local.get $f))))`
|
|
556
803
|
|
|
557
804
|
// __strws(c: i32) → i32 — ECMA StrWhiteSpace predicate. Covers TAB..CR, SP,
|
|
558
805
|
// NBSP, BOM, LS/PS, and every Unicode Space_Separator. U+180E is *not*
|
|
@@ -613,6 +860,8 @@ export default (ctx) => {
|
|
|
613
860
|
(if (f64.eq (local.get $f) (local.get $f)) (then (return (local.get $f))))
|
|
614
861
|
(if (i64.eq (local.get $v) (i64.const ${NULL_NAN})) (then (return (f64.const 0))))
|
|
615
862
|
(if (i64.eq (local.get $v) (i64.const ${UNDEF_NAN})) (then (return (f64.const nan))))
|
|
863
|
+
(if (i64.eq (local.get $v) (i64.const ${FALSE_NAN})) (then (return (f64.const 0))))
|
|
864
|
+
(if (i64.eq (local.get $v) (i64.const ${TRUE_NAN})) (then (return (f64.const 1))))
|
|
616
865
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
617
866
|
;; ToNumber(Symbol) is a TypeError. A Symbol is an ATOM (type 0) with a user
|
|
618
867
|
;; atom-id (>= 16); null/undefined returned above, and a bare NaN carries
|
|
@@ -854,23 +1103,22 @@ export default (ctx) => {
|
|
|
854
1103
|
// render as "true"/"false" (spec step 1: ToString) before parsing — otherwise
|
|
855
1104
|
// its 0/1 carrier bits are fed to the parser as if a string pointer. Other types
|
|
856
1105
|
// (string already, number/object ToPrimitive) stay out of scope per the runner.
|
|
857
|
-
const strInputI64 = (x) => valTypeOf(x) === VAL.BOOL ? asI64(
|
|
1106
|
+
const strInputI64 = (x) => valTypeOf(x) === VAL.BOOL ? asI64(bool(x)) : asI64(emit(x))
|
|
858
1107
|
|
|
859
1108
|
ctx.core.emit['Number.parseInt'] = (x, radix) => {
|
|
1109
|
+
if (ctx.transform.host === 'wasi') {
|
|
1110
|
+
inc('__parseInt')
|
|
1111
|
+
const radixIR = radix == null ? ['i32.const', 0] : toI32(toNumF64(radix, emit(radix)))
|
|
1112
|
+
return typed(['call', '$__parseInt', strInputI64(x), radixIR], 'f64')
|
|
1113
|
+
}
|
|
860
1114
|
needParseInt()
|
|
861
|
-
// Radix is coerced ToNumber then ToInt32 (modular wrap; NaN/±∞→0) per spec —
|
|
862
|
-
// a raw trunc would turn a string radix into garbage and Infinity into maxint.
|
|
863
1115
|
const radixIR = radix == null ? ['i32.const', 0] : toI32(toNumF64(radix, emit(radix)))
|
|
864
1116
|
return typed(['call', '$__parseInt', strInputI64(x), radixIR], 'f64')
|
|
865
1117
|
}
|
|
866
1118
|
ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
|
|
867
|
-
const
|
|
868
|
-
if (ctx.module.imports.some(i => i[1] === `"${mod}"` && i[2] === `"${name}"`)) return
|
|
869
|
-
ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, fn])
|
|
870
|
-
}
|
|
871
|
-
const needParseFloat = () => addImportOnce(ctx, 'env', 'parseFloat',
|
|
1119
|
+
const needParseFloat = () => hostImport('env', 'parseFloat',
|
|
872
1120
|
['func', '$__parseFloat', ['param', 'i64'], ['result', 'f64']])
|
|
873
|
-
const needParseInt = () =>
|
|
1121
|
+
const needParseInt = () => hostImport('env', 'parseInt',
|
|
874
1122
|
['func', '$__parseInt', ['param', 'i64'], ['param', 'i32'], ['result', 'f64']])
|
|
875
1123
|
|
|
876
1124
|
ctx.core.emit['Number.parseFloat'] = (x) => {
|
|
@@ -884,32 +1132,44 @@ export default (ctx) => {
|
|
|
884
1132
|
ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
|
|
885
1133
|
|
|
886
1134
|
// Boolean(x) → truthiness (non-zero → 1, zero → 0)
|
|
887
|
-
|
|
1135
|
+
reg('Boolean', ['__is_truthy'], (x) => {
|
|
888
1136
|
if (x === undefined) return typed(['f64.const', 0], 'f64')
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
return typed(['
|
|
892
|
-
}
|
|
1137
|
+
// Via truthyIR so a NUMBER arg gets the NaN-safe f64 test (Boolean(0/0) === false
|
|
1138
|
+
// on every platform); other types fall to __is_truthy inside truthyIR.
|
|
1139
|
+
return typed(['f64.convert_i32_s', truthyIR(emit(x))], 'f64')
|
|
1140
|
+
})
|
|
893
1141
|
|
|
894
1142
|
// === Instance method emitters ===
|
|
895
1143
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1144
|
+
const ftoaDefault = (v) => typed(['call', '$__ftoa', v, ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
1145
|
+
reg('.number:toString', ['__ftoa', '__num_radix'], (n, radix) => {
|
|
1146
|
+
const v = asF64(emit(n))
|
|
1147
|
+
if (radix == null) return ftoaDefault(v)
|
|
1148
|
+
const rv = emit(radix)
|
|
1149
|
+
// Constant radix folds the 10-vs-other choice at compile time; radix 10 keeps
|
|
1150
|
+
// __ftoa's shortest-repr (the radix loop would emit float-noise tail digits).
|
|
1151
|
+
if (Array.isArray(rv) && rv[0] === 'f64.const' && typeof rv[1] === 'number')
|
|
1152
|
+
return rv[1] === 10 ? ftoaDefault(v) : typed(['call', '$__num_radix', v, ['i32.const', rv[1] | 0]], 'f64')
|
|
1153
|
+
const vt = temp('rv'), rt = tempI32('rr')
|
|
1154
|
+
return typed(['block', ['result', 'f64'],
|
|
1155
|
+
['local.set', `$${vt}`, v],
|
|
1156
|
+
['local.set', `$${rt}`, asI32(rv)],
|
|
1157
|
+
['if', ['result', 'f64'], ['i32.eq', ['local.get', `$${rt}`], ['i32.const', 10]],
|
|
1158
|
+
['then', ftoaDefault(['local.get', `$${vt}`])],
|
|
1159
|
+
['else', ['call', '$__num_radix', ['local.get', `$${vt}`], ['local.get', `$${rt}`]]]]], 'f64')
|
|
1160
|
+
})
|
|
900
1161
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
1162
|
+
// BigInt.prototype.toString(radix) — i64-exact, default radix 10.
|
|
1163
|
+
reg('.bigint:toString', ['__radix_str'], (n, radix) =>
|
|
1164
|
+
typed(['call', '$__radix_str', asI64(emit(n)), radix == null ? ['i32.const', 10] : asI32(emit(radix))], 'f64'))
|
|
905
1165
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
return typed(['call', '$__toExp', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 0]], 'f64')
|
|
909
|
-
}
|
|
1166
|
+
reg('.number:toFixed', ['__ftoa'], (n, d) =>
|
|
1167
|
+
typed(['call', '$__ftoa', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 1]], 'f64'))
|
|
910
1168
|
|
|
911
|
-
|
|
912
|
-
|
|
1169
|
+
reg('.number:toExponential', ['__toExp'], (n, d) =>
|
|
1170
|
+
typed(['call', '$__toExp', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 0]], 'f64'))
|
|
1171
|
+
|
|
1172
|
+
reg('.number:toPrecision', ['__ftoa', '__toExp'], (n, p) => {
|
|
913
1173
|
const val = temp('pv'), t = temp('tp'), exp = tempI32('te'), pr = tempI32('pp')
|
|
914
1174
|
return typed(['block', ['result', 'f64'],
|
|
915
1175
|
['local.set', `$${val}`, asF64(emit(n))],
|
|
@@ -936,7 +1196,7 @@ export default (ctx) => {
|
|
|
936
1196
|
['else', ['call', '$__ftoa', ['local.get', `$${val}`],
|
|
937
1197
|
['i32.sub', ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]], ['local.get', `$${exp}`]],
|
|
938
1198
|
['i32.const', 1]]]]], 'f64')
|
|
939
|
-
}
|
|
1199
|
+
})
|
|
940
1200
|
|
|
941
1201
|
// Number(x) — identity for numbers, i64→f64 conversion for BigInt
|
|
942
1202
|
ctx.core.emit['Number'] = (x) => {
|
|
@@ -963,7 +1223,7 @@ export default (ctx) => {
|
|
|
963
1223
|
['if', ['result', 'f64'], ['f64.eq', ['local.get', `$${t}`], ['local.get', `$${t}`]],
|
|
964
1224
|
['then', ['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', ['local.get', `$${t}`]]]],
|
|
965
1225
|
['else', ['if', ['result', 'f64'],
|
|
966
|
-
['
|
|
1226
|
+
ptrTypeEq(['local.get', `$${t}`], PTR.STRING),
|
|
967
1227
|
['then', ['call', '$__to_bigint', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
968
1228
|
['else', ['local.get', `$${t}`]]]]]], 'f64')
|
|
969
1229
|
}
|
|
@@ -986,11 +1246,18 @@ export default (ctx) => {
|
|
|
986
1246
|
['i64.shr_s', ['i64.shl', vval, ['local.get', `$${t}`]], ['local.get', `$${t}`]]]], 'f64')
|
|
987
1247
|
}
|
|
988
1248
|
|
|
989
|
-
// BigInt.asUintN(bits, bigint) — truncate to unsigned N-bit
|
|
1249
|
+
// BigInt.asUintN(bits, bigint) — truncate to unsigned N-bit.
|
|
1250
|
+
// (val << (64 - bits)) >>> (64 - bits) — logical shift zero-extends the low `bits`.
|
|
1251
|
+
// The naive `val & ((1 << bits) - 1)` mask is wrong at bits=64: i64.shl shifts mod 64,
|
|
1252
|
+
// so `1 << 64` is `1 << 0 = 1`, making the mask `0` and asUintN(64,·) collapse to 0 —
|
|
1253
|
+
// which also zeroed every bigint *literal* (emit reinterprets `Nn` via
|
|
1254
|
+
// BigInt.asUintN(64,·).toString() through this very handler in the self-host).
|
|
990
1255
|
ctx.core.emit['BigInt.asUintN'] = (bits, val) => {
|
|
991
1256
|
const vbits = asI32(emit(bits)), vval = asI64(emit(val))
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1257
|
+
const shift = typed(['i64.sub', ['i64.const', 64], ['i64.extend_i32_s', vbits]], 'i64')
|
|
1258
|
+
const t = tempI64('bu')
|
|
1259
|
+
return typed(['f64.reinterpret_i64', ['block', ['result', 'i64'],
|
|
1260
|
+
['local.set', `$${t}`, shift],
|
|
1261
|
+
['i64.shr_u', ['i64.shl', vval, ['local.get', `$${t}`]], ['local.get', `$${t}`]]]], 'f64')
|
|
995
1262
|
}
|
|
996
1263
|
}
|