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