Simple Inner Loop Optimzation

Two simple loop optimizations.

1. Strength reduction of mul[tiplication] by loop induction
variable.

2. Hoisting of (address) base into phi where loop induction
variable is used only as a base (address) offset.

Limited to loops with a single body block, which happily
is always innermost loops. This restriction would not be
very hard to lift - it would require detecting the set of
loop blocks (and ensuring reducibility?)

Limited to loop induction variables with 0 initial value
and increment of 1 (for mul strength reduction). This
limitation is trivial to lift; however all of the
cproc/hare[c]/coremark opportunity is with 0/1 loops for
mul reduction, and 0 initial value for base-offset opt.
This commit is contained in:
Roland Paterson-Jones 2024-12-07 10:22:30 +02:00 committed by Quentin Carbonneaux
parent ac48f83f17
commit 5370616f7d
4 changed files with 224 additions and 2 deletions

View File

@ -5,8 +5,8 @@ PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
COMMOBJ = main.o util.o parse.o abi.o cfg.o mem.o ssa.o alias.o load.o \
copy.o fold.o gvn.o gcm.o simpl.o ifopt.o live.o spill.o rega.o \
emit.o
copy.o fold.o gvn.o gcm.o loopopt.o simpl.o ifopt.o live.o \
spill.o rega.o emit.o
AMD64OBJ = amd64/targ.o amd64/sysv.o amd64/isel.o amd64/emit.o amd64/winabi.o
ARM64OBJ = arm64/targ.o arm64/abi.o arm64/isel.o arm64/emit.o
RV64OBJ = rv64/targ.o rv64/abi.o rv64/isel.o rv64/emit.o

3
all.h
View File

@ -606,6 +606,9 @@ void gcm(Fn *);
/* ifopt.c */
void ifconvert(Fn *fn);
/* loopopt.c */
void loopopt(Fn *fn);
/* simpl.c */
void simpl(Fn *);

217
loopopt.c Normal file
View File

@ -0,0 +1,217 @@
#include "all.h"
typedef struct Cntr Cntr;
typedef struct Loop Loop;
static int
forloop(Blk *bh, Blk *bb)
{
if (bh->npred == 2)
if (bh->jmp.type == Jjnz)
if (bb->npred == 1)
if (bb->jmp.type == Jjmp)
if (bb->s1 == bh)
return 1;
return 0;
}
static int
doloop(Blk *bh)
{
if (bh->npred == 2)
if (bh->jmp.type == Jjnz)
if (bh->s1 == bh || bh->s2 == bh)
return 1;
return 0;
}
struct Loop {
Blk *bp; /* pre-header */
Blk *bh; /* header */
Blk *bb; /* body */
};
static int
invariant(Ref r, Loop l, Fn *fn)
{
Tmp *t;
if (rtype(r) == RCon)
return 1;
assert(rtype(r) == RTmp);
t = &fn->tmp[r.val];
if (t->bid != l.bh->id)
if (t->bid != l.bb->id)
return 1;
return 0;
}
struct Cntr {
Phi *p; /* defining phi */
Ref r0; /* initial value */
Ref rc; /* post-increment */
Ref ri; /* increment */
};
/* check if a phi defines an additive
* induction variable
*/
static int
counter(Fn *fn, Loop l, Phi *p, Cntr *c)
{
Tmp *t;
Ins *i;
assert(l.bh->npred == 2);
assert(l.bh->pred[0] == l.bb || l.bh->pred[1] == l.bb);
c->p = p;
c->r0 = phiarg(p, l.bp);
c->rc = phiarg(p, l.bb);
if (KBASE(p->cls) != 0
|| rtype(c->rc) != RTmp
|| (t = &fn->tmp[c->rc.val])->bid != l.bb->id
|| !(i = t->def)
|| i->op != Oadd
|| req(i->arg[0], p->to) == req(i->arg[1], p->to))
return 0;
c->ri = i->arg[req(i->arg[0], p->to)];
assert(p->cls == i->cls);
return invariant(c->ri, l, fn);
}
static void
mulredux(Fn *fn, Loop l, Cntr c)
{
Ins *i, *ie, ia;
Phi *p;
Ref rm;
/* dead simple case for now...
* majority case in practice */
if (!req(c.r0, con01[0]) || !req(c.ri, con01[1]))
return;
ia = (Ins){.op = Oadd};
ie = &l.bb->ins[l.bb->nins]; /* l.bb->ins may change */
for (i=l.bb->ins; i<ie; i++) {
/* TODO i->cls != p->cls is not necessary? */
if (i->op != Omul
|| i->cls != p->cls
|| req(i->arg[0], p->to) == req(i->arg[1], p->to))
continue;
assert(KBASE(i->cls) == 0);
rm = i->arg[req(i->arg[0], p->to)];
if (!invariant(rm, l, fn))
continue;
ia.to = newtmp("lop", i->cls, fn);
ia.cls = i->cls;
ia.arg[0] = i->to;
ia.arg[1] = rm;
addins(&l.bb->ins, &l.bb->nins, &ia);
p = alloc(sizeof *p);
p->cls = i->cls;
p->to = i->to;
p->narg = 2;
p->blk = vnew(2, sizeof p->blk[0], PFn);
p->blk[0] = l.bp;
p->blk[1] = l.bb;
p->arg = vnew(2, sizeof p->arg[0], PFn);
p->arg[0] = CON_Z;
p->arg[1] = ia.to;
p->link = l.bh->phi;
l.bh->phi = p;
*i = (Ins){.op = Onop};
}
}
static void
addbase(Fn *fn, Loop l, Cntr c)
{
Ins *i, *ii, *ib;
Tmp *t, *ti;
Use *u;
Ref rb;
uint n;
/* dead simple case for now...
* majority case in practice */
if (!req(c.r0, CON_Z))
return;
assert(rtype(c.p->to) == RTmp);
t = &fn->tmp[c.p->to.val];
if (t->nuse != 2)
return;
ii = ib = 0;
for (u=t->use; u<&t->use[t->nuse]; u++) {
if (u->type != UIns)
return;
i = u->u.ins;
if (i->op != Oadd
|| i->cls != c.p->cls
|| u->bid != l.bb->id)
return;
assert(req(i->arg[0], c.p->to) || req(i->arg[1], c.p->to));
if (req(i->to, c.rc)) {
assert(rtype(i->to) == RTmp);
ti = &fn->tmp[i->to.val];
if (ti->nuse != 1)
return;
assert(ti->use[0].type == UPhi);
assert(ti->use[0].bid == l.bh->id);
assert(ti->use[0].u.phi == c.p);
ii = i;
} else {
rb = i->arg[req(i->arg[0], c.p->to)];
if (!invariant(rb, l, fn))
return;
assert(!req(rb, c.p->to));
ib = i;
}
}
n = phiargn(c.p, l.bp);
assert(n != -1u && req(c.p->arg[n], CON_Z));
c.p->arg[n] = rb;
ii->arg[!req(ii->arg[0], c.p->to)] = ib->to;
c.p->to = ib->to;
*ib = (Ins){.op = Onop};
}
static void
counteropt(Fn *fn, void opt(Fn *, Loop, Cntr))
{
Loop l;
Cntr c;
Phi *p;
uint n;
for (n=0; n<fn->nblk; n++) {
l.bh = fn->rpo[n];
if (forloop(l.bh, l.bh->s1))
l.bb = l.bh->s1;
else if (forloop(l.bh, l.bh->s2))
l.bb = l.bh->s2;
else if (doloop(l.bh))
l.bb = l.bh; /* header and body */
else
continue;
l.bp = l.bh->pred[l.bh->pred[0] == l.bh];
assert(l.bh->loop > 1);
assert(l.bh->loop == l.bb->loop);
for (p=l.bh->phi; p; p=p->link)
if (counter(fn, l, p, &c)) {
assert(KBASE(p->cls) == 0);
opt(fn, l, c);
}
}
}
void
loopopt(Fn *fn)
{
counteropt(fn, mulredux);
filluse(fn);
counteropt(fn, addbase);
}

2
main.c
View File

@ -85,6 +85,8 @@ func(Fn *fn)
gcm(fn);
filluse(fn);
ssacheck(fn);
loopopt(fn);
filluse(fn);
if (T.cansel) {
ifconvert(fn);
fillcfg(fn);