Plan: Self-Hosted Typechecker (typecheck.tri) + Prove End-to-End
Status: IN PROGRESS
Context
Lexer and parser are self-hosted (std/compiler/lexer.tri 824 LOC, std/compiler/parser.tri 2,723 LOC). Next self-hosting piece: typechecker. This is needed regardless of backend (TASM or CORE). Secondary goal: prove the existing lexer/parser benches end-to-end via trisha.
Architecture
The Rust typechecker is ~2,843 LOC across 8 files. The type system is simple:
- 9 types: Field, XField, Bool, U32, Digest, Array, Tuple, Struct, Unit
- No polymorphism, no subtyping, no inference (almost)
- Two-pass: (1) register all signatures/structs/consts, (2) check function bodies
- Scopes: push/pop stack of variable maps
- ~30 builtins registered at startup
Same pattern as parser: single bounded loop, explicit state machine, data in RAM.
Data Representation
All data lives in RAM (same as lexer/parser). Key structures:
Type encoding (1 word tag + up to 3 words payload)
TY_FIELD=1, TY_XFIELD=2, TY_BOOL=3, TY_U32=4, TY_DIGEST=5
TY_ARRAY=6, TY_TUPLE=7, TY_STRUCT=8, TY_UNIT=9
- Scalars: just tag (1 word)
- XField/Digest: tag + width (2 words)
- Array: tag + inner_type_index + length (3 words) — inner_type_index points to type pool
- Tuple: tag + count + start_index (3 words) — sub-types stored in type pool
- Struct: tag + struct_table_index (2 words)
Type pool
Fixed-size array in RAM. Each resolved type gets an index. Avoids recursive structures.
Symbol tables (flat arrays in RAM)
- Functions: (name_hash, param_count, param_types_start, return_type_index) per entry
- Variables (scope stack): (name_hash, type_index, is_mutable, scope_depth)
- Structs: (name_hash, field_count, fields_start) — fields = (name_hash, type_index, is_pub)
- Constants: (name_hash, type_index, value)
Name matching: hash-based (no string comparison — only token position hashes from parser AST nodes).
Memory Layout
state_base +0..+31 Typechecker state (32 words)
type_pool +0..+N*4 Pool of resolved types
fn_table +0..+M*4 Function signature table
var_stack +0..+V*4 Variable scope stack
struct_table +0..+S*8 Struct definitions
const_table +0..+C*3 Constants
err_base +0..+E*3 Errors (severity, span_start, span_end)
diag_count count Number of diagnostics
Implementation Steps
Step 1: Skeleton + type representation (~400 LOC)
File: std/compiler/typecheck.tri
- Module header, imports (vm.core.field, vm.core.convert, vm.io.mem, std.compiler.parser)
- Type tag constants (TY_FIELD through TY_UNIT)
- Type pool:
alloc_type(),store_type(),load_type_tag(),type_eq() - Type width computation:
type_width(type_index) -> Field - Memory layout constants and state accessors
Step 2: Symbol tables (~500 LOC)
- Function table:
register_fn(),lookup_fn(),fn_param_type(),fn_return_type() - Variable scope:
push_scope(),pop_scope(),define_var(),lookup_var() - Struct table:
register_struct(),lookup_struct(),struct_field_type() - Constant table:
register_const(),lookup_const() - Name hashing: use token position or AST node field as identity key
Step 3: Builtins registration (~300 LOC)
register_builtins()— register ~30 builtin functions- Parameterized by digest_width, hash_rate, xfield_width (passed in state)
- Groups: I/O, assertions, field ops, u32 ops, hash, merkle, RAM, conversion, xfield
Step 4: Type resolution (~200 LOC)
resolve_type(ast_node_index) -> type_index— walk AST type nodes → type pool entries- Handle: named types (Field/Bool/U32/etc.), array types, tuple types, struct references
resolve_array_size()— constant expression evaluation
Step 5: Expression checking (~600 LOC)
check_expr(ast_node_index) -> type_index- Literal → Field/Bool/U32
- Var → scope lookup
- BinOp → operand checking + result type
- Call → function lookup, arg count/type checking, return type
- FieldAccess → struct field lookup
- Index → array/tuple element type
- StructInit → field matching
- ArrayInit / Tuple → element checking
Step 6: Statement checking (~500 LOC)
check_stmt(ast_node_index)- Let (name + tuple destructure), Assign, If, For, Return
- Match (with exhaustiveness: wildcard / bool coverage)
- Assert/AssertEq, Asm (skip body), Reveal/Seal
- Expression statements
Step 7: Top-level driver (~400 LOC)
check_file()— main entry point, two-pass- Pass 1: iterate AST items, register structs/fns/consts/events
- Pass 2: iterate again, check function bodies
- Recursion detection (call graph DFS — iterative, same as Rust impl)
- Unused import detection
- Error collection and reporting
Step 8: Bench harness (~100 LOC)
File: benches/harnesses/std/compiler/typecheck.tri
- Program that loads a known AST (from parser output), runs typecheck, asserts no errors
- Reference implementation in Rust for ground truth
Estimated Size
~3,000 LOC for typecheck.tri (comparable to parser.tri at 2,723 LOC).
Prove End-to-End (Secondary)
After typecheck.tri, prove existing benches via trisha:
trident build benches/harnesses/std/compiler/lexer.tri→ get .tasmtrisha run lexer_bench→ execute, verify correctnesstrisha prove lexer_bench→ generate STARK prooftrisha verify lexer_bench→ verify proof- Repeat for parser_bench and pipeline_bench
- Record cycle counts and proving times