use crate::{Config, Engine, abi::Regs, linker::HostMap};
use anyhow::{Context, Result, anyhow, bail};
use compiler::{Memory, trap};
use rv::Reg;
use std::{
fmt,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use translator::VmCtx;
#[derive(Debug)]
pub enum Trap {
MemoryFault {
address: Option<u64>,
},
BadIndirectTarget {
target: u64,
},
Breakpoint,
UnknownHostCall(u64),
HostCall(anyhow::Error),
IllegalInstruction,
Interrupted,
}
impl fmt::Display for Trap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Trap::MemoryFault {
address: Some(addr),
} => write!(f, "guest memory fault at {addr:#x}"),
Trap::MemoryFault { address: None } => {
write!(f, "memory fault outside the guest address space")
}
Trap::BadIndirectTarget { target } => {
write!(f, "indirect jump to an unknown target {target:#x}")
}
Trap::Breakpoint => write!(f, "guest executed ebreak"),
Trap::UnknownHostCall(number) => write!(f, "no host function for call {number}"),
Trap::HostCall(error) => write!(f, "host call failed: {error}"),
Trap::IllegalInstruction => write!(f, "guest reached an illegal instruction"),
Trap::Interrupted => write!(f, "guest was interrupted"),
}
}
}
impl std::error::Error for Trap {}
pub(crate) struct State<T> {
pub module: Arc<compiler::Module>,
pub memory: Memory,
pub ctx: VmCtx,
pub hosts: Arc<HostMap<T>>,
pub interrupt: Arc<AtomicU64>,
pub failure: Option<Trap>,
}
pub struct Store<T> {
data: T,
engine: Engine,
config: Config,
pub(crate) state: Option<State<T>>,
}
unsafe impl<T: Send> Send for Store<T> {}
impl<T> Store<T> {
pub fn new(engine: &Engine, data: T) -> Self {
Store {
data,
config: engine.config().clone(),
engine: engine.clone(),
state: None,
}
}
pub fn data(&self) -> &T {
&self.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub fn into_data(self) -> T {
self.data
}
pub fn engine(&self) -> &Engine {
&self.engine
}
pub fn interrupt_handle(&self) -> Result<Interrupt> {
let state = self.instantiated()?;
if !state.module.interruptible() {
bail!("this module was not compiled with Config::interruptible");
}
Ok(Interrupt(state.interrupt.clone()))
}
pub fn heap(&self) -> Result<std::ops::Range<u64>> {
Ok(self.instantiated()?.memory.heap())
}
pub fn read(&self, addr: u64, len: u64) -> Result<&[u8]> {
self.instantiated()?.memory.read(addr, len)
}
pub fn write(&mut self, addr: u64, data: &[u8]) -> Result<()> {
let state = self
.state
.as_mut()
.ok_or_else(|| anyhow!("store has no instance"))?;
state.memory.write(addr, data)
}
fn instantiated(&self) -> Result<&State<T>> {
self.state
.as_ref()
.ok_or_else(|| anyhow!("store has no instance; call Linker::instantiate first"))
}
pub(crate) fn instantiate(
&mut self,
module: Arc<compiler::Module>,
hosts: Arc<HostMap<T>>,
) -> Result<()> {
let memory = Memory::new(module.program(), module.memory_size(), self.stack_size())
.context("failed to map guest memory")?;
let interrupt = Arc::new(AtomicU64::new(0));
let ctx = VmCtx {
regs: [0; 32],
memory: memory.base(),
dispatch: module.dispatch().as_ptr(),
dispatch_len: module.dispatch().len() as u64,
text_base: module.program().text.start,
host_call: dispatch::<T> as *const u8,
host_data: std::ptr::null_mut(),
interrupt: Arc::as_ptr(&interrupt).cast(),
trap: 0,
detail: 0,
};
self.state = Some(State {
module,
memory,
ctx,
hosts,
interrupt,
failure: None,
});
Ok(())
}
fn stack_size(&self) -> u64 {
let page = compiler::memory::host_page();
self.config.stack_size.div_ceil(page).max(1) * page
}
}
pub(crate) fn enter<T, P: Regs, R: Regs>(
store: &mut Store<T>,
entry: *const u8,
params: P,
) -> Result<R> {
let host_data: *mut Store<T> = store;
let state = store
.state
.as_mut()
.ok_or_else(|| anyhow!("store has no instance; call Linker::instantiate first"))?;
state.failure = None;
state.ctx.trap = 0;
state.ctx.detail = 0;
state.ctx.host_data = host_data.cast();
state.ctx.regs = [0; 32];
state.ctx.regs[Reg::SP.index()] = state.memory.stack_pointer();
params.write(&mut state.ctx.regs);
trap::set_guest_region(state.memory.base() as usize, state.memory.size());
let trampoline: extern "C" fn(*mut VmCtx, *const u8) =
unsafe { std::mem::transmute(state.module.trampoline()) };
let ctx: *mut VmCtx = &raw mut state.ctx;
let outcome = trap::protect(|| trampoline(ctx, entry));
let state = store.state.as_mut().expect("instance is still present");
if let Err(fault) = outcome {
return Err(Trap::MemoryFault {
address: fault.guest,
}
.into());
}
if let Some(failure) = state.failure.take() {
return Err(failure.into());
}
match translator::Trap::from_code(state.ctx.trap) {
translator::Trap::None => Ok(R::read(&state.ctx.regs)),
translator::Trap::BadIndirectTarget => Err(Trap::BadIndirectTarget {
target: state.ctx.detail,
}
.into()),
translator::Trap::Breakpoint => Err(Trap::Breakpoint.into()),
translator::Trap::HostCall => Err(Trap::HostCall(anyhow!("host call failed")).into()),
translator::Trap::IllegalInstruction => Err(Trap::IllegalInstruction.into()),
translator::Trap::Interrupted => Err(Trap::Interrupted.into()),
}
}
extern "C" fn dispatch<T>(ctx: *mut VmCtx) -> u64 {
let (host_data, number) = unsafe { ((*ctx).host_data, (*ctx).regs[Reg::A7.index()]) };
let store = unsafe { &mut *(host_data as *mut Store<T>) };
let Some(state) = store.state.as_ref() else {
return 1;
};
let hosts = state.hosts.clone();
let Some(func) = hosts.get(&number) else {
fail(store, Trap::UnknownHostCall(number));
return 1;
};
match func(Caller { store }) {
Ok(()) => 0,
Err(error) => {
fail(store, Trap::HostCall(error));
1
}
}
}
fn fail<T>(store: &mut Store<T>, trap: Trap) {
if let Some(state) = store.state.as_mut() {
state.failure = Some(trap);
}
}
#[derive(Clone, Debug)]
pub struct Interrupt(Arc<AtomicU64>);
impl Interrupt {
pub fn interrupt(&self) {
self.0.store(1, Ordering::Release);
}
pub fn clear(&self) {
self.0.store(0, Ordering::Release);
}
pub fn is_set(&self) -> bool {
self.0.load(Ordering::Acquire) != 0
}
}
pub struct Caller<'a, T> {
pub(crate) store: &'a mut Store<T>,
}
impl<T> Caller<'_, T> {
pub fn data(&self) -> &T {
self.store.data()
}
pub fn data_mut(&mut self) -> &mut T {
self.store.data_mut()
}
pub fn reg(&self, reg: Reg) -> u64 {
self.state().ctx.regs[reg.index()]
}
pub fn set_reg(&mut self, reg: Reg, value: u64) {
self.state_mut().ctx.regs[reg.index()] = value;
}
pub fn heap(&self) -> std::ops::Range<u64> {
self.state().memory.heap()
}
pub fn read(&self, addr: u64, len: u64) -> Result<&[u8]> {
self.state().memory.read(addr, len)
}
pub fn write(&mut self, addr: u64, data: &[u8]) -> Result<()> {
self.state_mut().memory.write(addr, data)
}
pub(crate) fn reborrow(&mut self) -> Caller<'_, T> {
Caller { store: self.store }
}
pub(crate) fn arg(&self, index: usize) -> u64 {
self.reg(Reg::new((Reg::A0.index() + index) as u8))
}
pub(crate) fn set_results<R: Regs>(&mut self, results: R) {
results.write(&mut self.state_mut().ctx.regs);
}
fn state(&self) -> &State<T> {
self.store
.state
.as_ref()
.expect("caller implies an instance")
}
fn state_mut(&mut self) -> &mut State<T> {
self.store
.state
.as_mut()
.expect("caller implies an instance")
}
}