[committed] Add support for MIPS16e save/restore instructions

Richard Sandiford richard@codesourcery.com
Mon Jul 2 15:07:00 GMT 2007


MIPS16e includes a "save" instruction that:

  1. saves a given range of argument registers to the current stack pointer,
  2. allocates a given amount of stack, and
  3. saves a given set of registers at the top of the allocated area.

There's a corresponding "restore" instruction too.

This patch adds support for these instructions.  Because the instructions
only do 32-bit loads and stores, and because the saves done by (1) are
geared specifically for o32, the patch only uses the instructions for
o32 code.

As is usual for move-multiple instructions, the patch emits an rtl
instruction to allocate or deallocate stack and save or restore
registers in parallel.  The two main complications are:

  1. $16, $17 and $31 can be selected individually, but $s2-$s8
     must be selected as a range starting at $s2.

  2. We want to combine pre-existing argument saves into the prologue
     "save" instruction.  The "save" pattern's stores occur when the
     stack pointer still points at the top of the frame, whereas the
     pre-existing stores occur when the stack pointer points at the
     bottom of the frame.  Added to this is the problem that, with large
     frames, the address in the pre-existing store may be the result of
     several earlier instructions, such as in:

          move $17,$sp
          ...
          lw $2,.Lconst
          addiu $2,$17,$2
          sw $4,($2)

     It might have been nice in principle to add the argument stores
     to the "save" instruction and leave the rtl optimisers to delete
     the later stores as dead.  Unfortunately, we don't have any pass
     powerful enough to do that.  It also isn't very easy to determine
     a-priori which arguments have been saved.  You could scan the
     function's PARM_DECLs and see which of them now have a MEM DECL_RTL,
     but doing that, and determining which argument registers that
     DECL_RTL corresponds to, isn't particularly easy.  There would
     then have to be separate logic for varargs functions.

     All in all, it seemed easiest to get mips.c to scan the instruction
     stream and look for argument saves.  It can then record the saved
     register and delete the original store itself.  The code is a little
     ad-hoc, but it's also fairly simple.

     Doing this scan in the prologue expander is simpler than doing it
     in somewhere like mips_reorg, because we don't need to mess around
     with an already-existing save instruction.  It's also more effective,
     since the post-reload optimisers can then delete any support
     instructions that become dead (like the two assignments to $2 above).

It remains to be seen whether:

  /* In mips16 mode, we permit the $t temporary registers to be used
     for reload.  We prohibit the unused $s registers, since they
     are caller saved, and saving them via a mips16 register would
     probably waste more time than just reloading the value.  */
  if (TARGET_MIPS16)
    {
      fixed_regs[18] = call_used_regs[18] = 1;
      fixed_regs[19] = call_used_regs[19] = 1;
      fixed_regs[20] = call_used_regs[20] = 1;
      fixed_regs[21] = call_used_regs[21] = 1;
      fixed_regs[22] = call_used_regs[22] = 1;
      fixed_regs[23] = call_used_regs[23] = 1;
      fixed_regs[26] = call_used_regs[26] = 1;
      fixed_regs[27] = call_used_regs[27] = 1;
      fixed_regs[30] = call_used_regs[30] = 1;
    }

still makes sense when GENERATE_MIPS16E_SAVE_RESTORE, since saving and
restoring these registers is now so cheap.

Tested on mipsisa32r2-elfoabi.  Installed.

Richard


gcc/
2007-07-02  Sandra Loosemore  <sandra@codesourcery.com>
	    Richard Sandiford  <richard@codesourcery.com>
	    Nigel Stephens  <nigel@mips.com>

	* config/mips/mips-protos.h (mips16e_save_restore_info): New struct.
	(mips16e_output_save_restore): Declare.
	(mips16e_save_restore_pattern_p): Likewise.
	* config/mips/mips.h (GENERATE_MIPS16E_SAVE_RESTORE): New macro.
	* config/mips/mips.c (MIPS_MAX_FIRST_STACK_STEP): Return 0x7f8
	for GENERATE_MIPS16E_SAVE_RESTORE.  Return 0x400 for TARGET_MIPS16
	&& !GENERATE_MIPS16E_SAVE_RESTORE && !TARGET_64BIT.
	(BITSET_P): New global macro, extracted from...
	(mips_for_each_saved_reg): ...here.
	(mips16e_save_restore_info): New struct.
	(mips16e_s2_s8_regs, mips16e_a0_a3_regs): New variables.
	(mips16e_save_restore_regs): New variable.
	(mips_split_plus, mips16e_find_first_register): New functions.
	(mips16e_mask_registers): New function.
	(compute_frame_size): Expand the commentary before the function.
	Enforce the MIPS16e save and restore register range restrictions.
	Pad the general register save area at the low end.
	(mips16e_save_restore_reg, mips16e_build_save_restore)
	(mips16e_save_restore_pattern_p, mips16e_add_register_range)
	(mips16e_output_save_restore, mips16e_collect_propagate_value)
	(mips16e_collect_argument_save, mips16e_collect_argument_saves):
	New functions.
	(mips_expand_prologue, mips_expand_epilogue): Handle
	GENERATE_MIPS16E_SAVE_RESTORE.
	* config/mips/mips.md (*mips16e_save_restore): New pattern.

gcc/testsuite/
	* gcc.target/mips/save-restore-1.c: New test.
	* gcc.target/mips/save-restore-2.c: Likewise.
	* gcc.target/mips/save-restore-3.c: Likewise.
	* gcc.target/mips/save-restore-4.c: Likewise.

Index: gcc/config/mips/mips-protos.h
===================================================================
--- gcc/config/mips/mips-protos.h	2007-07-01 04:19:23.000000000 -0700
+++ gcc/config/mips/mips-protos.h	2007-07-02 03:30:27.000000000 -0700
@@ -136,6 +136,8 @@ enum mips_loadgp_style {
   LOADGP_RTP
 };
 
+struct mips16e_save_restore_info;
+
 extern bool mips_symbolic_constant_p (rtx, enum mips_symbol_type *);
 extern int mips_regno_mode_ok_for_base_p (int, enum machine_mode, int);
 extern bool mips_stack_address_p (rtx, enum machine_mode);
@@ -261,4 +263,8 @@ extern const char *current_section_name 
 extern unsigned int current_section_flags (void);
 extern bool mips_use_ins_ext_p (rtx, rtx, rtx);
 
+extern const char *mips16e_output_save_restore (rtx, HOST_WIDE_INT);
+extern bool mips16e_save_restore_pattern_p (rtx, HOST_WIDE_INT,
+					    struct mips16e_save_restore_info *);
+
 #endif /* ! GCC_MIPS_PROTOS_H */
Index: gcc/config/mips/mips.h
===================================================================
--- gcc/config/mips/mips.h	2007-07-02 03:30:26.000000000 -0700
+++ gcc/config/mips/mips.h	2007-07-02 03:30:27.000000000 -0700
@@ -209,6 +209,8 @@ #define TARGET_GPWORD (TARGET_ABICALLS &
 #define TARGET_MIPS16		((target_flags & MASK_MIPS16) != 0)
 /* Generate mips16e code. Default 16bit ASE for mips32/mips32r2/mips64 */
 #define GENERATE_MIPS16E	(TARGET_MIPS16 && mips_isa >= 32)
+/* Generate mips16e register save/restore sequences.  */
+#define GENERATE_MIPS16E_SAVE_RESTORE (GENERATE_MIPS16E && mips_abi == ABI_32)
 
 /* Generic ISA defines.  */
 #define ISA_MIPS1		    (mips_isa == 1)
Index: gcc/config/mips/mips.c
===================================================================
--- gcc/config/mips/mips.c	2007-07-02 03:29:31.000000000 -0700
+++ gcc/config/mips/mips.c	2007-07-02 03:30:27.000000000 -0700
@@ -74,15 +74,25 @@ #define UNSPEC_ADDRESS_TYPE(X) \
   ((enum mips_symbol_type) (XINT (X, 1) - UNSPEC_ADDRESS_FIRST))
 
 /* The maximum distance between the top of the stack frame and the
-   value $sp has when we save & restore registers.
+   value $sp has when we save and restore registers.
 
-   Use a maximum gap of 0x100 in the mips16 case.  We can then use
-   unextended instructions to save and restore registers, and to
-   allocate and deallocate the top part of the frame.
-
-   The value in the !mips16 case must be a SMALL_OPERAND and must
-   preserve the maximum stack alignment.  */
-#define MIPS_MAX_FIRST_STACK_STEP (TARGET_MIPS16 ? 0x100 : 0x7ff0)
+   The value for normal-mode code must be a SMALL_OPERAND and must
+   preserve the maximum stack alignment.  We therefore use a value
+   of 0x7ff0 in this case.
+
+   MIPS16e SAVE and RESTORE instructions can adjust the stack pointer by
+   up to 0x7f8 bytes and can usually save or restore all the registers
+   that we need to save or restore.  (Note that we can only use these
+   instructions for o32, for which the stack alignment is 8 bytes.)
+
+   We use a maximum gap of 0x100 or 0x400 for MIPS16 code when SAVE and
+   RESTORE are not available.  We can then use unextended instructions
+   to save and restore registers, and to allocate and deallocate the top
+   part of the frame.  */
+#define MIPS_MAX_FIRST_STACK_STEP					\
+  (!TARGET_MIPS16 ? 0x7ff0						\
+   : GENERATE_MIPS16E_SAVE_RESTORE ? 0x7f8				\
+   : TARGET_64BIT ? 0x100 : 0x400)
 
 /* True if INSN is a mips.md pattern or asm statement.  */
 #define USEFUL_INSN_P(INSN)						\
@@ -112,6 +122,9 @@ #define FOR_EACH_SUBINSN(SUBINSN, INSN)	
        (SUBINSN) != NEXT_INSN (SEQ_END (INSN));				\
        (SUBINSN) = NEXT_INSN (SUBINSN))
 
+/* True if bit BIT is set in VALUE.  */
+#define BITSET_P(VALUE, BIT) (((VALUE) & (1 << (BIT))) != 0)
+
 /* Classifies an address.
 
    ADDRESS_REG
@@ -543,6 +556,18 @@ struct mips_integer_op {
    an extra SLL at the end.  */
 #define MIPS_MAX_INTEGER_OPS 7
 
+/* Information about a MIPS16e SAVE or RESTORE instruction.  */
+struct mips16e_save_restore_info {
+  /* The number of argument registers saved by a SAVE instruction.
+     0 for RESTORE instructions.  */
+  unsigned int nargs;
+
+  /* Bit X is set if the instruction saves or restores GPR X.  */
+  unsigned int mask;
+
+  /* The total number of bytes to allocate.  */
+  HOST_WIDE_INT size;
+};
 
 /* Global variables for machine-dependent things.  */
 
@@ -1116,6 +1141,21 @@ static struct mips_rtx_cost_data const m
     },
   };
 
+/* If a MIPS16e SAVE or RESTORE instruction saves or restores register
+   mips16e_s2_s8_regs[X], it must also save the registers in indexes
+   X + 1 onwards.  Likewise mips16e_a0_a3_regs.  */
+static const unsigned char mips16e_s2_s8_regs[] = {
+  30, 23, 22, 21, 20, 19, 18
+};
+static const unsigned char mips16e_a0_a3_regs[] = {
+  4, 5, 6, 7
+};
+
+/* A list of the registers that can be saved by the MIPS16e SAVE instruction,
+   ordered from the uppermost in memory to the lowest in memory.  */
+static const unsigned char mips16e_save_restore_regs[] = {
+  31, 30, 23, 22, 21, 20, 19, 18, 17, 16, 7, 6, 5, 4
+};
 
 /* Nonzero if -march should decide the default value of MASK_SOFT_FLOAT.  */
 #ifndef MIPS_MARCH_CONTROLS_SOFT_FLOAT
@@ -1299,6 +1339,24 @@ mips_comp_type_attributes (tree type1, t
   return 1;
 }
 
+/* If X is a PLUS of a CONST_INT, return the two terms in *BASE_PTR
+   and *OFFSET_PTR.  Return X in *BASE_PTR and 0 in *OFFSET_PTR otherwise.  */
+
+static void
+mips_split_plus (rtx x, rtx *base_ptr, HOST_WIDE_INT *offset_ptr)
+{
+  if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 1)) == CONST_INT)
+    {
+      *base_ptr = XEXP (x, 0);
+      *offset_ptr = INTVAL (XEXP (x, 1));
+    }
+  else
+    {
+      *base_ptr = x;
+      *offset_ptr = 0;
+    }
+}
+
 /* Return true if SYMBOL_REF X is associated with a global symbol
    (in the STB_GLOBAL sense).  */
 
@@ -6458,6 +6516,41 @@ mips_save_reg_p (unsigned int regno)
   return false;
 }
 
+/* Return the index of the lowest X in the range [0, SIZE) for which
+   bit REGS[X] is set in MASK.  Return SIZE if there is no such X.  */
+
+static unsigned int
+mips16e_find_first_register (unsigned int mask, const unsigned char *regs,
+			     unsigned int size)
+{
+  unsigned int i;
+
+  for (i = 0; i < size; i++)
+    if (BITSET_P (mask, regs[i]))
+      break;
+
+  return i;
+}
+
+/* *MASK_PTR is a mask of general purpose registers and *GP_REG_SIZE_PTR
+   is the number of bytes that they occupy.  If *MASK_PTR contains REGS[X]
+   for some X in [0, SIZE), adjust *MASK_PTR and *GP_REG_SIZE_PTR so that
+   the same is true for all indexes (X, SIZE).  */
+
+static void
+mips16e_mask_registers (unsigned int *mask_ptr, const unsigned char *regs,
+			unsigned int size, HOST_WIDE_INT *gp_reg_size_ptr)
+{
+  unsigned int i;
+
+  i = mips16e_find_first_register (*mask_ptr, regs, size);
+  for (i++; i < size; i++)
+    if (!BITSET_P (*mask_ptr, regs[i]))
+      {
+	*gp_reg_size_ptr += GET_MODE_SIZE (gpr_mode);
+	*mask_ptr |= 1 << regs[i];
+      }
+}
 
 /* Return the bytes needed to compute the frame pointer from the current
    stack pointer.  SIZE is the size (in bytes) of the local variables.
@@ -6465,10 +6558,9 @@ mips_save_reg_p (unsigned int regno)
    MIPS stack frames look like:
 
              Before call		        After call
-        +-----------------------+	+-----------------------+
-   high |			|       |      			|
-   mem. |		        |	|			|
-        |  caller's temps.    	|       |  caller's temps.    	|
+   high +-----------------------+	+-----------------------+
+   mem. |			|       |      			|
+	|  caller's temps.    	|       |  caller's temps.    	|
 	|       		|       |       	        |
         +-----------------------+	+-----------------------+
  	|       		|	|		        |
@@ -6478,37 +6570,40 @@ mips_save_reg_p (unsigned int regno)
  	|  4 words to save     	|	|  4 words to save	|
 	|  arguments passed	|	|  arguments passed	|
 	|  in registers, even	|	|  in registers, even	|
-    SP->|  if not passed.       |  VFP->|  if not passed.	|
-	+-----------------------+       +-----------------------+
-					|		        |
-                                        |  fp register save     |
-					|			|
-					+-----------------------+
-					|		        |
-                                        |  gp register save     |
-                                        |       		|
-					+-----------------------+
-					|			|
-					|  local variables	|
-					|			|
+        |  if not passed.       |       |  if not passed.	|
+    SP->+-----------------------+  VFP->+-----------------------+
+	        (VFP = SP+fp_sp_offset) |		        |\
+					|  fp register save     | | fp_reg_size
+					|			|/
+		       SP+gp_sp_offset->+-----------------------+
+				       /|		        |\
+				      | |  gp register save     | | gp_reg_size
+		       gp_reg_rounded | |       		|/
+				      |	+-----------------------+
+				       \|  alignment padding    |
 					+-----------------------+
-					|			|
-                                        |  alloca allocations   |
-        				|			|
+					|		        |\
+					|  local variables	| | var_size
+					|			|/
 					+-----------------------+
 					|			|
-					|  GP save for V.4 abi	|
+					|  alloca allocations   |
 					|			|
 					+-----------------------+
-					|			|
-                                        |  arguments on stack   |
-        				|		        |
+				       /|			|
+		       cprestore_size | |  GP save for V.4 abi	|
+				       \|			|
 					+-----------------------+
-                                        |  4 words to save      |
-					|  arguments passed     |
-                                        |  in registers, even   |
-   low                              SP->|  if not passed.       |
-   memory        			+-----------------------+
+					|			|\
+					|  arguments on stack   | |
+					|		        | |
+					+-----------------------+ |
+					|  4 words to save      | | args_size
+					|  arguments passed     | |
+					|  in registers, even   | |
+					|  if not passed.       | |
+   low					|  (TARGET_OLDABI only) |/
+   memory			    SP->+-----------------------+
 
 */
 
@@ -6574,6 +6669,17 @@ compute_frame_size (HOST_WIDE_INT size)
 	}
     }
 
+  /* The MIPS16e SAVE and RESTORE instructions have two ranges of registers:
+     $a3-$a0 and $s2-$s8.  If we save one register in the range, we must
+     save all later registers too.  */
+  if (GENERATE_MIPS16E_SAVE_RESTORE)
+    {
+      mips16e_mask_registers (&mask, mips16e_s2_s8_regs,
+ 			      ARRAY_SIZE (mips16e_s2_s8_regs), &gp_reg_size);
+      mips16e_mask_registers (&mask, mips16e_a0_a3_regs,
+ 			      ARRAY_SIZE (mips16e_a0_a3_regs), &gp_reg_size);
+    }
+
   /* This loop must iterate over the same space as its companion in
      mips_for_each_saved_reg.  */
   for (regno = (FP_REG_LAST - MAX_FPRS_PER_FMT + 1);
@@ -6612,8 +6718,12 @@ compute_frame_size (HOST_WIDE_INT size)
     {
       HOST_WIDE_INT offset;
 
-      offset = (args_size + cprestore_size + var_size
-		+ gp_reg_size - GET_MODE_SIZE (gpr_mode));
+      /* MIPS16e SAVE and RESTORE instructions require the GP save area
+	 to be aligned at the high end with any padding at the low end,
+	 so do it that way all the time.  */
+      offset = (total_size
+		- MIPS_STACK_ALIGN (fp_reg_size)
+		- GET_MODE_SIZE (gpr_mode));
       cfun->machine->frame.gp_sp_offset = offset;
       cfun->machine->frame.gp_save_offset = offset - total_size;
     }
@@ -6710,8 +6820,6 @@ mips_save_restore_reg (enum machine_mode
 static void
 mips_for_each_saved_reg (HOST_WIDE_INT sp_offset, mips_save_restore_fn fn)
 {
-#define BITSET_P(VALUE, BIT) (((VALUE) & (1L << (BIT))) != 0)
-
   enum machine_mode fpr_mode;
   HOST_WIDE_INT offset;
   int regno;
@@ -6740,7 +6848,6 @@ #define BITSET_P(VALUE, BIT) (((VALUE) &
 	mips_save_restore_reg (fpr_mode, regno, offset, fn);
 	offset -= GET_MODE_SIZE (fpr_mode);
       }
-#undef BITSET_P
 }
 
 /* If we're generating n32 or n64 abicalls, and the current function
@@ -6987,6 +7094,395 @@ mips_save_reg (rtx reg, rtx mem)
     }
 }
 
+/* Return a move between register REGNO and memory location SP + OFFSET.
+   Make the move a load if RESTORE_P, otherwise make it a frame-related
+   store.  */
+
+static rtx
+mips16e_save_restore_reg (bool restore_p, HOST_WIDE_INT offset,
+			  unsigned int regno)
+{
+  rtx reg, mem;
+
+  mem = gen_frame_mem (SImode, plus_constant (stack_pointer_rtx, offset));
+  reg = gen_rtx_REG (SImode, regno);
+  return (restore_p
+	  ? gen_rtx_SET (VOIDmode, reg, mem)
+	  : mips_frame_set (mem, reg));
+}
+
+/* Return RTL for a MIPS16e SAVE or RESTORE instruction; RESTORE_P says which.
+   The instruction must:
+
+     - Allocate or deallocate SIZE bytes in total; SIZE is known
+       to be nonzero.
+
+     - Save or restore as many registers in *MASK_PTR as possible.
+       The instruction saves the first registers at the top of the
+       allocated area, with the other registers below it.
+
+     - Save NARGS argument registers above the allocated area.
+
+   (NARGS is always zero if RESTORE_P.)
+
+   The SAVE and RESTORE instructions cannot save and restore all general
+   registers, so there may be some registers left over for the caller to
+   handle.  Destructively modify *MASK_PTR so that it contains the registers
+   that still need to be saved or restored.  The caller can save these
+   registers in the memory immediately below *OFFSET_PTR, which is a
+   byte offset from the bottom of the allocated stack area.  */
+
+static rtx
+mips16e_build_save_restore (bool restore_p, unsigned int *mask_ptr,
+			    HOST_WIDE_INT *offset_ptr, unsigned int nargs,
+			    HOST_WIDE_INT size)
+{
+  rtx pattern, set;
+  HOST_WIDE_INT offset, top_offset;
+  unsigned int i, regno;
+  int n;
+
+  gcc_assert (cfun->machine->frame.fp_reg_size == 0);
+
+  /* Calculate the number of elements in the PARALLEL.  We need one element
+     for the stack adjustment, one for each argument register save, and one
+     for each additional register move.  */
+  n = 1 + nargs;
+  for (i = 0; i < ARRAY_SIZE (mips16e_save_restore_regs); i++)
+    if (BITSET_P (*mask_ptr, mips16e_save_restore_regs[i]))
+      n++;
+
+  /* Create the final PARALLEL.  */
+  pattern = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (n));
+  n = 0;
+
+  /* Add the stack pointer adjustment.  */
+  set = gen_rtx_SET (VOIDmode, stack_pointer_rtx,
+		     plus_constant (stack_pointer_rtx,
+				    restore_p ? size : -size));
+  RTX_FRAME_RELATED_P (set) = 1;
+  XVECEXP (pattern, 0, n++) = set;
+
+  /* Stack offsets in the PARALLEL are relative to the old stack pointer.  */
+  top_offset = restore_p ? size : 0;
+
+  /* Save the arguments.  */
+  for (i = 0; i < nargs; i++)
+    {
+      offset = top_offset + i * GET_MODE_SIZE (gpr_mode);
+      set = mips16e_save_restore_reg (restore_p, offset, GP_ARG_FIRST + i);
+      XVECEXP (pattern, 0, n++) = set;
+    }
+
+  /* Then fill in the other register moves.  */
+  offset = top_offset;
+  for (i = 0; i < ARRAY_SIZE (mips16e_save_restore_regs); i++)
+    {
+      regno = mips16e_save_restore_regs[i];
+      if (BITSET_P (*mask_ptr, regno))
+	{
+	  offset -= UNITS_PER_WORD;
+	  set = mips16e_save_restore_reg (restore_p, offset, regno);
+	  XVECEXP (pattern, 0, n++) = set;
+	  *mask_ptr &= ~(1 << regno);
+	}
+    }
+
+  /* Tell the caller what offset it should use for the remaining registers.  */
+  *offset_ptr = size + (offset - top_offset) + size;
+
+  gcc_assert (n == XVECLEN (pattern, 0));
+
+  return pattern;
+}
+
+/* PATTERN is a PARALLEL whose first element adds ADJUST to the stack
+   pointer.  Return true if PATTERN matches the kind of instruction
+   generated by mips16e_build_save_restore.  If INFO is nonnull,
+   initialize it when returning true.  */
+
+bool
+mips16e_save_restore_pattern_p (rtx pattern, HOST_WIDE_INT adjust,
+				struct mips16e_save_restore_info *info)
+{
+  unsigned int i, nargs, mask;
+  HOST_WIDE_INT top_offset, save_offset, offset, extra;
+  rtx set, reg, mem, base;
+  int n;
+
+  if (!GENERATE_MIPS16E_SAVE_RESTORE)
+    return false;
+
+  /* Stack offsets in the PARALLEL are relative to the old stack pointer.  */
+  top_offset = adjust > 0 ? adjust : 0;
+
+  /* Interpret all other members of the PARALLEL.  */
+  save_offset = top_offset - GET_MODE_SIZE (gpr_mode);
+  mask = 0;
+  nargs = 0;
+  i = 0;
+  for (n = 1; n < XVECLEN (pattern, 0); n++)
+    {
+      /* Check that we have a SET.  */
+      set = XVECEXP (pattern, 0, n);
+      if (GET_CODE (set) != SET)
+	return false;
+
+      /* Check that the SET is a load (if restoring) or a store
+	 (if saving).  */
+      mem = adjust > 0 ? SET_SRC (set) : SET_DEST (set);
+      if (!MEM_P (mem))
+	return false;
+
+      /* Check that the address is the sum of the stack pointer and a
+	 possibly-zero constant offset.  */
+      mips_split_plus (XEXP (mem, 0), &base, &offset);
+      if (base != stack_pointer_rtx)
+	return false;
+
+      /* Check that SET's other operand is a register.  */
+      reg = adjust > 0 ? SET_DEST (set) : SET_SRC (set);
+      if (!REG_P (reg))
+	return false;
+
+      /* Check for argument saves.  */
+      if (offset == top_offset + nargs * GET_MODE_SIZE (gpr_mode)
+	  && REGNO (reg) == GP_ARG_FIRST + nargs)
+	nargs++;
+      else if (offset == save_offset)
+	{
+	  while (mips16e_save_restore_regs[i++] != REGNO (reg))
+	    if (i == ARRAY_SIZE (mips16e_save_restore_regs))
+	      return false;
+
+	  mask |= 1 << REGNO (reg);
+	  save_offset -= GET_MODE_SIZE (gpr_mode);
+	}
+      else
+	return false;
+    }
+
+  /* Check that the restrictions on register ranges are met.  */
+  extra = 0;
+  mips16e_mask_registers (&mask, mips16e_s2_s8_regs,
+			  ARRAY_SIZE (mips16e_s2_s8_regs), &extra);
+  mips16e_mask_registers (&mask, mips16e_a0_a3_regs,
+			  ARRAY_SIZE (mips16e_a0_a3_regs), &extra);
+  if (extra != 0)
+    return false;
+
+  /* Pass back information, if requested.  */
+  if (info)
+    {
+      info->nargs = nargs;
+      info->mask = mask;
+      info->size = (adjust > 0 ? adjust : -adjust);
+    }
+
+  return true;
+}
+
+/* Add a MIPS16e SAVE or RESTORE register-range argument to string S
+   for the register range [MIN_REG, MAX_REG].  Return a pointer to
+   the null terminator.  */
+
+static char *
+mips16e_add_register_range (char *s, unsigned int min_reg,
+			    unsigned int max_reg)
+{
+  if (min_reg != max_reg)
+    s += sprintf (s, ",%s-%s", reg_names[min_reg], reg_names[max_reg]);
+  else
+    s += sprintf (s, ",%s", reg_names[min_reg]);
+  return s;
+}
+
+/* Return the assembly instruction for a MIPS16e SAVE or RESTORE instruction.
+   PATTERN and ADJUST are as for mips16e_save_restore_pattern_p.  */
+
+const char *
+mips16e_output_save_restore (rtx pattern, HOST_WIDE_INT adjust)
+{
+  static char buffer[300];
+
+  struct mips16e_save_restore_info info;
+  unsigned int i, end;
+  char *s;
+
+  /* Parse the pattern.  */
+  if (!mips16e_save_restore_pattern_p (pattern, adjust, &info))
+    gcc_unreachable ();
+
+  /* Add the mnemonic.  */
+  s = strcpy (buffer, adjust > 0 ? "restore\t" : "save\t");
+  s += strlen (s);
+
+  /* Save the arguments.  */
+  if (info.nargs > 1)
+    s += sprintf (s, "%s-%s,", reg_names[GP_ARG_FIRST],
+		  reg_names[GP_ARG_FIRST + info.nargs - 1]);
+  else if (info.nargs == 1)
+    s += sprintf (s, "%s,", reg_names[GP_ARG_FIRST]);
+
+  /* Emit the amount of stack space to allocate or deallocate.  */
+  s += sprintf (s, "%d", (int) info.size);
+
+  /* Save or restore $16.  */
+  if (BITSET_P (info.mask, 16))
+    s += sprintf (s, ",%s", reg_names[GP_REG_FIRST + 16]);
+
+  /* Save or restore $17.  */
+  if (BITSET_P (info.mask, 17))
+    s += sprintf (s, ",%s", reg_names[GP_REG_FIRST + 17]);
+
+  /* Save or restore registers in the range $s2...$s8, which
+     mips16e_s2_s8_regs lists in decreasing order.  Note that this
+     is a software register range; the hardware registers are not
+     numbered consecutively.  */
+  end = ARRAY_SIZE (mips16e_s2_s8_regs);
+  i = mips16e_find_first_register (info.mask, mips16e_s2_s8_regs, end);
+  if (i < end)
+    s = mips16e_add_register_range (s, mips16e_s2_s8_regs[end - 1],
+				    mips16e_s2_s8_regs[i]);
+
+  /* Save or restore registers in the range $a0...$a3.  */
+  end = ARRAY_SIZE (mips16e_a0_a3_regs);
+  i = mips16e_find_first_register (info.mask, mips16e_a0_a3_regs, end);
+  if (i < end)
+    s = mips16e_add_register_range (s, mips16e_a0_a3_regs[i],
+				    mips16e_a0_a3_regs[end - 1]);
+
+  /* Save or restore $31.  */
+  if (BITSET_P (info.mask, 31))
+    s += sprintf (s, ",%s", reg_names[GP_REG_FIRST + 31]);
+
+  return buffer;
+}
+
+/* Return a simplified form of X using the register values in REG_VALUES.
+   REG_VALUES[R] is the last value assigned to hard register R, or null
+   if R has not been modified.
+
+   This function is rather limited, but is good enough for our purposes.  */
+
+static rtx
+mips16e_collect_propagate_value (rtx x, rtx *reg_values)
+{
+  rtx x0, x1;
+
+  x = avoid_constant_pool_reference (x);
+
+  if (UNARY_P (x))
+    {
+      x0 = mips16e_collect_propagate_value (XEXP (x, 0), reg_values);
+      return simplify_gen_unary (GET_CODE (x), GET_MODE (x),
+				 x0, GET_MODE (XEXP (x, 0)));
+    }
+
+  if (ARITHMETIC_P (x))
+    {
+      x0 = mips16e_collect_propagate_value (XEXP (x, 0), reg_values);
+      x1 = mips16e_collect_propagate_value (XEXP (x, 1), reg_values);
+      return simplify_gen_binary (GET_CODE (x), GET_MODE (x), x0, x1);
+    }
+
+  if (REG_P (x)
+      && reg_values[REGNO (x)]
+      && !rtx_unstable_p (reg_values[REGNO (x)]))
+    return reg_values[REGNO (x)];
+
+  return x;
+}
+
+/* Return true if (set DEST SRC) stores an argument register into its
+   caller-allocated save slot.  If the register is not included in
+   [GP_ARG_FIRST, GP_ARG_LAST + *NARGS_PTR), destructively modify
+   *NARGS_PTR such that this condition holds.  REG_VALUES is as for
+   mips16e_collect_propagate_value.  */
+
+static bool
+mips16e_collect_argument_save (rtx dest, rtx src, rtx *reg_values,
+			       unsigned int *nargs_ptr)
+{
+  unsigned int argno, regno;
+  HOST_WIDE_INT offset, required_offset;
+  rtx addr, base;
+
+  /* Check that this is a word-mode store.  */
+  if (!MEM_P (dest) || !REG_P (src) || GET_MODE (dest) != word_mode)
+    return false;
+
+  /* Check that the register being saved is an unmodified argument
+     register.  */
+  regno = REGNO (src);
+  if (regno < GP_ARG_FIRST || regno > GP_ARG_LAST || reg_values[regno])
+    return false;
+  argno = regno - GP_ARG_FIRST;
+
+  /* Check whether the address is an appropriate stack pointer or
+     frame pointer access.  The frame pointer is offset from the
+     stack pointer by the size of the outgoing arguments.  */
+  addr = mips16e_collect_propagate_value (XEXP (dest, 0), reg_values);
+  mips_split_plus (addr, &base, &offset);
+  required_offset = cfun->machine->frame.total_size + argno * UNITS_PER_WORD;
+  if (base == hard_frame_pointer_rtx)
+    required_offset -= cfun->machine->frame.args_size;
+  else if (base != stack_pointer_rtx)
+    return false;
+  if (offset != required_offset)
+    return false;
+
+  /* Make sure that *NARGS_PTR is big enough.  */
+  if (*nargs_ptr <= argno)
+    *nargs_ptr = argno + 1;
+
+  return true;
+}
+
+/* A subroutine of mips_expand_prologue, called only when generating
+   MIPS16e SAVE instructions.  Search the start of the function for any
+   instructions that save argument registers into their caller-allocated
+   save slots.  Delete such instructions and return a value N such that
+   saving [GP_ARG_FIRST, GP_ARG_FIRST + N) would make all the deleted
+   instructions redundant.  */
+
+static unsigned int
+mips16e_collect_argument_saves (void)
+{
+  rtx reg_values[FIRST_PSEUDO_REGISTER];
+  rtx insn, next, set, dest, src;
+  unsigned int nargs;
+
+  push_topmost_sequence ();
+  nargs = 0;
+  memset (reg_values, 0, sizeof (reg_values));
+  for (insn = get_insns (); insn; insn = next)
+    {
+      next = NEXT_INSN (insn);
+      if (NOTE_P (insn))
+	continue;
+
+      if (!INSN_P (insn))
+	break;
+
+      set = PATTERN (insn);
+      if (GET_CODE (set) != SET)
+	break;
+
+      dest = SET_DEST (set);
+      src = SET_SRC (set);
+      if (mips16e_collect_argument_save (dest, src, reg_values, &nargs))
+	delete_insn (insn);
+      else if (REG_P (dest) && GET_MODE (dest) == word_mode)
+	reg_values[REGNO (dest)]
+	  = mips16e_collect_propagate_value (src, reg_values);
+      else
+	break;
+    }
+  pop_topmost_sequence ();
+
+  return nargs;
+}
 
 /* Expand the prologue into a bunch of separate insns.  */
 
@@ -6994,6 +7490,8 @@ mips_save_reg (rtx reg, rtx mem)
 mips_expand_prologue (void)
 {
   HOST_WIDE_INT size;
+  unsigned int nargs;
+  rtx insn;
 
   if (cfun->machine->global_pointer > 0)
     SET_REGNO (pic_offset_table_rtx, cfun->machine->global_pointer);
@@ -7008,11 +7506,39 @@ mips_expand_prologue (void)
       HOST_WIDE_INT step1;
 
       step1 = MIN (size, MIPS_MAX_FIRST_STACK_STEP);
-      RTX_FRAME_RELATED_P (emit_insn (gen_add3_insn (stack_pointer_rtx,
-						     stack_pointer_rtx,
-						     GEN_INT (-step1)))) = 1;
-      size -= step1;
-      mips_for_each_saved_reg (size, mips_save_reg);
+
+      if (GENERATE_MIPS16E_SAVE_RESTORE)
+ 	{
+ 	  HOST_WIDE_INT offset;
+ 	  unsigned int mask, regno;
+
+	  /* Try to merge argument stores into the save instruction.  */
+	  nargs = mips16e_collect_argument_saves ();
+
+	  /* Build the save instruction.  */
+	  mask = cfun->machine->frame.mask;
+	  insn = mips16e_build_save_restore (false, &mask, &offset,
+					     nargs, step1);
+	  RTX_FRAME_RELATED_P (emit_insn (insn)) = 1;
+ 	  size -= step1;
+
+ 	  /* Check if we need to save other registers.  */
+ 	  for (regno = GP_REG_FIRST; regno < GP_REG_LAST; regno++)
+ 	    if (BITSET_P (mask, regno - GP_REG_FIRST))
+ 	      {
+		offset -= GET_MODE_SIZE (gpr_mode);
+		mips_save_restore_reg (gpr_mode, regno, offset, mips_save_reg);
+ 	      }
+ 	}
+      else
+ 	{
+	  insn = gen_add3_insn (stack_pointer_rtx,
+				stack_pointer_rtx,
+				GEN_INT (-step1));
+	  RTX_FRAME_RELATED_P (emit_insn (insn)) = 1;
+	  size -= step1;
+	  mips_for_each_saved_reg (size, mips_save_reg);
+	}
     }
 
   /* Allocate the rest of the frame.  */
@@ -7252,15 +7778,40 @@ mips_expand_epilogue (int sibcall_p)
   if (TARGET_CALL_SAVED_GP && !TARGET_EXPLICIT_RELOCS)
     emit_insn (gen_blockage ());
 
-  /* Restore the registers.  */
-  mips_for_each_saved_reg (cfun->machine->frame.total_size - step2,
-			   mips_restore_reg);
-
-  /* Deallocate the final bit of the frame.  */
-  if (step2 > 0)
-    emit_insn (gen_add3_insn (stack_pointer_rtx,
-			      stack_pointer_rtx,
-			      GEN_INT (step2)));
+  if (GENERATE_MIPS16E_SAVE_RESTORE && cfun->machine->frame.mask != 0)
+    {
+      unsigned int regno, mask;
+      HOST_WIDE_INT offset;
+      rtx restore;
+
+      /* Generate the restore instruction.  */
+      mask = cfun->machine->frame.mask;
+      restore = mips16e_build_save_restore (true, &mask, &offset, 0, step2);
+
+      /* Restore any other registers manually.  */
+      for (regno = GP_REG_FIRST; regno < GP_REG_LAST; regno++)
+ 	if (BITSET_P (mask, regno - GP_REG_FIRST))
+ 	  {
+ 	    offset -= GET_MODE_SIZE (gpr_mode);
+ 	    mips_save_restore_reg (gpr_mode, regno, offset, mips_restore_reg);
+ 	  }
+
+      /* Restore the remaining registers and deallocate the final bit
+	 of the frame.  */
+      emit_insn (restore);
+    }
+  else
+    {
+      /* Restore the registers.  */
+      mips_for_each_saved_reg (cfun->machine->frame.total_size - step2,
+			       mips_restore_reg);
+
+      /* Deallocate the final bit of the frame.  */
+      if (step2 > 0)
+	emit_insn (gen_add3_insn (stack_pointer_rtx,
+				  stack_pointer_rtx,
+				  GEN_INT (step2)));
+    }
 
   /* Add in the __builtin_eh_return stack adjustment.  We need to
      use a temporary in mips16 code.  */
@@ -7282,8 +7833,11 @@ mips_expand_epilogue (int sibcall_p)
 
   if (!sibcall_p)
     {
-      /* The mips16 loads the return address into $7, not $31.  */
-      if (TARGET_MIPS16 && (cfun->machine->frame.mask & RA_MASK) != 0)
+      /* When generating MIPS16 code, the normal mips_for_each_saved_reg
+	 path will restore the return address into $7 rather than $31.  */
+      if (TARGET_MIPS16
+	  && !GENERATE_MIPS16E_SAVE_RESTORE
+	  && (cfun->machine->frame.mask & RA_MASK) != 0)
 	emit_jump_insn (gen_return_internal (gen_rtx_REG (Pmode,
 							  GP_REG_FIRST + 7)));
       else
Index: gcc/config/mips/mips.md
===================================================================
--- gcc/config/mips/mips.md	2007-07-01 04:19:23.000000000 -0700
+++ gcc/config/mips/mips.md	2007-07-02 03:30:27.000000000 -0700
@@ -5583,7 +5583,26 @@ (define_split
   "reload_completed"
   [(match_dup 0)]
   { operands[0] = mips_rewrite_small_data (operands[0]); })
-
+
+;;
+;;  ....................
+;;
+;;	MIPS16e Save/Restore
+;;
+;;  ....................
+;;
+
+(define_insn "*mips16e_save_restore"
+  [(match_parallel 0 ""
+       [(set (match_operand:SI 1 "register_operand")
+	     (plus:SI (match_dup 1)
+		      (match_operand:SI 2 "const_int_operand")))])]
+  "operands[1] == stack_pointer_rtx
+   && mips16e_save_restore_pattern_p (operands[0], INTVAL (operands[2]), NULL)"
+  { return mips16e_output_save_restore (operands[0], INTVAL (operands[2])); }
+  [(set_attr "type" "arith")
+   (set_attr "extended_mips16" "yes")])
+
 ; Thread-Local Storage
 
 ; The TLS base pointer is accessed via "rdhwr $v1, $29".  No current
Index: gcc/testsuite/gcc.target/mips/save-restore-1.c
===================================================================
--- /dev/null	2007-05-17 15:58:34.028651136 -0700
+++ gcc/testsuite/gcc.target/mips/save-restore-1.c	2007-07-02 05:23:10.000000000 -0700
@@ -0,0 +1,19 @@
+/* Check that we can use the save instruction to save varargs.  */
+/* { dg-mips-options "-mips32r2 -mgp32 -mips16 -O2" } */
+#include <stdarg.h>
+
+int bar (int, va_list ap);
+
+int
+foo (int n, ...)
+{
+  va_list ap;
+  int i;
+
+  va_start (ap, n);
+  i = bar (n, ap);
+  va_end (ap);
+  return i + 1;
+}
+/* { dg-final { scan-assembler "\tsave\t\\\$4-\\\$7" } } */
+/* { dg-final { scan-assembler "\trestore\t" } } */
Index: gcc/testsuite/gcc.target/mips/save-restore-2.c
===================================================================
--- /dev/null	2007-05-17 15:58:34.028651136 -0700
+++ gcc/testsuite/gcc.target/mips/save-restore-2.c	2007-07-02 05:23:17.000000000 -0700
@@ -0,0 +1,14 @@
+/* Check that we can use the save instruction to save spilled arguments.  */
+/* { dg-mips-options "-mips32r2 -mgp32 -mips16 -O2" } */
+void
+foo (int *a, int b, int c)
+{
+  asm volatile ("" ::: "$2", "$3", "$4", "$5", "$6", "$7", "$8",
+		"$9", "$10", "$11", "$12", "$13", "$14", "$15", "$16",
+		"$17", "$18", "$19", "$20", "$21", "$22", "$23", "$24",
+		"$25", "$30", "memory");
+  a[b] = 1;
+  a[c] = 1;
+}
+/* { dg-final { scan-assembler "\tsave\t\\\$4-\\\$6," } } */
+/* { dg-final { scan-assembler "\trestore\t" } } */
Index: gcc/testsuite/gcc.target/mips/save-restore-3.c
===================================================================
--- /dev/null	2007-05-17 15:58:34.028651136 -0700
+++ gcc/testsuite/gcc.target/mips/save-restore-3.c	2007-07-02 05:23:24.000000000 -0700
@@ -0,0 +1,19 @@
+/* Check that we can use the save instruction to save spilled arguments
+   when the argument save area is out of range of a direct load or store.  */
+/* { dg-mips-options "-mips32r2 -mgp32 -mips16 -O2" } */
+void bar (int *);
+
+void
+foo (int *a, int b, int c)
+{
+  int x[0x4000];
+  asm volatile ("" ::: "$2", "$3", "$4", "$5", "$6", "$7", "$8",
+		"$9", "$10", "$11", "$12", "$13", "$14", "$15", "$16",
+		"$17", "$18", "$19", "$20", "$21", "$22", "$23", "$24",
+		"$25", "$30", "memory");
+  bar (x);
+  a[b] = 1;
+  a[c] = 1;
+}
+/* { dg-final { scan-assembler "\tsave\t\\\$4-\\\$6," } } */
+/* { dg-final { scan-assembler "\trestore\t" } } */
Index: gcc/testsuite/gcc.target/mips/save-restore-4.c
===================================================================
--- /dev/null	2007-05-17 15:58:34.028651136 -0700
+++ gcc/testsuite/gcc.target/mips/save-restore-4.c	2007-07-02 05:26:28.000000000 -0700
@@ -0,0 +1,11 @@
+/* Check that we can use the save instruction to save $16, $17 and $31.  */
+/* { dg-mips-options "-mips32r2 -mgp32 -mips16 -O2" } */
+void bar (void);
+void
+foo (void)
+{
+  bar ();
+  asm volatile ("" ::: "$16", "$17");
+}
+/* { dg-final { scan-assembler "\tsave\t\[0-9\]*,\\\$16,\\\$17,\\\$31" } } */
+/* { dg-final { scan-assembler "\trestore\t\[0-9\]*,\\\$16,\\\$17,\\\$31" } } */



More information about the Gcc-patches mailing list