mirror of
git://c9x.me/qbe.git
synced 2026-04-05 09:59:47 +00:00
More or less as proposed in its ninth iteration with the
addition of a gcmmove() functionality to restore coherent
local schedules.
Changes since RFC 8:
Features:
- generalization of phi 1/0 detection
- collapse linear jmp chains before GVN; simplifies if-graph
detection used in 0/non-0 value inference and if-elim...
- infer 0/non-0 values from dominating blk jnz; eliminates
redundant cmp eq/ne 0 and associated jnz/blocks, for example
redundant null pointer checks (hare codebase likes this)
- remove (emergent) empty if-then-else graphlets between GVN and
GCM; improves GCM instruction placement, particularly cmps.
- merge %addr =l add %addr1, N sequences - reduces tmp count,
register pressure.
- squash consecutive associative ops with constant args, e.g.
t1 = add t, N ... t2 = add t2, M -> t2 = add t, N+M
Bug Fixes:
- remove "cmp eq/ne of non-identical RCon's " in copyref().
RCon's are not guaranteed to be dedup'ed, and symbols can
alias.
Codebase:
- moved some stuff into cfg.c including blkmerge()
- some refactoring in gvn.c
- simplification of reassoc.c - always reassoc all cmp ops
and Kl add %t, N. Better on coremark, smaller codebase.
- minor simplification of movins() - use vins
Testing - standard QBE, cproc, hare, harec, coremark
[still have Rust build issues with latest roland]
Benchmark
- coremark is ~15%+ faster than master
- hare "HARETEST_INCLUDE='slow' make check" ~8% faster
(crypto::sha1::sha1_1gb is biggest obvious win - ~25% faster)
Changes since RFC 7:
Bug fixes:
- remove isbad4gcm() in GVN/GCM - it is unsound due to different state
at GVN vs GCM time; replace with "reassociation" pass after GCM
- fix intra-blk use-before-def after GCM
- prevent GVN from deduping trapping instructions cos GCM will not
move them
- remove cmp eq/ne identical arg copy detection for floating point, it
is not valid for NaN
- fix cges/cged flagged as commutative in ops.h instead of cnes/cned
respectively; just a typo
Minor features:
- copy detection handles cmp le/lt/ge/gt with identical args
- treat (integer) div/rem by non-zero constant as non-trapping
- eliminate add N/sub N pairs in copy detection
- maintain accurate tmp use in GVN; not strictly necessary but enables
interim global state sanity checking
- "reassociation" of trivial constant offset load/store addresses, and
cmp ops with point-of-use in pass after GCM
- normalise commutative op arg order - e.g. op con, tmp -> op tmp, con
to simplify copy detection and GVN instruction dedup
Codebase:
- split out core copy detection and constant folding (back) out into
copy.c, fold.c respectively; gvn.c was getting monolithic
- generic support for instruction moving in ins.c - used by GCM and
reassoc
- new reassociation pass in reassoc.c
- other minor clean-up/refactor
Changes since RFC 6:
- More ext elimination in GVN by examination of def and use bit width
- elimination of redundant and mask by bit width examination
- Incorporation of Song's patch
Changes since RFC 5:
- avoidance of "bad" candidates for GVN/GCM - trivial address offset
calculations, and comparisons
- more copy detection mostly around boolean values
- allow elimination of unused load, alloc, trapping instructions
- detection of trivial boolean v ? 1 : 0 phi patterns
- bug fix for (removal of) "chg" optimisation in ins recreation - it
was missing removal of unused instructions in some cases
ifelim() between GVN and GCM; deeper nopunused()
196 lines
4.0 KiB
C
196 lines
4.0 KiB
C
#include "all.h"
|
|
|
|
void
|
|
addins(Ins **pvins, uint *pnins, Ins *i)
|
|
{
|
|
if (i->op == Onop)
|
|
return;
|
|
vgrow(pvins, ++(*pnins));
|
|
(*pvins)[(*pnins)-1] = *i;
|
|
}
|
|
|
|
void
|
|
addbins(Blk *b, Ins **pvins, uint *pnins)
|
|
{
|
|
Ins *i;
|
|
|
|
for (i = b->ins; i < &b->ins[b->nins]; i++)
|
|
addins(pvins, pnins, i);
|
|
}
|
|
|
|
static int
|
|
unusedins(Fn *fn, Ins *i);
|
|
|
|
static int
|
|
unusedtmp(Fn *fn, Tmp *t)
|
|
{
|
|
if (t->nuse == 0)
|
|
return 1;
|
|
if (t->nuse == 1)
|
|
if (t->use[0].type == UIns)
|
|
return unusedins(fn, t->use[0].u.ins);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
unusedtmpref(Fn *fn, Ref r)
|
|
{
|
|
if (rtype(r) != RTmp)
|
|
return 0;
|
|
return unusedtmp(fn, &fn->tmp[r.val]);
|
|
}
|
|
|
|
static int
|
|
unusedins(Fn *fn, Ins *i)
|
|
{
|
|
if (!INRANGE(i->op, Opar, Ocall))
|
|
if (unusedtmpref(fn, i->to))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* replace unused ins with nop */
|
|
/* needs use; breaks use */
|
|
void
|
|
nopunused(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 (unusedins(fn, i))
|
|
*i = (Ins){.op = Onop};
|
|
}
|
|
|
|
typedef struct FromLoc FromLoc;
|
|
struct FromLoc {
|
|
InsLoc from;
|
|
uint ton;
|
|
};
|
|
|
|
static int
|
|
loccmp(InsLoc *la, InsLoc *lb)
|
|
{
|
|
if (la->bid != lb->bid)
|
|
return (int)la->bid - (int)lb->bid;
|
|
return la->insn - lb->insn;
|
|
}
|
|
|
|
static int
|
|
tocmp(const void *a, const void *b)
|
|
{
|
|
InsMov *ma, *mb;
|
|
|
|
ma = (InsMov*)a;
|
|
mb = (InsMov*)b;
|
|
return loccmp(&ma->to, &mb->to);
|
|
}
|
|
|
|
static int
|
|
fromcmp(const void *a, const void *b)
|
|
{
|
|
FromLoc *fa, *fb;
|
|
|
|
fa = (FromLoc*)a;
|
|
fb = (FromLoc*)b;
|
|
return loccmp(&fa->from, &fb->from);
|
|
}
|
|
|
|
static int
|
|
loceq(InsLoc *a, InsLoc *b)
|
|
{
|
|
return a->bid == b->bid && a->insn == b->insn;
|
|
}
|
|
|
|
/* after, mov is sorted by to, and to.insn, from.insn are updated */
|
|
void
|
|
movins(Fn *fn, InsMov *mov, uint nmov, int del)
|
|
{
|
|
Blk *b, *b2;
|
|
uint bid, n, fromn, ton, ifromn, nins;
|
|
FromLoc *from;
|
|
uint *newbnins;
|
|
Ins **newbins, *vins;
|
|
|
|
qsort(mov, nmov, sizeof mov[0], tocmp);
|
|
|
|
from = emalloc(nmov * sizeof from[0]);
|
|
for (n = 0; n < nmov; n++) {
|
|
from[n].from = mov[n].from;
|
|
from[n].ton = n;
|
|
}
|
|
qsort(from, nmov, sizeof from[0], fromcmp);
|
|
|
|
nins = 0;
|
|
vins = vnew(nins, sizeof vins[0], PFn);
|
|
|
|
newbnins = emalloc(fn->nblk * sizeof newbnins[0]);
|
|
newbins = emalloc(fn->nblk * sizeof newbins[0]);
|
|
|
|
/* populate new ins buffers */
|
|
/* update mov to.insn, from insn */
|
|
fromn = ton = 0;
|
|
for (bid = 0; bid < fn->nblk; bid++) {
|
|
/* no moves to this block */
|
|
if (nmov <= ton || mov[ton].to.bid != bid) {
|
|
while (fromn < nmov && from[fromn].from.bid == bid)
|
|
fromn++;
|
|
continue;
|
|
}
|
|
b = fn->rpo[bid];
|
|
nins = 0;
|
|
for (ifromn = 0; ifromn < b->nins; ifromn++) {
|
|
/* insert new ins, update to */
|
|
while (ton < nmov && loceq(&mov[ton].to, &(InsLoc){.bid = bid, .insn = ifromn})) {
|
|
b2 = fn->rpo[mov[ton].from.bid];
|
|
assert(mov[ton].from.insn < b2->nins);
|
|
addins(&vins, &nins, &b2->ins[mov[ton].from.insn]);
|
|
mov[ton++].to.insn = nins-1;
|
|
}
|
|
/* update from */
|
|
while (fromn < nmov && loceq(&from[fromn].from, &(InsLoc){.bid = bid, .insn = ifromn}))
|
|
from[fromn++].from.insn = nins;
|
|
/* copy original ins */
|
|
addins(&vins, &nins, &b->ins[ifromn]);
|
|
}
|
|
/* append new ins, update to */
|
|
while (ton < nmov && mov[ton].to.bid == bid) {
|
|
assert(mov[ton].to.insn == b->nins);
|
|
b2 = fn->rpo[mov[ton].from.bid];
|
|
assert(mov[ton].from.insn < b2->nins);
|
|
addins(&vins, &nins, &b2->ins[mov[ton].from.insn]);
|
|
mov[ton++].to.insn = nins-1;
|
|
}
|
|
assert(ifromn == b->nins);
|
|
/* idup(&newbins[bid], vins, nins); */
|
|
newbins[bid] = vins;
|
|
vins = vnew(0, sizeof vins[0], PFn);
|
|
newbnins[bid] = nins;
|
|
}
|
|
assert(fromn == nmov);
|
|
assert(ton == nmov);
|
|
|
|
/* install new b->ins */
|
|
for (bid = 0; bid < fn->nblk; bid++) {
|
|
if (newbnins[bid] == 0)
|
|
continue;
|
|
b = fn->rpo[bid];
|
|
b->ins = newbins[bid];
|
|
b->nins = newbnins[bid];
|
|
}
|
|
|
|
/* remove from ins, update mov from insn */
|
|
for (fromn = 0; fromn < nmov; fromn++) {
|
|
b = fn->rpo[from[fromn].from.bid];
|
|
assert(from[fromn].from.insn < b->nins);
|
|
if (del)
|
|
b->ins[from[fromn].from.insn] = (Ins){.op = Onop};
|
|
mov[from[fromn].ton].from.insn = from[fromn].from.insn;
|
|
}
|
|
|
|
free(from);
|
|
free(newbins);
|
|
free(newbnins);
|
|
}
|