turbopack_core/module_graph/
binding_usage_info.rs1use std::collections::hash_map::Entry;
2
3use anyhow::{Context, Result, bail};
4use auto_hash_map::AutoSet;
5use rustc_hash::{FxHashMap, FxHashSet};
6use tracing::Instrument;
7use turbo_rcstr::RcStr;
8use turbo_tasks::{ResolvedVc, Vc};
9
10use crate::{
11 module::Module,
12 module_graph::{GraphEdgeIndex, GraphTraversalAction, ModuleGraph},
13 reference::ModuleReference,
14 resolve::{ExportUsage, ImportUsage},
15};
16
17#[turbo_tasks::value]
18#[derive(Clone, Default, Debug)]
19pub struct BindingUsageInfo {
20 unused_references: FxHashSet<ResolvedVc<Box<dyn ModuleReference>>>,
21 #[turbo_tasks(trace_ignore)]
22 unused_references_edges: FxHashSet<GraphEdgeIndex>,
23
24 used_exports: FxHashMap<ResolvedVc<Box<dyn Module>>, ModuleExportUsageInfo>,
25 export_circuit_breakers: FxHashSet<ResolvedVc<Box<dyn Module>>>,
26}
27
28#[turbo_tasks::value(transparent)]
29pub struct OptionBindingUsageInfo(Option<ResolvedVc<BindingUsageInfo>>);
30
31#[turbo_tasks::value]
32pub struct ModuleExportUsage {
33 pub export_usage: ResolvedVc<ModuleExportUsageInfo>,
34 pub is_circuit_breaker: bool,
36}
37#[turbo_tasks::value_impl]
38impl ModuleExportUsage {
39 #[turbo_tasks::function]
40 pub async fn all() -> Result<Vc<Self>> {
41 Ok(Self {
42 export_usage: ModuleExportUsageInfo::all().to_resolved().await?,
43 is_circuit_breaker: true,
44 }
45 .cell())
46 }
47}
48
49impl BindingUsageInfo {
50 pub fn is_reference_unused_edge(&self, edge: &GraphEdgeIndex) -> bool {
51 self.unused_references_edges.contains(edge)
52 }
53
54 pub fn is_reference_unused(&self, reference: &ResolvedVc<Box<dyn ModuleReference>>) -> bool {
55 self.unused_references.contains(reference)
56 }
57
58 pub async fn used_exports(
59 &self,
60 module: ResolvedVc<Box<dyn Module>>,
61 ) -> Result<Vc<ModuleExportUsage>> {
62 let is_circuit_breaker = self.export_circuit_breakers.contains(&module);
63 let Some(exports) = self.used_exports.get(&module) else {
64 let ident = module.ident_string().await?;
66 if ident.contains(".wasm_.loader.mjs") || ident.contains("/__nextjs-internal-proxy.") {
67 return Ok(ModuleExportUsage::all());
72 }
73
74 bail!("export usage not found for module: {ident:?}");
75 };
76 Ok(ModuleExportUsage {
77 export_usage: exports.clone().resolved_cell(),
78 is_circuit_breaker,
79 }
80 .cell())
81 }
82}
83
84#[turbo_tasks::function(operation)]
85pub async fn compute_binding_usage_info(
86 graph: ResolvedVc<ModuleGraph>,
87 remove_unused_imports: bool,
88) -> Result<Vc<BindingUsageInfo>> {
89 let span_outer = tracing::info_span!(
90 "compute bindung usage info",
91 visit_count = tracing::field::Empty,
92 unused_reference_count = tracing::field::Empty
93 );
94 let span = span_outer.clone();
95
96 async move {
97 let mut used_exports = FxHashMap::<_, ModuleExportUsageInfo>::default();
98 #[cfg(debug_assertions)]
99 let mut debug_unused_references_name = FxHashSet::<(
100 ResolvedVc<Box<dyn Module>>,
101 ExportUsage,
102 ResolvedVc<Box<dyn Module>>,
103 )>::default();
104 let mut unused_references_edges = FxHashSet::default();
105 let mut unused_references = FxHashSet::default();
106
107 if graph.await?.binding_usage.is_some() {
108 panic!(
120 "don't run compute_binding_usage_info on a graph after calling \
121 without_unused_references"
122 );
123 }
124
125 let graph = graph.read_graphs().await?;
126
127 let entries = graph.graphs.iter().flat_map(|g| g.entry_modules());
128
129 let visit_count = graph.traverse_edges_fixed_point_with_priority(
130 entries.map(|m| (m, 0)),
131 &mut (),
132 |parent, target, _| {
133 let Some((parent, ref_data, edge)) = parent else {
135 used_exports.insert(target, ModuleExportUsageInfo::All);
136 return Ok(GraphTraversalAction::Continue);
137 };
138
139 if remove_unused_imports {
140 match &ref_data.binding_usage.import {
142 ImportUsage::Exports(exports) => {
143 let source_used_exports = used_exports
144 .get(&parent)
145 .context("parent module must have usage info")?;
146 if exports
147 .iter()
148 .all(|e| !source_used_exports.is_export_used(e))
149 {
150 #[cfg(debug_assertions)]
151 debug_unused_references_name.insert((
152 parent,
153 ref_data.binding_usage.export.clone(),
154 target,
155 ));
156 unused_references_edges.insert(edge);
157 unused_references.insert(ref_data.reference);
158
159 return Ok(GraphTraversalAction::Skip);
160 } else {
161 #[cfg(debug_assertions)]
162 debug_unused_references_name.remove(&(
163 parent,
164 ref_data.binding_usage.export.clone(),
165 target,
166 ));
167 unused_references_edges.remove(&edge);
168 unused_references.remove(&ref_data.reference);
169 }
171 }
172 ImportUsage::SideEffects => {
173 #[cfg(debug_assertions)]
174 debug_unused_references_name.remove(&(
175 parent,
176 ref_data.binding_usage.export.clone(),
177 target,
178 ));
179 unused_references_edges.remove(&edge);
180 unused_references.remove(&ref_data.reference);
181 }
183 }
184 }
185
186 let entry = used_exports.entry(target);
187 let is_first_visit = matches!(entry, Entry::Vacant(_));
188 if entry.or_default().add(&ref_data.binding_usage.export) || is_first_visit {
189 Ok(GraphTraversalAction::Continue)
192 } else {
193 Ok(GraphTraversalAction::Skip)
194 }
195 },
196 |_, _| Ok(0),
197 )?;
198
199 let mut export_circuit_breakers = FxHashSet::default();
203 graph.traverse_cycles(
204 |e| e.chunking_type.is_parallel(),
205 |cycle| {
206 export_circuit_breakers.extend(cycle.iter().map(|n| **n));
218 Ok(())
219 },
220 )?;
221
222 span.record("visit_count", visit_count);
223 span.record("unused_reference_count", unused_references.len());
224
225 #[cfg(debug_assertions)]
226 {
227 use once_cell::sync::Lazy;
228 static PRINT_UNUSED_REFERENCES: Lazy<bool> = Lazy::new(|| {
229 std::env::var_os("TURBOPACK_PRINT_UNUSED_REFERENCES")
230 .is_some_and(|v| v == "1" || v == "true")
231 });
232 if *PRINT_UNUSED_REFERENCES {
233 use turbo_tasks::TryJoinIterExt;
234 println!(
235 "unused references: {:#?}",
236 debug_unused_references_name
237 .iter()
238 .map(async |(s, e, t)| Ok((
239 s.ident_string().await?,
240 e,
241 t.ident_string().await?,
242 )))
243 .try_join()
244 .await?
245 );
246 }
247 }
248
249 Ok(BindingUsageInfo {
250 unused_references,
251 unused_references_edges,
252 used_exports,
253 export_circuit_breakers,
254 }
255 .cell())
256 }
257 .instrument(span_outer)
258 .await
259}
260
261#[turbo_tasks::value]
262#[derive(Default, Clone, Debug)]
263pub enum ModuleExportUsageInfo {
264 #[default]
266 Evaluation,
267 Exports(AutoSet<RcStr>),
268 All,
269}
270
271#[turbo_tasks::value_impl]
272impl ModuleExportUsageInfo {
273 #[turbo_tasks::function]
274 pub fn all() -> Vc<Self> {
275 ModuleExportUsageInfo::All.cell()
276 }
277}
278
279impl ModuleExportUsageInfo {
280 pub fn add(&mut self, usage: &ExportUsage) -> bool {
282 match (&mut *self, usage) {
283 (Self::All, _) => false,
284 (_, ExportUsage::All) => {
285 *self = Self::All;
286 true
287 }
288 (Self::Evaluation, ExportUsage::Named(name)) => {
289 *self = Self::Exports(AutoSet::from_iter([name.clone()]));
291 true
292 }
293 (Self::Exports(l), ExportUsage::Named(r)) => {
294 l.insert(r.clone())
296 }
297 (_, ExportUsage::Evaluation) => false,
298 }
299 }
300
301 pub fn is_export_used(&self, export: &RcStr) -> bool {
302 match self {
303 Self::All => true,
304 Self::Evaluation => false,
305 Self::Exports(exports) => exports.contains(export),
306 }
307 }
308}