jz 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- 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 +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +281 -142
- 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 +461 -185
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +591 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +600 -205
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -2997
- package/src/jzify.js +0 -1553
- 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,12 @@ 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'],
|
|
141
158
|
__toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
|
|
159
|
+
__radix_str: ['__mkstr'],
|
|
160
|
+
__num_radix: ['__ftoa', '__mkstr'],
|
|
142
161
|
__to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str', '__skipws', '__ptr_aux'],
|
|
143
162
|
__skipws: ['__char_at', '__strws'],
|
|
144
163
|
__to_bigint: ['__char_at', '__str_byteLen', '__num_to_bigint'],
|
|
@@ -193,22 +212,131 @@ export default (ctx) => {
|
|
|
193
212
|
(br $l)))
|
|
194
213
|
;; Reverse
|
|
195
214
|
(local.set $j (i32.sub (local.get $len) (i32.const 1)))
|
|
196
|
-
|
|
197
|
-
|
|
215
|
+
${reverseBytesWat()}
|
|
216
|
+
(local.get $len))`
|
|
217
|
+
|
|
218
|
+
// __radix_str(val: i64, radix: i32) → f64 (NaN-boxed string)
|
|
219
|
+
// Signed integer → radix string for BigInt.prototype.toString(radix). Digits go
|
|
220
|
+
// 0-9 then a-z (lowercase, per spec); magnitude is taken unsigned so i64.MIN
|
|
221
|
+
// (whose two's-complement negation is itself) formats correctly via div_u/rem_u.
|
|
222
|
+
ctx.core.stdlib['__radix_str'] = `(func $__radix_str (param $val i64) (param $radix i32) (result f64)
|
|
223
|
+
(local $buf i32) (local $pos i32) (local $neg i32) (local $mag i64) (local $r i64)
|
|
224
|
+
(local $dg i32) (local $i i32) (local $j i32) (local $tmp i32)
|
|
225
|
+
(local.set $buf (call $__alloc (i32.const 72)))
|
|
226
|
+
(local.set $r (i64.extend_i32_s (local.get $radix)))
|
|
227
|
+
(if (i64.eqz (local.get $val))
|
|
228
|
+
(then (i32.store8 (local.get $buf) (i32.const 48)) (return (call $__mkstr (local.get $buf) (i32.const 1)))))
|
|
229
|
+
(local.set $mag (local.get $val))
|
|
230
|
+
(if (i64.lt_s (local.get $val) (i64.const 0))
|
|
231
|
+
(then (local.set $neg (i32.const 1)) (local.set $mag (i64.sub (i64.const 0) (local.get $val)))))
|
|
232
|
+
(block $mb (loop $ml
|
|
233
|
+
(br_if $mb (i64.eqz (local.get $mag)))
|
|
234
|
+
(local.set $dg (i32.wrap_i64 (i64.rem_u (local.get $mag) (local.get $r))))
|
|
235
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
236
|
+
(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))))
|
|
237
|
+
(local.set $mag (i64.div_u (local.get $mag) (local.get $r)))
|
|
238
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
239
|
+
(br $ml)))
|
|
240
|
+
(if (local.get $neg)
|
|
241
|
+
(then (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 45))
|
|
242
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))))
|
|
243
|
+
(local.set $j (i32.sub (local.get $pos) (i32.const 1)))
|
|
244
|
+
${reverseBytesWat()}
|
|
245
|
+
(call $__mkstr (local.get $buf) (local.get $pos)))`
|
|
246
|
+
|
|
247
|
+
// __num_radix(val: f64, radix: i32) → f64 (NaN-boxed string)
|
|
248
|
+
// Number.prototype.toString(radix) for radix != 10. Non-finite values defer to
|
|
249
|
+
// __ftoa ("NaN"/"Infinity"/"-Infinity"). Integer part uses exact i64 division
|
|
250
|
+
// (magnitude bounded by jz's i64 BigInt domain); the fraction multiplies out in
|
|
251
|
+
// f64, capped at 100 digits (radix fractions are implementation-defined precision).
|
|
252
|
+
//
|
|
253
|
+
// Per 21.1.3.6: radix must be an integer in [2, 36] — otherwise RangeError.
|
|
254
|
+
// The check sits at the canonical entry point so the const-radix fold path
|
|
255
|
+
// (which still calls here for any non-10 constant) and the dynamic-radix
|
|
256
|
+
// branch are validated uniformly. `(throw $__jz_err …)` is picked up by
|
|
257
|
+
// ensureThrowRuntime via stdlib scan, so callers do not need to flag throws.
|
|
258
|
+
ctx.core.stdlib['__num_radix'] = `(func $__num_radix (param $val f64) (param $radix i32) (result f64)
|
|
259
|
+
(local $buf i32) (local $pos i32) (local $neg i32) (local $iv i64) (local $r i64) (local $rf f64)
|
|
260
|
+
(local $int f64) (local $frac f64) (local $dg i32) (local $i i32) (local $j i32) (local $tmp i32) (local $fn i32) (local $rv f64)
|
|
261
|
+
(if (i32.or (i32.lt_s (local.get $radix) (i32.const 2)) (i32.gt_s (local.get $radix) (i32.const 36)))
|
|
262
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
263
|
+
(if (i32.or (f64.ne (local.get $val) (local.get $val)) (f64.eq (f64.abs (local.get $val)) (f64.const inf)))
|
|
264
|
+
(then (return (call $__ftoa (local.get $val) (i32.const 0) (i32.const 0)))))
|
|
265
|
+
(local.set $buf (call $__alloc (i32.const 180)))
|
|
266
|
+
(local.set $r (i64.extend_i32_s (local.get $radix)))
|
|
267
|
+
(local.set $rf (f64.convert_i32_s (local.get $radix)))
|
|
268
|
+
(if (f64.lt (local.get $val) (f64.const 0))
|
|
269
|
+
(then (local.set $neg (i32.const 1)) (local.set $val (f64.neg (local.get $val)))))
|
|
270
|
+
(local.set $int (f64.floor (local.get $val)))
|
|
271
|
+
(local.set $frac (f64.sub (local.get $val) (local.get $int)))
|
|
272
|
+
(local.set $iv (i64.trunc_sat_f64_u (local.get $int)))
|
|
273
|
+
(if (i64.eqz (local.get $iv))
|
|
274
|
+
(then (i32.store8 (local.get $buf) (i32.const 48)) (local.set $pos (i32.const 1)))
|
|
275
|
+
(else
|
|
276
|
+
(block $ib (loop $il
|
|
277
|
+
(br_if $ib (i64.eqz (local.get $iv)))
|
|
278
|
+
(local.set $dg (i32.wrap_i64 (i64.rem_u (local.get $iv) (local.get $r))))
|
|
279
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
280
|
+
(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))))
|
|
281
|
+
(local.set $iv (i64.div_u (local.get $iv) (local.get $r)))
|
|
282
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
283
|
+
(br $il)))))
|
|
284
|
+
(if (local.get $neg)
|
|
285
|
+
(then (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 45))
|
|
286
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))))
|
|
287
|
+
(local.set $j (i32.sub (local.get $pos) (i32.const 1)))
|
|
288
|
+
(block $rb (loop $rl
|
|
289
|
+
(br_if $rb (i32.ge_s (local.get $i) (local.get $j)))
|
|
198
290
|
(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))))
|
|
291
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $i)) (i32.load8_u (i32.add (local.get $buf) (local.get $j))))
|
|
201
292
|
(i32.store8 (i32.add (local.get $buf) (local.get $j)) (local.get $tmp))
|
|
202
293
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
203
294
|
(local.set $j (i32.sub (local.get $j) (i32.const 1)))
|
|
204
295
|
(br $rl)))
|
|
205
|
-
(local.get $
|
|
296
|
+
(if (f64.gt (local.get $frac) (f64.const 0))
|
|
297
|
+
(then
|
|
298
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
|
|
299
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
300
|
+
(block $fb (loop $fl
|
|
301
|
+
(br_if $fb (f64.le (local.get $frac) (f64.const 0)))
|
|
302
|
+
(br_if $fb (i32.ge_s (local.get $fn) (i32.const 100)))
|
|
303
|
+
(local.set $frac (f64.mul (local.get $frac) (local.get $rf)))
|
|
304
|
+
(local.set $dg (i32.trunc_f64_s (f64.floor (local.get $frac))))
|
|
305
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
306
|
+
(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))))
|
|
307
|
+
(local.set $frac (f64.sub (local.get $frac) (f64.floor (local.get $frac))))
|
|
308
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
309
|
+
(local.set $fn (i32.add (local.get $fn) (i32.const 1)))
|
|
310
|
+
(br $fl)))))
|
|
311
|
+
${!ctx.memory.shared ? `
|
|
312
|
+
;; When __mkstr packed an SSO result it allocated nothing, so the digit scratch
|
|
313
|
+
;; ($buf, at heap top) is dead — reclaim it so a heap-top accumulator stays on
|
|
314
|
+
;; top and \`s += n.toString(r)\` bump-extends instead of reallocating (O(n)).
|
|
315
|
+
(local.set $rv (call $__mkstr (local.get $buf) (local.get $pos)))
|
|
316
|
+
(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}))
|
|
317
|
+
(then (global.set $__heap (local.get $buf))))
|
|
318
|
+
(local.get $rv)` : `(call $__mkstr (local.get $buf) (local.get $pos))`})`
|
|
206
319
|
|
|
207
320
|
// __mkstr(buf: i32, len: i32) → f64 — copy scratch buffer to heap string.
|
|
208
321
|
// Hot (~60M calls in watr self-host via __ftoa). bulk memory.copy is ~10× faster than
|
|
209
322
|
// a hand-rolled byte loop (wasm2c lowers it to memcpy under PGO+LTO).
|
|
210
323
|
ctx.core.stdlib['__mkstr'] = `(func $__mkstr (param $buf i32) (param $len i32) (result f64)
|
|
211
|
-
(local $off i32)
|
|
324
|
+
(local $off i32) (local $i i32) (local $packed i32) (local $b i32)
|
|
325
|
+
;; SSO fast path: ≤4 ASCII bytes pack into the pointer with no allocation, so a
|
|
326
|
+
;; number-format result doesn't displace a heap-top accumulator — keeping the
|
|
327
|
+
;; canonical \`s += n.toString(r)\` builder O(n) via the bump-extend path.
|
|
328
|
+
(if (i32.le_u (local.get $len) (i32.const 4))
|
|
329
|
+
(then
|
|
330
|
+
(block $heap
|
|
331
|
+
(loop $pk
|
|
332
|
+
(if (i32.lt_u (local.get $i) (local.get $len))
|
|
333
|
+
(then
|
|
334
|
+
(local.set $b (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
|
|
335
|
+
(br_if $heap (i32.ge_u (local.get $b) (i32.const 0x80)))
|
|
336
|
+
(local.set $packed (i32.or (local.get $packed) (i32.shl (local.get $b) (i32.shl (local.get $i) (i32.const 3)))))
|
|
337
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
338
|
+
(br $pk))))
|
|
339
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $len)) (local.get $packed))))))
|
|
212
340
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
|
|
213
341
|
(i32.store (local.get $off) (local.get $len))
|
|
214
342
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
@@ -251,7 +379,12 @@ export default (ctx) => {
|
|
|
251
379
|
;; Default mode: auto-select precision (up to 9 digits, must fit i32 when scaled)
|
|
252
380
|
(if (i32.eqz (local.get $mode))
|
|
253
381
|
(then (local.set $prec (i32.const 9))))
|
|
254
|
-
;; Round and scale to integer: scaled = nearest(val * 10^prec)
|
|
382
|
+
;; Round and scale to integer: scaled = nearest(val * 10^prec).
|
|
383
|
+
;; NOTE: toFixed/toPrecision round ties-to-even here (f64.nearest), which differs from
|
|
384
|
+
;; JS's round-half-away-from-zero on exact halves like (2.5).toFixed(0) → '2' vs '3'.
|
|
385
|
+
;; A naive floor(x+0.5) "fixes" those but breaks values like 1.45 (whose ×10 rounds up
|
|
386
|
+
;; to 14.5 in f64, giving '1.5' vs JS '1.4'); bit-exact toFixed needs the exact-decimal
|
|
387
|
+
;; algorithm. Documented as a known difference rather than trading one error for another.
|
|
255
388
|
(local.set $scale (call $__pow10 (local.get $prec)))
|
|
256
389
|
(local.set $scaled (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
|
|
257
390
|
;; If scaled doesn't fit i32, reduce precision until it does (min prec=0)
|
|
@@ -267,19 +400,40 @@ export default (ctx) => {
|
|
|
267
400
|
(then
|
|
268
401
|
(local.set $int (i32.trunc_f64_u (f64.div (local.get $scaled) (local.get $scale))))
|
|
269
402
|
(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)))))
|
|
403
|
+
(f64.mul (f64.convert_i32_u (local.get $int)) (local.get $scale)))))
|
|
404
|
+
;; Default mode, fit loop reduced prec to 0: the rounded integer is ready, but the
|
|
405
|
+
;; original val may still have a fractional part that was discarded. Recover it:
|
|
406
|
+
;; frac_f = val - trunc(val); since frac_f ∈ [0,1), frac_f*10^9 < 10^9 < 2^31 — safe.
|
|
407
|
+
(if (i32.and (i32.eqz (local.get $mode)) (i32.eqz (local.get $prec)))
|
|
408
|
+
(then
|
|
409
|
+
(local.set $abs (f64.sub (local.get $val) (f64.trunc (local.get $val))))
|
|
410
|
+
(if (f64.gt (local.get $abs) (f64.const 0))
|
|
411
|
+
(then
|
|
412
|
+
;; $int was taken from f64.nearest(val), which rounds .5+ UP (999999999.9 → 1e9),
|
|
413
|
+
;; but $abs/$frac below derive from f64.trunc(val). Re-derive $int from the same
|
|
414
|
+
;; trunc so integer and fraction agree — else String(999999999.9) → "1000000000.9".
|
|
415
|
+
(local.set $int (i32.trunc_f64_u (f64.trunc (local.get $val))))
|
|
416
|
+
(local.set $prec (i32.const 9))
|
|
417
|
+
(local.set $scale (call $__pow10 (i32.const 9)))
|
|
418
|
+
;; round: trunc_u(x+0.5) == floor(x+0.5) for the positive frac scale
|
|
419
|
+
(local.set $frac (i32.trunc_f64_u (f64.add
|
|
420
|
+
(f64.mul (local.get $abs) (f64.const 1000000000))
|
|
421
|
+
(f64.const 0.5)))))))))
|
|
271
422
|
(else
|
|
272
423
|
(local.set $int (i32.const 0))
|
|
273
424
|
(local.set $frac (i32.const 0))
|
|
274
425
|
(local.set $prec (i32.const 0))
|
|
275
426
|
(local.set $abs (f64.trunc (local.get $val)))
|
|
276
|
-
;; Write large integer digits reversed
|
|
427
|
+
;; Write large integer digits reversed.
|
|
428
|
+
;; Clamp digit to [0,9]: f64 precision loss for large values can make the naive
|
|
429
|
+
;; subtraction (abs - trunc(abs/10)*10) go slightly negative → i32.trunc_f64_u trap.
|
|
277
430
|
(local.set $ilen (local.get $pos))
|
|
278
431
|
(block $ld (loop $ll
|
|
279
432
|
(br_if $ld (f64.lt (local.get $abs) (f64.const 1)))
|
|
280
433
|
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
281
|
-
(i32.add (i32.const 48) (i32.trunc_f64_u (f64.
|
|
282
|
-
(f64.
|
|
434
|
+
(i32.add (i32.const 48) (i32.trunc_f64_u (f64.max (f64.const 0) (f64.min (f64.const 9)
|
|
435
|
+
(f64.nearest (f64.sub (local.get $abs)
|
|
436
|
+
(f64.mul (f64.trunc (f64.div (local.get $abs) (f64.const 10))) (f64.const 10)))))))))
|
|
283
437
|
(local.set $abs (f64.trunc (f64.div (local.get $abs) (f64.const 10))))
|
|
284
438
|
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
285
439
|
(br $ll)))
|
|
@@ -294,6 +448,43 @@ export default (ctx) => {
|
|
|
294
448
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
295
449
|
(local.set $j (i32.sub (local.get $j) (i32.const 1)))
|
|
296
450
|
(br $rl)))
|
|
451
|
+
;; Default mode: emit fractional part if val has one (large-int path skipped it before).
|
|
452
|
+
;; frac_f = val - trunc(val); since frac_f ∈ [0,1), frac_f*10^9 < 10^9 < 2^31 — safe.
|
|
453
|
+
(if (i32.eqz (local.get $mode))
|
|
454
|
+
(then
|
|
455
|
+
(local.set $abs (f64.sub (local.get $val) (f64.trunc (local.get $val))))
|
|
456
|
+
(if (f64.gt (local.get $abs) (f64.const 0))
|
|
457
|
+
(then
|
|
458
|
+
;; round: trunc_u(x+0.5) == floor(x+0.5) for the positive frac scale
|
|
459
|
+
(local.set $frac (i32.trunc_f64_u (f64.add
|
|
460
|
+
(f64.mul (local.get $abs) (f64.const 1000000000))
|
|
461
|
+
(f64.const 0.5))))
|
|
462
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
|
|
463
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
464
|
+
;; 9 fractional digits from $frac, high-to-low
|
|
465
|
+
(local.set $i (i32.const 8))
|
|
466
|
+
(block $fd2 (loop $fl2
|
|
467
|
+
(br_if $fd2 (i32.lt_s (local.get $i) (i32.const 0)))
|
|
468
|
+
(local.set $j (i32.div_u (local.get $frac) (i32.trunc_f64_u (call $__pow10 (local.get $i)))))
|
|
469
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $pos))
|
|
470
|
+
(i32.add (i32.const 48) (i32.rem_u (local.get $j) (i32.const 10))))
|
|
471
|
+
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
472
|
+
(local.set $i (i32.sub (local.get $i) (i32.const 1)))
|
|
473
|
+
(br $fl2)))
|
|
474
|
+
;; Strip trailing zeros
|
|
475
|
+
(block $sz2 (loop $sl2
|
|
476
|
+
(br_if $sz2 (i32.le_s (local.get $pos) (i32.const 0)))
|
|
477
|
+
(br_if $sz2 (i32.ne
|
|
478
|
+
(i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1))))
|
|
479
|
+
(i32.const 48)))
|
|
480
|
+
(local.set $pos (i32.sub (local.get $pos) (i32.const 1)))
|
|
481
|
+
(br $sl2)))
|
|
482
|
+
;; Strip trailing dot
|
|
483
|
+
(if (i32.and (i32.gt_s (local.get $pos) (i32.const 0))
|
|
484
|
+
(i32.eq
|
|
485
|
+
(i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1))))
|
|
486
|
+
(i32.const 46)))
|
|
487
|
+
(then (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))))))))
|
|
297
488
|
(return (call $__mkstr (local.get $buf) (local.get $pos)))))
|
|
298
489
|
;; Write integer part
|
|
299
490
|
(local.set $ilen (call $__itoa (local.get $int) (i32.add (local.get $buf) (local.get $pos))))
|
|
@@ -365,7 +556,7 @@ export default (ctx) => {
|
|
|
365
556
|
(local.set $val (f64.mul (local.get $val) (f64.const 10)))
|
|
366
557
|
(local.set $exp (i32.sub (local.get $exp) (i32.const 1)))
|
|
367
558
|
(br $l2)))))
|
|
368
|
-
;; Scale to integer mantissa: nearest(val * 10^prec)
|
|
559
|
+
;; Scale to integer mantissa: nearest(val * 10^prec). Ties-to-even (see __ftoa note).
|
|
369
560
|
(local.set $scale (call $__pow10 (local.get $prec)))
|
|
370
561
|
(local.set $mantissa (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
|
|
371
562
|
;; Rounding overflow (e.g. 9.95 → 1000 when prec=1, scale=10)
|
|
@@ -441,7 +632,7 @@ export default (ctx) => {
|
|
|
441
632
|
ctx.core.emit['Number.MIN_VALUE'] = () => typed(['f64.const', 5e-324], 'f64')
|
|
442
633
|
ctx.core.emit['Number.POSITIVE_INFINITY'] = () => typed(['f64.const', Infinity], 'f64')
|
|
443
634
|
ctx.core.emit['Number.NEGATIVE_INFINITY'] = () => typed(['f64.const', -Infinity], 'f64')
|
|
444
|
-
ctx.core.emit['Number.NaN'] = () => typed(['f64.const',
|
|
635
|
+
ctx.core.emit['Number.NaN'] = () => typed(['f64.const', 'nan'], 'f64') // `nan` token, not raw NaN (survives self-host IR marshalling — see emitNum)
|
|
445
636
|
|
|
446
637
|
// === Number static methods ===
|
|
447
638
|
|
|
@@ -501,7 +692,8 @@ export default (ctx) => {
|
|
|
501
692
|
|
|
502
693
|
ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str i64) (param $radix i32) (result f64)
|
|
503
694
|
(local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
504
|
-
(local $
|
|
695
|
+
(local $digit i32) (local $seen i32) (local $f f64)
|
|
696
|
+
(local $acc i64) (local $rad i64) (local $ovf i32) (local $exp i32) (local $sticky i32) (local $k i32) (local $e i32)
|
|
505
697
|
(local.set $f (f64.reinterpret_i64 (local.get $str)))
|
|
506
698
|
;; If input is a number, just truncate
|
|
507
699
|
(if (f64.eq (local.get $f) (local.get $f)) (then (return (f64.trunc (local.get $f)))))
|
|
@@ -524,16 +716,21 @@ export default (ctx) => {
|
|
|
524
716
|
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
525
717
|
(i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 43)))
|
|
526
718
|
(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))
|
|
719
|
+
;; 0x prefix → radix 16 (stripped when radix is unspecified OR explicitly 16, per JS)
|
|
720
|
+
(if (i32.and (i32.or (i32.eqz (local.get $radix)) (i32.eq (local.get $radix) (i32.const 16)))
|
|
529
721
|
(i32.and (i32.le_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
|
|
530
722
|
(i32.and (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 48))
|
|
531
723
|
(i32.or (i32.eq (call $__char_at (local.get $str) (i32.add (local.get $i) (i32.const 1))) (i32.const 120))
|
|
532
724
|
(i32.eq (call $__char_at (local.get $str) (i32.add (local.get $i) (i32.const 1))) (i32.const 88))))))
|
|
533
725
|
(then (local.set $radix (i32.const 16)) (local.set $i (i32.add (local.get $i) (i32.const 2)))))
|
|
534
726
|
(if (i32.eqz (local.get $radix)) (then (local.set $radix (i32.const 10))))
|
|
535
|
-
;;
|
|
536
|
-
(local.
|
|
727
|
+
;; Power-of-two radix → exact bit width per digit (lets the >2^64 path round once).
|
|
728
|
+
(if (i32.eqz (i32.and (local.get $radix) (i32.sub (local.get $radix) (i32.const 1))))
|
|
729
|
+
(then (local.set $k (i32.ctz (local.get $radix)))))
|
|
730
|
+
(local.set $rad (i64.extend_i32_u (local.get $radix)))
|
|
731
|
+
;; Parse digits — accumulate EXACTLY in u64 (round-once via convert at the end, matching JS for
|
|
732
|
+
;; any value < 2^64). On u64 overflow, freeze the high bits and track the dropped magnitude
|
|
733
|
+
;; (exp) + a sticky bit so the final round-to-f64 still matches round-once for power-of-two radix.
|
|
537
734
|
(block $done (loop $lp
|
|
538
735
|
(br_if $done (i32.ge_s (local.get $i) (local.get $len)))
|
|
539
736
|
(local.set $c (call $__char_at (local.get $str) (local.get $i)))
|
|
@@ -547,12 +744,41 @@ export default (ctx) => {
|
|
|
547
744
|
(then (local.set $digit (i32.sub (local.get $c) (i32.const 55)))))
|
|
548
745
|
(br_if $done (i32.or (i32.lt_s (local.get $digit) (i32.const 0)) (i32.ge_s (local.get $digit) (local.get $radix))))
|
|
549
746
|
(local.set $seen (i32.const 1))
|
|
550
|
-
(
|
|
747
|
+
(if (i32.eqz (local.get $ovf))
|
|
748
|
+
(then
|
|
749
|
+
;; acc*radix + digit, exact while it stays within unsigned 64-bit
|
|
750
|
+
(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)))
|
|
751
|
+
(then (local.set $acc (i64.add (i64.mul (local.get $acc) (local.get $rad)) (i64.extend_i32_u (local.get $digit)))))
|
|
752
|
+
(else
|
|
753
|
+
(local.set $ovf (i32.const 1))
|
|
754
|
+
;; non-power-of-two radix: continue round-each in f64 from the exact seed (keeps magnitude)
|
|
755
|
+
(if (i32.eqz (local.get $k)) (then (local.set $f (f64.convert_i64_u (local.get $acc)))))))))
|
|
756
|
+
(if (local.get $ovf)
|
|
757
|
+
(then
|
|
758
|
+
(if (local.get $k)
|
|
759
|
+
(then ;; power-of-two: this digit and all later ones sit below the frozen high bits
|
|
760
|
+
(local.set $exp (i32.add (local.get $exp) (local.get $k)))
|
|
761
|
+
(if (local.get $digit) (then (local.set $sticky (i32.const 1)))))
|
|
762
|
+
(else ;; other radix: round-each f64 — ≤1 ULP past 2^53, but only for >2^64 values
|
|
763
|
+
(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
764
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
552
765
|
(br $lp)))
|
|
553
766
|
;; No digits consumed → NaN
|
|
554
767
|
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
555
|
-
|
|
768
|
+
;; Produce the f64. No overflow → exact round-once convert. Overflow+power-of-two → round the
|
|
769
|
+
;; frozen high bits (sticky-injected to break ties correctly) and scale by 2^exp. Overflow on
|
|
770
|
+
;; another radix → $f already holds the round-each result.
|
|
771
|
+
(if (i32.eqz (local.get $ovf))
|
|
772
|
+
(then (local.set $f (f64.convert_i64_u (local.get $acc))))
|
|
773
|
+
(else (if (local.get $k)
|
|
774
|
+
(then
|
|
775
|
+
(if (local.get $sticky) (then (local.set $acc (i64.or (local.get $acc) (i64.const 1)))))
|
|
776
|
+
(local.set $f (f64.convert_i64_u (local.get $acc)))
|
|
777
|
+
(local.set $e (i32.add (local.get $exp) (i32.const 1023)))
|
|
778
|
+
(if (i32.ge_s (local.get $e) (i32.const 2047))
|
|
779
|
+
(then (local.set $f (f64.const inf)))
|
|
780
|
+
(else (local.set $f (f64.mul (local.get $f) (f64.reinterpret_i64 (i64.shl (i64.extend_i32_u (local.get $e)) (i64.const 52)))))))))))
|
|
781
|
+
(if (result f64) (local.get $neg) (then (f64.neg (local.get $f))) (else (local.get $f))))`
|
|
556
782
|
|
|
557
783
|
// __strws(c: i32) → i32 — ECMA StrWhiteSpace predicate. Covers TAB..CR, SP,
|
|
558
784
|
// NBSP, BOM, LS/PS, and every Unicode Space_Separator. U+180E is *not*
|
|
@@ -613,6 +839,8 @@ export default (ctx) => {
|
|
|
613
839
|
(if (f64.eq (local.get $f) (local.get $f)) (then (return (local.get $f))))
|
|
614
840
|
(if (i64.eq (local.get $v) (i64.const ${NULL_NAN})) (then (return (f64.const 0))))
|
|
615
841
|
(if (i64.eq (local.get $v) (i64.const ${UNDEF_NAN})) (then (return (f64.const nan))))
|
|
842
|
+
(if (i64.eq (local.get $v) (i64.const ${FALSE_NAN})) (then (return (f64.const 0))))
|
|
843
|
+
(if (i64.eq (local.get $v) (i64.const ${TRUE_NAN})) (then (return (f64.const 1))))
|
|
616
844
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
617
845
|
;; ToNumber(Symbol) is a TypeError. A Symbol is an ATOM (type 0) with a user
|
|
618
846
|
;; atom-id (>= 16); null/undefined returned above, and a bare NaN carries
|
|
@@ -854,23 +1082,22 @@ export default (ctx) => {
|
|
|
854
1082
|
// render as "true"/"false" (spec step 1: ToString) before parsing — otherwise
|
|
855
1083
|
// its 0/1 carrier bits are fed to the parser as if a string pointer. Other types
|
|
856
1084
|
// (string already, number/object ToPrimitive) stay out of scope per the runner.
|
|
857
|
-
const strInputI64 = (x) => valTypeOf(x) === VAL.BOOL ? asI64(
|
|
1085
|
+
const strInputI64 = (x) => valTypeOf(x) === VAL.BOOL ? asI64(bool(x)) : asI64(emit(x))
|
|
858
1086
|
|
|
859
1087
|
ctx.core.emit['Number.parseInt'] = (x, radix) => {
|
|
1088
|
+
if (ctx.transform.host === 'wasi') {
|
|
1089
|
+
inc('__parseInt')
|
|
1090
|
+
const radixIR = radix == null ? ['i32.const', 0] : toI32(toNumF64(radix, emit(radix)))
|
|
1091
|
+
return typed(['call', '$__parseInt', strInputI64(x), radixIR], 'f64')
|
|
1092
|
+
}
|
|
860
1093
|
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
1094
|
const radixIR = radix == null ? ['i32.const', 0] : toI32(toNumF64(radix, emit(radix)))
|
|
864
1095
|
return typed(['call', '$__parseInt', strInputI64(x), radixIR], 'f64')
|
|
865
1096
|
}
|
|
866
1097
|
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',
|
|
1098
|
+
const needParseFloat = () => hostImport('env', 'parseFloat',
|
|
872
1099
|
['func', '$__parseFloat', ['param', 'i64'], ['result', 'f64']])
|
|
873
|
-
const needParseInt = () =>
|
|
1100
|
+
const needParseInt = () => hostImport('env', 'parseInt',
|
|
874
1101
|
['func', '$__parseInt', ['param', 'i64'], ['param', 'i32'], ['result', 'f64']])
|
|
875
1102
|
|
|
876
1103
|
ctx.core.emit['Number.parseFloat'] = (x) => {
|
|
@@ -884,32 +1111,44 @@ export default (ctx) => {
|
|
|
884
1111
|
ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
|
|
885
1112
|
|
|
886
1113
|
// Boolean(x) → truthiness (non-zero → 1, zero → 0)
|
|
887
|
-
|
|
1114
|
+
reg('Boolean', ['__is_truthy'], (x) => {
|
|
888
1115
|
if (x === undefined) return typed(['f64.const', 0], 'f64')
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
return typed(['
|
|
892
|
-
}
|
|
1116
|
+
// Via truthyIR so a NUMBER arg gets the NaN-safe f64 test (Boolean(0/0) === false
|
|
1117
|
+
// on every platform); other types fall to __is_truthy inside truthyIR.
|
|
1118
|
+
return typed(['f64.convert_i32_s', truthyIR(emit(x))], 'f64')
|
|
1119
|
+
})
|
|
893
1120
|
|
|
894
1121
|
// === Instance method emitters ===
|
|
895
1122
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1123
|
+
const ftoaDefault = (v) => typed(['call', '$__ftoa', v, ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
1124
|
+
reg('.number:toString', ['__ftoa', '__num_radix'], (n, radix) => {
|
|
1125
|
+
const v = asF64(emit(n))
|
|
1126
|
+
if (radix == null) return ftoaDefault(v)
|
|
1127
|
+
const rv = emit(radix)
|
|
1128
|
+
// Constant radix folds the 10-vs-other choice at compile time; radix 10 keeps
|
|
1129
|
+
// __ftoa's shortest-repr (the radix loop would emit float-noise tail digits).
|
|
1130
|
+
if (Array.isArray(rv) && rv[0] === 'f64.const' && typeof rv[1] === 'number')
|
|
1131
|
+
return rv[1] === 10 ? ftoaDefault(v) : typed(['call', '$__num_radix', v, ['i32.const', rv[1] | 0]], 'f64')
|
|
1132
|
+
const vt = temp('rv'), rt = tempI32('rr')
|
|
1133
|
+
return typed(['block', ['result', 'f64'],
|
|
1134
|
+
['local.set', `$${vt}`, v],
|
|
1135
|
+
['local.set', `$${rt}`, asI32(rv)],
|
|
1136
|
+
['if', ['result', 'f64'], ['i32.eq', ['local.get', `$${rt}`], ['i32.const', 10]],
|
|
1137
|
+
['then', ftoaDefault(['local.get', `$${vt}`])],
|
|
1138
|
+
['else', ['call', '$__num_radix', ['local.get', `$${vt}`], ['local.get', `$${rt}`]]]]], 'f64')
|
|
1139
|
+
})
|
|
900
1140
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
1141
|
+
// BigInt.prototype.toString(radix) — i64-exact, default radix 10.
|
|
1142
|
+
reg('.bigint:toString', ['__radix_str'], (n, radix) =>
|
|
1143
|
+
typed(['call', '$__radix_str', asI64(emit(n)), radix == null ? ['i32.const', 10] : asI32(emit(radix))], 'f64'))
|
|
905
1144
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
return typed(['call', '$__toExp', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 0]], 'f64')
|
|
909
|
-
}
|
|
1145
|
+
reg('.number:toFixed', ['__ftoa'], (n, d) =>
|
|
1146
|
+
typed(['call', '$__ftoa', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 1]], 'f64'))
|
|
910
1147
|
|
|
911
|
-
|
|
912
|
-
|
|
1148
|
+
reg('.number:toExponential', ['__toExp'], (n, d) =>
|
|
1149
|
+
typed(['call', '$__toExp', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 0]], 'f64'))
|
|
1150
|
+
|
|
1151
|
+
reg('.number:toPrecision', ['__ftoa', '__toExp'], (n, p) => {
|
|
913
1152
|
const val = temp('pv'), t = temp('tp'), exp = tempI32('te'), pr = tempI32('pp')
|
|
914
1153
|
return typed(['block', ['result', 'f64'],
|
|
915
1154
|
['local.set', `$${val}`, asF64(emit(n))],
|
|
@@ -936,7 +1175,7 @@ export default (ctx) => {
|
|
|
936
1175
|
['else', ['call', '$__ftoa', ['local.get', `$${val}`],
|
|
937
1176
|
['i32.sub', ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]], ['local.get', `$${exp}`]],
|
|
938
1177
|
['i32.const', 1]]]]], 'f64')
|
|
939
|
-
}
|
|
1178
|
+
})
|
|
940
1179
|
|
|
941
1180
|
// Number(x) — identity for numbers, i64→f64 conversion for BigInt
|
|
942
1181
|
ctx.core.emit['Number'] = (x) => {
|
|
@@ -963,7 +1202,7 @@ export default (ctx) => {
|
|
|
963
1202
|
['if', ['result', 'f64'], ['f64.eq', ['local.get', `$${t}`], ['local.get', `$${t}`]],
|
|
964
1203
|
['then', ['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', ['local.get', `$${t}`]]]],
|
|
965
1204
|
['else', ['if', ['result', 'f64'],
|
|
966
|
-
['
|
|
1205
|
+
ptrTypeEq(['local.get', `$${t}`], PTR.STRING),
|
|
967
1206
|
['then', ['call', '$__to_bigint', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
968
1207
|
['else', ['local.get', `$${t}`]]]]]], 'f64')
|
|
969
1208
|
}
|
|
@@ -986,11 +1225,18 @@ export default (ctx) => {
|
|
|
986
1225
|
['i64.shr_s', ['i64.shl', vval, ['local.get', `$${t}`]], ['local.get', `$${t}`]]]], 'f64')
|
|
987
1226
|
}
|
|
988
1227
|
|
|
989
|
-
// BigInt.asUintN(bits, bigint) — truncate to unsigned N-bit
|
|
1228
|
+
// BigInt.asUintN(bits, bigint) — truncate to unsigned N-bit.
|
|
1229
|
+
// (val << (64 - bits)) >>> (64 - bits) — logical shift zero-extends the low `bits`.
|
|
1230
|
+
// The naive `val & ((1 << bits) - 1)` mask is wrong at bits=64: i64.shl shifts mod 64,
|
|
1231
|
+
// so `1 << 64` is `1 << 0 = 1`, making the mask `0` and asUintN(64,·) collapse to 0 —
|
|
1232
|
+
// which also zeroed every bigint *literal* (emit reinterprets `Nn` via
|
|
1233
|
+
// BigInt.asUintN(64,·).toString() through this very handler in the self-host).
|
|
990
1234
|
ctx.core.emit['BigInt.asUintN'] = (bits, val) => {
|
|
991
1235
|
const vbits = asI32(emit(bits)), vval = asI64(emit(val))
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1236
|
+
const shift = typed(['i64.sub', ['i64.const', 64], ['i64.extend_i32_s', vbits]], 'i64')
|
|
1237
|
+
const t = tempI64('bu')
|
|
1238
|
+
return typed(['f64.reinterpret_i64', ['block', ['result', 'i64'],
|
|
1239
|
+
['local.set', `$${t}`, shift],
|
|
1240
|
+
['i64.shr_u', ['i64.shl', vval, ['local.get', `$${t}`]], ['local.get', `$${t}`]]]], 'f64')
|
|
995
1241
|
}
|
|
996
1242
|
}
|