1use 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}