IR program split design note
May 13, 2026 · View on GitHub
Updated: 2026-05-11
Purpose
This note narrows the next R3 step: split the root core.IRProgram into a small IR-owned model plus root-core execution metadata. The goal is a clean package boundary, not a wrapper layer.
Current problem
core.IRProgram mixes three concerns:
- IR model — bytecode, constants, slot counts, opcode analysis, arity subprogram shape.
- Compiler/runtime captures —
bindingKey, capturedObjectvalues, capture slot indices, self-call metadata. - Execution caches and helpers — typed/boxed failure bits, native helper functions,
FnExprreferences, trace naming, escape analysis.
That prevents moving the program model into core/ir without exporting broad root-core internals.
Proposed split
core/ir.Program
Owns the package-neutral IR representation:
type Program struct {
Code []byte
NumSlots int
ConstantsLen int
CaptureSlotIdxs []int
CaptureSlotSet []bool
HasSelf bool
FloatConsts []float64
ArityPrograms map[int]*Program
VariadicProgram *Program
VariadicMinArgs int
Analysis *Analysis
}
Notes:
ConstantsLencan be enough for diagnostics initially; moving actual[]Objectconstants waits on object boundaries.Analysisshould be thecore/irshape-analysis type, not rootcore.IRAnalysis.- Fields should be exported only if root-core callers need direct access during migration; prefer constructor/accessor helpers once usage stabilizes.
Root core.IRProgram
Temporarily remains the executable envelope:
type IRProgram struct {
model *ir.Program
constants []Object
captureKeys []bindingKey
captureSlots []Object
escapeInfo *EscapeInfo
typedFailed bool
execFailed bool
memNthFailed bool
nativeHelper nativeF64Fn
nativeHelper2 nativeF64Fn2
nativeChecked bool
arityPrograms map[int]*IRProgram
variadicProg *IRProgram
fnExprs []*FnExpr
traceName string
}
This lets the compiler/executor continue to use root-core object/runtime details while diagnostics, profiling, export shape, and WASM eligibility start reading the neutral model. Diagnostics and exported bytecode/slot accessors already do this; profile and WASM lowering still need migration.
Migration sequence
- Add
core/ir.Programand conversion/accessor helpers. Done: initial neutral model exists incore/ir/model.go. - Populate
IRProgram.modelduring compilation while leaving old fields in place. Done: root executable envelopes now refresh a neutral model. - Move analysis fields/helpers to the internal model. Started:
AnalyzeIRProgramwrites analysis back into the neutral model. - Update diagnostics/export/profile/WASM eligibility to read the model. Done for diagnostics, exported bytecode/slot accessors, native helper eligibility/lowering, and pure/imported/memory WASM eligibility/lowering. Profile paths are opcode-stream based and do not own program shape. Executor and escape-analysis paths stay on the root executable envelope until runtime/object execution metadata is split.
- Remove duplicated root fields once no callers use them.
- Only then consider moving compiler or executor pieces.
Guardrails
- Keep
go test ./core/internal/...inmake docs-check. - Keep benchmark correctness tests before performance work.
- Do not export
Object,FnExpr,bindingKey, or native helper internals just to move files. - Do not add long-term compatibility wrappers; root
IRProgramis a temporary executable envelope.