fix(bundler): Use proper algorithm for dependency analysis (#1610)

swc_bundler:
 - Optimize detection of circular imports.
This commit is contained in:
강동윤 2021-04-26 18:18:57 +09:00 committed by GitHub
parent 308792dc90
commit 731dc68c92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 90 additions and 82 deletions

View File

@ -9,7 +9,7 @@ include = ["Cargo.toml", "build.rs", "src/**/*.rs", "src/**/*.js"]
license = "Apache-2.0/MIT" license = "Apache-2.0/MIT"
name = "swc_bundler" name = "swc_bundler"
repository = "https://github.com/swc-project/swc.git" repository = "https://github.com/swc-project/swc.git"
version = "0.32.5" version = "0.32.7"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -63,7 +63,7 @@ where
module.visit_mut_with(&mut DefaultHandler { module.visit_mut_with(&mut DefaultHandler {
local_ctxt: info.local_ctxt(), local_ctxt: info.local_ctxt(),
}); });
module.sort(info.id, &ctx.graph, &self.cm); module.sort(info.id, &ctx.graph, &ctx.cycles, &self.cm);
let stmt = ModuleItem::Stmt(wrap_module( let stmt = ModuleItem::Stmt(wrap_module(
SyntaxContext::empty(), SyntaxContext::empty(),

View File

@ -29,6 +29,7 @@ use EdgeDirection::Outgoing;
pub(super) struct Ctx { pub(super) struct Ctx {
/// Full dependency graph. /// Full dependency graph.
pub graph: ModuleGraph, pub graph: ModuleGraph,
pub cycles: Vec<Vec<ModuleId>>,
pub merged: CHashSet<ModuleId>, pub merged: CHashSet<ModuleId>,
pub transitive_remap: CloneMap<SyntaxContext, SyntaxContext>, pub transitive_remap: CloneMap<SyntaxContext, SyntaxContext>,
pub export_stars_in_wrapped: Lock<FxHashMap<ModuleId, Vec<SyntaxContext>>>, pub export_stars_in_wrapped: Lock<FxHashMap<ModuleId, Vec<SyntaxContext>>>,
@ -405,7 +406,7 @@ where
inline(self.injected_ctxt, entry); inline(self.injected_ctxt, entry);
entry.sort(id, &ctx.graph, &self.cm); entry.sort(id, &ctx.graph, &ctx.cycles, &self.cm);
// crate::debug::print_hygiene("done", &self.cm, &entry.clone().into()); // crate::debug::print_hygiene("done", &self.cm, &entry.clone().into());

View File

@ -46,7 +46,7 @@ where
entries: AHashMap<String, TransformedModule>, entries: AHashMap<String, TransformedModule>,
) -> Result<Vec<Bundle>, Error> { ) -> Result<Vec<Bundle>, Error> {
let start = Instant::now(); let start = Instant::now();
let (plan, graph) = self.determine_entries(entries).context("failed to plan")?; let (plan, graph, cycles) = self.determine_entries(entries).context("failed to plan")?;
let dur = Instant::now() - start; let dur = Instant::now() - start;
log::debug!("Dependency analysis took {:?}", dur); log::debug!("Dependency analysis took {:?}", dur);
@ -68,6 +68,7 @@ where
let ctx = Ctx { let ctx = Ctx {
graph, graph,
cycles,
merged: Default::default(), merged: Default::default(),
transitive_remap: Default::default(), transitive_remap: Default::default(),
export_stars_in_wrapped: Default::default(), export_stars_in_wrapped: Default::default(),

View File

@ -10,6 +10,7 @@ struct PlanBuilder {
tracked: FxHashSet<(ModuleId, ModuleId)>, tracked: FxHashSet<(ModuleId, ModuleId)>,
graph: ModuleGraph, graph: ModuleGraph,
cycles: Vec<Vec<ModuleId>>,
all: Vec<ModuleId>, all: Vec<ModuleId>,
kinds: FxHashMap<ModuleId, BundleKind>, kinds: FxHashMap<ModuleId, BundleKind>,
@ -31,16 +32,7 @@ where
pub(super) fn determine_entries( pub(super) fn determine_entries(
&self, &self,
entries: AHashMap<String, TransformedModule>, entries: AHashMap<String, TransformedModule>,
) -> Result<(Plan, ModuleGraph), Error> { ) -> Result<(Plan, ModuleGraph, Vec<Vec<ModuleId>>), Error> {
let plan = self.calculate_plan(entries)?;
Ok(plan)
}
fn calculate_plan(
&self,
entries: AHashMap<String, TransformedModule>,
) -> Result<(Plan, ModuleGraph), Error> {
let mut builder = PlanBuilder::default(); let mut builder = PlanBuilder::default();
for (name, module) in entries { for (name, module) in entries {
@ -49,7 +41,7 @@ where
None => {} None => {}
} }
self.add_to_graph(&mut builder, module.id); self.add_to_graph(&mut builder, module.id, &mut vec![]);
} }
Ok(( Ok((
@ -58,11 +50,31 @@ where
all: builder.all, all: builder.all,
}, },
builder.graph, builder.graph,
builder.cycles,
)) ))
} }
fn add_to_graph(&self, builder: &mut PlanBuilder, module_id: ModuleId) { fn add_to_graph(
if !builder.all.contains(&module_id) { &self,
builder: &mut PlanBuilder,
module_id: ModuleId,
path: &mut Vec<ModuleId>,
) {
let visited = builder.all.contains(&module_id);
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();
builder.cycles.push(cycle);
}
path.push(module_id);
if !visited {
builder.all.push(module_id); builder.all.push(module_id);
} }
builder.graph.add_node(module_id); builder.graph.add_node(module_id);
@ -84,8 +96,11 @@ where
// Prevent infinite loops. // Prevent infinite loops.
if builder.tracked.insert((module_id, src.module_id)) { if builder.tracked.insert((module_id, src.module_id)) {
self.add_to_graph(builder, 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

@ -2,10 +2,8 @@ use super::stmt::sort_stmts;
use crate::dep_graph::ModuleGraph; use crate::dep_graph::ModuleGraph;
use crate::modules::Modules; use crate::modules::Modules;
use crate::ModuleId; use crate::ModuleId;
use fxhash::FxBuildHasher;
use fxhash::FxHashSet; use fxhash::FxHashSet;
use indexmap::IndexSet; use indexmap::IndexSet;
use petgraph::algo::all_simple_paths;
use petgraph::EdgeDirection::Outgoing; use petgraph::EdgeDirection::Outgoing;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::iter::from_fn; use std::iter::from_fn;
@ -29,6 +27,7 @@ impl Modules {
&mut self, &mut self,
entry_id: ModuleId, entry_id: ModuleId,
graph: &ModuleGraph, graph: &ModuleGraph,
cycle: &Vec<Vec<ModuleId>>,
cm: &Lrc<SourceMap>, cm: &Lrc<SourceMap>,
) -> Vec<Chunk> { ) -> Vec<Chunk> {
let injected_ctxt = self.injected_ctxt; let injected_ctxt = self.injected_ctxt;
@ -51,6 +50,7 @@ impl Modules {
modules, modules,
entry_id, entry_id,
graph, graph,
cycle,
cm, cm,
)); ));
@ -64,6 +64,7 @@ fn toposort_real_modules<'a>(
mut modules: Vec<(ModuleId, Module)>, mut modules: Vec<(ModuleId, Module)>,
entry: ModuleId, entry: ModuleId,
graph: &'a ModuleGraph, graph: &'a ModuleGraph,
cycles: &'a Vec<Vec<ModuleId>>,
cm: &Lrc<SourceMap>, cm: &Lrc<SourceMap>,
) -> Vec<Chunk> { ) -> Vec<Chunk> {
let mut queue = modules.iter().map(|v| v.0).collect::<VecDeque<_>>(); let mut queue = modules.iter().map(|v| v.0).collect::<VecDeque<_>>();
@ -72,7 +73,7 @@ fn toposort_real_modules<'a>(
let mut chunks = vec![]; let mut chunks = vec![];
let start = Instant::now(); let start = Instant::now();
let sorted_ids = toposort_real_module_ids(queue, graph).collect::<Vec<_>>(); let sorted_ids = toposort_real_module_ids(queue, graph, &cycles).collect::<Vec<_>>();
let end = Instant::now(); let end = Instant::now();
log::debug!("Toposort of module ids took {:?}", end - start); log::debug!("Toposort of module ids took {:?}", end - start);
for ids in sorted_ids { for ids in sorted_ids {
@ -128,50 +129,35 @@ fn toposort_real_modules<'a>(
chunks chunks
} }
/// Get all modules in a cycle. fn cycles_for(
fn all_modules_in_circle( cycles: &Vec<Vec<ModuleId>>,
id: ModuleId, id: ModuleId,
done: &FxHashSet<ModuleId>, checked: &mut Vec<ModuleId>,
already_in_index: &mut IndexSet<ModuleId, FxBuildHasher>, ) -> IndexSet<ModuleId> {
graph: &ModuleGraph, checked.push(id);
) -> IndexSet<ModuleId, FxBuildHasher> { let mut v = cycles
let deps = graph
.neighbors_directed(id, Outgoing)
.filter(|dep| !done.contains(&dep) && !already_in_index.contains(dep))
.collect::<Vec<_>>();
let mut ids = deps
.iter() .iter()
.copied() .filter(|v| v.contains(&id))
.flat_map(|dep| {
let mut paths =
all_simple_paths::<Vec<_>, _>(&graph, dep, id, 0, None).collect::<Vec<_>>();
for path in paths.iter_mut() {
path.reverse();
}
paths
})
.flatten() .flatten()
.filter(|module_id| !done.contains(&module_id) && !already_in_index.contains(module_id)) .copied()
.collect::<IndexSet<ModuleId, FxBuildHasher>>(); .collect::<IndexSet<_>>();
already_in_index.extend(ids.iter().copied()); let ids = v.clone();
let mut new_ids = IndexSet::<_, FxBuildHasher>::default();
for &dep_id in &ids { for added in ids {
let others = all_modules_in_circle(dep_id, done, already_in_index, graph); if checked.contains(&added) {
new_ids.extend(others) continue;
}
v.extend(cycles_for(cycles, added, checked));
} }
ids.extend(new_ids);
ids v
} }
fn toposort_real_module_ids<'a>( fn toposort_real_module_ids<'a>(
mut queue: VecDeque<ModuleId>, mut queue: VecDeque<ModuleId>,
graph: &'a ModuleGraph, graph: &'a ModuleGraph,
cycles: &'a Vec<Vec<ModuleId>>,
) -> impl 'a + Iterator<Item = Vec<ModuleId>> { ) -> impl 'a + Iterator<Item = Vec<ModuleId>> {
let mut done = FxHashSet::<ModuleId>::default(); let mut done = FxHashSet::<ModuleId>::default();
@ -197,8 +183,7 @@ fn toposort_real_module_ids<'a>(
// dbg!(&deps); // dbg!(&deps);
let mut all_modules_in_circle = let mut all_modules_in_circle = cycles_for(cycles, id, &mut Default::default());
all_modules_in_circle(id, &done, &mut Default::default(), graph);
all_modules_in_circle.reverse(); all_modules_in_circle.reverse();
if all_modules_in_circle.is_empty() { if all_modules_in_circle.is_empty() {

View File

@ -19,12 +19,18 @@ impl Modules {
/// dependency between statements. /// dependency between statements.
/// ///
/// TODO: Change this to return [Module]. /// TODO: Change this to return [Module].
pub fn sort(&mut self, entry_id: ModuleId, module_graph: &ModuleGraph, cm: &Lrc<SourceMap>) { pub fn sort(
&mut self,
entry_id: ModuleId,
module_graph: &ModuleGraph,
cycles: &Vec<Vec<ModuleId>>,
cm: &Lrc<SourceMap>,
) {
log::debug!("Sorting {:?}", entry_id); log::debug!("Sorting {:?}", entry_id);
let injected_ctxt = self.injected_ctxt; let injected_ctxt = self.injected_ctxt;
let start = Instant::now(); let start = Instant::now();
let chunks = self.take_chunks(entry_id, module_graph, cm); let chunks = self.take_chunks(entry_id, module_graph, cycles, cm);
let dur = Instant::now() - start; let dur = Instant::now() - start;
log::debug!("Sorting took {:?}", dur); log::debug!("Sorting took {:?}", dur);

View File

@ -1837,9 +1837,9 @@ function hasInvalidTimeData(obj) {
} else return false; } else return false;
} }
const INVALID = "Invalid DateTime"; const INVALID = "Invalid DateTime";
const INVALID1 = "Invalid Duration";
let intlDTCache = { let intlDTCache = {
}; };
const INVALID1 = "Invalid Duration";
let now = ()=>Date.now() let now = ()=>Date.now()
, defaultZone = null, defaultLocale = null, defaultNumberingSystem = null, defaultOutputCalendar = null, throwOnInvalid = false; , defaultZone = null, defaultLocale = null, defaultNumberingSystem = null, defaultOutputCalendar = null, throwOnInvalid = false;
const INVALID2 = "Invalid Interval"; const INVALID2 = "Invalid Interval";
@ -2575,6 +2575,17 @@ function diffRelative(start, end, opts) {
} }
return format(0, opts.units[opts.units.length - 1]); return format(0, opts.units[opts.units.length - 1]);
} }
function friendlyDuration(durationish) {
if (isNumber(durationish)) {
return Duration.fromMillis(durationish);
} else if (Duration.isDuration(durationish)) {
return durationish;
} else if (typeof durationish === "object") {
return Duration.fromObject(durationish);
} else {
throw new InvalidArgumentError(`Unknown duration argument ${durationish} of type ${typeof durationish}`);
}
}
class DateTime { class DateTime {
constructor(config1){ constructor(config1){
const zone1 = config1.zone || Settings.defaultZone; const zone1 = config1.zone || Settings.defaultZone;
@ -3709,17 +3720,6 @@ class Locale {
return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar; return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;
} }
} }
function friendlyDuration(durationish) {
if (isNumber(durationish)) {
return Duration.fromMillis(durationish);
} else if (Duration.isDuration(durationish)) {
return durationish;
} else if (typeof durationish === "object") {
return Duration.fromObject(durationish);
} else {
throw new InvalidArgumentError(`Unknown duration argument ${durationish} of type ${typeof durationish}`);
}
}
class Settings { class Settings {
static get now() { static get now() {
return now; return now;

View File

@ -1837,9 +1837,9 @@ function hasInvalidTimeData(obj) {
} else return false; } else return false;
} }
const INVALID = "Invalid DateTime"; const INVALID = "Invalid DateTime";
const INVALID1 = "Invalid Duration";
let intlDTCache = { let intlDTCache = {
}; };
const INVALID1 = "Invalid Duration";
let now = ()=>Date.now() let now = ()=>Date.now()
, defaultZone = null, defaultLocale = null, defaultNumberingSystem = null, defaultOutputCalendar = null, throwOnInvalid = false; , defaultZone = null, defaultLocale = null, defaultNumberingSystem = null, defaultOutputCalendar = null, throwOnInvalid = false;
const INVALID2 = "Invalid Interval"; const INVALID2 = "Invalid Interval";
@ -2576,6 +2576,17 @@ function diffRelative(start, end, opts) {
} }
return format(0, opts.units[opts.units.length - 1]); return format(0, opts.units[opts.units.length - 1]);
} }
function friendlyDuration(durationish) {
if (isNumber(durationish)) {
return Duration.fromMillis(durationish);
} else if (Duration.isDuration(durationish)) {
return durationish;
} else if (typeof durationish === "object") {
return Duration.fromObject(durationish);
} else {
throw new InvalidArgumentError(`Unknown duration argument ${durationish} of type ${typeof durationish}`);
}
}
class DateTime { class DateTime {
constructor(config1){ constructor(config1){
const zone1 = config1.zone || Settings.defaultZone; const zone1 = config1.zone || Settings.defaultZone;
@ -3710,17 +3721,6 @@ class Locale {
return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar; return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;
} }
} }
function friendlyDuration(durationish) {
if (isNumber(durationish)) {
return Duration.fromMillis(durationish);
} else if (Duration.isDuration(durationish)) {
return durationish;
} else if (typeof durationish === "object") {
return Duration.fromObject(durationish);
} else {
throw new InvalidArgumentError(`Unknown duration argument ${durationish} of type ${typeof durationish}`);
}
}
class Settings { class Settings {
static get now() { static get now() {
return now; return now;

View File

@ -1,4 +1,4 @@
console.log('a'); console.log('a');
console.log('c');
console.log('b'); console.log('b');
console.log('c');
console.log('entry'); console.log('entry');