Add support to the Xtensa target for creating trampolines for out-of-range branches.
authorDavid Weatherford <weath@cadence.com>
Fri, 21 Mar 2014 11:53:42 +0000 (11:53 +0000)
committerNick Clifton <nickc@redhat.com>
Fri, 21 Mar 2014 11:53:42 +0000 (11:53 +0000)
    * tc-xtensa.c (xtensa_check_frag_count, xtensa_create_trampoline_frag)
    (xtensa_maybe_create_trampoline_frag, init_trampoline_frag)
    (find_trampoline_seg, search_trampolines, get_best_trampoline)
    (check_and_update_trampolines, add_jump_to_trampoline)
    (dump_trampolines): New function.
    (md_parse_option): Add cases for --[no-]trampolines options.
    (md_assemble, finish_vinsn, xtensa_end): Add call to
    xtensa_check_frag_count.
    (xg_assemble_vliw_tokens): Add call to
    xtensa_maybe_create_trampoline_frag.
    (xtensa_relax_frag): Relax fragments with RELAX_TRAMPOLINE state.
    (relax_frag_immed): Relax jump instructions that cannot reach its
    target.
    * tc-xtensa.h (xtensa_relax_statesE::RELAX_TRAMPOLINE): New relax
    state.

    * as.texinfo: Document --[no-]trampolines command-line options.
    * c-xtensa.texi: Document trampolines relaxation and command line
    options.

    * frags.c (get_frag_count, clear_frag_count): New function.
    (frag_alloc): Increment totalfrags counter.
    * frags.h (get_frag_count, clear_frag_count): New function.

    * all.exp: Add test for trampoline relaxation.
    * trampoline.d: Trampoline relaxation expected dump.
    * trampoline.s: Trampoline relaxation test source.

gas/ChangeLog
gas/config/tc-xtensa.c
gas/config/tc-xtensa.h
gas/doc/as.texinfo
gas/doc/c-xtensa.texi
gas/frags.c
gas/frags.h
gas/testsuite/ChangeLog
gas/testsuite/gas/xtensa/all.exp
gas/testsuite/gas/xtensa/trampoline.d [new file with mode: 0644]
gas/testsuite/gas/xtensa/trampoline.s [new file with mode: 0644]

index 52fb7242a29d8705a78db52476c8a03c049d26d5..ef4b6869b3fb52c76b23038615b206bc6c818731 100644 (file)
@@ -1,3 +1,29 @@
+2014-03-21  David Weatherford <weath@cadence.com>
+            Max Filippov <jcmvbkbc@gmail.com>
+
+       * config/tc-xtensa.c (xtensa_check_frag_count)
+       xtensa_create_trampoline_frag,
+       xtensa_maybe_create_trampoline_frag, init_trampoline_frag,
+       find_trampoline_seg, search_trampolines, get_best_trampoline,
+       check_and_update_trampolines, add_jump_to_trampoline,
+       dump_trampolines): New functions.
+       (md_parse_option): Add cases for --[no-]trampolines options.
+       (md_assemble, finish_vinsn, xtensa_end): Add call to
+       xtensa_check_frag_count.
+       (xg_assemble_vliw_tokens): Add call to
+       xtensa_maybe_create_trampoline_frag.
+       (xtensa_relax_frag): Relax fragments with RELAX_TRAMPOLINE state.
+       (relax_frag_immed): Relax jump instructions that cannot reach its
+       target.
+       * config/tc-xtensa.h (xtensa_relax_statesE::RELAX_TRAMPOLINE): New
+       relax state.
+       * doc/as.texinfo: Document --[no-]trampolines command-line options.
+       * doc/c-xtensa.texi: Document trampolines relaxation and command
+       line options.
+       * frags.c (get_frag_count, clear_frag_count): New function.
+       (frag_alloc): Increment totalfrags counter.
+       * frags.h (get_frag_count, clear_frag_count): New function.
+
 2014-03-20  DJ Delorie  <dj@redhat.com>
 
        * config/rl78-defs.h (RL78_RELAX_NONE, RL78_RELAX_BRANCH): Add.
index fe8ec0fe3800908ea91bf6d18300c5a7122ce2de..ea23c9653230167ee966e8d216baf2928408e8df 100644 (file)
@@ -468,6 +468,12 @@ static void xtensa_set_frag_assembly_state (fragS *);
 static void finish_vinsn (vliw_insn *);
 static bfd_boolean emit_single_op (TInsn *);
 static int total_frag_text_expansion (fragS *);
+static bfd_boolean use_trampolines = TRUE;
+static void xtensa_check_frag_count (void);
+static void xtensa_create_trampoline_frag (bfd_boolean);
+static void xtensa_maybe_create_trampoline_frag (void);
+struct trampoline_frag;
+static int init_trampoline_frag (struct trampoline_frag *);
 
 /* Alignment Functions.  */
 
@@ -520,6 +526,7 @@ static void tinsn_from_chars (TInsn *, char *, int);
 static void tinsn_immed_from_frag (TInsn *, fragS *, int);
 static int get_num_stack_text_bytes (IStack *);
 static int get_num_stack_literal_bytes (IStack *);
+static bfd_boolean tinsn_to_slotbuf (xtensa_format, int, TInsn *, xtensa_insnbuf);
 
 /* vliw_insn functions.  */
 
@@ -687,7 +694,10 @@ enum
   option_prefer_l32r,
   option_prefer_const16,
 
-  option_target_hardware
+  option_target_hardware,
+
+  option_trampolines,
+  option_no_trampolines,
 };
 
 const char *md_shortopts = "";
@@ -760,6 +770,9 @@ struct option md_longopts[] =
 
   { "target-hardware", required_argument, NULL, option_target_hardware },
 
+  { "trampolines", no_argument, NULL, option_trampolines },
+  { "no-trampolines", no_argument, NULL, option_no_trampolines },
+
   { NULL, no_argument, NULL, 0 }
 };
 
@@ -940,6 +953,14 @@ md_parse_option (int c, char *arg)
       directive_state[directive_transform] = FALSE;
       return 1;
 
+    case option_trampolines:
+      use_trampolines = TRUE;
+      return 1;
+
+    case option_no_trampolines:
+      use_trampolines = FALSE;
+      return 1;
+
     default:
       return 0;
     }
@@ -963,7 +984,9 @@ Xtensa options:\n\
                           flix bundles\n\
   --no-allow-flix         neither allow hand-written nor generate\n\
                           flix bundles\n\
-  --rename-section old=new Rename section 'old' to 'new'\n", stream);
+  --rename-section old=new Rename section 'old' to 'new'\n\
+  --[no-]trampolines      [Do not] generate trampolines (jumps to jumps)\n\
+                          when jumps do not reach their targets\n", stream);
 }
 
 \f
@@ -5568,6 +5591,8 @@ md_assemble (char *str)
 
   /* We've just emitted a new instruction so clear the list of labels.  */
   xtensa_clear_insn_labels ();
+
+  xtensa_check_frag_count ();
 }
 
 
@@ -6372,6 +6397,8 @@ finish_vinsn (vliw_insn *vinsn)
   xg_assemble_vliw_tokens (vinsn);
 
   xg_clear_vinsn (vinsn);
+
+  xtensa_check_frag_count ();
 }
 
 
@@ -7140,6 +7167,7 @@ xg_assemble_vliw_tokens (vliw_insn *vinsn)
                    RELAX_UNREACHABLE,
                    frag_now->fr_symbol, frag_now->fr_offset, NULL);
          xtensa_set_frag_assembly_state (frag_now);
+         xtensa_maybe_create_trampoline_frag ();
        }
       else if (is_branch && do_align_targets ())
        {
@@ -7222,9 +7250,164 @@ xtensa_end (void)
   xtensa_sanity_check ();
 
   xtensa_add_config_info ();
+
+  xtensa_check_frag_count ();
+}
+
+
+struct trampoline_frag
+{
+  struct trampoline_frag *next;
+  bfd_boolean needs_jump_around;
+  fragS *fragP;
+  fixS *fixP;
+};
+
+struct trampoline_seg
+{
+  struct trampoline_seg *next;
+  asection *seg;
+  struct trampoline_frag trampoline_list;
+};
+
+static struct trampoline_seg trampoline_seg_list;
+#define J_RANGE (128 * 1024)
+
+static int unreachable_count = 0;
+
+
+static void
+xtensa_maybe_create_trampoline_frag (void)
+{
+  if (!use_trampolines)
+    return;
+
+  /* We create an area for possible trampolines every 10 unreachable frags.
+     These are preferred over the ones not preceded by an unreachable frag,
+     because we don't have to jump around them. This function is called after
+     each RELAX_UNREACHABLE frag is created.  */
+
+  if (++unreachable_count > 10)
+    {
+      xtensa_create_trampoline_frag (FALSE);
+      clear_frag_count ();
+      unreachable_count = 0;
+    }
+}
+
+static void
+xtensa_check_frag_count (void)
+{
+  if (!use_trampolines || frag_now->tc_frag_data.is_no_transform)
+    return;
+
+  /* We create an area for possible trampolines every 8000 frags or so. This
+     is an estimate based on the max range of a "j" insn (+/-128K) divided
+     by a typical frag byte count (16), minus a few for safety. This function
+     is called after each source line is processed.  */
+
+  if (get_frag_count () > 8000)
+    {
+      xtensa_create_trampoline_frag (TRUE);
+      clear_frag_count ();
+      unreachable_count = 0;
+    }
+}
+
+static xtensa_insnbuf trampoline_buf = NULL;
+static xtensa_insnbuf trampoline_slotbuf = NULL;
+
+#define TRAMPOLINE_FRAG_SIZE 3000
+
+static void
+xtensa_create_trampoline_frag (bfd_boolean needs_jump_around)
+{
+  /* Emit a frag where we can place intermediate jump instructions,
+     in case we need to jump farther than 128K bytes.
+     Each jump instruction takes three bytes.
+     We allocate enough for 1000 trampolines in each frag.
+     If that's not enough, oh well.  */
+
+  struct trampoline_seg *ts = trampoline_seg_list.next;
+  struct trampoline_frag *tf;
+  char *varP;
+  fragS *fragP;
+  int size = TRAMPOLINE_FRAG_SIZE;
+
+  for ( ; ts; ts = ts->next)
+    {
+      if (ts->seg == now_seg)
+       break;
+    }
+
+  if (ts == NULL)
+    {
+      ts = (struct trampoline_seg *)xcalloc(sizeof (struct trampoline_seg), 1);
+      ts->next = trampoline_seg_list.next;
+      trampoline_seg_list.next = ts;
+      ts->seg = now_seg;
+    }
+
+  frag_wane (frag_now);
+  frag_new (0);
+  xtensa_set_frag_assembly_state (frag_now);
+  varP = frag_var (rs_machine_dependent, size, size, RELAX_TRAMPOLINE, NULL, 0, NULL);
+  fragP = (fragS *)(varP - SIZEOF_STRUCT_FRAG);
+  if (trampoline_buf == NULL)
+    {
+      trampoline_buf = xtensa_insnbuf_alloc (xtensa_default_isa);
+      trampoline_slotbuf = xtensa_insnbuf_alloc (xtensa_default_isa);
+    }
+  tf = (struct trampoline_frag *)xmalloc(sizeof (struct trampoline_frag));
+  tf->next = ts->trampoline_list.next;
+  ts->trampoline_list.next = tf;
+  tf->needs_jump_around = needs_jump_around;
+  tf->fragP = fragP;
+  tf->fixP = NULL;
+}
+
+
+static struct trampoline_seg *
+find_trampoline_seg (asection *seg)
+{
+  struct trampoline_seg *ts = trampoline_seg_list.next;
+
+  for ( ; ts; ts = ts->next)
+    {
+      if (ts->seg == seg)
+       return ts;
+    }
+
+  return NULL;
 }
 
 
+void dump_trampolines (void);
+
+void
+dump_trampolines (void)
+{
+  struct trampoline_seg *ts = trampoline_seg_list.next;
+
+  for ( ; ts; ts = ts->next)
+    {
+      asection *seg = ts->seg;
+
+      if (seg == NULL)
+       continue;
+      fprintf(stderr, "SECTION %s\n", seg->name);
+      struct trampoline_frag *tf = ts->trampoline_list.next;
+      for ( ; tf; tf = tf->next)
+       {
+         if (tf->fragP == NULL)
+           continue;
+         fprintf(stderr, "   0x%08x: fix=%d, jump_around=%s\n",
+                 (int)tf->fragP->fr_address, (int)tf->fragP->fr_fix,
+                 tf->needs_jump_around ? "T" : "F");
+       }
+    }
+}
+
 static void
 xtensa_cleanup_align_frags (void)
 {
@@ -8708,6 +8891,149 @@ xtensa_relax_frag (fragS *fragP, long stretch, int *stretched_p)
        new_stretch += relax_frag_for_align (fragP, stretch);
       break;
 
+    case RELAX_TRAMPOLINE:
+      if (fragP->tc_frag_data.relax_seen)
+        {
+          segment_info_type *seginfo = seg_info (now_seg);
+          fragS *fP; /* The out-of-range jump.  */
+          fixS *fixP;
+
+          /* Scan for jumps that will not reach.  */
+          for (fixP = seginfo->fix_root; fixP ; fixP = fixP->fx_next)
+            {
+              symbolS *s = fixP->fx_addsy;
+             xtensa_opcode opcode;
+              int target;
+              int addr;
+              int delta;
+
+              if (fixP->fx_r_type < BFD_RELOC_XTENSA_SLOT0_OP ||
+                  fixP->fx_r_type > BFD_RELOC_XTENSA_SLOT14_OP)
+                continue;
+             xtensa_insnbuf_from_chars (isa, trampoline_buf,
+                                        (unsigned char *) fixP->fx_frag->fr_literal + fixP->fx_where,
+                                        0);
+             fmt = xtensa_format_decode (isa, trampoline_buf);
+             gas_assert (fmt != XTENSA_UNDEFINED);
+             slot = fixP->tc_fix_data.slot;
+             xtensa_format_get_slot (isa, fmt, slot, trampoline_buf, trampoline_slotbuf);
+             opcode = xtensa_opcode_decode (isa, fmt, slot, trampoline_slotbuf);
+             if (opcode != xtensa_j_opcode)
+               continue;
+              target = S_GET_VALUE (s);
+              addr = fixP->fx_frag->fr_address;
+              delta = target - addr + stretch;
+              if (delta > J_RANGE  || delta < -1 * J_RANGE)
+                { /* Found an out-of-range jump; scan the list of trampolines for the best match.  */
+                 struct trampoline_seg *ts = find_trampoline_seg (now_seg);
+                 struct trampoline_frag *tf = ts->trampoline_list.next;
+                 struct trampoline_frag *prev = &ts->trampoline_list;
+                 int lower = (target < addr) ? target : addr;
+                 int upper = (target > addr) ? target : addr;
+                 int midpoint = lower + (upper - lower) / 2;
+
+                 if ((upper - lower) > 2 * J_RANGE)
+                   {
+                     /* One trampoline won't suffice; we need multiple jumps.
+                        Jump to the trampoline that's farthest, but still in
+                        range relative to the original "j" instruction.  */
+                     for ( ; tf; prev = tf, tf = tf->next )
+                       {
+                         int this_addr = tf->fragP->fr_address + tf->fragP->fr_fix;
+                         int next_addr = (tf->next) ? tf->next->fragP->fr_address + tf->next->fragP->fr_fix : 0 ;
+
+                         if (addr == lower)
+                           {
+                             /* Forward jump.  */
+                             if (this_addr - addr < J_RANGE)
+                               break;
+                           }
+                         else
+                           {
+                             /* Backward jump.  */
+                             if (next_addr == 0 || addr - next_addr > J_RANGE)
+                               break;
+                           }
+                       }
+                   }
+                 else
+                   {
+                     struct trampoline_frag *best_tf = NULL;
+                     int best_delta = 0;
+
+                     for ( ; tf; prev = tf, tf = tf->next )
+                       {
+                         int this_addr = tf->fragP->fr_address + tf->fragP->fr_fix;
+                         int this_delta = abs (this_addr - midpoint);
+
+                         if (!best_tf || this_delta < best_delta)
+                           {
+                              best_tf = tf;
+                              best_delta = this_delta;
+                           }
+                       }
+                     tf = best_tf;
+                   }
+                 if (tf->fragP == fragP)
+                   {
+                     int trampaddr = fragP->fr_address + fragP->fr_fix;
+
+                     if (abs (addr - trampaddr) < J_RANGE)
+                       { /* The trampoline is in range of original; fix it!  */
+                         fixS *newfixP;
+                         int offset;
+                         TInsn insn;
+                         symbolS *lsym;
+
+                         new_stretch += init_trampoline_frag (tf);
+                         offset = fragP->fr_fix; /* Where to assemble the j insn.  */
+                         lsym = fragP->fr_symbol;
+                         fP = fixP->fx_frag;
+                         /* Assemble a jump to the target label here.  */
+                         tinsn_init (&insn);
+                         insn.insn_type = ITYPE_INSN;
+                         insn.opcode = xtensa_j_opcode;
+                         insn.ntok = 1;
+                         set_expr_symbol_offset (&insn.tok[0], lsym, offset);
+                         fmt = xg_get_single_format (xtensa_j_opcode);
+                         tinsn_to_slotbuf (fmt, 0, &insn, trampoline_slotbuf);
+                         xtensa_format_set_slot (isa, fmt, 0, trampoline_buf, trampoline_slotbuf);
+                         xtensa_insnbuf_to_chars (isa, trampoline_buf, (unsigned char *)fragP->fr_literal + offset, 3);
+                         fragP->fr_fix += 3;
+                         fragP->fr_var -= 3;
+                         /* Add a fix-up for the original j insn.  */
+                         newfixP = fix_new (fP, fixP->fx_where, fixP->fx_size, lsym, fragP->fr_fix - 3, TRUE, fixP->fx_r_type);
+                         newfixP->fx_no_overflow = 1;
+                         newfixP->tc_fix_data.X_add_symbol = lsym;
+                         newfixP->tc_fix_data.X_add_number = offset;
+                         newfixP->tc_fix_data.slot = slot;
+                         /* Move the fix-up from the original j insn to this one.  */
+                         fixP->fx_frag = fragP;
+                         fixP->fx_where = fragP->fr_fix - 3;
+                         fixP->tc_fix_data.slot = 0;
+                         /* Adjust the jump around this trampoline (if present).  */
+                         if (tf->fixP != NULL)
+                           {
+                             tf->fixP->fx_offset += 3;
+                           }
+                         new_stretch += 3;
+                         fragP->tc_frag_data.relax_seen = FALSE; /* Need another pass.  */
+                         /* Do we have room for more?  */
+                         if (fragP->fr_var < 3)
+                           { /* No, convert to fill.  */
+                             frag_wane (fragP);
+                             fragP->fr_subtype = 0;
+                             /* Remove from the trampoline_list.  */
+                             prev->next = tf->next;
+                             break;
+                           }
+                       }
+                   }
+                }
+            }
+        }
+      break;
+
     default:
       as_bad (_("bad relaxation state"));
     }
@@ -9146,6 +9472,200 @@ bytes_to_stretch (fragS *this_frag,
 }
 
 
+static struct trampoline_frag *
+search_trampolines (TInsn *tinsn, fragS *fragP, bfd_boolean unreachable_only)
+{
+  struct trampoline_seg *ts = find_trampoline_seg (now_seg);
+  struct trampoline_frag *tf = (ts) ? ts->trampoline_list.next : NULL;
+  struct trampoline_frag *best_tf = NULL;
+  int best_delta = 0;
+  int best_addr = 0;
+  symbolS *sym = tinsn->tok[0].X_add_symbol;
+  offsetT target = S_GET_VALUE (sym) + tinsn->tok[0].X_add_number;
+  offsetT addr = fragP->fr_address;
+  offsetT lower = (addr < target) ? addr : target;
+  offsetT upper = (addr > target) ? addr : target;
+  int delta = upper - lower;
+  offsetT midpoint = lower + delta / 2;
+  int this_delta = -1;
+  int this_addr = -1;
+
+  if (delta > 2 * J_RANGE)
+    {
+      /* One trampoline won't do; we need multiple.
+        Choose the farthest trampoline that's still in range of the original
+        and let a later pass finish the job.  */
+      for ( ; tf; tf = tf->next)
+       {
+         int next_addr = (tf->next) ? tf->next->fragP->fr_address + tf->next->fragP->fr_fix : 0;
+
+         this_addr = tf->fragP->fr_address + tf->fragP->fr_fix;
+         if (lower == addr)
+           {
+             /* Forward jump.  */
+             if (this_addr - addr < J_RANGE)
+               break;
+           }
+         else
+           {
+             /* Backward jump.  */
+             if (next_addr == 0 || addr - next_addr > J_RANGE)
+               break;
+           }
+         if (abs (addr - this_addr) < J_RANGE)
+           return tf;
+
+         return NULL;
+       }
+    }
+  for ( ; tf; tf = tf->next)
+    {
+      this_addr = tf->fragP->fr_address + tf->fragP->fr_fix;
+      this_delta = abs (this_addr - midpoint);
+      if (unreachable_only && tf->needs_jump_around)
+       continue;
+      if (!best_tf || this_delta < best_delta)
+        {
+         best_tf = tf;
+         best_delta = this_delta;
+         best_addr = this_addr;
+        }
+    }
+
+  if (best_tf &&
+      best_delta < J_RANGE &&
+      abs(best_addr - lower) < J_RANGE &&
+      abs(best_addr - upper) < J_RANGE)
+    return best_tf;
+
+  return NULL; /* No suitable trampoline found.  */
+}
+
+
+static struct trampoline_frag *
+get_best_trampoline (TInsn *tinsn, fragS *fragP)
+{
+  struct trampoline_frag *tf = NULL;
+
+  tf = search_trampolines (tinsn, fragP, TRUE); /* Try unreachable first.  */
+
+  if (tf == NULL)
+    tf = search_trampolines (tinsn, fragP, FALSE); /* Try ones needing a jump-around, too.  */
+
+  return tf;
+}
+
+
+static void
+check_and_update_trampolines (void)
+{
+  struct trampoline_seg *ts = find_trampoline_seg (now_seg);
+  struct trampoline_frag *tf = ts->trampoline_list.next;
+  struct trampoline_frag *prev = &ts->trampoline_list;
+
+  for ( ; tf; prev = tf, tf = tf->next)
+    {
+      if (tf->fragP->fr_var < 3)
+       {
+         frag_wane (tf->fragP);
+         prev->next = tf->next;
+         tf->fragP = NULL;
+       }
+    }
+}
+
+
+static int
+init_trampoline_frag (struct trampoline_frag *trampP)
+{
+  fragS *fp = trampP->fragP;
+  int growth = 0;
+
+  if (fp->fr_fix == 0)
+    {
+      symbolS *lsym;
+      char label[10 + 2 * sizeof(fp)];
+      sprintf (label, ".L0_TR_%p", fp);
+
+      lsym = (symbolS *)local_symbol_make (label, now_seg, 0, fp);
+      fp->fr_symbol = lsym;
+      if (trampP->needs_jump_around)
+        {
+         /* Add a jump around this block of jumps, in case
+            control flows into this block.  */
+         fixS *fixP;
+         TInsn insn;
+         xtensa_format fmt;
+         xtensa_isa isa = xtensa_default_isa;
+
+         fp->tc_frag_data.is_insn = 1;
+         /* Assemble a jump insn.  */
+         tinsn_init (&insn);
+         insn.insn_type = ITYPE_INSN;
+         insn.opcode = xtensa_j_opcode;
+         insn.ntok = 1;
+         set_expr_symbol_offset (&insn.tok[0], lsym, 3);
+         fmt = xg_get_single_format (xtensa_j_opcode);
+         tinsn_to_slotbuf (fmt, 0, &insn, trampoline_slotbuf);
+         xtensa_format_set_slot (isa, fmt, 0, trampoline_buf, trampoline_slotbuf);
+         xtensa_insnbuf_to_chars (isa, trampoline_buf, (unsigned char *)fp->fr_literal, 3);
+         fp->fr_fix += 3;
+         fp->fr_var -= 3;
+         growth = 3;
+         fixP = fix_new (fp, 0, 3, lsym, 3, TRUE, BFD_RELOC_XTENSA_SLOT0_OP);
+         trampP->fixP = fixP;
+        }
+    }
+  return growth;
+}
+
+
+static int
+add_jump_to_trampoline (struct trampoline_frag *trampP, fragS *origfrag)
+{
+  fragS *tramp = trampP->fragP;
+  fixS *fixP;
+  int offset = tramp->fr_fix; /* Where to assemble the j insn.  */
+  TInsn insn;
+  symbolS *lsym;
+  symbolS *tsym;
+  int toffset;
+  xtensa_format fmt;
+  xtensa_isa isa = xtensa_default_isa;
+  int growth = 0;
+
+  lsym = tramp->fr_symbol;
+  /* Assemble a jump to the target label in the trampoline frag.  */
+  tsym = origfrag->tc_frag_data.slot_symbols[0];
+  toffset = origfrag-> tc_frag_data.slot_offsets[0];
+  tinsn_init (&insn);
+  insn.insn_type = ITYPE_INSN;
+  insn.opcode = xtensa_j_opcode;
+  insn.ntok = 1;
+  set_expr_symbol_offset (&insn.tok[0], tsym, toffset);
+  fmt = xg_get_single_format (xtensa_j_opcode);
+  tinsn_to_slotbuf (fmt, 0, &insn, trampoline_slotbuf);
+  xtensa_format_set_slot (isa, fmt, 0, trampoline_buf, trampoline_slotbuf);
+  xtensa_insnbuf_to_chars (isa, trampoline_buf, (unsigned char *)tramp->fr_literal + offset, 3);
+  tramp->fr_fix += 3;
+  tramp->fr_var -= 3;
+  growth = 3;
+  /* add a fix-up for the trampoline jump.  */
+  fixP = fix_new (tramp, tramp->fr_fix - 3, 3, tsym, toffset, TRUE, BFD_RELOC_XTENSA_SLOT0_OP);
+  /* Modify the jump at the start of this trampoline to point past the newly-added jump.  */
+  fixP = trampP->fixP;
+  if (fixP)
+    fixP->fx_offset += 3;
+  /* Modify the original j to point here.  */
+  origfrag->tc_frag_data.slot_symbols[0] = lsym;
+  origfrag->tc_frag_data.slot_offsets[0] = tramp->fr_fix - 3;
+  /* If trampoline is full, remove it from the list.  */
+  check_and_update_trampolines ();
+
+  return growth;
+}
+
+
 static long
 relax_frag_immed (segT segP,
                  fragS *fragP,
@@ -9284,6 +9804,37 @@ relax_frag_immed (segT segP,
   if (negatable_branch && istack.ninsn > 1)
     update_next_frag_state (fragP);
 
+  /* If last insn is a jump, and it cannot reach its target, try to find a trampoline.  */
+  if (istack.ninsn > 2 &&
+      istack.insn[istack.ninsn - 1].insn_type == ITYPE_LABEL &&
+      istack.insn[istack.ninsn - 2].insn_type == ITYPE_INSN &&
+      istack.insn[istack.ninsn - 2].opcode == xtensa_j_opcode)
+    {
+      TInsn *jinsn = &istack.insn[istack.ninsn - 2];
+
+      if (!xg_symbolic_immeds_fit (jinsn, segP, fragP, fragP->fr_offset, total_text_diff))
+       {
+         struct trampoline_frag *tf = get_best_trampoline (jinsn, fragP);
+
+         if (tf)
+           {
+             this_text_diff += init_trampoline_frag (tf);
+             this_text_diff += add_jump_to_trampoline (tf, fragP);
+           }
+         else
+           {
+             /* If target symbol is undefined, assume it will reach once linked.  */
+             expressionS *exp = &istack.insn[istack.ninsn - 2].tok[0];
+
+             if (exp->X_op == O_symbol && S_IS_DEFINED (exp->X_add_symbol))
+               {
+                 as_bad_where (fragP->fr_file, fragP->fr_line,
+                   _("jump target out of range; no usable trampoline found"));
+               }
+           }
+       }
+    }
+
   return this_text_diff;
 }
 
@@ -9404,6 +9955,9 @@ md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT sec, fragS *fragp)
       else
        as_bad (_("invalid relaxation fragment result"));
       break;
+
+    case RELAX_TRAMPOLINE:
+      break;
     }
 
   fragp->fr_var = 0;
index 0bf1240e464dc3bde561cd3ac33d98b74687bb07..4672bc60c42e3bb23d0bc0526c49cda33d665c12 100644 (file)
@@ -180,6 +180,11 @@ enum xtensa_relax_statesE
      prevent the linker from changing the size of any frag between the
      section start and the org frag.  */
 
+  RELAX_TRAMPOLINE,
+  /* Every few thousand frags, we insert one of these, just in case we may
+     need some space for a trampoline (jump to a jump) because the function
+     has gotten too big. If not needed, it disappears. */
+
   RELAX_NONE
 };
 
index 5c8b000f60ff6c53ac28dd52c7e2948a90cb5dfd..dc971754d7d65154081123d3543f083faa77f5c6 100644 (file)
@@ -543,6 +543,7 @@ gcc(1), ld(1), and the Info entries for @file{binutils} and @file{ld}.
  [@b{--[no-]target-align}] [@b{--[no-]longcalls}]
  [@b{--[no-]transform}]
  [@b{--rename-section} @var{oldname}=@var{newname}]
+ [@b{--[no-]trampolines}]
 @end ifset
 
 @ifset Z80
index 406e635f3f80a5f8daa97ed5e92f09c419a69a87..e763e368f30186dbaa07a219583b945ebf5c839f 100644 (file)
@@ -91,6 +91,16 @@ instruction operands to be errors.
 @kindex --rename-section
 Rename the @var{oldname} section to @var{newname}.  This option can be used
 multiple times to rename multiple sections.
+
+@item --trampolines | --no-trampolines
+@kindex --trampolines
+@kindex --no-trampolines
+Enable or disable transformation of jump instructions to allow jumps
+across a greater range of addresses.  @xref{Xtensa Jump Relaxation,
+,Jump Trampolines}.  This option should be used when jump targets can
+potentially be out of range.  In the absence of such jumps this option
+does not affect code size or performance.  The default is
+@samp{--trampolines}.
 @end table
 
 @c man end
@@ -311,6 +321,7 @@ fields.
 @menu
 * Xtensa Branch Relaxation::        Relaxation of Branches.
 * Xtensa Call Relaxation::          Relaxation of Function Calls.
+* Xtensa Jump Relaxation::          Relaxation of Jumps.
 * Xtensa Immediate Relaxation::     Relaxation of other Immediate Fields.
 @end menu
 
@@ -398,6 +409,87 @@ and some of the calls are out of range, function call relaxation can be
 enabled using the @samp{--longcalls} command-line option or the
 @code{longcalls} directive (@pxref{Longcalls Directive, ,longcalls}).
 
+@node Xtensa Jump Relaxation
+@subsection Jump Relaxation
+@cindex relaxation of jump instructions
+@cindex jump instructions, relaxation
+
+Jump instruction may require relaxation because the Xtensa jump instruction
+(@code{J}) provide a PC-relative offset of only 128 Kbytes in either
+direction.  One option is to use jump long (@code{J.L}) instruction, which
+depending on jump distance may be assembled as jump (@code{J}) or indirect
+jump (@code{JX}).  However it needs a free register.  When there's no spare
+register it is possible to plant intermediate jump sites (trampolines)
+between the jump instruction and its target.  These sites may be located in
+areas unreachable by normal code execution flow, in that case they only
+contain intermediate jumps, or they may be inserted in the middle of code
+block, in which case there's an additional jump from the beginning of the
+trampoline to the instruction past its end.  So, for example:
+
+@smallexample
+@group
+    j 1f
+    ...
+    retw
+    ...
+    mov a10, a2
+    call8 func
+    ...
+1:
+    ...
+@end group
+@end smallexample
+
+might be relaxed to:
+
+@smallexample
+@group
+    j .L0_TR_1
+    ...
+    retw
+.L0_TR_1:
+    j 1f
+    ...
+    mov a10, a2
+    call8 func
+    ...
+1:
+    ...
+@end group
+@end smallexample
+
+or to:
+
+@smallexample
+@group
+    j .L0_TR_1
+    ...
+    retw
+    ...
+    mov a10, a2
+    j .L0_TR_0
+.L0_TR_1:
+    j 1f
+.L0_TR_0:
+    call8 func
+    ...
+1:
+    ...
+@end group
+@end smallexample
+
+The Xtensa assempler uses trampolines with jump around only when it cannot
+find suitable unreachable trampoline.  There may be multiple trampolines
+between the jump instruction and its target.
+
+This relaxation does not apply to jumps to undefined symbols, assuming they
+will reach their targets once resolved.
+
+Jump relaxation is enabled by default because it does not affect code size
+or performance while the code itself is small.  This relaxation may be
+disabled completely with @samp{--no-trampolines} or @samp{--no-transform}
+command-line options (@pxref{Xtensa Options, ,Command Line Options}).
+
 @node Xtensa Immediate Relaxation
 @subsection Other Immediate Field Relaxation
 @cindex immediate fields, relaxation
index 5f68480e512c6335ca1e2c14c06e8cc33b6583e2..e14099dda45c49ab2bcb192d224e355acb677b63 100644 (file)
 
 extern fragS zero_address_frag;
 extern fragS predefined_address_frag;
+
+static int totalfrags;
+
+int
+get_frag_count (void)
+{
+  return totalfrags;
+}
+
+void
+clear_frag_count (void)
+{
+  totalfrags = 0;
+}
 \f
 /* Initialization for frag routines.  */
 
@@ -70,6 +84,7 @@ frag_alloc (struct obstack *ob)
   ptr = (fragS *) obstack_alloc (ob, SIZEOF_STRUCT_FRAG);
   obstack_alignment_mask (ob) = oalign;
   memset (ptr, 0, SIZEOF_STRUCT_FRAG);
+  totalfrags++;
   return ptr;
 }
 \f
index 319898fbecc06864cb72456b175fdcd5e06ea413..2f9e1b571d295e954ce71aa69d63bab327cbdb4f 100644 (file)
@@ -155,4 +155,7 @@ char *frag_var (relax_stateT type,
 
 bfd_boolean frag_offset_fixed_p (const fragS *, const fragS *, offsetT *);
 
+int get_frag_count (void);
+void clear_frag_count (void);
+
 #endif /* FRAGS_H */
index a7bec28ccfd53fb9ebe5280c8cf9825b5542dab0..d29e949dd5ce065e4bf6167abf848dffc35a1a15 100644 (file)
@@ -1,3 +1,10 @@
+2014-03-21  David Weatherford <weath@cadence.com>
+            Max Filippov <jcmvbkbc@gmail.com>
+
+       * gas/xtensa/all.exp: Add test for trampoline relaxation.
+       * gas/xtensa/trampoline.d: Trampoline relaxation expected dump.
+       * gas/xtensa/trampoline.s: Trampoline relaxation test source.
+
 2014-03-20  Richard Sandiford  <rdsandiford@googlemail.com>
 
        * gas/all/gas.exp: Remove XFAIL of forward.d for MIPS.
index 2b2c294b8fb930fa3ae2bfce4db9781c3b073c6a..3683b78f4adc2ad60ea48e22c775130365b6b190 100644 (file)
@@ -98,6 +98,7 @@ if [istarget xtensa*-*-*] then {
     run_dump_test "pcrel"
     run_dump_test "weak-call"
     run_dump_test "jlong"
+    run_dump_test "trampoline"
 }
 
 if [info exists errorInfo] then {
diff --git a/gas/testsuite/gas/xtensa/trampoline.d b/gas/testsuite/gas/xtensa/trampoline.d
new file mode 100644 (file)
index 0000000..b4f65dc
--- /dev/null
@@ -0,0 +1,26 @@
+#as:
+#objdump: -d
+#name: trampolines relaxation
+
+.*: +file format .*xtensa.*
+#...
+.*0:.*j.0x1194c
+.*3:.*j.0x1194f
+.*6:.*j.0x11952
+.*9:.*j.0x1d4e4
+#...
+.*11949:.*j.0x11955
+.*1194c:.*j.0x24a0e
+.*1194f:.*j.0x24a0e
+.*11952:.*j.0x24a11
+#...
+.*1d4e1:.*j.0x1d4e7
+.*1d4e4:.*j.0x33462
+#...
+.*24a0e:.*j.0x24a0e
+.*24a11:.*j.0x24a11
+#...
+.*3345f:.*ret
+.*33462:.*j.0x49407
+#...
+.*49407:.*j.0x49407
diff --git a/gas/testsuite/gas/xtensa/trampoline.s b/gas/testsuite/gas/xtensa/trampoline.s
new file mode 100644 (file)
index 0000000..259a3bb
--- /dev/null
@@ -0,0 +1,21 @@
+       .text
+       j       1f
+       j       1f
+       j       2f
+       j       3f
+       .rep    25000
+99:
+       and     a2, a2, a3
+       bne     a2, a3, 99b
+       .endr
+1:
+       j       1b
+2:
+       j       2b
+
+       .rep    25000
+       and     a2, a2, a3
+       _ret
+       .endr
+3:
+       j       3b