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

refactor(minifier): change minimize conditionals into a loop #8413

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
79 changes: 41 additions & 38 deletions crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
};

if let Some(expr) = expr {
self.try_fold_expr_in_boolean_context(expr, Ctx(ctx));
Self::try_fold_expr_in_boolean_context(expr, Ctx(ctx));
}

if let Some(folded_stmt) = match stmt {
Expand All @@ -75,14 +75,29 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
}

fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if let Expression::ConditionalExpression(e) = expr {
self.try_fold_expr_in_boolean_context(&mut e.test, Ctx(ctx));
loop {
let mut changed = false;
if let Expression::ConditionalExpression(logical_expr) = expr {
if let Some(e) = Self::try_minimize_conditional(logical_expr, ctx) {
*expr = e;
changed = true;
}
}
if let Expression::ConditionalExpression(logical_expr) = expr {
if Self::try_fold_expr_in_boolean_context(&mut logical_expr.test, Ctx(ctx)) {
changed = true;
}
}
if changed {
self.changed = true;
} else {
break;
}
}

if let Some(folded_expr) = match expr {
Expression::UnaryExpression(e) => Self::try_minimize_not(e, ctx),
Expression::BinaryExpression(e) => Self::try_minimize_binary(e, ctx),
Expression::ConditionalExpression(e) => Self::try_minimize_conditional(e, ctx),
_ => None,
} {
*expr = folded_expr;
Expand Down Expand Up @@ -288,17 +303,17 @@ impl<'a> PeepholeMinimizeConditions {
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
// `(a, b) ? c : d` -> `a, b ? c : d`
if let Expression::SequenceExpression(sequence_expr) = &expr.test {
if let Expression::SequenceExpression(sequence_expr) = &mut expr.test {
if sequence_expr.expressions.len() > 1 {
let span = expr.span();
let mut sequence = ctx.ast.move_expression(&mut expr.test);
let Expression::SequenceExpression(ref mut sequence_expr) = &mut sequence else {
unreachable!()
};
let test = sequence_expr.expressions.pop().expect("sequence_expr.expressions");
expr.test = test;
let test = sequence_expr.expressions.pop().unwrap();
sequence_expr.expressions.push(ctx.ast.expression_conditional(
SPAN,
ctx.ast.move_expression(&mut expr.test),
span,
test,
ctx.ast.move_expression(&mut expr.consequent),
ctx.ast.move_expression(&mut expr.alternate),
));
Expand Down Expand Up @@ -382,16 +397,15 @@ impl<'a> PeepholeMinimizeConditions {
(&expr.test, &expr.consequent)
{
if test_ident.name == consequent_ident.name {
let ident = ctx.ast.move_expression(&mut expr.test);

return Some(ctx.ast.expression_logical(
expr.span,
ident,
ctx.ast.move_expression(&mut expr.test),
LogicalOperator::Or,
ctx.ast.move_expression(&mut expr.alternate),
));
}
}

// `a ? b : a` -> `a && b`
if let (Expression::Identifier(test_ident), Expression::Identifier(alternate_ident)) =
(&expr.test, &expr.alternate)
Expand Down Expand Up @@ -653,29 +667,14 @@ impl<'a> PeepholeMinimizeConditions {
/// Simplify syntax when we know it's used inside a boolean context, e.g. `if (boolean_context) {}`.
///
/// <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2059>
fn try_fold_expr_in_boolean_context(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
loop {
let changed = self.try_fold_expr_in_boolean_context_impl(e, ctx);
if changed {
self.changed = true;
} else {
return;
}
}
}

fn try_fold_expr_in_boolean_context_impl(
&mut self,
expr: &mut Expression<'a>,
ctx: Ctx<'a, '_>,
) -> bool {
fn try_fold_expr_in_boolean_context(expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool {
match expr {
// "!!a" => "a"
Expression::UnaryExpression(u1) if u1.operator.is_not() => {
if let Expression::UnaryExpression(u2) = &mut u1.argument {
if u2.operator.is_not() {
let mut e = ctx.ast.move_expression(&mut u2.argument);
self.try_fold_expr_in_boolean_context(&mut e, ctx);
Self::try_fold_expr_in_boolean_context(&mut e, ctx);
*expr = e;
return true;
}
Expand All @@ -701,8 +700,8 @@ impl<'a> PeepholeMinimizeConditions {
}
// "if (!!a && !!b)" => "if (a && b)"
Expression::LogicalExpression(e) if e.operator == LogicalOperator::And => {
self.try_fold_expr_in_boolean_context(&mut e.left, ctx);
self.try_fold_expr_in_boolean_context(&mut e.right, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.left, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.right, ctx);
// "if (anything && truthyNoSideEffects)" => "if (anything)"
if ctx.get_side_free_boolean_value(&e.right) == Some(true) {
*expr = ctx.ast.move_expression(&mut e.left);
Expand All @@ -711,8 +710,8 @@ impl<'a> PeepholeMinimizeConditions {
}
// "if (!!a ||!!b)" => "if (a || b)"
Expression::LogicalExpression(e) if e.operator == LogicalOperator::Or => {
self.try_fold_expr_in_boolean_context(&mut e.left, ctx);
self.try_fold_expr_in_boolean_context(&mut e.right, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.left, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.right, ctx);
// "if (anything || falsyNoSideEffects)" => "if (anything)"
if ctx.get_side_free_boolean_value(&e.right) == Some(false) {
*expr = ctx.ast.move_expression(&mut e.left);
Expand All @@ -721,8 +720,8 @@ impl<'a> PeepholeMinimizeConditions {
}
Expression::ConditionalExpression(e) => {
// "if (a ? !!b : !!c)" => "if (a ? b : c)"
self.try_fold_expr_in_boolean_context(&mut e.consequent, ctx);
self.try_fold_expr_in_boolean_context(&mut e.alternate, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.consequent, ctx);
Self::try_fold_expr_in_boolean_context(&mut e.alternate, ctx);
if let Some(boolean) = ctx.get_side_free_boolean_value(&e.consequent) {
let right = ctx.ast.move_expression(&mut e.alternate);
let left = ctx.ast.move_expression(&mut e.test);
Expand Down Expand Up @@ -1836,8 +1835,8 @@ mod test {

#[test]
fn test_coercion_substitution_not() {
test("var x = {}; var y = !(x != null) ? 1 : 2;", "var x = {}; var y = x != null ? 2 : 1;");
test("var x = 1; var y = !(x != 0) ? 1 : 2; ", "var x = 1; var y = x != 0 ? 2 : 1; ");
test("var x = {}; var y = !(x != null) ? 1 : 2;", "var x = {}; var y = x == null ? 1 : 2;");
test("var x = 1; var y = !(x != 0) ? 1 : 2; ", "var x = 1; var y = x == 0 ? 1 : 2; ");
}

#[test]
Expand Down Expand Up @@ -1973,7 +1972,7 @@ mod test {
}

#[test]
fn minimize_conditional_exprs_esbuild() {
fn minimize_conditional_exprs() {
test("(a, b) ? c : d", "a, b ? c : d");
test("!a ? b : c", "a ? c : b");
// test("/* @__PURE__ */ a() ? b : b", "b");
Expand All @@ -1994,6 +1993,10 @@ mod test {
test("a() != null ? a() : b", "a() == null ? b : a()");
// test("a != null ? a : b", "a ?? b");
// test("a != null ? a.b.c[d](e) : undefined", "a?.b.c[d](e)");
test("cmp !== 0 ? cmp : (bar, cmp);", "cmp === 0 && bar, cmp;");
test("cmp === 0 ? cmp : (bar, cmp);", "cmp === 0 || bar, cmp;");
test("cmp !== 0 ? (bar, cmp) : cmp;", "cmp === 0 || bar, cmp;");
test("cmp === 0 ? (bar, cmp) : cmp;", "cmp === 0 && bar, cmp;");
}

#[test]
Expand Down
26 changes: 13 additions & 13 deletions crates/oxc_minifier/tests/ast_passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ fn test_same(source_text: &str) {

// Oxc Integration Tests

#[test] // https://github.com/oxc-project/oxc/issues/4341
fn tagged_template() {
test_same("(1, o.f)()");
test_same("(1, o.f)``");
test_same("(!0 && o.f)()");
test_same("(!0 && o.f)``");
test("(!0 ? o.f : !1)()", "(0 ? !1: o.f)()");
test("(!0 ? o.f : !1)``", "(0 ? !1: o.f)``");

test("foo(true && o.f)", "foo(o.f)");
test("foo(true ? o.f : false)", "foo(o.f)");
}

#[test]
fn cjs() {
// Bail `cjs-module-lexer`.
Expand Down Expand Up @@ -56,16 +69,3 @@ fn cjs() {
});",
);
}

#[test] // https://github.com/oxc-project/oxc/issues/4341
fn tagged_template() {
test_same("(1, o.f)()");
test_same("(1, o.f)``");
test_same("(!0 && o.f)()");
test_same("(!0 && o.f)``");
test("(!0 ? o.f : !1)()", "(0 ? !1: o.f)()");
test("(!0 ? o.f : !1)``", "(0 ? !1: o.f)``");

test("foo(true && o.f)", "foo(o.f)");
test("foo(true ? o.f : false)", "foo(o.f)");
}
4 changes: 2 additions & 2 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Original | minified | minified | gzip | gzip | Fixture

555.77 kB | 273.21 kB | 270.13 kB | 90.92 kB | 90.80 kB | d3.js

1.01 MB | 460.18 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js
1.01 MB | 460.18 kB | 458.89 kB | 126.77 kB | 126.71 kB | bundle.min.js

1.25 MB | 652.85 kB | 646.76 kB | 163.54 kB | 163.73 kB | three.js

Expand All @@ -23,5 +23,5 @@ Original | minified | minified | gzip | gzip | Fixture

6.69 MB | 2.32 MB | 2.31 MB | 492.64 kB | 488.28 kB | antd.js

10.95 MB | 3.49 MB | 3.49 MB | 907.46 kB | 915.50 kB | typescript.js
10.95 MB | 3.49 MB | 3.49 MB | 907.45 kB | 915.50 kB | typescript.js

Loading