gas/
authorRichard Sandiford <rdsandiford@googlemail.com>
Wed, 29 Jun 2011 21:05:29 +0000 (21:05 +0000)
committerRichard Sandiford <rdsandiford@googlemail.com>
Wed, 29 Jun 2011 21:05:29 +0000 (21:05 +0000)
* 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.

gas/ChangeLog
gas/config/tc-mips.c

index 0f3dd27..4549318 100644 (file)
@@ -1,5 +1,12 @@
 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.
 
 2011-06-29  Richard Sandiford  <rdsandiford@googlemail.com>
index b77c3e9..cdcebf1 100644 (file)
@@ -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)