|
3 | 3 | #include "cinderx/Jit/hir/licm_c.h" |
4 | 4 | #include "cinderx/Jit/hir/licm.h" |
5 | 5 |
|
6 | | -#include "cinderx/Jit/hir/analysis.h" |
7 | 6 | #include "cinderx/Jit/hir/hir.h" |
8 | 7 |
|
9 | | -#include <queue> |
10 | | -#include <unordered_set> |
11 | | -#include <vector> |
12 | | - |
13 | 8 | namespace jit::hir { |
14 | 9 |
|
15 | | -namespace { |
16 | | - |
17 | | -// A natural loop identified by a back edge in the CFG. |
18 | | -struct LoopInfo { |
19 | | - BasicBlock* header; // The loop header (target of back edge) |
20 | | - std::unordered_set<BasicBlock*> body; // All blocks in the loop body |
21 | | - BasicBlock* preheader; // Single-entry predecessor of header (may be null) |
22 | | -}; |
23 | | - |
24 | | -// Check if block A dominates block B using the dominator analysis. |
25 | | -bool dominates( |
26 | | - DominatorAnalysis& doms, |
27 | | - const BasicBlock* a, |
28 | | - const BasicBlock* b) { |
29 | | - const BasicBlock* cur = b; |
30 | | - while (cur != nullptr) { |
31 | | - if (cur == a) { |
32 | | - return true; |
33 | | - } |
34 | | - cur = doms.immediateDominator(cur); |
35 | | - } |
36 | | - return false; |
37 | | -} |
38 | | - |
39 | | -// Find all natural loops in the function using back-edge detection. |
40 | | -std::vector<LoopInfo> findLoops( |
41 | | - Function& irfunc, |
42 | | - DominatorAnalysis& doms) { |
43 | | - std::vector<LoopInfo> loops; |
44 | | - auto rpo = irfunc.cfg.GetRPOTraversal(); |
45 | | - |
46 | | - for (BasicBlock* block : rpo) { |
47 | | - for (const Edge* edge : block->out_edges()) { |
48 | | - BasicBlock* target = edge->to(); |
49 | | - // A back edge is an edge where the target dominates the source. |
50 | | - if (target != nullptr && dominates(doms, target, block)) { |
51 | | - // Found back edge: block -> target. Compute natural loop body. |
52 | | - LoopInfo loop; |
53 | | - loop.header = target; |
54 | | - loop.body.insert(target); |
55 | | - loop.preheader = nullptr; |
56 | | - |
57 | | - // BFS backwards from the back-edge source to find all blocks |
58 | | - // that can reach the source without going through the header. |
59 | | - if (block != target) { |
60 | | - std::queue<BasicBlock*> worklist; |
61 | | - loop.body.insert(block); |
62 | | - worklist.push(block); |
63 | | - |
64 | | - while (!worklist.empty()) { |
65 | | - BasicBlock* cur = worklist.front(); |
66 | | - worklist.pop(); |
67 | | - for (const Edge* pred_edge : cur->in_edges()) { |
68 | | - BasicBlock* pred = pred_edge->from(); |
69 | | - if (pred != nullptr && loop.body.find(pred) == loop.body.end()) { |
70 | | - loop.body.insert(pred); |
71 | | - worklist.push(pred); |
72 | | - } |
73 | | - } |
74 | | - } |
75 | | - } |
76 | | - |
77 | | - // Find preheader: a predecessor of the header that is NOT in the |
78 | | - // loop body. If there is exactly one such predecessor, it is the |
79 | | - // preheader. Otherwise, we skip this loop (creating preheaders |
80 | | - // would require CFG modification). |
81 | | - BasicBlock* preheader_candidate = nullptr; |
82 | | - int non_loop_preds = 0; |
83 | | - for (const Edge* pred_edge : target->in_edges()) { |
84 | | - BasicBlock* pred = pred_edge->from(); |
85 | | - if (pred != nullptr && loop.body.find(pred) == loop.body.end()) { |
86 | | - preheader_candidate = pred; |
87 | | - non_loop_preds++; |
88 | | - } |
89 | | - } |
90 | | - if (non_loop_preds == 1) { |
91 | | - loop.preheader = preheader_candidate; |
92 | | - } |
93 | | - |
94 | | - if (loop.preheader != nullptr) { |
95 | | - loops.push_back(std::move(loop)); |
96 | | - } |
97 | | - } |
98 | | - } |
99 | | - } |
100 | | - |
101 | | - return loops; |
102 | | -} |
103 | | - |
104 | | -// Check if an instruction is a guard that can be hoisted. |
105 | | -// Only GuardType and GuardIs are candidates. |
106 | | -bool isHoistableGuard(const Instr& instr) { |
107 | | - return instr.IsGuardType() || instr.IsGuardIs(); |
108 | | -} |
109 | | - |
110 | | -// Check if a register is defined outside the loop. |
111 | | -bool isDefinedOutsideLoop( |
112 | | - Register* reg, |
113 | | - const std::unordered_set<BasicBlock*>& loop_body) { |
114 | | - if (reg == nullptr) { |
115 | | - return true; |
116 | | - } |
117 | | - Instr* def = reg->instr(); |
118 | | - if (def == nullptr) { |
119 | | - return true; // Function argument or constant — always outside loop |
120 | | - } |
121 | | - BasicBlock* def_block = def->block(); |
122 | | - return def_block == nullptr || loop_body.count(def_block) == 0; |
123 | | -} |
124 | | - |
125 | | -// Check if all uses of an instruction are defined outside the loop. |
126 | | -// For DeoptBase instructions (GuardType, GuardIs), this also checks the |
127 | | -// FrameState and live_regs — registers referenced by deoptimisation |
128 | | -// metadata must also be defined outside the loop, otherwise deopt after |
129 | | -// hoisting would reference uninitialised values and segfault. |
130 | | -bool allUsesOutsideLoop( |
131 | | - Instr& instr, |
132 | | - const std::unordered_set<BasicBlock*>& loop_body) { |
133 | | - bool all_outside = true; |
134 | | - instr.visitUses([&](Register*& reg) -> bool { |
135 | | - if (!isDefinedOutsideLoop(reg, loop_body)) { |
136 | | - all_outside = false; |
137 | | - return false; // Stop visiting |
138 | | - } |
139 | | - return true; |
140 | | - }); |
141 | | - return all_outside; |
142 | | -} |
143 | | - |
144 | | -// Hoist loop-invariant guards from a single loop to its preheader. |
145 | | -int hoistInvariantGuards(LoopInfo& loop) { |
146 | | - int hoisted = 0; |
147 | | - |
148 | | - // Collect instructions to hoist (can not modify while iterating). |
149 | | - std::vector<Instr*> to_hoist; |
150 | | - |
151 | | - for (BasicBlock* block : loop.body) { |
152 | | - if (block == loop.preheader) { |
153 | | - continue; // Don't scan the preheader itself |
154 | | - } |
155 | | - for (auto it = block->begin(); it != block->end(); ++it) { |
156 | | - Instr& instr = *it; |
157 | | - if (!isHoistableGuard(instr)) { |
158 | | - continue; |
159 | | - } |
160 | | - if (instr.IsPhi()) { |
161 | | - continue; // Never hoist phi nodes |
162 | | - } |
163 | | - if (allUsesOutsideLoop(instr, loop.body)) { |
164 | | - to_hoist.push_back(&instr); |
165 | | - } |
166 | | - } |
167 | | - } |
168 | | - |
169 | | - // Move each hoistable instruction to the preheader. |
170 | | - // Insert before the terminator (Branch to loop header). |
171 | | - for (Instr* instr : to_hoist) { |
172 | | - // Unlink from current block |
173 | | - BasicBlock* old_block = instr->block(); |
174 | | - instr->unlink(); |
175 | | - |
176 | | - // Insert into preheader before the terminator |
177 | | - auto term_it = loop.preheader->end(); |
178 | | - --term_it; // Point to terminator |
179 | | - loop.preheader->insert(instr, term_it); |
180 | | - |
181 | | - hoisted++; |
182 | | - JIT_LOG("LICM: hoisted {} from bb{} to preheader bb{}", |
183 | | - instr->opname(), old_block->id, loop.preheader->id); |
184 | | - } |
185 | | - |
186 | | - return hoisted; |
187 | | -} |
188 | | - |
189 | | -} // namespace |
190 | | - |
191 | 10 | void LICM::Run(Function& irfunc) { |
192 | | - DominatorAnalysis doms(irfunc); |
193 | | - auto loops = findLoops(irfunc, doms); |
194 | | - |
195 | | - if (loops.empty()) { |
196 | | - return; |
197 | | - } |
198 | | - |
199 | | - int total_hoisted = 0; |
200 | | - for (auto& loop : loops) { |
201 | | - total_hoisted += hoistInvariantGuards(loop); |
202 | | - } |
203 | | - |
204 | | - if (total_hoisted > 0) { |
205 | | - JIT_LOG("LICM: hoisted {} instructions across {} loops in {}", |
206 | | - total_hoisted, loops.size(), irfunc.fullname); |
207 | | - } |
| 11 | + hir_licm_run(static_cast<void*>(&irfunc)); |
208 | 12 | } |
209 | 13 |
|
210 | 14 | } // namespace jit::hir |
211 | | - |
212 | | -extern "C" void hir_licm_run(HirFunction func) { |
213 | | - jit::hir::LICM{}.Run( |
214 | | - *static_cast<jit::hir::Function*>(func)); |
215 | | -} |
0 commit comments