X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=gas%2Fconfig%2Ftc-z80.c;h=029513a26143e053e5805fd2a215ed4f3ac271da;hb=24cc21fe5b8a88dc1ecde9006ece10145fef1035;hp=d93c155d4fb331b5ac2c9be00e37f9931cc9817d;hpb=25045f79229a800bcef1027a9cacea8414c00848;p=binutils-gdb.git diff --git a/gas/config/tc-z80.c b/gas/config/tc-z80.c index d93c155d4fb..029513a2614 100644 --- a/gas/config/tc-z80.c +++ b/gas/config/tc-z80.c @@ -1,5 +1,5 @@ -/* tc-z80.c -- Assemble code for the Zilog Z80 and ASCII R800 - Copyright 2005, 2006, 2007, 2008, 2009, 2012 Free Software Foundation, Inc. +/* tc-z80.c -- Assemble code for the Zilog Z80, Z180, EZ80 and ASCII R800 + Copyright (C) 2005-2022 Free Software Foundation, Inc. Contributed by Arnold Metselaar This file is part of GAS, the GNU Assembler. @@ -22,38 +22,83 @@ #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[] = "RrFf\0"; +const char FLT_CHARS[] = "RrDdFfSsHh\0"; /* For machine specific options. */ const char * md_shortopts = ""; /* None yet. */ enum options { - OPTION_MACH_Z80 = OPTION_MD_BASE, + 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_MACH_FUP, + OPTION_FP_SINGLE_FORMAT, + OPTION_FP_DOUBLE_FORMAT, + OPTION_COMPAT_LL_PREFIX, + OPTION_COMPAT_COLONLESS, + OPTION_COMPAT_SDCC }; -#define INS_Z80 1 -#define INS_UNDOC 2 -#define INS_UNPORT 4 -#define INS_R800 8 +#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) 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 }, @@ -76,46 +121,258 @@ 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; +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, char* arg ATTRIBUTE_UNUSED) +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: - ins_ok &= ~INS_R800; - ins_err |= INS_R800; + setup_march ("z80", & ins_ok, & ins_err, & cpu_mode); break; case OPTION_MACH_R800: - ins_ok = INS_Z80 | INS_UNDOC | INS_R800; - ins_err = INS_UNPORT; + setup_march ("r800", & ins_ok, & ins_err, & cpu_mode); break; - case OPTION_MACH_IUD: - ins_ok |= INS_UNDOC; - ins_err &= ~INS_UNDOC; + case OPTION_MACH_Z180: + setup_march ("z180", & ins_ok, & ins_err, & cpu_mode); break; - case OPTION_MACH_IUP: - ins_ok |= INS_UNDOC | INS_UNPORT; - ins_err &= ~(INS_UNDOC | INS_UNPORT); + 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: - if ((ins_ok & INS_R800) == 0) - { - ins_ok &= ~(INS_UNDOC|INS_UNPORT); - ins_err &= ~INS_UNDOC; - } + case OPTION_MACH_IUD: + if ((ins_ok & INS_GBZ80) == 0) + { + ins_ok |= INS_UNDOC; + ins_err &= ~INS_UNDOC; + } break; case OPTION_MACH_WUP: - ins_ok &= ~INS_UNPORT; - ins_err &= ~(INS_UNDOC|INS_UNPORT); + 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) == 0) + if ((ins_ok & (INS_R800 | INS_GBZ80)) == 0) { ins_ok &= (INS_UNDOC | INS_UNPORT); ins_err |= INS_UNDOC | INS_UNPORT; @@ -125,6 +382,15 @@ md_parse_option (int c, char* arg ATTRIBUTE_UNUSED) 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; @@ -133,38 +399,41 @@ md_parse_option (int c, char* arg ATTRIBUTE_UNUSED) void md_show_usage (FILE * f) { - fprintf (f, "\n\ -CPU model/instruction set options:\n\ + 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\ - -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"); +Default: -march=z80+xyhl+infc\n")); } static symbolS * zero; struct reg_entry { - char* name; + const char* name; int number; + int isa; }; #define R_STACKABLE (0x80) #define R_ARITH (0x40) @@ -182,6 +451,7 @@ struct reg_entry #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) @@ -193,27 +463,28 @@ struct reg_entry 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 }, + {"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. */ @@ -226,11 +497,19 @@ md_begin (void) 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; @@ -240,16 +519,16 @@ md_begin (void) { for ( k = 0 ; regtable[i].name[k] ; ++k ) { - buf[k] = ( j & ( 1<e_flags = elf_flags; +*/ +} +#endif + static const char * skip_space (const char *s) { @@ -317,6 +609,7 @@ z80_start_line_hook (void) *p++ = buf[2]; break; } + /* Fall through. */ case '"': for (quote = *p++; quote != *p && '\n' != *p; ++p) /* No escapes. */ ; @@ -327,34 +620,53 @@ z80_start_line_hook (void) 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