511 lines
12 KiB
C
511 lines
12 KiB
C
/* $Header: mach5.c, v3.3 25-Apr-89 AJM */
|
|
|
|
branch(brtyp, link, val)
|
|
word_t brtyp;
|
|
word_t link;
|
|
valu_t val;
|
|
{
|
|
valu_t offset;
|
|
|
|
offset = val - DOTVAL - 8; /* Allow for pipeline */
|
|
if ((offset & 0xFC000000) != 0 && (offset & 0xFC000000) != 0xFC000000){
|
|
serror("offset out of range");
|
|
}
|
|
offset = offset>>2 & 0xFFFFFF;
|
|
emit4(brtyp|link|offset);
|
|
return;
|
|
}
|
|
|
|
data(opc, ins, val, typ)
|
|
long opc, ins;
|
|
valu_t val;
|
|
short typ;
|
|
{
|
|
valu_t tmpval;
|
|
int adrflag = 0;
|
|
|
|
if (typ == S_REG){ /* The argument is a register */
|
|
emit4(opc|ins|val);
|
|
return;
|
|
}
|
|
|
|
/* Do a bit of optimisation here, since the backend might produce instructions
|
|
of the type MOV R0, R0, #0. We can ignore these. */
|
|
|
|
if (((opc == ADD) || (opc == SUB)) && (val == 0)){ /* ADD or SUB 0 ? */
|
|
if ((ins & 0x000F0000) == ((ins & 0x0000F000) << 4)) /* Same reg ? */
|
|
return; /* Don't emit anything */
|
|
}
|
|
|
|
/* No optimisation, so carry on ... */
|
|
|
|
ins |= 0x02000000; /* The argument is an immediate value */
|
|
tmpval = val;
|
|
if (opc == 0xff){ /* This is an ADR */
|
|
adrflag = 1;
|
|
opc = MOV;
|
|
}
|
|
|
|
if (typ == S_ABS){ /* An absolute value */
|
|
if (calcimm(&opc, &tmpval, typ)){
|
|
emit4(opc|ins|tmpval);
|
|
return;
|
|
}
|
|
}
|
|
|
|
tmpval = val;
|
|
if (!adrflag){ /* Don't do this for ADRs */
|
|
if (oursmall(calcimm(&opc, &tmpval, typ), 12)){
|
|
emit4(opc|ins|tmpval);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (opc == MOV || opc == MVN || opc == ADD || opc == SUB){
|
|
if (!bflag && pass == PASS_3){ /* Debugging info */
|
|
/* warning("MOV/ADD extension"); */
|
|
/* if (dflag)
|
|
printf("value: %lx\n", val);*/
|
|
}
|
|
if (oursmall((val & 0xFFFF0000) == 0, 8)){
|
|
putaddr(opc, ins, val, 2);
|
|
return;
|
|
}
|
|
if (oursmall((val & 0xFF000000) == 0, 4)){
|
|
putaddr(opc, ins, val, 3);
|
|
return;
|
|
}
|
|
putaddr(opc, ins, val, 4);
|
|
return;
|
|
}
|
|
|
|
if (pass == PASS_1)
|
|
DOTVAL += 16; /* Worst case we can emit */
|
|
else
|
|
serror("immediate value out of range");
|
|
return;
|
|
}
|
|
|
|
|
|
/* Calculate an immediate value. This is not as easy as it sounds, because
|
|
the ARM uses an 8-bit value and 4-bit shift to encode the value into a
|
|
12-bit field. Unfortunately this means that some numbers may not fit at
|
|
all. */
|
|
|
|
calcimm(opc,val,typ)
|
|
word_t *opc;
|
|
valu_t *val;
|
|
short typ;
|
|
{
|
|
int i = 0;
|
|
|
|
if (typ == S_UND)
|
|
return(0); /* Can't do anything with an undefined label */
|
|
|
|
if ((*val & 0xFFFFFF00) == 0) /* Value is positive, but < 256, */
|
|
return(1); /* so doesn't need a shift */
|
|
|
|
if ((~*val & 0xFFFFFF00) == 0){ /* Value is negative, but < 256, */
|
|
if (*opc == AND) /* so no shift required, only */
|
|
{ /* inversion */
|
|
*val = ~*val;
|
|
*opc = BIC;
|
|
return(1);
|
|
}
|
|
if (*opc == MOV)
|
|
{
|
|
*val = ~*val;
|
|
*opc = MVN;
|
|
return(1);
|
|
}
|
|
if (*opc == ADC)
|
|
{
|
|
*val = ~*val;
|
|
*opc = SBC;
|
|
return(1);
|
|
}
|
|
|
|
}
|
|
if ((-1**val & 0xFFFFFF00) == 0){ /* Same idea ... */
|
|
if (*opc == ADD)
|
|
{
|
|
*val *= -1;
|
|
*opc = SUB;
|
|
return(1);
|
|
}
|
|
if (*opc == CMP)
|
|
{
|
|
*val *= -1;
|
|
*opc = CMN;
|
|
return(1);
|
|
}
|
|
}
|
|
|
|
do{ /* Now we need to shift */
|
|
rotateleft2(&*val); /* Rotate left by two bits */
|
|
i++;
|
|
if((*val & 0xFFFFFF00) == 0){ /* Got a value < 256 */
|
|
*val = *val|i<<8; /* OR in the shift */
|
|
return(1);
|
|
}
|
|
if ((~*val & 0xFFFFFF00) == 0){ /* If negative, carry out */
|
|
if (*opc == AND) /* inversion as before */
|
|
{
|
|
*val = ~*val|i<<8;
|
|
*opc = BIC;
|
|
return(1);
|
|
}
|
|
if (*opc == MOV)
|
|
{
|
|
*val = ~*val|i<<8;
|
|
*opc = MVN;
|
|
return(1);
|
|
}
|
|
if (*opc == ADC)
|
|
{
|
|
*val = ~*val|i<<8;
|
|
*opc = SBC;
|
|
return(1);
|
|
}
|
|
}
|
|
}while(i<15);
|
|
|
|
return(0); /* Failed if can't encode it after 16 rotates */
|
|
}
|
|
|
|
|
|
/* Calculate an offset in an address */
|
|
|
|
word_t
|
|
calcoffset(val)
|
|
valu_t val;
|
|
{
|
|
if((val & 0xFFFFF000) == 0)
|
|
return(val|0x00800000);
|
|
val *= -1;
|
|
if((val & 0xFFFFF000) == 0)
|
|
return(val);
|
|
serror("offset out of range");
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* This routine deals with STR and LDR instructions */
|
|
|
|
strldr(opc, ins, val)
|
|
long opc, ins;
|
|
valu_t val;
|
|
{
|
|
|
|
long reg, reg2; /* The registers we are using */
|
|
long tmpval;
|
|
|
|
/* If the expression was a register, then just output it and save 24
|
|
bytes */
|
|
|
|
if (success){
|
|
emit4(opc|ins|val);
|
|
return;
|
|
}
|
|
|
|
reg = ins & 0x0000F000; /* Extract register from instruction */
|
|
|
|
if (opc == LDR){
|
|
|
|
tmpval = val - DOTVAL - 8;
|
|
if (oursmall((tmpval & 0xFFFFF000) == 0, 16)){ /* If it's +ve */
|
|
emit4(opc|ins|tmpval|0x018F0000); /* PC rel, up bit */
|
|
return;
|
|
}
|
|
|
|
tmpval *= -1;
|
|
if (oursmall((tmpval & 0xFFFFF000) == 0, 16)){ /* If it's -ve */
|
|
emit4(opc|ins|tmpval|0x010F0000); /* PC rel, no up bit */
|
|
return;
|
|
}
|
|
|
|
if (!bflag && pass == PASS_3){ /* Debugging info */
|
|
/* warning("LDR address extension"); */
|
|
if (dflag)
|
|
printf("value: %lx\n", val);
|
|
}
|
|
|
|
opc = 0x03A00000; /* Set opc for putaddr */
|
|
|
|
if (oursmall((val & 0xFFFF0000) == 0, 8)){
|
|
putaddr(opc, ins & 0xFFBFFFFF, val, 2);
|
|
emit4(0x05100000|ins|reg<<4);
|
|
return;
|
|
}
|
|
if (oursmall((val & 0xFF000000) == 0, 4)){
|
|
putaddr(opc, ins & 0xFFBFFFFF, val, 3);
|
|
emit4(0x05100000|ins|reg<<4);
|
|
return;
|
|
}
|
|
putaddr(opc, ins & 0xFFBFFFFF, val, 4);
|
|
emit4(0x05100000|ins|reg<<4);
|
|
return;
|
|
}
|
|
|
|
/* If the failure was an STR instruction, things are a bit more complicated as
|
|
we can't overwrite the register before we store its value. We therefore
|
|
need to use another register as well, which must be saved and restored.
|
|
This register is saved on a stack pointed to by R12. Apart from this
|
|
complication, the scheme is similar to the LDR above. */
|
|
|
|
if (opc == STR){
|
|
reg2 = reg >> 12; /* Use R6 as the second register, */
|
|
reg2 = (reg2 == 6 ? 0 : 6); /* or R0 if we can't */
|
|
|
|
tmpval = val - DOTVAL - 8;
|
|
if (oursmall((tmpval & 0xFFFFF000) == 0, 24)){ /* If it's +ve */
|
|
emit4(opc|ins|tmpval|0x018F0000); /* PC rel, up bit */
|
|
return;
|
|
}
|
|
|
|
tmpval *= -1;
|
|
if (oursmall((tmpval & 0xFFFFF000) == 0, 24)){ /* If it's -ve */
|
|
emit4(opc|ins|tmpval|0x010F0000); /* PC rel, no up bit */
|
|
return;
|
|
}
|
|
|
|
if (!bflag && pass == PASS_3){ /* Debugging info */
|
|
/* warning("STR address extension"); */
|
|
if (dflag)
|
|
printf("value: %lx\n", val);
|
|
}
|
|
|
|
opc = 0x03A00000; /* Set opc for putaddr */
|
|
|
|
if (oursmall((val & 0xFFFF0000) == 0, 8)){
|
|
emit4(0xE92C0000|1<<reg2);
|
|
putaddr(opc, (ins & 0xFFBF0FFF)|reg2<<12, val, 2);
|
|
emit4(0x05000000|ins|reg2<<16);
|
|
emit4(0xE8BC0000|1<<reg2);
|
|
return;
|
|
}
|
|
if (oursmall((val & 0xFF000000) == 0, 4)){
|
|
emit4(0xE92C0000|1<<reg2);
|
|
putaddr(opc, (ins & 0xFFBF0FFF)|reg2<<12, val, 3);
|
|
emit4(0x05000000|ins|reg2<<16);
|
|
emit4(0xE8BC0000|1<<reg2);
|
|
return;
|
|
}
|
|
emit4(0xE92C0000|1<<reg2);
|
|
putaddr(opc, (ins & 0xFFBF0FFF)|reg2<<12, val, 4);
|
|
emit4(0x05000000|ins|reg2<<16);
|
|
emit4(0xE8BC0000|1<<reg2);
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* This routine deals with ADR instructions. The ARM does not have a
|
|
'calculate effective address' instruction, so we use ADD, SUB, MOV or
|
|
MVN instead. ADR is not a genuine instruction, but is provided to make
|
|
life easier. At present these are all calculated by using a MOV and
|
|
successive ADDs. Even if the address will fit into a single MOV, we
|
|
still use two instructions; the second is a no-op. This is to cure the
|
|
optimisation problem with mobile addresses ! */
|
|
|
|
|
|
calcadr(ins, reg, val, typ)
|
|
word_t ins, reg;
|
|
valu_t val;
|
|
short typ;
|
|
{
|
|
valu_t tmpval = val;
|
|
word_t opc = 0xff; /* Dummy opc used as a flag for data() */
|
|
|
|
/* First check that the address is in range */
|
|
|
|
if (val < 0)
|
|
tmpval = ~tmpval; /* Invert negative addresses for check */
|
|
|
|
if ((tmpval & 0xFC000000) && (typ != S_UND)){
|
|
serror("adr address out of range");
|
|
return;
|
|
}
|
|
|
|
/* Can't do it PC relative, so use an absolute MOV instead */
|
|
|
|
data (opc, ins|reg<<12, val, typ);
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
word_t
|
|
calcshft(val, typ, styp)
|
|
valu_t val;
|
|
short typ;
|
|
word_t styp;
|
|
{
|
|
if (typ == S_UND)
|
|
return(0);
|
|
|
|
if (val & 0xFFFFFFE0)
|
|
serror("shiftcount out of range");
|
|
|
|
if (styp && !val)
|
|
warning("shiftcount 0");
|
|
|
|
return((val & 0x1F)<<7);
|
|
}
|
|
|
|
rotateleft2(x)
|
|
long *x;
|
|
{
|
|
unsigned long bits;
|
|
|
|
bits = *x & 0xC0000000;
|
|
*x <<= 2 ;
|
|
if (bits){
|
|
bits >>= 30;
|
|
*x |= bits;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
This routine overcomes the 12-bit encoding problem by outputting a number
|
|
a byte at a time. For a MOV, it first uses a MOV, then successive ADDs.
|
|
It will not use any more ADDs than needed to completely output the number.
|
|
A similar approach is used for ADDs and SUBs.
|
|
There is a problem here with optimisation in the third pass; if the
|
|
instruction needed two ADDs in the second pass, but only one in the third
|
|
pass, then the second ADD is replaced with a no-op. We cannot emit one
|
|
less instruction, because that will upset other addresses.
|
|
*/
|
|
|
|
putaddr(opc, ins, val, count)
|
|
long opc, ins, val;
|
|
int count;
|
|
{
|
|
long tmpval = val;
|
|
long reg = ins & 0x0000F000;
|
|
|
|
emit4(opc|ins|(val & 0x000000FF));
|
|
|
|
tmpval = (val & 0x0000FF00) >> 8 | 0x00000C00;
|
|
|
|
/* Decide what to use for the additional instructions */
|
|
|
|
if (opc == 0x03a00000) /* This one is for strldr */
|
|
opc = 0x02800000;
|
|
|
|
if (opc == MOV)
|
|
opc = ADD;
|
|
|
|
if (opc == MVN)
|
|
opc = SUB;
|
|
|
|
if ((tmpval & 0x000000FF) != 0)
|
|
emit4(opc|ins|reg<<4|tmpval);
|
|
else
|
|
emit4(0xF0000000); /* No-op if a zero argument */
|
|
|
|
if (count == 3 || count == 4){ /* Must use three or more instructions */
|
|
if ((val & 0xFFFF0000) != 0){
|
|
tmpval = (val & 0x00FF0000) >> 16 | 0x00000800;
|
|
emit4(opc|ins|reg<<4|tmpval);
|
|
}
|
|
else
|
|
emit4(0xF0000000); /* No-op */
|
|
}
|
|
|
|
if (count == 4){ /* Must use four instructions */
|
|
if ((val & 0xFF000000) != 0){
|
|
tmpval = (val & 0xFF000000) >> 24 | 0x00000400;
|
|
emit4(opc|ins|reg<<4|tmpval);
|
|
}
|
|
else
|
|
emit4(0xF0000000); /* No-op */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* The following piece of code is stolen from comm7.c; it needs some minor
|
|
fixes for the ARM, so it is included here rather than altering the existing
|
|
code. It maintains a bit table to say whether or not an optimisation is
|
|
possible. The original had some problems:
|
|
(a). It assumed that the memory returned by malloc() was cleared to zero.
|
|
This is true on a Sun, but not under Minix; small() should really
|
|
use calloc() instead.
|
|
(b). It assumed that if an optimisation was possible in pass 2, it must
|
|
also be possible in pass 3, and produced an assertion error if it
|
|
wasn't. This is OK for optimising things like long or short branch
|
|
instructions on a 68000, but not for ADRs on the ARM. A previous
|
|
optimisation may place an address out of 12-bit encoding range on
|
|
pass 3, when it was in range on pass 2. However we have to be
|
|
careful here .....
|
|
*/
|
|
|
|
#define PBITTABSZ 128
|
|
static char *pbittab[PBITTABSZ];
|
|
|
|
oursmall(fitsmall, gain)
|
|
{
|
|
register bit;
|
|
register char *p;
|
|
|
|
if (DOTSCT == NULL)
|
|
nosect();
|
|
if (bflag)
|
|
return(0);
|
|
if (nbits == BITCHUNK) {
|
|
bitindex++;
|
|
nbits = 0;
|
|
if (bitindex == PBITTABSZ) {
|
|
static int w_given;
|
|
if (pass == PASS_1 && ! w_given) {
|
|
w_given = 1;
|
|
warning("bit table overflow");
|
|
}
|
|
return(0);
|
|
}
|
|
if (pbittab[bitindex] == 0 && pass == PASS_1) {
|
|
if ((pbittab[bitindex] = malloc(MEMINCR)) == 0) {
|
|
static int w2_given;
|
|
|
|
if (!w2_given) {
|
|
w2_given = 1;
|
|
warning("out of space for bit table");
|
|
}
|
|
}
|
|
}
|
|
if (pbittab[bitindex] == 0)
|
|
return (0);
|
|
}
|
|
bit = 1 << (nbits&7);
|
|
p = pbittab[bitindex]+(nbits>>3);
|
|
nbits++;
|
|
|
|
switch (pass) {
|
|
case PASS_1:
|
|
*p = 0;
|
|
return(0);
|
|
case PASS_2:
|
|
if (fitsmall) {
|
|
DOTGAIN += gain;
|
|
*p |= bit;
|
|
}
|
|
return(fitsmall);
|
|
case PASS_3:
|
|
if (!(fitsmall || (*p & bit) == 0)){
|
|
printf("line: %ld - small failed\n", lineno);
|
|
printf("fitsmall: %d bit: %d\n", fitsmall, (*p & bit));
|
|
if (fitsmall)
|
|
return(0);
|
|
else
|
|
serror("This one is fatal!");
|
|
}
|
|
return(*p & bit);
|
|
}
|
|
/*NOTREACHED*/
|
|
}
|