use crate::{Bytecode, Opcode, Value};
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RegOp {
Mov { dst: u16, src: u16 },
LoadConst { dst: u16, k: u32 },
LoadGlobal { dst: u16, slot: u32 },
StoreGlobal { slot: u32, src: u16 },
LoadUpvalue { dst: u16, idx: u32 },
StoreUpvalue { idx: u32, src: u16 },
Bin {
op: BinKind,
dst: u16,
lhs: u16,
rhs: u16,
},
Un { op: UnKind, dst: u16, src: u16 },
Cmp {
op: CmpKind,
dst: u16,
lhs: u16,
rhs: u16,
},
CmpImm {
op: CmpKind,
dst: u16,
lhs: u16,
imm: i64,
},
BinImm {
op: BinKind,
dst: u16,
lhs: u16,
imm: i64,
},
Jump { target: u32 },
JumpIfFalse { target: u32, cond: u16 },
JumpIfTrue { target: u32, cond: u16 },
Call {
dst: u16,
func: u16,
args: u16,
argc: u8,
},
TailCall { func: u16, args: u16, argc: u8 },
Ret { src: u16 },
GetProp { dst: u16, obj: u16, name: u32 },
SetProp { obj: u16, name: u32, src: u16 },
Invoke {
dst: u16,
obj: u16,
name: u32,
args: u16,
argc: u8,
},
BuildArr { dst: u16, start: u16, count: u16 },
BuildMap { dst: u16, start: u16, count: u16 },
IndexGet { dst: u16, obj: u16, idx: u16 },
IndexSet { obj: u16, idx: u16, src: u16 },
CreateClass { dst: u16, k: u32 },
CreateInstance {
dst: u16,
class: u16,
args: u16,
argc: u8,
},
MakeClosure {
dst: u16,
func: u16,
upvals: u16,
count: u8,
},
Wrap { kind: WrapKind, dst: u16, src: u16 },
Unwrap {
kind: WrapKind,
dst: u16,
flag: u16,
src: u16,
},
TryQ { dst: u16, src: u16 },
Argc { dst: u16 },
CallChunk {
dst: u16,
chunk: u32,
args: u16,
argc: u8,
},
Nop,
Halt { src: u16 },
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BinKind {
Add,
Sub,
Mul,
Div,
Mod,
Pow,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
And,
Or,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UnKind {
Neg,
Not,
BitNot,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CmpKind {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WrapKind {
Ok,
Err,
Some,
}
pub fn parameter_slots(bytecode: &Bytecode, chunk_idx: usize) -> u16 {
if chunk_idx == bytecode.main_chunk {
return 0;
}
let mut best: Option<usize> = None;
for ch in &bytecode.chunks {
for c in &ch.constants {
if let Value::Function(f) = c {
if f.chunk_index == chunk_idx {
best = Some(best.map_or(f.arity, |b: usize| b.max(f.arity)));
}
}
if let Value::Class(cls) = c {
for (_, ci) in cls.methods.iter() {
if *ci == chunk_idx {
if best.is_none() {
best = Some(1);
}
}
}
}
}
}
best.unwrap_or(0) as u16
}
#[derive(Debug, Clone)]
pub struct RegChunk {
pub name: String,
pub code: Vec<RegOp>,
pub constants: Vec<Value>,
pub n_regs: u16,
pub param_slots: u16,
pub arity: usize,
pub required: usize,
}
impl RegChunk {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
code: Vec::new(),
constants: Vec::new(),
n_regs: 0,
param_slots: 0,
arity: 0,
required: 0,
}
}
}
pub struct StackToRegister<'a> {
src_code: &'a [crate::Instruction],
#[allow(dead_code)]
src_consts: &'a [Value],
out: RegChunk,
ip_map: Vec<u32>,
jumps: Vec<(usize, usize)>,
param_slots: u16,
heights: Vec<u16>,
}
impl<'a> StackToRegister<'a> {
pub fn new(name: &str, code: &'a [crate::Instruction], consts: &'a [Value]) -> Self {
let mut out = RegChunk::new(name);
out.constants = consts.to_vec();
out.arity = 0;
out.required = 0;
Self {
src_code: code,
src_consts: consts,
out,
ip_map: Vec::new(),
jumps: Vec::new(),
param_slots: 0,
heights: Vec::new(),
}
}
pub fn with_param_slots(mut self, param_slots: u16) -> Self {
self.param_slots = param_slots;
self
}
fn emit(&mut self, op: RegOp) -> usize {
self.out.code.push(op);
self.out.code.len() - 1
}
fn use_reg(&mut self, r: u16) {
if r + 1 > self.out.n_regs {
self.out.n_regs = r + 1;
}
}
#[allow(unused_assignments)]
pub fn translate(mut self) -> RegChunk {
let param_slots = self.param_slots;
self.heights = compute_heights(self.src_code, param_slots);
self.out.n_regs = self.out.n_regs.max(param_slots);
self.out.param_slots = param_slots;
for (sip, ins) in self.src_code.iter().enumerate() {
self.ip_map.push(self.out.code.len() as u32);
let mut sp: u16 = self.heights[sip];
match ins.opcode {
Opcode::Push => {
let k = ins.operand.unwrap_or(0);
let dst = sp;
self.use_reg(dst);
self.emit(RegOp::LoadConst { dst, k });
sp += 1;
}
Opcode::Pop => {
sp = sp.saturating_sub(1);
}
Opcode::Dup => {
let src = sp.saturating_sub(1);
let dst = sp;
self.use_reg(dst);
self.emit(RegOp::Mov { dst, src });
sp += 1;
}
Opcode::LoadLocal => {
let idx = ins.operand.unwrap_or(0) as u16;
let dst = sp;
self.use_reg(idx.max(dst));
self.emit(RegOp::Mov { dst, src: idx });
sp += 1;
}
Opcode::StoreLocal => {
let idx = ins.operand.unwrap_or(0) as u16;
let src = sp.saturating_sub(1);
self.use_reg(idx.max(src));
self.emit(RegOp::Mov { dst: idx, src });
sp = sp.saturating_sub(1);
}
Opcode::LoadGlobal => {
let slot = ins.operand.unwrap_or(0);
let dst = sp;
self.use_reg(dst);
self.emit(RegOp::LoadGlobal { dst, slot });
sp += 1;
}
Opcode::StoreGlobal => {
let slot = ins.operand.unwrap_or(0);
let src = sp.saturating_sub(1);
self.use_reg(src);
self.emit(RegOp::StoreGlobal { slot, src });
sp = sp.saturating_sub(1);
}
Opcode::LoadUpvalue => {
let idx = ins.operand.unwrap_or(0);
let dst = sp;
self.use_reg(dst);
self.emit(RegOp::LoadUpvalue { dst, idx });
sp += 1;
}
Opcode::StoreUpvalue => {
let idx = ins.operand.unwrap_or(0);
let src = sp.saturating_sub(1);
self.use_reg(src);
self.emit(RegOp::StoreUpvalue { idx, src });
sp = sp.saturating_sub(1);
}
Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Div
| Opcode::Mod
| Opcode::Pow
| Opcode::BitAnd
| Opcode::BitOr
| Opcode::BitXor
| Opcode::Shl
| Opcode::Shr
| Opcode::And
| Opcode::Or => {
let kind = bin_kind(ins.opcode);
let rhs = sp.saturating_sub(1);
let lhs = sp.saturating_sub(2);
let dst = lhs;
self.use_reg(rhs.max(dst));
self.emit(RegOp::Bin {
op: kind,
dst,
lhs,
rhs,
});
sp = sp.saturating_sub(1);
}
Opcode::AddImm
| Opcode::SubImm
| Opcode::MulImm
| Opcode::EqImm
| Opcode::NeImm
| Opcode::LtImm
| Opcode::LeImm
| Opcode::GtImm
| Opcode::GeImm => {
let imm = ins.operand.unwrap_or(0) as i32 as i64;
let lhs = sp.saturating_sub(1);
let dst = lhs;
self.use_reg(dst);
let op = ins.opcode;
if matches!(op, Opcode::AddImm | Opcode::SubImm | Opcode::MulImm) {
self.emit(RegOp::BinImm {
op: bin_kind_imm(op),
dst,
lhs,
imm,
});
} else {
self.emit(RegOp::CmpImm {
op: cmp_kind_imm(op),
dst,
lhs,
imm,
});
}
}
Opcode::Neg | Opcode::Not | Opcode::BitNot => {
let src = sp.saturating_sub(1);
let dst = src;
self.use_reg(dst);
self.emit(RegOp::Un {
op: un_kind(ins.opcode),
dst,
src,
});
}
Opcode::Eq | Opcode::Ne | Opcode::Lt | Opcode::Le | Opcode::Gt | Opcode::Ge => {
let rhs = sp.saturating_sub(1);
let lhs = sp.saturating_sub(2);
let dst = lhs;
self.use_reg(rhs.max(dst));
self.emit(RegOp::Cmp {
op: cmp_kind(ins.opcode),
dst,
lhs,
rhs,
});
sp = sp.saturating_sub(1);
}
Opcode::Jump => {
let t = ins.operand.unwrap_or(0) as usize;
let site = self.emit(RegOp::Jump { target: 0 });
self.jumps.push((t, site));
}
Opcode::JumpIfFalse => {
let t = ins.operand.unwrap_or(0) as usize;
let cond = sp.saturating_sub(1);
self.use_reg(cond);
let site = self.emit(RegOp::JumpIfFalse { target: 0, cond });
self.jumps.push((t, site));
}
Opcode::JumpIfTrue => {
let t = ins.operand.unwrap_or(0) as usize;
let cond = sp.saturating_sub(1);
self.use_reg(cond);
let site = self.emit(RegOp::JumpIfTrue { target: 0, cond });
self.jumps.push((t, site));
}
Opcode::Call => {
let argc = ins.operand.unwrap_or(0) as u8;
let argc_us = argc as u16;
let func = sp.saturating_sub(argc_us + 1);
let args = func + 1;
let dst = func;
self.use_reg(sp.saturating_sub(1).max(dst));
self.emit(RegOp::Call {
dst,
func,
args,
argc,
});
sp = func + 1;
}
Opcode::TailCall => {
let argc = ins.operand.unwrap_or(0) as u8;
let argc_us = argc as u16;
let func = sp.saturating_sub(argc_us + 1);
let args = func + 1;
self.use_reg(sp.saturating_sub(1));
self.emit(RegOp::TailCall { func, args, argc });
sp = 0;
}
Opcode::Return => {
let src = sp.saturating_sub(1);
self.use_reg(src);
self.emit(RegOp::Ret { src });
sp = 0;
}
Opcode::GetProperty => {
let name = ins.operand.unwrap_or(0);
let obj = sp.saturating_sub(1);
let dst = obj;
self.use_reg(dst);
self.emit(RegOp::GetProp { dst, obj, name });
}
Opcode::SetProperty => {
let name = ins.operand.unwrap_or(0);
let src = sp.saturating_sub(1);
let obj = sp.saturating_sub(2);
self.use_reg(src.max(obj));
self.emit(RegOp::SetProp { obj, name, src });
sp = obj + 1;
}
Opcode::Invoke => {
let packed = ins.operand.unwrap_or(0);
let argc = (packed >> 16) as u8;
let name = packed & 0xFFFF;
let argc_us = argc as u16;
let obj = sp.saturating_sub(argc_us + 1);
let args = obj + 1;
let dst = obj;
self.use_reg(sp.saturating_sub(1).max(dst));
self.emit(RegOp::Invoke {
dst,
obj,
name,
args,
argc,
});
sp = obj + 1;
}
Opcode::BuildArray => {
let count = ins.operand.unwrap_or(0) as u16;
let start = sp.saturating_sub(count);
let dst = start;
self.use_reg(sp.saturating_sub(1).max(dst));
self.emit(RegOp::BuildArr { dst, start, count });
sp = start + 1;
}
Opcode::BuildMap => {
let count = ins.operand.unwrap_or(0) as u16; let slots = count * 2;
let start = sp.saturating_sub(slots);
let dst = start;
self.use_reg(sp.saturating_sub(1).max(dst));
self.emit(RegOp::BuildMap {
dst,
start,
count: slots,
});
sp = start + 1;
}
Opcode::IndexGet => {
let idx = sp.saturating_sub(1);
let obj = sp.saturating_sub(2);
let dst = obj;
self.use_reg(idx.max(dst));
self.emit(RegOp::IndexGet { dst, obj, idx });
sp = obj + 1;
}
Opcode::IndexSet => {
let src = sp.saturating_sub(1);
let idx = sp.saturating_sub(2);
let obj = sp.saturating_sub(3);
self.use_reg(src.max(obj));
self.emit(RegOp::IndexSet { obj, idx, src });
sp = obj;
}
Opcode::CreateClass => {
let k = ins.operand.unwrap_or(0);
let dst = sp;
self.use_reg(dst);
self.emit(RegOp::CreateClass { dst, k });
sp += 1;
}
Opcode::CreateInstance => {
let argc = 0u8; let _ = argc;
let class = sp.saturating_sub(1);
let dst = class;
self.use_reg(dst);
self.emit(RegOp::CreateInstance {
dst,
class,
args: class,
argc: 0,
});
}
Opcode::GetMethod => {
let name = ins.operand.unwrap_or(0);
let obj = sp.saturating_sub(1);
let dst = obj;
self.use_reg(dst);
self.emit(RegOp::GetProp { dst, obj, name });
}
Opcode::MatchPattern => {
let dst = sp;
self.use_reg(dst);
let k = {
let v = Value::Bool(true);
if let Some(i) = self.out.constants.iter().position(|c| c == &v) {
i as u32
} else {
self.out.constants.push(v);
(self.out.constants.len() - 1) as u32
}
};
self.emit(RegOp::LoadConst { dst, k });
}
Opcode::MakeClosure => {
let count = ins.operand.unwrap_or(0) as u8;
let count_us = count as u16;
let upvals = sp.saturating_sub(count_us + 1);
let func = sp.saturating_sub(count_us + 1);
let func_r = sp - count_us - 1;
let upvals_r = func_r + 1;
let dst = func_r;
self.use_reg(sp.saturating_sub(1).max(dst));
self.emit(RegOp::MakeClosure {
dst,
func: func_r,
upvals: upvals_r,
count,
});
sp = func_r + 1;
let _ = (upvals, func);
}
Opcode::Throw => {
let src = sp.saturating_sub(1);
self.use_reg(src);
self.emit(RegOp::Ret { src });
sp = 0;
}
Opcode::TryBegin | Opcode::TryEnd | Opcode::Nop => {
self.emit(RegOp::Nop);
}
Opcode::WrapOk | Opcode::WrapErr | Opcode::WrapSome => {
let kind = match ins.opcode {
Opcode::WrapOk => WrapKind::Ok,
Opcode::WrapErr => WrapKind::Err,
_ => WrapKind::Some,
};
let src = sp.saturating_sub(1);
let dst = src;
self.use_reg(dst);
self.emit(RegOp::Wrap { kind, dst, src });
}
Opcode::UnwrapOk | Opcode::UnwrapErr | Opcode::UnwrapSome => {
let kind = match ins.opcode {
Opcode::UnwrapOk => WrapKind::Ok,
Opcode::UnwrapErr => WrapKind::Err,
_ => WrapKind::Some,
};
let src = sp.saturating_sub(1);
let dst = src;
let flag = src + 1;
self.use_reg(flag);
self.emit(RegOp::Unwrap {
kind,
dst,
flag,
src,
});
let _ = sp;
}
Opcode::Try => {
let src = sp.saturating_sub(1);
let dst = src;
self.use_reg(dst);
self.emit(RegOp::TryQ { dst, src });
}
Opcode::Argc => {
let dst = sp;
self.use_reg(dst);
self.emit(RegOp::Argc { dst });
sp += 1;
}
Opcode::Slide => {
let n = ins.operand.unwrap_or(0) as u16;
if n > 0 {
let src = sp.saturating_sub(1);
let dst = src.saturating_sub(n);
self.use_reg(src.max(dst));
self.emit(RegOp::Mov { dst, src });
sp = sp.saturating_sub(n);
}
}
Opcode::Halt => {
let src = sp.saturating_sub(1);
self.use_reg(src);
self.emit(RegOp::Halt { src });
sp = 0;
}
Opcode::CallDirect => {
let packed = ins.operand.unwrap_or(0);
let chunk = packed >> 16;
let argc = (packed & 0xFFFF) as u8;
let argc_us = argc as u16;
let args = sp.saturating_sub(argc_us);
let dst = args;
self.use_reg(sp.saturating_sub(1).max(dst));
self.emit(RegOp::CallChunk {
dst,
chunk,
args,
argc,
});
sp = args + 1;
}
}
}
let mut code = core::mem::take(&mut self.out.code);
let ip_map = self.ip_map.clone();
let jumps = self.jumps.clone();
for (stack_target, site) in jumps {
let reg_target = if stack_target < ip_map.len() {
ip_map[stack_target]
} else {
code.len() as u32
};
match &mut code[site] {
RegOp::Jump { ref mut target } => *target = reg_target,
RegOp::JumpIfFalse { ref mut target, .. } => *target = reg_target,
RegOp::JumpIfTrue { ref mut target, .. } => *target = reg_target,
_ => {}
}
}
self.out.code = code;
self.out
}
pub fn with_arity(self, arity: usize, required: usize) -> RegChunk {
let mut c = self.translate();
c.arity = arity;
c.required = required;
c
}
pub fn translate_named(self) -> RegChunk {
self.translate()
}
}
fn bin_kind(op: Opcode) -> BinKind {
match op {
Opcode::Add | Opcode::AddImm => BinKind::Add,
Opcode::Sub | Opcode::SubImm => BinKind::Sub,
Opcode::Mul | Opcode::MulImm => BinKind::Mul,
Opcode::Div => BinKind::Div,
Opcode::Mod => BinKind::Mod,
Opcode::Pow => BinKind::Pow,
Opcode::BitAnd => BinKind::BitAnd,
Opcode::BitOr => BinKind::BitOr,
Opcode::BitXor => BinKind::BitXor,
Opcode::Shl => BinKind::Shl,
Opcode::Shr => BinKind::Shr,
Opcode::And => BinKind::And,
Opcode::Or => BinKind::Or,
_ => BinKind::Add,
}
}
fn bin_kind_imm(op: Opcode) -> BinKind {
match op {
Opcode::SubImm => BinKind::Sub,
Opcode::MulImm => BinKind::Mul,
_ => BinKind::Add,
}
}
fn cmp_kind(op: Opcode) -> CmpKind {
match op {
Opcode::Eq | Opcode::EqImm => CmpKind::Eq,
Opcode::Ne | Opcode::NeImm => CmpKind::Ne,
Opcode::Lt | Opcode::LtImm => CmpKind::Lt,
Opcode::Le | Opcode::LeImm => CmpKind::Le,
Opcode::Gt | Opcode::GtImm => CmpKind::Gt,
Opcode::Ge | Opcode::GeImm => CmpKind::Ge,
_ => CmpKind::Eq,
}
}
fn cmp_kind_imm(op: Opcode) -> CmpKind {
cmp_kind(op)
}
fn un_kind(op: Opcode) -> UnKind {
match op {
Opcode::Not => UnKind::Not,
Opcode::BitNot => UnKind::BitNot,
_ => UnKind::Neg,
}
}
fn is_unwrap(op: Opcode) -> bool {
matches!(
op,
Opcode::UnwrapOk | Opcode::UnwrapErr | Opcode::UnwrapSome
)
}
fn stack_out(op: Opcode, h: u16, operand: Option<u32>) -> u16 {
match op {
Opcode::Push
| Opcode::Dup
| Opcode::LoadLocal
| Opcode::LoadGlobal
| Opcode::LoadUpvalue
| Opcode::Argc
| Opcode::CreateClass
| Opcode::CreateInstance
| Opcode::GetMethod
| Opcode::MatchPattern => h.saturating_add(1),
Opcode::Pop | Opcode::StoreGlobal | Opcode::StoreUpvalue | Opcode::Throw => {
h.saturating_sub(1)
}
Opcode::Slide => h.saturating_sub(operand.unwrap_or(0) as u16),
Opcode::StoreLocal => {
let idx = operand.unwrap_or(0) as u16;
let after = h.saturating_sub(1);
if idx >= after {
idx + 1
} else {
after
}
}
Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Div
| Opcode::Mod
| Opcode::Pow
| Opcode::BitAnd
| Opcode::BitOr
| Opcode::BitXor
| Opcode::Shl
| Opcode::Shr
| Opcode::And
| Opcode::Or
| Opcode::Eq
| Opcode::Ne
| Opcode::Lt
| Opcode::Le
| Opcode::Gt
| Opcode::Ge
| Opcode::IndexGet => h.saturating_sub(1),
Opcode::IndexSet => h.saturating_sub(2),
Opcode::Call => {
let argc = operand.unwrap_or(0) as u16;
h.saturating_sub(argc + 1).saturating_add(1)
}
Opcode::CallDirect => {
let argc = operand.unwrap_or(0) as u16;
h.saturating_sub(argc).saturating_add(1)
}
Opcode::Invoke => {
let packed = operand.unwrap_or(0);
let argc = (packed >> 16) as u16;
h.saturating_sub(argc + 1).saturating_add(1)
}
Opcode::BuildArray => {
let count = operand.unwrap_or(0) as u16;
h.saturating_sub(count).saturating_add(1)
}
Opcode::BuildMap => {
let pairs = operand.unwrap_or(0) as u16;
h.saturating_sub(pairs * 2).saturating_add(1)
}
Opcode::SetProperty => h.saturating_sub(1),
Opcode::GetProperty => h,
Opcode::MakeClosure => {
let count = operand.unwrap_or(0) as u16;
h.saturating_sub(count + 1).saturating_add(1)
}
Opcode::WrapOk | Opcode::WrapErr | Opcode::WrapSome => h,
Opcode::UnwrapOk | Opcode::UnwrapErr | Opcode::UnwrapSome => h.saturating_add(1),
Opcode::Try => h,
Opcode::AddImm
| Opcode::SubImm
| Opcode::MulImm
| Opcode::EqImm
| Opcode::NeImm
| Opcode::LtImm
| Opcode::LeImm
| Opcode::GtImm
| Opcode::GeImm
| Opcode::Neg
| Opcode::Not
| Opcode::BitNot
| Opcode::Jump
| Opcode::JumpIfFalse
| Opcode::JumpIfTrue
| Opcode::TryBegin
| Opcode::TryEnd
| Opcode::Nop => h,
Opcode::Return | Opcode::Halt | Opcode::TailCall => h,
}
}
fn compute_heights(code: &[crate::Instruction], param_slots: u16) -> Vec<u16> {
let n = code.len();
if n == 0 {
return Vec::new();
}
let mut height = vec![u16::MAX; n];
let mut work = alloc::collections::VecDeque::new();
height[0] = param_slots;
work.push_back(0usize);
while let Some(ip) = work.pop_front() {
if ip >= n {
continue;
}
let h = height[ip];
let ins = &code[ip];
let out = stack_out(ins.opcode, h, ins.operand);
match ins.opcode {
Opcode::Jump => {
let t = ins.operand.unwrap_or(0) as usize;
if t < n {
let cur = height[t];
if cur == u16::MAX {
height[t] = out;
work.push_back(t);
} else if cur != out {
let m = cur.max(out);
if m != cur {
height[t] = m;
work.push_back(t);
}
}
}
}
Opcode::JumpIfFalse | Opcode::JumpIfTrue => {
let taken = if ip > 0 && is_unwrap(code[ip - 1].opcode) {
out.saturating_sub(1)
} else {
out
};
let ft = ip + 1;
if ft < n {
let cur = height[ft];
if cur == u16::MAX {
height[ft] = out;
work.push_back(ft);
} else if cur != out {
let m = cur.max(out);
if m != cur {
height[ft] = m;
work.push_back(ft);
}
}
}
let t = ins.operand.unwrap_or(0) as usize;
if t < n {
let cur = height[t];
if cur == u16::MAX {
height[t] = taken;
work.push_back(t);
} else if cur != taken {
let m = cur.max(taken);
if m != cur {
height[t] = m;
work.push_back(t);
}
}
}
}
Opcode::Return | Opcode::Halt | Opcode::TailCall | Opcode::Throw => {}
_ => {
let ft = ip + 1;
if ft < n {
let cur = height[ft];
if cur == u16::MAX {
height[ft] = out;
work.push_back(ft);
} else if cur != out {
let m = cur.max(out);
if m != cur {
height[ft] = m;
work.push_back(ft);
}
}
}
}
}
}
for h in height.iter_mut() {
if *h == u16::MAX {
*h = param_slots;
}
}
height
}