From 49c9b018bcdbd09aade6e88fdfb40e845de9bdf2 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 20 Feb 2026 12:31:02 +0000 Subject: [PATCH] pr7.9 --- .../prometeu-vm/src/virtual_machine.rs | 157 ++++++++++++++++++ files/TODOs.md | 35 ---- 2 files changed, 157 insertions(+), 35 deletions(-) diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index f283ced5..08170ac0 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -1369,6 +1369,163 @@ mod tests { let res = vm.run_budget(100, &mut native, &mut ctx); assert!(res.is_ok()); } + + #[test] + fn test_deterministic_pc_and_tick_trace_across_runs() { + // Program: + // PUSH_I32 1; YIELD; FrameSync; + // PUSH_I32 2; YIELD; FrameSync; + // PUSH_I32 3; FrameSync; HALT + // We collect (pc, tick, stack_height) after each run_budget slice and + // compare two independent VMs initialized from the same ROM. + let mut rom = Vec::new(); + // PUSH 1 + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&1i32.to_le_bytes()); + // YIELD + FrameSync + rom.extend_from_slice(&(OpCode::Yield as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes()); + // PUSH 2 + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&2i32.to_le_bytes()); + // YIELD + FrameSync + rom.extend_from_slice(&(OpCode::Yield as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes()); + // PUSH 3 + FrameSync + HALT + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&3i32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let mut vm1 = new_test_vm(rom.clone(), vec![]); + let mut vm2 = new_test_vm(rom.clone(), vec![]); + + let mut native = MockNative; + let mut ctx1 = HostContext::new(None); + let mut ctx2 = HostContext::new(None); + + let mut trace1 = Vec::new(); + let mut trace2 = Vec::new(); + + // Run both VMs in lockstep slices until both halt + for _ in 0..10 { + if !vm1.halted { + let rep = vm1.run_budget(4, &mut native, &mut ctx1).expect("vm1 ok"); + trace1.push((vm1.pc, vm1.current_tick, vm1.operand_stack.len(), format!("{:?}", rep.reason))); + } + if !vm2.halted { + let rep = vm2.run_budget(4, &mut native, &mut ctx2).expect("vm2 ok"); + trace2.push((vm2.pc, vm2.current_tick, vm2.operand_stack.len(), format!("{:?}", rep.reason))); + } + if vm1.halted && vm2.halted { break; } + } + + assert!(vm1.halted && vm2.halted, "Both VMs should reach HALT deterministically"); + assert_eq!(trace1, trace2, "Per-slice traces must be identical across runs"); + // Also verify final stack content deterministic + assert_eq!(vm1.pop().unwrap(), Value::Int32(3)); + assert_eq!(vm1.pop().unwrap(), Value::Int32(2)); + assert_eq!(vm1.pop().unwrap(), Value::Int32(1)); + assert!(vm1.operand_stack.is_empty()); + assert_eq!(vm2.pop().unwrap(), Value::Int32(3)); + assert_eq!(vm2.pop().unwrap(), Value::Int32(2)); + assert_eq!(vm2.pop().unwrap(), Value::Int32(1)); + assert!(vm2.operand_stack.is_empty()); + } + + #[test] + fn test_sleep_wake_determinism_across_runs() { + // Program: + // SLEEP 2; PUSH 7; FrameSync; HALT + let mut rom = Vec::new(); + rom.extend_from_slice(&(OpCode::Sleep as u16).to_le_bytes()); + rom.extend_from_slice(&(2u32).to_le_bytes()); + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&7i32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let mut native = MockNative; + let mut vm_a = new_test_vm(rom.clone(), vec![]); + let mut vm_b = new_test_vm(rom.clone(), vec![]); + let mut ctx_a = HostContext::new(None); + let mut ctx_b = HostContext::new(None); + + let mut ticks_a = Vec::new(); + let mut ticks_b = Vec::new(); + + // Slice 1 + let ra1 = vm_a.run_budget(100, &mut native, &mut ctx_a).unwrap(); + let rb1 = vm_b.run_budget(100, &mut native, &mut ctx_b).unwrap(); + ticks_a.push((vm_a.pc, vm_a.current_tick, format!("{:?}", ra1.reason))); + ticks_b.push((vm_b.pc, vm_b.current_tick, format!("{:?}", rb1.reason))); + + // Slice 2 + let ra2 = vm_a.run_budget(100, &mut native, &mut ctx_a).unwrap(); + let rb2 = vm_b.run_budget(100, &mut native, &mut ctx_b).unwrap(); + ticks_a.push((vm_a.pc, vm_a.current_tick, format!("{:?}", ra2.reason))); + ticks_b.push((vm_b.pc, vm_b.current_tick, format!("{:?}", rb2.reason))); + + // Slice 3 (wakes and pushes) + let ra3 = vm_a.run_budget(100, &mut native, &mut ctx_a).unwrap(); + let rb3 = vm_b.run_budget(100, &mut native, &mut ctx_b).unwrap(); + ticks_a.push((vm_a.pc, vm_a.current_tick, format!("{:?}", ra3.reason))); + ticks_b.push((vm_b.pc, vm_b.current_tick, format!("{:?}", rb3.reason))); + + assert_eq!(ticks_a, ticks_b, "Sleep/wake slices must match across runs"); + assert_eq!(vm_a.peek().unwrap(), &Value::Int32(7)); + assert_eq!(vm_b.peek().unwrap(), &Value::Int32(7)); + } + + #[test] + fn test_gc_many_coroutines_and_wake_order_determinism() { + use crate::heap::{CoroutineState}; + use crate::object::ObjectKind; + + // ROM: FrameSync; FrameSync; Halt (two deterministic safepoints back-to-back) + let mut rom = Vec::new(); + rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let mut vm = new_test_vm(rom.clone(), vec![]); + vm.gc_alloc_threshold = 1; // force GC at first safepoint + + // Allocate many coroutine objects: half Ready, half Sleeping with differing wake ticks. + let coro_count = 128u32; + for i in 0..coro_count { + let state = if i % 2 == 0 { CoroutineState::Ready } else { CoroutineState::Sleeping }; + let wake = if state == CoroutineState::Sleeping { (i / 2) as u64 } else { 0 }; + let _c = vm.heap.allocate_coroutine(state, wake, vec![], vec![]); + // Also allocate a tiny byte object to increase GC pressure. + let _b = vm.heap.allocate_object(ObjectKind::Bytes, &[i as u8]); + } + + // Sanity: allocations present + assert!(vm.heap.len() as u32 >= coro_count, "heap should contain coroutine objects and bytes"); + + let mut native = MockNative; + let mut ctx = HostContext::new(None); + + // Reaching FrameSync should run GC; Ready/Sleeping coroutines are treated as roots, so + // only unreferenced byte objects can be reclaimed. We just assert determinism: heap size + // should be stable across two consecutive FrameSyncs with no new allocations in between. + let before = vm.heap.len(); + match vm.step(&mut native, &mut ctx) { + Err(LogicalFrameEndingReason::FrameSync) => {} + other => panic!("Expected FrameSync, got {:?}", other), + } + let after_first = vm.heap.len(); + match vm.step(&mut native, &mut ctx) { + Err(LogicalFrameEndingReason::FrameSync) => {} + other => panic!("Expected FrameSync, got {:?}", other), + } + let after_second = vm.heap.len(); + + // GC effect should be deterministic and idempotent at steady state + assert!(after_first <= before); + assert_eq!(after_second, after_first); + } fn test_arithmetic_chain() { let mut native = MockNative; let mut ctx = HostContext::new(None); diff --git a/files/TODOs.md b/files/TODOs.md index 536283b0..04ce0de4 100644 --- a/files/TODOs.md +++ b/files/TODOs.md @@ -1,38 +1,3 @@ -# PR-7.9 — Determinism & Stress Tests - -## Briefing - -Validate deterministic behavior. - -## Target - -Tests must confirm: - -* Same order across runs. -* Sleep/wake order stable. -* GC works with many coroutines. - -## Checklist - -* [ ] Deterministic order tests. -* [ ] Stress test 100+ coroutines. - -## Junie Rules - -Tests must not depend on wall clock or randomness. - ---- - -## Final Definition of Done - -* Cooperative coroutines implemented. -* Deterministic scheduling. -* No mailbox. -* GC and verifier fully integrated. -* All tests pass. - ---- - # PR-8 — Tooling & Test Harness (JVM-Grade Discipline) This phase establishes tooling and test discipline required to keep the Prometeu VM at JVM-grade quality.