Skip to main content

oxilean_runtime/bytecode_interp/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{
6    BasicBlock, BytecodeChunk, BytecodeCompiler, CallResult, ChunkBuilder, ChunkStats,
7    ConstantFolder, DeadCodeEliminator, EncodedInstruction, FramedInterpreter, InlineCache,
8    Interpreter, LivenessInfo, Opcode, OpcodeInfo, OpcodeProfile, PeepholeOptimizer,
9    ProfilingInterpreter, StackValue,
10};
11
12#[cfg(test)]
13mod tests {
14    use super::*;
15    #[test]
16    fn test_compile_nat_executes() {
17        let chunk = BytecodeCompiler::compile_nat(42);
18        let mut interp = Interpreter::new();
19        let result = interp
20            .execute_chunk(&chunk)
21            .expect("execution should succeed");
22        assert_eq!(result, StackValue::Nat(42));
23    }
24    #[test]
25    fn test_compile_add_executes() {
26        let chunk = BytecodeCompiler::compile_add(10, 32);
27        let mut interp = Interpreter::new();
28        let result = interp
29            .execute_chunk(&chunk)
30            .expect("execution should succeed");
31        assert_eq!(result, StackValue::Nat(42));
32    }
33    #[test]
34    fn test_compile_if_true() {
35        let chunk = BytecodeCompiler::compile_if(true, 100, 200);
36        let mut interp = Interpreter::new();
37        let result = interp
38            .execute_chunk(&chunk)
39            .expect("execution should succeed");
40        assert_eq!(result, StackValue::Nat(100));
41    }
42    #[test]
43    fn test_compile_if_false() {
44        let chunk = BytecodeCompiler::compile_if(false, 100, 200);
45        let mut interp = Interpreter::new();
46        let result = interp
47            .execute_chunk(&chunk)
48            .expect("execution should succeed");
49        assert_eq!(result, StackValue::Nat(200));
50    }
51    #[test]
52    fn test_push_bool() {
53        let mut chunk = BytecodeChunk::new("test");
54        chunk.push_op(Opcode::PushBool(true));
55        chunk.push_op(Opcode::Halt);
56        let mut interp = Interpreter::new();
57        let result = interp
58            .execute_chunk(&chunk)
59            .expect("execution should succeed");
60        assert_eq!(result, StackValue::Bool(true));
61    }
62    #[test]
63    fn test_push_str() {
64        let mut chunk = BytecodeChunk::new("test");
65        chunk.push_op(Opcode::PushStr("hello".to_string()));
66        chunk.push_op(Opcode::Halt);
67        let mut interp = Interpreter::new();
68        let result = interp
69            .execute_chunk(&chunk)
70            .expect("execution should succeed");
71        assert_eq!(result, StackValue::Str("hello".to_string()));
72    }
73    #[test]
74    fn test_eq_same() {
75        let mut chunk = BytecodeChunk::new("test");
76        chunk.push_op(Opcode::Push(5));
77        chunk.push_op(Opcode::Push(5));
78        chunk.push_op(Opcode::Eq);
79        chunk.push_op(Opcode::Halt);
80        let mut interp = Interpreter::new();
81        let result = interp
82            .execute_chunk(&chunk)
83            .expect("execution should succeed");
84        assert_eq!(result, StackValue::Bool(true));
85    }
86    #[test]
87    fn test_lt_comparison() {
88        let mut chunk = BytecodeChunk::new("test");
89        chunk.push_op(Opcode::Push(3));
90        chunk.push_op(Opcode::Push(5));
91        chunk.push_op(Opcode::Lt);
92        chunk.push_op(Opcode::Halt);
93        let mut interp = Interpreter::new();
94        let result = interp
95            .execute_chunk(&chunk)
96            .expect("execution should succeed");
97        assert_eq!(result, StackValue::Bool(true));
98    }
99    #[test]
100    fn test_div_by_zero_error() {
101        let mut chunk = BytecodeChunk::new("test");
102        chunk.push_op(Opcode::Push(10));
103        chunk.push_op(Opcode::Push(0));
104        chunk.push_op(Opcode::Div);
105        chunk.push_op(Opcode::Halt);
106        let mut interp = Interpreter::new();
107        let result = interp.execute_chunk(&chunk);
108        assert!(result.is_err());
109        assert!(result.unwrap_err().contains("division by zero"));
110    }
111}
112/// Serialize an entire [`BytecodeChunk`] to a flat byte vector.
113#[allow(dead_code)]
114pub fn serialize_chunk(chunk: &BytecodeChunk) -> Vec<u8> {
115    let mut out = Vec::new();
116    let name_bytes = chunk.name.as_bytes();
117    out.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
118    out.extend_from_slice(name_bytes);
119    out.extend_from_slice(&(chunk.opcodes.len() as u32).to_le_bytes());
120    for op in &chunk.opcodes {
121        let enc = EncodedInstruction::encode(op);
122        out.extend_from_slice(&enc.bytes);
123    }
124    out
125}
126/// Deserialize a [`BytecodeChunk`] from bytes produced by [`serialize_chunk`].
127#[allow(dead_code)]
128pub fn deserialize_chunk(data: &[u8]) -> Option<BytecodeChunk> {
129    if data.len() < 4 {
130        return None;
131    }
132    let name_len = u32::from_le_bytes(data[0..4].try_into().ok()?) as usize;
133    if data.len() < 4 + name_len + 4 {
134        return None;
135    }
136    let name = std::str::from_utf8(&data[4..4 + name_len])
137        .ok()?
138        .to_string();
139    let mut pos = 4 + name_len;
140    let op_count = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
141    pos += 4;
142    let mut chunk = BytecodeChunk::new(&name);
143    for _ in 0..op_count {
144        let (op, consumed) = EncodedInstruction::decode(&data[pos..])?;
145        chunk.push_op(op);
146        pos += consumed;
147    }
148    Some(chunk)
149}
150/// Disassemble a [`BytecodeChunk`] into a human-readable string.
151#[allow(dead_code)]
152pub fn disassemble(chunk: &BytecodeChunk) -> String {
153    let mut out = String::new();
154    out.push_str(&format!(
155        "=== chunk: {} ({} ops) ===\n",
156        chunk.name,
157        chunk.opcodes.len()
158    ));
159    for (i, op) in chunk.opcodes.iter().enumerate() {
160        out.push_str(&format!("{:04}  {}\n", i, format_op(op)));
161    }
162    out
163}
164/// Format a single opcode as a string.
165#[allow(dead_code)]
166pub fn format_op(op: &Opcode) -> String {
167    match op {
168        Opcode::Push(n) => format!("PUSH        {}", n),
169        Opcode::PushBool(b) => format!("PUSH_BOOL   {}", b),
170        Opcode::PushStr(s) => format!("PUSH_STR    {:?}", s),
171        Opcode::PushNil => "PUSH_NIL".to_string(),
172        Opcode::Pop => "POP".to_string(),
173        Opcode::Dup => "DUP".to_string(),
174        Opcode::Swap => "SWAP".to_string(),
175        Opcode::Add => "ADD".to_string(),
176        Opcode::Sub => "SUB".to_string(),
177        Opcode::Mul => "MUL".to_string(),
178        Opcode::Div => "DIV".to_string(),
179        Opcode::Mod => "MOD".to_string(),
180        Opcode::Eq => "EQ".to_string(),
181        Opcode::Lt => "LT".to_string(),
182        Opcode::Le => "LE".to_string(),
183        Opcode::Not => "NOT".to_string(),
184        Opcode::And => "AND".to_string(),
185        Opcode::Or => "OR".to_string(),
186        Opcode::Jump(off) => format!("JUMP        {:+}", off),
187        Opcode::JumpIf(off) => format!("JUMP_IF     {:+}", off),
188        Opcode::JumpIfNot(off) => format!("JUMP_IFNOT  {:+}", off),
189        Opcode::Call(pos) => format!("CALL        @{}", pos),
190        Opcode::Return => "RETURN".to_string(),
191        Opcode::Load(idx) => format!("LOAD        L{}", idx),
192        Opcode::Store(idx) => format!("STORE       L{}", idx),
193        Opcode::LoadGlobal(n) => format!("LOAD_GLOBAL {}", n),
194        Opcode::MakeClosure(n) => format!("MAKE_CLOSURE {}", n),
195        Opcode::Apply => "APPLY".to_string(),
196        Opcode::Halt => "HALT".to_string(),
197    }
198}
199/// Return the full dispatch table for all supported opcodes.
200#[allow(dead_code)]
201pub fn opcode_dispatch_table() -> Vec<OpcodeInfo> {
202    vec![
203        OpcodeInfo {
204            tag: 0x01,
205            mnemonic: "PUSH",
206            byte_size: 9,
207            is_branch: false,
208            is_terminator: false,
209        },
210        OpcodeInfo {
211            tag: 0x02,
212            mnemonic: "PUSH_BOOL",
213            byte_size: 2,
214            is_branch: false,
215            is_terminator: false,
216        },
217        OpcodeInfo {
218            tag: 0x03,
219            mnemonic: "PUSH_STR",
220            byte_size: 0,
221            is_branch: false,
222            is_terminator: false,
223        },
224        OpcodeInfo {
225            tag: 0x04,
226            mnemonic: "PUSH_NIL",
227            byte_size: 1,
228            is_branch: false,
229            is_terminator: false,
230        },
231        OpcodeInfo {
232            tag: 0x10,
233            mnemonic: "POP",
234            byte_size: 1,
235            is_branch: false,
236            is_terminator: false,
237        },
238        OpcodeInfo {
239            tag: 0x11,
240            mnemonic: "DUP",
241            byte_size: 1,
242            is_branch: false,
243            is_terminator: false,
244        },
245        OpcodeInfo {
246            tag: 0x12,
247            mnemonic: "SWAP",
248            byte_size: 1,
249            is_branch: false,
250            is_terminator: false,
251        },
252        OpcodeInfo {
253            tag: 0x20,
254            mnemonic: "ADD",
255            byte_size: 1,
256            is_branch: false,
257            is_terminator: false,
258        },
259        OpcodeInfo {
260            tag: 0x21,
261            mnemonic: "SUB",
262            byte_size: 1,
263            is_branch: false,
264            is_terminator: false,
265        },
266        OpcodeInfo {
267            tag: 0x22,
268            mnemonic: "MUL",
269            byte_size: 1,
270            is_branch: false,
271            is_terminator: false,
272        },
273        OpcodeInfo {
274            tag: 0x23,
275            mnemonic: "DIV",
276            byte_size: 1,
277            is_branch: false,
278            is_terminator: false,
279        },
280        OpcodeInfo {
281            tag: 0x24,
282            mnemonic: "MOD",
283            byte_size: 1,
284            is_branch: false,
285            is_terminator: false,
286        },
287        OpcodeInfo {
288            tag: 0x30,
289            mnemonic: "EQ",
290            byte_size: 1,
291            is_branch: false,
292            is_terminator: false,
293        },
294        OpcodeInfo {
295            tag: 0x31,
296            mnemonic: "LT",
297            byte_size: 1,
298            is_branch: false,
299            is_terminator: false,
300        },
301        OpcodeInfo {
302            tag: 0x32,
303            mnemonic: "LE",
304            byte_size: 1,
305            is_branch: false,
306            is_terminator: false,
307        },
308        OpcodeInfo {
309            tag: 0x40,
310            mnemonic: "NOT",
311            byte_size: 1,
312            is_branch: false,
313            is_terminator: false,
314        },
315        OpcodeInfo {
316            tag: 0x41,
317            mnemonic: "AND",
318            byte_size: 1,
319            is_branch: false,
320            is_terminator: false,
321        },
322        OpcodeInfo {
323            tag: 0x42,
324            mnemonic: "OR",
325            byte_size: 1,
326            is_branch: false,
327            is_terminator: false,
328        },
329        OpcodeInfo {
330            tag: 0x50,
331            mnemonic: "JUMP",
332            byte_size: 5,
333            is_branch: true,
334            is_terminator: true,
335        },
336        OpcodeInfo {
337            tag: 0x51,
338            mnemonic: "JUMP_IF",
339            byte_size: 5,
340            is_branch: true,
341            is_terminator: false,
342        },
343        OpcodeInfo {
344            tag: 0x52,
345            mnemonic: "JUMP_IFNOT",
346            byte_size: 5,
347            is_branch: true,
348            is_terminator: false,
349        },
350        OpcodeInfo {
351            tag: 0x60,
352            mnemonic: "CALL",
353            byte_size: 5,
354            is_branch: true,
355            is_terminator: false,
356        },
357        OpcodeInfo {
358            tag: 0x61,
359            mnemonic: "RETURN",
360            byte_size: 1,
361            is_branch: true,
362            is_terminator: true,
363        },
364        OpcodeInfo {
365            tag: 0x70,
366            mnemonic: "LOAD",
367            byte_size: 5,
368            is_branch: false,
369            is_terminator: false,
370        },
371        OpcodeInfo {
372            tag: 0x71,
373            mnemonic: "STORE",
374            byte_size: 5,
375            is_branch: false,
376            is_terminator: false,
377        },
378        OpcodeInfo {
379            tag: 0x72,
380            mnemonic: "LOAD_GLOBAL",
381            byte_size: 0,
382            is_branch: false,
383            is_terminator: false,
384        },
385        OpcodeInfo {
386            tag: 0x80,
387            mnemonic: "MAKE_CLOSURE",
388            byte_size: 5,
389            is_branch: false,
390            is_terminator: false,
391        },
392        OpcodeInfo {
393            tag: 0x81,
394            mnemonic: "APPLY",
395            byte_size: 1,
396            is_branch: false,
397            is_terminator: false,
398        },
399        OpcodeInfo {
400            tag: 0xFF,
401            mnemonic: "HALT",
402            byte_size: 1,
403            is_branch: false,
404            is_terminator: true,
405        },
406    ]
407}
408/// Handle a function call according to arity conventions.
409#[allow(dead_code)]
410pub fn handle_call(fn_pos: u32, arity: u32, args: Vec<StackValue>) -> CallResult {
411    let n = args.len() as u32;
412    if n == arity {
413        CallResult::Exact { fn_pos, args }
414    } else if n < arity {
415        CallResult::Partial {
416            captured: args,
417            remaining_arity: arity - n,
418        }
419    } else {
420        let first_args = args[..arity as usize].to_vec();
421        let rest_args = args[arity as usize..].to_vec();
422        CallResult::Over {
423            fn_pos,
424            first_args,
425            rest_args,
426        }
427    }
428}
429/// Return a short string identifying the kind of opcode (for profiling keys).
430#[allow(dead_code)]
431pub(super) fn opcode_kind(op: &Opcode) -> String {
432    match op {
433        Opcode::Push(_) => "Push",
434        Opcode::PushBool(_) => "PushBool",
435        Opcode::PushStr(_) => "PushStr",
436        Opcode::PushNil => "PushNil",
437        Opcode::Pop => "Pop",
438        Opcode::Dup => "Dup",
439        Opcode::Swap => "Swap",
440        Opcode::Add => "Add",
441        Opcode::Sub => "Sub",
442        Opcode::Mul => "Mul",
443        Opcode::Div => "Div",
444        Opcode::Mod => "Mod",
445        Opcode::Eq => "Eq",
446        Opcode::Lt => "Lt",
447        Opcode::Le => "Le",
448        Opcode::Not => "Not",
449        Opcode::And => "And",
450        Opcode::Or => "Or",
451        Opcode::Jump(_) => "Jump",
452        Opcode::JumpIf(_) => "JumpIf",
453        Opcode::JumpIfNot(_) => "JumpIfNot",
454        Opcode::Call(_) => "Call",
455        Opcode::Return => "Return",
456        Opcode::Load(_) => "Load",
457        Opcode::Store(_) => "Store",
458        Opcode::LoadGlobal(_) => "LoadGlobal",
459        Opcode::MakeClosure(_) => "MakeClosure",
460        Opcode::Apply => "Apply",
461        Opcode::Halt => "Halt",
462    }
463    .to_string()
464}
465/// Perform basic block decomposition of a chunk.
466///
467/// Returns the list of basic blocks in order of their start offset.
468#[allow(dead_code)]
469pub fn find_basic_blocks(chunk: &BytecodeChunk) -> Vec<BasicBlock> {
470    let n = chunk.opcodes.len();
471    if n == 0 {
472        return Vec::new();
473    }
474    let mut leaders = std::collections::BTreeSet::new();
475    leaders.insert(0);
476    for (i, op) in chunk.opcodes.iter().enumerate() {
477        match op {
478            Opcode::Jump(off) => {
479                let target = (i as i64 + 1 + *off as i64) as usize;
480                if target < n {
481                    leaders.insert(target);
482                }
483                if i + 1 < n {
484                    leaders.insert(i + 1);
485                }
486            }
487            Opcode::JumpIf(off) | Opcode::JumpIfNot(off) => {
488                let target = (i as i64 + 1 + *off as i64) as usize;
489                if target < n {
490                    leaders.insert(target);
491                }
492                if i + 1 < n {
493                    leaders.insert(i + 1);
494                }
495            }
496            Opcode::Call(_) | Opcode::Return | Opcode::Halt => {
497                if i + 1 < n {
498                    leaders.insert(i + 1);
499                }
500            }
501            _ => {}
502        }
503    }
504    let leaders_vec: Vec<usize> = leaders.into_iter().collect();
505    let mut blocks = Vec::new();
506    for (k, &start) in leaders_vec.iter().enumerate() {
507        let end = if k + 1 < leaders_vec.len() {
508            leaders_vec[k + 1]
509        } else {
510            n
511        };
512        blocks.push(BasicBlock {
513            start,
514            end,
515            successors: Vec::new(),
516        });
517    }
518    blocks
519}
520/// Estimate the maximum stack depth required to execute a chunk.
521///
522/// This is a conservative estimate computed by simulating the abstract
523/// stack height through the instruction sequence (ignoring branches).
524#[allow(dead_code)]
525pub(super) fn estimate_max_stack_depth(chunk: &BytecodeChunk) -> usize {
526    let mut depth: i64 = 0;
527    let mut max_depth: i64 = 0;
528    for op in &chunk.opcodes {
529        depth += stack_delta(op);
530        if depth > max_depth {
531            max_depth = depth;
532        }
533    }
534    max_depth.max(0) as usize
535}
536/// Compute the net stack height change of a single opcode.
537#[allow(dead_code)]
538pub(super) fn stack_delta(op: &Opcode) -> i64 {
539    match op {
540        Opcode::Push(_) | Opcode::PushBool(_) | Opcode::PushStr(_) | Opcode::PushNil => 1,
541        Opcode::Pop => -1,
542        Opcode::Dup => 1,
543        Opcode::Swap => 0,
544        Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::Div | Opcode::Mod => -1,
545        Opcode::Eq | Opcode::Lt | Opcode::Le => -1,
546        Opcode::Not => 0,
547        Opcode::And | Opcode::Or => -1,
548        Opcode::Jump(_) => 0,
549        Opcode::JumpIf(_) | Opcode::JumpIfNot(_) => -1,
550        Opcode::Call(_) => 0,
551        Opcode::Return => 0,
552        Opcode::Load(_) => 1,
553        Opcode::Store(_) => 0,
554        Opcode::LoadGlobal(_) => 1,
555        Opcode::MakeClosure(n) => -(*n as i64) + 1,
556        Opcode::Apply => -1,
557        Opcode::Halt => 0,
558    }
559}
560#[cfg(test)]
561mod tests_extended {
562    use super::*;
563    #[test]
564    fn test_encode_decode_push() {
565        let op = Opcode::Push(12345);
566        let enc = EncodedInstruction::encode(&op);
567        let (decoded, consumed) =
568            EncodedInstruction::decode(&enc.bytes).expect("test operation should succeed");
569        assert_eq!(decoded, op);
570        assert_eq!(consumed, enc.bytes.len());
571    }
572    #[test]
573    fn test_encode_decode_push_str() {
574        let op = Opcode::PushStr("hello world".to_string());
575        let enc = EncodedInstruction::encode(&op);
576        let (decoded, _) =
577            EncodedInstruction::decode(&enc.bytes).expect("test operation should succeed");
578        assert_eq!(decoded, op);
579    }
580    #[test]
581    fn test_encode_decode_jump() {
582        let op = Opcode::Jump(-10);
583        let enc = EncodedInstruction::encode(&op);
584        let (decoded, consumed) =
585            EncodedInstruction::decode(&enc.bytes).expect("test operation should succeed");
586        assert_eq!(decoded, op);
587        assert_eq!(consumed, 5);
588    }
589    #[test]
590    fn test_encode_decode_halt() {
591        let op = Opcode::Halt;
592        let enc = EncodedInstruction::encode(&op);
593        let (decoded, consumed) =
594            EncodedInstruction::decode(&enc.bytes).expect("test operation should succeed");
595        assert_eq!(decoded, op);
596        assert_eq!(consumed, 1);
597    }
598    #[test]
599    fn test_serialize_deserialize_chunk() {
600        let chunk = BytecodeCompiler::compile_add(3, 4);
601        let bytes = serialize_chunk(&chunk);
602        let restored = deserialize_chunk(&bytes).expect("test operation should succeed");
603        assert_eq!(restored.name, chunk.name);
604        assert_eq!(restored.opcodes, chunk.opcodes);
605    }
606    #[test]
607    fn test_disassemble_smoke() {
608        let chunk = BytecodeCompiler::compile_nat(99);
609        let asm = disassemble(&chunk);
610        assert!(asm.contains("PUSH"));
611        assert!(asm.contains("HALT"));
612    }
613    #[test]
614    fn test_peephole_push_pop() {
615        let mut chunk = BytecodeChunk::new("test");
616        chunk.push_op(Opcode::Push(1));
617        chunk.push_op(Opcode::Pop);
618        chunk.push_op(Opcode::Push(42));
619        chunk.push_op(Opcode::Halt);
620        let opt = PeepholeOptimizer::new(1).optimize(&chunk);
621        assert_eq!(opt.opcodes.len(), 2);
622        assert_eq!(opt.opcodes[0], Opcode::Push(42));
623    }
624    #[test]
625    fn test_peephole_const_fold_add() {
626        let mut chunk = BytecodeChunk::new("test");
627        chunk.push_op(Opcode::Push(10));
628        chunk.push_op(Opcode::Push(20));
629        chunk.push_op(Opcode::Add);
630        chunk.push_op(Opcode::Halt);
631        let opt = PeepholeOptimizer::new(1).optimize(&chunk);
632        assert_eq!(opt.opcodes[0], Opcode::Push(30));
633        assert_eq!(opt.opcodes[1], Opcode::Halt);
634    }
635    #[test]
636    fn test_peephole_bool_and_fold() {
637        let mut chunk = BytecodeChunk::new("test");
638        chunk.push_op(Opcode::PushBool(true));
639        chunk.push_op(Opcode::PushBool(false));
640        chunk.push_op(Opcode::And);
641        chunk.push_op(Opcode::Halt);
642        let opt = PeepholeOptimizer::new(1).optimize(&chunk);
643        assert_eq!(opt.opcodes[0], Opcode::PushBool(false));
644    }
645    #[test]
646    fn test_peephole_not_fold() {
647        let mut chunk = BytecodeChunk::new("test");
648        chunk.push_op(Opcode::PushBool(true));
649        chunk.push_op(Opcode::Not);
650        chunk.push_op(Opcode::Halt);
651        let opt = PeepholeOptimizer::new(1).optimize(&chunk);
652        assert_eq!(opt.opcodes[0], Opcode::PushBool(false));
653    }
654    #[test]
655    fn test_handle_call_exact() {
656        let r = handle_call(5, 2, vec![StackValue::Nat(1), StackValue::Nat(2)]);
657        assert!(matches!(r, CallResult::Exact { fn_pos: 5, .. }));
658    }
659    #[test]
660    fn test_handle_call_partial() {
661        let r = handle_call(5, 3, vec![StackValue::Nat(1)]);
662        assert!(matches!(
663            r,
664            CallResult::Partial {
665                remaining_arity: 2,
666                ..
667            }
668        ));
669    }
670    #[test]
671    fn test_handle_call_over() {
672        let r = handle_call(
673            5,
674            2,
675            vec![StackValue::Nat(1), StackValue::Nat(2), StackValue::Nat(3)],
676        );
677        if let CallResult::Over { rest_args, .. } = r {
678            assert_eq!(rest_args.len(), 1);
679        } else {
680            panic!("expected Over");
681        }
682    }
683    #[test]
684    fn test_profiling_interpreter() {
685        let chunk = BytecodeCompiler::compile_add(5, 10);
686        let mut pi = ProfilingInterpreter::new();
687        let result = pi.execute_chunk(&chunk).expect("execution should succeed");
688        assert_eq!(result, StackValue::Nat(15));
689        assert!(pi.profile.count("Add") > 0);
690        assert!(pi.profile.total_executed > 0);
691    }
692    #[test]
693    fn test_opcode_profile_top_opcodes() {
694        let mut p = OpcodeProfile::new();
695        p.record(&Opcode::Push(1));
696        p.record(&Opcode::Push(2));
697        p.record(&Opcode::Add);
698        let top = p.top_opcodes();
699        assert_eq!(top[0].0, "Push");
700        assert_eq!(top[0].1, 2);
701    }
702    #[test]
703    fn test_find_basic_blocks_simple() {
704        let chunk = BytecodeCompiler::compile_nat(1);
705        let blocks = find_basic_blocks(&chunk);
706        assert!(!blocks.is_empty());
707    }
708    #[test]
709    fn test_find_basic_blocks_if() {
710        let chunk = BytecodeCompiler::compile_if(true, 1, 2);
711        let blocks = find_basic_blocks(&chunk);
712        assert!(blocks.len() >= 2);
713    }
714    #[test]
715    fn test_estimate_max_stack_depth_add() {
716        let chunk = BytecodeCompiler::compile_add(1, 2);
717        let depth = estimate_max_stack_depth(&chunk);
718        assert!(depth >= 2);
719    }
720    #[test]
721    fn test_framed_interpreter_push_pop() {
722        let mut fi = FramedInterpreter::new();
723        fi.push_frame(0, "main")
724            .expect("test operation should succeed");
725        assert_eq!(fi.depth(), 1);
726        let ret = fi.pop_frame().expect("test operation should succeed");
727        assert_eq!(ret, 0);
728        assert_eq!(fi.depth(), 0);
729    }
730    #[test]
731    fn test_framed_interpreter_max_depth() {
732        let mut fi = FramedInterpreter::new().with_max_depth(2);
733        fi.push_frame(0, "a")
734            .expect("test operation should succeed");
735        fi.push_frame(1, "b")
736            .expect("test operation should succeed");
737        let err = fi.push_frame(2, "c");
738        assert!(err.is_err());
739    }
740    #[test]
741    fn test_framed_interpreter_stack_trace() {
742        let mut fi = FramedInterpreter::new();
743        fi.push_frame(0, "main")
744            .expect("test operation should succeed");
745        fi.push_frame(10, "helper")
746            .expect("test operation should succeed");
747        let trace = fi.stack_trace();
748        assert!(trace.contains("helper"));
749        assert!(trace.contains("main"));
750    }
751    #[test]
752    fn test_opcode_dispatch_table_not_empty() {
753        let table = opcode_dispatch_table();
754        assert!(!table.is_empty());
755        assert!(table.iter().any(|i| i.mnemonic == "HALT"));
756    }
757    #[test]
758    fn test_decode_unknown_tag() {
759        let result = EncodedInstruction::decode(&[0xAB]);
760        assert!(result.is_none());
761    }
762    #[test]
763    fn test_serialize_empty_chunk() {
764        let chunk = BytecodeChunk::new("empty");
765        let bytes = serialize_chunk(&chunk);
766        let restored = deserialize_chunk(&bytes).expect("test operation should succeed");
767        assert_eq!(restored.name, "empty");
768        assert!(restored.is_empty());
769    }
770    #[test]
771    fn test_format_op_mnemonic() {
772        assert_eq!(format_op(&Opcode::Halt), "HALT");
773        assert!(format_op(&Opcode::Push(0)).contains("PUSH"));
774        assert!(format_op(&Opcode::LoadGlobal("foo".to_string())).contains("LOAD_GLOBAL"));
775    }
776}
777/// Compute a simplified backward liveness analysis for local variables.
778///
779/// Only tracks `Load` and `Store` instructions as uses/defs of locals.
780#[allow(dead_code)]
781pub fn compute_liveness(chunk: &BytecodeChunk) -> LivenessInfo {
782    let n = chunk.opcodes.len();
783    let mut live_sets: Vec<std::collections::BTreeSet<u32>> = vec![Default::default(); n + 1];
784    for i in (0..n).rev() {
785        let mut live = live_sets[i + 1].clone();
786        match &chunk.opcodes[i] {
787            Opcode::Load(idx) => {
788                live.insert(*idx);
789            }
790            Opcode::Store(idx) => {
791                live.remove(idx);
792            }
793            _ => {}
794        }
795        live_sets[i] = live;
796    }
797    LivenessInfo {
798        live_before: live_sets[..n].to_vec(),
799    }
800}
801#[cfg(test)]
802mod tests_phase2 {
803    use super::*;
804    #[test]
805    fn test_constant_folder_add() {
806        let mut chunk = BytecodeChunk::new("t");
807        chunk.push_op(Opcode::Push(3));
808        chunk.push_op(Opcode::Push(4));
809        chunk.push_op(Opcode::Add);
810        chunk.push_op(Opcode::Halt);
811        let folded = ConstantFolder::fold(&chunk);
812        let mut interp = Interpreter::new();
813        let result = interp
814            .execute_chunk(&folded)
815            .expect("execution should succeed");
816        assert_eq!(result, StackValue::Nat(7));
817    }
818    #[test]
819    fn test_constant_folder_mul() {
820        let mut chunk = BytecodeChunk::new("t");
821        chunk.push_op(Opcode::Push(6));
822        chunk.push_op(Opcode::Push(7));
823        chunk.push_op(Opcode::Mul);
824        chunk.push_op(Opcode::Halt);
825        let folded = ConstantFolder::fold(&chunk);
826        let mut interp = Interpreter::new();
827        let result = interp
828            .execute_chunk(&folded)
829            .expect("execution should succeed");
830        assert_eq!(result, StackValue::Nat(42));
831    }
832    #[test]
833    fn test_dead_code_elimination_after_halt() {
834        let mut chunk = BytecodeChunk::new("t");
835        chunk.push_op(Opcode::Push(1));
836        chunk.push_op(Opcode::Halt);
837        chunk.push_op(Opcode::Push(2));
838        chunk.push_op(Opcode::Halt);
839        let elim = DeadCodeEliminator::eliminate(&chunk);
840        assert_eq!(elim.opcodes.len(), 2);
841    }
842    #[test]
843    fn test_dead_code_elimination_jump() {
844        let mut chunk = BytecodeChunk::new("t");
845        chunk.push_op(Opcode::Jump(1));
846        chunk.push_op(Opcode::Push(99));
847        chunk.push_op(Opcode::Push(1));
848        chunk.push_op(Opcode::Halt);
849        let elim = DeadCodeEliminator::eliminate(&chunk);
850        assert!(!elim.opcodes.contains(&Opcode::Push(99)));
851    }
852    #[test]
853    fn test_inline_cache_monomorphic() {
854        let mut ic = InlineCache::new();
855        ic.record(10, 5);
856        ic.record(10, 5);
857        ic.record(10, 5);
858        let entry = ic.entry(10).expect("test operation should succeed");
859        assert!(entry.is_monomorphic);
860        assert_eq!(entry.hit_count, 3);
861    }
862    #[test]
863    fn test_inline_cache_polymorphic() {
864        let mut ic = InlineCache::new();
865        ic.record(10, 5);
866        ic.record(10, 6);
867        let entry = ic.entry(10).expect("test operation should succeed");
868        assert!(!entry.is_monomorphic);
869    }
870    #[test]
871    fn test_inline_cache_monomorphic_sites() {
872        let mut ic = InlineCache::new();
873        ic.record(0, 1);
874        ic.record(5, 2);
875        ic.record(5, 3);
876        let mono = ic.monomorphic_sites();
877        assert!(mono.contains(&0));
878        assert!(!mono.contains(&5));
879    }
880    #[test]
881    fn test_liveness_load_store() {
882        let mut chunk = BytecodeChunk::new("t");
883        chunk.push_op(Opcode::Push(42));
884        chunk.push_op(Opcode::Store(0));
885        chunk.push_op(Opcode::Load(0));
886        chunk.push_op(Opcode::Halt);
887        let info = compute_liveness(&chunk);
888        assert!(info.live_before[2].contains(&0));
889    }
890    #[test]
891    fn test_chunk_stats() {
892        let chunk = BytecodeCompiler::compile_if(true, 10, 20);
893        let stats = ChunkStats::compute(&chunk);
894        assert!(stats.total_instructions > 0);
895        assert!(stats.branch_count > 0);
896    }
897    #[test]
898    fn test_stack_value_display_nat() {
899        assert_eq!(format!("{}", StackValue::Nat(42)), "42");
900    }
901    #[test]
902    fn test_stack_value_display_bool() {
903        assert_eq!(format!("{}", StackValue::Bool(false)), "false");
904    }
905    #[test]
906    fn test_stack_value_display_nil() {
907        assert_eq!(format!("{}", StackValue::Nil), "nil");
908    }
909    #[test]
910    fn test_opcode_profile_reset() {
911        let mut p = OpcodeProfile::new();
912        p.record(&Opcode::Push(1));
913        p.reset();
914        assert_eq!(p.total_executed, 0);
915        assert_eq!(p.count("Push"), 0);
916    }
917    #[test]
918    fn test_framed_interpreter_reset() {
919        let mut fi = FramedInterpreter::new();
920        fi.push_frame(0, "f")
921            .expect("test operation should succeed");
922        fi.stack.push(StackValue::Nat(1));
923        fi.reset();
924        assert!(fi.stack.is_empty());
925        assert_eq!(fi.depth(), 0);
926        assert_eq!(fi.ip, 0);
927    }
928    #[test]
929    fn test_frame_at_depth() {
930        let mut fi = FramedInterpreter::new();
931        fi.push_frame(0, "outer")
932            .expect("test operation should succeed");
933        fi.push_frame(10, "inner")
934            .expect("test operation should succeed");
935        assert_eq!(
936            fi.frame(0).expect("test operation should succeed").fn_name,
937            "inner"
938        );
939        assert_eq!(
940            fi.frame(1).expect("test operation should succeed").fn_name,
941            "outer"
942        );
943    }
944    #[test]
945    fn test_encode_load_store() {
946        let load = Opcode::Load(7);
947        let enc = EncodedInstruction::encode(&load);
948        let (decoded, _) =
949            EncodedInstruction::decode(&enc.bytes).expect("test operation should succeed");
950        assert_eq!(decoded, load);
951        let store = Opcode::Store(3);
952        let enc2 = EncodedInstruction::encode(&store);
953        let (decoded2, _) =
954            EncodedInstruction::decode(&enc2.bytes).expect("test operation should succeed");
955        assert_eq!(decoded2, store);
956    }
957    #[test]
958    fn test_encode_make_closure() {
959        let op = Opcode::MakeClosure(5);
960        let enc = EncodedInstruction::encode(&op);
961        let (decoded, _) =
962            EncodedInstruction::decode(&enc.bytes).expect("test operation should succeed");
963        assert_eq!(decoded, op);
964    }
965    #[test]
966    fn test_encode_load_global() {
967        let op = Opcode::LoadGlobal("Nat.add".to_string());
968        let enc = EncodedInstruction::encode(&op);
969        let (decoded, _) =
970            EncodedInstruction::decode(&enc.bytes).expect("test operation should succeed");
971        assert_eq!(decoded, op);
972    }
973    #[test]
974    fn test_chunk_stats_arith() {
975        let mut chunk = BytecodeChunk::new("t");
976        chunk.push_op(Opcode::Push(1));
977        chunk.push_op(Opcode::Push(2));
978        chunk.push_op(Opcode::Add);
979        chunk.push_op(Opcode::Push(3));
980        chunk.push_op(Opcode::Mul);
981        chunk.push_op(Opcode::Halt);
982        let stats = ChunkStats::compute(&chunk);
983        assert_eq!(stats.arith_count, 2);
984    }
985    #[test]
986    fn test_peephole_mul_fold() {
987        let mut chunk = BytecodeChunk::new("t");
988        chunk.push_op(Opcode::Push(3));
989        chunk.push_op(Opcode::Push(3));
990        chunk.push_op(Opcode::Mul);
991        chunk.push_op(Opcode::Halt);
992        let opt = PeepholeOptimizer::new(1).optimize(&chunk);
993        assert_eq!(opt.opcodes[0], Opcode::Push(9));
994    }
995    #[test]
996    fn test_peephole_or_fold() {
997        let mut chunk = BytecodeChunk::new("t");
998        chunk.push_op(Opcode::PushBool(false));
999        chunk.push_op(Opcode::PushBool(true));
1000        chunk.push_op(Opcode::Or);
1001        chunk.push_op(Opcode::Halt);
1002        let opt = PeepholeOptimizer::new(1).optimize(&chunk);
1003        assert_eq!(opt.opcodes[0], Opcode::PushBool(true));
1004    }
1005    #[test]
1006    fn test_deserialize_invalid_data() {
1007        assert!(deserialize_chunk(&[]).is_none());
1008        assert!(deserialize_chunk(&[0xFF, 0xFF]).is_none());
1009    }
1010}
1011#[cfg(test)]
1012mod tests_builder {
1013    use super::*;
1014    #[test]
1015    fn test_chunk_builder_basic() {
1016        let chunk = ChunkBuilder::new("test")
1017            .push_nat(10)
1018            .push_nat(20)
1019            .add()
1020            .halt()
1021            .build();
1022        let mut interp = Interpreter::new();
1023        let result = interp
1024            .execute_chunk(&chunk)
1025            .expect("execution should succeed");
1026        assert_eq!(result, StackValue::Nat(30));
1027    }
1028    #[test]
1029    fn test_chunk_builder_sub() {
1030        let chunk = ChunkBuilder::new("sub")
1031            .push_nat(100)
1032            .push_nat(58)
1033            .sub()
1034            .halt()
1035            .build();
1036        let mut interp = Interpreter::new();
1037        let result = interp
1038            .execute_chunk(&chunk)
1039            .expect("execution should succeed");
1040        assert_eq!(result, StackValue::Nat(42));
1041    }
1042    #[test]
1043    fn test_chunk_builder_mul() {
1044        let chunk = ChunkBuilder::new("mul")
1045            .push_nat(6)
1046            .push_nat(7)
1047            .mul()
1048            .halt()
1049            .build();
1050        let mut interp = Interpreter::new();
1051        let result = interp
1052            .execute_chunk(&chunk)
1053            .expect("execution should succeed");
1054        assert_eq!(result, StackValue::Nat(42));
1055    }
1056    #[test]
1057    fn test_chunk_builder_div() {
1058        let chunk = ChunkBuilder::new("div")
1059            .push_nat(84)
1060            .push_nat(2)
1061            .div()
1062            .halt()
1063            .build();
1064        let mut interp = Interpreter::new();
1065        let result = interp
1066            .execute_chunk(&chunk)
1067            .expect("execution should succeed");
1068        assert_eq!(result, StackValue::Nat(42));
1069    }
1070    #[test]
1071    fn test_chunk_builder_mod() {
1072        let chunk = ChunkBuilder::new("mod")
1073            .push_nat(43)
1074            .push_nat(10)
1075            .modulo()
1076            .halt()
1077            .build();
1078        let mut interp = Interpreter::new();
1079        let result = interp
1080            .execute_chunk(&chunk)
1081            .expect("execution should succeed");
1082        assert_eq!(result, StackValue::Nat(3));
1083    }
1084    #[test]
1085    fn test_chunk_builder_bool() {
1086        let chunk = ChunkBuilder::new("bool")
1087            .push_bool(true)
1088            .not()
1089            .halt()
1090            .build();
1091        let mut interp = Interpreter::new();
1092        let result = interp
1093            .execute_chunk(&chunk)
1094            .expect("execution should succeed");
1095        assert_eq!(result, StackValue::Bool(false));
1096    }
1097    #[test]
1098    fn test_chunk_builder_current_ip() {
1099        let b = ChunkBuilder::new("t").push_nat(1).push_nat(2);
1100        assert_eq!(b.current_ip(), 2);
1101    }
1102    #[test]
1103    fn test_chunk_builder_load_store() {
1104        let chunk = ChunkBuilder::new("ls")
1105            .push_nat(99)
1106            .store(0)
1107            .load(0)
1108            .halt()
1109            .build();
1110        let mut interp = Interpreter::new();
1111        let result = interp
1112            .execute_chunk(&chunk)
1113            .expect("execution should succeed");
1114        assert_eq!(result, StackValue::Nat(99));
1115    }
1116    #[test]
1117    fn test_chunk_builder_swap() {
1118        let chunk = ChunkBuilder::new("swap")
1119            .push_nat(1)
1120            .push_nat(2)
1121            .swap()
1122            .halt()
1123            .build();
1124        let mut interp = Interpreter::new();
1125        let result = interp
1126            .execute_chunk(&chunk)
1127            .expect("execution should succeed");
1128        assert_eq!(result, StackValue::Nat(1));
1129    }
1130    #[test]
1131    fn test_chunk_builder_dup() {
1132        let chunk = ChunkBuilder::new("dup")
1133            .push_nat(7)
1134            .dup()
1135            .add()
1136            .halt()
1137            .build();
1138        let mut interp = Interpreter::new();
1139        let result = interp
1140            .execute_chunk(&chunk)
1141            .expect("execution should succeed");
1142        assert_eq!(result, StackValue::Nat(14));
1143    }
1144    #[test]
1145    fn test_chunk_builder_eq() {
1146        let chunk = ChunkBuilder::new("eq")
1147            .push_nat(5)
1148            .push_nat(5)
1149            .eq()
1150            .halt()
1151            .build();
1152        let mut interp = Interpreter::new();
1153        let result = interp
1154            .execute_chunk(&chunk)
1155            .expect("execution should succeed");
1156        assert_eq!(result, StackValue::Bool(true));
1157    }
1158    #[test]
1159    fn test_chunk_builder_le() {
1160        let chunk = ChunkBuilder::new("le")
1161            .push_nat(3)
1162            .push_nat(3)
1163            .le()
1164            .halt()
1165            .build();
1166        let mut interp = Interpreter::new();
1167        let result = interp
1168            .execute_chunk(&chunk)
1169            .expect("execution should succeed");
1170        assert_eq!(result, StackValue::Bool(true));
1171    }
1172    #[test]
1173    fn test_chunk_builder_lt() {
1174        let chunk = ChunkBuilder::new("lt")
1175            .push_nat(2)
1176            .push_nat(5)
1177            .lt()
1178            .halt()
1179            .build();
1180        let mut interp = Interpreter::new();
1181        let result = interp
1182            .execute_chunk(&chunk)
1183            .expect("execution should succeed");
1184        assert_eq!(result, StackValue::Bool(true));
1185    }
1186    #[test]
1187    fn test_chunk_builder_and() {
1188        let chunk = ChunkBuilder::new("and")
1189            .push_bool(true)
1190            .push_bool(true)
1191            .and()
1192            .halt()
1193            .build();
1194        let mut interp = Interpreter::new();
1195        let result = interp
1196            .execute_chunk(&chunk)
1197            .expect("execution should succeed");
1198        assert_eq!(result, StackValue::Bool(true));
1199    }
1200    #[test]
1201    fn test_chunk_builder_or() {
1202        let chunk = ChunkBuilder::new("or")
1203            .push_bool(false)
1204            .push_bool(true)
1205            .or()
1206            .halt()
1207            .build();
1208        let mut interp = Interpreter::new();
1209        let result = interp
1210            .execute_chunk(&chunk)
1211            .expect("execution should succeed");
1212        assert_eq!(result, StackValue::Bool(true));
1213    }
1214    #[test]
1215    fn test_stack_delta_push() {
1216        assert_eq!(stack_delta(&Opcode::Push(0)), 1);
1217        assert_eq!(stack_delta(&Opcode::PushBool(false)), 1);
1218        assert_eq!(stack_delta(&Opcode::Pop), -1);
1219        assert_eq!(stack_delta(&Opcode::Dup), 1);
1220        assert_eq!(stack_delta(&Opcode::Swap), 0);
1221        assert_eq!(stack_delta(&Opcode::Halt), 0);
1222    }
1223    #[test]
1224    fn test_stack_delta_arithmetic() {
1225        assert_eq!(stack_delta(&Opcode::Add), -1);
1226        assert_eq!(stack_delta(&Opcode::Sub), -1);
1227        assert_eq!(stack_delta(&Opcode::Mul), -1);
1228        assert_eq!(stack_delta(&Opcode::Div), -1);
1229        assert_eq!(stack_delta(&Opcode::Mod), -1);
1230    }
1231    #[test]
1232    fn test_stack_delta_jumps() {
1233        assert_eq!(stack_delta(&Opcode::JumpIf(0)), -1);
1234        assert_eq!(stack_delta(&Opcode::JumpIfNot(0)), -1);
1235        assert_eq!(stack_delta(&Opcode::Jump(0)), 0);
1236    }
1237    #[test]
1238    fn test_disassemble_if_chunk() {
1239        let chunk = BytecodeCompiler::compile_if(true, 5, 10);
1240        let asm = disassemble(&chunk);
1241        assert!(asm.contains("JUMP_IFNOT"));
1242        assert!(asm.contains("JUMP"));
1243    }
1244    #[test]
1245    fn test_constant_folder_not() {
1246        let mut chunk = BytecodeChunk::new("t");
1247        chunk.push_op(Opcode::PushBool(false));
1248        chunk.push_op(Opcode::Not);
1249        chunk.push_op(Opcode::Halt);
1250        let folded = ConstantFolder::fold(&chunk);
1251        let mut interp = Interpreter::new();
1252        let result = interp
1253            .execute_chunk(&folded)
1254            .expect("execution should succeed");
1255        assert_eq!(result, StackValue::Bool(true));
1256    }
1257}