use crate::{AddressKind, Engine, FatalError, HostCallError, OuterVm, PrettyBytes};
use alloc::vec::Vec;
use core::ops::RangeInclusive;
use log::{debug, log_enabled, trace, Level};
mod corevm;
mod pvm;
pub type HostCallHandler<O> = fn(&mut Engine<O>) -> Result<(), HostCallError>;
impl<O: OuterVm> Engine<O> {
pub(crate) fn create_host_call_handlers(
program_blob: &polkavm::ProgramBlob,
) -> (Vec<HostCallHandler<O>>, Vec<&'static str>) {
macro_rules! corevm {
($handler: ident) => {
(concat!("corevm_", stringify!($handler), "_ext"), corevm::$handler)
};
}
macro_rules! pvm {
($handler: ident) => {
(concat!("pvm_", stringify!($handler)), pvm::$handler)
};
}
let all_handlers: [(&str, HostCallHandler<O>); 18] = [
corevm!(gas),
corevm!(alloc),
corevm!(free),
corevm!(yield_console_data),
corevm!(video_mode),
corevm!(yield_video_frame),
corevm!(audio_mode),
corevm!(yield_audio_samples),
corevm!(recv_message),
corevm!(send_message),
corevm!(recv_host_message),
corevm!(poll),
corevm!(read_console_data),
corevm!(video_input_mode),
corevm!(read_video_frame),
corevm!(audio_input_mode),
corevm!(read_audio_frames),
pvm!(syscall),
];
let mut handlers: Vec<HostCallHandler<O>> =
Vec::with_capacity(program_blob.imports().len() as usize);
let mut names = if log_enabled!(Level::Debug) {
Vec::with_capacity(program_blob.imports().len() as usize)
} else {
Vec::new()
};
for (i, symbol) in program_blob.imports().into_iter().enumerate() {
let Some(symbol) = symbol.map(|symbol| symbol.into_inner()) else {
debug!("Unknown PVM import at {i}");
handlers.push(unknown);
names.push("unknown");
continue;
};
trace!("PVM import {:?} -> {i}", PrettyBytes(symbol));
let (handler, name) = all_handlers
.iter()
.find_map(|(name, handler)| {
(name.as_bytes() == symbol).then_some((*handler, *name))
})
.unwrap_or_else(|| {
debug!("Unknown PVM import {:?} at {i}", PrettyBytes(symbol));
(unknown, "unknown")
});
handlers.push(handler);
names.push(name);
}
(handlers, names)
}
}
fn unknown<O: OuterVm>(_engine: &mut Engine<O>) -> Result<(), HostCallError> {
Err(FatalError::UnknownHostCall.into())
}
impl<O: OuterVm> Engine<O> {
pub(crate) fn validate_guest_can_read(
&self,
address: u32,
length: u32,
) -> Result<(), FatalError> {
use AddressKind::*;
if length == 0 {
return Ok(());
}
let (first, last) = to_address_range(address, length)
.ok_or(FatalError::InvalidMemoryAccess)?
.into_inner();
match (self.memory_map.classify_address(first), self.memory_map.classify_address(last)) {
(Some(RoData), Some(RoData)) |
(Some(RwData), Some(RwData)) |
(Some(Stack), Some(Stack)) |
(Some(Heap), Some(Heap)) => Ok(()),
_ => Err(FatalError::InvalidMemoryAccess),
}
}
pub(crate) fn validate_guest_can_write(
&self,
address: u32,
length: u32,
) -> Result<(), FatalError> {
use AddressKind::*;
if length == 0 {
return Ok(());
}
let (first, last) = to_address_range(address, length)
.ok_or(FatalError::InvalidMemoryAccess)?
.into_inner();
match (self.memory_map.classify_address(first), self.memory_map.classify_address(last)) {
(Some(RwData), Some(RwData)) |
(Some(Stack), Some(Stack)) |
(Some(Heap), Some(Heap)) => Ok(()),
_ => Err(FatalError::InvalidMemoryAccess),
}
}
pub(crate) fn validate_guest_can_alloc(
&self,
address: u32,
length: u32,
) -> Result<(), FatalError> {
use AddressKind::*;
let (first, last) = to_address_range(address, length)
.ok_or(FatalError::InvalidMemoryAccess)?
.into_inner();
match (self.memory_map.classify_address(first), self.memory_map.classify_address(last)) {
(Some(Heap), Some(Heap)) => Ok(()),
_ => Err(FatalError::InvalidMemoryAccess),
}
}
}
fn to_address_range(address: u32, length: u32) -> Option<RangeInclusive<u32>> {
let first = address;
let last = first.checked_add(length.saturating_sub(1))?;
Some(first..=last)
}