mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
feat(minifier): process of return/throw in non last position and fix async * inlining (#26214)
This Pr brings couple optimizations and refactor, - Disable merging: `if (a) return x; return;` and `if (a) return; return x;` when is descendant of `async* function` - Handle empty chains: `if (a) return; return;` → `a; return;` - Process returns/throw anywhere in block via handler merge `if (a) return; return; var b` → `a; return; var b` fixes #26255 ref #24331
This commit is contained in:
1 parent
f6b75b4a6b
commit
a17de58e30
4 files changed
+161
-168
No files matched your search
@@ -110,120 +110,6 @@ impl<'a> PeepholeOptimizations {
|
||||
let dropped = stmts.pop().unwrap();
|
||||
ctx.drop_statement(&dropped);
|
||||
}
|
||||
|
||||
// Merge certain statements in reverse order
|
||||
if stmts.len() >= 2 && ctx.options().sequences {
|
||||
if let Some(Statement::ReturnStatement(_)) = stmts.last() {
|
||||
'return_loop: while stmts.len() >= 2 {
|
||||
let prev_index = stmts.len() - 2;
|
||||
let prev_stmt = &stmts[prev_index];
|
||||
match prev_stmt {
|
||||
// Merge the last two statements
|
||||
Statement::IfStatement(if_stmt) => {
|
||||
// The previous statement must be an if statement with no else clause
|
||||
if if_stmt.alternate.is_some() {
|
||||
break 'return_loop;
|
||||
}
|
||||
// The then clause must be a return
|
||||
let Statement::ReturnStatement(_) = &if_stmt.consequent else {
|
||||
break 'return_loop;
|
||||
};
|
||||
if let Some(Statement::ReturnStatement(last_return)) = stmts.last()
|
||||
&& let Some(arg) = &last_return.argument
|
||||
&& Self::conditional_expression_count_exceeded(arg)
|
||||
{
|
||||
break 'return_loop;
|
||||
}
|
||||
|
||||
ctx.notice_change();
|
||||
let last_stmt = stmts.pop().unwrap();
|
||||
let Statement::ReturnStatement(last_return) = last_stmt else {
|
||||
unreachable!()
|
||||
};
|
||||
let prev_stmt = stmts.pop().unwrap();
|
||||
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
|
||||
let prev_if = prev_if.unbox();
|
||||
let Statement::ReturnStatement(prev_return) = prev_if.consequent else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let left_span = prev_return.span;
|
||||
let right_span = last_return.span;
|
||||
// "if (a) return; return b;" => "return a ? void 0 : b;"
|
||||
let left = prev_return
|
||||
.unbox()
|
||||
.argument
|
||||
.unwrap_or_else(|| Expression::new_void_0(left_span, ctx));
|
||||
// "if (a) return a; return;" => "return a ? b : void 0;"
|
||||
let right = last_return
|
||||
.unbox()
|
||||
.argument
|
||||
.unwrap_or_else(|| Expression::new_void_0(right_span, ctx));
|
||||
|
||||
let argument = Self::minimize_conditional_after_if(
|
||||
prev_if.span,
|
||||
prev_if.test,
|
||||
left,
|
||||
right,
|
||||
ctx,
|
||||
);
|
||||
let last_return_stmt =
|
||||
Statement::new_return_statement(right_span, Some(argument), ctx);
|
||||
stmts.push(last_return_stmt);
|
||||
}
|
||||
_ => break 'return_loop,
|
||||
}
|
||||
}
|
||||
} else if let Some(Statement::ThrowStatement(_)) = stmts.last() {
|
||||
'throw_loop: while stmts.len() >= 2 {
|
||||
let prev_index = stmts.len() - 2;
|
||||
let prev_stmt = &stmts[prev_index];
|
||||
match prev_stmt {
|
||||
// Merge the last two statements
|
||||
Statement::IfStatement(if_stmt) => {
|
||||
// The previous statement must be an if statement with no else clause
|
||||
if if_stmt.alternate.is_some() {
|
||||
break 'throw_loop;
|
||||
}
|
||||
// The then clause must be a throw
|
||||
let Statement::ThrowStatement(_) = &if_stmt.consequent else {
|
||||
break 'throw_loop;
|
||||
};
|
||||
if let Some(Statement::ThrowStatement(last_throw)) = stmts.last()
|
||||
&& Self::conditional_expression_count_exceeded(&last_throw.argument)
|
||||
{
|
||||
break 'throw_loop;
|
||||
}
|
||||
|
||||
ctx.notice_change();
|
||||
let last_stmt = stmts.pop().unwrap();
|
||||
let Statement::ThrowStatement(last_throw) = last_stmt else {
|
||||
unreachable!()
|
||||
};
|
||||
let prev_stmt = stmts.pop().unwrap();
|
||||
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
|
||||
let prev_if = prev_if.unbox();
|
||||
let Statement::ThrowStatement(prev_throw) = prev_if.consequent else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let right_span = last_throw.span;
|
||||
let argument = Self::minimize_conditional_after_if(
|
||||
prev_if.span,
|
||||
prev_if.test,
|
||||
prev_throw.unbox().argument,
|
||||
last_throw.unbox().argument,
|
||||
ctx,
|
||||
);
|
||||
let last_throw_stmt =
|
||||
Statement::new_throw_statement(right_span, argument, ctx);
|
||||
stmts.push(last_throw_stmt);
|
||||
}
|
||||
_ => break 'throw_loop,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Some parsers cannot parse long conditional expressions.
|
||||
@@ -781,26 +667,12 @@ impl<'a> PeepholeOptimizations {
|
||||
|
||||
if !if_stmt.alternate.as_ref().is_none_or(Self::statement_cares_about_scope)
|
||||
&& if_stmt.consequent.is_terminated()
|
||||
&& let Some(stmt) = if_stmt.alternate.take()
|
||||
{
|
||||
// "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));
|
||||
loop {
|
||||
if let Some(Statement::IfStatement(if_stmt)) = result.last_mut()
|
||||
&& !if_stmt.alternate.as_ref().is_none_or(Self::statement_cares_about_scope)
|
||||
&& if_stmt.consequent.is_terminated()
|
||||
&& let Some(stmt) = if_stmt.alternate.take()
|
||||
{
|
||||
if let Statement::BlockStatement(block_stmt) = stmt {
|
||||
Self::handle_block(result, block_stmt, ctx);
|
||||
} else {
|
||||
result.push(stmt);
|
||||
ctx.notice_change();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return ControlFlow::Continue(());
|
||||
return Self::minimize_statement(stmt, i, stmts, result, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,6 +688,8 @@ impl<'a> PeepholeOptimizations {
|
||||
) {
|
||||
if let Some(ret_argument_expr) = &mut ret_stmt.argument {
|
||||
Self::substitute_single_use_symbol_in_statement(ret_argument_expr, result, ctx, false);
|
||||
// `var a; return a = b(), c;` => `var a = b(); return c;`
|
||||
Self::merge_leading_assignments_to_declaration(ret_argument_expr, false, result, ctx);
|
||||
}
|
||||
|
||||
if let Some(argument) = &mut ret_stmt.argument
|
||||
@@ -839,13 +713,9 @@ impl<'a> PeepholeOptimizations {
|
||||
if let Some(old) = ret_stmt.argument.take() {
|
||||
ctx.drop_expression(&old);
|
||||
}
|
||||
result.push(Statement::ReturnStatement(ret_stmt));
|
||||
return;
|
||||
}
|
||||
|
||||
if ctx.options().sequences
|
||||
&& let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut()
|
||||
} else if ctx.options().sequences
|
||||
&& let Some(argument) = &mut ret_stmt.argument
|
||||
&& let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut()
|
||||
{
|
||||
let a = &mut prev_expr_stmt.expression;
|
||||
let new_arg = Self::join_sequence(a, argument, ctx);
|
||||
@@ -853,9 +723,86 @@ impl<'a> PeepholeOptimizations {
|
||||
result.pop();
|
||||
}
|
||||
|
||||
// "var a; return a = b(), c;" => "var a = b(); return c;"
|
||||
if let Some(argument) = &mut ret_stmt.argument {
|
||||
Self::merge_leading_assignments_to_declaration(argument, false, result, ctx);
|
||||
// `if (a) return b; return c;` => `return a ? b : c;`
|
||||
// `if (a) return; return;` => `a; return;`
|
||||
if ctx.options().sequences {
|
||||
'return_loop: while !result.is_empty() {
|
||||
if let Some(Statement::IfStatement(if_stmt)) = result.last()
|
||||
&& if_stmt.alternate.is_none()
|
||||
&& let Statement::ReturnStatement(prev_return) = &if_stmt.consequent
|
||||
{
|
||||
let prev_has_arg = prev_return.argument.is_some();
|
||||
let ret_has_arg = ret_stmt.argument.is_some();
|
||||
|
||||
if !prev_has_arg && !ret_has_arg {
|
||||
// `if (a) return; return;` => `a; return;`
|
||||
ctx.notice_change();
|
||||
let prev_stmt = result.pop().unwrap();
|
||||
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
|
||||
let prev_if = prev_if.unbox();
|
||||
|
||||
let test_expr = prev_if.test;
|
||||
result.push(Statement::new_expression_statement(
|
||||
test_expr.span(),
|
||||
test_expr,
|
||||
ctx,
|
||||
));
|
||||
break 'return_loop;
|
||||
}
|
||||
|
||||
// do not collapse if conditional count exceeded
|
||||
if ret_stmt
|
||||
.argument
|
||||
.as_ref()
|
||||
.is_some_and(Self::conditional_expression_count_exceeded)
|
||||
{
|
||||
break 'return_loop;
|
||||
}
|
||||
// do not collapse if return could be removed
|
||||
if !ret_has_arg && ctx.parent().is_function_body() {
|
||||
break 'return_loop;
|
||||
}
|
||||
// do not collapse if parent is async generator and any of arguments is empty
|
||||
if (!prev_has_arg || !ret_has_arg)
|
||||
&& ctx.is_closest_function_scope_an_async_generator()
|
||||
{
|
||||
break 'return_loop;
|
||||
}
|
||||
|
||||
// `if (a) return b; return c;` => `return a ? b : c;`
|
||||
ctx.notice_change();
|
||||
let prev_stmt = result.pop().unwrap();
|
||||
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
|
||||
let prev_if = prev_if.unbox();
|
||||
let Statement::ReturnStatement(mut prev_return) = prev_if.consequent else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let left_span = prev_return.span();
|
||||
let right_span = ret_stmt.span();
|
||||
// "if (a) return; return b;" => "return a ? void 0 : b;"
|
||||
let left = prev_return
|
||||
.argument
|
||||
.take()
|
||||
.unwrap_or_else(|| Expression::new_void_0(left_span, ctx));
|
||||
// "if (a) return a; return;" => "return a ? b : void 0;"
|
||||
let right = ret_stmt
|
||||
.argument
|
||||
.take()
|
||||
.unwrap_or_else(|| Expression::new_void_0(right_span, ctx));
|
||||
|
||||
let argument = Self::minimize_conditional_after_if(
|
||||
prev_if.span,
|
||||
prev_if.test,
|
||||
left,
|
||||
right,
|
||||
ctx,
|
||||
);
|
||||
ret_stmt.argument = Some(argument);
|
||||
} else {
|
||||
break 'return_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.push(Statement::ReturnStatement(ret_stmt));
|
||||
@@ -874,6 +821,7 @@ impl<'a> PeepholeOptimizations {
|
||||
false,
|
||||
);
|
||||
|
||||
// `a; throw x` => `throw a, x`
|
||||
if ctx.options().sequences
|
||||
&& let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut()
|
||||
{
|
||||
@@ -884,7 +832,7 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx.drop_statement(&dropped);
|
||||
}
|
||||
|
||||
// "var a; throw a = b(), c;" => "var a = b(); throw c;"
|
||||
// `var a; throw a = b(), c;` => `var a = b(); throw c;`
|
||||
Self::merge_leading_assignments_to_declaration(
|
||||
&mut throw_stmt.argument,
|
||||
false,
|
||||
@@ -892,6 +840,37 @@ impl<'a> PeepholeOptimizations {
|
||||
ctx,
|
||||
);
|
||||
|
||||
// `if (a) throw b; throw c;` => `throw a ? b : c;`
|
||||
if ctx.options().sequences {
|
||||
'Loop: while !result.is_empty() {
|
||||
if let Some(Statement::IfStatement(if_stmt)) = result.last()
|
||||
&& if_stmt.alternate.is_none()
|
||||
&& matches!(&if_stmt.consequent, Statement::ThrowStatement(_))
|
||||
&& !Self::conditional_expression_count_exceeded(&throw_stmt.argument)
|
||||
{
|
||||
ctx.notice_change();
|
||||
let prev_stmt = result.pop().unwrap();
|
||||
let Statement::IfStatement(prev_if) = prev_stmt else { unreachable!() };
|
||||
let prev_if = prev_if.unbox();
|
||||
let Statement::ThrowStatement(prev_throw) = prev_if.consequent else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
ctx.replace_expression_with(&mut throw_stmt.argument, |expr, ctx| {
|
||||
Self::minimize_conditional_after_if(
|
||||
prev_if.span,
|
||||
prev_if.test,
|
||||
prev_throw.unbox().argument,
|
||||
expr,
|
||||
ctx,
|
||||
)
|
||||
});
|
||||
} else {
|
||||
break 'Loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.push(Statement::ThrowStatement(throw_stmt));
|
||||
}
|
||||
|
||||
|
||||
@@ -110,13 +110,10 @@ fn test_function_return_optimization() {
|
||||
"function f(){g:if(a()){return;}else{return;} return;}",
|
||||
"function f(){g:if(a())return;else return}",
|
||||
); // function f(){g:a()}
|
||||
test(
|
||||
"function f(){g:{if(a()){return;}else{return;} return;}}",
|
||||
"function f(){g:return a(),void 0}",
|
||||
); // function f(){g:a()}
|
||||
test("function f(){g:{if(a()){return;}else{return;} return;}}", "function f(){g:{a();return}}"); // function f(){g:a()}
|
||||
test(
|
||||
"function f(){g:{a();if(b()){return;}else{return;} return;}}",
|
||||
"function f(){g:return a(),b(),void 0}",
|
||||
"function f(){g:{a(),b();return}}",
|
||||
); // function f(){g:a(),b()}
|
||||
test(
|
||||
"function f(){try{g:if(a()){throw 9;} return;}finally{return}}",
|
||||
@@ -126,6 +123,23 @@ fn test_function_return_optimization() {
|
||||
"function g(a,b){if(a){}else if(b){return()=>typeof f}else function f(){}}",
|
||||
"function g(a,b){if(!a){if(b)return()=>typeof f;else function f(){}}}",
|
||||
);
|
||||
|
||||
test("function g(){if(a)return b;return c;var x}", "function g(){return a?b:c;var x;}");
|
||||
test("function g(){if(a)return;return;var x}", "function g(){if(!a){return;var x;}}");
|
||||
test("function g(){if(a)throw b;throw c;var x}", "function g(){throw a?b:c;var x;}");
|
||||
test(
|
||||
"function g(){if(d){if(a)return b;return;}return c;}",
|
||||
"function g(){return d?a?b:void 0:c;}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_gen_function_return() {
|
||||
test("async function* foo(){if(a)return b;return c;}", "async function* foo(){return a?b:c;}");
|
||||
test("async function* foo(){if(a)return;return c;}", "async function* foo(){if(!a)return c;}");
|
||||
test("async function* foo(){if(a)return b;return;}", "async function* foo(){if(a)return b;}");
|
||||
test("async function* foo(){if(a)return;return;}", "async function* foo(){a;}");
|
||||
test_same("async function* foo(){if(a){if(b)return;return d;}return e;}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -7,21 +7,21 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
|
||||
|
||||
287.63 kB | 89.01 kB | 90.07 kB | 30.85 kB | 31.95 kB | 2 | jquery.js
|
||||
|
||||
342.15 kB | 114.81 kB | 118.14 kB | 42.71 kB | 44.37 kB | 2 | vue.js
|
||||
342.15 kB | 114.80 kB | 118.14 kB | 42.71 kB | 44.37 kB | 2 | vue.js
|
||||
|
||||
544.10 kB | 69.88 kB | 72.48 kB | 25.58 kB | 26.20 kB | 2 | lodash.js
|
||||
|
||||
555.77 kB | 262.75 kB | 270.13 kB | 87.66 kB | 90.80 kB | 2 | d3.js
|
||||
|
||||
1.01 MB | 436.04 kB | 458.89 kB | 121.78 kB | 126.71 kB | 2 | bundle.min.js
|
||||
1.01 MB | 435.99 kB | 458.89 kB | 121.78 kB | 126.71 kB | 2 | bundle.min.js
|
||||
|
||||
1.25 MB | 634.39 kB | 646.76 kB | 158.43 kB | 163.73 kB | 2 | three.js
|
||||
|
||||
2.14 MB | 707.63 kB | 724.14 kB | 160.08 kB | 181.07 kB | 2 | victory.js
|
||||
|
||||
3.20 MB | 958.92 kB | 1.01 MB | 317.07 kB | 331.56 kB | 2 | echarts.js
|
||||
3.20 MB | 958.89 kB | 1.01 MB | 317.07 kB | 331.56 kB | 2 | echarts.js
|
||||
|
||||
6.69 MB | 2.12 MB | 2.31 MB | 453.44 kB | 488.28 kB | 5 | antd.js
|
||||
|
||||
10.95 MB | 3.31 MB | 3.49 MB | 850.04 kB | 915.50 kB | 4 | typescript.js
|
||||
10.95 MB | 3.31 MB | 3.49 MB | 849.97 kB | 915.50 kB | 4 | typescript.js
|
||||
|
||||
@@ -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: 144024
|
||||
arena reallocs: 28468
|
||||
arena size: 19127248 # 19.13 MB
|
||||
arena allocs: 143560
|
||||
arena reallocs: 28269
|
||||
arena size: 19100024 # 19.10 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: 15675
|
||||
arena reallocs: 2721
|
||||
arena size: 2880752 # 2.88 MB
|
||||
arena allocs: 15652
|
||||
arena reallocs: 2709
|
||||
arena size: 2879680 # 2.88 MB
|
||||
|
||||
RadixUIAdoptionSection.jsx:
|
||||
file size: 2520 # 2.52 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: 44801
|
||||
arena reallocs: 7718
|
||||
arena size: 6306768 # 6.31 MB
|
||||
arena allocs: 44756
|
||||
arena reallocs: 7699
|
||||
arena size: 6304256 # 6.30 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: 351645
|
||||
arena reallocs: 76248
|
||||
arena size: 48770232 # 48.77 MB
|
||||
arena allocs: 351162
|
||||
arena reallocs: 75941
|
||||
arena size: 48738712 # 48.74 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: 6809
|
||||
arena reallocs: 842
|
||||
arena size: 1164432 # 1.16 MB
|
||||
arena allocs: 6789
|
||||
arena reallocs: 834
|
||||
arena size: 1162400 # 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: 64956
|
||||
arena reallocs: 12252
|
||||
arena size: 9393120 # 9.39 MB
|
||||
arena allocs: 64844
|
||||
arena reallocs: 12187
|
||||
arena size: 9385624 # 9.39 MB
|
||||
Reference in new issue
Block a user