#![deny(
clippy::panic,
clippy::todo,
clippy::unimplemented,
clippy::indexing_slicing
)]
mod arith;
mod call;
mod cast;
mod cell;
mod collect;
mod convert;
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;
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::instance::Instance;
use crate::module::code::Code;
use crate::store::Store;
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};
#[derive(Debug)]
pub(crate) struct Execution {
values: Vec<cell::Cell>,
shadow: Vec<cell::RefTag>,
frames: Vec<Frame>,
host_reentry_depth: usize,
}
const GC_CHECK_INTERVAL: u32 = 1024;
const HOST_REENTRY_RESERVE: usize = 4096;
const _: fn() = || {
fn assert_send<T: Send>() {}
assert_send::<Execution>();
};
impl Default for Execution {
fn default() -> Self {
Execution {
values: Vec::new(),
shadow: Vec::new(),
frames: Vec::new(),
host_reentry_depth: 0,
}
}
}
impl Execution {
pub(crate) fn is_empty(&self) -> bool {
self.frames.is_empty() && self.values.is_empty() && self.shadow.is_empty()
}
fn enter_call(
&mut self,
delim: Delimiter,
instance: Instance,
func_index: u32,
code: Code,
args: Vec<Val>,
) -> Result<()> {
let value_base = self.values.len();
let frame_base = self.frames.len();
let old_reentry_depth = self.host_reentry_depth;
if delim == Delimiter::HostReentry {
self.host_reentry_depth = self
.host_reentry_depth
.checked_add(1)
.ok_or(InternalError::FrameStack("host re-entry depth overflow"))?;
}
if let Err(error) = self.push_delimiter(delim, instance, code.clone()) {
self.host_reentry_depth = old_reentry_depth;
return Err(error);
}
self.shadow.extend(args.iter().map(cell::RefTag::of_val));
self.values.extend(args.into_iter().map(cell::encode));
if let Err(error) = self.push_call(instance, func_index, code) {
self.values.truncate(value_base);
self.shadow.truncate(value_base);
self.frames.truncate(frame_base);
self.host_reentry_depth = old_reentry_depth;
return Err(error);
}
Ok(())
}
fn release_reentry(&mut self, stop_depth: usize) -> Result<()> {
match self
.frames
.get(stop_depth)
.and_then(|frame| frame.delimiter)
{
Some(Delimiter::HostReentry) => {
self.host_reentry_depth = self
.host_reentry_depth
.checked_sub(1)
.ok_or(InternalError::FrameStack("host re-entry depth underflow"))?;
}
Some(Delimiter::TopLevel) => {}
None => return Err(InternalError::FrameStack("missing call boundary delimiter").into()),
}
Ok(())
}
fn take_results(
&mut self,
value_base: usize,
stop_depth: usize,
result_tys: &[ValType],
) -> Result<Vec<cell::Cell>> {
if self.values.len() != self.shadow.len()
|| value_base > self.values.len()
|| stop_depth > self.frames.len()
{
return Err(InternalError::FrameStack("invalid completed call boundary").into());
}
self.validate_result_tags(value_base, result_tys)?;
self.release_reentry(stop_depth)?;
let results = self.values.split_off(value_base);
self.shadow.truncate(value_base);
self.frames.truncate(stop_depth);
Ok(results)
}
pub(crate) fn discard_to(&mut self, value_base: usize, stop_depth: usize) -> Result<()> {
if self.values.len() != self.shadow.len()
|| value_base > self.values.len()
|| value_base > self.shadow.len()
|| stop_depth > self.frames.len()
{
return Err(InternalError::FrameStack("invalid failed call boundary").into());
}
self.release_reentry(stop_depth)?;
self.values.truncate(value_base);
self.shadow.truncate(value_base);
self.frames.truncate(stop_depth);
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))
}
fn stack_bytes(&self) -> Result<usize> {
let values = self
.values
.len()
.checked_mul(std::mem::size_of::<cell::Cell>())
.ok_or(InternalError::FrameStack(
"operand stack byte size overflow",
))?;
let frames = self
.frames
.len()
.checked_mul(std::mem::size_of::<Frame>())
.ok_or(InternalError::FrameStack("frame stack byte size overflow"))?;
let reentry = self
.host_reentry_depth
.checked_mul(HOST_REENTRY_RESERVE)
.ok_or(InternalError::FrameStack(
"host re-entry byte size overflow",
))?;
values
.checked_add(self.shadow.len())
.and_then(|n| n.checked_add(frames))
.and_then(|n| n.checked_add(reentry))
.ok_or_else(|| InternalError::FrameStack("execution stack byte size overflow").into())
}
fn run<T>(&mut self, store: &mut Store<T>, stop_depth: usize) -> Result<Outcome> {
if self.frames.len() == stop_depth {
return Ok(Outcome::Finished);
}
if self.stack_bytes()? >= store.inner.engine().max_wasm_stack() {
let ip = self.frames.last().map_or(0, |f| f.ip);
return Err(self.attach_trap_backtrace(&store.inner, Trap::StackOverflow.into(), ip));
}
let fuel_enabled = store.inner.engine().consume_fuel();
let epoch_enabled = store.inner.engine().epoch_interruption();
if fuel_enabled || epoch_enabled {
self.dispatch::<T, true>(store, stop_depth, (fuel_enabled, epoch_enabled))
} else {
self.dispatch::<T, false>(store, stop_depth, (false, false))
}
}
#[allow(clippy::too_many_lines)] fn dispatch<T, const GATED: bool>(
&mut self,
store: &mut Store<T>,
stop_depth: usize,
gates: (bool, bool),
) -> Result<Outcome> {
let stack_limit = store.inner.engine().max_wasm_stack();
let gc_watch =
store.inner.gc.is_collecting() && store.inner.engine().gc_memory_threshold().is_some();
let mut gc_countdown: u32 = GC_CHECK_INTERVAL;
let (mut code, mut func, mut ip, mut base, mut instance) = self.top()?;
'frame: loop {
let ops = code.ops_of(&func);
loop {
let Some(op) = ops.get(ip as usize) else {
if self.do_return(func.n_results, stop_depth, false)? {
return Ok(Outcome::Finished);
}
(code, func, ip, base, instance) = self.top()?;
continue 'frame;
};
if GATED {
if let Some(out) = self.service_gates(&mut store.inner, gates, ip)? {
return Ok(out);
}
}
match self.step(&mut store.inner, &code, op, ip + 1, base, instance) {
Err(e) => match self.unwind(&mut store.inner, e, ip, stop_depth) {
Ok(()) => {
(code, func, ip, base, instance) = self.top()?;
continue 'frame;
}
Err(e) => return Err(self.attach_trap_backtrace(&store.inner, e, ip)),
},
Ok(StepOutcome::Advance(next)) => ip = next,
Ok(StepOutcome::DoCall(req)) => {
if self.stack_bytes()? >= stack_limit {
return Err(self.attach_trap_backtrace(
&store.inner,
Trap::StackOverflow.into(),
ip,
));
}
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = req.return_ip;
self.push_call(req.instance, req.func_index, req.code.clone())?;
(code, func, ip, base, instance) = self.top()?;
if gc_watch {
gc_countdown -= 1;
if gc_countdown == 0 {
gc_countdown = GC_CHECK_INTERVAL;
self.service_gc_pressure(&mut store.inner)?;
}
}
continue 'frame;
}
Ok(StepOutcome::DoTailCall(req)) => {
self.do_return(req.code.n_params(), stop_depth, true)?;
self.push_call(req.instance, req.func_index, req.code.clone())?;
(code, func, ip, base, instance) = self.top()?;
if gc_watch {
gc_countdown -= 1;
if gc_countdown == 0 {
gc_countdown = GC_CHECK_INTERVAL;
self.service_gc_pressure(&mut store.inner)?;
}
}
continue 'frame;
}
Ok(StepOutcome::DoTailHostCall {
func: host_fn,
instance: host_inst,
n_params,
}) => {
self.do_return(n_params, stop_depth, true)?;
self.invoke_host(store, host_fn, host_inst, stop_depth)?;
if self.frames.len() == stop_depth {
return Ok(Outcome::Finished);
}
(code, func, ip, base, instance) = self.top()?;
continue 'frame;
}
#[cfg(feature = "async")]
Ok(StepOutcome::DoTailHostAsyncCall {
func,
instance,
n_params,
}) => {
self.do_return(n_params, stop_depth, true)?;
return Ok(Outcome::HostAsync { func, instance });
}
Ok(StepOutcome::DoHostCall {
func: host_fn,
instance: host_inst,
return_ip,
}) => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
self.invoke_host(store, host_fn, host_inst, stop_depth)?;
if self.frames.len() == stop_depth {
return Ok(Outcome::Finished);
}
(code, func, ip, base, instance) = self.top()?;
continue 'frame;
}
#[cfg(feature = "async")]
Ok(StepOutcome::DoHostAsyncCall {
func,
instance,
return_ip,
}) => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
return Ok(Outcome::HostAsync { func, instance });
}
Ok(StepOutcome::DoGrow {
memory,
delta,
return_ip,
}) => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
return Ok(Outcome::Grow { memory, delta });
}
Ok(StepOutcome::DoTableGrow {
table,
delta,
init,
return_ip,
}) => {
self.frames
.last_mut()
.ok_or(InternalError::FrameStack("missing caller frame"))?
.ip = return_ip;
return Ok(Outcome::TableGrow { table, delta, init });
}
Ok(StepOutcome::DoGcGrow {
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;