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

View File

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

View File

@ -25,6 +25,7 @@ ahash = "0.7.6"
arrayvec = "0.7.2"
backtrace = { version = "0.3.61", optional = true }
indexmap = "1.7.0"
num_cpus = "1.13.1"
once_cell = "1.10.0"
parking_lot = "0.12.0"
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_common::{collections::AHashSet, SyntaxContext};
use swc_ecma_ast::*;
@ -163,35 +165,41 @@ pub(crate) struct ProgramData {
impl ProgramData {
pub(crate) fn expand_infected(
&self,
ids: impl IntoIterator<Item = Id>,
ids: FxHashSet<Id>,
max_num: usize,
) -> Result<FxHashSet<Id>, ()> {
let mut result = FxHashSet::default();
self.expand_infected_inner(ids, max_num, &mut result)?;
Ok(result)
}
fn expand_infected_inner(
&self,
ids: impl IntoIterator<Item = Id>,
max_num: usize,
result: &mut FxHashSet<Id>,
) -> Result<(), ()> {
for id in ids {
if !result.insert(id.clone()) {
let init =
HashSet::with_capacity_and_hasher(max_num, BuildHasherDefault::<FxHasher>::default());
ids.into_iter().try_fold(init, |mut res, id| {
let mut ids = Vec::with_capacity(max_num);
ids.push(id);
let mut ranges = vec![0..1usize];
loop {
let range = ranges.remove(0);
for index in range {
let iid = ids.get(index).unwrap();
if !res.insert(iid.clone()) {
continue;
}
if result.len() >= max_num {
if res.len() >= max_num {
return Err(());
}
if let Some(info) = self.vars.get(&id) {
let ids = info.infects.clone();
self.expand_infected_inner(ids, max_num, result)?;
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);
}
}
Ok(())
}
if ranges.is_empty() {
break;
}
}
Ok(res)
})
}
pub(crate) fn contains_unresolved(&self, e: &Expr) -> bool {
@ -633,7 +641,8 @@ where
e.visit_children_with(&mut *self.with_ctx(ctx));
if let Expr::Ident(i) = e {
if cfg!(feature = "debug") {
#[cfg(feature = "debug")]
{
// debug!(
// "Usage: `{}``; update = {:?}, assign_lhs = {:?} ",
// i,

View File

@ -1,3 +1,4 @@
use rayon::prelude::*;
use swc_common::{collections::AHashSet, pass::Repeated, util::take::Take, DUMMY_SP};
use swc_ecma_ast::*;
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 crate::{
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 {
@ -46,8 +47,9 @@ impl Hoister<'_> {
Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer>,
{
stmts.visit_mut_children_with(self);
let should_hoist = !is_sorted_by_key(stmts.iter(), |stmt| match stmt.as_stmt() {
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 => {
@ -68,11 +70,20 @@ impl Hoister<'_> {
_ => 3,
},
None => 3,
}) || (self.config.hoist_vars
&& stmts.windows(2).any(|stmts| {
}),
PartialOrd::partial_cmp,
) || (self.config.hoist_vars
&& if len >= *crate::LIGHT_TASK_PARALLELS {
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 {
return;

View File

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

View File

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

View File

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

View File

@ -504,7 +504,7 @@ where
let mut args = vec![];
if cons.args.as_ref().map(|v| v.len()).unwrap_or(0) == 1 {
args.push(ExprOrSpread {
args = vec![ExprOrSpread {
spread: None,
expr: Box::new(Expr::Cond(CondExpr {
span: DUMMY_SP,
@ -512,7 +512,7 @@ where
cons: cons.args.as_mut().unwrap()[0].expr.take(),
alt: alt.args.as_mut().unwrap()[0].expr.take(),
})),
});
}];
}
report_change!(
@ -715,10 +715,8 @@ where
return;
}
//
let mut new_stmts = vec![];
for stmt in stmts.take() {
let mut new_stmts = Vec::with_capacity(stmts.len() * 2);
stmts.take().into_iter().for_each(|stmt| {
match stmt.try_into_stmt() {
Ok(stmt) => match stmt {
Stmt::If(IfStmt {
@ -753,7 +751,7 @@ where
},
Err(stmt) => new_stmts.push(stmt),
}
}
});
self.changed = true;
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 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`.
impl<M> Optimizer<'_, M>
@ -399,12 +399,12 @@ where
match (ln.classify(), rn.classify()) {
(FpCategory::Zero, FpCategory::Zero) => {
// If a variable named `NaN` is in scope, don't convert e into NaN.
if self
.data
.vars
.iter()
.any(|(name, v)| v.declared && name.0 == js_word!("NaN"))
{
let data = &self.data.vars;
if maybe_par!(
data.iter()
.any(|(name, v)| v.declared && name.0 == js_word!("NaN")),
*crate::LIGHT_TASK_PARALLELS
) {
return;
}

View File

@ -4,7 +4,9 @@ use swc_ecma_utils::{undefined, StmtExt, StmtLike};
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
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
/// `if_return` is false.

View File

@ -13,9 +13,10 @@ use super::{
util::{MultiReplacer, MultiReplacerMode},
Optimizer,
};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{
compress::optimize::{util::Remapper, Ctx},
debug::dump,
mode::Mode,
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 crate::{
compress::optimize::util::{class_has_side_effect, is_valid_for_lhs},
debug::dump,
mode::Mode,
util::{idents_captured_by, idents_used_by, idents_used_by_ignoring_nested},
};
@ -32,7 +31,7 @@ where
trace_op!(
"inline: store_var_for_inlining({}, should_preserve = {:?})",
dump(&var.name, false),
crate::debug::dump(&var.name, false),
should_preserve
);
@ -66,7 +65,7 @@ where
if should_preserve && usage.var_kind != Some(VarDeclKind::Const) {
log_abort!(
"inline: [x] Preserving non-const variable `{}` because it's top-level",
dump(&var.name, false)
crate::debug::dump(&var.name, false)
);
return;
}
@ -606,6 +605,7 @@ where
}
self.changed = true;
#[cfg(feature = "debug")]
match &decl {
Decl::Class(c) => {
report_change!(
@ -711,7 +711,7 @@ where
*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,
};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
#[cfg(feature = "debug")]
use tracing::{debug, span, Level};
use Value::Known;
@ -22,11 +23,14 @@ use self::{
util::{MultiReplacer, MultiReplacerMode},
};
use super::util::{drop_invalid_stmts, is_fine_for_if_cons};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{
analyzer::{ProgramData, UsageAnalyzer},
compress::util::is_pure_undefined,
debug::{dump, AssertValid},
debug::AssertValid,
marks::Marks,
maybe_par,
mode::Mode,
option::CompressOptions,
util::{
@ -224,6 +228,7 @@ struct Optimizer<'a, M> {
mode: &'a M,
#[allow(unused)]
debug_infinite_loop: bool,
functions: FxHashMap<Id, FnMetadata>,
@ -351,10 +356,12 @@ where
// Don't set in_strict for directive itself.
stmt.visit_mut_with(self);
} 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);
}
@ -379,43 +386,50 @@ where
self.ctx.in_asm |= use_asm;
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.reorder_stmts(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.merge_sequences_in_stmts(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.merge_similar_ifs(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.make_sequences(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.drop_else_token(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.break_assignments_in_seqs(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
@ -978,6 +992,7 @@ where
span,
left,
right,
#[cfg(feature = "debug")]
op,
..
}) => {
@ -1194,11 +1209,11 @@ where
}
if let Stmt::Block(block) = &mut bs.stmts[0] {
if block
.stmts
.iter()
.all(|stmt| !matches!(stmt, Stmt::Decl(..)))
{
let stmts = &block.stmts;
if maybe_par!(
stmts.iter().all(|stmt| !matches!(stmt, Stmt::Decl(..))),
*crate::LIGHT_TASK_PARALLELS
) {
report_change!("optimizer: Removing nested block");
self.changed = true;
bs.stmts = block.stmts.take();
@ -1540,7 +1555,12 @@ where
#[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
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.store_typeofs(decl);
@ -1682,7 +1702,7 @@ where
}
#[cfg(feature = "trace-ast")]
debug!("Output: {}", dump(e, true));
tracing::debug!("Output: {}", dump(e, true));
}
#[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);
}
}
@ -1903,7 +1924,8 @@ where
if let Some(body) = n.body.as_mut() {
// Bypass block scope handler.
body.visit_mut_children_with(optimizer);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
body.visit_with(&mut AssertValid);
}
}
@ -1925,7 +1947,8 @@ where
drop_invalid_stmts(&mut body.stmts);
}
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
n.visit_with(&mut AssertValid);
}
}
@ -2107,7 +2130,8 @@ where
}
}
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
n.visit_with(&mut AssertValid);
}
}
@ -2189,7 +2213,8 @@ where
let old_prepend = self.prepend_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);
if text.lines().count() < 10 {
@ -2228,7 +2253,8 @@ where
_ => {}
}
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid);
}
}
@ -2274,7 +2300,8 @@ where
.collect(),
});
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid);
}
}
@ -2284,7 +2311,8 @@ where
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);
if text.lines().count() < 10 {
@ -2343,7 +2371,8 @@ where
debug_assert_eq!(self.prepend_stmts.len(), len);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid);
}
@ -2369,7 +2398,8 @@ where
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);
if text.lines().count() < 10 {
@ -2379,7 +2409,8 @@ where
debug_assert_eq!(self.prepend_stmts.len(), len);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
s.visit_with(&mut AssertValid);
}
@ -2388,7 +2419,8 @@ where
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
// Skip if `use asm` exists.
if stmts.iter().any(|stmt| match stmt.as_stmt() {
if maybe_par!(
stmts.iter().any(|stmt| match stmt.as_stmt() {
Some(Stmt::Expr(stmt)) => match &*stmt.expr {
Expr::Lit(Lit::Str(Str { raw, .. })) => {
matches!(raw, Some(value) if value == "\"use asm\"" || value == "'use asm'")
@ -2396,7 +2428,9 @@ where
_ => false,
},
_ => false,
}) {
}),
*crate::LIGHT_TASK_PARALLELS
) {
return;
}
@ -2420,7 +2454,8 @@ where
}
}
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,6 @@ pub(super) struct Ctx {
impl<'b> Pure<'b> {
/// RAII guard to change context temporarically
#[inline]
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b> {
let orig_ctx = self.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_ecma_ast::*;
use swc_ecma_utils::{extract_var_ids, ExprExt, StmtExt, StmtLike, Value};
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
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`.
impl Pure<'_> {
@ -229,15 +230,17 @@ impl Pure<'_> {
report_change!("Dropping statements after a control keyword");
let mut new_stmts = Vec::with_capacity(stmts.len());
let mut decls = vec![];
let mut hoisted_fns = vec![];
let stmts_len = stmts.len();
// Hoist function and `var` declarations above return.
stmts
.iter_mut()
.skip(idx + 1)
.for_each(|stmt| match stmt.take().try_into_stmt() {
let (decls, hoisted_fns, mut new_stmts) = stmts.iter_mut().skip(idx + 1).fold(
(
Vec::with_capacity(stmts_len),
Vec::<T>::with_capacity(stmts_len),
Vec::with_capacity(stmts_len),
),
|(mut decls, mut hoisted_fns, mut new_stmts), stmt| {
match stmt.take().try_into_stmt() {
Ok(Stmt::Decl(Decl::Fn(f))) => {
hoisted_fns.push(Stmt::Decl(Decl::Fn(f)).into());
}
@ -251,7 +254,10 @@ impl Pure<'_> {
decls.extend(ids);
}
Err(item) => new_stmts.push(item),
});
};
(decls, hoisted_fns, new_stmts)
},
);
if !decls.is_empty() {
new_stmts.push(
@ -380,34 +386,58 @@ impl Pure<'_> {
T: StmtLike,
{
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!(
stmts
.iter()
.all(|stmt| !matches!(stmt.as_stmt(), Some(Stmt::Block(b)) if is_ok(b)))
{
.all(|stmt| !matches!(stmt.as_stmt(), Some(Stmt::Block(b)) if is_ok(b))),
*crate::LIGHT_TASK_PARALLELS
) {
return;
}
self.changed = true;
report_change!("Dropping useless block");
let mut new = vec![];
for stmt in stmts.take() {
match stmt.try_into_stmt() {
let old_stmts = stmts.take();
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);
}
}
}
Err(v) => new.push(v),
});
new
};
*stmts = new;
}
@ -437,20 +467,24 @@ impl Pure<'_> {
return;
}
if !stmts.iter().any(|stmt| match stmt.as_stmt() {
if !maybe_par!(
stmts.iter().any(|stmt| match stmt.as_stmt() {
Some(Stmt::If(s)) => s.test.cast_to_bool(&self.expr_ctx).1.is_known(),
_ => false,
}) {
}),
*crate::LIGHT_TASK_PARALLELS
) {
return;
}
self.changed = true;
report_change!("dead_code: Removing dead codes");
let mut new = vec![];
for stmt in stmts.take() {
match stmt.try_into_stmt() {
let mut new = Vec::with_capacity(stmts.len());
stmts
.take()
.into_iter()
.for_each(|stmt| match stmt.try_into_stmt() {
Ok(stmt) => match stmt {
Stmt::If(mut s) => {
if let Value::Known(v) = s.test.cast_to_bool(&self.expr_ctx).1 {
@ -462,14 +496,16 @@ impl Pure<'_> {
if v {
if let Some(alt) = s.alt.take() {
var_ids.extend(alt.extract_var_ids().into_iter().map(|name| {
VarDeclarator {
var_ids = alt
.extract_var_ids()
.into_iter()
.map(|name| VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(name.into()),
init: None,
definite: Default::default(),
}
}));
})
.collect();
}
if !var_ids.is_empty() {
new.push(T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
@ -481,14 +517,17 @@ impl Pure<'_> {
}
new.push(T::from_stmt(*s.cons.take()));
} else {
var_ids.extend(s.cons.extract_var_ids().into_iter().map(|name| {
VarDeclarator {
var_ids = s
.cons
.extract_var_ids()
.into_iter()
.map(|name| VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(name.into()),
init: None,
definite: Default::default(),
}
}));
})
.collect();
if !var_ids.is_empty() {
new.push(T::from_stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
@ -502,14 +541,13 @@ impl Pure<'_> {
}
}
} else {
new.push(T::from_stmt(Stmt::If(s)))
new.push(T::from_stmt(Stmt::If(s)));
}
}
_ => new.push(T::from_stmt(stmt)),
},
Err(stmt) => new.push(stmt),
}
}
});
*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()`"
);
let mut new = vec![];
let mut fn_decls = vec![];
let mut new = Vec::with_capacity(stmts.len());
let mut fn_decls = Vec::with_capacity(stmts.len());
new.extend(stmts.drain(..pos_of_if));
let cons = stmts
.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) {
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_utils::{undefined, ExprCtx};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
#[cfg(feature = "debug")]
use tracing::{debug, span, Level};
use self::{ctx::Ctx, misc::DropOpts};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{
analyzer::ProgramData,
debug::{dump, AssertValid},
marks::Marks,
option::CompressOptions,
util::ModuleItemExt,
MAX_PAR_DEPTH,
analyzer::ProgramData, debug::AssertValid, marks::Marks, maybe_par, option::CompressOptions,
util::ModuleItemExt, MAX_PAR_DEPTH,
};
mod arrows;
@ -40,6 +39,8 @@ pub(crate) struct PureOptimizerConfig {
pub enable_join_vars: bool,
pub force_str_for_tpl: bool,
#[cfg(feature = "debug")]
pub debug_infinite_loop: bool,
}
@ -101,32 +102,37 @@ impl Pure<'_> {
{
self.remove_dead_branch(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.drop_unreachable_stmts(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.drop_useless_blocks(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
self.collapse_vars_without_init(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
if self.config.enable_join_vars {
self.join_vars(stmts);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
stmts.visit_with(&mut AssertValid);
}
}
@ -174,8 +180,10 @@ impl Pure<'_> {
self.changed |= v.changed;
}
} else {
let mut changed = false;
if nodes.len() >= *crate::HEAVY_TASK_PARALLELS {
GLOBALS.with(|globals| {
let changed = nodes
changed = nodes
.par_iter_mut()
.map(|node| {
GLOBALS.set(globals, || {
@ -194,9 +202,28 @@ impl Pure<'_> {
})
})
.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);
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) {
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);
@ -545,7 +573,8 @@ impl VisitMut for Pure<'_> {
self.optimize_arrow_method_prop(p);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
p.visit_with(&mut AssertValid);
}
}
@ -570,7 +599,11 @@ impl VisitMut for Pure<'_> {
fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
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![];
for e in e.exprs.take() {
@ -620,13 +653,15 @@ impl VisitMut for Pure<'_> {
e.exprs.retain(|e| !e.is_invalid());
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
e.visit_with(&mut AssertValid);
}
}
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);
if text.lines().count() < 10 {
@ -649,7 +684,8 @@ impl VisitMut for Pure<'_> {
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);
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);
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);
}
}
@ -717,7 +755,8 @@ impl VisitMut for Pure<'_> {
items.retain(|s| !matches!(s, Stmt::Empty(..)));
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
items.visit_with(&mut AssertValid);
}
}

View File

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

View File

@ -3,15 +3,19 @@ use std::f64;
use swc_atoms::js_word;
use swc_common::{util::take::Take, DUMMY_SP};
use swc_ecma_ast::*;
#[cfg(feature = "debug")]
use swc_ecma_transforms_base::fixer::fixer;
use swc_ecma_utils::{ExprCtx, ExprExt, IdentUsageFinder, Value};
#[cfg(feature = "debug")]
use swc_ecma_visit::{as_folder, FoldWith};
use swc_ecma_visit::{
as_folder, noop_visit_mut_type, noop_visit_type, FoldWith, Visit, VisitMut, VisitMutWith,
VisitWith,
noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
};
use unicode_id::UnicodeID;
use crate::{debug::dump, util::ModuleItemExt};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::util::ModuleItemExt;
#[cfg(test)]
mod tests;
@ -40,6 +44,7 @@ fn negate_inner(
in_bool_ctx: bool,
is_ret_val_ignored: bool,
) -> bool {
#[cfg(feature = "debug")]
let start_str = dump(&*e, false);
match e {
@ -225,7 +230,8 @@ pub(crate) fn is_ok_to_negate_rhs(expr_ctx: &ExprCtx, rhs: &Expr) -> bool {
return true;
}
if cfg!(feature = "debug") {
#[cfg(feature = "debug")]
{
tracing::warn!(
"unimplemented: is_ok_to_negate_rhs: `{}`",
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.
#[cfg_attr(feature = "debug", tracing::instrument(skip(e)))]
#[allow(clippy::let_and_return)]
pub(crate) fn negate_cost(
expr_ctx: &ExprCtx,
e: &Expr,
@ -359,7 +366,8 @@ pub(crate) fn negate_cost(
})();
// Print more info while testing negate_cost
if cfg!(test) {
#[cfg(test)]
{
trace_op!(
"negation cost of `{}` = {}",
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>,
{
if !force {
if !cfg!(feature = "debug") {
#[cfg(not(feature = "debug"))]
{
return String::new();
}
}
@ -71,7 +72,8 @@ where
/// If the cargo feature `debug` is disabled or the environment variable
/// `SWC_RUN` is not `1`, this function is noop.
pub(crate) fn invoke(module: &Module) {
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
module.visit_with(&mut AssertValid);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ use swc_atoms::JsWord;
use swc_common::collections::AHashMap;
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
#[cfg(feature = "debug")]
use tracing::trace;
use self::scope::Scope;
@ -47,17 +48,18 @@ impl Analyzer {
}
fn add_decl(&mut self, id: Id) {
if cfg!(feature = "debug") {
#[cfg(feature = "debug")]
{
trace!("add_decl({:?})", id);
}
self.scope.add_decl(&id);
}
fn add_usage(&mut self, id: Id) {
if cfg!(feature = "debug") {
#[cfg(feature = "debug")]
{
trace!("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_common::{collections::AHashMap, util::take::Take};
use swc_ecma_ast::Id;
#[cfg(debug_assertions)]
use tracing::debug;
use crate::util::base54;
use crate::{maybe_par, util::base54};
#[derive(Debug, Default)]
pub(crate) struct Scope {
@ -128,7 +129,6 @@ impl Scope {
}
}
#[inline(never)]
fn rename_one_scope(
&self,
to: &mut AHashMap<Id, JsWord>,
@ -158,7 +158,8 @@ impl Scope {
}
if self.can_rename(&id, &sym, cloned_reverse) {
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
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 {
// We can optimize this
// 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 {
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_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
use crate::maybe_par;
///
/// - merge exports
pub(crate) fn merge_exports() -> impl VisitMut {
@ -17,9 +19,12 @@ impl VisitMut for Merger {
noop_visit_mut_type!();
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
let was_module = stmts
let was_module = maybe_par!(
stmts
.iter()
.any(|s| matches!(s, ModuleItem::ModuleDecl(..)));
.any(|s| matches!(s, ModuleItem::ModuleDecl(..))),
*crate::LIGHT_TASK_PARALLELS
);
// Fast-path
if !was_module {

View File

@ -1,7 +1,7 @@
use swc_ecma_ast::*;
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 {
PostcompressOptimizer {
@ -38,9 +38,12 @@ impl VisitMut for PostcompressOptimizer<'_> {
}
fn visit_mut_module_items(&mut self, nodes: &mut Vec<ModuleItem>) {
self.ctx.is_module = nodes
self.ctx.is_module = maybe_par!(
nodes
.iter()
.any(|s| matches!(s, ModuleItem::ModuleDecl(..)));
.any(|s| matches!(s, ModuleItem::ModuleDecl(..))),
*crate::LIGHT_TASK_PARALLELS
);
self.ctx.is_top_level = true;
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::{
analyzer::{analyze, ProgramData, UsageAnalyzer},
marks::Marks,
maybe_par,
option::CompressOptions,
util::ModuleItemExt,
};
@ -24,7 +25,7 @@ pub(crate) fn precompress_optimizer(options: &CompressOptions, marks: Marks) ->
}
#[derive(Debug)]
struct PrecompressOptimizer<'a> {
pub(crate) struct PrecompressOptimizer<'a> {
options: &'a CompressOptions,
marks: Marks,
@ -50,9 +51,12 @@ impl PrecompressOptimizer<'_> {
}
if self.data.is_none() {
let has_decl = stmts
let has_decl = maybe_par!(
stmts
.iter()
.any(|stmt| matches!(stmt.as_module_decl(), Ok(..) | Err(Stmt::Decl(..))));
.any(|stmt| matches!(stmt.as_module_decl(), Ok(..) | Err(Stmt::Decl(..)))),
*crate::LIGHT_TASK_PARALLELS
);
if has_decl {
let data = Some(analyze(&*stmts, Some(self.marks)));
@ -66,8 +70,7 @@ impl PrecompressOptimizer<'_> {
});
return;
}
for stmt in stmts {
stmts.iter_mut().for_each(|stmt| {
stmt.visit_mut_with(&mut PrecompressOptimizer {
options: self.options,
marks: self.marks,
@ -75,7 +78,7 @@ impl PrecompressOptimizer<'_> {
fn_decl_count: Default::default(),
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>>) {
if exprs.is_empty() {
return;
@ -164,24 +163,20 @@ pub(crate) trait ExprOptExt: Sized {
}
impl ExprOptExt for Box<Expr> {
#[inline]
fn as_expr(&self) -> &Expr {
self
}
#[inline]
fn as_mut(&mut self) -> &mut Expr {
self
}
}
impl ExprOptExt for Expr {
#[inline]
fn as_expr(&self) -> &Expr {
self
}
#[inline]
fn as_mut(&mut self) -> &mut Expr {
self
}
@ -279,14 +274,12 @@ pub(crate) trait IsModuleItem {
}
impl IsModuleItem for Stmt {
#[inline]
fn is_module_item() -> bool {
false
}
}
impl IsModuleItem for ModuleItem {
#[inline]
fn is_module_item() -> bool {
true
}
@ -315,7 +308,6 @@ impl<V> Repeated for Optional<V>
where
V: Repeated,
{
#[inline]
fn changed(&self) -> bool {
if self.enabled {
return false;
@ -324,7 +316,6 @@ where
self.visitor.changed()
}
#[inline]
fn reset(&mut self) {
if self.enabled {
return;
@ -347,7 +338,6 @@ impl<V> Fold for Optional<V>
where
V: Fold,
{
#[inline(always)]
fn fold_module(&mut self, module: Module) -> Module {
if !self.enabled {
return module;
@ -548,9 +538,12 @@ pub(crate) fn can_end_conditionally(s: &Stmt) -> bool {
}
pub fn now() -> Option<Instant> {
if cfg!(target_arch = "wasm32") {
#[cfg(target_arch = "wasm32")]
{
None
} else {
}
#[cfg(not(target_arch = "wasm32"))]
{
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;
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
I: Iterator<Item = T>,
T: Copy,
F: FnMut(&T, &T) -> Option<Ordering>,
{
let mut last = match items.next() {
let last = match items.next() {
Some(e) => e,
None => return true,
};
for curr in items {
items
.try_fold(last, |last, curr| {
if let Some(Ordering::Greater) | None = compare(&last, &curr) {
return false;
return Err(());
}
last = curr;
}
true
}
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)
Ok(curr)
})
.is_ok()
}

View File

@ -4,9 +4,13 @@ use swc_common::Mark;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene};
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.
pub(crate) trait CompileUnit:
@ -19,12 +23,15 @@ pub(crate) trait CompileUnit:
fn is_module() -> bool;
fn dump(&self) -> String {
if !cfg!(feature = "debug") {
return String::new();
}
#[cfg(feature = "debug")]
{
self.force_dump()
}
#[cfg(not(feature = "debug"))]
{
String::new()
}
}
fn force_dump(&self) -> String;
@ -90,7 +97,8 @@ impl CompileUnit for FnExpr {
V: VisitMut,
{
self.visit_mut_with(&mut *visitor);
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
self.visit_with(&mut AssertValid);
}
}

View File

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

View File

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

View File

@ -14,7 +14,7 @@ bench = false
[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]
tikv-jemallocator = { version = "0.4", features = ["disable_initial_exec_tls"] }
[target.'cfg(all(target_os = "linux", not(all(target_env = "musl", target_arch = "aarch64"))))'.dependencies]
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.
#[cfg(not(target_os = "linux"))]
#[global_allocator]
static GLOBAL: mimalloc_rust::GlobalMiMalloc = mimalloc_rust::GlobalMiMalloc;
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(target_arch = "x86_64", target_arch = "aarch64")
not(all(target_os = "linux", target_env = "musl", target_arch = "aarch64")),
not(debug_assertions)
))]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
static ALLOC: mimalloc_rust::GlobalMiMalloc = mimalloc_rust::GlobalMiMalloc;