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 {
pub(crate) fn pretty_address(&self, address: u32) -> PrettyAddress {
let kind = self.classify_address(address);
PrettyAddress { address, kind }
}
pub(crate) fn pretty_page(&self, page: u32) -> PrettyPage {
PrettyPage { number: page, kind: self.classify_page(page), no_address: false }
}
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) }
}
pub(crate) fn pretty_page_ranges<I: Iterator<Item = u32> + Clone>(
&self,
pages: I,
) -> PrettyPages<'_, I> {
PrettyPages { pages, memory_map: self }
}
}
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()
}
}
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> {
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,
}
}
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 }
}
}
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]);
}
}