|
| 1 | +use std::iter; |
| 2 | + |
1 | 3 | use rolldown_common::ModuleId; |
2 | | -use rustc_hash::FxHashSet; |
| 4 | +use rustc_hash::{FxHashMap, FxHashSet}; |
3 | 5 |
|
4 | 6 | use super::LinkStage; |
5 | 7 |
|
6 | | -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] |
7 | | -enum Action { |
8 | | - Enter(ModuleId), |
9 | | - Exit(ModuleId), |
| 8 | +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] |
| 9 | +enum Status { |
| 10 | + ToBeExecuted(ModuleId), |
| 11 | + WaitForExit(ModuleId), |
10 | 12 | } |
11 | 13 |
|
12 | 14 | impl<'a> LinkStage<'a> { |
13 | 15 | pub fn sort_modules(&mut self) { |
14 | | - let mut stack = self |
| 16 | + // The runtime module should always be the first module to be executed |
| 17 | + let mut execution_stack = self |
15 | 18 | .entries |
16 | 19 | .iter() |
17 | | - .map(|entry_point| Action::Enter(entry_point.id.into())) |
18 | 20 | .rev() |
| 21 | + .map(|entry| Status::ToBeExecuted(entry.id.into())) |
| 22 | + .chain(iter::once(Status::ToBeExecuted(self.runtime.id().into()))) |
19 | 23 | .collect::<Vec<_>>(); |
20 | | - // The runtime module should always be the first module to be executed |
21 | | - stack.push(Action::Enter(self.runtime.id().into())); |
22 | | - let mut entered_ids = FxHashSet::default(); |
23 | | - entered_ids |
| 24 | + |
| 25 | + let mut stack_indexes_of_executing_id = FxHashMap::default(); |
| 26 | + let mut executed_ids = FxHashSet::default(); |
| 27 | + executed_ids |
24 | 28 | .shrink_to(self.module_table.normal_modules.len() + self.module_table.external_modules.len()); |
| 29 | + |
25 | 30 | let mut sorted_modules = Vec::with_capacity(self.module_table.normal_modules.len()); |
26 | 31 | let mut next_exec_order = 0; |
27 | | - while let Some(action) = stack.pop() { |
28 | | - match action { |
29 | | - Action::Enter(id) => { |
30 | | - if !entered_ids.contains(&id) { |
31 | | - entered_ids.insert(id); |
32 | | - stack.push(Action::Exit(id)); |
| 32 | + let mut circular_dependencies = FxHashSet::default(); |
| 33 | + while let Some(status) = execution_stack.pop() { |
| 34 | + match status { |
| 35 | + Status::ToBeExecuted(id) => { |
| 36 | + if executed_ids.contains(&id) { |
| 37 | + if let Some(index) = stack_indexes_of_executing_id.get(&id).copied() { |
| 38 | + // Executing |
| 39 | + let cycles = execution_stack[index..] |
| 40 | + .iter() |
| 41 | + .filter_map(|action| match action { |
| 42 | + // Only modules with `Status::WaitForExit` are on the execution chain |
| 43 | + Status::ToBeExecuted(_) => None, |
| 44 | + Status::WaitForExit(id) => Some(*id), |
| 45 | + }) |
| 46 | + .chain(iter::once(id)) |
| 47 | + .collect::<Box<[_]>>(); |
| 48 | + circular_dependencies.insert(cycles); |
| 49 | + } else { |
| 50 | + // It's already executed in other import chain, no need to execute again |
| 51 | + } |
| 52 | + } else { |
| 53 | + executed_ids.insert(id); |
| 54 | + execution_stack.push(Status::WaitForExit(id)); |
| 55 | + debug_assert!( |
| 56 | + !stack_indexes_of_executing_id.contains_key(&id), |
| 57 | + "A module should not be executing the same module twice" |
| 58 | + ); |
| 59 | + stack_indexes_of_executing_id.insert(id, execution_stack.len() - 1); |
| 60 | + |
33 | 61 | if let ModuleId::Normal(module_id) = id { |
34 | 62 | let module = &self.module_table.normal_modules[module_id]; |
35 | | - stack.extend( |
| 63 | + execution_stack.extend( |
36 | 64 | module |
37 | 65 | .import_records |
38 | 66 | .iter() |
39 | 67 | .filter(|rec| rec.kind.is_static()) |
40 | 68 | .map(|rec| rec.resolved_module) |
41 | 69 | .rev() |
42 | | - .map(Action::Enter), |
| 70 | + .map(Status::ToBeExecuted), |
43 | 71 | ); |
44 | 72 | } |
45 | 73 | } |
46 | 74 | } |
47 | | - Action::Exit(id) => { |
| 75 | + Status::WaitForExit(id) => { |
| 76 | + executed_ids.insert(id); |
48 | 77 | match id { |
49 | 78 | ModuleId::Normal(id) => { |
50 | 79 | let module = &mut self.module_table.normal_modules[id]; |
| 80 | + debug_assert!(module.exec_order == u32::MAX); |
51 | 81 | module.exec_order = next_exec_order; |
52 | 82 | sorted_modules.push(id); |
53 | 83 | } |
54 | 84 | ModuleId::External(id) => { |
55 | 85 | let module = &mut self.module_table.external_modules[id]; |
| 86 | + debug_assert!(module.exec_order == u32::MAX); |
56 | 87 | module.exec_order = next_exec_order; |
57 | 88 | } |
58 | 89 | } |
59 | 90 | next_exec_order += 1; |
| 91 | + debug_assert!(stack_indexes_of_executing_id.contains_key(&id)); |
| 92 | + stack_indexes_of_executing_id.remove(&id); |
60 | 93 | } |
61 | 94 | } |
62 | 95 | } |
| 96 | + |
| 97 | + if !circular_dependencies.is_empty() { |
| 98 | + let mut cycles = circular_dependencies.into_iter().collect::<Vec<_>>(); |
| 99 | + cycles.sort(); |
| 100 | + // TODO: emit warning |
| 101 | + } |
| 102 | + |
63 | 103 | self.sorted_modules = sorted_modules; |
64 | 104 | debug_assert_eq!( |
65 | 105 | self.sorted_modules.first().copied(), |
|
0 commit comments