Patchwork RFA: hookize ADJUST_INSN_LENGTH

login
register
mail settings
Submitter Joern Rennecke
Date Nov. 6, 2012, 3:32 a.m.
Message ID <20121105223238.hsg9w3jeo00w004c-nzlynne@webmail.spamcop.net>
Download mbox | patch
Permalink /patch/197398/
State New
Headers show

Comments

Joern Rennecke - Nov. 6, 2012, 3:32 a.m.
Quoting Richard Sandiford <rdsandiford@googlemail.com>:

> Hey, I wasn't suggesting anything like that :-)  I was just asking
> about alignment.  It seemed to me that part of the problem is that
> ARC wants to increase the length of an instruction in order to align
> a following instruction (which I agree is a useful thing to support).
> But as things stand, shorten_branches has no way of distinguing that from
> an instruction that was forced to grow because of offsets being out of range.
>
> If instead shorten_branches knows that an instruction prefers a particular
> alignment/misalignment, it can cooperate with the backend to extend earlier
> instructions to make that possible.  If we do it that way, shorten_branches
> knows what's going on.

I've now put together an interface that I hope should be rather versatile
and, if need be, also extensible.  The algorithm in final.c to use it
is not quite as versatile; it provides backward compatibility for the
existing ports, and the new support for using alignments / lengths variants
specified with the new interface that I believe should work to model
the instruction fetch alignment issues for ARC at the word level.
We can refine this for 4.9 to be more general.  If you agree with the
overall design, then I think my priority for 4.8 should be to make the
ARC port fit, and fill in any missing documentation bits.

build-tested for i686-pc-linux-gnu X mmix-knuth-mmixware
bootstrapped on i686-pc-linux-gnu.

If you want a full conversion of the existing ports with ADJUST_INSN_LENGTH
to TARGET_ADJUST_INSN_LENGTH, I can take the config part of the previous
patch and rip out the iter_threshold parameter - see second attachment.
2012-11-05  Joern Rennecke  <joern.rennecke@embecosm.com>

	* doc/tm.texi.in (@hook TARGET_ADJUST_INSN_LENGTH): Add.
	(@hook TARGET_INSN_LENGTH_PARAMETERS): Add.
	* doc/tm.texi: Regenerate.
	* final.c (get_attr_length_1): Assert HAVE_ATTR_length.
	Use targetm.adjust_insn_length instead of ADJUST_INSN_LENGTH.
	(shorten_branches_context_t): New typedef.
	(adjust_length): New function.
	(shorten_branches): Use adjust_length instead of ADJUST_INSN_LENGTH.
	Try to satidfy alignment by using variable length instructions.
	* target.def (adjust_insn_length, insn_length_parameters): New hooks.
	* target.h (insn_length_variant_t, insn_length_parameters_t): New types.
	* targhooks.c (default_adjust_insn_length): New function.
	* targhooks.h (default_adjust_insn_length): Declare.
2012-10-31  Joern Rennecke  <joern.rennecke@embecosm.com>

        * doc/tm.texi.in (@hook TARGET_ADJUST_INSN_LENGTH): Add.
        * doc/tm.texi: Regenerate. 
	* doc/md.texi (Insn Lengths): Update documentation about length
	adjustment.
        * final.c (get_attr_length_1): Use targetm.adjust_insn_length
        instead of ADJUST_INSN_LENGTH.
        (adjust_length): New function.
        (shorten_branches): Use adjust_length instead of ADJUST_INSN_LENGTH.
        * target.def (adjust_insn_length): New hook.
        * targhooks.c (default_adjust_insn_length): New function.
        * targhooks.h (default_adjust_insn_length): Declare.
	* config/arm/arm.c (TARGET_ADJUST_INSN_LENGTH): Redefine.
	(arm_adjust_insn_length): New function.
	* config/arm/arm.h (ADJUST_INSN_LENGTH): Don't define.
	* config/avr/avr.c (adjust_insn_length): Make static.
	Add new parameters.
	(TARGET_ADJUST_INSN_LENGTH): Redefine.
	* config/avr/avr.h (ADJUST_INSN_LENGTH): Don't define.
	* config/avr/avr-protos.h (adjust_insn_length): Don't declare.
	* config/iq2000/iq2000.c (TARGET_ADJUST_INSN_LENGTH): Redefine.
	(iq2000_adjust_insn_length): Make static.  Add new parameters.
	* config/iq2000/iq2000.h (ADJUST_INSN_LENGTH): Don't define.
	* config/iq2000/iq2000-protos.h (iq2000_adjust_insn_length):
	Don't declare.
	* config/mips/mips.c (mips_adjust_insn_length): Make static.
	Add new parameters.
	(TARGET_ADJUST_INSN_LENGTH): Redefine.
	* config/mips/mips.h (ADJUST_INSN_LENGTH): Don't define.
	* config/mips/mips-protos.h (mips_adjust_insn_length): Don't declare.
	* config/pa/pa.c  (TARGET_ADJUST_INSN_LENGTH): Redefine.
	(pa_adjust_insn_length): Make static.  Add new parameters.
	Return adjusted length instead of length adjustment.
	* config/pa/pa.h (ADJUST_INSN_LENGTH): Don't define.
	* config/pa/pa-protos.h (pa_adjust_insn_length): Don't declare.
	* config/rs6000/rs6000.h ADJUST_INSN_LENGTH): Don't mention.
	* config/sh/sh.c (TARGET_ADJUST_INSN_LENGTH): Redefine.
	(sh_insn_length_adjustment): Make static.  Add new parameters.
	Return adjusted length instead of length adjustment.
	* config/sh/sh.h (ADJUST_INSN_LENGTH): Don't define.
	* config/sh/sh-protos.h (sh_insn_length_adjustment): Don't declare.
	* config/tilegx/tilegx.c (tilegx_adjust_insn_length): Make static.
	Add new parameters.
	(TARGET_ADJUST_INSN_LENGTH): Redefine.
	* config/tilegx/tilegx.h (ADJUST_INSN_LENGTH): Don't define.
	* config/tilegx/tilegx-protos.h (tilegx_adjust_insn_length):
	Don't declare.
	* config/tilepro/tilepro.c (tilepro_adjust_insn_lengt): Make static.
	Add new parameters.
	(TARGET_ADJUST_INSN_LENGTH): Redefine.
	* config/tilepro/tilepro.h (ADJUST_INSN_LENGTH): Don't define.
	* config/tilepro/tilepro-protos.h (tilepro_adjust_insn_length):
	Don't declare.
	* system.h (ADJUST_INSN_LENGTH): Poison.

diff -drup gcc-haveattr/gcc/config/arm/arm.c gcc/gcc/config/arm/arm.c
--- gcc-haveattr/gcc/config/arm/arm.c	2012-10-25 03:33:47.000000000 +0100
+++ gcc/gcc/config/arm/arm.c	2012-10-31 09:57:34.260366262 +0000
@@ -269,6 +269,7 @@ static int arm_cortex_a5_branch_cost (bo
 
 static bool arm_vectorize_vec_perm_const_ok (enum machine_mode vmode,
 					     const unsigned char *sel);
+static int arm_adjust_insn_length (rtx insn, int length, bool, int*);
 
 
 /* Table of machine attributes.  */
@@ -626,6 +627,9 @@ static const struct attribute_spec arm_a
 #define TARGET_VECTORIZE_VEC_PERM_CONST_OK \
   arm_vectorize_vec_perm_const_ok
 
+#undef TARGET_ADJUST_INSN_LENGTH
+#define TARGET_ADJUST_INSN_LENGTH arm_adjust_insn_length
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 /* Obstack for minipool constant handling.  */
@@ -26576,6 +26580,17 @@ arm_vectorize_vec_perm_const_ok (enum ma
   return ret;
 }
 
+/* Implement TARGET_ADJUST_INSN_LENGTH.
+   Add two bytes to the length of conditionally executed Thumb-2
+   instructions for the IT instruction.  */
+static int
+arm_adjust_insn_length (rtx insn, int length, bool, int *)
+{
+  if (TARGET_THUMB2 && GET_CODE (PATTERN (insn)) == COND_EXEC) \
+    length += 2;
+  return length;
+}
+
 bool
 arm_autoinc_modes_ok_p (enum machine_mode mode, enum arm_auto_incmodes code)
 {
diff -drup gcc-haveattr/gcc/config/arm/arm.h gcc/gcc/config/arm/arm.h
--- gcc-haveattr/gcc/config/arm/arm.h	2012-10-23 05:41:58.000000000 +0100
+++ gcc/gcc/config/arm/arm.h	2012-10-31 09:57:34.263366304 +0000
@@ -2200,12 +2200,6 @@ extern int making_const_table;
     }
 #endif
 
-/* Add two bytes to the length of conditionally executed Thumb-2
-   instructions for the IT instruction.  */
-#define ADJUST_INSN_LENGTH(insn, length) \
-  if (TARGET_THUMB2 && GET_CODE (PATTERN (insn)) == COND_EXEC) \
-    length += 2;
-
 /* Only perform branch elimination (by making instructions conditional) if
    we're optimizing.  For Thumb-2 check if any IT instructions need
    outputting.  */
diff -drup gcc-haveattr/gcc/config/avr/avr.c gcc/gcc/config/avr/avr.c
--- gcc-haveattr/gcc/config/avr/avr.c	2012-10-27 19:59:26.673026312 +0100
+++ gcc/gcc/config/avr/avr.c	2012-10-31 09:57:34.267366344 +0000
@@ -1,6 +1,6 @@
 /* Subroutines for insn-output.c for ATMEL AVR micro controllers
-   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007, 2008,
-   2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004-2012
+   Free Software Foundation, Inc.
    Contributed by Denis Chertykov (chertykov@gmail.com)
 
    This file is part of GCC.
@@ -7468,8 +7468,8 @@ avr_out_fract (rtx insn, rtx operands[],
 /* Modifies the length assigned to instruction INSN
    LEN is the initially computed length of the insn.  */
 
-int
-adjust_insn_length (rtx insn, int len)
+static int
+adjust_insn_length (rtx insn, int len, bool, int *)
 {
   rtx *op = recog_data.operand;
   enum attr_adjust_len adjust_len;
@@ -11921,6 +11921,9 @@ avr_fold_builtin (tree fndecl, int n_arg
 #undef  TARGET_PRINT_OPERAND_PUNCT_VALID_P
 #define TARGET_PRINT_OPERAND_PUNCT_VALID_P avr_print_operand_punct_valid_p
 
+#undef TARGET_ADJUST_INSN_LENGTH
+#define TARGET_ADJUST_INSN_LENGTH adjust_insn_length
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 
diff -drup gcc-haveattr/gcc/config/avr/avr.h gcc/gcc/config/avr/avr.h
--- gcc-haveattr/gcc/config/avr/avr.h	2012-10-15 12:21:10.000000000 +0100
+++ gcc/gcc/config/avr/avr.h	2012-10-31 09:57:34.269366372 +0000
@@ -1,8 +1,6 @@
 /* Definitions of target machine for GNU compiler,
    for ATMEL AVR at90s8515, ATmega103/103L, ATmega603/603L microcontrollers.
-   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 
-   2008, 2009, 2010, 2011
-   Free Software Foundation, Inc.
+   Copyright (C) 1998-2012 Free Software Foundation, Inc.
    Contributed by Denis Chertykov (chertykov@gmail.com)
 
 This file is part of GCC.
@@ -474,9 +472,6 @@ typedef struct avr_args {
 #define FUNCTION_PROFILER(FILE, LABELNO)  \
   fprintf (FILE, "/* profiler %d */", (LABELNO))
 
-#define ADJUST_INSN_LENGTH(INSN, LENGTH) (LENGTH =\
-					  adjust_insn_length (INSN, LENGTH))
-
 extern const char *avr_device_to_arch (int argc, const char **argv);
 extern const char *avr_device_to_data_start (int argc, const char **argv);
 extern const char *avr_device_to_startfiles (int argc, const char **argv);
diff -drup gcc-haveattr/gcc/config/avr/avr-protos.h gcc/gcc/config/avr/avr-protos.h
--- gcc-haveattr/gcc/config/avr/avr-protos.h	2012-10-03 15:27:48.000000000 +0100
+++ gcc/gcc/config/avr/avr-protos.h	2012-10-31 09:57:34.270366384 +0000
@@ -1,7 +1,6 @@
 /* Prototypes for exported functions defined in avr.c
    
-   Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
-   2011
+   Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006-2012
    Free Software Foundation, Inc.
    Contributed by Denis Chertykov (chertykov@gmail.com)
 
@@ -98,7 +97,6 @@ extern bool avr_popcount_each_byte (rtx,
 extern bool avr_has_nibble_0xf (rtx);
 
 extern int extra_constraint_Q (rtx x);
-extern int adjust_insn_length (rtx insn, int len);
 extern const char* output_reload_inhi (rtx*, rtx, int*);
 extern const char* output_reload_insisf (rtx*, rtx, int*);
 extern const char* avr_out_reload_inpsi (rtx*, rtx, int*);
diff -drup gcc-haveattr/gcc/config/iq2000/iq2000.c gcc/gcc/config/iq2000/iq2000.c
--- gcc-haveattr/gcc/config/iq2000/iq2000.c	2012-10-25 03:33:46.000000000 +0100
+++ gcc/gcc/config/iq2000/iq2000.c	2012-10-31 09:57:34.271366396 +0000
@@ -1,6 +1,5 @@
 /* Subroutines used for code generation on Vitesse IQ2000 processors
-   Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
-   Free Software Foundation, Inc.
+   Copyright (C) 2003-2012 Free Software Foundation, Inc.
 
 This file is part of GCC.
 
@@ -171,6 +170,7 @@ static bool iq2000_legitimate_address_p
 static bool iq2000_can_eliminate      (const int, const int);
 static void iq2000_asm_trampoline_template (FILE *);
 static void iq2000_trampoline_init    (rtx, tree, rtx);
+static int iq2000_adjust_insn_length  (rtx, int, bool, int *);
 static rtx iq2000_function_value      (const_tree, const_tree, bool);
 static rtx iq2000_libcall_value       (enum machine_mode, const_rtx);
 static void iq2000_print_operand      (FILE *, rtx, int);
@@ -250,6 +250,9 @@ static bool iq2000_print_operand_punct_v
 #undef  TARGET_TRAMPOLINE_INIT
 #define TARGET_TRAMPOLINE_INIT		iq2000_trampoline_init
 
+#undef TARGET_ADJUST_INSN_LENGTH
+#define TARGET_ADJUST_INSN_LENGTH	iq2000_adjust_insn_length
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 /* Return nonzero if we split the address into high and low parts.  */
@@ -2280,9 +2283,16 @@ iq2000_pass_by_reference (cumulative_arg
 /* Return the length of INSN.  LENGTH is the initial length computed by
    attributes in the machine-description file.  */
 
-int
-iq2000_adjust_insn_length (rtx insn, int length)
+static int
+iq2000_adjust_insn_length (rtx insn, int length, bool in_delay_sequence,
+			   int *iter_threshold)
 {
+  if (in_delay_sequence)
+    {
+      *iter_threshold = INT_MAX; /* To stay true to previous version.  */
+      return length;
+    }
+
   /* A unconditional jump has an unfilled delay slot if it is not part
      of a sequence.  A conditional jump normally has a delay slot.  */
   if (simplejump_p (insn)
diff -drup gcc-haveattr/gcc/config/iq2000/iq2000.h gcc/gcc/config/iq2000/iq2000.h
--- gcc-haveattr/gcc/config/iq2000/iq2000.h	2012-10-18 19:40:43.000000000 +0100
+++ gcc/gcc/config/iq2000/iq2000.h	2012-10-31 09:57:34.275366442 +0000
@@ -1,7 +1,6 @@
 /* Definitions of target machine for GNU compiler.  
    Vitesse IQ2000 processors
-   Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
-   Free Software Foundation, Inc.
+   Copyright (C) 2003-2012 Free Software Foundation, Inc.
 
    This file is part of GCC.
 
@@ -779,14 +778,6 @@ enum delay_type
 #define GO_PRINTF2(x,y)
 #define GO_DEBUG_RTX(x)
 #endif
-
-/* If defined, modifies the length assigned to instruction INSN as a
-   function of the context in which it is used.  LENGTH is an lvalue
-   that contains the initially computed length of the insn and should
-   be updated with the correct length of the insn.  */
-#define ADJUST_INSN_LENGTH(INSN, LENGTH) \
-  ((LENGTH) = iq2000_adjust_insn_length ((INSN), (LENGTH)))
-
 
 
 
diff -drup gcc-haveattr/gcc/config/iq2000/iq2000-protos.h gcc/gcc/config/iq2000/iq2000-protos.h
--- gcc-haveattr/gcc/config/iq2000/iq2000-protos.h	2012-09-03 11:09:46.000000000 +0100
+++ gcc/gcc/config/iq2000/iq2000-protos.h	2012-10-31 09:57:34.275366442 +0000
@@ -1,5 +1,6 @@
 /* Definitions of target machine for GNU compiler for iq2000.
-   Copyright (C) 2003, 2004, 2007, 2009, 2010 Free Software Foundation, Inc.
+   Copyright (C) 2003, 2004, 2007, 2009, 2010, 2012
+   Free Software Foundation, Inc.
 
    This file is part of GCC.
 
@@ -32,7 +33,6 @@ extern void             iq2000_expand_pr
 extern void             iq2000_expand_epilogue (void);
 extern void             iq2000_expand_eh_return (rtx);
 extern int              iq2000_can_use_return_insn (void);
-extern int              iq2000_adjust_insn_length (rtx, int);
 extern char *           iq2000_output_conditional_branch (rtx, rtx *, int, int, int, int);
 
 #ifdef RTX_CODE
diff -drup gcc-haveattr/gcc/config/mips/mips.c gcc/gcc/config/mips/mips.c
--- gcc-haveattr/gcc/config/mips/mips.c	2012-10-31 09:11:11.308880380 +0000
+++ gcc/gcc/config/mips/mips.c	2012-10-31 09:57:34.281366514 +0000
@@ -12087,9 +12087,16 @@ mips_output_load_label (rtx target)
 /* Return the length of INSN.  LENGTH is the initial length computed by
    attributes in the machine-description file.  */
 
-int
-mips_adjust_insn_length (rtx insn, int length)
+static int
+mips_adjust_insn_length (rtx insn, int length, bool in_delay_sequence,
+			 int *iter_threshold)
 {
+  if (in_delay_sequence)
+    {
+      *iter_threshold = INT_MAX;
+      return length;
+    }
+
   /* mips.md uses MAX_PIC_BRANCH_LENGTH as a placeholder for the length
      of a PIC long-branch sequence.  Substitute the correct value.  */
   if (length == MAX_PIC_BRANCH_LENGTH
@@ -18269,6 +18276,9 @@ mips_expand_vec_minmax (rtx target, rtx
 #undef TARGET_VECTORIZE_VEC_PERM_CONST_OK
 #define TARGET_VECTORIZE_VEC_PERM_CONST_OK mips_vectorize_vec_perm_const_ok
 
+#undef TARGET_ADJUST_INSN_LENGTH
+#define TARGET_ADJUST_INSN_LENGTH mips_adjust_insn_length
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 #include "gt-mips.h"
diff -drup gcc-haveattr/gcc/config/mips/mips.h gcc/gcc/config/mips/mips.h
--- gcc-haveattr/gcc/config/mips/mips.h	2012-10-25 03:33:48.000000000 +0100
+++ gcc/gcc/config/mips/mips.h	2012-10-31 09:57:34.315366907 +0000
@@ -2421,13 +2421,6 @@ typedef struct mips_args {
 #define BRANCH_COST(speed_p, predictable_p) mips_branch_cost
 #define LOGICAL_OP_NON_SHORT_CIRCUIT 0
 
-/* If defined, modifies the length assigned to instruction INSN as a
-   function of the context in which it is used.  LENGTH is an lvalue
-   that contains the initially computed length of the insn and should
-   be updated with the correct length of the insn.  */
-#define ADJUST_INSN_LENGTH(INSN, LENGTH) \
-  ((LENGTH) = mips_adjust_insn_length ((INSN), (LENGTH)))
-
 /* Return the asm template for a non-MIPS16 conditional branch instruction.
    OPCODE is the opcode's mnemonic and OPERANDS is the asm template for
    its operands.  */
diff -drup gcc-haveattr/gcc/config/mips/mips-protos.h gcc/gcc/config/mips/mips-protos.h
--- gcc-haveattr/gcc/config/mips/mips-protos.h	2012-10-25 03:33:48.000000000 +0100
+++ gcc/gcc/config/mips/mips-protos.h	2012-10-31 09:57:34.316366919 +0000
@@ -307,7 +307,6 @@ extern enum reg_class mips_secondary_rel
 						   rtx, bool);
 extern int mips_class_max_nregs (enum reg_class, enum machine_mode);
 
-extern int mips_adjust_insn_length (rtx, int);
 extern void mips_output_load_label (rtx);
 extern const char *mips_output_conditional_branch (rtx, rtx *, const char *,
 						   const char *);
diff -drup gcc-haveattr/gcc/config/pa/pa.c gcc/gcc/config/pa/pa.c
--- gcc-haveattr/gcc/config/pa/pa.c	2012-10-25 03:33:47.000000000 +0100
+++ gcc/gcc/config/pa/pa.c	2012-10-31 09:57:34.320366969 +0000
@@ -1,7 +1,5 @@
 /* Subroutines for insn-output.c for HPPA.
-   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
-   2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
-   Free Software Foundation, Inc.
+   Copyright (C) 1992-2012 Free Software Foundation, Inc.
    Contributed by Tim Moore (moore@cs.utah.edu), based on sparc.c
 
 This file is part of GCC.
@@ -190,6 +188,8 @@ static bool pa_cannot_force_const_mem (e
 static bool pa_legitimate_constant_p (enum machine_mode, rtx);
 static unsigned int pa_section_type_flags (tree, const char *, int);
 
+static int pa_adjust_insn_length (rtx, int, bool, int *);
+
 /* The following extra sections are only used for SOM.  */
 static GTY(()) section *som_readonly_data_section;
 static GTY(()) section *som_one_only_readonly_data_section;
@@ -387,6 +387,9 @@ static size_t n_deferred_plabels = 0;
 #undef TARGET_SECTION_TYPE_FLAGS
 #define TARGET_SECTION_TYPE_FLAGS pa_section_type_flags
 
+#undef TARGET_ADJUST_INSN_LENGTH
+#define TARGET_ADJUST_INSN_LENGTH pa_adjust_insn_length
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 /* Parse the -mfixed-range= option string.  */
@@ -4889,24 +4892,31 @@ pa_issue_rate (void)
 
    Also compute the length of an inline block move here as it is too
    complicated to express as a length attribute in pa.md.  */
-int
-pa_adjust_insn_length (rtx insn, int length)
+static int
+pa_adjust_insn_length (rtx insn, int length, bool in_delay_sequence,
+		       int *iter_threshold)
 {
   rtx pat = PATTERN (insn);
 
+  if (in_delay_sequence)
+    {
+      *iter_threshold = INT_MAX;
+      return length;
+    }
+
   /* Jumps inside switch tables which have unfilled delay slots need
      adjustment.  */
   if (GET_CODE (insn) == JUMP_INSN
       && GET_CODE (pat) == PARALLEL
       && get_attr_type (insn) == TYPE_BTABLE_BRANCH)
-    return 4;
+    return length + 4;
   /* Millicode insn with an unfilled delay slot.  */
   else if (GET_CODE (insn) == INSN
 	   && GET_CODE (pat) != SEQUENCE
 	   && GET_CODE (pat) != USE
 	   && GET_CODE (pat) != CLOBBER
 	   && get_attr_type (insn) == TYPE_MILLI)
-    return 4;
+    return length + 4;
   /* Block move pattern.  */
   else if (GET_CODE (insn) == INSN
 	   && GET_CODE (pat) == PARALLEL
@@ -4915,7 +4925,7 @@ pa_adjust_insn_length (rtx insn, int len
 	   && GET_CODE (XEXP (XVECEXP (pat, 0, 0), 1)) == MEM
 	   && GET_MODE (XEXP (XVECEXP (pat, 0, 0), 0)) == BLKmode
 	   && GET_MODE (XEXP (XVECEXP (pat, 0, 0), 1)) == BLKmode)
-    return compute_movmem_length (insn) - 4;
+    return length + compute_movmem_length (insn) - 4;
   /* Block clear pattern.  */
   else if (GET_CODE (insn) == INSN
 	   && GET_CODE (pat) == PARALLEL
@@ -4923,7 +4933,7 @@ pa_adjust_insn_length (rtx insn, int len
 	   && GET_CODE (XEXP (XVECEXP (pat, 0, 0), 0)) == MEM
 	   && XEXP (XVECEXP (pat, 0, 0), 1) == const0_rtx
 	   && GET_MODE (XEXP (XVECEXP (pat, 0, 0), 0)) == BLKmode)
-    return compute_clrmem_length (insn) - 4;
+    return length + compute_clrmem_length (insn) - 4;
   /* Conditional branch with an unfilled delay slot.  */
   else if (GET_CODE (insn) == JUMP_INSN && ! simplejump_p (insn))
     {
@@ -4932,11 +4942,11 @@ pa_adjust_insn_length (rtx insn, int len
 	  && length == 4
 	  && JUMP_LABEL (insn) != NULL_RTX
 	  && ! forward_branch_p (insn))
-	return 4;
+	return 8;
       else if (GET_CODE (pat) == PARALLEL
 	       && get_attr_type (insn) == TYPE_PARALLEL_BRANCH
 	       && length == 4)
-	return 4;
+	return 8;
       /* Adjust dbra insn with short backwards conditional branch with
 	 unfilled delay slot -- only for case where counter is in a
 	 general register register.  */
@@ -4946,11 +4956,11 @@ pa_adjust_insn_length (rtx insn, int len
  	       && ! FP_REG_P (XEXP (XVECEXP (pat, 0, 1), 0))
 	       && length == 4
 	       && ! forward_branch_p (insn))
-	return 4;
+	return 8;
       else
-	return 0;
+	return length;
     }
-  return 0;
+  return length;
 }
 
 /* Implement the TARGET_PRINT_OPERAND_PUNCT_VALID_P hook.  */
diff -drup gcc-haveattr/gcc/config/pa/pa.h gcc/gcc/config/pa/pa.h
--- gcc-haveattr/gcc/config/pa/pa.h	2012-09-03 11:10:08.000000000 +0100
+++ gcc/gcc/config/pa/pa.h	2012-10-31 09:57:34.336367156 +0000
@@ -1,7 +1,5 @@
 /* Definitions of target machine for GNU compiler, for the HP Spectrum.
-   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
-   2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
-   Free Software Foundation, Inc.
+   Copyright (C) 1992-2012 Free Software Foundation, Inc.
    Contributed by Michael Tiemann (tiemann@cygnus.com) of Cygnus Support
    and Tim Moore (moore@defmacro.cs.utah.edu) of the Center for
    Software Science at the University of Utah.
@@ -1271,11 +1269,6 @@ do { 									\
 /* Adjust the cost of branches.  */
 #define BRANCH_COST(speed_p, predictable_p) (pa_cpu == PROCESSOR_8000 ? 2 : 1)
 
-/* Handling the special cases is going to get too complicated for a macro,
-   just call `pa_adjust_insn_length' to do the real work.  */
-#define ADJUST_INSN_LENGTH(INSN, LENGTH)	\
-  LENGTH += pa_adjust_insn_length (INSN, LENGTH);
-
 /* Millicode insns are actually function calls with some special
    constraints on arguments and register usage.
 
diff -drup gcc-haveattr/gcc/config/pa/pa-protos.h gcc/gcc/config/pa/pa-protos.h
--- gcc-haveattr/gcc/config/pa/pa-protos.h	2012-09-03 11:10:08.000000000 +0100
+++ gcc/gcc/config/pa/pa-protos.h	2012-10-31 09:57:34.340367206 +0000
@@ -1,7 +1,6 @@
 /* Prototypes for pa.c functions used in the md file & elsewhere.
-   Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2010, 2011
-   Free Software Foundation,
-   Inc.
+   Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2010-2012
+   Free Software Foundation, Inc.
 
 This file is part of GCC.
 
@@ -57,7 +56,6 @@ extern void pa_print_operand (FILE *, rt
 extern void pa_encode_label (rtx);
 extern int pa_symbolic_expression_p (rtx);
 extern bool pa_tls_referenced_p (rtx);
-extern int pa_adjust_insn_length (rtx, int);
 extern int pa_fmpyaddoperands (rtx *);
 extern int pa_fmpysuboperands (rtx *);
 extern void pa_emit_bcond_fp (rtx[]);
diff -drup gcc-haveattr/gcc/config/rs6000/rs6000.h gcc/gcc/config/rs6000/rs6000.h
--- gcc-haveattr/gcc/config/rs6000/rs6000.h	2012-10-18 19:40:45.000000000 +0100
+++ gcc/gcc/config/rs6000/rs6000.h	2012-10-31 09:57:34.341367218 +0000
@@ -1905,13 +1905,6 @@ extern unsigned rs6000_pmode;
    have been dropped from the PowerPC architecture.  */
 #define SHIFT_COUNT_TRUNCATED 0
 
-/* Adjust the length of an INSN.  LENGTH is the currently-computed length and
-   should be adjusted to reflect any required changes.  This macro is used when
-   there is some systematic length adjustment required that would be difficult
-   to express in the length attribute.  */
-
-/* #define ADJUST_INSN_LENGTH(X,LENGTH) */
-
 /* Given a comparison code (EQ, NE, etc.) and the first operand of a
    COMPARE, return the mode to be used for the comparison.  For
    floating-point, CCFPmode should be used.  CCUNSmode should be used
diff -drup gcc-haveattr/gcc/config/sh/sh.c gcc/gcc/config/sh/sh.c
--- gcc-haveattr/gcc/config/sh/sh.c	2012-10-27 19:59:26.659026051 +0100
+++ gcc/gcc/config/sh/sh.c	2012-10-31 09:57:34.346367276 +0000
@@ -309,6 +309,7 @@ static void sh_trampoline_init (rtx, tre
 static rtx sh_trampoline_adjust_address (rtx);
 static void sh_conditional_register_usage (void);
 static bool sh_legitimate_constant_p (enum machine_mode, rtx);
+static int sh_insn_length_adjustment (rtx, int, bool, int *);
 static int mov_insn_size (enum machine_mode, bool);
 static int max_mov_insn_displacement (enum machine_mode, bool);
 static int mov_insn_alignment_mask (enum machine_mode, bool);
@@ -594,6 +595,9 @@ static const struct attribute_spec sh_at
 #undef TARGET_ATOMIC_TEST_AND_SET_TRUEVAL
 #define TARGET_ATOMIC_TEST_AND_SET_TRUEVAL 0x80
 
+#undef TARGET_ADJUST_INSN_LENGTH
+#define TARGET_ADJUST_INSN_LENGTH sh_insn_length_adjustment
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 
@@ -10116,9 +10120,20 @@ sequence_insn_p (rtx insn)
   return INSN_P (next) && GET_CODE (PATTERN (next)) == SEQUENCE;
 }
 
-int
-sh_insn_length_adjustment (rtx insn)
+/* Instructions with unfilled delay slots take up an
+   extra two bytes for the nop in the delay slot.
+   sh-dsp parallel processing insns are four bytes long.  */
+
+static int
+sh_insn_length_adjustment (rtx insn, int length , bool in_delay_sequence,
+			   int *iter_threshold)
 {
+  if (in_delay_sequence)
+    {
+      *iter_threshold = INT_MAX;
+      return length;
+    }
+
   /* Instructions with unfilled delay slots take up an extra two bytes for
      the nop in the delay slot.  */
   if (((NONJUMP_INSN_P (insn)
@@ -10128,7 +10143,7 @@ sh_insn_length_adjustment (rtx insn)
        || (JUMP_P (insn) && !JUMP_TABLE_DATA_P (insn)))
       && ! sequence_insn_p (insn)
       && get_attr_needs_delay_slot (insn) == NEEDS_DELAY_SLOT_YES)
-    return 2;
+    return length + 2;
 
   /* SH2e has a bug that prevents the use of annulled branches, so if
      the delay slot is not filled, we'll have to put a NOP in it.  */
@@ -10136,13 +10151,13 @@ sh_insn_length_adjustment (rtx insn)
       && JUMP_P (insn) && !JUMP_TABLE_DATA_P (insn)
       && get_attr_type (insn) == TYPE_CBRANCH
       && ! sequence_insn_p (insn))
-    return 2;
+    return length + 2;
 
   /* sh-dsp parallel processing insn take four bytes instead of two.  */
 
   if (NONJUMP_INSN_P (insn))
     {
-      int sum = 0;
+      int sum = length;
       rtx body = PATTERN (insn);
       const char *templ;
       char c;
@@ -10154,7 +10169,7 @@ sh_insn_length_adjustment (rtx insn)
 	templ
 	  = decode_asm_operands (body, NULL, NULL, NULL, NULL, NULL);
       else
-	return 0;
+	return length;
       do
 	{
 	  int ppi_adjust = 0;
@@ -10191,7 +10206,7 @@ sh_insn_length_adjustment (rtx insn)
       while (c);
       return sum;
     }
-  return 0;
+  return length;
 }
 
 /* Return TRUE for a valid displacement for the REG+disp addressing
diff -drup gcc-haveattr/gcc/config/sh/sh.h gcc/gcc/config/sh/sh.h
--- gcc-haveattr/gcc/config/sh/sh.h	2012-10-15 12:21:09.000000000 +0100
+++ gcc/gcc/config/sh/sh.h	2012-10-31 09:57:34.356367396 +0000
@@ -2230,13 +2230,6 @@ extern tree *sh_deferred_function_attrib
 extern int current_function_interrupt;
 
 
-/* Instructions with unfilled delay slots take up an
-   extra two bytes for the nop in the delay slot.
-   sh-dsp parallel processing insns are four bytes long.  */
-
-#define ADJUST_INSN_LENGTH(X, LENGTH)				\
-  (LENGTH) += sh_insn_length_adjustment (X);
-
 /* Define this macro if it is advisable to hold scalars in registers
    in a wider mode than that declared by the program.  In such cases,
    the value is constrained to be within the bounds of the declared
diff -drup gcc-haveattr/gcc/config/sh/sh-protos.h gcc/gcc/config/sh/sh-protos.h
--- gcc-haveattr/gcc/config/sh/sh-protos.h	2012-10-18 19:40:43.000000000 +0100
+++ gcc/gcc/config/sh/sh-protos.h	2012-10-31 09:57:34.357367408 +0000
@@ -151,7 +151,6 @@ extern void expand_sf_unop (rtx (*)(rtx,
 extern void expand_sf_binop (rtx (*)(rtx, rtx, rtx, rtx), rtx *);
 extern void expand_df_unop (rtx (*)(rtx, rtx, rtx), rtx *);
 extern void expand_df_binop (rtx (*)(rtx, rtx, rtx, rtx), rtx *);
-extern int sh_insn_length_adjustment (rtx);
 extern bool sh_can_redirect_branch (rtx, rtx);
 extern void sh_expand_unop_v2sf (enum rtx_code, rtx, rtx);
 extern void sh_expand_binop_v2sf (enum rtx_code, rtx, rtx, rtx);
diff -drup gcc-haveattr/gcc/config/tilegx/tilegx.c gcc/gcc/config/tilegx/tilegx.c
--- gcc-haveattr/gcc/config/tilegx/tilegx.c	2012-10-31 09:46:20.138673734 +0000
+++ gcc/gcc/config/tilegx/tilegx.c	2012-10-31 09:57:34.360367444 +0000
@@ -4278,8 +4278,8 @@ tilegx_frame_pointer_required (void)
 /* Return the length of INSN.  LENGTH is the initial length computed
    by attributes in the machine-description file.  This is where we
    account for bundles.  */
-int
-tilegx_adjust_insn_length (rtx insn, int length)
+static int
+tilegx_adjust_insn_length (rtx insn, int length, bool, int *)
 {
   enum machine_mode mode = GET_MODE (insn);
 
@@ -5582,6 +5582,9 @@ tilegx_file_end (void)
 #undef  TARGET_ASM_ALIGNED_DI_OP
 #define TARGET_ASM_ALIGNED_DI_OP "\t.quad\t"
 
+#undef TARGET_ADJUST_INSN_LENGTH
+#define TARGET_ADJUST_INSN_LENGTH tilegx_adjust_insn_length
+
 
 struct gcc_target targetm = TARGET_INITIALIZER;
 
diff -drup gcc-haveattr/gcc/config/tilegx/tilegx.h gcc/gcc/config/tilegx/tilegx.h
--- gcc-haveattr/gcc/config/tilegx/tilegx.h	2012-09-03 11:09:36.000000000 +0100
+++ gcc/gcc/config/tilegx/tilegx.h	2012-10-31 09:57:34.384367725 +0000
@@ -388,9 +388,6 @@ enum reg_class
 
 #define NO_FUNCTION_CSE 1
 
-#define ADJUST_INSN_LENGTH(INSN, LENGTH) \
-  ((LENGTH) = tilegx_adjust_insn_length ((INSN), (LENGTH)))
-
 #define TARGET_FLOAT_FORMAT IEEE_FLOAT_FORMAT
 
 #define BRANCH_COST(speed_p, predictable_p) ((predictable_p) ? 2 : 6)
diff -drup gcc-haveattr/gcc/config/tilegx/tilegx-protos.h gcc/gcc/config/tilegx/tilegx-protos.h
--- gcc-haveattr/gcc/config/tilegx/tilegx-protos.h	2012-09-03 11:09:36.000000000 +0100
+++ gcc/gcc/config/tilegx/tilegx-protos.h	2012-10-31 09:57:34.385367735 +0000
@@ -62,7 +62,6 @@ extern void tilegx_expand_epilogue (bool
 extern int tilegx_initial_elimination_offset (int, int);
 extern rtx tilegx_return_addr (int, rtx);
 extern rtx tilegx_eh_return_handler_rtx (void);
-extern int tilegx_adjust_insn_length (rtx, int);
 
 extern int tilegx_asm_preferred_eh_data_format (int, int);
 extern void tilegx_final_prescan_insn (rtx);
diff -drup gcc-haveattr/gcc/config/tilepro/tilepro.c gcc/gcc/config/tilepro/tilepro.c
--- gcc-haveattr/gcc/config/tilepro/tilepro.c	2012-10-31 09:51:13.362115651 +0000
+++ gcc/gcc/config/tilepro/tilepro.c	2012-10-31 09:57:34.388367768 +0000
@@ -3887,8 +3887,8 @@ tilepro_frame_pointer_required (void)
 /* Return the length of INSN.  LENGTH is the initial length computed
    by attributes in the machine-description file.  This is where we
    account for bundles.  */
-int
-tilepro_adjust_insn_length (rtx insn, int length)
+static int
+tilepro_adjust_insn_length (rtx insn, int length, bool, int *)
 {
   enum machine_mode mode = GET_MODE (insn);
 
@@ -5072,6 +5072,9 @@ tilepro_file_end (void)
 #undef  TARGET_ASM_FILE_END
 #define TARGET_ASM_FILE_END tilepro_file_end
 
+#undef TARGET_ADJUST_INSN_LENGTH
+#define TARGET_ADJUST_INSN_LENGTH tilepro_adjust_insn_length
+
 
 struct gcc_target targetm = TARGET_INITIALIZER;
 
diff -drup gcc-haveattr/gcc/config/tilepro/tilepro.h gcc/gcc/config/tilepro/tilepro.h
--- gcc-haveattr/gcc/config/tilepro/tilepro.h	2012-09-03 11:10:01.000000000 +0100
+++ gcc/gcc/config/tilepro/tilepro.h	2012-10-31 09:57:34.389367782 +0000
@@ -360,9 +360,6 @@ enum reg_class
 
 #define NO_FUNCTION_CSE 1
 
-#define ADJUST_INSN_LENGTH(INSN, LENGTH) \
-  ((LENGTH) = tilepro_adjust_insn_length ((INSN), (LENGTH)))
-
 #define TARGET_FLOAT_FORMAT IEEE_FLOAT_FORMAT
 
 #define BRANCH_COST(speed_p, predictable_p) ((predictable_p) ? 2 : 6)
diff -drup gcc-haveattr/gcc/config/tilepro/tilepro-protos.h gcc/gcc/config/tilepro/tilepro-protos.h
--- gcc-haveattr/gcc/config/tilepro/tilepro-protos.h	2012-09-03 11:10:01.000000000 +0100
+++ gcc/gcc/config/tilepro/tilepro-protos.h	2012-10-31 09:57:34.390367795 +0000
@@ -63,7 +63,6 @@ extern void tilepro_expand_epilogue (boo
 extern int tilepro_initial_elimination_offset (int, int);
 extern rtx tilepro_return_addr (int, rtx);
 extern rtx tilepro_eh_return_handler_rtx (void);
-extern int tilepro_adjust_insn_length (rtx, int);
 
 extern int tilepro_asm_preferred_eh_data_format (int, int);
 extern void tilepro_final_prescan_insn (rtx);
diff -drup gcc-haveattr/gcc/doc/md.texi gcc/gcc/doc/md.texi
--- gcc-haveattr/gcc/doc/md.texi	2012-10-31 09:39:42.544278346 +0000
+++ gcc/gcc/doc/md.texi	2012-10-31 09:57:34.393367828 +0000
@@ -8062,21 +8062,8 @@ the number of vectors multiplied by the
 
 Lengths are measured in addressable storage units (bytes).
 
-The following macros can be used to refine the length computation:
-
-@table @code
-@findex ADJUST_INSN_LENGTH
-@item ADJUST_INSN_LENGTH (@var{insn}, @var{length})
-If defined, modifies the length assigned to instruction @var{insn} as a
-function of the context in which it is used.  @var{length} is an lvalue
-that contains the initially computed length of the insn and should be
-updated with the correct length of the insn.
-
-This macro will normally not be required.  A case in which it is
-required is the ROMP@.  On this machine, the size of an @code{addr_vec}
-insn must be increased by two to compensate for the fact that alignment
-may be required.
-@end table
+The the length computation can be refined by using the target hook
+@code{TARGET_ADJUST_INSN_LENGTH} (@pxref{Scheduling}).
 
 @findex get_attr_length
 The routine that returns @code{get_attr_length} (the value of the
diff -drup gcc-haveattr/gcc/doc/tm.texi gcc/gcc/doc/tm.texi
--- gcc-haveattr/gcc/doc/tm.texi	2012-10-31 09:10:58.173727601 +0000
+++ gcc/gcc/doc/tm.texi	2012-10-31 09:57:34.439368363 +0000
@@ -11334,3 +11334,7 @@ memory model bits are allowed.
 @deftypevr {Target Hook} {unsigned char} TARGET_ATOMIC_TEST_AND_SET_TRUEVAL
 This value should be set if the result written by @code{atomic_test_and_set} is not exactly 1, i.e. the @code{bool} @code{true}.
 @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} as a function of the context in which @var{insn} is used.  @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-haveattr/gcc/doc/tm.texi.in gcc/gcc/doc/tm.texi.in
--- gcc-haveattr/gcc/doc/tm.texi.in	2012-10-31 09:10:58.175727626 +0000
+++ gcc/gcc/doc/tm.texi.in	2012-10-31 09:58:36.645053770 +0000
@@ -11174,3 +11174,5 @@ memory model bits are allowed.
 @end deftypefn
 
 @hook TARGET_ATOMIC_TEST_AND_SET_TRUEVAL
+
+@hook TARGET_ADJUST_INSN_LENGTH
diff -drup gcc-haveattr/gcc/final.c gcc/gcc/final.c
--- gcc-haveattr/gcc/final.c	2012-10-31 09:39:42.555279565 +0000
+++ gcc/gcc/final.c	2012-10-31 09:57:34.457368574 +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-haveattr/gcc/system.h gcc/gcc/system.h
--- gcc-haveattr/gcc/system.h	2012-09-18 16:13:31.000000000 +0100
+++ gcc/gcc/system.h	2012-10-31 09:57:34.469368717 +0000
@@ -813,7 +813,7 @@ extern void fancy_abort (const char *, i
 	CAN_DEBUG_WITHOUT_FP UNLIKELY_EXECUTED_TEXT_SECTION_NAME	\
 	HOT_TEXT_SECTION_NAME LEGITIMATE_CONSTANT_P ALWAYS_STRIP_DOTDOT	\
 	OUTPUT_ADDR_CONST_EXTRA SMALL_REGISTER_CLASSES ASM_OUTPUT_IDENT	\
-	ASM_BYTE_OP MEMBER_TYPE_FORCES_BLK
+	ASM_BYTE_OP MEMBER_TYPE_FORCES_BLK ADJUST_INSN_LENGTH
 
 /* Target macros only used for code built for the target, that have
    moved to libgcc-tm.h or have never been present elsewhere.  */
diff -drup gcc-haveattr/gcc/target.def gcc/gcc/target.def
--- gcc-haveattr/gcc/target.def	2012-10-25 03:33:33.000000000 +0100
+++ gcc/gcc/target.def	2012-10-31 09:57:34.472368753 +0000
@@ -2818,6 +2818,18 @@ DEFHOOK
  enum unwind_info_type, (void),
  default_debug_unwind_info)
 
+DEFHOOK
+(adjust_insn_length,
+ "Return an adjusted length for @var{insn} as a function of the context\
+ in which @var{insn} is used.  @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-haveattr/gcc/targhooks.c gcc/gcc/targhooks.c
--- gcc-haveattr/gcc/targhooks.c	2012-10-25 03:33:26.000000000 +0100
+++ gcc/gcc/targhooks.c	2012-10-31 10:02:12.570491480 +0000
@@ -1540,4 +1540,15 @@ default_member_type_forces_blk (const_tr
   return false;
 }
 
+/* Default version of adjust_insn_length.  */
+
+int
+default_adjust_insn_length (rtx, int length, bool in_delay_sequence,
+			    int *iter_threshold)
+{
+  if (in_delay_sequence)
+    *iter_threshold = INT_MAX;
+  return length;
+}
+
 #include "gt-targhooks.h"
diff -drup gcc-haveattr/gcc/targhooks.h gcc/gcc/targhooks.h
--- gcc-haveattr/gcc/targhooks.h	2012-10-25 03:33:26.000000000 +0100
+++ gcc/gcc/targhooks.h	2012-10-31 09:57:34.488368941 +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 *);
Joern Rennecke - Nov. 7, 2012, 11:51 p.m.
Quoting Joern Rennecke <joern.rennecke@embecosm.com>:

> +                  varying_length[uid] = (varying_length[inner_uid] & 1);
Typo; I meant:
+                  varying_length[uid] |= (varying_length[inner_uid] & 1);

Patch

Index: doc/tm.texi
===================================================================
--- doc/tm.texi	(revision 193203)
+++ doc/tm.texi	(working copy)
@@ -11334,3 +11334,11 @@  @deftypefn {Target Hook} {unsigned HOST_
 @deftypevr {Target Hook} {unsigned char} TARGET_ATOMIC_TEST_AND_SET_TRUEVAL
 This value should be set if the result written by @code{atomic_test_and_set} is not exactly 1, i.e. the @code{bool} @code{true}.
 @end deftypevr
+
+@deftypefn {Target Hook} int TARGET_ADJUST_INSN_LENGTH (rtx @var{insn}, int @var{length}, bool @var{in_delay_sequence})
+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.  The default implementation uses @code{ADJUST_INSN_LENGTH}, if defined.
+@end deftypefn
+
+@deftypefn {Target Hook} void TARGET_INSN_LENGTH_PARAMETERS (insn_length_parameters_t *@var{insn_length_parameters})
+Fixme: add documentation
+@end deftypefn
Index: doc/tm.texi.in
===================================================================
--- doc/tm.texi.in	(revision 193203)
+++ doc/tm.texi.in	(working copy)
@@ -11174,3 +11174,7 @@  @hook TARGET_MEMMODEL_CHECK
 @end deftypefn
 
 @hook TARGET_ATOMIC_TEST_AND_SET_TRUEVAL
+
+@hook TARGET_ADJUST_INSN_LENGTH
+
+@hook TARGET_INSN_LENGTH_PARAMETERS
Index: final.c
===================================================================
--- final.c	(revision 193203)
+++ final.c	(working copy)
@@ -82,6 +82,7 @@  Software Foundation; either version 3, o
 #include "cfgloop.h"
 #include "params.h"
 #include "tree-pretty-print.h" /* for dump_function_header */
+#include "sbitmap.h"
 
 #ifdef XCOFF_DEBUGGING_INFO
 #include "xcoffout.h"		/* Needed for external data
@@ -377,8 +378,7 @@  get_attr_length_1 (rtx insn, int (*fallb
   int i;
   int length = 0;
 
-  if (!HAVE_ATTR_length)
-    return 0;
+  gcc_assert (HAVE_ATTR_length);
 
   if (insn_lengths_max_uid > INSN_UID (insn))
     return insn_lengths[INSN_UID (insn)];
@@ -424,10 +424,7 @@  get_attr_length_1 (rtx insn, int (*fallb
 	break;
       }
 
-#ifdef ADJUST_INSN_LENGTH
-  ADJUST_INSN_LENGTH (insn, length);
-#endif
-  return length;
+  return targetm.adjust_insn_length (insn, length, false);
 }
 
 /* Obtain the current length of an insn.  If branch shortening has been done,
@@ -828,6 +825,176 @@  struct rtl_opt_pass pass_compute_alignme
 };
 
 
+/* Context to pass from shorten_branches to adjust_length.  */
+typedef struct {
+  /* For each varying length insn, a length to keep across iterations, to
+     avoid cycles.  */
+  int *uid_lock_length;
+  /* Number of iterations since last lock_length change.  */
+  int niter;
+  /* Values obtained from targetm.insn_length_parameters call.  */
+  insn_length_parameters_t parameters;
+  /* A scratch space to hold all the variants of the insn currently being
+     considered.  */
+  insn_length_variant_t *variants;
+  /* For each variant number, an sbitmap that says if that variant is still
+     being considered for this shuid.  */
+  sbitmap *shuid_variants;
+  /* indexed by shuid, alignment offset required at end of insn.  */
+  signed char *request_align;
+  /* Indexed by uid, indicates if and how an insn length varies - see
+     varying_length variable in shorten_branches.  */
+  char *varying_length;
+  /* uid of last insn that provides a choice of lengths.  */
+  int last_aligning_insn;
+  bool something_changed;
+} shorten_branches_context_t;
+
+/* NEW_LENGTH is the length for INSN that has been computed by evaluating
+   the raw instruction length attribute.  Return an adjusted length by
+   avaluating the adjust_insn_length target hook and the get_variants hook
+   in parameters.
+   If INSN is inside a SEQUENCE, then SEQ is that SEQUENCE.
+   TARGET_P indicates if INSN is the target of a call, branch, or call
+   return.  */
+
+static int
+adjust_length (rtx insn, int new_length, bool seq_p,
+	       shorten_branches_context_t *ctx, bool target_p)
+{
+  int uid = INSN_UID (insn);
+  new_length = targetm.adjust_insn_length (insn, new_length, seq_p);
+    /* If the sequence as a whole is subject to variant selection, don't
+       try it for the constituent instructions too; at best, it'd be a waste
+       of time; at worst, it leads to chaos when trying to align the sequence
+       by tweaking a constituent instruction.
+       Also, in general, if we have previously established that
+       variant selection doesn't apply, stick with that decision.  */
+  bool select_variant = (ctx->varying_length[uid] & 4);
+  int n_variants;
+
+  if (new_length < 0)
+    fatal_insn ("negative insn length", insn);
+  int iter_threshold = 0;
+  if (select_variant
+      && (n_variants
+	  = ctx->parameters.get_variants (insn, new_length, seq_p, target_p,
+					  ctx->variants)) != 0)
+    {
+      unsigned align_base = 1 << ctx->parameters.align_base_log;
+      unsigned align_base_mask = align_base - 1;
+      unsigned align_unit_mask = (1 << ctx->parameters.align_unit_log) - 1;
+      gcc_assert ((insn_current_address & align_unit_mask) == 0);
+      int best_cost = new_length = INT_MAX;
+      bool can_align = false;
+      int best_need_align = 0;
+      int shuid = uid_shuid[uid];
+
+      /* Freeze disabled variants, and find cheapest variant;
+	 with any align if we have last_aligning_insn, otherwise with
+	 actual align.
+	 See if we can provide alignment at no extra cost.  */
+      for (int i = 0; i < n_variants; i++)
+	{
+	  insn_length_variant_t *variant = &ctx->variants[i];
+	  if (!variant->enabled)
+	    bitmap_clear_bit (ctx->shuid_variants[i], shuid);
+	  if (bitmap_bit_p (ctx->shuid_variants[i], shuid))
+	    {
+	      int need_align;
+	      unsigned align_offset
+		= insn_current_address >> ctx->parameters.align_unit_log;
+	      align_offset &= align_base_mask;
+	      if (variant->align_set == align_base_mask)
+		need_align = -1;
+	      if ((1 << align_offset) & variant->align_set)
+		need_align = 0; /* OK.  */
+	      /* Checks if adding one unit provides alignment.
+		 FIXME: this works for the current ARC port,
+		 but we should more generally search for an offset
+		 such that a variable length insn can provide it.  */
+	      else if  ((1 << ((align_offset + 1) & (align_base_mask))
+			 & variant->align_set)
+			&& ctx->last_aligning_insn)
+		need_align = 1;
+	      else
+		continue;
+	      int length = variant->length + need_align;
+	      /* FIXME: Add probabilistic weighting and target cost.  */
+	      int cost = (variant->fallthrough_cost + variant->branch_cost);
+	      if (cost > best_cost)
+		continue;
+	      if (cost < best_cost)
+		{
+		  best_cost = cost;
+		  can_align = false;
+		  continue;
+		}
+	      /* FIXME: to cover the general case, we should really
+		 build a bitmap of the offsets that we can manage for
+		 alignment purposes.  */
+	      if (length != new_length)
+		can_align = true;
+	      if (length < new_length)
+		{
+		  new_length = length;
+		  best_need_align = need_align;
+		}
+	      else if (length == new_length
+		       && need_align < best_need_align)
+		best_need_align = need_align;
+	    }
+	}
+      gcc_assert (best_cost < INT_MAX);
+      if (best_need_align >= 0)
+	ctx->request_align[uid_shuid[ctx->last_aligning_insn]]
+	  = (((INSN_ADDRESSES  (ctx->last_aligning_insn)
+	       + insn_lengths[ctx->last_aligning_insn])
+	      >> ctx->parameters.align_unit_log)
+	     + (best_need_align > 0 ? best_need_align : 0));
+      if (can_align)
+	{
+	  if (ctx->request_align[shuid] >= 0)
+	    {
+	      unsigned offset = insn_current_address + new_length;
+	      offset >>= ctx->parameters.align_unit_log;
+	      offset = ctx->request_align[shuid] - offset;
+	      /* Fixme: might want apply smaller mask if alignment
+		 requirement has matching bitmask.  */
+	      offset &= align_base_mask;
+	      new_length += offset;
+	    }
+	  ctx->last_aligning_insn = uid;
+	  ctx->request_align[shuid] = -1;
+	}
+      /* Since we are freezing out variants that have been disabled once,
+	 cycles should generally not happen, so bump up iter_threshold.  */
+      iter_threshold = 7;
+    }
+  /* Stabilizing by length should generally happen for entire delay slot
+     sequences, so doing it inside should be a last resort.  */
+  if (seq_p)
+    iter_threshold = 9;
+  if (new_length != insn_lengths[uid])
+    {
+      if (new_length < ctx->uid_lock_length[uid])
+	new_length = ctx->uid_lock_length[uid];
+      if (new_length == insn_lengths[uid])
+	; /* done here.  */
+      else if (ctx->niter < iter_threshold
+	       || new_length > insn_lengths[uid])
+	{
+	  if (ctx->niter >= iter_threshold)
+	    ctx->uid_lock_length[uid] = new_length, ctx->niter = 0;
+	  insn_lengths[uid] = new_length;
+	  ctx->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.  */
 
@@ -849,7 +1016,10 @@  shorten_branches (rtx first)
   int max_skip;
 #define MAX_CODE_ALIGN 16
   rtx seq;
-  int something_changed = 1;
+  /* Indexed by uid, indicates if and how an insn length varies.
+     Bit 0: varying length instruction
+     Bit 1: aligned label
+     Bit 2: apply instruction variant selection.  */
   char *varying_length;
   rtx body;
   int uid;
@@ -899,7 +1069,14 @@  shorten_branches (rtx first)
 
       INSN_SHUID (insn) = i++;
       if (INSN_P (insn))
-	continue;
+	{
+	  if (GET_CODE (PATTERN (insn)) == SEQUENCE)
+	    {
+	      insn = XVECEXP (PATTERN (insn), 0, 0);
+	      INSN_SHUID (insn) = i++;
+	    }
+	  continue;
+	}
 
       if (LABEL_P (insn))
 	{
@@ -964,14 +1141,39 @@  shorten_branches (rtx first)
   if (!HAVE_ATTR_length)
     return;
 
+  int max_shuid = INSN_SHUID (get_last_insn ()) + 1;
+
+  shorten_branches_context_t ctx;
+  insn_length_parameters_t *parameters = &ctx.parameters;
+  memset (parameters, 0, sizeof *parameters);
+  ctx.variants = 0;
+  if (targetm.insn_length_parameters)
+    {
+      targetm.insn_length_parameters (parameters);
+      ctx.variants = XCNEWVEC (insn_length_variant_t, parameters->max_variants);
+    }
+  unsigned align_base = 1 << parameters->align_base_log;
+  ctx.shuid_variants
+    = sbitmap_vector_alloc (parameters->max_variants, max_shuid);
+  bitmap_vector_ones (ctx.shuid_variants, parameters->max_variants);
+
+  ctx.request_align = 0;
+  if (parameters->align_base_log)
+    {
+      ctx.request_align = XNEWVEC (signed char, max_shuid);
+      gcc_assert ((1 << parameters->align_base_log) - 1 <= SCHAR_MAX);
+      memset (ctx.request_align, -1, max_shuid);
+    }
+
   /* Allocate the rest of the arrays.  */
-  insn_lengths = XNEWVEC (int, max_uid);
+  insn_lengths = XCNEWVEC (int, max_uid);
+  ctx.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.  */
   INSN_ADDRESSES_ALLOC (max_uid);
 
-  varying_length = XCNEWVEC (char, max_uid);
+  ctx.varying_length = varying_length = XCNEWVEC (char, max_uid);
 
   /* Initialize uid_align.  We scan instructions
      from end to start, and keep in align_tab[n] the last seen insn
@@ -1011,7 +1213,6 @@  shorten_branches (rtx first)
          label fields.  */
 
       int min_shuid = INSN_SHUID (get_insns ()) - 1;
-      int max_shuid = INSN_SHUID (get_last_insn ()) + 1;
       int rel;
 
       for (insn = first; insn != 0; insn = NEXT_INSN (insn))
@@ -1066,15 +1267,17 @@  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;
+  ctx.niter = increasing ? 0 : -1;
+  ctx.last_aligning_insn = 0;
+  gcc_assert (INSN_UID (get_insns ()) > ctx.last_aligning_insn);
 
+  bool target_p = true;
   for (insn_current_address = 0, insn = first;
        insn != 0;
        insn_current_address += insn_lengths[uid], insn = NEXT_INSN (insn))
     {
       uid = INSN_UID (insn);
 
-      insn_lengths[uid] = 0;
-
       if (LABEL_P (insn))
 	{
 	  int log = LABEL_TO_ALIGNMENT (insn);
@@ -1084,16 +1287,19 @@  shorten_branches (rtx first)
 	      int new_address = (insn_current_address + align - 1) & -align;
 	      insn_lengths[uid] = new_address - insn_current_address;
 	    }
+	  target_p = true;
 	}
 
       INSN_ADDRESSES (uid) = insn_current_address + insn_lengths[uid];
 
       if (NOTE_P (insn) || BARRIER_P (insn)
-	  || LABEL_P (insn) || DEBUG_INSN_P(insn))
+	  || LABEL_P (insn) || DEBUG_INSN_P (insn))
 	continue;
       if (INSN_DELETED_P (insn))
 	continue;
 
+      int length = 0;
+
       body = PATTERN (insn);
       if (GET_CODE (body) == ADDR_VEC || GET_CODE (body) == ADDR_DIFF_VEC)
 	{
@@ -1101,13 +1307,15 @@  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);
+	  target_p = false;
+	}
       else if (GET_CODE (body) == SEQUENCE)
 	{
 	  int i;
@@ -1135,50 +1343,78 @@  shorten_branches (rtx first)
 	      else
 		inner_length = inner_length_fun (inner_insn);
 
-	      insn_lengths[inner_uid] = inner_length;
+	      inner_length = adjust_length (inner_insn, inner_length, true,
+					    &ctx, target_p);
 	      if (const_delay_slots)
 		{
-		  if ((varying_length[inner_uid]
-		       = insn_variable_length_p (inner_insn)) != 0)
-		    varying_length[uid] = 1;
+		  if (parameters->get_variants
+		      && parameters->get_variants (inner_insn, inner_length,
+						   true, target_p && i == 0,
+						   ctx.variants) > 1)
+		    varying_length[inner_uid] = 5;
+		  else
+		    varying_length[inner_uid]
+		      = insn_variable_length_p (inner_insn);
+		  varying_length[uid] = (varying_length[inner_uid] & 1);
 		  INSN_ADDRESSES (inner_uid) = (insn_current_address
 						+ insn_lengths[uid]);
 		}
 	      else
 		varying_length[inner_uid] = 0;
-	      insn_lengths[uid] += inner_length;
+	      insn_lengths[inner_uid] = inner_length;
+	      length += inner_length;
 	    }
+	  if (parameters->get_variants
+	      && (parameters->get_variants (insn, length, false, target_p,
+					    ctx.variants)
+		  > 1))
+	    {
+	      varying_length[uid] = 5;
+	      for (i = 0; i < XVECLEN (body, 0); i++)
+		{
+		  rtx inner_insn = XVECEXP (body, 0, i);
+		  int inner_uid = INSN_UID (inner_insn);
+		  varying_length[inner_uid] &= ~4;
+		}
+	    }
+	  target_p = false;
 	}
       else if (GET_CODE (body) != USE && GET_CODE (body) != CLOBBER)
 	{
-	  insn_lengths[uid] = length_fun (insn);
-	  varying_length[uid] = insn_variable_length_p (insn);
+	  length = length_fun (insn);
+	  varying_length[uid]
+	    = (insn_variable_length_p (insn)
+	       || (parameters->get_variants
+		   && parameters->get_variants (insn, length, false, target_p,
+						ctx.variants) > 1));
+	  target_p = false;
 	}
-
+	
       /* 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
+      insn_lengths[uid] = adjust_length (insn, length, false, &ctx, target_p);
+      target_p
+	= (CALL_P (body)
+	   || (GET_CODE (body) == SEQUENCE && CALL_P (XVECEXP (body, 0, 0))));
     }
 
   /* Now loop over all the insns finding varying length insns.  For each,
      get the current insn length.  If it has changed, reflect the change.
      When nothing changes for a full pass, we are done.  */
 
-  while (something_changed)
+  ctx.something_changed = true;
+  while (ctx.something_changed)
     {
-      something_changed = 0;
+      if (increasing)
+	ctx.niter++;
+
+      ctx.something_changed = false;
+      ctx.last_aligning_insn = 0;
       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);
@@ -1197,6 +1433,16 @@  shorten_branches (rtx first)
 	      else
 		insn_lengths[uid] = 0;
 	      INSN_ADDRESSES (uid) = insn_current_address;
+	      if (log > parameters->align_base_log && ctx.last_aligning_insn)
+		{
+		  unsigned offset = insn_lengths[uid];
+		  offset -= INSN_ADDRESSES (ctx.last_aligning_insn);
+		  offset -= insn_lengths[ctx.last_aligning_insn];
+		  offset >>= parameters->align_unit_log;
+		  offset &= align_base - 1;
+		  ctx.request_align[uid_shuid[ctx.last_aligning_insn]] = offset;
+		  ctx.last_aligning_insn = 0;
+		}
 	      continue;
 	    }
 
@@ -1228,6 +1474,8 @@  shorten_branches (rtx first)
 	      flags = ADDR_DIFF_VEC_FLAGS (body);
 
 	      /* Try to find a known alignment for rel_lab.  */
+	      /* FIXME: We seem to have lost the code that sets
+		 varying_length & 2 for labels without max_skip.  */
 	      for (prev = rel_lab;
 		   prev
 		   && ! insn_lengths[INSN_UID (prev)]
@@ -1314,7 +1562,7 @@  shorten_branches (rtx first)
 		    = (XVECLEN (body, 1) * GET_MODE_SIZE (GET_MODE (body)));
 		  insn_current_address += insn_lengths[uid];
 		  if (insn_lengths[uid] != old_length)
-		    something_changed = 1;
+		    ctx.something_changed = true;
 		}
 
 	      continue;
@@ -1340,7 +1588,11 @@  shorten_branches (rtx first)
 		    }
 		}
 	      else
-		insn_current_address += insn_lengths[uid];
+		{
+		  insn_current_address += insn_lengths[uid];
+		  if (BARRIER_P (insn))
+		    ctx.last_aligning_insn = 0;
+		}
 
 	      continue;
 	    }
@@ -1364,17 +1616,11 @@  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 (inner_insn, inner_length,
+						    true, &ctx, target_p);
 		    }
 		  insn_current_address += inner_length;
 		  new_length += inner_length;
@@ -1386,27 +1632,21 @@  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, false, &ctx, target_p);
 	  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)
 	break;
     }
 
+  if (ctx.variants)
+    free (ctx.variants);
+  if (ctx.request_align)
+    free (ctx.request_align);
+  sbitmap_vector_free (ctx.shuid_variants);
   free (varying_length);
 }
 
Index: target.def
===================================================================
--- target.def	(revision 193203)
+++ target.def	(working copy)
@@ -2818,6 +2818,22 @@  HOOK_VECTOR_END (target_option)
  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. \
+ The default\
+ implementation uses @code{ADJUST_INSN_LENGTH}, if defined.",
+ int, (rtx insn, int length, bool in_delay_sequence),
+ default_adjust_insn_length)
+
+DEFHOOK
+(insn_length_parameters,
+ "Fixme: add documentation",
+ void, (insn_length_parameters_t *insn_length_parameters),
+ NULL)
+
 DEFHOOKPOD
 (atomic_test_and_set_trueval,
  "This value should be set if the result written by\
Index: target.h
===================================================================
--- target.h	(revision 193203)
+++ target.h	(working copy)
@@ -165,6 +165,28 @@  enum vect_cost_model_location {
   vect_epilogue = 2
 };
 
+typedef struct
+{
+  unsigned align_set;
+  /* Cost as a branch / call target or call return address.  */
+  int target_cost;
+  int fallthrough_cost;
+  int branch_cost;
+  int length;
+  /* 0 for not length sensitive, 1 for largest offset range,
+     2 for next smaller etc.  */
+  unsigned length_sensitive : 8;
+  bool enabled;
+} insn_length_variant_t;
+
+typedef struct insn_length_parameters_s
+{
+  int align_unit_log;
+  int align_base_log;
+  int max_variants;
+  int (*get_variants) (rtx, int, bool, bool, insn_length_variant_t *);
+} insn_length_parameters_t;
+
 /* The target structure.  This holds all the backend hooks.  */
 #define DEFHOOKPOD(NAME, DOC, TYPE, INIT) TYPE NAME;
 #define DEFHOOK(NAME, DOC, TYPE, PARAMS, INIT) TYPE (* NAME) PARAMS;
Index: targhooks.c
===================================================================
--- targhooks.c	(revision 193203)
+++ targhooks.c	(working copy)
@@ -1540,4 +1540,18 @@  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 ATTRIBUTE_UNUSED)
+{
+#ifdef ADJUST_INSN_LENGTH
+  if (!in_delay_sequence)
+    ADJUST_INSN_LENGTH (insn, length);
+#endif
+  return length;
+}
+
+
 #include "gt-targhooks.h"
Index: targhooks.h
===================================================================
--- targhooks.h	(revision 193203)
+++ targhooks.h	(working copy)
@@ -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);