❄️ Tinsel AoC DSL

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

text
+---------------------------------------------------------------+
|                     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):

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

  1. Scalars (ValInt, ValBool, ValChar, ValFloat): Stored entirely inside Num with Obj == nil. Float values bit-cast their 64-bit IEEE-754 representation into Num via math.Float64bits.
  2. Packed 32-bit Vec2 and Range (Obj == nil): Whenever both (X, Y) coordinates of a vec2 or both [start, end) bounds of a range fit within signed 32-bit integers (-2,147,483,648 to 2,147,483,647), they are packed into the high and low 32 bits of Num with Obj == nil. Creating, adding, comparing, and hashing 2D coordinates in bfs/dijkstra runs with zero heap allocations. Coordinates exceeding 32-bit bounds transparently fall back to heap-allocated *Vec2 or *Range structs in Obj.

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 packed Range return fixed-size comparable Go struct keys with zero allocations.
  • User-defined struct declarations (struct Name(f1, f2, ...)) record their ordered field names in __proto__.__fields__. When hashing a struct instance with 0 to 4 fields, Value.HashKey() constructs a comparable StructHashKey0..4 struct 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):

  1. AST Constant Folding & Immutable let Propagation:
    • Evaluates constant arithmetic, bitwise, string, character, range (1..=10), <-> Manhattan distance, and constant vec2/vec3 expressions at compile time.
    • Propagates immutable let scalar constants into downstream expressions and eliminates untaken if branches when conditions are compile-time constants.
  2. 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 0 accumulator for for loops that contain no yield statement, and skips element indexing on wildcard loops (for _ in a..b).
  3. 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.
  4. Dead Constant Pool Pruning:
    • Compacts Chunk.Constants after peephole optimization by removing unreferenced constants and remapping surviving constant indices.

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]).