plan9fox/sys/src/cmd/vl/sched.c
cinap_lenrek ebf85fb7b7 vl: prevent stack altering instructions to be hoisted above loads
fixes bug with libmemdraw where the linker would hoist
the final ADD $const, SP at the end over loads from the
stack causing the front to fall off once a interrupt hits.
2015-04-01 01:30:16 +02:00

695 lines
11 KiB
C

#include "l.h"
enum
{
E_HILO = 1<<0,
E_FCR = 1<<1,
E_MCR = 1<<2,
E_MEM = 1<<3,
E_MEMSP = 1<<4, /* uses offset and size */
E_MEMSB = 1<<5, /* uses offset and size */
ANYMEM = E_MEM|E_MEMSP|E_MEMSB,
DELAY = BRANCH|LOAD|FCMP,
};
typedef struct Sch Sch;
typedef struct Dep Dep;
struct Dep
{
ulong ireg;
ulong freg;
ulong cc;
};
struct Sch
{
Prog p;
Dep set;
Dep used;
long soffset;
char size;
char nop;
char comp;
};
void regsused(Sch*, Prog*);
int depend(Sch*, Sch*);
int conflict(Sch*, Sch*);
int offoverlap(Sch*, Sch*);
void dumpbits(Sch*, Dep*);
void
sched(Prog *p0, Prog *pe)
{
Prog *p, *q;
Sch sch[NSCHED], *s, *t, *u, *se, stmp;
/*
* build side structure
*/
s = sch;
for(p=p0;; p=p->link) {
memset(s, 0, sizeof(*s));
s->p = *p;
regsused(s, p);
if(debug['X']) {
Bprint(&bso, "%P\t\tset", &s->p);
dumpbits(s, &s->set);
Bprint(&bso, "; used");
dumpbits(s, &s->used);
if(s->comp)
Bprint(&bso, "; compound");
if(s->p.mark & LOAD)
Bprint(&bso, "; load");
if(s->p.mark & BRANCH)
Bprint(&bso, "; branch");
if(s->p.mark & FCMP)
Bprint(&bso, "; fcmp");
Bprint(&bso, "\n");
}
if(p == pe)
break;
s++;
}
se = s;
/*
* prepass to move things around
* does nothing, but tries to make
* the actual scheduler work better
*/
for(s=sch; s<=se; s++) {
if(!(s->p.mark & LOAD))
continue;
/* always good to put nonconflict loads together */
for(t=s+1; t<=se; t++) {
if(!(t->p.mark & LOAD))
continue;
if(t->p.mark & BRANCH || t->set.ireg & (1<<REGSP))
break;
if(conflict(s, t))
break;
for(u=t-1; u>s; u--)
if(depend(u, t))
goto no11;
u = s+1;
stmp = *t;
memmove(s+2, u, (uchar*)t - (uchar*)u);
*u = stmp;
break;
}
no11:
/* put schedule fodder above load */
for(t=s+1; t<=se; t++) {
if(t->p.mark & BRANCH || t->set.ireg & (1<<REGSP))
break;
if(s > sch && conflict(s-1, t))
continue;
for(u=t-1; u>=s; u--)
if(depend(t, u))
goto no1;
stmp = *t;
memmove(s+1, s, (uchar*)t - (uchar*)s);
*s = stmp;
if(!(s->p.mark & LOAD))
break;
no1:;
}
}
for(s=se; s>=sch; s--) {
if(!(s->p.mark & DELAY))
continue;
if(s < se)
if(!conflict(s, s+1))
goto out3;
/*
* s is load, s+1 is immediate use of result or end of block
* t is the trial instruction to insert between s and s+1
*/
if(!debug['Y'])
for(t=s-1; t>=sch; t--) {
if(t->comp)
if(s->p.mark & BRANCH)
goto no2;
if(t->p.mark & DELAY)
if(s >= se || conflict(t, s+1))
goto no2;
for(u=t+1; u<=s; u++)
if(depend(u, t))
goto no2;
goto out2;
no2:;
}
if(debug['X'])
Bprint(&bso, "?l%P\n", &s->p);
s->nop = 1;
if(debug['v']) {
if(s->p.mark & LOAD) {
nop.load.count++;
nop.load.outof++;
}
if(s->p.mark & BRANCH) {
nop.branch.count++;
nop.branch.outof++;
}
if(s->p.mark & FCMP) {
nop.fcmp.count++;
nop.fcmp.outof++;
}
}
continue;
out2:
if(debug['X']) {
Bprint(&bso, "!l%P\n", &t->p);
Bprint(&bso, "%P\n", &s->p);
}
stmp = *t;
memmove(t, t+1, (uchar*)s - (uchar*)t);
*s = stmp;
s--;
out3:
if(debug['v']) {
if(s->p.mark & LOAD)
nop.load.outof++;
if(s->p.mark & BRANCH)
nop.branch.outof++;
if(s->p.mark & FCMP)
nop.fcmp.outof++;
}
}
/* Avoid HI/LO use->set */
t = sch+1;
for(s=sch; s<se-1; s++, t++) {
if((s->used.cc & E_HILO) == 0)
continue;
if(t->set.cc & E_HILO)
s->nop = 2;
}
/*
* put it all back
*/
for(s=sch, p=p0; s<=se; s++, p=q) {
q = p->link;
if(q != s->p.link) {
*p = s->p;
p->link = q;
}
while(s->nop--)
addnop(p);
}
if(debug['X']) {
Bprint(&bso, "\n");
Bflush(&bso);
}
}
void
regsused(Sch *s, Prog *realp)
{
int c, ar, ad, ld, sz;
ulong m;
Prog *p;
p = &s->p;
s->comp = compound(p);
s->nop = 0;
if(s->comp) {
s->set.ireg |= 1<<REGTMP;
s->used.ireg |= 1<<REGTMP;
}
ar = 0; /* dest is really reference */
ad = 0; /* source/dest is really address */
ld = 0; /* opcode is load instruction */
sz = 20; /* size of load/store for overlap computation */
/*
* flags based on opcode
*/
switch(p->as) {
case ATEXT:
curtext = realp;
autosize = p->to.offset + 4;
ad = 1;
break;
case AJAL:
c = p->reg;
if(c == NREG)
c = REGLINK;
s->set.ireg |= 1<<c;
ar = 1;
ad = 1;
break;
case ABGEZAL:
case ABLTZAL:
s->set.ireg |= 1<<REGLINK;
case ABEQ:
case ABGEZ:
case ABGTZ:
case ABLEZ:
case ABLTZ:
case ABNE:
ar = 1;
ad = 1;
break;
case ABFPT:
case ABFPF:
ad = 1;
s->used.cc |= E_FCR;
break;
case ACMPEQD:
case ACMPEQF:
case ACMPGED:
case ACMPGEF:
case ACMPGTD:
case ACMPGTF:
ar = 1;
s->set.cc |= E_FCR;
p->mark |= FCMP;
break;
case AJMP:
ar = 1;
ad = 1;
break;
case AMOVB:
case AMOVBU:
sz = 1;
ld = 1;
break;
case AMOVH:
case AMOVHU:
sz = 2;
ld = 1;
break;
case AMOVF:
case AMOVW:
case AMOVWL:
case AMOVWR:
sz = 4;
ld = 1;
break;
case AMOVD:
case AMOVV:
case AMOVVL:
case AMOVVR:
sz = 8;
ld = 1;
break;
case ADIV:
case ADIVU:
case AMUL:
case AMULU:
case AREM:
case AREMU:
s->set.cc = E_HILO;
case AADD:
case AADDU:
case AAND:
case ANOR:
case AOR:
case ASGT:
case ASGTU:
case ASLL:
case ASRA:
case ASRL:
case ASUB:
case ASUBU:
case AXOR:
case AADDD:
case AADDF:
case AADDW:
case ASUBD:
case ASUBF:
case ASUBW:
case AMULF:
case AMULD:
case AMULW:
case ADIVF:
case ADIVD:
case ADIVW:
if(p->reg == NREG) {
if(p->to.type == D_REG || p->to.type == D_FREG)
p->reg = p->to.reg;
if(p->reg == NREG)
print("botch %P\n", p);
}
break;
}
/*
* flags based on 'to' field
*/
c = p->to.class;
if(c == 0) {
c = aclass(&p->to) + 1;
p->to.class = c;
}
c--;
switch(c) {
default:
print("unknown class %d %D\n", c, &p->to);
case C_ZCON:
case C_SCON:
case C_ADD0CON:
case C_AND0CON:
case C_ADDCON:
case C_ANDCON:
case C_UCON:
case C_LCON:
case C_NONE:
case C_SBRA:
case C_LBRA:
break;
case C_HI:
case C_LO:
s->set.cc |= E_HILO;
break;
case C_FCREG:
s->set.cc |= E_FCR;
break;
case C_MREG:
s->set.cc |= E_MCR;
break;
case C_ZOREG:
case C_SOREG:
case C_LOREG:
c = p->to.reg;
s->used.ireg |= 1<<c;
if(ad)
break;
s->size = sz;
s->soffset = regoff(&p->to);
m = ANYMEM;
if(c == REGSB)
m = E_MEMSB;
if(c == REGSP)
m = E_MEMSP;
if(ar)
s->used.cc |= m;
else
s->set.cc |= m;
break;
case C_SACON:
case C_LACON:
s->used.ireg |= 1<<REGSP;
break;
case C_SECON:
case C_LECON:
s->used.ireg |= 1<<REGSB;
break;
case C_REG:
if(ar)
s->used.ireg |= 1<<p->to.reg;
else
s->set.ireg |= 1<<p->to.reg;
break;
case C_FREG:
/* do better -- determine double prec */
if(ar) {
s->used.freg |= 1<<p->to.reg;
s->used.freg |= 1<<(p->to.reg|1);
} else {
s->set.freg |= 1<<p->to.reg;
s->set.freg |= 1<<(p->to.reg|1);
}
if(ld && p->from.type == D_REG)
p->mark |= LOAD;
break;
case C_SAUTO:
case C_LAUTO:
s->used.ireg |= 1<<REGSP;
if(ad)
break;
s->size = sz;
s->soffset = regoff(&p->to);
if(ar)
s->used.cc |= E_MEMSP;
else
s->set.cc |= E_MEMSP;
break;
case C_SEXT:
case C_LEXT:
s->used.ireg |= 1<<REGSB;
if(ad)
break;
s->size = sz;
s->soffset = regoff(&p->to);
if(ar)
s->used.cc |= E_MEMSB;
else
s->set.cc |= E_MEMSB;
break;
}
/*
* flags based on 'from' field
*/
c = p->from.class;
if(c == 0) {
c = aclass(&p->from) + 1;
p->from.class = c;
}
c--;
switch(c) {
default:
print("unknown class %d %D\n", c, &p->from);
case C_ZCON:
case C_SCON:
case C_ADD0CON:
case C_AND0CON:
case C_ADDCON:
case C_ANDCON:
case C_UCON:
case C_LCON:
case C_NONE:
case C_SBRA:
case C_LBRA:
break;
case C_HI:
case C_LO:
s->used.cc |= E_HILO;
break;
case C_FCREG:
s->used.cc |= E_FCR;
break;
case C_MREG:
s->used.cc |= E_MCR;
break;
case C_ZOREG:
case C_SOREG:
case C_LOREG:
c = p->from.reg;
s->used.ireg |= 1<<c;
if(ld)
p->mark |= LOAD;
s->size = sz;
s->soffset = regoff(&p->from);
m = ANYMEM;
if(c == REGSB)
m = E_MEMSB;
if(c == REGSP)
m = E_MEMSP;
s->used.cc |= m;
break;
case C_SACON:
case C_LACON:
s->used.ireg |= 1<<REGSP;
break;
case C_SECON:
case C_LECON:
s->used.ireg |= 1<<REGSB;
break;
case C_REG:
s->used.ireg |= 1<<p->from.reg;
break;
case C_FREG:
/* do better -- determine double prec */
s->used.freg |= 1<<p->from.reg;
s->used.freg |= 1<<(p->from.reg|1);
if(ld && p->to.type == D_REG)
p->mark |= LOAD;
break;
case C_SAUTO:
case C_LAUTO:
s->used.ireg |= 1<<REGSP;
if(ld)
p->mark |= LOAD;
if(ad)
break;
s->size = sz;
s->soffset = regoff(&p->from);
s->used.cc |= E_MEMSP;
break;
case C_SEXT:
case C_LEXT:
s->used.ireg |= 1<<REGSB;
if(ld)
p->mark |= LOAD;
if(ad)
break;
s->size = sz;
s->soffset = regoff(&p->from);
s->used.cc |= E_MEMSB;
break;
}
c = p->reg;
if(c != NREG) {
if(p->from.type == D_FREG || p->to.type == D_FREG) {
s->used.freg |= 1<<c;
s->used.freg |= 1<<(c|1);
} else
s->used.ireg |= 1<<c;
}
s->set.ireg &= ~(1<<REGZERO); /* R0 cant be set */
}
/*
* test to see if 2 instrictions can be
* interchanged without changing semantics
*/
int
depend(Sch *sa, Sch *sb)
{
ulong x;
if(sa->set.ireg & (sb->set.ireg|sb->used.ireg))
return 1;
if(sb->set.ireg & sa->used.ireg)
return 1;
if(sa->set.freg & (sb->set.freg|sb->used.freg))
return 1;
if(sb->set.freg & sa->used.freg)
return 1;
/*
* special case.
* loads from same address cannot pass.
* this is for hardware fifo's and the like
*/
if(sa->used.cc & sb->used.cc & E_MEM)
if(sa->p.reg == sb->p.reg)
if(regoff(&sa->p.from) == regoff(&sb->p.from))
return 1;
x = (sa->set.cc & (sb->set.cc|sb->used.cc)) |
(sb->set.cc & sa->used.cc);
if(x) {
/*
* allow SB and SP to pass each other.
* allow SB to pass SB iff doffsets are ok
* anything else conflicts
*/
if(x != E_MEMSP && x != E_MEMSB)
return 1;
x = sa->set.cc | sb->set.cc |
sa->used.cc | sb->used.cc;
if(x & E_MEM)
return 1;
if(offoverlap(sa, sb))
return 1;
}
return 0;
}
int
offoverlap(Sch *sa, Sch *sb)
{
if(sa->soffset < sb->soffset) {
if(sa->soffset+sa->size > sb->soffset)
return 1;
return 0;
}
if(sb->soffset+sb->size > sa->soffset)
return 1;
return 0;
}
/*
* test 2 adjacent instructions
* and find out if inserted instructions
* are desired to prevent stalls.
*/
int
conflict(Sch *sa, Sch *sb)
{
if(sa->set.ireg & sb->used.ireg)
return 1;
if(sa->set.freg & sb->used.freg)
return 1;
if(sa->set.cc & sb->used.cc)
return 1;
return 0;
}
int
compound(Prog *p)
{
Optab *o;
o = oplook(p);
if(o->size != 4)
return 1;
if(p->to.type == D_REG && p->to.reg == REGSB)
return 1;
return 0;
}
void
dumpbits(Sch *s, Dep *d)
{
int i;
for(i=0; i<32; i++)
if(d->ireg & (1<<i))
Bprint(&bso, " R%d", i);
for(i=0; i<32; i++)
if(d->freg & (1<<i))
Bprint(&bso, " F%d", i);
for(i=0; i<32; i++)
switch(d->cc & (1<<i)) {
default:
break;
case E_HILO:
Bprint(&bso, " HILO");
break;
case E_FCR:
Bprint(&bso, " FCR");
break;
case E_MCR:
Bprint(&bso, " MCR");
break;
case E_MEM:
Bprint(&bso, " MEM%d", s->size);
break;
case E_MEMSB:
Bprint(&bso, " SB%d", s->size);
break;
case E_MEMSP:
Bprint(&bso, " SP%d", s->size);
break;
}
}