Skip to content

Commit

Permalink
feat: deconflict symbols in nested scopes in parallel
Browse files Browse the repository at this point in the history
  • Loading branch information
hyf0 committed Jan 29, 2024
1 parent 0ae3ad3 commit b3c5e01
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 65 deletions.
17 changes: 3 additions & 14 deletions crates/rolldown/src/bundler/chunk/de_conflict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ impl Chunk {
pub fn de_conflict(&mut self, graph: &LinkStageOutput) {
let mut renamer = Renamer::new(&graph.symbols, graph.modules.len());

// TODO: reserve names for keywords in both non-strict and strict mode

self
.modules
.iter()
Expand Down Expand Up @@ -55,20 +57,7 @@ impl Chunk {
});

// rename non-top-level names

self.modules.iter().copied().for_each(|module| {
let Module::Normal(module) = &graph.modules[module] else { return };
module.scope.iter_bindings().map(|(scope_id, _, _)| scope_id).for_each(|scope_id| {
if scope_id == module.scope.root_scope_id() {
// Top level symbol are already processed above
return;
}
let bindings = module.scope.get_bindings(scope_id);
bindings.iter().for_each(|(_binding_name, symbol_id)| {
renamer.add_non_top_level_symbol(module.id, (module.id, *symbol_id).into());
});
});
});
renamer.rename_non_top_level_symbol(&self.modules, &graph.modules);

self.canonical_names = renamer.into_canonical_names();
}
Expand Down
7 changes: 7 additions & 0 deletions crates/rolldown/src/bundler/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ impl Module {
}
}

pub fn as_normal(&self) -> Option<&NormalModule> {
match self {
Self::Normal(m) => Some(m),
Self::External(_) => None,
}
}

pub fn _expect_normal_mut(&mut self) -> &mut NormalModule {
match self {
Self::Normal(m) => m,
Expand Down
119 changes: 80 additions & 39 deletions crates/rolldown/src/bundler/utils/renamer.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,31 @@
use std::borrow::Cow;

use index_vec::IndexVec;
use oxc::span::Atom;
use oxc::{semantic::ScopeId, span::Atom};
use rolldown_common::{ModuleId, SymbolRef};
use rustc_hash::{FxHashMap, FxHashSet};

use crate::bundler::module::{ModuleVec, NormalModule};

use super::symbols::Symbols;

#[derive(Debug)]
pub struct Renamer<'name> {
/// The usage count of top level names
top_level_name_counts: FxHashMap<Cow<'name, Atom>, u32>,
used_canonical_names: FxHashSet<Cow<'name, Atom>>,
module_to_used_canonical_name_count: IndexVec<ModuleId, FxHashMap<Cow<'name, Atom>, u32>>,
canonical_names: FxHashMap<SymbolRef, Atom>,
symbols: &'name Symbols,
}

impl<'name> Renamer<'name> {
pub fn new(symbols: &'name Symbols, modules_len: usize) -> Self {
pub fn new(symbols: &'name Symbols, _modules_len: usize) -> Self {
Self {
top_level_name_counts: FxHashMap::default(),
canonical_names: FxHashMap::default(),
symbols,
used_canonical_names: FxHashSet::default(),
module_to_used_canonical_name_count: index_vec::index_vec![FxHashMap::default(); modules_len],
}
}

pub fn reserve(&mut self, name: Cow<'name, Atom>) {
let count = self.top_level_name_counts.entry(name).or_default();
debug_assert!(*count <= 1, "It's unnecessary to reserve a global name twice");
*count = 1;
self.used_canonical_names.insert(name);
}

pub fn add_top_level_symbol(&mut self, symbol_ref: SymbolRef) {
Expand All @@ -40,19 +34,14 @@ impl<'name> Renamer<'name> {

match self.canonical_names.entry(canonical_ref) {
std::collections::hash_map::Entry::Vacant(vacant) => {
let count = self.top_level_name_counts.entry(original_name.clone()).or_default();
let mut canonical_name = if *count == 0 {
original_name.clone()
} else {
Cow::Owned(format!("{}${}", original_name, *count).into())
};
while self.used_canonical_names.contains(&canonical_name) {
*count += 1;
canonical_name = Cow::Owned(format!("{}${}", original_name, *count).into());
let mut count = 0;
let mut candidate_name = original_name.clone();
while self.used_canonical_names.contains(&candidate_name) {
count += 1;
candidate_name = Cow::Owned(format!("{original_name}${count}").into());
}
self.used_canonical_names.insert(canonical_name.clone());
vacant.insert(canonical_name.into_owned());
*count += 1;
self.used_canonical_names.insert(candidate_name.clone());
vacant.insert(candidate_name.into_owned());
}
std::collections::hash_map::Entry::Occupied(_) => {
// The symbol is already renamed
Expand All @@ -61,25 +50,77 @@ impl<'name> Renamer<'name> {
}

// non-top-level symbols won't be linked cross-module. So the canonical `SymbolRef` for them are themselves.
pub fn add_non_top_level_symbol(&mut self, module_id: ModuleId, canonical_ref: SymbolRef) {
let original_name = Cow::Borrowed(self.symbols.get_original_name(canonical_ref));
pub fn rename_non_top_level_symbol(
&mut self,
modules_in_chunk: &[ModuleId],
modules: &ModuleVec,
) {
use rayon::prelude::*;

match self.canonical_names.entry(canonical_ref) {
std::collections::hash_map::Entry::Occupied(_) => {
// The symbol is already renamed
}
std::collections::hash_map::Entry::Vacant(vacant) => {
let might_shadowed = self.used_canonical_names.contains(&original_name);
if might_shadowed {
let used_canonical_name_count = &mut self.module_to_used_canonical_name_count[module_id];
// The name is already used in top level, so the default count is 1
let count = used_canonical_name_count.entry(original_name.clone()).or_insert(1);
let canonical_name = format!("{}${}", original_name, *count);
vacant.insert(canonical_name.into());
*count += 1;
fn rename_symbols_of_nested_scopes<'name>(
module: &'name NormalModule,
scope_id: ScopeId,
stack: &mut Vec<Cow<FxHashSet<Cow<'name, Atom>>>>,
canonical_names: &mut FxHashMap<SymbolRef, Atom>,
) {
let bindings = module.scope.get_bindings(scope_id);
let mut used_canonical_names_for_this_scope = FxHashSet::default();
used_canonical_names_for_this_scope.shrink_to(bindings.len());
bindings.iter().for_each(|(binding_name, symbol_id)| {
used_canonical_names_for_this_scope.insert(Cow::Borrowed(binding_name));
let binding_ref: SymbolRef = (module.id, *symbol_id).into();

let mut count = 1;
let mut candidate_name = Cow::Borrowed(binding_name);
loop {
let is_shadowed =
stack.iter().any(|used_canonical_names| used_canonical_names.contains(&candidate_name));

if is_shadowed {
candidate_name = Cow::Owned(format!("{binding_name}${count}").into());
count += 1;
} else {
used_canonical_names_for_this_scope.insert(candidate_name.clone());
canonical_names.insert(binding_ref, candidate_name.into_owned());
break;
}
}
}
});

stack.push(Cow::Owned(used_canonical_names_for_this_scope));
let child_scopes = module.scope.get_child_ids(scope_id).cloned().unwrap_or_default();
child_scopes.into_iter().for_each(|scope_id| {
rename_symbols_of_nested_scopes(module, scope_id, stack, canonical_names);
});
stack.pop();
}

let canonical_names_of_nested_scopes = modules_in_chunk
.par_iter()
.copied()
.filter_map(|id| modules[id].as_normal())
.flat_map(|module| {
let child_scopes: &[ScopeId] =
module.scope.get_child_ids(module.scope.root_scope_id()).map_or(&[], Vec::as_slice);

child_scopes.into_par_iter().map(|child_scope_id| {
let mut stack = vec![Cow::Borrowed(&self.used_canonical_names)];
let mut canonical_names = FxHashMap::default();
rename_symbols_of_nested_scopes(
module,
*child_scope_id,
&mut stack,
&mut canonical_names,
);
canonical_names
})
})
.reduce(FxHashMap::default, |mut acc, canonical_names| {
acc.extend(canonical_names);
acc
});

self.canonical_names.extend(canonical_names_of_nested_scopes);
}

pub fn into_canonical_names(self) -> FxHashMap<SymbolRef, Atom> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ input_file: crates/rolldown/tests/esbuild/default/arrow_fn_scope
```js
// entry.js
tests = {
0:(x=y => x + y, y) => x + y,
1:(y, x=y => x + y) => x + y,
2:(x=(y=z => x + y + z, z) => x + y + z, y, z) => x + y + z,
3:(y, z, x=(z, y=z => x + y + z) => x + y + z) => x + y + z,
4:((x = y => x + y,y),x + y),
5:((y,x = y => x + y),x + y),
6:((x = (y=z => x + y + z, z) => x + y + z,y,z),x + y + z),
7:((y,z,x = (z, y=z => x + y + z) => x + y + z),x + y + z)
0:(x$1=y$2 => x$1 + y$2, y$1) => x$1 + y$1,
1:(y$1, x$1=y$2 => x$1 + y$2) => x$1 + y$1,
2:(x$1=(y$2=z$3 => x$1 + y$2 + z$3, z$2) => x$1 + y$2 + z$2, y$1, z$1) => x$1 + y$1 + z$1,
3:(y$1, z$1, x$1=(z$2, y$2=z$3 => x$1 + y$2 + z$3) => x$1 + y$2 + z$2) => x$1 + y$1 + z$1,
4:((x = y$1 => x + y$1,y),x + y),
5:((y,x = y$1 => x + y$1),x + y),
6:((x = (y$1=z$2 => x + y$1 + z$2, z$1) => x + y$1 + z$1,y,z),x + y + z),
7:((y,z,x = (z$1, y$1=z$2 => x + y$1 + z$2) => x + y$1 + z$1),x + y + z)
};
```
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ input_file: crates/rolldown/tests/esbuild/default/nested_scope_bug
// entry.js
(() => {
function a() {
b();
b$1();
}
{
var b = () => {
var b$1 = () => {
};
}
a();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ input_file: crates/rolldown/tests/esbuild/default/var_relocating_bundle
// function-nested.js
function x() {
if (true) {
var a;
for (var b; 0; ) ; for (var e of []) ; for (var {f:f, x:[g]} of []) ; for (var h in {}) ; for (var {j:j, x:[k]} in {}) ; function l() {
var a$1;
for (var b$1; 0; ) ; for (var e$1 of []) ; for (var {f:f$1, x:[g$1]} of []) ; for (var h$1 in {}) ; for (var {j:j$1, x:[k$1]} in {}) ; function l() {
}
}
}
Expand Down

0 comments on commit b3c5e01

Please sign in to comment.