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/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, 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,13 +119,27 @@ 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() {
|
|
@@ -139,7 +161,8 @@ export function resolveIncludes() {
|
|
|
139
161
|
}
|
|
140
162
|
|
|
141
163
|
/** Reset all compilation state. Called once per jz() invocation. */
|
|
142
|
-
export function reset(proto, globals) {
|
|
164
|
+
export function reset(proto, globals, bridge) {
|
|
165
|
+
ctx.bridge = bridge
|
|
143
166
|
ctx.core = {
|
|
144
167
|
emit: derive(proto),
|
|
145
168
|
stdlib: {},
|
|
@@ -152,6 +175,11 @@ export function reset(proto, globals) {
|
|
|
152
175
|
// Drained at module-assembly time into `(import "wasm:js-string" "name" …)`
|
|
153
176
|
// nodes; host wires JS-side polyfills via interop's
|
|
154
177
|
// env builder for engines without builtin support.
|
|
178
|
+
hostGlobals: new Set(), // host globals (globalThis/process/WebAssembly/…) referenced as
|
|
179
|
+
// values. Recorded by emit on first use; drained into
|
|
180
|
+
// `(import "env" "name" (global $name i64))` at assembly. Same
|
|
181
|
+
// usage-gated pattern as jsstring — emit records, assembly owns
|
|
182
|
+
// the ctx.module.imports write.
|
|
155
183
|
}
|
|
156
184
|
|
|
157
185
|
|
|
@@ -159,6 +187,8 @@ export function reset(proto, globals) {
|
|
|
159
187
|
imports: [],
|
|
160
188
|
modules: {},
|
|
161
189
|
importSources: null,
|
|
190
|
+
importAsts: null, // self-host: pre-parsed [specifier, ast] pairs (the kernel can't parse).
|
|
191
|
+
// Consulted by prepareModule before falling back to ctx.transform.parse(source).
|
|
162
192
|
hostImports: null,
|
|
163
193
|
hostImportValTypes: new Map(),
|
|
164
194
|
resolvedModules: new Map(),
|
|
@@ -170,7 +200,7 @@ export function reset(proto, globals) {
|
|
|
170
200
|
|
|
171
201
|
ctx.scope = {
|
|
172
202
|
chain: derive(globals),
|
|
173
|
-
globals: new Map(),
|
|
203
|
+
globals: new Map(), // name → { type, mut, init, export } records (see declGlobal)
|
|
174
204
|
userGlobals: new Set(),
|
|
175
205
|
globalTypes: new Map(),
|
|
176
206
|
globalValTypes: null,
|
|
@@ -190,6 +220,7 @@ export function reset(proto, globals) {
|
|
|
190
220
|
localReps: null,
|
|
191
221
|
refinements: new Map(), // flow-sensitive: name → {val?: VAL.*, notString?: true} inside a type-guarded branch
|
|
192
222
|
boxed: new Map(),
|
|
223
|
+
cellTypes: new Set(), // boxed vars whose CELL stores raw i32 (closure-capture narrowing)
|
|
193
224
|
stack: [],
|
|
194
225
|
uniq: 0,
|
|
195
226
|
inTry: false,
|
|
@@ -201,6 +232,10 @@ export function reset(proto, globals) {
|
|
|
201
232
|
// by the pass owners so re-entrant analyzeBody calls don't clobber each other.
|
|
202
233
|
localValTypesOverlay: null,
|
|
203
234
|
localTypedElemsOverlay: null,
|
|
235
|
+
_ccBody: null, // memo key: body node last scanned by inBoundsCharCodeAt (src/type.js)
|
|
236
|
+
ccInBounds: null, // memo value: Set of in-bounds charCodeAt callee nodes for _ccBody
|
|
237
|
+
_aiBody: null, // memo key: body node last scanned by inBoundsArrIdx (src/type.js)
|
|
238
|
+
aiInBounds: null, // memo value: Set of in-bounds "recv\0idx" array-read keys for _aiBody
|
|
204
239
|
}
|
|
205
240
|
|
|
206
241
|
ctx.types = {
|
|
@@ -212,6 +247,13 @@ export function reset(proto, globals) {
|
|
|
212
247
|
ctx.schema = {
|
|
213
248
|
list: [],
|
|
214
249
|
vars: new Map(),
|
|
250
|
+
poisoned: new Set(), // names whose assignments disagree on shape (literal +
|
|
251
|
+
// non-literal, or two different literals). A poisoned
|
|
252
|
+
// name never (re)binds in schema.vars: fixed-slot reads
|
|
253
|
+
// against ONE literal's layout would misread the other
|
|
254
|
+
// sources' objects. Populated by prepare's `=` handler;
|
|
255
|
+
// end-of-prepare state is what compile reads, so the
|
|
256
|
+
// conflict is order-insensitive.
|
|
215
257
|
register: null,
|
|
216
258
|
find: null,
|
|
217
259
|
targetStack: [],
|
|
@@ -246,6 +288,14 @@ export function reset(proto, globals) {
|
|
|
246
288
|
bodies: null,
|
|
247
289
|
make: null,
|
|
248
290
|
call: null,
|
|
291
|
+
numericReturn: null, // Set<closureBodyName> proven to return a plain number — lets
|
|
292
|
+
// callers skip the __to_num result coercion (function.js seeds it).
|
|
293
|
+
paramTypes: null, // Map<closureBodyName, bool[]> — per-param "every direct call site
|
|
294
|
+
// passed a number" lattice; emitClosureBody marks such params
|
|
295
|
+
// VAL.NUMBER so their body uses skip __to_num (tryDirectClosureCall seeds).
|
|
296
|
+
minArgc: null, // Map<closureBodyName, number> — fewest args any direct call passed.
|
|
297
|
+
// A slot at index ≥ minArgc is omitted by some call (→ may be undefined),
|
|
298
|
+
// so it must NOT be typed NUMBER, else `x === undefined` mis-folds to false.
|
|
249
299
|
}
|
|
250
300
|
|
|
251
301
|
ctx.runtime = {
|
|
@@ -258,6 +308,9 @@ export function reset(proto, globals) {
|
|
|
258
308
|
throws: false,
|
|
259
309
|
userThrows: false, // user wrote `throw`/`try`/`catch`/`finally` — keep runtime declared
|
|
260
310
|
// even when all throws are dead-code-eliminated (JS-side ABI contract).
|
|
311
|
+
staticPtrSlots: null, // [byteOffset] data-segment slots holding NaN-boxed ptrs (host relocates); lazy-init in ir.js
|
|
312
|
+
staticDataLen: 0, // byte length of the address-0 static string block (seeded by module/number staticStr)
|
|
313
|
+
typeofStrs: null, // [str] interned typeof result strings; lazy-init in module/core `typeof`
|
|
261
314
|
}
|
|
262
315
|
|
|
263
316
|
ctx.memory = {
|
|
@@ -294,6 +347,9 @@ export function reset(proto, globals) {
|
|
|
294
347
|
// Shape: { abi, functions: { [name]: { exported, params, results, ptrKind?, locals, callerReps } }, schemas }.
|
|
295
348
|
ctx.inspect = null
|
|
296
349
|
|
|
350
|
+
// Advisory sink. Populated when compile() receives opts.warnings.
|
|
351
|
+
ctx.warnings = null
|
|
352
|
+
|
|
297
353
|
// Feature flags: capabilities the compiled module may exercise at runtime.
|
|
298
354
|
// Set true by producer sites (import points, auto-imports, dynamic call sites).
|
|
299
355
|
// Read by stdlib template factories and deps graph at resolveIncludes() time to
|
|
@@ -309,7 +365,7 @@ export function reset(proto, globals) {
|
|
|
309
365
|
// (b) a capability needs an opt-in A/B switch against the default path
|
|
310
366
|
// (SSO is the planned first user — default string-literal emission
|
|
311
367
|
// currently forces SSO for ≤4 ASCII chars at string.js:49)
|
|
312
|
-
ctx.abi =
|
|
368
|
+
ctx.abi = makeAbi()
|
|
313
369
|
|
|
314
370
|
// Only flags actually read by codegen live here. Hash/regex/json substrates
|
|
315
371
|
// are pulled organically by inc(__*) — no flag mediates them, so no flag exists.
|
|
@@ -325,8 +381,67 @@ export function reset(proto, globals) {
|
|
|
325
381
|
}
|
|
326
382
|
}
|
|
327
383
|
|
|
384
|
+
/** Debug-mode invariant checks. Encodes the writers/readers contract documented
|
|
385
|
+
* above as runtime asserts so a bad refactor surfaces at the phase boundary
|
|
386
|
+
* instead of as a distant nondeterministic failure. No-op unless
|
|
387
|
+
* `JZ_DEBUG_INVARIANTS=1`; designed so phase-boundary callers can sprinkle
|
|
388
|
+
* `assertCtxInvariants('post-prepare')` without runtime cost in production.
|
|
389
|
+
*
|
|
390
|
+
* Phases checked:
|
|
391
|
+
* - `post-reset` : every sub-context exists; Maps/Sets initialized.
|
|
392
|
+
* - `post-prepare` : module + scope populated; func.list possibly empty.
|
|
393
|
+
* - `pre-emit` : func.current set; locals Map present; rep maps live.
|
|
394
|
+
* - `post-compile` : no transient temps leaked (func.uniq stable across calls). */
|
|
395
|
+
const DBG_INVARIANTS = typeof process !== 'undefined' && process.env?.JZ_DEBUG_INVARIANTS === '1'
|
|
396
|
+
export function assertCtxInvariants(phase) {
|
|
397
|
+
if (!DBG_INVARIANTS) return
|
|
398
|
+
const fail = msg => { throw new Error(`[ctx invariant] ${phase}: ${msg}`) }
|
|
399
|
+
const must = (cond, msg) => { if (!cond) fail(msg) }
|
|
400
|
+
|
|
401
|
+
must(ctx.core && ctx.module && ctx.scope && ctx.func && ctx.transform && ctx.features,
|
|
402
|
+
'sub-contexts present')
|
|
403
|
+
if (phase !== 'pre-reset') {
|
|
404
|
+
must(ctx.core.includes instanceof Set, 'core.includes is Set')
|
|
405
|
+
must(ctx.core.emit && typeof ctx.core.emit === 'object', 'core.emit table')
|
|
406
|
+
must(Array.isArray(ctx.func.list), 'func.list array')
|
|
407
|
+
must(ctx.func.locals instanceof Map, 'func.locals Map')
|
|
408
|
+
must(ctx.func.refinements instanceof Map, 'func.refinements Map')
|
|
409
|
+
}
|
|
410
|
+
if (phase === 'pre-emit') {
|
|
411
|
+
must(ctx.func.current, 'func.current set before emit')
|
|
412
|
+
must(ctx.func.locals.size != null, 'locals open for writes')
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Enable compile-time advisories. Pass `opts.warnings` (mirrors `opts.profile`). */
|
|
417
|
+
export function initWarnings(sink) {
|
|
418
|
+
if (sink == null) {
|
|
419
|
+
ctx.warnings = null
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
sink.entries ||= []
|
|
423
|
+
ctx.warnings = { sink, seen: new Set() }
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** Record one advisory; `loc` is a source byte offset used only to derive
|
|
427
|
+
* line/column — it is never persisted on the entry. No-op unless
|
|
428
|
+
* `initWarnings` wired a sink. */
|
|
429
|
+
export function warn(code, message, meta = {}, loc = null) {
|
|
430
|
+
if (!ctx.warnings) return
|
|
431
|
+
const key = `${code}:${meta.fn || ''}:${meta.line || ''}`
|
|
432
|
+
if (ctx.warnings.seen.has(key)) return
|
|
433
|
+
ctx.warnings.seen.add(key)
|
|
434
|
+
const entry = { code, message, ...meta }
|
|
435
|
+
if (loc != null && ctx.error.src) {
|
|
436
|
+
const before = ctx.error.src.slice(0, loc)
|
|
437
|
+
entry.line = before.split('\n').length
|
|
438
|
+
entry.column = loc - before.lastIndexOf('\n')
|
|
439
|
+
}
|
|
440
|
+
ctx.warnings.sink.entries.push(entry)
|
|
441
|
+
}
|
|
442
|
+
|
|
328
443
|
/** Throw with source location context. */
|
|
329
|
-
export function err(msg) {
|
|
444
|
+
export function err(msg, cause) {
|
|
330
445
|
let detail = msg
|
|
331
446
|
|
|
332
447
|
if (ctx.error.loc != null && ctx.error.src) {
|
|
@@ -345,7 +460,10 @@ export function err(msg) {
|
|
|
345
460
|
detail += `\n current AST: ${formatErrorNode(ctx.error.node)}`
|
|
346
461
|
}
|
|
347
462
|
|
|
348
|
-
|
|
463
|
+
// Preserve the triggering error (if any) as the cause: when an internal jz bug
|
|
464
|
+
// is wrapped, the original stack — pointing at the actual codegen site — survives
|
|
465
|
+
// in the chain (`Error: … [cause]: …`) instead of being replaced by this frame.
|
|
466
|
+
const e = cause !== undefined ? new Error(detail, { cause }) : new Error(detail)
|
|
349
467
|
const stackLines = e.stack.split('\n')
|
|
350
468
|
const firstFrame = stackLines.findIndex(line => line.trimStart().startsWith('at '))
|
|
351
469
|
const frames = firstFrame >= 0 ? stackLines.slice(firstFrame) : stackLines.slice(1)
|