diff --git a/gcc/config/i386/i386-options.cc b/gcc/config/i386/i386-options.cc
index 37cb5a0dcc4..92358f4ac41 100644
--- a/gcc/config/i386/i386-options.cc
+++ b/gcc/config/i386/i386-options.cc
@@ -3244,6 +3244,16 @@ ix86_set_indirect_branch_type (tree fndecl)
     }
 }
 
+unsigned
+ix86_fntype_to_abi_id (const_tree fntype)
+{
+  if (lookup_attribute ("nosseclobber", TYPE_ATTRIBUTES (fntype)))
+    return ABI_LESS_SSE;
+  if (lookup_attribute ("noanysseclobber", TYPE_ATTRIBUTES (fntype)))
+    return ABI_NO_SSE;
+  return ABI_DEFAULT;
+}
+
 /* Establish appropriate back-end context for processing the function
    FNDECL.  The argument might be NULL to indicate processing at top
    level, outside of any function scope.  */
@@ -3311,6 +3321,12 @@ ix86_set_current_function (tree fndecl)
       else
 	TREE_TARGET_GLOBALS (new_tree) = save_target_globals_default_opts ();
     }
+
+  unsigned prev_abi_id = 0;
+  if (ix86_previous_fndecl)
+    prev_abi_id = ix86_fntype_to_abi_id (TREE_TYPE (ix86_previous_fndecl));
+  unsigned this_abi_id = ix86_fntype_to_abi_id (TREE_TYPE (fndecl));
+
   ix86_previous_fndecl = fndecl;
 
   static bool prev_no_caller_saved_registers;
@@ -3327,6 +3343,8 @@ ix86_set_current_function (tree fndecl)
   else if (prev_no_caller_saved_registers
 	   != cfun->machine->no_caller_saved_registers)
     reinit_regs ();
+  else if (prev_abi_id != this_abi_id)
+    reinit_regs ();
 
   if (cfun->machine->func_type != TYPE_NORMAL
       || cfun->machine->no_caller_saved_registers)
@@ -3940,6 +3958,10 @@ const struct attribute_spec ix86_attribute_table[] =
     ix86_handle_fndecl_attribute, NULL },
   { "nodirect_extern_access", 0, 0, true, false, false, false,
     handle_nodirect_extern_access_attribute, NULL },
+  { "nosseclobber",	      0, 0, false, true, true, true,
+    NULL, NULL },
+  { "noanysseclobber",	      0, 0, false, true, true, true,
+    NULL, NULL },
 
   /* End element.  */
   { NULL, 0, 0, false, false, false, false, NULL, NULL }
diff --git a/gcc/config/i386/i386-options.h b/gcc/config/i386/i386-options.h
index 68666067fea..ad39661d852 100644
--- a/gcc/config/i386/i386-options.h
+++ b/gcc/config/i386/i386-options.h
@@ -53,6 +53,7 @@ extern unsigned int ix86_incoming_stack_boundary;
 extern char *ix86_offload_options (void);
 extern void ix86_option_override (void);
 extern void ix86_override_options_after_change (void);
+unsigned ix86_fntype_to_abi_id (const_tree fntype);
 void ix86_set_current_function (tree fndecl);
 bool ix86_function_naked (const_tree fn);
 void ix86_simd_clone_adjust (struct cgraph_node *node);
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index f0d6167e667..01387a3c38b 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -487,6 +487,20 @@ ix86_conditional_register_usage (void)
   
   CLEAR_HARD_REG_SET (reg_class_contents[(int)CLOBBERED_REGS]);
 
+  /* If this function is one of the non-SSE-clobber variants, remove
+     those from the call_used_regs.  */
+  if (cfun && ix86_fntype_to_abi_id (TREE_TYPE (cfun->decl)) != ABI_DEFAULT)
+    {
+      for (i = XMM8_REG; i < XMM16_REG; i++)
+	call_used_regs[i] = 0;
+      if (ix86_fntype_to_abi_id (TREE_TYPE (cfun->decl)) == ABI_NO_SSE)
+	{
+	  /* And from any accessible regs if this is ABI_NO_SSE.  */
+	  for (i = XMM8_REG; i < XMM16_REG; i++)
+	    CLEAR_HARD_REG_BIT (accessible_reg_set, i);
+	}
+    }
+
   for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
     {
       /* Set/reset conditionally defined registers from
@@ -1119,6 +1133,8 @@ ix86_comp_type_attributes (const_tree type1, const_tree type2)
   if (ix86_function_regparm (type1, NULL)
       != ix86_function_regparm (type2, NULL))
     return 0;
+  if (ix86_fntype_to_abi_id (type1) != ix86_fntype_to_abi_id (type2))
+    return 0;
 
   return 1;
 }
@@ -1791,6 +1807,21 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
   cum->warn_sse = true;
   cum->warn_mmx = true;
 
+  if (ix86_fntype_to_abi_id (TREE_TYPE (cfun->decl)) == ABI_NO_SSE
+      && (!fntype
+	  || ix86_fntype_to_abi_id (fntype) != ABI_NO_SSE))
+    {
+      if (fndecl)
+	error ("%qD without attribute noanysseclobber cannot be "
+	       "called from functions with that attribute", fndecl);
+      else if (fntype)
+	error ("%qT without attribute noanysseclobber cannot be "
+	       "called from functions with that attribute", fntype);
+      else
+	error ("functions without attribute noanysseclobber cannot be "
+	       "called from functions with that attribute");
+    }
+
   /* Because type might mismatch in between caller and callee, we need to
      use actual type of function for local calls.
      FIXME: cgraph_analyze can be told to actually record if function uses
@@ -6514,7 +6545,7 @@ ix86_nsaved_sseregs (void)
   int nregs = 0;
   int regno;
 
-  if (!TARGET_64BIT_MS_ABI)
+  if (!TARGET_64BIT_MS_ABI && crtl->abi->id() == ABI_DEFAULT)
     return 0;
   for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
     if (SSE_REGNO_P (regno) && ix86_save_reg (regno, true, true))
@@ -20285,6 +20316,34 @@ ix86_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
   return false;
 }
 
+/* Return the descriptor of an nosseclobber ABI_ID.  */
+
+static const predefined_function_abi &
+i386_less_sse_abi (unsigned abi_id)
+{
+  predefined_function_abi &myabi = function_abis[abi_id];
+  if (!myabi.initialized_p ())
+    {
+      HARD_REG_SET full_reg_clobbers
+	= default_function_abi.full_reg_clobbers ();
+      for (int regno = XMM8_REG; regno < XMM16_REG; regno++)
+	CLEAR_HARD_REG_BIT (full_reg_clobbers, regno);
+      myabi.initialize (abi_id, full_reg_clobbers);
+    }
+  return myabi;
+}
+
+/* Implement TARGET_FNTYPE_ABI.  */
+
+static const predefined_function_abi &
+i386_fntype_abi (const_tree fntype)
+{
+  unsigned abi_id = ix86_fntype_to_abi_id (fntype);
+  if (abi_id != ABI_DEFAULT)
+    return i386_less_sse_abi (abi_id);
+  return default_function_abi;
+}
+
 /* Implement TARGET_INSN_CALLEE_ABI.  */
 
 const predefined_function_abi &
@@ -20341,6 +20400,9 @@ ix86_hard_regno_call_part_clobbered (unsigned int abi_id, unsigned int regno,
 	      && ((TARGET_64BIT && REX_SSE_REGNO_P (regno))
 		  || LEGACY_SSE_REGNO_P (regno)));
 
+  if (abi_id == ABI_NO_SSE)
+    return false;
+
   return SSE_REGNO_P (regno) && GET_MODE_SIZE (mode) > 16;
 }
 
@@ -25594,6 +25656,9 @@ ix86_libgcc_floating_mode_supported_p
 #define TARGET_HARD_REGNO_CALL_PART_CLOBBERED \
   ix86_hard_regno_call_part_clobbered
 
+#undef TARGET_FNTYPE_ABI
+#define TARGET_FNTYPE_ABI i386_fntype_abi
+
 #undef TARGET_INSN_CALLEE_ABI
 #define TARGET_INSN_CALLEE_ABI ix86_insn_callee_abi
 
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index 844deeae6cb..44d32ec2e4f 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -471,7 +471,9 @@
 (define_constants
   [(ABI_DEFAULT		0)
    (ABI_VZEROUPPER	1)
-   (ABI_UNKNOWN		2)])
+   (ABI_LESS_SSE    2)
+   (ABI_NO_SSE      3)
+   (ABI_UNKNOWN		4)])
 
 ;; Insns whose names begin with "x86_" are emitted by gen_FOO calls
 ;; from i386.cc.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index d88fd75e06e..3adbbc75b1c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -6680,6 +6680,41 @@ Exception handlers should only be used for exceptions that push an error
 code; you should use an interrupt handler in other cases.  The system
 will crash if the wrong kind of handler is used.
 
+@cindex @code{nosseclobber} function attribute, x86
+@cindex @code{notanysseclobber} function attribute, x86
+@item nosseclobber
+@itemx notanysseclobber
+
+On 32-bit and 64-bit x86 targets, you can use these attributes to indicate that
+a so-marked function doesn't clobber a subset of the SSE2 and AVX registers.
+The @code{nosseclobber} attribute specifies that registers @code{%xmm8} through
+@code{%xmm15} are not clobbered by a function.  This includes the low 16 bytes
+of the corresponding AVX2 and AVX512 registers.  You can't make assumptions
+about the higher parts of these registers, or other registers: those are
+assumed to be clobbered (or not) according to the base ABI.
+
+The @code{notanysseclobber} attribute specifies that the function doesn't
+clobber @emph{any} parts of the SSE2/AVX2/AVX512 registers @code{%zmm8}
+through @code{%zmm15}, not even the high parts.
+
+Functions marked with @code{nosseclobber} can be defined
+without restrictions: they can contain arbitrary floating point or vector
+code, and they can call functions not marked with this attribute (i.e. those
+that must be assumed to clobber parts of these register).
+GCC will insert register saves and restores in the pro- and epilogue in
+those cases (only the low 16 bytes of the used registers will be
+saved/restored, like the attribute implies).
+
+In comparison functions defined with @code{notanysseclobber} are severely
+restricted: they can't call functions not marked with that attribute.
+They also can't write to any of the @code{%xmm8} through @code{%xmm15}
+registers (or their extended variants with other ISAs).  GCC does not
+emit any saves or restores for them.
+
+Calls to such functions (other than above) are unrestricted.  The effect
+is simply that some values can be kept in registers over calls to
+such marked functions.
+
 @cindex @code{target} function attribute
 @item target (@var{options})
 As discussed in @ref{Common Function Attributes}, this attribute 
diff --git a/gcc/function-abi.cc b/gcc/function-abi.cc
index 2ab9b2c5649..efbe114218c 100644
--- a/gcc/function-abi.cc
+++ b/gcc/function-abi.cc
@@ -42,6 +42,26 @@ void
 predefined_function_abi::initialize (unsigned int id,
 				     const_hard_reg_set full_reg_clobbers)
 {
+  /* Don't reinitialize an ABI struct.  We might be called from reinit_regs
+     from the targets conditional_register_usage hook which might depend
+     on cfun and might have changed the global register sets according
+     to that functions ABI already.  That's not the default ABI anymore.
+
+     XXX only avoid this if we're reinitializing the default ABI, and the
+     current function is _not_ of the default ABI.  That's for
+     backward compatibility where some backends modify the regsets with
+     the exception that those changes are then reflected also in the default
+     ABI (which rather is then the "current" ABI).  E.g. x86_64 with the
+     ms_abi vs sysv attribute.  They aren't reflected by separate ABI
+     structs, but handled different.  The "default" ABI hence changes
+     back and forth (and is expected to!) between a ms_abi and a sysv
+     function.  */
+  if (m_initialized
+      && id == 0
+      && cfun
+      && fndecl_abi (cfun->decl).base_abi ().id() != 0)
+    return;
+
   m_id = id;
   m_initialized = true;
   m_full_reg_clobbers = full_reg_clobbers;
@@ -224,6 +244,13 @@ insn_callee_abi (const rtx_insn *insn)
     if (tree fndecl = get_call_fndecl (insn))
       return fndecl_abi (fndecl);
 
+  if (rtx call = get_call_rtx_from (insn))
+    {
+      tree memexp = MEM_EXPR (XEXP (call, 0));
+      if (memexp)
+	return fntype_abi (TREE_TYPE (memexp));
+    }
+
   if (targetm.calls.insn_callee_abi)
     return targetm.calls.insn_callee_abi (insn);
 
diff --git a/gcc/testsuite/gcc.target/i386/sseclobber-1.c b/gcc/testsuite/gcc.target/i386/sseclobber-1.c
new file mode 100644
index 00000000000..8758e2d3109
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/sseclobber-1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target sse2 } */
+/* { dg-options "-O1" } */
+/* { dg-final { scan-assembler-times {mm[89], [0-9]*\(%rsp\)} 2 } } */
+/* { dg-final { scan-assembler-times {mm1[0-5], [0-9]*\(%rsp\)} 6 } } */
+
+extern int nonsse (int) __attribute__((nosseclobber));
+extern int normalfunc (int);
+
+/* Demonstrate that all regs potentially clobbered by normal psABI
+   functions are saved/restored by otherabi functions.  */
+__attribute__((nosseclobber)) int nonsse (int i)
+{
+  return normalfunc (i + 2) + 3;
+}
diff --git a/gcc/testsuite/gcc.target/i386/sseclobber-2.c b/gcc/testsuite/gcc.target/i386/sseclobber-2.c
new file mode 100644
index 00000000000..9abafa0a9ba
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/sseclobber-2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target sse2 } */
+/* { dg-options "-O1" } */
+/* { dg-final { scan-assembler-not {mm[0-9], [0-9]*\(%rsp\)} } } */
+
+extern int nonsse (int) __attribute__((nosseclobber));
+extern int othernonsse (int) __attribute__((nosseclobber));
+
+/* Demonstrate that calling a nosseclobber function from a nosseclobber
+   function does _not_ need to save all the regs (unlike in nonsse).  */
+__attribute__((nosseclobber)) int nonsse (int i)
+{
+  return othernonsse (i + 2) + 3;
+}
diff --git a/gcc/testsuite/gcc.target/i386/sseclobber-3.c b/gcc/testsuite/gcc.target/i386/sseclobber-3.c
new file mode 100644
index 00000000000..276c7fd926b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/sseclobber-3.c
@@ -0,0 +1,54 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target sse2 } */
+/* { dg-options "-O1" } */
+/* for docalc2 we should use the high xmm regs */
+/* { dg-final { scan-assembler {xmm[89]} } } */
+/* do docalc4_notany we should use the high ymm regs */
+/* { dg-final { scan-assembler {ymm[89]} } } */
+/* for docalc4 (and nowhere else) we should save/restore exactly
+   one reg to stack around the inner-loop call */
+/* { dg-final { scan-assembler-times {ymm[0-9]*, [0-9]*\(%rsp\)} 1 } } */
+
+typedef double dbl2 __attribute__((vector_size(16)));
+typedef double dbl4 __attribute__((vector_size(32)));
+typedef double dbl8 __attribute__((vector_size(64)));
+extern __attribute__((nosseclobber,const)) double nonsse (int);
+
+/* Demonstrate that some values can be kept in a register over calls
+   to otherabi functions.  nonsse saves the XMM register, so those
+   are usable, hence docalc2 should be able to keep values in registers
+   over the nonsse call.  */
+void docalc2 (dbl2 *d, dbl2 *a, dbl2 *b, int n)
+{
+  long i;
+  for (i = 0; i < n; i++)
+    {
+      d[i] = a[i] * b[i] * nonsse(i);
+    }
+}
+
+/* Here we're using YMM registers (four doubles) and those are _not_
+   saved by nonsse() (only the XMM parts) so docalc4 should not keep
+   the value in a register over the call to nonsse.  */
+void __attribute__((target("avx2"))) docalc4 (dbl4 *d, dbl4 *a, dbl4 *b, int n)
+{
+  long i;
+  for (i = 0; i < n; i++)
+    {
+      d[i] = a[i] * b[i] * nonsse(i);
+    }
+}
+
+/* And here we're also using YMM registers, but have a call to a
+   noanysseclobber function, which _does_ save all [XYZ]MM regs except
+   arguments, so docalc4_notany should again be able to keep the value
+   in a register.  */
+extern __attribute__((noanysseclobber,const)) double notanysse (int);
+void __attribute__((target("avx2"))) docalc4_notany (dbl4 *d, dbl4 *a, dbl4 *b, int n)
+{
+  long i;
+  for (i = 0; i < n; i++)
+    {
+      d[i] = a[i] * b[i] * notanysse(i);
+    }
+}
diff --git a/gcc/testsuite/gcc.target/i386/sseclobber-4.c b/gcc/testsuite/gcc.target/i386/sseclobber-4.c
new file mode 100644
index 00000000000..734f25068f0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/sseclobber-4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target sse2 } */
+/* { dg-options "-O1" } */
+/* { dg-final { scan-assembler-not {mm[0-9], [0-9]*\(%rsp\)} } } */
+
+extern __attribute__((nosseclobber)) int (*nonsse_ptr) (int);
+
+/* Demonstrate that some values can be kept in a register over calls
+   to otherabi functions when called via function pointer.  */
+double docalc (double d)
+{
+  double ret = d;
+  int i = 0;
+  while (1) {
+      int j = nonsse_ptr (i++);
+      if (!j)
+        break;
+      ret += j;
+  }
+  return ret;
+}
diff --git a/gcc/testsuite/gcc.target/i386/sseclobber-5.c b/gcc/testsuite/gcc.target/i386/sseclobber-5.c
new file mode 100644
index 00000000000..1869ae06148
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/sseclobber-5.c
@@ -0,0 +1,37 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target sse2 } */
+/* { dg-options "-O1" } */
+/* { dg-final { scan-assembler-not {mm[89]} } } */
+/* { dg-final { scan-assembler-not {mm1[0-5]} } } */
+
+extern int noanysse (int) __attribute__((noanysseclobber));
+extern int noanysse2 (int) __attribute__((noanysseclobber));
+extern __attribute__((noanysseclobber)) double calcstuff (double, double);
+
+/* Demonstrate that none of the clobbered SSE (or wider) regs are
+   used by a noanysse function.  */
+__attribute__((noanysseclobber)) double calcstuff (double d, double e)
+{
+  double s1, s2, s3, s4, s5, s6, s7, s8;
+  s1 = s2 = s3 = s4 = s5 = s6 = s7 = s8 = 0.0;
+  while (d > 0.1)
+    {
+      s1 += s2 * 2 + d;
+      s2 += s3 * 3 + e;
+      s3 += s4 * 5 + d * e;
+      s4 += e / d;
+      s5 += s2 * 7 + d - e;
+      s5 += 2 * d + e;
+      s6 += 5 * e + d;
+      s7 += 7 * e * (d+1);
+      d -= e;
+    }
+  return s1 + s2 + s3 + s4 + s5 + s6 + s7;
+}
+
+/* Demonstrate that we can call noanysse functions from noannysse
+   functions.  */
+__attribute__((noanysseclobber)) int noanysse2 (int i)
+{
+  return noanysse (i + 2) + 3;
+}
diff --git a/gcc/testsuite/gcc.target/i386/sseclobber-6.c b/gcc/testsuite/gcc.target/i386/sseclobber-6.c
new file mode 100644
index 00000000000..89ece11c9f2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/sseclobber-6.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target sse2 } */
+/* { dg-options "-O1" } */
+
+/* Various ways of invalid usage of the nosse attributes.  */
+extern __attribute__((nosseclobber)) int nonfndecl; /* { dg-warning "only applies to function types" } */
+
+extern int normalfunc (int);
+__attribute__((nosseclobber)) int (*nonsse_ptr) (int) = normalfunc; /* { dg-warning "from incompatible pointer type" } */
+
+extern int noanysse (int) __attribute__((noanysseclobber));
+/* Demonstrate that it's not allowed to call any functions that
+   aren't noanysse from noanysse functions.  */
+__attribute__((noanysseclobber)) int noanysse (int i)
+{
+  return normalfunc (i + 2) + 3; /* { dg-error "cannot be called from function" } */
+}
