Skip to main content

boa_engine/optimizer/
mod.rs

1pub(crate) mod pass;
2pub(crate) mod walker;
3
4use self::{
5    pass::{ConstantFolding, DeadCodeElimination, StrengthReduction},
6    walker::Walker,
7};
8use crate::Context;
9use bitflags::bitflags;
10use boa_ast::{
11    Expression, StatementList,
12    statement::Statement,
13    visitor::{VisitWith, VisitorMut},
14};
15use std::{fmt, ops::ControlFlow};
16
17bitflags! {
18    /// Optimizer options.
19    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20    pub struct OptimizerOptions: u8 {
21        /// Print statistics to `stdout`.
22        const STATISTICS = 0b0000_0001;
23        /// Apply constant folding optimization.
24        const CONSTANT_FOLDING = 0b0000_0010;
25        /// Apply strength reduction.
26        const STRENGTH_REDUCTION = 0b0000_0100;
27        /// Apply dead code elimination.
28        const DEAD_CODE_ELIMINATION = 0b0000_1000;
29        /// Apply all optimizations.
30        const OPTIMIZE_ALL = Self::CONSTANT_FOLDING.bits()
31            | Self::STRENGTH_REDUCTION.bits()
32            | Self::DEAD_CODE_ELIMINATION.bits();
33    }
34}
35
36/// The action to be performed after an optimization step.
37#[derive(Debug)]
38pub(crate) enum PassAction<T> {
39    /// Keep the node, do nothing.
40    Keep,
41    /// The node was modified inplace.
42    Modified,
43    /// Replace the node.
44    Replace(T),
45}
46
47/// Contains statistics about the optimizer execution.
48#[derive(Debug, Default, Clone, Copy)]
49pub struct OptimizerStatistics {
50    /// Constant folding run count.
51    pub constant_folding_run_count: usize,
52    /// Constant folding pass count.
53    pub constant_folding_pass_count: usize,
54    /// Strength reduction run count.
55    pub strength_reduction_run_count: usize,
56    /// Strength reduction pass count.
57    pub strength_reduction_pass_count: usize,
58    /// Dead code elimination count.
59    pub dead_code_elimination_count: usize,
60}
61
62impl fmt::Display for OptimizerStatistics {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        writeln!(f, "Optimizer {{")?;
65        writeln!(
66            f,
67            "    constant folding: {} run(s), {} pass(es) ({} mutating, {} checking)",
68            self.constant_folding_run_count,
69            self.constant_folding_pass_count,
70            self.constant_folding_pass_count
71                .saturating_sub(self.constant_folding_run_count),
72            self.constant_folding_run_count
73        )?;
74        writeln!(
75            f,
76            "    strength reduction: {} run(s), {} pass(es)",
77            self.strength_reduction_run_count, self.strength_reduction_pass_count,
78        )?;
79        writeln!(
80            f,
81            "    dead code elimination: {} elimination(s)",
82            self.dead_code_elimination_count,
83        )?;
84        writeln!(f, "}}")?;
85        Ok(())
86    }
87}
88
89/// This represents an AST optimizer.
90#[derive(Debug)]
91pub(crate) struct Optimizer<'context> {
92    statistics: OptimizerStatistics,
93    context: &'context mut Context,
94}
95
96impl<'context> Optimizer<'context> {
97    /// Create a optimizer.
98    pub(crate) fn new(context: &'context mut Context) -> Self {
99        Self {
100            statistics: OptimizerStatistics::default(),
101            context,
102        }
103    }
104
105    /// Run the constant folding optimization on an expression.
106    fn run_constant_folding_pass(&mut self, expr: &mut Expression) -> bool {
107        self.statistics.constant_folding_run_count += 1;
108
109        let mut has_changes = false;
110        for _ in 0..Self::MAX_PASS_ITERATIONS {
111            self.statistics.constant_folding_pass_count += 1;
112            let mut walker = Walker::new(|expr| -> PassAction<Expression> {
113                ConstantFolding::fold_expression(expr, self.context)
114            });
115            // NOTE: postoder traversal is optimal for constant folding,
116            // since it evaluates the tree bottom-up.
117            walker.walk_expression_postorder(expr);
118            if !walker.changed() {
119                break;
120            }
121            has_changes = true;
122        }
123        has_changes
124    }
125
126    /// Maximum number of iterations for optimization passes.
127    /// This prevents infinite loops if a pass has a bug that keeps producing changes.
128    const MAX_PASS_ITERATIONS: usize = 10;
129
130    /// Run the strength reduction optimization on an expression.
131    fn run_strength_reduction_pass(&mut self, expr: &mut Expression) -> bool {
132        self.statistics.strength_reduction_run_count += 1;
133
134        let mut has_changes = false;
135        for _ in 0..Self::MAX_PASS_ITERATIONS {
136            self.statistics.strength_reduction_pass_count += 1;
137            let mut walker = Walker::new(|expr| -> PassAction<Expression> {
138                StrengthReduction::reduce_expression(expr)
139            });
140            walker.walk_expression_postorder(expr);
141            if !walker.changed() {
142                break;
143            }
144            has_changes = true;
145        }
146        has_changes
147    }
148
149    fn run_all(&mut self, expr: &mut Expression) {
150        if self
151            .context
152            .optimizer_options()
153            .contains(OptimizerOptions::CONSTANT_FOLDING)
154        {
155            self.run_constant_folding_pass(expr);
156        }
157        if self
158            .context
159            .optimizer_options()
160            .contains(OptimizerOptions::STRENGTH_REDUCTION)
161        {
162            self.run_strength_reduction_pass(expr);
163        }
164    }
165
166    /// Apply optimizations inplace.
167    pub(crate) fn apply(&mut self, statement_list: &mut StatementList) -> OptimizerStatistics {
168        let _ = self.visit_statement_list_mut(statement_list);
169
170        #[allow(clippy::print_stdout)]
171        if self
172            .context
173            .optimizer_options()
174            .contains(OptimizerOptions::STATISTICS)
175        {
176            println!("{}", self.statistics);
177        }
178        self.statistics
179    }
180}
181
182impl<'ast> VisitorMut<'ast> for Optimizer<'_> {
183    type BreakTy = ();
184
185    fn visit_expression_mut(&mut self, node: &'ast mut Expression) -> ControlFlow<Self::BreakTy> {
186        self.run_all(node);
187        ControlFlow::Continue(())
188    }
189
190    fn visit_statement_mut(&mut self, node: &'ast mut Statement) -> ControlFlow<Self::BreakTy> {
191        // First, recurse into children so constant folding and
192        // strength reduction run on nested expressions.
193        node.visit_with_mut(self)?;
194
195        // Then apply dead code elimination if enabled.
196        if self
197            .context
198            .optimizer_options()
199            .contains(OptimizerOptions::DEAD_CODE_ELIMINATION)
200        {
201            match node {
202                Statement::If(if_stmt) => {
203                    if let PassAction::Replace(replacement) =
204                        DeadCodeElimination::try_eliminate_if(if_stmt)
205                    {
206                        *node = replacement;
207                        self.statistics.dead_code_elimination_count += 1;
208                    }
209                }
210                Statement::WhileLoop(while_loop) => {
211                    if let PassAction::Replace(replacement) =
212                        DeadCodeElimination::try_eliminate_while(while_loop)
213                    {
214                        *node = replacement;
215                        self.statistics.dead_code_elimination_count += 1;
216                    }
217                }
218                Statement::ForLoop(for_loop) => {
219                    if let PassAction::Replace(replacement) =
220                        DeadCodeElimination::try_eliminate_for(for_loop)
221                    {
222                        *node = replacement;
223                        self.statistics.dead_code_elimination_count += 1;
224                    }
225                }
226                _ => {}
227            }
228        }
229
230        ControlFlow::Continue(())
231    }
232}