/* 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 cfg.cc /// @brief CFG structure analysis and simplification passes. /// /// See docs/cfg.md for architectural notes. #include "rcc/all.h" #include namespace rcc { // Block construction Blk* newblk(Ctx* ctx) { auto* b = static_cast(alloc(ctx, sizeof(Blk))); memset(b, 0, sizeof *b); b->ins = static_cast(vnew(ctx, 0, sizeof(Ins), PFn)); b->pred = static_cast(vnew(ctx, 0, sizeof(Blk*), PFn)); return b; } // Reverse Post-Order (RPO) numbering + dead block elimination namespace { void post_visit(Blk* b, uint* counter) { if (!b || b->visit) return; b->visit = 1; // Heuristic: recurse outer branch first. Blk* a = b->s1; Blk* c = b->s2; if (a && c && a->loop > c->loop) std::swap(a, c); post_visit(a, counter); post_visit(c, counter); b->id = (*counter)++; } void fillrpo(Ctx* ctx, Fn* fn) { for (Blk* b = fn->start; b; b = b->link) b->visit = 0; uint cnt = 0; post_visit(fn->start, &cnt); fn->nblk = cnt; vensure(ctx, &fn->rpo, fn->nblk, sizeof(Blk*), PFn); // Second pass: remove unvisited, invert post-order. Blk** slot = &fn->start; while (Blk* b = *slot) { if (!b->visit) { b->id = ~0u; *slot = b->link; } else { b->id = fn->nblk - 1 - b->id; fn->rpo[b->id] = b; slot = &b->link; } } } } // namespace // Predecessor sets void fillpreds(Ctx* ctx, Fn* fn) { for (Blk* b = fn->start; b; b = b->link) b->npred = 0; for (Blk* src = fn->start; src; src = src->link) { if (src->s1) src->s1->npred++; if (src->s2 && src->s2 != src->s1) src->s2->npred++; } for (Blk* b = fn->start; b; b = b->link) { if (b->npred) vensure(ctx, &b->pred, b->npred, sizeof(Blk*), PFn); b->npred = 0; } for (Blk* src = fn->start; src; src = src->link) { if (src->s1) src->s1->pred[src->s1->npred++] = src; if (src->s2 && src->s2 != src->s1) src->s2->pred[src->s2->npred++] = src; } } // phi cleanup. namespace { void fixphis(Fn* fn) { for (Blk* b = fn->start; b; b = b->link) { for (Phi* p = b->phi; p; p = p->link) { uint keep = 0; for (uint i = 0; i < p->narg; i++) { Blk* src = p->blk[i]; if (src->id == ~0u) continue; if (src->s1 != b && src->s2 != b) continue; if (keep != i) { p->blk[keep] = src; p->arg[keep] = p->arg[i]; } keep++; } assert(keep > 0 && "phi has no live incoming edge"); p->narg = keep; } } } } // namespace void fillcfg(Ctx* ctx, Fn* fn) { fillrpo(ctx, fn); fillpreds(ctx, fn); fixphis(fn); } // Dominator Tree (Cooper-Harvey-Kennedy) namespace { /// Walk up idom chain to find the lowest common ancestor of x and y in dom-tree. /// Invariant: Larger RPO id means a deeper node in the tree, move the deeper one up by one step /// each time. Blk* dom_meet(Blk* x, Blk* y) { if (!x) return y; while (x != y) { if (x->id < y->id) std::swap(x, y); x = x->idom; assert(x); } return x; } } // namespace void filldom(Fn* fn) { for (Blk* b = fn->start; b; b = b->link) { b->idom = nullptr; b->dom = nullptr; b->dlink = nullptr; } // Fixpoint iteration. for (bool changed = true; changed;) { changed = false; for (uint i = 1; i < fn->nblk; i++) { Blk* b = fn->rpo[i]; Blk* d = nullptr; for (uint k = 0; k < b->npred; k++) { Blk* p = b->pred[k]; if (p->idom || p == fn->start) d = dom_meet(d, p); } if (d != b->idom) { b->idom = d; changed = true; } } } // Reverse chain: attach dom-children. for (Blk* b = fn->start; b; b = b->link) { if (Blk* d = b->idom) { assert(d != b); b->dlink = d->dom; d->dom = b; } } } int sdom(Blk* a, Blk* b) { assert(a && b); if (a == b) return 0; while (b->id > a->id) b = b->idom; return a == b; } int dom(Blk* a, Blk* b) { return a == b || sdom(a, b); } // Dominance Frontier namespace { void push_fron(Ctx* ctx, Blk* host, Blk* node) { for (uint i = 0; i < host->nfron; i++) if (host->fron[i] == node) return; vensure(ctx, &host->fron, host->nfron + 1, sizeof(Blk*), PFn); host->fron[host->nfron++] = node; } /// For edge (src -> dst): walk up idom chain from src, collect dst into the dominance frontier of /// all ancestors that do not strictly dominate dst. void contribute_fron(Ctx* ctx, Blk* src, Blk* dst) { for (Blk* a = src; a && !sdom(a, dst); a = a->idom) push_fron(ctx, a, dst); } } // namespace void fillfron(Ctx* ctx, Fn* fn) { for (Blk* b = fn->start; b; b = b->link) b->nfron = 0; for (Blk* b = fn->start; b; b = b->link) { if (b->s1) contribute_fron(ctx, b, b->s1); if (b->s2) contribute_fron(ctx, b, b->s2); } } // Natural loop identification / Loop weights namespace { void mark_loop_body(Blk* hd, Blk* node, void (*cb)(Blk*, Blk*)) { if (node->id < hd->id || node->visit == hd->id) return; node->visit = hd->id; cb(hd, node); for (uint k = 0; k < node->npred; k++) mark_loop_body(hd, node->pred[k], cb); } void loop_x10(Blk* hd, Blk* b) { (void)hd; b->loop *= 10; } } // namespace void loopiter(Fn* fn, void (*cb)(Blk*, Blk*)) { for (Blk* b = fn->start; b; b = b->link) b->visit = ~0u; for (uint i = 0; i < fn->nblk; i++) { Blk* hd = fn->rpo[i]; for (uint k = 0; k < hd->npred; k++) if (hd->pred[k]->id >= i) mark_loop_body(hd, hd->pred[k], cb); } } void fillloop(Fn* fn) { for (Blk* b = fn->start; b; b = b->link) b->loop = 1; loopiter(fn, loop_x10); } // dom-tree depth + lowest common ancestor void filldepth(Fn* fn) { for (Blk* b = fn->start; b; b = b->link) b->depth = -1; fn->start->depth = 0; // Walk up idom chain. for (Blk* b = fn->start; b; b = b->link) { if (b->depth != -1) continue; Blk* d = b->idom; int steps = 1; while (d->depth == -1) { d = d->idom; steps++; } steps += d->depth; b->depth = steps; for (Blk* a = b->idom; a->depth == -1; a = a->idom) a->depth = --steps; } } Blk* lca(Blk* a, Blk* b) { if (!a) return b; if (!b) return a; while (a->depth > b->depth) a = a->idom; while (b->depth > a->depth) b = b->idom; while (a != b) { a = a->idom; b = b->idom; } return a; } // simpljmp: fold empty transit blocks. namespace { /// Union-find query with path compression: find root along uf[], redirecting each node on the chain /// to root. Blk* uf_find(Blk** uf, Blk* x) { Blk* root = x; while (uf[root->id]) root = uf[root->id]; while (uf[x->id] && uf[x->id] != root) { Blk* nxt = uf[x->id]; uf[x->id] = root; x = nxt; } return root; } } // namespace void simpljmp(Ctx* ctx, Fn* fn) { Blk* exit_blk = newblk(ctx); exit_blk->id = fn->nblk++; exit_blk->jmp.type = Jret0; exit_blk->name = strf(ctx, PFn, "ret"); Blk** uf = static_cast(emalloc(fn->nblk * sizeof(Blk*))); memset(uf, 0, fn->nblk * sizeof(Blk*)); for (Blk* b = fn->start; b; b = b->link) { assert(!b->phi && "simpljmp requires phi-free CFG"); if (b->jmp.type == Jret0) { b->jmp.type = Jjmp; b->s1 = exit_blk; } if (b->nins == 0 && b->jmp.type == Jjmp) { Blk* r = uf_find(uf, b->s1); if (r != b) uf[b->id] = r; } } Blk** tail = &fn->start; for (Blk* b; (b = *tail); tail = &b->link) { if (b->s1) b->s1 = uf_find(uf, b->s1); if (b->s2) b->s2 = uf_find(uf, b->s2); if (b->s1 && b->s1 == b->s2) { b->jmp.type = Jjmp; b->s2 = nullptr; } } *tail = exit_blk; free(uf); } // Reachability / if-graph pattern recognition namespace { int dfs_can_reach(Blk* from, Blk* to) { if (from == to) return 1; if (!from || from->visit) return 0; from->visit = 1; if (dfs_can_reach(from->s1, to)) return 1; if (dfs_can_reach(from->s2, to)) return 1; return 0; } } // namespace int reaches(Fn* fn, Blk* from, Blk* to) { assert(to); int r = dfs_can_reach(from, to); for (Blk* b = fn->start; b; b = b->link) b->visit = 0; return r; } int reachesnotvia(Fn* fn, Blk* from, Blk* to, Blk* excl) { excl->visit = 1; return reaches(fn, from, to); } int ifgraph(Blk* head, Blk** pthen, Blk** pelse, Blk** pjoin) { if (head->jmp.type != Jjnz) return 0; Blk* lo = head->s1; Blk* hi = head->s2; bool swapped = false; if (lo->id > hi->id) { std::swap(lo, hi); swapped = true; } if (lo == hi) return 0; if (lo->jmp.type != Jjmp || lo->npred != 1) return 0; Blk** out_then = swapped ? pelse : pthen; Blk** out_else = swapped ? pthen : pelse; if (lo->s1 == hi) { // if-then (no else branch: head directly serves as else) if (hi->npred != 2) return 0; *out_then = lo; *out_else = head; *pjoin = hi; return 1; } if (hi->jmp.type != Jjmp || hi->npred != 1) return 0; if (lo->s1 != hi->s1 || lo->s1->npred != 2) return 0; assert(lo->s1 != head); *out_then = lo; *out_else = hi; *pjoin = lo->s1; return 1; } // simplcfg namespace { struct JmpSnap { short type; Ref arg; Blk* s1; Blk* s2; }; bool snap_eq(const JmpSnap& a, const JmpSnap& b) { return a.type == b.type && req(a.arg, b.arg) && a.s1 == b.s1 && a.s2 == b.s2; } bool target_phi_free(const JmpSnap& j) { if (j.s1 && j.s1->phi) return false; if (j.s2 && j.s2->phi) return false; return true; } bool block_is_empty(Blk* b) { if (b->phi) return false; for (Ins* i = b->ins; i < &b->ins[b->nins]; i++) if (i->op != Onop && i->op != Odbgloc) return false; return true; } void phi_to_copies(Ctx* ctx, Blk* b) { assert(b->npred == 1); Blk* p = b->pred[0]; Ins copy{}; copy.op = Ocopy; for (Phi* phi = b->phi; phi; phi = phi->link) { copy.cls = static_cast(phi->cls); copy.to = phi->to; copy.arg[0] = phiarg(phi, p); copy.arg[1] = Ref::none(); addins(ctx, &p->ins, &p->nins, ©); } b->phi = nullptr; } } // namespace void simplcfg(Ctx* ctx, Fn* fn) { if (debug['C']) { fprintf(ctx->dbg_buf, "\n> Before CFG simplification:\n"); printfn(fn, ctx->dbg_buf); } for (Blk* b = fn->start; b; b = b->link) if (b->npred == 1) phi_to_copies(ctx, b); auto* snap = static_cast(emalloc(fn->nblk * sizeof(JmpSnap))); auto* empty = static_cast(emalloc(fn->nblk * sizeof(bool))); for (Blk* b = fn->start; b; b = b->link) { snap[b->id] = {b->jmp.type, b->jmp.arg, b->s1, b->s2}; empty[b->id] = block_is_empty(b); } bool dirty; do { dirty = false; for (Blk* b = fn->start; b; b = b->link) { if (b->id == ~0u) continue; JmpSnap* j = &snap[b->id]; if (j->type == Jjmp && j->s1->npred == 1) { Blk* s = j->s1; assert(!s->phi); addbins(ctx, &b->ins, &b->nins, s); empty[b->id] = empty[b->id] && empty[s->id]; JmpSnap* sj = &snap[s->id]; Blk* succs[2] = {sj->s1, sj->s2}; for (Blk* sb : succs) { if (!sb) continue; for (Phi* p = sb->phi; p; p = p->link) { uint n = phiargn(p, s); if (n != ~0u) p->blk[n] = b; } } s->id = ~0u; *j = *sj; dirty = true; } else if (j->type == Jjnz && empty[j->s1->id] && empty[j->s2->id] && snap_eq(snap[j->s1->id], snap[j->s2->id]) && target_phi_free(snap[j->s1->id])) { *j = snap[j->s1->id]; dirty = true; } } } while (dirty); for (Blk* b = fn->start; b; b = b->link) { if (b->id == ~0u) continue; JmpSnap* j = &snap[b->id]; b->jmp.type = j->type; b->jmp.arg = j->arg; b->s1 = j->s1; b->s2 = j->s2; assert(!j->s1 || j->s1->id != ~0u); assert(!j->s2 || j->s2->id != ~0u); } fillcfg(ctx, fn); free(empty); free(snap); if (debug['C']) { fprintf(ctx->dbg_buf, "\n> After CFG simplification:\n"); printfn(fn, ctx->dbg_buf); } } } // namespace rcc