From 3f50bdaa70decf6307ba9d8c59e2eb1121d6df88 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 20 Feb 2026 11:15:50 +0000 Subject: [PATCH] pr7.5 --- .../console/prometeu-bytecode/src/opcode.rs | 10 ++ .../prometeu-bytecode/src/opcode_spec.rs | 13 ++ .../prometeu-vm/src/virtual_machine.rs | 142 ++++++++++++++---- files/TODOs.md | 40 ----- 4 files changed, 139 insertions(+), 66 deletions(-) diff --git a/crates/console/prometeu-bytecode/src/opcode.rs b/crates/console/prometeu-bytecode/src/opcode.rs index 7dfc0c52..e5a5ce2d 100644 --- a/crates/console/prometeu-bytecode/src/opcode.rs +++ b/crates/console/prometeu-bytecode/src/opcode.rs @@ -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 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, } diff --git a/crates/console/prometeu-bytecode/src/opcode_spec.rs b/crates/console/prometeu-bytecode/src/opcode_spec.rs index 83f18390..45b07400 100644 --- a/crates/console/prometeu-bytecode/src/opcode_spec.rs +++ b/crates/console/prometeu-bytecode/src/opcode_spec.rs @@ -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, diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index e6983aba..6664ed19 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -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, } @@ -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); - 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); + 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); diff --git a/files/TODOs.md b/files/TODOs.md index a6c6f16d..d08a5ac4 100644 --- a/files/TODOs.md +++ b/files/TODOs.md @@ -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