diff --git a/Cargo.lock b/Cargo.lock index 24aa5e127dd0..9502ee34fef5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5518,6 +5518,7 @@ dependencies = [ name = "swc_ecma_transforms_optimization" version = "7.1.1" dependencies = [ + "ahash", "dashmap 5.5.3", "indexmap 2.7.1", "once_cell", @@ -5540,7 +5541,9 @@ dependencies = [ "swc_ecma_utils", "swc_ecma_visit", "swc_fast_graph", + "swc_parallel", "testing", + "thread_local", "tracing", ] @@ -6413,9 +6416,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 6e7ba6fe4917..5e2c3c092a3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,7 @@ resolver = "2" tempfile = "3.6.0" termcolor = "1.0" thiserror = "1.0.30" + thread_local = "1.1.8" tokio = { version = "1", default-features = false } toml = "0.8.2" tracing = "0.1.40" diff --git a/crates/swc_ecma_transforms_optimization/Cargo.toml b/crates/swc_ecma_transforms_optimization/Cargo.toml index 18c65ac5dbc3..7cf9e3f87229 100644 --- a/crates/swc_ecma_transforms_optimization/Cargo.toml +++ b/crates/swc_ecma_transforms_optimization/Cargo.toml @@ -22,24 +22,29 @@ concurrent = [ debug = [] [dependencies] -dashmap = { workspace = true } -indexmap = { workspace = true } -once_cell = { workspace = true } -petgraph = { workspace = true } -rayon = { workspace = true, optional = true } -rustc-hash = { workspace = true } -serde_json = { workspace = true } -tracing = { workspace = true } +ahash = { workspace = true } +dashmap = { workspace = true } +indexmap = { workspace = true } +once_cell = { workspace = true } +petgraph = { workspace = true } +rayon = { workspace = true, optional = true } +rustc-hash = { workspace = true } +serde_json = { workspace = true } +thread_local = { workspace = true } +tracing = { workspace = true } -swc_atoms = { version = "3.0.3", path = "../swc_atoms" } -swc_common = { version = "5.0.0", path = "../swc_common" } -swc_ecma_ast = { version = "5.0.3", path = "../swc_ecma_ast" } -swc_ecma_parser = { version = "6.0.2", path = "../swc_ecma_parser" } -swc_ecma_transforms_base = { version = "7.1.1", path = "../swc_ecma_transforms_base" } +swc_atoms = { version = "3.0.3", path = "../swc_atoms" } +swc_common = { version = "5.0.0", path = "../swc_common" } +swc_ecma_ast = { version = "5.0.3", path = "../swc_ecma_ast" } +swc_ecma_parser = { version = "6.0.2", path = "../swc_ecma_parser" } +swc_ecma_transforms_base = { version = "7.1.1", path = "../swc_ecma_transforms_base" } swc_ecma_transforms_macros = { version = "1.0.0", path = "../swc_ecma_transforms_macros" } -swc_ecma_utils = { version = "7.0.4", path = "../swc_ecma_utils" } -swc_ecma_visit = { version = "5.0.0", path = "../swc_ecma_visit" } -swc_fast_graph = { version = "6.0.0", path = "../swc_fast_graph" } +swc_ecma_utils = { version = "7.0.4", path = "../swc_ecma_utils" } +swc_ecma_visit = { version = "5.0.0", path = "../swc_ecma_visit" } +swc_fast_graph = { version = "6.0.0", path = "../swc_fast_graph" } +swc_parallel = { version = "1.0.0", path = "../swc_parallel", default-features = false, features = [ + "indexmap", +] } [dev-dependencies] swc_ecma_transforms_compat = { version = "8.0.0", path = "../swc_ecma_transforms_compat" } diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs index f88080abc402..b76895ad18fd 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs @@ -1,11 +1,12 @@ -use std::{borrow::Cow, sync::Arc}; +use std::{borrow::Cow, cell::RefCell, mem::take, sync::Arc}; -use indexmap::IndexSet; +use ahash::RandomState; +use indexmap::{IndexMap, IndexSet}; use petgraph::{algo::tarjan_scc, Direction::Incoming}; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use swc_atoms::{atom, JsWord}; use swc_common::{ - collections::{AHashMap, AHashSet, ARandomState}, + collections::AHashSet, pass::{CompilerPass, Repeated}, util::take::Take, Mark, SyntaxContext, DUMMY_SP, @@ -13,7 +14,7 @@ use swc_common::{ use swc_ecma_ast::*; use swc_ecma_transforms_base::{ helpers::{Helpers, HELPERS}, - perf::{cpu_count, ParVisitMut, Parallel}, + perf::{cpu_count, ParVisit, ParVisitMut, Parallel}, }; use swc_ecma_utils::{ collect_decls, find_pat_ids, ExprCtx, ExprExt, IsEmpty, ModuleItemLike, StmtLike, Value::Known, @@ -22,6 +23,8 @@ use swc_ecma_visit::{ noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith, }; use swc_fast_graph::digraph::FastDiGraphMap; +use swc_parallel::merge::{merge_in_parallel, Merge}; +use thread_local::ThreadLocal; use tracing::{debug, span, Level}; use crate::debug_assert_valid; @@ -105,57 +108,67 @@ impl CompilerPass for TreeShaker { #[derive(Default)] struct Data { - used_names: AHashMap, + used_names: FxHashMap, - /// Variable usage graph - /// - /// We use `u32` because [FastDiGraphMap] stores types as `(N, 1 bit)` so if - /// we use u32 it fits into the cache line of cpu. - graph: FastDiGraphMap, + edges: Edges, /// Entrypoints. - entries: FxHashSet, - - graph_ix: IndexSet, + entry_ids: FxHashSet, } -impl Data { - fn node(&mut self, id: &Id) -> u32 { - self.graph_ix.get_index_of(id).unwrap_or_else(|| { - let ix = self.graph_ix.len(); - self.graph_ix.insert_full(id.clone()); - ix - }) as _ - } +#[derive(Default)] +struct Edges(IndexMap<(Id, Id), VarInfo, RandomState>); +impl Data { /// Add an edge to dependency graph - fn add_dep_edge(&mut self, from: &Id, to: &Id, assign: bool) { - let from = self.node(from); - let to = self.node(to); - - match self.graph.edge_weight_mut(from, to) { - Some(info) => { + fn add_dep_edge(&mut self, from: Id, to: Id, assign: bool) { + match self.edges.0.entry((from, to)) { + indexmap::map::Entry::Occupied(mut info) => { if assign { - info.assign += 1; + info.get_mut().assign += 1; } else { - info.usage += 1; + info.get_mut().usage += 1; } } - None => { - self.graph.add_edge( - from, - to, - VarInfo { - usage: u32::from(!assign), - assign: u32::from(assign), - }, - ); + indexmap::map::Entry::Vacant(info) => { + info.insert(VarInfo { + usage: u32::from(!assign), + assign: u32::from(assign), + }); } - }; + } } /// Traverse the graph and subtract usages from `used_names`. + #[allow(unused)] fn subtract_cycles(&mut self) { - let cycles = tarjan_scc(&self.graph); + let edges = take(&mut self.edges); + + let mut graph = FastDiGraphMap::with_capacity(self.used_names.len(), edges.0.len()); + let mut graph_ix: IndexMap<(JsWord, SyntaxContext), u32, RandomState> = + IndexMap::with_capacity_and_hasher(self.used_names.len(), Default::default()); + + let mut get_node = |id: Id| -> u32 { + let len = graph_ix.len(); + + let id = *graph_ix.entry(id).or_insert(len as u32); + + id as _ + }; + + let entries = self + .entry_ids + .iter() + .map(|id| get_node(id.clone())) + .collect::>(); + + for ((src, dst), info) in edges.0 { + let src = get_node(src); + let dst = get_node(dst); + + graph.add_edge(src, dst, info); + } + + let cycles = tarjan_scc(&graph); 'c: for cycle in cycles { if cycle.len() == 1 { @@ -166,11 +179,11 @@ impl Data { // of cycle. for &node in &cycle { // It's referenced by an outer node. - if self.entries.contains(&node) { + if entries.contains(&node) { continue 'c; } - if self.graph.neighbors_directed(node, Incoming).any(|node| { + if graph.neighbors_directed(node, Incoming).any(|node| { // Node in cycle does not matter !cycle.contains(&node) }) { @@ -184,13 +197,13 @@ impl Data { continue; } - let id = self.graph_ix.get_index(j as _); + let id = graph_ix.get_index(j as _); let id = match id { - Some(id) => id, + Some(id) => id.0, None => continue, }; - if let Some(w) = self.graph.edge_weight(i, j) { + if let Some(w) = graph.edge_weight(i, j) { let e = self.used_names.entry(id.clone()).or_default(); e.usage -= w.usage; e.assign -= w.assign; @@ -214,11 +227,47 @@ struct Analyzer<'a> { config: &'a Config, in_var_decl: bool, scope: Scope<'a>, - data: &'a mut Data, + data: Arc>>, cur_class_id: Option, cur_fn_id: Option, } +impl Parallel for Analyzer<'_> { + fn create(&self) -> Self { + Self { + data: self.data.clone(), + scope: Scope { + parent: self.scope.parent, + ast_path: self.scope.ast_path.clone(), + bindings_affected_by_arguements: Default::default(), + bindings_affected_by_eval: Default::default(), + ..self.scope + }, + cur_class_id: self.cur_class_id.clone(), + cur_fn_id: self.cur_fn_id.clone(), + ..*self + } + } + + fn merge(&mut self, other: Self) { + self.scope.ast_path = other.scope.ast_path; + + self.scope + .bindings_affected_by_eval + .reserve(other.scope.bindings_affected_by_eval.len()); + self.scope + .bindings_affected_by_eval + .extend(other.scope.bindings_affected_by_eval); + + self.scope + .bindings_affected_by_arguements + .reserve(other.scope.bindings_affected_by_arguements.len()); + self.scope + .bindings_affected_by_arguements + .extend(other.scope.bindings_affected_by_arguements); + } +} + #[derive(Debug, Default)] struct Scope<'a> { parent: Option<&'a Scope<'a>>, @@ -236,7 +285,7 @@ struct Scope<'a> { ast_path: Vec, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] enum ScopeKind { Fn, ArrowFn, @@ -274,7 +323,7 @@ impl Analyzer<'_> { let mut v = Analyzer { scope: child, - data: self.data, + data: self.data.clone(), cur_fn_id: self.cur_fn_id.clone(), cur_class_id: self.cur_class_id.clone(), ..*self @@ -291,7 +340,13 @@ impl Analyzer<'_> { // If we found eval, mark all declarations in scope and upper as used if child_scope.found_direct_eval { for id in child_scope.bindings_affected_by_eval { - self.data.used_names.entry(id).or_default().usage += 1; + self.data + .get_or_default() + .borrow_mut() + .used_names + .entry(id) + .or_default() + .usage += 1; } self.scope.found_direct_eval = true; @@ -301,7 +356,13 @@ impl Analyzer<'_> { // Parameters for id in child_scope.bindings_affected_by_arguements { - self.data.used_names.entry(id).or_default().usage += 1; + self.data + .get_or_default() + .borrow_mut() + .used_names + .entry(id) + .or_default() + .usage += 1; } if !matches!(kind, ScopeKind::Fn) { @@ -327,16 +388,17 @@ impl Analyzer<'_> { } } + let mut data = self.data.get_or_default().borrow_mut(); + if self.scope.is_ast_path_empty() { // Add references from top level items into graph - let idx = self.data.node(&id); - self.data.entries.insert(idx); + data.entry_ids.insert(id.clone()); } else { let mut scope = Some(&self.scope); while let Some(s) = scope { for component in &s.ast_path { - self.data.add_dep_edge(component, &id, assign); + data.add_dep_edge(component.clone(), id.clone(), assign); } if s.kind == ScopeKind::Fn && !s.ast_path.is_empty() { @@ -348,9 +410,9 @@ impl Analyzer<'_> { } if assign { - self.data.used_names.entry(id).or_default().assign += 1; + data.used_names.entry(id).or_default().assign += 1; } else { - self.data.used_names.entry(id).or_default().usage += 1; + data.used_names.entry(id).or_default().usage += 1; } } } @@ -548,6 +610,34 @@ impl Visit for Analyzer<'_> { self.in_var_decl = old; } + + fn visit_opt_vec_expr_or_spreads(&mut self, n: &[Option]) { + self.visit_par(cpu_count() * 8, n); + } + + fn visit_prop_or_spreads(&mut self, n: &[PropOrSpread]) { + self.visit_par(cpu_count() * 8, n); + } + + fn visit_expr_or_spreads(&mut self, n: &[ExprOrSpread]) { + self.visit_par(cpu_count() * 8, n); + } + + fn visit_exprs(&mut self, n: &[Box]) { + self.visit_par(cpu_count() * 8, n); + } + + fn visit_stmts(&mut self, n: &[Stmt]) { + self.visit_par(cpu_count() * 8, n); + } + + fn visit_module_items(&mut self, n: &[ModuleItem]) { + self.visit_par(cpu_count() * 8, n); + } + + fn visit_var_declarators(&mut self, n: &[VarDeclarator]) { + self.visit_par(cpu_count() * 8, n); + } } impl Repeated for TreeShaker { @@ -636,11 +726,8 @@ impl TreeShaker { } // Abort if the variable is declared on top level scope. - let ix = self.data.graph_ix.get_index_of(&name); - if let Some(ix) = ix { - if self.data.entries.contains(&(ix as u32)) { - return false; - } + if self.data.entry_ids.contains(&name) { + return false; } } @@ -895,31 +982,31 @@ impl VisitMut for TreeShaker { } fn visit_mut_module(&mut self, m: &mut Module) { - debug_assert_valid(m); + HELPERS.set(&Helpers::new(true), || { + debug_assert_valid(m); - let _tracing = span!(Level::ERROR, "tree-shaker", pass = self.pass).entered(); + let _tracing = span!(Level::ERROR, "tree-shaker", pass = self.pass).entered(); - if self.bindings.is_empty() { - self.bindings = Arc::new(collect_decls(&*m)) - } + if self.bindings.is_empty() { + self.bindings = Arc::new(collect_decls(&*m)) + } - let mut data = Default::default(); + let data: Arc>> = Default::default(); - { - let mut analyzer = Analyzer { - config: &self.config, - in_var_decl: false, - scope: Default::default(), - data: &mut data, - cur_class_id: Default::default(), - cur_fn_id: Default::default(), - }; - m.visit_with(&mut analyzer); - } - data.subtract_cycles(); - self.data = Arc::new(data); + { + let mut analyzer = Analyzer { + config: &self.config, + in_var_decl: false, + scope: Default::default(), + data: data.clone(), + cur_class_id: Default::default(), + cur_fn_id: Default::default(), + }; + m.visit_with(&mut analyzer); + } + + self.data = Arc::new(merge_data(data)); - HELPERS.set(&Helpers::new(true), || { m.visit_mut_children_with(self); }) } @@ -960,29 +1047,29 @@ impl VisitMut for TreeShaker { } fn visit_mut_script(&mut self, m: &mut Script) { - let _tracing = span!(Level::ERROR, "tree-shaker", pass = self.pass).entered(); + HELPERS.set(&Helpers::new(true), || { + let _tracing = span!(Level::ERROR, "tree-shaker", pass = self.pass).entered(); - if self.bindings.is_empty() { - self.bindings = Arc::new(collect_decls(&*m)) - } + if self.bindings.is_empty() { + self.bindings = Arc::new(collect_decls(&*m)) + } - let mut data = Default::default(); + let data: Arc>> = Default::default(); - { - let mut analyzer = Analyzer { - config: &self.config, - in_var_decl: false, - scope: Default::default(), - data: &mut data, - cur_class_id: Default::default(), - cur_fn_id: Default::default(), - }; - m.visit_with(&mut analyzer); - } - data.subtract_cycles(); - self.data = Arc::new(data); + { + let mut analyzer = Analyzer { + config: &self.config, + in_var_decl: false, + scope: Default::default(), + data: data.clone(), + cur_class_id: Default::default(), + cur_fn_id: Default::default(), + }; + m.visit_with(&mut analyzer); + } + + self.data = Arc::new(merge_data(data)); - HELPERS.set(&Helpers::new(true), || { m.visit_mut_children_with(self); }) } @@ -1116,6 +1203,37 @@ impl VisitMut for TreeShaker { } } +fn merge_data(data: Arc>>) -> Data { + let data = Arc::try_unwrap(data) + .map_err(|_| {}) + .unwrap() + .into_iter() + .map(|d| d.into_inner()) + .collect::>(); + + // merged.subtract_cycles(); + let mut merged = merge_in_parallel(data); + + merged.subtract_cycles(); + + merged +} + +impl Merge for VarInfo { + fn merge(&mut self, other: Self) { + self.usage += other.usage; + self.assign += other.assign; + } +} + +impl Merge for Data { + fn merge(&mut self, other: Self) { + self.used_names.merge(other.used_names); + self.entry_ids.extend(other.entry_ids); + // self.edges.0.merge(other.edges.0); + } +} + impl Scope<'_> { /// Returns true if it's not in a function or class. fn is_ast_path_empty(&self) -> bool {