Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 13 additions & 38 deletions crates/oxc_minifier/src/peephole/fold_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,12 @@ use crate::ctx::Ctx;

use super::PeepholeOptimizations;

/// Constant Folding
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
impl<'a> PeepholeOptimizations {
/// Constant Folding
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
pub fn fold_constants_exit_expression(&self, e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
match e {
Expression::TemplateLiteral(t) => {
self.try_inline_values_in_template_literal(t, ctx);
}
Expression::ObjectExpression(e) => self.fold_object_spread(e, ctx),
Expression::BinaryExpression(_) => {
Self::try_fold_binary_expr(e, ctx);
Self::try_fold_binary_typeof_comparison(e, ctx);
}
Expression::UnaryExpression(_) => Self::try_fold_unary_expr(e, ctx),
Expression::StaticMemberExpression(_) => Self::try_fold_static_member_expr(e, ctx),
Expression::ComputedMemberExpression(_) => Self::try_fold_computed_member_expr(e, ctx),
Expression::LogicalExpression(_) => Self::try_fold_logical_expr(e, ctx),
Expression::ChainExpression(_) => Self::try_fold_optional_chain(e, ctx),
Expression::CallExpression(_) => Self::try_fold_number_constructor(e, ctx),
_ => {}
}
}

#[expect(clippy::float_cmp)]
fn try_fold_unary_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_unary_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::UnaryExpression(e) = expr else { return };
match e.operator {
// Do not fold `void 0` back to `undefined`.
Expand All @@ -57,7 +37,7 @@ impl<'a> PeepholeOptimizations {
}
}

fn try_fold_static_member_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_static_member_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::StaticMemberExpression(e) = expr else { return };
// TODO: tryFoldObjectPropAccess(n, left, name)
if e.object.may_have_side_effects(ctx) {
Expand All @@ -69,7 +49,7 @@ impl<'a> PeepholeOptimizations {
}
}

fn try_fold_computed_member_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_computed_member_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::ComputedMemberExpression(e) = expr else { return };
// TODO: tryFoldObjectPropAccess(n, left, name)
if e.object.may_have_side_effects(ctx) || e.expression.may_have_side_effects(ctx) {
Expand All @@ -81,7 +61,7 @@ impl<'a> PeepholeOptimizations {
}
}

fn try_fold_logical_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_logical_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::LogicalExpression(e) = expr else { return };
if let Some(changed) = match e.operator {
LogicalOperator::And | LogicalOperator::Or => Self::try_fold_and_or(e, ctx),
Expand All @@ -92,7 +72,7 @@ impl<'a> PeepholeOptimizations {
}
}

fn try_fold_optional_chain(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_chain_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::ChainExpression(e) = expr else { return };
let left_expr = match &e.expression {
match_member_expression!(ChainElement) => {
Expand Down Expand Up @@ -273,7 +253,7 @@ impl<'a> PeepholeOptimizations {
}

#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn try_fold_binary_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_binary_expr(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::BinaryExpression(e) = expr else { return };
// TODO: tryReduceOperandsForOp

Expand Down Expand Up @@ -537,7 +517,7 @@ impl<'a> PeepholeOptimizations {
))
}

fn try_fold_number_constructor(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_call_expression(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::CallExpression(e) = expr else { return };
if !ctx.is_global_expr("Number", &e.callee) {
return;
Expand Down Expand Up @@ -577,7 +557,7 @@ impl<'a> PeepholeOptimizations {
ctx.state.changed = true;
}

fn try_fold_binary_typeof_comparison(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_binary_typeof_comparison(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::BinaryExpression(e) = expr else { return };
// `typeof a == typeof a` -> `true`, `typeof a != typeof a` -> `false`
if e.operator.is_equality() {
Expand Down Expand Up @@ -644,7 +624,7 @@ impl<'a> PeepholeOptimizations {
}
}

fn fold_object_spread(&self, e: &mut ObjectExpression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn fold_object_exp(&self, e: &mut ObjectExpression<'a>, ctx: &mut Ctx<'a, '_>) {
fn should_fold_spread_element<'a>(e: &Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
match e {
Expression::ArrayExpression(o) if o.elements.is_empty() => true,
Expand Down Expand Up @@ -739,12 +719,7 @@ impl<'a> PeepholeOptimizations {
/// Inline constant values in template literals
///
/// - `foo${1}bar${i}` => `foo1bar${i}`
fn try_inline_values_in_template_literal(
&self,
t: &mut TemplateLiteral<'a>,

ctx: &mut Ctx<'a, '_>,
) {
pub fn inline_template_literal(&self, t: &mut TemplateLiteral<'a>, ctx: &mut Ctx<'a, '_>) {
let has_expr_to_inline = t
.expressions
.iter()
Expand Down
39 changes: 6 additions & 33 deletions crates/oxc_minifier/src/peephole/minimize_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,6 @@ use super::PeepholeOptimizations;
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeMinimizeConditions.java>
impl<'a> PeepholeOptimizations {
pub fn minimize_conditions_exit_expression(
&self,
expr: &mut Expression<'a>,
ctx: &mut Ctx<'a, '_>,
) {
match expr {
Expression::UnaryExpression(_) => self.try_minimize_not(expr, ctx),
Expression::BinaryExpression(e) => {
Self::try_compress_is_loose_boolean(e, ctx);
Self::try_minimize_binary(expr, ctx);
}
Expression::LogicalExpression(_) => self.minimize_logical_expression(expr, ctx),
Expression::ConditionalExpression(logical_expr) => {
self.try_fold_expr_in_boolean_context(&mut logical_expr.test, ctx);
if let Some(changed) = self.try_minimize_conditional(logical_expr, ctx) {
*expr = changed;
ctx.state.changed = true;
}
}
Expression::AssignmentExpression(e) => {
self.try_compress_normal_assignment_to_combined_logical_assignment(e, ctx);
Self::try_compress_normal_assignment_to_combined_assignment(e, ctx);
Self::try_compress_assignment_to_update_expression(expr, ctx);
}
_ => {}
}
}

// The goal of this function is to "rotate" the AST if it's possible to use the
// left-associative property of the operator to avoid unnecessary parentheses.
//
Expand Down Expand Up @@ -95,7 +67,7 @@ impl<'a> PeepholeOptimizations {
// ^^^^^^^^^^^^^^ `ctx.expression_value_type(&e.left).is_boolean()` is `true`.
// `x >> +y !== 0` -> `x >> +y`
// ^^^^^^^ ctx.expression_value_type(&e.left).is_number()` is `true`.
fn try_minimize_binary(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn try_minimize_binary(expr: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::BinaryExpression(e) = expr else { return };
if !e.operator.is_equality() {
return;
Expand Down Expand Up @@ -152,7 +124,8 @@ impl<'a> PeepholeOptimizations {
///
/// In `IsLooselyEqual`, `true` and `false` are converted to `1` and `0` first.
/// <https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islooselyequal>
fn try_compress_is_loose_boolean(e: &mut BinaryExpression<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn try_compress_is_loose_boolean(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) {
let Expression::BinaryExpression(e) = e else { return };
if !matches!(e.operator, BinaryOperator::Equality | BinaryOperator::Inequality) {
return;
}
Expand Down Expand Up @@ -198,7 +171,7 @@ impl<'a> PeepholeOptimizations {
/// Compress `a = a || b` to `a ||= b`
///
/// This can only be done for resolved identifiers as this would avoid setting `a` when `a` is truthy.
fn try_compress_normal_assignment_to_combined_logical_assignment(
pub fn try_compress_normal_assignment_to_combined_logical_assignment(
&self,
expr: &mut AssignmentExpression<'a>,
ctx: &mut Ctx<'a, '_>,
Expand Down Expand Up @@ -236,7 +209,7 @@ impl<'a> PeepholeOptimizations {
}

/// Compress `a = a + b` to `a += b`
fn try_compress_normal_assignment_to_combined_assignment(
pub fn try_compress_normal_assignment_to_combined_assignment(
expr: &mut AssignmentExpression<'a>,
ctx: &mut Ctx<'a, '_>,
) {
Expand All @@ -256,7 +229,7 @@ impl<'a> PeepholeOptimizations {

/// Compress `a -= 1` to `--a` and `a -= -1` to `++a`
#[expect(clippy::float_cmp)]
fn try_compress_assignment_to_update_expression(
pub fn try_compress_assignment_to_update_expression(
expr: &mut Expression<'a>,
ctx: &mut Ctx<'a, '_>,
) {
Expand Down
12 changes: 4 additions & 8 deletions crates/oxc_minifier/src/peephole/minimize_statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,13 @@ impl<'a> PeepholeOptimizations {
Self::join_sequence(&mut prev_if.test, &mut b, ctx)
} else {
// "if (a) return b; return c;" => "return a ? b : c;"
let mut expr = self.minimize_conditional(
self.minimize_conditional(
prev_if.span,
prev_if.test.take_in(ctx.ast),
left,
right,
ctx,
);
self.minimize_conditions_exit_expression(&mut expr, ctx);
expr
)
};
let last_return_stmt =
ctx.ast.statement_return(right_span, Some(argument));
Expand Down Expand Up @@ -258,15 +256,13 @@ impl<'a> PeepholeOptimizations {
Self::join_sequence(&mut prev_if.test, &mut b, ctx)
} else {
// "if (a) throw b; throw c;" => "throw a ? b : c;"
let mut expr = self.minimize_conditional(
self.minimize_conditional(
prev_if.span,
prev_if.test.take_in(ctx.ast),
left,
right,
ctx,
);
self.minimize_conditions_exit_expression(&mut expr, ctx);
expr
)
};
let last_throw_stmt = ctx.ast.statement_throw(right_span, argument);
result.push(last_throw_stmt);
Expand Down
117 changes: 106 additions & 11 deletions crates/oxc_minifier/src/peephole/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,81 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
}

fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let mut ctx = Ctx::new(ctx);
self.fold_constants_exit_expression(expr, &mut ctx);
self.minimize_conditions_exit_expression(expr, &mut ctx);
self.remove_dead_code_exit_expression(expr, &mut ctx);
self.replace_known_methods_exit_expression(expr, &mut ctx);
self.substitute_exit_expression(expr, &mut ctx);
self.inline_identifier_reference(expr, &mut ctx);
let ctx = &mut Ctx::new(ctx);
match expr {
Expression::TemplateLiteral(t) => {
self.inline_template_literal(t, ctx);
Self::try_fold_template_literal(expr, ctx);
}
Expression::ObjectExpression(e) => self.fold_object_exp(e, ctx),
Expression::BinaryExpression(e) => {
Self::swap_binary_expressions(e);
Self::fold_binary_expr(expr, ctx);
Self::fold_binary_typeof_comparison(expr, ctx);
Self::try_compress_is_loose_boolean(expr, ctx);
Self::try_minimize_binary(expr, ctx);
Self::try_fold_loose_equals_undefined(expr, ctx);
Self::try_compress_typeof_undefined(expr, ctx);
}
Expression::UnaryExpression(_) => {
Self::fold_unary_expr(expr, ctx);
self.try_minimize_not(expr, ctx);
Self::try_remove_unary_plus(expr, ctx);
}
Expression::StaticMemberExpression(_) => {
Self::fold_static_member_expr(expr, ctx);
self.try_fold_known_property_access(expr, ctx);
}
Expression::ComputedMemberExpression(_) => {
Self::fold_computed_member_expr(expr, ctx);
self.try_fold_known_property_access(expr, ctx);
}
Expression::LogicalExpression(_) => {
Self::fold_logical_expr(expr, ctx);
self.minimize_logical_expression(expr, ctx);
Self::try_compress_is_object_and_not_null(expr, ctx);
Self::try_rotate_logical_expression(expr, ctx);
}
Expression::ChainExpression(_) => {
Self::fold_chain_expr(expr, ctx);
Self::try_compress_chain_call_expression(expr, ctx);
}
Expression::CallExpression(_) => {
Self::fold_call_expression(expr, ctx);
self.remove_dead_code_call_expression(expr, ctx);
self.try_fold_concat_chain(expr, ctx);
self.try_fold_known_global_methods(expr, ctx);
self.try_fold_simple_function_call(expr, ctx);
Self::try_fold_object_or_array_constructor(expr, ctx);
}
Expression::ConditionalExpression(logical_expr) => {
self.try_fold_expr_in_boolean_context(&mut logical_expr.test, ctx);
if let Some(changed) = self.try_minimize_conditional(logical_expr, ctx) {
*expr = changed;
ctx.state.changed = true;
}
self.try_fold_conditional_expression(expr, ctx);
}
Expression::AssignmentExpression(e) => {
self.try_compress_normal_assignment_to_combined_logical_assignment(e, ctx);
Self::try_compress_normal_assignment_to_combined_assignment(e, ctx);
Self::try_compress_assignment_to_update_expression(expr, ctx);
self.remove_unused_assignment_expression(expr, ctx);
}
Expression::SequenceExpression(_) => self.try_fold_sequence_expression(expr, ctx),
Expression::ArrowFunctionExpression(e) => self.try_compress_arrow_expression(e, ctx),
Expression::FunctionExpression(e) => Self::try_remove_name_from_functions(e, ctx),
Expression::ClassExpression(e) => Self::try_remove_name_from_classes(e, ctx),
Expression::NewExpression(e) => {
Self::try_compress_typed_array_constructor(e, ctx);
Self::try_fold_new_expression(expr, ctx);
Self::try_fold_object_or_array_constructor(expr, ctx);
}
Expression::BooleanLiteral(_) => Self::try_compress_boolean(expr, ctx),
Expression::ArrayExpression(_) => Self::try_compress_array_expression(expr, ctx),
Expression::Identifier(_) => self.inline_identifier_reference(expr, ctx),
_ => {}
}
}

fn exit_unary_expression(&mut self, expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>) {
Expand Down Expand Up @@ -328,10 +396,37 @@ impl<'a> Traverse<'a, MinifierState<'a>> for DeadCodeElimination {
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
}

fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let mut ctx = Ctx::new(ctx);
self.inner.fold_constants_exit_expression(expr, &mut ctx);
self.inner.remove_dead_code_exit_expression(expr, &mut ctx);
fn exit_expression(&mut self, e: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let ctx = &mut Ctx::new(ctx);
match e {
Expression::TemplateLiteral(t) => self.inner.inline_template_literal(t, ctx),
Expression::ObjectExpression(e) => self.inner.fold_object_exp(e, ctx),
Expression::BinaryExpression(_) => {
PeepholeOptimizations::fold_binary_expr(e, ctx);
PeepholeOptimizations::fold_binary_typeof_comparison(e, ctx);
}
Expression::UnaryExpression(_) => PeepholeOptimizations::fold_unary_expr(e, ctx),
Expression::StaticMemberExpression(_) => {
PeepholeOptimizations::fold_static_member_expr(e, ctx);
}
Expression::ComputedMemberExpression(_) => {
PeepholeOptimizations::fold_computed_member_expr(e, ctx);
}
Expression::LogicalExpression(_) => PeepholeOptimizations::fold_logical_expr(e, ctx),
Expression::ChainExpression(_) => PeepholeOptimizations::fold_chain_expr(e, ctx),
Expression::CallExpression(_) => {
PeepholeOptimizations::fold_call_expression(e, ctx);
self.inner.remove_dead_code_call_expression(e, ctx);
}
Expression::ConditionalExpression(_) => {
self.inner.try_fold_conditional_expression(e, ctx);
}
Expression::SequenceExpression(_) => self.inner.try_fold_sequence_expression(e, ctx),
Expression::AssignmentExpression(_) => {
self.inner.remove_unused_assignment_expression(e, ctx);
}
_ => {}
}
}
}

Expand Down
Loading
Loading