#![allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::indexing_slicing
)]
use super::cell::{
decode, encode, refkind_of_heap, refkind_of_irheap, stack_slot_for_field, Cell, RefTag,
TaggedCell, SLOT_BYTES,
};
#[inline]
pub(super) fn decode_val(c: Cell, t: &ValType) -> Val {
match t {
ValType::I32 => Val::I32(c.unwrap_i32()),
ValType::I64 => Val::I64(c.unwrap_i64()),
ValType::F32 => Val::F32(c.bits32()),
ValType::F64 => Val::F64(c.bits64()),
_ => decode(c, t),
}
}
#[inline]
pub(super) fn encode_val(v: Val) -> Cell {
match v {
Val::I32(x) => Cell::from_i32(x),
Val::I64(x) => Cell::from_i64(x),
Val::F32(bits) => Cell::of_bytes(bits.to_le_bytes()),
Val::F64(bits) => Cell::of_bytes(bits.to_le_bytes()),
_ => encode(v),
}
}
#[cold]
fn underflow_error() -> crate::Error {
InternalError::OperandStack("operand stack underflow").into()
}
#[cold]
fn slot_error() -> crate::Error {
InternalError::OperandStack("operand slot out of range").into()
}
#[cold]
fn scalar_tag_error() -> crate::Error {
InternalError::OperandStack("numeric operation found a reference tag").into()
}
fn clear_tags(slots: &mut [TaggedCell], range: std::ops::Range<usize>) {
if let Some(dead) = slots.get_mut(range) {
for slot in dead {
slot.tag = RefTag::NONE;
}
}
}
pub(super) type Bin = (usize, usize, usize);
pub(super) type Un = (usize, usize);
use super::Execution;
use crate::canon::{IrVal, RefKind, Slot};
use crate::error::InternalError;
use crate::store::{read_slot, NULL_REF};
use crate::value::{Val, ValType};
use crate::Result;
impl Execution {
fn expected_tag(ty: &ValType) -> RefTag {
match ty {
ValType::Ref(reference) => RefTag::of_refkind(refkind_of_heap(reference.heap_type())),
_ => RefTag::NONE,
}
}
#[allow(clippy::inline_always)] #[inline(always)]
pub(super) fn ensure_operands(&self, needed: usize) -> Result<usize> {
debug_assert_eq!(
self.floor,
self.frames
.last()
.map_or(usize::MAX, |frame| frame.operand_base as usize),
"stale cached frame floor (a frame push/pop skipped `sync_floor`)"
);
let len = self.slots.len();
match self.sp.checked_sub(needed) {
Some(base) if base >= self.floor && self.sp <= len => Ok(base),
_ => Err(underflow_error()),
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
fn ensure_scalar_operands(&self, needed: usize) -> Result<usize> {
let base = self.ensure_operands(needed)?;
for i in base..base + needed {
if self.tag_at(i)? != RefTag::NONE {
return Err(scalar_tag_error());
}
}
Ok(base)
}
#[allow(clippy::inline_always)]
#[inline(always)]
fn tag_at(&self, i: usize) -> Result<RefTag> {
self.slots
.get(i)
.map(|slot| slot.tag)
.ok_or_else(slot_error)
}
#[inline]
fn pop_scalar(&mut self) -> Result<Cell> {
let base = self.ensure_scalar_operands(1)?;
let cell = self.slots[base].cell;
self.sp = base;
Ok(cell)
}
#[inline]
fn push_slot(&mut self, cell: Cell, tag: RefTag) {
let sp = self.sp;
let entry = TaggedCell { cell, tag };
if let Some(slot) = self.slots.get_mut(sp) {
*slot = entry;
} else {
self.slots.push(entry);
}
self.sp = self.slots.len().min(sp.saturating_add(1));
}
#[inline]
pub(super) fn set_default(&mut self, at: usize, ty: &IrVal) -> Result<()> {
if let IrVal::Ref { heap, .. } = ty {
let tag = RefTag::of_refkind(refkind_of_irheap(heap));
self.set_cell(at, Cell::of_bytes(NULL_REF.to_le_bytes()), tag)
} else {
self.set_scalar(at, Cell::of_bytes([0u8; SLOT_BYTES]))
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
pub(super) fn scalar_at(&self, at: usize) -> Result<Cell> {
match self.slots.get(at) {
Some(&TaggedCell {
cell,
tag: RefTag::NONE,
}) => Ok(cell),
Some(_) => Err(scalar_tag_error()),
None => Err(slot_error()),
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
pub(super) fn set_scalar(&mut self, at: usize, cell: Cell) -> Result<()> {
let slot = self.slots.get_mut(at).ok_or_else(slot_error)?;
*slot = TaggedCell {
cell,
tag: RefTag::NONE,
};
Ok(())
}
#[inline]
pub(super) fn enter_window(&mut self, top: usize) -> Result<()> {
if top > self.slots.len() || top < self.floor {
return Err(slot_error());
}
self.sp = top;
self.set_live_end(top)
}
#[inline]
pub(super) fn set_live_end(&mut self, end: usize) -> Result<()> {
if let Some(frame) = self.frames.last_mut() {
frame.live_end = u32::try_from(end).map_err(|_| slot_error())?;
}
Ok(())
}
pub(super) fn widen_top_live_range(&mut self) -> Result<()> {
self.set_live_end(self.live_top())
}
#[inline]
pub(super) fn take_typed(&self, at: usize, ty: &ValType) -> Result<Val> {
let (cell, tag) = self.cell_at(at)?;
if tag != Self::expected_tag(ty) {
return Err(
InternalError::OperandStack("operand tag does not match value type").into(),
);
}
Ok(decode_val(cell, ty))
}
#[inline]
pub(super) fn select_at(&mut self, at: usize) -> Result<()> {
let (a, a_tag) = self.cell_at(at)?;
let (b, b_tag) = self.cell_at(at + 1)?;
let cond = self.scalar_at(at + 2)?;
if a_tag != b_tag {
return Err(InternalError::OperandStack("select operand tag mismatch").into());
}
let (cell, tag) = if cond.unwrap_i32() != 0 {
(a, a_tag)
} else {
(b, b_tag)
};
self.set_cell(at, cell, tag)
}
#[allow(clippy::inline_always)] #[cfg_attr(submilli_inline_hot, inline(always))]
#[cfg_attr(not(submilli_inline_hot), inline)]
pub(super) fn index_at(&self, at: usize, is_64: bool) -> Result<u64> {
let cell = self.scalar_at(at)?;
Ok(if is_64 {
cell.unwrap_i64() as u64
} else {
u64::from(cell.unwrap_i32() as u32)
})
}
#[inline]
pub(super) fn put_index(&mut self, at: usize, is_64: bool, v: u64) -> Result<()> {
let cell = if is_64 {
Cell::from_i64(v as i64)
} else {
Cell::from_i32(v as u32 as i32)
};
self.set_scalar(at, cell)
}
#[allow(clippy::inline_always)] #[cfg_attr(submilli_inline_hot, inline(always))]
#[cfg_attr(not(submilli_inline_hot), inline)]
pub(super) fn cond_at(&self, at: usize) -> Result<i32> {
let v = self.scalar_at(at)?.unwrap_i32();
Ok(v)
}
#[inline]
pub(super) fn ref_at(&self, at: usize) -> Result<(Cell, RefTag)> {
let (cell, tag) = self.cell_at(at)?;
if tag.refkind().is_none() {
return Err(
InternalError::OperandStack("reference operation found a scalar tag").into(),
);
}
Ok((cell, tag))
}
pub(super) fn take_params(&self, at: usize, tys: &[ValType]) -> Result<Vec<Val>> {
let end = at.checked_add(tys.len()).ok_or_else(slot_error)?;
let mut out = Vec::with_capacity(tys.len());
for (i, ty) in (at..end).zip(tys) {
let (cell, tag) = self.cell_at(i)?;
if tag != Self::expected_tag(ty) {
return Err(InternalError::OperandStack("parameter tag mismatch").into());
}
out.push(decode(cell, ty));
}
Ok(out)
}
pub(super) fn take_ref(&self, at: usize, kind: RefKind) -> Result<Val> {
let (cell, tag) = self.cell_at(at)?;
if tag != RefTag::of_refkind(kind) {
return Err(
InternalError::OperandStack("reference tag does not match operand type").into(),
);
}
Ok(read_slot(Slot::Ref { offset: 0, kind }, cell.bytes()))
}
#[inline]
pub(super) fn set_val(&mut self, at: usize, v: Val) -> Result<()> {
self.set_cell(at, encode(v), RefTag::of_val(&v))
}
#[allow(clippy::inline_always)] #[cfg_attr(submilli_inline_hot, inline(always))]
#[cfg_attr(not(submilli_inline_hot), inline)]
pub(super) fn binop_at(
&mut self,
(dst, lhs, rhs): Bin,
f: impl FnOnce(Cell, Cell) -> Cell,
) -> Result<()> {
let (a, b) = (self.scalar_at(lhs)?, self.scalar_at(rhs)?);
self.set_scalar(dst, f(a, b))
}
#[inline]
pub(super) fn binop_try_at(
&mut self,
(dst, lhs, rhs): Bin,
f: impl FnOnce(Cell, Cell) -> Result<Cell>,
) -> Result<()> {
let r = f(self.scalar_at(lhs)?, self.scalar_at(rhs)?)?;
self.set_scalar(dst, r)
}
#[allow(clippy::inline_always)] #[cfg_attr(submilli_inline_hot, inline(always))]
#[cfg_attr(not(submilli_inline_hot), inline)]
pub(super) fn unop_at(&mut self, (dst, src): Un, f: impl FnOnce(Cell) -> Cell) -> Result<()> {
let a = self.scalar_at(src)?;
self.set_scalar(dst, f(a))
}
#[inline]
pub(super) fn unop_try_at(
&mut self,
(dst, src): Un,
f: impl FnOnce(Cell) -> Result<Cell>,
) -> Result<()> {
let r = f(self.scalar_at(src)?)?;
self.set_scalar(dst, r)
}
#[inline]
pub(super) fn pop(&mut self) -> Result<Cell> {
let base = self.ensure_operands(1)?;
let cell = self.slots[base].cell;
self.sp = base;
Ok(cell)
}
#[inline]
pub(super) fn push(&mut self, v: Val) {
self.push_slot(encode(v), RefTag::of_val(&v));
}
#[inline]
pub(super) fn push_cell(&mut self, cell: Cell, tag: RefTag) {
self.push_slot(cell, tag);
}
#[inline]
pub(super) fn cell_at(&self, i: usize) -> Result<(Cell, RefTag)> {
let slot = self
.slots
.get(i)
.ok_or(InternalError::OperandStack("local index out of bounds"))?;
Ok((slot.cell, slot.tag))
}
#[allow(clippy::inline_always)]
#[inline(always)]
pub(super) fn copy_slot(&mut self, src: usize, dst: usize) -> Result<()> {
let entry = *self.slots.get(src).ok_or_else(slot_error)?;
*self.slots.get_mut(dst).ok_or_else(slot_error)? = entry;
Ok(())
}
#[inline]
pub(super) fn set_cell(&mut self, i: usize, cell: Cell, tag: RefTag) -> Result<()> {
let slot = self
.slots
.get_mut(i)
.ok_or(InternalError::OperandStack("local index out of bounds"))?;
*slot = TaggedCell { cell, tag };
Ok(())
}
#[inline]
pub(super) fn top_i32(&self) -> Result<i32> {
let base = self.ensure_scalar_operands(1)?;
Ok(self.slots[base].cell.unwrap_i32())
}
#[inline]
pub(super) fn pop_i32(&mut self) -> Result<i32> {
Ok(self.pop_scalar()?.unwrap_i32())
}
#[inline]
pub(super) fn pop_i64(&mut self) -> Result<i64> {
Ok(self.pop_scalar()?.unwrap_i64())
}
#[inline]
pub(super) fn pop_f32(&mut self) -> Result<f32> {
Ok(self.pop_scalar()?.unwrap_f32())
}
#[inline]
pub(super) fn pop_f64(&mut self) -> Result<f64> {
Ok(self.pop_scalar()?.unwrap_f64())
}
#[cfg(feature = "simd")]
#[inline]
pub(super) fn pop_v128_cell(&mut self) -> Result<Cell> {
self.pop_scalar()
}
#[inline]
pub(super) fn pop_ref(&mut self, kind: RefKind) -> Result<Val> {
let base = self.ensure_operands(1)?;
if self.tag_at(base)? != RefTag::of_refkind(kind) {
return Err(
InternalError::OperandStack("reference tag does not match operand type").into(),
);
}
let cell = self.pop()?;
Ok(read_slot(Slot::Ref { offset: 0, kind }, cell.bytes()))
}
#[inline]
pub(super) fn pop_anyref(&mut self) -> Result<Val> {
self.pop_ref(RefKind::Any)
}
#[inline]
pub(super) fn pop_val_for(&mut self, field: Slot) -> Result<Val> {
let stack_slot = stack_slot_for_field(field);
let base = self.ensure_operands(1)?;
let expected = match stack_slot {
Slot::Ref { kind, .. } => RefTag::of_refkind(kind),
Slot::Scalar { .. } => RefTag::NONE,
};
if self.tag_at(base)? != expected {
return Err(
InternalError::OperandStack("operand tag does not match field type").into(),
);
}
let cell = self.pop()?;
Ok(read_slot(stack_slot, cell.bytes()))
}
#[inline]
pub(super) fn pop_params_into(&mut self, tys: &[ValType], out: &mut Vec<Val>) -> Result<()> {
let base = self.ensure_operands(tys.len())?;
let args = self.slots.get(base..).ok_or_else(slot_error)?;
if args
.iter()
.zip(tys)
.any(|(slot, ty)| slot.tag != Self::expected_tag(ty))
{
return Err(InternalError::OperandStack("host parameter tag mismatch").into());
}
out.extend(args.iter().zip(tys).map(|(slot, t)| decode(slot.cell, t)));
self.sp = base;
Ok(())
}
pub(super) fn validate_result_tags(&self, base: usize, tys: &[ValType]) -> Result<()> {
let Some(results) = self.slots.get(base..) else {
return Err(InternalError::OperandStack("invalid result stack boundary").into());
};
if results.len() != tys.len() {
return Err(InternalError::ResultShape("wrong number of execution results").into());
}
if results
.iter()
.zip(tys)
.any(|(slot, ty)| slot.tag != Self::expected_tag(ty))
{
return Err(InternalError::ResultShape("execution result tag mismatch").into());
}
Ok(())
}
#[inline]
pub(super) fn push_results_slice(&mut self, results: &[Val]) {
for v in results {
self.push_slot(encode_val(*v), RefTag::of_val(v));
}
}
pub(crate) fn operand_roots(&mut self) -> Result<Vec<(u32, RefKind)>> {
let mut roots = Vec::new();
let (mut prev_base, mut dead_from) = (0, 0);
for frame in &self.frames {
let (base, end) = (frame.locals_base as usize, frame.live_end as usize);
if base < prev_base {
return Err(
InternalError::FrameStack("frames out of order during root scan").into(),
);
}
prev_base = base;
let Some(live) = self.slots.get(base..end) else {
return Err(InternalError::OperandStack("frame live range out of bounds").into());
};
roots.extend(live.iter().filter_map(|slot| {
let kind = slot.tag.refkind()?;
(!slot.cell.is_null()).then(|| (slot.cell.handle(), kind))
}));
clear_tags(&mut self.slots, dead_from..base);
dead_from = dead_from.max(end);
}
let len = self.slots.len();
clear_tags(&mut self.slots, dead_from..len);
Ok(roots)
}
}