/* 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 dead_loop.cc /// @brief Dead Loop Elimination: removal of entire loops that have no side-effects, are provably /// terminating, and have no live-outs. /// /// See docs/dead_loop.md for architectural notes. #include "rcc/all.h" #include "rcc/loopinfo.h" #include #include namespace rcc { namespace { bool has_side_effect_op(uint op) { if (isstore(op)) return true; if (isalloc(op)) return true; if (ispar(op)) return true; if (isarg(op)) return true; if (op == Ocall) return true; if (op == Oblit0 || op == Oblit1) return true; if (op == Ovastart || op == Ovaarg) return true; if (op == Odbgloc) return true; if (op == Osel0 || op == Osel1) return true; return false; } bool defined_outside(Fn* fn, const Loop& L, Ref r) { if (rtype(r) == RCon) return true; if (rtype(r) != RTmp) return false; if (r.val >= static_cast(fn->ntmp)) return false; uint bid = fn->tmp[r.val].bid; return L.body_bid.find(bid) == L.body_bid.end(); } Ins* find_cmp_in(Blk* b, Ref cond) { for (uint i = 0; i < b->nins; i++) if (req(b->ins[i].to, cond)) return &b->ins[i]; return nullptr; } int find_biv_idx(const std::vector& bivs, Ref r) { for (size_t k = 0; k < bivs.size(); k++) if (req(bivs[k].to, r)) return static_cast(k); return -1; } void collect_body_defs(const Loop& L, std::vector& defs) { for (Blk* b : L.body) { for (Phi* p = b->phi; p; p = p->link) if (rtype(p->to) == RTmp) defs.push_back(p->to); for (uint k = 0; k < b->nins; k++) if (rtype(b->ins[k].to) == RTmp) defs.push_back(b->ins[k].to); } } bool all_uses_inside(Fn* fn, const Loop& L, Ref tmp_ref) { if (rtype(tmp_ref) != RTmp) return true; if (tmp_ref.val >= static_cast(fn->ntmp)) return true; Tmp* t = &fn->tmp[tmp_ref.val]; for (uint u = 0; u < t->nuse; u++) { const Use& uu = t->use[u]; if (L.body_bid.find(uu.bid) == L.body_bid.end()) return false; } return true; } bool delete_loop_if_dead(Fn* fn, const Loop& L) { // Topology: unique exit_block + unique exiting_block Blk* exit_block = nullptr; Blk* exiting_block = nullptr; for (Blk* b : L.body) { Blk* succs[2] = {b->s1, b->s2}; for (Blk* s : succs) { if (!s) continue; if (L.body_bid.find(s->id) != L.body_bid.end()) continue; // Internal edge // Exit loop edge if (!exit_block) { exit_block = s; exiting_block = b; } else if (exit_block != s || exiting_block != b) { return false; // Multiple exits or multiple exiting blocks } } } if (!exit_block) return false; // No exit -- infinite loop // Termination: at least one constant BIV + header Jjnz cmp(BIV, invariant) std::vector bivs; detect_bivs(fn, L, bivs); if (bivs.empty()) return false; if (L.header->jmp.type != Jjnz) return false; Ref cond = L.header->jmp.arg; if (rtype(cond) != RTmp) return false; Ins* cmp = find_cmp_in(L.header, cond); if (!cmp) return false; int bi = find_biv_idx(bivs, cmp->arg[0]); Ref other = cmp->arg[1]; if (bi < 0) { bi = find_biv_idx(bivs, cmp->arg[1]); other = cmp->arg[0]; } if (bi < 0) return false; if (!defined_outside(fn, L, other)) return false; if (exiting_block != L.header) return false; // Side-effect scan for (Blk* b : L.body) { for (uint k = 0; k < b->nins; k++) { if (has_side_effect_op(b->ins[k].op)) return false; } } // Live-out scan for (Phi* p = exit_block->phi; p; p = p->link) { for (uint k = 0; k < p->narg; k++) { if (p->blk[k] != exiting_block) continue; if (!defined_outside(fn, L, p->arg[k])) return false; } } std::vector defs; collect_body_defs(L, defs); for (Ref r : defs) { if (!all_uses_inside(fn, L, r)) return false; } for (Phi* p = exit_block->phi; p; p = p->link) { for (uint k = 0; k < p->narg; k++) if (p->blk[k] == L.preheader) return false; } // Rewrite // preheader's jmp outgoing edge: the one pointing to header is changed to point to exit_block; if (L.preheader->s1 == L.header) L.preheader->s1 = exit_block; if (L.preheader->s2 == L.header) L.preheader->s2 = exit_block; for (Phi* p = exit_block->phi; p; p = p->link) { for (uint k = 0; k < p->narg; k++) if (p->blk[k] == exiting_block) p->blk[k] = L.preheader; } return true; } } // namespace void dead_loop(Ctx* ctx, Fn* fn) { fillpreds(ctx, fn); fillcfg(ctx, fn); filluse(ctx, fn); std::vector loops; find_loops(fn, loops); if (loops.empty()) return; int deleted = 0; for (const Loop& L : loops) { if (delete_loop_if_dead(fn, L)) deleted++; } if (debug['L']) { FILE* out = ctx->dbg_buf ? ctx->dbg_buf : stderr; fprintf(out, "\n> dead_loop %s: deleted %d of %zu loop(s)\n", fn->name, deleted, loops.size()); } } } // namespace rcc