perf(es/utils): Introduce NodeIgnoringSpan (#7030)

This commit is contained in:
HeYunfei 2023-03-08 14:49:41 +08:00 committed by GitHub
parent 50ee7d1c83
commit 8bfef35c1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 184 additions and 151 deletions

1
Cargo.lock generated
View File

@ -4104,6 +4104,7 @@ dependencies = [
"swc_ecma_ast",
"swc_ecma_parser",
"swc_ecma_visit",
"testing",
"tracing",
"unicode-id",
]

View File

@ -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

View File

@ -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);
};
})
}
}
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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" }

View File

@ -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,

View 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();
}