This commit is contained in:
bQUARKz 2026-02-18 17:14:50 +00:00
parent 3297980055
commit 7feeabc1b6
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 464 additions and 47 deletions

View File

@ -92,6 +92,11 @@ pub struct VirtualMachine {
pub halted: bool,
/// Set of ROM addresses used for software breakpoints in the debugger.
pub breakpoints: std::collections::HashSet<usize>,
/// GC: number of newly allocated live objects threshold to trigger a collection at safepoint.
/// The GC only runs at safepoints (e.g., FRAME_SYNC). 0 disables automatic GC.
pub gc_alloc_threshold: usize,
/// GC: snapshot of live objects count after the last collection (or VM init).
last_gc_live_count: usize,
}
@ -120,6 +125,8 @@ impl VirtualMachine {
cycles: 0,
halted: false,
breakpoints: std::collections::HashSet::new(),
gc_alloc_threshold: 1024, // conservative default; tests may override
last_gc_live_count: 0,
}
}
@ -140,6 +147,7 @@ impl VirtualMachine {
self.heap = Heap::new();
self.cycles = 0;
self.halted = true; // execution is impossible until a successful load
self.last_gc_live_count = 0;
// Only recognized format is loadable: PBS v0 industrial format
let program = if program_bytes.starts_with(b"PBS\0") {
@ -967,6 +975,30 @@ impl VirtualMachine {
OpCode::FrameSync => {
// Marks the logical end of a frame: consume cycles and signal to the driver
self.cycles += OpCode::FrameSync.cycles();
// GC Safepoint: only at FRAME_SYNC
if self.gc_alloc_threshold > 0 {
let live_now = self.heap.len();
let since_last = live_now.saturating_sub(self.last_gc_live_count);
if since_last >= self.gc_alloc_threshold {
// Collect GC roots from VM state
struct CollectRoots(Vec<prometeu_bytecode::HeapRef>);
impl crate::roots::RootVisitor for CollectRoots {
fn visit_heap_ref(&mut self, r: prometeu_bytecode::HeapRef) {
self.0.push(r);
}
}
let mut collector = CollectRoots(Vec::new());
self.visit_roots(&mut collector);
// Run mark-sweep
self.heap.mark_from_roots(collector.0);
self.heap.sweep();
// Update baseline for next cycles
self.last_gc_live_count = self.heap.len();
}
}
return Err(LogicalFrameEndingReason::FrameSync);
}
}
@ -2461,4 +2493,82 @@ mod tests {
_ => panic!("Expected Trap, got {:?}", report.reason),
}
}
#[test]
fn test_gc_triggers_only_at_frame_sync() {
use crate::object::ObjectKind;
// ROM: NOP; FRAME_SYNC; HALT
let mut rom = Vec::new();
rom.extend_from_slice(&(OpCode::Nop 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()
}]);
// Set a very low threshold to trigger GC as soon as we hit FRAME_SYNC
vm.gc_alloc_threshold = 1;
// Allocate an unreachable object (no roots referencing it)
let _orphan = vm.heap.allocate_object(ObjectKind::Bytes, &[1, 2, 3]);
assert_eq!(vm.heap.len(), 1);
let mut native = MockNative;
let mut ctx = HostContext::new(None);
// Step 1: NOP — should not run GC
vm.step(&mut native, &mut ctx).unwrap();
assert_eq!(vm.heap.len(), 1, "GC must not run except at safepoints");
// Step 2: FRAME_SYNC — GC should run and reclaim the unreachable object
match vm.step(&mut native, &mut ctx) {
Err(LogicalFrameEndingReason::FrameSync) => {}
other => panic!("Expected FrameSync, got {:?}", other),
}
assert_eq!(vm.heap.len(), 0, "Unreachable object must be reclaimed at FRAME_SYNC");
}
#[test]
fn test_gc_keeps_roots_and_collects_unreachable_at_frame_sync() {
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()
}]);
vm.gc_alloc_threshold = 1;
// Allocate two objects; make one a root by placing it on the operand stack
let rooted = vm.heap.allocate_object(ObjectKind::Bytes, &[9, 9]);
let unreachable = vm.heap.allocate_object(ObjectKind::Bytes, &[8, 8, 8]);
assert_eq!(vm.heap.len(), 2);
vm.operand_stack.push(Value::HeapRef(rooted));
let mut native = MockNative;
let mut ctx = HostContext::new(None);
// Execute FRAME_SYNC: should trigger GC
match vm.step(&mut native, &mut ctx) {
Err(LogicalFrameEndingReason::FrameSync) => {}
other => panic!("Expected FrameSync, got {:?}", other),
}
// Rooted must survive; unreachable must be collected
assert_eq!(vm.heap.len(), 1);
assert!(vm.heap.is_valid(rooted));
assert!(!vm.heap.is_valid(unreachable));
}
}

View File

@ -1,50 +1,3 @@
# PR-3.7 — Integrate GC Cycle at Safepoint (`FRAME_SYNC`)
### Briefing
The GC must only run at safepoints. This PR connects the mark-sweep collector to the VMs safepoint logic, primarily at `FRAME_SYNC`.
### Target
* Trigger GC cycles only at safepoints.
* Keep execution deterministic.
### Work items
* Identify safepoint handling code in the VM.
* Add logic:
* If allocation threshold exceeded, run GC at `FRAME_SYNC`.
* Ensure GC does not run in arbitrary instruction contexts.
### Acceptance checklist
* [ ] GC runs only at safepoints.
* [ ] No GC during arbitrary instruction execution.
* [ ] VM remains deterministic.
* [ ] `cargo test` passes.
### Tests
* Add a test where allocations trigger GC only at `FRAME_SYNC`.
### Junie instructions
**You MAY:**
* Hook GC invocation into safepoint handling.
**You MUST NOT:**
* Trigger GC at random points.
* Add background or concurrent GC.
**If unclear:**
* Ask before modifying safepoint semantics.
---
# PR-3.8 — GC Smoke and Stress Tests
### Briefing
@ -89,3 +42,357 @@ We need confidence that the GC behaves correctly under simple and stressed condi
**If unclear:**
* Ask before changing test scenarios.
# PR-4.1 — Define Canonical Stack Effect Table for Core ISA
### Briefing
The verifier must rely on a single canonical definition of stack effects for every opcode. This PR introduces the formal stack effect table for the new core ISA.
### Target
* Provide a single authoritative stack effect definition per opcode.
* Ensure verifier and tooling use the same source of truth.
### Work items
* Introduce a stack effect table or equivalent structure:
* Input slot count.
* Output slot count.
* Attach stack effect metadata to each opcode.
* Ensure the table is used by the verifier entry points.
* Document stack effects in code comments (English).
### Acceptance checklist
* [ ] Every opcode has a defined stack effect.
* [ ] Verifier uses the canonical table.
* [ ] No duplicated or ad-hoc stack effect logic remains.
* [ ] `cargo test` passes.
### Tests
* Add unit test ensuring all opcodes have stack effects defined.
### Junie instructions
**You MAY:**
* Introduce new metadata tables for opcodes.
* Refactor verifier code to use the canonical table.
**You MUST NOT:**
* Change opcode semantics.
* Introduce new instructions.
**If unclear:**
* Ask before assigning stack effects.
---
# PR-4.2 — Basic Stack Safety Verification (Underflow/Overflow)
### Briefing
The verifier must ensure the stack never underflows or exceeds defined limits during execution.
### Target
* Implement stack safety checks across the control-flow graph.
### Work items
* Simulate stack depth across instructions.
* Detect:
* Stack underflow.
* Stack overflow beyond declared limits.
* Emit appropriate verifier errors.
### Acceptance checklist
* [ ] Underflow cases are rejected.
* [ ] Overflow cases are rejected.
* [ ] Valid programs pass.
* [ ] `cargo test` passes.
### Tests
* Add tests:
* Program with underflow → verifier error.
* Program exceeding stack limit → verifier error.
* Valid program → passes.
### Junie instructions
**You MAY:**
* Add stack depth simulation.
* Introduce verifier error types.
**You MUST NOT:**
* Change runtime stack implementation.
* Introduce dynamic stack resizing.
**If unclear:**
* Ask before choosing stack limit rules.
---
# PR-4.3 — Control Flow and Jump Target Verification
### Briefing
The verifier must ensure all control flow transfers are valid and do not jump into the middle of instructions or outside function boundaries.
### Target
* Validate all jump targets.
* Reject invalid or unsafe control flow.
### Work items
* Use canonical layout utilities to identify instruction boundaries.
* Verify:
* Jump targets land on valid instruction boundaries.
* Targets are within the function range.
* Reject invalid targets with a verifier error.
### Acceptance checklist
* [ ] Invalid jump targets are rejected.
* [ ] Valid programs pass verification.
* [ ] No reliance on runtime traps for these cases.
* [ ] `cargo test` passes.
### Tests
* Add tests:
* Jump to middle of instruction → verifier error.
* Jump outside function → verifier error.
* Valid jump → passes.
### Junie instructions
**You MAY:**
* Reuse layout utilities for boundary checks.
* Add verifier error cases.
**You MUST NOT:**
* Modify instruction encoding.
* Introduce new trap codes.
**If unclear:**
* Ask before defining jump rules.
---
# PR-4.4 — Function Boundary and Terminator Verification
### Briefing
Functions must follow canonical entry and exit rules. The verifier must enforce valid terminators and boundaries.
### Target
* Ensure functions start and end correctly.
* Ensure terminator instructions are valid.
### Work items
* Verify function entry points are valid instruction boundaries.
* Verify function exit instructions:
* Proper `RET` usage.
* Proper `FRAME_SYNC` placement (as per spec).
* Reject functions without valid termination.
### Acceptance checklist
* [ ] All functions have valid entry and exit.
* [ ] Invalid termination is rejected.
* [ ] `cargo test` passes.
### Tests
* Add tests:
* Function without terminator → verifier error.
* Properly terminated function → passes.
### Junie instructions
**You MAY:**
* Add boundary validation logic.
**You MUST NOT:**
* Redefine function semantics.
* Change terminator opcode behavior.
**If unclear:**
* Ask before enforcing termination rules.
---
# PR-4.5 — Multi-Return (`ret_slots`) Verification
### Briefing
The new ABI supports multi-return via `ret_slots`. The verifier must ensure the stack shape matches the declared return slot count.
### Target
* Validate return slot counts at call and return sites.
### Work items
* For each function:
* Read declared `ret_slots`.
* At `RET`:
* Ensure stack contains exactly the required number of slots.
* At call sites:
* Ensure caller expects correct number of return slots.
### Acceptance checklist
* [ ] Mismatched return slot counts are rejected.
* [ ] Correct programs pass.
* [ ] `cargo test` passes.
### Tests
* Add tests:
* Too few return slots → verifier error.
* Too many return slots → verifier error.
* Correct return slots → passes.
### Junie instructions
**You MAY:**
* Use function metadata to validate returns.
**You MUST NOT:**
* Change calling convention semantics.
* Modify function metadata layout.
**If unclear:**
* Ask before implementing slot rules.
---
# PR-4.6 — Verifier Error Model Consolidation
### Briefing
Verifier errors must be deterministic, structured, and clearly separated from runtime traps.
### Target
* Introduce a coherent verifier error model.
### Work items
* Define a `VerifierError` enum covering:
* Stack underflow.
* Stack overflow.
* Invalid jump target.
* Invalid function boundary.
* Return slot mismatch.
* Ensure verifier returns structured errors.
* Update tests to expect structured errors.
### Acceptance checklist
* [ ] Verifier errors are structured and deterministic.
* [ ] No reliance on runtime traps for verifier failures.
* [ ] `cargo test` passes.
### Tests
* Update existing tests to assert specific verifier errors.
### Junie instructions
**You MAY:**
* Introduce a new verifier error enum.
* Refactor error returns.
**You MUST NOT:**
* Map verifier errors to runtime traps.
* Change runtime trap behavior.
**If unclear:**
* Ask before merging or renaming error categories.
---
# PR-4.7 — Verifier Golden Test Suite
### Briefing
We need a stable suite of valid and invalid bytecode samples to ensure verifier correctness over time.
### Target
* Introduce golden tests for the verifier.
### Work items
* Add a set of small bytecode samples:
* Valid programs.
* Invalid programs for each verifier error.
* Implement golden tests asserting:
* Successful verification.
* Specific error kinds for invalid programs.
### Acceptance checklist
* [ ] Golden tests exist for all verifier error categories.
* [ ] Tests are deterministic.
* [ ] `cargo test` passes.
### Tests
* New golden tests only.
### Junie instructions
**You MAY:**
* Add deterministic golden tests.
**You MUST NOT:**
* Modify verifier logic to fit tests without understanding the cause.
* Introduce random or timing-based tests.
**If unclear:**
* Ask before changing test expectations.