jz 0.5.1 → 0.7.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 +315 -318
- package/bench/README.md +369 -0
- package/bench/bench.svg +102 -0
- package/cli.js +104 -30
- package/dist/interop.js +1 -0
- package/dist/jz.js +6024 -0
- package/index.d.ts +126 -0
- package/index.js +362 -84
- package/interop.js +159 -186
- 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 +184 -0
- package/module/array.js +377 -176
- package/module/collection.js +639 -145
- package/module/console.js +55 -43
- package/module/core.js +264 -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 +551 -187
- package/module/number.js +327 -60
- package/module/object.js +474 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +117 -0
- package/module/string.js +621 -226
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +459 -73
- package/package.json +58 -14
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +29 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +824 -0
- package/src/compile/analyze.js +1600 -0
- package/src/compile/emit-assign.js +411 -0
- package/src/compile/emit.js +3512 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +778 -128
- package/src/{infer.js → compile/infer.js} +62 -98
- package/src/compile/loop-divmod.js +155 -0
- package/src/{narrow.js → compile/narrow.js} +409 -106
- package/src/compile/peel-stencil.js +261 -0
- package/src/compile/plan/advise.js +370 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +122 -0
- package/src/compile/plan/inline.js +682 -0
- package/src/compile/plan/literals.js +1199 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +649 -0
- package/src/compile/program-facts.js +423 -0
- package/src/ctx.js +220 -64
- package/src/ir.js +589 -172
- package/src/kind-traits.js +132 -0
- package/src/kind.js +524 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1432 -473
- package/src/optimize/vectorize.js +4660 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +649 -205
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +116 -0
- package/src/resolve.js +12 -3
- package/src/static.js +208 -0
- package/src/type.js +651 -0
- package/src/{assemble.js → wat/assemble.js} +275 -55
- package/src/wat/optimize.js +3938 -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/src/ctx.js
CHANGED
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
* Refactored into focused sub-contexts for better maintainability.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { makeAbi } from './abi/index.js'
|
|
11
|
+
export { HEAP, LAYOUT, PTR, ATOM, FORWARDING_MASK, nanPrefixHex, atomNanHex, ssoBitI64Hex, sliceBitI64Hex, ptrNanHex, ptrBoxPrefixBigInt, encodePtrHi, decodePtrType, decodePtrAux, ATOM_HI, oobNanLiteral, oobNanIR, followForwardingWat } from '../layout.js'
|
|
11
12
|
|
|
12
13
|
// === Carrier layout ===
|
|
14
|
+
// Canonical bit layout lives in layout.js (compiler-free). Re-exported above for
|
|
15
|
+
// backward-compatible `import { LAYOUT, PTR } from './ctx.js'`.
|
|
16
|
+
//
|
|
13
17
|
// i64 carrier holds either:
|
|
14
18
|
// - raw f64 number bits (any non-NaN-shape pattern), discriminated by
|
|
15
19
|
// `f64.eq(f, f)` — true for real numbers, false for NaN-shape pointers.
|
|
@@ -19,38 +23,6 @@ import { DEFAULTS as ABI_DEFAULTS } from './abi/index.js'
|
|
|
19
23
|
// `${LAYOUT.TAG_SHIFT}` etc. so a layout change propagates by re-evaluation.
|
|
20
24
|
// Hot dispatch (__ptr_type/__ptr_aux/__ptr_offset) keeps the inline expansion
|
|
21
25
|
// for codegen size; those sites are commented as LAYOUT-tied.
|
|
22
|
-
export const LAYOUT = {
|
|
23
|
-
TAG_SHIFT: 47,
|
|
24
|
-
TAG_MASK: 0xF, // 4-bit tag (16 type slots)
|
|
25
|
-
AUX_SHIFT: 32,
|
|
26
|
-
AUX_MASK: 0x7FFF, // 15-bit aux (schemaId, elemType, atomId, …)
|
|
27
|
-
OFFSET_MASK: 0xFFFFFFFF, // 32-bit offset (4GB heap)
|
|
28
|
-
NAN_PREFIX: 0x7FF8, // top 13 bits of any NaN-shape pointer
|
|
29
|
-
NAN_PREFIX_BITS: 0x7FF8000000000000n, // pre-shifted for i64 OR
|
|
30
|
-
SSO_BIT: 0x4000, // STRING aux bit 14: 1=inline (≤4 ASCII chars in offset), 0=heap
|
|
31
|
-
SLICE_BIT: 0x2000, // STRING aux bit 13: 1=view (no length header — len in aux[12:0],
|
|
32
|
-
// offset points into a parent buffer), 0=own heap string.
|
|
33
|
-
SLICE_LEN_MASK: 0x1FFF, // aux[12:0] — view length (≤8191; larger slices fall back to copy)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// === Tagged-pointer type codes ===
|
|
37
|
-
// 4-bit tag (LAYOUT.TAG_MASK = 16 slots).
|
|
38
|
-
// To retire a type, gate emission behind a feature flag and drop its dispatch
|
|
39
|
-
// branches; to add, renumber with care — hardcoded branches in module/* must update.
|
|
40
|
-
export const PTR = {
|
|
41
|
-
ATOM: 0, // null, undefined, booleans, symbols (aux=atomId)
|
|
42
|
-
ARRAY: 1, // heap-allocated arrays
|
|
43
|
-
BUFFER: 2, // ArrayBuffer: [-8:byteLen][-4:byteCap][bytes]
|
|
44
|
-
TYPED: 3, // TypedArrays (Float64Array, etc.)
|
|
45
|
-
STRING: 4, // strings (heap or inline-SSO; aux bit LAYOUT.SSO_BIT distinguishes)
|
|
46
|
-
// 5: free
|
|
47
|
-
OBJECT: 6, // plain objects
|
|
48
|
-
HASH: 7, // dynamic objects (Map-like)
|
|
49
|
-
SET: 8, // Set collections
|
|
50
|
-
MAP: 9, // Map collections
|
|
51
|
-
CLOSURE: 10, // first-class functions
|
|
52
|
-
EXTERNAL: 11, // JS host object refs (aux=0, offset→extMap index)
|
|
53
|
-
}
|
|
54
26
|
|
|
55
27
|
// === Global context with nested sub-contexts ===
|
|
56
28
|
// Each namespace has a single lifecycle phase and clear ownership. Violating
|
|
@@ -62,21 +34,35 @@ export const PTR = {
|
|
|
62
34
|
// function — per function being lowered
|
|
63
35
|
// emit — transient during a single AST→IR dispatch
|
|
64
36
|
//
|
|
65
|
-
// | Namespace | Phase | Writers
|
|
66
|
-
//
|
|
67
|
-
// | core | compile | reset, modules, inc()
|
|
68
|
-
// | module | compile | prepare, index.js
|
|
69
|
-
// | scope | compile | analyze, compile
|
|
70
|
-
// | func | function | compile
|
|
71
|
-
// | types | function | analyze | emit, modules
|
|
72
|
-
// | schema | compile | prepare, analyze, compile
|
|
73
|
-
// | closure | init | modules (fn plugin)
|
|
74
|
-
// | runtime | compile | emit, modules
|
|
75
|
-
// | memory | compile | index.js
|
|
76
|
-
// | error | compile | prepare, compile, emit
|
|
77
|
-
// | transform | compile | index.js
|
|
78
|
-
// | features | compile | emit, modules, prepare
|
|
79
|
-
// |
|
|
37
|
+
// | Namespace | Phase | Writers | Readers |
|
|
38
|
+
// |-----------|----------|---------------------------------|---------------------------|
|
|
39
|
+
// | core | compile | reset, modules, inc(), emit* | emit, compile, modules |
|
|
40
|
+
// | module | compile | prepare, index.js | prepare, compile, emit |
|
|
41
|
+
// | scope | compile | analyze, compile, plan, modules | compile, emit |
|
|
42
|
+
// | func | function | compile, narrow | emit, modules |
|
|
43
|
+
// | types | function | analyze, plan | emit, modules |
|
|
44
|
+
// | schema | compile | prepare, analyze, compile | prepare, analyze, emit |
|
|
45
|
+
// | closure | init | modules (fn plugin) | emit, compile |
|
|
46
|
+
// | runtime | compile | emit, modules | emit, compile |
|
|
47
|
+
// | memory | compile | index.js | compile |
|
|
48
|
+
// | error | compile | prepare, compile, emit | err() |
|
|
49
|
+
// | transform | compile | index.js | prepare, compile, emit |
|
|
50
|
+
// | features | compile | emit, modules, prepare | compile, stdlib factories |
|
|
51
|
+
// | abi | compile | reset (makeAbi) | ir.js codegen, optimizer |
|
|
52
|
+
// | bridge | compile | reset (bridge.js) | bridge.js → emit, modules |
|
|
53
|
+
//
|
|
54
|
+
// *emit's only `core` write is ctx.core.hostGlobals (a bare host-global reference),
|
|
55
|
+
// drained to env imports at compile (compile/index.js) — NOT to ctx.scope, so emit
|
|
56
|
+
// never writes scope. The stdlib module factories DO write ctx.scope.globals
|
|
57
|
+
// directly (core/string register __heap, __strBase, __tof_* there at compile phase).
|
|
58
|
+
//
|
|
59
|
+
// plan-phase writers (extending compile-phase): plan writes
|
|
60
|
+
// ctx.scope.{globalValTypes, globalTypedElem, globals, globalTypes} via
|
|
61
|
+
// inferModuleLetTypes / unboxConstTypedGlobals / inferModuleIntGlobals,
|
|
62
|
+
// and ctx.types.{dynKeyVars, anyDynKey} from collectProgramFacts results.
|
|
63
|
+
// narrow-phase writers: narrowSignatures (under plan) temporarily swaps
|
|
64
|
+
// ctx.func.{localReps, locals, current} per-function with save/restore
|
|
65
|
+
// so per-call-site signature inference sees the right scope.
|
|
80
66
|
export const ctx = {
|
|
81
67
|
core: {}, // emitter table + stdlib registry (seeded by reset + modules)
|
|
82
68
|
module: {}, // module graph: imports, resolved sources, module-init blocks
|
|
@@ -88,11 +74,22 @@ export const ctx = {
|
|
|
88
74
|
runtime: {}, // runtime state: data segments, string pool, atom table, throws flag
|
|
89
75
|
memory: {}, // module memory config (pages, shared)
|
|
90
76
|
error: {}, // source location carried through emit for err() messages
|
|
91
|
-
transform: {}, // compile-time options
|
|
77
|
+
transform: {}, // compile-time options + injected services. Three categories:
|
|
78
|
+
// user opts : noTailCall, strict, alloc, importMetaUrl, host, inspect
|
|
79
|
+
// derived cfg : optimize (resolved by resolveOptimize from user input)
|
|
80
|
+
// services : parse, resolveUrl, jzify (when set to a function by the
|
|
81
|
+
// host pipeline; boolean form is a user opt). Service
|
|
82
|
+
// injection is the pattern that lets the self-host kernel
|
|
83
|
+
// run without a parser — it omits these and prepare uses
|
|
84
|
+
// ctx.module.importAsts instead.
|
|
92
85
|
abi: {}, // per-type rep lookup (see abi/index.js). { number: rep, string: rep, ... }
|
|
93
86
|
// Set by reset() to the default carrier bundle. Read by codegen sites
|
|
94
87
|
// that delegate rep-specific behavior — today just the optimizer's
|
|
95
88
|
// peephole hook; expanding as per-site narrowing tags individual sites.
|
|
89
|
+
bridge: {}, // emit/flat/wat dispatch, bound by reset() (see bridge.js). Lets every
|
|
90
|
+
// module call emit() without importing the emitter — breaks the cycle.
|
|
91
|
+
features: {}, // codegen capability flags (external, sso, typedarray, …), reset() seeds
|
|
92
|
+
// the defaults; see reset() for the field list and who flips each.
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
/** Create a child scope via shallow flat copy (metacircular-safe: no prototype chain).
|
|
@@ -102,6 +99,17 @@ export const derive = (parent) => ({ ...parent })
|
|
|
102
99
|
/** Include stdlib names for emission. */
|
|
103
100
|
export const inc = (...names) => names.forEach(n => ctx.core.includes.add(n))
|
|
104
101
|
|
|
102
|
+
/** Declare a module global as a structured record — the single shape behind
|
|
103
|
+
* every `ctx.scope.globals` entry:
|
|
104
|
+
* { type: 'i32'|'i64'|'f64', mut: bool, init: number|string, export: string|null }
|
|
105
|
+
* `init` is a number or a watr const literal (`-1`, `nan:0x…`, hex). Replaces
|
|
106
|
+
* the old WAT-text strings: type queries are field reads, emission builds IR
|
|
107
|
+
* directly (no parse-back), and `globalTypes` is set in the same move. */
|
|
108
|
+
export const declGlobal = (name, type, init = 0, opts) => {
|
|
109
|
+
ctx.scope.globals.set(name, { type, mut: opts?.mut !== false, init, export: opts?.export ?? null })
|
|
110
|
+
ctx.scope.globalTypes.set(name, type)
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
/** Wrap an emit handler with a declarative stdlib-dependency list. The deps
|
|
106
114
|
* become data — exposed as `.deps` (tabulatable, analyzable) — and are `inc`'d
|
|
107
115
|
* on every call, while the body `fn` stays a pure `args → IR` builder (also
|
|
@@ -111,35 +119,77 @@ export const emitter = (deps, fn) => {
|
|
|
111
119
|
const run = (...args) => (inc(...deps), fn(...args))
|
|
112
120
|
run.deps = deps
|
|
113
121
|
run.pure = fn
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
|
|
122
|
+
// Carry the body's parameter count as `.argc`: the rest-param wrapper above
|
|
123
|
+
// reports `.length` 0, so a handler's logical arity must travel as plain data
|
|
124
|
+
// (read back via `emitArity`, never the masked function `.length`). Two
|
|
125
|
+
// consumers need it — `typeof Math.x` folding (callable builtin vs constant)
|
|
126
|
+
// and the `.`-emit property/method split (arity-1 reads as a value; arity ≥2
|
|
127
|
+
// is call-only).
|
|
128
|
+
run.argc = fn.length
|
|
118
129
|
return run
|
|
119
130
|
}
|
|
120
131
|
|
|
132
|
+
/** Logical arity of an emit handler: wrapped handlers (emitter/call/method/dual)
|
|
133
|
+
* carry it as `.argc`; bare ones expose it as the function's own `.length`. */
|
|
134
|
+
export const emitArity = (h) => h?.argc ?? h?.length
|
|
135
|
+
|
|
136
|
+
/** Tag an emit handler as a property getter — it yields a value when the
|
|
137
|
+
* property is *read* (`re.source`, `m.size`), so the `.`-read path may fire it.
|
|
138
|
+
* Untagged handlers are methods: a bare read of `m.values` must not invoke them
|
|
139
|
+
* (that would materialize a view instead of reading the `"values"` property);
|
|
140
|
+
* they fire only from the method-call path. Apply outermost: `getter(emitter(…))`. */
|
|
141
|
+
export const getter = (fn) => (fn.getter = true, fn)
|
|
142
|
+
|
|
121
143
|
/** Expand ctx.core.includes transitively via ctx.core.stdlibDeps. Call before WASM assembly.
|
|
122
144
|
* Each module co-locates its own deps with its stdlib registrations at init time. */
|
|
123
145
|
export function resolveIncludes() {
|
|
124
146
|
const graph = ctx.core.stdlibDeps
|
|
147
|
+
const stdlib = ctx.core.stdlib
|
|
148
|
+
// Auto-derived deps: a stdlib template that calls `$__foo` (a registered stdlib
|
|
149
|
+
// func) depends on it, whether or not the hand-maintained `deps()` list says so.
|
|
150
|
+
// Scanning the *realized* template keeps the graph honest, so a missing manual
|
|
151
|
+
// entry can't silently drop a transitively-needed helper (the bug class the old
|
|
152
|
+
// blanket `inc('__mkptr','__alloc')` masked). Factory templates are realized
|
|
153
|
+
// (called) so feature-gated branches — `${hasExt ? '(call $__ext_prop …)' : ''}`
|
|
154
|
+
// — resolve before scanning; reading raw source would over-pull the dead branch.
|
|
155
|
+
// jz's templates are pure string builders, so realizing here (and again at
|
|
156
|
+
// emission) is side-effect-free. A `$__foo` naming a global (not a stdlib func)
|
|
157
|
+
// is skipped. Realization can fail if called before its inputs are ready — then
|
|
158
|
+
// we return nothing *without caching*, so a later pass retries. Memoized per compile.
|
|
159
|
+
const autoCache = ctx.core._autoDeps ??= new Map()
|
|
160
|
+
const autoDepsOf = (name) => {
|
|
161
|
+
let found = autoCache.get(name)
|
|
162
|
+
if (found !== undefined) return found
|
|
163
|
+
const v = stdlib[name]
|
|
164
|
+
let text
|
|
165
|
+
if (typeof v === 'string') text = v
|
|
166
|
+
else if (typeof v === 'function') { try { text = v() } catch { return [] } }
|
|
167
|
+
if (typeof text !== 'string') return (autoCache.set(name, []), [])
|
|
168
|
+
found = []
|
|
169
|
+
const seen = new Set()
|
|
170
|
+
for (const m of text.matchAll(/\$(__[A-Za-z0-9_]+)/g)) {
|
|
171
|
+
const d = m[1]
|
|
172
|
+
if (d !== name && stdlib[d] && !seen.has(d)) { seen.add(d); found.push(d) }
|
|
173
|
+
}
|
|
174
|
+
autoCache.set(name, found)
|
|
175
|
+
return found
|
|
176
|
+
}
|
|
125
177
|
let changed = true
|
|
126
178
|
while (changed) {
|
|
127
179
|
changed = false
|
|
128
180
|
for (const name of [...ctx.core.includes]) {
|
|
129
181
|
const entry = graph[name]
|
|
130
182
|
const deps = typeof entry === 'function' ? entry() : entry
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
changed = true
|
|
135
|
-
}
|
|
136
|
-
}
|
|
183
|
+
const add = (dep) => { if (!ctx.core.includes.has(dep)) { ctx.core.includes.add(dep); changed = true } }
|
|
184
|
+
if (deps) for (const dep of deps) add(dep)
|
|
185
|
+
for (const dep of autoDepsOf(name)) add(dep)
|
|
137
186
|
}
|
|
138
187
|
}
|
|
139
188
|
}
|
|
140
189
|
|
|
141
190
|
/** Reset all compilation state. Called once per jz() invocation. */
|
|
142
|
-
export function reset(proto, globals) {
|
|
191
|
+
export function reset(proto, globals, bridge) {
|
|
192
|
+
ctx.bridge = bridge
|
|
143
193
|
ctx.core = {
|
|
144
194
|
emit: derive(proto),
|
|
145
195
|
stdlib: {},
|
|
@@ -152,6 +202,11 @@ export function reset(proto, globals) {
|
|
|
152
202
|
// Drained at module-assembly time into `(import "wasm:js-string" "name" …)`
|
|
153
203
|
// nodes; host wires JS-side polyfills via interop's
|
|
154
204
|
// env builder for engines without builtin support.
|
|
205
|
+
hostGlobals: new Set(), // host globals (globalThis/process/WebAssembly/…) referenced as
|
|
206
|
+
// values. Recorded by emit on first use; drained into
|
|
207
|
+
// `(import "env" "name" (global $name i64))` at assembly. Same
|
|
208
|
+
// usage-gated pattern as jsstring — emit records, assembly owns
|
|
209
|
+
// the ctx.module.imports write.
|
|
155
210
|
}
|
|
156
211
|
|
|
157
212
|
|
|
@@ -159,6 +214,8 @@ export function reset(proto, globals) {
|
|
|
159
214
|
imports: [],
|
|
160
215
|
modules: {},
|
|
161
216
|
importSources: null,
|
|
217
|
+
importAsts: null, // self-host: pre-parsed [specifier, ast] pairs (the kernel can't parse).
|
|
218
|
+
// Consulted by prepareModule before falling back to ctx.transform.parse(source).
|
|
162
219
|
hostImports: null,
|
|
163
220
|
hostImportValTypes: new Map(),
|
|
164
221
|
resolvedModules: new Map(),
|
|
@@ -170,7 +227,7 @@ export function reset(proto, globals) {
|
|
|
170
227
|
|
|
171
228
|
ctx.scope = {
|
|
172
229
|
chain: derive(globals),
|
|
173
|
-
globals: new Map(),
|
|
230
|
+
globals: new Map(), // name → { type, mut, init, export } records (see declGlobal)
|
|
174
231
|
userGlobals: new Set(),
|
|
175
232
|
globalTypes: new Map(),
|
|
176
233
|
globalValTypes: null,
|
|
@@ -190,6 +247,7 @@ export function reset(proto, globals) {
|
|
|
190
247
|
localReps: null,
|
|
191
248
|
refinements: new Map(), // flow-sensitive: name → {val?: VAL.*, notString?: true} inside a type-guarded branch
|
|
192
249
|
boxed: new Map(),
|
|
250
|
+
cellTypes: new Set(), // boxed vars whose CELL stores raw i32 (closure-capture narrowing)
|
|
193
251
|
stack: [],
|
|
194
252
|
uniq: 0,
|
|
195
253
|
inTry: false,
|
|
@@ -201,17 +259,29 @@ export function reset(proto, globals) {
|
|
|
201
259
|
// by the pass owners so re-entrant analyzeBody calls don't clobber each other.
|
|
202
260
|
localValTypesOverlay: null,
|
|
203
261
|
localTypedElemsOverlay: null,
|
|
262
|
+
_ccBody: null, // memo key: body node last scanned by inBoundsCharCodeAt (src/type.js)
|
|
263
|
+
ccInBounds: null, // memo value: Set of in-bounds charCodeAt callee nodes for _ccBody
|
|
264
|
+
_aiBody: null, // memo key: body node last scanned by inBoundsArrIdx (src/type.js)
|
|
265
|
+
aiInBounds: null, // memo value: Set of in-bounds "recv\0idx" array-read keys for _aiBody
|
|
204
266
|
}
|
|
205
267
|
|
|
206
268
|
ctx.types = {
|
|
207
269
|
typedElem: null,
|
|
208
270
|
dynKeyVars: null,
|
|
271
|
+
dynWriteVars: null,
|
|
209
272
|
anyDynKey: false,
|
|
210
273
|
}
|
|
211
274
|
|
|
212
275
|
ctx.schema = {
|
|
213
276
|
list: [],
|
|
214
277
|
vars: new Map(),
|
|
278
|
+
poisoned: new Set(), // names whose assignments disagree on shape (literal +
|
|
279
|
+
// non-literal, or two different literals). A poisoned
|
|
280
|
+
// name never (re)binds in schema.vars: fixed-slot reads
|
|
281
|
+
// against ONE literal's layout would misread the other
|
|
282
|
+
// sources' objects. Populated by prepare's `=` handler;
|
|
283
|
+
// end-of-prepare state is what compile reads, so the
|
|
284
|
+
// conflict is order-insensitive.
|
|
215
285
|
register: null,
|
|
216
286
|
find: null,
|
|
217
287
|
targetStack: [],
|
|
@@ -246,6 +316,14 @@ export function reset(proto, globals) {
|
|
|
246
316
|
bodies: null,
|
|
247
317
|
make: null,
|
|
248
318
|
call: null,
|
|
319
|
+
numericReturn: null, // Set<closureBodyName> proven to return a plain number — lets
|
|
320
|
+
// callers skip the __to_num result coercion (function.js seeds it).
|
|
321
|
+
paramTypes: null, // Map<closureBodyName, bool[]> — per-param "every direct call site
|
|
322
|
+
// passed a number" lattice; emitClosureBody marks such params
|
|
323
|
+
// VAL.NUMBER so their body uses skip __to_num (tryDirectClosureCall seeds).
|
|
324
|
+
minArgc: null, // Map<closureBodyName, number> — fewest args any direct call passed.
|
|
325
|
+
// A slot at index ≥ minArgc is omitted by some call (→ may be undefined),
|
|
326
|
+
// so it must NOT be typed NUMBER, else `x === undefined` mis-folds to false.
|
|
249
327
|
}
|
|
250
328
|
|
|
251
329
|
ctx.runtime = {
|
|
@@ -258,11 +336,15 @@ export function reset(proto, globals) {
|
|
|
258
336
|
throws: false,
|
|
259
337
|
userThrows: false, // user wrote `throw`/`try`/`catch`/`finally` — keep runtime declared
|
|
260
338
|
// even when all throws are dead-code-eliminated (JS-side ABI contract).
|
|
339
|
+
staticPtrSlots: null, // [byteOffset] data-segment slots holding NaN-boxed ptrs (host relocates); lazy-init in ir.js
|
|
340
|
+
staticDataLen: 0, // byte length of the address-0 static string block (seeded by module/number staticStr)
|
|
341
|
+
typeofStrs: null, // [str] interned typeof result strings; lazy-init in module/core `typeof`
|
|
261
342
|
}
|
|
262
343
|
|
|
263
344
|
ctx.memory = {
|
|
264
345
|
shared: false,
|
|
265
346
|
pages: 0,
|
|
347
|
+
max: 0, // 0 = unbounded; >0 emits a maximum on the memory type (cap growth)
|
|
266
348
|
}
|
|
267
349
|
|
|
268
350
|
ctx.error = {
|
|
@@ -294,6 +376,9 @@ export function reset(proto, globals) {
|
|
|
294
376
|
// Shape: { abi, functions: { [name]: { exported, params, results, ptrKind?, locals, callerReps } }, schemas }.
|
|
295
377
|
ctx.inspect = null
|
|
296
378
|
|
|
379
|
+
// Advisory sink. Populated when compile() receives opts.warnings.
|
|
380
|
+
ctx.warnings = null
|
|
381
|
+
|
|
297
382
|
// Feature flags: capabilities the compiled module may exercise at runtime.
|
|
298
383
|
// Set true by producer sites (import points, auto-imports, dynamic call sites).
|
|
299
384
|
// Read by stdlib template factories and deps graph at resolveIncludes() time to
|
|
@@ -309,7 +394,7 @@ export function reset(proto, globals) {
|
|
|
309
394
|
// (b) a capability needs an opt-in A/B switch against the default path
|
|
310
395
|
// (SSO is the planned first user — default string-literal emission
|
|
311
396
|
// currently forces SSO for ≤4 ASCII chars at string.js:49)
|
|
312
|
-
ctx.abi =
|
|
397
|
+
ctx.abi = makeAbi()
|
|
313
398
|
|
|
314
399
|
// Only flags actually read by codegen live here. Hash/regex/json substrates
|
|
315
400
|
// are pulled organically by inc(__*) — no flag mediates them, so no flag exists.
|
|
@@ -325,8 +410,76 @@ export function reset(proto, globals) {
|
|
|
325
410
|
}
|
|
326
411
|
}
|
|
327
412
|
|
|
413
|
+
/** Debug-mode invariant checks. Encodes the writers/readers contract documented
|
|
414
|
+
* above as runtime asserts so a bad refactor surfaces at the phase boundary
|
|
415
|
+
* instead of as a distant nondeterministic failure. No-op unless
|
|
416
|
+
* `JZ_DEBUG_INVARIANTS=1`; designed so phase-boundary callers can sprinkle
|
|
417
|
+
* `assertCtxInvariants('post-prepare')` without runtime cost in production.
|
|
418
|
+
*
|
|
419
|
+
* Phases checked:
|
|
420
|
+
* - `post-reset` : every sub-context exists; Maps/Sets initialized.
|
|
421
|
+
* - `post-prepare` : module + scope populated; func.list possibly empty.
|
|
422
|
+
* - `pre-emit` : func.current set; locals Map present; rep maps live.
|
|
423
|
+
* - `post-compile` : no transient temps leaked (func.uniq stable across calls). */
|
|
424
|
+
const DBG_INVARIANTS = typeof process !== 'undefined' && process.env?.JZ_DEBUG_INVARIANTS === '1'
|
|
425
|
+
export function assertCtxInvariants(phase) {
|
|
426
|
+
if (!DBG_INVARIANTS) return
|
|
427
|
+
const fail = msg => { throw new Error(`[ctx invariant] ${phase}: ${msg}`) }
|
|
428
|
+
const must = (cond, msg) => { if (!cond) fail(msg) }
|
|
429
|
+
|
|
430
|
+
must(ctx.core && ctx.module && ctx.scope && ctx.func && ctx.transform && ctx.features,
|
|
431
|
+
'sub-contexts present')
|
|
432
|
+
if (phase !== 'pre-reset') {
|
|
433
|
+
must(ctx.core.includes instanceof Set, 'core.includes is Set')
|
|
434
|
+
must(ctx.core.emit && typeof ctx.core.emit === 'object', 'core.emit table')
|
|
435
|
+
must(Array.isArray(ctx.func.list), 'func.list array')
|
|
436
|
+
must(ctx.func.locals instanceof Map, 'func.locals Map')
|
|
437
|
+
must(ctx.func.refinements instanceof Map, 'func.refinements Map')
|
|
438
|
+
}
|
|
439
|
+
if (phase === 'pre-emit') {
|
|
440
|
+
must(ctx.func.current, 'func.current set before emit')
|
|
441
|
+
must(ctx.func.locals.size != null, 'locals open for writes')
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Enable compile-time advisories. Pass `opts.warnings` (mirrors `opts.profile`). */
|
|
446
|
+
export function initWarnings(sink) {
|
|
447
|
+
if (sink == null) {
|
|
448
|
+
ctx.warnings = null
|
|
449
|
+
return
|
|
450
|
+
}
|
|
451
|
+
sink.entries ||= []
|
|
452
|
+
ctx.warnings = { sink, seen: new Set() }
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Record one advisory; `loc` is a source byte offset used only to derive
|
|
456
|
+
* line/column — it is never persisted on the entry. No-op unless
|
|
457
|
+
* `initWarnings` wired a sink. */
|
|
458
|
+
export function warn(code, message, meta = {}, loc = null) {
|
|
459
|
+
if (!ctx.warnings) return
|
|
460
|
+
const key = `${code}:${meta.fn || ''}:${meta.line || ''}`
|
|
461
|
+
if (ctx.warnings.seen.has(key)) return
|
|
462
|
+
ctx.warnings.seen.add(key)
|
|
463
|
+
const entry = { code, message, ...meta }
|
|
464
|
+
if (loc != null && ctx.error.src) {
|
|
465
|
+
const before = ctx.error.src.slice(0, loc)
|
|
466
|
+
entry.line = before.split('\n').length
|
|
467
|
+
entry.column = loc - before.lastIndexOf('\n')
|
|
468
|
+
}
|
|
469
|
+
ctx.warnings.sink.entries.push(entry)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** Advise that an emit site fell back to generic runtime dispatch (the slow,
|
|
473
|
+
* un-inferred path). Called from the actual emission point so it fires only when
|
|
474
|
+
* inference/optimization truly couldn't fold it — never a false positive on a
|
|
475
|
+
* case that vectorized/unrolled/slot-folded. `ctx.error.loc` is the current AST
|
|
476
|
+
* node's byte offset (kept up to date by the emit walk), giving line/column. */
|
|
477
|
+
export function warnDeopt(code, message) {
|
|
478
|
+
warn(code, message, { fn: ctx.func.current?.name }, ctx.error.loc)
|
|
479
|
+
}
|
|
480
|
+
|
|
328
481
|
/** Throw with source location context. */
|
|
329
|
-
export function err(msg) {
|
|
482
|
+
export function err(msg, cause) {
|
|
330
483
|
let detail = msg
|
|
331
484
|
|
|
332
485
|
if (ctx.error.loc != null && ctx.error.src) {
|
|
@@ -345,7 +498,10 @@ export function err(msg) {
|
|
|
345
498
|
detail += `\n current AST: ${formatErrorNode(ctx.error.node)}`
|
|
346
499
|
}
|
|
347
500
|
|
|
348
|
-
|
|
501
|
+
// Preserve the triggering error (if any) as the cause: when an internal jz bug
|
|
502
|
+
// is wrapped, the original stack — pointing at the actual codegen site — survives
|
|
503
|
+
// in the chain (`Error: … [cause]: …`) instead of being replaced by this frame.
|
|
504
|
+
const e = cause !== undefined ? new Error(detail, { cause }) : new Error(detail)
|
|
349
505
|
const stackLines = e.stack.split('\n')
|
|
350
506
|
const firstFrame = stackLines.findIndex(line => line.trimStart().startsWith('at '))
|
|
351
507
|
const frames = firstFrame >= 0 ? stackLines.slice(firstFrame) : stackLines.slice(1)
|