use crate::bytecode::instruction::Instruction;
use crate::vm::value::{Value, VmTable};
use num_bigint::BigInt;
use std::cell::{Cell, OnceCell, RefCell};
use std::fmt;
use std::rc::Rc;
#[derive(Clone, Debug, PartialEq)]
pub enum Constant {
Nil,
Bool(bool),
Int(BigInt),
Float(f64),
String(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UpvalueDesc {
pub in_stack: bool,
pub index: u8,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LocalVarInfo {
pub name: String,
pub reg: u8,
pub from_pc: u32,
pub to_pc: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Proto {
pub name: Option<String>,
pub num_params: u8,
pub max_registers: u8,
pub is_vararg: bool,
pub constants: Vec<Constant>,
pub instructions: Vec<Instruction>,
pub protos: Vec<Rc<Proto>>,
pub upvalues: Vec<UpvalueDesc>,
pub lines: Vec<u32>,
pub local_names: Vec<LocalVarInfo>,
pub source: Option<Rc<str>>,
pub upvalue_names: Vec<String>,
pub cache: ProtoCache,
}
#[derive(Default)]
pub struct ProtoCache {
values: OnceCell<Vec<Value>>,
verified: Cell<bool>,
imports: RefCell<Vec<ImportSlot>>,
}
#[derive(Clone)]
pub struct ImportSlot {
pub epoch: u64,
pub module: Option<Rc<RefCell<VmTable>>>,
pub value: Value,
}
impl Default for ImportSlot {
fn default() -> Self {
Self {
epoch: u64::MAX,
module: None,
value: Value::Nil,
}
}
}
impl PartialEq for ProtoCache {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Clone for ProtoCache {
fn clone(&self) -> Self {
ProtoCache::default()
}
}
impl fmt::Debug for ProtoCache {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ProtoCache")
}
}
impl Proto {
pub fn new(name: Option<String>, num_params: u8, is_vararg: bool) -> Self {
Self {
name,
num_params,
max_registers: 0,
is_vararg,
constants: Vec::new(),
instructions: Vec::new(),
protos: Vec::new(),
upvalues: Vec::new(),
lines: Vec::new(),
local_names: Vec::new(),
source: None,
upvalue_names: Vec::new(),
cache: ProtoCache::default(),
}
}
#[inline]
pub fn is_verified(&self) -> bool {
self.cache.verified.get()
}
pub fn mark_verified(&self) {
self.cache.verified.set(true);
for child in &self.protos {
child.mark_verified();
}
}
#[inline]
pub fn values(&self) -> &[Value] {
self.cache.values.get_or_init(|| {
self.constants
.iter()
.map(|c| match c {
Constant::Nil => Value::Nil,
Constant::Bool(b) => Value::Bool(*b),
Constant::Int(i) => Value::from_bigint(i.clone()),
Constant::Float(f) => Value::Float(*f),
Constant::String(s) => Value::str(s),
})
.collect()
})
}
pub fn import_cached(&self, site: usize, epoch: u64) -> Option<Value> {
let slots = self.cache.imports.borrow();
let slot = slots.get(site)?;
if slot.epoch != epoch {
return None;
}
let module = slot.module.as_ref()?;
if module.borrow().metatable.is_some() {
return None;
}
Some(slot.value.clone())
}
pub fn import_store(
&self,
site: usize,
epoch: u64,
module: Rc<RefCell<VmTable>>,
value: Value,
) {
let mut slots = self.cache.imports.borrow_mut();
if slots.len() <= site {
slots.resize(site + 1, ImportSlot::default());
}
slots[site] = ImportSlot {
epoch,
module: Some(module),
value,
};
}
pub fn add_constant(&mut self, c: Constant) -> u16 {
for (i, existing) in self.constants.iter().enumerate() {
if *existing == c {
return i as u16;
}
}
let idx = self.constants.len() as u16;
self.constants.push(c);
idx
}
pub fn emit(&mut self, inst: Instruction, line: u32) -> usize {
let idx = self.instructions.len();
self.instructions.push(inst);
self.lines.push(line);
idx
}
pub fn set_source(&mut self, source: Rc<str>) {
for child in &mut self.protos {
Rc::make_mut(child).set_source(source.clone());
}
self.source = Some(source);
}
pub fn push_local(&mut self, name: String, reg: u8, from_pc: u32) {
self.local_names.push(LocalVarInfo {
name,
reg,
from_pc,
to_pc: u32::MAX,
});
}
pub fn close_local(&mut self, name: &str, to_pc: u32) {
for info in self.local_names.iter_mut().rev() {
if info.name == name && info.to_pc == u32::MAX {
info.to_pc = to_pc;
return;
}
}
}
}