mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
perf(minifier): move owned AST nodes directly (#25837)
Several peephole transforms call `take_in` after removing or consuming the parent AST node. At that point there is no borrowed slot to preserve, so allocating a dummy replacement is unnecessary. Move those already-owned child nodes directly in the straightforward expression and declaration cases. This second branch stays intentionally mechanical and leaves the statement/reference-bookkeeping cases to the final PR in the stack. Relative to its parent, the tracked allocation fixtures record 22,957 fewer arena allocations and 370,544 fewer arena bytes. Minsize output is unchanged. Validated with `cargo test -p oxc_minifier`, `just minsize`, `cargo clippy -p oxc_minifier -- -D warnings`, `just fmt`, and `git diff --check`. AI assistance was used for research, implementation, review, and testing. I reviewed and take responsibility for the changes.
This commit is contained in:
1 parent
673b04bc46
commit
61b2aefa94
8 files changed
+72
-93
No files matched your search
@@ -1,4 +1,4 @@
|
||||
use oxc_allocator::TakeIn;
|
||||
use oxc_allocator::ReplaceWith;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_syntax::identifier::is_identifier_name_patched;
|
||||
|
||||
@@ -22,15 +22,13 @@ impl<'a> PeepholeOptimizations {
|
||||
let Expression::StringLiteral(s) = &e.expression else { return };
|
||||
if is_identifier_name_patched(&s.value) {
|
||||
let property = IdentifierName::new(s.span, s.value, ctx);
|
||||
let new_member = StaticMemberExpression::boxed(
|
||||
e.span,
|
||||
e.object.take_in(ctx),
|
||||
property,
|
||||
e.optional,
|
||||
ctx,
|
||||
);
|
||||
// Direct slot write: no typed helper for the `MemberExpression` enum slot; the sibling `notice_change()` preserves the mutation signal.
|
||||
*expr = MemberExpression::StaticMemberExpression(new_member);
|
||||
expr.replace_with(|expr| {
|
||||
let MemberExpression::ComputedMemberExpression(e) = expr else { unreachable!() };
|
||||
let ComputedMemberExpression { span, object, optional, .. } = e.unbox();
|
||||
MemberExpression::StaticMemberExpression(StaticMemberExpression::boxed(
|
||||
span, object, property, optional, ctx,
|
||||
))
|
||||
});
|
||||
ctx.notice_change();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,17 +42,16 @@ impl<'a> PeepholeOptimizations {
|
||||
let mut b = b;
|
||||
// "a op (b op c)" => "(a op b) op c"
|
||||
// "a op (b op (c op d))" => "((a op b) op c) op d"
|
||||
loop {
|
||||
if let Expression::LogicalExpression(logical_expr) = &mut b
|
||||
&& logical_expr.operator == op
|
||||
{
|
||||
let right = logical_expr.left.take_in(ctx);
|
||||
a = Self::join_with_left_associative_op(span, op, a, right, ctx);
|
||||
b = logical_expr.right.take_in(ctx);
|
||||
continue;
|
||||
let b = loop {
|
||||
match b {
|
||||
Expression::LogicalExpression(logical_expr) if logical_expr.operator == op => {
|
||||
let LogicalExpression { left, right, .. } = logical_expr.unbox();
|
||||
a = Self::join_with_left_associative_op(span, op, a, left, ctx);
|
||||
b = right;
|
||||
}
|
||||
b => break b,
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
// "a op b" => "a op b"
|
||||
// "(a op b) op c" => "(a op b) op c"
|
||||
let mut logic_expr = Expression::new_logical_expression(span, a, op, b, ctx);
|
||||
|
||||
@@ -40,9 +40,10 @@ impl<'a> PeepholeOptimizations {
|
||||
stmt => (stmt, None),
|
||||
};
|
||||
|
||||
let Statement::IfStatement(mut if_stmt) = first else { unreachable!() };
|
||||
let Statement::IfStatement(if_stmt) = first else { unreachable!() };
|
||||
let IfStatement { test, alternate, .. } = if_stmt.unbox();
|
||||
|
||||
let expr = match if_stmt.test.take_in(ctx) {
|
||||
let expr = match test {
|
||||
Expression::UnaryExpression(unary_expr) if unary_expr.operator.is_not() => {
|
||||
unary_expr.unbox().argument
|
||||
}
|
||||
@@ -61,7 +62,6 @@ impl<'a> PeepholeOptimizations {
|
||||
for_stmt.test = Some(expr);
|
||||
}
|
||||
|
||||
let alternate = if_stmt.alternate.take();
|
||||
let new_body = Self::drop_first_statement(span, body, alternate, ctx);
|
||||
ctx.replace_statement(&mut for_stmt.body, new_body);
|
||||
return;
|
||||
@@ -84,9 +84,10 @@ impl<'a> PeepholeOptimizations {
|
||||
stmt => (stmt, None),
|
||||
};
|
||||
|
||||
let Statement::IfStatement(mut if_stmt) = first else { unreachable!() };
|
||||
let Statement::IfStatement(if_stmt) = first else { unreachable!() };
|
||||
let IfStatement { test, consequent, .. } = if_stmt.unbox();
|
||||
|
||||
let expr = if_stmt.test.take_in(ctx);
|
||||
let expr = test;
|
||||
|
||||
if let Some(test) = &mut for_stmt.test {
|
||||
let left = test.take_in(ctx);
|
||||
@@ -100,7 +101,6 @@ impl<'a> PeepholeOptimizations {
|
||||
for_stmt.test = Some(expr);
|
||||
}
|
||||
|
||||
let consequent = if_stmt.consequent.take_in(ctx);
|
||||
let new_body = Self::drop_first_statement(span, body, Some(consequent), ctx);
|
||||
ctx.replace_statement(&mut for_stmt.body, new_body);
|
||||
}
|
||||
@@ -119,7 +119,7 @@ impl<'a> PeepholeOptimizations {
|
||||
} else if block_stmt.body.len() == 2
|
||||
&& !Self::statement_cares_about_scope(&block_stmt.body[1])
|
||||
{
|
||||
return block_stmt.body[1].take_in(ctx);
|
||||
return block_stmt.body.pop().unwrap();
|
||||
} else {
|
||||
block_stmt.body.remove(0);
|
||||
}
|
||||
|
||||
@@ -248,20 +248,20 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
let Expression::SequenceExpression(sequence_expr) = &mut e.right else { return };
|
||||
let Some(Expression::AssignmentExpression(mut assignment_expr)) =
|
||||
let Some(Expression::AssignmentExpression(assignment_expr)) =
|
||||
sequence_expr.expressions.pop()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
Self::mark_assignment_target_as_read(&assignment_expr.left, ctx);
|
||||
let AssignmentExpression { left, right, .. } = assignment_expr.unbox();
|
||||
Self::mark_assignment_target_as_read(&left, ctx);
|
||||
|
||||
let assign_value = assignment_expr.right.take_in(ctx);
|
||||
sequence_expr.expressions.push(assign_value);
|
||||
sequence_expr.expressions.push(right);
|
||||
let new_expr = Expression::new_assignment_expression(
|
||||
e.span,
|
||||
e.operator.to_assignment_operator(),
|
||||
assignment_expr.left.take_in(ctx),
|
||||
left,
|
||||
e.right.take_in(ctx),
|
||||
ctx,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::generated::ancestor::Ancestor;
|
||||
use oxc_allocator::{ArenaVec, ReplaceWith, TakeIn};
|
||||
use oxc_allocator::{ArenaVec, ReplaceWith};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{
|
||||
constant_evaluation::{ConstantValue, DetermineValueType, ValueType},
|
||||
@@ -132,8 +132,11 @@ impl<'a> Traverse<'a> for Normalize {
|
||||
}
|
||||
|
||||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if let Expression::ParenthesizedExpression(paren_expr) = expr {
|
||||
*expr = paren_expr.expression.take_in(ctx);
|
||||
if matches!(expr, Expression::ParenthesizedExpression(_)) {
|
||||
expr.replace_with(|expr| {
|
||||
let Expression::ParenthesizedExpression(paren_expr) = expr else { unreachable!() };
|
||||
paren_expr.unbox().expression
|
||||
});
|
||||
}
|
||||
// Handled outside the match below so the replacement can go through
|
||||
// `ctx.replace_expression`, which walks the dropped call (its
|
||||
|
||||
@@ -72,28 +72,21 @@ impl<'a> PeepholeOptimizations {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut second_arg = arguments.pop().expect("checked len above");
|
||||
let second_arg = second_arg.to_expression_mut(); // checked above
|
||||
let mut first_arg = arguments.pop().expect("checked len above");
|
||||
let first_arg = first_arg.to_expression_mut(); // checked above
|
||||
let second_arg = arguments.pop().expect("checked len above").into_expression();
|
||||
let first_arg = arguments.pop().expect("checked len above").into_expression();
|
||||
|
||||
let wrap_with_unary_plus_if_needed = |expr: &mut Expression<'a>| {
|
||||
let wrap_with_unary_plus_if_needed = |expr: Expression<'a>| {
|
||||
if expr.value_type(ctx).is_number() {
|
||||
expr.take_in(ctx)
|
||||
expr
|
||||
} else {
|
||||
Expression::new_unary_expression(
|
||||
SPAN,
|
||||
UnaryOperator::UnaryPlus,
|
||||
expr.take_in(ctx),
|
||||
ctx,
|
||||
)
|
||||
Expression::new_unary_expression(SPAN, UnaryOperator::UnaryPlus, expr, ctx)
|
||||
}
|
||||
};
|
||||
|
||||
Some(Expression::new_binary_expression(
|
||||
span,
|
||||
// see [`PeepholeOptimizations::is_binary_operator_that_does_number_conversion`] why it does not require `wrap_with_unary_plus_if_needed` here
|
||||
first_arg.take_in(ctx),
|
||||
first_arg,
|
||||
BinaryOperator::Exponential,
|
||||
wrap_with_unary_plus_if_needed(second_arg),
|
||||
ctx,
|
||||
|
||||
@@ -341,22 +341,18 @@ impl<'a> PeepholeOptimizations {
|
||||
if right.operator != e.operator {
|
||||
return;
|
||||
}
|
||||
let Expression::LogicalExpression(mut right) = e.right.take_in(ctx) else { return };
|
||||
let Expression::LogicalExpression(right) = e.right.take_in(ctx) else { return };
|
||||
let LogicalExpression { left: right_left, right: right_right, .. } = right.unbox();
|
||||
let mut new_left = Expression::new_logical_expression(
|
||||
e.span,
|
||||
e.left.take_in(ctx),
|
||||
e.operator,
|
||||
right.left.take_in(ctx),
|
||||
right_left,
|
||||
ctx,
|
||||
);
|
||||
Self::substitute_rotate_logical_expression(&mut new_left, ctx);
|
||||
let new_value = Expression::new_logical_expression(
|
||||
e.span,
|
||||
new_left,
|
||||
e.operator,
|
||||
right.right.take_in(ctx),
|
||||
ctx,
|
||||
);
|
||||
let new_value =
|
||||
Expression::new_logical_expression(e.span, new_left, e.operator, right_right, ctx);
|
||||
ctx.replace_expression(expr, new_value);
|
||||
}
|
||||
|
||||
@@ -382,24 +378,20 @@ impl<'a> PeepholeOptimizations {
|
||||
&& right.operator == e.operator
|
||||
&& !right.right.may_have_side_effects(ctx)
|
||||
{
|
||||
let Expression::BinaryExpression(mut right) = e.right.take_in(ctx) else {
|
||||
let Expression::BinaryExpression(right) = e.right.take_in(ctx) else {
|
||||
return;
|
||||
};
|
||||
let BinaryExpression { left: right_left, right: right_right, .. } = right.unbox();
|
||||
let mut new_left = Expression::new_binary_expression(
|
||||
e.span,
|
||||
e.left.take_in(ctx),
|
||||
e.operator,
|
||||
right.left.take_in(ctx),
|
||||
right_left,
|
||||
ctx,
|
||||
);
|
||||
Self::substitute_rotate_binary_expression(&mut new_left, ctx);
|
||||
let new_value = Expression::new_binary_expression(
|
||||
e.span,
|
||||
new_left,
|
||||
e.operator,
|
||||
right.right.take_in(ctx),
|
||||
ctx,
|
||||
);
|
||||
let new_value =
|
||||
Expression::new_binary_expression(e.span, new_left, e.operator, right_right, ctx);
|
||||
ctx.replace_expression(expr, new_value);
|
||||
return;
|
||||
}
|
||||
@@ -1471,31 +1463,25 @@ impl<'a> PeepholeOptimizations {
|
||||
let new_args = args;
|
||||
|
||||
for arg in old_args {
|
||||
if let Argument::SpreadElement(mut spread_el) = arg {
|
||||
if let Expression::ArrayExpression(array_expr) = &mut spread_el.argument {
|
||||
for el in &mut array_expr.elements {
|
||||
if let Argument::SpreadElement(spread_el) = arg {
|
||||
let SpreadElement { span, argument, .. } = spread_el.unbox();
|
||||
if let Expression::ArrayExpression(array_expr) = argument {
|
||||
for el in array_expr.unbox().elements {
|
||||
match el {
|
||||
ArrayExpressionElement::SpreadElement(spread_el) => {
|
||||
new_args.push(Argument::new_spread_element(
|
||||
spread_el.span,
|
||||
spread_el.argument.take_in(ctx),
|
||||
ctx,
|
||||
));
|
||||
let SpreadElement { span, argument, .. } = spread_el.unbox();
|
||||
new_args.push(Argument::new_spread_element(span, argument, ctx));
|
||||
}
|
||||
ArrayExpressionElement::Elision(elision) => {
|
||||
new_args.push(Expression::new_void_0(elision.span, ctx).into());
|
||||
}
|
||||
match_expression!(ArrayExpressionElement) => {
|
||||
new_args.push(el.to_expression_mut().take_in(ctx).into());
|
||||
new_args.push(el.into_expression().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_args.push(Argument::new_spread_element(
|
||||
spread_el.span,
|
||||
spread_el.argument.take_in(ctx),
|
||||
ctx,
|
||||
));
|
||||
new_args.push(Argument::new_spread_element(span, argument, ctx));
|
||||
}
|
||||
} else {
|
||||
new_args.push(arg);
|
||||
|
||||
@@ -5,9 +5,9 @@ checker.ts:
|
||||
sys deallocs: 150
|
||||
sys alloc bytes: 14996600 # 15.00 MB
|
||||
sys peak growth: 10169560 # 10.17 MB
|
||||
arena allocs: 148403
|
||||
arena allocs: 145315
|
||||
arena reallocs: 28468
|
||||
arena size: 19204712 # 19.20 MB
|
||||
arena size: 19154792 # 19.15 MB
|
||||
|
||||
App.tsx:
|
||||
file size: 415342 # 415.34 kB
|
||||
@@ -16,9 +16,9 @@ App.tsx:
|
||||
sys deallocs: 61
|
||||
sys alloc bytes: 2128555 # 2.13 MB
|
||||
sys peak growth: 1612427 # 1.61 MB
|
||||
arena allocs: 16038
|
||||
arena allocs: 15795
|
||||
arena reallocs: 2721
|
||||
arena size: 2887488 # 2.89 MB
|
||||
arena size: 2883328 # 2.88 MB
|
||||
|
||||
RadixUIAdoptionSection.jsx:
|
||||
file size: 2520 # 2.52 kB
|
||||
@@ -27,7 +27,7 @@ RadixUIAdoptionSection.jsx:
|
||||
sys deallocs: 21
|
||||
sys alloc bytes: 17400 # 17.40 kB
|
||||
sys peak growth: 11628 # 11.63 kB
|
||||
arena allocs: 30
|
||||
arena allocs: 29
|
||||
arena reallocs: 6
|
||||
arena size: 35632 # 35.63 kB
|
||||
|
||||
@@ -38,9 +38,9 @@ pdf.mjs:
|
||||
sys deallocs: 2427
|
||||
sys alloc bytes: 5334247 # 5.33 MB
|
||||
sys peak growth: 3693355 # 3.69 MB
|
||||
arena allocs: 45817
|
||||
arena allocs: 45153
|
||||
arena reallocs: 7718
|
||||
arena size: 6326224 # 6.33 MB
|
||||
arena size: 6314208 # 6.31 MB
|
||||
|
||||
antd.js:
|
||||
file size: 6686316 # 6.69 MB
|
||||
@@ -49,9 +49,9 @@ antd.js:
|
||||
sys deallocs: 1038
|
||||
sys alloc bytes: 29976031 # 29.98 MB
|
||||
sys peak growth: 19934725 # 19.93 MB
|
||||
arena allocs: 371595
|
||||
arena allocs: 353569
|
||||
arena reallocs: 76289
|
||||
arena size: 49108808 # 49.11 MB
|
||||
arena size: 48820232 # 48.82 MB
|
||||
|
||||
binder.ts:
|
||||
file size: 193077 # 193.08 kB
|
||||
@@ -60,9 +60,9 @@ binder.ts:
|
||||
sys deallocs: 33
|
||||
sys alloc bytes: 963008 # 963.01 kB
|
||||
sys peak growth: 657404 # 657.40 kB
|
||||
arena allocs: 7030
|
||||
arena allocs: 6877
|
||||
arena reallocs: 842
|
||||
arena size: 1167360 # 1.17 MB
|
||||
arena size: 1164880 # 1.16 MB
|
||||
|
||||
kitchen-sink.tsx:
|
||||
file size: 732898 # 732.90 kB
|
||||
@@ -71,6 +71,6 @@ kitchen-sink.tsx:
|
||||
sys deallocs: 1854
|
||||
sys alloc bytes: 5307512 # 5.31 MB
|
||||
sys peak growth: 3707425 # 3.71 MB
|
||||
arena allocs: 66752
|
||||
arena allocs: 65970
|
||||
arena reallocs: 12252
|
||||
arena size: 9424360 # 9.42 MB
|
||||
arena size: 9410968 # 9.41 MB
|
||||
Reference in new issue
Block a user