#![allow(clippy::indexing_slicing)]
use super::call::{self, CallKind};
use super::{cell, Execution, StepOutcome};
use crate::instance::Instance;
use crate::module::code::Code;
use crate::module::op::{CmpKind, Op};
use crate::store::StoreInner;
use crate::trap::Trap;
use crate::Result;
impl Execution {
#[allow(
clippy::too_many_lines,
clippy::inline_always,
clippy::too_many_arguments
)]
#[allow(clippy::cognitive_complexity)] #[inline(always)]
pub(super) fn step(
&mut self,
inner: &mut StoreInner,
code: &Code,
op: &Op,
next: u32,
base: u32,
instance: Instance,
) -> Result<StepOutcome> {
match op {
Op::Nop => {}
Op::Unreachable => return Err(Trap::UnreachableCodeReached.into()),
Op::I32Const(v) => self.push_i32(*v),
Op::I64Const(v) => self.push_i64(*v),
Op::F32Const(v) => self.push_f32_bits(*v),
Op::F64Const(v) => self.push_f64_bits(*v),
Op::Drop => {
self.pop()?;
}
Op::Select => {
let (cell, tag) = self.pop_select()?;
self.push_cell(cell, tag);
}
Op::LocalGet(i) => {
let local = base
.checked_add(*i)
.ok_or(crate::error::InternalError::FrameStack(
"local index overflow",
))?;
let (cell, tag) = self.cell_at(local as usize)?;
self.push_cell(cell, tag);
}
Op::LocalSet(i) => {
let (cell, tag) = self.pop_tagged()?;
let local = base
.checked_add(*i)
.ok_or(crate::error::InternalError::FrameStack(
"local index overflow",
))?;
self.set_cell(local as usize, cell, tag)?;
}
Op::LocalTee(i) => {
let (cell, tag) = self.top_cell()?;
let local = base
.checked_add(*i)
.ok_or(crate::error::InternalError::FrameStack(
"local index overflow",
))?;
self.set_cell(local as usize, cell, tag)?;
}
Op::GlobalGet(g) => {
let handle = inner.instance(instance).globals[*g as usize];
let v = inner.global(handle).value;
self.push(v);
}
Op::GlobalSet(g) => {
let handle = inner.instance(instance).globals[*g as usize];
let v = self.pop_typed(inner.global(handle).ty.content())?;
inner.global_mut(handle).value = v;
}
Op::Br(t) => {
self.take_branch(*t)?;
return Ok(StepOutcome::Advance(t.ip));
}
Op::BrIf(t) => {
if self.pop_i32()? != 0 {
self.take_branch(*t)?;
return Ok(StepOutcome::Advance(t.ip));
}
}
Op::BrIfNot(t) => {
if self.pop_i32()? == 0 {
self.take_branch(*t)?;
return Ok(StepOutcome::Advance(t.ip));
}
}
Op::BrIfCmp {
kind,
negate,
target,
} => {
let (a, b) = self.pop_i32_pair()?;
if cmp_i32(*kind, a, b) != *negate {
self.take_branch(*target)?;
return Ok(StepOutcome::Advance(target.ip));
}
}
Op::BrTable(range) => {
let pool = code.br_tables();
let end = range.base.checked_add(range.len).ok_or(
crate::error::InternalError::Compiler("branch table range overflow"),
)? as usize;
let cases = pool.get(range.base as usize..end).ok_or(
crate::error::InternalError::Compiler("branch table range is invalid"),
)?;
let default = pool.get(end).ok_or(crate::error::InternalError::Compiler(
"branch table default is missing",
))?;
let i = self.pop_i32()? as u32 as usize;
let t = cases.get(i).unwrap_or(default);
self.take_branch(*t)?;
return Ok(StepOutcome::Advance(t.ip));
}
call_op @ (Op::Call(f) | Op::ReturnCall(f)) => {
let callee = inner.instance(instance).funcs[*f as usize];
let kind = call_kind(matches!(call_op, Op::ReturnCall(_)), next);
return Ok(call::call_outcome(
inner,
call::resolve(inner, callee),
instance,
kind,
));
}
Op::CallIndirect { type_idx, table } => {
return self.do_call_indirect(
inner,
instance,
*type_idx,
*table,
CallKind::Nested(next),
)
}
Op::ReturnCallIndirect { type_idx, table } => {
return self.do_call_indirect(inner, instance, *type_idx, *table, CallKind::Tail)
}
Op::CallRef(_) => return self.do_call_ref(inner, instance, CallKind::Nested(next)),
Op::ReturnCallRef(_) => return self.do_call_ref(inner, instance, CallKind::Tail),
Op::BrOnNull(t) => {
let (r, tag) = self.pop_tagged_ref()?;
if r.is_null() {
self.take_branch(*t)?;
return Ok(StepOutcome::Advance(t.ip));
}
self.push_cell(r, tag); }
Op::BrOnNonNull(t) => {
let (r, tag) = self.pop_tagged_ref()?;
if !r.is_null() {
self.push_cell(r, tag); self.take_branch(*t)?;
return Ok(StepOutcome::Advance(t.ip));
}
}
Op::MemoryGrow(i) => {
let memory = inner.instance(instance).memories[*i as usize];
let delta = self.pop_index(inner.memory(memory).ty.is_64())?;
return Ok(StepOutcome::DoGrow {
memory,
delta,
return_ip: next,
});
}
Op::TableGrow(t) => {
let table = inner.instance(instance).tables[*t as usize];
let tt = &inner.table(table).ty;
let (is_64, kind) = (tt.is_64(), cell::refkind_of_heap(tt.element().heap_type()));
let delta = self.pop_index(is_64)?;
let init = self.pop_ref(kind)?.to_ref();
return Ok(StepOutcome::DoTableGrow {
table,
delta,
init,
return_ip: next,
});
}
op @ (Op::I32Load(_)
| Op::I64Load(_)
| Op::F32Load(_)
| Op::F64Load(_)
| Op::I32Load8S(_)
| Op::I32Load8U(_)
| Op::I32Load16S(_)
| Op::I32Load16U(_)
| Op::I64Load8S(_)
| Op::I64Load8U(_)
| Op::I64Load16S(_)
| Op::I64Load16U(_)
| Op::I64Load32S(_)
| Op::I64Load32U(_)
| Op::I32Store(_)
| Op::I64Store(_)
| Op::F32Store(_)
| Op::F64Store(_)
| Op::I32Store8(_)
| Op::I32Store16(_)
| Op::I64Store8(_)
| Op::I64Store16(_)
| Op::I64Store32(_)
| Op::MemorySize(_)
| Op::MemoryCopy(..)
| Op::MemoryFill(_)
| Op::MemoryInit(..)
| Op::DataDrop(_)) => {
self.exec_memory(inner, code, op, instance)?;
}
op @ (Op::RefNull(_) | Op::RefFunc(_) | Op::RefIsNull | Op::RefAsNonNull) => {
self.exec_ref(inner, op, instance)?;
}
op @ (Op::TableInit { .. }
| Op::TableCopy { .. }
| Op::ElemDrop(_)
| Op::TableGet(_)
| Op::TableSet(_)
| Op::TableSize(_)
| Op::TableFill(_)) => self.exec_table(inner, op, instance)?,
Op::Throw(tag) => return self.throw(inner, instance, *tag, next - 1),
Op::ThrowRef => return self.throw_ref(),
op @ Op::BrOnCast { .. } => {
if let Some(ip) = self.br_on_cast(inner, code, instance, op, false)? {
return Ok(StepOutcome::Advance(ip));
}
}
op @ Op::BrOnCastFail { .. } => {
if let Some(ip) = self.br_on_cast(inner, code, instance, op, true)? {
return Ok(StepOutcome::Advance(ip));
}
}
#[cfg(feature = "simd")]
Op::Simd(s) => self.exec_simd(inner, code, s, instance)?,
alloc @ (Op::StructNew(_)
| Op::StructNewDefault(_)
| Op::ArrayNew(_)
| Op::ArrayNewDefault(_)
| Op::ArrayNewFixed { .. }
| Op::ArrayNewData { .. }
| Op::ArrayNewElem { .. }) => {
if let Some(charge) = self.gc_alloc_charge(inner, instance, alloc)? {
if let Some(out) = self.gc_reserve(inner, charge, next - 1)? {
return Ok(out);
}
}
self.exec_gc(inner, alloc, instance)?;
}
other => self.exec_numeric(inner, other, instance)?,
}
Ok(StepOutcome::Advance(next))
}
}
#[allow(clippy::inline_always)] #[inline(always)]
#[allow(clippy::cast_sign_loss)] fn cmp_i32(kind: CmpKind, a: i32, b: i32) -> bool {
match kind {
CmpKind::Eq => a == b,
CmpKind::Ne => a != b,
CmpKind::LtS => a < b,
CmpKind::LtU => (a as u32) < (b as u32),
CmpKind::GtS => a > b,
CmpKind::GtU => (a as u32) > (b as u32),
CmpKind::LeS => a <= b,
CmpKind::LeU => (a as u32) <= (b as u32),
CmpKind::GeS => a >= b,
CmpKind::GeU => (a as u32) >= (b as u32),
}
}
#[inline]
fn call_kind(tail: bool, next: u32) -> CallKind {
if tail {
CallKind::Tail
} else {
CallKind::Nested(next)
}
}