use crate::{
Error,
Func,
TrapCode,
engine::{
ResumableHostTrapError,
ResumableOutOfFuelError,
StackConfig,
executor::{
Cell,
CellError,
CellsReader,
CellsWriter,
CodeMap,
InOutParams,
LoadFromCellsByValue,
StoreToCells,
handler::{
dispatch::{Control, ExecutionOutcome},
utils::extract_mem0,
},
},
utils::unreachable_unchecked,
},
instance::InstanceEntity,
ir::{self, BoundedSlotSpan, Slot, SlotSpan},
store::PrunedStore,
};
use alloc::vec::Vec;
use core::{
cmp,
marker::PhantomData,
mem,
ops,
ptr::{self, NonNull},
slice,
};
pub struct VmState<'vm> {
pub store: &'vm mut PrunedStore,
pub stack: &'vm mut Stack,
pub code: &'vm CodeMap,
done_reason: Option<DoneReason>,
}
impl<'vm> VmState<'vm> {
pub fn new(store: &'vm mut PrunedStore, stack: &'vm mut Stack, code: &'vm CodeMap) -> Self {
Self {
store,
stack,
code,
done_reason: None,
}
}
pub fn done_with(&mut self, reason: impl FnOnce() -> DoneReason) {
#[cold]
#[inline(never)]
fn err(prev: &DoneReason, reason: impl FnOnce() -> DoneReason) -> ! {
panic!(
"\
tried to done with reason while reason already exists:\n\
\t- new reason: {:?},\n\
\t- old reason: {:?},\
",
reason(),
prev,
)
}
if let Some(prev) = &self.done_reason {
err(prev, reason)
}
self.done_reason = Some(reason());
}
pub fn take_done_reason(&mut self) -> DoneReason {
let Some(reason) = self.done_reason.take() else {
panic!("missing break reason")
};
reason
}
pub fn execution_outcome(&mut self) -> Result<Sp, ExecutionOutcome> {
self.take_done_reason().into_execution_outcome()
}
}
#[derive(Debug)]
pub enum DoneReason {
Return(Sp),
Host(ResumableHostTrapError),
OutOfFuel(ResumableOutOfFuelError),
Error(Error),
}
impl DoneReason {
#[cold]
#[inline]
pub fn error(error: Error) -> Self {
Self::Error(error)
}
#[cold]
#[inline]
pub fn host_error(error: Error, func: Func, results: SlotSpan) -> Self {
Self::Host(ResumableHostTrapError::new(error, func, results))
}
#[cold]
#[inline]
pub fn out_of_fuel(required_fuel: u64) -> Self {
Self::OutOfFuel(ResumableOutOfFuelError::new(required_fuel))
}
#[inline]
pub fn into_execution_outcome(self) -> Result<Sp, ExecutionOutcome> {
let outcome = match self {
DoneReason::Return(sp) => return Ok(sp),
DoneReason::Host(error) => error.into(),
DoneReason::OutOfFuel(error) => error.into(),
DoneReason::Error(error) => error.into(),
};
Err(outcome)
}
}
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Inst {
value: NonNull<InstanceEntity>,
marker: PhantomData<*const InstanceEntity>,
}
impl From<&'_ InstanceEntity> for Inst {
fn from(entity: &'_ InstanceEntity) -> Self {
Self {
value: entity.into(),
marker: PhantomData,
}
}
}
impl PartialEq for Inst {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl Eq for Inst {}
impl Inst {
pub unsafe fn as_ref(&self) -> &InstanceEntity {
unsafe { self.value.as_ref() }
}
}
unsafe impl Send for Inst {}
unsafe impl Sync for Inst {}
mod inst_tests {
use super::*;
const _: fn() = || {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<InstanceEntity>();
assert_sync::<InstanceEntity>();
assert_send::<Inst>();
assert_sync::<Inst>();
};
}
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Mem0Ptr(*mut u8);
impl From<*mut u8> for Mem0Ptr {
fn from(value: *mut u8) -> Self {
Self(value)
}
}
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Mem0Len(usize);
impl From<usize> for Mem0Len {
fn from(value: usize) -> Self {
Self(value)
}
}
pub fn mem0_bytes<'a>(mem0: Mem0Ptr, mem0_len: Mem0Len) -> &'a mut [u8] {
unsafe { slice::from_raw_parts_mut(mem0.0, mem0_len.0) }
}
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Ip {
value: *const u8,
}
impl<'a> From<&'a [u8]> for Ip {
fn from(ops: &'a [u8]) -> Self {
Self {
value: ops.as_ptr(),
}
}
}
impl Ip {
#[inline]
pub unsafe fn decode<T: ir::Decode>(self) -> (Ip, T) {
struct IpDecoder(Ip);
impl ir::Decoder for IpDecoder {
#[inline(always)]
fn read_bytes(&mut self, buffer: &mut [u8]) -> Result<(), ir::DecodeError> {
let src = self.0.value;
let dst = buffer.as_mut_ptr();
let len = buffer.len();
unsafe { ptr::copy_nonoverlapping(src, dst, len) };
self.0 = unsafe { self.0.add(len) };
Ok(())
}
}
let mut ip = IpDecoder(self);
let decoded = match <T as ir::Decode>::decode(&mut ip) {
Ok(decoded) => decoded,
Err(error) => unsafe {
crate::engine::utils::unreachable_unchecked!(
"failed to decode `OpCode` or op-handler: {error}"
)
},
};
(ip.0, decoded)
}
#[inline]
pub unsafe fn skip<T: ir::Decode>(self) -> Ip {
let (ip, _) = unsafe { self.decode::<T>() };
ip
}
#[inline]
pub unsafe fn offset(self, delta: isize) -> Self {
let value = unsafe { self.value.byte_offset(delta) };
Self { value }
}
#[inline]
pub unsafe fn add(self, delta: usize) -> Self {
let value = unsafe { self.value.byte_add(delta) };
Self { value }
}
}
unsafe impl Send for Ip {}
mod ip_tests {
use super::*;
const _: fn() = || {
fn assert_send<T: Send>() {}
assert_send::<Ip>();
};
const _: fn() = || {
trait AmbiguousIfSync<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
struct Invalid;
impl<T: ?Sized + Sync> AmbiguousIfSync<Invalid> for T {}
let _ = <Ip as AmbiguousIfSync<_>>::some_item;
};
}
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Sp {
value: *mut Cell,
}
impl CellsWriter for Sp {
#[inline]
fn next(&mut self, value: Cell) -> Result<(), CellError> {
unsafe {
ptr::write(self.value, value);
self.value = self.value.add(1);
};
Ok(())
}
}
impl CellsReader for Sp {
#[inline]
fn next(&mut self) -> Result<Cell, CellError> {
let value = unsafe {
let value = ptr::read(self.value);
self.value = self.value.add(1);
value
};
Ok(value)
}
}
impl Sp {
#[inline]
pub fn new(value: *mut Cell) -> Self {
Self { value }
}
pub fn dangling() -> Self {
Self {
value: ptr::dangling_mut(),
}
}
pub fn offset(self, slot: Slot) -> Self {
let delta = usize::from(u16::from(slot));
let value = unsafe { self.value.add(delta) };
Self { value }
}
pub unsafe fn get<T>(self, slot: Slot) -> T
where
T: LoadFromCellsByValue,
{
let mut sp = self.offset(slot);
let Ok(value) = <T as LoadFromCellsByValue>::load_from_cells_by_value(&mut sp) else {
unsafe { unreachable_unchecked!() }
};
value
}
pub unsafe fn set<T>(self, slot: Slot, value: T)
where
T: StoreToCells,
{
let mut sp = self.offset(slot);
let Ok(_) = <T as StoreToCells>::store_to_cells(value, &mut sp) else {
unsafe { unreachable_unchecked!() }
};
}
}
#[derive(Debug)]
pub struct Stack {
values: ValueStack,
frames: CallStack,
}
type ReturnCallHost = Control<(Ip, Sp, Inst), Sp>;
impl Stack {
pub fn new(config: &StackConfig) -> Self {
Self {
values: ValueStack::new(config.min_stack_height(), config.max_stack_height()),
frames: CallStack::new(config.max_recursion_depth()),
}
}
pub fn empty() -> Self {
Self {
values: ValueStack::empty(),
frames: CallStack::empty(),
}
}
pub fn reset(&mut self) {
self.values.reset();
self.frames.reset();
}
pub fn bytes_allocated(&self) -> usize {
self.values
.bytes_allocated()
.saturating_add(self.frames.bytes_allocated())
}
pub fn sync_ip(&mut self, ip: Ip) {
self.frames.sync_ip(ip);
}
pub fn restore_frame(&mut self) -> (Ip, Sp, Inst) {
let Some((ip, start, instance)) = self.frames.restore_frame() else {
panic!("restore_frame: missing top-frame")
};
let sp = self.values.sp_or_dangling(start);
(ip, sp, instance)
}
pub fn return_prepare_host_frame<'a>(
&'a mut self,
callee_params: BoundedSlotSpan,
results_len: u16,
caller_instance: Inst,
) -> Result<(ReturnCallHost, InOutParams<'a>), TrapCode> {
let (callee_start, caller) = self.frames.return_prepare_host_frame(caller_instance);
self.values
.return_prepare_host_frame(caller, callee_start, callee_params, results_len)
}
pub fn prepare_host_frame<'a>(
&'a mut self,
caller_ip: Option<Ip>,
callee_params: BoundedSlotSpan,
results_len: u16,
) -> Result<(Sp, InOutParams<'a>), TrapCode> {
let caller_start = self.frames.prepare_host_frame(caller_ip);
self.values
.prepare_host_frame(caller_start, callee_params, results_len)
}
#[inline(always)]
pub fn push_frame(
&mut self,
caller_ip: Option<Ip>,
callee_ip: Ip,
callee_params: BoundedSlotSpan,
callee_size: usize,
callee_instance: Option<Inst>,
) -> Result<Sp, TrapCode> {
let start = self
.frames
.push(caller_ip, callee_ip, callee_params, callee_instance)?;
self.values.push(start, callee_size, callee_params.len())
}
pub fn pop_frame(
&mut self,
store: &mut PrunedStore,
mem0: Mem0Ptr,
mem0_len: Mem0Len,
instance: Inst,
) -> Option<(Ip, Sp, Mem0Ptr, Mem0Len, Inst)> {
let (ip, start, changed_instance) = self.frames.pop()?;
let sp = self.values.sp_or_dangling(start);
let (mem0, mem0_len, instance) = match changed_instance {
Some(instance) => {
let (mem0, mem0_len) = extract_mem0(store, instance);
(mem0, mem0_len, instance)
}
None => (mem0, mem0_len, instance),
};
Some((ip, sp, mem0, mem0_len, instance))
}
#[inline(always)]
pub fn replace_frame(
&mut self,
callee_ip: Ip,
callee_params: BoundedSlotSpan,
callee_size: usize,
callee_instance: Option<Inst>,
) -> Result<Sp, TrapCode> {
let start = self.frames.replace(callee_ip, callee_instance)?;
self.values.replace(start, callee_size, callee_params)
}
}
#[derive(Debug)]
pub struct ValueStack {
cells: Vec<Cell>,
max_height: usize,
}
impl ValueStack {
fn new(min_height: usize, max_height: usize) -> Self {
debug_assert!(min_height <= max_height);
let sizeof_cell = mem::size_of::<Cell>();
let min_height = min_height / sizeof_cell;
let max_height = max_height / sizeof_cell;
let cells = Vec::with_capacity(min_height);
Self { cells, max_height }
}
fn empty() -> Self {
Self {
cells: Vec::new(),
max_height: 0,
}
}
fn reset(&mut self) {
self.cells.clear();
}
fn bytes_allocated(&self) -> usize {
let bytes_per_frame = mem::size_of::<Cell>();
self.cells.capacity() * bytes_per_frame
}
fn sp(&mut self, start: SpOffset) -> Sp {
let offset = start.into_inner();
debug_assert!(
offset <= self.cells.len(),
"start = {}, cells.len() = {}",
offset,
self.cells.len()
);
let value = unsafe { self.cells.as_mut_ptr().add(offset) };
Sp::new(value)
}
fn sp_or_dangling(&mut self, start: SpOffset) -> Sp {
match self.cells.is_empty() {
true => {
debug_assert_eq!(start.into_inner(), 0);
Sp::dangling()
}
false => self.sp(start),
}
}
fn grow_if_needed(&mut self, new_len: SpOffset) -> Result<(), TrapCode> {
let new_len = new_len.into_inner();
if new_len > self.max_height {
return Err(TrapCode::StackOverflow);
}
let capacity = self.cells.capacity();
let len = self.cells.len();
if new_len > capacity {
debug_assert!(
self.cells.len() <= self.cells.capacity(),
"capacity must always be larger or equal to the actual number of the cells"
);
let additional = new_len - len;
self.cells
.try_reserve(additional)
.map_err(|_| TrapCode::OutOfSystemMemory)?;
debug_assert!(
self.cells.capacity() >= new_len,
"capacity must now be at least as large as `new_len` ({new_len}) but found {}",
self.cells.capacity()
);
}
let max_len = cmp::max(new_len, len);
unsafe { self.cells.set_len(max_len) };
Ok(())
}
fn return_prepare_host_frame<'a>(
&'a mut self,
caller: Option<(Ip, SpOffset, Inst)>,
callee_start: SpOffset,
callee_params: BoundedSlotSpan,
results_len: u16,
) -> Result<(ReturnCallHost, InOutParams<'a>), TrapCode> {
let caller_start = caller.map(|(_, start, _)| start).unwrap_or_default();
let params_offset = usize::from(u16::from(callee_params.span().head()));
let params_len = usize::from(callee_params.len());
let results_len = usize::from(results_len);
let callee_size = params_len.max(results_len);
if callee_size == 0 {
let sp = match caller {
Some(_) if caller_start != callee_start => self.sp(caller_start),
_ => Sp::dangling(),
};
let inout = InOutParams::new(&mut [], 0, 0).unwrap();
let control = match caller {
Some((ip, _, instance)) => ReturnCallHost::Continue((ip, sp, instance)),
None => ReturnCallHost::Break(sp),
};
return Ok((control, inout));
}
let params_start = callee_start.add(params_offset)?;
let params_end = params_start.add(params_len)?;
self.cells_copy_within(params_start..params_end, callee_start);
let callee_end = callee_start.add(callee_size)?;
self.grow_if_needed(callee_end)?;
let caller_sp = self.sp(caller_start);
let Some(cells) = self.cells_from_to(callee_start, callee_end) else {
unsafe { unreachable_unchecked!("must fit slice after `grow_if_needed` operation") }
};
let Ok(inout) = InOutParams::new(cells, params_len, results_len) else {
panic!("todo")
};
let control = match caller {
Some((ip, _, instance)) => ReturnCallHost::Continue((ip, caller_sp, instance)),
None => ReturnCallHost::Break(caller_sp),
};
Ok((control, inout))
}
fn prepare_host_frame<'a>(
&'a mut self,
caller_start: SpOffset,
callee_params: BoundedSlotSpan,
results_len: u16,
) -> Result<(Sp, InOutParams<'a>), TrapCode> {
let params_offset = usize::from(u16::from(callee_params.span().head()));
let params_len = usize::from(callee_params.len());
let results_len = usize::from(results_len);
let callee_size = params_len.max(results_len);
let callee_start = caller_start.add(params_offset)?;
let callee_end = callee_start.add(callee_size)?;
self.grow_if_needed(callee_end)?;
let sp = self.sp(caller_start);
let Some(cells) = self.cells_from_to(callee_start, callee_end) else {
unsafe { unreachable_unchecked!("must fit slice after `grow_if_needed` operation") }
};
let Ok(inout) = InOutParams::new(cells, params_len, results_len) else {
panic!("todo")
};
Ok((sp, inout))
}
#[inline(always)]
fn push(&mut self, start: SpOffset, len_slots: usize, len_params: u16) -> Result<Sp, TrapCode> {
let len_params = usize::from(len_params);
debug_assert!(len_params <= len_slots);
if len_slots == 0 {
return Ok(Sp::dangling());
}
let end = start.add(len_slots)?;
self.grow_if_needed(end)?;
let start_locals = start.into_inner().wrapping_add(len_params);
self.cells[start_locals..end.into_inner()].fill_with(Cell::default);
let sp = self.sp(start);
Ok(sp)
}
#[inline(always)]
fn replace(
&mut self,
callee_start: SpOffset,
callee_size: usize,
callee_params: BoundedSlotSpan,
) -> Result<Sp, TrapCode> {
let params_len = usize::from(callee_params.len());
let params_start = usize::from(u16::from(callee_params.span().head()));
let params_end = params_start.wrapping_add(params_len);
if callee_size == 0 {
return Ok(Sp::dangling());
}
let callee_end = callee_start.add(callee_size)?;
self.grow_if_needed(callee_end)?;
let Some(callee_cells) = self.cells_from(callee_start) else {
unsafe { unreachable_unchecked!("ValueStack::replace: out of bounds callee cells") }
};
callee_cells.copy_within(params_start..params_end, 0);
callee_cells[params_len..callee_size].fill_with(Cell::default);
let sp = self.sp(callee_start);
Ok(sp)
}
fn cells_from(&mut self, start: SpOffset) -> Option<&mut [Cell]> {
let start = start.into_inner();
self.cells.get_mut(start..)
}
fn cells_from_to(&mut self, start: SpOffset, end: SpOffset) -> Option<&mut [Cell]> {
let start = start.into_inner();
let end = end.into_inner();
self.cells.get_mut(start..end)
}
fn cells_copy_within(&mut self, range: ops::Range<SpOffset>, dest: SpOffset) {
let start = range.start.into_inner();
let end = range.end.into_inner();
let dest = dest.into_inner();
self.cells.copy_within(start..end, dest);
}
}
#[derive(Debug)]
pub struct CallStack {
frames: Vec<Frame>,
instance: Option<Inst>,
max_height: usize,
}
impl CallStack {
fn new(max_height: usize) -> Self {
Self {
frames: Vec::new(),
instance: None,
max_height,
}
}
fn bytes_allocated(&self) -> usize {
let bytes_per_frame = mem::size_of::<Frame>();
self.frames.capacity() * bytes_per_frame
}
fn empty() -> Self {
Self::new(0)
}
fn reset(&mut self) {
self.frames.clear();
self.instance = None;
}
fn top_start(&self) -> SpOffset {
let Some(top) = self.top() else {
return SpOffset::default();
};
top.start
}
fn top(&self) -> Option<&Frame> {
self.frames.last()
}
fn sync_ip(&mut self, ip: Ip) {
let Some(top) = self.frames.last_mut() else {
panic!("must have top call frame")
};
top.ip = ip;
}
fn restore_frame(&self) -> Option<(Ip, SpOffset, Inst)> {
let instance = self.instance?;
let top = self.top()?;
Some((top.ip, top.start, instance))
}
fn prepare_host_frame(&mut self, caller_ip: Option<Ip>) -> SpOffset {
if let Some(caller_ip) = caller_ip {
self.sync_ip(caller_ip);
}
self.top_start()
}
pub fn return_prepare_host_frame(
&mut self,
callee_instance: Inst,
) -> (SpOffset, Option<(Ip, SpOffset, Inst)>) {
let callee_start = self.top_start();
let caller = match self.pop() {
Some((ip, start, instance)) => {
let instance = instance.unwrap_or(callee_instance);
Some((ip, start, instance))
}
None => None,
};
(callee_start, caller)
}
#[inline(always)]
fn push(
&mut self,
caller_ip: Option<Ip>,
callee_ip: Ip,
callee_params: BoundedSlotSpan,
instance: Option<Inst>,
) -> Result<SpOffset, TrapCode> {
if self.frames.len() == self.max_height {
return Err(TrapCode::StackOverflow);
}
match caller_ip {
Some(caller_ip) => self.sync_ip(caller_ip),
None => debug_assert!(self.frames.is_empty()),
}
let prev_instance = match instance {
Some(instance) => self.instance.replace(instance),
None => self.instance,
};
let params_offset = usize::from(u16::from(callee_params.span().head()));
let start = self.top_start().add(params_offset)?;
self.frames.push(Frame {
ip: callee_ip,
start,
instance: prev_instance,
});
Ok(start)
}
fn pop(&mut self) -> Option<(Ip, SpOffset, Option<Inst>)> {
let Some(popped) = self.frames.pop() else {
unsafe { unreachable_unchecked!("call stack must not be empty") }
};
let top = self.top()?;
let ip = top.ip;
let start = top.start;
if let Some(instance) = popped.instance {
self.instance = Some(instance);
}
Some((ip, start, popped.instance))
}
#[inline(always)]
fn replace(&mut self, callee_ip: Ip, instance: Option<Inst>) -> Result<SpOffset, TrapCode> {
let Some(caller_frame) = self.frames.last_mut() else {
unsafe { unreachable_unchecked!("missing caller frame on the call stack") }
};
let prev_instance = match instance {
Some(instance) => self.instance.replace(instance),
None => self.instance,
};
let start = caller_frame.start;
*caller_frame = Frame {
start,
ip: callee_ip,
instance: prev_instance,
};
Ok(start)
}
}
#[derive(Debug)]
pub struct Frame {
pub ip: Ip,
start: SpOffset,
instance: Option<Inst>,
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub struct SpOffset(usize);
impl From<usize> for SpOffset {
#[inline]
fn from(value: usize) -> Self {
Self(value)
}
}
impl SpOffset {
#[inline]
fn add(self, delta: usize) -> Result<Self, TrapCode> {
match self.0.checked_add(delta) {
Some(new_sp) => Ok(Self::from(new_sp)),
None => Err(TrapCode::StackOverflow),
}
}
#[inline]
fn into_inner(self) -> usize {
self.0
}
}