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,
},
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, Default)]
pub struct OperandStack {
operands: Vec<StackOperand>,
local_heads: LocalsHead,
len_locals: usize,
temp_offset: u16,
max_offset: u16,
}
impl Reset for OperandStack {
fn reset(&mut self) {
self.operands.clear();
self.local_heads.reset();
self.len_locals = 0;
self.temp_offset = 0;
self.max_offset = 0;
}
}
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_temp_offset(required_cells)?;
Ok(())
}
fn push_temp_offset(&mut self, delta: u16) -> Result<SlotSpan, Error> {
let old_offset = self.temp_offset;
self.temp_offset = old_offset
.checked_add(delta)
.ok_or_else(|| Error::from(TranslationError::AllocatedTooManySlots))?;
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()).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;
Ok(LocalOperand::new(temp_slots, ty, local_index))
}
#[inline]
pub fn push_temp(&mut self, ty: ValType) -> Result<TempOperand, Error> {
let stack_pos = self.next_stack_pos();
let temp_slots = self.push_temp_offset(required_cells_for_ty(ty))?;
self.operands.push(StackOperand::Temp { temp_slots, ty });
Ok(TempOperand::new(temp_slots, ty, stack_pos))
}
#[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(),
}
}
#[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);
Operand::new(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);
Operand::new(stack_pos, operand)
}
#[inline]
fn get_at(&self, pos: StackPos) -> StackOperand {
self.operands[usize::from(pos)]
}
#[must_use]
pub fn operand_to_temp(&mut self, depth: usize) -> Operand {
let stack_pos = self.depth_to_stack_pos(depth);
let operand = self.operand_to_temp_at(stack_pos);
Operand::new(stack_pos, operand)
}
#[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.operands[usize::from(index)] = StackOperand::Temp { 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);
PreservedLocalsIter {
operands: self,
stack_pos,
}
}
#[must_use]
pub fn preserve_all_locals(&mut self) -> PreservedAllLocalsIter<'_> {
let index = self.operands.len();
PreservedAllLocalsIter {
operands: self,
stack_pos: index,
}
}
#[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 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> {
operands: &'stack mut OperandStack,
stack_pos: usize,
}
impl PreservedAllLocalsIter<'_> {
fn has_remaining_locals(&self) -> bool {
self.operands.len_locals != 0
}
fn find_next_local(&mut self) -> Option<usize> {
let mut stack_pos = self.stack_pos;
loop {
stack_pos -= 1;
let opd = self.operands.operands.get(stack_pos)?;
if let StackOperand::Local { .. } = opd {
return Some(stack_pos);
}
}
}
}
impl Iterator for PreservedAllLocalsIter<'_> {
type Item = LocalOperand;
fn next(&mut self) -> Option<Self::Item> {
if !self.has_remaining_locals() {
return None;
}
self.stack_pos = self.find_next_local()?;
let stack_pos = StackPos::from(self.stack_pos);
let operand = self.operands.operand_to_temp_at(stack_pos);
let StackOperand::Local {
temp_slots,
ty,
local_index,
..
} = operand
else {
panic!("expected `StackOperand::Local` but found: {operand:?}")
};
Some(LocalOperand::new(temp_slots, ty, local_index))
}
}
#[derive(Debug)]
pub struct PreservedLocalsIter<'stack> {
operands: &'stack mut OperandStack,
stack_pos: Option<StackPos>,
}
impl Iterator for PreservedLocalsIter<'_> {
type Item = LocalOperand;
fn next(&mut self) -> Option<Self::Item> {
let stack_pos = self.stack_pos?;
let operand = self.operands.operand_to_temp_at(stack_pos);
let StackOperand::Local {
temp_slots,
ty,
local_index,
next_local,
..
} = operand
else {
panic!("expected `StackOperand::Local` but found: {operand:?}")
};
self.stack_pos = next_local;
Some(LocalOperand::new(temp_slots, ty, local_index))
}
}
#[derive(Debug)]
pub struct PeekedOperands<'stack> {
stack_pos: usize,
operands: slice::Iter<'stack, StackOperand>,
}
impl<'stack> PeekedOperands<'stack> {
pub fn empty() -> Self {
Self {
stack_pos: 0,
operands: [].iter(),
}
}
}
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;
Some(Operand::new(stack_pos, operand))
}
}
impl ExactSizeIterator for PeekedOperands<'_> {
fn len(&self) -> usize {
self.operands.len()
}
}