257 lines
10 KiB
Rust

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<CrashReport> {
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<CrashReport> {
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();
}
}