/* 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 copy.cc /// @brief copy equivalence check + narrow parameter sinking - for GVN, no independent driver. /// /// See docs/copy.md for architectural notes. #include "rcc/all.h" #include namespace rcc { namespace { struct Ext { char zext; char nopw; char usew; }; bool decode_ext(int op, Ext* e) { switch (op) { case Oextsb: *e = Ext{0, 7, 8}; return true; case Oextub: *e = Ext{1, 8, 8}; return true; case Oextsh: *e = Ext{0, 15, 16}; return true; case Oextuh: *e = Ext{1, 16, 16}; return true; case Oextsw: *e = Ext{0, 31, 32}; return true; case Oextuw: *e = Ext{1, 32, 32}; return true; } return false; } int bitwidth(uint64_t v) { return v ? 64 - __builtin_clzll(v) : 0; } template int walk_phi_memo(Fn* fn, Ref r, int w, Walk walk) { int ret = walk(fn, r, w); for (Blk* b = fn->start; b; b = b->link) for (Phi* p = b->phi; p; p = p->link) p->visit = 0; return ret; } int uwl(Fn* fn, Ref r, int w); bool phi_use_ok(Fn* fn, Phi* p, int w) { if (p->visit || req(p->to, Ref::none())) return true; p->visit = 1; return uwl(fn, p->to, w) != 0; } bool ins_use_ok(Fn* fn, Ins* i, Ref r, int w) { Ext e; if (i->op == Ocopy && uwl(fn, i->to, w)) return true; if (decode_ext(i->op, &e)) { if (e.usew <= w) return true; return uwl(fn, i->to, w) != 0; } if (i->op == Oand) { Ref rc; if (req(r, i->arg[0])) { rc = i->arg[1]; } else { assert(req(r, i->arg[1])); rc = i->arg[0]; } int64_t v; return isconbits(fn, rc, &v) && bitwidth(static_cast(v)) <= w; } return false; } int uwl(Fn* fn, Ref r, int w) { assert(rtype(r) == RTmp); Tmp* t = &fn->tmp[r.val]; for (Use* u = &t->use[0]; u < &t->use[t->nuse]; ++u) { bool ok; switch (u->type) { case Use::UPhi: ok = phi_use_ok(fn, u->u.phi, w); break; case Use::UIns: assert(u->u.ins); ok = ins_use_ok(fn, u->u.ins, r, w); break; default: ok = false; break; } if (!ok) return 0; } return 1; } /* no more than w bits are used */ int usewidthle(Fn* fn, Ref r, int w) { return walk_phi_memo(fn, r, w, uwl); } int dwl(Fn* fn, Ref r, int w) { int64_t v; if (isconbits(fn, r, &v) && bitwidth(static_cast(v)) <= w) return 1; if (w <= 0) return 0; if (rtype(r) != RTmp) return 0; Tmp* t = &fn->tmp[r.val]; if (t->cls != Kw) return 0; if (!t->def) { Phi* p = fn->rpo[t->bid]->phi; for (; p; p = p->link) if (req(p->to, r)) break; assert(p); if (p->visit && p->visit <= w) return 1; p->visit = w; for (uint n = 0; n < p->narg; ++n) if (!dwl(fn, p->arg[n], w)) return 0; return 1; } Ins* i = t->def; if (i->op == Ocopy) return dwl(fn, i->arg[0], w); if (i->op == Oshr || i->op == Osar) { if (isconbits(fn, i->arg[1], &v) && 0 < v && v <= 32) { if (i->op == Oshr && w + v >= 32) return 1; if (w < 32) { int cap = (i->op == Osar) ? 31 : 32; w = std::min(cap, static_cast(w + v)); } } return dwl(fn, i->arg[0], w); } int x; if (iscmp(i->op, &x, &x)) return w >= 1; if (i->op == Oand) return (dwl(fn, i->arg[0], w) || dwl(fn, i->arg[1], w)) ? 1 : 0; if (i->op == Oor || i->op == Oxor) return (dwl(fn, i->arg[0], w) && dwl(fn, i->arg[1], w)) ? 1 : 0; Ext e; if (decode_ext(i->op, &e)) { if (e.zext && e.usew <= w) return 1; w = std::min(w, static_cast(e.nopw)); return dwl(fn, i->arg[0], w); } return 0; } /* is the ref narrower than w bits */ int defwidthle(Fn* fn, Ref r, int w) { return walk_phi_memo(fn, r, w, dwl); } int isw1(Fn* fn, Ref r) { return defwidthle(fn, r, 1); } bool phieq(Phi* pa, Phi* pb) { assert(pa->narg == pb->narg); for (uint n = 0; n < pa->narg; ++n) if (!req(pa->arg[n], phiarg(pb, pa->blk[n]))) return false; return true; } } // namespace Ref copyref(Ctx* ctx, Fn* fn, Blk* b, Ins* i) { static const bits extcpy[] = { /*WFull*/ 0, /*Wsb*/ BIT(Tmp::Wsb) | BIT(Tmp::Wsh) | BIT(Tmp::Wsw), /*Wub*/ BIT(Tmp::Wub) | BIT(Tmp::Wuh) | BIT(Tmp::Wuw), /*Wsh*/ BIT(Tmp::Wsh) | BIT(Tmp::Wsw), /*Wuh*/ BIT(Tmp::Wuh) | BIT(Tmp::Wuw), /*Wsw*/ BIT(Tmp::Wsw), /*Wuw*/ BIT(Tmp::Wuw), }; MAKESURE(extcpy_covers_all_widths, sizeof(extcpy) / sizeof(extcpy[0]) == Tmp::Wuw + 1); if (i->op == Ocopy) return i->arg[0]; const Op& od = optab[i->op]; if (od.hasid && KBASE(i->cls) == 0 && req(i->arg[1], ctx->gvn_con01[od.idval]) && (!od.cmpeqwl || isw1(fn, i->arg[0]))) return i->arg[0]; if (od.idemp && req(i->arg[0], i->arg[1])) return i->arg[0]; if ((od.cmpeqwl || od.cmplgtewl) && req(i->arg[0], i->arg[1])) return ctx->gvn_con01[od.eqval]; int z; if (od.cmpeqwl && req(i->arg[1], Ref::con_z()) && zeroval(fn, b, i->arg[0], argcls(i, 0), &z)) return ctx->gvn_con01[od.eqval ^ z ^ 1]; int64_t v; if (i->op == Oand && isconbits(fn, i->arg[1], &v) && v > 0 && ((v + 1) & v) == 0 && defwidthle(fn, i->arg[0], bitwidth(static_cast(v)))) return i->arg[0]; if (i->cls == Kw && (i->op == Oextsw || i->op == Oextuw)) return i->arg[0]; Ext e; if (decode_ext(i->op, &e) && rtype(i->arg[0]) == RTmp) { Tmp* t = &fn->tmp[i->arg[0].val]; assert(KBASE(t->cls) == 0); if (KWIDE(i->cls) > KWIDE(t->cls)) return Ref::none(); int w = Tmp::Wsb + (static_cast(i->op) - Oextsb); if (BIT(w) & extcpy[t->width]) return i->arg[0]; if ((!t->def || !ispar(t->def->op)) && usewidthle(fn, i->to, e.usew)) return i->arg[0]; if (defwidthle(fn, i->arg[0], e.nopw)) return i->arg[0]; } return Ref::none(); } Ref phicopyref(Ctx* ctx, Fn* fn, Blk* b, Phi* p) { Ref r = Ref::none(); uint n = 0; for (; n < p->narg; ++n) if (!req(p->arg[n], p->to)) { if (req(r, Ref::none())) r = p->arg[n]; else if (!req(p->arg[n], r)) break; } if (n == p->narg) return r; for (Phi* p1 = b->phi; p1 != p; p1 = p1->link) { assert(p1); if (phieq(p1, p)) return p1->to; } Blk* d = b->idom; if (p->narg != 2 || d->jmp.type != Jjnz || !isw1(fn, d->jmp.arg)) return Ref::none(); Blk* s[2] = {nullptr, nullptr}; for (uint k = 0; k < 2; ++k) for (uint c = 0; c < 2; ++c) if (req(p->arg[k], ctx->gvn_con01[c])) s[c] = p->blk[k]; if (d->s1 == s[1] && d->s2 == s[0] && d->s1->jmp.type == Jjmp && d->s2->jmp.type == Jjmp) return d->jmp.arg; return Ref::none(); } void narrowpars(Ctx* ctx, Fn* fn) { bool has_loop = false; for (Blk* b = fn->start; b; b = b->link) if (b->loop > 1) { has_loop = true; break; } if (!has_loop) return; Blk* b = fn->start; uint npar = 0; for (Ins* i = b->ins; i < &b->ins[b->nins]; ++i) { if (!ispar(i->op)) break; ++npar; } if (npar == 0) return; uint nins = b->nins + npar; Ins* ins = static_cast(vnew(ctx, nins, sizeof(Ins), PFn)); icpy(ins, b->ins, npar); icpy(ins + 2 * npar, b->ins + npar, b->nins - npar); b->ins = ins; b->nins = nins; for (Ins* i = b->ins; i < &b->ins[b->nins]; ++i) { if (!ispar(i->op)) break; Ins extins = {}; extins.op = Onop; if (i->cls == Kw && usewidthle(fn, i->to, 16)) { extins.op = usewidthle(fn, i->to, 8) ? Oextub : Oextuh; Ref r = newtmp(ctx, "vw", i->cls, fn); extins.cls = i->cls; extins.to = i->to; extins.arg[0] = r; i->to = r; } *(i + npar) = extins; } } } // namespace rcc