Skip to content

Commit

Permalink
feat(minifier): implement part of StatementFusion (#5936)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Sep 21, 2024
1 parent ff7d9c1 commit 9076dee
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ lint = "clippy --workspace --all-targets --all-features"
codecov = "llvm-cov --workspace --ignore-filename-regex tasks"
coverage = "run -p oxc_coverage --profile coverage --"
benchmark = "run -p oxc_benchmark --release --"
minsize = "run -p oxc_minsize --release --"
minsize = "run -p oxc_minsize --profile coverage --"
rule = "run -p rulegen"

# Build oxlint in release mode
Expand Down
150 changes: 132 additions & 18 deletions crates/oxc_minifier/src/ast_passes/statement_fusion.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use oxc_traverse::Traverse;
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_span::SPAN;
use oxc_traverse::{Traverse, TraverseCtx};

use crate::CompressorPass;

Expand All @@ -11,12 +14,135 @@ pub struct StatementFusion;

impl<'a> CompressorPass<'a> for StatementFusion {}

impl<'a> Traverse<'a> for StatementFusion {}
impl<'a> Traverse<'a> for StatementFusion {
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
Self::fuse_statements(&mut program.body, ctx);
}

fn exit_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) {
Self::fuse_statements(&mut body.statements, ctx);
}

fn exit_block_statement(&mut self, block: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) {
Self::fuse_statements(&mut block.body, ctx);
}
}

impl StatementFusion {
impl<'a> StatementFusion {
pub fn new() -> Self {
Self {}
}

fn fuse_statements(stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
if Self::can_fuse_into_one_statement(stmts) {
Self::fuse_into_one_statement(stmts, ctx);
}
}

fn can_fuse_into_one_statement(stmts: &[Statement<'a>]) -> bool {
let len = stmts.len();
if len <= 1 {
return false;
}
if stmts[0..len - 1].iter().any(|s| !matches!(s, Statement::ExpressionStatement(_))) {
return false;
}
Self::is_fusable_control_statement(&stmts[len - 1])
}

fn is_fusable_control_statement(stmt: &Statement<'a>) -> bool {
match stmt {
Statement::ExpressionStatement(_)
| Statement::IfStatement(_)
| Statement::ThrowStatement(_)
| Statement::SwitchStatement(_) => true,
Statement::ReturnStatement(return_stmt) => return_stmt.argument.is_some(),
// Statement::ForStatement(for_stmt) => {
// // Avoid cases where we have for(var x;_;_) { ....
// for_stmt.init.is_none()
// || for_stmt.init.as_ref().is_some_and(ForStatementInit::is_expression)
// }
// Statement::ForInStatement(for_in_stmt) => {
// TODO
// }
Statement::LabeledStatement(labeled_stmt) => {
Self::is_fusable_control_statement(&labeled_stmt.body)
}
// Statement::BlockStatement(_) => {
// TODO
// }
_ => false,
}
}

fn fuse_into_one_statement(stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
let len = stmts.len();
let mut expressions = ctx.ast.vec();

for stmt in stmts.iter_mut().take(len - 1) {
match stmt {
Statement::ExpressionStatement(expr_stmt) => {
if let Expression::SequenceExpression(sequence_expr) = &mut expr_stmt.expression
{
expressions.extend(
sequence_expr
.expressions
.iter_mut()
.map(|e| ctx.ast.move_expression(e)),
);
} else {
expressions.push(ctx.ast.move_expression(&mut expr_stmt.expression));
}
*stmt = ctx.ast.statement_empty(SPAN);
}
_ => unreachable!(),
}
}

let last = stmts.last_mut().unwrap();
Self::fuse_expression_into_control_flow_statement(last, expressions, ctx);

*stmts = ctx.ast.vec1(ctx.ast.move_statement(last));
}

fn fuse_expression_into_control_flow_statement(
stmt: &mut Statement<'a>,
exprs: Vec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
let mut exprs = exprs;
let expr = match stmt {
Statement::ExpressionStatement(expr_stmt) => &mut expr_stmt.expression,
Statement::IfStatement(if_stmt) => &mut if_stmt.test,
Statement::ThrowStatement(throw_stmt) => &mut throw_stmt.argument,
Statement::SwitchStatement(switch_stmt) => &mut switch_stmt.discriminant,
Statement::ReturnStatement(return_stmt) => return_stmt.argument.as_mut().unwrap(),
// Statement::ForStatement(for_stmt) => {
// if let Some(init) = for_stmt.init.as_mut() {
// init.as_expression_mut().unwrap()
// } else {
// for_stmt.init =
// Some(ctx.ast.for_statement_init_expression(
// ctx.ast.expression_sequence(SPAN, exprs),
// ));
// return;
// }
// }
Statement::LabeledStatement(labeled_stmt) => {
Self::fuse_expression_into_control_flow_statement(
&mut labeled_stmt.body,
exprs,
ctx,
);
return;
}
_ => {
unreachable!("must match with `Self::is_fusable_control_statement`");
}
};
exprs.push(ctx.ast.move_expression(expr));
*expr = ctx.ast.expression_sequence(SPAN, exprs);
}
}

#[cfg(test)]
Expand Down Expand Up @@ -47,7 +173,6 @@ mod test {
}

#[test]
#[ignore]
fn nothing_to_do() {
fuse_same("");
fuse_same("a");
Expand All @@ -56,7 +181,6 @@ mod test {
}

#[test]
#[ignore]
fn fold_block_with_statements() {
fuse("a;b;c", "a,b,c");
fuse("a();b();c();", "a(),b(),c()");
Expand All @@ -66,7 +190,6 @@ mod test {
}

#[test]
#[ignore]
fn fold_block_into_if() {
fuse("a;b;c;if(x){}", "if(a,b,c,x){}");
fuse("a;b;c;if(x,y){}else{}", "if(a,b,c,x,y){}else{}");
Expand All @@ -78,7 +201,6 @@ mod test {
}

#[test]
#[ignore]
fn fold_block_return() {
fuse("a;b;c;return x", "return a,b,c,x");
fuse("a;b;c;return x+y", "return a,b,c,x+y");
Expand All @@ -88,15 +210,13 @@ mod test {
}

#[test]
#[ignore]
fn fold_block_throw() {
fuse("a;b;c;throw x", "throw a,b,c,x");
fuse("a;b;c;throw x+y", "throw a,b,c,x+y");
fuse_same("a;b;c;throw x;a;b;c");
}

#[test]
#[ignore]
fn fold_switch() {
fuse("a;b;c;switch(x){}", "switch(a,b,c,x){}");
}
Expand All @@ -108,7 +228,6 @@ mod test {
}

#[test]
#[ignore]
fn fuse_into_for_in2() {
// This test case causes a parse warning in ES5 strict out, but is a parse error in ES6+ out.
// setAcceptedLanguage(CompilerOptions.LanguageMode.ECMASCRIPT5_STRICT);
Expand All @@ -134,11 +253,10 @@ mod test {
}

#[test]
#[ignore]
fn fuse_into_label() {
fuse("a;b;c;label:for(x in y){}", "label:for(x in a,b,c,y){}");
fuse("a;b;c;label:for(;g;){}", "label:for(a,b,c;g;){}");
fuse("a;b;c;l1:l2:l3:for(;g;){}", "l1:l2:l3:for(a,b,c;g;){}");
// fuse("a;b;c;label:for(x in y){}", "label:for(x in a,b,c,y){}");
// fuse("a;b;c;label:for(;g;){}", "label:for(a,b,c;g;){}");
// fuse("a;b;c;l1:l2:l3:for(;g;){}", "l1:l2:l3:for(a,b,c;g;){}");
fuse_same("a;b;c;label:while(true){}");
}

Expand All @@ -155,13 +273,11 @@ mod test {
}

#[test]
#[ignore]
fn no_fuse_into_while() {
fuse_same("a;b;c;while(x){}");
}

#[test]
#[ignore]
fn no_fuse_into_do() {
fuse_same("a;b;c;do{}while(x)");
}
Expand Down Expand Up @@ -191,13 +307,11 @@ mod test {
}

#[test]
#[ignore]
fn no_global_scope_changes() {
test_same("a,b,c");
}

#[test]
#[ignore]
fn no_function_block_changes() {
test_same("function foo() { a,b,c }");
}
Expand Down
3 changes: 2 additions & 1 deletion crates/oxc_minifier/src/compressor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl<'a> Compressor<'a> {
self.fold_constants(program, &mut ctx);
self.minimize_conditions(program, &mut ctx);
self.remove_dead_code(program, &mut ctx);
self.statement_fusion(program, &mut ctx);
// self.statement_fusion(program, &mut ctx);
self.substitute_alternate_syntax(program, &mut ctx);
self.collapse_variable_declarations(program, &mut ctx);
self.exploit_assigns(program, &mut ctx);
Expand Down Expand Up @@ -77,6 +77,7 @@ impl<'a> Compressor<'a> {
PeepholeRemoveDeadCode::new().build(program, ctx);
}

#[allow(unused)]
fn statement_fusion(&self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
StatementFusion::new().build(program, ctx);
}
Expand Down

0 comments on commit 9076dee

Please sign in to comment.