use super::*; use crate::CrashReport; use prometeu_hal::asset::{BankTelemetry, BankType}; use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::{HardwareBridge, HostContext, InputSignals}; use prometeu_vm::LogicalFrameEndingReason; use std::sync::atomic::Ordering; impl VirtualMachineRuntime { fn bank_telemetry_summary(hw: &dyn HardwareBridge) -> (BankTelemetry, BankTelemetry) { let telemetry = hw.assets().bank_telemetry(); let glyph = telemetry.iter().find(|entry| entry.bank_type == BankType::GLYPH).cloned().unwrap_or( BankTelemetry { bank_type: BankType::GLYPH, used_slots: 0, total_slots: 0 }, ); let sounds = telemetry.iter().find(|entry| entry.bank_type == BankType::SOUNDS).cloned().unwrap_or( BankTelemetry { bank_type: BankType::SOUNDS, used_slots: 0, total_slots: 0 }, ); (glyph, sounds) } pub fn debug_step_instruction( &mut self, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge, ) -> Option { let mut ctx = HostContext::new(Some(hw)); match vm.step(self, &mut ctx) { Ok(_) => None, Err(e) => { let report = match e { LogicalFrameEndingReason::Trap(trap) => CrashReport::VmTrap { trap }, LogicalFrameEndingReason::Panic(message) => { CrashReport::VmPanic { message, pc: Some(vm.pc() as u32) } } other => CrashReport::VmPanic { message: format!("Unexpected fault during step: {:?}", other), pc: Some(vm.pc() as u32), }, }; self.log( LogLevel::Error, LogSource::Vm, report.log_tag(), format!("PVM Fault during Step: {}", report), ); self.last_crash_report = Some(report.clone()); Some(report) } } } pub fn tick( &mut self, vm: &mut VirtualMachine, signals: &InputSignals, hw: &mut dyn HardwareBridge, ) -> Option { let start = Instant::now(); self.tick_index += 1; if self.paused && !self.debug_step_request { return None; } self.update_fs(); if !self.logical_frame_active { self.logical_frame_active = true; self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME; self.begin_logical_frame(signals, hw); if self.needs_prepare_entry_call || vm.call_stack_is_empty() { vm.prepare_boot_call(); self.needs_prepare_entry_call = false; } self.atomic_telemetry.frame_index.store(self.logical_frame_index, Ordering::Relaxed); self.atomic_telemetry.cycles_budget.store( self.certifier .config .cycles_budget_per_frame .unwrap_or(Self::CYCLES_PER_LOGICAL_FRAME), Ordering::Relaxed, ); self.atomic_telemetry.cycles_used.store(0, Ordering::Relaxed); self.atomic_telemetry.syscalls.store(0, Ordering::Relaxed); self.atomic_telemetry.vm_steps.store(0, Ordering::Relaxed); } let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles); if budget > 0 { let run_result = { let mut ctx = HostContext::new(Some(hw)); vm.run_budget(budget, self, &mut ctx) }; match run_result { Ok(run) => { self.logical_frame_remaining_cycles = self.logical_frame_remaining_cycles.saturating_sub(run.cycles_used); self.atomic_telemetry.cycles_used.fetch_add(run.cycles_used, Ordering::Relaxed); self.atomic_telemetry.vm_steps.fetch_add(run.steps_executed, Ordering::Relaxed); if run.reason == LogicalFrameEndingReason::Breakpoint { self.paused = true; self.debug_step_request = false; self.log( LogLevel::Info, LogSource::Vm, 0xDEB1, format!("Breakpoint hit at PC 0x{:X}", vm.pc()), ); } if let LogicalFrameEndingReason::Panic(err) = run.reason { let report = CrashReport::VmPanic { message: err, pc: Some(vm.pc() as u32) }; self.log( LogLevel::Error, LogSource::Vm, report.log_tag(), report.summary(), ); self.last_crash_report = Some(report.clone()); return Some(report); } if let LogicalFrameEndingReason::Trap(trap) = &run.reason { let report = CrashReport::VmTrap { trap: trap.clone() }; self.log( LogLevel::Error, LogSource::Vm, report.log_tag(), report.summary(), ); self.last_crash_report = Some(report.clone()); return Some(report); } if run.reason == LogicalFrameEndingReason::FrameSync || run.reason == LogicalFrameEndingReason::EndOfRom { hw.gfx_mut().render_all(); // 1. Snapshot full telemetry at logical frame end let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw); self.atomic_telemetry .glyph_slots_used .store(glyph_bank.used_slots as u32, Ordering::Relaxed); self.atomic_telemetry .glyph_slots_total .store(glyph_bank.total_slots as u32, Ordering::Relaxed); self.atomic_telemetry .sound_slots_used .store(sound_bank.used_slots as u32, Ordering::Relaxed); self.atomic_telemetry .sound_slots_total .store(sound_bank.total_slots as u32, Ordering::Relaxed); self.atomic_telemetry .heap_used_bytes .store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed); self.atomic_telemetry .host_cpu_time_us .store(start.elapsed().as_micros() as u64, Ordering::Relaxed); let current_frame_logs = self.atomic_telemetry.current_logs_count.load(Ordering::Relaxed); self.atomic_telemetry .logs_count .store(current_frame_logs, Ordering::Relaxed); let ts_ms = self.boot_time.elapsed().as_millis() as u64; let telemetry_snapshot = self.atomic_telemetry.snapshot(); let violations = self.certifier.evaluate( &telemetry_snapshot, &mut self.log_service, ts_ms, ) as u32; self.atomic_telemetry.violations.store(violations, Ordering::Relaxed); self.atomic_telemetry .completed_logical_frames .fetch_add(1, Ordering::Relaxed); self.log_service.reset_count(); self.logical_frame_index += 1; self.logical_frame_active = false; self.logical_frame_remaining_cycles = 0; if run.reason == LogicalFrameEndingReason::FrameSync { self.needs_prepare_entry_call = true; } if self.debug_step_request { self.paused = true; self.debug_step_request = false; } } } Err(e) => { let report = CrashReport::VmPanic { message: e, pc: Some(vm.pc() as u32) }; self.log(LogLevel::Error, LogSource::Vm, report.log_tag(), report.summary()); self.last_crash_report = Some(report.clone()); return Some(report); } } } self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; // 2. High-frequency telemetry update (only if inspection is active) if self.inspection_active { let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw); self.atomic_telemetry .glyph_slots_used .store(glyph_bank.used_slots as u32, Ordering::Relaxed); self.atomic_telemetry .glyph_slots_total .store(glyph_bank.total_slots as u32, Ordering::Relaxed); self.atomic_telemetry .sound_slots_used .store(sound_bank.used_slots as u32, Ordering::Relaxed); self.atomic_telemetry .sound_slots_total .store(sound_bank.total_slots as u32, Ordering::Relaxed); self.atomic_telemetry .heap_used_bytes .store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed); self.atomic_telemetry.frame_index.store(self.logical_frame_index, Ordering::Relaxed); self.atomic_telemetry .host_cpu_time_us .store(start.elapsed().as_micros() as u64, Ordering::Relaxed); } None } pub(crate) fn begin_logical_frame( &mut self, _signals: &InputSignals, hw: &mut dyn HardwareBridge, ) { hw.audio_mut().clear_commands(); self.logs_written_this_frame.clear(); } }