Skip to content

Commit 7344a63

Browse files
authored
feat(es/minifier): Optimize switch with side effect and termination tests (#9677)
**Description:** This is learnt from terser/terser#1044 **Key point:** all the cases (statements) after the last side-effect and non-terminated one are useless. There are also some points on whether a statement is side-effect free: - **Var declaration:** I think var declaration always contains side effect. - **Function declaration:** In non-strict mode, function declaration is same as var declaration. In strict mode, function is declared in enclosing block scope, so it's side-effect free. But there is a bad case when we call the function before it is declared in the switch case: ```js "use strict"; const k = (() => { let x = 1; switch (x) { case y(): case x: { async function y() { console.log(123); } } } return x; })(); ``` **This is an existing bug.** I'm not sure whether we should fix it since it is a very bad code and I also find some similar bad cases with terser. ~I'm also not sure it's appropriate to introduce context variable `in_strict` for `StmtExt:: may_have_side_effects`, because `in_strict` could change from `false` to `true`. But this is a **conservative** mistake and does not break the program. Maybe we should use visitor for context-aware side effect checker in the future.~ **Related issue:** - Closes #8953
1 parent 2bbd1e8 commit 7344a63

File tree

36 files changed

+307
-197
lines changed

36 files changed

+307
-197
lines changed

.changeset/happy-planes-dance.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
swc_ecma_utils: major
3+
---
4+
5+
feat(es/minifier): Optimize switch and replace empty test with side effect and termination tests
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,2 @@
11
//// [stringLiteralsWithSwitchStatements03.ts]
2-
var z;
3-
switch(void 0){
4-
case randBool() ? "foo" : "baz":
5-
case z || "baz":
6-
}
2+
randBool();

crates/swc/tests/tsc-references/switchStatements.2.minified.js

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ switch((M || (M = {})).fn = function(x) {
2525
case void 0 === x ? "undefined" : _type_of(x):
2626
case void 0 === M ? "undefined" : _type_of(M):
2727
case M.fn(1):
28-
default:
2928
}
3029
var x, M, C = function C() {
3130
_class_call_check(this, C);

crates/swc_ecma_minifier/src/compress/optimize/bools.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ impl Optimizer<'_> {
2121
expr: &mut Expr,
2222
is_ret_val_ignored: bool,
2323
) -> bool {
24-
let cost = negate_cost(&self.expr_ctx, expr, is_ret_val_ignored, is_ret_val_ignored);
24+
let cost = negate_cost(
25+
&self.ctx.expr_ctx,
26+
expr,
27+
is_ret_val_ignored,
28+
is_ret_val_ignored,
29+
);
2530
if cost >= 0 {
2631
return false;
2732
}
@@ -72,11 +77,12 @@ impl Optimizer<'_> {
7277

7378
let ctx = Ctx {
7479
in_bool_ctx: true,
75-
..self.ctx
80+
..self.ctx.clone()
7681
};
7782

78-
self.with_ctx(ctx).negate(&mut e.left, false);
79-
self.with_ctx(ctx).negate(&mut e.right, is_ret_val_ignored);
83+
self.with_ctx(ctx.clone()).negate(&mut e.left, false);
84+
self.with_ctx(ctx.clone())
85+
.negate(&mut e.right, is_ret_val_ignored);
8086

8187
dump_change_detail!("{} => {}", start, dump(&*e, false));
8288

crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ impl Optimizer<'_> {
3030
_ => {}
3131
}
3232

33-
if negate_cost(&self.expr_ctx, &stmt.test, true, false) < 0 {
33+
if negate_cost(&self.ctx.expr_ctx, &stmt.test, true, false) < 0 {
3434
report_change!("if_return: Negating `cond` of an if statement which has cons and alt");
3535
let ctx = Ctx {
3636
in_bool_ctx: true,
37-
..self.ctx
37+
..self.ctx.clone()
3838
};
3939
self.with_ctx(ctx).negate(&mut stmt.test, false);
4040
swap(alt, &mut *stmt.cons);
@@ -63,7 +63,7 @@ impl Optimizer<'_> {
6363
_ => return,
6464
};
6565

66-
if !cond.cons.may_have_side_effects(&self.expr_ctx) {
66+
if !cond.cons.may_have_side_effects(&self.ctx.expr_ctx) {
6767
self.changed = true;
6868
report_change!("conditionals: `cond ? useless : alt` => `cond || alt`");
6969
*e = BinExpr {
@@ -76,7 +76,7 @@ impl Optimizer<'_> {
7676
return;
7777
}
7878

79-
if !cond.alt.may_have_side_effects(&self.expr_ctx) {
79+
if !cond.alt.may_have_side_effects(&self.ctx.expr_ctx) {
8080
self.changed = true;
8181
report_change!("conditionals: `cond ? cons : useless` => `cond && cons`");
8282
*e = BinExpr {
@@ -878,7 +878,7 @@ impl Optimizer<'_> {
878878
) = (&*cons, &*alt)
879879
{
880880
// I don't know why, but terser behaves differently
881-
negate(&self.expr_ctx, &mut test, true, false);
881+
negate(&self.ctx.expr_ctx, &mut test, true, false);
882882

883883
swap(&mut cons, &mut alt);
884884
}

crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ impl Optimizer<'_> {
163163
//
164164

165165
for arg in &*args {
166-
if arg.spread.is_some() || arg.expr.may_have_side_effects(&self.expr_ctx) {
166+
if arg.spread.is_some() || arg.expr.may_have_side_effects(&self.ctx.expr_ctx) {
167167
return;
168168
}
169169
}
@@ -233,7 +233,7 @@ impl Optimizer<'_> {
233233
return;
234234
}
235235

236-
if let Known(char_code) = args[0].expr.as_pure_number(&self.expr_ctx) {
236+
if let Known(char_code) = args[0].expr.as_pure_number(&self.ctx.expr_ctx) {
237237
let v = char_code.floor() as u32;
238238

239239
if let Some(v) = char::from_u32(v) {
@@ -341,7 +341,7 @@ impl Optimizer<'_> {
341341
}
342342

343343
if let Expr::Call(..) = e {
344-
if let Some(value) = eval_as_number(&self.expr_ctx, e) {
344+
if let Some(value) = eval_as_number(&self.ctx.expr_ctx, e) {
345345
self.changed = true;
346346
report_change!("evaluate: Evaluated an expression as `{}`", value);
347347

@@ -367,8 +367,8 @@ impl Optimizer<'_> {
367367

368368
match e {
369369
Expr::Bin(bin @ BinExpr { op: op!("**"), .. }) => {
370-
let l = bin.left.as_pure_number(&self.expr_ctx);
371-
let r = bin.right.as_pure_number(&self.expr_ctx);
370+
let l = bin.left.as_pure_number(&self.ctx.expr_ctx);
371+
let r = bin.right.as_pure_number(&self.ctx.expr_ctx);
372372

373373
if let Known(l) = l {
374374
if let Known(r) = r {
@@ -395,9 +395,9 @@ impl Optimizer<'_> {
395395
}
396396

397397
Expr::Bin(bin @ BinExpr { op: op!("/"), .. }) => {
398-
let ln = bin.left.as_pure_number(&self.expr_ctx);
398+
let ln = bin.left.as_pure_number(&self.ctx.expr_ctx);
399399

400-
let rn = bin.right.as_pure_number(&self.expr_ctx);
400+
let rn = bin.right.as_pure_number(&self.ctx.expr_ctx);
401401
if let (Known(ln), Known(rn)) = (ln, rn) {
402402
// Prefer `0/0` over NaN.
403403
if ln == 0.0 && rn == 0.0 {
@@ -477,7 +477,7 @@ impl Optimizer<'_> {
477477
}
478478
// Remove rhs of lhs if possible.
479479

480-
let v = left.right.as_pure_bool(&self.expr_ctx);
480+
let v = left.right.as_pure_bool(&self.ctx.expr_ctx);
481481
if let Known(v) = v {
482482
// As we used as_pure_bool, we can drop it.
483483
if v && e.op == op!("&&") {

crates/swc_ecma_minifier/src/compress/optimize/if_return.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use swc_common::{util::take::Take, Spanned, DUMMY_SP};
22
use swc_ecma_ast::*;
33
use swc_ecma_transforms_optimization::debug_assert_valid;
4-
use swc_ecma_utils::{StmtExt, StmtLike};
4+
use swc_ecma_utils::StmtLike;
55
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
66

77
use super::Optimizer;
@@ -116,7 +116,7 @@ impl Optimizer<'_> {
116116
// for stmt in stmts.iter_mut() {
117117
// let ctx = Ctx {
118118
// is_nested_if_return_merging: true,
119-
// ..self.ctx
119+
// ..self.ctx.clone()
120120
// };
121121
// self.with_ctx(ctx).merge_nested_if_returns(stmt, terminate);
122122
// }
@@ -396,7 +396,7 @@ impl Optimizer<'_> {
396396
&& seq
397397
.exprs
398398
.last()
399-
.map(|v| is_pure_undefined(&self.expr_ctx, v))
399+
.map(|v| is_pure_undefined(&self.ctx.expr_ctx, v))
400400
.unwrap_or(true) =>
401401
{
402402
let expr = self.ignore_return_value(&mut cur);
@@ -570,7 +570,7 @@ fn always_terminates_with_return_arg(s: &Stmt) -> bool {
570570
fn can_merge_as_if_return(s: &Stmt) -> bool {
571571
fn cost(s: &Stmt) -> Option<isize> {
572572
if let Stmt::Block(..) = s {
573-
if !s.terminates() {
573+
if !swc_ecma_utils::StmtExt::terminates(s) {
574574
return None;
575575
}
576576
}

crates/swc_ecma_minifier/src/compress/optimize/iife.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ impl Optimizer<'_> {
293293
let ctx = Ctx {
294294
in_fn_like: true,
295295
top_level: false,
296-
..self.ctx
296+
..self.ctx.clone()
297297
};
298298
let mut optimizer = self.with_ctx(ctx);
299299
match find_body(callee) {

crates/swc_ecma_minifier/src/compress/optimize/inline.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ impl Optimizer<'_> {
7777
if ref_count == 0 {
7878
self.mode.store(ident.to_id(), &*init);
7979

80-
if init.may_have_side_effects(&self.expr_ctx) {
80+
if init.may_have_side_effects(&self.ctx.expr_ctx) {
8181
// TODO: Inline partially
8282
return;
8383
}
@@ -494,7 +494,7 @@ impl Optimizer<'_> {
494494
}
495495
}
496496

497-
if init.may_have_side_effects(&self.expr_ctx) {
497+
if init.may_have_side_effects(&self.ctx.expr_ctx) {
498498
return;
499499
}
500500

@@ -531,13 +531,13 @@ impl Optimizer<'_> {
531531
if body.stmts.len() == 1 {
532532
match &body.stmts[0] {
533533
Stmt::Expr(ExprStmt { expr, .. })
534-
if expr.size(self.expr_ctx.unresolved_ctxt) < cost_limit =>
534+
if expr.size(self.ctx.expr_ctx.unresolved_ctxt) < cost_limit =>
535535
{
536536
return true
537537
}
538538

539539
Stmt::Return(ReturnStmt { arg: Some(arg), .. })
540-
if arg.size(self.expr_ctx.unresolved_ctxt) < cost_limit =>
540+
if arg.size(self.ctx.expr_ctx.unresolved_ctxt) < cost_limit =>
541541
{
542542
return true
543543
}
@@ -719,7 +719,7 @@ impl Optimizer<'_> {
719719
})
720720
{
721721
if let Decl::Class(ClassDecl { class, .. }) = decl {
722-
if class_has_side_effect(&self.expr_ctx, class) {
722+
if class_has_side_effect(&self.ctx.expr_ctx, class) {
723723
return;
724724
}
725725
}

crates/swc_ecma_minifier/src/compress/optimize/loops.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl Optimizer<'_> {
6565

6666
match stmt {
6767
Stmt::While(w) => {
68-
let (purity, val) = w.test.cast_to_bool(&self.expr_ctx);
68+
let (purity, val) = w.test.cast_to_bool(&self.ctx.expr_ctx);
6969
if let Known(false) = val {
7070
if purity.is_pure() {
7171
let changed = UnreachableHandler::preserve_vars(stmt);
@@ -86,7 +86,7 @@ impl Optimizer<'_> {
8686
}
8787
Stmt::For(f) => {
8888
if let Some(test) = &mut f.test {
89-
let (purity, val) = test.cast_to_bool(&self.expr_ctx);
89+
let (purity, val) = test.cast_to_bool(&self.ctx.expr_ctx);
9090
if let Known(false) = val {
9191
let changed = UnreachableHandler::preserve_vars(&mut f.body);
9292
self.changed |= changed;

0 commit comments

Comments
 (0)