/* tc-z80.c -- Assemble code for the Zilog Z80, Z180, EZ80 and ASCII R800 Copyright (C) 2005-2024 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" #include "elf/z80.h" #include "dwarf2dbg.h" #include "dw2gencfi.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[] = "RrDdFfSsHh\0"; /* For machine specific options. */ const char md_shortopts[] = ""; /* None yet. */ enum options { OPTION_MARCH = OPTION_MD_BASE, OPTION_MACH_Z80, OPTION_MACH_R800, OPTION_MACH_Z180, OPTION_MACH_EZ80_Z80, OPTION_MACH_EZ80_ADL, OPTION_MACH_INST, OPTION_MACH_NO_INST, OPTION_MACH_IUD, OPTION_MACH_WUD, OPTION_MACH_FUD, OPTION_MACH_IUP, OPTION_MACH_WUP, OPTION_MACH_FUP, OPTION_FP_SINGLE_FORMAT, OPTION_FP_DOUBLE_FORMAT, OPTION_COMPAT_LL_PREFIX, OPTION_COMPAT_COLONLESS, OPTION_COMPAT_SDCC }; #define INS_Z80 (1 << 0) #define INS_R800 (1 << 1) #define INS_GBZ80 (1 << 2) #define INS_Z180 (1 << 3) #define INS_EZ80 (1 << 4) #define INS_Z80N (1 << 5) #define INS_MARCH_MASK 0xffff #define INS_IDX_HALF (1 << 16) #define INS_IN_F_C (1 << 17) #define INS_OUT_C_0 (1 << 18) #define INS_SLI (1 << 19) #define INS_ROT_II_LD (1 << 20) /* instructions like SLA (ii+d),r; which is: LD r,(ii+d); SLA r; LD (ii+d),r */ #define INS_TUNE_MASK 0xffff0000 #define INS_NOT_GBZ80 (INS_Z80 | INS_Z180 | INS_R800 | INS_EZ80 | INS_Z80N) #define INS_ALL 0 #define INS_UNDOC (INS_IDX_HALF | INS_IN_F_C) #define INS_UNPORT (INS_OUT_C_0 | INS_SLI | INS_ROT_II_LD) const struct option md_longopts[] = { { "march", required_argument, NULL, OPTION_MARCH}, { "z80", no_argument, NULL, OPTION_MACH_Z80}, { "r800", no_argument, NULL, OPTION_MACH_R800}, { "z180", no_argument, NULL, OPTION_MACH_Z180}, { "ez80", no_argument, NULL, OPTION_MACH_EZ80_Z80}, { "ez80-adl", no_argument, NULL, OPTION_MACH_EZ80_ADL}, { "fp-s", required_argument, NULL, OPTION_FP_SINGLE_FORMAT}, { "fp-d", required_argument, NULL, OPTION_FP_DOUBLE_FORMAT}, { "strict", no_argument, NULL, OPTION_MACH_FUD}, { "full", no_argument, NULL, OPTION_MACH_IUP}, { "with-inst", required_argument, NULL, OPTION_MACH_INST}, { "Wnins", required_argument, NULL, OPTION_MACH_INST}, { "without-inst", required_argument, NULL, OPTION_MACH_NO_INST}, { "local-prefix", required_argument, NULL, OPTION_COMPAT_LL_PREFIX}, { "colonless", no_argument, NULL, OPTION_COMPAT_COLONLESS}, { "sdcc", no_argument, NULL, OPTION_COMPAT_SDCC}, { "Fins", required_argument, NULL, OPTION_MACH_NO_INST}, { "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 } } ; const 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_Z80 | INS_UNDOC); /* eZ80 CPU mode (ADL or Z80) */ static int cpu_mode = 0; /* 0 - Z80, 1 - ADL */ /* accept SDCC specific instruction encoding */ static int sdcc_compat = 0; /* accept colonless labels */ static int colonless_labels = 0; /* local label prefix (NULL - default) */ static const char *local_label_prefix = NULL; /* floating point support */ typedef const char *(*str_to_float_t)(char *litP, int *sizeP); static str_to_float_t str_to_float; static str_to_float_t str_to_double; /* mode of current instruction */ #define INST_MODE_S 0 /* short data mode */ #define INST_MODE_IS 0 /* short instruction mode */ #define INST_MODE_L 2 /* long data mode */ #define INST_MODE_IL 1 /* long instruction mode */ #define INST_MODE_FORCED 4 /* CPU mode changed by instruction suffix*/ static char inst_mode; struct match_info { const char *name; int ins_ok; int ins_err; int cpu_mode; const char *comment; }; static const struct match_info match_cpu_table [] = { {"z80", INS_Z80, 0, 0, "Zilog Z80" }, {"ez80", INS_EZ80, 0, 0, "Zilog eZ80" }, {"gbz80", INS_GBZ80, INS_UNDOC|INS_UNPORT, 0, "GameBoy Z80" }, {"r800", INS_R800, INS_UNPORT, 0, "Ascii R800" }, {"z180", INS_Z180, INS_UNDOC|INS_UNPORT, 0, "Zilog Z180" }, {"z80n", INS_Z80N, 0, 0, "Z80 Next" } }; static const struct match_info match_ext_table [] = { {"full", INS_UNDOC|INS_UNPORT, 0, 0, "assemble all known instructions" }, {"adl", 0, 0, 1, "eZ80 ADL mode by default" }, {"xyhl", INS_IDX_HALF, 0, 0, "instructions with halves of index registers" }, {"infc", INS_IN_F_C, 0, 0, "instruction IN F,(C)" }, {"outc0", INS_OUT_C_0, 0, 0, "instruction OUT (C),0" }, {"sli", INS_SLI, 0, 0, "instruction known as SLI, SLL, or SL1" }, {"xdcb", INS_ROT_II_LD, 0, 0, "instructions like RL (IX+d),R (DD/FD CB dd oo)" } }; static int signed_overflow (signed long value, unsigned bitsize); static int unsigned_overflow (unsigned long value, unsigned bitsize); static int is_overflow (long value, unsigned bitsize); static void setup_march (const char *name, int *ok, int *err, int *mode) { unsigned i; size_t len = strcspn (name, "+-"); for (i = 0; i < ARRAY_SIZE (match_cpu_table); ++i) if (!strncasecmp (name, match_cpu_table[i].name, len) && strlen (match_cpu_table[i].name) == len) { *ok = match_cpu_table[i].ins_ok; *err = match_cpu_table[i].ins_err; *mode = match_cpu_table[i].cpu_mode; break; } if (i >= ARRAY_SIZE (match_cpu_table)) as_fatal (_("Invalid CPU is specified: %s"), name); while (name[len]) { name = &name[len + 1]; len = strcspn (name, "+-"); for (i = 0; i < ARRAY_SIZE (match_ext_table); ++i) if (!strncasecmp (name, match_ext_table[i].name, len) && strlen (match_ext_table[i].name) == len) { if (name[-1] == '+') { *ok |= match_ext_table[i].ins_ok; *err &= ~match_ext_table[i].ins_ok; *mode |= match_ext_table[i].cpu_mode; } else { *ok &= ~match_ext_table[i].ins_ok; *err |= match_ext_table[i].ins_ok; *mode &= ~match_ext_table[i].cpu_mode; } break; } if (i >= ARRAY_SIZE (match_ext_table)) as_fatal (_("Invalid EXTENSION is specified: %s"), name); } } static int setup_instruction (const char *inst, int *add, int *sub) { int n; if (!strcmp (inst, "idx-reg-halves")) n = INS_IDX_HALF; else if (!strcmp (inst, "sli")) n = INS_SLI; else if (!strcmp (inst, "op-ii-ld")) n = INS_ROT_II_LD; else if (!strcmp (inst, "in-f-c")) n = INS_IN_F_C; else if (!strcmp (inst, "out-c-0")) n = INS_OUT_C_0; else return 0; *add |= n; *sub &= ~n; return 1; } static const char * str_to_zeda32 (char *litP, int *sizeP); static const char * str_to_float48 (char *litP, int *sizeP); static const char * str_to_ieee754_h (char *litP, int *sizeP); static const char * str_to_ieee754_s (char *litP, int *sizeP); static const char * str_to_ieee754_d (char *litP, int *sizeP); static str_to_float_t get_str_to_float (const char *arg) { if (strcasecmp (arg, "zeda32") == 0) return str_to_zeda32; if (strcasecmp (arg, "math48") == 0) return str_to_float48; if (strcasecmp (arg, "half") != 0) return str_to_ieee754_h; if (strcasecmp (arg, "single") != 0) return str_to_ieee754_s; if (strcasecmp (arg, "double") != 0) return str_to_ieee754_d; if (strcasecmp (arg, "ieee754") == 0) as_fatal (_("invalid floating point numbers type `%s'"), arg); return NULL; } static int setup_instruction_list (const char *list, int *add, int *sub) { char buf[16]; const char *b; const char *e; int sz; int res = 0; for (b = list; *b != '\0';) { e = strchr (b, ','); if (e == NULL) sz = strlen (b); else sz = e - b; if (sz == 0 || sz >= (int)sizeof (buf)) { as_bad (_("invalid INST in command line: %s"), b); return 0; } memcpy (buf, b, sz); buf[sz] = '\0'; if (setup_instruction (buf, add, sub)) res++; else { as_bad (_("invalid INST in command line: %s"), buf); return 0; } b = &b[sz]; if (*b == ',') ++b; } return res; } int md_parse_option (int c, const char* arg) { switch (c) { default: return 0; case OPTION_MARCH: setup_march (arg, & ins_ok, & ins_err, & cpu_mode); break; case OPTION_MACH_Z80: setup_march ("z80", & ins_ok, & ins_err, & cpu_mode); break; case OPTION_MACH_R800: setup_march ("r800", & ins_ok, & ins_err, & cpu_mode); break; case OPTION_MACH_Z180: setup_march ("z180", & ins_ok, & ins_err, & cpu_mode); break; case OPTION_MACH_EZ80_Z80: setup_march ("ez80", & ins_ok, & ins_err, & cpu_mode); break; case OPTION_MACH_EZ80_ADL: setup_march ("ez80+adl", & ins_ok, & ins_err, & cpu_mode); break; case OPTION_FP_SINGLE_FORMAT: str_to_float = get_str_to_float (arg); break; case OPTION_FP_DOUBLE_FORMAT: str_to_double = get_str_to_float (arg); break; case OPTION_MACH_INST: if ((ins_ok & INS_GBZ80) == 0) return setup_instruction_list (arg, & ins_ok, & ins_err); break; case OPTION_MACH_NO_INST: if ((ins_ok & INS_GBZ80) == 0) return setup_instruction_list (arg, & ins_err, & ins_ok); break; case OPTION_MACH_WUD: case OPTION_MACH_IUD: if ((ins_ok & INS_GBZ80) == 0) { ins_ok |= INS_UNDOC; ins_err &= ~INS_UNDOC; } break; case OPTION_MACH_WUP: case OPTION_MACH_IUP: if ((ins_ok & INS_GBZ80) == 0) { ins_ok |= INS_UNDOC | INS_UNPORT; ins_err &= ~(INS_UNDOC | INS_UNPORT); } break; case OPTION_MACH_FUD: if ((ins_ok & (INS_R800 | INS_GBZ80)) == 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; case OPTION_COMPAT_LL_PREFIX: local_label_prefix = (arg && *arg) ? arg : NULL; break; case OPTION_COMPAT_SDCC: sdcc_compat = 1; break; case OPTION_COMPAT_COLONLESS: colonless_labels = 1; break; } return 1; } void md_show_usage (FILE * f) { unsigned i; fprintf (f, _("\n\ CPU model options:\n\ -march=CPU[+EXT...][-EXT...]\n\ \t\t\t generate code for CPU, where CPU is one of:\n")); for (i = 0; i < ARRAY_SIZE(match_cpu_table); ++i) fprintf (f, " %-8s\t\t %s\n", match_cpu_table[i].name, match_cpu_table[i].comment); fprintf (f, _("And EXT is combination (+EXT - add, -EXT - remove) of:\n")); for (i = 0; i < ARRAY_SIZE(match_ext_table); ++i) fprintf (f, " %-8s\t\t %s\n", match_ext_table[i].name, match_ext_table[i].comment); fprintf (f, _("\n\ Compatibility options:\n\ -local-prefix=TEXT\t treat labels prefixed by TEXT as local\n\ -colonless\t\t permit colonless labels\n\ -sdcc\t\t\t accept SDCC specific instruction syntax\n\ -fp-s=FORMAT\t\t set single precision FP numbers format\n\ -fp-d=FORMAT\t\t set double precision FP numbers format\n\ Where FORMAT one of:\n\ ieee754\t\t IEEE754 compatible (depends on directive)\n\ half\t\t\t IEEE754 half precision (16 bit)\n\ single\t\t IEEE754 single precision (32 bit)\n\ double\t\t IEEE754 double precision (64 bit)\n\ zeda32\t\t Zeda z80float library 32 bit format\n\ math48\t\t 48 bit format from Math48 library\n\ \n\ Default: -march=z80+xyhl+infc\n")); } static symbolS * zero; struct reg_entry { const char* name; int number; int isa; }; #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_MB (11) #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, INS_ALL }, {"af", REG_AF, INS_ALL }, {"b", REG_B, INS_ALL }, {"bc", REG_BC, INS_ALL }, {"c", REG_C, INS_ALL }, {"d", REG_D, INS_ALL }, {"de", REG_DE, INS_ALL }, {"e", REG_E, INS_ALL }, {"f", REG_F, INS_IN_F_C | INS_Z80N | INS_R800 }, {"h", REG_H, INS_ALL }, {"hl", REG_HL, INS_ALL }, {"i", REG_I, INS_NOT_GBZ80 }, {"ix", REG_IX, INS_NOT_GBZ80 }, {"ixh", REG_H | R_IX, INS_IDX_HALF | INS_EZ80 | INS_R800 | INS_Z80N }, {"ixl", REG_L | R_IX, INS_IDX_HALF | INS_EZ80 | INS_R800 | INS_Z80N }, {"iy", REG_IY, INS_NOT_GBZ80 }, {"iyh", REG_H | R_IY, INS_IDX_HALF | INS_EZ80 | INS_R800 | INS_Z80N }, {"iyl", REG_L | R_IY, INS_IDX_HALF | INS_EZ80 | INS_R800 | INS_Z80N }, {"l", REG_L, INS_ALL }, {"mb", REG_MB, INS_EZ80 }, {"r", REG_R, INS_NOT_GBZ80 }, {"sp", REG_SP, INS_ALL }, } ; #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]; memset (®, 0, sizeof (reg)); memset (&nul, 0, sizeof (nul)); if (ins_ok & INS_EZ80) /* if select EZ80 cpu then */ listing_lhs_width = 6; /* use 6 bytes per line in the listing */ 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 ) { if (regtable[i].isa && !(regtable[i].isa & ins_ok)) continue; reg.X_add_number = regtable[i].number; k = strlen ( regtable[i].name ); buf[k] = 0; if ( k+1 < BUFLEN ) { for ( j = ( 1<e_flags = elf_flags; */ } #endif static const char * skip_space (const char *s) { while (*s == ' ' || *s == '\t') ++s; return s; } /* A non-zero return-value causes a continue in the function read_a_source_file () in ../read.c. */ int z80_start_line_hook (void) { char *p, quote; char buf[4]; /* Convert one character constants. */ for (p = input_line_pointer; *p && *p != '\n'; ++p) { switch (*p) { case '\'': if (p[1] != 0 && p[1] != '\'' && p[2] == '\'') { snprintf (buf, 4, "%3d", (unsigned char)p[1]); *p++ = buf[0]; *p++ = buf[1]; *p++ = buf[2]; break; } /* Fall through. */ case '"': for (quote = *p++; quote != *p && '\n' != *p; ++p) /* No escapes. */ ; if (quote != *p) { as_bad (_("-- unterminated string")); ignore_rest_of_line (); return 1; } break; case '#': /* force to use next expression as immediate value in SDCC */ if (!sdcc_compat) break; if (ISSPACE(p[1]) && *skip_space (p + 1) == '(') { /* ld a,# (expr)... -> ld a,0+(expr)... */ *p++ = '0'; *p = '+'; } else /* ld a,#(expr)... -> ld a,+(expr); ld a,#expr -> ld a, expr */ *p = (p[1] == '(') ? '+' : ' '; break; } } /* Check for