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}