jz 0.0.0 → 0.1.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.
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Number module — toString, toFixed, toPrecision, toExponential, String().
3
+ *
4
+ * Core: __ftoa(f64, precision, mode) → f64 (NaN-boxed string pointer).
5
+ * Modes: 0=default (shortest repr), 1=fixed (toFixed).
6
+ * Uses integer-based digit extraction to avoid float drift.
7
+ * Static string table at address 0 for NaN, Infinity, etc.
8
+ *
9
+ * @module number
10
+ */
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'
14
+ import { valTypeOf, VAL } from '../src/analyze.js'
15
+ import { inc, PTR } from '../src/ctx.js'
16
+
17
+ export default (ctx) => {
18
+ Object.assign(ctx.core.stdlibDeps, {
19
+ __mkstr: ['__alloc'],
20
+ __ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str'],
21
+ __toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
22
+ __to_num: ['__char_at', '__str_byteLen', '__pow10'],
23
+ __parseInt: ['__char_at', '__str_byteLen'],
24
+ })
25
+
26
+
27
+ // __pow10(n: i32) → f64 — compute 10^n via loop
28
+ ctx.core.stdlib['__pow10'] = `(func $__pow10 (param $n i32) (result f64)
29
+ (local $r f64)
30
+ (local.set $r (f64.const 1))
31
+ (block $d (loop $l
32
+ (br_if $d (i32.le_s (local.get $n) (i32.const 0)))
33
+ (local.set $r (f64.mul (local.get $r) (f64.const 10)))
34
+ (local.set $n (i32.sub (local.get $n) (i32.const 1)))
35
+ (br $l)))
36
+ (local.get $r))`
37
+
38
+ // __itoa(val: i32, buf: i32) → i32 (digit count). Writes decimal digits to buf.
39
+ ctx.core.stdlib['__itoa'] = `(func $__itoa (param $val i32) (param $buf i32) (result i32)
40
+ (local $len i32) (local $i i32) (local $j i32) (local $tmp i32)
41
+ (if (i32.eqz (local.get $val))
42
+ (then (i32.store8 (local.get $buf) (i32.const 48)) (return (i32.const 1))))
43
+ (local.set $tmp (local.get $val))
44
+ (block $d (loop $l
45
+ (br_if $d (i32.eqz (local.get $tmp)))
46
+ (i32.store8 (i32.add (local.get $buf) (local.get $len))
47
+ (i32.add (i32.const 48) (i32.rem_u (local.get $tmp) (i32.const 10))))
48
+ (local.set $tmp (i32.div_u (local.get $tmp) (i32.const 10)))
49
+ (local.set $len (i32.add (local.get $len) (i32.const 1)))
50
+ (br $l)))
51
+ ;; Reverse
52
+ (local.set $j (i32.sub (local.get $len) (i32.const 1)))
53
+ (block $rd (loop $rl
54
+ (br_if $rd (i32.ge_s (local.get $i) (local.get $j)))
55
+ (local.set $tmp (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
56
+ (i32.store8 (i32.add (local.get $buf) (local.get $i))
57
+ (i32.load8_u (i32.add (local.get $buf) (local.get $j))))
58
+ (i32.store8 (i32.add (local.get $buf) (local.get $j)) (local.get $tmp))
59
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
60
+ (local.set $j (i32.sub (local.get $j) (i32.const 1)))
61
+ (br $rl)))
62
+ (local.get $len))`
63
+
64
+ // __mkstr(buf: i32, len: i32) → f64 — copy scratch buffer to heap string.
65
+ // Hot (~60M calls in watr self-host via __ftoa). bulk memory.copy is ~10× faster than
66
+ // a hand-rolled byte loop (wasm2c lowers it to memcpy under PGO+LTO).
67
+ ctx.core.stdlib['__mkstr'] = `(func $__mkstr (param $buf i32) (param $len i32) (result f64)
68
+ (local $off i32)
69
+ (local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
70
+ (i32.store (local.get $off) (local.get $len))
71
+ (local.set $off (i32.add (local.get $off) (i32.const 4)))
72
+ (memory.copy (local.get $off) (local.get $buf) (local.get $len))
73
+ (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
74
+
75
+ // __ftoa(val: f64, prec: i32, mode: i32) → f64 (NaN-boxed string)
76
+ // mode 0: default (shortest repr, strip trailing zeros)
77
+ // mode 1: fixed (exactly prec decimal places)
78
+ // Uses integer-scaled digit extraction to avoid float drift.
79
+ ctx.core.stdlib['__ftoa'] = `(func $__ftoa (param $val f64) (param $prec i32) (param $mode i32) (result f64)
80
+ (local $buf i32) (local $pos i32) (local $neg i32)
81
+ (local $abs f64) (local $scale f64) (local $scaled f64)
82
+ (local $int i32) (local $frac i32) (local $ilen i32) (local $flen i32)
83
+ (local $i i32) (local $j i32)
84
+ ;; Special values
85
+ (if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
86
+ (if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
87
+ (if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
88
+ (local.set $buf (call $__alloc (i32.const 40)))
89
+ ;; Sign
90
+ (if (f64.lt (local.get $val) (f64.const 0))
91
+ (then (local.set $neg (i32.const 1)) (local.set $val (f64.neg (local.get $val)))))
92
+ (if (i32.and (f64.eq (local.get $val) (f64.const 0)) (local.get $neg))
93
+ (then (local.set $neg (i32.const 0))))
94
+ (if (local.get $neg)
95
+ (then (i32.store8 (local.get $buf) (i32.const 45))
96
+ (local.set $pos (i32.const 1))))
97
+ ;; Default mode: auto-select precision (up to 9 digits, must fit i32 when scaled)
98
+ (if (i32.eqz (local.get $mode))
99
+ (then (local.set $prec (i32.const 9))))
100
+ ;; Round and scale to integer: scaled = nearest(val * 10^prec)
101
+ (local.set $scale (call $__pow10 (local.get $prec)))
102
+ (local.set $scaled (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
103
+ ;; If scaled doesn't fit i32, reduce precision until it does (min prec=0)
104
+ (block $fit (loop $fitl
105
+ (br_if $fit (f64.lt (local.get $scaled) (f64.const 2147483648)))
106
+ (br_if $fit (i32.le_s (local.get $prec) (i32.const 0)))
107
+ (local.set $prec (i32.sub (local.get $prec) (i32.const 1)))
108
+ (local.set $scale (call $__pow10 (local.get $prec)))
109
+ (local.set $scaled (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
110
+ (br $fitl)))
111
+ ;; Split: int = scaled / scale, frac = scaled % scale
112
+ (if (f64.lt (local.get $scaled) (f64.const 2147483648))
113
+ (then
114
+ (local.set $int (i32.trunc_f64_u (f64.div (local.get $scaled) (local.get $scale))))
115
+ (local.set $frac (i32.trunc_f64_u (f64.sub (local.get $scaled)
116
+ (f64.mul (f64.convert_i32_u (local.get $int)) (local.get $scale))))))
117
+ (else
118
+ (local.set $int (i32.const 0))
119
+ (local.set $frac (i32.const 0))
120
+ (local.set $prec (i32.const 0))
121
+ (local.set $abs (f64.trunc (local.get $val)))
122
+ ;; Write large integer digits reversed
123
+ (local.set $ilen (local.get $pos))
124
+ (block $ld (loop $ll
125
+ (br_if $ld (f64.lt (local.get $abs) (f64.const 1)))
126
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
127
+ (i32.add (i32.const 48) (i32.trunc_f64_u (f64.sub (local.get $abs)
128
+ (f64.mul (f64.trunc (f64.div (local.get $abs) (f64.const 10))) (f64.const 10))))))
129
+ (local.set $abs (f64.trunc (f64.div (local.get $abs) (f64.const 10))))
130
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
131
+ (br $ll)))
132
+ ;; Reverse
133
+ (local.set $i (local.get $ilen)) (local.set $j (i32.sub (local.get $pos) (i32.const 1)))
134
+ (block $rd (loop $rl
135
+ (br_if $rd (i32.ge_s (local.get $i) (local.get $j)))
136
+ (local.set $int (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
137
+ (i32.store8 (i32.add (local.get $buf) (local.get $i))
138
+ (i32.load8_u (i32.add (local.get $buf) (local.get $j))))
139
+ (i32.store8 (i32.add (local.get $buf) (local.get $j)) (local.get $int))
140
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
141
+ (local.set $j (i32.sub (local.get $j) (i32.const 1)))
142
+ (br $rl)))
143
+ (return (call $__mkstr (local.get $buf) (local.get $pos)))))
144
+ ;; Write integer part
145
+ (local.set $ilen (call $__itoa (local.get $int) (i32.add (local.get $buf) (local.get $pos))))
146
+ (local.set $pos (i32.add (local.get $pos) (local.get $ilen)))
147
+ ;; Write fractional part: extract digits from $frac by dividing by 10^(prec-1), 10^(prec-2), ...
148
+ (if (i32.gt_s (local.get $prec) (i32.const 0))
149
+ (then
150
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
151
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
152
+ (local.set $i (i32.sub (local.get $prec) (i32.const 1)))
153
+ (block $fd (loop $fl
154
+ (br_if $fd (i32.lt_s (local.get $i) (i32.const 0)))
155
+ (local.set $j (i32.div_u (local.get $frac) (i32.trunc_f64_u (call $__pow10 (local.get $i)))))
156
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
157
+ (i32.add (i32.const 48) (i32.rem_u (local.get $j) (i32.const 10))))
158
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
159
+ (local.set $i (i32.sub (local.get $i) (i32.const 1)))
160
+ (br $fl)))))
161
+ ;; Default mode: strip trailing zeros and dot — only when a fractional part was emitted.
162
+ ;; Gating on $prec>0 prevents stripping zeros from the integer part (e.g. 1079623680 → 107962368)
163
+ ;; for values where auto-fit reduced prec to 0 because the scaled integer wouldn't fit i32.
164
+ (if (i32.and (i32.eqz (local.get $mode)) (i32.gt_s (local.get $prec) (i32.const 0)))
165
+ (then
166
+ (block $sd (loop $sl
167
+ (br_if $sd (i32.le_s (local.get $pos) (i32.const 0)))
168
+ (br_if $sd (i32.ne (i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1)))) (i32.const 48)))
169
+ (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))
170
+ (br $sl)))
171
+ (if (i32.and (i32.gt_s (local.get $pos) (i32.const 0))
172
+ (i32.eq (i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1)))) (i32.const 46)))
173
+ (then (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))))))
174
+ (call $__mkstr (local.get $buf) (local.get $pos)))`
175
+
176
+ // __toExp(val: f64, prec: i32) → f64 (NaN-boxed string)
177
+ // Format: [-]d.ddd...e[+/-]dd — integer-based digit extraction
178
+ ctx.core.stdlib['__toExp'] = `(func $__toExp (param $val f64) (param $prec i32) (result f64)
179
+ (local $buf i32) (local $pos i32) (local $neg i32) (local $exp i32)
180
+ (local $len i32) (local $i i32) (local $j i32)
181
+ (local $mantissa f64) (local $scale f64)
182
+ (if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
183
+ (if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
184
+ (if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
185
+ (local.set $buf (call $__alloc (i32.const 32)))
186
+ ;; Sign
187
+ (if (f64.lt (local.get $val) (f64.const 0))
188
+ (then (local.set $neg (i32.const 1)) (local.set $val (f64.neg (local.get $val)))))
189
+ (if (i32.and (f64.eq (local.get $val) (f64.const 0)) (local.get $neg))
190
+ (then (local.set $neg (i32.const 0))))
191
+ (if (local.get $neg)
192
+ (then (i32.store8 (local.get $buf) (i32.const 45))
193
+ (local.set $pos (i32.const 1))))
194
+ ;; Normalize: 1 <= val < 10
195
+ (if (f64.gt (local.get $val) (f64.const 0))
196
+ (then
197
+ (block $d1 (loop $l1
198
+ (br_if $d1 (f64.lt (local.get $val) (f64.const 10)))
199
+ (local.set $val (f64.div (local.get $val) (f64.const 10)))
200
+ (local.set $exp (i32.add (local.get $exp) (i32.const 1)))
201
+ (br $l1)))
202
+ (block $d2 (loop $l2
203
+ (br_if $d2 (f64.ge (local.get $val) (f64.const 1)))
204
+ (local.set $val (f64.mul (local.get $val) (f64.const 10)))
205
+ (local.set $exp (i32.sub (local.get $exp) (i32.const 1)))
206
+ (br $l2)))))
207
+ ;; Scale to integer mantissa: nearest(val * 10^prec)
208
+ (local.set $scale (call $__pow10 (local.get $prec)))
209
+ (local.set $mantissa (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
210
+ ;; Rounding overflow (e.g. 9.95 → 1000 when prec=1, scale=10)
211
+ (if (f64.ge (local.get $mantissa) (f64.mul (f64.const 10) (local.get $scale)))
212
+ (then
213
+ (local.set $mantissa (f64.div (local.get $mantissa) (f64.const 10)))
214
+ (local.set $exp (i32.add (local.get $exp) (i32.const 1)))))
215
+ ;; Write mantissa digits via itoa
216
+ (local.set $len (call $__itoa (i32.trunc_f64_u (local.get $mantissa)) (i32.add (local.get $buf) (local.get $pos))))
217
+ ;; Insert '.' after first digit
218
+ (if (i32.gt_s (local.get $prec) (i32.const 0))
219
+ (then
220
+ (local.set $i (local.get $len))
221
+ (block $md (loop $ml
222
+ (br_if $md (i32.le_s (local.get $i) (i32.const 1)))
223
+ (i32.store8 (i32.add (local.get $buf) (i32.add (local.get $pos) (local.get $i)))
224
+ (i32.load8_u (i32.add (local.get $buf) (i32.add (local.get $pos) (i32.sub (local.get $i) (i32.const 1))))))
225
+ (local.set $i (i32.sub (local.get $i) (i32.const 1)))
226
+ (br $ml)))
227
+ (i32.store8 (i32.add (local.get $buf) (i32.add (local.get $pos) (i32.const 1))) (i32.const 46))
228
+ (local.set $pos (i32.add (local.get $pos) (i32.add (local.get $len) (i32.const 1)))))
229
+ (else (local.set $pos (i32.add (local.get $pos) (local.get $len)))))
230
+ ;; Write 'e', sign, exponent
231
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 101))
232
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
233
+ (if (i32.lt_s (local.get $exp) (i32.const 0))
234
+ (then (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 45))
235
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
236
+ (local.set $exp (i32.sub (i32.const 0) (local.get $exp))))
237
+ (else (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 43))
238
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))))
239
+ (local.set $pos (i32.add (local.get $pos) (call $__itoa (local.get $exp) (i32.add (local.get $buf) (local.get $pos)))))
240
+ (call $__mkstr (local.get $buf) (local.get $pos)))`
241
+
242
+ // __static_str(id: i32) → f64 — create heap string from data segment
243
+ // 0=NaN 1=Infinity 2=-Infinity 3=true 4=false 5=null 6=undefined 7=[Array] 8=[Object]
244
+ ctx.core.stdlib['__static_str'] = `(func $__static_str (param $id i32) (result f64)
245
+ (local $src i32) (local $len i32)
246
+ (local.set $src (i32.const 0)) (local.set $len (i32.const 0))
247
+ (if (i32.eqz (local.get $id)) (then (local.set $len (i32.const 3))))
248
+ (if (i32.eq (local.get $id) (i32.const 1)) (then (local.set $src (i32.const 3)) (local.set $len (i32.const 8))))
249
+ (if (i32.eq (local.get $id) (i32.const 2)) (then (local.set $src (i32.const 11)) (local.set $len (i32.const 9))))
250
+ (if (i32.eq (local.get $id) (i32.const 3)) (then (local.set $src (i32.const 20)) (local.set $len (i32.const 4))))
251
+ (if (i32.eq (local.get $id) (i32.const 4)) (then (local.set $src (i32.const 24)) (local.set $len (i32.const 5))))
252
+ (if (i32.eq (local.get $id) (i32.const 5)) (then (local.set $src (i32.const 29)) (local.set $len (i32.const 4))))
253
+ (if (i32.eq (local.get $id) (i32.const 6)) (then (local.set $src (i32.const 33)) (local.set $len (i32.const 9))))
254
+ (if (i32.eq (local.get $id) (i32.const 7)) (then (local.set $src (i32.const 42)) (local.set $len (i32.const 7))))
255
+ (if (i32.eq (local.get $id) (i32.const 8)) (then (local.set $src (i32.const 49)) (local.set $len (i32.const 8))))
256
+ (call $__mkstr (local.get $src) (local.get $len)))`
257
+
258
+ // R: Static strings seeded at address 0. Compile.js strips if __static_str unused.
259
+ // 0=NaN 1=Infinity 2=-Infinity 3=true 4=false 5=null 6=undefined 7=[Array] 8=[Object]
260
+ const staticStr = 'NaNInfinity-Infinitytruefalsenullundefined[Array][Object]'
261
+ ctx.runtime.staticDataLen = staticStr.length
262
+ ctx.runtime.data = (ctx.runtime.data || '') + staticStr
263
+
264
+ // === Number constants ===
265
+
266
+ ctx.core.emit['Number.MAX_SAFE_INTEGER'] = () => typed(['f64.const', 9007199254740991], 'f64')
267
+ ctx.core.emit['Number.MIN_SAFE_INTEGER'] = () => typed(['f64.const', -9007199254740991], 'f64')
268
+ ctx.core.emit['Number.EPSILON'] = () => typed(['f64.const', 2.220446049250313e-16], 'f64')
269
+ ctx.core.emit['Number.MAX_VALUE'] = () => typed(['f64.const', 1.7976931348623157e+308], 'f64')
270
+ ctx.core.emit['Number.MIN_VALUE'] = () => typed(['f64.const', 5e-324], 'f64')
271
+ ctx.core.emit['Number.POSITIVE_INFINITY'] = () => typed(['f64.const', Infinity], 'f64')
272
+ ctx.core.emit['Number.NEGATIVE_INFINITY'] = () => typed(['f64.const', -Infinity], 'f64')
273
+ ctx.core.emit['Number.NaN'] = () => typed(['f64.const', NaN], 'f64')
274
+
275
+ // === Number static methods ===
276
+
277
+ const emitIsNaN = (x) => {
278
+ const v = asF64(emit(x))
279
+ const t = temp('t')
280
+ return typed(['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]], 'i32')
281
+ }
282
+
283
+ const emitIsFinite = (x) => {
284
+ const v = asF64(emit(x))
285
+ const t = temp('t')
286
+ return typed(['i32.and',
287
+ ['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
288
+ ['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]], 'i32')
289
+ }
290
+
291
+ ctx.core.emit['Number.isNaN'] = emitIsNaN
292
+ ctx.core.emit['Number.isFinite'] = emitIsFinite
293
+
294
+ // Global isNaN/isFinite — coerce string→number first (unlike Number.isNaN/isFinite)
295
+ ctx.core.emit['isNaN'] = (x) => {
296
+ inc('__to_num')
297
+ const v = asF64(emit(x))
298
+ const t = temp('t')
299
+ return typed(['f64.ne',
300
+ ['local.tee', `$${t}`, ['call', '$__to_num', v]],
301
+ ['local.get', `$${t}`]], 'i32')
302
+ }
303
+ ctx.core.emit['isFinite'] = (x) => {
304
+ inc('__to_num')
305
+ const v = asF64(emit(x))
306
+ const t = temp('t')
307
+ return typed(['i32.and',
308
+ ['f64.eq', ['local.tee', `$${t}`, ['call', '$__to_num', v]], ['local.get', `$${t}`]],
309
+ ['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]], 'i32')
310
+ }
311
+
312
+ ctx.core.emit['Number.isInteger'] = (x) => {
313
+ const v = asF64(emit(x))
314
+ const t = temp('t')
315
+ return typed(['i32.and',
316
+ ['i32.and',
317
+ ['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
318
+ ['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]],
319
+ ['f64.eq', ['local.get', `$${t}`], ['f64.trunc', ['local.get', `$${t}`]]]], 'i32')
320
+ }
321
+
322
+ // parseInt(str, radix) — parse string to integer
323
+ ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str f64) (param $radix i32) (result f64)
324
+ (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)
326
+ ;; If input is a number, just truncate
327
+ (if (f64.eq (local.get $str) (local.get $str)) (then (return (f64.trunc (local.get $str)))))
328
+ ;; If NaN-boxed but not a string type (4=heap,5=SSO) → return NaN
329
+ (if (i32.and
330
+ (i32.ne (call $__ptr_type (local.get $str)) (i32.const 4))
331
+ (i32.ne (call $__ptr_type (local.get $str)) (i32.const 5)))
332
+ (then (return (f64.const nan))))
333
+ (local.set $off (call $__ptr_offset (local.get $str)))
334
+ (local.set $len (call $__str_byteLen (local.get $str)))
335
+ (local.set $i (i32.const 0))
336
+ ;; Skip whitespace
337
+ (block $ws (loop $wsl
338
+ (br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
339
+ (br_if $ws (i32.gt_s (call $__char_at (local.get $str) (local.get $i)) (i32.const 32)))
340
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
341
+ (br $wsl)))
342
+ ;; Sign
343
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
344
+ (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 45)))
345
+ (then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
346
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
347
+ (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 43)))
348
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
349
+ ;; 0x prefix → radix 16
350
+ (if (i32.and (i32.eqz (local.get $radix))
351
+ (i32.and (i32.le_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
352
+ (i32.and (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 48))
353
+ (i32.or (i32.eq (call $__char_at (local.get $str) (i32.add (local.get $i) (i32.const 1))) (i32.const 120))
354
+ (i32.eq (call $__char_at (local.get $str) (i32.add (local.get $i) (i32.const 1))) (i32.const 88))))))
355
+ (then (local.set $radix (i32.const 16)) (local.set $i (i32.add (local.get $i) (i32.const 2)))))
356
+ (if (i32.eqz (local.get $radix)) (then (local.set $radix (i32.const 10))))
357
+ ;; Parse digits
358
+ (local.set $result (f64.const 0))
359
+ (block $done (loop $lp
360
+ (br_if $done (i32.ge_s (local.get $i) (local.get $len)))
361
+ (local.set $c (call $__char_at (local.get $str) (local.get $i)))
362
+ ;; Digit value
363
+ (local.set $digit (i32.const -1))
364
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
365
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 48)))))
366
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 97)) (i32.le_s (local.get $c) (i32.const 122)))
367
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 87)))))
368
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 65)) (i32.le_s (local.get $c) (i32.const 90)))
369
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 55)))))
370
+ (br_if $done (i32.or (i32.lt_s (local.get $digit) (i32.const 0)) (i32.ge_s (local.get $digit) (local.get $radix))))
371
+ (local.set $seen (i32.const 1))
372
+ (local.set $result (f64.add (f64.mul (local.get $result) (f64.convert_i32_s (local.get $radix))) (f64.convert_i32_s (local.get $digit))))
373
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
374
+ (br $lp)))
375
+ ;; No digits consumed → NaN
376
+ (if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
377
+ (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
378
+
379
+ ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v f64) (result f64)
380
+ (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 $result f64) (local $scale f64)
383
+ (if (f64.eq (local.get $v) (local.get $v)) (then (return (local.get $v))))
384
+ (if (i64.eq (i64.reinterpret_f64 (local.get $v)) (i64.const ${NULL_NAN})) (then (return (f64.const 0))))
385
+ (if (i64.eq (i64.reinterpret_f64 (local.get $v)) (i64.const ${UNDEF_NAN})) (then (return (f64.const nan))))
386
+ (local.set $t (call $__ptr_type (local.get $v)))
387
+ (if (i32.eqz
388
+ (i32.or
389
+ (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
390
+ (i32.eq (local.get $t) (i32.const ${PTR.SSO}))))
391
+ (then (return (f64.const nan))))
392
+ (local.set $len (call $__str_byteLen (local.get $v)))
393
+ ;; Skip leading whitespace.
394
+ (block $ws (loop $wsl
395
+ (br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
396
+ (br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
397
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
398
+ (br $wsl)))
399
+ ;; Sign.
400
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
401
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
402
+ (then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
403
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
404
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
405
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
406
+ ;; 0x prefix → hex parse and early return
407
+ (if (i32.and
408
+ (i32.le_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
409
+ (i32.and (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 48))
410
+ (i32.or (i32.eq (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 120))
411
+ (i32.eq (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 88)))))
412
+ (then
413
+ (local.set $i (i32.add (local.get $i) (i32.const 2)))
414
+ (block $hexDone (loop $hexLoop
415
+ (br_if $hexDone (i32.ge_s (local.get $i) (local.get $len)))
416
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
417
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
418
+ (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 48)))))
419
+ (local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))
420
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 97)) (i32.le_s (local.get $c) (i32.const 102)))
421
+ (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 87)))))
422
+ (local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))
423
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 65)) (i32.le_s (local.get $c) (i32.const 70)))
424
+ (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
+ (local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))))
426
+ (return (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))))
427
+ ;; Integer part.
428
+ (block $intDone (loop $intLoop
429
+ (br_if $intDone (i32.ge_s (local.get $i) (local.get $len)))
430
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
431
+ (br_if $intDone
432
+ (i32.or
433
+ (i32.lt_s (local.get $c) (i32.const 48))
434
+ (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
+ (local.set $seen (i32.const 1))
440
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
441
+ (br $intLoop)))
442
+ ;; Fractional part.
443
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
444
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 46)))
445
+ (then
446
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
447
+ (local.set $scale (f64.const 0.1))
448
+ (block $fracDone (loop $fracLoop
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))))
455
+ (local.set $result
456
+ (f64.add
457
+ (local.get $result)
458
+ (f64.mul
459
+ (f64.convert_i32_s (i32.sub (local.get $c) (i32.const 48)))
460
+ (local.get $scale))))
461
+ (local.set $scale (f64.mul (local.get $scale) (f64.const 0.1)))
462
+ (local.set $seen (i32.const 1))
463
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
464
+ (br $fracLoop)))))
465
+ (if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
466
+ ;; Scientific notation.
467
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
468
+ (i32.or
469
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 101))
470
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 69))))
471
+ (then
472
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
473
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
474
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
475
+ (then (local.set $expNeg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
476
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
477
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
478
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
479
+ (block $expDone (loop $expLoop
480
+ (br_if $expDone (i32.ge_s (local.get $i) (local.get $len)))
481
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
482
+ (br_if $expDone
483
+ (i32.or
484
+ (i32.lt_s (local.get $c) (i32.const 48))
485
+ (i32.gt_s (local.get $c) (i32.const 57))))
486
+ (local.set $exp
487
+ (i32.add
488
+ (i32.mul (local.get $exp) (i32.const 10))
489
+ (i32.sub (local.get $c) (i32.const 48))))
490
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
491
+ (br $expLoop)))
492
+ (if (local.get $expNeg)
493
+ (then (local.set $result (f64.div (local.get $result) (call $__pow10 (local.get $exp)))))
494
+ (else (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $exp))))))))
495
+ (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
496
+
497
+ ctx.core.emit['Number.parseInt'] = (x, radix) => {
498
+ inc('__parseInt')
499
+ return typed(['call', '$__parseInt', asF64(emit(x)), radix ? asI32(emit(radix)) : ['i32.const', 0]], 'f64')
500
+ }
501
+ ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
502
+ ctx.core.emit['Number.parseFloat'] = (x) => {
503
+ inc('__to_num')
504
+ return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
505
+ }
506
+ ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
507
+
508
+ // Boolean(x) → truthiness (non-zero → 1, zero → 0)
509
+ ctx.core.emit['Boolean'] = (x) => {
510
+ inc('__is_truthy')
511
+ const v = asF64(emit(x))
512
+ return typed(['if', ['result', 'f64'], ['call', '$__is_truthy', v], ['then', ['f64.const', 1]], ['else', ['f64.const', 0]]], 'f64')
513
+ }
514
+
515
+ // === Instance method emitters ===
516
+
517
+ ctx.core.emit['.number:toString'] = (n) => {
518
+ inc('__ftoa')
519
+ return typed(['call', '$__ftoa', asF64(emit(n)), ['i32.const', 0], ['i32.const', 0]], 'f64')
520
+ }
521
+
522
+ ctx.core.emit['.number:toFixed'] = (n, d) => {
523
+ inc('__ftoa')
524
+ return typed(['call', '$__ftoa', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 1]], 'f64')
525
+ }
526
+
527
+ ctx.core.emit['.number:toExponential'] = (n, d) => {
528
+ inc('__toExp')
529
+ return typed(['call', '$__toExp', asF64(emit(n)), asI32(emit(d || [, 0]))], 'f64')
530
+ }
531
+
532
+ ctx.core.emit['.number:toPrecision'] = (n, p) => {
533
+ inc('__ftoa', '__toExp')
534
+ const val = temp('pv'), t = temp('tp'), exp = tempI32('te'), pr = tempI32('pp')
535
+ return typed(['block', ['result', 'f64'],
536
+ ['local.set', `$${val}`, asF64(emit(n))],
537
+ ['local.set', `$${pr}`, asI32(emit(p))],
538
+ ['local.set', `$${t}`, ['f64.abs', ['local.get', `$${val}`]]],
539
+ ['local.set', `$${exp}`, ['i32.const', 0]],
540
+ ['if', ['f64.gt', ['local.get', `$${t}`], ['f64.const', 0]],
541
+ ['then',
542
+ ['block', '$d1', ['loop', '$l1',
543
+ ['br_if', '$d1', ['f64.lt', ['local.get', `$${t}`], ['f64.const', 10]]],
544
+ ['local.set', `$${t}`, ['f64.div', ['local.get', `$${t}`], ['f64.const', 10]]],
545
+ ['local.set', `$${exp}`, ['i32.add', ['local.get', `$${exp}`], ['i32.const', 1]]],
546
+ ['br', '$l1']]],
547
+ ['block', '$d2', ['loop', '$l2',
548
+ ['br_if', '$d2', ['f64.ge', ['local.get', `$${t}`], ['f64.const', 1]]],
549
+ ['local.set', `$${t}`, ['f64.mul', ['local.get', `$${t}`], ['f64.const', 10]]],
550
+ ['local.set', `$${exp}`, ['i32.sub', ['local.get', `$${exp}`], ['i32.const', 1]]],
551
+ ['br', '$l2']]]]],
552
+ ['if', ['result', 'f64'],
553
+ ['i32.or',
554
+ ['i32.lt_s', ['local.get', `$${exp}`], ['i32.const', -6]],
555
+ ['i32.ge_s', ['local.get', `$${exp}`], ['local.get', `$${pr}`]]],
556
+ ['then', ['call', '$__toExp', ['local.get', `$${val}`], ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]]]],
557
+ ['else', ['call', '$__ftoa', ['local.get', `$${val}`],
558
+ ['i32.sub', ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]], ['local.get', `$${exp}`]],
559
+ ['i32.const', 1]]]]], 'f64')
560
+ }
561
+
562
+ // Number(x) — identity for numbers, i64→f64 conversion for BigInt
563
+ ctx.core.emit['Number'] = (x) => {
564
+ if (valTypeOf(x) === VAL.BIGINT)
565
+ return typed(['f64.convert_i64_s', asI64(emit(x))], 'f64')
566
+ inc('__to_num')
567
+ return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
568
+ }
569
+
570
+ // BigInt(x) — f64→i64 conversion (reinterpret as BigInt-as-f64).
571
+ // For number input: truncate directly. For string / unknown: first coerce via __to_num
572
+ // (handles both decimal and hex string parse), then truncate.
573
+ ctx.core.emit['BigInt'] = (x) => {
574
+ const vt = valTypeOf(x)
575
+ if (vt === VAL.BIGINT) return emit(x)
576
+ if (vt === VAL.NUMBER)
577
+ return typed(['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', asF64(emit(x))]], 'f64')
578
+ inc('__to_num')
579
+ return typed(['f64.reinterpret_i64',
580
+ ['i64.trunc_sat_f64_s', ['call', '$__to_num', asF64(emit(x))]]], 'f64')
581
+ }
582
+
583
+ // BigInt.asIntN(bits, bigint) — truncate to signed N-bit
584
+ ctx.core.emit['BigInt.asIntN'] = (bits, val) => {
585
+ const vbits = asI32(emit(bits)), vval = asI64(emit(val))
586
+ // (val << (64 - bits)) >> (64 - bits) — arithmetic shift for sign extension
587
+ const shift = typed(['i64.sub', ['i64.const', 64], ['i64.extend_i32_s', vbits]], 'i64')
588
+ const t = tempI64('bi')
589
+ return typed(['f64.reinterpret_i64', ['block', ['result', 'i64'],
590
+ ['local.set', `$${t}`, shift],
591
+ ['i64.shr_s', ['i64.shl', vval, ['local.get', `$${t}`]], ['local.get', `$${t}`]]]], 'f64')
592
+ }
593
+
594
+ // BigInt.asUintN(bits, bigint) — truncate to unsigned N-bit
595
+ ctx.core.emit['BigInt.asUintN'] = (bits, val) => {
596
+ const vbits = asI32(emit(bits)), vval = asI64(emit(val))
597
+ // val & ((1 << bits) - 1)
598
+ return typed(['f64.reinterpret_i64',
599
+ ['i64.and', vval, ['i64.sub', ['i64.shl', ['i64.const', 1], ['i64.extend_i32_s', vbits]], ['i64.const', 1]]]], 'f64')
600
+ }
601
+ }