#[allow(unused_imports)]
use super::instruction::{CompiledProgram, Constant, Op};
use super::native_tier::{ParamKind, SlotKind};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[allow(dead_code)] pub struct FnBytecode {
pub code: Vec<Op>,
pub constants: Vec<Constant>,
pub register_count: usize,
pub param_count: u16,
pub param_kinds: Vec<Option<ParamKind>>,
pub ret_kind: Option<SlotKind>,
pub named_regs: Vec<bool>,
}
impl FnBytecode {
pub fn is_well_formed(&self, n_funcs: usize) -> bool {
if self.code.is_empty() {
return false;
}
for op in &self.code {
match *op {
Op::Jump { target }
| Op::JumpIfFalse { target, .. }
| Op::JumpIfTrue { target, .. } => {
if target >= self.code.len() {
return false;
}
}
Op::Call { func, .. } => {
if func as usize >= n_funcs {
return false;
}
}
_ => {}
}
}
matches!(
self.code.last(),
Some(Op::Return { .. } | Op::ReturnNothing | Op::Halt)
)
}
}
pub(crate) fn rebase(op: Op, delta: isize) -> Op {
let shift = |t: usize| (t as isize + delta) as usize;
match op {
Op::Jump { target } => Op::Jump { target: shift(target) },
Op::JumpIfFalse { cond, target } => Op::JumpIfFalse { cond, target: shift(target) },
Op::JumpIfTrue { cond, target } => Op::JumpIfTrue { cond, target: shift(target) },
other => other,
}
}
#[allow(dead_code)]
pub fn slice_function(program: &CompiledProgram, fi: usize) -> FnBytecode {
let f = &program.functions[fi];
let entry = f.entry_pc;
let end = program
.functions
.iter()
.map(|g| g.entry_pc)
.filter(|&e| e > entry)
.min()
.unwrap_or(program.code.len());
let code = program.code[entry..end]
.iter()
.map(|&op| rebase(op, -(entry as isize)))
.collect();
FnBytecode {
code,
constants: program.constants.clone(),
register_count: f.register_count,
param_count: f.param_count,
param_kinds: f.param_kinds.clone(),
ret_kind: f.ret_kind,
named_regs: f.named_regs.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::intern::Interner;
use crate::vm::instruction::CompiledFunction;
fn jump_target(op: &Op) -> Option<usize> {
match *op {
Op::Jump { target }
| Op::JumpIfFalse { target, .. }
| Op::JumpIfTrue { target, .. } => Some(target),
_ => None,
}
}
#[test]
fn slice_rebases_jumps_zero_relative_and_round_trips() {
let mut it = Interner::new();
let name = it.intern("f");
let code = vec![
Op::Halt,
Op::JumpIfFalse { cond: 0, target: 3 },
Op::Jump { target: 1 },
Op::Halt,
];
let prog = CompiledProgram {
code,
functions: vec![CompiledFunction {
name,
entry_pc: 1,
param_count: 0,
register_count: 1,
captures: vec![],
named_regs: vec![true],
param_kinds: vec![],
ret_kind: None,
param_types: vec![],
return_type: None,
mutable_param_regs: vec![],
}],
..Default::default()
};
let fnbc = slice_function(&prog, 0);
assert_eq!(fnbc.code.len(), 3, "body is entry..next-entry");
assert!(matches!(fnbc.code[0], Op::JumpIfFalse { target: 2, .. }));
assert!(matches!(fnbc.code[1], Op::Jump { target: 0 }));
for op in &fnbc.code {
if let Some(t) = jump_target(op) {
assert!(t < fnbc.code.len(), "jump target {t} out of slice bounds");
}
}
let reabs: Vec<String> = fnbc.code.iter().map(|&op| format!("{:?}", rebase(op, 1))).collect();
let orig: Vec<String> = prog.code[1..4].iter().map(|op| format!("{:?}", op)).collect();
assert_eq!(reabs, orig, "slice must invert back to the original body");
assert_eq!(fnbc.register_count, 1);
assert_eq!(fnbc.named_regs, vec![true]);
}
fn body(code: Vec<Op>) -> FnBytecode {
FnBytecode {
code,
constants: vec![],
register_count: 1,
param_count: 0,
param_kinds: vec![],
ret_kind: None,
named_regs: vec![],
}
}
#[test]
fn is_well_formed_accepts_a_valid_body() {
let good = body(vec![
Op::JumpIfFalse { cond: 0, target: 2 },
Op::Jump { target: 0 },
Op::ReturnNothing,
]);
assert!(good.is_well_formed(1));
}
#[test]
fn is_well_formed_rejects_malformed_bodies() {
assert!(!body(vec![]).is_well_formed(1));
assert!(!body(vec![Op::Jump { target: 0 }]).is_well_formed(1));
assert!(!body(vec![Op::Jump { target: 99 }, Op::ReturnNothing]).is_well_formed(1));
assert!(!body(vec![
Op::Call { dst: 0, func: 5, args_start: 0, arg_count: 0 },
Op::ReturnNothing,
])
.is_well_formed(1));
assert!(body(vec![
Op::Call { dst: 0, func: 5, args_start: 0, arg_count: 0 },
Op::ReturnNothing,
])
.is_well_formed(6));
}
}