Skip to main content

ternlang_core/codegen/
betbc.rs

1use crate::ast::*;
2use crate::vm::bet::pack_trits;
3use crate::trit::Trit;
4
5pub struct BytecodeEmitter {
6    code: Vec<u8>,
7    symbols: std::collections::HashMap<String, u8>,
8    func_addrs: std::collections::HashMap<String, u16>,
9    function_patches: std::collections::HashMap<String, Vec<usize>>,
10    break_patches: Vec<usize>,
11    continue_patches: Vec<usize>,
12    next_reg: usize,
13    struct_layouts: std::collections::HashMap<String, Vec<String>>,
14    agent_type_ids: std::collections::HashMap<String, u16>,
15    agent_handlers: Vec<(u16, u16)>,
16    /// Snapshots of the local symbol table for each function, keyed by function name.
17    /// Captured just before scope is restored so callers can map reg→varname after execution.
18    function_symbols: std::collections::HashMap<String, std::collections::HashMap<String, u8>>,
19}
20
21impl BytecodeEmitter {
22    pub fn new() -> Self {
23        Self {
24            code: Vec::new(),
25            symbols: std::collections::HashMap::new(),
26            func_addrs: std::collections::HashMap::new(),
27            function_patches: std::collections::HashMap::new(),
28            break_patches: Vec::new(),
29            continue_patches: Vec::new(),
30            next_reg: 0,
31            struct_layouts: std::collections::HashMap::new(),
32            agent_type_ids: std::collections::HashMap::new(),
33            agent_handlers: Vec::new(),
34            function_symbols: std::collections::HashMap::new(),
35        }
36    }
37
38    /// Returns the top-level variable-name → register-number map.
39    pub fn get_symbols(&self) -> &std::collections::HashMap<String, u8> {
40        &self.symbols
41    }
42
43    /// Returns the local symbol snapshot for a specific function (e.g. "main").
44    /// Used by `ternlang-cli --emit-symbols` to correlate VM register dumps with source variable names.
45    pub fn get_function_symbols(&self, name: &str) -> Option<&std::collections::HashMap<String, u8>> {
46        self.function_symbols.get(name)
47    }
48
49    pub fn register_agents(&self, vm: &mut crate::vm::BetVm) {
50        for &(type_id, addr) in &self.agent_handlers {
51            vm.register_agent_type(type_id, addr as usize);
52        }
53    }
54
55    pub fn emit_header_jump(&mut self) -> usize {
56        let patch_pos = self.code.len() + 1;
57        self.code.push(0x0b); // TJMP
58        self.code.extend_from_slice(&[0u8, 0u8]);
59        patch_pos
60    }
61
62    pub fn patch_header_jump(&mut self, patch_pos: usize) {
63        let addr = self.code.len() as u16;
64        self.patch_u16(patch_pos, addr);
65    }
66
67    pub fn emit_program(&mut self, program: &Program) {
68        let parent_next_reg = self.next_reg;
69        for s in &program.structs {
70            let names: Vec<String> = s.fields.iter().map(|(n, _)| n.clone()).collect();
71            self.struct_layouts.insert(s.name.clone(), names);
72        }
73        for (idx, agent) in program.agents.iter().enumerate() {
74            self.agent_type_ids.insert(agent.name.clone(), idx as u16);
75        }
76
77        // PASS 1: Addresses
78        let real_code = std::mem::take(&mut self.code);
79        let real_func_addrs = std::mem::take(&mut self.func_addrs);
80        let real_agent_handlers = std::mem::take(&mut self.agent_handlers);
81        let base_addr = real_code.len() as u16;
82
83        for agent in &program.agents {
84            let type_id = self.agent_type_ids[&agent.name];
85            let mut handler_addr = None;
86            for method in &agent.methods {
87                let addr = base_addr + self.code.len() as u16;
88                if handler_addr.is_none() { handler_addr = Some(addr); }
89                self.emit_function(method);
90                // Restore correct absolute address overwritten by emit_function (TCALL-BUG fix):
91                self.func_addrs.insert(format!("{}::{}", agent.name, method.name), addr);
92            }
93            if let Some(addr) = handler_addr { self.agent_handlers.push((type_id, addr)); }
94        }
95        for func in &program.functions {
96            let addr = base_addr + self.code.len() as u16;
97            self.func_addrs.insert(func.name.clone(), addr);
98            // Ensure any global symbols or previous definitions are visible
99            self.emit_function(func);
100            // emit_function overwrites func_addrs[name] with a temp-buffer offset that
101            // omits base_addr. Restore the correct absolute address so that forward
102            // references resolved later in PASS 1 get the right TCALL target.
103            self.func_addrs.insert(func.name.clone(), addr);
104        }
105
106        let final_func_addrs = std::mem::replace(&mut self.func_addrs, real_func_addrs);
107        let final_agent_handlers = std::mem::replace(&mut self.agent_handlers, real_agent_handlers);
108        self.code = real_code;
109        self.func_addrs = final_func_addrs;
110        self.agent_handlers = final_agent_handlers;
111        self.next_reg = parent_next_reg;
112
113        // PASS 2: Real
114        for agent in &program.agents {
115            for method in &agent.methods { self.emit_function(method); }
116        }
117        for func in &program.functions { self.emit_function(func); }
118    }
119
120    pub fn emit_function(&mut self, func: &Function) {
121        let func_addr = self.code.len() as u16;
122        self.func_addrs.insert(func.name.clone(), func_addr);
123        if let Some(patches) = self.function_patches.remove(&func.name) {
124            for p in patches {
125                self.code[p..p + 2].copy_from_slice(&func_addr.to_le_bytes());
126            }
127        }
128        let parent_symbols = self.symbols.clone();
129        let parent_next_reg = self.next_reg;
130        self.next_reg = 0;
131
132        // If function has @sparseskip, we could emit a special header here.
133        // For now, it's just a marker in the AST.
134
135        for (name, ty) in func.params.iter().rev() {
136            if let Type::Named(s_name) = ty {
137                if let Some(fields) = self.struct_layouts.get(s_name).cloned() {
138                    // Structs are passed as a bundle: [field1, field2, ..., root_dummy]
139                    // We must pop root dummy first, then fields.
140                    
141                    // Pop root dummy
142                    let root_reg = self.alloc_reg();
143                    self.symbols.insert(name.clone(), root_reg);
144                    self.code.push(0x08); self.code.push(root_reg);
145
146                    // Pop fields in reverse order of how they were pushed
147                    for f_name in fields.iter().rev() {
148                        let f_reg = self.alloc_reg();
149                        let key = format!("{}.{}", name, f_name);
150                        self.symbols.insert(key, f_reg);
151                        self.code.push(0x08); self.code.push(f_reg);
152                    }
153                    continue;
154                }
155            }
156            let reg = self.alloc_reg();
157            self.symbols.insert(name.clone(), reg);
158            self.code.push(0x08); self.code.push(reg);
159        }
160        for stmt in &func.body { self.emit_stmt(stmt); }
161        // Snapshot local symbols before scope is restored — used by --emit-symbols
162        self.function_symbols.insert(func.name.clone(), self.symbols.clone());
163        self.symbols = parent_symbols;
164        self.next_reg = parent_next_reg;
165        self.code.push(0x11); // TRET
166    }
167
168    pub fn emit_stmt(&mut self, stmt: &Stmt) {
169        match stmt {
170            Stmt::Let { name, ty, value } => {
171                let mut handled = false;
172                match ty {
173                    Type::TritTensor { dims } | Type::IntTensor { dims } | Type::FloatTensor { dims } => {
174                        // Only auto-allocate if size is fixed (>0) and NO value is provided (defaults to TritLiteral(0))
175                        if !dims.is_empty() && !dims.contains(&0) && matches!(value, Expr::TritLiteral(0)) {
176                            let rows = dims[0];
177                            let cols = if dims.len() > 1 { dims[1] } else { 1 };
178                            self.code.push(0x0f);
179                            self.code.extend_from_slice(&(rows as u16).to_le_bytes());
180                            self.code.extend_from_slice(&(cols as u16).to_le_bytes());
181                            handled = true;
182                        }
183                    }
184                    Type::Named(_) => {
185                        if let Expr::StructLiteral { fields, .. } = value {
186                            // Flatten struct fields into mangled registers
187                            for (f_name, f_val) in fields {
188                                self.emit_expr(f_val);
189                                let reg = self.alloc_reg();
190                                let key = format!("{}.{}", name, f_name);
191                                self.symbols.insert(key, reg);
192                                self.code.push(0x08); self.code.push(reg);
193                            }
194                            // Now we let the normal path emit the root variable's dummy value
195                        }
196                    }
197                    _ => {}
198                }
199                if !handled {
200                    self.emit_expr(value);
201                }
202                let reg = self.alloc_reg();
203                self.symbols.insert(name.clone(), reg);
204                self.code.push(0x08); self.code.push(reg); // TSTORE
205            }
206            Stmt::Set { name, value } => {
207                self.emit_expr(value);
208                if let Some(&reg) = self.symbols.get(name) {
209                    self.code.push(0x08); self.code.push(reg);
210                }
211            }
212            Stmt::FieldSet { object, field, value } => {
213                let key = format!("{}.{}", object, field);
214                self.emit_expr(value);
215                if let Some(&reg) = self.symbols.get(&key) {
216                    self.code.push(0x08); self.code.push(reg);
217                }
218            }
219            Stmt::IndexSet { object, row, col, value } => {
220                if let Some(&reg) = self.symbols.get(object) {
221                    self.code.push(0x09); self.code.push(reg);
222                    self.emit_expr(row);
223                    self.emit_expr(col);
224                    self.emit_expr(value);
225                    self.code.push(0x23);
226                }
227            }
228            Stmt::IfTernary { condition, on_pos, on_zero, on_neg } => {
229                let pre_reg = self.next_reg;
230                self.emit_expr(condition);
231                let cond_reg = self.alloc_reg();
232                self.code.push(0x08); self.code.push(cond_reg); // Tstore
233                
234                // Load condition for checks
235                self.code.push(0x09); self.code.push(cond_reg); // Tload
236                
237                // Check POS
238                let pos_patch = self.code.len() + 1;
239                self.code.push(0x05); self.code.extend_from_slice(&[0, 0]); // TJMP_POS
240                
241                // Check ZERO
242                let zero_patch = self.code.len() + 1;
243                self.code.push(0x06); self.code.extend_from_slice(&[0, 0]); // TJMP_ZERO
244                
245                // NEG arm: pop the condition and execute
246                self.code.push(0x0c); // TPOP
247                self.emit_stmt(on_neg);
248                let exit_patch = self.code.len() + 1;
249                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]); // TJMP to end
250                
251                // POS arm
252                let pos_addr = self.code.len() as u16;
253                self.patch_u16(pos_patch, pos_addr);
254                self.code.push(0x0c); // TPOP
255                self.emit_stmt(on_pos);
256                let exit_pos = self.code.len() + 1;
257                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
258                
259                // ZERO arm
260                let zero_addr = self.code.len() as u16;
261                self.patch_u16(zero_patch, zero_addr);
262                self.code.push(0x0c); // TPOP
263                self.emit_stmt(on_zero);
264                
265                let end = self.code.len() as u16;
266                self.patch_u16(exit_patch, end);
267                self.patch_u16(exit_pos, end);
268                self.next_reg = pre_reg;
269            }
270            Stmt::Match { condition, arms } => {
271                let pre_reg = self.next_reg;
272                self.emit_expr(condition);
273                let cond_reg = self.alloc_reg();
274                self.code.push(0x08); self.code.push(cond_reg); // Tstore
275
276                let mut end_patches = Vec::new();
277                let mut next_arm_patch = None;
278
279                for (pattern, stmt) in arms {
280                    if let Some(p) = next_arm_patch {
281                        let addr = self.code.len() as u16;
282                        self.patch_u16(p, addr);
283                    }
284
285                    // Load condition for this arm
286                    self.code.push(0x09); self.code.push(cond_reg); // Tload
287
288                    let match_patch;
289                    match pattern {
290                        Pattern::Trit(1) | Pattern::Int(1) => {
291                            self.code.push(0x05); // TjmpPos (peeks)
292                            match_patch = self.code.len();
293                            self.code.extend_from_slice(&[0, 0]);
294                        }
295                        Pattern::Trit(0) | Pattern::Int(0) => {
296                            self.code.push(0x06); // TjmpZero (peeks)
297                            match_patch = self.code.len();
298                            self.code.extend_from_slice(&[0, 0]);
299                        }
300                        Pattern::Trit(-1) | Pattern::Int(-1) => {
301                            self.code.push(0x07); // TjmpNeg (peeks)
302                            match_patch = self.code.len();
303                            self.code.extend_from_slice(&[0, 0]);
304                        }
305                        Pattern::Int(v) => {
306                            self.code.push(0x25); // TjmpEqInt (peeks)
307                            self.code.extend_from_slice(&v.to_le_bytes());
308                            match_patch = self.code.len();
309                            self.code.extend_from_slice(&[0, 0]);
310                        }
311                        Pattern::Trit(v) => {
312                            self.code.push(0x25); // TjmpEqInt (peeks)
313                            self.code.extend_from_slice(&(*v as i64).to_le_bytes());
314                            match_patch = self.code.len();
315                            self.code.extend_from_slice(&[0, 0]);
316                        }
317                        Pattern::Float(v) => {
318                            self.code.push(0x2a); // TjmpEqFloat (peeks)
319                            self.code.extend_from_slice(&v.to_le_bytes());
320                            match_patch = self.code.len();
321                            self.code.extend_from_slice(&[0, 0]);
322                        }
323                    }
324
325                    // Mismatch: the conditional test above PEEKS (doesn't pop), so if it
326                    // didn't jump the TLOAD result is still on the stack. Pop it before
327                    // jumping to the next arm to keep the stack balanced.
328                    self.code.push(0x0c); // TPOP — discard unmatched arm's cond value
329                    let skip_patch = self.code.len() + 1;
330                    self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
331                    next_arm_patch = Some(skip_patch);
332
333                    // Match found: execute body
334                    let body_addr = self.code.len() as u16;
335                    self.patch_u16(match_patch, body_addr);
336                    
337                    // Body: first pop the condition we were peeking at
338                    self.code.push(0x0c); // Tpop
339                    self.emit_stmt(stmt);
340                    
341                    // After body, jump to end of match
342                    let end_patch = self.code.len() + 1;
343                    self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
344                    end_patches.push(end_patch);
345                }
346
347                if let Some(p) = next_arm_patch {
348                    let addr = self.code.len() as u16;
349                    self.patch_u16(p, addr);
350                }
351                
352                if !arms.is_empty() {
353                    // Each arm's mismatch path now does its own TPOP (see per-arm fix above),
354                    // so the stack is already clean when we reach the fallback.
355                    // VM-MATCH-001: non-exhaustive match — no arm was taken.
356                    // Push a Tend (hold/undefined) placeholder so the stack is balanced
357                    // even if the caller expects a return value from this match expression.
358                    self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend]));
359                }
360
361                let end_addr = self.code.len() as u16;
362                for p in end_patches { self.patch_u16(p, end_addr); }
363                self.next_reg = pre_reg;
364            }
365            Stmt::ForIn { var, iter, body } => {
366                // Save next_reg so loop-internal registers are freed after the loop ends.
367                // Without this, each for-in permanently consumes 4 registers, exhausting
368                // the register file after 6-7 loops in a single function.
369                let pre_loop_reg = self.next_reg;
370
371                self.emit_expr(iter);
372                let it_reg = self.alloc_reg();
373                self.code.push(0x08); self.code.push(it_reg);
374                self.code.push(0x09); self.code.push(it_reg);
375                self.code.push(0x24); // TSHAPE: pushes rows then cols (cols on top)
376                self.code.push(0x0c); // pop cols — iterate over rows, not cols
377                let r_reg = self.alloc_reg();
378                self.code.push(0x08); self.code.push(r_reg); // store rows as loop bound
379                let i_reg = self.alloc_reg();
380                self.code.push(0x17); self.code.extend_from_slice(&0i64.to_le_bytes());
381                self.code.push(0x08); self.code.push(i_reg);
382
383                // Use a register for the loop comparison so we avoid TDUP accumulation.
384                // Previously: TDUP + TjmpNeg/TjmpZero (peek) left 2 values on the stack per
385                // iteration, causing a stack leak that corrupted subsequent operations.
386                let cmp_reg = self.alloc_reg();
387
388                let top = self.code.len() as u16;
389                let pre_break = self.break_patches.len();
390                let pre_cont = self.continue_patches.len();
391
392                // Compute i < r → cmp_reg (stack neutral: push then immediately store)
393                self.code.push(0x09); self.code.push(i_reg);
394                self.code.push(0x09); self.code.push(r_reg);
395                self.code.push(0x14);                        // Tless → [cmp]
396                self.code.push(0x08); self.code.push(cmp_reg); // TSTORE cmp → []
397
398                // Load and test for NEG (i >= r → Reject → exit)
399                self.code.push(0x09); self.code.push(cmp_reg); // [cmp]
400                let neg = self.code.len() + 1;
401                self.code.push(0x07); self.code.extend_from_slice(&[0, 0]); // TjmpNeg → peeks
402                self.code.push(0x0c); // TPOP — clean up after failed neg check
403
404                // Load and test for ZERO (i == r → Tend → exit)
405                self.code.push(0x09); self.code.push(cmp_reg); // [cmp]
406                let zero = self.code.len() + 1;
407                self.code.push(0x06); self.code.extend_from_slice(&[0, 0]); // TjmpZero → peeks
408                self.code.push(0x0c); // TPOP — clean up after failed zero check, body runs clean
409
410                // Body: load element tensor[it, i, 0] → v_reg
411                self.code.push(0x09); self.code.push(it_reg);
412                self.code.push(0x09); self.code.push(i_reg);
413                self.code.push(0x17); self.code.extend_from_slice(&0i64.to_le_bytes());
414                self.code.push(0x22);
415                let v_reg = self.alloc_reg();
416                self.symbols.insert(var.clone(), v_reg);
417                self.code.push(0x08); self.code.push(v_reg);
418                self.emit_stmt(body);
419
420                let cont_addr = self.code.len() as u16;
421                let cs: Vec<usize> = self.continue_patches.drain(pre_cont..).collect();
422                for p in cs { self.patch_u16(p, cont_addr); }
423
424                self.code.push(0x09); self.code.push(i_reg);
425                self.code.push(0x17); self.code.extend_from_slice(&1i64.to_le_bytes());
426                self.code.push(0x18);
427                self.code.push(0x08); self.code.push(i_reg);
428                let back = self.code.len() + 1;
429                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
430                self.patch_u16(back, top);
431
432                // Exit paths for neg/zero: the TjmpNeg/TjmpZero PEEK so the cmp value
433                // is still on the stack when they jump. Add a TPOP cleanup then TJMP end.
434                let neg_exit_addr = self.code.len() as u16;
435                self.patch_u16(neg, neg_exit_addr);
436                self.code.push(0x0c); // TPOP — clean peeked cmp
437                let neg_to_end = self.code.len() + 1;
438                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
439
440                let zero_exit_addr = self.code.len() as u16;
441                self.patch_u16(zero, zero_exit_addr);
442                self.code.push(0x0c); // TPOP — clean peeked cmp
443                let zero_to_end = self.code.len() + 1;
444                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
445
446                let end = self.code.len() as u16;
447                self.patch_u16(neg_to_end, end);
448                self.patch_u16(zero_to_end, end);
449                let bs: Vec<usize> = self.break_patches.drain(pre_break..).collect();
450                for p in bs { self.patch_u16(p, end); }
451
452                // Free loop registers: loop variable is out of scope, and the 4
453                // internal registers (it, r, i, v) are no longer needed.
454                self.symbols.remove(var);
455                self.next_reg = pre_loop_reg;
456            }
457            Stmt::WhileTernary { condition, on_pos, on_zero, on_neg } => {
458                let pre_reg = self.next_reg;
459                let cond_reg = self.alloc_reg();
460                let top = self.code.len() as u16;
461                let pre_break = self.break_patches.len();
462                let pre_cont = self.continue_patches.len();
463
464                self.emit_expr(condition);
465                self.code.push(0x08); self.code.push(cond_reg); // Tstore
466                
467                // Load condition for checks
468                self.code.push(0x09); self.code.push(cond_reg); // Tload
469                
470                // Check POS
471                let pos_patch = self.code.len() + 1;
472                self.code.push(0x05); self.code.extend_from_slice(&[0, 0]); // TJMP_POS
473                
474                // Check ZERO
475                let zero_patch = self.code.len() + 1;
476                self.code.push(0x06); self.code.extend_from_slice(&[0, 0]); // TJMP_ZERO
477                
478                // NEG ARM: pop and execute and EXIT (don't loop back)
479                self.code.push(0x0c); // TPOP
480                self.emit_stmt(on_neg);
481                let exit_neg = self.code.len() + 1;
482                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]); // TJMP to end
483
484                // POS ARM: pop and execute and LOOP BACK
485                let pos_addr = self.code.len() as u16;
486                self.patch_u16(pos_patch, pos_addr);
487                self.code.push(0x0c); // TPOP
488                self.emit_stmt(on_pos);
489                let back_pos = self.code.len() + 1;
490                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
491                self.patch_u16(back_pos, top);
492
493                // ZERO ARM: pop and execute and EXIT (don't loop back)
494                let zero_addr = self.code.len() as u16;
495                self.patch_u16(zero_patch, zero_addr);
496                self.code.push(0x0c); // TPOP
497                self.emit_stmt(on_zero);
498                
499                let end = self.code.len() as u16;
500                self.patch_u16(exit_neg, end);
501
502                let cs: Vec<usize> = self.continue_patches.drain(pre_cont..).collect();
503                for p in cs { self.patch_u16(p, top); }
504                let bs: Vec<usize> = self.break_patches.drain(pre_break..).collect();
505                for p in bs { self.patch_u16(p, end); }
506                self.next_reg = pre_reg;
507            }
508            Stmt::Loop { body } => {
509                let top = self.code.len() as u16;
510                let pre_break = self.break_patches.len();
511                let pre_cont = self.continue_patches.len();
512                self.emit_stmt(body);
513                let back = self.code.len() + 1;
514                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
515                self.patch_u16(back, top);
516                let end = self.code.len() as u16;
517                let cs: Vec<usize> = self.continue_patches.drain(pre_cont..).collect();
518                for p in cs { self.patch_u16(p, top); }
519                let bs: Vec<usize> = self.break_patches.drain(pre_break..).collect();
520                for p in bs { self.patch_u16(p, end); }
521            }
522            Stmt::Break => {
523                let p = self.code.len() + 1;
524                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
525                self.break_patches.push(p);
526            }
527            Stmt::Continue => {
528                let p = self.code.len() + 1;
529                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
530                self.continue_patches.push(p);
531            }
532            Stmt::Send { target, message } => {
533                self.emit_expr(target);
534                self.emit_expr(message);
535                self.code.push(0x31); // TSEND
536            }
537            Stmt::Return(e) => { self.emit_expr(e); self.code.push(0x11); }
538            Stmt::Block(ss) => { for s in ss { self.emit_stmt(s); } }
539            Stmt::Expr(e) => { self.emit_expr(e); self.code.push(0x0c); }
540            Stmt::Decorated { directive: _, stmt } => { self.emit_stmt(stmt); }
541            _ => {}
542        }
543    }
544
545    fn emit_expr(&mut self, expr: &Expr) {
546        match expr {
547            Expr::TritLiteral(v) => {
548                self.code.push(0x01);
549                self.code.extend(pack_trits(&[Trit::from(*v)]));
550            }
551            Expr::IntLiteral(v) => {
552                self.code.push(0x17);
553                self.code.extend_from_slice(&v.to_le_bytes());
554            }
555            Expr::FloatLiteral(val) => {
556                self.code.push(0x19);
557                self.code.extend_from_slice(&val.to_le_bytes());
558            }
559            Expr::StringLiteral(val) => {
560                self.code.push(0x21); // TPUSH_STRING
561                let bytes = val.as_bytes();
562                self.code.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
563                self.code.extend_from_slice(bytes);
564            }
565            Expr::Ident(name) => {
566                // COMP-BOOL-001: `true`/`false` are not keywords in the lexer — they arrive
567                // as Token::Ident. Handle them here so they produce a value instead of
568                // causing a stack underflow when no symbol matches.
569                match name.as_str() {
570                    "true" => {
571                        self.code.push(0x17); // TpushInt
572                        self.code.extend_from_slice(&1i64.to_le_bytes());
573                    }
574                    "false" => {
575                        self.code.push(0x17); // TpushInt
576                        self.code.extend_from_slice(&0i64.to_le_bytes());
577                    }
578                    _ => {
579                        if let Some(&r) = self.symbols.get(name) {
580                            self.code.push(0x09); self.code.push(r);
581                        }
582                    }
583                }
584            }
585            Expr::BinaryOp { op, lhs, rhs } => {
586                self.emit_expr(lhs); self.emit_expr(rhs);
587                match op {
588                    BinOp::Add => self.code.push(0x02),
589                    BinOp::Mul => self.code.push(0x03),
590                    BinOp::Div => self.code.push(0x1e),
591                    BinOp::Mod => self.code.push(0x1f),
592                    BinOp::Sub => { self.code.push(0x04); self.code.push(0x02); }
593                    BinOp::Equal => self.code.push(0x16),
594                    BinOp::NotEqual => { self.code.push(0x16); self.code.push(0x04); }
595                    BinOp::And => self.code.push(0x28), // TAND = min(a,b)
596                    BinOp::Or  => self.code.push(0x29), // TOR  = max(a,b)
597                    BinOp::Less => self.code.push(0x14),
598                    BinOp::Greater => self.code.push(0x15),
599                    BinOp::LessEqual => self.code.push(0x26),
600                    BinOp::GreaterEqual => self.code.push(0x27),
601                }
602            }
603            Expr::UnaryOp { op, expr } => {
604                self.emit_expr(expr);
605                match op { UnOp::Neg => self.code.push(0x04) }
606            }
607            Expr::Call { callee, args } => {
608                match callee.as_str() {
609                    // `print` is an alias for `println` — same TPRINT opcode (0x20)
610                    "println" | "print" => {
611                        if args.is_empty() {
612                            // print newline only (not implemented, but let's push dummy)
613                        } else {
614                            for a in args {
615                                self.emit_expr(a);
616                                self.code.push(0x20); // TPRINT
617                            }
618                        }
619                        self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend])); // return hold()
620                    }
621                    "opent" => {
622                        if args.len() == 2 {
623                            for a in args { self.emit_expr(a); }
624                            self.code.push(0x33); // TOPENT (pushes Int handle)
625                        } else {
626                            // error but push dummy
627                            self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend]));
628                        }
629                    }
630                    "readt" => {
631                        if args.len() == 1 {
632                            self.emit_expr(&args[0]);
633                            self.code.push(0x34); // TREADT (pushes Trit)
634                        } else {
635                            self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend]));
636                        }
637                    }
638                    "writet" => {
639                        if args.len() == 2 {
640                            for a in args { self.emit_expr(a); }
641                            self.code.push(0x35); // TWRITET
642                        }
643                        self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend])); // push void/hold result
644                    }
645                    "consensus" => {
646
647                        for a in args { self.emit_expr(a); }
648                        if args.len() == 2 { self.code.push(0x0e); }
649                    }
650                    "length" => {
651                        if args.len() == 1 {
652                            self.emit_expr(&args[0]);
653                            self.code.push(0x24); // TSHAPE
654                            self.code.push(0x0c); // TPOP (cols)
655                        }
656                    }
657                    // VM-BUILTIN-001: `invert(t)` = ternary negation (Tneg, opcode 0x04)
658                    "invert" => {
659                        if args.len() == 1 {
660                            self.emit_expr(&args[0]);
661                            self.code.push(0x04); // Tneg
662                        }
663                    }
664                    // VM-BUILTIN-002: `len(arr)` is an alias for `length(arr)`
665                    "len" => {
666                        if args.len() == 1 {
667                            self.emit_expr(&args[0]);
668                            self.code.push(0x24); // TSHAPE
669                            self.code.push(0x0c); // TPOP (cols — TSHAPE pushes rows then cols)
670                        }
671                    }
672                    // VM-BUILTIN-001: `abs(n)` — inline: dup, push 0, less-than, branch on negative
673                    "abs" => {
674                        if args.len() == 1 {
675                            self.emit_expr(&args[0]);          // stack: [x]
676                            self.code.push(0x0a);              // TDUP   → [x, x]
677                            self.code.push(0x17);              // TpushInt 0
678                            self.code.extend_from_slice(&0i64.to_le_bytes()); // → [x, x, 0]
679                            self.code.push(0x14);              // Tless: (x < 0) → Affirm; [x, cmp]
680                            // TjmpPos (peek) to negate branch
681                            let neg_patch = self.code.len() + 1;
682                            self.code.push(0x05); self.code.extend_from_slice(&[0, 0]);
683                            // not negative: pop cmp, jump to end
684                            self.code.push(0x0c);              // TPOP → [x]
685                            let end_patch = self.code.len() + 1;
686                            self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
687                            // negate branch: pop cmp, negate x
688                            let neg_addr = self.code.len() as u16;
689                            self.patch_u16(neg_patch, neg_addr);
690                            self.code.push(0x0c);              // TPOP → [x]
691                            self.code.push(0x04);              // Tneg → [-x] (positive when x<0)
692                            let end_addr = self.code.len() as u16;
693                            self.patch_u16(end_patch, end_addr);
694                        }
695                    }
696                    // VM-BUILTIN-001: `min(a, b)` — inline with temp registers
697                    "min" => {
698                        if args.len() == 2 {
699                            let a_reg = self.alloc_reg();
700                            let b_reg = self.alloc_reg();
701                            self.emit_expr(&args[0]);
702                            self.code.push(0x08); self.code.push(a_reg); // TSTORE a
703                            self.emit_expr(&args[1]);
704                            self.code.push(0x08); self.code.push(b_reg); // TSTORE b
705                            self.code.push(0x09); self.code.push(a_reg); // TLOAD a
706                            self.code.push(0x09); self.code.push(b_reg); // TLOAD b
707                            self.code.push(0x14);                         // Tless: a < b → Affirm
708                            // TjmpPos → a is smaller, return a
709                            let a_smaller_patch = self.code.len() + 1;
710                            self.code.push(0x05); self.code.extend_from_slice(&[0, 0]);
711                            // a >= b: return b
712                            self.code.push(0x0c);              // TPOP cmp
713                            self.code.push(0x09); self.code.push(b_reg); // TLOAD b
714                            let end_patch = self.code.len() + 1;
715                            self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
716                            // a < b: return a
717                            let a_smaller_addr = self.code.len() as u16;
718                            self.patch_u16(a_smaller_patch, a_smaller_addr);
719                            self.code.push(0x0c);              // TPOP cmp
720                            self.code.push(0x09); self.code.push(a_reg); // TLOAD a
721                            let end_addr = self.code.len() as u16;
722                            self.patch_u16(end_patch, end_addr);
723                        }
724                    }
725                    // VM-BUILTIN-001: `max(a, b)` — inline with temp registers
726                    "max" => {
727                        if args.len() == 2 {
728                            let a_reg = self.alloc_reg();
729                            let b_reg = self.alloc_reg();
730                            self.emit_expr(&args[0]);
731                            self.code.push(0x08); self.code.push(a_reg); // TSTORE a
732                            self.emit_expr(&args[1]);
733                            self.code.push(0x08); self.code.push(b_reg); // TSTORE b
734                            self.code.push(0x09); self.code.push(b_reg); // TLOAD b
735                            self.code.push(0x09); self.code.push(a_reg); // TLOAD a
736                            self.code.push(0x14);                         // Tless: b < a → Affirm
737                            // TjmpPos → a is larger, return a
738                            let a_larger_patch = self.code.len() + 1;
739                            self.code.push(0x05); self.code.extend_from_slice(&[0, 0]);
740                            // b >= a: return b
741                            self.code.push(0x0c);              // TPOP cmp
742                            self.code.push(0x09); self.code.push(b_reg); // TLOAD b
743                            let end_patch = self.code.len() + 1;
744                            self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
745                            // b < a: return a
746                            let a_larger_addr = self.code.len() as u16;
747                            self.patch_u16(a_larger_patch, a_larger_addr);
748                            self.code.push(0x0c);              // TPOP cmp
749                            self.code.push(0x09); self.code.push(a_reg); // TLOAD a
750                            let end_addr = self.code.len() as u16;
751                            self.patch_u16(end_patch, end_addr);
752                        }
753                    }
754                    // `pow(base, exp)` — integer power via loop: result = 1; while exp>0 { result*=base; exp-=1; }
755                    "pow" => {
756                        if args.len() == 2 {
757                            let b_reg = self.alloc_reg(); // base
758                            let e_reg = self.alloc_reg(); // exponent
759                            let r_reg = self.alloc_reg(); // result
760                            // store base
761                            self.emit_expr(&args[0]);
762                            self.code.push(0x08); self.code.push(b_reg);
763                            // store exp
764                            self.emit_expr(&args[1]);
765                            self.code.push(0x08); self.code.push(e_reg);
766                            // result = 1
767                            self.code.push(0x17); self.code.extend_from_slice(&1i64.to_le_bytes());
768                            self.code.push(0x08); self.code.push(r_reg);
769                            // loop_start: check e > 0
770                            let loop_start = self.code.len() as u16;
771                            self.code.push(0x09); self.code.push(e_reg);  // TLOAD e
772                            self.code.push(0x17); self.code.extend_from_slice(&0i64.to_le_bytes()); // push 0
773                            self.code.push(0x15);  // Tgreater: e > 0 → Affirm
774                            // TjmpPos → jump to loop body
775                            let body_patch = self.code.len() + 1;
776                            self.code.push(0x05); self.code.extend_from_slice(&[0, 0]);
777                            // e <= 0: pop cmp, jump to end
778                            self.code.push(0x0c);
779                            let end_patch = self.code.len() + 1;
780                            self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]);
781                            // loop body:
782                            let body_addr = self.code.len() as u16;
783                            self.patch_u16(body_patch, body_addr);
784                            self.code.push(0x0c);  // TPOP cmp
785                            // r = r * b
786                            self.code.push(0x09); self.code.push(r_reg);
787                            self.code.push(0x09); self.code.push(b_reg);
788                            self.code.push(0x03);  // Tmul (handles int*int)
789                            self.code.push(0x08); self.code.push(r_reg);
790                            // e = e - 1
791                            self.code.push(0x09); self.code.push(e_reg);
792                            self.code.push(0x17); self.code.extend_from_slice(&(-1i64).to_le_bytes());
793                            self.code.push(0x18);  // TaddInt
794                            self.code.push(0x08); self.code.push(e_reg);
795                            // jump back to loop_start
796                            self.code.push(0x0b); self.code.extend_from_slice(&loop_start.to_le_bytes());
797                            // end:
798                            let end_addr = self.code.len() as u16;
799                            self.patch_u16(end_patch, end_addr);
800                            // push result
801                            self.code.push(0x09); self.code.push(r_reg);
802                        }
803                    }
804                    // `push(arr, val)` / `pop(arr)` — tensor mutation not yet implemented.
805                    // Emit argument expressions for side-effects then push a Tend stub so
806                    // callers get a value without falling through to an unresolved TCALL 0x0000
807                    // (which causes infinite recursion via jump-to-program-start).
808                    "push" => {
809                        for a in args { self.emit_expr(a); self.code.push(0x0c); } // eval + discard
810                        self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend])); // stub result
811                    }
812                    "pop" => {
813                        for a in args { self.emit_expr(a); self.code.push(0x0c); } // eval + discard
814                        self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend])); // stub result
815                    }
816                    "mul" => {
817                        for a in args { self.emit_expr(a); }
818                        if args.len() == 2 { self.code.push(0x03); }
819                    }
820                    "truth" => { self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Affirm])); }
821                    "hold" => { self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend])); }
822                    "conflict" => { self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Reject])); }
823                    _ => {
824                        for a in args {
825                            // If argument is a struct, we need to push all its flattened fields + root dummy
826                            let mut pushed_as_struct = false;
827                            if let Expr::Ident(name) = a {
828                                // We don't have the variable type here, but we can try to find if it's a struct
829                                // by looking for any mangled keys starting with "name.".
830                                // To get the correct field order, we'd need the struct name.
831                                // Let's try to find which struct layout matches the existing mangled keys.
832                                let mut fields_found = Vec::new();
833                                for (_s_name, s_fields) in &self.struct_layouts {
834                                    let mut all_present = true;
835                                    let mut current_regs = Vec::new();
836                                    for f in s_fields {
837                                        let key = format!("{}.{}", name, f);
838                                        if let Some(&r) = self.symbols.get(&key) {
839                                            current_regs.push(r);
840                                        } else {
841                                            all_present = false;
842                                            break;
843                                        }
844                                    }
845                                    if all_present && !s_fields.is_empty() {
846                                        fields_found = current_regs;
847                                        break;
848                                    }
849                                }
850
851                                if !fields_found.is_empty() {
852                                    for reg in fields_found {
853                                        self.code.push(0x09); self.code.push(reg); // TLOAD field
854                                    }
855                                    // Push root dummy
856                                    if let Some(&reg) = self.symbols.get(name) {
857                                        self.code.push(0x09); self.code.push(reg); // TLOAD root
858                                    }
859                                    pushed_as_struct = true;
860                                }
861                            }
862                            
863                            if !pushed_as_struct {
864                                self.emit_expr(a);
865                            }
866                        }
867                        self.code.push(0x10); // TCALL
868                        if let Some(&addr) = self.func_addrs.get(callee) {
869                            self.code.extend_from_slice(&addr.to_le_bytes());
870                        } else {
871                            let patch = self.code.len();
872                            self.code.extend_from_slice(&[0, 0]);
873                            self.function_patches.entry(callee.to_string()).or_default().push(patch);
874                        }
875                    }
876                }
877            }
878            Expr::Spawn { agent_name, .. } => {
879                if let Some(&type_id) = self.agent_type_ids.get(agent_name) {
880                    self.code.push(0x30); // TSPAWN
881                    self.code.extend_from_slice(&type_id.to_le_bytes());
882                } else {
883                    self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend]));
884                }
885            }
886            Expr::Await { target } => {
887                self.emit_expr(target);
888                self.code.push(0x32); // TAWAIT
889            }
890            Expr::TritTensorLiteral(vs) => {
891                let rows = vs.len();
892                let cols = 1;
893                self.code.push(0x0f);
894                self.code.extend_from_slice(&(rows as u16).to_le_bytes());
895                self.code.extend_from_slice(&(cols as u16).to_le_bytes());
896                let tr = self.next_reg; self.next_reg += 1;
897                self.code.push(0x08); self.code.push(tr.try_into().unwrap());
898                for (idx, &v) in vs.iter().enumerate() {
899                    self.code.push(0x09); self.code.push(tr.try_into().unwrap());
900                    self.code.push(0x17); self.code.extend_from_slice(&(idx as i64).to_le_bytes());
901                    self.code.push(0x17); self.code.extend_from_slice(&0i64.to_le_bytes());
902                    self.code.push(0x01); self.code.extend(pack_trits(&[Trit::from(v)]));
903                    self.code.push(0x23);
904                }
905                self.code.push(0x09); self.code.push(tr.try_into().unwrap());
906            }
907            Expr::StructLiteral { .. } => {
908                // Struct literals are largely handled by Stmt::Let by flattening.
909                // We push a dummy hold so Return(struct_lit) or Let works consistently.
910                self.code.push(0x01); self.code.extend(pack_trits(&[Trit::Tend]));
911            }
912            Expr::Propagate { expr } => {
913                self.emit_expr(expr);
914                self.code.push(0x0a); // TDUP
915                let patch = self.code.len() + 1;
916                self.code.push(0x07); self.code.extend_from_slice(&[0, 0]); // TJMP_NEG
917                let skip = self.code.len() + 1;
918                self.code.push(0x0b); self.code.extend_from_slice(&[0, 0]); // TJMP
919                let early_ret = self.code.len() as u16;
920                self.patch_u16(patch, early_ret);
921                self.code.push(0x11); // TRET
922                let next = self.code.len() as u16;
923                self.patch_u16(skip, next);
924            }
925            Expr::Index { object, row, col } => {
926                self.emit_expr(object); self.emit_expr(row); self.emit_expr(col);
927                self.code.push(0x22);
928            }
929            Expr::FieldAccess { object, field } => {
930                if let Expr::Ident(obj_name) = object.as_ref() {
931                    let key = format!("{}.{}", obj_name, field);
932                    if let Some(&r) = self.symbols.get(&key) {
933                        self.code.push(0x09); self.code.push(r); // TLOAD
934                    }
935                }
936            }
937            Expr::Cast { expr, .. } => {
938                // cast() is a type annotation hint only — pass inner expression through
939                self.emit_expr(expr);
940            }
941            Expr::NodeId => {
942                // Emit TNODEID (0x36): defers binding to runtime so that
943                // `--node-addr` / vm.set_node_id() is actually respected.
944                // Previously this emitted a hardcoded "127.0.0.1:7373" string
945                // literal at compile time, which meant distributed modules always
946                // announced the wrong address when deployed with a custom node addr.
947                self.code.push(0x36); // TNODEID — pushes Value::String(vm.node_id)
948            }
949        }
950    }
951
952    pub fn emit_entry_call(&mut self, name: &str) {
953        if let Some(&addr) = self.func_addrs.get(name) {
954            self.code.push(0x10); self.code.extend_from_slice(&addr.to_le_bytes());
955        }
956    }
957
958    /// Allocate the next register, returning its index as `u8` (the bytecode register width).
959    /// Emits a stderr diagnostic if the function requires more than 255 registers — programs
960    /// that hit this have much bigger structural problems anyway.
961    fn alloc_reg(&mut self) -> u8 {
962        let r = self.next_reg;
963        self.next_reg += 1;
964        if r > 255 {
965            eprintln!(
966                "[CODEGEN] Warning: register #{r} exceeds u8 range — \
967                 this function has too many local variables (max 255). \
968                 Split the function or reduce scope depth."
969            );
970        }
971        r as u8
972    }
973
974    pub fn get_agent_handlers(&self) -> Vec<(u16, usize)> {
975        self.agent_handlers.iter().map(|&(id, addr)| (id, addr as usize)).collect()
976    }
977
978    pub fn finalize(&mut self) -> Vec<u8> { std::mem::take(&mut self.code) }
979
980    fn patch_u16(&mut self, pos: usize, val: u16) {
981        let b = val.to_le_bytes();
982        self.code[pos] = b[0]; self.code[pos + 1] = b[1];
983    }
984}