From e19da24c65c5d914adec414c3b6b86f2eda6bfaa Mon Sep 17 00:00:00 2001 From: Chao-ying Fu Date: Thu, 2 Apr 2009 21:57:41 +0000 Subject: [PATCH] mips.c (mips_frame_info): Add acc_mask... 2009-04-02 Chao-ying Fu James Grosbach * 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 From-SVN: r145481 --- gcc/ChangeLog | 52 ++++ gcc/config/mips/mips-protos.h | 2 + gcc/config/mips/mips.c | 533 +++++++++++++++++++++++++++++++--- gcc/config/mips/mips.h | 40 ++- gcc/config/mips/mips.md | 60 ++++ gcc/config/mips/sde.h | 3 +- gcc/doc/extend.texi | 38 ++- 7 files changed, 676 insertions(+), 52 deletions(-) diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 0c7f96d7e17..6b8ad457ae6 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,55 @@ +2009-04-02 Chao-ying Fu + James Grosbach + + * 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 * config.gcc (powerpc64-*-gnu*): Add rs6000/default64.h to tm_file. diff --git a/gcc/config/mips/mips-protos.h b/gcc/config/mips/mips-protos.h index d00b368d9fa..a704750286b 100644 --- a/gcc/config/mips/mips-protos.h +++ b/gcc/config/mips/mips-protos.h @@ -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 */ diff --git a/gcc/config/mips/mips.c b/gcc/config/mips/mips.c index f153d137411..9912105ecfa 100644 --- a/gcc/config/mips/mips.c +++ b/gcc/config/mips/mips.c @@ -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 } }; @@ -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", ®_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 % attribute requires a MIPS32r2 processor"); + else if (TARGET_HARD_FLOAT) + error ("the % 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); } } @@ -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 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; +} /* Initialize the GCC target structure. */ #undef TARGET_ASM_ALIGNED_HI_OP diff --git a/gcc/config/mips/mips.h b/gcc/config/mips/mips.h index c2533c49e9f..fbcfdca61e9 100644 --- a/gcc/config/mips/mips.h +++ b/gcc/config/mips/mips.h @@ -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) -/* 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 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. */ diff --git a/gcc/config/mips/mips.md b/gcc/config/mips/mips.md index 10572743e75..8a6719466d3 100644 --- a/gcc/config/mips/mips.md +++ b/gcc/config/mips/mips.md @@ -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 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"))] diff --git a/gcc/config/mips/sde.h b/gcc/config/mips/sde.h index 3640883e042..d2a32967b75 100644 --- a/gcc/config/mips/sde.h +++ b/gcc/config/mips/sde.h @@ -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 diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 9500feb1112..e8b5628c0f1 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -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 -- 2.30.2