perf(es/minifier): Make more things parallel and reduce call stacks (#4915)

This commit is contained in:
LongYinan 2022-06-14 04:03:09 +08:00 committed by GitHub
parent 925f4d1641
commit 35806385ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 671 additions and 435 deletions

37
Cargo.lock generated
View File

@ -958,12 +958,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "fs_extra"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]] [[package]]
name = "fuchsia-cprng" name = "fuchsia-cprng"
version = "0.1.1" version = "0.1.1"
@ -1549,9 +1543,9 @@ dependencies = [
[[package]] [[package]]
name = "mimalloc-rust" name = "mimalloc-rust"
version = "0.1.5" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc30df9dfdb5bb6cb2470de65ca604c3eaa3e5dc2ad02a9a98f567df5844472" checksum = "6973866e0bc6504c03a16b6817b7e70839cc8a1dbd5d6dab00c65d8034868d8b"
dependencies = [ dependencies = [
"cty", "cty",
"mimalloc-rust-sys", "mimalloc-rust-sys",
@ -1559,9 +1553,9 @@ dependencies = [
[[package]] [[package]]
name = "mimalloc-rust-sys" name = "mimalloc-rust-sys"
version = "1.7.3-source" version = "1.7.6-source"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3adc8731262b982f4e0860770dba118305cafe1b2e7ebe95b29b2c2f46a70666" checksum = "7a50daf45336b979a202a19f53b4b382f2c4bd50f392a8dbdb4c6c56ba5dfa64"
dependencies = [ dependencies = [
"cc", "cc",
"cty", "cty",
@ -3407,6 +3401,7 @@ dependencies = [
"backtrace", "backtrace",
"criterion", "criterion",
"indexmap", "indexmap",
"num_cpus",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"pretty_assertions", "pretty_assertions",
@ -4023,7 +4018,6 @@ name = "swc_node_base"
version = "0.5.5" version = "0.5.5"
dependencies = [ dependencies = [
"mimalloc-rust", "mimalloc-rust",
"tikv-jemallocator",
] ]
[[package]] [[package]]
@ -4339,27 +4333,6 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "tikv-jemalloc-sys"
version = "0.4.3+5.2.1-patched.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1792ccb507d955b46af42c123ea8863668fae24d03721e40cad6a41773dbb49"
dependencies = [
"cc",
"fs_extra",
"libc",
]
[[package]]
name = "tikv-jemallocator"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5b7bcecfafe4998587d636f9ae9d55eb9d0499877b88757767c346875067098"
dependencies = [
"libc",
"tikv-jemalloc-sys",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"

View File

@ -23,7 +23,7 @@ members = [
] ]
[profile.release] [profile.release]
codegen-units = 1 lto = true
# We use CARGO_PROFILE_RELEASE_LTO for production builds # We use CARGO_PROFILE_RELEASE_LTO for production builds
# lto = "fat" # lto = "fat"
@ -32,7 +32,7 @@ codegen-units = 1
# opt-level = 'z' # opt-level = 'z'
[profile.bench] [profile.bench]
codegen-units = 1 lto = true
debug = true debug = true
# Without this, printing diff consumes more than a minute. # Without this, printing diff consumes more than a minute.
@ -44,5 +44,5 @@ opt-level = 3
opt-level = 3 opt-level = 3
[patch.crates-io] [patch.crates-io]
cranelift-codegen = {git = "https://github.com/kdy1/wasmtime", branch = "tls"} cranelift-codegen = { git = "https://github.com/kdy1/wasmtime", branch = "tls" }
cranelift-entity = {git = "https://github.com/kdy1/wasmtime", branch = "tls"} cranelift-entity = { git = "https://github.com/kdy1/wasmtime", branch = "tls" }

View File

@ -25,6 +25,7 @@ ahash = "0.7.6"
arrayvec = "0.7.2" arrayvec = "0.7.2"
backtrace = { version = "0.3.61", optional = true } backtrace = { version = "0.3.61", optional = true }
indexmap = "1.7.0" indexmap = "1.7.0"
num_cpus = "1.13.1"
once_cell = "1.10.0" once_cell = "1.10.0"
parking_lot = "0.12.0" parking_lot = "0.12.0"
pretty_assertions = { version = "1.1", optional = true } pretty_assertions = { version = "1.1", optional = true }

View File

@ -1,4 +1,6 @@
use rustc_hash::{FxHashMap, FxHashSet}; use std::{collections::HashSet, hash::BuildHasherDefault};
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use swc_atoms::{js_word, JsWord}; use swc_atoms::{js_word, JsWord};
use swc_common::{collections::AHashSet, SyntaxContext}; use swc_common::{collections::AHashSet, SyntaxContext};
use swc_ecma_ast::*; use swc_ecma_ast::*;
@ -163,35 +165,41 @@ pub(crate) struct ProgramData {
impl ProgramData { impl ProgramData {
pub(crate) fn expand_infected( pub(crate) fn expand_infected(
&self, &self,
ids: impl IntoIterator<Item = Id>, ids: FxHashSet<Id>,
max_num: usize, max_num: usize,
) -> Result<FxHashSet<Id>, ()> { ) -> Result<FxHashSet<Id>, ()> {
let mut result = FxHashSet::default(); let init =
self.expand_infected_inner(ids, max_num, &mut result)?; HashSet::with_capacity_and_hasher(max_num, BuildHasherDefault::<FxHasher>::default());
Ok(result) ids.into_iter().try_fold(init, |mut res, id| {
} let mut ids = Vec::with_capacity(max_num);
ids.push(id);
fn expand_infected_inner( let mut ranges = vec![0..1usize];
&self, loop {
ids: impl IntoIterator<Item = Id>, let range = ranges.remove(0);
max_num: usize, for index in range {
result: &mut FxHashSet<Id>, let iid = ids.get(index).unwrap();
) -> Result<(), ()> { if !res.insert(iid.clone()) {
for id in ids { continue;
if !result.insert(id.clone()) { }
continue; if res.len() >= max_num {
return Err(());
}
if let Some(info) = self.vars.get(iid) {
let infects = &info.infects;
if !infects.is_empty() {
let old_len = ids.len();
ids.extend_from_slice(infects.as_slice());
let new_len = ids.len();
ranges.push(old_len..new_len);
}
}
}
if ranges.is_empty() {
break;
}
} }
if result.len() >= max_num { Ok(res)
return Err(()); })
}
if let Some(info) = self.vars.get(&id) {
let ids = info.infects.clone();
self.expand_infected_inner(ids, max_num, result)?;
}
}
Ok(())
} }
pub(crate) fn contains_unresolved(&self, e: &Expr) -> bool { pub(crate) fn contains_unresolved(&self, e: &Expr) -> bool {
@ -633,7 +641,8 @@ where
e.visit_children_with(&mut *self.with_ctx(ctx)); e.visit_children_with(&mut *self.with_ctx(ctx));
if let Expr::Ident(i) = e { if let Expr::Ident(i) = e {
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
// debug!( // debug!(
// "Usage: `{}``; update = {:?}, assign_lhs = {:?} ", // "Usage: `{}``; update = {:?}, assign_lhs = {:?} ",
// i, // i,

View File

@ -1,3 +1,4 @@
use rayon::prelude::*;
use swc_common::{collections::AHashSet, pass::Repeated, util::take::Take, DUMMY_SP}; use swc_common::{collections::AHashSet, pass::Repeated, util::take::Take, DUMMY_SP};
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_utils::{find_pat_ids, StmtLike}; use swc_ecma_utils::{find_pat_ids, StmtLike};
@ -6,7 +7,7 @@ use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
use super::util::drop_invalid_stmts; use super::util::drop_invalid_stmts;
use crate::{ use crate::{
analyzer::{ProgramData, UsageAnalyzer}, analyzer::{ProgramData, UsageAnalyzer},
util::{is_hoisted_var_decl_without_init, sort::is_sorted_by_key, IsModuleItem, ModuleItemExt}, util::{is_hoisted_var_decl_without_init, sort::is_sorted_by, IsModuleItem, ModuleItemExt},
}; };
pub(super) struct DeclHoisterConfig { pub(super) struct DeclHoisterConfig {
@ -46,33 +47,43 @@ impl Hoister<'_> {
Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer>, Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer>,
{ {
stmts.visit_mut_children_with(self); stmts.visit_mut_children_with(self);
let len = stmts.len();
let should_hoist = !is_sorted_by(
stmts.iter().map(|stmt| match stmt.as_stmt() {
Some(stmt) => match stmt {
Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => 1,
Stmt::Decl(Decl::Var(var)) if self.config.hoist_vars => {
let ids: Vec<Id> = find_pat_ids(&var.decls);
let should_hoist = !is_sorted_by_key(stmts.iter(), |stmt| match stmt.as_stmt() { if ids.iter().any(|id| {
Some(stmt) => match stmt { self.data
Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => 1, .vars
Stmt::Decl(Decl::Var(var)) if self.config.hoist_vars => { .get(id)
let ids: Vec<Id> = find_pat_ids(&var.decls); .map(|v| !v.used_above_decl)
.unwrap_or(false)
if ids.iter().any(|id| { }) {
self.data 2
.vars } else {
.get(id) 3
.map(|v| !v.used_above_decl) }
.unwrap_or(false)
}) {
2
} else {
3
} }
} _ => 3,
_ => 3, },
}, None => 3,
None => 3, }),
}) || (self.config.hoist_vars PartialOrd::partial_cmp,
&& stmts.windows(2).any(|stmts| { ) || (self.config.hoist_vars
is_hoisted_var_decl_without_init(&stmts[0]) && if len >= *crate::LIGHT_TASK_PARALLELS {
&& is_hoisted_var_decl_without_init(&stmts[1]) stmts.par_chunks(2).any(|stmts| {
})); is_hoisted_var_decl_without_init(&stmts[0])
&& is_hoisted_var_decl_without_init(&stmts[1])
})
} else {
stmts.windows(2).any(|stmts| {
is_hoisted_var_decl_without_init(&stmts[0])
&& is_hoisted_var_decl_without_init(&stmts[1])
})
});
if !should_hoist { if !should_hoist {
return; return;

View File

@ -1,8 +1,9 @@
#[cfg(feature = "debug")]
use std::thread;
use std::{ use std::{
borrow::Cow, borrow::Cow,
fmt, fmt,
fmt::{Debug, Display, Formatter, Write}, fmt::{Debug, Display, Formatter, Write},
thread,
time::Instant, time::Instant,
}; };
@ -31,6 +32,7 @@ use crate::{
compress::hoist_decls::decl_hoister, compress::hoist_decls::decl_hoister,
debug::{dump, AssertValid}, debug::{dump, AssertValid},
marks::Marks, marks::Marks,
maybe_par,
mode::Mode, mode::Mode,
option::CompressOptions, option::CompressOptions,
util::{now, unit::CompileUnit, Optional}, util::{now, unit::CompileUnit, Optional},
@ -107,14 +109,17 @@ where
Vec<T>: VisitMutWith<Self> + for<'aa> VisitMutWith<hoist_decls::Hoister<'aa>>, Vec<T>: VisitMutWith<Self> + for<'aa> VisitMutWith<hoist_decls::Hoister<'aa>>,
{ {
// Skip if `use asm` exists. // Skip if `use asm` exists.
if stmts.iter().any(|stmt| match stmt.as_stmt() { if maybe_par!(
Some(Stmt::Expr(stmt)) => match &*stmt.expr { stmts.iter().any(|stmt| match stmt.as_stmt() {
// TODO improve check, directives can contain escaped characters Some(Stmt::Expr(stmt)) => match &*stmt.expr {
Expr::Lit(Lit::Str(Str { value, .. })) => &**value == "use asm", // TODO improve check, directives can contain escaped characters
Expr::Lit(Lit::Str(Str { value, .. })) => &**value == "use asm",
_ => false,
},
_ => false, _ => false,
}, }),
_ => false, *crate::LIGHT_TASK_PARALLELS
}) { ) {
return; return;
} }
@ -262,12 +267,11 @@ where
} }
} }
let start = if cfg!(feature = "debug") { #[cfg(feature = "debug")]
let start = {
let start = n.dump(); let start = n.dump();
debug!("===== Start =====\n{}", start); debug!("===== Start =====\n{}", start);
start start
} else {
String::new()
}; };
{ {
@ -284,7 +288,8 @@ where
self.changed |= visitor.changed(); self.changed |= visitor.changed();
if visitor.changed() { if visitor.changed() {
debug!("compressor: Simplified expressions"); debug!("compressor: Simplified expressions");
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
debug!("===== Simplified =====\n{}", dump(&*n, false)); debug!("===== Simplified =====\n{}", dump(&*n, false));
} }
} }
@ -299,7 +304,8 @@ where
); );
} }
if cfg!(feature = "debug") && !visitor.changed() { #[cfg(feature = "debug")]
if !visitor.changed() {
let simplified = n.dump(); let simplified = n.dump();
if start != simplified { if start != simplified {
assert_eq!( assert_eq!(
@ -322,6 +328,7 @@ where
PureOptimizerConfig { PureOptimizerConfig {
enable_join_vars: self.pass > 1, enable_join_vars: self.pass > 1,
force_str_for_tpl: M::force_str_for_tpl(), force_str_for_tpl: M::force_str_for_tpl(),
#[cfg(feature = "debug")]
debug_infinite_loop: self.pass >= 20, debug_infinite_loop: self.pass >= 20,
}, },
); );
@ -329,13 +336,15 @@ where
self.changed |= visitor.changed(); self.changed |= visitor.changed();
if cfg!(feature = "debug") && visitor.changed() { #[cfg(feature = "debug")]
if visitor.changed() {
let src = n.dump(); let src = n.dump();
debug!("===== After pure =====\n{}\n{}", start, src); debug!("===== After pure =====\n{}\n{}", start, src);
} }
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
n.visit_with(&mut AssertValid); n.visit_with(&mut AssertValid);
} }
@ -364,11 +373,8 @@ where
} }
if self.options.conditionals || self.options.dead_code { if self.options.conditionals || self.options.dead_code {
let start = if cfg!(feature = "debug") { #[cfg(feature = "debug")]
dump(&*n, false) let start = dump(&*n, false);
} else {
"".into()
};
let start_time = now(); let start_time = now();
@ -385,7 +391,8 @@ where
); );
} }
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
let simplified = dump(&*n, false); let simplified = dump(&*n, false);
if start != simplified { if start != simplified {

View File

@ -90,10 +90,13 @@ where
{ {
// If a function has a variable named `arguments`, we abort. // If a function has a variable named `arguments`, we abort.
let data: Vec<Id> = find_pat_ids(&f.body); let data: Vec<Id> = find_pat_ids(&f.body);
for id in &data { if data.iter().any(|id| {
if id.0 == js_word!("arguments") { if id.0 == js_word!("arguments") {
return; return true;
} }
false
}) {
return;
} }
} }

View File

@ -7,9 +7,10 @@ use swc_ecma_utils::{
}; };
use super::Optimizer; use super::Optimizer;
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{ use crate::{
compress::{optimize::Ctx, util::negate_cost}, compress::{optimize::Ctx, util::negate_cost},
debug::dump,
mode::Mode, mode::Mode,
}; };
@ -67,6 +68,7 @@ where
"bools: Negating: (!a && !b) => !(a || b) (because both expression are good for \ "bools: Negating: (!a && !b) => !(a || b) (because both expression are good for \
negation)", negation)",
); );
#[cfg(feature = "debug")]
let start = dump(&*e, false); let start = dump(&*e, false);
e.op = if e.op == op!("&&") { e.op = if e.op == op!("&&") {

View File

@ -504,7 +504,7 @@ where
let mut args = vec![]; let mut args = vec![];
if cons.args.as_ref().map(|v| v.len()).unwrap_or(0) == 1 { if cons.args.as_ref().map(|v| v.len()).unwrap_or(0) == 1 {
args.push(ExprOrSpread { args = vec![ExprOrSpread {
spread: None, spread: None,
expr: Box::new(Expr::Cond(CondExpr { expr: Box::new(Expr::Cond(CondExpr {
span: DUMMY_SP, span: DUMMY_SP,
@ -512,7 +512,7 @@ where
cons: cons.args.as_mut().unwrap()[0].expr.take(), cons: cons.args.as_mut().unwrap()[0].expr.take(),
alt: alt.args.as_mut().unwrap()[0].expr.take(), alt: alt.args.as_mut().unwrap()[0].expr.take(),
})), })),
}); }];
} }
report_change!( report_change!(
@ -715,10 +715,8 @@ where
return; return;
} }
// //
let mut new_stmts = Vec::with_capacity(stmts.len() * 2);
let mut new_stmts = vec![]; stmts.take().into_iter().for_each(|stmt| {
for stmt in stmts.take() {
match stmt.try_into_stmt() { match stmt.try_into_stmt() {
Ok(stmt) => match stmt { Ok(stmt) => match stmt {
Stmt::If(IfStmt { Stmt::If(IfStmt {
@ -753,7 +751,7 @@ where
}, },
Err(stmt) => new_stmts.push(stmt), Err(stmt) => new_stmts.push(stmt),
} }
} });
self.changed = true; self.changed = true;
report_change!("conditionals: Dropped useless `else` token"); report_change!("conditionals: Dropped useless `else` token");

View File

@ -6,7 +6,7 @@ use swc_ecma_ast::*;
use swc_ecma_utils::{undefined, ExprExt, Value::Known}; use swc_ecma_utils::{undefined, ExprExt, Value::Known};
use super::Optimizer; use super::Optimizer;
use crate::{compress::util::eval_as_number, mode::Mode, DISABLE_BUGGY_PASSES}; use crate::{compress::util::eval_as_number, maybe_par, mode::Mode, DISABLE_BUGGY_PASSES};
/// Methods related to the option `evaluate`. /// Methods related to the option `evaluate`.
impl<M> Optimizer<'_, M> impl<M> Optimizer<'_, M>
@ -399,12 +399,12 @@ where
match (ln.classify(), rn.classify()) { match (ln.classify(), rn.classify()) {
(FpCategory::Zero, FpCategory::Zero) => { (FpCategory::Zero, FpCategory::Zero) => {
// If a variable named `NaN` is in scope, don't convert e into NaN. // If a variable named `NaN` is in scope, don't convert e into NaN.
if self let data = &self.data.vars;
.data if maybe_par!(
.vars data.iter()
.iter() .any(|(name, v)| v.declared && name.0 == js_word!("NaN")),
.any(|(name, v)| v.declared && name.0 == js_word!("NaN")) *crate::LIGHT_TASK_PARALLELS
{ ) {
return; return;
} }

View File

@ -4,7 +4,9 @@ use swc_ecma_utils::{undefined, StmtExt, StmtLike};
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use super::Optimizer; use super::Optimizer;
use crate::{compress::util::is_pure_undefined, debug::dump, mode::Mode, util::ExprOptExt}; #[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{compress::util::is_pure_undefined, mode::Mode, util::ExprOptExt};
/// Methods related to the option `if_return`. All methods are noop if /// Methods related to the option `if_return`. All methods are noop if
/// `if_return` is false. /// `if_return` is false.

View File

@ -13,9 +13,10 @@ use super::{
util::{MultiReplacer, MultiReplacerMode}, util::{MultiReplacer, MultiReplacerMode},
Optimizer, Optimizer,
}; };
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{ use crate::{
compress::optimize::{util::Remapper, Ctx}, compress::optimize::{util::Remapper, Ctx},
debug::dump,
mode::Mode, mode::Mode,
util::{idents_captured_by, idents_used_by, make_number}, util::{idents_captured_by, idents_used_by, make_number},
}; };

View File

@ -6,7 +6,6 @@ use swc_ecma_utils::{find_pat_ids, ExprExt, IdentUsageFinder};
use super::Optimizer; use super::Optimizer;
use crate::{ use crate::{
compress::optimize::util::{class_has_side_effect, is_valid_for_lhs}, compress::optimize::util::{class_has_side_effect, is_valid_for_lhs},
debug::dump,
mode::Mode, mode::Mode,
util::{idents_captured_by, idents_used_by, idents_used_by_ignoring_nested}, util::{idents_captured_by, idents_used_by, idents_used_by_ignoring_nested},
}; };
@ -32,7 +31,7 @@ where
trace_op!( trace_op!(
"inline: store_var_for_inlining({}, should_preserve = {:?})", "inline: store_var_for_inlining({}, should_preserve = {:?})",
dump(&var.name, false), crate::debug::dump(&var.name, false),
should_preserve should_preserve
); );
@ -66,7 +65,7 @@ where
if should_preserve && usage.var_kind != Some(VarDeclKind::Const) { if should_preserve && usage.var_kind != Some(VarDeclKind::Const) {
log_abort!( log_abort!(
"inline: [x] Preserving non-const variable `{}` because it's top-level", "inline: [x] Preserving non-const variable `{}` because it's top-level",
dump(&var.name, false) crate::debug::dump(&var.name, false)
); );
return; return;
} }
@ -606,6 +605,7 @@ where
} }
self.changed = true; self.changed = true;
#[cfg(feature = "debug")]
match &decl { match &decl {
Decl::Class(c) => { Decl::Class(c) => {
report_change!( report_change!(
@ -711,7 +711,7 @@ where
*e = *value; *e = *value;
log_abort!("inline: [Change] {}", dump(&*e, false)) log_abort!("inline: [Change] {}", crate::debug::dump(&*e, false))
} }
} }
} }

View File

@ -14,6 +14,7 @@ use swc_ecma_utils::{
Type, Value, Type, Value,
}; };
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
#[cfg(feature = "debug")]
use tracing::{debug, span, Level}; use tracing::{debug, span, Level};
use Value::Known; use Value::Known;
@ -22,11 +23,14 @@ use self::{
util::{MultiReplacer, MultiReplacerMode}, util::{MultiReplacer, MultiReplacerMode},
}; };
use super::util::{drop_invalid_stmts, is_fine_for_if_cons}; use super::util::{drop_invalid_stmts, is_fine_for_if_cons};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{ use crate::{
analyzer::{ProgramData, UsageAnalyzer}, analyzer::{ProgramData, UsageAnalyzer},
compress::util::is_pure_undefined, compress::util::is_pure_undefined,
debug::{dump, AssertValid}, debug::AssertValid,
marks::Marks, marks::Marks,
maybe_par,
mode::Mode, mode::Mode,
option::CompressOptions, option::CompressOptions,
util::{ util::{
@ -224,6 +228,7 @@ struct Optimizer<'a, M> {
mode: &'a M, mode: &'a M,
#[allow(unused)]
debug_infinite_loop: bool, debug_infinite_loop: bool,
functions: FxHashMap<Id, FnMetadata>, functions: FxHashMap<Id, FnMetadata>,
@ -351,10 +356,12 @@ where
// Don't set in_strict for directive itself. // Don't set in_strict for directive itself.
stmt.visit_mut_with(self); stmt.visit_mut_with(self);
} else { } else {
stmt.visit_mut_with(&mut *self.with_ctx(child_ctx)); let child_optimizer = &mut *self.with_ctx(child_ctx);
stmt.visit_mut_with(child_optimizer);
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmt.visit_with(&mut AssertValid); stmt.visit_with(&mut AssertValid);
} }
@ -379,43 +386,50 @@ where
self.ctx.in_asm |= use_asm; self.ctx.in_asm |= use_asm;
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.reorder_stmts(stmts); self.reorder_stmts(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.merge_sequences_in_stmts(stmts); self.merge_sequences_in_stmts(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.merge_similar_ifs(stmts); self.merge_similar_ifs(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.make_sequences(stmts); self.make_sequences(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.drop_else_token(stmts); self.drop_else_token(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.break_assignments_in_seqs(stmts); self.break_assignments_in_seqs(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
@ -978,6 +992,7 @@ where
span, span,
left, left,
right, right,
#[cfg(feature = "debug")]
op, op,
.. ..
}) => { }) => {
@ -1194,11 +1209,11 @@ where
} }
if let Stmt::Block(block) = &mut bs.stmts[0] { if let Stmt::Block(block) = &mut bs.stmts[0] {
if block let stmts = &block.stmts;
.stmts if maybe_par!(
.iter() stmts.iter().all(|stmt| !matches!(stmt, Stmt::Decl(..))),
.all(|stmt| !matches!(stmt, Stmt::Decl(..))) *crate::LIGHT_TASK_PARALLELS
{ ) {
report_change!("optimizer: Removing nested block"); report_change!("optimizer: Removing nested block");
self.changed = true; self.changed = true;
bs.stmts = block.stmts.take(); bs.stmts = block.stmts.take();
@ -1540,7 +1555,12 @@ where
#[cfg_attr(feature = "debug", tracing::instrument(skip_all))] #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
fn visit_mut_decl(&mut self, decl: &mut Decl) { fn visit_mut_decl(&mut self, decl: &mut Decl) {
decl.visit_mut_children_with(self); match decl {
Decl::Class(class_decl) => self.visit_mut_class(&mut class_decl.class),
Decl::Fn(fn_decl) => self.visit_mut_fn_decl(fn_decl),
Decl::Var(var_decl) => self.visit_mut_var_decl(var_decl),
_ => decl.visit_mut_children_with(self),
};
self.drop_unused_decl(decl); self.drop_unused_decl(decl);
self.store_typeofs(decl); self.store_typeofs(decl);
@ -1682,7 +1702,7 @@ where
} }
#[cfg(feature = "trace-ast")] #[cfg(feature = "trace-ast")]
debug!("Output: {}", dump(e, true)); tracing::debug!("Output: {}", dump(e, true));
} }
#[cfg_attr(feature = "debug", tracing::instrument(skip_all))] #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
@ -1770,7 +1790,8 @@ where
} }
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
n.visit_with(&mut AssertValid); n.visit_with(&mut AssertValid);
} }
} }
@ -1903,7 +1924,8 @@ where
if let Some(body) = n.body.as_mut() { if let Some(body) = n.body.as_mut() {
// Bypass block scope handler. // Bypass block scope handler.
body.visit_mut_children_with(optimizer); body.visit_mut_children_with(optimizer);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
body.visit_with(&mut AssertValid); body.visit_with(&mut AssertValid);
} }
} }
@ -1925,7 +1947,8 @@ where
drop_invalid_stmts(&mut body.stmts); drop_invalid_stmts(&mut body.stmts);
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
n.visit_with(&mut AssertValid); n.visit_with(&mut AssertValid);
} }
} }
@ -2107,7 +2130,8 @@ where
} }
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
n.visit_with(&mut AssertValid); n.visit_with(&mut AssertValid);
} }
} }
@ -2189,7 +2213,8 @@ where
let old_prepend = self.prepend_stmts.take(); let old_prepend = self.prepend_stmts.take();
let old_append = self.append_stmts.take(); let old_append = self.append_stmts.take();
let _tracing = if cfg!(feature = "debug") && self.debug_infinite_loop { #[cfg(feature = "debug")]
let _tracing = if self.debug_infinite_loop {
let text = dump(&*s, false); let text = dump(&*s, false);
if text.lines().count() < 10 { if text.lines().count() < 10 {
@ -2228,7 +2253,8 @@ where
_ => {} _ => {}
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid); s.visit_with(&mut AssertValid);
} }
} }
@ -2274,7 +2300,8 @@ where
.collect(), .collect(),
}); });
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid); s.visit_with(&mut AssertValid);
} }
} }
@ -2284,7 +2311,8 @@ where
let len = self.prepend_stmts.len(); let len = self.prepend_stmts.len();
if cfg!(feature = "debug") && self.debug_infinite_loop { #[cfg(feature = "debug")]
if self.debug_infinite_loop {
let text = dump(&*s, false); let text = dump(&*s, false);
if text.lines().count() < 10 { if text.lines().count() < 10 {
@ -2343,7 +2371,8 @@ where
debug_assert_eq!(self.prepend_stmts.len(), len); debug_assert_eq!(self.prepend_stmts.len(), len);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid); s.visit_with(&mut AssertValid);
} }
@ -2369,7 +2398,8 @@ where
debug_assert_eq!(self.prepend_stmts.len(), len); debug_assert_eq!(self.prepend_stmts.len(), len);
if cfg!(feature = "debug") && self.debug_infinite_loop { #[cfg(feature = "debug")]
if self.debug_infinite_loop {
let text = dump(&*s, false); let text = dump(&*s, false);
if text.lines().count() < 10 { if text.lines().count() < 10 {
@ -2379,7 +2409,8 @@ where
debug_assert_eq!(self.prepend_stmts.len(), len); debug_assert_eq!(self.prepend_stmts.len(), len);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid); s.visit_with(&mut AssertValid);
} }
@ -2388,15 +2419,18 @@ where
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) { fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
// Skip if `use asm` exists. // Skip if `use asm` exists.
if stmts.iter().any(|stmt| match stmt.as_stmt() { if maybe_par!(
Some(Stmt::Expr(stmt)) => match &*stmt.expr { stmts.iter().any(|stmt| match stmt.as_stmt() {
Expr::Lit(Lit::Str(Str { raw, .. })) => { Some(Stmt::Expr(stmt)) => match &*stmt.expr {
matches!(raw, Some(value) if value == "\"use asm\"" || value == "'use asm'") Expr::Lit(Lit::Str(Str { raw, .. })) => {
} matches!(raw, Some(value) if value == "\"use asm\"" || value == "'use asm'")
}
_ => false,
},
_ => false, _ => false,
}, }),
_ => false, *crate::LIGHT_TASK_PARALLELS
}) { ) {
return; return;
} }
@ -2420,7 +2454,8 @@ where
} }
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
} }

View File

@ -10,16 +10,18 @@ use swc_ecma_utils::{
StmtLike, StmtLike,
}; };
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
#[cfg(feature = "debug")]
use tracing::{span, Level}; use tracing::{span, Level};
use super::{is_pure_undefined, Optimizer}; use super::{is_pure_undefined, Optimizer};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{ use crate::{
alias::{collect_infects_from, AliasConfig}, alias::{collect_infects_from, AliasConfig},
compress::{ compress::{
optimize::{unused::PropertyAccessOpts, util::replace_id_with_expr}, optimize::{unused::PropertyAccessOpts, util::replace_id_with_expr},
util::{is_directive, is_ident_used_by, replace_expr}, util::{is_directive, is_ident_used_by, replace_expr},
}, },
debug::dump,
mode::Mode, mode::Mode,
option::CompressOptions, option::CompressOptions,
util::{idents_used_by, idents_used_by_ignoring_nested, ExprOptExt, ModuleItemExt}, util::{idents_used_by, idents_used_by_ignoring_nested, ExprOptExt, ModuleItemExt},
@ -641,7 +643,8 @@ where
exprs.push(buf); exprs.push(buf);
let _tracing = if cfg!(feature = "debug") { #[cfg(feature = "debug")]
let _tracing = {
let buf_len = exprs.iter().map(|v| v.len()).collect::<Vec<_>>(); let buf_len = exprs.iter().map(|v| v.len()).collect::<Vec<_>>();
Some( Some(
tracing::span!( tracing::span!(
@ -651,8 +654,6 @@ where
) )
.entered(), .entered(),
) )
} else {
None
}; };
for mut exprs in exprs { for mut exprs in exprs {
@ -711,7 +712,8 @@ where
pub(super) fn merge_sequences_in_seq_expr(&mut self, e: &mut SeqExpr) { pub(super) fn merge_sequences_in_seq_expr(&mut self, e: &mut SeqExpr) {
self.normalize_sequences(e); self.normalize_sequences(e);
let _tracing = if cfg!(feature = "debug") { #[cfg(feature = "debug")]
let _tracing = {
let e_str = dump(&*e, false); let e_str = dump(&*e, false);
Some( Some(
@ -722,8 +724,6 @@ where
) )
.entered(), .entered(),
) )
} else {
None
}; };
if !self.options.sequences() && !e.span.has_mark(self.marks.synthesized_seq) { if !self.options.sequences() && !e.span.has_mark(self.marks.synthesized_seq) {
@ -752,13 +752,12 @@ where
/// TODO(kdy1): Check for side effects and call merge_sequential_expr more /// TODO(kdy1): Check for side effects and call merge_sequential_expr more
/// if expressions between a and b are side-effect-free. /// if expressions between a and b are side-effect-free.
fn merge_sequences_in_exprs(&mut self, exprs: &mut Vec<Mergable>) -> Result<(), ()> { fn merge_sequences_in_exprs(&mut self, exprs: &mut Vec<Mergable>) -> Result<(), ()> {
let _tracing = if cfg!(feature = "debug") { #[cfg(feature = "debug")]
let _tracing = {
Some( Some(
tracing::span!(Level::TRACE, "merge_sequences_in_exprs", len = exprs.len()) tracing::span!(Level::TRACE, "merge_sequences_in_exprs", len = exprs.len())
.entered(), .entered(),
) )
} else {
None
}; };
for idx in 0..exprs.len() { for idx in 0..exprs.len() {
@ -1207,7 +1206,8 @@ where
/// ///
/// Returns [Err] iff we should stop checking. /// Returns [Err] iff we should stop checking.
fn merge_sequential_expr(&mut self, a: &mut Mergable, b: &mut Expr) -> Result<bool, ()> { fn merge_sequential_expr(&mut self, a: &mut Mergable, b: &mut Expr) -> Result<bool, ()> {
let _tracing = if cfg!(feature = "debug") { #[cfg(feature = "debug")]
let _tracing = {
let b_str = dump(&*b, false); let b_str = dump(&*b, false);
let a_id = a.id(); let a_id = a.id();
@ -1220,8 +1220,6 @@ where
) )
.entered(), .entered(),
) )
} else {
None
}; };
match a { match a {
@ -1578,6 +1576,7 @@ where
_ => {} _ => {}
} }
#[cfg(feature = "debug")]
match a { match a {
Mergable::Var(a) => { Mergable::Var(a) => {
trace_op!( trace_op!(

View File

@ -4,11 +4,12 @@ use swc_ecma_ast::*;
use swc_ecma_utils::contains_ident_ref; use swc_ecma_utils::contains_ident_ref;
use super::Optimizer; use super::Optimizer;
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{ use crate::{
compress::{ compress::{
optimize::util::class_has_side_effect, util::is_global_var_with_pure_property_access, optimize::util::class_has_side_effect, util::is_global_var_with_pure_property_access,
}, },
debug::dump,
mode::Mode, mode::Mode,
option::PureGetterOption, option::PureGetterOption,
}; };
@ -55,6 +56,7 @@ where
} }
} }
#[cfg(debug_assertions)]
let had_init = var.init.is_some(); let had_init = var.init.is_some();
match &mut var.init { match &mut var.init {
@ -88,7 +90,8 @@ where
return; return;
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
if let Some(VarDeclKind::Const | VarDeclKind::Let) = self.ctx.var_kind { if let Some(VarDeclKind::Const | VarDeclKind::Let) = self.ctx.var_kind {
if had_init && var.init.is_none() { if had_init && var.init.is_none() {
unreachable!("const/let variable without initializer: {:#?}", var); unreachable!("const/let variable without initializer: {:#?}", var);

View File

@ -34,9 +34,9 @@ where
} }
/// RAII guard to change context temporarically /// RAII guard to change context temporarically
#[inline]
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b, M> { pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b, M> {
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
let scope_ctxt = ctx.scope; let scope_ctxt = ctx.scope;
if self.ctx.scope != scope_ctxt { if self.ctx.scope != scope_ctxt {
self.data.scopes.get(&scope_ctxt).expect("scope not found"); self.data.scopes.get(&scope_ctxt).expect("scope not found");
@ -250,7 +250,8 @@ impl VisitMut for MultiReplacer<'_> {
items.visit_mut_children_with(self); items.visit_mut_children_with(self);
if !self.changed { if !self.changed {
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
let keys = self.vars.iter().map(|(k, _)| k.clone()).collect::<Vec<_>>(); let keys = self.vars.iter().map(|(k, _)| k.clone()).collect::<Vec<_>>();
debug!("Dropping {:?}", keys); debug!("Dropping {:?}", keys);
} }

View File

@ -164,6 +164,7 @@ impl Pure<'_> {
let can_remove = if *op == op!("&&") { rb } else { !rb }; let can_remove = if *op == op!("&&") { rb } else { !rb };
if can_remove { if can_remove {
#[allow(clippy::if_same_then_else)]
if *op == op!("&&") { if *op == op!("&&") {
report_change!("booleans: Compressing `!foo && true` as `!foo`"); report_change!("booleans: Compressing `!foo && true` as `!foo`");
} else { } else {

View File

@ -5,7 +5,9 @@ use swc_ecma_ast::*;
use swc_ecma_utils::{ExprExt, Type, Value}; use swc_ecma_utils::{ExprExt, Type, Value};
use super::Pure; use super::Pure;
use crate::{compress::util::negate_cost, debug::dump, util::make_bool}; #[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{compress::util::negate_cost, util::make_bool};
impl Pure<'_> { impl Pure<'_> {
/// ///
@ -130,6 +132,7 @@ impl Pure<'_> {
} }
report_change!("conditionals: `a ? foo : bar` => `!a ? bar : foo` (considered cost)"); report_change!("conditionals: `a ? foo : bar` => `!a ? bar : foo` (considered cost)");
#[cfg(feature = "debug")]
let start_str = dump(&*cond, false); let start_str = dump(&*cond, false);
self.negate(&mut cond.test, true, false); self.negate(&mut cond.test, true, false);

View File

@ -29,7 +29,6 @@ pub(super) struct Ctx {
impl<'b> Pure<'b> { impl<'b> Pure<'b> {
/// RAII guard to change context temporarically /// RAII guard to change context temporarically
#[inline]
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b> { pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b> {
let orig_ctx = self.ctx; let orig_ctx = self.ctx;
self.ctx = ctx; self.ctx = ctx;

View File

@ -1,10 +1,11 @@
use rayon::prelude::*;
use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, DUMMY_SP}; use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, DUMMY_SP};
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_utils::{extract_var_ids, ExprExt, StmtExt, StmtLike, Value}; use swc_ecma_utils::{extract_var_ids, ExprExt, StmtExt, StmtLike, Value};
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use super::Pure; use super::Pure;
use crate::{compress::util::is_fine_for_if_cons, util::ModuleItemExt}; use crate::{compress::util::is_fine_for_if_cons, maybe_par, util::ModuleItemExt};
/// Methods related to option `dead_code`. /// Methods related to option `dead_code`.
impl Pure<'_> { impl Pure<'_> {
@ -229,29 +230,34 @@ impl Pure<'_> {
report_change!("Dropping statements after a control keyword"); report_change!("Dropping statements after a control keyword");
let mut new_stmts = Vec::with_capacity(stmts.len()); let stmts_len = stmts.len();
let mut decls = vec![];
let mut hoisted_fns = vec![];
// Hoist function and `var` declarations above return. // Hoist function and `var` declarations above return.
stmts let (decls, hoisted_fns, mut new_stmts) = stmts.iter_mut().skip(idx + 1).fold(
.iter_mut() (
.skip(idx + 1) Vec::with_capacity(stmts_len),
.for_each(|stmt| match stmt.take().try_into_stmt() { Vec::<T>::with_capacity(stmts_len),
Ok(Stmt::Decl(Decl::Fn(f))) => { Vec::with_capacity(stmts_len),
hoisted_fns.push(Stmt::Decl(Decl::Fn(f)).into()); ),
} |(mut decls, mut hoisted_fns, mut new_stmts), stmt| {
Ok(t) => { match stmt.take().try_into_stmt() {
let ids = extract_var_ids(&t).into_iter().map(|i| VarDeclarator { Ok(Stmt::Decl(Decl::Fn(f))) => {
span: i.span, hoisted_fns.push(Stmt::Decl(Decl::Fn(f)).into());
name: i.into(), }
init: None, Ok(t) => {
definite: false, let ids = extract_var_ids(&t).into_iter().map(|i| VarDeclarator {
}); span: i.span,
decls.extend(ids); name: i.into(),
} init: None,
Err(item) => new_stmts.push(item), definite: false,
}); });
decls.extend(ids);
}
Err(item) => new_stmts.push(item),
};
(decls, hoisted_fns, new_stmts)
},
);
if !decls.is_empty() { if !decls.is_empty() {
new_stmts.push( new_stmts.push(
@ -380,34 +386,58 @@ impl Pure<'_> {
T: StmtLike, T: StmtLike,
{ {
fn is_ok(b: &BlockStmt) -> bool { fn is_ok(b: &BlockStmt) -> bool {
b.stmts.iter().all(is_fine_for_if_cons) maybe_par!(
b.stmts.iter().all(is_fine_for_if_cons),
*crate::LIGHT_TASK_PARALLELS
)
} }
if stmts if maybe_par!(
.iter() stmts
.all(|stmt| !matches!(stmt.as_stmt(), Some(Stmt::Block(b)) if is_ok(b))) .iter()
{ .all(|stmt| !matches!(stmt.as_stmt(), Some(Stmt::Block(b)) if is_ok(b))),
*crate::LIGHT_TASK_PARALLELS
) {
return; return;
} }
self.changed = true; self.changed = true;
report_change!("Dropping useless block"); report_change!("Dropping useless block");
let mut new = vec![]; let old_stmts = stmts.take();
for stmt in stmts.take() {
match stmt.try_into_stmt() {
Ok(v) => match v {
Stmt::Block(v) if is_ok(&v) => {
new.extend(v.stmts.into_iter().map(T::from_stmt));
}
_ => new.push(T::from_stmt(v)),
},
Err(v) => {
new.push(v);
}
}
}
let new: Vec<T> = if old_stmts.len() >= *crate::LIGHT_TASK_PARALLELS {
old_stmts
.into_par_iter()
.flat_map(|stmt| match stmt.try_into_stmt() {
Ok(v) => match v {
Stmt::Block(v) if is_ok(&v) => {
let stmts = v.stmts;
maybe_par!(
stmts.into_iter().map(T::from_stmt).collect(),
*crate::LIGHT_TASK_PARALLELS
)
}
_ => vec![T::from_stmt(v)],
},
Err(v) => vec![v],
})
.collect()
} else {
let mut new = Vec::with_capacity(old_stmts.len() * 2);
old_stmts
.into_iter()
.for_each(|stmt| match stmt.try_into_stmt() {
Ok(v) => match v {
Stmt::Block(v) if is_ok(&v) => {
new.extend(v.stmts.into_iter().map(T::from_stmt));
}
_ => new.push(T::from_stmt(v)),
},
Err(v) => new.push(v),
});
new
};
*stmts = new; *stmts = new;
} }
@ -437,20 +467,24 @@ impl Pure<'_> {
return; return;
} }
if !stmts.iter().any(|stmt| match stmt.as_stmt() { if !maybe_par!(
Some(Stmt::If(s)) => s.test.cast_to_bool(&self.expr_ctx).1.is_known(), stmts.iter().any(|stmt| match stmt.as_stmt() {
_ => false, Some(Stmt::If(s)) => s.test.cast_to_bool(&self.expr_ctx).1.is_known(),
}) { _ => false,
}),
*crate::LIGHT_TASK_PARALLELS
) {
return; return;
} }
self.changed = true; self.changed = true;
report_change!("dead_code: Removing dead codes"); report_change!("dead_code: Removing dead codes");
let mut new = vec![]; let mut new = Vec::with_capacity(stmts.len());
stmts
for stmt in stmts.take() { .take()
match stmt.try_into_stmt() { .into_iter()
.for_each(|stmt| match stmt.try_into_stmt() {
Ok(stmt) => match stmt { Ok(stmt) => match stmt {
Stmt::If(mut s) => { Stmt::If(mut s) => {
if let Value::Known(v) = s.test.cast_to_bool(&self.expr_ctx).1 { if let Value::Known(v) = s.test.cast_to_bool(&self.expr_ctx).1 {
@ -462,14 +496,16 @@ impl Pure<'_> {
if v { if v {
if let Some(alt) = s.alt.take() { if let Some(alt) = s.alt.take() {
var_ids.extend(alt.extract_var_ids().into_iter().map(|name| { var_ids = alt
VarDeclarator { .extract_var_ids()
.into_iter()
.map(|name| VarDeclarator {
span: DUMMY_SP, span: DUMMY_SP,
name: Pat::Ident(name.into()), name: Pat::Ident(name.into()),
init: None, init: None,
definite: Default::default(), definite: Default::default(),
} })
})); .collect();
} }
if !var_ids.is_empty() { if !var_ids.is_empty() {
new.push(T::from_stmt(Stmt::Decl(Decl::Var(VarDecl { new.push(T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
@ -481,14 +517,17 @@ impl Pure<'_> {
} }
new.push(T::from_stmt(*s.cons.take())); new.push(T::from_stmt(*s.cons.take()));
} else { } else {
var_ids.extend(s.cons.extract_var_ids().into_iter().map(|name| { var_ids = s
VarDeclarator { .cons
.extract_var_ids()
.into_iter()
.map(|name| VarDeclarator {
span: DUMMY_SP, span: DUMMY_SP,
name: Pat::Ident(name.into()), name: Pat::Ident(name.into()),
init: None, init: None,
definite: Default::default(), definite: Default::default(),
} })
})); .collect();
if !var_ids.is_empty() { if !var_ids.is_empty() {
new.push(T::from_stmt(Stmt::Decl(Decl::Var(VarDecl { new.push(T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP, span: DUMMY_SP,
@ -502,14 +541,13 @@ impl Pure<'_> {
} }
} }
} else { } else {
new.push(T::from_stmt(Stmt::If(s))) new.push(T::from_stmt(Stmt::If(s)));
} }
} }
_ => new.push(T::from_stmt(stmt)), _ => new.push(T::from_stmt(stmt)),
}, },
Err(stmt) => new.push(stmt), Err(stmt) => new.push(stmt),
} });
}
*stmts = new; *stmts = new;
} }

View File

@ -85,8 +85,8 @@ impl Pure<'_> {
"if_return: Negating `foo` in `if (foo) return; bar()` to make it `if (!foo) bar()`" "if_return: Negating `foo` in `if (foo) return; bar()` to make it `if (!foo) bar()`"
); );
let mut new = vec![]; let mut new = Vec::with_capacity(stmts.len());
let mut fn_decls = vec![]; let mut fn_decls = Vec::with_capacity(stmts.len());
new.extend(stmts.drain(..pos_of_if)); new.extend(stmts.drain(..pos_of_if));
let cons = stmts let cons = stmts
.drain(1..) .drain(1..)

View File

@ -647,7 +647,6 @@ impl Pure<'_> {
})) }))
} }
#[inline(never)]
pub(super) fn ignore_return_value(&mut self, e: &mut Expr, opts: DropOpts) { pub(super) fn ignore_return_value(&mut self, e: &mut Expr, opts: DropOpts) {
self.optimize_expr_in_bool_ctx(e, true); self.optimize_expr_in_bool_ctx(e, true);

View File

@ -5,16 +5,15 @@ use swc_common::{pass::Repeated, util::take::Take, SyntaxContext, DUMMY_SP, GLOB
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_utils::{undefined, ExprCtx}; use swc_ecma_utils::{undefined, ExprCtx};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
#[cfg(feature = "debug")]
use tracing::{debug, span, Level}; use tracing::{debug, span, Level};
use self::{ctx::Ctx, misc::DropOpts}; use self::{ctx::Ctx, misc::DropOpts};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{ use crate::{
analyzer::ProgramData, analyzer::ProgramData, debug::AssertValid, marks::Marks, maybe_par, option::CompressOptions,
debug::{dump, AssertValid}, util::ModuleItemExt, MAX_PAR_DEPTH,
marks::Marks,
option::CompressOptions,
util::ModuleItemExt,
MAX_PAR_DEPTH,
}; };
mod arrows; mod arrows;
@ -40,6 +39,8 @@ pub(crate) struct PureOptimizerConfig {
pub enable_join_vars: bool, pub enable_join_vars: bool,
pub force_str_for_tpl: bool, pub force_str_for_tpl: bool,
#[cfg(feature = "debug")]
pub debug_infinite_loop: bool, pub debug_infinite_loop: bool,
} }
@ -101,32 +102,37 @@ impl Pure<'_> {
{ {
self.remove_dead_branch(stmts); self.remove_dead_branch(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.drop_unreachable_stmts(stmts); self.drop_unreachable_stmts(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.drop_useless_blocks(stmts); self.drop_useless_blocks(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
self.collapse_vars_without_init(stmts); self.collapse_vars_without_init(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
if self.config.enable_join_vars { if self.config.enable_join_vars {
self.join_vars(stmts); self.join_vars(stmts);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid); stmts.visit_with(&mut AssertValid);
} }
} }
@ -174,29 +180,50 @@ impl Pure<'_> {
self.changed |= v.changed; self.changed |= v.changed;
} }
} else { } else {
GLOBALS.with(|globals| { let mut changed = false;
let changed = nodes if nodes.len() >= *crate::HEAVY_TASK_PARALLELS {
.par_iter_mut() GLOBALS.with(|globals| {
.map(|node| { changed = nodes
GLOBALS.set(globals, || { .par_iter_mut()
let mut v = Pure { .map(|node| {
expr_ctx: self.expr_ctx.clone(), GLOBALS.set(globals, || {
ctx: Ctx { let mut v = Pure {
par_depth: self.ctx.par_depth + 1, expr_ctx: self.expr_ctx.clone(),
..self.ctx ctx: Ctx {
}, par_depth: self.ctx.par_depth + 1,
changed: false, ..self.ctx
..*self },
}; changed: false,
node.visit_mut_with(&mut v); ..*self
};
node.visit_mut_with(&mut v);
v.changed v.changed
})
}) })
}) .reduce(|| false, |a, b| a || b);
.reduce(|| false, |a, b| a || b); })
} else {
changed = nodes
.iter_mut()
.map(|node| {
let mut v = Pure {
expr_ctx: self.expr_ctx.clone(),
ctx: Ctx {
par_depth: self.ctx.par_depth + 1,
..self.ctx
},
changed: false,
..*self
};
node.visit_mut_with(&mut v);
self.changed |= changed; v.changed
}); })
.reduce(|a, b| a || b)
.unwrap_or(false);
}
self.changed |= changed;
} }
} }
} }
@ -217,7 +244,8 @@ impl VisitMut for Pure<'_> {
} }
fn visit_mut_bin_expr(&mut self, e: &mut BinExpr) { fn visit_mut_bin_expr(&mut self, e: &mut BinExpr) {
e.visit_mut_children_with(self); self.visit_mut_expr(&mut e.left);
self.visit_mut_expr(&mut e.right);
self.compress_cmp_with_long_op(e); self.compress_cmp_with_long_op(e);
@ -545,7 +573,8 @@ impl VisitMut for Pure<'_> {
self.optimize_arrow_method_prop(p); self.optimize_arrow_method_prop(p);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
p.visit_with(&mut AssertValid); p.visit_with(&mut AssertValid);
} }
} }
@ -570,7 +599,11 @@ impl VisitMut for Pure<'_> {
fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) { fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
e.visit_mut_children_with(self); e.visit_mut_children_with(self);
if e.exprs.iter().any(|e| e.is_seq()) { let exprs = &e.exprs;
if maybe_par!(
exprs.iter().any(|e| e.is_seq()),
*crate::LIGHT_TASK_PARALLELS
) {
let mut exprs = vec![]; let mut exprs = vec![];
for e in e.exprs.take() { for e in e.exprs.take() {
@ -620,13 +653,15 @@ impl VisitMut for Pure<'_> {
e.exprs.retain(|e| !e.is_invalid()); e.exprs.retain(|e| !e.is_invalid());
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
e.visit_with(&mut AssertValid); e.visit_with(&mut AssertValid);
} }
} }
fn visit_mut_stmt(&mut self, s: &mut Stmt) { fn visit_mut_stmt(&mut self, s: &mut Stmt) {
let _tracing = if cfg!(feature = "debug") && self.config.debug_infinite_loop { #[cfg(feature = "debug")]
let _tracing = if self.config.debug_infinite_loop {
let text = dump(&*s, false); let text = dump(&*s, false);
if text.lines().count() < 10 { if text.lines().count() < 10 {
@ -649,7 +684,8 @@ impl VisitMut for Pure<'_> {
s.visit_mut_children_with(&mut *self.with_ctx(ctx)); s.visit_mut_children_with(&mut *self.with_ctx(ctx));
} }
if cfg!(feature = "debug") && self.config.debug_infinite_loop { #[cfg(feature = "debug")]
if self.config.debug_infinite_loop {
let text = dump(&*s, false); let text = dump(&*s, false);
if text.lines().count() < 10 { if text.lines().count() < 10 {
@ -683,7 +719,8 @@ impl VisitMut for Pure<'_> {
} }
} }
if cfg!(feature = "debug") && self.config.debug_infinite_loop { #[cfg(feature = "debug")]
if self.config.debug_infinite_loop {
let text = dump(&*s, false); let text = dump(&*s, false);
if text.lines().count() < 10 { if text.lines().count() < 10 {
@ -691,7 +728,8 @@ impl VisitMut for Pure<'_> {
} }
} }
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid); s.visit_with(&mut AssertValid);
} }
} }
@ -717,7 +755,8 @@ impl VisitMut for Pure<'_> {
items.retain(|s| !matches!(s, Stmt::Empty(..))); items.retain(|s| !matches!(s, Stmt::Empty(..)));
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
items.visit_with(&mut AssertValid); items.visit_with(&mut AssertValid);
} }
} }

View File

@ -57,14 +57,13 @@ impl Pure<'_> {
self.changed = true; self.changed = true;
let mut cur: Option<VarDecl> = None; let mut cur: Option<VarDecl> = None;
let mut new = vec![];
for stmt in stmts.take() { let mut new: Vec<T> = Vec::with_capacity(stmts.len() * 2 + 1);
stmts.take().into_iter().for_each(|stmt| {
match stmt.try_into_stmt() { match stmt.try_into_stmt() {
Ok(stmt) => { Ok(stmt) => {
if is_directive(&stmt) { if is_directive(&stmt) {
new.push(T::from_stmt(stmt)); return new.push(T::from_stmt(stmt));
continue;
} }
match stmt { match stmt {
@ -73,11 +72,10 @@ impl Pure<'_> {
v.decls.extend(var.decls); v.decls.extend(var.decls);
} }
_ => { _ => {
new.extend( if let Some(s) = cur.take().map(|c| Stmt::Decl(Decl::Var(c))) {
cur.take().map(Decl::Var).map(Stmt::Decl).map(T::from_stmt), new.push(T::from_stmt(s));
); }
cur = Some(var);
cur = Some(var)
} }
}, },
Stmt::For(mut stmt) => match &mut stmt.init { Stmt::For(mut stmt) => match &mut stmt.init {
@ -86,22 +84,23 @@ impl Pure<'_> {
kind: VarDeclKind::Var, kind: VarDeclKind::Var,
.. ..
}, },
)) => match &mut cur { )) => {
Some(cur) if cur.kind == var.kind => { match &mut cur {
// Merge Some(cur) if cur.kind == var.kind => {
cur.decls.append(&mut var.decls); // Merge
var.decls = cur.decls.take(); cur.decls.append(&mut var.decls);
var.decls = cur.decls.take();
new.push(T::from_stmt(Stmt::For(stmt))) new.push(T::from_stmt(Stmt::For(stmt)));
}
_ => {
if let Some(s) = cur.take() {
new.push(T::from_stmt(Stmt::Decl(Decl::Var(s))));
}
new.push(T::from_stmt(Stmt::For(stmt)));
}
} }
_ => { }
new.extend(
cur.take().map(Decl::Var).map(Stmt::Decl).map(T::from_stmt),
);
new.push(T::from_stmt(Stmt::For(stmt)))
}
},
None if cur None if cur
.as_ref() .as_ref()
.map(|v| v.kind == VarDeclKind::Var) .map(|v| v.kind == VarDeclKind::Var)
@ -112,32 +111,35 @@ impl Pure<'_> {
.and_then(|v| if v.decls.is_empty() { None } else { Some(v) }) .and_then(|v| if v.decls.is_empty() { None } else { Some(v) })
.map(VarDeclOrExpr::VarDecl); .map(VarDeclOrExpr::VarDecl);
new.push(T::from_stmt(Stmt::For(stmt))) new.push(T::from_stmt(Stmt::For(stmt)));
} }
_ => { _ => {
new.extend( if let Some(s) = cur.take() {
cur.take().map(Decl::Var).map(Stmt::Decl).map(T::from_stmt), new.push(T::from_stmt(Stmt::Decl(Decl::Var(s))));
); }
new.push(T::from_stmt(Stmt::For(stmt)));
new.push(T::from_stmt(Stmt::For(stmt)))
} }
}, },
_ => { _ => {
new.extend(cur.take().map(Decl::Var).map(Stmt::Decl).map(T::from_stmt)); if let Some(s) = cur.take() {
new.push(T::from_stmt(Stmt::Decl(Decl::Var(s))));
new.push(T::from_stmt(stmt)) }
new.push(T::from_stmt(stmt));
} }
} }
} }
Err(item) => { Err(item) => {
new.extend(cur.take().map(Decl::Var).map(Stmt::Decl).map(T::from_stmt)); if let Some(s) = cur.take() {
new.push(T::from_stmt(Stmt::Decl(Decl::Var(s))));
}
new.push(item); new.push(item);
} }
} }
} });
new.extend(cur.take().map(Decl::Var).map(Stmt::Decl).map(T::from_stmt)); if let Some(s) = cur.take() {
new.push(T::from_stmt(Stmt::Decl(Decl::Var(s))));
}
drop_invalid_stmts(&mut new); drop_invalid_stmts(&mut new);
@ -160,11 +162,10 @@ impl Pure<'_> {
} }
{ {
let mut need_work = false;
let mut found_vars_without_init = false; let mut found_vars_without_init = false;
let mut found_other = false; let mut found_other = false;
let mut need_work = false; let if_need_work = stmts.iter().any(|stmt| {
for stmt in &*stmts {
match stmt.as_stmt() { match stmt.as_stmt() {
Some(Stmt::Decl(Decl::Var( Some(Stmt::Decl(Decl::Var(
v @ VarDecl { v @ VarDecl {
@ -172,7 +173,9 @@ impl Pure<'_> {
.. ..
}, },
))) => { ))) => {
if v.decls.iter().all(|v| v.init.is_none()) { if !(found_other && found_vars_without_init)
&& v.decls.iter().all(|v| v.init.is_none())
{
if found_other { if found_other {
need_work = true; need_work = true;
} }
@ -198,12 +201,13 @@ impl Pure<'_> {
found_other = true; found_other = true;
} }
} }
} need_work
});
// Check for nested variable declartions. // Check for nested variable declartions.
let mut v = VarWithOutInitCounter::default(); let mut v = VarWithOutInitCounter::default();
stmts.visit_with(&mut v); stmts.visit_with(&mut v);
if !need_work && !v.need_work { if !if_need_work && !v.need_work {
return; return;
} }
} }
@ -372,15 +376,15 @@ impl VisitMut for VarMover {
let has_init = d.iter().any(|v| v.init.is_some()); let has_init = d.iter().any(|v| v.init.is_some());
if has_init { if has_init {
let mut new = vec![]; let mut new = Vec::with_capacity(d.len());
for v in d.take() { d.take().into_iter().for_each(|v| {
if v.init.is_some() { if v.init.is_some() {
new.push(v) new.push(v)
} else { } else {
self.vars.push(v) self.vars.push(v)
} }
} });
*d = new; *d = new;
} }
@ -391,13 +395,13 @@ impl VisitMut for VarMover {
new.append(&mut self.vars); new.append(&mut self.vars);
} }
for v in d.take() { d.take().into_iter().for_each(|v| {
if v.init.is_some() { if v.init.is_some() {
new.push(v) new.push(v)
} else { } else {
self.vars.push(v) self.vars.push(v)
} }
} });
*d = new; *d = new;
} }

View File

@ -3,15 +3,19 @@ use std::f64;
use swc_atoms::js_word; use swc_atoms::js_word;
use swc_common::{util::take::Take, DUMMY_SP}; use swc_common::{util::take::Take, DUMMY_SP};
use swc_ecma_ast::*; use swc_ecma_ast::*;
#[cfg(feature = "debug")]
use swc_ecma_transforms_base::fixer::fixer; use swc_ecma_transforms_base::fixer::fixer;
use swc_ecma_utils::{ExprCtx, ExprExt, IdentUsageFinder, Value}; use swc_ecma_utils::{ExprCtx, ExprExt, IdentUsageFinder, Value};
#[cfg(feature = "debug")]
use swc_ecma_visit::{as_folder, FoldWith};
use swc_ecma_visit::{ use swc_ecma_visit::{
as_folder, noop_visit_mut_type, noop_visit_type, FoldWith, Visit, VisitMut, VisitMutWith, noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
VisitWith,
}; };
use unicode_id::UnicodeID; use unicode_id::UnicodeID;
use crate::{debug::dump, util::ModuleItemExt}; #[cfg(feature = "debug")]
use crate::debug::dump;
use crate::util::ModuleItemExt;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -40,6 +44,7 @@ fn negate_inner(
in_bool_ctx: bool, in_bool_ctx: bool,
is_ret_val_ignored: bool, is_ret_val_ignored: bool,
) -> bool { ) -> bool {
#[cfg(feature = "debug")]
let start_str = dump(&*e, false); let start_str = dump(&*e, false);
match e { match e {
@ -225,7 +230,8 @@ pub(crate) fn is_ok_to_negate_rhs(expr_ctx: &ExprCtx, rhs: &Expr) -> bool {
return true; return true;
} }
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
tracing::warn!( tracing::warn!(
"unimplemented: is_ok_to_negate_rhs: `{}`", "unimplemented: is_ok_to_negate_rhs: `{}`",
dump(&*rhs, false) dump(&*rhs, false)
@ -239,6 +245,7 @@ pub(crate) fn is_ok_to_negate_rhs(expr_ctx: &ExprCtx, rhs: &Expr) -> bool {
/// A negative value means that it's efficient to negate the expression. /// A negative value means that it's efficient to negate the expression.
#[cfg_attr(feature = "debug", tracing::instrument(skip(e)))] #[cfg_attr(feature = "debug", tracing::instrument(skip(e)))]
#[allow(clippy::let_and_return)]
pub(crate) fn negate_cost( pub(crate) fn negate_cost(
expr_ctx: &ExprCtx, expr_ctx: &ExprCtx,
e: &Expr, e: &Expr,
@ -359,7 +366,8 @@ pub(crate) fn negate_cost(
})(); })();
// Print more info while testing negate_cost // Print more info while testing negate_cost
if cfg!(test) { #[cfg(test)]
{
trace_op!( trace_op!(
"negation cost of `{}` = {}", "negation cost of `{}` = {}",
dump(&e.clone().fold_with(&mut as_folder(fixer(None))), true), dump(&e.clone().fold_with(&mut as_folder(fixer(None))), true),

View File

@ -41,7 +41,8 @@ where
N: swc_ecma_codegen::Node + Clone + VisitMutWith<DropSpan> + VisitMutWith<Debugger>, N: swc_ecma_codegen::Node + Clone + VisitMutWith<DropSpan> + VisitMutWith<Debugger>,
{ {
if !force { if !force {
if !cfg!(feature = "debug") { #[cfg(not(feature = "debug"))]
{
return String::new(); return String::new();
} }
} }
@ -71,7 +72,8 @@ where
/// If the cargo feature `debug` is disabled or the environment variable /// If the cargo feature `debug` is disabled or the environment variable
/// `SWC_RUN` is not `1`, this function is noop. /// `SWC_RUN` is not `1`, this function is noop.
pub(crate) fn invoke(module: &Module) { pub(crate) fn invoke(module: &Module) {
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
module.visit_with(&mut AssertValid); module.visit_with(&mut AssertValid);
} }

View File

@ -239,6 +239,7 @@ impl Evaluator {
PureOptimizerConfig { PureOptimizerConfig {
enable_join_vars: false, enable_join_vars: false,
force_str_for_tpl: Eval::force_str_for_tpl(), force_str_for_tpl: Eval::force_str_for_tpl(),
#[cfg(feature = "debug")]
debug_infinite_loop: false, debug_infinite_loop: false,
}, },
)); ));

View File

@ -28,6 +28,7 @@
use compress::{pure_optimizer, PureOptimizerConfig}; use compress::{pure_optimizer, PureOptimizerConfig};
use mode::Mode; use mode::Mode;
use once_cell::sync::Lazy;
use swc_common::{comments::Comments, pass::Repeat, sync::Lrc, SourceMap, GLOBALS}; use swc_common::{comments::Comments, pass::Repeat, sync::Lrc, SourceMap, GLOBALS};
use swc_ecma_ast::Module; use swc_ecma_ast::Module;
use swc_ecma_visit::{FoldWith, VisitMutWith}; use swc_ecma_visit::{FoldWith, VisitMutWith};
@ -67,7 +68,10 @@ mod util;
const DISABLE_BUGGY_PASSES: bool = true; const DISABLE_BUGGY_PASSES: bool = true;
const MAX_PAR_DEPTH: u8 = 3; const MAX_PAR_DEPTH: u8 = 3;
#[inline] pub(crate) static CPU_COUNT: Lazy<usize> = Lazy::new(num_cpus::get);
pub(crate) static HEAVY_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 8);
pub(crate) static LIGHT_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 100);
pub fn optimize( pub fn optimize(
mut m: Module, mut m: Module,
_cm: Lrc<SourceMap>, _cm: Lrc<SourceMap>,
@ -163,6 +167,7 @@ pub fn optimize(
PureOptimizerConfig { PureOptimizerConfig {
force_str_for_tpl: Minification::force_str_for_tpl(), force_str_for_tpl: Minification::force_str_for_tpl(),
enable_join_vars: true, enable_join_vars: true,
#[cfg(feature = "debug")]
debug_infinite_loop: false, debug_infinite_loop: false,
}, },
))); )));
@ -194,9 +199,6 @@ pub fn optimize(
if let Some(ref mut t) = timings { if let Some(ref mut t) = timings {
t.section("hygiene"); t.section("hygiene");
}
if let Some(ref mut t) = timings {
t.end_section(); t.end_section();
} }

View File

@ -1,6 +1,7 @@
/// Used when something is modified. /// Used when something is modified.
macro_rules! report_change { macro_rules! report_change {
($($tt:tt)+) => {{ ($($tt:tt)+) => {{
#[cfg(feature = "debug")]
tracing::debug!( tracing::debug!(
kind = "change", kind = "change",
$($tt)* $($tt)*
@ -11,7 +12,8 @@ macro_rules! report_change {
/// Used when a function decided to give up. /// Used when a function decided to give up.
macro_rules! log_abort { macro_rules! log_abort {
($($tt:tt)+) => {{ ($($tt:tt)+) => {{
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
tracing::trace!( tracing::trace!(
kind = "abort", kind = "abort",
$($tt)* $($tt)*
@ -22,7 +24,8 @@ macro_rules! log_abort {
macro_rules! dump_change_detail { macro_rules! dump_change_detail {
($($tt:tt)+) => {{ ($($tt:tt)+) => {{
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
tracing::trace!( tracing::trace!(
kind = "detail", kind = "detail",
$($tt)* $($tt)*
@ -33,7 +36,8 @@ macro_rules! dump_change_detail {
macro_rules! trace_op { macro_rules! trace_op {
($($tt:tt)+) => {{ ($($tt:tt)+) => {{
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
tracing::trace!( tracing::trace!(
$($tt)* $($tt)*
); );

View File

@ -79,7 +79,6 @@ impl VisitMut for GlobalDefs {
n.visit_mut_children_with(self); n.visit_mut_children_with(self);
} }
#[inline]
fn visit_mut_update_expr(&mut self, e: &mut UpdateExpr) { fn visit_mut_update_expr(&mut self, e: &mut UpdateExpr) {
match &mut *e.arg { match &mut *e.arg {
Expr::Ident(..) => {} Expr::Ident(..) => {}

View File

@ -3,6 +3,7 @@ use swc_atoms::JsWord;
use swc_common::collections::AHashMap; use swc_common::collections::AHashMap;
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
#[cfg(feature = "debug")]
use tracing::trace; use tracing::trace;
use self::scope::Scope; use self::scope::Scope;
@ -47,17 +48,18 @@ impl Analyzer {
} }
fn add_decl(&mut self, id: Id) { fn add_decl(&mut self, id: Id) {
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
trace!("add_decl({:?})", id); trace!("add_decl({:?})", id);
} }
self.scope.add_decl(&id); self.scope.add_decl(&id);
} }
fn add_usage(&mut self, id: Id) { fn add_usage(&mut self, id: Id) {
if cfg!(feature = "debug") { #[cfg(feature = "debug")]
{
trace!("add_usage({:?})", id); trace!("add_usage({:?})", id);
} }
self.scope.add_usage(&id); self.scope.add_usage(&id);
} }

View File

@ -6,9 +6,10 @@ use rustc_hash::{FxHashMap, FxHashSet};
use swc_atoms::{js_word, JsWord}; use swc_atoms::{js_word, JsWord};
use swc_common::{collections::AHashMap, util::take::Take}; use swc_common::{collections::AHashMap, util::take::Take};
use swc_ecma_ast::Id; use swc_ecma_ast::Id;
#[cfg(debug_assertions)]
use tracing::debug; use tracing::debug;
use crate::util::base54; use crate::{maybe_par, util::base54};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct Scope { pub(crate) struct Scope {
@ -128,7 +129,6 @@ impl Scope {
} }
} }
#[inline(never)]
fn rename_one_scope( fn rename_one_scope(
&self, &self,
to: &mut AHashMap<Id, JsWord>, to: &mut AHashMap<Id, JsWord>,
@ -158,7 +158,8 @@ impl Scope {
} }
if self.can_rename(&id, &sym, cloned_reverse) { if self.can_rename(&id, &sym, cloned_reverse) {
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
debug!("mangle: `{}{:?}` -> {}", id.0, id.1, sym); debug!("mangle: `{}{:?}` -> {}", id.0, id.1, sym);
} }
@ -173,7 +174,6 @@ impl Scope {
} }
} }
#[inline(never)]
fn can_rename(&self, id: &Id, symbol: &JsWord, reverse: &FxHashMap<JsWord, Vec<Id>>) -> bool { fn can_rename(&self, id: &Id, symbol: &JsWord, reverse: &FxHashMap<JsWord, Vec<Id>>) -> bool {
// We can optimize this // We can optimize this
// We only need to check the current scope and parents (ignoring `a` generated // We only need to check the current scope and parents (ignoring `a` generated
@ -194,6 +194,11 @@ impl Scope {
} }
pub fn rename_cost(&self) -> usize { pub fn rename_cost(&self) -> usize {
self.data.queue.len() + self.children.iter().map(|v| v.rename_cost()).sum::<usize>() let children = &self.children;
self.data.queue.len()
+ maybe_par!(
children.iter().map(|v| v.rename_cost()).sum::<usize>(),
*crate::LIGHT_TASK_PARALLELS
)
} }
} }

View File

@ -2,6 +2,8 @@ use swc_common::{util::take::Take, DUMMY_SP};
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
use crate::maybe_par;
/// ///
/// - merge exports /// - merge exports
pub(crate) fn merge_exports() -> impl VisitMut { pub(crate) fn merge_exports() -> impl VisitMut {
@ -17,9 +19,12 @@ impl VisitMut for Merger {
noop_visit_mut_type!(); noop_visit_mut_type!();
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) { fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
let was_module = stmts let was_module = maybe_par!(
.iter() stmts
.any(|s| matches!(s, ModuleItem::ModuleDecl(..))); .iter()
.any(|s| matches!(s, ModuleItem::ModuleDecl(..))),
*crate::LIGHT_TASK_PARALLELS
);
// Fast-path // Fast-path
if !was_module { if !was_module {

View File

@ -1,7 +1,7 @@
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
use crate::option::CompressOptions; use crate::{maybe_par, option::CompressOptions};
pub fn postcompress_optimizer(options: &CompressOptions) -> impl '_ + VisitMut { pub fn postcompress_optimizer(options: &CompressOptions) -> impl '_ + VisitMut {
PostcompressOptimizer { PostcompressOptimizer {
@ -38,9 +38,12 @@ impl VisitMut for PostcompressOptimizer<'_> {
} }
fn visit_mut_module_items(&mut self, nodes: &mut Vec<ModuleItem>) { fn visit_mut_module_items(&mut self, nodes: &mut Vec<ModuleItem>) {
self.ctx.is_module = nodes self.ctx.is_module = maybe_par!(
.iter() nodes
.any(|s| matches!(s, ModuleItem::ModuleDecl(..))); .iter()
.any(|s| matches!(s, ModuleItem::ModuleDecl(..))),
*crate::LIGHT_TASK_PARALLELS
);
self.ctx.is_top_level = true; self.ctx.is_top_level = true;
nodes.visit_mut_children_with(self); nodes.visit_mut_children_with(self);

View File

@ -6,6 +6,7 @@ use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
use crate::{ use crate::{
analyzer::{analyze, ProgramData, UsageAnalyzer}, analyzer::{analyze, ProgramData, UsageAnalyzer},
marks::Marks, marks::Marks,
maybe_par,
option::CompressOptions, option::CompressOptions,
util::ModuleItemExt, util::ModuleItemExt,
}; };
@ -24,7 +25,7 @@ pub(crate) fn precompress_optimizer(options: &CompressOptions, marks: Marks) ->
} }
#[derive(Debug)] #[derive(Debug)]
struct PrecompressOptimizer<'a> { pub(crate) struct PrecompressOptimizer<'a> {
options: &'a CompressOptions, options: &'a CompressOptions,
marks: Marks, marks: Marks,
@ -50,9 +51,12 @@ impl PrecompressOptimizer<'_> {
} }
if self.data.is_none() { if self.data.is_none() {
let has_decl = stmts let has_decl = maybe_par!(
.iter() stmts
.any(|stmt| matches!(stmt.as_module_decl(), Ok(..) | Err(Stmt::Decl(..)))); .iter()
.any(|stmt| matches!(stmt.as_module_decl(), Ok(..) | Err(Stmt::Decl(..)))),
*crate::LIGHT_TASK_PARALLELS
);
if has_decl { if has_decl {
let data = Some(analyze(&*stmts, Some(self.marks))); let data = Some(analyze(&*stmts, Some(self.marks)));
@ -66,8 +70,7 @@ impl PrecompressOptimizer<'_> {
}); });
return; return;
} }
stmts.iter_mut().for_each(|stmt| {
for stmt in stmts {
stmt.visit_mut_with(&mut PrecompressOptimizer { stmt.visit_mut_with(&mut PrecompressOptimizer {
options: self.options, options: self.options,
marks: self.marks, marks: self.marks,
@ -75,7 +78,7 @@ impl PrecompressOptimizer<'_> {
fn_decl_count: Default::default(), fn_decl_count: Default::default(),
ctx: self.ctx, ctx: self.ctx,
}) })
} });
} }
} }
} }

View File

@ -139,7 +139,6 @@ pub(crate) trait ExprOptExt: Sized {
} }
} }
#[inline]
fn prepend_exprs(&mut self, mut exprs: Vec<Box<Expr>>) { fn prepend_exprs(&mut self, mut exprs: Vec<Box<Expr>>) {
if exprs.is_empty() { if exprs.is_empty() {
return; return;
@ -164,24 +163,20 @@ pub(crate) trait ExprOptExt: Sized {
} }
impl ExprOptExt for Box<Expr> { impl ExprOptExt for Box<Expr> {
#[inline]
fn as_expr(&self) -> &Expr { fn as_expr(&self) -> &Expr {
self self
} }
#[inline]
fn as_mut(&mut self) -> &mut Expr { fn as_mut(&mut self) -> &mut Expr {
self self
} }
} }
impl ExprOptExt for Expr { impl ExprOptExt for Expr {
#[inline]
fn as_expr(&self) -> &Expr { fn as_expr(&self) -> &Expr {
self self
} }
#[inline]
fn as_mut(&mut self) -> &mut Expr { fn as_mut(&mut self) -> &mut Expr {
self self
} }
@ -279,14 +274,12 @@ pub(crate) trait IsModuleItem {
} }
impl IsModuleItem for Stmt { impl IsModuleItem for Stmt {
#[inline]
fn is_module_item() -> bool { fn is_module_item() -> bool {
false false
} }
} }
impl IsModuleItem for ModuleItem { impl IsModuleItem for ModuleItem {
#[inline]
fn is_module_item() -> bool { fn is_module_item() -> bool {
true true
} }
@ -315,7 +308,6 @@ impl<V> Repeated for Optional<V>
where where
V: Repeated, V: Repeated,
{ {
#[inline]
fn changed(&self) -> bool { fn changed(&self) -> bool {
if self.enabled { if self.enabled {
return false; return false;
@ -324,7 +316,6 @@ where
self.visitor.changed() self.visitor.changed()
} }
#[inline]
fn reset(&mut self) { fn reset(&mut self) {
if self.enabled { if self.enabled {
return; return;
@ -347,7 +338,6 @@ impl<V> Fold for Optional<V>
where where
V: Fold, V: Fold,
{ {
#[inline(always)]
fn fold_module(&mut self, module: Module) -> Module { fn fold_module(&mut self, module: Module) -> Module {
if !self.enabled { if !self.enabled {
return module; return module;
@ -548,9 +538,12 @@ pub(crate) fn can_end_conditionally(s: &Stmt) -> bool {
} }
pub fn now() -> Option<Instant> { pub fn now() -> Option<Instant> {
if cfg!(target_arch = "wasm32") { #[cfg(target_arch = "wasm32")]
{
None None
} else { }
#[cfg(not(target_arch = "wasm32"))]
{
Some(Instant::now()) Some(Instant::now())
} }
} }
@ -592,3 +585,97 @@ impl Visit for EvalFinder {
} }
} }
} }
#[macro_export(local_inner_macros)]
#[allow(clippy::crate_in_macro_def)]
macro_rules! maybe_par {
($prefix:ident.$name:ident.iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
if $prefix.$name.len() >= $threshold {
use rayon::prelude::*;
$prefix.$name.par_iter().$operator($($rest)*)
} else {
$prefix.$name.iter().$operator($($rest)*)
}
};
($prefix:ident.$name:ident.into_iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
if $prefix.$name.len() >= $threshold {
use rayon::prelude::*;
$prefix.$name.into_par_iter().$operator($($rest)*)
} else {
$prefix.$name.into_iter().$operator($($rest)*)
}
};
($name:ident.iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
if $name.len() >= $threshold {
use rayon::prelude::*;
$name.par_iter().$operator($($rest)*)
} else {
$name.iter().$operator($($rest)*)
}
};
($name:ident.into_iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
if $name.len() >= $threshold {
use rayon::prelude::*;
$name.into_par_iter().$operator($($rest)*)
} else {
$name.into_iter().$operator($($rest)*)
}
};
($name:ident.iter_mut().$operator:ident($($rest:expr)*), $threshold:expr) => {
if $name.len() >= $threshold {
use rayon::prelude::*;
$name.par_iter_mut().$operator($($rest)*)
} else {
$name.iter_mut().$operator($($rest)*)
}
};
($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
if $name.len() >= $threshold {
use rayon::prelude::*;
$name.par_iter().$operator($($rest)*).$operator2($($rest2)*)
} else {
$name.iter().$operator($($rest)*).$operator2($($rest2)*)
}
};
($name:ident.into_iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
if $name.len() >= $threshold {
use rayon::prelude::*;
$name.into_par_iter().$operator($($rest)*).$operator2($($rest2)*)
} else {
$name.into_iter().$operator($($rest)*).$operator2($($rest2)*)
}
};
($name:ident.iter_mut().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
if $name.len() >= $threshold {
use rayon::prelude::*;
$name.par_iter_mut().$operator($($rest)*).$operator2($($rest2)*)
} else {
$name.iter_mut().$operator($($rest)*).$operator2($($rest2)*)
}
};
($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident::<$t:ty>($($rest2:expr)*), $threshold:expr) => {
if $name.len() >= $threshold {
use rayon::prelude::*;
$name.par_iter().$operator($($rest)*).$operator2::<$t>($($rest2)*)
} else {
$name.iter().$operator($($rest)*).$operator2::<$t>($($rest2)*)
}
};
($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*).$operator3:ident($($rest3:expr)*), $threshold:expr) => {
if $name.len() >= $threshold {
use rayon::prelude::*;
$name.par_iter().$operator($($rest)*).$operator2($($rest2)*).$operator3($($rest3)*)
} else {
$name.iter().$operator($($rest)*).$operator2($($rest2)*).$operator3($($rest3)*)
}
};
}

View File

@ -1,30 +1,22 @@
use std::cmp::Ordering; use std::cmp::Ordering;
pub(crate) fn is_sorted_by<T, F>(mut items: impl Iterator<Item = T>, mut compare: F) -> bool pub(crate) fn is_sorted_by<I, T, F>(mut items: I, mut compare: F) -> bool
where where
I: Iterator<Item = T>,
T: Copy, T: Copy,
F: FnMut(&T, &T) -> Option<Ordering>, F: FnMut(&T, &T) -> Option<Ordering>,
{ {
let mut last = match items.next() { let last = match items.next() {
Some(e) => e, Some(e) => e,
None => return true, None => return true,
}; };
for curr in items { items
if let Some(Ordering::Greater) | None = compare(&last, &curr) { .try_fold(last, |last, curr| {
return false; if let Some(Ordering::Greater) | None = compare(&last, &curr) {
} return Err(());
last = curr; }
} Ok(curr)
})
true .is_ok()
}
pub(crate) fn is_sorted_by_key<T, F, K>(items: impl Iterator<Item = T>, key: F) -> bool
where
T: Copy,
F: FnMut(T) -> K,
K: Copy + PartialOrd,
{
is_sorted_by(items.map(key), PartialOrd::partial_cmp)
} }

View File

@ -4,9 +4,13 @@ use swc_common::Mark;
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene}; use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene};
use swc_ecma_utils::DropSpan; use swc_ecma_utils::DropSpan;
use swc_ecma_visit::{as_folder, FoldWith, VisitMut, VisitMutWith, VisitWith}; #[cfg(debug_assertions)]
use swc_ecma_visit::VisitWith;
use swc_ecma_visit::{as_folder, FoldWith, VisitMut, VisitMutWith};
use crate::debug::{dump, AssertValid}; use crate::debug::dump;
#[cfg(debug_assertions)]
use crate::debug::AssertValid;
/// Indicates a unit of minifaction. /// Indicates a unit of minifaction.
pub(crate) trait CompileUnit: pub(crate) trait CompileUnit:
@ -19,11 +23,14 @@ pub(crate) trait CompileUnit:
fn is_module() -> bool; fn is_module() -> bool;
fn dump(&self) -> String { fn dump(&self) -> String {
if !cfg!(feature = "debug") { #[cfg(feature = "debug")]
return String::new(); {
self.force_dump()
}
#[cfg(not(feature = "debug"))]
{
String::new()
} }
self.force_dump()
} }
fn force_dump(&self) -> String; fn force_dump(&self) -> String;
@ -90,7 +97,8 @@ impl CompileUnit for FnExpr {
V: VisitMut, V: VisitMut,
{ {
self.visit_mut_with(&mut *visitor); self.visit_mut_with(&mut *visitor);
if cfg!(debug_assertions) { #[cfg(debug_assertions)]
{
self.visit_with(&mut AssertValid); self.visit_with(&mut AssertValid);
} }
} }

View File

@ -1900,7 +1900,6 @@ pub fn opt_chain_test(
} }
/// inject `branch` after directives /// inject `branch` after directives
#[inline(never)]
pub fn prepend_stmt<T: StmtLike>(stmts: &mut Vec<T>, stmt: T) { pub fn prepend_stmt<T: StmtLike>(stmts: &mut Vec<T>, stmt: T) {
let idx = stmts let idx = stmts
.iter() .iter()

View File

@ -192,12 +192,10 @@ impl<V> Repeated for Folder<V>
where where
V: Repeated + VisitMut, V: Repeated + VisitMut,
{ {
#[inline(always)]
fn changed(&self) -> bool { fn changed(&self) -> bool {
self.0.changed() self.0.changed()
} }
#[inline(always)]
fn reset(&mut self) { fn reset(&mut self) {
self.0.reset(); self.0.reset();
} }
@ -214,7 +212,6 @@ where
macro_rules! delegate { macro_rules! delegate {
($name:ident, $T:ty) => { ($name:ident, $T:ty) => {
#[inline(always)]
fn $name(&mut self, n: &mut $T) { fn $name(&mut self, n: &mut $T) {
n.visit_mut_with(&mut self.0); n.visit_mut_with(&mut self.0);
} }
@ -249,7 +246,6 @@ where
macro_rules! method { macro_rules! method {
($name:ident, $T:ty) => { ($name:ident, $T:ty) => {
#[inline(always)]
fn $name(&mut self, mut n: $T) -> $T { fn $name(&mut self, mut n: $T) -> $T {
n.visit_mut_with(&mut self.0); n.visit_mut_with(&mut self.0);
n n
@ -295,7 +291,6 @@ where
#[macro_export] #[macro_export]
macro_rules! noop_fold_type { macro_rules! noop_fold_type {
($name:ident, $N:tt) => { ($name:ident, $N:tt) => {
#[inline]
fn $name(&mut self, node: $crate::swc_ecma_ast::$N) -> $crate::swc_ecma_ast::$N { fn $name(&mut self, node: $crate::swc_ecma_ast::$N) -> $crate::swc_ecma_ast::$N {
node node
} }
@ -372,7 +367,6 @@ macro_rules! noop_fold_type {
#[macro_export] #[macro_export]
macro_rules! noop_visit_type { macro_rules! noop_visit_type {
($name:ident, $N:tt) => { ($name:ident, $N:tt) => {
#[inline]
fn $name(&mut self, _: &$crate::swc_ecma_ast::$N) {} fn $name(&mut self, _: &$crate::swc_ecma_ast::$N) {}
}; };
() => { () => {
@ -434,7 +428,6 @@ macro_rules! noop_visit_type {
#[macro_export] #[macro_export]
macro_rules! noop_visit_mut_type { macro_rules! noop_visit_mut_type {
($name:ident, $N:ident) => { ($name:ident, $N:ident) => {
#[inline]
fn $name(&mut self, _: &mut $crate::swc_ecma_ast::$N) {} fn $name(&mut self, _: &mut $crate::swc_ecma_ast::$N) {}
}; };
() => { () => {

View File

@ -14,7 +14,7 @@ bench = false
[dependencies] [dependencies]
[target.'cfg(not(target_os = "linux"))'.dependencies] [target.'cfg(not(target_os = "linux"))'.dependencies]
mimalloc-rust = { version = "0.1" } mimalloc-rust = { version = "0.2" }
[target.'cfg(all(target_os = "linux", target_env = "gnu", any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies] [target.'cfg(all(target_os = "linux", not(all(target_env = "musl", target_arch = "aarch64"))))'.dependencies]
tikv-jemallocator = { version = "0.4", features = ["disable_initial_exec_tls"] } mimalloc-rust = { version = "0.2", features = ["local-dynamic-tls"] }

View File

@ -2,14 +2,9 @@
//! //!
//! The swc crates related to the node binding should depend on this crate. //! The swc crates related to the node binding should depend on this crate.
#[cfg(not(target_os = "linux"))]
#[global_allocator]
static GLOBAL: mimalloc_rust::GlobalMiMalloc = mimalloc_rust::GlobalMiMalloc;
#[cfg(all( #[cfg(all(
target_os = "linux", not(all(target_os = "linux", target_env = "musl", target_arch = "aarch64")),
target_env = "gnu", not(debug_assertions)
any(target_arch = "x86_64", target_arch = "aarch64")
))] ))]
#[global_allocator] #[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; static ALLOC: mimalloc_rust::GlobalMiMalloc = mimalloc_rust::GlobalMiMalloc;