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()
333 lines
6.8 KiB
C
333 lines
6.8 KiB
C
#include "all.h"
|
|
|
|
static uint
|
|
u64_wbits(uint64_t v)
|
|
{
|
|
uint n;
|
|
|
|
n = 0;
|
|
if (v >> 32) { n += 32; v >>= 32; }
|
|
if (v >> 16) { n += 16; v >>= 16; }
|
|
if (v >> 8) { n += 8; v >>= 8; }
|
|
if (v >> 4) { n += 4; v >>= 4; }
|
|
if (v >> 2) { n += 2; v >>= 2; }
|
|
if (v >> 1) { n += 1; v >>= 1; }
|
|
return n+v;
|
|
}
|
|
|
|
static int
|
|
EXTSIGNED[] = { /*extsb*/1, /*extub*/0, /*extsh*/1, /*extuh*/0, /*extsw*/1, /*extuw*/0 };
|
|
|
|
static uint
|
|
EXTMAXW[] = { /*extsb*/7, /*extub*/8, /*extsh*/15, /*extuh*/16, /*extsw*/31, /*extuw*/32 };
|
|
|
|
static uint
|
|
EXTW[] = { /*extsb*/8, /*extub*/8, /*extsh*/16, /*extuh*/16, /*extsw*/32, /*extuw*/32 };
|
|
|
|
static uint
|
|
STW[] = { /*storeb*/8, /*storeh*/16, /*storew*/32, /*storel*/64, /*stores*/32, /*stored*/64 };
|
|
|
|
/* is the ref used only as a narrow value? */
|
|
static int
|
|
usewidthle(Fn *fn, Ref r, uint wbits)
|
|
{
|
|
Tmp *t;
|
|
Use *u;
|
|
Phi *p;
|
|
int b;
|
|
Ins *i;
|
|
Ref rc;
|
|
int64_t v;
|
|
|
|
if (isconbits(fn, r, &v))
|
|
if (u64_wbits(v) <= wbits)
|
|
return 1;
|
|
if (rtype(r) != RTmp)
|
|
return 0;
|
|
t = &fn->tmp[r.val];
|
|
for (u = t->use; u < &t->use[t->nuse]; u++) {
|
|
switch (u->type) {
|
|
case UPhi:
|
|
p = u->u.phi;
|
|
if (p->visit)
|
|
continue;
|
|
p->visit = 1;
|
|
b = usewidthle(fn, p->to, wbits);
|
|
p->visit = 0;
|
|
if (b)
|
|
continue;
|
|
break;
|
|
case UIns:
|
|
i = u->u.ins;
|
|
assert(i != 0);
|
|
if (i->op == Ocopy)
|
|
if (usewidthle(fn, i->to, wbits))
|
|
continue;
|
|
if (isext(i->op)) {
|
|
if (EXTW[i->op - Oextsb] <= wbits)
|
|
continue;
|
|
else
|
|
if (usewidthle(fn, i->to, wbits))
|
|
continue;;
|
|
}
|
|
if (i->op == Oand) {
|
|
if (req(r, i->arg[0]))
|
|
rc = i->arg[1];
|
|
else {
|
|
assert(req(r, i->arg[1]));
|
|
rc = i->arg[0];
|
|
}
|
|
if (isconbits(fn, rc, &v))
|
|
if (u64_wbits(v) <= wbits)
|
|
continue;
|
|
break;
|
|
}
|
|
if (isstore(i->op))
|
|
if (req(r, i->arg[1]))
|
|
if (STW[i->op - Ostoreb] > wbits)
|
|
continue;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static Phi*
|
|
findphi(Fn *fn, uint bid, Ref to)
|
|
{
|
|
Phi *p;
|
|
for (p = fn->rpo[bid]->phi; p; p = p->link)
|
|
if (req(p->to, to))
|
|
break;
|
|
assert(p);
|
|
return p;
|
|
}
|
|
|
|
static uint
|
|
uint_min(uint v1, uint v2)
|
|
{
|
|
return v1 < v2 ? v1 : v2;
|
|
}
|
|
|
|
/* is the ref def a narrow value? */
|
|
static int
|
|
defwidthle(Fn *fn, Ref r, uint wbits)
|
|
{
|
|
Tmp *t;
|
|
Phi *p;
|
|
Ins *i;
|
|
uint n;
|
|
int64_t v;
|
|
int x;
|
|
|
|
if (isconbits(fn, r, &v))
|
|
if (u64_wbits(v) <= wbits)
|
|
return 1;
|
|
if (rtype(r) != RTmp)
|
|
return 0;
|
|
t = &fn->tmp[r.val];
|
|
if (t->cls != Kw)
|
|
return 0;
|
|
i = t->def;
|
|
if (i == 0) {
|
|
/* phi def */
|
|
p = findphi(fn, t->bid, r);
|
|
if (p->visit)
|
|
return 1;
|
|
p->visit = 1;
|
|
for (n = 0; n < p->narg; n++)
|
|
if (!defwidthle(fn, p->arg[n], wbits)) {
|
|
p->visit = 0;
|
|
return 0;
|
|
}
|
|
p->visit = 0;
|
|
return 1;
|
|
}
|
|
/* ins def */
|
|
if (i->op == Ocopy)
|
|
return defwidthle(fn, i->arg[0], wbits);
|
|
if (i->op == Oshr || i->op == Osar) {
|
|
if (isconbits(fn, i->arg[1], &v))
|
|
if (0 < v && v <= 32) {
|
|
if (i->op == Oshr && 32-v <= wbits)
|
|
return 1;
|
|
if (0 <= v && v < 32 && wbits < 32)
|
|
return defwidthle(fn, i->arg[0], uint_min((i->op == Osar ? 31 : 32), wbits+v));
|
|
}
|
|
return defwidthle(fn, i->arg[0], wbits);
|
|
}
|
|
if (iscmp(i->op, &x, &x))
|
|
return wbits >= 1;
|
|
if (i->op == Oand)
|
|
return defwidthle(fn, i->arg[0], wbits) || defwidthle(fn, i->arg[1], wbits);
|
|
if (i->op == Oor || i->op == Oxor)
|
|
return defwidthle(fn, i->arg[0], wbits) && defwidthle(fn, i->arg[1], wbits);
|
|
if (isext(i->op)) {
|
|
if (EXTSIGNED[i->op - Oextsb])
|
|
return defwidthle(fn, i->arg[0], uint_min(wbits, EXTMAXW[i->op - Oextsb]));
|
|
if (EXTW[i->op - Oextsb] <= wbits)
|
|
return 1;
|
|
return defwidthle(fn, i->arg[0], wbits);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* is the ref a boolean - 0, 1 - value? */
|
|
int
|
|
iswu1(Fn *fn, Ref r)
|
|
{
|
|
return defwidthle(fn, r, 1);
|
|
}
|
|
|
|
static int
|
|
isnarrowpar(Fn *fn, Ref r)
|
|
{
|
|
Tmp *t;
|
|
|
|
if (rtype(r) != RTmp)
|
|
return 0;
|
|
t = &fn->tmp[r.val];
|
|
if (t->bid != fn->start->id || t->def == 0)
|
|
return 0;
|
|
return ispar(t->def->op);
|
|
}
|
|
|
|
/* Insert extub/extuh instructions in start for pars used only narrowly */
|
|
/* needs use; breaks use */
|
|
void
|
|
narrowpars(Fn *fn)
|
|
{
|
|
Blk *b;
|
|
int loop;
|
|
Ins *i, *ins;
|
|
uint npar, nins;
|
|
enum O extop;
|
|
Ref r;
|
|
|
|
/* only useful for functions with loops */
|
|
loop = 0;
|
|
for (b = fn->start; b; b = b->link)
|
|
if (b->loop > 1) {
|
|
loop = 1;
|
|
break;
|
|
}
|
|
if (!loop)
|
|
return;
|
|
|
|
b = fn->start;
|
|
npar = 0;
|
|
|
|
for (i = b->ins; i < &b->ins[b->nins]; i++) {
|
|
if (!ispar(i->op))
|
|
break;
|
|
npar++;
|
|
}
|
|
|
|
if (npar == 0)
|
|
return;
|
|
|
|
nins = b->nins + npar;
|
|
ins = vnew(nins, sizeof ins[0], PFn); //alloc(nins * sizeof ins[0]);
|
|
memcpy(ins, b->ins, npar * sizeof ins[0]);
|
|
memcpy(ins + 2*npar, b->ins + npar, (b->nins - npar) * sizeof ins[0]);
|
|
b->ins = ins;
|
|
b->nins = nins;
|
|
|
|
for (i = b->ins; i < &b->ins[b->nins]; i++) {
|
|
if (!ispar(i->op))
|
|
break;
|
|
extop = Onop;
|
|
if (i->cls == Kw)
|
|
if (usewidthle(fn, i->to, 16)) {
|
|
if (usewidthle(fn, i->to, 8))
|
|
extop = Oextub;
|
|
else
|
|
extop = Oextuh;
|
|
}
|
|
if (extop == Onop) {
|
|
*(i+npar) = (Ins) {.op = Onop};
|
|
} else {
|
|
r = newtmp("vw", i->cls, fn);
|
|
*(i+npar) = (Ins) {.op = extop, .cls = i->cls, .to = i->to, .arg = {r}};
|
|
i->to = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* used by GVN */
|
|
Ref
|
|
copyref(Fn *fn, Blk *b, Ins *i)
|
|
{
|
|
static bits extcpy[] = {
|
|
[WFull] = 0,
|
|
[Wsb] = BIT(Wsb) | BIT(Wsh) | BIT(Wsw),
|
|
[Wub] = BIT(Wub) | BIT(Wuh) | BIT(Wuw),
|
|
[Wsh] = BIT(Wsh) | BIT(Wsw),
|
|
[Wuh] = BIT(Wuh) | BIT(Wuw),
|
|
[Wsw] = BIT(Wsw),
|
|
[Wuw] = BIT(Wuw),
|
|
};
|
|
bits bext;
|
|
Tmp *t;
|
|
int64_t v;
|
|
int is0;
|
|
|
|
if (i->op == Ocopy)
|
|
return i->arg[0];
|
|
|
|
/* op identity value */
|
|
if (optab[i->op].hasid)
|
|
if (KBASE(i->cls) == 0) /* integer only - fp NaN! */
|
|
if (req(i->arg[1], con01[optab[i->op].idval]))
|
|
if (!optab[i->op].cmpeqwl || iswu1(fn, i->arg[0]))
|
|
return i->arg[0];
|
|
|
|
/* idempotent op with identical args */
|
|
if (optab[i->op].idemp)
|
|
if (req(i->arg[0], i->arg[1]))
|
|
return i->arg[0];
|
|
|
|
/* integer cmp with identical args */
|
|
if (optab[i->op].cmpeqwl || optab[i->op].cmplgtewl)
|
|
if (req(i->arg[0], i->arg[1]))
|
|
return con01[optab[i->op].eqval];
|
|
|
|
/* cmpeq/ne 0 with 0/non-0 inference from dominating jnz */
|
|
if (optab[i->op].cmpeqwl)
|
|
if (req(i->arg[1], con01[0]))
|
|
if (is0non0(fn, b, i->arg[0], argcls(i,0), &is0))
|
|
return con01[optab[i->op].eqval^is0^1];
|
|
|
|
/* redundant and mask */
|
|
if (i->op == Oand)
|
|
if (isconbits(fn, i->arg[1], &v))
|
|
if (((v+1) & v) == 0) /* v == 2^N-1 */
|
|
if (defwidthle(fn, i->arg[0], u64_wbits(v)))
|
|
return i->arg[0];
|
|
|
|
if (!isext(i->op) || rtype(i->arg[0]) != RTmp)
|
|
return R;
|
|
if (i->op == Oextsw || i->op == Oextuw)
|
|
if (i->cls == Kw)
|
|
return i->arg[0];
|
|
|
|
t = &fn->tmp[i->arg[0].val];
|
|
assert(KBASE(t->cls) == 0);
|
|
if (i->cls == Kl && t->cls == Kw)
|
|
return R;
|
|
bext = extcpy[t->width];
|
|
if ((BIT(Wsb + (i->op-Oextsb)) & bext) != 0)
|
|
return i->arg[0];
|
|
|
|
if (!isnarrowpar(fn, i->arg[0]))
|
|
if (usewidthle(fn, i->to, EXTW[i->op - Oextsb]))
|
|
return i->arg[0];
|
|
if (defwidthle(fn, i->arg[0], EXTMAXW[i->op - Oextsb]))
|
|
return i->arg[0];
|
|
|
|
return R;
|
|
}
|