Skip to content

Commit

Permalink
feat(minifier): initialize conditions folding (#658)
Browse files Browse the repository at this point in the history
related: #401
  • Loading branch information
cijiugechu authored Jul 29, 2023
1 parent ee211ba commit e090b56
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 13 deletions.
50 changes: 49 additions & 1 deletion crates/oxc_hir/src/span.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use oxc_span::{GetSpan, Span};

use crate::hir::{Expression, MemberExpression};
use crate::hir::{Declaration, Expression, MemberExpression, ModuleDeclaration, Statement};

impl<'a> GetSpan for Expression<'a> {
fn span(&self) -> Span {
Expand Down Expand Up @@ -52,3 +52,51 @@ impl<'a> GetSpan for MemberExpression<'a> {
}
}
}

impl<'a> GetSpan for Statement<'a> {
fn span(&self) -> Span {
match self {
Self::BlockStatement(stmt) => stmt.span,
Self::BreakStatement(stmt) => stmt.span,
Self::ContinueStatement(stmt) => stmt.span,
Self::DebuggerStatement(stmt) => stmt.span,
Self::Declaration(stmt) => stmt.span(),
Self::DoWhileStatement(stmt) => stmt.span,
Self::ExpressionStatement(stmt) => stmt.span,
Self::ForInStatement(stmt) => stmt.span,
Self::ForOfStatement(stmt) => stmt.span,
Self::ForStatement(stmt) => stmt.span,
Self::IfStatement(stmt) => stmt.span,
Self::LabeledStatement(stmt) => stmt.span,
Self::ModuleDeclaration(decl) => decl.span(),
Self::ReturnStatement(stmt) => stmt.span,
Self::SwitchStatement(stmt) => stmt.span,
Self::ThrowStatement(stmt) => stmt.span,
Self::TryStatement(stmt) => stmt.span,
Self::WhileStatement(stmt) => stmt.span,
Self::WithStatement(stmt) => stmt.span,
}
}
}

impl<'a> GetSpan for Declaration<'a> {
fn span(&self) -> Span {
match self {
Self::ClassDeclaration(decl) => decl.span,
Self::FunctionDeclaration(decl) => decl.span,
Self::TSEnumDeclaration(decl) => decl.span,
Self::VariableDeclaration(decl) => decl.span,
}
}
}

impl<'a> GetSpan for ModuleDeclaration<'a> {
fn span(&self) -> Span {
match self {
Self::ExportAllDeclaration(decl) => decl.span,
Self::ExportDefaultDeclaration(decl) => decl.span,
Self::ExportNamedDeclaration(decl) => decl.span,
Self::ImportDeclaration(decl) => decl.span,
}
}
}
93 changes: 81 additions & 12 deletions crates/oxc_minifier/src/compressor/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,29 +783,23 @@ impl<'a> Compressor<'a> {
) -> Option<Expression<'a>> {
let boolean_value = get_boolean_value(&logic_expr.left);

let mut move_out = |dest: &mut Expression<'a>| {
let null_literal = self.hir.null_literal(dest.span());
let null_expr = self.hir.literal_null_expression(null_literal);
mem::replace(dest, null_expr)
};

if let Some(boolean_value) = boolean_value {
// (TRUE || x) => TRUE (also, (3 || x) => 3)
// (FALSE && x) => FALSE
if (boolean_value && op == LogicalOperator::Or)
|| (!boolean_value && op == LogicalOperator::And)
{
return Some(move_out(&mut logic_expr.left));
return Some(self.move_out_expression(&mut logic_expr.left));
} else if !logic_expr.left.may_have_side_effects() {
// (FALSE || x) => x
// (TRUE && x) => x
return Some(move_out(&mut logic_expr.right));
return Some(self.move_out_expression(&mut logic_expr.right));
}
// Left side may have side effects, but we know its boolean value.
// e.g. true_with_sideeffects || foo() => true_with_sideeffects, foo()
// or: false_with_sideeffects && foo() => false_with_sideeffects, foo()
let left = move_out(&mut logic_expr.left);
let right = move_out(&mut logic_expr.right);
let left = self.move_out_expression(&mut logic_expr.left);
let right = self.move_out_expression(&mut logic_expr.right);
let mut vec = self.hir.new_vec_with_capacity(2);
vec.push(left);
vec.push(right);
Expand All @@ -822,8 +816,8 @@ impl<'a> Compressor<'a> {
if !right_boolean && left_child_op == LogicalOperator::Or
|| right_boolean && left_child_op == LogicalOperator::And
{
let left = move_out(&mut left_child.left);
let right = move_out(&mut logic_expr.right);
let left = self.move_out_expression(&mut left_child.left);
let right = self.move_out_expression(&mut logic_expr.right);
let logic_expr = self.hir.logical_expression(
logic_expr.span,
left,
Expand All @@ -838,4 +832,79 @@ impl<'a> Compressor<'a> {
}
None
}

pub(crate) fn fold_condition<'b>(&mut self, stmt: &'b mut Statement<'a>) {
match stmt {
Statement::WhileStatement(while_stmt) => {
let minimized_expr = self.fold_expression_in_condition(&mut while_stmt.0.test);

if let Some(min_expr) = minimized_expr {
while_stmt.0.test = min_expr;
}
}
Statement::ForStatement(for_stmt) => {
let test_expr = for_stmt.0.test.as_mut();

if let Some(test_expr) = test_expr {
let minimized_expr = self.fold_expression_in_condition(test_expr);

if let Some(min_expr) = minimized_expr {
for_stmt.0.test = Some(min_expr);
}
}
}
_ => {}
};
}

fn fold_expression_in_condition(
&mut self,
expr: &mut Expression<'a>,
) -> Option<Expression<'a>> {
let folded_expr = match expr {
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::LogicalNot => {
let should_fold = self.try_minimize_not(&mut unary_expr.0.argument);

if should_fold {
Some(self.move_out_expression(&mut unary_expr.0.argument))
} else {
None
}
}
_ => None,
},
_ => None,
};

folded_expr
}

fn move_out_expression(&mut self, expr: &mut Expression<'a>) -> Expression<'a> {
let null_literal = self.hir.null_literal(expr.span());
let null_expr = self.hir.literal_null_expression(null_literal);
mem::replace(expr, null_expr)
}

/// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeMinimizeConditions.java#L401-L435)
fn try_minimize_not(&mut self, expr: &mut Expression<'a>) -> bool {
let span = &mut expr.span();

match expr {
Expression::BinaryExpression(binary_expr) => {
let new_op = binary_expr.0.operator.equality_inverse_operator();

match new_op {
Some(new_op) => {
binary_expr.0.operator = new_op;
binary_expr.0.span = *span;

true
}
_ => false,
}
}
_ => false,
}
}
}
1 change: 1 addition & 0 deletions crates/oxc_minifier/src/compressor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ impl<'a, 'b> VisitMut<'a, 'b> for Compressor<'a> {
fn visit_statement(&mut self, stmt: &'b mut Statement<'a>) {
self.compress_block(stmt);
self.compress_while(stmt);
self.fold_condition(stmt);
self.visit_statement_match(stmt);
}

Expand Down
21 changes: 21 additions & 0 deletions crates/oxc_minifier/tests/closure/fold_conditions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::test;

#[test]
fn test_fold_not() {
test("while(!(x==y)){a=b;}", "for(;x!=y;)a=b");
test("while(!(x!=y)){a=b;}", "for(;x==y;)a=b");
test("while(!(x===y)){a=b;}", "for(;x!==y;)a=b");
test("while(!(x!==y)){a=b;}", "for(;x===y;)a=b");

// Because !(x<NaN) != x>=NaN don't fold < and > cases.
test("while(!(x>y)){a=b;}", "for(;!(x>y);)a=b");
test("while(!(x>=y)){a=b;}", "for(;!(x>=y);)a=b");
test("while(!(x<y)){a=b;}", "for(;!(x<y);)a=b");
test("while(!(x<=y)){a=b;}", "for(;!(x<=y);)a=b");
test("while(!(x<=NaN)){a=b;}", "for(;!(x<=NaN);)a=b");

// NOT forces a boolean context
// test("x = !(y() && true)", "x=!y()");
// This will be further optimized by PeepholeFoldConstants.
// test("x = !true", "x=!1");
}
1 change: 1 addition & 0 deletions crates/oxc_minifier/tests/closure/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod fold_conditions;
mod fold_constants;
mod printer;
mod reorder_constant_expression;
Expand Down
10 changes: 10 additions & 0 deletions crates/oxc_syntax/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,16 @@ impl BinaryOperator {
}
}

pub fn equality_inverse_operator(self) -> Option<Self> {
match self {
Self::Equality => Some(Self::Inequality),
Self::Inequality => Some(Self::Equality),
Self::StrictEquality => Some(Self::StrictInequality),
Self::StrictInequality => Some(Self::StrictEquality),
_ => None,
}
}

pub fn as_str(&self) -> &'static str {
match self {
Self::Equality => "==",
Expand Down

0 comments on commit e090b56

Please sign in to comment.