Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(es/minifier): Fix performance issues & bugs #1988

Closed
wants to merge 84 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
70081fb
WIP: Pass &mut to base54 calculator
kdy1 Jul 31, 2021
5f844a2
Update test refs
kdy1 Jul 31, 2021
52bb824
Add a test for performance
kdy1 Jul 31, 2021
a6992f2
Add a test lib
kdy1 Jul 31, 2021
01fe088
Fix chars
kdy1 Jul 31, 2021
b3685af
Split tests
kdy1 Jul 31, 2021
7c068d6
Seems like #1987 is not about mangler
kdy1 Jul 31, 2021
7a9b0cb
debug
kdy1 Jul 31, 2021
24e0208
antd
kdy1 Jul 31, 2021
4ee15a4
Update test refs
kdy1 Jul 31, 2021
e7614fa
Remove logs on release build
kdy1 Jul 31, 2021
d5bc88f
Change log level
kdy1 Jul 31, 2021
ed67891
Change log level
kdy1 Jul 31, 2021
1ed761c
Bump version
kdy1 Jul 31, 2021
edb5ce0
less chaged
kdy1 Jul 31, 2021
6052e62
Add dbg!
kdy1 Jul 31, 2021
55abe27
Fix expr simplifier
kdy1 Jul 31, 2021
69f03a6
Remove dbg!
kdy1 Jul 31, 2021
c597d64
More
kdy1 Aug 1, 2021
ad8daf9
fixup
kdy1 Aug 1, 2021
5235b21
Remove d3
kdy1 Aug 1, 2021
75582aa
Update tets refs
kdy1 Aug 1, 2021
a5f4cb6
Log level
kdy1 Aug 1, 2021
899949a
Don't parse source map if not required
kdy1 Aug 1, 2021
1a5d0ce
Fix infinite loop
kdy1 Aug 1, 2021
b54d069
Depend on ahash
kdy1 Aug 1, 2021
8f06ee7
Remove unused
kdy1 Aug 1, 2021
4557546
Use ahash for analyzer
kdy1 Aug 1, 2021
7ecc020
Small optimization: Ignore nested ids
kdy1 Aug 1, 2021
8e98e79
Add compress/pure.rs
kdy1 Aug 1, 2021
7853c68
Add pure optimizer
kdy1 Aug 1, 2021
f7b4f6a
Use rayon
kdy1 Aug 1, 2021
7fe47d6
WIP: Pure visitor
kdy1 Aug 1, 2021
8e712e5
Bool optimizer is stateless
kdy1 Aug 1, 2021
41319cc
Add ctx for pure
kdy1 Aug 1, 2021
a5e625d
Move more to pure
kdy1 Aug 1, 2021
6d53260
Some threshold for parallel
kdy1 Aug 1, 2021
ac14d76
More pure comparison
kdy1 Aug 1, 2021
0e1f78b
More paralleization
kdy1 Aug 1, 2021
48d6fb8
Parallelize dropping of `""`
kdy1 Aug 1, 2021
4175024
Mark more operations as pure
kdy1 Aug 1, 2021
78854e1
Mark more operations as pure
kdy1 Aug 1, 2021
b97c1d4
Mark `eval_opt_chain` as pure
kdy1 Aug 1, 2021
0f7a022
Mark `concat_str` as pure
kdy1 Aug 1, 2021
814fbd6
Change order
kdy1 Aug 1, 2021
de6a1dd
Disable inling to see timings
kdy1 Aug 1, 2021
24e9ba8
`optimize_arrow_method_prop` is pure
kdy1 Aug 1, 2021
fd38f5c
Less inlining for investigation
kdy1 Aug 1, 2021
0524355
Use better logic for inlining
kdy1 Aug 1, 2021
5b01bee
Reduce deps
kdy1 Aug 2, 2021
ceecbbd
Remove noinline
kdy1 Aug 2, 2021
d6627ce
Use more memory to prevent rehasing
kdy1 Aug 2, 2021
ff2d4ac
Add a test
kdy1 Aug 2, 2021
ec68718
Move
kdy1 Aug 2, 2021
faf2479
Revert issue testing because it's fast
kdy1 Aug 2, 2021
e20695f
Revert pre-allocation
kdy1 Aug 2, 2021
e93c112
Make expr simplifier configurable
kdy1 Aug 2, 2021
3340092
Don't inject vars using expr simplifier
kdy1 Aug 2, 2021
b70fe4d
Change order
kdy1 Aug 2, 2021
598c05c
Don't clone
kdy1 Aug 2, 2021
705e3b6
wasm
kdy1 Aug 2, 2021
b5d87ac
pure optimizer: More parallelization
kdy1 Aug 2, 2021
a6becdf
Increase max par depth
kdy1 Aug 2, 2021
03cc0e5
precompress: Split scopes if possible
kdy1 Aug 2, 2021
aed7c0a
Reduce antd
kdy1 Aug 2, 2021
fbb5fb9
Fix precompress
kdy1 Aug 2, 2021
78dc4e0
Prepare using `done`.
kdy1 Aug 2, 2021
8a08e84
Small cleanup
kdy1 Aug 2, 2021
a472420
Small opt
kdy1 Aug 2, 2021
a0e9dfe
Split marks
kdy1 Aug 2, 2021
02bbe8f
Add Pure.modified_node
kdy1 Aug 2, 2021
c227793
pure optimizer: Track ast modifications
kdy1 Aug 2, 2021
87cbf38
Store mark in pure optimizer
kdy1 Aug 2, 2021
c7dff2b
Use swc globals
kdy1 Aug 2, 2021
7340c2f
Add respan
kdy1 Aug 2, 2021
da38d70
Track from pure optimizer
kdy1 Aug 2, 2021
b81e4b4
optimizer: Track if node is finished
kdy1 Aug 2, 2021
4b669ca
Fix respan impl for ident
kdy1 Aug 2, 2021
31d0adf
Remove reduced antd
kdy1 Aug 2, 2021
12eafd1
Fix tracking of `done`
kdy1 Aug 2, 2021
b4131bc
Add an assertion
kdy1 Aug 2, 2021
314cb0b
Add more assertions & remove mark correctly
kdy1 Aug 2, 2021
70a4978
Add esbuild-three
kdy1 Aug 2, 2021
a243f8a
Better check
kdy1 Aug 2, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ecmascript/minifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ version = "0.16.1"
debug = []

[dependencies]
ahash = "0.7.4"
fxhash = "0.2.1"
indexmap = "1.7.0"
log = "0.4"
once_cell = "1.5.2"
pretty_assertions = {version = "0.6.1", optional = true}
rayon = "1.5.1"
regex = "1.5.3"
retain_mut = "0.1.2"
serde = {version = "1.0.118", features = ["derive"]}
Expand Down
10 changes: 8 additions & 2 deletions ecmascript/minifier/scripts/project-ref.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ run() {
mkdir -p ./tests/projects/refs

run angular-1.2.5.js
run antd-4.16.9.js
run backbone-1.1.0.js
run echarts-5.1.2.js
run jquery-1.9.1.js
run jquery.mobile-1.4.2.js
run lodash-4.17.71.js
run moment-2.29.1.js
run mootools-1.4.5.js
run underscore-1.5.2.js
run yui-3.12.0.js
run react-17.0.2.js
run react-dom-17.0.2.js
run three-0.124.0.js
run underscore-1.5.2.js
run vue-2.6.12.js
run yui-3.12.0.js

prettier --write tests/projects/refs/
yarn run eslint --fix ./tests/projects/refs/
87 changes: 54 additions & 33 deletions ecmascript/minifier/src/analyzer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
use self::ctx::Ctx;
use crate::util::can_end_conditionally;
use crate::util::idents_used_by;
use crate::util::now;
use fxhash::FxHashMap;
use fxhash::FxHashSet;
use crate::util::FastHashMap;
use crate::util::FastHashSet;
use std::collections::hash_map::Entry;
use std::time::Instant;
use swc_atoms::JsWord;
use swc_common::SyntaxContext;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_utils::find_ids;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::Id;
use swc_ecma_visit::noop_visit_type;
Expand Down Expand Up @@ -75,7 +73,7 @@ pub(crate) struct VarUsageInfo {
pub mutated: bool,

pub has_property_access: bool,
pub accessed_props: FxHashSet<JsWord>,
pub accessed_props: FastHashSet<JsWord>,

pub exported: bool,
/// True if used **above** the declaration. (Not eval order).
Expand All @@ -102,7 +100,7 @@ pub(crate) struct VarUsageInfo {
pub no_side_effect_for_member_access: bool,

/// In `c = b`, `b` inffects `c`.
infects: Vec<Id>,
infects: FastHashSet<Id>,
}

impl VarUsageInfo {
Expand All @@ -123,7 +121,7 @@ pub(crate) struct ScopeData {
pub has_eval_call: bool,

/// Variables declared in the scope.
pub declared_symbols: FxHashMap<JsWord, FxHashSet<SyntaxContext>>,
pub declared_symbols: FastHashMap<JsWord, FastHashSet<SyntaxContext>>,
}

impl ScopeData {
Expand All @@ -142,14 +140,11 @@ impl ScopeData {
/// Analyzed info of a whole program we are working on.
#[derive(Debug, Default)]
pub(crate) struct ProgramData {
pub vars: FxHashMap<Id, VarUsageInfo>,
pub vars: FastHashMap<Id, VarUsageInfo>,

pub top: ScopeData,

pub scopes: FxHashMap<SyntaxContext, ScopeData>,

/// { dependant: [dependendcies] }
var_deps: FxHashMap<Id, FxHashSet<Id>>,
pub scopes: FastHashMap<SyntaxContext, ScopeData>,
}

impl ProgramData {
Expand Down Expand Up @@ -223,12 +218,6 @@ impl ProgramData {
}
}
}

/// TODO: Make this recursive
#[allow(unused)]
pub fn deps(&self, id: &Id) -> FxHashSet<Id> {
self.var_deps.get(id).cloned().unwrap_or_default()
}
}

/// This assumes there are no two variable with same name and same span hygiene.
Expand Down Expand Up @@ -262,7 +251,7 @@ impl UsageAnalyzer {
ret
}

fn report(&mut self, i: Id, is_modify: bool, dejavu: &mut FxHashSet<Id>) {
fn report(&mut self, i: Id, is_modify: bool, dejavu: &mut FastHashSet<Id>) {
// log::trace!("report({}{:?})", i.0, i.1);

let is_first = dejavu.is_empty();
Expand Down Expand Up @@ -805,22 +794,22 @@ impl Visit for UsageAnalyzer {
for decl in &n.decls {
match (&decl.name, decl.init.as_deref()) {
(Pat::Ident(var), Some(init)) => {
let used_idents = idents_used_by(init);
let used_idents = get_deps(init);

for id in used_idents {
self.data
.vars
.entry(id.clone())
.or_default()
.infects
.push(var.to_id());
.insert(var.to_id());

self.data
.vars
.entry(var.to_id())
.or_default()
.infects
.push(id);
.insert(id);
}
}
_ => {}
Expand All @@ -840,17 +829,6 @@ impl Visit for UsageAnalyzer {
};
e.name.visit_with(e, &mut *self.with_ctx(ctx));

let declared_names: Vec<Id> = find_ids(&e.name);
let used_names = idents_used_by(&e.init);

for name in declared_names {
self.data
.var_deps
.entry(name)
.or_default()
.extend(used_names.clone());
}

e.init.visit_with(e, self);
}

Expand All @@ -868,3 +846,46 @@ impl Visit for UsageAnalyzer {
n.visit_children_with(self);
}
}

fn get_deps<N>(n: &N) -> Vec<Id>
where
N: VisitWith<DepCalculator>,
{
let mut v = DepCalculator::default();
n.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
v.deps
}

#[derive(Default)]
struct DepCalculator {
deps: Vec<Id>,
}

impl DepCalculator {}

impl Visit for DepCalculator {
noop_visit_type!();

fn visit_block_stmt_or_expr(&mut self, _: &BlockStmtOrExpr, _: &dyn Node) {}

fn visit_constructor(&mut self, _s: &Constructor, _: &dyn Node) {}

fn visit_function(&mut self, _: &Function, _: &dyn Node) {}

fn visit_ident(&mut self, n: &Ident, _: &dyn Node) {
self.deps.push(n.to_id());
}

fn visit_member_expr(&mut self, n: &MemberExpr, _: &dyn Node) {
n.obj.visit_with(n, self);
}

fn visit_prop_name(&mut self, n: &PropName, _: &dyn Node) {
match n {
PropName::Computed(..) => {
n.visit_children_with(self);
}
_ => {}
}
}
}
41 changes: 38 additions & 3 deletions ecmascript/minifier/src/compress/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use self::optimize::optimizer;
use crate::analyzer::analyze;
use crate::analyzer::ProgramData;
use crate::compress::hoist_decls::decl_hoister;
use crate::compress::pure::pure_optimizer;
use crate::debug::dump;
use crate::debug::invoke;
use crate::marks::Marks;
Expand All @@ -23,11 +24,13 @@ use swc_common::pass::CompilerPass;
use swc_common::pass::Repeat;
use swc_common::pass::Repeated;
use swc_common::sync::Lrc;
use swc_common::Globals;
use swc_common::{chain, SourceMap};
use swc_ecma_ast::*;
use swc_ecma_transforms::fixer;
use swc_ecma_transforms::optimization::simplify::dead_branch_remover;
use swc_ecma_transforms::optimization::simplify::expr_simplifier;
use swc_ecma_transforms::optimization::simplify::expr_simplifier_with_config;
use swc_ecma_transforms::pass::JsPass;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::StmtLike;
Expand All @@ -40,9 +43,12 @@ use swc_ecma_visit::VisitMutWith;
mod drop_console;
mod hoist_decls;
mod optimize;
mod pure;
mod util;

pub(crate) fn compressor<'a>(
cm: Lrc<SourceMap>,
swc_globals: &'a Globals,
marks: Marks,
options: &'a CompressOptions,
comments: Option<&'a dyn Comments>,
Expand All @@ -53,6 +59,7 @@ pub(crate) fn compressor<'a>(
};
let compressor = Compressor {
cm,
swc_globals,
marks,
options,
comments,
Expand All @@ -70,6 +77,7 @@ pub(crate) fn compressor<'a>(

struct Compressor<'a> {
cm: Lrc<SourceMap>,
swc_globals: &'a Globals,
marks: Marks,
options: &'a CompressOptions,
comments: Option<&'a dyn Comments>,
Expand Down Expand Up @@ -134,8 +142,10 @@ impl VisitMut for Compressor<'_> {
self.data = Some(analyze(&*n));

if self.options.passes != 0 && self.options.passes + 1 <= self.pass {
let done = dump(&*n);
log::debug!("===== Done =====\n{}", done);
if cfg!(feature = "debug") {
let done = dump(&*n);
log::debug!("===== Done =====\n{}", done);
}
return;
}

Expand All @@ -144,6 +154,31 @@ impl VisitMut for Compressor<'_> {
panic!("Infinite loop detected (current pass = {})", self.pass)
}

{
// Pure optimizer uses all cpu cores, so we don't have to branch this.
log::info!("compress: Running pure optimizer (pass = {})", self.pass);

let start_time = now();
// TODO: reset_opt_flags
//
// This is swc version of `node.optimize(this);`.
let mut visitor = pure_optimizer(&self.swc_globals, self.marks, self.options);
n.visit_mut_with(&mut visitor);
self.changed |= visitor.changed();

if let Some(start_time) = start_time {
let end_time = Instant::now();

log::info!(
"compress: Pure optimizer took {:?} (pass = {})",
end_time - start_time,
self.pass
);
}
// let done = dump(&*n);
// log::debug!("===== Result =====\n{}", done);
}

let start = if cfg!(feature = "debug") {
let start = dump(&n.clone().fold_with(&mut fixer(None)));
log::debug!("===== Start =====\n{}", start);
Expand All @@ -160,7 +195,7 @@ impl VisitMut for Compressor<'_> {

let start_time = now();

let mut visitor = expr_simplifier();
let mut visitor = expr_simplifier_with_config(false);
n.visit_mut_with(&mut visitor);
self.changed |= visitor.changed();
if visitor.changed() {
Expand Down
1 change: 1 addition & 0 deletions ecmascript/minifier/src/compress/optimize/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use swc_ecma_visit::VisitMutWith;
impl Optimizer<'_> {
///
/// - `arguments['foo']` => `arguments.foo`

pub(super) fn optimize_str_access_to_arguments(&mut self, e: &mut Expr) {
if !self.options.arguments {
return;
Expand Down
Loading