mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-01-30 12:44:10 +08:00
gas/
* 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:
parent
0d1b033387
commit
a4e064680b
@ -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>
|
||||
|
||||
* config/tc-mips.c (append_insn): Remove bogus goto.
|
||||
|
@ -121,6 +121,21 @@ extern int target_big_endian;
|
||||
? ".rodata" \
|
||||
: (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
|
||||
and fixups. */
|
||||
struct mips_cl_insn
|
||||
@ -3073,6 +3088,168 @@ fix_loongson2f (struct mips_cl_insn * 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.
|
||||
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_pinfo2, pinfo2;
|
||||
relax_stateT prev_insn_frag_type = 0;
|
||||
bfd_boolean relaxed_branch = FALSE;
|
||||
segment_info_type *si = seg_info (now_seg);
|
||||
enum append_method method;
|
||||
|
||||
if (mips_fix_loongson2f)
|
||||
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
|
||||
/* The value passed to dwarf2_emit_insn is the distance between
|
||||
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);
|
||||
#endif
|
||||
|
||||
/* Record the frag type before frag_var. */
|
||||
if (history[0].frag)
|
||||
prev_insn_frag_type = history[0].frag->fr_type;
|
||||
|
||||
if (address_expr
|
||||
&& *reloc_type == BFD_RELOC_16_PCREL_S2
|
||||
&& (pinfo & INSN_UNCOND_BRANCH_DELAY || pinfo & INSN_COND_BRANCH_DELAY
|
||||
@ -3469,158 +3643,59 @@ append_insn (struct mips_cl_insn *ip, expressionS *address_expr,
|
||||
mips_gprmask |= gpr_read_mask (ip) | gpr_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)
|
||||
{
|
||||
/* Convert MIPS16 jr/jalr into a "compact" jump. */
|
||||
ip->insn_opcode |= 0x0080;
|
||||
find_altered_mips16_opcode (ip);
|
||||
install_insn (ip);
|
||||
insert_into_history (0, 1, ip);
|
||||
}
|
||||
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)
|
||||
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];
|
||||
if (mips_opts.mips16)
|
||||
{
|
||||
know (delay.frag == ip->frag);
|
||||
move_insn (ip, delay.frag, delay.where);
|
||||
move_insn (&delay, ip->frag, ip->where + insn_length (ip));
|
||||
}
|
||||
else if (relaxed_branch)
|
||||
{
|
||||
/* Add the delay slot instruction to the end of the
|
||||
current frag and shrink the fixed part of the
|
||||
original frag. If the branch occupies the tail of
|
||||
the latter, move it backwards to cover the gap. */
|
||||
delay.frag->fr_fix -= 4;
|
||||
if (delay.frag == ip->frag)
|
||||
move_insn (ip, ip->frag, ip->where - 4);
|
||||
add_fixed_insn (&delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
move_insn (&delay, ip->frag, ip->where);
|
||||
move_insn (ip, history[0].frag, history[0].where);
|
||||
}
|
||||
history[0] = *ip;
|
||||
delay.fixed_p = 1;
|
||||
insert_into_history (0, 1, &delay);
|
||||
}
|
||||
}
|
||||
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);
|
||||
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. */
|
||||
gas_assert (mips_opts.mips16);
|
||||
ip->insn_opcode |= 0x0080;
|
||||
find_altered_mips16_opcode (ip);
|
||||
install_insn (ip);
|
||||
insert_into_history (0, 1, ip);
|
||||
break;
|
||||
|
||||
case APPEND_SWAP:
|
||||
{
|
||||
struct mips_cl_insn delay = history[0];
|
||||
if (mips_opts.mips16)
|
||||
{
|
||||
know (delay.frag == ip->frag);
|
||||
move_insn (ip, delay.frag, delay.where);
|
||||
move_insn (&delay, ip->frag, ip->where + insn_length (ip));
|
||||
}
|
||||
else if (relaxed_branch)
|
||||
{
|
||||
/* Add the delay slot instruction to the end of the
|
||||
current frag and shrink the fixed part of the
|
||||
original frag. If the branch occupies the tail of
|
||||
the latter, move it backwards to cover the gap. */
|
||||
delay.frag->fr_fix -= 4;
|
||||
if (delay.frag == ip->frag)
|
||||
move_insn (ip, ip->frag, ip->where - 4);
|
||||
add_fixed_insn (&delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
move_insn (&delay, ip->frag, ip->where);
|
||||
move_insn (ip, history[0].frag, history[0].where);
|
||||
}
|
||||
history[0] = *ip;
|
||||
delay.fixed_p = 1;
|
||||
insert_into_history (0, 1, &delay);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
insert_into_history (0, 1, ip);
|
||||
|
||||
/* If we have just completed an unconditional branch, clear the history. */
|
||||
if ((history[1].insn_mo->pinfo & INSN_UNCOND_BRANCH_DELAY)
|
||||
|
Loading…
Reference in New Issue
Block a user