pub(crate) mod pass;
pub(crate) mod walker;
use self::{
pass::{ConstantFolding, DeadCodeElimination, StrengthReduction},
walker::Walker,
};
use crate::Context;
use bitflags::bitflags;
use boa_ast::{
Expression, StatementList,
statement::Statement,
visitor::{VisitWith, VisitorMut},
};
use std::{fmt, ops::ControlFlow};
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct OptimizerOptions: u8 {
const STATISTICS = 0b0000_0001;
const CONSTANT_FOLDING = 0b0000_0010;
const STRENGTH_REDUCTION = 0b0000_0100;
const DEAD_CODE_ELIMINATION = 0b0000_1000;
const OPTIMIZE_ALL = Self::CONSTANT_FOLDING.bits()
| Self::STRENGTH_REDUCTION.bits()
| Self::DEAD_CODE_ELIMINATION.bits();
}
}
#[derive(Debug)]
pub(crate) enum PassAction<T> {
Keep,
Modified,
Replace(T),
}
#[derive(Debug, Default, Clone, Copy)]
pub struct OptimizerStatistics {
pub constant_folding_run_count: usize,
pub constant_folding_pass_count: usize,
pub strength_reduction_run_count: usize,
pub strength_reduction_pass_count: usize,
pub dead_code_elimination_count: usize,
}
impl fmt::Display for OptimizerStatistics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Optimizer {{")?;
writeln!(
f,
" constant folding: {} run(s), {} pass(es) ({} mutating, {} checking)",
self.constant_folding_run_count,
self.constant_folding_pass_count,
self.constant_folding_pass_count
.saturating_sub(self.constant_folding_run_count),
self.constant_folding_run_count
)?;
writeln!(
f,
" strength reduction: {} run(s), {} pass(es)",
self.strength_reduction_run_count, self.strength_reduction_pass_count,
)?;
writeln!(
f,
" dead code elimination: {} elimination(s)",
self.dead_code_elimination_count,
)?;
writeln!(f, "}}")?;
Ok(())
}
}
#[derive(Debug)]
pub(crate) struct Optimizer<'context> {
statistics: OptimizerStatistics,
context: &'context mut Context,
}
impl<'context> Optimizer<'context> {
pub(crate) fn new(context: &'context mut Context) -> Self {
Self {
statistics: OptimizerStatistics::default(),
context,
}
}
fn run_constant_folding_pass(&mut self, expr: &mut Expression) -> bool {
self.statistics.constant_folding_run_count += 1;
let mut has_changes = false;
for _ in 0..Self::MAX_PASS_ITERATIONS {
self.statistics.constant_folding_pass_count += 1;
let mut walker = Walker::new(|expr| -> PassAction<Expression> {
ConstantFolding::fold_expression(expr, self.context)
});
walker.walk_expression_postorder(expr);
if !walker.changed() {
break;
}
has_changes = true;
}
has_changes
}
const MAX_PASS_ITERATIONS: usize = 10;
fn run_strength_reduction_pass(&mut self, expr: &mut Expression) -> bool {
self.statistics.strength_reduction_run_count += 1;
let mut has_changes = false;
for _ in 0..Self::MAX_PASS_ITERATIONS {
self.statistics.strength_reduction_pass_count += 1;
let mut walker = Walker::new(|expr| -> PassAction<Expression> {
StrengthReduction::reduce_expression(expr)
});
walker.walk_expression_postorder(expr);
if !walker.changed() {
break;
}
has_changes = true;
}
has_changes
}
fn run_all(&mut self, expr: &mut Expression) {
if self
.context
.optimizer_options()
.contains(OptimizerOptions::CONSTANT_FOLDING)
{
self.run_constant_folding_pass(expr);
}
if self
.context
.optimizer_options()
.contains(OptimizerOptions::STRENGTH_REDUCTION)
{
self.run_strength_reduction_pass(expr);
}
}
pub(crate) fn apply(&mut self, statement_list: &mut StatementList) -> OptimizerStatistics {
let _ = self.visit_statement_list_mut(statement_list);
#[allow(clippy::print_stdout)]
if self
.context
.optimizer_options()
.contains(OptimizerOptions::STATISTICS)
{
println!("{}", self.statistics);
}
self.statistics
}
}
impl<'ast> VisitorMut<'ast> for Optimizer<'_> {
type BreakTy = ();
fn visit_expression_mut(&mut self, node: &'ast mut Expression) -> ControlFlow<Self::BreakTy> {
self.run_all(node);
ControlFlow::Continue(())
}
fn visit_statement_mut(&mut self, node: &'ast mut Statement) -> ControlFlow<Self::BreakTy> {
node.visit_with_mut(self)?;
if self
.context
.optimizer_options()
.contains(OptimizerOptions::DEAD_CODE_ELIMINATION)
{
match node {
Statement::If(if_stmt) => {
if let PassAction::Replace(replacement) =
DeadCodeElimination::try_eliminate_if(if_stmt)
{
*node = replacement;
self.statistics.dead_code_elimination_count += 1;
}
}
Statement::WhileLoop(while_loop) => {
if let PassAction::Replace(replacement) =
DeadCodeElimination::try_eliminate_while(while_loop)
{
*node = replacement;
self.statistics.dead_code_elimination_count += 1;
}
}
Statement::ForLoop(for_loop) => {
if let PassAction::Replace(replacement) =
DeadCodeElimination::try_eliminate_for(for_loop)
{
*node = replacement;
self.statistics.dead_code_elimination_count += 1;
}
}
_ => {}
}
}
ControlFlow::Continue(())
}
}