From 57991c72d06fb8f5388e142db2e34cef1952dd91 Mon Sep 17 00:00:00 2001 From: bohan Date: Thu, 31 Jul 2025 18:28:52 +0800 Subject: [PATCH 1/4] pref(es/minifier): less clone atom --- crates/swc_bundler/src/id.rs | 6 +- crates/swc_bundler/src/util.rs | 2 +- crates/swc_ecma_ast/src/ident.rs | 18 +++ crates/swc_ecma_ast/src/lib.rs | 4 +- .../src/compress/hoist_decls.rs | 14 ++- .../src/compress/optimize/arguments.rs | 2 +- .../src/compress/optimize/bools.rs | 2 +- .../src/compress/optimize/conditionals.rs | 2 +- .../src/compress/optimize/dead_code.rs | 4 +- .../src/compress/optimize/evaluate.rs | 8 +- .../src/compress/optimize/iife.rs | 47 ++++---- .../src/compress/optimize/inline.rs | 78 +++++++------ .../src/compress/optimize/mod.rs | 38 +++---- .../src/compress/optimize/ops.rs | 6 +- .../src/compress/optimize/props.rs | 11 +- .../src/compress/optimize/rest_params.rs | 2 +- .../src/compress/optimize/sequences.rs | 62 +++++----- .../src/compress/optimize/unused.rs | 23 ++-- .../src/compress/optimize/util.rs | 63 ++++++----- .../src/compress/pure/evaluate.rs | 1 + .../src/compress/pure/misc.rs | 2 + .../src/compress/pure/sequences.rs | 4 +- .../src/compress/pure/switches.rs | 2 +- .../src/compress/pure/vars.rs | 2 +- crates/swc_ecma_minifier/src/eval.rs | 6 +- crates/swc_ecma_minifier/src/metadata/mod.rs | 14 +-- crates/swc_ecma_minifier/src/mode.rs | 4 +- crates/swc_ecma_minifier/src/program_data.rs | 38 +++---- crates/swc_ecma_minifier/src/util/mod.rs | 42 ++++--- .../swc_ecma_usage_analyzer/src/alias/mod.rs | 30 ++--- .../src/analyzer/mod.rs | 106 ++++++++++-------- .../src/analyzer/storage.rs | 14 +-- crates/swc_ecma_utils/src/ident.rs | 33 +++++- crates/swc_ecma_utils/src/lib.rs | 1 + 34 files changed, 398 insertions(+), 293 deletions(-) diff --git a/crates/swc_bundler/src/id.rs b/crates/swc_bundler/src/id.rs index ded9b72414c1..7c117ffd4dd9 100644 --- a/crates/swc_bundler/src/id.rs +++ b/crates/swc_bundler/src/id.rs @@ -81,15 +81,17 @@ impl Id { } impl IdentLike for Id { + type Id = (Atom, SyntaxContext); + fn from_ident(i: &Ident) -> Self { i.into() } - fn to_id(&self) -> (Atom, SyntaxContext) { + fn to_id(&self) -> Self::Id { (self.0.clone(), self.1) } - fn into_id(self) -> (Atom, SyntaxContext) { + fn into_id(self) -> Self::Id { (self.0, self.1) } } diff --git a/crates/swc_bundler/src/util.rs b/crates/swc_bundler/src/util.rs index 127ffc80c4b7..9ed0c18ee461 100644 --- a/crates/swc_bundler/src/util.rs +++ b/crates/swc_bundler/src/util.rs @@ -50,7 +50,7 @@ pub(crate) trait ExprExt: Into { #[track_caller] fn assign_to(self, lhs: T) -> VarDeclarator where - T: IdentLike, + T: IdentLike, { let init = self.into(); let lhs = lhs.into_id(); diff --git a/crates/swc_ecma_ast/src/ident.rs b/crates/swc_ecma_ast/src/ident.rs index bff302b894c7..13cb68256516 100644 --- a/crates/swc_ecma_ast/src/ident.rs +++ b/crates/swc_ecma_ast/src/ident.rs @@ -1,6 +1,7 @@ use std::{ borrow::Cow, fmt::Display, + hash::{Hash, Hasher}, ops::{Deref, DerefMut}, }; @@ -240,6 +241,13 @@ struct Align64(pub(crate) T); const T: bool = true; const F: bool = false; +pub fn hashed_id_from_id(id: &Id) -> HashedId { + let mut hasher = rustc_hash::FxHasher::default(); + id.0.hash(&mut hasher); + id.1.hash(&mut hasher); + HashedId(hasher.finish()) +} + impl Ident { /// In `op`, [EqIgnoreSpan] of [Ident] will ignore the syntax context. pub fn within_ignored_ctxt(op: F) -> Ret @@ -261,6 +269,13 @@ impl Ident { (self.sym.clone(), self.ctxt) } + pub fn hashed_id(&self) -> HashedId { + let mut hasher = rustc_hash::FxHasher::default(); + self.sym.hash(&mut hasher); + self.ctxt.hash(&mut hasher); + HashedId(hasher.finish()) + } + #[inline] pub fn is_valid_ascii_start(c: u8) -> bool { debug_assert!(c.is_ascii()); @@ -527,6 +542,9 @@ pub unsafe fn unsafe_id_from_ident(id: &Ident) -> UnsafeId { /// See [Ident] for documentation. pub type Id = (Atom, SyntaxContext); +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub struct HashedId(u64); + impl Take for Ident { fn dummy() -> Self { Ident::new_no_ctxt(atom!(""), DUMMY_SP) diff --git a/crates/swc_ecma_ast/src/lib.rs b/crates/swc_ecma_ast/src/lib.rs index 60291b480469..c42d2cc93b43 100644 --- a/crates/swc_ecma_ast/src/lib.rs +++ b/crates/swc_ecma_ast/src/lib.rs @@ -23,8 +23,8 @@ pub use self::{ expr::*, function::{Function, Param, ParamOrTsParamProp}, ident::{ - unsafe_id, unsafe_id_from_ident, BindingIdent, EsReserved, Id, Ident, IdentName, - PrivateName, UnsafeId, + hashed_id_from_id, unsafe_id, unsafe_id_from_ident, BindingIdent, EsReserved, HashedId, Id, + Ident, IdentName, PrivateName, UnsafeId, }, jsx::{ JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXClosingElement, JSXClosingFragment, diff --git a/crates/swc_ecma_minifier/src/compress/hoist_decls.rs b/crates/swc_ecma_minifier/src/compress/hoist_decls.rs index 2401325bbb14..9dfd40a328a3 100644 --- a/crates/swc_ecma_minifier/src/compress/hoist_decls.rs +++ b/crates/swc_ecma_minifier/src/compress/hoist_decls.rs @@ -58,9 +58,10 @@ impl Hoister<'_> { let ids: Vec = find_pat_ids(&var.decls); if ids.iter().any(|id| { + let id = hashed_id_from_id(id); self.data .vars - .get(id) + .get(&id) .map(|v| !v.flags.contains(VarUsageInfoFlags::USED_ABOVE_DECL)) .unwrap_or(false) }) { @@ -95,7 +96,7 @@ impl Hoister<'_> { let mut var_decls = Vec::new(); let mut fn_decls = Vec::with_capacity(stmts.len()); let mut new_stmts = Vec::with_capacity(stmts.len()); - let mut done = FxHashSet::default(); + let mut done: FxHashSet = FxHashSet::default(); let mut found_non_var_decl = false; for stmt in stmts.take() { @@ -122,14 +123,15 @@ impl Hoister<'_> { let ids: Vec = find_pat_ids(&decl.name); for id in ids { - if done.insert(id.to_id()) { + let hashed_id = id.hashed_id(); + if done.insert(hashed_id) { // If the enclosing function declares parameter with same // name, we can drop a varaible. if decl.init.is_none() && self .data .vars - .get(&id.to_id()) + .get(&hashed_id) .map(|v| { v.flags.contains( VarUsageInfoFlags::DECLARED_AS_FN_PARAM, @@ -215,7 +217,7 @@ impl Hoister<'_> { && self .data .vars - .get(&name.to_id()) + .get(&name.hashed_id()) .map(|v| { v.flags.contains( VarUsageInfoFlags::DECLARED_AS_FN_PARAM, @@ -226,7 +228,7 @@ impl Hoister<'_> { return false; } - done.insert(name.to_id()) + done.insert(name.hashed_id()) } _ => true, }; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs b/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs index ec6591154fca..cfeacd4d1e81 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs @@ -77,7 +77,7 @@ impl Optimizer<'_> { Pat::Ident(i) => self .data .vars - .get(&i.id.to_id()) + .get(&i.id.hashed_id()) .map(|v| v.declared_count >= 2) .unwrap_or(false), _ => true, diff --git a/crates/swc_ecma_minifier/src/compress/optimize/bools.rs b/crates/swc_ecma_minifier/src/compress/optimize/bools.rs index 5ef46f3fde6f..061e9c10ae5f 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/bools.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/bools.rs @@ -22,7 +22,7 @@ impl Optimizer<'_> { ) if &**l_v == "undefined" => { // TODO? if let Expr::Ident(arg) = &**arg { - if let Some(usage) = o.data.vars.get(&arg.to_id()) { + if let Some(usage) = o.data.vars.get(&arg.hashed_id()) { if !usage.flags.contains(VarUsageInfoFlags::DECLARED) { return false; } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs b/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs index daf8b7eab006..c3b09564e9ce 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs @@ -404,7 +404,7 @@ impl Optimizer<'_> { let side_effect_free = self .data .vars - .get(&cons_callee.to_id()) + .get(&cons_callee.hashed_id()) .map(|v| { v.flags.contains( VarUsageInfoFlags::IS_FN_LOCAL.union(VarUsageInfoFlags::DECLARED), diff --git a/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs b/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs index ca20562feb77..59e0d74372a9 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs @@ -58,7 +58,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&lhs.to_id()) + .get(&lhs.hashed_id()) .map(|var| { var.flags.contains( VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL), @@ -97,7 +97,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&lhs.to_id()) + .get(&lhs.hashed_id()) .map(|var| { var.flags.contains( VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL), diff --git a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs index 0e98eaf0d6ff..3fd80c7c4b1a 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs @@ -42,9 +42,9 @@ impl Optimizer<'_> { }) = e { if let Expr::Ident(obj) = &**obj { - let metadata = *self.functions.get(&obj.to_id())?; - - let usage = self.data.vars.get(&obj.to_id())?; + let hashed_id = obj.hashed_id(); + let metadata = *self.functions.get(&hashed_id)?; + let usage = self.data.vars.get(&hashed_id)?; if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { return None; @@ -101,7 +101,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&i.to_id()) + .get(&i.hashed_id()) .map(|var| var.flags.contains(VarUsageInfoFlags::DECLARED)) .unwrap_or(false) { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs index 314165044245..258ca7b0b75d 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs @@ -183,7 +183,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&ident.to_id()) + .get(&ident.hashed_id()) .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)) .is_some() { @@ -202,7 +202,7 @@ impl Optimizer<'_> { if param.sym == "arguments" { continue; } - if let Some(usage) = self.data.vars.get(¶m.to_id()) { + if let Some(usage) = self.data.vars.get(¶m.hashed_id()) { if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { continue; } @@ -225,7 +225,7 @@ impl Optimizer<'_> { param.id.sym, param.id.ctxt ); - vars.insert(param.to_id(), arg.clone()); + vars.insert(param.hashed_id(), arg.clone()); } else { trace_op!( "iife: Trying to inline argument ({}{:?}) (not inlinable)", @@ -240,13 +240,13 @@ impl Optimizer<'_> { param.id.ctxt ); - vars.insert(param.to_id(), Expr::undefined(param.span())); + vars.insert(param.hashed_id(), Expr::undefined(param.span())); } } Pat::Rest(rest_pat) => { if let Pat::Ident(param_id) = &*rest_pat.arg { - if let Some(usage) = self.data.vars.get(¶m_id.to_id()) { + if let Some(usage) = self.data.vars.get(¶m_id.hashed_id()) { if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || usage.ref_count != 1 || !usage.flags.contains(VarUsageInfoFlags::HAS_PROPERTY_ACCESS) @@ -269,7 +269,7 @@ impl Optimizer<'_> { } vars.insert( - param_id.to_id(), + param_id.hashed_id(), ArrayLit { span: param_id.span, elems: e @@ -353,7 +353,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&ident.to_id()) + .get(&ident.hashed_id()) .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)) .is_some() { @@ -367,7 +367,7 @@ impl Optimizer<'_> { // We check for parameter and argument for (idx, param) in params.iter_mut().enumerate() { if let Pat::Ident(param) = &mut **param { - if let Some(usage) = self.data.vars.get(¶m.to_id()) { + if let Some(usage) = self.data.vars.get(¶m.hashed_id()) { if usage.ref_count == 0 { removed.push(idx); } @@ -410,8 +410,11 @@ impl Optimizer<'_> { } #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))] - pub(super) fn inline_vars_in_node(&mut self, n: &mut N, mut vars: FxHashMap>) - where + pub(super) fn inline_vars_in_node( + &mut self, + n: &mut N, + mut vars: FxHashMap>, + ) where N: for<'aa> VisitMutWith>, { trace_op!("inline: inline_vars_in_node"); @@ -506,7 +509,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&i.to_id()) + .get(&i.hashed_id()) .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)) .is_some() { @@ -750,7 +753,7 @@ impl Optimizer<'_> { // Don't create top-level variables. if !self.may_add_ident() { for pid in param_ids.clone() { - if let Some(usage) = self.data.vars.get(&pid.to_id()) { + if let Some(usage) = self.data.vars.get(&pid.hashed_id()) { if usage.ref_count > 1 || usage.assign_count > 0 || usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) @@ -798,7 +801,7 @@ impl Optimizer<'_> { match &decl.name { Pat::Ident(id) if id.sym == "arguments" => return false, Pat::Ident(id) => { - if self.vars.has_pending_inline_for(&id.to_id()) { + if self.vars.has_pending_inline_for(id.hashed_id()) { log_abort!( "iife: [x] Cannot inline because pending inline of `{}`", id.id @@ -896,10 +899,10 @@ impl Optimizer<'_> { if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) { if params_len != 0 { - let captured = idents_captured_by(body); + let captured = idents_captured_by::<_, HashedId>(body); for param in param_ids { - if captured.contains(¶m.to_id()) { + if captured.contains(¶m.hashed_id()) { log_abort!( "iife: [x] Cannot inline because of the capture of `{}`", param @@ -942,7 +945,8 @@ impl Optimizer<'_> { let no_arg = arg.is_none(); if let Some(arg) = arg { - if let Some(usage) = self.data.vars.get_mut(¶m.to_id()) { + let hashed_id = param.hashed_id(); + if let Some(usage) = self.data.vars.get_mut(&hashed_id) { if usage.ref_count == 1 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) && usage.property_mutation_count == 0 @@ -954,7 +958,7 @@ impl Optimizer<'_> { ) { // We don't need to create a variable in this case - self.vars.vars_for_inlining.insert(param.to_id(), arg); + self.vars.vars_for_inlining.insert(hashed_id, arg); continue; } @@ -999,7 +1003,8 @@ impl Optimizer<'_> { let mut arg = args.get_mut(idx).map(|arg| arg.expr.take()); if let Some(arg) = &mut arg { - if let Some(usage) = self.data.vars.get_mut(¶m.to_id()) { + let hashed_id = param.hashed_id(); + if let Some(usage) = self.data.vars.get_mut(&hashed_id) { if usage.ref_count == 1 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) && usage.property_mutation_count == 0 @@ -1011,9 +1016,7 @@ impl Optimizer<'_> { ) { // We don't need to create a variable in this case - self.vars - .vars_for_inlining - .insert(param.to_id(), arg.take()); + self.vars.vars_for_inlining.insert(hashed_id, arg.take()); continue; } @@ -1585,7 +1588,7 @@ impl Optimizer<'_> { let mut substitutions = FxHashMap::default(); for (param, arg) in arrow.params.iter().zip(&call.args) { if let Pat::Ident(ident) = param { - substitutions.insert(ident.to_id(), arg.expr.clone()); + substitutions.insert(ident.hashed_id(), arg.expr.clone()); } } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs index 826c09edb806..5fdd378664ca 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs @@ -58,7 +58,7 @@ impl Optimizer<'_> { } } - if let Some(usage) = self.data.vars.get(&ident.to_id()) { + if let Some(usage) = self.data.vars.get(&ident.hashed_id()) { let ref_count = usage.ref_count - u32::from(can_drop && usage.ref_count > 1); if !usage.flags.contains(VarUsageInfoFlags::VAR_INITIALIZED) { return; @@ -96,7 +96,7 @@ impl Optimizer<'_> { // No use => dropped if ref_count == 0 { - self.mode.store(ident.to_id(), &*init); + self.mode.store(ident.hashed_id(), &*init); if init.may_have_side_effects(self.ctx.expr_ctx) { // TODO: Inline partially @@ -112,7 +112,7 @@ impl Optimizer<'_> { let mut inlined_into_init = false; - let id = ident.to_id(); + let hashed_id = ident.hashed_id(); // We inline arrays partially if it's pure (all elements are literal), and not // modified. @@ -149,7 +149,7 @@ impl Optimizer<'_> { ); self.vars .lits_for_array_access - .insert(ident.to_id(), Box::new(init.clone())); + .insert(ident.hashed_id(), Box::new(init.clone())); } } } @@ -183,17 +183,17 @@ impl Optimizer<'_> { if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { match init { Expr::Fn(..) | Expr::Arrow(..) | Expr::Class(..) => { - self.typeofs.insert(ident.to_id(), atom!("function")); + self.typeofs.insert(ident.hashed_id(), atom!("function")); } Expr::Array(..) | Expr::Object(..) => { - self.typeofs.insert(ident.to_id(), atom!("object")); + self.typeofs.insert(ident.hashed_id(), atom!("object")); } _ => {} } } if !usage.mutated() { - self.mode.store(ident.to_id(), &*init); + self.mode.store(ident.hashed_id(), &*init); } if usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) { @@ -214,7 +214,7 @@ impl Optimizer<'_> { Expr::Ident(id) if !id.eq_ignore_span(ident) => { if !usage.flags.contains(VarUsageInfoFlags::ASSIGNED_FN_LOCAL) { false - } else if let Some(u) = self.data.vars.get(&id.to_id()) { + } else if let Some(u) = self.data.vars.get(&id.hashed_id()) { let mut should_inline = !u.flags.contains(VarUsageInfoFlags::REASSIGNED) && u.flags.contains(VarUsageInfoFlags::DECLARED); @@ -276,7 +276,7 @@ impl Optimizer<'_> { } else { self.vars .lits_for_cmp - .insert(ident.to_id(), init.clone().into()); + .insert(ident.hashed_id(), init.clone().into()); false } } @@ -306,7 +306,7 @@ impl Optimizer<'_> { self.vars.inline_with_multi_replacer(init); } - self.mode.store(id.clone(), &*init); + self.mode.store(hashed_id, &*init); let VarUsageInfo { usage_count, @@ -316,7 +316,7 @@ impl Optimizer<'_> { } = **usage; let mut inc_usage = || { if let Expr::Ident(i) = &*init { - if let Some(u) = self.data.vars.get_mut(&i.to_id()) { + if let Some(u) = self.data.vars.get_mut(&i.hashed_id()) { u.flags |= flags & VarUsageInfoFlags::USED_AS_ARG; u.flags |= flags & VarUsageInfoFlags::USED_AS_REF; u.flags |= flags & VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY; @@ -356,7 +356,7 @@ impl Optimizer<'_> { inc_usage(); - self.vars.lits.insert(id.clone(), init.take().into()); + self.vars.lits.insert(hashed_id, init.take().into()); ident.take(); } else if self.options.inline != 0 || self.options.reduce_vars { @@ -366,15 +366,15 @@ impl Optimizer<'_> { ident.ctxt ); - self.mode.store(id.clone(), &*init); + self.mode.store(hashed_id, &*init); inc_usage(); - self.vars.lits.insert(id.clone(), init.clone().into()); + self.vars.lits.insert(hashed_id, init.clone().into()); } } - let usage = self.data.vars.get(&id).unwrap(); + let usage = self.data.vars.get(&hashed_id).unwrap(); // Single use => inlined if !self.ctx.bit_ctx.contains(BitCtx::IsExported) @@ -430,7 +430,10 @@ impl Optimizer<'_> { } Expr::Fn(f) => { - let excluded: Vec = find_pat_ids(&f.function.params); + let excluded: Vec = find_pat_ids::<_, Ident>(&f.function.params) + .iter() + .map(|i| i.hashed_id()) + .collect(); for id in idents_used_by(&f.function.params) { if excluded.contains(&id) { @@ -447,7 +450,10 @@ impl Optimizer<'_> { } Expr::Arrow(f) => { - let excluded: Vec = find_pat_ids(&f.params); + let excluded: Vec = find_pat_ids::<_, Ident>(&f.params) + .iter() + .map(|i| i.hashed_id()) + .collect(); for id in idents_used_by(&f.params) { if excluded.contains(&id) { @@ -478,7 +484,7 @@ impl Optimizer<'_> { return; } - if let Some(init_usage) = self.data.vars.get(&id.to_id()) { + if let Some(init_usage) = self.data.vars.get(&id.hashed_id()) { if init_usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || !init_usage.flags.contains(VarUsageInfoFlags::DECLARED) { @@ -544,10 +550,10 @@ impl Optimizer<'_> { // block_scoping pass. // If the function captures the environment, we // can't inline it. - let params: Vec = find_pat_ids(&f.function.params); + let params: Vec = find_pat_ids(&f.function.params); if !params.is_empty() { - let captured = idents_captured_by(&f.function.body); + let captured = idents_captured_by::<_, HashedId>(&f.function.body); for param in params { if captured.contains(¶m) { @@ -578,7 +584,7 @@ impl Optimizer<'_> { self.vars .vars_for_inlining - .insert(ident.take().to_id(), init.take().into()); + .insert(ident.take().hashed_id(), init.take().into()); } } } @@ -629,20 +635,21 @@ impl Optimizer<'_> { /// Stores `typeof` of [ClassDecl] and [FnDecl]. pub(super) fn store_typeofs(&mut self, decl: &mut Decl) { let i = match &*decl { - Decl::Class(v) => v.ident.clone(), - Decl::Fn(f) => f.ident.clone(), + Decl::Class(v) => &v.ident, + Decl::Fn(f) => &f.ident, _ => return, }; if i.sym == *"arguments" { return; } - if let Some(usage) = self.data.vars.get(&i.to_id()) { + let hashed_id = i.hashed_id(); + if let Some(usage) = self.data.vars.get(&hashed_id) { if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { trace_op!("typeofs: Storing typeof `{}{:?}`", i.sym, i.ctxt); match &*decl { Decl::Fn(..) | Decl::Class(..) => { - self.typeofs.insert(i.to_id(), atom!("function")); + self.typeofs.insert(hashed_id, atom!("function")); } _ => {} } @@ -697,7 +704,7 @@ impl Optimizer<'_> { return; } - if let Some(usage) = self.data.vars.get(&i.to_id()) { + if let Some(usage) = self.data.vars.get(&i.hashed_id()) { if usage .flags .contains(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM) @@ -763,7 +770,7 @@ impl Optimizer<'_> { } self.vars.simple_functions.insert( - i.to_id(), + i.hashed_id(), FnExpr { ident: None, function: f.function.clone(), @@ -859,7 +866,7 @@ impl Optimizer<'_> { } }; - self.vars.vars_for_inlining.insert(i.to_id(), e); + self.vars.vars_for_inlining.insert(i.hashed_id(), e); } else { log_abort!("inline: [x] Usage: {:?}", usage); } @@ -877,7 +884,7 @@ impl Optimizer<'_> { if let MemberProp::Computed(prop) = &mut me.prop { if let Expr::Lit(Lit::Num(..)) = &*prop.expr { if let Expr::Ident(obj) = &*me.obj { - let new = self.vars.lits_for_array_access.get(&obj.to_id()); + let new = self.vars.lits_for_array_access.get(&obj.hashed_id()); if let Some(new) = new { report_change!("inline: Inlined array access"); @@ -890,14 +897,14 @@ impl Optimizer<'_> { } } Expr::Ident(i) => { - let id = i.to_id(); + let id = i.hashed_id(); if let Some(mut value) = self .vars .lits .get(&id) .or_else(|| { if self.ctx.bit_ctx.contains(BitCtx::IsCallee) { - self.vars.simple_functions.get(&i.to_id()) + self.vars.simple_functions.get(&id) } else { None } @@ -924,8 +931,9 @@ impl Optimizer<'_> { let new_ctxt = *new_ctxt; - if let Some(usage) = self.data.vars.get(&id).cloned() { + if let Some(usage) = self.data.vars.get(&hashed_id_from_id(&id)).cloned() { let new_id = (id.0.clone(), new_ctxt); + let new_id = hashed_id_from_id(&new_id); self.data.vars.insert(new_id, usage); } @@ -944,8 +952,10 @@ impl Optimizer<'_> { return; } + let hashed_id = i.hashed_id(); + // Check without cloning - if let Some(value) = self.vars.vars_for_inlining.get(&i.to_id()) { + if let Some(value) = self.vars.vars_for_inlining.get(&hashed_id) { if self.ctx.bit_ctx.contains(BitCtx::IsExactLhsOfAssign) && !is_valid_for_lhs(value) { @@ -959,7 +969,7 @@ impl Optimizer<'_> { } } - if let Some(value) = self.vars.vars_for_inlining.remove(&i.to_id()) { + if let Some(value) = self.vars.vars_for_inlining.remove(&hashed_id) { self.changed = true; report_change!("inline: Replacing '{}' with an expression", i); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index 860796b9db8a..0c8cd5392b97 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs @@ -227,7 +227,7 @@ struct Optimizer<'a> { vars: Vars, - typeofs: Box>, + typeofs: Box>, /// This information is created by analyzing identifier usages. /// /// This is calculated multiple time, but only once per one @@ -237,7 +237,7 @@ struct Optimizer<'a> { mode: &'a dyn Mode, - functions: Box>, + functions: Box>, } #[derive(Default)] @@ -245,34 +245,34 @@ struct Vars { /// Cheap to clone. /// /// Used for inlining. - lits: FxHashMap>, + lits: FxHashMap>, /// Used for `hoist_props`. - hoisted_props: Box>, + hoisted_props: Box>, /// Literals which are cheap to clone, but not sure if we can inline without /// making output bigger. /// /// https://github.com/swc-project/swc/issues/4415 - lits_for_cmp: FxHashMap>, + lits_for_cmp: FxHashMap>, /// This stores [Expr::Array] if all elements are literals. - lits_for_array_access: FxHashMap>, + lits_for_array_access: FxHashMap>, /// Used for copying functions. /// /// We use this to distinguish [Callee::Expr] from other [Expr]s. - simple_functions: FxHashMap>, - vars_for_inlining: FxHashMap>, + simple_functions: FxHashMap>, + vars_for_inlining: FxHashMap>, /// Variables which should be removed by [Finalizer] because of the order of /// visit. - removed: FxHashSet, + removed: FxHashSet, } impl Vars { - fn has_pending_inline_for(&self, id: &Id) -> bool { - self.lits.contains_key(id) || self.vars_for_inlining.contains_key(id) + fn has_pending_inline_for(&self, id: HashedId) -> bool { + self.lits.contains_key(&id) || self.vars_for_inlining.contains_key(&id) } /// Returns true if something is changed. @@ -344,7 +344,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&id.to_id()) + .get(&id.hashed_id()) .is_some_and(|v| v.flags.contains(VarUsageInfoFlags::EXPORTED)) { return false; @@ -827,7 +827,7 @@ impl Optimizer<'_> { if let Expr::Ident(callee) = &**callee { if self.options.reduce_vars && self.options.side_effects { - if let Some(usage) = self.data.vars.get(&callee.to_id()) { + if let Some(usage) = self.data.vars.get(&callee.hashed_id()) { if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) && usage.flags.contains(VarUsageInfoFlags::PURE_FN) { @@ -894,7 +894,7 @@ impl Optimizer<'_> { right, .. }) => { - let old = i.id.to_id(); + let old = i.id.hashed_id(); self.store_var_for_inlining(&mut i.id, right, true); if i.is_dummy() && self.options.unused { @@ -1809,13 +1809,13 @@ impl VisitMut for Optimizer<'_> { .. }) => { if let Some(i) = left.as_ident_mut() { - let old = i.to_id(); + let old = i.hashed_id(); self.store_var_for_inlining(i, right, false); if i.is_dummy() && self.options.unused { report_change!("inline: Removed variable ({}, {:?})", old.0, old.1); - self.vars.removed.insert(old.clone()); + self.vars.removed.insert(old); } if right.is_invalid() { @@ -2045,7 +2045,7 @@ impl VisitMut for Optimizer<'_> { .entered(); self.functions - .entry(f.ident.to_id()) + .entry(f.ident.hashed_id()) .or_insert_with(|| FnMetadata::from(&*f.function)); self.drop_unused_params(&mut f.function.params); @@ -2064,7 +2064,7 @@ impl VisitMut for Optimizer<'_> { fn visit_mut_fn_expr(&mut self, e: &mut FnExpr) { if let Some(ident) = &e.ident { self.functions - .entry(ident.to_id()) + .entry(ident.hashed_id()) .or_insert_with(|| FnMetadata::from(&*e.function)); } @@ -3010,7 +3010,7 @@ impl VisitMut for Optimizer<'_> { if let Some(Expr::Invalid(..)) = var.init.as_deref() { if let Pat::Ident(i) = &var.name { - if let Some(usage) = self.data.vars.get(&i.id.to_id()) { + if let Some(usage) = self.data.vars.get(&i.id.hashed_id()) { if usage .flags .contains(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM) diff --git a/crates/swc_ecma_minifier/src/compress/optimize/ops.rs b/crates/swc_ecma_minifier/src/compress/optimize/ops.rs index 19897778fbdb..09e8a1669385 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/ops.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/ops.rs @@ -18,8 +18,8 @@ impl Optimizer<'_> { match e.op { op!("===") | op!("==") | op!("!==") | op!("!=") => { if e.left.is_ident() && e.left.eq_ignore_span(&e.right) { - let id: Ident = e.left.clone().ident().unwrap(); - if let Some(t) = self.typeofs.get(&id.to_id()) { + let hashed_id = e.left.as_ident().unwrap().hashed_id(); + if let Some(t) = self.typeofs.get(&hashed_id) { match &**t { "object" | "function" => { e.left = Box::new(make_bool( @@ -249,7 +249,7 @@ impl Optimizer<'_> { { match &**arg { Expr::Ident(arg) => { - if let Some(value) = self.typeofs.get(&arg.to_id()).cloned() { + if let Some(value) = self.typeofs.get(&arg.hashed_id()).cloned() { report_change!( "Converting typeof of variable to literal as we know the value" ); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/props.rs b/crates/swc_ecma_minifier/src/compress/optimize/props.rs index 67e635a13d65..d04a3fd9fad3 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/props.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/props.rs @@ -38,7 +38,7 @@ impl Optimizer<'_> { // If a variable is initialized multiple time, we currently don't do anything // smart. - let usage = self.data.vars.get(&name.to_id())?; + let usage = self.data.vars.get(&name.hashed_id())?; if usage.mutated() || usage.flags.intersects( VarUsageInfoFlags::USED_ABOVE_DECL @@ -73,7 +73,7 @@ impl Optimizer<'_> { let mut unknown_used_props = self .data .vars - .get(&name.to_id()) + .get(&name.hashed_id()) .map(|v| v.accessed_props.clone()) .unwrap_or_default(); @@ -124,7 +124,8 @@ impl Optimizer<'_> { } if let Some(init) = n.init.as_deref() { - self.mode.store(name.to_id(), init); + let hashed_id = name.hashed_id(); + self.mode.store(hashed_id, init); } let mut new_vars = Vec::new(); @@ -175,7 +176,7 @@ impl Optimizer<'_> { self.vars .hoisted_props - .insert((name.to_id(), key), new_var_name); + .insert((name.hashed_id(), key), new_var_name); new_vars.push(new_var); } @@ -210,7 +211,7 @@ impl Optimizer<'_> { if let Some(value) = self .vars .hoisted_props - .get(&(obj.to_id(), sym.clone())) + .get(&(obj.hashed_id(), sym.clone())) .cloned() { report_change!("hoist_props: Inlining `{}.{}`", obj.sym, sym); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs b/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs index 62f669cdc30d..85eb2001bbe1 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs @@ -41,7 +41,7 @@ impl Optimizer<'_> { // Get the identifier of the rest parameter let rest_id = match &*rest_pat.arg { - Pat::Ident(BindingIdent { id, .. }) => id.to_id(), + Pat::Ident(BindingIdent { id, .. }) => id.hashed_id(), _ => return, }; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs index 83a2ab135615..808354d56a9f 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs @@ -219,10 +219,10 @@ impl Optimizer<'_> { Expr::Assign(AssignExpr { op: op!("="), .. }) ) }) { - let ids_used_by_exprs = + let ids_used_by_exprs: FxHashSet = idents_used_by_ignoring_nested(&exprs); - let ids_used_by_first_expr = + let ids_used_by_first_expr: FxHashSet = idents_used_by_ignoring_nested(&*e.first_expr_mut()); let has_conflict = ids_used_by_exprs @@ -391,7 +391,7 @@ impl Optimizer<'_> { .. })) => { if let Some(id) = obj.as_ident() { - if let Some(usage) = self.data.vars.get(&id.to_id()) { + if let Some(usage) = self.data.vars.get(&id.hashed_id()) { id.ctxt != self.ctx.expr_ctx.unresolved_ctxt && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) } else { @@ -741,11 +741,13 @@ impl Optimizer<'_> { // name. if let (Pat::Ident(an), Pat::Ident(bn)) = (&av.name, &bv.name) { if an.ctxt == bn.ctxt && an.sym == bn.sym { + if an.id.ctxt == bn.id.ctxt && an.id.sym == bn.id.sym { // We need to preserve side effect of `av.init` match bv.init.as_deref_mut() { Some(b_init) => { if is_ident_used_by(an, b_init) { + if is_ident_used_by(&an.id, b_init) { log_abort!( "We can't duplicated binding because \ initializer uses the previous declaration of \ @@ -831,7 +833,7 @@ impl Optimizer<'_> { && self .data .vars - .get(&a_id.id.to_id()) + .get(&a_id.id.hashed_id()) .map(|u| { !u.flags.intersects( VarUsageInfoFlags::INLINE_PREVENTED.union(VarUsageInfoFlags::DECLARED_AS_FN_EXPR) @@ -1123,8 +1125,9 @@ impl Optimizer<'_> { return false; }; - if deps.contains(&(e.to_id(), AccessKind::Reference)) - || deps.contains(&(e.to_id(), AccessKind::Call)) + let e_hashed_id = e.hashed_id(); + if deps.contains(&(e_hashed_id, AccessKind::Reference)) + || deps.contains(&(e_hashed_id, AccessKind::Call)) { return false; } @@ -1223,12 +1226,14 @@ impl Optimizer<'_> { match a { Mergable::Var(a) => { if is_ident_used_by(left_id, &**a) { + if is_ident_used_by(&left_id.id, &**a) { log_abort!("e.left is used by a (var)"); return false; } } Mergable::Expr(a) => { if is_ident_used_by(left_id, &**a) { + if is_ident_used_by(&left_id.id, &**a) { log_abort!("e.left is used by a (expr)"); return false; } @@ -1236,6 +1241,7 @@ impl Optimizer<'_> { Mergable::FnDecl(a) => { // TODO(kdy1): I'm not sure if this check is required. if is_ident_used_by(left_id, &**a) { + if is_ident_used_by(&left_id.id, &**a) { log_abort!("e.left is used by a ()"); return false; } @@ -1257,12 +1263,12 @@ impl Optimizer<'_> { return false; } - let used_ids = idents_used_by(&*e.right); + let used_ids: FxHashSet = idents_used_by(&*e.right); if used_ids.is_empty() { return true; } - if used_ids.len() != 1 || !used_ids.contains(&left_id.to_id()) { + if used_ids.len() != 1 || !used_ids.contains(&left_id.hashed_id()) { log_abort!("bad used_ids"); return false; } @@ -1327,7 +1333,7 @@ impl Optimizer<'_> { if e.args.is_empty() { if let Callee::Expr(callee) = &e.callee { if let Expr::Fn(callee) = &**callee { - let ids = idents_used_by(&callee.function); + let ids: FxHashSet = idents_used_by(&callee.function); if ids .iter() @@ -1438,7 +1444,7 @@ impl Optimizer<'_> { } fn assignee_skippable_for_seq(&self, a: &Mergable, assignee: &Ident) -> bool { - let usgae = if let Some(usage) = self.data.vars.get(&assignee.to_id()) { + let usgae = if let Some(usage) = self.data.vars.get(&assignee.hashed_id()) { usage } else { return false; @@ -1671,13 +1677,13 @@ impl Optimizer<'_> { // // See https://github.com/swc-project/swc/pull/6509 - let obj_ids = idents_used_by_ignoring_nested(obj); let a_ids = match a { Mergable::Var(a) => idents_used_by_ignoring_nested(&a.init), Mergable::Expr(a) => idents_used_by_ignoring_nested(&**a), Mergable::FnDecl(a) => idents_used_by_ignoring_nested(&**a), Mergable::Drop => return Ok(false), }; + let obj_ids: FxHashSet = idents_used_by_ignoring_nested(obj); if !obj_ids.is_disjoint(&a_ids) { return Ok(false); } @@ -1726,7 +1732,7 @@ impl Optimizer<'_> { }; if let Some(left_obj) = b_left.obj.as_ident() { - if let Some(usage) = self.data.vars.get(&left_obj.to_id()) { + if let Some(usage) = self.data.vars.get(&left_obj.hashed_id()) { if left_obj.ctxt != self.ctx.expr_ctx.unresolved_ctxt && !usage .flags @@ -2083,7 +2089,7 @@ impl Optimizer<'_> { .. }) => { if let Expr::Ident(a_id) = &**arg { - if let Some(usage) = self.data.vars.get(&a_id.to_id()) { + if let Some(usage) = self.data.vars.get(&a_id.hashed_id()) { if let Some(VarDeclKind::Const) = usage.var_kind { return Err(()); } @@ -2118,6 +2124,7 @@ impl Optimizer<'_> { if let Expr::Ident(orig_expr) = &*e { if orig_expr.ctxt == a_id.ctxt && orig_expr.sym == a_id.sym { + if orig_expr.sym == a_id.sym && orig_expr.ctxt == a_id.ctxt { replaced = true; *e = UpdateExpr { span: DUMMY_SP, @@ -2158,7 +2165,7 @@ impl Optimizer<'_> { .. }) => { if let Expr::Ident(a_id) = &**arg { - if let Some(usage) = self.data.vars.get(&a_id.to_id()) { + if let Some(usage) = self.data.vars.get(&a_id.hashed_id()) { if let Some(VarDeclKind::Const) = usage.var_kind { return Err(()); } @@ -2193,6 +2200,7 @@ impl Optimizer<'_> { if let Expr::Ident(orig_expr) = &*e { if orig_expr.ctxt == a_id.ctxt && orig_expr.sym == a_id.sym { + if orig_expr.sym == a_id.sym && orig_expr.ctxt == a_id.ctxt { replaced = true; *e = UpdateExpr { span: DUMMY_SP, @@ -2258,7 +2266,7 @@ impl Optimizer<'_> { } }; - if let Some(usage) = self.data.vars.get(&left_id.to_id()) { + if let Some(usage) = self.data.vars.get(&left_id.hashed_id()) { if usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) { return Ok(false); } @@ -2303,7 +2311,7 @@ impl Optimizer<'_> { _ => return Ok(false), }; - if let Some(usage) = self.data.vars.get(&left.to_id()) { + if let Some(usage) = self.data.vars.get(&left.hashed_id()) { let is_lit = match a.init.as_deref() { Some(e) => is_trivial_lit(e), _ => false, @@ -2346,7 +2354,7 @@ impl Optimizer<'_> { } Mergable::FnDecl(a) => { - if let Some(usage) = self.data.vars.get(&a.ident.to_id()) { + if let Some(usage) = self.data.vars.get(&a.ident.hashed_id()) { if usage.ref_count != 1 || usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL) @@ -2386,7 +2394,7 @@ impl Optimizer<'_> { match a { Mergable::Var(a) => { if self.options.unused { - if let Some(usage) = self.data.vars.get(&left_id.to_id()) { + if let Some(usage) = self.data.vars.get(&left_id.hashed_id()) { // We are eliminating one usage, so we use 1 instead of // 0 if !force_drop @@ -2402,7 +2410,7 @@ impl Optimizer<'_> { if can_take_init || force_drop { let init = a.init.take(); - if let Some(usage) = self.data.vars.get(&left_id.to_id()) { + if let Some(usage) = self.data.vars.get(&left_id.hashed_id()) { if usage.var_kind == Some(VarDeclKind::Const) { a.init = Some(Expr::undefined(DUMMY_SP)); } @@ -2482,14 +2490,14 @@ impl Optimizer<'_> { let var_type = self .data .vars - .get(&left_id.to_id()) + .get(&left_id.hashed_id()) .and_then(|info| info.merged_var_type); let Some(a_type) = a_type else { return Ok(false); }; - let b_type = b.right.get_type(self.ctx.expr_ctx); if let Some(a_op) = a_op { + let b_type = b.right.get_type(self.ctx.expr_ctx); if can_drop_op_for(a_op, b.op, var_type, a_type, b_type) { if b_left.ctxt == left_id.ctxt && b_left.sym == left_id.sym { if let Some(bin_op) = b.op.to_update() { @@ -2551,11 +2559,11 @@ impl Optimizer<'_> { let to = take_a(a, false, false); - replace_id_with_expr(b, left_id.to_id(), to); + replace_id_with_expr(b, &left_id, to); if can_remove { report_change!("sequences: Removed variable ({})", left_id); - self.vars.removed.insert(left_id.to_id()); + self.vars.removed.insert(left_id.hashed_id()); } dump_change_detail!("sequences: {}", dump(&*b, false)); @@ -2586,6 +2594,8 @@ impl Optimizer<'_> { .iter() .any(|id| id.1 == a_id.ctxt && id.0 == a_id.sym) { + let used_by_b: FxHashSet = idents_used_by(&*b.right); + if used_by_b.contains(&a_id.hashed_id()) { return true; } } @@ -2705,6 +2715,7 @@ impl Mergable<'_> { }, Mergable::Expr(s) => match &**s { Expr::Assign(s) => s.left.as_ident().map(|i| &i.id), + Expr::Assign(s) => s.left.as_ident().map(|v| &v.id), _ => None, }, Mergable::FnDecl(f) => Some(&f.ident), @@ -2715,7 +2726,7 @@ impl Mergable<'_> { #[derive(Debug, Default)] struct MergeSequenceCache { - ident_usage_cache: Vec>>, + ident_usage_cache: Vec>>, top_retain_cache: Vec>, } @@ -2727,7 +2738,7 @@ impl MergeSequenceCache { } } - fn is_ident_used_by>( + fn is_ident_used_by>>( &mut self, ident: &Ident, node: &N, @@ -2737,6 +2748,7 @@ impl MergeSequenceCache { idents .iter() .any(|id| id.1 == ident.ctxt && id.0 == ident.sym) + idents.contains(&ident.hashed_id()) } fn invalidate(&mut self, node_id: usize) { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 448acfd932e8..48ed80729372 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs @@ -185,7 +185,7 @@ impl Optimizer<'_> { return; } - if let Some(v) = self.data.vars.get(&i.to_id()) { + if let Some(v) = self.data.vars.get(&i.hashed_id()) { let is_used_in_member = v.property_mutation_count > 0 || v.flags.contains(VarUsageInfoFlags::USED_AS_REF); if v.ref_count == 0 @@ -257,7 +257,7 @@ impl Optimizer<'_> { } } - if let Some(usage) = self.data.vars.get(&e.to_id()) { + if let Some(usage) = self.data.vars.get(&e.hashed_id()) { if !usage.flags.contains(VarUsageInfoFlags::DECLARED) { return true; } @@ -517,7 +517,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&ident.to_id()) + .get(&ident.hashed_id()) .map(|v| v.usage_count == 0 && v.property_mutation_count == 0) .unwrap_or(false) { @@ -569,7 +569,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&ident.to_id()) + .get(&ident.hashed_id()) .map(|v| v.usage_count == 0 && v.property_mutation_count == 0) .unwrap_or(false) { @@ -611,7 +611,7 @@ impl Optimizer<'_> { }; if let Expr::Ident(arg) = &*update.arg { - if let Some(var) = self.data.vars.get(&arg.to_id()) { + if let Some(var) = self.data.vars.get(&arg.hashed_id()) { // Update is counted as usage if var .flags @@ -659,7 +659,7 @@ impl Optimizer<'_> { }; if let AssignTarget::Simple(SimpleAssignTarget::Ident(left)) = &assign.left { - if let Some(var) = self.data.vars.get(&left.to_id()) { + if let Some(var) = self.data.vars.get(&left.hashed_id()) { // TODO: We don't need fn_local check if var .flags @@ -724,7 +724,7 @@ impl Optimizer<'_> { return; } - if let Some(var) = self.data.vars.get(&i.to_id()) { + if let Some(var) = self.data.vars.get(&i.hashed_id()) { // technically this is inline if !var.flags.intersects( VarUsageInfoFlags::INLINE_PREVENTED.union(VarUsageInfoFlags::EXPORTED), @@ -775,7 +775,7 @@ impl Optimizer<'_> { let can_remove_ident = self .data .vars - .get(&i.to_id()) + .get(&i.hashed_id()) .map(|v| { (!v.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) && v.ref_count == 0 @@ -805,7 +805,7 @@ impl Optimizer<'_> { for d in var.decls.iter_mut() { if d.init.is_none() { if let Pat::Ident(name) = &d.name { - if let Some(usage) = self.data.vars.get_mut(&name.to_id()) { + if let Some(usage) = self.data.vars.get_mut(&name.hashed_id()) { if usage.flags.contains( VarUsageInfoFlags::IS_FN_LOCAL .union(VarUsageInfoFlags::DECLARED_AS_FN_PARAM), @@ -837,6 +837,7 @@ impl Optimizer<'_> { if let Some(Expr::Fn(f)) = v.init.as_deref_mut() { let Some(f_ident) = f.ident.as_ref() else { + let Some(f_ident) = &f.ident else { return; }; @@ -864,7 +865,7 @@ impl Optimizer<'_> { let name = v.name.as_ident()?; let obj = v.init.as_mut()?.as_mut_object()?; - let usage = self.data.vars.get(&name.to_id())?; + let usage = self.data.vars.get(&name.hashed_id())?; if usage.flags.intersects( VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY @@ -902,7 +903,7 @@ impl Optimizer<'_> { let mut unknown_used_props = self .data .vars - .get(&name.to_id()) + .get(&name.hashed_id()) .map(|v| v.accessed_props.clone()) .unwrap_or_default(); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/util.rs b/crates/swc_ecma_minifier/src/compress/optimize/util.rs index 4d1827bb92c6..9cf1505ebda1 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/util.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/util.rs @@ -219,13 +219,13 @@ pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool { /// handle all edge cases and this type is the complement for it. #[derive(Clone, Copy)] pub(crate) struct Finalizer<'a> { - pub simple_functions: &'a FxHashMap>, - pub lits: &'a FxHashMap>, - pub lits_for_cmp: &'a FxHashMap>, - pub lits_for_array_access: &'a FxHashMap>, - pub hoisted_props: &'a FxHashMap<(Id, Atom), Ident>, + pub simple_functions: &'a FxHashMap>, + pub lits: &'a FxHashMap>, + pub lits_for_cmp: &'a FxHashMap>, + pub lits_for_array_access: &'a FxHashMap>, + pub hoisted_props: &'a FxHashMap<(HashedId, Atom), Ident>, - pub vars_to_remove: &'a FxHashSet, + pub vars_to_remove: &'a FxHashSet, pub changed: bool, } @@ -241,10 +241,10 @@ impl Parallel for Finalizer<'_> { } impl Finalizer<'_> { - fn var(&mut self, i: &Id, mode: FinalizerMode) -> Option> { + fn var(&mut self, i: HashedId, mode: FinalizerMode) -> Option> { let mut e = match mode { FinalizerMode::Callee => { - let mut value = self.simple_functions.get(i).cloned()?; + let mut value = self.simple_functions.get(&i).cloned()?; let mut cache = FxHashMap::default(); let mut remap = FxHashMap::default(); let bindings: FxHashSet = collect_decls(&*value); @@ -268,8 +268,8 @@ impl Finalizer<'_> { value } - FinalizerMode::ComparisonWithLit => self.lits_for_cmp.get(i).cloned()?, - FinalizerMode::MemberAccess => self.lits_for_array_access.get(i).cloned()?, + FinalizerMode::ComparisonWithLit => self.lits_for_cmp.get(&i).cloned()?, + FinalizerMode::MemberAccess => self.lits_for_array_access.get(&i).cloned()?, }; e.visit_mut_children_with(self); @@ -288,7 +288,7 @@ impl Finalizer<'_> { fn check(&mut self, e: &mut Expr, mode: FinalizerMode) { if let Expr::Ident(i) = e { - if let Some(new) = self.var(&i.to_id(), mode) { + if let Some(new) = self.var(i.hashed_id(), mode) { debug!("multi-replacer: Replaced `{}`", i); self.changed = true; @@ -341,7 +341,7 @@ impl VisitMut for Finalizer<'_> { fn visit_mut_expr(&mut self, n: &mut Expr) { match n { Expr::Ident(i) => { - if let Some(expr) = self.lits.get(&i.to_id()) { + if let Some(expr) = self.lits.get(&i.hashed_id()) { *n = *expr.clone(); return; } @@ -358,7 +358,7 @@ impl VisitMut for Finalizer<'_> { _ => return, }; - if let Some(ident) = self.hoisted_props.get(&(obj.to_id(), sym.clone())) { + if let Some(ident) = self.hoisted_props.get(&(obj.hashed_id(), sym.clone())) { self.changed = true; *n = ident.clone().into(); return; @@ -442,7 +442,7 @@ impl VisitMut for Finalizer<'_> { if n.init.is_none() { if let Pat::Ident(i) = &n.name { - if self.vars_to_remove.contains(&i.to_id()) { + if self.vars_to_remove.contains(&i.hashed_id()) { n.name.take(); } } @@ -459,7 +459,7 @@ impl VisitMut for Finalizer<'_> { n.visit_mut_children_with(self); if let Prop::Shorthand(i) = n { - if let Some(expr) = self.lits.get(&i.to_id()) { + if let Some(expr) = self.lits.get(&i.hashed_id()) { *n = Prop::KeyValue(KeyValueProp { key: i.take().into(), value: expr.clone(), @@ -471,21 +471,22 @@ impl VisitMut for Finalizer<'_> { } pub(crate) struct NormalMultiReplacer<'a> { - pub vars: &'a mut FxHashMap>, + pub vars: &'a mut FxHashMap>, pub changed: bool, } impl<'a> NormalMultiReplacer<'a> { /// `worked` will be changed to `true` if any replacement is done - pub fn new(vars: &'a mut FxHashMap>) -> Self { + pub fn new(vars: &'a mut FxHashMap>) -> Self { NormalMultiReplacer { vars, changed: false, } } - fn var(&mut self, i: &Id) -> Option> { - let mut e = self.vars.remove(i)?; + fn var(&mut self, i: &Ident) -> Option> { + let hashed_id = i.hashed_id(); + let mut e = self.vars.remove(&hashed_id)?; e.visit_mut_children_with(self); @@ -516,7 +517,7 @@ impl VisitMut for NormalMultiReplacer<'_> { } if let Expr::Ident(i) = e { - if let Some(new) = self.var(&i.to_id()) { + if let Some(new) = self.var(i) { debug!("multi-replacer: Replaced `{}`", i); self.changed = true; @@ -542,7 +543,7 @@ impl VisitMut for NormalMultiReplacer<'_> { p.visit_mut_children_with(self); if let Prop::Shorthand(i) = p { - if let Some(value) = self.var(&i.to_id()) { + if let Some(value) = self.var(i) { debug!("multi-replacer: Replaced `{}` as shorthand", i); self.changed = true; @@ -563,9 +564,13 @@ impl VisitMut for NormalMultiReplacer<'_> { } } -pub(crate) fn replace_id_with_expr(node: &mut N, from: Id, to: Box) -> Option> +pub(crate) fn replace_id_with_expr<'a, N>( + node: &mut N, + from: &'a Ident, + to: Box, +) -> Option> where - N: VisitMutWith, + N: VisitMutWith>, { let mut v = ExprReplacer { from, to: Some(to) }; node.visit_mut_with(&mut v); @@ -573,12 +578,12 @@ where v.to } -pub(crate) struct ExprReplacer { - from: Id, +pub(crate) struct ExprReplacer<'a> { + from: &'a Ident, to: Option>, } -impl ExprReplacer { +impl ExprReplacer<'_> { fn take(&mut self) -> Option> { let e = self.to.take()?; @@ -595,14 +600,14 @@ impl ExprReplacer { } } -impl VisitMut for ExprReplacer { +impl<'a> VisitMut for ExprReplacer<'a> { noop_visit_mut_type!(fail); fn visit_mut_expr(&mut self, e: &mut Expr) { e.visit_mut_children_with(self); if let Expr::Ident(i) = e { - if self.from.0 == i.sym && self.from.1 == i.ctxt { + if self.from.sym == i.sym && self.from.ctxt == i.ctxt { if let Some(new) = self.take() { *e = *new; } else { @@ -616,7 +621,7 @@ impl VisitMut for ExprReplacer { p.visit_mut_children_with(self); if let Prop::Shorthand(i) = p { - if self.from.0 == i.sym && self.from.1 == i.ctxt { + if self.from.sym == i.sym && self.from.ctxt == i.ctxt { let value = if let Some(new) = self.take() { new } else { diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index afb961fe2203..ebcf696b3b34 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -796,6 +796,7 @@ impl Pure<'_> { if let AssignTarget::Simple(SimpleAssignTarget::Ident(a_left)) = a_left { if let Expr::Ident(b_id) = b { if b_id.ctxt == a_left.id.ctxt && b_id.sym == a_left.id.sym { + if b_id.ctxt == a_left.ctxt && b_id.sym == a_left.sym { report_change!("evaluate: Trivial: `{}`", a_left.id); *b = *a_right.clone(); self.changed = true; diff --git a/crates/swc_ecma_minifier/src/compress/pure/misc.rs b/crates/swc_ecma_minifier/src/compress/pure/misc.rs index cceb3df3ee75..55cca39b5ab4 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/misc.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/misc.rs @@ -1981,6 +1981,8 @@ impl Pure<'_> { if l.ctxt == r.ctxt && l.ctxt != self.expr_ctx.unresolved_ctxt && l.sym == r.sym + && l.sym == r.sym + && l.ctxt != self.expr_ctx.unresolved_ctxt { self.changed = true; *e = *assign.right.take(); diff --git a/crates/swc_ecma_minifier/src/compress/pure/sequences.rs b/crates/swc_ecma_minifier/src/compress/pure/sequences.rs index dfcee072bda7..2ad978887abe 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/sequences.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/sequences.rs @@ -299,7 +299,9 @@ impl Pure<'_> { }) if prop.is_ident_with("apply") || prop.is_ident_with("call") => { // if let Expr::Ident(b_callee_obj) = &**b_callee_obj { - if b_callee_obj.to_id() != var_name.to_id() { + if b_callee_obj.ctxt != var_name.ctxt + || b_callee_obj.sym != var_name.sym + { continue; } } else { diff --git a/crates/swc_ecma_minifier/src/compress/pure/switches.rs b/crates/swc_ecma_minifier/src/compress/pure/switches.rs index 97714a92b03a..1d31a6223b8d 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/switches.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/switches.rs @@ -117,7 +117,7 @@ impl Pure<'_> { } else { if !may_match_other_than_exact && !test.is_ident() - && !idents_used_by(test).is_empty() + && !idents_used_by::<_, Id>(test).is_empty() { may_match_other_than_exact = true; } diff --git a/crates/swc_ecma_minifier/src/compress/pure/vars.rs b/crates/swc_ecma_minifier/src/compress/pure/vars.rs index 8c176ebe9e25..468df12a6f4f 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/vars.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/vars.rs @@ -165,7 +165,7 @@ impl Pure<'_> { } match &v.name { - Pat::Ident(i) => found.insert(i.to_id()), + Pat::Ident(i) => found.insert(i.hashed_id()), _ => true, } }) diff --git a/crates/swc_ecma_minifier/src/eval.rs b/crates/swc_ecma_minifier/src/eval.rs index ab89d366cd52..081e9e957135 100644 --- a/crates/swc_ecma_minifier/src/eval.rs +++ b/crates/swc_ecma_minifier/src/eval.rs @@ -50,7 +50,7 @@ struct Eval { #[derive(Default)] struct EvalStore { - cache: FxHashMap>, + cache: FxHashMap>, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -60,7 +60,7 @@ pub enum EvalResult { } impl Mode for Eval { - fn store(&self, id: Id, value: &Expr) { + fn store(&self, id: HashedId, value: &Expr) { let mut w = self.store.lock(); w.cache.insert(id, Box::new(value.clone())); } @@ -192,7 +192,7 @@ impl Evaluator { self.run(); let lock = self.data.store.lock(); - let val = lock.cache.get(&i.to_id())?; + let val = lock.cache.get(&i.hashed_id())?; return Some(val.clone()); } diff --git a/crates/swc_ecma_minifier/src/metadata/mod.rs b/crates/swc_ecma_minifier/src/metadata/mod.rs index 523adb89cb43..9784326d00d5 100644 --- a/crates/swc_ecma_minifier/src/metadata/mod.rs +++ b/crates/swc_ecma_minifier/src/metadata/mod.rs @@ -49,7 +49,7 @@ struct InfoMarker<'a> { #[allow(dead_code)] options: Option<&'a CompressOptions>, pure_funcs: Option>>, - pure_callee: FxHashSet, + pure_callee: FxHashSet, comments: Option<&'a dyn Comments>, marks: Marks, @@ -61,7 +61,7 @@ impl InfoMarker<'_> { fn is_pure_callee(&self, callee: &Expr) -> bool { match callee { Expr::Ident(callee) => { - if self.pure_callee.contains(&callee.to_id()) { + if self.pure_callee.contains(&callee.hashed_id()) { return true; } } @@ -230,7 +230,7 @@ const NO_SIDE_EFFECTS_FLAG: &str = "NO_SIDE_EFFECTS"; struct InfoCollector<'a> { comments: Option<&'a dyn Comments>, - pure_callees: &'a mut FxHashSet, + pure_callees: &'a mut FxHashSet, } impl Visit for InfoCollector<'_> { @@ -241,7 +241,7 @@ impl Visit for InfoCollector<'_> { if let Decl::Fn(f) = &f.decl { if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - self.pure_callees.insert(f.ident.to_id()); + self.pure_callees.insert(f.ident.hashed_id()); } } } @@ -250,7 +250,7 @@ impl Visit for InfoCollector<'_> { f.visit_children_with(self); if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - self.pure_callees.insert(f.ident.to_id()); + self.pure_callees.insert(f.ident.hashed_id()); } } @@ -259,7 +259,7 @@ impl Visit for InfoCollector<'_> { if let Some(ident) = &f.ident { if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - self.pure_callees.insert(ident.to_id()); + self.pure_callees.insert(ident.hashed_id()); } } } @@ -274,7 +274,7 @@ impl Visit for InfoCollector<'_> { || has_flag(self.comments, v.span, NO_SIDE_EFFECTS_FLAG) || has_flag(self.comments, init.span(), NO_SIDE_EFFECTS_FLAG) { - self.pure_callees.insert(ident.to_id()); + self.pure_callees.insert(ident.hashed_id()); } } } diff --git a/crates/swc_ecma_minifier/src/mode.rs b/crates/swc_ecma_minifier/src/mode.rs index 8a3b1da59192..36a83124eb27 100644 --- a/crates/swc_ecma_minifier/src/mode.rs +++ b/crates/swc_ecma_minifier/src/mode.rs @@ -1,7 +1,7 @@ use swc_ecma_ast::*; pub(crate) trait Mode: Send + Sync { - fn store(&self, id: Id, value: &Expr); + fn store(&self, id: HashedId, value: &Expr); fn preserve_vars(&self) -> bool; @@ -15,7 +15,7 @@ pub(crate) trait Mode: Send + Sync { pub struct Minification; impl Mode for Minification { - fn store(&self, _: Id, _: &Expr) {} + fn store(&self, _: HashedId, _: &Expr) {} fn preserve_vars(&self) -> bool { false diff --git a/crates/swc_ecma_minifier/src/program_data.rs b/crates/swc_ecma_minifier/src/program_data.rs index fe64a935de58..65672593f876 100644 --- a/crates/swc_ecma_minifier/src/program_data.rs +++ b/crates/swc_ecma_minifier/src/program_data.rs @@ -6,7 +6,7 @@ use swc_atoms::Atom; use swc_common::SyntaxContext; use swc_ecma_ast::*; use swc_ecma_usage_analyzer::{ - alias::{Access, AccessKind}, + alias::AccessKind, analyzer::{ analyze_with_custom_storage, storage::{ScopeDataLike, Storage, VarDataLike}, @@ -36,13 +36,13 @@ where /// Analyzed info of a whole program we are working on. #[derive(Debug, Default)] pub(crate) struct ProgramData { - pub(crate) vars: FxHashMap>, + pub(crate) vars: FxHashMap>, pub(crate) top: ScopeData, pub(crate) scopes: FxHashMap, - initialized_vars: IndexSet, + initialized_vars: IndexSet, pub(crate) property_atoms: Option>, } @@ -125,7 +125,7 @@ pub(crate) struct VarUsageInfo { /// `infects_to`. This should be renamed, but it will be done with another /// PR. (because it's hard to review) - infects_to: Vec, + infects_to: Vec<(HashedId, AccessKind)>, /// Only **string** properties. pub(crate) accessed_props: FxHashMap, } @@ -210,7 +210,7 @@ impl Storage for ProgramData { &mut self.top } - fn var_or_default(&mut self, id: Id) -> &mut Self::VarData { + fn var_or_default(&mut self, id: HashedId) -> &mut Self::VarData { self.vars.entry(id).or_default() } @@ -358,7 +358,7 @@ impl Storage for ProgramData { } } - fn report_usage(&mut self, ctx: Ctx, i: Id) { + fn report_usage(&mut self, ctx: Ctx, i: HashedId) { let inited = self.initialized_vars.contains(&i); let e = self.vars.entry(i).or_insert_with(|| { @@ -388,8 +388,8 @@ impl Storage for ProgramData { } } - fn report_assign(&mut self, ctx: Ctx, i: Id, is_op: bool, ty: Value) { - let e = self.vars.entry(i.clone()).or_default(); + fn report_assign(&mut self, ctx: Ctx, i: HashedId, is_op: bool, ty: Value) { + let e = self.vars.entry(i).or_default(); let inited = self.initialized_vars.contains(&i); @@ -401,7 +401,7 @@ impl Storage for ProgramData { e.assign_count += 1; if !is_op { - self.initialized_vars.insert(i.clone()); + self.initialized_vars.insert(i); if e.ref_count == 1 && e.var_kind != Some(VarDeclKind::Const) && !inited { e.flags.insert(VarUsageInfoFlags::VAR_INITIALIZED); } else { @@ -415,8 +415,8 @@ impl Storage for ProgramData { e.usage_count = e.usage_count.saturating_sub(1); } - let mut to_visit: IndexSet = - IndexSet::from_iter(e.infects_to.iter().cloned().map(|i| i.0)); + let mut to_visit: IndexSet = + IndexSet::from_iter(e.infects_to.iter().map(|i| i.0)); let mut idx = 0; @@ -440,7 +440,7 @@ impl Storage for ProgramData { usage.usage_count += 1; } - to_visit.extend(usage.infects_to.iter().cloned().map(|i| i.0)) + to_visit.extend(usage.infects_to.iter().map(|i| i.0)) } idx += 1; @@ -458,7 +458,7 @@ impl Storage for ProgramData { // debug!(has_init = has_init, "declare_decl(`{}`)", i); // } - let v = self.vars.entry(i.to_id()).or_default(); + let v = self.vars.entry(i.hashed_id()).or_default(); if ctx.is_top_level() { v.flags |= VarUsageInfoFlags::IS_TOP_LEVEL; } @@ -510,7 +510,7 @@ impl Storage for ProgramData { v.flags |= VarUsageInfoFlags::DECLARED; // not a VarDecl, thus always inited if init_type.is_some() || kind.is_none() { - self.initialized_vars.insert(i.to_id()); + self.initialized_vars.insert(i.hashed_id()); } if ctx.in_catch_param() { v.flags |= VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM; @@ -527,7 +527,7 @@ impl Storage for ProgramData { self.initialized_vars.truncate(len) } - fn mark_property_mutation(&mut self, id: Id) { + fn mark_property_mutation(&mut self, id: HashedId) { let e = self.vars.entry(id).or_default(); e.property_mutation_count += 1; @@ -535,7 +535,7 @@ impl Storage for ProgramData { .infects_to .iter() .filter(|(_, kind)| *kind == AccessKind::Reference) - .map(|(id, _)| id.clone()) + .map(|(id, _)| *id) .collect::>(); for other in to_mark_mutate { @@ -551,7 +551,7 @@ impl Storage for ProgramData { } } - fn get_var_data(&self, id: Id) -> Option<&Self::VarData> { + fn get_var_data(&self, id: HashedId) -> Option<&Self::VarData> { self.vars.get(&id).map(|v| v.as_ref()) } } @@ -625,7 +625,7 @@ impl VarDataLike for VarUsageInfo { self.flags.insert(VarUsageInfoFlags::USED_AS_REF); } - fn add_infects_to(&mut self, other: Access) { + fn add_infects_to(&mut self, other: (HashedId, AccessKind)) { self.infects_to.push(other); } @@ -749,7 +749,7 @@ impl ProgramData { return false; } - if let Some(v) = self.vars.get(&i.to_id()) { + if let Some(v) = self.vars.get(&i.hashed_id()) { return !v.flags.contains(VarUsageInfoFlags::DECLARED); } diff --git a/crates/swc_ecma_minifier/src/util/mod.rs b/crates/swc_ecma_minifier/src/util/mod.rs index fa88ae849a90..0c76faeaad32 100644 --- a/crates/swc_ecma_minifier/src/util/mod.rs +++ b/crates/swc_ecma_minifier/src/util/mod.rs @@ -7,7 +7,7 @@ use swc_atoms::Atom; use swc_common::{util::take::Take, Span, Spanned, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene}; -use swc_ecma_utils::{DropSpan, ModuleItemLike, StmtLike, Value}; +use swc_ecma_utils::{ident::IdentLike, DropSpan, ModuleItemLike, StmtLike, Value}; use swc_ecma_visit::{noop_visit_type, visit_mut_pass, visit_obj_and_computed, Visit, VisitWith}; pub(crate) mod base54; @@ -336,12 +336,12 @@ where } #[derive(Default)] -pub(crate) struct IdentUsageCollector { - ids: FxHashSet, +pub(crate) struct IdentUsageCollector { + ids: FxHashSet, ignore_nested: bool, } -impl Visit for IdentUsageCollector { +impl Visit for IdentUsageCollector { noop_visit_type!(fail); visit_obj_and_computed!(); @@ -387,7 +387,7 @@ impl Visit for IdentUsageCollector { } fn visit_ident(&mut self, n: &Ident) { - self.ids.insert(n.to_id()); + self.ids.insert(I::from_ident(n)); } fn visit_prop_name(&mut self, n: &PropName) { @@ -398,12 +398,12 @@ impl Visit for IdentUsageCollector { } #[derive(Default)] -pub(crate) struct CapturedIdCollector { - ids: FxHashSet, +pub(crate) struct CapturedIdCollector { + ids: FxHashSet, is_nested: bool, } -impl Visit for CapturedIdCollector { +impl Visit for CapturedIdCollector { noop_visit_type!(fail); visit_obj_and_computed!(); @@ -431,7 +431,7 @@ impl Visit for CapturedIdCollector { fn visit_ident(&mut self, n: &Ident) { if self.is_nested { - self.ids.insert(n.to_id()); + self.ids.insert(I::from_ident(n)); } } @@ -442,37 +442,43 @@ impl Visit for CapturedIdCollector { } } -pub(crate) fn idents_captured_by(n: &N) -> FxHashSet +pub(crate) fn idents_captured_by( + n: &N, +) -> FxHashSet where - N: VisitWith, + N: VisitWith>, { let mut v = CapturedIdCollector { is_nested: false, - ..Default::default() + ids: FxHashSet::default(), }; n.visit_with(&mut v); v.ids } -pub(crate) fn idents_used_by(n: &N) -> FxHashSet +pub(crate) fn idents_used_by( + n: &N, +) -> FxHashSet where - N: VisitWith, + N: VisitWith>, { let mut v = IdentUsageCollector { ignore_nested: false, - ..Default::default() + ids: FxHashSet::default(), }; n.visit_with(&mut v); v.ids } -pub(crate) fn idents_used_by_ignoring_nested(n: &N) -> FxHashSet +pub(crate) fn idents_used_by_ignoring_nested( + n: &N, +) -> FxHashSet where - N: VisitWith, + N: VisitWith>, { let mut v = IdentUsageCollector { ignore_nested: true, - ..Default::default() + ids: FxHashSet::default(), }; n.visit_with(&mut v); v.ids diff --git a/crates/swc_ecma_usage_analyzer/src/alias/mod.rs b/crates/swc_ecma_usage_analyzer/src/alias/mod.rs index 84d3093e1058..6a732d7dc771 100644 --- a/crates/swc_ecma_usage_analyzer/src/alias/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/alias/mod.rs @@ -83,7 +83,7 @@ pub enum AccessKind { pub type Access = (Id, AccessKind); -pub fn collect_infects_from(node: &N, config: AliasConfig) -> FxHashSet +pub fn collect_infects_from(node: &N, config: AliasConfig) -> FxHashSet<(HashedId, AccessKind)> where N: InfectableNode + VisitWith, { @@ -120,7 +120,7 @@ pub fn try_collect_infects_from( node: &N, config: AliasConfig, max_entries: usize, -) -> Result, TooManyAccesses> +) -> Result, TooManyAccesses> where N: InfectableNode + VisitWith, { @@ -158,34 +158,38 @@ pub struct InfectionCollector { config: AliasConfig, unresolved_ctxt: Option, - bindings: FxHashSet, + bindings: FxHashSet, ctx: Ctx, - accesses: FxHashSet, + accesses: FxHashSet<(HashedId, AccessKind)>, max_entries: Option, } impl InfectionCollector { fn add_binding(&mut self, e: &Ident) { - if self.bindings.insert(e.to_id()) { - self.accesses.remove(&(e.to_id(), AccessKind::Reference)); - self.accesses.remove(&(e.to_id(), AccessKind::Call)); + let hashed_id = e.hashed_id(); + if self.bindings.insert(hashed_id) { + self.accesses.remove(&(hashed_id, AccessKind::Reference)); + self.accesses.remove(&(hashed_id, AccessKind::Call)); } } - fn add_usage(&mut self, e: Id) { - if self.bindings.contains(&e) { + fn add_usage(&mut self, ident: &Ident) { + let hashed_id = ident.hashed_id(); + if self.bindings.contains(&hashed_id) { return; } - if self.unresolved_ctxt == Some(e.1) && is_global_var_with_pure_property_access(&e.0) { + if self.unresolved_ctxt == Some(ident.ctxt) + && is_global_var_with_pure_property_access(&ident.sym) + { return; } self.accesses.insert(( - e, + hashed_id, if self.ctx.contains(Ctx::IsCallee) { AccessKind::Call } else { @@ -297,7 +301,7 @@ impl Visit for InfectionCollector { match e { Expr::Ident(i) => { if self.ctx.contains(Ctx::TrackExprIdent) { - self.add_usage(i.to_id()); + self.add_usage(i); } } @@ -336,7 +340,7 @@ impl Visit for InfectionCollector { } fn visit_ident(&mut self, n: &Ident) { - self.add_usage(n.to_id()); + self.add_usage(n); } fn visit_member_expr(&mut self, n: &MemberExpr) { diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs index cdd3300e7d94..26672b051a23 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs @@ -2,9 +2,7 @@ use ctx::BitContext; use rustc_hash::FxHashMap; use swc_common::SyntaxContext; use swc_ecma_ast::*; -use swc_ecma_utils::{ - find_pat_ids, ident::IdentLike, ExprCtx, ExprExt, IsEmpty, StmtExt, Type, Value, -}; +use swc_ecma_utils::{find_pat_ids, ExprCtx, ExprExt, IsEmpty, StmtExt, Type, Value}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use swc_timer::timer; @@ -84,7 +82,7 @@ where scope: S::ScopeData, ctx: Ctx, expr_ctx: ExprCtx, - used_recursively: FxHashMap, + used_recursively: FxHashMap, } impl UsageAnalyzer @@ -152,23 +150,24 @@ where self.scope.mark_used_arguments(); } - let i = i.to_id(); + let id = i.hashed_id(); - if let Some(recr) = self.used_recursively.get(&i) { + if let Some(recr) = self.used_recursively.get(&id) { if let RecursiveUsage::Var { can_ignore: false } = recr { - self.data.report_usage(self.ctx, i.clone()); - self.data.var_or_default(i.clone()).mark_used_above_decl() + self.data.report_usage(self.ctx, id); + self.data.var_or_default(id).mark_used_above_decl() } - self.data.var_or_default(i.clone()).mark_used_recursively(); + self.data.var_or_default(id).mark_used_recursively(); return; } - self.data.report_usage(self.ctx, i) + self.data.report_usage(self.ctx, id) } fn report_assign_pat(&mut self, p: &Pat, is_read_modify: bool) { for id in find_pat_ids(p) { // It's hard to determined the type of pat assignment + let id = hashed_id_from_id(&id); self.data .report_assign(self.ctx, id, is_read_modify, Value::Unknown) } @@ -177,7 +176,7 @@ where match &**e { Expr::Ident(i) => { self.data - .report_assign(self.ctx, i.to_id(), is_read_modify, Value::Unknown) + .report_assign(self.ctx, i.hashed_id(), is_read_modify, Value::Unknown) } _ => self.mark_mutation_if_member(e.as_member()), } @@ -186,7 +185,7 @@ where fn report_assign_expr_if_ident(&mut self, e: Option<&Ident>, is_op: bool, ty: Value) { if let Some(i) = e { - self.data.report_assign(self.ctx, i.to_id(), is_op, ty) + self.data.report_assign(self.ctx, i.hashed_id(), is_op, ty) } } @@ -223,7 +222,7 @@ where fn mark_mutation_if_member(&mut self, e: Option<&MemberExpr>) { if let Some(m) = e { for_each_id_ref_in_expr(&m.obj, &mut |id| { - self.data.mark_property_mutation(id.to_id()) + self.data.mark_property_mutation(id.hashed_id()) }); } } @@ -287,6 +286,7 @@ where match &n.left { AssignTarget::Pat(p) => { for id in find_pat_ids(p) { + let id = hashed_id_from_id(&id); self.data.report_assign( self.ctx, id, @@ -307,7 +307,7 @@ where if n.op == op!("=") { let left = match &n.left { - AssignTarget::Simple(left) => left.leftmost().as_deref().map(Ident::to_id), + AssignTarget::Simple(left) => left.leftmost().as_deref().map(Ident::hashed_id), AssignTarget::Pat(..) => None, }; @@ -322,10 +322,10 @@ where }, ) { if v.is_none() { - v = Some(self.data.var_or_default(left.to_id())); + v = Some(self.data.var_or_default(left)); } - v.as_mut().unwrap().add_infects_to(id.clone()); + v.as_mut().unwrap().add_infects_to(id); } } } @@ -365,7 +365,7 @@ where } else { if e.op == op!("in") { for_each_id_ref_in_expr(&e.right, &mut |obj| { - let var = self.data.var_or_default(obj.to_id()); + let var = self.data.var_or_default(obj.hashed_id()); var.mark_used_as_ref(); match &*e.left { @@ -426,7 +426,9 @@ where if let Callee::Expr(callee) = &n.callee { for_each_id_ref_in_expr(callee, &mut |i| { - self.data.var_or_default(i.to_id()).mark_used_as_callee(); + self.data + .var_or_default(i.hashed_id()) + .mark_used_as_callee(); }); match &**callee { @@ -440,7 +442,7 @@ where if is_safe_to_access_prop(&arg.expr) { if let Pat::Ident(id) = &p.pat { self.data - .var_or_default(id.to_id()) + .var_or_default(id.hashed_id()) .mark_initialized_with_safe_value(); } } @@ -458,7 +460,7 @@ where if is_safe_to_access_prop(&arg.expr) { if let Pat::Ident(id) = &p { self.data - .var_or_default(id.to_id()) + .var_or_default(id.hashed_id()) .mark_initialized_with_safe_value(); } } @@ -485,7 +487,7 @@ where if call_may_mutate { for a in &n.args { for_each_id_ref_in_expr(&a.expr, &mut |id| { - self.data.mark_property_mutation(id.to_id()); + self.data.mark_property_mutation(id.hashed_id()); }); } } @@ -493,7 +495,7 @@ where for arg in &n.args { for_each_id_ref_in_expr(&arg.expr, &mut |arg| { - self.data.var_or_default(arg.to_id()).mark_used_as_arg(); + self.data.var_or_default(arg.hashed_id()).mark_used_as_arg(); }) } @@ -504,7 +506,7 @@ where } Expr::Member(m) if !m.obj.is_ident() => { for_each_id_ref_in_expr(&m.obj, &mut |id| { - self.data.var_or_default(id.to_id()).mark_used_as_ref() + self.data.var_or_default(id.hashed_id()).mark_used_as_ref() }) } _ => {} @@ -651,12 +653,12 @@ where match d { DefaultDecl::Class(c) => { if let Some(i) = &c.ident { - self.data.var_or_default(i.to_id()).prevent_inline(); + self.data.var_or_default(i.hashed_id()).prevent_inline(); } } DefaultDecl::Fn(f) => { if let Some(i) = &f.ident { - self.data.var_or_default(i.to_id()).prevent_inline(); + self.data.var_or_default(i.hashed_id()).prevent_inline(); } } _ => {} @@ -683,15 +685,20 @@ where match &n.decl { Decl::Class(c) => { - self.data.var_or_default(c.ident.to_id()).prevent_inline(); + self.data + .var_or_default(c.ident.hashed_id()) + .prevent_inline(); } Decl::Fn(f) => { - self.data.var_or_default(f.ident.to_id()).prevent_inline(); + self.data + .var_or_default(f.ident.hashed_id()) + .prevent_inline(); } Decl::Var(v) => { let ids = find_pat_ids(v); for id in ids { + let id = hashed_id_from_id(&id); self.data.var_or_default(id).mark_as_exported(); } } @@ -713,7 +720,7 @@ where match &n.orig { ModuleExportName::Ident(orig) => { self.report_usage(orig); - let v = self.data.var_or_default(orig.to_id()); + let v = self.data.var_or_default(orig.hashed_id()); v.prevent_inline(); v.mark_used_as_ref(); } @@ -765,7 +772,7 @@ where if e.spread.is_some() { for_each_id_ref_in_expr(&e.expr, &mut |i| { self.data - .var_or_default(i.to_id()) + .var_or_default(i.hashed_id()) .mark_indexed_with_dynamic_key(); }); } @@ -783,12 +790,13 @@ where .declare_decl(&n.ident, Some(Value::Known(Type::Obj)), None, true); if n.function.body.is_empty() { - self.data.var_or_default(n.ident.to_id()).mark_as_pure_fn(); + self.data + .var_or_default(n.ident.hashed_id()) + .mark_as_pure_fn(); } - let id = n.ident.to_id(); - self.used_recursively - .insert(id.clone(), RecursiveUsage::FnOrClass); + let id = n.ident.hashed_id(); + self.used_recursively.insert(id, RecursiveUsage::FnOrClass); n.visit_children_with(self); self.used_recursively.remove(&id); @@ -803,10 +811,10 @@ where }, ) { if v.is_none() { - v = Some(self.data.var_or_default(n.ident.to_id())); + v = Some(self.data.var_or_default(n.ident.hashed_id())); } - v.as_mut().unwrap().add_infects_to(id.clone()); + v.as_mut().unwrap().add_infects_to(id); } } } @@ -818,11 +826,11 @@ where fn visit_fn_expr(&mut self, n: &FnExpr) { if let Some(n_id) = &n.ident { self.data - .var_or_default(n_id.to_id()) + .var_or_default(n_id.hashed_id()) .mark_declared_as_fn_expr(); self.used_recursively - .insert(n_id.to_id(), RecursiveUsage::FnOrClass); + .insert(n_id.hashed_id(), RecursiveUsage::FnOrClass); n.visit_children_with(self); @@ -837,13 +845,13 @@ where }, ) { if v.is_none() { - v = Some(self.data.var_or_default(n_id.to_id())); + v = Some(self.data.var_or_default(n_id.hashed_id())); } v.as_mut().unwrap().add_infects_to(id); } } - self.used_recursively.remove(&n_id.to_id()); + self.used_recursively.remove(&n_id.hashed_id()); } else { n.visit_children_with(self); } @@ -1004,7 +1012,7 @@ where if let JSXElementName::Ident(i) = n { self.with_ctx(ctx).report_usage(i); self.data - .var_or_default(i.to_id()) + .var_or_default(i.hashed_id()) .mark_used_as_jsx_callee(); } } @@ -1024,7 +1032,7 @@ where } for_each_id_ref_in_expr(&e.obj, &mut |obj| { - let v = self.data.var_or_default(obj.to_id()); + let v = self.data.var_or_default(obj.hashed_id()); v.mark_has_property_access(); if let MemberProp::Computed(prop) = &e.prop { @@ -1049,7 +1057,7 @@ where match &*member_expr.obj { Expr::Member(member_expr) => is_root_of_member_expr_declared(member_expr, data), Expr::Ident(ident) => data - .get_var_data(ident.to_id()) + .get_var_data(ident.hashed_id()) .map(|var| var.is_declared()) .unwrap_or(false), @@ -1108,7 +1116,7 @@ where if let Some(args) = &n.args { for a in args { for_each_id_ref_in_expr(&a.expr, &mut |id| { - self.data.mark_property_mutation(id.to_id()); + self.data.mark_property_mutation(id.hashed_id()); }); } } @@ -1240,7 +1248,7 @@ where for_each_id_ref_in_expr(&e.expr, &mut |i| { self.data - .var_or_default(i.to_id()) + .var_or_default(i.hashed_id()) .mark_indexed_with_dynamic_key(); }); } @@ -1393,10 +1401,10 @@ where }, ) { if v.is_none() { - v = Some(self.data.var_or_default(var.to_id())); + v = Some(self.data.var_or_default(var.hashed_id())); } - v.as_mut().unwrap().add_infects_to(id.clone()); + v.as_mut().unwrap().add_infects_to(id); } } } @@ -1454,9 +1462,9 @@ where definite: false, .. } => { - let id = id.to_id(); + let id = id.hashed_id(); self.used_recursively.insert( - id.clone(), + id, RecursiveUsage::Var { can_ignore: !init.may_have_side_effects(self.expr_ctx), }, @@ -1471,7 +1479,7 @@ where init: None, .. } => { - self.data.var_or_default(id.to_id()).mark_as_lazy_init(); + self.data.var_or_default(id.hashed_id()).mark_as_lazy_init(); return; } _ => (), diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs index c04a4bb010f3..b3e5d495b002 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs @@ -4,7 +4,7 @@ use swc_ecma_ast::*; use swc_ecma_utils::{Type, Value}; use super::{ctx::Ctx, ScopeKind}; -use crate::alias::Access; +use crate::alias::AccessKind; pub trait Storage: Sized + Default { type ScopeData: ScopeDataLike; @@ -20,13 +20,13 @@ pub trait Storage: Sized + Default { fn top_scope(&mut self) -> &mut Self::ScopeData; - fn var_or_default(&mut self, id: Id) -> &mut Self::VarData; + fn var_or_default(&mut self, id: HashedId) -> &mut Self::VarData; fn merge(&mut self, kind: ScopeKind, child: Self); - fn report_usage(&mut self, ctx: Ctx, i: Id); + fn report_usage(&mut self, ctx: Ctx, i: HashedId); - fn report_assign(&mut self, ctx: Ctx, i: Id, is_op: bool, ty: Value); + fn report_assign(&mut self, ctx: Ctx, i: HashedId, is_op: bool, ty: Value); fn declare_decl( &mut self, @@ -39,9 +39,9 @@ pub trait Storage: Sized + Default { fn get_initialized_cnt(&self) -> usize; fn truncate_initialized_cnt(&mut self, len: usize); - fn mark_property_mutation(&mut self, id: Id); + fn mark_property_mutation(&mut self, id: HashedId); - fn get_var_data(&self, id: Id) -> Option<&Self::VarData>; + fn get_var_data(&self, id: HashedId) -> Option<&Self::VarData>; } pub trait ScopeDataLike: Sized + Default + Clone { @@ -80,7 +80,7 @@ pub trait VarDataLike: Sized { fn mark_used_as_ref(&mut self); - fn add_infects_to(&mut self, other: Access); + fn add_infects_to(&mut self, other: (HashedId, AccessKind)); fn prevent_inline(&mut self); diff --git a/crates/swc_ecma_utils/src/ident.rs b/crates/swc_ecma_utils/src/ident.rs index f267c876306c..b19ee98b2d4f 100644 --- a/crates/swc_ecma_utils/src/ident.rs +++ b/crates/swc_ecma_utils/src/ident.rs @@ -1,14 +1,17 @@ use swc_atoms::Atom; use swc_common::SyntaxContext; -use swc_ecma_ast::{unsafe_id_from_ident, BindingIdent, Id, Ident, UnsafeId}; +use swc_ecma_ast::{unsafe_id_from_ident, BindingIdent, HashedId, Id, Ident, UnsafeId}; pub trait IdentLike: Sized + Send + Sync + 'static { + type Id; fn from_ident(i: &Ident) -> Self; - fn to_id(&self) -> Id; - fn into_id(self) -> Id; + fn to_id(&self) -> Self::Id; + fn into_id(self) -> Self::Id; } impl IdentLike for Atom { + type Id = Id; + fn from_ident(i: &Ident) -> Self { i.sym.clone() } @@ -23,6 +26,8 @@ impl IdentLike for Atom { } impl IdentLike for BindingIdent { + type Id = Id; + fn from_ident(i: &Ident) -> Self { i.clone().into() } @@ -37,6 +42,8 @@ impl IdentLike for BindingIdent { } impl IdentLike for (Atom, SyntaxContext) { + type Id = Id; + #[inline] fn from_ident(i: &Ident) -> Self { (i.sym.clone(), i.ctxt) @@ -54,6 +61,8 @@ impl IdentLike for (Atom, SyntaxContext) { } impl IdentLike for Ident { + type Id = Id; + #[inline] fn from_ident(i: &Ident) -> Self { Ident::new(i.sym.clone(), i.span, i.ctxt) @@ -71,6 +80,8 @@ impl IdentLike for Ident { } impl IdentLike for UnsafeId { + type Id = Id; + fn from_ident(i: &Ident) -> Self { unsafe { unsafe_id_from_ident(i) } } @@ -83,3 +94,19 @@ impl IdentLike for UnsafeId { unreachable!("UnsafeId.into_id() is not allowed because it is very likely to be unsafe") } } + +impl IdentLike for HashedId { + type Id = HashedId; + + fn from_ident(i: &Ident) -> Self { + i.hashed_id() + } + + fn to_id(&self) -> Self::Id { + *self + } + + fn into_id(self) -> Self::Id { + self + } +} diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs index 2771b746eb82..451cc6e242a6 100644 --- a/crates/swc_ecma_utils/src/lib.rs +++ b/crates/swc_ecma_utils/src/lib.rs @@ -143,6 +143,7 @@ impl Visit for IdentRefFinder<'_> { match *e { Expr::Ident(ref i) if i.ctxt == self.ident.ctxt && i.sym == self.ident.sym => { + Expr::Ident(ref i) if i.sym == self.ident.sym && i.ctxt == self.ident.ctxt => { self.found = true; } _ => {} From a8e9e16f3348646b4773a3746a54eecb5fe2c5e5 Mon Sep 17 00:00:00 2001 From: bohan Date: Thu, 31 Jul 2025 22:15:43 +0800 Subject: [PATCH 2/4] perf(es/minifier): use intern --- Cargo.lock | 1 + crates/swc_ecma_ast/Cargo.toml | 1 + crates/swc_ecma_ast/src/ident.rs | 59 +++++++--- crates/swc_ecma_ast/src/lib.rs | 4 +- .../src/compress/hoist_decls.rs | 18 +-- .../src/compress/optimize/arguments.rs | 2 +- .../src/compress/optimize/bools.rs | 2 +- .../src/compress/optimize/conditionals.rs | 2 +- .../src/compress/optimize/dead_code.rs | 4 +- .../src/compress/optimize/evaluate.rs | 4 +- .../src/compress/optimize/iife.rs | 37 +++--- .../src/compress/optimize/inline.rs | 63 +++++----- .../src/compress/optimize/mod.rs | 34 +++--- .../src/compress/optimize/ops.rs | 5 +- .../src/compress/optimize/props.rs | 10 +- .../src/compress/optimize/rest_params.rs | 2 +- .../src/compress/optimize/sequences.rs | 51 +++++---- .../src/compress/optimize/unused.rs | 22 ++-- .../src/compress/optimize/util.rs | 33 +++--- .../src/compress/pure/vars.rs | 2 +- crates/swc_ecma_minifier/src/eval.rs | 7 +- crates/swc_ecma_minifier/src/metadata/mod.rs | 19 +-- crates/swc_ecma_minifier/src/mode.rs | 4 +- crates/swc_ecma_minifier/src/program_data.rs | 28 ++--- .../swc_ecma_usage_analyzer/src/alias/mod.rs | 25 ++-- .../src/analyzer/mod.rs | 108 +++++++++--------- .../src/analyzer/storage.rs | 14 +-- crates/swc_ecma_utils/src/ident.rs | 8 +- 28 files changed, 309 insertions(+), 260 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cc941edae8b..1237f43d4b3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4955,6 +4955,7 @@ dependencies = [ "arbitrary", "bitflags 2.6.0", "bytecheck 0.8.1", + "hashbrown 0.14.5", "is-macro", "num-bigint", "once_cell", diff --git a/crates/swc_ecma_ast/Cargo.toml b/crates/swc_ecma_ast/Cargo.toml index cc693937caac..3984df0cdfaa 100644 --- a/crates/swc_ecma_ast/Cargo.toml +++ b/crates/swc_ecma_ast/Cargo.toml @@ -39,6 +39,7 @@ shrink-to-fit = [ arbitrary = { workspace = true, features = ["derive"], optional = true } bitflags = { workspace = true } bytecheck = { workspace = true, optional = true } +hashbrown = { workspace = true } is-macro = { workspace = true } num-bigint = { workspace = true, features = ["serde"] } once_cell = { workspace = true } diff --git a/crates/swc_ecma_ast/src/ident.rs b/crates/swc_ecma_ast/src/ident.rs index 13cb68256516..2c4ae559133c 100644 --- a/crates/swc_ecma_ast/src/ident.rs +++ b/crates/swc_ecma_ast/src/ident.rs @@ -1,5 +1,6 @@ use std::{ borrow::Cow, + cell::RefCell, fmt::Display, hash::{Hash, Hasher}, ops::{Deref, DerefMut}, @@ -241,13 +242,6 @@ struct Align64(pub(crate) T); const T: bool = true; const F: bool = false; -pub fn hashed_id_from_id(id: &Id) -> HashedId { - let mut hasher = rustc_hash::FxHasher::default(); - id.0.hash(&mut hasher); - id.1.hash(&mut hasher); - HashedId(hasher.finish()) -} - impl Ident { /// In `op`, [EqIgnoreSpan] of [Ident] will ignore the syntax context. pub fn within_ignored_ctxt(op: F) -> Ret @@ -269,13 +263,6 @@ impl Ident { (self.sym.clone(), self.ctxt) } - pub fn hashed_id(&self) -> HashedId { - let mut hasher = rustc_hash::FxHasher::default(); - self.sym.hash(&mut hasher); - self.ctxt.hash(&mut hasher); - HashedId(hasher.finish()) - } - #[inline] pub fn is_valid_ascii_start(c: u8) -> bool { debug_assert!(c.is_ascii()); @@ -543,7 +530,49 @@ pub unsafe fn unsafe_id_from_ident(id: &Ident) -> UnsafeId { pub type Id = (Atom, SyntaxContext); #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] -pub struct HashedId(u64); +pub struct IdIdx(u32); + +impl IdIdx { + pub fn new(atom: &Atom, ctxt: SyntaxContext) -> Self { + GLOBAL_ID_LIST.with(|ids| ids.borrow_mut().intern(atom, ctxt)) + } + + #[inline(always)] + pub fn from_ident(i: &Ident) -> Self { + Self::new(&i.sym, i.ctxt) + } +} + +#[derive(Default)] +pub struct Ids { + map: hashbrown::HashMap, +} + +impl Ids { + pub fn intern(&mut self, atom: &Atom, ctxt: SyntaxContext) -> IdIdx { + let mut hasher = rustc_hash::FxHasher::default(); + atom.hash(&mut hasher); + ctxt.hash(&mut hasher); + let hash = hasher.finish(); + + let len = self.map.len(); + + let (_, idx) = self + .map + .raw_entry_mut() + .from_hash(hash, |id| id.1 == ctxt && id.0.eq(atom)) + .or_insert_with(|| { + let idx = IdIdx(len as u32); + let id = (atom.clone(), ctxt); + (id, idx) + }); + *idx + } +} + +thread_local! { + static GLOBAL_ID_LIST: RefCell = Default::default(); +} impl Take for Ident { fn dummy() -> Self { diff --git a/crates/swc_ecma_ast/src/lib.rs b/crates/swc_ecma_ast/src/lib.rs index c42d2cc93b43..3d9158158a8b 100644 --- a/crates/swc_ecma_ast/src/lib.rs +++ b/crates/swc_ecma_ast/src/lib.rs @@ -23,8 +23,8 @@ pub use self::{ expr::*, function::{Function, Param, ParamOrTsParamProp}, ident::{ - hashed_id_from_id, unsafe_id, unsafe_id_from_ident, BindingIdent, EsReserved, HashedId, Id, - Ident, IdentName, PrivateName, UnsafeId, + unsafe_id, unsafe_id_from_ident, BindingIdent, EsReserved, Id, IdIdx, Ident, IdentName, + Ids, PrivateName, UnsafeId, }, jsx::{ JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXClosingElement, JSXClosingFragment, diff --git a/crates/swc_ecma_minifier/src/compress/hoist_decls.rs b/crates/swc_ecma_minifier/src/compress/hoist_decls.rs index 9dfd40a328a3..e852144cdf5e 100644 --- a/crates/swc_ecma_minifier/src/compress/hoist_decls.rs +++ b/crates/swc_ecma_minifier/src/compress/hoist_decls.rs @@ -55,13 +55,12 @@ impl Hoister<'_> { 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 = find_pat_ids(&var.decls); + let ids: Vec = find_pat_ids(&var.decls); if ids.iter().any(|id| { - let id = hashed_id_from_id(id); self.data .vars - .get(&id) + .get(id) .map(|v| !v.flags.contains(VarUsageInfoFlags::USED_ABOVE_DECL)) .unwrap_or(false) }) { @@ -96,7 +95,7 @@ impl Hoister<'_> { let mut var_decls = Vec::new(); let mut fn_decls = Vec::with_capacity(stmts.len()); let mut new_stmts = Vec::with_capacity(stmts.len()); - let mut done: FxHashSet = FxHashSet::default(); + let mut done: FxHashSet = FxHashSet::default(); let mut found_non_var_decl = false; for stmt in stmts.take() { @@ -123,15 +122,15 @@ impl Hoister<'_> { let ids: Vec = find_pat_ids(&decl.name); for id in ids { - let hashed_id = id.hashed_id(); - if done.insert(hashed_id) { + let idx = IdIdx::from_ident(&id); + if done.insert(idx) { // If the enclosing function declares parameter with same // name, we can drop a varaible. if decl.init.is_none() && self .data .vars - .get(&hashed_id) + .get(&idx) .map(|v| { v.flags.contains( VarUsageInfoFlags::DECLARED_AS_FN_PARAM, @@ -211,13 +210,14 @@ impl Hoister<'_> { let preserve = match &decl.name { Pat::Ident(name) => { + let id = IdIdx::from_ident(&name.id); // If the enclosing function declares parameter with same // name, we can drop a varaible. (If it's side-effect free). if decl.init.is_none() && self .data .vars - .get(&name.hashed_id()) + .get(&id) .map(|v| { v.flags.contains( VarUsageInfoFlags::DECLARED_AS_FN_PARAM, @@ -228,7 +228,7 @@ impl Hoister<'_> { return false; } - done.insert(name.hashed_id()) + done.insert(id) } _ => true, }; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs b/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs index cfeacd4d1e81..686764203bba 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs @@ -77,7 +77,7 @@ impl Optimizer<'_> { Pat::Ident(i) => self .data .vars - .get(&i.id.hashed_id()) + .get(&IdIdx::from_ident(&i.id)) .map(|v| v.declared_count >= 2) .unwrap_or(false), _ => true, diff --git a/crates/swc_ecma_minifier/src/compress/optimize/bools.rs b/crates/swc_ecma_minifier/src/compress/optimize/bools.rs index 061e9c10ae5f..1be3cbcd1025 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/bools.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/bools.rs @@ -22,7 +22,7 @@ impl Optimizer<'_> { ) if &**l_v == "undefined" => { // TODO? if let Expr::Ident(arg) = &**arg { - if let Some(usage) = o.data.vars.get(&arg.hashed_id()) { + if let Some(usage) = o.data.vars.get(&IdIdx::from_ident(arg)) { if !usage.flags.contains(VarUsageInfoFlags::DECLARED) { return false; } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs b/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs index c3b09564e9ce..27bc2f8270ac 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs @@ -404,7 +404,7 @@ impl Optimizer<'_> { let side_effect_free = self .data .vars - .get(&cons_callee.hashed_id()) + .get(&IdIdx::from_ident(cons_callee)) .map(|v| { v.flags.contains( VarUsageInfoFlags::IS_FN_LOCAL.union(VarUsageInfoFlags::DECLARED), diff --git a/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs b/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs index 59e0d74372a9..5021650002cf 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs @@ -58,7 +58,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&lhs.hashed_id()) + .get(&IdIdx::from_ident(lhs)) .map(|var| { var.flags.contains( VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL), @@ -97,7 +97,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&lhs.hashed_id()) + .get(&IdIdx::from_ident(lhs)) .map(|var| { var.flags.contains( VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL), diff --git a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs index 3fd80c7c4b1a..c88d07a81b36 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs @@ -42,7 +42,7 @@ impl Optimizer<'_> { }) = e { if let Expr::Ident(obj) = &**obj { - let hashed_id = obj.hashed_id(); + let hashed_id = IdIdx::from_ident(obj); let metadata = *self.functions.get(&hashed_id)?; let usage = self.data.vars.get(&hashed_id)?; @@ -101,7 +101,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&i.hashed_id()) + .get(&IdIdx::from_ident(i)) .map(|var| var.flags.contains(VarUsageInfoFlags::DECLARED)) .unwrap_or(false) { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs index 258ca7b0b75d..06c0f3dc2b38 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs @@ -183,7 +183,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&ident.hashed_id()) + .get(&IdIdx::from_ident(ident)) .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)) .is_some() { @@ -202,7 +202,7 @@ impl Optimizer<'_> { if param.sym == "arguments" { continue; } - if let Some(usage) = self.data.vars.get(¶m.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(¶m.id)) { if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { continue; } @@ -225,7 +225,7 @@ impl Optimizer<'_> { param.id.sym, param.id.ctxt ); - vars.insert(param.hashed_id(), arg.clone()); + vars.insert(IdIdx::from_ident(param), arg.clone()); } else { trace_op!( "iife: Trying to inline argument ({}{:?}) (not inlinable)", @@ -240,13 +240,14 @@ impl Optimizer<'_> { param.id.ctxt ); - vars.insert(param.hashed_id(), Expr::undefined(param.span())); + vars.insert(IdIdx::from_ident(param), Expr::undefined(param.span())); } } Pat::Rest(rest_pat) => { if let Pat::Ident(param_id) = &*rest_pat.arg { - if let Some(usage) = self.data.vars.get(¶m_id.hashed_id()) { + let id = IdIdx::from_ident(param_id); + if let Some(usage) = self.data.vars.get(&id) { if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || usage.ref_count != 1 || !usage.flags.contains(VarUsageInfoFlags::HAS_PROPERTY_ACCESS) @@ -269,7 +270,7 @@ impl Optimizer<'_> { } vars.insert( - param_id.hashed_id(), + id, ArrayLit { span: param_id.span, elems: e @@ -350,10 +351,11 @@ impl Optimizer<'_> { ident: Some(ident), .. }) = callee { + let id = IdIdx::from_ident(ident); if self .data .vars - .get(&ident.hashed_id()) + .get(&id) .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)) .is_some() { @@ -367,7 +369,8 @@ impl Optimizer<'_> { // We check for parameter and argument for (idx, param) in params.iter_mut().enumerate() { if let Pat::Ident(param) = &mut **param { - if let Some(usage) = self.data.vars.get(¶m.hashed_id()) { + let id = IdIdx::from_ident(¶m.id); + if let Some(usage) = self.data.vars.get(&id) { if usage.ref_count == 0 { removed.push(idx); } @@ -413,7 +416,7 @@ impl Optimizer<'_> { pub(super) fn inline_vars_in_node( &mut self, n: &mut N, - mut vars: FxHashMap>, + mut vars: FxHashMap>, ) where N: for<'aa> VisitMutWith>, { @@ -509,7 +512,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&i.hashed_id()) + .get(&IdIdx::from_ident(i)) .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)) .is_some() { @@ -753,7 +756,7 @@ impl Optimizer<'_> { // Don't create top-level variables. if !self.may_add_ident() { for pid in param_ids.clone() { - if let Some(usage) = self.data.vars.get(&pid.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(pid)) { if usage.ref_count > 1 || usage.assign_count > 0 || usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) @@ -801,7 +804,7 @@ impl Optimizer<'_> { match &decl.name { Pat::Ident(id) if id.sym == "arguments" => return false, Pat::Ident(id) => { - if self.vars.has_pending_inline_for(id.hashed_id()) { + if self.vars.has_pending_inline_for(IdIdx::from_ident(id)) { log_abort!( "iife: [x] Cannot inline because pending inline of `{}`", id.id @@ -899,10 +902,10 @@ impl Optimizer<'_> { if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) { if params_len != 0 { - let captured = idents_captured_by::<_, HashedId>(body); + let captured = idents_captured_by::<_, IdIdx>(body); for param in param_ids { - if captured.contains(¶m.hashed_id()) { + if captured.contains(&IdIdx::from_ident(param)) { log_abort!( "iife: [x] Cannot inline because of the capture of `{}`", param @@ -945,7 +948,7 @@ impl Optimizer<'_> { let no_arg = arg.is_none(); if let Some(arg) = arg { - let hashed_id = param.hashed_id(); + let hashed_id = IdIdx::from_ident(param); if let Some(usage) = self.data.vars.get_mut(&hashed_id) { if usage.ref_count == 1 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) @@ -1003,7 +1006,7 @@ impl Optimizer<'_> { let mut arg = args.get_mut(idx).map(|arg| arg.expr.take()); if let Some(arg) = &mut arg { - let hashed_id = param.hashed_id(); + let hashed_id = IdIdx::from_ident(param); if let Some(usage) = self.data.vars.get_mut(&hashed_id) { if usage.ref_count == 1 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) @@ -1588,7 +1591,7 @@ impl Optimizer<'_> { let mut substitutions = FxHashMap::default(); for (param, arg) in arrow.params.iter().zip(&call.args) { if let Pat::Ident(ident) = param { - substitutions.insert(ident.hashed_id(), arg.expr.clone()); + substitutions.insert(IdIdx::from_ident(ident), arg.expr.clone()); } } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs index 5fdd378664ca..a08b6e9dab34 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs @@ -58,7 +58,7 @@ impl Optimizer<'_> { } } - if let Some(usage) = self.data.vars.get(&ident.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(ident)) { let ref_count = usage.ref_count - u32::from(can_drop && usage.ref_count > 1); if !usage.flags.contains(VarUsageInfoFlags::VAR_INITIALIZED) { return; @@ -96,7 +96,7 @@ impl Optimizer<'_> { // No use => dropped if ref_count == 0 { - self.mode.store(ident.hashed_id(), &*init); + self.mode.store(IdIdx::from_ident(ident), &*init); if init.may_have_side_effects(self.ctx.expr_ctx) { // TODO: Inline partially @@ -112,7 +112,7 @@ impl Optimizer<'_> { let mut inlined_into_init = false; - let hashed_id = ident.hashed_id(); + let hashed_id = IdIdx::from_ident(ident); // We inline arrays partially if it's pure (all elements are literal), and not // modified. @@ -149,7 +149,7 @@ impl Optimizer<'_> { ); self.vars .lits_for_array_access - .insert(ident.hashed_id(), Box::new(init.clone())); + .insert(IdIdx::from_ident(ident), Box::new(init.clone())); } } } @@ -183,17 +183,19 @@ impl Optimizer<'_> { if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { match init { Expr::Fn(..) | Expr::Arrow(..) | Expr::Class(..) => { - self.typeofs.insert(ident.hashed_id(), atom!("function")); + self.typeofs + .insert(IdIdx::from_ident(ident), atom!("function")); } Expr::Array(..) | Expr::Object(..) => { - self.typeofs.insert(ident.hashed_id(), atom!("object")); + self.typeofs + .insert(IdIdx::from_ident(ident), atom!("object")); } _ => {} } } if !usage.mutated() { - self.mode.store(ident.hashed_id(), &*init); + self.mode.store(IdIdx::from_ident(ident), &*init); } if usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) { @@ -214,7 +216,7 @@ impl Optimizer<'_> { Expr::Ident(id) if !id.eq_ignore_span(ident) => { if !usage.flags.contains(VarUsageInfoFlags::ASSIGNED_FN_LOCAL) { false - } else if let Some(u) = self.data.vars.get(&id.hashed_id()) { + } else if let Some(u) = self.data.vars.get(&IdIdx::from_ident(id)) { let mut should_inline = !u.flags.contains(VarUsageInfoFlags::REASSIGNED) && u.flags.contains(VarUsageInfoFlags::DECLARED); @@ -276,7 +278,7 @@ impl Optimizer<'_> { } else { self.vars .lits_for_cmp - .insert(ident.hashed_id(), init.clone().into()); + .insert(IdIdx::from_ident(ident), init.clone().into()); false } } @@ -316,7 +318,7 @@ impl Optimizer<'_> { } = **usage; let mut inc_usage = || { if let Expr::Ident(i) = &*init { - if let Some(u) = self.data.vars.get_mut(&i.hashed_id()) { + if let Some(u) = self.data.vars.get_mut(&IdIdx::from_ident(i)) { u.flags |= flags & VarUsageInfoFlags::USED_AS_ARG; u.flags |= flags & VarUsageInfoFlags::USED_AS_REF; u.flags |= flags & VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY; @@ -430,10 +432,7 @@ impl Optimizer<'_> { } Expr::Fn(f) => { - let excluded: Vec = find_pat_ids::<_, Ident>(&f.function.params) - .iter() - .map(|i| i.hashed_id()) - .collect(); + let excluded: Vec = find_pat_ids::<_, IdIdx>(&f.function.params); for id in idents_used_by(&f.function.params) { if excluded.contains(&id) { @@ -450,10 +449,7 @@ impl Optimizer<'_> { } Expr::Arrow(f) => { - let excluded: Vec = find_pat_ids::<_, Ident>(&f.params) - .iter() - .map(|i| i.hashed_id()) - .collect(); + let excluded: Vec = find_pat_ids::<_, IdIdx>(&f.params); for id in idents_used_by(&f.params) { if excluded.contains(&id) { @@ -484,7 +480,7 @@ impl Optimizer<'_> { return; } - if let Some(init_usage) = self.data.vars.get(&id.hashed_id()) { + if let Some(init_usage) = self.data.vars.get(&IdIdx::from_ident(id)) { if init_usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || !init_usage.flags.contains(VarUsageInfoFlags::DECLARED) { @@ -550,10 +546,10 @@ impl Optimizer<'_> { // block_scoping pass. // If the function captures the environment, we // can't inline it. - let params: Vec = find_pat_ids(&f.function.params); + let params: Vec = find_pat_ids(&f.function.params); if !params.is_empty() { - let captured = idents_captured_by::<_, HashedId>(&f.function.body); + let captured = idents_captured_by::<_, IdIdx>(&f.function.body); for param in params { if captured.contains(¶m) { @@ -582,9 +578,9 @@ impl Optimizer<'_> { ); self.changed = true; - self.vars - .vars_for_inlining - .insert(ident.take().hashed_id(), init.take().into()); + let ident = ident.take(); + let id = IdIdx::from_ident(&ident); + self.vars.vars_for_inlining.insert(id, init.take().into()); } } } @@ -643,7 +639,7 @@ impl Optimizer<'_> { return; } - let hashed_id = i.hashed_id(); + let hashed_id = IdIdx::from_ident(i); if let Some(usage) = self.data.vars.get(&hashed_id) { if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { trace_op!("typeofs: Storing typeof `{}{:?}`", i.sym, i.ctxt); @@ -704,7 +700,7 @@ impl Optimizer<'_> { return; } - if let Some(usage) = self.data.vars.get(&i.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&i)) { if usage .flags .contains(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM) @@ -770,7 +766,7 @@ impl Optimizer<'_> { } self.vars.simple_functions.insert( - i.hashed_id(), + IdIdx::from_ident(&i), FnExpr { ident: None, function: f.function.clone(), @@ -866,7 +862,7 @@ impl Optimizer<'_> { } }; - self.vars.vars_for_inlining.insert(i.hashed_id(), e); + self.vars.vars_for_inlining.insert(IdIdx::from_ident(&i), e); } else { log_abort!("inline: [x] Usage: {:?}", usage); } @@ -884,7 +880,7 @@ impl Optimizer<'_> { if let MemberProp::Computed(prop) = &mut me.prop { if let Expr::Lit(Lit::Num(..)) = &*prop.expr { if let Expr::Ident(obj) = &*me.obj { - let new = self.vars.lits_for_array_access.get(&obj.hashed_id()); + let new = self.vars.lits_for_array_access.get(&IdIdx::from_ident(obj)); if let Some(new) = new { report_change!("inline: Inlined array access"); @@ -897,7 +893,7 @@ impl Optimizer<'_> { } } Expr::Ident(i) => { - let id = i.hashed_id(); + let id = IdIdx::from_ident(i); if let Some(mut value) = self .vars .lits @@ -931,9 +927,8 @@ impl Optimizer<'_> { let new_ctxt = *new_ctxt; - if let Some(usage) = self.data.vars.get(&hashed_id_from_id(&id)).cloned() { - let new_id = (id.0.clone(), new_ctxt); - let new_id = hashed_id_from_id(&new_id); + if let Some(usage) = self.data.vars.get(&IdIdx::new(&id.0, id.1)).cloned() { + let new_id = IdIdx::new(&id.0, new_ctxt); self.data.vars.insert(new_id, usage); } @@ -952,7 +947,7 @@ impl Optimizer<'_> { return; } - let hashed_id = i.hashed_id(); + let hashed_id = IdIdx::from_ident(i); // Check without cloning if let Some(value) = self.vars.vars_for_inlining.get(&hashed_id) { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index 0c8cd5392b97..a5d24b4b60ed 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs @@ -227,7 +227,7 @@ struct Optimizer<'a> { vars: Vars, - typeofs: Box>, + typeofs: Box>, /// This information is created by analyzing identifier usages. /// /// This is calculated multiple time, but only once per one @@ -237,7 +237,7 @@ struct Optimizer<'a> { mode: &'a dyn Mode, - functions: Box>, + functions: Box>, } #[derive(Default)] @@ -245,33 +245,33 @@ struct Vars { /// Cheap to clone. /// /// Used for inlining. - lits: FxHashMap>, + lits: FxHashMap>, /// Used for `hoist_props`. - hoisted_props: Box>, + hoisted_props: Box>, /// Literals which are cheap to clone, but not sure if we can inline without /// making output bigger. /// /// https://github.com/swc-project/swc/issues/4415 - lits_for_cmp: FxHashMap>, + lits_for_cmp: FxHashMap>, /// This stores [Expr::Array] if all elements are literals. - lits_for_array_access: FxHashMap>, + lits_for_array_access: FxHashMap>, /// Used for copying functions. /// /// We use this to distinguish [Callee::Expr] from other [Expr]s. - simple_functions: FxHashMap>, - vars_for_inlining: FxHashMap>, + simple_functions: FxHashMap>, + vars_for_inlining: FxHashMap>, /// Variables which should be removed by [Finalizer] because of the order of /// visit. - removed: FxHashSet, + removed: FxHashSet, } impl Vars { - fn has_pending_inline_for(&self, id: HashedId) -> bool { + fn has_pending_inline_for(&self, id: IdIdx) -> bool { self.lits.contains_key(&id) || self.vars_for_inlining.contains_key(&id) } @@ -344,7 +344,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&id.hashed_id()) + .get(&IdIdx::from_ident(id)) .is_some_and(|v| v.flags.contains(VarUsageInfoFlags::EXPORTED)) { return false; @@ -827,7 +827,7 @@ impl Optimizer<'_> { if let Expr::Ident(callee) = &**callee { if self.options.reduce_vars && self.options.side_effects { - if let Some(usage) = self.data.vars.get(&callee.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(callee)) { if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) && usage.flags.contains(VarUsageInfoFlags::PURE_FN) { @@ -894,7 +894,7 @@ impl Optimizer<'_> { right, .. }) => { - let old = i.id.hashed_id(); + let old = IdIdx::from_ident(&i.id); self.store_var_for_inlining(&mut i.id, right, true); if i.is_dummy() && self.options.unused { @@ -1809,7 +1809,7 @@ impl VisitMut for Optimizer<'_> { .. }) => { if let Some(i) = left.as_ident_mut() { - let old = i.hashed_id(); + let old = IdIdx::from_ident(&i.id); self.store_var_for_inlining(i, right, false); @@ -2045,7 +2045,7 @@ impl VisitMut for Optimizer<'_> { .entered(); self.functions - .entry(f.ident.hashed_id()) + .entry(IdIdx::from_ident(&f.ident)) .or_insert_with(|| FnMetadata::from(&*f.function)); self.drop_unused_params(&mut f.function.params); @@ -2064,7 +2064,7 @@ impl VisitMut for Optimizer<'_> { fn visit_mut_fn_expr(&mut self, e: &mut FnExpr) { if let Some(ident) = &e.ident { self.functions - .entry(ident.hashed_id()) + .entry(IdIdx::from_ident(ident)) .or_insert_with(|| FnMetadata::from(&*e.function)); } @@ -3010,7 +3010,7 @@ impl VisitMut for Optimizer<'_> { if let Some(Expr::Invalid(..)) = var.init.as_deref() { if let Pat::Ident(i) = &var.name { - if let Some(usage) = self.data.vars.get(&i.id.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&i.id)) { if usage .flags .contains(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM) diff --git a/crates/swc_ecma_minifier/src/compress/optimize/ops.rs b/crates/swc_ecma_minifier/src/compress/optimize/ops.rs index 09e8a1669385..566f5eae534d 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/ops.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/ops.rs @@ -18,7 +18,8 @@ impl Optimizer<'_> { match e.op { op!("===") | op!("==") | op!("!==") | op!("!=") => { if e.left.is_ident() && e.left.eq_ignore_span(&e.right) { - let hashed_id = e.left.as_ident().unwrap().hashed_id(); + let e_left_ident = e.left.as_ident().unwrap(); + let hashed_id = IdIdx::from_ident(e_left_ident); if let Some(t) = self.typeofs.get(&hashed_id) { match &**t { "object" | "function" => { @@ -249,7 +250,7 @@ impl Optimizer<'_> { { match &**arg { Expr::Ident(arg) => { - if let Some(value) = self.typeofs.get(&arg.hashed_id()).cloned() { + if let Some(value) = self.typeofs.get(&IdIdx::from_ident(arg)).cloned() { report_change!( "Converting typeof of variable to literal as we know the value" ); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/props.rs b/crates/swc_ecma_minifier/src/compress/optimize/props.rs index d04a3fd9fad3..844254372837 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/props.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/props.rs @@ -38,7 +38,7 @@ impl Optimizer<'_> { // If a variable is initialized multiple time, we currently don't do anything // smart. - let usage = self.data.vars.get(&name.hashed_id())?; + let usage = self.data.vars.get(&IdIdx::from_ident(&name.id))?; if usage.mutated() || usage.flags.intersects( VarUsageInfoFlags::USED_ABOVE_DECL @@ -73,7 +73,7 @@ impl Optimizer<'_> { let mut unknown_used_props = self .data .vars - .get(&name.hashed_id()) + .get(&IdIdx::from_ident(name)) .map(|v| v.accessed_props.clone()) .unwrap_or_default(); @@ -124,7 +124,7 @@ impl Optimizer<'_> { } if let Some(init) = n.init.as_deref() { - let hashed_id = name.hashed_id(); + let hashed_id = IdIdx::from_ident(name); self.mode.store(hashed_id, init); } @@ -176,7 +176,7 @@ impl Optimizer<'_> { self.vars .hoisted_props - .insert((name.hashed_id(), key), new_var_name); + .insert((IdIdx::from_ident(name), key), new_var_name); new_vars.push(new_var); } @@ -211,7 +211,7 @@ impl Optimizer<'_> { if let Some(value) = self .vars .hoisted_props - .get(&(obj.hashed_id(), sym.clone())) + .get(&(IdIdx::from_ident(obj), sym.clone())) .cloned() { report_change!("hoist_props: Inlining `{}.{}`", obj.sym, sym); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs b/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs index 85eb2001bbe1..9a821bfac9d3 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs @@ -41,7 +41,7 @@ impl Optimizer<'_> { // Get the identifier of the rest parameter let rest_id = match &*rest_pat.arg { - Pat::Ident(BindingIdent { id, .. }) => id.hashed_id(), + Pat::Ident(BindingIdent { id, .. }) => IdIdx::from_ident(id), _ => return, }; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs index 808354d56a9f..873a139df7d8 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs @@ -219,10 +219,10 @@ impl Optimizer<'_> { Expr::Assign(AssignExpr { op: op!("="), .. }) ) }) { - let ids_used_by_exprs: FxHashSet = + let ids_used_by_exprs: FxHashSet = idents_used_by_ignoring_nested(&exprs); - let ids_used_by_first_expr: FxHashSet = + let ids_used_by_first_expr: FxHashSet = idents_used_by_ignoring_nested(&*e.first_expr_mut()); let has_conflict = ids_used_by_exprs @@ -391,7 +391,7 @@ impl Optimizer<'_> { .. })) => { if let Some(id) = obj.as_ident() { - if let Some(usage) = self.data.vars.get(&id.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(id)) { id.ctxt != self.ctx.expr_ctx.unresolved_ctxt && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) } else { @@ -833,7 +833,7 @@ impl Optimizer<'_> { && self .data .vars - .get(&a_id.id.hashed_id()) + .get(&IdIdx::from_ident(&a_id.id)) .map(|u| { !u.flags.intersects( VarUsageInfoFlags::INLINE_PREVENTED.union(VarUsageInfoFlags::DECLARED_AS_FN_EXPR) @@ -1125,9 +1125,9 @@ impl Optimizer<'_> { return false; }; - let e_hashed_id = e.hashed_id(); - if deps.contains(&(e_hashed_id, AccessKind::Reference)) - || deps.contains(&(e_hashed_id, AccessKind::Call)) + let e_id = IdIdx::from_ident(e); + if deps.contains(&(e_id, AccessKind::Reference)) + || deps.contains(&(e_id, AccessKind::Call)) { return false; } @@ -1263,12 +1263,12 @@ impl Optimizer<'_> { return false; } - let used_ids: FxHashSet = idents_used_by(&*e.right); + let used_ids: FxHashSet = idents_used_by(&*e.right); if used_ids.is_empty() { return true; } - if used_ids.len() != 1 || !used_ids.contains(&left_id.hashed_id()) { + if used_ids.len() != 1 || !used_ids.contains(&IdIdx::from_ident(left_id)) { log_abort!("bad used_ids"); return false; } @@ -1444,7 +1444,7 @@ impl Optimizer<'_> { } fn assignee_skippable_for_seq(&self, a: &Mergable, assignee: &Ident) -> bool { - let usgae = if let Some(usage) = self.data.vars.get(&assignee.hashed_id()) { + let usgae = if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(assignee)) { usage } else { return false; @@ -1683,7 +1683,7 @@ impl Optimizer<'_> { Mergable::FnDecl(a) => idents_used_by_ignoring_nested(&**a), Mergable::Drop => return Ok(false), }; - let obj_ids: FxHashSet = idents_used_by_ignoring_nested(obj); + let obj_ids: FxHashSet = idents_used_by_ignoring_nested(obj); if !obj_ids.is_disjoint(&a_ids) { return Ok(false); } @@ -1732,7 +1732,9 @@ impl Optimizer<'_> { }; if let Some(left_obj) = b_left.obj.as_ident() { - if let Some(usage) = self.data.vars.get(&left_obj.hashed_id()) { + if let Some(usage) = + self.data.vars.get(&IdIdx::from_ident(left_obj)) + { if left_obj.ctxt != self.ctx.expr_ctx.unresolved_ctxt && !usage .flags @@ -2089,7 +2091,7 @@ impl Optimizer<'_> { .. }) => { if let Expr::Ident(a_id) = &**arg { - if let Some(usage) = self.data.vars.get(&a_id.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(a_id)) { if let Some(VarDeclKind::Const) = usage.var_kind { return Err(()); } @@ -2165,7 +2167,7 @@ impl Optimizer<'_> { .. }) => { if let Expr::Ident(a_id) = &**arg { - if let Some(usage) = self.data.vars.get(&a_id.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(a_id)) { if let Some(VarDeclKind::Const) = usage.var_kind { return Err(()); } @@ -2266,7 +2268,7 @@ impl Optimizer<'_> { } }; - if let Some(usage) = self.data.vars.get(&left_id.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&left_id)) { if usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) { return Ok(false); } @@ -2311,7 +2313,7 @@ impl Optimizer<'_> { _ => return Ok(false), }; - if let Some(usage) = self.data.vars.get(&left.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&left)) { let is_lit = match a.init.as_deref() { Some(e) => is_trivial_lit(e), _ => false, @@ -2354,7 +2356,7 @@ impl Optimizer<'_> { } Mergable::FnDecl(a) => { - if let Some(usage) = self.data.vars.get(&a.ident.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&a.ident)) { if usage.ref_count != 1 || usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL) @@ -2394,7 +2396,7 @@ impl Optimizer<'_> { match a { Mergable::Var(a) => { if self.options.unused { - if let Some(usage) = self.data.vars.get(&left_id.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&left_id)) { // We are eliminating one usage, so we use 1 instead of // 0 if !force_drop @@ -2410,7 +2412,7 @@ impl Optimizer<'_> { if can_take_init || force_drop { let init = a.init.take(); - if let Some(usage) = self.data.vars.get(&left_id.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&left_id)) { if usage.var_kind == Some(VarDeclKind::Const) { a.init = Some(Expr::undefined(DUMMY_SP)); } @@ -2490,7 +2492,7 @@ impl Optimizer<'_> { let var_type = self .data .vars - .get(&left_id.hashed_id()) + .get(&IdIdx::from_ident(&left_id)) .and_then(|info| info.merged_var_type); let Some(a_type) = a_type else { return Ok(false); @@ -2563,7 +2565,7 @@ impl Optimizer<'_> { if can_remove { report_change!("sequences: Removed variable ({})", left_id); - self.vars.removed.insert(left_id.hashed_id()); + self.vars.removed.insert(IdIdx::from_ident(&left_id)); } dump_change_detail!("sequences: {}", dump(&*b, false)); @@ -2596,6 +2598,8 @@ impl Optimizer<'_> { { let used_by_b: FxHashSet = idents_used_by(&*b.right); if used_by_b.contains(&a_id.hashed_id()) { + let used_by_b: FxHashSet = idents_used_by(&*b.right); + if used_by_b.contains(&IdIdx::from_ident(a_id)) { return true; } } @@ -2726,7 +2730,7 @@ impl Mergable<'_> { #[derive(Debug, Default)] struct MergeSequenceCache { - ident_usage_cache: Vec>>, + ident_usage_cache: Vec>>, top_retain_cache: Vec>, } @@ -2738,7 +2742,7 @@ impl MergeSequenceCache { } } - fn is_ident_used_by>>( + fn is_ident_used_by>>( &mut self, ident: &Ident, node: &N, @@ -2749,6 +2753,7 @@ impl MergeSequenceCache { .iter() .any(|id| id.1 == ident.ctxt && id.0 == ident.sym) idents.contains(&ident.hashed_id()) + idents.contains(&IdIdx::from_ident(ident)) } fn invalidate(&mut self, node_id: usize) { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 48ed80729372..6cdc3c3d0fc3 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs @@ -185,7 +185,7 @@ impl Optimizer<'_> { return; } - if let Some(v) = self.data.vars.get(&i.hashed_id()) { + if let Some(v) = self.data.vars.get(&IdIdx::from_ident(i)) { let is_used_in_member = v.property_mutation_count > 0 || v.flags.contains(VarUsageInfoFlags::USED_AS_REF); if v.ref_count == 0 @@ -257,7 +257,7 @@ impl Optimizer<'_> { } } - if let Some(usage) = self.data.vars.get(&e.hashed_id()) { + if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(e)) { if !usage.flags.contains(VarUsageInfoFlags::DECLARED) { return true; } @@ -517,7 +517,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&ident.hashed_id()) + .get(&IdIdx::from_ident(ident)) .map(|v| v.usage_count == 0 && v.property_mutation_count == 0) .unwrap_or(false) { @@ -569,7 +569,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&ident.hashed_id()) + .get(&IdIdx::from_ident(ident)) .map(|v| v.usage_count == 0 && v.property_mutation_count == 0) .unwrap_or(false) { @@ -611,7 +611,7 @@ impl Optimizer<'_> { }; if let Expr::Ident(arg) = &*update.arg { - if let Some(var) = self.data.vars.get(&arg.hashed_id()) { + if let Some(var) = self.data.vars.get(&IdIdx::from_ident(arg)) { // Update is counted as usage if var .flags @@ -659,7 +659,7 @@ impl Optimizer<'_> { }; if let AssignTarget::Simple(SimpleAssignTarget::Ident(left)) = &assign.left { - if let Some(var) = self.data.vars.get(&left.hashed_id()) { + if let Some(var) = self.data.vars.get(&IdIdx::from_ident(&left.id)) { // TODO: We don't need fn_local check if var .flags @@ -724,7 +724,7 @@ impl Optimizer<'_> { return; } - if let Some(var) = self.data.vars.get(&i.hashed_id()) { + if let Some(var) = self.data.vars.get(&IdIdx::from_ident(i)) { // technically this is inline if !var.flags.intersects( VarUsageInfoFlags::INLINE_PREVENTED.union(VarUsageInfoFlags::EXPORTED), @@ -775,7 +775,7 @@ impl Optimizer<'_> { let can_remove_ident = self .data .vars - .get(&i.hashed_id()) + .get(&IdIdx::from_ident(i)) .map(|v| { (!v.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) && v.ref_count == 0 @@ -805,7 +805,7 @@ impl Optimizer<'_> { for d in var.decls.iter_mut() { if d.init.is_none() { if let Pat::Ident(name) = &d.name { - if let Some(usage) = self.data.vars.get_mut(&name.hashed_id()) { + if let Some(usage) = self.data.vars.get_mut(&IdIdx::from_ident(name)) { if usage.flags.contains( VarUsageInfoFlags::IS_FN_LOCAL .union(VarUsageInfoFlags::DECLARED_AS_FN_PARAM), @@ -865,7 +865,7 @@ impl Optimizer<'_> { let name = v.name.as_ident()?; let obj = v.init.as_mut()?.as_mut_object()?; - let usage = self.data.vars.get(&name.hashed_id())?; + let usage = self.data.vars.get(&IdIdx::from_ident(name))?; if usage.flags.intersects( VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY @@ -903,7 +903,7 @@ impl Optimizer<'_> { let mut unknown_used_props = self .data .vars - .get(&name.hashed_id()) + .get(&IdIdx::from_ident(name)) .map(|v| v.accessed_props.clone()) .unwrap_or_default(); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/util.rs b/crates/swc_ecma_minifier/src/compress/optimize/util.rs index 9cf1505ebda1..c78d1b40ac46 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/util.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/util.rs @@ -219,13 +219,13 @@ pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool { /// handle all edge cases and this type is the complement for it. #[derive(Clone, Copy)] pub(crate) struct Finalizer<'a> { - pub simple_functions: &'a FxHashMap>, - pub lits: &'a FxHashMap>, - pub lits_for_cmp: &'a FxHashMap>, - pub lits_for_array_access: &'a FxHashMap>, - pub hoisted_props: &'a FxHashMap<(HashedId, Atom), Ident>, + pub simple_functions: &'a FxHashMap>, + pub lits: &'a FxHashMap>, + pub lits_for_cmp: &'a FxHashMap>, + pub lits_for_array_access: &'a FxHashMap>, + pub hoisted_props: &'a FxHashMap<(IdIdx, Atom), Ident>, - pub vars_to_remove: &'a FxHashSet, + pub vars_to_remove: &'a FxHashSet, pub changed: bool, } @@ -241,7 +241,7 @@ impl Parallel for Finalizer<'_> { } impl Finalizer<'_> { - fn var(&mut self, i: HashedId, mode: FinalizerMode) -> Option> { + fn var(&mut self, i: IdIdx, mode: FinalizerMode) -> Option> { let mut e = match mode { FinalizerMode::Callee => { let mut value = self.simple_functions.get(&i).cloned()?; @@ -288,7 +288,7 @@ impl Finalizer<'_> { fn check(&mut self, e: &mut Expr, mode: FinalizerMode) { if let Expr::Ident(i) = e { - if let Some(new) = self.var(i.hashed_id(), mode) { + if let Some(new) = self.var(IdIdx::from_ident(i), mode) { debug!("multi-replacer: Replaced `{}`", i); self.changed = true; @@ -341,7 +341,7 @@ impl VisitMut for Finalizer<'_> { fn visit_mut_expr(&mut self, n: &mut Expr) { match n { Expr::Ident(i) => { - if let Some(expr) = self.lits.get(&i.hashed_id()) { + if let Some(expr) = self.lits.get(&IdIdx::from_ident(i)) { *n = *expr.clone(); return; } @@ -358,7 +358,10 @@ impl VisitMut for Finalizer<'_> { _ => return, }; - if let Some(ident) = self.hoisted_props.get(&(obj.hashed_id(), sym.clone())) { + if let Some(ident) = self + .hoisted_props + .get(&(IdIdx::from_ident(obj), sym.clone())) + { self.changed = true; *n = ident.clone().into(); return; @@ -442,7 +445,7 @@ impl VisitMut for Finalizer<'_> { if n.init.is_none() { if let Pat::Ident(i) = &n.name { - if self.vars_to_remove.contains(&i.hashed_id()) { + if self.vars_to_remove.contains(&IdIdx::from_ident(i)) { n.name.take(); } } @@ -459,7 +462,7 @@ impl VisitMut for Finalizer<'_> { n.visit_mut_children_with(self); if let Prop::Shorthand(i) = n { - if let Some(expr) = self.lits.get(&i.hashed_id()) { + if let Some(expr) = self.lits.get(&IdIdx::from_ident(i)) { *n = Prop::KeyValue(KeyValueProp { key: i.take().into(), value: expr.clone(), @@ -471,13 +474,13 @@ impl VisitMut for Finalizer<'_> { } pub(crate) struct NormalMultiReplacer<'a> { - pub vars: &'a mut FxHashMap>, + pub vars: &'a mut FxHashMap>, pub changed: bool, } impl<'a> NormalMultiReplacer<'a> { /// `worked` will be changed to `true` if any replacement is done - pub fn new(vars: &'a mut FxHashMap>) -> Self { + pub fn new(vars: &'a mut FxHashMap>) -> Self { NormalMultiReplacer { vars, changed: false, @@ -485,7 +488,7 @@ impl<'a> NormalMultiReplacer<'a> { } fn var(&mut self, i: &Ident) -> Option> { - let hashed_id = i.hashed_id(); + let hashed_id = IdIdx::from_ident(i); let mut e = self.vars.remove(&hashed_id)?; e.visit_mut_children_with(self); diff --git a/crates/swc_ecma_minifier/src/compress/pure/vars.rs b/crates/swc_ecma_minifier/src/compress/pure/vars.rs index 468df12a6f4f..3f1115095065 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/vars.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/vars.rs @@ -165,7 +165,7 @@ impl Pure<'_> { } match &v.name { - Pat::Ident(i) => found.insert(i.hashed_id()), + Pat::Ident(i) => found.insert(IdIdx::from_ident(i)), _ => true, } }) diff --git a/crates/swc_ecma_minifier/src/eval.rs b/crates/swc_ecma_minifier/src/eval.rs index 081e9e957135..f9b663290b0c 100644 --- a/crates/swc_ecma_minifier/src/eval.rs +++ b/crates/swc_ecma_minifier/src/eval.rs @@ -50,7 +50,7 @@ struct Eval { #[derive(Default)] struct EvalStore { - cache: FxHashMap>, + cache: FxHashMap>, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -60,7 +60,7 @@ pub enum EvalResult { } impl Mode for Eval { - fn store(&self, id: HashedId, value: &Expr) { + fn store(&self, id: IdIdx, value: &Expr) { let mut w = self.store.lock(); w.cache.insert(id, Box::new(value.clone())); } @@ -192,7 +192,8 @@ impl Evaluator { self.run(); let lock = self.data.store.lock(); - let val = lock.cache.get(&i.hashed_id())?; + let id = IdIdx::from_ident(i); + let val = lock.cache.get(&id)?; return Some(val.clone()); } diff --git a/crates/swc_ecma_minifier/src/metadata/mod.rs b/crates/swc_ecma_minifier/src/metadata/mod.rs index 9784326d00d5..75ce01400e17 100644 --- a/crates/swc_ecma_minifier/src/metadata/mod.rs +++ b/crates/swc_ecma_minifier/src/metadata/mod.rs @@ -49,7 +49,7 @@ struct InfoMarker<'a> { #[allow(dead_code)] options: Option<&'a CompressOptions>, pure_funcs: Option>>, - pure_callee: FxHashSet, + pure_callee: FxHashSet, comments: Option<&'a dyn Comments>, marks: Marks, @@ -61,7 +61,8 @@ impl InfoMarker<'_> { fn is_pure_callee(&self, callee: &Expr) -> bool { match callee { Expr::Ident(callee) => { - if self.pure_callee.contains(&callee.hashed_id()) { + let id = IdIdx::from_ident(callee); + if self.pure_callee.contains(&id) { return true; } } @@ -230,7 +231,7 @@ const NO_SIDE_EFFECTS_FLAG: &str = "NO_SIDE_EFFECTS"; struct InfoCollector<'a> { comments: Option<&'a dyn Comments>, - pure_callees: &'a mut FxHashSet, + pure_callees: &'a mut FxHashSet, } impl Visit for InfoCollector<'_> { @@ -241,7 +242,8 @@ impl Visit for InfoCollector<'_> { if let Decl::Fn(f) = &f.decl { if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - self.pure_callees.insert(f.ident.hashed_id()); + let id = IdIdx::from_ident(&f.ident); + self.pure_callees.insert(id); } } } @@ -250,7 +252,8 @@ impl Visit for InfoCollector<'_> { f.visit_children_with(self); if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - self.pure_callees.insert(f.ident.hashed_id()); + let id = IdIdx::from_ident(&f.ident); + self.pure_callees.insert(id); } } @@ -259,7 +262,8 @@ impl Visit for InfoCollector<'_> { if let Some(ident) = &f.ident { if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - self.pure_callees.insert(ident.hashed_id()); + let id = IdIdx::from_ident(ident); + self.pure_callees.insert(id); } } } @@ -274,7 +278,8 @@ impl Visit for InfoCollector<'_> { || has_flag(self.comments, v.span, NO_SIDE_EFFECTS_FLAG) || has_flag(self.comments, init.span(), NO_SIDE_EFFECTS_FLAG) { - self.pure_callees.insert(ident.hashed_id()); + let id = IdIdx::from_ident(ident); + self.pure_callees.insert(id); } } } diff --git a/crates/swc_ecma_minifier/src/mode.rs b/crates/swc_ecma_minifier/src/mode.rs index 36a83124eb27..09bb8eca7ab5 100644 --- a/crates/swc_ecma_minifier/src/mode.rs +++ b/crates/swc_ecma_minifier/src/mode.rs @@ -1,7 +1,7 @@ use swc_ecma_ast::*; pub(crate) trait Mode: Send + Sync { - fn store(&self, id: HashedId, value: &Expr); + fn store(&self, id: IdIdx, value: &Expr); fn preserve_vars(&self) -> bool; @@ -15,7 +15,7 @@ pub(crate) trait Mode: Send + Sync { pub struct Minification; impl Mode for Minification { - fn store(&self, _: HashedId, _: &Expr) {} + fn store(&self, _: IdIdx, _: &Expr) {} fn preserve_vars(&self) -> bool { false diff --git a/crates/swc_ecma_minifier/src/program_data.rs b/crates/swc_ecma_minifier/src/program_data.rs index 65672593f876..89123b190118 100644 --- a/crates/swc_ecma_minifier/src/program_data.rs +++ b/crates/swc_ecma_minifier/src/program_data.rs @@ -6,7 +6,7 @@ use swc_atoms::Atom; use swc_common::SyntaxContext; use swc_ecma_ast::*; use swc_ecma_usage_analyzer::{ - alias::AccessKind, + alias::{Access, AccessKind}, analyzer::{ analyze_with_custom_storage, storage::{ScopeDataLike, Storage, VarDataLike}, @@ -36,13 +36,13 @@ where /// Analyzed info of a whole program we are working on. #[derive(Debug, Default)] pub(crate) struct ProgramData { - pub(crate) vars: FxHashMap>, + pub(crate) vars: FxHashMap>, pub(crate) top: ScopeData, pub(crate) scopes: FxHashMap, - initialized_vars: IndexSet, + initialized_vars: IndexSet, pub(crate) property_atoms: Option>, } @@ -125,7 +125,7 @@ pub(crate) struct VarUsageInfo { /// `infects_to`. This should be renamed, but it will be done with another /// PR. (because it's hard to review) - infects_to: Vec<(HashedId, AccessKind)>, + infects_to: Vec, /// Only **string** properties. pub(crate) accessed_props: FxHashMap, } @@ -210,7 +210,7 @@ impl Storage for ProgramData { &mut self.top } - fn var_or_default(&mut self, id: HashedId) -> &mut Self::VarData { + fn var_or_default(&mut self, id: IdIdx) -> &mut Self::VarData { self.vars.entry(id).or_default() } @@ -358,7 +358,7 @@ impl Storage for ProgramData { } } - fn report_usage(&mut self, ctx: Ctx, i: HashedId) { + fn report_usage(&mut self, ctx: Ctx, i: IdIdx) { let inited = self.initialized_vars.contains(&i); let e = self.vars.entry(i).or_insert_with(|| { @@ -388,7 +388,7 @@ impl Storage for ProgramData { } } - fn report_assign(&mut self, ctx: Ctx, i: HashedId, is_op: bool, ty: Value) { + fn report_assign(&mut self, ctx: Ctx, i: IdIdx, is_op: bool, ty: Value) { let e = self.vars.entry(i).or_default(); let inited = self.initialized_vars.contains(&i); @@ -415,7 +415,7 @@ impl Storage for ProgramData { e.usage_count = e.usage_count.saturating_sub(1); } - let mut to_visit: IndexSet = + let mut to_visit: IndexSet = IndexSet::from_iter(e.infects_to.iter().map(|i| i.0)); let mut idx = 0; @@ -458,7 +458,7 @@ impl Storage for ProgramData { // debug!(has_init = has_init, "declare_decl(`{}`)", i); // } - let v = self.vars.entry(i.hashed_id()).or_default(); + let v = self.vars.entry(IdIdx::from_ident(i)).or_default(); if ctx.is_top_level() { v.flags |= VarUsageInfoFlags::IS_TOP_LEVEL; } @@ -510,7 +510,7 @@ impl Storage for ProgramData { v.flags |= VarUsageInfoFlags::DECLARED; // not a VarDecl, thus always inited if init_type.is_some() || kind.is_none() { - self.initialized_vars.insert(i.hashed_id()); + self.initialized_vars.insert(IdIdx::from_ident(i)); } if ctx.in_catch_param() { v.flags |= VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM; @@ -527,7 +527,7 @@ impl Storage for ProgramData { self.initialized_vars.truncate(len) } - fn mark_property_mutation(&mut self, id: HashedId) { + fn mark_property_mutation(&mut self, id: IdIdx) { let e = self.vars.entry(id).or_default(); e.property_mutation_count += 1; @@ -551,7 +551,7 @@ impl Storage for ProgramData { } } - fn get_var_data(&self, id: HashedId) -> Option<&Self::VarData> { + fn get_var_data(&self, id: IdIdx) -> Option<&Self::VarData> { self.vars.get(&id).map(|v| v.as_ref()) } } @@ -625,7 +625,7 @@ impl VarDataLike for VarUsageInfo { self.flags.insert(VarUsageInfoFlags::USED_AS_REF); } - fn add_infects_to(&mut self, other: (HashedId, AccessKind)) { + fn add_infects_to(&mut self, other: Access) { self.infects_to.push(other); } @@ -749,7 +749,7 @@ impl ProgramData { return false; } - if let Some(v) = self.vars.get(&i.hashed_id()) { + if let Some(v) = self.vars.get(&IdIdx::from_ident(i)) { return !v.flags.contains(VarUsageInfoFlags::DECLARED); } diff --git a/crates/swc_ecma_usage_analyzer/src/alias/mod.rs b/crates/swc_ecma_usage_analyzer/src/alias/mod.rs index 6a732d7dc771..79e79b1330f5 100644 --- a/crates/swc_ecma_usage_analyzer/src/alias/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/alias/mod.rs @@ -81,9 +81,9 @@ pub enum AccessKind { Call, } -pub type Access = (Id, AccessKind); +pub type Access = (IdIdx, AccessKind); -pub fn collect_infects_from(node: &N, config: AliasConfig) -> FxHashSet<(HashedId, AccessKind)> +pub fn collect_infects_from(node: &N, config: AliasConfig) -> FxHashSet<(IdIdx, AccessKind)> where N: InfectableNode + VisitWith, { @@ -120,7 +120,7 @@ pub fn try_collect_infects_from( node: &N, config: AliasConfig, max_entries: usize, -) -> Result, TooManyAccesses> +) -> Result, TooManyAccesses> where N: InfectableNode + VisitWith, { @@ -154,31 +154,30 @@ where } pub struct InfectionCollector { - #[allow(unused)] config: AliasConfig, unresolved_ctxt: Option, - bindings: FxHashSet, + bindings: FxHashSet, ctx: Ctx, - accesses: FxHashSet<(HashedId, AccessKind)>, + accesses: FxHashSet, max_entries: Option, } impl InfectionCollector { fn add_binding(&mut self, e: &Ident) { - let hashed_id = e.hashed_id(); - if self.bindings.insert(hashed_id) { - self.accesses.remove(&(hashed_id, AccessKind::Reference)); - self.accesses.remove(&(hashed_id, AccessKind::Call)); + let id = IdIdx::from_ident(e); + if self.bindings.insert(id) { + self.accesses.remove(&(id, AccessKind::Reference)); + self.accesses.remove(&(id, AccessKind::Call)); } } fn add_usage(&mut self, ident: &Ident) { - let hashed_id = ident.hashed_id(); - if self.bindings.contains(&hashed_id) { + let id = IdIdx::from_ident(ident); + if self.bindings.contains(&id) { return; } @@ -189,7 +188,7 @@ impl InfectionCollector { } self.accesses.insert(( - hashed_id, + id, if self.ctx.contains(Ctx::IsCallee) { AccessKind::Call } else { diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs index 26672b051a23..a59c9d1e2066 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs @@ -82,7 +82,7 @@ where scope: S::ScopeData, ctx: Ctx, expr_ctx: ExprCtx, - used_recursively: FxHashMap, + used_recursively: FxHashMap, } impl UsageAnalyzer @@ -150,7 +150,7 @@ where self.scope.mark_used_arguments(); } - let id = i.hashed_id(); + let id = IdIdx::from_ident(i); if let Some(recr) = self.used_recursively.get(&id) { if let RecursiveUsage::Var { can_ignore: false } = recr { @@ -167,7 +167,6 @@ where fn report_assign_pat(&mut self, p: &Pat, is_read_modify: bool) { for id in find_pat_ids(p) { // It's hard to determined the type of pat assignment - let id = hashed_id_from_id(&id); self.data .report_assign(self.ctx, id, is_read_modify, Value::Unknown) } @@ -175,8 +174,9 @@ where if let Pat::Expr(e) = p { match &**e { Expr::Ident(i) => { + let id = IdIdx::from_ident(i); self.data - .report_assign(self.ctx, i.hashed_id(), is_read_modify, Value::Unknown) + .report_assign(self.ctx, id, is_read_modify, Value::Unknown) } _ => self.mark_mutation_if_member(e.as_member()), } @@ -185,7 +185,8 @@ where fn report_assign_expr_if_ident(&mut self, e: Option<&Ident>, is_op: bool, ty: Value) { if let Some(i) = e { - self.data.report_assign(self.ctx, i.hashed_id(), is_op, ty) + let id = IdIdx::from_ident(i); + self.data.report_assign(self.ctx, id, is_op, ty) } } @@ -222,7 +223,8 @@ where fn mark_mutation_if_member(&mut self, e: Option<&MemberExpr>) { if let Some(m) = e { for_each_id_ref_in_expr(&m.obj, &mut |id| { - self.data.mark_property_mutation(id.hashed_id()) + let id = IdIdx::from_ident(id); + self.data.mark_property_mutation(id) }); } } @@ -286,7 +288,6 @@ where match &n.left { AssignTarget::Pat(p) => { for id in find_pat_ids(p) { - let id = hashed_id_from_id(&id); self.data.report_assign( self.ctx, id, @@ -307,7 +308,7 @@ where if n.op == op!("=") { let left = match &n.left { - AssignTarget::Simple(left) => left.leftmost().as_deref().map(Ident::hashed_id), + AssignTarget::Simple(left) => left.leftmost(), AssignTarget::Pat(..) => None, }; @@ -322,6 +323,7 @@ where }, ) { if v.is_none() { + let left = IdIdx::from_ident(&left); v = Some(self.data.var_or_default(left)); } @@ -365,7 +367,8 @@ where } else { if e.op == op!("in") { for_each_id_ref_in_expr(&e.right, &mut |obj| { - let var = self.data.var_or_default(obj.hashed_id()); + let id = IdIdx::from_ident(obj); + let var = self.data.var_or_default(id); var.mark_used_as_ref(); match &*e.left { @@ -426,9 +429,8 @@ where if let Callee::Expr(callee) = &n.callee { for_each_id_ref_in_expr(callee, &mut |i| { - self.data - .var_or_default(i.hashed_id()) - .mark_used_as_callee(); + let id = IdIdx::from_ident(i); + self.data.var_or_default(id).mark_used_as_callee(); }); match &**callee { @@ -441,8 +443,9 @@ where if is_safe_to_access_prop(&arg.expr) { if let Pat::Ident(id) = &p.pat { + let id = IdIdx::from_ident(id); self.data - .var_or_default(id.hashed_id()) + .var_or_default(id) .mark_initialized_with_safe_value(); } } @@ -459,8 +462,9 @@ where if is_safe_to_access_prop(&arg.expr) { if let Pat::Ident(id) = &p { + let id = IdIdx::from_ident(id); self.data - .var_or_default(id.hashed_id()) + .var_or_default(id) .mark_initialized_with_safe_value(); } } @@ -487,7 +491,8 @@ where if call_may_mutate { for a in &n.args { for_each_id_ref_in_expr(&a.expr, &mut |id| { - self.data.mark_property_mutation(id.hashed_id()); + let id = IdIdx::from_ident(id); + self.data.mark_property_mutation(id); }); } } @@ -495,7 +500,8 @@ where for arg in &n.args { for_each_id_ref_in_expr(&arg.expr, &mut |arg| { - self.data.var_or_default(arg.hashed_id()).mark_used_as_arg(); + let id = IdIdx::from_ident(arg); + self.data.var_or_default(id).mark_used_as_arg(); }) } @@ -506,7 +512,8 @@ where } Expr::Member(m) if !m.obj.is_ident() => { for_each_id_ref_in_expr(&m.obj, &mut |id| { - self.data.var_or_default(id.hashed_id()).mark_used_as_ref() + let id = IdIdx::from_ident(id); + self.data.var_or_default(id).mark_used_as_ref() }) } _ => {} @@ -653,12 +660,14 @@ where match d { DefaultDecl::Class(c) => { if let Some(i) = &c.ident { - self.data.var_or_default(i.hashed_id()).prevent_inline(); + let id = IdIdx::from_ident(i); + self.data.var_or_default(id).prevent_inline(); } } DefaultDecl::Fn(f) => { if let Some(i) = &f.ident { - self.data.var_or_default(i.hashed_id()).prevent_inline(); + let id = IdIdx::from_ident(i); + self.data.var_or_default(id).prevent_inline(); } } _ => {} @@ -685,20 +694,17 @@ where match &n.decl { Decl::Class(c) => { - self.data - .var_or_default(c.ident.hashed_id()) - .prevent_inline(); + let id = IdIdx::from_ident(&c.ident); + self.data.var_or_default(id).prevent_inline(); } Decl::Fn(f) => { - self.data - .var_or_default(f.ident.hashed_id()) - .prevent_inline(); + let id = IdIdx::from_ident(&f.ident); + self.data.var_or_default(id).prevent_inline(); } Decl::Var(v) => { let ids = find_pat_ids(v); for id in ids { - let id = hashed_id_from_id(&id); self.data.var_or_default(id).mark_as_exported(); } } @@ -720,7 +726,8 @@ where match &n.orig { ModuleExportName::Ident(orig) => { self.report_usage(orig); - let v = self.data.var_or_default(orig.hashed_id()); + let id = IdIdx::from_ident(orig); + let v = self.data.var_or_default(id); v.prevent_inline(); v.mark_used_as_ref(); } @@ -771,9 +778,8 @@ where if e.spread.is_some() { for_each_id_ref_in_expr(&e.expr, &mut |i| { - self.data - .var_or_default(i.hashed_id()) - .mark_indexed_with_dynamic_key(); + let id = IdIdx::from_ident(i); + self.data.var_or_default(id).mark_indexed_with_dynamic_key(); }); } } @@ -789,13 +795,11 @@ where self.with_ctx(ctx) .declare_decl(&n.ident, Some(Value::Known(Type::Obj)), None, true); + let id = IdIdx::from_ident(&n.ident); if n.function.body.is_empty() { - self.data - .var_or_default(n.ident.hashed_id()) - .mark_as_pure_fn(); + self.data.var_or_default(id).mark_as_pure_fn(); } - let id = n.ident.hashed_id(); self.used_recursively.insert(id, RecursiveUsage::FnOrClass); n.visit_children_with(self); self.used_recursively.remove(&id); @@ -811,7 +815,7 @@ where }, ) { if v.is_none() { - v = Some(self.data.var_or_default(n.ident.hashed_id())); + v = Some(self.data.var_or_default(IdIdx::from_ident(&n.ident))); } v.as_mut().unwrap().add_infects_to(id); @@ -826,11 +830,11 @@ where fn visit_fn_expr(&mut self, n: &FnExpr) { if let Some(n_id) = &n.ident { self.data - .var_or_default(n_id.hashed_id()) + .var_or_default(IdIdx::from_ident(n_id)) .mark_declared_as_fn_expr(); self.used_recursively - .insert(n_id.hashed_id(), RecursiveUsage::FnOrClass); + .insert(IdIdx::from_ident(n_id), RecursiveUsage::FnOrClass); n.visit_children_with(self); @@ -845,13 +849,13 @@ where }, ) { if v.is_none() { - v = Some(self.data.var_or_default(n_id.hashed_id())); + v = Some(self.data.var_or_default(IdIdx::from_ident(n_id))); } v.as_mut().unwrap().add_infects_to(id); } } - self.used_recursively.remove(&n_id.hashed_id()); + self.used_recursively.remove(&IdIdx::from_ident(n_id)); } else { n.visit_children_with(self); } @@ -1011,9 +1015,8 @@ where if let JSXElementName::Ident(i) = n { self.with_ctx(ctx).report_usage(i); - self.data - .var_or_default(i.hashed_id()) - .mark_used_as_jsx_callee(); + let id = IdIdx::from_ident(i); + self.data.var_or_default(id).mark_used_as_jsx_callee(); } } @@ -1032,7 +1035,8 @@ where } for_each_id_ref_in_expr(&e.obj, &mut |obj| { - let v = self.data.var_or_default(obj.hashed_id()); + let id = IdIdx::from_ident(obj); + let v = self.data.var_or_default(id); v.mark_has_property_access(); if let MemberProp::Computed(prop) = &e.prop { @@ -1057,7 +1061,7 @@ where match &*member_expr.obj { Expr::Member(member_expr) => is_root_of_member_expr_declared(member_expr, data), Expr::Ident(ident) => data - .get_var_data(ident.hashed_id()) + .get_var_data(IdIdx::from_ident(ident)) .map(|var| var.is_declared()) .unwrap_or(false), @@ -1116,7 +1120,8 @@ where if let Some(args) = &n.args { for a in args { for_each_id_ref_in_expr(&a.expr, &mut |id| { - self.data.mark_property_mutation(id.hashed_id()); + let id = IdIdx::from_ident(id); + self.data.mark_property_mutation(id); }); } } @@ -1247,9 +1252,8 @@ where e.visit_children_with(self); for_each_id_ref_in_expr(&e.expr, &mut |i| { - self.data - .var_or_default(i.hashed_id()) - .mark_indexed_with_dynamic_key(); + let id = IdIdx::from_ident(i); + self.data.var_or_default(id).mark_indexed_with_dynamic_key(); }); } @@ -1401,7 +1405,7 @@ where }, ) { if v.is_none() { - v = Some(self.data.var_or_default(var.hashed_id())); + v = Some(self.data.var_or_default(IdIdx::from_ident(var))); } v.as_mut().unwrap().add_infects_to(id); @@ -1462,7 +1466,7 @@ where definite: false, .. } => { - let id = id.hashed_id(); + let id = IdIdx::from_ident(id); self.used_recursively.insert( id, RecursiveUsage::Var { @@ -1479,7 +1483,9 @@ where init: None, .. } => { - self.data.var_or_default(id.hashed_id()).mark_as_lazy_init(); + self.data + .var_or_default(IdIdx::from_ident(id)) + .mark_as_lazy_init(); return; } _ => (), diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs index b3e5d495b002..6a921c5a0ce1 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs @@ -4,7 +4,7 @@ use swc_ecma_ast::*; use swc_ecma_utils::{Type, Value}; use super::{ctx::Ctx, ScopeKind}; -use crate::alias::AccessKind; +use crate::alias::Access; pub trait Storage: Sized + Default { type ScopeData: ScopeDataLike; @@ -20,13 +20,13 @@ pub trait Storage: Sized + Default { fn top_scope(&mut self) -> &mut Self::ScopeData; - fn var_or_default(&mut self, id: HashedId) -> &mut Self::VarData; + fn var_or_default(&mut self, id: IdIdx) -> &mut Self::VarData; fn merge(&mut self, kind: ScopeKind, child: Self); - fn report_usage(&mut self, ctx: Ctx, i: HashedId); + fn report_usage(&mut self, ctx: Ctx, i: IdIdx); - fn report_assign(&mut self, ctx: Ctx, i: HashedId, is_op: bool, ty: Value); + fn report_assign(&mut self, ctx: Ctx, i: IdIdx, is_op: bool, ty: Value); fn declare_decl( &mut self, @@ -39,9 +39,9 @@ pub trait Storage: Sized + Default { fn get_initialized_cnt(&self) -> usize; fn truncate_initialized_cnt(&mut self, len: usize); - fn mark_property_mutation(&mut self, id: HashedId); + fn mark_property_mutation(&mut self, id: IdIdx); - fn get_var_data(&self, id: HashedId) -> Option<&Self::VarData>; + fn get_var_data(&self, id: IdIdx) -> Option<&Self::VarData>; } pub trait ScopeDataLike: Sized + Default + Clone { @@ -80,7 +80,7 @@ pub trait VarDataLike: Sized { fn mark_used_as_ref(&mut self); - fn add_infects_to(&mut self, other: (HashedId, AccessKind)); + fn add_infects_to(&mut self, other: Access); fn prevent_inline(&mut self); diff --git a/crates/swc_ecma_utils/src/ident.rs b/crates/swc_ecma_utils/src/ident.rs index b19ee98b2d4f..e62c05cfc6d6 100644 --- a/crates/swc_ecma_utils/src/ident.rs +++ b/crates/swc_ecma_utils/src/ident.rs @@ -1,6 +1,6 @@ use swc_atoms::Atom; use swc_common::SyntaxContext; -use swc_ecma_ast::{unsafe_id_from_ident, BindingIdent, HashedId, Id, Ident, UnsafeId}; +use swc_ecma_ast::{unsafe_id_from_ident, BindingIdent, Id, IdIdx, Ident, UnsafeId}; pub trait IdentLike: Sized + Send + Sync + 'static { type Id; @@ -95,11 +95,11 @@ impl IdentLike for UnsafeId { } } -impl IdentLike for HashedId { - type Id = HashedId; +impl IdentLike for IdIdx { + type Id = IdIdx; fn from_ident(i: &Ident) -> Self { - i.hashed_id() + IdIdx::from_ident(i) } fn to_id(&self) -> Self::Id { From 9cc3b150bf49cf2bb4df104daabbeb0f42911203 Mon Sep 17 00:00:00 2001 From: bohan Date: Fri, 1 Aug 2025 01:47:22 +0800 Subject: [PATCH 3/4] use indexmap --- Cargo.lock | 2 +- crates/swc_ecma_ast/Cargo.toml | 2 +- crates/swc_ecma_ast/src/ident.rs | 34 ++++++++++++++++++++++---------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1237f43d4b3d..469977b9de5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4955,7 +4955,7 @@ dependencies = [ "arbitrary", "bitflags 2.6.0", "bytecheck 0.8.1", - "hashbrown 0.14.5", + "indexmap 2.7.1", "is-macro", "num-bigint", "once_cell", diff --git a/crates/swc_ecma_ast/Cargo.toml b/crates/swc_ecma_ast/Cargo.toml index 3984df0cdfaa..14cd0a40c059 100644 --- a/crates/swc_ecma_ast/Cargo.toml +++ b/crates/swc_ecma_ast/Cargo.toml @@ -39,7 +39,7 @@ shrink-to-fit = [ arbitrary = { workspace = true, features = ["derive"], optional = true } bitflags = { workspace = true } bytecheck = { workspace = true, optional = true } -hashbrown = { workspace = true } +indexmap = { workspace = true } is-macro = { workspace = true } num-bigint = { workspace = true, features = ["serde"] } once_cell = { workspace = true } diff --git a/crates/swc_ecma_ast/src/ident.rs b/crates/swc_ecma_ast/src/ident.rs index 2c4ae559133c..d0fbc193e68f 100644 --- a/crates/swc_ecma_ast/src/ident.rs +++ b/crates/swc_ecma_ast/src/ident.rs @@ -6,6 +6,7 @@ use std::{ ops::{Deref, DerefMut}, }; +use indexmap::map::RawEntryApiV1; use once_cell::sync::Lazy; use phf::phf_set; use rustc_hash::FxHashSet; @@ -545,28 +546,41 @@ impl IdIdx { #[derive(Default)] pub struct Ids { - map: hashbrown::HashMap, + map: indexmap::IndexMap, } impl Ids { + #[inline(always)] + pub fn intern_ident(&mut self, ident: &Ident) -> IdIdx { + self.intern(&ident.sym, ident.ctxt) + } + pub fn intern(&mut self, atom: &Atom, ctxt: SyntaxContext) -> IdIdx { let mut hasher = rustc_hash::FxHasher::default(); atom.hash(&mut hasher); ctxt.hash(&mut hasher); let hash = hasher.finish(); - let len = self.map.len(); - - let (_, idx) = self + use indexmap::map::raw_entry_v1::RawEntryMut::*; + let idx = match self .map - .raw_entry_mut() + .raw_entry_mut_v1() .from_hash(hash, |id| id.1 == ctxt && id.0.eq(atom)) - .or_insert_with(|| { - let idx = IdIdx(len as u32); + { + Occupied(occ) => occ.index(), + Vacant(vac) => { let id = (atom.clone(), ctxt); - (id, idx) - }); - *idx + let idx = vac.index(); + vac.insert_hashed_nocheck(hash, id, ()); + idx + } + }; + IdIdx(idx as u32) + } + + #[inline(always)] + pub fn get(&self, idx: IdIdx) -> &Id { + &self.map.get_index(idx.0 as usize).unwrap().0 } } From 82e22082f20e678907ba050e3a6e03e4d31f2539 Mon Sep 17 00:00:00 2001 From: bohan Date: Fri, 1 Aug 2025 18:57:06 +0800 Subject: [PATCH 4/4] dont use tls --- crates/swc_ecma_ast/src/ident.rs | 20 +- .../src/compress/hoist_decls.rs | 20 +- crates/swc_ecma_minifier/src/compress/mod.rs | 10 +- .../src/compress/optimize/arguments.rs | 2 +- .../src/compress/optimize/bools.rs | 2 +- .../src/compress/optimize/conditionals.rs | 8 +- .../src/compress/optimize/dead_code.rs | 4 +- .../src/compress/optimize/evaluate.rs | 4 +- .../src/compress/optimize/iife.rs | 64 ++- .../src/compress/optimize/inline.rs | 87 +-- .../src/compress/optimize/mod.rs | 34 +- .../src/compress/optimize/ops.rs | 4 +- .../src/compress/optimize/props.rs | 10 +- .../src/compress/optimize/rest_params.rs | 2 +- .../src/compress/optimize/sequences.rs | 155 +++--- .../src/compress/optimize/unused.rs | 25 +- .../src/compress/optimize/util.rs | 79 +-- .../src/compress/pure/evaluate.rs | 1 - .../src/compress/pure/mod.rs | 42 +- .../src/compress/pure/switches.rs | 4 +- .../src/compress/pure/vars.rs | 2 +- crates/swc_ecma_minifier/src/eval.rs | 16 +- crates/swc_ecma_minifier/src/lib.rs | 6 +- crates/swc_ecma_minifier/src/metadata/mod.rs | 18 +- .../src/pass/mangle_props.rs | 3 +- crates/swc_ecma_minifier/src/program_data.rs | 105 ++-- .../src/util/ident_usage_collector.rs | 92 +++ crates/swc_ecma_minifier/src/util/mod.rs | 111 +--- crates/swc_ecma_minifier/tests/eval.rs | 80 ++- .../swc_ecma_usage_analyzer/src/alias/ctx.rs | 41 -- .../src/alias/infection_collector.rs | 404 ++++++++++++++ .../swc_ecma_usage_analyzer/src/alias/mod.rs | 376 +------------ .../src/analyzer/ctx.rs | 55 -- .../src/analyzer/mod.rs | 523 +++++++++++------- .../src/analyzer/storage.rs | 2 +- crates/swc_ecma_utils/src/find_pat_ids.rs | 77 +++ crates/swc_ecma_utils/src/ident.rs | 18 +- crates/swc_ecma_utils/src/lib.rs | 42 +- 38 files changed, 1372 insertions(+), 1176 deletions(-) create mode 100644 crates/swc_ecma_minifier/src/util/ident_usage_collector.rs create mode 100644 crates/swc_ecma_usage_analyzer/src/alias/infection_collector.rs create mode 100644 crates/swc_ecma_utils/src/find_pat_ids.rs diff --git a/crates/swc_ecma_ast/src/ident.rs b/crates/swc_ecma_ast/src/ident.rs index d0fbc193e68f..f69abc3390bf 100644 --- a/crates/swc_ecma_ast/src/ident.rs +++ b/crates/swc_ecma_ast/src/ident.rs @@ -1,6 +1,5 @@ use std::{ borrow::Cow, - cell::RefCell, fmt::Display, hash::{Hash, Hasher}, ops::{Deref, DerefMut}, @@ -533,18 +532,7 @@ pub type Id = (Atom, SyntaxContext); #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] pub struct IdIdx(u32); -impl IdIdx { - pub fn new(atom: &Atom, ctxt: SyntaxContext) -> Self { - GLOBAL_ID_LIST.with(|ids| ids.borrow_mut().intern(atom, ctxt)) - } - - #[inline(always)] - pub fn from_ident(i: &Ident) -> Self { - Self::new(&i.sym, i.ctxt) - } -} - -#[derive(Default)] +#[derive(Default, Debug)] pub struct Ids { map: indexmap::IndexMap, } @@ -580,14 +568,10 @@ impl Ids { #[inline(always)] pub fn get(&self, idx: IdIdx) -> &Id { - &self.map.get_index(idx.0 as usize).unwrap().0 + self.map.get_index(idx.0 as usize).unwrap().0 } } -thread_local! { - static GLOBAL_ID_LIST: RefCell = Default::default(); -} - impl Take for Ident { fn dummy() -> Self { Ident::new_no_ctxt(atom!(""), DUMMY_SP) diff --git a/crates/swc_ecma_minifier/src/compress/hoist_decls.rs b/crates/swc_ecma_minifier/src/compress/hoist_decls.rs index e852144cdf5e..1224d635b697 100644 --- a/crates/swc_ecma_minifier/src/compress/hoist_decls.rs +++ b/crates/swc_ecma_minifier/src/compress/hoist_decls.rs @@ -3,7 +3,7 @@ use rustc_hash::FxHashSet; use swc_common::{pass::Repeated, util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_usage_analyzer::analyzer::UsageAnalyzer; -use swc_ecma_utils::{find_pat_ids, StmtLike}; +use swc_ecma_utils::{find_pat_ids, find_pat_ids_with_idx, StmtLike}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith}; use super::util::drop_invalid_stmts; @@ -18,11 +18,16 @@ pub(super) struct DeclHoisterConfig { pub _top_level: bool, } -pub(super) fn decl_hoister(config: DeclHoisterConfig, data: &ProgramData) -> Hoister { +pub(super) fn decl_hoister<'a>( + config: DeclHoisterConfig, + data: &'a ProgramData, + id_map: &'a mut Ids, +) -> Hoister<'a> { Hoister { config, changed: false, data, + id_map, } } @@ -30,6 +35,7 @@ pub(super) struct Hoister<'a> { config: DeclHoisterConfig, changed: bool, data: &'a ProgramData, + id_map: &'a mut Ids, } impl Repeated for Hoister<'_> { @@ -42,11 +48,11 @@ impl Repeated for Hoister<'_> { } } -impl Hoister<'_> { +impl<'a> Hoister<'a> { fn handle_stmt_likes(&mut self, stmts: &mut Vec) where T: StmtLike + IsModuleItem + ModuleItemExt, - Vec: for<'aa> VisitMutWith> + VisitWith>, + Vec: for<'aa> VisitMutWith> + VisitWith>, { stmts.visit_mut_children_with(self); let len = stmts.len(); @@ -55,7 +61,7 @@ impl Hoister<'_> { 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 = find_pat_ids(&var.decls); + let ids: Vec = find_pat_ids_with_idx(&var.decls, self.id_map); if ids.iter().any(|id| { self.data @@ -122,7 +128,7 @@ impl Hoister<'_> { let ids: Vec = find_pat_ids(&decl.name); for id in ids { - let idx = IdIdx::from_ident(&id); + let idx = self.id_map.intern_ident(&id); if done.insert(idx) { // If the enclosing function declares parameter with same // name, we can drop a varaible. @@ -210,7 +216,7 @@ impl Hoister<'_> { let preserve = match &decl.name { Pat::Ident(name) => { - let id = IdIdx::from_ident(&name.id); + let id = self.id_map.intern_ident(&name); // If the enclosing function declares parameter with same // name, we can drop a varaible. (If it's side-effect free). if decl.init.is_none() diff --git a/crates/swc_ecma_minifier/src/compress/mod.rs b/crates/swc_ecma_minifier/src/compress/mod.rs index d40b772475a0..de9d9df5005f 100644 --- a/crates/swc_ecma_minifier/src/compress/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/mod.rs @@ -36,6 +36,7 @@ pub(crate) fn compressor<'a, M>( options: &'a CompressOptions, mangle_options: Option<&'a MangleOptions>, mode: &'a M, + id_map: &'a mut Ids, ) -> impl 'a + Pass where M: Mode, @@ -47,6 +48,7 @@ where changed: false, pass: 1, mode, + id_map, } } @@ -58,6 +60,7 @@ struct Compressor<'a> { pass: usize, mode: &'a dyn Mode, + id_map: &'a mut Ids, } impl CompilerPass for Compressor<'_> { @@ -80,7 +83,7 @@ impl Compressor<'_> { ); if self.options.hoist_vars || self.options.hoist_fns { - let data = analyze(&*n, Some(self.marks), false); + let data = analyze(&*n, Some(self.marks), false, self.id_map); let mut v = decl_hoister( DeclHoisterConfig { @@ -89,6 +92,7 @@ impl Compressor<'_> { _top_level: self.options.top_level(), }, &data, + self.id_map, ); n.visit_mut_with(&mut v); self.changed |= v.changed(); @@ -145,6 +149,7 @@ impl Compressor<'_> { let mut visitor = pure_optimizer( self.options, + self.id_map, self.marks, PureOptimizerConfig { enable_join_vars: self.pass > 1, @@ -173,7 +178,7 @@ impl Compressor<'_> { { let _timer = timer!("apply full optimizer"); - let mut data = analyze(&*n, Some(self.marks), false); + let mut data = analyze(&*n, Some(self.marks), false, self.id_map); // TODO: reset_opt_flags // @@ -185,6 +190,7 @@ impl Compressor<'_> { self.mangle_options, &mut data, self.mode, + self.id_map, ); n.visit_mut_with(&mut visitor); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs b/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs index 686764203bba..3f21710817d3 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs @@ -77,7 +77,7 @@ impl Optimizer<'_> { Pat::Ident(i) => self .data .vars - .get(&IdIdx::from_ident(&i.id)) + .get(&self.id_map.intern_ident(&i.id)) .map(|v| v.declared_count >= 2) .unwrap_or(false), _ => true, diff --git a/crates/swc_ecma_minifier/src/compress/optimize/bools.rs b/crates/swc_ecma_minifier/src/compress/optimize/bools.rs index 1be3cbcd1025..08044c0481bc 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/bools.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/bools.rs @@ -22,7 +22,7 @@ impl Optimizer<'_> { ) if &**l_v == "undefined" => { // TODO? if let Expr::Ident(arg) = &**arg { - if let Some(usage) = o.data.vars.get(&IdIdx::from_ident(arg)) { + if let Some(usage) = o.data.vars.get(&o.id_map.intern_ident(arg)) { if !usage.flags.contains(VarUsageInfoFlags::DECLARED) { return false; } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs b/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs index 27bc2f8270ac..bfca2e62c0bb 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs @@ -387,7 +387,7 @@ impl Optimizer<'_> { // to f(a, a = 1 ? true : false) let side_effects_in_test = test.may_have_side_effects(self.ctx.expr_ctx); - if self.data.contains_unresolved(test) { + if self.data.contains_unresolved(test, self.id_map) { return None; } @@ -404,7 +404,7 @@ impl Optimizer<'_> { let side_effect_free = self .data .vars - .get(&IdIdx::from_ident(cons_callee)) + .get(&self.id_map.intern_ident(cons_callee)) .map(|v| { v.flags.contains( VarUsageInfoFlags::IS_FN_LOCAL.union(VarUsageInfoFlags::DECLARED), @@ -526,7 +526,7 @@ impl Optimizer<'_> { } (Expr::New(cons), Expr::New(alt)) => { - if self.data.contains_unresolved(test) { + if self.data.contains_unresolved(test, self.id_map) { return None; } @@ -590,7 +590,7 @@ impl Optimizer<'_> { ) if cons.left.eq_ignore_span(&alt.left) && cons.left.as_ident().is_some() => { if self .data - .ident_is_unresolved(&cons.left.as_ident().unwrap().id) + .ident_is_unresolved(&cons.left.as_ident().unwrap().id, self.id_map) { return None; } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs b/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs index 5021650002cf..083e90bdcf3a 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs @@ -58,7 +58,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&IdIdx::from_ident(lhs)) + .get(&self.id_map.intern_ident(lhs)) .map(|var| { var.flags.contains( VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL), @@ -97,7 +97,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&IdIdx::from_ident(lhs)) + .get(&self.id_map.intern_ident(lhs)) .map(|var| { var.flags.contains( VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL), diff --git a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs index c88d07a81b36..0cdfeae6fd9e 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs @@ -42,7 +42,7 @@ impl Optimizer<'_> { }) = e { if let Expr::Ident(obj) = &**obj { - let hashed_id = IdIdx::from_ident(obj); + let hashed_id = self.id_map.intern_ident(obj); let metadata = *self.functions.get(&hashed_id)?; let usage = self.data.vars.get(&hashed_id)?; @@ -101,7 +101,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&IdIdx::from_ident(i)) + .get(&self.id_map.intern_ident(i)) .map(|var| var.flags.contains(VarUsageInfoFlags::DECLARED)) .unwrap_or(false) { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs index 06c0f3dc2b38..992123deb834 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs @@ -3,7 +3,9 @@ use std::{collections::HashMap, mem::swap}; use rustc_hash::FxHashMap; use swc_common::{pass::Either, util::take::Take, Span, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; -use swc_ecma_utils::{contains_arguments, contains_this_expr, find_pat_ids, ExprFactory}; +use swc_ecma_utils::{ + contains_arguments, contains_this_expr, find_pat_ids, find_pat_ids_with_idx, ExprFactory, +}; use swc_ecma_visit::{noop_visit_type, Visit, VisitMutWith, VisitWith}; use super::{util::NormalMultiReplacer, BitCtx, Optimizer}; @@ -183,7 +185,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&IdIdx::from_ident(ident)) + .get(&self.id_map.intern_ident(ident)) .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)) .is_some() { @@ -202,7 +204,9 @@ impl Optimizer<'_> { if param.sym == "arguments" { continue; } - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(¶m.id)) { + if let Some(usage) = + self.data.vars.get(&self.id_map.intern_ident(¶m.id)) + { if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { continue; } @@ -225,7 +229,7 @@ impl Optimizer<'_> { param.id.sym, param.id.ctxt ); - vars.insert(IdIdx::from_ident(param), arg.clone()); + vars.insert(self.id_map.intern_ident(param), arg.clone()); } else { trace_op!( "iife: Trying to inline argument ({}{:?}) (not inlinable)", @@ -240,13 +244,16 @@ impl Optimizer<'_> { param.id.ctxt ); - vars.insert(IdIdx::from_ident(param), Expr::undefined(param.span())); + vars.insert( + self.id_map.intern_ident(param), + Expr::undefined(param.span()), + ); } } Pat::Rest(rest_pat) => { if let Pat::Ident(param_id) = &*rest_pat.arg { - let id = IdIdx::from_ident(param_id); + let id = self.id_map.intern_ident(param_id); if let Some(usage) = self.data.vars.get(&id) { if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || usage.ref_count != 1 @@ -351,7 +358,7 @@ impl Optimizer<'_> { ident: Some(ident), .. }) = callee { - let id = IdIdx::from_ident(ident); + let id = self.id_map.intern_ident(ident); if self .data .vars @@ -369,7 +376,7 @@ impl Optimizer<'_> { // We check for parameter and argument for (idx, param) in params.iter_mut().enumerate() { if let Pat::Ident(param) = &mut **param { - let id = IdIdx::from_ident(¶m.id); + let id = self.id_map.intern_ident(¶m.id); if let Some(usage) = self.data.vars.get(&id) { if usage.ref_count == 0 { removed.push(idx); @@ -422,12 +429,12 @@ impl Optimizer<'_> { { trace_op!("inline: inline_vars_in_node"); - let mut v = NormalMultiReplacer::new(&mut vars); + let mut v = NormalMultiReplacer::new(self.id_map, &mut vars); n.visit_mut_with(&mut v); self.changed |= v.changed; } - fn may_invoke_iife(&self, call: &mut CallExpr) -> bool { + fn may_invoke_iife(&mut self, call: &mut CallExpr) -> bool { if self.options.inline == 0 && !(self.options.reduce_vars && self.options.reduce_fns && self.options.evaluate) { @@ -512,7 +519,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&IdIdx::from_ident(i)) + .get(&self.id_map.intern_ident(i)) .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY)) .is_some() { @@ -624,7 +631,7 @@ impl Optimizer<'_> { exprs.push(arg.expr.take()); } } - if self.vars.inline_with_multi_replacer(body) { + if self.vars.inline_with_multi_replacer(body, self.id_map) { self.changed = true; } exprs.push(body.take()); @@ -752,11 +759,14 @@ impl Optimizer<'_> { } } - fn can_extract_param<'a>(&self, param_ids: impl Iterator + Clone) -> bool { + fn can_extract_param<'a>( + &mut self, + param_ids: impl Iterator + Clone, + ) -> bool { // Don't create top-level variables. if !self.may_add_ident() { for pid in param_ids.clone() { - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(pid)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(pid)) { if usage.ref_count > 1 || usage.assign_count > 0 || usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) @@ -781,7 +791,7 @@ impl Optimizer<'_> { true } - fn can_inline_fn_stmt(&self, stmt: &Stmt, for_stmt: bool) -> bool { + fn can_inline_fn_stmt(&mut self, stmt: &Stmt, for_stmt: bool) -> bool { match stmt { Stmt::Decl(Decl::Var(var)) => { for decl in &var.decls { @@ -804,7 +814,10 @@ impl Optimizer<'_> { match &decl.name { Pat::Ident(id) if id.sym == "arguments" => return false, Pat::Ident(id) => { - if self.vars.has_pending_inline_for(IdIdx::from_ident(id)) { + if self + .vars + .has_pending_inline_for(self.id_map.intern_ident(id)) + { log_abort!( "iife: [x] Cannot inline because pending inline of `{}`", id.id @@ -856,7 +869,7 @@ impl Optimizer<'_> { } fn can_inline_fn_like<'a>( - &self, + &mut self, param_ids: impl Iterator + Clone, params_len: usize, body: &BlockStmt, @@ -902,10 +915,10 @@ impl Optimizer<'_> { if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) { if params_len != 0 { - let captured = idents_captured_by::<_, IdIdx>(body); + let captured = idents_captured_by(body, self.id_map); for param in param_ids { - if captured.contains(&IdIdx::from_ident(param)) { + if captured.contains(&self.id_map.intern_ident(param)) { log_abort!( "iife: [x] Cannot inline because of the capture of `{}`", param @@ -948,7 +961,7 @@ impl Optimizer<'_> { let no_arg = arg.is_none(); if let Some(arg) = arg { - let hashed_id = IdIdx::from_ident(param); + let hashed_id = self.id_map.intern_ident(param); if let Some(usage) = self.data.vars.get_mut(&hashed_id) { if usage.ref_count == 1 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) @@ -1006,7 +1019,7 @@ impl Optimizer<'_> { let mut arg = args.get_mut(idx).map(|arg| arg.expr.take()); if let Some(arg) = &mut arg { - let hashed_id = IdIdx::from_ident(param); + let hashed_id = self.id_map.intern_ident(param); if let Some(usage) = self.data.vars.get_mut(&hashed_id) { if usage.ref_count == 1 && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) @@ -1053,7 +1066,7 @@ impl Optimizer<'_> { return None; } - if self.vars.inline_with_multi_replacer(body) { + if self.vars.inline_with_multi_replacer(body, self.id_map) { self.changed = true; } @@ -1086,7 +1099,7 @@ impl Optimizer<'_> { Stmt::Decl(Decl::Var(ref mut var)) => { for decl in &mut var.decls { if decl.init.is_some() { - let ids = find_pat_ids(decl); + let ids = find_pat_ids_with_idx(decl, self.id_map); for id in ids { if let Some(usage) = self.data.vars.get_mut(&id) { @@ -1591,14 +1604,15 @@ impl Optimizer<'_> { let mut substitutions = FxHashMap::default(); for (param, arg) in arrow.params.iter().zip(&call.args) { if let Pat::Ident(ident) = param { - substitutions.insert(IdIdx::from_ident(ident), arg.expr.clone()); + substitutions.insert(self.id_map.intern_ident(ident), arg.expr.clone()); } } // Apply substitutions to the body let mut new_body = (**body).clone(); if !substitutions.is_empty() { - let mut replacer = NormalMultiReplacer::new(&mut substitutions); + let mut replacer = + NormalMultiReplacer::new(self.id_map, &mut substitutions); new_body.visit_mut_with(&mut replacer); } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs index a08b6e9dab34..7b9accfcbe8c 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs @@ -4,7 +4,8 @@ use swc_common::{util::take::Take, EqIgnoreSpan, Mark}; use swc_ecma_ast::*; use swc_ecma_usage_analyzer::alias::{collect_infects_from, AliasConfig}; use swc_ecma_utils::{ - class_has_side_effect, collect_decls, contains_this_expr, find_pat_ids, ExprExt, Remapper, + class_has_side_effect, collect_decls, contains_this_expr, find_pat_ids_with_idx, ExprExt, + Remapper, }; use swc_ecma_visit::VisitMutWith; @@ -16,7 +17,9 @@ use crate::{ }, program_data::{ScopeData, VarUsageInfo, VarUsageInfoFlags}, util::{ - idents_captured_by, idents_used_by, idents_used_by_ignoring_nested, size::SizeWithCtxt, + ident_usage_collector::{idents_used_by, idents_used_by_ignoring_nested}, + idents_captured_by, + size::SizeWithCtxt, }, }; @@ -58,7 +61,7 @@ impl Optimizer<'_> { } } - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(ident)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(ident)) { let ref_count = usage.ref_count - u32::from(can_drop && usage.ref_count > 1); if !usage.flags.contains(VarUsageInfoFlags::VAR_INITIALIZED) { return; @@ -96,7 +99,7 @@ impl Optimizer<'_> { // No use => dropped if ref_count == 0 { - self.mode.store(IdIdx::from_ident(ident), &*init); + self.mode.store(self.id_map.intern_ident(ident), &*init); if init.may_have_side_effects(self.ctx.expr_ctx) { // TODO: Inline partially @@ -112,7 +115,7 @@ impl Optimizer<'_> { let mut inlined_into_init = false; - let hashed_id = IdIdx::from_ident(ident); + let hashed_id = self.id_map.intern_ident(ident); // We inline arrays partially if it's pure (all elements are literal), and not // modified. @@ -141,7 +144,7 @@ impl Optimizer<'_> { }) { inlined_into_init = true; - self.vars.inline_with_multi_replacer(arr); + self.vars.inline_with_multi_replacer(arr, self.id_map); report_change!( "inline: Decided to store '{}{:?}' for array access", ident.sym, @@ -149,7 +152,7 @@ impl Optimizer<'_> { ); self.vars .lits_for_array_access - .insert(IdIdx::from_ident(ident), Box::new(init.clone())); + .insert(self.id_map.intern_ident(ident), Box::new(init.clone())); } } } @@ -184,18 +187,18 @@ impl Optimizer<'_> { match init { Expr::Fn(..) | Expr::Arrow(..) | Expr::Class(..) => { self.typeofs - .insert(IdIdx::from_ident(ident), atom!("function")); + .insert(self.id_map.intern_ident(ident), atom!("function")); } Expr::Array(..) | Expr::Object(..) => { self.typeofs - .insert(IdIdx::from_ident(ident), atom!("object")); + .insert(self.id_map.intern_ident(ident), atom!("object")); } _ => {} } } if !usage.mutated() { - self.mode.store(IdIdx::from_ident(ident), &*init); + self.mode.store(self.id_map.intern_ident(ident), &*init); } if usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) { @@ -216,7 +219,7 @@ impl Optimizer<'_> { Expr::Ident(id) if !id.eq_ignore_span(ident) => { if !usage.flags.contains(VarUsageInfoFlags::ASSIGNED_FN_LOCAL) { false - } else if let Some(u) = self.data.vars.get(&IdIdx::from_ident(id)) { + } else if let Some(u) = self.data.vars.get(&self.id_map.intern_ident(id)) { let mut should_inline = !u.flags.contains(VarUsageInfoFlags::REASSIGNED) && u.flags.contains(VarUsageInfoFlags::DECLARED); @@ -278,7 +281,7 @@ impl Optimizer<'_> { } else { self.vars .lits_for_cmp - .insert(IdIdx::from_ident(ident), init.clone().into()); + .insert(self.id_map.intern_ident(ident), init.clone().into()); false } } @@ -305,7 +308,7 @@ impl Optimizer<'_> { { if !inlined_into_init { inlined_into_init = true; - self.vars.inline_with_multi_replacer(init); + self.vars.inline_with_multi_replacer(init, self.id_map); } self.mode.store(hashed_id, &*init); @@ -318,7 +321,7 @@ impl Optimizer<'_> { } = **usage; let mut inc_usage = || { if let Expr::Ident(i) = &*init { - if let Some(u) = self.data.vars.get_mut(&IdIdx::from_ident(i)) { + if let Some(u) = self.data.vars.get_mut(&self.id_map.intern_ident(i)) { u.flags |= flags & VarUsageInfoFlags::USED_AS_ARG; u.flags |= flags & VarUsageInfoFlags::USED_AS_REF; u.flags |= flags & VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY; @@ -432,9 +435,10 @@ impl Optimizer<'_> { } Expr::Fn(f) => { - let excluded: Vec = find_pat_ids::<_, IdIdx>(&f.function.params); + let excluded: Vec = + find_pat_ids_with_idx(&f.function.params, self.id_map); - for id in idents_used_by(&f.function.params) { + for id in idents_used_by(&f.function.params, &mut self.id_map) { if excluded.contains(&id) { continue; } @@ -449,9 +453,9 @@ impl Optimizer<'_> { } Expr::Arrow(f) => { - let excluded: Vec = find_pat_ids::<_, IdIdx>(&f.params); + let excluded: Vec = find_pat_ids_with_idx(&f.params, self.id_map); - for id in idents_used_by(&f.params) { + for id in idents_used_by(&f.params, &mut self.id_map) { if excluded.contains(&id) { continue; } @@ -466,7 +470,7 @@ impl Optimizer<'_> { } Expr::Object(..) if self.options.pristine_globals => { - for id in idents_used_by_ignoring_nested(init) { + for id in idents_used_by_ignoring_nested(init, &mut self.id_map) { if let Some(v_usage) = self.data.vars.get(&id) { if v_usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { return; @@ -480,7 +484,8 @@ impl Optimizer<'_> { return; } - if let Some(init_usage) = self.data.vars.get(&IdIdx::from_ident(id)) { + if let Some(init_usage) = self.data.vars.get(&self.id_map.intern_ident(id)) + { if init_usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || !init_usage.flags.contains(VarUsageInfoFlags::DECLARED) { @@ -512,7 +517,7 @@ impl Optimizer<'_> { } _ => { - for id in idents_used_by(init) { + for id in idents_used_by(init, &mut self.id_map) { if let Some(v_usage) = self.data.vars.get(&id) { if v_usage.property_mutation_count > usage.property_mutation_count || v_usage.flags.intersects( @@ -546,10 +551,11 @@ impl Optimizer<'_> { // block_scoping pass. // If the function captures the environment, we // can't inline it. - let params: Vec = find_pat_ids(&f.function.params); + let params: Vec = + find_pat_ids_with_idx(&f.function.params, self.id_map); if !params.is_empty() { - let captured = idents_captured_by::<_, IdIdx>(&f.function.body); + let captured = idents_captured_by(&f.function.body, self.id_map); for param in params { if captured.contains(¶m) { @@ -569,7 +575,7 @@ impl Optimizer<'_> { } if !inlined_into_init { - self.vars.inline_with_multi_replacer(init); + self.vars.inline_with_multi_replacer(init, self.id_map); } report_change!( @@ -579,7 +585,7 @@ impl Optimizer<'_> { self.changed = true; let ident = ident.take(); - let id = IdIdx::from_ident(&ident); + let id = self.id_map.intern_ident(&ident); self.vars.vars_for_inlining.insert(id, init.take().into()); } } @@ -639,7 +645,7 @@ impl Optimizer<'_> { return; } - let hashed_id = IdIdx::from_ident(i); + let hashed_id = self.id_map.intern_ident(i); if let Some(usage) = self.data.vars.get(&hashed_id) { if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) { trace_op!("typeofs: Storing typeof `{}{:?}`", i.sym, i.ctxt); @@ -700,7 +706,7 @@ impl Optimizer<'_> { return; } - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&i)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(&i)) { if usage .flags .contains(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM) @@ -726,7 +732,7 @@ impl Optimizer<'_> { } // Inline very simple functions. - self.vars.inline_with_multi_replacer(decl); + self.vars.inline_with_multi_replacer(decl, self.id_map); match decl { Decl::Fn(f) if self.options.inline >= 2 && f.ident.sym != *"arguments" => { if let Some(body) = &f.function.body { @@ -759,6 +765,7 @@ impl Optimizer<'_> { AliasConfig::default() .marks(Some(self.marks)) .need_all(true), + self.id_map, ) { if let Some(usage) = self.data.vars.get_mut(&i.0) { usage.ref_count += 1; @@ -766,7 +773,7 @@ impl Optimizer<'_> { } self.vars.simple_functions.insert( - IdIdx::from_ident(&i), + self.id_map.intern_ident(&i), FnExpr { ident: None, function: f.function.clone(), @@ -862,7 +869,9 @@ impl Optimizer<'_> { } }; - self.vars.vars_for_inlining.insert(IdIdx::from_ident(&i), e); + self.vars + .vars_for_inlining + .insert(self.id_map.intern_ident(&i), e); } else { log_abort!("inline: [x] Usage: {:?}", usage); } @@ -880,7 +889,10 @@ impl Optimizer<'_> { if let MemberProp::Computed(prop) = &mut me.prop { if let Expr::Lit(Lit::Num(..)) = &*prop.expr { if let Expr::Ident(obj) = &*me.obj { - let new = self.vars.lits_for_array_access.get(&IdIdx::from_ident(obj)); + let new = self + .vars + .lits_for_array_access + .get(&self.id_map.intern_ident(obj)); if let Some(new) = new { report_change!("inline: Inlined array access"); @@ -893,7 +905,7 @@ impl Optimizer<'_> { } } Expr::Ident(i) => { - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); if let Some(mut value) = self .vars .lits @@ -927,8 +939,13 @@ impl Optimizer<'_> { let new_ctxt = *new_ctxt; - if let Some(usage) = self.data.vars.get(&IdIdx::new(&id.0, id.1)).cloned() { - let new_id = IdIdx::new(&id.0, new_ctxt); + if let Some(usage) = self + .data + .vars + .get(&self.id_map.intern(&id.0, id.1)) + .cloned() + { + let new_id = self.id_map.intern(&id.0, new_ctxt); self.data.vars.insert(new_id, usage); } @@ -947,7 +964,7 @@ impl Optimizer<'_> { return; } - let hashed_id = IdIdx::from_ident(i); + let hashed_id = self.id_map.intern_ident(i); // Check without cloning if let Some(value) = self.vars.vars_for_inlining.get(&hashed_id) { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index a5d24b4b60ed..a71afc2b1d9d 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs @@ -60,6 +60,7 @@ pub(super) fn optimizer<'a>( mangle_options: Option<&'a MangleOptions>, data: &'a mut ProgramData, mode: &'a dyn Mode, + id_map: &'a mut Ids, ) -> impl 'a + VisitMut + Repeated { assert!( options.top_retain.iter().all(|s| s.trim() != ""), @@ -90,6 +91,7 @@ pub(super) fn optimizer<'a>( ctx, mode, functions: Default::default(), + id_map, } } @@ -215,6 +217,8 @@ impl Ctx { } struct Optimizer<'a> { + id_map: &'a mut Ids, + marks: Marks, changed: bool, @@ -276,7 +280,7 @@ impl Vars { } /// Returns true if something is changed. - fn inline_with_multi_replacer(&mut self, n: &mut N) -> bool + fn inline_with_multi_replacer(&mut self, n: &mut N, id_map: &mut Ids) -> bool where N: for<'aa> VisitMutWith>, N: for<'aa> VisitMutWith>, @@ -297,13 +301,14 @@ impl Vars { hoisted_props: &self.hoisted_props, vars_to_remove: &self.removed, changed: false, + id_map, }; n.visit_mut_with(&mut v); changed |= v.changed; } if !self.vars_for_inlining.is_empty() { - let mut v = NormalMultiReplacer::new(&mut self.vars_for_inlining); + let mut v = NormalMultiReplacer::new(id_map, &mut self.vars_for_inlining); n.visit_mut_with(&mut v); changed |= v.changed; } @@ -339,12 +344,12 @@ impl From<&Function> for FnMetadata { } } -impl Optimizer<'_> { - fn may_remove_ident(&self, id: &Ident) -> bool { +impl<'a> Optimizer<'a> { + fn may_remove_ident(&mut self, id: &Ident) -> bool { if self .data .vars - .get(&IdIdx::from_ident(id)) + .get(&self.id_map.intern_ident(id)) .is_some_and(|v| v.flags.contains(VarUsageInfoFlags::EXPORTED)) { return false; @@ -443,7 +448,8 @@ impl Optimizer<'_> { fn handle_stmt_likes(&mut self, stmts: &mut Vec, will_terminate: bool) where T: StmtLike + ModuleItemLike + ModuleItemExt + VisitMutWith + VisitWith, - Vec: VisitMutWith + VisitWith> + VisitWith, + Vec: + VisitMutWith + VisitWith> + VisitWith, { let mut use_asm = false; let prepend_stmts = self.prepend_stmts.take(); @@ -827,7 +833,7 @@ impl Optimizer<'_> { if let Expr::Ident(callee) = &**callee { if self.options.reduce_vars && self.options.side_effects { - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(callee)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(callee)) { if !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) && usage.flags.contains(VarUsageInfoFlags::PURE_FN) { @@ -894,7 +900,7 @@ impl Optimizer<'_> { right, .. }) => { - let old = IdIdx::from_ident(&i.id); + let old = self.id_map.intern_ident(&i.id); self.store_var_for_inlining(&mut i.id, right, true); if i.is_dummy() && self.options.unused { @@ -1809,7 +1815,7 @@ impl VisitMut for Optimizer<'_> { .. }) => { if let Some(i) = left.as_ident_mut() { - let old = IdIdx::from_ident(&i.id); + let old = self.id_map.intern_ident(&i.id); self.store_var_for_inlining(i, right, false); @@ -2045,7 +2051,7 @@ impl VisitMut for Optimizer<'_> { .entered(); self.functions - .entry(IdIdx::from_ident(&f.ident)) + .entry(self.id_map.intern_ident(&f.ident)) .or_insert_with(|| FnMetadata::from(&*f.function)); self.drop_unused_params(&mut f.function.params); @@ -2064,7 +2070,7 @@ impl VisitMut for Optimizer<'_> { fn visit_mut_fn_expr(&mut self, e: &mut FnExpr) { if let Some(ident) = &e.ident { self.functions - .entry(IdIdx::from_ident(ident)) + .entry(self.id_map.intern_ident(ident)) .or_insert_with(|| FnMetadata::from(&*e.function)); } @@ -2273,7 +2279,7 @@ impl VisitMut for Optimizer<'_> { let ctx = self.ctx.clone().with(BitCtx::TopLevel, true); self.with_ctx(ctx).handle_stmt_likes(stmts, true); - if self.vars.inline_with_multi_replacer(stmts) { + if self.vars.inline_with_multi_replacer(stmts, self.id_map) { self.changed = true; } @@ -2368,7 +2374,7 @@ impl VisitMut for Optimizer<'_> { let ctx = self.ctx.clone().with(BitCtx::TopLevel, true); s.visit_mut_children_with(&mut *self.with_ctx(ctx)); - if self.vars.inline_with_multi_replacer(s) { + if self.vars.inline_with_multi_replacer(s, self.id_map) { self.changed = true; } @@ -3010,7 +3016,7 @@ impl VisitMut for Optimizer<'_> { if let Some(Expr::Invalid(..)) = var.init.as_deref() { if let Pat::Ident(i) = &var.name { - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&i.id)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(&i.id)) { if usage .flags .contains(VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM) diff --git a/crates/swc_ecma_minifier/src/compress/optimize/ops.rs b/crates/swc_ecma_minifier/src/compress/optimize/ops.rs index 566f5eae534d..0aba69b3caee 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/ops.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/ops.rs @@ -19,7 +19,7 @@ impl Optimizer<'_> { op!("===") | op!("==") | op!("!==") | op!("!=") => { if e.left.is_ident() && e.left.eq_ignore_span(&e.right) { let e_left_ident = e.left.as_ident().unwrap(); - let hashed_id = IdIdx::from_ident(e_left_ident); + let hashed_id = self.id_map.intern_ident(e_left_ident); if let Some(t) = self.typeofs.get(&hashed_id) { match &**t { "object" | "function" => { @@ -250,7 +250,7 @@ impl Optimizer<'_> { { match &**arg { Expr::Ident(arg) => { - if let Some(value) = self.typeofs.get(&IdIdx::from_ident(arg)).cloned() { + if let Some(value) = self.typeofs.get(&self.id_map.intern_ident(arg)).cloned() { report_change!( "Converting typeof of variable to literal as we know the value" ); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/props.rs b/crates/swc_ecma_minifier/src/compress/optimize/props.rs index 844254372837..2e2ce4ba4e2b 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/props.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/props.rs @@ -38,7 +38,7 @@ impl Optimizer<'_> { // If a variable is initialized multiple time, we currently don't do anything // smart. - let usage = self.data.vars.get(&IdIdx::from_ident(&name.id))?; + let usage = self.data.vars.get(&self.id_map.intern_ident(&name.id))?; if usage.mutated() || usage.flags.intersects( VarUsageInfoFlags::USED_ABOVE_DECL @@ -73,7 +73,7 @@ impl Optimizer<'_> { let mut unknown_used_props = self .data .vars - .get(&IdIdx::from_ident(name)) + .get(&self.id_map.intern_ident(name)) .map(|v| v.accessed_props.clone()) .unwrap_or_default(); @@ -124,7 +124,7 @@ impl Optimizer<'_> { } if let Some(init) = n.init.as_deref() { - let hashed_id = IdIdx::from_ident(name); + let hashed_id = self.id_map.intern_ident(name); self.mode.store(hashed_id, init); } @@ -176,7 +176,7 @@ impl Optimizer<'_> { self.vars .hoisted_props - .insert((IdIdx::from_ident(name), key), new_var_name); + .insert((self.id_map.intern_ident(name), key), new_var_name); new_vars.push(new_var); } @@ -211,7 +211,7 @@ impl Optimizer<'_> { if let Some(value) = self .vars .hoisted_props - .get(&(IdIdx::from_ident(obj), sym.clone())) + .get(&(self.id_map.intern_ident(obj), sym.clone())) .cloned() { report_change!("hoist_props: Inlining `{}.{}`", obj.sym, sym); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs b/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs index 9a821bfac9d3..fd476fbe6451 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs @@ -41,7 +41,7 @@ impl Optimizer<'_> { // Get the identifier of the rest parameter let rest_id = match &*rest_pat.arg { - Pat::Ident(BindingIdent { id, .. }) => IdIdx::from_ident(id), + Pat::Ident(BindingIdent { id, .. }) => self.id_map.intern_ident(id), _ => return, }; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs index 873a139df7d8..badeb860c330 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs @@ -25,8 +25,10 @@ use crate::{ option::CompressOptions, program_data::{ScopeData, VarUsageInfoFlags}, util::{ - idents_used_by, idents_used_by_ignoring_nested, ExprOptExt, IdentUsageCollector, - ModuleItemExt, + ident_usage_collector::{ + idents_used_by, idents_used_by_ignoring_nested, IdentUsageCollector, + }, + ExprOptExt, ModuleItemExt, }, }; @@ -219,11 +221,15 @@ impl Optimizer<'_> { Expr::Assign(AssignExpr { op: op!("="), .. }) ) }) { - let ids_used_by_exprs: FxHashSet = - idents_used_by_ignoring_nested(&exprs); + let ids_used_by_exprs = idents_used_by_ignoring_nested( + &exprs, + &mut self.id_map, + ); - let ids_used_by_first_expr: FxHashSet = - idents_used_by_ignoring_nested(&*e.first_expr_mut()); + let ids_used_by_first_expr = idents_used_by_ignoring_nested( + &*e.first_expr_mut(), + &mut self.id_map, + ); let has_conflict = ids_used_by_exprs .iter() @@ -391,7 +397,8 @@ impl Optimizer<'_> { .. })) => { if let Some(id) = obj.as_ident() { - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(id)) { + let id_idx = self.id_map.intern_ident(id); + if let Some(usage) = self.data.vars.get(&id_idx) { id.ctxt != self.ctx.expr_ctx.unresolved_ctxt && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED) } else { @@ -741,13 +748,11 @@ impl Optimizer<'_> { // name. if let (Pat::Ident(an), Pat::Ident(bn)) = (&av.name, &bv.name) { if an.ctxt == bn.ctxt && an.sym == bn.sym { - if an.id.ctxt == bn.id.ctxt && an.id.sym == bn.id.sym { // We need to preserve side effect of `av.init` match bv.init.as_deref_mut() { Some(b_init) => { if is_ident_used_by(an, b_init) { - if is_ident_used_by(&an.id, b_init) { log_abort!( "We can't duplicated binding because \ initializer uses the previous declaration of \ @@ -833,7 +838,7 @@ impl Optimizer<'_> { && self .data .vars - .get(&IdIdx::from_ident(&a_id.id)) + .get(&self.id_map.intern_ident(a_id)) .map(|u| { !u.flags.intersects( VarUsageInfoFlags::INLINE_PREVENTED.union(VarUsageInfoFlags::DECLARED_AS_FN_EXPR) @@ -931,7 +936,13 @@ impl Optimizer<'_> { } if let Some(id) = a.ident() { - if merge_seq_cache.is_ident_used_by(id, &**e2, b_idx) { + let id = self.id_map.intern_ident(id); + if merge_seq_cache.is_ident_used_by( + &mut self.id_map, + id, + &**e2, + b_idx, + ) { break; } } @@ -942,7 +953,13 @@ impl Optimizer<'_> { } if let Some(id) = a.ident() { - if merge_seq_cache.is_ident_used_by(id, &**e2, b_idx) { + let id = self.id_map.intern_ident(id); + if merge_seq_cache.is_ident_used_by( + &mut self.id_map, + id, + &**e2, + b_idx, + ) { break; } } @@ -1038,7 +1055,7 @@ impl Optimizer<'_> { } fn is_simple_assign_target_skippable_for_seq( - &self, + &mut self, a: Option<&Mergable>, e: &SimpleAssignTarget, ) -> bool { @@ -1049,7 +1066,7 @@ impl Optimizer<'_> { } } - fn is_ident_skippable_for_seq(&self, a: Option<&Mergable>, e: &Ident) -> bool { + fn is_ident_skippable_for_seq(&mut self, a: Option<&Mergable>, e: &Ident) -> bool { if e.ctxt == self.ctx.expr_ctx.unresolved_ctxt && self.options.pristine_globals && is_global_var_with_pure_property_access(&e.sym) @@ -1093,6 +1110,7 @@ impl Optimizer<'_> { .ignore_nested(true) .need_all(true), 8, + self.id_map, ) }), Mergable::Expr(a) => match a { @@ -1103,6 +1121,7 @@ impl Optimizer<'_> { .ignore_nested(true) .need_all(true), 8, + self.id_map, )), _ => None, @@ -1115,6 +1134,7 @@ impl Optimizer<'_> { .ignore_nested(true) .need_all(true), 8, + self.id_map, )), Mergable::Drop => return false, @@ -1125,7 +1145,7 @@ impl Optimizer<'_> { return false; }; - let e_id = IdIdx::from_ident(e); + let e_id = self.id_map.intern_ident(e); if deps.contains(&(e_id, AccessKind::Reference)) || deps.contains(&(e_id, AccessKind::Call)) { @@ -1142,7 +1162,7 @@ impl Optimizer<'_> { } fn is_member_expr_skippable_for_seq( - &self, + &mut self, a: Option<&Mergable>, MemberExpr { obj, prop, .. }: &MemberExpr, ) -> bool { @@ -1170,7 +1190,7 @@ impl Optimizer<'_> { } #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))] - fn is_skippable_for_seq(&self, a: Option<&Mergable>, e: &Expr) -> bool { + fn is_skippable_for_seq(&mut self, a: Option<&Mergable>, e: &Expr) -> bool { if self.ctx.bit_ctx.contains(BitCtx::InTryBlock) { log_abort!("try block"); return false; @@ -1226,14 +1246,12 @@ impl Optimizer<'_> { match a { Mergable::Var(a) => { if is_ident_used_by(left_id, &**a) { - if is_ident_used_by(&left_id.id, &**a) { log_abort!("e.left is used by a (var)"); return false; } } Mergable::Expr(a) => { if is_ident_used_by(left_id, &**a) { - if is_ident_used_by(&left_id.id, &**a) { log_abort!("e.left is used by a (expr)"); return false; } @@ -1241,7 +1259,6 @@ impl Optimizer<'_> { Mergable::FnDecl(a) => { // TODO(kdy1): I'm not sure if this check is required. if is_ident_used_by(left_id, &**a) { - if is_ident_used_by(&left_id.id, &**a) { log_abort!("e.left is used by a ()"); return false; } @@ -1263,12 +1280,12 @@ impl Optimizer<'_> { return false; } - let used_ids: FxHashSet = idents_used_by(&*e.right); + let used_ids = idents_used_by(&*e.right, &mut self.id_map); if used_ids.is_empty() { return true; } - if used_ids.len() != 1 || !used_ids.contains(&IdIdx::from_ident(left_id)) { + if used_ids.len() != 1 || !used_ids.contains(&self.id_map.intern_ident(&left_id)) { log_abort!("bad used_ids"); return false; } @@ -1333,12 +1350,11 @@ impl Optimizer<'_> { if e.args.is_empty() { if let Callee::Expr(callee) = &e.callee { if let Expr::Fn(callee) = &**callee { - let ids: FxHashSet = idents_used_by(&callee.function); + let ids = idents_used_by(&callee.function, &mut self.id_map); - if ids - .iter() - .all(|id| id.1.outer() == self.marks.unresolved_mark) - { + if ids.iter().all(|id| { + self.id_map.get(*id).1.outer() == self.marks.unresolved_mark + }) { return true; } } @@ -1443,8 +1459,8 @@ impl Optimizer<'_> { } } - fn assignee_skippable_for_seq(&self, a: &Mergable, assignee: &Ident) -> bool { - let usgae = if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(assignee)) { + fn assignee_skippable_for_seq(&mut self, a: &Mergable, assignee: &Ident) -> bool { + let usgae = if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(assignee)) { usage } else { return false; @@ -1677,13 +1693,13 @@ impl Optimizer<'_> { // // See https://github.com/swc-project/swc/pull/6509 + let obj_ids = idents_used_by_ignoring_nested(obj, &mut self.id_map); let a_ids = match a { - Mergable::Var(a) => idents_used_by_ignoring_nested(&a.init), - Mergable::Expr(a) => idents_used_by_ignoring_nested(&**a), - Mergable::FnDecl(a) => idents_used_by_ignoring_nested(&**a), + Mergable::Var(a) => idents_used_by_ignoring_nested(&a.init, &mut self.id_map), + Mergable::Expr(a) => idents_used_by_ignoring_nested(&**a, &mut self.id_map), + Mergable::FnDecl(a) => idents_used_by_ignoring_nested(&**a, &mut self.id_map), Mergable::Drop => return Ok(false), }; - let obj_ids: FxHashSet = idents_used_by_ignoring_nested(obj); if !obj_ids.is_disjoint(&a_ids) { return Ok(false); } @@ -1733,7 +1749,7 @@ impl Optimizer<'_> { if let Some(left_obj) = b_left.obj.as_ident() { if let Some(usage) = - self.data.vars.get(&IdIdx::from_ident(left_obj)) + self.data.vars.get(&self.id_map.intern_ident(left_obj)) { if left_obj.ctxt != self.ctx.expr_ctx.unresolved_ctxt && !usage @@ -2091,7 +2107,7 @@ impl Optimizer<'_> { .. }) => { if let Expr::Ident(a_id) = &**arg { - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(a_id)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(a_id)) { if let Some(VarDeclKind::Const) = usage.var_kind { return Err(()); } @@ -2126,7 +2142,6 @@ impl Optimizer<'_> { if let Expr::Ident(orig_expr) = &*e { if orig_expr.ctxt == a_id.ctxt && orig_expr.sym == a_id.sym { - if orig_expr.sym == a_id.sym && orig_expr.ctxt == a_id.ctxt { replaced = true; *e = UpdateExpr { span: DUMMY_SP, @@ -2167,7 +2182,7 @@ impl Optimizer<'_> { .. }) => { if let Expr::Ident(a_id) = &**arg { - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(a_id)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(a_id)) { if let Some(VarDeclKind::Const) = usage.var_kind { return Err(()); } @@ -2202,7 +2217,6 @@ impl Optimizer<'_> { if let Expr::Ident(orig_expr) = &*e { if orig_expr.ctxt == a_id.ctxt && orig_expr.sym == a_id.sym { - if orig_expr.sym == a_id.sym && orig_expr.ctxt == a_id.ctxt { replaced = true; *e = UpdateExpr { span: DUMMY_SP, @@ -2268,7 +2282,8 @@ impl Optimizer<'_> { } }; - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&left_id)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(&left_id)) + { if usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) { return Ok(false); } @@ -2313,7 +2328,7 @@ impl Optimizer<'_> { _ => return Ok(false), }; - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&left)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(&left)) { let is_lit = match a.init.as_deref() { Some(e) => is_trivial_lit(e), _ => false, @@ -2356,7 +2371,7 @@ impl Optimizer<'_> { } Mergable::FnDecl(a) => { - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&a.ident)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(&a.ident)) { if usage.ref_count != 1 || usage.flags.contains(VarUsageInfoFlags::REASSIGNED) || !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL) @@ -2392,11 +2407,12 @@ impl Optimizer<'_> { } } - let take_a = |a: &mut Mergable, force_drop: bool, drop_op| { + let take_a = |this: &mut Self, a: &mut Mergable, force_drop: bool, drop_op| { match a { Mergable::Var(a) => { - if self.options.unused { - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&left_id)) { + if this.options.unused { + if let Some(usage) = this.data.vars.get(&this.id_map.intern_ident(&left_id)) + { // We are eliminating one usage, so we use 1 instead of // 0 if !force_drop @@ -2412,7 +2428,8 @@ impl Optimizer<'_> { if can_take_init || force_drop { let init = a.init.take(); - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(&left_id)) { + if let Some(usage) = this.data.vars.get(&this.id_map.intern_ident(&left_id)) + { if usage.var_kind == Some(VarDeclKind::Const) { a.init = Some(Expr::undefined(DUMMY_SP)); } @@ -2466,7 +2483,7 @@ impl Optimizer<'_> { report_change!("sequences: Merged assignment into another assignment"); self.changed = true; - let mut a_expr = take_a(a, true, false); + let mut a_expr = take_a(self, a, true, false); let a_expr = self.ignore_return_value(&mut a_expr); if let Some(a) = a_expr { @@ -2492,14 +2509,14 @@ impl Optimizer<'_> { let var_type = self .data .vars - .get(&IdIdx::from_ident(&left_id)) + .get(&self.id_map.intern_ident(&left_id)) .and_then(|info| info.merged_var_type); let Some(a_type) = a_type else { return Ok(false); }; + let b_type = b.right.get_type(self.ctx.expr_ctx); if let Some(a_op) = a_op { - let b_type = b.right.get_type(self.ctx.expr_ctx); if can_drop_op_for(a_op, b.op, var_type, a_type, b_type) { if b_left.ctxt == left_id.ctxt && b_left.sym == left_id.sym { if let Some(bin_op) = b.op.to_update() { @@ -2510,7 +2527,7 @@ impl Optimizer<'_> { b.op = a_op; - let to = take_a(a, true, true); + let to = take_a(self, a, true, true); b.right = BinExpr { span: DUMMY_SP, @@ -2559,13 +2576,13 @@ impl Optimizer<'_> { left_id.ctxt ); - let to = take_a(a, false, false); + let to = take_a(self, a, false, false); replace_id_with_expr(b, &left_id, to); if can_remove { report_change!("sequences: Removed variable ({})", left_id); - self.vars.removed.insert(IdIdx::from_ident(&left_id)); + self.vars.removed.insert(self.id_map.intern_ident(&left_id)); } dump_change_detail!("sequences: {}", dump(&*b, false)); @@ -2582,7 +2599,7 @@ impl Optimizer<'_> { /// This check blocks optimization of clearly valid optimizations like `i += /// 1, arr[i]` // - fn should_not_check_rhs_of_assign(&self, a: &Mergable, b: &mut AssignExpr) -> bool { + fn should_not_check_rhs_of_assign(&mut self, a: &Mergable, b: &mut AssignExpr) -> bool { if b.op.may_short_circuit() { return true; } @@ -2591,15 +2608,9 @@ impl Optimizer<'_> { match a { Mergable::Expr(Expr::Assign(AssignExpr { op: op!("="), .. })) => {} Mergable::Expr(Expr::Assign(..)) => { - let used_by_b = idents_used_by(&*b.right); - if used_by_b - .iter() - .any(|id| id.1 == a_id.ctxt && id.0 == a_id.sym) - { - let used_by_b: FxHashSet = idents_used_by(&*b.right); - if used_by_b.contains(&a_id.hashed_id()) { - let used_by_b: FxHashSet = idents_used_by(&*b.right); - if used_by_b.contains(&IdIdx::from_ident(a_id)) { + let used_by_b = idents_used_by(&*b.right, &mut self.id_map); + let a_id = self.id_map.intern_ident(a_id); + if used_by_b.contains(&a_id) { return true; } } @@ -2719,7 +2730,6 @@ impl Mergable<'_> { }, Mergable::Expr(s) => match &**s { Expr::Assign(s) => s.left.as_ident().map(|i| &i.id), - Expr::Assign(s) => s.left.as_ident().map(|v| &v.id), _ => None, }, Mergable::FnDecl(f) => Some(&f.ident), @@ -2742,25 +2752,28 @@ impl MergeSequenceCache { } } - fn is_ident_used_by>>( + fn is_ident_used_by<'a, N: VisitWith>>( &mut self, - ident: &Ident, + id_map: &'a mut Ids, + ident: IdIdx, node: &N, node_id: usize, ) -> bool { - let idents = self.ident_usage_cache[node_id].get_or_insert_with(|| idents_used_by(node)); - idents - .iter() - .any(|id| id.1 == ident.ctxt && id.0 == ident.sym) - idents.contains(&ident.hashed_id()) - idents.contains(&IdIdx::from_ident(ident)) + if let Some(cached) = &self.ident_usage_cache[node_id] { + cached.contains(&ident) + } else { + let idents = idents_used_by(node, id_map); + let ret = idents.contains(&ident); + self.ident_usage_cache[node_id] = Some(idents); + ret + } } fn invalidate(&mut self, node_id: usize) { self.ident_usage_cache[node_id] = None; } - fn is_top_retain(&mut self, optimizer: &Optimizer, a: &Mergable, node_id: usize) -> bool { + fn is_top_retain(&mut self, optimizer: &mut Optimizer, a: &Mergable, node_id: usize) -> bool { *self.top_retain_cache[node_id].get_or_insert_with(|| { if let Mergable::Drop = a { return true; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 6cdc3c3d0fc3..f7956267e0bd 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs @@ -185,7 +185,7 @@ impl Optimizer<'_> { return; } - if let Some(v) = self.data.vars.get(&IdIdx::from_ident(i)) { + if let Some(v) = self.data.vars.get(&self.id_map.intern_ident(i)) { let is_used_in_member = v.property_mutation_count > 0 || v.flags.contains(VarUsageInfoFlags::USED_AS_REF); if v.ref_count == 0 @@ -241,7 +241,7 @@ impl Optimizer<'_> { } pub(crate) fn should_preserve_property_access( - &self, + &mut self, e: &Expr, opts: PropertyAccessOpts, ) -> bool { @@ -257,7 +257,7 @@ impl Optimizer<'_> { } } - if let Some(usage) = self.data.vars.get(&IdIdx::from_ident(e)) { + if let Some(usage) = self.data.vars.get(&self.id_map.intern_ident(e)) { if !usage.flags.contains(VarUsageInfoFlags::DECLARED) { return true; } @@ -517,7 +517,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&IdIdx::from_ident(ident)) + .get(&self.id_map.intern_ident(ident)) .map(|v| v.usage_count == 0 && v.property_mutation_count == 0) .unwrap_or(false) { @@ -569,7 +569,7 @@ impl Optimizer<'_> { if self .data .vars - .get(&IdIdx::from_ident(ident)) + .get(&self.id_map.intern_ident(ident)) .map(|v| v.usage_count == 0 && v.property_mutation_count == 0) .unwrap_or(false) { @@ -611,7 +611,7 @@ impl Optimizer<'_> { }; if let Expr::Ident(arg) = &*update.arg { - if let Some(var) = self.data.vars.get(&IdIdx::from_ident(arg)) { + if let Some(var) = self.data.vars.get(&self.id_map.intern_ident(arg)) { // Update is counted as usage if var .flags @@ -659,7 +659,7 @@ impl Optimizer<'_> { }; if let AssignTarget::Simple(SimpleAssignTarget::Ident(left)) = &assign.left { - if let Some(var) = self.data.vars.get(&IdIdx::from_ident(&left.id)) { + if let Some(var) = self.data.vars.get(&self.id_map.intern_ident(&left.id)) { // TODO: We don't need fn_local check if var .flags @@ -724,7 +724,7 @@ impl Optimizer<'_> { return; } - if let Some(var) = self.data.vars.get(&IdIdx::from_ident(i)) { + if let Some(var) = self.data.vars.get(&self.id_map.intern_ident(i)) { // technically this is inline if !var.flags.intersects( VarUsageInfoFlags::INLINE_PREVENTED.union(VarUsageInfoFlags::EXPORTED), @@ -775,7 +775,7 @@ impl Optimizer<'_> { let can_remove_ident = self .data .vars - .get(&IdIdx::from_ident(i)) + .get(&self.id_map.intern_ident(i)) .map(|v| { (!v.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY) && v.ref_count == 0 @@ -805,7 +805,7 @@ impl Optimizer<'_> { for d in var.decls.iter_mut() { if d.init.is_none() { if let Pat::Ident(name) = &d.name { - if let Some(usage) = self.data.vars.get_mut(&IdIdx::from_ident(name)) { + if let Some(usage) = self.data.vars.get_mut(&self.id_map.intern_ident(name)) { if usage.flags.contains( VarUsageInfoFlags::IS_FN_LOCAL .union(VarUsageInfoFlags::DECLARED_AS_FN_PARAM), @@ -837,7 +837,6 @@ impl Optimizer<'_> { if let Some(Expr::Fn(f)) = v.init.as_deref_mut() { let Some(f_ident) = f.ident.as_ref() else { - let Some(f_ident) = &f.ident else { return; }; @@ -865,7 +864,7 @@ impl Optimizer<'_> { let name = v.name.as_ident()?; let obj = v.init.as_mut()?.as_mut_object()?; - let usage = self.data.vars.get(&IdIdx::from_ident(name))?; + let usage = self.data.vars.get(&self.id_map.intern_ident(name))?; if usage.flags.intersects( VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY @@ -903,7 +902,7 @@ impl Optimizer<'_> { let mut unknown_used_props = self .data .vars - .get(&IdIdx::from_ident(name)) + .get(&self.id_map.intern_ident(name)) .map(|v| v.accessed_props.clone()) .unwrap_or_default(); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/util.rs b/crates/swc_ecma_minifier/src/compress/optimize/util.rs index c78d1b40ac46..c867dcf99a98 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/util.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/util.rs @@ -7,13 +7,11 @@ use rustc_hash::{FxHashMap, FxHashSet}; use swc_atoms::Atom; use swc_common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; -use swc_ecma_transforms_base::perf::{Parallel, ParallelExt}; use swc_ecma_utils::{collect_decls, ExprCtx, ExprExt, Remapper}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; use tracing::debug; use super::{Ctx, Optimizer}; -use crate::HEAVY_TASK_PARALLELS; impl<'b> Optimizer<'b> { pub(super) fn normalize_expr(&mut self, e: &mut Expr) { @@ -217,7 +215,6 @@ pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool { /// A visitor responsible for inlining special kind of variables and removing /// (some) unused variables. Due to the order of visit, the main visitor cannot /// handle all edge cases and this type is the complement for it. -#[derive(Clone, Copy)] pub(crate) struct Finalizer<'a> { pub simple_functions: &'a FxHashMap>, pub lits: &'a FxHashMap>, @@ -228,17 +225,18 @@ pub(crate) struct Finalizer<'a> { pub vars_to_remove: &'a FxHashSet, pub changed: bool, + pub id_map: &'a mut Ids, } -impl Parallel for Finalizer<'_> { - fn create(&self) -> Self { - *self - } +// impl Parallel for Finalizer<'_> { +// fn create(&self) -> Self { +// *self +// } - fn merge(&mut self, other: Self) { - self.changed |= other.changed; - } -} +// fn merge(&mut self, other: Self) { +// self.changed |= other.changed; +// } +// } impl Finalizer<'_> { fn var(&mut self, i: IdIdx, mode: FinalizerMode) -> Option> { @@ -288,7 +286,8 @@ impl Finalizer<'_> { fn check(&mut self, e: &mut Expr, mode: FinalizerMode) { if let Expr::Ident(i) = e { - if let Some(new) = self.var(IdIdx::from_ident(i), mode) { + let id = self.id_map.intern_ident(i); + if let Some(new) = self.var(id, mode) { debug!("multi-replacer: Replaced `{}`", i); self.changed = true; @@ -333,15 +332,15 @@ impl VisitMut for Finalizer<'_> { } fn visit_mut_class_members(&mut self, members: &mut Vec) { - self.maybe_par(*HEAVY_TASK_PARALLELS, members, |v, member| { - member.visit_mut_with(v); - }); + for member in members { + member.visit_mut_with(self); + } } fn visit_mut_expr(&mut self, n: &mut Expr) { match n { Expr::Ident(i) => { - if let Some(expr) = self.lits.get(&IdIdx::from_ident(i)) { + if let Some(expr) = self.lits.get(&self.id_map.intern_ident(i)) { *n = *expr.clone(); return; } @@ -360,7 +359,7 @@ impl VisitMut for Finalizer<'_> { if let Some(ident) = self .hoisted_props - .get(&(IdIdx::from_ident(obj), sym.clone())) + .get(&(self.id_map.intern_ident(obj), sym.clone())) { self.changed = true; *n = ident.clone().into(); @@ -375,15 +374,15 @@ impl VisitMut for Finalizer<'_> { } fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec) { - self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| { - n.visit_mut_with(v); - }); + for item in n { + item.visit_mut_with(self); + } } fn visit_mut_exprs(&mut self, n: &mut Vec>) { - self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| { - n.visit_mut_with(v); - }); + for item in n { + item.visit_mut_with(self); + } } fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) { @@ -397,9 +396,9 @@ impl VisitMut for Finalizer<'_> { } fn visit_mut_module_items(&mut self, n: &mut Vec) { - self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| { - n.visit_mut_with(v); - }); + for item in n { + item.visit_mut_with(self); + } } fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option) { @@ -413,15 +412,15 @@ impl VisitMut for Finalizer<'_> { } fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec>) { - self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| { - n.visit_mut_with(v); - }); + for item in n { + item.visit_mut_with(self); + } } fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec) { - self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| { - n.visit_mut_with(v); - }); + for item in n { + item.visit_mut_with(self); + } } fn visit_mut_stmt(&mut self, n: &mut Stmt) { @@ -435,9 +434,9 @@ impl VisitMut for Finalizer<'_> { } fn visit_mut_stmts(&mut self, n: &mut Vec) { - self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| { - n.visit_mut_with(v); - }); + for item in n { + item.visit_mut_with(self); + } } fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) { @@ -445,7 +444,7 @@ impl VisitMut for Finalizer<'_> { if n.init.is_none() { if let Pat::Ident(i) = &n.name { - if self.vars_to_remove.contains(&IdIdx::from_ident(i)) { + if self.vars_to_remove.contains(&self.id_map.intern_ident(i)) { n.name.take(); } } @@ -462,7 +461,7 @@ impl VisitMut for Finalizer<'_> { n.visit_mut_children_with(self); if let Prop::Shorthand(i) = n { - if let Some(expr) = self.lits.get(&IdIdx::from_ident(i)) { + if let Some(expr) = self.lits.get(&self.id_map.intern_ident(i)) { *n = Prop::KeyValue(KeyValueProp { key: i.take().into(), value: expr.clone(), @@ -474,21 +473,23 @@ impl VisitMut for Finalizer<'_> { } pub(crate) struct NormalMultiReplacer<'a> { + pub id_map: &'a mut Ids, pub vars: &'a mut FxHashMap>, pub changed: bool, } impl<'a> NormalMultiReplacer<'a> { /// `worked` will be changed to `true` if any replacement is done - pub fn new(vars: &'a mut FxHashMap>) -> Self { + pub fn new(id_map: &'a mut Ids, vars: &'a mut FxHashMap>) -> Self { NormalMultiReplacer { + id_map, vars, changed: false, } } fn var(&mut self, i: &Ident) -> Option> { - let hashed_id = IdIdx::from_ident(i); + let hashed_id = self.id_map.intern_ident(i); let mut e = self.vars.remove(&hashed_id)?; e.visit_mut_children_with(self); diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index ebcf696b3b34..afb961fe2203 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -796,7 +796,6 @@ impl Pure<'_> { if let AssignTarget::Simple(SimpleAssignTarget::Ident(a_left)) = a_left { if let Expr::Ident(b_id) = b { if b_id.ctxt == a_left.id.ctxt && b_id.sym == a_left.id.sym { - if b_id.ctxt == a_left.ctxt && b_id.sym == a_left.sym { report_change!("evaluate: Trivial: `{}`", a_left.id); *b = *a_right.clone(); self.changed = true; diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 48d4596e442c..0e14e6a17f83 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -4,10 +4,7 @@ use swc_common::{pass::Repeated, util::take::Take, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_optimization::{debug_assert_valid, simplify}; use swc_ecma_usage_analyzer::marks::Marks; -use swc_ecma_utils::{ - parallel::{cpu_count, Parallel, ParallelExt}, - ExprCtx, -}; +use swc_ecma_utils::ExprCtx; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith}; #[cfg(feature = "debug")] use tracing::Level; @@ -45,11 +42,13 @@ pub(crate) struct PureOptimizerConfig { #[allow(clippy::needless_lifetimes)] pub(crate) fn pure_optimizer<'a>( options: &'a CompressOptions, + id_map: &'a mut Ids, marks: Marks, config: PureOptimizerConfig, ) -> impl 'a + VisitMut + Repeated { Pure { options, + id_map, config, marks, expr_ctx: ExprCtx { @@ -65,6 +64,7 @@ pub(crate) fn pure_optimizer<'a>( struct Pure<'a> { options: &'a CompressOptions, + id_map: &'a mut Ids, config: PureOptimizerConfig, marks: Marks, expr_ctx: ExprCtx, @@ -73,17 +73,17 @@ struct Pure<'a> { changed: bool, } -impl Parallel for Pure<'_> { - fn create(&self) -> Self { - Self { ..*self } - } +// impl Parallel for Pure<'_> { +// fn create(&self) -> Self { +// Self { ..*self } +// } - fn merge(&mut self, other: Self) { - if other.changed { - self.changed = true; - } - } -} +// fn merge(&mut self, other: Self) { +// if other.changed { +// self.changed = true; +// } +// } +// } impl Repeated for Pure<'_> { fn changed(&self) -> bool { @@ -174,22 +174,22 @@ impl Pure<'_> { } /// Visit `nodes`, maybe in parallel. - fn visit_par(&mut self, threshold_multiplier: usize, nodes: &mut Vec) + fn visit_par(&mut self, _threshold_multiplier: usize, nodes: &mut Vec) where N: for<'aa> VisitMutWith> + Send + Sync, { - self.maybe_par(cpu_count() * threshold_multiplier, nodes, |v, node| { - node.visit_mut_with(v); - }); + for node in nodes { + node.visit_mut_with(self); + } } fn visit_par_ref(&mut self, nodes: &mut [&mut N]) where N: for<'aa> VisitMutWith> + Send + Sync, { - self.maybe_par(0, nodes, |v, node| { - node.visit_mut_with(v); - }); + for node in nodes { + node.visit_mut_with(self); + } } } diff --git a/crates/swc_ecma_minifier/src/compress/pure/switches.rs b/crates/swc_ecma_minifier/src/compress/pure/switches.rs index 1d31a6223b8d..6991e580724a 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/switches.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/switches.rs @@ -9,7 +9,7 @@ use crate::{ pure::{Ctx, DropOpts}, util::is_primitive, }, - util::idents_used_by, + util::ident_usage_collector::idents_used_by, }; /// Methods related to option `switches`. @@ -117,7 +117,7 @@ impl Pure<'_> { } else { if !may_match_other_than_exact && !test.is_ident() - && !idents_used_by::<_, Id>(test).is_empty() + && !idents_used_by(test, &mut Ids::default()).is_empty() { may_match_other_than_exact = true; } diff --git a/crates/swc_ecma_minifier/src/compress/pure/vars.rs b/crates/swc_ecma_minifier/src/compress/pure/vars.rs index 3f1115095065..9bf4ae19dd97 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/vars.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/vars.rs @@ -165,7 +165,7 @@ impl Pure<'_> { } match &v.name { - Pat::Ident(i) => found.insert(IdIdx::from_ident(i)), + Pat::Ident(i) => found.insert(self.id_map.intern_ident(i)), _ => true, } }) diff --git a/crates/swc_ecma_minifier/src/eval.rs b/crates/swc_ecma_minifier/src/eval.rs index f9b663290b0c..933dce5f17e2 100644 --- a/crates/swc_ecma_minifier/src/eval.rs +++ b/crates/swc_ecma_minifier/src/eval.rs @@ -15,7 +15,9 @@ use crate::{ option::{CompressOptions, TopLevelOptions}, }; -pub struct Evaluator { +pub struct Evaluator<'a> { + id_map: &'a mut Ids, + expr_ctx: ExprCtx, program: Program, @@ -25,8 +27,8 @@ pub struct Evaluator { done: bool, } -impl Evaluator { - pub fn new(module: Module, marks: Marks) -> Self { +impl<'a> Evaluator<'a> { + pub fn new(module: Module, marks: Marks, id_map: &'a mut Ids) -> Self { Evaluator { expr_ctx: ExprCtx { unresolved_ctxt: SyntaxContext::empty().apply_mark(marks.unresolved_mark), @@ -39,6 +41,7 @@ impl Evaluator { marks, data: Default::default(), done: Default::default(), + id_map, } } } @@ -78,7 +81,7 @@ impl Mode for Eval { } } -impl Evaluator { +impl<'a> Evaluator<'a> { #[tracing::instrument(name = "Evaluator::run", level = "debug", skip_all)] fn run(&mut self) { if !self.done { @@ -97,6 +100,7 @@ impl Evaluator { }, None, &data, + self.id_map, )); } } @@ -192,7 +196,7 @@ impl Evaluator { self.run(); let lock = self.data.store.lock(); - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); let val = lock.cache.get(&id)?; return Some(val.clone()); @@ -212,6 +216,7 @@ impl Evaluator { e.visit_mut_with(&mut pure_optimizer( &Default::default(), + self.id_map, self.marks, PureOptimizerConfig { enable_join_vars: false, @@ -249,6 +254,7 @@ impl Evaluator { { e.visit_mut_with(&mut pure_optimizer( &Default::default(), + self.id_map, self.marks, PureOptimizerConfig { enable_join_vars: false, diff --git a/crates/swc_ecma_minifier/src/lib.rs b/crates/swc_ecma_minifier/src/lib.rs index ebf53693d873..7f0bcb5fbbb5 100644 --- a/crates/swc_ecma_minifier/src/lib.rs +++ b/crates/swc_ecma_minifier/src/lib.rs @@ -98,6 +98,7 @@ pub fn optimize( ) -> Program { let _timer = timer!("minify"); + let mut id_map = Ids::default(); let mut marks = Marks::new(); marks.top_level_ctxt = SyntaxContext::empty().apply_mark(extra.top_level_mark); marks.unresolved_mark = extra.unresolved_mark; @@ -128,6 +129,7 @@ pub fn optimize( comments, marks, // extra.unresolved_mark, + &mut id_map, )); debug_assert_valid(&n); } @@ -164,6 +166,7 @@ pub fn optimize( c, options.mangle.as_ref(), &Minification, + &mut id_map, )); perform_dce(&mut n, c, marks); @@ -177,6 +180,7 @@ pub fn optimize( n.visit_mut_with(&mut pure_optimizer( c, + &mut id_map, marks, PureOptimizerConfig { force_str_for_tpl: Minification.force_str_for_tpl(), @@ -220,7 +224,7 @@ pub fn optimize( ); if let Some(property_mangle_options) = &mangle.props { - mangle_properties(&mut n, property_mangle_options, chars); + mangle_properties(&mut n, property_mangle_options, chars, &mut id_map); } } diff --git a/crates/swc_ecma_minifier/src/metadata/mod.rs b/crates/swc_ecma_minifier/src/metadata/mod.rs index 75ce01400e17..c89a24783ab8 100644 --- a/crates/swc_ecma_minifier/src/metadata/mod.rs +++ b/crates/swc_ecma_minifier/src/metadata/mod.rs @@ -20,6 +20,7 @@ pub(crate) fn info_marker<'a>( options: Option<&'a CompressOptions>, comments: Option<&'a dyn Comments>, marks: Marks, + id_map: &'a mut Ids, // unresolved_mark: Mark, ) -> impl 'a + VisitMut { let pure_funcs = options.map(|options| { @@ -37,6 +38,7 @@ pub(crate) fn info_marker<'a>( // unresolved_mark, state: Default::default(), pure_callee: Default::default(), + id_map, } } @@ -55,13 +57,14 @@ struct InfoMarker<'a> { marks: Marks, // unresolved_mark: Mark, state: State, + id_map: &'a mut Ids, } impl InfoMarker<'_> { - fn is_pure_callee(&self, callee: &Expr) -> bool { + fn is_pure_callee(&mut self, callee: &Expr) -> bool { match callee { Expr::Ident(callee) => { - let id = IdIdx::from_ident(callee); + let id = self.id_map.intern_ident(callee); if self.pure_callee.contains(&id) { return true; } @@ -176,6 +179,7 @@ impl VisitMut for InfoMarker<'_> { fn visit_mut_module(&mut self, n: &mut Module) { n.visit_with(&mut InfoCollector { + id_map: self.id_map, comments: self.comments, pure_callees: &mut self.pure_callee, }); @@ -193,6 +197,7 @@ impl VisitMut for InfoMarker<'_> { fn visit_mut_script(&mut self, n: &mut Script) { n.visit_with(&mut InfoCollector { + id_map: self.id_map, comments: self.comments, pure_callees: &mut self.pure_callee, }); @@ -229,6 +234,7 @@ fn is_param_one_of(p: &Param, allowed: &[&str]) -> bool { const NO_SIDE_EFFECTS_FLAG: &str = "NO_SIDE_EFFECTS"; struct InfoCollector<'a> { + id_map: &'a mut Ids, comments: Option<&'a dyn Comments>, pure_callees: &'a mut FxHashSet, @@ -242,7 +248,7 @@ impl Visit for InfoCollector<'_> { if let Decl::Fn(f) = &f.decl { if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - let id = IdIdx::from_ident(&f.ident); + let id = self.id_map.intern_ident(&f.ident); self.pure_callees.insert(id); } } @@ -252,7 +258,7 @@ impl Visit for InfoCollector<'_> { f.visit_children_with(self); if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - let id = IdIdx::from_ident(&f.ident); + let id = self.id_map.intern_ident(&f.ident); self.pure_callees.insert(id); } } @@ -262,7 +268,7 @@ impl Visit for InfoCollector<'_> { if let Some(ident) = &f.ident { if has_flag(self.comments, f.function.span, NO_SIDE_EFFECTS_FLAG) { - let id = IdIdx::from_ident(ident); + let id = self.id_map.intern_ident(ident); self.pure_callees.insert(id); } } @@ -278,7 +284,7 @@ impl Visit for InfoCollector<'_> { || has_flag(self.comments, v.span, NO_SIDE_EFFECTS_FLAG) || has_flag(self.comments, init.span(), NO_SIDE_EFFECTS_FLAG) { - let id = IdIdx::from_ident(ident); + let id = self.id_map.intern_ident(ident); self.pure_callees.insert(id); } } diff --git a/crates/swc_ecma_minifier/src/pass/mangle_props.rs b/crates/swc_ecma_minifier/src/pass/mangle_props.rs index 7322b5fed930..9a67ec681187 100644 --- a/crates/swc_ecma_minifier/src/pass/mangle_props.rs +++ b/crates/swc_ecma_minifier/src/pass/mangle_props.rs @@ -102,6 +102,7 @@ pub(crate) fn mangle_properties( m: &mut Program, options: &ManglePropertiesOptions, chars: Base54Chars, + id_map: &mut Ids, ) { let mut state = ManglePropertiesState { options, @@ -112,7 +113,7 @@ pub(crate) fn mangle_properties( n: 0, }; - let mut data = analyze(&*m, None, true); + let mut data = analyze(&*m, None, true, id_map); for prop in std::mem::take(data.property_atoms.as_mut().unwrap()) { state.add(prop); diff --git a/crates/swc_ecma_minifier/src/program_data.rs b/crates/swc_ecma_minifier/src/program_data.rs index 89123b190118..9f0378e79333 100644 --- a/crates/swc_ecma_minifier/src/program_data.rs +++ b/crates/swc_ecma_minifier/src/program_data.rs @@ -18,9 +18,14 @@ use swc_ecma_usage_analyzer::{ use swc_ecma_utils::{Merge, Type, Value}; use swc_ecma_visit::VisitWith; -pub(crate) fn analyze(n: &N, marks: Option, collect_property_atoms: bool) -> ProgramData +pub(crate) fn analyze<'a, N>( + n: &N, + marks: Option, + collect_property_atoms: bool, + id_map: &'a mut Ids, +) -> ProgramData where - N: VisitWith>, + N: VisitWith>, { let data = if collect_property_atoms { ProgramData { @@ -30,7 +35,7 @@ where } else { ProgramData::default() }; - analyze_with_custom_storage(data, n, marks) + analyze_with_custom_storage(data, n, marks, id_map) } /// Analyzed info of a whole program we are working on. @@ -450,7 +455,7 @@ impl Storage for ProgramData { fn declare_decl( &mut self, ctx: Ctx, - i: &Ident, + i: IdIdx, init_type: Option>, kind: Option, ) -> &mut VarUsageInfo { @@ -458,7 +463,7 @@ impl Storage for ProgramData { // debug!(has_init = has_init, "declare_decl(`{}`)", i); // } - let v = self.vars.entry(IdIdx::from_ident(i)).or_default(); + let v = self.vars.entry(i).or_default(); if ctx.is_top_level() { v.flags |= VarUsageInfoFlags::IS_TOP_LEVEL; } @@ -510,7 +515,7 @@ impl Storage for ProgramData { v.flags |= VarUsageInfoFlags::DECLARED; // not a VarDecl, thus always inited if init_type.is_some() || kind.is_none() { - self.initialized_vars.insert(IdIdx::from_ident(i)); + self.initialized_vars.insert(i); } if ctx.in_catch_param() { v.flags |= VarUsageInfoFlags::DECLARED_AS_CATCH_PARAM; @@ -665,17 +670,17 @@ impl VarDataLike for VarUsageInfo { impl ProgramData { /// This should be used only for conditionals pass. - pub(crate) fn contains_unresolved(&self, e: &Expr) -> bool { + pub(crate) fn contains_unresolved(&self, e: &Expr, id_map: &mut Ids) -> bool { match e { - Expr::Ident(i) => self.ident_is_unresolved(i), + Expr::Ident(i) => self.ident_is_unresolved(i, id_map), Expr::Member(MemberExpr { obj, prop, .. }) => { - if self.contains_unresolved(obj) { + if self.contains_unresolved(obj, id_map) { return true; } if let MemberProp::Computed(prop) = prop { - if self.contains_unresolved(&prop.expr) { + if self.contains_unresolved(&prop.expr, id_map) { return true; } } @@ -683,39 +688,43 @@ impl ProgramData { false } Expr::Bin(BinExpr { left, right, .. }) => { - self.contains_unresolved(left) || self.contains_unresolved(right) + self.contains_unresolved(left, id_map) || self.contains_unresolved(right, id_map) + } + Expr::Unary(UnaryExpr { arg, .. }) => self.contains_unresolved(arg, id_map), + Expr::Update(UpdateExpr { arg, .. }) => self.contains_unresolved(arg, id_map), + Expr::Seq(SeqExpr { exprs, .. }) => { + exprs.iter().any(|e| self.contains_unresolved(e, id_map)) } - Expr::Unary(UnaryExpr { arg, .. }) => self.contains_unresolved(arg), - Expr::Update(UpdateExpr { arg, .. }) => self.contains_unresolved(arg), - Expr::Seq(SeqExpr { exprs, .. }) => exprs.iter().any(|e| self.contains_unresolved(e)), Expr::Assign(AssignExpr { left, right, .. }) => { // TODO (match left { AssignTarget::Simple(left) => { - self.simple_assign_target_contains_unresolved(left) + self.simple_assign_target_contains_unresolved(left, id_map) } AssignTarget::Pat(_) => false, - }) || self.contains_unresolved(right) + }) || self.contains_unresolved(right, id_map) } Expr::Cond(CondExpr { test, cons, alt, .. }) => { - self.contains_unresolved(test) - || self.contains_unresolved(cons) - || self.contains_unresolved(alt) + self.contains_unresolved(test, id_map) + || self.contains_unresolved(cons, id_map) + || self.contains_unresolved(alt, id_map) } Expr::New(NewExpr { args, .. }) => args.iter().flatten().any(|arg| match arg.spread { - Some(..) => self.contains_unresolved(&arg.expr), + Some(..) => self.contains_unresolved(&arg.expr, id_map), None => false, }), Expr::Yield(YieldExpr { arg, .. }) => { - matches!(arg, Some(arg) if self.contains_unresolved(arg)) + matches!(arg, Some(arg) if self.contains_unresolved(arg, id_map)) + } + Expr::Tpl(Tpl { exprs, .. }) => { + exprs.iter().any(|e| self.contains_unresolved(e, id_map)) } - Expr::Tpl(Tpl { exprs, .. }) => exprs.iter().any(|e| self.contains_unresolved(e)), - Expr::Paren(ParenExpr { expr, .. }) => self.contains_unresolved(expr), - Expr::Await(AwaitExpr { arg, .. }) => self.contains_unresolved(arg), + Expr::Paren(ParenExpr { expr, .. }) => self.contains_unresolved(expr, id_map), + Expr::Await(AwaitExpr { arg, .. }) => self.contains_unresolved(arg, id_map), Expr::Array(ArrayLit { elems, .. }) => elems.iter().any(|elem| match elem { - Some(elem) => self.contains_unresolved(&elem.expr), + Some(elem) => self.contains_unresolved(&elem.expr, id_map), None => false, }), @@ -724,24 +733,27 @@ impl ProgramData { args, .. }) => { - if self.contains_unresolved(callee) { + if self.contains_unresolved(callee, id_map) { return true; } - if args.iter().any(|arg| self.contains_unresolved(&arg.expr)) { + if args + .iter() + .any(|arg| self.contains_unresolved(&arg.expr, id_map)) + { return true; } false } - Expr::OptChain(o) => self.opt_chain_expr_contains_unresolved(o), + Expr::OptChain(o) => self.opt_chain_expr_contains_unresolved(o, id_map), _ => false, } } - pub(crate) fn ident_is_unresolved(&self, i: &Ident) -> bool { + pub(crate) fn ident_is_unresolved(&self, i: &Ident, id_map: &mut Ids) -> bool { // We treat `window` and `global` as resolved if is_global_var_with_pure_property_access(&i.sym) || matches!(&*i.sym, "arguments" | "window" | "global") @@ -749,22 +761,25 @@ impl ProgramData { return false; } - if let Some(v) = self.vars.get(&IdIdx::from_ident(i)) { + if let Some(v) = self.vars.get(&id_map.intern_ident(i)) { return !v.flags.contains(VarUsageInfoFlags::DECLARED); } true } - fn opt_chain_expr_contains_unresolved(&self, o: &OptChainExpr) -> bool { + fn opt_chain_expr_contains_unresolved(&self, o: &OptChainExpr, id_map: &mut Ids) -> bool { match &*o.base { - OptChainBase::Member(me) => self.member_expr_contains_unresolved(me), + OptChainBase::Member(me) => self.member_expr_contains_unresolved(me, id_map), OptChainBase::Call(OptCall { callee, args, .. }) => { - if self.contains_unresolved(callee) { + if self.contains_unresolved(callee, id_map) { return true; } - if args.iter().any(|arg| self.contains_unresolved(&arg.expr)) { + if args + .iter() + .any(|arg| self.contains_unresolved(&arg.expr, id_map)) + { return true; } @@ -773,13 +788,13 @@ impl ProgramData { } } - fn member_expr_contains_unresolved(&self, n: &MemberExpr) -> bool { - if self.contains_unresolved(&n.obj) { + fn member_expr_contains_unresolved(&self, n: &MemberExpr, id_map: &mut Ids) -> bool { + if self.contains_unresolved(&n.obj, id_map) { return true; } if let MemberProp::Computed(prop) = &n.prop { - if self.contains_unresolved(&prop.expr) { + if self.contains_unresolved(&prop.expr, id_map) { return true; } } @@ -787,21 +802,25 @@ impl ProgramData { false } - fn simple_assign_target_contains_unresolved(&self, n: &SimpleAssignTarget) -> bool { + fn simple_assign_target_contains_unresolved( + &self, + n: &SimpleAssignTarget, + id_map: &mut Ids, + ) -> bool { match n { - SimpleAssignTarget::Ident(i) => self.ident_is_unresolved(&i.id), - SimpleAssignTarget::Member(me) => self.member_expr_contains_unresolved(me), + SimpleAssignTarget::Ident(i) => self.ident_is_unresolved(&i.id, id_map), + SimpleAssignTarget::Member(me) => self.member_expr_contains_unresolved(me, id_map), SimpleAssignTarget::SuperProp(n) => { if let SuperProp::Computed(prop) = &n.prop { - if self.contains_unresolved(&prop.expr) { + if self.contains_unresolved(&prop.expr, id_map) { return true; } } false } - SimpleAssignTarget::Paren(n) => self.contains_unresolved(&n.expr), - SimpleAssignTarget::OptChain(n) => self.opt_chain_expr_contains_unresolved(n), + SimpleAssignTarget::Paren(n) => self.contains_unresolved(&n.expr, id_map), + SimpleAssignTarget::OptChain(n) => self.opt_chain_expr_contains_unresolved(n, id_map), SimpleAssignTarget::TsAs(..) | SimpleAssignTarget::TsSatisfies(..) | SimpleAssignTarget::TsNonNull(..) diff --git a/crates/swc_ecma_minifier/src/util/ident_usage_collector.rs b/crates/swc_ecma_minifier/src/util/ident_usage_collector.rs new file mode 100644 index 000000000000..ab93c0e1b509 --- /dev/null +++ b/crates/swc_ecma_minifier/src/util/ident_usage_collector.rs @@ -0,0 +1,92 @@ +use rustc_hash::FxHashSet; +use swc_ecma_ast::*; +use swc_ecma_visit::{noop_visit_type, visit_obj_and_computed, Visit, VisitWith}; + +pub(crate) struct IdentUsageCollector<'a> { + id_map: &'a mut Ids, + ids: FxHashSet, + ignore_nested: bool, +} + +impl Visit for IdentUsageCollector<'_> { + noop_visit_type!(fail); + + visit_obj_and_computed!(); + + fn visit_block_stmt_or_expr(&mut self, n: &BlockStmtOrExpr) { + if self.ignore_nested { + return; + } + + n.visit_children_with(self); + } + + fn visit_constructor(&mut self, n: &Constructor) { + if self.ignore_nested { + return; + } + + n.visit_children_with(self); + } + + fn visit_function(&mut self, n: &Function) { + if self.ignore_nested { + return; + } + + n.visit_children_with(self); + } + + fn visit_getter_prop(&mut self, n: &GetterProp) { + if self.ignore_nested { + return; + } + + n.visit_children_with(self); + } + + fn visit_setter_prop(&mut self, n: &SetterProp) { + if self.ignore_nested { + return; + } + + n.visit_children_with(self); + } + + fn visit_ident(&mut self, n: &Ident) { + let id = self.id_map.intern_ident(n); + self.ids.insert(id); + } + + fn visit_prop_name(&mut self, n: &PropName) { + if let PropName::Computed(..) = n { + n.visit_children_with(self); + } + } +} + +pub(crate) fn idents_used_by<'a, N>(n: &N, id_map: &'a mut Ids) -> FxHashSet +where + N: VisitWith>, +{ + let mut v = IdentUsageCollector { + ignore_nested: false, + ids: FxHashSet::default(), + id_map, + }; + n.visit_with(&mut v); + v.ids +} + +pub(crate) fn idents_used_by_ignoring_nested<'a, N>(n: &N, id_map: &'a mut Ids) -> FxHashSet +where + N: VisitWith>, +{ + let mut v = IdentUsageCollector { + ignore_nested: true, + ids: FxHashSet::default(), + id_map, + }; + n.visit_with(&mut v); + v.ids +} diff --git a/crates/swc_ecma_minifier/src/util/mod.rs b/crates/swc_ecma_minifier/src/util/mod.rs index 0c76faeaad32..2587f564cd55 100644 --- a/crates/swc_ecma_minifier/src/util/mod.rs +++ b/crates/swc_ecma_minifier/src/util/mod.rs @@ -7,10 +7,11 @@ use swc_atoms::Atom; use swc_common::{util::take::Take, Span, Spanned, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene}; -use swc_ecma_utils::{ident::IdentLike, DropSpan, ModuleItemLike, StmtLike, Value}; +use swc_ecma_utils::{DropSpan, ModuleItemLike, StmtLike, Value}; use swc_ecma_visit::{noop_visit_type, visit_mut_pass, visit_obj_and_computed, Visit, VisitWith}; pub(crate) mod base54; +pub(crate) mod ident_usage_collector; pub(crate) mod size; pub(crate) mod sort; @@ -335,75 +336,13 @@ where visitor.found } -#[derive(Default)] -pub(crate) struct IdentUsageCollector { - ids: FxHashSet, - ignore_nested: bool, -} - -impl Visit for IdentUsageCollector { - noop_visit_type!(fail); - - visit_obj_and_computed!(); - - fn visit_block_stmt_or_expr(&mut self, n: &BlockStmtOrExpr) { - if self.ignore_nested { - return; - } - - n.visit_children_with(self); - } - - fn visit_constructor(&mut self, n: &Constructor) { - if self.ignore_nested { - return; - } - - n.visit_children_with(self); - } - - fn visit_function(&mut self, n: &Function) { - if self.ignore_nested { - return; - } - - n.visit_children_with(self); - } - - fn visit_getter_prop(&mut self, n: &GetterProp) { - if self.ignore_nested { - return; - } - - n.visit_children_with(self); - } - - fn visit_setter_prop(&mut self, n: &SetterProp) { - if self.ignore_nested { - return; - } - - n.visit_children_with(self); - } - - fn visit_ident(&mut self, n: &Ident) { - self.ids.insert(I::from_ident(n)); - } - - fn visit_prop_name(&mut self, n: &PropName) { - if let PropName::Computed(..) = n { - n.visit_children_with(self); - } - } -} - -#[derive(Default)] -pub(crate) struct CapturedIdCollector { - ids: FxHashSet, +pub(crate) struct CapturedIdCollector<'a> { + id_map: &'a mut Ids, + ids: FxHashSet, is_nested: bool, } -impl Visit for CapturedIdCollector { +impl Visit for CapturedIdCollector<'_> { noop_visit_type!(fail); visit_obj_and_computed!(); @@ -431,7 +370,8 @@ impl Visit for CapturedIdCollec fn visit_ident(&mut self, n: &Ident) { if self.is_nested { - self.ids.insert(I::from_ident(n)); + let id = self.id_map.intern_ident(n); + self.ids.insert(id); } } @@ -442,43 +382,14 @@ impl Visit for CapturedIdCollec } } -pub(crate) fn idents_captured_by( - n: &N, -) -> FxHashSet +pub(crate) fn idents_captured_by<'a, N>(n: &N, id_map: &'a mut Ids) -> FxHashSet where - N: VisitWith>, + N: VisitWith>, { let mut v = CapturedIdCollector { is_nested: false, ids: FxHashSet::default(), - }; - n.visit_with(&mut v); - v.ids -} - -pub(crate) fn idents_used_by( - n: &N, -) -> FxHashSet -where - N: VisitWith>, -{ - let mut v = IdentUsageCollector { - ignore_nested: false, - ids: FxHashSet::default(), - }; - n.visit_with(&mut v); - v.ids -} - -pub(crate) fn idents_used_by_ignoring_nested( - n: &N, -) -> FxHashSet -where - N: VisitWith>, -{ - let mut v = IdentUsageCollector { - ignore_nested: true, - ids: FxHashSet::default(), + id_map, }; n.visit_with(&mut v); v.ids diff --git a/crates/swc_ecma_minifier/tests/eval.rs b/crates/swc_ecma_minifier/tests/eval.rs index 5bcd12952723..e83988b947cd 100644 --- a/crates/swc_ecma_minifier/tests/eval.rs +++ b/crates/swc_ecma_minifier/tests/eval.rs @@ -39,7 +39,8 @@ fn eval(module: &str, expr: &str) -> Option { .unwrap() }; - let mut evaluator = Evaluator::new(module_ast, marks); + let mut id_map = Ids::default(); + let mut evaluator = Evaluator::new(module_ast, marks, &mut id_map); let res = evaluator.eval(&expr_ast); @@ -72,12 +73,11 @@ fn eval_lit() { assert_eq!(eval("", "false").unwrap(), "false"); } -struct PartialInliner { - marks: Marks, - eval: Option, +struct PartialInliner<'a> { + eval: Evaluator<'a>, } -impl PartialInliner { +impl<'a> PartialInliner<'a> { fn run_test(src: &str, op: F) where F: FnOnce(Lrc, Module, &mut PartialInliner), @@ -99,10 +99,9 @@ impl PartialInliner { .unwrap(); module.visit_mut_with(&mut resolver(Mark::new(), Mark::new(), false)); - let mut inliner = PartialInliner { - marks, - eval: Default::default(), - }; + let mut id_map = Ids::default(); + let eval = Evaluator::new(module.clone(), marks, &mut id_map); + let mut inliner = PartialInliner { eval }; op(cm, module, &mut inliner); @@ -168,52 +167,45 @@ impl PartialInliner { } } -impl VisitMut for PartialInliner { +impl VisitMut for PartialInliner<'_> { noop_visit_mut_type!(fail); fn visit_mut_expr(&mut self, e: &mut Expr) { e.visit_mut_children_with(self); - if let Some(evaluator) = self.eval.as_mut() { - if let Expr::TaggedTpl(tt) = e { - if let Expr::Ident(ref tag) = &*tt.tag { - if &*tag.sym == "css" { - let res = evaluator.eval_tpl(&tt.tpl); - - let res = match res { - Some(v) => v, - None => return, - }; - - match res { - EvalResult::Lit(Lit::Str(s)) => { - let el = TplElement { - span: s.span, - tail: true, - // TODO possible bug for quotes - raw: Atom::new(&*s.value), - cooked: Some(Atom::new(&*s.value)), - }; - tt.tpl = Box::new(Tpl { - span: el.span, - exprs: Default::default(), - quasis: vec![el], - }); - } - _ => { - unreachable!() - } + if let Expr::TaggedTpl(tt) = e { + if let Expr::Ident(ref tag) = &*tt.tag { + if &*tag.sym == "css" { + let res = self.eval.eval_tpl(&tt.tpl); + + let res = match res { + Some(v) => v, + None => return, + }; + + match res { + EvalResult::Lit(Lit::Str(s)) => { + let el = TplElement { + span: s.span, + tail: true, + // TODO possible bug for quotes + raw: Atom::new(&*s.value), + cooked: Some(Atom::new(&*s.value)), + }; + tt.tpl = Box::new(Tpl { + span: el.span, + exprs: Default::default(), + quasis: vec![el], + }); + } + _ => { + unreachable!() } } } } } } - - fn visit_mut_module(&mut self, module: &mut Module) { - self.eval = Some(Evaluator::new(module.clone(), self.marks)); - module.visit_mut_children_with(self); - } } #[test] diff --git a/crates/swc_ecma_usage_analyzer/src/alias/ctx.rs b/crates/swc_ecma_usage_analyzer/src/alias/ctx.rs index c30c9bc09d76..4e623611e84a 100644 --- a/crates/swc_ecma_usage_analyzer/src/alias/ctx.rs +++ b/crates/swc_ecma_usage_analyzer/src/alias/ctx.rs @@ -1,21 +1,5 @@ -use std::ops::{Deref, DerefMut}; - use bitflags::bitflags; -use super::InfectionCollector; - -impl InfectionCollector { - pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx { - let orig_ctx = self.ctx; - self.ctx = ctx; - - WithCtx { - analyzer: self, - orig_ctx, - } - } -} - bitflags! { #[derive(Debug, Default, Clone, Copy)] pub struct Ctx: u8 { @@ -24,28 +8,3 @@ bitflags! { const IsPatDecl = 1 << 2; } } - -pub(super) struct WithCtx<'a> { - analyzer: &'a mut InfectionCollector, - orig_ctx: Ctx, -} - -impl Deref for WithCtx<'_> { - type Target = InfectionCollector; - - fn deref(&self) -> &Self::Target { - self.analyzer - } -} - -impl DerefMut for WithCtx<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.analyzer - } -} - -impl Drop for WithCtx<'_> { - fn drop(&mut self) { - self.analyzer.ctx = self.orig_ctx; - } -} diff --git a/crates/swc_ecma_usage_analyzer/src/alias/infection_collector.rs b/crates/swc_ecma_usage_analyzer/src/alias/infection_collector.rs new file mode 100644 index 000000000000..86a8d834b236 --- /dev/null +++ b/crates/swc_ecma_usage_analyzer/src/alias/infection_collector.rs @@ -0,0 +1,404 @@ +use rustc_hash::FxHashSet; +use swc_common::SyntaxContext; +use swc_ecma_ast::*; +use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; + +use super::{ctx::Ctx, AliasConfig}; +use crate::{ + alias::{Access, AccessKind, InfectableNode}, + util::is_global_var_with_pure_property_access, +}; + +pub struct InfectionCollector<'a> { + config: AliasConfig, + unresolved_ctxt: Option, + + bindings: FxHashSet, + + pub(super) ctx: Ctx, + + accesses: FxHashSet, + + max_entries: Option, + + id_map: &'a mut Ids, +} + +impl InfectionCollector<'_> { + fn add_binding(&mut self, e: &Ident) { + let id = self.id_map.intern_ident(e); + if self.bindings.insert(id) { + self.accesses.remove(&(id, AccessKind::Reference)); + self.accesses.remove(&(id, AccessKind::Call)); + } + } + + fn add_usage(&mut self, ident: &Ident) { + let id = self.id_map.intern_ident(ident); + if self.bindings.contains(&id) { + return; + } + + if self.unresolved_ctxt == Some(ident.ctxt) + && is_global_var_with_pure_property_access(&ident.sym) + { + return; + } + + self.accesses.insert(( + id, + if self.ctx.contains(Ctx::IsCallee) { + AccessKind::Call + } else { + AccessKind::Reference + }, + )); + } +} + +impl Visit for InfectionCollector<'_> { + noop_visit_type!(); + + fn visit_arrow_expr(&mut self, n: &ArrowExpr) { + let old = self.ctx.contains(Ctx::IsPatDecl); + + for p in &n.params { + self.ctx.insert(Ctx::IsPatDecl); + p.visit_with(self); + } + + n.body.visit_with(self); + self.ctx.set(Ctx::IsPatDecl, old); + } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if self.config.ignore_named_child_scope + && n.op == op!("=") + && n.left.as_simple().and_then(|l| l.leftmost()).is_some() + { + n.left.visit_with(self); + return; + } + + n.visit_children_with(self); + } + + fn visit_assign_pat_prop(&mut self, node: &AssignPatProp) { + node.value.visit_with(self); + + if self.ctx.contains(Ctx::IsPatDecl) { + self.add_binding(&node.key.clone().into()); + } + } + + fn visit_bin_expr(&mut self, e: &BinExpr) { + match e.op { + op!("in") + | op!("instanceof") + | op!(bin, "-") + | op!(bin, "+") + | op!("/") + | op!("*") + | op!("%") + | op!("&") + | op!("^") + | op!("|") + | op!("==") + | op!("===") + | op!("!=") + | op!("!==") + | op!("<") + | op!("<=") + | op!(">") + | op!(">=") + | op!("<<") + | op!(">>") + | op!(">>>") => { + let saved_ctx = self.ctx; + self.ctx = self.ctx - Ctx::TrackExprIdent - Ctx::IsCallee; + e.visit_children_with(self); + self.ctx = saved_ctx; + } + _ => { + let saved_ctx = self.ctx; + self.ctx = (self.ctx | Ctx::TrackExprIdent) - Ctx::IsCallee; + e.visit_children_with(self); + self.ctx = saved_ctx; + } + } + } + + fn visit_callee(&mut self, n: &Callee) { + let saved_ctx = self.ctx; + self.ctx = self.ctx - Ctx::TrackExprIdent; + n.visit_children_with(self); + self.ctx = saved_ctx; + } + + fn visit_class_decl(&mut self, node: &ClassDecl) { + self.add_binding(&node.ident); + + node.visit_children_with(self); + } + + fn visit_cond_expr(&mut self, e: &CondExpr) { + { + let saved_ctx = self.ctx; + self.ctx = self.ctx - Ctx::TrackExprIdent - Ctx::IsCallee; + e.test.visit_with(self); + self.ctx = saved_ctx; + } + + { + let saved_ctx = self.ctx; + self.ctx = self.ctx - Ctx::IsCallee; + e.cons.visit_with(self); + self.ctx = saved_ctx - Ctx::IsCallee; + e.alt.visit_with(self); + self.ctx = saved_ctx; + } + } + + fn visit_expr(&mut self, e: &Expr) { + if let Some(max_entries) = self.max_entries { + if self.accesses.len() >= max_entries { + return; + } + } + + match e { + Expr::Ident(i) => { + if self.ctx.contains(Ctx::TrackExprIdent) { + self.add_usage(i); + } + } + + _ => { + let saved_ctx = self.ctx; + self.ctx = (self.ctx | Ctx::TrackExprIdent) - Ctx::IsPatDecl; + e.visit_children_with(self); + self.ctx = saved_ctx; + } + } + } + + fn visit_fn_decl(&mut self, n: &FnDecl) { + self.add_binding(&n.ident); + + if self.config.ignore_named_child_scope { + return; + } + + n.visit_children_with(self); + } + + fn visit_fn_expr(&mut self, n: &FnExpr) { + if self.config.ignore_named_child_scope && n.ident.is_some() { + return; + } + n.visit_children_with(self); + } + + fn visit_function(&mut self, n: &Function) { + if let Some(max_entries) = self.max_entries { + if self.accesses.len() >= max_entries { + return; + } + } + + n.visit_children_with(self); + } + + fn visit_ident(&mut self, n: &Ident) { + self.add_usage(n); + } + + fn visit_member_expr(&mut self, n: &MemberExpr) { + { + let saved_ctx = self.ctx; + self.ctx.set(Ctx::TrackExprIdent, self.config.need_all); + n.obj.visit_with(self); + self.ctx = saved_ctx; + } + + { + let saved_ctx = self.ctx; + self.ctx.set(Ctx::TrackExprIdent, self.config.need_all); + n.prop.visit_with(self); + self.ctx = saved_ctx; + } + } + + fn visit_member_prop(&mut self, n: &MemberProp) { + if let MemberProp::Computed(c) = &n { + let saved_ctx = self.ctx; + self.ctx = self.ctx - Ctx::IsCallee; + c.visit_with(self); + self.ctx = saved_ctx; + } + } + + fn visit_param(&mut self, node: &Param) { + let old = self.ctx.contains(Ctx::IsPatDecl); + self.ctx.insert(Ctx::IsPatDecl); + node.visit_children_with(self); + self.ctx.set(Ctx::IsPatDecl, old); + } + + fn visit_pat(&mut self, node: &Pat) { + node.visit_children_with(self); + + if self.ctx.contains(Ctx::IsPatDecl) { + if let Pat::Ident(i) = node { + self.add_binding(i) + } + } + } + + fn visit_prop_name(&mut self, n: &PropName) { + if let PropName::Computed(c) = &n { + let saved_ctx = self.ctx; + self.ctx = self.ctx - Ctx::IsCallee; + c.visit_with(self); + self.ctx = saved_ctx; + } + } + + fn visit_stmt(&mut self, n: &Stmt) { + if let Some(max_entries) = self.max_entries { + if self.accesses.len() >= max_entries { + return; + } + } + + n.visit_children_with(self); + } + + fn visit_super_prop_expr(&mut self, n: &SuperPropExpr) { + if let SuperProp::Computed(c) = &n.prop { + let saved_ctx = self.ctx; + self.ctx = self.ctx - Ctx::IsCallee; + c.visit_with(self); + self.ctx = saved_ctx; + } + } + + fn visit_unary_expr(&mut self, e: &UnaryExpr) { + match e.op { + op!("~") + | op!(unary, "-") + | op!(unary, "+") + | op!("!") + | op!("typeof") + | op!("void") => { + let saved_ctx = self.ctx; + self.ctx = self.ctx - Ctx::TrackExprIdent - Ctx::IsCallee; + e.visit_children_with(self); + self.ctx = saved_ctx; + } + + _ => { + let saved_ctx = self.ctx; + self.ctx = (self.ctx | Ctx::TrackExprIdent) - Ctx::IsCallee; + e.visit_children_with(self); + self.ctx = saved_ctx; + } + } + } + + fn visit_update_expr(&mut self, e: &UpdateExpr) { + let saved_ctx = self.ctx; + self.ctx = (self.ctx | Ctx::TrackExprIdent) - Ctx::IsCallee; + e.arg.visit_with(self); + self.ctx = saved_ctx; + } + + fn visit_var_declarator(&mut self, n: &VarDeclarator) { + { + let old = self.ctx.contains(Ctx::IsPatDecl); + self.ctx.insert(Ctx::IsPatDecl); + n.name.visit_with(self); + self.ctx.set(Ctx::IsPatDecl, old); + } + + if self.config.ignore_named_child_scope { + if let (Pat::Ident(..), Some(..)) = (&n.name, n.init.as_deref()) { + return; + } + } + + { + let old = self.ctx.contains(Ctx::IsPatDecl); + self.ctx.remove(Ctx::IsPatDecl); + n.init.visit_with(self); + self.ctx.set(Ctx::IsPatDecl, old); + } + } +} + +fn collect<'a, N>( + node: &N, + config: AliasConfig, + max_entries: Option, + id_map: &'a mut Ids, +) -> FxHashSet<(IdIdx, AccessKind)> +where + N: InfectableNode + VisitWith>, +{ + if config.ignore_nested && node.is_fn_or_arrow_expr() { + return Default::default(); + } + + let unresolved_ctxt = config + .marks + .map(|m| SyntaxContext::empty().apply_mark(m.unresolved_mark)); + + let mut visitor = InfectionCollector { + config, + unresolved_ctxt, + + ctx: Ctx::TrackExprIdent, + + bindings: FxHashSet::default(), + accesses: FxHashSet::default(), + + max_entries, + id_map, + }; + + node.visit_with(&mut visitor); + + visitor.accesses +} + +pub fn collect_infects_from<'a, N>( + node: &N, + config: AliasConfig, + id_map: &'a mut Ids, +) -> FxHashSet +where + N: InfectableNode + VisitWith>, +{ + collect(node, config, None, id_map) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TooManyAccesses; + +/// If the number of accesses exceeds `max_entries`, it returns `Err(())`. +pub fn try_collect_infects_from<'a, N>( + node: &N, + config: AliasConfig, + max_entries: usize, + id_map: &'a mut Ids, +) -> Result, TooManyAccesses> +where + N: InfectableNode + VisitWith>, +{ + let ret = collect(node, config, Some(max_entries), id_map); + if ret.len() > max_entries { + Err(TooManyAccesses) + } else { + Ok(ret) + } +} diff --git a/crates/swc_ecma_usage_analyzer/src/alias/mod.rs b/crates/swc_ecma_usage_analyzer/src/alias/mod.rs index 79e79b1330f5..fcd9d664a168 100644 --- a/crates/swc_ecma_usage_analyzer/src/alias/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/alias/mod.rs @@ -1,14 +1,15 @@ #![allow(clippy::needless_update)] -use rustc_hash::FxHashSet; -use swc_common::SyntaxContext; use swc_ecma_ast::*; -use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; -use self::ctx::Ctx; -use crate::{marks::Marks, util::is_global_var_with_pure_property_access}; +use crate::marks::Marks; mod ctx; +mod infection_collector; + +pub use infection_collector::{ + collect_infects_from, try_collect_infects_from, InfectionCollector, TooManyAccesses, +}; #[derive(Default)] #[non_exhaustive] @@ -82,368 +83,3 @@ pub enum AccessKind { } pub type Access = (IdIdx, AccessKind); - -pub fn collect_infects_from(node: &N, config: AliasConfig) -> FxHashSet<(IdIdx, AccessKind)> -where - N: InfectableNode + VisitWith, -{ - if config.ignore_nested && node.is_fn_or_arrow_expr() { - return Default::default(); - } - - let unresolved_ctxt = config - .marks - .map(|m| SyntaxContext::empty().apply_mark(m.unresolved_mark)); - - let mut visitor = InfectionCollector { - config, - unresolved_ctxt, - - ctx: Ctx::TrackExprIdent, - - bindings: FxHashSet::default(), - accesses: FxHashSet::default(), - - max_entries: None, - }; - - node.visit_with(&mut visitor); - - visitor.accesses -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct TooManyAccesses; - -/// If the number of accesses exceeds `max_entries`, it returns `Err(())`. -pub fn try_collect_infects_from( - node: &N, - config: AliasConfig, - max_entries: usize, -) -> Result, TooManyAccesses> -where - N: InfectableNode + VisitWith, -{ - if config.ignore_nested && node.is_fn_or_arrow_expr() { - return Ok(Default::default()); - } - - let unresolved_ctxt = config - .marks - .map(|m| SyntaxContext::empty().apply_mark(m.unresolved_mark)); - - let mut visitor = InfectionCollector { - config, - unresolved_ctxt, - - ctx: Ctx::TrackExprIdent, - - bindings: FxHashSet::default(), - accesses: FxHashSet::default(), - - max_entries: Some(max_entries), - }; - - node.visit_with(&mut visitor); - - if visitor.accesses.len() > max_entries { - return Err(TooManyAccesses); - } - - Ok(visitor.accesses) -} - -pub struct InfectionCollector { - config: AliasConfig, - unresolved_ctxt: Option, - - bindings: FxHashSet, - - ctx: Ctx, - - accesses: FxHashSet, - - max_entries: Option, -} - -impl InfectionCollector { - fn add_binding(&mut self, e: &Ident) { - let id = IdIdx::from_ident(e); - if self.bindings.insert(id) { - self.accesses.remove(&(id, AccessKind::Reference)); - self.accesses.remove(&(id, AccessKind::Call)); - } - } - - fn add_usage(&mut self, ident: &Ident) { - let id = IdIdx::from_ident(ident); - if self.bindings.contains(&id) { - return; - } - - if self.unresolved_ctxt == Some(ident.ctxt) - && is_global_var_with_pure_property_access(&ident.sym) - { - return; - } - - self.accesses.insert(( - id, - if self.ctx.contains(Ctx::IsCallee) { - AccessKind::Call - } else { - AccessKind::Reference - }, - )); - } -} - -impl Visit for InfectionCollector { - noop_visit_type!(); - - fn visit_arrow_expr(&mut self, n: &ArrowExpr) { - let old = self.ctx.contains(Ctx::IsPatDecl); - - for p in &n.params { - self.ctx.insert(Ctx::IsPatDecl); - p.visit_with(self); - } - - n.body.visit_with(self); - self.ctx.set(Ctx::IsPatDecl, old); - } - - fn visit_assign_expr(&mut self, n: &AssignExpr) { - if self.config.ignore_named_child_scope - && n.op == op!("=") - && n.left.as_simple().and_then(|l| l.leftmost()).is_some() - { - n.left.visit_with(self); - return; - } - - n.visit_children_with(self); - } - - fn visit_assign_pat_prop(&mut self, node: &AssignPatProp) { - node.value.visit_with(self); - - if self.ctx.contains(Ctx::IsPatDecl) { - self.add_binding(&node.key.clone().into()); - } - } - - fn visit_bin_expr(&mut self, e: &BinExpr) { - match e.op { - op!("in") - | op!("instanceof") - | op!(bin, "-") - | op!(bin, "+") - | op!("/") - | op!("*") - | op!("%") - | op!("&") - | op!("^") - | op!("|") - | op!("==") - | op!("===") - | op!("!=") - | op!("!==") - | op!("<") - | op!("<=") - | op!(">") - | op!(">=") - | op!("<<") - | op!(">>") - | op!(">>>") => { - let ctx = self.ctx - Ctx::TrackExprIdent - Ctx::IsCallee; - e.visit_children_with(&mut *self.with_ctx(ctx)); - } - _ => { - let ctx = (self.ctx | Ctx::TrackExprIdent) - Ctx::IsCallee; - e.visit_children_with(&mut *self.with_ctx(ctx)); - } - } - } - - fn visit_callee(&mut self, n: &Callee) { - let ctx = self.ctx | Ctx::IsCallee; - n.visit_children_with(&mut *self.with_ctx(ctx)); - } - - fn visit_class_decl(&mut self, node: &ClassDecl) { - self.add_binding(&node.ident); - - node.visit_children_with(self); - } - - fn visit_cond_expr(&mut self, e: &CondExpr) { - { - let ctx = self.ctx - Ctx::TrackExprIdent - Ctx::IsCallee; - e.test.visit_with(&mut *self.with_ctx(ctx)); - } - - { - let ctx = self.ctx | Ctx::TrackExprIdent; - e.cons.visit_with(&mut *self.with_ctx(ctx)); - e.alt.visit_with(&mut *self.with_ctx(ctx)); - } - } - - fn visit_expr(&mut self, e: &Expr) { - if let Some(max_entries) = self.max_entries { - if self.accesses.len() >= max_entries { - return; - } - } - - match e { - Expr::Ident(i) => { - if self.ctx.contains(Ctx::TrackExprIdent) { - self.add_usage(i); - } - } - - _ => { - let ctx = (self.ctx | Ctx::TrackExprIdent) - Ctx::IsPatDecl; - e.visit_children_with(&mut *self.with_ctx(ctx)); - } - } - } - - fn visit_fn_decl(&mut self, n: &FnDecl) { - self.add_binding(&n.ident); - - if self.config.ignore_named_child_scope { - return; - } - - n.visit_children_with(self); - } - - fn visit_fn_expr(&mut self, n: &FnExpr) { - if self.config.ignore_named_child_scope && n.ident.is_some() { - return; - } - n.visit_children_with(self); - } - - fn visit_function(&mut self, n: &Function) { - if let Some(max_entries) = self.max_entries { - if self.accesses.len() >= max_entries { - return; - } - } - - n.visit_children_with(self); - } - - fn visit_ident(&mut self, n: &Ident) { - self.add_usage(n); - } - - fn visit_member_expr(&mut self, n: &MemberExpr) { - { - let mut ctx = self.ctx; - ctx.set(Ctx::TrackExprIdent, self.config.need_all); - n.obj.visit_with(&mut *self.with_ctx(ctx)); - } - - { - let mut ctx = self.ctx; - ctx.set(Ctx::TrackExprIdent, self.config.need_all); - n.prop.visit_with(&mut *self.with_ctx(ctx)); - } - } - - fn visit_member_prop(&mut self, n: &MemberProp) { - if let MemberProp::Computed(c) = &n { - c.visit_with(&mut *self.with_ctx(self.ctx - Ctx::IsCallee)); - } - } - - fn visit_param(&mut self, node: &Param) { - let old = self.ctx.contains(Ctx::IsPatDecl); - self.ctx.insert(Ctx::IsPatDecl); - node.visit_children_with(self); - self.ctx.set(Ctx::IsPatDecl, old); - } - - fn visit_pat(&mut self, node: &Pat) { - node.visit_children_with(self); - - if self.ctx.contains(Ctx::IsPatDecl) { - if let Pat::Ident(i) = node { - self.add_binding(i) - } - } - } - - fn visit_prop_name(&mut self, n: &PropName) { - if let PropName::Computed(c) = &n { - c.visit_with(&mut *self.with_ctx(self.ctx - Ctx::IsCallee)); - } - } - - fn visit_stmt(&mut self, n: &Stmt) { - if let Some(max_entries) = self.max_entries { - if self.accesses.len() >= max_entries { - return; - } - } - - n.visit_children_with(self); - } - - fn visit_super_prop_expr(&mut self, n: &SuperPropExpr) { - if let SuperProp::Computed(c) = &n.prop { - c.visit_with(&mut *self.with_ctx(self.ctx - Ctx::IsCallee)); - } - } - - fn visit_unary_expr(&mut self, e: &UnaryExpr) { - match e.op { - op!("~") - | op!(unary, "-") - | op!(unary, "+") - | op!("!") - | op!("typeof") - | op!("void") => { - let ctx = self.ctx - Ctx::TrackExprIdent - Ctx::IsCallee; - e.visit_children_with(&mut *self.with_ctx(ctx)); - } - - _ => { - let ctx = (self.ctx | Ctx::TrackExprIdent) - Ctx::IsCallee; - e.visit_children_with(&mut *self.with_ctx(ctx)); - } - } - } - - fn visit_update_expr(&mut self, e: &UpdateExpr) { - let ctx = self.ctx - Ctx::TrackExprIdent - Ctx::IsCallee; - e.arg.visit_with(&mut *self.with_ctx(ctx)); - } - - fn visit_var_declarator(&mut self, n: &VarDeclarator) { - { - let old = self.ctx.contains(Ctx::IsPatDecl); - self.ctx.insert(Ctx::IsPatDecl); - n.name.visit_with(self); - self.ctx.set(Ctx::IsPatDecl, old); - } - - if self.config.ignore_named_child_scope { - if let (Pat::Ident(..), Some(..)) = (&n.name, n.init.as_deref()) { - return; - } - } - - { - let old = self.ctx.contains(Ctx::IsPatDecl); - self.ctx.remove(Ctx::IsPatDecl); - n.init.visit_with(self); - self.ctx.set(Ctx::IsPatDecl, old); - } - } -} diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs index 9deefe939825..48cbabcbcd65 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs @@ -1,27 +1,9 @@ #![allow(dead_code)] -use std::ops::{Deref, DerefMut}; - use bitflags::bitflags; use swc_ecma_ast::VarDeclKind; use swc_ecma_utils::{Type, Value}; -use super::{storage::Storage, UsageAnalyzer}; - -impl UsageAnalyzer -where - S: Storage, -{ - pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx { - let orig_ctx = self.ctx; - self.ctx = ctx; - WithCtx { - analyzer: self, - orig_ctx, - } - } -} - #[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] pub struct Ctx { @@ -103,40 +85,3 @@ impl BitContext { self } } - -pub(super) struct WithCtx<'a, S> -where - S: Storage, -{ - analyzer: &'a mut UsageAnalyzer, - orig_ctx: Ctx, -} - -impl Deref for WithCtx<'_, S> -where - S: Storage, -{ - type Target = UsageAnalyzer; - - fn deref(&self) -> &Self::Target { - self.analyzer - } -} - -impl DerefMut for WithCtx<'_, S> -where - S: Storage, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.analyzer - } -} - -impl Drop for WithCtx<'_, S> -where - S: Storage, -{ - fn drop(&mut self) { - self.analyzer.ctx = self.orig_ctx; - } -} diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs index a59c9d1e2066..29bbfa2a83f8 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs @@ -2,7 +2,7 @@ use ctx::BitContext; use rustc_hash::FxHashMap; use swc_common::SyntaxContext; use swc_ecma_ast::*; -use swc_ecma_utils::{find_pat_ids, ExprCtx, ExprExt, IsEmpty, StmtExt, Type, Value}; +use swc_ecma_utils::{find_pat_ids_with_idx, ExprCtx, ExprExt, IsEmpty, StmtExt, Type, Value}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use swc_timer::timer; @@ -21,18 +21,23 @@ pub mod storage; /// TODO: Scope-local. (Including block) /// /// If `marks` is [None], markers are ignored. -pub fn analyze_with_storage(n: &N, marks: Option) -> S +pub fn analyze_with_storage<'a, S, N>(n: &N, marks: Option, id_map: &'a mut Ids) -> S where S: Storage, - N: VisitWith>, + N: VisitWith>, { - analyze_with_custom_storage(Default::default(), n, marks) + analyze_with_custom_storage(Default::default(), n, marks, id_map) } -pub fn analyze_with_custom_storage(data: S, n: &N, marks: Option) -> S +pub fn analyze_with_custom_storage<'a, S, N>( + data: S, + n: &N, + marks: Option, + id_map: &'a mut Ids, +) -> S where S: Storage, - N: VisitWith>, + N: VisitWith>, { let _timer = timer!("analyze"); @@ -49,6 +54,7 @@ where remaining_depth: 3, }, used_recursively: FxHashMap::default(), + id_map, }; n.visit_with(&mut v); let top_scope = v.scope; @@ -73,7 +79,7 @@ enum RecursiveUsage { /// This assumes there are no two variable with same name and same span hygiene. #[derive(Debug)] -pub struct UsageAnalyzer +pub struct UsageAnalyzer<'a, S> where S: Storage, { @@ -83,9 +89,10 @@ where ctx: Ctx, expr_ctx: ExprCtx, used_recursively: FxHashMap, + id_map: &'a mut Ids, } -impl UsageAnalyzer +impl UsageAnalyzer<'_, S> where S: Storage, { @@ -102,6 +109,7 @@ where expr_ctx: self.expr_ctx, scope: Default::default(), used_recursively, + id_map: self.id_map, }; let ret = op(&mut child); @@ -150,7 +158,7 @@ where self.scope.mark_used_arguments(); } - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); if let Some(recr) = self.used_recursively.get(&id) { if let RecursiveUsage::Var { can_ignore: false } = recr { @@ -165,7 +173,7 @@ where } fn report_assign_pat(&mut self, p: &Pat, is_read_modify: bool) { - for id in find_pat_ids(p) { + for id in find_pat_ids_with_idx(p, self.id_map) { // It's hard to determined the type of pat assignment self.data .report_assign(self.ctx, id, is_read_modify, Value::Unknown) @@ -174,7 +182,7 @@ where if let Pat::Expr(e) = p { match &**e { Expr::Ident(i) => { - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); self.data .report_assign(self.ctx, id, is_read_modify, Value::Unknown) } @@ -185,7 +193,7 @@ where fn report_assign_expr_if_ident(&mut self, e: Option<&Ident>, is_op: bool, ty: Value) { if let Some(i) = e { - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); self.data.report_assign(self.ctx, id, is_op, ty) } } @@ -199,7 +207,8 @@ where ) -> &mut S::VarData { self.scope.add_declared_symbol(i); - let v = self.data.declare_decl(self.ctx, i, init_type, kind); + let id = self.id_map.intern_ident(i); + let v = self.data.declare_decl(self.ctx, id, init_type, kind); if is_fn_decl { v.mark_declared_as_fn_decl(); @@ -223,22 +232,24 @@ where fn mark_mutation_if_member(&mut self, e: Option<&MemberExpr>) { if let Some(m) = e { for_each_id_ref_in_expr(&m.obj, &mut |id| { - let id = IdIdx::from_ident(id); + let id = self.id_map.intern_ident(id); self.data.mark_property_mutation(id) }); } } } -impl Visit for UsageAnalyzer +impl Visit for UsageAnalyzer<'_, S> where S: Storage, { noop_visit_type!(); fn visit_array_lit(&mut self, n: &ArrayLit) { - let ctx = self.ctx.with(BitContext::IsIdRef, true); - n.visit_children_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -248,11 +259,13 @@ where fn visit_arrow_expr(&mut self, n: &ArrowExpr) { self.with_child(n.ctxt, ScopeKind::Fn, |child| { { - let ctx = child + let saved_ctx = child.ctx; + child.ctx = child .ctx .with(BitContext::InPatOfParam, true) .with(BitContext::InlinePrevented, true); - n.params.visit_with(&mut *child.with_ctx(ctx)); + n.params.visit_with(child); + child.ctx = saved_ctx; } match &*n.body { @@ -274,20 +287,22 @@ where let is_op_assign = n.op != op!("="); n.left.visit_with(self); + let saved_ctx = self.ctx; // We mark bar in // // foo[i] = bar // // as `used_as_ref`. - let ctx = self.ctx.with( + self.ctx = self.ctx.with( BitContext::IsIdRef, matches!(n.op, op!("=") | op!("||=") | op!("&&=") | op!("??=")), ); - n.right.visit_with(&mut *self.with_ctx(ctx)); + n.right.visit_with(self); + self.ctx = saved_ctx; match &n.left { AssignTarget::Pat(p) => { - for id in find_pat_ids(p) { + for id in find_pat_ids_with_idx(p, self.id_map) { self.data.report_assign( self.ctx, id, @@ -321,9 +336,10 @@ where ignore_named_child_scope: true, ..Default::default() }, + self.id_map, ) { if v.is_none() { - let left = IdIdx::from_ident(&left); + let left = self.id_map.intern_ident(&left); v = Some(self.data.var_or_default(left)); } @@ -337,12 +353,14 @@ where p.left.visit_with(self); { - let ctx = Ctx { + let saved_ctx = self.ctx; + self.ctx = Ctx { bit_ctx: self.ctx.bit_ctx.with(BitContext::InPatOfParam, false), var_decl_kind_of_pat: None, ..self.ctx }; - p.right.visit_with(&mut *self.with_ctx(ctx)) + p.right.visit_with(self); + self.ctx = saved_ctx; } } @@ -351,23 +369,26 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_await_expr(&mut self, n: &AwaitExpr) { - let ctx = self.ctx.with(BitContext::InAwaitArg, true); - n.visit_children_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::InAwaitArg, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } fn visit_bin_expr(&mut self, e: &BinExpr) { if e.op.may_short_circuit() { - let ctx = self.ctx.with(BitContext::IsIdRef, true); - e.left.visit_with(&mut *self.with_ctx(ctx)); - let ctx = self - .ctx + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + e.left.visit_with(self); + self.ctx = saved_ctx .with(BitContext::InCond, true) .with(BitContext::IsIdRef, true); - self.with_ctx(ctx).visit_in_cond(&e.right); + self.visit_in_cond(&e.right); + self.ctx = saved_ctx; } else { if e.op == op!("in") { for_each_id_ref_in_expr(&e.right, &mut |obj| { - let id = IdIdx::from_ident(obj); + let id = self.id_map.intern_ident(obj); let var = self.data.var_or_default(id); var.mark_used_as_ref(); @@ -384,8 +405,10 @@ where }) } - let ctx = self.ctx.with(BitContext::IsIdRef, false); - e.visit_children_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, false); + e.visit_children_with(self); + self.ctx = saved_ctx; } } @@ -423,13 +446,15 @@ where .unwrap_or_default(); { - let ctx = self.ctx.with(BitContext::InlinePrevented, inline_prevented); - n.callee.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::InlinePrevented, inline_prevented); + n.callee.visit_with(self); + self.ctx = saved_ctx; } if let Callee::Expr(callee) = &n.callee { for_each_id_ref_in_expr(callee, &mut |i| { - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); self.data.var_or_default(id).mark_used_as_callee(); }); @@ -443,7 +468,7 @@ where if is_safe_to_access_prop(&arg.expr) { if let Pat::Ident(id) = &p.pat { - let id = IdIdx::from_ident(id); + let id = self.id_map.intern_ident(id); self.data .var_or_default(id) .mark_initialized_with_safe_value(); @@ -462,7 +487,7 @@ where if is_safe_to_access_prop(&arg.expr) { if let Pat::Ident(id) = &p { - let id = IdIdx::from_ident(id); + let id = self.id_map.intern_ident(&id); self.data .var_or_default(id) .mark_initialized_with_safe_value(); @@ -477,11 +502,13 @@ where } { - let ctx = self + let saved_ctx = self.ctx; + self.ctx = self .ctx .with(BitContext::InlinePrevented, inline_prevented) .with(BitContext::IsIdRef, true); - n.args.visit_with(&mut *self.with_ctx(ctx)); + n.args.visit_with(self); + self.ctx = saved_ctx; let call_may_mutate = match &n.callee { Callee::Expr(e) => call_may_mutate(e, self.expr_ctx), @@ -491,7 +518,7 @@ where if call_may_mutate { for a in &n.args { for_each_id_ref_in_expr(&a.expr, &mut |id| { - let id = IdIdx::from_ident(id); + let id = self.id_map.intern_ident(id); self.data.mark_property_mutation(id); }); } @@ -500,7 +527,7 @@ where for arg in &n.args { for_each_id_ref_in_expr(&arg.expr, &mut |arg| { - let id = IdIdx::from_ident(arg); + let id = self.id_map.intern_ident(arg); self.data.var_or_default(id).mark_used_as_arg(); }) } @@ -512,7 +539,7 @@ where } Expr::Member(m) if !m.obj.is_ident() => { for_each_id_ref_in_expr(&m.obj, &mut |id| { - let id = IdIdx::from_ident(id); + let id = self.id_map.intern_ident(id); self.data.var_or_default(id).mark_used_as_ref() }) } @@ -527,16 +554,20 @@ where )] fn visit_catch_clause(&mut self, n: &CatchClause) { { - let ctx = self + let saved_ctx = self.ctx; + self.ctx = self .ctx .with(BitContext::InCond, true) .with(BitContext::InCatchParam, true); - n.param.visit_with(&mut *self.with_ctx(ctx)); + n.param.visit_with(self); + self.ctx = saved_ctx; } { - let ctx = self.ctx.with(BitContext::InCond, true); - self.with_ctx(ctx).visit_in_cond(&n.body); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::InCond, true); + self.visit_in_cond(&n.body); + self.ctx = saved_ctx; } } @@ -548,8 +579,10 @@ where n.decorators.visit_with(self); { - let ctx = self.ctx.with(BitContext::InlinePrevented, true); - n.super_class.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::InlinePrevented, true); + n.super_class.visit_with(self); + self.ctx = saved_ctx; } self.with_child(n.ctxt, ScopeKind::Fn, |child| n.body.visit_with(child)) @@ -587,8 +620,10 @@ where self.with_child(n.function.ctxt, ScopeKind::Fn, |a| { n.key.visit_with(a); { - let ctx = a.ctx.with(BitContext::InPatOfParam, true); - n.function.params.visit_with(&mut *a.with_ctx(ctx)); + let saved_ctx = a.ctx; + a.ctx = saved_ctx.with(BitContext::InPatOfParam, true); + n.function.params.visit_with(a); + a.ctx = saved_ctx; } n.function.visit_with(a); @@ -600,9 +635,10 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_class_prop(&mut self, n: &ClassProp) { - let ctx = self.ctx.with(BitContext::IsIdRef, true); - - n.visit_children_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -610,9 +646,10 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_computed_prop_name(&mut self, n: &ComputedPropName) { - let ctx = self.ctx.with(BitContext::IsIdRef, true); - - n.visit_children_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -621,18 +658,24 @@ where )] fn visit_cond_expr(&mut self, n: &CondExpr) { { - let ctx = self.ctx.with(BitContext::IsIdRef, false); - - n.test.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, false); + n.test.visit_with(self); + self.ctx = saved_ctx; } { - let ctx = self + let saved_ctx = self.ctx; + self.ctx = self .ctx .with(BitContext::InCond, true) .with(BitContext::IsIdRef, true); - self.with_ctx(ctx).visit_in_cond(&n.cons); - self.with_ctx(ctx).visit_in_cond(&n.alt); + self.visit_in_cond(&n.cons); + self.ctx = saved_ctx + .with(BitContext::InCond, true) + .with(BitContext::IsIdRef, true); + self.visit_in_cond(&n.alt); + self.ctx = saved_ctx; } } @@ -643,8 +686,10 @@ where fn visit_constructor(&mut self, n: &Constructor) { self.with_child(n.ctxt, ScopeKind::Fn, |child| { { - let ctx = child.ctx.with(BitContext::InPatOfParam, true); - n.params.visit_with(&mut *child.with_ctx(ctx)); + let saved_ctx = child.ctx; + child.ctx = child.ctx.with(BitContext::InPatOfParam, true); + n.params.visit_with(child); + child.ctx = saved_ctx; } // Bypass visit_block_stmt @@ -660,13 +705,13 @@ where match d { DefaultDecl::Class(c) => { if let Some(i) = &c.ident { - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); self.data.var_or_default(id).prevent_inline(); } } DefaultDecl::Fn(f) => { if let Some(i) = &f.ident { - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); self.data.var_or_default(id).prevent_inline(); } } @@ -679,10 +724,12 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_do_while_stmt(&mut self, n: &DoWhileStmt) { - n.body - .visit_with(&mut *self.with_ctx(self.ctx.with(BitContext::ExecutedMultipleTime, true))); - n.test - .visit_with(&mut *self.with_ctx(self.ctx.with(BitContext::ExecutedMultipleTime, true))); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::ExecutedMultipleTime, true); + n.body.visit_with(self); + self.ctx = saved_ctx.with(BitContext::ExecutedMultipleTime, true); + n.test.visit_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -694,15 +741,15 @@ where match &n.decl { Decl::Class(c) => { - let id = IdIdx::from_ident(&c.ident); + let id = self.id_map.intern_ident(&c.ident); self.data.var_or_default(id).prevent_inline(); } Decl::Fn(f) => { - let id = IdIdx::from_ident(&f.ident); + let id = self.id_map.intern_ident(&f.ident); self.data.var_or_default(id).prevent_inline(); } Decl::Var(v) => { - let ids = find_pat_ids(v); + let ids = find_pat_ids_with_idx(v, self.id_map); for id in ids { self.data.var_or_default(id).mark_as_exported(); @@ -717,16 +764,17 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { - let ctx = self.ctx.with(BitContext::IsIdRef, true); - - n.visit_children_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { match &n.orig { ModuleExportName::Ident(orig) => { self.report_usage(orig); - let id = IdIdx::from_ident(orig); + let id = self.id_map.intern_ident(orig); let v = self.data.var_or_default(id); v.prevent_inline(); v.mark_used_as_ref(); @@ -740,6 +788,7 @@ where tracing::instrument(level = "debug", skip(self, e)) )] fn visit_expr(&mut self, e: &Expr) { + let saved_ctx = self.ctx; let ctx = Ctx { bit_ctx: self .ctx @@ -751,8 +800,9 @@ where in_pat_of_var_decl_with_init: None, ..self.ctx }; - - e.visit_children_with(&mut *self.with_ctx(ctx)); + self.ctx = ctx; + e.visit_children_with(self); + self.ctx = saved_ctx; if let Expr::Ident(i) = e { #[cfg(feature = "tracing-spans")] @@ -765,7 +815,9 @@ where // ); } - self.with_ctx(ctx).report_usage(i); + self.ctx = ctx; + self.report_usage(i); + self.ctx = saved_ctx; } } @@ -778,7 +830,7 @@ where if e.spread.is_some() { for_each_id_ref_in_expr(&e.expr, &mut |i| { - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); self.data.var_or_default(id).mark_indexed_with_dynamic_key(); }); } @@ -789,13 +841,14 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_fn_decl(&mut self, n: &FnDecl) { - let ctx = self + let saved_ctx = self.ctx; + self.ctx = self .ctx .with(BitContext::InDeclWithNoSideEffectForMemberAccess, true); - self.with_ctx(ctx) - .declare_decl(&n.ident, Some(Value::Known(Type::Obj)), None, true); + self.declare_decl(&n.ident, Some(Value::Known(Type::Obj)), None, true); + self.ctx = saved_ctx; - let id = IdIdx::from_ident(&n.ident); + let id = self.id_map.intern_ident(&n.ident); if n.function.body.is_empty() { self.data.var_or_default(id).mark_as_pure_fn(); } @@ -813,9 +866,10 @@ where ignore_named_child_scope: true, ..Default::default() }, + self.id_map, ) { if v.is_none() { - v = Some(self.data.var_or_default(IdIdx::from_ident(&n.ident))); + v = Some(self.data.var_or_default(self.id_map.intern_ident(&n.ident))); } v.as_mut().unwrap().add_infects_to(id); @@ -830,11 +884,11 @@ where fn visit_fn_expr(&mut self, n: &FnExpr) { if let Some(n_id) = &n.ident { self.data - .var_or_default(IdIdx::from_ident(n_id)) + .var_or_default(self.id_map.intern_ident(n_id)) .mark_declared_as_fn_expr(); self.used_recursively - .insert(IdIdx::from_ident(n_id), RecursiveUsage::FnOrClass); + .insert(self.id_map.intern_ident(n_id), RecursiveUsage::FnOrClass); n.visit_children_with(self); @@ -847,15 +901,17 @@ where ignore_named_child_scope: true, ..Default::default() }, + self.id_map, ) { if v.is_none() { - v = Some(self.data.var_or_default(IdIdx::from_ident(n_id))); + v = Some(self.data.var_or_default(self.id_map.intern_ident(n_id))); } v.as_mut().unwrap().add_infects_to(id); } } - self.used_recursively.remove(&IdIdx::from_ident(n_id)); + self.used_recursively + .remove(&self.id_map.intern_ident(n_id)); } else { n.visit_children_with(self); } @@ -869,26 +925,33 @@ where n.right.visit_with(self); self.with_child(SyntaxContext::empty(), ScopeKind::Block, |child| { + let saved_ctx = child.ctx; let head_ctx = child .ctx .with(BitContext::InLeftOfForLoop, true) .with(BitContext::IsIdRef, true) .with(BitContext::ExecutedMultipleTime, true) .with(BitContext::InCond, true); - n.left.visit_with(&mut *child.with_ctx(head_ctx)); + child.ctx = head_ctx; + n.left.visit_with(child); + child.ctx = saved_ctx; n.right.visit_with(child); if let ForHead::Pat(pat) = &n.left { - child.with_ctx(head_ctx).report_assign_pat(pat, true) + child.ctx = head_ctx; + child.report_assign_pat(pat, true); + child.ctx = saved_ctx; } - let ctx = child + let saved_ctx = child.ctx; + child.ctx = child .ctx .with(BitContext::ExecutedMultipleTime, true) .with(BitContext::InCond, true); - child.with_ctx(ctx).visit_in_cond(&n.body); + child.visit_in_cond(&n.body); + child.ctx = saved_ctx; }); } @@ -900,23 +963,30 @@ where n.right.visit_with(self); self.with_child(SyntaxContext::empty(), ScopeKind::Block, |child| { + let saved_ctx = child.ctx; let head_ctx = child .ctx .with(BitContext::InLeftOfForLoop, true) .with(BitContext::IsIdRef, true) .with(BitContext::ExecutedMultipleTime, true) .with(BitContext::InCond, true); - n.left.visit_with(&mut *child.with_ctx(head_ctx)); + child.ctx = head_ctx; + n.left.visit_with(child); + child.ctx = saved_ctx; if let ForHead::Pat(pat) = &n.left { - child.with_ctx(head_ctx).report_assign_pat(pat, true) + child.ctx = head_ctx; + child.report_assign_pat(pat, true); + child.ctx = saved_ctx; } - let ctx = child + let saved_ctx = child.ctx; + child.ctx = child .ctx .with(BitContext::ExecutedMultipleTime, true) .with(BitContext::InCond, true); - child.with_ctx(ctx).visit_in_cond(&n.body); + child.visit_in_cond(&n.body); + child.ctx = saved_ctx; }); } @@ -927,14 +997,19 @@ where fn visit_for_stmt(&mut self, n: &ForStmt) { n.init.visit_with(self); + let saved_ctx = self.ctx; let ctx = self .ctx .with(BitContext::ExecutedMultipleTime, true) .with(BitContext::InCond, true); - self.with_ctx(ctx).visit_in_cond(&n.test); - self.with_ctx(ctx).visit_in_cond(&n.update); - self.with_ctx(ctx).visit_in_cond(&n.body); + self.ctx = ctx; + self.visit_in_cond(&n.test); + self.ctx = ctx; + self.visit_in_cond(&n.update); + self.ctx = ctx; + self.visit_in_cond(&n.body); + self.ctx = saved_ctx; } #[cfg_attr( @@ -944,18 +1019,19 @@ where fn visit_function(&mut self, n: &Function) { n.decorators.visit_with(self); - let ctx = Ctx { ..self.ctx }; + let saved_ctx = self.ctx; - self.with_ctx(ctx) - .with_child(n.ctxt, ScopeKind::Fn, |child| { - n.params.visit_with(child); + self.with_child(n.ctxt, ScopeKind::Fn, |child| { + n.params.visit_with(child); - if let Some(body) = &n.body { - // We use visit_children_with instead of visit_with to bypass block scope - // handler. - body.visit_children_with(child); - } - }) + if let Some(body) = &n.body { + // We use visit_children_with instead of visit_with to bypass block scope + // handler. + body.visit_children_with(child); + } + }); + + self.ctx = saved_ctx; } #[cfg_attr( @@ -975,11 +1051,15 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_if_stmt(&mut self, n: &IfStmt) { + let saved_ctx = self.ctx; let ctx = self.ctx.with(BitContext::InCond, true); + self.ctx = ctx; n.test.visit_with(self); - - self.with_ctx(ctx).visit_in_cond(&n.cons); - self.with_ctx(ctx).visit_in_cond(&n.alt); + self.ctx = ctx; + self.visit_in_cond(&n.cons); + self.ctx = ctx; + self.visit_in_cond(&n.alt); + self.ctx = saved_ctx; } fn visit_import_default_specifier(&mut self, n: &ImportDefaultSpecifier) { @@ -999,6 +1079,7 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_jsx_element_name(&mut self, n: &JSXElementName) { + let saved_ctx = self.ctx; let ctx = Ctx { bit_ctx: self .ctx @@ -1011,11 +1092,15 @@ where ..self.ctx }; - n.visit_children_with(&mut *self.with_ctx(ctx)); + self.ctx = ctx; + n.visit_children_with(self); + self.ctx = saved_ctx; if let JSXElementName::Ident(i) = n { - self.with_ctx(ctx).report_usage(i); - let id = IdIdx::from_ident(i); + self.ctx = ctx; + self.report_usage(i); + self.ctx = saved_ctx; + let id = self.id_map.intern_ident(i); self.data.var_or_default(id).mark_used_as_jsx_callee(); } } @@ -1026,8 +1111,10 @@ where )] fn visit_member_expr(&mut self, e: &MemberExpr) { { - let ctx = self.ctx.with(BitContext::IsIdRef, false); - e.obj.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, false); + e.obj.visit_with(self); + self.ctx = saved_ctx; } if let MemberProp::Computed(c) = &e.prop { @@ -1035,7 +1122,7 @@ where } for_each_id_ref_in_expr(&e.obj, &mut |obj| { - let id = IdIdx::from_ident(obj); + let id = self.id_map.intern_ident(obj); let v = self.data.var_or_default(id); v.mark_has_property_access(); @@ -1057,11 +1144,15 @@ where } }); - fn is_root_of_member_expr_declared(member_expr: &MemberExpr, data: &impl Storage) -> bool { + fn is_root_of_member_expr_declared( + this: &mut UsageAnalyzer<'_, S>, + member_expr: &MemberExpr, + ) -> bool { match &*member_expr.obj { - Expr::Member(member_expr) => is_root_of_member_expr_declared(member_expr, data), - Expr::Ident(ident) => data - .get_var_data(IdIdx::from_ident(ident)) + Expr::Member(member_expr) => is_root_of_member_expr_declared(this, member_expr), + Expr::Ident(ident) => this + .data + .get_var_data(this.id_map.intern_ident(ident)) .map(|var| var.is_declared()) .unwrap_or(false), @@ -1069,7 +1160,7 @@ where } } - if is_root_of_member_expr_declared(e, &self.data) { + if is_root_of_member_expr_declared(self, e) { if let MemberProp::Ident(ident) = &e.prop { self.data.add_property_atom(ident.sym.clone()); } @@ -1086,8 +1177,10 @@ where self.with_child(n.function.ctxt, ScopeKind::Fn, |a| { n.key.visit_with(a); { - let ctx = a.ctx.with(BitContext::InPatOfParam, true); - n.function.params.visit_with(&mut *a.with_ctx(ctx)); + let saved_ctx = a.ctx; + a.ctx = a.ctx.with(BitContext::InPatOfParam, true); + n.function.params.visit_with(a); + a.ctx = saved_ctx; } n.function.visit_with(a); @@ -1095,8 +1188,10 @@ where } fn visit_module(&mut self, n: &Module) { - let ctx = self.ctx.with(BitContext::IsTopLevel, true); - n.visit_children_with(&mut *self.with_ctx(ctx)) + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsTopLevel, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } fn visit_named_export(&mut self, n: &NamedExport) { @@ -1113,14 +1208,16 @@ where fn visit_new_expr(&mut self, n: &NewExpr) { { n.callee.visit_with(self); - let ctx = self.ctx.with(BitContext::IsIdRef, true); - n.args.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + n.args.visit_with(self); + self.ctx = saved_ctx; if call_may_mutate(&n.callee, self.expr_ctx) { if let Some(args) = &n.args { for a in args { for_each_id_ref_in_expr(&a.expr, &mut |id| { - let id = IdIdx::from_ident(id); + let id = self.id_map.intern_ident(id); self.data.mark_property_mutation(id); }); } @@ -1134,10 +1231,12 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_param(&mut self, n: &Param) { - let ctx = self.ctx.with(BitContext::InPatOfParam, false); - n.decorators.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::InPatOfParam, false); + n.decorators.visit_with(self); + self.ctx = saved_ctx; - let ctx = Ctx { + self.ctx = Ctx { bit_ctx: self .ctx .bit_ctx @@ -1146,7 +1245,8 @@ where var_decl_kind_of_pat: None, ..self.ctx }; - n.pat.visit_with(&mut *self.with_ctx(ctx)); + n.pat.visit_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -1159,10 +1259,12 @@ where i.visit_with(self); } _ => { - let ctx = self + let saved_ctx = self.ctx; + self.ctx = self .ctx .with(BitContext::InDeclWithNoSideEffectForMemberAccess, false); - n.visit_children_with(&mut *self.with_ctx(ctx)); + n.visit_children_with(self); + self.ctx = saved_ctx; } } } @@ -1177,8 +1279,10 @@ where self.with_child(n.function.ctxt, ScopeKind::Fn, |a| { n.key.visit_with(a); { - let ctx = a.ctx.with(BitContext::InPatOfParam, true); - n.function.params.visit_with(&mut *a.with_ctx(ctx)); + let saved_ctx = a.ctx; + a.ctx = a.ctx.with(BitContext::InPatOfParam, true); + n.function.params.visit_with(a); + a.ctx = saved_ctx; } n.function.visit_with(a); @@ -1190,8 +1294,10 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_private_prop(&mut self, n: &PrivateProp) { - let ctx = self.ctx.with(BitContext::IsIdRef, true); - n.visit_children_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -1199,13 +1305,16 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_prop(&mut self, n: &Prop) { + let saved_ctx = self.ctx; if let Prop::Shorthand(i) = n { - let ctx = self.ctx.with(BitContext::IsIdRef, true); - self.with_ctx(ctx).report_usage(i); + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + self.report_usage(i); + self.ctx = saved_ctx; self.data.add_property_atom(i.sym.clone()); } else { - let ctx = self.ctx.with(BitContext::IsIdRef, true); - n.visit_children_with(&mut *self.with_ctx(ctx)); + self.ctx = self.ctx.with(BitContext::IsIdRef, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } } @@ -1224,8 +1333,10 @@ where } fn visit_script(&mut self, n: &Script) { - let ctx = self.ctx.with(BitContext::IsTopLevel, true); - n.visit_children_with(&mut *self.with_ctx(ctx)) + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsTopLevel, true); + n.visit_children_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -1236,8 +1347,10 @@ where self.with_child(SyntaxContext::empty(), ScopeKind::Fn, |a| { n.key.visit_with(a); { - let ctx = a.ctx.with(BitContext::InPatOfParam, true); - n.param.visit_with(&mut *a.with_ctx(ctx)); + let saved_ctx = a.ctx; + a.ctx = a.ctx.with(BitContext::InPatOfParam, true); + n.param.visit_with(a); + a.ctx = saved_ctx; } n.body.visit_with(a); @@ -1252,7 +1365,7 @@ where e.visit_children_with(self); for_each_id_ref_in_expr(&e.expr, &mut |i| { - let id = IdIdx::from_ident(i); + let id = self.id_map.intern_ident(i); self.data.var_or_default(id).mark_indexed_with_dynamic_key(); }); } @@ -1262,26 +1375,29 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_stmt(&mut self, n: &Stmt) { - let ctx = self + let saved_ctx = self.ctx; + self.ctx = self .ctx .with(BitContext::InAwaitArg, false) .with(BitContext::IsIdRef, true); - n.visit_children_with(&mut *self.with_ctx(ctx)); + n.visit_children_with(self); + self.ctx = saved_ctx; } fn visit_stmts(&mut self, stmts: &[Stmt]) { let mut had_cond = false; for stmt in stmts { - let ctx = self + let saved_ctx = self.ctx; + self.ctx = self .ctx .with( BitContext::InCond, self.ctx.bit_ctx.contains(BitContext::InCond) || had_cond, ) .with(BitContext::IsIdRef, true); - - stmt.visit_with(&mut *self.with_ctx(ctx)); + stmt.visit_with(self); + self.ctx = saved_ctx; had_cond |= can_end_conditionally(stmt); } @@ -1293,14 +1409,18 @@ where )] fn visit_super_prop_expr(&mut self, e: &SuperPropExpr) { if let SuperProp::Computed(c) = &e.prop { - let ctx = self.ctx.with(BitContext::IsIdRef, false); - c.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, false); + c.visit_with(self); + self.ctx = saved_ctx; } } fn visit_switch_case(&mut self, n: &SwitchCase) { - let ctx = self.ctx.with(BitContext::IsIdRef, false); - n.visit_children_with(&mut *self.with_ctx(ctx)) + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, false); + n.visit_children_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -1313,13 +1433,17 @@ where let mut fallthrough = false; for case in n.cases.iter() { + let saved_ctx = self.ctx; let ctx = self.ctx.with(BitContext::InCond, true); + self.ctx = ctx; if fallthrough { - self.with_ctx(ctx).visit_in_cond(&case.test); - self.with_ctx(ctx).visit_in_cond(&case.cons); + self.visit_in_cond(&case.test); + self.ctx = ctx; + self.visit_in_cond(&case.cons); } else { - self.with_ctx(ctx).visit_in_cond(case); + self.visit_in_cond(case); } + self.ctx = saved_ctx; fallthrough = !case.cons.iter().rev().any(|s| s.terminates()) } } @@ -1329,16 +1453,20 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_tagged_tpl(&mut self, n: &TaggedTpl) { + let saved_ctx = self.ctx; { - let ctx = self.ctx.with(BitContext::IsIdRef, false); - n.tag.visit_with(&mut *self.with_ctx(ctx)); + self.ctx = self.ctx.with(BitContext::IsIdRef, false); + n.tag.visit_with(self); + self.ctx = saved_ctx; } { - let ctx = self.ctx.with(BitContext::IsIdRef, true); + self.ctx = self.ctx.with(BitContext::IsIdRef, true); // Bypass visit_tpl - n.tpl.visit_children_with(&mut *self.with_ctx(ctx)) + n.tpl.visit_children_with(self); } + + self.ctx = saved_ctx; } #[cfg_attr( @@ -1346,8 +1474,10 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_tpl(&mut self, n: &Tpl) { - let ctx = self.ctx.with(BitContext::IsIdRef, false); - n.visit_children_with(&mut *self.with_ctx(ctx)) + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::IsIdRef, false); + n.visit_children_with(self); + self.ctx = saved_ctx; } #[cfg_attr( @@ -1355,8 +1485,10 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_try_stmt(&mut self, n: &TryStmt) { - let ctx = self.ctx.with(BitContext::InCond, true); - self.with_ctx(ctx).visit_children_in_cond(n); + let saved_ctx = self.ctx; + self.ctx = self.ctx.with(BitContext::InCond, true); + self.visit_children_in_cond(n); + self.ctx = saved_ctx; } #[cfg_attr( @@ -1386,12 +1518,14 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_var_decl(&mut self, n: &VarDecl) { - let ctx = Ctx { + let saved_ctx = self.ctx; + self.ctx = Ctx { var_decl_kind_of_pat: Some(n.kind), bit_ctx: self.ctx.bit_ctx.with(BitContext::InAwaitArg, false), ..self.ctx }; - n.visit_children_with(&mut *self.with_ctx(ctx)); + n.visit_children_with(self); + self.ctx = saved_ctx; for decl in &n.decls { if let (Pat::Ident(var), Some(init)) = (&decl.name, decl.init.as_deref()) { @@ -1403,9 +1537,10 @@ where ignore_named_child_scope: true, ..Default::default() }, + self.id_map, ) { if v.is_none() { - v = Some(self.data.var_or_default(IdIdx::from_ident(var))); + v = Some(self.data.var_or_default(self.id_map.intern_ident(var))); } v.as_mut().unwrap().add_infects_to(id); @@ -1424,7 +1559,8 @@ where .. }) if (&**arguments == "arguments")); { - let ctx = Ctx { + let saved_ctx = self.ctx; + self.ctx = Ctx { bit_ctx: self .ctx .bit_ctx @@ -1446,7 +1582,8 @@ where .map(|init| init.get_type(self.expr_ctx)), ..self.ctx }; - e.name.visit_with(&mut *self.with_ctx(ctx)); + e.name.visit_with(self); + self.ctx = saved_ctx; } { @@ -1466,14 +1603,18 @@ where definite: false, .. } => { - let id = IdIdx::from_ident(id); + let id = self.id_map.intern_ident(id); self.used_recursively.insert( id, RecursiveUsage::Var { can_ignore: !init.may_have_side_effects(self.expr_ctx), }, ); - e.init.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + self.ctx = ctx; + e.init.visit_with(self); + self.ctx = saved_ctx; + self.used_recursively.remove(&id); return; } @@ -1484,7 +1625,7 @@ where .. } => { self.data - .var_or_default(IdIdx::from_ident(id)) + .var_or_default(self.id_map.intern_ident(id)) .mark_as_lazy_init(); return; } @@ -1492,7 +1633,9 @@ where } } - e.init.visit_with(&mut *self.with_ctx(ctx)); + let saved_ctx = self.ctx; + e.init.visit_with(self); + self.ctx = saved_ctx; } } @@ -1501,13 +1644,17 @@ where tracing::instrument(level = "debug", skip_all) )] fn visit_while_stmt(&mut self, n: &WhileStmt) { - n.test - .visit_with(&mut *self.with_ctx(self.ctx.with(BitContext::ExecutedMultipleTime, true))); - let ctx = self - .ctx + let saved_ctx = self.ctx; + + self.ctx = self.ctx.with(BitContext::ExecutedMultipleTime, true); + n.test.visit_with(self); + + self.ctx = saved_ctx .with(BitContext::ExecutedMultipleTime, true) .with(BitContext::InCond, true); - self.with_ctx(ctx).visit_in_cond(&n.body); + self.visit_in_cond(&n.body); + + self.ctx = saved_ctx; } #[cfg_attr( diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs index 6a921c5a0ce1..013dbf75dfd3 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs @@ -31,7 +31,7 @@ pub trait Storage: Sized + Default { fn declare_decl( &mut self, ctx: Ctx, - i: &Ident, + i: IdIdx, init_type: Option>, kind: Option, ) -> &mut Self::VarData; diff --git a/crates/swc_ecma_utils/src/find_pat_ids.rs b/crates/swc_ecma_utils/src/find_pat_ids.rs new file mode 100644 index 000000000000..d5705ae3e15e --- /dev/null +++ b/crates/swc_ecma_utils/src/find_pat_ids.rs @@ -0,0 +1,77 @@ +use swc_ecma_ast::*; +use swc_ecma_visit::{Visit, VisitWith}; + +use crate::ident::IdentLike; + +/// Finds all **binding** idents of `node`. +/// +/// If you want to avoid allocation, use [`for_each_binding_ident`] instead. +pub fn find_pat_ids(node: &T) -> Vec +where + T: VisitWith>, +{ + let mut v = DestructuringFinder { found: Vec::new() }; + node.visit_with(&mut v); + + v.found +} + +pub fn find_pat_ids_with_idx<'a, T: VisitWith>>( + node: &T, + id_map: &'a mut Ids, +) -> Vec { + let mut v = DestructuringFinderWithIdx { + found: Vec::new(), + id_map, + }; + node.visit_with(&mut v); + v.found +} + +/// Finds all **binding** idents of variables. +pub struct DestructuringFinderWithIdx<'a> { + id_map: &'a mut Ids, + found: Vec, +} + +impl Visit for DestructuringFinderWithIdx<'_> { + /// No-op (we don't care about expressions) + fn visit_expr(&mut self, _: &Expr) {} + + fn visit_ident(&mut self, i: &Ident) { + let id = self.id_map.intern_ident(i); + self.found.push(id); + } + + fn visit_jsx_member_expr(&mut self, n: &JSXMemberExpr) { + n.obj.visit_with(self); + } + + /// No-op (we don't care about expressions) + fn visit_prop_name(&mut self, _: &PropName) {} + + fn visit_ts_type(&mut self, _: &TsType) {} +} + +/// Finds all **binding** idents of variables. +pub struct DestructuringFinder { + found: Vec, +} + +impl Visit for DestructuringFinder { + /// No-op (we don't care about expressions) + fn visit_expr(&mut self, _: &Expr) {} + + fn visit_ident(&mut self, i: &Ident) { + self.found.push(I::from_ident(i)); + } + + fn visit_jsx_member_expr(&mut self, n: &JSXMemberExpr) { + n.obj.visit_with(self); + } + + /// No-op (we don't care about expressions) + fn visit_prop_name(&mut self, _: &PropName) {} + + fn visit_ts_type(&mut self, _: &TsType) {} +} diff --git a/crates/swc_ecma_utils/src/ident.rs b/crates/swc_ecma_utils/src/ident.rs index e62c05cfc6d6..50fd9ce0e3d2 100644 --- a/crates/swc_ecma_utils/src/ident.rs +++ b/crates/swc_ecma_utils/src/ident.rs @@ -1,6 +1,6 @@ use swc_atoms::Atom; use swc_common::SyntaxContext; -use swc_ecma_ast::{unsafe_id_from_ident, BindingIdent, Id, IdIdx, Ident, UnsafeId}; +use swc_ecma_ast::{unsafe_id_from_ident, BindingIdent, Id, Ident, UnsafeId}; pub trait IdentLike: Sized + Send + Sync + 'static { type Id; @@ -94,19 +94,3 @@ impl IdentLike for UnsafeId { unreachable!("UnsafeId.into_id() is not allowed because it is very likely to be unsafe") } } - -impl IdentLike for IdIdx { - type Id = IdIdx; - - fn from_ident(i: &Ident) -> Self { - IdIdx::from_ident(i) - } - - fn to_id(&self) -> Self::Id { - *self - } - - fn into_id(self) -> Self::Id { - self - } -} diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs index 451cc6e242a6..07fd88424412 100644 --- a/crates/swc_ecma_utils/src/lib.rs +++ b/crates/swc_ecma_utils/src/lib.rs @@ -57,6 +57,11 @@ pub mod number; pub mod stack_size; pub mod str; pub use node_ignore_span::NodeIgnoringSpan; +mod find_pat_ids; + +pub use find_pat_ids::{ + find_pat_ids, find_pat_ids_with_idx, DestructuringFinder, DestructuringFinderWithIdx, +}; // TODO: remove pub struct ThisVisitor { @@ -143,7 +148,6 @@ impl Visit for IdentRefFinder<'_> { match *e { Expr::Ident(ref i) if i.ctxt == self.ident.ctxt && i.sym == self.ident.sym => { - Expr::Ident(ref i) if i.sym == self.ident.sym && i.ctxt == self.ident.ctxt => { self.found = true; } _ => {} @@ -1603,42 +1607,6 @@ impl IsDirective for &ModuleItem { } } -/// Finds all **binding** idents of variables. -pub struct DestructuringFinder { - pub found: Vec, -} - -/// Finds all **binding** idents of `node`. -/// -/// If you want to avoid allocation, use [`for_each_binding_ident`] instead. -pub fn find_pat_ids(node: &T) -> Vec -where - T: VisitWith>, -{ - let mut v = DestructuringFinder { found: Vec::new() }; - node.visit_with(&mut v); - - v.found -} - -impl Visit for DestructuringFinder { - /// No-op (we don't care about expressions) - fn visit_expr(&mut self, _: &Expr) {} - - fn visit_ident(&mut self, i: &Ident) { - self.found.push(I::from_ident(i)); - } - - fn visit_jsx_member_expr(&mut self, n: &JSXMemberExpr) { - n.obj.visit_with(self); - } - - /// No-op (we don't care about expressions) - fn visit_prop_name(&mut self, _: &PropName) {} - - fn visit_ts_type(&mut self, _: &TsType) {} -} - /// Finds all **binding** idents of variables. pub struct BindingIdentifierVisitor where