diff -drup gcc-192879-haveattr/doc/tm.texi gcc/doc/tm.texi
--- gcc-192879-haveattr/doc/tm.texi	2012-10-28 01:07:38.463469923 +0000
+++ gcc/doc/tm.texi	2012-10-28 01:31:15.522227927 +0000
@@ -11341,3 +11341,7 @@ This value should be set if the result w
 @deftypevrx {Target Hook} {bool} TARGET_HAVE_ATTR_LENGTH
 These flags are automatically generated;  you should not override them in tm.c:
 @end deftypevr
+
+@deftypefn {Target Hook} int TARGET_ADJUST_INSN_LENGTH (rtx @var{insn}, int @var{length}, bool @var{in_delay_sequence}, int *@var{iteration_threshold})
+Return an adjusted length for @var{insn}.  @var{length} is the value that has been calculated using the @code{length} instruction attribute.  @var{in_delay_sequence} if @var{insn} forms part of a delay sequence.  *@var{iteration_threshold} specifies the number of branch shortening iterations before length decreases are inhibited.  The default implementation uses @code{ADJUST_INSN_LENGTH}, if defined.
+@end deftypefn
diff -drup gcc-192879-haveattr/doc/tm.texi.in gcc/doc/tm.texi.in
--- gcc-192879-haveattr/doc/tm.texi.in	2012-10-28 01:07:38.489470252 +0000
+++ gcc/doc/tm.texi.in	2012-10-28 01:31:55.578745701 +0000
@@ -11175,3 +11175,5 @@ memory model bits are allowed.
 @hook TARGET_ATOMIC_TEST_AND_SET_TRUEVAL
 
 @hook TARGET_HAVE_CC0
+
+@hook TARGET_ADJUST_INSN_LENGTH
diff -drup gcc-192879-haveattr/final.c gcc/final.c
--- gcc-192879-haveattr/final.c	2012-10-28 01:07:38.492470288 +0000
+++ gcc/final.c	2012-10-28 15:24:49.731169660 +0000
@@ -424,10 +424,8 @@ get_attr_length_1 (rtx insn, int (*fallb
 	break;
       }
 
-#ifdef ADJUST_INSN_LENGTH
-  ADJUST_INSN_LENGTH (insn, length);
-#endif
-  return length;
+  int dummy = -1;
+  return targetm.adjust_insn_length (insn, length, false, &dummy);
 }
 
 /* Obtain the current length of an insn.  If branch shortening has been done,
@@ -827,6 +825,56 @@ struct rtl_opt_pass pass_compute_alignme
 };
 
 
+/* Helper function for shorten_branches, to apply the adjust_insn_length
+   target hook, and lock in length increases in order to avoid infinite
+   iteration cycles.
+   INSN the the instruction being processed, and new_length is the length
+   that has been calulated for it from its length attribute.  UID is
+   its UID.  SEQ_P indicates if it is inside a delay slot sequence.
+   INSN_LENGTHS and UID_LOCK_LENGTH are the arrays holding the current lengths
+   and lockeds-in maximum lengths from the prevuious iterations, and are
+   updated for UID when appropriate.
+   *NITER contains the number of iterations since we last made a change
+   to uid_lock_length.
+   *SOMETHING_CHANGED will be set if we make a change to INSN_LENGTHS.
+   The return value is the new length taking the result of the
+   adjust_insn_length and uid_lock_length into account.
+   Note that when *NITER is negative, no length locking is effective, and
+   the new lengths are just assigned.  This is used in the initial pass,
+   and when only making a single pass to update instruction length downward.  */
+static int
+adjust_length (rtx insn, int new_length, int uid, int *insn_lengths,
+	       int *uid_lock_length, bool seq_p, int *niter,
+	       bool *something_changed)
+
+{
+  int iter_threshold = 0;
+  new_length
+    = targetm.adjust_insn_length (insn, new_length, seq_p, &iter_threshold);
+  if (new_length < 0)
+    fatal_insn ("negative insn length", insn);
+  if (iter_threshold < 0)
+    fatal_insn ("negative shorten_branches iteration threshold", insn);
+  if (new_length != insn_lengths[uid])
+    {
+      if (new_length < uid_lock_length[uid])
+	new_length = uid_lock_length[uid];
+      if (new_length == insn_lengths[uid])
+	; /* done here.  */
+      else if (*niter < iter_threshold
+	       || new_length > insn_lengths[uid])
+	{
+	  if (*niter >= iter_threshold)
+	    uid_lock_length[uid] = new_length, *niter = 0;
+	  insn_lengths[uid] = new_length;
+	  *something_changed = true;
+	}
+      else
+	new_length = insn_lengths[uid];
+    }
+  return new_length;
+}
+
 /* Make a pass over all insns and compute their actual lengths by shortening
    any branches of variable length if possible.  */
 
@@ -848,7 +896,7 @@ shorten_branches (rtx first)
   int max_skip;
 #define MAX_CODE_ALIGN 16
   rtx seq;
-  int something_changed = 1;
+  bool something_changed = true;
   char *varying_length;
   rtx body;
   int uid;
@@ -964,7 +1012,8 @@ shorten_branches (rtx first)
     return;
 
   /* Allocate the rest of the arrays.  */
-  insn_lengths = XNEWVEC (int, max_uid);
+  insn_lengths = XCNEWVEC (int, max_uid);
+  int *uid_lock_length = XCNEWVEC (int, max_uid);
   insn_lengths_max_uid = max_uid;
   /* Syntax errors can lead to labels being outside of the main insn stream.
      Initialize insn_addresses, so that we get reproducible results.  */
@@ -1065,6 +1114,7 @@ shorten_branches (rtx first)
 
   /* Compute initial lengths, addresses, and varying flags for each insn.  */
   int (*length_fun) (rtx) = increasing ? insn_min_length : insn_default_length;
+  int niter = increasing ? 0 : -1;
 
   for (insn_current_address = 0, insn = first;
        insn != 0;
@@ -1072,8 +1122,6 @@ shorten_branches (rtx first)
     {
       uid = INSN_UID (insn);
 
-      insn_lengths[uid] = 0;
-
       if (LABEL_P (insn))
 	{
 	  int log = LABEL_TO_ALIGNMENT (insn);
@@ -1093,6 +1141,8 @@ shorten_branches (rtx first)
       if (INSN_DELETED_P (insn))
 	continue;
 
+      int length = 0;
+
       body = PATTERN (insn);
       if (GET_CODE (body) == ADDR_VEC || GET_CODE (body) == ADDR_DIFF_VEC)
 	{
@@ -1100,13 +1150,12 @@ shorten_branches (rtx first)
 	     section.  */
 	  if (JUMP_TABLES_IN_TEXT_SECTION
 	      || readonly_data_section == text_section)
-	    insn_lengths[uid] = (XVECLEN (body,
-					  GET_CODE (body) == ADDR_DIFF_VEC)
-				 * GET_MODE_SIZE (GET_MODE (body)));
+	    length = (XVECLEN (body, GET_CODE (body) == ADDR_DIFF_VEC)
+		      * GET_MODE_SIZE (GET_MODE (body)));
 	  /* Alignment is handled by ADDR_VEC_ALIGN.  */
 	}
       else if (GET_CODE (body) == ASM_INPUT || asm_noperands (body) >= 0)
-	insn_lengths[uid] = asm_insn_count (body) * insn_default_length (insn);
+	length = asm_insn_count (body) * insn_default_length (insn);
       else if (GET_CODE (body) == SEQUENCE)
 	{
 	  int i;
@@ -1134,7 +1183,10 @@ shorten_branches (rtx first)
 	      else
 		inner_length = inner_length_fun (inner_insn);
 
-	      insn_lengths[inner_uid] = inner_length;
+	      inner_length
+		= adjust_length (insn, inner_length, inner_uid, insn_lengths,
+				 uid_lock_length, true, &niter,
+				 &something_changed);
 	      if (const_delay_slots)
 		{
 		  if ((varying_length[inner_uid]
@@ -1145,21 +1197,18 @@ shorten_branches (rtx first)
 		}
 	      else
 		varying_length[inner_uid] = 0;
-	      insn_lengths[uid] += inner_length;
+	      length += inner_length;
 	    }
 	}
       else if (GET_CODE (body) != USE && GET_CODE (body) != CLOBBER)
 	{
-	  insn_lengths[uid] = length_fun (insn);
+	  length = length_fun (insn);
 	  varying_length[uid] = insn_variable_length_p (insn);
 	}
-
+	
       /* If needed, do any adjustment.  */
-#ifdef ADJUST_INSN_LENGTH
-      ADJUST_INSN_LENGTH (insn, insn_lengths[uid]);
-      if (insn_lengths[uid] < 0)
-	fatal_insn ("negative insn length", insn);
-#endif
+      adjust_length (insn, length, uid, insn_lengths, uid_lock_length,
+		     false, &niter, &something_changed);
     }
 
   /* Now loop over all the insns finding varying length insns.  For each,
@@ -1168,16 +1217,16 @@ shorten_branches (rtx first)
 
   while (something_changed)
     {
-      something_changed = 0;
+      if (increasing)
+	niter++;
+
+      something_changed = false;
       insn_current_align = MAX_CODE_ALIGN - 1;
       for (insn_current_address = 0, insn = first;
 	   insn != 0;
 	   insn = NEXT_INSN (insn))
 	{
 	  int new_length;
-#ifdef ADJUST_INSN_LENGTH
-	  int tmp_length;
-#endif
 	  int length_align;
 
 	  uid = INSN_UID (insn);
@@ -1363,17 +1412,13 @@ shorten_branches (rtx first)
 		  if (! varying_length[inner_uid])
 		    inner_length = insn_lengths[inner_uid];
 		  else
-		    inner_length = insn_current_length (inner_insn);
-
-		  if (inner_length != insn_lengths[inner_uid])
 		    {
-		      if (!increasing || inner_length > insn_lengths[inner_uid])
-			{
-			  insn_lengths[inner_uid] = inner_length;
-			  something_changed = 1;
-			}
-		      else
-			inner_length = insn_lengths[inner_uid];
+		      inner_length = insn_current_length (inner_insn);
+
+		      inner_length
+			= adjust_length (insn, inner_length, inner_uid,
+					 insn_lengths, uid_lock_length, true,
+					 &niter, &something_changed);
 		    }
 		  insn_current_address += inner_length;
 		  new_length += inner_length;
@@ -1385,21 +1430,13 @@ shorten_branches (rtx first)
 	      insn_current_address += new_length;
 	    }
 
-#ifdef ADJUST_INSN_LENGTH
 	  /* If needed, do any adjustment.  */
-	  tmp_length = new_length;
-	  ADJUST_INSN_LENGTH (insn, new_length);
+	  int tmp_length = new_length;
+	  new_length
+	    = adjust_length (insn, new_length, uid, insn_lengths,
+			     uid_lock_length, false, &niter,
+			     &something_changed);
 	  insn_current_address += (new_length - tmp_length);
-#endif
-
-	  if (new_length != insn_lengths[uid]
-	      && (!increasing || new_length > insn_lengths[uid]))
-	    {
-	      insn_lengths[uid] = new_length;
-	      something_changed = 1;
-	    }
-	  else
-	    insn_current_address += insn_lengths[uid] - new_length;
 	}
       /* For a non-optimizing compile, do only a single pass.  */
       if (!increasing)
diff -drup gcc-192879-haveattr/target.def gcc/target.def
--- gcc-192879-haveattr/target.def	2012-10-28 01:07:38.517470606 +0000
+++ gcc/target.def	2012-10-28 01:31:15.525227979 +0000
@@ -2818,6 +2818,17 @@ DEFHOOK
  enum unwind_info_type, (void),
  default_debug_unwind_info)
 
+DEFHOOK
+(adjust_insn_length,
+ "Return an adjusted length for @var{insn}.  @var{length} is the value that\
+ has been calculated using the @code{length} instruction attribute. \
+ @var{in_delay_sequence} if @var{insn} forms part of a delay sequence. \
+ *@var{iteration_threshold} specifies the number of branch shortening\
+ iterations before length decreases are inhibited.  The default\
+ implementation uses @code{ADJUST_INSN_LENGTH}, if defined.",
+ int, (rtx insn, int length, bool in_delay_sequence, int *iteration_threshold),
+ default_adjust_insn_length)
+
 DEFHOOKPOD
 (atomic_test_and_set_trueval,
  "This value should be set if the result written by\
diff -drup gcc-192879-haveattr/targhooks.c gcc/targhooks.c
--- gcc-192879-haveattr/targhooks.c	2012-10-25 03:33:26.000000000 +0100
+++ gcc/targhooks.c	2012-10-28 01:41:35.082931024 +0000
@@ -1540,4 +1540,20 @@ default_member_type_forces_blk (const_tr
   return false;
 }
 
+/* Default version of adjust_insn_length.  */
+
+int
+default_adjust_insn_length (rtx insn ATTRIBUTE_UNUSED, int length,
+			    bool in_delay_sequence, int *iter_threshold)
+{
+  if (in_delay_sequence)
+    *iter_threshold = INT_MAX;
+#ifdef ADJUST_INSN_LENGTH
+  else
+    ADJUST_INSN_LENGTH (insn, length);
+#endif
+  return length;
+}
+
+
 #include "gt-targhooks.h"
diff -drup gcc-192879-haveattr/targhooks.h gcc/targhooks.h
--- gcc-192879-haveattr/targhooks.h	2012-10-25 03:33:26.000000000 +0100
+++ gcc/targhooks.h	2012-10-28 01:31:15.479227682 +0000
@@ -193,3 +193,4 @@ extern const char *default_pch_valid_p (
 extern void default_asm_output_ident_directive (const char*);
 
 extern bool default_member_type_forces_blk (const_tree, enum machine_mode);
+extern int default_adjust_insn_length (rtx, int, bool, int *);
