refactor(minifier): use Ctx (#8716)

This commit is contained in:
Boshen 2025-01-25 16:03:18 +00:00
parent 0af0267077
commit bf8be23f11
10 changed files with 134 additions and 181 deletions

View file

@ -1,6 +1,7 @@
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::TraverseCtx;
use crate::ctx::Ctx;
use super::PeepholeOptimizations;
@ -17,7 +18,7 @@ impl<'a> PeepholeOptimizations {
pub fn collapse_variable_declarations(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
self.join_vars(stmts, ctx);
self.maybe_collapse_into_for_statements(stmts, ctx);
@ -44,7 +45,7 @@ impl<'a> PeepholeOptimizations {
None
}
fn join_vars(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
fn join_vars(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: Ctx<'a, '_>) {
if stmts.len() < 2 {
return;
}
@ -102,7 +103,7 @@ impl<'a> PeepholeOptimizations {
fn maybe_collapse_into_for_statements(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
if stmts.len() <= 1 {
return;
@ -142,7 +143,7 @@ impl<'a> PeepholeOptimizations {
&mut self,
i: usize,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
if let Statement::ExpressionStatement(expr_stmt) = ctx.ast.move_statement(&mut stmts[i]) {
if let Statement::ForStatement(for_stmt) = &mut stmts[i + 1] {
@ -156,7 +157,7 @@ impl<'a> PeepholeOptimizations {
&mut self,
i: usize,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
if let Statement::VariableDeclaration(var) = ctx.ast.move_statement(&mut stmts[i]) {
if let Statement::ForStatement(for_stmt) = &mut stmts[i + 1] {
@ -181,7 +182,7 @@ impl<'a> PeepholeOptimizations {
&mut self,
i: usize,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
if let Statement::VariableDeclaration(decl) = &stmts[i] {
if decl.kind.is_var()

View file

@ -1,9 +1,9 @@
use oxc_ast::ast::*;
use oxc_syntax::identifier::is_identifier_name;
use oxc_traverse::TraverseCtx;
use crate::ctx::Ctx;
use super::LatePeepholeOptimizations;
use crate::ctx::Ctx;
impl<'a> LatePeepholeOptimizations {
/// Converts property accesses from quoted string or bracket access syntax to dot or unquoted string
@ -13,10 +13,7 @@ impl<'a> LatePeepholeOptimizations {
///
/// `foo['bar']` -> `foo.bar`
/// `foo?.['bar']` -> `foo?.bar`
pub fn convert_to_dotted_properties(
expr: &mut MemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
pub fn convert_to_dotted_properties(expr: &mut MemberExpression<'a>, ctx: Ctx<'a, '_>) {
let MemberExpression::ComputedMemberExpression(e) = expr else { return };
let Expression::StringLiteral(s) = &e.expression else { return };
if is_identifier_name(&s.value) {
@ -32,8 +29,7 @@ impl<'a> LatePeepholeOptimizations {
return;
}
if let Some(n) = Ctx::string_to_equivalent_number_value(v) {
e.expression =
Ctx(ctx).ast.expression_numeric_literal(s.span, n, None, NumberBase::Decimal);
e.expression = ctx.ast.expression_numeric_literal(s.span, n, None, NumberBase::Decimal);
}
}
}

View file

@ -8,7 +8,7 @@ use oxc_syntax::{
number::NumberBase,
operator::{BinaryOperator, LogicalOperator},
};
use oxc_traverse::{Ancestor, TraverseCtx};
use oxc_traverse::Ancestor;
use crate::ctx::Ctx;
@ -18,12 +18,7 @@ impl<'a, 'b> PeepholeOptimizations {
/// Constant Folding
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
pub fn fold_constants_exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let ctx = Ctx(ctx);
pub fn fold_constants_exit_expression(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
if let Some(folded_expr) = match expr {
Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, ctx)
.or_else(|| Self::try_fold_binary_typeof_comparison(e, ctx)),

View file

@ -7,7 +7,7 @@ use oxc_ecmascript::{
};
use oxc_span::{cmp::ContentEq, GetSpan};
use oxc_syntax::es_target::ESTarget;
use oxc_traverse::{Ancestor, TraverseCtx};
use oxc_traverse::Ancestor;
use crate::ctx::Ctx;
@ -24,7 +24,7 @@ impl<'a> PeepholeOptimizations {
pub fn minimize_conditions_exit_statements(
&mut self,
stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
let mut changed = false;
loop {
@ -45,7 +45,7 @@ impl<'a> PeepholeOptimizations {
pub fn minimize_conditions_exit_statement(
&mut self,
stmt: &mut Statement<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
let expr = match stmt {
Statement::IfStatement(s) => Some(&mut s.test),
@ -80,7 +80,7 @@ impl<'a> PeepholeOptimizations {
pub fn minimize_conditions_exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
let mut changed = false;
loop {
@ -123,7 +123,7 @@ impl<'a> PeepholeOptimizations {
}
}
fn minimize_not(span: Span, expr: Expression<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
fn minimize_not(span: Span, expr: Expression<'a>, ctx: Ctx<'a, '_>) -> Expression<'a> {
let mut unary = ctx.ast.unary_expression(span, UnaryOperator::LogicalNot, expr);
Self::try_minimize_not(&mut unary, ctx)
.unwrap_or_else(|| Expression::UnaryExpression(ctx.ast.alloc(unary)))
@ -131,7 +131,7 @@ impl<'a> PeepholeOptimizations {
fn try_minimize_not(
expr: &mut UnaryExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
if !expr.operator.is_not() {
return None;
@ -159,10 +159,7 @@ impl<'a> PeepholeOptimizations {
}
}
fn try_minimize_if(
stmt: &mut Statement<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> {
fn try_minimize_if(stmt: &mut Statement<'a>, ctx: Ctx<'a, '_>) -> Option<Statement<'a>> {
let Statement::IfStatement(if_stmt) = stmt else { unreachable!() };
// `if (x) foo()` -> `x && foo()`
@ -276,7 +273,7 @@ impl<'a> PeepholeOptimizations {
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
changed: &mut bool,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
for i in 0..stmts.len() {
let Statement::IfStatement(if_stmt) = &stmts[i] else {
@ -328,7 +325,7 @@ impl<'a> PeepholeOptimizations {
matches!(stmt.get_one_child(), Some(Statement::ExpressionStatement(_)))
}
fn get_block_expression(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
fn get_block_expression(stmt: &mut Statement<'a>, ctx: Ctx<'a, '_>) -> Expression<'a> {
let Some(Statement::ExpressionStatement(s)) = stmt.get_one_child_mut() else {
unreachable!()
};
@ -353,15 +350,12 @@ impl<'a> PeepholeOptimizations {
}
}
fn get_block_return_expression(
stmt: &mut Statement<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
fn get_block_return_expression(stmt: &mut Statement<'a>, ctx: Ctx<'a, '_>) -> Expression<'a> {
let Some(stmt) = stmt.get_one_child_mut() else { unreachable!() };
Self::take_return_argument(stmt, ctx)
}
fn take_return_argument(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
fn take_return_argument(stmt: &mut Statement<'a>, ctx: Ctx<'a, '_>) -> Expression<'a> {
let Statement::ReturnStatement(return_stmt) = ctx.ast.move_statement(stmt) else {
unreachable!()
};
@ -377,7 +371,7 @@ impl<'a> PeepholeOptimizations {
test: Expression<'a>,
consequent: Expression<'a>,
alternate: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Expression<'a> {
let mut cond_expr = ctx.ast.conditional_expression(span, test, consequent, alternate);
Self::try_minimize_conditional(&mut cond_expr, ctx)
@ -387,7 +381,7 @@ impl<'a> PeepholeOptimizations {
// <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2745>
fn try_minimize_conditional(
expr: &mut ConditionalExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
match &mut expr.test {
// "(a, b) ? c : d" => "a, b ? c : d"
@ -484,7 +478,7 @@ impl<'a> PeepholeOptimizations {
// "a ? b ? c : d : d" => "a && b ? c : d"
if let Expression::ConditionalExpression(consequent) = &mut expr.consequent {
if Ctx(ctx).expr_eq(&consequent.alternate, &expr.alternate) {
if ctx.expr_eq(&consequent.alternate, &expr.alternate) {
return Some(ctx.ast.expression_conditional(
expr.span,
ctx.ast.expression_logical(
@ -501,7 +495,7 @@ impl<'a> PeepholeOptimizations {
// "a ? b : c ? b : d" => "a || c ? b : d"
if let Expression::ConditionalExpression(alternate) = &mut expr.alternate {
if Ctx(ctx).expr_eq(&alternate.consequent, &expr.consequent) {
if ctx.expr_eq(&alternate.consequent, &expr.consequent) {
return Some(ctx.ast.expression_conditional(
expr.span,
ctx.ast.expression_logical(
@ -519,7 +513,7 @@ impl<'a> PeepholeOptimizations {
// "a ? c : (b, c)" => "(a || b), c"
if let Expression::SequenceExpression(alternate) = &mut expr.alternate {
if alternate.expressions.len() == 2
&& Ctx(ctx).expr_eq(&alternate.expressions[1], &expr.consequent)
&& ctx.expr_eq(&alternate.expressions[1], &expr.consequent)
{
return Some(ctx.ast.expression_sequence(
expr.span,
@ -539,7 +533,7 @@ impl<'a> PeepholeOptimizations {
// "a ? (b, c) : c" => "(a && b), c"
if let Expression::SequenceExpression(consequent) = &mut expr.consequent {
if consequent.expressions.len() == 2
&& Ctx(ctx).expr_eq(&consequent.expressions[1], &expr.alternate)
&& ctx.expr_eq(&consequent.expressions[1], &expr.alternate)
{
return Some(ctx.ast.expression_sequence(
expr.span,
@ -559,7 +553,7 @@ impl<'a> PeepholeOptimizations {
// "a ? b || c : c" => "(a && b) || c"
if let Expression::LogicalExpression(logical_expr) = &mut expr.consequent {
if logical_expr.operator == LogicalOperator::Or
&& Ctx(ctx).expr_eq(&logical_expr.right, &expr.alternate)
&& ctx.expr_eq(&logical_expr.right, &expr.alternate)
{
return Some(ctx.ast.expression_logical(
expr.span,
@ -578,7 +572,7 @@ impl<'a> PeepholeOptimizations {
// "a ? c : b && c" => "(a || b) && c"
if let Expression::LogicalExpression(logical_expr) = &mut expr.alternate {
if logical_expr.operator == LogicalOperator::And
&& Ctx(ctx).expr_eq(&logical_expr.right, &expr.consequent)
&& ctx.expr_eq(&logical_expr.right, &expr.consequent)
{
return Some(ctx.ast.expression_logical(
expr.span,
@ -602,8 +596,8 @@ impl<'a> PeepholeOptimizations {
) = (&expr.test, &mut expr.consequent, &mut expr.alternate)
{
if consequent.arguments.len() == alternate.arguments.len()
&& !Ctx(ctx).is_global_reference(test)
&& Ctx(ctx).expr_eq(&consequent.callee, &alternate.callee)
&& !ctx.is_global_reference(test)
&& ctx.expr_eq(&consequent.callee, &alternate.callee)
&& consequent
.arguments
.iter()
@ -671,7 +665,7 @@ impl<'a> PeepholeOptimizations {
// TODO: Try using the "??" or "?." operators
if Ctx(ctx).expr_eq(&expr.alternate, &expr.consequent) {
if ctx.expr_eq(&expr.alternate, &expr.consequent) {
// TODO:
// "/* @__PURE__ */ a() ? b : b" => "b"
// if ctx.ExprCanBeRemovedIfUnused(test) {
@ -694,7 +688,7 @@ impl<'a> PeepholeOptimizations {
/// - `x ? a = 0 : a = 1` -> `a = x ? 0 : 1`
fn try_merge_conditional_expression_inside(
expr: &mut ConditionalExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let (
Expression::AssignmentExpression(consequent),
@ -733,10 +727,7 @@ impl<'a> PeepholeOptimizations {
/// Simplify syntax when we know it's used inside a boolean context, e.g. `if (boolean_context) {}`.
///
/// <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2059>
fn try_fold_expr_in_boolean_context(
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> bool {
fn try_fold_expr_in_boolean_context(expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool {
match expr {
// "!!a" => "a"
Expression::UnaryExpression(u1) if u1.operator.is_not() => {
@ -772,7 +763,7 @@ impl<'a> PeepholeOptimizations {
Self::try_fold_expr_in_boolean_context(&mut e.left, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.right, ctx);
// "if (anything && truthyNoSideEffects)" => "if (anything)"
if Ctx(ctx).get_side_free_boolean_value(&e.right) == Some(true) {
if ctx.get_side_free_boolean_value(&e.right) == Some(true) {
*expr = ctx.ast.move_expression(&mut e.left);
return true;
}
@ -782,7 +773,7 @@ impl<'a> PeepholeOptimizations {
Self::try_fold_expr_in_boolean_context(&mut e.left, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.right, ctx);
// "if (anything || falsyNoSideEffects)" => "if (anything)"
if Ctx(ctx).get_side_free_boolean_value(&e.right) == Some(false) {
if ctx.get_side_free_boolean_value(&e.right) == Some(false) {
*expr = ctx.ast.move_expression(&mut e.left);
return true;
}
@ -791,7 +782,7 @@ impl<'a> PeepholeOptimizations {
// "if (a ? !!b : !!c)" => "if (a ? b : c)"
Self::try_fold_expr_in_boolean_context(&mut e.consequent, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.alternate, ctx);
if let Some(boolean) = Ctx(ctx).get_side_free_boolean_value(&e.consequent) {
if let Some(boolean) = ctx.get_side_free_boolean_value(&e.consequent) {
let right = ctx.ast.move_expression(&mut e.alternate);
let left = ctx.ast.move_expression(&mut e.test);
if boolean {
@ -806,7 +797,7 @@ impl<'a> PeepholeOptimizations {
}
return true;
}
if let Some(boolean) = Ctx(ctx).get_side_free_boolean_value(&e.alternate) {
if let Some(boolean) = ctx.get_side_free_boolean_value(&e.alternate) {
let left = ctx.ast.move_expression(&mut e.test);
let right = ctx.ast.move_expression(&mut e.consequent);
if boolean {
@ -836,7 +827,7 @@ impl<'a> PeepholeOptimizations {
// ^^^^^^ ValueType::from(&e.left).is_number()` is `true`.
fn try_minimize_binary(
e: &mut BinaryExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
if !e.operator.is_equality() {
return None;
@ -895,7 +886,7 @@ impl<'a> PeepholeOptimizations {
/// - `document.all == null` is `true`
fn try_compress_is_null_or_undefined(
expr: &mut LogicalExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let op = expr.operator;
let target_ops = match op {
@ -941,7 +932,7 @@ impl<'a> PeepholeOptimizations {
right: &mut Expression<'a>,
span: Span,
(find_op, replace_op): (BinaryOperator, BinaryOperator),
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
enum LeftPairValueResult {
Null(Span),
@ -1032,7 +1023,7 @@ impl<'a> PeepholeOptimizations {
fn try_compress_normal_assignment_to_combined_logical_assignment(
&mut self,
expr: &mut AssignmentExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> bool {
if self.target < ESTarget::ES2020 {
return false;
@ -1053,7 +1044,7 @@ impl<'a> PeepholeOptimizations {
};
// It should also early return when the reference might refer to a reference value created by a with statement
// when the minifier supports with statements
if write_id_ref.name != read_id_ref.name || Ctx(ctx).is_global_reference(write_id_ref) {
if write_id_ref.name != read_id_ref.name || ctx.is_global_reference(write_id_ref) {
return false;
}
@ -1066,7 +1057,7 @@ impl<'a> PeepholeOptimizations {
fn try_compress_logical_expression_to_assignment_expression(
&self,
expr: &mut LogicalExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
if self.target < ESTarget::ES2020 {
return None;
@ -1093,7 +1084,7 @@ impl<'a> PeepholeOptimizations {
/// Compress `a = a + b` to `a += b`
fn try_compress_normal_assignment_to_combined_assignment(
expr: &mut AssignmentExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> bool {
if !matches!(expr.operator, AssignmentOperator::Assign) {
return false;
@ -1125,7 +1116,7 @@ impl<'a> PeepholeOptimizations {
fn has_no_side_effect_for_evaluation_same_target(
assignment_target: &AssignmentTarget,
expr: &Expression,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> bool {
if let (
AssignmentTarget::AssignmentTargetIdentifier(write_id_ref),
@ -1146,7 +1137,7 @@ impl<'a> PeepholeOptimizations {
if let Some(read_expr) = expr.as_member_expression() {
// It should also return false when the reference might refer to a reference value created by a with statement
// when the minifier supports with statements
return !Ctx(ctx).is_global_reference(write_expr_object_id)
return !ctx.is_global_reference(write_expr_object_id)
&& write_expr.content_eq(read_expr);
}
}
@ -1156,7 +1147,7 @@ impl<'a> PeepholeOptimizations {
/// Compress `a = a + b` to `a += b`
fn try_compress_assignment_to_update_expression(
expr: &mut AssignmentExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let target = expr.left.as_simple_assignment_target_mut()?;
if !matches!(expr.operator, AssignmentOperator::Subtraction) {

View file

@ -1,6 +1,7 @@
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::TraverseCtx;
use crate::ctx::Ctx;
use super::PeepholeOptimizations;
@ -9,11 +10,7 @@ impl<'a> PeepholeOptimizations {
/// are minimized and instead flows to implicit exits conditions.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/MinimizeExitPoints.java>
pub fn minimize_exit_points(
&mut self,
body: &mut FunctionBody<'_>,
_ctx: &mut TraverseCtx<'_>,
) {
pub fn minimize_exit_points(&mut self, body: &mut FunctionBody<'_>, _ctx: Ctx<'a, '_>) {
self.remove_last_return(&mut body.statements);
}

View file

@ -9,14 +9,17 @@ mod replace_known_methods;
mod statement_fusion;
mod substitute_alternate_syntax;
use rustc_hash::FxHashSet;
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_data_structures::stack::NonEmptyStack;
use oxc_syntax::{es_target::ESTarget, scope::ScopeId};
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
pub use normalize::{Normalize, NormalizeOptions};
use rustc_hash::FxHashSet;
use crate::ctx::Ctx;
pub use self::normalize::{Normalize, NormalizeOptions};
pub struct PeepholeOptimizations {
target: ESTarget,
@ -138,6 +141,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.statement_fusion_exit_statements(stmts, ctx);
self.collapse_variable_declarations(stmts, ctx);
self.minimize_conditions_exit_statements(stmts, ctx);
@ -148,6 +152,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.minimize_conditions_exit_statement(stmt, ctx);
self.remove_dead_code_exit_statement(stmt, ctx);
}
@ -156,6 +161,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_return_statement(stmt, ctx);
}
@ -163,6 +169,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.minimize_exit_points(body, ctx);
}
@ -174,6 +181,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_variable_declaration(decl, ctx);
}
@ -181,6 +189,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.fold_constants_exit_expression(expr, ctx);
self.minimize_conditions_exit_expression(expr, ctx);
self.remove_dead_code_exit_expression(expr, ctx);
@ -192,6 +201,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_call_expression(expr, ctx);
}
@ -199,6 +209,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_object_property(prop, ctx);
}
@ -210,6 +221,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_assignment_target_property_property(prop, ctx);
}
@ -217,6 +229,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_binding_property(prop, ctx);
}
@ -228,6 +241,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_method_definition(prop, ctx);
}
@ -239,6 +253,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_property_definition(prop, ctx);
}
@ -250,6 +265,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
if !self.is_prev_function_changed() {
return;
}
let ctx = Ctx(ctx);
self.substitute_accessor_property(prop, ctx);
}
}
@ -276,19 +292,19 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations {
expr: &mut MemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
Self::convert_to_dotted_properties(expr, ctx);
Self::convert_to_dotted_properties(expr, Ctx(ctx));
}
fn exit_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
Self::remove_dead_code_exit_class_body(body, ctx);
Self::remove_dead_code_exit_class_body(body, Ctx(ctx));
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
Self::substitute_exit_expression(expr, ctx);
Self::substitute_exit_expression(expr, Ctx(ctx));
}
fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) {
self.substitute_catch_clause(catch, ctx);
self.substitute_catch_clause(catch, Ctx(ctx));
}
}
@ -308,15 +324,15 @@ impl<'a> DeadCodeElimination {
impl<'a> Traverse<'a> for DeadCodeElimination {
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
self.inner.remove_dead_code_exit_statement(stmt, ctx);
self.inner.remove_dead_code_exit_statement(stmt, Ctx(ctx));
}
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.inner.remove_dead_code_exit_statements(stmts, ctx);
self.inner.remove_dead_code_exit_statements(stmts, Ctx(ctx));
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.inner.fold_constants_exit_expression(expr, ctx);
self.inner.remove_dead_code_exit_expression(expr, ctx);
self.inner.fold_constants_exit_expression(expr, Ctx(ctx));
self.inner.remove_dead_code_exit_expression(expr, Ctx(ctx));
}
}

View file

@ -5,7 +5,7 @@ use oxc_ecmascript::{
side_effects::MayHaveSideEffects,
};
use oxc_span::GetSpan;
use oxc_traverse::{Ancestor, TraverseCtx};
use oxc_traverse::Ancestor;
use crate::{ctx::Ctx, keep_var::KeepVar};
@ -21,20 +21,15 @@ impl<'a, 'b> PeepholeOptimizations {
pub fn remove_dead_code_exit_statements(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
if stmts.iter().any(|stmt| matches!(stmt, Statement::EmptyStatement(_))) {
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
}
self.dead_code_elimination(stmts, Ctx(ctx));
self.dead_code_elimination(stmts, ctx);
}
pub fn remove_dead_code_exit_statement(
&mut self,
stmt: &mut Statement<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let ctx = Ctx(ctx);
pub fn remove_dead_code_exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: Ctx<'a, '_>) {
if let Some(new_stmt) = match stmt {
Statement::BlockStatement(s) => Self::try_optimize_block(s, ctx),
Statement::IfStatement(s) => self.try_fold_if(s, ctx),
@ -59,9 +54,8 @@ impl<'a, 'b> PeepholeOptimizations {
pub fn remove_dead_code_exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
let ctx = Ctx(ctx);
if let Some(folded_expr) = match expr {
Expression::ConditionalExpression(e) => Self::try_fold_conditional_expression(e, ctx),
Expression::SequenceExpression(sequence_expression) => {
@ -578,7 +572,7 @@ impl<'a, 'b> PeepholeOptimizations {
}
impl<'a> LatePeepholeOptimizations {
pub fn remove_dead_code_exit_class_body(body: &mut ClassBody<'a>, _ctx: &mut TraverseCtx<'a>) {
pub fn remove_dead_code_exit_class_body(body: &mut ClassBody<'a>, _ctx: Ctx<'a, '_>) {
body.body.retain(|e| !matches!(e, ClassElement::StaticBlock(s) if s.body.is_empty()));
}
}

View file

@ -9,7 +9,7 @@ use oxc_ecmascript::{
};
use oxc_span::SPAN;
use oxc_syntax::es_target::ESTarget;
use oxc_traverse::{Ancestor, TraverseCtx};
use oxc_traverse::Ancestor;
use crate::ctx::Ctx;
@ -21,18 +21,14 @@ impl<'a> PeepholeOptimizations {
pub fn replace_known_methods_exit_expression(
&mut self,
node: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
self.try_fold_concat_chain(node, ctx);
self.try_fold_known_string_methods(node, ctx);
self.try_fold_known_property_access(node, ctx);
}
fn try_fold_known_string_methods(
&mut self,
node: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
fn try_fold_known_string_methods(&mut self, node: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
let Expression::CallExpression(ce) = node else { return };
let (name, object) = match &ce.callee {
Expression::StaticMemberExpression(member) if !member.optional => {
@ -70,7 +66,7 @@ impl<'a> PeepholeOptimizations {
ce: &CallExpression<'a>,
name: &str,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
if ce.arguments.len() >= 1 {
return None;
@ -89,7 +85,7 @@ impl<'a> PeepholeOptimizations {
ce: &CallExpression<'a>,
name: &str,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
if args.len() >= 3 {
@ -118,7 +114,7 @@ impl<'a> PeepholeOptimizations {
fn try_fold_string_substring_or_slice(
ce: &CallExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
if args.len() > 2 {
@ -127,11 +123,11 @@ impl<'a> PeepholeOptimizations {
let Expression::StringLiteral(s) = object else { return None };
let start_idx = args.first().and_then(|arg| match arg {
Argument::SpreadElement(_) => None,
_ => Ctx(ctx).get_side_free_number_value(arg.to_expression()),
_ => ctx.get_side_free_number_value(arg.to_expression()),
});
let end_idx = args.get(1).and_then(|arg| match arg {
Argument::SpreadElement(_) => None,
_ => Ctx(ctx).get_side_free_number_value(arg.to_expression()),
_ => ctx.get_side_free_number_value(arg.to_expression()),
});
#[expect(clippy::cast_precision_loss)]
if start_idx.is_some_and(|start| start > s.value.len() as f64 || start < 0.0)
@ -154,7 +150,7 @@ impl<'a> PeepholeOptimizations {
fn try_fold_string_char_at(
ce: &CallExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
if args.len() > 1 {
@ -183,13 +179,13 @@ impl<'a> PeepholeOptimizations {
fn try_fold_string_char_code_at(
ce: &CallExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let Expression::StringLiteral(s) = object else { return None };
let char_at_index = match ce.arguments.first() {
None => Some(0.0),
Some(Argument::SpreadElement(_)) => None,
Some(e) => Ctx(ctx).get_side_free_number_value(e.to_expression()),
Some(e) => ctx.get_side_free_number_value(e.to_expression()),
}?;
let value = if (0.0..65536.0).contains(&char_at_index) {
s.value.as_str().char_code_at(Some(char_at_index))? as f64
@ -205,7 +201,7 @@ impl<'a> PeepholeOptimizations {
ce: &CallExpression<'a>,
name: &str,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
if ce.arguments.len() != 2 {
return None;
@ -216,14 +212,14 @@ impl<'a> PeepholeOptimizations {
let search_value = match search_value {
Argument::SpreadElement(_) => return None,
match_expression!(Argument) => {
Ctx(ctx).get_side_free_string_value(search_value.to_expression())?
ctx.get_side_free_string_value(search_value.to_expression())?
}
};
let replace_value = ce.arguments.get(1).unwrap();
let replace_value = match replace_value {
Argument::SpreadElement(_) => return None,
match_expression!(Argument) => {
Ctx(ctx).get_side_free_string_value(replace_value.to_expression())?
ctx.get_side_free_string_value(replace_value.to_expression())?
}
};
if replace_value.contains('$') {
@ -241,10 +237,9 @@ impl<'a> PeepholeOptimizations {
fn try_fold_string_from_char_code(
ce: &CallExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let Expression::Identifier(ident) = object else { return None };
let ctx = Ctx(ctx);
if ident.name != "String" || !ctx.is_global_reference(ident) {
return None;
}
@ -269,7 +264,7 @@ impl<'a> PeepholeOptimizations {
fn try_fold_to_string(
ce: &CallExpression<'a>,
object: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let args = &ce.arguments;
match object {
@ -335,7 +330,7 @@ impl<'a> PeepholeOptimizations {
/// `[].concat(a).concat(b)` -> `[].concat(a, b)`
/// `"".concat(a).concat(b)` -> `"".concat(a, b)`
fn try_fold_concat_chain(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
fn try_fold_concat_chain(&mut self, node: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
let original_span = if let Expression::CallExpression(root_call_expr) = node {
root_call_expr.span
} else {
@ -415,10 +410,7 @@ impl<'a> PeepholeOptimizations {
}
/// `[].concat(1, 2)` -> `[1, 2]`
fn try_fold_concat(
ce: &mut CallExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
fn try_fold_concat(ce: &mut CallExpression<'a>, ctx: Ctx<'a, '_>) -> Option<Expression<'a>> {
// let concat chaining reduction handle it first
if let Ancestor::StaticMemberExpressionObject(parent_member) = ctx.parent() {
if parent_member.property().name.as_str() == "concat" {
@ -476,11 +468,7 @@ impl<'a> PeepholeOptimizations {
}
}
fn try_fold_known_property_access(
&mut self,
node: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
fn try_fold_known_property_access(&mut self, node: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
let (name, object, span) = match &node {
Expression::StaticMemberExpression(member) if !member.optional => {
(member.property.name.as_str(), &member.object, member.span)
@ -495,7 +483,6 @@ impl<'a> PeepholeOptimizations {
};
let Expression::Identifier(ident) = object else { return };
let ctx = &mut Ctx(ctx);
if !ctx.is_global_reference(ident) {
return;
}
@ -515,7 +502,7 @@ impl<'a> PeepholeOptimizations {
&self,
name: &str,
span: Span,
ctx: &mut Ctx<'a, '_>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let num = |span: Span, n: f64| {
ctx.ast.expression_numeric_literal(span, n, None, NumberBase::Decimal)

View file

@ -1,7 +1,8 @@
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_span::GetSpan;
use oxc_traverse::TraverseCtx;
use crate::ctx::Ctx;
use super::PeepholeOptimizations;
@ -14,7 +15,7 @@ impl<'a> PeepholeOptimizations {
pub fn statement_fusion_exit_statements(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
let len = stmts.len();
@ -81,7 +82,7 @@ impl<'a> PeepholeOptimizations {
}
}
fn fuse_into_one_statement(stmts: &mut [Statement<'a>], ctx: &mut TraverseCtx<'a>) {
fn fuse_into_one_statement(stmts: &mut [Statement<'a>], ctx: Ctx<'a, '_>) {
let mut exprs = ctx.ast.vec();
let len = stmts.len();
@ -108,7 +109,7 @@ impl<'a> PeepholeOptimizations {
fn fuse_expression_into_control_flow_statement(
stmt: &mut Statement<'a>,
exprs: Vec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
let mut exprs = exprs;
let expr = match stmt {

View file

@ -11,7 +11,7 @@ use oxc_syntax::{
number::NumberBase,
operator::{BinaryOperator, UnaryOperator},
};
use oxc_traverse::{Ancestor, TraverseCtx};
use oxc_traverse::Ancestor;
use crate::ctx::Ctx;
@ -22,18 +22,14 @@ use super::{LatePeepholeOptimizations, PeepholeOptimizations};
/// with literals, and simplifying returns.
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntax.java>
impl<'a> PeepholeOptimizations {
pub fn substitute_object_property(
&mut self,
prop: &mut ObjectProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
pub fn substitute_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: Ctx<'a, '_>) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
pub fn substitute_assignment_target_property_property(
&mut self,
prop: &mut AssignmentTargetPropertyProperty<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
self.try_compress_property_key(&mut prop.name, &mut prop.computed, ctx);
}
@ -41,7 +37,7 @@ impl<'a> PeepholeOptimizations {
pub fn substitute_binding_property(
&mut self,
prop: &mut BindingProperty<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
@ -49,7 +45,7 @@ impl<'a> PeepholeOptimizations {
pub fn substitute_method_definition(
&mut self,
prop: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
@ -57,7 +53,7 @@ impl<'a> PeepholeOptimizations {
pub fn substitute_property_definition(
&mut self,
prop: &mut PropertyDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
@ -65,7 +61,7 @@ impl<'a> PeepholeOptimizations {
pub fn substitute_accessor_property(
&mut self,
prop: &mut AccessorProperty<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
@ -73,7 +69,7 @@ impl<'a> PeepholeOptimizations {
pub fn substitute_return_statement(
&mut self,
stmt: &mut ReturnStatement<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
self.compress_return_statement(stmt, ctx);
}
@ -81,28 +77,18 @@ impl<'a> PeepholeOptimizations {
pub fn substitute_variable_declaration(
&mut self,
decl: &mut VariableDeclaration<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
for declarator in &mut decl.declarations {
self.compress_variable_declarator(declarator, Ctx(ctx));
self.compress_variable_declarator(declarator, ctx);
}
}
pub fn substitute_call_expression(
&mut self,
expr: &mut CallExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
pub fn substitute_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: Ctx<'a, '_>) {
self.try_compress_call_expression_arguments(expr, ctx);
}
pub fn substitute_exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let ctx = Ctx(ctx);
pub fn substitute_exit_expression(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
// Change syntax
match expr {
Expression::ArrowFunctionExpression(e) => self.try_compress_arrow_expression(e, ctx),
@ -445,16 +431,12 @@ impl<'a> PeepholeOptimizations {
///
/// `return undefined` -> `return`
/// `return void 0` -> `return`
fn compress_return_statement(
&mut self,
stmt: &mut ReturnStatement<'a>,
ctx: &mut TraverseCtx<'a>,
) {
fn compress_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: Ctx<'a, '_>) {
let Some(argument) = &stmt.argument else { return };
if !match argument {
Expression::Identifier(ident) => Ctx(ctx).is_identifier_undefined(ident),
Expression::Identifier(ident) => ctx.is_identifier_undefined(ident),
Expression::UnaryExpression(e) => {
e.operator.is_void() && !Ctx(ctx).expression_may_have_side_efffects(argument)
e.operator.is_void() && !ctx.expression_may_have_side_efffects(argument)
}
_ => false,
} {
@ -762,7 +744,7 @@ impl<'a> PeepholeOptimizations {
&mut self,
key: &mut PropertyKey<'a>,
computed: &mut bool,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
if let PropertyKey::NumericLiteral(_) = key {
if *computed {
@ -805,7 +787,7 @@ impl<'a> PeepholeOptimizations {
fn try_compress_call_expression_arguments(
&mut self,
node: &mut CallExpression<'a>,
ctx: &mut TraverseCtx<'a>,
ctx: Ctx<'a, '_>,
) {
let (new_size, should_fold) =
node.arguments.iter().fold((0, false), |(mut new_size, mut should_fold), arg| {
@ -865,7 +847,7 @@ impl<'a> PeepholeOptimizations {
}
impl<'a> LatePeepholeOptimizations {
pub fn substitute_exit_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn substitute_exit_expression(expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
if let Expression::NewExpression(e) = expr {
Self::try_compress_typed_array_constructor(e, ctx);
}
@ -879,10 +861,10 @@ impl<'a> LatePeepholeOptimizations {
}
/// `new Int8Array(0)` -> `new Int8Array()` (also for other TypedArrays)
fn try_compress_typed_array_constructor(e: &mut NewExpression<'a>, ctx: &mut TraverseCtx<'a>) {
fn try_compress_typed_array_constructor(e: &mut NewExpression<'a>, ctx: Ctx<'a, '_>) {
let Expression::Identifier(ident) = &e.callee else { return };
let name = ident.name.as_str();
if !Self::is_typed_array_name(name) || !Ctx(ctx).is_global_reference(ident) {
if !Self::is_typed_array_name(name) || !ctx.is_global_reference(ident) {
return;
}
if e.arguments.len() == 1
@ -893,10 +875,7 @@ impl<'a> LatePeepholeOptimizations {
}
/// Transforms boolean expression `true` => `!0` `false` => `!1`.
fn try_compress_boolean(
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
fn try_compress_boolean(expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> Option<Expression<'a>> {
let Expression::BooleanLiteral(lit) = expr else { return None };
let num = ctx.ast.expression_numeric_literal(
lit.span,
@ -907,11 +886,7 @@ impl<'a> LatePeepholeOptimizations {
Some(ctx.ast.expression_unary(lit.span, UnaryOperator::LogicalNot, num))
}
pub fn substitute_catch_clause(
&mut self,
catch: &mut CatchClause<'a>,
ctx: &mut TraverseCtx<'a>,
) {
pub fn substitute_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: Ctx<'a, '_>) {
if self.target >= ESTarget::ES2019 {
if let Some(param) = &catch.param {
if let BindingPatternKind::BindingIdentifier(ident) = &param.pattern.kind {