jz 0.1.1 → 0.2.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 +257 -90
- package/cli.js +34 -8
- package/index.js +102 -13
- package/module/array.js +396 -133
- package/module/collection.js +455 -212
- package/module/console.js +169 -88
- package/module/core.js +246 -144
- package/module/date.js +691 -0
- package/module/function.js +81 -21
- package/module/index.js +2 -1
- package/module/json.js +483 -138
- package/module/math.js +79 -39
- package/module/number.js +188 -78
- package/module/object.js +227 -47
- package/module/regex.js +33 -30
- package/module/schema.js +14 -17
- package/module/string.js +434 -235
- package/module/symbol.js +4 -3
- package/module/timer.js +89 -31
- package/module/typedarray.js +174 -44
- package/package.json +3 -10
- package/src/analyze.js +626 -132
- package/src/autoload.js +14 -6
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +634 -145
- package/src/host.js +244 -51
- package/src/ir.js +81 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1132 -4
- package/src/prepare.js +321 -75
- package/src/vectorize.js +1016 -0
- package/wasi.js +13 -0
- package/src/key.js +0 -73
- package/src/source.js +0 -76
package/module/json.js
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* @module json
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { typed, asF64, temp, nullExpr, allocPtr, slotAddr } from '../src/ir.js'
|
|
11
|
+
import { typed, asF64, asI64, temp, tempI32, nullExpr, allocPtr, slotAddr, mkPtrIR, extractF64Bits, appendStaticSlots, NULL_WAT } from '../src/ir.js'
|
|
12
12
|
import { emit } from '../src/emit.js'
|
|
13
13
|
import { T } from '../src/analyze.js'
|
|
14
|
-
import { err, inc, PTR } from '../src/ctx.js'
|
|
14
|
+
import { err, inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
15
15
|
import { strHashLiteral } from './collection.js'
|
|
16
16
|
|
|
17
17
|
function jsonConstString(ctx, expr) {
|
|
@@ -21,6 +21,18 @@ function jsonConstString(ctx, expr) {
|
|
|
21
21
|
return null
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function jsonShapeString(ctx, expr) {
|
|
25
|
+
if (typeof expr === 'string') return ctx.scope.shapeStrs?.get(expr) ?? null
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function jsonShapeStrings(ctx, expr) {
|
|
30
|
+
const single = jsonShapeString(ctx, expr)
|
|
31
|
+
if (single != null) return [single]
|
|
32
|
+
if (Array.isArray(expr) && expr[0] === '[]' && typeof expr[1] === 'string') return ctx.scope.shapeStrArrays?.get(expr[1]) ?? null
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
function hashCapFor(n) {
|
|
25
37
|
let cap = 8
|
|
26
38
|
const need = Math.max(1, Math.ceil(n * 4 / 3))
|
|
@@ -36,15 +48,30 @@ export default (ctx) => {
|
|
|
36
48
|
__json_obj: ['__ptr_offset', '__ptr_aux', '__len', '__jput', '__jput_str', '__json_val'],
|
|
37
49
|
__jput_num: ['__ftoa'],
|
|
38
50
|
__jput_str: ['__char_at', '__str_byteLen'],
|
|
39
|
-
__jp: ['__jp_val', '__jp_str', '__jp_num', '__jp_arr', '__jp_obj', '
|
|
51
|
+
__jp: ['__jp_val', '__jp_str', '__jp_num', '__jp_arr', '__jp_obj', '__sso_char', '__ptr_aux', '__ptr_type', '__ptr_offset', '__str_byteLen'],
|
|
52
|
+
__jp_val: ['__jp_str', '__jp_num', '__jp_arr', '__jp_obj'],
|
|
40
53
|
__jp_str: ['__sso_char', '__char_at', '__str_byteLen'],
|
|
41
54
|
__jp_num: ['__pow10'],
|
|
42
55
|
__jp_arr: ['__jp_val'],
|
|
43
|
-
__jp_obj: ['__jp_val', '
|
|
56
|
+
__jp_obj: ['__jp_val', '__jp_str', '__jp_schema_get', '__alloc_hdr', '__mkptr'],
|
|
57
|
+
__jp_schema_get: ['__alloc', '__alloc_hdr', '__mkptr'],
|
|
44
58
|
})
|
|
45
59
|
|
|
60
|
+
// Emit a compile-time-known JSON value tree.
|
|
61
|
+
//
|
|
62
|
+
// Objects → fixed-shape OBJECT (schema-tagged, slot-based). Property reads
|
|
63
|
+
// on the receiving binding compile to direct f64.load at the slot offset
|
|
64
|
+
// (no hash probe, no key-string compare). Per-iter cost ≈ alloc + N stores
|
|
65
|
+
// where N is the schema length, vs HASH's alloc + N hash_set_local_h calls.
|
|
66
|
+
//
|
|
67
|
+
// Arrays → ARRAY pointer with f64 element slots, same as before.
|
|
68
|
+
//
|
|
69
|
+
// For pure-numeric/literal trees (no nested objects with computed values),
|
|
70
|
+
// the {...} static-data fast path in module/object.js would apply if we
|
|
71
|
+
// routed through the same recognizer; for now we always alloc fresh per
|
|
72
|
+
// call to preserve `JSON.parse(SRC); a.x = 7; b.x === original` semantics.
|
|
46
73
|
function emitJsonConstValue(v) {
|
|
47
|
-
if (v == null) return
|
|
74
|
+
if (v == null) return nullExpr()
|
|
48
75
|
if (typeof v === 'number') return asF64(emit(v))
|
|
49
76
|
if (typeof v === 'string') return asF64(emit(['str', v]))
|
|
50
77
|
if (typeof v === 'boolean') return asF64(emit(v ? 1 : 0))
|
|
@@ -57,15 +84,19 @@ export default (ctx) => {
|
|
|
57
84
|
}
|
|
58
85
|
if (typeof v === 'object') {
|
|
59
86
|
const keys = Object.keys(v)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
for (const k of keys) {
|
|
64
|
-
body.push(['local.set', `$${h}`,
|
|
65
|
-
['call', '$__hash_set_local_h', ['local.get', `$${h}`], asF64(emit(['str', k])), ['i32.const', strHashLiteral(k)], emitJsonConstValue(v[k])]])
|
|
87
|
+
// Empty object: minimal OBJECT with no slots.
|
|
88
|
+
if (keys.length === 0) {
|
|
89
|
+
return mkPtrIR(PTR.OBJECT, 0, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', 1], ['i32.const', 8]])
|
|
66
90
|
}
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
const schemaId = ctx.schema.register(keys)
|
|
92
|
+
const t = tempI32('jobj')
|
|
93
|
+
const body = [
|
|
94
|
+
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', keys.length], ['i32.const', 8]]],
|
|
95
|
+
]
|
|
96
|
+
for (let i = 0; i < keys.length; i++) {
|
|
97
|
+
body.push(['f64.store', slotAddr(t, i), asF64(emitJsonConstValue(v[keys[i]]))])
|
|
98
|
+
}
|
|
99
|
+
body.push(mkPtrIR(PTR.OBJECT, schemaId, ['local.get', `$${t}`]))
|
|
69
100
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
70
101
|
}
|
|
71
102
|
return asF64(emit(nullExpr))
|
|
@@ -94,8 +125,8 @@ export default (ctx) => {
|
|
|
94
125
|
(i32.store8 (i32.add (global.get $__jbuf) (global.get $__jpos)) (local.get $b))
|
|
95
126
|
(global.set $__jpos (i32.add (global.get $__jpos) (i32.const 1))))`
|
|
96
127
|
|
|
97
|
-
// __jput_str(ptr:
|
|
98
|
-
ctx.core.stdlib['__jput_str'] = `(func $__jput_str (param $ptr
|
|
128
|
+
// __jput_str(ptr: i64) — append string chars (without quotes) to buffer
|
|
129
|
+
ctx.core.stdlib['__jput_str'] = `(func $__jput_str (param $ptr i64)
|
|
99
130
|
(local $len i32) (local $i i32) (local $ch i32)
|
|
100
131
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
101
132
|
(local.set $i (i32.const 0))
|
|
@@ -120,19 +151,20 @@ export default (ctx) => {
|
|
|
120
151
|
|
|
121
152
|
// __jput_num(val: f64) — convert number to string, append bytes to buffer
|
|
122
153
|
ctx.core.stdlib['__jput_num'] = `(func $__jput_num (param $val f64)
|
|
123
|
-
(call $__jput_str (call $__ftoa (local.get $val) (i32.const 0) (i32.const 0))))`
|
|
154
|
+
(call $__jput_str (i64.reinterpret_f64 (call $__ftoa (local.get $val) (i32.const 0) (i32.const 0)))))`
|
|
124
155
|
|
|
125
|
-
// __json_val(val:
|
|
126
|
-
ctx.core.stdlib['__json_val'] = `(func $__json_val (param $val
|
|
127
|
-
(local $type i32) (local $len i32) (local $i i32) (local $off i32)
|
|
156
|
+
// __json_val(val: i64) — stringify any value, append to buffer
|
|
157
|
+
ctx.core.stdlib['__json_val'] = `(func $__json_val (param $val i64)
|
|
158
|
+
(local $type i32) (local $len i32) (local $i i32) (local $off i32) (local $f f64)
|
|
159
|
+
(local.set $f (f64.reinterpret_i64 (local.get $val)))
|
|
128
160
|
;; Number (not NaN) — but Infinity must be null per JSON spec
|
|
129
|
-
(if (f64.eq (local.get $
|
|
161
|
+
(if (f64.eq (local.get $f) (local.get $f))
|
|
130
162
|
(then
|
|
131
|
-
(if (f64.eq (f64.abs (local.get $
|
|
163
|
+
(if (f64.eq (f64.abs (local.get $f)) (f64.const inf))
|
|
132
164
|
(then
|
|
133
165
|
(call $__jput (i32.const 110)) (call $__jput (i32.const 117))
|
|
134
166
|
(call $__jput (i32.const 108)) (call $__jput (i32.const 108)) (return)))
|
|
135
|
-
(call $__jput_num (local.get $
|
|
167
|
+
(call $__jput_num (local.get $f)) (return)))
|
|
136
168
|
;; NaN-boxed pointer
|
|
137
169
|
(local.set $type (call $__ptr_type (local.get $val)))
|
|
138
170
|
;; Plain NaN (type=0) → null
|
|
@@ -141,8 +173,7 @@ export default (ctx) => {
|
|
|
141
173
|
(call $__jput (i32.const 110)) (call $__jput (i32.const 117))
|
|
142
174
|
(call $__jput (i32.const 108)) (call $__jput (i32.const 108)) (return)))
|
|
143
175
|
;; String
|
|
144
|
-
(if (i32.
|
|
145
|
-
(i32.eq (local.get $type) (i32.const ${PTR.SSO})))
|
|
176
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.STRING}))
|
|
146
177
|
(then
|
|
147
178
|
(call $__jput (i32.const 34))
|
|
148
179
|
(call $__jput_str (local.get $val))
|
|
@@ -157,7 +188,7 @@ export default (ctx) => {
|
|
|
157
188
|
(block $d (loop $l
|
|
158
189
|
(br_if $d (i32.ge_s (local.get $i) (local.get $len)))
|
|
159
190
|
(if (local.get $i) (then (call $__jput (i32.const 44)))) ;; ,
|
|
160
|
-
(call $__json_val (
|
|
191
|
+
(call $__json_val (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
161
192
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
162
193
|
(br $l)))
|
|
163
194
|
(call $__jput (i32.const 93)) ;; ]
|
|
@@ -173,9 +204,9 @@ export default (ctx) => {
|
|
|
173
204
|
(call $__jput (i32.const 110)) (call $__jput (i32.const 117))
|
|
174
205
|
(call $__jput (i32.const 108)) (call $__jput (i32.const 108)))`
|
|
175
206
|
|
|
176
|
-
// __json_hash(val:
|
|
207
|
+
// __json_hash(val: i64) — stringify HASH/MAP: iterate slots, emit {"key":val,...}
|
|
177
208
|
// Slot layout: 24 bytes each — [hash:f64][key:f64][val:f64]. Empty slots have hash==0.
|
|
178
|
-
ctx.core.stdlib['__json_hash'] = `(func $__json_hash (param $val
|
|
209
|
+
ctx.core.stdlib['__json_hash'] = `(func $__json_hash (param $val i64)
|
|
179
210
|
(local $off i32) (local $cap i32) (local $i i32) (local $slot i32) (local $first i32)
|
|
180
211
|
(local.set $off (call $__ptr_offset (local.get $val)))
|
|
181
212
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
@@ -190,10 +221,10 @@ export default (ctx) => {
|
|
|
190
221
|
(then (call $__jput (i32.const 44))))
|
|
191
222
|
(local.set $first (i32.const 0))
|
|
192
223
|
(call $__jput (i32.const 34))
|
|
193
|
-
(call $__jput_str (
|
|
224
|
+
(call $__jput_str (i64.load (i32.add (local.get $slot) (i32.const 8))))
|
|
194
225
|
(call $__jput (i32.const 34))
|
|
195
226
|
(call $__jput (i32.const 58))
|
|
196
|
-
(call $__json_val (
|
|
227
|
+
(call $__json_val (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
197
228
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
198
229
|
(br $l)))
|
|
199
230
|
(call $__jput (i32.const 125)))`
|
|
@@ -202,32 +233,32 @@ export default (ctx) => {
|
|
|
202
233
|
// Schema name table: global $__schema_tbl → array of f64 pointers.
|
|
203
234
|
// schema_tbl[schemaId * 8] = f64 pointer to jz Array of key name strings.
|
|
204
235
|
// Object props are sequential f64 at ptr_offset, indexed same as schema.
|
|
205
|
-
ctx.core.stdlib['__json_obj'] = `(func $__json_obj (param $val
|
|
236
|
+
ctx.core.stdlib['__json_obj'] = `(func $__json_obj (param $val i64)
|
|
206
237
|
(local $off i32) (local $sid i32) (local $keys i32) (local $nkeys i32)
|
|
207
238
|
(local $i i32) (local $koff i32)
|
|
208
239
|
(local.set $off (call $__ptr_offset (local.get $val)))
|
|
209
240
|
(local.set $sid (call $__ptr_aux (local.get $val)))
|
|
210
241
|
;; Load keys array from schema table: schema_tbl + sid * 8
|
|
211
242
|
(local.set $keys (call $__ptr_offset
|
|
212
|
-
(
|
|
243
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))
|
|
213
244
|
(local.set $nkeys (call $__len
|
|
214
|
-
(
|
|
245
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))
|
|
215
246
|
(local.set $koff (local.get $keys))
|
|
216
247
|
(call $__jput (i32.const 123))
|
|
217
248
|
(block $d (loop $l
|
|
218
249
|
(br_if $d (i32.ge_s (local.get $i) (local.get $nkeys)))
|
|
219
250
|
(if (local.get $i) (then (call $__jput (i32.const 44))))
|
|
220
251
|
(call $__jput (i32.const 34))
|
|
221
|
-
(call $__jput_str (
|
|
252
|
+
(call $__jput_str (i64.load (i32.add (local.get $koff) (i32.shl (local.get $i) (i32.const 3)))))
|
|
222
253
|
(call $__jput (i32.const 34))
|
|
223
254
|
(call $__jput (i32.const 58))
|
|
224
|
-
(call $__json_val (
|
|
255
|
+
(call $__json_val (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
225
256
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
226
257
|
(br $l)))
|
|
227
258
|
(call $__jput (i32.const 125)))`
|
|
228
259
|
|
|
229
|
-
// __stringify(val:
|
|
230
|
-
ctx.core.stdlib['__stringify'] = `(func $__stringify (param $val
|
|
260
|
+
// __stringify(val: i64) → f64 (NaN-boxed string)
|
|
261
|
+
ctx.core.stdlib['__stringify'] = `(func $__stringify (param $val i64) (result f64)
|
|
231
262
|
;; Reset output buffer
|
|
232
263
|
(global.set $__jbuf (call $__alloc (i32.const 256)))
|
|
233
264
|
(global.set $__jpos (i32.const 0))
|
|
@@ -241,62 +272,92 @@ export default (ctx) => {
|
|
|
241
272
|
ctx.scope.globals.set('__jpstr', '(global $__jpstr (mut i32) (i32.const 0))') // input string offset
|
|
242
273
|
ctx.scope.globals.set('__jplen', '(global $__jplen (mut i32) (i32.const 0))') // input length
|
|
243
274
|
ctx.scope.globals.set('__jppos', '(global $__jppos (mut i32) (i32.const 0))') // current parse position
|
|
275
|
+
// Side-channel hash for the most-recently-parsed string. __jp_str folds an
|
|
276
|
+
// FNV-1a pass into its scan loop; __jp_obj forwards it to __hash_set_local_h
|
|
277
|
+
// and skips the redundant __str_hash call inside the generic insert. 0 is a
|
|
278
|
+
// sentinel meaning "string had escapes — recompute via __str_hash".
|
|
279
|
+
ctx.scope.globals.set('__jp_keyh', '(global $__jp_keyh (mut i32) (i32.const 0))')
|
|
280
|
+
// Runtime schema infrastructure. __schema_next points at the first free slot
|
|
281
|
+
// in $__schema_tbl reserved for runtime registration; compile.js initializes
|
|
282
|
+
// it to ctx.schema.list.length when __jp_obj is included. The schema cache
|
|
283
|
+
// is a 64-entry open-addressed hash on key-sequence FNV — repeated parses of
|
|
284
|
+
// the same shape reuse a previously-registered sid, so __jp_obj allocates a
|
|
285
|
+
// fresh-shape OBJECT once and converts to slot stores thereafter (skipping
|
|
286
|
+
// every __hash_set_local). Cache slot layout: i32 hash, i32 sid (8 bytes).
|
|
287
|
+
// Hash 0 = empty slot; we bump <=1 to 2 like __str_hash to avoid sentinel
|
|
288
|
+
// collision with valid hashes.
|
|
289
|
+
ctx.scope.globals.set('__schema_next', '(global $__schema_next (mut i32) (i32.const 0))')
|
|
290
|
+
ctx.scope.globals.set('__schema_cache', '(global $__schema_cache (mut i32) (i32.const 0))')
|
|
244
291
|
|
|
245
292
|
// Sentinel-driven peek: __jp copies input to a scratch buffer with 0xFF bytes
|
|
246
293
|
// appended past the end. i32.load8_s sign-extends, so the sentinel reads as -1
|
|
247
|
-
// — exactly the EOF value all callers already test for.
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
ctx.core.stdlib['__jp_adv'] = `(func $__jp_adv (param $n i32)
|
|
253
|
-
(global.set $__jppos (i32.add (global.get $__jppos) (local.get $n))))`
|
|
294
|
+
// — exactly the EOF value all callers already test for. Inlined into every
|
|
295
|
+
// parser body via PEEK/ADV string templates; the per-char function-call
|
|
296
|
+
// overhead (~50 calls/char in well-formed JSON) was the dominant cost.
|
|
297
|
+
const PEEK = `(i32.load8_s (i32.add (global.get $__jpstr) (global.get $__jppos)))`
|
|
298
|
+
const ADV = (n) => `(global.set $__jppos (i32.add (global.get $__jppos) (i32.const ${n})))`
|
|
254
299
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
300
|
+
// Whitespace skip — inlined at every call site as a tight loop. Compact
|
|
301
|
+
// JSON often has zero whitespace between tokens, so the dominant case is
|
|
302
|
+
// a single peek + break. WS chars (9/10/13/32) all fit in [0..32]; we
|
|
303
|
+
// exit on anything > 32 unsigned. The sentinel byte (PEEK returns -1
|
|
304
|
+
// sign-extended) is 0xFFFFFFFF unsigned — > 32 — so the same check
|
|
305
|
+
// handles EOF without a separate guard. Other control chars in [0..8],
|
|
306
|
+
// [11..12], [14..31] would be falsely consumed as WS, but those aren't
|
|
307
|
+
// valid in well-formed JSON anyway.
|
|
308
|
+
let WS_ID = 0
|
|
309
|
+
const WS = () => {
|
|
310
|
+
const id = WS_ID++
|
|
311
|
+
return `(block $jpws_d${id} (loop $jpws_l${id}
|
|
312
|
+
(br_if $jpws_d${id} (i32.gt_u ${PEEK} (i32.const 32)))
|
|
313
|
+
${ADV(1)}
|
|
314
|
+
(br $jpws_l${id})))`
|
|
315
|
+
}
|
|
265
316
|
|
|
266
|
-
// Parse string (after opening " consumed).
|
|
267
|
-
//
|
|
268
|
-
//
|
|
317
|
+
// Parse string (after opening " consumed). Single-pass scan that folds three
|
|
318
|
+
// concerns into one byte loop: simplicity flag (no escapes / no high-bit),
|
|
319
|
+
// SSO byte packing for ≤4-char ASCII keys, and FNV-1a hash. The hash is
|
|
320
|
+
// stashed in $__jp_keyh so __jp_obj can use the prehashed insert and skip
|
|
321
|
+
// a redundant __str_hash call.
|
|
269
322
|
ctx.core.stdlib['__jp_str'] = `(func $__jp_str (result f64)
|
|
270
|
-
(local $start i32) (local $ch i32) (local $len i32) (local $off i32) (local $i i32) (local $simple i32) (local $sso i32)
|
|
323
|
+
(local $start i32) (local $ch i32) (local $len i32) (local $off i32) (local $i i32) (local $simple i32) (local $sso i32) (local $h i32)
|
|
271
324
|
(local.set $start (global.get $__jppos))
|
|
272
325
|
(local.set $simple (i32.const 1))
|
|
326
|
+
(local.set $h (i32.const 0x811c9dc5))
|
|
273
327
|
(block $d (loop $l
|
|
274
|
-
(local.set $ch
|
|
328
|
+
(local.set $ch ${PEEK})
|
|
275
329
|
(br_if $d (i32.eq (local.get $ch) (i32.const 34)))
|
|
276
330
|
(br_if $d (i32.eq (local.get $ch) (i32.const -1)))
|
|
277
331
|
;; Mark non-simple: escape (\\=92) or non-ASCII (load8_s gives <0 for byte≥128).
|
|
278
332
|
(if (i32.or (i32.eq (local.get $ch) (i32.const 92)) (i32.lt_s (local.get $ch) (i32.const 0)))
|
|
279
333
|
(then (local.set $simple (i32.const 0))))
|
|
280
334
|
(if (i32.eq (local.get $ch) (i32.const 92))
|
|
281
|
-
(then
|
|
282
|
-
|
|
335
|
+
(then
|
|
336
|
+
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|
|
337
|
+
${ADV(2)})
|
|
338
|
+
(else
|
|
339
|
+
;; Pack first 4 bytes into SSO slot (used only when len ≤ 4).
|
|
340
|
+
(if (i32.lt_u (local.get $len) (i32.const 4))
|
|
341
|
+
(then (local.set $sso
|
|
342
|
+
(i32.or (local.get $sso)
|
|
343
|
+
(i32.shl (i32.and (local.get $ch) (i32.const 0xFF))
|
|
344
|
+
(i32.shl (local.get $len) (i32.const 3)))))))
|
|
345
|
+
(local.set $h (i32.mul (i32.xor (local.get $h) (i32.and (local.get $ch) (i32.const 0xFF))) (i32.const 0x01000193)))
|
|
346
|
+
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|
|
347
|
+
${ADV(1)}))
|
|
283
348
|
(br $l)))
|
|
284
|
-
|
|
285
|
-
(
|
|
286
|
-
|
|
287
|
-
|
|
349
|
+
;; Stash hash. 0/1 bumped to 2 to match __str_hash convention; escape strings
|
|
350
|
+
;; (simple==0) get sentinel 0 so __jp_obj falls back to non-prehashed insert.
|
|
351
|
+
(global.set $__jp_keyh
|
|
352
|
+
(if (result i32) (local.get $simple)
|
|
353
|
+
(then (if (result i32) (i32.le_s (local.get $h) (i32.const 1))
|
|
354
|
+
(then (i32.add (local.get $h) (i32.const 2))) (else (local.get $h))))
|
|
355
|
+
(else (i32.const 0))))
|
|
356
|
+
${ADV(1)} ;; skip "
|
|
357
|
+
;; SSO fast path: ≤4 ASCII chars, no escapes — bytes already packed inline.
|
|
288
358
|
(if (i32.and (local.get $simple) (i32.le_u (local.get $len) (i32.const 4)))
|
|
289
359
|
(then
|
|
290
|
-
(
|
|
291
|
-
(block $sd (loop $sl
|
|
292
|
-
(br_if $sd (i32.ge_s (local.get $i) (local.get $len)))
|
|
293
|
-
(local.set $sso
|
|
294
|
-
(i32.or (local.get $sso)
|
|
295
|
-
(i32.shl (i32.load8_u (i32.add (i32.add (global.get $__jpstr) (local.get $start)) (local.get $i)))
|
|
296
|
-
(i32.shl (local.get $i) (i32.const 3)))))
|
|
297
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
298
|
-
(br $sl)))
|
|
299
|
-
(return (call $__mkptr (i32.const ${PTR.SSO}) (local.get $len) (local.get $sso)))))
|
|
360
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $len)) (local.get $sso)))))
|
|
300
361
|
;; Simple STRING fast path: no escapes, len > 4 — bulk memcpy from parse buffer,
|
|
301
362
|
;; skip rewind + per-byte escape-decode loop. Hits 5+ char keys without escapes.
|
|
302
363
|
(if (local.get $simple)
|
|
@@ -313,25 +374,25 @@ export default (ctx) => {
|
|
|
313
374
|
(global.set $__jppos (local.get $start)) ;; rewind to re-scan
|
|
314
375
|
(local.set $len (i32.const 0)) ;; actual output length
|
|
315
376
|
(block $d2 (loop $l2
|
|
316
|
-
(local.set $ch
|
|
377
|
+
(local.set $ch ${PEEK})
|
|
317
378
|
(br_if $d2 (i32.eq (local.get $ch) (i32.const 34)))
|
|
318
379
|
(br_if $d2 (i32.eq (local.get $ch) (i32.const -1)))
|
|
319
380
|
(if (i32.eq (local.get $ch) (i32.const 92))
|
|
320
381
|
(then
|
|
321
|
-
|
|
322
|
-
(local.set $ch
|
|
323
|
-
|
|
382
|
+
${ADV(1)}
|
|
383
|
+
(local.set $ch ${PEEK})
|
|
384
|
+
${ADV(1)}
|
|
324
385
|
;; Decode escape: n→10 t→9 r→13 b→8 f→12, else literal
|
|
325
386
|
(if (i32.eq (local.get $ch) (i32.const 110)) (then (local.set $ch (i32.const 10))))
|
|
326
387
|
(if (i32.eq (local.get $ch) (i32.const 116)) (then (local.set $ch (i32.const 9))))
|
|
327
388
|
(if (i32.eq (local.get $ch) (i32.const 114)) (then (local.set $ch (i32.const 13))))
|
|
328
389
|
(if (i32.eq (local.get $ch) (i32.const 98)) (then (local.set $ch (i32.const 8))))
|
|
329
390
|
(if (i32.eq (local.get $ch) (i32.const 102)) (then (local.set $ch (i32.const 12)))))
|
|
330
|
-
(else
|
|
391
|
+
(else ${ADV(1)}))
|
|
331
392
|
(i32.store8 (i32.add (local.get $off) (local.get $len)) (local.get $ch))
|
|
332
393
|
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|
|
333
394
|
(br $l2)))
|
|
334
|
-
|
|
395
|
+
${ADV(1)} ;; skip closing "
|
|
335
396
|
;; Store actual length in header
|
|
336
397
|
(i32.store (i32.sub (local.get $off) (i32.const 4)) (local.get $len))
|
|
337
398
|
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
@@ -340,37 +401,37 @@ export default (ctx) => {
|
|
|
340
401
|
ctx.core.stdlib['__jp_num'] = `(func $__jp_num (result f64)
|
|
341
402
|
(local $neg i32) (local $val f64) (local $scale f64) (local $ch i32)
|
|
342
403
|
(local $exp i32) (local $expNeg i32)
|
|
343
|
-
(if (i32.eq
|
|
344
|
-
(then (local.set $neg (i32.const 1))
|
|
404
|
+
(if (i32.eq ${PEEK} (i32.const 45))
|
|
405
|
+
(then (local.set $neg (i32.const 1)) ${ADV(1)}))
|
|
345
406
|
(block $d (loop $l
|
|
346
|
-
(local.set $ch
|
|
407
|
+
(local.set $ch ${PEEK})
|
|
347
408
|
(br_if $d (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
|
|
348
409
|
(local.set $val (f64.add (f64.mul (local.get $val) (f64.const 10))
|
|
349
410
|
(f64.convert_i32_s (i32.sub (local.get $ch) (i32.const 48)))))
|
|
350
|
-
|
|
351
|
-
(if (i32.eq
|
|
411
|
+
${ADV(1)} (br $l)))
|
|
412
|
+
(if (i32.eq ${PEEK} (i32.const 46))
|
|
352
413
|
(then
|
|
353
|
-
|
|
414
|
+
${ADV(1)}
|
|
354
415
|
(local.set $scale (f64.const 0.1))
|
|
355
416
|
(block $fd (loop $fl
|
|
356
|
-
(local.set $ch
|
|
417
|
+
(local.set $ch ${PEEK})
|
|
357
418
|
(br_if $fd (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
|
|
358
419
|
(local.set $val (f64.add (local.get $val)
|
|
359
420
|
(f64.mul (local.get $scale) (f64.convert_i32_s (i32.sub (local.get $ch) (i32.const 48))))))
|
|
360
421
|
(local.set $scale (f64.mul (local.get $scale) (f64.const 0.1)))
|
|
361
|
-
|
|
362
|
-
(if (i32.or (i32.eq
|
|
422
|
+
${ADV(1)} (br $fl)))))
|
|
423
|
+
(if (i32.or (i32.eq ${PEEK} (i32.const 101)) (i32.eq ${PEEK} (i32.const 69)))
|
|
363
424
|
(then
|
|
364
|
-
|
|
365
|
-
(if (i32.eq
|
|
366
|
-
(then (local.set $expNeg (i32.const 1))
|
|
367
|
-
(else (if (i32.eq
|
|
368
|
-
(then
|
|
425
|
+
${ADV(1)}
|
|
426
|
+
(if (i32.eq ${PEEK} (i32.const 45))
|
|
427
|
+
(then (local.set $expNeg (i32.const 1)) ${ADV(1)})
|
|
428
|
+
(else (if (i32.eq ${PEEK} (i32.const 43))
|
|
429
|
+
(then ${ADV(1)}))))
|
|
369
430
|
(block $ed (loop $el
|
|
370
|
-
(local.set $ch
|
|
431
|
+
(local.set $ch ${PEEK})
|
|
371
432
|
(br_if $ed (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
|
|
372
433
|
(local.set $exp (i32.add (i32.mul (local.get $exp) (i32.const 10)) (i32.sub (local.get $ch) (i32.const 48))))
|
|
373
|
-
|
|
434
|
+
${ADV(1)} (br $el)))
|
|
374
435
|
(if (local.get $expNeg) (then (local.set $exp (i32.sub (i32.const 0) (local.get $exp)))))
|
|
375
436
|
(local.set $val (f64.mul (local.get $val) (call $__pow10
|
|
376
437
|
(if (result i32) (i32.lt_s (local.get $exp) (i32.const 0))
|
|
@@ -385,14 +446,14 @@ export default (ctx) => {
|
|
|
385
446
|
(local.set $cap (i32.const 8))
|
|
386
447
|
(local.set $ptr (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $cap) (i32.const 3)))))
|
|
387
448
|
(local.set $ptr (i32.add (local.get $ptr) (i32.const 8)))
|
|
388
|
-
(
|
|
389
|
-
(if (i32.eq
|
|
390
|
-
(then
|
|
449
|
+
${WS()}
|
|
450
|
+
(if (i32.eq ${PEEK} (i32.const 93))
|
|
451
|
+
(then ${ADV(1)}
|
|
391
452
|
(i32.store (i32.sub (local.get $ptr) (i32.const 8)) (i32.const 0))
|
|
392
453
|
(i32.store (i32.sub (local.get $ptr) (i32.const 4)) (local.get $cap))
|
|
393
454
|
(return (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $ptr)))))
|
|
394
455
|
(block $d (loop $l
|
|
395
|
-
(
|
|
456
|
+
${WS()}
|
|
396
457
|
;; Grow if needed
|
|
397
458
|
(if (i32.ge_s (local.get $len) (local.get $cap))
|
|
398
459
|
(then
|
|
@@ -403,75 +464,350 @@ export default (ctx) => {
|
|
|
403
464
|
(local.set $ptr (local.get $new))))
|
|
404
465
|
(f64.store (i32.add (local.get $ptr) (i32.shl (local.get $len) (i32.const 3))) (call $__jp_val))
|
|
405
466
|
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|
|
406
|
-
(
|
|
407
|
-
(local.set $ch
|
|
467
|
+
${WS()}
|
|
468
|
+
(local.set $ch ${PEEK})
|
|
408
469
|
(br_if $d (i32.eq (local.get $ch) (i32.const 93)))
|
|
409
|
-
(if (i32.eq (local.get $ch) (i32.const 44)) (then
|
|
470
|
+
(if (i32.eq (local.get $ch) (i32.const 44)) (then ${ADV(1)}))
|
|
410
471
|
(br $l)))
|
|
411
|
-
|
|
472
|
+
${ADV(1)}
|
|
412
473
|
(i32.store (i32.sub (local.get $ptr) (i32.const 8)) (local.get $len))
|
|
413
474
|
(i32.store (i32.sub (local.get $ptr) (i32.const 4)) (local.get $cap))
|
|
414
475
|
(call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $ptr)))`
|
|
415
476
|
|
|
416
|
-
//
|
|
477
|
+
// Schema cache lookup/register. Cache is a 64-entry open-addressed table
|
|
478
|
+
// keyed by FNV of (key1_hash, key2_hash, ..., n). On hit, sid is reused
|
|
479
|
+
// and the OBJECT is allocated with that schemaId so subsequent property
|
|
480
|
+
// accesses go through the slot fast path. On miss, register a new schema
|
|
481
|
+
// by allocating a jz Array of key STRINGs and storing it in $__schema_tbl
|
|
482
|
+
// at the next free slot. Allocated lazily on first call.
|
|
483
|
+
//
|
|
484
|
+
// kbuf layout: 16 bytes per entry — [key:i64][val:i64]. n entries at $kbuf.
|
|
485
|
+
// Returns sid (i32). Caller materializes OBJECT with given sid + values.
|
|
486
|
+
ctx.core.stdlib['__jp_schema_get'] = `(func $__jp_schema_get (param $kbuf i32) (param $n i32) (param $hh i32) (result i32)
|
|
487
|
+
(local $cache i32) (local $idx i32) (local $entry i32) (local $eh i32) (local $sid i32)
|
|
488
|
+
(local $karr i32) (local $karr_off i32) (local $i i32) (local $tries i32)
|
|
489
|
+
(local.set $cache (global.get $__schema_cache))
|
|
490
|
+
;; Lazy-init cache: 64 entries × 8 bytes = 512 bytes, zero-filled by alloc.
|
|
491
|
+
(if (i32.eqz (local.get $cache))
|
|
492
|
+
(then
|
|
493
|
+
(local.set $cache (call $__alloc (i32.const 512)))
|
|
494
|
+
(global.set $__schema_cache (local.get $cache))))
|
|
495
|
+
(local.set $idx (i32.and (local.get $hh) (i32.const 63)))
|
|
496
|
+
(block $found (block $miss (loop $probe
|
|
497
|
+
(local.set $entry (i32.add (local.get $cache) (i32.shl (local.get $idx) (i32.const 3))))
|
|
498
|
+
(local.set $eh (i32.load (local.get $entry)))
|
|
499
|
+
(br_if $miss (i32.eqz (local.get $eh)))
|
|
500
|
+
(if (i32.eq (local.get $eh) (local.get $hh))
|
|
501
|
+
(then
|
|
502
|
+
(local.set $sid (i32.load (i32.add (local.get $entry) (i32.const 4))))
|
|
503
|
+
;; Verify by comparing key i64s against schema_tbl[sid]'s key array.
|
|
504
|
+
(local.set $karr (i32.wrap_i64 (i64.and
|
|
505
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))
|
|
506
|
+
(i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
507
|
+
(if (i32.eq (i32.load (i32.sub (local.get $karr) (i32.const 8))) (local.get $n))
|
|
508
|
+
(then
|
|
509
|
+
(local.set $i (i32.const 0))
|
|
510
|
+
(block $eq (block $neq (loop $cmp
|
|
511
|
+
(br_if $eq (i32.ge_s (local.get $i) (local.get $n)))
|
|
512
|
+
(br_if $neq (i64.ne
|
|
513
|
+
(i64.load (i32.add (local.get $karr) (i32.shl (local.get $i) (i32.const 3))))
|
|
514
|
+
(i64.load (i32.add (local.get $kbuf) (i32.shl (local.get $i) (i32.const 4))))))
|
|
515
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
516
|
+
(br $cmp)))
|
|
517
|
+
(br $found)))))
|
|
518
|
+
;; Hash collision or length mismatch — keep probing.
|
|
519
|
+
)
|
|
520
|
+
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
521
|
+
(br_if $miss (i32.ge_s (local.get $tries) (i32.const 64)))
|
|
522
|
+
(local.set $idx (i32.and (i32.add (local.get $idx) (i32.const 1)) (i32.const 63)))
|
|
523
|
+
(br $probe)))
|
|
524
|
+
;; miss: register new schema.
|
|
525
|
+
(local.set $sid (global.get $__schema_next))
|
|
526
|
+
(global.set $__schema_next (i32.add (local.get $sid) (i32.const 1)))
|
|
527
|
+
;; Allocate jz Array of n keys. __alloc_hdr(len, cap, stride) returns base
|
|
528
|
+
;; of slot region with len@-8 and cap@-4. The schema dispatch arm reads
|
|
529
|
+
;; nkeys from -8, so len must equal cap=n.
|
|
530
|
+
(local.set $karr (call $__alloc_hdr (local.get $n) (local.get $n) (i32.const 8)))
|
|
531
|
+
(local.set $i (i32.const 0))
|
|
532
|
+
(block $cd (loop $cl
|
|
533
|
+
(br_if $cd (i32.ge_s (local.get $i) (local.get $n)))
|
|
534
|
+
(i64.store
|
|
535
|
+
(i32.add (local.get $karr) (i32.shl (local.get $i) (i32.const 3)))
|
|
536
|
+
(i64.load (i32.add (local.get $kbuf) (i32.shl (local.get $i) (i32.const 4)))))
|
|
537
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
538
|
+
(br $cl)))
|
|
539
|
+
;; Store ARRAY ptr in schema table at sid.
|
|
540
|
+
(i64.store
|
|
541
|
+
(i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3)))
|
|
542
|
+
(i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $karr))))
|
|
543
|
+
;; Insert into cache at probe position.
|
|
544
|
+
(i32.store (local.get $entry) (local.get $hh))
|
|
545
|
+
(i32.store (i32.add (local.get $entry) (i32.const 4)) (local.get $sid)))
|
|
546
|
+
(local.get $sid))`
|
|
547
|
+
|
|
548
|
+
// Parse object → OBJECT (schema-tagged, slot-based) when key sequence has a
|
|
549
|
+
// cached/registerable shape; falls back to HASH only on extreme key counts.
|
|
550
|
+
// Builds a transient (key, val) buffer during parse, then resolves a sid via
|
|
551
|
+
// the runtime schema cache, allocs an OBJECT, and copies values into slots.
|
|
552
|
+
// Walk-side `obj.prop` accesses then route through the OBJECT fast path
|
|
553
|
+
// (slot load) instead of the dispatcher → __hash_get_local chain.
|
|
417
554
|
ctx.core.stdlib['__jp_obj'] = `(func $__jp_obj (result f64)
|
|
418
|
-
(local $
|
|
419
|
-
(local
|
|
420
|
-
(
|
|
421
|
-
(
|
|
422
|
-
|
|
555
|
+
(local $kbuf i32) (local $kn i32) (local $kcap i32) (local $hh i32)
|
|
556
|
+
(local $key i64) (local $val i64) (local $h i32) (local $ch i32)
|
|
557
|
+
(local $sid i32) (local $obj i32) (local $i i32) (local $newbuf i32)
|
|
558
|
+
(local.set $kcap (i32.const 8))
|
|
559
|
+
(local.set $kbuf (call $__alloc (i32.shl (local.get $kcap) (i32.const 4))))
|
|
560
|
+
(local.set $hh (i32.const 0x811c9dc5))
|
|
561
|
+
${WS()}
|
|
562
|
+
;; Empty object — alloc an empty OBJECT with sid 0 (schema slot 0 may be
|
|
563
|
+
;; empty/unused; downstream Object.keys handles 0-length names array).
|
|
564
|
+
(if (i32.eq ${PEEK} (i32.const 125))
|
|
565
|
+
(then ${ADV(1)}
|
|
566
|
+
(local.set $sid (call $__jp_schema_get (local.get $kbuf) (i32.const 0) (local.get $hh)))
|
|
567
|
+
(return (call $__mkptr (i32.const ${PTR.OBJECT}) (local.get $sid)
|
|
568
|
+
(call $__alloc_hdr (i32.const 0) (i32.const 1) (i32.const 8))))))
|
|
423
569
|
(block $d (loop $l
|
|
424
|
-
(
|
|
425
|
-
(if (i32.eq
|
|
426
|
-
(then
|
|
427
|
-
(local.set $key (call $__jp_str))
|
|
428
|
-
(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
(
|
|
434
|
-
|
|
570
|
+
${WS()}
|
|
571
|
+
(if (i32.eq ${PEEK} (i32.const 34))
|
|
572
|
+
(then ${ADV(1)}))
|
|
573
|
+
(local.set $key (i64.reinterpret_f64 (call $__jp_str)))
|
|
574
|
+
(local.set $h (global.get $__jp_keyh))
|
|
575
|
+
;; Mix key hash into running sequence hash. Escape-bearing keys (h=0)
|
|
576
|
+
;; still mix; identical key sequences differing only by escapes will
|
|
577
|
+
;; collide here, but the verify-step in __jp_schema_get rejects via
|
|
578
|
+
;; i64.ne on the actual key bytes.
|
|
579
|
+
(local.set $hh (i32.mul (i32.xor (local.get $hh) (local.get $h)) (i32.const 0x01000193)))
|
|
580
|
+
${WS()}
|
|
581
|
+
(if (i32.eq ${PEEK} (i32.const 58))
|
|
582
|
+
(then ${ADV(1)}))
|
|
583
|
+
${WS()}
|
|
584
|
+
(local.set $val (i64.reinterpret_f64 (call $__jp_val)))
|
|
585
|
+
;; Grow kbuf if at capacity.
|
|
586
|
+
(if (i32.ge_s (local.get $kn) (local.get $kcap))
|
|
587
|
+
(then
|
|
588
|
+
(local.set $kcap (i32.shl (local.get $kcap) (i32.const 1)))
|
|
589
|
+
(local.set $newbuf (call $__alloc (i32.shl (local.get $kcap) (i32.const 4))))
|
|
590
|
+
(memory.copy (local.get $newbuf) (local.get $kbuf) (i32.shl (local.get $kn) (i32.const 4)))
|
|
591
|
+
(local.set $kbuf (local.get $newbuf))))
|
|
592
|
+
;; Append (key, val).
|
|
593
|
+
(i64.store (i32.add (local.get $kbuf) (i32.shl (local.get $kn) (i32.const 4))) (local.get $key))
|
|
594
|
+
(i64.store (i32.add (local.get $kbuf) (i32.add (i32.shl (local.get $kn) (i32.const 4)) (i32.const 8))) (local.get $val))
|
|
595
|
+
(local.set $kn (i32.add (local.get $kn) (i32.const 1)))
|
|
596
|
+
${WS()}
|
|
597
|
+
(local.set $ch ${PEEK})
|
|
435
598
|
(br_if $d (i32.eq (local.get $ch) (i32.const 125)))
|
|
436
|
-
(if (i32.eq (local.get $ch) (i32.const 44)) (then
|
|
599
|
+
(if (i32.eq (local.get $ch) (i32.const 44)) (then ${ADV(1)}))
|
|
437
600
|
(br $l)))
|
|
438
|
-
|
|
439
|
-
(
|
|
601
|
+
${ADV(1)}
|
|
602
|
+
;; Resolve schema sid (cached or freshly registered).
|
|
603
|
+
(local.set $sid (call $__jp_schema_get (local.get $kbuf) (local.get $kn) (local.get $hh)))
|
|
604
|
+
;; Allocate OBJECT slot region: kn × 8 bytes, with header (size at -8,
|
|
605
|
+
;; cap at -4) matching the static-fold path's emitJsonConstValue layout.
|
|
606
|
+
(local.set $obj (call $__alloc_hdr (i32.const 0) (local.get $kn) (i32.const 8)))
|
|
607
|
+
;; Copy values into OBJECT slots.
|
|
608
|
+
(local.set $i (i32.const 0))
|
|
609
|
+
(block $vd (loop $vl
|
|
610
|
+
(br_if $vd (i32.ge_s (local.get $i) (local.get $kn)))
|
|
611
|
+
(i64.store
|
|
612
|
+
(i32.add (local.get $obj) (i32.shl (local.get $i) (i32.const 3)))
|
|
613
|
+
(i64.load (i32.add (local.get $kbuf) (i32.add (i32.shl (local.get $i) (i32.const 4)) (i32.const 8)))))
|
|
614
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
615
|
+
(br $vl)))
|
|
616
|
+
(call $__mkptr (i32.const ${PTR.OBJECT}) (local.get $sid) (local.get $obj)))`
|
|
440
617
|
|
|
441
618
|
// Main value dispatcher
|
|
442
619
|
ctx.core.stdlib['__jp_val'] = `(func $__jp_val (result f64)
|
|
443
620
|
(local $ch i32)
|
|
444
|
-
(
|
|
445
|
-
(local.set $ch
|
|
621
|
+
${WS()}
|
|
622
|
+
(local.set $ch ${PEEK})
|
|
446
623
|
(if (i32.eq (local.get $ch) (i32.const 34))
|
|
447
|
-
(then
|
|
624
|
+
(then ${ADV(1)} (return (call $__jp_str))))
|
|
448
625
|
(if (i32.eq (local.get $ch) (i32.const 91))
|
|
449
|
-
(then
|
|
626
|
+
(then ${ADV(1)} (return (call $__jp_arr))))
|
|
450
627
|
(if (i32.eq (local.get $ch) (i32.const 123))
|
|
451
|
-
(then
|
|
628
|
+
(then ${ADV(1)} (return (call $__jp_obj))))
|
|
452
629
|
(if (i32.or (i32.and (i32.ge_s (local.get $ch) (i32.const 48)) (i32.le_s (local.get $ch) (i32.const 57)))
|
|
453
630
|
(i32.eq (local.get $ch) (i32.const 45)))
|
|
454
631
|
(then (return (call $__jp_num))))
|
|
455
632
|
(if (i32.eq (local.get $ch) (i32.const 116))
|
|
456
|
-
(then
|
|
633
|
+
(then ${ADV(4)} (return (f64.const 1))))
|
|
457
634
|
(if (i32.eq (local.get $ch) (i32.const 102))
|
|
458
|
-
(then
|
|
635
|
+
(then ${ADV(5)} (return (f64.const 0))))
|
|
459
636
|
(if (i32.eq (local.get $ch) (i32.const 110))
|
|
460
|
-
(then
|
|
461
|
-
|
|
637
|
+
(then ${ADV(4)} (return ${NULL_WAT})))
|
|
638
|
+
${NULL_WAT})`
|
|
639
|
+
|
|
640
|
+
function canSpecializeJsonShape(v) {
|
|
641
|
+
if (v == null) return true
|
|
642
|
+
if (typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean') return true
|
|
643
|
+
if (Array.isArray(v)) return v.length > 0 && v.every(x => sameJsonShape(v[0], x)) && canSpecializeJsonShape(v[0])
|
|
644
|
+
if (typeof v === 'object') return Object.keys(v).every(k => /^[\x20-\x21\x23-\x5b\x5d-\x7e]*$/.test(k) && canSpecializeJsonShape(v[k]))
|
|
645
|
+
return false
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function sameJsonShape(a, b) {
|
|
649
|
+
if (a == null || b == null) return a == null && b == null
|
|
650
|
+
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length > 0 && b.length > 0 && sameJsonShape(a[0], b[0])
|
|
651
|
+
if (typeof a !== typeof b) return false
|
|
652
|
+
if (typeof a !== 'object') return true
|
|
653
|
+
const ak = Object.keys(a), bk = Object.keys(b)
|
|
654
|
+
return ak.length === bk.length && ak.every((k, i) => k === bk[i] && sameJsonShape(a[k], b[k]))
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function emitJsonShapeParser(parsed) {
|
|
658
|
+
if (!canSpecializeJsonShape(parsed)) return null
|
|
659
|
+
ctx.runtime.jsonShapeParsers ||= new Map()
|
|
660
|
+
const sig = JSON.stringify(shapeSignature(parsed))
|
|
661
|
+
const cached = ctx.runtime.jsonShapeParsers.get(sig)
|
|
662
|
+
if (cached) return cached
|
|
663
|
+
|
|
664
|
+
const name = `__jp_shape_${ctx.runtime.jsonShapeParsers.size}`
|
|
665
|
+
const locals = new Map([['len', 'i32'], ['buf', 'i32'], ['i', 'i32'], ['ch', 'i32']])
|
|
666
|
+
let uniq = 0
|
|
667
|
+
const local = (p, t) => {
|
|
668
|
+
const n = `${p}${uniq++}`
|
|
669
|
+
locals.set(n, t)
|
|
670
|
+
return n
|
|
671
|
+
}
|
|
672
|
+
const fail = `(return (call $__jp (local.get $str)))`
|
|
673
|
+
const expect = (byte) => `(if (i32.ne ${PEEK} (i32.const ${byte})) (then ${fail}))
|
|
674
|
+
${ADV(1)}`
|
|
675
|
+
const expectText = (text) => [...text].map(c => expect(c.charCodeAt(0))).join('\n ')
|
|
676
|
+
const parse = (v, out) => {
|
|
677
|
+
if (v == null) return `${expectText('null')}
|
|
678
|
+
(local.set $${out} ${NULL_WAT})`
|
|
679
|
+
if (typeof v === 'boolean') return `${expectText(v ? 'true' : 'false')}
|
|
680
|
+
(local.set $${out} (f64.const ${v ? 1 : 0}))`
|
|
681
|
+
if (typeof v === 'number') return `(local.set $${out} (call $__jp_num))`
|
|
682
|
+
if (typeof v === 'string') return `${expect(34)}
|
|
683
|
+
(local.set $${out} (call $__jp_str))`
|
|
684
|
+
if (Array.isArray(v)) return parseArray(v[0], out)
|
|
685
|
+
return parseObject(v, out)
|
|
686
|
+
}
|
|
687
|
+
const parseObject = (v, out) => {
|
|
688
|
+
const keys = Object.keys(v)
|
|
689
|
+
const obj = local('obj', 'i32')
|
|
690
|
+
const val = local('val', 'f64')
|
|
691
|
+
const sid = ctx.schema.register(keys)
|
|
692
|
+
let body = `${WS()}
|
|
693
|
+
${expect(123)}
|
|
694
|
+
(local.set $${obj} (call $__alloc_hdr (i32.const 0) (i32.const ${Math.max(1, keys.length)}) (i32.const 8)))`
|
|
695
|
+
keys.forEach((k, i) => {
|
|
696
|
+
body += `
|
|
697
|
+
${WS()}
|
|
698
|
+
${expect(34)}
|
|
699
|
+
${expectText(k)}
|
|
700
|
+
${expect(34)}
|
|
701
|
+
${WS()}
|
|
702
|
+
${expect(58)}
|
|
703
|
+
${WS()}
|
|
704
|
+
${parse(v[k], val)}
|
|
705
|
+
(f64.store (i32.add (local.get $${obj}) (i32.const ${i * 8})) (local.get $${val}))
|
|
706
|
+
${WS()}
|
|
707
|
+
${expect(i === keys.length - 1 ? 125 : 44)}`
|
|
708
|
+
})
|
|
709
|
+
if (keys.length === 0) body += `
|
|
710
|
+
${WS()}
|
|
711
|
+
${expect(125)}`
|
|
712
|
+
return `${body}
|
|
713
|
+
(local.set $${out} (call $__mkptr (i32.const ${PTR.OBJECT}) (i32.const ${sid}) (local.get $${obj})))`
|
|
714
|
+
}
|
|
715
|
+
const parseArray = (elem, out) => {
|
|
716
|
+
const ptr = local('arr', 'i32')
|
|
717
|
+
const len = local('alen', 'i32')
|
|
718
|
+
const cap = local('acap', 'i32')
|
|
719
|
+
const val = local('aval', 'f64')
|
|
720
|
+
const next = local('anew', 'i32')
|
|
721
|
+
const id = uniq++
|
|
722
|
+
return `${WS()}
|
|
723
|
+
${expect(91)}
|
|
724
|
+
(local.set $${cap} (i32.const 8))
|
|
725
|
+
(local.set $${ptr} (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $${cap}) (i32.const 3)))))
|
|
726
|
+
(local.set $${ptr} (i32.add (local.get $${ptr}) (i32.const 8)))
|
|
727
|
+
${WS()}
|
|
728
|
+
(if (i32.eq ${PEEK} (i32.const 93))
|
|
729
|
+
(then
|
|
730
|
+
${ADV(1)}
|
|
731
|
+
(i32.store (i32.sub (local.get $${ptr}) (i32.const 8)) (i32.const 0))
|
|
732
|
+
(i32.store (i32.sub (local.get $${ptr}) (i32.const 4)) (local.get $${cap}))
|
|
733
|
+
(local.set $${out} (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $${ptr}))))
|
|
734
|
+
(else
|
|
735
|
+
(block $ad${id} (loop $al${id}
|
|
736
|
+
(if (i32.ge_s (local.get $${len}) (local.get $${cap}))
|
|
737
|
+
(then
|
|
738
|
+
(local.set $${cap} (i32.shl (local.get $${cap}) (i32.const 1)))
|
|
739
|
+
(local.set $${next} (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $${cap}) (i32.const 3)))))
|
|
740
|
+
(local.set $${next} (i32.add (local.get $${next}) (i32.const 8)))
|
|
741
|
+
(memory.copy (local.get $${next}) (local.get $${ptr}) (i32.shl (local.get $${len}) (i32.const 3)))
|
|
742
|
+
(local.set $${ptr} (local.get $${next}))))
|
|
743
|
+
${parse(elem, val)}
|
|
744
|
+
(f64.store (i32.add (local.get $${ptr}) (i32.shl (local.get $${len}) (i32.const 3))) (local.get $${val}))
|
|
745
|
+
(local.set $${len} (i32.add (local.get $${len}) (i32.const 1)))
|
|
746
|
+
${WS()}
|
|
747
|
+
(local.set $ch ${PEEK})
|
|
748
|
+
(br_if $ad${id} (i32.eq (local.get $ch) (i32.const 93)))
|
|
749
|
+
(if (i32.ne (local.get $ch) (i32.const 44)) (then ${fail}))
|
|
750
|
+
${ADV(1)}
|
|
751
|
+
${WS()}
|
|
752
|
+
(br $al${id})))
|
|
753
|
+
${ADV(1)}
|
|
754
|
+
(i32.store (i32.sub (local.get $${ptr}) (i32.const 8)) (local.get $${len}))
|
|
755
|
+
(i32.store (i32.sub (local.get $${ptr}) (i32.const 4)) (local.get $${cap}))
|
|
756
|
+
(local.set $${out} (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $${ptr})))))`
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const out = local('out', 'f64')
|
|
760
|
+
const body = `${parse(parsed, out)}
|
|
761
|
+
${WS()}
|
|
762
|
+
(if (i32.ne ${PEEK} (i32.const -1)) (then ${fail}))
|
|
763
|
+
(local.get $${out})`
|
|
764
|
+
const localDecls = [...locals].map(([n, t]) => ` (local $${n} ${t})`).join('\n')
|
|
765
|
+
ctx.core.stdlib[name] = `(func $${name} (param $str i64) (result f64)
|
|
766
|
+
${localDecls}
|
|
767
|
+
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
768
|
+
(local.set $buf (call $__alloc (i32.add (local.get $len) (i32.const 8))))
|
|
769
|
+
(i64.store (i32.add (local.get $buf) (local.get $len)) (i64.const -1))
|
|
770
|
+
(if (i32.and (call $__ptr_aux (local.get $str)) (i32.const ${LAYOUT.SSO_BIT}))
|
|
771
|
+
(then
|
|
772
|
+
(local.set $i (i32.const 0))
|
|
773
|
+
(block $sd (loop $sl
|
|
774
|
+
(br_if $sd (i32.ge_s (local.get $i) (local.get $len)))
|
|
775
|
+
(i32.store8 (i32.add (local.get $buf) (local.get $i))
|
|
776
|
+
(call $__sso_char (local.get $str) (local.get $i)))
|
|
777
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
778
|
+
(br $sl))))
|
|
779
|
+
(else
|
|
780
|
+
(memory.copy (local.get $buf) (call $__ptr_offset (local.get $str)) (local.get $len))))
|
|
781
|
+
(global.set $__jpstr (local.get $buf))
|
|
782
|
+
(global.set $__jplen (local.get $len))
|
|
783
|
+
(global.set $__jppos (i32.const 0))
|
|
784
|
+
${body})`
|
|
785
|
+
ctx.core.stdlibDeps[name] = ['__jp', '__jp_num', '__jp_str', '__str_byteLen', '__alloc', '__ptr_aux', '__sso_char', '__ptr_offset', '__alloc_hdr', '__mkptr']
|
|
786
|
+
ctx.runtime.jsonShapeParsers.set(sig, name)
|
|
787
|
+
return name
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function shapeSignature(v) {
|
|
791
|
+
if (v == null) return null
|
|
792
|
+
if (typeof v === 'number') return 'number'
|
|
793
|
+
if (typeof v === 'string') return 'string'
|
|
794
|
+
if (typeof v === 'boolean') return 'boolean'
|
|
795
|
+
if (Array.isArray(v)) return ['array', shapeSignature(v[0])]
|
|
796
|
+
return ['object', Object.keys(v).map(k => [k, shapeSignature(v[k])])]
|
|
797
|
+
}
|
|
462
798
|
|
|
463
799
|
// Entry point — copies input to a scratch buffer with 0xFF sentinel padding
|
|
464
800
|
// past the end so __jp_peek can omit its bounds check. Pad is 8 bytes so any
|
|
465
801
|
// overshoot from speculative peek/adv on malformed input still hits sentinel,
|
|
466
802
|
// not unallocated memory.
|
|
467
|
-
ctx.core.stdlib['__jp'] = `(func $__jp (param $str
|
|
803
|
+
ctx.core.stdlib['__jp'] = `(func $__jp (param $str i64) (result f64)
|
|
468
804
|
(local $len i32) (local $buf i32) (local $i i32)
|
|
469
805
|
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
470
806
|
(local.set $buf (call $__alloc (i32.add (local.get $len) (i32.const 8))))
|
|
471
807
|
;; Pre-fill 8 sentinel bytes at end (writes overlapping a 64-bit slot).
|
|
472
808
|
(i64.store (i32.add (local.get $buf) (local.get $len)) (i64.const -1))
|
|
473
|
-
;; SSO: byte-by-byte via __sso_char; STRING: bulk memcpy from string offset.
|
|
474
|
-
(if (i32.
|
|
809
|
+
;; SSO: byte-by-byte via __sso_char; heap STRING: bulk memcpy from string offset.
|
|
810
|
+
(if (i32.and (call $__ptr_aux (local.get $str)) (i32.const ${LAYOUT.SSO_BIT}))
|
|
475
811
|
(then
|
|
476
812
|
(local.set $i (i32.const 0))
|
|
477
813
|
(block $d (loop $l
|
|
@@ -491,7 +827,7 @@ export default (ctx) => {
|
|
|
491
827
|
|
|
492
828
|
ctx.core.emit['JSON.stringify'] = (x) => {
|
|
493
829
|
inc('__stringify')
|
|
494
|
-
return typed(['call', '$__stringify',
|
|
830
|
+
return typed(['call', '$__stringify', asI64(emit(x))], 'f64')
|
|
495
831
|
}
|
|
496
832
|
|
|
497
833
|
ctx.core.emit['JSON.parse'] = (x) => {
|
|
@@ -500,7 +836,16 @@ export default (ctx) => {
|
|
|
500
836
|
try { return emitJsonConstValue(JSON.parse(src)) }
|
|
501
837
|
catch { /* fall through to runtime parser for invalid JSON so runtime behavior stays unchanged */ }
|
|
502
838
|
}
|
|
839
|
+
const shapeSrcs = jsonShapeStrings(ctx, x)
|
|
840
|
+
if (shapeSrcs) {
|
|
841
|
+
try {
|
|
842
|
+
const parsed = shapeSrcs.map(src => JSON.parse(src))
|
|
843
|
+
if (!parsed.every(v => sameJsonShape(parsed[0], v))) throw new Error('mixed JSON shapes')
|
|
844
|
+
const fn = emitJsonShapeParser(parsed[0])
|
|
845
|
+
if (fn) { inc(fn); return typed(['call', `$${fn}`, asI64(emit(x))], 'f64') }
|
|
846
|
+
} catch { /* fall through to generic runtime parser */ }
|
|
847
|
+
}
|
|
503
848
|
inc('__jp')
|
|
504
|
-
return typed(['call', '$__jp',
|
|
849
|
+
return typed(['call', '$__jp', asI64(emit(x))], 'f64')
|
|
505
850
|
}
|
|
506
851
|
}
|