mirror of
https://github.com/swc-project/swc.git
synced 2024-12-24 06:05:02 +03:00
perf(es/utils): Introduce NodeIgnoringSpan
(#7030)
This commit is contained in:
parent
50ee7d1c83
commit
8bfef35c1b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4104,6 +4104,7 @@ dependencies = [
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_parser",
|
||||
"swc_ecma_visit",
|
||||
"testing",
|
||||
"tracing",
|
||||
"unicode-id",
|
||||
]
|
||||
|
@ -72,6 +72,7 @@ use swc_ecma_transforms_optimization::{
|
||||
simplify::{dce::Config as DceConfig, Config as SimplifyConfig},
|
||||
GlobalExprMap,
|
||||
};
|
||||
use swc_ecma_utils::NodeIgnoringSpan;
|
||||
use swc_ecma_visit::{Fold, VisitMutWith};
|
||||
|
||||
pub use crate::plugin::PluginConfig;
|
||||
@ -1698,11 +1699,11 @@ impl GlobalPassOption {
|
||||
.filter(|(k, _)| k.contains('.'))
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
*expr(cm, handler, k.to_string()),
|
||||
NodeIgnoringSpan::owned(*expr(cm, handler, k.to_string())),
|
||||
*expr(cm, handler, v.to_string()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<AHashMap<_, _>>();
|
||||
let map = Arc::new(map);
|
||||
CACHE.insert(cache_key, map.clone());
|
||||
map
|
||||
|
@ -1,50 +1,17 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use swc_common::{
|
||||
comments::{Comment, CommentKind, Comments},
|
||||
EqIgnoreSpan, Span, SyntaxContext,
|
||||
Span, SyntaxContext,
|
||||
};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_usage_analyzer::marks::Marks;
|
||||
use swc_ecma_utils::find_pat_ids;
|
||||
use swc_ecma_utils::{find_pat_ids, NodeIgnoringSpan};
|
||||
use swc_ecma_visit::{
|
||||
noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
|
||||
};
|
||||
|
||||
use crate::option::CompressOptions;
|
||||
|
||||
#[derive(Debug, Eq)]
|
||||
struct HashEqIgnoreSpanExprRef<'a>(&'a Expr);
|
||||
|
||||
impl<'a> PartialEq for HashEqIgnoreSpanExprRef<'a> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Ident::within_ignored_ctxt(|| self.0.eq_ignore_span(other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hash for HashEqIgnoreSpanExprRef<'a> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
// In pratice, most of cases/input we are dealing with are Expr::Member or
|
||||
// Expr::Ident.
|
||||
match self.0 {
|
||||
Expr::Ident(i) => {
|
||||
i.sym.hash(state);
|
||||
}
|
||||
Expr::Member(i) => {
|
||||
Self(&i.obj).hash(state);
|
||||
if let MemberProp::Ident(prop) = &i.prop {
|
||||
prop.sym.hash(state);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Other expression kind would fallback to the same empty hash.
|
||||
// So, their will spend linear time to do comparisons.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@ -59,7 +26,7 @@ pub(crate) fn info_marker<'a>(
|
||||
options
|
||||
.pure_funcs
|
||||
.iter()
|
||||
.map(|f| HashEqIgnoreSpanExprRef(f))
|
||||
.map(|f| NodeIgnoringSpan::borrowed(f.as_ref()))
|
||||
.collect()
|
||||
});
|
||||
InfoMarker {
|
||||
@ -81,7 +48,7 @@ struct State {
|
||||
struct InfoMarker<'a> {
|
||||
#[allow(dead_code)]
|
||||
options: Option<&'a CompressOptions>,
|
||||
pure_funcs: Option<FxHashSet<HashEqIgnoreSpanExprRef<'a>>>,
|
||||
pure_funcs: Option<FxHashSet<NodeIgnoringSpan<'a, Expr>>>,
|
||||
comments: Option<&'a dyn Comments>,
|
||||
marks: Marks,
|
||||
// unresolved_mark: Mark,
|
||||
@ -170,9 +137,11 @@ impl VisitMut for InfoMarker<'_> {
|
||||
} else if let Some(pure_fns) = &self.pure_funcs {
|
||||
if let Callee::Expr(e) = &n.callee {
|
||||
// Check for pure_funcs
|
||||
if pure_fns.contains(&HashEqIgnoreSpanExprRef(e)) {
|
||||
n.span = n.span.apply_mark(self.marks.pure);
|
||||
};
|
||||
Ident::within_ignored_ctxt(|| {
|
||||
if pure_fns.contains(&NodeIgnoringSpan::borrowed(e)) {
|
||||
n.span = n.span.apply_mark(self.marks.pure);
|
||||
};
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,80 +158,3 @@
|
||||
// fn export_default_fn_1() {
|
||||
// assert_standalone("export default function f(module, exports) {}", 0);
|
||||
// }
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use swc_common::{util::take::Take, Mark, DUMMY_SP};
|
||||
use swc_ecma_utils::{member_expr, quote_expr};
|
||||
|
||||
use super::HashEqIgnoreSpanExprRef;
|
||||
|
||||
#[test]
|
||||
fn test_hash_eq_ignore_span_expr_ref() {
|
||||
use swc_ecma_ast::*;
|
||||
|
||||
fn expr_ref(expr_ref: &Expr) -> HashEqIgnoreSpanExprRef {
|
||||
HashEqIgnoreSpanExprRef(expr_ref)
|
||||
}
|
||||
|
||||
testing::run_test(false, |_cm, _handler| {
|
||||
let dummy_sp = DUMMY_SP;
|
||||
let meaningful_sp = dummy_sp.apply_mark(Mark::new());
|
||||
|
||||
let meaningful_ident_expr = Expr::Ident(Ident::new("foo".into(), meaningful_sp));
|
||||
let dummy_ident_expr = Expr::Ident(Ident::new("foo".into(), dummy_sp));
|
||||
|
||||
let meaningful_member_expr = member_expr!(meaningful_sp, foo.bar);
|
||||
let dummy_member_expr = member_expr!(dummy_sp, foo.bar);
|
||||
|
||||
let meaningful_null_expr = quote_expr!(meaningful_sp, null);
|
||||
let dummy_null_expr = quote_expr!(dummy_sp, null);
|
||||
|
||||
let meaningful_array_expr = Box::new(Expr::Array(ArrayLit {
|
||||
span: meaningful_sp,
|
||||
elems: Default::default(),
|
||||
}));
|
||||
|
||||
let dummy_array_expr = Box::new(Expr::Array(ArrayLit::dummy()));
|
||||
|
||||
// Should equal ignoring span and syntax context
|
||||
assert_eq!(
|
||||
expr_ref(&meaningful_ident_expr),
|
||||
expr_ref(&dummy_ident_expr)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
expr_ref(&meaningful_array_expr),
|
||||
expr_ref(&dummy_array_expr)
|
||||
);
|
||||
|
||||
let mut set = FxHashSet::from_iter([
|
||||
expr_ref(&meaningful_ident_expr),
|
||||
expr_ref(&meaningful_member_expr),
|
||||
expr_ref(&meaningful_null_expr),
|
||||
expr_ref(&meaningful_array_expr),
|
||||
]);
|
||||
|
||||
// Should produce the same hash value ignoring span and syntax context
|
||||
assert!(set.contains(&expr_ref(&dummy_ident_expr)));
|
||||
assert!(set.contains(&expr_ref(&dummy_member_expr)));
|
||||
assert!(set.contains(&expr_ref(&dummy_null_expr)));
|
||||
assert!(set.contains(&expr_ref(&dummy_array_expr)));
|
||||
|
||||
set.insert(expr_ref(&dummy_ident_expr));
|
||||
set.insert(expr_ref(&dummy_member_expr));
|
||||
set.insert(expr_ref(&dummy_null_expr));
|
||||
set.insert(expr_ref(&dummy_array_expr));
|
||||
assert_eq!(set.len(), 4);
|
||||
|
||||
// Should not equal ignoring span and syntax context
|
||||
let dummy_ident_expr = Expr::Ident(Ident::new("baz".into(), dummy_sp));
|
||||
let dummy_member_expr = member_expr!(dummy_sp, baz.bar);
|
||||
let dummy_arrow_expr = Box::new(Expr::Arrow(ArrowExpr::dummy()));
|
||||
assert!(!set.contains(&expr_ref(&dummy_ident_expr)));
|
||||
assert!(!set.contains(&expr_ref(&dummy_member_expr)));
|
||||
assert!(!set.contains(&expr_ref(&dummy_arrow_expr)));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -2,17 +2,16 @@ use swc_atoms::{js_word, JsWord};
|
||||
use swc_common::{
|
||||
collections::{AHashMap, AHashSet},
|
||||
sync::Lrc,
|
||||
EqIgnoreSpan,
|
||||
};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms_base::perf::Parallel;
|
||||
use swc_ecma_transforms_macros::parallel;
|
||||
use swc_ecma_utils::collect_decls;
|
||||
use swc_ecma_utils::{collect_decls, NodeIgnoringSpan};
|
||||
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
|
||||
|
||||
/// The key will be compared using [EqIgnoreSpan::eq_ignore_span], and matched
|
||||
/// expressions will be replaced with the value.
|
||||
pub type GlobalExprMap = Lrc<Vec<(Expr, Expr)>>;
|
||||
pub type GlobalExprMap = Lrc<AHashMap<NodeIgnoringSpan<'static, Expr>, Expr>>;
|
||||
|
||||
/// Create a global inlining pass, which replaces expressions with the specified
|
||||
/// value.
|
||||
@ -49,7 +48,7 @@ pub fn inline_globals2(
|
||||
struct InlineGlobals {
|
||||
envs: Lrc<AHashMap<JsWord, Expr>>,
|
||||
globals: Lrc<AHashMap<JsWord, Expr>>,
|
||||
global_exprs: GlobalExprMap,
|
||||
global_exprs: Lrc<AHashMap<NodeIgnoringSpan<'static, Expr>, Expr>>,
|
||||
|
||||
typeofs: Lrc<AHashMap<JsWord, JsWord>>,
|
||||
|
||||
@ -93,12 +92,12 @@ impl VisitMut for InlineGlobals {
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value) in self.global_exprs.iter() {
|
||||
if Ident::within_ignored_ctxt(|| key.eq_ignore_span(&*expr)) {
|
||||
*expr = value.clone();
|
||||
expr.visit_mut_with(self);
|
||||
return;
|
||||
}
|
||||
if let Some(value) =
|
||||
Ident::within_ignored_ctxt(|| self.global_exprs.get(&NodeIgnoringSpan::borrowed(expr)))
|
||||
{
|
||||
*expr = value.clone();
|
||||
expr.visit_mut_with(self);
|
||||
return;
|
||||
}
|
||||
|
||||
expr.visit_mut_children_with(self);
|
||||
|
@ -1,16 +1,16 @@
|
||||
[package]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
description = "Utilities for swc ecmascript ast nodes"
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
description = "Utilities for swc ecmascript ast nodes"
|
||||
documentation = "https://rustdoc.swc.rs/swc_ecma_utils/"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
name = "swc_ecma_utils"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.111.4"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
name = "swc_ecma_utils"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.111.4"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -20,17 +20,18 @@ bench = false
|
||||
concurrent = ["swc_common/concurrent", "rayon"]
|
||||
|
||||
[dependencies]
|
||||
indexmap = "1.6.1"
|
||||
num_cpus = "1.13.1"
|
||||
once_cell = "1.10.0"
|
||||
rayon = {version = "1.5.1", optional = true}
|
||||
rustc-hash = "1.1.0"
|
||||
swc_atoms = {version = "0.4.39", path = "../swc_atoms"}
|
||||
swc_common = {version = "0.29.35", path = "../swc_common"}
|
||||
swc_ecma_ast = {version = "0.98.2", path = "../swc_ecma_ast"}
|
||||
swc_ecma_visit = {version = "0.84.2", path = "../swc_ecma_visit"}
|
||||
tracing = "0.1.32"
|
||||
unicode-id = "0.3"
|
||||
indexmap = "1.6.1"
|
||||
num_cpus = "1.13.1"
|
||||
once_cell = "1.10.0"
|
||||
rayon = { version = "1.5.1", optional = true }
|
||||
rustc-hash = "1.1.0"
|
||||
swc_atoms = { version = "0.4.39", path = "../swc_atoms" }
|
||||
swc_common = { version = "0.29.35", path = "../swc_common" }
|
||||
swc_ecma_ast = { version = "0.98.2", path = "../swc_ecma_ast" }
|
||||
swc_ecma_visit = { version = "0.84.2", path = "../swc_ecma_visit" }
|
||||
tracing = "0.1.32"
|
||||
unicode-id = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
swc_ecma_parser = {version = "0.128.4", path = "../swc_ecma_parser"}
|
||||
swc_ecma_parser = { version = "0.128.4", path = "../swc_ecma_parser" }
|
||||
testing = { version = "0.31.37", path = "../testing" }
|
||||
|
@ -53,6 +53,9 @@ pub mod parallel;
|
||||
mod value;
|
||||
pub mod var;
|
||||
|
||||
mod node_ignore_span;
|
||||
pub use node_ignore_span::NodeIgnoringSpan;
|
||||
|
||||
// TODO: remove
|
||||
pub struct ThisVisitor {
|
||||
found: bool,
|
||||
|
136
crates/swc_ecma_utils/src/node_ignore_span.rs
Normal file
136
crates/swc_ecma_utils/src/node_ignore_span.rs
Normal file
@ -0,0 +1,136 @@
|
||||
use std::{borrow::Cow, fmt::Debug, hash::Hash};
|
||||
|
||||
use swc_common::EqIgnoreSpan;
|
||||
use swc_ecma_ast::{Expr, MemberProp};
|
||||
|
||||
/// A newtype that will ignore Span while doing `eq` or `hash`.
|
||||
pub struct NodeIgnoringSpan<'a, Node: ToOwned + Debug>(Cow<'a, Node>);
|
||||
|
||||
impl<'a, Node: ToOwned + Debug> Debug for NodeIgnoringSpan<'a, Node> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("NodeIgnoringSpan").field(&*self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Node: ToOwned + Debug> NodeIgnoringSpan<'a, Node> {
|
||||
#[inline]
|
||||
pub fn borrowed(expr: &'a Node) -> Self {
|
||||
Self(Cow::Borrowed(expr))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn owned(expr: <Node as ToOwned>::Owned) -> Self {
|
||||
Self(Cow::Owned(expr))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Node: EqIgnoreSpan + ToOwned + Debug> PartialEq for NodeIgnoringSpan<'a, Node> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.eq_ignore_span(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Node: EqIgnoreSpan + ToOwned + Debug> Eq for NodeIgnoringSpan<'a, Node> {}
|
||||
|
||||
// TODO: This is only a workaround for Expr. we need something like
|
||||
// `hash_ignore_span` for each node in the end.
|
||||
impl<'a> Hash for NodeIgnoringSpan<'a, Expr> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
// In pratice, most of cases/input we are dealing with are Expr::Member or
|
||||
// Expr::Ident.
|
||||
match &*self.0 {
|
||||
Expr::Ident(i) => {
|
||||
i.sym.hash(state);
|
||||
}
|
||||
Expr::Member(i) => {
|
||||
{
|
||||
NodeIgnoringSpan::borrowed(i.obj.as_ref()).hash(state);
|
||||
}
|
||||
if let MemberProp::Ident(prop) = &i.prop {
|
||||
prop.sym.hash(state);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Other expression kinds would fallback to the same empty hash.
|
||||
// So, they will spend linear time to do comparisons.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_eq_ignore_span_expr_ref() {
|
||||
use rustc_hash::FxHashSet;
|
||||
use swc_common::{util::take::Take, Mark, DUMMY_SP};
|
||||
use swc_ecma_ast::*;
|
||||
|
||||
use crate::{member_expr, quote_expr};
|
||||
|
||||
fn expr_ref(expr_ref: &Expr) -> NodeIgnoringSpan<Expr> {
|
||||
NodeIgnoringSpan::borrowed(expr_ref)
|
||||
}
|
||||
|
||||
testing::run_test(false, |_cm, _handler| {
|
||||
Ident::within_ignored_ctxt(|| {
|
||||
let dummy_sp = DUMMY_SP;
|
||||
let meaningful_sp = dummy_sp.apply_mark(Mark::new());
|
||||
|
||||
let meaningful_ident_expr = Expr::Ident(Ident::new("foo".into(), meaningful_sp));
|
||||
let dummy_ident_expr = Expr::Ident(Ident::new("foo".into(), dummy_sp));
|
||||
|
||||
let meaningful_member_expr = member_expr!(meaningful_sp, foo.bar);
|
||||
let dummy_member_expr = member_expr!(dummy_sp, foo.bar);
|
||||
|
||||
let meaningful_null_expr = quote_expr!(meaningful_sp, null);
|
||||
let dummy_null_expr = quote_expr!(dummy_sp, null);
|
||||
|
||||
let meaningful_array_expr = Box::new(Expr::Array(ArrayLit {
|
||||
span: meaningful_sp,
|
||||
elems: Default::default(),
|
||||
}));
|
||||
|
||||
let dummy_array_expr = Box::new(Expr::Array(ArrayLit::dummy()));
|
||||
|
||||
// Should equal ignoring span and syntax context
|
||||
assert_eq!(
|
||||
expr_ref(&meaningful_ident_expr),
|
||||
expr_ref(&dummy_ident_expr)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
expr_ref(&meaningful_array_expr),
|
||||
expr_ref(&dummy_array_expr)
|
||||
);
|
||||
|
||||
let mut set = FxHashSet::from_iter([
|
||||
expr_ref(&meaningful_ident_expr),
|
||||
expr_ref(&meaningful_member_expr),
|
||||
expr_ref(&meaningful_null_expr),
|
||||
expr_ref(&meaningful_array_expr),
|
||||
]);
|
||||
|
||||
// Should produce the same hash value ignoring span and syntax
|
||||
assert!(set.contains(&expr_ref(&dummy_ident_expr)));
|
||||
assert!(set.contains(&expr_ref(&dummy_member_expr)));
|
||||
assert!(set.contains(&expr_ref(&dummy_null_expr)));
|
||||
assert!(set.contains(&expr_ref(&dummy_array_expr)));
|
||||
|
||||
set.insert(expr_ref(&dummy_ident_expr));
|
||||
set.insert(expr_ref(&dummy_member_expr));
|
||||
set.insert(expr_ref(&dummy_null_expr));
|
||||
set.insert(expr_ref(&dummy_array_expr));
|
||||
assert_eq!(set.len(), 4);
|
||||
|
||||
// Should not equal ignoring span and syntax context
|
||||
let dummy_ident_expr = Expr::Ident(Ident::new("baz".into(), dummy_sp));
|
||||
let dummy_member_expr = member_expr!(dummy_sp, baz.bar);
|
||||
let dummy_arrow_expr = Box::new(Expr::Arrow(ArrowExpr::dummy()));
|
||||
assert!(!set.contains(&expr_ref(&dummy_ident_expr)));
|
||||
assert!(!set.contains(&expr_ref(&dummy_member_expr)));
|
||||
assert!(!set.contains(&expr_ref(&dummy_arrow_expr)));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
Loading…
Reference in New Issue
Block a user