feat(es/minifier): Implement rules for optimizing loops (#4157)

This commit is contained in:
Donny/강동윤 2022-03-25 21:08:14 +09:00 committed by GitHub
parent 7e9118f6b6
commit b37dafbd27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 307 additions and 71 deletions

View File

@ -96,7 +96,7 @@ optional = true
version = "2.0.0"
[dev-dependencies]
rayon = "1"
rayon = "1.5.1"
swc_ecma_lints = { version = "0.28.0", path = "../swc_ecma_lints", features = [
"non_critical_lints",
] }

View File

@ -1,7 +1,7 @@
for(;;)continue;
ONE: for(;;)continue ONE;
TWO: THREE: for(;;)continue THREE;
for(;;);
for(;;);
TWO: for(;;);
FOUR: for(;;)FIVE: for(;;)continue FOUR;
for(;;)SIX: for(;;)continue SIX;
SEVEN: for(;;)for(;;)for(;;)continue SEVEN;
EIGHT: for(;;)continue EIGHT;
for(;;);

View File

@ -1,7 +1,7 @@
for(;;)continue;
ONE: for(;;)continue ONE;
TWO: THREE: for(;;)continue THREE;
for(;;);
for(;;);
TWO: for(;;);
FOUR: for(;;)FIVE: for(;;)continue FOUR;
for(;;)SIX: for(;;)continue SIX;
SEVEN: for(;;)for(;;)for(;;)continue SEVEN;
EIGHT: for(;;)continue EIGHT;
for(;;);

View File

@ -1,7 +1,7 @@
for(;;)continue;
ONE: for(;;)continue ONE;
TWO: THREE: for(;;)continue THREE;
for(;;);
for(;;);
TWO: for(;;);
FOUR: for(;;)FIVE: for(;;)continue FOUR;
for(;;)SIX: for(;;)continue SIX;
SEVEN: for(;;)for(;;)for(;;)continue SEVEN;
EIGHT: for(;;)continue EIGHT;
for(;;);

View File

@ -1,7 +1,7 @@
for(;;)continue;
ONE: for(;;)continue ONE;
TWO: THREE: for(;;)continue THREE;
for(;;);
for(;;);
TWO: for(;;);
FOUR: for(;;)FIVE: for(;;)continue FOUR;
for(;;)SIX: for(;;)continue SIX;
SEVEN: for(;;)for(;;)for(;;)continue SEVEN;
EIGHT: for(;;)continue EIGHT;
for(;;);

View File

@ -1,7 +1,7 @@
for(var x in {})continue;
ONE: for(var x in {})continue ONE;
TWO: THREE: for(var x in {})continue THREE;
for(var x in {});
for(var x in {});
TWO: for(var x in {});
FOUR: for(var x in {})FIVE: for(var x in {})continue FOUR;
for(var x in {})SIX: for(var x in {})continue SIX;
SEVEN: for(var x in {})for(var x in {})for(var x in {})continue SEVEN;
EIGHT: for(var x in {})continue EIGHT;
for(var x in {});

View File

@ -1,7 +1,7 @@
for(var x in {})continue;
ONE: for(var x in {})continue ONE;
TWO: THREE: for(var x in {})continue THREE;
for(var x in {});
for(var x in {});
TWO: for(var x in {});
FOUR: for(var x in {})FIVE: for(var x in {})continue FOUR;
for(var x in {})SIX: for(var x in {})continue SIX;
SEVEN: for(var x in {})for(var x in {})for(var x in {})continue SEVEN;
EIGHT: for(var x in {})continue EIGHT;
for(var x in {});

View File

@ -1 +0,0 @@
target: break target;

View File

@ -1 +0,0 @@
target: break target;

View File

@ -1 +1 @@
for(var i in something)continue;
for(var i in something);

View File

@ -1 +1 @@
for(var i in something)continue;
for(var i in something);

View File

@ -1 +1 @@
label1: for(var i = 0; i < 1; i++)continue label1;
for(var i = 0; i < 1; i++);

View File

@ -1 +1 @@
label1: for(var i = 0; i < 1; i++)continue label1;
for(var i = 0; i < 1; i++);

View File

@ -1 +1 @@
target: for(;;)continue target;
for(;;);

View File

@ -1 +1 @@
target: for(;;)continue target;
for(;;);

View File

@ -1 +1 @@
target1: target2: for(;;)continue target2;
target1: for(;;);

View File

@ -1 +1 @@
target1: target2: for(;;)continue target2;
target1: for(;;);

View File

@ -1,9 +1,9 @@
for(;;)continue;
for(;;)continue;
ONE: for(;;)continue ONE;
TWO: THREE: for(;;)continue THREE;
for(;;);
for(;;);
for(;;);
TWO: for(;;);
FOUR: for(;;)FIVE: for(;;)continue FOUR;
for(;;)SIX: for(;;)continue SIX;
SEVEN: for(;;)for(;;)for(;;)continue SEVEN;
EIGHT: for(;;)continue EIGHT;
NINE: for(;;)continue NINE;
for(;;);
for(;;);

View File

@ -1,9 +1,9 @@
for(;;)continue;
for(;;)continue;
ONE: for(;;)continue ONE;
TWO: THREE: for(;;)continue THREE;
for(;;);
for(;;);
for(;;);
TWO: for(;;);
FOUR: for(;;)FIVE: for(;;)continue FOUR;
for(;;)SIX: for(;;)continue SIX;
SEVEN: for(;;)for(;;)for(;;)continue SEVEN;
EIGHT: for(;;)continue EIGHT;
NINE: for(;;)continue NINE;
for(;;);
for(;;);

View File

@ -1,8 +1,10 @@
use std::{
fs::create_dir_all,
panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
path::{Path, PathBuf},
};
use rayon::prelude::*;
use serde::de::DeserializeOwned;
use swc::{
config::{Config, IsModule, JscConfig, Options, SourceMapsConfig},
@ -38,19 +40,33 @@ fn fixture(input: PathBuf) {
return;
}
for (name, opts) in matrix() {
let output_dir = Path::new("tests").join("tsc-references");
let panics = matrix()
.into_par_iter()
.filter_map(|(name, opts)| {
//
let _ = create_dir_all(&output_dir);
catch_unwind(AssertUnwindSafe(|| {
let output_dir = Path::new("tests").join("tsc-references");
let output_path = output_dir.join(format!(
"{}_{}.js",
input.file_stem().unwrap().to_str().unwrap(),
name
));
let _ = create_dir_all(&output_dir);
compile(&input, &output_path, opts);
let output_path = output_dir.join(format!(
"{}_{}.js",
input.file_stem().unwrap().to_str().unwrap(),
name
));
compile(&input, &output_path, opts);
}))
.err()
})
.collect::<Vec<_>>();
if panics.is_empty() {
return;
}
resume_unwind(panics.into_iter().next().unwrap());
}
fn from_json<T>(s: &str) -> T

View File

@ -1,6 +1,7 @@
use swc_common::{util::take::Take, DUMMY_SP};
use swc_common::{util::take::Take, EqIgnoreSpan, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{ExprExt, StmtLike, Value};
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use super::Pure;
use crate::{
@ -10,6 +11,182 @@ use crate::{
/// Methods related to option `dead_code`.
impl Pure<'_> {
/// - Removes `L1: break L1`
pub(super) fn drop_instant_break(&mut self, s: &mut Stmt) {
if let Stmt::Labeled(ls) = s {
if let Stmt::Break(BreakStmt {
label: Some(label), ..
}) = &*ls.body
{
if label.sym == ls.label.sym {
self.changed = true;
tracing::debug!("Dropping instant break");
s.take();
}
}
}
}
/// # Operations
///
///
/// - Convert if break to conditionals
///
/// ```js
/// out: {
/// if (foo) break out;
/// console.log("bar");
/// }
/// ```
///
/// =>
///
/// ```js
/// foo || console.log("bar");
/// ```
pub(super) fn optimize_labeled_stmt(&mut self, s: &mut Stmt) -> Option<()> {
if !self.options.dead_code {
return None;
}
if let Stmt::Labeled(ls) = s {
if let Stmt::Block(bs) = &mut *ls.body {
let first = bs.stmts.first_mut()?;
if let Stmt::If(IfStmt {
test,
cons,
alt: None,
..
}) = first
{
if let Stmt::Break(BreakStmt {
label: Some(label), ..
}) = &**cons
{
if ls.label.sym == label.sym {
self.changed = true;
tracing::debug!("Optimizing labeled stmt with a break to if statement");
self.negate(test, true, false);
let test = test.take();
let mut cons = bs.take();
cons.stmts.remove(0);
*s = Stmt::If(IfStmt {
span: ls.span,
test,
cons: Box::new(Stmt::Block(cons)),
alt: None,
});
return None;
}
}
}
if let Stmt::If(IfStmt {
test,
cons,
alt: Some(alt),
..
}) = first
{
if let Stmt::Break(BreakStmt {
label: Some(label), ..
}) = &**alt
{
if ls.label.sym == label.sym {
self.changed = true;
tracing::debug!(
"Optimizing labeled stmt with a break in alt to if statement"
);
let test = test.take();
let cons = *cons.take();
let mut new_cons = bs.take();
new_cons.stmts[0] = cons;
*s = Stmt::If(IfStmt {
span: ls.span,
test,
cons: Box::new(Stmt::Block(new_cons)),
alt: None,
});
return None;
}
}
}
}
}
None
}
/// Remove the last statement of a loop if it's continue
pub(super) fn drop_useless_continue(&mut self, s: &mut Stmt) {
match s {
Stmt::Labeled(ls) => {
let new = self.drop_useless_continue_inner(Some(ls.label.clone()), &mut ls.body);
if let Some(new) = new {
*s = new;
}
}
_ => {
let new = self.drop_useless_continue_inner(None, s);
if let Some(new) = new {
*s = new;
}
}
}
}
/// Returns [Some] if the whole statement sohuld be replaced
fn drop_useless_continue_inner(
&mut self,
label: Option<Ident>,
loop_stmt: &mut Stmt,
) -> Option<Stmt> {
let body = match loop_stmt {
Stmt::While(ws) => &mut *ws.body,
Stmt::For(fs) => &mut *fs.body,
Stmt::ForIn(fs) => &mut *fs.body,
Stmt::ForOf(fs) => &mut *fs.body,
_ => return None,
};
if let Stmt::Block(b) = body {
let last = b.stmts.last_mut()?;
if let Stmt::Continue(last_cs) = last {
match last_cs.label {
Some(_) => {
if label.eq_ignore_span(&last_cs.label) {
} else {
return None;
}
}
None => {}
}
} else {
return None;
}
self.changed = true;
tracing::debug!("Remove useless continue (last stmt of a loop)");
b.stmts.remove(b.stmts.len() - 1);
if let Some(label) = &label {
if !contains_label(b, label) {
return Some(loop_stmt.take());
}
}
}
None
}
pub(super) fn drop_unreachable_stmts<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike + ModuleItemExt + Take,
@ -159,3 +336,45 @@ impl Pure<'_> {
*stmts = new;
}
}
fn contains_label<N>(node: &N, label: &Ident) -> bool
where
for<'aa> N: VisitWith<LabelFinder<'aa>>,
{
let mut v = LabelFinder {
label,
found: false,
};
node.visit_with(&mut v);
v.found
}
struct LabelFinder<'a> {
label: &'a Ident,
found: bool,
}
impl Visit for LabelFinder<'_> {
noop_visit_type!();
fn visit_break_stmt(&mut self, s: &BreakStmt) {
match &s.label {
Some(label) => {
if label.sym == self.label.sym {
self.found = true;
}
}
None => {}
}
}
fn visit_continue_stmt(&mut self, s: &ContinueStmt) {
match &s.label {
Some(label) => {
if label.sym == self.label.sym {
self.found = true;
}
}
None => {}
}
}
}

View File

@ -598,6 +598,12 @@ impl VisitMut for Pure<'_> {
self.loop_to_for_stmt(s);
self.drop_instant_break(s);
self.optimize_labeled_stmt(s);
self.drop_useless_continue(s);
if let Stmt::Expr(es) = s {
if es.expr.is_invalid() {
*s = Stmt::Empty(EmptyStmt { span: DUMMY_SP });

View File

@ -518,12 +518,7 @@ keep_names/keep_some_classnames/input.js
keep_names/keep_some_fnames/input.js
keep_names/keep_some_fnames_reduce/input.js
keep_quoted_strict/keep_quoted_strict/input.js
labels/labels_1/input.js
labels/labels_2/input.js
labels/labels_4/input.js
labels/labels_6/input.js
labels/labels_7/input.js
labels/labels_9/input.js
logical_assignment/assign_in_conditional_part/input.js
logical_assignment/assignment_in_left_part_2/input.js
loops/drop_if_else_break_1/input.js

View File

@ -813,10 +813,15 @@ keep_names/keep_classnames/input.js
keep_names/keep_fnames/input.js
keep_names/keep_fnames_and_avoid_collisions/input.js
keep_names/keep_var_fnames/input.js
labels/labels_1/input.js
labels/labels_10/input.js
labels/labels_2/input.js
labels/labels_3/input.js
labels/labels_5/input.js
labels/labels_6/input.js
labels/labels_7/input.js
labels/labels_8/input.js
labels/labels_9/input.js
logical_assignment/assign_in_conditional_part_reused/input.js
logical_assignment/assignment_in_left_part/input.js
logical_assignment/logical_assignment_not_always_happens/input.js

View File

@ -9,9 +9,6 @@ function up(){
}
up
up
up
up
git add -A