Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(minifier): initialize conditions folding #658

Merged
merged 3 commits into from
Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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