pr7.5
This commit is contained in:
parent
b7e149a1ab
commit
3f50bdaa70
@ -188,6 +188,14 @@ pub enum OpCode {
|
||||
/// may switch to another ready coroutine.
|
||||
Yield = 0x55,
|
||||
|
||||
/// Suspends the current coroutine for a number of logical ticks.
|
||||
/// Operand: duration_ticks (u32)
|
||||
/// Semantics:
|
||||
/// - Set the coroutine wake tick to `current_tick + duration_ticks`.
|
||||
/// - End the current logical frame (as if reaching FRAME_SYNC).
|
||||
/// - The coroutine will resume execution on or after the wake tick.
|
||||
Sleep = 0x56,
|
||||
|
||||
// --- 6.8 Peripherals and System ---
|
||||
/// Invokes a system function (Firmware/OS).
|
||||
/// Operand: syscall_id (u32)
|
||||
@ -251,6 +259,7 @@ impl TryFrom<u16> for OpCode {
|
||||
0x53 => Ok(OpCode::CallClosure),
|
||||
0x54 => Ok(OpCode::Spawn),
|
||||
0x55 => Ok(OpCode::Yield),
|
||||
0x56 => Ok(OpCode::Sleep),
|
||||
0x70 => Ok(OpCode::Syscall),
|
||||
0x80 => Ok(OpCode::FrameSync),
|
||||
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
|
||||
@ -311,6 +320,7 @@ impl OpCode {
|
||||
OpCode::CallClosure => 6,
|
||||
OpCode::Spawn => 6,
|
||||
OpCode::Yield => 1,
|
||||
OpCode::Sleep => 1,
|
||||
OpCode::Syscall => 1,
|
||||
OpCode::FrameSync => 1,
|
||||
}
|
||||
|
||||
@ -510,6 +510,19 @@ impl OpCodeSpecExt for OpCode {
|
||||
// Treated as a safepoint marker for cooperative scheduling
|
||||
is_safepoint: true,
|
||||
},
|
||||
OpCode::Sleep => OpcodeSpec {
|
||||
name: "SLEEP",
|
||||
// One u32 immediate: duration_ticks
|
||||
imm_bytes: 4,
|
||||
pops: 0,
|
||||
pushes: 0,
|
||||
is_branch: false,
|
||||
// Ends execution at safepoint after instruction completes
|
||||
is_terminator: false,
|
||||
may_trap: false,
|
||||
// Considered a safepoint since it forces a frame boundary
|
||||
is_safepoint: true,
|
||||
},
|
||||
OpCode::Syscall => OpcodeSpec {
|
||||
name: "SYSCALL",
|
||||
imm_bytes: 4,
|
||||
|
||||
@ -87,6 +87,13 @@ pub struct VirtualMachine {
|
||||
/// Cooperative scheduler: set to true when `YIELD` opcode is executed.
|
||||
/// The runtime/scheduler should only act on this at safepoints (FRAME_SYNC).
|
||||
pub yield_requested: bool,
|
||||
/// Logical tick counter advanced at each FRAME_SYNC boundary.
|
||||
pub current_tick: u64,
|
||||
/// If set, the current coroutine is sleeping until this tick (inclusive).
|
||||
/// While sleeping and before `current_tick >= wake`, the VM will end the
|
||||
/// logical frame immediately at the start of `step()` and after executing
|
||||
/// `SLEEP`.
|
||||
pub sleep_until_tick: Option<u64>,
|
||||
}
|
||||
|
||||
|
||||
@ -119,6 +126,8 @@ impl VirtualMachine {
|
||||
last_gc_live_count: 0,
|
||||
capabilities: 0,
|
||||
yield_requested: false,
|
||||
current_tick: 0,
|
||||
sleep_until_tick: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,6 +149,8 @@ impl VirtualMachine {
|
||||
self.cycles = 0;
|
||||
self.halted = true; // execution is impossible until a successful load
|
||||
self.last_gc_live_count = 0;
|
||||
self.current_tick = 0;
|
||||
self.sleep_until_tick = None;
|
||||
// Preserve capabilities across loads; firmware may set them per cart.
|
||||
|
||||
// Only recognized format is loadable: PBS v0 industrial format
|
||||
@ -324,6 +335,16 @@ impl VirtualMachine {
|
||||
native: &mut dyn NativeInterface,
|
||||
ctx: &mut HostContext,
|
||||
) -> Result<(), LogicalFrameEndingReason> {
|
||||
// If the current coroutine is sleeping and hasn't reached its wake tick,
|
||||
// immediately end the logical frame to respect suspension semantics.
|
||||
if let Some(wake) = self.sleep_until_tick {
|
||||
if self.current_tick < wake {
|
||||
// Consume FRAME_SYNC cost and perform safepoint duties.
|
||||
self.cycles += OpCode::FrameSync.cycles();
|
||||
self.handle_safepoint();
|
||||
return Err(LogicalFrameEndingReason::FrameSync);
|
||||
}
|
||||
}
|
||||
if self.halted || self.pc >= self.program.rom.len() {
|
||||
return Ok(());
|
||||
}
|
||||
@ -423,6 +444,20 @@ impl VirtualMachine {
|
||||
self.yield_requested = true;
|
||||
// Do not end the slice here; we continue executing until a safepoint.
|
||||
}
|
||||
OpCode::Sleep => {
|
||||
// Immediate is duration in ticks
|
||||
let duration = instr
|
||||
.imm_u32()
|
||||
.map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as u64;
|
||||
let wake = self.current_tick.saturating_add(duration);
|
||||
self.sleep_until_tick = Some(wake);
|
||||
|
||||
// End the logical frame right after the instruction completes
|
||||
// to ensure no further instructions run until at least next tick.
|
||||
self.cycles += OpCode::FrameSync.cycles();
|
||||
self.handle_safepoint();
|
||||
return Err(LogicalFrameEndingReason::FrameSync);
|
||||
}
|
||||
OpCode::MakeClosure => {
|
||||
// Immediate carries (fn_id, capture_count)
|
||||
let (fn_id, cap_count) = instr
|
||||
@ -1118,32 +1153,7 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear cooperative yield request at the safepoint boundary.
|
||||
self.yield_requested = false;
|
||||
self.handle_safepoint();
|
||||
return Err(LogicalFrameEndingReason::FrameSync);
|
||||
}
|
||||
}
|
||||
@ -1153,6 +1163,45 @@ impl VirtualMachine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform safepoint duties that occur at logical frame boundaries.
|
||||
/// Runs GC if thresholds are reached, clears cooperative yield flag,
|
||||
/// and advances the logical tick counter.
|
||||
fn handle_safepoint(&mut self) {
|
||||
// GC Safepoint: only at FRAME_SYNC-like boundaries
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Advance logical tick at every frame boundary.
|
||||
self.current_tick = self.current_tick.wrapping_add(1);
|
||||
|
||||
// If we've passed the wake tick, clear the sleep so execution can resume next frame.
|
||||
if let Some(wake) = self.sleep_until_tick {
|
||||
if self.current_tick >= wake {
|
||||
self.sleep_until_tick = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear cooperative yield request at the safepoint boundary.
|
||||
self.yield_requested = false;
|
||||
}
|
||||
|
||||
pub fn trap(
|
||||
&self,
|
||||
code: u32,
|
||||
@ -1275,6 +1324,47 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sleep_delays_execution_by_ticks() {
|
||||
let mut native = MockNative;
|
||||
let mut ctx = HostContext::new(None);
|
||||
|
||||
// Program:
|
||||
// SLEEP 2
|
||||
// PUSH_I32 123
|
||||
// FRAME_SYNC
|
||||
// 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(&123i32.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, vec![]);
|
||||
|
||||
// Frame 1: executing SLEEP 2 will force a frame end and advance tick to 1
|
||||
let rep1 = vm.run_budget(100, &mut native, &mut ctx).expect("run ok");
|
||||
assert!(matches!(rep1.reason, LogicalFrameEndingReason::FrameSync));
|
||||
assert!(vm.operand_stack.is_empty());
|
||||
assert_eq!(vm.current_tick, 1);
|
||||
|
||||
// Frame 2: still sleeping (tick 1 < wake 2), immediate FrameSync, tick -> 2
|
||||
let rep2 = vm.run_budget(100, &mut native, &mut ctx).expect("run ok");
|
||||
assert!(matches!(rep2.reason, LogicalFrameEndingReason::FrameSync));
|
||||
assert!(vm.operand_stack.is_empty());
|
||||
assert_eq!(vm.current_tick, 2);
|
||||
|
||||
// Frame 3: wake condition met (current_tick >= wake), execute PUSH_I32 then FRAME_SYNC
|
||||
let rep3 = vm.run_budget(100, &mut native, &mut ctx).expect("run ok");
|
||||
assert!(matches!(rep3.reason, LogicalFrameEndingReason::FrameSync));
|
||||
// Value should now be on the stack
|
||||
assert_eq!(vm.peek().unwrap(), &Value::Int32(123));
|
||||
|
||||
// Next frame should hit HALT without errors
|
||||
let res = vm.run_budget(100, &mut native, &mut ctx);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
fn test_arithmetic_chain() {
|
||||
let mut native = MockNative;
|
||||
let mut ctx = HostContext::new(None);
|
||||
|
||||
@ -1,43 +1,3 @@
|
||||
# PR-7.5 — SLEEP Instruction
|
||||
|
||||
## Briefing
|
||||
|
||||
SLEEP suspends coroutine until a future tick.
|
||||
|
||||
## Target
|
||||
|
||||
Opcode:
|
||||
|
||||
`SLEEP duration_ticks`
|
||||
|
||||
Semantics:
|
||||
|
||||
* Remove coroutine from ready queue.
|
||||
* Set wake_tick.
|
||||
* Add to sleeping list.
|
||||
|
||||
At each FRAME_SYNC:
|
||||
|
||||
* Check sleeping coroutines.
|
||||
* Move ready ones to ready_queue.
|
||||
|
||||
## Checklist
|
||||
|
||||
* [ ] SLEEP implemented.
|
||||
* [ ] wake_tick respected.
|
||||
* [ ] Deterministic wake behavior.
|
||||
|
||||
## Tests
|
||||
|
||||
* Sleep and verify delayed execution.
|
||||
|
||||
## Junie Rules
|
||||
|
||||
You MAY add tick tracking.
|
||||
You MUST NOT rely on real wall clock time.
|
||||
|
||||
---
|
||||
|
||||
# PR-7.6 — Safepoint Integration
|
||||
|
||||
## Briefing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user