/* tc-mips.c -- assemble code for a MIPS chip.
Copyright 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
- 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+ 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
Contributed by the OSF and Ralph Campbell.
Written by Keith Knowles and Ralph Campbell, working independently.
Modified for ECOFF and R4000 support by Ian Lance Taylor of Cygnus
#endif
#define ZERO 0
-#define AT 1
+#define ATREG 1
#define TREG 24
#define PIC_CALL_REG 25
#define KT0 26
#define ILLEGAL_REG (32)
+#define AT mips_opts.at
+
/* Allow override of standard little-endian ECOFF format. */
#ifndef ECOFF_LITTLE_FORMAT
/* Non-zero if we should not reorder instructions. Changed by `.set
reorder' and `.set noreorder'. */
int noreorder;
- /* Non-zero if we should not permit the $at ($1) register to be used
- in instructions. Changed by `.set at' and `.set noat'. */
- int noat;
+ /* Non-zero if we should not permit the register designated "assembler
+ temporary" to be used in instructions. The value is the register
+ number, normally $at ($1). Changed by `.set at=REG', `.set noat'
+ (same as `.set at=$0') and `.set at' (same as `.set at=$1'). */
+ unsigned int at;
/* Non-zero if we should warn when a macro instruction expands into
more than one machine instruction. Changed by `.set nomacro' and
`.set macro'. */
static struct mips_set_options mips_opts =
{
- ISA_UNKNOWN, -1, -1, 0, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, CPU_UNKNOWN, FALSE
+ ISA_UNKNOWN, -1, -1, 0, -1, -1, -1, -1, 0, ATREG, 0, 0, 0, 0, 0, 0, CPU_UNKNOWN, FALSE
};
/* These variables are filled in with the masks of registers used.
/* Relatively generic pseudo-ops that happen to be used on MIPS
chips. */
- {"asciiz", stringer, 1},
+ {"asciiz", stringer, 8 + 1},
{"bss", s_change_sec, 'b'},
{"err", s_err, 0},
{"half", s_cons, 1},
};
static struct insn_label_list *free_insn_labels;
-#define label_list tc_segment_info_data
+#define label_list tc_segment_info_data.labels
static void mips_clear_insn_labels (void);
insn->mips16_absolute_jump_p = 0;
}
+/* Record the current MIPS16 mode in now_seg. */
+
+static void
+mips_record_mips16_mode (void)
+{
+ segment_info_type *si;
+
+ si = seg_info (now_seg);
+ if (si->tc_segment_info_data.mips16 != mips_opts.mips16)
+ si->tc_segment_info_data.mips16 = mips_opts.mips16;
+}
+
/* Install INSN at the location specified by its "frag" and "where" fields. */
static void
}
md_number_to_chars (f, insn->insn_opcode, 2);
}
+ mips_record_mips16_mode ();
}
/* Move INSN to offset WHERE in FRAG. Adjust the fixups accordingly
.set noat if we use $at for PIC computations. If it turns
out that the branch was out-of-range, we'll get an error. */
&& !mips_opts.warn_about_macros
- && !(mips_opts.noat && mips_pic != NO_PIC)
+ && (mips_opts.at || mips_pic == NO_PIC)
&& !mips_opts.mips16)
{
relaxed_branch = TRUE;
macro_build (NULL, ADDRESS_ADD_INSN, "d,v,t", AT, AT, breg);
macro_build (ep, op, "t,o(b)", treg, BFD_RELOC_LO16, AT);
- if (mips_opts.noat)
+ if (!mips_opts.at)
as_bad (_("Macro used $at after \".set noat\""));
}
}
relax_switch ();
}
- if (*used_at == 0 && !mips_opts.noat)
+ if (*used_at == 0 && mips_opts.at)
{
macro_build (ep, "lui", "t,u", reg, BFD_RELOC_MIPS_HIGHEST);
macro_build (ep, "lui", "t,u", AT, BFD_RELOC_HI16_S);
else
abort ();
- if (mips_opts.noat && *used_at == 1)
+ if (!mips_opts.at && *used_at == 1)
as_bad (_("Macro used $at after \".set noat\""));
}
static void
macro (struct mips_cl_insn *ip)
{
- int treg, sreg, dreg, breg;
- int tempreg;
+ unsigned int treg, sreg, dreg, breg;
+ unsigned int tempreg;
int mask;
int used_at = 0;
expressionS expr1;
break;
}
- if (!mips_opts.noat && (treg == breg))
+ if (mips_opts.at && (treg == breg))
{
tempreg = AT;
used_at = 1;
relax_switch ();
}
- if (used_at == 0 && !mips_opts.noat)
+ if (used_at == 0 && mips_opts.at)
{
macro_build (&offset_expr, "lui", "t,u",
tempreg, BFD_RELOC_MIPS_HIGHEST);
relax_switch ();
}
- if (used_at == 0 && !mips_opts.noat)
+ if (used_at == 0 && mips_opts.at)
{
macro_build (&offset_expr, "lui", "t,u", tempreg,
BFD_RELOC_MIPS_HIGHEST);
macro2 (ip);
break;
}
- if (mips_opts.noat && used_at)
+ if (!mips_opts.at && used_at)
as_bad (_("Macro used $at after \".set noat\""));
}
static void
macro2 (struct mips_cl_insn *ip)
{
- int treg, sreg, dreg, breg;
- int tempreg;
+ unsigned int treg, sreg, dreg, breg;
+ unsigned int tempreg;
int mask;
int used_at;
expressionS expr1;
as_bad (_("Macro %s not implemented yet"), ip->insn_mo->name);
break;
}
- if (mips_opts.noat && used_at)
+ if (!mips_opts.at && used_at)
as_bad (_("Macro used $at after \".set noat\""));
}
else
{
ok = reg_lookup (&s, RTYPE_NUM | RTYPE_GP, ®no);
- if (regno == AT && ! mips_opts.noat)
- as_warn ("Used $at without \".set noat\"");
+ if (regno == AT && mips_opts.at)
+ {
+ if (mips_opts.at == ATREG)
+ as_warn (_("used $at without \".set noat\""));
+ else
+ as_warn (_("used $%u with \".set at=$%u\""),
+ regno, mips_opts.at);
+ }
}
if (ok)
{
if (c == 'z' && regno != 0)
break;
+ if (c == 's' && !strcmp (ip->insn_mo->name, "jalr"))
+ {
+ if (regno == lastregno)
+ {
+ insn_error = _("source and destinationations must be different");
+ continue;
+ }
+ if (regno == 31 && lastregno == 0)
+ {
+ insn_error = _("a destination register must be supplied");
+ continue;
+ }
+ }
/* Now that we have assembled one operand, we use the args string
* to figure out where it goes in the instruction. */
switch (c)
case 'X':
case 'Y':
- if (regno == AT && ! mips_opts.noat)
- as_warn (_("used $at without \".set noat\""));
+ if (regno == AT && mips_opts.at)
+ {
+ if (mips_opts.at == ATREG)
+ as_warn (_("used $at without \".set noat\""));
+ else
+ as_warn (_("used $%u with \".set at=$%u\""),
+ regno, mips_opts.at);
+ }
break;
default:
S_SET_VALUE (ep->X_add_symbol, val + 1);
}
-/* Turn a string in input_line_pointer into a floating point constant
- of type TYPE, and store the appropriate bytes in *LITP. The number
- of LITTLENUMS emitted is stored in *SIZEP. An error message is
- returned, or NULL on OK. */
-
char *
md_atof (int type, char *litP, int *sizeP)
{
- int prec;
- LITTLENUM_TYPE words[4];
- char *t;
- int i;
-
- switch (type)
- {
- case 'f':
- prec = 2;
- break;
-
- case 'd':
- prec = 4;
- break;
-
- default:
- *sizeP = 0;
- return _("bad call to md_atof");
- }
-
- t = atof_ieee (input_line_pointer, type, words);
- if (t)
- input_line_pointer = t;
-
- *sizeP = prec * 2;
-
- if (! target_big_endian)
- {
- for (i = prec - 1; i >= 0; i--)
- {
- md_number_to_chars (litP, words[i], 2);
- litP += 2;
- }
- }
- else
- {
- for (i = 0; i < prec; i++)
- {
- md_number_to_chars (litP, words[i], 2);
- litP += 2;
- }
- }
-
- return NULL;
+ return ieee_md_atof (type, litP, sizeP, target_big_endian);
}
void
break;
case 'O':
- if (arg && arg[0] == '0')
+ if (arg == NULL)
+ mips_optimize = 1;
+ else if (arg[0] == '0')
+ mips_optimize = 0;
+ else if (arg[0] == '1')
mips_optimize = 1;
else
mips_optimize = 2;
return p;
}
-/* Align the current frag to a given power of two. The MIPS assembler
- also automatically adjusts any preceding label. */
+/* Align the current frag to a given power of two. If a particular
+ fill byte should be used, FILL points to an integer that contains
+ that byte, otherwise FILL is null.
+
+ The MIPS assembler also automatically adjusts any preceding
+ label. */
static void
-mips_align (int to, int fill, symbolS *label)
+mips_align (int to, int *fill, symbolS *label)
{
mips_emit_delays ();
- frag_align (to, fill, 0);
+ mips_record_mips16_mode ();
+ if (fill == NULL && subseg_text_p (now_seg))
+ frag_align_code (to, 0);
+ else
+ frag_align (to, fill ? *fill : 0, 0);
record_alignment (now_seg, to);
if (label != NULL)
{
static void
s_align (int x ATTRIBUTE_UNUSED)
{
- int temp;
- long temp_fill;
- long max_alignment = 15;
+ int temp, fill_value, *fill_ptr;
+ long max_alignment = 28;
/* o Note that the assembler pulls down any immediately preceding label
to the aligned address.
if (*input_line_pointer == ',')
{
++input_line_pointer;
- temp_fill = get_absolute_expression ();
+ fill_value = get_absolute_expression ();
+ fill_ptr = &fill_value;
}
else
- temp_fill = 0;
+ fill_ptr = 0;
if (temp)
{
segment_info_type *si = seg_info (now_seg);
struct insn_label_list *l = si->label_list;
/* Auto alignment should be switched on by next section change. */
auto_align = 1;
- mips_align (temp, (int) temp_fill, l != NULL ? l->label : NULL);
+ mips_align (temp, fill_ptr, l != NULL ? l->label : NULL);
}
else
{
if (!mips_opts.noreorder)
start_noreorder ();
}
+ else if (strncmp (name, "at=", 3) == 0)
+ {
+ char *s = name + 3;
+
+ if (!reg_lookup (&s, RTYPE_NUM | RTYPE_GP, &mips_opts.at))
+ as_bad (_("Unrecognized register name `%s'"), s);
+ }
else if (strcmp (name, "at") == 0)
{
- mips_opts.noat = 0;
+ mips_opts.at = ATREG;
}
else if (strcmp (name, "noat") == 0)
{
- mips_opts.noat = 1;
+ mips_opts.at = ZERO;
}
else if (strcmp (name, "macro") == 0)
{
static procS *cur_proc_ptr;
static int numprocs;
-/* Fill in an rs_align_code fragment. */
+/* Implement NOP_OPCODE. We encode a MIPS16 nop as "1" and a normal
+ nop as "0". */
+
+char
+mips_nop_opcode (void)
+{
+ return seg_info (now_seg)->tc_segment_info_data.mips16;
+}
+
+/* Fill in an rs_align_code fragment. This only needs to do something
+ for MIPS16 code, where 0 is not a nop. */
void
mips_handle_align (fragS *fragp)
{
+ char *p;
+
if (fragp->fr_type != rs_align_code)
return;
- if (mips_opts.mips16)
+ p = fragp->fr_literal + fragp->fr_fix;
+ if (*p)
{
- static const unsigned char be_nop[] = { 0x65, 0x00 };
- static const unsigned char le_nop[] = { 0x00, 0x65 };
-
int bytes;
- char *p;
bytes = fragp->fr_next->fr_address - fragp->fr_address - fragp->fr_fix;
- p = fragp->fr_literal + fragp->fr_fix;
-
if (bytes & 1)
{
*p++ = 0;
fragp->fr_fix++;
}
-
- memcpy (p, (target_big_endian ? be_nop : le_nop), 2);
+ md_number_to_chars (p, mips16_nop_insn.insn_opcode, 2);
fragp->fr_var = 2;
}
-
- /* For mips32, a nop is a zero, which we trivially get by doing nothing. */
}
static void
{ "sb1a", MIPS_CPU_ASE_MIPS3D | MIPS_CPU_ASE_MDMX,
ISA_MIPS64, CPU_SB1 },
+ /* ST Microelectronics Loongson 2E and 2F cores */
+ { "loongson2e", 0, ISA_MIPS3, CPU_LOONGSON_2E },
+ { "loongson2f", 0, ISA_MIPS3, CPU_LOONGSON_2F },
+
+ /* Cavium Networks Octeon CPU core */
+ { "octeon", 0, ISA_MIPS64R2, CPU_OCTEON },
+
/* End marker */
{ NULL, 0, 0, 0 }
};
enum dwarf2_format
mips_dwarf2_format (void)
{
- if (mips_abi == N64_ABI)
+ if (HAVE_64BIT_SYMBOLS)
{
#ifdef TE_IRIX
return dwarf2_format_64bit_irix;
int
mips_dwarf2_addr_size (void)
{
- if (mips_abi == N64_ABI)
+ if (HAVE_64BIT_SYMBOLS)
return 8;
else
return 4;