jz 0.6.0 → 0.8.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 +99 -72
- package/bench/README.md +253 -100
- package/bench/bench.svg +58 -81
- package/cli.js +85 -12
- package/dist/interop.js +1 -0
- package/dist/jz.js +6196 -0
- package/index.d.ts +126 -0
- package/index.js +222 -35
- package/interop.js +240 -141
- package/layout.js +34 -18
- package/module/array.js +99 -111
- package/module/collection.js +124 -30
- package/module/console.js +1 -1
- package/module/core.js +163 -19
- package/module/json.js +3 -3
- package/module/math.js +162 -3
- package/module/number.js +268 -13
- package/module/object.js +37 -5
- package/module/regex.js +8 -7
- package/module/simd.js +37 -5
- package/module/string.js +203 -168
- package/module/typedarray.js +565 -112
- package/package.json +26 -7
- package/src/abi/string.js +29 -29
- package/src/ast.js +19 -2
- package/src/autoload.js +3 -0
- package/src/compile/analyze-scans.js +174 -11
- package/src/compile/analyze.js +101 -4
- package/src/compile/cse-load.js +200 -0
- package/src/compile/emit-assign.js +82 -20
- package/src/compile/emit.js +592 -51
- package/src/compile/index.js +504 -89
- package/src/compile/infer.js +36 -1
- package/src/compile/loop-divmod.js +109 -0
- package/src/compile/loop-model.js +91 -0
- package/src/compile/loop-square.js +102 -0
- package/src/compile/narrow.js +275 -41
- package/src/compile/peel-stencil.js +215 -0
- package/src/compile/plan/advise.js +55 -1
- package/src/compile/plan/common.js +29 -0
- package/src/compile/plan/index.js +8 -1
- package/src/compile/plan/inline.js +180 -22
- package/src/compile/plan/literals.js +313 -24
- package/src/compile/plan/scope.js +115 -39
- package/src/compile/program-facts.js +21 -2
- package/src/ctx.js +96 -19
- package/src/helper-counters.js +137 -0
- package/src/ir.js +157 -20
- package/src/kind-traits.js +34 -3
- package/src/kind.js +79 -4
- package/src/op-policy.js +5 -2
- package/src/ops.js +119 -0
- package/src/optimize/index.js +1274 -151
- package/src/optimize/recurse.js +182 -0
- package/src/optimize/vectorize.js +4187 -253
- package/src/prepare/index.js +75 -14
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +6 -2
- package/src/static.js +9 -0
- package/src/type.js +63 -51
- package/src/wat/assemble.js +286 -21
- package/src/widen.js +21 -0
- package/src/wat/optimize.js +0 -3760
package/module/collection.js
CHANGED
|
@@ -12,8 +12,8 @@ import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64
|
|
|
12
12
|
import { emit, deps, call } from '../src/bridge.js'
|
|
13
13
|
import { valTypeOf } from '../src/kind.js'
|
|
14
14
|
import { VAL, lookupValType } from '../src/reps.js'
|
|
15
|
-
import { hasOwnContinue, isBlockBody } from '../src/ast.js'
|
|
16
|
-
import { ctx, inc, PTR, LAYOUT,
|
|
15
|
+
import { hasOwnContinue, isBlockBody, isLiteralStr } from '../src/ast.js'
|
|
16
|
+
import { ctx, inc, PTR, LAYOUT, registerGetter, declGlobal } from '../src/ctx.js'
|
|
17
17
|
import { STR_INTERN_BIT } from '../layout.js'
|
|
18
18
|
|
|
19
19
|
const SET_ENTRY = 16 // hash + key
|
|
@@ -43,6 +43,20 @@ function numConstLiteral(expr) {
|
|
|
43
43
|
return null
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Compile-time probe hash for a LITERAL collection/property key, else null. A numeric
|
|
47
|
+
// constant → numHashLiteral; an ASCII string literal → strHashLiteral. The string case is
|
|
48
|
+
// ASCII-only on purpose: strHashLiteral folds `charCodeAt(i) & 0xFF`, which equals __str_hash /
|
|
49
|
+
// __map_hash (FNV-1a over the UTF-8 bytes) ONLY for code points < 0x80 — a non-ASCII literal
|
|
50
|
+
// would fold a different hash than its stored key and silently miss, so it falls back to the
|
|
51
|
+
// runtime hash. Lets `m.get("if")` / `m.has("x")` skip the per-access __map_hash call.
|
|
52
|
+
const ASCII_KEY = /^[\x00-\x7f]*$/
|
|
53
|
+
const litKeyHash = (key) => {
|
|
54
|
+
const num = numConstLiteral(key)
|
|
55
|
+
if (num != null) return numHashLiteral(num)
|
|
56
|
+
if (isLiteralStr(key) && ASCII_KEY.test(key[1])) return strHashLiteral(key[1])
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
// Equality expressions for probe templates
|
|
47
61
|
const sameValueZeroEq = '(call $__same_value_zero (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
48
62
|
const strEq = '(call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
@@ -55,9 +69,22 @@ const strEq = '(call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8
|
|
|
55
69
|
// walking shared-prefix bytes through __str_eq (31M unequal content-compares
|
|
56
70
|
// per self-host bench run before this). If-expression for short-circuit:
|
|
57
71
|
// i32.and would still evaluate the call.
|
|
72
|
+
//
|
|
73
|
+
// On a hash hit, an inline `storedKey == queryKey` (one i64.eq on the slot's key
|
|
74
|
+
// word) decides the overwhelmingly-common identity case — interned/SSO literals and
|
|
75
|
+
// the same heap pointer are bit-equal — WITHOUT the __str_eq / __same_value_zero call
|
|
76
|
+
// frame. Sound for both: bit-equality implies string-equality and SameValueZero (the
|
|
77
|
+
// only cross-bit-pattern equals — +0/-0, distinct NaN payloads — fall through to the
|
|
78
|
+
// full compare, never the reverse). Only a content-equal-but-bit-distinct key (a heap
|
|
79
|
+
// string vs a literal) still pays the call. This attacks the __str_eq call tax directly
|
|
80
|
+
// and helps the runtimes that DON'T inline tiny callees (wasmtime, wasm2c).
|
|
58
81
|
const hashGuard = (eqExpr) =>
|
|
59
82
|
`(if (result i32) (i32.eq (i32.load (local.get $slot)) (local.get $h))
|
|
60
|
-
(then
|
|
83
|
+
(then (if (result i32)
|
|
84
|
+
(i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))
|
|
85
|
+
(then (i32.const 1))
|
|
86
|
+
(else ${eqExpr})))
|
|
87
|
+
(else (i32.const 0)))`
|
|
61
88
|
const strEqG = hashGuard(strEq)
|
|
62
89
|
const sameValueZeroEqG = hashGuard(sameValueZeroEq)
|
|
63
90
|
|
|
@@ -367,34 +394,39 @@ function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing
|
|
|
367
394
|
(i64.const ${missing}))`
|
|
368
395
|
}
|
|
369
396
|
|
|
370
|
-
|
|
397
|
+
// wantValue=true (default): return the slot value, missing → `missing` (i64). wantValue=false:
|
|
398
|
+
// return an i32 0/1 existence flag (for `.has`). Mirrors genLookup's two-mode shape, prehashed.
|
|
399
|
+
function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing = UNDEF_NAN, hasExt = false, wantValue = true) {
|
|
400
|
+
const rt = wantValue ? 'i64' : 'i32'
|
|
401
|
+
const onEmpty = wantValue ? `(return (i64.const ${missing}))` : '(return (i32.const 0))'
|
|
402
|
+
const onFound = wantValue ? '(return (i64.load (i32.add (local.get $slot) (i32.const 16))))' : '(return (i32.const 1))'
|
|
403
|
+
const notFound = wantValue ? `(i64.const ${missing})` : '(i32.const 0)'
|
|
404
|
+
const extHit = wantValue ? '(call $__ext_prop (local.get $coll) (local.get $key))' : '(call $__ext_has (local.get $coll) (local.get $key))'
|
|
371
405
|
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))`
|
|
372
406
|
const typeGuard = hasExt
|
|
373
407
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
374
408
|
(then
|
|
375
409
|
(if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL}))
|
|
376
|
-
(then (return
|
|
377
|
-
(else
|
|
410
|
+
(then (return ${extHit}))
|
|
411
|
+
(else ${onEmpty}))))`
|
|
378
412
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
379
|
-
(then
|
|
413
|
+
(then ${onEmpty}))`
|
|
380
414
|
// SET/MAP/HASH grow by forward-marking; a boxed pointer may be stale → follow the chain.
|
|
381
415
|
const offExpr = '(call $__ptr_offset (local.get $coll))'
|
|
382
|
-
return `(func $${name} (param $coll i64) (param $key i64) (param $h i32) (result
|
|
416
|
+
return `(func $${name} (param $coll i64) (param $key i64) (param $h i32) (result ${rt})
|
|
383
417
|
(local $off i32) (local $cap i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
384
418
|
${typeGuard}
|
|
385
419
|
(local.set $off ${offExpr})
|
|
386
420
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
387
421
|
${probeStart(entrySize)}
|
|
388
422
|
(block $done (loop $probe
|
|
389
|
-
(if (i64.eqz (i64.load (local.get $slot)))
|
|
390
|
-
|
|
391
|
-
(if ${eqExpr}
|
|
392
|
-
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
423
|
+
(if (i64.eqz (i64.load (local.get $slot))) (then ${onEmpty}))
|
|
424
|
+
(if ${eqExpr} (then ${onFound}))
|
|
393
425
|
${probeNext(entrySize)}
|
|
394
426
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
395
427
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
396
428
|
(br $probe)))
|
|
397
|
-
|
|
429
|
+
${notFound})`
|
|
398
430
|
}
|
|
399
431
|
|
|
400
432
|
function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
@@ -440,6 +472,9 @@ export default (ctx) => {
|
|
|
440
472
|
__map_get: () => ctx.features.external ? ['__ext_prop', '__map_set', '__ptr_offset'] : ['__map_set', '__ptr_offset'],
|
|
441
473
|
__map_get_h: () => ctx.features.external ? ['__ext_prop', '__same_value_zero', '__ptr_offset'] : ['__same_value_zero', '__ptr_offset'],
|
|
442
474
|
__map_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ext_has'] : ['__map_hash', '__same_value_zero', '__ptr_offset'],
|
|
475
|
+
// Prehashed has-probes: caller folds the hash, so no __map_hash dependency.
|
|
476
|
+
__map_has_h: () => ctx.features.external ? ['__same_value_zero', '__ptr_offset', '__ext_has'] : ['__same_value_zero', '__ptr_offset'],
|
|
477
|
+
__set_has_h: () => ctx.features.external ? ['__same_value_zero', '__ptr_offset', '__ext_has'] : ['__same_value_zero', '__ptr_offset'],
|
|
443
478
|
__map_delete: ['__map_hash', '__same_value_zero'],
|
|
444
479
|
__map_from: ['__ptr_type', '__ptr_offset', '__len', '__typed_idx', '__map_set', '__mkptr', '__alloc_hdr_n', '__coll_order'],
|
|
445
480
|
__hash_set: () => ctx.features.external
|
|
@@ -628,20 +663,35 @@ export default (ctx) => {
|
|
|
628
663
|
// the Set probe carries a PTR.SET type guard that rejects a Map outright, so
|
|
629
664
|
// routing a Map through `__set_has`/`__set_delete` makes every lookup/delete
|
|
630
665
|
// silently report absent. Mirrors collViewDyn below; typed receivers skip it.
|
|
631
|
-
|
|
666
|
+
// `h` (optional precomputed key hash) appends to the call → routes to the `_h` prehashed
|
|
667
|
+
// probes, skipping __map_hash per access; omitted → the generic hashing probes.
|
|
668
|
+
const collProbeDyn = (mapFn, setFn) => (collExpr, key, h) => {
|
|
632
669
|
inc(mapFn, setFn, '__ptr_type')
|
|
633
670
|
const o = temp('cp'), k = tempI64('cpk')
|
|
671
|
+
const extra = h != null ? [['i32.const', h]] : []
|
|
634
672
|
return typed(['block', ['result', 'f64'],
|
|
635
673
|
['local.set', `$${o}`, asF64(emit(collExpr))],
|
|
636
674
|
['local.set', `$${k}`, asI64(emit(key))],
|
|
637
675
|
['f64.convert_i32_s', ['if', ['result', 'i32'],
|
|
638
676
|
ptrTypeEq(['local.get', `$${o}`], PTR.MAP),
|
|
639
|
-
['then', ['call', `$${mapFn}`, ['i64.reinterpret_f64', ['local.get', `$${o}`]], ['local.get', `$${k}`]]],
|
|
640
|
-
['else', ['call', `$${setFn}`, ['i64.reinterpret_f64', ['local.get', `$${o}`]], ['local.get', `$${k}`]]]]]], 'f64')
|
|
677
|
+
['then', ['call', `$${mapFn}`, ['i64.reinterpret_f64', ['local.get', `$${o}`]], ['local.get', `$${k}`], ...extra]],
|
|
678
|
+
['else', ['call', `$${setFn}`, ['i64.reinterpret_f64', ['local.get', `$${o}`]], ['local.get', `$${k}`], ...extra]]]]], 'f64')
|
|
679
|
+
}
|
|
680
|
+
// `.has` on an unproven receiver: a literal key folds its hash and uses the _h probes.
|
|
681
|
+
ctx.core.emit['.has'] = (collExpr, key) => {
|
|
682
|
+
const h = litKeyHash(key)
|
|
683
|
+
return h != null
|
|
684
|
+
? collProbeDyn('__map_has_h', '__set_has_h')(collExpr, key, h)
|
|
685
|
+
: collProbeDyn('__map_has', '__set_has')(collExpr, key)
|
|
641
686
|
}
|
|
642
|
-
ctx.core.emit['.has'] = collProbeDyn('__map_has', '__set_has')
|
|
643
687
|
ctx.core.emit['.delete'] = collProbeDyn('__map_delete', '__set_delete')
|
|
644
|
-
|
|
688
|
+
// Typed Set.has: literal key → prehashed __set_has_h, else the generic probe.
|
|
689
|
+
ctx.core.emit[`.${VAL.SET}:has`] = (collExpr, key) => {
|
|
690
|
+
const h = litKeyHash(key)
|
|
691
|
+
if (h == null) return call('__set_has', 'II', 'i32')(collExpr, key)
|
|
692
|
+
inc('__set_has_h')
|
|
693
|
+
return typed(['f64.convert_i32_s', ['call', '$__set_has_h', asI64(emit(collExpr)), asI64(emit(key)), ['i32.const', h]]], 'f64')
|
|
694
|
+
}
|
|
645
695
|
ctx.core.emit[`.${VAL.SET}:delete`] = call('__set_delete', 'II', 'i32')
|
|
646
696
|
|
|
647
697
|
// Map.prototype.clear / Set.prototype.clear — drop every entry. `.clear` only
|
|
@@ -666,7 +716,7 @@ export default (ctx) => {
|
|
|
666
716
|
(i32.store (i32.sub (local.get $off) (i32.const 8)) (i32.const 0))))
|
|
667
717
|
(f64.reinterpret_i64 (i64.const ${UNDEF_NAN})))`
|
|
668
718
|
|
|
669
|
-
|
|
719
|
+
registerGetter('.size', (expr) => {
|
|
670
720
|
return typed(['f64.convert_i32_s', ['call', '$__len', ['i64.reinterpret_f64', asF64(emit(expr))]]], 'f64')
|
|
671
721
|
})
|
|
672
722
|
|
|
@@ -691,6 +741,7 @@ export default (ctx) => {
|
|
|
691
741
|
// Generated Set probe functions
|
|
692
742
|
ctx.core.stdlib['__set_add'] = () => genUpsert('__set_add', SET_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.SET, false, ctx.features.external)
|
|
693
743
|
ctx.core.stdlib['__set_has'] = () => genLookup('__set_has', SET_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.SET, false, ctx.features.external)
|
|
744
|
+
ctx.core.stdlib['__set_has_h'] = () => genLookupStrictPrehashed('__set_has_h', SET_ENTRY, sameValueZeroEqG, PTR.SET, UNDEF_NAN, ctx.features.external, false)
|
|
694
745
|
ctx.core.stdlib['__set_delete'] = genDelete('__set_delete', SET_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.SET)
|
|
695
746
|
|
|
696
747
|
// === Map ===
|
|
@@ -717,10 +768,10 @@ export default (ctx) => {
|
|
|
717
768
|
ctx.core.emit[`.${VAL.MAP}:set`] = ctx.core.emit['.set']
|
|
718
769
|
|
|
719
770
|
const emitMapGet = (mapExpr, key) => {
|
|
720
|
-
const
|
|
721
|
-
if (
|
|
771
|
+
const h = litKeyHash(key)
|
|
772
|
+
if (h != null) {
|
|
722
773
|
inc('__map_get_h')
|
|
723
|
-
return typed(['f64.reinterpret_i64', ['call', '$__map_get_h', asI64(emit(mapExpr)), asI64(emit(key)), ['i32.const',
|
|
774
|
+
return typed(['f64.reinterpret_i64', ['call', '$__map_get_h', asI64(emit(mapExpr)), asI64(emit(key)), ['i32.const', h]]], 'f64')
|
|
724
775
|
}
|
|
725
776
|
inc('__map_get')
|
|
726
777
|
return typed(['f64.reinterpret_i64', ['call', '$__map_get', asI64(emit(mapExpr)), asI64(emit(key))]], 'f64')
|
|
@@ -729,7 +780,13 @@ export default (ctx) => {
|
|
|
729
780
|
ctx.core.emit['.get'] = emitMapGet
|
|
730
781
|
ctx.core.emit[`.${VAL.MAP}:get`] = emitMapGet
|
|
731
782
|
|
|
732
|
-
|
|
783
|
+
// Typed Map.has: literal key → prehashed __map_has_h, else the generic probe.
|
|
784
|
+
ctx.core.emit[`.${VAL.MAP}:has`] = (collExpr, key) => {
|
|
785
|
+
const h = litKeyHash(key)
|
|
786
|
+
if (h == null) return call('__map_has', 'II', 'i32')(collExpr, key)
|
|
787
|
+
inc('__map_has_h')
|
|
788
|
+
return typed(['f64.convert_i32_s', ['call', '$__map_has_h', asI64(emit(collExpr)), asI64(emit(key)), ['i32.const', h]]], 'f64')
|
|
789
|
+
}
|
|
733
790
|
ctx.core.emit[`.${VAL.MAP}:delete`] = call('__map_delete', 'II', 'i32')
|
|
734
791
|
|
|
735
792
|
// Map/Set iteration views: keys() / values() / entries() materialize a dense
|
|
@@ -752,22 +809,30 @@ export default (ctx) => {
|
|
|
752
809
|
// statically proven (e.g. a Map read off an object field): resolve MAP vs SET
|
|
753
810
|
// vs ARRAY once at runtime. ARRAY.values() is the array itself; .keys() yields
|
|
754
811
|
// indices; .entries() yields [i, el]. Any other receiver passes through.
|
|
755
|
-
|
|
812
|
+
// A typed array (PTR.TYPED) is NOT PTR.ARRAY, so without an explicit branch it fell
|
|
813
|
+
// through to `local.get $t` (return the receiver). For .values that is correct (the
|
|
814
|
+
// receiver iterates its values), but .keys then yielded values instead of indices and
|
|
815
|
+
// .entries yielded scalars instead of [i, value] pairs. The typedWalk arg restores the
|
|
816
|
+
// right behavior for typed receivers (keys → index array, entries → typed-aware pairs).
|
|
817
|
+
const collViewDyn = (mapWalk, setWalk, arrWalk, typedWalk) => (expr) => {
|
|
756
818
|
inc('__ptr_type')
|
|
757
819
|
const t = temp('cd')
|
|
758
820
|
const pt = () => ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]
|
|
759
821
|
const branch = (tag, walk, rest) =>
|
|
760
822
|
['if', ['result', 'f64'], ['i32.eq', pt(), ['i32.const', tag]], ['then', walk(t)], ['else', rest]]
|
|
761
823
|
const tree = branch(PTR.MAP, mapWalk, branch(PTR.SET, setWalk,
|
|
762
|
-
branch(PTR.ARRAY, arrWalk, ['local.get', `$${t}`])))
|
|
824
|
+
branch(PTR.ARRAY, arrWalk, branch(PTR.TYPED, typedWalk, ['local.get', `$${t}`]))))
|
|
763
825
|
return typed(['block', ['result', 'f64'], ['local.set', `$${t}`, asF64(emit(expr))], tree], 'f64')
|
|
764
826
|
}
|
|
827
|
+
// keys: index array [0..len-1] for both plain and typed (length-based, no element reads).
|
|
765
828
|
ctx.core.emit['.keys'] = collViewDyn(
|
|
766
|
-
t => collKeysFromTemp(t, MAP_ENTRY, 8), t => collKeysFromTemp(t, SET_ENTRY, 8), arrIdxFromTemp)
|
|
829
|
+
t => collKeysFromTemp(t, MAP_ENTRY, 8), t => collKeysFromTemp(t, SET_ENTRY, 8), arrIdxFromTemp, arrIdxFromTemp)
|
|
830
|
+
// values: the receiver iterates its own elements (correct for plain and typed alike).
|
|
767
831
|
ctx.core.emit['.values'] = collViewDyn(
|
|
768
|
-
t => collKeysFromTemp(t, MAP_ENTRY, 16), t => collKeysFromTemp(t, SET_ENTRY, 8), t => ['local.get', `$${t}`])
|
|
832
|
+
t => collKeysFromTemp(t, MAP_ENTRY, 16), t => collKeysFromTemp(t, SET_ENTRY, 8), t => ['local.get', `$${t}`], t => ['local.get', `$${t}`])
|
|
833
|
+
// entries: [i, element] pairs; the typed variant reads elements width/kind-aware.
|
|
769
834
|
ctx.core.emit['.entries'] = collViewDyn(
|
|
770
|
-
t => collEntriesFromTemp(t, MAP_ENTRY, 8, 16), t => collEntriesFromTemp(t, SET_ENTRY, 8, 8), arrEntriesFromTemp)
|
|
835
|
+
t => collEntriesFromTemp(t, MAP_ENTRY, 8, 16), t => collEntriesFromTemp(t, SET_ENTRY, 8, 8), arrEntriesFromTemp, arrEntriesFromTempTyped)
|
|
771
836
|
|
|
772
837
|
// Map/Set forEach(cb): invoke cb(value, key) per live entry in insertion order.
|
|
773
838
|
// Map yields (value=val@16, key=key@8); Set yields (value=key@8, key@8) — the
|
|
@@ -807,6 +872,7 @@ export default (ctx) => {
|
|
|
807
872
|
ctx.core.stdlib['__map_set'] = () => genUpsert('__map_set', MAP_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.MAP, true, ctx.features.external)
|
|
808
873
|
ctx.core.stdlib['__map_get'] = () => genLookup('__map_get', MAP_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.MAP, true, ctx.features.external)
|
|
809
874
|
ctx.core.stdlib['__map_get_h'] = () => genLookupStrictPrehashed('__map_get_h', MAP_ENTRY, sameValueZeroEqG, PTR.MAP, UNDEF_NAN, ctx.features.external)
|
|
875
|
+
ctx.core.stdlib['__map_has_h'] = () => genLookupStrictPrehashed('__map_has_h', MAP_ENTRY, sameValueZeroEqG, PTR.MAP, UNDEF_NAN, ctx.features.external, false)
|
|
810
876
|
ctx.core.stdlib['__map_has'] = () => genLookup('__map_has', MAP_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.MAP, false, ctx.features.external)
|
|
811
877
|
ctx.core.stdlib['__map_delete'] = genDelete('__map_delete', MAP_ENTRY, '$__map_hash', sameValueZeroEqG, PTR.MAP)
|
|
812
878
|
|
|
@@ -869,12 +935,12 @@ export default (ctx) => {
|
|
|
869
935
|
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $s) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
870
936
|
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.STRING})) (i32.shr_u (local.get $aux) (i32.const 14)))
|
|
871
937
|
(then
|
|
872
|
-
(local.set $len (i32.and (local.get $aux) (i32.const 7)))
|
|
938
|
+
(local.set $len (i32.and (i32.shr_u (local.get $aux) (i32.const 10)) (i32.const 7)))
|
|
873
939
|
(block $ds (loop $ls
|
|
874
940
|
(br_if $ds (i32.ge_s (local.get $i) (local.get $len)))
|
|
875
941
|
(local.set $h (i32.mul
|
|
876
942
|
(i32.xor (local.get $h)
|
|
877
|
-
(i32.and (
|
|
943
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $s) (i64.mul (i64.extend_i32_u (local.get $i)) (i64.const 7))) (i64.const 0x7f))))
|
|
878
944
|
(i32.const 0x01000193)))
|
|
879
945
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
880
946
|
(br $ls))))
|
|
@@ -1754,3 +1820,31 @@ function arrEntriesFromTemp(t) {
|
|
|
1754
1820
|
['br', `$aeloop${id}`]]],
|
|
1755
1821
|
out.ptr]
|
|
1756
1822
|
}
|
|
1823
|
+
|
|
1824
|
+
// TypedArray.prototype.entries() → dense Array of [index, element] pairs, reading each
|
|
1825
|
+
// element width/kind-aware via __typed_get_idx (the plain arrEntriesFromTemp uses a raw
|
|
1826
|
+
// f64.load, wrong for non-f64 typed arrays). Guarded on __typed_get_idx being registered:
|
|
1827
|
+
// it only is when the typedarray module is loaded, which is exactly when a PTR.TYPED
|
|
1828
|
+
// value can exist — so when absent, this branch is dead and falls back to the receiver.
|
|
1829
|
+
function arrEntriesFromTempTyped(t) {
|
|
1830
|
+
if (!ctx.core.stdlib['__typed_get_idx']) return ['local.get', `$${t}`]
|
|
1831
|
+
inc('__len', '__typed_get_idx', '__alloc_hdr')
|
|
1832
|
+
const n = tempI32('aetn'), i = tempI32('aeti'), pair = tempI32('aetp')
|
|
1833
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'aet' })
|
|
1834
|
+
const id = ctx.func.uniq++
|
|
1835
|
+
const P = () => ['i64.reinterpret_f64', ['local.get', `$${t}`]]
|
|
1836
|
+
return ['block', ['result', 'f64'],
|
|
1837
|
+
['local.set', `$${n}`, ['call', '$__len', P()]],
|
|
1838
|
+
out.init,
|
|
1839
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1840
|
+
['block', `$aetbrk${id}`, ['loop', `$aetloop${id}`,
|
|
1841
|
+
['br_if', `$aetbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
1842
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
1843
|
+
['f64.store', ['local.get', `$${pair}`], ['f64.convert_i32_s', ['local.get', `$${i}`]]],
|
|
1844
|
+
['f64.store', ['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]],
|
|
1845
|
+
['call', '$__typed_get_idx', P(), ['local.get', `$${i}`]]],
|
|
1846
|
+
elemStore(out.local, i, mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])),
|
|
1847
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1848
|
+
['br', `$aetloop${id}`]]],
|
|
1849
|
+
out.ptr]
|
|
1850
|
+
}
|
package/module/console.js
CHANGED
|
@@ -69,7 +69,7 @@ const setupWasi = (ctx) => {
|
|
|
69
69
|
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
70
70
|
(if (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT}))
|
|
71
71
|
(then
|
|
72
|
-
(local.set $len (i32.and (local.get $aux) (i32.const 7)))
|
|
72
|
+
(local.set $len (i32.and (i32.shr_u (local.get $aux) (i32.const 10)) (i32.const 7)))
|
|
73
73
|
(local.set $buf (call $__alloc (local.get $len)))
|
|
74
74
|
(local.set $off (i32.const 0))
|
|
75
75
|
(block $done (loop $loop
|
package/module/core.js
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, FALSE_NAN, TRUE_NAN, temp, usesDynProps, ptrOffsetIR, isNullish, valKindToPtr, sidecarOverride, undefExpr } from '../src/ir.js'
|
|
13
|
-
import { emit, spread, deps } from '../src/bridge.js'
|
|
13
|
+
import { emit, spread, deps, wat } from '../src/bridge.js'
|
|
14
14
|
import { reconstructArgsWithSpreads } from '../src/ir.js'
|
|
15
15
|
import { valTypeOf, shapeOf } from '../src/kind.js'
|
|
16
16
|
import { T } from '../src/ast.js'
|
|
17
17
|
import { inlineArraySid } from '../src/static.js'
|
|
18
18
|
import { VAL, lookupValType, lookupNotString, repOf, updateRep } from '../src/reps.js'
|
|
19
|
-
import { ctx, err, inc, PTR, LAYOUT, HEAP, emitArity, followForwardingWat, declGlobal } from '../src/ctx.js'
|
|
19
|
+
import { ctx, err, inc, PTR, LAYOUT, HEAP, FORWARDING_MASK, emitArity, followForwardingWat, declGlobal } from '../src/ctx.js'
|
|
20
20
|
import { ptrOffsetFwdWat, STR_INTERN_BIT } from '../layout.js'
|
|
21
21
|
import { nanPrefixHex } from '../layout.js'
|
|
22
22
|
import { initSchema } from './schema.js'
|
|
@@ -36,11 +36,12 @@ export default (ctx) => {
|
|
|
36
36
|
__is_str_key: ['__ptr_type'],
|
|
37
37
|
__str_len: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
38
38
|
__set_len: ['__ptr_offset_fwd'],
|
|
39
|
-
__length: ['__ptr_type', '
|
|
39
|
+
__length: ['__ptr_type', '__str_len', '__len'],
|
|
40
40
|
__alloc: ['__memgrow'],
|
|
41
41
|
__alloc_hdr: ['__alloc'],
|
|
42
42
|
__alloc_hdr_n: ['__alloc'],
|
|
43
43
|
__coll_order: ['__alloc'],
|
|
44
|
+
__obj_clone: ['__ptr_type', '__ptr_aux', '__ptr_offset', '__len', '__cap', '__alloc_hdr', '__alloc_hdr_n', '__mkptr'],
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
ctx.core.stdlib['__is_nullish'] = `(func $__is_nullish (param $v i64) (result i32)
|
|
@@ -143,6 +144,79 @@ export default (ctx) => {
|
|
|
143
144
|
(i64.shl (i64.and (i64.extend_i32_u (local.get $aux)) (i64.const ${LAYOUT.AUX_MASK})) (i64.const ${LAYOUT.AUX_SHIFT}))
|
|
144
145
|
(i64.and (i64.extend_i32_u (local.get $offset)) (i64.const ${LAYOUT.OFFSET_MASK})))))))`
|
|
145
146
|
|
|
147
|
+
// Relative-index clamp to `[0, len]` — the JS `RelativeIndex`/`ToIntegerOrInfinity`
|
|
148
|
+
// bounds dance shared by slice/subarray/fill/copyWithin (string + typed + array).
|
|
149
|
+
// Single shared body so N method bodies don't each inline the same six branches.
|
|
150
|
+
wat('__clamp_idx', `(func $__clamp_idx (param $v i32) (param $len i32) (result i32)
|
|
151
|
+
(if (i32.lt_s (local.get $v) (i32.const 0)) (then (local.set $v (i32.add (local.get $v) (local.get $len)))))
|
|
152
|
+
(if (i32.lt_s (local.get $v) (i32.const 0)) (then (local.set $v (i32.const 0))))
|
|
153
|
+
(if (i32.gt_s (local.get $v) (local.get $len)) (then (local.set $v (local.get $len))))
|
|
154
|
+
(local.get $v))`)
|
|
155
|
+
|
|
156
|
+
// Polymorphic element read for any heap-indexable (ARRAY or TYPED). The one
|
|
157
|
+
// home for `arr[i]` lowering: ARRAY and typed reads both route here, plain-array
|
|
158
|
+
// programs get the ARRAY-only collapse, typed programs the full elem dispatch.
|
|
159
|
+
ctx.core.stdlib['__typed_idx'] = () => {
|
|
160
|
+
if (!ctx.features.typedarray && !ctx.features.external) {
|
|
161
|
+
return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
|
|
162
|
+
(local $len i32)
|
|
163
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
164
|
+
(if (result f64)
|
|
165
|
+
(i32.or
|
|
166
|
+
(i32.lt_s (local.get $i) (i32.const 0))
|
|
167
|
+
(i32.ge_u (local.get $i) (local.get $len)))
|
|
168
|
+
(then (f64.const nan:${UNDEF_NAN}))
|
|
169
|
+
(else (f64.load (i32.add (call $__ptr_offset (local.get $ptr)) (i32.shl (local.get $i) (i32.const 3)))))))`
|
|
170
|
+
}
|
|
171
|
+
// Hot (~37M calls in watr self-host). Type/aux/offset extracted once from $ptr.
|
|
172
|
+
return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
|
|
173
|
+
(local $t i32) (local $off i32) (local $et i32) (local $len i32) (local $aux i32)
|
|
174
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
175
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
176
|
+
;; ARRAY fast path: follow forwarding inline, bounds-check against header len, f64.load — no $__len call.
|
|
177
|
+
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.ARRAY})) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
178
|
+
(then
|
|
179
|
+
${followForwardingWat('$off', { lowGuard: false })}
|
|
180
|
+
(return (if (result f64)
|
|
181
|
+
(i32.and (i32.ge_s (local.get $i) (i32.const 0)) (i32.lt_u (local.get $i) (i32.load (i32.sub (local.get $off) (i32.const 8)))))
|
|
182
|
+
(then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
183
|
+
(else (f64.const nan:${UNDEF_NAN}))))))
|
|
184
|
+
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
185
|
+
(if
|
|
186
|
+
(i32.and
|
|
187
|
+
(i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
|
|
188
|
+
(i32.ne (i32.and (local.get $aux) (i32.const 8)) (i32.const 0)))
|
|
189
|
+
(then (local.set $off (i32.load (i32.add (local.get $off) (i32.const 4))))))
|
|
190
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
191
|
+
(if (result f64)
|
|
192
|
+
(i32.or
|
|
193
|
+
(i32.lt_s (local.get $i) (i32.const 0))
|
|
194
|
+
(i32.ge_u (local.get $i) (local.get $len)))
|
|
195
|
+
(then (f64.const nan:${UNDEF_NAN}))
|
|
196
|
+
(else
|
|
197
|
+
(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
|
|
198
|
+
(then
|
|
199
|
+
(local.set $et (i32.and (local.get $aux) (i32.const 7)))
|
|
200
|
+
(if (result f64) (i32.ge_u (local.get $et) (i32.const 6))
|
|
201
|
+
(then (if (result f64) (i32.eq (local.get $et) (i32.const 7))
|
|
202
|
+
(then (if (result f64) (i32.and (local.get $aux) (i32.const 16))
|
|
203
|
+
(then (f64.reinterpret_i64 (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))
|
|
204
|
+
(else (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))))
|
|
205
|
+
(else (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
206
|
+
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 4))
|
|
207
|
+
(then (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
208
|
+
(then (f64.convert_i32_u (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
|
|
209
|
+
(else (f64.convert_i32_s (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
210
|
+
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 2))
|
|
211
|
+
(then (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
212
|
+
(then (f64.convert_i32_u (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
|
|
213
|
+
(else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))))
|
|
214
|
+
(else (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
215
|
+
(then (f64.convert_i32_u (i32.load8_u (i32.add (local.get $off) (local.get $i)))))
|
|
216
|
+
(else (f64.convert_i32_s (i32.load8_s (i32.add (local.get $off) (local.get $i)))))))))))))
|
|
217
|
+
(else (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))))))`
|
|
218
|
+
}
|
|
219
|
+
|
|
146
220
|
ctx.core.stdlib['__ptr_offset_fwd'] = ptrOffsetFwdWat()
|
|
147
221
|
|
|
148
222
|
ctx.core.stdlib['__ptr_offset'] = `(func $__ptr_offset (param $ptr i64) (result i32)
|
|
@@ -153,9 +227,7 @@ export default (ctx) => {
|
|
|
153
227
|
;; (cap=-1 sentinel at -4, new offset at -8). Other types never forward, so they skip
|
|
154
228
|
;; the loop; a well-formed ptr without forwarding pays one bounds + cap check per hop.
|
|
155
229
|
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
156
|
-
(if (i32.
|
|
157
|
-
(i32.or (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
158
|
-
(i32.or (i32.eq (local.get $t) (i32.const ${PTR.SET})) (i32.eq (local.get $t) (i32.const ${PTR.MAP})))))
|
|
230
|
+
(if (i32.and (i32.shl (i32.const 1) (local.get $t)) (i32.const ${FORWARDING_MASK}))
|
|
159
231
|
(then
|
|
160
232
|
${followForwardingWat('$off', { lowGuard: true })}))
|
|
161
233
|
(local.get $off))`
|
|
@@ -257,12 +329,29 @@ export default (ctx) => {
|
|
|
257
329
|
(call $__memgrow (local.get $next))
|
|
258
330
|
(i32.store (i32.const ${HEAP.PTR_ADDR}) (local.get $next))
|
|
259
331
|
(local.get $ptr))`
|
|
332
|
+
// NOTE: shared memory rewinds to the raw HEAP.START, NOT a post-init high-water
|
|
333
|
+
// mark — so a shared module whose `__start` heap-allocates (strPool memory.init,
|
|
334
|
+
// module-init state) loses that state on `_clear`. Pre-existing; unlike the owned
|
|
335
|
+
// path below it has no `__heap_reset` analogue because the rewind target would need
|
|
336
|
+
// a reserved low-memory cell (the [0,HEAP.START) region is already spoken for —
|
|
337
|
+
// clock at 0, heap ptr at HEAP.PTR_ADDR). Owned memory (the self-host + default
|
|
338
|
+
// case) is the one fixed below; revisit shared if a thread-pooled reset hits it.
|
|
260
339
|
ctx.core.stdlib['__clear'] = `(func $__clear
|
|
261
340
|
(i32.store (i32.const ${HEAP.PTR_ADDR}) (i32.const ${HEAP.START})))`
|
|
262
341
|
} else {
|
|
263
342
|
// Own memory: heap offset in a global, exported so the JS-side adapter
|
|
264
343
|
// (alloc:false, no `_alloc` export) shares the pointer.
|
|
265
344
|
declGlobal('__heap', 'i32', HEAP.START, { export: '__heap' })
|
|
345
|
+
// `__clear` rewinds to the *post-module-init* high-water mark, not the static
|
|
346
|
+
// data end: a module whose top-level code heap-allocates (e.g. the self-host
|
|
347
|
+
// compiler building its GLOBALS/atom tables in `__start`) leaves live state
|
|
348
|
+
// above the data segment that a reset must preserve. `__heap_reset` is seeded
|
|
349
|
+
// to the data end (assemble.js heapBase patch) and overwritten by `__start`'s
|
|
350
|
+
// tail with the heap top after init runs (buildStartFn) — so for a module with
|
|
351
|
+
// no init allocations it equals the data end, and for self-host it spares the
|
|
352
|
+
// compiler's init state. (Distinct from `__heap_start`, the propsPtr watermark,
|
|
353
|
+
// which must stay at the data end or init-time heap objects misread as static.)
|
|
354
|
+
declGlobal('__heap_reset', 'i32', HEAP.START)
|
|
266
355
|
ctx.core.stdlib['__alloc'] = `(func $__alloc (param $bytes i32) (result i32)
|
|
267
356
|
(local $ptr i32) (local $next i32)
|
|
268
357
|
(local.set $ptr (global.get $__heap))
|
|
@@ -271,7 +360,7 @@ export default (ctx) => {
|
|
|
271
360
|
(global.set $__heap (local.get $next))
|
|
272
361
|
(local.get $ptr))`
|
|
273
362
|
ctx.core.stdlib['__clear'] = `(func $__clear
|
|
274
|
-
(global.set $__heap (
|
|
363
|
+
(global.set $__heap (global.get $__heap_reset)))`
|
|
275
364
|
}
|
|
276
365
|
|
|
277
366
|
// Build an insertion-ordered list of live slot offsets for a Set/Map/HASH
|
|
@@ -409,14 +498,15 @@ export default (ctx) => {
|
|
|
409
498
|
(else (i32.load (i32.sub (local.get $off) (i32.const 4))))))
|
|
410
499
|
(else (i32.const 0))))`
|
|
411
500
|
|
|
412
|
-
// String length (UTF-8 byte count). Heap: [-4:len(i32)][chars...]; SSO
|
|
501
|
+
// String length (UTF-8 byte count). Heap: [-4:len(i32)][chars...]; SSO (7-bit codec):
|
|
502
|
+
// len at aux bits 10-12 (= payload bits 42-44). See module/string.js codec.
|
|
413
503
|
ctx.core.stdlib['__str_len'] = `(func $__str_len (param $ptr i64) (result i32)
|
|
414
504
|
(local $off i32) (local $aux i32)
|
|
415
505
|
(if (i32.ne (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.STRING}))
|
|
416
506
|
(then (return (i32.const 0))))
|
|
417
507
|
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
418
508
|
(if (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT}))
|
|
419
|
-
(then (return (i32.and (local.get $aux) (i32.const 7)))))
|
|
509
|
+
(then (return (i32.and (i32.shr_u (local.get $aux) (i32.const 10)) (i32.const 7)))))
|
|
420
510
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
421
511
|
(if (result i32) (i32.ge_u (local.get $off) (i32.const 4))
|
|
422
512
|
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
@@ -477,6 +567,56 @@ export default (ctx) => {
|
|
|
477
567
|
(memory.fill (i32.add (local.get $ptr) (i32.const 16)) (i32.const 0) (i32.mul (local.get $cap) (local.get $stride)))
|
|
478
568
|
(i32.add (local.get $ptr) (i32.const 16)))`
|
|
479
569
|
|
|
570
|
+
// Shallow clone of an OBJECT or HASH, preserving its runtime type — the copy
|
|
571
|
+
// semantics of a single unknown spread `{ ...src }` (module/object.js). Without
|
|
572
|
+
// this, `{ ...src }` aliases src, so any later write to the result mutates the
|
|
573
|
+
// source (a real bug: jz's own narrow.js had to route around it). Per JS spread,
|
|
574
|
+
// the clone is SHALLOW: scalar slots are copied by value; nested object/string
|
|
575
|
+
// pointers are shared (immutable strings; nested objects are aliased as in V8).
|
|
576
|
+
//
|
|
577
|
+
// - OBJECT: alloc a fresh header'd object with the same schemaId and copy its N
|
|
578
|
+
// schema slots (N = key count of __schema_tbl[sid], robust to static-segment
|
|
579
|
+
// sources that carry no len/cap header). Then deep-copy the per-instance
|
|
580
|
+
// dyn-props HASH (base-16) so `o[k]=v` keys added before the spread carry over
|
|
581
|
+
// independently — heap objects only; static-segment objects have no header.
|
|
582
|
+
// - HASH: copy header + every probe slot wholesale (entries hold immutable
|
|
583
|
+
// string keys + scalar/pointer values — a byte copy is an independent dict).
|
|
584
|
+
// - anything else (primitive): nothing to clone, return as-is.
|
|
585
|
+
ctx.core.stdlib['__obj_clone'] = `(func $__obj_clone (param $v f64) (result f64)
|
|
586
|
+
(local $bits i64) (local $t i32) (local $sid i32) (local $n i32) (local $cap i32)
|
|
587
|
+
(local $src i32) (local $dst i32) (local $props i64)
|
|
588
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $v)))
|
|
589
|
+
(local.set $t (call $__ptr_type (local.get $bits)))
|
|
590
|
+
(if (i32.eq (local.get $t) (i32.const ${PTR.OBJECT}))
|
|
591
|
+
(then
|
|
592
|
+
(local.set $sid (call $__ptr_aux (local.get $bits)))
|
|
593
|
+
(local.set $src (call $__ptr_offset (local.get $bits)))
|
|
594
|
+
(local.set $n (i32.const 0))
|
|
595
|
+
(if (i32.ne (global.get $__schema_tbl) (i32.const 0))
|
|
596
|
+
(then (local.set $n (call $__len
|
|
597
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))))
|
|
598
|
+
(local.set $cap (i32.add (local.get $n) (i32.eqz (local.get $n))))
|
|
599
|
+
(local.set $dst (call $__alloc_hdr (i32.const 0) (local.get $cap)))
|
|
600
|
+
(memory.copy (local.get $dst) (local.get $src) (i32.shl (local.get $n) (i32.const 3)))
|
|
601
|
+
(if (i32.ge_u (local.get $src) (global.get $__heap_start))
|
|
602
|
+
(then
|
|
603
|
+
(local.set $props (i64.load (i32.sub (local.get $src) (i32.const 16))))
|
|
604
|
+
(if (i32.eq (call $__ptr_type (local.get $props)) (i32.const ${PTR.HASH}))
|
|
605
|
+
(then (i64.store (i32.sub (local.get $dst) (i32.const 16))
|
|
606
|
+
(i64.reinterpret_f64 (call $__obj_clone (f64.reinterpret_i64 (local.get $props)))))))))
|
|
607
|
+
(return (call $__mkptr (i32.const ${PTR.OBJECT}) (local.get $sid) (local.get $dst)))))
|
|
608
|
+
(if (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
609
|
+
(then
|
|
610
|
+
(local.set $cap (call $__cap (local.get $bits)))
|
|
611
|
+
(local.set $src (call $__ptr_offset (local.get $bits)))
|
|
612
|
+
(local.set $dst (call $__alloc_hdr_n (i32.const 0) (local.get $cap) (i32.const 24)))
|
|
613
|
+
(memory.copy
|
|
614
|
+
(i32.sub (local.get $dst) (i32.const 16))
|
|
615
|
+
(i32.sub (local.get $src) (i32.const 16))
|
|
616
|
+
(i32.add (i32.const 16) (i32.mul (local.get $cap) (i32.const 24))))
|
|
617
|
+
(return (call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0) (local.get $dst)))))
|
|
618
|
+
(local.get $v))`
|
|
619
|
+
|
|
480
620
|
// Allocator + exports are deferred: only included when memory is actually needed.
|
|
481
621
|
// Any module using allocPtr/inc('__alloc') pulls these in via ctx.core.stdlibDeps.
|
|
482
622
|
// compile.js emits _alloc/_clear exports + memory section only when __alloc is in includes.
|
|
@@ -732,12 +872,14 @@ export default (ctx) => {
|
|
|
732
872
|
const eqT = (n) => `(i32.eq (local.get $t) (i32.const ${n}))`
|
|
733
873
|
let disj = eqT(types[0])
|
|
734
874
|
for (let i = 1; i < types.length; i++) disj = `(i32.or ${disj} ${eqT(types[i])})`
|
|
735
|
-
const lenArm = `(
|
|
875
|
+
const lenArm = `(block (result f64)
|
|
876
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $v) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
877
|
+
(if (result f64) ${disj}
|
|
736
878
|
(then
|
|
737
879
|
(if (result f64) (i32.ge_u (local.get $off) (i32.const 8))
|
|
738
880
|
(then (f64.convert_i32_s (call $__len (local.get $v))))
|
|
739
881
|
(else (f64.const nan:${UNDEF_NAN}))))
|
|
740
|
-
(else (f64.const nan:${UNDEF_NAN})))`
|
|
882
|
+
(else (f64.const nan:${UNDEF_NAN}))))`
|
|
741
883
|
const stringArm = `(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
742
884
|
(then (f64.convert_i32_s (call $__str_len (local.get $v))))
|
|
743
885
|
(else ${lenArm}))`
|
|
@@ -748,7 +890,6 @@ export default (ctx) => {
|
|
|
748
890
|
(then (f64.const nan:${UNDEF_NAN}))
|
|
749
891
|
(else
|
|
750
892
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
751
|
-
(local.set $off (call $__ptr_offset (local.get $v)))
|
|
752
893
|
${stringArm})))`
|
|
753
894
|
}
|
|
754
895
|
|
|
@@ -825,8 +966,9 @@ export default (ctx) => {
|
|
|
825
966
|
const ptRep = typeof obj === 'string' ? repOf(obj) : null
|
|
826
967
|
const ptVt = ptRep ? ptRep.val : valTypeOf(obj)
|
|
827
968
|
if (ptVt) {
|
|
828
|
-
const
|
|
829
|
-
|
|
969
|
+
const tpKey = `.${ptVt}:${prop}`
|
|
970
|
+
const tpEmitter = ctx.core.emit[tpKey]
|
|
971
|
+
if (tpEmitter && ctx.core.getters.has(tpKey)) return tpEmitter(obj)
|
|
830
972
|
}
|
|
831
973
|
|
|
832
974
|
// valueOf/toString are ToPrimitive hooks (ES2024 7.1.1) that an own data
|
|
@@ -853,7 +995,7 @@ export default (ctx) => {
|
|
|
853
995
|
// fall through to a real property read — `m.values` reads the "values" field.
|
|
854
996
|
const propKey = `.${prop}`
|
|
855
997
|
const propEmitter = ctx.core.emit[propKey]
|
|
856
|
-
if (propEmitter
|
|
998
|
+
if (propEmitter && ctx.core.getters.has(propKey)) return propEmitter(obj)
|
|
857
999
|
|
|
858
1000
|
return emitPropAccess(emit(obj), obj, prop)
|
|
859
1001
|
}
|
|
@@ -906,11 +1048,13 @@ export default (ctx) => {
|
|
|
906
1048
|
// re-emitting `obj`. Without this `s?.size` fell straight to emitPropAccess (a
|
|
907
1049
|
// plain field read) and returned undefined — a Set/Map size getter never ran.
|
|
908
1050
|
if (vt) {
|
|
909
|
-
const
|
|
910
|
-
|
|
1051
|
+
const tgKey = `.${vt}:${prop}`
|
|
1052
|
+
const tg = ctx.core.emit[tgKey]
|
|
1053
|
+
if (tg && ctx.core.getters.has(tgKey)) return tg(t)
|
|
911
1054
|
}
|
|
912
|
-
const
|
|
913
|
-
|
|
1055
|
+
const gKey = `.${prop}`
|
|
1056
|
+
const g = ctx.core.emit[gKey]
|
|
1057
|
+
if (g && ctx.core.getters.has(gKey)) return g(t)
|
|
914
1058
|
return emitPropAccess(typed(['local.get', `$${t}`], 'f64'), obj, prop, true)
|
|
915
1059
|
})
|
|
916
1060
|
|
package/module/json.js
CHANGED
|
@@ -711,12 +711,12 @@ export default (ctx) => {
|
|
|
711
711
|
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|
|
712
712
|
${ADV(2)})
|
|
713
713
|
(else
|
|
714
|
-
;; Pack first 4 bytes into SSO slot (used only when len ≤ 4).
|
|
714
|
+
;; Pack first 4 bytes into SSO slot (used only when len ≤ 4): 7-bit ASCII, char at bit len*7.
|
|
715
715
|
(if (i32.lt_u (local.get $len) (i32.const 4))
|
|
716
716
|
(then (local.set $sso
|
|
717
717
|
(i32.or (local.get $sso)
|
|
718
718
|
(i32.shl (i32.and (local.get $ch) (i32.const 0xFF))
|
|
719
|
-
(i32.
|
|
719
|
+
(i32.mul (local.get $len) (i32.const 7)))))))
|
|
720
720
|
(local.set $h (i32.mul (i32.xor (local.get $h) (i32.and (local.get $ch) (i32.const 0xFF))) (i32.const 0x01000193)))
|
|
721
721
|
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|
|
722
722
|
${ADV(1)}))
|
|
@@ -735,7 +735,7 @@ export default (ctx) => {
|
|
|
735
735
|
;; SSO fast path: ≤4 ASCII chars, no escapes — bytes already packed inline.
|
|
736
736
|
(if (i32.and (local.get $simple) (i32.le_u (local.get $len) (i32.const 4)))
|
|
737
737
|
(then
|
|
738
|
-
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $len)) (local.get $sso)))))
|
|
738
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.shl (local.get $len) (i32.const 10))) (local.get $sso)))))
|
|
739
739
|
;; Simple STRING fast path: no escapes, len > 4 — bulk memcpy from parse buffer,
|
|
740
740
|
;; skip rewind + per-byte escape-decode loop. Hits 5+ char keys without escapes.
|
|
741
741
|
(if (local.get $simple)
|