use std::io::{Read, Write};
use serde::{Deserialize, Serialize};
use crate::arch::Hart;
use crate::arch::csr::Csrs;
use crate::arch::pmp::PmpEntry;
use crate::common::PhysAddr;
use crate::isa::privileged::PrivilegeMode;
use crate::isa::reg::RegIdx;
use crate::sim::memory::Ram;
use crate::system::simulator::Simulator;
const MAGIC: &str = "rvsim-checkpoint";
pub const VERSION: u64 = 6;
const PAGE_BYTES: usize = 4096;
#[derive(Debug, thiserror::Error)]
pub enum CheckpointError {
#[error("checkpoint I/O: {0}")]
Io(#[from] std::io::Error),
#[error("checkpoint header: {0}")]
Header(#[from] serde_json::Error),
#[error("not an rvsim checkpoint")]
NotACheckpoint,
#[error("checkpoint version {found} cannot be restored; this build restores version {VERSION}")]
Version {
found: u64,
},
#[error("checkpoint device: {0}")]
Device(String),
#[error("checkpoint has {what} {saved}, this system has {current}")]
Mismatch {
what: &'static str,
saved: u64,
current: u64,
},
}
#[derive(Serialize, Deserialize)]
struct Header {
magic: String,
version: u64,
cycle: u64,
direct_mode: bool,
trace: bool,
ram_base: u64,
ram_size: u64,
vlen_bits: u64,
harts: Vec<HartState>,
devices: serde_json::Value,
}
#[derive(Serialize, Deserialize)]
struct HartState {
pc: u64,
privilege: u8,
wfi_waiting: bool,
sw_seip: bool,
instructions_retired: u64,
gpr: Vec<u64>,
fpr: Vec<u64>,
vpr: Vec<u8>,
csrs: Csrs,
pmp: Vec<PmpEntry>,
reservation: Option<u64>,
}
impl HartState {
fn of(hart: &Hart, reservation: Option<PhysAddr>) -> Self {
let registers = 0u8..32;
Self {
pc: hart.pc,
privilege: hart.privilege.to_u8(),
wfi_waiting: hart.wfi_waiting,
sw_seip: hart.sw_seip,
instructions_retired: hart.instructions_retired,
gpr: registers.clone().map(|i| hart.regs.read(RegIdx::new(i))).collect(),
fpr: registers.map(|i| hart.regs.read_f(RegIdx::new(i))).collect(),
vpr: hart.regs.vpr().bytes().to_vec(),
csrs: hart.csrs.clone(),
pmp: hart.pmp.entries().to_vec(),
reservation: reservation.map(|line| line.val()),
}
}
fn apply(&self, hart: &mut Hart) {
hart.pc = self.pc;
hart.privilege = PrivilegeMode::from_u8(self.privilege);
hart.wfi_waiting = self.wfi_waiting;
hart.sw_seip = self.sw_seip;
hart.instructions_retired = self.instructions_retired;
for (i, &value) in (0u8..).zip(&self.gpr) {
hart.regs.write(RegIdx::new(i), value);
}
for (i, &value) in (0u8..).zip(&self.fpr) {
hart.regs.write_f(RegIdx::new(i), value);
}
hart.regs.vpr_mut().set_bytes(&self.vpr);
hart.csrs = self.csrs.clone();
hart.pmp.restore(&self.pmp);
}
}
fn write_pages(ram: &[u8], out: &mut impl Write) -> std::io::Result<()> {
let pages: Vec<&[u8]> = ram.chunks(PAGE_BYTES).collect();
let mut present = vec![0u8; pages.len().div_ceil(8)];
for (index, page) in pages.iter().enumerate() {
if page.iter().any(|&byte| byte != 0) {
present[index / 8] |= 1 << (index % 8);
}
}
out.write_all(&present)?;
for (index, page) in pages.iter().enumerate() {
if present[index / 8] & (1 << (index % 8)) != 0 {
out.write_all(page)?;
}
}
Ok(())
}
fn read_pages(input: &mut impl Read, ram: &mut [u8]) -> std::io::Result<()> {
let mut present = vec![0u8; ram.len().div_ceil(PAGE_BYTES).div_ceil(8)];
input.read_exact(&mut present)?;
for (index, page) in ram.chunks_mut(PAGE_BYTES).enumerate() {
if present[index / 8] & (1 << (index % 8)) != 0 {
input.read_exact(page)?;
} else {
page.fill(0);
}
}
Ok(())
}
const fn mismatch(what: &'static str, saved: u64, current: u64) -> Result<(), CheckpointError> {
if saved == current { Ok(()) } else { Err(CheckpointError::Mismatch { what, saved, current }) }
}
impl Simulator {
pub fn save_checkpoint(&mut self, out: &mut impl Write) -> Result<(), CheckpointError> {
self.drain();
let state = &self.state;
let ram = state.memory.ram();
let reservations = state.memory.reservations();
let header = Header {
magic: MAGIC.into(),
version: VERSION,
cycle: state.cycle,
direct_mode: state.direct_mode,
trace: state.trace.armed,
ram_base: ram.map_or(0, Ram::base),
ram_size: ram.map_or(0, Ram::size),
vlen_bits: state.config.pipeline.vlen.bits() as u64,
harts: state
.harts
.iter()
.map(|hart| HartState::of(hart, reservations.reserved(hart.hart_id)))
.collect(),
devices: state.bus.checkpoint_devices(),
};
let header = serde_json::to_vec(&header)?;
out.write_all(&(header.len() as u64).to_le_bytes())?;
out.write_all(&header)?;
if let Some(ram) = ram {
write_pages(ram.bytes(), out)?;
}
out.flush()?;
Ok(())
}
pub fn restore_checkpoint(&mut self, input: &mut impl Read) -> Result<(), CheckpointError> {
let mut length = [0u8; 8];
input.read_exact(&mut length)?;
let mut header = vec![0u8; u64::from_le_bytes(length) as usize];
input.read_exact(&mut header)?;
let header: Header = serde_json::from_slice(&header)?;
if header.magic != MAGIC {
return Err(CheckpointError::NotACheckpoint);
}
if header.version != VERSION {
return Err(CheckpointError::Version { found: header.version });
}
let ram = self.state.memory.ram();
mismatch("harts", header.harts.len() as u64, self.state.harts.len() as u64)?;
mismatch("RAM bytes", header.ram_size, ram.map_or(0, Ram::size))?;
mismatch("RAM base", header.ram_base, ram.map_or(0, Ram::base))?;
mismatch("VLEN", header.vlen_bits, self.state.config.pipeline.vlen.bits() as u64)?;
self.state.bus.check_device_states(&header.devices).map_err(CheckpointError::Device)?;
self.drain();
if let Some(ram) = self.state.memory.ram_mut() {
read_pages(input, ram.bytes_mut())?;
}
self.apply_header(&header)
}
fn apply_header(&mut self, header: &Header) -> Result<(), CheckpointError> {
let state = &mut self.state;
state.cycle = header.cycle;
state.direct_mode = header.direct_mode;
state.trace.armed = header.trace;
for (hart, saved) in state.harts.iter_mut().zip(&header.harts) {
saved.apply(hart);
}
for (hart, saved) in state.harts.iter().zip(&header.harts) {
let reservations = state.uncore.memory.reservations_mut();
match saved.reservation {
Some(line) => reservations.set(hart.hart_id, PhysAddr::new(line)),
None => reservations.clear(hart.hart_id),
}
}
state.uncore.bus.restore_devices(&header.devices).map_err(CheckpointError::Device)?;
for core in &mut state.cores {
let units = &mut core.units;
units.l1_i_cache.invalidate_all(&mut state.uncore.stats);
units.l1_d_cache.invalidate_all(&mut state.uncore.stats);
units.l2_cache.invalidate_all(&mut state.uncore.stats);
units.mmu.dtlb.flush();
units.mmu.itlb.flush();
units.mmu.l2_tlb.flush();
}
state.uncore.l3_cache.invalidate_all(&mut state.uncore.stats);
state.mem_controller.resume_at(header.cycle);
if let Some(coherence) = &mut state.uncore.coherence {
coherence.forget_cached_lines();
}
self.state.reset_stats();
self.sync_arch_regs();
Ok(())
}
}