Skip to content

Commit

Permalink
perf(es/minifier): Make analyzer not call collect_infects_from recu…
Browse files Browse the repository at this point in the history
…rsively (#9924)

**Description:**

We don't need to make time complexity non-linear by recursively visiting the effects of infection. Instead, suppose we stop the infection analyzer when the usage analyzer invokes the infection analyzer. In that case, we will have practically identical analysis results because the points are named using an id, and it points to all the infections that occurred by referencing the id.

**Related issue:**
 - Closes #9927
  • Loading branch information
kdy1 authored Jan 28, 2025
1 parent 9109a53 commit 37616c3
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 41 deletions.
5 changes: 5 additions & 0 deletions .changeset/lovely-snakes-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¡---
swc_ecma_usage_analyzer: major
---

perf(es/minifier): Make analyzer not call `collect_infects_from` recursively
8 changes: 3 additions & 5 deletions crates/swc_ecma_minifier/src/compress/optimize/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,11 +685,9 @@ impl Optimizer<'_> {

for i in collect_infects_from(
&f.function,
AliasConfig {
marks: Some(self.marks),
ignore_nested: false,
need_all: true,
},
AliasConfig::default()
.marks(Some(self.marks))
.need_all(true),
) {
if let Some(usage) = self.data.vars.get_mut(&i.0) {
usage.ref_count += 1;
Expand Down
27 changes: 12 additions & 15 deletions crates/swc_ecma_minifier/src/compress/optimize/sequences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,33 +1150,30 @@ impl Optimizer<'_> {
Mergable::Var(a) => a.init.as_ref().map(|init| {
collect_infects_from(
init,
AliasConfig {
marks: Some(self.marks),
ignore_nested: true,
need_all: true,
},
AliasConfig::default()
.marks(Some(self.marks))
.ignore_nested(true)
.need_all(true),
)
}),
Mergable::Expr(a) => match a {
Expr::Assign(a) if a.is_simple_assign() => Some(collect_infects_from(
&a.right,
AliasConfig {
marks: Some(self.marks),
ignore_nested: true,
need_all: true,
},
AliasConfig::default()
.marks(Some(self.marks))
.ignore_nested(true)
.need_all(true),
)),

_ => None,
},

Mergable::FnDecl(a) => Some(collect_infects_from(
&a.function,
AliasConfig {
marks: Some(self.marks),
ignore_nested: true,
need_all: true,
},
AliasConfig::default()
.marks(Some(self.marks))
.ignore_nested(true)
.need_all(true),
)),

Mergable::Drop => return false,
Expand Down
108 changes: 87 additions & 21 deletions crates/swc_ecma_usage_analyzer/src/alias/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,41 @@ use crate::{marks::Marks, util::is_global_var_with_pure_property_access};
mod ctx;

#[derive(Default)]
#[non_exhaustive]
pub struct AliasConfig {
pub marks: Option<Marks>,
pub ignore_nested: bool,
/// TODO(kdy1): This field is used for sequential inliner.
/// It should be renamed to some correct name.
pub need_all: bool,

/// We can skip visiting children nodes in some cases.
///
/// Because we recurse in the usage analyzer, we don't need to recurse into
/// child node that the usage analyzer will invoke [`collect_infects_from`]
/// on.
pub ignore_named_child_scope: bool,
}
impl AliasConfig {
pub fn marks(mut self, arg: Option<Marks>) -> Self {
self.marks = arg;
self
}

pub fn ignore_nested(mut self, arg: bool) -> Self {
self.ignore_nested = arg;
self
}

pub fn ignore_named_child_scope(mut self, arg: bool) -> Self {
self.ignore_named_child_scope = arg;
self
}

pub fn need_all(mut self, arg: bool) -> Self {
self.need_all = arg;
self
}
}

pub trait InfectableNode {
Expand Down Expand Up @@ -99,8 +128,8 @@ pub struct InfectionCollector<'a> {
}

impl InfectionCollector<'_> {
fn add_id(&mut self, e: &Id) {
if self.exclude.contains(e) {
fn add_id(&mut self, e: Id) {
if self.exclude.contains(&e) {
return;
}

Expand All @@ -109,7 +138,7 @@ impl InfectionCollector<'_> {
}

self.accesses.insert((
e.clone(),
e,
if self.ctx.is_callee {
AccessKind::Call
} else {
Expand All @@ -122,6 +151,18 @@ impl InfectionCollector<'_> {
impl Visit for InfectionCollector<'_> {
noop_visit_type!();

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_bin_expr(&mut self, e: &BinExpr) {
match e.op {
op!("in")
Expand Down Expand Up @@ -163,6 +204,14 @@ impl Visit for InfectionCollector<'_> {
}
}

fn visit_callee(&mut self, n: &Callee) {
let ctx = Ctx {
is_callee: true,
..self.ctx
};
n.visit_children_with(&mut *self.with_ctx(ctx));
}

fn visit_cond_expr(&mut self, e: &CondExpr) {
{
let ctx = Ctx {
Expand All @@ -183,15 +232,11 @@ impl Visit for InfectionCollector<'_> {
}
}

fn visit_ident(&mut self, n: &Ident) {
self.add_id(&n.to_id());
}

fn visit_expr(&mut self, e: &Expr) {
match e {
Expr::Ident(i) => {
if self.ctx.track_expr_ident {
self.add_id(&i.to_id());
self.add_id(i.to_id());
}
}

Expand All @@ -205,6 +250,25 @@ impl Visit for InfectionCollector<'_> {
}
}

fn visit_fn_decl(&mut self, n: &FnDecl) {
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_ident(&mut self, n: &Ident) {
self.add_id(n.to_id());
}

fn visit_member_expr(&mut self, n: &MemberExpr) {
{
let ctx = Ctx {
Expand Down Expand Up @@ -232,6 +296,15 @@ impl Visit for InfectionCollector<'_> {
}
}

fn visit_prop_name(&mut self, n: &PropName) {
if let PropName::Computed(c) = &n {
c.visit_with(&mut *self.with_ctx(Ctx {
is_callee: false,
..self.ctx
}));
}
}

fn visit_super_prop_expr(&mut self, n: &SuperPropExpr) {
if let SuperProp::Computed(c) = &n.prop {
c.visit_with(&mut *self.with_ctx(Ctx {
Expand Down Expand Up @@ -277,20 +350,13 @@ impl Visit for InfectionCollector<'_> {
e.arg.visit_with(&mut *self.with_ctx(ctx));
}

fn visit_prop_name(&mut self, n: &PropName) {
if let PropName::Computed(c) = &n {
c.visit_with(&mut *self.with_ctx(Ctx {
is_callee: false,
..self.ctx
}));
fn visit_var_declarator(&mut self, n: &VarDeclarator) {
if self.config.ignore_named_child_scope {
if let (Pat::Ident(..), Some(..)) = (&n.name, n.init.as_deref()) {
return;
}
}
}

fn visit_callee(&mut self, n: &Callee) {
let ctx = Ctx {
is_callee: true,
..self.ctx
};
n.visit_children_with(&mut *self.with_ctx(ctx));
n.visit_children_with(self);
}
}
4 changes: 4 additions & 0 deletions crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ where
&n.right,
AliasConfig {
marks: self.marks,
ignore_named_child_scope: true,
..Default::default()
},
) {
Expand Down Expand Up @@ -758,6 +759,7 @@ where
&n.function,
AliasConfig {
marks: self.marks,
ignore_named_child_scope: true,
..Default::default()
},
) {
Expand Down Expand Up @@ -788,6 +790,7 @@ where
&n.function,
AliasConfig {
marks: self.marks,
ignore_named_child_scope: true,
..Default::default()
},
) {
Expand Down Expand Up @@ -1285,6 +1288,7 @@ where
init,
AliasConfig {
marks: self.marks,
ignore_named_child_scope: true,
..Default::default()
},
) {
Expand Down

0 comments on commit 37616c3

Please sign in to comment.