Message ID | 20240120142752.1387725-2-hjl.tools@gmail.com |
---|---|
State | New |
Headers | show |
Series | x86: Don't save callee-saved registers if not needed | expand |
On Sat, Jan 20, 2024 at 10:30 PM H.J. Lu <hjl.tools@gmail.com> wrote: > > When an interrupt handler is implemented by an assembly stub which does: > > 1. Save all registers. > 2. Call a C function. > 3. Restore all registers. > 4. Return from interrupt. > > it is completely unnecessary to save and restore any registers in the C > function called by the assembly stub, even if they would normally be > callee-saved. > > Add no_callee_saved_registers function attribute, which is complementary > to no_caller_saved_registers function attribute, to mark a function which > doesn't have any callee-saved registers. Such a function won't save and > restore any registers. Classify function call-saved register handling > type with: > > 1. Default call-saved registers. > 2. No caller-saved registers with no_caller_saved_registers attribute. > 3. No callee-saved registers with no_callee_saved_registers attribute. > > Disallow sibcall if callee is a no_callee_saved_registers function > and caller isn't a no_callee_saved_registers function. Otherwise, > callee-saved registers won't be preserved. > > After a no_callee_saved_registers function is called, all registers may > be clobbered. If the calling function isn't a no_callee_saved_registers > function, we need to preserve all registers which aren't used by function > calls. > > gcc/ > > PR target/103503 > PR target/113312 > * config/i386/i386-expand.cc (ix86_expand_call): Set > call_no_callee_saved_registers to true when calling function > with no_callee_saved_registers attribute. Replace > no_caller_saved_registers check with call_saved_registers check. > * config/i386/i386-options.cc (ix86_set_func_type): Set > call_saved_registers to TYPE_NO_CALLEE_SAVED_REGISTERS for > noreturn function. Disallow no_callee_saved_registers with > interrupt or no_caller_saved_registers attributes together. > (ix86_set_current_function): Replace no_caller_saved_registers > check with call_saved_registers check. > (ix86_handle_no_caller_saved_registers_attribute): Renamed to ... > (ix86_handle_call_saved_registers_attribute): This. > (ix86_gnu_attributes): Add > ix86_handle_call_saved_registers_attribute. > * config/i386/i386.cc (ix86_conditional_register_usage): Replace > no_caller_saved_registers check with call_saved_registers check. > (ix86_function_ok_for_sibcall): Don't allow callee with > no_callee_saved_registers attribute when the calling function > has callee-saved registers. > (ix86_comp_type_attributes): Also check > no_callee_saved_registers. > (ix86_epilogue_uses): Replace no_caller_saved_registers check > with call_saved_registers check. > (ix86_hard_regno_scratch_ok): Likewise. > (ix86_save_reg): Replace no_caller_saved_registers check with > call_saved_registers check. Don't save any registers for > TYPE_NO_CALLEE_SAVED_REGISTERS. Save all registers with > TYPE_DEFAULT_CALL_SAVED_REGISTERS if function with > no_callee_saved_registers attribute is called. > (find_drap_reg): Replace no_caller_saved_registers check with > call_saved_registers check. > * config/i386/i386.h (call_saved_registers_type): New enum. > (machine_function): Replace no_caller_saved_registers with > call_saved_registers. Add call_no_callee_saved_registers. > * doc/extend.texi: Document no_callee_saved_registers attribute. > > gcc/testsuite/ > > PR target/103503 > PR target/113312 > * gcc.dg/torture/no-callee-saved-run-1a.c: New file. > * gcc.dg/torture/no-callee-saved-run-1b.c: Likewise. > * gcc.target/i386/no-callee-saved-1.c: Likewise. > * gcc.target/i386/no-callee-saved-2.c: Likewise. > * gcc.target/i386/no-callee-saved-3.c: Likewise. > * gcc.target/i386/no-callee-saved-4.c: Likewise. > * gcc.target/i386/no-callee-saved-5.c: Likewise. > * gcc.target/i386/no-callee-saved-6.c: Likewise. > * gcc.target/i386/no-callee-saved-7.c: Likewise. > * gcc.target/i386/no-callee-saved-8.c: Likewise. > * gcc.target/i386/no-callee-saved-9.c: Likewise. > * gcc.target/i386/no-callee-saved-10.c: Likewise. > * gcc.target/i386/no-callee-saved-11.c: Likewise. > * gcc.target/i386/no-callee-saved-12.c: Likewise. > * gcc.target/i386/no-callee-saved-13.c: Likewise. > * gcc.target/i386/no-callee-saved-14.c: Likewise. > * gcc.target/i386/no-callee-saved-15.c: Likewise. > * gcc.target/i386/no-callee-saved-16.c: Likewise. > * gcc.target/i386/no-callee-saved-17.c: Likewise. > * gcc.target/i386/no-callee-saved-18.c: Likewise. > --- > gcc/config/i386/i386-expand.cc | 72 ++++++++++++++++--- > gcc/config/i386/i386-options.cc | 49 +++++++++---- > gcc/config/i386/i386.cc | 70 ++++++++++++++---- > gcc/config/i386/i386.h | 20 +++++- > gcc/doc/extend.texi | 8 +++ > .../gcc.dg/torture/no-callee-saved-run-1a.c | 23 ++++++ > .../gcc.dg/torture/no-callee-saved-run-1b.c | 59 +++++++++++++++ > .../gcc.target/i386/no-callee-saved-1.c | 30 ++++++++ > .../gcc.target/i386/no-callee-saved-10.c | 46 ++++++++++++ > .../gcc.target/i386/no-callee-saved-11.c | 11 +++ > .../gcc.target/i386/no-callee-saved-12.c | 10 +++ > .../gcc.target/i386/no-callee-saved-13.c | 16 +++++ > .../gcc.target/i386/no-callee-saved-14.c | 16 +++++ > .../gcc.target/i386/no-callee-saved-15.c | 17 +++++ > .../gcc.target/i386/no-callee-saved-16.c | 16 +++++ > .../gcc.target/i386/no-callee-saved-17.c | 16 +++++ > .../gcc.target/i386/no-callee-saved-18.c | 51 +++++++++++++ > .../gcc.target/i386/no-callee-saved-2.c | 30 ++++++++ > .../gcc.target/i386/no-callee-saved-3.c | 8 +++ > .../gcc.target/i386/no-callee-saved-4.c | 8 +++ > .../gcc.target/i386/no-callee-saved-5.c | 11 +++ > .../gcc.target/i386/no-callee-saved-6.c | 12 ++++ > .../gcc.target/i386/no-callee-saved-7.c | 49 +++++++++++++ > .../gcc.target/i386/no-callee-saved-8.c | 50 +++++++++++++ > .../gcc.target/i386/no-callee-saved-9.c | 49 +++++++++++++ > 25 files changed, 709 insertions(+), 38 deletions(-) > create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c > create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-1.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-10.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-11.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-12.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-13.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-14.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-15.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-16.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-17.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-18.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-2.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-3.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-4.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-5.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-6.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-7.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-8.c > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-9.c > > diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc > index 52754e114f4..c0c7c697440 100644 > --- a/gcc/config/i386/i386-expand.cc > +++ b/gcc/config/i386/i386-expand.cc > @@ -9739,17 +9739,41 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > rtx use = NULL, call; > unsigned int vec_len = 0. > tree fndecl; > + bool call_no_callee_saved_registers = false; > > if (GET_CODE (XEXP (fnaddr, 0)) == SYMBOL_REF) > { > fndecl = SYMBOL_REF_DECL (XEXP (fnaddr, 0)); > - if (fndecl > - && (lookup_attribute ("interrupt", > - TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))) > - error ("interrupt service routine cannot be called directly"); > + if (fndecl) > + { > + if (lookup_attribute ("interrupt", > + TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) > + error ("interrupt service routine cannot be called directly"); > + else if (lookup_attribute ("no_callee_saved_registers", > + TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) > + { > + cfun->machine->call_no_callee_saved_registers = true; > + call_no_callee_saved_registers = true; > + } > + } > } > else > - fndecl = NULL_TREE; > + { > + if (MEM_P (fnaddr)) > + { > + tree mem_expr = MEM_EXPR (fnaddr); > + if (mem_expr != nullptr > + && TREE_CODE (mem_expr) == MEM_REF > + && lookup_attribute ("no_callee_saved_registers", > + TYPE_ATTRIBUTES (TREE_TYPE (mem_expr)))) > + { > + cfun->machine->call_no_callee_saved_registers = true; > + call_no_callee_saved_registers = true; > + } > + } > + > + fndecl = NULL_TREE; > + } > > if (pop == const0_rtx) > pop = NULL; > @@ -9884,13 +9908,18 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > vec[vec_len++] = pop; > } > > - if (cfun->machine->no_caller_saved_registers > + static const char ix86_call_used_regs[] = CALL_USED_REGISTERS; > + > + char clobbered_registers[FIRST_PSEUDO_REGISTER]; > + memset (clobbered_registers, 0, sizeof (clobbered_registers)); > + > + if ((cfun->machine->call_saved_registers > + == TYPE_NO_CALLER_SAVED_REGISTERS) > && (!fndecl > || (!TREE_THIS_VOLATILE (fndecl) > && !lookup_attribute ("no_caller_saved_registers", > TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))))) > { > - static const char ix86_call_used_regs[] = CALL_USED_REGISTERS; > bool is_64bit_ms_abi = (TARGET_64BIT > && ix86_function_abi (fndecl) == MS_ABI); > char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi); > @@ -9903,8 +9932,11 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > || (ix86_call_used_regs[i] & c_mask)) > && !STACK_REGNO_P (i) > && !MMX_REGNO_P (i)) > - clobber_reg (&use, > - gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i)); > + { > + clobber_reg (&use, > + gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i)); > + clobbered_registers[i] = 1; > + } > } > else if (TARGET_64BIT_MS_ABI > && (!callarg2 || INTVAL (callarg2) != -2)) > @@ -9917,6 +9949,7 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > machine_mode mode = SSE_REGNO_P (regno) ? TImode : DImode; > > clobber_reg (&use, gen_rtx_REG (mode, regno)); > + clobbered_registers[i] = 1; > } > > /* Set here, but it may get cleared later. */ > @@ -9953,6 +9986,27 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > resolver could be used which clobbers R11 and R10. */ > clobber_reg (&use, gen_rtx_REG (DImode, R11_REG)); > clobber_reg (&use, gen_rtx_REG (DImode, R10_REG)); > + clobbered_registers[R11_REG] = 1; > + clobbered_registers[R10_REG] = 1; > + } > + > + if (call_no_callee_saved_registers) > + { > + /* After calling a no_callee_saved_registers function, all > + registers may be clobbered. Clobber all registers that are > + not clobbered yet and not used by the callee. */ > + bool is_64bit_ms_abi = (TARGET_64BIT > + && ix86_function_abi (fndecl) == MS_ABI); > + char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi); > + for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++) > + if (!fixed_regs[i] > + && !clobbered_registers[i] It seems to me clobbered_registers is only used here which seems redundant, remove !clobbered_registers[i] should also be fine? > + && !(ix86_call_used_regs[i] == 1 > + || (ix86_call_used_regs[i] & c_mask)) > + && !STACK_REGNO_P (i) > + && !MMX_REGNO_P (i)) > + clobber_reg (&use, > + gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i)); > } > > if (vec_len > 1) > diff --git a/gcc/config/i386/i386-options.cc b/gcc/config/i386/i386-options.cc > index b6f634e9a32..0cdea30599e 100644 > --- a/gcc/config/i386/i386-options.cc > +++ b/gcc/config/i386/i386-options.cc > @@ -3371,6 +3371,10 @@ ix86_simd_clone_adjust (struct cgraph_node *node) > static void > ix86_set_func_type (tree fndecl) > { > + bool has_no_callee_saved_registers > + = lookup_attribute ("no_callee_saved_registers", > + TYPE_ATTRIBUTES (TREE_TYPE (fndecl))); > + > if (cfun->machine->func_type == TYPE_UNKNOWN) > { > if (lookup_attribute ("interrupt", > @@ -3380,12 +3384,18 @@ ix86_set_func_type (tree fndecl) > error_at (DECL_SOURCE_LOCATION (fndecl), > "interrupt and naked attributes are not compatible"); > > + if (has_no_callee_saved_registers) > + error_at (DECL_SOURCE_LOCATION (fndecl), > + "%qs and %qs attributes are not compatible", > + "interrupt", "no_callee_saved_registers"); > + > int nargs = 0; > for (tree arg = DECL_ARGUMENTS (fndecl); > arg; > arg = TREE_CHAIN (arg)) > nargs++; > - cfun->machine->no_caller_saved_registers = true; > + cfun->machine->call_saved_registers > + = TYPE_NO_CALLER_SAVED_REGISTERS; > cfun->machine->func_type > = nargs == 2 ? TYPE_EXCEPTION : TYPE_INTERRUPT; > > @@ -3401,7 +3411,19 @@ ix86_set_func_type (tree fndecl) > cfun->machine->func_type = TYPE_NORMAL; > if (lookup_attribute ("no_caller_saved_registers", > TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) > - cfun->machine->no_caller_saved_registers = true; > + cfun->machine->call_saved_registers > + = TYPE_NO_CALLER_SAVED_REGISTERS; > + if (has_no_callee_saved_registers) > + { > + if (cfun->machine->call_saved_registers > + == TYPE_NO_CALLER_SAVED_REGISTERS) > + error_at (DECL_SOURCE_LOCATION (fndecl), > + "%qs and %qs attributes are not compatible", > + "no_caller_saved_registers", > + "no_callee_saved_registers"); > + cfun->machine->call_saved_registers > + = TYPE_NO_CALLEE_SAVED_REGISTERS; > + } > } > } > } > @@ -3571,7 +3593,7 @@ ix86_set_current_function (tree fndecl) > } > ix86_previous_fndecl = fndecl; > > - static bool prev_no_caller_saved_registers; > + static call_saved_registers_type prev_call_saved_registers; > > /* 64-bit MS and SYSV ABI have different set of call used registers. > Avoid expensive re-initialization of init_regs each time we switch > @@ -3582,12 +3604,13 @@ ix86_set_current_function (tree fndecl) > reinit_regs (); > /* Need to re-initialize init_regs if caller-saved registers are > changed. */ > - else if (prev_no_caller_saved_registers > - != cfun->machine->no_caller_saved_registers) > + else if (prev_call_saved_registers > + != cfun->machine->call_saved_registers) > reinit_regs (); > > if (cfun->machine->func_type != TYPE_NORMAL > - || cfun->machine->no_caller_saved_registers) > + || (cfun->machine->call_saved_registers > + == TYPE_NO_CALLER_SAVED_REGISTERS)) > { > /* Don't allow SSE, MMX nor x87 instructions since they > may change processor state. */ > @@ -3614,12 +3637,12 @@ ix86_set_current_function (tree fndecl) > "the %<no_caller_saved_registers%> attribute", isa); > /* Don't issue the same error twice. */ > cfun->machine->func_type = TYPE_NORMAL; > - cfun->machine->no_caller_saved_registers = false; > + cfun->machine->call_saved_registers > + = TYPE_DEFAULT_CALL_SAVED_REGISTERS; > } > } > > - prev_no_caller_saved_registers > - = cfun->machine->no_caller_saved_registers; > + prev_call_saved_registers = cfun->machine->call_saved_registers; > } > > /* Implement the TARGET_OFFLOAD_OPTIONS hook. */ > @@ -4018,8 +4041,8 @@ ix86_handle_fndecl_attribute (tree *node, tree name, tree args, int, > } > > static tree > -ix86_handle_no_caller_saved_registers_attribute (tree *, tree, tree, > - int, bool *) > +ix86_handle_call_saved_registers_attribute (tree *, tree, tree, > + int, bool *) > { > return NULL_TREE; > } > @@ -4181,7 +4204,9 @@ static const attribute_spec ix86_gnu_attributes[] = > { "interrupt", 0, 0, false, true, true, false, > ix86_handle_interrupt_attribute, NULL }, > { "no_caller_saved_registers", 0, 0, false, true, true, false, > - ix86_handle_no_caller_saved_registers_attribute, NULL }, > + ix86_handle_call_saved_registers_attribute, NULL }, > + { "no_callee_saved_registers", 0, 0, false, true, true, true, > + ix86_handle_call_saved_registers_attribute, NULL }, > { "naked", 0, 0, true, false, false, false, > ix86_handle_fndecl_attribute, NULL }, > { "indirect_branch", 1, 1, true, false, false, false, > diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc > index c5eaeedc7e0..f10e745fb40 100644 > --- a/gcc/config/i386/i386.cc > +++ b/gcc/config/i386/i386.cc > @@ -475,7 +475,9 @@ ix86_conditional_register_usage (void) > except fixed_regs and registers used for function return value > since aggregate_value_p checks call_used_regs[regno] on return > value. */ > - if (cfun && cfun->machine->no_caller_saved_registers) > + if (cfun > + && (cfun->machine->call_saved_registers > + == TYPE_NO_CALLER_SAVED_REGISTERS)) > for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) > if (!fixed_regs[i] && !ix86_function_value_regno_p (i)) > call_used_regs[i] = 0; > @@ -944,7 +946,8 @@ ix86_function_ok_for_sibcall (tree decl, tree exp) > > /* Sibling call isn't OK if there are no caller-saved registers > since all registers must be preserved before return. */ > - if (cfun->machine->no_caller_saved_registers) > + if (cfun->machine->call_saved_registers > + == TYPE_NO_CALLER_SAVED_REGISTERS) > return false; > > /* If we are generating position-independent code, we cannot sibcall > @@ -977,6 +980,14 @@ ix86_function_ok_for_sibcall (tree decl, tree exp) > decl_or_type = type; > } > > + /* Sibling call isn't OK if callee has no callee-saved registers > + and the calling function has callee-saved registers. */ > + if ((cfun->machine->call_saved_registers > + != TYPE_NO_CALLEE_SAVED_REGISTERS) > + && lookup_attribute ("no_callee_saved_registers", > + TYPE_ATTRIBUTES (type))) > + return false; > + > /* If outgoing reg parm stack space changes, we cannot do sibcall. */ > if ((OUTGOING_REG_PARM_STACK_SPACE (type) > != OUTGOING_REG_PARM_STACK_SPACE (TREE_TYPE (current_function_decl))) > @@ -1139,6 +1150,12 @@ ix86_comp_type_attributes (const_tree type1, const_tree type2) > != ix86_function_regparm (type2, NULL)) > return 0; > > + if (lookup_attribute ("no_callee_saved_registers", > + TYPE_ATTRIBUTES (type1)) > + != lookup_attribute ("no_callee_saved_registers", > + TYPE_ATTRIBUTES (type2))) > + return 0; > + > return 1; > } > > @@ -6569,7 +6586,8 @@ ix86_epilogue_uses (int regno) > and restoring registers. Don't explicitly save SP register since > it is always preserved. */ > return (epilogue_completed > - && cfun->machine->no_caller_saved_registers > + && (cfun->machine->call_saved_registers > + == TYPE_NO_CALLER_SAVED_REGISTERS) > && !fixed_regs[regno] > && !STACK_REGNO_P (regno) > && !MMX_REGNO_P (regno)); > @@ -6585,7 +6603,8 @@ ix86_hard_regno_scratch_ok (unsigned int regno) > as a scratch register after epilogue and use REGNO as scratch > register only if it has been used before to avoid saving and > restoring it. */ > - return (!cfun->machine->no_caller_saved_registers > + return ((cfun->machine->call_saved_registers > + != TYPE_NO_CALLER_SAVED_REGISTERS) > || (!epilogue_completed > && df_regs_ever_live_p (regno))); > } > @@ -6595,14 +6614,32 @@ ix86_hard_regno_scratch_ok (unsigned int regno) > bool > ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined) > { > - /* If there are no caller-saved registers, we preserve all registers, > - except for MMX and x87 registers which aren't supported when saving > - and restoring registers. Don't explicitly save SP register since > - it is always preserved. */ > - if (cfun->machine->no_caller_saved_registers) > - { > - /* Don't preserve registers used for function return value. */ > - rtx reg = crtl->return_rtx; > + rtx reg; > + > + switch (cfun->machine->call_saved_registers) > + { > + case TYPE_DEFAULT_CALL_SAVED_REGISTERS: > + /* If any no_callee_saved_registers functions are called and this > + is not a no_callee_saved_registers function, we preserve all > + registers which aren't used by function calls, except for MMX > + and x87 registers which aren't supported when saving and > + restoring registers. Don't explicitly save SP register since > + it is always preserved. */ > + if (cfun->machine->call_no_callee_saved_registers) > + return (!fixed_regs[regno] > + && !call_used_regs[regno] > + && !STACK_REGNO_P (regno) > + && !MMX_REGNO_P (regno)); > + break; > + > + case TYPE_NO_CALLER_SAVED_REGISTERS: > + /* If there are no caller-saved registers, we preserve all > + registers, except for MMX and x87 registers which aren't > + supported when saving and restoring registers. Don't > + explicitly save SP register since it is always preserved. > + > + Don't preserve registers used for function return value. */ > + reg = crtl->return_rtx; > if (reg) > { > unsigned int i = REGNO (reg); > @@ -6618,6 +6655,9 @@ ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined) > && !MMX_REGNO_P (regno) > && (regno != HARD_FRAME_POINTER_REGNUM > || !frame_pointer_needed)); > + > + case TYPE_NO_CALLEE_SAVED_REGISTERS: > + return false; > } > > if (regno == REAL_PIC_OFFSET_TABLE_REGNUM > @@ -7717,7 +7757,8 @@ find_drap_reg (void) > registers in epilogue, DRAP must not use caller-saved > register in such case. */ > if (DECL_STATIC_CHAIN (decl) > - || cfun->machine->no_caller_saved_registers > + || (cfun->machine->call_saved_registers > + == TYPE_NO_CALLER_SAVED_REGISTERS) > || crtl->tail_call_emit) > return R13_REG; > > @@ -7730,7 +7771,8 @@ find_drap_reg (void) > registers in epilogue, DRAP must not use caller-saved > register in such case. */ > if (DECL_STATIC_CHAIN (decl) > - || cfun->machine->no_caller_saved_registers > + || (cfun->machine->call_saved_registers > + == TYPE_NO_CALLER_SAVED_REGISTERS) > || crtl->tail_call_emit > || crtl->calls_eh_return) > return DI_REG; > diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h > index b9c574e62e1..bf1ca6014f5 100644 > --- a/gcc/config/i386/i386.h > +++ b/gcc/config/i386/i386.h > @@ -2724,6 +2724,17 @@ enum function_type > TYPE_EXCEPTION > }; > > +enum call_saved_registers_type > +{ > + TYPE_DEFAULT_CALL_SAVED_REGISTERS = 0, > + /* The current function is a function specified with the "interrupt" > + or "no_caller_saved_registers" attribute. */ > + TYPE_NO_CALLER_SAVED_REGISTERS, > + /* The current function is a function specified with the "noreturn" > + or "no_callee_saved_registers" attribute. */ > + TYPE_NO_CALLEE_SAVED_REGISTERS > +}; > + > enum queued_insn_type > { > TYPE_NONE = 0, > @@ -2793,9 +2804,12 @@ struct GTY(()) machine_function { > /* How to generate function return. */ > ENUM_BITFIELD(indirect_branch) function_return_type : 3; > > - /* If true, the current function is a function specified with > - the "interrupt" or "no_caller_saved_registers" attribute. */ > - BOOL_BITFIELD no_caller_saved_registers : 1; > + /* Call saved registers type. */ > + ENUM_BITFIELD(call_saved_registers_type) call_saved_registers : 2; > + > + /* If true, the current function calls no_callee_saved_registers > + functions. */ > + BOOL_BITFIELD call_no_callee_saved_registers : 1; > > /* If true, there is register available for argument passing. This > is used only in ix86_function_ok_for_sibcall by 32-bit to determine > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > index 0bc586d120e..4cafa6d416b 100644 > --- a/gcc/doc/extend.texi > +++ b/gcc/doc/extend.texi > @@ -6767,6 +6767,14 @@ On x86-32 targets, the @code{stdcall} attribute causes the compiler to > assume that the called function pops off the stack space used to > pass arguments, unless it takes a variable number of arguments. > > +@cindex @code{no_callee_saved_registers} function attribute, x86 > +@item no_callee_saved_registers > +Use this attribute to indicate that the specified function has no > +callee-saved registers. That is, all registers can be used as scratch > +registers. For example, this attribute can be used for a function > +called from the interrupt handler assembly stub which will preserve > +all registers and return from interrupt. > + > @cindex @code{no_caller_saved_registers} function attribute, x86 > @item no_caller_saved_registers > Use this attribute to indicate that the specified function has no > diff --git a/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c > new file mode 100644 > index 00000000000..8c48ec0c79a > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c > @@ -0,0 +1,23 @@ > +/* { dg-do run { target i?86-*-* x86_64-*-* } } */ > +/* { dg-additional-sources no-callee-saved-run-1b.c } */ > + > +extern void bar0 (int, int, int, int, int, int, int, int, int) > + __attribute__ ((no_callee_saved_registers)); > + > +void > +foo (void) > +{ > + bar0 (0, 1, 2, 3, 4, 5, 6, 7, 8); > +} > + > +int > +bar (int x) > +{ > + return x; > +} > + > +void > +bad (void) > +{ > + __builtin_abort (); > +} > diff --git a/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c > new file mode 100644 > index 00000000000..b3ce7e72e85 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c > @@ -0,0 +1,59 @@ > +/* { dg-do compile { target i?86-*-* x86_64-*-* } } */ > + > +extern void foo (void); > +extern void bad (void); > +extern int bar (int); > + > +void > +__attribute__ ((no_callee_saved_registers)) > +bar0 (int i0, int i1, int i2, int i3, int i4, int i5, int i6, > + int i7, int i8) > +{ > + if (i0 != 0) > + bad (); > + > + if (i1 != 1) > + bad (); > + > + if (i2 != 2) > + bad (); > + > + if (i3 != 3) > + bad (); > + > + if (i4 != 4) > + bad (); > + > + if (i5 != 5) > + bad (); > + > + if (i6 != 6) > + bad (); > + > + if (i7 != 7) > + bad (); > + > + if (i8 != 8) > + bad (); > + > + int a,b,c,d,e,f,i; > + a = bar (5); > + b = bar (a); > + c = bar (b); > + d = bar (c); > + e = bar (d); > + f = bar (e); > + for (i = 1; i < 10; i++) > + { > + a += bar (a + i) + bar (b + i) + > + bar (c + i) + bar (d + i) + > + bar (e + i) + bar (f + i); > + } > +} > + > +int > +main () > +{ > + foo (); > + return 0; > +} > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c > new file mode 100644 > index 00000000000..8fe36eb5198 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c > @@ -0,0 +1,30 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +extern int bar (int) > +#ifndef __x86_64__ > +__attribute__ ((regparm(3))) > +#endif > +; > + > +__attribute__ ((no_callee_saved_registers)) > +void > +foo (void *frame) > +{ > + int a,b,c,d,e,f,i; > + a = bar (5); > + b = bar (a); > + c = bar (b); > + d = bar (c); > + e = bar (d); > + f = bar (e); > + for (i = 1; i < 10; i++) > + { > + a += bar (a + i) + bar (b + i) + > + bar (c + i) + bar (d + i) + > + bar (e + i) + bar (f + i); > + } > +} > + > +/* { dg-final { scan-assembler-not "push" } } */ > +/* { dg-final { scan-assembler-not "pop" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c > new file mode 100644 > index 00000000000..87766c6cd88 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c > @@ -0,0 +1,46 @@ > +/* { dg-do compile { target *-*-linux* } } */ > +/* { dg-options "-O2 -mgeneral-regs-only -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +extern void bar (void) __attribute__ ((no_callee_saved_registers)); > + > +__attribute__ ((no_caller_saved_registers)) > +void > +foo (void) > +{ > + bar (); > +} > + > +/* foo must save and restore all caller saved registers since bar won't > + preserve any. */ > +/* { dg-final { scan-assembler-not "jmp\[\\t \]+_?bar" } } */ > +/* { dg-final { scan-assembler "call\[\\t \]+_?bar" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)ax" 1 } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)cx" 1 } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)dx" 1 } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)si" 1 } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)di" 1 } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r8" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r9" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r10" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r11" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)ax" 1 } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)cx" 1 } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)dx" 1 } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)si" 1 } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)di" 1 } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r8" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r9" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r10" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r11" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c > new file mode 100644 > index 00000000000..902a764489e > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c > @@ -0,0 +1,11 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2" } */ > + > +extern void foo (void); /* { dg-note "previous declaration" } */ > + > +__attribute__ ((no_callee_saved_registers)) > +void > +foo (void) /* { dg-error "conflicting types" } */ > +{ > +} > + > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c > new file mode 100644 > index 00000000000..5524a4af29c > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c > @@ -0,0 +1,10 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2" } */ > + > +extern void foo (void) __attribute__ ((no_callee_saved_registers)); /* { dg-note "previous declaration" } */ > + > +void > +foo (void) /* { dg-error "conflicting types" } */ > +{ > +} > + > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c > new file mode 100644 > index 00000000000..6757e72d848 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c > @@ -0,0 +1,16 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +extern void foo (void); > + > +__attribute__ ((no_callee_saved_registers)) > +void > +bar (void) > +{ > + foo (); > +} > + > +/* { dg-final { scan-assembler-not "push" } } */ > +/* { dg-final { scan-assembler-not "pop" } } */ > +/* { dg-final { scan-assembler-not "call\[\\t \]+_?foo" } } */ > +/* { dg-final { scan-assembler "jmp\[\\t \]+_?foo" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c > new file mode 100644 > index 00000000000..2239e286e6a > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c > @@ -0,0 +1,16 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +extern void bar (void) __attribute__ ((no_callee_saved_registers)); > + > +__attribute__ ((no_callee_saved_registers)) > +void > +foo (void) > +{ > + bar (); > +} > + > +/* { dg-final { scan-assembler-not "push" } } */ > +/* { dg-final { scan-assembler-not "pop" } } */ > +/* { dg-final { scan-assembler "jmp\[\\t \]+_?bar" } } */ > +/* { dg-final { scan-assembler-not "call\[\\t \]+_?bar" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c > new file mode 100644 > index 00000000000..10135fec9c1 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c > @@ -0,0 +1,17 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); > +extern fn_t bar; > + > +__attribute__ ((no_callee_saved_registers)) > +void > +foo (void) > +{ > + bar (); > +} > + > +/* { dg-final { scan-assembler-not "push" } } */ > +/* { dg-final { scan-assembler-not "pop" } } */ > +/* { dg-final { scan-assembler "jmp" } } */ > +/* { dg-final { scan-assembler-not "call\[\\t \]+" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c > new file mode 100644 > index 00000000000..112d1764f3e > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c > @@ -0,0 +1,16 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); > + > +__attribute__ ((no_callee_saved_registers)) > +void > +foo (fn_t bar) > +{ > + bar (); > +} > + > +/* { dg-final { scan-assembler-not "push" } } */ > +/* { dg-final { scan-assembler-not "pop" } } */ > +/* { dg-final { scan-assembler "jmp" } } */ > +/* { dg-final { scan-assembler-not "call\[\\t \]+" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c > new file mode 100644 > index 00000000000..1fd5daadf08 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c > @@ -0,0 +1,16 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +extern void foo (void) __attribute__ ((no_caller_saved_registers)); > + > +__attribute__ ((no_callee_saved_registers)) > +void > +bar (void) > +{ > + foo (); > +} > + > +/* { dg-final { scan-assembler-not "push" } } */ > +/* { dg-final { scan-assembler-not "pop" } } */ > +/* { dg-final { scan-assembler-not "call\[\\t \]+_?foo" } } */ > +/* { dg-final { scan-assembler "jmp\[\\t \]+_?foo" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c > new file mode 100644 > index 00000000000..e7101009be4 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c > @@ -0,0 +1,51 @@ > +/* { dg-do compile { target *-*-linux* } } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +#include <stdint.h> > + > +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); > + > +void > +foo (uintptr_t p) > +{ > + ((fn_t) p) (); > +} > + > +/* foo must save and restore all caller saved registers since bar won't > + preserve any. */ > +/* { dg-final { scan-assembler-not "jmp" } } */ > +/* { dg-final { scan-assembler "call\[\\t \]+" } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c > new file mode 100644 > index 00000000000..ce4ab3b1799 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c > @@ -0,0 +1,30 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +extern int bar (int) __attribute__ ((no_caller_saved_registers)) > +#ifndef __x86_64__ > +__attribute__ ((regparm(3))) > +#endif > +; > + > +__attribute__ ((no_callee_saved_registers)) > +void > +foo (void *frame) > +{ > + int a,b,c,d,e,f,i; > + a = bar (5); > + b = bar (a); > + c = bar (b); > + d = bar (c); > + e = bar (d); > + f = bar (e); > + for (i = 1; i < 10; i++) > + { > + a += bar (a + i) + bar (b + i) + > + bar (c + i) + bar (d + i) + > + bar (e + i) + bar (f + i); > + } > +} > + > +/* { dg-final { scan-assembler-not "push" } } */ > +/* { dg-final { scan-assembler-not "pop" } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c > new file mode 100644 > index 00000000000..453272e11c0 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c > @@ -0,0 +1,8 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2" } */ > + > +__attribute__ ((no_callee_saved_registers, no_caller_saved_registers)) > +void > +foo (void) /* { dg-error "attributes are not compatible" } */ > +{ > +} > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c > new file mode 100644 > index 00000000000..ec566aaf09f > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c > @@ -0,0 +1,8 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -mgeneral-regs-only" } */ > + > +__attribute__ ((no_callee_saved_registers, interrupt)) > +void > +foo (void *frame) /* { dg-error "attributes are not compatible" } */ > +{ > +} > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c > new file mode 100644 > index 00000000000..b28b211986a > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c > @@ -0,0 +1,11 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2" } */ > + > +typedef void (*fn_t) (void *) __attribute__ ((no_callee_saved_registers)); > + > +void > +foo (void *frame) > +{ > +} > + > +fn_t func = foo; /* { dg-error "incompatible pointer type" } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c > new file mode 100644 > index 00000000000..a7b3bdabf43 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c > @@ -0,0 +1,12 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2" } */ > + > +typedef void (*fn_t) (void *) __attribute__ ((no_callee_saved_registers)); > + > +__attribute__ ((no_callee_saved_registers)) > +void > +foo (void *frame) > +{ > +} > + > +fn_t func = foo; > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c > new file mode 100644 > index 00000000000..a1837fdfd4b > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c > @@ -0,0 +1,49 @@ > +/* { dg-do compile { target *-*-linux* } } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +extern void bar (void) __attribute__ ((no_callee_saved_registers)); > + > +void > +foo (void) > +{ > + bar (); > +} > + > +/* foo must save and restore all caller saved registers since bar won't > + preserve any. */ > +/* { dg-final { scan-assembler-not "jmp\[\\t \]+_?bar" } } */ > +/* { dg-final { scan-assembler "call\[\\t \]+_?bar" } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c > new file mode 100644 > index 00000000000..90b98a21aef > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c > @@ -0,0 +1,50 @@ > +/* { dg-do compile { target *-*-linux* } } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); > +extern fn_t bar; > + > +void > +foo (void) > +{ > + bar (); > +} > + > +/* foo must save and restore all caller saved registers since bar won't > + preserve any. */ > +/* { dg-final { scan-assembler-not "jmp" } } */ > +/* { dg-final { scan-assembler "call\[\\t \]+" } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c > new file mode 100644 > index 00000000000..e261100ac1a > --- /dev/null > +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c > @@ -0,0 +1,49 @@ > +/* { dg-do compile { target *-*-linux* } } */ > +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ > + > +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); > + > +void > +foo (fn_t bar) > +{ > + bar (); > +} > + > +/* foo must save and restore all caller saved registers since bar won't > + preserve any. */ > +/* { dg-final { scan-assembler-not "jmp" } } */ > +/* { dg-final { scan-assembler "call\[\\t \]+" } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ > +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ > +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ > +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ > +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ > +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ > +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ > -- > 2.43.0 > -- BR, Hongtao
On Sun, Jan 21, 2024 at 8:03 PM Hongtao Liu <crazylht@gmail.com> wrote: > > On Sat, Jan 20, 2024 at 10:30 PM H.J. Lu <hjl.tools@gmail.com> wrote: > > > > When an interrupt handler is implemented by an assembly stub which does: > > > > 1. Save all registers. > > 2. Call a C function. > > 3. Restore all registers. > > 4. Return from interrupt. > > > > it is completely unnecessary to save and restore any registers in the C > > function called by the assembly stub, even if they would normally be > > callee-saved. > > > > Add no_callee_saved_registers function attribute, which is complementary > > to no_caller_saved_registers function attribute, to mark a function which > > doesn't have any callee-saved registers. Such a function won't save and > > restore any registers. Classify function call-saved register handling > > type with: > > > > 1. Default call-saved registers. > > 2. No caller-saved registers with no_caller_saved_registers attribute. > > 3. No callee-saved registers with no_callee_saved_registers attribute. > > > > Disallow sibcall if callee is a no_callee_saved_registers function > > and caller isn't a no_callee_saved_registers function. Otherwise, > > callee-saved registers won't be preserved. > > > > After a no_callee_saved_registers function is called, all registers may > > be clobbered. If the calling function isn't a no_callee_saved_registers > > function, we need to preserve all registers which aren't used by function > > calls. > > > > gcc/ > > > > PR target/103503 > > PR target/113312 > > * config/i386/i386-expand.cc (ix86_expand_call): Set > > call_no_callee_saved_registers to true when calling function > > with no_callee_saved_registers attribute. Replace > > no_caller_saved_registers check with call_saved_registers check. > > * config/i386/i386-options.cc (ix86_set_func_type): Set > > call_saved_registers to TYPE_NO_CALLEE_SAVED_REGISTERS for > > noreturn function. Disallow no_callee_saved_registers with > > interrupt or no_caller_saved_registers attributes together. > > (ix86_set_current_function): Replace no_caller_saved_registers > > check with call_saved_registers check. > > (ix86_handle_no_caller_saved_registers_attribute): Renamed to ... > > (ix86_handle_call_saved_registers_attribute): This. > > (ix86_gnu_attributes): Add > > ix86_handle_call_saved_registers_attribute. > > * config/i386/i386.cc (ix86_conditional_register_usage): Replace > > no_caller_saved_registers check with call_saved_registers check. > > (ix86_function_ok_for_sibcall): Don't allow callee with > > no_callee_saved_registers attribute when the calling function > > has callee-saved registers. > > (ix86_comp_type_attributes): Also check > > no_callee_saved_registers. > > (ix86_epilogue_uses): Replace no_caller_saved_registers check > > with call_saved_registers check. > > (ix86_hard_regno_scratch_ok): Likewise. > > (ix86_save_reg): Replace no_caller_saved_registers check with > > call_saved_registers check. Don't save any registers for > > TYPE_NO_CALLEE_SAVED_REGISTERS. Save all registers with > > TYPE_DEFAULT_CALL_SAVED_REGISTERS if function with > > no_callee_saved_registers attribute is called. > > (find_drap_reg): Replace no_caller_saved_registers check with > > call_saved_registers check. > > * config/i386/i386.h (call_saved_registers_type): New enum. > > (machine_function): Replace no_caller_saved_registers with > > call_saved_registers. Add call_no_callee_saved_registers. > > * doc/extend.texi: Document no_callee_saved_registers attribute. > > > > gcc/testsuite/ > > > > PR target/103503 > > PR target/113312 > > * gcc.dg/torture/no-callee-saved-run-1a.c: New file. > > * gcc.dg/torture/no-callee-saved-run-1b.c: Likewise. > > * gcc.target/i386/no-callee-saved-1.c: Likewise. > > * gcc.target/i386/no-callee-saved-2.c: Likewise. > > * gcc.target/i386/no-callee-saved-3.c: Likewise. > > * gcc.target/i386/no-callee-saved-4.c: Likewise. > > * gcc.target/i386/no-callee-saved-5.c: Likewise. > > * gcc.target/i386/no-callee-saved-6.c: Likewise. > > * gcc.target/i386/no-callee-saved-7.c: Likewise. > > * gcc.target/i386/no-callee-saved-8.c: Likewise. > > * gcc.target/i386/no-callee-saved-9.c: Likewise. > > * gcc.target/i386/no-callee-saved-10.c: Likewise. > > * gcc.target/i386/no-callee-saved-11.c: Likewise. > > * gcc.target/i386/no-callee-saved-12.c: Likewise. > > * gcc.target/i386/no-callee-saved-13.c: Likewise. > > * gcc.target/i386/no-callee-saved-14.c: Likewise. > > * gcc.target/i386/no-callee-saved-15.c: Likewise. > > * gcc.target/i386/no-callee-saved-16.c: Likewise. > > * gcc.target/i386/no-callee-saved-17.c: Likewise. > > * gcc.target/i386/no-callee-saved-18.c: Likewise. > > --- > > gcc/config/i386/i386-expand.cc | 72 ++++++++++++++++--- > > gcc/config/i386/i386-options.cc | 49 +++++++++---- > > gcc/config/i386/i386.cc | 70 ++++++++++++++---- > > gcc/config/i386/i386.h | 20 +++++- > > gcc/doc/extend.texi | 8 +++ > > .../gcc.dg/torture/no-callee-saved-run-1a.c | 23 ++++++ > > .../gcc.dg/torture/no-callee-saved-run-1b.c | 59 +++++++++++++++ > > .../gcc.target/i386/no-callee-saved-1.c | 30 ++++++++ > > .../gcc.target/i386/no-callee-saved-10.c | 46 ++++++++++++ > > .../gcc.target/i386/no-callee-saved-11.c | 11 +++ > > .../gcc.target/i386/no-callee-saved-12.c | 10 +++ > > .../gcc.target/i386/no-callee-saved-13.c | 16 +++++ > > .../gcc.target/i386/no-callee-saved-14.c | 16 +++++ > > .../gcc.target/i386/no-callee-saved-15.c | 17 +++++ > > .../gcc.target/i386/no-callee-saved-16.c | 16 +++++ > > .../gcc.target/i386/no-callee-saved-17.c | 16 +++++ > > .../gcc.target/i386/no-callee-saved-18.c | 51 +++++++++++++ > > .../gcc.target/i386/no-callee-saved-2.c | 30 ++++++++ > > .../gcc.target/i386/no-callee-saved-3.c | 8 +++ > > .../gcc.target/i386/no-callee-saved-4.c | 8 +++ > > .../gcc.target/i386/no-callee-saved-5.c | 11 +++ > > .../gcc.target/i386/no-callee-saved-6.c | 12 ++++ > > .../gcc.target/i386/no-callee-saved-7.c | 49 +++++++++++++ > > .../gcc.target/i386/no-callee-saved-8.c | 50 +++++++++++++ > > .../gcc.target/i386/no-callee-saved-9.c | 49 +++++++++++++ > > 25 files changed, 709 insertions(+), 38 deletions(-) > > create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c > > create mode 100644 gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-1.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-10.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-11.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-12.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-13.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-14.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-15.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-16.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-17.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-18.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-2.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-3.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-4.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-5.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-6.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-7.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-8.c > > create mode 100644 gcc/testsuite/gcc.target/i386/no-callee-saved-9.c > > > > diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc > > index 52754e114f4..c0c7c697440 100644 > > --- a/gcc/config/i386/i386-expand.cc > > +++ b/gcc/config/i386/i386-expand.cc > > @@ -9739,17 +9739,41 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > > rtx use = NULL, call; > > unsigned int vec_len = 0. > > tree fndecl; > > + bool call_no_callee_saved_registers = false; > > > > if (GET_CODE (XEXP (fnaddr, 0)) == SYMBOL_REF) > > { > > fndecl = SYMBOL_REF_DECL (XEXP (fnaddr, 0)); > > - if (fndecl > > - && (lookup_attribute ("interrupt", > > - TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))) > > - error ("interrupt service routine cannot be called directly"); > > + if (fndecl) > > + { > > + if (lookup_attribute ("interrupt", > > + TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) > > + error ("interrupt service routine cannot be called directly"); > > + else if (lookup_attribute ("no_callee_saved_registers", > > + TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) > > + { > > + cfun->machine->call_no_callee_saved_registers = true; > > + call_no_callee_saved_registers = true; > > + } > > + } > > } > > else > > - fndecl = NULL_TREE; > > + { > > + if (MEM_P (fnaddr)) > > + { > > + tree mem_expr = MEM_EXPR (fnaddr); > > + if (mem_expr != nullptr > > + && TREE_CODE (mem_expr) == MEM_REF > > + && lookup_attribute ("no_callee_saved_registers", > > + TYPE_ATTRIBUTES (TREE_TYPE (mem_expr)))) > > + { > > + cfun->machine->call_no_callee_saved_registers = true; > > + call_no_callee_saved_registers = true; > > + } > > + } > > + > > + fndecl = NULL_TREE; > > + } > > > > if (pop == const0_rtx) > > pop = NULL; > > @@ -9884,13 +9908,18 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > > vec[vec_len++] = pop; > > } > > > > - if (cfun->machine->no_caller_saved_registers > > + static const char ix86_call_used_regs[] = CALL_USED_REGISTERS; > > + > > + char clobbered_registers[FIRST_PSEUDO_REGISTER]; > > + memset (clobbered_registers, 0, sizeof (clobbered_registers)); > > + > > + if ((cfun->machine->call_saved_registers > > + == TYPE_NO_CALLER_SAVED_REGISTERS) > > && (!fndecl > > || (!TREE_THIS_VOLATILE (fndecl) > > && !lookup_attribute ("no_caller_saved_registers", > > TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))))) > > { > > - static const char ix86_call_used_regs[] = CALL_USED_REGISTERS; > > bool is_64bit_ms_abi = (TARGET_64BIT > > && ix86_function_abi (fndecl) == MS_ABI); > > char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi); > > @@ -9903,8 +9932,11 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > > || (ix86_call_used_regs[i] & c_mask)) > > && !STACK_REGNO_P (i) > > && !MMX_REGNO_P (i)) > > - clobber_reg (&use, > > - gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i)); > > + { > > + clobber_reg (&use, > > + gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i)); > > + clobbered_registers[i] = 1; > > + } > > } > > else if (TARGET_64BIT_MS_ABI > > && (!callarg2 || INTVAL (callarg2) != -2)) > > @@ -9917,6 +9949,7 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > > machine_mode mode = SSE_REGNO_P (regno) ? TImode : DImode; > > > > clobber_reg (&use, gen_rtx_REG (mode, regno)); > > + clobbered_registers[i] = 1; > > } > > > > /* Set here, but it may get cleared later. */ > > @@ -9953,6 +9986,27 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, > > resolver could be used which clobbers R11 and R10. */ > > clobber_reg (&use, gen_rtx_REG (DImode, R11_REG)); > > clobber_reg (&use, gen_rtx_REG (DImode, R10_REG)); > > + clobbered_registers[R11_REG] = 1; > > + clobbered_registers[R10_REG] = 1; > > + } > > + > > + if (call_no_callee_saved_registers) > > + { > > + /* After calling a no_callee_saved_registers function, all > > + registers may be clobbered. Clobber all registers that are > > + not clobbered yet and not used by the callee. */ > > + bool is_64bit_ms_abi = (TARGET_64BIT > > + && ix86_function_abi (fndecl) == MS_ABI); > > + char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi); > > + for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++) > > + if (!fixed_regs[i] > > + && !clobbered_registers[i] > It seems to me clobbered_registers is only used here which seems > redundant, remove !clobbered_registers[i] should also be fine? > You are right. Here is the v2 patch set: https://patchwork.sourceware.org/project/gcc/list/?series=30050 Changes in v2: 1. Rebase against commit f9df00340e3 2. Don't add redundant clobbered_registers check in ix86_expand_call. Thanks.
diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc index 52754e114f4..c0c7c697440 100644 --- a/gcc/config/i386/i386-expand.cc +++ b/gcc/config/i386/i386-expand.cc @@ -9739,17 +9739,41 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, rtx use = NULL, call; unsigned int vec_len = 0; tree fndecl; + bool call_no_callee_saved_registers = false; if (GET_CODE (XEXP (fnaddr, 0)) == SYMBOL_REF) { fndecl = SYMBOL_REF_DECL (XEXP (fnaddr, 0)); - if (fndecl - && (lookup_attribute ("interrupt", - TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))) - error ("interrupt service routine cannot be called directly"); + if (fndecl) + { + if (lookup_attribute ("interrupt", + TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) + error ("interrupt service routine cannot be called directly"); + else if (lookup_attribute ("no_callee_saved_registers", + TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) + { + cfun->machine->call_no_callee_saved_registers = true; + call_no_callee_saved_registers = true; + } + } } else - fndecl = NULL_TREE; + { + if (MEM_P (fnaddr)) + { + tree mem_expr = MEM_EXPR (fnaddr); + if (mem_expr != nullptr + && TREE_CODE (mem_expr) == MEM_REF + && lookup_attribute ("no_callee_saved_registers", + TYPE_ATTRIBUTES (TREE_TYPE (mem_expr)))) + { + cfun->machine->call_no_callee_saved_registers = true; + call_no_callee_saved_registers = true; + } + } + + fndecl = NULL_TREE; + } if (pop == const0_rtx) pop = NULL; @@ -9884,13 +9908,18 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, vec[vec_len++] = pop; } - if (cfun->machine->no_caller_saved_registers + static const char ix86_call_used_regs[] = CALL_USED_REGISTERS; + + char clobbered_registers[FIRST_PSEUDO_REGISTER]; + memset (clobbered_registers, 0, sizeof (clobbered_registers)); + + if ((cfun->machine->call_saved_registers + == TYPE_NO_CALLER_SAVED_REGISTERS) && (!fndecl || (!TREE_THIS_VOLATILE (fndecl) && !lookup_attribute ("no_caller_saved_registers", TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))))) { - static const char ix86_call_used_regs[] = CALL_USED_REGISTERS; bool is_64bit_ms_abi = (TARGET_64BIT && ix86_function_abi (fndecl) == MS_ABI); char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi); @@ -9903,8 +9932,11 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, || (ix86_call_used_regs[i] & c_mask)) && !STACK_REGNO_P (i) && !MMX_REGNO_P (i)) - clobber_reg (&use, - gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i)); + { + clobber_reg (&use, + gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i)); + clobbered_registers[i] = 1; + } } else if (TARGET_64BIT_MS_ABI && (!callarg2 || INTVAL (callarg2) != -2)) @@ -9917,6 +9949,7 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, machine_mode mode = SSE_REGNO_P (regno) ? TImode : DImode; clobber_reg (&use, gen_rtx_REG (mode, regno)); + clobbered_registers[i] = 1; } /* Set here, but it may get cleared later. */ @@ -9953,6 +9986,27 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1, resolver could be used which clobbers R11 and R10. */ clobber_reg (&use, gen_rtx_REG (DImode, R11_REG)); clobber_reg (&use, gen_rtx_REG (DImode, R10_REG)); + clobbered_registers[R11_REG] = 1; + clobbered_registers[R10_REG] = 1; + } + + if (call_no_callee_saved_registers) + { + /* After calling a no_callee_saved_registers function, all + registers may be clobbered. Clobber all registers that are + not clobbered yet and not used by the callee. */ + bool is_64bit_ms_abi = (TARGET_64BIT + && ix86_function_abi (fndecl) == MS_ABI); + char c_mask = CALL_USED_REGISTERS_MASK (is_64bit_ms_abi); + for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++) + if (!fixed_regs[i] + && !clobbered_registers[i] + && !(ix86_call_used_regs[i] == 1 + || (ix86_call_used_regs[i] & c_mask)) + && !STACK_REGNO_P (i) + && !MMX_REGNO_P (i)) + clobber_reg (&use, + gen_rtx_REG (GET_MODE (regno_reg_rtx[i]), i)); } if (vec_len > 1) diff --git a/gcc/config/i386/i386-options.cc b/gcc/config/i386/i386-options.cc index b6f634e9a32..0cdea30599e 100644 --- a/gcc/config/i386/i386-options.cc +++ b/gcc/config/i386/i386-options.cc @@ -3371,6 +3371,10 @@ ix86_simd_clone_adjust (struct cgraph_node *node) static void ix86_set_func_type (tree fndecl) { + bool has_no_callee_saved_registers + = lookup_attribute ("no_callee_saved_registers", + TYPE_ATTRIBUTES (TREE_TYPE (fndecl))); + if (cfun->machine->func_type == TYPE_UNKNOWN) { if (lookup_attribute ("interrupt", @@ -3380,12 +3384,18 @@ ix86_set_func_type (tree fndecl) error_at (DECL_SOURCE_LOCATION (fndecl), "interrupt and naked attributes are not compatible"); + if (has_no_callee_saved_registers) + error_at (DECL_SOURCE_LOCATION (fndecl), + "%qs and %qs attributes are not compatible", + "interrupt", "no_callee_saved_registers"); + int nargs = 0; for (tree arg = DECL_ARGUMENTS (fndecl); arg; arg = TREE_CHAIN (arg)) nargs++; - cfun->machine->no_caller_saved_registers = true; + cfun->machine->call_saved_registers + = TYPE_NO_CALLER_SAVED_REGISTERS; cfun->machine->func_type = nargs == 2 ? TYPE_EXCEPTION : TYPE_INTERRUPT; @@ -3401,7 +3411,19 @@ ix86_set_func_type (tree fndecl) cfun->machine->func_type = TYPE_NORMAL; if (lookup_attribute ("no_caller_saved_registers", TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))) - cfun->machine->no_caller_saved_registers = true; + cfun->machine->call_saved_registers + = TYPE_NO_CALLER_SAVED_REGISTERS; + if (has_no_callee_saved_registers) + { + if (cfun->machine->call_saved_registers + == TYPE_NO_CALLER_SAVED_REGISTERS) + error_at (DECL_SOURCE_LOCATION (fndecl), + "%qs and %qs attributes are not compatible", + "no_caller_saved_registers", + "no_callee_saved_registers"); + cfun->machine->call_saved_registers + = TYPE_NO_CALLEE_SAVED_REGISTERS; + } } } } @@ -3571,7 +3593,7 @@ ix86_set_current_function (tree fndecl) } ix86_previous_fndecl = fndecl; - static bool prev_no_caller_saved_registers; + static call_saved_registers_type prev_call_saved_registers; /* 64-bit MS and SYSV ABI have different set of call used registers. Avoid expensive re-initialization of init_regs each time we switch @@ -3582,12 +3604,13 @@ ix86_set_current_function (tree fndecl) reinit_regs (); /* Need to re-initialize init_regs if caller-saved registers are changed. */ - else if (prev_no_caller_saved_registers - != cfun->machine->no_caller_saved_registers) + else if (prev_call_saved_registers + != cfun->machine->call_saved_registers) reinit_regs (); if (cfun->machine->func_type != TYPE_NORMAL - || cfun->machine->no_caller_saved_registers) + || (cfun->machine->call_saved_registers + == TYPE_NO_CALLER_SAVED_REGISTERS)) { /* Don't allow SSE, MMX nor x87 instructions since they may change processor state. */ @@ -3614,12 +3637,12 @@ ix86_set_current_function (tree fndecl) "the %<no_caller_saved_registers%> attribute", isa); /* Don't issue the same error twice. */ cfun->machine->func_type = TYPE_NORMAL; - cfun->machine->no_caller_saved_registers = false; + cfun->machine->call_saved_registers + = TYPE_DEFAULT_CALL_SAVED_REGISTERS; } } - prev_no_caller_saved_registers - = cfun->machine->no_caller_saved_registers; + prev_call_saved_registers = cfun->machine->call_saved_registers; } /* Implement the TARGET_OFFLOAD_OPTIONS hook. */ @@ -4018,8 +4041,8 @@ ix86_handle_fndecl_attribute (tree *node, tree name, tree args, int, } static tree -ix86_handle_no_caller_saved_registers_attribute (tree *, tree, tree, - int, bool *) +ix86_handle_call_saved_registers_attribute (tree *, tree, tree, + int, bool *) { return NULL_TREE; } @@ -4181,7 +4204,9 @@ static const attribute_spec ix86_gnu_attributes[] = { "interrupt", 0, 0, false, true, true, false, ix86_handle_interrupt_attribute, NULL }, { "no_caller_saved_registers", 0, 0, false, true, true, false, - ix86_handle_no_caller_saved_registers_attribute, NULL }, + ix86_handle_call_saved_registers_attribute, NULL }, + { "no_callee_saved_registers", 0, 0, false, true, true, true, + ix86_handle_call_saved_registers_attribute, NULL }, { "naked", 0, 0, true, false, false, false, ix86_handle_fndecl_attribute, NULL }, { "indirect_branch", 1, 1, true, false, false, false, diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc index c5eaeedc7e0..f10e745fb40 100644 --- a/gcc/config/i386/i386.cc +++ b/gcc/config/i386/i386.cc @@ -475,7 +475,9 @@ ix86_conditional_register_usage (void) except fixed_regs and registers used for function return value since aggregate_value_p checks call_used_regs[regno] on return value. */ - if (cfun && cfun->machine->no_caller_saved_registers) + if (cfun + && (cfun->machine->call_saved_registers + == TYPE_NO_CALLER_SAVED_REGISTERS)) for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) if (!fixed_regs[i] && !ix86_function_value_regno_p (i)) call_used_regs[i] = 0; @@ -944,7 +946,8 @@ ix86_function_ok_for_sibcall (tree decl, tree exp) /* Sibling call isn't OK if there are no caller-saved registers since all registers must be preserved before return. */ - if (cfun->machine->no_caller_saved_registers) + if (cfun->machine->call_saved_registers + == TYPE_NO_CALLER_SAVED_REGISTERS) return false; /* If we are generating position-independent code, we cannot sibcall @@ -977,6 +980,14 @@ ix86_function_ok_for_sibcall (tree decl, tree exp) decl_or_type = type; } + /* Sibling call isn't OK if callee has no callee-saved registers + and the calling function has callee-saved registers. */ + if ((cfun->machine->call_saved_registers + != TYPE_NO_CALLEE_SAVED_REGISTERS) + && lookup_attribute ("no_callee_saved_registers", + TYPE_ATTRIBUTES (type))) + return false; + /* If outgoing reg parm stack space changes, we cannot do sibcall. */ if ((OUTGOING_REG_PARM_STACK_SPACE (type) != OUTGOING_REG_PARM_STACK_SPACE (TREE_TYPE (current_function_decl))) @@ -1139,6 +1150,12 @@ ix86_comp_type_attributes (const_tree type1, const_tree type2) != ix86_function_regparm (type2, NULL)) return 0; + if (lookup_attribute ("no_callee_saved_registers", + TYPE_ATTRIBUTES (type1)) + != lookup_attribute ("no_callee_saved_registers", + TYPE_ATTRIBUTES (type2))) + return 0; + return 1; } @@ -6569,7 +6586,8 @@ ix86_epilogue_uses (int regno) and restoring registers. Don't explicitly save SP register since it is always preserved. */ return (epilogue_completed - && cfun->machine->no_caller_saved_registers + && (cfun->machine->call_saved_registers + == TYPE_NO_CALLER_SAVED_REGISTERS) && !fixed_regs[regno] && !STACK_REGNO_P (regno) && !MMX_REGNO_P (regno)); @@ -6585,7 +6603,8 @@ ix86_hard_regno_scratch_ok (unsigned int regno) as a scratch register after epilogue and use REGNO as scratch register only if it has been used before to avoid saving and restoring it. */ - return (!cfun->machine->no_caller_saved_registers + return ((cfun->machine->call_saved_registers + != TYPE_NO_CALLER_SAVED_REGISTERS) || (!epilogue_completed && df_regs_ever_live_p (regno))); } @@ -6595,14 +6614,32 @@ ix86_hard_regno_scratch_ok (unsigned int regno) bool ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined) { - /* If there are no caller-saved registers, we preserve all registers, - except for MMX and x87 registers which aren't supported when saving - and restoring registers. Don't explicitly save SP register since - it is always preserved. */ - if (cfun->machine->no_caller_saved_registers) - { - /* Don't preserve registers used for function return value. */ - rtx reg = crtl->return_rtx; + rtx reg; + + switch (cfun->machine->call_saved_registers) + { + case TYPE_DEFAULT_CALL_SAVED_REGISTERS: + /* If any no_callee_saved_registers functions are called and this + is not a no_callee_saved_registers function, we preserve all + registers which aren't used by function calls, except for MMX + and x87 registers which aren't supported when saving and + restoring registers. Don't explicitly save SP register since + it is always preserved. */ + if (cfun->machine->call_no_callee_saved_registers) + return (!fixed_regs[regno] + && !call_used_regs[regno] + && !STACK_REGNO_P (regno) + && !MMX_REGNO_P (regno)); + break; + + case TYPE_NO_CALLER_SAVED_REGISTERS: + /* If there are no caller-saved registers, we preserve all + registers, except for MMX and x87 registers which aren't + supported when saving and restoring registers. Don't + explicitly save SP register since it is always preserved. + + Don't preserve registers used for function return value. */ + reg = crtl->return_rtx; if (reg) { unsigned int i = REGNO (reg); @@ -6618,6 +6655,9 @@ ix86_save_reg (unsigned int regno, bool maybe_eh_return, bool ignore_outlined) && !MMX_REGNO_P (regno) && (regno != HARD_FRAME_POINTER_REGNUM || !frame_pointer_needed)); + + case TYPE_NO_CALLEE_SAVED_REGISTERS: + return false; } if (regno == REAL_PIC_OFFSET_TABLE_REGNUM @@ -7717,7 +7757,8 @@ find_drap_reg (void) registers in epilogue, DRAP must not use caller-saved register in such case. */ if (DECL_STATIC_CHAIN (decl) - || cfun->machine->no_caller_saved_registers + || (cfun->machine->call_saved_registers + == TYPE_NO_CALLER_SAVED_REGISTERS) || crtl->tail_call_emit) return R13_REG; @@ -7730,7 +7771,8 @@ find_drap_reg (void) registers in epilogue, DRAP must not use caller-saved register in such case. */ if (DECL_STATIC_CHAIN (decl) - || cfun->machine->no_caller_saved_registers + || (cfun->machine->call_saved_registers + == TYPE_NO_CALLER_SAVED_REGISTERS) || crtl->tail_call_emit || crtl->calls_eh_return) return DI_REG; diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h index b9c574e62e1..bf1ca6014f5 100644 --- a/gcc/config/i386/i386.h +++ b/gcc/config/i386/i386.h @@ -2724,6 +2724,17 @@ enum function_type TYPE_EXCEPTION }; +enum call_saved_registers_type +{ + TYPE_DEFAULT_CALL_SAVED_REGISTERS = 0, + /* The current function is a function specified with the "interrupt" + or "no_caller_saved_registers" attribute. */ + TYPE_NO_CALLER_SAVED_REGISTERS, + /* The current function is a function specified with the "noreturn" + or "no_callee_saved_registers" attribute. */ + TYPE_NO_CALLEE_SAVED_REGISTERS +}; + enum queued_insn_type { TYPE_NONE = 0, @@ -2793,9 +2804,12 @@ struct GTY(()) machine_function { /* How to generate function return. */ ENUM_BITFIELD(indirect_branch) function_return_type : 3; - /* If true, the current function is a function specified with - the "interrupt" or "no_caller_saved_registers" attribute. */ - BOOL_BITFIELD no_caller_saved_registers : 1; + /* Call saved registers type. */ + ENUM_BITFIELD(call_saved_registers_type) call_saved_registers : 2; + + /* If true, the current function calls no_callee_saved_registers + functions. */ + BOOL_BITFIELD call_no_callee_saved_registers : 1; /* If true, there is register available for argument passing. This is used only in ix86_function_ok_for_sibcall by 32-bit to determine diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 0bc586d120e..4cafa6d416b 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -6767,6 +6767,14 @@ On x86-32 targets, the @code{stdcall} attribute causes the compiler to assume that the called function pops off the stack space used to pass arguments, unless it takes a variable number of arguments. +@cindex @code{no_callee_saved_registers} function attribute, x86 +@item no_callee_saved_registers +Use this attribute to indicate that the specified function has no +callee-saved registers. That is, all registers can be used as scratch +registers. For example, this attribute can be used for a function +called from the interrupt handler assembly stub which will preserve +all registers and return from interrupt. + @cindex @code{no_caller_saved_registers} function attribute, x86 @item no_caller_saved_registers Use this attribute to indicate that the specified function has no diff --git a/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c new file mode 100644 index 00000000000..8c48ec0c79a --- /dev/null +++ b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1a.c @@ -0,0 +1,23 @@ +/* { dg-do run { target i?86-*-* x86_64-*-* } } */ +/* { dg-additional-sources no-callee-saved-run-1b.c } */ + +extern void bar0 (int, int, int, int, int, int, int, int, int) + __attribute__ ((no_callee_saved_registers)); + +void +foo (void) +{ + bar0 (0, 1, 2, 3, 4, 5, 6, 7, 8); +} + +int +bar (int x) +{ + return x; +} + +void +bad (void) +{ + __builtin_abort (); +} diff --git a/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c new file mode 100644 index 00000000000..b3ce7e72e85 --- /dev/null +++ b/gcc/testsuite/gcc.dg/torture/no-callee-saved-run-1b.c @@ -0,0 +1,59 @@ +/* { dg-do compile { target i?86-*-* x86_64-*-* } } */ + +extern void foo (void); +extern void bad (void); +extern int bar (int); + +void +__attribute__ ((no_callee_saved_registers)) +bar0 (int i0, int i1, int i2, int i3, int i4, int i5, int i6, + int i7, int i8) +{ + if (i0 != 0) + bad (); + + if (i1 != 1) + bad (); + + if (i2 != 2) + bad (); + + if (i3 != 3) + bad (); + + if (i4 != 4) + bad (); + + if (i5 != 5) + bad (); + + if (i6 != 6) + bad (); + + if (i7 != 7) + bad (); + + if (i8 != 8) + bad (); + + int a,b,c,d,e,f,i; + a = bar (5); + b = bar (a); + c = bar (b); + d = bar (c); + e = bar (d); + f = bar (e); + for (i = 1; i < 10; i++) + { + a += bar (a + i) + bar (b + i) + + bar (c + i) + bar (d + i) + + bar (e + i) + bar (f + i); + } +} + +int +main () +{ + foo (); + return 0; +} diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c new file mode 100644 index 00000000000..8fe36eb5198 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-1.c @@ -0,0 +1,30 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +extern int bar (int) +#ifndef __x86_64__ +__attribute__ ((regparm(3))) +#endif +; + +__attribute__ ((no_callee_saved_registers)) +void +foo (void *frame) +{ + int a,b,c,d,e,f,i; + a = bar (5); + b = bar (a); + c = bar (b); + d = bar (c); + e = bar (d); + f = bar (e); + for (i = 1; i < 10; i++) + { + a += bar (a + i) + bar (b + i) + + bar (c + i) + bar (d + i) + + bar (e + i) + bar (f + i); + } +} + +/* { dg-final { scan-assembler-not "push" } } */ +/* { dg-final { scan-assembler-not "pop" } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c new file mode 100644 index 00000000000..87766c6cd88 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-10.c @@ -0,0 +1,46 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -mgeneral-regs-only -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +extern void bar (void) __attribute__ ((no_callee_saved_registers)); + +__attribute__ ((no_caller_saved_registers)) +void +foo (void) +{ + bar (); +} + +/* foo must save and restore all caller saved registers since bar won't + preserve any. */ +/* { dg-final { scan-assembler-not "jmp\[\\t \]+_?bar" } } */ +/* { dg-final { scan-assembler "call\[\\t \]+_?bar" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)ax" 1 } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)cx" 1 } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)dx" 1 } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)si" 1 } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)di" 1 } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r8" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r9" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r10" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r11" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)ax" 1 } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)cx" 1 } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)dx" 1 } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)si" 1 } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)di" 1 } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r8" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r9" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r10" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r11" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c new file mode 100644 index 00000000000..902a764489e --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-11.c @@ -0,0 +1,11 @@ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ + +extern void foo (void); /* { dg-note "previous declaration" } */ + +__attribute__ ((no_callee_saved_registers)) +void +foo (void) /* { dg-error "conflicting types" } */ +{ +} + diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c new file mode 100644 index 00000000000..5524a4af29c --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-12.c @@ -0,0 +1,10 @@ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ + +extern void foo (void) __attribute__ ((no_callee_saved_registers)); /* { dg-note "previous declaration" } */ + +void +foo (void) /* { dg-error "conflicting types" } */ +{ +} + diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c new file mode 100644 index 00000000000..6757e72d848 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-13.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +extern void foo (void); + +__attribute__ ((no_callee_saved_registers)) +void +bar (void) +{ + foo (); +} + +/* { dg-final { scan-assembler-not "push" } } */ +/* { dg-final { scan-assembler-not "pop" } } */ +/* { dg-final { scan-assembler-not "call\[\\t \]+_?foo" } } */ +/* { dg-final { scan-assembler "jmp\[\\t \]+_?foo" } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c new file mode 100644 index 00000000000..2239e286e6a --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-14.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +extern void bar (void) __attribute__ ((no_callee_saved_registers)); + +__attribute__ ((no_callee_saved_registers)) +void +foo (void) +{ + bar (); +} + +/* { dg-final { scan-assembler-not "push" } } */ +/* { dg-final { scan-assembler-not "pop" } } */ +/* { dg-final { scan-assembler "jmp\[\\t \]+_?bar" } } */ +/* { dg-final { scan-assembler-not "call\[\\t \]+_?bar" } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c new file mode 100644 index 00000000000..10135fec9c1 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-15.c @@ -0,0 +1,17 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); +extern fn_t bar; + +__attribute__ ((no_callee_saved_registers)) +void +foo (void) +{ + bar (); +} + +/* { dg-final { scan-assembler-not "push" } } */ +/* { dg-final { scan-assembler-not "pop" } } */ +/* { dg-final { scan-assembler "jmp" } } */ +/* { dg-final { scan-assembler-not "call\[\\t \]+" } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c new file mode 100644 index 00000000000..112d1764f3e --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-16.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); + +__attribute__ ((no_callee_saved_registers)) +void +foo (fn_t bar) +{ + bar (); +} + +/* { dg-final { scan-assembler-not "push" } } */ +/* { dg-final { scan-assembler-not "pop" } } */ +/* { dg-final { scan-assembler "jmp" } } */ +/* { dg-final { scan-assembler-not "call\[\\t \]+" } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c new file mode 100644 index 00000000000..1fd5daadf08 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-17.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +extern void foo (void) __attribute__ ((no_caller_saved_registers)); + +__attribute__ ((no_callee_saved_registers)) +void +bar (void) +{ + foo (); +} + +/* { dg-final { scan-assembler-not "push" } } */ +/* { dg-final { scan-assembler-not "pop" } } */ +/* { dg-final { scan-assembler-not "call\[\\t \]+_?foo" } } */ +/* { dg-final { scan-assembler "jmp\[\\t \]+_?foo" } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c new file mode 100644 index 00000000000..e7101009be4 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-18.c @@ -0,0 +1,51 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +#include <stdint.h> + +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); + +void +foo (uintptr_t p) +{ + ((fn_t) p) (); +} + +/* foo must save and restore all caller saved registers since bar won't + preserve any. */ +/* { dg-final { scan-assembler-not "jmp" } } */ +/* { dg-final { scan-assembler "call\[\\t \]+" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c new file mode 100644 index 00000000000..ce4ab3b1799 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-2.c @@ -0,0 +1,30 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +extern int bar (int) __attribute__ ((no_caller_saved_registers)) +#ifndef __x86_64__ +__attribute__ ((regparm(3))) +#endif +; + +__attribute__ ((no_callee_saved_registers)) +void +foo (void *frame) +{ + int a,b,c,d,e,f,i; + a = bar (5); + b = bar (a); + c = bar (b); + d = bar (c); + e = bar (d); + f = bar (e); + for (i = 1; i < 10; i++) + { + a += bar (a + i) + bar (b + i) + + bar (c + i) + bar (d + i) + + bar (e + i) + bar (f + i); + } +} + +/* { dg-final { scan-assembler-not "push" } } */ +/* { dg-final { scan-assembler-not "pop" } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c new file mode 100644 index 00000000000..453272e11c0 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-3.c @@ -0,0 +1,8 @@ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ + +__attribute__ ((no_callee_saved_registers, no_caller_saved_registers)) +void +foo (void) /* { dg-error "attributes are not compatible" } */ +{ +} diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c new file mode 100644 index 00000000000..ec566aaf09f --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-4.c @@ -0,0 +1,8 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mgeneral-regs-only" } */ + +__attribute__ ((no_callee_saved_registers, interrupt)) +void +foo (void *frame) /* { dg-error "attributes are not compatible" } */ +{ +} diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c new file mode 100644 index 00000000000..b28b211986a --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-5.c @@ -0,0 +1,11 @@ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ + +typedef void (*fn_t) (void *) __attribute__ ((no_callee_saved_registers)); + +void +foo (void *frame) +{ +} + +fn_t func = foo; /* { dg-error "incompatible pointer type" } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c new file mode 100644 index 00000000000..a7b3bdabf43 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-6.c @@ -0,0 +1,12 @@ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ + +typedef void (*fn_t) (void *) __attribute__ ((no_callee_saved_registers)); + +__attribute__ ((no_callee_saved_registers)) +void +foo (void *frame) +{ +} + +fn_t func = foo; diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c new file mode 100644 index 00000000000..a1837fdfd4b --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-7.c @@ -0,0 +1,49 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +extern void bar (void) __attribute__ ((no_callee_saved_registers)); + +void +foo (void) +{ + bar (); +} + +/* foo must save and restore all caller saved registers since bar won't + preserve any. */ +/* { dg-final { scan-assembler-not "jmp\[\\t \]+_?bar" } } */ +/* { dg-final { scan-assembler "call\[\\t \]+_?bar" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c new file mode 100644 index 00000000000..90b98a21aef --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-8.c @@ -0,0 +1,50 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); +extern fn_t bar; + +void +foo (void) +{ + bar (); +} + +/* foo must save and restore all caller saved registers since bar won't + preserve any. */ +/* { dg-final { scan-assembler-not "jmp" } } */ +/* { dg-final { scan-assembler "call\[\\t \]+" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ diff --git a/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c new file mode 100644 index 00000000000..e261100ac1a --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/no-callee-saved-9.c @@ -0,0 +1,49 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -mtune-ctrl=^prologue_using_move,^epilogue_using_move" } */ + +typedef void (*fn_t) (void) __attribute__ ((no_callee_saved_registers)); + +void +foo (fn_t bar) +{ + bar (); +} + +/* foo must save and restore all caller saved registers since bar won't + preserve any. */ +/* { dg-final { scan-assembler-not "jmp" } } */ +/* { dg-final { scan-assembler "call\[\\t \]+" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ +/* { dg-final { scan-assembler-not "push(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ +/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rsi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rdi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r8" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r9" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r10" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r11" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "pushq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)ax" } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bx" 1 } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)cx" } } */ +/* { dg-final { scan-assembler-not "pop(?:l|q)\[\\t \]*%(?:e|r)dx" } } */ +/* { dg-final { scan-assembler-times "pop(?:l|q)\[\\t \]*%(?:e|r)bp" 1 } } */ +/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rsi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%rdi" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r8" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r9" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r10" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-not "popq\[\\t \]*%r11" { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r12" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r13" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r14" 1 { target { ! ia32 } } } } */ +/* { dg-final { scan-assembler-times "popq\[\\t \]*%r15" 1 { target { ! ia32 } } } } */