[PATCH] Add "--trampolines" option to allow assembler to relax out-of-range jumps

Max Filippov jcmvbkbc@gmail.com
Thu Mar 13 15:12:00 GMT 2014


2014-02-28  David Weatherford <weath@cadence.com>
            Max Filippov <jcmvbkbc@gmail.com>

gas/config/
    * tc-xtensa.c (xtensa_check_frag_count): New function.
    (xtensa_create_trampoline_frag): New function.
    (xtensa_maybe_create_trampoline_frag): New function.
    (init_trampoline_frag): New function.
    (find_tramp_seg): New function.
    (search_trampolines): New function.
    (get_best_trampoline): New function.
    (check_and_update_trampolines): New function.
    (add_jump_to_trampoline): New function.
    (dump_tramps): New function.
    (md_parse_option): Add cases for --[no-]trampolines options.
    (md_assemble): Add call to xtensa_check_frag_count.
    (finish_vinsn): likewise.
    (xtensa_end): likewise.
    (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.

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

gas/
    * frags.c (get_frag_count): New function.
    (clear_frag_count): New function.
    * frags.h (get_frag_count): New function.
    (clear_frag_count): New function.

gas/testsuite/gas/xtensa/
    * all.exp: Add test for trampoline relaxation.
    * trampoline.d: Trampoline relaxation expected dump.
    * trampoline.s: Trampoline relaxation test source.
---
 gas/config/tc-xtensa.c                | 537 +++++++++++++++++++++++++++++++++-
 gas/config/tc-xtensa.h                |   5 +
 gas/doc/as.texinfo                    |   1 +
 gas/doc/c-xtensa.texi                 |  92 ++++++
 gas/frags.c                           |  15 +
 gas/frags.h                           |   3 +
 gas/testsuite/gas/xtensa/all.exp      |   1 +
 gas/testsuite/gas/xtensa/trampoline.d |  26 ++
 gas/testsuite/gas/xtensa/trampoline.s |  21 ++
 9 files changed, 699 insertions(+), 2 deletions(-)
 create mode 100644 gas/testsuite/gas/xtensa/trampoline.d
 create mode 100644 gas/testsuite/gas/xtensa/trampoline.s

diff --git a/gas/config/tc-xtensa.c b/gas/config/tc-xtensa.c
index fe8ec0f..29c48f6 100644
--- a/gas/config/tc-xtensa.c
+++ b/gas/config/tc-xtensa.c
@@ -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 tramp_frag;
+static int init_trampoline_frag (struct tramp_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);
 }
 
 
@@ -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,160 @@ xtensa_end (void)
   xtensa_sanity_check ();
 
   xtensa_add_config_info ();
+
+  xtensa_check_frag_count ();
 }
 
 
+struct tramp_frag {
+  struct tramp_frag *next;
+  bfd_boolean needs_jump_around;
+  fragS *fragP;
+  fixS *fixP;
+};
+
+struct tramp_seg {
+  struct tramp_seg *next;
+  asection *seg;
+  struct tramp_frag tramp_list;
+};
+
+static struct tramp_seg tramp_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 tramp_seg *ts = tramp_seg_list.next;
+  struct tramp_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 tramp_seg *)xcalloc(sizeof (struct tramp_seg), 1);
+      ts->next = tramp_seg_list.next;
+      tramp_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 tramp_frag *)xmalloc(sizeof (struct tramp_frag));
+  tf->next = ts->tramp_list.next;
+  ts->tramp_list.next = tf;
+  tf->needs_jump_around = needs_jump_around;
+  tf->fragP = fragP;
+  tf->fixP = NULL;
+}
+
+
+static struct tramp_seg *
+find_tramp_seg (asection *seg)
+{
+  struct tramp_seg *ts = tramp_seg_list.next;
+
+  for ( ; ts; ts = ts->next)
+    {
+      if (ts->seg == seg)
+	return ts;
+    }
+
+  return NULL;
+}
+
+
+void dump_tramps (void);
+
+void
+dump_tramps (void)
+{
+  struct tramp_seg *ts = tramp_seg_list.next;
+  for ( ; ts; ts = ts->next)
+    {
+      asection *seg = ts->seg;
+      if (seg == NULL)
+	continue;
+      fprintf(stderr, "SECTION %s\n", seg->name);
+      struct tramp_frag *tf = ts->tramp_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 +8887,142 @@ 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 tramp_seg *ts = find_tramp_seg (now_seg);
+		  struct tramp_frag *tf = ts->tramp_list.next;
+		  struct tramp_frag *prev = &ts->tramp_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 tramp_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 tramp_list.  */
+			      prev->next = tf->next;
+			      break;
+			    }
+			}
+		    }
+                }
+            }
+        }
+      break;
+
     default:
       as_bad (_("bad relaxation state"));
     }
@@ -9146,6 +9461,193 @@ bytes_to_stretch (fragS *this_frag,
 }
 
 
+static struct tramp_frag *
+search_trampolines (TInsn *tinsn, fragS *fragP, bfd_boolean unreachable_only)
+{
+  struct tramp_seg *ts = find_tramp_seg (now_seg);
+  struct tramp_frag *tf = (ts) ? ts->tramp_list.next : NULL;
+  struct tramp_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)
+	{
+	  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 (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 tramp_frag *
+get_best_trampoline (TInsn *tinsn, fragS *fragP)
+{
+  struct tramp_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 tramp_seg *ts = find_tramp_seg (now_seg);
+  struct tramp_frag *tf = ts->tramp_list.next;
+  struct tramp_frag *prev = &ts->tramp_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 tramp_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 tramp_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 +9786,34 @@ 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 tramp_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 +9934,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;
diff --git a/gas/config/tc-xtensa.h b/gas/config/tc-xtensa.h
index 0bf1240..4672bc6 100644
--- a/gas/config/tc-xtensa.h
+++ b/gas/config/tc-xtensa.h
@@ -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
 };
 
diff --git a/gas/doc/as.texinfo b/gas/doc/as.texinfo
index efeac93..5d4b0fe 100644
--- a/gas/doc/as.texinfo
+++ b/gas/doc/as.texinfo
@@ -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
diff --git a/gas/doc/c-xtensa.texi b/gas/doc/c-xtensa.texi
index 406e635..e763e36 100644
--- a/gas/doc/c-xtensa.texi
+++ b/gas/doc/c-xtensa.texi
@@ -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
diff --git a/gas/frags.c b/gas/frags.c
index 5f68480..e14099d 100644
--- a/gas/frags.c
+++ b/gas/frags.c
@@ -24,6 +24,20 @@
 
 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;
+}
 
 /* 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;
 }
 
diff --git a/gas/frags.h b/gas/frags.h
index 319898f..2f9e1b5 100644
--- a/gas/frags.h
+++ b/gas/frags.h
@@ -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 */
diff --git a/gas/testsuite/gas/xtensa/all.exp b/gas/testsuite/gas/xtensa/all.exp
index 2b2c294..3683b78 100644
--- a/gas/testsuite/gas/xtensa/all.exp
+++ b/gas/testsuite/gas/xtensa/all.exp
@@ -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
index 0000000..b4f65dc
--- /dev/null
+++ b/gas/testsuite/gas/xtensa/trampoline.d
@@ -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
index 0000000..259a3bb
--- /dev/null
+++ b/gas/testsuite/gas/xtensa/trampoline.s
@@ -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
-- 
1.8.1.4



More information about the Binutils mailing list