use super::{ImmediateOperand, LocalIdx, LocalOperand, LocalsHead, Operand, Reset, TempOperand};
use crate::{
Error,
ValType,
core::{RawVal, TypedRawVal},
engine::{TranslationError, translator::utils::required_cells_for_ty},
ir::{Slot, SlotSpan},
};
use alloc::vec::Vec;
use core::{num::NonZero, slice};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct StackPos(NonZero<usize>);
impl From<StackPos> for usize {
fn from(value: StackPos) -> Self {
value.0.get().wrapping_sub(1)
}
}
impl From<usize> for StackPos {
fn from(value: usize) -> Self {
let Some(operand_idx) = NonZero::new(value.wrapping_add(1)) else {
panic!("out of bounds `StackPos`: {value}")
};
Self(operand_idx)
}
}
#[derive(Debug, Copy, Clone)]
pub enum StackOperand {
Local {
temp_slots: SlotSpan,
ty: ValType,
local_index: LocalIdx,
prev_local: Option<StackPos>,
next_local: Option<StackPos>,
},
Temp {
temp_slots: SlotSpan,
ty: ValType,
in_reg: bool,
},
Immediate {
temp_slots: SlotSpan,
ty: ValType,
val: RawVal,
},
}
impl StackOperand {
pub fn ty(&self) -> ValType {
match self {
| Self::Temp { ty, .. } | Self::Immediate { ty, .. } | Self::Local { ty, .. } => *ty,
}
}
pub fn temp_slots(&self) -> SlotSpan {
match self {
| Self::Temp { temp_slots, .. }
| Self::Immediate { temp_slots, .. }
| Self::Local { temp_slots, .. } => *temp_slots,
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum Allocation {
None,
Reg,
}
impl Allocation {
pub fn is_reg(&self) -> bool {
matches!(self, Self::Reg)
}
}
#[derive(Debug, Default)]
pub struct OperandStack {
operands: Vec<StackOperand>,
local_heads: LocalsHead,
len_locals: usize,
local_slots: u16,
temp_offset: u16,
max_offset: u16,
regs: RegisterMap,
}
#[derive(Debug, Default, Copy, Clone)]
pub struct RegisterMap {
ireg: Option<RegisterLink>,
freg32: Option<RegisterLink>,
freg64: Option<RegisterLink>,
}
impl Reset for RegisterMap {
fn reset(&mut self) {
self.ireg = None;
self.freg32 = None;
self.freg64 = None;
}
}
impl RegisterMap {
fn get_mut(&mut self, ty: ValType) -> &mut Option<RegisterLink> {
match ty {
| ValType::I32 | ValType::FuncRef | ValType::ExternRef | ValType::I64 => &mut self.ireg,
| ValType::F32 => &mut self.freg32,
| ValType::F64 => &mut self.freg64,
| ValType::V128 => unreachable!(),
}
}
pub fn alloc(&mut self, ty: ValType, link: RegisterLink) -> Option<RegisterLink> {
self.get_mut(ty).replace(link)
}
pub fn dealloc(&mut self, ty: ValType) -> Option<RegisterLink> {
self.get_mut(ty).take()
}
pub fn dealloc_temp(&mut self, ty: ValType, skip_threshold: usize) -> Option<StackPos> {
let link = self.get_mut(ty);
let Some(RegisterLink::Temp(pos)) = link else {
return None;
};
if usize::from(*pos) >= skip_threshold {
return None;
}
let pos = *pos;
link.take();
Some(pos)
}
pub fn get(&self, ty: ValType) -> Option<RegisterLink> {
match ty {
| ValType::I32 | ValType::FuncRef | ValType::ExternRef | ValType::I64 => self.ireg,
| ValType::F32 => self.freg32,
| ValType::F64 => self.freg64,
| ValType::V128 => None,
}
}
pub fn is_local_in_reg(&self, ty: ValType, local_index: LocalIdx) -> bool {
let Some(RegisterLink::Local(index)) = self.get(ty) else {
return false;
};
index == local_index
}
pub fn is_local_in_any_reg(&self, local_index: LocalIdx) -> bool {
self.is_local_in_reg(ValType::I64, local_index)
|| self.is_local_in_reg(ValType::F32, local_index)
|| self.is_local_in_reg(ValType::F64, local_index)
}
pub fn wrap_operand(&self, pos: StackPos, operand: StackOperand) -> Operand {
let in_reg = match operand {
StackOperand::Local {
ty, local_index, ..
} => self.is_local_in_reg(ty, local_index),
_ => false,
};
Operand::new(pos, operand, in_reg)
}
pub fn discard_local_regs(&mut self) {
fn is_local(link: &mut RegisterLink) -> bool {
matches!(link, RegisterLink::Local(_))
}
self.ireg.take_if(is_local);
self.freg32.take_if(is_local);
self.freg64.take_if(is_local);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RegisterLink {
Temp(StackPos),
Local(LocalIdx),
}
impl Reset for OperandStack {
fn reset(&mut self) {
self.operands.clear();
self.local_heads.reset();
self.len_locals = 0;
self.local_slots = 0;
self.temp_offset = 0;
self.max_offset = 0;
self.regs.reset();
}
}
impl OperandStack {
pub fn register_locals(&mut self, amount: usize, ty: ValType) -> Result<(), Error> {
self.local_heads.register(amount)?;
let cells_per_item = required_cells_for_ty(ty);
let Ok(amount) = u16::try_from(amount) else {
return Err(Error::from(TranslationError::TooManyLocalVariables));
};
let required_cells = amount
.checked_mul(cells_per_item)
.ok_or_else(|| Error::from(TranslationError::AllocatedTooManySlots))?;
self.push_local_slots(required_cells)?;
Ok(())
}
pub fn register_local_for_reg(
&mut self,
ty: ValType,
local_index: LocalIdx,
) -> Result<(), Error> {
if matches!(ty, ValType::V128) {
return Ok(());
}
self.regs.alloc(ty, RegisterLink::Local(local_index));
Ok(())
}
pub fn dealloc_local_for_reg(
&mut self,
ty: ValType,
local_index: LocalIdx,
) -> Result<(), Error> {
if matches!(ty, ValType::V128) {
return Ok(());
}
if self.regs.is_local_in_any_reg(local_index) {
self.regs.dealloc(ty);
}
Ok(())
}
pub fn dealloc_reg(&mut self, ty: ValType) -> Option<TempOperand> {
let link = self.regs.dealloc(ty)?;
let RegisterLink::Temp(pos) = link else {
return None;
};
let operand = self.get_at_mut(pos);
let StackOperand::Temp {
temp_slots,
ty,
in_reg,
} = operand
else {
unreachable!()
};
let returned = TempOperand::new(*temp_slots, *ty, pos, *in_reg);
*in_reg = false;
Some(returned)
}
pub fn get_registers(&self) -> RegisterMap {
self.regs
}
pub fn set_registers(&mut self, registers: RegisterMap) {
self.regs = registers;
}
pub fn get_local_slots(&self) -> u16 {
self.local_slots
}
fn push_local_slots(&mut self, delta: u16) -> Result<(), Error> {
debug_assert_eq!(self.local_slots, self.max_offset);
let Some(new_value) = self.local_slots.checked_add(delta) else {
return Err(Error::from(TranslationError::AllocatedTooManySlots));
};
self.local_slots = new_value;
self.temp_offset = new_value;
self.max_offset = self.max_offset.max(self.temp_offset);
Ok(())
}
fn push_temp_offset(&mut self, delta: u16) -> Result<SlotSpan, Error> {
let old_offset = self.temp_offset;
let Some(new_value) = old_offset.checked_add(delta) else {
return Err(Error::from(TranslationError::AllocatedTooManySlots));
};
self.temp_offset = new_value;
self.max_offset = self.max_offset.max(self.temp_offset);
Ok(SlotSpan::new(Slot::from(old_offset)))
}
fn pop_temp_offset(&mut self, delta: usize) -> Result<(), Error> {
let Ok(delta) = u16::try_from(delta) else {
return Err(Error::from(TranslationError::AllocatedTooManySlots));
};
self.temp_offset = self.temp_offset.checked_sub(delta).unwrap_or_else(|| {
panic!(
"underflow in `pop_temp_offset`: temp_offset = {}, delta = {delta}",
self.temp_offset
)
});
Ok(())
}
pub fn height(&self) -> usize {
self.operands.len()
}
pub fn next_temp_slots(&self) -> SlotSpan {
SlotSpan::new(Slot::from(self.temp_offset))
}
pub fn max_stack_offset(&self) -> usize {
usize::from(self.max_offset)
}
fn next_stack_pos(&self) -> StackPos {
StackPos::from(self.operands.len())
}
fn depth_to_stack_pos(&self, depth: usize) -> StackPos {
StackPos::from(self.height() - depth - 1)
}
#[inline]
pub fn push_operand(&mut self, operand: Operand) -> Result<Operand, Error> {
match operand {
Operand::Local(op) => self
.push_local(op.local_index(), op.ty())
.map(Operand::from),
Operand::Temp(op) => self.push_temp(op.ty(), op.alloc()).map(Operand::from),
Operand::Immediate(op) => self.push_immediate(op.val()).map(Operand::from),
}
}
#[inline]
pub fn push_local(
&mut self,
local_index: LocalIdx,
ty: ValType,
) -> Result<LocalOperand, Error> {
let stack_pos = self.next_stack_pos();
let next_local = self.local_heads.replace_first(local_index, Some(stack_pos));
if let Some(next_local) = next_local {
self.update_prev_local(next_local, Some(stack_pos));
}
let temp_slots = self.push_temp_offset(required_cells_for_ty(ty))?;
self.operands.push(StackOperand::Local {
temp_slots,
ty,
local_index,
prev_local: None,
next_local,
});
self.len_locals += 1;
let in_reg = self.regs.is_local_in_reg(ty, local_index);
Ok(LocalOperand::new(temp_slots, ty, local_index, in_reg))
}
#[inline]
pub fn push_temp(&mut self, ty: ValType, alloc: Allocation) -> Result<TempOperand, Error> {
let stack_pos = self.next_stack_pos();
if alloc.is_reg() {
self.alloc_reg(RegisterLink::Temp(stack_pos), ty);
}
let temp_slots = self.push_temp_offset(required_cells_for_ty(ty))?;
let in_reg = alloc.is_reg();
self.operands.push(StackOperand::Temp {
temp_slots,
ty,
in_reg,
});
Ok(TempOperand::new(temp_slots, ty, stack_pos, in_reg))
}
#[inline]
pub fn push_immediate(
&mut self,
value: impl Into<TypedRawVal>,
) -> Result<ImmediateOperand, Error> {
let value = value.into();
let ty = value.ty();
let val = value.raw();
let temp_slots = self.push_temp_offset(required_cells_for_ty(ty))?;
self.operands.push(StackOperand::Immediate {
temp_slots,
ty,
val,
});
Ok(ImmediateOperand::new(temp_slots, ty, val))
}
pub fn peek(&self, n: usize) -> PeekedOperands<'_> {
let len_operands = self.operands.len();
let first_index = len_operands - n;
let Some(operands) = self.operands.get(first_index..) else {
return PeekedOperands::empty();
};
PeekedOperands {
stack_pos: first_index,
operands: operands.iter(),
regs: self.regs,
}
}
#[inline]
pub fn pop(&mut self) -> Operand {
let Some(operand) = self.operands.pop() else {
panic!("tried to pop operand from empty stack");
};
self.pop_temp_offset(usize::from(required_cells_for_ty(operand.ty())))
.unwrap_or_else(|error| panic!("failed to pop temporary offset: {error}"));
let stack_pos = self.next_stack_pos();
self.try_unlink_local(operand);
if let Some(reg_pos) = self.try_unlink_reg(operand) {
debug_assert_eq!(stack_pos, reg_pos);
}
self.regs.wrap_operand(stack_pos, operand)
}
#[inline]
pub fn get(&self, depth: usize) -> Operand {
let stack_pos = self.depth_to_stack_pos(depth);
let operand = self.get_at(stack_pos);
self.regs.wrap_operand(stack_pos, operand)
}
#[inline]
fn get_at(&self, pos: StackPos) -> StackOperand {
self.operands[usize::from(pos)]
}
#[inline]
fn get_at_mut(&mut self, pos: StackPos) -> &mut StackOperand {
&mut self.operands[usize::from(pos)]
}
#[inline]
pub fn operand_to_temp(&mut self, depth: usize) -> Operand {
let pos = self.depth_to_stack_pos(depth);
let opd = self.operand_to_temp_at(pos);
Operand::new(pos, opd, false)
}
#[must_use]
fn operand_to_temp_at(&mut self, index: StackPos) -> StackOperand {
let operand = self.get_at(index);
let temp_slots = operand.temp_slots();
let ty = operand.ty();
self.try_unlink_local(operand);
self.try_unlink_reg(operand);
self.operands[usize::from(index)] = StackOperand::Temp {
temp_slots,
ty,
in_reg: false,
};
operand
}
fn preserve_local_at(&mut self, pos: StackPos) -> StackOperand {
let operand = self.get_at(pos);
let StackOperand::Local {
temp_slots,
ty,
local_index,
prev_local,
next_local,
} = operand
else {
unreachable!()
};
self.unlink_local(local_index, prev_local, next_local);
self.operands[usize::from(pos)] = StackOperand::Temp {
in_reg: false,
temp_slots,
ty,
};
operand
}
#[must_use]
pub fn preserve_locals(&mut self, local_index: LocalIdx) -> PreservedLocalsIter<'_> {
let stack_pos = self.local_heads.replace_first(local_index, None);
let in_reg = self.regs.is_local_in_any_reg(local_index);
PreservedLocalsIter {
stack: self,
stack_pos,
in_reg,
}
}
#[must_use]
pub fn preserve_all_locals(&mut self, skip: usize) -> PreservedAllLocalsIter<'_> {
PreservedAllLocalsIter::new(self, skip)
}
}
#[derive(Copy, Clone)]
pub struct PreservedRegs {
pub ireg: Option<Slot>,
pub freg32: Option<Slot>,
pub freg64: Option<Slot>,
}
impl OperandStack {
pub fn discard_local_regs(&mut self) {
self.regs.discard_local_regs()
}
#[must_use]
pub fn preserve_all_regs(&mut self) -> PreservedRegs {
fn preserve_reg(this: &mut OperandStack, link: RegisterLink) -> Option<Slot> {
let RegisterLink::Temp(stack_pos) = link else {
return None;
};
Some(this.operand_to_temp_at(stack_pos).temp_slots().head())
}
let reg_tys = [ValType::I64, ValType::F32, ValType::F64];
let [ireg, freg32, freg64] = reg_tys.map(|ty| {
self.regs
.dealloc(ty)
.and_then(|reg| preserve_reg(self, reg))
});
PreservedRegs {
ireg,
freg32,
freg64,
}
}
#[must_use]
pub fn preserve_all_temp_regs(&mut self, skip: usize) -> PreservedRegs {
let skip_threshold = self.operands.len() - skip;
let reg_tys = [ValType::I64, ValType::F32, ValType::F64];
let [ireg, freg32, freg64] = reg_tys.map(|ty| {
self.regs
.dealloc_temp(ty, skip_threshold)
.map(|pos| self.operand_to_temp_at(pos).temp_slots().head())
});
PreservedRegs {
ireg,
freg32,
freg64,
}
}
#[inline]
fn try_unlink_local(&mut self, operand: StackOperand) {
let StackOperand::Local {
local_index,
prev_local,
next_local,
..
} = operand
else {
return;
};
self.unlink_local(local_index, prev_local, next_local);
}
fn alloc_reg(&mut self, link: RegisterLink, ty: ValType) {
let prev_link = self.regs.alloc(ty, link);
debug_assert!(
prev_link.is_none(),
"a register operand already exists on the stack",
);
}
#[inline]
fn try_unlink_reg(&mut self, operand: StackOperand) -> Option<StackPos> {
let ty = match operand {
StackOperand::Temp {
in_reg: true, ty, ..
} => ty,
_ => return None,
};
match self.regs.dealloc(ty)? {
RegisterLink::Temp(stack_pos) => Some(stack_pos),
RegisterLink::Local(_) => None,
}
}
fn unlink_local(
&mut self,
local_index: LocalIdx,
prev_local: Option<StackPos>,
next_local: Option<StackPos>,
) {
if let Some(prev_local) = prev_local {
self.update_next_local(prev_local, next_local);
} else {
self.local_heads.replace_first(local_index, next_local);
}
if let Some(next_local) = next_local {
self.update_prev_local(next_local, prev_local);
}
self.len_locals -= 1;
}
fn update_prev_local(&mut self, local_pos: StackPos, prev_pos: Option<StackPos>) {
match self.operands.get_mut(usize::from(local_pos)) {
Some(StackOperand::Local { prev_local, .. }) => {
*prev_local = prev_pos;
}
entry => panic!("expected `StackOperand::Local` but found: {entry:?}"),
}
}
fn update_next_local(&mut self, local_pos: StackPos, next_pos: Option<StackPos>) {
match self.operands.get_mut(usize::from(local_pos)) {
Some(StackOperand::Local { next_local, .. }) => {
*next_local = next_pos;
}
entry => panic!("expected `StackOperand::Local` but found: {entry:?}"),
}
}
}
#[derive(Debug)]
pub struct PreservedAllLocalsIter<'stack> {
stack: &'stack mut OperandStack,
stack_pos: usize,
skip: usize,
remaining_locals: usize,
}
impl<'stack> PreservedAllLocalsIter<'stack> {
pub fn new(stack: &'stack mut OperandStack, skip: usize) -> Self {
let stack_pos = stack.operands.len();
let remaining_locals = stack.len_locals;
Self {
stack,
stack_pos,
skip,
remaining_locals,
}
}
fn has_remaining_locals(&self) -> bool {
self.remaining_locals != 0
}
fn find_next_local(&mut self) -> Option<usize> {
let mut stack_pos = self.stack_pos;
loop {
if !self.has_remaining_locals() {
return None;
}
stack_pos -= 1;
let opd = self.stack.operands.get(stack_pos)?;
let StackOperand::Local { .. } = opd else {
continue;
};
self.remaining_locals -= 1;
if self.skip > 0 {
self.skip -= 1;
continue;
}
return Some(stack_pos);
}
}
}
impl Iterator for PreservedAllLocalsIter<'_> {
type Item = LocalOperand;
fn next(&mut self) -> Option<Self::Item> {
self.stack_pos = self.find_next_local()?;
let stack_pos = StackPos::from(self.stack_pos);
let operand = self.stack.preserve_local_at(stack_pos);
let StackOperand::Local {
temp_slots,
ty,
local_index,
..
} = operand
else {
unreachable!("expected `StackOperand::Local` but found: {operand:?}")
};
let in_reg = self.stack.regs.is_local_in_reg(ty, local_index);
Some(LocalOperand::new(temp_slots, ty, local_index, in_reg))
}
}
#[derive(Debug)]
pub struct PreservedLocalsIter<'stack> {
stack: &'stack mut OperandStack,
stack_pos: Option<StackPos>,
in_reg: bool,
}
impl Iterator for PreservedLocalsIter<'_> {
type Item = LocalOperand;
fn next(&mut self) -> Option<Self::Item> {
let stack_pos = self.stack_pos?;
let operand = self.stack.preserve_local_at(stack_pos);
let StackOperand::Local {
temp_slots,
ty,
local_index,
next_local,
..
} = operand
else {
unreachable!("expected `StackOperand::Local` but found: {operand:?}")
};
self.stack_pos = next_local;
Some(LocalOperand::new(temp_slots, ty, local_index, self.in_reg))
}
}
#[derive(Debug)]
pub struct PeekedOperands<'stack> {
stack_pos: usize,
operands: slice::Iter<'stack, StackOperand>,
regs: RegisterMap,
}
impl<'stack> PeekedOperands<'stack> {
pub fn empty() -> Self {
Self {
stack_pos: 0,
operands: [].iter(),
regs: RegisterMap::default(),
}
}
}
impl Iterator for PeekedOperands<'_> {
type Item = Operand;
fn next(&mut self) -> Option<Self::Item> {
let operand = self.operands.next().copied()?;
let stack_pos = StackPos::from(self.stack_pos);
self.stack_pos += 1;
let wrapped = self.regs.wrap_operand(stack_pos, operand);
Some(wrapped)
}
}
impl ExactSizeIterator for PeekedOperands<'_> {
fn len(&self) -> usize {
self.operands.len()
}
}