use super::{Operand, Reset};
use crate::{
ValType,
engine::{
BlockType,
translator::func::{Pos, labels::LabelRef, stack::operands::RegisterMap},
},
ir,
ir::BoundedSlotSpan,
};
use alloc::vec::{Drain, Vec};
use core::slice;
#[cfg(doc)]
use crate::ir::Op;
#[derive(Debug, Copy, Clone)]
pub struct StackHeight(u16);
impl From<StackHeight> for usize {
fn from(height: StackHeight) -> Self {
usize::from(height.0)
}
}
impl From<usize> for StackHeight {
fn from(height: usize) -> Self {
let Ok(height) = u16::try_from(height) else {
panic!("out of bounds stack height: {height}")
};
Self(height)
}
}
#[derive(Debug, Copy, Clone)]
pub struct BranchParams {
temp_slots: BoundedSlotSpan,
temp_len: u16,
regs: Option<BranchParamRegs>,
}
impl BranchParams {
pub fn new(temp_slots: BoundedSlotSpan, temp_len: u16, regs: Option<BranchParamRegs>) -> Self {
Self {
temp_slots,
temp_len,
regs,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> u16 {
self.len_temps() + self.len_regs()
}
pub fn len_regs(&self) -> u16 {
self.regs.as_ref().map(BranchParamRegs::len).unwrap_or(0)
}
pub fn len_temps(&self) -> u16 {
self.temp_len
}
pub fn regs(&self) -> &[RegKind] {
self.regs
.as_ref()
.map(BranchParamRegs::as_slice)
.unwrap_or(&[])
}
pub fn temp_slots(&self) -> BoundedSlotSpan {
self.temp_slots
}
pub fn claims_reg(&self, kind: RegKind) -> bool {
self.regs().contains(&kind)
}
}
#[derive(Debug, Copy, Clone)]
pub enum BranchParamRegs {
One(RegKind),
Two([RegKind; 2]),
Three([RegKind; 3]),
}
impl BranchParamRegs {
pub fn new_one(kind: RegKind) -> Self {
Self::One(kind)
}
pub fn new_two(kinds: [RegKind; 2]) -> Self {
debug_assert_ne!(kinds[0], kinds[1]);
Self::Two(kinds)
}
pub fn new_three(kinds: [RegKind; 3]) -> Self {
debug_assert_ne!(kinds[0], kinds[1]);
debug_assert_ne!(kinds[0], kinds[2]);
debug_assert_ne!(kinds[1], kinds[2]);
Self::Three(kinds)
}
pub fn len(&self) -> u16 {
match self {
Self::One(_) => 1,
Self::Two(_) => 2,
Self::Three(_) => 3,
}
}
pub fn as_slice(&self) -> &[RegKind] {
match self {
Self::One(kind) => slice::from_ref(kind),
Self::Two(kinds) => {
debug_assert_ne!(kinds[0], kinds[1]);
&kinds[..]
}
Self::Three(kinds) => {
debug_assert_ne!(kinds[0], kinds[1]);
debug_assert_ne!(kinds[0], kinds[2]);
debug_assert_ne!(kinds[1], kinds[2]);
&kinds[..]
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum RegKind {
Ireg,
Freg32,
Freg64,
}
impl RegKind {
pub fn new(ty: ValType) -> Option<Self> {
let kind = match ty {
ValType::I32 | ValType::FuncRef | ValType::ExternRef | ValType::I64 => Self::Ireg,
ValType::F32 => Self::Freg32,
ValType::F64 => Self::Freg64,
ValType::V128 => return None,
};
Some(kind)
}
pub fn matches_ty(&self, ty: ValType) -> bool {
match self {
RegKind::Ireg => matches!(
ty,
ValType::I32 | ValType::FuncRef | ValType::ExternRef | ValType::I64
),
RegKind::Freg32 => matches!(ty, ValType::F32),
RegKind::Freg64 => matches!(ty, ValType::F64),
}
}
}
#[derive(Debug, Default)]
pub struct ControlStack {
frames: Vec<ControlFrame>,
fuel_pos: Option<Pos<ir::BlockFuel>>,
else_operands: ElseOperands,
orphaned_else_operands: bool,
}
#[derive(Debug, Default)]
pub struct ElseOperands {
ends: Vec<usize>,
operands: Vec<Operand>,
}
impl Reset for ElseOperands {
fn reset(&mut self) {
self.ends.clear();
self.operands.clear();
}
}
impl ElseOperands {
pub fn push(&mut self, operands: impl IntoIterator<Item = Operand>) {
self.operands.extend(operands);
let end = self.operands.len();
self.ends.push(end);
}
pub fn pop(&mut self) -> Option<Drain<'_, Operand>> {
let end = self.ends.pop()?;
let start = self.ends.last().copied().unwrap_or(0);
Some(self.operands.drain(start..end))
}
}
impl Reset for ControlStack {
fn reset(&mut self) {
self.frames.clear();
self.else_operands.reset();
self.orphaned_else_operands = false;
}
}
impl ControlStack {
#[inline]
pub fn fuel_pos(&self) -> Option<Pos<ir::BlockFuel>> {
debug_assert!(!self.is_empty());
self.fuel_pos
}
pub fn is_empty(&self) -> bool {
self.height() == 0
}
pub fn height(&self) -> usize {
self.frames.len()
}
pub fn push_unreachable(&mut self, kind: ControlFrameKind) {
debug_assert!(!self.orphaned_else_operands);
self.frames.push(ControlFrame::from(kind))
}
pub fn push_block(
&mut self,
ty: BlockType,
height: usize,
branch_params: BranchParams,
label: LabelRef,
fuel_pos: Option<Pos<ir::BlockFuel>>,
) {
debug_assert!(!self.orphaned_else_operands);
self.frames.push(ControlFrame::from(BlockControlFrame {
ty,
height: StackHeight::from(height),
branch_params,
is_branched_to: false,
fuel_pos,
label,
}));
self.fuel_pos = fuel_pos;
}
pub fn push_loop(
&mut self,
ty: BlockType,
height: usize,
branch_params: BranchParams,
label: LabelRef,
fuel_pos: Option<Pos<ir::BlockFuel>>,
) -> LoopControlFrame {
debug_assert!(!self.orphaned_else_operands);
let loop_frame = LoopControlFrame {
ty,
height: StackHeight::from(height),
branch_params,
is_branched_to: false,
fuel_pos,
label,
};
self.frames.push(ControlFrame::from(loop_frame.clone()));
self.fuel_pos = fuel_pos;
loop_frame
}
#[expect(clippy::too_many_arguments)]
pub fn push_if(
&mut self,
ty: BlockType,
height: usize,
branch_params: BranchParams,
label: LabelRef,
fuel_pos: Option<Pos<ir::BlockFuel>>,
reachability: IfReachability,
else_operands: impl IntoIterator<Item = Operand>,
registers: RegisterMap,
) {
debug_assert!(!self.orphaned_else_operands);
self.frames.push(ControlFrame::from(IfControlFrame {
ty,
height: StackHeight::from(height),
branch_params,
is_branched_to: false,
fuel_pos,
label,
reachability,
registers,
}));
if matches!(reachability, IfReachability::Both { .. }) {
self.else_operands.push(else_operands);
}
self.fuel_pos = fuel_pos;
}
pub fn push_else(
&mut self,
if_frame: IfControlFrame,
fuel_pos: Option<Pos<ir::BlockFuel>>,
is_end_of_then_reachable: bool,
) {
debug_assert!(!self.orphaned_else_operands);
let ty = if_frame.ty();
let height = if_frame.height();
let branch_params = if_frame.branch_params;
let label = if_frame.label();
let is_branched_to = if_frame.is_branched_to();
let reachability = match if_frame.reachability {
IfReachability::Both { .. } => ElseReachability::Both {
is_end_of_then_reachable,
},
IfReachability::OnlyThen => ElseReachability::OnlyThen {
is_end_of_then_reachable,
},
IfReachability::OnlyElse => ElseReachability::OnlyElse,
};
self.frames.push(ControlFrame::from(ElseControlFrame {
ty,
height: StackHeight::from(height),
branch_params,
is_branched_to,
fuel_pos,
label,
reachability,
}));
self.fuel_pos = fuel_pos;
}
pub fn pop(&mut self) -> Option<ControlFrame> {
debug_assert!(!self.orphaned_else_operands);
let frame = self.frames.pop()?;
if !matches!(frame, ControlFrame::Block(_) | ControlFrame::Unreachable(_)) {
self.fuel_pos = self.get(0).fuel_pos();
}
self.orphaned_else_operands = match &frame {
ControlFrame::If(frame) => {
matches!(frame.reachability, IfReachability::Both { .. })
}
_ => false,
};
Some(frame)
}
pub fn pop_else_operands(&mut self) -> Drain<'_, Operand> {
debug_assert!(self.orphaned_else_operands);
let Some(else_operands) = self.else_operands.pop() else {
panic!("missing `else` operands")
};
self.orphaned_else_operands = false;
else_operands
}
pub fn get(&self, depth: usize) -> &ControlFrame {
let height = self.height();
self.frames.iter().rev().nth(depth).unwrap_or_else(|| {
panic!(
"out of bounds control frame at depth (={depth}) for stack of height (={height})"
)
})
}
pub fn get_mut(&mut self, depth: usize) -> ControlFrameMut<'_> {
let height = self.height();
self.frames
.iter_mut()
.rev()
.nth(depth)
.map(ControlFrameMut)
.unwrap_or_else(|| {
panic!(
"out of bounds control frame at depth (={depth}) for stack of height (={height})"
)
})
}
}
#[derive(Debug)]
pub struct ControlFrameMut<'a>(&'a mut ControlFrame);
impl<'a> ControlFrameBase for ControlFrameMut<'a> {
fn kind(&self) -> ControlFrameKind {
self.0.kind()
}
fn ty(&self) -> BlockType {
self.0.ty()
}
fn height(&self) -> usize {
self.0.height()
}
fn branch_params(&self) -> BranchParams {
self.0.branch_params()
}
fn label(&self) -> LabelRef {
self.0.label()
}
fn is_branched_to(&self) -> bool {
self.0.is_branched_to()
}
fn branch_to(&mut self) {
self.0.branch_to()
}
fn fuel_pos(&self) -> Option<Pos<ir::BlockFuel>> {
self.0.fuel_pos()
}
}
#[derive(Debug)]
pub enum AcquiredTarget<'stack> {
Return(ControlFrameMut<'stack>),
Branch(ControlFrameMut<'stack>),
}
impl<'stack> AcquiredTarget<'stack> {
pub fn control_frame(self) -> ControlFrameMut<'stack> {
match self {
Self::Return(frame) => frame,
Self::Branch(frame) => frame,
}
}
}
impl ControlStack {
pub fn acquire_target(&mut self, depth: usize) -> AcquiredTarget<'_> {
let is_root = self.is_root(depth);
let frame = self.get_mut(depth);
if is_root {
AcquiredTarget::Return(frame)
} else {
AcquiredTarget::Branch(frame)
}
}
fn is_root(&self, depth: usize) -> bool {
if self.frames.is_empty() {
return false;
}
depth == self.height() - 1
}
}
#[derive(Debug)]
pub enum ControlFrame {
Block(BlockControlFrame),
Loop(LoopControlFrame),
If(IfControlFrame),
Else(ElseControlFrame),
Unreachable(ControlFrameKind),
}
impl From<BlockControlFrame> for ControlFrame {
fn from(frame: BlockControlFrame) -> Self {
Self::Block(frame)
}
}
impl From<LoopControlFrame> for ControlFrame {
fn from(frame: LoopControlFrame) -> Self {
Self::Loop(frame)
}
}
impl From<IfControlFrame> for ControlFrame {
fn from(frame: IfControlFrame) -> Self {
Self::If(frame)
}
}
impl From<ElseControlFrame> for ControlFrame {
fn from(frame: ElseControlFrame) -> Self {
Self::Else(frame)
}
}
impl From<ControlFrameKind> for ControlFrame {
fn from(frame: ControlFrameKind) -> Self {
Self::Unreachable(frame)
}
}
pub trait ControlFrameBase {
fn kind(&self) -> ControlFrameKind;
fn ty(&self) -> BlockType;
fn height(&self) -> usize;
fn branch_params(&self) -> BranchParams;
fn label(&self) -> LabelRef;
fn is_branched_to(&self) -> bool;
fn branch_to(&mut self);
fn fuel_pos(&self) -> Option<Pos<ir::BlockFuel>>;
}
impl ControlFrameBase for ControlFrame {
fn kind(&self) -> ControlFrameKind {
match self {
ControlFrame::Block(frame) => frame.kind(),
ControlFrame::Loop(frame) => frame.kind(),
ControlFrame::If(frame) => frame.kind(),
ControlFrame::Else(frame) => frame.kind(),
ControlFrame::Unreachable(_) => {
panic!("invalid query for unreachable control frame: `ControlFrameBase::kind`")
}
}
}
fn ty(&self) -> BlockType {
match self {
ControlFrame::Block(frame) => frame.ty(),
ControlFrame::Loop(frame) => frame.ty(),
ControlFrame::If(frame) => frame.ty(),
ControlFrame::Else(frame) => frame.ty(),
ControlFrame::Unreachable(_) => {
panic!("invalid query for unreachable control frame: `ControlFrameBase::ty`")
}
}
}
fn height(&self) -> usize {
match self {
ControlFrame::Block(frame) => frame.height(),
ControlFrame::Loop(frame) => frame.height(),
ControlFrame::If(frame) => frame.height(),
ControlFrame::Else(frame) => frame.height(),
ControlFrame::Unreachable(_) => {
panic!("invalid query for unreachable control frame: `ControlFrameBase::height`")
}
}
}
fn branch_params(&self) -> BranchParams {
match self {
ControlFrame::Block(frame) => frame.branch_params(),
ControlFrame::Loop(frame) => frame.branch_params(),
ControlFrame::If(frame) => frame.branch_params(),
ControlFrame::Else(frame) => frame.branch_params(),
ControlFrame::Unreachable(_) => {
panic!(
"invalid query for unreachable control frame: `ControlFrameBase::branch_params`"
)
}
}
}
fn label(&self) -> LabelRef {
match self {
ControlFrame::Block(frame) => frame.label(),
ControlFrame::Loop(frame) => frame.label(),
ControlFrame::If(frame) => frame.label(),
ControlFrame::Else(frame) => frame.label(),
ControlFrame::Unreachable(_) => {
panic!("invalid query for unreachable control frame: `ControlFrame::label`")
}
}
}
fn is_branched_to(&self) -> bool {
match self {
ControlFrame::Block(frame) => frame.is_branched_to(),
ControlFrame::Loop(frame) => frame.is_branched_to(),
ControlFrame::If(frame) => frame.is_branched_to(),
ControlFrame::Else(frame) => frame.is_branched_to(),
ControlFrame::Unreachable(_) => {
panic!(
"invalid query for unreachable control frame: `ControlFrame::is_branched_to`"
)
}
}
}
fn branch_to(&mut self) {
match self {
ControlFrame::Block(frame) => frame.branch_to(),
ControlFrame::Loop(frame) => frame.branch_to(),
ControlFrame::If(frame) => frame.branch_to(),
ControlFrame::Else(frame) => frame.branch_to(),
ControlFrame::Unreachable(_) => {
panic!("invalid query for unreachable control frame: `ControlFrame::branch_to`")
}
}
}
fn fuel_pos(&self) -> Option<Pos<ir::BlockFuel>> {
match self {
ControlFrame::Block(frame) => frame.fuel_pos(),
ControlFrame::Loop(frame) => frame.fuel_pos(),
ControlFrame::If(frame) => frame.fuel_pos(),
ControlFrame::Else(frame) => frame.fuel_pos(),
ControlFrame::Unreachable(_) => {
panic!("invalid query for unreachable control frame: `ControlFrame::fuel_pos`")
}
}
}
}
#[derive(Debug)]
pub struct BlockControlFrame {
ty: BlockType,
height: StackHeight,
branch_params: BranchParams,
is_branched_to: bool,
fuel_pos: Option<Pos<ir::BlockFuel>>,
label: LabelRef,
}
impl ControlFrameBase for BlockControlFrame {
fn kind(&self) -> ControlFrameKind {
ControlFrameKind::Block
}
fn ty(&self) -> BlockType {
self.ty
}
fn height(&self) -> usize {
self.height.into()
}
fn branch_params(&self) -> BranchParams {
self.branch_params
}
fn label(&self) -> LabelRef {
self.label
}
fn is_branched_to(&self) -> bool {
self.is_branched_to
}
fn branch_to(&mut self) {
self.is_branched_to = true;
}
fn fuel_pos(&self) -> Option<Pos<ir::BlockFuel>> {
self.fuel_pos
}
}
#[derive(Debug, Clone)]
pub struct LoopControlFrame {
ty: BlockType,
height: StackHeight,
branch_params: BranchParams,
is_branched_to: bool,
fuel_pos: Option<Pos<ir::BlockFuel>>,
label: LabelRef,
}
impl ControlFrameBase for LoopControlFrame {
fn kind(&self) -> ControlFrameKind {
ControlFrameKind::Loop
}
fn ty(&self) -> BlockType {
self.ty
}
fn height(&self) -> usize {
self.height.into()
}
fn branch_params(&self) -> BranchParams {
self.branch_params
}
fn label(&self) -> LabelRef {
self.label
}
fn is_branched_to(&self) -> bool {
self.is_branched_to
}
fn branch_to(&mut self) {
self.is_branched_to = true;
}
fn fuel_pos(&self) -> Option<Pos<ir::BlockFuel>> {
self.fuel_pos
}
}
#[derive(Debug)]
pub struct IfControlFrame {
ty: BlockType,
height: StackHeight,
branch_params: BranchParams,
is_branched_to: bool,
fuel_pos: Option<Pos<ir::BlockFuel>>,
label: LabelRef,
reachability: IfReachability,
registers: RegisterMap,
}
impl IfControlFrame {
pub fn reachability(&self) -> IfReachability {
self.reachability
}
pub fn is_else_reachable(&self) -> bool {
match self.reachability {
IfReachability::Both { .. } | IfReachability::OnlyElse => true,
IfReachability::OnlyThen => false,
}
}
pub(super) fn registers(&self) -> RegisterMap {
self.registers
}
}
impl ControlFrameBase for IfControlFrame {
fn kind(&self) -> ControlFrameKind {
ControlFrameKind::If
}
fn ty(&self) -> BlockType {
self.ty
}
fn height(&self) -> usize {
self.height.into()
}
fn branch_params(&self) -> BranchParams {
self.branch_params
}
fn label(&self) -> LabelRef {
self.label
}
fn is_branched_to(&self) -> bool {
self.is_branched_to
}
fn branch_to(&mut self) {
self.is_branched_to = true;
}
fn fuel_pos(&self) -> Option<Pos<ir::BlockFuel>> {
self.fuel_pos
}
}
#[derive(Debug, Copy, Clone)]
pub enum IfReachability {
Both { else_label: LabelRef },
OnlyThen,
OnlyElse,
}
#[derive(Debug)]
pub struct ElseControlFrame {
ty: BlockType,
height: StackHeight,
branch_params: BranchParams,
is_branched_to: bool,
fuel_pos: Option<Pos<ir::BlockFuel>>,
label: LabelRef,
reachability: ElseReachability,
}
#[derive(Debug, Copy, Clone)]
pub enum ElseReachability {
Both {
is_end_of_then_reachable: bool,
},
OnlyThen {
is_end_of_then_reachable: bool,
},
OnlyElse,
}
impl ElseControlFrame {
pub fn reachability(&self) -> ElseReachability {
self.reachability
}
pub fn is_end_of_then_reachable(&self) -> bool {
match self.reachability {
ElseReachability::Both {
is_end_of_then_reachable,
}
| ElseReachability::OnlyThen {
is_end_of_then_reachable,
} => is_end_of_then_reachable,
ElseReachability::OnlyElse => false,
}
}
}
impl ControlFrameBase for ElseControlFrame {
fn kind(&self) -> ControlFrameKind {
ControlFrameKind::Else
}
fn ty(&self) -> BlockType {
self.ty
}
fn height(&self) -> usize {
self.height.into()
}
fn branch_params(&self) -> BranchParams {
self.branch_params
}
fn label(&self) -> LabelRef {
self.label
}
fn is_branched_to(&self) -> bool {
self.is_branched_to
}
fn branch_to(&mut self) {
self.is_branched_to = true;
}
fn fuel_pos(&self) -> Option<Pos<ir::BlockFuel>> {
self.fuel_pos
}
}
#[derive(Debug, Copy, Clone)]
pub enum ControlFrameKind {
Block,
Loop,
If,
Else,
}