jz 0.5.0 → 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 +281 -142
- 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 +461 -185
- 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 +591 -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} +600 -205
- 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 -2997
- package/src/jzify.js +0 -1553
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/src/reps.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValueRep storage + VAL lattice lookups (ctx-backed, cycle-free).
|
|
3
|
+
*
|
|
4
|
+
* Thin accessors shared by ir, emit, stdlib, and analyze. Keeps heavy AST
|
|
5
|
+
* walkers in analyze.js without pulling them into ir.js.
|
|
6
|
+
*
|
|
7
|
+
* ## Lookup priority (lookupValType / lookupNotString)
|
|
8
|
+
*
|
|
9
|
+
* A single binding can carry several pieces of type knowledge, set at different
|
|
10
|
+
* lifecycle phases. Accessors resolve them in this fixed order — first hit wins:
|
|
11
|
+
*
|
|
12
|
+
* 1. `ctx.func.refinements` flow-sensitive (typeof/instanceof guard)
|
|
13
|
+
* 2. `ctx.func.localValTypesOverlay` call-site / loop-iter overlay (transient)
|
|
14
|
+
* 3. `ctx.func.localReps` per-function plan/analyze fact (durable)
|
|
15
|
+
* 4. `ctx.scope.globalValTypes` module-level binding (durable)
|
|
16
|
+
*
|
|
17
|
+
* Writes go through `updateRep` (#3 mutator) / `updateGlobalRep` (#4 mutator).
|
|
18
|
+
* Refinements (#1) are managed by `withRefinements` in emit; overlay (#2) is
|
|
19
|
+
* scoped by call/loop-emit code and torn down when the scope exits.
|
|
20
|
+
*
|
|
21
|
+
* Mutation sites by phase:
|
|
22
|
+
* plan.js — initial reps from prepare-pass typing
|
|
23
|
+
* analyze.js — boxing decisions, schema bindings, sched facts
|
|
24
|
+
* compile/index.js — closure-arg upgrades, propagation across calls
|
|
25
|
+
* emit.js — withRefinements / overlay, transient narrowing only
|
|
26
|
+
*
|
|
27
|
+
* @module reps
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { ctx } from './ctx.js'
|
|
31
|
+
|
|
32
|
+
/** Value kinds — method dispatch, schema, carrier selection. */
|
|
33
|
+
export const VAL = {
|
|
34
|
+
NUMBER: 'number', ARRAY: 'array', STRING: 'string',
|
|
35
|
+
OBJECT: 'object', HASH: 'hash', SET: 'set', MAP: 'map',
|
|
36
|
+
CLOSURE: 'closure', TYPED: 'typed', REGEX: 'regex',
|
|
37
|
+
BIGINT: 'bigint', BUFFER: 'buffer', DATE: 'date',
|
|
38
|
+
BOOL: 'boolean',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A binding's inferred representation. Every field optional; absence = "unknown".
|
|
43
|
+
* Written only through updateRep / updateGlobalRep, plus a few direct r.wasm /
|
|
44
|
+
* r.typedCtor mutations in narrow.js's signature fixpoint. This is the *closed*
|
|
45
|
+
* shape: REP_FIELDS is the single source of truth — it gates updateRep in debug
|
|
46
|
+
* mode and drives repView, so a typo'd key surfaces loudly instead of silently
|
|
47
|
+
* vanishing into the open `{...prev, ...fields}` spread.
|
|
48
|
+
*
|
|
49
|
+
* @typedef {Object} ValueRep
|
|
50
|
+
* @property {string} [val] VAL.* kind (number/array/string/…).
|
|
51
|
+
* @property {number} [ptrKind] PTR.* pointer class for NaN-box rebox.
|
|
52
|
+
* @property {number} [ptrAux] aux bits in the NaN-box (schema id / elem type).
|
|
53
|
+
* @property {number} [schemaId] object-shape id (OBJECT kind).
|
|
54
|
+
* @property {number} [intConst] proven constant integer value.
|
|
55
|
+
* @property {boolean} [intCertain] integer-valued on every path.
|
|
56
|
+
* @property {boolean} [notString] proven not a string (skips string-path guards).
|
|
57
|
+
* @property {number} [arrayElemSchema] element object-schema id for arrays.
|
|
58
|
+
* @property {string} [arrayElemValType] element VAL.* kind for arrays.
|
|
59
|
+
* @property {string} [carrier] abi carrier id override (e.g. 'jsstring').
|
|
60
|
+
* @property {boolean} [unsigned] i32 carries an unsigned value (`>>>` result).
|
|
61
|
+
* @property {*} [jsonShape] inferred shape for the JSON.stringify fast path.
|
|
62
|
+
* @property {string} [typedCtor] TypedArray ctor name (TYPED kind); null = bimorphic.
|
|
63
|
+
* @property {string} [wasm] wasm storage type 'i32'|'f64' (narrow.js fixpoint).
|
|
64
|
+
* @property {boolean} [nullable] binding can hold null/undefined on some path
|
|
65
|
+
* (init or an assignment was a nullish literal) — suppresses the `=== null` /
|
|
66
|
+
* `=== undefined` constant-fold even when `val` is a definite non-null kind.
|
|
67
|
+
*/
|
|
68
|
+
export const REP_FIELDS = new Set([
|
|
69
|
+
'val', 'ptrKind', 'ptrAux', 'schemaId', 'intConst', 'intCertain', 'notString',
|
|
70
|
+
'arrayElemSchema', 'arrayElemValType', 'carrier', 'unsigned', 'jsonShape',
|
|
71
|
+
'typedCtor', 'wasm', 'nullable',
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
const DBG_REPS = typeof process !== 'undefined' && process.env?.JZ_DEBUG_INVARIANTS === '1'
|
|
75
|
+
const assertRepFields = (name, fields) => {
|
|
76
|
+
for (const k in fields)
|
|
77
|
+
if (!REP_FIELDS.has(k))
|
|
78
|
+
throw new Error(`updateRep('${name}', {${k}}): unknown ValueRep field — typo, or add it to REP_FIELDS in reps.js`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @returns {ValueRep|undefined} */
|
|
82
|
+
export const repOf = name => ctx.func.localReps?.get(name)
|
|
83
|
+
|
|
84
|
+
export const updateRep = (name, fields) => {
|
|
85
|
+
if (DBG_REPS) assertRepFields(name, fields)
|
|
86
|
+
const m = ctx.func.localReps ||= new Map()
|
|
87
|
+
const prev = m.get(name) || {}
|
|
88
|
+
const next = { ...prev, ...fields }
|
|
89
|
+
for (const k of Object.keys(next)) if (next[k] === undefined) delete next[k]
|
|
90
|
+
if (Object.keys(next).length === 0) m.delete(name)
|
|
91
|
+
else m.set(name, next)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const repOfGlobal = name => ctx.scope.globalReps?.get(name)
|
|
95
|
+
|
|
96
|
+
export const updateGlobalRep = (name, fields) => {
|
|
97
|
+
if (DBG_REPS) assertRepFields(name, fields)
|
|
98
|
+
const m = ctx.scope.globalReps ||= new Map()
|
|
99
|
+
const prev = m.get(name)
|
|
100
|
+
m.set(name, prev ? { ...prev, ...fields } : { ...fields })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const lookupValType = name => {
|
|
104
|
+
const r = ctx.func.refinements
|
|
105
|
+
if (r?.size) { const v = r.get(name)?.val; if (v) return v }
|
|
106
|
+
const ov = ctx.func.localValTypesOverlay
|
|
107
|
+
if (ov) { const v = ov.get(name); if (v) return v }
|
|
108
|
+
return ctx.func.localReps?.get(name)?.val || ctx.scope.globalValTypes?.get(name) || null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const lookupNotString = name => {
|
|
112
|
+
const r = ctx.func.refinements
|
|
113
|
+
if (r?.size && r.get(name)?.notString) return true
|
|
114
|
+
return ctx.func.localReps?.get(name)?.notString === true
|
|
115
|
+
}
|
package/src/resolve.js
CHANGED
|
@@ -14,9 +14,18 @@ import { readFileSync, existsSync } from 'fs'
|
|
|
14
14
|
import { dirname, resolve, join } from 'path'
|
|
15
15
|
import { execFileSync } from 'child_process'
|
|
16
16
|
|
|
17
|
-
// Matches
|
|
18
|
-
//
|
|
19
|
-
|
|
17
|
+
// Matches real module imports/exports at statement position — and ONLY those:
|
|
18
|
+
// import 'x' (bare side-effect import)
|
|
19
|
+
// import … from 'x' (default/named/namespace)
|
|
20
|
+
// export … from 'x' / export * from (re-export)
|
|
21
|
+
// The specifier MUST follow `from` (or be a bare `import`'s string). The old
|
|
22
|
+
// `(?:import|export)\s+[^'"]*?['"]…` matched any post-keyword string because
|
|
23
|
+
// `[^'"]` spans newlines, so `export const X = [⏎ 'lit'` was read as `export …
|
|
24
|
+
// 'lit'` — bundling then rewrote the bare string literal `'lit'` to a module path.
|
|
25
|
+
// (Self-host fallout: `PASS_NAMES = ['watr', …]` had `'watr'` rewritten to
|
|
26
|
+
// `…/node_modules/watr/watr.js`, corrupting every `'watr'` constant — and so the
|
|
27
|
+
// kernel's whole optimize config, since `cfg.watr` then read that path.)
|
|
28
|
+
const importRe = /^\s*(?:import\b[^'"]*?\bfrom\s*|import\s+|export\b[^'"]*?\bfrom\s*)['"]([^'"]+)['"]/gm
|
|
20
29
|
|
|
21
30
|
/**
|
|
22
31
|
* @param {string} entryFile - absolute or cwd-relative path to the entry module.
|
package/src/static.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time static evaluation — literals, property keys, schema ids.
|
|
3
|
+
* @module static
|
|
4
|
+
*/
|
|
5
|
+
import { I32_MIN, I32_MAX } from './ast.js'
|
|
6
|
+
import { ctx } from './ctx.js'
|
|
7
|
+
import { repOf, VAL } from './reps.js'
|
|
8
|
+
|
|
9
|
+
/** Extract integer value from AST literal node. Returns null if not a 32-bit integer. */
|
|
10
|
+
export function intLiteralValue(expr) {
|
|
11
|
+
let v = null
|
|
12
|
+
if (typeof expr === 'number') v = expr
|
|
13
|
+
else if (Array.isArray(expr) && expr[0] == null && typeof expr[1] === 'number') v = expr[1]
|
|
14
|
+
else if (Array.isArray(expr) && expr[0] === 'u-' && typeof expr[1] === 'number') v = -expr[1]
|
|
15
|
+
else if (typeof expr === 'string') v = repOf(expr)?.intConst ?? ctx.scope.constInts?.get(expr) ?? null
|
|
16
|
+
return v != null && Number.isInteger(v) && v >= I32_MIN && v <= I32_MAX ? v : null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Non-negative integer literal — used for string/typed-array index bounds. */
|
|
20
|
+
export const nonNegIntLiteral = (node) => { const n = intLiteralValue(node); return n != null && n >= 0 ? n : null }
|
|
21
|
+
|
|
22
|
+
/** Fold compile-time integer expressions (literals, const bindings, + - * <<). */
|
|
23
|
+
export function constIntExpr(node) {
|
|
24
|
+
let lit = intLiteralValue(node)
|
|
25
|
+
if (lit == null && typeof node === 'number' && Number.isInteger(node)) lit = node
|
|
26
|
+
if (lit == null && Array.isArray(node) && node[0] == null && Number.isInteger(node[1])) lit = node[1]
|
|
27
|
+
if (lit != null) return lit
|
|
28
|
+
if (typeof node === 'string') return repOf(node)?.intConst ?? ctx.scope.constInts?.get(node) ?? null
|
|
29
|
+
if (!Array.isArray(node)) return null
|
|
30
|
+
const op = node[0]
|
|
31
|
+
if (op === 'u-') {
|
|
32
|
+
const v = constIntExpr(node[1])
|
|
33
|
+
return v == null ? null : -v
|
|
34
|
+
}
|
|
35
|
+
if (node.length !== 3) return null
|
|
36
|
+
const a = constIntExpr(node[1]), b = constIntExpr(node[2])
|
|
37
|
+
if (a == null || b == null) return null
|
|
38
|
+
if (op === '+') return a + b
|
|
39
|
+
if (op === '-') return a - b
|
|
40
|
+
if (op === '*') return a * b
|
|
41
|
+
if (op === '<<') return a << b
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export const NO_VALUE = Symbol('no-static-property-key')
|
|
47
|
+
|
|
48
|
+
export function staticPropertyKey(node) {
|
|
49
|
+
const value = staticValue(node)
|
|
50
|
+
return value === NO_VALUE ? null : String(value)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function staticValue(node) {
|
|
54
|
+
if (node === undefined) return undefined
|
|
55
|
+
if (node === null || typeof node === 'number' || typeof node === 'boolean') return node
|
|
56
|
+
if (typeof node === 'string') return ctx.scope.constStrs?.get(node) ?? NO_VALUE
|
|
57
|
+
if (!Array.isArray(node)) return NO_VALUE
|
|
58
|
+
|
|
59
|
+
const [op, ...args] = node
|
|
60
|
+
if (op == null) return args.length ? args[0] : undefined
|
|
61
|
+
if (op === 'str') return args[0]
|
|
62
|
+
// Self-host kernel boundary marks a literal bool as `['bool', 1|0]` (interop
|
|
63
|
+
// normalizeBigints), where native keeps `[, true]` (caught by op==null above).
|
|
64
|
+
// Recover the boolean from its 0/1 carrier so const-folded keys/conditions
|
|
65
|
+
// resolve on the kernel leg (e.g. `{ [true ? 3 : 4]: 5 }`).
|
|
66
|
+
if (op === 'bool') { const c = staticValue(args[0]); return c === NO_VALUE ? NO_VALUE : !!c }
|
|
67
|
+
if (op === '[]' && args.length === 1) return staticValue(args[0])
|
|
68
|
+
if (op === '()' && args[0] === 'String' && args.length === 2) {
|
|
69
|
+
const value = staticValue(args[1])
|
|
70
|
+
return value === NO_VALUE ? NO_VALUE : String(value)
|
|
71
|
+
}
|
|
72
|
+
if (op === '()' && args[0] === 'Number' && args.length === 2) {
|
|
73
|
+
const value = staticValue(args[1])
|
|
74
|
+
return value === NO_VALUE ? NO_VALUE : Number(value)
|
|
75
|
+
}
|
|
76
|
+
if (op === '?:' || op === '?') {
|
|
77
|
+
const cond = staticValue(args[0])
|
|
78
|
+
return cond === NO_VALUE ? NO_VALUE : staticValue(cond ? args[1] : args[2])
|
|
79
|
+
}
|
|
80
|
+
if (op === '&&' || op === '||') {
|
|
81
|
+
const left = staticValue(args[0])
|
|
82
|
+
if (left === NO_VALUE) return NO_VALUE
|
|
83
|
+
return op === '&&' ? (left ? staticValue(args[1]) : left) : (left ? left : staticValue(args[1]))
|
|
84
|
+
}
|
|
85
|
+
if (op === '??') {
|
|
86
|
+
const left = staticValue(args[0])
|
|
87
|
+
return left === NO_VALUE ? NO_VALUE : left == null ? staticValue(args[1]) : left
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (args.length === 1) {
|
|
91
|
+
const value = staticValue(args[0])
|
|
92
|
+
if (value === NO_VALUE) return NO_VALUE
|
|
93
|
+
// Parser emits raw `-`/`+` for both unary and binary; prep later normalizes
|
|
94
|
+
// unary to `u-`/`u+`, but staticPropertyKey runs on raw parser AST.
|
|
95
|
+
if (op === 'u+' || op === '+') return +value
|
|
96
|
+
if (op === 'u-' || op === '-') return -value
|
|
97
|
+
if (op === '!') return !value
|
|
98
|
+
if (op === '~') return ~value
|
|
99
|
+
return NO_VALUE
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (args.length === 2) {
|
|
103
|
+
const left = staticValue(args[0])
|
|
104
|
+
const right = staticValue(args[1])
|
|
105
|
+
if (left === NO_VALUE || right === NO_VALUE) return NO_VALUE
|
|
106
|
+
switch (op) {
|
|
107
|
+
case '+': return typeof left === 'string' || typeof right === 'string' ? String(left) + String(right) : Number(left) + Number(right)
|
|
108
|
+
case '-': return Number(left) - Number(right)
|
|
109
|
+
case '*': return Number(left) * Number(right)
|
|
110
|
+
case '/': return Number(left) / Number(right)
|
|
111
|
+
case '%': return Number(left) % Number(right)
|
|
112
|
+
case '**': return Number(left) ** Number(right)
|
|
113
|
+
case '&': return Number(left) & Number(right)
|
|
114
|
+
case '|': return Number(left) | Number(right)
|
|
115
|
+
case '^': return Number(left) ^ Number(right)
|
|
116
|
+
case '<<': return Number(left) << Number(right)
|
|
117
|
+
case '>>': return Number(left) >> Number(right)
|
|
118
|
+
case '>>>': return Number(left) >>> Number(right)
|
|
119
|
+
default: return NO_VALUE
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return NO_VALUE
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Decode a `['{}', ...]` AST's children into `{names, values}`, or null if any
|
|
127
|
+
* property is non-static-key (computed key, spread, shorthand). Matches the
|
|
128
|
+
* emitter's flatten rule for comma-grouped props. Used by collectProgramFacts,
|
|
129
|
+
* narrowSignatures, and objLiteralSchemaId; the emitter (module/object.js)
|
|
130
|
+
* does its own decoding because it must handle the spread/computed-key paths. */
|
|
131
|
+
export function staticObjectProps(args) {
|
|
132
|
+
const raw = args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',' ? args[0].slice(1) : args
|
|
133
|
+
const names = [], values = []
|
|
134
|
+
for (const p of raw) {
|
|
135
|
+
if (!Array.isArray(p) || p[0] !== ':' || typeof p[1] !== 'string') return null
|
|
136
|
+
names.push(p[1]); values.push(p[2])
|
|
137
|
+
}
|
|
138
|
+
return names.length ? { names, values } : null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function staticArrayElems(expr) {
|
|
142
|
+
if (!Array.isArray(expr)) return null
|
|
143
|
+
if (expr[0] === '[') return expr.slice(1)
|
|
144
|
+
if (expr[0] !== '[]' || expr.length >= 3) return null
|
|
145
|
+
const arg = expr[1]
|
|
146
|
+
if (arg == null) return []
|
|
147
|
+
return Array.isArray(arg) && arg[0] === ',' ? arg.slice(1) : [arg]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Schema-id for an object literal expression. Returns null on dynamic keys, spread, shorthand. */
|
|
151
|
+
export function objLiteralSchemaId(expr) {
|
|
152
|
+
if (!Array.isArray(expr) || expr[0] !== '{}' || !ctx.schema?.register) return null
|
|
153
|
+
const parsed = staticObjectProps(expr.slice(1))
|
|
154
|
+
return parsed ? ctx.schema.register(parsed.names) : null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Resolve schemaId of an expression, given a per-function schemaId map for locals.
|
|
158
|
+
* Used for both intra-function arr elem-schema observation and func.arrayElemSchema
|
|
159
|
+
* return inference. Recognizes: object literals, var names with bound schemaId,
|
|
160
|
+
* user fn calls with narrowed result schema, ?: / && / || when both branches agree. */
|
|
161
|
+
export function exprSchemaId(expr, localSchemaMap) {
|
|
162
|
+
if (typeof expr === 'string') {
|
|
163
|
+
if (localSchemaMap?.has(expr)) return localSchemaMap.get(expr)
|
|
164
|
+
return ctx.schema?.idOf?.(expr) ?? null
|
|
165
|
+
}
|
|
166
|
+
if (!Array.isArray(expr)) return null
|
|
167
|
+
const op = expr[0]
|
|
168
|
+
if (op === '{}') return objLiteralSchemaId(expr)
|
|
169
|
+
if (op === '()' && typeof expr[1] === 'string') {
|
|
170
|
+
const f = ctx.func.map?.get(expr[1])
|
|
171
|
+
if (f?.valResult === VAL.OBJECT && f.sig?.ptrAux != null) return f.sig.ptrAux
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
if (op === '?:') {
|
|
175
|
+
const a = exprSchemaId(expr[2], localSchemaMap)
|
|
176
|
+
const b = exprSchemaId(expr[3], localSchemaMap)
|
|
177
|
+
return a != null && a === b ? a : null
|
|
178
|
+
}
|
|
179
|
+
if (op === '&&' || op === '||') {
|
|
180
|
+
const a = exprSchemaId(expr[1], localSchemaMap)
|
|
181
|
+
const b = exprSchemaId(expr[2], localSchemaMap)
|
|
182
|
+
return a != null && a === b ? a : null
|
|
183
|
+
}
|
|
184
|
+
return null
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function inlineArraySid(name) {
|
|
188
|
+
if (typeof name !== 'string') return null
|
|
189
|
+
// structInline is keyed on the per-function `localReps` rep, so it is only
|
|
190
|
+
// consistent for a *function-local* array — a write site and a read site in the
|
|
191
|
+
// same frame agree. A module-global array is read across functions whose frames
|
|
192
|
+
// carry no rep for it, so the carrier would diverge: `G.push({a,b})` in one
|
|
193
|
+
// function flattens the struct into K cells, while `G.length` / `G[i].a` in
|
|
194
|
+
// another sees a plain array (K=1) and reads garbage. Never inline a global's
|
|
195
|
+
// element struct — the plain Array<ptr> representation is consistent everywhere.
|
|
196
|
+
if (ctx.scope.globals?.has(name)) return null
|
|
197
|
+
const sid = ctx.func.localReps?.get(name)?.arrayElemSchema
|
|
198
|
+
return sid != null && ctx.schema.inlineArray?.has(sid) ? sid : null
|
|
199
|
+
}
|