home *** CD-ROM | disk | FTP | other *** search
- /* Copyright (c) 1989 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
-