use super::{Capture, CodeUnit, CodeUnitId, DebugInfo, Format, Handler, Insn, Opcode};
use crate::heap::atom::PropertyKey;
use crate::vm::ic::{FIELD_NO_SITE, GLOBAL_NO_SITE, IcKind};
#[derive(Default)]
pub struct CodeBuilder {
code: Vec<Insn>,
handlers: Vec<Handler>,
}
impl CodeBuilder {
pub fn new() -> CodeBuilder {
CodeBuilder::default()
}
#[inline]
pub fn pc(&self) -> u32 {
self.code.len() as u32
}
#[track_caller]
fn push(&mut self, format: Format, insn: Insn) -> u32 {
debug_assert_eq!(
insn.op.format(),
format,
"{} built with the wrong format",
insn.op.name()
);
let pc = self.pc();
self.code.push(insn);
pc
}
pub fn raw(&mut self, insn: Insn) -> u32 {
let format = insn.op.format();
self.push(format, insn)
}
#[track_caller]
pub fn op(&mut self, op: Opcode) -> u32 {
self.push(Format::None, Insn::new(op, 0, 0, 0, 0))
}
#[track_caller]
pub fn a(&mut self, op: Opcode, a: u8) -> u32 {
self.push(Format::A, Insn::new(op, a, 0, 0, 0))
}
#[track_caller]
pub fn ab(&mut self, op: Opcode, a: u8, b: u8) -> u32 {
self.push(Format::AB, Insn::new(op, a, b, 0, 0))
}
#[track_caller]
pub fn abc(&mut self, op: Opcode, a: u8, b: u8, c: u8) -> u32 {
self.push(Format::ABC, Insn::new(op, a, b, c, 0))
}
#[track_caller]
pub fn a_int(&mut self, op: Opcode, a: u8, value: i32) -> u32 {
self.push(Format::AInt, Insn::new(op, a, 0, 0, value as u32))
}
#[track_caller]
pub fn ab_int(&mut self, op: Opcode, a: u8, b: u8, value: i32) -> u32 {
self.push(Format::ABInt, Insn::new(op, a, b, 0, value as u32))
}
#[track_caller]
pub fn a_const(&mut self, op: Opcode, a: u8, index: u32) -> u32 {
self.push(Format::AConst, Insn::new(op, a, 0, 0, index))
}
#[track_caller]
pub fn key(&mut self, op: Opcode, key: PropertyKey) -> u32 {
self.push(Format::Key, Insn::new(op, 0, 0, 0, key.raw()))
}
#[track_caller]
pub fn a_key(&mut self, op: Opcode, a: u8, key: PropertyKey) -> u32 {
self.push(Format::AKey, Insn::new(op, a, 0, 0, key.raw()))
}
#[track_caller]
pub fn ab_key(&mut self, op: Opcode, a: u8, b: u8, key: PropertyKey) -> u32 {
self.push(Format::ABKey, Insn::new(op, a, b, 0, key.raw()))
}
#[track_caller]
pub fn a_capture(&mut self, op: Opcode, a: u8, capture: u32) -> u32 {
self.push(Format::ACapture, Insn::new(op, a, 0, 0, capture))
}
#[track_caller]
pub fn a_count(&mut self, op: Opcode, a: u8, count: u32) -> u32 {
self.push(Format::ACount, Insn::new(op, a, 0, 0, count))
}
#[track_caller]
pub fn ab_range(&mut self, op: Opcode, a: u8, first: u8, count: u32) -> u32 {
self.push(Format::ABRange, Insn::new(op, a, first, 0, count))
}
#[track_caller]
pub fn a_child(&mut self, op: Opcode, a: u8, child: u32) -> u32 {
self.push(Format::AChild, Insn::new(op, a, 0, 0, child))
}
#[track_caller]
pub fn call(&mut self, op: Opcode, dst: u8, callee: u8, argc: u8) -> u32 {
self.push(Format::Call, Insn::new(op, dst, callee, argc, 0))
}
#[track_caller]
pub fn jump(&mut self, op: Opcode, target: u32) -> u32 {
self.push(Format::Jump, Insn::new(op, 0, 0, 0, target))
}
#[track_caller]
pub fn a_jump(&mut self, op: Opcode, a: u8, target: u32) -> u32 {
self.push(Format::AJump, Insn::new(op, a, 0, 0, target))
}
#[track_caller]
pub fn ab_jump(&mut self, op: Opcode, a: u8, b: u8, target: u32) -> u32 {
self.push(Format::ABJump, Insn::new(op, a, b, 0, target))
}
#[track_caller]
pub fn a_short_jump(&mut self, op: Opcode, a: u8, value: i16, target: u32) -> u32 {
let [b, c] = value.to_le_bytes();
self.push(Format::AShortJump, Insn::new(op, a, b, c, target))
}
#[track_caller]
pub fn a_wide(&mut self, op: Opcode, a: u8, register: u32) -> u32 {
self.push(Format::AWide, Insn::new(op, a, 0, 0, register))
}
pub fn retarget(&mut self, pc: u32, a: u8) {
self.code[pc as usize].a = a;
}
#[track_caller]
pub fn patch_target(&mut self, pc: u32, target: u32) {
let insn = &mut self.code[pc as usize];
assert!(insn.has_target(), "{} has no jump target", insn.op.name());
insn.imm = target;
}
pub fn handler(&mut self, handler: Handler) {
self.handlers.push(handler);
}
pub fn code(&self) -> &[Insn] {
&self.code
}
pub fn mark_back_edges(&mut self) {
for (pc, insn) in self.code.iter_mut().enumerate() {
if insn.op == Opcode::Jmp && insn.imm as usize <= pc {
insn.op = Opcode::JmpBack;
}
}
}
pub fn finish(mut self, layout: UnitLayout) -> CodeUnit {
let ic_count = number_sites(&mut self.code);
CodeUnit {
code: self.code.into(),
handlers: self.handlers.into(),
register_count: layout.register_count,
parameter_count: layout.parameter_count,
local_count: layout.local_count,
flags: layout.flags,
this_slots: layout.this_slots,
constant_count: layout.constant_count,
spill_register: layout.spill_register.unwrap_or(super::NO_SPILL),
spill_slots: layout.spill_slots,
ic_base: 0,
ic_count,
children: layout.children.into(),
captures: layout.captures.into(),
debug: DebugInfo {
name: layout.name.into(),
filename: layout.filename,
positions: Box::new([]),
definition: None,
},
name_atom: layout.name_atom,
length: layout.length,
source: layout.source,
direct_eval_sites: Box::new([]),
atoms: Box::new([]),
}
}
}
fn number_sites(code: &mut [Insn]) -> u32 {
let is_field = |insn: &Insn| matches!(IcKind::of(insn.op), Some(IcKind::Get | IcKind::Set));
let is_global = |insn: &Insn| IcKind::of(insn.op) == Some(IcKind::Global);
let fields = code.iter().filter(|insn| is_field(insn)).count() as u32;
let reserved = u32::from(FIELD_NO_SITE);
let mut next = 0u32;
for insn in code.iter_mut().filter(|insn| is_field(insn)) {
insn.c = next.min(reserved) as u8;
next += 1;
}
next = if fields > reserved {
reserved + 1
} else {
fields
};
let globals = code.iter().filter(|insn| is_global(insn)).count() as u32;
let first_global = next;
for insn in code.iter_mut().filter(|insn| is_global(insn)) {
let site = next.min(GLOBAL_NO_SITE);
insn.b = site as u8;
insn.c = (site >> 8) as u8;
next += 1;
}
if first_global + globals > GLOBAL_NO_SITE {
next = GLOBAL_NO_SITE + 1;
}
for insn in code.iter_mut().filter(|insn| insn.op == Opcode::New) {
insn.imm = next;
next += 1;
}
next
}
#[derive(Clone, Debug, Default)]
pub struct UnitLayout {
pub register_count: u16,
pub parameter_count: u16,
pub local_count: u16,
pub flags: u16,
pub this_slots: u16,
pub constant_count: u32,
pub spill_register: Option<u16>,
pub spill_slots: u16,
pub children: Vec<CodeUnitId>,
pub captures: Vec<Capture>,
pub name: String,
pub name_atom: Option<crate::value::StringId>,
pub length: u16,
pub source: Option<Box<[u8]>>,
pub filename: Option<std::rc::Rc<str>>,
}