From be1403cb8d915b023b6d15e792d3d97dbafceb39 Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <hjl.tools@gmail.com>
Date: Wed, 25 Nov 2015 12:14:05 -0800
Subject: [PATCH] Update TARGET_FUNCTION_INCOMING_ARG documentation

On x86, interrupt handlers are only called by processors which push
interrupt data onto stack at the address where the normal return address
is.  Since interrupt handlers must access interrupt data via pointers so
that they can update interrupt data, the pointer argument is passed as
"argument pointer - word".

TARGET_FUNCTION_INCOMING_ARG defines how callee sees its argument.
Normally it returns REG, NULL, or CONST_INT.  This patch adds arbitrary
address computation based on hard register, which can be forced into a
register, to the list.

When copying an incoming argument onto stack, assign_parm_setup_stack
has:

if (argument in memory)
  copy argument in memory to stack
else
  move argument to stack

Since an arbitrary address computation may be passed as an argument, we
change it to:

if (argument in memory)
  copy argument in memory to stack
else
  {
    if (argument isn't in register)
      force argument into a register
    move argument to stack
  }

	* function.c (assign_parm_setup_stack): Force source into a
	register if needed.
	* target.def (function_incoming_arg): Update documentation to
	allow arbitrary address computation based on hard register.
	* doc/tm.texi: Regenerated.

Implement x86 interrupt attribute

The interrupt and exception handlers are called by x86 processors.  X86
hardware pushes information onto stack and calls the handler.  The
requirements are

1. Both interrupt and exception handlers must use the 'IRET' instruction,
instead of the 'RET' instruction, to return from the handlers.
2. All registers are callee-saved in interrupt and exception handlers.
3. The difference between interrupt and exception handlers is the
exception handler must pop 'ERROR_CODE' off the stack before the 'IRET'
instruction.

The design goals of interrupt and exception handlers for x86 processors
are:

1. Support both 32-bit and 64-bit modes.
2. Flexible for compilers to optimize.
3. Easy to use by programmers.

To implement interrupt and exception handlers for x86 processors, a
compiler should support:

'interrupt' attribute

Use this attribute to indicate that the specified function with
mandatory arguments is an interrupt or exception handler.  The compiler
generates function entry and exit sequences suitable for use in an
interrupt handler when this attribute is present.  The 'IRET' instruction,
instead of the 'RET' instruction, is used to return from interrupt or
exception handlers.  All registers, except for the EFLAGS register which
is restored by the 'IRET' instruction, are preserved by the compiler.

Any interruptible-without-stack-switch code must be compiled with
-mno-red-zone since interrupt handlers can and will, because of the
hardware design, touch the red zone.

1. interrupt handler must be declared with a mandatory pointer argument:

struct interrupt_frame;

__attribute__ ((interrupt))
void
f (struct interrupt_frame *frame)
{
...
}

and user must properly define the structure the pointer pointing to.

2. exception handler:

The exception handler is very similar to the interrupt handler with
a different mandatory function signature:

typedef unsigned int uword_t __attribute__ ((mode (__word__)));

struct interrupt_frame;

__attribute__ ((interrupt))
void
f (struct interrupt_frame *frame, uword_t error_code)
{
...
}

and compiler pops the error code off stack before the 'IRET' instruction.

The exception handler should only be used for exceptions which push an
error code and all other exceptions must use the interrupt handler.
The system will crash if the wrong handler is used.

To be feature complete, compiler may implement the optional
'no_caller_saved_registers' attribute:

Use this attribute to indicate that the specified function has no
caller-saved registers.  That is, all registers are callee-saved.
The compiler generates proper function entry and exit sequences to
save and restore any modified registers.

The user can call functions specified with 'no_caller_saved_registers'
attribute from an interrupt handler without saving and restoring all
call clobbered registers.

On x86, interrupt handlers are only called by processors which push
interrupt data onto stack at the address where the normal return address
is.  Interrupt handlers must access interrupt data via pointers so that
they can update interrupt data.

gcc/

	PR target/66960
	PR target/67630
	PR target/67634
	PR target/67841
	PR target/68037
	PR target/68618
	PR target/68661
	* config/i386/i386-protos.h (ix86_epilogue_uses): New prototype.
	* config/i386/i386.c (ix86_frame): Add nbndregs, nmaskregs,
	bnd_reg_save_offset and mask_reg_save_offset.
	(ix86_conditional_register_usage): Preserve all registers,
	except for function return registers if there are no caller-saved
	registers.
	(ix86_set_current_function): Set no_caller_saved_registers and
	func_type.  Sorry if non-DWARF debug format is used.  Call
	reinit_regs if caller-saved registers are changed.
	(ix86_function_ok_for_sibcall): Return false if there are no
	caller-saved registers.
	(type_natural_mode): Don't warn ABI change for MMX in interrupt
	handler.
	(ix86_function_arg_advance): Skip for callee in interrupt
	handler.
	(ix86_function_arg): Return special arguments in interrupt
	handler.
	(ix86_promote_function_mode): Promote pointer to word_mode only
	for normal functions.
	(ix86_can_use_return_insn_p): Don't use `ret' instruction in
	interrupt handler.
	(ix86_epilogue_uses): New function.
	(ix86_hard_regno_scratch_ok): Likewise.
	(ix86_nsaved_bndregs): Likewise.
	(ix86_nsaved_maskregs): Likewise.
	(ix86_emit_save_bnd_regs_using_mov): Likewise.
	(ix86_emit_save_mask_regs_using_mov): Likewise.
	(ix86_emit_restore_bnd_regs_using_mov): Likewise.
	(ix86_emit_restore_mask_regs_using_mov): Likewise.
	(ix86_handle_no_caller_saved_registers_attribute): Likewise.
	(ix86_handle_interrupt_attribute): Likewise.
	(ix86_save_reg): Preserve all registers in interrupt function
	after reload.  Preserve all registers, except for function
	return registers, if there are no caller-saved registers after
	reload.
	(ix86_nsaved_sseregs): Don't return 0 if there are no
	caller-saved registers.
	(ix86_compute_frame_layout): Set nbndregs and nmaskregs.  Set
	and allocate BND and MASK register save areas.  Allocate space to
	save full vector registers if there are no caller-saved registers.
	(ix86_emit_save_reg_using_mov): Don't use UNSPEC_STOREU to
	SSE registers.
	(ix86_emit_save_sse_regs_using_mov): Save full vector registers
	if there are no caller-saved registers.
	(find_drap_reg): Always use callee-saved register if there are
	no caller-saved registers.
	(ix86_minimum_incoming_stack_boundary): Return MIN_STACK_BOUNDARY
	for interrupt handler.
	(ix86_expand_prologue): Save BND and MASK registers.
	(ix86_emit_restore_sse_regs_using_mov): Restore full vector
	registers if there are no caller-saved registers.
	(ix86_expand_epilogue): Restore BND and MASK registers.  Generate
	interrupt return for interrupt handler and pop the 'ERROR_CODE'
	off the stack before interrupt return in exception handler.
	(ix86_expand_move): Disallow 80387 instructions in exception
	handler.
	(ix86_expand_vector_move): Disallow MMX/3Dnow instructions in
	exception handler.
	(ix86_expand_call): Disallow calling interrupt handler directly.
	If there are no caller-saved registers, mark all registers that
	are clobbered by the call which returns as clobbered.
	(ix86_attribute_table): Add interrupt and no_caller_saved_registers
	attributes.
	(TARGET_HARD_REGNO_SCRATCH_OK): Likewise.
	* config/i386/i386.h (ACCUMULATE_OUTGOING_ARGS): Use argument
	accumulation in interrupt function if stack may be realigned to
	avoid DRAP.
	(EPILOGUE_USES): New.
	(function_type): New enum.
	(machine_function): Add func_type and no_caller_saved_registers.
	* config/i386/i386.md (UNSPEC_INTERRUPT_RETURN): New.
	(interrupt_return): New pattern.
	* doc/extend.texi: Document x86 interrupt and
	no_caller_saved_registers attributes.

gcc/testsuite/

	PR target/66960
	PR target/67630
	PR target/67634
	PR target/67841
	PR target/68037
	PR target/68618
	PR target/68661
	* gcc.dg/guality/pr68037-1.c: New test.
	* gcc.dg/guality/pr68037-2.c: Likewise.
	* gcc.dg/guality/pr68037-3.c: Likewise.
	* gcc.dg/torture/pr68037-1.c: Likewise.
	* gcc.dg/torture/pr68037-2.c: Likewise.
	* gcc.dg/torture/pr68037-3.c: Likewise.
	* gcc.dg/torture/pr68661-1a.c: Likewise.
	* gcc.dg/torture/pr68661-1b.c: Likewise.
	* gcc.target/i386/interrupt-1.c: Likewise.
	* gcc.target/i386/interrupt-2.c: Likewise.
	* gcc.target/i386/interrupt-3.c: Likewise.
	* gcc.target/i386/interrupt-4.c: Likewise.
	* gcc.target/i386/interrupt-5.c: Likewise.
	* gcc.target/i386/interrupt-6.c: Likewise.
	* gcc.target/i386/interrupt-7.c: Likewise.
	* gcc.target/i386/interrupt-8.c: Likewise.
	* gcc.target/i386/interrupt-9.c: Likewise.
	* gcc.target/i386/interrupt-10.c: Likewise.
	* gcc.target/i386/interrupt-11.c: Likewise.
	* gcc.target/i386/interrupt-12.c: Likewise.
	* gcc.target/i386/interrupt-13.c: Likewise.
	* gcc.target/i386/interrupt-14.c: Likewise.
	* gcc.target/i386/interrupt-15.c: Likewise.
	* gcc.target/i386/interrupt-16.c: Likewise.
	* gcc.target/i386/interrupt-17.c: Likewise.
	* gcc.target/i386/interrupt-18.c: Likewise.
	* gcc.target/i386/interrupt-19.c: Likewise.
	* gcc.target/i386/interrupt-20.c: Likewise.
	* gcc.target/i386/interrupt-21.c: Likewise.
	* gcc.target/i386/interrupt-22.c: Likewise.
	* gcc.target/i386/interrupt-23.c: Likewise.
	* gcc.target/i386/interrupt-24.c: Likewise.
	* gcc.target/i386/interrupt-25.c: Likewise.
	* gcc.target/i386/interrupt-26.c: Likewise.
	* gcc.target/i386/interrupt-27.c: Likewise.
	* gcc.target/i386/interrupt-28.c: Likewise.
	* gcc.target/i386/interrupt-29.c: Likewise.
	* gcc.target/i386/interrupt-30.c: Likewise.
	* gcc.target/i386/interrupt-31.c: Likewise.
	* gcc.target/i386/interrupt-32.c: Likewise.
	* gcc.target/i386/interrupt-33.c: Likewise.
	* gcc.target/i386/interrupt-34.c: Likewise.
	* gcc.target/i386/interrupt-35.c: Likewise.
	* gcc.target/i386/interrupt-387-err.c: Likewise.
	* gcc.target/i386/interrupt-bnd.c: Likewise.
	* gcc.target/i386/interrupt-iamcu.c: Likewise.
	* gcc.target/i386/interrupt-mmx-err.c: Likewise.
	* gcc.target/i386/interrupt-redzone-1.c: Likewise.
	* gcc.target/i386/interrupt-redzone-2.c: Likewise.
	* gcc.target/i386/interrupt-sibcall-1.c: Likewise.
	* gcc.target/i386/interrupt-sibcall-2.c: Likewise.
	* gcc.target/i386/interrupt-switch-abi.c: Likewise.
	* gcc.target/i386/interrupt-xmm.c: Likewise.
	* gcc.target/i386/interrupt-ymm.c: Likewise.
	* gcc.target/i386/interrupt-zmm.c: Likewise.

Rebuild array with the updated function pointer type

When we apply function attribute to array of function pointer, we need
to rebuild array with the updated function pointer type.

gcc/

	PR c/68637
	* attribs.c (decl_attributes): Rebuild array with the updated
	function pointer type.

gcc/testsuite/

	PR c/68637
	* gcc.target/i386/pr68637-1.c: New test.
	* gcc.target/i386/pr68637-2.c: Likewise.

Add an array of function pointer test for PR 68661

Array of function pointer with no_caller_saved_registers attribute only
works if PR c/68637 is fixed.

	PR target/68637
	PR target/68661
	* gcc.dg/torture/pr68661-2a.c: Likewise.
	* gcc.dg/torture/pr68661-2b.c: Likewise.
---
 gcc/attribs.c                                      |  18 +-
 gcc/config/i386/i386-protos.h                      |   2 +
 gcc/config/i386/i386.c                             | 589 +++++++++++++++++++--
 gcc/config/i386/i386.h                             |  37 +-
 gcc/config/i386/i386.md                            |   9 +
 gcc/doc/extend.texi                                |  63 +++
 gcc/doc/tm.texi                                    |   4 +
 gcc/function.c                                     |   6 +-
 gcc/target.def                                     |   8 +-
 gcc/testsuite/gcc.dg/guality/pr68037-1.c           |  65 +++
 gcc/testsuite/gcc.dg/guality/pr68037-2.c           |  60 +++
 gcc/testsuite/gcc.dg/guality/pr68037-3.c           |  76 +++
 gcc/testsuite/gcc.dg/torture/pr68037-1.c           |  57 ++
 gcc/testsuite/gcc.dg/torture/pr68037-2.c           |  53 ++
 gcc/testsuite/gcc.dg/torture/pr68037-3.c           |  69 +++
 gcc/testsuite/gcc.dg/torture/pr68661-1a.c          |  17 +
 gcc/testsuite/gcc.dg/torture/pr68661-1b.c          |  44 ++
 gcc/testsuite/gcc.dg/torture/pr68661-2a.c          |  17 +
 gcc/testsuite/gcc.dg/torture/pr68661-2b.c          |  52 ++
 gcc/testsuite/gcc.target/i386/interrupt-1.c        |  53 ++
 gcc/testsuite/gcc.target/i386/interrupt-10.c       |  24 +
 gcc/testsuite/gcc.target/i386/interrupt-11.c       |  37 ++
 gcc/testsuite/gcc.target/i386/interrupt-12.c       |  20 +
 gcc/testsuite/gcc.target/i386/interrupt-13.c       |  17 +
 gcc/testsuite/gcc.target/i386/interrupt-14.c       |  39 ++
 gcc/testsuite/gcc.target/i386/interrupt-15.c       |  28 +
 gcc/testsuite/gcc.target/i386/interrupt-16.c       |  28 +
 gcc/testsuite/gcc.target/i386/interrupt-17.c       |  30 ++
 gcc/testsuite/gcc.target/i386/interrupt-18.c       |  35 ++
 gcc/testsuite/gcc.target/i386/interrupt-19.c       |  21 +
 gcc/testsuite/gcc.target/i386/interrupt-2.c        |  16 +
 gcc/testsuite/gcc.target/i386/interrupt-20.c       |  22 +
 gcc/testsuite/gcc.target/i386/interrupt-21.c       |  25 +
 gcc/testsuite/gcc.target/i386/interrupt-22.c       |  25 +
 gcc/testsuite/gcc.target/i386/interrupt-23.c       |  25 +
 gcc/testsuite/gcc.target/i386/interrupt-24.c       |  21 +
 gcc/testsuite/gcc.target/i386/interrupt-25.c       |  24 +
 gcc/testsuite/gcc.target/i386/interrupt-26.c       |  11 +
 gcc/testsuite/gcc.target/i386/interrupt-27.c       |  16 +
 gcc/testsuite/gcc.target/i386/interrupt-28.c       |  16 +
 gcc/testsuite/gcc.target/i386/interrupt-29.c       |  27 +
 gcc/testsuite/gcc.target/i386/interrupt-3.c        |  14 +
 gcc/testsuite/gcc.target/i386/interrupt-30.c       |  28 +
 gcc/testsuite/gcc.target/i386/interrupt-31.c       |  27 +
 gcc/testsuite/gcc.target/i386/interrupt-32.c       |  42 ++
 gcc/testsuite/gcc.target/i386/interrupt-33.c       |  19 +
 gcc/testsuite/gcc.target/i386/interrupt-34.c       |  15 +
 gcc/testsuite/gcc.target/i386/interrupt-35.c       |  54 ++
 gcc/testsuite/gcc.target/i386/interrupt-387-err.c  |  27 +
 gcc/testsuite/gcc.target/i386/interrupt-4.c        |  32 ++
 gcc/testsuite/gcc.target/i386/interrupt-5.c        |  23 +
 gcc/testsuite/gcc.target/i386/interrupt-6.c        |  40 ++
 gcc/testsuite/gcc.target/i386/interrupt-7.c        |  12 +
 gcc/testsuite/gcc.target/i386/interrupt-8.c        |  20 +
 gcc/testsuite/gcc.target/i386/interrupt-9.c        |  22 +
 gcc/testsuite/gcc.target/i386/interrupt-bnd.c      |  16 +
 gcc/testsuite/gcc.target/i386/interrupt-iamcu.c    |  35 ++
 gcc/testsuite/gcc.target/i386/interrupt-mmx-err.c  |  37 ++
 .../gcc.target/i386/interrupt-redzone-1.c          |  31 ++
 .../gcc.target/i386/interrupt-redzone-2.c          |  32 ++
 .../gcc.target/i386/interrupt-sibcall-1.c          |  12 +
 .../gcc.target/i386/interrupt-sibcall-2.c          |  13 +
 .../gcc.target/i386/interrupt-switch-abi.c         |  16 +
 gcc/testsuite/gcc.target/i386/interrupt-xmm.c      |  16 +
 gcc/testsuite/gcc.target/i386/interrupt-ymm.c      |  16 +
 gcc/testsuite/gcc.target/i386/interrupt-zmm.c      |  16 +
 gcc/testsuite/gcc.target/i386/pr68637-1.c          |  10 +
 gcc/testsuite/gcc.target/i386/pr68637-2.c          |  25 +
 68 files changed, 2411 insertions(+), 45 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/guality/pr68037-1.c
 create mode 100644 gcc/testsuite/gcc.dg/guality/pr68037-2.c
 create mode 100644 gcc/testsuite/gcc.dg/guality/pr68037-3.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/pr68037-1.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/pr68037-2.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/pr68037-3.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/pr68661-1a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/pr68661-1b.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/pr68661-2a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/pr68661-2b.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-10.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-11.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-12.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-13.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-14.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-15.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-16.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-17.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-18.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-19.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-2.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-20.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-21.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-22.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-23.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-24.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-25.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-26.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-27.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-28.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-29.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-3.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-30.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-31.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-32.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-33.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-34.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-35.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-387-err.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-4.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-5.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-6.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-7.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-8.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-9.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-bnd.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-iamcu.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-mmx-err.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-redzone-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-redzone-2.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-sibcall-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-sibcall-2.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-switch-abi.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-xmm.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-ymm.c
 create mode 100644 gcc/testsuite/gcc.target/i386/interrupt-zmm.c
 create mode 100644 gcc/testsuite/gcc.target/i386/pr68637-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/pr68637-2.c

diff --git a/gcc/attribs.c b/gcc/attribs.c
index c18bff2..118db5a 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -513,9 +513,18 @@ decl_attributes (tree *node, tree attributes, int flags)
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
+      tree array_type = (TREE_CODE (*anode) == ARRAY_TYPE
+			 ? *anode
+			 : NULL_TREE);
+
       if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
 	  && TREE_CODE (*anode) != METHOD_TYPE)
 	{
+	  /* We need to rebuid array with the updated function pointer
+	     type later. */
+	  if (array_type)
+	    *anode = TREE_TYPE (*anode);
+
 	  if (TREE_CODE (*anode) == POINTER_TYPE
 	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
 		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
@@ -636,7 +645,14 @@ decl_attributes (tree *node, tree attributes, int flags)
 	  if (fn_ptr_quals)
 	    fn_ptr_tmp = build_qualified_type (fn_ptr_tmp, fn_ptr_quals);
 	  if (DECL_P (*node))
-	    TREE_TYPE (*node) = fn_ptr_tmp;
+	    {
+	      if (array_type)
+		TREE_TYPE (*node)
+		  = build_array_type (fn_ptr_tmp,
+				      TYPE_DOMAIN (array_type));
+	      else
+		TREE_TYPE (*node) = fn_ptr_tmp;
+	    }
 	  else
 	    {
 	      gcc_assert (TREE_CODE (*node) == POINTER_TYPE);
diff --git a/gcc/config/i386/i386-protos.h b/gcc/config/i386/i386-protos.h
index 3962427..23152c3 100644
--- a/gcc/config/i386/i386-protos.h
+++ b/gcc/config/i386/i386-protos.h
@@ -279,6 +279,8 @@ extern rtx maybe_get_pool_constant (rtx);
 extern char internal_label_prefix[16];
 extern int internal_label_prefix_len;
 
+extern bool ix86_epilogue_uses (int);
+
 enum ix86_address_seg { SEG_DEFAULT, SEG_FS, SEG_GS };
 struct ix86_address
 {
diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c
index 322962f..20c66f2 100644
--- a/gcc/config/i386/i386.c
+++ b/gcc/config/i386/i386.c
@@ -2404,6 +2404,8 @@ struct ix86_frame
 {
   int nsseregs;
   int nregs;
+  int nbndregs;
+  int nmaskregs;
   int va_arg_size;
   int red_zone_size;
   int outgoing_arguments_size;
@@ -2414,6 +2416,8 @@ struct ix86_frame
   HOST_WIDE_INT stack_pointer_offset;
   HOST_WIDE_INT hfp_save_offset;
   HOST_WIDE_INT reg_save_offset;
+  HOST_WIDE_INT bnd_reg_save_offset;
+  HOST_WIDE_INT mask_reg_save_offset;
   HOST_WIDE_INT sse_reg_save_offset;
 
   /* When save_regs_using_mov is set, emit prologue using
@@ -4511,6 +4515,15 @@ ix86_conditional_register_usage (void)
 {
   int i, c_mask;
 
+  /* If there are no caller-saved registers, preserve all registers.
+     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)
+    for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+      if (!fixed_regs[i] && !ix86_function_value_regno_p (i))
+	call_used_regs[i] = 0;
+
   /* For 32-bit targets, squash the REX registers.  */
   if (! TARGET_64BIT)
     {
@@ -5306,6 +5319,27 @@ ix86_set_current_function (tree fndecl)
       return;
     }
 
+  if (lookup_attribute ("interrupt",
+			TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
+    {
+      int nargs = 0;
+      for (tree arg = DECL_ARGUMENTS (fndecl);
+	   arg;
+	   arg = TREE_CHAIN (arg))
+	nargs++;
+      cfun->machine->no_caller_saved_registers = true;
+      cfun->machine->func_type
+	= nargs == 2 ? TYPE_EXCEPTION : TYPE_INTERRUPT;
+
+      /* Only dwarf2out.c can handle -WORD(AP) as a pointer argument.  */
+      if (write_symbols != NO_DEBUG && write_symbols != DWARF2_DEBUG)
+	sorry ("Only DWARF debug format is supported for interrupt "
+	       "service routine.");
+    }
+  else if (lookup_attribute ("no_caller_saved_registers",
+			     TYPE_ATTRIBUTES (TREE_TYPE (fndecl))))
+    cfun->machine->no_caller_saved_registers = true;
+
   tree new_tree = DECL_FUNCTION_SPECIFIC_TARGET (fndecl);
   if (new_tree == NULL_TREE)
     new_tree = target_option_default_node;
@@ -5322,6 +5356,8 @@ ix86_set_current_function (tree fndecl)
     }
   ix86_previous_fndecl = fndecl;
 
+  static bool prev_no_caller_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
      function context.  */
@@ -5329,6 +5365,14 @@ ix86_set_current_function (tree fndecl)
       && (call_used_regs[SI_REG]
 	  == (cfun->machine->call_abi == MS_ABI)))
     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)
+    reinit_regs ();
+
+  prev_no_caller_saved_registers
+    = cfun->machine->no_caller_saved_registers;
 }
 
 
@@ -5592,6 +5636,11 @@ ix86_function_ok_for_sibcall (tree decl, tree exp)
   tree type, decl_or_type;
   rtx a, b;
 
+  /* 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)
+    return false;
+
   /* If we are generating position-independent code, we cannot sibcall
      optimize direct calls to global functions, as the PLT requires
      %ebx be live. (Darwin does not have a PLT.)  */
@@ -6777,6 +6826,8 @@ type_natural_mode (const_tree type, const CUMULATIVE_ARGS *cum,
 		      }
 		  }
 		else if ((size == 8 && !TARGET_64BIT)
+			 && (!cfun
+			     || cfun->machine->func_type == TYPE_NORMAL)
 			 && !TARGET_MMX
 			 && !TARGET_IAMCU)
 		  {
@@ -7756,6 +7807,11 @@ ix86_function_arg_advance (cumulative_args_t cum_v, machine_mode mode,
   HOST_WIDE_INT bytes, words;
   int nregs;
 
+  /* The argument of interrupt handler is a special case and is
+     handled in ix86_function_arg.  */
+  if (!cum->caller && cfun->machine->func_type != TYPE_NORMAL)
+    return;
+
   if (mode == BLKmode)
     bytes = int_size_in_bytes (type);
   else
@@ -8067,6 +8123,36 @@ ix86_function_arg (cumulative_args_t cum_v, machine_mode omode,
   HOST_WIDE_INT bytes, words;
   rtx arg;
 
+  if (!cum->caller && cfun->machine->func_type != TYPE_NORMAL)
+    {
+      gcc_assert (type != NULL_TREE);
+      if (POINTER_TYPE_P (type))
+	{
+	  /* This is the pointer argument.  */
+	  gcc_assert (TYPE_MODE (type) == Pmode);
+	  if (cfun->machine->func_type == TYPE_INTERRUPT)
+	    /* -WORD(AP) in the current frame in interrupt handler.  */
+	    arg = plus_constant (Pmode, arg_pointer_rtx,
+				 -UNITS_PER_WORD);
+	  else
+	    /* (AP) in the current frame in exception handler.  */
+	    arg = arg_pointer_rtx;
+	}
+      else
+	{
+	  gcc_assert (cfun->machine->func_type == TYPE_EXCEPTION
+		      && TREE_CODE (type) == INTEGER_TYPE
+		      && TYPE_MODE (type) == word_mode);
+	  /* The integer argument is the error code at -WORD(AP) in
+	     the current frame in exception handler.  */
+	  arg = gen_rtx_MEM (word_mode,
+			     plus_constant (Pmode,
+					    arg_pointer_rtx,
+					    -UNITS_PER_WORD));
+	}
+      return arg;
+    }
+
   /* All pointer bounds arguments are handled separately here.  */
   if ((type && POINTER_BOUNDS_TYPE_P (type))
       || POINTER_BOUNDS_MODE_P (mode))
@@ -8622,14 +8708,16 @@ ix86_function_value_bounds (const_tree valtype,
 }
 
 /* Pointer function arguments and return values are promoted to
-   word_mode.  */
+   word_mode for normal functions.  */
 
 static machine_mode
 ix86_promote_function_mode (const_tree type, machine_mode mode,
 			    int *punsignedp, const_tree fntype,
 			    int for_return)
 {
-  if (type != NULL_TREE && POINTER_TYPE_P (type))
+  if (cfun->machine->func_type == TYPE_NORMAL
+      && type != NULL_TREE
+      && POINTER_TYPE_P (type))
     {
       *punsignedp = POINTERS_EXTEND_UNSIGNED;
       return word_mode;
@@ -9797,7 +9885,10 @@ ix86_can_use_return_insn_p (void)
 {
   struct ix86_frame frame;
 
-  if (! reload_completed || frame_pointer_needed)
+  /* Don't use `ret' instruction in interrupt handler.  */
+  if (! reload_completed
+      || frame_pointer_needed
+      || cfun->machine->func_type != TYPE_NORMAL)
     return 0;
 
   /* Don't allow more than 32k pop, since that's all we can do
@@ -10109,11 +10200,77 @@ ix86_select_alt_pic_regnum (void)
   return INVALID_REGNUM;
 }
 
+/* Return true if REGNO is used by the epilogue.  */
+
+bool
+ix86_epilogue_uses (int regno)
+{
+  /* 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.  */
+  return (epilogue_completed
+	  && cfun->machine->no_caller_saved_registers
+	  && !fixed_regs[regno]
+	  && !STACK_REGNO_P (regno)
+	  && !MMX_REGNO_P (regno));
+}
+
+/* Return nonzero if register REGNO can be used as a scratch register
+   in peephole2.  */
+
+static bool
+ix86_hard_regno_scratch_ok (unsigned int regno)
+{
+  /* If there are no caller-saved registers, we can't use any register
+     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
+	  || (!epilogue_completed
+	      && df_regs_ever_live_p (regno)));
+}
+
 /* Return TRUE if we need to save REGNO.  */
 
 static bool
 ix86_save_reg (unsigned int regno, bool maybe_eh_return)
 {
+  /* 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;
+      if (reg)
+	{
+	  unsigned int i = REGNO (reg);
+	  unsigned int nregs = hard_regno_nregs[i][GET_MODE (reg)];
+	  while (nregs-- > 0)
+	    if ((i + nregs) == regno)
+	      return false;
+
+	  reg = crtl->return_bnd;
+	  if (reg)
+	    {
+	      i = REGNO (reg);
+	      nregs = hard_regno_nregs[i][GET_MODE (reg)];
+	      while (nregs-- > 0)
+		if ((i + nregs) == regno)
+		  return false;
+	    }
+	}
+
+      return (df_regs_ever_live_p (regno)
+	      && !fixed_regs[regno]
+	      && !STACK_REGNO_P (regno)
+	      && !MMX_REGNO_P (regno)
+	      && (regno != HARD_FRAME_POINTER_REGNUM
+		  || !frame_pointer_needed));
+    }
+
   if (regno == REAL_PIC_OFFSET_TABLE_REGNUM
       && pic_offset_table_rtx)
     {
@@ -10170,6 +10327,34 @@ ix86_nsaved_regs (void)
   return nregs;
 }
 
+/* Return number of saved bound registers.  */
+
+static int
+ix86_nsaved_bndregs (void)
+{
+  int nregs = 0;
+  int regno;
+
+  for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
+    if (BND_REGNO_P (regno) && ix86_save_reg (regno, true))
+      nregs ++;
+  return nregs;
+}
+
+/* Return number of saved mask registers.  */
+
+static int
+ix86_nsaved_maskregs (void)
+{
+  int nregs = 0;
+  int regno;
+
+  for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
+    if (MASK_REGNO_P (regno) && ix86_save_reg (regno, true))
+      nregs ++;
+  return nregs;
+}
+
 /* Return number of saved SSE registers.  */
 
 static int
@@ -10178,7 +10363,10 @@ ix86_nsaved_sseregs (void)
   int nregs = 0;
   int regno;
 
-  if (!TARGET_64BIT_MS_ABI)
+  /* Always need to save SSE registers if there are no caller-saved
+     registers.  */
+  if (!TARGET_64BIT_MS_ABI
+      && !cfun->machine->no_caller_saved_registers)
     return 0;
   for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
     if (SSE_REGNO_P (regno) && ix86_save_reg (regno, true))
@@ -10259,6 +10447,8 @@ ix86_compute_frame_layout (struct ix86_frame *frame)
   HOST_WIDE_INT to_allocate;
 
   frame->nregs = ix86_nsaved_regs ();
+  frame->nbndregs = ix86_nsaved_bndregs ();
+  frame->nmaskregs = ix86_nsaved_maskregs ();
   frame->nsseregs = ix86_nsaved_sseregs ();
 
   /* 64-bit MS ABI seem to require stack alignment to be always 16,
@@ -10363,19 +10553,39 @@ ix86_compute_frame_layout (struct ix86_frame *frame)
   if (TARGET_SEH)
     frame->hard_frame_pointer_offset = offset;
 
+  /* Set BND register save area.  */
+  if (frame->nbndregs)
+    offset += frame->nbndregs * (ix86_pmode == PMODE_DI ? 16 : 8);
+  frame->bnd_reg_save_offset = offset;
+
+  /* Set MASK register save area.  */
+  if (frame->nmaskregs)
+    offset += frame->nmaskregs * (TARGET_AVX512BW ? 8 : 2);
+  frame->mask_reg_save_offset = offset;
+
   /* Align and set SSE register save area.  */
   if (frame->nsseregs)
     {
+      /* We must save full vector registers if there are no
+	 caller-saved registers.  */
+      unsigned int vec_regsize;
+      if (cfun->machine->no_caller_saved_registers)
+	vec_regsize = (TARGET_AVX512F ? 64 : (TARGET_AVX ? 32 : 16));
+      else
+	vec_regsize = 16;
+
       /* The only ABI that has saved SSE registers (Win64) also has a
 	 16-byte aligned default stack, and thus we don't need to be
 	 within the re-aligned local stack frame to save them.  In case
 	 incoming stack boundary is aligned to less than 16 bytes,
 	 unaligned move of SSE register will be emitted, so there is
 	 no point to round up the SSE register save area outside the
-	 re-aligned local stack frame to 16 bytes.  */
-      if (ix86_incoming_stack_boundary >= 128)
-	offset = (offset + 16 - 1) & -16;
-      offset += frame->nsseregs * 16;
+	 re-aligned local stack frame to 16 bytes.  When full vector
+	 registers are saved, we check incoming stack boundary against
+	 the size of vector registers.  */
+      if (ix86_incoming_stack_boundary >= (vec_regsize * BITS_PER_UNIT))
+	offset = (offset + vec_regsize) & -vec_regsize;
+      offset += frame->nsseregs * vec_regsize;
     }
   frame->sse_reg_save_offset = offset;
 
@@ -10429,7 +10639,7 @@ ix86_compute_frame_layout (struct ix86_frame *frame)
   frame->stack_pointer_offset = offset;
 
   /* Size prologue needs to allocate.  */
-  to_allocate = offset - frame->sse_reg_save_offset;
+  to_allocate = offset - frame->bnd_reg_save_offset;
 
   if ((!to_allocate && frame->nregs <= 1)
       || (TARGET_64BIT && to_allocate >= (HOST_WIDE_INT) 0x80000000))
@@ -10682,18 +10892,67 @@ ix86_emit_save_regs_using_mov (HOST_WIDE_INT cfa_offset)
       }
 }
 
+/* Emit code to save BND registers using MOV insns.
+   First register is stored at CFA - CFA_OFFSET.  */
+static void
+ix86_emit_save_bnd_regs_using_mov (HOST_WIDE_INT cfa_offset)
+{
+  unsigned int regno;
+  enum machine_mode mode = BNDmode;
+  unsigned int size = GET_MODE_SIZE (mode);
+
+  for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
+    if (BND_REGNO_P (regno) && ix86_save_reg (regno, true))
+      {
+	ix86_emit_save_reg_using_mov (mode, regno, cfa_offset);
+	cfa_offset -= size;
+      }
+}
+
+/* Emit code to save MASK registers using MOV insns.
+   First register is stored at CFA - CFA_OFFSET.  */
+static void
+ix86_emit_save_mask_regs_using_mov (HOST_WIDE_INT cfa_offset)
+{
+  unsigned int regno;
+  enum machine_mode mode = TARGET_AVX512BW ? DImode : HImode;
+  unsigned int size = GET_MODE_SIZE (mode);
+
+  for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
+    if (MASK_REGNO_P (regno) && ix86_save_reg (regno, true))
+      {
+	ix86_emit_save_reg_using_mov (mode, regno, cfa_offset);
+	cfa_offset -= size;
+      }
+}
+
 /* Emit code to save SSE registers using MOV insns.
    First register is stored at CFA - CFA_OFFSET.  */
 static void
 ix86_emit_save_sse_regs_using_mov (HOST_WIDE_INT cfa_offset)
 {
   unsigned int regno;
+  enum machine_mode vector_reg_mode;
+
+  if (cfun->machine->no_caller_saved_registers)
+    {
+      /* We must save full vector registers if there are no
+	 caller-saved registers.  */
+      if (TARGET_AVX512F)
+	vector_reg_mode = V16SFmode;
+      else if (TARGET_AVX)
+	vector_reg_mode = V8SFmode;
+      else
+	vector_reg_mode = V4SFmode;
+    }
+  else
+    vector_reg_mode = V4SFmode;
 
   for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
     if (SSE_REGNO_P (regno) && ix86_save_reg (regno, true))
       {
-	ix86_emit_save_reg_using_mov (V4SFmode, regno, cfa_offset);
-	cfa_offset -= GET_MODE_SIZE (V4SFmode);
+	ix86_emit_save_reg_using_mov (vector_reg_mode, regno, cfa_offset);
+	cfa_offset -= GET_MODE_SIZE (vector_reg_mode);
       }
 }
 
@@ -10849,13 +11108,17 @@ find_drap_reg (void)
 {
   tree decl = cfun->decl;
 
+  /* Always use callee-saved register if there are no caller-saved
+     registers.  */
   if (TARGET_64BIT)
     {
       /* Use R13 for nested function or function need static chain.
 	 Since function with tail call may use any caller-saved
 	 registers in epilogue, DRAP must not use caller-saved
 	 register in such case.  */
-      if (DECL_STATIC_CHAIN (decl) || crtl->tail_call_emit)
+      if (DECL_STATIC_CHAIN (decl)
+	  || cfun->machine->no_caller_saved_registers
+	  || crtl->tail_call_emit)
 	return R13_REG;
 
       return R10_REG;
@@ -10866,7 +11129,9 @@ find_drap_reg (void)
 	 Since function with tail call may use any caller-saved
 	 registers in epilogue, DRAP must not use caller-saved
 	 register in such case.  */
-      if (DECL_STATIC_CHAIN (decl) || crtl->tail_call_emit)
+      if (DECL_STATIC_CHAIN (decl)
+	  || cfun->machine->no_caller_saved_registers
+	  || crtl->tail_call_emit)
 	return DI_REG;
 
       /* Reuse static chain register if it isn't used for parameter
@@ -10907,8 +11172,12 @@ ix86_minimum_incoming_stack_boundary (bool sibcall)
 {
   unsigned int incoming_stack_boundary;
 
+  /* Stack of interrupt handler is always aligned to MIN_STACK_BOUNDARY.
+   */
+  if (cfun->machine->func_type != TYPE_NORMAL)
+    incoming_stack_boundary = MIN_STACK_BOUNDARY;
   /* Prefer the one specified at command line. */
-  if (ix86_user_incoming_stack_boundary)
+  else if (ix86_user_incoming_stack_boundary)
     incoming_stack_boundary = ix86_user_incoming_stack_boundary;
   /* In 32bit, use MIN_STACK_BOUNDARY for incoming stack boundary
      if -mstackrealign is used, it isn't used for sibcall check and
@@ -11541,6 +11810,8 @@ ix86_expand_prologue (void)
   struct ix86_frame frame;
   HOST_WIDE_INT allocate;
   bool int_registers_saved;
+  bool bnd_registers_saved;
+  bool mask_registers_saved;
   bool sse_registers_saved;
   rtx static_chain = NULL_RTX;
 
@@ -11660,6 +11931,12 @@ ix86_expand_prologue (void)
     {
       int align_bytes = crtl->stack_alignment_needed / BITS_PER_UNIT;
 
+      /* Can't use DRAP in interrupt function.  */
+      if (cfun->machine->func_type != TYPE_NORMAL)
+	sorry ("Dynamic Realign Argument Pointer (DRAP) not supported "
+	       "in interrupt service routine.  This may be worked "
+	       "around by avoiding functions with aggregate return.");
+
       /* Only need to push parameter pointer reg if it is caller saved.  */
       if (!call_used_regs[REGNO (crtl->drap_reg)])
 	{
@@ -11706,6 +11983,8 @@ ix86_expand_prologue (void)
     }
 
   int_registers_saved = (frame.nregs == 0);
+  bnd_registers_saved = (frame.nbndregs == 0);
+  mask_registers_saved = (frame.nmaskregs == 0);
   sse_registers_saved = (frame.nsseregs == 0);
 
   if (frame_pointer_needed && !m->fs.fp_valid)
@@ -11770,10 +12049,10 @@ ix86_expand_prologue (void)
 	 that we must allocate the size of the register save area before
 	 performing the actual alignment.  Otherwise we cannot guarantee
 	 that there's enough storage above the realignment point.  */
-      if (m->fs.sp_offset != frame.sse_reg_save_offset)
+      if (m->fs.sp_offset != frame.bnd_reg_save_offset)
         pro_epilogue_adjust_stack (stack_pointer_rtx, stack_pointer_rtx,
 				   GEN_INT (m->fs.sp_offset
-					    - frame.sse_reg_save_offset),
+					    - frame.bnd_reg_save_offset),
 				   -1, false);
 
       /* Align the stack.  */
@@ -12000,6 +12279,10 @@ ix86_expand_prologue (void)
 
   if (!int_registers_saved)
     ix86_emit_save_regs_using_mov (frame.reg_save_offset);
+  if (!bnd_registers_saved)
+    ix86_emit_save_bnd_regs_using_mov (frame.bnd_reg_save_offset);
+  if (!mask_registers_saved)
+    ix86_emit_save_mask_regs_using_mov (frame.mask_reg_save_offset);
   if (!sse_registers_saved)
     ix86_emit_save_sse_regs_using_mov (frame.sse_reg_save_offset);
 
@@ -12185,6 +12468,90 @@ ix86_emit_restore_regs_using_mov (HOST_WIDE_INT cfa_offset,
       }
 }
 
+/* Emit code to restore saved BND registers using MOV insns.
+   First register is restored from CFA - CFA_OFFSET.  */
+static void
+ix86_emit_restore_bnd_regs_using_mov (HOST_WIDE_INT cfa_offset,
+				      bool maybe_eh_return)
+{
+  struct machine_function *m = cfun->machine;
+  unsigned int regno;
+  enum machine_mode mode = BNDmode;
+  unsigned int size = GET_MODE_SIZE (mode);
+
+  for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
+    if (BND_REGNO_P (regno) && ix86_save_reg (regno, maybe_eh_return))
+      {
+	rtx reg = gen_rtx_REG (mode, regno);
+	rtx mem;
+	rtx_insn *insn;
+
+	mem = choose_baseaddr (cfa_offset);
+	mem = gen_frame_mem (mode, mem);
+	insn = emit_move_insn (reg, mem);
+
+        if (m->fs.cfa_reg == crtl->drap_reg && regno == REGNO (crtl->drap_reg))
+	  {
+	    /* Previously we'd represented the CFA as an expression
+	       like *(%ebp - 8).  We've just popped that value from
+	       the stack, which means we need to reset the CFA to
+	       the drap register.  This will remain until we restore
+	       the stack pointer.  */
+	    add_reg_note (insn, REG_CFA_DEF_CFA, reg);
+	    RTX_FRAME_RELATED_P (insn) = 1;
+
+	    /* This means that the DRAP register is valid for addressing.  */
+	    m->fs.drap_valid = true;
+	  }
+	else
+	  ix86_add_cfa_restore_note (NULL, reg, cfa_offset);
+
+	cfa_offset -= size;
+      }
+}
+
+/* Emit code to restore saved MASK registers using MOV insns.
+   First register is restored from CFA - CFA_OFFSET.  */
+static void
+ix86_emit_restore_mask_regs_using_mov (HOST_WIDE_INT cfa_offset,
+				       bool maybe_eh_return)
+{
+  struct machine_function *m = cfun->machine;
+  unsigned int regno;
+  enum machine_mode mode = TARGET_AVX512BW ? DImode : HImode;
+  unsigned int size = GET_MODE_SIZE (mode);
+
+  for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
+    if (MASK_REGNO_P (regno) && ix86_save_reg (regno, maybe_eh_return))
+      {
+	rtx reg = gen_rtx_REG (mode, regno);
+	rtx mem;
+	rtx_insn *insn;
+
+	mem = choose_baseaddr (cfa_offset);
+	mem = gen_frame_mem (mode, mem);
+	insn = emit_move_insn (reg, mem);
+
+        if (m->fs.cfa_reg == crtl->drap_reg && regno == REGNO (crtl->drap_reg))
+	  {
+	    /* Previously we'd represented the CFA as an expression
+	       like *(%ebp - 8).  We've just popped that value from
+	       the stack, which means we need to reset the CFA to
+	       the drap register.  This will remain until we restore
+	       the stack pointer.  */
+	    add_reg_note (insn, REG_CFA_DEF_CFA, reg);
+	    RTX_FRAME_RELATED_P (insn) = 1;
+
+	    /* This means that the DRAP register is valid for addressing.  */
+	    m->fs.drap_valid = true;
+	  }
+	else
+	  ix86_add_cfa_restore_note (NULL, reg, cfa_offset);
+
+	cfa_offset -= size;
+      }
+}
+
 /* Emit code to restore saved registers using MOV insns.
    First register is restored from CFA - CFA_OFFSET.  */
 static void
@@ -12192,36 +12559,53 @@ ix86_emit_restore_sse_regs_using_mov (HOST_WIDE_INT cfa_offset,
 				      bool maybe_eh_return)
 {
   unsigned int regno;
+  enum machine_mode vector_reg_mode;
+
+  if (cfun->machine->no_caller_saved_registers)
+    {
+      /* We must restore full vector registers if there are no
+	 caller-saved registers.  */
+      if (TARGET_AVX512F)
+	vector_reg_mode = V16SFmode;
+      else if (TARGET_AVX)
+	vector_reg_mode = V8SFmode;
+      else
+	vector_reg_mode = V4SFmode;
+    }
+  else
+    vector_reg_mode = V4SFmode;
 
   for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
     if (SSE_REGNO_P (regno) && ix86_save_reg (regno, maybe_eh_return))
       {
-	rtx reg = gen_rtx_REG (V4SFmode, regno);
+	rtx reg = gen_rtx_REG (vector_reg_mode, regno);
 	rtx mem;
  	unsigned int align;
 
 	mem = choose_baseaddr (cfa_offset);
-	mem = gen_rtx_MEM (V4SFmode, mem);
-
- 	/* The location is aligned up to INCOMING_STACK_BOUNDARY.  */
-	align = MIN (GET_MODE_ALIGNMENT (V4SFmode), INCOMING_STACK_BOUNDARY);
- 	set_mem_align (mem, align);
-
- 	/* SSE saves are not within re-aligned local stack frame.
- 	   In case INCOMING_STACK_BOUNDARY is misaligned, we have
- 	   to emit unaligned load.  */
- 	if (align < 128)
- 	  {
- 	    rtx unspec = gen_rtx_UNSPEC (V4SFmode, gen_rtvec (1, mem),
- 					 UNSPEC_LOADU);
+	mem = gen_rtx_MEM (vector_reg_mode, mem);
+
+	/* The location is aligned up to INCOMING_STACK_BOUNDARY.  */
+	align = MIN (GET_MODE_ALIGNMENT (vector_reg_mode),
+		     INCOMING_STACK_BOUNDARY);
+	set_mem_align (mem, align);
+
+	/* SSE saves are not within re-aligned local stack frame.
+	   In case INCOMING_STACK_BOUNDARY is misaligned, we have
+	   to emit unaligned load.  */
+	if (vector_reg_mode == V4SFmode && align < 128)
+	  {
+	    rtx unspec = gen_rtx_UNSPEC (vector_reg_mode,
+					 gen_rtvec (1, mem),
+					 UNSPEC_LOADU);
  	    emit_insn (gen_rtx_SET (VOIDmode, reg, unspec));
- 	  }
- 	else
+	  }
+	else
  	  emit_insn (gen_rtx_SET (VOIDmode, reg, mem));
 
 	ix86_add_cfa_restore_note (NULL_RTX, reg, cfa_offset);
 
-	cfa_offset -= GET_MODE_SIZE (V4SFmode);
+	cfa_offset -= GET_MODE_SIZE (vector_reg_mode);
       }
 }
 
@@ -12320,16 +12704,25 @@ ix86_expand_epilogue (int style)
       if (TARGET_64BIT
 	  && m->fs.sp_offset > 0x7fffffff
 	  && !(m->fs.fp_valid || m->fs.drap_valid)
-	  && (frame.nsseregs + frame.nregs) != 0)
+	  && (frame.nsseregs + frame.nmaskregs + frame.nbndregs
+	      + frame.nregs) != 0)
 	{
 	  pro_epilogue_adjust_stack (stack_pointer_rtx, stack_pointer_rtx,
 				     GEN_INT (m->fs.sp_offset
-					      - frame.sse_reg_save_offset),
+					      - frame.bnd_reg_save_offset),
 				     style,
 				     m->fs.cfa_reg == stack_pointer_rtx);
 	}
     }
 
+  if (frame.nbndregs)
+    ix86_emit_restore_bnd_regs_using_mov (frame.bnd_reg_save_offset,
+					  style == 2);
+
+  if (frame.nmaskregs)
+    ix86_emit_restore_mask_regs_using_mov (frame.mask_reg_save_offset,
+					   style == 2);
+
   /* If there are any SSE registers to restore, then we have to do it
      via moves, since there's obviously no pop for SSE regs.  */
   if (frame.nsseregs)
@@ -12526,7 +12919,20 @@ ix86_expand_epilogue (int style)
       return;
     }
 
-  if (crtl->args.pops_args && crtl->args.size)
+  if (cfun->machine->func_type != TYPE_NORMAL)
+    {
+      /* Return with the "IRET" instruction from interrupt handler.
+	 Pop the 'ERROR_CODE' off the stack before the 'IRET'
+	 instruction in exception handler.  */
+      if (cfun->machine->func_type == TYPE_EXCEPTION)
+	{
+	  rtx r = plus_constant (Pmode, stack_pointer_rtx,
+				 UNITS_PER_WORD);
+	  emit_insn (gen_rtx_SET (VOIDmode, stack_pointer_rtx, r));
+	}
+      emit_jump_insn (gen_interrupt_return ());
+    }
+  else if (crtl->args.pops_args && crtl->args.size)
     {
       rtx popc = GEN_INT (crtl->args.pops_args);
 
@@ -17598,6 +18004,14 @@ ix86_expand_move (machine_mode mode, rtx operands[])
   rtx op0, op1;
   enum tls_model model;
 
+  if (cfun->machine->func_type != TYPE_NORMAL && IS_STACK_MODE (mode))
+    {
+      error ("80387 instructions aren't allowed in %s service routine",
+	     (cfun->machine->func_type == TYPE_EXCEPTION
+	      ? "exception" : "interrupt"));
+      return;
+    }
+
   op0 = operands[0];
   op1 = operands[1];
 
@@ -17749,6 +18163,15 @@ ix86_expand_vector_move (machine_mode mode, rtx operands[])
 			? GET_MODE_BITSIZE (mode)
 			: GET_MODE_ALIGNMENT (mode));
 
+  if (cfun->machine->func_type != TYPE_NORMAL
+      && VALID_MMX_REG_MODE (mode))
+    {
+      error ("MMX/3Dnow instructions aren't allowed in %s service routine",
+	     (cfun->machine->func_type == TYPE_EXCEPTION
+	      ? "exception" : "interrupt"));
+      return;
+    }
+
   if (push_operand (op0, VOIDmode))
     op0 = emit_move_resolve_push (mode, op0);
 
@@ -25885,6 +26308,18 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
   rtx vec[3];
   rtx use = NULL, call;
   unsigned int vec_len = 0;
+  tree fndecl;
+
+  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 can't be called directly");
+    }
+  else
+    fndecl = NULL_TREE;
 
   if (pop == const0_rtx)
     pop = NULL;
@@ -25976,8 +26411,30 @@ ix86_expand_call (rtx retval, rtx fnaddr, rtx callarg1,
       vec[vec_len++] = pop;
     }
 
-  if (TARGET_64BIT_MS_ABI
-      && (!callarg2 || INTVAL (callarg2) != -2))
+  if (cfun->machine->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);
+
+      /* If there are no caller-saved registers, add all registers
+	 that are clobbered by the call which returns.  */
+      for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+	if (!fixed_regs[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));
+    }
+  else if (TARGET_64BIT_MS_ABI
+	   && (!callarg2 || INTVAL (callarg2) != -2))
     {
       int const cregs_size
 	= ARRAY_SIZE (x86_64_ms_sysv_extra_clobbered_registers);
@@ -43253,6 +43710,54 @@ ix86_handle_fndecl_attribute (tree *node, tree name, tree, int,
   return NULL_TREE;
 }
 
+static tree
+ix86_handle_no_caller_saved_registers_attribute (tree *, tree, tree,
+						 int, bool *)
+{
+  return NULL_TREE;
+}
+
+static tree
+ix86_handle_interrupt_attribute (tree *node, tree, tree, int, bool *)
+{
+  /* DECL_RESULT and DECL_ARGUMENTS do not exist there yet,
+     but the function type contains args and return type data.  */
+  tree func_type = *node;
+  tree return_type = TREE_TYPE (func_type);
+
+  int nargs = 0;
+  tree current_arg_type = TYPE_ARG_TYPES (func_type);
+  while (current_arg_type
+	 && ! VOID_TYPE_P (TREE_VALUE (current_arg_type)))
+    {
+      if (nargs == 0)
+	{
+	  if (! POINTER_TYPE_P (TREE_VALUE (current_arg_type)))
+	    error ("interrupt service routine should have a pointer "
+		   "as the first argument");
+	}
+      else if (nargs == 1)
+	{
+	  if (TREE_CODE (TREE_VALUE (current_arg_type)) != INTEGER_TYPE
+	      || TYPE_MODE (TREE_VALUE (current_arg_type)) != word_mode)
+	    error ("interrupt service routine should have unsigned %s"
+		   "int as the second argument",
+		   TARGET_64BIT
+		   ? (TARGET_X32 ? "long long " : "long ")
+		   : "");
+	}
+      nargs++;
+      current_arg_type = TREE_CHAIN (current_arg_type);
+    }
+  if (!nargs || nargs > 2)
+    error ("interrupt service routine can only have a pointer argument "
+	   "and an optional integer argument");
+  if (! VOID_TYPE_P (return_type))
+    error ("interrupt service routine can't have non-void return value");
+
+  return NULL_TREE;
+}
+
 static bool
 ix86_ms_bitfield_layout_p (const_tree record_type)
 {
@@ -47138,6 +47643,11 @@ static const struct attribute_spec ix86_attribute_table[] =
     false },
   { "callee_pop_aggregate_return", 1, 1, false, true, true,
     ix86_handle_callee_pop_aggregate_return, true },
+  { "interrupt", 0, 0, false, true, true,
+    ix86_handle_interrupt_attribute, false },
+  { "no_caller_saved_registers", 0, 0, false, true, true,
+    ix86_handle_no_caller_saved_registers_attribute, false },
+
   /* End element.  */
   { NULL,        0, 0, false, false, false, NULL, false }
 };
@@ -52773,6 +53283,9 @@ ix86_binds_local_p (const_tree exp)
 #undef TARGET_ABSOLUTE_BIGGEST_ALIGNMENT
 #define TARGET_ABSOLUTE_BIGGEST_ALIGNMENT 512
 
+#undef TARGET_HARD_REGNO_SCRATCH_OK
+#define TARGET_HARD_REGNO_SCRATCH_OK ix86_hard_regno_scratch_ok
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 #include "gt-i386.h"
diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h
index 1a369c2..88f2f46 100644
--- a/gcc/config/i386/i386.h
+++ b/gcc/config/i386/i386.h
@@ -1619,11 +1619,18 @@ enum reg_class
 
    If stack probes are required, the space used for large function
    arguments on the stack must also be probed, so enable
-   -maccumulate-outgoing-args so this happens in the prologue.  */
+   -maccumulate-outgoing-args so this happens in the prologue.
+
+   We must use argument accumulation in interrupt function if stack
+   may be realigned to avoid DRAP.  */
 
 #define ACCUMULATE_OUTGOING_ARGS \
-  ((TARGET_ACCUMULATE_OUTGOING_ARGS && optimize_function_for_speed_p (cfun)) \
-   || TARGET_STACK_PROBE || TARGET_64BIT_MS_ABI)
+  ((TARGET_ACCUMULATE_OUTGOING_ARGS \
+    && optimize_function_for_speed_p (cfun)) \
+   || (cfun->machine->func_type != TYPE_NORMAL \
+       && crtl->stack_realign_needed) \
+   || TARGET_STACK_PROBE \
+   || TARGET_64BIT_MS_ABI)
 
 /* If defined, a C expression whose value is nonzero when we want to use PUSH
    instructions to pass outgoing arguments.  */
@@ -1731,6 +1738,11 @@ typedef struct ix86_args {
 
 #define EXIT_IGNORE_STACK 1
 
+/* Define this macro as a C expression that is nonzero for registers
+   used by the epilogue or the `return' pattern.  */
+
+#define EPILOGUE_USES(REGNO) ix86_epilogue_uses (REGNO)
+
 /* Output assembler code for a block containing the constant parts
    of a trampoline, leaving space for the variable parts.  */
 
@@ -2446,6 +2458,18 @@ struct GTY(()) machine_frame_state
 /* Private to winnt.c.  */
 struct seh_frame_state;
 
+enum function_type
+{
+  TYPE_NORMAL = 0,
+  /* The current function is an interrupt service routine with a
+     pointer argument as specified by the "interrupt" attribute.  */
+  TYPE_INTERRUPT,
+  /* The current function is an interrupt service routine with a
+     pointer argument and an integer argument as specified by the
+     "interrupt" attribute.  */
+  TYPE_EXCEPTION
+};
+
 struct GTY(()) machine_function {
   struct stack_local_entry *stack_locals;
   const char *some_ld_name;
@@ -2496,6 +2520,13 @@ struct GTY(()) machine_function {
   /* If true, it is safe to not save/restore DRAP register.  */
   BOOL_BITFIELD no_drap_save_restore : 1;
 
+  /* Function type.  */
+  ENUM_BITFIELD(function_type) func_type : 2;
+
+  /* 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;
+
   /* If true, there is register available for argument passing.  This
      is used only in ix86_function_ok_for_sibcall by 32-bit to determine
      if there is scratch register available for indirect sibcall.  In
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index f6d990d..a3f218b 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -193,6 +193,9 @@
   UNSPEC_BNDCU
   UNSPEC_BNDCN
   UNSPEC_MPX_FENCE
+
+  ;; IRET support
+  UNSPEC_INTERRUPT_RETURN
 ])
 
 (define_c_enum "unspecv" [
@@ -12217,6 +12220,12 @@
    (set_attr "length_immediate" "0")
    (set_attr "modrm" "0")])
 
+(define_insn "interrupt_return"
+  [(simple_return)
+   (unspec [(const_int 0)] UNSPEC_INTERRUPT_RETURN)]
+  "reload_completed"
+  "iret")
+
 ;; Used by x86_machine_dependent_reorg to avoid penalty on single byte RET
 ;; instruction Athlon and K8 have.
 
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 70a0903..20331a7 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3929,6 +3929,69 @@ 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.
 
+@item no_caller_saved_registers
+@cindex @code{no_caller_saved_registers} function attribute, x86
+Use this attribute to indicate that the specified function has no
+caller-saved registers.  That is, all registers are callee-saved.
+The compiler generates proper function entry and exit sequences to
+save and restore any modified registers.
+
+@item interrupt
+@cindex @code{interrupt} function attribute, x86
+Use this attribute to indicate that the specified function is an
+interrupt handler.  The compiler generates function
+entry and exit sequences suitable for use in an interrupt handler when
+this attribute is present.  The @code{IRET} instruction, instead of the
+@code{RET} instruction, is used to return from interrupt handlers.  All
+registers, except for the EFLAGS register which is restored by the
+@code{IRET} instruction, are preserved by the compiler.
+
+Any interruptible-without-stack-switch code must be compiled with
+@option{-mno-red-zone} since interrupt handlers can and will, because
+of the hardware design, touch the red zone.
+
+An interrupt handler must be declared with a mandatory pointer
+argument:
+
+@smallexample
+struct interrupt_frame;
+
+__attribute__ ((interrupt))
+void
+f (struct interrupt_frame *frame)
+@{
+@}
+@end smallexample
+
+and user must properly define the structure the pointer pointing to.
+
+The exception handler is very similar to the interrupt handler with
+a different mandatory function signature:
+
+@smallexample
+#ifdef __x86_64__
+typedef unsigned long long int uword_t;
+#else
+typedef unsigned int uword_t;
+#endif
+
+struct interrupt_frame;
+
+__attribute__ ((interrupt))
+void
+f (struct interrupt_frame *frame, uword_t error_code)
+@{
+  ...
+@}
+@end smallexample
+
+and compiler pops the error code off the stack before the @code{IRET}
+instruction.
+
+The exception handler should only be used for exceptions which push an
+error code and all other exceptions must use the interrupt handler.
+The system will crash if the wrong handler is used.
+
 @item syscall_linkage
 @cindex @code{syscall_linkage} function attribute, IA-64
 This attribute is used to modify the IA-64 calling convention by marking
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index e32e77e..b252d6d 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -3941,6 +3941,10 @@ which the caller passes the value, and
 fashion to tell the function being called where the arguments will
 arrive.
 
+@code{TARGET_FUNCTION_INCOMING_ARG} can also return arbitrary address
+computation using hard register, which can be forced into a register,
+so that it can be used to pass special arguments.
+
 If @code{TARGET_FUNCTION_INCOMING_ARG} is not defined,
 @code{TARGET_FUNCTION_ARG} serves both purposes.
 @end deftypefn
diff --git a/gcc/function.c b/gcc/function.c
index 5059cdf..7299936 100644
--- a/gcc/function.c
+++ b/gcc/function.c
@@ -3360,7 +3360,11 @@ assign_parm_setup_stack (struct assign_parm_data_all *all, tree parm,
 			   BLOCK_OP_NORMAL);
 	}
       else
-	emit_move_insn (dest, src);
+	{
+	  if (!REG_P (src))
+	    src = force_reg (GET_MODE (src), src);
+	  emit_move_insn (dest, src);
+	}
     }
 
   if (to_conversion)
diff --git a/gcc/target.def b/gcc/target.def
index a00181a..fff7409 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -4359,8 +4359,8 @@ a register.",
        bool named),
  default_function_arg)
 
-/* Likewise, but for machines with register windows.  Return the
-   location where the argument will appear to the callee.  */
+/* Likewise, but for machines with register windows or special arguments.
+   Return the location where the argument will appear to the callee.  */
 DEFHOOK
 (function_incoming_arg,
  "Define this hook if the target machine has ``register windows'', so\n\
@@ -4374,6 +4374,10 @@ which the caller passes the value, and\n\
 fashion to tell the function being called where the arguments will\n\
 arrive.\n\
 \n\
+@code{TARGET_FUNCTION_INCOMING_ARG} can also return arbitrary address\n\
+computation using hard register, which can be forced into a register,\n\
+so that it can be used to pass special arguments.\n\
+\n\
 If @code{TARGET_FUNCTION_INCOMING_ARG} is not defined,\n\
 @code{TARGET_FUNCTION_ARG} serves both purposes.",
  rtx, (cumulative_args_t ca, machine_mode mode, const_tree type,
diff --git a/gcc/testsuite/gcc.dg/guality/pr68037-1.c b/gcc/testsuite/gcc.dg/guality/pr68037-1.c
new file mode 100644
index 0000000..ac3431c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/guality/pr68037-1.c
@@ -0,0 +1,65 @@
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+/* { dg-options "-g" } */
+
+extern void exit (int);
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+#define ERROR		0x12345670
+#define IP		0x12345671
+#define CS		0x12345672
+#define FLAGS		0x12345673
+#define SP		0x12345674
+#define SS		0x12345675
+
+#define STRING(x)	XSTRING(x)
+#define XSTRING(x)	#x
+
+struct interrupt_frame
+{
+  uword_t ip;
+  uword_t cs;
+  uword_t flags;
+  uword_t sp;
+  uword_t ss;
+};
+
+__attribute__((interrupt, used))
+void
+fn (struct interrupt_frame *frame, uword_t error)
+{
+  if (ERROR != error)		/* BREAK */
+    __builtin_abort ();
+  if (IP != frame->ip)
+    __builtin_abort ();
+  if (CS != frame->cs)
+    __builtin_abort ();
+  if (FLAGS != frame->flags)
+    __builtin_abort ();
+  if (SP != frame->sp)
+    __builtin_abort ();
+  if (SS != frame->ss)
+    __builtin_abort ();
+
+  exit (0);
+}
+
+int
+main ()
+{
+  asm ("push	$" STRING (SS) ";		\
+	push	$" STRING (SP) ";		\
+	push	$" STRING (FLAGS) ";		\
+	push	$" STRING (CS) ";		\
+	push	$" STRING (IP) ";		\
+	push	$" STRING (ERROR) ";		\
+	jmp	fn");
+  return 0;
+}
+
+/* { dg-final { gdb-test 31 "error" "0x12345670" } } */
+/* { dg-final { gdb-test 31 "frame->ip" "0x12345671" } } */
+/* { dg-final { gdb-test 31 "frame->cs" "0x12345672" } } */
+/* { dg-final { gdb-test 31 "frame->flags" "0x12345673" } } */
+/* { dg-final { gdb-test 31 "frame->sp" "0x12345674" } } */
+/* { dg-final { gdb-test 31 "frame->ss" "0x12345675" } } */
diff --git a/gcc/testsuite/gcc.dg/guality/pr68037-2.c b/gcc/testsuite/gcc.dg/guality/pr68037-2.c
new file mode 100644
index 0000000..2010384
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/guality/pr68037-2.c
@@ -0,0 +1,60 @@
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+/* { dg-options "-g" } */
+
+extern void exit (int);
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+#define IP		0x12345671
+#define CS		0x12345672
+#define FLAGS		0x12345673
+#define SP		0x12345674
+#define SS		0x12345675
+
+#define STRING(x)	XSTRING(x)
+#define XSTRING(x)	#x
+
+struct interrupt_frame
+{
+  uword_t ip;
+  uword_t cs;
+  uword_t flags;
+  uword_t sp;
+  uword_t ss;
+};
+
+__attribute__((interrupt, used))
+void
+fn (struct interrupt_frame *frame)
+{
+  if (IP != frame->ip)		/* BREAK */
+    __builtin_abort ();
+  if (CS != frame->cs)
+    __builtin_abort ();
+  if (FLAGS != frame->flags)
+    __builtin_abort ();
+  if (SP != frame->sp)
+    __builtin_abort ();
+  if (SS != frame->ss)
+    __builtin_abort ();
+
+  exit (0);
+}
+
+int
+main ()
+{
+  asm ("push	$" STRING (SS) ";		\
+	push	$" STRING (SP) ";		\
+	push	$" STRING (FLAGS) ";		\
+	push	$" STRING (CS) ";		\
+	push	$" STRING (IP) ";		\
+	jmp	fn");
+  return 0;
+}
+
+/* { dg-final { gdb-test 30 "frame->ip" "0x12345671" } } */
+/* { dg-final { gdb-test 30 "frame->cs" "0x12345672" } } */
+/* { dg-final { gdb-test 30 "frame->flags" "0x12345673" } } */
+/* { dg-final { gdb-test 30 "frame->sp" "0x12345674" } } */
+/* { dg-final { gdb-test 30 "frame->ss" "0x12345675" } } */
diff --git a/gcc/testsuite/gcc.dg/guality/pr68037-3.c b/gcc/testsuite/gcc.dg/guality/pr68037-3.c
new file mode 100644
index 0000000..10854f5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/guality/pr68037-3.c
@@ -0,0 +1,76 @@
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+/* { dg-options "-g" } */
+
+#include <stddef.h>
+
+extern void exit (int);
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+typedef int aligned __attribute__((aligned(64)));
+
+#define IP		0x12345671
+#define CS		0x12345672
+#define FLAGS		0x12345673
+#define SP		0x12345674
+#define SS		0x12345675
+
+#define STRING(x)	XSTRING(x)
+#define XSTRING(x)	#x
+
+struct interrupt_frame
+{
+  uword_t ip;
+  uword_t cs;
+  uword_t flags;
+  uword_t sp;
+  uword_t ss;
+};
+
+int
+check_int (int *i, int align)
+{
+  *i = 20;
+  if ((((ptrdiff_t) i) & (align - 1)) != 0)
+    __builtin_abort ();
+  return *i;
+}
+
+__attribute__((interrupt, used))
+void
+fn (struct interrupt_frame *frame)
+{
+  aligned i;
+  if (check_int (&i, __alignof__(i)) != i)
+    __builtin_abort ();
+
+  if (IP != frame->ip)		/* BREAK */
+    __builtin_abort ();
+  if (CS != frame->cs)
+    __builtin_abort ();
+  if (FLAGS != frame->flags)
+    __builtin_abort ();
+  if (SP != frame->sp)
+    __builtin_abort ();
+  if (SS != frame->ss)
+    __builtin_abort ();
+
+  exit (0);
+}
+
+int
+main ()
+{
+  asm ("push	$" STRING (SS) ";		\
+	push	$" STRING (SP) ";		\
+	push	$" STRING (FLAGS) ";		\
+	push	$" STRING (CS) ";		\
+	push	$" STRING (IP) ";		\
+	jmp	fn");
+  return 0;
+}
+
+/* { dg-final { gdb-test 46 "frame->ip" "0x12345671" } } */
+/* { dg-final { gdb-test 46 "frame->cs" "0x12345672" } } */
+/* { dg-final { gdb-test 46 "frame->flags" "0x12345673" } } */
+/* { dg-final { gdb-test 46 "frame->sp" "0x12345674" } } */
+/* { dg-final { gdb-test 46 "frame->ss" "0x12345675" } } */
diff --git a/gcc/testsuite/gcc.dg/torture/pr68037-1.c b/gcc/testsuite/gcc.dg/torture/pr68037-1.c
new file mode 100644
index 0000000..0730c41
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr68037-1.c
@@ -0,0 +1,57 @@
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+
+extern void exit (int);
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+#define ERROR		0x12345670
+#define IP		0x12345671
+#define CS		0x12345672
+#define FLAGS		0x12345673
+#define SP		0x12345674
+#define SS		0x12345675
+
+#define STRING(x)	XSTRING(x)
+#define XSTRING(x)	#x
+
+struct interrupt_frame
+{
+  uword_t ip;
+  uword_t cs;
+  uword_t flags;
+  uword_t sp;
+  uword_t ss;
+};
+
+__attribute__((interrupt, used))
+void
+fn (struct interrupt_frame *frame, uword_t error)
+{
+  if (ERROR != error)
+    __builtin_abort ();
+  if (IP != frame->ip)
+    __builtin_abort ();
+  if (CS != frame->cs)
+    __builtin_abort ();
+  if (FLAGS != frame->flags)
+    __builtin_abort ();
+  if (SP != frame->sp)
+    __builtin_abort ();
+  if (SS != frame->ss)
+    __builtin_abort ();
+
+  exit (0);
+}
+
+int
+main ()
+{
+  asm ("push	$" STRING (SS) ";		\
+	push	$" STRING (SP) ";		\
+	push	$" STRING (FLAGS) ";		\
+	push	$" STRING (CS) ";		\
+	push	$" STRING (IP) ";		\
+	push	$" STRING (ERROR) ";		\
+	jmp	fn");
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/pr68037-2.c b/gcc/testsuite/gcc.dg/torture/pr68037-2.c
new file mode 100644
index 0000000..197e8a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr68037-2.c
@@ -0,0 +1,53 @@
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+
+extern void exit (int);
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+#define IP		0x12345671
+#define CS		0x12345672
+#define FLAGS		0x12345673
+#define SP		0x12345674
+#define SS		0x12345675
+
+#define STRING(x)	XSTRING(x)
+#define XSTRING(x)	#x
+
+struct interrupt_frame
+{
+  uword_t ip;
+  uword_t cs;
+  uword_t flags;
+  uword_t sp;
+  uword_t ss;
+};
+
+__attribute__((interrupt, used))
+void
+fn (struct interrupt_frame *frame)
+{
+  if (IP != frame->ip)
+    __builtin_abort ();
+  if (CS != frame->cs)
+    __builtin_abort ();
+  if (FLAGS != frame->flags)
+    __builtin_abort ();
+  if (SP != frame->sp)
+    __builtin_abort ();
+  if (SS != frame->ss)
+    __builtin_abort ();
+
+  exit (0);
+}
+
+int
+main ()
+{
+  asm ("push	$" STRING (SS) ";		\
+	push	$" STRING (SP) ";		\
+	push	$" STRING (FLAGS) ";		\
+	push	$" STRING (CS) ";		\
+	push	$" STRING (IP) ";		\
+	jmp	fn");
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/pr68037-3.c b/gcc/testsuite/gcc.dg/torture/pr68037-3.c
new file mode 100644
index 0000000..6bcc317
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr68037-3.c
@@ -0,0 +1,69 @@
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+
+#include <stddef.h>
+
+extern void exit (int);
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+typedef int aligned __attribute__((aligned(64)));
+
+#define IP		0x12345671
+#define CS		0x12345672
+#define FLAGS		0x12345673
+#define SP		0x12345674
+#define SS		0x12345675
+
+#define STRING(x)	XSTRING(x)
+#define XSTRING(x)	#x
+
+struct interrupt_frame
+{
+  uword_t ip;
+  uword_t cs;
+  uword_t flags;
+  uword_t sp;
+  uword_t ss;
+};
+
+int
+check_int (int *i, int align)
+{
+  *i = 20;
+  if ((((ptrdiff_t) i) & (align - 1)) != 0)
+    __builtin_abort ();
+  return *i;
+}
+
+__attribute__((interrupt, used))
+void
+fn (struct interrupt_frame *frame)
+{
+  aligned i;
+  if (check_int (&i, __alignof__(i)) != i)
+    __builtin_abort ();
+
+  if (IP != frame->ip)
+    __builtin_abort ();
+  if (CS != frame->cs)
+    __builtin_abort ();
+  if (FLAGS != frame->flags)
+    __builtin_abort ();
+  if (SP != frame->sp)
+    __builtin_abort ();
+  if (SS != frame->ss)
+    __builtin_abort ();
+
+  exit (0);
+}
+
+int
+main ()
+{
+  asm ("push	$" STRING (SS) ";		\
+	push	$" STRING (SP) ";		\
+	push	$" STRING (FLAGS) ";		\
+	push	$" STRING (CS) ";		\
+	push	$" STRING (IP) ";		\
+	jmp	fn");
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/pr68661-1a.c b/gcc/testsuite/gcc.dg/torture/pr68661-1a.c
new file mode 100644
index 0000000..f42d3bc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr68661-1a.c
@@ -0,0 +1,17 @@
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+/* { dg-additional-sources pr68661-1b.c } */
+
+extern void bar0 (int, int, int, int, int, int, int, int, int)
+   __attribute__ ((no_caller_saved_registers));
+
+void
+foo (void)
+{
+  bar0 (0, 1, 2, 3, 4, 5, 6, 7, 8);
+}
+
+void
+bad (void)
+{
+  __builtin_abort ();
+}
diff --git a/gcc/testsuite/gcc.dg/torture/pr68661-1b.c b/gcc/testsuite/gcc.dg/torture/pr68661-1b.c
new file mode 100644
index 0000000..e891a76
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr68661-1b.c
@@ -0,0 +1,44 @@
+/* { dg-do compile { target i?86-*-* x86_64-*-* } } */
+
+extern void foo (void);
+extern void bad (void);
+
+void
+__attribute__ ((no_caller_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
+main ()
+{
+  foo ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/pr68661-2a.c b/gcc/testsuite/gcc.dg/torture/pr68661-2a.c
new file mode 100644
index 0000000..5eb2361
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr68661-2a.c
@@ -0,0 +1,17 @@
+/* { dg-do run { target i?86-*-* x86_64-*-* } } */
+/* { dg-additional-sources pr68661-2b.c } */
+
+extern void (*bar[]) (int, int, int, int, int, int, int, int, int)
+   __attribute__ ((no_caller_saved_registers));
+
+void
+foo (void)
+{
+  bar[0] (0, 1, 2, 3, 4, 5, 6, 7, 8);
+}
+
+void
+bad (void)
+{
+  __builtin_abort ();
+}
diff --git a/gcc/testsuite/gcc.dg/torture/pr68661-2b.c b/gcc/testsuite/gcc.dg/torture/pr68661-2b.c
new file mode 100644
index 0000000..dd5fb64
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr68661-2b.c
@@ -0,0 +1,52 @@
+/* { dg-do compile { target i?86-*-* x86_64-*-* } } */
+
+extern void foo (void);
+extern void bad (void);
+
+static void
+__attribute__ ((no_caller_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 ();
+}
+
+typedef void (*func_t) (int, int, int, int, int, int, int, int, int)
+   __attribute__ ((no_caller_saved_registers));
+
+func_t bar[] =
+{
+  bar0,
+};
+
+int
+main ()
+{
+  foo ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-1.c b/gcc/testsuite/gcc.target/i386/interrupt-1.c
new file mode 100644
index 0000000..70b8f3f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-1.c
@@ -0,0 +1,53 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mno-push-args -maccumulate-outgoing-args" } */
+
+extern void foo (void *) __attribute__ ((interrupt));
+extern int bar (int);
+
+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-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)si" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { 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)si" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { 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 } } } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-10.c b/gcc/testsuite/gcc.target/i386/interrupt-10.c
new file mode 100644
index 0000000..64e621c
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-10.c
@@ -0,0 +1,24 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mavx512bw -mno-iamcu -maccumulate-outgoing-args" } */
+
+extern void bar (void);
+
+struct interrupt_frame;
+
+void
+ __attribute__ ((interrupt))
+foo (struct interrupt_frame *frame)
+{
+  bar ();
+}
+
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%zmm\[0-9\]+,\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\)" 32 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%zmm\[0-9\]+" 32 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%zmm\[0-9\]+,\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\)" 8 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%zmm\[0-9\]+" 8 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "kmovq\[\\t \]*%k\[0-7\]+,\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\)" 8 } } */
+/* { dg-final { scan-assembler-times "kmovq\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%k\[0-7\]+" 8 } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rbx" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r1\[2-5\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%ebx" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%e(s|d)i" { target ia32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-11.c b/gcc/testsuite/gcc.target/i386/interrupt-11.c
new file mode 100644
index 0000000..ba47019
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-11.c
@@ -0,0 +1,37 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-sse -mno-iamcu -maccumulate-outgoing-args" } */
+
+extern void bar (void);
+
+void
+ __attribute__ ((interrupt))
+foo (void *frame)
+{
+  bar ();
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t .\]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "kmov.\[\\t \]*%k\[0-7\]+,\[\\t \]*\[\\-]?\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "kmov.\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%k\[0-7\]+" } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rbx" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r1\[2-5\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%ebx" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%e(s|d)i" { target ia32 } } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)ax" 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 "pushq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rsi" 1 { target { ! ia32 } } } } */
+/* { 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 "pop(?:l|q)\[\\t \]*%(?:e|r)ax" 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 "popq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%rsi" 1 { target { ! ia32 } } } } */
+/* { 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 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-12.c b/gcc/testsuite/gcc.target/i386/interrupt-12.c
new file mode 100644
index 0000000..5c9eb74
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-12.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern int check_int (int *i, void *, int align);
+typedef int aligned __attribute__((aligned(64)));
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+__attribute__((interrupt))
+void
+foo (void *frame, uword_t error_code)
+{
+  aligned j;
+  if (check_int (frame, &j, __alignof__(j)))
+    __builtin_abort ();
+}
+
+/* { dg-final { scan-assembler-times "and\[lq\]?\[^\\n\]*-64,\[^\\n\]*sp" 1 } } */
+/* { dg-final { scan-assembler-times "add(?:l|q)\[\\t \]*\\\$8,\[\\t \]*%\[re\]?sp" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "addl\[\\t \]*\\\$4,\[\\t \]*%esp" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-13.c b/gcc/testsuite/gcc.target/i386/interrupt-13.c
new file mode 100644
index 0000000..8cf0b25
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-13.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern int check_int (int *i, void *, int align);
+typedef int aligned __attribute__((aligned(64)));
+
+__attribute__((interrupt))
+void
+foo (void *frame)
+{
+  aligned j;
+  if (check_int (frame, &j, __alignof__(j)))
+    __builtin_abort ();
+}
+
+/* { dg-final { scan-assembler-times "and\[lq\]?\[^\\n\]*-64,\[^\\n\]*sp" 1 } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-14.c b/gcc/testsuite/gcc.target/i386/interrupt-14.c
new file mode 100644
index 0000000..751c328
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-14.c
@@ -0,0 +1,39 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-sse -mno-iamcu -mno-sse4 -mno-popcnt -maccumulate-outgoing-args" } */
+
+extern int i, cnt;
+
+void
+ __attribute__ ((interrupt))
+foo (void *frame)
+{
+  cnt = __builtin_popcount (i);
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t .\]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "kmov.\[\\t \]*%k\[0-7\]+,\[\\t \]*\[\\-]?\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "kmov.\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%k\[0-7\]+" } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rbx" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r1\[2-5\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%ebx" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%edi" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%esi" { target ia32 } } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:e|r)ax" 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 "pushq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rsi" 1 { target { ! ia32 } } } } */
+/* { 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 "pop(?:l|q)\[\\t \]*%(?:e|r)ax" 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 "popq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%rsi" 1 { target { ! ia32 } } } } */
+/* { 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 "iret" 1 } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-15.c b/gcc/testsuite/gcc.target/i386/interrupt-15.c
new file mode 100644
index 0000000..16d82c2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-15.c
@@ -0,0 +1,28 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx -mpush-args -maccumulate-outgoing-args" } */
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+extern void bar (int) __attribute__ ((no_caller_saved_registers));
+
+void
+ __attribute__ ((interrupt))
+fn1 (void *frame, uword_t error)
+{
+  bar (error);
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t .\]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)si" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%edi" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%rax" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:r|e)bp" 1 } } */
+/* { dg-final { scan-assembler-times "leave" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%eax" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "movl\[\\t \]*-4\\(%ebp\\),\[\\t \]*%eax" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movq\[\\t \]*-8\\(%(?:r|e)bp\\),\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "addl\[\\t \]*\\\$4,\[\\t \]*%esp" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "add(?:l|q)\[\\t \]*\\\$8,\[\\t \]*%\[re\]?sp" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "iret" 1 } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-16.c b/gcc/testsuite/gcc.target/i386/interrupt-16.c
new file mode 100644
index 0000000..f20f117
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-16.c
@@ -0,0 +1,28 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx -mno-push-args -maccumulate-outgoing-args" } */
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+extern void bar (int) __attribute__ ((no_caller_saved_registers));
+
+void
+ __attribute__ ((interrupt))
+fn1 (void *frame, uword_t error)
+{
+  bar (error);
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t .\]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)si" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%edi" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%rax" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:r|e)bp" 1 } } */
+/* { dg-final { scan-assembler-times "leave" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%eax" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "movl\[\\t \]*-4\\(%ebp\\),\[\\t \]*%eax" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movq\[\\t \]*-8\\(%(?:r|e)bp\\),\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "addl\[\\t \]*\\\$4,\[\\t \]*%esp" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "add(?:l|q)\[\\t \]*\\\$8,\[\\t \]*%\[re\]?sp" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "iret" 1 } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-17.c b/gcc/testsuite/gcc.target/i386/interrupt-17.c
new file mode 100644
index 0000000..4bad7dc
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-17.c
@@ -0,0 +1,30 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx -mpush-args -mno-accumulate-outgoing-args" } */
+
+extern void bar (int) __attribute__ ((no_caller_saved_registers));
+
+void
+ __attribute__ ((interrupt))
+fn1 (void *frame)
+{
+  bar (3);
+}
+
+void
+ __attribute__ ((interrupt))
+fn2 (void *frame)
+{
+  bar (3);
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t .\]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%edi" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)si" } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[8-9\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r1\[0-5\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:r|e)bp" 2 } } */
+/* { dg-final { scan-assembler-times "leave" 2 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rdi" 2 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movq\[\\t \]*-8\\(%(?:r|e)bp\\),\[\\t \]*%rdi" 2 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "iret" 2 } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-18.c b/gcc/testsuite/gcc.target/i386/interrupt-18.c
new file mode 100644
index 0000000..af32851
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-18.c
@@ -0,0 +1,35 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx -mpush-args -maccumulate-outgoing-args" } */
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+extern void bar (int) __attribute__ ((no_caller_saved_registers));
+
+void
+ __attribute__ ((interrupt))
+fn1 (void *frame, uword_t error)
+{
+  bar (error);
+}
+
+void
+ __attribute__ ((interrupt))
+fn2 (void *frame, uword_t error)
+{
+  bar (error);
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t .\]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)si" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%edi" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%rax" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "push(?:l|q)\[\\t \]*%(?:r|e)bp" 2 } } */
+/* { dg-final { scan-assembler-times "leave" 2 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%eax" 2 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "movl\[\\t \]*-4\\(%ebp\\),\[\\t \]*%eax" 2 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rdi" 2 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movq\[\\t \]*-8\\(%(?:r|e)bp\\),\[\\t \]*%rdi" 2 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "addl\[\\t \]*\\\$4,\[\\t \]*%esp" 2 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "add(?:l|q)\[\\t \]*\\\$8,\[\\t \]*%\[re\]?sp" 2 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "iret" 2 } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-19.c b/gcc/testsuite/gcc.target/i386/interrupt-19.c
new file mode 100644
index 0000000..e1aaaec
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-19.c
@@ -0,0 +1,21 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx -mno-push-args -maccumulate-outgoing-args" } */
+
+extern int foo (int) __attribute__ ((no_caller_saved_registers));
+extern int bar (int) __attribute__ ((no_caller_saved_registers));
+
+int
+foo (int i)
+{
+  return bar (i);
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t \]*%(x|y|z)mm\[0-9\]+,\[\\t \]-*\[0-9\]*\\(%\[re\]?bp\\)" } } */
+/* { dg-final { scan-assembler-not "movups\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)si" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)bp" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%edi" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-2.c b/gcc/testsuite/gcc.target/i386/interrupt-2.c
new file mode 100644
index 0000000..743e4b9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-2.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wall -g" } */
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+}
+
+void (*fns[]) (void *) __attribute__((interrupt)) =
+{
+  fn,
+};
+
+/* { dg-final { scan-assembler-not "add(l|q)\[\\t \]*\\$\[0-9\]*,\[\\t \]*%\[re\]?sp" } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-20.c b/gcc/testsuite/gcc.target/i386/interrupt-20.c
new file mode 100644
index 0000000..ade8ee3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-20.c
@@ -0,0 +1,22 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O3 -mno-avx512f -mno-iamcu -mavx -mno-push-args" } */
+
+extern int foo (int) __attribute__ ((no_caller_saved_registers));
+extern int bar (int) __attribute__ ((no_caller_saved_registers));
+
+int
+foo (int i)
+{
+  return bar (i + 1);
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t \]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)si" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)bp" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%edi" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%rdx" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "jmp" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-21.c b/gcc/testsuite/gcc.target/i386/interrupt-21.c
new file mode 100644
index 0000000..7cc5181
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-21.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mno-avx -mno-iamcu -msse" } */
+
+void
+__attribute__((no_caller_saved_registers))
+fn (void)
+{
+  asm ("#"
+       :
+       :
+       : "xmm3");
+}
+
+/* { dg-final { scan-assembler-times "movaps\[\\t \]*%xmm3,\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "movaps\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%xmm3" 1 } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[0-2\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[0-2\]+" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[4-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[4-9\]+" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm1\[0-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm1\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(s|d)i" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)bp" } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-22.c b/gcc/testsuite/gcc.target/i386/interrupt-22.c
new file mode 100644
index 0000000..5dd6290
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-22.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx" } */
+
+void
+__attribute__((no_caller_saved_registers))
+fn (void)
+{
+  asm ("#"
+       :
+       :
+       : "xmm3");
+}
+
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%ymm3,\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%ymm3" 1 } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[0-2\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[0-2\]+" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[4-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[4-9\]+" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm1\[0-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm1\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(s|d)i" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)bp" } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-23.c b/gcc/testsuite/gcc.target/i386/interrupt-23.c
new file mode 100644
index 0000000..e0cf550
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-23.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mavx512f -mno-iamcu" } */
+
+void
+__attribute__((no_caller_saved_registers))
+fn (void)
+{
+  asm ("#"
+       :
+       :
+       : "xmm3");
+}
+
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%zmm3,\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%zmm3" 1 } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[0-2\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[0-2\]+" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[4-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[4-9\]+" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm1\[0-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm1\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(s|d)i" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)bp" } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-24.c b/gcc/testsuite/gcc.target/i386/interrupt-24.c
new file mode 100644
index 0000000..9b92c00
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-24.c
@@ -0,0 +1,21 @@
+/* { dg-do compile { target { ! x32 } } } */
+/* { dg-options "-O2 -mno-iamcu -mmpx" } */
+
+void
+__attribute__((no_caller_saved_registers))
+fn (void)
+{
+  asm ("#"
+       :
+       :
+       : "bnd3");
+}
+
+/* { dg-final { scan-assembler-times "bndmov\[\\t \]*%bnd3,\[\\t \]*\[\\-\]?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "bndmov\[\\t \]*\[\\-\]?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%bnd3" 1 } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[0-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(s|d)i" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)bp" } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-25.c b/gcc/testsuite/gcc.target/i386/interrupt-25.c
new file mode 100644
index 0000000..849fd3a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-25.c
@@ -0,0 +1,24 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -mno-avx -mno-iamcu -msse -mincoming-stack-boundary=2" } */
+
+void
+__attribute__((no_caller_saved_registers))
+fn (void)
+{
+  asm ("#"
+       :
+       :
+       : "xmm3");
+}
+
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%xmm3,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%xmm3" 1 } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[0-2\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[0-2\]+" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm\[4-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm\[4-9\]+" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*%(x|y|z)mm1\[0-9\]+,\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\)" } } */
+/* { dg-final { scan-assembler-not "mov(a|u)ps\[\\t \]*\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%(x|y|z)mm1\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%e(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%e(s|d)i" } } */
+/* { dg-final { scan-assembler-not "(push|pop)l\[\\t \]*%ebp" } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-26.c b/gcc/testsuite/gcc.target/i386/interrupt-26.c
new file mode 100644
index 0000000..27b40b0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-26.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wall -g" } */
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+}
+
+/* { dg-final { scan-assembler-not "add(l|q)\[\\t \]*\\$\[0-9\]*,\[\\t \]*%\[re\]?sp" } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-27.c b/gcc/testsuite/gcc.target/i386/interrupt-27.c
new file mode 100644
index 0000000..774bb4b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-27.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -g -mno-avx -mno-iamcu -msse" } */
+
+typedef float __v4sf __attribute__ ((__vector_size__ (16)));
+__v4sf x, y;
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+  x = y;
+}
+
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%xmm0,\[\\t \]*-?\[0-9\]*\\(%\[re\]?bp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-?\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%xmm0" 1 } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-28.c b/gcc/testsuite/gcc.target/i386/interrupt-28.c
new file mode 100644
index 0000000..0768a46
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-28.c
@@ -0,0 +1,16 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -mno-iamcu" } */
+
+struct ret
+{
+  int i[8];
+};
+
+extern struct ret bar (void);
+
+void
+ __attribute__ ((interrupt))
+fn (void *frame)
+{
+  bar ();
+} /* { dg-message "sorry, unimplemented: Dynamic Realign Argument Pointer" } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-29.c b/gcc/testsuite/gcc.target/i386/interrupt-29.c
new file mode 100644
index 0000000..fa64297
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-29.c
@@ -0,0 +1,27 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -miamcu -maccumulate-outgoing-args" } */
+
+struct interrupt_frame;
+
+void (*callback[1])(unsigned int id, unsigned int len);
+unsigned int remaining;
+
+void
+handler(int uart)
+{
+  while (1) {
+    if (remaining) {
+      callback[uart](0, 0);
+      break;
+    }
+  }
+}
+
+int uart;
+
+void
+__attribute__((interrupt))
+my_isr(struct interrupt_frame *frame)
+{
+  handler(uart);
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-3.c b/gcc/testsuite/gcc.target/i386/interrupt-3.c
new file mode 100644
index 0000000..b09f56c
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g" } */
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+void
+__attribute__((interrupt))
+fn (void* frame, uword_t error)
+{
+}
+
+/* { dg-final { scan-assembler-times "add(?:l|q)\[\\t \]*\\\$8,\[\\t \]*%\[re\]?sp" 1 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "addl\[\\t \]*\\\$4,\[\\t \]*%esp" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-30.c b/gcc/testsuite/gcc.target/i386/interrupt-30.c
new file mode 100644
index 0000000..82c65e8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-30.c
@@ -0,0 +1,28 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -miamcu -maccumulate-outgoing-args" } */
+
+struct interrupt_frame;
+
+void (*callback[1])(unsigned int id, unsigned int len);
+unsigned int remaining;
+
+void
+__attribute__((no_caller_saved_registers))
+handler(int uart)
+{
+  while (1) {
+    if (remaining) {
+      callback[uart](0, 0);
+      break;
+    }
+  }
+}
+
+int uart;
+
+void
+__attribute__((interrupt))
+my_isr(struct interrupt_frame *frame)
+{
+  handler(uart);
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-31.c b/gcc/testsuite/gcc.target/i386/interrupt-31.c
new file mode 100644
index 0000000..ce45895
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-31.c
@@ -0,0 +1,27 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -miamcu -maccumulate-outgoing-args" } */
+
+struct interrupt_frame;
+
+void (*callback) (unsigned int id, unsigned int len)
+  __attribute__((no_caller_saved_registers));
+unsigned int remaining;
+
+void
+__attribute__((no_caller_saved_registers))
+handler(void)
+{
+  while (1) {
+    if (remaining) {
+      callback(0, 0);
+      break;
+    }
+  }
+}
+
+void
+__attribute__((interrupt))
+my_isr(struct interrupt_frame *frame)
+{
+  handler();
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-32.c b/gcc/testsuite/gcc.target/i386/interrupt-32.c
new file mode 100644
index 0000000..db13842
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-32.c
@@ -0,0 +1,42 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -miamcu -maccumulate-outgoing-args" } */
+
+struct interrupt_frame;
+
+extern void callback0 (unsigned int id, unsigned int len)
+  __attribute__((no_caller_saved_registers));
+extern void callback1 (unsigned int id, unsigned int len)
+  __attribute__((no_caller_saved_registers));
+extern void callback2 (unsigned int id, unsigned int len)
+  __attribute__((no_caller_saved_registers));
+
+void (*callback[]) (unsigned int id, unsigned int len)
+  __attribute__((no_caller_saved_registers)) =
+{
+  callback0,
+  callback1,
+  callback2,
+};
+
+unsigned int remaining;
+
+void
+__attribute__((no_caller_saved_registers))
+handler(int uart)
+{
+  while (1) {
+    if (remaining) {
+      callback[uart](0, 0);
+      break;
+    }
+  }
+}
+
+int uart;
+
+void
+__attribute__((interrupt))
+my_isr(struct interrupt_frame *frame)
+{
+  handler(uart);
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-33.c b/gcc/testsuite/gcc.target/i386/interrupt-33.c
new file mode 100644
index 0000000..4cba9c9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-33.c
@@ -0,0 +1,19 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx -mno-push-args" } */
+
+extern void bar (void) __attribute__ ((noreturn));
+
+void
+__attribute__ ((no_caller_saved_registers))
+foo (int i0, int i1, int i2, int i3, int i4, int i5, int i6,
+     int i7, int i8)
+{
+  if (i7)
+    bar ();
+}
+
+/* { dg-final { scan-assembler-not "movups\[\\t \]*%(x|y|z)mm\[0-9\]+" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(a|b|c|d)x" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)(d|s)i" } } */
+/* { dg-final { scan-assembler-not "(push|pop)(l|q)\[\\t \]*%(r|e)bp" } } */
+/* { dg-final { scan-assembler-not "(push|pop)q\[\\t \]*%r\[0-9\]+" { target { ! ia32 } } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-34.c b/gcc/testsuite/gcc.target/i386/interrupt-34.c
new file mode 100644
index 0000000..43f8b43
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-34.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g -mno-avx -mno-iamcu -msse" } */
+
+typedef float xmm_t __attribute__ ((__vector_size__ (16)));
+extern xmm_t x, y;
+
+void
+__attribute__((no_caller_saved_registers))
+fn (void)
+{
+  x = __builtin_ia32_addss (x, y);
+}
+
+/* { dg-final { scan-assembler-times "movaps\[\\t \]*%xmm0,\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "movaps\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%xmm0" 1 } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-35.c b/gcc/testsuite/gcc.target/i386/interrupt-35.c
new file mode 100644
index 0000000..8386623
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-35.c
@@ -0,0 +1,54 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mno-push-args -maccumulate-outgoing-args" } */
+
+extern int bar (int);
+
+__attribute__((no_caller_saved_registers))
+void
+foo (void)
+{
+  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-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)si" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "pushq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { 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)si" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "popq\[\\t \]*%rdi" 1 { target { ! ia32 } } } } */
+/* { 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/interrupt-387-err.c b/gcc/testsuite/gcc.target/i386/interrupt-387-err.c
new file mode 100644
index 0000000..36094f2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-387-err.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -m80387 -mlong-double-80 -mno-iamcu -mno-sse" } */
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+extern long double y, x;
+
+void
+fn0 (void)
+{
+  x = y;
+  y = 0;
+}
+
+void
+__attribute__((interrupt))
+fn1 (void *frame, uword_t error)
+{
+  x = 0; /* { dg-error "80387 instructions aren't allowed in exception service routine" } */
+}
+
+void
+__attribute__((interrupt))
+fn2 (void *frame)
+{
+  x = y; /* { dg-error "80387 instructions aren't allowed in interrupt service routine" } */
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-4.c b/gcc/testsuite/gcc.target/i386/interrupt-4.c
new file mode 100644
index 0000000..c34dbe0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-4.c
@@ -0,0 +1,32 @@
+/* { dg-do link } */
+/* { dg-options "-O -g" } */
+
+#include <stdint.h>
+
+extern void link_error (void);
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+struct interrupt_frame
+{
+  uword_t ip;
+  uword_t cs;
+  uword_t flags;
+  uword_t sp;
+  uword_t ss;
+};
+
+__attribute__ ((used, interrupt))
+void
+foo (struct interrupt_frame *frame)
+{
+  void *ra = __builtin_return_address (0);
+  if ((uintptr_t) ra != (uintptr_t) frame->ip)
+    link_error ();
+}
+
+int
+main (void)
+{
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-5.c b/gcc/testsuite/gcc.target/i386/interrupt-5.c
new file mode 100644
index 0000000..3c4bc2d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-5.c
@@ -0,0 +1,23 @@
+/* { dg-do link } */
+/* { dg-options "-O -g" } */
+
+#include <stdint.h>
+
+extern void link_error (void);
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+__attribute__ ((used, interrupt))
+void
+foo (void *frame, uword_t error)
+{
+  void *ra = __builtin_return_address (0);
+  if ((uintptr_t) ra != (uintptr_t) error)
+    link_error ();
+}
+
+int
+main (void)
+{
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-6.c b/gcc/testsuite/gcc.target/i386/interrupt-6.c
new file mode 100644
index 0000000..a1a3d0e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-6.c
@@ -0,0 +1,40 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+
+extern int error;
+
+__attribute__((interrupt))
+void
+fn1 (void *p, short error_code)
+{ /* { dg-error "interrupt service routine should have unsigned \(long long |long |\)int as the second argument" } */
+}
+
+__attribute__((interrupt))
+void
+fn2 (void)
+{ /* { dg-error "interrupt service routine can only have a pointer argument and an optional integer argument" } */
+}
+
+__attribute__((interrupt))
+void
+fn3 (uword_t error_code)
+{ /* { dg-error "interrupt service routine should have a pointer as the first argument" } */
+  error = error_code;
+}
+
+__attribute__((interrupt))
+void
+fn4 (uword_t error_code, void *frame)
+{ /* { dg-error "interrupt service routine should have .* the .* argument" } */
+  error = error_code;
+}
+
+extern int fn5 (void *) __attribute__ ((interrupt)); /* { dg-error "interrupt service routine can't have non-void return value" } */
+
+int
+fn5 (void *frame)
+{
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-7.c b/gcc/testsuite/gcc.target/i386/interrupt-7.c
new file mode 100644
index 0000000..e7441a2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-7.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern int error;
+
+extern void fn (void *) __attribute__((interrupt));
+
+void
+foo (void)
+{
+  fn (&error); /* { dg-error "interrupt service routine can't be called directly" } */
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-8.c b/gcc/testsuite/gcc.target/i386/interrupt-8.c
new file mode 100644
index 0000000..defec2e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-8.c
@@ -0,0 +1,20 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx -maccumulate-outgoing-args" } */
+
+extern void bar (void);
+
+void
+ __attribute__ ((interrupt))
+foo (void *frame)
+{
+  bar ();
+}
+
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%ymm\[0-9\]+,\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\)" 16 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%ymm\[0-9\]+" 16 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%ymm\[0-9\]+,\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\)" 8 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%ymm\[0-9\]+" 8 { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rbx" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r1\[2-5\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%ebx" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%e(s|d)i" { target ia32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-9.c b/gcc/testsuite/gcc.target/i386/interrupt-9.c
new file mode 100644
index 0000000..56bd8dd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-9.c
@@ -0,0 +1,22 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -mno-avx512bw -mno-iamcu -mavx512f -maccumulate-outgoing-args" } */
+
+extern void bar (void);
+
+void
+ __attribute__ ((interrupt))
+foo (void *frame)
+{
+  bar ();
+}
+
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%zmm\[0-9\]+,\[\\t \]-*\[0-9\]*\\(%\[re\]?bp\\)" 32 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%zmm\[0-9\]+" 32 { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%zmm\[0-9\]+,\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\)" 8 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%zmm\[0-9\]+" 8 { target ia32 } } } */
+/* { dg-final { scan-assembler-times "kmovw\[\\t \]*%k\[0-7\]+,\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\)" 8 } } */
+/* { dg-final { scan-assembler-times "kmovw\[\\t \]*-\[0-9\]*\\(%\[re\]?bp\\),\[\\t \]*%k\[0-7\]+" 8 } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%rbx" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[\\t \]*%r1\[2-5\]+" { target { ! ia32 } } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%ebx" { target ia32 } } } */
+/* { dg-final { scan-assembler-not "pushl\[\\t \]*%e(s|d)i" { target ia32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-bnd.c b/gcc/testsuite/gcc.target/i386/interrupt-bnd.c
new file mode 100644
index 0000000..9f50661
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-bnd.c
@@ -0,0 +1,16 @@
+/* { dg-do compile { target { ! x32 } } } */
+/* { dg-options "-O2 -mno-iamcu -mmpx" } */
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+  asm ("#"
+       :
+       :
+       : "bnd3");
+}
+
+/* { dg-final { scan-assembler-times "bndmov\[\\t \]*%bnd3,\[\\t \]*\[\\-\]?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "bndmov\[\\t \]*\[\\-\]?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%bnd3" 1 } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-iamcu.c b/gcc/testsuite/gcc.target/i386/interrupt-iamcu.c
new file mode 100644
index 0000000..712b85f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-iamcu.c
@@ -0,0 +1,35 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -miamcu -maccumulate-outgoing-args" } */
+
+extern void foo (void *) __attribute__ ((interrupt));
+extern int bar (int);
+
+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-times "pushl\[\\t \]*%eax" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%ebx" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%ecx" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edx" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%edi" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%esi" 1 } } */
+/* { dg-final { scan-assembler-times "pushl\[\\t \]*%ebp" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%eax" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%ecx" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%edx" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%edi" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%esi" 1 } } */
+/* { dg-final { scan-assembler-times "popl\[\\t \]*%ebp" 1 } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-mmx-err.c b/gcc/testsuite/gcc.target/i386/interrupt-mmx-err.c
new file mode 100644
index 0000000..fc4f367
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-mmx-err.c
@@ -0,0 +1,37 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mmmx -mno-iamcu" } */
+
+typedef unsigned int uword_t __attribute__ ((mode (__word__)));
+typedef short __v4hi __attribute__ ((__vector_size__ (8)));
+typedef int __m64 __attribute__ ((__vector_size__ (8), __may_alias__));
+
+extern __m64 y, x;
+
+void
+fn0 (void)
+{
+  x = __extension__ (__m64){ 0 };
+  x = (__m64) __builtin_ia32_packsswb ((__v4hi) x, (__v4hi) y);
+  y = x;
+}
+
+void
+__attribute__((interrupt))
+fn1 (void *frame)
+{
+  x = (__m64) __builtin_ia32_packsswb ((__v4hi) x, (__v4hi) y); /* { dg-error "MMX/3Dnow (instructions|intrinsics) aren't allowed in interrupt service routine" } */
+}
+
+void
+__attribute__((interrupt))
+fn2 (void *frame)
+{
+  x = y; /* { dg-error "MMX/3Dnow instructions aren't allowed in interrupt service routine" } */
+}
+
+void
+__attribute__((interrupt))
+fn3 (void *frame, uword_t error)
+{
+  x = __extension__ (__m64){ 0 }; /* { dg-error "MMX/3Dnow instructions aren't allowed in exception service routine" } */
+}
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-redzone-1.c b/gcc/testsuite/gcc.target/i386/interrupt-redzone-1.c
new file mode 100644
index 0000000..78d3f27
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-redzone-1.c
@@ -0,0 +1,31 @@
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -mred-zone" } */
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+  /* No need to adjust stack if less than 128 bytes are used on stack
+     with a 128-byte red zone.  */
+  long long int i0;
+  long long int i1;
+  long long int i2;
+  long long int i3;
+  long long int i4;
+  long long int i5;
+  long long int i6;
+  long long int i7;
+  long long int i8;
+  long long int i9;
+  long long int i10;
+  long long int i11;
+  long long int i12;
+  long long int i13;
+  asm ("# %0, %1, %2, %3, %4, %5, %6, %7"
+       : "=m" (i0), "=m" (i1), "=m" (i2), "=m" (i3),
+         "=m" (i4), "=m" (i5), "=m" (i6), "=m" (i7),
+         "=m" (i8), "=m" (i9), "=m" (i10), "=m" (i11),
+	 "=m" (i12), "=m" (i13));
+}
+
+/* { dg-final { scan-assembler-not "(sub|add)(l|q)\[\\t \]*\\$\[0-9\]*,\[\\t \]*%\[re\]?sp" } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-redzone-2.c b/gcc/testsuite/gcc.target/i386/interrupt-redzone-2.c
new file mode 100644
index 0000000..1ab7041
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-redzone-2.c
@@ -0,0 +1,32 @@
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -mred-zone" } */
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+  /* Need to adjust stack if more than 128 bytes are used on stack
+     with a 128-byte red zone.  */
+  long long int i0;
+  long long int i1;
+  long long int i2;
+  long long int i3;
+  long long int i4;
+  long long int i5;
+  long long int i6;
+  long long int i7;
+  long long int i8;
+  long long int i9;
+  long long int i10;
+  long long int i11;
+  long long int i12;
+  long long int i13;
+  char c;
+  asm ("# %0, %1, %2, %3, %4, %5, %6, %7"
+       : "=m" (i0), "=m" (i1), "=m" (i2), "=m" (i3),
+         "=m" (i4), "=m" (i5), "=m" (i6), "=m" (i7),
+         "=m" (i8), "=m" (i9), "=m" (i10), "=m" (i11),
+	 "=m" (i12), "=m" (i13), "=m" (c));
+}
+
+/* { dg-final { scan-assembler-times "(?:sub|add)(?:l|q)\[\\t \]*\\\$\[0-9\]*,\[\\t \]*%\[re\]?sp" 2 } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-sibcall-1.c b/gcc/testsuite/gcc.target/i386/interrupt-sibcall-1.c
new file mode 100644
index 0000000..e222acf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-sibcall-1.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3" } */
+
+extern void foo (void *) __attribute__ ((interrupt));
+extern void bar (void);
+
+void foo (void *frame)
+{
+  bar ();
+}
+/* { dg-final { scan-assembler-not "jmp" } }*/
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-sibcall-2.c b/gcc/testsuite/gcc.target/i386/interrupt-sibcall-2.c
new file mode 100644
index 0000000..5d11083
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-sibcall-2.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -mno-sse -mpreferred-stack-boundary=3" { target { ! { ia32 } } } } */
+/* { dg-options "-O3 -mpreferred-stack-boundary=2" { target { ia32 } } } */
+
+extern void foo (void *) __attribute__ ((interrupt));
+extern void bar (void) __attribute__ ((no_caller_saved_registers));
+
+void foo (void *frame)
+{
+  bar ();
+}
+/* { dg-final { scan-assembler-not "jmp" } }*/
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-switch-abi.c b/gcc/testsuite/gcc.target/i386/interrupt-switch-abi.c
new file mode 100644
index 0000000..b1b0abe
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-switch-abi.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern void bar (int);
+
+void f1 (void){  bar (1); }
+__attribute__((interrupt))
+void f2 (void *frame){  bar (2); }
+void f3 (void){  bar (3); }
+__attribute__((interrupt))
+void f4 (void *frame){  bar (4); }
+void f5 (void){  bar (5); }
+
+/* { dg-final { scan-assembler-times "push.\t%.ax" 2 } } */
+/* { dg-final { scan-assembler-times "pop.\t%.ax" 2 } } */
+/* { dg-final { scan-assembler-times "iret" 2 } } */
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-xmm.c b/gcc/testsuite/gcc.target/i386/interrupt-xmm.c
new file mode 100644
index 0000000..6ffdd96
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-xmm.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mno-avx -mno-iamcu -msse" } */
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+  asm ("#"
+       :
+       :
+       : "xmm3");
+}
+
+/* { dg-final { scan-assembler-times "movups\[\\t \]*%xmm3,\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "movups\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%xmm3" 1 } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-ymm.c b/gcc/testsuite/gcc.target/i386/interrupt-ymm.c
new file mode 100644
index 0000000..0754ae0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-ymm.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mno-avx512f -mno-iamcu -mavx" } */
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+  asm ("#"
+       :
+       :
+       : "xmm3");
+}
+
+/* { dg-final { scan-assembler-times "vmovups\[\\t \]*%ymm3,\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "vmovups\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%ymm3" 1 } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/interrupt-zmm.c b/gcc/testsuite/gcc.target/i386/interrupt-zmm.c
new file mode 100644
index 0000000..f4d2fa1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/interrupt-zmm.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mavx512f -mno-iamcu" } */
+
+void
+__attribute__((interrupt))
+fn (void *frame)
+{
+  asm ("#"
+       :
+       :
+       : "xmm3");
+}
+
+/* { dg-final { scan-assembler-times "vmovups\[\\t \]*%zmm3,\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\)" 1 } } */
+/* { dg-final { scan-assembler-times "vmovups\[\\t \]*-?\[0-9\]*\\(%\[re\]?sp\\),\[\\t \]*%zmm3" 1 } } */
+/* { dg-final { scan-assembler "iret" } }*/
diff --git a/gcc/testsuite/gcc.target/i386/pr68637-1.c b/gcc/testsuite/gcc.target/i386/pr68637-1.c
new file mode 100644
index 0000000..b1661fd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr68637-1.c
@@ -0,0 +1,10 @@
+/* { dg-do run { target ia32 } } */
+/* { dg-additional-sources pr68637-2.c } */
+
+extern void (*bar[]) (int, int) __attribute__ ((regparm (2)));
+
+void
+foo (void)
+{
+  bar[0] (1, 2);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr68637-2.c b/gcc/testsuite/gcc.target/i386/pr68637-2.c
new file mode 100644
index 0000000..7d9654d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr68637-2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile { target ia32 } } */
+
+static void 
+__attribute__ ((regparm (2)))
+bar0 (int i, int j)
+{
+  if (i != 1 || j != 2)
+    __builtin_abort ();
+}
+
+typedef void (*func_t) (int, int) __attribute__ ((regparm (2)));
+
+func_t bar[] =
+{
+  bar0,
+};
+
+extern void foo (void);
+
+int
+main ()
+{
+  foo ();
+  return 0;
+}
-- 
2.1.0

