+ case BFD_RELOC_32_GOTOFF:
+ *r_type_p = BFD_RELOC_SH_GOTOFF_MEDHI16;
+ break;
+
+ case BFD_RELOC_SH_GOTPLT32:
+ *r_type_p = BFD_RELOC_SH_GOTPLT_MEDHI16;
+ break;
+
+ case BFD_RELOC_32_GOT_PCREL:
+ *r_type_p = BFD_RELOC_SH_GOT_MEDHI16;
+ break;
+
+ case BFD_RELOC_32_PLT_PCREL:
+ *r_type_p = BFD_RELOC_SH_PLT_MEDHI16;
+ break;
+
+ default:
+ abort ();
+ }
+ break;
+
+ case BFD_RELOC_SH_IMM_HI16:
+ switch (exp->X_md)
+ {
+ case BFD_RELOC_32_GOTOFF:
+ *r_type_p = BFD_RELOC_SH_GOTOFF_HI16;
+ break;
+
+ case BFD_RELOC_SH_GOTPLT32:
+ *r_type_p = BFD_RELOC_SH_GOTPLT_HI16;
+ break;
+
+ case BFD_RELOC_32_GOT_PCREL:
+ *r_type_p = BFD_RELOC_SH_GOT_HI16;
+ break;
+
+ case BFD_RELOC_32_PLT_PCREL:
+ *r_type_p = BFD_RELOC_SH_PLT_HI16;
+ break;
+
+ default:
+ abort ();
+ }
+ break;
+#endif
+
+ default:
+ abort ();
+ }
+ if (exp == main_exp)
+ exp->X_op = O_symbol;
+ else
+ {
+ main_exp->X_add_symbol = exp->X_add_symbol;
+ main_exp->X_add_number += exp->X_add_number;
+ }
+ }
+ else
+ return (sh_PIC_related_p (exp->X_add_symbol)
+ || sh_PIC_related_p (exp->X_op_symbol));
+
+ return 0;
+}
+
+/* Add expression EXP of SIZE bytes to offset OFF of fragment FRAG. */
+
+void
+sh_cons_fix_new (fragS *frag, int off, int size, expressionS *exp,
+ bfd_reloc_code_real_type r_type)
+{
+ r_type = BFD_RELOC_UNUSED;
+
+ if (sh_check_fixup (exp, &r_type))
+ as_bad (_("Invalid PIC expression."));
+
+ if (r_type == BFD_RELOC_UNUSED)
+ 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;
+
+ case 8:
+ r_type = BFD_RELOC_64;
+ break;
+
+ default:
+ goto error;
+ }
+ else if (size != 4)
+ {
+ error:
+ as_bad (_("unsupported BFD relocation size %u"), size);
+ r_type = BFD_RELOC_UNUSED;
+ }
+
+ fix_new_exp (frag, off, size, exp, 0, r_type);
+}
+
+/* The regular cons() function, that reads constants, doesn't support
+ suffixes such as @GOT, @GOTOFF and @PLT, that generate
+ machine-specific relocation types. So we must define it here. */
+/* Clobbers input_line_pointer, checks end-of-line. */
+/* NBYTES 1=.byte, 2=.word, 4=.long */
+static void
+sh_elf_cons (int nbytes)
+{
+ expressionS exp;
+
+#ifdef HAVE_SH64
+
+ /* Update existing range to include a previous insn, if there was one. */
+ sh64_update_contents_mark (TRUE);
+
+ /* We need to make sure the contents type is set to data. */
+ sh64_flag_output ();
+
+#endif /* HAVE_SH64 */
+
+ if (is_it_end_of_statement ())
+ {
+ demand_empty_rest_of_line ();
+ return;
+ }
+
+#ifdef md_cons_align
+ md_cons_align (nbytes);
+#endif
+
+ do
+ {
+ expression (&exp);
+ emit_expr (&exp, (unsigned int) nbytes);
+ }
+ while (*input_line_pointer++ == ',');
+
+ input_line_pointer--; /* Put terminator back into stream. */
+ if (*input_line_pointer == '#' || *input_line_pointer == '!')
+ {
+ while (! is_end_of_line[(unsigned char) *input_line_pointer++]);
+ }
+ else
+ demand_empty_rest_of_line ();
+}
+
+/* The regular frag_offset_fixed_p doesn't work for rs_align_test
+ frags. */
+
+static bfd_boolean
+align_test_frag_offset_fixed_p (const fragS *frag1, const fragS *frag2,
+ bfd_vma *offset)
+{
+ const fragS *frag;
+ bfd_vma off;
+
+ /* Start with offset initialised to difference between the two frags.
+ Prior to assigning frag addresses this will be zero. */
+ off = frag1->fr_address - frag2->fr_address;
+ if (frag1 == frag2)
+ {
+ *offset = off;
+ return TRUE;
+ }
+
+ /* Maybe frag2 is after frag1. */
+ frag = frag1;
+ while (frag->fr_type == rs_fill
+ || frag->fr_type == rs_align_test)
+ {
+ if (frag->fr_type == rs_fill)
+ off += frag->fr_fix + frag->fr_offset * frag->fr_var;
+ else
+ off += frag->fr_fix;
+ frag = frag->fr_next;
+ if (frag == NULL)
+ break;
+ if (frag == frag2)
+ {
+ *offset = off;
+ return TRUE;
+ }
+ }
+
+ /* Maybe frag1 is after frag2. */
+ off = frag1->fr_address - frag2->fr_address;
+ frag = frag2;
+ while (frag->fr_type == rs_fill
+ || frag->fr_type == rs_align_test)
+ {
+ if (frag->fr_type == rs_fill)
+ off -= frag->fr_fix + frag->fr_offset * frag->fr_var;
+ else
+ off -= frag->fr_fix;
+ frag = frag->fr_next;
+ if (frag == NULL)
+ break;
+ if (frag == frag1)
+ {
+ *offset = off;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* Optimize a difference of symbols which have rs_align_test frag if
+ possible. */
+
+int
+sh_optimize_expr (expressionS *l, operatorT op, expressionS *r)
+{
+ bfd_vma frag_off;
+
+ if (op == O_subtract
+ && l->X_op == O_symbol
+ && r->X_op == O_symbol
+ && S_GET_SEGMENT (l->X_add_symbol) == S_GET_SEGMENT (r->X_add_symbol)
+ && (SEG_NORMAL (S_GET_SEGMENT (l->X_add_symbol))
+ || r->X_add_symbol == l->X_add_symbol)
+ && align_test_frag_offset_fixed_p (symbol_get_frag (l->X_add_symbol),
+ symbol_get_frag (r->X_add_symbol),
+ &frag_off))
+ {
+ offsetT symval_diff = S_GET_VALUE (l->X_add_symbol)
+ - S_GET_VALUE (r->X_add_symbol);
+ subtract_from_result (l, r->X_add_number, r->X_extrabit);
+ subtract_from_result (l, frag_off / OCTETS_PER_BYTE, 0);
+ add_to_result (l, symval_diff, symval_diff < 0);
+ l->X_op = O_constant;
+ l->X_add_symbol = 0;
+ return 1;
+ }
+ return 0;
+}
+#endif /* OBJ_ELF */
+\f
+/* This function is called once, at assembler startup time. This should
+ set up all the tables, etc that the MD part of the assembler needs. */
+
+void
+md_begin (void)
+{
+ const sh_opcode_info *opcode;
+ const char *prev_name = "";
+ unsigned int target_arch;
+
+ target_arch
+ = preset_target_arch ? preset_target_arch : arch_sh_up & ~arch_sh_has_dsp;
+ valid_arch = target_arch;
+
+#ifdef HAVE_SH64
+ shmedia_md_begin ();
+#endif
+
+ opcode_hash_control = hash_new ();
+
+ /* Insert unique names into hash table. */
+ for (opcode = sh_table; opcode->name; opcode++)
+ {
+ if (strcmp (prev_name, opcode->name) != 0)
+ {
+ if (!SH_MERGE_ARCH_SET_VALID (opcode->arch, target_arch))
+ continue;
+ prev_name = opcode->name;
+ hash_insert (opcode_hash_control, opcode->name, (char *) opcode);
+ }
+ }
+}
+
+static int reg_m;
+static int reg_n;
+static int reg_x, reg_y;
+static int reg_efg;
+static int reg_b;
+
+#define IDENT_CHAR(c) (ISALNUM (c) || (c) == '_')
+
+/* Try to parse a reg name. Return the number of chars consumed. */
+
+static unsigned int
+parse_reg_without_prefix (char *src, int *mode, int *reg)
+{
+ char l0 = TOLOWER (src[0]);
+ char l1 = l0 ? TOLOWER (src[1]) : 0;
+
+ /* We use ! IDENT_CHAR for the next character after the register name, to
+ make sure that we won't accidentally recognize a symbol name such as
+ 'sram' or sr_ram as being a reference to the register 'sr'. */
+
+ if (l0 == 'r')
+ {
+ if (l1 == '1')
+ {
+ if (src[2] >= '0' && src[2] <= '5'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_REG_N;
+ *reg = 10 + src[2] - '0';
+ return 3;
+ }
+ }
+ if (l1 >= '0' && l1 <= '9'
+ && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = A_REG_N;
+ *reg = (l1 - '0');
+ return 2;
+ }
+ if (l1 >= '0' && l1 <= '7' && strncasecmp (&src[2], "_bank", 5) == 0
+ && ! IDENT_CHAR ((unsigned char) src[7]))
+ {
+ *mode = A_REG_B;
+ *reg = (l1 - '0');
+ return 7;
+ }
+
+ if (l1 == 'e' && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = A_RE;
+ return 2;
+ }
+ if (l1 == 's' && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = A_RS;
+ return 2;
+ }
+ }
+
+ if (l0 == 'a')
+ {
+ if (l1 == '0')
+ {
+ if (! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = DSP_REG_N;
+ *reg = A_A0_NUM;
+ return 2;
+ }
+ if (TOLOWER (src[2]) == 'g' && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = DSP_REG_N;
+ *reg = A_A0G_NUM;
+ return 3;
+ }
+ }
+ if (l1 == '1')
+ {
+ if (! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = DSP_REG_N;
+ *reg = A_A1_NUM;
+ return 2;
+ }
+ if (TOLOWER (src[2]) == 'g' && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = DSP_REG_N;
+ *reg = A_A1G_NUM;
+ return 3;
+ }
+ }
+
+ if (l1 == 'x' && src[2] >= '0' && src[2] <= '1'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_REG_N;
+ *reg = 4 + (l1 - '0');
+ return 3;
+ }
+ if (l1 == 'y' && src[2] >= '0' && src[2] <= '1'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_REG_N;
+ *reg = 6 + (l1 - '0');
+ return 3;
+ }
+ if (l1 == 's' && src[2] >= '0' && src[2] <= '3'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ int n = l1 - '0';
+
+ *mode = A_REG_N;
+ *reg = n | ((~n & 2) << 1);
+ return 3;
+ }
+ }
+
+ if (l0 == 'i' && l1 && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ if (l1 == 's')
+ {
+ *mode = A_REG_N;
+ *reg = 8;
+ return 2;
+ }
+ if (l1 == 'x')
+ {
+ *mode = A_REG_N;
+ *reg = 8;
+ return 2;
+ }
+ if (l1 == 'y')
+ {
+ *mode = A_REG_N;
+ *reg = 9;
+ return 2;
+ }
+ }
+
+ if (l0 == 'x' && l1 >= '0' && l1 <= '1'
+ && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = DSP_REG_N;
+ *reg = A_X0_NUM + l1 - '0';
+ return 2;
+ }
+
+ if (l0 == 'y' && l1 >= '0' && l1 <= '1'
+ && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = DSP_REG_N;
+ *reg = A_Y0_NUM + l1 - '0';
+ return 2;
+ }
+
+ if (l0 == 'm' && l1 >= '0' && l1 <= '1'
+ && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = DSP_REG_N;
+ *reg = l1 == '0' ? A_M0_NUM : A_M1_NUM;
+ return 2;
+ }
+
+ if (l0 == 's'
+ && l1 == 's'
+ && TOLOWER (src[2]) == 'r' && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_SSR;
+ return 3;
+ }
+
+ if (l0 == 's' && l1 == 'p' && TOLOWER (src[2]) == 'c'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_SPC;
+ return 3;
+ }
+
+ if (l0 == 's' && l1 == 'g' && TOLOWER (src[2]) == 'r'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_SGR;
+ return 3;
+ }
+
+ if (l0 == 'd' && l1 == 's' && TOLOWER (src[2]) == 'r'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_DSR;
+ return 3;
+ }
+
+ if (l0 == 'd' && l1 == 'b' && TOLOWER (src[2]) == 'r'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_DBR;
+ return 3;
+ }
+
+ if (l0 == 's' && l1 == 'r' && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = A_SR;
+ return 2;
+ }
+
+ if (l0 == 's' && l1 == 'p' && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = A_REG_N;
+ *reg = 15;
+ return 2;
+ }
+
+ if (l0 == 'p' && l1 == 'r' && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ *mode = A_PR;
+ return 2;
+ }
+ if (l0 == 'p' && l1 == 'c' && ! IDENT_CHAR ((unsigned char) src[2]))
+ {
+ /* Don't use A_DISP_PC here - that would accept stuff like 'mova pc,r0'
+ and use an uninitialized immediate. */
+ *mode = A_PC;
+ return 2;
+ }
+ if (l0 == 'g' && l1 == 'b' && TOLOWER (src[2]) == 'r'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_GBR;
+ return 3;
+ }
+ if (l0 == 'v' && l1 == 'b' && TOLOWER (src[2]) == 'r'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_VBR;
+ return 3;
+ }
+
+ if (l0 == 't' && l1 == 'b' && TOLOWER (src[2]) == 'r'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_TBR;
+ return 3;
+ }
+ if (l0 == 'm' && l1 == 'a' && TOLOWER (src[2]) == 'c'
+ && ! IDENT_CHAR ((unsigned char) src[4]))
+ {
+ if (TOLOWER (src[3]) == 'l')
+ {
+ *mode = A_MACL;
+ return 4;
+ }
+ if (TOLOWER (src[3]) == 'h')
+ {
+ *mode = A_MACH;
+ return 4;
+ }
+ }
+ if (l0 == 'm' && l1 == 'o' && TOLOWER (src[2]) == 'd'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = A_MOD;
+ return 3;
+ }
+ if (l0 == 'f' && l1 == 'r')
+ {
+ if (src[2] == '1')
+ {
+ if (src[3] >= '0' && src[3] <= '5'
+ && ! IDENT_CHAR ((unsigned char) src[4]))
+ {
+ *mode = F_REG_N;
+ *reg = 10 + src[3] - '0';
+ return 4;
+ }
+ }
+ if (src[2] >= '0' && src[2] <= '9'
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = F_REG_N;
+ *reg = (src[2] - '0');
+ return 3;
+ }
+ }
+ if (l0 == 'd' && l1 == 'r')
+ {
+ if (src[2] == '1')
+ {
+ if (src[3] >= '0' && src[3] <= '4' && ! ((src[3] - '0') & 1)
+ && ! IDENT_CHAR ((unsigned char) src[4]))
+ {
+ *mode = D_REG_N;
+ *reg = 10 + src[3] - '0';
+ return 4;
+ }
+ }
+ if (src[2] >= '0' && src[2] <= '8' && ! ((src[2] - '0') & 1)
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = D_REG_N;
+ *reg = (src[2] - '0');
+ return 3;
+ }
+ }
+ if (l0 == 'x' && l1 == 'd')
+ {
+ if (src[2] == '1')
+ {
+ if (src[3] >= '0' && src[3] <= '4' && ! ((src[3] - '0') & 1)
+ && ! IDENT_CHAR ((unsigned char) src[4]))
+ {
+ *mode = X_REG_N;
+ *reg = 11 + src[3] - '0';
+ return 4;
+ }
+ }
+ if (src[2] >= '0' && src[2] <= '8' && ! ((src[2] - '0') & 1)
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = X_REG_N;
+ *reg = (src[2] - '0') + 1;
+ return 3;
+ }
+ }
+ if (l0 == 'f' && l1 == 'v')
+ {
+ if (src[2] == '1'&& src[3] == '2' && ! IDENT_CHAR ((unsigned char) src[4]))
+ {
+ *mode = V_REG_N;
+ *reg = 12;
+ return 4;
+ }
+ if ((src[2] == '0' || src[2] == '4' || src[2] == '8')
+ && ! IDENT_CHAR ((unsigned char) src[3]))
+ {
+ *mode = V_REG_N;
+ *reg = (src[2] - '0');
+ return 3;
+ }
+ }
+ if (l0 == 'f' && l1 == 'p' && TOLOWER (src[2]) == 'u'
+ && TOLOWER (src[3]) == 'l'
+ && ! IDENT_CHAR ((unsigned char) src[4]))
+ {
+ *mode = FPUL_N;
+ return 4;
+ }
+
+ if (l0 == 'f' && l1 == 'p' && TOLOWER (src[2]) == 's'
+ && TOLOWER (src[3]) == 'c'
+ && TOLOWER (src[4]) == 'r' && ! IDENT_CHAR ((unsigned char) src[5]))
+ {
+ *mode = FPSCR_N;
+ return 5;
+ }
+
+ if (l0 == 'x' && l1 == 'm' && TOLOWER (src[2]) == 't'
+ && TOLOWER (src[3]) == 'r'
+ && TOLOWER (src[4]) == 'x' && ! IDENT_CHAR ((unsigned char) src[5]))
+ {
+ *mode = XMTRX_M4;
+ return 5;
+ }
+
+ return 0;
+}
+
+/* Like parse_reg_without_prefix, but this version supports
+ $-prefixed register names if enabled by the user. */
+
+static unsigned int
+parse_reg (char *src, int *mode, int *reg)
+{
+ unsigned int prefix;
+ unsigned int consumed;
+
+ if (src[0] == '$')
+ {
+ if (allow_dollar_register_prefix)
+ {
+ src ++;
+ prefix = 1;
+ }
+ else
+ return 0;
+ }
+ else
+ prefix = 0;
+
+ consumed = parse_reg_without_prefix (src, mode, reg);
+
+ if (consumed == 0)
+ return 0;
+
+ return consumed + prefix;
+}
+
+static char *
+parse_exp (char *s, sh_operand_info *op)
+{
+ char *save;
+ char *new_pointer;
+
+ save = input_line_pointer;
+ input_line_pointer = s;
+ expression (&op->immediate);
+ if (op->immediate.X_op == O_absent)
+ as_bad (_("missing operand"));
+ new_pointer = input_line_pointer;
+ input_line_pointer = save;
+ return new_pointer;
+}
+
+/* The many forms of operand:
+
+ Rn Register direct
+ @Rn Register indirect
+ @Rn+ Autoincrement
+ @-Rn Autodecrement
+ @(disp:4,Rn)
+ @(disp:8,GBR)
+ @(disp:8,PC)
+
+ @(R0,Rn)
+ @(R0,GBR)
+
+ disp:8
+ disp:12
+ #imm8
+ pr, gbr, vbr, macl, mach
+ */
+
+static char *
+parse_at (char *src, sh_operand_info *op)
+{
+ int len;
+ int mode;
+ src++;
+ if (src[0] == '@')
+ {
+ src = parse_at (src, op);
+ if (op->type == A_DISP_TBR)
+ op->type = A_DISP2_TBR;
+ else
+ as_bad (_("illegal double indirection"));
+ }
+ else if (src[0] == '-')
+ {
+ /* Must be predecrement. */
+ src++;
+
+ len = parse_reg (src, &mode, &(op->reg));
+ if (mode != A_REG_N)
+ as_bad (_("illegal register after @-"));
+
+ op->type = A_DEC_N;
+ src += len;
+ }
+ else if (src[0] == '(')
+ {
+ /* Could be @(disp, rn), @(disp, gbr), @(disp, pc), @(r0, gbr) or
+ @(r0, rn). */
+ src++;
+ len = parse_reg (src, &mode, &(op->reg));
+ if (len && mode == A_REG_N)
+ {
+ src += len;
+ if (op->reg != 0)
+ {
+ as_bad (_("must be @(r0,...)"));
+ }
+ if (src[0] == ',')
+ {
+ src++;
+ /* Now can be rn or gbr. */
+ len = parse_reg (src, &mode, &(op->reg));
+ }
+ else
+ {
+ len = 0;
+ }
+ if (len)
+ {
+ if (mode == A_GBR)
+ {
+ op->type = A_R0_GBR;
+ }
+ else if (mode == A_REG_N)
+ {
+ op->type = A_IND_R0_REG_N;
+ }
+ else
+ {
+ as_bad (_("syntax error in @(r0,...)"));
+ }