mips.c (mips_frame_info): Add acc_mask...
authorChao-ying Fu <fu@mips.com>
Thu, 2 Apr 2009 21:57:41 +0000 (21:57 +0000)
committerChao-ying Fu <chaoyingfu@gcc.gnu.org>
Thu, 2 Apr 2009 21:57:41 +0000 (21:57 +0000)
2009-04-02  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 registers 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 that
includes doubleword accumulators and COP0 registers.
(mips_for_each_saved_acc): New function.
(mips_for_each_saved_gpr_and_fpr): Change the function name from
mips_for_each_saved_reg.
(mips_save_reg): Save accumulators.
(mips_kernel_reg_p): A new for_each_rtx callback.
(mips_expand_prologue): Support interrupt handlers.
(mips_restore_reg): Restore accumulators.
(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.

Co-Authored-By: James Grosbach <james.grosbach@microchip.com>
From-SVN: r145481

gcc/ChangeLog
gcc/config/mips/mips-protos.h
gcc/config/mips/mips.c
gcc/config/mips/mips.h
gcc/config/mips/mips.md
gcc/config/mips/sde.h
gcc/doc/extend.texi

index 0c7f96d7e17aee9683b32aaa3e2a5cfb0ae9ba40..6b8ad457ae6cb6747a5d9fa25810afa1a29480f6 100644 (file)
@@ -1,3 +1,55 @@
+2009-04-02  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 registers 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 that
+       includes doubleword accumulators and COP0 registers.
+       (mips_for_each_saved_acc): New function.
+       (mips_for_each_saved_gpr_and_fpr): Change the function name from
+       mips_for_each_saved_reg.
+       (mips_save_reg): Save accumulators.
+       (mips_kernel_reg_p): A new for_each_rtx callback.
+       (mips_expand_prologue): Support interrupt handlers.
+       (mips_restore_reg): Restore accumulators.
+       (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.
+
 2009-04-03  Alan Modra  <amodra@bigpond.net.au>
 
        * config.gcc (powerpc64-*-gnu*): Add rs6000/default64.h to tm_file.
index d00b368d9fa3ee0b51bc9fbdc0c5023959cf3de7..a704750286bed99302d8b2424cfb483b976895f8 100644 (file)
@@ -332,4 +332,6 @@ extern void mips_expand_atomic_qihi (union mips_gen_fn_ptrs,
 
 extern void mips_expand_vector_init (rtx, rtx);
 
+extern bool mips_epilogue_uses (unsigned int);
+
 #endif /* ! GCC_MIPS_PROTOS_H */
index f153d13741123910c674392acbdef664c20501ca..9912105ecfac81a5dd9483db120436215779c320 100644 (file)
@@ -261,18 +261,29 @@ struct mips_frame_info GTY(()) {
   /* Likewise FPR X.  */
   unsigned int fmask;
 
-  /* The number of GPRs and FPRs saved.  */
+  /* Likewise doubleword accumulator X ($acX).  */
+  unsigned int acc_mask;
+
+  /* The number of GPRs, FPRs, doubleword 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 +321,20 @@ 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 this is an interrupt handler that should keep interrupts
+     masked.  */
+  bool keep_interrupts_masked_p;
+
+  /* True if this is an interrupt handler that should use DERET
+     instead of ERET.  */
+  bool use_debug_exception_return_p;
 };
 
 /* Information about a single argument.  */
@@ -554,6 +579,11 @@ const struct attribute_spec mips_attribute_table[] = {
      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 }
 };
 \f
@@ -1172,6 +1202,42 @@ 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 +6254,11 @@ mips_function_ok_for_sibcall (tree decl, tree exp ATTRIBUTE_UNUSED)
   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 +7300,11 @@ mips_print_operand (FILE *file, rtx op, int letter)
                || (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 +8511,53 @@ 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, return true for registers that aren't ordinarily
+        call-clobbered.  */
+      return call_really_used_regs[regno];
+    }
+
+  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)
 {
+  /* Interrupt handlers 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 +8606,13 @@ mips_cfun_might_clobber_call_saved_reg_p (unsigned int regno)
   if (regno == GP_REG_FIRST + 31 && mips16_cfun_returns_in_fpr_p ())
     return true;
 
+  /* If REGNO is ordinarily call-clobbered, we must assume that any
+     called function could modify it.  */
+  if (cfun->machine->interrupt_handler_p
+      && !current_function_is_leaf
+      && mips_interrupt_extra_call_saved_reg_p (regno))
+    return true;
+
   return false;
 }
 
@@ -8545,6 +8668,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 +8719,28 @@ mips_compute_frame_info (void)
   HOST_WIDE_INT offset, size;
   unsigned int regno, i;
 
+  /* Set this function's interrupt properties.  */
+  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 ();
@@ -8657,7 +8810,7 @@ mips_compute_frame_info (void)
     }
 
   /* Find out which FPRs we need to save.  This loop must iterate over
-     the same space as its companion in mips_for_each_saved_reg.  */
+     the same space as its companion in mips_for_each_saved_gpr_and_fpr.  */
   if (TARGET_HARD_FLOAT)
     for (regno = FP_REG_FIRST; regno <= FP_REG_LAST; regno += MAX_FPRS_PER_FMT)
       if (mips_save_reg_p (regno))
@@ -8673,6 +8826,47 @@ 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 = DSP_ACC_REG_FIRST; i <= DSP_ACC_REG_LAST; i += 2)
+       if (mips_save_reg_p (i) || mips_save_reg_p (i + 1))
+         {
+           frame->num_acc++;
+           frame->acc_mask |= 1 << (((i - DSP_ACC_REG_FIRST) / 2) + 1);
+         }
+
+      /* 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 accumulator save area.  */
+  if (frame->num_acc > 0)
+    {
+      /* Each accumulator needs 2 words.  */
+      offset += frame->num_acc * 2 * 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 +8880,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
@@ -8882,12 +9080,41 @@ mips_save_restore_reg (enum machine_mode mode, int regno,
   fn (gen_rtx_REG (mode, regno), mem);
 }
 
+/* Call FN for each accumlator that is saved by the current function.
+   SP_OFFSET is the offset of the current stack pointer from the start
+   of the frame.  */
+
+static void
+mips_for_each_saved_acc (HOST_WIDE_INT sp_offset, mips_save_restore_fn fn)
+{
+  HOST_WIDE_INT offset;
+  int regno;
+
+  offset = cfun->machine->frame.acc_sp_offset - sp_offset;
+  if (BITSET_P (cfun->machine->frame.acc_mask, 0))
+    {
+      mips_save_restore_reg (word_mode, LO_REGNUM, offset, fn);
+      offset -= UNITS_PER_WORD;
+      mips_save_restore_reg (word_mode, HI_REGNUM, offset, fn);
+      offset -= UNITS_PER_WORD;
+    }
+
+  for (regno = DSP_ACC_REG_FIRST; regno <= DSP_ACC_REG_LAST; regno++)
+    if (BITSET_P (cfun->machine->frame.acc_mask,
+                 ((regno - DSP_ACC_REG_FIRST) / 2) + 1))
+      {
+       mips_save_restore_reg (word_mode, regno, offset, fn);
+       offset -= UNITS_PER_WORD;
+      }
+}
+
 /* Call FN for each register that is saved by the current function.
    SP_OFFSET is the offset of the current stack pointer from the start
    of the frame.  */
 
 static void
-mips_for_each_saved_reg (HOST_WIDE_INT sp_offset, mips_save_restore_fn fn)
+mips_for_each_saved_gpr_and_fpr (HOST_WIDE_INT sp_offset,
+                                mips_save_restore_fn fn)
 {
   enum machine_mode fpr_mode;
   HOST_WIDE_INT offset;
@@ -9075,13 +9302,24 @@ 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)
        {
-         /* 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.  */
+         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)))
+       {
+         /* If the register has no direct store instruction, move it
+            through a temporary.  Note that there's a special MIPS16
+            instruction to save $31.  */
          mips_emit_move (MIPS_PROLOGUE_TEMP (GET_MODE (reg)), reg);
          mips_emit_move (mem, MIPS_PROLOGUE_TEMP (GET_MODE (reg)));
        }
@@ -9153,6 +9391,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,7 +9418,8 @@ 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;
 
@@ -9203,12 +9450,97 @@ 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;
-         size -= step1;
-         mips_for_each_saved_reg (size, mips_save_reg);
+         if (cfun->machine->interrupt_handler_p)
+           {
+             HOST_WIDE_INT offset;
+             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 first part of the frame.  */
+             insn = gen_add3_insn (stack_pointer_rtx, stack_pointer_rtx,
+                                   GEN_INT (-step1));
+             RTX_FRAME_RELATED_P (emit_insn (insn)) = 1;
+             size -= step1;
+
+             /* Start at the uppermost location for saving.  */
+             offset = frame->cop0_sp_offset - size;
+             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));
+                 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));
+             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)));
+
+             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)));
+           }
+         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_acc (size, mips_save_reg);
+         mips_for_each_saved_gpr_and_fpr (size, mips_save_reg);
        }
     }
 
@@ -9293,6 +9625,20 @@ 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)
+    {
+      for (insn = get_last_insn (); insn != NULL_RTX; insn = PREV_INSN (insn))
+       if (INSN_P (insn)
+           && for_each_rtx (&PATTERN (insn), mips_kernel_reg_p, NULL))
+         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 +9655,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);
@@ -9345,7 +9704,7 @@ mips_expand_epilogue (bool sibcall_p)
 {
   const struct mips_frame_info *frame;
   HOST_WIDE_INT step1, step2;
-  rtx base, target;
+  rtx base, target, insn;
 
   if (!sibcall_p && mips_can_use_return_insn ())
     {
@@ -9378,7 +9737,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;
@@ -9440,13 +9800,53 @@ mips_expand_epilogue (bool sibcall_p)
   else
     {
       /* Restore the registers.  */
-      mips_for_each_saved_reg (frame->total_size - step2, mips_restore_reg);
+      mips_for_each_saved_acc (frame->total_size - step2, mips_restore_reg);
+      mips_for_each_saved_gpr_and_fpr (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;
+         rtx mem;
+
+         offset = frame->cop0_sp_offset - (frame->total_size - step2);
+         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);
+             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);
+         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
@@ -9469,18 +9869,44 @@ mips_expand_epilogue (bool sibcall_p)
 
   if (!sibcall_p)
     {
-      unsigned int regno;
-
-      /* 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
-         && BITSET_P (frame->mask, 31))
-       regno = GP_REG_FIRST + 7;
-      else
-       regno = GP_REG_FIRST + 31;
       mips_expand_before_return ();
-      emit_jump_insn (gen_return_internal (gen_rtx_REG (Pmode, regno)));
+      if (cfun->machine->interrupt_handler_p)
+       {
+         /* Interrupt handlers generate eret or deret.  */
+         if (cfun->machine->use_debug_exception_return_p)
+           emit_jump_insn (gen_mips_deret ());
+         else
+           emit_jump_insn (gen_mips_eret ());
+       }
+      else
+       {
+         unsigned int regno;
+
+         /* When generating MIPS16 code, the normal
+            mips_for_each_saved_gpr_and_fpr path will restore the return
+            address into $7 rather than $31.  */
+         if (TARGET_MIPS16
+             && !GENERATE_MIPS16E_SAVE_RESTORE
+             && BITSET_P (frame->mask, 31))
+           regno = GP_REG_FIRST + 7;
+         else
+           regno = GP_REG_FIRST + 31;
+         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)
+    {
+      for (insn = get_insns (); insn != NULL_RTX; insn = NEXT_INSN (insn))
+       if (INSN_P (insn)
+           && for_each_rtx (&PATTERN(insn), mips_kernel_reg_p, NULL))
+         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);
     }
 }
 \f
@@ -9491,6 +9917,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 +14672,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;
+
+  /* An interrupt handler must preserve some registers that are
+     ordinarily call-clobbered.  */
+  if (cfun->machine->interrupt_handler_p
+      && mips_interrupt_extra_call_saved_reg_p (regno))
+    return true;
+
+  return false;
+}
 \f
 /* Initialize the GCC target structure.  */
 #undef TARGET_ASM_ALIGNED_HI_OP
index c2533c49e9f024aa2ab2ee6e337f1c7a060ebd3e..fbcfdca61e97b19b9ebd12f81579d5cc0bf96607 100644 (file)
@@ -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
+   the cause register for the EIC interrupt mode.  */
+#define CAUSE_IPL      10
+/* Interrupt Priority Level is from bit 10 to bit 15 of the status register.  */
+#define SR_IPL         10
+/* Exception Level is at bit 1 of the status register.  */
+#define SR_EXL         1
+/* Interrupt Enable is at bit 0 of the 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,18 @@ 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)
@@ -2284,14 +2309,7 @@ typedef struct mips_args {
        (mips_abi == ABI_EABI && UNITS_PER_FPVALUE >= UNITS_PER_DOUBLE)
 
 \f
-/* 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 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))
+#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 10572743e754eb8b6e05bbe2b2686b402fc35130..8a6719466d3ba4fac3d3ac47fff1ce91966a064a 100644 (file)
    (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)
 
   [(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 interrupts.
+(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 3640883e042c17844385a2d9e2f1e56287410dba..d2a32967b75488e5c352c229d0ccb253d30c6ad8 100644 (file)
@@ -90,7 +90,8 @@ along with GCC; see the file COPYING3.  If not see
 
 /* 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
index 9500feb1112c1f2b580d1460942b45c77845bbed..e8b5628c0f1c40bd5c8bffcffd40d611d11615b7 100644 (file)
@@ -2402,7 +2402,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
@@ -2425,6 +2425,42 @@ Permissible values for this parameter are: IRQ, FIQ, SWI, ABORT and UNDEF@.
 On ARMv7-M the interrupt type is ignored, and the attribute means the function
 may be called with a word aligned stack pointer.
 
+On MIPS targets, you can use the following attributes to modify the behavior
+of an interrupt handler:
+@table @code
+@item use_shadow_register_set
+@cindex @code{use_shadow_register_set} attribute
+Assume that the handler uses a shadow register set, instead of
+the main general-purpose registers.
+
+@item keep_interrupts_masked
+@cindex @code{keep_interrupts_masked} attribute
+Keep interrupts masked for the whole function.  Without this attribute,
+GCC tries to reenable interrupts for as much of the function as it can.
+
+@item use_debug_exception_return
+@cindex @code{use_debug_exception_return} attribute
+Return using the @code{deret} instruction.  Interrupt handlers that don't
+have this attribute return using @code{eret} instead.
+@end table
+
+You can use any combination of these attributes, as shown below:
+@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