corevm-engine 0.1.28

CoreVM engine that drives program execution either on the builder or CoreVM service side
Documentation
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 {
			// Address can be anything when the length is zero.
			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 {
			// Address can be anything when the length is zero.
			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),
		}
	}
}

/// Returns the address range or `None` on overflow.
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)
}