Skip to content

Commit 1fc4fb8

Browse files
committed
integrate into binding_usage_info
1 parent 4c65537 commit 1fc4fb8

File tree

2 files changed

+104
-53
lines changed

2 files changed

+104
-53
lines changed

turbopack/crates/turbopack-core/src/module_graph/binding_usage_info.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use turbo_tasks::{ResolvedVc, Vc};
99

1010
use crate::{
1111
module::Module,
12-
module_graph::{GraphEdgeIndex, GraphTraversalAction, ModuleGraph},
12+
module_graph::{
13+
GraphEdgeIndex, GraphTraversalAction, ModuleGraph,
14+
side_effect_module_info::compute_side_effect_free_module_info,
15+
},
1316
reference::ModuleReference,
1417
resolve::{ExportUsage, ImportUsage},
1518
};
@@ -121,6 +124,7 @@ pub async fn compute_binding_usage_info(
121124
without_unused_references"
122125
);
123126
}
127+
let side_effect_free_modules = compute_side_effect_free_module_info(*graph).await?;
124128

125129
let graph = graph.read_graphs().await?;
126130

@@ -145,6 +149,7 @@ pub async fn compute_binding_usage_info(
145149
.iter()
146150
.all(|e| !source_used_exports.is_export_used(e))
147151
{
152+
// all exports are unused
148153
#[cfg(debug_assertions)]
149154
debug_unused_references_name.insert((
150155
parent,
@@ -168,15 +173,28 @@ pub async fn compute_binding_usage_info(
168173
}
169174
}
170175
ImportUsage::SideEffects => {
171-
#[cfg(debug_assertions)]
172-
debug_unused_references_name.remove(&(
173-
parent,
174-
ref_data.binding_usage.export.clone(),
175-
target,
176-
));
177-
unused_references_edges.remove(&edge);
178-
unused_references.remove(&ref_data.reference);
179-
// Continue, has to always be included
176+
if side_effect_free_modules.contains(&target) {
177+
#[cfg(debug_assertions)]
178+
debug_unused_references_name.insert((
179+
parent,
180+
ref_data.binding_usage.export.clone(),
181+
target,
182+
));
183+
unused_references_edges.insert(edge);
184+
unused_references.insert(ref_data.reference);
185+
186+
return Ok(GraphTraversalAction::Skip);
187+
} else {
188+
#[cfg(debug_assertions)]
189+
debug_unused_references_name.remove(&(
190+
parent,
191+
ref_data.binding_usage.export.clone(),
192+
target,
193+
));
194+
unused_references_edges.remove(&edge);
195+
unused_references.remove(&ref_data.reference);
196+
// Continue, has to always be included
197+
}
180198
}
181199
}
182200
}
Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use anyhow::Result;
2-
use rustc_hash::FxHashSet;
2+
use rustc_hash::{FxHashMap, FxHashSet};
33
use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
44

55
use crate::{
6-
module::Module,
6+
module::{Module, ModuleSideEffects},
77
module_graph::{GraphTraversalAction, ModuleGraph, SingleModuleGraphWithBindingUsage},
88
};
99

@@ -13,7 +13,7 @@ use crate::{
1313
#[turbo_tasks::value(transparent)]
1414
pub struct SideEffectFreeModules(FxHashSet<ResolvedVc<Box<dyn Module>>>);
1515

16-
#[turbo_tasks::function(operation)]
16+
#[turbo_tasks::function]
1717
pub async fn compute_side_effect_free_module_info(
1818
graphs: ResolvedVc<ModuleGraph>,
1919
) -> Result<Vc<SideEffectFreeModules>> {
@@ -32,56 +32,89 @@ async fn compute_side_effect_free_module_info_single(
3232
graph: SingleModuleGraphWithBindingUsage,
3333
parent_side_effect_free_modules: Vc<SideEffectFreeModules>,
3434
) -> Result<Vc<SideEffectFreeModules>> {
35-
let parent_async_modules = parent_side_effect_free_modules.await?;
35+
let parent_side_effect_free_modules = parent_side_effect_free_modules.await?;
3636
let graph = graph.read().await?;
3737

38-
let self_async_modules = graph
39-
.graphs
40-
.first()
41-
.unwrap()
42-
.iter_nodes()
43-
.map(async |node| Ok((node, *node.is_self_async().await?)))
38+
let module_side_effects = graph
39+
.enumerate_nodes()
40+
.map(async |(_, node)| {
41+
Ok(match node {
42+
super::SingleModuleGraphNode::Module(module) => {
43+
// This turbo task always has a cache hit since it is called when building the
44+
// module graph. we could consider moving this information
45+
// into to the module graph, but then changes would invalidate the whole graph.
46+
(*module, *module.side_effects().await?)
47+
}
48+
super::SingleModuleGraphNode::VisitedModule { idx: _, module } => (
49+
*module,
50+
if parent_side_effect_free_modules.contains(module) {
51+
ModuleSideEffects::DeclaredSideEffectFree
52+
} else {
53+
ModuleSideEffects::SideEffectful
54+
},
55+
),
56+
})
57+
})
4458
.try_join()
4559
.await?
4660
.into_iter()
47-
.flat_map(|(k, v)| v.then_some(k))
48-
.chain(parent_async_modules.iter().copied())
49-
.collect::<FxHashSet<_>>();
61+
.collect::<FxHashMap<_, _>>();
5062

51-
// To determine which modules are async, we need to propagate the self-async flag to all
52-
// importers, which is done using a postorder traversal of the graph.
53-
//
54-
// This however doesn't cover cycles of async modules, which are handled by determining all
55-
// strongly-connected components, and then marking all the whole SCC as async if one of the
56-
// modules in the SCC is async.
63+
// Modules are categorized as side-effectful, locally side effect free and side effect free.
64+
// So we are really just interested in determining what modules that are locally side effect
65+
// free. logically we want to start at all such modules are determine if their transitive
66+
// dependencies are side effect free.
5767

58-
let mut async_modules = self_async_modules;
59-
graph.traverse_edges_from_entries_dfs(
60-
graph.graphs.first().unwrap().entry_modules(),
61-
&mut (),
62-
|_, _, _| Ok(GraphTraversalAction::Continue),
63-
|parent_info, module, _| {
64-
let Some((parent_module, ref_data)) = parent_info else {
65-
// An entry module
66-
return Ok(());
67-
};
68-
69-
if ref_data.chunking_type.is_inherit_async() && async_modules.contains(&module) {
70-
async_modules.insert(parent_module);
68+
let mut locally_side_effect_free_modules_that_have_side_effects = FxHashSet::default();
69+
graph.traverse_edges_from_entries_dfs_reversed(
70+
// Start from all the side effectful nodes
71+
module_side_effects.iter().filter_map(|(m, e)| {
72+
if *e == ModuleSideEffects::SideEffectful {
73+
Some(*m)
74+
} else {
75+
None
7176
}
72-
Ok(())
77+
}),
78+
&mut (),
79+
// child is a previously visited module that we know is side effectful
80+
|child, _parent, _s| {
81+
Ok(if let Some((child_module, _edge)) = child {
82+
match module_side_effects.get(&child_module).unwrap() {
83+
ModuleSideEffects::SideEffectful
84+
| ModuleSideEffects::DeclaredSideEffectFree => {
85+
// We have either already seen this or don't want to follow it
86+
GraphTraversalAction::Exclude
87+
}
88+
ModuleSideEffects::ModuleEvaluationIsSideEffectFree => {
89+
// this module is side effect free locally but must depend on something
90+
// effectful so it to is effectful
91+
locally_side_effect_free_modules_that_have_side_effects
92+
.insert(child_module);
93+
GraphTraversalAction::Continue
94+
}
95+
}
96+
} else {
97+
// entry point, keep going
98+
GraphTraversalAction::Continue
99+
})
73100
},
101+
|_, _, _| Ok(()),
74102
)?;
75-
76-
graph.traverse_cycles(
77-
|ref_data| ref_data.chunking_type.is_inherit_async(),
78-
|cycle| {
79-
if cycle.iter().any(|node| async_modules.contains(node)) {
80-
async_modules.extend(cycle.iter().map(|n| **n));
103+
let side_effect_free_modules = module_side_effects
104+
.into_iter()
105+
.filter_map(|(m, e)| match e {
106+
ModuleSideEffects::SideEffectful => None,
107+
ModuleSideEffects::DeclaredSideEffectFree => Some(m),
108+
ModuleSideEffects::ModuleEvaluationIsSideEffectFree => {
109+
if locally_side_effect_free_modules_that_have_side_effects.contains(&m) {
110+
None
111+
} else {
112+
Some(m)
113+
}
81114
}
82-
Ok(())
83-
},
84-
)?;
115+
})
116+
.chain(parent_side_effect_free_modules.iter().copied())
117+
.collect::<FxHashSet<_>>();
85118

86-
Ok(Vc::cell(async_modules))
119+
Ok(Vc::cell(side_effect_free_modules))
87120
}

0 commit comments

Comments
 (0)