use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use bytecode_lang::{FunctionBuilder, Inst, Label, Reg, ValType};
use hir_lang::{BinderId, ExprId, NodeRef};
#[derive(Clone, Copy, Debug)]
pub(crate) struct Target {
pub(crate) label: Option<BinderId>,
pub(crate) is_loop: bool,
pub(crate) brk: Label,
pub(crate) cont: Label,
pub(crate) result: Reg,
pub(crate) fin_depth: usize,
pub(crate) id: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Exit {
Return,
Break(u32, bool),
Continue(u32),
}
#[derive(Clone, Debug)]
pub(crate) struct Fin {
pub(crate) kind: Reg,
pub(crate) value: Reg,
pub(crate) entry: Label,
pub(crate) exits: Vec<Exit>,
pub(crate) node: NodeRef,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct TryState {
pub(crate) e: Option<ExprId>,
pub(crate) node: NodeRef,
pub(crate) finally: Option<ExprId>,
pub(crate) result: Option<Reg>,
pub(crate) catch: Option<(Label, u32, Label, Label, Reg)>,
pub(crate) fin: Option<(Label, u32, Label, Label)>,
pub(crate) end: Label,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Scoped {
pub(crate) binder: BinderId,
pub(crate) reg: Reg,
pub(crate) start: Label,
}
pub(crate) struct FnCx {
pub(crate) f: FunctionBuilder,
pub(crate) origins: Vec<NodeRef>,
pub(crate) node: NodeRef,
pub(crate) located: Option<NodeRef>,
types: Vec<ValType>,
live: Vec<bool>,
free: BTreeMap<ValType, Vec<Reg>>,
shapes: Vec<Vec<ValType>>,
windows: Vec<Vec<Reg>>,
pub(crate) scopes: Vec<usize>,
pub(crate) scoped: Vec<Scoped>,
pub(crate) targets: Vec<Target>,
pub(crate) fins: Vec<Fin>,
pub(crate) tries: Vec<TryState>,
pub(crate) next_target: u32,
}
impl FnCx {
pub(crate) fn new(f: FunctionBuilder, params: &[ValType], node: NodeRef) -> Self {
let n = params.len();
FnCx {
f,
origins: Vec::new(),
node,
located: None,
types: params.to_vec(),
live: alloc::vec![true; n],
free: BTreeMap::new(),
shapes: Vec::new(),
windows: Vec::new(),
scopes: Vec::new(),
scoped: Vec::new(),
targets: Vec::new(),
fins: Vec::new(),
tries: Vec::new(),
next_target: 0,
}
}
pub(crate) fn emit(&mut self, inst: Inst) -> u32 {
self.origins.push(self.node);
self.f.emit(inst)
}
fn declare(&mut self, ty: ValType) -> Reg {
let r = self.f.reg(ty);
if r.index() == self.types.len() {
self.types.push(ty);
self.live.push(true);
}
r
}
pub(crate) fn temp(&mut self, ty: ValType) -> Reg {
if let Some(r) = self.free.get_mut(&ty).and_then(Vec::pop) {
if let Some(l) = self.live.get_mut(r.index()) {
*l = true;
}
return r;
}
self.declare(ty)
}
pub(crate) fn release(&mut self, r: Reg) {
let Some(&ty) = self.types.get(r.index()) else {
return;
};
if let Some(l) = self.live.get_mut(r.index()) {
debug_assert!(*l, "register {r} released twice");
if !*l {
return;
}
*l = false;
}
self.free.entry(ty).or_default().push(r);
}
pub(crate) fn type_of(&self, r: Reg) -> ValType {
self.types.get(r.index()).copied().unwrap_or(ValType::Dyn)
}
pub(crate) fn window(&mut self, shape: &[ValType]) -> (u32, Reg) {
let id = match self.shapes.iter().position(|s| s.as_slice() == shape) {
Some(i) => i,
None => {
self.shapes.push(shape.to_vec());
self.windows.push(Vec::new());
self.shapes.len() - 1
}
};
let id32 = u32::try_from(id).unwrap_or(u32::MAX);
if let Some(base) = self.windows.get_mut(id).and_then(Vec::pop) {
return (id32, base);
}
let base = Reg(u16::try_from(self.types.len()).unwrap_or(u16::MAX));
for &ty in shape {
let _ = self.declare(ty);
}
(id32, base)
}
pub(crate) fn release_window(&mut self, shape: u32, base: Reg) {
if let Some(w) = self.windows.get_mut(shape as usize) {
debug_assert!(!w.contains(&base), "window {base} released twice");
w.push(base);
}
}
pub(crate) fn slot(base: Reg, i: usize) -> Reg {
Reg(base.0.saturating_add(u16::try_from(i).unwrap_or(u16::MAX)))
}
}
#[cfg(test)]
mod tests {
use bytecode_lang::{ModuleBuilder, ValType};
use hir_lang::{ItemId, NodeRef};
use super::FnCx;
fn cx() -> FnCx {
let mut m = ModuleBuilder::new();
let f = m.function("f", &[ValType::Dyn], &[ValType::Dyn]);
let node = NodeRef::Item(ItemId::from_index(0).unwrap_or_else(unreachable_id));
FnCx::new(f, &[ValType::Dyn], node)
}
fn unreachable_id() -> ItemId {
panic!("id 0 exists")
}
#[test]
fn temporaries_are_reused_per_type() {
let mut fx = cx();
let a = fx.temp(ValType::Dyn);
let b = fx.temp(ValType::Bool);
assert_ne!(a, b);
fx.release(a);
assert_eq!(fx.temp(ValType::Dyn), a);
fx.release(b);
assert_ne!(fx.temp(ValType::Dyn), b);
assert_eq!(fx.type_of(b), ValType::Bool);
}
#[test]
fn windows_are_consecutive_and_pooled_by_shape() {
let mut fx = cx();
let shape = [ValType::Dyn, ValType::Dyn, ValType::I64];
let (s, base) = fx.window(&shape);
assert_eq!(fx.type_of(FnCx::slot(base, 2)), ValType::I64);
let (_, other) = fx.window(&shape);
assert_ne!(base, other);
fx.release_window(s, base);
assert_eq!(fx.window(&shape).1, base);
assert_ne!(fx.window(&[ValType::Dyn]).1, base);
}
}