257 lines
10 KiB
Rust
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();
|
|
}
|
|
}
|