use super::instruction::{Instruction, OpCode, Operand, Bytecode};
use crate::diagnostics::Result;
use std::time::Instant;
pub struct BytecodeOptimizer {
config: OptimizationConfig,
stats: OptimizationStats,
}
#[derive(Debug, Clone)]
pub struct OptimizationConfig {
pub constant_folding: bool,
pub dead_code_elimination: bool,
pub tail_call_optimization: bool,
pub instruction_combining: bool,
pub register_allocation: bool,
pub max_passes: usize,
}
impl Default for OptimizationConfig {
fn default() -> Self {
Self {
constant_folding: true,
dead_code_elimination: true,
tail_call_optimization: true,
instruction_combining: true,
register_allocation: false,
max_passes: 3,
}
}
}
#[derive(Debug, Clone)]
pub struct OptimizationStats {
pub passes_applied: usize,
pub instructions_before: usize,
pub instructions_after: usize,
pub instructions_eliminated: usize,
pub optimization_time_us: u64,
pub memory_saved_bytes: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizationPass {
ConstantFolding,
DeadCodeElimination,
TailCallOptimization,
InstructionCombining,
RegisterAllocation,
}
impl BytecodeOptimizer {
pub fn new() -> Self {
Self {
config: OptimizationConfig::default(),
stats: OptimizationStats {
passes_applied: 0,
instructions_before: 0,
instructions_after: 0,
instructions_eliminated: 0,
optimization_time_us: 0,
memory_saved_bytes: 0,
},
}
}
pub fn with_config(config: OptimizationConfig) -> Self {
Self {
config,
stats: OptimizationStats {
passes_applied: 0,
instructions_before: 0,
instructions_after: 0,
instructions_eliminated: 0,
optimization_time_us: 0,
memory_saved_bytes: 0,
},
}
}
pub fn optimize(&mut self, mut bytecode: Bytecode) -> Result<Bytecode> {
let start_time = Instant::now();
let initial_instruction_count = bytecode.instructions.len();
self.stats.instructions_before = initial_instruction_count;
let mut changed = true;
let mut pass_count = 0;
while changed && pass_count < self.config.max_passes {
changed = false;
if self.config.constant_folding && self.apply_constant_folding(&mut bytecode)? {
changed = true;
}
if self.config.dead_code_elimination && self.apply_dead_code_elimination(&mut bytecode)? {
changed = true;
}
if self.config.instruction_combining && self.apply_instruction_combining(&mut bytecode)? {
changed = true;
}
if self.config.tail_call_optimization && self.apply_tail_call_optimization(&mut bytecode)? {
changed = true;
}
if self.config.register_allocation && self.apply_register_allocation(&mut bytecode)? {
changed = true;
}
pass_count += 1;
}
self.stats.passes_applied += pass_count;
self.stats.instructions_after = bytecode.instructions.len();
self.stats.instructions_eliminated = initial_instruction_count.saturating_sub(bytecode.instructions.len());
self.stats.optimization_time_us += start_time.elapsed().as_micros() as u64;
self.stats.memory_saved_bytes = self.stats.instructions_eliminated * 16;
Ok(bytecode)
}
fn apply_constant_folding(&self, bytecode: &mut Bytecode) -> Result<bool> {
let mut changed = false;
let mut new_instructions = Vec::new();
let mut i = 0;
while i < bytecode.instructions.len() {
if i + 2 < bytecode.instructions.len() {
if let (
Instruction { opcode: OpCode::LoadConst, operand: Operand::ConstIndex(idx1), .. },
Instruction { opcode: OpCode::LoadConst, operand: Operand::ConstIndex(idx2), .. },
Instruction { opcode: OpCode::Add, .. }
) = (&bytecode.instructions[i], &bytecode.instructions[i + 1], &bytecode.instructions[i + 2]) {
if let (Some(const1), Some(const2)) = (
bytecode.constants.get_constant(*idx1),
bytecode.constants.get_constant(*idx2)
) {
if let (Some(n1), Some(n2)) = (self.constant_to_number(const1), self.constant_to_number(const2)) {
let result = n1 + n2;
let result_const = super::instruction::ConstantValue::Number(result);
let result_idx = bytecode.constants.add_constant(result_const);
new_instructions.push(Instruction::with_operand(OpCode::LoadConst, Operand::ConstIndex(result_idx)));
i += 3; changed = true;
continue;
}
}
}
}
new_instructions.push(bytecode.instructions[i].clone());
i += 1;
}
if changed {
bytecode.instructions = new_instructions;
}
Ok(changed)
}
fn apply_dead_code_elimination(&self, bytecode: &mut Bytecode) -> Result<bool> {
let mut changed = false;
let mut reachable = vec![false; bytecode.instructions.len()];
let mut worklist = vec![bytecode.entry_point];
while let Some(index) = worklist.pop() {
if index >= bytecode.instructions.len() || reachable[index] {
continue;
}
reachable[index] = true;
match &bytecode.instructions[index] {
Instruction { opcode: OpCode::Jump, operand: Operand::JumpOffset(offset), .. } => {
let target = (index as i32 + offset) as usize;
if target < bytecode.instructions.len() {
worklist.push(target);
}
}
Instruction { opcode: OpCode::JumpIfTrue | OpCode::JumpIfFalse, operand: Operand::JumpOffset(offset), .. } => {
let target = (index as i32 + offset) as usize;
if target < bytecode.instructions.len() {
worklist.push(target);
}
worklist.push(index + 1);
}
Instruction { opcode: OpCode::Return | OpCode::Halt, .. } => {
}
_ => {
worklist.push(index + 1);
}
}
}
let original_len = bytecode.instructions.len();
let instructions = std::mem::take(&mut bytecode.instructions);
bytecode.instructions = instructions.into_iter()
.enumerate()
.filter(|(i, _)| reachable[*i])
.map(|(_, inst)| inst)
.collect();
if bytecode.instructions.len() < original_len {
changed = true;
}
Ok(changed)
}
fn apply_instruction_combining(&self, bytecode: &mut Bytecode) -> Result<bool> {
let mut changed = false;
let mut new_instructions = Vec::new();
let mut i = 0;
while i < bytecode.instructions.len() {
if i + 1 < bytecode.instructions.len() {
if let (
Instruction { opcode: OpCode::Pop, .. },
Instruction { opcode: OpCode::LoadConst, .. }
) = (&bytecode.instructions[i], &bytecode.instructions[i + 1]) {
new_instructions.push(bytecode.instructions[i + 1].clone());
i += 2;
changed = true;
continue;
}
}
if i + 1 < bytecode.instructions.len() {
if let (
Instruction { opcode: OpCode::Dup, .. },
Instruction { opcode: OpCode::Pop, .. }
) = (&bytecode.instructions[i], &bytecode.instructions[i + 1]) {
i += 2;
changed = true;
continue;
}
}
new_instructions.push(bytecode.instructions[i].clone());
i += 1;
}
if changed {
bytecode.instructions = new_instructions;
}
Ok(changed)
}
fn apply_tail_call_optimization(&self, bytecode: &mut Bytecode) -> Result<bool> {
let mut changed = false;
for i in 0..bytecode.instructions.len().saturating_sub(1) {
if let (
Instruction { opcode: OpCode::Call, operand, .. },
Instruction { opcode: OpCode::Return, .. }
) = (&bytecode.instructions[i], &bytecode.instructions[i + 1]) {
bytecode.instructions[i] = Instruction::with_operand(OpCode::TailCall, operand.clone());
bytecode.instructions.remove(i + 1);
changed = true;
break; }
}
Ok(changed)
}
fn apply_register_allocation(&self, _bytecode: &mut Bytecode) -> Result<bool> {
Ok(false)
}
fn constant_to_number(&self, constant: &super::instruction::ConstantValue) -> Option<f64> {
match constant {
super::instruction::ConstantValue::Number(n) => Some(*n),
_ => None,
}
}
pub fn configure(&mut self, config: OptimizationConfig) {
self.config = config;
}
pub fn get_stats(&self) -> OptimizationStats {
self.stats.clone()
}
pub fn reset_stats(&mut self) {
self.stats = OptimizationStats {
passes_applied: 0,
instructions_before: 0,
instructions_after: 0,
instructions_eliminated: 0,
optimization_time_us: 0,
memory_saved_bytes: 0,
};
}
pub fn generate_report(&self) -> String {
let stats = &self.stats;
let mut report = String::new();
report.push_str("=== Bytecode Optimization Report ===\n");
report.push_str(&format!("Optimization passes applied: {}\n", stats.passes_applied));
report.push_str(&format!("Instructions before: {}\n", stats.instructions_before));
report.push_str(&format!("Instructions after: {}\n", stats.instructions_after));
report.push_str(&format!("Instructions eliminated: {}\n", stats.instructions_eliminated));
if stats.instructions_before > 0 {
let reduction_percent = (stats.instructions_eliminated as f64 / stats.instructions_before as f64) * 100.0;
report.push_str(&format!("Code size reduction: {reduction_percent:.1}%\n"));
}
report.push_str(&format!("Optimization time: {:.2} ms\n", stats.optimization_time_us as f64 / 1000.0));
report.push_str(&format!("Estimated memory saved: {} bytes\n", stats.memory_saved_bytes));
report.push_str("\n=== Recommendations ===\n");
if stats.instructions_eliminated == 0 {
report.push_str("• No optimizations applied - consider enabling more optimization passes\n");
}
if stats.optimization_time_us > 10000 {
report.push_str("• High optimization time - consider reducing max_passes for faster compilation\n");
}
report
}
}
impl Default for BytecodeOptimizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::instruction::{Instruction, OpCode, Operand, ConstantPool, ConstantValue, Bytecode};
#[test]
fn test_optimizer_creation() {
let optimizer = BytecodeOptimizer::new();
assert!(optimizer.config.constant_folding);
assert!(optimizer.config.dead_code_elimination);
}
#[test]
fn test_constant_folding() {
let mut optimizer = BytecodeOptimizer::new();
let mut bytecode = Bytecode::new();
bytecode.constants.add_constant(ConstantValue::Number(5.0));
bytecode.constants.add_constant(ConstantValue::Number(3.0));
bytecode.add_instruction(Instruction::with_operand(OpCode::LoadConst, Operand::ConstIndex(0)));
bytecode.add_instruction(Instruction::with_operand(OpCode::LoadConst, Operand::ConstIndex(1)));
bytecode.add_instruction(Instruction::new(OpCode::Add));
let original_len = bytecode.instructions.len();
let optimized = optimizer.optimize(bytecode).unwrap();
assert!(optimized.instructions.len() <= original_len);
let stats = optimizer.get_stats();
assert!(stats.passes_applied > 0);
}
#[test]
fn test_dead_code_elimination() {
let mut optimizer = BytecodeOptimizer::with_config(OptimizationConfig {
constant_folding: false,
dead_code_elimination: true,
tail_call_optimization: false,
instruction_combining: false,
register_allocation: false,
max_passes: 1,
});
let mut bytecode = Bytecode::new();
bytecode.add_instruction(Instruction::new(OpCode::Halt)); bytecode.add_instruction(Instruction::new(OpCode::LoadConst)); bytecode.add_instruction(Instruction::new(OpCode::Add));
let original_len = bytecode.instructions.len();
let optimized = optimizer.optimize(bytecode).unwrap();
assert!(optimized.instructions.len() < original_len);
let stats = optimizer.get_stats();
assert!(stats.instructions_eliminated > 0);
}
#[test]
fn test_instruction_combining() {
let mut optimizer = BytecodeOptimizer::with_config(OptimizationConfig {
constant_folding: false,
dead_code_elimination: false,
tail_call_optimization: false,
instruction_combining: true,
register_allocation: false,
max_passes: 1,
});
let mut bytecode = Bytecode::new();
bytecode.add_instruction(Instruction::new(OpCode::Dup));
bytecode.add_instruction(Instruction::new(OpCode::Pop));
bytecode.add_instruction(Instruction::new(OpCode::Halt));
let original_len = bytecode.instructions.len();
let optimized = optimizer.optimize(bytecode).unwrap();
assert!(optimized.instructions.len() < original_len);
}
#[test]
fn test_optimization_report() {
let mut optimizer = BytecodeOptimizer::new();
let mut bytecode = Bytecode::new();
bytecode.add_instruction(Instruction::new(OpCode::Halt));
let _optimized = optimizer.optimize(bytecode).unwrap();
let report = optimizer.generate_report();
assert!(report.contains("Bytecode Optimization Report"));
assert!(report.contains("Instructions before"));
assert!(report.contains("Instructions after"));
}
}