refactor(swc_bundler): Extract logic for analyzing cycles (#2733)

This commit is contained in:
Donny/강동윤 2021-11-12 23:33:32 +09:00 committed by GitHub
parent 3294a35417
commit b869c81888
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 207 additions and 80 deletions

View File

@ -48,6 +48,10 @@ jobs:
run: |
(cd crates/swc_ecma_ast && cargo hack check --feature-powerset --no-dev-deps)
- name: Check swc_ecma_loader
run: |
(cd crates/swc_ecma_loader && cargo hack check --feature-powerset --no-dev-deps)
- name: Check swc_ecma_transforms
run: |
(cd crates/swc_ecma_transforms && cargo hack check --feature-powerset --no-dev-deps)

39
Cargo.lock generated
View File

@ -181,6 +181,18 @@ dependencies = [
"syn",
]
[[package]]
name = "auto_impl"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "0.1.7"
@ -2477,7 +2489,7 @@ dependencies = [
[[package]]
name = "swc_bundler"
version = "0.81.0"
version = "0.81.1"
dependencies = [
"ahash",
"anyhow",
@ -2510,6 +2522,8 @@ dependencies = [
"swc_ecma_transforms_typescript",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_fast_graph",
"swc_graph_analyzer",
"swc_node_base",
"tempfile",
"testing",
@ -2577,7 +2591,7 @@ dependencies = [
name = "swc_css_codegen"
version = "0.32.0"
dependencies = [
"auto_impl",
"auto_impl 0.4.1",
"bitflags",
"swc_atoms 0.2.9",
"swc_common",
@ -3120,6 +3134,27 @@ dependencies = [
"syn",
]
[[package]]
name = "swc_fast_graph"
version = "0.1.0"
dependencies = [
"ahash",
"indexmap",
"petgraph",
"swc_common",
]
[[package]]
name = "swc_graph_analyzer"
version = "0.1.0"
dependencies = [
"ahash",
"auto_impl 0.5.0",
"petgraph",
"swc_fast_graph",
"tracing",
]
[[package]]
name = "swc_macros_common"
version = "0.3.3"

View File

@ -9,7 +9,7 @@ include = ["Cargo.toml", "build.rs", "src/**/*.rs", "src/**/*.js"]
license = "Apache-2.0/MIT"
name = "swc_bundler"
repository = "https://github.com/swc-project/swc.git"
version = "0.81.0"
version = "0.81.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
@ -46,6 +46,8 @@ swc_ecma_transforms_base = {version = "0.44.0", path = "../swc_ecma_transforms_b
swc_ecma_transforms_optimization = {version = "0.64.0", path = "../swc_ecma_transforms_optimization"}
swc_ecma_utils = {version = "0.52.0", path = "../swc_ecma_utils"}
swc_ecma_visit = {version = "0.44.0", path = "../swc_ecma_visit"}
swc_fast_graph = {version = "0.1", path = "../swc_fast_graph/"}
swc_graph_analyzer = {version = "0.1", path = "../swc_graph_analyzer/"}
tracing = "0.1.28"
[dev-dependencies]

View File

@ -1,22 +1,17 @@
use crate::{
bundler::load::TransformedModule, dep_graph::ModuleGraph, BundleKind, Bundler, Load, ModuleId,
Resolve,
bundler::{load::TransformedModule, scope::Scope},
dep_graph::ModuleGraph,
BundleKind, Bundler, Load, ModuleId, Resolve,
};
use anyhow::{bail, Error};
use swc_common::collections::{AHashMap, AHashSet};
use swc_common::collections::AHashMap;
use swc_graph_analyzer::{DepGraph, GraphAnalyzer};
#[cfg(test)]
mod tests;
#[derive(Debug, Default)]
struct PlanBuilder {
/// `(src, dst)`
tracked: AHashSet<(ModuleId, ModuleId)>,
graph: ModuleGraph,
cycles: Vec<Vec<ModuleId>>,
all: Vec<ModuleId>,
kinds: AHashMap<ModuleId, BundleKind>,
}
@ -28,6 +23,21 @@ pub(super) struct Plan {
pub all: Vec<ModuleId>,
}
impl DepGraph for Scope {
type ModuleId = ModuleId;
fn deps_of(&self, module_id: Self::ModuleId) -> Vec<Self::ModuleId> {
let m = self.get_module(module_id).expect("failed to get module");
m.imports
.specifiers
.iter()
.chain(m.exports.reexports.iter())
.map(|v| v.0.module_id)
.collect()
}
}
impl<L, R> Bundler<'_, L, R>
where
L: Load,
@ -38,6 +48,7 @@ where
entries: AHashMap<String, TransformedModule>,
) -> Result<(Plan, ModuleGraph, Vec<Vec<ModuleId>>), Error> {
let mut builder = PlanBuilder::default();
let mut analyzer = GraphAnalyzer::new(&self.scope);
for (name, module) in entries {
match builder.kinds.insert(module.id, BundleKind::Named { name }) {
@ -45,77 +56,19 @@ where
None => {}
}
self.add_to_graph(&mut builder, module.id, &mut vec![module.id]);
analyzer.load(module.id);
}
let res = analyzer.into_result();
// dbg!(&builder.cycles);
Ok((
Plan {
entries: builder.kinds,
all: builder.all,
all: res.all,
},
builder.graph,
builder.cycles,
res.graph,
res.cycles,
))
}
fn add_to_graph(
&self,
builder: &mut PlanBuilder,
module_id: ModuleId,
path: &mut Vec<ModuleId>,
) {
if cfg!(test) {
tracing::debug!("Adding {:?} to the graph (path = {:?})", module_id, path);
}
let visited = builder.all.contains(&module_id);
// dbg!(visited);
// dbg!(&path);
let cycle_rpos = if visited {
path.iter().rposition(|v| *v == module_id)
} else {
None
};
if let Some(rpos) = cycle_rpos {
let cycle = path[rpos..].to_vec();
tracing::debug!("Found cycle: {:?}", cycle);
builder.cycles.push(cycle);
}
let prev_last = *path.last().unwrap();
// Prevent infinite recursion.
if !builder.tracked.insert((prev_last, module_id)) {
return;
}
path.push(module_id);
if !visited {
builder.all.push(module_id);
}
builder.graph.add_node(module_id);
let m = self
.scope
.get_module(module_id)
.expect("failed to get module");
for (src, _) in m
.imports
.specifiers
.iter()
.chain(m.exports.reexports.iter())
{
tracing::debug!("Dep: {} -> {}", module_id, src.module_id);
builder.graph.add_edge(module_id, src.module_id, ());
self.add_to_graph(builder, src.module_id, path);
}
let res = path.pop();
debug_assert_eq!(res, Some(module_id));
}
}

View File

@ -1,3 +1,4 @@
use crate::{util::fast_graph::FastDiGraphMap, ModuleId};
use crate::ModuleId;
use swc_fast_graph::digraph::FastDiGraphMap;
pub(crate) type ModuleGraph = FastDiGraphMap<ModuleId, ()>;

View File

@ -1,10 +1,10 @@
use crate::util::fast_graph::FastDiGraphMap;
use petgraph::{
EdgeDirection,
EdgeDirection::{Incoming, Outgoing},
};
use std::{collections::VecDeque, iter::repeat};
use swc_common::collections::AHashSet;
use swc_fast_graph::digraph::FastDiGraphMap;
/// Is dependency between nodes hard?
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View File

@ -6,8 +6,6 @@ use swc_ecma_ast::*;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_visit::{noop_visit_mut_type, VisitMut};
pub(crate) mod fast_graph;
#[cfg(feature = "concurrent")]
pub(crate) type Readonly<T> = std::sync::Arc<T>;

View File

@ -0,0 +1,17 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "Faster version of petgraph"
edition = "2018"
include = ["Cargo.toml", "src/**/*.rs"]
license = "Apache-2.0"
name = "swc_fast_graph"
repository = "https://github.com/swc-project/swc.git"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ahash = "0.7.6"
indexmap = "1.7.0"
petgraph = "0.5"
swc_common = {version = "0.14.6", path = "../swc_common"}

View File

@ -0,0 +1 @@
pub mod digraph;

View File

@ -0,0 +1,17 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "Graph analyzer"
edition = "2018"
include = ["Cargo.toml", "src/**/*.rs"]
license = "Apache-2.0"
name = "swc_graph_analyzer"
repository = "https://github.com/swc-project/swc.git"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ahash = "0.7.6"
auto_impl = "0.5.0"
petgraph = "0.5.0"
swc_fast_graph = {version = "0.1", path = "../swc_fast_graph/"}
tracing = "0.1.29"

View File

@ -0,0 +1,99 @@
use ahash::AHashSet;
use auto_impl::auto_impl;
use std::{fmt::Debug, hash::Hash, marker::PhantomData};
use swc_fast_graph::digraph::FastDiGraphMap;
#[auto_impl(&, Box, Rc, Arc)]
pub trait DepGraph {
type ModuleId: Debug + Copy + Eq + Hash + Ord;
fn deps_of(&self, module_id: Self::ModuleId) -> Vec<Self::ModuleId>;
}
pub struct GraphAnalyzer<G>
where
G: DepGraph,
{
/// `(src, dst)`
tracked: AHashSet<(G::ModuleId, G::ModuleId)>,
dep_graph: G,
data: GraphResult<G>,
}
impl<G> GraphAnalyzer<G>
where
G: DepGraph,
{
pub fn new(dep_graph: G) -> Self {
Self {
dep_graph,
data: GraphResult {
all: Default::default(),
graph: Default::default(),
cycles: Default::default(),
_marker: Default::default(),
},
tracked: Default::default(),
}
}
pub fn load(&mut self, entry: G::ModuleId) {
self.add_to_graph(entry, &mut vec![entry])
}
fn add_to_graph(&mut self, module_id: G::ModuleId, path: &mut Vec<G::ModuleId>) {
let visited = self.data.all.contains(&module_id);
// dbg!(visited);
// dbg!(&path);
let cycle_rpos = if visited {
path.iter().rposition(|v| *v == module_id)
} else {
None
};
if let Some(rpos) = cycle_rpos {
let cycle = path[rpos..].to_vec();
tracing::debug!("Found cycle: {:?}", cycle);
self.data.cycles.push(cycle);
}
let prev_last = *path.last().unwrap();
// Prevent infinite recursion.
if !self.tracked.insert((prev_last, module_id)) {
return;
}
path.push(module_id);
if !visited {
self.data.all.push(module_id);
}
self.data.graph.add_node(module_id);
for dep_module_id in self.dep_graph.deps_of(module_id) {
tracing::debug!("Dep: {:?} -> {:?}", module_id, dep_module_id);
self.data.graph.add_edge(module_id, dep_module_id, ());
self.add_to_graph(dep_module_id, path);
}
let res = path.pop();
debug_assert_eq!(res, Some(module_id));
}
pub fn into_result(self) -> GraphResult<G> {
self.data
}
}
pub struct GraphResult<G>
where
G: DepGraph,
{
pub all: Vec<G::ModuleId>,
pub graph: FastDiGraphMap<G::ModuleId, ()>,
pub cycles: Vec<Vec<G::ModuleId>>,
_marker: PhantomData<G>,
}