feat(minifier): initialize conditions folding (#658)

related: #401
This commit is contained in:
阿良仔 2023-07-30 01:02:32 +08:00 committed by GitHub
parent ee211baabc
commit e090b560be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 13 deletions

View file

@ -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 {
@ -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,
}
}
}

View file

@ -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);
@ -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,
@ -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,
}
}
}

View file

@ -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);
}

View file

@ -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");
}

View file

@ -1,3 +1,4 @@
mod fold_conditions;
mod fold_constants;
mod printer;
mod reorder_constant_expression;

View file

@ -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 => "==",