#![deny(
clippy::panic,
clippy::todo,
clippy::unimplemented,
clippy::indexing_slicing
)]
mod arith;
mod call;
mod cast;
mod cell;
mod collect;
mod convert;
pub(crate) mod epoch;
mod exn;
mod frame;
mod gates;
mod gc;
mod gc_array;
pub(crate) mod guard;
pub(crate) mod host;
#[cfg(feature = "async")]
pub(crate) mod host_async;
mod memory;
pub(crate) mod native;
mod numeric;
mod outcome;
mod ref_;
#[cfg(feature = "simd")]
mod simd;
mod stack;
mod step;
mod table;
pub(crate) mod trace;
use crate::error::InternalError;
use crate::func::Func;
use crate::instance::Instance;
use crate::module::code::Code;
use crate::module::op::Op;
use crate::store::{Store, StoreInner};
use crate::trap::Trap;
use crate::value::{Val, ValType};
use crate::Result;
use self::frame::{Delimiter, Frame};
pub(crate) use outcome::Outcome;
use outcome::{CallReq, ResolvedCall, StepOutcome, CALL_INTERNAL, CONTROL};
#[derive(Debug)]
pub(crate) struct Execution {
slots: Vec<cell::TaggedCell>,
frames: Vec<Frame>,
native_charge: usize,
floor: usize,
control: Option<StepOutcome>,
sp: usize,
mem0: (u32, u32),
code: Option<Code>,
stack_limit: usize,
gates_passed: bool,
}
const GC_CHECK_INTERVAL: u32 = 1024;
trait HostStore {
fn inner(&mut self) -> &mut StoreInner;
fn call_host(
&mut self,
exec: &mut Execution,
func: Func,
instance: Instance,
stop_depth: usize,
) -> Result<()>;
}
impl<T> HostStore for Store<T> {
fn inner(&mut self) -> &mut StoreInner {
&mut self.inner
}
fn call_host(
&mut self,
exec: &mut Execution,
func: Func,
instance: Instance,
stop_depth: usize,
) -> Result<()> {
exec.invoke_host(self, func, instance, stop_depth)
}
}
const HOST_REENTRY_RESERVE: usize = 4 << 10;
const SLOT_CHARGE: usize = 8;
const NO_MEM0: (u32, u32) = (u32::MAX, u32::MAX);
const _: fn() = || {
fn assert_send<T: Send>() {}
assert_send::<Execution>();
};
impl Default for Execution {
fn default() -> Self {
Execution {
slots: Vec::new(),
frames: Vec::new(),
native_charge: 0,
floor: usize::MAX,
control: None,
sp: 0,
mem0: NO_MEM0,
code: None,
stack_limit: 0,
gates_passed: false,
}
}
}
impl Execution {
pub(crate) fn is_empty(&self) -> bool {
self.frames.is_empty() && self.slots.is_empty()
}
pub(crate) fn is_pristine(&self) -> bool {
self.is_empty() && self.native_charge == 0 && self.control.is_none() && self.code.is_none()
}
pub(crate) fn swap_parked(&mut self, other: &mut Execution) {
debug_assert!(self.control.is_none() && other.control.is_none());
let Execution {
slots,
frames,
native_charge,
floor,
control: _,
sp,
mem0,
code,
stack_limit,
gates_passed,
} = self;
std::mem::swap(slots, &mut other.slots);
std::mem::swap(frames, &mut other.frames);
std::mem::swap(native_charge, &mut other.native_charge);
std::mem::swap(floor, &mut other.floor);
std::mem::swap(sp, &mut other.sp);
std::mem::swap(mem0, &mut other.mem0);
std::mem::swap(code, &mut other.code);
std::mem::swap(stack_limit, &mut other.stack_limit);
std::mem::swap(gates_passed, &mut other.gates_passed);
}
pub(crate) fn retained_bytes(&self) -> usize {
let slots = self
.slots
.capacity()
.saturating_mul(std::mem::size_of::<cell::TaggedCell>());
let frames = self
.frames
.capacity()
.saturating_mul(std::mem::size_of::<Frame>());
slots.saturating_add(frames)
}
fn enter_call(
&mut self,
(delim, native): (Delimiter, usize),
instance: Instance,
func_index: u32,
code: Code,
args: &[Val],
) -> Result<()> {
let value_base = self.slots.len();
let frame_base = self.frames.len();
let saved_charge = self.native_charge;
self.native_charge = saved_charge.max(native);
if let Err(error) = self.push_delimiter(delim, instance, &code, saved_charge) {
self.native_charge = saved_charge;
return Err(error);
}
self.slots.extend(args.iter().map(|&v| cell::TaggedCell {
cell: stack::encode_val(v),
tag: cell::RefTag::of_val(&v),
}));
if let Err(error) = self.push_call(instance, func_index, &code, value_base) {
self.slots.truncate(value_base);
self.frames.truncate(frame_base);
self.sync_floor();
self.native_charge = saved_charge;
return Err(error);
}
self.code = Some(code);
Ok(())
}
fn release_reentry(&mut self, stop_depth: usize) -> Result<()> {
match self.frames.get(stop_depth) {
Some(frame) if frame.delimiter.is_some() => {
self.native_charge = frame.native_saved as usize;
Ok(())
}
_ => Err(InternalError::FrameStack("missing call boundary delimiter").into()),
}
}
fn take_results(
&mut self,
value_base: usize,
stop_depth: usize,
result_tys: &[ValType],
out: &mut [Val],
) -> Result<()> {
if out.len() != result_tys.len() {
return Err(
InternalError::ResultShape("results buffer does not match the call").into(),
);
}
if value_base > self.slots.len() || stop_depth > self.frames.len() {
return Err(InternalError::FrameStack("invalid completed call boundary").into());
}
self.validate_result_tags(value_base, result_tys)?;
self.code = None;
self.release_reentry(stop_depth)?;
let cells = self.slots.get(value_base..).unwrap_or_default();
for ((slot, ty), out) in cells.iter().zip(result_tys).zip(out) {
*out = stack::decode_val(slot.cell, ty);
}
self.slots.truncate(value_base);
self.frames.truncate(stop_depth);
self.sync_floor();
Ok(())
}
pub(crate) fn discard_to(&mut self, value_base: usize, stop_depth: usize) -> Result<()> {
if value_base > self.slots.len() || stop_depth > self.frames.len() {
return Err(InternalError::FrameStack("invalid failed call boundary").into());
}
self.release_reentry(stop_depth)?;
self.code = None;
self.slots.truncate(value_base);
self.frames.truncate(stop_depth);
self.sync_floor();
Ok(())
}
pub(crate) fn discard_current_call(&mut self) -> Result<(usize, usize)> {
let (stop_depth, value_base) = self
.frames
.iter()
.enumerate()
.rev()
.find_map(|(depth, frame)| {
frame
.delimiter
.is_some()
.then_some((depth, frame.locals_base as usize))
})
.ok_or(InternalError::FrameStack(
"missing call boundary during panic cleanup",
))?;
self.discard_to(value_base, stop_depth)?;
Ok((value_base, stop_depth))
}
#[inline]
fn stack_bytes(&self) -> usize {
self.stack_bytes_for(self.live_top(), self.frames.len())
}
#[inline]
fn stack_bytes_for(&self, slots: usize, frames: usize) -> usize {
Self::heap_bytes_for(slots, frames).saturating_add(self.native_charge)
}
#[inline]
fn heap_bytes_for(slots: usize, frames: usize) -> usize {
slots
.saturating_mul(SLOT_CHARGE)
.saturating_add(frames.saturating_mul(std::mem::size_of::<Frame>()))
}
pub(crate) fn heap_stack_bytes(&self) -> usize {
Self::heap_bytes_for(self.live_top(), self.frames.len())
}
fn run<T>(&mut self, store: &mut Store<T>, stop_depth: usize) -> Result<Outcome> {
self.run_erased(store, stop_depth)
}
fn run_erased(&mut self, store: &mut dyn HostStore, stop_depth: usize) -> Result<Outcome> {
if self.frames.len() == stop_depth {
return Ok(Outcome::Finished);
}
if self.stack_bytes() >= self.stack_limit {
let ip = self.frames.last().map_or(0, |f| f.ip);
return Err(self.attach_trap_backtrace(store.inner(), Trap::StackOverflow.into(), ip));
}
let engine = store.inner().engine();
let (fuel_enabled, epoch_enabled) = (engine.consume_fuel(), engine.epoch_interruption());
if fuel_enabled || epoch_enabled {
self.dispatch::<true>(store, stop_depth, (fuel_enabled, epoch_enabled))
} else {
self.dispatch::<false>(store, stop_depth, (false, false))
}
}
fn dispatch<const GATED: bool>(
&mut self,
store: &mut dyn HostStore,
stop_depth: usize,
gates: (bool, bool),
) -> Result<Outcome> {
let mut code = match self.code.take() {
Some(code) => code,
None => self.top_frame()?.code_in(store.inner())?,
};
let outcome = self.dispatch_loop::<GATED>(store, stop_depth, gates, &mut code);
self.code = Some(code);
outcome
}
#[allow(clippy::too_many_lines)] fn dispatch_loop<const GATED: bool>(
&mut self,
store: &mut dyn HostStore,
stop_depth: usize,
gates: (bool, bool),
code: &mut Code,
) -> Result<Outcome> {
let mut inner = store.inner();
let gc_watch = inner.gc.is_collecting() && inner.engine().gc_memory_threshold().is_some();
let mut gc_countdown: u32 = GC_CHECK_INTERVAL;
let (mut func, mut ip, mut base, mut instance) = self.top_into(inner, code)?;
'frame: loop {
let ops = code.ops_in(func.ops)?;
loop {
let Some(op) = ops.get(ip as usize) else {
if self.do_return(func.n_results, stop_depth)? {
return Ok(Outcome::Finished);
}
(func, ip, base, instance) = self.top_into(inner, code)?;
continue 'frame;
};
if GATED {
if let Some(out) = self.service_gates(inner, gates, ip)? {
return Ok(out);
}
}
let next = match self.step(inner, code, op, ip + 1, base, instance) {
Ok(next) => next,
Err(e) => match self.unwind(inner, e, ip, stop_depth) {
Ok(()) => {
(func, ip, base, instance) = self.top_into(inner, code)?;
continue 'frame;
}
Err(e) => return Err(self.attach_trap_backtrace(inner, e, ip)),
},
};
if next < CALL_INTERNAL {
ip = next;
continue;
}
if next == CALL_INTERNAL {
let Op::CallInternal(index, s) = *op else {
return Err(InternalError::FrameStack("call marker without a call").into());
};
match self.call_internal(code, instance, index, step::slot(base, s), ip + 1) {
Ok((f, b)) => (func, ip, base) = (f, 0, b),
Err(e) => return Err(self.attach_trap_backtrace(inner, e, ip)),
}
if gc_watch {
gc_countdown -= 1;
if gc_countdown == 0 {
gc_countdown = GC_CHECK_INTERVAL;
self.service_gc_pressure(inner)?;
}
}
continue 'frame;
}
let outcome = self.control.take().ok_or(InternalError::FrameStack(
"control marker without a pending outcome",
))?;
match outcome {
StepOutcome::Call(req) => {
let caller = self
.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?;
caller.ip = req.return_ip;
caller.live_end = u32::try_from(req.args_at)
.map_err(|_| InternalError::FrameStack("operand index exceeds u32"))?;
let callee = req.callee;
frame::switch_code(inner, code, &callee);
match self.push_call(callee.instance, callee.func_index, code, req.args_at)
{
Ok((f, b)) => (func, ip, base, instance) = (f, 0, b, callee.instance),
Err(e) => return Err(self.attach_trap_backtrace(inner, e, ip)),
}
if gc_watch {
gc_countdown -= 1;
if gc_countdown == 0 {
gc_countdown = GC_CHECK_INTERVAL;
self.service_gc_pressure(inner)?;
}
}
continue 'frame;
}
StepOutcome::TailCall(req) => {
let dst = self.frame_base()?;
let callee = req.callee;
self.return_from(req.args_at, callee.n_params as usize, stop_depth)?;
frame::switch_code(inner, code, &callee);
match self.push_call(callee.instance, callee.func_index, code, dst) {
Ok((f, b)) => (func, ip, base, instance) = (f, 0, b, callee.instance),
Err(e) => return Err(self.attach_suspension_backtrace(inner, e)),
}
if gc_watch {
gc_countdown -= 1;
if gc_countdown == 0 {
gc_countdown = GC_CHECK_INTERVAL;
self.service_gc_pressure(inner)?;
}
}
continue 'frame;
}
StepOutcome::TailHostCall {
func: host_fn,
instance: host_inst,
args_at,
n_params,
} => {
let dst = self.frame_base()?;
self.return_from(args_at, n_params, stop_depth)?;
self.enter_window(dst.saturating_add(n_params))?;
store.call_host(self, host_fn, host_inst, stop_depth)?;
inner = store.inner();
if self.frames.len() == stop_depth {
return Ok(Outcome::Finished);
}
(func, ip, base, instance) = self.top_into(inner, code)?;
continue 'frame;
}
#[cfg(feature = "async")]
StepOutcome::TailHostAsyncCall {
func,
instance,
args_at,
n_params,
} => {
let dst = self.frame_base()?;
self.return_from(args_at, n_params, stop_depth)?;
self.enter_window(dst.saturating_add(n_params))?;
return Ok(Outcome::HostAsync { func, instance });
}
StepOutcome::HostCall {
func: host_fn,
instance: host_inst,
top,
return_ip,
} => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
self.enter_window(top)?; store.call_host(self, host_fn, host_inst, stop_depth)?;
inner = store.inner();
if self.frames.len() == stop_depth {
return Ok(Outcome::Finished);
}
(func, ip, base, instance) = self.top_into(inner, code)?;
continue 'frame;
}
#[cfg(feature = "async")]
StepOutcome::HostAsyncCall {
func,
instance,
top,
return_ip,
} => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
self.enter_window(top)?; return Ok(Outcome::HostAsync { func, instance });
}
StepOutcome::Grow {
memory,
delta,
at,
return_ip,
} => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
return Ok(Outcome::Grow { memory, delta, at });
}
StepOutcome::TableGrow {
table,
delta,
init,
at,
return_ip,
} => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
return Ok(Outcome::TableGrow {
table,
delta,
init,
at,
});
}
StepOutcome::GcGrow {
reserved_target,
bytes_needed,
return_ip,
} => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
return Ok(Outcome::GcGrow {
reserved_target,
bytes_needed,
});
}
}
}
}
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;