pub mod builder;
pub mod disasm;
pub mod opcode;
pub mod validate;
use std::fmt;
pub use self::builder::CodeBuilder;
pub use self::disasm::disassemble;
pub use self::opcode::{Format, Opcode};
pub use self::validate::{ValidateError, ValidateErrorKind};
use crate::heap::atom::PropertyKey;
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Insn {
pub op: Opcode,
pub a: u8,
pub b: u8,
pub c: u8,
pub imm: u32,
}
const _: () = assert!(size_of::<Insn>() == 8);
pub const WINDOW: u32 = 256;
impl Insn {
#[inline]
pub const fn new(op: Opcode, a: u8, b: u8, c: u8, imm: u32) -> Insn {
Insn { op, a, b, c, imm }
}
#[inline]
pub const fn int(self) -> i32 {
self.imm as i32
}
#[inline]
pub const fn short(self) -> i16 {
(self.b as u16 | (self.c as u16) << 8) as i16
}
#[inline]
pub const fn key(self) -> PropertyKey {
PropertyKey::from_raw(self.imm)
}
#[inline]
pub const fn site(self) -> Option<u32> {
match self.op {
Opcode::GetField | Opcode::SetField => Some(self.c as u32),
Opcode::GetGlobal | Opcode::GetGlobalOrUndefined | Opcode::SetGlobal => {
Some(self.b as u32 | (self.c as u32) << 8)
}
Opcode::New => Some(self.imm),
_ => None,
}
}
#[inline]
pub const fn has_target(self) -> bool {
matches!(
self.op.format(),
Format::Jump | Format::AJump | Format::ABJump | Format::AShortJump
)
}
}
impl fmt::Debug for Insn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} a={} b={} c={} imm={:#x}",
self.op.name(),
self.a,
self.b,
self.c,
self.imm
)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CodeUnitId(pub(crate) u32);
impl CodeUnitId {
#[inline]
pub const fn index(self) -> u32 {
self.0
}
}
impl fmt::Debug for CodeUnitId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CodeUnitId({})", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Handler {
pub start: u32,
pub end: u32,
pub target: u32,
pub register: u16,
}
pub mod flags {
pub const STRICT: u16 = 1 << 0;
pub const CONSTRUCTOR: u16 = 1 << 1;
pub const ARROW: u16 = 1 << 2;
pub const SCRIPT: u16 = 1 << 3;
pub const ARGUMENTS: u16 = 1 << 4;
pub const NEW_TARGET: u16 = 1 << 5;
pub const CLASS_CONSTRUCTOR: u16 = 1 << 6;
pub const DERIVED: u16 = 1 << 7;
pub const GENERATOR: u16 = 1 << 8;
pub const ASYNC: u16 = 1 << 9;
pub const PROTOTYPE: u16 = 1 << 10;
pub const MODULE: u16 = 1 << 11;
pub const BRAND: u16 = 1 << 12;
pub const EVAL: u16 = 1 << 13;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Capture {
None,
Register(u16),
Outer(u16),
Slot(u16),
}
pub const NO_SPILL: u16 = u16::MAX;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DebugInfo {
pub name: Box<str>,
pub filename: Option<std::rc::Rc<str>>,
pub positions: Box<[SourcePosition]>,
pub definition: Option<(u32, u32)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SourcePosition {
pub pc: u32,
pub line: u32,
pub column: u32,
}
impl DebugInfo {
pub fn position(&self, pc: u32) -> Option<(u32, u32)> {
let after = self.positions.partition_point(|entry| entry.pc <= pc);
let entry = self.positions[..after].last()?;
Some((entry.line, entry.column))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CodeUnit {
pub code: Box<[Insn]>,
pub handlers: Box<[Handler]>,
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: u16,
pub spill_slots: u16,
pub ic_base: u32,
pub ic_count: u32,
pub children: Box<[CodeUnitId]>,
pub captures: Box<[Capture]>,
pub debug: DebugInfo,
pub name_atom: Option<crate::value::StringId>,
pub length: u16,
pub source: Option<Box<[u8]>>,
pub direct_eval_sites: Box<[u32]>,
pub atoms: Box<[crate::value::StringId]>,
}
impl CodeUnit {
#[inline]
pub const fn temp_base(&self) -> u32 {
if self.spill_register != NO_SPILL {
self.spill_register as u32 + 1
} else {
1 + self.parameter_count as u32 + self.local_count as u32
}
}
#[inline]
pub const fn has(&self, flag: u16) -> bool {
self.flags & flag != 0
}
}
pub(crate) const DYNAMIC: u32 = 1 << 31;
impl CodeUnitId {
#[inline]
pub const fn is_dynamic(self) -> bool {
self.0 & DYNAMIC != 0
}
}
pub enum UnitRef<'a> {
Static(&'a CodeUnit),
Dynamic(std::rc::Rc<CodeUnit>),
}
impl std::ops::Deref for UnitRef<'_> {
type Target = CodeUnit;
#[inline(always)]
fn deref(&self) -> &CodeUnit {
match self {
UnitRef::Static(unit) => unit,
UnitRef::Dynamic(unit) => unit,
}
}
}
#[derive(Default)]
pub struct CodeStore {
units: Vec<CodeUnit>,
}
impl CodeStore {
pub fn publish(&mut self, unit: CodeUnit) -> Result<CodeUnitId, ValidateError> {
validate::validate(&unit, &|id| self.lookup(id))?;
Ok(self.push(unit))
}
pub(crate) fn push(&mut self, unit: CodeUnit) -> CodeUnitId {
let id = u32::try_from(self.units.len())
.ok()
.filter(|&id| id < DYNAMIC)
.expect("code store overflow");
self.units.push(unit);
CodeUnitId(id)
}
pub fn lookup(&self, id: CodeUnitId) -> Option<&CodeUnit> {
if id.is_dynamic() {
return None;
}
self.units.get(id.0 as usize)
}
#[inline]
pub fn get(&self, id: CodeUnitId) -> &CodeUnit {
debug_assert!(!id.is_dynamic(), "a unit of the code store");
&self.units[id.0 as usize]
}
pub fn len(&self) -> usize {
self.units.len()
}
pub fn units(&self) -> impl Iterator<Item = (CodeUnitId, &CodeUnit)> {
self.units
.iter()
.enumerate()
.map(|(index, unit)| (CodeUnitId(index as u32), unit))
}
pub fn is_empty(&self) -> bool {
self.units.is_empty()
}
pub fn tree(&self, id: CodeUnitId) -> Vec<CodeUnitId> {
tree(id, |id| &self.get(id).children[..])
}
}
fn tree<'a>(id: CodeUnitId, children: impl Fn(CodeUnitId) -> &'a [CodeUnitId]) -> Vec<CodeUnitId> {
let mut order = Vec::new();
let mut pending = vec![id];
while let Some(id) = pending.pop() {
order.push(id);
pending.extend(children(id).iter().rev());
}
order
}
#[derive(Clone, Copy)]
pub struct Units<'a> {
pub(crate) code: &'a CodeStore,
pub(crate) dynamic: &'a crate::heap::code::DynamicUnits,
}
impl<'a> Units<'a> {
#[inline]
pub fn get(self, id: CodeUnitId) -> UnitRef<'a> {
if id.is_dynamic() {
return UnitRef::Dynamic(self.dynamic.get(id));
}
UnitRef::Static(self.code.get(id))
}
pub fn lookup(self, id: CodeUnitId) -> Option<&'a CodeUnit> {
if id.is_dynamic() {
return self.dynamic.lookup(id);
}
self.code.lookup(id)
}
pub fn tree(self, id: CodeUnitId) -> Vec<CodeUnitId> {
tree(id, |id| {
&self.lookup(id).expect("a published unit").children[..]
})
}
pub fn all(self) -> impl Iterator<Item = (CodeUnitId, &'a CodeUnit)> {
self.code.units().chain(self.dynamic.units())
}
}
impl CodeUnit {
pub(crate) fn key_atoms(&self) -> Box<[crate::value::StringId]> {
use crate::heap::atom::KeyKind;
let mut atoms: Vec<crate::value::StringId> = self
.code
.iter()
.filter(|insn| matches!(insn.op.format(), Format::Key | Format::AKey | Format::ABKey))
.filter_map(|insn| match insn.key().kind() {
KeyKind::String(id) => Some(id),
_ => None,
})
.chain(self.name_atom)
.collect();
atoms.sort_unstable();
atoms.dedup();
atoms.into()
}
pub fn bytes(&self) -> usize {
size_of::<CodeUnit>()
+ std::mem::size_of_val(&*self.code)
+ std::mem::size_of_val(&*self.handlers)
+ std::mem::size_of_val(&*self.children)
+ std::mem::size_of_val(&*self.captures)
+ std::mem::size_of_val(&*self.debug.positions)
+ self.debug.name.len()
+ self.source.as_ref().map_or(0, |source| source.len())
+ std::mem::size_of_val(&*self.direct_eval_sites)
+ std::mem::size_of_val(&*self.atoms)
+ self.ic_count as usize * crate::vm::ic::ENTRY_BYTES
}
}