11use anyhow:: Result ;
2- use rustc_hash:: FxHashSet ;
2+ use rustc_hash:: { FxHashMap , FxHashSet } ;
33use turbo_tasks:: { ResolvedVc , TryJoinIterExt , Vc } ;
44
55use 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) ]
1414pub struct SideEffectFreeModules ( FxHashSet < ResolvedVc < Box < dyn Module > > > ) ;
1515
16- #[ turbo_tasks:: function( operation ) ]
16+ #[ turbo_tasks:: function]
1717pub 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