Compiler & VM Architecture Reference
This document details Tinsel's internal compilation pipeline, 24-byte tagged Value representation, zero-sort structural hashing, in-place prototype dispatch, multi-pass optimizer, and bytecode instruction set.
1. Compilation & Execution Pipeline
+---------------------------------------------------------------+
| Tinsel Source (.tin) |
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| Lexer (internal/lexer) |
| - Token stream with line & column span tracking |
| - Template pattern strings & f"..." string interpolation |
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| Pratt Parser (internal/parser) |
| - Top-down operator precedence (binding powers 0..140) |
| - Expression & Pattern AST construction |
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| Bytecode Compiler (internal/vm) |
| - AST constant folding & comprehension reduction fusion |
| - Flat bytecode chunks, lexical locals & closure upvalues |
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| Multi-Pass Bytecode Optimizer (internal/vm) |
| - Jump threading, dead-code & loop-accumulator elision |
| - Peephole superinstruction fusion & constant pool pruning |
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| Stack-Based Virtual Machine (internal/vm) |
| - 24-byte tagged Value stack & inline 32-bit Vec2/Range |
| - In-place prototype (__proto__) CallFrame method dispatch |
+---------------------------------------------------------------+
2. The 24-Byte Tagged Value Memory Model
To avoid heap-boxing primitives in Go's any (interface{}) during tight loops and graph searches, every runtime value is represented by a uniform 24-byte struct (internal/value/value.go):
type Value struct {
Type ValueType // 8-bit discriminator tag (ValInt, ValFloat, ValVec2, ValRange, ...)
Num int64 // Inline payload: int64, bool (0/1), rune, IEEE-754 float64 bits, or packed 2x32-bit Vec2/Range
Obj any // Heap pointer only when needed (string, *Array, *Map, *Closure, *big.Int, 64-bit Vec2/Range)
}
Zero-Allocation Inline Packing
- Scalars (
ValInt,ValBool,ValChar,ValFloat): Stored entirely insideNumwithObj == nil. Float values bit-cast their 64-bit IEEE-754 representation intoNumviamath.Float64bits. - Packed 32-bit
Vec2andRange(Obj == nil): Whenever both (X, Y) coordinates of avec2or both [start, end) bounds of arangefit within signed 32-bit integers (-2,147,483,648to2,147,483,647), they are packed into the high and low 32 bits ofNumwithObj == nil. Creating, adding, comparing, and hashing 2D coordinates inbfs/dijkstraruns with zero heap allocations. Coordinates exceeding 32-bit bounds transparently fall back to heap-allocated*Vec2or*Rangestructs inObj.
3. Zero-Sort Structural Hashing & Prototype Dispatch
Comparable Map & Struct Keys (Value.HashKey())
Tinsel allows any composite value (arrays, tuples, vectors, ranges, maps, and user-defined struct instances) to be used as a map or set key:
- Primitives, packed
Vec2, and packedRangereturn fixed-size comparable Go struct keys with zero allocations. - User-defined
structdeclarations (struct Name(f1, f2, ...)) record their ordered field names in__proto__.__fields__. When hashing astructinstance with 0 to 4 fields,Value.HashKey()constructs a comparableStructHashKey0..4struct directly in declaration order—bypassing map key sorting and string serialization entirely.
In-Place Prototype CallFrame Dispatch (__proto__)
Standard library composite types (grid, rangeset, set, deque, Iter) and user-defined struct types are implemented in pure Tinsel (stdlib/*.tin) and attach a __proto__ table.
When OP_METHOD_CALL invokes a Tinsel method closure on a struct, prototype-backed map, or built-in type (array, string, int, option), the VM pushes a new CallFrame directly onto vm.frames in-place rather than re-entering a recursive Go interpreter loop.
4. Compiler & Multi-Pass Bytecode Optimizer
When optimization is enabled (the default for tinsel run and tinsel compile), Tinsel applies both AST-level and bytecode-level optimizations (internal/vm/optimizer.go):
- AST Constant Folding & Immutable
letPropagation:- Evaluates constant arithmetic, bitwise, string, character, range (
1..=10),<->Manhattan distance, and constantvec2/vec3expressions at compile time. - Propagates immutable
letscalar constants into downstream expressions and eliminates untakenifbranches when conditions are compile-time constants.
- Evaluates constant arithmetic, bitwise, string, character, range (
- Comprehension Reduction Fusion:
- Fuses
(for x in iterable { ... yield expr }).sum()(as well as.count(),.min(),.max(), and.product()) into a single scalar accumulator register without allocating an intermediate array. - Elides the hidden
OP_BUILD_ARRAY 0accumulator forforloops that contain noyieldstatement, and skips element indexing on wildcard loops (for _ in a..b).
- Fuses
- Peephole Superinstruction Fusion & Jump Threading:
- Fuses common multi-instruction sequences into single-dispatch superinstructions (
OP_INC_LOCAL,OP_LESS_LOCAL_JMP_F,OP_GET_LOCAL_INDEX, etc.). - Threads jump-to-jump chains and strips unreachable instructions after
return/break/continue.
- Fuses common multi-instruction sequences into single-dispatch superinstructions (
- Dead Constant Pool Pruning:
- Compacts
Chunk.Constantsafter peephole optimization by removing unreferenced constants and remapping surviving constant indices.
- Compacts
5. Bytecode Instruction Set Reference
Inspect compiled bytecode for any file via ./bin/tinsel compile <file.tin> (optimized) or ./bin/tinsel compile --no-opt <file.tin> (unoptimized). All 16-bit operands (u16) use big-endian encoding.
5.1 Constants, Literals & Stack Manipulation
| Opcode | Bytes | Operands | Stack Effect & Semantics |
|---|---|---|---|
OP_CONSTANT |
3 | constIdx: u16 |
Pushes chunk.Constants[constIdx]. |
OP_NIL |
1 | — | Pushes nil. |
OP_TRUE / OP_FALSE |
1 | — | Pushes true or false. |
OP_POP |
1 | — | Pops and discards top of stack. |
OP_DUP |
1 | — | Duplicates top of stack. |
5.2 Local Variables, Globals & Lexical Upvalues
| Opcode | Bytes | Operands | Stack Effect & Semantics |
|---|---|---|---|
OP_GET_LOCAL |
3 | slot: u16 |
Pushes stack[frame.Base + slot]. |
OP_SET_LOCAL |
3 | slot: u16 |
Stores top of stack into stack[frame.Base + slot] (leaves value on stack). |
OP_GET_GLOBAL |
3 | nameIdx: u16 |
Pushes module global, prelude function, or built-in named Constants[nameIdx]. |
OP_SET_GLOBAL |
3 | nameIdx: u16 |
Stores top of stack into module global Constants[nameIdx]. |
OP_GET_UPVALUE |
3 | upvalIdx: u16 |
Pushes captured lexical upvalue closure.Upvalues[upvalIdx]. |
OP_SET_UPVALUE |
3 | upvalIdx: u16 |
Stores top of stack into closure.Upvalues[upvalIdx]. |
5.3 Arithmetic, Bitwise, Relational & Spatial Operators
| Opcode | Bytes | Operands | Stack Effect & Semantics |
|---|---|---|---|
OP_ADD / OP_SUB |
1 | — | Pops b, a; pushes a + b or a - b (int, bigint, float, string, char, vec2, vec3, array). |
OP_MUL / OP_DIV / OP_MOD |
1 | — | Pops b, a; pushes a * b, a / b, or a % b. |
OP_NEGATE / OP_NOT |
1 | — | Unary -x or boolean negation !IsTruthy(x). |
OP_EQUAL / OP_NOT_EQUAL |
1 | — | Pops b, a; pushes structural equality a == b or a != b. |
OP_LESS / OP_LESS_EQUAL / OP_GREATER / OP_GREATER_EQUAL |
1 | — | Pops b, a; pushes relational comparison result. |
OP_IN |
1 | — | Pops container, elem; pushes membership check elem in container (invokes contains hook on prototypes). |
OP_MANHATTAN_DIST |
1 | — | Pops b, a; pushes a <-> b (vec2, vec3, int, float). |
OP_BIT_AND / OP_BIT_OR / OP_BIT_XOR / OP_BIT_NOT |
1 | — | Bitwise &, |, ^, and unary ~. |
OP_SHIFT_LEFT / OP_SHIFT_RIGHT |
1 | — | Bitwise a << b and arithmetic a >> b. |
OP_OPT_UNWRAP / OP_OPT_IS_SOME |
1 | — | Postfix opt! (unwraps Some(v) or panics) and opt? (true if Some, false if None). |
5.4 Collections, Structs, Indexing & Fields
| Opcode | Bytes | Operands | Stack Effect & Semantics |
|---|---|---|---|
OP_MAKE_VEC2 / OP_MAKE_VEC3 |
1 | — | Pops y, x (or z, y, x) and pushes vec2 / vec3. |
OP_MAKE_RANGE |
1 | — | Pops end, start and pushes half-open start..end. |
OP_BUILD_ARRAY |
3 | count: u16 |
Pops count items and pushes [...]. |
OP_BUILD_MAP |
3 | pairs: u16 |
Pops pairs key-value pairs and pushes {...}. |
OP_ARRAY_SLICE |
3 | flags: u16 |
Slices array or string with optional start/end/inclusive flags. |
OP_INDEX |
1 | — | Pops index, receiver; pushes receiver[index] (or calls index_get). |
OP_INDEX_SET |
1 | — | Pops val, index, receiver; sets receiver[index] = val (or calls index_set) and pushes val. |
OP_GET_FIELD / OP_SET_FIELD |
3 | nameIdx: u16 |
Gets or sets property Constants[nameIdx] on receiver. |
5.5 Control Flow, Calls, Closures & Modules
| Opcode | Bytes | Operands | Stack Effect & Semantics |
|---|---|---|---|
OP_JUMP |
3 | target: u16 |
Unconditional jump (frame.IP = target). |
OP_JUMP_IF_FALSE / OP_JUMP_IF_TRUE |
3 | target: u16 |
Pops condition; jumps to target if falsy / truthy. |
OP_CALL |
3 | argc: u16 |
Calls callable at stack[top - 1 - argc] with argc arguments. |
OP_METHOD_CALL |
5 | nameIdx: u16, argc: u16 |
Dispatches method Constants[nameIdx] on receiver with argc arguments. |
OP_CLOSURE |
3 + 3N | fnIdx: u16, upvals... |
Instantiates closure from Constants[fnIdx] capturing N (isLocal: u8, idx: u16) upvalues. |
OP_RETURN |
1 | — | Closes frame upvalues, pops frame, and pushes return value onto caller stack. |
OP_YIELD |
1 | — | Pops value and appends to enclosing for comprehension accumulator. |
OP_MATCH_STRING |
3 | patIdx: u16 |
Matches string against template pattern Constants[patIdx]. |
OP_PARSE_INTS |
1 | — | Intrinsic scanning signed integers from top-of-stack string into [int]. |
OP_IMPORT / OP_IMPORT_ALL |
1 | — | Loads/caches module from path string on stack. |
5.6 Fused Peephole Superinstructions
| Superinstruction | Bytes | Operands | Fused Sequence / Semantics |
|---|---|---|---|
OP_POP_N |
3 | count: u16 |
Pops count stack slots in one dispatch (OpPop \times N). |
OP_SET_LOCAL_POP |
3 | slot: u16 |
Pops top of stack into stack[Base + slot] (OpSetLocal + OpPop). |
OP_SET_GLOBAL_POP |
3 | nameIdx: u16 |
Pops top of stack into module global Constants[nameIdx] (OpSetGlobal + OpPop). |
OP_GET_LOCAL_2 |
5 | s1: u16, s2: u16 |
Pushes stack[Base + s1] and stack[Base + s2] (OpGetLocal + OpGetLocal). |
OP_GET_LOCAL_INDEX |
5 | arrSlot: u16, idxSlot: u16 |
Pushes stack[Base + arrSlot][stack[Base + idxSlot]] (OpGetLocal + OpGetLocal + OpIndex). |
OP_ADD_CONST / OP_SUB_CONST |
3 | constIdx: u16 |
Adds or subtracts Constants[constIdx] in-place (OpConstant + OpAdd/OpSub). |
OP_INC_LOCAL / OP_DEC_LOCAL |
5 | slot: u16, constIdx: u16 |
Increments or decrements stack[Base + slot] by Constants[constIdx] in-place. |
OP_LESS_LOCAL_JMP_F |
7 | s1: u16, s2: u16, target: u16 |
Jumps to target if !(stack[Base + s1] < stack[Base + s2]). |
OP_LESS_EQ_LOCAL_JMP_F |
7 | s1: u16, s2: u16, target: u16 |
Jumps to target if !(stack[Base + s1] <= stack[Base + s2]). |
OP_LESS_CONST_JMP_F |
7 | slot: u16, cIdx: u16, target: u16 |
Jumps to target if !(stack[Base + slot] < Constants[cIdx]). |
OP_EQ_CONST_JMP_F |
7 | slot: u16, cIdx: u16, target: u16 |
Jumps to target if !(stack[Base + slot] == Constants[cIdx]). |
OP_NEQ_CONST_JMP_F |
7 | slot: u16, cIdx: u16, target: u16 |
Jumps to target if !(stack[Base + slot] != Constants[cIdx]). |