corevm-engine 0.1.28

CoreVM engine that drives program execution either on the builder or CoreVM service side
Documentation
use alloc::vec::Vec;
use bytes::Bytes;
use core::ops::Range;
use corevm_host::{
	fs::{self, ReadBlock},
	PageNum, PAGE_SIZE,
};
use jam_pvm_common::{ApiError, InvokeOutcome};
use jam_types::{SignedGas, SEGMENT_LEN};

/// A trait that abstracts JAM calls related to an inner VM.
pub trait InnerVm {
	fn void(&mut self, page: PageNum, num_pages: u32) -> Result<(), ApiError>;
	fn zero(&mut self, page: PageNum, num_pages: u32) -> Result<(), ApiError>;
	fn poke(&mut self, outer_src: &[u8], inner_dst: u32) -> Result<(), ApiError>;
	fn peek_into(&mut self, outer_dst: &mut [u8], inner_src: u32) -> Result<(), ApiError>;
	fn invoke(
		&mut self,
		gas: SignedGas,
		regs: [u64; 13],
	) -> Result<(InvokeOutcome, SignedGas, [u64; 13]), ApiError>;
	fn expunge(self) -> Result<u64, ApiError>;

	fn zero_poke(&mut self, outer_src: &[u8], inner_dst: u32) -> Result<(), ApiError> {
		// Make pages writable by calling `zero`.
		let page = PageNum::from_address(inner_dst);
		let num_pages = (outer_src.len() as u32).div_ceil(PAGE_SIZE);
		self.zero(page, num_pages)?;
		// Copy the data.
		self.poke(outer_src, inner_dst)?;
		Ok(())
	}

	fn poke_ro_rw_data_page(
		&mut self,
		data: &[u8],
		address_range: &Range<u32>,
		address: u32,
	) -> Result<(), ApiError> {
		debug_assert_eq!(0, address % PAGE_SIZE);
		debug_assert!(address_range.contains(&address));
		let data_start = address - address_range.start;
		if data_start < data.len() as u32 {
			// copy non-zero data
			let data_start = data_start as usize;
			let data_end = (data_start + PAGE_SIZE as usize).min(data.len());
			let data_range = data_start..data_end;
			self.zero_poke(&data[data_range], address)?;
		} else {
			// zero-out the remaining data
			let page = PageNum::from_address(address);
			self.zero(page, 1)?;
		}
		Ok(())
	}
}

/// A trait that abstracts JAM host-calls available in a refine environment and related to an outer
/// VM.
pub trait OuterVm {
	/// Inner VM wrapper.
	type InnerVm: crate::InnerVm;

	/// Export `segment` from the work package.
	///
	/// The segment length must not be longer than [`SEGMENT_LEN`](jam_types::SEGMENT_LEN).
	/// If it's shorter, the rest of of the bytes are zeroed.
	fn export(&mut self, segment: &[u8; SEGMENT_LEN]) -> Result<(), ApiError>;

	/// Reads file system block specified by its reference.
	fn read_file_block(&mut self, block_ref: &fs::BlockRef) -> Option<Bytes>;

	/// Creates a new inner VM and returns its wrapped handle.
	fn machine(&mut self, code: &[u8], program_counter: u64) -> Result<Self::InnerVm, ApiError>;

	/// Returns the contents of a file specified by its main block reference.
	///
	/// Uses [`read_file_block`](Self::read_file_block) to read file blocks.
	fn read_file(&mut self, block_ref: &fs::BlockRef) -> Result<Vec<u8>, fs::Error> {
		let mut block_reader = self.block_reader();
		let buf = fs::read(block_ref, &mut block_reader)?;
		Ok(buf)
	}

	/// Returns file system block reader that uses [`read_file_block`](Self::read_file_block) to
	/// read file blocks.
	fn block_reader(&mut self) -> Lookup<'_, Self> {
		Lookup { outer_vm: self }
	}
}

/// An implementation of [`ReadBlock`] that uses `OuterVM::read_file_block` to read files blocks.
pub struct Lookup<'a, O: OuterVm + ?Sized> {
	pub outer_vm: &'a mut O,
}

impl<O: OuterVm + ?Sized> ReadBlock for Lookup<'_, O> {
	fn read_block(&mut self, block_ref: &fs::BlockRef) -> Result<Bytes, fs::IoError> {
		self.outer_vm.read_file_block(block_ref).ok_or(fs::IoError)
	}
}