Files
cell/src/qbe/gcm.c
2026-02-17 10:23:47 -06:00

461 lines
7.6 KiB
C

#include "all.h"
#define NOBID (-1u)
static int
isdivwl(Ins *i)
{
switch (i->op) {
case Odiv:
case Orem:
case Oudiv:
case Ourem:
return KBASE(i->cls) == 0;
default:
return 0;
}
}
int
pinned(Ins *i)
{
return optab[i->op].pinned || isdivwl(i);
}
/* pinned ins that can be eliminated if unused */
static int
canelim(Ins *i)
{
return isload(i->op) || isalloc(i->op) || isdivwl(i);
}
static uint earlyins(Fn *, Blk *, Ins *);
static uint
schedearly(Fn *fn, Ref r)
{
Tmp *t;
Blk *b;
if (rtype(r) != RTmp)
return 0;
t = &fn->tmp[r.val];
if (t->gcmbid != NOBID)
return t->gcmbid;
b = fn->rpo[t->bid];
if (t->def) {
assert(b->ins <= t->def && t->def < &b->ins[b->nins]);
t->gcmbid = 0; /* mark as visiting */
t->gcmbid = earlyins(fn, b, t->def);
} else {
/* phis do not move */
t->gcmbid = t->bid;
}
return t->gcmbid;
}
static uint
earlyins(Fn *fn, Blk *b, Ins *i)
{
uint b0, b1;
b0 = schedearly(fn, i->arg[0]);
assert(b0 != NOBID);
b1 = schedearly(fn, i->arg[1]);
assert(b1 != NOBID);
if (fn->rpo[b0]->depth < fn->rpo[b1]->depth) {
assert(dom(fn->rpo[b0], fn->rpo[b1]));
b0 = b1;
}
return pinned(i) ? b->id : b0;
}
static void
earlyblk(Fn *fn, uint bid)
{
Blk *b;
Phi *p;
Ins *i;
uint n;
b = fn->rpo[bid];
for (p=b->phi; p; p=p->link)
for (n=0; n<p->narg; n++)
schedearly(fn, p->arg[n]);
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (pinned(i)) {
schedearly(fn, i->arg[0]);
schedearly(fn, i->arg[1]);
}
schedearly(fn, b->jmp.arg);
}
/* least common ancestor in dom tree */
static uint
lcabid(Fn *fn, uint bid1, uint bid2)
{
Blk *b;
if (bid1 == NOBID)
return bid2;
if (bid2 == NOBID)
return bid1;
b = lca(fn->rpo[bid1], fn->rpo[bid2]);
assert(b);
return b->id;
}
static uint
bestbid(Fn *fn, uint earlybid, uint latebid)
{
Blk *curb, *earlyb, *bestb;
if (latebid == NOBID)
return NOBID; /* unused */
assert(earlybid != NOBID);
earlyb = fn->rpo[earlybid];
bestb = curb = fn->rpo[latebid];
assert(dom(earlyb, curb));
while (curb != earlyb) {
curb = curb->idom;
if (curb->loop < bestb->loop)
bestb = curb;
}
return bestb->id;
}
static uint lateins(Fn *, Blk *, Ins *, Ref r);
static uint latephi(Fn *, Phi *, Ref r);
static uint latejmp(Blk *, Ref r);
/* return lca bid of ref uses */
static uint
schedlate(Fn *fn, Ref r)
{
Tmp *t;
Blk *b;
Use *u;
uint earlybid;
uint latebid;
uint uselatebid;
if (rtype(r) != RTmp)
return NOBID;
t = &fn->tmp[r.val];
if (t->visit)
return t->gcmbid;
t->visit = 1;
earlybid = t->gcmbid;
if (earlybid == NOBID)
return NOBID; /* not used */
/* reuse gcmbid for late bid */
t->gcmbid = t->bid;
latebid = NOBID;
for (u=t->use; u<&t->use[t->nuse]; u++) {
assert(u->bid < fn->nblk);
b = fn->rpo[u->bid];
switch (u->type) {
case UXXX:
die("unreachable");
break;
case UPhi:
uselatebid = latephi(fn, u->u.phi, r);
break;
case UIns:
uselatebid = lateins(fn, b, u->u.ins, r);
break;
case UJmp:
uselatebid = latejmp(b, r);
break;
}
latebid = lcabid(fn, latebid, uselatebid);
}
/* latebid may be NOBID if the temp is used
* in fixed instructions that may be eliminated
* and are themselves unused transitively */
if (t->def && !pinned(t->def))
t->gcmbid = bestbid(fn, earlybid, latebid);
/* else, keep the early one */
/* now, gcmbid is the best bid */
return t->gcmbid;
}
/* returns lca bid of uses or NOBID if
* the definition can be eliminated */
static uint
lateins(Fn *fn, Blk *b, Ins *i, Ref r)
{
uint latebid;
assert(b->ins <= i && i < &b->ins[b->nins]);
assert(req(i->arg[0], r) || req(i->arg[1], r));
latebid = schedlate(fn, i->to);
if (pinned(i)) {
if (latebid == NOBID)
if (canelim(i))
return NOBID;
return b->id;
}
return latebid;
}
static uint
latephi(Fn *fn, Phi *p, Ref r)
{
uint n;
uint latebid;
if (!p->narg)
return NOBID; /* marked as unused */
latebid = NOBID;
for (n = 0; n < p->narg; n++)
if (req(p->arg[n], r))
latebid = lcabid(fn, latebid, p->blk[n]->id);
assert(latebid != NOBID);
return latebid;
}
static uint
latejmp(Blk *b, Ref r)
{
if (req(b->jmp.arg, R))
return NOBID;
else {
assert(req(b->jmp.arg, r));
return b->id;
}
}
static void
lateblk(Fn *fn, uint bid)
{
Blk *b;
Phi **pp;
Ins *i;
b = fn->rpo[bid];
for (pp=&b->phi; *(pp);)
if (schedlate(fn, (*pp)->to) == NOBID) {
(*pp)->narg = 0; /* mark unused */
*pp = (*pp)->link; /* remove phi */
} else
pp = &(*pp)->link;
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (pinned(i))
schedlate(fn, i->to);
}
static void
addgcmins(Fn *fn, Ins *vins, uint nins)
{
Ins *i;
Tmp *t;
Blk *b;
for (i=vins; i<&vins[nins]; i++) {
assert(rtype(i->to) == RTmp);
t = &fn->tmp[i->to.val];
b = fn->rpo[t->gcmbid];
addins(&b->ins, &b->nins, i);
}
}
/* move live instructions to the
* end of their target block; use-
* before-def errors are fixed by
* schedblk */
static void
gcmmove(Fn *fn)
{
Tmp *t;
Ins *vins, *i;
uint nins;
nins = 0;
vins = vnew(nins, sizeof vins[0], PFn);
for (t=fn->tmp; t<&fn->tmp[fn->ntmp]; t++) {
if (t->def == 0)
continue;
if (t->bid == t->gcmbid)
continue;
i = t->def;
if (pinned(i) && !canelim(i))
continue;
assert(rtype(i->to) == RTmp);
assert(t == &fn->tmp[i->to.val]);
if (t->gcmbid != NOBID)
addins(&vins, &nins, i);
*i = (Ins){.op = Onop};
}
addgcmins(fn, vins, nins);
}
/* dfs ordering */
static Ins *
schedins(Fn *fn, Blk *b, Ins *i, Ins **pvins, uint *pnins)
{
Ins *i0, *i1;
Tmp *t;
uint n;
igroup(b, i, &i0, &i1);
for (i=i0; i<i1; i++)
for (n=0; n<2; n++) {
if (rtype(i->arg[n]) != RTmp)
continue;
t = &fn->tmp[i->arg[n].val];
if (t->bid != b->id || !t->def)
continue;
schedins(fn, b, t->def, pvins, pnins);
}
for (i=i0; i<i1; i++) {
addins(pvins, pnins, i);
*i = (Ins){.op = Onop};
}
return i1;
}
/* order ins within a block */
static void
schedblk(Fn *fn)
{
Blk *b;
Ins *i, *vins;
uint nins;
vins = vnew(0, sizeof vins[0], PHeap);
for (b=fn->start; b; b=b->link) {
nins = 0;
for (i=b->ins; i<&b->ins[b->nins];)
i = schedins(fn, b, i, &vins, &nins);
idup(b, vins, nins);
}
vfree(vins);
}
static int
cheap(Ins *i)
{
int x;
if (KBASE(i->cls) != 0)
return 0;
switch (i->op) {
case Oneg:
case Oadd:
case Osub:
case Omul:
case Oand:
case Oor:
case Oxor:
case Osar:
case Oshr:
case Oshl:
return 1;
default:
return iscmp(i->op, &x, &x);
}
}
static void
sinkref(Fn *fn, Blk *b, Ref *pr)
{
Ins i;
Tmp *t;
Ref r;
if (rtype(*pr) != RTmp)
return;
t = &fn->tmp[pr->val];
if (!t->def
|| t->bid == b->id
|| pinned(t->def)
|| !cheap(t->def))
return;
/* sink t->def to b */
i = *t->def;
r = newtmp("snk", t->cls, fn);
t = 0; /* invalidated */
*pr = r;
i.to = r;
fn->tmp[r.val].gcmbid = b->id;
emiti(i);
sinkref(fn, b, &i.arg[0]);
sinkref(fn, b, &i.arg[1]);
}
/* redistribute trivial ops to point of
* use to reduce register pressure
* requires rpo, use; breaks use
*/
static void
sink(Fn *fn)
{
Blk *b;
Ins *i;
for (b=fn->start; b; b=b->link) {
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (isload(i->op))
sinkref(fn, b, &i->arg[0]);
else if (isstore(i->op))
sinkref(fn, b, &i->arg[1]);
sinkref(fn, b, &b->jmp.arg);
}
addgcmins(fn, curi, &insb[NIns] - curi);
}
/* requires use dom
* maintains rpo pred dom
* breaks use
*/
void
gcm(Fn *fn)
{
Tmp *t;
uint bid;
filldepth(fn);
fillloop(fn);
for (t=fn->tmp; t<&fn->tmp[fn->ntmp]; t++) {
t->visit = 0;
t->gcmbid = NOBID;
}
for (bid=0; bid<fn->nblk; bid++)
earlyblk(fn, bid);
for (bid=0; bid<fn->nblk; bid++)
lateblk(fn, bid);
gcmmove(fn);
filluse(fn);
curi = &insb[NIns];
sink(fn);
filluse(fn);
schedblk(fn);
if (debug['G']) {
fprintf(stderr, "\n> After GCM:\n");
printfn(fn, stderr);
}
}