URL
https://opencores.org/ocsvn/open8_urisc/open8_urisc/trunk
Subversion Repositories open8_urisc
[/] [open8_urisc/] [trunk/] [gnu/] [binutils/] [gas/] [config/] [tc-tic6x.c] - Rev 249
Go to most recent revision | Compare with Previous | Blame | View Log
/* TI C6X assembler. Copyright 2010, 2011 Free Software Foundation, Inc. Contributed by Joseph Myers <joseph@codesourcery.com> Bernd Schmidt <bernds@codesourcery.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. */ #include "as.h" #include "dwarf2dbg.h" #include "dw2gencfi.h" #include "safe-ctype.h" #include "subsegs.h" #include "opcode/tic6x.h" #include "elf/tic6x.h" #include "elf32-tic6x.h" /* Truncate and sign-extend at 32 bits, so that building on a 64-bit host gives identical results to a 32-bit host. */ #define TRUNC(X) ((valueT) (X) & 0xffffffffU) #define SEXT(X) ((TRUNC (X) ^ 0x80000000U) - 0x80000000U) #define streq(a, b) (strcmp (a, b) == 0) /* Stuff for .scomm symbols. */ static segT sbss_section; static asection scom_section; static asymbol scom_symbol; const char comment_chars[] = ";"; const char line_comment_chars[] = "#*;"; const char line_separator_chars[] = "@"; const char EXP_CHARS[] = "eE"; const char FLT_CHARS[] = "dDfF"; const char *md_shortopts = ""; enum { OPTION_MARCH = OPTION_MD_BASE, OPTION_MBIG_ENDIAN, OPTION_MLITTLE_ENDIAN, OPTION_MDSBT, OPTION_MNO_DSBT, OPTION_MPID, OPTION_MPIC, OPTION_MNO_PIC, OPTION_MGENERATE_REL }; struct option md_longopts[] = { { "march", required_argument, NULL, OPTION_MARCH }, { "mbig-endian", no_argument, NULL, OPTION_MBIG_ENDIAN }, { "mlittle-endian", no_argument, NULL, OPTION_MLITTLE_ENDIAN }, { "mdsbt", no_argument, NULL, OPTION_MDSBT }, { "mno-dsbt", no_argument, NULL, OPTION_MNO_DSBT }, { "mpid", required_argument, NULL, OPTION_MPID }, { "mpic", no_argument, NULL, OPTION_MPIC }, { "mno-pic", no_argument, NULL, OPTION_MNO_PIC }, { "mgenerate-rel", no_argument, NULL, OPTION_MGENERATE_REL }, { NULL, no_argument, NULL, 0 } }; size_t md_longopts_size = sizeof (md_longopts); /* The instructions enabled based only on the selected architecture (all instructions, if no architecture specified). */ static unsigned short tic6x_arch_enable = (TIC6X_INSN_C62X | TIC6X_INSN_C64X | TIC6X_INSN_C64XP | TIC6X_INSN_C67X | TIC6X_INSN_C67XP | TIC6X_INSN_C674X); /* The instructions enabled based on the current set of features (architecture, as modified by other options). */ static unsigned short tic6x_features; /* The architecture attribute value, or C6XABI_Tag_ISA_none if not yet set. */ static int tic6x_arch_attribute = C6XABI_Tag_ISA_none; /* Whether any instructions at all have been seen. Once any instructions have been seen, architecture attributes merge into the previous attribute value rather than replacing it. */ static bfd_boolean tic6x_seen_insns = FALSE; /* The number of registers in each register file supported by the current architecture. */ static unsigned int tic6x_num_registers; /* Whether predication on A0 is possible. */ static bfd_boolean tic6x_predicate_a0; /* Whether execute packets can cross fetch packet boundaries. */ static bfd_boolean tic6x_can_cross_fp_boundary; /* Whether there are constraints on simultaneous reads and writes of 40-bit data. */ static bfd_boolean tic6x_long_data_constraints; /* Whether compact instructions are available. */ static bfd_boolean tic6x_compact_insns; /* Whether to generate RELA relocations. */ static bfd_boolean tic6x_generate_rela = TRUE; /* Whether the code uses DSBT addressing. */ static bfd_boolean tic6x_dsbt; /* Types of position-independent data (attribute values for Tag_ABI_PID). */ typedef enum { tic6x_pid_no = 0, tic6x_pid_near = 1, tic6x_pid_far = 2 } tic6x_pid_type; /* The type of data addressing used in this code. */ static tic6x_pid_type tic6x_pid; /* Whether the code uses position-independent code. */ static bfd_boolean tic6x_pic; /* Table of supported architecture variants. */ typedef struct { const char *arch; int attr; unsigned short features; } tic6x_arch_table; static const tic6x_arch_table tic6x_arches[] = { { "c62x", C6XABI_Tag_ISA_C62X, TIC6X_INSN_C62X }, { "c64x", C6XABI_Tag_ISA_C64X, TIC6X_INSN_C62X | TIC6X_INSN_C64X }, { "c64x+", C6XABI_Tag_ISA_C64XP, (TIC6X_INSN_C62X | TIC6X_INSN_C64X | TIC6X_INSN_C64XP) }, { "c67x", C6XABI_Tag_ISA_C67X, TIC6X_INSN_C62X | TIC6X_INSN_C67X }, { "c67x+", C6XABI_Tag_ISA_C67XP, (TIC6X_INSN_C62X | TIC6X_INSN_C67X | TIC6X_INSN_C67XP) }, { "c674x", C6XABI_Tag_ISA_C674X, (TIC6X_INSN_C62X | TIC6X_INSN_C64X | TIC6X_INSN_C64XP | TIC6X_INSN_C67X | TIC6X_INSN_C67XP | TIC6X_INSN_C674X) } }; /* Caller saved register encodings. The standard frame layout uses this order, starting from the highest address. There must be TIC6X_NUM_UNWIND_REGS values. */ enum { UNWIND_A15, UNWIND_B15, UNWIND_B14, UNWIND_B13, UNWIND_B12, UNWIND_B11, UNWIND_B10, UNWIND_B3, UNWIND_A14, UNWIND_A13, UNWIND_A12, UNWIND_A11, UNWIND_A10 }; static void tic6x_output_unwinding (bfd_boolean need_extab); /* Return the frame unwind state for the current function, allocating as necessary. */ static tic6x_unwind_info *tic6x_get_unwind (void) { tic6x_unwind_info *unwind; unwind = seg_info (now_seg)->tc_segment_info_data.unwind; if (unwind) return unwind; unwind = seg_info (now_seg)->tc_segment_info_data.text_unwind; if (unwind) return unwind; unwind = (tic6x_unwind_info *)xmalloc (sizeof (tic6x_unwind_info)); seg_info (now_seg)->tc_segment_info_data.unwind = unwind; memset (unwind, 0, sizeof (*unwind)); return unwind; } /* Update the selected architecture based on ARCH, giving an error if ARCH is an invalid value. Does not call tic6x_update_features; the caller must do that if necessary. */ static void tic6x_use_arch (const char *arch) { unsigned int i; for (i = 0; i < ARRAY_SIZE (tic6x_arches); i++) if (strcmp (arch, tic6x_arches[i].arch) == 0) { tic6x_arch_enable = tic6x_arches[i].features; if (tic6x_seen_insns) tic6x_arch_attribute = elf32_tic6x_merge_arch_attributes (tic6x_arch_attribute, tic6x_arches[i].attr); else tic6x_arch_attribute = tic6x_arches[i].attr; return; } as_bad (_("unknown architecture '%s'"), arch); } /* Table of supported -mpid arguments. */ typedef struct { const char *arg; tic6x_pid_type attr; } tic6x_pid_type_table; static const tic6x_pid_type_table tic6x_pid_types[] = { { "no", tic6x_pid_no }, { "near", tic6x_pid_near }, { "far", tic6x_pid_far } }; /* Handle -mpid=ARG. */ static void tic6x_use_pid (const char *arg) { unsigned int i; for (i = 0; i < ARRAY_SIZE (tic6x_pid_types); i++) if (strcmp (arg, tic6x_pid_types[i].arg) == 0) { tic6x_pid = tic6x_pid_types[i].attr; return; } as_bad (_("unknown -mpid= argument '%s'"), arg); } /* Parse a target-specific option. */ int md_parse_option (int c, char *arg) { switch (c) { case OPTION_MARCH: tic6x_use_arch (arg); break; case OPTION_MBIG_ENDIAN: target_big_endian = 1; break; case OPTION_MLITTLE_ENDIAN: target_big_endian = 0; break; case OPTION_MDSBT: tic6x_dsbt = 1; break; case OPTION_MNO_DSBT: tic6x_dsbt = 0; break; case OPTION_MPID: tic6x_use_pid (arg); break; case OPTION_MPIC: tic6x_pic = 1; break; case OPTION_MNO_PIC: tic6x_pic = 0; break; case OPTION_MGENERATE_REL: tic6x_generate_rela = FALSE; break; default: return 0; } return 1; } void md_show_usage (FILE *stream ATTRIBUTE_UNUSED) { unsigned int i; fputc ('\n', stream); fprintf (stream, _("TMS320C6000 options:\n")); fprintf (stream, _(" -march=ARCH enable instructions from architecture ARCH\n")); fprintf (stream, _(" -mbig-endian generate big-endian code\n")); fprintf (stream, _(" -mlittle-endian generate little-endian code\n")); fprintf (stream, _(" -mdsbt code uses DSBT addressing\n")); fprintf (stream, _(" -mno-dsbt code does not use DSBT addressing\n")); fprintf (stream, _(" -mpid=no code uses position-dependent data addressing\n")); fprintf (stream, _(" -mpid=near code uses position-independent data addressing,\n" " GOT accesses use near DP addressing\n")); fprintf (stream, _(" -mpid=far code uses position-independent data addressing,\n" " GOT accesses use far DP addressing\n")); fprintf (stream, _(" -mpic code addressing is position-independent\n")); fprintf (stream, _(" -mno-pic code addressing is position-dependent\n")); /* -mgenerate-rel is only for testsuite use and is deliberately undocumented. */ fputc ('\n', stream); fprintf (stream, _("Supported ARCH values are:")); for (i = 0; i < ARRAY_SIZE (tic6x_arches); i++) fprintf (stream, " %s", tic6x_arches[i].arch); fputc ('\n', stream); } /* Update enabled features based on the current architecture and related settings. */ static void tic6x_update_features (void) { tic6x_features = tic6x_arch_enable; tic6x_num_registers = (tic6x_arch_enable & (TIC6X_INSN_C64X | TIC6X_INSN_C67XP)) ? 32 : 16; tic6x_predicate_a0 = (tic6x_arch_enable & TIC6X_INSN_C64X) ? TRUE : FALSE; tic6x_can_cross_fp_boundary = (tic6x_arch_enable & (TIC6X_INSN_C64X | TIC6X_INSN_C67XP)) ? TRUE : FALSE; tic6x_long_data_constraints = (tic6x_arch_enable & TIC6X_INSN_C64X) ? FALSE : TRUE; tic6x_compact_insns = (tic6x_arch_enable & TIC6X_INSN_C64XP) ? TRUE : FALSE; } /* Do configuration after all options have been parsed. */ void tic6x_after_parse_args (void) { tic6x_update_features (); } /* Parse a .cantunwind directive. */ static void s_tic6x_cantunwind (int ignored ATTRIBUTE_UNUSED) { tic6x_unwind_info *unwind = tic6x_get_unwind (); /* GCC sometimes spits out superfluous .cantunwind directives, so ignore them. */ if (unwind->data_bytes == 0) return; if (unwind->data_bytes != -1) { as_bad (_("unexpected .cantunwind directive")); return; } demand_empty_rest_of_line (); if (unwind->personality_routine || unwind->personality_index != -1) as_bad (_("personality routine specified for cantunwind frame")); unwind->personality_index = -2; } /* Parse a .handlerdata directive. */ static void s_tic6x_handlerdata (int ignored ATTRIBUTE_UNUSED) { tic6x_unwind_info *unwind = tic6x_get_unwind (); if (!unwind->saved_seg) { as_bad (_("unexpected .handlerdata directive")); return; } if (unwind->table_entry || unwind->personality_index == -2) { as_bad (_("duplicate .handlerdata directive")); return; } if (unwind->personality_index == -1 && unwind->personality_routine == NULL) { as_bad (_("personality routine required before .handlerdata directive")); return; } tic6x_output_unwinding (TRUE); } /* Parse a .endp directive. */ static void s_tic6x_endp (int ignored ATTRIBUTE_UNUSED) { tic6x_unwind_info *unwind = tic6x_get_unwind (); if (unwind->data_bytes != 0) { /* Output a .exidx entry if we have not already done so. Then switch back to the text section. */ if (!unwind->table_entry) tic6x_output_unwinding (FALSE); subseg_set (unwind->saved_seg, unwind->saved_subseg); } unwind->saved_seg = NULL; unwind->table_entry = NULL; unwind->data_bytes = 0; } /* Parse a .personalityindex directive. */ static void s_tic6x_personalityindex (int ignored ATTRIBUTE_UNUSED) { tic6x_unwind_info *unwind = tic6x_get_unwind (); expressionS exp; if (unwind->personality_routine || unwind->personality_index != -1) as_bad (_("duplicate .personalityindex directive")); expression (&exp); if (exp.X_op != O_constant || exp.X_add_number < 0 || exp.X_add_number > 15) { as_bad (_("bad personality routine number")); ignore_rest_of_line (); return; } unwind->personality_index = exp.X_add_number; demand_empty_rest_of_line (); } static void s_tic6x_personality (int ignored ATTRIBUTE_UNUSED) { char *name, *p, c; tic6x_unwind_info *unwind = tic6x_get_unwind (); if (unwind->personality_routine || unwind->personality_index != -1) as_bad (_("duplicate .personality directive")); name = input_line_pointer; c = get_symbol_end (); p = input_line_pointer; unwind->personality_routine = symbol_find_or_make (name); *p = c; demand_empty_rest_of_line (); } /* Parse a .arch directive. */ static void s_tic6x_arch (int ignored ATTRIBUTE_UNUSED) { char c; char *arch; arch = input_line_pointer; while (*input_line_pointer && !ISSPACE (*input_line_pointer)) input_line_pointer++; c = *input_line_pointer; *input_line_pointer = 0; tic6x_use_arch (arch); tic6x_update_features (); *input_line_pointer = c; demand_empty_rest_of_line (); } /* Parse a .ehtype directive. */ static void s_tic6x_ehtype (int ignored ATTRIBUTE_UNUSED) { expressionS exp; char *p; #ifdef md_flush_pending_output md_flush_pending_output (); #endif if (is_it_end_of_statement ()) { demand_empty_rest_of_line (); return; } #ifdef md_cons_align md_cons_align (4); #endif expression (&exp); if (exp.X_op != O_symbol) { as_bad (_("expected symbol")); return; } p = frag_more (4); fix_new_exp (frag_now, p - frag_now->fr_literal, 4, &exp, 0, BFD_RELOC_C6000_EHTYPE); demand_empty_rest_of_line (); } /* Parse a .nocmp directive. */ static void s_tic6x_nocmp (int ignored ATTRIBUTE_UNUSED) { seg_info (now_seg)->tc_segment_info_data.nocmp = TRUE; demand_empty_rest_of_line (); } /* .scomm pseudo-op handler. This is a new pseudo-op to handle putting objects in .scommon. By doing this the linker won't need to do any work, and more importantly it removes the implicit -G arg necessary to correctly link the object file. */ static void s_tic6x_scomm (int ignore ATTRIBUTE_UNUSED) { char *name; char c; char *p; offsetT size; symbolS *symbolP; offsetT align; int align2; name = input_line_pointer; c = get_symbol_end (); /* Just after name is now '\0'. */ p = input_line_pointer; *p = c; SKIP_WHITESPACE (); if (*input_line_pointer != ',') { as_bad (_("expected comma after symbol name")); ignore_rest_of_line (); return; } /* Skip ','. */ input_line_pointer++; if ((size = get_absolute_expression ()) < 0) { /* xgettext:c-format */ as_warn (_("invalid length for .scomm directive")); ignore_rest_of_line (); return; } /* The third argument to .scomm is the alignment. */ if (*input_line_pointer != ',') align = 8; else { ++input_line_pointer; align = get_absolute_expression (); if (align <= 0) { as_warn (_("alignment is not a positive number")); align = 8; } } /* Convert to a power of 2 alignment. */ if (align) { for (align2 = 0; (align & 1) == 0; align >>= 1, ++align2) continue; if (align != 1) { as_bad (_("alignment is not a power of 2")); ignore_rest_of_line (); return; } } else align2 = 0; *p = 0; symbolP = symbol_find_or_make (name); *p = c; if (S_IS_DEFINED (symbolP)) { /* xgettext:c-format */ as_bad (_("attempt to re-define symbol `%s'"), S_GET_NAME (symbolP)); ignore_rest_of_line (); return; } if (S_GET_VALUE (symbolP) && S_GET_VALUE (symbolP) != (valueT) size) { /* xgettext:c-format */ as_bad (_("attempt to redefine `%s' with a different length"), S_GET_NAME (symbolP)); ignore_rest_of_line (); return; } if (symbol_get_obj (symbolP)->local) { segT old_sec = now_seg; int old_subsec = now_subseg; char *pfrag; record_alignment (sbss_section, align2); subseg_set (sbss_section, 0); if (align2) frag_align (align2, 0, 0); if (S_GET_SEGMENT (symbolP) == sbss_section) symbol_get_frag (symbolP)->fr_symbol = 0; symbol_set_frag (symbolP, frag_now); pfrag = frag_var (rs_org, 1, 1, (relax_substateT) 0, symbolP, size, (char *) 0); *pfrag = 0; S_SET_SIZE (symbolP, size); S_SET_SEGMENT (symbolP, sbss_section); S_CLEAR_EXTERNAL (symbolP); subseg_set (old_sec, old_subsec); } else { S_SET_VALUE (symbolP, (valueT) size); S_SET_ALIGN (symbolP, 1 << align2); S_SET_EXTERNAL (symbolP); S_SET_SEGMENT (symbolP, &scom_section); } symbol_get_bfdsym (symbolP)->flags |= BSF_OBJECT; demand_empty_rest_of_line (); } /* Track for each attribute whether it has been set explicitly (and so should not have a default value set by the assembler). */ static bfd_boolean tic6x_attributes_set_explicitly[NUM_KNOWN_OBJ_ATTRIBUTES]; /* Parse a .c6xabi_attribute directive. */ static void s_tic6x_c6xabi_attribute (int ignored ATTRIBUTE_UNUSED) { int tag = s_vendor_attribute (OBJ_ATTR_PROC); if (tag < NUM_KNOWN_OBJ_ATTRIBUTES) tic6x_attributes_set_explicitly[tag] = TRUE; } typedef struct { const char *name; int tag; } tic6x_attribute_table; static const tic6x_attribute_table tic6x_attributes[] = { #define TAG(tag, value) { #tag, tag }, #include "elf/tic6x-attrs.h" #undef TAG }; /* Convert an attribute name to a number. */ int tic6x_convert_symbolic_attribute (const char *name) { unsigned int i; for (i = 0; i < ARRAY_SIZE (tic6x_attributes); i++) if (strcmp (name, tic6x_attributes[i].name) == 0) return tic6x_attributes[i].tag; return -1; } const pseudo_typeS md_pseudo_table[] = { { "arch", s_tic6x_arch, 0 }, { "c6xabi_attribute", s_tic6x_c6xabi_attribute, 0 }, { "nocmp", s_tic6x_nocmp, 0 }, { "scomm", s_tic6x_scomm, 0 }, { "word", cons, 4 }, { "ehtype", s_tic6x_ehtype, 0 }, { "endp", s_tic6x_endp, 0 }, { "handlerdata", s_tic6x_handlerdata, 0 }, { "personalityindex", s_tic6x_personalityindex, 0 }, { "personality", s_tic6x_personality, 0 }, { "cantunwind", s_tic6x_cantunwind, 0 }, { 0, 0, 0 } }; /* Hash table of opcodes. For each opcode name, this stores a pointer to a tic6x_opcode_list listing (in an arbitrary order) all opcode table entries with that name. */ static struct hash_control *opcode_hash; /* Initialize the assembler (called once at assembler startup). */ void md_begin (void) { tic6x_opcode_id id; flagword applicable; segT seg; subsegT subseg; bfd_set_arch_mach (stdoutput, TARGET_ARCH, 0); /* Insert opcodes into the hash table. */ opcode_hash = hash_new (); for (id = 0; id < tic6x_opcode_max; id++) { const char *errmsg; tic6x_opcode_list *opc = xmalloc (sizeof (tic6x_opcode_list)); opc->id = id; opc->next = hash_find (opcode_hash, tic6x_opcode_table[id].name); if ((errmsg = hash_jam (opcode_hash, tic6x_opcode_table[id].name, opc)) != NULL) as_fatal ("%s", _(errmsg)); } /* Save the current subseg so we can restore it [it's the default one and we don't want the initial section to be .sbss]. */ seg = now_seg; subseg = now_subseg; /* The sbss section is for local .scomm symbols. */ sbss_section = subseg_new (".bss", 0); seg_info (sbss_section)->bss = 1; /* This is copied from perform_an_assembly_pass. */ applicable = bfd_applicable_section_flags (stdoutput); bfd_set_section_flags (stdoutput, sbss_section, applicable & SEC_ALLOC); subseg_set (seg, subseg); /* We must construct a fake section similar to bfd_com_section but with the name .scommon. */ scom_section = bfd_com_section; scom_section.name = ".scommon"; scom_section.output_section = & scom_section; scom_section.symbol = & scom_symbol; scom_section.symbol_ptr_ptr = & scom_section.symbol; scom_symbol = * bfd_com_section.symbol; scom_symbol.name = ".scommon"; scom_symbol.section = & scom_section; } /* Whether the current line being parsed had the "||" parallel bars. */ static bfd_boolean tic6x_line_parallel; /* Whether the current line being parsed started "||^" to indicate an SPMASKed parallel instruction. */ static bfd_boolean tic6x_line_spmask; /* If the current line being parsed had an instruction predicate, the creg value for that predicate (which must be nonzero); otherwise 0. */ static unsigned int tic6x_line_creg; /* If the current line being parsed had an instruction predicate, the z value for that predicate; otherwise 0. */ static unsigned int tic6x_line_z; /* Return 1 (updating input_line_pointer as appropriate) if the line starting with C (immediately before input_line_pointer) starts with pre-opcode text appropriate for this target, 0 otherwise. */ int tic6x_unrecognized_line (int c) { char *p, *endp; unsigned int z; bfd_boolean areg; bfd_boolean bad_predicate; switch (c) { case '|': if (input_line_pointer[0] == '|') { if (input_line_pointer[1] == '^') { tic6x_line_spmask = TRUE; input_line_pointer += 2; } else input_line_pointer += 1; if (tic6x_line_parallel) as_bad (_("multiple '||' on same line")); tic6x_line_parallel = TRUE; if (tic6x_line_creg) as_bad (_("'||' after predicate")); return 1; } return 0; case '[': /* If it doesn't look like a predicate at all, just return 0. If it looks like one but not a valid one, give a better error. */ p = input_line_pointer; while (*p != ']' && !is_end_of_line[(unsigned char) *p]) p++; if (*p != ']') return 0; endp = p + 1; p = input_line_pointer; z = 0; bad_predicate = FALSE; if (*p == '!') { z = 1; p++; } if (*p == 'A' || *p == 'a') areg = TRUE; else if (*p == 'B' || *p == 'b') areg = FALSE; else { areg = TRUE; /* Avoid uninitialized warning. */ bad_predicate = TRUE; } if (!bad_predicate) { p++; if (*p != '0' && *p != '1' && *p != '2') bad_predicate = TRUE; else if (p[1] != ']') bad_predicate = TRUE; else input_line_pointer = p + 2; } if (tic6x_line_creg) as_bad (_("multiple predicates on same line")); if (bad_predicate) { char ctmp = *endp; *endp = 0; as_bad (_("bad predicate '%s'"), input_line_pointer - 1); *endp = ctmp; input_line_pointer = endp; return 1; } switch (*p) { case '0': tic6x_line_creg = (areg ? 6 : 1); if (areg && !tic6x_predicate_a0) as_bad (_("predication on A0 not supported on this architecture")); break; case '1': tic6x_line_creg = (areg ? 4 : 2); break; case '2': tic6x_line_creg = (areg ? 5 : 3); break; default: abort (); } tic6x_line_z = z; return 1; default: return 0; } } /* Do any target-specific handling of a label required. */ void tic6x_frob_label (symbolS *sym) { segment_info_type *si; tic6x_label_list *list; if (tic6x_line_parallel) { as_bad (_("label after '||'")); tic6x_line_parallel = FALSE; tic6x_line_spmask = FALSE; } if (tic6x_line_creg) { as_bad (_("label after predicate")); tic6x_line_creg = 0; tic6x_line_z = 0; } si = seg_info (now_seg); list = si->tc_segment_info_data.label_list; si->tc_segment_info_data.label_list = xmalloc (sizeof (tic6x_label_list)); si->tc_segment_info_data.label_list->next = list; si->tc_segment_info_data.label_list->label = sym; /* Defining tc_frob_label overrides the ELF definition of obj_frob_label, so we need to apply its effects here. */ dwarf2_emit_label (sym); } /* At end-of-line, give errors for start-of-line decorations that needed an instruction but were not followed by one. */ static void tic6x_end_of_line (void) { if (tic6x_line_parallel) { as_bad (_("'||' not followed by instruction")); tic6x_line_parallel = FALSE; tic6x_line_spmask = FALSE; } if (tic6x_line_creg) { as_bad (_("predicate not followed by instruction")); tic6x_line_creg = 0; tic6x_line_z = 0; } } /* Do any target-specific handling of the start of a logical line. */ void tic6x_start_line_hook (void) { tic6x_end_of_line (); } /* Do target-specific handling immediately after an input file from the command line, and any other inputs it includes, have been read. */ void tic6x_cleanup (void) { tic6x_end_of_line (); } /* Do target-specific initialization after arguments have been processed and the output file created. */ void tic6x_init_after_args (void) { elf32_tic6x_set_use_rela_p (stdoutput, tic6x_generate_rela); } /* Free LIST of labels (possibly NULL). */ static void tic6x_free_label_list (tic6x_label_list *list) { while (list) { tic6x_label_list *old = list; list = list->next; free (old); } } /* Handle a data alignment of N bytes. */ void tic6x_cons_align (int n ATTRIBUTE_UNUSED) { segment_info_type *seginfo = seg_info (now_seg); /* Data means there is no current execute packet, and that any label applies to that data rather than a subsequent instruction. */ tic6x_free_label_list (seginfo->tc_segment_info_data.label_list); seginfo->tc_segment_info_data.label_list = NULL; seginfo->tc_segment_info_data.execute_packet_frag = NULL; seginfo->tc_segment_info_data.last_insn_lsb = NULL; seginfo->tc_segment_info_data.spmask_addr = NULL; seginfo->tc_segment_info_data.func_units_used = 0; } /* Handle an alignment directive. Return TRUE if the machine-independent frag generation should be skipped. */ bfd_boolean tic6x_do_align (int n, char *fill, int len ATTRIBUTE_UNUSED, int max) { /* Given code alignments of 4, 8, 16 or 32 bytes, we try to handle them in the md_end pass by inserting NOPs in parallel with previous instructions. We only do this in sections containing nothing but instructions. Code alignments of 1 or 2 bytes have no effect in such sections (but we record them with machine-dependent frags anyway so they can be skipped or converted to machine-independent), while those of more than 64 bytes cannot reliably be handled in this way. */ if (n > 0 && max >= 0 && max < (1 << n) && !need_pass_2 && fill == NULL && subseg_text_p (now_seg)) { fragS *align_frag; char *p; if (n > 5) return FALSE; /* Machine-independent code would generate a frag here, but we wish to handle it in a machine-dependent way. */ if (frag_now_fix () != 0) { if (frag_now->fr_type != rs_machine_dependent) frag_wane (frag_now); frag_new (0); } frag_grow (32); align_frag = frag_now; p = frag_var (rs_machine_dependent, 32, 32, max, NULL, n, NULL); /* This must be the same as the frag to which a pointer was just saved. */ if (p != align_frag->fr_literal) abort (); align_frag->tc_frag_data.is_insns = FALSE; return TRUE; } else return FALSE; } /* Types of operand for parsing purposes. These are used as bit-masks to tell tic6x_parse_operand what forms of operand are permitted. */ #define TIC6X_OP_EXP 0x0001u #define TIC6X_OP_REG 0x0002u #define TIC6X_OP_REGPAIR 0x0004u #define TIC6X_OP_IRP 0x0008u #define TIC6X_OP_NRP 0x0010u /* With TIC6X_OP_MEM_NOUNREG, the contents of a () offset are always interpreted as an expression, which may be a symbol with the same name as a register that ends up being implicitly DP-relative. With TIC6X_OP_MEM_UNREG, the contents of a () offset are interpreted as a register if they match one, and failing that as an expression, which must be constant. */ #define TIC6X_OP_MEM_NOUNREG 0x0020u #define TIC6X_OP_MEM_UNREG 0x0040u #define TIC6X_OP_CTRL 0x0080u #define TIC6X_OP_FUNC_UNIT 0x0100u /* A register or register pair read by the assembler. */ typedef struct { /* The side the register is on (1 or 2). */ unsigned int side; /* The register number (0 to 31). */ unsigned int num; } tic6x_register; /* Types of modification of a base address. */ typedef enum { tic6x_mem_mod_none, tic6x_mem_mod_plus, tic6x_mem_mod_minus, tic6x_mem_mod_preinc, tic6x_mem_mod_predec, tic6x_mem_mod_postinc, tic6x_mem_mod_postdec } tic6x_mem_mod; /* Scaled [] or unscaled () nature of an offset. */ typedef enum { tic6x_offset_none, tic6x_offset_scaled, tic6x_offset_unscaled } tic6x_mem_scaling; /* A memory operand read by the assembler. */ typedef struct { /* The base register. */ tic6x_register base_reg; /* How the base register is modified. */ tic6x_mem_mod mod; /* Whether there is an offset (required with plain "+" and "-"), and whether it is scaled or unscaled if so. */ tic6x_mem_scaling scaled; /* Whether the offset is a register (TRUE) or an expression (FALSE). */ bfd_boolean offset_is_reg; /* The offset. */ union { expressionS exp; tic6x_register reg; } offset; } tic6x_mem_ref; /* A functional unit in SPMASK operands read by the assembler. */ typedef struct { /* The basic unit. */ tic6x_func_unit_base base; /* The side (1 or 2). */ unsigned int side; } tic6x_func_unit_operand; /* An operand read by the assembler. */ typedef struct { /* The syntactic form of the operand, as one of the bit-masks above. */ unsigned int form; /* The operand value. */ union { /* An expression: TIC6X_OP_EXP. */ expressionS exp; /* A register: TIC6X_OP_REG, TIC6X_OP_REGPAIR. */ tic6x_register reg; /* A memory reference: TIC6X_OP_MEM_NOUNREG, TIC6X_OP_MEM_UNREG. */ tic6x_mem_ref mem; /* A control register: TIC6X_OP_CTRL. */ tic6x_ctrl_id ctrl; /* A functional unit: TIC6X_OP_FUNC_UNIT. */ tic6x_func_unit_operand func_unit; } value; } tic6x_operand; #define skip_whitespace(str) do { if (*(str) == ' ') ++(str); } while (0) /* Parse a register operand, or part of an operand, starting at *P. If syntactically OK (including that the number is in the range 0 to 31, but not necessarily in range for this architecture), return TRUE, putting the register side and number in *REG and update *P to point immediately after the register number; otherwise return FALSE without changing *P (but possibly changing *REG). Do not print any diagnostics. */ static bfd_boolean tic6x_parse_register (char **p, tic6x_register *reg) { char *r = *p; switch (*r) { case 'a': case 'A': reg->side = 1; break; case 'b': case 'B': reg->side = 2; break; default: return FALSE; } r++; if (*r >= '0' && *r <= '9') { reg->num = *r - '0'; r++; } else return FALSE; if (reg->num > 0 && *r >= '0' && *r <= '9') { reg->num = reg->num * 10 + (*r - '0'); r++; } if (*r >= '0' && *r <= '9') return FALSE; if (reg->num >= 32) return FALSE; *p = r; return TRUE; } /* Parse the initial two characters of a functional unit name starting at *P. If OK, set *BASE and *SIDE and return TRUE; otherwise, return FALSE. */ static bfd_boolean tic6x_parse_func_unit_base (char *p, tic6x_func_unit_base *base, unsigned int *side) { bfd_boolean good_func_unit = TRUE; tic6x_func_unit_base maybe_base = tic6x_func_unit_nfu; unsigned int maybe_side = 0; switch (p[0]) { case 'd': case 'D': maybe_base = tic6x_func_unit_d; break; case 'l': case 'L': maybe_base = tic6x_func_unit_l; break; case 'm': case 'M': maybe_base = tic6x_func_unit_m; break; case 's': case 'S': maybe_base = tic6x_func_unit_s; break; default: good_func_unit = FALSE; break; } if (good_func_unit) switch (p[1]) { case '1': maybe_side = 1; break; case '2': maybe_side = 2; break; default: good_func_unit = FALSE; break; } if (good_func_unit) { *base = maybe_base; *side = maybe_side; } return good_func_unit; } /* Parse an operand starting at *P. If the operand parses OK, return TRUE and store the value in *OP; otherwise return FALSE (possibly changing *OP). In any case, update *P to point to the following comma or end of line. The possible operand forms are given by OP_FORMS. For diagnostics, this is operand OPNO of an opcode starting at STR, length OPC_LEN. */ static bfd_boolean tic6x_parse_operand (char **p, tic6x_operand *op, unsigned int op_forms, char *str, int opc_len, unsigned int opno) { bfd_boolean operand_parsed = FALSE; char *q = *p; if ((op_forms & (TIC6X_OP_MEM_NOUNREG | TIC6X_OP_MEM_UNREG)) == (TIC6X_OP_MEM_NOUNREG | TIC6X_OP_MEM_UNREG)) abort (); /* Check for functional unit names for SPMASK and SPMASKR. */ if (!operand_parsed && (op_forms & TIC6X_OP_FUNC_UNIT)) { tic6x_func_unit_base base = tic6x_func_unit_nfu; unsigned int side = 0; if (tic6x_parse_func_unit_base (q, &base, &side)) { char *rq = q + 2; skip_whitespace (rq); if (is_end_of_line[(unsigned char) *rq] || *rq == ',') { op->form = TIC6X_OP_FUNC_UNIT; op->value.func_unit.base = base; op->value.func_unit.side = side; operand_parsed = TRUE; q = rq; } } } /* Check for literal "irp". */ if (!operand_parsed && (op_forms & TIC6X_OP_IRP)) { if ((q[0] == 'i' || q[0] == 'I') && (q[1] == 'r' || q[1] == 'R') && (q[2] == 'p' || q[2] == 'P')) { char *rq = q + 3; skip_whitespace (rq); if (is_end_of_line[(unsigned char) *rq] || *rq == ',') { op->form = TIC6X_OP_IRP; operand_parsed = TRUE; q = rq; } } } /* Check for literal "nrp". */ if (!operand_parsed && (op_forms & TIC6X_OP_NRP)) { if ((q[0] == 'n' || q[0] == 'N') && (q[1] == 'r' || q[1] == 'R') && (q[2] == 'p' || q[2] == 'P')) { char *rq = q + 3; skip_whitespace (rq); if (is_end_of_line[(unsigned char) *rq] || *rq == ',') { op->form = TIC6X_OP_NRP; operand_parsed = TRUE; q = rq; } } } /* Check for control register names. */ if (!operand_parsed && (op_forms & TIC6X_OP_CTRL)) { tic6x_ctrl_id crid; for (crid = 0; crid < tic6x_ctrl_max; crid++) { size_t len = strlen (tic6x_ctrl_table[crid].name); if (strncasecmp (tic6x_ctrl_table[crid].name, q, len) == 0) { char *rq = q + len; skip_whitespace (rq); if (is_end_of_line[(unsigned char) *rq] || *rq == ',') { op->form = TIC6X_OP_CTRL; op->value.ctrl = crid; operand_parsed = TRUE; q = rq; if (!(tic6x_ctrl_table[crid].isa_variants & tic6x_features)) as_bad (_("control register '%s' not supported " "on this architecture"), tic6x_ctrl_table[crid].name); } } } } /* See if this looks like a memory reference. */ if (!operand_parsed && (op_forms & (TIC6X_OP_MEM_NOUNREG | TIC6X_OP_MEM_UNREG))) { bfd_boolean mem_ok = TRUE; char *mq = q; tic6x_mem_mod mem_mod = tic6x_mem_mod_none; tic6x_register base_reg; bfd_boolean require_offset, permit_offset; tic6x_mem_scaling scaled; bfd_boolean offset_is_reg; expressionS offset_exp; tic6x_register offset_reg; if (*mq == '*') mq++; else mem_ok = FALSE; if (mem_ok) { skip_whitespace (mq); switch (*mq) { case '+': if (mq[1] == '+') { mem_mod = tic6x_mem_mod_preinc; mq += 2; } else { mem_mod = tic6x_mem_mod_plus; mq++; } break; case '-': if (mq[1] == '-') { mem_mod = tic6x_mem_mod_predec; mq += 2; } else { mem_mod = tic6x_mem_mod_minus; mq++; } break; default: break; } } if (mem_ok) { skip_whitespace (mq); mem_ok = tic6x_parse_register (&mq, &base_reg); } if (mem_ok && mem_mod == tic6x_mem_mod_none) { skip_whitespace (mq); if (mq[0] == '+' && mq[1] == '+') { mem_mod = tic6x_mem_mod_postinc; mq += 2; } else if (mq[0] == '-' && mq[1] == '-') { mem_mod = tic6x_mem_mod_postdec; mq += 2; } } if (mem_mod == tic6x_mem_mod_none) permit_offset = FALSE; else permit_offset = TRUE; if (mem_mod == tic6x_mem_mod_plus || mem_mod == tic6x_mem_mod_minus) require_offset = TRUE; else require_offset = FALSE; scaled = tic6x_offset_none; offset_is_reg = FALSE; if (mem_ok && permit_offset) { char endc = 0; skip_whitespace (mq); switch (*mq) { case '[': scaled = tic6x_offset_scaled; mq++; endc = ']'; break; case '(': scaled = tic6x_offset_unscaled; mq++; endc = ')'; break; default: break; } if (scaled != tic6x_offset_none) { skip_whitespace (mq); if (scaled == tic6x_offset_scaled || (op_forms & TIC6X_OP_MEM_UNREG)) { bfd_boolean reg_ok; char *rq = mq; reg_ok = tic6x_parse_register (&rq, &offset_reg); if (reg_ok) { skip_whitespace (rq); if (*rq == endc) { mq = rq; offset_is_reg = TRUE; } } } if (!offset_is_reg) { char *save_input_line_pointer; save_input_line_pointer = input_line_pointer; input_line_pointer = mq; expression (&offset_exp); mq = input_line_pointer; input_line_pointer = save_input_line_pointer; } skip_whitespace (mq); if (*mq == endc) mq++; else mem_ok = FALSE; } } if (mem_ok && require_offset && scaled == tic6x_offset_none) mem_ok = FALSE; if (mem_ok) { skip_whitespace (mq); if (!is_end_of_line[(unsigned char) *mq] && *mq != ',') mem_ok = FALSE; } if (mem_ok) { op->form = op_forms & (TIC6X_OP_MEM_NOUNREG | TIC6X_OP_MEM_UNREG); op->value.mem.base_reg = base_reg; op->value.mem.mod = mem_mod; op->value.mem.scaled = scaled; op->value.mem.offset_is_reg = offset_is_reg; if (offset_is_reg) op->value.mem.offset.reg = offset_reg; else op->value.mem.offset.exp = offset_exp; operand_parsed = TRUE; q = mq; if (base_reg.num >= tic6x_num_registers) as_bad (_("register number %u not supported on this architecture"), base_reg.num); if (offset_is_reg && offset_reg.num >= tic6x_num_registers) as_bad (_("register number %u not supported on this architecture"), offset_reg.num); } } /* See if this looks like a register or register pair. */ if (!operand_parsed && (op_forms & (TIC6X_OP_REG | TIC6X_OP_REGPAIR))) { tic6x_register first_reg, second_reg; bfd_boolean reg_ok; char *rq = q; reg_ok = tic6x_parse_register (&rq, &first_reg); if (reg_ok) { if (*rq == ':' && (op_forms & TIC6X_OP_REGPAIR)) { rq++; reg_ok = tic6x_parse_register (&rq, &second_reg); if (reg_ok) { skip_whitespace (rq); if (is_end_of_line[(unsigned char) *rq] || *rq == ',') { if ((second_reg.num & 1) || (first_reg.num != second_reg.num + 1) || (first_reg.side != second_reg.side)) as_bad (_("register pair for operand %u of '%.*s'" " not a valid even/odd pair"), opno, opc_len, str); op->form = TIC6X_OP_REGPAIR; op->value.reg = second_reg; operand_parsed = TRUE; q = rq; } } } else if (op_forms & TIC6X_OP_REG) { skip_whitespace (rq); if (is_end_of_line[(unsigned char) *rq] || *rq == ',') { op->form = TIC6X_OP_REG; op->value.reg = first_reg; operand_parsed = TRUE; q = rq; } } } if (operand_parsed) { if (first_reg.num >= tic6x_num_registers) as_bad (_("register number %u not supported on this architecture"), first_reg.num); if (op->form == TIC6X_OP_REGPAIR && second_reg.num >= tic6x_num_registers) as_bad (_("register number %u not supported on this architecture"), second_reg.num); } } /* Otherwise, parse it as an expression. */ if (!operand_parsed && (op_forms & TIC6X_OP_EXP)) { char *save_input_line_pointer; save_input_line_pointer = input_line_pointer; input_line_pointer = q; op->form = TIC6X_OP_EXP; expression (&op->value.exp); q = input_line_pointer; input_line_pointer = save_input_line_pointer; operand_parsed = TRUE; } if (operand_parsed) { /* Now the operand has been parsed, there must be nothing more before the comma or end of line. */ skip_whitespace (q); if (!is_end_of_line[(unsigned char) *q] && *q != ',') { operand_parsed = FALSE; as_bad (_("junk after operand %u of '%.*s'"), opno, opc_len, str); while (!is_end_of_line[(unsigned char) *q] && *q != ',') q++; } } else { /* This could not be parsed as any acceptable form of operand. */ switch (op_forms) { case TIC6X_OP_REG | TIC6X_OP_REGPAIR: as_bad (_("bad register or register pair for operand %u of '%.*s'"), opno, opc_len, str); break; case TIC6X_OP_REG | TIC6X_OP_CTRL: case TIC6X_OP_REG: as_bad (_("bad register for operand %u of '%.*s'"), opno, opc_len, str); break; case TIC6X_OP_REGPAIR: as_bad (_("bad register pair for operand %u of '%.*s'"), opno, opc_len, str); break; case TIC6X_OP_FUNC_UNIT: as_bad (_("bad functional unit for operand %u of '%.*s'"), opno, opc_len, str); break; default: as_bad (_("bad operand %u of '%.*s'"), opno, opc_len, str); break; } while (!is_end_of_line[(unsigned char) *q] && *q != ',') q++; } *p = q; return operand_parsed; } /* Table of assembler operators and associated O_* values. */ typedef struct { const char *name; operatorT op; } tic6x_operator_table; static const tic6x_operator_table tic6x_operators[] = { #define O_dsbt_index O_md1 { "dsbt_index", O_dsbt_index }, #define O_got O_md2 { "got", O_got }, #define O_dpr_got O_md3 { "dpr_got", O_dpr_got }, #define O_dpr_byte O_md4 { "dpr_byte", O_dpr_byte }, #define O_dpr_hword O_md5 { "dpr_hword", O_dpr_hword }, #define O_dpr_word O_md6 { "dpr_word", O_dpr_word }, #define O_pcr_offset O_md7 { "pcr_offset", O_pcr_offset } }; /* Parse a name in some machine-specific way. Used on C6X to handle assembler operators. */ int tic6x_parse_name (const char *name, expressionS *exprP, enum expr_mode mode ATTRIBUTE_UNUSED, char *nextchar) { char *p = input_line_pointer; char c, *name_start, *name_end; const char *inner_name; unsigned int i; operatorT op = O_illegal; symbolS *sym, *op_sym = NULL; if (*name != '$') return 0; for (i = 0; i < ARRAY_SIZE (tic6x_operators); i++) if (strcasecmp (name + 1, tic6x_operators[i].name) == 0) { op = tic6x_operators[i].op; break; } if (op == O_illegal) return 0; *input_line_pointer = *nextchar; skip_whitespace (p); if (*p != '(') { *input_line_pointer = 0; return 0; } p++; skip_whitespace (p); if (!is_name_beginner (*p)) { *input_line_pointer = 0; return 0; } name_start = p; p++; while (is_part_of_name (*p)) p++; name_end = p; skip_whitespace (p); if (op == O_pcr_offset) { char *op_name_start, *op_name_end; if (*p != ',') { *input_line_pointer = 0; return 0; } p++; skip_whitespace (p); if (!is_name_beginner (*p)) { *input_line_pointer = 0; return 0; } op_name_start = p; p++; while (is_part_of_name (*p)) p++; op_name_end = p; skip_whitespace (p); c = *op_name_end; *op_name_end = 0; op_sym = symbol_find_or_make (op_name_start); *op_name_end = c; } if (*p != ')') { *input_line_pointer = 0; return 0; } input_line_pointer = p + 1; *nextchar = *input_line_pointer; *input_line_pointer = 0; c = *name_end; *name_end = 0; inner_name = name_start; if (op == O_dsbt_index && strcmp (inner_name, "__c6xabi_DSBT_BASE") != 0) { as_bad (_("$DSBT_INDEX must be used with __c6xabi_DSBT_BASE")); inner_name = "__c6xabi_DSBT_BASE"; } sym = symbol_find_or_make (inner_name); *name_end = c; exprP->X_op = op; exprP->X_add_symbol = sym; exprP->X_add_number = 0; exprP->X_op_symbol = op_sym; exprP->X_md = 0; return 1; } /* Create a fixup for an expression. Same arguments as fix_new_exp, plus FIX_ADDA which is TRUE for ADDA instructions (to indicate that fixes resolving to constants should have those constants implicitly shifted) and FALSE otherwise, but look for C6X-specific expression types and adjust the relocations or give errors accordingly. */ static void tic6x_fix_new_exp (fragS *frag, int where, int size, expressionS *exp, int pcrel, bfd_reloc_code_real_type r_type, bfd_boolean fix_adda) { bfd_reloc_code_real_type new_reloc = BFD_RELOC_UNUSED; symbolS *subsy = NULL; fixS *fix; switch (exp->X_op) { case O_dsbt_index: switch (r_type) { case BFD_RELOC_C6000_SBR_U15_W: new_reloc = BFD_RELOC_C6000_DSBT_INDEX; break; default: as_bad (_("$DSBT_INDEX not supported in this context")); return; } break; case O_got: switch (r_type) { case BFD_RELOC_C6000_SBR_U15_W: new_reloc = BFD_RELOC_C6000_SBR_GOT_U15_W; break; default: as_bad (_("$GOT not supported in this context")); return; } break; case O_dpr_got: switch (r_type) { case BFD_RELOC_C6000_ABS_L16: new_reloc = BFD_RELOC_C6000_SBR_GOT_L16_W; break; case BFD_RELOC_C6000_ABS_H16: new_reloc = BFD_RELOC_C6000_SBR_GOT_H16_W; break; default: as_bad (_("$DPR_GOT not supported in this context")); return; } break; case O_dpr_byte: switch (r_type) { case BFD_RELOC_C6000_ABS_S16: new_reloc = BFD_RELOC_C6000_SBR_S16; break; case BFD_RELOC_C6000_ABS_L16: new_reloc = BFD_RELOC_C6000_SBR_L16_B; break; case BFD_RELOC_C6000_ABS_H16: new_reloc = BFD_RELOC_C6000_SBR_H16_B; break; default: as_bad (_("$DPR_BYTE not supported in this context")); return; } break; case O_dpr_hword: switch (r_type) { case BFD_RELOC_C6000_ABS_L16: new_reloc = BFD_RELOC_C6000_SBR_L16_H; break; case BFD_RELOC_C6000_ABS_H16: new_reloc = BFD_RELOC_C6000_SBR_H16_H; break; default: as_bad (_("$DPR_HWORD not supported in this context")); return; } break; case O_dpr_word: switch (r_type) { case BFD_RELOC_C6000_ABS_L16: new_reloc = BFD_RELOC_C6000_SBR_L16_W; break; case BFD_RELOC_C6000_ABS_H16: new_reloc = BFD_RELOC_C6000_SBR_H16_W; break; default: as_bad (_("$DPR_WORD not supported in this context")); return; } break; case O_pcr_offset: subsy = exp->X_op_symbol; switch (r_type) { case BFD_RELOC_C6000_ABS_S16: case BFD_RELOC_C6000_ABS_L16: new_reloc = BFD_RELOC_C6000_PCR_L16; break; case BFD_RELOC_C6000_ABS_H16: new_reloc = BFD_RELOC_C6000_PCR_H16; break; default: as_bad (_("$PCR_OFFSET not supported in this context")); return; } break; case O_symbol: break; default: if (pcrel) { as_bad (_("invalid PC-relative operand")); return; } break; } if (new_reloc == BFD_RELOC_UNUSED) fix = fix_new_exp (frag, where, size, exp, pcrel, r_type); else fix = fix_new (frag, where, size, exp->X_add_symbol, exp->X_add_number, pcrel, new_reloc); fix->tc_fix_data.fix_subsy = subsy; fix->tc_fix_data.fix_adda = fix_adda; } /* Generate a fix for a constant (.word etc.). Needed to ensure these go through the error checking in tic6x_fix_new_exp. */ void tic6x_cons_fix_new (fragS *frag, int where, int size, expressionS *exp) { bfd_reloc_code_real_type r_type; switch (size) { case 1: r_type = BFD_RELOC_8; break; case 2: r_type = BFD_RELOC_16; break; case 4: r_type = BFD_RELOC_32; break; default: as_bad (_("no %d-byte relocations available"), size); return; } tic6x_fix_new_exp (frag, where, size, exp, 0, r_type, FALSE); } /* Initialize target-specific fix data. */ void tic6x_init_fix_data (fixS *fixP) { fixP->tc_fix_data.fix_adda = FALSE; fixP->tc_fix_data.fix_subsy = NULL; } /* Return true if the fix can be handled by GAS, false if it must be passed through to the linker. */ bfd_boolean tic6x_fix_adjustable (fixS *fixP) { switch (fixP->fx_r_type) { /* Adjust_reloc_syms doesn't know about the GOT. */ case BFD_RELOC_C6000_SBR_GOT_U15_W: case BFD_RELOC_C6000_SBR_GOT_H16_W: case BFD_RELOC_C6000_SBR_GOT_L16_W: case BFD_RELOC_C6000_EHTYPE: return 0; case BFD_RELOC_C6000_PREL31: return 0; case BFD_RELOC_C6000_PCR_H16: case BFD_RELOC_C6000_PCR_L16: return 0; default: return 1; } } /* Given the fine-grained form of an operand, return the coarse (bit-mask) form. */ static unsigned int tic6x_coarse_operand_form (tic6x_operand_form form) { switch (form) { case tic6x_operand_asm_const: case tic6x_operand_link_const: return TIC6X_OP_EXP; case tic6x_operand_reg: case tic6x_operand_xreg: case tic6x_operand_dreg: case tic6x_operand_areg: case tic6x_operand_retreg: return TIC6X_OP_REG; case tic6x_operand_regpair: case tic6x_operand_xregpair: case tic6x_operand_dregpair: return TIC6X_OP_REGPAIR; case tic6x_operand_irp: return TIC6X_OP_IRP; case tic6x_operand_nrp: return TIC6X_OP_NRP; case tic6x_operand_ctrl: return TIC6X_OP_CTRL; case tic6x_operand_mem_short: case tic6x_operand_mem_long: case tic6x_operand_mem_deref: return TIC6X_OP_MEM_NOUNREG; case tic6x_operand_mem_ndw: return TIC6X_OP_MEM_UNREG; case tic6x_operand_func_unit: return TIC6X_OP_FUNC_UNIT; default: abort (); } } /* How an operand may match or not match a desired form. If different instruction alternatives fail in different ways, the first failure in this list determines the diagnostic. */ typedef enum { /* Matches. */ tic6x_match_matches, /* Bad coarse form. */ tic6x_match_coarse, /* Not constant. */ tic6x_match_non_const, /* Register on wrong side. */ tic6x_match_wrong_side, /* Not a valid address register. */ tic6x_match_bad_address, /* Not a valid return address register. */ tic6x_match_bad_return, /* Control register not readable. */ tic6x_match_ctrl_write_only, /* Control register not writable. */ tic6x_match_ctrl_read_only, /* Not a valid memory reference for this instruction. */ tic6x_match_bad_mem } tic6x_operand_match; /* Return whether an operand matches the given fine-grained form and read/write usage, and, if it does not match, how it fails to match. The main functional unit side is SIDE; the cross-path side is CROSS (the same as SIDE if a cross path not used); the data side is DATA_SIDE. */ static tic6x_operand_match tic6x_operand_matches_form (const tic6x_operand *op, tic6x_operand_form form, tic6x_rw rw, unsigned int side, unsigned int cross, unsigned int data_side) { unsigned int coarse = tic6x_coarse_operand_form (form); if (coarse != op->form) return tic6x_match_coarse; switch (form) { case tic6x_operand_asm_const: if (op->value.exp.X_op == O_constant) return tic6x_match_matches; else return tic6x_match_non_const; case tic6x_operand_link_const: case tic6x_operand_irp: case tic6x_operand_nrp: case tic6x_operand_func_unit: /* All expressions are link-time constants, although there may not be relocations to express them in the output file. "irp" and "nrp" are unique operand values. All parsed functional unit names are valid. */ return tic6x_match_matches; case tic6x_operand_reg: case tic6x_operand_regpair: if (op->value.reg.side == side) return tic6x_match_matches; else return tic6x_match_wrong_side; case tic6x_operand_xreg: case tic6x_operand_xregpair: if (op->value.reg.side == cross) return tic6x_match_matches; else return tic6x_match_wrong_side; case tic6x_operand_dreg: case tic6x_operand_dregpair: if (op->value.reg.side == data_side) return tic6x_match_matches; else return tic6x_match_wrong_side; case tic6x_operand_areg: if (op->value.reg.side != cross) return tic6x_match_wrong_side; else if (op->value.reg.side == 2 && (op->value.reg.num == 14 || op->value.reg.num == 15)) return tic6x_match_matches; else return tic6x_match_bad_address; case tic6x_operand_retreg: if (op->value.reg.side != side) return tic6x_match_wrong_side; else if (op->value.reg.num != 3) return tic6x_match_bad_return; else return tic6x_match_matches; case tic6x_operand_ctrl: switch (rw) { case tic6x_rw_read: if (tic6x_ctrl_table[op->value.ctrl].rw == tic6x_rw_read || tic6x_ctrl_table[op->value.ctrl].rw == tic6x_rw_read_write) return tic6x_match_matches; else return tic6x_match_ctrl_write_only; case tic6x_rw_write: if (tic6x_ctrl_table[op->value.ctrl].rw == tic6x_rw_write || tic6x_ctrl_table[op->value.ctrl].rw == tic6x_rw_read_write) return tic6x_match_matches; else return tic6x_match_ctrl_read_only; default: abort (); } case tic6x_operand_mem_deref: if (op->value.mem.mod != tic6x_mem_mod_none) return tic6x_match_bad_mem; else if (op->value.mem.scaled != tic6x_offset_none) abort (); else if (op->value.mem.base_reg.side != side) return tic6x_match_bad_mem; else return tic6x_match_matches; case tic6x_operand_mem_short: case tic6x_operand_mem_ndw: if (op->value.mem.base_reg.side != side) return tic6x_match_bad_mem; if (op->value.mem.mod == tic6x_mem_mod_none) { if (op->value.mem.scaled != tic6x_offset_none) abort (); return tic6x_match_matches; } if (op->value.mem.scaled == tic6x_offset_none) { if (op->value.mem.mod == tic6x_mem_mod_plus || op->value.mem.mod == tic6x_mem_mod_minus) abort (); return tic6x_match_matches; } if (op->value.mem.offset_is_reg) { if (op->value.mem.scaled == tic6x_offset_unscaled && form != tic6x_operand_mem_ndw) abort (); if (op->value.mem.offset.reg.side == side) return tic6x_match_matches; else return tic6x_match_bad_mem; } else { if (op->value.mem.offset.exp.X_op == O_constant) return tic6x_match_matches; else return tic6x_match_bad_mem; } case tic6x_operand_mem_long: if (op->value.mem.base_reg.side == 2 && (op->value.mem.base_reg.num == 14 || op->value.mem.base_reg.num == 15)) { switch (op->value.mem.mod) { case tic6x_mem_mod_none: if (op->value.mem.scaled != tic6x_offset_none) abort (); return tic6x_match_matches; case tic6x_mem_mod_plus: if (op->value.mem.scaled == tic6x_offset_none) abort (); if (op->value.mem.offset_is_reg) return tic6x_match_bad_mem; else if (op->value.mem.scaled == tic6x_offset_scaled && op->value.mem.offset.exp.X_op != O_constant) return tic6x_match_bad_mem; else return tic6x_match_matches; case tic6x_mem_mod_minus: case tic6x_mem_mod_preinc: case tic6x_mem_mod_predec: case tic6x_mem_mod_postinc: case tic6x_mem_mod_postdec: return tic6x_match_bad_mem; default: abort (); } } else return tic6x_match_bad_mem; default: abort (); } } /* Return the number of bits shift used with DP-relative coding method CODING. */ static unsigned int tic6x_dpr_shift (tic6x_coding_method coding) { switch (coding) { case tic6x_coding_ulcst_dpr_byte: return 0; case tic6x_coding_ulcst_dpr_half: return 1; case tic6x_coding_ulcst_dpr_word: return 2; default: abort (); } } /* Return the relocation used with DP-relative coding method CODING. */ static bfd_reloc_code_real_type tic6x_dpr_reloc (tic6x_coding_method coding) { switch (coding) { case tic6x_coding_ulcst_dpr_byte: return BFD_RELOC_C6000_SBR_U15_B; case tic6x_coding_ulcst_dpr_half: return BFD_RELOC_C6000_SBR_U15_H; case tic6x_coding_ulcst_dpr_word: return BFD_RELOC_C6000_SBR_U15_W; default: abort (); } } /* Given a memory reference *MEM_REF as originally parsed, fill in defaults for missing offsets. */ static void tic6x_default_mem_ref (tic6x_mem_ref *mem_ref) { switch (mem_ref->mod) { case tic6x_mem_mod_none: if (mem_ref->scaled != tic6x_offset_none) abort (); mem_ref->mod = tic6x_mem_mod_plus; mem_ref->scaled = tic6x_offset_unscaled; mem_ref->offset_is_reg = FALSE; memset (&mem_ref->offset.exp, 0, sizeof mem_ref->offset.exp); mem_ref->offset.exp.X_op = O_constant; mem_ref->offset.exp.X_add_number = 0; mem_ref->offset.exp.X_unsigned = 0; break; case tic6x_mem_mod_plus: case tic6x_mem_mod_minus: if (mem_ref->scaled == tic6x_offset_none) abort (); break; case tic6x_mem_mod_preinc: case tic6x_mem_mod_predec: case tic6x_mem_mod_postinc: case tic6x_mem_mod_postdec: if (mem_ref->scaled != tic6x_offset_none) break; mem_ref->scaled = tic6x_offset_scaled; mem_ref->offset_is_reg = FALSE; memset (&mem_ref->offset.exp, 0, sizeof mem_ref->offset.exp); mem_ref->offset.exp.X_op = O_constant; mem_ref->offset.exp.X_add_number = 1; mem_ref->offset.exp.X_unsigned = 0; break; default: abort (); } } /* Return the encoding in the 8-bit field of an SPMASK or SPMASKR instruction of the specified UNIT, side SIDE. */ static unsigned int tic6x_encode_spmask (tic6x_func_unit_base unit, unsigned int side) { switch (unit) { case tic6x_func_unit_l: return 1 << (side - 1); case tic6x_func_unit_s: return 1 << (side + 1); case tic6x_func_unit_d: return 1 << (side + 3); case tic6x_func_unit_m: return 1 << (side + 5); default: abort (); } } /* Try to encode the instruction with opcode number ID and operands OPERANDS (number NUM_OPERANDS), creg value THIS_LINE_CREG and z value THIS_LINE_Z; FUNC_UNIT_SIDE, FUNC_UNIT_CROSS and FUNC_UNIT_DATA_SIDE describe the functional unit specification; SPLOOP_II is the ii value from the previous SPLOOP-family instruction, or 0 if not in such a loop; the only possible problems are operands being out of range (they already match the fine-grained form), and inappropriate predication. If this succeeds, return the encoding and set *OK to TRUE; otherwise return 0 and set *OK to FALSE. If a fix is needed, set *FIX_NEEDED to true and fill in *FIX_EXP, *FIX_PCREL, *FX_R_TYPE and *FIX_ADDA. Print error messages for failure if PRINT_ERRORS is TRUE; the opcode starts at STR and has length OPC_LEN. */ static unsigned int tic6x_try_encode (tic6x_opcode_id id, tic6x_operand *operands, unsigned int num_operands, unsigned int this_line_creg, unsigned int this_line_z, unsigned int func_unit_side, unsigned int func_unit_cross, unsigned int func_unit_data_side, int sploop_ii, expressionS **fix_exp, int *fix_pcrel, bfd_reloc_code_real_type *fx_r_type, bfd_boolean *fix_adda, bfd_boolean *fix_needed, bfd_boolean *ok, bfd_boolean print_errors, char *str, int opc_len) { const tic6x_opcode *opct; const tic6x_insn_format *fmt; unsigned int opcode_value; unsigned int fld; opct = &tic6x_opcode_table[id]; fmt = &tic6x_insn_format_table[opct->format]; opcode_value = fmt->cst_bits; for (fld = 0; fld < opct->num_fixed_fields; fld++) { if (opct->fixed_fields[fld].min_val == opct->fixed_fields[fld].max_val) { const tic6x_insn_field *fldd; fldd = tic6x_field_from_fmt (fmt, opct->fixed_fields[fld].field_id); if (fldd == NULL) abort (); opcode_value |= opct->fixed_fields[fld].min_val << fldd->low_pos; } } for (fld = 0; fld < opct->num_variable_fields; fld++) { const tic6x_insn_field *fldd; unsigned int value; unsigned int opno; unsigned int ffld; offsetT sign_value; unsigned int bits; unsigned int fcyc_bits; expressionS *expp; expressionS ucexp; tic6x_mem_ref mem; fldd = tic6x_field_from_fmt (fmt, opct->variable_fields[fld].field_id); if (fldd == NULL) abort (); opno = opct->variable_fields[fld].operand_num; switch (opct->variable_fields[fld].coding_method) { case tic6x_coding_ucst: if (operands[opno].form != TIC6X_OP_EXP) abort (); if (operands[opno].value.exp.X_op != O_constant) abort (); ucexp = operands[opno].value.exp; unsigned_constant: if (ucexp.X_add_number < 0 || ucexp.X_add_number >= (1 << fldd->width)) { if (print_errors) as_bad (_("operand %u of '%.*s' out of range"), opno + 1, opc_len, str); *ok = FALSE; return 0; } value = ucexp.X_add_number; break; case tic6x_coding_scst: if (operands[opno].form != TIC6X_OP_EXP) abort (); if (operands[opno].value.exp.X_op != O_constant) { value = 0; /* Opcode table should not permit non-constants without a known relocation for them. */ if (fldd->low_pos != 7 || fldd->width != 16) abort (); *fix_needed = TRUE; *fix_exp = &operands[opno].value.exp; *fix_pcrel = 0; *fx_r_type = BFD_RELOC_C6000_ABS_S16; *fix_adda = FALSE; break; } sign_value = SEXT (operands[opno].value.exp.X_add_number); signed_constant: if (sign_value < -(1 << (fldd->width - 1)) || (sign_value >= (1 << (fldd->width - 1)))) { if (print_errors) as_bad (_("operand %u of '%.*s' out of range"), opno + 1, opc_len, str); *ok = FALSE; return 0; } value = sign_value + (1 << (fldd->width - 1)); value ^= (1 << (fldd->width - 1)); break; case tic6x_coding_ucst_minus_one: if (operands[opno].form != TIC6X_OP_EXP) abort (); if (operands[opno].value.exp.X_op != O_constant) abort (); if (operands[opno].value.exp.X_add_number <= 0 || operands[opno].value.exp.X_add_number > (1 << fldd->width)) { if (print_errors) as_bad (_("operand %u of '%.*s' out of range"), opno + 1, opc_len, str); *ok = FALSE; return 0; } value = operands[opno].value.exp.X_add_number - 1; break; case tic6x_coding_scst_negate: if (operands[opno].form != TIC6X_OP_EXP) abort (); if (operands[opno].value.exp.X_op != O_constant) abort (); sign_value = SEXT (-operands[opno].value.exp.X_add_number); goto signed_constant; case tic6x_coding_ulcst_dpr_byte: case tic6x_coding_ulcst_dpr_half: case tic6x_coding_ulcst_dpr_word: bits = tic6x_dpr_shift (opct->variable_fields[fld].coding_method); switch (operands[opno].form) { case TIC6X_OP_EXP: if (operands[opno].value.exp.X_op == O_constant) { ucexp = operands[opno].value.exp; goto unsigned_constant; } expp = &operands[opno].value.exp; break; case TIC6X_OP_MEM_NOUNREG: mem = operands[opno].value.mem; tic6x_default_mem_ref (&mem); if (mem.offset_is_reg) abort (); if (mem.offset.exp.X_op == O_constant) { ucexp = mem.offset.exp; if (mem.scaled == tic6x_offset_unscaled) { if (ucexp.X_add_number & ((1 << bits) - 1)) { if (print_errors) as_bad (_("offset in operand %u of '%.*s' not " "divisible by %u"), opno + 1, opc_len, str, 1u << bits); *ok = FALSE; return 0; } ucexp.X_add_number >>= bits; } goto unsigned_constant; } if (mem.scaled != tic6x_offset_unscaled) abort (); if (operands[opno].value.mem.mod == tic6x_mem_mod_none || operands[opno].value.mem.scaled != tic6x_offset_unscaled || operands[opno].value.mem.offset_is_reg) abort (); expp = &operands[opno].value.mem.offset.exp; break; default: abort (); } value = 0; /* Opcode table should not use this encoding without a known relocation. */ if (fldd->low_pos != 8 || fldd->width != 15) abort (); /* We do not check for offset divisibility here; such a check is not needed at this point to encode the value, and if there is eventually a problem it will be detected either in md_apply_fix or at link time. */ *fix_needed = TRUE; *fix_exp = expp; *fix_pcrel = 0; *fx_r_type = tic6x_dpr_reloc (opct->variable_fields[fld].coding_method); if (operands[opno].form == TIC6X_OP_EXP) *fix_adda = TRUE; else *fix_adda = FALSE; break; case tic6x_coding_lcst_low16: if (operands[opno].form != TIC6X_OP_EXP) abort (); if (operands[opno].value.exp.X_op == O_constant) value = operands[opno].value.exp.X_add_number & 0xffff; else { value = 0; /* Opcode table should not use this encoding without a known relocation. */ if (fldd->low_pos != 7 || fldd->width != 16) abort (); *fix_needed = TRUE; *fix_exp = &operands[opno].value.exp; *fix_pcrel = 0; *fx_r_type = BFD_RELOC_C6000_ABS_L16; *fix_adda = FALSE; } break; case tic6x_coding_lcst_high16: if (operands[opno].form != TIC6X_OP_EXP) abort (); if (operands[opno].value.exp.X_op == O_constant) value = (operands[opno].value.exp.X_add_number >> 16) & 0xffff; else { value = 0; /* Opcode table should not use this encoding without a known relocation. */ if (fldd->low_pos != 7 || fldd->width != 16) abort (); *fix_needed = TRUE; *fix_exp = &operands[opno].value.exp; *fix_pcrel = 0; *fx_r_type = BFD_RELOC_C6000_ABS_H16; *fix_adda = FALSE; } break; case tic6x_coding_pcrel: case tic6x_coding_pcrel_half: if (operands[opno].form != TIC6X_OP_EXP) abort (); value = 0; *fix_needed = TRUE; *fix_exp = &operands[opno].value.exp; *fix_pcrel = 1; if (fldd->low_pos == 7 && fldd->width == 21) *fx_r_type = BFD_RELOC_C6000_PCR_S21; else if (fldd->low_pos == 16 && fldd->width == 12) *fx_r_type = BFD_RELOC_C6000_PCR_S12; else if (fldd->low_pos == 13 && fldd->width == 10) *fx_r_type = BFD_RELOC_C6000_PCR_S10; else if (fldd->low_pos == 16 && fldd->width == 7) *fx_r_type = BFD_RELOC_C6000_PCR_S7; else /* Opcode table should not use this encoding without a known relocation. */ abort (); *fix_adda = FALSE; break; case tic6x_coding_reg: switch (operands[opno].form) { case TIC6X_OP_REG: case TIC6X_OP_REGPAIR: value = operands[opno].value.reg.num; break; case TIC6X_OP_MEM_NOUNREG: case TIC6X_OP_MEM_UNREG: value = operands[opno].value.mem.base_reg.num; break; default: abort (); } break; case tic6x_coding_areg: switch (operands[opno].form) { case TIC6X_OP_REG: value = (operands[opno].value.reg.num == 15 ? 1 : 0); break; case TIC6X_OP_MEM_NOUNREG: value = (operands[opno].value.mem.base_reg.num == 15 ? 1 : 0); break; default: abort (); } break; case tic6x_coding_crlo: if (operands[opno].form != TIC6X_OP_CTRL) abort (); value = tic6x_ctrl_table[operands[opno].value.ctrl].crlo; break; case tic6x_coding_crhi: if (operands[opno].form != TIC6X_OP_CTRL) abort (); value = 0; break; case tic6x_coding_reg_shift: if (operands[opno].form != TIC6X_OP_REGPAIR) abort (); value = operands[opno].value.reg.num >> 1; break; case tic6x_coding_mem_offset: if (operands[opno].form != TIC6X_OP_MEM_NOUNREG) abort (); mem = operands[opno].value.mem; tic6x_default_mem_ref (&mem); if (mem.offset_is_reg) { if (mem.scaled != tic6x_offset_scaled) abort (); value = mem.offset.reg.num; } else { int scale; if (mem.offset.exp.X_op != O_constant) abort (); switch (mem.scaled) { case tic6x_offset_scaled: scale = 1; break; case tic6x_offset_unscaled: scale = opct->operand_info[opno].size; if (scale != 1 && scale != 2 && scale != 4 && scale != 8) abort (); break; default: abort (); } if (mem.offset.exp.X_add_number < 0 || mem.offset.exp.X_add_number >= (1 << fldd->width) * scale) { if (print_errors) as_bad (_("offset in operand %u of '%.*s' out of range"), opno + 1, opc_len, str); *ok = FALSE; return 0; } if (mem.offset.exp.X_add_number % scale) { if (print_errors) as_bad (_("offset in operand %u of '%.*s' not " "divisible by %u"), opno + 1, opc_len, str, scale); *ok = FALSE; return 0; } value = mem.offset.exp.X_add_number / scale; } break; case tic6x_coding_mem_offset_noscale: if (operands[opno].form != TIC6X_OP_MEM_UNREG) abort (); mem = operands[opno].value.mem; tic6x_default_mem_ref (&mem); if (mem.offset_is_reg) value = mem.offset.reg.num; else { if (mem.offset.exp.X_op != O_constant) abort (); if (mem.offset.exp.X_add_number < 0 || mem.offset.exp.X_add_number >= (1 << fldd->width)) { if (print_errors) as_bad (_("offset in operand %u of '%.*s' out of range"), opno + 1, opc_len, str); *ok = FALSE; return 0; } value = mem.offset.exp.X_add_number; } break; case tic6x_coding_mem_mode: if (operands[opno].form != TIC6X_OP_MEM_NOUNREG && operands[opno].form != TIC6X_OP_MEM_UNREG) abort (); mem = operands[opno].value.mem; tic6x_default_mem_ref (&mem); switch (mem.mod) { case tic6x_mem_mod_plus: value = 1; break; case tic6x_mem_mod_minus: value = 0; break; case tic6x_mem_mod_preinc: value = 9; break; case tic6x_mem_mod_predec: value = 8; break; case tic6x_mem_mod_postinc: value = 11; break; case tic6x_mem_mod_postdec: value = 10; break; default: abort (); } value += (mem.offset_is_reg ? 4 : 0); break; case tic6x_coding_scaled: if (operands[opno].form != TIC6X_OP_MEM_UNREG) abort (); mem = operands[opno].value.mem; tic6x_default_mem_ref (&mem); switch (mem.scaled) { case tic6x_offset_unscaled: value = 0; break; case tic6x_offset_scaled: value = 1; break; default: abort (); } break; case tic6x_coding_spmask: /* The position of such a field is hardcoded in the handling of "||^". */ if (fldd->low_pos != 18) abort (); value = 0; for (opno = 0; opno < num_operands; opno++) { unsigned int v; v = tic6x_encode_spmask (operands[opno].value.func_unit.base, operands[opno].value.func_unit.side); if (value & v) { if (print_errors) as_bad (_("functional unit already masked for operand " "%u of '%.*s'"), opno + 1, opc_len, str); *ok = FALSE; return 0; } value |= v; } break; case tic6x_coding_reg_unused: /* This is a placeholder; correct handling goes along with resource constraint checks. */ value = 0; break; case tic6x_coding_fstg: case tic6x_coding_fcyc: if (operands[opno].form != TIC6X_OP_EXP) abort (); if (operands[opno].value.exp.X_op != O_constant) abort (); if (!sploop_ii) { if (print_errors) as_bad (_("'%.*s' instruction not in a software " "pipelined loop"), opc_len, str); *ok = FALSE; return 0; } if (sploop_ii <= 1) fcyc_bits = 0; else if (sploop_ii <= 2) fcyc_bits = 1; else if (sploop_ii <= 4) fcyc_bits = 2; else if (sploop_ii <= 8) fcyc_bits = 3; else if (sploop_ii <= 14) fcyc_bits = 4; else abort (); if (fcyc_bits > fldd->width) abort (); if (opct->variable_fields[fld].coding_method == tic6x_coding_fstg) { int i, t; if (operands[opno].value.exp.X_add_number < 0 || (operands[opno].value.exp.X_add_number >= (1 << (fldd->width - fcyc_bits)))) { if (print_errors) as_bad (_("operand %u of '%.*s' out of range"), opno + 1, opc_len, str); *ok = FALSE; return 0; } value = operands[opno].value.exp.X_add_number; for (t = 0, i = fcyc_bits; i < fldd->width; i++) { t = (t << 1) | (value & 1); value >>= 1; } value = t << fcyc_bits; } else { if (operands[opno].value.exp.X_add_number < 0 || (operands[opno].value.exp.X_add_number >= sploop_ii)) { if (print_errors) as_bad (_("operand %u of '%.*s' out of range"), opno + 1, opc_len, str); *ok = FALSE; return 0; } value = operands[opno].value.exp.X_add_number; } break; case tic6x_coding_fu: value = func_unit_side == 2 ? 1 : 0; break; case tic6x_coding_data_fu: value = func_unit_data_side == 2 ? 1 : 0; break; case tic6x_coding_xpath: value = func_unit_cross; break; default: abort (); } for (ffld = 0; ffld < opct->num_fixed_fields; ffld++) if ((opct->fixed_fields[ffld].field_id == opct->variable_fields[fld].field_id) && (value < opct->fixed_fields[ffld].min_val || value > opct->fixed_fields[ffld].max_val)) { if (print_errors) as_bad (_("operand %u of '%.*s' out of range"), opno + 1, opc_len, str); *ok = FALSE; return 0; } opcode_value |= value << fldd->low_pos; } if (this_line_creg) { const tic6x_insn_field *creg; const tic6x_insn_field *z; creg = tic6x_field_from_fmt (fmt, tic6x_field_creg); if (creg == NULL) { if (print_errors) as_bad (_("instruction '%.*s' cannot be predicated"), opc_len, str); *ok = FALSE; return 0; } z = tic6x_field_from_fmt (fmt, tic6x_field_z); /* If there is a creg field, there must be a z field; otherwise there is an error in the format table. */ if (z == NULL) abort (); opcode_value |= this_line_creg << creg->low_pos; opcode_value |= this_line_z << z->low_pos; } *ok = TRUE; return opcode_value; } /* Convert the target integer stored in N bytes in BUF to a host integer, returning that value. */ static valueT md_chars_to_number (char *buf, int n) { valueT result = 0; unsigned char *p = (unsigned char *) buf; if (target_big_endian) { while (n--) { result <<= 8; result |= (*p++ & 0xff); } } else { while (n--) { result <<= 8; result |= (p[n] & 0xff); } } return result; } /* Assemble the instruction starting at STR (an opcode, with the opcode name all-lowercase). */ void md_assemble (char *str) { char *p; int opc_len; bfd_boolean this_line_parallel; bfd_boolean this_line_spmask; unsigned int this_line_creg; unsigned int this_line_z; tic6x_label_list *this_insn_label_list; segment_info_type *seginfo; tic6x_opcode_list *opc_list, *opc; tic6x_func_unit_base func_unit_base = tic6x_func_unit_nfu; unsigned int func_unit_side = 0; unsigned int func_unit_cross = 0; unsigned int cross_side = 0; unsigned int func_unit_data_side = 0; unsigned int max_matching_opcodes, num_matching_opcodes; tic6x_opcode_id *opcm = NULL; unsigned int opc_rank[TIC6X_NUM_PREFER]; const tic6x_opcode *opct = NULL; int min_rank, try_rank, max_rank; bfd_boolean num_operands_permitted[TIC6X_MAX_SOURCE_OPERANDS + 1] = { FALSE }; unsigned int operand_forms[TIC6X_MAX_SOURCE_OPERANDS] = { 0 }; tic6x_operand operands[TIC6X_MAX_SOURCE_OPERANDS]; unsigned int max_num_operands; unsigned int num_operands_read; bfd_boolean ok_this_arch, ok_this_fu, ok_this_arch_fu; bfd_boolean bad_operands = FALSE; unsigned int opcode_value; bfd_boolean encoded_ok; bfd_boolean fix_needed = FALSE; expressionS *fix_exp = NULL; int fix_pcrel = 0; bfd_reloc_code_real_type fx_r_type = BFD_RELOC_UNUSED; bfd_boolean fix_adda = FALSE; fragS *insn_frag; char *output; p = str; while (*p && !is_end_of_line[(unsigned char) *p] && *p != ' ') p++; /* This function should only have been called when there is actually an instruction to assemble. */ if (p == str) abort (); /* Now an instruction has been seen, architecture attributes from .arch directives merge with rather than overriding the previous value. */ tic6x_seen_insns = TRUE; /* If no .arch directives or -march options have been seen, we are assessing instruction validity based on the C674X default, so set the attribute accordingly. */ if (tic6x_arch_attribute == C6XABI_Tag_ISA_none) tic6x_arch_attribute = C6XABI_Tag_ISA_C674X; /* Reset global settings for parallel bars and predicates now to avoid extra errors if there are problems with this opcode. */ this_line_parallel = tic6x_line_parallel; this_line_spmask = tic6x_line_spmask; this_line_creg = tic6x_line_creg; this_line_z = tic6x_line_z; tic6x_line_parallel = FALSE; tic6x_line_spmask = FALSE; tic6x_line_creg = 0; tic6x_line_z = 0; seginfo = seg_info (now_seg); this_insn_label_list = seginfo->tc_segment_info_data.label_list; seginfo->tc_segment_info_data.label_list = NULL; opc_list = hash_find_n (opcode_hash, str, p - str); if (opc_list == NULL) { char c = *p; *p = 0; as_bad (_("unknown opcode '%s'"), str); *p = c; return; } opc_len = p - str; skip_whitespace (p); /* See if there is something that looks like a functional unit specifier. */ if (*p == '.') { bfd_boolean good_func_unit; tic6x_func_unit_base maybe_base = tic6x_func_unit_nfu; unsigned int maybe_side = 0; unsigned int maybe_cross = 0; unsigned int maybe_data_side = 0; good_func_unit = tic6x_parse_func_unit_base (p + 1, &maybe_base, &maybe_side); if (good_func_unit) { if (p[3] == ' ' || is_end_of_line[(unsigned char) p[3]]) p += 3; else if ((p[3] == 'x' || p[3] == 'X') && (p[4] == ' ' || is_end_of_line[(unsigned char) p[4]])) { maybe_cross = 1; p += 4; } else if (maybe_base == tic6x_func_unit_d && (p[3] == 't' || p[3] == 'T') && (p[4] == '1' || p[4] == '2') && (p[5] == ' ' || is_end_of_line[(unsigned char) p[5]])) { maybe_data_side = p[4] - '0'; p += 5; } else good_func_unit = FALSE; } if (good_func_unit) { func_unit_base = maybe_base; func_unit_side = maybe_side; func_unit_cross = maybe_cross; cross_side = (func_unit_cross ? 3 - func_unit_side : func_unit_side); func_unit_data_side = maybe_data_side; } skip_whitespace (p); } /* Determine which entries in the opcode table match, and the associated permitted forms of operands. */ max_matching_opcodes = 0; for (opc = opc_list; opc; opc = opc->next) max_matching_opcodes++; num_matching_opcodes = 0; opcm = xmalloc (max_matching_opcodes * sizeof (*opcm)); max_num_operands = 0; ok_this_arch = FALSE; ok_this_fu = FALSE; ok_this_arch_fu = FALSE; for (opc = opc_list; opc; opc = opc->next) { unsigned int num_operands; unsigned int i; bfd_boolean this_opc_arch_ok = TRUE; bfd_boolean this_opc_fu_ok = TRUE; if (tic6x_insn_format_table[tic6x_opcode_table[opc->id].format].num_bits != 32) continue; if (!(tic6x_opcode_table[opc->id].isa_variants & tic6x_features)) this_opc_arch_ok = FALSE; if (tic6x_opcode_table[opc->id].func_unit != func_unit_base) this_opc_fu_ok = FALSE; if (func_unit_side == 1 && (tic6x_opcode_table[opc->id].flags & TIC6X_FLAG_SIDE_B_ONLY)) this_opc_fu_ok = FALSE; if (func_unit_cross && (tic6x_opcode_table[opc->id].flags & TIC6X_FLAG_NO_CROSS)) this_opc_fu_ok = FALSE; if (!func_unit_data_side && (tic6x_opcode_table[opc->id].flags & (TIC6X_FLAG_LOAD | TIC6X_FLAG_STORE))) this_opc_fu_ok = FALSE; if (func_unit_data_side && !(tic6x_opcode_table[opc->id].flags & (TIC6X_FLAG_LOAD | TIC6X_FLAG_STORE))) this_opc_fu_ok = FALSE; if (func_unit_data_side == 1 && (tic6x_opcode_table[opc->id].flags & TIC6X_FLAG_SIDE_T2_ONLY)) this_opc_fu_ok = FALSE; if (this_opc_arch_ok) ok_this_arch = TRUE; if (this_opc_fu_ok) ok_this_fu = TRUE; if (!this_opc_arch_ok || !this_opc_fu_ok) continue; ok_this_arch_fu = TRUE; opcm[num_matching_opcodes] = opc->id; num_matching_opcodes++; num_operands = tic6x_opcode_table[opc->id].num_operands; if (tic6x_opcode_table[opc->id].flags & TIC6X_FLAG_SPMASK) { if (num_operands != 1 || (tic6x_opcode_table[opc->id].operand_info[0].form != tic6x_operand_func_unit)) abort (); num_operands = 8; for (i = 0; i < num_operands; i++) { operand_forms[i] |= tic6x_coarse_operand_form (tic6x_operand_func_unit); num_operands_permitted[i] = TRUE; } } else { for (i = 0; i < num_operands; i++) { tic6x_operand_form f = tic6x_opcode_table[opc->id].operand_info[i].form; operand_forms[i] |= tic6x_coarse_operand_form (f); } } num_operands_permitted[num_operands] = TRUE; if (num_operands > max_num_operands) max_num_operands = num_operands; } if (!ok_this_arch) { as_bad (_("'%.*s' instruction not supported on this architecture"), opc_len, str); free (opcm); return; } if (!ok_this_fu) { as_bad (_("'%.*s' instruction not supported on this functional unit"), opc_len, str); free (opcm); return; } if (!ok_this_arch_fu) { as_bad (_("'%.*s' instruction not supported on this functional unit" " for this architecture"), opc_len, str); free (opcm); return; } /* If there were no instructions matching the above availability checks, we should now have given an error and returned. */ if (num_matching_opcodes == 0) abort (); num_operands_read = 0; while (TRUE) { skip_whitespace (p); if (is_end_of_line[(unsigned char) *p]) { if (num_operands_read > 0) { as_bad (_("missing operand after comma")); bad_operands = TRUE; } break; } if (max_num_operands == 0) { as_bad (_("too many operands to '%.*s'"), opc_len, str); bad_operands = TRUE; break; } if (!tic6x_parse_operand (&p, &operands[num_operands_read], operand_forms[num_operands_read], str, opc_len, num_operands_read + 1)) bad_operands = TRUE; num_operands_read++; if (is_end_of_line[(unsigned char) *p]) break; else if (*p == ',') { p++; if (num_operands_read == max_num_operands) { as_bad (_("too many operands to '%.*s'"), opc_len, str); bad_operands = TRUE; break; } continue; } else /* Operand parsing should consume whole operands. */ abort (); } if (!bad_operands && !num_operands_permitted[num_operands_read]) { as_bad (_("bad number of operands to '%.*s'"), opc_len, str); bad_operands = TRUE; } if (!bad_operands) { /* Each operand is of the right syntactic form for some opcode choice, and the number of operands is valid. Check that each operand is OK in detail for some opcode choice with the right number of operands. */ unsigned int i; for (i = 0; i < num_operands_read; i++) { bfd_boolean coarse_ok = FALSE; bfd_boolean fine_ok = FALSE; tic6x_operand_match fine_failure = tic6x_match_matches; unsigned int j; for (j = 0; j < num_matching_opcodes; j++) { tic6x_operand_form f; tic6x_rw rw; unsigned int cf; tic6x_operand_match this_fine_failure; if (tic6x_opcode_table[opcm[j]].flags & TIC6X_FLAG_SPMASK) { f = tic6x_operand_func_unit; rw = tic6x_rw_none; } else { if (tic6x_opcode_table[opcm[j]].num_operands != num_operands_read) continue; f = tic6x_opcode_table[opcm[j]].operand_info[i].form; rw = tic6x_opcode_table[opcm[j]].operand_info[i].rw; } cf = tic6x_coarse_operand_form (f); if (operands[i].form != cf) continue; coarse_ok = TRUE; this_fine_failure = tic6x_operand_matches_form (&operands[i], f, rw, func_unit_side, cross_side, func_unit_data_side); if (this_fine_failure == tic6x_match_matches) { fine_ok = TRUE; break; } if (fine_failure == tic6x_match_matches || fine_failure > this_fine_failure) fine_failure = this_fine_failure; } /* No instructions should have operand syntactic forms only acceptable with certain numbers of operands, so no diagnostic for this case. */ if (!coarse_ok) abort (); if (!fine_ok) { switch (fine_failure) { case tic6x_match_non_const: as_bad (_("operand %u of '%.*s' not constant"), i + 1, opc_len, str); break; case tic6x_match_wrong_side: as_bad (_("operand %u of '%.*s' on wrong side"), i + 1, opc_len, str); break; case tic6x_match_bad_return: as_bad (_("operand %u of '%.*s' not a valid return " "address register"), i + 1, opc_len, str); break; case tic6x_match_ctrl_write_only: as_bad (_("operand %u of '%.*s' is write-only"), i + 1, opc_len, str); break; case tic6x_match_ctrl_read_only: as_bad (_("operand %u of '%.*s' is read-only"), i + 1, opc_len, str); break; case tic6x_match_bad_mem: as_bad (_("operand %u of '%.*s' not a valid memory " "reference"), i + 1, opc_len, str); break; case tic6x_match_bad_address: as_bad (_("operand %u of '%.*s' not a valid base " "address register"), i + 1, opc_len, str); break; default: abort (); } bad_operands = TRUE; break; } } } if (!bad_operands) { /* Each operand is OK for some opcode choice, and the number of operands is valid. Check whether there is an opcode choice for which all operands are simultaneously valid. */ unsigned int i; bfd_boolean found_match = FALSE; for (i = 0; i < TIC6X_NUM_PREFER; i++) opc_rank[i] = (unsigned int) -1; min_rank = TIC6X_NUM_PREFER - 1; max_rank = 0; for (i = 0; i < num_matching_opcodes; i++) { unsigned int j; bfd_boolean this_matches = TRUE; if (!(tic6x_opcode_table[opcm[i]].flags & TIC6X_FLAG_SPMASK) && tic6x_opcode_table[opcm[i]].num_operands != num_operands_read) continue; for (j = 0; j < num_operands_read; j++) { tic6x_operand_form f; tic6x_rw rw; if (tic6x_opcode_table[opcm[i]].flags & TIC6X_FLAG_SPMASK) { f = tic6x_operand_func_unit; rw = tic6x_rw_none; } else { f = tic6x_opcode_table[opcm[i]].operand_info[j].form; rw = tic6x_opcode_table[opcm[i]].operand_info[j].rw; } if (tic6x_operand_matches_form (&operands[j], f, rw, func_unit_side, cross_side, func_unit_data_side) != tic6x_match_matches) { this_matches = FALSE; break; } } if (this_matches) { int rank = TIC6X_PREFER_VAL (tic6x_opcode_table[opcm[i]].flags); if (rank < min_rank) min_rank = rank; if (rank > max_rank) max_rank = rank; if (opc_rank[rank] == (unsigned int) -1) opc_rank[rank] = i; else /* The opcode table should provide a total ordering for all cases where multiple matches may get here. */ abort (); found_match = TRUE; } } if (!found_match) { as_bad (_("bad operand combination for '%.*s'"), opc_len, str); bad_operands = TRUE; } } if (bad_operands) { free (opcm); return; } opcode_value = 0; encoded_ok = FALSE; for (try_rank = max_rank; try_rank >= min_rank; try_rank--) { fix_needed = FALSE; if (opc_rank[try_rank] == (unsigned int) -1) continue; opcode_value = tic6x_try_encode (opcm[opc_rank[try_rank]], operands, num_operands_read, this_line_creg, this_line_z, func_unit_side, func_unit_cross, func_unit_data_side, seginfo->tc_segment_info_data.sploop_ii, &fix_exp, &fix_pcrel, &fx_r_type, &fix_adda, &fix_needed, &encoded_ok, (try_rank == min_rank ? TRUE : FALSE), str, opc_len); if (encoded_ok) { opct = &tic6x_opcode_table[opcm[opc_rank[try_rank]]]; break; } } free (opcm); if (!encoded_ok) return; if (this_line_parallel) { insn_frag = seginfo->tc_segment_info_data.execute_packet_frag; if (insn_frag == NULL) { as_bad (_("parallel instruction not following another instruction")); return; } if (insn_frag->fr_fix >= 32) { as_bad (_("too many instructions in execute packet")); return; } if (this_insn_label_list != NULL) as_bad (_("label not at start of execute packet")); if (opct->flags & TIC6X_FLAG_FIRST) as_bad (_("'%.*s' instruction not at start of execute packet"), opc_len, str); *seginfo->tc_segment_info_data.last_insn_lsb |= 0x1; output = insn_frag->fr_literal + insn_frag->fr_fix; } else { tic6x_label_list *l; seginfo->tc_segment_info_data.spmask_addr = NULL; seginfo->tc_segment_info_data.func_units_used = 0; /* Start a new frag for this execute packet. */ if (frag_now_fix () != 0) { if (frag_now->fr_type != rs_machine_dependent) frag_wane (frag_now); frag_new (0); } frag_grow (32); insn_frag = seginfo->tc_segment_info_data.execute_packet_frag = frag_now; for (l = this_insn_label_list; l; l = l->next) { symbol_set_frag (l->label, frag_now); S_SET_VALUE (l->label, 0); S_SET_SEGMENT (l->label, now_seg); } tic6x_free_label_list (this_insn_label_list); dwarf2_emit_insn (0); output = frag_var (rs_machine_dependent, 32, 32, 0, NULL, 0, NULL); /* This must be the same as the frag to which a pointer was just saved. */ if (output != insn_frag->fr_literal) abort (); insn_frag->tc_frag_data.is_insns = TRUE; insn_frag->tc_frag_data.can_cross_fp_boundary = tic6x_can_cross_fp_boundary; } if (func_unit_base != tic6x_func_unit_nfu) { unsigned int func_unit_enc; func_unit_enc = tic6x_encode_spmask (func_unit_base, func_unit_side); if (seginfo->tc_segment_info_data.func_units_used & func_unit_enc) as_bad (_("functional unit already used in this execute packet")); seginfo->tc_segment_info_data.func_units_used |= func_unit_enc; } if (opct->flags & TIC6X_FLAG_SPLOOP) { if (seginfo->tc_segment_info_data.sploop_ii) as_bad (_("nested software pipelined loop")); if (num_operands_read != 1 || operands[0].form != TIC6X_OP_EXP || operands[0].value.exp.X_op != O_constant) abort (); seginfo->tc_segment_info_data.sploop_ii = operands[0].value.exp.X_add_number; } else if (opct->flags & TIC6X_FLAG_SPKERNEL) { if (!seginfo->tc_segment_info_data.sploop_ii) as_bad (_("'%.*s' instruction not in a software pipelined loop"), opc_len, str); seginfo->tc_segment_info_data.sploop_ii = 0; } if (this_line_spmask) { if (seginfo->tc_segment_info_data.spmask_addr == NULL) as_bad (_("'||^' without previous SPMASK")); else if (func_unit_base == tic6x_func_unit_nfu) as_bad (_("cannot mask instruction using no functional unit")); else { unsigned int spmask_opcode; unsigned int mask_bit; spmask_opcode = md_chars_to_number (seginfo->tc_segment_info_data.spmask_addr, 4); mask_bit = tic6x_encode_spmask (func_unit_base, func_unit_side); mask_bit <<= 18; if (spmask_opcode & mask_bit) as_bad (_("functional unit already masked")); spmask_opcode |= mask_bit; md_number_to_chars (seginfo->tc_segment_info_data.spmask_addr, spmask_opcode, 4); } } record_alignment (now_seg, 5); md_number_to_chars (output, opcode_value, 4); if (fix_needed) tic6x_fix_new_exp (insn_frag, output - insn_frag->fr_literal, 4, fix_exp, fix_pcrel, fx_r_type, fix_adda); insn_frag->fr_fix += 4; insn_frag->fr_var -= 4; seginfo->tc_segment_info_data.last_insn_lsb = (target_big_endian ? output + 3 : output); if (opct->flags & TIC6X_FLAG_SPMASK) seginfo->tc_segment_info_data.spmask_addr = output; } /* Modify NEWVAL (32-bit) by inserting VALUE, shifted right by SHIFT and the least significant BITS bits taken, at position POS. */ #define MODIFY_VALUE(NEWVAL, VALUE, SHIFT, POS, BITS) \ do { \ (NEWVAL) &= 0xffffffffU & ~(((1U << (BITS)) - 1) << (POS)); \ (NEWVAL) |= (((VALUE) >> (SHIFT)) & ((1U << (BITS)) - 1)) << (POS); \ } while (0) /* Apply a fixup to the object file. */ void md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED) { offsetT value = *valP; char *buf = fixP->fx_where + fixP->fx_frag->fr_literal; value = SEXT (value); *valP = value; fixP->fx_offset = SEXT (fixP->fx_offset); if (fixP->fx_addsy == NULL && fixP->fx_pcrel == 0) fixP->fx_done = 1; /* We do our own overflow checks. */ fixP->fx_no_overflow = 1; switch (fixP->fx_r_type) { case BFD_RELOC_NONE: case BFD_RELOC_C6000_EHTYPE: /* Force output to the object file. */ fixP->fx_done = 0; break; case BFD_RELOC_32: if (fixP->fx_done || !seg->use_rela_p) md_number_to_chars (buf, value, 4); break; case BFD_RELOC_16: if (fixP->fx_done || !seg->use_rela_p) { if (value < -0x8000 || value > 0xffff) as_bad_where (fixP->fx_file, fixP->fx_line, _("value too large for 2-byte field")); md_number_to_chars (buf, value, 2); } break; case BFD_RELOC_8: if (fixP->fx_done || !seg->use_rela_p) { if (value < -0x80 || value > 0xff) as_bad_where (fixP->fx_file, fixP->fx_line, _("value too large for 1-byte field")); md_number_to_chars (buf, value, 1); } break; case BFD_RELOC_C6000_ABS_S16: case BFD_RELOC_C6000_ABS_L16: case BFD_RELOC_C6000_SBR_S16: case BFD_RELOC_C6000_SBR_L16_B: case BFD_RELOC_C6000_SBR_L16_H: case BFD_RELOC_C6000_SBR_L16_W: case BFD_RELOC_C6000_SBR_GOT_L16_W: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); int shift; switch (fixP->fx_r_type) { case BFD_RELOC_C6000_SBR_L16_H: shift = 1; break; case BFD_RELOC_C6000_SBR_L16_W: case BFD_RELOC_C6000_SBR_GOT_L16_W: shift = 2; break; default: shift = 0; break; } MODIFY_VALUE (newval, value, shift, 7, 16); if ((value < -0x8000 || value > 0x7fff) && (fixP->fx_r_type == BFD_RELOC_C6000_ABS_S16 || fixP->fx_r_type == BFD_RELOC_C6000_SBR_S16)) as_bad_where (fixP->fx_file, fixP->fx_line, _("immediate offset out of range")); md_number_to_chars (buf, newval, 4); } if (fixP->fx_done && fixP->fx_r_type != BFD_RELOC_C6000_ABS_S16 && fixP->fx_r_type != BFD_RELOC_C6000_ABS_L16) abort (); break; case BFD_RELOC_C6000_ABS_H16: case BFD_RELOC_C6000_SBR_H16_B: case BFD_RELOC_C6000_SBR_H16_H: case BFD_RELOC_C6000_SBR_H16_W: case BFD_RELOC_C6000_SBR_GOT_H16_W: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); int shift; switch (fixP->fx_r_type) { case BFD_RELOC_C6000_SBR_H16_H: shift = 17; break; case BFD_RELOC_C6000_SBR_H16_W: case BFD_RELOC_C6000_SBR_GOT_H16_W: shift = 18; break; default: shift = 16; break; } MODIFY_VALUE (newval, value, shift, 7, 16); md_number_to_chars (buf, newval, 4); } if (fixP->fx_done && fixP->fx_r_type != BFD_RELOC_C6000_ABS_H16) abort (); break; case BFD_RELOC_C6000_PCR_H16: case BFD_RELOC_C6000_PCR_L16: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); int shift = fixP->fx_r_type == BFD_RELOC_C6000_PCR_H16 ? 16 : 0; MODIFY_VALUE (newval, value, shift, 7, 16); md_number_to_chars (buf, newval, 4); } break; case BFD_RELOC_C6000_SBR_U15_B: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); MODIFY_VALUE (newval, value, 0, 8, 15); if (value < 0 || value > 0x7fff) as_bad_where (fixP->fx_file, fixP->fx_line, _("immediate offset out of range")); md_number_to_chars (buf, newval, 4); } break; case BFD_RELOC_C6000_SBR_U15_H: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); /* Constant ADDA operands, processed as constant when the instruction is parsed, are encoded as-is rather than shifted. If the operand of an ADDA instruction is now constant (for example, the difference between two labels found after the instruction), ensure it is encoded the same way it would have been if the constant value had been known when the instruction was parsed. */ if (fixP->tc_fix_data.fix_adda && fixP->fx_done) value <<= 1; MODIFY_VALUE (newval, value, 1, 8, 15); if (value & 1) as_bad_where (fixP->fx_file, fixP->fx_line, _("immediate offset not 2-byte-aligned")); if (value < 0 || value > 0xfffe) as_bad_where (fixP->fx_file, fixP->fx_line, _("immediate offset out of range")); md_number_to_chars (buf, newval, 4); } break; case BFD_RELOC_C6000_SBR_U15_W: case BFD_RELOC_C6000_SBR_GOT_U15_W: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); /* Constant ADDA operands, processed as constant when the instruction is parsed, are encoded as-is rather than shifted. If the operand of an ADDA instruction is now constant (for example, the difference between two labels found after the instruction), ensure it is encoded the same way it would have been if the constant value had been known when the instruction was parsed. */ if (fixP->tc_fix_data.fix_adda && fixP->fx_done) value <<= 2; MODIFY_VALUE (newval, value, 2, 8, 15); if (value & 3) as_bad_where (fixP->fx_file, fixP->fx_line, _("immediate offset not 4-byte-aligned")); if (value < 0 || value > 0x1fffc) as_bad_where (fixP->fx_file, fixP->fx_line, _("immediate offset out of range")); md_number_to_chars (buf, newval, 4); } if (fixP->fx_done && fixP->fx_r_type != BFD_RELOC_C6000_SBR_U15_W) abort (); break; case BFD_RELOC_C6000_DSBT_INDEX: if (value != 0) as_bad_where (fixP->fx_file, fixP->fx_line, _("addend used with $DSBT_INDEX")); if (fixP->fx_done) abort (); break; case BFD_RELOC_C6000_PCR_S21: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); MODIFY_VALUE (newval, value, 2, 7, 21); if (value & 3) as_bad_where (fixP->fx_file, fixP->fx_line, _("PC-relative offset not 4-byte-aligned")); if (value < -0x400000 || value > 0x3ffffc) as_bad_where (fixP->fx_file, fixP->fx_line, _("PC-relative offset out of range")); md_number_to_chars (buf, newval, 4); } break; case BFD_RELOC_C6000_PCR_S12: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); MODIFY_VALUE (newval, value, 2, 16, 12); if (value & 3) as_bad_where (fixP->fx_file, fixP->fx_line, _("PC-relative offset not 4-byte-aligned")); if (value < -0x2000 || value > 0x1ffc) as_bad_where (fixP->fx_file, fixP->fx_line, _("PC-relative offset out of range")); md_number_to_chars (buf, newval, 4); } break; case BFD_RELOC_C6000_PCR_S10: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); MODIFY_VALUE (newval, value, 2, 13, 10); if (value & 3) as_bad_where (fixP->fx_file, fixP->fx_line, _("PC-relative offset not 4-byte-aligned")); if (value < -0x800 || value > 0x7fc) as_bad_where (fixP->fx_file, fixP->fx_line, _("PC-relative offset out of range")); md_number_to_chars (buf, newval, 4); } break; case BFD_RELOC_C6000_PCR_S7: if (fixP->fx_done || !seg->use_rela_p) { offsetT newval = md_chars_to_number (buf, 4); MODIFY_VALUE (newval, value, 2, 16, 7); if (value & 3) as_bad_where (fixP->fx_file, fixP->fx_line, _("PC-relative offset not 4-byte-aligned")); if (value < -0x100 || value > 0xfc) as_bad_where (fixP->fx_file, fixP->fx_line, _("PC-relative offset out of range")); md_number_to_chars (buf, newval, 4); } break; case BFD_RELOC_C6000_PREL31: /* Force output to the object file. */ fixP->fx_done = 0; break; default: abort (); } } /* Convert a floating-point number to target (IEEE) format. */ char * md_atof (int type, char *litP, int *sizeP) { return ieee_md_atof (type, litP, sizeP, target_big_endian); } /* Adjust the frags in SECTION (see tic6x_end). */ static void tic6x_adjust_section (bfd *abfd ATTRIBUTE_UNUSED, segT section, void *dummy ATTRIBUTE_UNUSED) { segment_info_type *info; frchainS *frchp; fragS *fragp; bfd_boolean have_code = FALSE; bfd_boolean have_non_code = FALSE; info = seg_info (section); if (info == NULL) return; for (frchp = info->frchainP; frchp; frchp = frchp->frch_next) for (fragp = frchp->frch_root; fragp; fragp = fragp->fr_next) switch (fragp->fr_type) { case rs_machine_dependent: if (fragp->tc_frag_data.is_insns) have_code = TRUE; break; case rs_dummy: case rs_fill: if (fragp->fr_fix > 0) have_non_code = TRUE; break; default: have_non_code = TRUE; break; } /* Process alignment requirements in a code-only section. */ if (have_code && !have_non_code) { /* If we need to insert an odd number of instructions to meet an alignment requirement, there must have been an odd number of instructions since the last 8-byte-aligned execute packet boundary. So there must have been an execute packet with an odd number (and so a number fewer than 8) of instructions into which we can insert a NOP without breaking any previous alignments. If then we need to insert a number 2 mod 4 of instructions, the number of instructions since the last 16-byte-aligned execute packet boundary must be 2 mod 4. So between that boundary and the following 8-byte-aligned boundary there must either be at least one execute packet with 2-mod-4 instructions, or at least two with an odd number of instructions; again, greedily inserting NOPs as soon as possible suffices to meet the alignment requirement. If then we need to insert 4 instructions, we look between the last 32-byte-aligned boundary and the following 16-byte-aligned boundary. The sizes of the execute packets in this range total 4 instructions mod 8, so again there is room for greedy insertion of NOPs to meet the alignment requirement, and before any intermediate point with 8-byte (2-instruction) alignment requirement the sizes of execute packets (and so the room for NOPs) will total 2 instructions mod 4 so greedy insertion will not break such alignments. So we can always meet these alignment requirements by inserting NOPs in parallel with existing execute packets, and by induction the approach described above inserts the minimum number of such NOPs. */ /* The number of NOPs we are currently looking to insert, if we have gone back to insert NOPs. */ unsigned int want_insert = 0; /* Out of that number, the number inserted so far in the current stage of the above algorithm. */ unsigned int want_insert_done_so_far = 0; /* The position mod 32 at the start of the current frag. */ unsigned int pos = 0; /* The locations in the frag chain of the most recent frags at the start of which there is the given alignment. */ frchainS *frchp_last32, *frchp_last16, *frchp_last8; fragS *fragp_last32, *fragp_last16, *fragp_last8; unsigned int pos_last32, pos_last16, pos_last8; frchp_last32 = frchp_last16 = frchp_last8 = info->frchainP; fragp_last32 = fragp_last16 = fragp_last8 = info->frchainP->frch_root; pos_last32 = pos_last16 = pos_last8 = 0; for (frchp = info->frchainP; frchp; frchp = frchp->frch_next) for (fragp = frchp->frch_root; fragp; fragp = fragp->fr_next) look_at_frag: { bfd_boolean go_back = FALSE; frchainS *frchp_next; fragS *fragp_next; if (fragp->fr_type != rs_machine_dependent) continue; if (fragp->tc_frag_data.is_insns && pos + fragp->fr_fix > 32 && !fragp->tc_frag_data.can_cross_fp_boundary) { /* As described above, we should always have met an alignment requirement by the time we come back to it. */ if (want_insert) abort (); if (pos & 3) abort (); want_insert = (32 - pos) >> 2; if (want_insert > 7) abort (); want_insert_done_so_far = 0; go_back = TRUE; } if (!fragp->tc_frag_data.is_insns) { unsigned int would_insert_bytes; if (!(pos & ((1 << fragp->fr_offset) - 1))) /* This alignment requirement is already met. */ continue; /* As described above, we should always have met an alignment requirement by the time we come back to it. */ if (want_insert) abort (); /* We may not be able to meet this requirement within the given number of characters. */ would_insert_bytes = ((1 << fragp->fr_offset) - (pos & ((1 << fragp->fr_offset) - 1))); if (fragp->fr_subtype != 0 && would_insert_bytes > fragp->fr_subtype) continue; /* An unmet alignment must be 8, 16 or 32 bytes; smaller ones must always be met within code-only sections and larger ones cause the section not to be code-only. */ if (fragp->fr_offset != 3 && fragp->fr_offset != 4 && fragp->fr_offset != 5) abort (); if (would_insert_bytes & 3) abort (); want_insert = would_insert_bytes >> 2; if (want_insert > 7) abort (); want_insert_done_so_far = 0; go_back = TRUE; } else if (want_insert && !go_back) { unsigned int num_insns = fragp->fr_fix >> 2; unsigned int max_poss_nops = 8 - num_insns; if (max_poss_nops) { unsigned int cur_want_nops, max_want_nops, do_nops, i; if (want_insert & 1) cur_want_nops = 1; else if (want_insert & 2) cur_want_nops = 2; else if (want_insert & 4) cur_want_nops = 4; else abort (); max_want_nops = cur_want_nops - want_insert_done_so_far; do_nops = (max_poss_nops < max_want_nops ? max_poss_nops : max_want_nops); for (i = 0; i < do_nops; i++) { md_number_to_chars (fragp->fr_literal + fragp->fr_fix, 0, 4); if (target_big_endian) fragp->fr_literal[fragp->fr_fix - 1] |= 0x1; else fragp->fr_literal[fragp->fr_fix - 4] |= 0x1; fragp->fr_fix += 4; fragp->fr_var -= 4; } want_insert_done_so_far += do_nops; if (want_insert_done_so_far == cur_want_nops) { want_insert -= want_insert_done_so_far; want_insert_done_so_far = 0; if (want_insert) go_back = TRUE; } } } if (go_back) { if (want_insert & 1) { frchp = frchp_last8; fragp = fragp_last8; pos = pos_last8; } else if (want_insert & 2) { frchp = frchp_last8 = frchp_last16; fragp = fragp_last8 = fragp_last16; pos = pos_last8 = pos_last16; } else if (want_insert & 4) { frchp = frchp_last8 = frchp_last16 = frchp_last32; fragp = fragp_last8 = fragp_last16 = fragp_last32; pos = pos_last8 = pos_last16 = pos_last32; } else abort (); goto look_at_frag; } /* Update current position for moving past a code frag. */ pos += fragp->fr_fix; pos &= 31; frchp_next = frchp; fragp_next = fragp->fr_next; if (fragp_next == NULL) { frchp_next = frchp->frch_next; if (frchp_next != NULL) fragp_next = frchp_next->frch_root; } if (!(pos & 7)) { frchp_last8 = frchp_next; fragp_last8 = fragp_next; pos_last8 = pos; } if (!(pos & 15)) { frchp_last16 = frchp_next; fragp_last16 = fragp_next; pos_last16 = pos; } if (!(pos & 31)) { frchp_last32 = frchp_next; fragp_last32 = fragp_next; pos_last32 = pos; } } } /* Now convert the machine-dependent frags to machine-independent ones. */ for (frchp = info->frchainP; frchp; frchp = frchp->frch_next) for (fragp = frchp->frch_root; fragp; fragp = fragp->fr_next) { if (fragp->fr_type == rs_machine_dependent) { if (fragp->tc_frag_data.is_insns) frag_wane (fragp); else { fragp->fr_type = rs_align_code; fragp->fr_var = 1; *fragp->fr_literal = 0; } } } } /* Initialize the machine-dependent parts of a frag. */ void tic6x_frag_init (fragS *fragp) { fragp->tc_frag_data.is_insns = FALSE; fragp->tc_frag_data.can_cross_fp_boundary = FALSE; } /* Set an attribute if it has not already been set by the user. */ static void tic6x_set_attribute_int (int tag, int value) { if (tag < 1 || tag >= NUM_KNOWN_OBJ_ATTRIBUTES) abort (); if (!tic6x_attributes_set_explicitly[tag]) bfd_elf_add_proc_attr_int (stdoutput, tag, value); } /* Set object attributes deduced from the input file and command line rather than given explicitly. */ static void tic6x_set_attributes (void) { if (tic6x_arch_attribute == C6XABI_Tag_ISA_none) tic6x_arch_attribute = C6XABI_Tag_ISA_C674X; tic6x_set_attribute_int (Tag_ISA, tic6x_arch_attribute); tic6x_set_attribute_int (Tag_ABI_DSBT, tic6x_dsbt); tic6x_set_attribute_int (Tag_ABI_PID, tic6x_pid); tic6x_set_attribute_int (Tag_ABI_PIC, tic6x_pic); } /* Do machine-dependent manipulations of the frag chains after all input has been read and before the machine-independent sizing and relaxing. */ void tic6x_end (void) { /* Set object attributes at this point if not explicitly set. */ tic6x_set_attributes (); /* Meeting alignment requirements may require inserting NOPs in parallel in execute packets earlier in the segment. Future 16-bit instruction generation involves whole-segment optimization to determine the best choice and ordering of 32-bit or 16-bit instructions. This doesn't fit will in the general relaxation framework, so handle alignment and 16-bit instruction generation here. */ bfd_map_over_sections (stdoutput, tic6x_adjust_section, NULL); } /* No machine-dependent frags at this stage; all converted in tic6x_end. */ void md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT asec ATTRIBUTE_UNUSED, fragS *fragp ATTRIBUTE_UNUSED) { abort (); } /* No machine-dependent frags at this stage; all converted in tic6x_end. */ int md_estimate_size_before_relax (fragS *fragp ATTRIBUTE_UNUSED, segT seg ATTRIBUTE_UNUSED) { abort (); } /* Put a number into target byte order. */ void md_number_to_chars (char *buf, valueT val, int n) { if (target_big_endian) number_to_chars_bigendian (buf, val, n); else number_to_chars_littleendian (buf, val, n); } /* Machine-dependent operand parsing not currently needed. */ void md_operand (expressionS *op ATTRIBUTE_UNUSED) { } /* PC-relative operands are relative to the start of the fetch packet. */ long tic6x_pcrel_from_section (fixS *fixp, segT sec) { if (fixp->fx_addsy != NULL && (!S_IS_DEFINED (fixp->fx_addsy) || S_GET_SEGMENT (fixp->fx_addsy) != sec)) return 0; return (fixp->fx_where + fixp->fx_frag->fr_address) & ~(long) 0x1f; } /* Round up a section size to the appropriate boundary. */ valueT md_section_align (segT segment ATTRIBUTE_UNUSED, valueT size) { /* Round up section sizes to ensure that text sections consist of whole fetch packets. */ int align = bfd_get_section_alignment (stdoutput, segment); return ((size + (1 << align) - 1) & ((valueT) -1 << align)); } /* No special undefined symbol handling needed for now. */ symbolS * md_undefined_symbol (char *name ATTRIBUTE_UNUSED) { return NULL; } /* Translate internal representation of relocation info to BFD target format. */ arelent * tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixp) { arelent *reloc; asymbol *symbol; bfd_reloc_code_real_type r_type; reloc = xmalloc (sizeof (arelent)); reloc->sym_ptr_ptr = xmalloc (sizeof (asymbol *)); symbol = symbol_get_bfdsym (fixp->fx_addsy); *reloc->sym_ptr_ptr = symbol; reloc->address = fixp->fx_frag->fr_address + fixp->fx_where; reloc->addend = (tic6x_generate_rela ? fixp->fx_offset : 0); r_type = fixp->fx_r_type; reloc->howto = bfd_reloc_type_lookup (stdoutput, r_type); if (reloc->howto == NULL) { as_bad_where (fixp->fx_file, fixp->fx_line, _("Cannot represent relocation type %s"), bfd_get_reloc_code_name (r_type)); return NULL; } /* Correct for adjustments bfd_install_relocation will make. */ if (reloc->howto->pcrel_offset && reloc->howto->partial_inplace) { reloc->addend += reloc->address; if (!bfd_is_com_section (symbol)) reloc->addend -= symbol->value; } if (r_type == BFD_RELOC_C6000_PCR_H16 || r_type == BFD_RELOC_C6000_PCR_L16) { symbolS *t = fixp->tc_fix_data.fix_subsy; segT sub_symbol_segment; resolve_symbol_value (t); sub_symbol_segment = S_GET_SEGMENT (t); if (sub_symbol_segment == undefined_section) as_bad_where (fixp->fx_file, fixp->fx_line, _("undefined symbol %s in PCR relocation"), S_GET_NAME (t)); else { reloc->addend = reloc->address & ~0x1F; reloc->addend -= S_GET_VALUE (t); } } return reloc; } /* Convert REGNAME to a DWARF-2 register number. */ int tic6x_regname_to_dw2regnum (char *regname) { bfd_boolean reg_ok; tic6x_register reg; char *rq = regname; reg_ok = tic6x_parse_register (&rq, ®); if (!reg_ok) return -1; switch (reg.side) { case 1: /* A regs. */ if (reg.num < 16) return reg.num; else if (reg.num < 32) return (reg.num - 16) + 37; else return -1; case 2: /* B regs. */ if (reg.num < 16) return reg.num + 16; else if (reg.num < 32) return (reg.num - 16) + 53; else return -1; default: return -1; } } /* Initialize the DWARF-2 unwind information for this procedure. */ void tic6x_frame_initial_instructions (void) { /* CFA is initial stack pointer (B15). */ cfi_add_CFA_def_cfa (31, 0); } /* Start an exception table entry. If idx is nonzero this is an index table entry. */ static void tic6x_start_unwind_section (const segT text_seg, int idx) { tic6x_unwind_info *unwind = tic6x_get_unwind (); const char * text_name; const char * prefix; const char * prefix_once; const char * group_name; size_t prefix_len; size_t text_len; char * sec_name; size_t sec_name_len; int type; int flags; int linkonce; if (idx) { prefix = ELF_STRING_C6000_unwind; prefix_once = ELF_STRING_C6000_unwind_once; type = SHT_C6000_UNWIND; } else { prefix = ELF_STRING_C6000_unwind_info; prefix_once = ELF_STRING_C6000_unwind_info_once; type = SHT_PROGBITS; } text_name = segment_name (text_seg); if (streq (text_name, ".text")) text_name = ""; if (strncmp (text_name, ".gnu.linkonce.t.", strlen (".gnu.linkonce.t.")) == 0) { prefix = prefix_once; text_name += strlen (".gnu.linkonce.t."); } prefix_len = strlen (prefix); text_len = strlen (text_name); sec_name_len = prefix_len + text_len; sec_name = (char *) xmalloc (sec_name_len + 1); memcpy (sec_name, prefix, prefix_len); memcpy (sec_name + prefix_len, text_name, text_len); sec_name[prefix_len + text_len] = '\0'; flags = SHF_ALLOC; linkonce = 0; group_name = 0; /* Handle COMDAT group. */ if (prefix != prefix_once && (text_seg->flags & SEC_LINK_ONCE) != 0) { group_name = elf_group_name (text_seg); if (group_name == NULL) { as_bad (_("group section `%s' has no group signature"), segment_name (text_seg)); ignore_rest_of_line (); return; } flags |= SHF_GROUP; linkonce = 1; } obj_elf_change_section (sec_name, type, flags, 0, group_name, linkonce, 0); /* Set the section link for index tables. */ if (idx) elf_linked_to_section (now_seg) = text_seg; seg_info (now_seg)->tc_segment_info_data.text_unwind = unwind; } static const int tic6x_unwind_frame_regs[TIC6X_NUM_UNWIND_REGS] = /* A15 B15 B14 B13 B12 B11 B10 B3 A14 A13 A12 A11 A10. */ { 15, 31, 30, 29, 28, 27, 26, 19, 14, 13, 12, 11, 10 }; /* Register save offsets for __c6xabi_push_rts. */ static const int tic6x_pop_rts_offset_little[TIC6X_NUM_UNWIND_REGS] = /* A15 B15 B14 B13 B12 B11 B10 B3 A14 A13 A12 A11 A10. */ { -1, 1, 0, -3, -4, -7, -8,-11, -2, -5, -6, -9,-10}; static const int tic6x_pop_rts_offset_big[TIC6X_NUM_UNWIND_REGS] = /* A15 B15 B14 B13 B12 B11 B10 B3 A14 A13 A12 A11 A10. */ { -2, 1, 0, -4, -3, -8, -7,-12, -1, -6, -5,-10, -9}; /* Map from dwarf register number to unwind frame register number. */ static int tic6x_unwind_reg_from_dwarf (int dwarf) { int reg; for (reg = 0; reg < TIC6X_NUM_UNWIND_REGS; reg++) { if (tic6x_unwind_frame_regs[reg] == dwarf) return reg; } return -1; } /* Unwinding bytecode definitions. */ #define UNWIND_OP_ADD_SP 0x00 #define UNWIND_OP_ADD_SP2 0xd2 #define UNWIND_OP2_POP 0x8000 #define UNWIND_OP2_POP_COMPACT 0xa000 #define UNWIND_OP_POP_REG 0xc0 #define UNWIND_OP_MV_FP 0xd0 #define UNWIND_OP_POP_RTS 0xd1 #define UNWIND_OP_RET 0xe0 /* Maximum stack adjustment for __c6xabi_unwind_cpp_pr3/4 */ #define MAX_COMPACT_SP_OFFSET (0x7f << 3) static void tic6x_flush_unwind_word (valueT data) { tic6x_unwind_info *unwind = tic6x_get_unwind (); char *ptr; /* Create EXTAB entry if it does not exist. */ if (unwind->table_entry == NULL) { tic6x_start_unwind_section (unwind->saved_seg, 0); frag_align (2, 0, 0); record_alignment (now_seg, 2); unwind->table_entry = expr_build_dot (); ptr = frag_more (4); unwind->frag_start = ptr; } else { /* Append additional word of data. */ ptr = frag_more (4); } md_number_to_chars (ptr, data, 4); } /* Add a single byte of unwinding data. */ static void tic6x_unwind_byte (int byte) { tic6x_unwind_info *unwind = tic6x_get_unwind (); unwind->data_bytes++; /* Only flush the first word after we know multiple words are required. */ if (unwind->data_bytes == 5) { if (unwind->personality_index == -1) { /* At this point we know we are too big for pr0. */ unwind->personality_index = 1; tic6x_flush_unwind_word (0x81000000 | ((unwind->data >> 8) & 0xffff)); unwind->data = ((unwind->data & 0xff) << 8) | byte; unwind->data_bytes++; } else { tic6x_flush_unwind_word (unwind->data); unwind->data = byte; } } else { unwind->data = (unwind->data << 8) | byte; if ((unwind->data_bytes & 3) == 0 && unwind->data_bytes > 4) { tic6x_flush_unwind_word (unwind->data); unwind->data = 0; } } } /* Add a two-byte unwinding opcode. */ static void tic6x_unwind_2byte (int bytes) { tic6x_unwind_byte (bytes >> 8); tic6x_unwind_byte (bytes & 0xff); } static void tic6x_unwind_uleb (offsetT offset) { while (offset > 0x7f) { tic6x_unwind_byte ((offset & 0x7f) | 0x80); offset >>= 7; } tic6x_unwind_byte (offset); } void tic6x_cfi_startproc (void) { tic6x_unwind_info *unwind = tic6x_get_unwind (); unwind->personality_index = -1; unwind->personality_routine = NULL; if (unwind->table_entry) as_bad (_("missing .endp before .cfi_startproc")); unwind->table_entry = NULL; unwind->data_bytes = -1; } static void tic6x_output_exidx_entry (void) { char *ptr; long where; unsigned int marked_pr_dependency; segT old_seg; subsegT old_subseg; tic6x_unwind_info *unwind = tic6x_get_unwind (); old_seg = now_seg; old_subseg = now_subseg; /* Add index table entry. This is two words. */ tic6x_start_unwind_section (unwind->saved_seg, 1); frag_align (2, 0, 0); record_alignment (now_seg, 2); ptr = frag_more (8); where = frag_now_fix () - 8; /* Self relative offset of the function start. */ fix_new (frag_now, where, 4, unwind->function_start, 0, 1, BFD_RELOC_C6000_PREL31); /* Indicate dependency on ABI-defined personality routines to the linker, if it hasn't been done already. */ marked_pr_dependency = seg_info (now_seg)->tc_segment_info_data.marked_pr_dependency; if (unwind->personality_index >= 0 && unwind->personality_index < 5 && !(marked_pr_dependency & (1 << unwind->personality_index))) { static const char *const name[] = { "__c6xabi_unwind_cpp_pr0", "__c6xabi_unwind_cpp_pr1", "__c6xabi_unwind_cpp_pr2", "__c6xabi_unwind_cpp_pr3", "__c6xabi_unwind_cpp_pr4" }; symbolS *pr = symbol_find_or_make (name[unwind->personality_index]); fix_new (frag_now, where, 0, pr, 0, 1, BFD_RELOC_NONE); seg_info (now_seg)->tc_segment_info_data.marked_pr_dependency |= 1 << unwind->personality_index; } if (unwind->table_entry) { /* Self relative offset of the table entry. */ fix_new (frag_now, where + 4, 4, unwind->table_entry, 0, 1, BFD_RELOC_C6000_PREL31); } else { /* Inline exception table entry. */ md_number_to_chars (ptr + 4, unwind->data, 4); } /* Restore the original section. */ subseg_set (old_seg, old_subseg); } static void tic6x_output_unwinding (bfd_boolean need_extab) { tic6x_unwind_info *unwind = tic6x_get_unwind (); unsigned safe_mask = unwind->safe_mask; unsigned compact_mask = unwind->compact_mask; unsigned reg_saved_mask = unwind->reg_saved_mask; offsetT cfa_offset = unwind->cfa_offset; long where; int reg; if (unwind->personality_index == -2) { /* Function can not be unwound. */ unwind->data = 1; tic6x_output_exidx_entry (); return; } if (unwind->personality_index == -1 && unwind->personality_routine == NULL) { /* Auto-select a personality routine if none specified. */ if (reg_saved_mask || cfa_offset >= MAX_COMPACT_SP_OFFSET) unwind->personality_index = -1; else if (safe_mask) unwind->personality_index = 3; else unwind->personality_index = 4; } /* Calculate unwinding opcodes, and emit to EXTAB if necessary. */ unwind->table_entry = NULL; if (unwind->personality_index == 3 || unwind->personality_index == 4) { if (cfa_offset >= MAX_COMPACT_SP_OFFSET) { as_bad (_("stack pointer offset too large for personality routine")); return; } if (reg_saved_mask || (unwind->personality_index == 3 && compact_mask != 0) || (unwind->personality_index == 4 && safe_mask != 0)) { as_bad (_("stack frame layout does not match personality routine")); return; } unwind->data = (1u << 31) | (unwind->personality_index << 24); if (unwind->cfa_reg == 15) unwind->data |= 0x7f << 17; else unwind->data |= cfa_offset << (17 - 3); if (unwind->personality_index == 3) unwind->data |= safe_mask << 4; else unwind->data |= compact_mask << 4; unwind->data |= unwind->return_reg; unwind->data_bytes = 4; } else { if (unwind->personality_routine) { unwind->data = 0; unwind->data_bytes = 5; tic6x_flush_unwind_word (0); /* First word is personality routine. */ where = frag_now_fix () - 4; fix_new (frag_now, where, 4, unwind->personality_routine, 0, 1, BFD_RELOC_C6000_PREL31); } else if (unwind->personality_index > 0) { unwind->data = 0x8000 | (unwind->personality_index << 8); unwind->data_bytes = 2; } else /* pr0 or undecided */ { unwind->data = 0x80; unwind->data_bytes = 1; } if (unwind->return_reg != UNWIND_B3) { tic6x_unwind_byte (UNWIND_OP_RET | unwind->return_reg); } if (unwind->cfa_reg == 15) { tic6x_unwind_byte (UNWIND_OP_MV_FP); } else if (cfa_offset != 0) { cfa_offset >>= 3; if (cfa_offset > 0x80) { tic6x_unwind_byte (UNWIND_OP_ADD_SP2); tic6x_unwind_uleb (cfa_offset - 0x81); } else if (cfa_offset > 0x40) { tic6x_unwind_byte (UNWIND_OP_ADD_SP | 0x3f); tic6x_unwind_byte (UNWIND_OP_ADD_SP | (cfa_offset - 0x40)); } else { tic6x_unwind_byte (UNWIND_OP_ADD_SP | (cfa_offset - 1)); } } if (safe_mask) tic6x_unwind_2byte (UNWIND_OP2_POP | unwind->safe_mask); else if (unwind->pop_rts) tic6x_unwind_byte (UNWIND_OP_POP_RTS); else if (compact_mask) tic6x_unwind_2byte (UNWIND_OP2_POP_COMPACT | unwind->compact_mask); else if (reg_saved_mask) { offsetT cur_offset; int val; int last_val; tic6x_unwind_byte (UNWIND_OP_POP_REG | unwind->saved_reg_count); last_val = 0; for (cur_offset = 0; unwind->saved_reg_count > 0; cur_offset -= 4) { val = 0xf; for (reg = 0; reg < TIC6X_NUM_UNWIND_REGS; reg++) { if (!unwind->reg_saved[reg]) continue; if (unwind->reg_offset[reg] == cur_offset) { unwind->saved_reg_count--; val = reg; break; } } if ((cur_offset & 4) == 4) tic6x_unwind_byte ((last_val << 4) | val); else last_val = val; } if ((cur_offset & 4) == 4) tic6x_unwind_byte ((last_val << 4) | 0xf); } /* Pad with RETURN opcodes. */ while ((unwind->data_bytes & 3) != 0) tic6x_unwind_byte (UNWIND_OP_RET | UNWIND_B3); if (unwind->personality_index == -1 && unwind->personality_routine == NULL) unwind->personality_index = 0; } /* Force creation of an EXTAB entry if an LSDA is required. */ if (need_extab && !unwind->table_entry) { if (unwind->data_bytes != 4) abort (); tic6x_flush_unwind_word (unwind->data); } else if (unwind->table_entry && !need_extab) { /* Add an empty descriptor if there is no user-specified data. */ char *ptr = frag_more (4); md_number_to_chars (ptr, 0, 4); } /* Fill in length of unwinding bytecode. */ if (unwind->table_entry) { valueT tmp; if (unwind->data_bytes > 0x400) as_bad (_("too many unwinding instructions")); if (unwind->personality_index == -1) { tmp = md_chars_to_number (unwind->frag_start + 4, 4); tmp |= ((unwind->data_bytes - 8) >> 2) << 24; md_number_to_chars (unwind->frag_start + 4, tmp, 4); } else if (unwind->personality_index == 1 || unwind->personality_index == 2) { tmp = md_chars_to_number (unwind->frag_start, 4); tmp |= ((unwind->data_bytes - 4) >> 2) << 16; md_number_to_chars (unwind->frag_start, tmp, 4); } } tic6x_output_exidx_entry (); } /* FIXME: This will get horribly confused if cfi directives are emitted for function epilogue. */ void tic6x_cfi_endproc (struct fde_entry *fde) { tic6x_unwind_info *unwind = tic6x_get_unwind (); struct cfi_insn_data *insn; int reg; unsigned safe_mask = 0; unsigned compact_mask = 0; unsigned reg_saved_mask = 0; offsetT cfa_offset = 0; offsetT save_offset = 0; unwind->cfa_reg = 31; unwind->return_reg = UNWIND_B3; unwind->saved_reg_count = 0; unwind->pop_rts = FALSE; unwind->saved_seg = now_seg; unwind->saved_subseg = now_subseg; for (reg = 0; reg < TIC6X_NUM_UNWIND_REGS; reg++) unwind->reg_saved[reg] = FALSE; /* Scan FDE instructions to build up stack frame layout. */ for (insn = fde->data; insn; insn = insn->next) { switch (insn->insn) { case DW_CFA_advance_loc: break; case DW_CFA_def_cfa: unwind->cfa_reg = insn->u.ri.reg; cfa_offset = insn->u.ri.offset; break; case DW_CFA_def_cfa_register: unwind->cfa_reg = insn->u.r; break; case DW_CFA_def_cfa_offset: cfa_offset = insn->u.i; break; case DW_CFA_undefined: case DW_CFA_same_value: reg = tic6x_unwind_reg_from_dwarf (insn->u.r); if (reg >= 0) unwind->reg_saved[reg] = FALSE; break; case DW_CFA_offset: reg = tic6x_unwind_reg_from_dwarf (insn->u.ri.reg); if (reg < 0) { as_bad (_("unable to generate unwinding opcode for reg %d"), insn->u.ri.reg); return; } unwind->reg_saved[reg] = TRUE; unwind->reg_offset[reg] = insn->u.ri.offset; if (insn->u.ri.reg == UNWIND_B3) unwind->return_reg = UNWIND_B3; break; case DW_CFA_register: if (insn->u.rr.reg1 != 19) { as_bad (_("unable to generate unwinding opcode for reg %d"), insn->u.rr.reg1); return; } reg = tic6x_unwind_reg_from_dwarf (insn->u.rr.reg2); if (reg < 0) { as_bad (_("unable to generate unwinding opcode for reg %d"), insn->u.rr.reg2); return; } unwind->return_reg = reg; unwind->reg_saved[UNWIND_B3] = FALSE; if (unwind->reg_saved[reg]) { as_bad (_("unable to restore return address from " "previously restored reg")); return; } break; case DW_CFA_restore: case DW_CFA_remember_state: case DW_CFA_restore_state: case DW_CFA_GNU_window_save: case CFI_escape: case CFI_val_encoded_addr: as_bad (_("unhandled CFA insn for unwinding (%d)"), insn->insn); break; default: abort (); } } if (unwind->cfa_reg != 15 && unwind->cfa_reg != 31) { as_bad (_("unable to generate unwinding opcode for frame pointer reg %d"), unwind->cfa_reg); return; } if (unwind->cfa_reg == 15) { if (cfa_offset != 0) { as_bad (_("unable to generate unwinding opcode for " "frame pointer offset")); return; } } else { if ((cfa_offset & 7) != 0) { as_bad (_("unwound stack pointer not doubleword aligned")); return; } } for (reg = 0; reg < TIC6X_NUM_UNWIND_REGS; reg++) { if (unwind->reg_saved[reg]) reg_saved_mask |= 1 << (TIC6X_NUM_UNWIND_REGS - (reg + 1)); } /* Check for standard "safe debug" frame layout */ if (reg_saved_mask) { save_offset = 0; for (reg = 0; reg < TIC6X_NUM_UNWIND_REGS; reg++) { if (!unwind->reg_saved[reg]) continue; if (target_big_endian && reg < TIC6X_NUM_UNWIND_REGS - 1 && unwind->reg_saved[reg + 1] && tic6x_unwind_frame_regs[reg] == tic6x_unwind_frame_regs[reg + 1] + 1 && (tic6x_unwind_frame_regs[reg] & 1) == 1 && (save_offset & 4) == 4) { /* Swapped pair */ if (save_offset != unwind->reg_offset[reg + 1] || save_offset - 4 != unwind->reg_offset[reg]) break; save_offset -= 8; reg++; } else { if (save_offset != unwind->reg_offset[reg]) break; save_offset -= 4; } } if (reg == TIC6X_NUM_UNWIND_REGS) { safe_mask = reg_saved_mask; reg_saved_mask = 0; } } /* Check for compact frame layout. */ if (reg_saved_mask) { save_offset = 0; for (reg = 0; reg < TIC6X_NUM_UNWIND_REGS; reg++) { int reg2; if (!unwind->reg_saved[reg]) continue; if (reg < TIC6X_NUM_UNWIND_REGS - 1) { reg2 = reg + 1; if (!unwind->reg_saved[reg2] || tic6x_unwind_frame_regs[reg] != tic6x_unwind_frame_regs[reg2] + 1 || (tic6x_unwind_frame_regs[reg2] & 1) != 0 || save_offset == 0) reg2 = -1; } else reg2 = -1; if (reg2 >= 0) { int high_offset; if (target_big_endian) high_offset = 4; /* lower address = positive stack offset. */ else high_offset = 0; if (save_offset + 4 - high_offset != unwind->reg_offset[reg] || save_offset + high_offset != unwind->reg_offset[reg2]) { break; } reg++; } else { if (save_offset != unwind->reg_offset[reg]) break; } save_offset -= 8; } if (reg == TIC6X_NUM_UNWIND_REGS) { compact_mask = reg_saved_mask; reg_saved_mask = 0; } } /* Check for __c6xabi_pop_rts format */ if (reg_saved_mask == 0x17ff) { const int *pop_rts_offset = target_big_endian ? tic6x_pop_rts_offset_big : tic6x_pop_rts_offset_little; save_offset = 0; for (reg = 0; reg < TIC6X_NUM_UNWIND_REGS; reg++) { if (reg == UNWIND_B15) continue; if (unwind->reg_offset[reg] != pop_rts_offset[reg] * 4) break; } if (reg == TIC6X_NUM_UNWIND_REGS) { unwind->pop_rts = TRUE; reg_saved_mask = 0; } } /* If all else fails then describe the frame manually. */ if (reg_saved_mask) { save_offset = 0; for (reg = 0; reg < TIC6X_NUM_UNWIND_REGS; reg++) { if (!unwind->reg_saved[reg]) continue; unwind->saved_reg_count++; /* Encoding uses 4 bits per word, so size of unwinding opcode data limits the save area size. The exact cap will be figured out later due to overflow, the 0x800 here is just a quick sanity check to weed out obviously excessive offsets. */ if (unwind->reg_offset[reg] > 0 || unwind->reg_offset[reg] < -0x800 || (unwind->reg_offset[reg] & 3) != 0) { as_bad (_("stack frame layout too complex for unwinder")); return; } if (unwind->reg_offset[reg] < save_offset) save_offset = unwind->reg_offset[reg] - 4; } } /* Align to 8-byte boundary (stack grows towards negative offsets). */ save_offset &= ~7; if (unwind->cfa_reg == 31 && !reg_saved_mask) { cfa_offset += save_offset; if (cfa_offset < 0) { as_bad (_("unwound frame has negative size")); return; } } unwind->safe_mask = safe_mask; unwind->compact_mask = compact_mask; unwind->reg_saved_mask = reg_saved_mask; unwind->cfa_offset = cfa_offset; unwind->function_start = fde->start_address; }
Go to most recent revision | Compare with Previous | Blame | View Log