diff --git a/gcc/config/m68k/m68k-protos.h b/gcc/config/m68k/m68k-protos.h index abb8e9b061de1..1f4bdd9484f17 100644 --- a/gcc/config/m68k/m68k-protos.h +++ b/gcc/config/m68k/m68k-protos.h @@ -107,7 +107,7 @@ extern enum reg_class m68k_secondary_reload_class (enum reg_class, extern enum reg_class m68k_preferred_reload_class (rtx, enum reg_class); extern void m68k_expand_prologue (void); extern bool m68k_use_return_insn (void); -extern void m68k_expand_epilogue (bool); +extern void m68k_expand_epilogue (enum m68k_epilogue_kind); extern const char *m68k_cpp_cpu_ident (const char *); extern const char *m68k_cpp_cpu_family (const char *); extern void init_68881_table (void); diff --git a/gcc/config/m68k/m68k.cc b/gcc/config/m68k/m68k.cc index b6134892883e3..6b14e5689ebe8 100644 --- a/gcc/config/m68k/m68k.cc +++ b/gcc/config/m68k/m68k.cc @@ -1201,8 +1201,27 @@ m68k_use_return_insn (void) return current_frame.offset == 0; } -/* Emit RTL for the "epilogue" or "sibcall_epilogue" define_expand; - SIBCALL_P says which. +/* Return the subset of the integer registers saved by the prologue that an + epilogue of kind KIND has to restore. The EH_RETURN_DATA_REGNO save slots + are only filled in by the unwinder, so reloading them anywhere but on the + exception return path would clobber the function's return value. */ + +static unsigned int +m68k_epilogue_reg_mask (enum m68k_epilogue_kind kind) +{ + unsigned int mask; + unsigned int i; + + mask = current_frame.reg_mask; + if (crtl->calls_eh_return && kind != M68K_EPILOGUE_EH_RETURN) + for (i = 0; EH_RETURN_DATA_REGNO (i) != INVALID_REGNUM; i++) + mask &= ~(1u << (EH_RETURN_DATA_REGNO (i) - D0_REG)); + + return mask; +} + +/* Emit RTL for the "epilogue", "sibcall_epilogue" or "eh_return_internal" + define_expand; KIND says which. The function epilogue should not depend on the current stack pointer! It should use the frame pointer only, if there is a frame pointer. @@ -1210,13 +1229,26 @@ m68k_use_return_insn (void) omit stack adjustments before returning. */ void -m68k_expand_epilogue (bool sibcall_p) +m68k_expand_epilogue (enum m68k_epilogue_kind kind) { HOST_WIDE_INT fsize, fsize_with_regs; bool big, restore_from_sp; + unsigned int reg_mask, skipped_mask; + int reg_no; + HOST_WIDE_INT skipped_size; + bool post_increment_p; m68k_compute_frame_layout (); + reg_mask = m68k_epilogue_reg_mask (kind); + reg_no = popcount_hwi (reg_mask); + + /* The skipped registers are the lowest numbered saved ones, so their slots + form a block at the bottom of the save area. */ + skipped_mask = current_frame.reg_mask & ~reg_mask; + gcc_assert ((skipped_mask & (skipped_mask + 1)) == 0); + skipped_size = (current_frame.reg_no - reg_no) * GET_MODE_SIZE (SImode); + fsize = current_frame.size; big = false; restore_from_sp = false; @@ -1243,7 +1275,7 @@ m68k_expand_epilogue (bool sibcall_p) if (current_frame.offset + fsize >= 0x8000 && !restore_from_sp - && (current_frame.reg_mask || current_frame.fpu_mask)) + && (reg_mask || current_frame.fpu_mask)) { if (TARGET_COLDFIRE && (current_frame.reg_no >= MIN_MOVEM_REGS @@ -1268,16 +1300,34 @@ m68k_expand_epilogue (bool sibcall_p) } } - if (current_frame.reg_no < MIN_MOVEM_REGS) + /* ColdFire's movem cannot post-increment, so the prologue folded the save + area into the frame allocation and the epilogue addresses it directly. */ + post_increment_p = (restore_from_sp + && !(TARGET_COLDFIRE + && current_frame.reg_no >= MIN_MOVEM_REGS)); + + /* The skipped slots sit at the bottom of the save area, so step the stack + pointer past them before the post-increment reloads. */ + if (post_increment_p && skipped_size != 0) + { + if (reg_mask == 0 && current_frame.fpu_no == 0) + fsize_with_regs += skipped_size; + else + emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (skipped_size))); + } + + if (reg_no < MIN_MOVEM_REGS) { /* Restore each register separately in the same order moveml does. */ int i; HOST_WIDE_INT offset; - offset = current_frame.offset + fsize; + /* OFFSET is the position of the register within the save area. */ + offset = skipped_size; for (i = 0; i < 16; i++) - if (current_frame.reg_mask & (1 << i)) - { + if (reg_mask & (1 << i)) + { rtx addr; if (big) @@ -1285,36 +1335,38 @@ m68k_expand_epilogue (bool sibcall_p) /* Generate the address -OFFSET(%fp,%a1.l). */ addr = gen_rtx_REG (Pmode, A1_REG); addr = gen_rtx_PLUS (Pmode, addr, frame_pointer_rtx); - addr = plus_constant (Pmode, addr, -offset); + addr = plus_constant (Pmode, addr, + offset - (current_frame.offset + fsize)); } - else if (restore_from_sp) + else if (post_increment_p) addr = gen_rtx_POST_INC (Pmode, stack_pointer_rtx); + else if (restore_from_sp) + addr = plus_constant (Pmode, stack_pointer_rtx, offset); else - addr = plus_constant (Pmode, frame_pointer_rtx, -offset); + addr = plus_constant (Pmode, frame_pointer_rtx, + offset - (current_frame.offset + fsize)); emit_move_insn (gen_rtx_REG (SImode, D0_REG + i), gen_frame_mem (SImode, addr)); - offset -= GET_MODE_SIZE (SImode); + offset += GET_MODE_SIZE (SImode); } } - else if (current_frame.reg_mask) + else if (reg_mask) { if (big) m68k_emit_movem (gen_rtx_PLUS (Pmode, gen_rtx_REG (Pmode, A1_REG), frame_pointer_rtx), - -(current_frame.offset + fsize), - current_frame.reg_no, D0_REG, - current_frame.reg_mask, false, false); + skipped_size - (current_frame.offset + fsize), + reg_no, D0_REG, reg_mask, false, false); else if (restore_from_sp) - m68k_emit_movem (stack_pointer_rtx, 0, - current_frame.reg_no, D0_REG, - current_frame.reg_mask, false, - !TARGET_COLDFIRE); + m68k_emit_movem (stack_pointer_rtx, + post_increment_p ? 0 : skipped_size, + reg_no, D0_REG, reg_mask, false, + post_increment_p); else m68k_emit_movem (frame_pointer_rtx, - -(current_frame.offset + fsize), - current_frame.reg_no, D0_REG, - current_frame.reg_mask, false, false); + skipped_size - (current_frame.offset + fsize), + reg_no, D0_REG, reg_mask, false, false); } if (current_frame.fpu_no > 0) @@ -1364,12 +1416,12 @@ m68k_expand_epilogue (bool sibcall_p) stack_pointer_rtx, GEN_INT (fsize_with_regs))); - if (crtl->calls_eh_return) + if (kind == M68K_EPILOGUE_EH_RETURN) emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, EH_RETURN_STACKADJ_RTX)); - if (!sibcall_p) + if (kind != M68K_EPILOGUE_SIBCALL) emit_jump_insn (ret_rtx); } diff --git a/gcc/config/m68k/m68k.h b/gcc/config/m68k/m68k.h index 7f59dbb3c1b65..512e1e9554c22 100644 --- a/gcc/config/m68k/m68k.h +++ b/gcc/config/m68k/m68k.h @@ -738,6 +738,16 @@ __transfer_from_trampoline () \ #define EPILOGUE_USES(REGNO) m68k_epilogue_uses (REGNO) +/* The kind of return an epilogue is expanded for. Only the exception return + path restores the EH_RETURN_DATA_REGNO registers and applies the unwinder's + stack adjustment. */ +enum m68k_epilogue_kind +{ + M68K_EPILOGUE_NORMAL, + M68K_EPILOGUE_SIBCALL, + M68K_EPILOGUE_EH_RETURN +}; + /* Describe how we implement __builtin_eh_return. */ #define EH_RETURN_DATA_REGNO(N) \ ((N) < 2 ? (N) : INVALID_REGNUM) diff --git a/gcc/config/m68k/m68k.md b/gcc/config/m68k/m68k.md index 227399feeee4b..b999e24a145e5 100644 --- a/gcc/config/m68k/m68k.md +++ b/gcc/config/m68k/m68k.md @@ -6114,7 +6114,7 @@ [(return)] "" { - m68k_expand_epilogue (false); + m68k_expand_epilogue (M68K_EPILOGUE_NORMAL); DONE; }) @@ -6122,7 +6122,31 @@ [(return)] "" { - m68k_expand_epilogue (true); + m68k_expand_epilogue (M68K_EPILOGUE_SIBCALL); + DONE; +}) + +;; The handler address overwrites the return address in the frame, so the +;; transfer of control is still an ordinary "rts"; only the epilogue differs. + +(define_expand "eh_return" + [(use (match_operand:SI 0 "general_operand"))] + "" +{ + emit_move_insn (EH_RETURN_HANDLER_RTX, operands[0]); + emit_jump_insn (gen_eh_return_internal ()); + emit_barrier (); + DONE; +}) + +(define_insn_and_split "eh_return_internal" + [(eh_return)] + "" + "#" + "epilogue_completed" + [(const_int 0)] +{ + m68k_expand_epilogue (M68K_EPILOGUE_EH_RETURN); DONE; }) diff --git a/gcc/testsuite/gcc.target/m68k/pr126736.c b/gcc/testsuite/gcc.target/m68k/pr126736.c new file mode 100644 index 0000000000000..f02700d42921c --- /dev/null +++ b/gcc/testsuite/gcc.target/m68k/pr126736.c @@ -0,0 +1,100 @@ +/* { dg-do run } */ +/* { dg-require-effective-target builtin_eh_return } */ +/* { dg-options "-O2" } */ + +/* A function that calls __builtin_eh_return saves %d0 and %d1, the + EH_RETURN_DATA_REGNO registers. Only the exception return path may + restore them: on a normal return they hold the return value. */ + +extern void use (void *); + +__attribute__((noipa)) +int +leaf_frame (int should_unwind, long offset) +{ + if (should_unwind) + __builtin_eh_return (offset, 0); + return 42; +} + +/* Saves three registers, so the prologue uses moveml but the epilogue has + too few left to restore with it. */ + +__attribute__((noipa)) +int +partial_moveml (int should_unwind, long offset) +{ + register int a __asm__ ("%d2") = 7; + + __asm__ ("" : "+r" (a)); + if (should_unwind) + __builtin_eh_return (offset, 0); + return a + 35; +} + +__attribute__((noipa)) +int +full_moveml (int should_unwind, long offset) +{ + register int a __asm__ ("%d2") = 1; + register int b __asm__ ("%d3") = 2; + register int c __asm__ ("%d4") = 3; + register int d __asm__ ("%a2") = 4; + + __asm__ ("" : "+r" (a), "+r" (b), "+r" (c), "+r" (d)); + if (should_unwind) + __builtin_eh_return (offset, 0); + return a + b + c + d + 32; +} + +__attribute__((noipa)) +int +alloca_frame (int should_unwind, long offset, int size) +{ + char *buffer = __builtin_alloca (size); + + use (buffer); + if (should_unwind) + __builtin_eh_return (offset, 0); + return 42; +} + +/* A frame too big for a 16-bit offset, so the restores go through %a1. */ + +__attribute__((noipa)) +int +big_frame (int should_unwind, long offset) +{ + register int a __asm__ ("%d2") = 7; + volatile char buffer[0x9000]; + + buffer[0] = 1; + use ((void *) buffer); + __asm__ ("" : "+r" (a)); + if (should_unwind) + __builtin_eh_return (offset, 0); + return a + buffer[0] + 34; +} + +__attribute__((noinline, noclone)) +void +use (void *p) +{ + __asm__ ("" :: "r" (p) : "memory"); +} + +int +main (void) +{ + if (leaf_frame (0, 0) != 42) + __builtin_abort (); + if (partial_moveml (0, 0) != 42) + __builtin_abort (); + if (full_moveml (0, 0) != 42) + __builtin_abort (); + if (alloca_frame (0, 0, 64) != 42) + __builtin_abort (); + if (big_frame (0, 0) != 42) + __builtin_abort (); + return 0; +}