home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Crawly Crypt Collection 1
/
crawlyvol1.bin
/
program
/
compiler
/
sozobon
/
scsrc20
/
top
/
reg.c
< prev
next >
Wrap
C/C++ Source or Header
|
1991-02-22
|
19KB
|
835 lines
/* Copyright (c) 1989,1991 by Sozobon, Limited. Author: Tony Andrews
*
* Permission is granted to anyone to use this software for any purpose
* on any computer system, and to redistribute it freely, with the
* following restrictions:
* 1) No charge may be made other than reasonable charges for reproduction.
* 2) Modified versions must be clearly marked as such.
* 3) The authors are not responsible for any harmful consequences
* of using this software, even if they result from defects in it.
*/
/*
* The code in this file deals with "registerizing" local variables and
* parameters. The general idea is to look for highly referenced local
* variables and parameters and effectively turn them into register
* variables automatically. Only the D registers are used, currently, so
* for pointer variables, a manual "register" declaration in the source
* code is actually better.
*
* We need to be certain of several things about a variable before placing
* it in a register. It's address must not be taken, and it must not be
* referred to through "aliases" (e.g. when casting to a shorter object).
* It must be able to fit in a register. And to keep things like printf from
* breaking, parameters can only be registerized if none of the parameters
* have their address taken.
*
* The compiler makes this all possible by placing "hints" within the
* generated assembly code. These hints appear as comments, but are parsed
* by the optimizer, and the information is stashed away by calling addvar().
* The hints give us the size and offset of each parameter and local variable.
* Their names are also given, although that information isn't needed here.
*
* There are tradeoffs to be wary of when registerizing. If no register
* variables exist yet, then "movem" instructions have to be added, requiring
* more references to make this worthwhile. In the case of parameters, the
* register has to be initialized from the stack. The four cases are:
*
* Locals w/ other regs: 1 reference required
* no other regs: 4 references required
* Parms w/ other regs: 2 references required
* no other regs: 6 references required
*
* The numbers above represent the break-even point based on a savings of
* 2 bytes per reference, and the incremental cost of adding "movem" or
* "move" instructions as needed.
*
* This optimizes for space only. To optimize for time, each reference would
* be weighted based on the loop nesting level at which it occurs.
*/
#include "top.h"
#define MAXLOCALS 100
static struct linfo {
long offset; /* offset from A6 */
int size; /* size of the object */
int ref; /* # of references to the local */
int reg; /* reg. we assigned it to */
int flags; /* length, etc. */
} locals[MAXLOCALS];
#define ALIASED 0x1 /* offset is aliased with another */
#define ADDR_TAKEN 0x2 /* address of the variable was taken */
#define IS_LOCAL(x) (locals[(x)].offset < 0)
#define IS_PARM(x) (locals[(x)].offset > 0)
static bool paddr; /* address of a parameter was taken */
static int lcnt; /* number of local variables we've seen */
static int rcnt; /* number of locals that got registerized */
static int omask, nmask; /* old and new register masks */
/*
* addvar(size, off) - add a variable entry for the current function
*
* These come from hints the compiler gives us about local variables.
* We use the size and offset here to make sure we don't have aliasing
* problems with the local variables we want to registerize.
*/
void
addvar(size, off)
int size;
int off;
{
locals[lcnt].offset = off;
locals[lcnt].size = size;
locals[lcnt].flags = 0;
locals[lcnt].ref = 0;
lcnt++;
}
/*
* clrvar() - clear the variable list
*/
void
clrvar()
{
register int i;
/*
* re-initialize the local information
*/
for (i=0; i < MAXLOCALS ;i++) {
locals[i].ref = 0;
locals[i].reg = -1;
locals[i].flags = 0;
locals[i].offset = 0;
locals[i].size = 0;
}
paddr = FALSE;
rcnt = lcnt = 0;
}
/*
* setreg() - try to "registerize" local variables in the given function
*/
void
setreg(bp)
BLOCK *bp;
{
void lcheck(), lassign(), lrewrite();
lcheck(bp);
lassign();
#ifdef DEBUG
if (debug)
dump_table();
#endif
if (rcnt > 0)
lrewrite(bp);
s_reg += rcnt; /* keep totals for accounting */
}
/*
* lcheck() - scan for local variable references in the given function
*/
static void
lcheck(bp)
BLOCK *bp;
{
void ckref();
register int i;
register BLOCK *cb;
register INST *ci;
for (cb = bp; cb != NULL ;cb = cb->next) {
for (ci = cb->first; ci != NULL ;ci = ci->next) {
ckref(ci, &ci->src);
ckref(ci, &ci->dst);
}
}
/*
* Now figure out which registers are currently used.
*/
ci = bp->first->next;
if (ci != NULL && ci->opcode == MOVEM) {
if (ci->src.amode == REG)
omask = RM(ci->src.areg);
else
omask = stomask(ci->src.astr);
} else
omask = 0;
}
/*
* ckref() - check for a local variable reference
*
* If a local variable reference is found, it's added to the table or
* (if already there) its reference count is incremented. If we're
* taking its address, note that too.
*/
static void
ckref(ip, op)
INST *ip;
struct opnd *op;
{
register int i;
register int sz;
if (op->amode != REGID || op->areg != A6)
return;
switch (ip->flags) {
case LENL:
sz = 4;
break;
case LENW:
sz = 2;
break;
case LENB:
default: /* for LEA and PEA */
sz = 1;
break;
}
/*
* is the local variable already in the table?
*/
for (i=0; i < lcnt ;i++) {
if (locals[i].offset == op->disp && locals[i].size == sz) {
locals[i].ref++;
break;
}
}
/*
* If not in the table, add an entry for it. If we add an entry
* here, it must be an alias for one of the entries we got via
* the compiler hints.
*/
if (i == lcnt) {
locals[lcnt].offset = op->disp;
locals[lcnt].size = sz;
locals[lcnt].flags = 0;
locals[lcnt].ref = 1;
lcnt++;
}
if (ip->opcode == LEA || ip->opcode == PEA) {
locals[i].flags = ADDR_TAKEN;
/*
* If we took the address of a parameter, note that
* by setting 'paddr'.
*/
if (IS_PARM(i))
paddr = TRUE;
}
}
/*
* lassign() - assign local variable to registers
*
* Check for aliases, sort the table, and then decide how to assign
* the local variables to registers.
*/
static void
lassign()
{
void ck_aliases(), sort_table(), do_sort();
register int i;
register int r;
int minlref; /* min. required references for a local */
int minpref; /* min. required references for a parameter */
ck_aliases(); /* disqualify any "aliased" references */
sort_table(); /* and sort by reference count */
/*
* If there were already "movem" instructions, then we should
* convert as many locals as possible to registers. If we're
* going to have to add the movem's, then we need at least 4
* references for this to be worthwhile. The 2 movem instructions
* take 8 bytes, and each reference conversion saves 2 bytes.
* This analysis optimizes for size.
*/
minlref = (omask != 0) ? 1 : 4;
minpref = (omask != 0) ? 2 : 6;
nmask = omask;
for (i=0, r=D3; r <= D7 ;) {
/*
* If the register is already in use, skip it.
*/
if (omask & RM(r)) {
r++;
continue;
}
/*
* If no more eligible variables, then stop.
*/
if (locals[i].ref <= 0)
break;
/*
* If something meets the minimums, then assign it to
* the current register, and adjust the minimums.
*/
if ((IS_LOCAL(i) && locals[i].ref >= minlref) ||
(IS_PARM(i) && locals[i].ref >= minpref)) {
locals[i].reg = r;
nmask |= RM(r);
minlref = 1;
minpref = 2;
r++;
i++;
} else {
/*
* If we run into something that isn't referenced
* enough, disqualify it and re-sort. There might
* still be something else worth doing.
*/
locals[i].ref = -locals[i].ref;
do_sort();
}
}
rcnt = i;
}
/*
* ck_aliases() - check for aliases in the locals table
*
* An alias occurs when two different offsets off of A6 both reference
* the same local. This can happen when casting to a smaller type. Since
* these references would be a pain to rewrite, we just bag it.
*/
static void
ck_aliases()
{
static bool ck_aref();
register int i;
register int s;
register long d;
for (i=0; i < lcnt ;i++) {
d = locals[i].offset;
s = locals[i].size;
if (ck_aref(d, s))
locals[i].flags |= ALIASED;
}
}
/*
* ck_aref() - check for an aliased reference
*/
static bool
ck_aref(d, len)
register long d;
register int len;
{
register int i;
for (i=0; i < lcnt ;i++) {
if (locals[i].offset == d && locals[i].size == len)
continue;
if (overlaps(d, len, locals[i].offset, locals[i].size)) {
locals[i].flags |= ALIASED;
return TRUE;
}
}
return FALSE;
}
static bool
overlaps(d1, s1, d2, s2)
register long d1, d2;
int s1, s2;
{
register long e1, e2;
e1 = d1 + s1 - 1;
e2 = d2 + s2 - 1;
if (d1 >= d2 && d1 <= e2) /* d1 inside d2 <=> e2 */
return TRUE;
if (e1 >= d2 && e1 <= e2) /* e1 inside d2 <=> e2 */
return TRUE;
return FALSE;
}
static void
sort_table()
{
register int i;
/*
* Remove uninteresting references from consideration:
*
* 1. Variables whose address was taken, or are aliased with another.
* 2. Variables that don't fit in a register.
*/
for (i=0; i < lcnt ;i++) {
if (locals[i].flags&(ADDR_TAKEN|ALIASED) || locals[i].size > 4)
locals[i].ref = -locals[i].ref;
}
/*
* If paddr is set, remove any parameters from consideration. We
* have to do this so that things like printf (that take the address
* of a parameter and increment it) don't break. Only if no parameter
* addresses are taken, can we consider registerizing any of them.
*/
if (paddr) {
for (i=0; i < lcnt ;i++) {
if (IS_PARM(i) && (locals[i].ref > 0))
locals[i].ref = -locals[i].ref;
}
}
do_sort();
}
static void
do_sort()
{
register int i;
struct linfo l;
/*
* simple bubble sort
*/
for (i=0; i < (lcnt-1) ;) {
if (locals[i].ref < locals[i+1].ref) {
l = locals[i];
locals[i] = locals[i+1];
locals[i+1] = l;
if (i > 0)
i--;
} else
i++;
}
}
/*
* lrewrite() - rewrite the function based on the new register assignments
*
* Fixing the references is easy, but we have to fix up (or add) the movem
* instructions as well. Also, we call addinits() to initialize any registers
* that will contain parameters.
*/
static void
lrewrite(bp)
BLOCK *bp;
{
void fixref(), fixmove(), addmovem();
INST *findlnk();
register int i;
register BLOCK *cb;
register INST *ci;
/*
* First, rewrite all the references to the locals that
* we've reassigned to registers.
*/
for (cb = bp; cb != NULL ;cb = cb->next) {
for (ci = cb->first; ci != NULL ;ci = ci->next) {
fixref(&ci->src);
fixref(&ci->dst);
}
}
/*
* If the movem's are there, just find them and fix up the
* register specs.
*/
ci = bp->first->next;
if (ci != NULL && ci->opcode == MOVEM) {
/*
* First, add the initialization instructions.
*/
addinits(bp, bp->first->next);
fixmove(&ci->src);
for (cb = bp; cb != NULL ;cb = cb->next) {
if (cb->flags & B_RET) {
for (ci=cb->last; ci != NULL ;ci=ci->prev) {
if (ci->opcode == MOVEM) {
fixmove(&ci->dst);
return;
}
}
}
}
return;
}
#ifdef DEBUG
if (debug)
printf("adding movem instructions\n");
#endif
/*
* There aren't any movem instructions, so we have to add
* them here. What a pain...
*/
addmovem(bp, findlnk(bp), TRUE);
addinits(bp, findlnk(bp)->next);
for (cb = bp; cb != NULL ;cb = cb->next) {
if (cb->last->opcode == RTS) {
for (ci=cb->last; ci != NULL ;ci=ci->prev) {
if (ci->opcode == UNLK) {
addmovem(cb, ci, FALSE);
return;
}
}
}
}
/*
* Reaching this point would be an error, you'd think. It means
* we didn't find the exit from this function. Strangely enough,
* this can actually happen in routines with infinite loops.
* Since the "return" block isn't reachable, branch optimization
* actually removes it. So we can't consider it an error here if
* we don't find any "unlk" instruction.
*/
}
static void
fixmove(op)
struct opnd *op;
{
char *masktos();
freeop(op);
op->amode = ABS;
op->astr = strsave(masktos(nmask));
}
/*
* findlnk() - find the LINK instruction in the given block
*
* When profiling, the LINK isn't the first instruction in the entry
* block. This function lets us handle both cases cleanly.
*/
static INST *
findlnk(bp)
BLOCK *bp;
{
INST *ip;
for (ip=bp->first; ip != NULL ;ip = ip->next) {
if (ip->opcode == LINK)
return ip;
}
return NULL;
}
static void
addmovem(bp, ip, is_entry)
BLOCK *bp; /* block where we're working */
INST *ip; /* instruction before or after the movem */
bool is_entry; /* true if we're doing the entry code */
{
char *masktos();
register INST *ni;
struct opnd *op;
register int i;
if (ip == NULL) /* no LINK found */
return;
/*
* Allocate and initialize a new instruction
*/
ni = (INST *) alloc(sizeof(INST));
ni->flags = LENL;
ni->opcode = MOVEM;
ni->live = 0;
ni->rref = ni->rset = 0;
ni->src.areg = ni->dst.areg = 0;
ni->src.ireg = ni->dst.ireg = 0;
ni->src.disp = ni->dst.disp = 0;
/*
* Set up the SP reference
*/
op = (is_entry) ? &ni->dst : &ni->src;
op->amode = (is_entry) ? REGI|DEC : REGI|INC;
op->areg = SP;
/*
* Set up the register spec operand
*/
op = (is_entry) ? &ni->src : &ni->dst;
op->amode = ABS;
op->astr = strsave(masktos(nmask));
/*
* If there's only one register being used, we should really
* change the operand to be register direct. This way, the
* peephole optimization will turn the "movem" into a simple
* "move". Since we're adding an instruction here, we really
* need to make it as painless as possible.
*/
if (rcnt == 1) {
free(op->astr);
op->amode = REG;
for (i=D0; i <= D7 ;i++) {
if (nmask & RM(i)) {
op->areg = i;
break;
}
}
}
/*
* Link the instruction into the block
*/
if (is_entry) {
ni->next = ip->next; /* link the MOVEM to its neighbors */
ni->prev = ip;
ip->next = ni; /* link its neighbors to the MOVEM */
if (bp->last == ip)
bp->last = ni;
else
ni->next->prev = ni;
} else {
ni->next = ip; /* link the MOVEM to its neighbors */
ni->prev = ip->prev;
ip->prev = ni; /* link its neighbors to the MOVEM */
if (bp->first == ip)
bp->first = ni;
else
ni->prev->next = ni;
}
}
static void
addinits(bp, ip)
BLOCK *bp; /* block where we're working */
INST *ip; /* instruction before the moves */
{
char *masktos();
register INST *ni;
struct opnd *op;
register int i;
if (ip == NULL) /* no LINK found */
return;
for (i=0; i < rcnt ;i++) {
/*
* If it's a local variable, we don't have to do anything.
*/
if (IS_LOCAL(i))
continue;
/*
* Allocate and initialize a new instruction
*/
ni = (INST *) alloc(sizeof(INST));
switch (locals[i].size) {
case 1:
ni->flags = LENB;
break;
case 2:
ni->flags = LENW;
break;
case 4:
ni->flags = LENL;
break;
default:
fprintf(stderr, "Invalid length\n");
exit(1);
}
ni->opcode = MOVE;
ni->live = 0;
ni->rref = ni->rset = 0;
ni->src.ireg = ni->dst.ireg = 0;
/*
* Set up the variable reference.
*/
ni->src.amode = REGID;
ni->src.areg = A6;
ni->src.disp = locals[i].offset;
/*
* Set up the register spec operand
*/
ni->dst.amode = REG;
ni->dst.areg = locals[i].reg;
ni->dst.disp = 0;
/*
* Link the instruction into the block
*/
ni->next = ip->next; /* link MOVE to its neighbors */
ni->prev = ip;
ip->next = ni; /* link neighbors to the MOVE */
if (bp->last == ip)
bp->last = ni;
else
ni->next->prev = ni;
}
}
static void
fixref(op)
struct opnd *op;
{
register int i;
if (op->amode != REGID || op->areg != A6)
return;
/*
* Does the reference need to be changed?
*/
for (i=0; i < rcnt ;i++) {
if (locals[i].offset == op->disp) {
op->amode = REG;
op->areg = locals[i].reg;
return;
}
}
}
/*
* stomask() - convert a register list to a mask
*
* Convert a string like "Rm-Rn/Ro-Rp" or "Rm-Rn" to the appropriate
* mask value.
*/
static int
stomask(s)
char *s;
{
register int mask;
register char *p;
mask = dorspec(s);
for (p=s; *p && *p != '/' ;p++)
;
if (*p == '/')
mask |= dorspec(p+1);
return mask;
}
/*
* dorspec() - convert a partial register spec
*
* Convert a string like "Rm" or "Rm-Rn" to a mask.
*/
static int
dorspec(s)
register char *s;
{
register int base;
register int m, n;
register int mask;
base = (s[0] == 'd') ? D0 : A0;
m = s[1] - '0' + base;
if (s[2] != '-')
return RM(m);
n = s[4] - '0' + base;
for (mask=0; m <= n ;m++)
mask |= RM(m);
return mask;
}
/*
* masktos() - convert a register mask to a descriptive string
*
* Generates a string of the form "Rm/Rn/Ro/..."
*/
static char *
masktos(mask)
register int mask;
{
static char buf[64];
register char *p = buf;
register int r;
for (r = D0; r <= D7 ;r++) {
if (mask & RM(r)) {
if (p != buf)
*p++ = '/';
*p++ = 'd';
*p++ = (r - D0) + '0';
}
}
for (r = A0; r <= A7 ;r++) {
if (mask & RM(r)) {
if (p != buf)
*p++ = '/';
*p++ = 'a';
*p++ = (r - A0) + '0';
}
}
*p = '\0';
return buf;
}
#ifdef DEBUG
dump_table()
{
register int i;
printf("%d local variables and parameters found\n", lcnt);
for (i=0; i < lcnt ;i++) {
printf("len = %d\n", locals[i].size);
printf("%02d: disp=%3ld, len=%d ref=%2d reg=%s",
i, locals[i].offset,
locals[i].size,
locals[i].ref,
locals[i].reg >= 0 ? masktos(RM(locals[i].reg)) : "-");
if (locals[i].flags & ADDR_TAKEN)
printf(" ADDR_TAKEN");
if (locals[i].flags & ALIASED)
printf(" ALIASED");
printf("\n");
}
}
#endif