1
+use std::collections::HashMap;
2
+
3
+use forget_diagnostics::{invariant, Diagnostic};
4
+use thiserror::Error;
5
+
6
+use crate::{
7
+ mark_instruction_ids, mark_predecessors, BasicBlock, BlockId, BlockKind, BlockRewriter,
8
+ BlockRewriterAction, Environment, Function, IdentifierOperand, InstrIx, Instruction,
9
+ InstructionKind, InstructionValue, LValue, LoadLocal, Operand, StoreLocal, TerminalValue,
10
+};
11
+
12
+/// Merges sequences of blocks that will always execute consecutively —
13
+/// ie where the predecessor always transfers control to the successor
14
+/// (ends in a goto) and where the predecessor is the only predecessor
15
+/// for that successor (ie, there is no other way to reach the successor).
16
+///
17
+/// Note that this pass leaves value/loop blocks alone because they cannot
18
+/// be merged without breaking the structure of the high-level terminals
19
+/// that reference them.
20
+pub fn merge_consecutive_blocks<'a>(
21
+ env: &Environment<'a>,
22
+ fun: &mut Function<'a>,
23
+) -> Result<(), Diagnostic> {
24
+ let mut merged = MergedBlocks::default();
25
+ let blocks = &mut fun.body.blocks;
26
+ let instructions = &mut fun.body.instructions;
27
+ let mut rewriter = BlockRewriter::new(blocks, fun.body.entry);
28
+ let mut has_changes = false;
29
+
30
+ rewriter.each_block(|mut block, rewriter| {
31
+ let block_id = block.id;
32
+ // Visit instructions to merge blocks within function expressions
33
+ for instr_ix in &block.instructions {
34
+ let instr = &mut instructions[usize::from(*instr_ix)];
35
+ if let InstructionValue::Function(fun) = &mut instr.value {
36
+ merge_consecutive_blocks(env, &mut fun.lowered_function)?;
37
+ }
38
+ }
39
+
40
+ // Can't merge value blocks and can't merge blocks with multiple
41
+ // predecessors
42
+ if block.kind != BlockKind::Block || block.predecessors.len() != 1 {
43
+ return Ok(BlockRewriterAction::Keep(block));
44
+ }
45
+
46
+ let original_predecessor_id = block.predecessors.first().unwrap(); // length checked above
47
+ let predecessor_id = merged.get(*original_predecessor_id);
48
+ let predecessor = rewriter.block_mut(predecessor_id);
49
+ if predecessor.kind != BlockKind::Block
50
+ || !matches!(predecessor.terminal.value, TerminalValue::Goto(_))
51
+ {
52
+ // Can't merge value blocks, and we can't merge if the predecessor
53
+ // has multiple successors (and isn't guaranteed to transfer here)
54
+ return Ok(BlockRewriterAction::Keep(block));
55
+ }
56
+
57
+ // Replace phis in the merged block with canonical assignments to the single
58
+ // operand value
59
+ for phi in block.phis.iter_mut() {
60
+ invariant(phi.operands.len() == 1, || {
61
+ Diagnostic::invariant(ExpectedSingleOperandPhis { block: block_id }, None)
62
+ })?;
63
+ let (_, operand) = phi.operands.first().unwrap();
64
+ // load the operand
65
+ let load = Instruction {
66
+ id: predecessor.terminal.id,
67
+ value: InstructionValue::LoadLocal(LoadLocal {
68
+ place: IdentifierOperand {
69
+ effect: None,
70
+ identifier: operand.clone(),
71
+ },
72
+ }),
73
+ };
74
+ let load_ix = InstrIx::new(instructions.len() as u32);
75
+ instructions.push(load);
76
+ predecessor.instructions.push(load_ix);
77
+ // store it into the phi id
78
+ let store = Instruction {
79
+ id: predecessor.terminal.id,
80
+ value: InstructionValue::StoreLocal(StoreLocal {
81
+ lvalue: LValue {
82
+ kind: InstructionKind::Reassign,
83
+ identifier: IdentifierOperand {
84
+ identifier: phi.identifier.clone(),
85
+ effect: None,
86
+ },
87
+ },
88
+ value: Operand {
89
+ effect: None,
90
+ ix: load_ix,
91
+ },
92
+ }),
93
+ };
94
+ let store_ix = InstrIx::new(instructions.len() as u32);
95
+ instructions.push(store);
96
+ predecessor.instructions.push(store_ix);
97
+ }
98
+ let BasicBlock {
99
+ instructions,
100
+ terminal,
101
+ ..
102
+ } = *block;
103
+ predecessor.instructions.extend(instructions);
104
+ predecessor.terminal = terminal;
105
+ merged.merge(block_id, predecessor_id);
106
+
107
+ has_changes = true;
108
+ Ok(BlockRewriterAction::Remove)
109
+ })?;
110
+
111
+ if has_changes {
112
+ mark_instruction_ids(&mut fun.body)?;
113
+ mark_predecessors(&mut fun.body);
114
+ }
115
+
116
+ Ok(())
117
+}
118
+
119
+#[derive(Default)]
120
+struct MergedBlocks {
121
+ merged: HashMap<BlockId, BlockId>,
122
+}
123
+
124
+impl MergedBlocks {
125
+ fn merge(&mut self, block: BlockId, into: BlockId) {
126
+ let target = self.get(into);
127
+ self.merged.insert(block, target);
128
+ }
129
+
130
+ fn get(&self, block: BlockId) -> BlockId {
131
+ let mut current = block;
132
+ while let Some(mapped) = self.merged.get(¤t) {
133
+ current = *mapped;
134
+ }
135
+ current
136
+ }
137
+}
138
+
139
+#[derive(Debug, Error)]
140
+#[error("Expected predecessor {predecessor} to exist")]
141
+pub struct ExpectedPredecessorToExist {
142
+ predecessor: BlockId,
143
+}
144
+
145
+#[derive(Debug, Error)]
146
+#[error(
147
+ "Expected block {block} with single predecessor to have no phis or
148
+ phis with a single operand, found multiple operands"
149
+)]
150
+pub struct ExpectedSingleOperandPhis {
151
+ block: BlockId,
152
+}