ack/mach/arm/as/mach5.c

491 lines
12 KiB
C
Raw Normal View History

1994-06-24 14:02:31 +00:00
/* $Id: mach5.c, v3.3 25-Apr-89 AJM */
1988-11-07 09:24:36 +00:00
2019-03-24 16:10:58 +00:00
void branch(word_t brtyp, word_t link, valu_t val)
1988-02-18 09:20:09 +00:00
{
valu_t offset;
1990-11-12 15:29:14 +00:00
offset = val - DOTVAL - 8; /* Allow for pipeline */
1988-02-18 09:20:09 +00:00
if ((offset & 0xFC000000) != 0 && (offset & 0xFC000000) != 0xFC000000){
serror("offset out of range");
}
offset = offset>>2 & 0xFFFFFF;
emit4(brtyp|link|offset);
return;
}
2019-03-24 16:10:58 +00:00
void data(long opc, long ins, valu_t val, short typ)
1988-02-18 09:20:09 +00:00
{
valu_t tmpval;
1990-11-12 15:29:14 +00:00
int adrflag = 0;
1988-02-18 09:20:09 +00:00
1990-11-12 15:29:14 +00:00
if (typ == S_REG){ /* The argument is a register */
1988-02-18 09:20:09 +00:00
emit4(opc|ins|val);
return;
}
1990-11-12 15:29:14 +00:00
/* Do a bit of optimisation here, since the backend might produce instructions
of the type MOV R0, R0, #0. We can ignore these. */
1988-02-18 09:20:09 +00:00
1990-11-12 15:29:14 +00:00
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 */
1988-02-18 09:20:09 +00:00
tmpval = val;
1990-11-12 15:29:14 +00:00
if (opc == 0xff){ /* This is an ADR */
adrflag = 1;
opc = MOV;
}
if (typ == S_ABS){ /* An absolute value */
1988-02-18 09:20:09 +00:00
if (calcimm(&opc, &tmpval, typ)){
emit4(opc|ins|tmpval);
return;
}
}
tmpval = val;
1990-11-12 15:29:14 +00:00
if (!adrflag){ /* Don't do this for ADRs */
if (oursmall(calcimm(&opc, &tmpval, typ), 12)){
emit4(opc|ins|tmpval);
1988-02-18 09:20:09 +00:00
return;
1990-11-12 15:29:14 +00:00
}
}
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);*/
1988-02-18 09:20:09 +00:00
}
1990-11-12 15:29:14 +00:00
if (oursmall((val & 0xFFFF0000) == 0, 8)){
putaddr(opc, ins, val, 2);
1988-02-18 09:20:09 +00:00
return;
}
1990-11-12 15:29:14 +00:00
if (oursmall((val & 0xFF000000) == 0, 4)){
putaddr(opc, ins, val, 3);
1988-02-18 09:20:09 +00:00
return;
}
1990-11-12 15:29:14 +00:00
putaddr(opc, ins, val, 4);
1988-02-18 09:20:09 +00:00
return;
}
1990-11-12 15:29:14 +00:00
if (pass == PASS_1)
DOTVAL += 16; /* Worst case we can emit */
else
serror("immediate value out of range");
return;
1988-02-18 09:20:09 +00:00
}
1990-11-12 15:29:14 +00:00
/* 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. */
2019-03-24 16:10:58 +00:00
int calcimm(word_t *opc, valu_t *val,short typ)
1988-02-18 09:20:09 +00:00
{
int i = 0;
1990-11-12 15:29:14 +00:00
if (typ == S_UND)
return(0); /* Can't do anything with an undefined label */
1988-02-18 09:20:09 +00:00
1990-11-12 15:29:14 +00:00
if ((*val & 0xFFFFFF00) == 0) /* Value is positive, but < 256, */
return(1); /* so doesn't need a shift */
1988-02-18 09:20:09 +00:00
1990-11-12 15:29:14 +00:00
if ((~*val & 0xFFFFFF00) == 0){ /* Value is negative, but < 256, */
if (*opc == AND) /* so no shift required, only */
{ /* inversion */
1988-02-18 09:20:09 +00:00
*val = ~*val;
*opc = BIC;
1990-11-12 15:29:14 +00:00
return(1);
1988-11-07 09:24:36 +00:00
}
if (*opc == MOV)
{
1988-02-18 09:20:09 +00:00
*val = ~*val;
*opc = MVN;
1990-11-12 15:29:14 +00:00
return(1);
1988-11-07 09:24:36 +00:00
}
if (*opc == ADC)
{
1988-02-18 09:20:09 +00:00
*val = ~*val;
*opc = SBC;
1990-11-12 15:29:14 +00:00
return(1);
1988-11-07 09:24:36 +00:00
}
1988-02-18 09:20:09 +00:00
}
1990-11-12 15:29:14 +00:00
if ((-1**val & 0xFFFFFF00) == 0){ /* Same idea ... */
1988-11-07 09:24:36 +00:00
if (*opc == ADD)
{
1988-02-18 09:20:09 +00:00
*val *= -1;
*opc = SUB;
1990-11-12 15:29:14 +00:00
return(1);
1988-11-07 09:24:36 +00:00
}
if (*opc == CMP)
{
1988-02-18 09:20:09 +00:00
*val *= -1;
*opc = CMN;
1990-11-12 15:29:14 +00:00
return(1);
1988-11-07 09:24:36 +00:00
}
1988-02-18 09:20:09 +00:00
}
1990-11-12 15:29:14 +00:00
do{ /* Now we need to shift */
rotateleft2(&*val); /* Rotate left by two bits */
1988-02-18 09:20:09 +00:00
i++;
1990-11-12 15:29:14 +00:00
if((*val & 0xFFFFFF00) == 0){ /* Got a value < 256 */
*val = *val|i<<8; /* OR in the shift */
return(1);
1988-02-18 09:20:09 +00:00
}
1990-11-12 15:29:14 +00:00
if ((~*val & 0xFFFFFF00) == 0){ /* If negative, carry out */
if (*opc == AND) /* inversion as before */
1988-11-07 09:24:36 +00:00
{
1988-02-18 09:20:09 +00:00
*val = ~*val|i<<8;
*opc = BIC;
1990-11-12 15:29:14 +00:00
return(1);
1988-11-07 09:24:36 +00:00
}
if (*opc == MOV)
{
1988-02-18 09:20:09 +00:00
*val = ~*val|i<<8;
*opc = MVN;
1990-11-12 15:29:14 +00:00
return(1);
1988-11-07 09:24:36 +00:00
}
if (*opc == ADC)
{
1988-02-18 09:20:09 +00:00
*val = ~*val|i<<8;
*opc = SBC;
1990-11-12 15:29:14 +00:00
return(1);
1988-11-07 09:24:36 +00:00
}
1988-02-18 09:20:09 +00:00
}
}while(i<15);
1990-11-12 15:29:14 +00:00
return(0); /* Failed if can't encode it after 16 rotates */
1988-02-18 09:20:09 +00:00
}
1990-11-12 15:29:14 +00:00
/* Calculate an offset in an address */
2019-03-24 16:10:58 +00:00
word_t calcoffset(valu_t val)
1988-02-18 09:20:09 +00:00
{
1988-11-07 09:24:36 +00:00
if((val & 0xFFFFF000) == 0)
1988-02-18 09:20:09 +00:00
return(val|0x00800000);
val *= -1;
1988-11-07 09:24:36 +00:00
if((val & 0xFFFFF000) == 0)
1988-02-18 09:20:09 +00:00
return(val);
serror("offset out of range");
return(0);
}
1990-11-12 15:29:14 +00:00
/* This routine deals with STR and LDR instructions */
2019-03-24 16:10:58 +00:00
void strldr(long opc, long ins, valu_t val)
1988-02-18 09:20:09 +00:00
{
1990-11-12 15:29:14 +00:00
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;
1988-02-18 09:20:09 +00:00
}
1990-11-12 15:29:14 +00:00
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;
}
1988-02-18 09:20:09 +00:00
}
1990-11-12 15:29:14 +00:00
/* 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 ! */
2019-03-24 16:10:58 +00:00
void calcadr(word_t ins, word_t reg, valu_t val, short typ)
1988-02-18 09:20:09 +00:00
{
valu_t tmpval = val;
1990-11-12 15:29:14 +00:00
word_t opc = 0xff; /* Dummy opc used as a flag for data() */
1988-02-18 09:20:09 +00:00
1990-11-12 15:29:14 +00:00
/* First check that the address is in range */
1988-02-18 09:20:09 +00:00
1990-11-12 15:29:14 +00:00
if (val < 0)
tmpval = ~tmpval; /* Invert negative addresses for check */
if ((tmpval & 0xFC000000) && (typ != S_UND)){
serror("adr address out of range");
return;
1988-11-07 09:24:36 +00:00
}
1988-02-18 09:20:09 +00:00
1990-11-12 15:29:14 +00:00
/* Can't do it PC relative, so use an absolute MOV instead */
data (opc, ins|reg<<12, val, typ);
return;
1988-02-18 09:20:09 +00:00
}
2019-03-24 16:10:58 +00:00
word_t calcshft(valu_t val, short typ, word_t styp)
1988-02-18 09:20:09 +00:00
{
1990-11-12 15:29:14 +00:00
if (typ == S_UND)
return(0);
if (val & 0xFFFFFFE0)
serror("shiftcount out of range");
if (styp && !val)
warning("shiftcount 0");
1988-02-18 09:20:09 +00:00
return((val & 0x1F)<<7);
}
2019-03-24 16:10:58 +00:00
void rotateleft2(long *x)
1988-02-18 09:20:09 +00:00
{
unsigned long bits;
bits = *x & 0xC0000000;
*x <<= 2 ;
if (bits){
bits >>= 30;
*x |= bits;
}
return;
}
1990-11-12 15:29:14 +00:00
/*
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.
*/
2019-03-24 16:10:58 +00:00
void putaddr(long opc, long ins, long val, int count)
1990-11-12 15:29:14 +00:00
{
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];
2019-03-24 16:10:58 +00:00
int oursmall(int fitsmall, int gain)
1990-11-12 15:29:14 +00:00
{
2019-03-24 16:10:58 +00:00
register int bit;
1990-11-12 15:29:14 +00:00
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);
2019-03-24 16:10:58 +00:00
default:
assert(0);
1990-11-12 15:29:14 +00:00
}
/*NOTREACHED*/
}