perf(minifier): consume nodes in minimize_statements in reverse order (#26258)

changes `minimize_statement`s to consume statement via a single
`reverse()` + `pop()` pass instead of repeatedly calling `take_in` on
each slot.

potential pitfall: result is in forward order, old stmts are in reverse,
thus we have to be careful in places where we want to take all of them

| File | Arena allocs | Delta | Arena size | Delta |
|---|---:|---:|---:|---:|
| `checker.ts` | `144024 -> 77557` | `-66467` | `19.13 MB -> 17.52 MB` |
`-1.61 MB` |
| `App.tsx` | `15675 -> 9622` | `-6053` | `2.88 MB -> 2.74 MB` | `-0.14
MB` |
| `RadixUIAdoptionSection.jsx` | `29 -> 16` | `-13` | `35.63 kB -> 35.63
kB` | `~0` |
| `pdf.mjs` | `44801 -> 26527` | `-18274` | `6.31 MB -> 5.89 MB` |
`-0.42 MB` |
| `antd.js` | `351645 -> 222308` | `-129337` | `48.77 MB -> 45.76 MB` |
`-3.01 MB` |
| `binder.ts` | `6809 -> 4124` | `-2685` | `1.16 MB -> 1.10 MB` | `-0.06
MB` |
| `kitchen-sink.tsx` | `64956 -> 34571` | `-30385` | `9.39 MB -> 8.74
MB` | `-0.65 MB` |

---------

Co-authored-by: Dunqing <dengqing0821@gmail.com>
This commit is contained in:
ArmanoandDunqing authored and GitHub committed 2026-09-07 17:24:05 +08:00
1 parent 9a023379c6
commit 2ca7d29b7a
2 files changed
+43 -48

No files matched your search

@@ -1,4 +1,4 @@
use std::{iter, ops::ControlFlow};
use std::iter;
use crate::generated::ancestor::Ancestor;
use oxc_allocator::{ArenaBox, ArenaVec, TakeIn};
@@ -15,6 +15,8 @@ use crate::{TraverseCtx, is_terminated::IsTerminated, keep_var::KeepVar};
use super::PeepholeOptimizations;
type StatementIter<'a> = <ArenaVec<'a, Statement<'a>> as IntoIterator>::IntoIter;
/// `false` when dropping `stmt` produces a byte-identical AST — a `var`
/// with no initializers, which `KeepVar` re-emits unchanged at the end of
/// the block. Flagging such an identity drop as a real change would
@@ -48,12 +50,11 @@ impl<'a> PeepholeOptimizations {
/// ## MinimizeExitPoints:
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/MinimizeExitPoints.java>
pub fn minimize_statements(stmts: &mut ArenaVec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
let mut old_stmts = stmts.take_in(ctx);
let mut old_stmts = stmts.take_in(ctx).into_iter();
let mut is_control_flow_dead = false;
let mut keep_var = KeepVar::new();
let mut identity_drops = 0u32;
for i in 0..old_stmts.len() {
let stmt = old_stmts[i].take_in(ctx);
while let Some(stmt) = old_stmts.next() {
if is_control_flow_dead
&& !stmt.is_module_declaration()
&& !matches!(stmt.as_declaration(), Some(Declaration::FunctionDeclaration(_)))
@@ -73,14 +74,15 @@ impl<'a> PeepholeOptimizations {
}
continue; // drop: `stmt` is intentionally not pushed into `stmts`.
}
if Self::minimize_statement(stmt, i, &mut old_stmts, stmts, ctx).is_break() {
break;
}
Self::minimize_statement(stmt, &mut old_stmts, stmts, ctx);
// A statement that never completes normally — a direct jump, a
// kept block ending in a jump, an if/else or try/catch where
// every branch jumps — makes the rest of the list unreachable.
// https://github.com/rolldown/rolldown/issues/10184
if !is_control_flow_dead && stmts.last().is_some_and(Statement::is_terminated) {
if !is_control_flow_dead
&& !old_stmts.as_slice().is_empty()
&& stmts.last().is_some_and(Statement::is_terminated)
{
is_control_flow_dead = true;
}
}
@@ -129,11 +131,10 @@ impl<'a> PeepholeOptimizations {
fn minimize_statement(
stmt: Statement<'a>,
i: usize,
stmts: &mut ArenaVec<'a, Statement<'a>>,
stmts: &mut StatementIter<'a>,
result: &mut ArenaVec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> ControlFlow<()> {
) {
match stmt {
Statement::EmptyStatement(_) => (),
Statement::VariableDeclaration(var_decl) => {
@@ -146,9 +147,7 @@ impl<'a> PeepholeOptimizations {
Self::handle_switch_statement(switch_stmt, result, ctx);
}
Statement::IfStatement(if_stmt) => {
if Self::handle_if_statement(i, stmts, if_stmt, result, ctx).is_break() {
return ControlFlow::Break(());
}
Self::handle_if_statement(stmts, if_stmt, result, ctx);
}
Statement::ReturnStatement(ret_stmt) => {
Self::handle_return_statement(ret_stmt, result, ctx);
@@ -168,7 +167,6 @@ impl<'a> PeepholeOptimizations {
Statement::BlockStatement(block_stmt) => Self::handle_block(result, block_stmt, ctx),
stmt => result.push(stmt),
}
ControlFlow::Continue(())
}
fn join_sequence(
@@ -532,13 +530,12 @@ impl<'a> PeepholeOptimizations {
}
fn handle_if_statement(
i: usize,
stmts: &mut ArenaVec<'a, Statement<'a>>,
stmts: &mut StatementIter<'a>,
mut if_stmt: ArenaBox<'a, IfStatement<'a>>,
result: &mut ArenaVec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> ControlFlow<()> {
) {
Self::substitute_single_use_symbol_in_statement(&mut if_stmt.test, result, ctx, false);
// "var a; if (a = b(), c) d;" => "var a = b(); if (c) d;"
@@ -596,12 +593,10 @@ impl<'a> PeepholeOptimizations {
//
let can_move_branch_condition_outside_scope =
!if_stmt.alternate.as_ref().is_some_and(Self::statement_cares_about_scope)
&& !stmts.get(i + 1..).is_some_and(|stmts| {
stmts.iter().any(Self::statement_cares_about_scope)
});
&& !stmts.as_slice().iter().any(Self::statement_cares_about_scope);
if can_move_branch_condition_outside_scope {
let drained_stmts = stmts.drain(i + 1..);
let drained_stmts = stmts.by_ref();
let mut body = if let Some(alternate) = if_stmt.alternate.take() {
ArenaVec::from_iter_in(iter::once(alternate).chain(drained_stmts), ctx)
} else {
@@ -630,7 +625,7 @@ impl<'a> PeepholeOptimizations {
});
result.push(if_stmt);
ctx.notice_change();
return ControlFlow::Break(());
return;
}
}
}
@@ -642,12 +637,12 @@ impl<'a> PeepholeOptimizations {
// "if (a) return b; else if (c) return d; else return e;" => "if (a) return b; if (c) return d; return e;"
ctx.notice_change();
result.push(Statement::IfStatement(if_stmt));
return Self::minimize_statement(stmt, i, stmts, result, ctx);
Self::minimize_statement(stmt, stmts, result, ctx);
return;
}
}
result.push(Statement::IfStatement(if_stmt));
ControlFlow::Continue(())
}
fn handle_return_statement(
@@ -3,22 +3,22 @@ checker.ts:
sys allocs: 150
sys reallocs: 5
sys deallocs: 150
sys alloc bytes: 14996600 # 15.00 MB
sys alloc bytes: 14996632 # 15.00 MB
sys peak growth: 10169560 # 10.17 MB
arena allocs: 143446
arena allocs: 77041
arena reallocs: 28269
arena size: 19097584 # 19.10 MB
arena size: 17492768 # 17.49 MB
App.tsx:
file size: 415342 # 415.34 kB
sys allocs: 61
sys reallocs: 8
sys deallocs: 61
sys alloc bytes: 2128555 # 2.13 MB
sys alloc bytes: 2128563 # 2.13 MB
sys peak growth: 1612427 # 1.61 MB
arena allocs: 15643
arena allocs: 9590
arena reallocs: 2709
arena size: 2879680 # 2.88 MB
arena size: 2741920 # 2.74 MB
RadixUIAdoptionSection.jsx:
file size: 2520 # 2.52 kB
@@ -27,50 +27,50 @@ RadixUIAdoptionSection.jsx:
sys deallocs: 21
sys alloc bytes: 17400 # 17.40 kB
sys peak growth: 11628 # 11.63 kB
arena allocs: 29
arena allocs: 16
arena reallocs: 6
arena size: 35632 # 35.63 kB
arena size: 35224 # 35.22 kB
pdf.mjs:
file size: 567296 # 567.30 kB
sys allocs: 2427
sys reallocs: 205
sys deallocs: 2427
sys alloc bytes: 5334247 # 5.33 MB
sys peak growth: 3693355 # 3.69 MB
arena allocs: 44749
sys alloc bytes: 5316219 # 5.32 MB
sys peak growth: 3693363 # 3.69 MB
arena allocs: 26475
arena reallocs: 7699
arena size: 6304256 # 6.30 MB
arena size: 5889456 # 5.89 MB
antd.js:
file size: 6686316 # 6.69 MB
sys allocs: 1038
sys reallocs: 56
sys deallocs: 1038
sys alloc bytes: 29976031 # 29.98 MB
sys peak growth: 19934725 # 19.93 MB
arena allocs: 351149
sys alloc bytes: 29976375 # 29.98 MB
sys peak growth: 19934749 # 19.93 MB
arena allocs: 221812
arena reallocs: 75941
arena size: 48738712 # 48.74 MB
arena size: 45729992 # 45.73 MB
binder.ts:
file size: 193077 # 193.08 kB
sys allocs: 33
sys reallocs: 1
sys deallocs: 33
sys alloc bytes: 963008 # 963.01 kB
sys alloc bytes: 963032 # 963.03 kB
sys peak growth: 657404 # 657.40 kB
arena allocs: 6784
arena allocs: 4099
arena reallocs: 834
arena size: 1162400 # 1.16 MB
arena size: 1098864 # 1.10 MB
kitchen-sink.tsx:
file size: 732898 # 732.90 kB
sys allocs: 1854
sys reallocs: 174
sys deallocs: 1854
sys alloc bytes: 5307512 # 5.31 MB
sys peak growth: 3707425 # 3.71 MB
arena allocs: 64833
sys alloc bytes: 5304692 # 5.30 MB
sys peak growth: 3707457 # 3.71 MB
arena allocs: 34448
arena reallocs: 12187
arena size: 9385624 # 9.39 MB
arena size: 8732520 # 8.73 MB