/* tc-m68hc11.c -- Assembler code for the Motorola 68HC11 & 68HC12. Copyright (C) 1999, 2000 Free Software Foundation. Written by Stephane Carrez (stcarrez@worldnet.fr) 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 2, 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "as.h" #include "subsegs.h" #include "opcode/m68hc11.h" #include "dwarf2dbg.h" struct dwarf2_line_info debug_line; 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_XBCC_BRANCH (4) #define STATE_CONDITIONAL_BRANCH_6812 (5) #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 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 . 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 and bra . 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, 1, 0}, {1, 1, 0, 0}, /* Relax for dbeq/ibeq/tbeq r,: 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 on 68HC12. These insns are translated into lbcc . */ {(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 } 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} }; /* local functions */ static register_id reg_name_search PARAMS ((char *name)); static register_id register_name PARAMS (()); static int check_range PARAMS ((long num, int mode)); static void print_opcode_list PARAMS ((void)); static void get_default_target PARAMS ((void)); static void print_insn_format PARAMS ((char *name)); static int get_operand PARAMS ((operand * op, int first, long opmode)); static void fixup8 PARAMS ((expressionS * oper, int mode, int opmode)); static void fixup16 PARAMS ((expressionS * oper, int mode, int opmode)); static struct m68hc11_opcode *find_opcode PARAMS ( (struct m68hc11_opcode_def * opc, operand operands[], int *nb_operands)); static void build_jump_insn PARAMS ( (struct m68hc11_opcode * opcode, operand operands[], int nb_operands, int optimize)); static void build_insn PARAMS ((struct m68hc11_opcode * opcode, operand operands[], int nb_operands)); /* 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 forbidds this. */ static short flag_fixed_branchs = 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 struct hash_control *m68hc11_hash; /* Current cpu (either cpu6811 or cpu6812). This is determined automagically by 'get_default_target' by looking at default BFD vector. This is overriden with the -m 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; /* 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}, {"fcc", stringer, 1}, {"rmb", s_space, 0}, {"file", dwarf2_directive_file, 0}, {"loc", dwarf2_directive_loc, 0}, {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-branchs", no_argument, NULL, OPTION_FORCE_LONG_BRANCH}, #define OPTION_SHORT_BRANCHS (OPTION_MD_BASE + 1) {"short-branchs", no_argument, NULL, OPTION_SHORT_BRANCHS}, #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}, {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 () { get_default_target (); if (current_architecture & cpu6811) return "elf32-m68hc11"; else return "elf32-m68hc12"; } enum bfd_architecture m68hc11_arch () { get_default_target (); if (current_architecture & cpu6811) return bfd_arch_m68hc11; else return bfd_arch_m68hc12; } int m68hc11_mach () { return 0; } void md_show_usage (stream) FILE *stream; { get_default_target (); fprintf (stream, _("\ Motorola 68HC11/68HC12 options:\n\ -m68hc11 | -m68hc12 specify the processor [default %s]\n\ --force-long-branchs always turn relative branchs into absolute ones\n\ -S,--short-branchs do not turn relative branchs 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\ --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 () { 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 *file; { int i; struct m68hc11_opcode_def *opc; hash_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 (c, arg) int c; char *arg; { get_default_target (); switch (c) { /* -S means keep external to 2 bits offset rather than 16 bits one. */ case OPTION_SHORT_BRANCHS: case 'S': flag_fixed_branchs = 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 'm': if (strcasecmp (arg, "68hc11") == 0) current_architecture = cpu6811; else if (strcasecmp (arg, "68hc12") == 0) current_architecture = cpu6812; else as_bad (_("Option `%s' is not recognized."), arg); break; default: return 0; } return 1; } symbolS * md_undefined_symbol (name) char *name ATTRIBUTE_UNUSED; { return 0; } /* Equal to MAX_PRECISION in atof-ieee.c */ #define MAX_LITTLENUMS 6 /* Turn a string in input_line_pointer into a floating point constant of type TYPE, and store the appropriate bytes in *LITP. The number of LITTLENUMS emitted is stored in *SIZEP. An error message is returned, or NULL on OK. */ char * md_atof (type, litP, sizeP) char type; char *litP; int *sizeP; { int prec; LITTLENUM_TYPE words[MAX_LITTLENUMS]; LITTLENUM_TYPE *wordP; char *t; switch (type) { case 'f': case 'F': case 's': case 'S': prec = 2; break; case 'd': case 'D': case 'r': case 'R': prec = 4; break; case 'x': case 'X': prec = 6; break; case 'p': case 'P': prec = 6; break; default: *sizeP = 0; return _("Bad call to MD_ATOF()"); } t = atof_ieee (input_line_pointer, type, words); if (t) input_line_pointer = t; *sizeP = prec * sizeof (LITTLENUM_TYPE); for (wordP = words; prec--;) { md_number_to_chars (litP, (long) (*wordP++), sizeof (LITTLENUM_TYPE)); litP += sizeof (LITTLENUM_TYPE); } return 0; } valueT md_section_align (seg, addr) asection *seg; valueT addr; { int align = bfd_get_section_alignment (stdoutput, seg); return ((addr + (1 << align) - 1) & (-1 << align)); } static int cmp_opcode (op1, op2) struct m68hc11_opcode *op1; struct m68hc11_opcode *op2; { return strcmp (op1->name, op2->name); } /* Initialize the assembler. Create the opcode hash table (sorted on the names) with the M6811 opcode table (from opcode library). */ void md_begin () { char *prev_name = ""; struct m68hc11_opcode *opcodes; struct m68hc11_opcode_def *opc = 0; int i, j; get_default_target (); m68hc11_hash = hash_new (); /* Get a writable copy of the opcode table and sort it on the names. */ opcodes = (struct m68hc11_opcode *) xmalloc (m68hc11_num_opcodes * sizeof (struct m68hc11_opcode)); 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), cmp_opcode); opc = (struct m68hc11_opcode_def *) xmalloc (num_opcodes * sizeof (struct m68hc11_opcode_def)); 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; hash_insert (m68hc11_hash, opcodes->name, (char *) opc); } opc->nb_modes++; opc->format |= opcodes->format; /* See how many operands this opcode needs. */ expect = 0; 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++; if (expect < opc->min_operands) opc->min_operands = 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 () { } /* 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 (opcode, example) struct m68hc11_opcode *opcode; int example; { static char buf[128]; int format = opcode->format; char *p; p = buf; buf[0] = 0; if (format & M6811_OP_IMM8) { if (example) sprintf (p, "#%d", rand () & 0x0FF); else strcpy (p, _("#")); p = &p[strlen (p)]; } if (format & M6811_OP_IMM16) { if (example) sprintf (p, "#%d", rand () & 0x0FFFF); else strcpy (p, _("#")); p = &p[strlen (p)]; } if (format & M6811_OP_IX) { if (example) sprintf (p, "%d,X", rand () & 0x0FF); else strcpy (p, _(",X")); p = &p[strlen (p)]; } if (format & M6811_OP_IY) { if (example) sprintf (p, "%d,X", rand () & 0x0FF); else strcpy (p, _(",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 & M6811_OP_DIRECT) { if (example) sprintf (p, "*Z%d", rand () & 0x0FF); else strcpy (p, _("*")); p = &p[strlen (p)]; } if (format & M6811_OP_BITMASK) { if (buf[0]) *p++ = ' '; if (example) sprintf (p, "#$%02x", rand () & 0x0FF); else strcpy (p, _("#")); 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, _("")); 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, _("