Skip to main content

tl_compiler/
opcode.rs

1// ThinkingLanguage — Bytecode Instruction Set
2// Register-based: [opcode:8][A:8][B:8][C:8] or [opcode:8][A:8][Bx:16]
3
4/// Bytecode operations for the TL virtual machine.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6#[repr(u8)]
7pub enum Op {
8    // ── Constants & moves ──
9    /// Load constant pool[Bx] into register A
10    LoadConst = 0,
11    /// Load None into register A
12    LoadNone = 1,
13    /// Load true into register A
14    LoadTrue = 2,
15    /// Load false into register A
16    LoadFalse = 3,
17    /// Copy register B into register A
18    Move = 4,
19
20    // ── Variables ──
21    /// Load local slot B into register A
22    GetLocal = 5,
23    /// Store register A into local slot B
24    SetLocal = 6,
25    /// Load global named by constant Bx into register A
26    GetGlobal = 7,
27    /// Store register A into global named by constant Bx
28    SetGlobal = 8,
29    /// Load upvalue index B into register A
30    GetUpvalue = 9,
31    /// Store register A into upvalue index B
32    SetUpvalue = 10,
33
34    // ── Arithmetic ──
35    /// A = B + C
36    Add = 11,
37    /// A = B - C
38    Sub = 12,
39    /// A = B * C
40    Mul = 13,
41    /// A = B / C
42    Div = 14,
43    /// A = B % C
44    Mod = 15,
45    /// A = B ** C
46    Pow = 16,
47    /// A = -B
48    Neg = 17,
49
50    // ── Comparison ──
51    /// A = (B == C)
52    Eq = 18,
53    /// A = (B != C)
54    Neq = 19,
55    /// A = (B < C)
56    Lt = 20,
57    /// A = (B > C)
58    Gt = 21,
59    /// A = (B <= C)
60    Lte = 22,
61    /// A = (B >= C)
62    Gte = 23,
63
64    // ── Logical ──
65    /// A = B and C
66    And = 24,
67    /// A = B or C
68    Or = 25,
69    /// A = not B
70    Not = 26,
71
72    // ── String ──
73    /// A = concat(B, C)
74    Concat = 27,
75
76    // ── Control flow ──
77    /// Jump by signed offset Bx (as i16)
78    Jump = 28,
79    /// If register A is falsy, jump by signed offset Bx
80    JumpIfFalse = 29,
81    /// If register A is truthy, jump by signed offset Bx
82    JumpIfTrue = 30,
83
84    // ── Functions ──
85    /// Call: A = function register, B = first arg register, C = arg count
86    /// Result goes into register A
87    Call = 31,
88    /// Return register A
89    Return = 32,
90    /// Create closure from prototype at constant Bx, store in A
91    /// Followed by upvalue descriptors
92    Closure = 33,
93
94    // ── Data structures ──
95    /// Create list: A = dest, B = start register, C = count
96    NewList = 34,
97    /// A = B[C]
98    GetIndex = 35,
99    /// B[C] = A
100    SetIndex = 36,
101    /// Create map: A = dest, B = start register (alternating key/value), C = pair count
102    NewMap = 37,
103
104    // ── Table operations ──
105    /// TablePipe: A = table reg, B = op constant index, C = args start
106    /// The VM handles this specially for DataFusion table ops
107    TablePipe = 38,
108
109    // ── Builtins ──
110    /// CallBuiltin: A = dest, B = builtin id, C = first arg reg
111    /// Next instruction word: arg count in A field
112    CallBuiltin = 39,
113
114    // ── Iteration ──
115    /// ForIter: A = iterator reg, B = value dest, jump offset in next Bx if done
116    ForIter = 40,
117    /// ForPrep: A = dest for iterator, B = list register
118    ForPrep = 41,
119
120    // ── Pattern matching ──
121    /// TestMatch: A = subject reg, B = pattern reg, C = dest bool reg
122    TestMatch = 42,
123
124    // ── Null coalesce ──
125    /// NullCoalesce: if A is None, A = B
126    NullCoalesce = 43,
127
128    // ── Member access ──
129    /// GetMember: A = dest, B = object reg, C = field name constant
130    GetMember = 44,
131
132    // ── String interpolation ──
133    /// Interpolate: A = dest, B = template constant, C = values start reg
134    /// Next instruction word: value count in A field
135    Interpolate = 45,
136
137    /// Train: A = dest for model, B = algorithm constant, C = config constant
138    Train = 46,
139
140    /// PipelineExec: A = dest for result, B = pipeline blocks constant, C = config constant
141    PipelineExec = 47,
142    /// StreamExec: A = dest, B = stream def constant, C = source register
143    StreamExec = 48,
144    /// ConnectorDecl: A = dest, B = connector type constant, C = config constant
145    ConnectorDecl = 49,
146
147    // ── Phase 5: Language completeness ──
148    /// NewStruct: A = dest, B = type name constant, C = field count
149    /// Followed by field name/value register pairs
150    NewStruct = 50,
151    /// SetMember: A = object reg, B = field name constant, C = value reg
152    SetMember = 51,
153    /// NewEnum: A = dest, B = type+variant name constant, C = args start reg
154    /// Next instruction: arg count in A field
155    NewEnum = 52,
156    /// MatchEnum: A = subject reg, B = variant name constant, C = dest bool reg
157    MatchEnum = 53,
158    /// MethodCall: A = dest/func reg, B = object reg, C = method name constant
159    /// Next instruction: args_start in A, arg_count in B
160    MethodCall = 54,
161    /// Throw: A = value register to throw
162    Throw = 55,
163    /// TryBegin: A = catch handler offset (as Bx signed)
164    TryBegin = 56,
165    /// TryEnd: pops the try handler
166    TryEnd = 57,
167    /// Import: A = dest, Bx = path constant
168    Import = 58,
169
170    // ── Phase 7: Concurrency ──
171    /// Await: A = dest, B = task register (passthrough if not a task)
172    Await = 59,
173
174    // ── Phase 8: Iterators & Generators ──
175    /// Yield: A = value register to yield (suspends generator)
176    Yield = 60,
177
178    // ── Phase 10: Type System ──
179    /// TryPropagate: A = dest, B = source register
180    /// If source is Err(...) → early return from current function
181    /// If source is Ok(v) → A = v (unwrap)
182    /// If source is None → early return None
183    /// Otherwise → passthrough
184    TryPropagate = 61,
185
186    // ── Phase 17: Pattern Matching ──
187    /// ExtractField: A = dest, B = source reg, C = field index
188    /// Extracts field[C] from an enum instance or list into dest.
189    /// If C has high bit set (C | 0x80), extracts rest (sublist from index C & 0x7F).
190    ExtractField = 62,
191    /// ExtractNamedField: A = dest, B = source reg, C = field name constant index
192    /// Extracts a named field from a struct into dest.
193    ExtractNamedField = 63,
194
195    // ── Phase 28: Ownership & Move Semantics ──
196    /// LoadMoved: A = Moved tombstone
197    LoadMoved = 64,
198    /// MakeRef: A = Ref(B) — wrap value in read-only reference
199    MakeRef = 65,
200    /// ParallelFor: A = list reg, B = body prototype constant, C = unused
201    ParallelFor = 66,
202
203    // ── Phase 34: AI Agent Framework ──
204    /// AgentExec: A = dest, B = name constant, C = config constant
205    AgentExec = 67,
206}
207
208impl Op {
209    /// Return a human-readable name for this opcode.
210    pub fn name(&self) -> &'static str {
211        match self {
212            Op::LoadConst => "LoadConst",
213            Op::LoadNone => "LoadNone",
214            Op::LoadTrue => "LoadTrue",
215            Op::LoadFalse => "LoadFalse",
216            Op::Move => "Move",
217            Op::GetLocal => "GetLocal",
218            Op::SetLocal => "SetLocal",
219            Op::GetGlobal => "GetGlobal",
220            Op::SetGlobal => "SetGlobal",
221            Op::GetUpvalue => "GetUpvalue",
222            Op::SetUpvalue => "SetUpvalue",
223            Op::Add => "Add",
224            Op::Sub => "Sub",
225            Op::Mul => "Mul",
226            Op::Div => "Div",
227            Op::Mod => "Mod",
228            Op::Pow => "Pow",
229            Op::Neg => "Neg",
230            Op::Eq => "Eq",
231            Op::Neq => "Neq",
232            Op::Lt => "Lt",
233            Op::Gt => "Gt",
234            Op::Lte => "Lte",
235            Op::Gte => "Gte",
236            Op::And => "And",
237            Op::Or => "Or",
238            Op::Not => "Not",
239            Op::Concat => "Concat",
240            Op::Jump => "Jump",
241            Op::JumpIfFalse => "JumpIfFalse",
242            Op::JumpIfTrue => "JumpIfTrue",
243            Op::Call => "Call",
244            Op::Return => "Return",
245            Op::Closure => "Closure",
246            Op::NewList => "NewList",
247            Op::GetIndex => "GetIndex",
248            Op::SetIndex => "SetIndex",
249            Op::NewMap => "NewMap",
250            Op::TablePipe => "TablePipe",
251            Op::CallBuiltin => "CallBuiltin",
252            Op::ForIter => "ForIter",
253            Op::ForPrep => "ForPrep",
254            Op::TestMatch => "TestMatch",
255            Op::NullCoalesce => "NullCoalesce",
256            Op::GetMember => "GetMember",
257            Op::Interpolate => "Interpolate",
258            Op::Train => "Train",
259            Op::PipelineExec => "PipelineExec",
260            Op::StreamExec => "StreamExec",
261            Op::ConnectorDecl => "ConnectorDecl",
262            Op::NewStruct => "NewStruct",
263            Op::SetMember => "SetMember",
264            Op::NewEnum => "NewEnum",
265            Op::MatchEnum => "MatchEnum",
266            Op::MethodCall => "MethodCall",
267            Op::Throw => "Throw",
268            Op::TryBegin => "TryBegin",
269            Op::TryEnd => "TryEnd",
270            Op::Import => "Import",
271            Op::Await => "Await",
272            Op::Yield => "Yield",
273            Op::TryPropagate => "TryPropagate",
274            Op::ExtractField => "ExtractField",
275            Op::ExtractNamedField => "ExtractNamedField",
276            Op::LoadMoved => "LoadMoved",
277            Op::MakeRef => "MakeRef",
278            Op::ParallelFor => "ParallelFor",
279            Op::AgentExec => "AgentExec",
280        }
281    }
282}
283
284/// Encode an ABC-format instruction: [op:8][A:8][B:8][C:8]
285pub fn encode_abc(op: Op, a: u8, b: u8, c: u8) -> u32 {
286    ((op as u32) << 24) | ((a as u32) << 16) | ((b as u32) << 8) | (c as u32)
287}
288
289/// Encode an ABx-format instruction: [op:8][A:8][Bx:16]
290pub fn encode_abx(op: Op, a: u8, bx: u16) -> u32 {
291    ((op as u32) << 24) | ((a as u32) << 16) | (bx as u32)
292}
293
294impl TryFrom<u8> for Op {
295    type Error = u8;
296
297    fn try_from(value: u8) -> Result<Self, Self::Error> {
298        match value {
299            0 => Ok(Op::LoadConst),
300            1 => Ok(Op::LoadNone),
301            2 => Ok(Op::LoadTrue),
302            3 => Ok(Op::LoadFalse),
303            4 => Ok(Op::Move),
304            5 => Ok(Op::GetLocal),
305            6 => Ok(Op::SetLocal),
306            7 => Ok(Op::GetGlobal),
307            8 => Ok(Op::SetGlobal),
308            9 => Ok(Op::GetUpvalue),
309            10 => Ok(Op::SetUpvalue),
310            11 => Ok(Op::Add),
311            12 => Ok(Op::Sub),
312            13 => Ok(Op::Mul),
313            14 => Ok(Op::Div),
314            15 => Ok(Op::Mod),
315            16 => Ok(Op::Pow),
316            17 => Ok(Op::Neg),
317            18 => Ok(Op::Eq),
318            19 => Ok(Op::Neq),
319            20 => Ok(Op::Lt),
320            21 => Ok(Op::Gt),
321            22 => Ok(Op::Lte),
322            23 => Ok(Op::Gte),
323            24 => Ok(Op::And),
324            25 => Ok(Op::Or),
325            26 => Ok(Op::Not),
326            27 => Ok(Op::Concat),
327            28 => Ok(Op::Jump),
328            29 => Ok(Op::JumpIfFalse),
329            30 => Ok(Op::JumpIfTrue),
330            31 => Ok(Op::Call),
331            32 => Ok(Op::Return),
332            33 => Ok(Op::Closure),
333            34 => Ok(Op::NewList),
334            35 => Ok(Op::GetIndex),
335            36 => Ok(Op::SetIndex),
336            37 => Ok(Op::NewMap),
337            38 => Ok(Op::TablePipe),
338            39 => Ok(Op::CallBuiltin),
339            40 => Ok(Op::ForIter),
340            41 => Ok(Op::ForPrep),
341            42 => Ok(Op::TestMatch),
342            43 => Ok(Op::NullCoalesce),
343            44 => Ok(Op::GetMember),
344            45 => Ok(Op::Interpolate),
345            46 => Ok(Op::Train),
346            47 => Ok(Op::PipelineExec),
347            48 => Ok(Op::StreamExec),
348            49 => Ok(Op::ConnectorDecl),
349            50 => Ok(Op::NewStruct),
350            51 => Ok(Op::SetMember),
351            52 => Ok(Op::NewEnum),
352            53 => Ok(Op::MatchEnum),
353            54 => Ok(Op::MethodCall),
354            55 => Ok(Op::Throw),
355            56 => Ok(Op::TryBegin),
356            57 => Ok(Op::TryEnd),
357            58 => Ok(Op::Import),
358            59 => Ok(Op::Await),
359            60 => Ok(Op::Yield),
360            61 => Ok(Op::TryPropagate),
361            62 => Ok(Op::ExtractField),
362            63 => Ok(Op::ExtractNamedField),
363            64 => Ok(Op::LoadMoved),
364            65 => Ok(Op::MakeRef),
365            66 => Ok(Op::ParallelFor),
366            67 => Ok(Op::AgentExec),
367            _ => Err(value),
368        }
369    }
370}
371
372/// Decode opcode from instruction
373pub fn decode_op(inst: u32) -> Op {
374    Op::try_from((inst >> 24) as u8).expect("valid opcode in instruction")
375}
376
377/// Decode A field
378pub fn decode_a(inst: u32) -> u8 {
379    ((inst >> 16) & 0xFF) as u8
380}
381
382/// Decode B field
383pub fn decode_b(inst: u32) -> u8 {
384    ((inst >> 8) & 0xFF) as u8
385}
386
387/// Decode C field
388pub fn decode_c(inst: u32) -> u8 {
389    (inst & 0xFF) as u8
390}
391
392/// Decode Bx field (16-bit unsigned)
393pub fn decode_bx(inst: u32) -> u16 {
394    (inst & 0xFFFF) as u16
395}
396
397/// Decode Bx as signed offset (for jumps)
398pub fn decode_sbx(inst: u32) -> i16 {
399    (inst & 0xFFFF) as i16
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    #[test]
407    fn test_abc_round_trip() {
408        let inst = encode_abc(Op::Add, 3, 1, 2);
409        assert_eq!(decode_op(inst), Op::Add);
410        assert_eq!(decode_a(inst), 3);
411        assert_eq!(decode_b(inst), 1);
412        assert_eq!(decode_c(inst), 2);
413    }
414
415    #[test]
416    fn test_abx_round_trip() {
417        let inst = encode_abx(Op::LoadConst, 5, 1000);
418        assert_eq!(decode_op(inst), Op::LoadConst);
419        assert_eq!(decode_a(inst), 5);
420        assert_eq!(decode_bx(inst), 1000);
421    }
422
423    #[test]
424    fn test_signed_offset() {
425        let inst = encode_abx(Op::Jump, 0, (-10_i16) as u16);
426        assert_eq!(decode_op(inst), Op::Jump);
427        assert_eq!(decode_sbx(inst), -10);
428    }
429
430    #[test]
431    fn test_all_ops_encode() {
432        // Verify encoding/decoding for boundary values
433        let inst = encode_abc(Op::Return, 255, 255, 255);
434        assert_eq!(decode_op(inst), Op::Return);
435        assert_eq!(decode_a(inst), 255);
436        assert_eq!(decode_b(inst), 255);
437        assert_eq!(decode_c(inst), 255);
438
439        let inst = encode_abx(Op::LoadConst, 0, 0xFFFF);
440        assert_eq!(decode_bx(inst), 0xFFFF);
441    }
442
443    #[test]
444    fn test_op_try_from_valid() {
445        for v in 0..=67u8 {
446            assert!(Op::try_from(v).is_ok(), "Op::try_from({v}) should succeed");
447        }
448        // Round-trip: value matches discriminant
449        assert_eq!(Op::try_from(0).unwrap(), Op::LoadConst);
450        assert_eq!(Op::try_from(67).unwrap(), Op::AgentExec);
451    }
452
453    #[test]
454    fn test_op_try_from_invalid() {
455        assert_eq!(Op::try_from(68), Err(68));
456        assert_eq!(Op::try_from(255), Err(255));
457    }
458}