mirror of
https://github.com/swc-project/swc.git
synced 2024-12-24 22:22:34 +03:00
perf(es/minifier): Make pure function check O(1)
(#6840)
This commit is contained in:
parent
05724e5122
commit
58208ef8ae
@ -1,3 +1,6 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use swc_common::{
|
||||
comments::{Comment, CommentKind, Comments},
|
||||
EqIgnoreSpan, Span, SyntaxContext,
|
||||
@ -11,6 +14,37 @@ use swc_ecma_visit::{
|
||||
|
||||
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;
|
||||
|
||||
@ -21,10 +55,18 @@ pub(crate) fn info_marker<'a>(
|
||||
marks: Marks,
|
||||
// unresolved_mark: Mark,
|
||||
) -> impl 'a + VisitMut {
|
||||
let pure_funcs = options.map(|options| {
|
||||
options
|
||||
.pure_funcs
|
||||
.iter()
|
||||
.map(|f| HashEqIgnoreSpanExprRef(f))
|
||||
.collect()
|
||||
});
|
||||
InfoMarker {
|
||||
options,
|
||||
comments,
|
||||
marks,
|
||||
pure_funcs,
|
||||
// unresolved_mark,
|
||||
state: Default::default(),
|
||||
}
|
||||
@ -37,8 +79,9 @@ struct State {
|
||||
}
|
||||
|
||||
struct InfoMarker<'a> {
|
||||
#[allow(dead_code)]
|
||||
options: Option<&'a CompressOptions>,
|
||||
|
||||
pure_funcs: Option<FxHashSet<HashEqIgnoreSpanExprRef<'a>>>,
|
||||
comments: Option<&'a dyn Comments>,
|
||||
marks: Marks,
|
||||
// unresolved_mark: Mark,
|
||||
@ -124,17 +167,12 @@ impl VisitMut for InfoMarker<'_> {
|
||||
|
||||
if self.has_pure(n.span) {
|
||||
n.span = n.span.apply_mark(self.marks.pure);
|
||||
} else if let Some(options) = self.options {
|
||||
} else if let Some(pure_fns) = &self.pure_funcs {
|
||||
if let Callee::Expr(e) = &n.callee {
|
||||
// Check for pure_funcs
|
||||
|
||||
if options
|
||||
.pure_funcs
|
||||
.iter()
|
||||
.any(|pure_func| Ident::within_ignored_ctxt(|| e.eq_ignore_span(pure_func)))
|
||||
{
|
||||
if pure_fns.contains(&HashEqIgnoreSpanExprRef(e)) {
|
||||
n.span = n.span.apply_mark(self.marks.pure);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,3 +158,80 @@
|
||||
// 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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user