jz 0.5.1 → 0.6.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 +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +266 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +414 -186
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +586 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +589 -202
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/module/date.js
CHANGED
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { typed, asF64, toNumF64, allocPtr, temp } from '../src/ir.js'
|
|
13
|
-
import { emit } from '../src/
|
|
13
|
+
import { emit, deps } from '../src/bridge.js'
|
|
14
14
|
import { inc, PTR } from '../src/ctx.js'
|
|
15
|
-
import { VAL } from '../src/
|
|
15
|
+
import { VAL } from '../src/reps.js'
|
|
16
16
|
|
|
17
17
|
const MS_PER_DAY = 86400000
|
|
18
18
|
const MAX_TIME = 8640000000000000
|
|
19
19
|
|
|
20
20
|
export default (ctx) => {
|
|
21
|
-
|
|
21
|
+
deps({
|
|
22
22
|
__date_days_from_year: [],
|
|
23
23
|
__date_make_day: ['__date_days_from_year'],
|
|
24
24
|
__date_make_time: [],
|
|
@@ -686,6 +686,18 @@ export default (ctx) => {
|
|
|
686
686
|
ctx.core.emit['.getDay'] = ctx.core.emit['.getUTCDay']
|
|
687
687
|
ctx.core.emit[`.${VAL.DATE}:getDay`] = ctx.core.emit[`.${VAL.DATE}:getUTCDay`]
|
|
688
688
|
|
|
689
|
+
ctx.core.emit['.getHours'] = ctx.core.emit['.getUTCHours']
|
|
690
|
+
ctx.core.emit[`.${VAL.DATE}:getHours`] = ctx.core.emit[`.${VAL.DATE}:getUTCHours`]
|
|
691
|
+
|
|
692
|
+
ctx.core.emit['.getMinutes'] = ctx.core.emit['.getUTCMinutes']
|
|
693
|
+
ctx.core.emit[`.${VAL.DATE}:getMinutes`] = ctx.core.emit[`.${VAL.DATE}:getUTCMinutes`]
|
|
694
|
+
|
|
695
|
+
ctx.core.emit['.getSeconds'] = ctx.core.emit['.getUTCSeconds']
|
|
696
|
+
ctx.core.emit[`.${VAL.DATE}:getSeconds`] = ctx.core.emit[`.${VAL.DATE}:getUTCSeconds`]
|
|
697
|
+
|
|
698
|
+
ctx.core.emit['.getMilliseconds'] = ctx.core.emit['.getUTCMilliseconds']
|
|
699
|
+
ctx.core.emit[`.${VAL.DATE}:getMilliseconds`] = ctx.core.emit[`.${VAL.DATE}:getUTCMilliseconds`]
|
|
700
|
+
|
|
689
701
|
// ── UTC setter emit handlers ──────────────────────────────────────────────
|
|
690
702
|
|
|
691
703
|
const emitDatePtr = (dateExpr) =>
|
package/module/function.js
CHANGED
|
@@ -10,11 +10,41 @@
|
|
|
10
10
|
* @module fn
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { typed, asF64, asI32, mkPtrIR, temp, tempI32, MAX_CLOSURE_ARITY, UNDEF_NAN } from '../src/ir.js'
|
|
14
|
-
import { emit } from '../src/
|
|
15
|
-
import { isReassigned } from '../src/
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
13
|
+
import { typed, asF64, asI32, mkPtrIR, temp, tempI32, MAX_CLOSURE_ARITY, UNDEF_NAN, appendStaticSlots } from '../src/ir.js'
|
|
14
|
+
import { emit } from '../src/bridge.js'
|
|
15
|
+
import { isReassigned } from '../src/ast.js'
|
|
16
|
+
import { findFreeVars } from '../src/compile/analyze.js'
|
|
17
|
+
import { T } from '../src/ast.js'
|
|
18
|
+
import { lookupValType, repOf, VAL } from '../src/reps.js'
|
|
19
|
+
import { valTypeOf } from '../src/kind.js'
|
|
20
|
+
import { PTR, LAYOUT, inc, err, declGlobal } from '../src/ctx.js'
|
|
21
|
+
|
|
22
|
+
// A closure whose every return value is provably a plain number lets each caller
|
|
23
|
+
// skip the `__to_num` result coercion — the dominant per-sample cost in closure-heavy
|
|
24
|
+
// numeric kernels (floatbeats etc.). The check is structural at make time: arithmetic /
|
|
25
|
+
// bitwise / `|0` / math.* returns resolve via `valTypeOf`, as do chained numeric-closure
|
|
26
|
+
// calls (`g(x)=>…; f()=>g(…)` — `g`'s entry is already set when the outer `f` is made,
|
|
27
|
+
// since decls emit in order). An unprovable shape (polymorphic `a[i]`, a value-returning
|
|
28
|
+
// fall-through, a bare local that could collide with a parent binding) stays boxed.
|
|
29
|
+
const closureReturnExprs = (body) => {
|
|
30
|
+
if (!Array.isArray(body) || body[0] !== '{}') return [body] // expression-bodied arrow
|
|
31
|
+
const stmts = Array.isArray(body[1]) && body[1][0] === ';' ? body[1].slice(1) : [body[1]]
|
|
32
|
+
const last = stmts[stmts.length - 1]
|
|
33
|
+
if (!Array.isArray(last) || last[0] !== 'return') return null // may fall off the end with undefined
|
|
34
|
+
const rets = []
|
|
35
|
+
let ok = true
|
|
36
|
+
const walk = n => {
|
|
37
|
+
if (!ok || !Array.isArray(n) || n[0] === '=>') return // don't descend into nested closures
|
|
38
|
+
if (n[0] === 'return') { if (n.length < 2) ok = false; else rets.push(n[1]); return }
|
|
39
|
+
for (let i = 1; i < n.length; i++) walk(n[i])
|
|
40
|
+
}
|
|
41
|
+
for (const s of stmts) walk(s)
|
|
42
|
+
return ok ? rets : null
|
|
43
|
+
}
|
|
44
|
+
const returnsPlainNumber = (body) => {
|
|
45
|
+
const rets = closureReturnExprs(body)
|
|
46
|
+
return !!rets && rets.length > 0 && rets.every(e => valTypeOf(e) === VAL.NUMBER)
|
|
47
|
+
}
|
|
18
48
|
|
|
19
49
|
const intConstExpr = (node) => {
|
|
20
50
|
if (typeof node === 'number' && Number.isInteger(node)) return node
|
|
@@ -107,6 +137,11 @@ export default (ctx) => {
|
|
|
107
137
|
// call_indirect on the captured pointer). Gated on isReassigned over the inner body
|
|
108
138
|
// so a local rewrite of the captured name disables propagation.
|
|
109
139
|
const captureDirectClosures = new Map()
|
|
140
|
+
// Propagate the parent's `nullable` mark: a capture whose parent binding can
|
|
141
|
+
// hold null/undefined (e.g. `let x = null` later assigned a number) must keep
|
|
142
|
+
// that fact inside the body, or the body's own write facts (val = NUMBER)
|
|
143
|
+
// would let `x == null` fold to a constant false and skip the guard.
|
|
144
|
+
const captureNullables = new Set()
|
|
110
145
|
for (const name of envCaptures) {
|
|
111
146
|
const vt = lookupValType(name)
|
|
112
147
|
if (vt != null) captureValTypes.set(name, vt)
|
|
@@ -117,6 +152,7 @@ export default (ctx) => {
|
|
|
117
152
|
const bodyName = ctx.func.directClosures?.get(name)
|
|
118
153
|
if (bodyName && !isReassigned(body, name)) captureDirectClosures.set(name, bodyName)
|
|
119
154
|
if (repOf(name)?.intCertain === true) captureIntCertain.add(name)
|
|
155
|
+
if (repOf(name)?.nullable) captureNullables.add(name)
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
const schemaNames = ctx.schema.vars?.size ? new Set(ctx.schema.vars.keys()) : null
|
|
@@ -134,17 +170,23 @@ export default (ctx) => {
|
|
|
134
170
|
// All closures use uniform convention: (env: f64, args_array: f64) → f64
|
|
135
171
|
// The body unpacks individual params from the args array
|
|
136
172
|
const boxedCaptures = envCaptures.filter(c => ctx.func.boxed?.has(c))
|
|
173
|
+
// i32-narrowed cells travel with the capture: the closure body must access
|
|
174
|
+
// the shared cell at the same width the owner does (see funcFacts.cellTypes).
|
|
175
|
+
const cellI32Captures = boxedCaptures.filter(c => ctx.func.cellTypes?.has(c))
|
|
137
176
|
const bodyFn = { name: fnName, params, body, captures: envCaptures, arity: 1,
|
|
138
177
|
...(restParam && { rest: restParam }),
|
|
139
178
|
...(defaults && { defaults }),
|
|
140
179
|
...(boxedCaptures.length && { boxed: new Set(boxedCaptures) }),
|
|
180
|
+
...(cellI32Captures.length && { cellI32: new Set(cellI32Captures) }),
|
|
141
181
|
...(captureIntConsts.size && { intConsts: captureIntConsts }),
|
|
142
182
|
...(captureIntCertain.size && { intCertain: captureIntCertain }),
|
|
183
|
+
...(captureNullables.size && { nullables: captureNullables }),
|
|
143
184
|
...(captureValTypes.size && { valTypes: captureValTypes }),
|
|
144
185
|
...(captureSchemaVars.size && { schemaVars: captureSchemaVars }),
|
|
145
186
|
...(captureTypedElems.size && { typedElems: captureTypedElems }),
|
|
146
187
|
...(captureDirectClosures.size && { directClosures: captureDirectClosures }) }
|
|
147
188
|
ctx.closure.bodies.push(bodyFn)
|
|
189
|
+
if (returnsPlainNumber(body)) (ctx.closure.numericReturn ||= new Set()).add(fnName)
|
|
148
190
|
|
|
149
191
|
const tableIdx = addToTable(fnName)
|
|
150
192
|
|
|
@@ -160,6 +202,27 @@ export default (ctx) => {
|
|
|
160
202
|
return ir
|
|
161
203
|
}
|
|
162
204
|
|
|
205
|
+
if (body._nonEscaping && ctx.transform.optimize) {
|
|
206
|
+
// Allocate environmental slots in the static data segment
|
|
207
|
+
const staticOff = appendStaticSlots(new Array(envCaptures.length).fill('0x0000000000000000'))
|
|
208
|
+
const block = []
|
|
209
|
+
// Store captured values in env: boxed cells as raw i32 in low 4 bytes, others as f64.
|
|
210
|
+
// Avoids i32↔f64 roundtrip; body loads via i32.load/f64.load using the same branch.
|
|
211
|
+
for (let i = 0; i < envCaptures.length; i++) {
|
|
212
|
+
const addr = ['i32.const', staticOff + i * 8]
|
|
213
|
+
if (ctx.func.boxed?.has(envCaptures[i]))
|
|
214
|
+
block.push(['i32.store', addr, ['local.get', `$${ctx.func.boxed.get(envCaptures[i])}`]])
|
|
215
|
+
else
|
|
216
|
+
block.push(['f64.store', addr, asF64(emit(envCaptures[i]))])
|
|
217
|
+
}
|
|
218
|
+
block.push(mkPtrIR(PTR.CLOSURE, tableIdx, ['i32.const', staticOff]))
|
|
219
|
+
|
|
220
|
+
const ir = typed(['block', ['result', 'f64'], ...block], 'f64')
|
|
221
|
+
ir.closureBodyName = fnName
|
|
222
|
+
ir.closureFuncIdx = tableIdx
|
|
223
|
+
return ir
|
|
224
|
+
}
|
|
225
|
+
|
|
163
226
|
const t = tempI32('env')
|
|
164
227
|
|
|
165
228
|
const block = [
|
|
@@ -195,7 +258,10 @@ export default (ctx) => {
|
|
|
195
258
|
|
|
196
259
|
if (prebuiltArray) {
|
|
197
260
|
// Spread path: decode array into inline slots. Slots beyond array len padded with UNDEF.
|
|
198
|
-
//
|
|
261
|
+
// The full args array offset is published in $__closure_spill so a rest-param
|
|
262
|
+
// callee can recover elements beyond width W (the W inline slots hold args[0..W-1];
|
|
263
|
+
// the rest reads args[W..argc-1] straight from the spill array). Unbounded arity.
|
|
264
|
+
declGlobal('__closure_spill', 'i32')
|
|
199
265
|
const arrT = tempI32('sa')
|
|
200
266
|
const lenL = tempI32('sl')
|
|
201
267
|
const setup = [
|
|
@@ -220,6 +286,7 @@ export default (ctx) => {
|
|
|
220
286
|
return typed(['block', ['result', 'f64'],
|
|
221
287
|
...setup,
|
|
222
288
|
['local.set', `$${t}`, asF64(closureExpr)],
|
|
289
|
+
['global.set', '$__closure_spill', ['local.get', `$${arrT}`]],
|
|
223
290
|
['call_indirect', ['type', '$ftN'],
|
|
224
291
|
['local.get', `$${t}`],
|
|
225
292
|
['local.get', `$${lenL}`],
|
package/module/index.js
CHANGED
|
@@ -13,4 +13,5 @@ import json from './json.js'
|
|
|
13
13
|
import regex from './regex.js'
|
|
14
14
|
import timer from './timer.js'
|
|
15
15
|
import date from './date.js'
|
|
16
|
-
|
|
16
|
+
import simd from './simd.js'
|
|
17
|
+
export { math, core, array, object, string, number, fn, typedarray, collection, symbol, console, json, regex, timer, date, simd }
|
package/module/json.js
CHANGED
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
* @module json
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { typed, asF64, asI64, temp, tempI32, nullExpr, undefExpr, allocPtr, slotAddr, mkPtrIR, extractF64Bits, appendStaticSlots, NULL_WAT, UNDEF_NAN, UNDEF_WAT } from '../src/ir.js'
|
|
12
|
-
import { emit,
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { typed, asF64, asI64, toStrI64, temp, tempI32, nullExpr, undefExpr, allocPtr, slotAddr, mkPtrIR, extractF64Bits, appendStaticSlots, NULL_WAT, UNDEF_NAN, UNDEF_WAT, FALSE_NAN, TRUE_NAN, FALSE_IR, TRUE_IR } from '../src/ir.js'
|
|
12
|
+
import { emit, bool, deps } from '../src/bridge.js'
|
|
13
|
+
import { valTypeOf } from '../src/kind.js'
|
|
14
|
+
import { T } from '../src/ast.js'
|
|
15
|
+
import { VAL } from '../src/reps.js'
|
|
16
|
+
import { err, inc, PTR, LAYOUT, declGlobal } from '../src/ctx.js'
|
|
15
17
|
import { strHashLiteral } from './collection.js'
|
|
16
18
|
|
|
17
19
|
function jsonConstString(ctx, expr) {
|
|
@@ -58,6 +60,60 @@ function literalValue(node) {
|
|
|
58
60
|
return NOT_LIT
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
// A BigInt has no JSON representation: `JSON.stringify` of a bigint — anywhere in
|
|
64
|
+
// the value graph — is a TypeError (ES JSON.stringify → SerializeJSONProperty).
|
|
65
|
+
// Detects it in a resolved literal value so the caller can emit the throw rather
|
|
66
|
+
// than handing the bigint to the host `JSON.stringify` (which crashes the fold).
|
|
67
|
+
const literalHasBigInt = (v) =>
|
|
68
|
+
typeof v === 'bigint' ||
|
|
69
|
+
(Array.isArray(v) ? v.some(literalHasBigInt)
|
|
70
|
+
: !!v && typeof v === 'object' && Object.values(v).some(literalHasBigInt))
|
|
71
|
+
|
|
72
|
+
// Fold a literal AST node directly to its compact JSON string (no replacer /
|
|
73
|
+
// no space). Unlike the value-level `literalValue` + `JSON.stringify` path, this
|
|
74
|
+
// renders the self-host kernel boolean marker `['bool', 1|0]` as true/false:
|
|
75
|
+
// the value path can't, because jz carries a boolean as a 0/1 *number*, so the
|
|
76
|
+
// kernel's compile-time `JSON.stringify` of the folded value emits "1"/"0", not
|
|
77
|
+
// "true"/"false" (native folds via real JS booleans and is unaffected). Scalars
|
|
78
|
+
// delegate to the value path for exact number/string formatting; anything not
|
|
79
|
+
// cleanly renderable (undefined, holes, function/symbol) returns NOT_LIT so the
|
|
80
|
+
// caller falls back. Returns a JSON string or NOT_LIT.
|
|
81
|
+
function foldJsonStr(node) {
|
|
82
|
+
if (!Array.isArray(node)) return NOT_LIT
|
|
83
|
+
const op = node[0]
|
|
84
|
+
// `['bool', carrier]` — prepare wraps the 0/1 carrier as a number-literal node
|
|
85
|
+
// `[, 0|1]` on the kernel leg (bare number in native), so unwrap before testing.
|
|
86
|
+
if (op === 'bool') {
|
|
87
|
+
const c = Array.isArray(node[1]) ? node[1][1] : node[1]
|
|
88
|
+
return c ? 'true' : 'false'
|
|
89
|
+
}
|
|
90
|
+
if (op === '[') {
|
|
91
|
+
const parts = []
|
|
92
|
+
for (let i = 1; i < node.length; i++) {
|
|
93
|
+
const s = foldJsonStr(node[i])
|
|
94
|
+
if (s === NOT_LIT) return NOT_LIT
|
|
95
|
+
parts.push(s)
|
|
96
|
+
}
|
|
97
|
+
return '[' + parts.join(',') + ']'
|
|
98
|
+
}
|
|
99
|
+
if (op === '{}') {
|
|
100
|
+
const parts = []
|
|
101
|
+
for (let i = 1; i < node.length; i++) {
|
|
102
|
+
const e = node[i]
|
|
103
|
+
if (!Array.isArray(e) || e[0] !== ':' || (typeof e[1] !== 'string' && typeof e[1] !== 'number')) return NOT_LIT
|
|
104
|
+
const s = foldJsonStr(e[2])
|
|
105
|
+
if (s === NOT_LIT) return NOT_LIT
|
|
106
|
+
parts.push(JSON.stringify(String(e[1])) + ':' + s)
|
|
107
|
+
}
|
|
108
|
+
return '{' + parts.join(',') + '}'
|
|
109
|
+
}
|
|
110
|
+
// Scalar (number / string / null / u-): exact formatting via the value path.
|
|
111
|
+
const v = literalValue(node)
|
|
112
|
+
if (v === NOT_LIT || typeof v === 'bigint') return NOT_LIT // bigint ⇒ TypeError, not a literal string
|
|
113
|
+
const s = JSON.stringify(v)
|
|
114
|
+
return s === undefined ? NOT_LIT : s // undefined/function/symbol — defer
|
|
115
|
+
}
|
|
116
|
+
|
|
61
117
|
function jsonShapeString(ctx, expr) {
|
|
62
118
|
if (typeof expr === 'string') return ctx.scope.shapeStrs?.get(expr) ?? null
|
|
63
119
|
return null
|
|
@@ -78,15 +134,15 @@ function hashCapFor(n) {
|
|
|
78
134
|
}
|
|
79
135
|
|
|
80
136
|
export default (ctx) => {
|
|
81
|
-
|
|
137
|
+
deps({
|
|
82
138
|
__stringify: ['__json_val', '__json_setgap', '__json_omit', '__jput', '__jput_str', '__jput_num', '__mkstr'],
|
|
83
139
|
__json_setgap: ['__alloc', '__ptr_type', '__str_byteLen', '__char_at'],
|
|
84
140
|
__json_omit: ['__ptr_type'],
|
|
85
141
|
__json_enter: ['__alloc'],
|
|
86
142
|
__jindent: ['__jput'],
|
|
87
143
|
__json_val: ['__ptr_type', '__len', '__ptr_offset', '__jput', '__jindent', '__jput_num', '__jput_str', '__json_enter', '__json_leave', '__json_hash', '__json_obj'],
|
|
88
|
-
__json_hash: ['__ptr_offset', '__jput', '__jindent', '__jput_str', '__json_omit', '__json_enter', '__json_leave', '__json_val'],
|
|
89
|
-
__json_obj: ['__ptr_offset', '__ptr_aux', '__len', '__jput', '__jindent', '__jput_str', '__json_omit', '__json_enter', '__json_leave', '__json_val'],
|
|
144
|
+
__json_hash: ['__ptr_offset', '__jput', '__jindent', '__jput_str', '__json_omit', '__json_enter', '__json_leave', '__json_val', '__coll_order'],
|
|
145
|
+
__json_obj: ['__ptr_offset', '__ptr_aux', '__len', '__jput', '__jindent', '__jput_str', '__json_omit', '__json_enter', '__json_leave', '__json_val', '__coll_order'],
|
|
90
146
|
__jput_num: ['__ftoa'],
|
|
91
147
|
__jput_str: ['__char_at', '__str_byteLen'],
|
|
92
148
|
__jp: ['__jp_val', '__jp_str', '__jp_num', '__jp_arr', '__jp_obj', '__sso_char', '__ptr_aux', '__ptr_type', '__ptr_offset', '__str_byteLen'],
|
|
@@ -116,7 +172,7 @@ export default (ctx) => {
|
|
|
116
172
|
if (v == null) return nullExpr()
|
|
117
173
|
if (typeof v === 'number') return asF64(emit(v))
|
|
118
174
|
if (typeof v === 'string') return asF64(emit(['str', v]))
|
|
119
|
-
if (typeof v === 'boolean') return
|
|
175
|
+
if (typeof v === 'boolean') return typed((v ? TRUE_IR : FALSE_IR).slice(), 'f64')
|
|
120
176
|
if (Array.isArray(v)) {
|
|
121
177
|
const a = allocPtr({ type: PTR.ARRAY, len: v.length, cap: Math.max(v.length, 4), tag: 'jarr' })
|
|
122
178
|
const body = [a.init]
|
|
@@ -150,23 +206,23 @@ export default (ctx) => {
|
|
|
150
206
|
// Scratch buffer approach: __json_buf is a growable output buffer.
|
|
151
207
|
// Functions append bytes to it, __json_pos tracks current write position.
|
|
152
208
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
209
|
+
declGlobal('__jbuf', 'i32')
|
|
210
|
+
declGlobal('__jpos', 'i32')
|
|
211
|
+
declGlobal('__jcap', 'i32')
|
|
212
|
+
declGlobal('__schema_tbl', 'i32')
|
|
157
213
|
// Pretty-print state for the `space` argument. $__jgap points at the gap
|
|
158
214
|
// string bytes ($__jgaplen of them); $__jdepth is the live nesting depth.
|
|
159
215
|
// $__jgaplen == 0 ⇒ compact mode — every indent emission below is gated on
|
|
160
216
|
// it, so the no-space path stays byte-identical to the unindented output.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
217
|
+
declGlobal('__jgap', 'i32')
|
|
218
|
+
declGlobal('__jgaplen', 'i32')
|
|
219
|
+
declGlobal('__jdepth', 'i32')
|
|
164
220
|
// Cycle-detection stack: the i64 values of the containers currently open on
|
|
165
221
|
// the recursion path. JSON.stringify of a structure that points back at an
|
|
166
222
|
// ancestor must throw a TypeError. $__jstack is a lazily-allocated buffer of
|
|
167
223
|
// up to 256 entries; $__jsp is the live depth.
|
|
168
|
-
|
|
169
|
-
|
|
224
|
+
declGlobal('__jstack', 'i32')
|
|
225
|
+
declGlobal('__jsp', 'i32')
|
|
170
226
|
|
|
171
227
|
// __jput(byte: i32) — append one byte to output buffer
|
|
172
228
|
ctx.core.stdlib['__jput'] = `(func $__jput (param $b i32)
|
|
@@ -322,6 +378,16 @@ export default (ctx) => {
|
|
|
322
378
|
(call $__jput_num (local.get $f)) (return)))
|
|
323
379
|
;; NaN-boxed pointer
|
|
324
380
|
(local.set $type (call $__ptr_type (local.get $val)))
|
|
381
|
+
;; Boolean atoms: FALSE_NAN / TRUE_NAN → "false" / "true"
|
|
382
|
+
(if (i64.eq (local.get $val) (i64.const ${FALSE_NAN}))
|
|
383
|
+
(then
|
|
384
|
+
(call $__jput (i32.const 102)) (call $__jput (i32.const 97))
|
|
385
|
+
(call $__jput (i32.const 108)) (call $__jput (i32.const 115))
|
|
386
|
+
(call $__jput (i32.const 101)) (return)))
|
|
387
|
+
(if (i64.eq (local.get $val) (i64.const ${TRUE_NAN}))
|
|
388
|
+
(then
|
|
389
|
+
(call $__jput (i32.const 116)) (call $__jput (i32.const 114))
|
|
390
|
+
(call $__jput (i32.const 117)) (call $__jput (i32.const 101)) (return)))
|
|
325
391
|
;; Plain NaN (type=0) → null
|
|
326
392
|
(if (i32.eqz (local.get $type))
|
|
327
393
|
(then
|
|
@@ -369,34 +435,35 @@ export default (ctx) => {
|
|
|
369
435
|
(call $__jput (i32.const 110)) (call $__jput (i32.const 117))
|
|
370
436
|
(call $__jput (i32.const 108)) (call $__jput (i32.const 108)))`
|
|
371
437
|
|
|
372
|
-
// __json_hash(val: i64) — stringify HASH/MAP:
|
|
373
|
-
// Slot layout: 24 bytes each — [hash:f64][key:f64][val:f64].
|
|
438
|
+
// __json_hash(val: i64) — stringify HASH/MAP: emit {"key":val,...} in insertion
|
|
439
|
+
// order. Slot layout: 24 bytes each — [hash:f64][key:f64][val:f64]. __coll_order
|
|
440
|
+
// returns the n live slot offsets sorted by packed seq, matching the JS spec.
|
|
374
441
|
ctx.core.stdlib['__json_hash'] = `(func $__json_hash (param $val i64)
|
|
375
|
-
(local $off i32) (local $cap i32) (local $i i32) (local $slot i32) (local $first i32) (local $pv i64)
|
|
442
|
+
(local $off i32) (local $cap i32) (local $n i32) (local $i i32) (local $slot i32) (local $ord i32) (local $first i32) (local $pv i64)
|
|
376
443
|
(local.set $off (call $__ptr_offset (local.get $val)))
|
|
377
444
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
445
|
+
(local.set $n (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
446
|
+
(local.set $ord (call $__coll_order (local.get $off) (local.get $cap) (i32.const 24)))
|
|
378
447
|
(local.set $first (i32.const 1))
|
|
379
448
|
(call $__json_enter (local.get $val))
|
|
380
449
|
(call $__jput (i32.const 123))
|
|
381
450
|
(global.set $__jdepth (i32.add (global.get $__jdepth) (i32.const 1)))
|
|
382
451
|
(block $d (loop $l
|
|
383
|
-
(br_if $d (i32.ge_s (local.get $i) (local.get $
|
|
384
|
-
(local.set $slot (i32.add (local.get $
|
|
385
|
-
(
|
|
452
|
+
(br_if $d (i32.ge_s (local.get $i) (local.get $n)))
|
|
453
|
+
(local.set $slot (i32.load (i32.add (local.get $ord) (i32.shl (local.get $i) (i32.const 2)))))
|
|
454
|
+
(local.set $pv (i64.load (i32.add (local.get $slot) (i32.const 16))))
|
|
455
|
+
(if (i32.eqz (call $__json_omit (local.get $pv)))
|
|
386
456
|
(then
|
|
387
|
-
(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
(call $__jput (i32.const 58))
|
|
398
|
-
(if (global.get $__jgaplen) (then (call $__jput (i32.const 32))))
|
|
399
|
-
(call $__json_val (local.get $pv))))))
|
|
457
|
+
(if (i32.eqz (local.get $first))
|
|
458
|
+
(then (call $__jput (i32.const 44))))
|
|
459
|
+
(local.set $first (i32.const 0))
|
|
460
|
+
(call $__jindent)
|
|
461
|
+
(call $__jput (i32.const 34))
|
|
462
|
+
(call $__jput_str (i64.load (i32.add (local.get $slot) (i32.const 8))))
|
|
463
|
+
(call $__jput (i32.const 34))
|
|
464
|
+
(call $__jput (i32.const 58))
|
|
465
|
+
(if (global.get $__jgaplen) (then (call $__jput (i32.const 32))))
|
|
466
|
+
(call $__json_val (local.get $pv))))
|
|
400
467
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
401
468
|
(br $l)))
|
|
402
469
|
(global.set $__jdepth (i32.sub (global.get $__jdepth) (i32.const 1)))
|
|
@@ -411,14 +478,21 @@ export default (ctx) => {
|
|
|
411
478
|
ctx.core.stdlib['__json_obj'] = `(func $__json_obj (param $val i64)
|
|
412
479
|
(local $off i32) (local $sid i32) (local $keys i32) (local $nkeys i32)
|
|
413
480
|
(local $i i32) (local $koff i32) (local $first i32) (local $pv i64)
|
|
481
|
+
(local $props i64) (local $poff i32) (local $pcap i32) (local $dn i32) (local $slot i32) (local $ord i32)
|
|
482
|
+
(local $j i32) (local $skip i32)
|
|
414
483
|
(local.set $off (call $__ptr_offset (local.get $val)))
|
|
415
484
|
(local.set $sid (call $__ptr_aux (local.get $val)))
|
|
416
|
-
;;
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
(local.set $nkeys (
|
|
420
|
-
|
|
421
|
-
(
|
|
485
|
+
;; Schema keys: schema_tbl + sid*8. Dyn-only programs (empty {} + computed
|
|
486
|
+
;; o[k]=v) never allocate __schema_tbl, leaving it 0 — guard the read so the
|
|
487
|
+
;; dyn-prop walk below still runs.
|
|
488
|
+
(local.set $nkeys (i32.const 0))
|
|
489
|
+
(local.set $koff (i32.const 0))
|
|
490
|
+
(if (i32.ne (global.get $__schema_tbl) (i32.const 0))
|
|
491
|
+
(then
|
|
492
|
+
(local.set $koff (call $__ptr_offset
|
|
493
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))
|
|
494
|
+
(local.set $nkeys (call $__len
|
|
495
|
+
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))))
|
|
422
496
|
(local.set $first (i32.const 1))
|
|
423
497
|
(call $__json_enter (local.get $val))
|
|
424
498
|
(call $__jput (i32.const 123))
|
|
@@ -439,6 +513,54 @@ export default (ctx) => {
|
|
|
439
513
|
(call $__json_val (local.get $pv))))
|
|
440
514
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
441
515
|
(br $l)))
|
|
516
|
+
;; Dynamic (off-schema) properties: heap OBJECTs carry a HASH propsPtr at
|
|
517
|
+
;; off-16 (set by __dyn_set). Walk it in insertion order via __coll_order —
|
|
518
|
+
;; schema-only enumeration would drop computed props (e.g. {} then o.a=1).
|
|
519
|
+
;; Skip propsPtr entries whose key already appears in the schema: object
|
|
520
|
+
;; literals with needsDynShadow(target)=true shadow-write each schema key
|
|
521
|
+
;; into propsPtr so dyn-key reads can resolve via hash lookup. That mirror
|
|
522
|
+
;; is a runtime acceleration, not an enumeration entity — without dedup,
|
|
523
|
+
;; JSON output emits each schema key twice. Schema-key interns equal the
|
|
524
|
+
;; keys we shadow-write (same compile-time string literal), so i64.eq matches.
|
|
525
|
+
(if (i32.ge_u (local.get $off) (global.get $__heap_start))
|
|
526
|
+
(then
|
|
527
|
+
(local.set $props (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
528
|
+
(if (i32.eq
|
|
529
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $props) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
530
|
+
(i32.const ${PTR.HASH}))
|
|
531
|
+
(then
|
|
532
|
+
(local.set $poff (call $__ptr_offset (local.get $props)))
|
|
533
|
+
(local.set $pcap (i32.load (i32.sub (local.get $poff) (i32.const 4))))
|
|
534
|
+
(local.set $dn (i32.load (i32.sub (local.get $poff) (i32.const 8))))
|
|
535
|
+
(local.set $ord (call $__coll_order (local.get $poff) (local.get $pcap) (i32.const 24)))
|
|
536
|
+
(local.set $i (i32.const 0))
|
|
537
|
+
(block $dd (loop $dl
|
|
538
|
+
(br_if $dd (i32.ge_s (local.get $i) (local.get $dn)))
|
|
539
|
+
(local.set $slot (i32.load (i32.add (local.get $ord) (i32.shl (local.get $i) (i32.const 2)))))
|
|
540
|
+
(local.set $pv (i64.load (i32.add (local.get $slot) (i32.const 16))))
|
|
541
|
+
(local.set $skip (i32.const 0))
|
|
542
|
+
(local.set $j (i32.const 0))
|
|
543
|
+
(block $sd (loop $sl
|
|
544
|
+
(br_if $sd (i32.ge_s (local.get $j) (local.get $nkeys)))
|
|
545
|
+
(if (i64.eq
|
|
546
|
+
(i64.load (i32.add (local.get $slot) (i32.const 8)))
|
|
547
|
+
(i64.load (i32.add (local.get $koff) (i32.shl (local.get $j) (i32.const 3)))))
|
|
548
|
+
(then (local.set $skip (i32.const 1)) (br $sd)))
|
|
549
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
550
|
+
(br $sl)))
|
|
551
|
+
(if (i32.and (i32.eqz (local.get $skip)) (i32.eqz (call $__json_omit (local.get $pv))))
|
|
552
|
+
(then
|
|
553
|
+
(if (i32.eqz (local.get $first)) (then (call $__jput (i32.const 44))))
|
|
554
|
+
(local.set $first (i32.const 0))
|
|
555
|
+
(call $__jindent)
|
|
556
|
+
(call $__jput (i32.const 34))
|
|
557
|
+
(call $__jput_str (i64.load (i32.add (local.get $slot) (i32.const 8))))
|
|
558
|
+
(call $__jput (i32.const 34))
|
|
559
|
+
(call $__jput (i32.const 58))
|
|
560
|
+
(if (global.get $__jgaplen) (then (call $__jput (i32.const 32))))
|
|
561
|
+
(call $__json_val (local.get $pv))))
|
|
562
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
563
|
+
(br $dl)))))))
|
|
442
564
|
(global.set $__jdepth (i32.sub (global.get $__jdepth) (i32.const 1)))
|
|
443
565
|
(if (i32.eqz (local.get $first)) (then (call $__jindent)))
|
|
444
566
|
(call $__jput (i32.const 125))
|
|
@@ -460,19 +582,19 @@ export default (ctx) => {
|
|
|
460
582
|
|
|
461
583
|
// === JSON.parse ===
|
|
462
584
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
585
|
+
declGlobal('__jpstr', 'i32') // input string offset
|
|
586
|
+
declGlobal('__jplen', 'i32') // input length
|
|
587
|
+
declGlobal('__jppos', 'i32') // current parse position
|
|
466
588
|
// Side-channel hash for the most-recently-parsed string. __jp_str folds an
|
|
467
589
|
// FNV-1a pass into its scan loop; __jp_obj forwards it to __hash_set_local_h
|
|
468
590
|
// and skips the redundant __str_hash call inside the generic insert. 0 is a
|
|
469
591
|
// sentinel meaning "string had escapes — recompute via __str_hash".
|
|
470
|
-
|
|
592
|
+
declGlobal('__jp_keyh', 'i32')
|
|
471
593
|
// Sticky syntax-error flag. Set by any parser sub-routine on malformed input;
|
|
472
594
|
// checked once by __jp after the top-level value, which throws if it is set.
|
|
473
595
|
// Sticky (never cleared mid-parse) so recursive descent need not thread an
|
|
474
596
|
// error result through every call — __jp resets it per invocation.
|
|
475
|
-
|
|
597
|
+
declGlobal('__jp_err', 'i32')
|
|
476
598
|
// Runtime schema infrastructure. __schema_next points at the first free slot
|
|
477
599
|
// in $__schema_tbl reserved for runtime registration; compile.js initializes
|
|
478
600
|
// it to ctx.schema.list.length when __jp_obj is included. The schema cache
|
|
@@ -482,8 +604,8 @@ export default (ctx) => {
|
|
|
482
604
|
// every __hash_set_local). Cache slot layout: i32 hash, i32 sid (8 bytes).
|
|
483
605
|
// Hash 0 = empty slot; we bump <=1 to 2 like __str_hash to avoid sentinel
|
|
484
606
|
// collision with valid hashes.
|
|
485
|
-
|
|
486
|
-
|
|
607
|
+
declGlobal('__schema_next', 'i32')
|
|
608
|
+
declGlobal('__schema_cache', 'i32')
|
|
487
609
|
|
|
488
610
|
// Sentinel-driven peek: __jp copies input to a scratch buffer with 0xFF bytes
|
|
489
611
|
// appended past the end. i32.load8_s sign-extends, so the sentinel reads as -1
|
|
@@ -905,9 +1027,17 @@ export default (ctx) => {
|
|
|
905
1027
|
// word[0]; AND-check word[1..] against the bytes after the parse position.
|
|
906
1028
|
// Past-end reads land in the 0xFF sentinel pad, which matches nothing.
|
|
907
1029
|
const litMatch = (word) => {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1030
|
+
// Index with charCodeAt rather than `[...word]` spread: jz (self-host) does not
|
|
1031
|
+
// spread a string into a char array, so the spread form yields an empty test set
|
|
1032
|
+
// and the literal match folds to a constant-false `(if 0 …)` — breaking
|
|
1033
|
+
// JSON.parse of true/false/null once jz compiles its own parser. The first char
|
|
1034
|
+
// is already matched by the caller's `ch` switch, so compare offsets 1..len-1.
|
|
1035
|
+
let acc = ''
|
|
1036
|
+
for (let i = 1; i < word.length; i++) {
|
|
1037
|
+
const t = `(i32.eq (i32.load8_u (i32.add (global.get $__jpstr) (i32.add (global.get $__jppos) (i32.const ${i})))) (i32.const ${word.charCodeAt(i)}))`
|
|
1038
|
+
acc = acc ? `(i32.and ${acc} ${t})` : t
|
|
1039
|
+
}
|
|
1040
|
+
return acc
|
|
911
1041
|
}
|
|
912
1042
|
const litCase = (ch, word, valIR, adv) => `(if (i32.eq (local.get $ch) (i32.const ${ch}))
|
|
913
1043
|
(then (if ${litMatch(word)}
|
|
@@ -928,8 +1058,8 @@ export default (ctx) => {
|
|
|
928
1058
|
(if (i32.or (i32.and (i32.ge_s (local.get $ch) (i32.const 48)) (i32.le_s (local.get $ch) (i32.const 57)))
|
|
929
1059
|
(i32.eq (local.get $ch) (i32.const 45)))
|
|
930
1060
|
(then (return (call $__jp_num))))
|
|
931
|
-
${litCase(116, 'true',
|
|
932
|
-
${litCase(102, 'false',
|
|
1061
|
+
${litCase(116, 'true', `(f64.const nan:${TRUE_NAN})`, 4)}
|
|
1062
|
+
${litCase(102, 'false', `(f64.const nan:${FALSE_NAN})`, 5)}
|
|
933
1063
|
${litCase(110, 'null', NULL_WAT, 4)}
|
|
934
1064
|
;; No production matched — malformed JSON value.
|
|
935
1065
|
(global.set $__jp_err (i32.const 1))
|
|
@@ -971,18 +1101,24 @@ export default (ctx) => {
|
|
|
971
1101
|
const expect = (byte) => `(if (i32.ne ${PEEK} (i32.const ${byte})) (then ${fail}))
|
|
972
1102
|
${ADV(1)}`
|
|
973
1103
|
const expectText = (text) => [...text].map(c => expect(c.charCodeAt(0))).join('\n ')
|
|
1104
|
+
// Forward-declared with `let` (assigned below) so `parse` captures a boxed
|
|
1105
|
+
// cell, not a not-yet-initialized `const` binding — the self-host kernel
|
|
1106
|
+
// miscompiles the latter capture in this deeply-nested mutually-recursive
|
|
1107
|
+
// context (parse ends up calling a garbage `parseObject`, emitting a corrupt
|
|
1108
|
+
// node into the shaped parser).
|
|
1109
|
+
let parseObject, parseArray
|
|
974
1110
|
const parse = (v, out) => {
|
|
975
1111
|
if (v == null) return `${expectText('null')}
|
|
976
1112
|
(local.set $${out} ${NULL_WAT})`
|
|
977
1113
|
if (typeof v === 'boolean') return `${expectText(v ? 'true' : 'false')}
|
|
978
|
-
(local.set $${out} (f64.const
|
|
1114
|
+
(local.set $${out} (f64.const nan:${v ? TRUE_NAN : FALSE_NAN}))`
|
|
979
1115
|
if (typeof v === 'number') return `(local.set $${out} (call $__jp_num))`
|
|
980
1116
|
if (typeof v === 'string') return `${expect(34)}
|
|
981
1117
|
(local.set $${out} (call $__jp_str))`
|
|
982
1118
|
if (Array.isArray(v)) return parseArray(v[0], out)
|
|
983
1119
|
return parseObject(v, out)
|
|
984
1120
|
}
|
|
985
|
-
|
|
1121
|
+
parseObject = (v, out) => {
|
|
986
1122
|
const keys = Object.keys(v)
|
|
987
1123
|
const obj = local('obj', 'i32')
|
|
988
1124
|
const val = local('val', 'f64')
|
|
@@ -1010,7 +1146,7 @@ export default (ctx) => {
|
|
|
1010
1146
|
return `${body}
|
|
1011
1147
|
(local.set $${out} (call $__mkptr (i32.const ${PTR.OBJECT}) (i32.const ${sid}) (local.get $${obj})))`
|
|
1012
1148
|
}
|
|
1013
|
-
|
|
1149
|
+
parseArray = (elem, out) => {
|
|
1014
1150
|
const ptr = local('arr', 'i32')
|
|
1015
1151
|
const len = local('alen', 'i32')
|
|
1016
1152
|
const cap = local('acap', 'i32')
|
|
@@ -1139,13 +1275,28 @@ ${localDecls}
|
|
|
1139
1275
|
// the runtime call entirely. The runtime `__stringify` path (which ignores a
|
|
1140
1276
|
// replacer) handles every non-constant case unchanged.
|
|
1141
1277
|
ctx.core.emit['JSON.stringify'] = (x, replacer, space) => {
|
|
1278
|
+
// An explicit `null`/`undefined` replacer is spec-equivalent to none; only a
|
|
1279
|
+
// real array/function replacer changes the result. (foldStringify normalizes
|
|
1280
|
+
// the same way via literalValue.) Used by both peepholes below.
|
|
1281
|
+
const noReplacer = replacer == null || literalValue(replacer) == null
|
|
1282
|
+
// BigInt has no JSON form — spec throws a TypeError. Emit the throw for the
|
|
1283
|
+
// statically-known cases (a top-level bigint expr, or a literal tree holding
|
|
1284
|
+
// one); the host fold can't render a bigint either. A function/array replacer
|
|
1285
|
+
// may rewrite the value, so defer to the fold/runtime path when one is present.
|
|
1286
|
+
if (noReplacer) {
|
|
1287
|
+
const lv = literalValue(x)
|
|
1288
|
+
if (valTypeOf(x) === VAL.BIGINT || (lv !== NOT_LIT && literalHasBigInt(lv))) {
|
|
1289
|
+
ctx.runtime.throws = true
|
|
1290
|
+
return typed(['block', ['result', 'f64'], ['throw', '$__jz_err', ['f64.const', 0]]], 'f64')
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1142
1293
|
const folded = foldStringify(x, replacer, space)
|
|
1143
1294
|
if (folded !== undefined) return folded
|
|
1144
1295
|
// Scalar boolean: the working-rep is the 0/1 number carrier, so the runtime
|
|
1145
1296
|
// tag-walker would emit "0"/"1". A boolean's JSON is the bare word
|
|
1146
|
-
// true/false — exactly
|
|
1147
|
-
//
|
|
1148
|
-
if (
|
|
1297
|
+
// true/false — exactly bool. Guard on no replacer: a replacer function may
|
|
1298
|
+
// rewrite the top-level value, in which case fall to runtime.
|
|
1299
|
+
if (noReplacer && valTypeOf(x) === VAL.BOOL) return bool(x)
|
|
1149
1300
|
inc('__stringify')
|
|
1150
1301
|
const spaceIR = asI64(space == null ? undefExpr() : emit(space))
|
|
1151
1302
|
return typed(['call', '$__stringify', asI64(emit(x)), spaceIR], 'f64')
|
|
@@ -1153,6 +1304,12 @@ ${localDecls}
|
|
|
1153
1304
|
|
|
1154
1305
|
// Returns folded IR, or `undefined` when any argument is non-constant.
|
|
1155
1306
|
function foldStringify(x, replacer, space) {
|
|
1307
|
+
// No replacer / no space: try the bool-aware AST string fold first so a
|
|
1308
|
+
// literal boolean renders as true/false on the self-host leg (see foldJsonStr).
|
|
1309
|
+
if (replacer == null && space == null) {
|
|
1310
|
+
const s = foldJsonStr(x)
|
|
1311
|
+
if (s !== NOT_LIT) return asF64(emit(['str', s]))
|
|
1312
|
+
}
|
|
1156
1313
|
const val = literalValue(x)
|
|
1157
1314
|
if (val === NOT_LIT) return undefined
|
|
1158
1315
|
|
|
@@ -1210,6 +1367,14 @@ ${localDecls}
|
|
|
1210
1367
|
} catch { /* fall through to generic runtime parser */ }
|
|
1211
1368
|
}
|
|
1212
1369
|
inc('__jp')
|
|
1213
|
-
|
|
1370
|
+
const value = temp('jp_arg')
|
|
1371
|
+
const input = valTypeOf(x) === VAL.BOOL
|
|
1372
|
+
? asI64(bool(x))
|
|
1373
|
+
: valTypeOf(x) === VAL.STRING
|
|
1374
|
+
? ['i64.reinterpret_f64', ['local.get', `$${value}`]]
|
|
1375
|
+
: toStrI64(null, typed(['local.get', `$${value}`], 'f64'))
|
|
1376
|
+
return typed(['block', ['result', 'f64'],
|
|
1377
|
+
['local.set', `$${value}`, asF64(emit(x))],
|
|
1378
|
+
['call', '$__jp', input]], 'f64')
|
|
1214
1379
|
}
|
|
1215
1380
|
}
|