[PATCH] New MIPS interrupt handler patch

Fu, Chao-Ying fu@mips.com
Thu Mar 12 22:17:00 GMT 2009


Hi Richard,

  Thanks for your review!  It's very helpful.
I revised the patch as attached.  One thing is that in order to
put COP0 EPC/Status to/from the stack with correct frame info,
I don't use mips_save_restore_reg, but create instructions and 
use mips_set_frame_expr directly.  Thanks!

Regards,
Chao-ying

gcc/ChangeLog
2009-03-12  Chao-ying Fu  <fu@mips.com>
		James Grosbach <james.grosbach@microchip.com>

	* config/mips/mips.c (mips_frame_info): Add acc_mask, num_acc, num_cop0_regs,
	acc_save_offset, cop0_save_offset, acc_sp_offset, cop0_sp_offset.
	(machine_function): Add interrupt_handler_p, use_shadow_register_set_p,
	keep_interrupts_masked_p, use_debug_exception_return_p.
	(mips_attribute_table): Add interrupt, use_shadow_register_set,
	keep_interrupts_masked, use_debug_exception_return.
	(mips_interrupt_type_p, mips_use_shadow_register_set_p,
	mips_keep_interrupts_masked_p, mips_use_debug_exception_return_p):
	New functions.
	(mips_function_ok_for_sibcall): Return false for interrupt handlers.
	(mips_print_operand): Process COP0 register to print $0 .. $31 correctly
	for GAS to process.
	(mips_interrupt_extra_call_saved_reg_p): New function.
	(mips_cfun_call_saved_reg_p): For interrupt handlers, we need to check
	extra registers.
	(mips_cfun_might_clobber_call_saved_reg_p): Likewise.
	(mips_compute_frame_info): Add supports for interrupt context.
	(mips_kernel_reg_p): A new for_each_rtx callback.
	(mips_expand_prologue): Support interrupt handlers.
	(mips_expand_epilogue): Support interrupt handlers.
	(mips_can_use_return_insn): Return false for interrupt handlers.
	(mips_epilogue_uses): New function.

	* config/mips/mips.md (UNSPEC_ERET, UNSPEC_DERET,
	UNSPEC_DI, UNSPEC_EHB, UNSPEC_RDPGPR, UNSPEC_COP0): New UNSPEC.
	(mips_eret, mips_deret, mips_di, mips_ehb, mips_rdpgpr,
	cop0_move): New instructions.
	
	* config/mips/mips-protos.h (mips_epilogue_uses): Declare.

	* config/mips/mips.h (K0_REG_NUM, K1_REG_NUM, KERNEL_REG_P):
	New defines.
	(COP0_STATUS_REG_NUM, COP0_CAUSE_REG_NUM, COP0_EPC_REG_NUM):
	New defines.
	(CAUSE_IPL, SR_IPL, SR_EXL, SR_IE): New defines.
	(MIPS_PROLOGUE_TEMP_REGNUM, MIPS_EPILOGUE_TEMP_REGNUM): For
	interrupt handlers, we use K0 as the temporary register.
	(EPILOGUE_USES): Change to a function call.

	* config/mips/sde.h (MIPS_EPILOGUE_TEMP_REGNUM): For
	interrupt handlers, we use K0 as the temporary register.
	
	* doc/extend.texi (Function Attributes): Document interrupt,
	use_shadow_register_set, keep_interrupts_masked,
	use_debug_exception_return for MIPS attributes.

gcc/testsuite/ChangeLog
2009-03-12  Chao-ying Fu  <fu@mips.com>

        * gcc.target/mips/interrupt_handler.c: New test.

Index: gcc4x/gcc/gcc/config/mips/mips.c
===================================================================
--- gcc4x.orig/gcc/gcc/config/mips/mips.c	2009-03-11 14:25:12.000000000 -0700
+++ gcc4x/gcc/gcc/config/mips/mips.c	2009-03-12 14:31:10.869099000 -0700
@@ -261,18 +261,28 @@ struct mips_frame_info GTY(()) {
   /* Likewise FPR X.  */
   unsigned int fmask;
 
-  /* The number of GPRs and FPRs saved.  */
+  /* Likewise accumulator X.  */
+  unsigned int acc_mask;
+
+  /* The number of GPRs, FPRs, accumulators and COP0 registers saved.  */
   unsigned int num_gp;
   unsigned int num_fp;
+  unsigned int num_acc;
+  unsigned int num_cop0_regs;
 
-  /* The offset of the topmost GPR and FPR save slots from the top of
-     the frame, or zero if no such slots are needed.  */
+  /* The offset of the topmost GPR, FPR, accumulator and COP0-register
+     save slots from the top of the frame, or zero if no such slots are
+     needed.  */
   HOST_WIDE_INT gp_save_offset;
   HOST_WIDE_INT fp_save_offset;
+  HOST_WIDE_INT acc_save_offset;
+  HOST_WIDE_INT cop0_save_offset;
 
   /* Likewise, but giving offsets from the bottom of the frame.  */
   HOST_WIDE_INT gp_sp_offset;
   HOST_WIDE_INT fp_sp_offset;
+  HOST_WIDE_INT acc_sp_offset;
+  HOST_WIDE_INT cop0_sp_offset;
 
   /* The offset of arg_pointer_rtx from frame_pointer_rtx.  */
   HOST_WIDE_INT arg_pointer_offset;
@@ -310,6 +320,18 @@ struct machine_function GTY(()) {
   /* True if we have emitted an instruction to initialize
      mips16_gp_pseudo_rtx.  */
   bool initialized_mips16_gp_pseudo_p;
+
+  /* True if this is an interrupt handler.  */
+  bool interrupt_handler_p;
+
+  /* True if this is an interrupt handler that uses shadow registers.  */
+  bool use_shadow_register_set_p;
+
+  /* True if we want to keep interrupts masked in interrupt handlers.  */
+  bool keep_interrupts_masked_p;
+
+  /* True if we want to use debug exception return in interrupt handlers.  */
+  bool use_debug_exception_return_p;
 };
 
 /* Information about a single argument.  */
@@ -554,6 +576,11 @@ const struct attribute_spec mips_attribu
      code generation but don't carry other semantics.  */
   { "mips16", 	   0, 0, true,  false, false, NULL },
   { "nomips16",    0, 0, true,  false, false, NULL },
+  /* Allow functions to be specified as interrupt handlers */
+  { "interrupt",   0, 0, false, true,  true, NULL },
+  { "use_shadow_register_set",	0, 0, false, true,  true, NULL },
+  { "keep_interrupts_masked",	0, 0, false, true,  true, NULL },
+  { "use_debug_exception_return", 0, 0, false, true,  true, NULL },
   { NULL,	   0, 0, false, false, false, NULL }
 };
 

@@ -1172,6 +1199,43 @@ mips_nomips16_decl_p (const_tree decl)
   return lookup_attribute ("nomips16", DECL_ATTRIBUTES (decl)) != NULL;
 }
 
+/* Check if the interrupt attribute is set for a function.  */
+
+static bool
+mips_interrupt_type_p (tree type)
+{
+  return lookup_attribute ("interrupt", TYPE_ATTRIBUTES (type)) != NULL;
+}
+
+/* Check if the attribute to use shadow register set is set for a function.  */
+
+static bool
+mips_use_shadow_register_set_p (tree type)
+{
+  return lookup_attribute ("use_shadow_register_set", TYPE_ATTRIBUTES (type))
+	 != NULL;
+}
+
+/* Check if the attribute to keep interrupts masked is set for a function.  */
+
+static bool
+mips_keep_interrupts_masked_p (tree type)
+{
+  return lookup_attribute ("keep_interrupts_masked", TYPE_ATTRIBUTES (type))
+	 != NULL;
+}
+
+/* Check if the attribute to use debug exception return is set for
+   a function.  */
+
+static bool
+mips_use_debug_exception_return_p (tree type)
+{
+  return lookup_attribute ("use_debug_exception_return",
+			   TYPE_ATTRIBUTES (type))
+         != NULL;
+}
+
 /* Return true if function DECL is a MIPS16 function.  Return the ambient
    setting if DECL is null.  */
 
@@ -6188,6 +6252,11 @@ mips_function_ok_for_sibcall (tree decl,
   if (!TARGET_SIBCALLS)
     return false;
 
+  /* Interrupt handlers need special epilogue code and therefore can't
+     use sibcalls.  */
+  if (mips_interrupt_type_p (TREE_TYPE (current_function_decl)))
+    return false;
+
   /* We can't do a sibcall if the called function is a MIPS16 function
      because there is no direct "jx" instruction equivalent to "jalx" to
      switch the ISA mode.  We only care about cases where the sibling
@@ -7229,7 +7298,11 @@ mips_print_operand (FILE *file, rtx op, 
 		|| (letter == 'L' && TARGET_BIG_ENDIAN)
 		|| letter == 'D')
 	      regno++;
-	    fprintf (file, "%s", reg_names[regno]);
+	    /* We need to print $0 .. $31 for COP0 registers.  */
+	    if (COP0_REG_P (regno))
+	      fprintf (file, "$%s", &reg_names[regno][4]);
+	    else
+	      fprintf (file, "%s", reg_names[regno]);
 	  }
 	  break;
 
@@ -8436,12 +8509,52 @@ mips_global_pointer (void)
   return GLOBAL_POINTER_REGNUM;
 }
 
+/* Return true if REGNO is a register that is ordinarily call-clobbered
+   but must nevertheless be preserved by an interrupt handler.  */
+
+static bool
+mips_interrupt_extra_call_saved_reg_p (unsigned int regno)
+{
+  if (MD_REG_P (regno))
+    return true;
+
+  if (TARGET_DSP && DSP_ACC_REG_P (regno))
+    return true;
+
+  if (GP_REG_P (regno) && !cfun->machine->use_shadow_register_set_p)
+    {
+      /* $0 is hard-wired.  */
+      if (regno == GP_REG_FIRST)
+	return false;
+
+      /* The interrupt handler can treat kernel registers as
+	 scratch registers.  */
+      if (KERNEL_REG_P (regno))
+	return false;
+
+      /* The function will return the stack pointer to its original value
+	 anyway.  */
+      if (regno == STACK_POINTER_REGNUM)
+	return false;
+
+      /* Otherwise, all GPRs are call-clobbered.  */
+      return true;
+    }
+
+  return false;
+}
+
 /* Return true if the current function should treat register REGNO
    as call-saved.  */
 
 static bool
 mips_cfun_call_saved_reg_p (unsigned int regno)
 {
+  /* For interrupt handlers, we need to save extra registers.  */
+  if (cfun->machine->interrupt_handler_p
+      && mips_interrupt_extra_call_saved_reg_p (regno))
+    return true;
+
   /* call_insns preserve $28 unless they explicitly say otherwise,
      so call_really_used_regs[] treats $28 as call-saved.  However,
      we want the ABI property rather than the default call_insn
@@ -8490,6 +8603,12 @@ mips_cfun_might_clobber_call_saved_reg_p
   if (regno == GP_REG_FIRST + 31 && mips16_cfun_returns_in_fpr_p ())
     return true;
 
+  /* For non-leaf interrupt handlers, we need to save extra registers.  */
+  if (cfun->machine->interrupt_handler_p
+      && !current_function_is_leaf
+      && mips_interrupt_extra_call_saved_reg_p (regno))
+    return true;
+
   return false;
 }
 
@@ -8545,6 +8664,14 @@ mips_save_reg_p (unsigned int regno)
       C |  callee-allocated save area   |
 	|  for register varargs         |
 	|                               |
+	+-------------------------------+ <-- frame_pointer_rtx
+	|                               |       + cop0_sp_offset
+	|  COP0 reg save area           |	+ UNITS_PER_WORD
+	|                               |
+	+-------------------------------+ <-- frame_pointer_rtx + acc_sp_offset
+	|                               |       + UNITS_PER_WORD
+	|  accumulator save area        |
+	|                               |
 	+-------------------------------+ <-- frame_pointer_rtx + fp_sp_offset
 	|                               |       + UNITS_PER_HWFPVALUE
 	|  FPR save area                |
@@ -8588,6 +8715,29 @@ mips_compute_frame_info (void)
   HOST_WIDE_INT offset, size;
   unsigned int regno, i;
 
+  /* Set interrupt handler attributes, only when we use soft-float
+     and MIPS32R2.  */
+  if (mips_interrupt_type_p (TREE_TYPE (current_function_decl)))
+    {
+      if (!ISA_MIPS32R2)
+	error ("the %<interrupt%> attribute requires a MIPS32r2 processor");
+      else if (TARGET_HARD_FLOAT)
+	error ("the %<interrupt%> attribute requires %<-msoft-float%>");
+      else if (TARGET_MIPS16)
+	error ("interrupt handlers cannot be MIPS16 functions");
+      else
+	{
+	  cfun->machine->interrupt_handler_p = true;
+	  cfun->machine->use_shadow_register_set_p =
+	    mips_use_shadow_register_set_p (TREE_TYPE (current_function_decl));
+	  cfun->machine->keep_interrupts_masked_p =
+	    mips_keep_interrupts_masked_p (TREE_TYPE (current_function_decl));
+	  cfun->machine->use_debug_exception_return_p =
+	    mips_use_debug_exception_return_p (TREE_TYPE
+					       (current_function_decl));
+	}
+    }
+
   frame = &cfun->machine->frame;
   memset (frame, 0, sizeof (*frame));
   size = get_frame_size ();
@@ -8673,6 +8823,49 @@ mips_compute_frame_info (void)
       frame->fp_sp_offset = offset - UNITS_PER_HWFPVALUE;
     }
 
+  /* Add in space for the interrupt context information.  */
+  if (cfun->machine->interrupt_handler_p)
+    {
+      /* Check HI/LO.  */
+      if (mips_save_reg_p (LO_REGNUM) || mips_save_reg_p (HI_REGNUM))
+	{
+	  frame->num_acc++;
+	  frame->acc_mask |= (1 << 0);
+	}
+
+      /* Check accumulators 1, 2, 3.  */
+      for (i = 1; i < 4; i++)
+	{
+	  if (mips_save_reg_p (DSP_ACC_REG_FIRST + ((i - 1) << 1))
+	      || mips_save_reg_p (DSP_ACC_REG_FIRST + ((i - 1) << 1) + 1))
+	    {
+	      frame->num_acc++;
+	      frame->acc_mask |= (1 << i);
+	    }
+	}
+
+      /* All interrupt context functions need space to preserve STATUS.  */
+      frame->num_cop0_regs++;
+
+      /* If we don't keep interrupts masked, we need to save EPC.  */
+      if (!cfun->machine->keep_interrupts_masked_p)
+	frame->num_cop0_regs++;
+    }
+
+  /* Move above the accumulators save area.  */
+  if (frame->num_acc > 0)
+    {
+      offset += frame->num_acc * UNITS_PER_WORD;
+      frame->acc_sp_offset = offset - UNITS_PER_WORD;
+    }
+
+  /* Move above the COP0 register save area.  */
+  if (frame->num_cop0_regs > 0)
+    {
+      offset += frame->num_cop0_regs * UNITS_PER_WORD;
+      frame->cop0_sp_offset = offset - UNITS_PER_WORD;
+    }
+
   /* Move above the callee-allocated varargs save area.  */
   offset += MIPS_STACK_ALIGN (cfun->machine->varargs_size);
   frame->arg_pointer_offset = offset;
@@ -8686,6 +8879,10 @@ mips_compute_frame_info (void)
     frame->gp_save_offset = frame->gp_sp_offset - offset;
   if (frame->fp_sp_offset > 0)
     frame->fp_save_offset = frame->fp_sp_offset - offset;
+  if (frame->acc_sp_offset > 0)
+    frame->acc_save_offset = frame->acc_sp_offset - offset;
+  if (frame->num_cop0_regs > 0)
+    frame->cop0_save_offset = frame->cop0_sp_offset - offset;
 
   /* MIPS16 code offsets the frame pointer by the size of the outgoing
      arguments.  This tends to increase the chances of using unextended
@@ -9075,13 +9272,25 @@ mips_save_reg (rtx reg, rtx mem)
     }
   else
     {
-      if (TARGET_MIPS16
-	  && REGNO (reg) != GP_REG_FIRST + 31
-	  && !M16_REG_P (REGNO (reg)))
+      if (REGNO (reg) == HI_REGNUM)
+	{
+	  if (TARGET_64BIT)
+	    emit_insn (gen_mfhidi_ti (MIPS_PROLOGUE_TEMP (DImode),
+				      gen_rtx_REG (TImode, MD_REG_FIRST)));
+	  else
+	    emit_insn (gen_mfhisi_di (MIPS_PROLOGUE_TEMP (SImode),
+				      gen_rtx_REG (DImode, MD_REG_FIRST)));
+	  mips_emit_move (mem, MIPS_PROLOGUE_TEMP (GET_MODE (reg)));
+	}
+      else if ((TARGET_MIPS16
+		&& REGNO (reg) != GP_REG_FIRST + 31
+		&& !M16_REG_P (REGNO (reg)))
+	       || ACC_REG_P (REGNO (reg)))
 	{
 	  /* Save a non-MIPS16 register by moving it through a temporary.
 	     We don't need to do this for $31 since there's a special
-	     instruction for it.  */
+	     instruction for it.
+	     Save accumulators by moving it through a temporary.  */
 	  mips_emit_move (MIPS_PROLOGUE_TEMP (GET_MODE (reg)), reg);
 	  mips_emit_move (mem, MIPS_PROLOGUE_TEMP (GET_MODE (reg)));
 	}
@@ -9153,6 +9362,14 @@ mips_emit_loadgp (void)
     emit_insn (gen_loadgp_blockage ());
 }
 
+/* A for_each_rtx callback.  Stop the search if *X is a kernel register.  */
+
+static int
+mips_kernel_reg_p (rtx *x, void *data ATTRIBUTE_UNUSED)
+{
+  return GET_CODE (*x) == REG && KERNEL_REG_P (REGNO (*x));
+}
+
 /* Expand the "prologue" pattern.  */
 
 void
@@ -9172,11 +9389,123 @@ mips_expand_prologue (void)
   /* Save the registers.  Allocate up to MIPS_MAX_FIRST_STACK_STEP
      bytes beforehand; this is enough to cover the register save area
      without going out of range.  */
-  if ((frame->mask | frame->fmask) != 0)
+  if (((frame->mask | frame->fmask | frame->acc_mask) != 0)
+      || frame->num_cop0_regs > 0)
     {
       HOST_WIDE_INT step1;
 
       step1 = MIN (size, MIPS_MAX_FIRST_STACK_STEP);
+
+      if (cfun->machine->interrupt_handler_p)
+	{
+	  HOST_WIDE_INT offset;
+	  unsigned int i;
+	  rtx mem;
+
+	  /* If this interrupt is using a shadow register set, we need to
+	     get the stack pointer from the previous register set.  */
+	  if (cfun->machine->use_shadow_register_set_p)
+	    emit_insn (gen_mips_rdpgpr (stack_pointer_rtx, stack_pointer_rtx));
+
+	  if (!cfun->machine->keep_interrupts_masked_p)
+	    {
+	      /* Move from COP0 Cause to K0.  */
+	      emit_insn (gen_cop0_move (gen_rtx_REG (SImode, K0_REG_NUM),
+					gen_rtx_REG (SImode,
+						     COP0_CAUSE_REG_NUM)));
+
+	      /* Move from COP0 EPC to K1.  */
+	      emit_insn (gen_cop0_move (gen_rtx_REG (SImode, K1_REG_NUM),
+					gen_rtx_REG (SImode,
+						     COP0_EPC_REG_NUM)));
+	    }
+
+	  /* Allocate the stack space to save the registers.  If more space
+	     needs to be allocated, the expand_prologue() function handled
+	     it.  */
+	  insn = gen_add3_insn (stack_pointer_rtx,
+				stack_pointer_rtx,
+				GEN_INT (-step1));
+	  RTX_FRAME_RELATED_P (emit_insn (insn)) = 1;
+
+	  /* Start at the uppermost location for saving.  */
+	  offset = cfun->machine->frame.cop0_sp_offset;
+	  if (!cfun->machine->keep_interrupts_masked_p)
+	    {
+	      /* Push EPC into its stack slot.  */
+	      mem = gen_frame_mem (word_mode,
+				   plus_constant (stack_pointer_rtx, offset));
+	      mips_emit_move (mem, gen_rtx_REG (word_mode, K1_REG_NUM));
+	      mips_set_frame_expr (mips_frame_set
+				    (mem, gen_rtx_REG (word_mode,
+						       COP0_EPC_REG_NUM)));
+	      offset -= UNITS_PER_WORD;
+	    }
+
+	  /* Move from COP0 Status to K1.  */
+	  emit_insn (gen_cop0_move (gen_rtx_REG (SImode, K1_REG_NUM),
+				    gen_rtx_REG (SImode, COP0_STATUS_REG_NUM)));
+
+	  /* Right justify the RIPL in k0.  */
+	  if (!cfun->machine->keep_interrupts_masked_p)
+	    emit_insn (gen_lshrsi3 (gen_rtx_REG (SImode, K0_REG_NUM),
+				    gen_rtx_REG (SImode, K0_REG_NUM),
+				    GEN_INT (CAUSE_IPL)));
+
+	  /* Push Status into its stack slot.  */
+	  mem = gen_frame_mem (word_mode,
+			       plus_constant (stack_pointer_rtx, offset));
+	  mips_emit_move (mem, gen_rtx_REG (word_mode, K1_REG_NUM));
+	  mips_set_frame_expr (mips_frame_set
+				 (mem, gen_rtx_REG (word_mode,
+						    COP0_STATUS_REG_NUM)));
+	  offset -= UNITS_PER_WORD;
+
+	  /* Insert the RIPL into our copy of SR (k1) as the new IPL.  */
+	  if (!cfun->machine->keep_interrupts_masked_p)
+	    emit_insn (gen_insvsi (gen_rtx_REG (SImode, K1_REG_NUM),
+				   GEN_INT (6),
+				   GEN_INT (SR_IPL),
+				   gen_rtx_REG (SImode, K0_REG_NUM)));
+
+	  /* Save accumulators to the stack.  */
+	  offset = cfun->machine->frame.acc_sp_offset;
+	  if (BITSET_P (cfun->machine->frame.acc_mask, 0))
+	    {
+	      mips_save_restore_reg (word_mode, LO_REGNUM, offset,
+				     mips_save_reg);
+	      offset -= UNITS_PER_WORD;
+	      mips_save_restore_reg (word_mode, HI_REGNUM, offset,
+				     mips_save_reg);
+	      offset -= UNITS_PER_WORD;
+	    }
+
+	  for (i = 2; i < 8; i++)
+	    if (BITSET_P (cfun->machine->frame.acc_mask, i >> 1))
+	      {
+		mips_save_restore_reg (word_mode, DSP_ACC_REG_FIRST + i - 2,
+				       offset, mips_save_reg);
+		offset -= UNITS_PER_WORD;
+	      }
+
+	  if (!cfun->machine->keep_interrupts_masked_p)
+	    /* Enable interrupts by clearing the KSU ERL and EXL bits.
+	       IE is already the correct value, so we don't have to do
+	       anything explicit.  */
+	    emit_insn (gen_insvsi (gen_rtx_REG (SImode, K1_REG_NUM),
+				   GEN_INT (4),
+				   GEN_INT (SR_EXL),
+				   gen_rtx_REG (SImode, GP_REG_FIRST)));
+	  else
+	    /* Disable interrupts by clearing the KSU, ERL, EXL,
+	       and IE bits.  */
+	    emit_insn (gen_insvsi (gen_rtx_REG (SImode, K1_REG_NUM),
+				   GEN_INT (5),
+				   GEN_INT (SR_IE),
+				   gen_rtx_REG (SImode, GP_REG_FIRST)));
+
+	}
+
       if (GENERATE_MIPS16E_SAVE_RESTORE)
  	{
  	  HOST_WIDE_INT offset;
@@ -9203,10 +9532,16 @@ mips_expand_prologue (void)
  	}
       else
  	{
-	  insn = gen_add3_insn (stack_pointer_rtx,
-				stack_pointer_rtx,
-				GEN_INT (-step1));
-	  RTX_FRAME_RELATED_P (emit_insn (insn)) = 1;
+	  /* If this is an interrupt function, this first stack allocation
+	     will be performed by the static frame code and doesn't need
+	     to be done here.  */
+	  if (!cfun->machine->interrupt_handler_p)
+	    {
+	      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);
 	}
@@ -9293,6 +9628,22 @@ mips_expand_prologue (void)
 			pic_offset_table_rtx);
     }
 
+  /* We need to search back to the last use of K0 or K1.  */
+  if (cfun->machine->interrupt_handler_p)
+    {
+      rtx insn;
+      for (insn = get_last_insn (); insn != NULL_RTX; insn = PREV_INSN (insn))
+ 	{
+	  if (for_each_rtx (&PATTERN(insn), mips_kernel_reg_p, NULL) == true)
+	    break;
+	}
+      /* Emit a move from K1 to COP0 Status after insn.  */
+      gcc_assert (insn != NULL_RTX);
+      emit_insn_after (gen_cop0_move (gen_rtx_REG (SImode, COP0_STATUS_REG_NUM),
+				      gen_rtx_REG (SImode, K1_REG_NUM)),
+		       insn);
+    }
+
   /* If we are profiling, make sure no instructions are scheduled before
      the call to mcount.  */
   if (crtl->profile)
@@ -9309,7 +9660,20 @@ mips_restore_reg (rtx reg, rtx mem)
   if (TARGET_MIPS16 && REGNO (reg) == GP_REG_FIRST + 31)
     reg = gen_rtx_REG (GET_MODE (reg), GP_REG_FIRST + 7);
 
-  if (TARGET_MIPS16 && !M16_REG_P (REGNO (reg)))
+  if (REGNO (reg) == HI_REGNUM)
+    {
+      mips_emit_move (MIPS_EPILOGUE_TEMP (GET_MODE (reg)), mem);
+      if (TARGET_64BIT)
+	emit_insn (gen_mthisi_di (gen_rtx_REG (TImode, MD_REG_FIRST),
+				  MIPS_EPILOGUE_TEMP (DImode),
+				  gen_rtx_REG (DImode, LO_REGNUM)));
+      else
+	emit_insn (gen_mthisi_di (gen_rtx_REG (DImode, MD_REG_FIRST),
+				  MIPS_EPILOGUE_TEMP (SImode),
+				  gen_rtx_REG (SImode, LO_REGNUM)));
+    }
+  else if ((TARGET_MIPS16 && !M16_REG_P (REGNO (reg)))
+	   || ACC_REG_P (REGNO (reg)))
     {
       /* Can't restore directly; move through a temporary.  */
       mips_emit_move (MIPS_EPILOGUE_TEMP (GET_MODE (reg)), mem);
@@ -9378,7 +9742,8 @@ mips_expand_epilogue (bool sibcall_p)
 
   /* If we need to restore registers, deallocate as much stack as
      possible in the second step without going out of range.  */
-  if ((frame->mask | frame->fmask) != 0)
+  if ((frame->mask | frame->fmask | frame->acc_mask) != 0
+      || frame->num_cop0_regs > 0)
     {
       step2 = MIN (step1, MIPS_MAX_FIRST_STACK_STEP);
       step1 -= step2;
@@ -9442,11 +9807,76 @@ mips_expand_epilogue (bool sibcall_p)
       /* Restore the registers.  */
       mips_for_each_saved_reg (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 (cfun->machine->interrupt_handler_p)
+	{
+	  HOST_WIDE_INT offset;
+	  unsigned int i;
+	  rtx mem;
+
+	  /* Restore accumulators from the stack.  */
+	  offset = cfun->machine->frame.acc_sp_offset;
+	  if (BITSET_P (cfun->machine->frame.acc_mask, 0))
+	    {
+	      mips_save_restore_reg (word_mode, LO_REGNUM, offset,
+				     mips_restore_reg);
+	      offset -= UNITS_PER_WORD;
+	      mips_save_restore_reg (word_mode, HI_REGNUM, offset,
+				     mips_restore_reg);
+	      offset -= UNITS_PER_WORD;
+	    }
+
+	  for (i = 2; i < 8; i++)
+	    if (BITSET_P (cfun->machine->frame.acc_mask, i >> 1))
+	      {
+		mips_save_restore_reg (word_mode, DSP_ACC_REG_FIRST + i - 2,
+				       offset, mips_restore_reg);
+		offset -= UNITS_PER_WORD;
+	      }
+
+	  offset = cfun->machine->frame.cop0_sp_offset;
+	  if (!cfun->machine->keep_interrupts_masked_p)
+	    {
+	      /* Restore the original EPC.  */
+	      mem = gen_frame_mem (word_mode,
+				   plus_constant (stack_pointer_rtx, offset));
+	      mips_emit_move (gen_rtx_REG (word_mode, K0_REG_NUM), mem);
+	      mips_set_frame_expr (mips_frame_set
+				   (mem, gen_rtx_REG (word_mode,
+						      COP0_EPC_REG_NUM)));
+	      offset -= UNITS_PER_WORD;
+
+	      /* Move to COP0 EPC.  */
+	      emit_insn (gen_cop0_move (gen_rtx_REG (SImode, COP0_EPC_REG_NUM),
+					gen_rtx_REG (SImode, K0_REG_NUM)));
+	    }
+
+	  /* Restore the original Status.  */
+	  mem = gen_frame_mem (word_mode,
+				 plus_constant (stack_pointer_rtx, offset));
+	  mips_emit_move (gen_rtx_REG (word_mode, K0_REG_NUM), mem);
+	  mips_set_frame_expr (mips_frame_set
+				 (mem, gen_rtx_REG (word_mode,
+						    COP0_STATUS_REG_NUM)));
+	  offset -= UNITS_PER_WORD;
+
+	  /* If we don't use shoadow register set, we need to update SP.  */
+	  if (!cfun->machine->use_shadow_register_set_p && step2 > 0)
+	    emit_insn (gen_add3_insn (stack_pointer_rtx,
+				      stack_pointer_rtx,
+				      GEN_INT (step2)));
+
+	  /* Move to COP0 Status.  */
+	  emit_insn (gen_cop0_move (gen_rtx_REG (SImode, COP0_STATUS_REG_NUM),
+				    gen_rtx_REG (SImode, K0_REG_NUM)));
+	}
+      else
+	{
+	  /* 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
@@ -9480,7 +9910,35 @@ mips_expand_epilogue (bool sibcall_p)
       else
 	regno = GP_REG_FIRST + 31;
       mips_expand_before_return ();
-      emit_jump_insn (gen_return_internal (gen_rtx_REG (Pmode, regno)));
+
+      /* Interrupt handlers generate eret or deret.  */
+      if (cfun->machine->interrupt_handler_p)
+	{
+	  if (cfun->machine->use_debug_exception_return_p)
+	    emit_jump_insn (gen_mips_deret ());
+	  else
+	    emit_jump_insn (gen_mips_eret ());
+	}
+      else
+	emit_jump_insn (gen_return_internal (gen_rtx_REG (Pmode, regno)));
+    }
+
+  /* Search from the beginning to the first use of K0 or K1.  */
+  if (cfun->machine->interrupt_handler_p
+      && !cfun->machine->keep_interrupts_masked_p)
+    {
+      rtx insn;
+      for (insn = get_first_nonnote_insn (); insn != NULL_RTX;
+	   insn = NEXT_INSN (insn))
+ 	{
+	  if (for_each_rtx (&PATTERN(insn), mips_kernel_reg_p, NULL)
+	      == true)
+	    break;
+	}
+      gcc_assert (insn != NULL_RTX);
+      /* Insert disable interrupts before the first use of K0 or K1.  */
+      emit_insn_before (gen_mips_di (), insn);
+      emit_insn_before (gen_mips_ehb (), insn);
     }
 }
 

@@ -9491,6 +9949,10 @@ mips_expand_epilogue (bool sibcall_p)
 bool
 mips_can_use_return_insn (void)
 {
+  /* Interrupt handlers need to go through the epilogue.  */
+  if (cfun->machine->interrupt_handler_p)
+    return false;
+
   if (!reload_completed)
     return false;
 
@@ -14242,6 +14704,31 @@ mips_order_regs_for_local_alloc (void)
       reg_alloc_order[24] = 0;
     }
 }
+
+/* Implement EPILOGUE_USES.  */
+
+bool
+mips_epilogue_uses (unsigned int regno)
+{
+  /* Say that the epilogue uses the return address register.  Note that
+     in the case of sibcalls, the values "used by the epilogue" are
+     considered live at the start of the called function.  */
+  if (regno == 31)
+    return true;
+
+  /* If using a GOT, say that the epilogue also uses GOT_VERSION_REGNUM.
+     See the comment above load_call<mode> for details.  */
+  if (TARGET_USE_GOT && (regno) == GOT_VERSION_REGNUM)
+    return true;
+
+  /* If the register is part of the GPRs that's saved for interrupt context,
+     we need to mark it as used by the epilogue.  */
+  if (cfun->machine->interrupt_handler_p
+      && mips_interrupt_extra_call_saved_reg_p (regno))
+    return true;
+
+  return false;
+}
 

 /* Initialize the GCC target structure.  */
 #undef TARGET_ASM_ALIGNED_HI_OP
Index: gcc4x/gcc/gcc/config/mips/mips.md
===================================================================
--- gcc4x.orig/gcc/gcc/config/mips/mips.md	2009-03-11 14:25:12.000000000 -0700
+++ gcc4x/gcc/gcc/config/mips/mips.md	2009-03-11 14:52:27.000000000 -0700
@@ -67,6 +67,12 @@
    (UNSPEC_SET_GOT_VERSION	46)
    (UNSPEC_UPDATE_GOT_VERSION	47)
    (UNSPEC_COPYGP		48)
+   (UNSPEC_ERET			49)
+   (UNSPEC_DERET		50)
+   (UNSPEC_DI			51)
+   (UNSPEC_EHB			52)
+   (UNSPEC_RDPGPR		53)
+   (UNSPEC_COP0			54)
    
    (UNSPEC_ADDRESS_FIRST	100)
 
@@ -5679,6 +5685,60 @@
   [(set_attr "type"	"jump")
    (set_attr "mode"	"none")])
 
+;; Exception return.
+(define_insn "mips_eret"
+  [(return)
+   (unspec_volatile [(const_int 0)] UNSPEC_ERET)]
+  ""
+  "eret"
+  [(set_attr "type"	"trap")
+   (set_attr "mode"	"none")])
+
+;; Debug exception return.
+(define_insn "mips_deret"
+  [(return)
+   (unspec_volatile [(const_int 0)] UNSPEC_DERET)]
+  ""
+  "deret"
+  [(set_attr "type"	"trap")
+   (set_attr "mode"	"none")])
+
+;; Disable interrupt.
+(define_insn "mips_di"
+  [(unspec_volatile [(const_int 0)] UNSPEC_DI)]
+  ""
+  "di"
+  [(set_attr "type"	"trap")
+   (set_attr "mode"	"none")])
+
+;; Execution hazard barrier.
+(define_insn "mips_ehb"
+  [(unspec_volatile [(const_int 0)] UNSPEC_EHB)]
+  ""
+  "ehb"
+  [(set_attr "type"	"trap")
+   (set_attr "mode"	"none")])
+
+;; Read GPR from previous shadow register set.
+(define_insn "mips_rdpgpr"
+  [(set (match_operand:SI 0 "register_operand" "=d")
+	(unspec_volatile:SI [(match_operand:SI 1 "register_operand" "d")]
+			    UNSPEC_RDPGPR))]
+  ""
+  "rdpgpr\t%0,%1"
+  [(set_attr "type"	"move")
+   (set_attr "mode"	"SI")])
+
+;; Move involving COP0 registers.
+(define_insn "cop0_move"
+  [(set (match_operand:SI 0 "register_operand" "=B,d")
+	(unspec_volatile:SI [(match_operand:SI 1 "register_operand" "d,B")]
+			    UNSPEC_COP0))]
+  ""
+{ return mips_output_move (operands[0], operands[1]); }
+  [(set_attr "type"	"mtc,mfc")
+   (set_attr "mode"	"SI")])
+
 ;; This is used in compiling the unwind routines.
 (define_expand "eh_return"
   [(use (match_operand 0 "general_operand"))]
Index: gcc4x/gcc/gcc/config/mips/mips-protos.h
===================================================================
--- gcc4x.orig/gcc/gcc/config/mips/mips-protos.h	2009-03-11 14:25:12.000000000 -0700
+++ gcc4x/gcc/gcc/config/mips/mips-protos.h	2009-03-11 14:25:26.000000000 -0700
@@ -332,4 +332,6 @@ extern void mips_expand_atomic_qihi (uni
 
 extern void mips_expand_vector_init (rtx, rtx);
 
+extern bool mips_epilogue_uses (unsigned int);
+
 #endif /* ! GCC_MIPS_PROTOS_H */
Index: gcc4x/gcc/gcc/config/mips/mips.h
===================================================================
--- gcc4x.orig/gcc/gcc/config/mips/mips.h	2009-03-11 14:25:12.000000000 -0700
+++ gcc4x/gcc/gcc/config/mips/mips.h	2009-03-12 14:24:25.743475000 -0700
@@ -1622,6 +1622,9 @@ enum mips_code_readable_setting {
 #define GP_REG_LAST  31
 #define GP_REG_NUM   (GP_REG_LAST - GP_REG_FIRST + 1)
 #define GP_DBX_FIRST 0
+#define K0_REG_NUM   (GP_REG_FIRST + 26)
+#define K1_REG_NUM   (GP_REG_FIRST + 27)
+#define KERNEL_REG_P(REGNO)	(IN_RANGE (REGNO, K0_REG_NUM, K1_REG_NUM))
 
 #define FP_REG_FIRST 32
 #define FP_REG_LAST  63
@@ -1649,6 +1652,10 @@ enum mips_code_readable_setting {
 #define COP0_REG_LAST 111
 #define COP0_REG_NUM (COP0_REG_LAST - COP0_REG_FIRST + 1)
 
+#define COP0_STATUS_REG_NUM	(COP0_REG_FIRST + 12)
+#define COP0_CAUSE_REG_NUM	(COP0_REG_FIRST + 13)
+#define COP0_EPC_REG_NUM	(COP0_REG_FIRST + 14)
+
 #define COP2_REG_FIRST 112
 #define COP2_REG_LAST 143
 #define COP2_REG_NUM (COP2_REG_LAST - COP2_REG_FIRST + 1)
@@ -1667,6 +1674,17 @@ enum mips_code_readable_setting {
 #define HI_REGNUM	(TARGET_BIG_ENDIAN ? MD_REG_FIRST : MD_REG_FIRST + 1)
 #define LO_REGNUM	(TARGET_BIG_ENDIAN ? MD_REG_FIRST + 1 : MD_REG_FIRST)
 
+/* A few bitfield locations for the coprocessor registers.  */
+/* Request Interrupt Priority Level is from bit 10 to bit 15 of
+   cause register for the EIC interrupt mode.  */
+#define CAUSE_IPL	10
+/* Interrupt Priority Level is from bit 10 to bit 15 of status register.  */
+#define SR_IPL		10
+/* Exception Level is at bit 1 of status register.  */
+#define SR_EXL		1
+/* Interrupt Enable is at bit 0 of status register.  */
+#define SR_IE		0
+
 /* FPSW_REGNUM is the single condition code used if !ISA_HAS_8CC.
    If ISA_HAS_8CC, it should not be used, and an arbitrary ST_REG
    should be used instead.  */
@@ -1754,11 +1772,17 @@ enum mips_code_readable_setting {
    incoming arguments, the static chain pointer, or the frame pointer.
    The epilogue temporary mustn't conflict with the return registers,
    the PIC call register ($25), the frame pointer, the EH stack adjustment,
-   or the EH data registers.  */
+   or the EH data registers.
+
+   If we're generating interrupt handlers, we use K0 as a temporary register
+   in prologue/epilogue code.  */
 
 #define MIPS16_PIC_TEMP_REGNUM (GP_REG_FIRST + 2)
-#define MIPS_PROLOGUE_TEMP_REGNUM (GP_REG_FIRST + 3)
-#define MIPS_EPILOGUE_TEMP_REGNUM (GP_REG_FIRST + (TARGET_MIPS16 ? 6 : 8))
+#define MIPS_PROLOGUE_TEMP_REGNUM (cfun->machine->interrupt_handler_p ?	\
+				   K0_REG_NUM : GP_REG_FIRST + 3)
+#define MIPS_EPILOGUE_TEMP_REGNUM (cfun->machine->interrupt_handler_p ?	\
+				   K0_REG_NUM : (GP_REG_FIRST +		\
+						 (TARGET_MIPS16 ? 6 : 8))
 
 #define MIPS16_PIC_TEMP gen_rtx_REG (Pmode, MIPS16_PIC_TEMP_REGNUM)
 #define MIPS_PROLOGUE_TEMP(MODE) gen_rtx_REG (MODE, MIPS_PROLOGUE_TEMP_REGNUM)
@@ -2289,9 +2313,10 @@ typedef struct mips_args {
    considered live at the start of the called function.
 
    If using a GOT, say that the epilogue also uses GOT_VERSION_REGNUM.
-   See the comment above load_call<mode> for details.  */
-#define EPILOGUE_USES(REGNO) \
-  ((REGNO) == 31 || (TARGET_USE_GOT && (REGNO) == GOT_VERSION_REGNUM))
+   See the comment above load_call<mode> for details.
+
+   For interrupt handlers, registers in interrupt context are used.  */
+#define EPILOGUE_USES(REGNO)	(mips_epilogue_uses (REGNO))
 
 /* Treat LOC as a byte offset from the stack pointer and round it up
    to the next fully-aligned offset.  */
Index: gcc4x/gcc/gcc/doc/extend.texi
===================================================================
--- gcc4x.orig/gcc/gcc/doc/extend.texi	2009-03-09 11:39:03.000000000 -0700
+++ gcc4x/gcc/gcc/doc/extend.texi	2009-03-11 14:25:26.000000000 -0700
@@ -2398,7 +2398,7 @@ This attribute is ignored for R8C target
 
 @item interrupt
 @cindex interrupt handler functions
-Use this attribute on the ARM, AVR, CRX, M32C, M32R/D, m68k,
+Use this attribute on the ARM, AVR, CRX, M32C, M32R/D, m68k, MIPS
 and Xstormy16 ports to indicate that the specified function is an
 interrupt handler.  The compiler will generate function entry and exit
 sequences suitable for use in an interrupt handler when this attribute
@@ -2421,6 +2421,31 @@ Permissible values for this parameter ar
 On ARMv7-M the interrupt type is ignored, and the attribute means the function
 may be called with a word aligned stack pointer.
 
+Note, for the MIPS, you can specify the behavior of interrupt by
+adding more attributes in addition to the interrupt attribute.
+These attributes are use_shadow_register_set, keep_interrupts_masked,
+and use_debug_exception_return.
+Without these attributes, the default behaviors are as follows.
+Don't use shadow register set (use normal registers);
+don't keep interrupts masked (enable nested interrupts);
+don't use debug exception return instruction (use exception return instruction).
+
+@smallexample
+void __attribute__ ((interrupt)) v0 ();
+void __attribute__ ((interrupt, use_shadow_register_set)) v1 ();
+void __attribute__ ((interrupt, keep_interrupts_masked)) v2 ();
+void __attribute__ ((interrupt, use_debug_exception_return)) v3 ();
+void __attribute__ ((interrupt, use_shadow_register_set,
+		     keep_interrupts_masked)) v4 ();
+void __attribute__ ((interrupt, use_shadow_register_set,
+		     use_debug_exception_return)) v5 ();
+void __attribute__ ((interrupt, keep_interrupts_masked,
+		     use_debug_exception_return)) v6 ();
+void __attribute__ ((interrupt, use_shadow_register_set,
+		     keep_interrupts_masked,
+		     use_debug_exception_return)) v7 ();
+@end smallexample
+
 @item interrupt_handler
 @cindex interrupt handler functions on the Blackfin, m68k, H8/300 and SH processors
 Use this attribute on the Blackfin, m68k, H8/300, H8/300H, H8S, and SH to
Index: gcc4x/gcc/gcc/testsuite/gcc.target/mips/interrupt_handler.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gcc4x/gcc/gcc/testsuite/gcc.target/mips/interrupt_handler.c	2009-03-11 14:25:26.000000000 -0700
@@ -0,0 +1,23 @@
+/* Test attributes for interrupt handlers */
+/* { dg-do compile } */
+/* { dg-options "-mips32r2 -msoft-float" } */
+
+void f () { }
+
+void __attribute__ ((interrupt)) v0 () { }
+void __attribute__ ((interrupt, use_shadow_register_set)) v1 () { }
+void __attribute__ ((interrupt, keep_interrupts_masked)) v2 () { }
+void __attribute__ ((interrupt, use_debug_exception_return)) v3 () { }
+void __attribute__ ((interrupt, use_shadow_register_set, keep_interrupts_masked)) v4 () { }
+void __attribute__ ((interrupt, use_shadow_register_set, use_debug_exception_return)) v5 () { }
+void __attribute__ ((interrupt, keep_interrupts_masked, use_debug_exception_return)) v6 () { }
+void __attribute__ ((interrupt, use_shadow_register_set, keep_interrupts_masked, use_debug_exception_return)) v7 () { }
+
+void __attribute__ ((interrupt)) w0 () { t(); }
+void __attribute__ ((interrupt, use_shadow_register_set)) w1 () { t(); }
+void __attribute__ ((interrupt, keep_interrupts_masked)) w2 () { t(); }
+void __attribute__ ((interrupt, use_debug_exception_return)) w3 () { t(); }
+void __attribute__ ((interrupt, use_shadow_register_set, keep_interrupts_masked)) w4 () { t(); }
+void __attribute__ ((interrupt, use_shadow_register_set, use_debug_exception_return)) w5 () { t(); }
+void __attribute__ ((interrupt, keep_interrupts_masked, use_debug_exception_return)) w6 () { t(); }
+void __attribute__ ((interrupt, use_shadow_register_set, keep_interrupts_masked, use_debug_exception_return)) w7 () { t(); }
Index: gcc4x/gcc/gcc/config/mips/sde.h
===================================================================
--- gcc4x.orig/gcc/gcc/config/mips/sde.h	2009-03-11 17:40:24.000000000 -0700
+++ gcc4x/gcc/gcc/config/mips/sde.h	2009-03-11 17:40:09.000000000 -0700
@@ -90,7 +90,8 @@ along with GCC; see the file COPYING3.  
 
 /* Use $5 as a temporary for both MIPS16 and non-MIPS16.  */
 #undef MIPS_EPILOGUE_TEMP_REGNUM
-#define MIPS_EPILOGUE_TEMP_REGNUM (GP_REG_FIRST + 5)
+#define MIPS_EPILOGUE_TEMP_REGNUM (cfun->machine->interrupt_handler_p ?	\
+				   K0_REG_NUM : (GP_REG_FIRST + 5))
 
 /* Using long will always be right for size_t and ptrdiff_t, since
    sizeof(long) must equal sizeof(void *), following from the setting



More information about the Gcc-patches mailing list