Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add scope hoisting #8271

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/turbopack-ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod minify;
pub mod parse;
mod path_visitor;
pub mod references;
pub mod scope_hoisting;
pub mod side_effect_optimization;
pub(crate) mod special_cases;
pub(crate) mod static_code;
Expand Down
167 changes: 167 additions & 0 deletions crates/turbopack-ecmascript/src/scope_hoisting/list_finder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use std::{collections::VecDeque, mem::take};

use anyhow::Result;
use indexmap::IndexSet;
use rustc_hash::FxHashMap;
use turbo_tasks::Vc;

#[turbo_tasks::value]
pub struct Item {
pub shallow_list: Vc<Vec<Vc<Item>>>,
pub cond_loaded_modules: Vc<IndexSet<Vc<Item>>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ItemKind {
Enter,
Leave,
}

pub async fn find_list(entrypoints: Vc<Vec<Vc<Item>>>) -> Result<Vc<Vec<Vc<Item>>>> {
// Create an empty set X for items that have been processed.
let mut done = IndexSet::new();
let mut queue = VecDeque::<(_, ItemKind)>::new();
queue.extend(entrypoints.await?.iter().map(|vc| (*vc, ItemKind::Enter)));

// Create an empty list L.
let mut list = vec![];

// Create an empty set S.
let mut set = IndexSet::default();

while let Some((item, kind)) = queue.pop_front() {
// Add item to X.
if !done.insert(item) {
continue;
}

match kind {
ItemKind::Enter => {
// Put all items from the shallow list of the dequeued item at the front of the
// queue Q with type ENTER
for shallow_item in item.await?.shallow_list.await?.iter() {
queue.push_front((*shallow_item, ItemKind::Enter));
}

// Put all items from conditional loaded modules into set S.
for cond_loaded_module in item.await?.cond_loaded_modules.await?.iter() {
set.insert(*cond_loaded_module);
}

// Put the current item into the queue with type LEAVE
queue.push_back((item, ItemKind::Leave));
}
ItemKind::Leave => {
// Put the current item into the list L.
list.push(item);
}
}

// Remove all items of L from S.
//
// An item is already loaded by an import and conditionally loading won’t have
// an evaluation effect anymore
for &item in &list {
set.remove(&item);
}

// Set the item’s shallow list to L and the conditionally loaded modules
// to S.
let item = Item {
shallow_list: Vc::cell(take(&mut list)),
cond_loaded_modules: Vc::cell(take(&mut set)),
};

// Enqueue all items from S for processing
queue.extend(
item.cond_loaded_modules
.await?
.iter()
.map(|vc| (*vc, ItemKind::Enter)),
);
}

// Create a reverse mapping from item → (list, index)[] (sorted by
// smallest index first)
//
// So we can cheaply determine in which lists an item is and at which
// index.
macro_rules! reverse_map {
($list:expr) => {{
let mut reverse_mapping = FxHashMap::default();
for (i, list) in $list.iter().enumerate() {
for (j, item) in list.await?.iter().enumerate() {
reverse_mapping
.entry(*item)
.or_insert_with(Vec::new)
.push((i, j));
}
}

reverse_mapping
}};
}

// Gather all lists from X.
let mut lists = vec![];
for &item in done.iter() {
lists.push(item.await?.shallow_list.clone());
}

let mut reverse_mapping = reverse_map!(lists);

// Remove all mappings that only point to a single list-index tuple.
//
// They are already done and we don’t want to sort them.*
reverse_mapping.retain(|_item, list_indices| list_indices.len() != 1);

// Sort the mapping by smallest index
//
// This is important to be able to find the longest common slice in the
// following*
for (_, list_indices) in reverse_mapping.iter_mut() {
list_indices.sort_by_key(|(list, index)| (*list, *index));
}

// For each item in the mappings:

for (item, list_indices) in reverse_mapping {

Check failure on line 128 in crates/turbopack-ecmascript/src/scope_hoisting/list_finder.rs

View workflow job for this annotation

GitHub Actions / Turbopack rust check

unused variable: `list_indices`

Check failure on line 128 in crates/turbopack-ecmascript/src/scope_hoisting/list_finder.rs

View workflow job for this annotation

GitHub Actions / Turbopack rust check

unused variable: `list_indices`
// We need to split the item into a separate list. We want the list
// to be as long as possible.

// Create a new list L.
let mut list = vec![];

// TODO
// Walk all lists at the same item as long as the items in these
// lists are common (starting by current index):

let mut current_index = 0;

Check failure on line 139 in crates/turbopack-ecmascript/src/scope_hoisting/list_finder.rs

View workflow job for this annotation

GitHub Actions / Turbopack rust check

variable does not need to be mutable

Check failure on line 139 in crates/turbopack-ecmascript/src/scope_hoisting/list_finder.rs

View workflow job for this annotation

GitHub Actions / Turbopack rust check

variable does not need to be mutable
{
// Put item into L
list.push(item);

// Remove item from the mapping.
reverse_mapping.remove(&item);

Check failure on line 145 in crates/turbopack-ecmascript/src/scope_hoisting/list_finder.rs

View workflow job for this annotation

GitHub Actions / Turbopack rust check

borrow of moved value: `reverse_mapping`

Check failure on line 145 in crates/turbopack-ecmascript/src/scope_hoisting/list_finder.rs

View workflow job for this annotation

GitHub Actions / Turbopack rust check

borrow of moved value: `reverse_mapping`
}

// Put L into the set of all lists.
lists.push(Vc::cell(list));

// TODO
// Put all remaining items in the lists (starting by current index)
// into new lists and update mappings correctly. (No need to
// updating sorting, Items might not have a mapping)

let mut new_lists = vec![];

new_lists.extend(lists[current_index..].iter().map(|list| *list));

lists = new_lists;
reverse_mapping = reverse_map!(lists);
}

// At this point every item is exactly in one list.

Ok(Vc::cell(list))
}
1 change: 1 addition & 0 deletions crates/turbopack-ecmascript/src/scope_hoisting/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod list_finder;
Loading