diff options
Diffstat (limited to 'src/mid/gvn.cc')
| -rw-r--r-- | src/mid/gvn.cc | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/src/mid/gvn.cc b/src/mid/gvn.cc new file mode 100644 index 0000000..8e029da --- /dev/null +++ b/src/mid/gvn.cc @@ -0,0 +1,414 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file gvn.cc +/// @brief Global Value Numbering (GVN) - Cross-block deduplication + jnz simplification + dead +/// block rescue. +/// +/// See docs/gvn.md for Global Value Numbering architectural notes. + +#include "rcc/all.h" + +namespace rcc { + +namespace { + +// Instruction hashing. + +inline uint mix(uint x0, uint x1) { + return x0 + 31 * x1; +} + +inline uint rhash(Ref r) { + return mix(r.type, r.val); +} + +uint ihash(Ins* i) { + uint h = mix(i->op, i->cls); + h = mix(h, rhash(i->arg[0])); + h = mix(h, rhash(i->arg[1])); + return h; +} + +bool ieq(Ins* a, Ins* b) { + return a->op == b->op && a->cls == b->cls && req(a->arg[0], b->arg[0]) && + req(a->arg[1], b->arg[1]); +} + +// Lookup instruction. +Ins* gvndup(Ctx* ctx, Ins* i, int insert) { + uint idx = ihash(i) % ctx->gvn_tbln; + for (Ins* ii; (ii = ctx->gvn_tbl[idx]);) { + if (ieq(i, ii)) + return ii; + if (++idx >= ctx->gvn_tbln) + idx = 0; + } + if (insert) + ctx->gvn_tbl[idx] = i; + return nullptr; +} + +// Use replacement. + +void replaceuse(Ctx* ctx, Fn* fn, Use* u, Ref r1, Ref r2) { + Tmp* t2 = (rtype(r2) == RTmp) ? &fn->tmp[r2.val] : nullptr; + Blk* b = fn->rpo[u->bid]; + switch (u->type) { + case Use::UPhi: { + Phi* p = u->u.phi; + for (Ref* pr = p->arg; pr < &p->arg[p->narg]; ++pr) + if (req(*pr, r1)) + *pr = r2; + if (t2) + adduse_phi(ctx, t2, b, p); + break; + } + case Use::UIns: { + Ins* i = u->u.ins; + for (int n = 0; n < 2; ++n) + if (req(i->arg[n], r1)) + i->arg[n] = r2; + if (t2) + adduse_ins(ctx, t2, b, i); + break; + } + case Use::UJmp: + if (req(b->jmp.arg, r1)) + b->jmp.arg = r2; + if (t2) + adduse_jmp(ctx, t2, b); + break; + case Use::UXXX: + die("unreachable"); + } +} + +void replaceuses(Ctx* ctx, Fn* fn, Ref r1, Ref r2) { + assert(rtype(r1) == RTmp); + Tmp* t1 = &fn->tmp[r1.val]; + for (Use* u = t1->use; u < &t1->use[t1->nuse]; ++u) + replaceuse(ctx, fn, u, r1, r2); + t1->nuse = 0; +} + +// Nullify instruction. +void killins(Ctx* ctx, Fn* fn, Ins* i, Ref r) { + replaceuses(ctx, fn, i->to, r); + *i = {}; + i->op = Onop; +} + +// Handle degraded phi. +void dedupphi(Ctx* ctx, Fn* fn, Blk* b) { + Phi** pp = &b->phi; + for (Phi* p; (p = *pp);) { + Ref r = phicopyref(ctx, fn, b, p); + if (req(r, Ref::none())) { + pp = &p->link; + } else { + replaceuses(ctx, fn, p->to, r); + p->to = Ref::none(); + *pp = p->link; + } + } +} + +// Instruction normalization. + +int rcmp(Ref a, Ref b) { + if (rtype(a) != rtype(b)) + return rtype(a) - rtype(b); + return static_cast<int>(a.val) - static_cast<int>(b.val); +} + +void normins(Ctx* ctx, Fn* fn, Ins* i) { + int64_t v; + // Truncate to 32 bits. + for (int n = 0; n < 2; ++n) + if (!KWIDE(argcls(i, n)) && isconbits(fn, i->arg[n], &v) && (v & 0xffffffffLL) != v) + i->arg[n] = getcon(ctx, v & 0xffffffffLL, fn); + // Commutative op sort. + if (optab[i->op].commutes && rcmp(i->arg[0], i->arg[1]) > 0) { + Ref r = i->arg[1]; + i->arg[1] = i->arg[0]; + i->arg[0] = r; + } +} + +// Negate constant. +int negcon(int cls, Con* c) { + Con z = {}; + z.type = Con::CBits; + z.bits.i = 0; + return foldint(c, Osub, cls, &z, c); +} + +// Merge constants. +void assoccon(Ctx* ctx, Fn* fn, Blk* b, Ins* i1) { + int op = (i1->op == Osub) ? Oadd : static_cast<int>(i1->op); + + if (!optab[op].assoc || KBASE(i1->cls) != 0 || rtype(i1->arg[0]) != RTmp || + rtype(i1->arg[1]) != RCon) + return; + Con c1 = fn->con[i1->arg[1].val]; + + Tmp* t2 = &fn->tmp[i1->arg[0].val]; + if (!t2->def) + return; + Ins* i2 = t2->def; + + if (op != (i2->op == Osub ? Oadd : static_cast<int>(i2->op)) || rtype(i2->arg[1]) != RCon) + return; + Con c2 = fn->con[i2->arg[1].val]; + + assert(KBASE(i2->cls) == 0); + assert(KWIDE(i2->cls) >= KWIDE(i1->cls)); + + // Unify as add. + if (i1->op == Osub && negcon(i1->cls, &c1)) + return; + if (i2->op == Osub && negcon(i2->cls, &c2)) + return; + Con c; + if (foldint(&c, op, i1->cls, &c1, &c2)) + return; + + // Revert to sub. + if (op == Oadd && c.type == Con::CBits && + ((i1->cls == Kl && c.bits.i < 0) || + (i1->cls == Kw && static_cast<int32_t>(c.bits.i) < 0))) { + int fail = negcon(i1->cls, &c); + assert(fail == 0); + (void)fail; + op = Osub; + } + + i1->op = static_cast<uint>(op); + i1->arg[0] = i2->arg[0]; + i1->arg[1] = newcon(ctx, &c, fn); + adduse_ins(ctx, &fn->tmp[i1->arg[0].val], b, i1); +} + +// Deduplication. +void dedupins(Ctx* ctx, Fn* fn, Blk* b, Ins* i) { + normins(ctx, fn, i); + if (i->op == Onop || pinned(i)) + return; + // sel skip. + assert(i->op != Osel0); + assert(!req(i->to, Ref::none())); + assoccon(ctx, fn, b, i); + + Ref r = copyref(ctx, fn, b, i); + if (!req(r, Ref::none())) { + killins(ctx, fn, i, r); + return; + } + r = foldref(ctx, fn, i); + if (!req(r, Ref::none())) { + killins(ctx, fn, i, r); + return; + } + Ins* i1 = gvndup(ctx, i, 1); + if (i1) + killins(ctx, fn, i, i1->to); +} + +// jnz simplification. + +// Check cmp eq/ne 0. +int cmpeqz(Fn* fn, Ref r, Ref* arg, int* cls, int* eqval) { + if (rtype(r) != RTmp) + return 0; + Ins* i = fn->tmp[r.val].def; + if (i && optab[i->op].cmpeqwl && req(i->arg[1], Ref::con_z())) { + *arg = i->arg[0]; + *cls = argcls(i, 0); + *eqval = optab[i->op].eqval; + return 1; + } + return 0; +} + +// Check branch dominance. +int branchdom(Fn* fn, Blk* bif, Blk* bbr1, Blk* bbr2, Blk* b) { + assert(bif->jmp.type == Jjnz); + return b != bif && dom(bbr1, b) && !reachesnotvia(fn, bbr2, b, bif); +} + +// Check 0-branch. +int domzero(Fn* fn, Blk* d, Blk* b, int* z) { + if (branchdom(fn, d, d->s1, d->s2, b)) { + *z = 0; + return 1; + } + if (branchdom(fn, d, d->s2, d->s1, b)) { + *z = 1; + return 1; + } + return 0; +} + +// Actual width class. +int usecls(Use* u, Ref r, int cls) { + switch (u->type) { + case Use::UIns: { + int k = Kx; // widest + if (req(u->u.ins->arg[0], r)) + k = argcls(u->u.ins, 0); + if (req(u->u.ins->arg[1], r) && (k == Kx || !KWIDE(k))) + k = argcls(u->u.ins, 1); + return k == Kx ? cls : k; + } + case Use::UPhi: + if (req(u->u.phi->to, Ref::none())) + return cls; // eliminated + return u->u.phi->cls; + case Use::UJmp: + return Kw; + default: + break; + } + die("unreachable"); +} + +// Replace with 0. +void propjnz0(Ctx* ctx, Fn* fn, Blk* bif, Blk* s0, Blk* snon0, Ref r, int cls) { + if (s0->npred != 1 || rtype(r) != RTmp) + return; + Tmp* t = &fn->tmp[r.val]; + for (Use* u = t->use; u < &t->use[t->nuse]; ++u) { + Blk* b = fn->rpo[u->bid]; + // Match width. + if (usecls(u, r, cls) == cls && branchdom(fn, bif, s0, snon0, b)) + replaceuse(ctx, fn, u, r, Ref::con_z()); + } +} + +void dedupjmp(Ctx* ctx, Fn* fn, Blk* b) { + if (b->jmp.type != Jjnz) + return; + + // Propagate jmp arg. + propjnz0(ctx, fn, b, b->s2, b->s1, b->jmp.arg, Kw); + // Propagate underlying arg. + Ref arg; + int cls, eqval; + if (cmpeqz(fn, b->jmp.arg, &arg, &cls, &eqval)) { + Blk* ps[2] = {b->s1, b->s2}; + propjnz0(ctx, fn, b, ps[eqval ^ 1], ps[eqval], arg, cls); + } + + // Collapse jnz. + int64_t v = 1; + int z = 0; + if (b->s1 == b->s2 || isconbits(fn, b->jmp.arg, &v) || zeroval(fn, b, b->jmp.arg, Kw, &z)) { + if (v == 0 || z) + b->s1 = b->s2; + b->s2 = nullptr; // Dead branch live instructions are rescued later in rebuildcfg + b->jmp.type = Jjmp; + b->jmp.arg = Ref::none(); + } +} + +// Rebuild CFG. + +void rebuildcfg(Ctx* ctx, Fn* fn) { + uint nblk = fn->nblk; + Blk** rpo = static_cast<Blk**>(emalloc(nblk * sizeof rpo[0])); + memcpy(rpo, fn->rpo, nblk * sizeof rpo[0]); + + fillcfg(ctx, fn); // Dead blocks are marked with id == ~0u here + + Blk* s = fn->start; + for (uint n = 0; n < nblk; ++n) { + Blk* b = rpo[n]; + if (b->id != ~0u) + continue; // Still reachable after GVN, skip + assert(b != s); + for (Ins* i = b->ins; i < &b->ins[b->nins]; ++i) + if (!optab[i->op].pinned && gvndup(ctx, i, 0) == i) + addins(ctx, &s->ins, &s->nins, i); + } + free(rpo); +} + +} // namespace + +// zeroval infer. +int zeroval(Fn* fn, Blk* b, Ref r, int cls, int* z) { + Ref arg; + int cls1, eqval; + for (Blk* d = b->idom; d; d = d->idom) { + if (d->jmp.type != Jjnz) + continue; + if (req(r, d->jmp.arg) && cls == Kw && domzero(fn, d, b, z)) + return 1; + if (cmpeqz(fn, d->jmp.arg, &arg, &cls1, &eqval) && req(r, arg) && cls == cls1 && + domzero(fn, d, b, z)) { + *z ^= eqval; + return 1; + } + } + return 0; +} + +// gvn entry. +void gvn(Ctx* ctx, Fn* fn) { + ctx->gvn_con01[0] = getcon(ctx, 0, fn); + ctx->gvn_con01[1] = getcon(ctx, 1, fn); + + // Zero out width memo. + for (Blk* b = fn->start; b; b = b->link) + for (Phi* p = b->phi; p; p = p->link) + p->visit = 0; + + fillloop(fn); + narrowpars(ctx, fn); + filluse(ctx, fn); + ssacheck(fn); + + uint nins = 0; + for (Blk* b = fn->start; b; b = b->link) { + b->visit = 0; + nins += b->nins; + } + + ctx->gvn_tbln = nins + nins / 2; + ctx->gvn_tbl = static_cast<Ins**>(emalloc(ctx->gvn_tbln * sizeof ctx->gvn_tbl[0])); + for (uint n = 0; n < fn->nblk; ++n) { + Blk* b = fn->rpo[n]; + dedupphi(ctx, fn, b); + for (Ins* i = b->ins; i < &b->ins[b->nins]; ++i) + dedupins(ctx, fn, b, i); + dedupjmp(ctx, fn, b); + } + rebuildcfg(ctx, fn); + free(ctx->gvn_tbl); + ctx->gvn_tbl = nullptr; + ctx->gvn_tbln = 0; + + if (debug['G']) { + fprintf(ctx->dbg_buf, "\n> After GVN:\n"); + printfn(fn, ctx->dbg_buf); + } +} + +} // namespace rcc |
