From becfe05e6138362a050fd8003c7adaf4c9c39c57 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 16 Jul 1993 16:26:41 +0000 Subject: [PATCH] * config/tc-hppa.h (tc_frob_label): Define. * config/tc-mips.c: Many changes to support simple assembler optimization. (insn_label, prev_insn, prev_prev_insn, dummy_opcode, prev_insn_valid, prev_insn_frag, prev_insn_where, prev_insn_fixp, prev_insn_is_delay_slot): New static variables. (insn_uses_reg, mips_no_prev_insn, mips_emit_delays, mips_align, s_stringer, s_mips_space): New static functions. (mips_define_label): New global function. (md_pseudo_table): For "ascii", "asciz", "asciiz", call s_stringer. Changed argument to float_cons from 0 or 1 to 'f' or 'd'. For "space" call s_mips_space. (md_begin): Call mips_no_prev_insn. (append_insn): Only insert necessary NOP instructions. (macro): Call mips_emit_delays before setting mips_noreorder. Increment and decrement mips_noreorder rather than using save_reorder_condition. Don't bother to use noreorder in M_L_DOB and M_L_DAB, since append_insn will not insert a NOP. (md_atof): Handle floating point numbers correctly for both big and little endian targets. (s_align, s_cons): Call mips_align rather than frag_align. (s_change_seg, s_cons): Call mips_emit_delays. (s_float_cons): Let float_cons do the work. (s_mipsset): Call mips_emit_delays when setting noreorder. * config/tc-mips.h (tc_frob_label): Define to be mips_define_label. --- gas/config/tc-hppa.h | 2 + gas/config/tc-mips.c | 681 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 560 insertions(+), 123 deletions(-) diff --git a/gas/config/tc-hppa.h b/gas/config/tc-hppa.h index 308863f460..821ec3d00d 100644 --- a/gas/config/tc-hppa.h +++ b/gas/config/tc-hppa.h @@ -468,6 +468,8 @@ extern struct default_space_dict pa_def_spaces[]; space. */ +#define tc_frob_label(sym) pa_define_label (sym) + typedef struct label_symbol_struct { symbolS *lss_label; /* the label symbol */ diff --git a/gas/config/tc-mips.c b/gas/config/tc-mips.c index 18c1bcfd1a..9075b26ac9 100644 --- a/gas/config/tc-mips.c +++ b/gas/config/tc-mips.c @@ -95,6 +95,37 @@ static char *insn_error; static int byte_order = BYTE_ORDER; static int auto_align = 1; + +/* Symbol labelling the current insn. */ +static symbolS *insn_label; + +/* To output NOP instructions correctly, we need to keep information + about the previous two instructions. */ + +/* The previous instruction. */ +static struct mips_cl_insn prev_insn; + +/* The instruction before prev_insn. */ +static struct mips_cl_insn prev_prev_insn; + +/* If we don't want information for prev_insn or prev_prev_insn, we + point the insn_mo field at this dummy integer. */ +static const struct mips_opcode dummy_opcode = { 0 }; + +/* Non-zero if prev_insn is valid. */ +static int prev_insn_valid; + +/* The frag for the previous instruction. */ +static struct frag *prev_insn_frag; + +/* The offset into prev_insn_frag for the previous instruction. */ +static long prev_insn_where; + +/* The reloc for the previous instruction, if any. */ +static fixS *prev_insn_fixp; + +/* Non-zero if the previous instruction was in a delay slot. */ +static int prev_insn_is_delay_slot; /* Prototypes for static functions. */ @@ -105,9 +136,13 @@ static int auto_align = 1; #define internalError() as_fatal ("MIPS internal Error"); #endif +static int insn_uses_reg PARAMS ((struct mips_cl_insn *ip, + int reg, int fpr)); static void append_insn PARAMS ((struct mips_cl_insn * ip, expressionS * p, bfd_reloc_code_real_type r)); +static void mips_no_prev_insn PARAMS ((void)); +static void mips_emit_delays PARAMS ((void)); static int gp_reference PARAMS ((expressionS * ep)); static void macro_build PARAMS ((int *counter, expressionS * ep, const char *name, const char *fmt, @@ -127,7 +162,9 @@ static int my_getSmallExpression PARAMS ((expressionS * ep, char *str)); static void my_getExpression PARAMS ((expressionS * ep, char *str)); static symbolS *get_symbol PARAMS ((void)); static long get_optional_absolute_expression PARAMS ((void)); +static void mips_align PARAMS ((int to, int fill)); static void s_align PARAMS ((int)); +static void s_stringer PARAMS ((int)); static void s_change_sec PARAMS ((int)); static void s_cons PARAMS ((int)); static void s_err PARAMS ((int)); @@ -135,6 +172,7 @@ static void s_extern PARAMS ((int)); static void s_float_cons PARAMS ((int)); static void s_option PARAMS ((int)); static void s_mipsset PARAMS ((int)); +static void s_mips_space PARAMS ((int)); #ifndef OBJ_ECOFF static void md_obj_begin PARAMS ((void)); static void md_obj_end PARAMS ((void)); @@ -175,7 +213,7 @@ const pseudo_typeS md_pseudo_table[] = /* Relatively generic pseudo-ops that happen to be used on MIPS chips. */ - {"asciiz", stringer, 1}, + {"asciiz", s_stringer, 1}, {"bss", s_change_sec, 'b'}, {"err", s_err, 0}, {"half", s_cons, 1}, @@ -183,11 +221,14 @@ const pseudo_typeS md_pseudo_table[] = /* These pseudo-ops are defined in read.c, but must be overridden here for one reason or another. */ {"align", s_align, 0}, + {"ascii", s_stringer, 0}, + {"asciz", s_stringer, 1}, {"byte", s_cons, 0}, {"data", s_change_sec, 'd'}, - {"double", s_float_cons, 1}, + {"double", s_float_cons, 'd'}, {"extern", s_extern, 0}, - {"float", s_float_cons, 0}, + {"float", s_float_cons, 'f'}, + {"space", s_mips_space, 0}, {"text", s_change_sec, 't'}, {"word", s_cons, 2}, @@ -262,6 +303,8 @@ md_begin () while ((i < NUMOPCODES) && !strcmp (mips_opcodes[i].name, name)); } + mips_no_prev_insn (); + #ifndef OBJ_ECOFF md_obj_begin (); #endif @@ -317,6 +360,48 @@ md_assemble (str) } } +/* See whether instruction IP reads register REG. If FPR is non-zero, + REG is a floating point register. */ + +static int +insn_uses_reg (ip, reg, fpr) + struct mips_cl_insn *ip; + int reg; + int fpr; +{ + /* Don't report on general register 0, since it never changes. */ + if (! fpr && reg == 0) + return 0; + + if (fpr) + { + /* If we are called with either $f0 or $f1, we must check $f0. + This is not optimal, because it will introduce an unnecessary + NOP between "lwc1 $f0" and "swc1 $f1". To fix this we would + need to distinguish reading both $f0 and $f1 or just one of + them. Note that we don't have to check the other way, + because there is no instruction that sets both $f0 and $f1 + and requires a delay. */ + if ((ip->insn_mo->pinfo & INSN_READ_FPR_S) + && ((ip->insn_opcode >> OP_SH_FS) & OP_MASK_FS) == (reg &~ 1)) + return 1; + if ((ip->insn_mo->pinfo & INSN_READ_FPR_T) + && ((ip->insn_opcode >> OP_SH_FT) & OP_MASK_FT) == (reg &~ 1)) + return 1; + } + else + { + if ((ip->insn_mo->pinfo & INSN_READ_GPR_S) + && ((ip->insn_opcode >> OP_SH_RS) & OP_MASK_RS) == reg) + return 1; + if ((ip->insn_mo->pinfo & INSN_READ_GPR_T) + && ((ip->insn_opcode >> OP_SH_RT) & OP_MASK_RT) == reg) + return 1; + } + + return 0; +} + #define ALIGN_ERR "Attempt to assemble instruction onto non word boundary." #define ALIGN_ERR2 "GAS doesn't do implicit alignment; use .align directive." @@ -331,20 +416,161 @@ append_insn (ip, address_expr, reloc_type) bfd_reloc_code_real_type reloc_type; { char *f; + fixS *fixp; + int nops = 0; + if (! mips_noreorder) + { + /* If the previous insn required any delay slots, see if we need + to insert a NOP or two. There are six kinds of possible + hazards, of which an instruction can have at most one type. + (1) a load delay + (2) an unconditional branch delay + (3) a conditional branch delay + (4) a generic coprocessor delay + (5) a coprocessor condition code delay + (6) a HI/LO special register delay + + There are a lot of optimizations we could do that we don't. + In particular, we do not, in general, reorder instructions. + If you use gcc with optimization, it will reorder + instructions and generally do much more optimization then we + do here; repeating all that work in the assembler would only + benefit hand written assembly code, and does not seem worth + it. */ + + /* This is how a NOP is emitted. */ +#define emit_nop() md_number_to_chars (frag_more (4), 0, 4) + + /* The previous insn might require a delay slot, depending upon + the contents of the current insn. */ + if (prev_insn.insn_mo->pinfo & INSN_LOAD_DELAY) + { + /* A load delay. All load delays delay the use of general + register rt for one instruction. */ + know (prev_insn.insn_mo->pinfo & INSN_WRITE_GPR_T); + if (insn_uses_reg (ip, + (prev_insn.insn_opcode >> OP_SH_RT) & OP_MASK_RT, + 0)) + ++nops; + } + else if (prev_insn.insn_mo->pinfo & INSN_COPROC_DELAY) + { + /* A generic coprocessor delay. The previous instruction + modified a coprocessor general or control register. If + it modified a control register, we need to avoid any + coprocessor instruction (this is probably not always + required, but it sometimes is). If it modified a general + register, we avoid using that register. + + This case is not handled very well. There is no special + knowledge of CP0 handling, and the coprocessors other + than the floating point unit are not distinguished at + all. */ + if (prev_insn.insn_mo->pinfo & INSN_WRITE_FPR_T) + { + if (insn_uses_reg (ip, + ((prev_insn.insn_opcode >> OP_SH_RT) + & OP_MASK_RT), + 1)) + ++nops; + } + else if (prev_insn.insn_mo->pinfo & INSN_WRITE_FPR_D) + { + if (insn_uses_reg (ip, + ((prev_insn.insn_opcode >> OP_SH_RD) + & OP_MASK_RD), + 1)) + ++nops; + } + else + { + /* We don't know exactly what the previous instruction + does. If the current instruction uses a coprocessor + register, we must insert a NOP. If previous + instruction may set the condition codes, and the + current instruction uses them, we must insert two + NOPS. */ + if ((prev_insn.insn_mo->pinfo & INSN_WRITE_COND_CODE) + && (ip->insn_mo->pinfo & INSN_READ_COND_CODE)) + nops += 2; + else if (ip->insn_mo->pinfo & INSN_COP) + ++nops; + } + } + else if (prev_insn.insn_mo->pinfo & INSN_WRITE_COND_CODE) + { + /* The previous instruction sets the coprocessor condition + codes, but does not require a general coprocessor delay + (this means it is a floating point comparison + instruction). If this instruction uses the condition + codes, we need to insert a single NOP. */ + if (ip->insn_mo->pinfo & INSN_READ_COND_CODE) + ++nops; + } + else if (prev_insn.insn_mo->pinfo & INSN_READ_LO) + { + /* The previous instruction reads the LO register; if the + current instruction writes to the LO register, we must + insert two NOPS. */ + if (ip->insn_mo->pinfo & INSN_WRITE_LO) + nops += 2; + } + else if (prev_insn.insn_mo->pinfo & INSN_READ_HI) + { + /* The previous instruction reads the HI register; if the + current instruction writes to the HI register, we must + insert a NOP. */ + if (ip->insn_mo->pinfo & INSN_WRITE_HI) + nops += 2; + } + + /* There are two cases which require two intervening + instructions: 1) setting the condition codes using a move to + coprocessor instruction which requires a general coprocessor + delay and then reading the condition codes 2) reading the HI + or LO register and then writing to it. If we are not already + emitting a NOP instruction, we must check for these cases + compared to the instruction previous to the previous + instruction. */ + if (nops == 0 + && (((prev_prev_insn.insn_mo->pinfo & INSN_COPROC_DELAY) + && (prev_prev_insn.insn_mo->pinfo & INSN_WRITE_COND_CODE) + && (ip->insn_mo->pinfo & INSN_READ_COND_CODE)) + || ((prev_prev_insn.insn_mo->pinfo & INSN_READ_LO) + && (ip->insn_mo->pinfo & INSN_WRITE_LO)) + || ((prev_prev_insn.insn_mo->pinfo & INSN_READ_HI) + && (ip->insn_mo->pinfo & INSN_WRITE_HI)))) + ++nops; + + /* Now emit the right number of NOP instructions. */ + if (nops > 0) + { + emit_nop (); + if (nops > 1) + emit_nop (); + if (insn_label != NULL) + { + assert (S_GET_SEGMENT (insn_label) == now_seg); + insn_label->sy_frag = frag_now; + S_SET_VALUE (insn_label, frag_now_fix ()); + } + } + } + f = frag_more (4); -#if 0 /* This is testing the address of the frag, not the alignment of - the instruction in the current section. */ +#if 0 + /* This is testing the address of the frag, not the alignment of + the instruction in the current section. */ if ((int) f & 3) { as_bad (ALIGN_ERR); as_bad (ALIGN_ERR2); } #endif + fixp = NULL; if (address_expr != NULL) { - fixS *fixP; - if (address_expr->X_seg == &bfd_abs_section) { switch (reloc_type) @@ -369,7 +595,7 @@ append_insn (ip, address_expr, reloc_type) { assert (reloc_type != BFD_RELOC_UNUSED); need_reloc: - fixP = fix_new (frag_now, f - frag_now->fr_literal, 4, + fixp = fix_new (frag_now, f - frag_now->fr_literal, 4, address_expr->X_add_symbol, address_expr->X_subtract_symbol, address_expr->X_add_number, @@ -377,25 +603,203 @@ append_insn (ip, address_expr, reloc_type) reloc_type); } } + md_number_to_chars (f, ip->insn_opcode, 4); - /* - * Fill all delay slots with nops. - */ - if (!mips_noreorder) + if (! mips_noreorder) { - if (ip->insn_mo->pinfo & ANY_DELAY) + /* Filling the branch delay slot is more complex. We try to + switch the branch with the previous instruction, which we can + do if the previous instruction does not set up a condition + that the branch tests and if the branch is not itself the + target of any branch. */ + if ((ip->insn_mo->pinfo & INSN_UNCOND_BRANCH_DELAY) + || (ip->insn_mo->pinfo & INSN_COND_BRANCH_DELAY)) + { + /* If we had to emit any NOP instructions, then we already + know we can not swap. */ + if (nops != 0 + /* If we don't even know the previous insn, we can not + swap. */ + || ! prev_insn_valid + /* If the previous insn is already in a branch delay + slot, then we can not swap. */ + || prev_insn_is_delay_slot + /* If the branch is itself the target of a branch, we + can not swap. We cheat on this; all we check for is + whether there is a label on this instruction. If + there are any branches to anything other than a + label, users must use .set noreorder. */ + || insn_label != NULL + /* If the branch reads the condition codes, we don't + even try to swap, because in the sequence + ctc1 $X,$31 + INSN + INSN + bc1t LABEL + we can not swap, and I don't feel like handling that + case. */ + || (ip->insn_mo->pinfo & INSN_READ_COND_CODE) + /* We can not swap with an instruction that requires a + delay slot, becase the target of the branch might + interfere with that instruction. */ + || (prev_insn.insn_mo->pinfo + & (INSN_LOAD_DELAY + | INSN_COPROC_DELAY + | INSN_WRITE_COND_CODE + | INSN_READ_LO + | INSN_READ_HI)) + /* We can not swap with a branch instruction. */ + || (prev_insn.insn_mo->pinfo + & (INSN_UNCOND_BRANCH_DELAY | INSN_COND_BRANCH_DELAY)) + /* If the branch reads a register that the previous + instruction sets, we can not swap. */ + || ((prev_insn.insn_mo->pinfo & INSN_WRITE_GPR_T) + && insn_uses_reg (ip, + ((prev_insn.insn_opcode >> OP_SH_RT) + & OP_MASK_RT), + 0)) + || ((prev_insn.insn_mo->pinfo & INSN_WRITE_GPR_D) + && insn_uses_reg (ip, + ((prev_insn.insn_opcode >> OP_SH_RD) + & OP_MASK_RD), + 0)) + /* If the branch writes a register that the previous + instruction reads, we can not swap (we know that + branches only write to RD or to $31). */ + || ((ip->insn_mo->pinfo & INSN_WRITE_GPR_D) + && insn_uses_reg (&prev_insn, + ((ip->insn_opcode >> OP_SH_RD) + & OP_MASK_RD), + 0)) + || ((ip->insn_mo->pinfo & INSN_WRITE_GPR_31) + && insn_uses_reg (&prev_insn, 31, 0)) + /* If the previous previous instruction has a load + delay, and sets a register that the branch reads, we + can not swap. */ + || ((prev_prev_insn.insn_mo->pinfo & INSN_LOAD_DELAY) + && insn_uses_reg (ip, + ((prev_prev_insn.insn_opcode >> OP_SH_RT) + & OP_MASK_RT), + 0))) + { + /* We could do even better for unconditional branches to + portions of this object file; we could pick up the + instruction at the destination, put it in the delay + slot, and bump the destination address. */ + emit_nop (); + /* Update the previous insn information. */ + prev_prev_insn = *ip; + prev_insn.insn_mo = &dummy_opcode; + } + else + { + char *prev_f; + char temp[4]; + + /* It looks like we can actually do the swap. */ + prev_f = prev_insn_frag->fr_literal + prev_insn_where; + memcpy (temp, prev_f, 4); + memcpy (prev_f, f, 4); + memcpy (f, temp, 4); + if (prev_insn_fixp) + { + prev_insn_fixp->fx_frag = frag_now; + prev_insn_fixp->fx_where = f - frag_now->fr_literal; + } + if (fixp) + { + fixp->fx_frag = prev_insn_frag; + fixp->fx_where = prev_insn_where; + } + /* Update the previous insn information; leave prev_insn + unchanged. */ + prev_prev_insn = *ip; + } + prev_insn_is_delay_slot = 1; + + /* If that was an unconditional branch, forget the previous + insn information. */ + if (ip->insn_mo->pinfo & INSN_UNCOND_BRANCH_DELAY) + { + prev_prev_insn.insn_mo = &dummy_opcode; + prev_insn.insn_mo = &dummy_opcode; + } + } + else { - f = frag_more (4); - md_number_to_chars (f, 0, 4); - }; + /* Update the previous insn information. */ + if (nops > 0) + prev_prev_insn.insn_mo = &dummy_opcode; + else + prev_prev_insn = prev_insn; + prev_insn = *ip; + + /* Any time we see a branch, we always fill the delay slot + immediately; since this insn is not a branch, we know it + is not in a delay slot. */ + prev_insn_is_delay_slot = 0; + } + + prev_insn_frag = frag_now; + prev_insn_where = f - frag_now->fr_literal; + prev_insn_fixp = fixp; + prev_insn_valid = 1; + } - /* One extra nop */ - if (ip->insn_mo->pinfo & (INSN_READ_HI | INSN_READ_LO)) + /* We just output an insn, so the next one doesn't have a label. */ + insn_label = NULL; +} + +/* This function forgets that there was any previous instruction or + label. */ + +static void +mips_no_prev_insn () +{ + prev_insn.insn_mo = &dummy_opcode; + prev_prev_insn.insn_mo = &dummy_opcode; + prev_insn_valid = 0; + prev_insn_is_delay_slot = 0; + insn_label = NULL; +} + +/* This function must be called whenever we turn on noreorder or emit + something other than instructions. It inserts any NOPS which might + be needed by the previous instruction, and clears the information + kept for the previous instructions. */ + +static void +mips_emit_delays () +{ + if (! mips_noreorder) + { + int nop; + + nop = 0; + if (prev_insn.insn_mo->pinfo & ANY_DELAY) + { + nop = 1; + if ((prev_insn.insn_mo->pinfo & INSN_WRITE_COND_CODE) + || (prev_insn.insn_mo->pinfo & INSN_READ_HI) + || (prev_insn.insn_mo->pinfo & INSN_READ_LO)) + emit_nop (); + } + else if ((prev_prev_insn.insn_mo->pinfo & INSN_WRITE_COND_CODE) + || (prev_prev_insn.insn_mo->pinfo & INSN_READ_HI) + || (prev_prev_insn.insn_mo->pinfo & INSN_READ_LO)) + nop = 1; + if (nop) { - f = frag_more (4); - md_number_to_chars (f, 0, 4); + emit_nop (); + if (insn_label != NULL) + { + assert (S_GET_SEGMENT (insn_label) == now_seg); + insn_label->sy_frag = frag_now; + S_SET_VALUE (insn_label, frag_now_fix ()); + } } + mips_no_prev_insn (); } } @@ -769,7 +1173,6 @@ macro (ip) int mask; int icnt = 0; int used_at; - int save_reorder_condition; expressionS expr1; const char *s; const char *fmt; @@ -796,8 +1199,8 @@ macro (ip) : nop */ - save_reorder_condition = mips_noreorder; - mips_noreorder = 1; + mips_emit_delays (); + ++mips_noreorder; expr1.X_add_number = 8; macro_build (&icnt, &expr1, "bgez", "s,p", sreg); @@ -805,7 +1208,7 @@ macro (ip) macro_build (&icnt, NULL, mask == M_ABS ? "sub" : "subu", "d,v,t", dreg, 0, sreg); - mips_noreorder = save_reorder_condition; + --mips_noreorder; return; case M_ADD_I: @@ -1110,8 +1513,8 @@ macro (ip) return; } - save_reorder_condition = mips_noreorder; - mips_noreorder = 1; + mips_emit_delays (); + ++mips_noreorder; macro_build (&icnt, NULL, "div", "s,t", sreg, treg); expr1.X_add_number = 8; macro_build (&icnt, &expr1, "bne", "s,t,p", treg, 0); @@ -1127,7 +1530,7 @@ macro (ip) macro_build (&icnt, &expr1, "bne", "s,t,p", sreg, AT); macro_build (&icnt, NULL, "nop", "", 0); macro_build (&icnt, NULL, "break", "c", 6); - mips_noreorder = save_reorder_condition; + --mips_noreorder; macro_build (&icnt, NULL, mask == M_DIV_3 ? "mflo" : "mfhi", "d", dreg); /* with reorder on there will be two implicit nop instructions here. */ break; @@ -1166,14 +1569,14 @@ macro (ip) case M_DIVU_3: case M_REMU_3: - save_reorder_condition = mips_noreorder; - mips_noreorder = 1; + mips_emit_delays (); + ++mips_noreorder; macro_build (&icnt, NULL, "divu", "s,t", sreg, treg); expr1.X_add_number = 8; macro_build (&icnt, &expr1, "bne", "s,t,p", treg, 0); macro_build (&icnt, NULL, "nop", "", 0); macro_build (&icnt, NULL, "break", "c", 7); - mips_noreorder = save_reorder_condition; + --mips_noreorder; macro_build (&icnt, NULL, mask == M_DIVU_3 ? "mflo" : "mfhi", "d", dreg); /* with reorder on there will be two implicit nop instructions here. */ return; @@ -1347,13 +1750,9 @@ macro (ip) case M_L_DOB: /* Even on a big endian machine $fn comes before $fn+1. We have to adjust when loading from memory. */ - save_reorder_condition = mips_noreorder; - mips_noreorder = 1; macro_build (&icnt, &offset_expr, "lwc1", "T,o(b)", byte_order == LITTLE_ENDIAN ? treg : treg + 1, breg); - /* unecessary implicit nop */ - mips_noreorder = save_reorder_condition; offset_expr.X_add_number += 4; macro_build (&icnt, &offset_expr, "lwc1", "T,o(b)", byte_order == LITTLE_ENDIAN ? treg + 1 : treg, @@ -1391,13 +1790,9 @@ macro (ip) } /* Even on a big endian machine $fn comes before $fn+1. We have to adjust when loading from memory. */ - save_reorder_condition = mips_noreorder; - mips_noreorder = 1; macro_build (&icnt, &offset_expr, "lwc1", "T,o(b)", byte_order == LITTLE_ENDIAN ? treg : treg + 1, tempreg); - /* unecessary implicit nop */ - mips_noreorder = save_reorder_condition; offset_expr.X_add_number += 4; macro_build (&icnt, &offset_expr, "lwc1", "T,o(b)", byte_order == LITTLE_ENDIAN ? treg + 1 : treg, @@ -1782,8 +2177,8 @@ macro (ip) * Is the double cfc1 instruction a bug in the mips assembler; * or is there a reason for it? */ - save_reorder_condition = mips_noreorder; - mips_noreorder = 1; + mips_emit_delays (); + ++mips_noreorder; macro_build (&icnt, NULL, "cfc1", "t,G", treg, 31); macro_build (&icnt, NULL, "cfc1", "t,G", treg, 31); macro_build (&icnt, NULL, "nop", ""); @@ -1797,7 +2192,7 @@ macro (ip) mask == M_TRUNCWD ? "cvt.w.d" : "cvt.w.s", "D,S", dreg, sreg); macro_build (&icnt, NULL, "ctc1", "t,G", treg, 31); macro_build (&icnt, NULL, "nop", ""); - mips_noreorder = save_reorder_condition; + --mips_noreorder; break; case M_ULH: @@ -2443,13 +2838,60 @@ my_getExpression (ep, str) input_line_pointer = save_in; } +/* 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 (type, litP, sizeP) - char type; + int type; char *litP; int *sizeP; { - internalError (); + 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 (byte_order == LITTLE_ENDIAN) + { + for (i = prec - 1; i >= 0; i--) + { + md_number_to_chars (litP, (valueT) words[i], 2); + litP += 2; + } + } + else + { + for (i = 0; i < prec; i++) + { + md_number_to_chars (litP, (valueT) words[i], 2); + litP += 2; + } + } + return NULL; } @@ -2729,6 +3171,28 @@ get_optional_absolute_expression () return exp.X_add_number; } +/* Align the current frag to a given power of two. The MIPS assembler + also automatically adjusts any preceding label. */ + +static void +mips_align (to, fill) + int to; + int fill; +{ + mips_emit_delays (); + frag_align (to, fill); + record_alignment (now_seg, to); + if (insn_label != NULL) + { + assert (S_GET_SEGMENT (insn_label) == now_seg); + insn_label->sy_frag = frag_now; + S_SET_VALUE (insn_label, frag_now_fix ()); + } +} + +/* Align to a given power of two. .align 0 turns off the automatic + alignment used by the data creating pseudo-ops. */ + static void s_align (x) int x; @@ -2767,23 +3231,35 @@ s_align (x) if (temp) { auto_align = 1; - if (!need_pass_2) - frag_align (temp, (int) temp_fill); + mips_align (temp, (int) temp_fill); } else { auto_align = 0; } - record_alignment (now_seg, temp); - demand_empty_rest_of_line (); } +/* Handle .ascii and .asciiz. This just calls stringer and forgets + that there was a previous instruction. */ + +static void +s_stringer (append_zero) + int append_zero; +{ + mips_emit_delays (); + stringer (append_zero); +} + static void s_change_sec (sec) int sec; { + segT segment; + + mips_emit_delays (); + segment = now_seg; switch (sec) { case 't': @@ -2792,6 +3268,7 @@ s_change_sec (sec) case 'r': #ifdef OBJ_ECOFF subseg_new (".rdata", (subsegT) get_absolute_expression ()); + demand_empty_rest_of_line (); break; #else /* Fall through. */ @@ -2810,9 +3287,11 @@ s_change_sec (sec) case 's': #ifdef OBJ_ECOFF subseg_new (".sdata", (subsegT) get_absolute_expression ()); + demand_empty_rest_of_line (); break; #else as_bad ("Global pointers not supported; recompile -G 0"); + demand_empty_rest_of_line (); return; #endif } @@ -2823,9 +3302,9 @@ static void s_cons (log_size) int log_size; { - + mips_emit_delays (); if (log_size > 0 && auto_align) - frag_align (log_size, 0); + mips_align (log_size, 0); cons (1 << log_size); } @@ -2858,85 +3337,18 @@ s_extern (x) } static void -s_float_cons (is_double) - int is_double; +s_float_cons (type) + int type; { - char *f; - short words[4]; - int error_code, repeat; - extern FLONUM_TYPE generic_floating_point_number; + mips_emit_delays (); if (auto_align) - if (is_double) - frag_align (3, 0); + if (type == 'd') + mips_align (3, 0); else - frag_align (2, 0); + mips_align (2, 0); - SKIP_WHITESPACE (); - if (!is_end_of_line[(unsigned char) *input_line_pointer]) - { - do - { - error_code = atof_generic (&input_line_pointer, ".", EXP_CHARS, - &generic_floating_point_number); - if (error_code) - { - if (error_code == ERROR_EXPONENT_OVERFLOW) - as_warn ("Bad floating-point constant: exponent overflow"); - else - as_warn ("Bad floating-point constant: unknown error code=%d.", error_code); - } - - if (is_double) - { - gen_to_words ((LITTLENUM_TYPE *) words, - 4 /* precision */ , - 11 /* exponent_bits */ ); - } - else - { - gen_to_words ((LITTLENUM_TYPE *) words, - 2 /* precision */ , - 8 /* exponent_bits */ ); - } - if (*input_line_pointer == ':') - { - input_line_pointer++; - repeat = get_absolute_expression (); - } - else - { - repeat = 1; - } - if (is_double) - { - f = frag_more (repeat * 8); - for (; repeat--; f += 8) - { - md_number_to_chars (f + 6, words[0], 2); - md_number_to_chars (f + 4, words[1], 2); - md_number_to_chars (f + 2, words[2], 2); - md_number_to_chars (f, words[3], 2); - } - } - else - { - f = frag_more (repeat * 4); - for (; repeat--; f += 4) - { - md_number_to_chars (f + 2, words[0], 2); - md_number_to_chars (f, words[1], 2); - } - } - SKIP_WHITESPACE (); - if (*input_line_pointer != ',') - break; - input_line_pointer++; - SKIP_WHITESPACE (); - } - while (1); - } - demand_empty_rest_of_line (); + float_cons (type); } static void @@ -2966,6 +3378,7 @@ s_mipsset (x) } else if (strcmp (name, "noreorder") == 0) { + mips_emit_delays (); mips_noreorder = 1; } else if (strcmp (name, "at") == 0) @@ -3010,6 +3423,17 @@ s_mipsset (x) demand_empty_rest_of_line (); } +/* The same as the usual .space directive, except that we have to + forget about any previous instruction. */ + +static void +s_mips_space (param) + int param; +{ + mips_emit_delays (); + s_space (param); +} + int tc_get_register () { @@ -3098,6 +3522,17 @@ md_estimate_size_before_relax (fragP, segtype) as_fatal ("md_estimate_size_before_relax"); return (1); } /* md_estimate_size_before_relax() */ + +/* This function is called whenever a label is defined. It is used + when handling branch delays; if a branch has a label, we assume we + can not move it. */ + +void +mips_define_label (sym) + symbolS *sym; +{ + insn_label = sym; +} #ifndef OBJ_ECOFF -- 2.34.1