#![cfg_attr(not(test), deny(clippy::unwrap_used))]
#![cfg_attr(not(test), deny(clippy::float_cmp))]
#![warn(missing_docs)]
mod compiler;
mod host;
mod instr;
mod lua_std;
#[doc(hidden)]
mod patterns;
mod vm;
mod vm_aux;
pub mod error;
pub use host::{DefaultCallbacks, HostCallbacks};
pub use instr::{ArgCount, RetCount};
pub use vm::LuaType;
pub use vm::RustFunc;
pub use vm::State;
use compiler::Chunk;
use instr::Instr;
pub type Result<T> = std::result::Result<T, error::Error>;
#[derive(Debug, Default, Clone)]
pub struct ScopeCost {
pub name: String,
pub own_cost: u64,
pub total_cost: u64,
pub arithmetic_ops: u64,
pub negations: u64,
pub table_creations: u64,
pub table_writes: u64,
pub array_elements: u64,
pub function_calls: u64,
pub instructions: u64,
pub nested: Vec<ScopeCost>,
}
impl ScopeCost {
fn analyze_chunk(chunk: &Chunk, name: String) -> Self {
let mut scope = ScopeCost {
name,
..Default::default()
};
for inst in &chunk.code {
scope.instructions += 1;
match inst.opcode() {
Instr::OP_ADD
| Instr::OP_SUBTRACT
| Instr::OP_MULTIPLY
| Instr::OP_DIVIDE
| Instr::OP_POW
| Instr::OP_MOD => {
scope.arithmetic_ops += 1;
scope.own_cost += 1;
}
Instr::OP_NEGATE => {
scope.negations += 1;
scope.own_cost += 1;
}
Instr::OP_NEW_TABLE => {
scope.table_creations += 1;
scope.own_cost += 1;
}
Instr::OP_INIT_FIELD
| Instr::OP_INIT_INDEX
| Instr::OP_SET_FIELD
| Instr::OP_SET_TABLE => {
scope.table_writes += 1;
scope.own_cost += 1;
}
Instr::OP_SET_LIST => {
let n = inst.a();
if n == 0 {
scope.own_cost += 1;
} else {
scope.array_elements += n as u64;
scope.own_cost += n as u64;
}
}
Instr::OP_CALL => {
scope.function_calls += 1;
}
_ => {}
}
}
for (i, nested_chunk) in chunk.nested.iter().enumerate() {
let nested_name = match &nested_chunk.name {
Some(name) => name.clone(),
None => format!("anonymous #{}", i + 1),
};
let nested_scope = Self::analyze_chunk(nested_chunk, nested_name);
scope.nested.push(nested_scope);
}
scope.total_cost = scope.own_cost + scope.nested.iter().map(|n| n.total_cost).sum::<u64>();
scope
}
fn fmt_indent(&self, f: &mut std::fmt::Formatter<'_>, indent: usize) -> std::fmt::Result {
let pad = " ".repeat(indent);
if self.own_cost == 0 && self.nested.is_empty() {
writeln!(f, "{}{}: cost 0 (free)", pad, self.name)?;
return Ok(());
}
if self.nested.is_empty() {
writeln!(f, "{}{}: cost {}", pad, self.name, self.own_cost)?;
} else {
writeln!(
f,
"{}{}: cost {} (own) / {} (total)",
pad, self.name, self.own_cost, self.total_cost
)?;
}
if self.own_cost > 0 {
let inner_pad = " ".repeat(indent + 1);
if self.arithmetic_ops > 0 {
writeln!(f, "{}arithmetic: {}", inner_pad, self.arithmetic_ops)?;
}
if self.negations > 0 {
writeln!(f, "{}negation: {}", inner_pad, self.negations)?;
}
if self.table_creations > 0 {
writeln!(f, "{}table creation: {}", inner_pad, self.table_creations)?;
}
if self.table_writes > 0 {
writeln!(f, "{}table writes: {}", inner_pad, self.table_writes)?;
}
if self.array_elements > 0 {
writeln!(f, "{}array elements: {}", inner_pad, self.array_elements)?;
}
}
for nested in &self.nested {
nested.fmt_indent(f, indent + 1)?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct CostAnalysis {
pub root: ScopeCost,
}
impl CostAnalysis {
fn totals(&self) -> ScopeTotals {
let mut totals = ScopeTotals::default();
self.root.accumulate(&mut totals);
totals
}
}
#[derive(Default)]
struct ScopeTotals {
total_cost: u64,
arithmetic_ops: u64,
negations: u64,
table_creations: u64,
table_writes: u64,
array_elements: u64,
function_calls: u64,
instructions: u64,
function_count: u64,
}
impl ScopeCost {
fn accumulate(&self, totals: &mut ScopeTotals) {
totals.total_cost += self.own_cost;
totals.arithmetic_ops += self.arithmetic_ops;
totals.negations += self.negations;
totals.table_creations += self.table_creations;
totals.table_writes += self.table_writes;
totals.array_elements += self.array_elements;
totals.function_calls += self.function_calls;
totals.instructions += self.instructions;
totals.function_count += self.nested.len() as u64;
for nested in &self.nested {
nested.accumulate(totals);
}
}
}
impl std::fmt::Display for CostAnalysis {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let totals = self.totals();
writeln!(f, "=== Cost Analysis ===")?;
writeln!(f)?;
writeln!(f, "Minimum cost (static): {}", totals.total_cost)?;
writeln!(f)?;
writeln!(f, "--- Costed Operations ---")?;
if totals.arithmetic_ops > 0 {
writeln!(
f,
" Arithmetic (+,-,*,/,%,^): {} ops",
totals.arithmetic_ops
)?;
}
if totals.negations > 0 {
writeln!(f, " Unary negation (-): {} ops", totals.negations)?;
}
if totals.table_creations > 0 {
writeln!(
f,
" Table creation {{}}: {} ops",
totals.table_creations
)?;
}
if totals.table_writes > 0 {
writeln!(f, " Table writes (t[k]=v): {} ops", totals.table_writes)?;
}
if totals.array_elements > 0 {
writeln!(
f,
" Array elements: {} elements",
totals.array_elements
)?;
}
writeln!(f)?;
writeln!(f, "--- Statistics ---")?;
writeln!(f, " Total instructions: {}", totals.instructions)?;
writeln!(f, " Function definitions: {}", totals.function_count)?;
writeln!(f, " Function calls: {}", totals.function_calls)?;
writeln!(f)?;
writeln!(f, "--- Per-Scope Breakdown ---")?;
self.root.fmt_indent(f, 0)?;
Ok(())
}
}
pub fn analyze_cost(source: &str) -> Result<CostAnalysis> {
let chunk = compiler::parse_str(source)?;
let root = ScopeCost::analyze_chunk(&chunk, "main".to_string());
Ok(CostAnalysis { root })
}