jz 0.3.1 → 0.5.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 +277 -258
- package/cli.js +10 -52
- package/index.js +181 -29
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +195 -76
- package/module/collection.js +249 -20
- package/module/console.js +1 -1
- package/module/core.js +267 -119
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +367 -61
- package/module/math.js +568 -128
- package/module/number.js +390 -227
- package/module/object.js +352 -39
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +555 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +12 -6
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1872 -487
- package/src/assemble.js +58 -20
- package/src/autoload.js +13 -1
- package/src/codegen.js +177 -0
- package/src/compile.js +462 -186
- package/src/ctx.js +85 -18
- package/src/emit.js +668 -197
- package/src/infer.js +548 -0
- package/src/ir.js +302 -27
- package/src/jzify.js +898 -259
- package/src/narrow.js +455 -246
- package/src/optimize.js +263 -99
- package/src/plan.js +713 -35
- package/src/prepare.js +818 -293
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
package/module/collection.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64, allocPtr, undefExpr } from '../src/ir.js'
|
|
12
12
|
import { emit, emitFlat } from '../src/emit.js'
|
|
13
13
|
import { valTypeOf, lookupValType, VAL } from '../src/analyze.js'
|
|
14
|
+
import { hasOwnContinue } from '../src/analyze.js'
|
|
14
15
|
import { inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
15
16
|
|
|
16
17
|
const SET_ENTRY = 16 // hash + key
|
|
@@ -93,19 +94,20 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
/** Generate lookup probe function.
|
|
96
|
-
* wantValue=true: return slot value, missing =>
|
|
97
|
+
* wantValue=true: return slot value, missing => `undefined` (UNDEF_NAN) — a
|
|
98
|
+
* missing Map entry / object property reads as `undefined` in JS, never null.
|
|
97
99
|
* wantValue=false: return i32 0/1 existence flag.
|
|
98
100
|
* hasExt: emit EXTERNAL fallthrough (delegate to __ext_prop/__ext_has). */
|
|
99
101
|
function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, hasExt) {
|
|
100
102
|
const rt = wantValue ? 'i64' : 'i32'
|
|
101
103
|
const onEmpty = wantValue
|
|
102
|
-
? `(return (i64.const ${
|
|
104
|
+
? `(return (i64.const ${UNDEF_NAN}))`
|
|
103
105
|
: '(return (i32.const 0))'
|
|
104
106
|
const onFound = wantValue
|
|
105
107
|
? '(return (i64.load (i32.add (local.get $slot) (i32.const 16))))'
|
|
106
108
|
: '(return (i32.const 1))'
|
|
107
109
|
const notFound = wantValue
|
|
108
|
-
? `(i64.const ${
|
|
110
|
+
? `(i64.const ${UNDEF_NAN})`
|
|
109
111
|
: '(i32.const 0)'
|
|
110
112
|
const tExpr = `(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))`
|
|
111
113
|
const typeGuard = hasExt
|
|
@@ -186,7 +188,7 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
186
188
|
(if (i32.ge_s (i32.mul (local.get $size) (i32.const 4)) (i32.mul (local.get $cap) (i32.const 3)))
|
|
187
189
|
(then
|
|
188
190
|
(local.set $newcap (i32.shl (local.get $cap) (i32.const 1)))
|
|
189
|
-
(local.set $newptr (call $
|
|
191
|
+
(local.set $newptr (call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${entrySize})))
|
|
190
192
|
(local.set $i (i32.const 0))
|
|
191
193
|
(block $rd (loop $rl
|
|
192
194
|
(br_if $rd (i32.ge_s (local.get $i) (local.get $cap)))
|
|
@@ -335,14 +337,14 @@ export default (ctx) => {
|
|
|
335
337
|
__hash_has: () => ctx.features.external
|
|
336
338
|
? ['__str_hash', '__str_eq', '__ptr_type', '__ext_has']
|
|
337
339
|
: ['__str_hash', '__str_eq', '__ptr_type'],
|
|
338
|
-
__hash_new: ['
|
|
339
|
-
__hash_new_small: ['
|
|
340
|
+
__hash_new: ['__alloc_hdr_n'],
|
|
341
|
+
__hash_new_small: ['__alloc_hdr_n', '__mkptr'],
|
|
340
342
|
__hash_get_local: ['__str_hash', '__str_eq'],
|
|
341
343
|
__hash_get_local_h: ['__str_eq'],
|
|
342
344
|
__hash_set_local_h: ['__str_eq'],
|
|
343
|
-
__hash_set_local: ['__str_hash', '__str_eq'],
|
|
345
|
+
__hash_set_local: ['__str_hash', '__str_eq', '__alloc_hdr_n', '__mkptr'],
|
|
344
346
|
__ihash_get_local: ['__map_hash'],
|
|
345
|
-
__ihash_set_local: ['__map_hash', '
|
|
347
|
+
__ihash_set_local: ['__map_hash', '__alloc_hdr_n', '__mkptr'],
|
|
346
348
|
__dyn_get_t: ['__dyn_get_t_h', '__str_hash'],
|
|
347
349
|
__dyn_get_t_h: ['__ihash_get_local', '__str_eq', '__is_nullish'],
|
|
348
350
|
__dyn_get: ['__dyn_get_t', '__ptr_type'],
|
|
@@ -353,9 +355,15 @@ export default (ctx) => {
|
|
|
353
355
|
__dyn_get_any_t: () => ctx.features.external
|
|
354
356
|
? ['__dyn_get_t', '__hash_get_local', '__ext_prop']
|
|
355
357
|
: ['__dyn_get_t', '__hash_get_local'],
|
|
358
|
+
__dyn_get_any_t_h: () => ctx.features.external
|
|
359
|
+
? ['__dyn_get_t_h', '__hash_get_local_h', '__ext_prop']
|
|
360
|
+
: ['__dyn_get_t_h', '__hash_get_local_h'],
|
|
356
361
|
__dyn_get_or: ['__dyn_get'],
|
|
357
362
|
__dyn_set: ['__hash_new', '__hash_new_small', '__ihash_get_local', '__ihash_set_local', '__hash_set_local', '__ptr_offset', '__is_nullish', '__str_eq'],
|
|
358
363
|
__dyn_move: ['__ihash_get_local', '__ihash_set_local', '__is_nullish'],
|
|
364
|
+
__hash_del_local: ['__str_hash', '__str_eq', '__ptr_type'],
|
|
365
|
+
__dyn_del: ['__hash_del_local', '__ihash_get_local', '__is_nullish'],
|
|
366
|
+
__coll_clear: ['__ptr_type', '__ptr_offset'],
|
|
359
367
|
})
|
|
360
368
|
|
|
361
369
|
inc('__ptr_offset', '__cap')
|
|
@@ -437,14 +445,57 @@ export default (ctx) => {
|
|
|
437
445
|
// __map_new() → f64 — allocate empty Map (for JSON.parse, runtime creation)
|
|
438
446
|
ctx.core.stdlib['__map_new'] = `(func $__map_new (result f64)
|
|
439
447
|
(call $__mkptr (i32.const ${PTR.MAP}) (i32.const 0)
|
|
440
|
-
(call $
|
|
448
|
+
(call $__alloc_hdr_n (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))`
|
|
441
449
|
|
|
442
450
|
// === Set ===
|
|
443
451
|
|
|
444
|
-
ctx.core.emit['new.Set'] = () => {
|
|
452
|
+
ctx.core.emit['new.Set'] = (iterExpr) => {
|
|
445
453
|
ctx.features.set = true
|
|
446
|
-
|
|
447
|
-
|
|
454
|
+
if (iterExpr == null) {
|
|
455
|
+
const out = allocPtr({ type: PTR.SET, len: 0, cap: INIT_CAP, stride: SET_ENTRY, tag: 'set' })
|
|
456
|
+
return typed(['block', ['result', 'f64'], out.init, out.ptr], 'f64')
|
|
457
|
+
}
|
|
458
|
+
// new Set(iterable): seed from an array argument's elements. __set_add does
|
|
459
|
+
// SameValueZero dedup + −0 normalization. A non-array argument leaves the set
|
|
460
|
+
// empty — the ptr_type guard zeroes the length so the loop is skipped.
|
|
461
|
+
//
|
|
462
|
+
// __set_add is a FIXED-capacity probe (genUpsert never grows the table); a
|
|
463
|
+
// full table makes its probe loop spin forever. So the table is pre-sized to
|
|
464
|
+
// the smallest power of two greater than 2*len: distinct entries ≤ len, so the
|
|
465
|
+
// table stays ≤50% full and every insert finds a free slot. cap = 1 <<
|
|
466
|
+
// (32 − clz(m−1)) with m = 2*len + INIT_CAP rounds 2*len up to a power of two
|
|
467
|
+
// and floors at INIT_CAP for the empty/short case.
|
|
468
|
+
inc('__set_add', '__ptr_type', '__len', '__typed_idx')
|
|
469
|
+
const setL = temp('nss'), arrL = temp('nsa')
|
|
470
|
+
const iL = tempI32('nsi'), lenL = tempI32('nsl')
|
|
471
|
+
const capExpr = ['i32.shl', ['i32.const', 1],
|
|
472
|
+
['i32.sub', ['i32.const', 32], ['i32.clz',
|
|
473
|
+
['i32.sub',
|
|
474
|
+
['i32.add', ['i32.shl', ['local.get', `$${lenL}`], ['i32.const', 1]], ['i32.const', INIT_CAP]],
|
|
475
|
+
['i32.const', 1]]]]]
|
|
476
|
+
const out = allocPtr({ type: PTR.SET, len: 0, cap: capExpr, stride: SET_ENTRY, tag: 'set' })
|
|
477
|
+
return typed(['block', ['result', 'f64'],
|
|
478
|
+
['local.set', `$${arrL}`, asF64(emit(iterExpr))],
|
|
479
|
+
['local.set', `$${lenL}`, ['i32.const', 0]],
|
|
480
|
+
['if', ['i32.eq',
|
|
481
|
+
['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${arrL}`]]],
|
|
482
|
+
['i32.const', PTR.ARRAY]],
|
|
483
|
+
['then', ['local.set', `$${lenL}`,
|
|
484
|
+
['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${arrL}`]]]]]],
|
|
485
|
+
out.init,
|
|
486
|
+
['local.set', `$${setL}`, out.ptr],
|
|
487
|
+
['local.set', `$${iL}`, ['i32.const', 0]],
|
|
488
|
+
['block', `$d_${iL}`, ['loop', `$l_${iL}`,
|
|
489
|
+
['br_if', `$d_${iL}`, ['i32.ge_s', ['local.get', `$${iL}`], ['local.get', `$${lenL}`]]],
|
|
490
|
+
['local.set', `$${setL}`, ['f64.reinterpret_i64',
|
|
491
|
+
['call', '$__set_add',
|
|
492
|
+
['i64.reinterpret_f64', ['local.get', `$${setL}`]],
|
|
493
|
+
['i64.reinterpret_f64', ['call', '$__typed_idx',
|
|
494
|
+
['i64.reinterpret_f64', ['local.get', `$${arrL}`]],
|
|
495
|
+
['local.get', `$${iL}`]]]]]],
|
|
496
|
+
['local.set', `$${iL}`, ['i32.add', ['local.get', `$${iL}`], ['i32.const', 1]]],
|
|
497
|
+
['br', `$l_${iL}`]]],
|
|
498
|
+
['local.get', `$${setL}`]], 'f64')
|
|
448
499
|
}
|
|
449
500
|
|
|
450
501
|
ctx.core.emit['.add'] = (setExpr, val) => {
|
|
@@ -462,10 +513,55 @@ export default (ctx) => {
|
|
|
462
513
|
return typed(['f64.convert_i32_s', ['call', '$__set_delete', asI64(emit(setExpr)), asI64(emit(val))]], 'f64')
|
|
463
514
|
}
|
|
464
515
|
|
|
516
|
+
// Map.prototype.clear / Set.prototype.clear — drop every entry. `.clear` only
|
|
517
|
+
// exists on Map/Set in JS, so a single generic emitter is unambiguous; the
|
|
518
|
+
// stdlib reads the ptr tag to pick the entry stride.
|
|
519
|
+
ctx.core.emit['.clear'] = (collExpr) => {
|
|
520
|
+
inc('__coll_clear')
|
|
521
|
+
return typed(['call', '$__coll_clear', asI64(emit(collExpr))], 'f64')
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Zero every slot (so probes see empty) and reset the length header. Entry
|
|
525
|
+
// stride is 16 for a SET, 24 for a MAP — `(t == MAP) << 3` adds the 8-byte
|
|
526
|
+
// value column. Returns undefined; a non-collation arg is a guarded no-op.
|
|
527
|
+
ctx.core.stdlib['__coll_clear'] = `(func $__coll_clear (param $coll i64) (result f64)
|
|
528
|
+
(local $off i32) (local $cap i32) (local $t i32)
|
|
529
|
+
(local.set $t (call $__ptr_type (local.get $coll)))
|
|
530
|
+
(if (i32.or (i32.eq (local.get $t) (i32.const ${PTR.SET})) (i32.eq (local.get $t) (i32.const ${PTR.MAP})))
|
|
531
|
+
(then
|
|
532
|
+
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
533
|
+
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
534
|
+
(memory.fill (local.get $off) (i32.const 0)
|
|
535
|
+
(i32.mul (local.get $cap)
|
|
536
|
+
(i32.add (i32.const ${SET_ENTRY})
|
|
537
|
+
(i32.shl (i32.eq (local.get $t) (i32.const ${PTR.MAP})) (i32.const 3)))))
|
|
538
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8)) (i32.const 0))))
|
|
539
|
+
(f64.reinterpret_i64 (i64.const ${UNDEF_NAN})))`
|
|
540
|
+
|
|
465
541
|
ctx.core.emit['.size'] = (expr) => {
|
|
466
542
|
return typed(['f64.convert_i32_s', ['call', '$__len', ['i64.reinterpret_f64', asF64(emit(expr))]]], 'f64')
|
|
467
543
|
}
|
|
468
544
|
|
|
545
|
+
// x instanceof Map / Set — typed-pointer predicates emitted by jzify. NaN-check
|
|
546
|
+
// first (non-pointer numbers must report false), then compare __ptr_type tag.
|
|
547
|
+
// Mirrors module/array.js's Array.isArray inline form. Result is i32 (boolean).
|
|
548
|
+
ctx.core.emit['__is_map'] = (x) => {
|
|
549
|
+
inc('__ptr_type')
|
|
550
|
+
const v = asF64(emit(x))
|
|
551
|
+
const t = temp('imap')
|
|
552
|
+
return typed(['i32.and',
|
|
553
|
+
['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
554
|
+
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', PTR.MAP]]], 'i32')
|
|
555
|
+
}
|
|
556
|
+
ctx.core.emit['__is_set'] = (x) => {
|
|
557
|
+
inc('__ptr_type')
|
|
558
|
+
const v = asF64(emit(x))
|
|
559
|
+
const t = temp('iset')
|
|
560
|
+
return typed(['i32.and',
|
|
561
|
+
['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
562
|
+
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', PTR.SET]]], 'i32')
|
|
563
|
+
}
|
|
564
|
+
|
|
469
565
|
// Generated Set probe functions
|
|
470
566
|
ctx.core.stdlib['__set_add'] = () => genUpsert('__set_add', SET_ENTRY, '$__map_hash', sameValueZeroEq, PTR.SET, false, ctx.features.external)
|
|
471
567
|
ctx.core.stdlib['__set_has'] = () => genLookup('__set_has', SET_ENTRY, '$__map_hash', sameValueZeroEq, PTR.SET, false, ctx.features.external)
|
|
@@ -512,7 +608,7 @@ export default (ctx) => {
|
|
|
512
608
|
// Generated Map probe functions
|
|
513
609
|
ctx.core.stdlib['__map_set'] = () => genUpsert('__map_set', MAP_ENTRY, '$__map_hash', sameValueZeroEq, PTR.MAP, true, ctx.features.external)
|
|
514
610
|
ctx.core.stdlib['__map_get'] = () => genLookup('__map_get', MAP_ENTRY, '$__map_hash', sameValueZeroEq, PTR.MAP, true, ctx.features.external)
|
|
515
|
-
ctx.core.stdlib['__map_get_h'] = () => genLookupStrictPrehashed('__map_get_h', MAP_ENTRY, sameValueZeroEq, PTR.MAP,
|
|
611
|
+
ctx.core.stdlib['__map_get_h'] = () => genLookupStrictPrehashed('__map_get_h', MAP_ENTRY, sameValueZeroEq, PTR.MAP, UNDEF_NAN, ctx.features.external)
|
|
516
612
|
ctx.core.stdlib['__map_has'] = () => genLookup('__map_has', MAP_ENTRY, '$__map_hash', sameValueZeroEq, PTR.MAP, false, ctx.features.external)
|
|
517
613
|
ctx.core.stdlib['__map_delete'] = genDelete('__map_delete', MAP_ENTRY, '$__map_hash', sameValueZeroEq, PTR.MAP)
|
|
518
614
|
|
|
@@ -567,19 +663,25 @@ export default (ctx) => {
|
|
|
567
663
|
|
|
568
664
|
ctx.core.stdlib['__hash_new'] = `(func $__hash_new (result f64)
|
|
569
665
|
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
570
|
-
(call $
|
|
666
|
+
(call $__alloc_hdr_n (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))`
|
|
571
667
|
|
|
572
668
|
// Small initial capacity for propsPtr-style hashes (per-object dyn props).
|
|
573
669
|
// Most receivers in real code carry 0-2 dyn props; paying 8-slot up-front
|
|
574
670
|
// is wasted memory + probe-loop cache pressure. Grows to 4/8/... on demand.
|
|
671
|
+
// L3/'speed' opts into a larger initial cap (default 8) to skip 2→4→8 growth
|
|
672
|
+
// when AST-style nodes carry 3-5 props (watr.compile's profile).
|
|
673
|
+
const smallCap = Math.max(ctx.transform.optimize?.hashSmallInitCap | 0, 2)
|
|
575
674
|
ctx.core.stdlib['__hash_new_small'] = `(func $__hash_new_small (result f64)
|
|
576
675
|
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
577
|
-
(call $
|
|
676
|
+
(call $__alloc_hdr_n (i32.const 0) (i32.const ${smallCap}) (i32.const ${MAP_ENTRY}))))`
|
|
578
677
|
|
|
579
678
|
ctx.core.stdlib['__hash_get_local'] = genLookupStrict('__hash_get_local', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH)
|
|
580
679
|
ctx.core.stdlib['__hash_get_local_h'] = genLookupStrictPrehashed('__hash_get_local_h', MAP_ENTRY, strEq, PTR.HASH)
|
|
581
680
|
ctx.core.stdlib['__hash_set_local_h'] = genUpsertStrictPrehashed('__hash_set_local_h', MAP_ENTRY, strEq, PTR.HASH)
|
|
582
681
|
ctx.core.stdlib['__hash_set_local'] = genUpsertGrow('__hash_set_local', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, true)
|
|
682
|
+
// Tombstones an entry in a HASH (string keys). Returns 1 if found+deleted, 0 otherwise.
|
|
683
|
+
// Used as the bucket-level primitive for __dyn_del.
|
|
684
|
+
ctx.core.stdlib['__hash_del_local'] = genDelete('__hash_del_local', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH)
|
|
583
685
|
// Outer __dyn_props hash: keyed by object offset (i32 as f64 bits), value is per-object props hash.
|
|
584
686
|
// Uses bit-hash + i64.eq — no string allocation for the unique integer key.
|
|
585
687
|
ctx.core.stdlib['__ihash_get_local'] = genLookupStrict('__ihash_get_local', MAP_ENTRY, '$__map_hash', '(i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))', PTR.HASH)
|
|
@@ -709,6 +811,11 @@ export default (ctx) => {
|
|
|
709
811
|
(br_if $haveProps (i32.eq
|
|
710
812
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $props) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
711
813
|
(i32.const ${PTR.HASH})))
|
|
814
|
+
;; Fresh array (header propsPtr=0, no shift, no props): skip the
|
|
815
|
+
;; global hash probe — there's nothing to find. Shifted arrays read
|
|
816
|
+
;; forwarding bytes here (low32=newOff, high32=-1) → non-zero, so
|
|
817
|
+
;; this br_if doesn't fire and they fall through to __dyn_props.
|
|
818
|
+
(br_if $dynDone (i64.eqz (local.get $props)))
|
|
712
819
|
(local.set $props (i64.const 0))))
|
|
713
820
|
;; OBJECT: heap-allocated (off >= __heap_start) carries propsPtr at
|
|
714
821
|
;; off-16 from __alloc_hdr. The slot is either 0 (no dyn props yet) or
|
|
@@ -829,6 +936,28 @@ export default (ctx) => {
|
|
|
829
936
|
(else ${extArm})))))`
|
|
830
937
|
}
|
|
831
938
|
|
|
939
|
+
// Prehashed variant of __dyn_get_any_t for constant string keys: the FNV hash
|
|
940
|
+
// is folded at compile time (strHashLiteral), so no __str_hash call at runtime.
|
|
941
|
+
// Hot for the layered-parser pattern — `parse.step`/`parse.space`/… reads a
|
|
942
|
+
// function-object property with a literal key on every parser step.
|
|
943
|
+
ctx.core.stdlib['__dyn_get_any_t_h'] = () => {
|
|
944
|
+
const extArm = ctx.features.external
|
|
945
|
+
? `(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.EXTERNAL}))
|
|
946
|
+
(then (call $__ext_prop (local.get $obj) (local.get $key)))
|
|
947
|
+
(else (i64.const ${NULL_NAN})))`
|
|
948
|
+
: `(i64.const ${NULL_NAN})`
|
|
949
|
+
return `(func $__dyn_get_any_t_h (param $obj i64) (param $key i64) (param $t i32) (param $h i32) (result i64)
|
|
950
|
+
(local $val i64)
|
|
951
|
+
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
952
|
+
(then (call $__hash_get_local_h (local.get $obj) (local.get $key) (local.get $h)))
|
|
953
|
+
(else
|
|
954
|
+
(local.set $val (call $__dyn_get_t_h (local.get $obj) (local.get $key) (local.get $t) (local.get $h)))
|
|
955
|
+
(if (result i64)
|
|
956
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
957
|
+
(then (local.get $val))
|
|
958
|
+
(else ${extArm})))))`
|
|
959
|
+
}
|
|
960
|
+
|
|
832
961
|
// Hot for `node.loc = pos` patterns (e.g. watr's parser tags every nested level).
|
|
833
962
|
// Defer the root insert to the end and gate it on props-ptr change: most calls hit
|
|
834
963
|
// the no-grow case where the ptr is unchanged and the root slot already points to it.
|
|
@@ -925,6 +1054,91 @@ export default (ctx) => {
|
|
|
925
1054
|
(then (global.set $__dyn_get_cache_props (f64.reinterpret_i64 (local.get $props)))))))
|
|
926
1055
|
(local.get $val))`
|
|
927
1056
|
|
|
1057
|
+
// Tag-dispatched delete (mirrors __dyn_set's dispatch). Returns 1 if a slot was
|
|
1058
|
+
// found+tombstoned, 0 otherwise. Header types (ARRAY non-shifted, OBJECT heap-only,
|
|
1059
|
+
// TYPED/HASH/SET/MAP) carry propsPtr at off-16; others fall back to the global
|
|
1060
|
+
// __dyn_props hash keyed by offset.
|
|
1061
|
+
// Schema-aware delete arm: when the receiver is an OBJECT with a known schema and
|
|
1062
|
+
// the key matches a static slot, overwrite that slot with UNDEF_NAN so subsequent
|
|
1063
|
+
// reads see "absent" (matches `delete obj.a; obj.a → undefined`). Without this,
|
|
1064
|
+
// the shadow-store delete alone would leave the structural slot intact and a later
|
|
1065
|
+
// ctx[k] read would re-surface the original value.
|
|
1066
|
+
const buildObjectSchemaDelArm = () => (ctx.schema.list.length > 0 || ctx.core.includes.has('__jp_obj')) ? `
|
|
1067
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
1068
|
+
(i32.ne (global.get $__schema_tbl) (i32.const 0)))
|
|
1069
|
+
(then
|
|
1070
|
+
(local.set $sid (i32.wrap_i64 (i64.and (i64.shr_u
|
|
1071
|
+
(local.get $obj) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
1072
|
+
(local.set $kbits
|
|
1073
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3)))))
|
|
1074
|
+
(local.set $koff (i32.wrap_i64 (i64.and (local.get $kbits) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
1075
|
+
(local.set $nkeys (i32.load (i32.sub (local.get $koff) (i32.const 8))))
|
|
1076
|
+
(local.set $idx (i32.const 0))
|
|
1077
|
+
(block $schemaDelDone (loop $schemaDelLoop
|
|
1078
|
+
(br_if $schemaDelDone (i32.ge_s (local.get $idx) (local.get $nkeys)))
|
|
1079
|
+
(if (call $__str_eq
|
|
1080
|
+
(i64.load (i32.add (local.get $koff) (i32.shl (local.get $idx) (i32.const 3))))
|
|
1081
|
+
(local.get $key))
|
|
1082
|
+
(then
|
|
1083
|
+
(i64.store (i32.add (local.get $off) (i32.shl (local.get $idx) (i32.const 3))) (i64.const ${UNDEF_NAN}))
|
|
1084
|
+
(local.set $hit (i32.const 1))
|
|
1085
|
+
(br $schemaDelDone)))
|
|
1086
|
+
(local.set $idx (i32.add (local.get $idx) (i32.const 1)))
|
|
1087
|
+
(br $schemaDelLoop)))))` : ''
|
|
1088
|
+
|
|
1089
|
+
ctx.core.stdlib['__dyn_del'] = () => `(func $__dyn_del (param $obj i64) (param $key i64) (result i32)
|
|
1090
|
+
(local $root i64) (local $props i64) (local $oldProps i64)
|
|
1091
|
+
(local $off i32) (local $type i32) (local $hit i32) ${buildObjectSchemaSetLocals()}
|
|
1092
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
1093
|
+
(local.set $type (i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
1094
|
+
${buildObjectSchemaDelArm()}
|
|
1095
|
+
;; CLOSURE with no env: rekey to function table index (parallels __dyn_set / __dyn_get_t_h).
|
|
1096
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.CLOSURE})) (i32.eqz (local.get $off)))
|
|
1097
|
+
(then (local.set $off (i32.sub (i32.const -1)
|
|
1098
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK})))))))
|
|
1099
|
+
;; ARRAY: follow forwarding chain to landed base.
|
|
1100
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
1101
|
+
(then
|
|
1102
|
+
(block $done
|
|
1103
|
+
(loop $follow
|
|
1104
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 16)))
|
|
1105
|
+
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
1106
|
+
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
1107
|
+
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
1108
|
+
(br $follow)))))
|
|
1109
|
+
;; ARRAY landed propsPtr (HASH-tagged means real sidecar; else fall through to global).
|
|
1110
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
1111
|
+
(i32.ge_u (local.get $off) (i32.const 16)))
|
|
1112
|
+
(then
|
|
1113
|
+
(local.set $oldProps (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
1114
|
+
(if (i32.eq
|
|
1115
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $oldProps) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
1116
|
+
(i32.const ${PTR.HASH}))
|
|
1117
|
+
(then (return (i32.or (local.get $hit) (call $__hash_del_local (local.get $oldProps) (local.get $key))))))))
|
|
1118
|
+
;; OBJECT heap: propsPtr directly at off-16.
|
|
1119
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
|
|
1120
|
+
(i32.ge_u (local.get $off) (global.get $__heap_start)))
|
|
1121
|
+
(then
|
|
1122
|
+
(local.set $oldProps (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
1123
|
+
(if (i64.eqz (local.get $oldProps)) (then (return (local.get $hit))))
|
|
1124
|
+
(return (i32.or (local.get $hit) (call $__hash_del_local (local.get $oldProps) (local.get $key))))))
|
|
1125
|
+
;; Other header types (TYPED/HASH/SET/MAP).
|
|
1126
|
+
(if (i32.and (i32.ge_u (local.get $off) (i32.const 16))
|
|
1127
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.TYPED}))
|
|
1128
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.HASH}))
|
|
1129
|
+
(i32.or (i32.eq (local.get $type) (i32.const ${PTR.SET}))
|
|
1130
|
+
(i32.eq (local.get $type) (i32.const ${PTR.MAP}))))))
|
|
1131
|
+
(then
|
|
1132
|
+
(local.set $oldProps (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
1133
|
+
(if (i64.eqz (local.get $oldProps)) (then (return (local.get $hit))))
|
|
1134
|
+
(return (i32.or (local.get $hit) (call $__hash_del_local (local.get $oldProps) (local.get $key))))))
|
|
1135
|
+
;; Fallback: global __dyn_props keyed by offset.
|
|
1136
|
+
(local.set $root (i64.reinterpret_f64 (global.get $__dyn_props)))
|
|
1137
|
+
(if (i64.eqz (local.get $root)) (then (return (local.get $hit))))
|
|
1138
|
+
(local.set $props (call $__ihash_get_local (local.get $root) (i64.reinterpret_f64 (f64.convert_i32_s (local.get $off)))))
|
|
1139
|
+
(if (call $__is_nullish (local.get $props)) (then (return (local.get $hit))))
|
|
1140
|
+
(i32.or (local.get $hit) (call $__hash_del_local (local.get $props) (local.get $key))))`
|
|
1141
|
+
|
|
928
1142
|
ctx.core.stdlib['__dyn_move'] = `(func $__dyn_move (param $oldOff i32) (param $newOff i32)
|
|
929
1143
|
(local $props i64) (local $root i64)
|
|
930
1144
|
(if (f64.eq (global.get $__dyn_props) (f64.const 0)) (then (return)))
|
|
@@ -938,6 +1152,15 @@ export default (ctx) => {
|
|
|
938
1152
|
ctx.core.stdlib['__hash_get'] = () => genLookup('__hash_get', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, true, ctx.features.external)
|
|
939
1153
|
ctx.core.stdlib['__hash_has'] = () => genLookup('__hash_has', MAP_ENTRY, '$__str_hash', strEq, PTR.HASH, false, ctx.features.external)
|
|
940
1154
|
|
|
1155
|
+
// === `delete obj[k]`: lift from prepare for computed-key removal ===
|
|
1156
|
+
// Static-key `delete obj.x` / `delete obj["x"]` is rejected in prepare (fixed schema);
|
|
1157
|
+
// only the runtime-dispatched form reaches here. JS returns `true` on success — we
|
|
1158
|
+
// surface the actual found/not-found bit as i32 (`true`/`false` ↔ 1/0 in jz NaN-box).
|
|
1159
|
+
ctx.core.emit['delete'] = (obj, key) => {
|
|
1160
|
+
inc('__dyn_del')
|
|
1161
|
+
return typed(['call', '$__dyn_del', asI64(emit(obj)), asI64(emit(key))], 'i32')
|
|
1162
|
+
}
|
|
1163
|
+
|
|
941
1164
|
// === `in` operator: key in obj → HASH key existence check ===
|
|
942
1165
|
ctx.core.emit['in'] = (key, obj) => {
|
|
943
1166
|
const objType = typeof obj === 'string' ? lookupValType(obj) : valTypeOf(obj)
|
|
@@ -1032,8 +1255,14 @@ export default (ctx) => {
|
|
|
1032
1255
|
const ptrI64 = tempI64('hp'), srcOff = tempI32('hso'), srcType = tempI32('hst')
|
|
1033
1256
|
if (!ctx.func.locals.has(varName)) ctx.func.locals.set(varName, 'f64')
|
|
1034
1257
|
const id = ctx.func.uniq++
|
|
1258
|
+
const brk = `$brk${id}`, loop = `$loop${id}`, cont = `$cont${id}`
|
|
1035
1259
|
const va = asF64(emit(src))
|
|
1036
|
-
const
|
|
1260
|
+
const needsCont = hasOwnContinue(body)
|
|
1261
|
+
ctx.func.stack.push({ brk, loop: needsCont ? cont : loop })
|
|
1262
|
+
let bodyFlat
|
|
1263
|
+
try { bodyFlat = emitFlat(body) }
|
|
1264
|
+
finally { ctx.func.stack.pop() }
|
|
1265
|
+
const bodyBlock = needsCont ? [['block', cont, ...bodyFlat]] : bodyFlat
|
|
1037
1266
|
inc('__ptr_type')
|
|
1038
1267
|
return [
|
|
1039
1268
|
// Save source ptr as i64
|
|
@@ -1054,16 +1283,16 @@ export default (ctx) => {
|
|
|
1054
1283
|
['local.set', `$${off}`, ['call', '$__ptr_offset', ['local.get', `$${ptrI64}`]]],
|
|
1055
1284
|
['local.set', `$${cap}`, ['call', '$__cap', ['local.get', `$${ptrI64}`]]],
|
|
1056
1285
|
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1057
|
-
['block',
|
|
1058
|
-
['br_if',
|
|
1286
|
+
['block', brk, ['loop', loop,
|
|
1287
|
+
['br_if', brk, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${cap}`]]],
|
|
1059
1288
|
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${off}`],
|
|
1060
1289
|
['i32.mul', ['local.get', `$${i}`], ['i32.const', MAP_ENTRY]]]],
|
|
1061
1290
|
['if', ['i64.ne', ['i64.load', ['local.get', `$${slot}`]], ['i64.const', 0]],
|
|
1062
1291
|
['then',
|
|
1063
1292
|
['local.set', `$${varName}`, ['f64.reinterpret_i64', ['i64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]]],
|
|
1064
|
-
...
|
|
1293
|
+
...bodyBlock]],
|
|
1065
1294
|
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1066
|
-
['br',
|
|
1295
|
+
['br', loop]]]]]
|
|
1067
1296
|
]
|
|
1068
1297
|
}
|
|
1069
1298
|
}
|
package/module/console.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Console + clocks module — two host-mode lowerings.
|
|
3
3
|
*
|
|
4
4
|
* `host: 'js'` (default): emit `env.print(val: i64, fd: i32, sep: i32)` and
|
|
5
|
-
* `env.now(clock: i32) -> f64`. The JS host (
|
|
5
|
+
* `env.now(clock: i32) -> f64`. The JS host (interop.js) wires both
|
|
6
6
|
* automatically — `print` reads the NaN-boxed value via `mem.read`, so
|
|
7
7
|
* stringification happens host-side (no __ftoa / __write_str / __write_val
|
|
8
8
|
* stdlib in the binary). `sep`: 10=newline, 32=space, 0=no separator.
|