1
-use std::collections::HashMap;
1
+use std::collections::HashSet;
2
3
-use hir::{BasicBlock, BlockId, Environment};
3
+use hir::{BasicBlock, BlockId, Environment, GotoKind, InstructionIdGenerator, TerminalValue, HIR};
4
+use indexmap::IndexMap;
5
6
/// Helper struct used when converting from ESTree to HIR. Includes:
7
/// - Variable resolution
13
/// labels and variables, and then calling `build()` when the HIR
14
/// is complete.
15
pub struct Builder<'a> {
16
+ #[allow(dead_code)]
17
environment: &'a Environment<'a>,
16
- completed: HashMap<BlockId, BasicBlock<'a>>,
18
+
19
+ completed: IndexMap<BlockId, BasicBlock<'a>>,
20
+
21
entry: BlockId,
22
}
23
+
24
+impl<'a> Builder<'a> {
25
+ pub(crate) fn new(environment: &'a Environment<'a>) -> Self {
26
+ let entry = environment.next_block_id();
27
+ Self {
28
+ environment,
29
+ completed: Default::default(),
30
+ entry,
31
+ }
32
+ }
33
+
34
+ /// Completes the builder and returns the HIR if it was valid,
35
+ /// or a Diagnostic if a validation error occured.
36
+ ///
37
+ /// TODO: refine the type, only invariants should be possible here,
38
+ /// not other types of errors
39
+ pub(crate) fn build(self) -> Result<HIR<'a>, Diagnostic> {
40
+ let mut hir = HIR {
41
+ entry: self.entry,
42
+ blocks: self.completed,
43
+ };
44
+
45
+ reverse_postorder_blocks(&mut hir);
46
+ remove_unreachable_for_updates(&mut hir);
47
+ remove_unreachable_fallthroughs(&mut hir);
48
+ remove_unreachable_do_while_statements(&mut hir);
49
+ mark_instruction_ids(&mut hir)?;
50
+ mark_predecessors(&mut hir);
51
+
52
+ Ok(hir)
53
+ }
54
+}
55
+
56
+/// Modifies the HIR to put the blocks in reverse postorder, with predecessors before
57
+/// successors (except for the case of loops)
58
+fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
59
+ let mut visited = HashSet::<BlockId>::with_capacity(hir.blocks.len());
60
+ let mut postorder = Vec::<BlockId>::with_capacity(hir.blocks.len());
61
+ fn visit<'a>(
62
+ block_id: BlockId,
63
+ hir: &HIR<'a>,
64
+ visited: &mut HashSet<BlockId>,
65
+ postorder: &mut Vec<BlockId>,
66
+ ) {
67
+ if !visited.insert(block_id) {
68
+ // already visited
69
+ return;
70
+ }
71
+ let block = hir.block(block_id);
72
+ let terminal = &block.terminal;
73
+ match &terminal.value {
74
+ TerminalValue::IfTerminal(terminal) => {
75
+ visit(terminal.alternate, hir, visited, postorder);
76
+ visit(terminal.consequent, hir, visited, postorder);
77
+ }
78
+ TerminalValue::ForTerminal(terminal) => {
79
+ visit(terminal.init, hir, visited, postorder);
80
+ }
81
+ TerminalValue::DoWhileTerminal(terminal) => {
82
+ visit(terminal.body, hir, visited, postorder);
83
+ }
84
+ TerminalValue::GotoTerminal(terminal) => {
85
+ visit(terminal.block, hir, visited, postorder);
86
+ }
87
+ TerminalValue::ReturnTerminal(..) => { /* no-op */ }
88
+ }
89
+ }
90
+ visit(hir.entry, &hir, &mut visited, &mut postorder);
91
+
92
+ // NOTE: could consider sorting the blocks in-place by key
93
+ let mut blocks = IndexMap::with_capacity(hir.blocks.len());
94
+ for id in postorder.iter().rev().cloned() {
95
+ blocks.insert(id, hir.blocks.remove(&id).unwrap());
96
+ }
97
+
98
+ hir.blocks = blocks;
99
+}
100
+
101
+/// Prunes ForTerminal.update values (sets to None) if they are unreachable
102
+fn remove_unreachable_for_updates<'a>(hir: &mut HIR<'a>) {
103
+ let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
104
+
105
+ for block in hir.blocks.values_mut() {
106
+ if let TerminalValue::ForTerminal(terminal) = &mut block.terminal.value {
107
+ if let Some(update) = terminal.update {
108
+ if !block_ids.contains(&update) {
109
+ terminal.update = None;
110
+ }
111
+ }
112
+ }
113
+ }
114
+}
115
+
116
+/// Prunes unreachable fallthrough values, setting them to None if the referenced
117
+/// block was not otherwise reachable.
118
+fn remove_unreachable_fallthroughs<'a>(hir: &mut HIR<'a>) {
119
+ let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
120
+
121
+ for block in hir.blocks.values_mut() {
122
+ block
123
+ .terminal
124
+ .value
125
+ .map_optional_fallthroughs(|fallthrough| {
126
+ if block_ids.contains(&fallthrough) {
127
+ Some(fallthrough)
128
+ } else {
129
+ None
130
+ }
131
+ })
132
+ }
133
+}
134
+
135
+/// Rewrites DoWhile statements into Gotos if the test block is not reachable
136
+fn remove_unreachable_do_while_statements<'a>(hir: &mut HIR<'a>) {
137
+ let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
138
+
139
+ for block in hir.blocks.values_mut() {
140
+ if let TerminalValue::DoWhileTerminal(terminal) = &mut block.terminal.value {
141
+ if !block_ids.contains(&terminal.test) {
142
+ block.terminal.value = TerminalValue::GotoTerminal(hir::GotoTerminal {
143
+ block: terminal.body,
144
+ kind: GotoKind::Break,
145
+ });
146
+ }
147
+ }
148
+ }
149
+}
150
+
151
+/// Updates the instruction ids for all instructions and blocks
152
+/// Relies on the blocks being in reverse postorder to ensure that id ordering is correct
153
+fn mark_instruction_ids<'a>(hir: &mut HIR<'a>) -> Result<(), Diagnostic> {
154
+ let mut id_gen = InstructionIdGenerator::new();
155
+ let mut visited = HashSet::<(usize, usize)>::new();
156
+ for (block_ix, block) in hir.blocks.values_mut().enumerate() {
157
+ for (instr_ix, instr) in block.instructions.iter_mut().enumerate() {
158
+ invariant(!visited.insert((block_ix, instr_ix)), || ())?;
159
+ instr.id = id_gen.next();
160
+ }
161
+ block.terminal.id = id_gen.next();
162
+ }
163
+ Ok(())
164
+}
165
+
166
+/// Updates the predecessors of each block
167
+fn mark_predecessors<'a>(hir: &mut HIR<'a>) {
168
+ for block in hir.blocks.values_mut() {
169
+ block.predecessors.clear();
170
+ }
171
+ let mut visited = HashSet::<BlockId>::with_capacity(hir.blocks.len());
172
+ fn visit<'a>(
173
+ block_id: BlockId,
174
+ prev_id: Option<BlockId>,
175
+ hir: &mut HIR<'a>,
176
+ visited: &mut HashSet<BlockId>,
177
+ ) {
178
+ let block = hir.block_mut(block_id);
179
+ if let Some(prev_id) = prev_id {
180
+ block.predecessors.insert(prev_id);
181
+ }
182
+ if !visited.insert(block_id) {
183
+ return;
184
+ }
185
+ for successor in block.terminal.value.successors() {
186
+ visit(successor, Some(block_id), hir, visited)
187
+ }
188
+ }
189
+ visit(hir.entry, None, hir, &mut visited);
190
+}
191
+
192
+fn invariant<F>(cond: bool, _f: F) -> Result<(), Diagnostic>
193
+where
194
+ F: FnOnce() -> Diagnostic,
195
+{
196
+ if !cond {
197
+ panic!("Oops invariant failed");
198
+ }
199
+ Ok(())
200
+}
201
+
202
+type Diagnostic = ();