/* tc-z80.c -- Assemble code for the Zilog Z80 and ASCII R800 Copyright 2005, 2006, 2007, 2008, 2009, 2012 Free Software Foundation, Inc. Contributed by Arnold Metselaar 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" /* Exported constants. */ const char comment_chars[] = ";\0"; const char line_comment_chars[] = "#;\0"; const char line_separator_chars[] = "\0"; const char EXP_CHARS[] = "eE\0"; const char FLT_CHARS[] = "RrFf\0"; /* For machine specific options. */ const char * md_shortopts = ""; /* None yet. */ enum options { OPTION_MACH_Z80 = OPTION_MD_BASE, OPTION_MACH_R800, OPTION_MACH_IUD, OPTION_MACH_WUD, OPTION_MACH_FUD, OPTION_MACH_IUP, OPTION_MACH_WUP, OPTION_MACH_FUP }; #define INS_Z80 1 #define INS_UNDOC 2 #define INS_UNPORT 4 #define INS_R800 8 struct option md_longopts[] = { { "z80", no_argument, NULL, OPTION_MACH_Z80}, { "r800", no_argument, NULL, OPTION_MACH_R800}, { "ignore-undocumented-instructions", no_argument, NULL, OPTION_MACH_IUD }, { "Wnud", no_argument, NULL, OPTION_MACH_IUD }, { "warn-undocumented-instructions", no_argument, NULL, OPTION_MACH_WUD }, { "Wud", no_argument, NULL, OPTION_MACH_WUD }, { "forbid-undocumented-instructions", no_argument, NULL, OPTION_MACH_FUD }, { "Fud", no_argument, NULL, OPTION_MACH_FUD }, { "ignore-unportable-instructions", no_argument, NULL, OPTION_MACH_IUP }, { "Wnup", no_argument, NULL, OPTION_MACH_IUP }, { "warn-unportable-instructions", no_argument, NULL, OPTION_MACH_WUP }, { "Wup", no_argument, NULL, OPTION_MACH_WUP }, { "forbid-unportable-instructions", no_argument, NULL, OPTION_MACH_FUP }, { "Fup", no_argument, NULL, OPTION_MACH_FUP }, { NULL, no_argument, NULL, 0 } } ; size_t md_longopts_size = sizeof (md_longopts); extern int coff_flags; /* Instruction classes that silently assembled. */ static int ins_ok = INS_Z80 | INS_UNDOC; /* Instruction classes that generate errors. */ static int ins_err = INS_R800; /* Instruction classes actually used, determines machine type. */ static int ins_used = INS_Z80; int md_parse_option (int c, char* arg ATTRIBUTE_UNUSED) { switch (c) { default: return 0; case OPTION_MACH_Z80: ins_ok &= ~INS_R800; ins_err |= INS_R800; break; case OPTION_MACH_R800: ins_ok = INS_Z80 | INS_UNDOC | INS_R800; ins_err = INS_UNPORT; break; case OPTION_MACH_IUD: ins_ok |= INS_UNDOC; ins_err &= ~INS_UNDOC; break; case OPTION_MACH_IUP: ins_ok |= INS_UNDOC | INS_UNPORT; ins_err &= ~(INS_UNDOC | INS_UNPORT); break; case OPTION_MACH_WUD: if ((ins_ok & INS_R800) == 0) { ins_ok &= ~(INS_UNDOC|INS_UNPORT); ins_err &= ~INS_UNDOC; } break; case OPTION_MACH_WUP: ins_ok &= ~INS_UNPORT; ins_err &= ~(INS_UNDOC|INS_UNPORT); break; case OPTION_MACH_FUD: if ((ins_ok & INS_R800) == 0) { ins_ok &= (INS_UNDOC | INS_UNPORT); ins_err |= INS_UNDOC | INS_UNPORT; } break; case OPTION_MACH_FUP: ins_ok &= ~INS_UNPORT; ins_err |= INS_UNPORT; break; } return 1; } void md_show_usage (FILE * f) { fprintf (f, "\n\ CPU model/instruction set options:\n\ \n\ -z80\t\t assemble for Z80\n\ -ignore-undocumented-instructions\n\ -Wnud\n\ \tsilently assemble undocumented Z80-instructions that work on R800\n\ -ignore-unportable-instructions\n\ -Wnup\n\ \tsilently assemble all undocumented Z80-instructions\n\ -warn-undocumented-instructions\n\ -Wud\n\ \tissue warnings for undocumented Z80-instructions that work on R800\n\ -warn-unportable-instructions\n\ -Wup\n\ \tissue warnings for other undocumented Z80-instructions\n\ -forbid-undocumented-instructions\n\ -Fud\n\ \ttreat all undocumented z80-instructions as errors\n\ -forbid-unportable-instructions\n\ -Fup\n\ \ttreat undocumented z80-instructions that do not work on R800 as errors\n\ -r800\t assemble for R800\n\n\ Default: -z80 -ignore-undocument-instructions -warn-unportable-instructions.\n"); } static symbolS * zero; struct reg_entry { char* name; int number; }; #define R_STACKABLE (0x80) #define R_ARITH (0x40) #define R_IX (0x20) #define R_IY (0x10) #define R_INDEX (R_IX | R_IY) #define REG_A (7) #define REG_B (0) #define REG_C (1) #define REG_D (2) #define REG_E (3) #define REG_H (4) #define REG_L (5) #define REG_F (6 | 8) #define REG_I (9) #define REG_R (10) #define REG_AF (3 | R_STACKABLE) #define REG_BC (0 | R_STACKABLE | R_ARITH) #define REG_DE (1 | R_STACKABLE | R_ARITH) #define REG_HL (2 | R_STACKABLE | R_ARITH) #define REG_IX (REG_HL | R_IX) #define REG_IY (REG_HL | R_IY) #define REG_SP (3 | R_ARITH) static const struct reg_entry regtable[] = { {"a", REG_A }, {"af", REG_AF }, {"b", REG_B }, {"bc", REG_BC }, {"c", REG_C }, {"d", REG_D }, {"de", REG_DE }, {"e", REG_E }, {"f", REG_F }, {"h", REG_H }, {"hl", REG_HL }, {"i", REG_I }, {"ix", REG_IX }, {"ixh",REG_H | R_IX }, {"ixl",REG_L | R_IX }, {"iy", REG_IY }, {"iyh",REG_H | R_IY }, {"iyl",REG_L | R_IY }, {"l", REG_L }, {"r", REG_R }, {"sp", REG_SP }, } ; #define BUFLEN 8 /* Large enough for any keyword. */ void md_begin (void) { expressionS nul, reg; char * p; unsigned int i, j, k; char buf[BUFLEN]; reg.X_op = O_register; reg.X_md = 0; reg.X_add_symbol = reg.X_op_symbol = 0; for ( i = 0 ; i < ARRAY_SIZE ( regtable ) ; ++i ) { reg.X_add_number = regtable[i].number; k = strlen ( regtable[i].name ); buf[k] = 0; if ( k+1 < BUFLEN ) { for ( j = ( 1<[:] [.](EQU|DEFL) . */ if (is_name_beginner (*input_line_pointer)) { char c, *rest, *line_start; int len; line_start = input_line_pointer; if (ignore_input ()) return 0; c = get_symbol_end (); rest = input_line_pointer + 1; if (*rest == ':') ++rest; if (*rest == ' ' || *rest == '\t') ++rest; if (*rest == '.') ++rest; if (strncasecmp (rest, "EQU", 3) == 0) len = 3; else if (strncasecmp (rest, "DEFL", 4) == 0) len = 4; else len = 0; if (len && (!ISALPHA(rest[len]) ) ) { /* Handle assignment here. */ if (line_start[-1] == '\n') { bump_line_counters (); LISTING_NEWLINE (); } input_line_pointer = rest + len - 1; /* Allow redefining with "DEFL" (len == 4), but not with "EQU". */ equals (line_start, len == 4); return 1; } else { /* Restore line and pointer. */ *input_line_pointer = c; input_line_pointer = line_start; } } return 0; } symbolS * md_undefined_symbol (char *name ATTRIBUTE_UNUSED) { return NULL; } char * md_atof (int type ATTRIBUTE_UNUSED, char *litP ATTRIBUTE_UNUSED, int *sizeP ATTRIBUTE_UNUSED) { return _("floating point numbers are not implemented"); } valueT md_section_align (segT seg ATTRIBUTE_UNUSED, valueT size) { return size; } long md_pcrel_from (fixS * fixp) { return fixp->fx_where + fixp->fx_frag->fr_address + 1; } typedef const char * (asfunc)(char, char, const char*); typedef struct _table_t { char* name; char prefix; char opcode; asfunc * fp; } table_t; /* Compares the key for structs that start with a char * to the key. */ static int key_cmp (const void * a, const void * b) { const char *str_a, *str_b; str_a = *((const char**)a); str_b = *((const char**)b); return strcmp (str_a, str_b); } char buf[BUFLEN]; const char *key = buf; /* Prevent an error on a line from also generating a "junk at end of line" error message. */ static char err_flag; static void error (const char * message) { as_bad ("%s", message); err_flag = 1; } static void ill_op (void) { error (_("illegal operand")); } static void wrong_mach (int ins_type) { const char *p; switch (ins_type) { case INS_UNDOC: p = "undocumented instruction"; break; case INS_UNPORT: p = "instruction does not work on R800"; break; case INS_R800: p = "instruction only works R800"; break; default: p = 0; /* Not reachable. */ } if (ins_type & ins_err) error (_(p)); else as_warn (_(p)); } static void check_mach (int ins_type) { if ((ins_type & ins_ok) == 0) wrong_mach (ins_type); ins_used |= ins_type; } /* Check whether an expression is indirect. */ static int is_indir (const char *s) { char quote; const char *p; int indir, depth; /* Indirection is indicated with parentheses. */ indir = (*s == '('); for (p = s, depth = 0; *p && *p != ','; ++p) { switch (*p) { case '"': case '\'': for (quote = *p++; quote != *p && *p != '\n'; ++p) if (*p == '\\' && p[1]) ++p; break; case '(': ++ depth; break; case ')': -- depth; if (depth == 0) { p = skip_space (p + 1); if (*p && *p != ',') indir = 0; --p; } if (depth < 0) error (_("mismatched parentheses")); break; } } if (depth != 0) error (_("mismatched parentheses")); return indir; } /* Check whether a symbol involves a register. */ static int contains_register(symbolS *sym) { if (sym) { expressionS * ex = symbol_get_value_expression(sym); return (O_register == ex->X_op) || (ex->X_add_symbol && contains_register(ex->X_add_symbol)) || (ex->X_op_symbol && contains_register(ex->X_op_symbol)); } else return 0; } /* Parse general expression, not loooking for indexed adressing. */ static const char * parse_exp_not_indexed (const char *s, expressionS *op) { const char *p; int indir; segT dummy; p = skip_space (s); op->X_md = indir = is_indir (p); input_line_pointer = (char*) s ; dummy = expression (op); switch (op->X_op) { case O_absent: error (_("missing operand")); break; case O_illegal: error (_("bad expression syntax")); break; } return input_line_pointer; } /* Parse expression, change operator to O_md1 for indexed addressing*/ static const char * parse_exp (const char *s, expressionS *op) { const char* res = parse_exp_not_indexed (s, op); switch (op->X_op) { case O_add: case O_subtract: if (op->X_md && (O_register == symbol_get_value_expression(op->X_add_symbol)->X_op)) { int rnum = symbol_get_value_expression(op->X_add_symbol)->X_add_number; if ( ((REG_IX != rnum) && (REG_IY != rnum)) || contains_register(op->X_op_symbol) ) { ill_op(); } else { if (O_subtract == op->X_op) { expressionS minus; minus.X_op = O_uminus; minus.X_add_number = 0; minus.X_add_symbol = op->X_op_symbol; minus.X_op_symbol = 0; op->X_op_symbol = make_expr_symbol(&minus); op->X_op = O_add; } symbol_get_value_expression(op->X_op_symbol)->X_add_number += op->X_add_number; op->X_add_number = rnum; op->X_add_symbol = op->X_op_symbol; op->X_op_symbol = 0; op->X_op = O_md1; } } break; case O_register: if ( op->X_md && ((REG_IX == op->X_add_number)||(REG_IY == op->X_add_number)) ) { op->X_add_symbol = zero; op->X_op = O_md1; } break; } return res; } /* Condition codes, including some synonyms provided by HiTech zas. */ static const struct reg_entry cc_tab[] = { { "age", 6 << 3 }, { "alt", 7 << 3 }, { "c", 3 << 3 }, { "di", 4 << 3 }, { "ei", 5 << 3 }, { "lge", 2 << 3 }, { "llt", 3 << 3 }, { "m", 7 << 3 }, { "nc", 2 << 3 }, { "nz", 0 << 3 }, { "p", 6 << 3 }, { "pe", 5 << 3 }, { "po", 4 << 3 }, { "z", 1 << 3 }, } ; /* Parse condition code. */ static const char * parse_cc (const char *s, char * op) { const char *p; int i; struct reg_entry * cc_p; for (i = 0; i < BUFLEN; ++i) { if (!ISALPHA (s[i])) /* Condition codes consist of letters only. */ break; buf[i] = TOLOWER (s[i]); } if ((i < BUFLEN) && ((s[i] == 0) || (s[i] == ','))) { buf[i] = 0; cc_p = bsearch (&key, cc_tab, ARRAY_SIZE (cc_tab), sizeof (cc_tab[0]), key_cmp); } else cc_p = NULL; if (cc_p) { *op = cc_p->number; p = s + i; } else p = NULL; return p; } static const char * emit_insn (char prefix, char opcode, const char * args) { char *p; if (prefix) { p = frag_more (2); *p++ = prefix; } else p = frag_more (1); *p = opcode; return args; } void z80_cons_fix_new (fragS *frag_p, int offset, int nbytes, expressionS *exp) { bfd_reloc_code_real_type r[4] = { BFD_RELOC_8, BFD_RELOC_16, BFD_RELOC_24, BFD_RELOC_32 }; if (nbytes < 1 || nbytes > 4) { as_bad (_("unsupported BFD relocation size %u"), nbytes); } else { fix_new_exp (frag_p, offset, nbytes, exp, 0, r[nbytes-1]); } } static void emit_byte (expressionS * val, bfd_reloc_code_real_type r_type) { char *p; int lo, hi; fixS * fixp; p = frag_more (1); *p = val->X_add_number; if ( contains_register(val->X_add_symbol) || contains_register(val->X_op_symbol) ) { ill_op(); } else if ((r_type == BFD_RELOC_8_PCREL) && (val->X_op == O_constant)) { as_bad (_("cannot make a relative jump to an absolute location")); } else if (val->X_op == O_constant) { lo = -128; hi = (BFD_RELOC_8 == r_type) ? 255 : 127; if ((val->X_add_number < lo) || (val->X_add_number > hi)) { if (r_type == BFD_RELOC_Z80_DISP8) as_bad (_("offset too large")); else as_warn (_("overflow")); } } else { fixp = fix_new_exp (frag_now, p - frag_now->fr_literal, 1, val, (r_type == BFD_RELOC_8_PCREL) ? TRUE : FALSE, r_type); /* FIXME : Process constant offsets immediately. */ } } static void emit_word (expressionS * val) { char *p; p = frag_more (2); if ( (val->X_op == O_register) || (val->X_op == O_md1) || contains_register(val->X_add_symbol) || contains_register(val->X_op_symbol) ) ill_op (); else { *p = val->X_add_number; p[1] = (val->X_add_number>>8); if (val->X_op != O_constant) fix_new_exp (frag_now, p - frag_now->fr_literal, 2, val, FALSE, BFD_RELOC_16); } } static void emit_mx (char prefix, char opcode, int shift, expressionS * arg) /* The operand m may be r, (hl), (ix+d), (iy+d), if 0 == prefix m may also be ixl, ixh, iyl, iyh. */ { char *q; int rnum; rnum = arg->X_add_number; switch (arg->X_op) { case O_register: if (arg->X_md) { if (rnum != REG_HL) { ill_op (); break; } else rnum = 6; } else { if ((prefix == 0) && (rnum & R_INDEX)) { prefix = (rnum & R_IX) ? 0xDD : 0xFD; check_mach (INS_UNDOC); rnum &= ~R_INDEX; } if (rnum > 7) { ill_op (); break; } } q = frag_more (prefix ? 2 : 1); if (prefix) * q ++ = prefix; * q ++ = opcode + (rnum << shift); break; case O_md1: q = frag_more (2); *q++ = (rnum & R_IX) ? 0xDD : 0xFD; *q = (prefix) ? prefix : (opcode + (6 << shift)); { expressionS offset = *arg; offset.X_op = O_symbol; offset.X_add_number = 0; emit_byte (&offset, BFD_RELOC_Z80_DISP8); } if (prefix) { q = frag_more (1); *q = opcode+(6<X_op) { case O_register: case O_md1: emit_mx (prefix, opcode, 0, arg_p); break; default: if (arg_p->X_md) ill_op (); else { q = frag_more (prefix ? 2 : 1); if (prefix) *q++ = prefix; *q = opcode ^ 0x46; emit_byte (arg_p, BFD_RELOC_8); } } } /* The operand s may be r, (hl), (ix+d), (iy+d), n. */ static const char * emit_s (char prefix, char opcode, const char *args) { expressionS arg_s; const char *p; p = parse_exp (args, & arg_s); emit_sx (prefix, opcode, & arg_s); return p; } static const char * emit_call (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args) { expressionS addr; const char *p; char *q; p = parse_exp_not_indexed (args, &addr); if (addr.X_md) ill_op (); else { q = frag_more (1); *q = opcode; emit_word (& addr); } return p; } /* Operand may be rr, r, (hl), (ix+d), (iy+d). */ static const char * emit_incdec (char prefix, char opcode, const char * args) { expressionS operand; int rnum; const char *p; char *q; p = parse_exp (args, &operand); rnum = operand.X_add_number; if ((! operand.X_md) && (operand.X_op == O_register) && (R_ARITH&rnum)) { q = frag_more ((rnum & R_INDEX) ? 2 : 1); if (rnum & R_INDEX) *q++ = (rnum & R_IX) ? 0xDD : 0xFD; *q = prefix + ((rnum & 3) << 4); } else { if ((operand.X_op == O_md1) || (operand.X_op == O_register)) emit_mx (0, opcode, 3, & operand); else ill_op (); } return p; } static const char * emit_jr (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args) { expressionS addr; const char *p; char *q; p = parse_exp_not_indexed (args, &addr); if (addr.X_md) ill_op (); else { q = frag_more (1); *q = opcode; emit_byte (&addr, BFD_RELOC_8_PCREL); } return p; } static const char * emit_jp (char prefix, char opcode, const char * args) { expressionS addr; const char *p; char *q; int rnum; p = parse_exp_not_indexed (args, & addr); if (addr.X_md) { rnum = addr.X_add_number; if ((O_register == addr.X_op) && (REG_HL == (rnum & ~R_INDEX))) { q = frag_more ((rnum & R_INDEX) ? 2 : 1); if (rnum & R_INDEX) *q++ = (rnum & R_IX) ? 0xDD : 0xFD; *q = prefix; } else ill_op (); } else { q = frag_more (1); *q = opcode; emit_word (& addr); } return p; } static const char * emit_im (char prefix, char opcode, const char * args) { expressionS mode; const char *p; char *q; p = parse_exp (args, & mode); if (mode.X_md || (mode.X_op != O_constant)) ill_op (); else switch (mode.X_add_number) { case 1: case 2: ++mode.X_add_number; /* Fall through. */ case 0: q = frag_more (2); *q++ = prefix; *q = opcode + 8*mode.X_add_number; break; default: ill_op (); } return p; } static const char * emit_pop (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args) { expressionS regp; const char *p; char *q; p = parse_exp (args, & regp); if ((!regp.X_md) && (regp.X_op == O_register) && (regp.X_add_number & R_STACKABLE)) { int rnum; rnum = regp.X_add_number; if (rnum&R_INDEX) { q = frag_more (2); *q++ = (rnum&R_IX)?0xDD:0xFD; } else q = frag_more (1); *q = opcode + ((rnum & 3) << 4); } else ill_op (); return p; } static const char * emit_retcc (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args) { char cc, *q; const char *p; p = parse_cc (args, &cc); q = frag_more (1); if (p) *q = opcode + cc; else *q = prefix; return p ? p : args; } static const char * emit_adc (char prefix, char opcode, const char * args) { expressionS term; int rnum; const char *p; char *q; p = parse_exp (args, &term); if (*p++ != ',') { error (_("bad instruction syntax")); return p; } if ((term.X_md) || (term.X_op != O_register)) ill_op (); else switch (term.X_add_number) { case REG_A: p = emit_s (0, prefix, p); break; case REG_HL: p = parse_exp (p, &term); if ((!term.X_md) && (term.X_op == O_register)) { rnum = term.X_add_number; if (R_ARITH == (rnum & (R_ARITH | R_INDEX))) { q = frag_more (2); *q++ = 0xED; *q = opcode + ((rnum & 3) << 4); break; } } /* Fall through. */ default: ill_op (); } return p; } static const char * emit_add (char prefix, char opcode, const char * args) { expressionS term; int lhs, rhs; const char *p; char *q; p = parse_exp (args, &term); if (*p++ != ',') { error (_("bad instruction syntax")); return p; } if ((term.X_md) || (term.X_op != O_register)) ill_op (); else switch (term.X_add_number & ~R_INDEX) { case REG_A: p = emit_s (0, prefix, p); break; case REG_HL: lhs = term.X_add_number; p = parse_exp (p, &term); if ((!term.X_md) && (term.X_op == O_register)) { rhs = term.X_add_number; if ((rhs & R_ARITH) && ((rhs == lhs) || ((rhs & ~R_INDEX) != REG_HL))) { q = frag_more ((lhs & R_INDEX) ? 2 : 1); if (lhs & R_INDEX) *q++ = (lhs & R_IX) ? 0xDD : 0xFD; *q = opcode + ((rhs & 3) << 4); break; } } /* Fall through. */ default: ill_op (); } return p; } static const char * emit_bit (char prefix, char opcode, const char * args) { expressionS b; int bn; const char *p; p = parse_exp (args, &b); if (*p++ != ',') error (_("bad instruction syntax")); bn = b.X_add_number; if ((!b.X_md) && (b.X_op == O_constant) && (0 <= bn) && (bn < 8)) { if (opcode == 0x40) /* Bit : no optional third operand. */ p = emit_m (prefix, opcode + (bn << 3), p); else /* Set, res : resulting byte can be copied to register. */ p = emit_mr (prefix, opcode + (bn << 3), p); } else ill_op (); return p; } static const char * emit_jpcc (char prefix, char opcode, const char * args) { char cc; const char *p; p = parse_cc (args, & cc); if (p && *p++ == ',') p = emit_call (0, opcode + cc, p); else p = (prefix == (char)0xC3) ? emit_jp (0xE9, prefix, args) : emit_call (0, prefix, args); return p; } static const char * emit_jrcc (char prefix, char opcode, const char * args) { char cc; const char *p; p = parse_cc (args, &cc); if (p && *p++ == ',') { if (cc > (3 << 3)) error (_("condition code invalid for jr")); else p = emit_jr (0, opcode + cc, p); } else p = emit_jr (0, prefix, args); return p; } static const char * emit_ex (char prefix_in ATTRIBUTE_UNUSED, char opcode_in ATTRIBUTE_UNUSED, const char * args) { expressionS op; const char * p; char prefix, opcode; p = parse_exp_not_indexed (args, &op); p = skip_space (p); if (*p++ != ',') { error (_("bad instruction syntax")); return p; } prefix = opcode = 0; if (op.X_op == O_register) switch (op.X_add_number | (op.X_md ? 0x8000 : 0)) { case REG_AF: if (TOLOWER (*p++) == 'a' && TOLOWER (*p++) == 'f') { /* The scrubber changes '\'' to '`' in this context. */ if (*p == '`') ++p; opcode = 0x08; } break; case REG_DE: if (TOLOWER (*p++) == 'h' && TOLOWER (*p++) == 'l') opcode = 0xEB; break; case REG_SP|0x8000: p = parse_exp (p, & op); if (op.X_op == O_register && op.X_md == 0 && (op.X_add_number & ~R_INDEX) == REG_HL) { opcode = 0xE3; if (R_INDEX & op.X_add_number) prefix = (R_IX & op.X_add_number) ? 0xDD : 0xFD; } break; } if (opcode) emit_insn (prefix, opcode, p); else ill_op (); return p; } static const char * emit_in (char prefix ATTRIBUTE_UNUSED, char opcode ATTRIBUTE_UNUSED, const char * args) { expressionS reg, port; const char *p; char *q; p = parse_exp (args, ®); if (*p++ != ',') { error (_("bad instruction syntax")); return p; } p = parse_exp (p, &port); if (reg.X_md == 0 && reg.X_op == O_register && (reg.X_add_number <= 7 || reg.X_add_number == REG_F) && (port.X_md)) { if (port.X_op != O_md1 && port.X_op != O_register) { if (REG_A == reg.X_add_number) { q = frag_more (1); *q = 0xDB; emit_byte (&port, BFD_RELOC_8); } else ill_op (); } else { if (port.X_add_number == REG_C) { if (reg.X_add_number == REG_F) check_mach (INS_UNDOC); else { q = frag_more (2); *q++ = 0xED; *q = 0x40|((reg.X_add_number&7)<<3); } } else ill_op (); } } else ill_op (); return p; } static const char * emit_out (char prefix ATTRIBUTE_UNUSED, char opcode ATTRIBUTE_UNUSED, const char * args) { expressionS reg, port; const char *p; char *q; p = parse_exp (args, & port); if (*p++ != ',') { error (_("bad instruction syntax")); return p; } p = parse_exp (p, ®); if (!port.X_md) { ill_op (); return p; } /* Allow "out (c), 0" as unportable instruction. */ if (reg.X_op == O_constant && reg.X_add_number == 0) { check_mach (INS_UNPORT); reg.X_op = O_register; reg.X_add_number = 6; } if (reg.X_md || reg.X_op != O_register || reg.X_add_number > 7) ill_op (); else if (port.X_op != O_register && port.X_op != O_md1) { if (REG_A == reg.X_add_number) { q = frag_more (1); *q = 0xD3; emit_byte (&port, BFD_RELOC_8); } else ill_op (); } else { if (REG_C == port.X_add_number) { q = frag_more (2); *q++ = 0xED; *q = 0x41 | (reg.X_add_number << 3); } else ill_op (); } return p; } static const char * emit_rst (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args) { expressionS addr; const char *p; char *q; p = parse_exp_not_indexed (args, &addr); if (addr.X_op != O_constant) { error ("rst needs constant address"); return p; } if (addr.X_add_number & ~(7 << 3)) ill_op (); else { q = frag_more (1); *q = opcode + (addr.X_add_number & (7 << 3)); } return p; } static void emit_ldxhl (char prefix, char opcode, expressionS *src, expressionS *d) { char *q; if (src->X_md) ill_op (); else { if (src->X_op == O_register) { if (src->X_add_number>7) ill_op (); if (prefix) { q = frag_more (2); *q++ = prefix; } else q = frag_more (1); *q = opcode + src->X_add_number; if (d) emit_byte (d, BFD_RELOC_Z80_DISP8); } else { if (prefix) { q = frag_more (2); *q++ = prefix; } else q = frag_more (1); *q = opcode^0x46; if (d) emit_byte (d, BFD_RELOC_Z80_DISP8); emit_byte (src, BFD_RELOC_8); } } } static void emit_ldreg (int dest, expressionS * src) { char *q; int rnum; switch (dest) { /* 8 Bit ld group: */ case REG_I: case REG_R: if (src->X_md == 0 && src->X_op == O_register && src->X_add_number == REG_A) { q = frag_more (2); *q++ = 0xED; *q = (dest == REG_I) ? 0x47 : 0x4F; } else ill_op (); break; case REG_A: if ((src->X_md) && src->X_op != O_register && src->X_op != O_md1) { q = frag_more (1); *q = 0x3A; emit_word (src); break; } if ((src->X_md) && src->X_op == O_register && (src->X_add_number == REG_BC || src->X_add_number == REG_DE)) { q = frag_more (1); *q = 0x0A + ((src->X_add_number & 1) << 4); break; } if ((!src->X_md) && src->X_op == O_register && (src->X_add_number == REG_R || src->X_add_number == REG_I)) { q = frag_more (2); *q++ = 0xED; *q = (src->X_add_number == REG_I) ? 0x57 : 0x5F; break; } /* Fall through. */ case REG_B: case REG_C: case REG_D: case REG_E: emit_sx (0, 0x40 + (dest << 3), src); break; case REG_H: case REG_L: if ((src->X_md == 0) && (src->X_op == O_register) && (src->X_add_number & R_INDEX)) ill_op (); else emit_sx (0, 0x40 + (dest << 3), src); break; case R_IX | REG_H: case R_IX | REG_L: case R_IY | REG_H: case R_IY | REG_L: if (src->X_md) { ill_op (); break; } check_mach (INS_UNDOC); if (src-> X_op == O_register) { rnum = src->X_add_number; if ((rnum & ~R_INDEX) < 8 && ((rnum & R_INDEX) == (dest & R_INDEX) || ( (rnum & ~R_INDEX) != REG_H && (rnum & ~R_INDEX) != REG_L))) { q = frag_more (2); *q++ = (dest & R_IX) ? 0xDD : 0xFD; *q = 0x40 + ((dest & 0x07) << 3) + (rnum & 7); } else ill_op (); } else { q = frag_more (2); *q++ = (dest & R_IX) ? 0xDD : 0xFD; *q = 0x06 + ((dest & 0x07) << 3); emit_byte (src, BFD_RELOC_8); } break; /* 16 Bit ld group: */ case REG_SP: if (src->X_md == 0 && src->X_op == O_register && REG_HL == (src->X_add_number &~ R_INDEX)) { q = frag_more ((src->X_add_number & R_INDEX) ? 2 : 1); if (src->X_add_number & R_INDEX) *q++ = (src->X_add_number & R_IX) ? 0xDD : 0xFD; *q = 0xF9; break; } /* Fall through. */ case REG_BC: case REG_DE: if (src->X_op == O_register || src->X_op == O_md1) ill_op (); q = frag_more (src->X_md ? 2 : 1); if (src->X_md) { *q++ = 0xED; *q = 0x4B + ((dest & 3) << 4); } else *q = 0x01 + ((dest & 3) << 4); emit_word (src); break; case REG_HL: case REG_HL | R_IX: case REG_HL | R_IY: if (src->X_op == O_register || src->X_op == O_md1) ill_op (); q = frag_more ((dest & R_INDEX) ? 2 : 1); if (dest & R_INDEX) * q ++ = (dest & R_IX) ? 0xDD : 0xFD; *q = (src->X_md) ? 0x2A : 0x21; emit_word (src); break; case REG_AF: case REG_F: ill_op (); break; default: abort (); } } static const char * emit_ld (char prefix_in ATTRIBUTE_UNUSED, char opcode_in ATTRIBUTE_UNUSED, const char * args) { expressionS dst, src; const char *p; char *q; char prefix, opcode; p = parse_exp (args, &dst); if (*p++ != ',') error (_("bad instruction syntax")); p = parse_exp (p, &src); switch (dst.X_op) { case O_md1: { expressionS dst_offset = dst; dst_offset.X_op = O_symbol; dst_offset.X_add_number = 0; emit_ldxhl ((dst.X_add_number & R_IX) ? 0xDD : 0xFD, 0x70, &src, &dst_offset); } break; case O_register: if (dst.X_md) { switch (dst.X_add_number) { case REG_BC: case REG_DE: if (src.X_md == 0 && src.X_op == O_register && src.X_add_number == REG_A) { q = frag_more (1); *q = 0x02 + ( (dst.X_add_number & 1) << 4); } else ill_op (); break; case REG_HL: emit_ldxhl (0, 0x70, &src, NULL); break; default: ill_op (); } } else emit_ldreg (dst.X_add_number, &src); break; default: if (src.X_md != 0 || src.X_op != O_register) ill_op (); prefix = opcode = 0; switch (src.X_add_number) { case REG_A: opcode = 0x32; break; case REG_BC: case REG_DE: case REG_SP: prefix = 0xED; opcode = 0x43 + ((src.X_add_number&3)<<4); break; case REG_HL: opcode = 0x22; break; case REG_HL|R_IX: prefix = 0xDD; opcode = 0x22; break; case REG_HL|R_IY: prefix = 0xFD; opcode = 0x22; break; } if (opcode) { q = frag_more (prefix?2:1); if (prefix) *q++ = prefix; *q = opcode; emit_word (&dst); } else ill_op (); } return p; } static void emit_data (int size ATTRIBUTE_UNUSED) { const char *p, *q; char *u, quote; int cnt; expressionS exp; if (is_it_end_of_statement ()) { demand_empty_rest_of_line (); return; } p = skip_space (input_line_pointer); do { if (*p == '\"' || *p == '\'') { for (quote = *p, q = ++p, cnt = 0; *p && quote != *p; ++p, ++cnt) ; u = frag_more (cnt); memcpy (u, q, cnt); if (!*p) as_warn (_("unterminated string")); else p = skip_space (p+1); } else { p = parse_exp (p, &exp); if (exp.X_op == O_md1 || exp.X_op == O_register) { ill_op (); break; } if (exp.X_md) as_warn (_("parentheses ignored")); emit_byte (&exp, BFD_RELOC_8); p = skip_space (p); } } while (*p++ == ',') ; input_line_pointer = (char *)(p-1); } static const char * emit_mulub (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args) { const char *p; p = skip_space (args); if (TOLOWER (*p++) != 'a' || *p++ != ',') ill_op (); else { char *q, reg; reg = TOLOWER (*p++); switch (reg) { case 'b': case 'c': case 'd': case 'e': check_mach (INS_R800); if (!*skip_space (p)) { q = frag_more (2); *q++ = prefix; *q = opcode + ((reg - 'b') << 3); break; } default: ill_op (); } } return p; } static const char * emit_muluw (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args) { const char *p; p = skip_space (args); if (TOLOWER (*p++) != 'h' || TOLOWER (*p++) != 'l' || *p++ != ',') ill_op (); else { expressionS reg; char *q; p = parse_exp (p, & reg); if ((!reg.X_md) && reg.X_op == O_register) switch (reg.X_add_number) { case REG_BC: case REG_SP: check_mach (INS_R800); q = frag_more (2); *q++ = prefix; *q = opcode + ((reg.X_add_number & 3) << 4); break; default: ill_op (); } } return p; } /* Port specific pseudo ops. */ const pseudo_typeS md_pseudo_table[] = { { "db" , emit_data, 1}, { "d24", cons, 3}, { "d32", cons, 4}, { "def24", cons, 3}, { "def32", cons, 4}, { "defb", emit_data, 1}, { "defs", s_space, 1}, /* Synonym for ds on some assemblers. */ { "defw", cons, 2}, { "ds", s_space, 1}, /* Fill with bytes rather than words. */ { "dw", cons, 2}, { "psect", obj_coff_section, 0}, /* TODO: Translate attributes. */ { "set", 0, 0}, /* Real instruction on z80. */ { NULL, 0, 0 } } ; static table_t instab[] = { { "adc", 0x88, 0x4A, emit_adc }, { "add", 0x80, 0x09, emit_add }, { "and", 0x00, 0xA0, emit_s }, { "bit", 0xCB, 0x40, emit_bit }, { "call", 0xCD, 0xC4, emit_jpcc }, { "ccf", 0x00, 0x3F, emit_insn }, { "cp", 0x00, 0xB8, emit_s }, { "cpd", 0xED, 0xA9, emit_insn }, { "cpdr", 0xED, 0xB9, emit_insn }, { "cpi", 0xED, 0xA1, emit_insn }, { "cpir", 0xED, 0xB1, emit_insn }, { "cpl", 0x00, 0x2F, emit_insn }, { "daa", 0x00, 0x27, emit_insn }, { "dec", 0x0B, 0x05, emit_incdec }, { "di", 0x00, 0xF3, emit_insn }, { "djnz", 0x00, 0x10, emit_jr }, { "ei", 0x00, 0xFB, emit_insn }, { "ex", 0x00, 0x00, emit_ex}, { "exx", 0x00, 0xD9, emit_insn }, { "halt", 0x00, 0x76, emit_insn }, { "im", 0xED, 0x46, emit_im }, { "in", 0x00, 0x00, emit_in }, { "inc", 0x03, 0x04, emit_incdec }, { "ind", 0xED, 0xAA, emit_insn }, { "indr", 0xED, 0xBA, emit_insn }, { "ini", 0xED, 0xA2, emit_insn }, { "inir", 0xED, 0xB2, emit_insn }, { "jp", 0xC3, 0xC2, emit_jpcc }, { "jr", 0x18, 0x20, emit_jrcc }, { "ld", 0x00, 0x00, emit_ld }, { "ldd", 0xED, 0xA8, emit_insn }, { "lddr", 0xED, 0xB8, emit_insn }, { "ldi", 0xED, 0xA0, emit_insn }, { "ldir", 0xED, 0xB0, emit_insn }, { "mulub", 0xED, 0xC5, emit_mulub }, /* R800 only. */ { "muluw", 0xED, 0xC3, emit_muluw }, /* R800 only. */ { "neg", 0xed, 0x44, emit_insn }, { "nop", 0x00, 0x00, emit_insn }, { "or", 0x00, 0xB0, emit_s }, { "otdr", 0xED, 0xBB, emit_insn }, { "otir", 0xED, 0xB3, emit_insn }, { "out", 0x00, 0x00, emit_out }, { "outd", 0xED, 0xAB, emit_insn }, { "outi", 0xED, 0xA3, emit_insn }, { "pop", 0x00, 0xC1, emit_pop }, { "push", 0x00, 0xC5, emit_pop }, { "res", 0xCB, 0x80, emit_bit }, { "ret", 0xC9, 0xC0, emit_retcc }, { "reti", 0xED, 0x4D, emit_insn }, { "retn", 0xED, 0x45, emit_insn }, { "rl", 0xCB, 0x10, emit_mr }, { "rla", 0x00, 0x17, emit_insn }, { "rlc", 0xCB, 0x00, emit_mr }, { "rlca", 0x00, 0x07, emit_insn }, { "rld", 0xED, 0x6F, emit_insn }, { "rr", 0xCB, 0x18, emit_mr }, { "rra", 0x00, 0x1F, emit_insn }, { "rrc", 0xCB, 0x08, emit_mr }, { "rrca", 0x00, 0x0F, emit_insn }, { "rrd", 0xED, 0x67, emit_insn }, { "rst", 0x00, 0xC7, emit_rst}, { "sbc", 0x98, 0x42, emit_adc }, { "scf", 0x00, 0x37, emit_insn }, { "set", 0xCB, 0xC0, emit_bit }, { "sla", 0xCB, 0x20, emit_mr }, { "sli", 0xCB, 0x30, emit_mr }, { "sll", 0xCB, 0x30, emit_mr }, { "sra", 0xCB, 0x28, emit_mr }, { "srl", 0xCB, 0x38, emit_mr }, { "sub", 0x00, 0x90, emit_s }, { "xor", 0x00, 0xA8, emit_s }, } ; void md_assemble (char* str) { const char *p; char * old_ptr; int i; table_t *insp; err_flag = 0; old_ptr = input_line_pointer; p = skip_space (str); for (i = 0; (i < BUFLEN) && (ISALPHA (*p));) buf[i++] = TOLOWER (*p++); if (i == BUFLEN) { buf[BUFLEN-3] = buf[BUFLEN-2] = '.'; /* Mark opcode as abbreviated. */ buf[BUFLEN-1] = 0; as_bad (_("Unknown instruction '%s'"), buf); } else if ((*p) && (!ISSPACE (*p))) as_bad (_("syntax error")); else { buf[i] = 0; p = skip_space (p); key = buf; insp = bsearch (&key, instab, ARRAY_SIZE (instab), sizeof (instab[0]), key_cmp); if (!insp) as_bad (_("Unknown instruction '%s'"), buf); else { p = insp->fp (insp->prefix, insp->opcode, p); p = skip_space (p); if ((!err_flag) && *p) as_bad (_("junk at end of line, first unrecognized character is `%c'"), *p); } } input_line_pointer = old_ptr; } void md_apply_fix (fixS * fixP, valueT* valP, segT seg ATTRIBUTE_UNUSED) { long val = * (long *) valP; char *p_lit = fixP->fx_where + fixP->fx_frag->fr_literal; switch (fixP->fx_r_type) { case BFD_RELOC_8_PCREL: if (fixP->fx_addsy) { fixP->fx_no_overflow = 1; fixP->fx_done = 0; } else { fixP->fx_no_overflow = (-128 <= val && val < 128); if (!fixP->fx_no_overflow) as_bad_where (fixP->fx_file, fixP->fx_line, _("relative jump out of range")); *p_lit++ = val; fixP->fx_done = 1; } break; case BFD_RELOC_Z80_DISP8: if (fixP->fx_addsy) { fixP->fx_no_overflow = 1; fixP->fx_done = 0; } else { fixP->fx_no_overflow = (-128 <= val && val < 128); if (!fixP->fx_no_overflow) as_bad_where (fixP->fx_file, fixP->fx_line, _("index offset out of range")); *p_lit++ = val; fixP->fx_done = 1; } break; case BFD_RELOC_8: if (val > 255 || val < -128) as_warn_where (fixP->fx_file, fixP->fx_line, _("overflow")); *p_lit++ = val; fixP->fx_no_overflow = 1; if (fixP->fx_addsy == NULL) fixP->fx_done = 1; break; case BFD_RELOC_16: *p_lit++ = val; *p_lit++ = (val >> 8); fixP->fx_no_overflow = 1; if (fixP->fx_addsy == NULL) fixP->fx_done = 1; break; case BFD_RELOC_24: /* Def24 may produce this. */ *p_lit++ = val; *p_lit++ = (val >> 8); *p_lit++ = (val >> 16); fixP->fx_no_overflow = 1; if (fixP->fx_addsy == NULL) fixP->fx_done = 1; break; case BFD_RELOC_32: /* Def32 and .long may produce this. */ *p_lit++ = val; *p_lit++ = (val >> 8); *p_lit++ = (val >> 16); *p_lit++ = (val >> 24); if (fixP->fx_addsy == NULL) fixP->fx_done = 1; break; default: printf (_("md_apply_fix: unknown r_type 0x%x\n"), fixP->fx_r_type); abort (); } } /* GAS will call this to generate a reloc. GAS will pass the resulting reloc to `bfd_install_relocation'. This currently works poorly, as `bfd_install_relocation' often does the wrong thing, and instances of `tc_gen_reloc' have been written to work around the problems, which in turns makes it difficult to fix `bfd_install_relocation'. */ /* If while processing a fixup, a reloc really needs to be created then it is done here. */ arelent * tc_gen_reloc (asection *seg ATTRIBUTE_UNUSED , fixS *fixp) { arelent *reloc; if (! bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type)) { as_bad_where (fixp->fx_file, fixp->fx_line, _("reloc %d not supported by object file format"), (int) fixp->fx_r_type); return NULL; } reloc = xmalloc (sizeof (arelent)); reloc->sym_ptr_ptr = xmalloc (sizeof (asymbol *)); *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); reloc->address = fixp->fx_frag->fr_address + fixp->fx_where; reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type); reloc->addend = fixp->fx_offset; return reloc; }