diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 58d7d016197..2b45d62ce21 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2695,7 +2695,8 @@ struct GTY(()) lang_decl_fn {
   unsigned omp_declare_reduction_p : 1;
   unsigned has_dependent_explicit_spec_p : 1;
   unsigned immediate_fn_p : 1;
-  unsigned spare : 11;
+  unsigned maybe_deleted : 1;
+  unsigned spare : 10;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3137,6 +3138,11 @@ struct GTY(()) lang_decl {
 #define DECL_HAS_DEPENDENT_EXPLICIT_SPEC_P(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->has_dependent_explicit_spec_p)
 
+/* Nonzero for a defaulted FUNCTION_DECL for which we haven't decided yet if
+   it's deleted.  */
+#define DECL_MAYBE_DELETED(NODE) \
+  (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -5203,6 +5209,7 @@ enum special_function_kind {
 			      destroyed.  */
   sfk_conversion,	   /* A conversion operator.  */
   sfk_deduction_guide,	   /* A class template deduction guide.  */
+  sfk_comparison,	   /* A comparison operator (e.g. ==, <, <=>).  */
   sfk_virtual_destructor   /* Used by member synthesis fns.  */
 };
 
@@ -5565,6 +5572,17 @@ enum overload_flags { NO_SPECIAL = 0, DTOR_FLAG, TYPENAME_FLAG };
 #define LOOKUP_ALLOW_FLEXARRAY_INIT (LOOKUP_DELEGATING_CONS << 1)
 /* Require constant initialization of a non-constant variable.  */
 #define LOOKUP_CONSTINIT (LOOKUP_ALLOW_FLEXARRAY_INIT << 1)
+/* We're looking for either a rewritten comparison operator candidate or the
+   operator to use on the former's result.  We distinguish between the two by
+   knowing that comparisons other than == and <=> must be the latter, as must
+   a <=> expression trying to rewrite to <=> without reversing.  */
+#define LOOKUP_REWRITTEN (LOOKUP_CONSTINIT << 1)
+/* Reverse the order of the two arguments for comparison rewriting.  First we
+   swap the arguments in add_operator_candidates, then we swap the conversions
+   in add_candidate (so that they correspond to the original order of the
+   args), then we swap the conversions back in build_new_op_1 (so they
+   correspond to the order of the args in the candidate).  */
+#define LOOKUP_REVERSED (LOOKUP_REWRITTEN << 1)
 
 #define LOOKUP_NAMESPACES_ONLY(F)  \
   (((F) & LOOKUP_PREFER_NAMESPACES) && !((F) & LOOKUP_PREFER_TYPES))
@@ -6371,6 +6389,8 @@ extern bool type_has_virtual_destructor		(tree);
 extern bool classtype_has_move_assign_or_move_ctor_p (tree, bool user_declared);
 extern bool classtype_has_non_deleted_move_ctor (tree);
 extern tree classtype_has_depr_implicit_copy	(tree);
+extern bool classtype_has_op (tree, tree_code);
+extern tree classtype_has_defaulted_op (tree, tree_code);
 extern bool type_build_ctor_call		(tree);
 extern bool type_build_dtor_call		(tree);
 extern void explain_non_literal_class		(tree);
@@ -7544,6 +7564,8 @@ extern tree composite_pointer_type		(const op_location_t &,
 extern tree merge_types				(tree, tree);
 extern tree strip_array_domain			(tree);
 extern tree check_return_expr			(tree, bool *);
+extern tree spaceship_type			(tree, tsubst_flags_t = tf_warning_or_error);
+extern tree genericize_spaceship		(tree, tree, tree);
 extern tree cp_build_binary_op                  (const op_location_t &,
 						 enum tree_code, tree, tree,
 						 tsubst_flags_t);
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index c655d3ffc90..ed108f17bfa 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -78,6 +78,7 @@ struct _cpp_file;
   OP(NOT_EQ,		"!=")						\
   OP(GREATER_EQ,	">=")						\
   OP(LESS_EQ,		"<=")						\
+  OP(SPACESHIP,		"<=>")						\
 									\
   /* These two are unary + / - in preprocessor expressions.  */		\
   OP(PLUS_EQ,		"+=")	/* math */				\
diff --git a/gcc/c-family/c-cppbuiltin.c b/gcc/c-family/c-cppbuiltin.c
index f9cd76b2c86..cf3d437fc37 100644
--- a/gcc/c-family/c-cppbuiltin.c
+++ b/gcc/c-family/c-cppbuiltin.c
@@ -990,6 +990,7 @@ c_cpp_builtins (cpp_reader *pfile)
 	  cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806L");
 	  cpp_define (pfile, "__cpp_impl_destroying_delete=201806L");
 	  cpp_define (pfile, "__cpp_constexpr_dynamic_alloc=201907L");
+	  cpp_define (pfile, "__cpp_impl_three_way_comparison=201907L");
 	}
       if (flag_concepts)
         {
diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index 390a4c581e2..0034c1cee0d 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -514,6 +514,9 @@ struct z_candidate {
 
   /* The flags active in add_candidate.  */
   int flags;
+
+  bool rewritten () { return (flags & LOOKUP_REWRITTEN); }
+  bool reversed () { return (flags & LOOKUP_REVERSED); }
 };
 
 /* Returns true iff T is a null pointer constant in the sense of
@@ -2106,6 +2109,11 @@ add_candidate (struct z_candidate **candidates,
   cand->flags = flags;
   *candidates = cand;
 
+  if (convs && cand->reversed ())
+    /* Swap the conversions for comparison in joust; we'll swap them back
+       before build_over_call.  */
+    std::swap (convs[0], convs[1]);
+
   return cand;
 }
 
@@ -2737,6 +2745,16 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
      where  LR  is  the  result of the usual arithmetic conversions between
      types L and R.
 
+     For every integral type T there exists a candidate operator function of
+     the form
+
+       std::strong_ordering operator<=>(T, T);
+
+     For every pair of floating-point types L and R, there exists a candidate
+     operator function of the form
+
+       std::partial_ordering operator<=>(L, R);
+
    14For every pair of types T and I, where T  is  a  cv-qualified  or  cv-
      unqualified  complete  object  type and I is a promoted integral type,
      there exist candidate operator functions of the form
@@ -2758,11 +2776,15 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
 	     bool    operator>=(T, T);
 	     bool    operator==(T, T);
 	     bool    operator!=(T, T);
+	     R       operator<=>(T, T);
+
+     where R is the result type specified in [expr.spaceship].
 
    17For every pointer to member type T,  there  exist  candidate  operator
      functions of the form
 	     bool    operator==(T, T);
-	     bool    operator!=(T, T);  */
+	     bool    operator!=(T, T);
+	     std::strong_equality operator<=>(T, T);  */
 
     case MINUS_EXPR:
       if (TYPE_PTROB_P (type1) && TYPE_PTROB_P (type2))
@@ -2780,6 +2802,11 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
 	break;
       return;
 
+      /* This isn't exactly what's specified above for operator<=>, but it's
+	 close enough.  In particular, we don't care about the return type
+	 specified above; it doesn't participate in overload resolution and it
+	 doesn't affect the semantics of the built-in operator.  */
+    case SPACESHIP_EXPR:
     case EQ_EXPR:
     case NE_EXPR:
       if ((TYPE_PTRMEMFUNC_P (type1) && TYPE_PTRMEMFUNC_P (type2))
@@ -3138,6 +3165,7 @@ add_builtin_candidates (struct z_candidate **candidates, enum tree_code code,
     case LE_EXPR:
     case GT_EXPR:
     case GE_EXPR:
+    case SPACESHIP_EXPR:
       enum_p = 1;
       /* Fall through.  */
 
@@ -5740,6 +5768,15 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
 	  fn_args = non_static_args;
 	}
 
+      /* Don't bother reversing an operator with two identical parameters.  */
+      else if (args->length () == 2 && (flags & LOOKUP_REVERSED))
+	{
+	  tree parmlist = TYPE_ARG_TYPES (TREE_TYPE (fn));
+	  if (same_type_p (TREE_VALUE (parmlist),
+			   TREE_VALUE (TREE_CHAIN (parmlist))))
+	    continue;
+	}
+
       if (TREE_CODE (fn) == TEMPLATE_DECL)
 	add_template_candidate (candidates,
 				fn,
@@ -5800,6 +5837,178 @@ op_is_ordered (tree_code code)
     }
 }
 
+/* Subroutine of build_new_op_1: Add to CANDIDATES all candidates for the
+   operator indicated by CODE/CODE2.  This function calls itself recursively to
+   handle C++20 rewritten comparison operator candidates.  */
+
+static tree
+add_operator_candidates (z_candidate **candidates,
+			 tree_code code, tree_code code2,
+			 vec<tree, va_gc> *arglist,
+			 int flags, tsubst_flags_t complain)
+{
+  z_candidate *start_candidates = *candidates;
+  bool ismodop = code2 != ERROR_MARK;
+  tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code);
+
+  /* LOOKUP_REWRITTEN is set when we're looking for the == or <=> operator to
+     rewrite from, and also when we're looking for the e.g. < operator to use
+     on the result of <=>.  In the latter case, we don't want the flag set in
+     the candidate, we just want to suppress looking for rewrites.  */
+  bool rewritten = (flags & LOOKUP_REWRITTEN);
+  if (rewritten && code != EQ_EXPR && code != SPACESHIP_EXPR)
+    flags &= ~LOOKUP_REWRITTEN;
+
+  bool memonly = false;
+  switch (code)
+    {
+      /* =, ->, [], () must be non-static member functions.  */
+    case MODIFY_EXPR:
+      if (code2 != NOP_EXPR)
+	break;
+      /* FALLTHRU */
+    case COMPONENT_REF:
+    case ARRAY_REF:
+      memonly = true;
+      break;
+
+    default:
+      break;
+    }
+
+  /* Add namespace-scope operators to the list of functions to
+     consider.  */
+  if (!memonly)
+    {
+      tree fns = lookup_name_real (fnname, 0, 1, /*block_p=*/true, 0, 0);
+      fns = lookup_arg_dependent (fnname, fns, arglist);
+      add_candidates (fns, NULL_TREE, arglist, NULL_TREE,
+		      NULL_TREE, false, NULL_TREE, NULL_TREE,
+		      flags, candidates, complain);
+    }
+
+  /* Add class-member operators to the candidate set.  */
+  tree arg1_type = TREE_TYPE ((*arglist)[0]);
+  unsigned nargs = arglist->length () > 1 ? 2 : 1;
+  tree arg2_type = nargs > 1 ? TREE_TYPE ((*arglist)[1]) : NULL_TREE;
+  if (CLASS_TYPE_P (arg1_type))
+    {
+      tree fns = lookup_fnfields (arg1_type, fnname, 1);
+      if (fns == error_mark_node)
+	return error_mark_node;
+      if (fns)
+	add_candidates (BASELINK_FUNCTIONS (fns),
+			NULL_TREE, arglist, NULL_TREE,
+			NULL_TREE, false,
+			BASELINK_BINFO (fns),
+			BASELINK_ACCESS_BINFO (fns),
+			flags, candidates, complain);
+    }
+  /* Per [over.match.oper]3.2, if no operand has a class type, then
+     only non-member functions that have type T1 or reference to
+     cv-qualified-opt T1 for the first argument, if the first argument
+     has an enumeration type, or T2 or reference to cv-qualified-opt
+     T2 for the second argument, if the second argument has an
+     enumeration type.  Filter out those that don't match.  */
+  else if (! arg2_type || ! CLASS_TYPE_P (arg2_type))
+    {
+      struct z_candidate **candp, **next;
+
+      for (candp = candidates; *candp != start_candidates; candp = next)
+	{
+	  unsigned i;
+	  z_candidate *cand = *candp;
+	  next = &cand->next;
+
+	  tree parmlist = TYPE_ARG_TYPES (TREE_TYPE (cand->fn));
+
+	  for (i = 0; i < nargs; ++i)
+	    {
+	      tree parmtype = TREE_VALUE (parmlist);
+	      tree argtype = unlowered_expr_type ((*arglist)[i]);
+
+	      if (TYPE_REF_P (parmtype))
+		parmtype = TREE_TYPE (parmtype);
+	      if (TREE_CODE (argtype) == ENUMERAL_TYPE
+		  && (same_type_ignoring_top_level_qualifiers_p
+		      (argtype, parmtype)))
+		break;
+
+	      parmlist = TREE_CHAIN (parmlist);
+	    }
+
+	  /* No argument has an appropriate type, so remove this
+	     candidate function from the list.  */
+	  if (i == nargs)
+	    {
+	      *candp = cand->next;
+	      next = candp;
+	    }
+	}
+    }
+
+  if (!rewritten)
+    {
+      /* The standard says to rewrite built-in candidates, too,
+	 but there's no point.  */
+      add_builtin_candidates (candidates, code, code2, fnname, arglist,
+			      flags, complain);
+
+      /* Maybe add C++20 rewritten comparison candidates.  */
+      tree_code rewrite_code = ERROR_MARK;
+      if (cxx_dialect >= cxx2a
+	  && nargs == 2
+	  && (OVERLOAD_TYPE_P (arg1_type) || OVERLOAD_TYPE_P (arg2_type)))
+	switch (code)
+	  {
+	  case LT_EXPR:
+	  case LE_EXPR:
+	  case GT_EXPR:
+	  case GE_EXPR:
+	  case SPACESHIP_EXPR:
+	    rewrite_code = SPACESHIP_EXPR;
+	    break;
+
+	  case NE_EXPR:
+	  case EQ_EXPR:
+	    rewrite_code = EQ_EXPR;
+	    break;
+
+	  default:;
+	  }
+
+      if (rewrite_code)
+	{
+	  flags |= LOOKUP_REWRITTEN;
+	  if (rewrite_code != code)
+	    /* Add rewritten candidates in same order.  */
+	    add_operator_candidates (candidates, rewrite_code, ERROR_MARK,
+				     arglist, flags, complain);
+
+	  z_candidate *save_cand = *candidates;
+
+	  /* Add rewritten candidates in reverse order.  */
+	  flags |= LOOKUP_REVERSED;
+	  vec<tree,va_gc> *revlist = make_tree_vector ();
+	  revlist->quick_push ((*arglist)[1]);
+	  revlist->quick_push ((*arglist)[0]);
+	  add_operator_candidates (candidates, rewrite_code, ERROR_MARK,
+				   revlist, flags, complain);
+
+	  /* Release the vec if we didn't add a candidate that uses it.  */
+	  for (z_candidate *c = *candidates; c != save_cand; c = c->next)
+	    if (c->args == revlist)
+	      {
+		revlist = NULL;
+		break;
+	      }
+	  release_tree_vector (revlist);
+	}
+    }
+
+  return NULL_TREE;
+}
+
 static tree
 build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
 		tree arg1, tree arg2, tree arg3, tree *overload,
@@ -5809,7 +6018,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
   vec<tree, va_gc> *arglist;
   tree result = NULL_TREE;
   bool result_valid_p = false;
-  enum tree_code code2 = NOP_EXPR;
+  enum tree_code code2 = ERROR_MARK;
   enum tree_code code_orig_arg1 = ERROR_MARK;
   enum tree_code code_orig_arg2 = ERROR_MARK;
   conversion *conv;
@@ -5828,14 +6037,12 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
       code2 = TREE_CODE (arg3);
       arg3 = NULL_TREE;
     }
-  tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code);
 
   tree arg1_type = unlowered_expr_type (arg1);
   tree arg2_type = arg2 ? unlowered_expr_type (arg2) : NULL_TREE;
 
   arg1 = prep_operand (arg1);
 
-  bool memonly = false;
   switch (code)
     {
     case NEW_EXPR:
@@ -5868,16 +6075,6 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
       code_orig_arg2 = TREE_CODE (arg2_type);
       break;
 
-      /* =, ->, [], () must be non-static member functions.  */
-    case MODIFY_EXPR:
-      if (code2 != NOP_EXPR)
-	break;
-      /* FALLTHRU */
-    case COMPONENT_REF:
-    case ARRAY_REF:
-      memonly = true;
-      break;
-
     default:
       break;
     }
@@ -5908,82 +6105,10 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
   /* Get the high-water mark for the CONVERSION_OBSTACK.  */
   p = conversion_obstack_alloc (0);
 
-  /* Add namespace-scope operators to the list of functions to
-     consider.  */
-  if (!memonly)
-    {
-      tree fns = lookup_name_real (fnname, 0, 1, /*block_p=*/true, 0, 0);
-      fns = lookup_arg_dependent (fnname, fns, arglist);
-      add_candidates (fns, NULL_TREE, arglist, NULL_TREE,
-		      NULL_TREE, false, NULL_TREE, NULL_TREE,
-		      flags, &candidates, complain);
-    }
-
-  /* Add class-member operators to the candidate set.  */
-  if (CLASS_TYPE_P (arg1_type))
-    {
-      tree fns;
-
-      fns = lookup_fnfields (arg1_type, fnname, 1);
-      if (fns == error_mark_node)
-	{
-	  result = error_mark_node;
-	  goto user_defined_result_ready;
-	}
-      if (fns)
-	add_candidates (BASELINK_FUNCTIONS (fns),
-			NULL_TREE, arglist, NULL_TREE,
-			NULL_TREE, false,
-			BASELINK_BINFO (fns),
-			BASELINK_ACCESS_BINFO (fns),
-			flags, &candidates, complain);
-    }
-  /* Per [over.match.oper]3.2, if no operand has a class type, then
-     only non-member functions that have type T1 or reference to
-     cv-qualified-opt T1 for the first argument, if the first argument
-     has an enumeration type, or T2 or reference to cv-qualified-opt
-     T2 for the second argument, if the second argument has an
-     enumeration type.  Filter out those that don't match.  */
-  else if (! arg2 || ! CLASS_TYPE_P (arg2_type))
-    {
-      struct z_candidate **candp, **next;
-
-      for (candp = &candidates; *candp; candp = next)
-	{
-	  tree parmlist, parmtype;
-	  int i, nargs = (arg2 ? 2 : 1);
-
-	  cand = *candp;
-	  next = &cand->next;
-
-	  parmlist = TYPE_ARG_TYPES (TREE_TYPE (cand->fn));
-
-	  for (i = 0; i < nargs; ++i)
-	    {
-	      parmtype = TREE_VALUE (parmlist);
-
-	      if (TYPE_REF_P (parmtype))
-		parmtype = TREE_TYPE (parmtype);
-	      if (TREE_CODE (unlowered_expr_type ((*arglist)[i])) == ENUMERAL_TYPE
-		  && (same_type_ignoring_top_level_qualifiers_p
-		      (unlowered_expr_type ((*arglist)[i]), parmtype)))
-		break;
-
-	      parmlist = TREE_CHAIN (parmlist);
-	    }
-
-	  /* No argument has an appropriate type, so remove this
-	     candidate function from the list.  */
-	  if (i == nargs)
-	    {
-	      *candp = cand->next;
-	      next = candp;
-	    }
-	}
-    }
-
-  add_builtin_candidates (&candidates, code, code2, fnname, arglist,
-			  flags, complain);
+  result = add_operator_candidates (&candidates, code, code2, arglist,
+				    flags, complain);
+  if (result == error_mark_node)
+    goto user_defined_result_ready;
 
   switch (code)
     {
@@ -6021,6 +6146,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
 	     -fpermissive.  */
 	  else
 	    {
+	      tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code);
 	      const char *msg = (flag_permissive) 
 		? G_("no %<%D(int)%> declared for postfix %qs,"
 		     " trying prefix operator instead")
@@ -6091,7 +6217,12 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
 	  if (resolve_args (arglist, complain) == NULL)
 	    result = error_mark_node;
 	  else
-	    result = build_over_call (cand, LOOKUP_NORMAL, complain);
+	    {
+	      if (cand->reversed ())
+		/* We swapped these in add_candidate, swap them back now.  */
+		std::swap (cand->convs[0], cand->convs[1]);
+	      result = build_over_call (cand, LOOKUP_NORMAL, complain);
+	    }
 
 	  if (trivial_fn_p (cand->fn))
 	    /* There won't be a CALL_EXPR.  */;
@@ -6121,6 +6252,73 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
 		  break;
 		}
 	    }
+
+	  /* If this was a C++20 rewritten comparison, adjust the result.  */
+	  if (cand->rewritten ())
+	    {
+	      /* FIXME build_min_non_dep_op_overload can't handle rewrites.  */
+	      if (overload)
+		*overload = NULL_TREE;
+	      switch (code)
+		{
+		case EQ_EXPR:
+		  gcc_checking_assert (cand->reversed ());
+		  gcc_fallthrough ();
+		case NE_EXPR:
+		  /* If a rewritten operator== candidate is selected by
+		     overload resolution for an operator @, its return type
+		     shall be cv bool.... */
+		  if (TREE_CODE (TREE_TYPE (result)) != BOOLEAN_TYPE)
+		    {
+		      if (complain & tf_error)
+			{
+			  auto_diagnostic_group d;
+			  error_at (loc, "return type of %qD is not %qs",
+				    cand->fn, "bool");
+			  inform (loc, "used as rewritten candidate for "
+				  "comparison of %qT and %qT",
+				  arg1_type, arg2_type);
+			}
+		      result = error_mark_node;
+		    }
+		  else if (code == NE_EXPR)
+		    /* !(y == x) or !(x == y)  */
+		    result = build1_loc (loc, TRUTH_NOT_EXPR,
+					 boolean_type_node, result);
+		  break;
+
+		  /* If a rewritten operator<=> candidate is selected by
+		     overload resolution for an operator @, x @ y is
+		     interpreted as 0 @ (y <=> x) if the selected candidate is
+		     a synthesized candidate with reversed order of parameters,
+		     or (x <=> y) @ 0 otherwise, using the selected rewritten
+		     operator<=> candidate.  */
+		case SPACESHIP_EXPR:
+		  if (!cand->reversed ())
+		    /* We're in the build_new_op call below for an outer
+		       reversed call; we don't need to do anything more.  */
+		    break;
+		  gcc_fallthrough ();
+		case LT_EXPR:
+		case LE_EXPR:
+		case GT_EXPR:
+		case GE_EXPR:
+		  {
+		    tree lhs = result;
+		    tree rhs = integer_zero_node;
+		    if (cand->reversed ())
+		      std::swap (lhs, rhs);
+		    result = build_new_op (loc, code,
+					   LOOKUP_NORMAL|LOOKUP_REWRITTEN,
+					   lhs, rhs, NULL_TREE,
+					   NULL, complain);
+		  }
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	    }
 	}
       else
 	{
@@ -6232,6 +6430,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
       if (complain & tf_warning && warn_tautological_compare)
 	warn_tautological_cmp (loc, code, arg1, arg2);
       /* Fall through.  */
+    case SPACESHIP_EXPR:
     case PLUS_EXPR:
     case MINUS_EXPR:
     case MULT_EXPR:
@@ -6307,6 +6506,29 @@ extract_call_expr (tree call)
     call = TREE_OPERAND (call, 0);
   if (TREE_CODE (call) == TARGET_EXPR)
     call = TARGET_EXPR_INITIAL (call);
+  if (cxx_dialect >= cxx2a)
+    switch (TREE_CODE (call))
+      {
+	/* C++20 rewritten comparison operators.  */
+      case TRUTH_NOT_EXPR:
+	call = TREE_OPERAND (call, 0);
+	break;
+      case LT_EXPR:
+      case LE_EXPR:
+      case GT_EXPR:
+      case GE_EXPR:
+      case SPACESHIP_EXPR:
+	{
+	  tree op0 = TREE_OPERAND (call, 0);
+	  if (integer_zerop (op0))
+	    call = TREE_OPERAND (call, 1);
+	  else
+	    call = op0;
+	}
+	break;
+      default:;
+      }
+
   gcc_assert (TREE_CODE (call) == CALL_EXPR
 	      || TREE_CODE (call) == AGGR_INIT_EXPR
 	      || call == error_mark_node);
@@ -10772,6 +10994,20 @@ joust_maybe_elide_copy (z_candidate *&cand)
   return false;
 }
 
+/* True if cand1 and cand2 represent the same function or function
+   template.  */
+
+static bool
+same_fn_or_template (z_candidate *cand1, z_candidate *cand2)
+{
+  if (cand1->fn == cand2->fn)
+    return true;
+  if (!cand1->template_decl || !cand2->template_decl)
+    return false;
+  return (most_general_template (TI_TEMPLATE (cand1->template_decl))
+	  == most_general_template (TI_TEMPLATE (cand2->template_decl)));
+}
+
 /* Compare two candidates for overloading as described in
    [over.match.best].  Return values:
 
@@ -10798,6 +11034,7 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
   /* If we have two pseudo-candidates for conversions to the same type,
      or two candidates for the same function, arbitrarily pick one.  */
   if (cand1->fn == cand2->fn
+      && cand1->reversed () == cand2->reversed ()
       && (IS_TYPE_OR_DECL_P (cand1->fn)))
     return 1;
 
@@ -10917,6 +11154,21 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
 
 	  if (winner && comp != winner)
 	    {
+	      if (same_fn_or_template (cand1, cand2))
+		{
+		  /* Ambiguity between normal and reversed versions of the
+		     same comparison operator; prefer the normal one.
+		     https://lists.isocpp.org/core/2019/10/7438.php  */
+		  if (cand1->reversed ())
+		    winner = -1;
+		  else
+		    {
+		      gcc_checking_assert (cand2->reversed ());
+		      winner = 1;
+		    }
+		  break;
+		}
+
 	      winner = 0;
 	      goto tweak;
 	    }
@@ -11046,6 +11298,21 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
 	return winner;
     }
 
+  /* F2 is a rewritten candidate (12.4.1.2) and F1 is not, or F1 and F2 are
+     rewritten candidates, and F2 is a synthesized candidate with reversed
+     order of parameters and F1 is not.  */
+  if (cand1->rewritten ())
+    {
+      if (!cand2->rewritten ())
+	return -1;
+      if (!cand1->reversed () && cand2->reversed ())
+	return 1;
+      if (cand1->reversed () && !cand2->reversed ())
+	return -1;
+    }
+  else if (cand2->rewritten ())
+    return 1;
+
   /* F1 is generated from a deduction-guide (13.3.1.8) and F2 is not */
   if (deduction_guide_p (cand1->fn))
     {
diff --git a/gcc/cp/class.c b/gcc/cp/class.c
index 35727074156..89ed1c040f6 100644
--- a/gcc/cp/class.c
+++ b/gcc/cp/class.c
@@ -3234,6 +3234,17 @@ add_implicitly_declared_members (tree t, tree* access_decls,
      a virtual function from a base class.  */
   declare_virt_assop_and_dtor (t);
 
+  /* If the class definition does not explicitly declare an == operator
+     function, but declares a defaulted three-way comparison operator function,
+     an == operator function is declared implicitly.  */
+  if (!classtype_has_op (t, EQ_EXPR))
+    if (tree space = classtype_has_defaulted_op (t, SPACESHIP_EXPR))
+      {
+	tree eq = implicitly_declare_fn (sfk_comparison, t, false, space,
+					 NULL_TREE);
+	add_method (t, eq, false);
+      }
+
   while (*access_decls)
     {
       tree using_decl = TREE_VALUE (*access_decls);
@@ -5386,6 +5397,44 @@ classtype_has_depr_implicit_copy (tree t)
   return NULL_TREE;
 }
 
+/* True iff T has a member or friend declaration of operator OP.  */
+
+bool
+classtype_has_op (tree t, tree_code op)
+{
+  tree name = ovl_op_identifier (op);
+  if (get_class_binding (t, name))
+    return true;
+  for (tree f = DECL_FRIENDLIST (TYPE_MAIN_DECL (t)); f; f = TREE_CHAIN (f))
+    if (FRIEND_NAME (f) == name)
+      return true;
+  return false;
+}
+
+
+/* If T has a defaulted member or friend declaration of OP, return it.  */
+
+tree
+classtype_has_defaulted_op (tree t, tree_code op)
+{
+  tree name = ovl_op_identifier (op);
+  for (ovl_iterator oi (get_class_binding (t, name)); oi; ++oi)
+    {
+      tree fn = *oi;
+      if (DECL_DEFAULTED_FN (fn))
+	return fn;
+    }
+  for (tree f = DECL_FRIENDLIST (TYPE_MAIN_DECL (t)); f; f = TREE_CHAIN (f))
+    if (FRIEND_NAME (f) == name)
+      for (tree l = FRIEND_DECLS (f); l; l = TREE_CHAIN (l))
+	{
+	  tree fn = TREE_VALUE (l);
+	  if (DECL_DEFAULTED_FN (fn))
+	    return fn;
+	}
+  return NULL_TREE;
+}
+
 /* Nonzero if we need to build up a constructor call when initializing an
    object of this class, either because it has a user-declared constructor
    or because it doesn't have a default constructor (so we need to give an
diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index ce910cd5a3b..20fddc57825 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -2480,6 +2480,12 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t,
   else if (code == POINTER_PLUS_EXPR)
     r = cxx_fold_pointer_plus_expression (ctx, t, lhs, rhs, non_constant_p,
 					  overflow_p);
+  else if (code == SPACESHIP_EXPR)
+    {
+      r = genericize_spaceship (type, lhs, rhs);
+      r = cxx_eval_constant_expression (ctx, r, false, non_constant_p,
+					overflow_p);
+    }
 
   if (r == NULL_TREE)
     r = fold_binary_loc (loc, code, type, lhs, rhs);
@@ -5226,6 +5232,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
     case GE_EXPR:
     case EQ_EXPR:
     case NE_EXPR:
+    case SPACESHIP_EXPR:
     case UNORDERED_EXPR:
     case ORDERED_EXPR:
     case UNLT_EXPR:
@@ -7037,6 +7044,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
     case GE_EXPR:
     case EQ_EXPR:
     case NE_EXPR:
+    case SPACESHIP_EXPR:
       want_rval = true;
       goto binary;
 
diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
index abd82b3fe42..744707e792d 100644
--- a/gcc/cp/cp-gimplify.c
+++ b/gcc/cp/cp-gimplify.c
@@ -1144,6 +1144,17 @@ cp_fold_function (tree fndecl)
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &pset, NULL);
 }
 
+/* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
+
+static tree genericize_spaceship (tree expr)
+{
+  iloc_sentinel s (cp_expr_location (expr));
+  tree type = TREE_TYPE (expr);
+  tree op0 = TREE_OPERAND (expr, 0);
+  tree op1 = TREE_OPERAND (expr, 1);
+  return genericize_spaceship (type, op0, op1);
+}
+
 /* Perform any pre-gimplification lowering of C++ front end trees to
    GENERIC.  */
 
@@ -1574,6 +1585,10 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
       genericize_break_stmt (stmt_p);
       break;
 
+    case SPACESHIP_EXPR:
+      *stmt_p = genericize_spaceship (*stmt_p);
+      break;
+
     case OMP_FOR:
     case OMP_SIMD:
     case OMP_DISTRIBUTE:
diff --git a/gcc/cp/cp-objcp-common.c b/gcc/cp/cp-objcp-common.c
index 60dcbe44105..b9bc2c6592c 100644
--- a/gcc/cp/cp-objcp-common.c
+++ b/gcc/cp/cp-objcp-common.c
@@ -518,6 +518,7 @@ cp_common_init_ts (void)
   MARK_TS_EXP (VEC_DELETE_EXPR);
   MARK_TS_EXP (VEC_INIT_EXPR);
   MARK_TS_EXP (VEC_NEW_EXPR);
+  MARK_TS_EXP (SPACESHIP_EXPR);
 
   /* Fold expressions.  */
   MARK_TS_EXP (BINARY_LEFT_FOLD_EXPR);
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 07112aad092..3bfcfb2c6b7 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -16793,6 +16793,13 @@ finish_function (bool inline_p)
 	}
     }
 
+  if (DECL_DELETED_FN (fndecl))
+    {
+      DECL_INITIAL (fndecl) = error_mark_node;
+      DECL_SAVED_TREE (fndecl) = NULL_TREE;
+      goto cleanup;
+    }
+
   // If this is a concept, check that the definition is reasonable.
   if (DECL_DECLARED_CONCEPT_P (fndecl))
     check_function_concept (fndecl);
@@ -16939,6 +16946,7 @@ finish_function (bool inline_p)
   if (!processing_template_decl && !DECL_IMMEDIATE_FUNCTION_P (fndecl))
     cp_genericize (fndecl);
 
+ cleanup:
   /* We're leaving the context of this function, so zap cfun.  It's still in
      DECL_STRUCT_FUNCTION, and we'll restore it in tree_rest_of_compilation.  */
   set_cfun (NULL);
diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c
index 4dc54811122..4f0b2161db1 100644
--- a/gcc/cp/decl2.c
+++ b/gcc/cp/decl2.c
@@ -927,6 +927,10 @@ grokfield (const cp_declarator *declarator,
 	    }
 	  else if (init == ridpointers[(int)RID_DEFAULT])
 	    {
+	      if (friendp)
+		/* ??? do_friend doesn't set this because funcdef_flag is false
+		   for in-class defaulted functions.  So set it here.  */
+		SET_DECL_FRIEND_CONTEXT (value, current_class_type);
 	      if (defaultable_fn_check (value))
 		{
 		  DECL_DEFAULTED_FN (value) = 1;
@@ -5471,6 +5475,17 @@ mark_used (tree decl, tsubst_flags_t complain)
   if (TREE_CODE (decl) == CONST_DECL)
     used_types_insert (DECL_CONTEXT (decl));
 
+  if (TREE_CODE (decl) == FUNCTION_DECL
+      && DECL_MAYBE_DELETED (decl))
+    {
+      /* ??? Switch other defaulted functions to use DECL_MAYBE_DELETED?  */
+      gcc_assert (special_function_p (decl) == sfk_comparison);
+
+      ++function_depth;
+      synthesize_method (decl);
+      --function_depth;
+    }
+
   if (TREE_CODE (decl) == FUNCTION_DECL
       && !maybe_instantiate_noexcept (decl, complain))
     return false;
@@ -5592,7 +5607,6 @@ mark_used (tree decl, tsubst_flags_t complain)
 
   /* Is it a synthesized method that needs to be synthesized?  */
   if (TREE_CODE (decl) == FUNCTION_DECL
-      && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)
       && DECL_DEFAULTED_FN (decl)
       /* A function defaulted outside the class is synthesized either by
 	 cp_finish_decl or instantiate_decl.  */
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index d104a4d574c..c06776f565a 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -2286,6 +2286,7 @@ dump_expr (cxx_pretty_printer *pp, tree t, int flags)
     case GE_EXPR:
     case EQ_EXPR:
     case NE_EXPR:
+    case SPACESHIP_EXPR:
     case EXACT_DIV_EXPR:
       dump_binary_op (pp, OVL_OP_INFO (false, TREE_CODE (t))->name, t, flags);
       break;
diff --git a/gcc/cp/method.c b/gcc/cp/method.c
index 09e9c73cda5..c9dd90fcba7 100644
--- a/gcc/cp/method.c
+++ b/gcc/cp/method.c
@@ -406,6 +406,7 @@ type_has_trivial_fn (tree ctype, special_function_kind sfk)
     case sfk_virtual_destructor:
       return !TYPE_HAS_NONTRIVIAL_DESTRUCTOR (ctype);
     case sfk_inheriting_constructor:
+    case sfk_comparison:
       return false;
     default:
       gcc_unreachable ();
@@ -877,6 +878,588 @@ do_build_copy_assign (tree fndecl)
   finish_compound_stmt (compound_stmt);
 }
 
+/* C++20 <compare> comparison category types.  */
+
+enum comp_cat_tag
+{
+  cc_weak_equality,
+  cc_strong_equality,
+  cc_partial_ordering,
+  cc_weak_ordering,
+  cc_strong_ordering,
+  cc_last
+};
+
+/* Names of the comparison categories and their value members, to be indexed by
+   comp_cat_tag enumerators.  genericize_spaceship below relies on the ordering
+   of the members.  */
+
+struct comp_cat_info_t
+{
+  const char *name;
+  const char *members[4];
+};
+static const comp_cat_info_t comp_cat_info[cc_last]
+= {
+   { "weak_equality", "equivalent", "nonequivalent" },
+   { "strong_equality", "equal", "nonequal" },
+   { "partial_ordering", "equivalent", "greater", "less", "unordered" },
+   { "weak_ordering", "equivalent", "greater", "less" },
+   { "strong_ordering", "equal", "greater", "less" }
+};
+
+/* A cache of the category types to speed repeated lookups.  */
+
+static GTY((deletable)) tree comp_cat_cache[cc_last];
+
+/* Look up one of the result variables in the comparison category type.  */
+
+static tree
+lookup_comparison_result (tree type, const char *name_str,
+			  tsubst_flags_t complain = tf_warning_or_error)
+{
+  tree name = get_identifier (name_str);
+  tree decl = lookup_qualified_name (type, name);
+  if (TREE_CODE (decl) != VAR_DECL)
+    {
+      if (complain & tf_error)
+	{
+	  auto_diagnostic_group d;
+	  if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST)
+	    qualified_name_lookup_error (type, name, decl, input_location);
+	  else
+	    error ("%<%T::%D%> is not a static data member", type, decl);
+	  inform (input_location, "determining value of %qs", "operator<=>");
+	}
+      return error_mark_node;
+    }
+  return decl;
+}
+
+/* Look up a <compare> comparison category type in std.  */
+
+static tree
+lookup_comparison_category (comp_cat_tag tag,
+			    tsubst_flags_t complain = tf_warning_or_error)
+{
+  if (tree cached = comp_cat_cache[tag])
+    return cached;
+
+  tree name = get_identifier (comp_cat_info[tag].name);
+  tree decl = lookup_qualified_name (std_node, name);
+  if (TREE_CODE (decl) != TYPE_DECL)
+    {
+      if (complain & tf_error)
+	{
+	  auto_diagnostic_group d;
+	  if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST)
+	    qualified_name_lookup_error (std_node, name, decl, input_location);
+	  else
+	    error ("%<std::%D%> is not a type", decl);
+	  inform (input_location, "forming type of %qs", "operator<=>");
+	}
+      return error_mark_node;
+    }
+  /* Also make sure we can look up the value members now, since we won't
+     really use them until genericize time.  */
+  tree type = TREE_TYPE (decl);
+  for (int i = 0; i < 4; ++i)
+    {
+      const char *p = comp_cat_info[tag].members[i];
+      if (!p) break;
+      if (lookup_comparison_result (type, p, complain)
+	  == error_mark_node)
+	return error_mark_node;
+    }
+  return comp_cat_cache[tag] = type;
+}
+
+/* Wrapper that takes the tag rather than the type.  */
+
+static tree
+lookup_comparison_result (comp_cat_tag tag, const char *name_str,
+			  tsubst_flags_t complain = tf_warning_or_error)
+{
+  tree type = lookup_comparison_category (tag, complain);
+  return lookup_comparison_result (type, name_str, complain);
+}
+
+/* Wrapper that takes the index into the members array instead of the name.  */
+
+static tree
+lookup_comparison_result (comp_cat_tag tag, tree type, int idx)
+{
+  const char *name_str = comp_cat_info[tag].members[idx];
+  if (!name_str)
+    return NULL_TREE;
+  return lookup_comparison_result (type, name_str);
+}
+
+/* Does TYPE correspond to TAG?  */
+
+static bool
+is_cat (tree type, comp_cat_tag tag)
+{
+  tree name = TYPE_LINKAGE_IDENTIFIER (type);
+  return id_equal (name, comp_cat_info[tag].name);
+}
+
+/* Return the comp_cat_tag for TYPE.  */
+
+static comp_cat_tag
+cat_tag_for (tree type)
+{
+  for (int i = 0; i < cc_last; ++i)
+    {
+      comp_cat_tag tag = (comp_cat_tag)i;
+      if (is_cat (type, tag))
+	return tag;
+    }
+  return cc_last;
+}
+
+/* Return the comparison category tag of a <=> expression with non-class type
+   OPTYPE.  */
+
+static comp_cat_tag
+spaceship_comp_cat (tree optype)
+{
+  if (INTEGRAL_OR_ENUMERATION_TYPE_P (optype) || TYPE_PTROBV_P (optype))
+    return cc_strong_ordering;
+  else if (TREE_CODE (optype) == REAL_TYPE)
+    return cc_partial_ordering;
+  else if (TYPE_PTRFN_P (optype) || TYPE_PTRMEM_P (optype)
+	   || NULLPTR_TYPE_P (optype))
+    return cc_strong_equality;
+  else if (TREE_CODE (optype) == COMPLEX_TYPE)
+    {
+      tree intype = optype;
+      while (TREE_CODE (intype) == COMPLEX_TYPE)
+	intype = TREE_TYPE (intype);
+      if (TREE_CODE (intype) == REAL_TYPE)
+	return cc_weak_equality;
+      else
+	return cc_strong_equality;
+    }
+
+  /* FIXME should vector <=> produce a vector of one of the above?  */
+  gcc_unreachable ();
+}
+
+/* Return the comparison category type of a <=> expression with non-class type
+   OPTYPE.  */
+
+tree
+spaceship_type (tree optype, tsubst_flags_t complain)
+{
+  comp_cat_tag tag = spaceship_comp_cat (optype);
+  return lookup_comparison_category (tag, complain);
+}
+
+/* Turn <=> with type TYPE and operands OP0 and OP1 into GENERIC.  */
+
+tree
+genericize_spaceship (tree type, tree op0, tree op1)
+{
+  /* ??? maybe optimize based on knowledge of representation? */
+  comp_cat_tag tag = cat_tag_for (type);
+  gcc_checking_assert (tag < cc_last);
+
+  tree eq = lookup_comparison_result (tag, type, 0);
+  tree negt = lookup_comparison_result (tag, type, 1);
+
+  if (tag == cc_strong_equality || tag == cc_weak_equality)
+    {
+      tree comp = fold_build2 (EQ_EXPR, boolean_type_node, op0, op1);
+      return fold_build3 (COND_EXPR, type, comp, eq, negt);
+    }
+
+  tree r;
+  op0 = save_expr (op0);
+  op1 = save_expr (op1);
+
+  if (tag == cc_partial_ordering)
+    {
+      /* op0 == op1 ? equivalent : op0 < op1 ? less :
+	 op0 > op1 ? greater : unordered */
+      tree uo = lookup_comparison_result (tag, type, 3);
+      tree comp = fold_build2 (GT_EXPR, boolean_type_node, op0, op1);
+      r = fold_build3 (COND_EXPR, type, comp, negt, uo);
+    }
+  else
+    /* op0 == op1 ? equal : op0 < op1 ? less : greater */
+    r = negt;
+
+  tree lt = lookup_comparison_result (tag, type, 2);
+  tree comp = fold_build2 (LT_EXPR, boolean_type_node, op0, op1);
+  r = fold_build3 (COND_EXPR, type, comp, lt, r);
+
+  comp = fold_build2 (EQ_EXPR, boolean_type_node, op0, op1);
+  r = fold_build3 (COND_EXPR, type, comp, eq, r);
+
+  return r;
+}
+
+/* Check that the signature of a defaulted comparison operator is
+   well-formed.  */
+
+static bool
+early_check_defaulted_comparison (tree fn)
+{
+  location_t loc = DECL_SOURCE_LOCATION (fn);
+  tree ctx;
+  if (DECL_CLASS_SCOPE_P (fn))
+    ctx = DECL_CONTEXT (fn);
+  else
+    ctx = DECL_FRIEND_CONTEXT (fn);
+  bool ok = true;
+
+  if (!DECL_OVERLOADED_OPERATOR_IS (fn, SPACESHIP_EXPR)
+      && !same_type_p (TREE_TYPE (TREE_TYPE (fn)), boolean_type_node))
+    {
+      error_at (loc, "defaulted %qD must return %<bool%>", fn);
+      ok = false;
+    }
+
+  int i = DECL_NONSTATIC_MEMBER_FUNCTION_P (fn);
+  if (i && type_memfn_quals (TREE_TYPE (fn)) != TYPE_QUAL_CONST)
+    {
+      error_at (loc, "defaulted %qD must be %<const%>", fn);
+      ok = false;
+    }
+  tree parmnode = FUNCTION_FIRST_USER_PARMTYPE (fn);
+  for (; parmnode != void_list_node; parmnode = TREE_CHAIN (parmnode))
+    {
+      ++i;
+      tree parmtype = TREE_VALUE (parmnode);
+      diagnostic_t kind = DK_UNSPECIFIED;
+      int opt = 0;
+      if (same_type_p (parmtype, ctx))
+	/* The draft specifies const reference, but let's also allow by-value
+	   unless -Wpedantic, hopefully it will be added soon. */
+	kind = DK_PEDWARN,
+	  opt = OPT_Wpedantic;
+      else if (TREE_CODE (parmtype) != REFERENCE_TYPE
+	       || TYPE_QUALS (TREE_TYPE (parmtype)) != TYPE_QUAL_CONST
+	       || !(same_type_ignoring_top_level_qualifiers_p
+		    (TREE_TYPE (parmtype), ctx)))
+	kind = DK_ERROR;
+      if (kind)
+	emit_diagnostic (kind, loc, opt, "defaulted %qD must have "
+			 "parameter type %<const %T&%>", fn, ctx);
+      if (kind == DK_ERROR)
+	ok = false;
+    }
+
+  /* We still need to deduce deleted/constexpr/noexcept and maybe return. */
+  DECL_MAYBE_DELETED (fn) = true;
+
+  return ok;
+}
+
+/* Subroutine of build_comparison_op.  Given the vec of memberwise
+   comparisons COMPS, calculate the overall comparison category for
+   operator<=>.  */
+
+static tree
+common_comparison_type (vec<tree> &comps)
+{
+  tree seen[cc_last] = {};
+
+  for (unsigned i = 0; i < comps.length(); ++i)
+    {
+      tree comp = comps[i];
+      tree ctype = TREE_TYPE (comp);
+      comp_cat_tag tag = cat_tag_for (ctype);
+      if (tag < cc_last)
+	seen[tag] = ctype;
+      else
+	/* If any Ti is not a comparison category type, U is void.  */
+	return void_type_node;
+    }
+
+  /* Otherwise, if at least one T i is std::weak_equality, or at least one T i
+     is std::strong_equality and at least one T j is std::partial_ordering or
+     std::weak_ordering, U is std::weak_equality.  */
+  if (tree t = seen[cc_weak_equality]) return t;
+  if (seen[cc_strong_equality]
+      && (seen[cc_partial_ordering] || seen[cc_weak_ordering]))
+    return lookup_comparison_category (cc_weak_equality);
+
+  /* Otherwise, if at least one T i is std::strong_equality, U is
+     std::strong_equality.  */
+  if (tree t = seen[cc_strong_equality]) return t;
+
+  /* Otherwise, if at least one T i is std::partial_ordering, U is
+     std::partial_ordering.  */
+  if (tree t = seen[cc_partial_ordering]) return t;
+
+  /* Otherwise, if at least one T i is std::weak_ordering, U is
+     std::weak_ordering.  */
+  if (tree t = seen[cc_weak_ordering]) return t;
+
+  /* Otherwise, U is std::strong_ordering.  */
+  if (tree t = seen[cc_strong_ordering]) return t;
+  return lookup_comparison_category (cc_strong_ordering);
+}
+
+/* Data structure for build_comparison_op.  */
+
+struct comp_info
+{
+  tree fndecl;
+  location_t loc;
+  bool defining;
+  bool first_time;
+  bool constexp;
+  bool was_constexp;
+  bool noex;
+
+  comp_info (tree fndecl, tsubst_flags_t &complain)
+    : fndecl (fndecl)
+  {
+    loc = DECL_SOURCE_LOCATION (fndecl);
+
+    /* We only have tf_error set when we're called from
+       explain_invalid_constexpr_fn or maybe_explain_implicit_delete.  */
+    defining = !(complain & tf_error);
+
+    first_time = DECL_MAYBE_DELETED (fndecl);
+    DECL_MAYBE_DELETED (fndecl) = false;
+
+    /* Do we want to try to set constexpr?  */
+    was_constexp = DECL_DECLARED_CONSTEXPR_P (fndecl);
+    constexp = first_time;
+    if (constexp)
+      /* Set this for var_in_constexpr_fn.  */
+      DECL_DECLARED_CONSTEXPR_P (fndecl) = true;
+
+    /* Do we want to try to set noexcept?  */
+    noex = first_time;
+    if (noex)
+      {
+	tree raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl));
+	if (raises && !UNEVALUATED_NOEXCEPT_SPEC_P (raises))
+	  /* There was an explicit exception-specification.  */
+	  noex = false;
+      }
+  }
+
+  /* EXPR is an expression built as part of the function body.
+     Adjust the properties appropriately.  */
+  void check (tree expr)
+  {
+    if (expr == error_mark_node)
+      DECL_DELETED_FN (fndecl) = true;
+    if ((constexp || was_constexp)
+	&& !potential_rvalue_constant_expression (expr))
+      {
+	if (was_constexp)
+	  require_potential_rvalue_constant_expression (expr);
+	else
+	  constexp = false;
+      }
+    if (noex && !expr_noexcept_p (expr, tf_none))
+      noex = false;
+  }
+};
+
+/* Build up the definition of a defaulted comparison operator.  Unlike other
+   defaulted functions that use synthesized_method_walk to determine whether
+   the function is e.g. deleted, for comparisons we use the same code.  We try
+   to use synthesize_method at the earliest opportunity and bail out if the
+   function ends up being deleted.  */
+
+static void
+build_comparison_op (tree fndecl, tsubst_flags_t complain)
+{
+  comp_info info (fndecl, complain);
+
+  if (!info.defining && !(complain & tf_error) && !DECL_MAYBE_DELETED (fndecl))
+    return;
+
+  int flags = LOOKUP_NORMAL | LOOKUP_NONVIRTUAL | LOOKUP_DEFAULTED;
+  const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (DECL_NAME (fndecl));
+  tree_code code = op->tree_code;
+
+  tree lhs = DECL_ARGUMENTS (fndecl);
+  tree rhs = DECL_CHAIN (lhs);
+  if (is_this_parameter (lhs))
+    lhs = cp_build_fold_indirect_ref (lhs);
+  else
+    lhs = convert_from_reference (lhs);
+  rhs = convert_from_reference (rhs);
+  tree ctype = TYPE_MAIN_VARIANT (TREE_TYPE (lhs));
+
+  iloc_sentinel ils (info.loc);
+
+  /* A defaulted comparison operator function for class C is defined as
+     deleted if ... C is a union-like class.  */
+  if (TREE_CODE (ctype) == UNION_TYPE)
+    {
+      if (complain & tf_error)
+	inform (info.loc, "cannot default compare union %qT", ctype);
+      DECL_DELETED_FN (fndecl) = true;
+    }
+
+  tree compound_stmt = NULL_TREE;
+  if (info.defining)
+    compound_stmt = begin_compound_stmt (0);
+  else
+    ++cp_unevaluated_operand;
+
+  tree rettype = TREE_TYPE (TREE_TYPE (fndecl));
+  if (code != SPACESHIP_EXPR && is_auto (rettype))
+    {
+      rettype = boolean_type_node;
+      apply_deduced_return_type (fndecl, rettype);
+    }
+
+  if (code == EQ_EXPR || code == SPACESHIP_EXPR)
+    {
+      auto_vec<tree> comps;
+
+      /* Compare each of the subobjects.  Note that we get bases from
+	 next_initializable_field because we're past C++17.  */
+      for (tree field = next_initializable_field (TYPE_FIELDS (ctype));
+	   field;
+	   field = next_initializable_field (DECL_CHAIN (field)))
+	{
+	  tree expr_type = TREE_TYPE (field);
+
+	  /* A defaulted comparison operator function for class C is defined as
+	     deleted if any non-static data member of C is of reference type or
+	     C is a union-like class.  */
+	  if (TREE_CODE (expr_type) == REFERENCE_TYPE)
+	    {
+	      if (complain & tf_error)
+		inform (DECL_SOURCE_LOCATION (field), "cannot default compare "
+			"reference member %qD", field);
+	      DECL_DELETED_FN (fndecl) = true;
+	      continue;
+	    }
+	  else if (ANON_UNION_TYPE_P (expr_type))
+	    {
+	      if (complain & tf_error)
+		inform (DECL_SOURCE_LOCATION (field), "cannot default compare "
+			"anonymous union member");
+	      DECL_DELETED_FN (fndecl) = true;
+	      continue;
+	    }
+
+	  tree lhs_mem = build3 (COMPONENT_REF, expr_type, lhs, field,
+				 NULL_TREE);
+	  tree rhs_mem = build3 (COMPONENT_REF, expr_type, rhs, field,
+				 NULL_TREE);
+	  tree comp = build_new_op (info.loc, code, flags, lhs_mem, rhs_mem,
+				    NULL_TREE, NULL, complain);
+	  comps.safe_push (comp);
+	}
+      if (code == SPACESHIP_EXPR && is_auto (rettype))
+	{
+	  rettype = common_comparison_type (comps);
+	  apply_deduced_return_type (fndecl, rettype);
+	}
+      for (unsigned i = 0; i < comps.length(); ++i)
+	{
+	  tree comp = comps[i];
+	  tree eq, retval = NULL_TREE, if_ = NULL_TREE;
+	  if (info.defining)
+	    if_ = begin_if_stmt ();
+	  /* Spaceship is specified to use !=, but for the comparison category
+	     types, != is equivalent to !(==), so let's use == directly.  */
+	  if (code == EQ_EXPR)
+	    {
+	      /* if (x==y); else return false; */
+	      eq = comp;
+	      retval = boolean_false_node;
+	    }
+	  else
+	    {
+	      /* if (auto v = x<=>y, v == 0); else return v; */
+	      if (TREE_CODE (comp) == SPACESHIP_EXPR)
+		TREE_TYPE (comp) = rettype;
+	      else
+		comp = build_static_cast (rettype, comp, complain);
+	      info.check (comp);
+	      if (info.defining)
+		{
+		  tree var = create_temporary_var (rettype);
+		  pushdecl (var);
+		  cp_finish_decl (var, comp, false, NULL_TREE, flags);
+		  comp = retval = var;
+		}
+	      eq = build_new_op (info.loc, EQ_EXPR, flags, comp,
+				 integer_zero_node, NULL_TREE, NULL,
+				 complain);
+	    }
+	  tree ceq = contextual_conv_bool (eq, complain);
+	  info.check (ceq);
+	  if (info.defining)
+	    {
+	      finish_if_stmt_cond (ceq, if_);
+	      finish_then_clause (if_);
+	      begin_else_clause (if_);
+	      finish_return_stmt (retval);
+	      finish_else_clause (if_);
+	      finish_if_stmt (if_);
+	    }
+	}
+      if (info.defining)
+	{
+	  tree val;
+	  if (code == EQ_EXPR)
+	    val = boolean_true_node;
+	  else
+	    {
+	      tree seql = lookup_comparison_result (cc_strong_ordering,
+						    "equal", complain);
+	      val = build_static_cast (rettype, seql, complain);
+	    }
+	  finish_return_stmt (val);
+	}
+    }
+  else if (code == NE_EXPR)
+    {
+      tree comp = build_new_op (info.loc, EQ_EXPR, flags, lhs, rhs,
+				NULL_TREE, NULL, complain);
+      comp = contextual_conv_bool (comp, complain);
+      info.check (comp);
+      if (info.defining)
+	{
+	  tree neg = build1 (TRUTH_NOT_EXPR, boolean_type_node, comp);
+	  finish_return_stmt (neg);
+	}
+    }
+  else
+    {
+      tree comp = build_new_op (info.loc, SPACESHIP_EXPR, flags, lhs, rhs,
+				NULL_TREE, NULL, complain);
+      tree comp2 = build_new_op (info.loc, code, flags, comp, integer_zero_node,
+				 NULL_TREE, NULL, complain);
+      info.check (comp2);
+      if (info.defining)
+	finish_return_stmt (comp2);
+    }
+
+  if (info.defining)
+    finish_compound_stmt (compound_stmt);
+  else
+    --cp_unevaluated_operand;
+
+  if (info.first_time)
+    {
+      DECL_DECLARED_CONSTEXPR_P (fndecl) = info.constexp || info.was_constexp;
+      tree raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl));
+      if (!raises || UNEVALUATED_NOEXCEPT_SPEC_P (raises))
+	{
+	  raises = info.noex ? noexcept_true_spec : noexcept_false_spec;
+	  TREE_TYPE (fndecl) = build_exception_variant (TREE_TYPE (fndecl),
+							raises);
+	}
+    }
+}
+
 /* Synthesize FNDECL, a non-static member function.   */
 
 void
@@ -889,6 +1472,7 @@ synthesize_method (tree fndecl)
   location_t save_input_location = input_location;
   int error_count = errorcount;
   int warning_count = warningcount + werrorcount;
+  special_function_kind sfk = special_function_p (fndecl);
 
   /* Reset the source location, we might have been previously
      deferred, and thus have saved where we were first needed.  */
@@ -930,6 +1514,12 @@ synthesize_method (tree fndecl)
       else
 	finish_mem_initializers (NULL_TREE);
     }
+  else if (sfk == sfk_comparison)
+    {
+      /* Pass tf_none so the function is just deleted if there's a problem.  */
+      build_comparison_op (fndecl, tf_none);
+      need_body = false;
+    }
 
   /* If we haven't yet generated the body of the function, just
      generate an empty compound statement.  */
@@ -941,7 +1531,10 @@ synthesize_method (tree fndecl)
     }
 
   finish_function_body (stmt);
-  expand_or_defer_fn (finish_function (/*inline_p=*/false));
+  finish_function (/*inline_p=*/false);
+
+  if (!DECL_DELETED_FN (fndecl))
+    expand_or_defer_fn (fndecl);
 
   input_location = save_input_location;
 
@@ -1753,6 +2346,13 @@ get_defaulted_eh_spec (tree decl, tsubst_flags_t complain)
   if (DECL_CLONED_FUNCTION_P (decl))
     decl = DECL_CLONED_FUNCTION (decl);
   special_function_kind sfk = special_function_p (decl);
+  if (sfk == sfk_comparison)
+    {
+      /* We're in synthesize_method. Start with NULL_TREE, build_comparison_op
+	 will adjust as needed.  */
+      gcc_assert (decl == current_function_decl);
+      return NULL_TREE;
+    }
   tree ctype = DECL_CONTEXT (decl);
   tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl);
   tree parm_type = TREE_VALUE (parms);
@@ -1836,7 +2436,14 @@ maybe_explain_implicit_delete (tree decl)
 	      informed = true;
 	    }
 	}
-      if (!informed)
+      if (!informed && sfk == sfk_comparison)
+	{
+	  inform (DECL_SOURCE_LOCATION (decl),
+		  "%q#D is implicitly deleted because the default "
+		  "definition would be ill-formed:", decl);
+	  build_comparison_op (decl, tf_warning_or_error);
+	}
+      else if (!informed)
 	{
 	  tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl);
 	  bool const_p = false;
@@ -1891,10 +2498,18 @@ explain_implicit_non_constexpr (tree decl)
   bool const_p = CP_TYPE_CONST_P (non_reference (TREE_VALUE (parms)));
   tree inh = DECL_INHERITED_CTOR (decl);
   bool dummy;
-  synthesized_method_walk (DECL_CLASS_CONTEXT (decl),
-			   special_function_p (decl), const_p,
-			   NULL, NULL, NULL, &dummy, true,
-			   &inh, parms);
+  special_function_kind sfk = special_function_p (decl);
+  if (sfk == sfk_comparison)
+    {
+      DECL_DECLARED_CONSTEXPR_P (decl) = true;
+      build_comparison_op (decl, tf_warning_or_error);
+      DECL_DECLARED_CONSTEXPR_P (decl) = false;
+    }
+  else
+    synthesized_method_walk (DECL_CLASS_CONTEXT (decl),
+			     sfk, const_p,
+			     NULL, NULL, NULL, &dummy, true,
+			     &inh, parms);
 }
 
 /* DECL is an instantiation of an inheriting constructor template.  Deduce
@@ -1933,12 +2548,12 @@ deduce_inheriting_ctor (tree decl)
 /* Implicitly declare the special function indicated by KIND, as a
    member of TYPE.  For copy constructors and assignment operators,
    CONST_P indicates whether these functions should take a const
-   reference argument or a non-const reference.  Returns the
-   FUNCTION_DECL for the implicitly declared function.  */
+   reference argument or a non-const reference.
+   Returns the FUNCTION_DECL for the implicitly declared function.  */
 
 tree
 implicitly_declare_fn (special_function_kind kind, tree type,
-		       bool const_p, tree inherited_ctor,
+		       bool const_p, tree pattern_fn,
 		       tree inherited_parms)
 {
   tree fn;
@@ -1950,8 +2565,11 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   tree this_parm;
   tree name;
   HOST_WIDE_INT saved_processing_template_decl;
-  bool deleted_p;
-  bool constexpr_p;
+  bool deleted_p = false;
+  bool constexpr_p = false;
+  bool friend_p = (kind == sfk_comparison && DECL_FRIEND_P (pattern_fn));
+  tree inherited_ctor = (kind == sfk_inheriting_constructor
+			 ? pattern_fn : NULL_TREE);
 
   /* Because we create declarations for implicitly declared functions
      lazily, we may be creating the declaration for a member of TYPE
@@ -1978,6 +2596,7 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   else
     return_type = void_type_node;
 
+  int this_quals = TYPE_UNQUALIFIED;
   switch (kind)
     {
     case sfk_destructor:
@@ -2021,6 +2640,36 @@ implicitly_declare_fn (special_function_kind kind, tree type,
 	}
       break;
     }
+
+    case sfk_comparison:
+      /* If the class definition does not explicitly declare an == operator
+	 function, but declares a defaulted three-way comparison operator
+	 function, an == operator function is declared implicitly with the same
+	 access as the three-way comparison operator function.
+
+	 The implicitly-declared == operator for a class X is an inline member
+	 and is defined as defaulted in the definition of X.
+
+	 If the three-way comparison operator function is declared as a
+	 non-static const member, the implicitly-declared == operator function
+	 is a member of the form
+
+	   bool X::operator==(const X&) const;
+
+	 Otherwise, the implicitly-declared == operator function is of the form
+
+	   friend bool operator==(const X&, const X&); */
+      /* No other comparison operator is implicitly declared.  */
+      name = ovl_op_identifier (false, EQ_EXPR);
+      return_type = boolean_type_node;
+      rhs_parm_type = cp_build_qualified_type (type, TYPE_QUAL_CONST);
+      rhs_parm_type = cp_build_reference_type (rhs_parm_type, false);
+      parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types);
+      if (friend_p)
+	parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types);
+      this_quals = TYPE_QUAL_CONST;
+      break;
+
     default:
       gcc_unreachable ();
     }
@@ -2038,9 +2687,10 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   else if (cxx_dialect >= cxx11)
     {
       raises = noexcept_deferred_spec;
-      synthesized_method_walk (type, kind, const_p, NULL, &trivial_p,
-			       &deleted_p, &constexpr_p, false,
-			       &inherited_ctor, inherited_parms);
+      if (kind != sfk_comparison)
+	synthesized_method_walk (type, kind, const_p, NULL, &trivial_p,
+				 &deleted_p, &constexpr_p, false,
+				 &inherited_ctor, inherited_parms);
     }
   else
     synthesized_method_walk (type, kind, const_p, &raises, &trivial_p,
@@ -2062,7 +2712,9 @@ implicitly_declare_fn (special_function_kind kind, tree type,
     type_set_nontrivial_flag (type, kind);
 
   /* Create the function.  */
-  fn_type = build_method_type_directly (type, return_type, parameter_types);
+  tree this_type = cp_build_qualified_type (type, this_quals);
+  fn_type = build_method_type_directly (this_type, return_type,
+					parameter_types);
   if (raises)
     {
       if (raises != error_mark_node)
@@ -2073,16 +2725,25 @@ implicitly_declare_fn (special_function_kind kind, tree type,
 	gcc_assert (seen_error ());
     }
   fn = build_lang_decl (FUNCTION_DECL, name, fn_type);
-  if (kind != sfk_inheriting_constructor)
+  if (kind == sfk_comparison)
+    {
+      DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (pattern_fn);
+      DECL_MAYBE_DELETED (fn) = true;
+    }
+  else if (kind != sfk_inheriting_constructor)
     DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (TYPE_NAME (type));
 
-  if (!IDENTIFIER_CDTOR_P (name))
-    /* Assignment operator.  */
-    DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = OVL_OP_NOP_EXPR;
+  if (IDENTIFIER_OVL_OP_P (name))
+    {
+      const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (name);
+      DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = op->ovl_op_code;
+    }
   else if (IDENTIFIER_CTOR_P (name))
     DECL_CXX_CONSTRUCTOR_P (fn) = true;
-  else
+  else if (IDENTIFIER_DTOR_P (name))
     DECL_CXX_DESTRUCTOR_P (fn) = true;
+  else
+    gcc_unreachable ();
 
   SET_DECL_ALIGN (fn, MINIMUM_METHOD_BOUNDARY);
 
@@ -2097,6 +2758,13 @@ implicitly_declare_fn (special_function_kind kind, tree type,
       retrofit_lang_decl (decl);
       DECL_PARM_INDEX (decl) = DECL_PARM_LEVEL (decl) = 1;
       DECL_ARGUMENTS (fn) = decl;
+      if (friend_p)
+	{
+	  /* The second parm of friend op==.  */
+	  tree decl2 = copy_decl (decl);
+	  DECL_CHAIN (decl) = decl2;
+	  DECL_PARM_INDEX (decl2) = 2;
+	}
     }
   else if (kind == sfk_inheriting_constructor)
     {
@@ -2122,7 +2790,7 @@ implicitly_declare_fn (special_function_kind kind, tree type,
       constexpr_p = DECL_DECLARED_CONSTEXPR_P (inherited_ctor);
     }
   /* Add the "this" parameter.  */
-  this_parm = build_this_parm (fn, fn_type, TYPE_UNQUALIFIED);
+  this_parm = build_this_parm (fn, fn_type, this_quals);
   DECL_CHAIN (this_parm) = DECL_ARGUMENTS (fn);
   DECL_ARGUMENTS (fn) = this_parm;
 
@@ -2141,6 +2809,12 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   set_linkage_according_to_type (type, fn);
   if (TREE_PUBLIC (fn))
     DECL_COMDAT (fn) = 1;
+  if (kind == sfk_comparison && !friend_p)
+    {
+      /* The implicit op== has the same access as the op<=>.  */
+      TREE_PRIVATE (fn) = TREE_PRIVATE (pattern_fn);
+      TREE_PROTECTED (fn) = TREE_PROTECTED (pattern_fn);
+    }
   rest_of_decl_compilation (fn, namespace_bindings_p (), at_eof);
   gcc_assert (!TREE_USED (fn));
 
@@ -2182,6 +2856,16 @@ defaulted_late_check (tree fn)
   /* Complain about invalid signature for defaulted fn.  */
   tree ctx = DECL_CONTEXT (fn);
   special_function_kind kind = special_function_p (fn);
+
+  if (kind == sfk_comparison)
+    {
+      /* If the function was declared constexpr, check that the definition
+	 qualifies.  Otherwise we can define the function lazily.  */
+      if (DECL_DECLARED_CONSTEXPR_P (fn))
+	synthesize_method (fn);
+      return;
+    }
+
   bool fn_const_p = (copy_fn_p (fn) == 2);
   tree implicit_fn = implicitly_declare_fn (kind, ctx, fn_const_p,
 					    NULL, NULL);
@@ -2272,6 +2956,13 @@ defaultable_fn_check (tree fn)
       else if (move_fn_p (fn))
 	kind = sfk_move_assignment;
     }
+  else if (DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) >= OVL_OP_EQ_EXPR
+	   && DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) <= OVL_OP_SPACESHIP_EXPR)
+    {
+      kind = sfk_comparison;
+      if (!early_check_defaulted_comparison (fn))
+	return false;
+    }
 
   if (kind == sfk_none)
     {
@@ -2293,7 +2984,7 @@ defaultable_fn_check (tree fn)
 	if (DECL_NAME (p))
 	  TREE_NO_WARNING (p) = 1;
 
-      if (TYPE_BEING_DEFINED (DECL_CONTEXT (fn)))
+      if (current_class_type && TYPE_BEING_DEFINED (current_class_type))
 	/* Defer checking.  */;
       else if (!processing_template_decl)
 	defaulted_late_check (fn);
diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c
index cbb61697d7c..cd0d9551aa3 100644
--- a/gcc/cp/name-lookup.c
+++ b/gcc/cp/name-lookup.c
@@ -5592,6 +5592,12 @@ get_std_name_hint (const char *name)
     {"atomic_ref", "<atomic>", cxx2a},
     /* <bitset>.  */
     {"bitset", "<bitset>", cxx11},
+    /* <compare> */
+    {"weak_equality", "<compare>", cxx2a},
+    {"strong_equality", "<compare>", cxx2a},
+    {"partial_ordering", "<compare>", cxx2a},
+    {"weak_ordering", "<compare>", cxx2a},
+    {"strong_ordering", "<compare>", cxx2a},
     /* <complex>.  */
     {"complex", "<complex>", cxx98},
     {"complex_literals", "<complex>", cxx14},
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 11468c0af42..cbbf946d32c 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -1850,6 +1850,7 @@ enum cp_parser_prec
   PREC_AND_EXPRESSION,
   PREC_EQUALITY_EXPRESSION,
   PREC_RELATIONAL_EXPRESSION,
+  PREC_SPACESHIP_EXPRESSION,
   PREC_SHIFT_EXPRESSION,
   PREC_ADDITIVE_EXPRESSION,
   PREC_MULTIPLICATIVE_EXPRESSION,
@@ -1921,6 +1922,8 @@ static const cp_parser_binary_operations_map_node binops[] = {
   { CPP_LSHIFT, LSHIFT_EXPR, PREC_SHIFT_EXPRESSION },
   { CPP_RSHIFT, RSHIFT_EXPR, PREC_SHIFT_EXPRESSION },
 
+  { CPP_SPACESHIP, SPACESHIP_EXPR, PREC_SPACESHIP_EXPRESSION },
+
   { CPP_LESS, LT_EXPR, PREC_RELATIONAL_EXPRESSION },
   { CPP_GREATER, GT_EXPR, PREC_RELATIONAL_EXPRESSION },
   { CPP_LESS_EQ, LE_EXPR, PREC_RELATIONAL_EXPRESSION },
@@ -15507,6 +15510,10 @@ cp_parser_operator (cp_parser* parser, location_t start_loc)
       op = GE_EXPR;
       break;
 
+    case CPP_SPACESHIP:
+      op = SPACESHIP_EXPR;
+      break;
+
     case CPP_AND_AND:
       op = TRUTH_ANDIF_EXPR;
       break;
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index b8f8f6dbb59..313b8073a3c 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -19003,6 +19003,7 @@ tsubst_copy_and_build (tree t,
     case GE_EXPR:
     case LT_EXPR:
     case GT_EXPR:
+    case SPACESHIP_EXPR:
     case MEMBER_REF:
     case DOTSTAR_EXPR:
       {
diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c
index a4b8b00c922..5cdeb6a07fe 100644
--- a/gcc/cp/tree.c
+++ b/gcc/cp/tree.c
@@ -5042,6 +5042,9 @@ special_function_p (const_tree decl)
     return sfk_conversion;
   if (deduction_guide_p (decl))
     return sfk_deduction_guide;
+  if (DECL_OVERLOADED_OPERATOR_CODE_RAW (decl) >= OVL_OP_EQ_EXPR
+      && DECL_OVERLOADED_OPERATOR_CODE_RAW (decl) <= OVL_OP_SPACESHIP_EXPR)
+    return sfk_comparison;
 
   return sfk_none;
 }
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 50240537938..38a15d14620 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -4889,6 +4889,7 @@ cp_build_binary_op (const op_location_t &location,
 
     case EQ_EXPR:
     case NE_EXPR:
+    case SPACESHIP_EXPR:
       if (code0 == VECTOR_TYPE && code1 == VECTOR_TYPE)
 	goto vector_compare;
       if ((complain & tf_warning)
@@ -4965,7 +4966,9 @@ cp_build_binary_op (const op_location_t &location,
 	  warn_for_null_address (location, op1, complain);
 	}
       else if ((code0 == POINTER_TYPE && code1 == POINTER_TYPE)
-	       || (TYPE_PTRDATAMEM_P (type0) && TYPE_PTRDATAMEM_P (type1)))
+	       || (code == SPACESHIP_EXPR
+		   ? TYPE_PTRMEM_P (type0) && TYPE_PTRMEM_P (type1)
+		   : TYPE_PTRDATAMEM_P (type0) && TYPE_PTRDATAMEM_P (type1)))
 	result_type = composite_pointer_type (location,
 					      type0, type1, op0, op1,
 					      CPO_COMPARISON, complain);
@@ -5358,6 +5361,55 @@ cp_build_binary_op (const op_location_t &location,
 				  location);
     }
 
+  if (code == SPACESHIP_EXPR)
+    {
+      iloc_sentinel s (location);
+
+      tree orig_type0 = TREE_TYPE (orig_op0);
+      tree_code orig_code0 = TREE_CODE (orig_type0);
+      tree orig_type1 = TREE_TYPE (orig_op1);
+      tree_code orig_code1 = TREE_CODE (orig_type1);
+      if ((orig_code0 == BOOLEAN_TYPE) != (orig_code1 == BOOLEAN_TYPE))
+	/* "If one of the operands is of type bool and the other is not, the
+	   program is ill-formed."  */
+	result_type = NULL_TREE;
+      else if (code0 == POINTER_TYPE && orig_code0 != POINTER_TYPE
+	       && code1 == POINTER_TYPE && orig_code1 != POINTER_TYPE)
+	/* We only do array/function-to-pointer conversion if "at least one of
+	   the operands is of pointer type".  */
+	result_type = NULL_TREE;
+      else if (orig_code0 == ENUMERAL_TYPE && orig_code1 == ENUMERAL_TYPE
+	       && !(same_type_ignoring_top_level_qualifiers_p
+		    (orig_type0, orig_type1)))
+	/* "If both operands have arithmetic types, or one operand has integral
+	   type and the other operand has unscoped enumeration type, the usual
+	   arithmetic conversions are applied to the operands."  So we don't do
+	   arithmetic conversions if the operands both have enumeral type.  */
+	result_type = NULL_TREE;
+
+      if (result_type)
+	build_type = spaceship_type (result_type, complain);
+
+      if (result_type && arithmetic_types_p)
+	{
+	  /* If a narrowing conversion is required, other than from an integral
+	     type to a floating point type, the program is ill-formed.  */
+	  bool ok = true;
+	  if (TREE_CODE (result_type) == REAL_TYPE
+	      && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op0)))
+	    /* OK */;
+	  else if (!check_narrowing (result_type, orig_op0, complain))
+	    ok = false;
+	  if (TREE_CODE (result_type) == REAL_TYPE
+	      && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op1)))
+	    /* OK */;
+	  else if (!check_narrowing (result_type, orig_op1, complain))
+	    ok = false;
+	  if (!ok && !(complain & tf_error))
+	    return error_mark_node;
+	}
+    }
+
   if (!result_type)
     {
       if (complain & tf_error)
diff --git a/gcc/testsuite/c-c++-common/cpp/spaceship-1.c b/gcc/testsuite/c-c++-common/cpp/spaceship-1.c
new file mode 100644
index 00000000000..a3dc38dd325
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/cpp/spaceship-1.c
@@ -0,0 +1,6 @@
+/* { dg-do preprocess } */
+/* { dg-options "-std=c11" { target c } } */
+
+#define A(x, y) x##y
+A(<=, >)	/* { dg-error "does not give a valid preprocessing token" "" { target { ! c++2a } } } */
+A(<=>, >)	/* { dg-error "does not give a valid preprocessing token" "" { target c++2a } } */
diff --git a/gcc/testsuite/g++.dg/cpp/spaceship-1.C b/gcc/testsuite/g++.dg/cpp/spaceship-1.C
new file mode 100644
index 00000000000..241b277c05b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp/spaceship-1.C
@@ -0,0 +1,8 @@
+// { dg-do compile { target c++17_down } }
+// { dg-options "-Wno-pointer-arith" }
+
+struct X {};
+bool operator<= (X, X);
+template<bool (X, X)> struct Y {};
+Y<&operator<=> y;
+bool foo (bool (*fn) (X, X), int n) { return n+&operator<=> fn; }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C
new file mode 100644
index 00000000000..9d008f19fea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C
@@ -0,0 +1,15 @@
+// { dg-do compile { target c++2a } }
+
+struct A
+{
+  int i;
+  bool operator==(A a) const { return i == a.i; }
+};
+
+struct B
+{
+  A a;
+  bool operator==(const B&) const = default; // { dg-error "A::operator==" }
+};
+
+constexpr bool x = B() == B();	// { dg-error "non-.constexpr" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C
new file mode 100644
index 00000000000..19a03fb04df
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C
@@ -0,0 +1,17 @@
+// { dg-do run { target c++2a } }
+
+struct D
+{
+  int i;
+  bool operator==(const D& x) const = default; // OK, returns x.i == y.i
+  bool operator!=(const D& z) const = default;  // OK, returns !(*this == z)
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+int main()
+{
+  D d{42};
+  assert (d == d);
+  assert (!(d != d));
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C
new file mode 100644
index 00000000000..7e98c472cd1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C
@@ -0,0 +1,24 @@
+// { dg-do run { target c++2a } }
+
+template <class T>
+struct D
+{
+  T i;
+  bool operator==(const D& x) const = default; // OK, returns x.i == y.i
+  bool operator!=(const D& z) const = default;  // OK, returns !(*this == z)
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+template <class T>
+void f()
+{
+  D<T> d{42};
+  assert (d == d);
+  assert (!(d != d));
+}
+
+int main()
+{
+  f<int>();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C
new file mode 100644
index 00000000000..06b988f6e57
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C
@@ -0,0 +1,12 @@
+// { dg-do compile { target c++2a } }
+
+struct D
+{
+  int i;
+  bool operator==(const D& x) const = default; // OK, returns x.i == y.i
+  bool operator!=(const D& z) const = default;  // OK, returns !(*this == z)
+};
+
+constexpr D d{42};
+static_assert (d == d);
+static_assert (!(d != d));
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C
new file mode 100644
index 00000000000..490726de56f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C
@@ -0,0 +1,16 @@
+// { dg-do compile { target c++2a } }
+
+struct A {
+  bool operator==(const A&) const;
+};
+
+struct D
+{
+  A i;
+  bool operator==(const D& x) const = default; // { dg-error "A::operator==" }
+  bool operator!=(const D& z) const = default; // { dg-error "D::operator==" }
+};
+
+constexpr D d{A()};
+static_assert (d == d);		// { dg-error "non-constant|constexpr" }
+static_assert (!(d != d));	// { dg-error "non-constant|constexpr" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C
new file mode 100644
index 00000000000..d89fc885e50
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C
@@ -0,0 +1,8 @@
+// { dg-do compile { target c++2a } }
+
+struct A {
+  int operator==(const A&) const = default; // { dg-error "return .bool" }
+  bool operator==(const A&, const A&) const = default; // { dg-error "exactly one" }
+  bool operator==(int) const = default; // { dg-error "parameter type" }
+  bool operator==(const A&) = default; // { dg-error "const" }
+};
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C
new file mode 100644
index 00000000000..ac24f366c94
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C
@@ -0,0 +1,10 @@
+// { dg-do compile { target c++2a } }
+
+struct A {
+  int &r;			// { dg-message "reference" }
+  bool operator==(const A&) const = default; // { dg-message "deleted" }
+};
+
+int i;
+A a { i };
+bool b = a == a;		// { dg-error "deleted" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C
new file mode 100644
index 00000000000..f804e133714
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C
@@ -0,0 +1,10 @@
+// { dg-do compile { target c++2a } }
+
+struct A
+{
+  union { int i; }			     // { dg-message "union" }
+  bool operator==(const A&) const = default; // { dg-message "deleted" }
+};
+
+A a { 42 };
+bool b = a == a;		// { dg-error "deleted" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C
new file mode 100644
index 00000000000..8112eaa4f80
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C
@@ -0,0 +1,10 @@
+// { dg-do compile { target c++2a } }
+
+union A
+{
+  int i;
+  bool operator==(const A&) const = default; // { dg-message "union" }
+};
+
+A a { 42 };
+bool b = a == a;		// { dg-error "deleted" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C
new file mode 100644
index 00000000000..ce7b56ce574
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C
@@ -0,0 +1,5 @@
+// Test that we suggest adding #include <compare>.
+// { dg-do compile { target c++2a } }
+
+auto x = 1<=>2;			// { dg-error "" }
+// { dg-message "<compare>" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C
new file mode 100644
index 00000000000..6461c6ab60a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C
@@ -0,0 +1,7 @@
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+template <class T, T x = (T() <=> T())> // { dg-error "31:0 <=> 0" }
+void f(T);
+//constexpr int f(...) { return 42; }
+constexpr int i = f(24);	//  { dg-error "no match" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C
new file mode 100644
index 00000000000..bb60302622f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C
@@ -0,0 +1,15 @@
+// This should continue to work.
+// { dg-do compile { target c++2a } }
+
+template<class T>
+struct A {
+  template<class U>
+  bool operator==(const A<U>&);
+};
+
+int main()
+{
+  A<int> a1;
+  A<void> a2;
+  return a1 == a2;
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C
new file mode 100644
index 00000000000..2ca86b748ff
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C
@@ -0,0 +1,93 @@
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while(0)
+
+void f(){}
+void g(){}
+
+int main()
+{
+  {
+    constexpr auto v = 1 <=> 2;
+    static_assert (__is_same_as (decltype (v), const std::strong_ordering));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+    static_assert (is_lt (v));
+    static_assert (is_lteq (v));
+    static_assert (!is_gt (v));
+    static_assert (!is_gteq (v));
+  }
+
+  {
+    enum E { a = 0 };
+    constexpr auto v = E::a <=> 1;
+    static_assert (__is_same_as (decltype (v), const std::strong_ordering));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+    static_assert (is_lt (v));
+    static_assert (is_lteq (v));
+    static_assert (!is_gt (v));
+    static_assert (!is_gteq (v));
+  }
+
+  {
+    enum class E { a, b };
+    constexpr auto v = E::a <=> E::b;
+    static_assert (__is_same_as (decltype (v), const std::strong_ordering));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+    static_assert (is_lt (v));
+    static_assert (is_lteq (v));
+    static_assert (!is_gt (v));
+    static_assert (!is_gteq (v));
+  }
+
+  {
+    int ar[2];
+    constexpr auto v = &ar[1] <=> &ar[0];
+    static_assert (__is_same_as (decltype (v), const std::strong_ordering));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+    static_assert (!is_lt (v));
+    static_assert (!is_lteq (v));
+    static_assert (is_gt (v));
+    static_assert (is_gteq (v));
+  }
+
+  {
+    constexpr auto v = 3.14 <=> 3.14;
+    static_assert (__is_same_as (decltype (v), const std::partial_ordering));
+    static_assert (is_eq (v));
+    static_assert (!is_neq (v));
+    static_assert (!is_lt (v));
+    static_assert (is_lteq (v));
+    static_assert (!is_gt (v));
+    static_assert (is_gteq (v));
+  }
+
+  {
+    // GCC doesn't consider &f == &g to be a constant expression (PR 69681)
+    const auto v = &f <=> &g;
+    static_assert (__is_same_as (decltype (v), const std::strong_equality));
+    assert (!is_eq (v));
+    assert (is_neq (v));
+  }
+
+  {
+    struct A { int i; int j; };
+    constexpr auto v = &A::i <=> &A::j;
+    static_assert (__is_same_as (decltype (v), const std::strong_equality));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+  }
+
+  {
+    struct A { void f(); };
+    constexpr auto v = &A::f <=> &A::f;
+    static_assert (__is_same_as (decltype (v), const std::strong_equality));
+    static_assert (is_eq (v));
+    static_assert (!is_neq (v));
+  }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C
new file mode 100644
index 00000000000..1dc95497fd9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C
@@ -0,0 +1,41 @@
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while(0)
+
+void f(){}
+void g(){}
+
+template <class T, class U, class R>
+constexpr bool check(T a, U b, R expected)
+{
+  auto r = a <=> b;
+  static_assert (__is_same_as (decltype (r), R));
+  return r == expected;
+}
+
+int main()
+{
+  static_assert (check (1, 2, std::strong_ordering::less));
+
+  enum E1 { a = 0 };
+  static_assert (check (a, 1, std::strong_ordering::less));
+
+  enum class E2 { a, b };
+  static_assert (check (E2::a, E2::b, std::strong_ordering::less));
+
+  int ar[2];
+  static_assert (check (&ar[1], &ar[0], std::strong_ordering::greater));
+
+  static_assert (check (3.14, 3.14, std::partial_ordering::equivalent));
+
+  // GCC doesn't consider &f == &g to be a constant expression (PR 69681)
+  assert (check (&f, &g, std::strong_equality::nonequal));
+
+  struct A { int i; int j; };
+  static_assert (check (&A::i, &A::j, std::strong_equality::nonequal));
+
+  struct A2 { void f(); };
+  static_assert (check (&A2::f, &A2::f, std::strong_equality::equal));
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C
new file mode 100644
index 00000000000..d3cb0a6b0f0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C
@@ -0,0 +1,11 @@
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+
+int main()
+{
+  { true <=> 1; }		// { dg-error "bool" }
+  { int a[2]; a <=> a; }	// { dg-error "2" }
+  { -1 <=> 1U; }		// { dg-error "narrowing" }
+  { enum A { a }; enum B { b }; a <=> b; } // { dg-error "A" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C
new file mode 100644
index 00000000000..20bc8e61455
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C
@@ -0,0 +1,21 @@
+// { dg-do run { target c++2a } }
+// { dg-options "-fext-numeric-literals" }
+
+#include <compare>
+
+int main()
+{
+  // GCC complex literal extension  
+  {
+    constexpr auto v = 1 <=> 1i;
+    static_assert (__is_same_as (decltype (v), const std::strong_equality));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+  }
+  {
+    constexpr auto v = 1i <=> 1.0i;
+    static_assert (__is_same_as (decltype (v), const std::weak_equality));
+    static_assert (is_eq (v));
+    static_assert (!is_neq (v));
+  }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C
new file mode 100644
index 00000000000..6a03f54b91a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C
@@ -0,0 +1,7 @@
+// { dg-do compile { target c++2a } }
+
+// missing #include <compare>
+template <class T, T x = (T() <=> T()) == 0>
+void f(T);
+constexpr int f(...) { return 42; }
+constexpr int i = f(24);
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C
new file mode 100644
index 00000000000..2a35de99e09
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C
@@ -0,0 +1,43 @@
+// Test with all operators explicitly defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+struct D
+{
+  int i;
+  auto operator<=>(const D& x) const = default;
+  bool operator==(const D& x) const = default;
+  bool operator!=(const D& x) const = default;
+  bool operator<(const D& x) const = default;
+  bool operator<=(const D& x) const = default;
+  bool operator>(const D& x) const = default;
+  bool operator>=(const D& x) const = default;
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+int main()
+{
+  D d{42};
+  D d2{24};
+
+  assert (is_eq (d <=> d));
+  assert (is_lteq (d <=> d));
+  assert (is_gteq (d <=> d));
+  assert (is_lt (d2 <=> d));
+  assert (is_lteq (d2 <=> d));
+  assert (is_gt (d <=> d2));
+  assert (is_gteq (d <=> d2));
+
+  assert (d == d);
+  assert (!(d2 == d));
+  assert (!(d == d2));
+  assert (d != d2);
+  assert (!(d2 != d2));
+
+  assert (d2 < d);
+  assert (d2 <= d);
+  assert (d > d2);
+  assert (d >= d2);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C
new file mode 100644
index 00000000000..32314579dcb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C
@@ -0,0 +1,113 @@
+// Test with all operators explicitly defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+template <class T>
+struct D
+{
+  T i;
+  auto operator<=>(const D& x) const = default;
+  bool operator==(const D& x) const = default;
+  bool operator!=(const D& x) const = default;
+  bool operator<(const D& x) const = default;
+  bool operator<=(const D& x) const = default;
+  bool operator>(const D& x) const = default;
+  bool operator>=(const D& x) const = default;
+};
+
+template <class T>
+struct E
+{
+  T i;
+  auto operator<=>(const E& x) const = default;
+  // auto operator==(const E& x) const = default;
+  // auto operator!=(const E& x) const = default;
+  // auto operator<(const E& x) const = default;
+  // auto operator<=(const E& x) const = default;
+  // auto operator>(const E& x) const = default;
+  // auto operator>=(const E& x) const = default;
+};
+
+template <class T>
+struct F
+{
+  T i;
+  constexpr auto operator<=>(T x) const { return i<=>x; }
+  constexpr bool operator== (T x) const { return i==x;  }
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+template <class T, class U>
+constexpr bool check_eq (T d, U d2)
+{
+  return is_eq (d <=> d2)
+    && is_eq (d2 <=> d)
+    && is_lteq (d <=> d2)
+    && is_lteq (d2 <=> d)
+    && !is_lt (d <=> d2)
+    && !is_lt (d2 <=> d)
+    && is_gteq (d <=> d2)
+    && is_gteq (d2 <=> d)
+    && !is_gt (d <=> d2)
+    && !is_gt (d2 <=> d)
+    && d == d2
+    && d2 == d
+    && !(d != d2)
+    && !(d2 != d)
+    && d >= d2
+    && d <= d2
+    && d2 >= d
+    && d2 <= d
+    && !(d < d2)
+    && !(d2 < d)
+    && !(d > d2)
+    && !(d2 > d);
+}
+
+template <class T, class U>
+constexpr bool check_less (T d, U d2)
+{
+  return !is_eq (d <=> d2)
+    && !is_eq (d2 <=> d)
+    && is_lteq (d <=> d2)
+    && !is_lteq (d2 <=> d)
+    && is_lt (d <=> d2)
+    && !is_lt (d2 <=> d)
+    && !is_gteq (d <=> d2)
+    && is_gteq (d2 <=> d)
+    && !is_gt (d <=> d2)
+    && is_gt (d2 <=> d)
+    && !(d == d2)
+    && !(d2 == d)
+    && (d != d2)
+    && (d2 != d)
+    && !(d >= d2)
+    && (d <= d2)
+    && (d2 >= d)
+    && !(d2 <= d)
+    && (d < d2)
+    && !(d2 < d)
+    && !(d > d2)
+    && (d2 > d);
+}
+
+int main()
+{
+  constexpr D<int> d{42};
+  constexpr D<int> d2{24};
+
+  static_assert (check_eq (d, d));
+  static_assert (check_less (d2, d));
+
+  constexpr E<float> e { 3.14 };
+  constexpr E<float> ee { 2.72 };
+  static_assert (check_eq (e, e));
+  static_assert (check_less (ee, e));
+
+  constexpr F<char> f { 'b' };
+  static_assert (check_eq (f, 'b'));
+  static_assert (check_less (f, 'c'));
+  static_assert (check_less ('a', f));
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C
new file mode 100644
index 00000000000..cf23c9771e0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C
@@ -0,0 +1,43 @@
+// Test with only spaceship defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+struct D
+{
+  int i;
+  auto operator<=>(const D& x) const = default;
+  // auto operator==(const D& x) const = default;
+  // auto operator!=(const D& x) const = default;
+  // auto operator<(const D& x) const = default;
+  // auto operator<=(const D& x) const = default;
+  // auto operator>(const D& x) const = default;
+  // auto operator>=(const D& x) const = default;
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+int main()
+{
+  D d{42};
+  D d2{24};
+
+  assert (is_eq (d <=> d));
+  assert (is_lteq (d <=> d));
+  assert (is_gteq (d <=> d));
+  assert (is_lt (d2 <=> d));
+  assert (is_lteq (d2 <=> d));
+  assert (is_gt (d <=> d2));
+  assert (is_gteq (d <=> d2));
+
+  assert (d == d);
+  assert (!(d2 == d));
+  assert (!(d == d2));
+  assert (d != d2);
+  assert (!(d2 != d2));
+
+  assert (d2 < d);
+  assert (d2 <= d);
+  assert (d > d2);
+  assert (d >= d2);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C
new file mode 100644
index 00000000000..0fc5aa2c9b8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C
@@ -0,0 +1,48 @@
+// Test for reversed candidates.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+struct D
+{
+  int i;
+  auto operator<=>(int x) const { return i<=>x; }
+  bool operator== (int x) const { return i==x;  }
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+int main()
+{
+  D d{42};
+  int d1 = 42;
+  int d2 = 24;
+
+  assert (is_eq (d <=> d1));
+  assert (is_eq (d1 <=> d));
+  assert (is_lteq (d <=> d1));
+  assert (is_lteq (d1 <=> d));
+  assert (is_gteq (d <=> d1));
+  assert (is_gteq (d1 <=> d));
+  assert (is_lt (d2 <=> d));
+  assert (is_lteq (d2 <=> d));
+  assert (is_gt (d <=> d2));
+  assert (is_gteq (d <=> d2));
+
+  assert (d == d1);
+  assert (d1 == d);
+  assert (!(d2 == d));
+  assert (!(d == d2));
+  assert (d != d2);
+  assert (d2 != d);
+  assert (!(d != d1));
+  assert (!(d1 != d));
+
+  assert (d2 < d);
+  assert (d2 <= d);
+  assert (d1 <= d);
+  assert (d > d2);
+  assert (d >= d2);
+  assert (d >= d1);
+  assert (d <= d1);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C
new file mode 100644
index 00000000000..89f84899fcc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C
@@ -0,0 +1,54 @@
+// Test for reversed candidates.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+struct D
+{
+  int i;
+  auto operator<=>(int x) const { return i<=>x; }
+  bool operator== (int x) const { return i==x;  }
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+template <class T>
+void f()
+{
+  D d{42};
+  int d1 = 42;
+  int d2 = 24;
+
+  assert (is_eq (d <=> d1));
+  assert (is_eq (d1 <=> d));
+  assert (is_lteq (d <=> d1));
+  assert (is_lteq (d1 <=> d));
+  assert (is_gteq (d <=> d1));
+  assert (is_gteq (d1 <=> d));
+  assert (is_lt (d2 <=> d));
+  assert (is_lteq (d2 <=> d));
+  assert (is_gt (d <=> d2));
+  assert (is_gteq (d <=> d2));
+
+  assert (d == d1);
+  assert (d1 == d);
+  assert (!(d2 == d));
+  assert (!(d == d2));
+  assert (d != d2);
+  assert (d2 != d);
+  assert (!(d != d1));
+  assert (!(d1 != d));
+
+  assert (d2 < d);
+  assert (d2 <= d);
+  assert (d1 <= d);
+  assert (d > d2);
+  assert (d >= d2);
+  assert (d >= d1);
+  assert (d <= d1);
+}
+
+int main()
+{
+  f<int>();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C
new file mode 100644
index 00000000000..1ff39549973
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C
@@ -0,0 +1,15 @@
+// Test explicit weak_ordering.
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+struct A
+{
+  int i;
+  std::weak_ordering operator<=> (const A&) const = default;
+};
+
+constexpr A a = { 42 };
+constexpr auto c = a <=> a;
+static_assert (std::same_as <decltype (c), const std::weak_ordering>);
+static_assert (std::is_eq (c));
+
diff --git a/gcc/testsuite/g++.dg/lookup/pr21802.C b/gcc/testsuite/g++.dg/lookup/pr21802.C
index 139f7b49c25..18b2219166a 100644
--- a/gcc/testsuite/g++.dg/lookup/pr21802.C
+++ b/gcc/testsuite/g++.dg/lookup/pr21802.C
@@ -60,6 +60,7 @@ struct Y : virtual X
   template <typename T>
   int operator&(T x) { return m + x + 1; }
   friend int operator==(Y o, int x) { return o.m + x + 1; }
+  int operator!=(int x) { return m + x + 1; }
 };
 
 /* The folloiwng "FooN" functions each contain a different way to call and to
@@ -81,7 +82,6 @@ Foo1 (T)
   { int t = x | I; assert (t == 7); }
   { int t = x && I; assert (t == 7); }
   { int t = x || I; assert (t == 7); }
-  { int t = x != I; assert (t == 7); }
   { int t = x < I; assert (t == 7); }
   { int t = x <= I; assert (t == 7); }
   { int t = x > I; assert (t == 7); }
@@ -104,6 +104,7 @@ Foo1 (T)
   { int t = x & I; assert (t == 8); }
   { int t = &x; assert (t == 8); }
   { int t = x == I; assert (t == 8); }
+  { int t = x != I; assert (t == 8); }
 }
 
 template <typename T>
@@ -204,7 +205,6 @@ Foo4 (T)
   { int t = x.operator| (I); assert (t == 7); }
   { int t = x.operator&& (I); assert (t == 7); }
   { int t = x.operator|| (I); assert (t == 7); }
-  { int t = x.operator!= (I); assert (t == 7); }
   { int t = x.operator< (I); assert (t == 7); }
   { int t = x.operator<= (I); assert (t == 7); }
   { int t = x.operator> (I); assert (t == 7); }
@@ -227,6 +227,7 @@ Foo4 (T)
   { int t = x.operator& (); assert (t == 8); }
   { int t = x.operator& (I); assert (t == 8); }
   { int t = operator== (x, I); assert (t == 8); }
+  { int t = x.operator!= (I); assert (t == 8); }
 }
 
 
diff --git a/gcc/testsuite/g++.old-deja/g++.robertl/eb22.C b/gcc/testsuite/g++.old-deja/g++.robertl/eb22.C
index 62dd18d0ccb..c0921bb43de 100644
--- a/gcc/testsuite/g++.old-deja/g++.robertl/eb22.C
+++ b/gcc/testsuite/g++.old-deja/g++.robertl/eb22.C
@@ -11,18 +11,17 @@ public:
         operator int() const {return 2;}
 };
 
-bool operator==(const MyInt& a, const int& b)   // { dg-message "operator==" } candidate
+bool operator==(const MyInt& a, const int& b) // { dg-message "operator==" "" { target c++17_down } }
 {
         return (int)a == b;
 }
 
-bool operator==(const MyInt& a, const MyInt& b) // { dg-message "operator==" } candidate
+bool operator==(const MyInt& a, const MyInt& b) // { dg-message "operator==" "" { target c++17_down } }
 {
         return (int)a == (int)b;
 }
 
 bool f()
 {
-  return 3 == MyInt();                          // { dg-error "ambiguous" "err" } 
-  // { dg-message "operator==" "match candidate text" { target *-*-* } .-1 }
+  return 3 == MyInt();	// { dg-error "ambiguous" "err" { target c++17_down } }
 }
diff --git a/libcpp/lex.c b/libcpp/lex.c
index 3e7d1c37ff5..e95eda3f44e 100644
--- a/libcpp/lex.c
+++ b/libcpp/lex.c
@@ -2980,7 +2980,13 @@ _cpp_lex_direct (cpp_reader *pfile)
 
       result->type = CPP_LESS;
       if (*buffer->cur == '=')
-	buffer->cur++, result->type = CPP_LESS_EQ;
+	{
+	  buffer->cur++, result->type = CPP_LESS_EQ;
+	  if (*buffer->cur == '>'
+	      && CPP_OPTION (pfile, cplusplus)
+	      && CPP_OPTION (pfile, lang) >= CLK_GNUCXX2A)
+	    buffer->cur++, result->type = CPP_SPACESHIP;
+	}
       else if (*buffer->cur == '<')
 	{
 	  buffer->cur++;
@@ -3491,6 +3497,7 @@ cpp_avoid_paste (cpp_reader *pfile, const cpp_token *token1,
 				|| (CPP_OPTION (pfile, objc)
 				    && token1->val.str.text[0] == '@'
 				    && (b == CPP_NAME || b == CPP_STRING)));
+    case CPP_LESS_EQ:	return c == '>';
     case CPP_STRING:
     case CPP_WSTRING:
     case CPP_UTF8STRING:
diff --git a/libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc b/libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc
index 338cf1ba32c..938b75e39d9 100644
--- a/libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc
+++ b/libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc
@@ -52,7 +52,7 @@ static_assert( ! std::regular<HasReference> );
 
 struct HasEq { };
 bool operator==(HasEq, HasEq) { return true; }
-#ifdef __cpp_lib_three_way_comparison
+#ifdef __cpp_impl_three_way_comparison
 static_assert( std::regular<HasEq> );
 #else
 static_assert( ! std::regular<HasEq> );
diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def
index 845a7e251f3..4e798e3b614 100644
--- a/gcc/cp/cp-tree.def
+++ b/gcc/cp/cp-tree.def
@@ -255,6 +255,7 @@ DEFTREECODE (IMPLICIT_CONV_EXPR, "implicit_conv_expr", tcc_unary, 1)
 DEFTREECODE (DOTSTAR_EXPR, "dotstar_expr", tcc_expression, 2)
 DEFTREECODE (TYPEID_EXPR, "typeid_expr", tcc_expression, 1)
 DEFTREECODE (NOEXCEPT_EXPR, "noexcept_expr", tcc_unary, 1)
+DEFTREECODE (SPACESHIP_EXPR, "spaceship_expr", tcc_expression, 2)
 
 /* A placeholder for an expression that is not type-dependent, but
    does occur in a template.  When an expression that is not
diff --git a/gcc/cp/operators.def b/gcc/cp/operators.def
index cc52e9bd967..ee0a4c17592 100644
--- a/gcc/cp/operators.def
+++ b/gcc/cp/operators.def
@@ -104,12 +104,16 @@ DEF_OPERATOR ("|", BIT_IOR_EXPR, "or", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("^", BIT_XOR_EXPR, "eo", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("<<", LSHIFT_EXPR, "ls", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR (">>", RSHIFT_EXPR, "rs", OVL_OP_FLAG_BINARY)
+
+/* defaultable_fn_check relies on the ordering of the comparison operators.  */
 DEF_OPERATOR ("==", EQ_EXPR, "eq", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("!=", NE_EXPR, "ne", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("<", LT_EXPR, "lt", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR (">", GT_EXPR, "gt", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("<=", LE_EXPR, "le", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR (">=", GE_EXPR, "ge", OVL_OP_FLAG_BINARY)
+DEF_OPERATOR ("<=>", SPACESHIP_EXPR, "ss", OVL_OP_FLAG_BINARY)
+
 DEF_OPERATOR ("&&", TRUTH_ANDIF_EXPR, "aa", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("||", TRUTH_ORIF_EXPR, "oo", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR (",", COMPOUND_EXPR, "cm", OVL_OP_FLAG_BINARY)
diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am
index 3e526dc14b7..49fd41360bc 100644
--- a/libstdc++-v3/include/Makefile.am
+++ b/libstdc++-v3/include/Makefile.am
@@ -1385,8 +1385,9 @@ endif
 # <new>, <typeinfo>, <exception>, <initializer_list>, <cstdalign>, <cstdarg>,
 # <concepts>, <cstdbool>, <type_traits>, <bit>, <atomic>,
 # and any files which they include (and which we provide).
-# <new>, <typeinfo>, <exception>, and <initializer_list> are installed by
-# libsupc++, so only the others and the sub-includes are copied here.
+# <new>, <typeinfo>, <exception>, <initializer_list> and <compare>
+# are installed by libsupc++, so only the others and the sub-includes
+# are copied here.
 install-freestanding-headers:
 	$(mkinstalldirs) $(DESTDIR)${gxx_include_dir}/bits
 	for file in c++0x_warning.h atomic_base.h concept_check.h move.h; do \
diff --git a/libstdc++-v3/libsupc++/Makefile.am b/libstdc++-v3/libsupc++/Makefile.am
index eec7b953514..8303f88ef41 100644
--- a/libstdc++-v3/libsupc++/Makefile.am
+++ b/libstdc++-v3/libsupc++/Makefile.am
@@ -31,7 +31,7 @@ toolexeclib_LTLIBRARIES = libsupc++.la
 noinst_LTLIBRARIES = libsupc++convenience.la
 
 std_HEADERS = \
-	cxxabi.h exception initializer_list new typeinfo
+	compare cxxabi.h exception initializer_list new typeinfo
 
 bits_HEADERS = \
 	atomic_lockfree_defines.h cxxabi_forced.h \
diff --git a/libstdc++-v3/include/std/functional b/libstdc++-v3/include/std/functional
index dad7781378b..b6a57ac9b54 100644
--- a/libstdc++-v3/include/std/functional
+++ b/libstdc++-v3/include/std/functional
@@ -66,6 +66,7 @@
 #endif
 #if __cplusplus > 201703L
 # include <bits/range_cmp.h>
+# include <compare>
 #endif
 
 namespace std _GLIBCXX_VISIBILITY(default)
diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
index 2df13696668..fa6d27467f7 100644
--- a/libstdc++-v3/include/std/version
+++ b/libstdc++-v3/include/std/version
@@ -187,6 +187,9 @@
 #define __cpp_lib_list_remove_return_type 201806L
 #define __cpp_lib_math_constants 201907L
 #define __cpp_lib_span 201902L
+#if __cpp_impl_three_way_comparison >= 201907L
+# define __cpp_lib_three_way_comparison 201711L
+#endif
 #define __cpp_lib_to_array 201907L
 #endif
 #endif // C++2a
diff --git a/libstdc++-v3/libsupc++/compare b/libstdc++-v3/libsupc++/compare
new file mode 100644
index 00000000000..379b2d48582
--- /dev/null
+++ b/libstdc++-v3/libsupc++/compare
@@ -0,0 +1,644 @@
+// -*- C++ -*- operator<=> three-way comparison support.
+
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of GCC.
+//
+// GCC is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3, or (at your option)
+// any later version.
+//
+// GCC is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+/** @file compare
+ *  This is a Standard C++ Library header.
+ */
+
+#ifndef _COMPARE
+#define _COMPARE
+
+#pragma GCC system_header
+
+#if __cplusplus > 201703L && __cpp_impl_three_way_comparison >= 201907L
+
+#pragma GCC visibility push(default)
+
+#include <concepts>
+
+namespace std
+{
+#define __cpp_lib_three_way_comparison 201711L
+
+  // [cmp.categories], comparison category types
+
+  namespace __cmp_cat
+  {
+    enum class _Eq
+    { equal = 0, equivalent = equal, nonequal = 1, nonequivalent = nonequal };
+
+    enum class _Ord { _Less = -1, _Greater = 1 };
+
+    enum class _Ncmp { _Unordered = -127 };
+
+    struct __unspec
+    {
+      constexpr __unspec(__unspec*) { }
+    };
+  }
+
+  class weak_equality
+  {
+    int _M_value;
+
+    constexpr explicit
+    weak_equality(__cmp_cat::_Eq __val) noexcept
+    : _M_value(int(__val))
+    { }
+
+  public:
+    // valid values
+
+    static const weak_equality equivalent;
+    static const weak_equality nonequivalent;
+
+    // comparisons
+
+    friend constexpr bool
+    operator==(weak_equality __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(weak_equality, weak_equality) noexcept = default;
+
+    friend constexpr weak_equality
+    operator<=>(weak_equality __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr weak_equality
+    operator<=>(__cmp_cat::__unspec, weak_equality __v) noexcept
+    { return __v; }
+  };
+
+  // valid values' definitions
+  inline constexpr weak_equality
+  weak_equality::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr weak_equality
+  weak_equality::nonequivalent(__cmp_cat::_Eq::nonequivalent);
+
+  class strong_equality
+  {
+    int _M_value;
+
+    constexpr explicit
+    strong_equality(__cmp_cat::_Eq __val) noexcept
+    : _M_value(int(__val))
+    { }
+
+  public:
+    // valid values
+
+    static const strong_equality equal;
+    static const strong_equality nonequal;
+    static const strong_equality equivalent;
+    static const strong_equality nonequivalent;
+
+    // conversion
+    constexpr operator weak_equality() const noexcept
+    {
+      if (_M_value == 0)
+	return weak_equality::equivalent;
+      else
+	return weak_equality::nonequivalent;
+    }
+
+    // comparisons
+
+    friend constexpr bool
+    operator==(strong_equality __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(strong_equality, strong_equality) noexcept = default;
+
+    friend constexpr strong_equality
+    operator<=>(strong_equality __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr strong_equality
+    operator<=>(__cmp_cat::__unspec, strong_equality __v) noexcept
+    { return __v; }
+  };
+
+  // valid values' definitions
+  inline constexpr strong_equality
+  strong_equality::equal(__cmp_cat::_Eq::equal);
+
+  inline constexpr strong_equality
+  strong_equality::nonequal(__cmp_cat::_Eq::nonequal);
+
+  inline constexpr strong_equality
+  strong_equality::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr strong_equality
+  strong_equality::nonequivalent(__cmp_cat::_Eq::nonequivalent);
+
+  class partial_ordering
+  {
+    int _M_value;
+    bool _M_is_ordered;
+
+    constexpr explicit
+    partial_ordering(__cmp_cat::_Eq __v) noexcept
+    : _M_value(int(__v)), _M_is_ordered(true)
+    { }
+
+    constexpr explicit
+    partial_ordering(__cmp_cat::_Ord __v) noexcept
+    : _M_value(int(__v)), _M_is_ordered(true)
+    { }
+
+    constexpr explicit
+    partial_ordering(__cmp_cat::_Ncmp __v) noexcept
+    : _M_value(int(__v)), _M_is_ordered(false)
+    { }
+
+  public:
+    // valid values
+    static const partial_ordering less;
+    static const partial_ordering equivalent;
+    static const partial_ordering greater;
+    static const partial_ordering unordered;
+
+    // conversion
+    constexpr operator weak_equality() const noexcept
+    {
+      if (_M_value == 0)
+	return weak_equality::equivalent;
+      else
+	return weak_equality::nonequivalent;
+    }
+
+    // comparisons
+    friend constexpr bool
+    operator==(partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(partial_ordering, partial_ordering) noexcept = default;
+
+    friend constexpr bool
+    operator< (partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value < 0; }
+
+    friend constexpr bool
+    operator> (partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value > 0; }
+
+    friend constexpr bool
+    operator<=(partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value <= 0; }
+
+    friend constexpr bool
+    operator>=(partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value >= 0; }
+
+    friend constexpr bool
+    operator< (__cmp_cat::__unspec, partial_ordering __v) noexcept
+    { return __v._M_is_ordered && 0 < __v._M_value; }
+
+    friend constexpr bool
+    operator> (__cmp_cat::__unspec, partial_ordering __v) noexcept
+    { return __v._M_is_ordered && 0 > __v._M_value; }
+
+    friend constexpr bool
+    operator<=(__cmp_cat::__unspec, partial_ordering __v) noexcept
+    { return __v._M_is_ordered && 0 <= __v._M_value; }
+
+    friend constexpr bool
+    operator>=(__cmp_cat::__unspec, partial_ordering __v) noexcept
+    { return __v._M_is_ordered && 0 >= __v._M_value; }
+
+    friend constexpr partial_ordering
+    operator<=>(partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr partial_ordering
+    operator<=>(__cmp_cat::__unspec, partial_ordering __v) noexcept
+    {
+      if (__v < 0)
+	return  partial_ordering::greater;
+      else if (__v > 0)
+	return partial_ordering::less;
+      else
+	return __v;
+    }
+  };
+
+  // valid values' definitions
+  inline constexpr partial_ordering
+  partial_ordering::less(__cmp_cat::_Ord::_Less);
+
+  inline constexpr partial_ordering
+  partial_ordering::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr partial_ordering
+  partial_ordering::greater(__cmp_cat::_Ord::_Greater);
+
+  inline constexpr partial_ordering
+  partial_ordering::unordered(__cmp_cat::_Ncmp::_Unordered);
+
+  class weak_ordering
+  {
+    int _M_value;
+
+    constexpr explicit
+    weak_ordering(__cmp_cat::_Eq __v) noexcept : _M_value(int(__v))
+    { }
+
+    constexpr explicit
+    weak_ordering(__cmp_cat::_Ord __v) noexcept : _M_value(int(__v))
+    { }
+
+  public:
+    // valid values
+    static const weak_ordering less;
+    static const weak_ordering equivalent;
+    static const weak_ordering greater;
+
+    // conversions
+    constexpr operator weak_equality() const noexcept
+    {
+      if (_M_value == 0)
+	return weak_equality::equivalent;
+      else
+	return weak_equality::nonequivalent;
+    }
+
+    constexpr operator partial_ordering() const noexcept
+    {
+      if (_M_value == 0)
+	return partial_ordering::equivalent;
+      else if (_M_value < 0)
+	return partial_ordering::less;
+      else
+	return partial_ordering::greater;
+    }
+
+    // comparisons
+    friend constexpr bool
+    operator==(weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(weak_ordering, weak_ordering) noexcept = default;
+
+    friend constexpr bool
+    operator< (weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value < 0; }
+
+    friend constexpr bool
+    operator> (weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value > 0; }
+
+    friend constexpr bool
+    operator<=(weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value <= 0; }
+
+    friend constexpr bool
+    operator>=(weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value >= 0; }
+
+    friend constexpr bool
+    operator< (__cmp_cat::__unspec, weak_ordering __v) noexcept
+    { return 0 < __v._M_value; }
+
+    friend constexpr bool
+    operator> (__cmp_cat::__unspec, weak_ordering __v) noexcept
+    { return 0 > __v._M_value; }
+
+    friend constexpr bool
+    operator<=(__cmp_cat::__unspec, weak_ordering __v) noexcept
+    { return 0 <= __v._M_value; }
+
+    friend constexpr bool
+    operator>=(__cmp_cat::__unspec, weak_ordering __v) noexcept
+    { return 0 >= __v._M_value; }
+
+    friend constexpr weak_ordering
+    operator<=>(weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr weak_ordering
+    operator<=>(__cmp_cat::__unspec, weak_ordering __v) noexcept
+    {
+      if (__v < 0)
+	return  weak_ordering::greater;
+      else if (__v > 0)
+	return weak_ordering::less;
+      else
+	return __v;
+    }
+  };
+
+  // valid values' definitions
+  inline constexpr weak_ordering
+  weak_ordering::less(__cmp_cat::_Ord::_Less);
+
+  inline constexpr weak_ordering
+  weak_ordering::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr weak_ordering
+  weak_ordering::greater(__cmp_cat::_Ord::_Greater);
+
+  class strong_ordering
+  {
+    int _M_value;
+
+    constexpr explicit
+    strong_ordering(__cmp_cat::_Eq __v) noexcept
+    : _M_value(int(__v))
+    { }
+
+    constexpr explicit
+    strong_ordering(__cmp_cat::_Ord __v) noexcept
+    : _M_value(int(__v))
+    { }
+
+  public:
+    // valid values
+    static const strong_ordering less;
+    static const strong_ordering equal;
+    static const strong_ordering equivalent;
+    static const strong_ordering greater;
+
+    // conversions
+    constexpr operator weak_equality() const noexcept
+    {
+      if (_M_value == 0)
+	return weak_equality::equivalent;
+      else
+	return weak_equality::nonequivalent;
+    }
+
+    constexpr operator strong_equality() const noexcept
+    {
+      if (_M_value == 0)
+	return strong_equality::equal;
+      else
+	return strong_equality::nonequal;
+    }
+
+    constexpr operator partial_ordering() const noexcept
+    {
+      if (_M_value == 0)
+	return partial_ordering::equivalent;
+      else if (_M_value < 0)
+	return partial_ordering::less;
+      else
+	return partial_ordering::greater;
+    }
+
+    constexpr operator weak_ordering() const noexcept
+    {
+      if (_M_value == 0)
+	return weak_ordering::equivalent;
+      else if (_M_value < 0)
+	return weak_ordering::less;
+      else
+	return weak_ordering::greater;
+    }
+
+    // comparisons
+    friend constexpr bool
+    operator==(strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(strong_ordering, strong_ordering) noexcept = default;
+
+    friend constexpr bool
+    operator< (strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value < 0; }
+
+    friend constexpr bool
+    operator> (strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value > 0; }
+
+    friend constexpr bool
+    operator<=(strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value <= 0; }
+
+    friend constexpr bool
+    operator>=(strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value >= 0; }
+
+    friend constexpr bool
+    operator< (__cmp_cat::__unspec, strong_ordering __v) noexcept
+    { return 0 < __v._M_value; }
+
+    friend constexpr bool
+    operator> (__cmp_cat::__unspec, strong_ordering __v) noexcept
+    { return 0 > __v._M_value; }
+
+    friend constexpr bool
+    operator<=(__cmp_cat::__unspec, strong_ordering __v) noexcept
+    { return 0 <= __v._M_value; }
+
+    friend constexpr bool
+    operator>=(__cmp_cat::__unspec, strong_ordering __v) noexcept
+    { return 0 >= __v._M_value; }
+
+    friend constexpr strong_ordering
+    operator<=>(strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr strong_ordering
+    operator<=>(__cmp_cat::__unspec, strong_ordering __v) noexcept
+    {
+      if (__v < 0)
+	return  strong_ordering::greater;
+      else if (__v > 0)
+	return strong_ordering::less;
+      else
+	return __v;
+    }
+  };
+
+  // valid values' definitions
+  inline constexpr strong_ordering
+  strong_ordering::less(__cmp_cat::_Ord::_Less);
+
+  inline constexpr strong_ordering
+  strong_ordering::equal(__cmp_cat::_Eq::equal);
+
+  inline constexpr strong_ordering
+  strong_ordering::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr strong_ordering
+  strong_ordering::greater(__cmp_cat::_Ord::_Greater);
+
+
+  // named comparison functions
+  constexpr bool
+  is_eq(weak_equality __cmp) noexcept
+  { return __cmp == 0; }
+
+  constexpr bool
+  is_neq(weak_equality __cmp) noexcept
+  { return __cmp != 0; }
+
+  constexpr bool
+  is_lt  (partial_ordering __cmp) noexcept
+  { return __cmp < 0; }
+
+  constexpr bool
+  is_lteq(partial_ordering __cmp) noexcept
+  { return __cmp <= 0; }
+
+  constexpr bool
+  is_gt  (partial_ordering __cmp) noexcept
+  { return __cmp > 0; }
+
+  constexpr bool
+  is_gteq(partial_ordering __cmp) noexcept
+  { return __cmp >= 0; }
+
+  // [cmp.common], common comparison category type
+  template<typename... _Ts>
+    struct common_comparison_category {
+      // using type = TODO
+    };
+
+  template<typename... _Ts>
+    using common_comparison_category_t
+      = typename common_comparison_category<_Ts...>::type;
+
+#if __cpp_concepts
+  namespace __detail
+  {
+    template<typename _Tp, typename _Cat>
+      concept __compares_as
+	= same_as<common_comparison_category_t<_Tp, _Cat>, _Cat>;
+
+    template<typename _Tp, typename _Up>
+      concept __partially_ordered_with
+	= requires(const remove_reference_t<_Tp>& __t,
+		   const remove_reference_t<_Up>& __u) {
+	  { __t <  __u } -> boolean;
+	  { __t >  __u } -> boolean;
+	  { __t <= __u } -> boolean;
+	  { __t >= __u } -> boolean;
+	  { __u <  __t } -> boolean;
+	  { __u >  __t } -> boolean;
+	  { __u <= __t } -> boolean;
+	  { __u >= __t } -> boolean;
+	};
+  } // namespace __detail
+
+  // [cmp.concept], concept three_way_comparable
+  template<typename _Tp, typename _Cat = partial_ordering>
+    concept three_way_comparable
+      = __detail::__weakly_eq_cmp_with<_Tp, _Tp>
+      && (!convertible_to<_Cat, partial_ordering>
+	  || __detail::__partially_ordered_with<_Tp, _Tp>)
+      && requires(const remove_reference_t<_Tp>& __a,
+		  const remove_reference_t<_Tp>& __b) {
+	{ __a <=> __b } -> __detail::__compares_as<_Cat>;
+      };
+
+  template<typename _Tp, typename _Up, typename _Cat = partial_ordering>
+    concept three_way_comparable_with
+      = __detail::__weakly_eq_cmp_with<_Tp, _Up>
+      && (!convertible_to<_Cat, partial_ordering>
+	  || __detail::__partially_ordered_with<_Tp, _Up>)
+      && three_way_comparable<_Tp, _Cat>
+      && three_way_comparable<_Up, _Cat>
+      && common_reference_with<const remove_reference_t<_Tp>&,
+			       const remove_reference_t<_Up>&>
+      && three_way_comparable<
+	  common_reference_t<const remove_reference_t<_Tp>&,
+			     const remove_reference_t<_Up>&>, _Cat>
+      && requires(const remove_reference_t<_Tp>& __t,
+		  const remove_reference_t<_Up>& __u) {
+	{ __t <=> __u } -> __detail::__compares_as<_Cat>;
+	{ __u <=> __t } -> __detail::__compares_as<_Cat>;
+      };
+#endif
+
+  template<typename _Tp, typename _Up>
+    using __cmp2way_res_t
+      = decltype(std::declval<_Tp&>() <=> std::declval<_Up&>());
+
+  template<typename _Tp, typename _Up = _Tp, typename = void>
+    struct __cmp3way_helper
+    { };
+
+  template<typename _Tp, typename _Up>
+    struct __cmp3way_helper<_Tp, _Up, void_t<__cmp2way_res_t<_Tp, _Up>>>
+    {
+      using type = __cmp2way_res_t<_Tp, _Up>;
+      using __type = type;
+    };
+
+  /// [cmp.result], result of three-way comparison
+  template<typename _Tp, typename _Up = _Tp>
+    struct compare_three_way_result
+    : __cmp3way_helper<_Tp, _Up>
+    { };
+
+  template<typename _Tp, typename _Up = _Tp>
+    using compare_three_way_result_t
+      = typename compare_three_way_result<_Tp, _Up>::__type;
+
+  // [cmp.object], typename compare_three_way
+  struct compare_three_way
+  {
+    // TODO
+#if 0
+    template<typename _Tp, typename _Up>
+      requires (three_way_comparable_with<_Tp, _Up>
+	  || BUILTIN-PTR-THREE-WAY(_Tp, _Up))
+    constexpr auto
+    operator()(_Tp&& __t, _Up&& __u) const noexcept
+    {
+      // TODO
+    }
+#endif
+
+    using is_transparent = void;
+  };
+
+  // [cmp.alg], comparison algorithms
+  inline namespace __cmp_alg
+  {
+    // TODO
+#if 0
+    inline constexpr unspecified strong_order = unspecified;
+    inline constexpr unspecified weak_order = unspecified;
+    inline constexpr unspecified partial_order = unspecified;
+    inline constexpr unspecified compare_strong_order_fallback = unspecified;
+    inline constexpr unspecified compare_weak_order_fallback = unspecified;
+    inline constexpr unspecified compare_partial_order_fallback = unspecified;
+#endif
+  }
+}
+
+#pragma GCC visibility pop
+
+#endif // C++20
+
+#endif // _COMPARE
