perf(es/minifier): Make pure function check O(1) (#6840)

This commit is contained in:
HeYunfei 2023-01-24 14:16:23 +08:00 committed by GitHub
parent 05724e5122
commit 58208ef8ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 124 additions and 9 deletions

View File

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

View File

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