use super::{Operand, Reset};
use crate::{
engine::{
translator::{labels::LabelRef, utils::Instr},
BlockType,
},
Engine,
};
use alloc::vec::{Drain, Vec};
#[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, Default)]
pub struct ControlStack {
frames: Vec<ControlFrame>,
consume_fuel_instr: Option<Instr>,
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 consume_fuel_instr(&self) -> Option<Instr> {
debug_assert!(!self.is_empty());
self.consume_fuel_instr
}
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,
label: LabelRef,
consume_fuel: Option<Instr>,
) {
debug_assert!(!self.orphaned_else_operands);
self.frames.push(ControlFrame::from(BlockControlFrame {
ty,
height: StackHeight::from(height),
is_branched_to: false,
consume_fuel,
label,
}));
self.consume_fuel_instr = consume_fuel;
}
pub fn push_loop(
&mut self,
ty: BlockType,
height: usize,
label: LabelRef,
consume_fuel: Option<Instr>,
) {
debug_assert!(!self.orphaned_else_operands);
self.frames.push(ControlFrame::from(LoopControlFrame {
ty,
height: StackHeight::from(height),
is_branched_to: false,
consume_fuel,
label,
}));
self.consume_fuel_instr = consume_fuel;
}
pub fn push_if(
&mut self,
ty: BlockType,
height: usize,
label: LabelRef,
consume_fuel: Option<Instr>,
reachability: IfReachability,
else_operands: impl IntoIterator<Item = Operand>,
) {
debug_assert!(!self.orphaned_else_operands);
self.frames.push(ControlFrame::from(IfControlFrame {
ty,
height: StackHeight::from(height),
is_branched_to: false,
consume_fuel,
label,
reachability,
}));
if matches!(reachability, IfReachability::Both { .. }) {
self.else_operands.push(else_operands);
}
self.consume_fuel_instr = consume_fuel;
}
pub fn push_else(
&mut self,
if_frame: IfControlFrame,
consume_fuel: Option<Instr>,
is_end_of_then_reachable: bool,
) {
debug_assert!(!self.orphaned_else_operands);
let ty = if_frame.ty();
let height = if_frame.height();
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),
is_branched_to,
consume_fuel,
label,
reachability,
}));
self.consume_fuel_instr = consume_fuel;
}
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.consume_fuel_instr = self.get(0).consume_fuel_instr();
}
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 ty(&self) -> BlockType {
self.0.ty()
}
fn height(&self) -> usize {
self.0.height()
}
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 len_branch_params(&self, engine: &Engine) -> u16 {
self.0.len_branch_params(engine)
}
fn consume_fuel_instr(&self) -> Option<Instr> {
self.0.consume_fuel_instr()
}
}
#[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 ty(&self) -> BlockType;
fn height(&self) -> usize;
fn label(&self) -> LabelRef;
fn is_branched_to(&self) -> bool;
fn branch_to(&mut self);
fn len_branch_params(&self, engine: &Engine) -> u16;
fn consume_fuel_instr(&self) -> Option<Instr>;
}
impl ControlFrameBase for ControlFrame {
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 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 len_branch_params(&self, engine: &Engine) -> u16 {
match self {
ControlFrame::Block(frame) => frame.len_branch_params(engine),
ControlFrame::Loop(frame) => frame.len_branch_params(engine),
ControlFrame::If(frame) => frame.len_branch_params(engine),
ControlFrame::Else(frame) => frame.len_branch_params(engine),
ControlFrame::Unreachable(_) => {
panic!("invalid query for unreachable control frame: `ControlFrame::len_branch_params`")
}
}
}
fn consume_fuel_instr(&self) -> Option<Instr> {
match self {
ControlFrame::Block(frame) => frame.consume_fuel_instr(),
ControlFrame::Loop(frame) => frame.consume_fuel_instr(),
ControlFrame::If(frame) => frame.consume_fuel_instr(),
ControlFrame::Else(frame) => frame.consume_fuel_instr(),
ControlFrame::Unreachable(_) => {
panic!("invalid query for unreachable control frame: `ControlFrame::consume_fuel_instr`")
}
}
}
}
#[derive(Debug)]
pub struct BlockControlFrame {
ty: BlockType,
height: StackHeight,
is_branched_to: bool,
consume_fuel: Option<Instr>,
label: LabelRef,
}
impl ControlFrameBase for BlockControlFrame {
fn ty(&self) -> BlockType {
self.ty
}
fn height(&self) -> usize {
self.height.into()
}
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 len_branch_params(&self, engine: &Engine) -> u16 {
self.ty.len_results(engine)
}
fn consume_fuel_instr(&self) -> Option<Instr> {
self.consume_fuel
}
}
#[derive(Debug)]
pub struct LoopControlFrame {
ty: BlockType,
height: StackHeight,
is_branched_to: bool,
consume_fuel: Option<Instr>,
label: LabelRef,
}
impl ControlFrameBase for LoopControlFrame {
fn ty(&self) -> BlockType {
self.ty
}
fn height(&self) -> usize {
self.height.into()
}
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 len_branch_params(&self, engine: &Engine) -> u16 {
self.ty.len_params(engine)
}
fn consume_fuel_instr(&self) -> Option<Instr> {
self.consume_fuel
}
}
#[derive(Debug)]
pub struct IfControlFrame {
ty: BlockType,
height: StackHeight,
is_branched_to: bool,
consume_fuel: Option<Instr>,
label: LabelRef,
reachability: IfReachability,
}
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,
}
}
}
impl ControlFrameBase for IfControlFrame {
fn ty(&self) -> BlockType {
self.ty
}
fn height(&self) -> usize {
self.height.into()
}
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 len_branch_params(&self, engine: &Engine) -> u16 {
self.ty.len_results(engine)
}
fn consume_fuel_instr(&self) -> Option<Instr> {
self.consume_fuel
}
}
#[derive(Debug, Copy, Clone)]
pub enum IfReachability {
Both { else_label: LabelRef },
OnlyThen,
OnlyElse,
}
#[derive(Debug)]
pub struct ElseControlFrame {
ty: BlockType,
height: StackHeight,
is_branched_to: bool,
consume_fuel: Option<Instr>,
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 ty(&self) -> BlockType {
self.ty
}
fn height(&self) -> usize {
self.height.into()
}
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 len_branch_params(&self, engine: &Engine) -> u16 {
self.ty.len_results(engine)
}
fn consume_fuel_instr(&self) -> Option<Instr> {
self.consume_fuel
}
}
#[derive(Debug, Copy, Clone)]
pub enum ControlFrameKind {
Block,
Loop,
If,
Else,
}