mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 21:21:31 +03:00
feat(es/minifier): Implement rules for optimizing loops (#4157)
This commit is contained in:
parent
7e9118f6b6
commit
b37dafbd27
@ -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",
|
||||
] }
|
||||
|
@ -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(;;);
|
||||
|
@ -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(;;);
|
||||
|
@ -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(;;);
|
||||
|
@ -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(;;);
|
||||
|
@ -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 {});
|
||||
|
@ -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 {});
|
||||
|
@ -1 +0,0 @@
|
||||
target: break target;
|
@ -1 +0,0 @@
|
||||
target: break target;
|
@ -1 +1 @@
|
||||
for(;;)continue;
|
||||
for(;;);
|
||||
|
@ -1 +1 @@
|
||||
for(;;)continue;
|
||||
for(;;);
|
||||
|
@ -1 +1 @@
|
||||
for(;;)continue;
|
||||
for(;;);
|
||||
|
@ -1 +1 @@
|
||||
for(;;)continue;
|
||||
for(;;);
|
||||
|
@ -1 +1 @@
|
||||
for(;;)continue;
|
||||
for(;;);
|
||||
|
@ -1 +1 @@
|
||||
for(;;)continue;
|
||||
for(;;);
|
||||
|
@ -1 +1 @@
|
||||
for(var i in something)continue;
|
||||
for(var i in something);
|
||||
|
@ -1 +1 @@
|
||||
for(var i in something)continue;
|
||||
for(var i in something);
|
||||
|
@ -1 +1 @@
|
||||
label1: for(var i = 0; i < 1; i++)continue label1;
|
||||
for(var i = 0; i < 1; i++);
|
||||
|
@ -1 +1 @@
|
||||
label1: for(var i = 0; i < 1; i++)continue label1;
|
||||
for(var i = 0; i < 1; i++);
|
||||
|
@ -1 +1 @@
|
||||
target: for(;;)continue target;
|
||||
for(;;);
|
||||
|
@ -1 +1 @@
|
||||
target: for(;;)continue target;
|
||||
for(;;);
|
||||
|
@ -1 +1 @@
|
||||
target1: target2: for(;;)continue target2;
|
||||
target1: for(;;);
|
||||
|
@ -1 +1 @@
|
||||
target1: target2: for(;;)continue target2;
|
||||
target1: for(;;);
|
||||
|
@ -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(;;);
|
||||
|
@ -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(;;);
|
||||
|
@ -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
|
||||
|
@ -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 => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 });
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -9,9 +9,6 @@ function up(){
|
||||
}
|
||||
|
||||
up
|
||||
up
|
||||
up
|
||||
up
|
||||
|
||||
|
||||
git add -A
|
||||
|
Loading…
Reference in New Issue
Block a user