6502/vasm/cpus/arm/cpu.c

1756 lines
48 KiB
C
Raw Normal View History

/*
** cpu.c ARM cpu-description file
** (c) in 2004,2006,2010,2011,2014-2018 by Frank Wille
*/
#include "vasm.h"
mnemonic mnemonics[] = {
#include "opcodes.h"
};
int mnemonic_cnt = sizeof(mnemonics)/sizeof(mnemonics[0]);
char *cpu_copyright = "vasm ARM cpu backend 0.4e (c) 2004,2006,2010,2011,2014-2018 Frank Wille";
char *cpuname = "ARM";
int bitsperbyte = 8;
int bytespertaddr = 4;
uint32_t cpu_type = AAANY;
int arm_be_mode = 0; /* Little-endian is default */
int thumb_mode = 0; /* 1: Thumb instruction set (16 bit) is active */
/* options */
static unsigned char opt_ldrpc = 0; /* LDR r,sym -> ADD / LDR */
static unsigned char opt_adr = 0; /* ADR r,sym -> ADRL (ADD/ADD|SUB/SUB) */
/* constant data */
static const char *condition_codes = "eqnecsccmiplvsvchilsgeltgtlealnvhsloul";
static const char *addrmode_strings[] = {
"da","ia","db","ib",
"fa","fd","ea","ed",
"bt","tb","sb","sh","t","b","h","s","l",
"p",NULL,"<none>"
};
enum {
AM_DA=0,AM_IA,AM_DB,AM_IB,AM_FA,AM_FD,AM_EA,AM_ED,
AM_BT,AM_TB,AM_SB,AM_SH,AM_T,AM_B,AM_H,AM_S,AM_L,
AM_P,AM_NULL,AM_NONE
};
#define NUM_SHIFTTYPES 6
static const char *shift_strings[NUM_SHIFTTYPES] = {
"LSL","LSR","ASR","ROR","RRX","ASL"
};
static int OC_SWP,OC_NOP;
static int elfoutput = 0; /* output will be an ELF object file */
static section *last_section = 0;
static int last_data_type = -1; /* for mapping symbol generation */
#define TYPE_ARM 0
#define TYPE_THUMB 1
#define TYPE_DATA 2
#define THB_PREFETCH 4 /* prefetch-correction for Thumb-branches */
#define ARM_PREFETCH 8 /* prefetch-correction for ARM-branches */
operand *new_operand(void)
{
return mycalloc(sizeof(operand));
}
int cpu_available(int idx)
{
return (mnemonics[idx].ext.available & cpu_type) != 0;
}
char *parse_cpu_special(char *start)
/* parse cpu-specific directives; return pointer to end of
cpu-specific text */
{
char *name=start,*s=start;
if (ISIDSTART(*s)) {
s++;
while (ISIDCHAR(*s))
s++;
if (s-name==6 && !strncmp(name,".thumb",6)) {
thumb_mode = 1;
if (inst_alignment > 1)
inst_alignment = 2;
return s;
}
else if (s-name==4 && !strncmp(name,".arm",4)) {
thumb_mode = 0;
if (inst_alignment > 1)
inst_alignment = 4;
return s;
}
}
return start;
}
char *parse_instruction(char *s,int *inst_len,char **ext,int *ext_len,
int *ext_cnt)
/* parse instruction and save extension locations */
{
char *inst = s;
int cnt = *ext_cnt;
while (*s && !isspace((unsigned char)*s))
s++;
if (thumb_mode) { /* no qualifiers in THUMB code */
*inst_len = s - inst;
}
else { /* ARM mode - we might have up to 2 different qualifiers */
int len = s - inst;
char c = tolower((unsigned char)*inst);
if (len > 2) {
if (c=='b' && strnicmp(inst,"bic",3) && (len==3 || len==4)) {
*inst_len = len - 2;
}
else if ((c=='u' || c=='s') &&
tolower((unsigned char)*(inst+1))=='m' && len>=5) {
*inst_len = 5;
}
else
*inst_len = 3;
len -= *inst_len;
if (len > 0) {
char *p = inst + *inst_len;
if (len >= 2) {
const char *cc = condition_codes;
while (*cc) {
if (!strnicmp(p,cc,2))
break;
cc += 2;
}
if (*cc) { /* matched against a condition code */
ext[cnt] = p;
ext_len[cnt++] = 2;
p += 2;
len -= 2;
}
}
if (len >= 1) {
const char **am = addrmode_strings;
do {
if (len==strlen(*am) && !strnicmp(*am,p,len))
break;
am++;
}
while (*am);
if (*am!=NULL || (len==1 && tolower((unsigned char)*p)=='s')) {
ext[cnt] = p;
ext_len[cnt++] = len;
}
}
}
else if (len < 0)
ierror(0);
}
else
*inst_len = len;
*ext_cnt = cnt;
}
return s;
}
int set_default_qualifiers(char **q,int *q_len)
/* fill in pointers to default qualifiers, return number of qualifiers */
{
return 0;
}
static int parse_reg(char **pp)
/* parse register, return -1 on error */
{
char *p = *pp;
char *name = p;
regsym *sym;
if (ISIDSTART(*p)) {
p++;
while (ISIDCHAR(*p))
p++;
if (sym = find_regsym_nc(name,p-name)) {
*pp = p;
return sym->reg_num;
}
}
return -1; /* no valid register found */
}
static int parse_reglist(char **pp)
/* parse register-list, return -1 on error */
{
int r=0,list=0,lastreg=-1;
char *p = *pp;
char *name;
regsym *sym;
if (*p++ == '{') {
p = skip(p);
do {
if (ISIDSTART(*p)) {
name = p++;
while (ISIDCHAR(*p))
p++;
if (sym = find_regsym_nc(name,p-name)) {
r = sym->reg_num;
if (lastreg >= 0) { /* range-mode? */
if (lastreg < r) {
r = lastreg;
lastreg = sym->reg_num;
}
for (; r<=lastreg; list |= 1<<r++);
}
else
list |= 1<<r;
p = skip(p);
}
else
return -1;
}
if (*p == ',') {
lastreg = -1;
p = skip(p+1);
}
else if (*p == '-') {
lastreg = r;
p = skip(p+1);
}
}
while (*p!='\0' && *p!='}');
if (*p) {
*pp = ++p;
return list;
}
}
return -1;
}
int parse_operand(char *p,int len,operand *op,int optype)
/* Parses operands, reads expressions and assigns relocation types */
{
char *start = p;
op->type = optype;
op->flags = 0;
op->value = NULL;
p = skip(p);
if (optype == DATA64_OP) {
op->value = parse_expr_huge(&p);
}
else if (thumb_mode) {
if (ARMOPER(optype)) { /* standard ARM instruction */
return PO_NOMATCH;
}
else if (THREGOPER(optype)) {
/* parse a register */
int r;
if (optype==TR5IN || optype==TPCPR || optype==TSPPR) {
if (*p++ != '[')
return PO_NOMATCH;
p = skip(p);
}
if ((r = parse_reg(&p)) < 0)
return PO_NOMATCH;
op->value = number_expr((taddr)r);
if (optype==TPCRG || optype==TPCPR) {
if (r != 15)
return PO_NOMATCH;
}
else if (optype==TSPRG || optype==TSPPR) {
if (r != 13)
return PO_NOMATCH;
}
else if (optype==THR02 || optype==THR05) {
if (r<8 || r>15)
return PO_NOMATCH;
}
else {
if (r<0 || r>7)
return PO_NOMATCH;
}
if (optype == TR8IN) {
p = skip(p);
if (*p++ != ']')
return PO_NOMATCH;
}
else if (optype == TR10W) {
if (*p++ != '!')
return PO_NOMATCH;
}
}
else if (THREGLIST(optype)) {
taddr list = parse_reglist(&p);
if (optype == TRLST) {
if (list & ~0xff)
return PO_NOMATCH; /* only r0-r7 allowed */
}
else {
if ((list&0x8000) && optype==TRLPC) {
list = list&~0x8000 | 0x100;
}
else if ((list&0x4000) && optype==TRLLR) {
list = list&~0x4000 | 0x100;
}
if (list & ~0x1ff)
return PO_NOMATCH; /* only r0-r7 / pc / lr allowed */
}
op->value = number_expr(list);
}
else if (THIMMOPER(optype)) {
if (*p++ != '#')
return PO_NOMATCH;
p = skip(p);
op->value = parse_expr(&p);
if (THIMMINDIR(optype)) {
p = skip(p);
if (*p++ != ']')
return PO_NOMATCH;
}
}
else { /* just parse an expression */
char *q = p;
/* check that this isn't any other valid operand */
if (*p=='#' || *p=='[' || *p=='{' || parse_reg(&q)>=0)
return PO_NOMATCH;
op->value = parse_expr(&p);
}
}
else { /* ARM mode */
if (THUMBOPER(optype)) { /* Thumb instruction */
return PO_NOMATCH;
}
else if (STDOPER(optype)) {
/* parse an expression (register, label, imm.) and assign to 'value' */
if (IMMEDOPER(optype)) {
if (*p++ != '#')
return PO_NOMATCH;
p = skip(p);
}
else if (optype==R19PR || optype==R19PO) {
if (*p++ != '[')
return PO_NOMATCH;
p = skip(p);
}
if (UPDOWNOPER(optype)) {
if (*p == '-') {
p = skip(p+1);
}
else {
if (*p == '+')
p = skip(p+1);
op->flags |= OFL_UP;
}
}
if (REGOPER(optype)) {
int r = parse_reg(&p);
if (r >= 0)
op->value = number_expr((taddr)r);
else
return PO_NOMATCH;
}
else { /* an expression */
if (ISIDSTART(*p) || isdigit((unsigned char)*p) ||
(!UPDOWNOPER(optype) && (*p=='-' || *p=='+')))
op->value = parse_expr(&p);
else
return PO_NOMATCH;
}
if (optype==R19PO || optype==R3UD1 || optype==IMUD1 || optype==IMCP1) {
p = skip(p);
if (*p++ != ']')
return PO_NOMATCH;
}
if (optype==R19WB || optype==R3UD1 || optype==IMUD1 || optype==IMCP1) {
if (*p == '!') {
p++;
op->flags |= OFL_WBACK;
}
}
}
else if (SHIFTOPER(optype)) {
char *name = p;
int i;
p = skip_identifier(p);
if (p == NULL)
return PO_NOMATCH;
for (i=0; i<NUM_SHIFTTYPES; i++) {
if (!strnicmp(shift_strings[i],name,p-name))
break;
}
if (i >= NUM_SHIFTTYPES)
return PO_NOMATCH;
if (i == 4) {
/* RRX is ROR with immediate value 0 */
op->flags |= OFL_IMMEDSHIFT;
op->value = number_expr(0);
i = 3; /* ROR */
}
else {
/* parse immediate or register for LSL, LSR, ASR, ROR */
p = skip(p);
if (i == 5)
i = 0; /* ASL -> LSL */
if (*p == '#') {
p++;
op->flags |= OFL_IMMEDSHIFT;
op->value = parse_expr(&p);
}
else if (optype == SHIFT) {
int r = parse_reg(&p);
if (r >= 0)
op->value = number_expr((taddr)r);
else
return PO_NOMATCH;
}
else
return PO_NOMATCH; /* no shift-count in register allowed */
}
op->flags |= i & OFL_SHIFTOP;
if (optype == SHIM1) {
/* check for pre-indexed with optional write-back */
p = skip(p);
if (*p++ != ']')
return PO_NOMATCH;
if (*p == '!') {
p++;
op->flags |= OFL_WBACK;
}
}
}
else if (optype == CSPSR) {
char *name = p;
p = skip_identifier(p);
if (p == NULL)
return PO_NOMATCH;
if (!strnicmp(name,"CPSR",p-name))
op->flags &= ~OFL_SPSR;
else if (!strnicmp(name,"SPSR",p-name))
op->flags |= OFL_SPSR;
else
return PO_NOMATCH;
op->value = number_expr(0xf); /* all fields f,s,x,c */
}
else if (optype == PSR_F) {
char *name = p;
taddr fields = 0xf;
p = skip_identifier(p);
if (p==NULL || (p-name)<4)
return PO_NOMATCH;
if (!strnicmp(name,"CPSR",4))
op->flags &= ~OFL_SPSR;
else if (!strnicmp(name,"SPSR",4))
op->flags |= OFL_SPSR;
else
return PO_NOMATCH;
if ((p-name)>5 && *(name+4)=='_') {
fields = 0;
name += 5;
while (name < p) {
switch (tolower((unsigned char)*name++)) {
case 'f': fields |= 1; break;
case 's': fields |= 2; break;
case 'x': fields |= 4; break;
case 'c': fields |= 8; break;
default: return PO_NOMATCH;
}
}
}
else if ((p-name) > 4)
return PO_NOMATCH;
op->value = number_expr(fields);
}
else if (optype == RLIST) {
taddr list = parse_reglist(&p);
if (list >= 0) {
op->value = number_expr(list);
if (*p == '^') {
p++;
op->flags |= OFL_FORCE; /* set "load PSR / force user mode" flag */
}
}
else
return PO_NOMATCH;
}
else
ierror(0);
}
return (skip(p)-start < len) ? PO_NOMATCH : PO_MATCH;
}
static void create_mapping_symbol(int type,section *sec,taddr pc)
/* create mapping symbol ($a, $t, $d) as required by ARM ELF ABI */
{
static char names[3][4] = { "$a","$t","$d" };
static int types[3] = { TYPE_FUNCTION,TYPE_FUNCTION,TYPE_OBJECT };
symbol *sym;
if (type<TYPE_ARM || type>TYPE_DATA)
ierror(0);
if (elfoutput) {
sym = mymalloc(sizeof(symbol));
sym->type = LABSYM;
sym->flags = types[type];
sym->name = names[type];
sym->sec = sec;
sym->pc = pc;
sym->expr = 0;
sym->size = 0;
sym->align = 0;
add_symbol(sym);
}
last_data_type = type;
}
size_t eval_thumb_operands(instruction *ip,section *sec,taddr pc,
uint16_t *insn,dblock *db)
/* evaluate expressions and try to optimize THUMB instruction,
return size of instruction */
{
operand op;
mnemonic *mnemo = &mnemonics[ip->code];
int opcnt = 0;
size_t isize = 2;
if (insn) {
if (pc & 1)
cpu_error(27); /* instruction at unaligned address */
if (ip->op[0] == NULL) {
/* handle inst. without operands, which don't have Thumb entries */
if (ip->code == OC_NOP)
*insn = 0x46c0; /* nop => mov r0,r0 */
return 2;
}
else
*insn = (uint16_t)mnemo->ext.opcode;
}
for (opcnt=0; opcnt<MAX_OPERANDS && ip->op[opcnt]!=NULL; opcnt++) {
taddr val;
symbol *base = NULL;
int btype;
op = *(ip->op[opcnt]);
if (!eval_expr(op.value,&val,sec,pc))
btype = find_base(op.value,&base,sec,pc);
/* do optimizations first */
if (op.type==TPCLW || THBRANCH(op.type)) {
/* PC-relative offsets (take prefetch into account: PC+4) */
if (base!=NULL && btype==BASE_OK) {
if (!is_pc_reloc(base,sec)) {
/* no relocation required, can be resolved immediately */
if (op.type == TPCLW) {
/* bit 1 of PC is forced to 0 */
val -= (pc&~2) + 4;
}
else
val -= pc + 4;
if (op.type == TBR08) {
if (val<-0x100 || val>0xfe) {
/* optimize to: B<!cc> .+4 ; B label */
if (insn) {
*insn++ ^= 0x100; /* negate branch-condition */
*insn = 0xe000; /* B unconditional to label */
}
if (val < 0)
val -= 2; /* backward-branches are 2 bytes longer */
isize += 2;
op.type = TBR11;
}
}
else if (op.type == TBRHL) {
/* BL always consists of two instructions */
isize += 2;
}
else if (op.type == TPCLW) {
/* @@@ optimization makes any sense? */
op.type = TUIMA;
base = NULL; /* no more checks */
}
}
else {
/* symbol is in a different section or externally declared */
if (op.type == TBRHL) {
val -= THB_PREFETCH;
if (db) {
add_extnreloc_masked(&db->relocs,base,val,REL_PC,
arm_be_mode?5:0,11,0,0x7ff000);
add_extnreloc_masked(&db->relocs,base,val,REL_PC,
arm_be_mode?16+5:16+0,11,0,0xffe);
}
isize += 2; /* we need two instructions for a 23-bit branch */
}
else if (op.type == TPCLW) {
/* val -= THB_PREFETCH; @@@ only positive offsets allowed! */
op.type = TUIMA;
if (db)
add_extnreloc_masked(&db->relocs,base,val,REL_PC,
arm_be_mode?8:0,8,0,0x3fc);
base = NULL; /* no more checks */
}
else if (insn)
cpu_error(22); /* operation not allowed on external symbols */
}
}
else if (insn)
cpu_error(2); /* label from current section required */
}
/* optimizations should be finished at this stage -
inserts operands into the opcode now: */
if (insn) {
if (THREGOPER(op.type)) {
/* insert register operand, check was already done in parse_operand */
if (!THPCORSP(op.type)) {
switch (op.type) {
case TRG02:
case THR02:
*insn |= val&7;
break;
case TRG05:
case THR05:
case TR5IN:
*insn |= (val&7) << 3;
break;
case TRG08:
case TR8IN:
*insn |= (val&7) << 6;
break;
case TRG10:
case TR10W:
*insn |= (val&7) << 8;
break;
default:
ierror(0);
break;
}
}
}
else if (THREGLIST(op.type)) {
/* register list was already checked in parse_operand - just insert */
*insn |= val;
}
else if (THIMMOPER(op.type) || op.type==TSWI8) {
/* immediate operand */
switch (op.type) {
case TUIM3:
if (val>=0 && val<=7) {
*insn |= val<<6;
}
else
cpu_error(25,3,(long)val); /* immediate offset out of range */
break;
case TUIM5:
case TUI5I:
if (val>=0 && val<=0x1f) {
*insn |= val<<6;
}
else
cpu_error(25,5,(long)val); /* immediate offset out of range */
break;
case TUI6I:
if (val>=0 && val<=0x3e) {
if ((val & 1) == 0)
*insn |= (val&0x3e)<<5;
else
cpu_error(26,2); /* offset has to be a multiple of 2 */
}
else
cpu_error(25,6,(long)val); /* immediate offset out of range */
break;
case TUI7I:
if (val>=0 && val<=0x7c) {
if ((val & 3) == 0)
*insn |= (val&0x7c)<<4;
else
cpu_error(26,4); /* offset has to be a multiple of 4 */
}
else
cpu_error(25,7,(long)val); /* immediate offset out of range */
break;
case TUIM8:
case TSWI8:
if (val>=0 && val<=0xff) {
*insn |= val;
}
else
cpu_error(25,8,(long)val); /* immediate offset out of range */
break;
case TUIM9:
if (val>=0 && val<=0x1fc) {
if ((val & 3) == 0)
*insn |= val>>2;
else
cpu_error(26,4); /* offset has to be a multiple of 4 */
}
else
cpu_error(25,9,(long)val); /* immediate offset out of range */
break;
case TUIMA:
case TUIAI:
if (val>=0 && val<=0x3fc) {
if ((val & 3) == 0)
*insn |= val>>2;
else
cpu_error(26,4); /* offset has to be a multiple of 4 */
}
else
cpu_error(25,10,(long)val); /* immediate offset out of range */
break;
}
if (base!=NULL && db!=NULL) {
if (btype == BASE_OK) {
if (op.type==TUIM5 || op.type==TUI5I)
add_extnreloc_masked(&db->relocs,base,val,REL_ABS,
arm_be_mode?5:6,5,0,0x1f);
else if (op.type == TSWI8)
add_extnreloc_masked(&db->relocs,base,val,REL_ABS,
arm_be_mode?8:0,8,0,0xff);
else
cpu_error(6); /* constant integer expression required */
}
else
general_error(38); /* illegal relocation */
}
}
else if (op.type == TBR08) {
/* only write offset, relocs and optimizations are handled above */
if (val & 1)
cpu_error(8,(long)val); /* branch to unaligned address */
*insn |= (val>>1) & 0xff;
}
else if (op.type == TBR11) {
/* only write offset, relocs and optimizations are handled above */
if (val<-0x800 || val>0x7fe)
cpu_error(3,(long)val); /* branch offset is out of range */
if (val & 1)
cpu_error(8,(long)val); /* branch to unaligned address */
*insn |= (val>>1) & 0x7ff;
}
else if (op.type == TBRHL) {
/* split 23-bit offset over two instructions, ignoring bit 0 */
if (val<-0x400000 || val>0x3ffffe)
cpu_error(3,(long)val); /* branch offset is out of range */
if (val & 1)
cpu_error(8,(long)val); /* branch to unaligned address */
*insn++ |= (val>>12) & 0x7ff;
*insn = 0xf800 | ((val>>1) & 0x7ff);
}
else
ierror(0);
}
}
return isize;
}
#define ROTFAIL (0xffffff)
static uint32_t rotated_immediate(uint32_t val)
/* check if a 32-bit value can be represented as 8-bit-rotated,
return ROTFAIL when impossible */
{
uint32_t i,a;
for (i=0; i<32; i+=2) {
if ((a = val<<i | val>>(32-i)) <= 0xff)
return (i<<7) | a;
}
return ROTFAIL;
}
static int negated_rot_immediate(uint32_t val,mnemonic *mnemo,
uint32_t *insn)
/* check if negating the ALU-operation makes a valid 8-bit-rotated value,
insert it into the current instruction, when successful */
{
uint32_t neg = rotated_immediate(-val);
uint32_t inv = rotated_immediate(~val);
uint32_t op = (mnemo->ext.opcode & 0x01e00000) >> 21;
switch (op) {
/* AND <-> BIC */
case 0: op=14; val=inv; break;
case 14: op=0; val=inv; break;
/* ADD <-> SUB */
case 2: op=4; val=neg; break;
case 4: op=2; val=neg; break;
/* ADC <-> SBC */
case 5: op=6; val=inv; break;
case 6: op=5; val=inv; break;
/* CMP <-> CMN */
case 10: op=11; val=neg; break;
case 11: op=10; val=neg; break;
/* MOV <-> MVN */
case 13: op=15; val=inv; break;
case 15: op=13; val=inv; break;
default: return 0;
}
if (val == ROTFAIL)
return 0;
if (insn) {
*insn &= ~0x01e00000;
*insn |= (op<<21) | val;
}
return 1;
}
static uint32_t double_rot_immediate(uint32_t val,uint32_t *hi)
/* check if a 32-bit value can be represented by combining two
8-bit rotated values, return ROTFAIL otherwise */
{
uint32_t i,a;
for (i=0; i<32; i+=2) {
if (((a = val<<i | val>>(32-i)) & 0xff) != 0) {
if (a & 0xff00) {
if (a & 0xffff0000)
continue;
*hi = ((i+24)<<7) | (a>>8);
}
else if (a & 0xff0000) {
if (a & 0xff000000)
continue;
*hi = ((i+16)<<7) | (a>>16);
}
else if (a & 0xff000000)
*hi = ((i+8)<<7) | (a>>24);
else
ierror(0);
return (i<<7) | (a&0xff);
}
}
return ROTFAIL;
}
static uint32_t calc_2nd_rot_opcode(uint32_t op)
/* calculates ALU operation for second instruction */
{
if (op == 13)
op = 12; /* MOV + ORR */
else if (op == 15)
op = 1; /* MVN + EOR */
/* ADD and SUB stay the same */
return op << 21;
}
static int negated_double_rot_immediate(uint32_t val,uint32_t *insn)
/* check if negating the ALU-operation and/or a second ADD/SUB operation
makes a valid 8-bit-rotated value, insert it into the current
instruction, when successful */
{
uint32_t op = (*insn & 0x01e00000) >> 21;
if ((op==2 || op==4 || op==13 || op==15) && insn!=NULL) {
/* combined instructions only possible for ADD/SUB/MOV/MVN */
uint32_t lo,hi;
*(insn+1) = *insn & ~0x01ef0000;
*(insn+1) |= (*insn&0xf000) << 4; /* Rn = Rd of first instruction */
if ((lo = double_rot_immediate(val,&hi)) != ROTFAIL) {
*insn++ |= hi;
*insn |= calc_2nd_rot_opcode(op) | lo;
return 1;
}
/* @@@ try negated or inverted values */
}
return 0;
}
static uint32_t get_condcode(instruction *ip)
/* returns condition (bit 31-28) from instruction's qualifiers */
{
const char *cc = condition_codes;
char *q;
if (q = ip->qualifiers[0]) {
uint32_t code = 0;
while (*cc) {
if (!strnicmp(q,cc,2) && *(q+2)=='\0')
break;
cc += 2;
code++;
}
if (*cc) { /* condition code in qualifier valid */
if (code == 16) /* hs -> cs */
code = 2;
else if (code==17 || code==18) /* lo/ul -> cc */
code = 3;
return code<<28;
}
}
return 0xe0000000; /* AL - always */
}
static int get_addrmode(instruction *ip)
/* return addressing mode from instruction's qualifiers */
{
char *q;
if ((q = ip->qualifiers[1]) == NULL)
q = ip->qualifiers[0];
if (q) {
const char **am = addrmode_strings;
int mode = AM_DA;
do {
if (!stricmp(*am,q))
break;
am++;
mode++;
}
while (*am);
if (*am != NULL)
return mode;
}
return AM_NONE;
}
size_t eval_arm_operands(instruction *ip,section *sec,taddr pc,
uint32_t *insn,dblock *db)
/* evaluate expressions and try to optimize ARM instruction,
return size of instruction */
{
operand op;
mnemonic *mnemo = &mnemonics[ip->code];
int am = get_addrmode(ip);
int aa4ldst = 0;
int opcnt = 0;
size_t isize = 4;
taddr chkreg = -1;
if (insn) {
if (pc & 3)
cpu_error(27); /* instruction at unaligned address */
*insn = mnemo->ext.opcode | get_condcode(ip);
if ((mnemo->ext.flags & SETCC)!=0 && am==AM_S)
*insn |= 0x00100000; /* set-condition-codes flag */
if ((mnemo->ext.flags & SETPSR)!=0 && am==AM_P) {
/* Rd = R15 for changing the PSR. Recommended for ARM2/250/3 only. */
*insn |= 0x0000f000;
if (cpu_type & ~AA2)
cpu_error(28); /* deprecated on 32-bit architectures */
}
if (!strcmp(mnemo->name,"ldr") || !strcmp(mnemo->name,"str")) {
if (am==AM_T || am==AM_B || am==AM_BT || am==AM_TB) { /* std. ldr/str */
if (am != AM_B) {
*insn |= 0x00200000; /* W-flag for post-indexed mode */
*insn &= ~0x01000000; /* force post-indexed */
}
if (am != AM_T)
*insn |= 0x00400000; /* B-flag for byte-transfer */
}
else if (am==AM_SB || am==AM_H || am==AM_SH) { /* arch.4 ldr/str */
if (cpu_type & AA4UP) {
/* take P-, I- and L-bit from previous standard instruction */
*insn = (*insn&0xf1100000)
| (((*insn&0x02000000)^0x02000000)>>3) /* I-bit is flipped */
| 0x90;
if (am != AM_H) {
if (*insn & 0x00100000) /* load */
*insn |= 0x40; /* signed transfer */
else
cpu_error(18,addrmode_strings[am]); /* illegal addr. mode */
}
if (am != AM_SB)
*insn |= 0x20; /* halfword-transfer */
aa4ldst = 1;
}
else
cpu_error(0); /* instruction not supported on selected arch. */
}
else if (am != AM_NONE)
cpu_error(18,addrmode_strings[am]); /* illegal addr. mode */
}
else if (ip->code == OC_SWP) {
if (am == AM_B)
*insn |= 0x00400000; /* swap bytes */
else if (am != AM_NONE)
cpu_error(18,addrmode_strings[am]); /* illegal addr. mode */
}
}
else { /* called by instruction_size() */
if (am==AM_SB || am==AM_H || am==AM_SH)
aa4ldst = 1;
}
for (opcnt=0; opcnt<MAX_OPERANDS && ip->op[opcnt]!=NULL; opcnt++) {
taddr val;
symbol *base = NULL;
int btype;
op = *(ip->op[opcnt]);
if (!eval_expr(op.value,&val,sec,pc))
btype = find_base(op.value,&base,sec,pc);
/* do optimizations first */
if (op.type==PCL12 || op.type==PCLRT ||
op.type==PCLCP || op.type==BRA24) {
/* PC-relative offsets (take prefetch into account: PC+8) */
if (base!=NULL && btype==BASE_OK) {
if (!is_pc_reloc(base,sec)) {
/* no relocation required, can be resolved immediately */
val -= pc + 8;
switch (op.type) {
case BRA24:
if (val>=0x2000000 || val<-0x2000000) {
/* @@@ optimize? to what? */
if (insn)
cpu_error(3,(long)val); /* branch offset is out of range */
}
break;
case PCL12:
if ((!aa4ldst && val<0x1000 && val>-0x1000) ||
(aa4ldst && val<0x100 && val>-0x100)) {
op.type = IMUD2; /* handle as normal #+/-Imm12 */
if (val < 0)
val = -val;
else
op.flags |= OFL_UP;
base = NULL; /* no more checks */
}
else {
if (opt_ldrpc &&
((!aa4ldst && val<0x100000 && val>-0x100000) ||
(aa4ldst && val<0x10000 && val>-0x10000))) {
/* ADD/SUB Rd,PC,#offset&0xff000 */
/* LDR/STR Rd,[Rd,#offset&0xfff] */
if (insn) {
taddr v;
*(insn+1) = *insn;
*insn &= 0xf0000000; /* clear all except cond. */
if (val < 0) {
v = -val;
*insn |= 0x024f0a00; /* SUB */
*(insn+1) &= ~0x00800000; /* clear U-bit */
}
else {
v = val;
*insn |= 0x028f0a00; /* ADD */
*(insn+1) |= 0x00800000; /* set U-bit */
}
if (aa4ldst)
*insn |= (*(insn+1)&0xf000) | ((v&0xff00)>>8);
else
*insn |= (*(insn+1)&0xf000) | ((v&0xff000)>>12);
*(insn+1) &= ~0x000f0000; /* replace PC by Rd */
*(insn+1) |= (*insn & 0xf000) << 4;
insn++;
}
if (val < 0)
val = -val;
else
op.flags |= OFL_UP;
val = aa4ldst ? (val & 0xff) : (val & 0xfff);
isize += 4;
op.type = IMUD2;
base = NULL; /* no more checks */
}
else {
op.type = NOOP;
if (insn)
cpu_error(4,val); /* PC-relative ldr/str out of range */
}
}
break;
case PCLCP:
if (val<0x400 && val>-0x400) {
op.type = IMCP2; /* handle as normal #+/-Imm10>>2 */
if (val < 0)
val = -val;
else
op.flags |= OFL_UP;
base = NULL; /* no more checks */
}
else {
/* no optimization, because we don't have a free register */
op.type = NOOP;
if (insn)
cpu_error(4,val); /* PC-relative ldc/stc out of range */
}
break;
case PCLRT:
op.type = NOOP; /* is handled here */
if (val < 0) {
/* use SUB instead of ADD */
if (insn)
*insn ^= 0x00c00000;
val = -val;
}
if ((val = rotated_immediate(val)) != ROTFAIL) {
if (insn)
*insn |= val;
}
else if (opt_adr || am==AM_L) {
/* ADRL or optimize ADR automatically to ADRL */
uint32_t hi,lo;
isize += 4;
if ((lo = double_rot_immediate(val,&hi)) != ROTFAIL) {
/* ADD/SUB Rd,PC,#hi8rotated */
/* ADD/SUB Rd,Rd,#lo8rotated */
if (insn) {
*(insn+1) = *insn & ~0xf0000;
*(insn+1) |= (*insn&0xf000) << 4;
*insn++ |= hi;
*insn |= lo;
}
}
else if (insn)
cpu_error(5,(uint32_t)val); /* Cannot make rot.immed.*/
}
else if (insn)
cpu_error(5,(uint32_t)val); /* Cannot make rot.immed.*/
break;
default:
ierror(0);
}
}
else {
/* symbol is in a different section or externally declared */
switch (op.type) {
case BRA24:
val -= ARM_PREFETCH;
if (db)
add_extnreloc_masked(&db->relocs,base,val,REL_PC,
arm_be_mode?8:0,24,0,0x3fffffc);
break;
case PCL12:
op.type = IMUD2;
if (db) {
if (val<0x1000 && val>-0x1000) {
add_extnreloc_masked(&db->relocs,base,val,REL_PC,
arm_be_mode?20:0,12,0,0x1fff);
base = NULL; /* don't add another REL_ABS below */
}
else
cpu_error(22); /* operation not allowed on external symbols */
}
break;
case PCLCP:
if (db)
cpu_error(22); /* operation not allowed on external symbols */
break;
case PCLRT:
op.type = NOOP;
if (am==AM_L && val==0) { /* ADRL */
isize += 4; /* always reserve two ADD instructions */
if (insn!=NULL && db!=NULL) {
*(insn+1) = *insn & ~0xf0000;
*(insn+1) |= (*insn&0xf000) << 4;
add_extnreloc_masked(&db->relocs,base,val,REL_PC,
arm_be_mode?24:0,8,0,0xff00);
add_extnreloc_masked(&db->relocs,base,val,REL_PC,
arm_be_mode?32+24:32+0,8,0,0xff);
}
}
else if (val == 0) { /* ADR */
if (db)
add_extnreloc_masked(&db->relocs,base,val,REL_PC,
arm_be_mode?24:0,8,0,0xff);
}
else if (db)
cpu_error(22); /* operation not allowed on external symbols */
break;
default:
ierror(0);
}
}
}
else if (insn) {
op.type = NOOP;
cpu_error(2); /* label from current section required */
}
}
else if (op.type == IMROT) {
op.type = NOOP; /* is handled here */
if (base == NULL) {
uint32_t rotval;
if ((rotval = rotated_immediate(val)) != ROTFAIL) {
if (insn)
*insn |= rotval;
}
else if (!negated_rot_immediate(val,mnemo,insn)) {
/* rotation, negation and inversion failed - try a 2nd operation */
isize += 4;
if (insn) {
if (!negated_double_rot_immediate(val,insn))
cpu_error(7,(uint32_t)val); /* const not suitable */
}
}
}
else if (insn)
cpu_error(6); /* constant integer expression required */
}
/* optimizations should be finished at this stage -
inserts operands into the opcode now: */
if (insn) {
if (REGOPER(op.type)) {
/* insert register operand */
if (base!=NULL || val<0 || val>15)
cpu_error(9); /* not a valid ARM register */
if (REG19OPER(op.type))
*insn |= val<<16;
else if (REG15OPER(op.type))
*insn |= val<<12;
else if (REG11OPER(op.type))
*insn |= val<<8;
else if (REG03OPER(op.type))
*insn |= val;
if (op.type==R3UD1 && !(*insn&0x01000000))
cpu_error(21); /* post-indexed addressing mode expected */
if (op.flags & OFL_WBACK)
*insn |= 0x00200000;
if (op.flags & OFL_UP)
*insn |= 0x00800000;
/* some more checks: */
if ((mnemo->ext.flags&NOPC) && val==15)
cpu_error(10); /* PC (r15) not allowed in this mode */
if ((mnemo->ext.flags&NOPCR03) && val==15 && REG03OPER(op.type))
cpu_error(11); /* PC (r15) not allowed for offset register Rm */
if ((mnemo->ext.flags&NOPC) && val==15 && (op.flags&OFL_WBACK))
cpu_error(12); /* PC (r15) not allowed with write-back */
/* check for illegal double register specifications */
if (((mnemo->ext.flags&DIFR03) && REG03OPER(op.type)) ||
((mnemo->ext.flags&DIFR11) && REG11OPER(op.type)) ||
((mnemo->ext.flags&DIFR15) && REG15OPER(op.type)) ||
((mnemo->ext.flags&DIFR19) && REG19OPER(op.type))) {
if (chkreg != -1) {
if (val == chkreg)
cpu_error(13,(long)val); /* register was used multiple times */
}
else
chkreg = val;
}
}
else if (op.type == BRA24) {
/* only write offset, relocs and optimizations are handled above */
if (val & 3)
cpu_error(8,(long)val); /* branch to unaligned address */
*insn |= (val>>2) & 0xffffff;
}
else if (op.type==IMUD1 || op.type==IMUD2) {
if (aa4ldst) {
/* insert splitted 8-bit immediate for signed/halfword ldr/str */
if (val>=0 && val<=0xff) {
*insn |= ((val&0xf0)<<4) | (val&0x0f);
}
else
cpu_error(20,8,(long)val); /* immediate offset out of range */
}
else {
/* insert immediate 12-bit with up/down flag */
if (val>=0 && val<=0xfff) {
*insn |= val;
}
else
cpu_error(20,12,(long)val); /* immediate offset out of range */
}
if (op.type==IMUD1 && !(*insn&0x01000000))
cpu_error(21); /* post-indexed addressing mode exptected */
if (op.flags & OFL_WBACK)
*insn |= 0x00200000; /* set write-back flag */
if (op.flags & OFL_UP)
*insn |= 0x00800000; /* set UP-flag */
if (base) {
if (btype == BASE_OK) {
if (EXTREF(base)) {
if (!aa4ldst) {
/* @@@ does this make any sense? */
*insn |= 0x00800000; /* only UP */
add_extnreloc_masked(&db->relocs,base,val,REL_ABS,
arm_be_mode?20:0,12,0,0xfff);
}
else
cpu_error(22); /* operation not allowed on external symbols */
}
else
cpu_error(6); /* constant integer expression required */
}
else
general_error(38); /* illegal relocation */
}
}
else if (op.type==IMCP1 || op.type==IMCP2) {
/* insert immediate 10-bit shifted left by 2, with up/down flag */
if (val>=0 && val<=0x3ff) {
if ((val & 3) == 0)
*insn |= val>>2;
else
cpu_error(23); /* ldc/stc offset has to be a multiple of 4 */
}
else
cpu_error(20,10,(long)val); /* immediate offset out of range */
if (op.flags & OFL_WBACK)
*insn |= 0x00200000; /* set write-back flag */
if (op.flags & OFL_UP)
*insn |= 0x00800000; /* set UP-flag */
if (base)
cpu_error(6); /* constant integer expression required */
}
else if (op.type == SWI24) {
/* insert 24-bit immediate (SWI instruction) */
if (val>=0 && val<0x1000000) {
*insn |= val;
if (base!=NULL && db!=NULL)
add_extnreloc_masked(&db->relocs,base,val,REL_ABS,
arm_be_mode?8:0,24,0,0xffffff);
}
else
cpu_error(16); /* 24-bit unsigned immediate expected */
if (base)
cpu_error(6); /* constant integer expression required */
}
else if (op.type == IROTV) {
/* insert 4-bit rotate constant (even value, shifted right) */
if (val>=0 && val<=30 && (val&1)==0)
*insn |= val << 7;
else
cpu_error(29,(long)val); /* must be even number between 0 and 30 */
if (base)
cpu_error(6); /* constant integer expression required */
}
else if (op.type == IMMD8) {
/* unsigned 8-bit immediate constant, used together with IROTV */
if (val>=0 && val<0x100 && base==NULL)
*insn |= val;
else
cpu_error(30,8,(long)val); /* 8-bit unsigned constant required */
}
else if (SHIFTOPER(op.type)) {
/* insert a register- or immediate shift */
int sh_op = op.flags & OFL_SHIFTOP;
if (aa4ldst)
cpu_error(19); /* signed/halfword ldr/str doesn't support shifts */
if (op.type==SHIM1 && !(*insn&0x01000000))
cpu_error(21); /* post-indexed addressing mode exptected */
if (op.flags & OFL_IMMEDSHIFT) {
if (sh_op==1 || sh_op==2) { /* lsr/asr permit shift-count #32 */
if (val == 32)
val = 0;
}
if (base==NULL && val>=0 && val<32) {
*insn |= (val<<7) | ((op.flags&OFL_SHIFTOP)<<5);
if (op.flags & OFL_WBACK)
*insn |= 0x00200000;
}
else
cpu_error(14,(long)val); /* illegal immediate shift count */
}
else { /* shift count in register */
if (base==NULL && val>=0 && val<16) {
*insn |= (val<<8) | ((op.flags&OFL_SHIFTOP)<<5) | 0x10;
}
else
cpu_error(15); /* not a valid shift register */
}
}
else if (CPOPCODE(op.type)) {
/* insert coprocessor operation/type */
if (base == NULL) {
switch (op.type) {
case CPOP3:
if (val>=0 && val<8)
*insn |= val<<21;
else
cpu_error(24,val); /* illegal coprocessor operation */
break;
case CPOP4:
if (val>=0 && val<16)
*insn |= val<<20;
else
cpu_error(24,val); /* illegal coprocessor operation */
break;
case CPTYP:
if (val>=0 && val<8)
*insn |= val<<5;
else
cpu_error(24,val); /* illegal coprocessor operation */
break;
default: ierror(0);
}
}
else
cpu_error(24,val); /* illegal coprocessor operation */
}
else if (op.type==CSPSR || op.type==PSR_F) {
/* insert PSR type - no checks needed */
*insn |= val<<16;
if (op.flags & OFL_SPSR)
*insn |= 0x00400000;
}
else if (op.type == RLIST) {
/* insert register-list field */
if (am<AM_DA || am>AM_ED)
cpu_error(18,addrmode_strings[am]); /* illegal addr. mode */
if (am>=AM_FA && am<=AM_ED) {
/* fix stack-addressing mode */
if (!(mnemo->ext.opcode & 0x00100000))
am ^= 3; /* invert P/U modes for store operations */
}
*insn |= ((am&3)<<23) | val;
if (op.flags & OFL_FORCE)
*insn |= 0x00400000;
}
else if (op.type != NOOP)
ierror(0);
}
}
return isize;
}
size_t instruction_size(instruction *ip,section *sec,taddr pc)
/* Calculate the size of the current instruction; must be identical
to the data created by eval_instruction. */
{
if (mnemonics[ip->code].ext.flags & THUMB)
return eval_thumb_operands(ip,sec,pc,NULL,NULL);
/* ARM mode */
return eval_arm_operands(ip,sec,pc,NULL,NULL);
}
dblock *eval_instruction(instruction *ip,section *sec,taddr pc)
/* Convert an instruction into a DATA atom including relocations,
if necessary. */
{
dblock *db = new_dblock();
int inst_type;
if (sec != last_section) {
last_section = sec;
last_data_type = -1;
}
inst_type = (mnemonics[ip->code].ext.flags & THUMB) ? TYPE_THUMB : TYPE_ARM;
if (inst_type == TYPE_THUMB) {
uint16_t insn[2];
if (db->size = eval_thumb_operands(ip,sec,pc,insn,db)) {
unsigned char *d = db->data = mymalloc(db->size);
int i;
for (i=0; i<db->size/2; i++)
d = setval(arm_be_mode,d,2,insn[i]);
}
}
else { /* ARM mode */
uint32_t insn[2];
if (db->size = eval_arm_operands(ip,sec,pc,insn,db)) {
unsigned char *d = db->data = mymalloc(db->size);
int i;
for (i=0; i<db->size/4; i++)
d = setval(arm_be_mode,d,4,insn[i]);
}
}
if (inst_type != last_data_type)
create_mapping_symbol(inst_type,sec,pc);
return db;
}
dblock *eval_data(operand *op,size_t bitsize,section *sec,taddr pc)
/* Create a dblock (with relocs, if necessary) for size bits of data. */
{
dblock *db = new_dblock();
taddr val;
if (sec != last_section) {
last_section = sec;
last_data_type = -1;
}
if ((bitsize & 7) || bitsize > 64)
cpu_error(17,bitsize); /* data size not supported */
if (op->type!=DATA_OP && op->type!=DATA64_OP)
ierror(0);
db->size = bitsize >> 3;
db->data = mymalloc(db->size);
if (op->type == DATA64_OP) {
thuge hval;
if (!eval_expr_huge(op->value,&hval))
general_error(59); /* cannot evaluate huge integer */
huge_to_mem(arm_be_mode,db->data,db->size,hval);
}
else {
if (!eval_expr(op->value,&val,sec,pc)) {
symbol *base;
int btype;
btype = find_base(op->value,&base,sec,pc);
if (base)
add_extnreloc(&db->relocs,base,val,
btype==BASE_PCREL?REL_PC:REL_ABS,0,bitsize,0);
else if (btype != BASE_NONE)
general_error(38); /* illegal relocation */
}
switch (db->size) {
case 1:
db->data[0] = val & 0xff;
break;
case 2:
case 4:
setval(arm_be_mode,db->data,db->size,val);
break;
default:
ierror(0);
break;
}
}
if (last_data_type != TYPE_DATA)
create_mapping_symbol(TYPE_DATA,sec,pc);
return db;
}
int init_cpu()
{
char r[4];
int i;
for (i=0; i<mnemonic_cnt; i++) {
if (!strcmp(mnemonics[i].name,"swp"))
OC_SWP = i;
else if (!strcmp(mnemonics[i].name,"nop"))
OC_NOP = i;
}
if (!strcmp(output_format,"elf"))
elfoutput = 1;
/* define register symbols */
for (i=0; i<16; i++) {
sprintf(r,"r%d",i);
new_regsym(0,1,r,0,0,i);
sprintf(r,"c%d",i);
new_regsym(0,1,r,0,0,i);
sprintf(r,"p%d",i);
new_regsym(0,1,r,0,0,i);
}
/* ATPCS synonyms */
for (i=0; i<8; i++) {
if (i < 4) {
sprintf(r,"a%d",i+1);
new_regsym(0,1,r,0,0,i);
}
sprintf(r,"v%d",i+1);
new_regsym(0,1,r,0,0,i+4);
}
/* well known aliases */
new_regsym(0,1,"wr",0,0,7);
new_regsym(0,1,"sb",0,0,9);
new_regsym(0,1,"sl",0,0,10);
new_regsym(0,1,"fp",0,0,11);
new_regsym(0,1,"ip",0,0,12);
new_regsym(0,1,"sp",0,0,13);
new_regsym(0,1,"lr",0,0,14);
new_regsym(0,1,"pc",0,0,15);
/* instruction alignment, determined by thumb-mode */
if (inst_alignment > 1)
inst_alignment = thumb_mode ? 2 : 4;
return 1;
}
int cpu_args(char *p)
{
if (!strncmp(p,"-m",2)) {
p += 2;
if (!strcmp(p,"2")) cpu_type = ARM2;
else if (!strcmp(p,"250")) cpu_type = ARM250;
else if (!strcmp(p,"3")) cpu_type = ARM3;
else if (!strcmp(p,"6")) cpu_type = ARM6;
else if (!strcmp(p,"600")) cpu_type = ARM600;
else if (!strcmp(p,"610")) cpu_type = ARM610;
else if (!strcmp(p,"7")) cpu_type = ARM7;
else if (!strcmp(p,"710")) cpu_type = ARM710;
else if (!strcmp(p,"7500")) cpu_type = ARM7500;
else if (!strcmp(p,"7d")) cpu_type = ARM7d;
else if (!strcmp(p,"7di")) cpu_type = ARM7di;
else if (!strcmp(p,"7dm")) cpu_type = ARM7dm;
else if (!strcmp(p,"7dmi")) cpu_type = ARM7dmi;
else if (!strcmp(p,"7tdmi")) cpu_type = ARM7tdmi;
else if (!strcmp(p,"8")) cpu_type = ARM8;
else if (!strcmp(p,"810")) cpu_type = ARM810;
else if (!strcmp(p,"9")) cpu_type = ARM9;
else if (!strcmp(p,"920")) cpu_type = ARM920;
else if (!strcmp(p,"920t")) cpu_type = ARM920t;
else if (!strcmp(p,"9tdmi")) cpu_type = ARM9tdmi;
else if (!strcmp(p,"sa1")) cpu_type = SA1;
else if (!strcmp(p,"strongarm")) cpu_type = STRONGARM;
else if (!strcmp(p,"strongarm110")) cpu_type = STRONGARM110;
else if (!strcmp(p,"strongarm1100")) cpu_type = STRONGARM1100;
else return 0;
}
else if (!strncmp(p,"-a",2)) {
p += 2;
if (!strcmp(p,"2")) cpu_type = AA2;
else if (!strcmp(p,"3")) cpu_type = AA3;
else if (!strcmp(p,"3m")) cpu_type = AA3M;
else if (!strcmp(p,"4")) cpu_type = AA4;
else if (!strcmp(p,"4t")) cpu_type = AA4T;
else return 0;
}
else if (!strcmp(p,"-little"))
arm_be_mode = 0;
else if (!strcmp(p,"-big"))
arm_be_mode = 1;
else if (!strcmp(p,"-thumb"))
thumb_mode = 1;
else if (!strcmp(p,"-opt-ldrpc"))
opt_ldrpc = 1;
else if (!strcmp(p,"-opt-adr"))
opt_adr = 1;
return 1;
}