| /* tc-m68hc11.c -- Assembler code for the Motorola 68HC11 & 68HC12. |
| Copyright (C) 1999-2021 Free Software Foundation, Inc. |
| Written by Stephane Carrez (stcarrez@nerim.fr) |
| XGATE and S12X added by James Murray (jsm@jsm-net.demon.co.uk) |
| |
| 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 "subsegs.h" |
| #include "opcode/m68hc11.h" |
| #include "dwarf2dbg.h" |
| #include "elf/m68hc11.h" |
| |
| const char comment_chars[] = ";!"; |
| const char line_comment_chars[] = "#*"; |
| const char line_separator_chars[] = ""; |
| |
| const char EXP_CHARS[] = "eE"; |
| const char FLT_CHARS[] = "dD"; |
| |
| #define STATE_CONDITIONAL_BRANCH (1) |
| #define STATE_PC_RELATIVE (2) |
| #define STATE_INDEXED_OFFSET (3) |
| #define STATE_INDEXED_PCREL (4) |
| #define STATE_XBCC_BRANCH (5) |
| #define STATE_CONDITIONAL_BRANCH_6812 (6) |
| |
| #define STATE_BYTE (0) |
| #define STATE_BITS5 (0) |
| #define STATE_WORD (1) |
| #define STATE_BITS9 (1) |
| #define STATE_LONG (2) |
| #define STATE_BITS16 (2) |
| #define STATE_UNDF (3) /* Symbol undefined in pass1 */ |
| |
| /* This macro has no side-effects. */ |
| #define ENCODE_RELAX(what,length) (((what) << 2) + (length)) |
| #define RELAX_STATE(s) ((s) >> 2) |
| #define RELAX_LENGTH(s) ((s) & 3) |
| |
| #define IS_OPCODE(C1,C2) (((C1) & 0x0FF) == ((C2) & 0x0FF)) |
| |
| /* This table describes how you change sizes for the various types of variable |
| size expressions. This version only supports two kinds. */ |
| |
| /* The fields are: |
| How far Forward this mode will reach. |
| How far Backward this mode will reach. |
| How many bytes this mode will add to the size of the frag. |
| Which mode to go to if the offset won't fit in this one. */ |
| |
| relax_typeS md_relax_table[] = |
| { |
| {1, 1, 0, 0}, /* First entries aren't used. */ |
| {1, 1, 0, 0}, /* For no good reason except. */ |
| {1, 1, 0, 0}, /* that the VAX doesn't either. */ |
| {1, 1, 0, 0}, |
| |
| /* Relax for bcc <L>. |
| These insns are translated into b!cc +3 jmp L. */ |
| {(127), (-128), 0, ENCODE_RELAX (STATE_CONDITIONAL_BRANCH, STATE_WORD)}, |
| {0, 0, 3, 0}, |
| {1, 1, 0, 0}, |
| {1, 1, 0, 0}, |
| |
| /* Relax for bsr <L> and bra <L>. |
| These insns are translated into jsr and jmp. */ |
| {(127), (-128), 0, ENCODE_RELAX (STATE_PC_RELATIVE, STATE_WORD)}, |
| {0, 0, 1, 0}, |
| {1, 1, 0, 0}, |
| {1, 1, 0, 0}, |
| |
| /* Relax for indexed offset: 5-bits, 9-bits, 16-bits. */ |
| {(15), (-16), 0, ENCODE_RELAX (STATE_INDEXED_OFFSET, STATE_BITS9)}, |
| {(255), (-256), 1, ENCODE_RELAX (STATE_INDEXED_OFFSET, STATE_BITS16)}, |
| {0, 0, 2, 0}, |
| {1, 1, 0, 0}, |
| |
| /* Relax for PC relative offset: 5-bits, 9-bits, 16-bits. |
| For the 9-bit case, there will be a -1 correction to take into |
| account the new byte that's why the range is -255..256. */ |
| {(15), (-16), 0, ENCODE_RELAX (STATE_INDEXED_PCREL, STATE_BITS9)}, |
| {(256), (-255), 1, ENCODE_RELAX (STATE_INDEXED_PCREL, STATE_BITS16)}, |
| {0, 0, 2, 0}, |
| {1, 1, 0, 0}, |
| |
| /* Relax for dbeq/ibeq/tbeq r,<L>: |
| These insns are translated into db!cc +3 jmp L. */ |
| {(255), (-256), 0, ENCODE_RELAX (STATE_XBCC_BRANCH, STATE_WORD)}, |
| {0, 0, 3, 0}, |
| {1, 1, 0, 0}, |
| {1, 1, 0, 0}, |
| |
| /* Relax for bcc <L> on 68HC12. |
| These insns are translated into lbcc <L>. */ |
| {(127), (-128), 0, ENCODE_RELAX (STATE_CONDITIONAL_BRANCH_6812, STATE_WORD)}, |
| {0, 0, 2, 0}, |
| {1, 1, 0, 0}, |
| {1, 1, 0, 0}, |
| |
| }; |
| |
| /* 68HC11 and 68HC12 registers. They are numbered according to the 68HC12. */ |
| typedef enum register_id |
| { |
| REG_NONE = -1, |
| REG_A = 0, |
| REG_B = 1, |
| REG_CCR = 2, |
| REG_D = 4, |
| REG_X = 5, |
| REG_Y = 6, |
| REG_SP = 7, |
| REG_PC = 8, |
| REG_R0 = 0, |
| REG_R1 = 1, |
| REG_R2 = 2, |
| REG_R3 = 3, |
| REG_R4 = 4, |
| REG_R5 = 5, |
| REG_R6 = 6, |
| REG_R7 = 7, |
| REG_SP_XG = 8, |
| REG_PC_XG = 9, |
| REG_CCR_XG = 10 |
| } register_id; |
| |
| typedef struct operand |
| { |
| expressionS exp; |
| register_id reg1; |
| register_id reg2; |
| int mode; |
| } operand; |
| |
| struct m68hc11_opcode_def |
| { |
| long format; |
| int min_operands; |
| int max_operands; |
| int nb_modes; |
| int used; |
| struct m68hc11_opcode *opcode; |
| }; |
| |
| static struct m68hc11_opcode_def *m68hc11_opcode_defs = 0; |
| static int m68hc11_nb_opcode_defs = 0; |
| |
| typedef struct alias |
| { |
| const char *name; |
| const char *alias; |
| } alias; |
| |
| static alias alias_opcodes[] = |
| { |
| {"cpd", "cmpd"}, |
| {"cpx", "cmpx"}, |
| {"cpy", "cmpy"}, |
| {0, 0} |
| }; |
| |
| struct m9s12xg_opcode_def |
| { |
| long format; |
| int min_operands; |
| int max_operands; |
| int nb_modes; |
| int used; |
| struct m9s12xg_opcode *opcode; |
| }; |
| |
| /* Local functions. */ |
| static register_id reg_name_search (char *); |
| static register_id register_name (void); |
| static int cmp_opcode (struct m68hc11_opcode *, struct m68hc11_opcode *); |
| static char *print_opcode_format (struct m68hc11_opcode *, int); |
| static char *skip_whites (char *); |
| static int check_range (long, int); |
| static void print_opcode_list (void); |
| static void get_default_target (void); |
| static void print_insn_format (char *); |
| static int get_operand (operand *, int, long); |
| static void fixup8 (expressionS *, int, int); |
| static void fixup16 (expressionS *, int, int); |
| static void fixup24 (expressionS *, int, int); |
| static void fixup8_xg (expressionS *, int, int); |
| static unsigned char convert_branch (unsigned char); |
| static char *m68hc11_new_insn (int); |
| static void build_dbranch_insn (struct m68hc11_opcode *, |
| operand *, int, int); |
| static int build_indexed_byte (operand *, int, int); |
| static int build_reg_mode (operand *, int); |
| |
| static struct m68hc11_opcode *find (struct m68hc11_opcode_def *, |
| operand *, int); |
| static struct m68hc11_opcode *find_opcode (struct m68hc11_opcode_def *, |
| operand *, int *); |
| static void build_jump_insn (struct m68hc11_opcode *, operand *, int, int); |
| static void build_insn_xg (struct m68hc11_opcode *, operand *, int); |
| static void build_insn (struct m68hc11_opcode *, operand *, int); |
| static int relaxable_symbol (symbolS *); |
| |
| /* Pseudo op to indicate a relax group. */ |
| static void s_m68hc11_relax (int); |
| |
| /* Pseudo op to control the ELF flags. */ |
| static void s_m68hc11_mode (int); |
| |
| /* Process directives specified via pseudo ops. */ |
| static void s_m68hc11_parse_pseudo_instruction (int); |
| |
| /* Mark the symbols with STO_M68HC12_FAR to indicate the functions |
| are using 'rtc' for returning. It is necessary to use 'call' |
| to invoke them. This is also used by the debugger to correctly |
| find the stack frame. */ |
| static void s_m68hc11_mark_symbol (int); |
| |
| /* Controls whether relative branches can be turned into long branches. |
| When the relative offset is too large, the insn are changed: |
| bra -> jmp |
| bsr -> jsr |
| bcc -> b!cc +3 |
| jmp L |
| dbcc -> db!cc +3 |
| jmp L |
| |
| Setting the flag forbids this. */ |
| static short flag_fixed_branches = 0; |
| |
| /* Force to use long jumps (absolute) instead of relative branches. */ |
| static short flag_force_long_jumps = 0; |
| |
| /* Change the direct addressing mode into an absolute addressing mode |
| when the insn does not support direct addressing. |
| For example, "clr *ZD0" is normally not possible and is changed |
| into "clr ZDO". */ |
| static short flag_strict_direct_addressing = 1; |
| |
| /* When an opcode has invalid operand, print out the syntax of the opcode |
| to stderr. */ |
| static short flag_print_insn_syntax = 0; |
| |
| /* Dumps the list of instructions with syntax and then exit: |
| 1 -> Only dumps the list (sorted by name) |
| 2 -> Generate an example (or test) that can be compiled. */ |
| static short flag_print_opcodes = 0; |
| |
| /* Opcode hash table. */ |
| static htab_t m68hc11_hash; |
| |
| /* Current cpu (either cpu6811 or cpu6812). This is determined automagically |
| by 'get_default_target' by looking at default BFD vector. This is overridden |
| with the -m<cpu> option. */ |
| static int current_architecture = 0; |
| |
| /* Default cpu determined by 'get_default_target'. */ |
| static const char *default_cpu; |
| |
| /* Number of opcodes in the sorted table (filtered by current cpu). */ |
| static int num_opcodes; |
| |
| /* The opcodes sorted by name and filtered by current cpu. */ |
| static struct m68hc11_opcode *m68hc11_sorted_opcodes; |
| |
| /* ELF flags to set in the output file header. */ |
| static int elf_flags = E_M68HC11_F64; |
| |
| /* These are the machine dependent pseudo-ops. These are included so |
| the assembler can work on the output from the SUN C compiler, which |
| generates these. */ |
| |
| /* 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[] = |
| { |
| /* The following pseudo-ops are supported for MRI compatibility. */ |
| {"fcb", cons, 1}, |
| {"fdb", cons, 2}, |
| {"fqb", cons, 4}, |
| {"fcc", stringer, 8 + 1}, |
| {"rmb", s_space, 0}, |
| |
| /* Motorola ALIS. */ |
| {"xrefb", s_ignore, 0}, /* Same as xref */ |
| |
| /* Gcc driven relaxation. */ |
| {"relax", s_m68hc11_relax, 0}, |
| |
| /* .mode instruction (ala SH). */ |
| {"mode", s_m68hc11_mode, 0}, |
| |
| /* .far instruction. */ |
| {"far", s_m68hc11_mark_symbol, STO_M68HC12_FAR}, |
| |
| /* .interrupt instruction. */ |
| {"interrupt", s_m68hc11_mark_symbol, STO_M68HC12_INTERRUPT}, |
| |
| /* .nobankwarning instruction. */ |
| {"nobankwarning", s_m68hc11_parse_pseudo_instruction, E_M68HC11_NO_BANK_WARNING}, |
| |
| {0, 0, 0} |
| }; |
| |
| /* Options and initialization. */ |
| |
| const char *md_shortopts = "Sm:"; |
| |
| struct option md_longopts[] = |
| { |
| #define OPTION_FORCE_LONG_BRANCH (OPTION_MD_BASE) |
| {"force-long-branches", no_argument, NULL, OPTION_FORCE_LONG_BRANCH}, |
| {"force-long-branchs", no_argument, NULL, OPTION_FORCE_LONG_BRANCH}, /* Misspelled version kept for backwards compatibility. */ |
| |
| #define OPTION_SHORT_BRANCHES (OPTION_MD_BASE + 1) |
| {"short-branches", no_argument, NULL, OPTION_SHORT_BRANCHES}, |
| {"short-branchs", no_argument, NULL, OPTION_SHORT_BRANCHES}, /* Misspelled version kept for backwards compatibility. */ |
| |
| #define OPTION_STRICT_DIRECT_MODE (OPTION_MD_BASE + 2) |
| {"strict-direct-mode", no_argument, NULL, OPTION_STRICT_DIRECT_MODE}, |
| |
| #define OPTION_PRINT_INSN_SYNTAX (OPTION_MD_BASE + 3) |
| {"print-insn-syntax", no_argument, NULL, OPTION_PRINT_INSN_SYNTAX}, |
| |
| #define OPTION_PRINT_OPCODES (OPTION_MD_BASE + 4) |
| {"print-opcodes", no_argument, NULL, OPTION_PRINT_OPCODES}, |
| |
| #define OPTION_GENERATE_EXAMPLE (OPTION_MD_BASE + 5) |
| {"generate-example", no_argument, NULL, OPTION_GENERATE_EXAMPLE}, |
| |
| #define OPTION_MSHORT (OPTION_MD_BASE + 6) |
| {"mshort", no_argument, NULL, OPTION_MSHORT}, |
| |
| #define OPTION_MLONG (OPTION_MD_BASE + 7) |
| {"mlong", no_argument, NULL, OPTION_MLONG}, |
| |
| #define OPTION_MSHORT_DOUBLE (OPTION_MD_BASE + 8) |
| {"mshort-double", no_argument, NULL, OPTION_MSHORT_DOUBLE}, |
| |
| #define OPTION_MLONG_DOUBLE (OPTION_MD_BASE + 9) |
| {"mlong-double", no_argument, NULL, OPTION_MLONG_DOUBLE}, |
| |
| #define OPTION_XGATE_RAMOFFSET (OPTION_MD_BASE + 10) |
| {"xgate-ramoffset", no_argument, NULL, OPTION_XGATE_RAMOFFSET}, |
| |
| {NULL, no_argument, NULL, 0} |
| }; |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| /* Get the target cpu for the assembler. This is based on the configure |
| options and on the -m68hc11/-m68hc12 option. If no option is specified, |
| we must get the default. */ |
| const char * |
| m68hc11_arch_format (void) |
| { |
| get_default_target (); |
| if (current_architecture & cpu6811) |
| return "elf32-m68hc11"; |
| else |
| return "elf32-m68hc12"; |
| } |
| |
| enum bfd_architecture |
| m68hc11_arch (void) |
| { |
| get_default_target (); |
| if (current_architecture & cpu6811) |
| return bfd_arch_m68hc11; |
| else |
| return bfd_arch_m68hc12; |
| } |
| |
| int |
| m68hc11_mach (void) |
| { |
| return 0; |
| } |
| |
| /* Listing header selected according to cpu. */ |
| const char * |
| m68hc11_listing_header (void) |
| { |
| if (current_architecture & cpu6811) |
| return "M68HC11 GAS "; |
| else if (current_architecture & cpuxgate) |
| return "XGATE GAS "; |
| else if (current_architecture & cpu9s12x) |
| return "S12X GAS "; |
| else |
| return "M68HC12 GAS "; |
| } |
| |
| void |
| md_show_usage (FILE *stream) |
| { |
| get_default_target (); |
| fprintf (stream, _("\ |
| Motorola 68HC11/68HC12/68HCS12 options:\n\ |
| -m68hc11 | -m68hc12 |\n\ |
| -m68hcs12 | -mm9s12x |\n\ |
| -mm9s12xg specify the processor [default %s]\n\ |
| -mshort use 16-bit int ABI (default)\n\ |
| -mlong use 32-bit int ABI\n\ |
| -mshort-double use 32-bit double ABI\n\ |
| -mlong-double use 64-bit double ABI (default)\n\ |
| --force-long-branches always turn relative branches into absolute ones\n\ |
| -S,--short-branches do not turn relative branches into absolute ones\n\ |
| when the offset is out of range\n\ |
| --strict-direct-mode do not turn the direct mode into extended mode\n\ |
| when the instruction does not support direct mode\n\ |
| --print-insn-syntax print the syntax of instruction in case of error\n\ |
| --print-opcodes print the list of instructions with syntax\n\ |
| --xgate-ramoffset offset ram addresses by 0xc000\n\ |
| --generate-example generate an example of each instruction\n\ |
| (used for testing)\n"), default_cpu); |
| |
| } |
| |
| /* Try to identify the default target based on the BFD library. */ |
| static void |
| get_default_target (void) |
| { |
| const bfd_target *target; |
| bfd abfd; |
| |
| if (current_architecture != 0) |
| return; |
| |
| default_cpu = "unknown"; |
| target = bfd_find_target (0, &abfd); |
| if (target && target->name) |
| { |
| if (strcmp (target->name, "elf32-m68hc12") == 0) |
| { |
| current_architecture = cpu6812; |
| default_cpu = "m68hc12"; |
| } |
| else if (strcmp (target->name, "elf32-m68hc11") == 0) |
| { |
| current_architecture = cpu6811; |
| default_cpu = "m68hc11"; |
| } |
| else |
| { |
| as_bad (_("Default target `%s' is not supported."), target->name); |
| } |
| } |
| } |
| |
| void |
| m68hc11_print_statistics (FILE *file) |
| { |
| int i; |
| struct m68hc11_opcode_def *opc; |
| |
| htab_print_statistics (file, "opcode table", m68hc11_hash); |
| |
| opc = m68hc11_opcode_defs; |
| if (opc == 0 || m68hc11_nb_opcode_defs == 0) |
| return; |
| |
| /* Dump the opcode statistics table. */ |
| fprintf (file, _("Name # Modes Min ops Max ops Modes mask # Used\n")); |
| for (i = 0; i < m68hc11_nb_opcode_defs; i++, opc++) |
| { |
| fprintf (file, "%-7.7s %5d %7d %7d 0x%08lx %7d\n", |
| opc->opcode->name, |
| opc->nb_modes, |
| opc->min_operands, opc->max_operands, opc->format, opc->used); |
| } |
| } |
| |
| int |
| md_parse_option (int c, const char *arg) |
| { |
| get_default_target (); |
| switch (c) |
| { |
| /* -S means keep external to 2 bit offset rather than 16 bit one. */ |
| case OPTION_SHORT_BRANCHES: |
| case 'S': |
| flag_fixed_branches = 1; |
| break; |
| |
| case OPTION_FORCE_LONG_BRANCH: |
| flag_force_long_jumps = 1; |
| break; |
| |
| case OPTION_PRINT_INSN_SYNTAX: |
| flag_print_insn_syntax = 1; |
| break; |
| |
| case OPTION_PRINT_OPCODES: |
| flag_print_opcodes = 1; |
| break; |
| |
| case OPTION_STRICT_DIRECT_MODE: |
| flag_strict_direct_addressing = 0; |
| break; |
| |
| case OPTION_GENERATE_EXAMPLE: |
| flag_print_opcodes = 2; |
| break; |
| |
| case OPTION_MSHORT: |
| elf_flags &= ~E_M68HC11_I32; |
| break; |
| |
| case OPTION_MLONG: |
| elf_flags |= E_M68HC11_I32; |
| break; |
| |
| case OPTION_MSHORT_DOUBLE: |
| elf_flags &= ~E_M68HC11_F64; |
| break; |
| |
| case OPTION_MLONG_DOUBLE: |
| elf_flags |= E_M68HC11_F64; |
| break; |
| |
| case OPTION_XGATE_RAMOFFSET: |
| elf_flags |= E_M68HC11_XGATE_RAMOFFSET; |
| break; |
| |
| case 'm': |
| if ((strcasecmp (arg, "68hc11") == 0) |
| || (strcasecmp (arg, "m68hc11") == 0)) |
| current_architecture = cpu6811; |
| else if ((strcasecmp (arg, "68hc12") == 0) |
| || (strcasecmp (arg, "m68hc12") == 0)) |
| current_architecture = cpu6812; |
| else if ((strcasecmp (arg, "68hcs12") == 0) |
| || (strcasecmp (arg, "m68hcs12") == 0)) |
| current_architecture = cpu6812 | cpu6812s; |
| else if (strcasecmp (arg, "m9s12x") == 0) |
| current_architecture = cpu6812 | cpu6812s | cpu9s12x; |
| else if ((strcasecmp (arg, "m9s12xg") == 0) |
| || (strcasecmp (arg, "xgate") == 0)) |
| /* xgate for backwards compatibility */ |
| current_architecture = cpuxgate; |
| else |
| as_bad (_("Option `%s' is not recognized."), arg); |
| break; |
| |
| default: |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| symbolS * |
| md_undefined_symbol (char *name ATTRIBUTE_UNUSED) |
| { |
| return 0; |
| } |
| |
| const char * |
| md_atof (int type, char *litP, int *sizeP) |
| { |
| return ieee_md_atof (type, litP, sizeP, true); |
| } |
| |
| valueT |
| md_section_align (asection *seg, valueT addr) |
| { |
| int align = bfd_section_alignment (seg); |
| return ((addr + (1 << align) - 1) & -(1 << align)); |
| } |
| |
| static int |
| cmp_opcode (struct m68hc11_opcode *op1, struct m68hc11_opcode *op2) |
| { |
| return strcmp (op1->name, op2->name); |
| } |
| |
| #define IS_CALL_SYMBOL(MODE) \ |
| (((MODE) & (M6812_OP_PAGE|M6811_OP_IND16)) \ |
| == ((M6812_OP_PAGE|M6811_OP_IND16))) |
| |
| /* Initialize the assembler. Create the opcode hash table |
| (sorted on the names) with the M6811 opcode table |
| (from opcode library). */ |
| void |
| md_begin (void) |
| { |
| const char *prev_name = ""; |
| struct m68hc11_opcode *opcodes; |
| struct m68hc11_opcode_def *opc = 0; |
| int i, j; |
| |
| get_default_target (); |
| |
| m68hc11_hash = str_htab_create (); |
| |
| /* Get a writable copy of the opcode table and sort it on the names. */ |
| opcodes = XNEWVEC (struct m68hc11_opcode, m68hc11_num_opcodes); |
| m68hc11_sorted_opcodes = opcodes; |
| num_opcodes = 0; |
| for (i = 0; i < m68hc11_num_opcodes; i++) |
| { |
| if (m68hc11_opcodes[i].arch & current_architecture) |
| { |
| opcodes[num_opcodes] = m68hc11_opcodes[i]; |
| if (opcodes[num_opcodes].name[0] == 'b' |
| && opcodes[num_opcodes].format & M6811_OP_JUMP_REL |
| && !(opcodes[num_opcodes].format & M6811_OP_BITMASK)) |
| { |
| num_opcodes++; |
| opcodes[num_opcodes] = m68hc11_opcodes[i]; |
| } |
| num_opcodes++; |
| for (j = 0; alias_opcodes[j].name != 0; j++) |
| if (strcmp (m68hc11_opcodes[i].name, alias_opcodes[j].name) == 0) |
| { |
| opcodes[num_opcodes] = m68hc11_opcodes[i]; |
| opcodes[num_opcodes].name = alias_opcodes[j].alias; |
| num_opcodes++; |
| break; |
| } |
| } |
| } |
| qsort (opcodes, num_opcodes, sizeof (struct m68hc11_opcode), |
| (int (*) (const void*, const void*)) cmp_opcode); |
| |
| opc = XNEWVEC (struct m68hc11_opcode_def, num_opcodes); |
| m68hc11_opcode_defs = opc--; |
| |
| /* Insert unique names into hash table. The M6811 instruction set |
| has several 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. */ |
| for (i = 0; i < num_opcodes; i++, opcodes++) |
| { |
| int expect; |
| |
| if (strcmp (prev_name, opcodes->name)) |
| { |
| prev_name = (char *) opcodes->name; |
| |
| opc++; |
| opc->format = 0; |
| opc->min_operands = 100; |
| opc->max_operands = 0; |
| opc->nb_modes = 0; |
| opc->opcode = opcodes; |
| opc->used = 0; |
| str_hash_insert (m68hc11_hash, opcodes->name, opc, 0); |
| } |
| opc->nb_modes++; |
| opc->format |= opcodes->format; |
| |
| /* See how many operands this opcode needs. */ |
| expect = 0; |
| if (opcodes->arch == cpuxgate) |
| { |
| if (opcodes->format & (M68XG_OP_IMM3 | M68XG_OP_R | M68XG_OP_REL9 |
| | M68XG_OP_REL10 )) |
| expect = 1; |
| else if (opcodes->format & (M68XG_OP_R_R | M68XG_OP_R_IMM4 |
| | M68XG_OP_R_IMM8 | M68XG_OP_R_IMM8)) |
| expect = 2; |
| else if (opcodes->format & (M68XG_OP_R_R_R | M68XG_OP_R_R_OFFS5 |
| | M68XG_OP_RD_RB_RI | M68XG_OP_RD_RB_RIp |
| | M68XG_OP_RD_RB_mRI)) |
| expect = 3; |
| } |
| else |
| { |
| if (opcodes->format & M6811_OP_MASK) |
| expect++; |
| if (opcodes->format & M6811_OP_BITMASK) |
| expect++; |
| if (opcodes->format & (M6811_OP_JUMP_REL | M6812_OP_JUMP_REL16)) |
| expect++; |
| if (opcodes->format & (M6812_OP_IND16_P2 | M6812_OP_IDX_P2)) |
| expect++; |
| /* Special case for call instruction. */ |
| if ((opcodes->format & M6812_OP_PAGE) |
| && !(opcodes->format & M6811_OP_IND16)) |
| expect++; |
| } |
| |
| if (expect < opc->min_operands) |
| opc->min_operands = expect; |
| if (IS_CALL_SYMBOL (opcodes->format)) |
| expect++; |
| if (expect > opc->max_operands) |
| opc->max_operands = expect; |
| } |
| opc++; |
| m68hc11_nb_opcode_defs = opc - m68hc11_opcode_defs; |
| |
| if (flag_print_opcodes) |
| { |
| print_opcode_list (); |
| exit (EXIT_SUCCESS); |
| } |
| } |
| |
| void |
| m68hc11_init_after_args (void) |
| { |
| } |
| |
| /* Builtin help. */ |
| |
| /* Return a string that represents the operand format for the instruction. |
| When example is true, this generates an example of operand. This is used |
| to give an example and also to generate a test. */ |
| |
| static char * |
| print_opcode_format (struct m68hc11_opcode *opcode, int example) |
| { |
| static char buf[128]; |
| int format = opcode->format; |
| char *p; |
| |
| p = buf; |
| buf[0] = 0; |
| |
| if (current_architecture == cpuxgate) |
| { |
| if (format & M68XG_OP_IMM3) |
| { |
| if (example) |
| sprintf (p, "#%d", rand () & 0x007); |
| else |
| strcpy (p, _("imm3")); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_R) |
| { |
| if (example) |
| sprintf (p, "R%d", rand () & 0x07); |
| else |
| strcpy (p, _("RD")); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_R_R) |
| { |
| if (example) |
| sprintf (p, "R%d,R%d", rand () & 0x07, rand () & 0x07); |
| else |
| strcpy (p, _("RD,RS")); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_R_IMM4) |
| { |
| if (example) |
| sprintf (p, "R%d,#%d", rand () & 0x07, rand () & 0x0f); |
| else |
| strcpy (p, _("RI, #imm4")); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_R_R_R) |
| { |
| if (example) |
| sprintf (p, "R%d,R%d,R%d", rand () & 0x07, rand () & 0x07, rand () & 0x07); |
| else |
| strcpy (p, "RD,RS1,RS2"); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_REL9) |
| { |
| if (example) |
| sprintf (p, "%d", rand () & 0x1FF); |
| else |
| strcpy (p, "<rel9>"); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_REL10) |
| { |
| if (example) |
| sprintf (p, "%d", rand () & 0x3FF); |
| else |
| strcpy (p, "<rel10>"); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_R_R_OFFS5) |
| { |
| if (example) |
| sprintf (p, "R%d, (R%d, #0x%x)", rand () & 0x07, rand () & 0x07, rand () & 0x1f); |
| else |
| strcpy (p, _("RD, (RI,#offs5)")); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_RD_RB_RI) |
| { |
| if (example) |
| sprintf (p, "R%d, (R%d, R%d)", rand () & 0x07, rand () & 0x07, rand () & 0x07); |
| else |
| strcpy (p, "RD, (RB, RI)"); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_RD_RB_RIp) |
| { |
| if (example) |
| sprintf (p, "R%d, (R%d, R%d+)", rand () & 0x07, rand () & 0x07, rand () & 0x07); |
| else |
| strcpy (p, "RD, (RB, RI+)"); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_RD_RB_mRI) |
| { |
| if (example) |
| sprintf (p, "R%d, (R%d, -R%d)", rand () & 0x07, rand () & 0x07, rand () & 0x07); |
| else |
| strcpy (p, "RD, (RB, -RI)"); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_R_IMM8) |
| { |
| if (example) |
| sprintf (p, "R%d, #0x%x", rand () & 0x07, rand () & 0xff); |
| else |
| strcpy (p, "RD, #imm8"); |
| p = &p[strlen (p)]; |
| } |
| else if (format & M68XG_OP_R_IMM16) |
| { |
| if (example) |
| sprintf (p, "R%d, #0x%x", rand () & 0x07, rand () & 0xffff); |
| else |
| strcpy (p, "RD, #imm16"); |
| p = &p[strlen (p)]; |
| } |
| } |
| else |
| { |
| |
| if (format & M6811_OP_IMM8) |
| { |
| if (example) |
| sprintf (p, "#%d", rand () & 0x0FF); |
| else |
| strcpy (p, _("#<imm8>")); |
| p = &p[strlen (p)]; |
| } |
| |
| if (format & M6811_OP_IMM16) |
| { |
| if (example) |
| sprintf (p, "#%d", rand () & 0x0FFFF); |
| else |
| strcpy (p, _("#<imm16>")); |
| p = &p[strlen (p)]; |
| } |
| |
| if (format & M6811_OP_IX) |
| { |
| if (example) |
| sprintf (p, "%d,X", rand () & 0x0FF); |
| else |
| strcpy (p, _("<imm8>,X")); |
| p = &p[strlen (p)]; |
| } |
| |
| if (format & M6811_OP_IY) |
| { |
| if (example) |
| sprintf (p, "%d,X", rand () & 0x0FF); |
| else |
| strcpy (p, _("<imm8>,X")); |
| p = &p[strlen (p)]; |
| } |
| |
| if (format & M6812_OP_IDX) |
| { |
| if (example) |
| sprintf (p, "%d,X", rand () & 0x0FF); |
| else |
| strcpy (p, "n,r"); |
| p = &p[strlen (p)]; |
| } |
| |
| if (format & M6812_OP_PAGE) |
| { |
| if (example) |
| sprintf (p, ", %d", rand () & 0x0FF); |
| else |
| strcpy (p, ", <page>"); |
| p = &p[strlen (p)]; |
| } |
| |
| if (format & M6811_OP_DIRECT) |
| { |
| if (example) |
| sprintf (p, "*Z%d", rand () & 0x0FF); |
| else |
| strcpy (p, _("*<abs8>")); |
| p = &p[strlen (p)]; |
| } |
| |
| if (format & M6811_OP_BITMASK) |
| { |
| if (buf[0]) |
| *p++ = ' '; |
| |
| if (example) |
| sprintf (p, "#$%02x", rand () & 0x0FF); |
| else |
| strcpy (p, _("#<mask>")); |
| |
| p = &p[strlen (p)]; |
| if (format & M6811_OP_JUMP_REL) |
| *p++ = ' '; |
| } |
| |
| if (format & M6811_OP_IND16) |
| { |
| if (example) |
| sprintf (p, _("symbol%d"), rand () & 0x0FF); |
| else |
| strcpy (p, _("<abs>")); |
| |
| p = &p[strlen (p)]; |
| } |
| |
| if (format & (M6811_OP_JUMP_REL | M6812_OP_JUMP_REL16)) |
| { |
| if (example) |
| { |
| if (format & M6811_OP_BITMASK) |
| { |
| sprintf (p, ".+%d", rand () & 0x7F); |
| } |
| else |
| { |
| sprintf (p, "L%d", rand () & 0x0FF); |
| } |
| } |
| else |
| strcpy (p, _("<label>")); |
| } |
| } |
| return buf; |
| } |
| |
| /* Prints the list of instructions with the possible operands. */ |
| static void |
| print_opcode_list (void) |
| { |
| int i; |
| const char *prev_name = ""; |
| struct m68hc11_opcode *opcodes; |
| int example = flag_print_opcodes == 2; |
| |
| if (example) |
| printf (_("# Example of `%s' instructions\n\t.sect .text\n_start:\n"), |
| default_cpu); |
| |
| opcodes = m68hc11_sorted_opcodes; |
| |
| /* Walk the list sorted on names (by md_begin). We only report |
| one instruction per line, and we collect the different operand |
| formats. */ |
| for (i = 0; i < num_opcodes; i++, opcodes++) |
| { |
| char *fmt = print_opcode_format (opcodes, example); |
| |
| if (example) |
| { |
| printf ("L%d:\t", i); |
| printf ("%s %s\n", opcodes->name, fmt); |
| } |
| else |
| { |
| if (strcmp (prev_name, opcodes->name)) |
| { |
| if (i > 0) |
| printf ("\n"); |
| |
| printf ("%-5.5s ", opcodes->name); |
| prev_name = (char *) opcodes->name; |
| } |
| if (fmt[0]) |
| printf (" [%s]", fmt); |
| } |
| } |
| printf ("\n"); |
| } |
| |
| /* Print the instruction format. This operation is called when some |
| instruction is not correct. Instruction format is printed as an |
| error message. */ |
| static void |
| print_insn_format (char *name) |
| { |
| struct m68hc11_opcode_def *opc; |
| struct m68hc11_opcode *opcode; |
| char buf[128]; |
| |
| opc = (struct m68hc11_opcode_def *) str_hash_find (m68hc11_hash, name); |
| if (opc == NULL) |
| { |
| as_bad (_("Instruction `%s' is not recognized."), name); |
| return; |
| } |
| opcode = opc->opcode; |
| |
| as_bad (_("Instruction formats for `%s':"), name); |
| do |
| { |
| char *fmt; |
| |
| fmt = print_opcode_format (opcode, 0); |
| sprintf (buf, "\t%-5.5s %s", opcode->name, fmt); |
| |
| as_bad ("%s", buf); |
| opcode++; |
| } |
| while (strcmp (opcode->name, name) == 0); |
| } |
| |
| /* Analysis of 68HC11 and 68HC12 operands. */ |
| |
| /* reg_name_search() finds the register number given its name. |
| Returns the register number or REG_NONE on failure. */ |
| static register_id |
| reg_name_search (char *name) |
| { |
| if (strcasecmp (name, "x") == 0 || strcasecmp (name, "ix") == 0) |
| return REG_X; |
| if (strcasecmp (name, "y") == 0 || strcasecmp (name, "iy") == 0) |
| return REG_Y; |
| if (strcasecmp (name, "a") == 0) |
| return REG_A; |
| if (strcasecmp (name, "b") == 0) |
| return REG_B; |
| if (strcasecmp (name, "d") == 0) |
| return REG_D; |
| if (strcasecmp (name, "sp") == 0) |
| return REG_SP; |
| if (strcasecmp (name, "pc") == 0) |
| return REG_PC; |
| if (strcasecmp (name, "ccr") == 0) |
| return REG_CCR; |
| /* XGATE */ |
| if (strcasecmp (name, "r0") == 0) |
| return REG_R0; |
| if (strcasecmp (name, "r1") == 0) |
| return REG_R1; |
| if (strcasecmp (name, "r2") == 0) |
| return REG_R2; |
| if (strcasecmp (name, "r3") == 0) |
| return REG_R3; |
| if (strcasecmp (name, "r4") == 0) |
| return REG_R4; |
| if (strcasecmp (name, "r5") == 0) |
| return REG_R5; |
| if (strcasecmp (name, "r6") == 0) |
| return REG_R6; |
| if (strcasecmp (name, "r7") == 0) |
| return REG_R7; |
| if (strcasecmp (name, "sp") == 0) |
| return REG_SP_XG; |
| if (strcasecmp (name, "pc") == 0) |
| return REG_PC_XG; |
| if (strcasecmp (name, "ccr") == 0) |
| return REG_CCR_XG; |
| return REG_NONE; |
| } |
| |
| static char * |
| skip_whites (char *p) |
| { |
| while (*p == ' ' || *p == '\t') |
| p++; |
| |
| return p; |
| } |
| |
| /* Check the string at input_line_pointer |
| to see if it is a valid register name. */ |
| static register_id |
| register_name (void) |
| { |
| register_id reg_number; |
| char c, *p = input_line_pointer; |
| |
| if (!is_name_beginner (*p++)) |
| return REG_NONE; |
| |
| while (is_part_of_name (*p++)) |
| continue; |
| |
| c = *--p; |
| if (c) |
| *p++ = 0; |
| |
| /* Look to see if it's in the register table. */ |
| reg_number = reg_name_search (input_line_pointer); |
| if (reg_number != REG_NONE) |
| { |
| if (c) |
| *--p = c; |
| |
| input_line_pointer = p; |
| return reg_number; |
| } |
| if (c) |
| *--p = c; |
| |
| return reg_number; |
| } |
| #define M6811_OP_CALL_ADDR 0x00800000 |
| #define M6811_OP_PAGE_ADDR 0x04000000 |
| |
| /* Parse a string of operands and return an array of expressions. |
| |
| Operand mode[0] mode[1] exp[0] exp[1] |
| #n M6811_OP_IMM16 - O_* |
| *<exp> M6811_OP_DIRECT - O_* |
| .{+-}<exp> M6811_OP_JUMP_REL - O_* |
| <exp> M6811_OP_IND16 - O_* |
| ,r N,r M6812_OP_IDX M6812_OP_REG O_constant O_register |
| n,-r M6812_PRE_DEC M6812_OP_REG O_constant O_register |
| n,+r M6812_PRE_INC " " |
| n,r- M6812_POST_DEC " " |
| n,r+ M6812_POST_INC " " |
| A,r B,r D,r M6811_OP_REG M6812_OP_REG O_register O_register |
| [D,r] M6811_OP_D_IDX M6812_OP_REG O_register O_register |
| [n,r] M6811_OP_D_IDX_2 M6812_OP_REG O_constant O_register */ |
| static int |
| get_operand (operand *oper, int which, long opmode) |
| { |
| char *p = input_line_pointer; |
| int mode; |
| register_id reg; |
| |
| oper->exp.X_op = O_absent; |
| oper->reg1 = REG_NONE; |
| oper->reg2 = REG_NONE; |
| mode = M6811_OP_NONE; |
| |
| p = skip_whites (p); |
| |
| if (*p == 0 || *p == '\n' || *p == '\r') |
| { |
| input_line_pointer = p; |
| return 0; |
| } |
| |
| if (*p == '*' && (opmode & (M6811_OP_DIRECT | M6811_OP_IND16))) |
| { |
| mode = M6811_OP_DIRECT; |
| p++; |
| } |
| else if (*p == '#') |
| { |
| if (!(opmode & (M6811_OP_IMM8 | M6811_OP_IMM16 | M6811_OP_BITMASK))) |
| { |
| as_bad (_("Immediate operand is not allowed for operand %d."), |
| which); |
| return -1; |
| } |
| |
| mode = M6811_OP_IMM16; |
| p++; |
| if (startswith (p, "%hi")) |
| { |
| p += 3; |
| mode |= M6811_OP_HIGH_ADDR; |
| } |
| else if (startswith (p, "%lo")) |
| { |
| p += 3; |
| mode |= M6811_OP_LOW_ADDR; |
| } |
| /* %page modifier is used to obtain only the page number |
| of the address of a function. */ |
| else if (startswith (p, "%page")) |
| { |
| p += 5; |
| mode |= M6811_OP_PAGE_ADDR; |
| } |
| |
| /* %addr modifier is used to obtain the physical address part |
| of the function (16-bit). For 68HC12 the function will be |
| mapped in the 16K window at 0x8000 and the value will be |
| within that window (although the function address may not fit |
| in 16-bit). See bfd/elf32-m68hc12.c for the translation. */ |
| else if (startswith (p, "%addr")) |
| { |
| p += 5; |
| mode |= M6811_OP_CALL_ADDR; |
| } |
| } |
| else if (*p == '.' && (p[1] == '+' || p[1] == '-')) |
| { |
| p++; |
| mode = M6811_OP_JUMP_REL; |
| } |
| else if (*p == '[') |
| { |
| if (current_architecture & cpu6811) |
| as_bad (_("Indirect indexed addressing is not valid for 68HC11.")); |
| |
| p++; |
| mode = M6812_OP_D_IDX; |
| p = skip_whites (p); |
| } |
| else if (*p == ',') /* Special handling of ,x and ,y. */ |
| { |
| p++; |
| input_line_pointer = p; |
| |
| reg = register_name (); |
| if (reg != REG_NONE) |
| { |
| oper->reg1 = reg; |
| oper->exp.X_op = O_constant; |
| oper->exp.X_add_number = 0; |
| oper->mode = M6812_OP_IDX; |
| return 1; |
| } |
| as_bad (_("Spurious `,' or bad indirect register addressing mode.")); |
| return -1; |
| } |
| /* Handle 68HC12 page specification in 'call foo,%page(bar)'. */ |
| else if ((opmode & M6812_OP_PAGE) && startswith (p, "%page")) |
| { |
| p += 5; |
| mode = M6811_OP_PAGE_ADDR | M6812_OP_PAGE | M6811_OP_IND16; |
| } |
| input_line_pointer = p; |
| |
| if (mode == M6811_OP_NONE || mode == M6812_OP_D_IDX) |
| reg = register_name (); |
| else |
| reg = REG_NONE; |
| |
| if (reg != REG_NONE) |
| { |
| p = skip_whites (input_line_pointer); |
| if (*p == ']' && mode == M6812_OP_D_IDX) |
| { |
| as_bad |
| (_("Missing second register or offset for indexed-indirect mode.")); |
| return -1; |
| } |
| |
| oper->reg1 = reg; |
| oper->mode = mode | M6812_OP_REG; |
| if (*p != ',') |
| { |
| if (mode == M6812_OP_D_IDX) |
| { |
| as_bad (_("Missing second register for indexed-indirect mode.")); |
| return -1; |
| } |
| return 1; |
| } |
| |
| p++; |
| input_line_pointer = p; |
| reg = register_name (); |
| if (reg != REG_NONE) |
| { |
| p = skip_whites (input_line_pointer); |
| if (mode == M6812_OP_D_IDX) |
| { |
| if (*p != ']') |
| { |
| as_bad (_("Missing `]' to close indexed-indirect mode.")); |
| return -1; |
| } |
| p++; |
| oper->mode = M6812_OP_D_IDX; |
| } |
| input_line_pointer = p; |
| |
| oper->reg2 = reg; |
| return 1; |
| } |
| return 1; |
| } |
| |
| /* In MRI mode, isolate the operand because we can't distinguish |
| operands from comments. */ |
| if (flag_mri) |
| { |
| char c = 0; |
| |
| p = skip_whites (p); |
| while (*p && *p != ' ' && *p != '\t') |
| p++; |
| |
| if (*p) |
| { |
| c = *p; |
| *p = 0; |
| } |
| |
| /* Parse as an expression. */ |
| expression (&oper->exp); |
| |
| if (c) |
| { |
| *p = c; |
| } |
| } |
| else |
| { |
| expression (&oper->exp); |
| } |
| |
| if (oper->exp.X_op == O_illegal) |
| { |
| as_bad (_("Illegal operand.")); |
| return -1; |
| } |
| else if (oper->exp.X_op == O_absent) |
| { |
| as_bad (_("Missing operand.")); |
| return -1; |
| } |
| |
| p = input_line_pointer; |
| |
| if (mode == M6811_OP_NONE || mode == M6811_OP_DIRECT |
| || mode == M6812_OP_D_IDX) |
| { |
| p = skip_whites (input_line_pointer); |
| |
| if (*p == ',') |
| { |
| int possible_mode = M6811_OP_NONE; |
| char *old_input_line; |
| |
| old_input_line = p; |
| p++; |
| |
| /* 68HC12 pre increment or decrement. */ |
| if (mode == M6811_OP_NONE) |
| { |
| if (*p == '-') |
| { |
| possible_mode = M6812_PRE_DEC; |
| p++; |
| } |
| else if (*p == '+') |
| { |
| possible_mode = M6812_PRE_INC; |
| p++; |
| } |
| p = skip_whites (p); |
| } |
| input_line_pointer = p; |
| reg = register_name (); |
| |
| /* Backtrack if we have a valid constant expression and |
| it does not correspond to the offset of the 68HC12 indexed |
| addressing mode (as in N,x). */ |
| if (reg == REG_NONE && mode == M6811_OP_NONE |
| && possible_mode != M6811_OP_NONE) |
| { |
| oper->mode = M6811_OP_IND16 | M6811_OP_JUMP_REL; |
| input_line_pointer = skip_whites (old_input_line); |
| return 1; |
| } |
| |
| if (possible_mode != M6811_OP_NONE) |
| mode = possible_mode; |
| |
| if ((current_architecture & cpu6811) |
| && possible_mode != M6811_OP_NONE) |
| as_bad (_("Pre-increment mode is not valid for 68HC11")); |
| /* Backtrack. */ |
| if (which == 0 && opmode & M6812_OP_IDX_P2 |
| && reg != REG_X && reg != REG_Y |
| && reg != REG_PC && reg != REG_SP) |
| { |
| reg = REG_NONE; |
| input_line_pointer = p; |
| } |
| |
| if (reg == REG_NONE && mode != M6811_OP_DIRECT |
| && !(mode == M6811_OP_NONE && opmode & M6811_OP_IND16)) |
| { |
| as_bad (_("Wrong register in register indirect mode.")); |
| return -1; |
| } |
| if (mode == M6812_OP_D_IDX) |
| { |
| p = skip_whites (input_line_pointer); |
| if (*p++ != ']') |
| { |
| as_bad (_("Missing `]' to close register indirect operand.")); |
| return -1; |
| } |
| input_line_pointer = p; |
| oper->reg1 = reg; |
| oper->mode = M6812_OP_D_IDX_2; |
| return 1; |
| } |
| if (reg != REG_NONE) |
| { |
| oper->reg1 = reg; |
| if (mode == M6811_OP_NONE) |
| { |
| p = input_line_pointer; |
| if (*p == '-') |
| { |
| mode = M6812_POST_DEC; |
| p++; |
| if (current_architecture & cpu6811) |
| as_bad |
| (_("Post-decrement mode is not valid for 68HC11.")); |
| } |
| else if (*p == '+') |
| { |
| mode = M6812_POST_INC; |
| p++; |
| if (current_architecture & cpu6811) |
| as_bad |
| (_("Post-increment mode is not valid for 68HC11.")); |
| } |
| else |
| mode = M6812_OP_IDX; |
| |
| input_line_pointer = p; |
| } |
| else |
| mode |= M6812_OP_IDX; |
| |
| oper->mode = mode; |
| return 1; |
| } |
| input_line_pointer = old_input_line; |
| } |
| |
| if (mode == M6812_OP_D_IDX_2) |
| { |
| as_bad (_("Invalid indexed indirect mode.")); |
| return -1; |
| } |
| } |
| |
| /* If the mode is not known until now, this is either a label |
| or an indirect address. */ |
| if (mode == M6811_OP_NONE) |
| mode = M6811_OP_IND16 | M6811_OP_JUMP_REL; |
| |
| p = input_line_pointer; |
| while (*p == ' ' || *p == '\t') |
| p++; |
| input_line_pointer = p; |
| oper->mode = mode; |
| |
| return 1; |
| } |
| |
| #define M6812_AUTO_INC_DEC (M6812_PRE_INC | M6812_PRE_DEC \ |
| | M6812_POST_INC | M6812_POST_DEC) |
| |
| /* Checks that the number 'num' fits for a given mode. */ |
| static int |
| check_range (long num, int mode) |
| { |
| if (current_architecture == cpuxgate) |
| { |
| switch (mode) |
| { |
| case M68XG_OP_IMM3: |
| return (num >= 0 && num <= 7) ? 1 : 0; |
| |
| case M68XG_OP_R_IMM4: |
| return (num >= 0 && num <= 15) ? 1 : 0; |
| |
| case M68XG_OP_R_R_OFFS5: |
| return (num >= 0 && num <= 31) ? 1 : 0; |
| |
| case M68XG_OP_R_IMM8: |
| return (num >= 0 && num <= 255) ? 1 : 0; |
| |
| case M68XG_OP_R_IMM16: |
| return (num >= 0 && num <= 65535) ? 1 : 0; |
| |
| case M68XG_OP_B_MARKER: |
| return (num >= -512 && num <= 511) ? 1 : 0; |
| |
| case M68XG_OP_BRA_MARKER: |
| return (num >= -1024 && num <= 1023) ? 1 : 0; |
| |
| default: |
| return 0; |
| } |
| } |
| else |
| { |
| /* Auto increment and decrement are ok for [-8..8] without 0. */ |
| if (mode & M6812_AUTO_INC_DEC) |
| return (num != 0 && num <= 8 && num >= -8); |
| |
| /* The 68HC12 supports 5, 9 and 16-bit offsets. */ |
| if (mode & (M6812_INDEXED_IND | M6812_INDEXED | M6812_OP_IDX)) |
| mode = M6811_OP_IND16; |
| |
| if (mode & M6812_OP_JUMP_REL16) |
| mode = M6811_OP_IND16; |
| |
| mode &= ~M6811_OP_BRANCH; |
| switch (mode) |
| { |
| case M6811_OP_IX: |
| case M6811_OP_IY: |
| case M6811_OP_DIRECT: |
| return (num >= 0 && num <= 255) ? 1 : 0; |
| |
| case M6811_OP_BITMASK: |
| case M6811_OP_IMM8: |
| case M6812_OP_PAGE: |
| return (((num & 0xFFFFFF00) == 0) || ((num & 0xFFFFFF00) == 0xFFFFFF00)) |
| ? 1 : 0; |
| |
| case M6811_OP_JUMP_REL: |
| return (num >= -128 && num <= 127) ? 1 : 0; |
| |
| case M6811_OP_IND16: |
| case M6811_OP_IND16 | M6812_OP_PAGE: |
| case M6811_OP_IMM16: |
| return (((num & 0xFFFF0000) == 0) || ((num & 0xFFFF0000) == 0xFFFF0000)) |
| ? 1 : 0; |
| |
| case M6812_OP_IBCC_MARKER: |
| case M6812_OP_TBCC_MARKER: |
| case M6812_OP_DBCC_MARKER: |
| return (num >= -256 && num <= 255) ? 1 : 0; |
| |
| case M6812_OP_TRAP_ID: |
| return ((num >= 0x30 && num <= 0x39) |
| || (num >= 0x40 && num <= 0x0ff)) ? 1 : 0; |
| |
| default: |
| return 0; |
| } |
| } |
| } |
| |
| /* Gas fixup generation. */ |
| |
| /* Put a 1 byte expression described by 'oper'. If this expression contains |
| unresolved symbols, generate an 8-bit fixup. */ |
| static void |
| fixup8 (expressionS *oper, int mode, int opmode) |
| { |
| char *f; |
| |
| f = frag_more (1); |
| |
| if (oper->X_op == O_constant) |
| { |
| if (mode & M6812_OP_TRAP_ID |
| && !check_range (oper->X_add_number, M6812_OP_TRAP_ID)) |
| { |
| static char trap_id_warn_once = 0; |
| |
| as_bad (_("Trap id `%ld' is out of range."), oper->X_add_number); |
| if (trap_id_warn_once == 0) |
| { |
| trap_id_warn_once = 1; |
| as_bad (_("Trap id must be within [0x30..0x39] or [0x40..0xff].")); |
| } |
| } |
| |
| if (!(mode & M6812_OP_TRAP_ID) |
| && !check_range (oper->X_add_number, mode)) |
| { |
| as_bad (_("Operand out of 8-bit range: `%ld'."), oper->X_add_number); |
| } |
| number_to_chars_bigendian (f, oper->X_add_number & 0x0FF, 1); |
| } |
| else if (oper->X_op != O_register) |
| { |
| if (mode & M6812_OP_TRAP_ID) |
| as_bad (_("The trap id must be a constant.")); |
| |
| if (mode == M6811_OP_JUMP_REL) |
| { |
| fix_new_exp (frag_now, f - frag_now->fr_literal, 1, |
| oper, true, BFD_RELOC_8_PCREL); |
| } |
| else |
| { |
| fixS *fixp; |
| bfd_reloc_code_real_type reloc; |
| |
| /* Now create an 8-bit fixup. If there was some %hi, %lo |
| or %page modifier, generate the reloc accordingly. */ |
| if (opmode & M6811_OP_HIGH_ADDR) |
| reloc = BFD_RELOC_M68HC11_HI8; |
| else if (opmode & M6811_OP_LOW_ADDR) |
| reloc = BFD_RELOC_M68HC11_LO8; |
| else if (opmode & M6811_OP_PAGE_ADDR) |
| reloc = BFD_RELOC_M68HC11_PAGE; |
| else |
| reloc = BFD_RELOC_8; |
| |
| fixp = fix_new_exp (frag_now, f - frag_now->fr_literal, 1, |
| oper, false, reloc); |
| if (reloc != BFD_RELOC_8) |
| fixp->fx_no_overflow = 1; |
| } |
| number_to_chars_bigendian (f, 0, 1); |
| } |
| else |
| { |
| as_fatal (_("Operand `%x' not recognized in fixup8."), oper->X_op); |
| } |
| } |
| |
| /* Put a 2 byte expression described by 'oper'. If this expression contains |
| unresolved symbols, generate a 16-bit fixup. */ |
| static void |
| fixup16 (expressionS *oper, int mode, int opmode ATTRIBUTE_UNUSED) |
| { |
| char *f; |
| |
| f = frag_more (2); |
| |
| if (oper->X_op == O_constant) |
| { |
| if (!check_range (oper->X_add_number, mode)) |
| { |
| as_bad (_("Operand out of 16-bit range: `%ld'."), |
| oper->X_add_number); |
| } |
| number_to_chars_bigendian (f, oper->X_add_number & 0x0FFFF, 2); |
| } |
| else if (oper->X_op != O_register) |
| { |
| fixS *fixp; |
| bfd_reloc_code_real_type reloc; |
| |
| if ((opmode & M6811_OP_CALL_ADDR) && (mode & M6811_OP_IMM16)) |
| reloc = BFD_RELOC_M68HC11_LO16; |
| else if (mode & M6812_OP_JUMP_REL16) |
| reloc = BFD_RELOC_16_PCREL; |
| else if (mode & M6812_OP_PAGE) |
| reloc = BFD_RELOC_M68HC11_LO16; |
| else |
| reloc = BFD_RELOC_16; |
| |
| /* Now create a 16-bit fixup. */ |
| fixp = fix_new_exp (frag_now, f - frag_now->fr_literal, 2, |
| oper, |
| reloc == BFD_RELOC_16_PCREL, |
| reloc); |
| number_to_chars_bigendian (f, 0, 2); |
| |
| if (reloc == BFD_RELOC_M68HC11_LO16) |
| fixp->fx_no_overflow = 1; |
| } |
| else |
| { |
| as_fatal (_("Operand `%x' not recognized in fixup16."), oper->X_op); |
| } |
| } |
| |
| /* Put a 3 byte expression described by 'oper'. If this expression contains |
| unresolved symbols, generate a 24-bit fixup. */ |
| static void |
| fixup24 (expressionS *oper, int mode, int opmode ATTRIBUTE_UNUSED) |
| { |
| char *f; |
| |
| f = frag_more (3); |
| |
| if (oper->X_op == O_constant) |
| { |
| if (!check_range (oper->X_add_number, mode)) |
| { |
| as_bad (_("Operand out of 16-bit range: `%ld'."), |
| oper->X_add_number); |
| } |
| number_to_chars_bigendian (f, oper->X_add_number & 0x0FFFFFF, 3); |
| } |
| else if (oper->X_op != O_register) |
| { |
| /* Now create a 24-bit fixup. */ |
| fix_new_exp (frag_now, f - frag_now->fr_literal, 3, |
| oper, false, BFD_RELOC_M68HC11_24); |
| number_to_chars_bigendian (f, 0, 3); |
| } |
| else |
| { |
| as_fatal (_("Operand `%x' not recognized in fixup16."), oper->X_op); |
| } |
| } |
| |
| /* XGATE Put a 1 byte expression described by 'oper'. If this expression |
| contains unresolved symbols, generate an 8-bit fixup. */ |
| static void |
| fixup8_xg (expressionS *oper, int mode, int opmode) |
| { |
| char *f; |
| |
| f = frag_more (1); |
| |
| if (oper->X_op == O_constant) |
| { |
| fixS *fixp; |
| bfd_reloc_code_real_type reloc; |
| |
| if ((opmode & M6811_OP_HIGH_ADDR) || (opmode & M6811_OP_LOW_ADDR)) |
| { |
| if (opmode & M6811_OP_HIGH_ADDR) |
| reloc = BFD_RELOC_M68HC11_HI8; |
| else |
| reloc = BFD_RELOC_M68HC11_LO8; |
| |
| fixp = fix_new_exp (frag_now, f - frag_now->fr_literal, 1, |
| oper, false, reloc); |
| fixp->fx_no_overflow = 1; |
| number_to_chars_bigendian (f, 0, 1); |
| } |
| else |
| { |
| if (!(check_range (oper->X_add_number, mode))) |
| as_bad (_("Operand out of 8-bit range: `%ld'."), |
| oper->X_add_number); |
| number_to_chars_bigendian (f, oper->X_add_number & 0x0FF, 1); |
| } |
| } |
| else if (oper->X_op != O_register) |
| { |
| if (mode == M68XG_OP_REL9) |
| { |
| /* Future improvement: |
| This fixup/reloc isn't adding on constants to symbols. */ |
| fix_new_exp (frag_now, f - frag_now->fr_literal -1, 2, |
| oper, true, BFD_RELOC_M68HC12_9_PCREL); |
| } |
| else if (mode == M68XG_OP_REL10) |
| { |
| /* Future improvement: |
| This fixup/reloc isn't adding on constants to symbols. */ |
| fix_new_exp (frag_now, f - frag_now->fr_literal -1, 2, |
| oper, true, BFD_RELOC_M68HC12_10_PCREL); |
| } |
| else |
| { |
| fixS *fixp; |
| bfd_reloc_code_real_type reloc; |
| |
| /* Now create an 8-bit fixup. If there was some %hi, %lo |
| modifier, generate the reloc accordingly. */ |
| if (opmode & M6811_OP_HIGH_ADDR) |
| reloc = BFD_RELOC_M68HC11_HI8; |
| else if (opmode & M6811_OP_LOW_ADDR) |
| reloc = BFD_RELOC_M68HC11_LO8; |
| else |
| reloc = BFD_RELOC_8; |
| |
| fixp = fix_new_exp (frag_now, f - frag_now->fr_literal, 1, |
| oper, false, reloc); |
| if (reloc != BFD_RELOC_8) |
| fixp->fx_no_overflow = 1; |
| } |
| number_to_chars_bigendian (f, 0, 1); |
| } |
| else |
| as_fatal (_("Operand `%x' not recognized in fixup8."), oper->X_op); |
| } |
| |
| /* 68HC11 and 68HC12 code generation. */ |
| |
| /* Translate the short branch/bsr instruction into a long branch. */ |
| |
| static unsigned char |
| convert_branch (unsigned char code) |
| { |
| if (IS_OPCODE (code, M6812_BSR)) |
| return M6812_JSR; |
| else if (IS_OPCODE (code, M6811_BSR)) |
| return M6811_JSR; |
| else if (IS_OPCODE (code, M6811_BRA)) |
| return (current_architecture & cpu6812) ? M6812_JMP : M6811_JMP; |
| else |
| as_fatal (_("Unexpected branch conversion with `%x'"), code); |
| |
| /* Keep gcc happy. */ |
| return M6811_JSR; |
| } |
| |
| /* Start a new insn that contains at least 'size' bytes. Record the |
| line information of that insn in the dwarf2 debug sections. */ |
| static char * |
| m68hc11_new_insn (int size) |
| { |
| char *f; |
| |
| f = frag_more (size); |
| |
| dwarf2_emit_insn (size); |
| |
| return f; |
| } |
| |
| /* Builds a jump instruction (bra, bcc, bsr). */ |
| static void |
| build_jump_insn (struct m68hc11_opcode *opcode, operand operands[], |
| int nb_operands, int jmp_mode) |
| { |
| unsigned char code; |
| char *f; |
| unsigned long n; |
| |
| /* The relative branch conversion is not supported for |
| brclr and brset. */ |
| gas_assert ((opcode->format & M6811_OP_BITMASK) == 0); |
| gas_assert (nb_operands == 1); |
| gas_assert (operands[0].reg1 == REG_NONE && operands[0].reg2 == REG_NONE); |
| |
| code = opcode->opcode; |
| |
| n = operands[0].exp.X_add_number; |
| |
| /* Turn into a long branch: |
| - when force long branch option (and not for jbcc pseudos), |
| - when jbcc and the constant is out of -128..127 range, |
| - when branch optimization is allowed and branch out of range. */ |
| if ((jmp_mode == 0 && flag_force_long_jumps) |
| || (operands[0].exp.X_op == O_constant |
| && (!check_range (n, opcode->format) && |
| (jmp_mode == 1 || flag_fixed_branches == 0)))) |
| { |
| fix_new (frag_now, frag_now_fix (), 0, |
| &abs_symbol, 0, 1, BFD_RELOC_M68HC11_RL_JUMP); |
| |
| if (code == M6811_BSR || code == M6811_BRA || code == M6812_BSR) |
| { |
| code = convert_branch (code); |
| |
| f = m68hc11_new_insn (1); |
| number_to_chars_bigendian (f, code, 1); |
| } |
| else if (current_architecture & cpu6812) |
| { |
| /* 68HC12: translate the bcc into a lbcc. */ |
| f = m68hc11_new_insn (2); |
| number_to_chars_bigendian (f, M6811_OPCODE_PAGE2, 1); |
| number_to_chars_bigendian (f + 1, code, 1); |
| fixup16 (&operands[0].exp, M6812_OP_JUMP_REL16, |
| M6812_OP_JUMP_REL16); |
| return; |
| } |
| else |
| { |
| /* 68HC11: translate the bcc into b!cc +3; jmp <L>. */ |
| f = m68hc11_new_insn (3); |
| code ^= 1; |
| number_to_chars_bigendian (f, code, 1); |
| number_to_chars_bigendian (f + 1, 3, 1); |
| number_to_chars_bigendian (f + 2, M6811_JMP, 1); |
| } |
| fixup16 (&operands[0].exp, M6811_OP_IND16, M6811_OP_IND16); |
| return; |
| } |
| |
| /* Branch with a constant that must fit in 8-bits. */ |
| if (operands[0].exp.X_op == O_constant) |
| { |
| if (!check_range (n, opcode->format)) |
| { |
| as_bad (_("Operand out of range for a relative branch: `%ld'"), |
| n); |
| } |
| else if (opcode->format & M6812_OP_JUMP_REL16) |
| { |
| f = m68hc11_new_insn (4); |
| number_to_chars_bigendian (f, M6811_OPCODE_PAGE2, 1); |
| number_to_chars_bigendian (f + 1, code, 1); |
| number_to_chars_bigendian (f + 2, n & 0x0ffff, 2); |
| } |
| else |
| { |
| f = m68hc11_new_insn (2); |
| number_to_chars_bigendian (f, code, 1); |
| number_to_chars_bigendian (f + 1, n & 0x0FF, 1); |
| } |
| } |
| else if (opcode->format & M6812_OP_JUMP_REL16) |
| { |
| fix_new (frag_now, frag_now_fix (), 0, |
| &abs_symbol, 0, 1, BFD_RELOC_M68HC11_RL_JUMP); |
| |
| f = m68hc11_new_insn (2); |
| number_to_chars_bigendian (f, M6811_OPCODE_PAGE2, 1); |
| number_to_chars_bigendian (f + 1, code, 1); |
| fixup16 (&operands[0].exp, M6812_OP_JUMP_REL16, M6812_OP_JUMP_REL16); |
| } |
| else |
| { |
| char *op; |
| |
| fix_new (frag_now, frag_now_fix (), 0, |
| &abs_symbol, 0, 1, BFD_RELOC_M68HC11_RL_JUMP); |
| |
| /* Branch offset must fit in 8-bits, don't do some relax. */ |
| if (jmp_mode == 0 && flag_fixed_branches) |
| { |
| op = m68hc11_new_insn (1); |
| number_to_chars_bigendian (op, code, 1); |
| fixup8 (&operands[0].exp, M6811_OP_JUMP_REL, M6811_OP_JUMP_REL); |
| } |
| |
| /* bra/bsr made be changed into jmp/jsr. */ |
| else if (code == M6811_BSR || code == M6811_BRA || code == M6812_BSR) |
| { |
| /* Allocate worst case storage. */ |
| op = m68hc11_new_insn (3); |
| number_to_chars_bigendian (op, code, 1); |
| number_to_chars_bigendian (op + 1, 0, 1); |
| frag_variant (rs_machine_dependent, 1, 1, |
| ENCODE_RELAX (STATE_PC_RELATIVE, STATE_UNDF), |
| operands[0].exp.X_add_symbol, (offsetT) n, |
| op); |
| } |
| else if (current_architecture & cpu6812) |
| { |
| op = m68hc11_new_insn (2); |
| number_to_chars_bigendian (op, code, 1); |
| number_to_chars_bigendian (op + 1, 0, 1); |
| frag_var (rs_machine_dependent, 2, 2, |
| ENCODE_RELAX (STATE_CONDITIONAL_BRANCH_6812, STATE_UNDF), |
| operands[0].exp.X_add_symbol, (offsetT) n, op); |
| } |
| else |
| { |
| op = m68hc11_new_insn (2); |
| number_to_chars_bigendian (op, code, 1); |
| number_to_chars_bigendian (op + 1, 0, 1); |
| frag_var (rs_machine_dependent, 3, 3, |
| ENCODE_RELAX (STATE_CONDITIONAL_BRANCH, STATE_UNDF), |
| operands[0].exp.X_add_symbol, (offsetT) n, op); |
| } |
| } |
| } |
| |
| /* Builds a dbne/dbeq/tbne/tbeq instruction. */ |
| static void |
| build_dbranch_insn (struct m68hc11_opcode *opcode, operand operands[], |
| int nb_operands, int jmp_mode) |
| { |
| unsigned char code; |
| char *f; |
| unsigned long n; |
| |
| /* The relative branch conversion is not supported for |
| brclr and brset. */ |
| gas_assert ((opcode->format & M6811_OP_BITMASK) == 0); |
| gas_assert (nb_operands == 2); |
| gas_assert (operands[0].reg1 != REG_NONE); |
| |
| code = opcode->opcode & 0x0FF; |
| |
| f = m68hc11_new_insn (1); |
| number_to_chars_bigendian (f, code, 1); |
| |
| n = operands[1].exp.X_add_number; |
| code = operands[0].reg1; |
| |
| if (operands[0].reg1 == REG_NONE || operands[0].reg1 == REG_CCR |
| || operands[0].reg1 == REG_PC) |
| as_bad (_("Invalid register for dbcc/tbcc instruction.")); |
| |
| if (opcode->format & M6812_OP_IBCC_MARKER) |
| code |= 0x80; |
| else if (opcode->format & M6812_OP_TBCC_MARKER) |
| code |= 0x40; |
| |
| if (!(opcode->format & M6812_OP_EQ_MARKER)) |
| code |= 0x20; |
| |
| /* Turn into a long branch: |
| - when force long branch option (and not for jbcc pseudos), |
| - when jdbcc and the constant is out of -256..255 range, |
| - when branch optimization is allowed and branch out of range. */ |
| if ((jmp_mode == 0 && flag_force_long_jumps) |
| || (operands[1].exp.X_op == O_constant |
| && (!check_range (n, M6812_OP_IBCC_MARKER) && |
| (jmp_mode == 1 || flag_fixed_branches == 0)))) |
| { |
| f = frag_more (2); |
| code ^= 0x20; |
| number_to_chars_bigendian (f, code, 1); |
| number_to_chars_bigendian (f + 1, M6812_JMP, 1); |
| fixup16 (&operands[0].exp, M6811_OP_IND16, M6811_OP_IND16); |
| return; |
| } |
| |
| /* Branch with a constant that must fit in 9-bits. */ |
| if (operands[1].exp.X_op == O_constant) |
| { |
| if (!check_range (n, M6812_OP_IBCC_MARKER)) |
| { |
| as_bad (_("Operand out of range for a relative branch: `%ld'"), |
| n); |
| } |
| else |
| { |
| if ((long) n < 0) |
| code |= 0x10; |
| |
| f = frag_more (2); |
| number_to_chars_bigendian (f, code, 1); |
| number_to_chars_bigendian (f + 1, n & 0x0FF, 1); |
| } |
| } |
| else |
| { |
| /* Branch offset must fit in 8-bits, don't do some relax. */ |
| if (jmp_mode == 0 && flag_fixed_branches) |
| { |
| fixup8 (&operands[0].exp, M6811_OP_JUMP_REL, M6811_OP_JUMP_REL); |
| } |
| |
| else |
| { |
| f = frag_more (2); |
| number_to_chars_bigendian (f, code, 1); |
| number_to_chars_bigendian (f + 1, 0, 1); |
| frag_var (rs_machine_dependent, 3, 3, |
| ENCODE_RELAX (STATE_XBCC_BRANCH, STATE_UNDF), |
| operands[1].exp.X_add_symbol, (offsetT) n, f); |
| } |
| } |
| } |
| |
| #define OP_EXTENDED (M6811_OP_PAGE2 | M6811_OP_PAGE3 | M6811_OP_PAGE4) |
| |
| /* Assemble the post index byte for 68HC12 extended addressing modes. */ |
| |
| static int |
| build_indexed_byte (operand *op, int format ATTRIBUTE_UNUSED, int move_insn) |
| { |
| unsigned char byte = 0; |
| char *f; |
| int mode; |
| long val; |
| |
| val = op->exp.X_add_number; |
| mode = op->mode; |
| if (mode & M6812_AUTO_INC_DEC) |
| { |
| byte = 0x20; |
| if (mode & (M6812_POST_INC | M6812_POST_DEC)) |
| byte |= 0x10; |
| |
| if (op->exp.X_op == O_constant) |
| { |
| if (!check_range (val, mode)) |
| as_bad (_("Increment/decrement value is out of range: `%ld'."), |
| val); |
| |
| if (mode & (M6812_POST_INC | M6812_PRE_INC)) |
| byte |= (val - 1) & 0x07; |
| else |
| byte |= (8 - ((val) & 7)) | 0x8; |
| } |
| |
| switch (op->reg1) |
| { |
| case REG_NONE: |
| as_fatal (_("Expecting a register.")); |
| |
| case REG_X: |
| byte |= 0; |
| break; |
| |
| case REG_Y: |
| byte |= 0x40; |
| break; |
| |
| case REG_SP: |
| byte |= 0x80; |
| break; |
| |
| default: |
| as_bad (_("Invalid register for post/pre increment.")); |
| break; |
| } |
| |
| f = frag_more (1); |
| number_to_chars_bigendian (f, byte, 1); |
| return 1; |
| } |
| |
| if (mode & (M6812_OP_IDX | M6812_OP_D_IDX_2)) |
| { |
| switch (op->reg1) |
| { |
| case REG_X: |
| byte = 0; |
| break; |
| |
| case REG_Y: |
| byte = 1; |
| break; |
| |
| case REG_SP: |
| byte = 2; |
| break; |
| |
| case REG_PC: |
| byte = 3; |
| break; |
| |
| default: |
| as_bad (_("Invalid register.")); |
| break; |
| } |
| |
| if (op->exp.X_op == O_constant) |
| { |
| if (!check_range (val, M6812_OP_IDX)) |
| as_bad (_("Offset out of 16-bit range: %ld."), val); |
| |
| if (move_insn && !(val >= -16 && val <= 15) |
| && ((!(mode & M6812_OP_IDX) && !(mode & M6812_OP_D_IDX_2)) |
| || !(current_architecture & cpu9s12x))) |
| { |
| as_bad (_("Offset out of 5-bit range for movw/movb insn: %ld."), |
| val); |
| return -1; |
| } |
| |
| if (val >= -16 && val <= 15 && !(mode & M6812_OP_D_IDX_2)) |
| { |
| byte = byte << 6; |
| byte |= val & 0x1f; |
| f = frag_more (1); |
| number_to_chars_bigendian (f, byte, 1); |
| return 1; |
| } |
| else if (val >= -256 && val <= 255 && !(mode & M6812_OP_D_IDX_2)) |
| { |
| byte = byte << 3; |
| byte |= 0xe0; |
| if (val < 0) |
| byte |= 0x1; |
| f = frag_more (2); |
| number_to_chars_bigendian (f, byte, 1); |
| number_to_chars_bigendian (f + 1, val & 0x0FF, 1); |
| return 2; |
| } |
| else |
| { |
| byte = byte << 3; |
| if (mode & M6812_OP_D_IDX_2) |
| byte |= 0xe3; |
| else |
| byte |= 0xe2; |
| |
| f = frag_more (3); |
| number_to_chars_bigendian (f, byte, 1); |
| number_to_chars_bigendian (f + 1, val & 0x0FFFF, 2); |
| return 3; |
| } |
| } |
| |
| if (mode & M6812_OP_D_IDX_2) |
| { |
| byte = (byte << 3) | 0xe3; |
| f = frag_more (1); |
| number_to_chars_bigendian (f, byte, 1); |
| |
| fixup16 (&op->exp, 0, 0); |
| } |
| else if (op->reg1 != REG_PC) |
| { |
| symbolS *sym; |
| offsetT off; |
| |
| f = frag_more (1); |
| number_to_chars_bigendian (f, byte, 1); |
| sym = op->exp.X_add_symbol; |
| off = op->exp.X_add_number; |
| if (op->exp.X_op != O_symbol) |
| { |
| sym = make_expr_symbol (&op->exp); |
| off = 0; |
| } |
| |
| /* movb/movw cannot be relaxed. */ |
| if (move_insn) |
| { |
| if ((mode & M6812_OP_IDX) && (current_architecture & cpu9s12x)) |
| { |
| /* Must treat as a 16bit relocate as size of final result is unknown. */ |
| |
| byte <<= 3; |
| byte |= 0xe2; |
| number_to_chars_bigendian (f, byte, 1); |
| f = frag_more (2); |
| fix_new (frag_now, f - frag_now->fr_literal, 2, |
| sym, off, 0, BFD_RELOC_M68HC12_16B); |
| return 1; |
| } |
| else |
| { |
| /* Non-S12X will fail at relocate stage if offset out of range. */ |
| byte <<= 6; |
| number_to_chars_bigendian (f, byte, 1); |
| fix_new (frag_now, f - frag_now->fr_literal, 1, |
| sym, off, 0, BFD_RELOC_M68HC12_5B); |
| return 1; |
| } |
| } |
| else |
| { |
| number_to_chars_bigendian (f, byte, 1); |
| frag_var (rs_machine_dependent, 2, 2, |
| ENCODE_RELAX (STATE_INDEXED_OFFSET, STATE_UNDF), |
| sym, off, f); |
| } |
| } |
| else |
| { |
| f = frag_more (1); |
| |
| /* movb/movw cannot be relaxed. */ |
| if (move_insn) |
| { |
| byte <<= 6; |
| number_to_chars_bigendian (f, byte, 1); |
| fix_new (frag_now, f - frag_now->fr_literal, 1, |
| op->exp.X_add_symbol, op->exp.X_add_number, 0, BFD_RELOC_M68HC12_5B); |
| return 1; |
| } |
| else |
| { |
| number_to_chars_bigendian (f, byte, 1); |
| frag_var (rs_machine_dependent, 2, 2, |
| ENCODE_RELAX (STATE_INDEXED_PCREL, STATE_UNDF), |
| op->exp.X_add_symbol, |
| op->exp.X_add_number, f); |
| } |
| } |
| return 3; |
| } |
| |
| if (mode & (M6812_OP_REG | M6812_OP_D_IDX)) |
| { |
| if (mode & M6812_OP_D_IDX) |
| { |
| if (op->reg1 != REG_D) |
| as_bad (_("Expecting register D for indexed indirect mode.")); |
| if ((move_insn) && (!(current_architecture & cpu9s12x))) |
| as_bad (_("Indexed indirect mode is not allowed for movb/movw.")); |
| |
| byte = 0xE7; |
| } |
| else |
| { |
| switch (op->reg1) |
| { |
| case REG_A: |
| byte = 0xE4; |
| break; |
| |
| case REG_B: |
| byte = 0xE5; |
| break; |
| |
| default: |
| as_bad (_("Invalid accumulator register.")); |
| /* Fall through. */ |
| |
| case REG_D: |
| byte = 0xE6; |
| break; |
| } |
| } |
| switch (op->reg2) |
| { |
| case REG_X: |
| break; |
| |
| case REG_Y: |
| byte |= (1 << 3); |
| break; |
| |
| case REG_SP: |
| byte |= (2 << 3); |
| break; |
| |
| case REG_PC: |
| byte |= (3 << 3); |
| break; |
| |
| default: |
| as_bad (_("Invalid indexed register.")); |
| break; |
| } |
| f = frag_more (1); |
| number_to_chars_bigendian (f, byte, 1); |
| return 1; |
| } |
| |
| fprintf (stderr, "mode = 0x%x\nop->reg1 = 0x%x\nop->reg2 = 0x%x\n", |
| mode, op->reg1, op->reg2); |
| as_fatal (_("Addressing mode not implemented yet.")); |
| return 0; |
| } |
| |
| /* Assemble the 68HC12 register mode byte. */ |
| static int |
| build_reg_mode (operand *op, int format) |
| { |
| unsigned char byte; |
| char *f; |
| |
| if ((format & M6812_OP_SEX_MARKER) |
| && (op->reg1 != REG_A) && (op->reg1 != REG_B) && (op->reg1 != REG_CCR) |
| && (!(current_architecture & cpu9s12x))) |
| as_bad (_("Invalid source register for this instruction, use 'tfr'.")); |
| else if (op->reg1 == REG_NONE || op->reg1 == REG_PC) |
| as_bad (_("Invalid source register.")); |
| |
| if (format & M6812_OP_SEX_MARKER |
| && op->reg2 != REG_D |
| && op->reg2 != REG_X && op->reg2 != REG_Y && op->reg2 != REG_SP) |
| as_bad (_("Invalid destination register for this instruction, use 'tfr'.")); |
| else if (op->reg2 == REG_NONE || op->reg2 == REG_PC) |
| as_bad (_("Invalid destination register.")); |
| |
| byte = (op->reg1 << 4) | (op->reg2); |
| if (format & M6812_OP_EXG_MARKER) |
| byte |= 0x80; |
| |
| if ((format & M6812_OP_SEX_MARKER) |
| && (op->reg1 == REG_D) && (current_architecture & cpu9s12x)) |
| byte |= 0x08; |
| |
| f = frag_more (1); |
| number_to_chars_bigendian (f, byte, 1); |
| return 1; |
| } |
| |
| /* build_insn_xg takes a pointer to the opcode entry in the opcode table, |
| the array of operand expressions and builds the corresponding instruction. */ |
| |
| static void |
| build_insn_xg (struct m68hc11_opcode *opcode, |
| operand operands[], |
| int nb_operands ATTRIBUTE_UNUSED) |
| { |
| char *f; |
| long format; |
| |
| /* Put the page code instruction if there is one. */ |
| format = opcode->format; |
| |
| if (!(operands[0].mode & (M6811_OP_LOW_ADDR | M6811_OP_HIGH_ADDR))) |
| /* Need to retain those two modes, but clear for others. */ |
| operands[0].mode = 0; |
| |
| if (format & M68XG_OP_R_IMM8) |
| { |
| /* These opcodes are byte followed by imm8. */ |
| f = m68hc11_new_insn (1); |
| number_to_chars_bigendian (f, opcode->opcode >> 8, 1); |
| fixup8_xg (&operands[0].exp, format, operands[0].mode); |
| } |
| else if (format & M68XG_OP_R_IMM16) |
| { |
| fixS *fixp; |
| /* These opcodes expand into two imm8 instructions. |
| Emit as low:high as per the Freescale datasheet. |
| The linker requires them to be adjacent to handle the upper byte. */ |
| |
| /* Build low byte. */ |
| f = m68hc11_new_insn (1); |
| number_to_chars_bigendian (f, opcode->opcode >> 8, 1); |
| operands[0].mode = M6811_OP_LOW_ADDR; |
| f = frag_more (1); |
| fixp = fix_new_exp (frag_now, f - frag_now->fr_literal, 1, |
| &operands[0].exp, false, BFD_RELOC_M68HC12_LO8XG); |
| fixp->fx_no_overflow = 1; |
| number_to_chars_bigendian (f, 0, 1); |
| |
| /* Build high byte. */ |
| f = m68hc11_new_insn (1); |
| number_to_chars_bigendian (f, (opcode->opcode >> 8) | 0x08, 1); |
| operands[0].mode = M6811_OP_HIGH_ADDR; |
| f = frag_more (1); |
| fixp = fix_new_exp (frag_now, f - frag_now->fr_literal, 1, |
| &operands[0].exp, false, BFD_RELOC_M68HC12_HI8XG); |
| fixp->fx_no_overflow = 1; |
| number_to_chars_bigendian (f, 0, 1); |
| |
| } |
| else if (format & M68XG_OP_REL9) |
| { |
| f = m68hc11_new_insn (1); |
| number_to_chars_bigendian (f, opcode->opcode >> 8, 1); /* High byte. */ |
| fixup8_xg (&operands[0].exp, format, M68XG_OP_REL9); |
| } |
| else if (format & M68XG_OP_REL10) |
| { |
| f = m68hc11_new_insn (1); |
| number_to_chars_bigendian (f, opcode->opcode >> 8, 1); /* High byte. */ |
| fixup8_xg (&operands[0].exp, format, M68XG_OP_REL10); |
| } |
| else |
| { |
| f = m68hc11_new_insn (2); |
| number_to_chars_bigendian (f, opcode->opcode, 2); |
| } |
| return; |
| } |
| |
| /* build_insn takes a pointer to the opcode entry in the opcode table, |
| the array of operand expressions and builds the corresponding instruction. |
| This operation only deals with non relative jumps insn (need special |
| handling). */ |
| |
| static void |
| build_insn (struct m68hc11_opcode *opcode, |
| operand operands[], |
| int nb_operands ATTRIBUTE_UNUSED) |
| { |
| int i; |
| char *f; |
| long format; |
| int move_insn = 0; |
| |
| /* Put the page code instruction if there is one. */ |
| format = opcode->format; |
| |
| if (format & M6811_OP_BRANCH) |
| fix_new (frag_now, frag_now_fix (), 0, |
| &abs_symbol, 0, 1, BFD_RELOC_M68HC11_RL_JUMP); |
| |
| if (format & OP_EXTENDED) |
| { |
| int page_code; |
| |
| f = m68hc11_new_insn (2); |
| if (format & M6811_OP_PAGE2) |
| page_code = M6811_OPCODE_PAGE2; |
| else if (format & M6811_OP_PAGE3) |
| page_code = M6811_OPCODE_PAGE3; |
| else |
| page_code = M6811_OPCODE_PAGE4; |
| |
| number_to_chars_bigendian (f, page_code, 1); |
| f++; |
| } |
| else |
| f = m68hc11_new_insn (1); |
| |
| number_to_chars_bigendian (f, opcode->opcode, 1); |
| |
| i = 0; |
| |
| /* The 68HC12 movb and movw instructions are special. We have to handle |
| them in a special way. */ |
| if (format & (M6812_OP_IND16_P2 | M6812_OP_IDX_P2)) |
| { |
| move_insn = 1; |
| if (format & M6812_OP_IDX) |
| { |
| build_indexed_byte (&operands[0], format, 1); |
| i = 1; |
| format &= ~M6812_OP_IDX; |
| } |
| if (format & M6812_OP_IDX_P2) |
| { |
| build_indexed_byte (&operands[1], format, 1); |
| i = 0; |
| format &= ~M6812_OP_IDX_P2; |
| } |
| } |
| |
| if (format & (M6811_OP_DIRECT | M6811_OP_IMM8)) |
| { |
| fixup8 (&operands[i].exp, |
| format & (M6811_OP_DIRECT | M6811_OP_IMM8 | M6812_OP_TRAP_ID), |
| operands[i].mode); |
| i++; |
| } |
| else if (IS_CALL_SYMBOL (format) && nb_operands == 1) |
| { |
| format &= ~M6812_OP_PAGE; |
| fixup24 (&operands[i].exp, format & M6811_OP_IND16, |
| operands[i].mode); |
| i++; |
| } |
| else if (format & (M6811_OP_IMM16 | M6811_OP_IND16)) |
| { |
| fixup16 (&operands[i].exp, |
| format & (M6811_OP_IMM16 | M6811_OP_IND16 | M6812_OP_PAGE), |
| operands[i].mode); |
| i++; |
| } |
| else if (format & (M6811_OP_IX | M6811_OP_IY)) |
| { |
| if ((format & M6811_OP_IX) && (operands[0].reg1 != REG_X)) |
| as_bad (_("Invalid indexed register, expecting register X.")); |
| if ((format & M6811_OP_IY) && (operands[0].reg1 != REG_Y)) |
| as_bad (_("Invalid indexed register, expecting register Y.")); |
| |
| fixup8 (&operands[0].exp, M6811_OP_IX, operands[0].mode); |
| i = 1; |
| } |
| else if (format & |
| (M6812_OP_IDX | M6812_OP_IDX_2 | M6812_OP_IDX_1 |
| | M6812_OP_D_IDX | M6812_OP_D_IDX_2)) |
| { |
| build_indexed_byte (&operands[i], format, move_insn); |
| i++; |
| } |
| else if (format & M6812_OP_REG && current_architecture & cpu6812) |
| { |
| build_reg_mode (&operands[i], format); |
| i++; |
| } |
| if (format & M6811_OP_BITMASK) |
| { |
| fixup8 (&operands[i].exp, M6811_OP_BITMASK, operands[i].mode); |
| i++; |
| } |
| if (format & M6811_OP_JUMP_REL) |
| { |
| fixup8 (&operands[i].exp, M6811_OP_JUMP_REL, operands[i].mode); |
| } |
| else if (format & M6812_OP_IND16_P2) |
| { |
| fixup16 (&operands[1].exp, M6811_OP_IND16, operands[1].mode); |
| } |
| if (format & M6812_OP_PAGE) |
| { |
| fixup8 (&operands[i].exp, M6812_OP_PAGE, operands[i].mode); |
| } |
| } |
| |
| /* Opcode identification and operand analysis. */ |
| |
| /* find() gets a pointer to an entry in the opcode table. It must look at all |
| opcodes with the same name and use the operands to choose the correct |
| opcode. Returns the opcode pointer if there was a match and 0 if none. */ |
| static struct m68hc11_opcode * |
| find (struct m68hc11_opcode_def *opc, operand operands[], int nb_operands) |
| { |
| int i, match, pos; |
| struct m68hc11_opcode *opcode; |
| struct m68hc11_opcode *op_indirect; |
| |
| op_indirect = 0; |
| opcode = opc->opcode; |
| |
| /* Now search the opcode table table for one with operands |
| that matches what we've got. */ |
| |
| if (current_architecture & cpuxgate) |
| { |
| /* Many XGATE insns are simple enough that we get an exact match. */ |
| for (pos = match = 0; match == 0 && pos < opc->nb_modes; pos++, opcode++) |
| if (opcode->format == operands[nb_operands-1].mode) |
| return opcode; |
| |
| return 0; |
| } |
| |
| /* Non XGATE */ |
| |
| /* Now search the opcode table table for one with operands |
| that matches what we've got. We're only done if the operands matched so |
| far AND there are no more to check. */ |
| for (pos = match = 0; match == 0 && pos < opc->nb_modes; pos++, opcode++) |
| { |
| int poss_indirect = 0; |
| long format = opcode->format; |
| int expect; |
| |
| expect = 0; |
| if (opcode->format & M6811_OP_MASK) |
| expect++; |
| if (opcode->format & M6811_OP_BITMASK) |
| expect++; |
| if (opcode->format & (M6811_OP_JUMP_REL | M6812_OP_JUMP_REL16)) |
| expect++; |
| if (opcode->format & (M6812_OP_IND16_P2 | M6812_OP_IDX_P2)) |
| expect++; |
| if ((opcode->format & M6812_OP_PAGE) |
| && (!IS_CALL_SYMBOL (opcode->format) || nb_operands == 2)) |
| expect++; |
| |
| for (i = 0; expect == nb_operands && i < nb_operands; i++) |
| { |
| int mode = operands[i].mode; |
| |
| if (mode & M6811_OP_IMM16) |
| { |
| if (format & |
| (M6811_OP_IMM8 | M6811_OP_IMM16 | M6811_OP_BITMASK)) |
| continue; |
| break; |
| } |
| if (mode == M6811_OP_DIRECT) |
| { |
| if (format & M6811_OP_DIRECT) |
| continue; |
| |
| /* If the operand is a page 0 operand, remember a |
| possible <abs-16> addressing mode. We mark |
| this and continue to check other operands. */ |
| if (format & M6811_OP_IND16 |
| && flag_strict_direct_addressing && op_indirect == 0) |
| { |
| poss_indirect = 1; |
| continue; |
| } |
| break; |
| } |
| if (mode & M6811_OP_IND16) |
| { |
| if (i == 0 && (format & M6811_OP_IND16) != 0) |
| continue; |
| if (i != 0 && (format & M6812_OP_PAGE) != 0) |
| continue; |
| if (i != 0 && (format & M6812_OP_IND16_P2) != 0) |
| continue; |
| if (i == 0 && (format & M6811_OP_BITMASK)) |
| break; |
| } |
| if (mode & (M6811_OP_JUMP_REL | M6812_OP_JUMP_REL16)) |
| { |
| if (format & (M6811_OP_JUMP_REL | M6812_OP_JUMP_REL16)) |
| continue; |
| } |
| if (mode & M6812_OP_REG) |
| { |
| if (i == 0 |
| && (format & M6812_OP_REG) |
| && (operands[i].reg2 == REG_NONE)) |
| continue; |
| if (i == 0 |
| && (format & M6812_OP_REG) |
| && (format & M6812_OP_REG_2) |
| && (operands[i].reg2 != REG_NONE)) |
| continue; |
| if (i == 0 |
| && (format & M6812_OP_IDX) |
| && (operands[i].reg2 != REG_NONE)) |
| continue; |
| if (i == 0 |
| && (format & M6812_OP_IDX) |
| && (format & (M6812_OP_IND16_P2 | M6812_OP_IDX_P2))) |
| continue; |
| if (i == 1 |
| && (format & M6812_OP_IDX_P2)) |
| continue; |
| break; |
| } |
| if (mode & M6812_OP_IDX) |
| { |
| if (format & M6811_OP_IX && operands[i].reg1 == REG_X) |
| continue; |
| if (format & M6811_OP_IY && operands[i].reg1 == REG_Y) |
| continue; |
| if (i == 0 |
| && format & (M6812_OP_IDX | M6812_OP_IDX_1 | M6812_OP_IDX_2) |
| && (operands[i].reg1 == REG_X |
| || operands[i].reg1 == REG_Y |
| || operands[i].reg1 == REG_SP |
| || operands[i].reg1 == REG_PC)) |
| continue; |
| if (i == 1 && (format & M6812_OP_IDX_P2)) |
| continue; |
| } |
| if (mode & format & (M6812_OP_D_IDX | M6812_OP_D_IDX_2)) |
| { |
| if (i == 0) |
| continue; |
| } |
| if (mode & M6812_AUTO_INC_DEC) |
| { |
| if (i == 0 |
| && format & (M6812_OP_IDX | M6812_OP_IDX_1 | |
| M6812_OP_IDX_2)) |
| continue; |
| if (i == 1 && format & M6812_OP_IDX_P2) |
| continue; |
| } |
| break; |
| } |
| match = i == nb_operands; |
| |
| /* Operands are ok but an operand uses page 0 addressing mode |
| while the insn supports abs-16 mode. Keep a reference to this |
| insns in case there is no insn supporting page 0 addressing. */ |
| if (match && poss_indirect) |
| { |
| op_indirect = opcode; |
| match = 0; |
| } |
| if (match) |
| break; |
| } |
| |
| /* Page 0 addressing is used but not supported by any insn. |
| If absolute addresses are supported, we use that insn. */ |
| if (match == 0 && op_indirect) |
| { |
| opcode = op_indirect; |
| match = 1; |
| } |
| |
| return match ? opcode : 0; |
| } |
| |
| /* Find the real opcode and its associated operands. We use a progressive |
| approach here. On entry, 'opc' points to the first opcode in the |
| table that matches the opcode name in the source line. We try to |
| isolate an operand, find a possible match in the opcode table. |
| We isolate another operand if no match were found. The table 'operands' |
| is filled while operands are recognized. |
| |
| Returns the opcode pointer that matches the opcode name in the |
| source line and the associated operands. */ |
| static struct m68hc11_opcode * |
| find_opcode (struct m68hc11_opcode_def *opc, operand operands[], |
| int *nb_operands) |
| { |
| struct m68hc11_opcode *opcode; |
| int i; |
| |
| if (opc->max_operands == 0) |
| { |
| *nb_operands = 0; |
| return opc->opcode; |
| } |
| |
| for (i = 0; i < opc->max_operands;) |
| { |
| int result; |
| |
| result = get_operand (&operands[i], i, opc->format); |
| if (result <= 0) |
| return 0; |
| |
| /* Special case where the bitmask of the bclr/brclr |
| instructions is not introduced by #. |
| Example: bclr 3,x $80. */ |
| if (i == 1 && (opc->format & M6811_OP_BITMASK) |
| && (operands[i].mode & M6811_OP_IND16)) |
| { |
| operands[i].mode = M6811_OP_IMM16; |
| } |
| |
| i += result; |
| *nb_operands = i; |
| if (i >= opc->min_operands) |
| { |
| opcode = find (opc, operands, i); |
| |
| /* Another special case for 'call foo,page' instructions. |
| Since we support 'call foo' and 'call foo,page' we must look |
| if the optional page specification is present otherwise we will |
| assemble immediately and treat the page spec as garbage. */ |
| if (opcode && !(opcode->format & M6812_OP_PAGE)) |
| return opcode; |
| |
| if (opcode && *input_line_pointer != ',') |
| return opcode; |
| } |
| |
| if (*input_line_pointer == ',') |
| input_line_pointer++; |
| } |
| |
| return 0; |
| } |
| |
| #define M6812_XBCC_MARKER (M6812_OP_TBCC_MARKER \ |
| | M6812_OP_DBCC_MARKER \ |
| | M6812_OP_IBCC_MARKER) |
| |
| /* Gas line assembler entry point. */ |
| |
| /* This is the main entry point for the machine-dependent assembler. str |
| points to a machine-dependent instruction. This function is supposed to |
| emit the frags/bytes it assembles to. */ |
| void |
| md_assemble (char *str) |
| { |
| struct m68hc11_opcode_def *opc; |
| struct m68hc11_opcode *opcode; |
| |
| struct m68hc11_opcode opcode_local; |
| unsigned char *op_start, *op_end; |
| char *save; |
| char name[20]; |
| int nlen = 0; |
| operand operands[M6811_MAX_OPERANDS]; |
| int nb_operands = 0; |
| int branch_optimize = 0; |
| int alias_id = -1; |
| |
| /* Drop leading whitespace. */ |
| while (*str == ' ') |
| str++; |
| |
| /* Find the opcode end and get the opcode in 'name'. The opcode is forced |
| lower case (the opcode table only has lower case op-codes). */ |
| for (op_start = op_end = (unsigned char *) str; |
| *op_end && !is_end_of_line[*op_end] && *op_end != ' '; |
| op_end++) |
| { |
| name[nlen] = TOLOWER (op_start[nlen]); |
| nlen++; |
| if (nlen == sizeof (name) - 1) |
| break; |
| } |
| name[nlen] = 0; |
| |
| if (nlen == 0) |
| { |
| as_bad (_("No instruction or missing opcode.")); |
| return; |
| } |
| |
| if (current_architecture == cpuxgate) |
| { |
| /* Find the opcode definition given its name. */ |
| opc = (struct m68hc11_opcode_def *) str_hash_find (m68hc11_hash, name); |
| if (opc == NULL) |
| { |
| as_bad (_("Opcode `%s' is not recognized."), name); |
| return; |
| } |
| |
| /* Grab a local copy. */ |
| opcode_local.name = opc->opcode->name; |
| /* These will be incomplete where multiple variants exist. */ |
| opcode_local.opcode = opc->opcode->opcode; |
| opcode_local.format = opc->opcode->format; |
| |
| save = input_line_pointer; |
| input_line_pointer = (char *) op_end; |
| |
| if (opc->format == M68XG_OP_NONE) |
| { |
| /* No special handling required. */ |
| opcode_local.format = M68XG_OP_NONE; |
| build_insn_xg (opc->opcode, operands, 0); |
| return; |
| } |
| |
| /* Special handling of TFR. */ |
| if (startswith (opc->opcode->name, "tfr")) |
| { |
| /* There must be two operands with a comma. */ |
| input_line_pointer = skip_whites (input_line_pointer); |
| operands[0].reg1 = register_name (); |
| if (operands[0].reg1 == REG_NONE) |
| { |
| as_bad ("Invalid register\n"); |
| return; |
| } |
| input_line_pointer = skip_whites (input_line_pointer); |
| if (*input_line_pointer != ',') |
| { |
| as_bad ("Missing comma.\n"); |
| return; |
| } |
| input_line_pointer++; |
| input_line_pointer = skip_whites (input_line_pointer); |
| operands[1].reg1 = register_name (); |
| if (operands[1].reg1 == REG_NONE) |
| { |
| as_bad ("Invalid register\n"); |
| return; |
| } |
| input_line_pointer = skip_whites (input_line_pointer); |
| if (*input_line_pointer != '\n' && *input_line_pointer) |
| { |
| as_bad (_("Garbage at end of instruction: `%s'."), |
| input_line_pointer); |
| return; |
| } |
| if (operands[1].reg1 == REG_CCR) /* ,CCR */ |
| opc->opcode->opcode = 0x00f8 | ( operands[0].reg1 << 8); |
| else if (operands[0].reg1 == REG_CCR) /* CCR, */ |
| opc->opcode->opcode = 0x00f9 | ( operands[1].reg1 << 8); |
| else if (operands[1].reg1 == REG_PC) /* ,PC */ |
| opc->opcode->opcode = 0x00fa | ( operands[0].reg1 << 8); |
| else |
| { |
| as_bad ("Invalid operand to TFR\n"); |
| return; |
| } |
| /* no special handling required */ |
| opcode_local.format = M68XG_OP_NONE; |
| opcode_local.opcode = opc->opcode->opcode; |
| build_insn_xg (&opcode_local, operands, 0); |
| return; |
| } |
| |
| /* CSEM, SSEM */ |
| if (opc->format & M68XG_OP_IMM3) |
| { |
| /* Either IMM3 or R */ |
| input_line_pointer = skip_whites (input_line_pointer); |
| if ((*input_line_pointer == 'R') || (*input_line_pointer == 'r')) |
| { |
| operands[0].reg1 = register_name (); |
| if (operands[0].reg1 == REG_NONE) |
| { |
| as_bad ("Invalid register\n"); |
| return; |
| } |
| operands[0].mode = M68XG_OP_R; |
| /* One opcode has multiple modes, so find right one. */ |
| opcode = find (opc, operands, 1); |
| if (opcode) |
| { |
| opcode_local.opcode = opcode->opcode |
| | (operands[0].reg1 << 8); |
| opcode_local.format = M68XG_OP_NONE; |
| build_insn_xg (&opcode_local, operands, 1); |
| } |
| else |
| as_bad ("No opcode found\n"); |
| |
| return; |
| } |
| else |
| { |
| if (*input_line_pointer == '#') |
| input_line_pointer++; |
| |
| expression (&operands[0].exp); |
| if (operands[0].exp.X_op == O_illegal) |
| { |
| as_bad (_("Illegal operand.")); |
| return; |
| } |
| else if (operands[0].exp.X_op == O_absent) |
| { |
| as_bad (_("Missing operand.")); |
| return; |
| } |
| |
| if (check_range (operands[0].exp.X_add_number,M68XG_OP_IMM3)) |
| { |
| opcode_local.opcode |= (operands[0].exp.X_add_number); |
| operands[0].mode = M68XG_OP_IMM3; |
| |
| opcode = find (opc, operands, 1); |
| if (opcode) |
| { |
| opcode_local.opcode = opcode->opcode; |
| opcode_local.opcode |
| |= (operands[0].exp.X_add_number) << 8; |
| opcode_local.format = M68XG_OP_NONE; |
| build_insn_xg (&opcode_local, operands, 1); |
| } |
| else |
| as_bad ("No opcode found\n"); |
| |
| return; |
| } |
| else |
| { |
| as_bad ("Number out of range for IMM3\n"); |
| return; |
| } |
| } |
| } |
| |
| /* Special handling of SIF. */ |
| if (startswith (opc->opcode->name, "sif")) |
| { |
| /* Either OP_NONE or OP_RS. */ |
| if (*input_line_pointer != '\n') |
| input_line_pointer = skip_whites (input_line_pointer); |
| |
| if ((*input_line_pointer == '\n') || (*input_line_pointer == '\r') |
| || (*input_line_pointer == '\0')) |
| opc->opcode->opcode = 0x0300; |
| else |
| { |
| operands[0].reg1 = register_name (); |
| if (operands[0].reg1 == REG_NONE) |
| { |
| as_bad ("Invalid register\n"); |
| return; |
| } |
| opcode_local.opcode = 0x00f7 | (operands[0].reg1 << 8); |
| } |
| opcode_local.format = M68XG_OP_NONE; |
| build_insn_xg (&opcode_local, operands, 0); |
| return; |
| } |
| |
| /* SEX, PAR, JAL plus aliases NEG, TST, COM */ |
| if (opc->format & M68XG_OP_R) |
| { |
| input_line_pointer = skip_whites (input_line_pointer); |
| operands[0].reg1 = register_name (); |
|