Follow-up to #6 / #115, surfaced while sizing a state-graph panel for the demo's TM-1 and PM-1 pages (the graph panel was left hidden for the toolchain engines in machines-demo#136; the demo-side issue is linked below).
Both compilers already have what a graph panel needs: --emit-ir writes a versioned JSON document (docs/formats.md, "IR JSON"), and IrProgram::to_mermaid() behind tmt ir graph / pmt ir graph renders it. Neither reaches the browser: Toolchain exposes build / check / format / stdlibSource and the tape-block codec, and nothing returns the IR or a diagram.
Ask
Toolchain.ir(lang, source, opts): string — the same document --emit-ir writes (IR_VERSION = 4 for .pmc, TM_IR_VERSION = 3 for .tmc), for the source languages only; pma / tma have no IR and should throw the same way an unknown lang does. opts carries the optLevel of BuildOptions plus a stage: 'lowered' | 'final' matching --emit-ir=STAGE (after:<pass> can stay CLI-only). The distinction matters: lowered is the graph straight from the source — one state per state block, one row per rule — and is what a panel should show by default; final is what codegen received, which at -O1 carries tail_call / branch shapes the source never had. At -O0 the two are byte-identical (the optimizer contract in docs/tmt/optimizer.md), and the demo builds at -O0 today, so the default can be lowered without a visible difference until the demo exposes optimization levels.
Toolchain.irGraph(lang, source, opts): { name: string; mermaid: string }[] — one entry per world / function, the text ir graph prints, split at the %% name headers; same opts as ir. This is the cheap first step for a consumer; the JSON form is what a consumer needs once it wants to draw its own labels (see below).
Optional, if it is cheap: the glyph tables. The IR is index-only by design; the demo can substitute glyphs itself from Program.tapes() (it already has them for the tape view), so this is not a blocker, and the pow2 rendering linked below was produced exactly that way.
What the IR documents look like at demo scale
Measured on v0.5.0 with the demo's bundled pow2.tmc: one world, 10 states, 19 rows — the raw ir graph output is readable as is, and with glyphs substituted every arrow label is the source rule verbatim (copyExp reads [1,*,*] w[␣,1,-] m[>,>,.]). Rendered: https://claude.ai/code/artifact/b6b863e2-cf9a-41d4-be4a-9109cd27453f
At docs/examples/rpnwide scale the raw form is not drawable (4,711 arrows over 14 tapes); that is a renderer question, tracked separately as the row-merging issue linked below.
Later, for a live highlight
The demo highlights the paused state on the JS pages. For TM-1 the IR's per-state and per-rule line fields plus Program.lineOf(addr) give a line-ownership mapping that is good enough for a first version. A precise mapping would be the map sidecar carrying the IR state id per address; worth a separate issue once the panel exists and the approximation shows its gaps.
Links
Follow-up to #6 / #115, surfaced while sizing a state-graph panel for the demo's TM-1 and PM-1 pages (the graph panel was left hidden for the toolchain engines in machines-demo#136; the demo-side issue is linked below).
Both compilers already have what a graph panel needs:
--emit-irwrites a versioned JSON document (docs/formats.md, "IR JSON"), andIrProgram::to_mermaid()behindtmt ir graph/pmt ir graphrenders it. Neither reaches the browser:Toolchainexposesbuild/check/format/stdlibSourceand the tape-block codec, and nothing returns the IR or a diagram.Ask
Toolchain.ir(lang, source, opts): string— the same document--emit-irwrites (IR_VERSION = 4for.pmc,TM_IR_VERSION = 3for.tmc), for the source languages only;pma/tmahave no IR and should throw the same way an unknownlangdoes.optscarries theoptLevelofBuildOptionsplus astage: 'lowered' | 'final'matching--emit-ir=STAGE(after:<pass>can stay CLI-only). The distinction matters:loweredis the graph straight from the source — one state perstateblock, one row per rule — and is what a panel should show by default;finalis what codegen received, which at-O1carriestail_call/branchshapes the source never had. At-O0the two are byte-identical (the optimizer contract indocs/tmt/optimizer.md), and the demo builds at-O0today, so the default can beloweredwithout a visible difference until the demo exposes optimization levels.Toolchain.irGraph(lang, source, opts): { name: string; mermaid: string }[]— one entry per world / function, the textir graphprints, split at the%% nameheaders; sameoptsasir. This is the cheap first step for a consumer; the JSON form is what a consumer needs once it wants to draw its own labels (see below).Optional, if it is cheap: the glyph tables. The IR is index-only by design; the demo can substitute glyphs itself from
Program.tapes()(it already has them for the tape view), so this is not a blocker, and the pow2 rendering linked below was produced exactly that way.What the IR documents look like at demo scale
Measured on v0.5.0 with the demo's bundled
pow2.tmc: one world, 10 states, 19 rows — the rawir graphoutput is readable as is, and with glyphs substituted every arrow label is the source rule verbatim (copyExpreads[1,*,*] w[␣,1,-] m[>,>,.]). Rendered: https://claude.ai/code/artifact/b6b863e2-cf9a-41d4-be4a-9109cd27453fAt
docs/examples/rpnwidescale the raw form is not drawable (4,711 arrows over 14 tapes); that is a renderer question, tracked separately as the row-merging issue linked below.Later, for a live highlight
The demo highlights the paused state on the JS pages. For TM-1 the IR's per-state and per-rule
linefields plusProgram.lineOf(addr)give a line-ownership mapping that is good enough for a first version. A precise mapping would be the map sidecar carrying the IR state id per address; worth a separate issue once the panel exists and the approximation shows its gaps.Links