Skip to content

Commit 7e9b0c5

Browse files
committed
LICM: port to pure C, wire as sole path (-200 lines C++)
New licm_c.c (260 lines pure C) replaces C++ LICM implementation. Uses phx_dom_create for dominator analysis, hir_cfg_get_rpo_c for RPO, bit-array block set for loop body tracking. licm.cpp reduced to 15-line bridge (LICM::Run delegates to hir_licm_run).
1 parent eb27753 commit 7e9b0c5

File tree

2 files changed

+290
-202
lines changed

2 files changed

+290
-202
lines changed

Python/jit/hir/licm.cpp

Lines changed: 1 addition & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -3,213 +3,12 @@
33
#include "cinderx/Jit/hir/licm_c.h"
44
#include "cinderx/Jit/hir/licm.h"
55

6-
#include "cinderx/Jit/hir/analysis.h"
76
#include "cinderx/Jit/hir/hir.h"
87

9-
#include <queue>
10-
#include <unordered_set>
11-
#include <vector>
12-
138
namespace jit::hir {
149

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-
19110
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));
20812
}
20913

21014
} // 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

Comments
 (0)