| /* tc-z8k.c -- Assemble code for the Zilog Z800n |
| Copyright (C) 1992-2021 Free Software Foundation, Inc. |
| |
| 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. */ |
| |
| /* Written By Steve Chamberlain <sac@cygnus.com>. */ |
| |
| #include "as.h" |
| #include "safe-ctype.h" |
| #define DEFINE_TABLE |
| #include "opcodes/z8k-opc.h" |
| |
| const char comment_chars[] = "!"; |
| const char line_comment_chars[] = "#"; |
| const char line_separator_chars[] = ";"; |
| |
| extern int machine; |
| extern int coff_flags; |
| int segmented_mode; |
| |
| /* This is non-zero if target was set from the command line. |
| If non-zero, 1 means Z8002 (non-segmented), 2 means Z8001 (segmented). */ |
| static int z8k_target_from_cmdline; |
| |
| static void |
| s_segm (int segm) |
| { |
| if (segm) |
| { |
| segmented_mode = 1; |
| bfd_set_arch_mach (stdoutput, TARGET_ARCH, bfd_mach_z8001); |
| } |
| else |
| { |
| segmented_mode = 0; |
| bfd_set_arch_mach (stdoutput, TARGET_ARCH, bfd_mach_z8002); |
| } |
| } |
| |
| static void |
| even (int ignore ATTRIBUTE_UNUSED) |
| { |
| frag_align (1, 0, 0); |
| record_alignment (now_seg, 1); |
| } |
| |
| static int |
| tohex (int c) |
| { |
| if (ISDIGIT (c)) |
| return c - '0'; |
| if (ISLOWER (c)) |
| return c - 'a' + 10; |
| return c - 'A' + 10; |
| } |
| |
| static void |
| sval (int ignore ATTRIBUTE_UNUSED) |
| { |
| SKIP_WHITESPACE (); |
| if (*input_line_pointer == '\'') |
| { |
| int c; |
| input_line_pointer++; |
| c = *input_line_pointer++; |
| while (c != '\'') |
| { |
| if (c == '%') |
| { |
| c = (tohex (input_line_pointer[0]) << 4) |
| | tohex (input_line_pointer[1]); |
| input_line_pointer += 2; |
| } |
| FRAG_APPEND_1_CHAR (c); |
| c = *input_line_pointer++; |
| } |
| demand_empty_rest_of_line (); |
| } |
| } |
| |
| /* This table describes all the machine specific pseudo-ops the assembler |
| has to support. The fields are: |
| pseudo-op name without dot |
| function to call to execute this pseudo-op |
| Integer arg to pass to the function |
| */ |
| |
| const pseudo_typeS md_pseudo_table[] = { |
| {"int" , cons , 2}, |
| {"data.b" , cons , 1}, |
| {"data.w" , cons , 2}, |
| {"data.l" , cons , 4}, |
| {"form" , listing_psize , 0}, |
| {"heading", listing_title , 0}, |
| {"import" , s_ignore , 0}, |
| {"page" , listing_eject , 0}, |
| {"program", s_ignore , 0}, |
| {"z8001" , s_segm , 1}, |
| {"z8002" , s_segm , 0}, |
| |
| {"segm" , s_segm , 1}, |
| {"unsegm" , s_segm , 0}, |
| {"unseg" , s_segm , 0}, |
| {"name" , s_app_file , 0}, |
| {"global" , s_globl , 0}, |
| {"wval" , cons , 2}, |
| {"lval" , cons , 4}, |
| {"bval" , cons , 1}, |
| {"sval" , sval , 0}, |
| {"rsect" , obj_coff_section, 0}, |
| {"sect" , obj_coff_section, 0}, |
| {"block" , s_space , 0}, |
| {"even" , even , 0}, |
| {0 , 0 , 0} |
| }; |
| |
| const char EXP_CHARS[] = "eE"; |
| |
| /* Chars that mean this number is a floating point constant. |
| As in 0f12.456 |
| or 0d1.2345e12 */ |
| const char FLT_CHARS[] = "rRsSfFdDxXpP"; |
| |
| /* Opcode mnemonics. */ |
| static htab_t opcode_hash_control; |
| |
| void |
| md_begin (void) |
| { |
| const opcode_entry_type *opcode; |
| unsigned int idx = -1u; |
| |
| opcode_hash_control = str_htab_create (); |
| |
| for (opcode = z8k_table; opcode->name; opcode++) |
| { |
| /* Only enter unique codes into the table. */ |
| if (idx != opcode->idx) |
| str_hash_insert (opcode_hash_control, opcode->name, opcode, 0); |
| idx = opcode->idx; |
| } |
| |
| /* Default to z8002. */ |
| s_segm (z8k_target_from_cmdline ? z8k_target_from_cmdline - 1 : 0); |
| |
| /* Insert the pseudo ops, too. */ |
| for (idx = 0; md_pseudo_table[idx].poc_name; idx++) |
| { |
| opcode_entry_type *fake_opcode; |
| fake_opcode = XNEW (opcode_entry_type); |
| fake_opcode->name = md_pseudo_table[idx].poc_name; |
| fake_opcode->func = (void *) (md_pseudo_table + idx); |
| fake_opcode->opcode = 250; |
| str_hash_insert (opcode_hash_control, fake_opcode->name, fake_opcode, 0); |
| } |
| } |
| |
| typedef struct z8k_op { |
| /* CLASS_REG_xxx. */ |
| int regsize; |
| |
| /* 0 .. 15. */ |
| unsigned int reg; |
| |
| int mode; |
| |
| /* Any other register associated with the mode. */ |
| unsigned int x_reg; |
| |
| /* Any expression. */ |
| expressionS exp; |
| } op_type; |
| |
| static expressionS *da_operand; |
| static expressionS *imm_operand; |
| |
| static int reg[16]; |
| static int the_cc; |
| static int the_ctrl; |
| static int the_flags; |
| static int the_interrupt; |
| |
| /* Determine register number. src points to the ascii number |
| (after "rl", "rh", "r", "rr", or "rq"). If a character |
| outside the set of {0,',',')','('} follows the number, |
| return NULL to indicate that it's not a valid register |
| number. */ |
| |
| static char * |
| whatreg (unsigned int *preg, char *src) |
| { |
| unsigned int new_reg; |
| |
| /* src[0] is already known to be a digit. */ |
| if (ISDIGIT (src[1])) |
| { |
| new_reg = (src[0] - '0') * 10 + src[1] - '0'; |
| src += 2; |
| } |
| else |
| { |
| new_reg = (src[0] - '0'); |
| src += 1; |
| } |
| |
| if (src[0] != 0 && src[0] != ',' && src[0] != '(' && src[0] != ')') |
| return NULL; |
| |
| *preg = new_reg; |
| return src; |
| } |
| |
| /* Parse operands |
| |
| rh0-rh7, rl0-rl7 |
| r0-r15 |
| rr0-rr14 |
| rq0--rq12 |
| WREG r0,r1,r2,r3,r4,r5,r6,r7,fp,sp |
| r0l,r0h,..r7l,r7h |
| @WREG |
| @WREG+ |
| @-WREG |
| #const |
| */ |
| |
| /* Try to parse a reg name. Return a pointer to the first character |
| in SRC after the reg name. */ |
| |
| static char * |
| parse_reg (char *src, int *mode, unsigned int *preg) |
| { |
| char *res = NULL; |
| char regno; |
| |
| /* Check for stack pointer "sp" alias. */ |
| if ((src[0] == 's' || src[0] == 'S') |
| && (src[1] == 'p' || src[1] == 'P') |
| && (src[2] == 0 || src[2] == ',')) |
| { |
| if (segmented_mode) |
| { |
| *mode = CLASS_REG_LONG; |
| *preg = 14; |
| } |
| else |
| { |
| *mode = CLASS_REG_WORD; |
| *preg = 15; |
| } |
| return src + 2; |
| } |
| |
| if (src[0] == 'r' || src[0] == 'R') |
| { |
| if (src[1] == 'r' || src[1] == 'R') |
| { |
| if (src[2] < '0' || src[2] > '9') |
| return NULL; /* Assume no register name but a label starting with 'rr'. */ |
| *mode = CLASS_REG_LONG; |
| res = whatreg (preg, src + 2); |
| if (res == NULL) |
| return NULL; /* Not a valid register name. */ |
| regno = *preg; |
| if (regno > 14) |
| as_bad (_("register rr%d out of range"), regno); |
| if (regno & 1) |
| as_bad (_("register rr%d does not exist"), regno); |
| } |
| else if (src[1] == 'h' || src[1] == 'H') |
| { |
| if (src[2] < '0' || src[2] > '9') |
| return NULL; /* Assume no register name but a label starting with 'rh'. */ |
| *mode = CLASS_REG_BYTE; |
| res = whatreg (preg, src + 2); |
| if (res == NULL) |
| return NULL; /* Not a valid register name. */ |
| regno = *preg; |
| if (regno > 7) |
| as_bad (_("register rh%d out of range"), regno); |
| } |
| else if (src[1] == 'l' || src[1] == 'L') |
| { |
| if (src[2] < '0' || src[2] > '9') |
| return NULL; /* Assume no register name but a label starting with 'rl'. */ |
| *mode = CLASS_REG_BYTE; |
| res = whatreg (preg, src + 2); |
| if (res == NULL) |
| return NULL; /* Not a valid register name. */ |
| regno = *preg; |
| if (regno > 7) |
| as_bad (_("register rl%d out of range"), regno); |
| *preg += 8; |
| } |
| else if (src[1] == 'q' || src[1] == 'Q') |
| { |
| if (src[2] < '0' || src[2] > '9') |
| return NULL; /* Assume no register name but a label starting with 'rq'. */ |
| *mode = CLASS_REG_QUAD; |
| res = whatreg (preg, src + 2); |
| if (res == NULL) |
| return NULL; /* Not a valid register name. */ |
| regno = *preg; |
| if (regno > 12) |
| as_bad (_("register rq%d out of range"), regno); |
| if (regno & 3) |
| as_bad (_("register rq%d does not exist"), regno); |
| } |
| else |
| { |
| if (src[1] < '0' || src[1] > '9') |
| return NULL; /* Assume no register name but a label starting with 'r'. */ |
| *mode = CLASS_REG_WORD; |
| res = whatreg (preg, src + 1); |
| if (res == NULL) |
| return NULL; /* Not a valid register name. */ |
| regno = *preg; |
| if (regno > 15) |
| as_bad (_("register r%d out of range"), regno); |
| } |
| } |
| return res; |
| } |
| |
| static char * |
| parse_exp (char *s, expressionS *op) |
| { |
| char *save = input_line_pointer; |
| char *new_pointer; |
| |
| input_line_pointer = s; |
| expression (op); |
| if (op->X_op == O_absent) |
| as_bad (_("missing operand")); |
| new_pointer = input_line_pointer; |
| input_line_pointer = save; |
| return new_pointer; |
| } |
| |
| /* The many forms of operand: |
| |
| <rb> |
| <r> |
| <rr> |
| <rq> |
| @r |
| #exp |
| exp |
| exp(r) |
| r(#exp) |
| r(r) |
| */ |
| |
| static char * |
| checkfor (char *ptr, char what) |
| { |
| if (*ptr == what) |
| ptr++; |
| else |
| as_bad (_("expected %c"), what); |
| |
| return ptr; |
| } |
| |
| /* Make sure the mode supplied is the size of a word. */ |
| |
| static void |
| regword (int mode, const char *string) |
| { |
| int ok; |
| |
| ok = CLASS_REG_WORD; |
| if (ok != mode) |
| { |
| as_bad (_("register is wrong size for a word %s"), string); |
| } |
| } |
| |
| /* Make sure the mode supplied is the size of an address. */ |
| |
| static void |
| regaddr (int mode, const char *string) |
| { |
| int ok; |
| |
| ok = segmented_mode ? CLASS_REG_LONG : CLASS_REG_WORD; |
| if (ok != mode) |
| { |
| as_bad (_("register is wrong size for address %s"), string); |
| } |
| } |
| |
| struct ctrl_names { |
| int value; |
| const char *name; |
| }; |
| |
| static struct ctrl_names ctrl_table[] = { |
| { 0x1, "flags" }, /* ldctlb only. */ |
| { 0x2, "fcw" }, /* ldctl only. Applies to all remaining control registers. */ |
| { 0x3, "refresh" }, |
| { 0x4, "psapseg" }, |
| { 0x5, "psapoff" }, |
| { 0x5, "psap" }, |
| { 0x6, "nspseg" }, |
| { 0x7, "nspoff" }, |
| { 0x7, "nsp" }, |
| { 0 , 0 } |
| }; |
| |
| static void |
| get_ctrl_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED) |
| { |
| char *src = *ptr; |
| int i, l; |
| |
| while (*src == ' ') |
| src++; |
| |
| mode->mode = CLASS_CTRL; |
| for (i = 0; ctrl_table[i].name; i++) |
| { |
| l = strlen (ctrl_table[i].name); |
| if (! strncasecmp (ctrl_table[i].name, src, l)) |
| { |
| the_ctrl = ctrl_table[i].value; |
| if (*(src + l) && *(src + l) != ',') |
| break; |
| *ptr = src + l; /* Valid control name found: "consume" it. */ |
| return; |
| } |
| } |
| the_ctrl = 0; |
| } |
| |
| struct flag_names { |
| int value; |
| const char *name; |
| }; |
| |
| static struct flag_names flag_table[] = { |
| { 0x1, "P" }, |
| { 0x1, "V" }, |
| { 0x2, "S" }, |
| { 0x4, "Z" }, |
| { 0x8, "C" }, |
| { 0x0, "+" }, |
| { 0x0, "," }, |
| { 0, 0 } |
| }; |
| |
| static void |
| get_flags_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED) |
| { |
| char *src = *ptr; |
| char c; |
| int i; |
| int j; |
| |
| while (*src == ' ') |
| src++; |
| |
| mode->mode = CLASS_FLAGS; |
| the_flags = 0; |
| for (j = 0; j <= 9; j++) |
| { |
| if (!src[j]) |
| goto done; |
| c = TOUPPER(src[j]); |
| for (i = 0; flag_table[i].name; i++) |
| { |
| if (flag_table[i].name[0] == c) |
| { |
| the_flags = the_flags | flag_table[i].value; |
| goto match; |
| } |
| } |
| goto done; |
| match: |
| ; |
| } |
| done: |
| *ptr = src + j; |
| } |
| |
| struct interrupt_names { |
| int value; |
| const char *name; |
| }; |
| |
| static struct interrupt_names intr_table[] = { |
| { 0x1, "nvi" }, |
| { 0x2, "vi" }, |
| { 0x3, "both" }, |
| { 0x3, "all" }, |
| { 0, 0 } |
| }; |
| |
| static void |
| get_interrupt_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED) |
| { |
| char *src = *ptr; |
| int i, l; |
| |
| while (*src == ' ') |
| src++; |
| |
| mode->mode = CLASS_IMM; |
| the_interrupt = 0; |
| |
| while (*src) |
| { |
| for (i = 0; intr_table[i].name; i++) |
| { |
| l = strlen (intr_table[i].name); |
| if (! strncasecmp (intr_table[i].name, src, l)) |
| { |
| the_interrupt |= intr_table[i].value; |
| if (*(src + l) && *(src + l) != ',') |
| { |
| *ptr = src + l; |
| invalid: |
| as_bad (_("unknown interrupt %s"), src); |
| while (**ptr && ! is_end_of_line[(unsigned char) **ptr]) |
| (*ptr)++; /* Consume rest of line. */ |
| return; |
| } |
| src += l; |
| if (! *src) |
| { |
| *ptr = src; |
| return; |
| } |
| } |
| } |
| if (*src == ',') |
| src++; |
| else |
| { |
| *ptr = src; |
| goto invalid; |
| } |
| } |
| |
| /* No interrupt type specified, opcode won't do anything. */ |
| as_warn (_("opcode has no effect")); |
| the_interrupt = 0x0; |
| } |
| |
| struct cc_names { |
| int value; |
| const char *name; |
| }; |
| |
| static struct cc_names table[] = { |
| { 0x0, "f" }, |
| { 0x1, "lt" }, |
| { 0x2, "le" }, |
| { 0x3, "ule" }, |
| { 0x4, "ov/pe" }, |
| { 0x4, "ov" }, |
| { 0x4, "pe/ov" }, |
| { 0x4, "pe" }, |
| { 0x5, "mi" }, |
| { 0x6, "eq" }, |
| { 0x6, "z" }, |
| { 0x7, "c/ult" }, |
| { 0x7, "c" }, |
| { 0x7, "ult/c" }, |
| { 0x7, "ult" }, |
| { 0x8, "t" }, |
| { 0x9, "ge" }, |
| { 0xa, "gt" }, |
| { 0xb, "ugt" }, |
| { 0xc, "nov/po" }, |
| { 0xc, "nov" }, |
| { 0xc, "po/nov" }, |
| { 0xc, "po" }, |
| { 0xd, "pl" }, |
| { 0xe, "ne" }, |
| { 0xe, "nz" }, |
| { 0xf, "nc/uge" }, |
| { 0xf, "nc" }, |
| { 0xf, "uge/nc" }, |
| { 0xf, "uge" }, |
| { 0 , 0 } |
| }; |
| |
| static void |
| get_cc_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED) |
| { |
| char *src = *ptr; |
| int i, l; |
| |
| while (*src == ' ') |
| src++; |
| |
| mode->mode = CLASS_CC; |
| for (i = 0; table[i].name; i++) |
| { |
| l = strlen (table[i].name); |
| if (! strncasecmp (table[i].name, src, l)) |
| { |
| the_cc = table[i].value; |
| if (*(src + l) && *(src + l) != ',') |
| break; |
| *ptr = src + l; /* Valid cc found: "consume" it. */ |
| return; |
| } |
| } |
| the_cc = 0x8; /* Not recognizing the cc defaults to t. (Assuming no cc present.) */ |
| } |
| |
| static void |
| get_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED) |
| { |
| char *src = *ptr; |
| char *end; |
| |
| mode->mode = 0; |
| |
| while (*src == ' ') |
| src++; |
| if (*src == '#') |
| { |
| mode->mode = CLASS_IMM; |
| imm_operand = &(mode->exp); |
| src = parse_exp (src + 1, &(mode->exp)); |
| } |
| else if (*src == '@') |
| { |
| mode->mode = CLASS_IR; |
| src = parse_reg (src + 1, &mode->regsize, &mode->reg); |
| } |
| else |
| { |
| unsigned int regn; |
| |
| end = parse_reg (src, &mode->mode, ®n); |
| |
| if (end) |
| { |
| int nw; |
| unsigned int nr; |
| |
| src = end; |
| if (*src == '(') |
| { |
| src++; |
| end = parse_reg (src, &nw, &nr); |
| if (end) |
| { |
| /* Got Ra(Rb). */ |
| src = end; |
| |
| if (*src != ')') |
| as_bad (_("Missing ) in ra(rb)")); |
| else |
| src++; |
| |
| regaddr (mode->mode, "ra(rb) ra"); |
| mode->mode = CLASS_BX; |
| mode->reg = regn; |
| mode->x_reg = nr; |
| reg[ARG_RX] = nr; |
| } |
| else |
| { |
| /* Got Ra(disp). */ |
| if (*src == '#') |
| src++; |
| src = parse_exp (src, &(mode->exp)); |
| src = checkfor (src, ')'); |
| mode->mode = CLASS_BA; |
| mode->reg = regn; |
| mode->x_reg = 0; |
| imm_operand = &(mode->exp); |
| } |
| } |
| else |
| { |
| mode->reg = regn; |
| mode->x_reg = 0; |
| } |
| } |
| else |
| { |
| /* No initial reg. */ |
| src = parse_exp (src, &(mode->exp)); |
| if (*src == '(') |
| { |
| src++; |
| end = parse_reg (src, &(mode->mode), ®n); |
| regword (mode->mode, "addr(Ra) ra"); |
| mode->mode = CLASS_X; |
| mode->reg = regn; |
| mode->x_reg = 0; |
| da_operand = &(mode->exp); |
| src = checkfor (end, ')'); |
| } |
| else |
| { |
| /* Just an address. */ |
| mode->mode = CLASS_DA; |
| mode->reg = 0; |
| mode->x_reg = 0; |
| da_operand = &(mode->exp); |
| } |
| } |
| } |
| *ptr = src; |
| } |
| |
| static char * |
| get_operands (const opcode_entry_type *opcode, char *op_end, op_type *operand) |
| { |
| char *ptr = op_end; |
| char *savptr; |
| |
| switch (opcode->noperands) |
| { |
| case 0: |
| operand[0].mode = 0; |
| operand[1].mode = 0; |
| while (*ptr == ' ') |
| ptr++; |
| break; |
| |
| case 1: |
| if (opcode->arg_info[0] == CLASS_CC) |
| { |
| get_cc_operand (&ptr, operand + 0, 0); |
| while (*ptr == ' ') |
| ptr++; |
| if (*ptr && ! is_end_of_line[(unsigned char) *ptr]) |
| { |
| as_bad (_("invalid condition code '%s'"), ptr); |
| while (*ptr && ! is_end_of_line[(unsigned char) *ptr]) |
| ptr++; /* Consume rest of line. */ |
| } |
| } |
| else if (opcode->arg_info[0] == CLASS_FLAGS) |
| { |
| get_flags_operand (&ptr, operand + 0, 0); |
| while (*ptr == ' ') |
| ptr++; |
| if (*ptr && ! is_end_of_line[(unsigned char) *ptr]) |
| { |
| as_bad (_("invalid flag '%s'"), ptr); |
| while (*ptr && ! is_end_of_line[(unsigned char) *ptr]) |
| ptr++; /* Consume rest of line. */ |
| } |
| } |
| else if (opcode->arg_info[0] == (CLASS_IMM + (ARG_IMM2))) |
| get_interrupt_operand (&ptr, operand + 0, 0); |
| else |
| get_operand (&ptr, operand + 0, 0); |
| |
| operand[1].mode = 0; |
| break; |
| |
| case 2: |
| savptr = ptr; |
| if (opcode->arg_info[0] == CLASS_CC) |
| { |
| get_cc_operand (&ptr, operand + 0, 0); |
| while (*ptr == ' ') |
| ptr++; |
| if (*ptr != ',' && strchr (ptr + 1, ',')) |
| { |
| savptr = ptr; |
| while (*ptr != ',') |
| ptr++; |
| *ptr = 0; |
| ptr++; |
| as_bad (_("invalid condition code '%s'"), savptr); |
| } |
| } |
| else if (opcode->arg_info[0] == CLASS_CTRL) |
| { |
| get_ctrl_operand (&ptr, operand + 0, 0); |
| |
| if (the_ctrl == 0) |
| { |
| ptr = savptr; |
| get_operand (&ptr, operand + 0, 0); |
| |
| if (ptr == 0) |
| return NULL; |
| if (*ptr == ',') |
| ptr++; |
| get_ctrl_operand (&ptr, operand + 1, 1); |
| if (the_ctrl == 0) |
| return NULL; |
| return ptr; |
| } |
| } |
| else |
| get_operand (&ptr, operand + 0, 0); |
| |
| if (ptr == 0) |
| return NULL; |
| if (*ptr == ',') |
| ptr++; |
| get_operand (&ptr, operand + 1, 1); |
| break; |
| |
| case 3: |
| get_operand (&ptr, operand + 0, 0); |
| if (*ptr == ',') |
| ptr++; |
| get_operand (&ptr, operand + 1, 1); |
| if (*ptr == ',') |
| ptr++; |
| get_operand (&ptr, operand + 2, 2); |
| break; |
| |
| case 4: |
| get_operand (&ptr, operand + 0, 0); |
| if (*ptr == ',') |
| ptr++; |
| get_operand (&ptr, operand + 1, 1); |
| if (*ptr == ',') |
| ptr++; |
| get_operand (&ptr, operand + 2, 2); |
| if (*ptr == ',') |
| ptr++; |
| get_cc_operand (&ptr, operand + 3, 3); |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| return ptr; |
| } |
| |
| /* Passed a pointer to a list of opcodes which use different |
| addressing modes. Return the opcode which matches the opcodes |
| provided. */ |
| |
| static opcode_entry_type * |
| get_specific (opcode_entry_type *opcode, op_type *operands) |
| { |
| opcode_entry_type *this_try = opcode; |
| int found = 0; |
| unsigned int noperands = opcode->noperands; |
| |
| unsigned int this_index = opcode->idx; |
| |
| while (this_index == opcode->idx && !found) |
| { |
| unsigned int i; |
| |
| this_try = opcode++; |
| for (i = 0; i < noperands; i++) |
| { |
| unsigned int mode = operands[i].mode; |
| |
| if (((mode & CLASS_MASK) == CLASS_IR) && ((this_try->arg_info[i] & CLASS_MASK) == CLASS_IRO)) |
| { |
| mode = operands[i].mode = (operands[i].mode & ~CLASS_MASK) | CLASS_IRO; |
| } |
| |
| if ((mode & CLASS_MASK) != (this_try->arg_info[i] & CLASS_MASK)) |
| { |
| /* It could be a pc rel operand, if this is a da mode |
| and we like disps, then insert it. */ |
| |
| if (mode == CLASS_DA && this_try->arg_info[i] == CLASS_DISP) |
| { |
| /* This is the case. */ |
| operands[i].mode = CLASS_DISP; |
| } |
| else if (mode == CLASS_BA && this_try->arg_info[i]) |
| { |
| /* Can't think of a way to turn what we've been |
| given into something that's OK. */ |
| goto fail; |
| } |
| else if (this_try->arg_info[i] & CLASS_PR) |
| { |
| if (mode == CLASS_REG_LONG && segmented_mode) |
| { |
| /* OK. */ |
| } |
| else if (mode == CLASS_REG_WORD && !segmented_mode) |
| { |
| /* OK. */ |
| } |
| else |
| goto fail; |
| } |
| else |
| goto fail; |
| } |
| switch (mode & CLASS_MASK) |
| { |
| default: |
| break; |
| case CLASS_IRO: |
| if (operands[i].regsize != CLASS_REG_WORD) |
| as_bad (_("invalid indirect register size")); |
| reg[this_try->arg_info[i] & ARG_MASK] = operands[i].reg; |
| break; |
| case CLASS_IR: |
| if ((segmented_mode && operands[i].regsize != CLASS_REG_LONG) |
| || (!segmented_mode && operands[i].regsize != CLASS_REG_WORD)) |
| as_bad (_("invalid indirect register size")); |
| reg[this_try->arg_info[i] & ARG_MASK] = operands[i].reg; |
| break; |
| case CLASS_X: |
| case CLASS_BA: |
| case CLASS_BX: |
| case CLASS_DISP: |
| case CLASS_REG: |
| case CLASS_REG_WORD: |
| case CLASS_REG_BYTE: |
| case CLASS_REG_QUAD: |
| case CLASS_REG_LONG: |
| case CLASS_REGN0: |
| reg[this_try->arg_info[i] & ARG_MASK] = operands[i].reg; |
| break; |
| case CLASS_CTRL: |
| if (this_try->opcode == OPC_ldctlb && the_ctrl != 1) |
| as_bad (_("invalid control register name")); |
| break; |
| } |
| } |
| |
| found = 1; |
| fail: |
| ; |
| } |
| if (found) |
| return this_try; |
| else |
| return 0; |
| } |
| |
| static unsigned char buffer[20]; |
| |
| static void |
| newfix (int ptr, bfd_reloc_code_real_type type, int size, expressionS *operand) |
| { |
| fixS *fixP; |
| |
| /* Size is in nibbles. */ |
| if (operand->X_add_symbol |
| || operand->X_op_symbol |
| || operand->X_add_number) |
| { |
| int is_pcrel; |
| switch(type) |
| { |
| case BFD_RELOC_8_PCREL: |
| case BFD_RELOC_Z8K_CALLR: |
| case BFD_RELOC_Z8K_DISP7: |
| is_pcrel = 1; |
| break; |
| default: |
| is_pcrel = 0; |
| break; |
| } |
| fixP = fix_new_exp (frag_now, ptr, size / 2, |
| operand, is_pcrel, type); |
| if (is_pcrel) |
| fixP->fx_no_overflow = 1; |
| } |
| } |
| |
| static unsigned char * |
| apply_fix (unsigned char *ptr, bfd_reloc_code_real_type type, |
| expressionS *operand, int size) |
| { |
| long n = operand->X_add_number; |
| |
| /* size is in nibbles. */ |
| |
| newfix ((ptr - buffer) / 2, type, size + 1, operand); |
| |
| if (type == BFD_RELOC_Z8K_DISP7) |
| { |
| /* 2 nibbles, but most significant bit is part of the opcode == 7 bits. */ |
| *ptr++ = (n >> 4) & 7; |
| *ptr++ = n >> 0; |
| } |
| else |
| { |
| switch (size) |
| { |
| case 8: /* 8 nibbles == 32 bits. */ |
| *ptr++ = n >> 28; |
| *ptr++ = n >> 24; |
| *ptr++ = n >> 20; |
| *ptr++ = n >> 16; |
| /* Fall through. */ |
| case 4: /* 4 nibbles == 16 bits. */ |
| *ptr++ = n >> 12; |
| *ptr++ = n >> 8; |
| /* Fall through. */ |
| case 2: |
| *ptr++ = n >> 4; |
| /* Fall through. */ |
| case 1: |
| *ptr++ = n >> 0; |
| break; |
| } |
| } |
| return ptr; |
| } |
| |
| /* Now we know what sort of opcodes it is. Let's build the bytes. */ |
| |
| static void |
| build_bytes (opcode_entry_type *this_try, struct z8k_op *operand ATTRIBUTE_UNUSED) |
| { |
| unsigned char *output_ptr = buffer; |
| int c; |
| int nibble; |
| unsigned int *class_ptr; |
| |
| frag_wane (frag_now); |
| frag_new (0); |
| |
| if (frag_room () < 8) |
| frag_grow (8); /* Make room for maximum instruction size. */ |
| |
| memset (buffer, 0, sizeof (buffer)); |
| class_ptr = this_try->byte_info; |
| |
| for (nibble = 0; (c = *class_ptr++); nibble++) |
| { |
| |
| switch (c & CLASS_MASK) |
| { |
| default: |
| abort (); |
| |
| case CLASS_ADDRESS: |
| /* Direct address, we don't cope with the SS mode right now. */ |
| if (segmented_mode) |
| { |
| /* da_operand->X_add_number |= 0x80000000; -- Now set at relocation time. */ |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_32, da_operand, 8); |
| } |
| else |
| { |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_16, da_operand, 4); |
| } |
| da_operand = 0; |
| break; |
| case CLASS_DISP8: |
| /* pc rel 8 bit */ |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_8_PCREL, da_operand, 2); |
| da_operand = 0; |
| break; |
| |
| case CLASS_0DISP7: |
| /* pc rel 7 bit */ |
| *output_ptr = 0; |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_DISP7, da_operand, 2); |
| da_operand = 0; |
| break; |
| |
| case CLASS_1DISP7: |
| /* pc rel 7 bit */ |
| *output_ptr = 0x80; |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_DISP7, da_operand, 2); |
| output_ptr[-2] = 0x8; |
| da_operand = 0; |
| break; |
| |
| case CLASS_BIT_1OR2: |
| *output_ptr = c & 0xf; |
| if (imm_operand) |
| { |
| if (imm_operand->X_add_number == 2) |
| *output_ptr |= 2; |
| else if (imm_operand->X_add_number != 1) |
| as_bad (_("immediate must be 1 or 2")); |
| } |
| else |
| as_bad (_("immediate 1 or 2 expected")); |
| output_ptr++; |
| break; |
| case CLASS_CC: |
| *output_ptr++ = the_cc; |
| break; |
| case CLASS_0CCC: |
| if (the_ctrl < 2 || the_ctrl > 7) |
| as_bad (_("invalid control register name")); |
| *output_ptr++ = the_ctrl; |
| break; |
| case CLASS_1CCC: |
| if (the_ctrl < 2 || the_ctrl > 7) |
| as_bad (_("invalid control register name")); |
| *output_ptr++ = the_ctrl | 0x8; |
| break; |
| case CLASS_00II: |
| *output_ptr++ = (~the_interrupt & 0x3); |
| break; |
| case CLASS_01II: |
| *output_ptr++ = (~the_interrupt & 0x3) | 0x4; |
| break; |
| case CLASS_FLAGS: |
| *output_ptr++ = the_flags; |
| break; |
| case CLASS_IGNORE: |
| case CLASS_BIT: |
| *output_ptr++ = c & 0xf; |
| break; |
| case CLASS_REGN0: |
| if (reg[c & 0xf] == 0) |
| as_bad (_("can't use R0 here")); |
| /* Fall through. */ |
| case CLASS_REG: |
| case CLASS_REG_BYTE: |
| case CLASS_REG_WORD: |
| case CLASS_REG_LONG: |
| case CLASS_REG_QUAD: |
| /* Insert bit pattern of right reg. */ |
| *output_ptr++ = reg[c & 0xf]; |
| break; |
| case CLASS_DISP: |
| switch (c & ARG_MASK) |
| { |
| case ARG_DISP12: |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_CALLR, da_operand, 4); |
| break; |
| case ARG_DISP16: |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_16_PCREL, da_operand, 4); |
| break; |
| default: |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_16, da_operand, 4); |
| } |
| da_operand = 0; |
| break; |
| |
| case CLASS_IMM: |
| { |
| switch (c & ARG_MASK) |
| { |
| case ARG_NIM4: |
| if (imm_operand->X_add_number > 15) |
| as_bad (_("immediate value out of range")); |
| imm_operand->X_add_number = -imm_operand->X_add_number; |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_IMM4L, imm_operand, 1); |
| break; |
| /*case ARG_IMMNMINUS1: not used. */ |
| case ARG_IMM4M1: |
| imm_operand->X_add_number--; |
| /* Fall through. */ |
| case ARG_IMM4: |
| if (imm_operand->X_add_number > 15) |
| as_bad (_("immediate value out of range")); |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_IMM4L, imm_operand, 1); |
| break; |
| case ARG_NIM8: |
| imm_operand->X_add_number = -imm_operand->X_add_number; |
| /* Fall through. */ |
| case ARG_IMM8: |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_8, imm_operand, 2); |
| break; |
| case ARG_IMM16: |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_16, imm_operand, 4); |
| break; |
| case ARG_IMM32: |
| output_ptr = apply_fix (output_ptr, BFD_RELOC_32, imm_operand, 8); |
| break; |
| default: |
| abort (); |
| } |
| } |
| } |
| } |
| |
| /* Copy from the nibble buffer into the frag. */ |
| { |
| int length = (output_ptr - buffer) / 2; |
| unsigned char *src = buffer; |
| unsigned char *fragp = (unsigned char *) frag_more (length); |
| |
| while (src < output_ptr) |
| { |
| *fragp = ((src[0] & 0xf) << 4) | (src[1] & 0xf); |
| src += 2; |
| fragp++; |
| } |
| } |
| } |
| |
| /* This is the guts of 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) |
| { |
| char c; |
| char *op_start; |
| char *op_end; |
| struct z8k_op operand[4]; |
| opcode_entry_type *opcode; |
| |
| /* Drop leading whitespace. */ |
| while (*str == ' ') |
| str++; |
| |
| /* Find the op code end. */ |
| for (op_start = op_end = str; |
| *op_end != 0 && *op_end != ' ' && ! is_end_of_line[(unsigned char) *op_end]; |
| op_end++) |
| ; |
| |
| if (op_end == op_start) |
| { |
| as_bad (_("can't find opcode ")); |
| } |
| c = *op_end; |
| |
| *op_end = 0; /* Zero-terminate op code string for str_hash_find() call. */ |
| |
| opcode = (opcode_entry_type *) str_hash_find (opcode_hash_control, op_start); |
| |
| if (opcode == NULL) |
| { |
| as_bad (_("unknown opcode")); |
| return; |
| } |
| |
| *op_end = c; /* Restore original string. */ |
| |
| if (opcode->opcode == 250) |
| { |
| pseudo_typeS *p; |
| char oc; |
| char *old = input_line_pointer; |
| |
| /* Was really a pseudo op. */ |
| |
| input_line_pointer = op_end; |
| |
| oc = *old; |
| *old = '\n'; |
| while (*input_line_pointer == ' ') |
| input_line_pointer++; |
| p = (pseudo_typeS *) (opcode->func); |
| |
| (p->poc_handler) (p->poc_val); |
| input_line_pointer = old; |
| *old = oc; |
| } |
| else |
| { |
| char *new_input_line_pointer; |
| |
| new_input_line_pointer = get_operands (opcode, op_end, operand); |
| if (new_input_line_pointer) |
| { |
| input_line_pointer = new_input_line_pointer; |
| opcode = get_specific (opcode, operand); |
| } |
| |
| if (new_input_line_pointer == NULL || opcode == NULL) |
| { |
| /* Couldn't find an opcode which matched the operands. */ |
| char *where = frag_more (2); |
| |
| where[0] = 0x0; |
| where[1] = 0x0; |
| |
| as_bad (_("Can't find opcode to match operands")); |
| return; |
| } |
| |
| build_bytes (opcode, operand); |
| } |
| } |
| |
| /* We have no need to default values of symbols. */ |
| |
| symbolS * |
| md_undefined_symbol (char *name ATTRIBUTE_UNUSED) |
| { |
| return 0; |
| } |
| |
| /* Various routines to kill one day. */ |
| |
| const char * |
| md_atof (int type, char *litP, int *sizeP) |
| { |
| return ieee_md_atof (type, litP, sizeP, true); |
| } |
| |
| const char *md_shortopts = "z:"; |
| |
| struct option md_longopts[] = |
| { |
| #define OPTION_RELAX (OPTION_MD_BASE) |
| {"linkrelax", no_argument, NULL, OPTION_RELAX}, |
| {NULL, no_argument, NULL, 0} |
| }; |
| |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| int |
| md_parse_option (int c, const char *arg) |
| { |
| switch (c) |
| { |
| case 'z': |
| if (!strcmp (arg, "8001")) |
| z8k_target_from_cmdline = 2; |
| else if (!strcmp (arg, "8002")) |
| z8k_target_from_cmdline = 1; |
| else |
| { |
| as_bad (_("invalid architecture -z%s"), arg); |
| return 0; |
| } |
| break; |
| |
| case OPTION_RELAX: |
| linkrelax = 1; |
| break; |
| |
| default: |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| void |
| md_show_usage (FILE *stream) |
| { |
| fprintf (stream, _("\ |
| Z8K options:\n\ |
| -z8001 generate segmented code\n\ |
| -z8002 generate unsegmented code\n\ |
| -linkrelax create linker relaxable code\n")); |
| } |
| |
| void |
| md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, |
| segT sec ATTRIBUTE_UNUSED, |
| fragS *fragP ATTRIBUTE_UNUSED) |
| { |
| printf (_("call to md_convert_frag\n")); |
| abort (); |
| } |
| |
| /* Generate a machine dependent reloc from a fixup. */ |
| |
| arelent* |
| tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, |
| fixS *fixp ATTRIBUTE_UNUSED) |
| { |
| arelent *reloc; |
| |
| reloc = XNEW (arelent); |
| reloc->sym_ptr_ptr = XNEW (asymbol *); |
| *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); |
| reloc->address = fixp->fx_frag->fr_address + fixp->fx_where; |
| reloc->addend = fixp->fx_offset; |
| reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type); |
| |
| if (! reloc->howto) |
| { |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("Cannot represent %s relocation in object file"), |
| bfd_get_reloc_code_name (fixp->fx_r_type)); |
| abort (); |
| } |
| return reloc; |
| } |
| |
| valueT |
| md_section_align (segT seg, valueT size) |
| { |
| int align = bfd_section_alignment (seg); |
| valueT mask = ((valueT) 1 << align) - 1; |
| |
| return (size + mask) & ~mask; |
| } |
| |
| /* Attempt to simplify or eliminate a fixup. To indicate that a fixup |
| has been eliminated, set fix->fx_done. If fix->fx_addsy is non-NULL, |
| we will have to generate a reloc entry. */ |
| void |
| md_apply_fix (fixS *fixP, valueT *valP, segT segment ATTRIBUTE_UNUSED) |
| { |
| long val = * (long *) valP; |
| char *buf = fixP->fx_where + fixP->fx_frag->fr_literal; |
| |
| switch (fixP->fx_r_type) |
| { |
| case BFD_RELOC_Z8K_IMM4L: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 0; |
| } |
| else |
| buf[0] = (buf[0] & 0xf0) | (val & 0xf); |
| break; |
| |
| case BFD_RELOC_8: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 0; |
| } |
| else |
| *buf++ = val; |
| break; |
| |
| case BFD_RELOC_16: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 0; |
| } |
| else |
| { |
| *buf++ = (val >> 8); |
| *buf++ = val; |
| } |
| break; |
| |
| case BFD_RELOC_32: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 0; |
| } |
| else |
| { |
| *buf++ = (val >> 24); |
| *buf++ = (val >> 16); |
| *buf++ = (val >> 8); |
| *buf++ = val; |
| } |
| break; |
| |
| case BFD_RELOC_8_PCREL: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 0; |
| } |
| else |
| { |
| if (val & 1) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("cannot branch to odd address")); |
| val /= 2; |
| if (val > 127 || val < -128) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("relative jump out of range")); |
| *buf++ = val; |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 1; |
| } |
| break; |
| |
| case BFD_RELOC_16_PCREL: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 0; |
| } |
| else |
| { |
| val = val - fixP->fx_frag->fr_address + fixP->fx_where - fixP->fx_size; |
| if (val > 32767 || val < -32768) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("relative address out of range")); |
| *buf++ = (val >> 8); |
| *buf++ = val; |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 1; |
| } |
| break; |
| |
| case BFD_RELOC_Z8K_CALLR: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 0; |
| } |
| else |
| { |
| if (val & 1) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("cannot branch to odd address")); |
| if (val > 4096 || val < -4095) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("relative call out of range")); |
| val = -val / 2; |
| *buf = (*buf & 0xf0) | ((val >> 8) & 0xf); |
| buf++; |
| *buf++ = val & 0xff; |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 1; |
| } |
| break; |
| |
| case BFD_RELOC_Z8K_DISP7: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 0; |
| } |
| else |
| { |
| if (val & 1) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("cannot branch to odd address")); |
| val /= 2; |
| if (val > 0 || val < -127) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("relative jump out of range")); |
| *buf = (*buf & 0x80) | (-val & 0x7f); |
| fixP->fx_no_overflow = 1; |
| fixP->fx_done = 1; |
| } |
| break; |
| |
| default: |
| printf(_("md_apply_fix: unknown r_type 0x%x\n"), fixP->fx_r_type); |
| abort (); |
| } |
| |
| if (fixP->fx_addsy == NULL && fixP->fx_pcrel == 0) |
| fixP->fx_done = 1; |
| } |
| |
| int |
| md_estimate_size_before_relax (fragS *fragP ATTRIBUTE_UNUSED, |
| segT segment_type ATTRIBUTE_UNUSED) |
| { |
| printf (_("call to md_estimate_size_before_relax\n")); |
| abort (); |
| } |
| |
| /* Put number into target byte order. */ |
| |
| void |
| md_number_to_chars (char *ptr, valueT use, int nbytes) |
| { |
| number_to_chars_bigendian (ptr, use, nbytes); |
| } |
| |
| /* On the Z8000, a PC-relative offset is relative to the address of the |
| instruction plus its size. */ |
| long |
| md_pcrel_from (fixS *fixP) |
| { |
| return fixP->fx_size + fixP->fx_where + fixP->fx_frag->fr_address; |
| } |
| |
| void |
| tc_coff_symbol_emit_hook (symbolS *s ATTRIBUTE_UNUSED) |
| { |
| } |