/* tc-cr16.c -- Assembler code for the CR16 CPU core. Copyright (C) 2007-2021 Free Software Foundation, Inc. Contributed by M R Swami Reddy This file is part of GAS, the GNU Assembler. GAS is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GAS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GAS; see the file COPYING. If not, write to the Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "as.h" #include "safe-ctype.h" #include "dwarf2dbg.h" #include "opcode/cr16.h" #include "elf/cr16.h" #include #ifndef CHAR_BIT #define CHAR_BIT 8 #endif /* Word is considered here as a 16-bit unsigned short int. */ #define WORD_SHIFT 16 /* Register is 2-byte size. */ #define REG_SIZE 2 /* Maximum size of a single instruction (in words). */ #define INSN_MAX_SIZE 3 /* Maximum bits which may be set in a `mask16' operand. */ #define MAX_REGS_IN_MASK16 8 /* Assign a number NUM, shifted by SHIFT bytes, into a location pointed by index BYTE of array 'output_opcode'. */ #define CR16_PRINT(BYTE, NUM, SHIFT) output_opcode[BYTE] |= (NUM) << (SHIFT) /* Operand errors. */ typedef enum { OP_LEGAL = 0, /* Legal operand. */ OP_OUT_OF_RANGE, /* Operand not within permitted range. */ OP_NOT_EVEN /* Operand is Odd number, should be even. */ } op_err; /* Opcode mnemonics hash table. */ static htab_t cr16_inst_hash; /* CR16 registers hash table. */ static htab_t reg_hash; /* CR16 register pair hash table. */ static htab_t regp_hash; /* CR16 processor registers hash table. */ static htab_t preg_hash; /* CR16 processor registers 32 bit hash table. */ static htab_t pregp_hash; /* Current instruction we're assembling. */ const inst *instruction; static int code_label = 0; /* Global variables. */ /* Array to hold an instruction encoding. */ long output_opcode[2]; /* Nonzero means a relocatable symbol. */ int relocatable; /* A copy of the original instruction (used in error messages). */ char ins_parse[MAX_INST_LEN]; /* The current processed argument number. */ int cur_arg_num; /* Generic assembler global variables which must be defined by all targets. */ /* Characters which always start a comment. */ const char comment_chars[] = "#"; /* Characters which start a comment at the beginning of a line. */ const char line_comment_chars[] = "#"; /* This array holds machine specific line separator characters. */ const char line_separator_chars[] = ";"; /* Chars that can be used to separate mant from exp in floating point nums. */ const char EXP_CHARS[] = "eE"; /* Chars that mean this number is a floating point constant as in 0f12.456 */ const char FLT_CHARS[] = "f'"; #ifdef OBJ_ELF /* Pre-defined "_GLOBAL_OFFSET_TABLE_" */ symbolS * GOT_symbol; #endif /* Target-specific multicharacter options, not const-declared at usage. */ const char *md_shortopts = ""; struct option md_longopts[] = { {NULL, no_argument, NULL, 0} }; size_t md_longopts_size = sizeof (md_longopts); static void l_cons (int nbytes) { int c; expressionS exp; #ifdef md_flush_pending_output md_flush_pending_output (); #endif if (is_it_end_of_statement ()) { demand_empty_rest_of_line (); return; } #ifdef TC_ADDRESS_BYTES if (nbytes == 0) nbytes = TC_ADDRESS_BYTES (); #endif #ifdef md_cons_align md_cons_align (nbytes); #endif c = 0; do { unsigned int bits_available = BITS_PER_CHAR * nbytes; char *hold = input_line_pointer; expression (&exp); if (*input_line_pointer == ':') { /* Bitfields. */ long value = 0; for (;;) { unsigned long width; if (*input_line_pointer != ':') { input_line_pointer = hold; break; } if (exp.X_op == O_absent) { as_warn (_("using a bit field width of zero")); exp.X_add_number = 0; exp.X_op = O_constant; } if (exp.X_op != O_constant) { *input_line_pointer = '\0'; as_bad (_("field width \"%s\" too complex for a bitfield"), hold); *input_line_pointer = ':'; demand_empty_rest_of_line (); return; } if ((width = exp.X_add_number) > (unsigned int)(BITS_PER_CHAR * nbytes)) { as_warn (ngettext ("field width %lu too big to fit in %d" " byte: truncated to %d bits", "field width %lu too big to fit in %d" " bytes: truncated to %d bits", nbytes), width, nbytes, (BITS_PER_CHAR * nbytes)); width = BITS_PER_CHAR * nbytes; } if (width > bits_available) { /* FIXME-SOMEDAY: backing up and reparsing is wasteful. */ input_line_pointer = hold; exp.X_add_number = value; break; } /* Skip ':'. */ hold = ++input_line_pointer; expression (&exp); if (exp.X_op != O_constant) { char cache = *input_line_pointer; *input_line_pointer = '\0'; as_bad (_("field value \"%s\" too complex for a bitfield"), hold); *input_line_pointer = cache; demand_empty_rest_of_line (); return; } value |= ((~(-(1 << width)) & exp.X_add_number) << ((BITS_PER_CHAR * nbytes) - bits_available)); if ((bits_available -= width) == 0 || is_it_end_of_statement () || *input_line_pointer != ',') break; hold = ++input_line_pointer; expression (&exp); } exp.X_add_number = value; exp.X_op = O_constant; exp.X_unsigned = 1; } if ((*(input_line_pointer) == '@') && (*(input_line_pointer +1) == 'c')) code_label = 1; emit_expr (&exp, (unsigned int) nbytes); ++c; if ((*(input_line_pointer) == '@') && (*(input_line_pointer +1) == 'c')) { input_line_pointer +=3; break; } } while ((*input_line_pointer++ == ',')); /* Put terminator back into stream. */ input_line_pointer--; demand_empty_rest_of_line (); } /* This table describes all the machine specific pseudo-ops the assembler has to support. The fields are: *** Pseudo-op name without dot. *** Function to call to execute this pseudo-op. *** Integer arg to pass to the function. */ const pseudo_typeS md_pseudo_table[] = { /* In CR16 machine, align is in bytes (not a ptwo boundary). */ {"align", s_align_bytes, 0}, {"long", l_cons, 4 }, {"4byte", l_cons, 4 }, {0, 0, 0} }; /* CR16 relaxation table. */ const relax_typeS md_relax_table[] = { /* bCC */ {0x7f, -0x80, 2, 1}, /* 8 */ {0xfffe, -0x10000, 4, 2}, /* 16 */ {0xfffffe, -0x1000000, 6, 0}, /* 24 */ }; /* Return the bit size for a given operand. */ static int get_opbits (operand_type op) { if (op < MAX_OPRD) return cr16_optab[op].bit_size; return 0; } /* Return the argument type of a given operand. */ static argtype get_optype (operand_type op) { if (op < MAX_OPRD) return cr16_optab[op].arg_type; else return nullargs; } /* Return the flags of a given operand. */ static int get_opflags (operand_type op) { if (op < MAX_OPRD) return cr16_optab[op].flags; return 0; } /* Get the cc code. */ static int get_cc (char *cc_name) { unsigned int i; for (i = 0; i < cr16_num_cc; i++) if (strcmp (cc_name, cr16_b_cond_tab[i]) == 0) return i; return -1; } /* Get the core processor register 'reg_name'. */ static reg get_register (char *reg_name) { const reg_entry *rreg; rreg = (const reg_entry *) str_hash_find (reg_hash, reg_name); if (rreg != NULL) return rreg->value.reg_val; return nullregister; } /* Get the core processor register-pair 'reg_name'. */ static reg get_register_pair (char *reg_name) { const reg_entry *rreg; char tmp_rp[16]="\0"; /* Add '(' and ')' to the reg pair, if it's not present. */ if (reg_name[0] != '(') { tmp_rp[0] = '('; strcat (tmp_rp, reg_name); strcat (tmp_rp,")"); rreg = (const reg_entry *) str_hash_find (regp_hash, tmp_rp); } else rreg = (const reg_entry *) str_hash_find (regp_hash, reg_name); if (rreg != NULL) return rreg->value.reg_val; return nullregister; } /* Get the index register 'reg_name'. */ static reg get_index_register (char *reg_name) { const reg_entry *rreg; rreg = (const reg_entry *) str_hash_find (reg_hash, reg_name); if ((rreg != NULL) && ((rreg->value.reg_val == 12) || (rreg->value.reg_val == 13))) return rreg->value.reg_val; return nullregister; } /* Get the core processor index register-pair 'reg_name'. */ static reg get_index_register_pair (char *reg_name) { const reg_entry *rreg; rreg = (const reg_entry *) str_hash_find (regp_hash, reg_name); if (rreg != NULL) { if ((rreg->value.reg_val != 1) || (rreg->value.reg_val != 7) || (rreg->value.reg_val != 9) || (rreg->value.reg_val > 10)) return rreg->value.reg_val; as_bad (_("Unknown register pair - index relative mode: `%d'"), rreg->value.reg_val); } return nullregister; } /* Get the processor register 'preg_name'. */ static preg get_pregister (char *preg_name) { const reg_entry *prreg; prreg = (const reg_entry *) str_hash_find (preg_hash, preg_name); if (prreg != NULL) return prreg->value.preg_val; return nullpregister; } /* Get the processor register 'preg_name 32 bit'. */ static preg get_pregisterp (char *preg_name) { const reg_entry *prreg; prreg = (const reg_entry *) str_hash_find (pregp_hash, preg_name); if (prreg != NULL) return prreg->value.preg_val; return nullpregister; } /* Round up a section size to the appropriate boundary. */ valueT md_section_align (segT seg, valueT val) { /* Round .text section to a multiple of 2. */ if (seg == text_section) return (val + 1) & ~1; return val; } /* Parse an operand that is machine-specific (remove '*'). */ void md_operand (expressionS * exp) { char c = *input_line_pointer; switch (c) { case '*': input_line_pointer++; expression (exp); break; default: break; } } /* Reset global variables before parsing a new instruction. */ static void reset_vars (char *op) { cur_arg_num = relocatable = 0; memset (& output_opcode, '\0', sizeof (output_opcode)); /* Save a copy of the original OP (used in error messages). */ strncpy (ins_parse, op, sizeof ins_parse - 1); ins_parse [sizeof ins_parse - 1] = 0; } /* This macro decides whether a particular reloc is an entry in a switch table. It is used when relaxing, because the linker needs to know about all such entries so that it can adjust them if necessary. */ #define SWITCH_TABLE(fix) \ ((fix)->fx_addsy != NULL \ && (fix)->fx_subsy != NULL \ && ((fix)->fx_r_type == BFD_RELOC_CR16_NUM8 \ || (fix)->fx_r_type == BFD_RELOC_CR16_NUM16 \ || (fix)->fx_r_type == BFD_RELOC_CR16_NUM32 \ || (fix)->fx_r_type == BFD_RELOC_CR16_NUM32a) \ && S_GET_SEGMENT ((fix)->fx_addsy) != undefined_section \ && S_GET_SEGMENT ((fix)->fx_addsy) == S_GET_SEGMENT ((fix)->fx_subsy)) /* See whether we need to force a relocation into the output file. This is used to force out switch and PC relative relocations when relaxing. */ int cr16_force_relocation (fixS *fix) { if (generic_force_reloc (fix) || SWITCH_TABLE (fix)) return 1; return 0; } /* Record a fixup for a cons expression. */ void cr16_cons_fix_new (fragS *frag, int offset, int len, expressionS *exp, bfd_reloc_code_real_type rtype) { switch (len) { default: rtype = BFD_RELOC_NONE; break; case 1: rtype = BFD_RELOC_CR16_NUM8 ; break; case 2: rtype = BFD_RELOC_CR16_NUM16; break; case 4: if (code_label) { rtype = BFD_RELOC_CR16_NUM32a; code_label = 0; } else rtype = BFD_RELOC_CR16_NUM32; break; } fix_new_exp (frag, offset, len, exp, 0, rtype); } /* Generate a relocation entry for a fixup. */ arelent * tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS * fixP) { arelent * reloc; /* If symbols are local and resolved, then no relocation needed. */ if ( ((fixP->fx_addsy) && (S_GET_SEGMENT (fixP->fx_addsy) == absolute_section)) || ((fixP->fx_subsy) && (S_GET_SEGMENT (fixP->fx_subsy) == absolute_section))) return NULL; reloc = XNEW (arelent); reloc->sym_ptr_ptr = XNEW (asymbol *); *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixP->fx_addsy); reloc->address = fixP->fx_frag->fr_address + fixP->fx_where; reloc->addend = fixP->fx_offset; if (fixP->fx_subsy != NULL) { if (SWITCH_TABLE (fixP)) { /* Keep the current difference in the addend. */ reloc->addend = (S_GET_VALUE (fixP->fx_addsy) - S_GET_VALUE (fixP->fx_subsy) + fixP->fx_offset); switch (fixP->fx_r_type) { case BFD_RELOC_CR16_NUM8: fixP->fx_r_type = BFD_RELOC_CR16_SWITCH8; break; case BFD_RELOC_CR16_NUM16: fixP->fx_r_type = BFD_RELOC_CR16_SWITCH16; break; case BFD_RELOC_CR16_NUM32: fixP->fx_r_type = BFD_RELOC_CR16_SWITCH32; break; case BFD_RELOC_CR16_NUM32a: fixP->fx_r_type = BFD_RELOC_CR16_NUM32a; break; default: abort (); break; } } else { /* We only resolve difference expressions in the same section. */ as_bad_subtract (fixP); free (reloc->sym_ptr_ptr); free (reloc); return NULL; } } #ifdef OBJ_ELF if ((fixP->fx_r_type == BFD_RELOC_CR16_GOT_REGREL20) && GOT_symbol && fixP->fx_addsy == GOT_symbol) { reloc->addend = fixP->fx_offset = reloc->address; } else if ((fixP->fx_r_type == BFD_RELOC_CR16_GOTC_REGREL20) && GOT_symbol && fixP->fx_addsy == GOT_symbol) { reloc->addend = fixP->fx_offset = reloc->address; } #endif gas_assert ((int) fixP->fx_r_type > 0); reloc->howto = bfd_reloc_type_lookup (stdoutput, fixP->fx_r_type); if (reloc->howto == NULL) { as_bad_where (fixP->fx_file, fixP->fx_line, _("internal error: reloc %d (`%s') not supported by object file format"), fixP->fx_r_type, bfd_get_reloc_code_name (fixP->fx_r_type)); return NULL; } gas_assert (!fixP->fx_pcrel == !reloc->howto->pc_relative); return reloc; } /* Prepare machine-dependent frags for relaxation. */ int md_estimate_size_before_relax (fragS *fragp, asection *seg) { /* If symbol is undefined or located in a different section, select the largest supported relocation. */ relax_substateT subtype; relax_substateT rlx_state[] = {0, 2}; for (subtype = 0; subtype < ARRAY_SIZE (rlx_state); subtype += 2) { if (fragp->fr_subtype == rlx_state[subtype] && (!S_IS_DEFINED (fragp->fr_symbol) || seg != S_GET_SEGMENT (fragp->fr_symbol))) { fragp->fr_subtype = rlx_state[subtype + 1]; break; } } if (fragp->fr_subtype >= ARRAY_SIZE (md_relax_table)) abort (); return md_relax_table[fragp->fr_subtype].rlx_length; } void md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, fragS *fragP) { /* 'opcode' points to the start of the instruction, whether we need to change the instruction's fixed encoding. */ char *opcode = &fragP->fr_literal[0] + fragP->fr_fix; bfd_reloc_code_real_type reloc; subseg_change (sec, 0); switch (fragP->fr_subtype) { case 0: reloc = BFD_RELOC_CR16_DISP8; break; case 1: /* If the subtype is not changed due to :m operand qualifier, then no need to update the opcode value. */ if ((int)opcode[1] != 0x18) { opcode[0] = (opcode[0] & 0xf0); opcode[1] = 0x18; } reloc = BFD_RELOC_CR16_DISP16; break; case 2: /* If the subtype is not changed due to :l operand qualifier, then no need to update the opcode value. */ if ((int)opcode[1] != 0) { opcode[2] = opcode[0]; opcode[0] = opcode[1]; opcode[1] = 0x0; } reloc = BFD_RELOC_CR16_DISP24; break; default: abort(); } fix_new (fragP, fragP->fr_fix, bfd_get_reloc_size (bfd_reloc_type_lookup (stdoutput, reloc)), fragP->fr_symbol, fragP->fr_offset, 1, reloc); fragP->fr_var = 0; fragP->fr_fix += md_relax_table[fragP->fr_subtype].rlx_length; } symbolS * md_undefined_symbol (char *name) { if (*name == '_' && *(name + 1) == 'G' && strcmp (name, "_GLOBAL_OFFSET_TABLE_") == 0) { if (!GOT_symbol) { if (symbol_find (name)) as_bad (_("GOT already in symbol table")); GOT_symbol = symbol_new (name, undefined_section, &zero_address_frag, 0); } return GOT_symbol; } return 0; } /* Process machine-dependent command line options. Called once for each option on the command line that the machine-independent part of GAS does not understand. */ int md_parse_option (int c ATTRIBUTE_UNUSED, const char *arg ATTRIBUTE_UNUSED) { return 0; } /* Machine-dependent usage-output. */ void md_show_usage (FILE *stream ATTRIBUTE_UNUSED) { return; } const char * md_atof (int type, char *litP, int *sizeP) { return ieee_md_atof (type, litP, sizeP, target_big_endian); } /* Apply a fixS (fixup of an instruction or data that we didn't have enough info to complete immediately) to the data in a frag. Since linkrelax is nonzero and TC_LINKRELAX_FIXUP is defined to disable relaxation of debug sections, this function is called only when fixuping relocations of debug sections. */ void md_apply_fix (fixS *fixP, valueT *valP, segT seg) { valueT val = * valP; if (fixP->fx_addsy == NULL && fixP->fx_pcrel == 0) fixP->fx_done = 1; else if (fixP->fx_pcrel == 1 && fixP->fx_addsy != NULL && S_GET_SEGMENT (fixP->fx_addsy) == seg) fixP->fx_done = 1; else fixP->fx_done = 0; if (fixP->fx_addsy != NULL && !fixP->fx_pcrel) { val = fixP->fx_offset; fixP->fx_done = 1; } if (fixP->fx_done) { char *buf = fixP->fx_frag->fr_literal + fixP->fx_where; fixP->fx_offset = 0; switch (fixP->fx_r_type) { case BFD_RELOC_CR16_NUM8: bfd_put_8 (stdoutput, (unsigned char) val, buf); break; case BFD_RELOC_CR16_NUM16: bfd_put_16 (stdoutput, val, buf); break; case BFD_RELOC_CR16_NUM32: bfd_put_32 (stdoutput, val, buf); break; case BFD_RELOC_CR16_NUM32a: bfd_put_32 (stdoutput, val, buf); break; default: /* We shouldn't ever get here because linkrelax is nonzero. */ abort (); break; } fixP->fx_done = 0; } else fixP->fx_offset = * valP; } /* The location from which a PC relative jump should be calculated, given a PC relative reloc. */ long md_pcrel_from (fixS *fixp) { return fixp->fx_frag->fr_address + fixp->fx_where; } static void initialise_reg_hash_table (htab_t *hash_table, const reg_entry *register_table, const unsigned int num_entries) { const reg_entry *rreg; *hash_table = str_htab_create (); for (rreg = register_table; rreg < (register_table + num_entries); rreg++) if (str_hash_insert (*hash_table, rreg->name, rreg, 0) != NULL) as_fatal (_("duplicate %s"), rreg->name); } /* This function is called once, at assembler startup time. This should set up all the tables, etc that the MD part of the assembler needs. */ void md_begin (void) { int i = 0; /* Set up a hash table for the instructions. */ cr16_inst_hash = str_htab_create (); while (cr16_instruction[i].mnemonic != NULL) { const char *mnemonic = cr16_instruction[i].mnemonic; if (str_hash_insert (cr16_inst_hash, mnemonic, cr16_instruction + i, 0)) as_fatal (_("duplicate %s"), mnemonic); /* Insert unique names into hash table. The CR16 instruction set has many identical opcode names that have different opcodes based on the operands. This hash table then provides a quick index to the first opcode with a particular name in the opcode table. */ do { ++i; } while (cr16_instruction[i].mnemonic != NULL && streq (cr16_instruction[i].mnemonic, mnemonic)); } /* Initialize reg_hash hash table. */ initialise_reg_hash_table (& reg_hash, cr16_regtab, NUMREGS); /* Initialize regp_hash hash table. */ initialise_reg_hash_table (& regp_hash, cr16_regptab, NUMREGPS); /* Initialize preg_hash hash table. */ initialise_reg_hash_table (& preg_hash, cr16_pregtab, NUMPREGS); /* Initialize pregp_hash hash table. */ initialise_reg_hash_table (& pregp_hash, cr16_pregptab, NUMPREGPS); /* Set linkrelax here to avoid fixups in most sections. */ linkrelax = 1; } /* Process constants (immediate/absolute) and labels (jump targets/Memory locations). */ static void process_label_constant (char *str, ins * cr16_ins) { char *saved_input_line_pointer; int symbol_with_at = 0; int symbol_with_s = 0; int symbol_with_m = 0; int symbol_with_l = 0; int symbol_with_at_got = 0; int symbol_with_at_gotc = 0; argument *cur_arg = cr16_ins->arg + cur_arg_num; /* Current argument. */ saved_input_line_pointer = input_line_pointer; input_line_pointer = str; expression (&cr16_ins->exp); switch (cr16_ins->exp.X_op) { case O_big: case O_absent: /* Missing or bad expr becomes absolute 0. */ as_bad (_("missing or invalid displacement expression `%s' taken as 0"), str); cr16_ins->exp.X_op = O_constant; cr16_ins->exp.X_add_number = 0; cr16_ins->exp.X_add_symbol = NULL; cr16_ins->exp.X_op_symbol = NULL; /* Fall through. */ case O_constant: cur_arg->X_op = O_constant; cur_arg->constant = cr16_ins->exp.X_add_number; break; case O_symbol: case O_subtract: case O_add: cur_arg->X_op = O_symbol; cur_arg->constant = cr16_ins->exp.X_add_number; cr16_ins->exp.X_add_number = 0; cr16_ins->rtype = BFD_RELOC_NONE; relocatable = 1; if (startswith (input_line_pointer, "@c")) symbol_with_at = 1; if (startswith (input_line_pointer, "@l") || startswith (input_line_pointer, ":l")) symbol_with_l = 1; if (startswith (input_line_pointer, "@m") || startswith (input_line_pointer, ":m")) symbol_with_m = 1; if (startswith (input_line_pointer, "@s") || startswith (input_line_pointer, ":s")) symbol_with_s = 1; if (startswith (input_line_pointer, "@cGOT") || startswith (input_line_pointer, "@cgot")) { if (GOT_symbol == NULL) GOT_symbol = symbol_find_or_make (GLOBAL_OFFSET_TABLE_NAME); symbol_with_at_gotc = 1; } else if (startswith (input_line_pointer, "@GOT") || startswith (input_line_pointer, "@got")) { if ((startswith (input_line_pointer, "+")) || (startswith (input_line_pointer, "-"))) as_warn (_("GOT bad expression with %s."), input_line_pointer); if (GOT_symbol == NULL) GOT_symbol = symbol_find_or_make (GLOBAL_OFFSET_TABLE_NAME); symbol_with_at_got = 1; } switch (cur_arg->type) { case arg_cr: if (IS_INSN_TYPE (LD_STOR_INS) || IS_INSN_TYPE (CSTBIT_INS)) { if (symbol_with_at_got) cr16_ins->rtype = BFD_RELOC_CR16_GOT_REGREL20; else if (symbol_with_at_gotc) cr16_ins->rtype = BFD_RELOC_CR16_GOTC_REGREL20; else if (cur_arg->size == 20) cr16_ins->rtype = BFD_RELOC_CR16_REGREL20; else cr16_ins->rtype = BFD_RELOC_CR16_REGREL20a; } break; case arg_crp: if (IS_INSN_TYPE (LD_STOR_INS) || IS_INSN_TYPE (CSTBIT_INS)) { if (symbol_with_at_got) cr16_ins->rtype = BFD_RELOC_CR16_GOT_REGREL20; else if (symbol_with_at_gotc) cr16_ins->rtype = BFD_RELOC_CR16_GOTC_REGREL20; } else { switch (instruction->size) { case 1: switch (cur_arg->size) { case 0: cr16_ins->rtype = BFD_RELOC_CR16_REGREL0; break; case 4: if (IS_INSN_MNEMONIC ("loadb") || IS_INSN_MNEMONIC ("storb")) cr16_ins->rtype = BFD_RELOC_CR16_REGREL4; else cr16_ins->rtype = BFD_RELOC_CR16_REGREL4a; break; default: break; } break; case 2: cr16_ins->rtype = BFD_RELOC_CR16_REGREL16; break; case 3: if (cur_arg->size == 20) cr16_ins->rtype = BFD_RELOC_CR16_REGREL20; else cr16_ins->rtype = BFD_RELOC_CR16_REGREL20a; break; default: break; } } break; case arg_idxr: if (IS_INSN_TYPE (LD_STOR_INS) || IS_INSN_TYPE (CSTBIT_INS)) { if (symbol_with_at_got) cr16_ins->rtype = BFD_RELOC_CR16_GOT_REGREL20; else if (symbol_with_at_gotc) cr16_ins->rtype = BFD_RELOC_CR16_GOTC_REGREL20; else cr16_ins->rtype = BFD_RELOC_CR16_REGREL20; } break; case arg_idxrp: if (IS_INSN_TYPE (LD_STOR_INS) || IS_INSN_TYPE (CSTBIT_INS)) { if (symbol_with_at_got) cr16_ins->rtype = BFD_RELOC_CR16_GOT_REGREL20; else if (symbol_with_at_gotc) cr16_ins->rtype = BFD_RELOC_CR16_GOTC_REGREL20; else { switch (instruction->size) { case 1: cr16_ins->rtype = BFD_RELOC_CR16_REGREL0; break; case 2: cr16_ins->rtype = BFD_RELOC_CR16_REGREL14; break; case 3: cr16_ins->rtype = BFD_RELOC_CR16_REGREL20; break; default: break; } } } break; case arg_c: if (IS_INSN_MNEMONIC ("bal")) cr16_ins->rtype = BFD_RELOC_CR16_DISP24; else if (IS_INSN_TYPE (BRANCH_INS)) { if (symbol_with_l) cr16_ins->rtype = BFD_RELOC_CR16_DISP24; else if (symbol_with_m) cr16_ins->rtype = BFD_RELOC_CR16_DISP16; else cr16_ins->rtype = BFD_RELOC_CR16_DISP8; } else if (IS_INSN_TYPE (STOR_IMM_INS) || IS_INSN_TYPE (LD_STOR_INS) || IS_INSN_TYPE (CSTBIT_INS)) { if (symbol_with_s) as_bad (_("operand %d: illegal use expression: `%s`"), cur_arg_num + 1, str); if (symbol_with_at_got) cr16_ins->rtype = BFD_RELOC_CR16_GOT_REGREL20; else if (symbol_with_at_gotc) cr16_ins->rtype = BFD_RELOC_CR16_GOTC_REGREL20; else if (symbol_with_m) cr16_ins->rtype = BFD_RELOC_CR16_ABS20; else /* Default to (symbol_with_l) */ cr16_ins->rtype = BFD_RELOC_CR16_ABS24; } else if (IS_INSN_TYPE (BRANCH_NEQ_INS)) cr16_ins->rtype = BFD_RELOC_CR16_DISP4; break; case arg_ic: if (IS_INSN_TYPE (ARITH_INS)) { if (symbol_with_at_got) cr16_ins->rtype = BFD_RELOC_CR16_GOT_REGREL20; else if (symbol_with_at_gotc) cr16_ins->rtype = BFD_RELOC_CR16_GOTC_REGREL20; else if (symbol_with_s) cr16_ins->rtype = BFD_RELOC_CR16_IMM4; else if (symbol_with_m) cr16_ins->rtype = BFD_RELOC_CR16_IMM20; else if (symbol_with_at) cr16_ins->rtype = BFD_RELOC_CR16_IMM32a; else /* Default to (symbol_with_l) */ cr16_ins->rtype = BFD_RELOC_CR16_IMM32; } else if (IS_INSN_TYPE (ARITH_BYTE_INS)) { cr16_ins->rtype = BFD_RELOC_CR16_IMM16; } break; default: break; } break; default: cur_arg->X_op = cr16_ins->exp.X_op; break; } input_line_pointer = saved_input_line_pointer; return; } /* Retrieve the opcode image of a given register. If the register is illegal for the current instruction, issue an error. */ static int getreg_image (reg r) { const reg_entry *rreg; char *reg_name; int is_procreg = 0; /* Nonzero means argument should be processor reg. */ /* Check whether the register is in registers table. */ if (r < MAX_REG) rreg = cr16_regtab + r; else /* Register not found. */ { as_bad (_("Unknown register: `%d'"), r); return 0; } reg_name = rreg->name; /* Issue a error message when register is illegal. */ #define IMAGE_ERR \ as_bad (_("Illegal register (`%s') in Instruction: `%s'"), \ reg_name, ins_parse); switch (rreg->type) { case CR16_R_REGTYPE: if (! is_procreg) return rreg->image; else IMAGE_ERR; break; case CR16_P_REGTYPE: return rreg->image; break; default: IMAGE_ERR; break; } return 0; } /* Parsing different types of operands -> constants Immediate/Absolute/Relative numbers -> Labels Relocatable symbols -> (reg pair base) Register pair base -> (rbase) Register base -> disp(rbase) Register relative -> [rinx]disp(reg pair) Register index with reg pair mode -> disp(rbase,ridx,scl) Register index mode. */ static void set_operand (char *operand, ins * cr16_ins) { char *operandS; /* Pointer to start of sub-operand. */ char *operandE; /* Pointer to end of sub-operand. */ argument *cur_arg = &cr16_ins->arg[cur_arg_num]; /* Current argument. */ /* Initialize pointers. */ operandS = operandE = operand; switch (cur_arg->type) { case arg_ic: /* Case $0x18. */ operandS++; /* Fall through. */ case arg_c: /* Case 0x18. */ /* Set constant. */ process_label_constant (operandS, cr16_ins); if (cur_arg->type != arg_ic) cur_arg->type = arg_c; break; case arg_icr: /* Case $0x18(r1). */ operandS++; case arg_cr: /* Case 0x18(r1). */ /* Set displacement constant. */ while (*operandE != '(') operandE++; *operandE = '\0'; process_label_constant (operandS, cr16_ins); operandS = operandE; /* Fall through. */ case arg_rbase: /* Case (r1) or (r1,r0). */ operandS++; /* Set register base. */ while (*operandE != ')') operandE++; *operandE = '\0'; if ((cur_arg->r = get_register (operandS)) == nullregister) as_bad (_("Illegal register `%s' in Instruction `%s'"), operandS, ins_parse); /* set the arg->rp, if reg is "r12" or "r13" or "14" or "15" */ if ((cur_arg->type != arg_rbase) && ((getreg_image (cur_arg->r) == 12) || (getreg_image (cur_arg->r) == 13) || (getreg_image (cur_arg->r) == 14) || (getreg_image (cur_arg->r) == 15))) { cur_arg->type = arg_crp; cur_arg->rp = cur_arg->r; } break; case arg_crp: /* Case 0x18(r1,r0). */ /* Set displacement constant. */ while (*operandE != '(') operandE++; *operandE = '\0'; process_label_constant (operandS, cr16_ins); operandS = operandE; operandS++; /* Set register pair base. */ while (*operandE != ')') operandE++; *operandE = '\0'; if ((cur_arg->rp = get_register_pair (operandS)) == nullregister) as_bad (_("Illegal register pair `%s' in Instruction `%s'"), operandS, ins_parse); break; case arg_idxr: /* Set register pair base. */ if ((strchr (operandS,'(') != NULL)) { while ((*operandE != '(') && (! ISSPACE (*operandE))) operandE++; if ((cur_arg->rp = get_index_register_pair (operandE)) == nullregister) as_bad (_("Illegal register pair `%s' in Instruction `%s'"), operandS, ins_parse); *operandE++ = '\0'; cur_arg->type = arg_idxrp; } else cur_arg->rp = -1; operandE = operandS; /* Set displacement constant. */ while (*operandE != ']') operandE++; process_label_constant (++operandE, cr16_ins); *operandE++ = '\0'; operandE = operandS; /* Set index register . */ operandS = strchr (operandE,'['); if (operandS != NULL) { /* Eliminate '[', detach from rest of operand. */ *operandS++ = '\0'; operandE = strchr (operandS, ']'); if (operandE == NULL) as_bad (_("unmatched '['")); else { /* Eliminate ']' and make sure it was the last thing in the string. */ *operandE = '\0'; if (*(operandE + 1) != '\0') as_bad (_("garbage after index spec ignored")); } } if ((cur_arg->i_r = get_index_register (operandS)) == nullregister) as_bad (_("Illegal register `%s' in Instruction `%s'"), operandS, ins_parse); *operandE = '\0'; *operandS = '\0'; break; default: break; } } /* Parse a single operand. operand - Current operand to parse. cr16_ins - Current assembled instruction. */ static void parse_operand (char *operand, ins * cr16_ins) { int ret_val; argument *cur_arg = cr16_ins->arg + cur_arg_num; /* Current argument. */ /* Initialize the type to NULL before parsing. */ cur_arg->type = nullargs; /* Check whether this is a condition code . */ if ((IS_INSN_MNEMONIC ("b")) && ((ret_val = get_cc (operand)) != -1)) { cur_arg->type = arg_cc; cur_arg->cc = ret_val; cur_arg->X_op = O_register; return; } /* Check whether this is a general processor register. */ if ((ret_val = get_register (operand)) != nullregister) { cur_arg->type = arg_r; cur_arg->r = ret_val; cur_arg->X_op = 0; return; } /* Check whether this is a general processor register pair. */ if ((operand[0] == '(') && ((ret_val = get_register_pair (operand)) != nullregister)) { cur_arg->type = arg_rp; cur_arg->rp = ret_val; cur_arg->X_op = O_register; return; } /* Check whether the operand is a processor register. For "lprd" and "sprd" instruction, only 32 bit processor registers used. */ if (!(IS_INSN_MNEMONIC ("lprd") || (IS_INSN_MNEMONIC ("sprd"))) && ((ret_val = get_pregister (operand)) != nullpregister)) { cur_arg->type = arg_pr; cur_arg->pr = ret_val; cur_arg->X_op = O_register; return; } /* Check whether this is a processor register - 32 bit. */ if ((ret_val = get_pregisterp (operand)) != nullpregister) { cur_arg->type = arg_prp; cur_arg->prp = ret_val; cur_arg->X_op = O_register; return; } /* Deal with special characters. */ switch (operand[0]) { case '$': if (strchr (operand, '(') != NULL) cur_arg->type = arg_icr; else cur_arg->type = arg_ic; goto set_params; break; case '(': cur_arg->type = arg_rbase; goto set_params; break; case '[': cur_arg->type = arg_idxr; goto set_params; break; default: break; } if (strchr (operand, '(') != NULL) { if (strchr (operand, ',') != NULL && (strchr (operand, ',') > strchr (operand, '('))) cur_arg->type = arg_crp; else cur_arg->type = arg_cr; } else cur_arg->type = arg_c; /* Parse an operand according to its type. */ set_params: cur_arg->constant = 0; set_operand (operand, cr16_ins); } /* Parse the various operands. Each operand is then analyzed to fillup the fields in the cr16_ins data structure. */ static void parse_operands (ins * cr16_ins, char *operands) { char *operandS; /* Operands string. */ char *operandH, *operandT; /* Single operand head/tail pointers. */ int allocated = 0; /* Indicates a new operands string was allocated.*/ char *operand[MAX_OPERANDS];/* Separating the operands. */ int op_num = 0; /* Current operand number we are parsing. */ int bracket_flag = 0; /* Indicates a bracket '(' was found. */ int sq_bracket_flag = 0; /* Indicates a square bracket '[' was found. */ /* Preprocess the list of registers, if necessary. */ operandS = operandH = operandT = operands; while (*operandT != '\0') { if (*operandT == ',' && bracket_flag != 1 && sq_bracket_flag != 1) { *operandT++ = '\0'; operand[op_num++] = strdup (operandH); operandH = operandT; continue; } if (*operandT == ' ') as_bad (_("Illegal operands (whitespace): `%s'"), ins_parse); if (*operandT == '(') bracket_flag = 1; else if (*operandT == '[') sq_bracket_flag = 1; if (*operandT == ')') { if (bracket_flag) bracket_flag = 0; else as_fatal (_("Missing matching brackets : `%s'"), ins_parse); } else if (*operandT == ']') { if (sq_bracket_flag) sq_bracket_flag = 0; else as_fatal (_("Missing matching brackets : `%s'"), ins_parse); } if (bracket_flag == 1 && *operandT == ')') bracket_flag = 0; else if (sq_bracket_flag == 1 && *operandT == ']') sq_bracket_flag = 0; operandT++; } /* Adding the last operand. */ operand[op_num++] = strdup (operandH); cr16_ins->nargs = op_num; /* Verifying correct syntax of operands (all brackets should be closed). */ if (bracket_flag || sq_bracket_flag) as_fatal (_("Missing matching brackets : `%s'"), ins_parse); /* Now we parse each operand separately. */ for (op_num = 0; op_num < cr16_ins->nargs; op_num++) { cur_arg_num = op_num; parse_operand (operand[op_num], cr16_ins); free (operand[op_num]); } if (allocated) free (operandS); } /* Get the trap index in dispatch table, given its name. This routine is used by assembling the 'excp' instruction. */ static int gettrap (char *s) { const trap_entry *trap; for (trap = cr16_traps; trap < (cr16_traps + NUMTRAPS); trap++) if (strcasecmp (trap->name, s) == 0) return trap->entry; /* To make compatible with CR16 4.1 tools, the below 3-lines of * code added. Refer: Development Tracker item #123 */ for (trap = cr16_traps; trap < (cr16_traps + NUMTRAPS); trap++) if (trap->entry == (unsigned int) atoi (s)) return trap->entry; as_bad (_("Unknown exception: `%s'"), s); return 0; } /* Top level module where instruction parsing starts. cr16_ins - data structure holds some information. operands - holds the operands part of the whole instruction. */ static void parse_insn (ins *insn, char *operands) { int i; /* Handle instructions with no operands. */ for (i = 0; cr16_no_op_insn[i] != NULL; i++) { if (streq (cr16_no_op_insn[i], instruction->mnemonic)) { insn->nargs = 0; return; } } /* Handle 'excp' instructions. */ if (IS_INSN_MNEMONIC ("excp")) { insn->nargs = 1; insn->arg[0].type = arg_ic; insn->arg[0].constant = gettrap (operands); insn->arg[0].X_op = O_constant; return; } if (operands != NULL) parse_operands (insn, operands); } /* bCC instruction requires special handling. */ static char * get_b_cc (char * op) { unsigned int i; if (op[1] == 0 || (op[2] != 0 && op[3] != 0)) return NULL; for (i = 0; i < cr16_num_cc ; i++) if (streq (op + 1, cr16_b_cond_tab[i])) return (char *) cr16_b_cond_tab[i]; return NULL; } /* bCC instruction requires special handling. */ static int is_bcc_insn (char * op) { if (!(streq (op, "bal") || streq (op, "beq0b") || streq (op, "bnq0b") || streq (op, "beq0w") || streq (op, "bnq0w"))) if ((op[0] == 'b') && (get_b_cc (op) != NULL)) return 1; return 0; } /* Cinv instruction requires special handling. */ static void check_cinv_options (char * operand) { char *p = operand; while (*++p != ']') { switch (*p) { case ',': case ' ': case 'i': case 'u': case 'd': break; default: as_bad (_("Illegal `cinv' parameter: `%c'"), *p); } } } /* Retrieve the opcode image of a given register pair. If the register is illegal for the current instruction, issue an error. */ static int getregp_image (reg r) { const reg_entry *rreg; char *reg_name; /* Check whether the register is in registers table. */ if (r < MAX_REG) rreg = cr16_regptab + r; /* Register not found. */ else { as_bad (_("Unknown register pair: `%d'"), r); return 0; } reg_name = rreg->name; /* Issue a error message when register pair is illegal. */ #define RPAIR_IMAGE_ERR \ as_bad (_("Illegal register pair (`%s') in Instruction: `%s'"), \ reg_name, ins_parse); \ break; switch (rreg->type) { case CR16_RP_REGTYPE: return rreg->image; default: RPAIR_IMAGE_ERR; } return 0; } /* Retrieve the opcode image of a given index register pair. If the register is illegal for the current instruction, issue an error. */ static int getidxregp_image (reg r) { const reg_entry *rreg; char *reg_name; /* Check whether the register is in registers table. */ if (r < MAX_REG) rreg = cr16_regptab + r; /* Register not found. */ else { as_bad (_("Unknown register pair: `%d'"), r); return 0; } reg_name = rreg->name; /* Issue a error message when register pair is illegal. */ #define IDX_RPAIR_IMAGE_ERR \ as_bad (_("Illegal index register pair (`%s') in Instruction: `%s'"), \ reg_name, ins_parse); \ if (rreg->type == CR16_RP_REGTYPE) { switch (rreg->image) { case 0: return 0; break; case 2: return 1; break; case 4: return 2; break; case 6: return 3; break; case 8: return 4; break; case 10: return 5; break; case 3: return 6; break; case 5: return 7; break; default: break; } } IDX_RPAIR_IMAGE_ERR; return 0; } /* Retrieve the opcode image of a given processor register. If the register is illegal for the current instruction, issue an error. */ static int getprocreg_image (int r) { const reg_entry *rreg; char *reg_name; /* Check whether the register is in registers table. */ if (r >= MAX_REG && r < MAX_PREG) rreg = &cr16_pregtab[r - MAX_REG]; /* Register not found. */ else { as_bad (_("Unknown processor register : `%d'"), r); return 0; } reg_name = rreg->name; /* Issue a error message when register pair is illegal. */ #define PROCREG_IMAGE_ERR \ as_bad (_("Illegal processor register (`%s') in Instruction: `%s'"), \ reg_name, ins_parse); \ break; switch (rreg->type) { case CR16_P_REGTYPE: return rreg->image; default: PROCREG_IMAGE_ERR; } return 0; } /* Retrieve the opcode image of a given processor register. If the register is illegal for the current instruction, issue an error. */ static int getprocregp_image (int r) { const reg_entry *rreg; char *reg_name; int pregptab_disp = 0; /* Check whether the register is in registers table. */ if (r >= MAX_REG && r < MAX_PREG) { r = r - MAX_REG; switch (r) { case 4: pregptab_disp = 1; break; case 6: pregptab_disp = 2; break; case 8: case 9: case 10: pregptab_disp = 3; break; case 12: pregptab_disp = 4; break; case 14: pregptab_disp = 5; break; default: break; } rreg = &cr16_pregptab[r - pregptab_disp]; } /* Register not found. */ else { as_bad (_("Unknown processor register (32 bit) : `%d'"), r); return 0; } reg_name = rreg->name; /* Issue a error message when register pair is illegal. */ #define PROCREGP_IMAGE_ERR \ as_bad (_("Illegal 32 bit - processor register (`%s') in Instruction: `%s'"), \ reg_name, ins_parse); \ break; switch (rreg->type) { case CR16_P_REGTYPE: return rreg->image; default: PROCREGP_IMAGE_ERR; } return 0; } /* Routine used to represent integer X using NBITS bits. */ static long getconstant (long x, int nbits) { if ((unsigned) nbits >= sizeof (x) * CHAR_BIT) return x; return x & ((1UL << nbits) - 1); } /* Print a constant value to 'output_opcode': ARG holds the operand's type and value. SHIFT represents the location of the operand to be print into. NBITS determines the size (in bits) of the constant. */ static void print_constant (int nbits, int shift, argument *arg) { unsigned long mask = 0; unsigned long constant = getconstant (arg->constant, nbits); switch (nbits) { case 32: case 28: /* mask the upper part of the constant, that is, the bits going to the lowest byte of output_opcode[0]. The upper part of output_opcode[1] is always filled, therefore it is always masked with 0xFFFF. */ mask = (1 << (nbits - 16)) - 1; /* Divide the constant between two consecutive words : 0 1 2 3 +---------+---------+---------+---------+ | | X X X X | x X x X | | +---------+---------+---------+---------+ output_opcode[0] output_opcode[1] */ CR16_PRINT (0, (constant >> WORD_SHIFT) & mask, 0); CR16_PRINT (1, constant & 0xFFFF, WORD_SHIFT); break; case 21: if ((nbits == 21) && (IS_INSN_TYPE (LD_STOR_INS))) nbits = 20; /* Fall through. */ case 24: case 22: case 20: /* mask the upper part of the constant, that is, the bits going to the lowest byte of output_opcode[0]. The upper part of output_opcode[1] is always filled, therefore it is always masked with 0xFFFF. */ mask = (1 << (nbits - 16)) - 1; /* Divide the constant between two consecutive words : 0 1 2 3 +---------+---------+---------+---------+ | | X X X X | - X - X | | +---------+---------+---------+---------+ output_opcode[0] output_opcode[1] */ if (instruction->size > 2 && shift == WORD_SHIFT) { if (arg->type == arg_idxrp) { CR16_PRINT (0, ((constant >> WORD_SHIFT) & mask) << 8, 0); CR16_PRINT (1, constant & 0xFFFF, WORD_SHIFT); } else { CR16_PRINT (0, ((((constant >> WORD_SHIFT) & mask & 0xf) << 8) | (((constant >> WORD_SHIFT) & mask & 0xf0) >> 4)), 0); CR16_PRINT (1, constant & 0xFFFF, WORD_SHIFT); } } else CR16_PRINT (0, constant, shift); break; case 14: if (arg->type == arg_idxrp) { if (instruction->size == 2) { CR16_PRINT (0, (constant) & 0xf, shift); /* 0-3 bits. */ CR16_PRINT (0, (constant >> 4) & 0x3, shift + 20); /* 4-5 bits. */ CR16_PRINT (0, (constant >> 6) & 0x3, shift + 14); /* 6-7 bits. */ CR16_PRINT (0, (constant >> 8) & 0x3f, shift + 8); /* 8-13 bits. */ } else CR16_PRINT (0, constant, shift); } break; case 16: case 12: /* When instruction size is 3 and 'shift' is 16, a 16-bit constant is always filling the upper part of output_opcode[1]. If we mistakenly write it to output_opcode[0], the constant prefix (that is, 'match') will be overridden. 0 1 2 3 +---------+---------+---------+---------+ | 'match' | | X X X X | | +---------+---------+---------+---------+ output_opcode[0] output_opcode[1] */ if (instruction->size > 2 && shift == WORD_SHIFT) CR16_PRINT (1, constant, WORD_SHIFT); else CR16_PRINT (0, constant, shift); break; case 8: CR16_PRINT (0, (constant / 2) & 0xf, shift); CR16_PRINT (0, (constant / 2) >> 4, shift + 8); break; default: CR16_PRINT (0, constant, shift); break; } } /* Print an operand to 'output_opcode', which later on will be printed to the object file: ARG holds the operand's type, size and value. SHIFT represents the printing location of operand. NBITS determines the size (in bits) of a constant operand. */ static void print_operand (int nbits, int shift, argument *arg) { switch (arg->type) { case arg_cc: CR16_PRINT (0, arg->cc, shift); break; case arg_r: CR16_PRINT (0, getreg_image (arg->r), shift); break; case arg_rp: CR16_PRINT (0, getregp_image (arg->rp), shift); break; case arg_pr: CR16_PRINT (0, getprocreg_image (arg->pr), shift); break; case arg_prp: CR16_PRINT (0, getprocregp_image (arg->prp), shift); break; case arg_idxrp: /* 16 12 8 6 0 +-----------------------------+ | r_index | disp | rp_base | +-----------------------------+ */ if (instruction->size == 3) { CR16_PRINT (0, getidxregp_image (arg->rp), 0); CR16_PRINT (0, getreg_image (arg->i_r) & 1, 3); } else { CR16_PRINT (0, getidxregp_image (arg->rp), 16); CR16_PRINT (0, getreg_image (arg->i_r) & 1, 19); } print_constant (nbits, shift, arg); break; case arg_idxr: CR16_PRINT (0, getreg_image (arg->i_r) & 1, (IS_INSN_TYPE (CSTBIT_INS) && instruction->mnemonic[4] == 'b') ? 23 : 24); print_constant (nbits, shift, arg); break; case arg_ic: case arg_c: print_constant (nbits, shift, arg); break; case arg_rbase: CR16_PRINT (0, getreg_image (arg->r), shift); break; case arg_cr: print_constant (nbits, shift, arg); /* Add the register argument to the output_opcode. */ CR16_PRINT (0, getreg_image (arg->r), shift - 16); break; case arg_crp: print_constant (nbits, shift, arg); if ((IS_INSN_TYPE (LD_STOR_INS) || IS_INSN_TYPE (CSTBIT_INS)) && instruction->size == 1) CR16_PRINT (0, getregp_image (arg->rp), 16); else if (instruction->size > 1) CR16_PRINT (0, getregp_image (arg->rp), (shift + 16) & 31); else CR16_PRINT (0, getregp_image (arg->rp), shift); break; default: break; } } /* Retrieve the number of operands for the current assembled instruction. */ static int get_number_of_operands (void) { int i; for (i = 0; instruction->operands[i].op_type && i < MAX_OPERANDS; i++) ; return i; } /* Verify that the number NUM can be represented in BITS bits (that is, within its permitted range), based on the instruction's FLAGS. If UPDATE is nonzero, update the value of NUM if necessary. Return OP_LEGAL upon success, actual error type upon failure. */ static op_err check_range (long *num, int bits, int unsigned flags, int update) { int32_t min, max; op_err retval = OP_LEGAL; int32_t value = *num; /* Verify operand value is even. */ if (flags & OP_EVEN) { if (value % 2) return OP_NOT_EVEN; } if (flags & OP_DEC) { value -= 1; if (update) *num = value; } if (flags & OP_SHIFT) { value >>= 1; if (update) *num = value; } else if (flags & OP_SHIFT_DEC) { value = (value >> 1) - 1; if (update) *num = value; } if (flags & OP_ABS20) { if (value > 0xEFFFF) return OP_OUT_OF_RANGE; } if (flags & OP_ESC) { if (value == 0xB || value == 0x9) return OP_OUT_OF_RANGE; else if (value == -1) { if (update) *num = 9; return retval; } } if (flags & OP_ESC1) { if (value > 13) return OP_OUT_OF_RANGE; } if (bits == 0) { if (value != 0) retval = OP_OUT_OF_RANGE; return retval; } if (flags & OP_SIGNED) { max = (1U << (bits - 1)) - 1; min = - (1U << (bits - 1)); if (value > max || value < min) retval = OP_OUT_OF_RANGE; } else if (flags & OP_UNSIGNED) { max = (1U << (bits - 1) << 1) - 1; if ((uint32_t) value > (uint32_t) max) retval = OP_OUT_OF_RANGE; } else if (flags & OP_NEG) { min = - ((1U << (bits - 1)) - 1); if (value < min) retval = OP_OUT_OF_RANGE; } return retval; } /* Bunch of error checking. The checks are made after a matching instruction was found. */ static void warn_if_needed (ins *insn) { /* If the post-increment address mode is used and the load/store source register is the same as rbase, the result of the instruction is undefined. */ if (IS_INSN_TYPE (LD_STOR_INS_INC)) { /* Enough to verify that one of the arguments is a simple reg. */ if ((insn->arg[0].type == arg_r) || (insn->arg[1].type == arg_r)) if (insn->arg[0].r == insn->arg[1].r) as_bad (_("Same src/dest register is used (`r%d'), " "result is undefined"), insn->arg[0].r); } if (IS_INSN_MNEMONIC ("pop") || IS_INSN_MNEMONIC ("push") || IS_INSN_MNEMONIC ("popret")) { unsigned int count = insn->arg[0].constant, reg_val; /* Check if count operand caused to save/retrieve the RA twice to generate warning message. */ if (insn->nargs > 2) { reg_val = getreg_image (insn->arg[1].r); if ( ((reg_val == 9) && (count > 7)) || ((reg_val == 10) && (count > 6)) || ((reg_val == 11) && (count > 5)) || ((reg_val == 12) && (count > 4)) || ((reg_val == 13) && (count > 2)) || ((reg_val == 14) && (count > 0))) as_warn (_("RA register is saved twice.")); /* Check if the third operand is "RA" or "ra" */ if (!(((insn->arg[2].r) == ra) || ((insn->arg[2].r) == RA))) as_bad (_("`%s' Illegal use of registers."), ins_parse); } if (insn->nargs > 1) { reg_val = getreg_image (insn->arg[1].r); /* If register is a register pair ie r12/r13/r14 in operand1, then the count constant should be validated. */ if (((reg_val == 11) && (count > 7)) || ((reg_val == 12) && (count > 6)) || ((reg_val == 13) && (count > 4)) || ((reg_val == 14) && (count > 2)) || ((reg_val == 15) && (count > 0))) as_bad (_("`%s' Illegal count-register combination."), ins_parse); } else { /* Check if the operand is "RA" or "ra" */ if (!(((insn->arg[0].r) == ra) || ((insn->arg[0].r) == RA))) as_bad (_("`%s' Illegal use of register."), ins_parse); } } /* Some instruction assume the stack pointer as rptr operand. Issue an error when the register to be loaded is also SP. */ if (instruction->flags & NO_SP) { if (getreg_image (insn->arg[1].r) == getreg_image (sp)) as_bad (_("`%s' has undefined result"), ins_parse); } /* If the rptr register is specified as one of the registers to be loaded, the final contents of rptr are undefined. Thus, we issue an error. */ if (instruction->flags & NO_RPTR) { if ((1 << getreg_image (insn->arg[0].r)) & insn->arg[1].constant) as_bad (_("Same src/dest register is used (`r%d'),result is undefined"), getreg_image (insn->arg[0].r)); } } /* In some cases, we need to adjust the instruction pointer although a match was already found. Here, we gather all these cases. Returns 1 if instruction pointer was adjusted, otherwise 0. */ static int adjust_if_needed (ins *insn ATTRIBUTE_UNUSED) { int ret_value = 0; if ((IS_INSN_TYPE (CSTBIT_INS)) || (IS_INSN_TYPE (LD_STOR_INS))) { if ((instruction->operands[0].op_type == abs24) && ((insn->arg[0].constant) > 0xF00000)) { insn->arg[0].constant &= 0xFFFFF; instruction--; ret_value = 1; } } return ret_value; } /* Assemble a single instruction: INSN is already parsed (that is, all operand values and types are set). For instruction to be assembled, we need to find an appropriate template in the instruction table, meeting the following conditions: 1: Has the same number of operands. 2: Has the same operand types. 3: Each operand size is sufficient to represent the instruction's values. Returns 1 upon success, 0 upon failure. */ static int assemble_insn (const char *mnemonic, ins *insn) { /* Type of each operand in the current template. */ argtype cur_type[MAX_OPERANDS]; /* Size (in bits) of each operand in the current template. */ unsigned int cur_size[MAX_OPERANDS]; /* Flags of each operand in the current template. */ unsigned int cur_flags[MAX_OPERANDS]; /* Instruction type to match. */ unsigned int ins_type; /* Boolean flag to mark whether a match was found. */ int match = 0; int i; /* Nonzero if an instruction with same number of operands was found. */ int found_same_number_of_operands = 0; /* Nonzero if an instruction with same argument types was found. */ int found_same_argument_types = 0; /* Nonzero if a constant was found within the required range. */ int found_const_within_range = 0; /* Argument number of an operand with invalid type. */ int invalid_optype = -1; /* Argument number of an operand with invalid constant value. */ int invalid_const = -1; /* Operand error (used for issuing various constant error messages). */ op_err op_error, const_err = OP_LEGAL; /* Retrieve data (based on FUNC) for each operand of a given instruction. */ #define GET_CURRENT_DATA(FUNC, ARRAY) \ for (i = 0; i < insn->nargs; i++) \ ARRAY[i] = FUNC (instruction->operands[i].op_type) #define GET_CURRENT_TYPE GET_CURRENT_DATA (get_optype, cur_type) #define GET_CURRENT_SIZE GET_CURRENT_DATA (get_opbits, cur_size) #define GET_CURRENT_FLAGS GET_CURRENT_DATA (get_opflags, cur_flags) /* Instruction has no operands -> only copy the constant opcode. */ if (insn->nargs == 0) { output_opcode[0] = BIN (instruction->match, instruction->match_bits); return 1; } /* In some case, same mnemonic can appear with different instruction types. For example, 'storb' is supported with 3 different types : LD_STOR_INS, LD_STOR_INS_INC, STOR_IMM_INS. We assume that when reaching this point, the instruction type was pre-determined. We need to make sure that the type stays the same during a search for matching instruction. */ ins_type = CR16_INS_TYPE (instruction->flags); while (/* Check that match is still not found. */ match != 1 /* Check we didn't get to end of table. */ && instruction->mnemonic != NULL /* Check that the actual mnemonic is still available. */ && IS_INSN_MNEMONIC (mnemonic) /* Check that the instruction type wasn't changed. */ && IS_INSN_TYPE (ins_type)) { /* Check whether number of arguments is legal. */ if (get_number_of_operands () != insn->nargs) goto next_insn; found_same_number_of_operands = 1; /* Initialize arrays with data of each operand in current template. */ GET_CURRENT_TYPE; GET_CURRENT_SIZE; GET_CURRENT_FLAGS; /* Check for type compatibility. */ for (i = 0; i < insn->nargs; i++) { if (cur_type[i] != insn->arg[i].type) { if (invalid_optype == -1) invalid_optype = i + 1; goto next_insn; } } found_same_argument_types = 1; for (i = 0; i < insn->nargs; i++) { /* If 'bal' instruction size is '2' and reg operand is not 'ra' then goto next instruction. */ if (IS_INSN_MNEMONIC ("bal") && (i == 0) && (instruction->size == 2) && (insn->arg[i].rp != 14)) goto next_insn; /* If 'storb' instruction with 'sp' reg and 16-bit disp of * reg-pair, leads to undefined trap, so this should use * 20-bit disp of reg-pair. */ if (IS_INSN_MNEMONIC ("storb") && (instruction->size == 2) && (insn->arg[i].r == 15) && (insn->arg[i + 1].type == arg_crp)) goto next_insn; /* Only check range - don't update the constant's value, since the current instruction may not be the last we try to match. The constant's value will be updated later, right before printing it to the object file. */ if ((insn->arg[i].X_op == O_constant) && (op_error = check_range (&insn->arg[i].constant, cur_size[i], cur_flags[i], 0))) { if (invalid_const == -1) { invalid_const = i + 1; const_err = op_error; } goto next_insn; } /* For symbols, we make sure the relocation size (which was already determined) is sufficient. */ else if ((insn->arg[i].X_op == O_symbol) && ((bfd_reloc_type_lookup (stdoutput, insn->rtype))->bitsize > cur_size[i])) goto next_insn; } found_const_within_range = 1; /* If we got till here -> Full match is found. */ match = 1; break; /* Try again with next instruction. */ next_insn: instruction++; } if (!match) { /* We haven't found a match - instruction can't be assembled. */ if (!found_same_number_of_operands) as_bad (_("Incorrect number of operands")); else if (!found_same_argument_types) as_bad (_("Illegal type of operand (arg %d)"), invalid_optype); else if (!found_const_within_range) { switch (const_err) { case OP_OUT_OF_RANGE: as_bad (_("Operand out of range (arg %d)"), invalid_const); break; case OP_NOT_EVEN: as_bad (_("Operand has odd displacement (arg %d)"), invalid_const); break; default: as_bad (_("Illegal operand (arg %d)"), invalid_const); break; } } return 0; } else /* Full match - print the encoding to output file. */ { /* Make further checking (such that couldn't be made earlier). Warn the user if necessary. */ warn_if_needed (insn); /* Check whether we need to adjust the instruction pointer. */ if (adjust_if_needed (insn)) /* If instruction pointer was adjusted, we need to update the size of the current template operands. */ GET_CURRENT_SIZE; for (i = 0; i < insn->nargs; i++) { int j = instruction->flags & REVERSE_MATCH ? i == 0 ? 1 : i == 1 ? 0 : i : i; /* This time, update constant value before printing it. */ if ((insn->arg[j].X_op == O_constant) && (check_range (&insn->arg[j].constant, cur_size[j], cur_flags[j], 1) != OP_LEGAL)) as_fatal (_("Illegal operand (arg %d)"), j+1); } /* First, copy the instruction's opcode. */ output_opcode[0] = BIN (instruction->match, instruction->match_bits); for (i = 0; i < insn->nargs; i++) { /* For BAL (ra),disp17 instruction only. And also set the DISP24a relocation type. */ if (IS_INSN_MNEMONIC ("bal") && (instruction->size == 2) && i == 0) { insn->rtype = BFD_RELOC_CR16_DISP24a; continue; } cur_arg_num = i; print_operand (cur_size[i], instruction->operands[i].shift, &insn->arg[i]); } } return 1; } /* Print the instruction. Handle also cases where the instruction is relaxable/relocatable. */ static void print_insn (ins *insn) { unsigned int i, j, insn_size; char *this_frag; unsigned short words[4]; int addr_mod; /* Arrange the insn encodings in a WORD size array. */ for (i = 0, j = 0; i < 2; i++) { words[j++] = (output_opcode[i] >> 16) & 0xFFFF; words[j++] = output_opcode[i] & 0xFFFF; } /* Handle relocation. */ if ((instruction->flags & RELAXABLE) && relocatable) { int relax_subtype; /* Write the maximal instruction size supported. */ insn_size = INSN_MAX_SIZE; if (IS_INSN_TYPE (BRANCH_INS)) { switch (insn->rtype) { case BFD_RELOC_CR16_DISP24: relax_subtype = 2; break; case BFD_RELOC_CR16_DISP16: relax_subtype = 1; break; default: relax_subtype = 0; break; } } else abort (); this_frag = frag_var (rs_machine_dependent, insn_size *2, 4, relax_subtype, insn->exp.X_add_symbol, 0, 0); } else { insn_size = instruction->size; this_frag = frag_more (insn_size * 2); if ((relocatable) && (insn->rtype != BFD_RELOC_NONE)) { reloc_howto_type *reloc_howto; int size; reloc_howto = bfd_reloc_type_lookup (stdoutput, insn->rtype); if (!reloc_howto) abort (); size = bfd_get_reloc_size (reloc_howto); if (size < 1 || size > 4) abort (); fix_new_exp (frag_now, this_frag - frag_now->fr_literal, size, &insn->exp, reloc_howto->pc_relative, insn->rtype); } } /* Verify a 2-byte code alignment. */ addr_mod = frag_now_fix () & 1; if (frag_now->has_code && frag_now->insn_addr != addr_mod) as_bad (_("instruction address is not a multiple of 2")); frag_now->insn_addr = addr_mod; frag_now->has_code = 1; /* Write the instruction encoding to frag. */ for (i = 0; i < insn_size; i++) { md_number_to_chars (this_frag, (valueT) words[i], 2); this_frag += 2; } } /* Actually assemble an instruction. */ static void cr16_assemble (const char *op, char *param) { ins cr16_ins; /* Find the instruction. */ instruction = (const inst *) str_hash_find (cr16_inst_hash, op); if (instruction == NULL) { as_bad (_("Unknown opcode: `%s'"), op); return; } /* Tie dwarf2 debug info to the address at the start of the insn. */ dwarf2_emit_insn (0); /* Parse the instruction's operands. */ parse_insn (&cr16_ins, param); /* Assemble the instruction - return upon failure. */ if (assemble_insn (op, &cr16_ins) == 0) return; /* Print the instruction. */ print_insn (&cr16_ins); } /* This is the guts of the machine-dependent assembler. OP points to a machine dependent instruction. This function is supposed to emit the frags/bytes it assembles to. */ void md_assemble (char *op) { ins cr16_ins; char *param, param1[32]; /* Reset global variables for a new instruction. */ reset_vars (op); /* Strip the mnemonic. */ for (param = op; *param != 0 && !ISSPACE (*param); param++) ; *param++ = '\0'; /* bCC instructions and adjust the mnemonic by adding extra white spaces. */ if (is_bcc_insn (op)) { strcpy (param1, get_b_cc (op)); strcat (param1,","); strcat (param1, param); param = (char *) ¶m1; cr16_assemble ("b", param); return; } /* Checking the cinv options and adjust the mnemonic by removing the extra white spaces. */ if (streq ("cinv", op)) { /* Validate the cinv options. */ unsigned int op_len, param_len; check_cinv_options (param); op_len = strlen (op); param_len = strlen (param) + 1; memmove (op + op_len, param, param_len); } /* MAPPING - SHIFT INSN, if imm4/imm16 positive values lsh[b/w] imm4/imm6, reg ==> ashu[b/w] imm4/imm16, reg as CR16 core doesn't support lsh[b/w] right shift operations. */ if ((streq ("lshb", op) || streq ("lshw", op) || streq ("lshd", op)) && (param [0] == '$')) { strcpy (param1, param); /* Find the instruction. */ instruction = (const inst *) str_hash_find (cr16_inst_hash, op); parse_operands (&cr16_ins, param1); if (((&cr16_ins)->arg[0].type == arg_ic) && ((&cr16_ins)->arg[0].constant >= 0)) { if (streq ("lshb", op)) cr16_assemble ("ashub", param); else if (streq ("lshd", op)) cr16_assemble ("ashud", param); else cr16_assemble ("ashuw", param); return; } } cr16_assemble (op, param); }