1
1
use std:: ops:: Index ;
2
2
3
3
use petgraph:: { visit:: EdgeRef , Direction , Graph } ;
4
- use rustc_hash:: FxHashMap ;
4
+ use rustc_hash:: { FxHashMap , FxHashSet } ;
5
5
use turbo_tasks:: FxIndexSet ;
6
6
7
- use crate :: tree_shake:: graph:: { Dependency , ItemData , ItemId , ItemIdGroupKind , ItemIdItemKind } ;
7
+ use crate :: tree_shake:: graph:: { Dependency , ItemId , ItemIdItemKind } ;
8
8
9
9
pub ( super ) struct GraphOptimizer < ' a > {
10
10
pub graph_ix : & ' a FxIndexSet < ItemId > ,
11
- pub data : & ' a FxHashMap < ItemId , ItemData > ,
12
11
}
13
12
14
13
impl Index < u32 > for GraphOptimizer < ' _ > {
@@ -33,14 +32,13 @@ impl GraphOptimizer<'_> {
33
32
// imports for import bindings so the static code analysis pass can detect imports like
34
33
// 'next/dynamic'.
35
34
36
- ( matches ! (
35
+ matches ! (
37
36
item_id,
38
37
ItemId :: Item {
39
38
kind: ItemIdItemKind :: ImportBinding ( ..) ,
40
39
..
41
40
}
42
- ) ) || ( matches ! ( item_id, ItemId :: Group ( ItemIdGroupKind :: Export ( ..) ) )
43
- && self . data [ item_id] . disable_export_merging )
41
+ )
44
42
}
45
43
46
44
fn should_not_merge_iter < N > ( & self , items : & [ N ] ) -> bool
@@ -124,4 +122,132 @@ impl GraphOptimizer<'_> {
124
122
125
123
did_work
126
124
}
125
+
126
+ /// This function merges nodes that can only be reached from a single starting point.
127
+ /// Example:
128
+ /// If we have a graph with edges: A->B, B->C, A->C, B->E, D->E
129
+ /// Then B and C can only be reached from A, so they will be merged into A.
130
+ /// The resulting graph would have edges like: (A,B,C)->E, D->E
131
+ pub ( super ) fn merge_nodes_with_same_starting_point < N > (
132
+ & self ,
133
+ g : & mut Graph < Vec < N > , Dependency > ,
134
+ ) -> bool
135
+ where
136
+ N : Copy ,
137
+ Self : Index < N , Output = ItemId > ,
138
+ {
139
+ let mut did_work = false ;
140
+ let mut reachability: FxHashMap < _ , FxHashSet < _ > > = FxHashMap :: default ( ) ;
141
+
142
+ // Step 1: Build a reverse reachability map (which starting nodes can reach each node)
143
+ // We consider a "starting node" as one with no incoming edges
144
+ let starting_nodes: Vec < _ > = g
145
+ . node_indices ( )
146
+ . filter ( |& node| g. edges_directed ( node, Direction :: Incoming ) . count ( ) == 0 )
147
+ . collect ( ) ;
148
+
149
+ // For each starting node, find all nodes reachable from it
150
+ for & start in & starting_nodes {
151
+ let mut visited = FxHashSet :: default ( ) ;
152
+ let mut queue = vec ! [ start] ;
153
+
154
+ while let Some ( node) = queue. pop ( ) {
155
+ if !visited. insert ( node) {
156
+ continue ;
157
+ }
158
+
159
+ // For each outgoing edge, add the target to queue
160
+ for edge in g. edges_directed ( node, Direction :: Outgoing ) {
161
+ let target = edge. target ( ) ;
162
+ queue. push ( target) ;
163
+
164
+ // Add this starting node to the set of starting nodes that can reach target
165
+ reachability. entry ( target) . or_default ( ) . insert ( start) ;
166
+ }
167
+ }
168
+ }
169
+
170
+ // Step 2: Find nodes that are reachable from exactly one starting node
171
+ // and group them by that starting node
172
+ let mut merge_groups: FxHashMap < _ , Vec < _ > > = FxHashMap :: default ( ) ;
173
+
174
+ for node in g. node_indices ( ) {
175
+ // Skip starting nodes
176
+ if starting_nodes. contains ( & node) {
177
+ continue ;
178
+ }
179
+
180
+ // Skip nodes that should not be merged
181
+ if self . should_not_merge_iter ( g. node_weight ( node) . expect ( "Node should exist" ) ) {
182
+ continue ;
183
+ }
184
+
185
+ // If this node is reachable from exactly one starting node, add it to that group
186
+ if let Some ( reachable_from) = reachability. get ( & node) {
187
+ if reachable_from. len ( ) == 1 {
188
+ let start = * reachable_from. iter ( ) . next ( ) . unwrap ( ) ;
189
+
190
+ // Don't merge if the starting node should not be merged
191
+ if self . should_not_merge_iter ( g. node_weight ( start) . expect ( "Node should exist" ) )
192
+ {
193
+ continue ;
194
+ }
195
+
196
+ merge_groups. entry ( start) . or_default ( ) . push ( node) ;
197
+ }
198
+ }
199
+ }
200
+
201
+ // Step 3: Merge nodes into their starting points
202
+ for ( start, nodes_to_merge) in merge_groups {
203
+ if nodes_to_merge. is_empty ( ) {
204
+ continue ;
205
+ }
206
+
207
+ let mut nodes_to_remove = Vec :: new ( ) ;
208
+
209
+ for node in nodes_to_merge {
210
+ // Move outgoing edges from node to start
211
+ let outgoing_edges: Vec < _ > = g
212
+ . edges_directed ( node, Direction :: Outgoing )
213
+ . map ( |e| ( e. target ( ) , * e. weight ( ) ) )
214
+ . collect ( ) ;
215
+
216
+ for ( target, weight) in outgoing_edges {
217
+ // If there's already an edge from start to target, only update if necessary
218
+ let existing_edge = g. find_edge ( start, target) ;
219
+ match existing_edge {
220
+ Some ( e) => {
221
+ let edge_weight = g. edge_weight_mut ( e) . unwrap ( ) ;
222
+ // Only upgrade from weak to strong dependency
223
+ if matches ! ( edge_weight, Dependency :: Weak )
224
+ && !matches ! ( weight, Dependency :: Weak )
225
+ {
226
+ * edge_weight = weight;
227
+ }
228
+ }
229
+ None => {
230
+ // Add a new edge
231
+ g. add_edge ( start, target, weight) ;
232
+ }
233
+ }
234
+ }
235
+
236
+ // Move items from this node to the starting node
237
+ let items = g. node_weight ( node) . expect ( "Node should exist" ) . clone ( ) ;
238
+ g. node_weight_mut ( start) . unwrap ( ) . extend ( items) ;
239
+
240
+ nodes_to_remove. push ( node) ;
241
+ }
242
+
243
+ // Remove merged nodes (in reverse order to preserve indices)
244
+ nodes_to_remove. sort ( ) ;
245
+ for node in nodes_to_remove. into_iter ( ) . rev ( ) {
246
+ g. remove_node ( node) ;
247
+ did_work = true ;
248
+ }
249
+ }
250
+
251
+ did_work
252
+ }
127
253
}
0 commit comments