jz 0.1.0 → 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 +221 -62
- package/cli.js +34 -8
- package/index.js +133 -14
- package/module/array.js +399 -131
- package/module/collection.js +457 -212
- package/module/console.js +170 -87
- package/module/core.js +247 -143
- package/module/date.js +691 -0
- package/module/function.js +84 -23
- package/module/index.js +2 -1
- package/module/json.js +485 -138
- package/module/math.js +81 -40
- package/module/number.js +189 -83
- package/module/object.js +228 -46
- package/module/regex.js +34 -30
- package/module/schema.js +17 -18
- package/module/string.js +483 -227
- package/module/symbol.js +6 -4
- package/module/timer.js +90 -32
- package/module/typedarray.js +176 -44
- package/package.json +5 -10
- package/src/analyze.js +639 -124
- package/src/autoload.js +183 -0
- package/src/compile.js +448 -1083
- package/src/ctx.js +42 -12
- package/src/emit.js +646 -147
- package/src/host.js +273 -68
- package/src/ir.js +81 -31
- package/src/jzify.js +220 -36
- package/src/narrow.js +956 -0
- package/src/optimize.js +628 -42
- package/src/plan.js +1233 -0
- package/src/prepare.js +438 -256
- package/src/vectorize.js +1016 -0
- package/wasi.js +23 -4
package/module/json.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* @module json
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { typed, asF64, asI64, temp, tempI32, nullExpr, allocPtr, slotAddr, mkPtrIR, extractF64Bits, appendStaticSlots, NULL_WAT } from '../src/ir.js'
|
|
12
|
+
import { emit } from '../src/emit.js'
|
|
13
|
+
import { T } from '../src/analyze.js'
|
|
14
|
+
import { err, inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
13
15
|
import { strHashLiteral } from './collection.js'
|
|
14
16
|
|
|
15
17
|
function jsonConstString(ctx, expr) {
|
|
@@ -19,6 +21,18 @@ function jsonConstString(ctx, expr) {
|
|
|
19
21
|
return null
|
|
20
22
|
}
|
|
21
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
|
+
|
|
22
36
|
function hashCapFor(n) {
|
|
23
37
|
let cap = 8
|
|
24
38
|
const need = Math.max(1, Math.ceil(n * 4 / 3))
|
|
@@ -34,15 +48,30 @@ export default (ctx) => {
|
|
|
34
48
|
__json_obj: ['__ptr_offset', '__ptr_aux', '__len', '__jput', '__jput_str', '__json_val'],
|
|
35
49
|
__jput_num: ['__ftoa'],
|
|
36
50
|
__jput_str: ['__char_at', '__str_byteLen'],
|
|
37
|
-
__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'],
|
|
38
53
|
__jp_str: ['__sso_char', '__char_at', '__str_byteLen'],
|
|
39
54
|
__jp_num: ['__pow10'],
|
|
40
55
|
__jp_arr: ['__jp_val'],
|
|
41
|
-
__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'],
|
|
42
58
|
})
|
|
43
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.
|
|
44
73
|
function emitJsonConstValue(v) {
|
|
45
|
-
if (v == null) return
|
|
74
|
+
if (v == null) return nullExpr()
|
|
46
75
|
if (typeof v === 'number') return asF64(emit(v))
|
|
47
76
|
if (typeof v === 'string') return asF64(emit(['str', v]))
|
|
48
77
|
if (typeof v === 'boolean') return asF64(emit(v ? 1 : 0))
|
|
@@ -55,15 +84,19 @@ export default (ctx) => {
|
|
|
55
84
|
}
|
|
56
85
|
if (typeof v === 'object') {
|
|
57
86
|
const keys = Object.keys(v)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
for (const k of keys) {
|
|
62
|
-
body.push(['local.set', `$${h}`,
|
|
63
|
-
['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]])
|
|
64
90
|
}
|
|
65
|
-
|
|
66
|
-
|
|
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}`]))
|
|
67
100
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
68
101
|
}
|
|
69
102
|
return asF64(emit(nullExpr))
|
|
@@ -92,8 +125,8 @@ export default (ctx) => {
|
|
|
92
125
|
(i32.store8 (i32.add (global.get $__jbuf) (global.get $__jpos)) (local.get $b))
|
|
93
126
|
(global.set $__jpos (i32.add (global.get $__jpos) (i32.const 1))))`
|
|
94
127
|
|
|
95
|
-
// __jput_str(ptr:
|
|
96
|
-
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)
|
|
97
130
|
(local $len i32) (local $i i32) (local $ch i32)
|
|
98
131
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
99
132
|
(local.set $i (i32.const 0))
|
|
@@ -118,19 +151,20 @@ export default (ctx) => {
|
|
|
118
151
|
|
|
119
152
|
// __jput_num(val: f64) — convert number to string, append bytes to buffer
|
|
120
153
|
ctx.core.stdlib['__jput_num'] = `(func $__jput_num (param $val f64)
|
|
121
|
-
(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)))))`
|
|
122
155
|
|
|
123
|
-
// __json_val(val:
|
|
124
|
-
ctx.core.stdlib['__json_val'] = `(func $__json_val (param $val
|
|
125
|
-
(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)))
|
|
126
160
|
;; Number (not NaN) — but Infinity must be null per JSON spec
|
|
127
|
-
(if (f64.eq (local.get $
|
|
161
|
+
(if (f64.eq (local.get $f) (local.get $f))
|
|
128
162
|
(then
|
|
129
|
-
(if (f64.eq (f64.abs (local.get $
|
|
163
|
+
(if (f64.eq (f64.abs (local.get $f)) (f64.const inf))
|
|
130
164
|
(then
|
|
131
165
|
(call $__jput (i32.const 110)) (call $__jput (i32.const 117))
|
|
132
166
|
(call $__jput (i32.const 108)) (call $__jput (i32.const 108)) (return)))
|
|
133
|
-
(call $__jput_num (local.get $
|
|
167
|
+
(call $__jput_num (local.get $f)) (return)))
|
|
134
168
|
;; NaN-boxed pointer
|
|
135
169
|
(local.set $type (call $__ptr_type (local.get $val)))
|
|
136
170
|
;; Plain NaN (type=0) → null
|
|
@@ -139,8 +173,7 @@ export default (ctx) => {
|
|
|
139
173
|
(call $__jput (i32.const 110)) (call $__jput (i32.const 117))
|
|
140
174
|
(call $__jput (i32.const 108)) (call $__jput (i32.const 108)) (return)))
|
|
141
175
|
;; String
|
|
142
|
-
(if (i32.
|
|
143
|
-
(i32.eq (local.get $type) (i32.const ${PTR.SSO})))
|
|
176
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.STRING}))
|
|
144
177
|
(then
|
|
145
178
|
(call $__jput (i32.const 34))
|
|
146
179
|
(call $__jput_str (local.get $val))
|
|
@@ -155,7 +188,7 @@ export default (ctx) => {
|
|
|
155
188
|
(block $d (loop $l
|
|
156
189
|
(br_if $d (i32.ge_s (local.get $i) (local.get $len)))
|
|
157
190
|
(if (local.get $i) (then (call $__jput (i32.const 44)))) ;; ,
|
|
158
|
-
(call $__json_val (
|
|
191
|
+
(call $__json_val (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
159
192
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
160
193
|
(br $l)))
|
|
161
194
|
(call $__jput (i32.const 93)) ;; ]
|
|
@@ -171,9 +204,9 @@ export default (ctx) => {
|
|
|
171
204
|
(call $__jput (i32.const 110)) (call $__jput (i32.const 117))
|
|
172
205
|
(call $__jput (i32.const 108)) (call $__jput (i32.const 108)))`
|
|
173
206
|
|
|
174
|
-
// __json_hash(val:
|
|
207
|
+
// __json_hash(val: i64) — stringify HASH/MAP: iterate slots, emit {"key":val,...}
|
|
175
208
|
// Slot layout: 24 bytes each — [hash:f64][key:f64][val:f64]. Empty slots have hash==0.
|
|
176
|
-
ctx.core.stdlib['__json_hash'] = `(func $__json_hash (param $val
|
|
209
|
+
ctx.core.stdlib['__json_hash'] = `(func $__json_hash (param $val i64)
|
|
177
210
|
(local $off i32) (local $cap i32) (local $i i32) (local $slot i32) (local $first i32)
|
|
178
211
|
(local.set $off (call $__ptr_offset (local.get $val)))
|
|
179
212
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
@@ -188,10 +221,10 @@ export default (ctx) => {
|
|
|
188
221
|
(then (call $__jput (i32.const 44))))
|
|
189
222
|
(local.set $first (i32.const 0))
|
|
190
223
|
(call $__jput (i32.const 34))
|
|
191
|
-
(call $__jput_str (
|
|
224
|
+
(call $__jput_str (i64.load (i32.add (local.get $slot) (i32.const 8))))
|
|
192
225
|
(call $__jput (i32.const 34))
|
|
193
226
|
(call $__jput (i32.const 58))
|
|
194
|
-
(call $__json_val (
|
|
227
|
+
(call $__json_val (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
195
228
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
196
229
|
(br $l)))
|
|
197
230
|
(call $__jput (i32.const 125)))`
|
|
@@ -200,32 +233,32 @@ export default (ctx) => {
|
|
|
200
233
|
// Schema name table: global $__schema_tbl → array of f64 pointers.
|
|
201
234
|
// schema_tbl[schemaId * 8] = f64 pointer to jz Array of key name strings.
|
|
202
235
|
// Object props are sequential f64 at ptr_offset, indexed same as schema.
|
|
203
|
-
ctx.core.stdlib['__json_obj'] = `(func $__json_obj (param $val
|
|
236
|
+
ctx.core.stdlib['__json_obj'] = `(func $__json_obj (param $val i64)
|
|
204
237
|
(local $off i32) (local $sid i32) (local $keys i32) (local $nkeys i32)
|
|
205
238
|
(local $i i32) (local $koff i32)
|
|
206
239
|
(local.set $off (call $__ptr_offset (local.get $val)))
|
|
207
240
|
(local.set $sid (call $__ptr_aux (local.get $val)))
|
|
208
241
|
;; Load keys array from schema table: schema_tbl + sid * 8
|
|
209
242
|
(local.set $keys (call $__ptr_offset
|
|
210
|
-
(
|
|
243
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))
|
|
211
244
|
(local.set $nkeys (call $__len
|
|
212
|
-
(
|
|
245
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))
|
|
213
246
|
(local.set $koff (local.get $keys))
|
|
214
247
|
(call $__jput (i32.const 123))
|
|
215
248
|
(block $d (loop $l
|
|
216
249
|
(br_if $d (i32.ge_s (local.get $i) (local.get $nkeys)))
|
|
217
250
|
(if (local.get $i) (then (call $__jput (i32.const 44))))
|
|
218
251
|
(call $__jput (i32.const 34))
|
|
219
|
-
(call $__jput_str (
|
|
252
|
+
(call $__jput_str (i64.load (i32.add (local.get $koff) (i32.shl (local.get $i) (i32.const 3)))))
|
|
220
253
|
(call $__jput (i32.const 34))
|
|
221
254
|
(call $__jput (i32.const 58))
|
|
222
|
-
(call $__json_val (
|
|
255
|
+
(call $__json_val (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
223
256
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
224
257
|
(br $l)))
|
|
225
258
|
(call $__jput (i32.const 125)))`
|
|
226
259
|
|
|
227
|
-
// __stringify(val:
|
|
228
|
-
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)
|
|
229
262
|
;; Reset output buffer
|
|
230
263
|
(global.set $__jbuf (call $__alloc (i32.const 256)))
|
|
231
264
|
(global.set $__jpos (i32.const 0))
|
|
@@ -239,62 +272,92 @@ export default (ctx) => {
|
|
|
239
272
|
ctx.scope.globals.set('__jpstr', '(global $__jpstr (mut i32) (i32.const 0))') // input string offset
|
|
240
273
|
ctx.scope.globals.set('__jplen', '(global $__jplen (mut i32) (i32.const 0))') // input length
|
|
241
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))')
|
|
242
291
|
|
|
243
292
|
// Sentinel-driven peek: __jp copies input to a scratch buffer with 0xFF bytes
|
|
244
293
|
// appended past the end. i32.load8_s sign-extends, so the sentinel reads as -1
|
|
245
|
-
// — exactly the EOF value all callers already test for.
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
ctx.core.stdlib['__jp_adv'] = `(func $__jp_adv (param $n i32)
|
|
251
|
-
(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})))`
|
|
252
299
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+
}
|
|
263
316
|
|
|
264
|
-
// Parse string (after opening " consumed).
|
|
265
|
-
//
|
|
266
|
-
//
|
|
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.
|
|
267
322
|
ctx.core.stdlib['__jp_str'] = `(func $__jp_str (result f64)
|
|
268
|
-
(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)
|
|
269
324
|
(local.set $start (global.get $__jppos))
|
|
270
325
|
(local.set $simple (i32.const 1))
|
|
326
|
+
(local.set $h (i32.const 0x811c9dc5))
|
|
271
327
|
(block $d (loop $l
|
|
272
|
-
(local.set $ch
|
|
328
|
+
(local.set $ch ${PEEK})
|
|
273
329
|
(br_if $d (i32.eq (local.get $ch) (i32.const 34)))
|
|
274
330
|
(br_if $d (i32.eq (local.get $ch) (i32.const -1)))
|
|
275
331
|
;; Mark non-simple: escape (\\=92) or non-ASCII (load8_s gives <0 for byte≥128).
|
|
276
332
|
(if (i32.or (i32.eq (local.get $ch) (i32.const 92)) (i32.lt_s (local.get $ch) (i32.const 0)))
|
|
277
333
|
(then (local.set $simple (i32.const 0))))
|
|
278
334
|
(if (i32.eq (local.get $ch) (i32.const 92))
|
|
279
|
-
(then
|
|
280
|
-
|
|
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)}))
|
|
281
348
|
(br $l)))
|
|
282
|
-
|
|
283
|
-
(
|
|
284
|
-
|
|
285
|
-
|
|
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.
|
|
286
358
|
(if (i32.and (local.get $simple) (i32.le_u (local.get $len) (i32.const 4)))
|
|
287
359
|
(then
|
|
288
|
-
(
|
|
289
|
-
(block $sd (loop $sl
|
|
290
|
-
(br_if $sd (i32.ge_s (local.get $i) (local.get $len)))
|
|
291
|
-
(local.set $sso
|
|
292
|
-
(i32.or (local.get $sso)
|
|
293
|
-
(i32.shl (i32.load8_u (i32.add (i32.add (global.get $__jpstr) (local.get $start)) (local.get $i)))
|
|
294
|
-
(i32.shl (local.get $i) (i32.const 3)))))
|
|
295
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
296
|
-
(br $sl)))
|
|
297
|
-
(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)))))
|
|
298
361
|
;; Simple STRING fast path: no escapes, len > 4 — bulk memcpy from parse buffer,
|
|
299
362
|
;; skip rewind + per-byte escape-decode loop. Hits 5+ char keys without escapes.
|
|
300
363
|
(if (local.get $simple)
|
|
@@ -311,25 +374,25 @@ export default (ctx) => {
|
|
|
311
374
|
(global.set $__jppos (local.get $start)) ;; rewind to re-scan
|
|
312
375
|
(local.set $len (i32.const 0)) ;; actual output length
|
|
313
376
|
(block $d2 (loop $l2
|
|
314
|
-
(local.set $ch
|
|
377
|
+
(local.set $ch ${PEEK})
|
|
315
378
|
(br_if $d2 (i32.eq (local.get $ch) (i32.const 34)))
|
|
316
379
|
(br_if $d2 (i32.eq (local.get $ch) (i32.const -1)))
|
|
317
380
|
(if (i32.eq (local.get $ch) (i32.const 92))
|
|
318
381
|
(then
|
|
319
|
-
|
|
320
|
-
(local.set $ch
|
|
321
|
-
|
|
382
|
+
${ADV(1)}
|
|
383
|
+
(local.set $ch ${PEEK})
|
|
384
|
+
${ADV(1)}
|
|
322
385
|
;; Decode escape: n→10 t→9 r→13 b→8 f→12, else literal
|
|
323
386
|
(if (i32.eq (local.get $ch) (i32.const 110)) (then (local.set $ch (i32.const 10))))
|
|
324
387
|
(if (i32.eq (local.get $ch) (i32.const 116)) (then (local.set $ch (i32.const 9))))
|
|
325
388
|
(if (i32.eq (local.get $ch) (i32.const 114)) (then (local.set $ch (i32.const 13))))
|
|
326
389
|
(if (i32.eq (local.get $ch) (i32.const 98)) (then (local.set $ch (i32.const 8))))
|
|
327
390
|
(if (i32.eq (local.get $ch) (i32.const 102)) (then (local.set $ch (i32.const 12)))))
|
|
328
|
-
(else
|
|
391
|
+
(else ${ADV(1)}))
|
|
329
392
|
(i32.store8 (i32.add (local.get $off) (local.get $len)) (local.get $ch))
|
|
330
393
|
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|
|
331
394
|
(br $l2)))
|
|
332
|
-
|
|
395
|
+
${ADV(1)} ;; skip closing "
|
|
333
396
|
;; Store actual length in header
|
|
334
397
|
(i32.store (i32.sub (local.get $off) (i32.const 4)) (local.get $len))
|
|
335
398
|
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
@@ -338,37 +401,37 @@ export default (ctx) => {
|
|
|
338
401
|
ctx.core.stdlib['__jp_num'] = `(func $__jp_num (result f64)
|
|
339
402
|
(local $neg i32) (local $val f64) (local $scale f64) (local $ch i32)
|
|
340
403
|
(local $exp i32) (local $expNeg i32)
|
|
341
|
-
(if (i32.eq
|
|
342
|
-
(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)}))
|
|
343
406
|
(block $d (loop $l
|
|
344
|
-
(local.set $ch
|
|
407
|
+
(local.set $ch ${PEEK})
|
|
345
408
|
(br_if $d (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
|
|
346
409
|
(local.set $val (f64.add (f64.mul (local.get $val) (f64.const 10))
|
|
347
410
|
(f64.convert_i32_s (i32.sub (local.get $ch) (i32.const 48)))))
|
|
348
|
-
|
|
349
|
-
(if (i32.eq
|
|
411
|
+
${ADV(1)} (br $l)))
|
|
412
|
+
(if (i32.eq ${PEEK} (i32.const 46))
|
|
350
413
|
(then
|
|
351
|
-
|
|
414
|
+
${ADV(1)}
|
|
352
415
|
(local.set $scale (f64.const 0.1))
|
|
353
416
|
(block $fd (loop $fl
|
|
354
|
-
(local.set $ch
|
|
417
|
+
(local.set $ch ${PEEK})
|
|
355
418
|
(br_if $fd (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
|
|
356
419
|
(local.set $val (f64.add (local.get $val)
|
|
357
420
|
(f64.mul (local.get $scale) (f64.convert_i32_s (i32.sub (local.get $ch) (i32.const 48))))))
|
|
358
421
|
(local.set $scale (f64.mul (local.get $scale) (f64.const 0.1)))
|
|
359
|
-
|
|
360
|
-
(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)))
|
|
361
424
|
(then
|
|
362
|
-
|
|
363
|
-
(if (i32.eq
|
|
364
|
-
(then (local.set $expNeg (i32.const 1))
|
|
365
|
-
(else (if (i32.eq
|
|
366
|
-
(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)}))))
|
|
367
430
|
(block $ed (loop $el
|
|
368
|
-
(local.set $ch
|
|
431
|
+
(local.set $ch ${PEEK})
|
|
369
432
|
(br_if $ed (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
|
|
370
433
|
(local.set $exp (i32.add (i32.mul (local.get $exp) (i32.const 10)) (i32.sub (local.get $ch) (i32.const 48))))
|
|
371
|
-
|
|
434
|
+
${ADV(1)} (br $el)))
|
|
372
435
|
(if (local.get $expNeg) (then (local.set $exp (i32.sub (i32.const 0) (local.get $exp)))))
|
|
373
436
|
(local.set $val (f64.mul (local.get $val) (call $__pow10
|
|
374
437
|
(if (result i32) (i32.lt_s (local.get $exp) (i32.const 0))
|
|
@@ -383,14 +446,14 @@ export default (ctx) => {
|
|
|
383
446
|
(local.set $cap (i32.const 8))
|
|
384
447
|
(local.set $ptr (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $cap) (i32.const 3)))))
|
|
385
448
|
(local.set $ptr (i32.add (local.get $ptr) (i32.const 8)))
|
|
386
|
-
(
|
|
387
|
-
(if (i32.eq
|
|
388
|
-
(then
|
|
449
|
+
${WS()}
|
|
450
|
+
(if (i32.eq ${PEEK} (i32.const 93))
|
|
451
|
+
(then ${ADV(1)}
|
|
389
452
|
(i32.store (i32.sub (local.get $ptr) (i32.const 8)) (i32.const 0))
|
|
390
453
|
(i32.store (i32.sub (local.get $ptr) (i32.const 4)) (local.get $cap))
|
|
391
454
|
(return (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $ptr)))))
|
|
392
455
|
(block $d (loop $l
|
|
393
|
-
(
|
|
456
|
+
${WS()}
|
|
394
457
|
;; Grow if needed
|
|
395
458
|
(if (i32.ge_s (local.get $len) (local.get $cap))
|
|
396
459
|
(then
|
|
@@ -401,75 +464,350 @@ export default (ctx) => {
|
|
|
401
464
|
(local.set $ptr (local.get $new))))
|
|
402
465
|
(f64.store (i32.add (local.get $ptr) (i32.shl (local.get $len) (i32.const 3))) (call $__jp_val))
|
|
403
466
|
(local.set $len (i32.add (local.get $len) (i32.const 1)))
|
|
404
|
-
(
|
|
405
|
-
(local.set $ch
|
|
467
|
+
${WS()}
|
|
468
|
+
(local.set $ch ${PEEK})
|
|
406
469
|
(br_if $d (i32.eq (local.get $ch) (i32.const 93)))
|
|
407
|
-
(if (i32.eq (local.get $ch) (i32.const 44)) (then
|
|
470
|
+
(if (i32.eq (local.get $ch) (i32.const 44)) (then ${ADV(1)}))
|
|
408
471
|
(br $l)))
|
|
409
|
-
|
|
472
|
+
${ADV(1)}
|
|
410
473
|
(i32.store (i32.sub (local.get $ptr) (i32.const 8)) (local.get $len))
|
|
411
474
|
(i32.store (i32.sub (local.get $ptr) (i32.const 4)) (local.get $cap))
|
|
412
475
|
(call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $ptr)))`
|
|
413
476
|
|
|
414
|
-
//
|
|
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.
|
|
415
554
|
ctx.core.stdlib['__jp_obj'] = `(func $__jp_obj (result f64)
|
|
416
|
-
(local $
|
|
417
|
-
(local
|
|
418
|
-
(
|
|
419
|
-
(
|
|
420
|
-
|
|
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))))))
|
|
421
569
|
(block $d (loop $l
|
|
422
|
-
(
|
|
423
|
-
(if (i32.eq
|
|
424
|
-
(then
|
|
425
|
-
(local.set $key (call $__jp_str))
|
|
426
|
-
(
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
(
|
|
432
|
-
|
|
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})
|
|
433
598
|
(br_if $d (i32.eq (local.get $ch) (i32.const 125)))
|
|
434
|
-
(if (i32.eq (local.get $ch) (i32.const 44)) (then
|
|
599
|
+
(if (i32.eq (local.get $ch) (i32.const 44)) (then ${ADV(1)}))
|
|
435
600
|
(br $l)))
|
|
436
|
-
|
|
437
|
-
(
|
|
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)))`
|
|
438
617
|
|
|
439
618
|
// Main value dispatcher
|
|
440
619
|
ctx.core.stdlib['__jp_val'] = `(func $__jp_val (result f64)
|
|
441
620
|
(local $ch i32)
|
|
442
|
-
(
|
|
443
|
-
(local.set $ch
|
|
621
|
+
${WS()}
|
|
622
|
+
(local.set $ch ${PEEK})
|
|
444
623
|
(if (i32.eq (local.get $ch) (i32.const 34))
|
|
445
|
-
(then
|
|
624
|
+
(then ${ADV(1)} (return (call $__jp_str))))
|
|
446
625
|
(if (i32.eq (local.get $ch) (i32.const 91))
|
|
447
|
-
(then
|
|
626
|
+
(then ${ADV(1)} (return (call $__jp_arr))))
|
|
448
627
|
(if (i32.eq (local.get $ch) (i32.const 123))
|
|
449
|
-
(then
|
|
628
|
+
(then ${ADV(1)} (return (call $__jp_obj))))
|
|
450
629
|
(if (i32.or (i32.and (i32.ge_s (local.get $ch) (i32.const 48)) (i32.le_s (local.get $ch) (i32.const 57)))
|
|
451
630
|
(i32.eq (local.get $ch) (i32.const 45)))
|
|
452
631
|
(then (return (call $__jp_num))))
|
|
453
632
|
(if (i32.eq (local.get $ch) (i32.const 116))
|
|
454
|
-
(then
|
|
633
|
+
(then ${ADV(4)} (return (f64.const 1))))
|
|
455
634
|
(if (i32.eq (local.get $ch) (i32.const 102))
|
|
456
|
-
(then
|
|
635
|
+
(then ${ADV(5)} (return (f64.const 0))))
|
|
457
636
|
(if (i32.eq (local.get $ch) (i32.const 110))
|
|
458
|
-
(then
|
|
459
|
-
|
|
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
|
+
}
|
|
460
798
|
|
|
461
799
|
// Entry point — copies input to a scratch buffer with 0xFF sentinel padding
|
|
462
800
|
// past the end so __jp_peek can omit its bounds check. Pad is 8 bytes so any
|
|
463
801
|
// overshoot from speculative peek/adv on malformed input still hits sentinel,
|
|
464
802
|
// not unallocated memory.
|
|
465
|
-
ctx.core.stdlib['__jp'] = `(func $__jp (param $str
|
|
803
|
+
ctx.core.stdlib['__jp'] = `(func $__jp (param $str i64) (result f64)
|
|
466
804
|
(local $len i32) (local $buf i32) (local $i i32)
|
|
467
805
|
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
468
806
|
(local.set $buf (call $__alloc (i32.add (local.get $len) (i32.const 8))))
|
|
469
807
|
;; Pre-fill 8 sentinel bytes at end (writes overlapping a 64-bit slot).
|
|
470
808
|
(i64.store (i32.add (local.get $buf) (local.get $len)) (i64.const -1))
|
|
471
|
-
;; SSO: byte-by-byte via __sso_char; STRING: bulk memcpy from string offset.
|
|
472
|
-
(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}))
|
|
473
811
|
(then
|
|
474
812
|
(local.set $i (i32.const 0))
|
|
475
813
|
(block $d (loop $l
|
|
@@ -489,7 +827,7 @@ export default (ctx) => {
|
|
|
489
827
|
|
|
490
828
|
ctx.core.emit['JSON.stringify'] = (x) => {
|
|
491
829
|
inc('__stringify')
|
|
492
|
-
return typed(['call', '$__stringify',
|
|
830
|
+
return typed(['call', '$__stringify', asI64(emit(x))], 'f64')
|
|
493
831
|
}
|
|
494
832
|
|
|
495
833
|
ctx.core.emit['JSON.parse'] = (x) => {
|
|
@@ -498,7 +836,16 @@ export default (ctx) => {
|
|
|
498
836
|
try { return emitJsonConstValue(JSON.parse(src)) }
|
|
499
837
|
catch { /* fall through to runtime parser for invalid JSON so runtime behavior stays unchanged */ }
|
|
500
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
|
+
}
|
|
501
848
|
inc('__jp')
|
|
502
|
-
return typed(['call', '$__jp',
|
|
849
|
+
return typed(['call', '$__jp', asI64(emit(x))], 'f64')
|
|
503
850
|
}
|
|
504
851
|
}
|