perf(minifier): reduce allocations when minimizing assignments (#26609)

This commit is contained in:
Armano authored and GitHub committed 2026-09-22 15:32:40 +08:00
1 parent 57c91ffce4
commit fc37ba07d3
2 files changed
+45 -51

No files matched your search

@@ -1,4 +1,3 @@
use oxc_allocator::TakeIn;
use oxc_ast::ast::*;
use oxc_compat::ESFeature;
use oxc_ecmascript::{
@@ -73,18 +72,15 @@ impl<'a> PeepholeOptimizations {
}
// `NaN == 0` and `!NaN` differ, so require proof that the left side is a non-NaN Number.
if e.right.is_number_0() && e.left.is_int32_or_uint32(ctx) {
let argument = e.left.take_in(ctx);
let mut new_expr =
Expression::new_unary_expression(e.span, UnaryOperator::LogicalNot, argument, ctx);
if matches!(e.operator, BinaryOperator::Inequality | BinaryOperator::StrictInequality) {
new_expr = Expression::new_unary_expression(
e.span,
UnaryOperator::LogicalNot,
new_expr,
ctx,
);
}
ctx.replace_expression(expr, new_expr);
let inequal =
matches!(e.operator, BinaryOperator::Inequality | BinaryOperator::StrictInequality);
ctx.replace_expression_with(expr, |old, ctx| {
let Expression::BinaryExpression(e) = old else { unreachable!() };
let e = e.unbox();
let value =
if inequal { Self::minimize_not(e.span, e.left, ctx, true) } else { e.left };
Expression::new_unary_expression(e.span, UnaryOperator::LogicalNot, value, ctx)
});
return;
}
let left = e.left.value_type(ctx);
@@ -103,33 +99,22 @@ impl<'a> PeepholeOptimizations {
_ => {}
}
}
if !left.is_boolean() {
return;
}
if e.right.may_have_side_effects(ctx) {
if !left.is_boolean() || e.right.may_have_side_effects(ctx) {
return;
}
let Some(mut b) = e.right.evaluate_value(ctx).and_then(ConstantValue::into_boolean) else {
return;
};
match e.operator {
BinaryOperator::Inequality | BinaryOperator::StrictInequality => {
e.operator = BinaryOperator::Equality;
b = !b;
}
BinaryOperator::StrictEquality => {
e.operator = BinaryOperator::Equality;
}
BinaryOperator::Equality => {}
_ => return,
if matches!(e.operator, BinaryOperator::Inequality) {
b = !b;
}
let new_expr = if b {
e.left.take_in(ctx)
} else {
let argument = e.left.take_in(ctx);
Expression::new_unary_expression(e.span, UnaryOperator::LogicalNot, argument, ctx)
};
ctx.replace_expression(expr, new_expr);
ctx.drop_expression(&e.right);
ctx.replace_expression_with(expr, |old, ctx| {
let Expression::BinaryExpression(e) = old else { unreachable!() };
let e = e.unbox();
if b { e.left } else { Self::minimize_not(e.span, e.left, ctx, false) }
});
}
/// Compress `foo == true` into `foo == 1`.
@@ -224,10 +209,12 @@ impl<'a> PeepholeOptimizations {
let reference = ctx.scoping_mut().get_reference_mut(write_id_ref.reference_id());
reference.flags_mut().insert(ReferenceFlags::Read);
let new_op = logical_expr.operator.to_assignment_operator();
let new_right = logical_expr.right.take_in(ctx);
expr.operator = new_op;
ctx.replace_expression(&mut expr.right, new_right);
expr.operator = logical_expr.operator.to_assignment_operator();
ctx.drop_expression(&logical_expr.left);
ctx.replace_expression_with(&mut expr.right, |e, _ctx| {
let Expression::LogicalExpression(e) = e else { unreachable!() };
e.unbox().right
});
}
/// Compress `a = a + b` to `a += b`
@@ -238,7 +225,7 @@ impl<'a> PeepholeOptimizations {
if !matches!(expr.operator, AssignmentOperator::Assign) {
return;
}
let Expression::BinaryExpression(binary_expr) = &mut expr.right else { return };
let Expression::BinaryExpression(binary_expr) = &expr.right else { return };
let Some(new_op) = binary_expr.operator.to_assignment_operator() else { return };
if !Self::has_no_side_effect_for_evaluation_same_target(&expr.left, &binary_expr.left, ctx)
{
@@ -247,9 +234,12 @@ impl<'a> PeepholeOptimizations {
Self::mark_assignment_target_as_read(&expr.left, ctx);
let new_right = binary_expr.right.take_in(ctx);
expr.operator = new_op;
ctx.replace_expression(&mut expr.right, new_right);
ctx.drop_expression(&binary_expr.left);
ctx.replace_expression_with(&mut expr.right, |e, _ctx| {
let Expression::BinaryExpression(e) = e else { unreachable!() };
e.unbox().right
});
}
/// Compress `a -= 1` to `--a` and `a -= -1` to `++a`
@@ -259,7 +249,7 @@ impl<'a> PeepholeOptimizations {
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(e) = expr else { return };
if !matches!(e.operator, AssignmentOperator::Subtraction) {
if e.operator != AssignmentOperator::Subtraction || !e.left.is_simple_assignment_target() {
return;
}
let operator = if let Expression::NumericLiteral(num) = &e.right {
@@ -273,9 +263,13 @@ impl<'a> PeepholeOptimizations {
} else {
return;
};
let Some(target) = e.left.as_simple_assignment_target_mut() else { return };
let target = target.take_in(ctx);
let new_expr = Expression::new_update_expression(e.span, operator, true, target, ctx);
ctx.replace_expression(expr, new_expr);
ctx.replace_expression_with(expr, |e, ctx| {
let Expression::AssignmentExpression(e) = e else {
unreachable!();
};
let e = e.unbox();
let target = e.left.into_simple_assignment_target();
Expression::new_update_expression(e.span, operator, true, target, ctx)
});
}
}
@@ -5,7 +5,7 @@ checker.ts:
sys deallocs: 122
sys alloc bytes: 14996287 # 15.00 MB
sys peak growth: 10169560 # 10.17 MB
arena allocs: 56226
arena allocs: 56215
arena reallocs: 5084
arena size: 16317648 # 16.32 MB
@@ -38,7 +38,7 @@ pdf.mjs:
sys deallocs: 2427
sys alloc bytes: 5313795 # 5.31 MB
sys peak growth: 3693355 # 3.69 MB
arena allocs: 19526
arena allocs: 19523
arena reallocs: 2574
arena size: 5708848 # 5.71 MB
@@ -49,7 +49,7 @@ antd.js:
sys deallocs: 860
sys alloc bytes: 29973448 # 29.97 MB
sys peak growth: 19934725 # 19.93 MB
arena allocs: 181721
arena allocs: 181686
arena reallocs: 34406
arena size: 43045192 # 43.05 MB
@@ -60,7 +60,7 @@ binder.ts:
sys deallocs: 33
sys alloc bytes: 963008 # 963.01 kB
sys peak growth: 657404 # 657.40 kB
arena allocs: 2923
arena allocs: 2922
arena reallocs: 244
arena size: 1074976 # 1.07 MB
@@ -71,6 +71,6 @@ kitchen-sink.tsx:
sys deallocs: 1854
sys alloc bytes: 5304252 # 5.30 MB
sys peak growth: 3707425 # 3.71 MB
arena allocs: 31159
arena allocs: 31152
arena reallocs: 3012
arena size: 8444304 # 8.44 MB