jz 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/bench/README.md +176 -73
- package/bench/bench.svg +58 -71
- package/cli.js +12 -5
- package/dist/interop.js +1 -1
- package/dist/jz.js +1366 -1101
- package/index.js +40 -9
- package/interop.js +193 -138
- package/layout.js +29 -18
- package/module/array.js +49 -73
- package/module/collection.js +83 -25
- package/module/console.js +1 -1
- package/module/core.js +161 -15
- package/module/json.js +3 -3
- package/module/math.js +167 -117
- package/module/number.js +247 -13
- package/module/object.js +11 -5
- package/module/regex.js +8 -7
- package/module/string.js +295 -171
- package/module/typedarray.js +169 -105
- package/package.json +7 -3
- package/src/abi/string.js +40 -35
- package/src/ast.js +19 -2
- package/src/compile/analyze.js +64 -2
- package/src/compile/cse-load.js +200 -0
- package/src/compile/emit-assign.js +73 -14
- package/src/compile/emit.js +324 -34
- package/src/compile/index.js +204 -61
- package/src/compile/infer.js +8 -1
- package/src/compile/loop-divmod.js +12 -58
- package/src/compile/loop-model.js +91 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/loop-square.js +102 -0
- package/src/compile/narrow.js +180 -34
- package/src/compile/peel-stencil.js +18 -64
- package/src/compile/plan/common.js +29 -0
- package/src/compile/plan/index.js +4 -1
- package/src/compile/plan/inline.js +176 -21
- package/src/compile/plan/literals.js +93 -19
- package/src/ctx.js +51 -12
- package/src/helper-counters.js +137 -0
- package/src/ir.js +102 -13
- package/src/kind-traits.js +7 -3
- package/src/kind.js +14 -1
- package/src/op-policy.js +5 -2
- package/src/ops.js +119 -0
- package/src/optimize/index.js +1125 -136
- package/src/optimize/recurse.js +182 -0
- package/src/optimize/vectorize.js +1302 -144
- package/src/prepare/index.js +29 -12
- package/src/reps.js +4 -1
- package/src/type.js +53 -45
- package/src/wat/assemble.js +92 -9
- package/src/widen.js +21 -0
- package/src/wat/optimize.js +0 -3938
package/module/number.js
CHANGED
|
@@ -145,12 +145,224 @@ const sciExponent = (tail) => `
|
|
|
145
145
|
${tail}))`
|
|
146
146
|
|
|
147
147
|
// Apply the accumulated base-10 exponent to $result via __pow10.
|
|
148
|
+
// Used as fallback when EL cannot determine a unique rounding.
|
|
148
149
|
const POW10_SCALE = `
|
|
149
150
|
(if (i32.gt_s (local.get $decExp) (i32.const 0))
|
|
150
151
|
(then (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))
|
|
151
152
|
(if (i32.lt_s (local.get $decExp) (i32.const 0))
|
|
152
153
|
(then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))`
|
|
153
154
|
|
|
155
|
+
// Eisel-Lemire correctly-rounded decimal-to-f64.
|
|
156
|
+
// Used in place of FINISH_SIGNIFICAND + POW10_SCALE. Keeps $mant as i64 until after
|
|
157
|
+
// sciExponent finalizes $decExp, then calls $__dec_to_f64 with both.
|
|
158
|
+
// Falls back to f64.convert_i64_u + POW10_SCALE when EL returns NaN (ambiguous).
|
|
159
|
+
// The caller handles sign INSIDE this fragment (so the final return is the signed result).
|
|
160
|
+
const EL_SCALE = `
|
|
161
|
+
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
162
|
+
(if (local.get $round) (then (local.set $mant (i64.add (local.get $mant) (i64.const 1)))))
|
|
163
|
+
(local.set $result (call $__dec_to_f64 (local.get $mant) (local.get $decExp)))
|
|
164
|
+
(if (f64.ne (local.get $result) (local.get $result))
|
|
165
|
+
(then
|
|
166
|
+
(local.set $result (f64.convert_i64_u (local.get $mant)))
|
|
167
|
+
${POW10_SCALE}))
|
|
168
|
+
(if (local.get $neg) (then (local.set $result (f64.neg (local.get $result)))))`
|
|
169
|
+
|
|
170
|
+
// $__dec_to_f64: Eisel-Lemire correctly-rounded f64 from (mant × 10^exp10).
|
|
171
|
+
// Returns NaN (as sentinel) for ambiguous cases — caller falls back to POW10_SCALE.
|
|
172
|
+
// Reads 651-entry 128-bit power-of-ten table from global $__el_tbl (injected at compile time).
|
|
173
|
+
// Table layout: entry[i] = lo_i64_LE || hi_i64_LE, i = exp10 + 342.
|
|
174
|
+
//
|
|
175
|
+
// Algorithm: normalize mant to 64 bits, multiply by 128-bit table entry to get 128-bit
|
|
176
|
+
// product (prodHi:prodLo), extract 52-bit IEEE mantissa with correct rounding.
|
|
177
|
+
// Handles subnormals, overflow to Infinity, and early-exit for 0/trivial ranges.
|
|
178
|
+
const DEC_TO_F64_WAT = `(func $__dec_to_f64
|
|
179
|
+
(param $mant i64) (param $exp10 i32)
|
|
180
|
+
(result f64)
|
|
181
|
+
(local $mBits i32)
|
|
182
|
+
(local $w i64)
|
|
183
|
+
(local $tbl i32)
|
|
184
|
+
(local $tblHi i64) (local $tblLo i64)
|
|
185
|
+
;; 128-bit product: prodHi × 2^64 + prodLo
|
|
186
|
+
(local $p1hi i64) (local $p1lo i64)
|
|
187
|
+
(local $p2hi i64) (local $p2lo i64)
|
|
188
|
+
(local $prodHi i64) (local $prodLo i64)
|
|
189
|
+
(local $carry i64)
|
|
190
|
+
(local $lz i32)
|
|
191
|
+
(local $log2floor i32)
|
|
192
|
+
(local $exp2 i32) (local $biased i32)
|
|
193
|
+
(local $roundShift i32)
|
|
194
|
+
(local $roundBit i64)
|
|
195
|
+
(local $sticky i64)
|
|
196
|
+
(local $mant52 i64)
|
|
197
|
+
(local $subnShift i32) (local $totalShift i32)
|
|
198
|
+
(local $snRound i64) (local $snSticky i64) (local $snMant i64)
|
|
199
|
+
;; 32-bit limb temps for mul64
|
|
200
|
+
(local $a0 i32) (local $a1 i32) (local $b0 i32) (local $b1 i32)
|
|
201
|
+
(local $t00 i64) (local $t01 i64) (local $t10 i64) (local $t11 i64)
|
|
202
|
+
(local $mid i64) (local $mid_carry i64)
|
|
203
|
+
;; Zero check
|
|
204
|
+
(if (i64.eqz (local.get $mant)) (then (return (f64.const 0))))
|
|
205
|
+
;; Compute bit length of mant (1..64) by normalizing to 64 bits via clz
|
|
206
|
+
;; mBits = 64 - clz64(mant)
|
|
207
|
+
(local.set $mBits (i32.sub (i32.const 64) (i32.wrap_i64 (i64.clz (local.get $mant)))))
|
|
208
|
+
;; Normalize mant to 64-bit: w = mant << (64 - mBits)
|
|
209
|
+
(local.set $w (i64.shl (local.get $mant) (i64.extend_i32_u (i32.sub (i32.const 64) (local.get $mBits)))))
|
|
210
|
+
;; Range checks: the table is TRIMMED to exp10 in [-65..65] (covers every
|
|
211
|
+
;; real-world / source / JSON constant; fft/synth's coefficients are ~10^-23).
|
|
212
|
+
;; Outside that, return NaN → caller falls back to POW10_SCALE (the pre-EL path,
|
|
213
|
+
;; ~1 ULP for moderate exponents, exact only near the f64 limits no real literal
|
|
214
|
+
;; reaches). This keeps the data segment ~2KB instead of ~10KB on every program
|
|
215
|
+
;; that does an untyped numeric coercion (which pulls __to_num).
|
|
216
|
+
(if (i32.or (i32.lt_s (local.get $exp10) (i32.const -65))
|
|
217
|
+
(i32.gt_s (local.get $exp10) (i32.const 65)))
|
|
218
|
+
(then (return (f64.const nan))))
|
|
219
|
+
;; Load 128-bit table entry for exp10: tbl = $__el_tbl + (exp10 + 65) * 16
|
|
220
|
+
(local.set $tbl (i32.add (global.get $__el_tbl) (i32.shl (i32.add (local.get $exp10) (i32.const 65)) (i32.const 4))))
|
|
221
|
+
(local.set $tblLo (i64.load (local.get $tbl)))
|
|
222
|
+
(local.set $tblHi (i64.load (i32.add (local.get $tbl) (i32.const 8))))
|
|
223
|
+
;; ─── Two-product: (prodHi:prodLo) = w × tblHi + (w × tblLo >> 64) ──────────
|
|
224
|
+
;; mul64(w, tblHi) → p1hi:p1lo
|
|
225
|
+
;; Split w into 32-bit halves
|
|
226
|
+
(local.set $a0 (i32.wrap_i64 (i64.and (local.get $w) (i64.const 0xFFFFFFFF))))
|
|
227
|
+
(local.set $a1 (i32.wrap_i64 (i64.shr_u (local.get $w) (i64.const 32))))
|
|
228
|
+
(local.set $b0 (i32.wrap_i64 (i64.and (local.get $tblHi) (i64.const 0xFFFFFFFF))))
|
|
229
|
+
(local.set $b1 (i32.wrap_i64 (i64.shr_u (local.get $tblHi) (i64.const 32))))
|
|
230
|
+
(local.set $t00 (i64.mul (i64.extend_i32_u (local.get $a0)) (i64.extend_i32_u (local.get $b0))))
|
|
231
|
+
(local.set $t01 (i64.mul (i64.extend_i32_u (local.get $a0)) (i64.extend_i32_u (local.get $b1))))
|
|
232
|
+
(local.set $t10 (i64.mul (i64.extend_i32_u (local.get $a1)) (i64.extend_i32_u (local.get $b0))))
|
|
233
|
+
(local.set $t11 (i64.mul (i64.extend_i32_u (local.get $a1)) (i64.extend_i32_u (local.get $b1))))
|
|
234
|
+
;; mid = t01 + (t00>>32), track carry; then mid += t10, track carry
|
|
235
|
+
;; Each addition can carry at most 1 bit; total carry ≤ 2 → hi += carry<<32
|
|
236
|
+
(local.set $mid (i64.add (local.get $t01) (i64.shr_u (local.get $t00) (i64.const 32))))
|
|
237
|
+
(local.set $mid_carry (i64.extend_i32_u (i64.lt_u (local.get $mid) (local.get $t01))))
|
|
238
|
+
(local.set $mid (i64.add (local.get $mid) (local.get $t10)))
|
|
239
|
+
(local.set $mid_carry (i64.add (local.get $mid_carry)
|
|
240
|
+
(i64.extend_i32_u (i64.lt_u (local.get $mid) (local.get $t10)))))
|
|
241
|
+
(local.set $p1hi (i64.add
|
|
242
|
+
(i64.add (local.get $t11) (i64.shr_u (local.get $mid) (i64.const 32)))
|
|
243
|
+
(i64.shl (local.get $mid_carry) (i64.const 32))))
|
|
244
|
+
(local.set $p1lo (i64.or
|
|
245
|
+
(i64.and (local.get $t00) (i64.const 0xFFFFFFFF))
|
|
246
|
+
(i64.shl (i64.and (local.get $mid) (i64.const 0xFFFFFFFF)) (i64.const 32))))
|
|
247
|
+
;; mul64(w, tblLo) → p2hi:p2lo
|
|
248
|
+
(local.set $b0 (i32.wrap_i64 (i64.and (local.get $tblLo) (i64.const 0xFFFFFFFF))))
|
|
249
|
+
(local.set $b1 (i32.wrap_i64 (i64.shr_u (local.get $tblLo) (i64.const 32))))
|
|
250
|
+
(local.set $t00 (i64.mul (i64.extend_i32_u (local.get $a0)) (i64.extend_i32_u (local.get $b0))))
|
|
251
|
+
(local.set $t01 (i64.mul (i64.extend_i32_u (local.get $a0)) (i64.extend_i32_u (local.get $b1))))
|
|
252
|
+
(local.set $t10 (i64.mul (i64.extend_i32_u (local.get $a1)) (i64.extend_i32_u (local.get $b0))))
|
|
253
|
+
(local.set $t11 (i64.mul (i64.extend_i32_u (local.get $a1)) (i64.extend_i32_u (local.get $b1))))
|
|
254
|
+
(local.set $mid (i64.add (local.get $t01) (i64.shr_u (local.get $t00) (i64.const 32))))
|
|
255
|
+
(local.set $mid_carry (i64.extend_i32_u (i64.lt_u (local.get $mid) (local.get $t01))))
|
|
256
|
+
(local.set $mid (i64.add (local.get $mid) (local.get $t10)))
|
|
257
|
+
(local.set $mid_carry (i64.add (local.get $mid_carry)
|
|
258
|
+
(i64.extend_i32_u (i64.lt_u (local.get $mid) (local.get $t10)))))
|
|
259
|
+
(local.set $p2hi (i64.add
|
|
260
|
+
(i64.add (local.get $t11) (i64.shr_u (local.get $mid) (i64.const 32)))
|
|
261
|
+
(i64.shl (local.get $mid_carry) (i64.const 32))))
|
|
262
|
+
(local.set $p2lo (i64.or
|
|
263
|
+
(i64.and (local.get $t00) (i64.const 0xFFFFFFFF))
|
|
264
|
+
(i64.shl (i64.and (local.get $mid) (i64.const 0xFFFFFFFF)) (i64.const 32))))
|
|
265
|
+
;; Combine: prodHi:prodLo = p1hi:(p1lo + p2hi) with carry propagation
|
|
266
|
+
(local.set $carry (i64.add (local.get $p1lo) (local.get $p2hi)))
|
|
267
|
+
;; Check if carry propagated (carry < p1lo → overflow into prodHi)
|
|
268
|
+
(local.set $prodHi (i64.add (local.get $p1hi)
|
|
269
|
+
(i64.extend_i32_u (i64.lt_u (local.get $carry) (local.get $p1lo)))))
|
|
270
|
+
(local.set $prodLo (local.get $carry))
|
|
271
|
+
;; ─── Determine lz (leading-zero flag): 1 if MSB of prodHi is 0 ──────────────
|
|
272
|
+
(local.set $lz (i32.wrap_i64 (i64.xor (i64.shr_u (local.get $prodHi) (i64.const 63)) (i64.const 1))))
|
|
273
|
+
;; ─── Compute biased exponent ─────────────────────────────────────────────────
|
|
274
|
+
;; floor(exp10 * log2(10)) via fixed-point: (exp10 * 14267572527) >> 32
|
|
275
|
+
;; 14267572527 = floor(log2(10) * 2^32) — correct for all exp10 in -342..308.
|
|
276
|
+
(local.set $log2floor (i32.wrap_i64 (i64.shr_s
|
|
277
|
+
(i64.mul (i64.extend_i32_s (local.get $exp10)) (i64.const 14267572527))
|
|
278
|
+
(i64.const 32))))
|
|
279
|
+
;; exp2 = mBits + floor(exp10 * log2(10)) - lz
|
|
280
|
+
(local.set $exp2 (i32.sub (i32.add (local.get $mBits) (local.get $log2floor)) (local.get $lz)))
|
|
281
|
+
(local.set $biased (i32.add (local.get $exp2) (i32.const 1023)))
|
|
282
|
+
;; Overflow → Infinity
|
|
283
|
+
(if (i32.ge_s (local.get $biased) (i32.const 2047)) (then (return (f64.const inf))))
|
|
284
|
+
;; ─── Subnormal path (biased ≤ 0) ────────────────────────────────────────────
|
|
285
|
+
(if (i32.le_s (local.get $biased) (i32.const 0))
|
|
286
|
+
(then
|
|
287
|
+
;; total_shift = 11 - lz + (1 - biased) = 12 - lz - biased
|
|
288
|
+
(local.set $totalShift (i32.sub (i32.sub (i32.const 12) (local.get $lz)) (local.get $biased)))
|
|
289
|
+
;; If totalShift >= 64: prodHi >> totalShift == 0 in BigInt, but here:
|
|
290
|
+
;; For totalShift in [64..127]: snMant = (prodHi >> (totalShift-64)) >> 64... = 0
|
|
291
|
+
;; We just use BigInt-style: clamp to 0 if >=64 (prodHi is 64-bit)
|
|
292
|
+
(if (i32.ge_u (local.get $totalShift) (i32.const 64))
|
|
293
|
+
(then
|
|
294
|
+
;; All mantissa bits are 0; only rounding could give min subnormal.
|
|
295
|
+
;; round bit: at position (totalShift-1) of prodHi → always 0 for totalShift>=65
|
|
296
|
+
;; For totalShift==64: round bit = bit 63 of prodHi = MSB
|
|
297
|
+
(if (i32.eq (local.get $totalShift) (i32.const 64))
|
|
298
|
+
(then
|
|
299
|
+
(local.set $snRound (i64.shr_u (local.get $prodHi) (i64.const 63)))
|
|
300
|
+
(local.set $snSticky (i64.or (local.get $prodLo) (local.get $p2lo)))
|
|
301
|
+
;; Return min-subnormal if snRound=1 AND snSticky!=0 (boolean AND, not bitwise)
|
|
302
|
+
(if (i32.and
|
|
303
|
+
(i32.wrap_i64 (local.get $snRound))
|
|
304
|
+
(i64.ne (local.get $snSticky) (i64.const 0)))
|
|
305
|
+
(then (return (f64.reinterpret_i64 (i64.const 1)))))
|
|
306
|
+
))
|
|
307
|
+
(return (f64.const 0))))
|
|
308
|
+
;; totalShift in [1..63]: extract directly from prodHi
|
|
309
|
+
(local.set $snMant (i64.and
|
|
310
|
+
(i64.shr_u (local.get $prodHi) (i64.extend_i32_u (local.get $totalShift)))
|
|
311
|
+
(i64.const 0x000FFFFFFFFFFFFF)))
|
|
312
|
+
(local.set $snRound
|
|
313
|
+
(i64.and (i64.shr_u (local.get $prodHi)
|
|
314
|
+
(i64.extend_i32_u (i32.sub (local.get $totalShift) (i32.const 1))))
|
|
315
|
+
(i64.const 1)))
|
|
316
|
+
(local.set $snSticky (i64.or
|
|
317
|
+
(i64.and (local.get $prodHi)
|
|
318
|
+
(i64.sub (i64.shl (i64.const 1) (i64.extend_i32_u (i32.sub (local.get $totalShift) (i32.const 1))))
|
|
319
|
+
(i64.const 1)))
|
|
320
|
+
(i64.or (local.get $prodLo) (local.get $p2lo))))
|
|
321
|
+
;; Round: snMant++ if roundBit && (sticky > 0 || snMant is odd)
|
|
322
|
+
(if (i64.ne (i64.and (local.get $snRound)
|
|
323
|
+
(i64.or (i64.extend_i32_u (i64.ne (local.get $snSticky) (i64.const 0)))
|
|
324
|
+
(i64.and (local.get $snMant) (i64.const 1))))
|
|
325
|
+
(i64.const 0))
|
|
326
|
+
(then (local.set $snMant (i64.add (local.get $snMant) (i64.const 1)))))
|
|
327
|
+
;; Overflow of subnormal mantissa → minimum normal (biased=1, mant=0)
|
|
328
|
+
(if (i64.ge_u (local.get $snMant) (i64.const 0x0010000000000000))
|
|
329
|
+
(then (return (f64.reinterpret_i64 (i64.const 0x0010000000000000)))))
|
|
330
|
+
(return (f64.reinterpret_i64 (local.get $snMant)))))
|
|
331
|
+
;; ─── Normal path ─────────────────────────────────────────────────────────────
|
|
332
|
+
;; roundShift = 10 - lz (bit position of round bit in prodHi)
|
|
333
|
+
(local.set $roundShift (i32.sub (i32.const 10) (local.get $lz)))
|
|
334
|
+
(local.set $roundBit (i64.and
|
|
335
|
+
(i64.shr_u (local.get $prodHi) (i64.extend_i32_u (local.get $roundShift)))
|
|
336
|
+
(i64.const 1)))
|
|
337
|
+
;; sticky = bits below roundBit in prodHi, plus all of prodLo and p2lo
|
|
338
|
+
(local.set $sticky (i64.or
|
|
339
|
+
(i64.and (local.get $prodHi)
|
|
340
|
+
(i64.sub (i64.shl (i64.const 1) (i64.extend_i32_u (local.get $roundShift)))
|
|
341
|
+
(i64.const 1)))
|
|
342
|
+
(i64.or (local.get $prodLo) (local.get $p2lo))))
|
|
343
|
+
;; mant52 = (prodHi >> (11 - lz)) & MASK52
|
|
344
|
+
(local.set $mant52 (i64.and
|
|
345
|
+
(i64.shr_u (local.get $prodHi) (i64.extend_i32_u (i32.sub (i32.const 11) (local.get $lz))))
|
|
346
|
+
(i64.const 0x000FFFFFFFFFFFFF)))
|
|
347
|
+
;; Round: mant52++ if roundBit && (sticky > 0 || mant52 is odd)
|
|
348
|
+
(if (i64.ne (i64.and (local.get $roundBit)
|
|
349
|
+
(i64.or (i64.extend_i32_u (i64.ne (local.get $sticky) (i64.const 0)))
|
|
350
|
+
(i64.and (local.get $mant52) (i64.const 1))))
|
|
351
|
+
(i64.const 0))
|
|
352
|
+
(then (local.set $mant52 (i64.add (local.get $mant52) (i64.const 1)))))
|
|
353
|
+
;; Mantissa overflow: carry into exponent
|
|
354
|
+
(if (i64.ge_u (local.get $mant52) (i64.const 0x0010000000000000))
|
|
355
|
+
(then
|
|
356
|
+
(local.set $mant52 (i64.const 0))
|
|
357
|
+
(local.set $biased (i32.add (local.get $biased) (i32.const 1)))))
|
|
358
|
+
;; Final overflow check after rounding
|
|
359
|
+
(if (i32.ge_s (local.get $biased) (i32.const 2047)) (then (return (f64.const inf))))
|
|
360
|
+
;; Assemble IEEE 754 bits: biased_exp << 52 | mant52
|
|
361
|
+
(f64.reinterpret_i64
|
|
362
|
+
(i64.or
|
|
363
|
+
(i64.shl (i64.extend_i32_u (local.get $biased)) (i64.const 52))
|
|
364
|
+
(local.get $mant52))))`
|
|
365
|
+
|
|
154
366
|
export default (ctx) => {
|
|
155
367
|
deps({
|
|
156
368
|
__mkstr: ['__alloc'],
|
|
@@ -159,11 +371,11 @@ export default (ctx) => {
|
|
|
159
371
|
__toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
|
|
160
372
|
__radix_str: ['__mkstr'],
|
|
161
373
|
__num_radix: ['__ftoa', '__mkstr'],
|
|
162
|
-
__to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str', '__skipws', '__ptr_aux'],
|
|
374
|
+
__to_num: ['__char_at', '__str_byteLen', '__pow10', '__dec_to_f64', '__to_str', '__skipws', '__ptr_aux'],
|
|
163
375
|
__skipws: ['__char_at', '__strws'],
|
|
164
376
|
__to_bigint: ['__char_at', '__str_byteLen', '__num_to_bigint'],
|
|
165
377
|
__parseInt: ['__char_at', '__str_byteLen'],
|
|
166
|
-
__parseFloat: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
|
|
378
|
+
__parseFloat: ['__char_at', '__str_byteLen', '__pow10', '__dec_to_f64', '__to_str'],
|
|
167
379
|
})
|
|
168
380
|
|
|
169
381
|
|
|
@@ -354,10 +566,11 @@ export default (ctx) => {
|
|
|
354
566
|
(then
|
|
355
567
|
(local.set $b (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
|
|
356
568
|
(br_if $heap (i32.ge_u (local.get $b) (i32.const 0x80)))
|
|
357
|
-
|
|
569
|
+
;; 7-bit ASCII SSO: char i at bit i*7 (chars fit the offset for len ≤4); len at aux bits 10-12.
|
|
570
|
+
(local.set $packed (i32.or (local.get $packed) (i32.shl (local.get $b) (i32.mul (local.get $i) (i32.const 7)))))
|
|
358
571
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
359
572
|
(br $pk))))
|
|
360
|
-
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $len)) (local.get $packed))))))
|
|
573
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.shl (local.get $len) (i32.const 10))) (local.get $packed))))))
|
|
361
574
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
|
|
362
575
|
(i32.store (local.get $off) (local.get $len))
|
|
363
576
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
@@ -644,8 +857,31 @@ export default (ctx) => {
|
|
|
644
857
|
ctx.runtime.staticDataLen = staticStr.length
|
|
645
858
|
ctx.runtime.data = (ctx.runtime.data || '') + staticStr
|
|
646
859
|
|
|
860
|
+
// Eisel-Lemire power-of-10 table: 131 entries × 16 bytes = 2096 bytes,
|
|
861
|
+
// staticStr.length = 57, pad 7 → table starts at byte 64 (always constant).
|
|
862
|
+
// The table bytes are stashed in ctx.runtime.elTable. src/compile/index.js appends
|
|
863
|
+
// them to ctx.runtime.data (padded to 8-byte boundary) only when $__dec_to_f64 is
|
|
864
|
+
// actually pulled in, then declares global $__el_tbl = that offset. This keeps the
|
|
865
|
+
// 2096-byte data segment out of modules that don't do decimal→f64 conversion.
|
|
866
|
+
// TRIMMED to exp10 in [-65..65] (see __dec_to_f64 range check). Each entry: 8
|
|
867
|
+
// bytes tblLo (LE) + 8 bytes tblHi (LE), for exp10 = -65 + i.
|
|
868
|
+
const EL_TABLE_HEX = 'e9b4c29f1247e998eaba94ea52bbcc862462b347d798233fa5e939a527ea7fa8ad3aa0190d7fec8e0e64888eb1e49fd2ac24043068cf5319893e15f9eeeea383d72d053c42c3a85f2b8e5ab7aaea8ca44d7906cb12f49237b63131655525b0cdd00be4be8bd8bbe211bf3e5f55178e80c40e9daeaece6a5bd66e0eb72a9db1a07552445a5a8245f28b0ad2647504dec81267d5f0f0e2d6ee2e8d06be928515fb6b608596d64d46553d18c4b67b73ed9c86b8263c4ce197aa4c1e75a45ad028c4a866304b9fd93dd5df65924d710433f52940fe8e03a846e5ab7f7bd0c6e23f9933d0bd72045298de965f9a8478db8fbf40446d8f85663e967cf7c0a556d273efa84aa4791300e7ddad9a98277663a895525d0d5818c0605559c17eb1537c12bba6b4106e1ef0b8aaaf71de9d681bd7e9e870ca041396b3ca0d07ab6221712692220dfdc5977b603dd1c855bb690db0b66a507cb77d9ab88c053b2b2ac4105ce442b2ad928e60f377e3045b9a7a8ab98ed31e5937b238f0551cc6f14019ed67b288662fc5de466c6ba3372e915fe801df15a03d3b4bac2323c6e2bcba3b31618b1a080d0a5e97ecab771b6ca98a7d39ae214a908c35bde7965522c753eddcc7d9542eda7741d6507e75755c5414ea1c88e9b9d0d5d10be5ddd2927369992424aa64e8444bc64e5e958777d0c3bf2dadd43e110bef3bf15abdb44a62da973cec848ed5cdea8aadb1ec61ddfad0bd4b27a6f24a81a5ed18de67ba943945ad1eb1cfd7ce708794cfea80f4fc434b2cb3ce818d024da9798325a131fc145ef75f42a23043a01358e46e093e3b9a35f5f7d2cafc5388186e9dca8b0dca0083f2b587fd7d3455cf64a25e77487ee091b7d1749e9d812a03fe4a3695da9d5876250612c60422f583bddd833a51c5eed3ae8796f742357972966a92c4523b7544cd14be9a9382170f3c05b775278a9295009a6dc13863dd128bc62453b12cf7ba8000c9f1035ecaeb16fcf6d3ee7bda7450a01d9784f5bca61cbbf488ea1a11926408e5bce5326cd0e3e9312ba56195b67d4a1eeccf9f43622e32ff3a075d1d928eee9293c287d4fab9febe0949b4a43632aa77b8b3a9897968be2e4c5be14dc4be9495e6100af64b01379d0fd9acb03af77c1d90948cf39ec18484530fd85c0935dc24b4b96fb006f2a56528130eb44b42132ee1d3452e44b7873ff9cb88506f09ccbc8c48d73915a5698ff7feaa24cb0bffebaf1b4d885a0e4473b5bed5edbdcefee6db303095f8880a683197a5b436415f70893d7cba362b0dc2fdfcce61841177ccab4c1b69047690323dbc427ae5d594bfd60fb1c1c2499a3fa6b5696caf05bd3786531d7233dc80cf0f2384471b47acc5a7a8a44e401361c3d32b6519e25817b7d1e9263108ac1c5a643bdf4f8d976e1283a3703d0ad7a3703d0ad7a3703d0ad7a3cccccccccccccccccccccccccccccccc00000000000000000000000000000080000000000000000000000000000000a0000000000000000000000000000000c8000000000000000000000000000000fa0000000000000000000000000000409c000000000000000000000000000050c3000000000000000000000000000024f4000000000000000000000000008096980000000000000000000000000020bcbe00000000000000000000000000286bee00000000000000000000000000f9029500000000000000000000000040b743ba00000000000000000000000010a5d4e80000000000000000000000002ae78491000000000000000000000080f420e6b50000000000000000000000a031a95fe3000000000000000000000004bfc91b8e0000000000000000000000c52ebca2b10000000000000000000040763a6b0bde00000000000000000000e8890423c78a0000000000000000000062acc5eb78ad000000000000000000807a17b726d7d800000000000000000090ac6e32788687000000000000000000b4570a3f1668a9000000000000000000a1edccce1bc2d30000000000000000a0841440615159840000000000000000c8a51990b9a56fa500000000000000003a0f20f4278fcbce0000000000000040840994f878393f810000000000000050e50bb936d7078fa100000000000000a4de4e6704cdc9f2c9000000000000004d96228145407c6ffc00000000000020f09db5702ba8adc59d000000000000286c05e34c36121937c500000000000032c7c61be0c356df84f60000000000407f3c5c116c3a960b139a0000000000109f4bb31507c97bce97c00000000000d4861e20db48bb1ac2bdf00000000080441413f4880db55099769600000000a055d91731eb50e2a43f14bc0000000008abcf5dfd25e51a8e4f19eb00000000e5caa15abe37cfd0b8d1ef92000000409e3d4af1ad05030527c6abb7000000d005cd9c6d19c743c6b0b796e5000000a2230082e46f5cea7bce327e8f0000808a2c80a2dd8bf3e41a82bf5db3000020ad37200bd56e309ea1622f35e0000034cc22f4264545de02a59d3d218c0000417f2bb17096d695430e058d29af0040115f76dd0c3c4c7bd45146f0f3da00c86afb690a88a50fcd24f32b76d888007a457a040dea8e5300eeefb6930eab80d8d6984590a4726880e9aba438d2d55047867f2bdaa64741f071eb6663a38524d9675fb6909099516c4ea6403c0ca76dcf41f7e3b4f4ff6507e2cf504bcfd0a421897a0ef1f8bf9f44ed81128f81820d6a2b19522df7afc7956822d7f221a39044769fa6f8f49b39bb02eb8c6feacbb4d55347d036f202086ac325700be5fe9065942c4262d70145229a1726274f9ff57eb9b7d23a4d42d6aa809deff022c7b2dea7658789e0d28bd5e0842badebf82feb889ff455cc6377850c333b4c939bfb256bc7716bbf3cd5a6cfff491f78c27aef45394e46ef8b8a90c37f1c2716f3'
|
|
869
|
+
// Pre-decode the EL table bytes once and stash in ctx.runtime.
|
|
870
|
+
// src/compile/index.js appends elTable to ctx.runtime.data only when
|
|
871
|
+
// __dec_to_f64 is actually pulled in via deps (lazy, keeps small modules clean).
|
|
872
|
+
let elTableBytes = ''
|
|
873
|
+
for (let i = 0; i < EL_TABLE_HEX.length; i += 2)
|
|
874
|
+
elTableBytes += String.fromCharCode(parseInt(EL_TABLE_HEX.slice(i, i + 2), 16))
|
|
875
|
+
ctx.runtime.elTable = elTableBytes
|
|
876
|
+
|
|
877
|
+
// Register the stdlib function (no data appended here — see compile/index.js hook)
|
|
878
|
+
ctx.core.stdlib['__dec_to_f64'] = DEC_TO_F64_WAT
|
|
879
|
+
|
|
647
880
|
// === Number constants ===
|
|
648
881
|
|
|
882
|
+
// Each folds to inline (f64.const …), no stdlib dep. Written out (not a table
|
|
883
|
+
// loop) to stay within the self-host subset. `NaN` uses the `nan` token (not raw
|
|
884
|
+
// NaN) so it survives self-host IR marshalling — see emitNum.
|
|
649
885
|
ctx.core.emit['Number.MAX_SAFE_INTEGER'] = () => typed(['f64.const', 9007199254740991], 'f64')
|
|
650
886
|
ctx.core.emit['Number.MIN_SAFE_INTEGER'] = () => typed(['f64.const', -9007199254740991], 'f64')
|
|
651
887
|
ctx.core.emit['Number.EPSILON'] = () => typed(['f64.const', 2.220446049250313e-16], 'f64')
|
|
@@ -653,7 +889,7 @@ export default (ctx) => {
|
|
|
653
889
|
ctx.core.emit['Number.MIN_VALUE'] = () => typed(['f64.const', 5e-324], 'f64')
|
|
654
890
|
ctx.core.emit['Number.POSITIVE_INFINITY'] = () => typed(['f64.const', Infinity], 'f64')
|
|
655
891
|
ctx.core.emit['Number.NEGATIVE_INFINITY'] = () => typed(['f64.const', -Infinity], 'f64')
|
|
656
|
-
ctx.core.emit['Number.NaN'] = () => typed(['f64.const', 'nan'], 'f64')
|
|
892
|
+
ctx.core.emit['Number.NaN'] = () => typed(['f64.const', 'nan'], 'f64')
|
|
657
893
|
|
|
658
894
|
// === Number static methods ===
|
|
659
895
|
|
|
@@ -950,9 +1186,6 @@ export default (ctx) => {
|
|
|
950
1186
|
;; Decimal significand. Keep 18 significant decimal digits, track the
|
|
951
1187
|
;; base-10 exponent for skipped digits, and round once before pow10 scaling.
|
|
952
1188
|
${DEC_SIGNIFICAND}
|
|
953
|
-
;; No digits — the literal was a bare sign or stray text ("abc", "+") → NaN.
|
|
954
|
-
;; (Empty / all-whitespace strings already returned +0 above.)
|
|
955
|
-
${FINISH_SIGNIFICAND}
|
|
956
1189
|
;; Scientific notation. 'e'/'E' commits to an ExponentPart — at least one
|
|
957
1190
|
;; digit must follow ("1e", "5e+" are NaN).
|
|
958
1191
|
${sciExponent(`(if (i32.eqz (local.get $expDigits)) (then (return (f64.const nan))))
|
|
@@ -962,8 +1195,9 @@ export default (ctx) => {
|
|
|
962
1195
|
;; Reject trailing non-whitespace ("5px", numeric separators "1_0", …).
|
|
963
1196
|
(local.set $i (call $__skipws (local.get $v) (local.get $i) (local.get $len)))
|
|
964
1197
|
(if (i32.lt_s (local.get $i) (local.get $len)) (then (return (f64.const nan))))
|
|
965
|
-
|
|
966
|
-
|
|
1198
|
+
;; Eisel-Lemire exact rounding; fallback to __pow10 for ambiguous cases.
|
|
1199
|
+
${EL_SCALE}
|
|
1200
|
+
(local.get $result))`
|
|
967
1201
|
|
|
968
1202
|
// NumberToBigInt: a RangeError unless n is an integral Number — finite and
|
|
969
1203
|
// equal to its own truncation. NaN fails the f64.eq integrality test;
|
|
@@ -1089,15 +1323,15 @@ export default (ctx) => {
|
|
|
1089
1323
|
;; Decimal significand. Keep 18 significant decimal digits, track the
|
|
1090
1324
|
;; base-10 exponent for skipped digits, and round once before pow10 scaling.
|
|
1091
1325
|
${DEC_SIGNIFICAND}
|
|
1092
|
-
${FINISH_SIGNIFICAND}
|
|
1093
1326
|
;; Scientific notation.
|
|
1094
1327
|
${sciExponent(`(if (local.get $expDigits)
|
|
1095
1328
|
(then
|
|
1096
1329
|
(if (local.get $expNeg)
|
|
1097
1330
|
(then (local.set $decExp (i32.sub (local.get $decExp) (local.get $exp))))
|
|
1098
1331
|
(else (local.set $decExp (i32.add (local.get $decExp) (local.get $exp)))))))`)}
|
|
1099
|
-
|
|
1100
|
-
|
|
1332
|
+
;; Eisel-Lemire exact rounding; fallback to __pow10 for ambiguous cases.
|
|
1333
|
+
${EL_SCALE}
|
|
1334
|
+
(local.get $result))`
|
|
1101
1335
|
|
|
1102
1336
|
// ToString(arg) for the string-input builtins. A statically-known boolean must
|
|
1103
1337
|
// render as "true"/"false" (spec step 1: ToString) before parsing — otherwise
|
package/module/object.js
CHANGED
|
@@ -790,11 +790,17 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
790
790
|
else allKnown = false
|
|
791
791
|
} else if (Array.isArray(p) && p[0] === ':') addName(p[1])
|
|
792
792
|
}
|
|
793
|
-
// Single unknown spread `{ ...
|
|
794
|
-
//
|
|
795
|
-
//
|
|
796
|
-
|
|
797
|
-
|
|
793
|
+
// Single unknown spread `{ ...src }` → shallow-clone src at runtime, preserving
|
|
794
|
+
// its type (OBJECT→OBJECT, HASH→HASH). Aliasing src (the old shortcut) leaked
|
|
795
|
+
// every later write to the result back into the source — a real correctness bug
|
|
796
|
+
// (jz's own narrow.js had to hand-route around it). __obj_clone keys off the
|
|
797
|
+
// box's runtime schemaId, so it copies static-segment sources too; the schema
|
|
798
|
+
// table it reads must exist, so declare + force it (assemble.js).
|
|
799
|
+
if (!allKnown && props.length === 1 && Array.isArray(props[0]) && props[0][0] === '...') {
|
|
800
|
+
inc('__obj_clone')
|
|
801
|
+
if (!ctx.scope.globals.has('__schema_tbl')) declGlobal('__schema_tbl', 'i32')
|
|
802
|
+
return typed(['call', '$__obj_clone', asF64(emit(props[0][1]))], 'f64')
|
|
803
|
+
}
|
|
798
804
|
if (!allKnown) return emitDynamicSpread(props)
|
|
799
805
|
|
|
800
806
|
const schemaId = ctx.schema.register(allNames)
|
package/module/regex.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { typed, asF64, asI64, UNDEF_NAN, NULL_NAN, mkPtrIR, temp, tempI32 } from '../src/ir.js'
|
|
11
11
|
import { emit, deps } from '../src/bridge.js'
|
|
12
|
-
import { ctx, err, inc, PTR, LAYOUT,
|
|
12
|
+
import { ctx, err, inc, PTR, LAYOUT, registerGetter, declGlobal } from '../src/ctx.js'
|
|
13
13
|
import { valTypeOf } from '../src/kind.js'
|
|
14
14
|
import { VAL } from '../src/reps.js'
|
|
15
15
|
|
|
@@ -746,13 +746,14 @@ export default (ctx) => {
|
|
|
746
746
|
(if (i32.eqz (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT})))
|
|
747
747
|
(then (return (call $__ptr_offset (local.get $ptr)))))
|
|
748
748
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
749
|
-
(local.set $len (i32.and (local.get $aux) (i32.const 7)))
|
|
749
|
+
(local.set $len (i32.and (i32.shr_u (local.get $aux) (i32.const 10)) (i32.const 7)))
|
|
750
750
|
(local.set $buf (call $__alloc (local.get $len)))
|
|
751
751
|
(local.set $i (i32.const 0))
|
|
752
752
|
(block $done (loop $next
|
|
753
753
|
(br_if $done (i32.ge_u (local.get $i) (local.get $len)))
|
|
754
|
+
;; 7-bit ASCII SSO: char i at payload bit i*7 (read from the full i64 ptr; chars 4-5 span aux).
|
|
754
755
|
(i32.store8 (i32.add (local.get $buf) (local.get $i))
|
|
755
|
-
(i32.and (
|
|
756
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.mul (i64.extend_i32_u (local.get $i)) (i64.const 7))) (i64.const 0x7f))))
|
|
756
757
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
757
758
|
(br $next)))
|
|
758
759
|
(local.get $buf))`
|
|
@@ -911,14 +912,14 @@ export default (ctx) => {
|
|
|
911
912
|
// RegExp.prototype.source — the pattern text. A literal stores it verbatim
|
|
912
913
|
// (already grammar-escaped), so `/A/.source` is the 6-char "A".
|
|
913
914
|
// An empty pattern serializes to "(?:)" so the result re-parses to a regex.
|
|
914
|
-
|
|
915
|
+
registerGetter('.regex:source', (obj) => {
|
|
915
916
|
const a = regexAstOf(obj)
|
|
916
917
|
return emit(['str', (a && a[1]) || '(?:)'])
|
|
917
918
|
})
|
|
918
919
|
|
|
919
920
|
// RegExp.prototype.flags — flag characters in canonical order (sec-get-regexp.prototype.flags).
|
|
920
921
|
const FLAG_ORDER = 'dgimsvy'
|
|
921
|
-
|
|
922
|
+
registerGetter('.regex:flags', (obj) => {
|
|
922
923
|
const f = flagsOf(obj)
|
|
923
924
|
return emit(['str', [...FLAG_ORDER].filter(c => f.includes(c)).join('')])
|
|
924
925
|
})
|
|
@@ -927,10 +928,10 @@ export default (ctx) => {
|
|
|
927
928
|
for (const [prop, ch] of [
|
|
928
929
|
['global', 'g'], ['ignoreCase', 'i'], ['multiline', 'm'], ['dotAll', 's'],
|
|
929
930
|
['unicode', 'u'], ['sticky', 'y'], ['hasIndices', 'd'], ['unicodeSets', 'v'],
|
|
930
|
-
])
|
|
931
|
+
]) registerGetter(`.regex:${prop}`, (obj) => typed(['f64.const', flagsOf(obj).includes(ch) ? 1 : 0], 'f64'))
|
|
931
932
|
|
|
932
933
|
// lastIndex — for /g and /y regexes, reads the mutable global; others always 0.
|
|
933
|
-
|
|
934
|
+
registerGetter('.regex:lastIndex', (obj) => {
|
|
934
935
|
const id = resolveRegex(obj)
|
|
935
936
|
if (id != null) {
|
|
936
937
|
const flags = flagsOf(obj)
|