mirror of
https://github.com/danbulant/oxc
synced 2026-05-21 21:29:01 +00:00
feat(minifier): minimize if (x) return; return 1 -> return x ? void 0 : 1 (#8130)
This commit is contained in:
parent
04c7d38287
commit
f615bfa773
3 changed files with 135 additions and 25 deletions
|
|
@ -151,6 +151,7 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations {
|
||||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.x1_collapse_variable_declarations.exit_statements(stmts, ctx);
|
self.x1_collapse_variable_declarations.exit_statements(stmts, ctx);
|
||||||
self.x2_peephole_remove_dead_code.exit_statements(stmts, ctx);
|
self.x2_peephole_remove_dead_code.exit_statements(stmts, ctx);
|
||||||
|
self.x3_peephole_minimize_conditions.exit_statements(stmts, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
|
@ -223,19 +224,20 @@ impl<'a> CompressorPass<'a> for PeepholeOptimizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Traverse<'a> for PeepholeOptimizations {
|
impl<'a> Traverse<'a> for PeepholeOptimizations {
|
||||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
|
||||||
self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx);
|
|
||||||
self.x5_peephole_remove_dead_code.exit_statement(stmt, ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.x5_peephole_remove_dead_code.exit_program(program, ctx);
|
self.x5_peephole_remove_dead_code.exit_program(program, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
self.x2_peephole_minimize_conditions.exit_statements(stmts, ctx);
|
||||||
self.x5_peephole_remove_dead_code.exit_statements(stmts, ctx);
|
self.x5_peephole_remove_dead_code.exit_statements(stmts, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx);
|
||||||
|
self.x5_peephole_remove_dead_code.exit_statement(stmt, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.x3_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx);
|
self.x3_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use oxc_allocator::Vec;
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
|
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
|
||||||
|
|
||||||
|
|
@ -26,6 +27,21 @@ impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
|
impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
|
||||||
|
fn exit_statements(
|
||||||
|
&mut self,
|
||||||
|
stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>,
|
||||||
|
ctx: &mut TraverseCtx<'a>,
|
||||||
|
) {
|
||||||
|
self.try_replace_if(stmts, ctx);
|
||||||
|
while self.changed {
|
||||||
|
self.changed = false;
|
||||||
|
self.try_replace_if(stmts, ctx);
|
||||||
|
if stmts.iter().any(|stmt| matches!(stmt, Statement::EmptyStatement(_))) {
|
||||||
|
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
if let Some(folded_stmt) = match stmt {
|
if let Some(folded_stmt) = match stmt {
|
||||||
// If the condition is a literal, we'll let other optimizations try to remove useless code.
|
// If the condition is a literal, we'll let other optimizations try to remove useless code.
|
||||||
|
|
@ -115,6 +131,103 @@ impl<'a> PeepholeMinimizeConditions {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_replace_if(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
for i in 0..stmts.len() {
|
||||||
|
let Statement::IfStatement(if_stmt) = &stmts[i] else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let then_branch = &if_stmt.consequent;
|
||||||
|
let else_branch = &if_stmt.alternate;
|
||||||
|
let next_node = stmts.get(i + 1);
|
||||||
|
|
||||||
|
if next_node.is_some_and(|s| matches!(s, Statement::IfStatement(_)))
|
||||||
|
&& else_branch.is_none()
|
||||||
|
&& Self::is_return_block(then_branch)
|
||||||
|
{
|
||||||
|
/* TODO */
|
||||||
|
} else if next_node.is_some_and(Self::is_return_expression)
|
||||||
|
&& else_branch.is_none()
|
||||||
|
&& Self::is_return_block(then_branch)
|
||||||
|
{
|
||||||
|
// `if (x) return; return 1` -> `return x ? void 0 : 1`
|
||||||
|
let Statement::IfStatement(if_stmt) = ctx.ast.move_statement(&mut stmts[i]) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let mut if_stmt = if_stmt.unbox();
|
||||||
|
let consequent = Self::get_block_return_expression(&mut if_stmt.consequent, ctx);
|
||||||
|
let alternate = Self::take_return_argument(&mut stmts[i + 1], ctx);
|
||||||
|
let argument = ctx.ast.expression_conditional(
|
||||||
|
if_stmt.span,
|
||||||
|
if_stmt.test,
|
||||||
|
consequent,
|
||||||
|
alternate,
|
||||||
|
);
|
||||||
|
stmts[i] = ctx.ast.statement_return(if_stmt.span, Some(argument));
|
||||||
|
self.changed = true;
|
||||||
|
break;
|
||||||
|
} else if else_branch.is_some() && Self::statement_must_exit_parent(then_branch) {
|
||||||
|
let Statement::IfStatement(if_stmt) = &mut stmts[i] else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let else_branch = if_stmt.alternate.take().unwrap();
|
||||||
|
stmts.insert(i + 1, else_branch);
|
||||||
|
self.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_return_block(stmt: &Statement<'a>) -> bool {
|
||||||
|
match stmt {
|
||||||
|
Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => {
|
||||||
|
matches!(block_stmt.body[0], Statement::ReturnStatement(_))
|
||||||
|
}
|
||||||
|
Statement::ReturnStatement(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_return_expression(stmt: &Statement<'a>) -> bool {
|
||||||
|
matches!(stmt, Statement::ReturnStatement(return_stmt) if return_stmt.argument.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn statement_must_exit_parent(stmt: &Statement<'a>) -> bool {
|
||||||
|
match stmt {
|
||||||
|
Statement::ThrowStatement(_) | Statement::ReturnStatement(_) => true,
|
||||||
|
Statement::BlockStatement(block_stmt) => {
|
||||||
|
block_stmt.body.last().is_some_and(Self::statement_must_exit_parent)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_block_return_expression(
|
||||||
|
stmt: &mut Statement<'a>,
|
||||||
|
ctx: &mut TraverseCtx<'a>,
|
||||||
|
) -> Expression<'a> {
|
||||||
|
match stmt {
|
||||||
|
Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => {
|
||||||
|
if let Statement::ReturnStatement(_) = &mut block_stmt.body[0] {
|
||||||
|
Self::take_return_argument(stmt, ctx)
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::ReturnStatement(_) => Self::take_return_argument(stmt, ctx),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_return_argument(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
||||||
|
let Statement::ReturnStatement(return_stmt) = ctx.ast.move_statement(stmt) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let return_stmt = return_stmt.unbox();
|
||||||
|
match return_stmt.argument {
|
||||||
|
Some(e) => e,
|
||||||
|
None => ctx.ast.void_0(return_stmt.span),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeMinimizeConditionsTest.java>
|
/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeMinimizeConditionsTest.java>
|
||||||
|
|
@ -226,7 +339,6 @@ mod test {
|
||||||
|
|
||||||
/** Try to minimize returns */
|
/** Try to minimize returns */
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn test_fold_returns() {
|
fn test_fold_returns() {
|
||||||
fold("function f(){if(x)return 1;else return 2}", "function f(){return x?1:2}");
|
fold("function f(){if(x)return 1;else return 2}", "function f(){return x?1:2}");
|
||||||
fold("function f(){if(x)return 1;return 2}", "function f(){return x?1:2}");
|
fold("function f(){if(x)return 1;return 2}", "function f(){return x?1:2}");
|
||||||
|
|
@ -238,10 +350,10 @@ mod test {
|
||||||
"function f(){return x?(y+=1):(y+=2)}",
|
"function f(){return x?(y+=1):(y+=2)}",
|
||||||
);
|
);
|
||||||
|
|
||||||
fold("function f(){if(x)return;else return 2-x}", "function f(){if(x);else return 2-x}");
|
fold("function f(){if(x)return;else return 2-x}", "function f(){return x?void 0:2-x}");
|
||||||
fold("function f(){if(x)return;return 2-x}", "function f(){return x?void 0:2-x}");
|
fold("function f(){if(x)return;return 2-x}", "function f(){return x?void 0:2-x}");
|
||||||
fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;{}}");
|
fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;return;}");
|
||||||
fold("function f(){if(x)return x;return}", "function f(){if(x)return x}");
|
fold("function f(){if(x)return x;return}", "function f(){if(x)return x;return}");
|
||||||
|
|
||||||
fold_same("function f(){for(var x in y) { return x.y; } return k}");
|
fold_same("function f(){for(var x in y) { return x.y; } return k}");
|
||||||
}
|
}
|
||||||
|
|
@ -347,7 +459,6 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn test_fold_returns_integration2() {
|
fn test_fold_returns_integration2() {
|
||||||
// late = true;
|
// late = true;
|
||||||
// disableNormalize();
|
// disableNormalize();
|
||||||
|
|
@ -359,7 +470,6 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn test_dont_remove_duplicate_statements_without_normalization() {
|
fn test_dont_remove_duplicate_statements_without_normalization() {
|
||||||
// In the following test case, we can't remove the duplicate "alert(x);" lines since each "x"
|
// In the following test case, we can't remove the duplicate "alert(x);" lines since each "x"
|
||||||
// refers to a different variable.
|
// refers to a different variable.
|
||||||
|
|
@ -516,13 +626,11 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn test_preserve_if() {
|
fn test_preserve_if() {
|
||||||
fold_same("if(!a&&!b)for(;f(););");
|
fold_same("if(!a&&!b)for(;f(););");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn test_no_swap_with_dangling_else() {
|
fn test_no_swap_with_dangling_else() {
|
||||||
fold_same("if(!x) {for(;;)foo(); for(;;)bar()} else if(y) for(;;) f()");
|
fold_same("if(!x) {for(;;)foo(); for(;;)bar()} else if(y) for(;;) f()");
|
||||||
fold_same("if(!a&&!b) {for(;;)foo(); for(;;)bar()} else if(y) for(;;) f()");
|
fold_same("if(!a&&!b) {for(;;)foo(); for(;;)bar()} else if(y) for(;;) f()");
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
| Oxc | ESBuild | Oxc | ESBuild |
|
| Oxc | ESBuild | Oxc | ESBuild |
|
||||||
Original | minified | minified | gzip | gzip | Fixture
|
Original | minified | minified | gzip | gzip | Fixture
|
||||||
-------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------
|
||||||
72.14 kB | 23.89 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js
|
72.14 kB | 23.85 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js
|
||||||
|
|
||||||
173.90 kB | 61.39 kB | 59.82 kB | 19.60 kB | 19.33 kB | moment.js
|
173.90 kB | 60.60 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js
|
||||||
|
|
||||||
287.63 kB | 92.05 kB | 90.07 kB | 32.44 kB | 31.95 kB | jquery.js
|
287.63 kB | 91.40 kB | 90.07 kB | 32.36 kB | 31.95 kB | jquery.js
|
||||||
|
|
||||||
342.15 kB | 120.72 kB | 118.14 kB | 44.86 kB | 44.37 kB | vue.js
|
342.15 kB | 120.07 kB | 118.14 kB | 44.79 kB | 44.37 kB | vue.js
|
||||||
|
|
||||||
544.10 kB | 73.15 kB | 72.48 kB | 26.28 kB | 26.20 kB | lodash.js
|
544.10 kB | 72.91 kB | 72.48 kB | 26.27 kB | 26.20 kB | lodash.js
|
||||||
|
|
||||||
555.77 kB | 275.48 kB | 270.13 kB | 91.48 kB | 90.80 kB | d3.js
|
555.77 kB | 275.31 kB | 270.13 kB | 91.45 kB | 90.80 kB | d3.js
|
||||||
|
|
||||||
1.01 MB | 465.45 kB | 458.89 kB | 127.12 kB | 126.71 kB | bundle.min.js
|
1.01 MB | 462.98 kB | 458.89 kB | 127.06 kB | 126.71 kB | bundle.min.js
|
||||||
|
|
||||||
1.25 MB | 659.74 kB | 646.76 kB | 164.45 kB | 163.73 kB | three.js
|
1.25 MB | 658.98 kB | 646.76 kB | 164.40 kB | 163.73 kB | three.js
|
||||||
|
|
||||||
2.14 MB | 739.59 kB | 724.14 kB | 181.75 kB | 181.07 kB | victory.js
|
2.14 MB | 736.98 kB | 724.14 kB | 181.33 kB | 181.07 kB | victory.js
|
||||||
|
|
||||||
3.20 MB | 1.02 MB | 1.01 MB | 332.98 kB | 331.56 kB | echarts.js
|
3.20 MB | 1.02 MB | 1.01 MB | 332.77 kB | 331.56 kB | echarts.js
|
||||||
|
|
||||||
6.69 MB | 2.39 MB | 2.31 MB | 496.49 kB | 488.28 kB | antd.js
|
6.69 MB | 2.38 MB | 2.31 MB | 495.91 kB | 488.28 kB | antd.js
|
||||||
|
|
||||||
10.95 MB | 3.54 MB | 3.49 MB | 912.49 kB | 915.50 kB | typescript.js
|
10.95 MB | 3.52 MB | 3.49 MB | 911.59 kB | 915.50 kB | typescript.js
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue