Skip to content

How it works

Reflection.Emit needs a JIT, so ilrepl separates the Native AOT terminal frontend from a framework-dependent execution host.

ilreplNative AOT front-endcommand lineterminal UIbatch modetranscript and completionhost clientilrepl-hostframework-dependent, runs on your dotnettype and member resolutionstack simulationIL compilationthe sessionconsole captureJSON-RPCsocket
Point at a part of the diagram, or tab to it, to read what it does.

The frontend owns the editor, transcript, and acknowledged source workspace. An embedded opcode and command catalog makes the prompt editable before the host connects. Operand completion and analysis read immutable metadata snapshots without waiting for a running cell. Caret movement reuses analysis of the same source.

JSON-RPC travels directly between the frontend and host over a private Unix domain socket on Windows, Linux, and macOS. User console output streams separately from replies, so a long-running cell can print before it returns. Replies carry document and runtime identities to prevent stale results from replacing current ones.

Cells run on one persistent thread with an explicitly requested 8 MiB stack. Thread-static values, thread-local values, culture, and the thread name persist between cells. The operating system and runtime still determine usable stack space; unbounded recursion can overflow it.

On Unix, a separate lifetime supervisor launches and tracks execution process groups. It does not relay RPC or console output. If the supervisor exits, the frontend can start a replacement and adopt the existing groups without replacing a healthy host. Windows uses a job owned by the frontend. Losing the frontend stops its execution processes even when user code never returns.

Before entering user code, the host waits for the frontend to acknowledge the source checkpoint. Recovery reconstructs definitions and edits without replaying cells. See Restart and recovery.

a lineparsesimulateacceptreplayrunretthe next cell
Point at a part of the diagram, or tab to it, to read what it does.

Every line is validated against a replayable model of the cell: declared locals and arguments, the instructions, the labels, the open exception blocks, and the simulated stack. When the cell runs, the accepted lines are replayed against a fresh MethodBuilder, so generic parameters bind to the method that is emitted and the same cell can compile more than once. Compiled cells are collectible AssemblyBuilder types with one static Run method returning object; the epilogue boxes a value type left on the stack, and vararg cells get a standard-convention wrapper, because reflection cannot invoke a vararg method directly.

The editor analyzes the whole unsent document through a separate request. A fixed-point worklist merges incoming stacks and retains producer locations; runtime and symbolic operands share the transfer rules. Requests yield during large analyses and release their metadata leases when cancelled. Cached presentation facts let caret movement reuse the graph without retaining types. Replies carry document and session identities, so stale diagnostics cannot replace current ones.

Methods defined with .method and types defined with .class are not recreated on each cell run. Each is kept as a replayable model while its block is open and, when the block closes, written once with Mono.Cecil into a small assembly of its own and loaded into a collectible load context. A type is therefore one runtime type for the whole session, with its statics and its instances, and a saved assembly is produced by the same writer, so the file carries the metadata the session ran.

A session method’s callers bind to a stable trampoline; redefining the method with the same signature compiles a new body and points the trampoline at it, so existing callers, delegates included, see the new body. Redefining a type, or a method with a new signature, creates new identities and rebuilds the affected dependency group against the new definitions, including cycles; one refusal restores the session. Instances and delegates a cell still holds keep the previous definitions alive until they are released.

Cells and definitions skip the runtime’s access checks for session assemblies, so the REPL enforces ECMA-335 accessibility as you type. The names are unique per definition, so a load context never binds the wrong one, and .reset unloads what the session held.

.edit keeps the original method and copies its declaring type and required helpers. Changes pass through the same parser, stack checks, and writer as other methods. A successful revision updates dependent callers; a failed one leaves the previous version in place.

.compare runs each version in a fresh process. Both start with the same inputs. The report compares their arguments, results, exceptions, and console output. Previously run cells are not replayed, and ending a comparison leaves the session available.

Native inspection runs in a fresh CoreCLR worker with the terminal host’s runtime and architecture. It preserves captured method images and re-emits retained cells without replaying earlier execution. The report combines native listings with observed runtime compilation events. See Native code for compilation settings, execution boundaries, and comparison behavior.

Project Purpose
IlRepl.Protocol The transcript model, completion requests and pages, the caret classifier, and the JSON-RPC contract.
IlRepl.Engine Parsing, resolution, simulation, compilation, the session, and the REPL core.
IlRepl.Host Runs user IL and serves the engine over a direct socket connection.
IlRepl.Tui The Hex1b widgets: transcript, prompt with completion palette, status bar.
IlRepl The tool: command line, host client, batch mode.