jz 0.8.0 → 0.9.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 +56 -9
- package/bench/README.md +121 -50
- package/bench/bench.svg +27 -27
- package/cli.js +3 -1
- package/dist/interop.js +1 -1
- package/dist/jz.js +7541 -6178
- package/index.js +165 -74
- package/interop.js +189 -17
- package/jzify/arguments.js +32 -1
- package/jzify/async.js +409 -0
- package/jzify/classes.js +136 -18
- package/jzify/generators.js +639 -0
- package/jzify/hoist-vars.js +73 -1
- package/jzify/index.js +123 -4
- package/jzify/names.js +1 -0
- package/jzify/transform.js +239 -1
- package/layout.js +48 -3
- package/module/array.js +318 -43
- package/module/atomics.js +144 -0
- package/module/collection.js +1429 -155
- package/module/core.js +431 -40
- package/module/date.js +142 -120
- package/module/fs.js +144 -0
- package/module/function.js +6 -3
- package/module/index.js +4 -1
- package/module/json.js +270 -49
- package/module/math.js +1032 -145
- package/module/number.js +532 -163
- package/module/object.js +353 -95
- package/module/regex.js +157 -7
- package/module/schema.js +100 -2
- package/module/string.js +428 -93
- package/module/typedarray.js +264 -72
- package/module/web.js +36 -0
- package/package.json +5 -5
- package/src/abi/string.js +82 -11
- package/src/ast.js +11 -5
- package/src/autoload.js +28 -1
- package/src/bridge.js +7 -2
- package/src/compile/analyze-scans.js +22 -7
- package/src/compile/analyze.js +136 -30
- package/src/compile/dyn-closure-tables.js +277 -0
- package/src/compile/emit-assign.js +281 -15
- package/src/compile/emit.js +1055 -70
- package/src/compile/index.js +277 -29
- package/src/compile/infer.js +42 -4
- package/src/compile/inplace-store.js +329 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/narrow.js +555 -18
- package/src/compile/peel-stencil.js +5 -0
- package/src/compile/plan/index.js +45 -3
- package/src/compile/plan/inline.js +8 -1
- package/src/compile/plan/literals.js +39 -0
- package/src/compile/plan/scope.js +430 -23
- package/src/compile/program-facts.js +732 -38
- package/src/ctx.js +64 -9
- package/src/helper-counters.js +7 -1
- package/src/ir.js +113 -5
- package/src/kind-traits.js +66 -3
- package/src/kind.js +84 -12
- package/src/op-policy.js +7 -4
- package/src/optimize/index.js +1179 -704
- package/src/optimize/recurse.js +2 -2
- package/src/optimize/vectorize.js +982 -67
- package/src/prepare/index.js +809 -67
- package/src/prepare/math-kernel.js +331 -0
- package/src/prepare/pre-eval.js +714 -0
- package/src/snapshot.js +194 -0
- package/src/type.js +1170 -56
- package/src/wat/assemble.js +403 -65
- package/src/wat/codegen.js +74 -14
- package/transform.js +113 -4
- package/wasi.js +3 -0
package/module/json.js
CHANGED
|
@@ -14,7 +14,8 @@ import { valTypeOf } from '../src/kind.js'
|
|
|
14
14
|
import { T } from '../src/ast.js'
|
|
15
15
|
import { VAL } from '../src/reps.js'
|
|
16
16
|
import { err, inc, PTR, LAYOUT, declGlobal } from '../src/ctx.js'
|
|
17
|
-
import {
|
|
17
|
+
import { i64Hex } from '../layout.js'
|
|
18
|
+
import { strHashLiteral, heapResetWat } from './collection.js'
|
|
18
19
|
|
|
19
20
|
function jsonConstString(ctx, expr) {
|
|
20
21
|
if (Array.isArray(expr) && expr[0] === 'str' && typeof expr[1] === 'string') return expr[1]
|
|
@@ -142,12 +143,22 @@ export default (ctx) => {
|
|
|
142
143
|
__jindent: ['__jput'],
|
|
143
144
|
__json_val: ['__ptr_type', '__len', '__ptr_offset', '__jput', '__jindent', '__jput_num', '__jput_str', '__json_enter', '__json_leave', '__json_hash', '__json_obj'],
|
|
144
145
|
__json_hash: ['__ptr_offset', '__jput', '__jindent', '__jput_str', '__json_omit', '__json_enter', '__json_leave', '__json_val', '__coll_order'],
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
// Durable-receiver global-table merge (see __json_obj's body) pulls in
|
|
147
|
+
// __ihash_get_local/__is_nullish only when collection.js's dyn-props
|
|
148
|
+
// machinery is actually part of this build (mirrors array.js's
|
|
149
|
+
// needsArrayDynMove-gated deps thunks) — a program that never writes a
|
|
150
|
+
// dynamic prop anywhere never loads collection.js.
|
|
151
|
+
__json_obj: () => [...(ctx.schema.dateSid != null ? ['__date_to_iso_string'] : []),
|
|
152
|
+
'__ptr_offset', '__ptr_aux', '__len', '__jput', '__jindent', '__jput_str', '__json_omit', '__json_enter', '__json_leave', '__json_val', '__coll_order',
|
|
153
|
+
...(ctx.scope.globals.has('__dyn_props') ? ['__ihash_get_local', '__is_nullish'] : [])],
|
|
154
|
+
// Chain edges ($__jput_num → $__jput_str → $__jput): each body CALLS the
|
|
155
|
+
// next stage; without the explicit edge they ride the auto-dep scan, which
|
|
156
|
+
// silently yields nothing under self-host (test/selfhost-includes.js).
|
|
157
|
+
__jput_num: ['__ftoa', '__jput_str'],
|
|
158
|
+
__jput_str: ['__char_at', '__str_byteLen', '__jput'],
|
|
148
159
|
__jp: ['__jp_val', '__jp_str', '__jp_num', '__jp_arr', '__jp_obj', '__sso_char', '__ptr_aux', '__ptr_type', '__ptr_offset', '__str_byteLen'],
|
|
149
160
|
__jp_val: ['__jp_str', '__jp_num', '__jp_arr', '__jp_obj'],
|
|
150
|
-
__jp_str: ['__sso_char', '__char_at', '__str_byteLen', '__hex4', '__ishex', '__utf8_enc'],
|
|
161
|
+
__jp_str: ['__sso_char', '__char_at', '__str_byteLen', '__hex4', '__ishex', '__utf8_enc', '__sso_norm'],
|
|
151
162
|
__hex4: ['__hex1'],
|
|
152
163
|
__jp_num: ['__pow10'],
|
|
153
164
|
__jp_arr: ['__jp_val'],
|
|
@@ -187,6 +198,12 @@ export default (ctx) => {
|
|
|
187
198
|
return mkPtrIR(PTR.OBJECT, 0, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', ctx.abi.object.ops.allocSlots(0)]])
|
|
188
199
|
}
|
|
189
200
|
const schemaId = ctx.schema.register(keys)
|
|
201
|
+
// Extern-write belt: these slot stores bypass the write censuses (they
|
|
202
|
+
// are IR, not AST `{}` literals). plan's hazard scan (jsonParseKeysets)
|
|
203
|
+
// normally kind-safe-marks this sid earlier; a sid it MISSED gets a
|
|
204
|
+
// null-kinds entry so every reader belt fails closed on it.
|
|
205
|
+
const hzLive = ctx.schema.slotWriteHazards
|
|
206
|
+
if (hzLive && !hzLive.kindSafeSids.has(schemaId)) hzLive.kindSafeSids.set(schemaId, null)
|
|
190
207
|
const t = tempI32('jobj')
|
|
191
208
|
const body = [
|
|
192
209
|
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', ctx.abi.object.ops.allocSlots(keys.length)]]],
|
|
@@ -475,13 +492,35 @@ export default (ctx) => {
|
|
|
475
492
|
// Schema name table: global $__schema_tbl → array of f64 pointers.
|
|
476
493
|
// schema_tbl[schemaId * 8] = f64 pointer to jz Array of key name strings.
|
|
477
494
|
// Object props are sequential f64 at ptr_offset, indexed same as schema.
|
|
478
|
-
|
|
495
|
+
// Thunked (not a plain template string) so heapResetWat()/the __dyn_props
|
|
496
|
+
// presence check below read the FINAL declaration state — see collection.js's
|
|
497
|
+
// heapResetWat comment for why (module load order isn't otherwise settled
|
|
498
|
+
// at the time this string would eagerly evaluate).
|
|
499
|
+
ctx.core.stdlib['__json_obj'] = () => `(func $__json_obj (param $val i64)
|
|
479
500
|
(local $off i32) (local $sid i32) (local $keys i32) (local $nkeys i32)
|
|
480
501
|
(local $i i32) (local $koff i32) (local $first i32) (local $pv i64)
|
|
481
|
-
(local $props i64) (local $
|
|
482
|
-
|
|
502
|
+
(local $props i64) (local $slot i32) (local $j i32) (local $skip i32)
|
|
503
|
+
;; Two dyn-prop sources for a DURABLE receiver — see collection.js's
|
|
504
|
+
;; heapResetWat and module/object.js's emitEnumerateObject for the full
|
|
505
|
+
;; rationale: G(lobal) holds runtime/post-init keys, S(idecar) holds
|
|
506
|
+
;; init-time keys. An EPHEMERAL receiver only ever populates S.
|
|
507
|
+
(local $poffG i32) (local $pcapG i32) (local $dnG i32) (local $ordG i32)
|
|
508
|
+
(local $poffS i32) (local $pcapS i32) (local $dnS i32) (local $ordS i32)
|
|
483
509
|
(local.set $off (call $__ptr_offset (local.get $val)))
|
|
484
510
|
(local.set $sid (call $__ptr_aux (local.get $val)))
|
|
511
|
+
${ctx.schema.dateSid != null ? `
|
|
512
|
+
;; Branded Date (module/date.js schema): serialize as its toJSON — the ISO
|
|
513
|
+
;; string, or null for an invalid date — matching host JSON.stringify(date).
|
|
514
|
+
(if (i32.eq (local.get $sid) (i32.const ${ctx.schema.dateSid}))
|
|
515
|
+
(then
|
|
516
|
+
(if (f64.ne (f64.load (local.get $off)) (f64.load (local.get $off)))
|
|
517
|
+
(then
|
|
518
|
+
(call $__jput (i32.const 110)) (call $__jput (i32.const 117))
|
|
519
|
+
(call $__jput (i32.const 108)) (call $__jput (i32.const 108)) (return)))
|
|
520
|
+
(call $__jput (i32.const 34))
|
|
521
|
+
(call $__jput_str (i64.reinterpret_f64 (call $__date_to_iso_string (f64.load (local.get $off)))))
|
|
522
|
+
(call $__jput (i32.const 34))
|
|
523
|
+
(return)))` : ''}
|
|
485
524
|
;; Schema keys: schema_tbl + sid*8. Dyn-only programs (empty {} + computed
|
|
486
525
|
;; o[k]=v) never allocate __schema_tbl, leaving it 0 — guard the read so the
|
|
487
526
|
;; dyn-prop walk below still runs.
|
|
@@ -513,41 +552,73 @@ export default (ctx) => {
|
|
|
513
552
|
(call $__json_val (local.get $pv))))
|
|
514
553
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
515
554
|
(br $l)))
|
|
516
|
-
;; Dynamic (off-schema) properties: heap OBJECTs carry a HASH propsPtr
|
|
517
|
-
;; off-16 (
|
|
518
|
-
;;
|
|
519
|
-
;;
|
|
520
|
-
;;
|
|
521
|
-
;;
|
|
522
|
-
;;
|
|
523
|
-
;;
|
|
524
|
-
;;
|
|
555
|
+
;; Dynamic (off-schema) properties: heap OBJECTs carry a HASH propsPtr
|
|
556
|
+
;; either at off-16 (populated by an init-time write, or by any write at
|
|
557
|
+
;; all on an EPHEMERAL receiver — one allocated after the post-init
|
|
558
|
+
;; high-water mark, per the durable-receiver policy) or in the global
|
|
559
|
+
;; __dyn_props table keyed by offset (populated by a RUNTIME/post-init
|
|
560
|
+
;; write on a DURABLE receiver; see collection.js's heapResetWat for the
|
|
561
|
+
;; full policy). A durable receiver can carry BOTH — gather each into its
|
|
562
|
+
;; own (poff/pcap/dn) pair; G(lobal) and S(idecar) below. Static-segment
|
|
563
|
+
;; objects (off < __heap_start) have no header at all — guard both reads
|
|
564
|
+
;; on off >= __heap_start so neither ever reads neighbor static data.
|
|
525
565
|
(if (i32.ge_u (local.get $off) (global.get $__heap_start))
|
|
526
566
|
(then
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
567
|
+
;; mask bit0 — durable words may carry the runtime-shadowed marker
|
|
568
|
+
;; (collection.js __dyn_set); unmasked, the resolved sidecar off is odd.
|
|
569
|
+
(local.set $props (i64.and (i64.load (i32.sub (local.get $off) (i32.const 16))) (i64.const -2)))
|
|
570
|
+
(if (i32.eq
|
|
571
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $props) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
572
|
+
(i32.const ${PTR.HASH}))
|
|
573
|
+
(then
|
|
574
|
+
(local.set $poffS (call $__ptr_offset (local.get $props)))
|
|
575
|
+
(local.set $pcapS (i32.load (i32.sub (local.get $poffS) (i32.const 4))))
|
|
576
|
+
(local.set $dnS (i32.load (i32.sub (local.get $poffS) (i32.const 8))))))))${ctx.scope.globals.has('__dyn_props') ? `
|
|
577
|
+
;; Global (runtime keys on a durable receiver) — independent of the sidecar
|
|
578
|
+
;; gate above: STATIC-SEGMENT receivers (off < __heap_start, no header) also
|
|
579
|
+
;; store their dyn writes here, and the probe is keyed by offset alone.
|
|
580
|
+
(if (i32.lt_u (local.get $off) ${heapResetWat()})
|
|
581
|
+
(then
|
|
582
|
+
(if (f64.ne (global.get $__dyn_props) (f64.const 0))
|
|
531
583
|
(then
|
|
532
|
-
(local.set $
|
|
533
|
-
(
|
|
534
|
-
|
|
535
|
-
|
|
584
|
+
(local.set $props (call $__ihash_get_local (i64.reinterpret_f64 (global.get $__dyn_props)) (i64.reinterpret_f64 (f64.convert_i32_s (local.get $off)))))
|
|
585
|
+
(if (i32.eqz (call $__is_nullish (local.get $props)))
|
|
586
|
+
(then
|
|
587
|
+
(local.set $poffG (call $__ptr_offset (local.get $props)))
|
|
588
|
+
(local.set $pcapG (i32.load (i32.sub (local.get $poffG) (i32.const 4))))
|
|
589
|
+
(local.set $dnG (i32.load (i32.sub (local.get $poffG) (i32.const 8))))))))))` : ''}
|
|
590
|
+
;; Walk in insertion order via __coll_order — schema-only enumeration
|
|
591
|
+
;; would drop computed props (e.g. {} then o.a=1). Skip entries whose key
|
|
592
|
+
;; already appears in the schema: object literals with
|
|
593
|
+
;; needsDynShadow(target)=true shadow-write each schema key into propsPtr
|
|
594
|
+
;; so dyn-key reads can resolve via hash lookup. That mirror is a runtime
|
|
595
|
+
;; acceleration, not an enumeration entity — without dedup, JSON output
|
|
596
|
+
;; emits each schema key twice. Schema-key interns equal the keys we
|
|
597
|
+
;; shadow-write (same compile-time string literal), so i64.eq matches.
|
|
598
|
+
;; G walks first (schema-dedup only); S walks second (schema-dedup AND
|
|
599
|
+
;; G-dedup, so a key present in both — reassigned at runtime after being
|
|
600
|
+
;; set at init — emits once, from the authoritative G copy).
|
|
601
|
+
(if (i32.ne (local.get $poffG) (i32.const 0))
|
|
602
|
+
(then (local.set $ordG (call $__coll_order (local.get $poffG) (local.get $pcapG) (i32.const 24)))))
|
|
603
|
+
(if (i32.ne (local.get $poffS) (i32.const 0))
|
|
604
|
+
(then (local.set $ordS (call $__coll_order (local.get $poffS) (local.get $pcapS) (i32.const 24)))))
|
|
605
|
+
(if (i32.ne (local.get $dnG) (i32.const 0))
|
|
606
|
+
(then
|
|
536
607
|
(local.set $i (i32.const 0))
|
|
537
|
-
(block $
|
|
538
|
-
(br_if $
|
|
539
|
-
(local.set $slot (i32.load (i32.add (local.get $
|
|
608
|
+
(block $gd (loop $gl
|
|
609
|
+
(br_if $gd (i32.ge_s (local.get $i) (local.get $dnG)))
|
|
610
|
+
(local.set $slot (i32.load (i32.add (local.get $ordG) (i32.shl (local.get $i) (i32.const 2)))))
|
|
540
611
|
(local.set $pv (i64.load (i32.add (local.get $slot) (i32.const 16))))
|
|
541
612
|
(local.set $skip (i32.const 0))
|
|
542
613
|
(local.set $j (i32.const 0))
|
|
543
|
-
(block $
|
|
544
|
-
(br_if $
|
|
614
|
+
(block $gsd (loop $gsl
|
|
615
|
+
(br_if $gsd (i32.ge_s (local.get $j) (local.get $nkeys)))
|
|
545
616
|
(if (i64.eq
|
|
546
617
|
(i64.load (i32.add (local.get $slot) (i32.const 8)))
|
|
547
618
|
(i64.load (i32.add (local.get $koff) (i32.shl (local.get $j) (i32.const 3)))))
|
|
548
|
-
(then (local.set $skip (i32.const 1)) (br $
|
|
619
|
+
(then (local.set $skip (i32.const 1)) (br $gsd)))
|
|
549
620
|
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
550
|
-
(br $
|
|
621
|
+
(br $gsl)))
|
|
551
622
|
(if (i32.and (i32.eqz (local.get $skip)) (i32.eqz (call $__json_omit (local.get $pv))))
|
|
552
623
|
(then
|
|
553
624
|
(if (i32.eqz (local.get $first)) (then (call $__jput (i32.const 44))))
|
|
@@ -560,7 +631,48 @@ export default (ctx) => {
|
|
|
560
631
|
(if (global.get $__jgaplen) (then (call $__jput (i32.const 32))))
|
|
561
632
|
(call $__json_val (local.get $pv))))
|
|
562
633
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
563
|
-
(br $
|
|
634
|
+
(br $gl)))))
|
|
635
|
+
(if (i32.ne (local.get $dnS) (i32.const 0))
|
|
636
|
+
(then
|
|
637
|
+
(local.set $i (i32.const 0))
|
|
638
|
+
(block $sd (loop $sl
|
|
639
|
+
(br_if $sd (i32.ge_s (local.get $i) (local.get $dnS)))
|
|
640
|
+
(local.set $slot (i32.load (i32.add (local.get $ordS) (i32.shl (local.get $i) (i32.const 2)))))
|
|
641
|
+
(local.set $pv (i64.load (i32.add (local.get $slot) (i32.const 16))))
|
|
642
|
+
(local.set $skip (i32.const 0))
|
|
643
|
+
(local.set $j (i32.const 0))
|
|
644
|
+
(block $ssd (loop $ssl
|
|
645
|
+
(br_if $ssd (i32.ge_s (local.get $j) (local.get $nkeys)))
|
|
646
|
+
(if (i64.eq
|
|
647
|
+
(i64.load (i32.add (local.get $slot) (i32.const 8)))
|
|
648
|
+
(i64.load (i32.add (local.get $koff) (i32.shl (local.get $j) (i32.const 3)))))
|
|
649
|
+
(then (local.set $skip (i32.const 1)) (br $ssd)))
|
|
650
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
651
|
+
(br $ssl)))
|
|
652
|
+
(if (i32.eqz (local.get $skip))
|
|
653
|
+
(then
|
|
654
|
+
(local.set $j (i32.const 0))
|
|
655
|
+
(block $sgd (loop $sgl
|
|
656
|
+
(br_if $sgd (i32.ge_s (local.get $j) (local.get $dnG)))
|
|
657
|
+
(if (i64.eq
|
|
658
|
+
(i64.load (i32.add (local.get $slot) (i32.const 8)))
|
|
659
|
+
(i64.load (i32.add (i32.load (i32.add (local.get $ordG) (i32.shl (local.get $j) (i32.const 2)))) (i32.const 8))))
|
|
660
|
+
(then (local.set $skip (i32.const 1)) (br $sgd)))
|
|
661
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
662
|
+
(br $sgl)))))
|
|
663
|
+
(if (i32.and (i32.eqz (local.get $skip)) (i32.eqz (call $__json_omit (local.get $pv))))
|
|
664
|
+
(then
|
|
665
|
+
(if (i32.eqz (local.get $first)) (then (call $__jput (i32.const 44))))
|
|
666
|
+
(local.set $first (i32.const 0))
|
|
667
|
+
(call $__jindent)
|
|
668
|
+
(call $__jput (i32.const 34))
|
|
669
|
+
(call $__jput_str (i64.load (i32.add (local.get $slot) (i32.const 8))))
|
|
670
|
+
(call $__jput (i32.const 34))
|
|
671
|
+
(call $__jput (i32.const 58))
|
|
672
|
+
(if (global.get $__jgaplen) (then (call $__jput (i32.const 32))))
|
|
673
|
+
(call $__json_val (local.get $pv))))
|
|
674
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
675
|
+
(br $sl)))))
|
|
564
676
|
(global.set $__jdepth (i32.sub (global.get $__jdepth) (i32.const 1)))
|
|
565
677
|
(if (i32.eqz (local.get $first)) (then (call $__jindent)))
|
|
566
678
|
(call $__jput (i32.const 125))
|
|
@@ -585,10 +697,14 @@ export default (ctx) => {
|
|
|
585
697
|
declGlobal('__jpstr', 'i32') // input string offset
|
|
586
698
|
declGlobal('__jplen', 'i32') // input length
|
|
587
699
|
declGlobal('__jppos', 'i32') // current parse position
|
|
588
|
-
// Side-channel hash for the most-recently-parsed string. __jp_str folds
|
|
589
|
-
// FNV-1a pass into its scan loop; __jp_obj
|
|
590
|
-
//
|
|
591
|
-
//
|
|
700
|
+
// Side-channel hash for the most-recently-parsed string. __jp_str folds a
|
|
701
|
+
// byte-FNV-1a pass into its scan loop; __jp_obj mixes it straight into the
|
|
702
|
+
// schema-cache's key-SEQUENCE hash ($hh, __jp_schema_get's probe key) — an
|
|
703
|
+
// independent hash from __str_hash, so it need not agree bit-for-bit with the
|
|
704
|
+
// runtime string hash (a schema-cache hit is verified by direct i64 key-array
|
|
705
|
+
// comparison, not by hash equality). 0 is a sentinel meaning "string had
|
|
706
|
+
// escapes"; it still mixes into $hh (a false schema-cache collision is caught
|
|
707
|
+
// by the verify step, never silently wrong).
|
|
592
708
|
declGlobal('__jp_keyh', 'i32')
|
|
593
709
|
// Sticky syntax-error flag. Set by any parser sub-routine on malformed input;
|
|
594
710
|
// checked once by __jp after the top-level value, which throws if it is set.
|
|
@@ -636,9 +752,10 @@ export default (ctx) => {
|
|
|
636
752
|
|
|
637
753
|
// Parse string (after opening " consumed). Single-pass scan that folds three
|
|
638
754
|
// concerns into one byte loop: simplicity flag (no escapes / no high-bit),
|
|
639
|
-
// SSO byte packing for ≤4-char ASCII keys, and FNV-1a hash. The hash is
|
|
640
|
-
// stashed in $__jp_keyh so __jp_obj can
|
|
641
|
-
//
|
|
755
|
+
// SSO byte packing for ≤4-char ASCII keys, and byte-FNV-1a hash. The hash is
|
|
756
|
+
// stashed in $__jp_keyh so __jp_obj can mix it into the schema-cache sequence
|
|
757
|
+
// hash without re-scanning the key bytes (see the $__jp_keyh declaration above
|
|
758
|
+
// for why it need not match __str_hash's SSO-mix output).
|
|
642
759
|
// Hex nibble: '0'-'9' / 'a'-'f' / 'A'-'F' → 0..15; anything else → 0 (lenient).
|
|
643
760
|
ctx.core.stdlib['__hex1'] = `(func $__hex1 (param $c i32) (result i32)
|
|
644
761
|
(if (i32.le_u (i32.sub (local.get $c) (i32.const 48)) (i32.const 9))
|
|
@@ -724,8 +841,11 @@ export default (ctx) => {
|
|
|
724
841
|
;; Loop exited on the closing quote (34) or EOF (-1); the latter means an
|
|
725
842
|
;; unterminated string literal.
|
|
726
843
|
(if (i32.eq (local.get $ch) (i32.const -1)) (then (global.set $__jp_err (i32.const 1))))
|
|
727
|
-
;; Stash hash. 0/1 bumped to 2 to match __str_hash convention
|
|
728
|
-
;;
|
|
844
|
+
;; Stash hash. 0/1 bumped to 2 to match __str_hash's clamp convention (kept for
|
|
845
|
+
;; sentinel consistency, though __jp_obj's consumer — the schema sequence hash —
|
|
846
|
+
;; doesn't itself need agreement with __str_hash). Escape strings (simple==0) get
|
|
847
|
+
;; sentinel 0, which still mixes into $hh (harmless: __jp_schema_get verifies by
|
|
848
|
+
;; key-array compare, not hash equality).
|
|
729
849
|
(global.set $__jp_keyh
|
|
730
850
|
(if (result i32) (local.get $simple)
|
|
731
851
|
(then (if (result i32) (i32.le_s (local.get $h) (i32.const 1))
|
|
@@ -738,13 +858,14 @@ export default (ctx) => {
|
|
|
738
858
|
(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
859
|
;; Simple STRING fast path: no escapes, len > 4 — bulk memcpy from parse buffer,
|
|
740
860
|
;; skip rewind + per-byte escape-decode loop. Hits 5+ char keys without escapes.
|
|
861
|
+
;; 5-6 char ASCII results route through __sso_norm (string-module invariant).
|
|
741
862
|
(if (local.get $simple)
|
|
742
863
|
(then
|
|
743
864
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
|
|
744
865
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
745
866
|
(i32.store (i32.sub (local.get $off) (i32.const 4)) (local.get $len))
|
|
746
867
|
(memory.copy (local.get $off) (i32.add (global.get $__jpstr) (local.get $start)) (local.get $len))
|
|
747
|
-
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))))
|
|
868
|
+
(return (call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off))))))
|
|
748
869
|
;; Copy chars to new string (handles escapes inline)
|
|
749
870
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
|
|
750
871
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
@@ -795,7 +916,8 @@ export default (ctx) => {
|
|
|
795
916
|
${ADV(1)} ;; skip closing "
|
|
796
917
|
;; Store actual length in header
|
|
797
918
|
(i32.store (i32.sub (local.get $off) (i32.const 4)) (local.get $len))
|
|
798
|
-
|
|
919
|
+
;; escape-decoded result may be short ASCII ("\\n" → 1 char) → normalize (invariant)
|
|
920
|
+
(call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off))))`
|
|
799
921
|
|
|
800
922
|
// Parse number
|
|
801
923
|
ctx.core.stdlib['__jp_num'] = `(func $__jp_num (result f64)
|
|
@@ -949,8 +1071,8 @@ export default (ctx) => {
|
|
|
949
1071
|
(i32.store (i32.add (local.get $entry) (i32.const 4)) (local.get $sid)))
|
|
950
1072
|
(local.get $sid))`
|
|
951
1073
|
|
|
952
|
-
// Parse object → OBJECT (schema-tagged, slot-based)
|
|
953
|
-
//
|
|
1074
|
+
// Parse object → OBJECT (schema-tagged, slot-based), always, via the runtime
|
|
1075
|
+
// schema cache (no HASH fallback in this build — every key sequence gets a sid).
|
|
954
1076
|
// Builds a transient (key, val) buffer during parse, then resolves a sid via
|
|
955
1077
|
// the runtime schema cache, allocs an OBJECT, and copies values into slots.
|
|
956
1078
|
// Walk-side `obj.prop` accesses then route through the OBJECT fast path
|
|
@@ -1090,7 +1212,7 @@ export default (ctx) => {
|
|
|
1090
1212
|
if (cached) return cached
|
|
1091
1213
|
|
|
1092
1214
|
const name = `__jp_shape_${ctx.runtime.jsonShapeParsers.size}`
|
|
1093
|
-
const locals = new Map([['len', 'i32'], ['buf', 'i32'], ['i', 'i32'], ['ch', 'i32']])
|
|
1215
|
+
const locals = new Map([['len', 'i32'], ['buf', 'i32'], ['i', 'i32'], ['ch', 'i32'], ['kp', 'i32']])
|
|
1094
1216
|
let uniq = 0
|
|
1095
1217
|
const local = (p, t) => {
|
|
1096
1218
|
const n = `${p}${uniq++}`
|
|
@@ -1100,7 +1222,38 @@ export default (ctx) => {
|
|
|
1100
1222
|
const fail = `(return (call $__jp (local.get $str)))`
|
|
1101
1223
|
const expect = (byte) => `(if (i32.ne ${PEEK} (i32.const ${byte})) (then ${fail}))
|
|
1102
1224
|
${ADV(1)}`
|
|
1103
|
-
|
|
1225
|
+
// SWAR text match: a known ≥2-byte ASCII run (schema keys, null/true/false)
|
|
1226
|
+
// compares as 8/4/2/1-byte chunks against packed little-endian constants —
|
|
1227
|
+
// one load + compare + branch per CHUNK instead of per character, and ONE
|
|
1228
|
+
// pos advance. In-bounds by construction: the entry allocates len+8 and
|
|
1229
|
+
// stores an 8-byte 0xFF sentinel, so any chunk starting inside the text
|
|
1230
|
+
// ends within the buffer, and a chunk overlapping the sentinel simply
|
|
1231
|
+
// fails the compare (0xFF matches no ASCII) into the generic-reparse fail,
|
|
1232
|
+
// exactly like the per-byte path. ASCII-only (a multi-byte char's UTF-8
|
|
1233
|
+
// bytes would need encoding here) — non-ASCII keys keep per-byte expects.
|
|
1234
|
+
const expectText = (text) => {
|
|
1235
|
+
const bytes = [...text].map(c => c.charCodeAt(0))
|
|
1236
|
+
if (bytes.length < 2 || bytes.some(b => b > 127))
|
|
1237
|
+
return [...text].map(c => expect(c.charCodeAt(0))).join('\n ')
|
|
1238
|
+
const le = (arr) => arr.reduce((a, b, k) => a | (BigInt(b) << BigInt(8 * k)), 0n)
|
|
1239
|
+
const at = (i) => (i ? `offset=${i} ` : '') + '(local.get $kp)'
|
|
1240
|
+
const out = [`(local.set $kp (i32.add (global.get $__jpstr) (global.get $__jppos)))`]
|
|
1241
|
+
let i = 0
|
|
1242
|
+
for (; bytes.length - i >= 8; i += 8)
|
|
1243
|
+
out.push(`(if (i64.ne (i64.load ${at(i)}) (i64.const ${i64Hex(le(bytes.slice(i, i + 8)))})) (then ${fail}))`)
|
|
1244
|
+
if (bytes.length - i >= 4) {
|
|
1245
|
+
out.push(`(if (i32.ne (i32.load ${at(i)}) (i32.const ${Number(le(bytes.slice(i, i + 4)))})) (then ${fail}))`)
|
|
1246
|
+
i += 4
|
|
1247
|
+
}
|
|
1248
|
+
if (bytes.length - i >= 2) {
|
|
1249
|
+
out.push(`(if (i32.ne (i32.load16_u ${at(i)}) (i32.const ${Number(le(bytes.slice(i, i + 2)))})) (then ${fail}))`)
|
|
1250
|
+
i += 2
|
|
1251
|
+
}
|
|
1252
|
+
if (i < bytes.length)
|
|
1253
|
+
out.push(`(if (i32.ne (i32.load8_u ${at(i)}) (i32.const ${bytes[i]})) (then ${fail}))`)
|
|
1254
|
+
out.push(ADV(bytes.length))
|
|
1255
|
+
return out.join('\n ')
|
|
1256
|
+
}
|
|
1104
1257
|
// Forward-declared with `let` (assigned below) so `parse` captures a boxed
|
|
1105
1258
|
// cell, not a not-yet-initialized `const` binding — the self-host kernel
|
|
1106
1259
|
// miscompiles the latter capture in this deeply-nested mutually-recursive
|
|
@@ -1123,6 +1276,10 @@ export default (ctx) => {
|
|
|
1123
1276
|
const obj = local('obj', 'i32')
|
|
1124
1277
|
const val = local('val', 'f64')
|
|
1125
1278
|
const sid = ctx.schema.register(keys)
|
|
1279
|
+
// Extern-write belt (see emitJsonConstValue): a shaped-parser sid the
|
|
1280
|
+
// plan hook missed fails closed via a null-kinds entry.
|
|
1281
|
+
const hzLive = ctx.schema.slotWriteHazards
|
|
1282
|
+
if (hzLive && !hzLive.kindSafeSids.has(sid)) hzLive.kindSafeSids.set(sid, null)
|
|
1126
1283
|
let body = `${WS()}
|
|
1127
1284
|
${expect(123)}
|
|
1128
1285
|
(local.set $${obj} (call $__alloc_hdr (i32.const 0) (i32.const ${Math.max(1, keys.length)})))`
|
|
@@ -1292,6 +1449,10 @@ ${localDecls}
|
|
|
1292
1449
|
}
|
|
1293
1450
|
const folded = foldStringify(x, replacer, space)
|
|
1294
1451
|
if (folded !== undefined) return folded
|
|
1452
|
+
// A real replacer that the const-fold could not absorb would be silently
|
|
1453
|
+
// IGNORED by the runtime walker — a wrong result, not a slow one. Reject.
|
|
1454
|
+
if (!noReplacer)
|
|
1455
|
+
err('JSON.stringify with a runtime replacer is not supported — the constant-foldable case works; otherwise filter/map the value before stringifying')
|
|
1295
1456
|
// Scalar boolean: the working-rep is the 0/1 number carrier, so the runtime
|
|
1296
1457
|
// tag-walker would emit "0"/"1". A boolean's JSON is the bare word
|
|
1297
1458
|
// true/false — exactly bool. Guard on no replacer: a replacer function may
|
|
@@ -1335,12 +1496,72 @@ ${localDecls}
|
|
|
1335
1496
|
if (sp === NOT_LIT) return undefined
|
|
1336
1497
|
}
|
|
1337
1498
|
|
|
1499
|
+
// Array replacer = a per-level property whitelist (ES SerializeJSONObject
|
|
1500
|
+
// step: PropertyList). Applied by a hand-rolled pre-filter, NOT the host
|
|
1501
|
+
// stringify's replacer param — the kernel's stringify drops replacers, so
|
|
1502
|
+
// in-kernel folds would silently ignore the filter (host≠kernel).
|
|
1503
|
+
let picked = val
|
|
1504
|
+
if (rep !== undefined && rep != null) {
|
|
1505
|
+
const keys = rep.map(String)
|
|
1506
|
+
const pick = (v) => {
|
|
1507
|
+
if (Array.isArray(v)) return v.map(pick)
|
|
1508
|
+
if (v !== null && typeof v === 'object') {
|
|
1509
|
+
const o = {}
|
|
1510
|
+
for (const k of keys) if (k in v) o[k] = pick(v[k])
|
|
1511
|
+
return o
|
|
1512
|
+
}
|
|
1513
|
+
return v
|
|
1514
|
+
}
|
|
1515
|
+
picked = pick(val)
|
|
1516
|
+
}
|
|
1338
1517
|
let result
|
|
1339
|
-
try { result = JSON.stringify(
|
|
1518
|
+
try { result = JSON.stringify(picked, null, sp) }
|
|
1340
1519
|
catch { return undefined }
|
|
1341
1520
|
return result === undefined ? undefExpr() : asF64(emit(['str', result]))
|
|
1342
1521
|
}
|
|
1343
1522
|
|
|
1523
|
+
// Plan-time mirror of the JSON.parse emit dispatch below: resolve the same
|
|
1524
|
+
// const/shape sources and return `{keys, kinds}` for every object key-set
|
|
1525
|
+
// the parser could WRITE into a compile-time schema (const fold or shaped
|
|
1526
|
+
// parser). The generic runtime parser needs no entry — it mints sids from
|
|
1527
|
+
// $__schema_next, seeded past the compile-time table (assemble.js), so it
|
|
1528
|
+
// can never alias a censused sid. `kinds` are the SAMPLE's per-slot VAL
|
|
1529
|
+
// kinds — trustworthy because both emit paths are shape-guarded: the const
|
|
1530
|
+
// fold bakes exactly these values, and the shaped parser falls back to the
|
|
1531
|
+
// generic parser (disjoint sids) on ANY text/structure divergence, so an
|
|
1532
|
+
// object carrying the sid always matches the sample's kinds. A null kind
|
|
1533
|
+
// (sample `null` value) poisons just that slot. Consumed by plan's
|
|
1534
|
+
// collectSlotWriteHazards BEFORE any census consumer bakes a decision.
|
|
1535
|
+
const JSON_KIND = (v) => v == null ? null
|
|
1536
|
+
: typeof v === 'number' ? VAL.NUMBER
|
|
1537
|
+
: typeof v === 'string' ? VAL.STRING
|
|
1538
|
+
: typeof v === 'boolean' ? VAL.BOOL
|
|
1539
|
+
: Array.isArray(v) ? VAL.ARRAY
|
|
1540
|
+
: VAL.OBJECT
|
|
1541
|
+
ctx.schema.jsonParseKeysets = (x) => {
|
|
1542
|
+
if (x === undefined || (Array.isArray(x) && x[0] == null && typeof x[1] !== 'string')) return []
|
|
1543
|
+
const out = []
|
|
1544
|
+
const collect = (v) => {
|
|
1545
|
+
if (v == null || typeof v !== 'object') return
|
|
1546
|
+
if (Array.isArray(v)) { for (const e of v) collect(e); return }
|
|
1547
|
+
const keys = Object.keys(v)
|
|
1548
|
+
if (keys.length) out.push({ keys, kinds: keys.map(k => JSON_KIND(v[k])) })
|
|
1549
|
+
for (const k of keys) collect(v[k])
|
|
1550
|
+
}
|
|
1551
|
+
const src = jsonConstString(ctx, x)
|
|
1552
|
+
if (src != null) {
|
|
1553
|
+
try { collect(JSON.parse(src)); return out } catch { /* emitter falls through the same way */ }
|
|
1554
|
+
}
|
|
1555
|
+
const shapeSrcs = jsonShapeStrings(ctx, x)
|
|
1556
|
+
if (shapeSrcs) {
|
|
1557
|
+
try {
|
|
1558
|
+
const parsed = shapeSrcs.map(s => JSON.parse(s))
|
|
1559
|
+
if (parsed.every(v => sameJsonShape(parsed[0], v))) collect(parsed[0])
|
|
1560
|
+
} catch { /* generic parser — disjoint runtime sids */ }
|
|
1561
|
+
}
|
|
1562
|
+
return out
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1344
1565
|
ctx.core.emit['JSON.parse'] = (x) => {
|
|
1345
1566
|
// A non-string primitive literal argument is coerced via ToString, then
|
|
1346
1567
|
// parsed: JSON.parse(0) → "0", JSON.parse(null) → "null", JSON.parse() →
|