1mod block;
2mod builder;
3pub mod dot;
4pub mod visit;
5
6use std::fmt;
7
8use itertools::Itertools;
9use nonmax::NonMaxU32;
10use oxc_index::{Idx, IndexVec};
11use petgraph::{
12 Direction,
13 visit::{Control, DfsEvent, EdgeRef},
14};
15
16pub mod graph {
17 pub use petgraph::*;
18 pub mod visit {
19 pub use petgraph::visit::*;
20
21 pub use super::super::visit::*;
22 }
23}
24
25pub use block::*;
26pub use builder::{ControlFlowGraphBuilder, CtxCursor, CtxFlags};
27pub use dot::DisplayDot;
28use visit::set_depth_first_search;
29
30pub type BlockNodeId = petgraph::stable_graph::NodeIndex;
31#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub struct BasicBlockId(NonMaxU32);
33
34impl Idx for BasicBlockId {
35 #[expect(clippy::cast_possible_truncation)]
36 fn from_usize(idx: usize) -> Self {
37 assert!(idx < u32::MAX as usize);
38 Self(unsafe { NonMaxU32::new_unchecked(idx as u32) })
40 }
41
42 fn index(self) -> usize {
43 self.0.get() as usize
44 }
45}
46
47impl PartialEq<u32> for BasicBlockId {
48 #[inline]
49 fn eq(&self, other: &u32) -> bool {
50 self.0.get() == *other
51 }
52}
53
54impl fmt::Display for BasicBlockId {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 self.0.fmt(f)
57 }
58}
59
60pub type Graph = petgraph::graph::DiGraph<BasicBlockId, EdgeType>;
61
62#[derive(Debug, Clone)]
63pub enum EdgeType {
64 Jump,
66 Normal,
68 Backedge,
70 NewFunction,
72 Finalize,
74 Error(ErrorEdgeKind),
76
77 Unreachable,
79 Join,
82}
83
84#[derive(Default, Debug, Clone, Copy)]
85pub enum ErrorEdgeKind {
86 Explicit,
88 #[default]
90 Implicit,
91}
92
93pub enum EvalConstConditionResult {
94 NotFound,
95 Fail,
96 Eval(bool),
97}
98
99#[derive(Debug)]
100pub struct ControlFlowGraph {
101 pub graph: Graph,
102 pub basic_blocks: IndexVec<BasicBlockId, BasicBlock>,
103}
104
105impl ControlFlowGraph {
106 pub fn graph(&self) -> &Graph {
107 &self.graph
108 }
109
110 pub fn basic_block(&self, id: BlockNodeId) -> &BasicBlock {
112 let ix = *self.graph.node_weight(id).expect("expected a valid node id in self.graph");
113 self.basic_blocks.get(ix).expect("expected a valid node id in self.basic_blocks")
114 }
115
116 pub fn basic_block_mut(&mut self, id: BlockNodeId) -> &mut BasicBlock {
118 let ix = *self.graph.node_weight(id).expect("expected a valid node id in self.graph");
119 self.basic_blocks.get_mut(ix).expect("expected a valid node id in self.basic_blocks")
120 }
121
122 pub fn is_reachable(&self, from: BlockNodeId, to: BlockNodeId) -> bool {
123 self.is_reachable_filtered(from, to, |_| Control::Continue)
124 }
125
126 pub fn is_reachable_filtered<F: Fn(BlockNodeId) -> Control<bool>>(
127 &self,
128 from: BlockNodeId,
129 to: BlockNodeId,
130 filter: F,
131 ) -> bool {
132 if from == to {
133 return true;
134 }
135 let graph = &self.graph;
136 set_depth_first_search(&self.graph, Some(from), |event| match event {
137 DfsEvent::TreeEdge(a, b) => {
138 let filter_result = filter(a);
139 if !matches!(filter_result, Control::Continue) {
140 return filter_result;
141 }
142 let unreachable = !graph.edges_connecting(a, b).any(|edge| {
143 !matches!(edge.weight(), EdgeType::NewFunction | EdgeType::Unreachable)
144 });
145
146 if unreachable {
147 Control::Prune
148 } else if b == to {
149 return Control::Break(true);
150 } else {
151 Control::Continue
152 }
153 }
154 _ => Control::Continue,
155 })
156 .break_value()
157 .unwrap_or(false)
158 }
159
160 pub fn is_infinite_loop_start<F>(
167 &self,
168 node: BlockNodeId,
169 try_eval_const_condition: F,
170 ) -> Option<(BlockNodeId, BlockNodeId)>
171 where
172 F: Fn(&Instruction) -> EvalConstConditionResult,
173 {
174 fn get_jump_target(graph: &Graph, node: BlockNodeId) -> Option<BlockNodeId> {
175 graph
176 .edges_directed(node, Direction::Outgoing)
177 .find_or_first(|e| matches!(e.weight(), EdgeType::Jump))
178 .map(|it| it.target())
179 }
180
181 let basic_block = self.basic_block(node);
182 let mut backedges = self
183 .graph
184 .edges_directed(node, Direction::Incoming)
185 .filter(|e| matches!(e.weight(), EdgeType::Backedge));
186
187 let backedge = backedges.next()?;
189
190 assert!(
191 backedges.next().is_none(),
192 "there should only be one backedge to each basic block."
193 );
194
195 if basic_block.instructions().is_empty()
197 && !self
198 .graph
199 .edges_directed(node, Direction::Outgoing)
200 .any(|e| matches!(e.weight(), EdgeType::Backedge))
201 {
202 return get_jump_target(&self.graph, node).map(|it| (it, node));
203 }
204
205 let Ok(only_instruction) = basic_block.instructions().iter().exactly_one() else {
207 return None;
208 };
209
210 if matches!(
213 try_eval_const_condition(only_instruction),
214 EvalConstConditionResult::Eval(true)
215 ) {
216 get_jump_target(&self.graph, node).map(|it| (it, node))
217 } else if matches!(
218 self.basic_block(backedge.source())
219 .instructions()
220 .iter()
221 .exactly_one()
222 .map_or_else(|_| EvalConstConditionResult::NotFound, try_eval_const_condition),
223 EvalConstConditionResult::Eval(true)
224 ) {
225 get_jump_target(&self.graph, node).map(|it| (node, it))
226 } else {
227 None
228 }
229 }
230
231 pub fn is_cyclic(&self, node: BlockNodeId) -> bool {
232 set_depth_first_search(&self.graph, Some(node), |event| match event {
233 DfsEvent::BackEdge(_, id) if id == node => Err(()),
234 _ => Ok(()),
235 })
236 .is_err()
237 }
238}