| /* tc-tic54x.c -- Assembly code for the Texas Instruments TMS320C54X |
| Copyright (C) 1999-2021 Free Software Foundation, Inc. |
| Contributed by Timothy Wall (twall@cygnus.com) |
| |
| 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. */ |
| |
| /* Texas Instruments TMS320C54X machine specific gas. |
| Written by Timothy Wall (twall@alum.mit.edu). |
| |
| Valuable things to do: |
| Pipeline conflict warnings |
| We encode/decode "ld #_label, dp" differently in relocatable files |
| This means we're not compatible with TI output containing those |
| expressions. We store the upper nine bits; TI stores the lower nine |
| bits. How they recover the original upper nine bits is beyond me. |
| |
| Tests to add to expect testsuite: |
| '=' and '==' with .if, .elseif, and .break |
| |
| Incompatibilities (mostly trivial): |
| We don't allow ''' |
| We fill text section with zeroes instead of "nop"s |
| We don't convert '' or "" to a single instance |
| We don't convert '' to '\0' |
| We don't allow strings with .byte/.half/.short/.long |
| Probably details of the subsym stuff are different |
| TI sets labels to be data type 4 (T_INT); GAS uses T_NULL. |
| |
| COFF1 limits section names to 8 characters. |
| Some of the default behavior changed from COFF1 to COFF2. */ |
| |
| #include "as.h" |
| #include <limits.h> |
| #include "safe-ctype.h" |
| #include "sb.h" |
| #include "macro.h" |
| #include "subsegs.h" |
| #include "opcode/tic54x.h" |
| #include "obj-coff.h" |
| #include <math.h> |
| |
| |
| static struct stag |
| { |
| symbolS *sym; /* Symbol for this stag; value is offset. */ |
| const char *name; /* Shortcut to symbol name. */ |
| bfd_vma size; /* Size of struct/union. */ |
| int current_bitfield_offset; /* Temporary for tracking fields. */ |
| int is_union; |
| struct stag_field /* List of fields. */ |
| { |
| const char *name; |
| bfd_vma offset; /* Of start of this field. */ |
| int bitfield_offset; /* Of start of this field. */ |
| struct stag *stag; /* If field is struct/union. */ |
| struct stag_field *next; |
| } *field; |
| /* For nesting; used only in stag construction. */ |
| struct stag *inner; /* Enclosed .struct. */ |
| struct stag *outer; /* Enclosing .struct. */ |
| } *current_stag = NULL; |
| |
| #define MAX_LINE 256 /* Lines longer than this are truncated by TI's asm. */ |
| |
| typedef struct _tic54x_insn |
| { |
| const insn_template *tm; /* Opcode template. */ |
| |
| char mnemonic[MAX_LINE]; /* Opcode name/mnemonic. */ |
| char parmnemonic[MAX_LINE]; /* 2nd mnemonic of parallel insn. */ |
| |
| int opcount; |
| struct opstruct |
| { |
| char buf[MAX_LINE]; |
| enum optype type; |
| expressionS exp; |
| } operands[MAX_OPERANDS]; |
| |
| int paropcount; |
| struct opstruct paroperands[MAX_OPERANDS]; |
| |
| int is_lkaddr; |
| int lkoperand; |
| int words; /* Size of insn in 16-bit words. */ |
| int using_default_dst; /* Do we need to explicitly set an |
| omitted OP_DST operand? */ |
| struct |
| { |
| unsigned short word; /* Final encoded opcode data. */ |
| int unresolved; |
| int r_nchars; /* Relocation size. */ |
| bfd_reloc_code_real_type r_type; /* Relocation type. */ |
| expressionS addr_expr; /* Storage for unresolved expressions. */ |
| } opcode[3]; |
| } tic54x_insn; |
| |
| enum cpu_version |
| { |
| VNONE = 0, V541 = 1, V542 = 2, V543 = 3, V545 = 5, V548 = 8, V549 = 9, |
| V545LP = 15, V546LP = 16 |
| }; |
| |
| enum address_mode |
| { |
| c_mode, /* 16-bit addresses. */ |
| far_mode /* >16-bit addresses. */ |
| }; |
| |
| static segT stag_saved_seg; |
| static subsegT stag_saved_subseg; |
| |
| const char comment_chars[] = ";"; |
| const char line_comment_chars[] = ";*#"; /* At column zero only. */ |
| const char line_separator_chars[] = ""; /* Not permitted. */ |
| |
| int emitting_long = 0; |
| |
| /* Characters which indicate that this is a floating point constant. */ |
| const char FLT_CHARS[] = "fF"; |
| |
| /* Characters that can be used to separate mantissa from exp in FP |
| nums. */ |
| const char EXP_CHARS[] = "eE"; |
| |
| const char *md_shortopts = ""; |
| |
| #define OPTION_ADDRESS_MODE (OPTION_MD_BASE) |
| #define OPTION_CPU_VERSION (OPTION_ADDRESS_MODE + 1) |
| #define OPTION_COFF_VERSION (OPTION_CPU_VERSION + 1) |
| #define OPTION_STDERR_TO_FILE (OPTION_COFF_VERSION + 1) |
| |
| struct option md_longopts[] = |
| { |
| { "mfar-mode", no_argument, NULL, OPTION_ADDRESS_MODE }, |
| { "mf", no_argument, NULL, OPTION_ADDRESS_MODE }, |
| { "mcpu", required_argument, NULL, OPTION_CPU_VERSION }, |
| { "merrors-to-file", required_argument, NULL, OPTION_STDERR_TO_FILE }, |
| { "me", required_argument, NULL, OPTION_STDERR_TO_FILE }, |
| { NULL, no_argument, NULL, 0}, |
| }; |
| |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| static int assembly_begun = 0; |
| /* Addressing mode is not entirely implemented; the latest rev of the Other |
| assembler doesn't seem to make any distinction whatsoever; all relocations |
| are stored as extended relocations. Older versions used REL16 vs RELEXT16, |
| but now it seems all relocations are RELEXT16. We use all RELEXT16. |
| |
| The cpu version is kind of a waste of time as well. There is one |
| instruction (RND) for LP devices only, and several for devices with |
| extended addressing only. We include it for compatibility. */ |
| static enum address_mode amode = c_mode; |
| static enum cpu_version cpu = VNONE; |
| |
| /* Include string substitutions in listing? */ |
| static int listing_sslist = 0; |
| |
| /* Did we do subsym substitutions on the line? */ |
| static int substitution_line = 0; |
| |
| /* Last label seen. */ |
| static symbolS *last_label_seen = NULL; |
| |
| /* This ensures that all new labels are unique. */ |
| static int local_label_id; |
| |
| static htab_t subsym_recurse_hash; /* Prevent infinite recurse. */ |
| static htab_t math_hash; /* Built-in math functions. */ |
| /* Allow maximum levels of macro nesting; level 0 is the main substitution |
| symbol table. The other assembler only does 32 levels, so there! */ |
| #define MAX_SUBSYM_HASH 100 |
| static htab_t subsym_hash[MAX_SUBSYM_HASH]; |
| |
| /* Keep track of local labels so we can substitute them before GAS sees them |
| since macros use their own 'namespace' for local labels, use a separate hash |
| |
| We do our own local label handling 'cuz it's subtly different from the |
| stock GAS handling. |
| |
| We use our own macro nesting counter, since GAS overloads it when expanding |
| other things (like conditionals and repeat loops). */ |
| static int macro_level = 0; |
| static htab_t local_label_hash[100]; |
| /* Keep track of struct/union tags. */ |
| static htab_t stag_hash; |
| static htab_t op_hash; |
| static htab_t parop_hash; |
| static htab_t reg_hash; |
| static htab_t mmreg_hash; |
| static htab_t cc_hash; |
| static htab_t cc2_hash; |
| static htab_t cc3_hash; |
| static htab_t sbit_hash; |
| static htab_t misc_symbol_hash; |
| |
| /* Only word (et al.), align, or conditionals are allowed within |
| .struct/.union. */ |
| #define ILLEGAL_WITHIN_STRUCT() \ |
| do \ |
| if (current_stag != NULL) \ |
| { \ |
| as_bad (_("pseudo-op illegal within .struct/.union")); \ |
| return; \ |
| } \ |
| while (0) |
| |
| |
| static void subsym_create_or_replace (char *, char *); |
| static char *subsym_lookup (char *, int); |
| static char *subsym_substitute (char *, int); |
| |
| |
| void |
| md_show_usage (FILE *stream) |
| { |
| fprintf (stream, _("C54x-specific command line options:\n")); |
| fprintf (stream, _("-mfar-mode | -mf Use extended addressing\n")); |
| fprintf (stream, _("-mcpu=<CPU version> Specify the CPU version\n")); |
| fprintf (stream, _("-merrors-to-file <filename>\n")); |
| fprintf (stream, _("-me <filename> Redirect errors to a file\n")); |
| } |
| |
| /* Output a single character (upper octet is zero). */ |
| |
| static void |
| tic54x_emit_char (char c) |
| { |
| expressionS expn; |
| |
| expn.X_op = O_constant; |
| expn.X_add_number = c; |
| emit_expr (&expn, 2); |
| } |
| |
| /* Walk backwards in the frag chain. */ |
| |
| static fragS * |
| frag_prev (fragS *frag, segT seg) |
| { |
| segment_info_type *seginfo = seg_info (seg); |
| fragS *fragp; |
| |
| for (fragp = seginfo->frchainP->frch_root; fragp; fragp = fragp->fr_next) |
| if (fragp->fr_next == frag) |
| return fragp; |
| |
| return NULL; |
| } |
| |
| static fragS * |
| bit_offset_frag (fragS *frag, segT seg) |
| { |
| while (frag != NULL) |
| { |
| if (frag->fr_fix == 0 |
| && frag->fr_opcode == NULL |
| && frag->tc_frag_data == 0) |
| frag = frag_prev (frag, seg); |
| else |
| return frag; |
| } |
| return NULL; |
| } |
| |
| /* Return the number of bits allocated in the most recent word, or zero if |
| none. .field/.space/.bes may leave words partially allocated. */ |
| |
| static int |
| frag_bit_offset (fragS *frag, segT seg) |
| { |
| frag = bit_offset_frag (frag, seg); |
| |
| if (frag) |
| return frag->fr_opcode != NULL ? -1 : frag->tc_frag_data; |
| |
| return 0; |
| } |
| |
| /* Read an expression from a C string; returns a pointer past the end of the |
| expression. */ |
| |
| static char * |
| parse_expression (char *str, expressionS *expn) |
| { |
| char *s; |
| char *tmp; |
| |
| tmp = input_line_pointer; /* Save line pointer. */ |
| input_line_pointer = str; |
| expression (expn); |
| s = input_line_pointer; |
| input_line_pointer = tmp; /* Restore line pointer. */ |
| return s; /* Return pointer to where parsing stopped. */ |
| } |
| |
| /* .asg "character-string"|character-string, symbol |
| |
| .eval is the only pseudo-op allowed to perform arithmetic on substitution |
| symbols. all other use of symbols defined with .asg are currently |
| unsupported. */ |
| |
| static void |
| tic54x_asg (int x ATTRIBUTE_UNUSED) |
| { |
| int c; |
| char *name; |
| char *str; |
| int quoted = *input_line_pointer == '"'; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| if (quoted) |
| { |
| int len; |
| str = demand_copy_C_string (&len); |
| c = *input_line_pointer; |
| } |
| else |
| { |
| str = input_line_pointer; |
| while ((c = *input_line_pointer) != ',') |
| { |
| if (is_end_of_line[(unsigned char) c]) |
| break; |
| ++input_line_pointer; |
| } |
| *input_line_pointer = 0; |
| } |
| if (c != ',') |
| { |
| as_bad (_("Comma and symbol expected for '.asg STRING, SYMBOL'")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| ++input_line_pointer; |
| c = get_symbol_name (&name); /* Get terminator. */ |
| if (!ISALPHA (*name)) |
| { |
| as_bad (_("symbols assigned with .asg must begin with a letter")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| str = xstrdup (str); |
| name = xstrdup (name); |
| subsym_create_or_replace (name, str); |
| (void) restore_line_pointer (c); |
| demand_empty_rest_of_line (); |
| } |
| |
| /* .eval expression, symbol |
| There's something screwy about this. The other assembler sometimes does and |
| sometimes doesn't substitute symbols defined with .eval. |
| We'll put the symbols into the subsym table as well as the normal symbol |
| table, since that's what works best. */ |
| |
| static void |
| tic54x_eval (int x ATTRIBUTE_UNUSED) |
| { |
| char c; |
| int value; |
| char *name; |
| symbolS *symbolP; |
| char valuestr[32], *tmp; |
| int quoted; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| SKIP_WHITESPACE (); |
| |
| quoted = *input_line_pointer == '"'; |
| if (quoted) |
| ++input_line_pointer; |
| value = get_absolute_expression (); |
| if (quoted) |
| { |
| if (*input_line_pointer != '"') |
| { |
| as_bad (_("Unterminated string after absolute expression")); |
| ignore_rest_of_line (); |
| return; |
| } |
| ++input_line_pointer; |
| } |
| if (*input_line_pointer++ != ',') |
| { |
| as_bad (_("Comma and symbol expected for '.eval EXPR, SYMBOL'")); |
| ignore_rest_of_line (); |
| return; |
| } |
| c = get_symbol_name (&name); /* Get terminator. */ |
| name = xstrdup (name); |
| (void) restore_line_pointer (c); |
| |
| if (!ISALPHA (*name)) |
| { |
| as_bad (_("symbols assigned with .eval must begin with a letter")); |
| ignore_rest_of_line (); |
| return; |
| } |
| symbolP = symbol_new (name, absolute_section, &zero_address_frag, value); |
| SF_SET_LOCAL (symbolP); |
| symbol_table_insert (symbolP); |
| |
| /* The "other" assembler sometimes doesn't put .eval's in the subsym table |
| But since there's not written rule as to when, don't even bother trying |
| to match their behavior. */ |
| sprintf (valuestr, "%d", value); |
| tmp = xstrdup (valuestr); |
| subsym_create_or_replace (name, tmp); |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* .bss symbol, size [, [blocking flag] [, alignment flag] |
| |
| alignment is to a longword boundary; blocking is to 128-word boundary. |
| |
| 1) if there is a hole in memory, this directive should attempt to fill it |
| (not yet implemented). |
| |
| 2) if the blocking flag is not set, allocate at the current SPC |
| otherwise, check to see if the current SPC plus the space to be |
| allocated crosses the page boundary (128 words). |
| if there's not enough space, create a hole and align with the next page |
| boundary. |
| (not yet implemented). */ |
| |
| static void |
| tic54x_bss (int x ATTRIBUTE_UNUSED) |
| { |
| char c; |
| char *name; |
| char *p; |
| int words; |
| segT current_seg; |
| subsegT current_subseg; |
| symbolS *symbolP; |
| int block = 0; |
| int align = 0; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| current_seg = now_seg; /* Save current seg. */ |
| current_subseg = now_subseg; /* Save current subseg. */ |
| |
| c = get_symbol_name (&name); /* Get terminator. */ |
| if (c == '"') |
| c = * ++ input_line_pointer; |
| if (c != ',') |
| { |
| as_bad (_(".bss size argument missing\n")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| ++input_line_pointer; |
| words = get_absolute_expression (); |
| if (words < 0) |
| { |
| as_bad (_(".bss size %d < 0!"), words); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| if (*input_line_pointer == ',') |
| { |
| /* The blocking flag may be missing. */ |
| ++input_line_pointer; |
| if (*input_line_pointer != ',') |
| block = get_absolute_expression (); |
| else |
| block = 0; |
| |
| if (*input_line_pointer == ',') |
| { |
| ++input_line_pointer; |
| align = get_absolute_expression (); |
| } |
| else |
| align = 0; |
| } |
| else |
| block = align = 0; |
| |
| subseg_set (bss_section, 0); |
| symbolP = symbol_find_or_make (name); |
| |
| if (S_GET_SEGMENT (symbolP) == bss_section) |
| symbol_get_frag (symbolP)->fr_symbol = (symbolS *) NULL; |
| |
| symbol_set_frag (symbolP, frag_now); |
| p = frag_var (rs_org, 1, 1, (relax_substateT) 0, symbolP, |
| (offsetT) (words * OCTETS_PER_BYTE), (char *) 0); |
| *p = 0; /* Fill char. */ |
| |
| S_SET_SEGMENT (symbolP, bss_section); |
| |
| /* The symbol may already have been created with a preceding |
| ".globl" directive -- be careful not to step on storage class |
| in that case. Otherwise, set it to static. */ |
| if (S_GET_STORAGE_CLASS (symbolP) != C_EXT) |
| S_SET_STORAGE_CLASS (symbolP, C_STAT); |
| |
| if (align) |
| { |
| /* s_align eats end of line; restore it */ |
| s_align_bytes (4); |
| --input_line_pointer; |
| } |
| |
| if (block) |
| bss_section->flags |= SEC_TIC54X_BLOCK; |
| |
| subseg_set (current_seg, current_subseg); /* Restore current seg. */ |
| demand_empty_rest_of_line (); |
| } |
| |
| static void |
| stag_add_field_symbols (struct stag *stag, |
| const char *path, |
| bfd_vma base_offset, |
| symbolS *rootsym, |
| const char *root_stag_name) |
| { |
| char * prefix; |
| struct stag_field *field = stag->field; |
| |
| /* Construct a symbol for every field contained within this structure |
| including fields within structure fields. */ |
| prefix = concat (path, *path ? "." : "", NULL); |
| |
| while (field != NULL) |
| { |
| char *name = concat (prefix, field->name, NULL); |
| char *freename = name; |
| |
| if (rootsym == NULL) |
| { |
| symbolS *sym; |
| sym = symbol_new (name, absolute_section, &zero_address_frag, |
| (field->stag ? field->offset |
| : base_offset + field->offset)); |
| SF_SET_LOCAL (sym); |
| symbol_table_insert (sym); |
| } |
| else |
| { |
| char *replacement; |
| |
| replacement = concat (S_GET_NAME (rootsym), "+", root_stag_name, |
| name + strlen (S_GET_NAME (rootsym)), NULL); |
| str_hash_insert (subsym_hash[0], name, replacement, 0); |
| freename = NULL; |
| } |
| |
| /* Recurse if the field is a structure. |
| Note the field offset is relative to the outermost struct. */ |
| if (field->stag != NULL) |
| stag_add_field_symbols (field->stag, name, |
| field->offset, |
| rootsym, root_stag_name); |
| field = field->next; |
| free (freename); |
| } |
| free (prefix); |
| } |
| |
| /* Keep track of stag fields so that when structures are nested we can add the |
| complete dereferencing symbols to the symbol table. */ |
| |
| static void |
| stag_add_field (struct stag *parent, |
| const char *name, |
| bfd_vma offset, |
| struct stag *stag) |
| { |
| struct stag_field *sfield = XCNEW (struct stag_field); |
| |
| sfield->name = xstrdup (name); |
| sfield->offset = offset; |
| sfield->bitfield_offset = parent->current_bitfield_offset; |
| sfield->stag = stag; |
| if (parent->field == NULL) |
| parent->field = sfield; |
| else |
| { |
| struct stag_field *sf = parent->field; |
| while (sf->next != NULL) |
| sf = sf->next; |
| sf->next = sfield; |
| } |
| /* Only create a symbol for this field if the parent has no name. */ |
| if (startswith (parent->name, ".fake")) |
| { |
| symbolS *sym = symbol_new (name, absolute_section, &zero_address_frag, |
| offset); |
| SF_SET_LOCAL (sym); |
| symbol_table_insert (sym); |
| } |
| } |
| |
| /* [STAG] .struct [OFFSET] |
| Start defining structure offsets (symbols in absolute section). */ |
| |
| static void |
| tic54x_struct (int arg) |
| { |
| int start_offset = 0; |
| int is_union = arg; |
| |
| if (!current_stag) |
| { |
| /* Starting a new struct, switch to absolute section. */ |
| stag_saved_seg = now_seg; |
| stag_saved_subseg = now_subseg; |
| subseg_set (absolute_section, 0); |
| } |
| /* Align the current pointer. */ |
| else if (current_stag->current_bitfield_offset != 0) |
| { |
| ++abs_section_offset; |
| current_stag->current_bitfield_offset = 0; |
| } |
| |
| /* Offset expression is only meaningful for global .structs. */ |
| if (!is_union) |
| { |
| /* Offset is ignored in inner structs. */ |
| SKIP_WHITESPACE (); |
| if (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| start_offset = get_absolute_expression (); |
| else |
| start_offset = 0; |
| } |
| |
| if (current_stag) |
| { |
| /* Nesting, link to outer one. */ |
| current_stag->inner = XCNEW (struct stag); |
| current_stag->inner->outer = current_stag; |
| current_stag = current_stag->inner; |
| if (start_offset) |
| as_warn (_("Offset on nested structures is ignored")); |
| start_offset = abs_section_offset; |
| } |
| else |
| { |
| current_stag = XCNEW (struct stag); |
| abs_section_offset = start_offset; |
| } |
| current_stag->is_union = is_union; |
| |
| if (line_label == NULL) |
| { |
| static int struct_count = 0; |
| char fake[] = ".fake_stagNNNNNNN"; |
| sprintf (fake, ".fake_stag%d", struct_count++); |
| current_stag->sym = symbol_new (fake, absolute_section, |
| &zero_address_frag, |
| abs_section_offset); |
| } |
| else |
| { |
| char * label = xstrdup (S_GET_NAME (line_label)); |
| current_stag->sym = symbol_new (label, |
| absolute_section, |
| &zero_address_frag, |
| abs_section_offset); |
| free (label); |
| } |
| current_stag->name = S_GET_NAME (current_stag->sym); |
| SF_SET_LOCAL (current_stag->sym); |
| /* Nested .structs don't go into the symbol table. */ |
| if (current_stag->outer == NULL) |
| symbol_table_insert (current_stag->sym); |
| |
| line_label = NULL; |
| } |
| |
| /* [LABEL] .endstruct |
| finish defining structure offsets; optional LABEL's value will be the size |
| of the structure. */ |
| |
| static void |
| tic54x_endstruct (int is_union) |
| { |
| int size; |
| const char *path = |
| startswith (current_stag->name, ".fake") ? "" : current_stag->name; |
| |
| if (!current_stag || current_stag->is_union != is_union) |
| { |
| as_bad (_(".end%s without preceding .%s"), |
| is_union ? "union" : "struct", |
| is_union ? "union" : "struct"); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| /* Align end of structures. */ |
| if (current_stag->current_bitfield_offset) |
| { |
| ++abs_section_offset; |
| current_stag->current_bitfield_offset = 0; |
| } |
| |
| if (current_stag->is_union) |
| size = current_stag->size; |
| else |
| size = abs_section_offset - S_GET_VALUE (current_stag->sym); |
| if (line_label != NULL) |
| { |
| S_SET_VALUE (line_label, size); |
| symbol_table_insert (line_label); |
| line_label = NULL; |
| } |
| |
| /* Union size has already been calculated. */ |
| if (!current_stag->is_union) |
| current_stag->size = size; |
| /* Nested .structs don't get put in the stag table. */ |
| if (current_stag->outer == NULL) |
| { |
| str_hash_insert (stag_hash, current_stag->name, current_stag, 0); |
| stag_add_field_symbols (current_stag, path, |
| S_GET_VALUE (current_stag->sym), |
| NULL, NULL); |
| } |
| current_stag = current_stag->outer; |
| |
| /* If this is a nested .struct/.union, add it as a field to the enclosing |
| one. otherwise, restore the section we were in. */ |
| if (current_stag != NULL) |
| { |
| stag_add_field (current_stag, current_stag->inner->name, |
| S_GET_VALUE (current_stag->inner->sym), |
| current_stag->inner); |
| } |
| else |
| subseg_set (stag_saved_seg, stag_saved_subseg); |
| } |
| |
| /* [LABEL] .tag STAG |
| Reference a structure within a structure, as a sized field with an optional |
| label. |
| If used outside of a .struct/.endstruct, overlays the given structure |
| format on the existing allocated space. */ |
| |
| static void |
| tic54x_tag (int ignore ATTRIBUTE_UNUSED) |
| { |
| char *name; |
| int c = get_symbol_name (&name); |
| struct stag *stag = (struct stag *) str_hash_find (stag_hash, name); |
| |
| if (!stag) |
| { |
| if (*name) |
| as_bad (_("Unrecognized struct/union tag '%s'"), name); |
| else |
| as_bad (_(".tag requires a structure tag")); |
| ignore_rest_of_line (); |
| return; |
| } |
| if (line_label == NULL) |
| { |
| as_bad (_("Label required for .tag")); |
| ignore_rest_of_line (); |
| return; |
| } |
| else |
| { |
| char * label; |
| |
| label = xstrdup (S_GET_NAME (line_label)); |
| if (current_stag != NULL) |
| stag_add_field (current_stag, label, |
| abs_section_offset - S_GET_VALUE (current_stag->sym), |
| stag); |
| else |
| { |
| symbolS *sym = symbol_find (label); |
| |
| if (!sym) |
| { |
| as_bad (_(".tag target '%s' undefined"), label); |
| ignore_rest_of_line (); |
| free (label); |
| return; |
| } |
| stag_add_field_symbols (stag, S_GET_NAME (sym), |
| S_GET_VALUE (stag->sym), sym, stag->name); |
| } |
| free (label); |
| } |
| |
| /* Bump by the struct size, but only if we're within a .struct section. */ |
| if (current_stag != NULL && !current_stag->is_union) |
| abs_section_offset += stag->size; |
| |
| (void) restore_line_pointer (c); |
| demand_empty_rest_of_line (); |
| line_label = NULL; |
| } |
| |
| /* Handle all .byte, .char, .double, .field, .float, .half, .int, .long, |
| .short, .string, .ubyte, .uchar, .uhalf, .uint, .ulong, .ushort, .uword, |
| and .word. */ |
| |
| static void |
| tic54x_struct_field (int type) |
| { |
| int size; |
| int count = 1; |
| int new_bitfield_offset = 0; |
| int field_align = current_stag->current_bitfield_offset != 0; |
| int longword_align = 0; |
| |
| SKIP_WHITESPACE (); |
| if (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| count = get_absolute_expression (); |
| |
| switch (type) |
| { |
| case 'b': |
| case 'B': |
| case 'c': |
| case 'C': |
| case 'h': |
| case 'H': |
| case 'i': |
| case 'I': |
| case 's': |
| case 'S': |
| case 'w': |
| case 'W': |
| case '*': /* String. */ |
| size = 1; |
| break; |
| case 'f': |
| case 'l': |
| case 'L': |
| longword_align = 1; |
| size = 2; |
| break; |
| case '.': /* Bitfield. */ |
| size = 0; |
| if (count < 1 || count > 32) |
| { |
| as_bad (_(".field count '%d' out of range (1 <= X <= 32)"), count); |
| ignore_rest_of_line (); |
| return; |
| } |
| if (current_stag->current_bitfield_offset + count > 16) |
| { |
| /* Set the appropriate size and new field offset. */ |
| if (count == 32) |
| { |
| size = 2; |
| count = 1; |
| } |
| else if (count > 16) |
| { |
| size = 1; |
| count = 1; |
| new_bitfield_offset = count - 16; |
| } |
| else |
| new_bitfield_offset = count; |
| } |
| else |
| { |
| field_align = 0; |
| new_bitfield_offset = current_stag->current_bitfield_offset + count; |
| } |
| break; |
| default: |
| as_bad (_("Unrecognized field type '%c'"), type); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| if (field_align) |
| { |
| /* Align to the actual starting position of the field. */ |
| current_stag->current_bitfield_offset = 0; |
| ++abs_section_offset; |
| } |
| /* Align to longword boundary. */ |
| if (longword_align && (abs_section_offset & 0x1)) |
| ++abs_section_offset; |
| |
| if (line_label == NULL) |
| { |
| static int fieldno = 0; |
| char fake[] = ".fake_fieldNNNNN"; |
| |
| sprintf (fake, ".fake_field%d", fieldno++); |
| stag_add_field (current_stag, fake, |
| abs_section_offset - S_GET_VALUE (current_stag->sym), |
| NULL); |
| } |
| else |
| { |
| char * label; |
| |
| label = xstrdup (S_GET_NAME (line_label)); |
| stag_add_field (current_stag, label, |
| abs_section_offset - S_GET_VALUE (current_stag->sym), |
| NULL); |
| free (label); |
| } |
| |
| if (current_stag->is_union) |
| { |
| /* Note we treat the element as if it were an array of COUNT. */ |
| if (current_stag->size < (unsigned) size * count) |
| current_stag->size = size * count; |
| } |
| else |
| { |
| abs_section_offset += (unsigned) size * count; |
| current_stag->current_bitfield_offset = new_bitfield_offset; |
| } |
| line_label = NULL; |
| } |
| |
| /* Handle .byte, .word. .int, .long and all variants. */ |
| |
| static void |
| tic54x_cons (int type) |
| { |
| unsigned int c; |
| int octets; |
| |
| /* If we're within a .struct construct, don't actually allocate space. */ |
| if (current_stag != NULL) |
| { |
| tic54x_struct_field (type); |
| return; |
| } |
| |
| #ifdef md_flush_pending_output |
| md_flush_pending_output (); |
| #endif |
| |
| generate_lineno_debug (); |
| |
| /* Align long words to long word boundaries (4 octets). */ |
| if (type == 'l' || type == 'L') |
| { |
| frag_align (2, 0, 2); |
| /* If there's a label, assign it to the first allocated word. */ |
| if (line_label != NULL) |
| { |
| symbol_set_frag (line_label, frag_now); |
| S_SET_VALUE (line_label, frag_now_fix ()); |
| } |
| } |
| |
| switch (type) |
| { |
| case 'l': |
| case 'L': |
| case 'x': |
| octets = 4; |
| break; |
| case 'b': |
| case 'B': |
| case 'c': |
| case 'C': |
| octets = 1; |
| break; |
| default: |
| octets = 2; |
| break; |
| } |
| |
| do |
| { |
| if (*input_line_pointer == '"') |
| { |
| input_line_pointer++; |
| while (is_a_char (c = next_char_of_string ())) |
| tic54x_emit_char (c); |
| know (input_line_pointer[-1] == '\"'); |
| } |
| else |
| { |
| expressionS expn; |
| |
| input_line_pointer = parse_expression (input_line_pointer, &expn); |
| if (expn.X_op == O_constant) |
| { |
| offsetT value = expn.X_add_number; |
| /* Truncate overflows. */ |
| switch (octets) |
| { |
| case 1: |
| if ((value > 0 && value > 0xFF) |
| || (value < 0 && value < - 0x100)) |
| as_warn (_("Overflow in expression, truncated to 8 bits")); |
| break; |
| case 2: |
| if ((value > 0 && value > 0xFFFF) |
| || (value < 0 && value < - 0x10000)) |
| as_warn (_("Overflow in expression, truncated to 16 bits")); |
| break; |
| } |
| } |
| if (expn.X_op != O_constant && octets < 2) |
| { |
| /* Disallow .byte with a non constant expression that will |
| require relocation. */ |
| as_bad (_("Relocatable values require at least WORD storage")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| if (expn.X_op != O_constant |
| && amode == c_mode |
| && octets == 4) |
| { |
| /* FIXME -- at one point TI tools used to output REL16 |
| relocations, but I don't think the latest tools do at all |
| The current tools output extended relocations regardless of |
| the addressing mode (I actually think that ".c_mode" is |
| totally ignored in the latest tools). */ |
| amode = far_mode; |
| emitting_long = 1; |
| emit_expr (&expn, 4); |
| emitting_long = 0; |
| amode = c_mode; |
| } |
| else |
| { |
| emitting_long = octets == 4; |
| emit_expr (&expn, (octets == 1) ? 2 : octets); |
| emitting_long = 0; |
| } |
| } |
| } |
| while (*input_line_pointer++ == ','); |
| |
| input_line_pointer--; /* Put terminator back into stream. */ |
| demand_empty_rest_of_line (); |
| } |
| |
| /* .global <symbol>[,...,<symbolN>] |
| .def <symbol>[,...,<symbolN>] |
| .ref <symbol>[,...,<symbolN>] |
| |
| These all identify global symbols. |
| |
| .def means the symbol is defined in the current module and can be accessed |
| by other files. The symbol should be placed in the symbol table. |
| |
| .ref means the symbol is used in the current module but defined in another |
| module. The linker is to resolve this symbol's definition at link time. |
| |
| .global should act as a .ref or .def, as needed. |
| |
| global, def and ref all have symbol storage classes of C_EXT. |
| |
| I can't identify any difference in how the "other" c54x assembler treats |
| these, so we ignore the type here. */ |
| |
| void |
| tic54x_global (int type) |
| { |
| char *name; |
| int c; |
| symbolS *symbolP; |
| |
| if (type == 'r') |
| as_warn (_("Use of .def/.ref is deprecated. Use .global instead")); |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| do |
| { |
| c = get_symbol_name (&name); |
| symbolP = symbol_find_or_make (name); |
| c = restore_line_pointer (c); |
| |
| S_SET_STORAGE_CLASS (symbolP, C_EXT); |
| if (c == ',') |
| { |
| input_line_pointer++; |
| if (is_end_of_line[(unsigned char) *input_line_pointer]) |
| c = *input_line_pointer; |
| } |
| } |
| while (c == ','); |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Remove the symbol from the local label hash lookup. */ |
| |
| static int |
| tic54x_remove_local_label (void **slot, void *arg ATTRIBUTE_UNUSED) |
| { |
| string_tuple_t *tuple = *((string_tuple_t **) slot); |
| void *elem = str_hash_find (local_label_hash[macro_level], tuple->key); |
| str_hash_delete (local_label_hash[macro_level], tuple->key); |
| free (elem); |
| return 0; |
| } |
| |
| /* Reset all local labels. */ |
| |
| static void |
| tic54x_clear_local_labels (int ignored ATTRIBUTE_UNUSED) |
| { |
| htab_traverse (local_label_hash[macro_level], tic54x_remove_local_label, NULL); |
| } |
| |
| /* .text |
| .data |
| .sect "section name" |
| |
| Initialized section |
| make sure local labels get cleared when changing sections |
| |
| ARG is 't' for text, 'd' for data, or '*' for a named section |
| |
| For compatibility, '*' sections are SEC_CODE if instructions are |
| encountered, or SEC_DATA if not. |
| */ |
| |
| static void |
| tic54x_sect (int arg) |
| { |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| /* Local labels are cleared when changing sections. */ |
| tic54x_clear_local_labels (0); |
| |
| if (arg == 't') |
| s_text (0); |
| else if (arg == 'd') |
| s_data (0); |
| else |
| { |
| char *name = NULL; |
| int len; |
| /* Make sure all named initialized sections flagged properly. If we |
| encounter instructions, we'll flag it with SEC_CODE as well. */ |
| const char *flags = ",\"w\"\n"; |
| |
| /* If there are quotes, remove them. */ |
| if (*input_line_pointer == '"') |
| { |
| name = demand_copy_C_string (&len); |
| demand_empty_rest_of_line (); |
| name = concat (name, flags, (char *) NULL); |
| } |
| else |
| { |
| int c; |
| |
| c = get_symbol_name (&name); |
| name = concat (name, flags, (char *) NULL); |
| (void) restore_line_pointer (c); |
| demand_empty_rest_of_line (); |
| } |
| |
| input_scrub_insert_line (name); |
| obj_coff_section (0); |
| |
| /* If there was a line label, make sure that it gets assigned the proper |
| section. This is for compatibility, even though the actual behavior |
| is not explicitly defined. For consistency, we make .sect behave |
| like .usect, since that is probably what people expect. */ |
| if (line_label != NULL) |
| { |
| S_SET_SEGMENT (line_label, now_seg); |
| symbol_set_frag (line_label, frag_now); |
| S_SET_VALUE (line_label, frag_now_fix ()); |
| if (S_GET_STORAGE_CLASS (line_label) != C_EXT) |
| S_SET_STORAGE_CLASS (line_label, C_LABEL); |
| } |
| } |
| } |
| |
| /* [symbol] .space space_in_bits |
| [symbol] .bes space_in_bits |
| BES puts the symbol at the *last* word allocated |
| |
| cribbed from s_space. */ |
| |
| static void |
| tic54x_space (int arg) |
| { |
| expressionS expn; |
| char *p = 0; |
| int octets = 0; |
| long words; |
| int bits_per_byte = (OCTETS_PER_BYTE * 8); |
| int bit_offset = 0; |
| symbolS *label = line_label; |
| int bes = arg; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| #ifdef md_flush_pending_output |
| md_flush_pending_output (); |
| #endif |
| |
| /* Read the bit count. */ |
| expression (&expn); |
| |
| /* Some expressions are unresolvable until later in the assembly pass; |
| postpone until relaxation/fixup. we also have to postpone if a previous |
| partial allocation has not been completed yet. */ |
| if (expn.X_op != O_constant || frag_bit_offset (frag_now, now_seg) == -1) |
| { |
| struct bit_info *bi = XNEW (struct bit_info); |
| |
| bi->seg = now_seg; |
| bi->type = bes; |
| bi->sym = label; |
| p = frag_var (rs_machine_dependent, |
| 65536 * 2, 1, (relax_substateT) 0, |
| make_expr_symbol (&expn), (offsetT) 0, |
| (char *) bi); |
| if (p) |
| *p = 0; |
| |
| return; |
| } |
| |
| /* Reduce the required size by any bit offsets currently left over |
| from a previous .space/.bes/.field directive. */ |
| bit_offset = frag_now->tc_frag_data; |
| if (bit_offset != 0 && bit_offset < 16) |
| { |
| int spare_bits = bits_per_byte - bit_offset; |
| |
| if (spare_bits >= expn.X_add_number) |
| { |
| /* Don't have to do anything; sufficient bits have already been |
| allocated; just point the label to the right place. */ |
| if (label != NULL) |
| { |
| symbol_set_frag (label, frag_now); |
| S_SET_VALUE (label, frag_now_fix () - 1); |
| label = NULL; |
| } |
| frag_now->tc_frag_data += expn.X_add_number; |
| goto getout; |
| } |
| expn.X_add_number -= spare_bits; |
| /* Set the label to point to the first word allocated, which in this |
| case is the previous word, which was only partially filled. */ |
| if (!bes && label != NULL) |
| { |
| symbol_set_frag (label, frag_now); |
| S_SET_VALUE (label, frag_now_fix () - 1); |
| label = NULL; |
| } |
| } |
| /* Convert bits to bytes/words and octets, rounding up. */ |
| words = ((expn.X_add_number + bits_per_byte - 1) / bits_per_byte); |
| /* How many do we have left over? */ |
| bit_offset = expn.X_add_number % bits_per_byte; |
| octets = words * OCTETS_PER_BYTE; |
| if (octets < 0) |
| { |
| as_warn (_(".space/.bes repeat count is negative, ignored")); |
| goto getout; |
| } |
| else if (octets == 0) |
| { |
| as_warn (_(".space/.bes repeat count is zero, ignored")); |
| goto getout; |
| } |
| |
| /* If we are in the absolute section, just bump the offset. */ |
| if (now_seg == absolute_section) |
| { |
| abs_section_offset += words; |
| if (bes && label != NULL) |
| S_SET_VALUE (label, abs_section_offset - 1); |
| frag_now->tc_frag_data = bit_offset; |
| goto getout; |
| } |
| |
| if (!need_pass_2) |
| p = frag_var (rs_fill, 1, 1, |
| (relax_substateT) 0, (symbolS *) 0, |
| (offsetT) octets, (char *) 0); |
| |
| /* Make note of how many bits of this word we've allocated so far. */ |
| frag_now->tc_frag_data = bit_offset; |
| |
| /* .bes puts label at *last* word allocated. */ |
| if (bes && label != NULL) |
| { |
| symbol_set_frag (label, frag_now); |
| S_SET_VALUE (label, frag_now_fix () - 1); |
| } |
| |
| if (p) |
| *p = 0; |
| |
| getout: |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* [symbol] .usect "section-name", size-in-words |
| [, [blocking-flag] [, alignment-flag]] |
| |
| Uninitialized section. |
| Non-zero blocking means that if the section would cross a page (128-word) |
| boundary, it will be page-aligned. |
| Non-zero alignment aligns on a longword boundary. |
| |
| Has no effect on the current section. */ |
| |
| static void |
| tic54x_usect (int x ATTRIBUTE_UNUSED) |
| { |
| char c; |
| char *name; |
| char *section_name; |
| char *p; |
| segT seg; |
| int size, blocking_flag, alignment_flag; |
| segT current_seg; |
| subsegT current_subseg; |
| flagword flags; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| current_seg = now_seg; /* Save current seg. */ |
| current_subseg = now_subseg; /* Save current subseg. */ |
| |
| c = get_symbol_name (§ion_name); /* Get terminator. */ |
| name = xstrdup (section_name); |
| c = restore_line_pointer (c); |
| |
| if (c == ',') |
| ++input_line_pointer; |
| else |
| { |
| as_bad (_("Missing size argument")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| size = get_absolute_expression (); |
| |
| /* Read a possibly present third argument (blocking flag). */ |
| if (*input_line_pointer == ',') |
| { |
| ++input_line_pointer; |
| if (*input_line_pointer != ',') |
| blocking_flag = get_absolute_expression (); |
| else |
| blocking_flag = 0; |
| |
| /* Read a possibly present fourth argument (alignment flag). */ |
| if (*input_line_pointer == ',') |
| { |
| ++input_line_pointer; |
| alignment_flag = get_absolute_expression (); |
| } |
| else |
| alignment_flag = 0; |
| } |
| else |
| blocking_flag = alignment_flag = 0; |
| |
| seg = subseg_new (name, 0); |
| flags = bfd_section_flags (seg) | SEC_ALLOC; |
| |
| if (alignment_flag) |
| { |
| /* s_align eats end of line; restore it. */ |
| s_align_bytes (4); |
| --input_line_pointer; |
| } |
| |
| if (line_label != NULL) |
| { |
| S_SET_SEGMENT (line_label, seg); |
| symbol_set_frag (line_label, frag_now); |
| S_SET_VALUE (line_label, frag_now_fix ()); |
| /* Set scl to label, since that's what TI does. */ |
| if (S_GET_STORAGE_CLASS (line_label) != C_EXT) |
| S_SET_STORAGE_CLASS (line_label, C_LABEL); |
| } |
| |
| seg_info (seg)->bss = 1; /* Uninitialized data. */ |
| |
| p = frag_var (rs_fill, 1, 1, |
| (relax_substateT) 0, (symbolS *) line_label, |
| size * OCTETS_PER_BYTE, (char *) 0); |
| *p = 0; |
| |
| if (blocking_flag) |
| flags |= SEC_TIC54X_BLOCK; |
| |
| if (!bfd_set_section_flags (seg, flags)) |
| as_warn (_("Error setting flags for \"%s\": %s"), name, |
| bfd_errmsg (bfd_get_error ())); |
| |
| subseg_set (current_seg, current_subseg); /* Restore current seg. */ |
| demand_empty_rest_of_line (); |
| } |
| |
| static enum cpu_version |
| lookup_version (const char *ver) |
| { |
| enum cpu_version version = VNONE; |
| |
| if (ver[0] == '5' && ver[1] == '4') |
| { |
| if (strlen (ver) == 3 |
| && (ver[2] == '1' || ver[2] == '2' || ver[2] == '3' |
| || ver[2] == '5' || ver[2] == '8' || ver[2] == '9')) |
| version = ver[2] - '0'; |
| else if (strlen (ver) == 5 |
| && TOUPPER (ver[3]) == 'L' |
| && TOUPPER (ver[4]) == 'P' |
| && (ver[2] == '5' || ver[2] == '6')) |
| version = ver[2] - '0' + 10; |
| } |
| |
| return version; |
| } |
| |
| static void |
| set_cpu (enum cpu_version version) |
| { |
| cpu = version; |
| if (version == V545LP || version == V546LP) |
| { |
| symbolS *symbolP = symbol_new ("__allow_lp", absolute_section, |
| &zero_address_frag, 1); |
| SF_SET_LOCAL (symbolP); |
| symbol_table_insert (symbolP); |
| } |
| } |
| |
| /* .version cpu-version |
| cpu-version may be one of the following: |
| 541 |
| 542 |
| 543 |
| 545 |
| 545LP |
| 546LP |
| 548 |
| 549 |
| |
| This is for compatibility only. It currently has no affect on assembly. */ |
| static int cpu_needs_set = 1; |
| |
| static void |
| tic54x_version (int x ATTRIBUTE_UNUSED) |
| { |
| enum cpu_version version = VNONE; |
| enum cpu_version old_version = cpu; |
| int c; |
| char *ver; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| SKIP_WHITESPACE (); |
| ver = input_line_pointer; |
| while (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| ++input_line_pointer; |
| c = *input_line_pointer; |
| *input_line_pointer = 0; |
| |
| version = lookup_version (ver); |
| |
| if (cpu != VNONE && cpu != version) |
| as_warn (_("CPU version has already been set")); |
| |
| if (version == VNONE) |
| { |
| as_bad (_("Unrecognized version '%s'"), ver); |
| ignore_rest_of_line (); |
| return; |
| } |
| else if (assembly_begun && version != old_version) |
| { |
| as_bad (_("Changing of CPU version on the fly not supported")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| set_cpu (version); |
| |
| *input_line_pointer = c; |
| demand_empty_rest_of_line (); |
| } |
| |
| /* 'f' = float, 'x' = xfloat, 'd' = double, 'l' = ldouble. */ |
| |
| static void |
| tic54x_float_cons (int type) |
| { |
| if (current_stag != 0) |
| tic54x_struct_field ('f'); |
| |
| #ifdef md_flush_pending_output |
| md_flush_pending_output (); |
| #endif |
| |
| /* Align to long word boundary (4 octets) unless it's ".xfloat". */ |
| if (type != 'x') |
| { |
| frag_align (2, 0, 2); |
| /* If there's a label, assign it to the first allocated word. */ |
| if (line_label != NULL) |
| { |
| symbol_set_frag (line_label, frag_now); |
| S_SET_VALUE (line_label, frag_now_fix ()); |
| } |
| } |
| |
| float_cons ('f'); |
| } |
| |
| /* The argument is capitalized if it should be zero-terminated |
| 's' is normal string with upper 8-bits zero-filled, 'p' is packed. |
| Code copied from stringer, and slightly modified so that strings are packed |
| and encoded into the correct octets. */ |
| |
| static void |
| tic54x_stringer (int type) |
| { |
| unsigned int c; |
| int append_zero = type == 'S' || type == 'P'; |
| int packed = type == 'p' || type == 'P'; |
| int last_char = -1; /* Packed strings need two bytes at a time to encode. */ |
| |
| if (current_stag != NULL) |
| { |
| tic54x_struct_field ('*'); |
| return; |
| } |
| |
| #ifdef md_flush_pending_output |
| md_flush_pending_output (); |
| #endif |
| |
| c = ','; /* Do loop. */ |
| while (c == ',') |
| { |
| SKIP_WHITESPACE (); |
| switch (*input_line_pointer) |
| { |
| default: |
| { |
| unsigned short value = get_absolute_expression (); |
| FRAG_APPEND_1_CHAR ( value & 0xFF); |
| FRAG_APPEND_1_CHAR ((value >> 8) & 0xFF); |
| break; |
| } |
| case '\"': |
| ++input_line_pointer; /* -> 1st char of string. */ |
| while (is_a_char (c = next_char_of_string ())) |
| { |
| if (!packed) |
| { |
| FRAG_APPEND_1_CHAR (c); |
| FRAG_APPEND_1_CHAR (0); |
| } |
| else |
| { |
| /* Packed strings are filled MS octet first. */ |
| if (last_char == -1) |
| last_char = c; |
| else |
| { |
| FRAG_APPEND_1_CHAR (c); |
| FRAG_APPEND_1_CHAR (last_char); |
| last_char = -1; |
| } |
| } |
| } |
| if (append_zero) |
| { |
| if (packed && last_char != -1) |
| { |
| FRAG_APPEND_1_CHAR (0); |
| FRAG_APPEND_1_CHAR (last_char); |
| last_char = -1; |
| } |
| else |
| { |
| FRAG_APPEND_1_CHAR (0); |
| FRAG_APPEND_1_CHAR (0); |
| } |
| } |
| know (input_line_pointer[-1] == '\"'); |
| break; |
| } |
| SKIP_WHITESPACE (); |
| c = *input_line_pointer; |
| if (!is_end_of_line[c]) |
| ++input_line_pointer; |
| } |
| |
| /* Finish up any leftover packed string. */ |
| if (packed && last_char != -1) |
| { |
| FRAG_APPEND_1_CHAR (0); |
| FRAG_APPEND_1_CHAR (last_char); |
| } |
| demand_empty_rest_of_line (); |
| } |
| |
| static void |
| tic54x_p2align (int arg ATTRIBUTE_UNUSED) |
| { |
| as_bad (_("p2align not supported on this target")); |
| } |
| |
| static void |
| tic54x_align_words (int arg) |
| { |
| /* Only ".align" with no argument is allowed within .struct/.union. */ |
| int count = arg; |
| |
| if (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| { |
| if (arg == 2) |
| as_warn (_("Argument to .even ignored")); |
| else |
| count = get_absolute_expression (); |
| } |
| |
| if (current_stag != NULL && arg == 128) |
| { |
| if (current_stag->current_bitfield_offset != 0) |
| { |
| current_stag->current_bitfield_offset = 0; |
| ++abs_section_offset; |
| } |
| demand_empty_rest_of_line (); |
| return; |
| } |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| s_align_bytes (count << 1); |
| } |
| |
| /* Initialize multiple-bit fields within a single word of memory. */ |
| |
| static void |
| tic54x_field (int ignore ATTRIBUTE_UNUSED) |
| { |
| expressionS expn; |
| int size = 16; |
| char *p; |
| valueT value; |
| symbolS *label = line_label; |
| |
| if (current_stag != NULL) |
| { |
| tic54x_struct_field ('.'); |
| return; |
| } |
| |
| input_line_pointer = parse_expression (input_line_pointer, &expn); |
| |
| if (*input_line_pointer == ',') |
| { |
| ++input_line_pointer; |
| size = get_absolute_expression (); |
| if (size < 1 || size > 32) |
| { |
| as_bad (_("Invalid field size, must be from 1 to 32")); |
| ignore_rest_of_line (); |
| return; |
| } |
| } |
| |
| /* Truncate values to the field width. */ |
| if (expn.X_op != O_constant) |
| { |
| /* If the expression value is relocatable, the field size *must* |
| be 16. */ |
| if (size != 16) |
| { |
| as_bad (_("field size must be 16 when value is relocatable")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| frag_now->tc_frag_data = 0; |
| emit_expr (&expn, 2); |
| } |
| else |
| { |
| unsigned long fmask = (size == 32) ? 0xFFFFFFFF : (1ul << size) - 1; |
| |
| value = expn.X_add_number; |
| expn.X_add_number &= fmask; |
| if (value != (valueT) expn.X_add_number) |
| as_warn (_("field value truncated")); |
| value = expn.X_add_number; |
| /* Bits are stored MS first. */ |
| while (size >= 16) |
| { |
| frag_now->tc_frag_data = 0; |
| p = frag_more (2); |
| md_number_to_chars (p, (value >> (size - 16)) & 0xFFFF, 2); |
| size -= 16; |
| } |
| if (size > 0) |
| { |
| int bit_offset = frag_bit_offset (frag_now, now_seg); |
| |
| fragS *alloc_frag = bit_offset_frag (frag_now, now_seg); |
| if (bit_offset == -1) |
| { |
| struct bit_info *bi = XNEW (struct bit_info); |
| /* We don't know the previous offset at this time, so store the |
| info we need and figure it out later. */ |
| expressionS size_exp; |
| |
| size_exp.X_op = O_constant; |
| size_exp.X_add_number = size; |
| bi->seg = now_seg; |
| bi->type = TYPE_FIELD; |
| bi->value = value; |
| p = frag_var (rs_machine_dependent, |
| 4, 1, (relax_substateT) 0, |
| make_expr_symbol (&size_exp), (offsetT) 0, |
| (char *) bi); |
| goto getout; |
| } |
| else if (bit_offset == 0 || bit_offset + size > 16) |
| { |
| /* Align a new field. */ |
| p = frag_more (2); |
| frag_now->tc_frag_data = 0; |
| alloc_frag = frag_now; |
| } |
| else |
| { |
| /* Put the new value entirely within the existing one. */ |
| p = alloc_frag == frag_now ? |
| frag_now->fr_literal + frag_now_fix_octets () - 2 : |
| alloc_frag->fr_literal; |
| if (label != NULL) |
| { |
| symbol_set_frag (label, alloc_frag); |
| if (alloc_frag == frag_now) |
| S_SET_VALUE (label, frag_now_fix () - 1); |
| label = NULL; |
| } |
| } |
| value <<= 16 - alloc_frag->tc_frag_data - size; |
| |
| /* OR in existing value. */ |
| if (alloc_frag->tc_frag_data) |
| value |= ((unsigned short) p[1] << 8) | p[0]; |
| md_number_to_chars (p, value, 2); |
| alloc_frag->tc_frag_data += size; |
| if (alloc_frag->tc_frag_data == 16) |
| alloc_frag->tc_frag_data = 0; |
| } |
| } |
| getout: |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Ideally, we want to check SEC_LOAD and SEC_HAS_CONTENTS, but those aren't |
| available yet. seg_info ()->bss is the next best thing. */ |
| |
| static int |
| tic54x_initialized_section (segT seg) |
| { |
| return !seg_info (seg)->bss; |
| } |
| |
| /* .clink ["section name"] |
| |
| Marks the section as conditionally linked (link only if contents are |
| referenced elsewhere. |
| Without a name, refers to the current initialized section. |
| Name is required for uninitialized sections. */ |
| |
| static void |
| tic54x_clink (int ignored ATTRIBUTE_UNUSED) |
| { |
| segT seg = now_seg; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| if (*input_line_pointer == '\"') |
| { |
| char *section_name = ++input_line_pointer; |
| char *name; |
| |
| while (is_a_char (next_char_of_string ())) |
| ; |
| know (input_line_pointer[-1] == '\"'); |
| input_line_pointer[-1] = 0; |
| name = xstrdup (section_name); |
| |
| seg = bfd_get_section_by_name (stdoutput, name); |
| if (seg == NULL) |
| { |
| as_bad (_("Unrecognized section '%s'"), section_name); |
| ignore_rest_of_line (); |
| return; |
| } |
| } |
| else |
| { |
| if (!tic54x_initialized_section (seg)) |
| { |
| as_bad (_("Current section is uninitialized, " |
| "section name required for .clink")); |
| ignore_rest_of_line (); |
| return; |
| } |
| } |
| |
| seg->flags |= SEC_TIC54X_CLINK; |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Change the default include directory to be the current source file's |
| directory, instead of the current working directory. If DOT is non-zero, |
| set to "." instead. */ |
| |
| static void |
| tic54x_set_default_include (void) |
| { |
| char *dir, *tmp = NULL; |
| const char *curfile; |
| unsigned lineno; |
| |
| curfile = as_where (&lineno); |
| dir = xstrdup (curfile); |
| tmp = strrchr (dir, '/'); |
| if (tmp != NULL) |
| { |
| int len; |
| |
| *tmp = '\0'; |
| len = strlen (dir); |
| if (include_dir_count == 0) |
| { |
| include_dirs = XNEWVEC (const char *, 1); |
| include_dir_count = 1; |
| } |
| include_dirs[0] = dir; |
| if (len > include_dir_maxlen) |
| include_dir_maxlen = len; |
| } |
| else if (include_dirs != NULL) |
| include_dirs[0] = "."; |
| } |
| |
| /* .include "filename" | filename |
| .copy "filename" | filename |
| |
| FIXME 'include' file should be omitted from any output listing, |
| 'copy' should be included in any output listing |
| FIXME -- prevent any included files from changing listing (compat only) |
| FIXME -- need to include source file directory in search path; what's a |
| good way to do this? |
| |
| Entering/exiting included/copied file clears all local labels. */ |
| |
| static void |
| tic54x_include (int ignored ATTRIBUTE_UNUSED) |
| { |
| char newblock[] = " .newblock\n"; |
| char *filename; |
| char *input; |
| int len, c = -1; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| SKIP_WHITESPACE (); |
| |
| if (*input_line_pointer == '"') |
| { |
| filename = demand_copy_C_string (&len); |
| demand_empty_rest_of_line (); |
| } |
| else |
| { |
| filename = input_line_pointer; |
| while (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| ++input_line_pointer; |
| c = *input_line_pointer; |
| *input_line_pointer = '\0'; |
| filename = xstrdup (filename); |
| *input_line_pointer = c; |
| demand_empty_rest_of_line (); |
| } |
| /* Insert a partial line with the filename (for the sake of s_include) |
| and a .newblock. |
| The included file will be inserted before the newblock, so that the |
| newblock is executed after the included file is processed. */ |
| input = concat ("\"", filename, "\"\n", newblock, (char *) NULL); |
| input_scrub_insert_line (input); |
| |
| tic54x_clear_local_labels (0); |
| |
| tic54x_set_default_include (); |
| |
| s_include (0); |
| } |
| |
| static void |
| tic54x_message (int type) |
| { |
| char *msg; |
| char c; |
| int len; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| if (*input_line_pointer == '"') |
| msg = demand_copy_C_string (&len); |
| else |
| { |
| msg = input_line_pointer; |
| while (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| ++input_line_pointer; |
| c = *input_line_pointer; |
| *input_line_pointer = 0; |
| msg = xstrdup (msg); |
| *input_line_pointer = c; |
| } |
| |
| switch (type) |
| { |
| case 'm': |
| as_tsktsk ("%s", msg); |
| break; |
| case 'w': |
| as_warn ("%s", msg); |
| break; |
| case 'e': |
| as_bad ("%s", msg); |
| break; |
| } |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* .label <symbol> |
| Define a special symbol that refers to the loadtime address rather than the |
| runtime address within the current section. |
| |
| This symbol gets a special storage class so that when it is resolved, it is |
| resolved relative to the load address (lma) of the section rather than the |
| run address (vma). */ |
| |
| static void |
| tic54x_label (int ignored ATTRIBUTE_UNUSED) |
| { |
| char *name; |
| symbolS *symbolP; |
| int c; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| c = get_symbol_name (&name); |
| symbolP = colon (name); |
| S_SET_STORAGE_CLASS (symbolP, C_STATLAB); |
| |
| (void) restore_line_pointer (c); |
| demand_empty_rest_of_line (); |
| } |
| |
| /* .mmregs |
| Install all memory-mapped register names into the symbol table as |
| absolute local symbols. */ |
| |
| static void |
| tic54x_register_mmregs (int ignored ATTRIBUTE_UNUSED) |
| { |
| const tic54x_symbol *sym; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| for (sym = tic54x_mmregs; sym->name; sym++) |
| { |
| symbolS *symbolP = symbol_new (sym->name, absolute_section, |
| &zero_address_frag, sym->value); |
| SF_SET_LOCAL (symbolP); |
| symbol_table_insert (symbolP); |
| } |
| } |
| |
| /* .loop [count] |
| Count defaults to 1024. */ |
| |
| static void |
| tic54x_loop (int count) |
| { |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| SKIP_WHITESPACE (); |
| if (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| count = get_absolute_expression (); |
| |
| do_repeat ((size_t) count, "LOOP", "ENDLOOP"); |
| } |
| |
| /* Normally, endloop gets eaten by the preceding loop. */ |
| |
| static void |
| tic54x_endloop (int ignore ATTRIBUTE_UNUSED) |
| { |
| as_bad (_("ENDLOOP without corresponding LOOP")); |
| ignore_rest_of_line (); |
| } |
| |
| /* .break [condition]. */ |
| |
| static void |
| tic54x_break (int ignore ATTRIBUTE_UNUSED) |
| { |
| int cond = 1; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| SKIP_WHITESPACE (); |
| if (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| cond = get_absolute_expression (); |
| |
| if (cond) |
| end_repeat (substitution_line ? 1 : 0); |
| } |
| |
| static void |
| set_address_mode (int mode) |
| { |
| amode = mode; |
| if (mode == far_mode) |
| { |
| symbolS *symbolP = symbol_new ("__allow_far", absolute_section, |
| &zero_address_frag, 1); |
| SF_SET_LOCAL (symbolP); |
| symbol_table_insert (symbolP); |
| } |
| } |
| |
| static int address_mode_needs_set = 1; |
| |
| static void |
| tic54x_address_mode (int mode) |
| { |
| if (assembly_begun && amode != (unsigned) mode) |
| { |
| as_bad (_("Mixing of normal and extended addressing not supported")); |
| ignore_rest_of_line (); |
| return; |
| } |
| if (mode == far_mode && cpu != VNONE && cpu != V548 && cpu != V549) |
| { |
| as_bad (_("Extended addressing not supported on the specified CPU")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| set_address_mode (mode); |
| demand_empty_rest_of_line (); |
| } |
| |
| /* .sblock "section"|section [,...,"section"|section] |
| Designate initialized sections for blocking. */ |
| |
| static void |
| tic54x_sblock (int ignore ATTRIBUTE_UNUSED) |
| { |
| int c = ','; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| while (c == ',') |
| { |
| segT seg; |
| char *name; |
| |
| if (*input_line_pointer == '"') |
| { |
| int len; |
| |
| name = demand_copy_C_string (&len); |
| } |
| else |
| { |
| char *section_name; |
| |
| c = get_symbol_name (§ion_name); |
| name = xstrdup (section_name); |
| (void) restore_line_pointer (c); |
| } |
| |
| seg = bfd_get_section_by_name (stdoutput, name); |
| if (seg == NULL) |
| { |
| as_bad (_("Unrecognized section '%s'"), name); |
| ignore_rest_of_line (); |
| return; |
| } |
| else if (!tic54x_initialized_section (seg)) |
| { |
| as_bad (_(".sblock may be used for initialized sections only")); |
| ignore_rest_of_line (); |
| return; |
| } |
| seg->flags |= SEC_TIC54X_BLOCK; |
| |
| c = *input_line_pointer; |
| if (!is_end_of_line[(unsigned char) c]) |
| ++input_line_pointer; |
| } |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* symbol .set value |
| symbol .equ value |
| |
| value must be defined externals; no forward-referencing allowed |
| symbols assigned with .set/.equ may not be redefined. */ |
| |
| static void |
| tic54x_set (int ignore ATTRIBUTE_UNUSED) |
| { |
| symbolS *symbolP; |
| char *name; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| if (!line_label) |
| { |
| as_bad (_("Symbol missing for .set/.equ")); |
| ignore_rest_of_line (); |
| return; |
| } |
| name = xstrdup (S_GET_NAME (line_label)); |
| line_label = NULL; |
| if ((symbolP = symbol_find (name)) == NULL |
| && (symbolP = md_undefined_symbol (name)) == NULL) |
| { |
| symbolP = symbol_new (name, absolute_section, &zero_address_frag, 0); |
| S_SET_STORAGE_CLASS (symbolP, C_STAT); |
| } |
| free (name); |
| S_SET_DATA_TYPE (symbolP, T_INT); |
| S_SET_SEGMENT (symbolP, absolute_section); |
| symbol_table_insert (symbolP); |
| pseudo_set (symbolP); |
| demand_empty_rest_of_line (); |
| } |
| |
| /* .fclist |
| .fcnolist |
| List false conditional blocks. */ |
| |
| static void |
| tic54x_fclist (int show) |
| { |
| if (show) |
| listing &= ~LISTING_NOCOND; |
| else |
| listing |= LISTING_NOCOND; |
| demand_empty_rest_of_line (); |
| } |
| |
| static void |
| tic54x_sslist (int show) |
| { |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| listing_sslist = show; |
| } |
| |
| /* .var SYM[,...,SYMN] |
| Define a substitution string to be local to a macro. */ |
| |
| static void |
| tic54x_var (int ignore ATTRIBUTE_UNUSED) |
| { |
| static char empty[] = ""; |
| char *name; |
| int c; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| if (macro_level == 0) |
| { |
| as_bad (_(".var may only be used within a macro definition")); |
| ignore_rest_of_line (); |
| return; |
| } |
| do |
| { |
| if (!ISALPHA (*input_line_pointer)) |
| { |
| as_bad (_("Substitution symbols must begin with a letter")); |
| ignore_rest_of_line (); |
| return; |
| } |
| c = get_symbol_name (&name); |
| /* .var symbols start out with a null string. */ |
| name = xstrdup (name); |
| str_hash_insert (subsym_hash[macro_level], name, empty, 0); |
| c = restore_line_pointer (c); |
| if (c == ',') |
| { |
| ++input_line_pointer; |
| if (is_end_of_line[(unsigned char) *input_line_pointer]) |
| c = *input_line_pointer; |
| } |
| } |
| while (c == ','); |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* .mlib <macro library filename> |
| |
| Macro libraries are archived (standard AR-format) text macro definitions |
| Expand the file and include it. |
| |
| FIXME need to try the source file directory as well. */ |
| |
| static void |
| tic54x_mlib (int ignore ATTRIBUTE_UNUSED) |
| { |
| char *filename; |
| char *path; |
| int len, i; |
| bfd *abfd, *mbfd; |
| |
| ILLEGAL_WITHIN_STRUCT (); |
| |
| /* Parse the filename. */ |
| if (*input_line_pointer == '"') |
| { |
| if ((filename = demand_copy_C_string (&len)) == NULL) |
| return; |
| } |
| else |
| { |
| SKIP_WHITESPACE (); |
| len = 0; |
| while (!is_end_of_line[(unsigned char) *input_line_pointer] |
| && !ISSPACE (*input_line_pointer)) |
| { |
| obstack_1grow (¬es, *input_line_pointer); |
| ++input_line_pointer; |
| ++len; |
| } |
| obstack_1grow (¬es, '\0'); |
| filename = obstack_finish (¬es); |
| } |
| demand_empty_rest_of_line (); |
| |
| tic54x_set_default_include (); |
| path = XNEWVEC (char, (unsigned long) len + include_dir_maxlen + 5); |
| |
| for (i = 0; i < include_dir_count; i++) |
| { |
| FILE *try; |
| |
| strcpy (path, include_dirs[i]); |
| strcat (path, "/"); |
| strcat (path, filename); |
| if ((try = fopen (path, "r")) != NULL) |
| { |
| fclose (try); |
| break; |
| } |
| } |
| |
| if (i >= include_dir_count) |
| { |
| free (path); |
| path = filename; |
| } |
| |
| /* FIXME: if path is found, malloc'd storage is not freed. Of course, this |
| happens all over the place, and since the assembler doesn't usually keep |
| running for a very long time, it really doesn't matter. */ |
| register_dependency (path); |
| |
| /* Expand all archive entries to temporary files and include them. */ |
| abfd = bfd_openr (path, NULL); |
| if (!abfd) |
| { |
| as_bad (_("can't open macro library file '%s' for reading: %s"), |
| path, bfd_errmsg (bfd_get_error ())); |
| ignore_rest_of_line (); |
| return; |
| } |
| if (!bfd_check_format (abfd, bfd_archive)) |
| { |
| as_bad (_("File '%s' not in macro archive format"), path); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| /* Open each BFD as binary (it should be straight ASCII text). */ |
| for (mbfd = bfd_openr_next_archived_file (abfd, NULL); |
| mbfd != NULL; mbfd = bfd_openr_next_archived_file (abfd, mbfd)) |
| { |
| /* Get a size at least as big as the archive member. */ |
| bfd_size_type size = bfd_get_size (mbfd); |
| char *buf = XNEWVEC (char, size); |
| char *fname = tmpnam (NULL); |
| FILE *ftmp; |
| |
| /* We're not sure how big it is, but it will be smaller than "size". */ |
| size = bfd_bread (buf, size, mbfd); |
| |
| /* Write to a temporary file, then use s_include to include it |
| a bit of a hack. */ |
| ftmp = fopen (fname, "w+b"); |
| fwrite ((void *) buf, size, 1, ftmp); |
| if (size == 0 || buf[size - 1] != '\n') |
| fwrite ("\n", 1, 1, ftmp); |
| fclose (ftmp); |
| free (buf); |
| input_scrub_insert_file (fname); |
| remove (fname); |
| } |
| } |
| |
| const pseudo_typeS md_pseudo_table[] = |
| { |
| { "algebraic", s_ignore , 0 }, |
| { "align" , tic54x_align_words , 128 }, |
| { "ascii" , tic54x_stringer , 'p' }, |
| { "asciz" , tic54x_stringer , 'P' }, |
| { "even" , tic54x_align_words , 2 }, |
| { "asg" , tic54x_asg , 0 }, |
| { "eval" , tic54x_eval , 0 }, |
| { "bss" , tic54x_bss , 0 }, |
| { "byte" , tic54x_cons , 'b' }, |
| { "ubyte" , tic54x_cons , 'B' }, |
| { "char" , tic54x_cons , 'c' }, |
| { "uchar" , tic54x_cons , 'C' }, |
| { "clink" , tic54x_clink , 0 }, |
| { "c_mode" , tic54x_address_mode , c_mode }, |
| { "copy" , tic54x_include , 'c' }, |
| { "include" , tic54x_include , 'i' }, |
| { "data" , tic54x_sect , 'd' }, |
| { "double" , tic54x_float_cons , 'd' }, |
| { "ldouble" , tic54x_float_cons , 'l' }, |
| { "drlist" , s_ignore , 0 }, |
| { "drnolist" , s_ignore , 0 }, |
| { "emsg" , tic54x_message , 'e' }, |
| { "mmsg" , tic54x_message , 'm' }, |
| { "wmsg" , tic54x_message , 'w' }, |
| { "far_mode" , tic54x_address_mode , far_mode }, |
| { "fclist" , tic54x_fclist , 1 }, |
| { "fcnolist" , tic54x_fclist , 0 }, |
| { "field" , tic54x_field , -1 }, |
| { "float" , tic54x_float_cons , 'f' }, |
| { "xfloat" , tic54x_float_cons , 'x' }, |
| { "global" , tic54x_global , 'g' }, |
| { "def" , tic54x_global , 'd' }, |
| { "ref" , tic54x_global , 'r' }, |
| { "half" , tic54x_cons , 'h' }, |
| { "uhalf" , tic54x_cons , 'H' }, |
| { "short" , tic54x_cons , 's' }, |
| { "ushort" , tic54x_cons , 'S' }, |
| { "if" , s_if , (int) O_ne }, |
| { "elseif" , s_elseif , (int) O_ne }, |
| { "else" , s_else , 0 }, |
| { "endif" , s_endif , 0 }, |
| { "int" , tic54x_cons , 'i' }, |
| { "uint" , tic54x_cons , 'I' }, |
| { "word" , tic54x_cons , 'w' }, |
| { "uword" , tic54x_cons , 'W' }, |
| { "label" , tic54x_label , 0 }, /* Loadtime |
| address. */ |
| { "length" , s_ignore , 0 }, |
| { "width" , s_ignore , 0 }, |
| { "long" , tic54x_cons , 'l' }, |
| { "ulong" , tic54x_cons , 'L' }, |
| { "xlong" , tic54x_cons , 'x' }, |
| { "loop" , tic54x_loop , 1024 }, |
| { "break" , tic54x_break , 0 }, |
| { "endloop" , tic54x_endloop , 0 }, |
| { "mlib" , tic54x_mlib , 0 }, |
| { "mlist" , s_ignore , 0 }, |
| { "mnolist" , s_ignore , 0 }, |
| { "mmregs" , tic54x_register_mmregs , 0 }, |
| { "newblock" , tic54x_clear_local_labels, 0 }, |
| { "option" , s_ignore , 0 }, |
| { "p2align" , tic54x_p2align , 0 }, |
| { "sblock" , tic54x_sblock , 0 }, |
| { "sect" , tic54x_sect , '*' }, |
| { "set" , tic54x_set , 0 }, |
| { "equ" , tic54x_set , 0 }, |
| { "space" , tic54x_space , 0 }, |
| { "bes" , tic54x_space , 1 }, |
| { "sslist" , tic54x_sslist , 1 }, |
| { "ssnolist" , tic54x_sslist , 0 }, |
| { "string" , tic54x_stringer , 's' }, |
| { "pstring" , tic54x_stringer , 'p' }, |
| { "struct" , tic54x_struct , 0 }, |
| { "tag" , tic54x_tag , 0 }, |
| { "endstruct", tic54x_endstruct , 0 }, |
| { "tab" , s_ignore , 0 }, |
| { "text" , tic54x_sect , 't' }, |
| { "union" , tic54x_struct , 1 }, |
| { "endunion" , tic54x_endstruct , 1 }, |
| { "usect" , tic54x_usect , 0 }, |
| { "var" , tic54x_var , 0 }, |
| { "version" , tic54x_version , 0 }, |
| {0 , 0 , 0 } |
| }; |
| |
| int |
| md_parse_option (int c, const char *arg) |
| { |
| switch (c) |
| { |
| default: |
| return 0; |
| case OPTION_COFF_VERSION: |
| { |
| int version = atoi (arg); |
| |
| if (version != 0 && version != 1 && version != 2) |
| as_fatal (_("Bad COFF version '%s'"), arg); |
| /* FIXME -- not yet implemented. */ |
| break; |
| } |
| case OPTION_CPU_VERSION: |
| { |
| cpu = lookup_version (arg); |
| cpu_needs_set = 1; |
| if (cpu == VNONE) |
| as_fatal (_("Bad CPU version '%s'"), arg); |
| break; |
| } |
| case OPTION_ADDRESS_MODE: |
| amode = far_mode; |
| address_mode_needs_set = 1; |
| break; |
| case OPTION_STDERR_TO_FILE: |
| { |
| const char *filename = arg; |
| FILE *fp = fopen (filename, "w+"); |
| |
| if (fp == NULL) |
| as_fatal (_("Can't redirect stderr to the file '%s'"), filename); |
| fclose (fp); |
| if ((fp = freopen (filename, "w+", stderr)) == NULL) |
| as_fatal (_("Can't redirect stderr to the file '%s'"), filename); |
| break; |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* Create a "local" substitution string hash table for a new macro level |
| Some docs imply that macros have to use .newblock in order to be able |
| to re-use a local label. We effectively do an automatic .newblock by |
| deleting the local label hash between macro invocations. */ |
| |
| void |
| tic54x_macro_start (void) |
| { |
| if (++macro_level >= MAX_SUBSYM_HASH) |
| { |
| as_fatal (_("Macro nesting is too deep")); |
| return; |
| } |
| subsym_hash[macro_level] = str_htab_create (); |
| local_label_hash[macro_level] = str_htab_create (); |
| } |
| |
| void |
| tic54x_macro_info (const macro_entry *macro) |
| { |
| const formal_entry *entry; |
| |
| /* Put the formal args into the substitution symbol table. */ |
| for (entry = macro->formals; entry; entry = entry->next) |
| { |
| char *name = xstrndup (entry->name.ptr, entry->name.len); |
| char *value = xstrndup (entry->actual.ptr, entry->actual.len); |
| |
| name[entry->name.len] = '\0'; |
| value[entry->actual.len] = '\0'; |
| str_hash_insert (subsym_hash[macro_level], name, value, 0); |
| } |
| } |
| |
| /* Get rid of this macro's .var's, arguments, and local labels. */ |
| |
| void |
| tic54x_macro_end (void) |
| { |
| htab_delete (subsym_hash[macro_level]); |
| subsym_hash[macro_level] = NULL; |
| htab_delete (local_label_hash[macro_level]); |
| local_label_hash[macro_level] = NULL; |
| --macro_level; |
| } |
| |
| static int |
| subsym_symlen (char *a, char *ignore ATTRIBUTE_UNUSED) |
| { |
| return strlen (a); |
| } |
| |
| /* Compare symbol A to string B. */ |
| |
| static int |
| subsym_symcmp (char *a, char *b) |
| { |
| return strcmp (a, b); |
| } |
| |
| /* Return the index of the first occurrence of B in A, or zero if none |
| assumes b is an integer char value as a string. Index is one-based. */ |
| |
| static int |
| subsym_firstch (char *a, char *b) |
| { |
| int val = atoi (b); |
| char *tmp = strchr (a, val); |
| |
| return tmp ? tmp - a + 1 : 0; |
| } |
| |
| /* Similar to firstch, but returns index of last occurrence of B in A. */ |
| |
| static int |
| subsym_lastch (char *a, char *b) |
| { |
| int val = atoi (b); |
| char *tmp = strrchr (a, val); |
| |
| return tmp ? tmp - a + 1 : 0; |
| } |
| |
| /* Returns 1 if string A is defined in the symbol table (NOT the substitution |
| symbol table). */ |
| |
| static int |
| subsym_isdefed (char *a, char *ignore ATTRIBUTE_UNUSED) |
| { |
| symbolS *symbolP = symbol_find (a); |
| |
| return symbolP != NULL; |
| } |
| |
| /* Assign first member of comma-separated list B (e.g. "1,2,3") to the symbol |
| A, or zero if B is a null string. Both arguments *must* be substitution |
| symbols, unsubstituted. */ |
| |
| static int |
| subsym_ismember (char *sym, char *list) |
| { |
| char *elem, *ptr, *listv; |
| |
| if (!list) |
| return 0; |
| |
| listv = subsym_lookup (list, macro_level); |
| if (!listv) |
| { |
| as_bad (_("Undefined substitution symbol '%s'"), list); |
| ignore_rest_of_line (); |
| return 0; |
| } |
| |
| ptr = elem = xstrdup (listv); |
| while (*ptr && *ptr != ',') |
| ++ptr; |
| *ptr++ = 0; |
| |
| subsym_create_or_replace (sym, elem); |
| |
| /* Reassign the list. */ |
| subsym_create_or_replace (list, ptr); |
| |
| /* Assume this value, docs aren't clear. */ |
| return *list != 0; |
| } |
| |
| /* Return zero if not a constant; otherwise: |
| 1 if binary |
| 2 if octal |
| 3 if hexadecimal |
| 4 if character |
| 5 if decimal. */ |
| |
| static int |
| subsym_iscons (char *a, char *ignore ATTRIBUTE_UNUSED) |
| { |
| expressionS expn; |
| |
| parse_expression (a, &expn); |
| |
| if (expn.X_op == O_constant) |
| { |
| int len = strlen (a); |
| |
| switch (TOUPPER (a[len - 1])) |
| { |
| case 'B': |
| return 1; |
| case 'Q': |
| return 2; |
| case 'H': |
| return 3; |
| case '\'': |
| return 4; |
| default: |
| break; |
| } |
| /* No suffix; either octal, hex, or decimal. */ |
| if (*a == '0' && len > 1) |
| { |
| if (TOUPPER (a[1]) == 'X') |
| return 3; |
| return 2; |
| } |
| return 5; |
| } |
| |
| return 0; |
| } |
| |
| /* Return 1 if A is a valid symbol name. Expects string input. */ |
| |
| static int |
| subsym_isname (char *a, char *ignore ATTRIBUTE_UNUSED) |
| { |
| if (!is_name_beginner (*a)) |
| return 0; |
| while (*a) |
| { |
| if (!is_part_of_name (*a)) |
| return 0; |
| ++a; |
| } |
| return 1; |
| } |
| |
| /* Return whether the string is a register; accepts ar0-7, unless .mmregs has |
| been seen; if so, recognize any memory-mapped register. |
| Note this does not recognize "A" or "B" accumulators. */ |
| |
| static int |
| subsym_isreg (char *a, char *ignore ATTRIBUTE_UNUSED) |
| { |
| if (str_hash_find (reg_hash, a)) |
| return 1; |
| if (str_hash_find (mmreg_hash, a)) |
| return 1; |
| return 0; |
| } |
| |
| /* Return the structure size, given the stag. */ |
| |
| static int |
| subsym_structsz (char *name, char *ignore ATTRIBUTE_UNUSED) |
| { |
| struct stag *stag = (struct stag *) str_hash_find (stag_hash, name); |
| |
| if (stag) |
| return stag->size; |
| |
| return 0; |
| } |
| |
| /* If anybody actually uses this, they can fix it :) |
| FIXME I'm not sure what the "reference point" of a structure is. It might |
| be either the initial offset given .struct, or it may be the offset of the |
| structure within another structure, or it might be something else |
| altogether. since the TI assembler doesn't seem to ever do anything but |
| return zero, we punt and return zero. */ |
| |
| static int |
| subsym_structacc (char *stag_name ATTRIBUTE_UNUSED, |
| char *ignore ATTRIBUTE_UNUSED) |
| { |
| return 0; |
| } |
| |
| static float |
| math_ceil (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) ceil (arg1); |
| } |
| |
| static float |
| math_cvi (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (int) arg1; |
| } |
| |
| static float |
| math_floor (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) floor (arg1); |
| } |
| |
| static float |
| math_fmod (float arg1, float arg2) |
| { |
| return (int) arg1 % (int) arg2; |
| } |
| |
| static float |
| math_int (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return ((float) ((int) arg1)) == arg1; |
| } |
| |
| static float |
| math_round (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return arg1 > 0 ? (int) (arg1 + 0.5) : (int) (arg1 - 0.5); |
| } |
| |
| static float |
| math_sgn (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (arg1 < 0) ? -1 : (arg1 ? 1 : 0); |
| } |
| |
| static float |
| math_trunc (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (int) arg1; |
| } |
| |
| static float |
| math_acos (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) acos (arg1); |
| } |
| |
| static float |
| math_asin (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) asin (arg1); |
| } |
| |
| static float |
| math_atan (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) atan (arg1); |
| } |
| |
| static float |
| math_atan2 (float arg1, float arg2) |
| { |
| return (float) atan2 (arg1, arg2); |
| } |
| |
| static float |
| math_cosh (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) cosh (arg1); |
| } |
| |
| static float |
| math_cos (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) cos (arg1); |
| } |
| |
| static float |
| math_cvf (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) arg1; |
| } |
| |
| static float |
| math_exp (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) exp (arg1); |
| } |
| |
| static float |
| math_fabs (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) fabs (arg1); |
| } |
| |
| /* expr1 * 2^expr2. */ |
| |
| static float |
| math_ldexp (float arg1, float arg2) |
| { |
| return arg1 * (float) pow (2.0, arg2); |
| } |
| |
| static float |
| math_log10 (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) log10 (arg1); |
| } |
| |
| static float |
| math_log (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) log (arg1); |
| } |
| |
| static float |
| math_max (float arg1, float arg2) |
| { |
| return (arg1 > arg2) ? arg1 : arg2; |
| } |
| |
| static float |
| math_min (float arg1, float arg2) |
| { |
| return (arg1 < arg2) ? arg1 : arg2; |
| } |
| |
| static float |
| math_pow (float arg1, float arg2) |
| { |
| return (float) pow (arg1, arg2); |
| } |
| |
| static float |
| math_sin (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) sin (arg1); |
| } |
| |
| static float |
| math_sinh (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) sinh (arg1); |
| } |
| |
| static float |
| math_sqrt (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) sqrt (arg1); |
| } |
| |
| static float |
| math_tan (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) tan (arg1); |
| } |
| |
| static float |
| math_tanh (float arg1, float ignore ATTRIBUTE_UNUSED) |
| { |
| return (float) tanh (arg1); |
| } |
| |
| /* Built-in substitution symbol functions and math functions. */ |
| typedef struct |
| { |
| const char *name; |
| int (*proc) (char *, char *); |
| int nargs; |
| } subsym_proc_entry; |
| |
| static const subsym_proc_entry subsym_procs[] = |
| { |
| /* Assembler built-in string substitution functions. */ |
| { "$symlen", subsym_symlen, 1, }, |
| { "$symcmp", subsym_symcmp, 2, }, |
| { "$firstch", subsym_firstch, 2, }, |
| { "$lastch", subsym_lastch, 2, }, |
| { "$isdefed", subsym_isdefed, 1, }, |
| { "$ismember", subsym_ismember, 2, }, |
| { "$iscons", subsym_iscons, 1, }, |
| { "$isname", subsym_isname, 1, }, |
| { "$isreg", subsym_isreg, 1, }, |
| { "$structsz", subsym_structsz, 1, }, |
| { "$structacc", subsym_structacc, 1, }, |
| { NULL, NULL, 0 }, |
| }; |
| |
| typedef struct |
| { |
| const char *name; |
| float (*proc) (float, float); |
| int nargs; |
| int int_return; |
| } math_proc_entry; |
| |
| static const math_proc_entry math_procs[] = |
| { |
| /* Integer-returning built-in math functions. */ |
| { "$cvi", math_cvi, 1, 1 }, |
| { "$int", math_int, 1, 1 }, |
| { "$sgn", math_sgn, 1, 1 }, |
| |
| /* Float-returning built-in math functions. */ |
| { "$acos", math_acos, 1, 0 }, |
| { "$asin", math_asin, 1, 0 }, |
| { "$atan", math_atan, 1, 0 }, |
| { "$atan2", math_atan2, 2, 0 }, |
| { "$ceil", math_ceil, 1, 0 }, |
| { "$cosh", math_cosh, 1, 0 }, |
| { "$cos", math_cos, 1, 0 }, |
| { "$cvf", math_cvf, 1, 0 }, |
| { "$exp", math_exp, 1, 0 }, |
| { "$fabs", math_fabs, 1, 0 }, |
| { "$floor", math_floor, 1, 0 }, |
| { "$fmod", math_fmod, 2, 0 }, |
| { "$ldexp", math_ldexp, 2, 0 }, |
| { "$log10", math_log10, 1, 0 }, |
| { "$log", math_log, 1, 0 }, |
| { "$max", math_max, 2, 0 }, |
| { "$min", math_min, 2, 0 }, |
| { "$pow", math_pow, 2, 0 }, |
| { "$round", math_round, 1, 0 }, |
| { "$sin", math_sin, 1, 0 }, |
| { "$sinh", math_sinh, 1, 0 }, |
| { "$sqrt", math_sqrt, 1, 0 }, |
| { "$tan", math_tan, 1, 0 }, |
| { "$tanh", math_tanh, 1, 0 }, |
| { "$trunc", math_trunc, 1, 0 }, |
| { NULL, NULL, 0, 0 }, |
| }; |
| |
| void |
| md_begin (void) |
| { |
| insn_template *tm; |
| const tic54x_symbol *sym; |
| const subsym_proc_entry *subsym_proc; |
| const math_proc_entry *math_proc; |
| const char **symname; |
| char *TIC54X_DIR = getenv ("TIC54X_DIR"); |
| char *A_DIR = TIC54X_DIR ? TIC54X_DIR : getenv ("A_DIR"); |
| |
| local_label_id = 0; |
| |
| /* Look for A_DIR and add it to the include list. */ |
| if (A_DIR != NULL) |
| { |
| char *tmp = xstrdup (A_DIR); |
| |
| do |
| { |
| char *next = strchr (tmp, ';'); |
| |
| if (next) |
| *next++ = '\0'; |
| add_include_dir (tmp); |
| tmp = next; |
| } |
| while (tmp != NULL); |
| } |
| |
| op_hash = str_htab_create (); |
| for (tm = (insn_template *) tic54x_optab; tm->name; tm++) |
| str_hash_insert (op_hash, tm->name, tm, 0); |
| |
| parop_hash = str_htab_create (); |
| for (tm = (insn_template *) tic54x_paroptab; tm->name; tm++) |
| str_hash_insert (parop_hash, tm->name, tm, 0); |
| |
| reg_hash = str_htab_create (); |
| for (sym = tic54x_regs; sym->name; sym++) |
| { |
| /* Add basic registers to the symbol table. */ |
| symbolS *symbolP = symbol_new (sym->name, absolute_section, |
| &zero_address_frag, sym->value); |
| SF_SET_LOCAL (symbolP); |
| symbol_table_insert (symbolP); |
| str_hash_insert (reg_hash, sym->name, sym, 0); |
| } |
| for (sym = tic54x_mmregs; sym->name; sym++) |
| str_hash_insert (reg_hash, sym->name, sym, 0); |
| mmreg_hash = str_htab_create (); |
| for (sym = tic54x_mmregs; sym->name; sym++) |
| str_hash_insert (mmreg_hash, sym->name, sym, 0); |
| |
| cc_hash = str_htab_create (); |
| for (sym = tic54x_condition_codes; sym->name; sym++) |
| str_hash_insert (cc_hash, sym->name, sym, 0); |
| |
| cc2_hash = str_htab_create (); |
| for (sym = tic54x_cc2_codes; sym->name; sym++) |
| str_hash_insert (cc2_hash, sym->name, sym, 0); |
| |
| cc3_hash = str_htab_create (); |
| for (sym = tic54x_cc3_codes; sym->name; sym++) |
| str_hash_insert (cc3_hash, sym->name, sym, 0); |
| |
| sbit_hash = str_htab_create (); |
| for (sym = tic54x_status_bits; sym->name; sym++) |
| str_hash_insert (sbit_hash, sym->name, sym, 0); |
| |
| misc_symbol_hash = str_htab_create (); |
| for (symname = tic54x_misc_symbols; *symname; symname++) |
| str_hash_insert (misc_symbol_hash, *symname, *symname, 0); |
| |
| /* Only the base substitution table and local label table are initialized; |
| the others (for local macro substitution) get instantiated as needed. */ |
| local_label_hash[0] = str_htab_create (); |
| subsym_hash[0] = str_htab_create (); |
| for (subsym_proc = subsym_procs; subsym_proc->name; subsym_proc++) |
| str_hash_insert (subsym_hash[0], subsym_proc->name, subsym_proc, 0); |
| |
| math_hash = str_htab_create (); |
| for (math_proc = math_procs; math_proc->name; math_proc++) |
| { |
| /* Insert into the main subsym hash for recognition; insert into |
| the math hash to actually store information. */ |
| str_hash_insert (subsym_hash[0], math_proc->name, math_proc, 0); |
| str_hash_insert (math_hash, math_proc->name, math_proc, 0); |
| } |
| subsym_recurse_hash = str_htab_create (); |
| stag_hash = str_htab_create (); |
| } |
| |
| static int |
| is_accumulator (struct opstruct *operand) |
| { |
| return strcasecmp (operand->buf, "a") == 0 |
| || strcasecmp (operand->buf, "b") == 0; |
| } |
| |
| /* Return the number of operands found, or -1 on error, copying the |
| operands into the given array and the accompanying expressions into |
| the next array. */ |
| |
| static int |
| get_operands (struct opstruct operands[], char *line) |
| { |
| char *lptr = line; |
| int numexp = 0; |
| int expecting_operand = 0; |
| int i; |
| |
| while (numexp < MAX_OPERANDS && !is_end_of_line[(unsigned char) *lptr]) |
| { |
| int paren_not_balanced = 0; |
| char *op_start, *op_end; |
| |
| while (*lptr && ISSPACE (*lptr)) |
| ++lptr; |
| op_start = lptr; |
| while (paren_not_balanced || *lptr != ',') |
| { |
| if (*lptr == '\0') |
| { |
| if (paren_not_balanced) |
| { |
| as_bad (_("Unbalanced parenthesis in operand %d"), numexp); |
| return -1; |
| } |
| else |
| break; |
| } |
| if (*lptr == '(') |
| ++paren_not_balanced; |
| else if (*lptr == ')') |
| --paren_not_balanced; |
| ++lptr; |
| } |
| op_end = lptr; |
| if (op_end != op_start) |
| { |
| int len = op_end - op_start; |
| |
| strncpy (operands[numexp].buf, op_start, len); |
| operands[numexp].buf[len] = 0; |
| /* Trim trailing spaces; while the preprocessor gets rid of most, |
| there are weird usage patterns that can introduce them |
| (i.e. using strings for macro args). */ |
| while (len > 0 && ISSPACE (operands[numexp].buf[len - 1])) |
| operands[numexp].buf[--len] = 0; |
| lptr = op_end; |
| ++numexp; |
| } |
| else |
| { |
| if (expecting_operand || *lptr == ',') |
| { |
| as_bad (_("Expecting operand after ','")); |
| return -1; |
| } |
| } |
| if (*lptr == ',') |
| { |
| if (*++lptr == '\0') |
| { |
| as_bad (_("Expecting operand after ','")); |
| return -1; |
| } |
| expecting_operand = 1; |
| } |
| } |
| |
| while (*lptr && ISSPACE (*lptr++)) |
| ; |
| if (!is_end_of_line[(unsigned char) *lptr]) |
| { |
| as_bad (_("Extra junk on line")); |
| return -1; |
| } |
| |
| /* OK, now parse them into expressions. */ |
| for (i = 0; i < numexp; i++) |
| { |
| memset (&operands[i].exp, 0, sizeof (operands[i].exp)); |
| if (operands[i].buf[0] == '#') |
| { |
| /* Immediate. */ |
| parse_expression (operands[i].buf + 1, &operands[i].exp); |
| } |
| else if (operands[i].buf[0] == '@') |
| { |
| /* Direct notation. */ |
| parse_expression (operands[i].buf + 1, &operands[i].exp); |
| } |
| else if (operands[i].buf[0] == '*') |
| { |
| /* Indirect. */ |
| char *paren = strchr (operands[i].buf, '('); |
| |
| /* Allow immediate syntax in the inner expression. */ |
| if (paren && paren[1] == '#') |
| *++paren = '('; |
| |
| /* Pull out the lk expression or SP offset, if present. */ |
| if (paren != NULL) |
| { |
| int len = strlen (paren); |
| char *end = paren + len; |
| int c; |
| |
| while (end[-1] != ')') |
| if (--end <= paren) |
| { |
| as_bad (_("Badly formed address expression")); |
| return -1; |
| } |
| c = *end; |
| *end = '\0'; |
| parse_expression (paren, &operands[i].exp); |
| *end = c; |
| } |
| else |
| operands[i].exp.X_op = O_absent; |
| } |
| else |
| parse_expression (operands[i].buf, &operands[i].exp); |
| } |
| |
| return numexp; |
| } |
| |
| /* Predicates for different operand types. */ |
| |
| static int |
| is_immediate (struct opstruct *operand) |
| { |
| return *operand->buf == '#'; |
| } |
| |
| /* This is distinguished from immediate because some numbers must be constants |
| and must *not* have the '#' prefix. */ |
| |
| static int |
| is_absolute (struct opstruct *operand) |
| { |
|