mod exp_desc;
mod lexer;
mod parser;
mod token;
use std::cell::Cell;
use std::sync::Arc;
use super::Instr;
use super::Result;
use super::error;
use super::vm::{ObjectPtr, Val};
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) enum UpvalueDesc {
Local(u8),
Upvalue(u8),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct GlobalLookupCacheEntry {
pub(super) globals_version: u64,
pub(super) index: usize,
}
#[derive(Debug, Default)]
pub(super) struct GlobalLookupCacheSlot {
entry: Cell<Option<GlobalLookupCacheEntry>>,
}
impl GlobalLookupCacheSlot {
pub(super) fn get(&self) -> Option<GlobalLookupCacheEntry> {
self.entry.get()
}
pub(super) fn set(&self, entry: GlobalLookupCacheEntry) {
self.entry.set(Some(entry));
}
}
impl Clone for GlobalLookupCacheSlot {
fn clone(&self) -> Self {
Self::default()
}
}
impl PartialEq for GlobalLookupCacheSlot {
fn eq(&self, _other: &Self) -> bool {
true
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct FieldLookupCacheEntry {
pub(super) table: ObjectPtr,
pub(super) table_version: u64,
pub(super) index: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct MethodLookupCacheEntry {
pub(super) receiver_metatable: ObjectPtr,
pub(super) index_key: Val,
pub(super) index_field_index: usize,
pub(super) index_handler: Val,
pub(super) method_table_version: u64,
pub(super) method_index: Option<usize>,
pub(super) globals_version: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct StringMethodCacheEntry {
pub(super) string_lib: ObjectPtr,
pub(super) version: u64,
pub(super) index: usize,
pub(super) globals_version: u64,
}
#[derive(Debug, Default)]
pub(super) struct FieldLookupCacheSlot {
field_entry: Cell<Option<FieldLookupCacheEntry>>,
method_entry: Cell<Option<MethodLookupCacheEntry>>,
string_method_entry: Cell<Option<StringMethodCacheEntry>>,
}
impl FieldLookupCacheSlot {
pub(super) fn get_field(&self) -> Option<FieldLookupCacheEntry> {
self.field_entry.get()
}
pub(super) fn set_field(&self, entry: FieldLookupCacheEntry) {
self.field_entry.set(Some(entry));
}
pub(super) fn get_method(&self) -> Option<MethodLookupCacheEntry> {
self.method_entry.get()
}
pub(super) fn set_method(&self, entry: MethodLookupCacheEntry) {
self.method_entry.set(Some(entry));
}
pub(super) fn get_string_method(&self) -> Option<StringMethodCacheEntry> {
self.string_method_entry.get()
}
pub(super) fn set_string_method(&self, entry: StringMethodCacheEntry) {
self.string_method_entry.set(Some(entry));
}
}
impl Clone for FieldLookupCacheSlot {
fn clone(&self) -> Self {
Self::default()
}
}
impl PartialEq for FieldLookupCacheSlot {
fn eq(&self, _other: &Self) -> bool {
true
}
}
#[derive(Debug, Default)]
pub(super) struct SetFieldLookupCacheSlot {
entry: Cell<Option<FieldLookupCacheEntry>>,
}
impl SetFieldLookupCacheSlot {
pub(super) fn get(&self) -> Option<FieldLookupCacheEntry> {
self.entry.get()
}
pub(super) fn set(&self, entry: FieldLookupCacheEntry) {
self.entry.set(Some(entry));
}
}
impl Clone for SetFieldLookupCacheSlot {
fn clone(&self) -> Self {
Self::default()
}
}
impl PartialEq for SetFieldLookupCacheSlot {
fn eq(&self, _other: &Self) -> bool {
true
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct Bytecode {
pub(super) code: Vec<Instr>,
pub(super) number_literals: Vec<f64>,
pub(super) string_literals: Vec<Vec<u8>>,
pub(super) global_cache_slots: u16,
pub(super) field_cache_slots: u16,
pub(super) set_field_cache_slots: u8,
pub(super) num_params: u8,
pub(super) num_locals: u8,
pub(super) nested: Vec<Arc<Bytecode>>,
pub(super) upvalues: Vec<UpvalueDesc>,
pub(super) is_vararg: bool,
pub(super) name: Option<String>,
pub(super) source: Option<String>,
pub(super) line_info: Vec<u32>,
}
impl Bytecode {
fn assign_cache_slots(&mut self) {
let mut global_cache_indices = vec![None; self.string_literals.len()];
let mut global_cache_len = 0usize;
let mut field_cache_len = 0usize;
let mut set_field_cache_len = 0usize;
for inst in &mut self.code {
match inst.opcode() {
Instr::OP_GET_GLOBAL => {
let string_idx = inst.a() as usize;
let Some(cache_idx) = global_cache_indices.get_mut(string_idx) else {
continue;
};
let cache_idx = match *cache_idx {
Some(cache_idx) => cache_idx,
None => {
let next_idx = u16::try_from(global_cache_len)
.expect("too many global lookup cache slots");
*cache_idx = Some(next_idx);
global_cache_len += 1;
next_idx
}
};
*inst = Instr::get_global_cached(inst.a(), cache_idx);
}
Instr::OP_GET_FIELD => {
let cache_idx =
u16::try_from(field_cache_len).expect("too many field lookup cache slots");
field_cache_len += 1;
*inst = Instr::get_field_cached(inst.a(), cache_idx);
}
Instr::OP_SET_FIELD => {
let cache_idx = u8::try_from(set_field_cache_len)
.expect("too many set-field lookup cache slots");
set_field_cache_len += 1;
*inst = Instr::set_field_cached(inst.a(), inst.b(), cache_idx);
}
_ => {}
}
}
self.global_cache_slots =
u16::try_from(global_cache_len).expect("too many global lookup cache slots");
self.field_cache_slots =
u16::try_from(field_cache_len).expect("too many field lookup cache slots");
self.set_field_cache_slots =
u8::try_from(set_field_cache_len).expect("too many set-field lookup cache slots");
}
}
#[derive(Debug, Default)]
pub(crate) struct RuntimeCaches {
pub(super) global_lookup: Vec<GlobalLookupCacheSlot>,
pub(super) field_lookup: Vec<FieldLookupCacheSlot>,
pub(super) set_field_lookup: Vec<SetFieldLookupCacheSlot>,
}
unsafe impl Sync for RuntimeCaches {}
impl RuntimeCaches {
pub(crate) fn new(bc: &Bytecode) -> Self {
Self {
global_lookup: (0..bc.global_cache_slots as usize)
.map(|_| GlobalLookupCacheSlot::default())
.collect(),
field_lookup: (0..bc.field_cache_slots as usize)
.map(|_| FieldLookupCacheSlot::default())
.collect(),
set_field_lookup: (0..bc.set_field_cache_slots as usize)
.map(|_| SetFieldLookupCacheSlot::default())
.collect(),
}
}
}
#[hotpath::measure]
pub(super) fn parse_str(source: impl AsRef<str>) -> Result<Bytecode> {
let mut bc = parser::parse_str(source.as_ref())?;
finalize(&mut bc);
Ok(bc)
}
#[hotpath::measure]
pub(super) fn parse_str_named(
source: impl AsRef<str>,
source_name: Option<String>,
) -> Result<Bytecode> {
let mut bc = parser::parse_str_named(source.as_ref(), source_name)?;
finalize(&mut bc);
Ok(bc)
}
fn finalize(bc: &mut Bytecode) {
bc.assign_cache_slots();
for nested in &mut bc.nested {
let inner =
Arc::get_mut(nested).expect("nested Bytecode should be uniquely owned during finalize");
finalize(inner);
}
}
#[cfg(test)]
mod runtime_cache_tests {
use super::*;
#[test]
fn global_lookup_cache_tracks_distinct_get_global_names_only() {
let bc = parse_str(
r#"
local literal = "not a global"
local t = { field = literal }
foo = foo + foo
bar = bar
"#,
)
.unwrap();
let get_globals: Vec<_> = bc
.code
.iter()
.filter(|inst| inst.opcode() == Instr::OP_GET_GLOBAL)
.collect();
assert_eq!(get_globals.len(), 3);
assert_eq!(bc.global_cache_slots, 2);
assert!(bc.string_literals.len() > bc.global_cache_slots as usize);
assert_eq!(get_globals[0].bx(), get_globals[1].bx());
assert_ne!(get_globals[0].bx(), get_globals[2].bx());
}
#[test]
fn field_lookup_cache_tracks_get_field_call_sites() {
let bc = parse_str(
r#"
local t = { x = 1, y = 2 }
return t.x + t.x + t.y
"#,
)
.unwrap();
let get_fields: Vec<_> = bc
.code
.iter()
.filter(|inst| inst.opcode() == Instr::OP_GET_FIELD)
.collect();
assert_eq!(get_fields.len(), 3);
assert_eq!(bc.field_cache_slots, 3);
assert_eq!(get_fields[0].bx(), 0);
assert_eq!(get_fields[1].bx(), 1);
assert_eq!(get_fields[2].bx(), 2);
}
#[test]
fn set_field_lookup_cache_tracks_set_field_call_sites() {
let bc = parse_str(
r#"
local t = { x = 0, y = 0 }
t.x = 1
t.x = 2
t.y = 3
"#,
)
.unwrap();
let set_fields: Vec<_> = bc
.code
.iter()
.filter(|inst| inst.opcode() == Instr::OP_SET_FIELD)
.collect();
assert_eq!(set_fields.len(), 3);
assert_eq!(bc.set_field_cache_slots, 3);
assert_eq!(set_fields[0].c(), 0);
assert_eq!(set_fields[1].c(), 1);
assert_eq!(set_fields[2].c(), 2);
}
}