Skip to content

The knowledge graph

Between parsing and code generation, the compiler works on one representation: a knowledge graph, an enriched expression DAG. Name resolution, type checking, capability checking, reduction and lowering all read and extend it, and no phase re-reads source text. Debug and check builds emit it as <artifact>.resid-graph.cbor.

Each node has an id (deterministic: source order, then reduction order), a kind, a type, a knowledge state, a value when known, ordered operand edges, the effects it may perform, the capabilities it requires, its provenance (a source span, a provider result or a derivation), its doc comments, and a content hash over kind, type and operands.

State Meaning
known the value is fully determined at compile time
effect the node performs an effect; its order is fixed
residual the node depends on runtime values and is lowered
invalid the node carries a diagnostic; the program is rejected
Edge From → to
operand a node → its i-th dependency
def a reference → the binding, parameter or function it names
seq an effect → the effect before it
derive a result node → the node it replaced, labelled with the rule
lowered a node → the code range and runtime location emitted for it

Operand, def and seq edges are acyclic; recursion is a def edge to a function node.

Reduction adds nodes and never deletes one reachable from source. Each result has a derive edge to what it replaced, labelled with its rule (fold, known, beta, eval, select, unwrap, interpolate, concat, specialize, comptime, accumulate, rebuild). A node left residual records why: unknown(dep), effect(e), capability(c), annotated (rt), budget(kind), loop, whistle.

Invariants: every node in the residual program is a source node or reaches source through derive edges, and every source node is known, invalid, lowered, or replaced.

Lowering reads only the residual part and records, for each emitted code range, the node it lowers, and for each residual binding its runtime location. Debug builds carry this as DWARF (line entries at each statement’s span, variables named after their bindings). The binary records the graph’s content hash (resid_graph_hash), so a debugger can check that graph and binary match.

resid-why, resid-graph and resid-debug answer questions by walking the graph, not by re-analyzing source: what was reduced and by which rule, why something stayed residual, and, live, which node the program counter is in and which folded values it relies on.