* config/tc-mips.c (append_method): New enum.
	(can_swap_branch_p, get_append_method): New functions.
	(append_insn): Use get_append_method to decide how the instruction
	should be added.
This commit is contained in:
Richard Sandiford
2011-06-29 21:05:29 +00:00
parent 0d1b033387
commit a4e064680b
2 changed files with 238 additions and 156 deletions

View File

@ -1,3 +1,10 @@
2011-06-29 Richard Sandiford <rdsandiford@googlemail.com>
* config/tc-mips.c (append_method): New enum.
(can_swap_branch_p, get_append_method): New functions.
(append_insn): Use get_append_method to decide how the instruction
should be added.
2011-06-29 Richard Sandiford <rdsandiford@googlemail.com> 2011-06-29 Richard Sandiford <rdsandiford@googlemail.com>
* config/tc-mips.c (append_insn): Remove bogus goto. * config/tc-mips.c (append_insn): Remove bogus goto.

View File

@ -121,6 +121,21 @@ extern int target_big_endian;
? ".rodata" \ ? ".rodata" \
: (abort (), "")) : (abort (), ""))
/* Ways in which an instruction can be "appended" to the output. */
enum append_method {
/* Just add it normally. */
APPEND_ADD,
/* Add it normally and then add a nop. */
APPEND_ADD_WITH_NOP,
/* Turn an instruction with a delay slot into a "compact" version. */
APPEND_ADD_COMPACT,
/* Insert the instruction before the last one. */
APPEND_SWAP
};
/* Information about an instruction, including its format, operands /* Information about an instruction, including its format, operands
and fixups. */ and fixups. */
struct mips_cl_insn struct mips_cl_insn
@ -3073,6 +3088,168 @@ fix_loongson2f (struct mips_cl_insn * ip)
fix_loongson2f_jump (ip); fix_loongson2f_jump (ip);
} }
/* IP is a branch that has a delay slot, and we need to fill it
automatically. Return true if we can do that by swapping IP
with the previous instruction. */
static bfd_boolean
can_swap_branch_p (struct mips_cl_insn *ip)
{
unsigned long pinfo, prev_pinfo;
unsigned int gpr_read, gpr_write, prev_gpr_read, prev_gpr_write;
/* -O2 and above is required for this optimization. */
if (mips_optimize < 2)
return FALSE;
/* If we have seen .set volatile or .set nomove, don't optimize. */
if (mips_opts.nomove)
return FALSE;
/* We can't swap if the previous instruction's position is fixed. */
if (history[0].fixed_p)
return FALSE;
/* If the previous previous insn was in a .set noreorder, we can't
swap. Actually, the MIPS assembler will swap in this situation.
However, gcc configured -with-gnu-as will generate code like
.set noreorder
lw $4,XXX
.set reorder
INSN
bne $4,$0,foo
in which we can not swap the bne and INSN. If gcc is not configured
-with-gnu-as, it does not output the .set pseudo-ops. */
if (history[1].noreorder_p)
return FALSE;
/* If the previous instruction had a fixup in mips16 mode, we can not
swap. This normally means that the previous instruction was a 4
byte branch anyhow. */
if (mips_opts.mips16 && history[0].fixp[0])
return FALSE;
/* 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. */
if (seg_info (now_seg)->label_list)
return FALSE;
/* If the previous instruction is in a variant frag other than this
branch's one, we cannot do the swap. This does not apply to the
mips16, which uses variant frags for different purposes. */
if (!mips_opts.mips16
&& history[0].frag
&& history[0].frag->fr_type == rs_machine_dependent)
return FALSE;
/* We do not swap with a trap instruction, since it complicates trap
handlers to have the trap instruction be in a delay slot. */
prev_pinfo = history[0].insn_mo->pinfo;
if (prev_pinfo & INSN_TRAP)
return FALSE;
/* If the previous instruction is a sync, sync.l, or sync.p, we can
not swap. */
if (prev_pinfo & INSN_SYNC)
return FALSE;
/* If the previous instruction is an ERET or DERET, avoid the swap. */
if (history[0].insn_opcode == INSN_ERET)
return FALSE;
if (history[0].insn_opcode == INSN_DERET)
return FALSE;
/* Check for conflicts between the branch and the instructions
before the candidate delay slot. */
if (nops_for_insn (0, history + 1, ip) > 0)
return FALSE;
/* Check for conflicts between the swapped sequence and the
target of the branch. */
if (nops_for_sequence (2, 0, history + 1, ip, history) > 0)
return FALSE;
/* If the branch reads a register that the previous
instruction sets, we can not swap. */
gpr_read = gpr_read_mask (ip);
prev_gpr_write = gpr_write_mask (&history[0]);
if (gpr_read & prev_gpr_write)
return FALSE;
/* If the branch writes a register that the previous
instruction sets, we can not swap. */
gpr_write = gpr_write_mask (ip);
if (gpr_write & prev_gpr_write)
return FALSE;
/* If the branch writes a register that the previous
instruction reads, we can not swap. */
prev_gpr_read = gpr_read_mask (&history[0]);
if (gpr_write & prev_gpr_read)
return FALSE;
/* If one instruction sets a condition code and the
other one uses a condition code, we can not swap. */
pinfo = ip->insn_mo->pinfo;
if ((pinfo & INSN_READ_COND_CODE)
&& (prev_pinfo & INSN_WRITE_COND_CODE))
return FALSE;
if ((pinfo & INSN_WRITE_COND_CODE)
&& (prev_pinfo & INSN_READ_COND_CODE))
return FALSE;
/* If the previous instruction uses the PC, we can not swap. */
if (mips_opts.mips16 && (prev_pinfo & MIPS16_INSN_READ_PC))
return FALSE;
return TRUE;
}
/* Decide how we should add IP to the instruction stream. */
static enum append_method
get_append_method (struct mips_cl_insn *ip)
{
unsigned long pinfo;
/* The relaxed version of a macro sequence must be inherently
hazard-free. */
if (mips_relax.sequence == 2)
return APPEND_ADD;
/* We must not dabble with instructions in a ".set norerorder" block. */
if (mips_opts.noreorder)
return APPEND_ADD;
/* Otherwise, it's our responsibility to fill branch delay slots. */
pinfo = ip->insn_mo->pinfo;
if ((pinfo & INSN_UNCOND_BRANCH_DELAY)
|| (pinfo & INSN_COND_BRANCH_DELAY))
{
if (can_swap_branch_p (ip))
return APPEND_SWAP;
if (mips_opts.mips16
&& ISA_SUPPORTS_MIPS16E
&& (pinfo & INSN_UNCOND_BRANCH_DELAY)
&& (pinfo & (MIPS16_INSN_READ_X | MIPS16_INSN_READ_31)))
return APPEND_ADD_COMPACT;
return APPEND_ADD_WITH_NOP;
}
/* We don't bother trying to track the target of branches, so there's
nothing we can use to fill a branch-likely slot. */
if (pinfo & INSN_COND_BRANCH_LIKELY)
return APPEND_ADD_WITH_NOP;
return APPEND_ADD;
}
/* IP is a MIPS16 instruction whose opcode we have just changed. /* IP is a MIPS16 instruction whose opcode we have just changed.
Point IP->insn_mo to the new opcode's definition. */ Point IP->insn_mo to the new opcode's definition. */
@ -3101,9 +3278,8 @@ append_insn (struct mips_cl_insn *ip, expressionS *address_expr,
{ {
unsigned long prev_pinfo, pinfo; unsigned long prev_pinfo, pinfo;
unsigned long prev_pinfo2, pinfo2; unsigned long prev_pinfo2, pinfo2;
relax_stateT prev_insn_frag_type = 0;
bfd_boolean relaxed_branch = FALSE; bfd_boolean relaxed_branch = FALSE;
segment_info_type *si = seg_info (now_seg); enum append_method method;
if (mips_fix_loongson2f) if (mips_fix_loongson2f)
fix_loongson2f (ip); fix_loongson2f (ip);
@ -3273,6 +3449,8 @@ append_insn (struct mips_cl_insn *ip, expressionS *address_expr,
} }
} }
method = get_append_method (ip);
#ifdef OBJ_ELF #ifdef OBJ_ELF
/* The value passed to dwarf2_emit_insn is the distance between /* The value passed to dwarf2_emit_insn is the distance between
the beginning of the current instruction and the address that the beginning of the current instruction and the address that
@ -3282,10 +3460,6 @@ append_insn (struct mips_cl_insn *ip, expressionS *address_expr,
dwarf2_emit_insn (mips_opts.mips16 ? -1 : 0); dwarf2_emit_insn (mips_opts.mips16 ? -1 : 0);
#endif #endif
/* Record the frag type before frag_var. */
if (history[0].frag)
prev_insn_frag_type = history[0].frag->fr_type;
if (address_expr if (address_expr
&& *reloc_type == BFD_RELOC_16_PCREL_S2 && *reloc_type == BFD_RELOC_16_PCREL_S2
&& (pinfo & INSN_UNCOND_BRANCH_DELAY || pinfo & INSN_COND_BRANCH_DELAY && (pinfo & INSN_UNCOND_BRANCH_DELAY || pinfo & INSN_COND_BRANCH_DELAY
@ -3469,116 +3643,30 @@ append_insn (struct mips_cl_insn *ip, expressionS *address_expr,
mips_gprmask |= gpr_read_mask (ip) | gpr_write_mask (ip); mips_gprmask |= gpr_read_mask (ip) | gpr_write_mask (ip);
mips_cprmask[1] |= fpr_read_mask (ip) | fpr_write_mask (ip); mips_cprmask[1] |= fpr_read_mask (ip) | fpr_write_mask (ip);
if (mips_relax.sequence != 2 && !mips_opts.noreorder) switch (method)
{
/* 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 ((pinfo & INSN_UNCOND_BRANCH_DELAY)
|| (pinfo & INSN_COND_BRANCH_DELAY))
{
if (mips_optimize < 2
/* If we have seen .set volatile or .set nomove, don't
optimize. */
|| mips_opts.nomove != 0
/* We can't swap if the previous instruction's position
is fixed. */
|| history[0].fixed_p
/* If the previous previous insn was in a .set
noreorder, we can't swap. Actually, the MIPS
assembler will swap in this situation. However, gcc
configured -with-gnu-as will generate code like
.set noreorder
lw $4,XXX
.set reorder
INSN
bne $4,$0,foo
in which we can not swap the bne and INSN. If gcc is
not configured -with-gnu-as, it does not output the
.set pseudo-ops. */
|| history[1].noreorder_p
/* 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. */
|| si->label_list != NULL
/* If the previous instruction is in a variant frag
other than this branch's one, we cannot do the swap.
This does not apply to the mips16, which uses variant
frags for different purposes. */
|| (! mips_opts.mips16
&& prev_insn_frag_type == rs_machine_dependent)
/* Check for conflicts between the branch and the instructions
before the candidate delay slot. */
|| nops_for_insn (0, history + 1, ip) > 0
/* Check for conflicts between the swapped sequence and the
target of the branch. */
|| nops_for_sequence (2, 0, history + 1, ip, history) > 0
/* We do not swap with a trap instruction, since it
complicates trap handlers to have the trap
instruction be in a delay slot. */
|| (prev_pinfo & INSN_TRAP)
/* If the branch reads a register that the previous
instruction sets, we can not swap. */
|| (gpr_read_mask (ip) & gpr_write_mask (&history[0])) != 0
/* If the branch writes a register that the previous
instruction sets, we can not swap. */
|| (gpr_write_mask (ip) & gpr_write_mask (&history[0])) != 0
/* If the branch writes a register that the previous
instruction reads, we can not swap. */
|| (gpr_write_mask (ip) & gpr_read_mask (&history[0])) != 0
/* If one instruction sets a condition code and the
other one uses a condition code, we can not swap. */
|| ((pinfo & INSN_READ_COND_CODE)
&& (prev_pinfo & INSN_WRITE_COND_CODE))
|| ((pinfo & INSN_WRITE_COND_CODE)
&& (prev_pinfo & INSN_READ_COND_CODE))
/* If the previous instruction uses the PC, we can not
swap. */
|| (mips_opts.mips16
&& (prev_pinfo & MIPS16_INSN_READ_PC))
/* If the previous instruction had a fixup in mips16
mode, we can not swap. This normally means that the
previous instruction was a 4 byte branch anyhow. */
|| (mips_opts.mips16 && history[0].fixp[0])
/* If the previous instruction is a sync, sync.l, or
sync.p, we can not swap. */
|| (prev_pinfo & INSN_SYNC)
/* If the previous instruction is an ERET or
DERET, avoid the swap. */
|| (history[0].insn_opcode == INSN_ERET)
|| (history[0].insn_opcode == INSN_DERET))
{
if (mips_opts.mips16
&& (pinfo & INSN_UNCOND_BRANCH_DELAY)
&& (pinfo & (MIPS16_INSN_READ_X | MIPS16_INSN_READ_31))
&& ISA_SUPPORTS_MIPS16E)
{ {
case APPEND_ADD:
insert_into_history (0, 1, ip);
break;
case APPEND_ADD_WITH_NOP:
insert_into_history (0, 1, ip);
emit_nop ();
if (mips_relax.sequence)
mips_relax.sizes[mips_relax.sequence - 1] += 4;
break;
case APPEND_ADD_COMPACT:
/* Convert MIPS16 jr/jalr into a "compact" jump. */ /* Convert MIPS16 jr/jalr into a "compact" jump. */
gas_assert (mips_opts.mips16);
ip->insn_opcode |= 0x0080; ip->insn_opcode |= 0x0080;
find_altered_mips16_opcode (ip); find_altered_mips16_opcode (ip);
install_insn (ip); install_insn (ip);
insert_into_history (0, 1, ip); insert_into_history (0, 1, ip);
} break;
else
{
/* 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. */
insert_into_history (0, 1, ip);
emit_nop ();
}
if (mips_relax.sequence) case APPEND_SWAP:
mips_relax.sizes[mips_relax.sequence - 1] += 4;
}
else
{ {
/* It looks like we can actually do the swap. */
struct mips_cl_insn delay = history[0]; struct mips_cl_insn delay = history[0];
if (mips_opts.mips16) if (mips_opts.mips16)
{ {
@ -3606,21 +3694,8 @@ append_insn (struct mips_cl_insn *ip, expressionS *address_expr,
delay.fixed_p = 1; delay.fixed_p = 1;
insert_into_history (0, 1, &delay); insert_into_history (0, 1, &delay);
} }
break;
} }
else if (pinfo & INSN_COND_BRANCH_LIKELY)
{
/* We don't yet optimize a branch likely. What we should do
is look at the target, copy the instruction found there
into the delay slot, and increment the branch to jump to
the next instruction. */
insert_into_history (0, 1, ip);
emit_nop ();
}
else
insert_into_history (0, 1, ip);
}
else
insert_into_history (0, 1, ip);
/* If we have just completed an unconditional branch, clear the history. */ /* If we have just completed an unconditional branch, clear the history. */
if ((history[1].insn_mo->pinfo & INSN_UNCOND_BRANCH_DELAY) if ((history[1].insn_mo->pinfo & INSN_UNCOND_BRANCH_DELAY)