/* Source code for RCC-Legacy. Copyright (C) 2026 Richard Qin This file is part of RCC. RCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. RCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RCC; see the file LICENSE. If not see . */ /// @file licm.cc /// @brief Phase A Loop Invariant Code Motion (LICM). /// /// See docs/licm.md for architectural notes. #include "rcc/all.h" #include namespace rcc { namespace { struct LoopInfo { Blk* header; std::vector body; // includes header Blk* preheader; // nullptr if no unique non-back-edge predecessor }; Ins make_nop() { Ins i = {}; i.op = Onop; return i; } // Loop Identification. void build_loops(Fn* fn, std::vector& loops, std::vector& blk_loop) { loops.clear(); blk_loop.assign(static_cast(fn->nblk), -1); for (Blk* b = fn->start; b; b = b->link) b->visit = ~0u; auto refine_inner = [&](uint bid, int lidx) { int cur = blk_loop[bid]; if (cur < 0 || loops[static_cast(cur)].header->id < loops[static_cast(lidx)].header->id) blk_loop[bid] = lidx; }; for (uint i = 0; i < fn->nblk; i++) { Blk* hd = fn->rpo[i]; bool has_back = false; for (uint k = 0; k < hd->npred; k++) { if (hd->pred[k]->id >= i) { has_back = true; break; } } if (!has_back) continue; int lidx = static_cast(loops.size()); loops.push_back({hd, {hd}, nullptr}); refine_inner(hd->id, lidx); // DFS body collect. std::vector stack; for (uint k = 0; k < hd->npred; k++) if (hd->pred[k]->id >= i) stack.push_back(hd->pred[k]); while (!stack.empty()) { Blk* n = stack.back(); stack.pop_back(); if (n->id < hd->id || n->visit == hd->id) continue; n->visit = hd->id; loops[static_cast(lidx)].body.push_back(n); refine_inner(n->id, lidx); for (uint k = 0; k < n->npred; k++) stack.push_back(n->pred[k]); } } // Find preheader. for (auto& L : loops) { Blk* phdr = nullptr; bool unique = true; for (uint k = 0; k < L.header->npred; k++) { Blk* p = L.header->pred[k]; bool in_body = false; for (Blk* b : L.body) if (b == p) { in_body = true; break; } if (in_body) continue; // back-edge if (phdr) { unique = false; break; } phdr = p; } L.preheader = unique ? phdr : nullptr; } } // Invariance bool blk_in(Blk* x, const std::vector& body) { for (Blk* b : body) if (b == x) return true; return false; } bool is_invariant(Fn* fn, Ref r, const LoopInfo& L) { if (rtype(r) == RCon) return true; if (rtype(r) != RTmp) return false; Tmp* t = &fn->tmp[r.val]; if (t->bid == ~0u) return true; // Undefined (treat as param/global-like) - equivalent to "outside loop" return !blk_in(fn->rpo[t->bid], L.body); } // Check safety. bool clobbered_in_loop(Fn* fn, Ins* load_ins, Ref addr, int sz, const LoopInfo& L) { for (Blk* b : L.body) { for (Ins* i = b->ins; i < &b->ins[b->nins]; i++) { if (i == load_ins) continue; if (i->op == Ocall) return true; // Conservative: call is treated as writing all escapable memory if (isstore(i->op)) { int ssz = storesz(i); int delta; if (alias(addr, 0, sz, i->arg[1], ssz, &delta, fn) != NoAlias) return true; } } } return false; } } // namespace void licm(Ctx* ctx, Fn* fn) { fillloop(fn); // Phase A hoisted load. int total_hoisted = 0; int iter = 0; for (; iter < 16; iter++) { std::vector loops; std::vector blk_loop; build_loops(fn, loops, blk_loop); if (loops.empty()) break; int round_hoisted = 0; for (Blk* b = fn->start; b; b = b->link) { if (b->id >= blk_loop.size()) continue; int lidx = blk_loop[b->id]; if (lidx < 0) continue; LoopInfo& L = loops[static_cast(lidx)]; if (!L.preheader) continue; for (Ins* i = b->ins; i < &b->ins[b->nins]; i++) { if (!isload(i->op)) continue; if (!is_invariant(fn, i->arg[0], L)) continue; int sz = loadsz(i); if (clobbered_in_loop(fn, i, i->arg[0], sz, L)) continue; // Hit: move to preheader. addins(ctx, &L.preheader->ins, &L.preheader->nins, i); *i = make_nop(); round_hoisted++; } } total_hoisted += round_hoisted; if (round_hoisted == 0) break; // Reset loop state. filluse(ctx, fn); } if (debug['L']) { FILE* out = ctx->dbg_buf ? ctx->dbg_buf : stderr; fprintf(out, "\n> LICM hoisted %d load(s) in %s (in %d round%s)\n", total_hoisted, fn->name, iter, iter == 1 ? "" : "s"); } } } // namespace rcc