diff --git a/crates/console/prometeu-vm/src/heap.rs b/crates/console/prometeu-vm/src/heap.rs index b4ba5468..e5f22099 100644 --- a/crates/console/prometeu-vm/src/heap.rs +++ b/crates/console/prometeu-vm/src/heap.rs @@ -290,4 +290,34 @@ mod tests { // We can't access internal vector here, but stability is implied by handle not changing. assert_eq!(a.0, a.0); // placeholder sanity check } + + #[test] + fn sweep_reclaims_unrooted_cycle() { + let mut heap = Heap::new(); + + // Build a 2-node cycle A <-> B using internal mutation (module-private access). + let a = heap.allocate_array(vec![]); + let b = heap.allocate_array(vec![]); + + // Make A point to B and B point to A. + if let Some(slot) = heap.objects.get_mut(a.0 as usize) { + if let Some(obj) = slot.as_mut() { + obj.array_elems = Some(vec![Value::HeapRef(b)]); + obj.header.payload_len = 1; + } + } + if let Some(slot) = heap.objects.get_mut(b.0 as usize) { + if let Some(obj) = slot.as_mut() { + obj.array_elems = Some(vec![Value::HeapRef(a)]); + obj.header.payload_len = 1; + } + } + + // No roots: perform sweep directly; both should be reclaimed. + heap.sweep(); + + assert!(!heap.is_valid(a)); + assert!(!heap.is_valid(b)); + assert_eq!(heap.len(), 0); + } } diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index 5c7e5199..ff9d95e0 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -2537,9 +2537,10 @@ mod tests { fn test_gc_keeps_roots_and_collects_unreachable_at_frame_sync() { use crate::object::ObjectKind; - // ROM: FRAME_SYNC; HALT + // ROM: FRAME_SYNC; FRAME_SYNC; HALT 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![]); @@ -2571,4 +2572,88 @@ mod tests { assert!(vm.heap.is_valid(rooted)); assert!(!vm.heap.is_valid(unreachable)); } + + #[test] + fn test_gc_simple_allocation_collection_cycle() { + use crate::object::ObjectKind; + + // ROM: FRAME_SYNC; FRAME_SYNC; HALT + 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.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta { + code_offset: 0, + code_len: rom.len() as u32, + ..Default::default() + }]); + + // Make GC trigger on any allocation delta + vm.gc_alloc_threshold = 1; + + // Cycle 1: allocate one unreachable object + let _h1 = vm.heap.allocate_object(ObjectKind::Bytes, &[1]); + assert_eq!(vm.heap.len(), 1); + + let mut native = MockNative; + let mut ctx = HostContext::new(None); + + // FRAME_SYNC should collect it (first FRAME_SYNC) + match vm.step(&mut native, &mut ctx) { + Err(LogicalFrameEndingReason::FrameSync) => {} + other => panic!("Expected FrameSync, got {:?}", other), + } + assert_eq!(vm.heap.len(), 0); + + // Cycle 2: allocate again and collect again deterministically + let _h2 = vm.heap.allocate_object(ObjectKind::Bytes, &[2]); + assert_eq!(vm.heap.len(), 1); + // Second FRAME_SYNC should also be reached deterministically + match vm.step(&mut native, &mut ctx) { + Err(LogicalFrameEndingReason::FrameSync) => {} + other => panic!("Expected FrameSync, got {:?}", other), + } + assert_eq!(vm.heap.len(), 0); + } + + #[test] + fn test_gc_many_short_lived_objects_stress() { + use crate::object::ObjectKind; + + // ROM: FRAME_SYNC; HALT + let mut rom = Vec::new(); + 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.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta { + code_offset: 0, + code_len: rom.len() as u32, + ..Default::default() + }]); + + // Deterministic: trigger collection when any growth since last sweep occurs + vm.gc_alloc_threshold = 1; + + // Allocate many small, unreferenced objects + let count = 2048usize; // stress but still quick + for i in 0..count { + let byte = (i & 0xFF) as u8; + let _ = vm.heap.allocate_object(ObjectKind::Bytes, &[byte]); + } + assert_eq!(vm.heap.len(), count); + + let mut native = MockNative; + let mut ctx = HostContext::new(None); + + // Single FRAME_SYNC should reclaim all since there are no roots + match vm.step(&mut native, &mut ctx) { + Err(LogicalFrameEndingReason::FrameSync) => {} + other => panic!("Expected FrameSync, got {:?}", other), + } + + assert_eq!(vm.heap.len(), 0, "All short-lived objects must be reclaimed deterministically"); + } } diff --git a/files/TODOs.md b/files/TODOs.md index b324b30d..fe3eefb4 100644 --- a/files/TODOs.md +++ b/files/TODOs.md @@ -1,47 +1,3 @@ -# PR-3.8 — GC Smoke and Stress Tests - -### Briefing - -We need confidence that the GC behaves correctly under simple and stressed conditions. - -### Target - -* Add deterministic smoke and stress tests for the GC. - -### Work items - -* Add tests: - - * Simple allocation and collection cycle. - * Many short-lived objects. - * Cyclic references. -* Ensure tests are deterministic. - -### Acceptance checklist - -* [ ] Smoke tests pass. -* [ ] Stress tests pass. -* [ ] No nondeterministic failures. -* [ ] `cargo test` passes. - -### Tests - -* New GC-specific tests. - -### Junie instructions - -**You MAY:** - -* Add deterministic tests. - -**You MUST NOT:** - -* Introduce random or timing-dependent tests. -* Modify GC semantics to satisfy tests. - -**If unclear:** - -* Ask before changing test scenarios. # PR-4.1 — Define Canonical Stack Effect Table for Core ISA ### Briefing @@ -396,3 +352,301 @@ We need a stable suite of valid and invalid bytecode samples to ensure verifier * Ask before changing test expectations. +--- + +# PR-5.1 — Define Canonical Syscall Metadata Table + +### Briefing + +Syscalls must follow a unified, function-like ABI based on slot counts and capabilities. This PR introduces the canonical metadata table describing every syscall. + +### Target + +* Define a single authoritative metadata structure for syscalls. +* Ensure all syscalls are described in terms of slot-based ABI. + +### Work items + +* Introduce a `SyscallMeta` struct containing: + + * Syscall identifier. + * `arg_slots`. + * `ret_slots`. + * Capability flags. + * Determinism flags (if defined in spec). +* Create a canonical syscall table/registry. +* Ensure all existing syscalls are registered in this table. +* Document the structure in code comments (English). + +### Acceptance checklist + +* [ ] All syscalls have a `SyscallMeta` entry. +* [ ] Metadata includes arg and return slot counts. +* [ ] No syscall is invoked without metadata. +* [ ] `cargo test` passes. + +### Tests + +* Add a test ensuring all registered syscalls have metadata. + +### Junie instructions + +**You MAY:** + +* Introduce a syscall metadata struct. +* Add a central registry for syscalls. + +**You MUST NOT:** + +* Change existing syscall semantics. +* Add new syscalls. + +**If unclear:** + +* Ask before defining metadata fields. + +--- + +# PR-5.2 — Implement Slot-Based Syscall Calling Convention + +### Briefing + +Syscalls must behave like functions using the stack-based slot ABI. This PR implements the unified calling convention. + +### Target + +* Ensure syscalls read arguments from the stack. +* Ensure syscalls push return values based on `ret_slots`. + +### Work items + +* Modify the VM syscall dispatch logic to: + + * Look up `SyscallMeta`. + * Pop `arg_slots` from the stack. + * Invoke the syscall. + * Push `ret_slots` results. +* Remove any legacy argument handling paths. + +### Acceptance checklist + +* [ ] All syscalls use slot-based calling convention. +* [ ] Argument and return slot counts are enforced. +* [ ] No legacy calling paths remain. +* [ ] `cargo test` passes. + +### Tests + +* Add tests: + + * Syscall with correct arg/ret slots → passes. + * Syscall with insufficient args → trap or verifier failure. + +### Junie instructions + +**You MAY:** + +* Refactor syscall dispatch code. +* Use metadata to enforce slot counts. + +**You MUST NOT:** + +* Change the stack model. +* Add compatibility layers. + +**If unclear:** + +* Ask before modifying dispatch semantics. + +--- + +# PR-5.3 — Enforce Capability Checks in Syscall Dispatch + +### Briefing + +Syscalls must be capability-gated. The runtime must verify that the current program has permission to invoke each syscall. + +### Target + +* Enforce capability checks before syscall execution. + +### Work items + +* Extend `SyscallMeta` with capability requirements. +* Add capability data to the VM or execution context. +* In syscall dispatch: + + * Check capabilities. + * If missing, trigger `TRAP_INVALID_SYSCALL` or equivalent. + +### Acceptance checklist + +* [ ] Capability checks occur before syscall execution. +* [ ] Missing capability leads to deterministic trap. +* [ ] Valid capabilities allow execution. +* [ ] `cargo test` passes. + +### Tests + +* Add tests: + + * Syscall without capability → trap. + * Syscall with capability → success. + +### Junie instructions + +**You MAY:** + +* Add capability checks in dispatch. +* Extend metadata with capability fields. + +**You MUST NOT:** + +* Change trap semantics. +* Introduce dynamic or nondeterministic permission systems. + +**If unclear:** + +* Ask before defining capability behavior. + +--- + +# PR-5.4 — Verifier Integration for Syscall Slot Rules + +### Briefing + +The verifier must ensure that syscall calls respect argument and return slot counts before runtime. + +### Target + +* Extend verifier to validate syscall usage. + +### Work items + +* At syscall call sites: + + * Look up `SyscallMeta`. + * Ensure enough argument slots are available. + * Ensure stack shape after call matches `ret_slots`. +* Emit verifier errors for mismatches. + +### Acceptance checklist + +* [ ] Verifier rejects incorrect syscall slot usage. +* [ ] Correct programs pass. +* [ ] Runtime traps are not required for verifier-detectable cases. +* [ ] `cargo test` passes. + +### Tests + +* Add tests: + + * Too few args for syscall → verifier error. + * Correct args/returns → passes. + +### Junie instructions + +**You MAY:** + +* Extend verifier with syscall checks. + +**You MUST NOT:** + +* Change runtime trap logic. +* Add new trap categories. + +**If unclear:** + +* Ask before enforcing slot rules. + +--- + +# PR-5.5 — Remove Legacy Syscall Entry Paths + +### Briefing + +Any old or experimental syscall entry paths must be removed so that the slot-based ABI is the only supported mechanism. + +### Target + +* Ensure only the new unified syscall dispatch path exists. + +### Work items + +* Search for legacy or alternate syscall invocation logic. +* Remove or refactor them to use the canonical dispatch. +* Update modules and exports accordingly. + +### Acceptance checklist + +* [ ] Only one syscall dispatch path remains. +* [ ] No legacy syscall logic is present. +* [ ] `cargo test` passes. + +### Tests + +* Existing tests only. + +### Junie instructions + +**You MAY:** + +* Remove legacy syscall code paths. +* Refactor callers to use the unified dispatch. + +**You MUST NOT:** + +* Introduce new syscall semantics. +* Keep compatibility shims. + +**If unclear:** + +* Ask before deleting anything that looks externally visible. + +--- + +# PR-5.6 — Syscall Multi-Return Tests + +### Briefing + +We must ensure multi-return syscalls behave correctly with the slot-based ABI. + +### Target + +* Add deterministic tests covering multi-return behavior. + +### Work items + +* Create or adapt at least one syscall with `ret_slots > 1`. +* Add tests: + + * Verify correct stack results after syscall. + * Verify incorrect caller expectations fail verification. + +### Acceptance checklist + +* [ ] Multi-return syscalls behave correctly. +* [ ] Verifier catches mismatches. +* [ ] `cargo test` passes. + +### Tests + +* New multi-return syscall tests. + +### Junie instructions + +**You MAY:** + +* Add deterministic tests. +* Use existing syscalls or create a simple test-only syscall. + +**You MUST NOT:** + +* Modify syscall semantics to satisfy tests. +* Add nondeterministic behavior. + +**If unclear:** + +* Ask before introducing new test syscalls. + +