corevm-engine 0.1.28

CoreVM engine that drives program execution either on the builder or CoreVM service side
Documentation
//! Utilities for pretty-printing addresses, pages and other entities.

use crate::{AddressKind, Engine, MemoryMap, OuterVm};
use core::{
	iter::FusedIterator,
	ops::{Range, RangeInclusive},
};
use corevm_host::PAGE_SIZE;
use jam_pvm_common::InvokeOutcome;

pub struct PrettyInvokeOutcome {
	outcome: InvokeOutcome,
	host_call: Option<PrettyHostCall>,
	kind: Option<AddressKind>,
}

impl core::fmt::Debug for PrettyInvokeOutcome {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		match self.outcome {
			InvokeOutcome::Halt => f.write_str("Halt"),
			InvokeOutcome::PageFault(address) => {
				let address = PrettyAddress { kind: self.kind, address: address as u32 };
				f.debug_tuple("PageFault").field(&address).finish()
			},
			InvokeOutcome::HostCallFault(i) => {
				let inner: &dyn core::fmt::Debug = match self.host_call {
					Some(ref host_call) => host_call,
					None => &i,
				};
				f.debug_tuple("HostCallFault").field(inner).finish()
			},
			InvokeOutcome::Panic => f.write_str("Panic"),
			InvokeOutcome::OutOfGas => f.write_str("OutOfGas"),
		}
	}
}

pub struct PrettyAddress {
	address: u32,
	kind: Option<AddressKind>,
}

impl core::fmt::Debug for PrettyAddress {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		if self.address == 0 {
			write!(f, "null")
		} else {
			let kind = self.kind.map(AddressKind::as_str).unwrap_or("?");
			let address = self.address;
			let page = address / PAGE_SIZE;
			write!(f, "{address:#x}/{page}/{kind}")
		}
	}
}

pub struct PrettyPage {
	number: u32,
	kind: Option<AddressKind>,
	no_address: bool,
}

impl core::fmt::Debug for PrettyPage {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		let page = self.number;
		let address = page.checked_mul(PAGE_SIZE);
		match address {
			Some(address) => {
				let kind = self.kind.map(AddressKind::as_str).unwrap_or("?");
				if self.no_address {
					write!(f, "{page}/{kind}")
				} else {
					write!(f, "{page}/{address:#x}/{kind}")
				}
			},
			None => write!(f, "{page}(invalid!)"),
		}
	}
}

pub struct PrettyPageRange {
	first: PrettyPage,
	last: PrettyPage,
}

impl core::fmt::Debug for PrettyPageRange {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		if self.first.number == self.last.number {
			core::fmt::Debug::fmt(&self.first, f)
		} else {
			write!(f, "{:?}..={:?}", self.first, self.last)
		}
	}
}

impl MemoryMap {
	/// Prints address, the corresponding page and memory region.
	pub(crate) fn pretty_address(&self, address: u32) -> PrettyAddress {
		let kind = self.classify_address(address);
		PrettyAddress { address, kind }
	}

	/// Prints page, the corresponding address and memory region.
	pub(crate) fn pretty_page(&self, page: u32) -> PrettyPage {
		PrettyPage { number: page, kind: self.classify_page(page), no_address: false }
	}

	/// Prints a range of pages, the corresponding addresses and memory regions.
	pub(crate) fn pretty_page_range(&self, pages: Range<u32>) -> PrettyPageRange {
		let first = pages.start;
		let last = pages.end.saturating_sub(1);
		PrettyPageRange { first: self.pretty_page(first), last: self.pretty_page(last) }
	}

	/// Prints a range of pages and the corresponding memory regions.
	pub(crate) fn pretty_page_ranges<I: Iterator<Item = u32> + Clone>(
		&self,
		pages: I,
	) -> PrettyPages<'_, I> {
		PrettyPages { pages, memory_map: self }
	}
}

/// Displays the byte slice either as UTF-8 string or as a hexadecimal string.
pub struct PrettyBytes<'a>(pub &'a [u8]);

impl core::fmt::Debug for PrettyBytes<'_> {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		const MAX_BYTES: usize = 32;
		if let Ok(s) = core::str::from_utf8(self.0) {
			f.write_str("\"")?;
			let mut chars = s.chars();
			for _ in 0..MAX_BYTES {
				let Some(ch) = chars.next() else {
					break;
				};
				write!(f, "{}", ch.escape_debug())?;
			}
			f.write_str("\"")?;
			if chars.next().is_some() {
				f.write_str("…")?;
			}
		} else {
			f.write_str("0x")?;
			for b in self.0.iter().copied().take(MAX_BYTES) {
				write!(f, "{b:02x}")?;
			}
			if self.0.len() > MAX_BYTES {
				f.write_str("…")?;
			}
		}
		Ok(())
	}
}

pub struct PrettyPages<'a, I> {
	pages: I,
	memory_map: &'a MemoryMap,
}

impl<I: Iterator<Item = u32> + Clone> core::fmt::Debug for PrettyPages<'_, I> {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		let ranges = RangesIter::new(self.pages.clone()).map(|range| {
			let (first, last) = range.into_inner();
			PrettyPageRange {
				first: PrettyPage {
					number: first,
					kind: self.memory_map.classify_page(first),
					no_address: true,
				},
				last: PrettyPage {
					number: last,
					kind: self.memory_map.classify_page(last),
					no_address: true,
				},
			}
		});
		f.debug_list().entries(ranges).finish()
	}
}

/// An iterator over consecutive page ranges.
///
/// Turns `Iterator<Item = u32>` into `Iterator<Item = RangeInclusive<u32>>`, i.e.
/// breaks the original sequence into contiguous sub-sequences, and creates an iterator over
/// them.
///
/// Expects a sequence of _unique_ page numbers in _ascending_ order.
///
/// # Example
///
/// Sequence `[0, 1, 2, 3, 5, 7, 8, 9]` is turned into `[0..=3, 5..=5, 7..=9]`.
struct RangesIter<I: Iterator> {
	inner: I,
	start: Option<I::Item>,
	end: u32,
}

impl<I: Iterator> RangesIter<I> {
	pub const fn new(inner: I) -> Self {
		Self { inner, start: None, end: 0 }
	}
}

impl<I: Iterator<Item = u32>> Iterator for RangesIter<I> {
	type Item = RangeInclusive<u32>;

	fn next(&mut self) -> Option<Self::Item> {
		for i in self.inner.by_ref() {
			match self.start {
				Some(start) =>
					if self.end.saturating_add(1) != i {
						let end = self.end;
						self.start = Some(i);
						self.end = i;
						return Some(start..=end);
					},
				None => {
					self.start = Some(i);
				},
			}
			self.end = i;
		}
		if let Some(start) = self.start.take() {
			return Some(start..=self.end);
		}
		None
	}
}

impl<I: Iterator<Item = u32> + FusedIterator> FusedIterator for RangesIter<I> {}

pub struct PrettyHostCall {
	name: &'static str,
	index: u64,
}

impl core::fmt::Debug for PrettyHostCall {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		write!(f, "{}({})", self.name, self.index)
	}
}

impl<O: OuterVm> Engine<O> {
	/// Prints invoke outcome variant with additional info for host-call fault and page fault.
	pub(crate) fn pretty_invoke_outcome(&self, outcome: InvokeOutcome) -> PrettyInvokeOutcome {
		PrettyInvokeOutcome {
			kind: match outcome {
				InvokeOutcome::PageFault(address) => u32::try_from(address)
					.ok()
					.and_then(|address| self.memory_map.classify_address(address)),
				_ => None,
			},
			host_call: match outcome {
				InvokeOutcome::HostCallFault(i) => Some(self.pretty_host_call(i)),
				_ => None,
			},
			outcome,
		}
	}

	/// Prints host-call name and number.
	pub(crate) fn pretty_host_call(&self, index: u64) -> PrettyHostCall {
		let name = self.host_call_names.get(index as usize).copied().unwrap_or("unknown");
		PrettyHostCall { name, index }
	}
}

/// Prints unsigned integer either as is or as `NAME::MAX` if it's maximum value.
pub struct PrettyUint<T>(pub T);

impl<T: UintMax + core::fmt::Display + core::cmp::PartialEq> core::fmt::Debug for PrettyUint<T> {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		if self.0 == T::MAX {
			write!(f, "{}::MAX", core::any::type_name::<T>())
		} else {
			write!(f, "{}", self.0)
		}
	}
}

pub trait UintMax {
	const MAX: Self;
}

macro_rules! impl_uint_max_for {
	($($type: ident)+) => {
        $(
            impl UintMax for $type {
                const MAX: $type = $type::MAX;
            }
        )+
	};
}

impl_uint_max_for! {u64 u32 u16 u8}

#[cfg(test)]
mod tests {
	use super::*;
	use alloc::{vec, vec::Vec};

	#[test]
	fn ranges_iter_works() {
		assert_eq!(
			RangesIter::new([0_u32, 1, 2, 3, 5, 7, 8, 9].into_iter()).collect::<Vec<_>>(),
			vec![0_u32..=3, 5..=5, 7..=9]
		);
		assert_eq!(RangesIter::new([].into_iter()).collect::<Vec<_>>(), vec![]);
		assert_eq!(RangesIter::new(0_u32..10).collect::<Vec<_>>(), vec![0_u32..=9]);
	}
}