public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c: Implement C23 nullptr (N3042)
@ 2022-08-13 21:35 Marek Polacek
  2022-08-15 17:48 ` Joseph Myers
  2022-08-15 20:03 ` [PATCH] " Jason Merrill
  0 siblings, 2 replies; 9+ messages in thread
From: Marek Polacek @ 2022-08-13 21:35 UTC (permalink / raw)
  To: GCC Patches, Joseph Myers, Jason Merrill

This patch implements the C23 nullptr literal:
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm>, which is
intended to replace the problematic definition of NULL which might be
either of integer type or void*.

Since C++ has had nullptr for over a decade now, it was relatively easy
to just move the built-in node definitions from the C++ FE to the C/C++
common code.  Also, our DWARF emitter already handles NULLPTR_TYPE by
emitting DW_TAG_unspecified_type.  However, I had to handle a lot of
contexts such as ?:, comparison, conversion, etc.

There are some minor differences, e.g. in C you can do

  bool b = nullptr;

but in C++ you have to use direct-initialization:

  bool b{nullptr};

And I think that

  nullptr_t n = 0;

is only valid in C++.

Of course, C doesn't have to handle mangling, RTTI, substitution,
overloading, ...

This patch also defines nullptr_t in <stddef.h>.  I'm uncertain about
the __STDC_VERSION__ version I should be checking.  Also, I'm not
defining __STDC_VERSION_STDDEF_H__ yet, because I don't know what value
it should be defined to.  Do we know yet?

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

gcc/c-family/ChangeLog:

	* c-common.cc (c_common_reswords): Enable nullptr in C.
	(c_common_nodes_and_builtins): Create the built-in node for nullptr.
	* c-common.h (enum c_tree_index): Add CTI_NULLPTR, CTI_NULLPTR_TYPE.
	(nullptr_node): Define.
	(nullptr_type_node): Define.
	(NULLPTR_TYPE_P): Define.
	* c-pretty-print.cc (c_pretty_printer::simple_type_specifier): Handle
	NULLPTR_TYPE.
	(c_pretty_printer::direct_abstract_declarator): Likewise.
	(c_pretty_printer::constant): Likewise.

gcc/c/ChangeLog:

	* c-convert.cc (c_convert) <case POINTER_TYPE>: Handle NULLPTR_TYPE.
	Give a better diagnostic when converting to nullptr_t.
	* c-decl.cc (c_init_decl_processing): Perform C-specific nullptr
	initialization.
	* c-parser.cc (c_parser_postfix_expression): Handle RID_NULLPTR.
	* c-typeck.cc (null_pointer_constant_p): Return true for NULLPTR_TYPE_P.
	(build_unary_op) <case TRUTH_NOT_EXPR>: Handle NULLPTR_TYPE.
	(build_conditional_expr): Handle the case when the second/third operand
	is NULLPTR_TYPE and third/second operand is POINTER_TYPE.
	(convert_for_assignment): Handle converting an expression of type
	nullptr_t to pointer/bool.
	(build_binary_op) <case TRUTH_XOR_EXPR>: Handle NULLPTR_TYPE.
	<case EQ_EXPR>: Likewise.

gcc/cp/ChangeLog:

	* cp-tree.h (enum cp_tree_index): Remove CTI_NULLPTR, CTI_NULLPTR_TYPE.
	Move it to c_tree_index.
	(nullptr_node): No longer define here.
	(nullptr_type_node): Likewise.
	(NULLPTR_TYPE_P): Likewise.
	* decl.cc (cxx_init_decl_processing): Only keep C++-specific nullptr
	initialization; move the shared code to c_common_nodes_and_builtins.

gcc/ChangeLog:

	* ginclude/stddef.h: Define nullptr_t.

gcc/testsuite/ChangeLog:

	* gcc.dg/Wcxx-compat-2.c: Remove nullptr test.
	* gcc.dg/c2x-nullptr-1.c: New test.
	* gcc.dg/c2x-nullptr-2.c: New test.
	* gcc.dg/c2x-nullptr-3.c: New test.
	* gcc.dg/c2x-nullptr-4.c: New test.
	* gcc.dg/c2x-nullptr-5.c: New test.
---
 gcc/c-family/c-common.cc             |  13 +-
 gcc/c-family/c-common.h              |   8 +
 gcc/c-family/c-pretty-print.cc       |   7 +
 gcc/c/c-convert.cc                   |  19 ++-
 gcc/c/c-decl.cc                      |   6 +
 gcc/c/c-parser.cc                    |   8 +
 gcc/c/c-typeck.cc                    |  55 +++++-
 gcc/cp/cp-tree.h                     |   8 -
 gcc/cp/decl.cc                       |   8 +-
 gcc/ginclude/stddef.h                |   8 +
 gcc/testsuite/gcc.dg/Wcxx-compat-2.c |   1 -
 gcc/testsuite/gcc.dg/c2x-nullptr-1.c | 239 +++++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/c2x-nullptr-2.c |   9 +
 gcc/testsuite/gcc.dg/c2x-nullptr-3.c |  62 +++++++
 gcc/testsuite/gcc.dg/c2x-nullptr-4.c |  10 ++
 gcc/testsuite/gcc.dg/c2x-nullptr-5.c |  11 ++
 16 files changed, 448 insertions(+), 24 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-2.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-3.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-4.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-5.c

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 6e41ceb38e9..809e7ff5804 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -500,7 +500,7 @@ const struct c_common_resword c_common_reswords[] =
   { "namespace",	RID_NAMESPACE,	D_CXXONLY | D_CXXWARN },
   { "new",		RID_NEW,	D_CXXONLY | D_CXXWARN },
   { "noexcept",		RID_NOEXCEPT,	D_CXXONLY | D_CXX11 | D_CXXWARN },
-  { "nullptr",		RID_NULLPTR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
+  { "nullptr",		RID_NULLPTR,	D_CXX11 | D_CXXWARN },
   { "operator",		RID_OPERATOR,	D_CXXONLY | D_CXXWARN },
   { "private",		RID_PRIVATE,	D_CXX_OBJC | D_CXXWARN },
   { "protected",	RID_PROTECTED,	D_CXX_OBJC | D_CXXWARN },
@@ -4723,6 +4723,17 @@ c_common_nodes_and_builtins (void)
   null_node = make_int_cst (1, 1);
   TREE_TYPE (null_node) = c_common_type_for_size (POINTER_SIZE, 0);
 
+  /* Create the built-in nullptr node.  This part of its initialization is
+     common to C and C++.  The front ends can further adjust its definition
+     in {c,cxx}_init_decl_processing.  */
+  nullptr_type_node = make_node (NULLPTR_TYPE);
+  TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
+  TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
+  TYPE_UNSIGNED (nullptr_type_node) = 1;
+  TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
+  SET_TYPE_MODE (nullptr_type_node, ptr_mode);
+  nullptr_node = build_int_cst (nullptr_type_node, 0);
+
   /* Since builtin_types isn't gc'ed, don't export these nodes.  */
   memset (builtin_types, 0, sizeof (builtin_types));
 }
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c06769b6f0b..d30174334c2 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -375,6 +375,8 @@ enum c_tree_index
     CTI_DEFAULT_FUNCTION_TYPE,
 
     CTI_NULL,
+    CTI_NULLPTR,
+    CTI_NULLPTR_TYPE,
 
     /* These are not types, but we have to look them up all the time.  */
     CTI_FUNCTION_NAME_DECL,
@@ -534,6 +536,9 @@ extern const unsigned int num_c_common_reswords;
 
 /* The node for C++ `__null'.  */
 #define null_node                       c_global_trees[CTI_NULL]
+/* The nodes for `nullptr'.  */
+#define nullptr_node                    c_global_trees[CTI_NULLPTR]
+#define nullptr_type_node               c_global_trees[CTI_NULLPTR_TYPE]
 
 extern GTY(()) tree c_global_trees[CTI_MAX];
 
@@ -1009,6 +1014,9 @@ extern void c_parse_final_cleanups (void);
 #define DECL_UNNAMED_BIT_FIELD(NODE) \
   (DECL_C_BIT_FIELD (NODE) && !DECL_NAME (NODE))
 
+/* True iff TYPE is cv decltype(nullptr).  */
+#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
+
 extern tree do_case (location_t, tree, tree);
 extern tree build_stmt (location_t, enum tree_code, ...);
 extern tree build_real_imag_expr (location_t, enum tree_code, tree);
diff --git a/gcc/c-family/c-pretty-print.cc b/gcc/c-family/c-pretty-print.cc
index 71a0cb51093..efa1768f4d6 100644
--- a/gcc/c-family/c-pretty-print.cc
+++ b/gcc/c-family/c-pretty-print.cc
@@ -321,6 +321,7 @@ pp_c_pointer (c_pretty_printer *pp, tree t)
       _Bool                          -- C99
       _Complex                       -- C99
       _Imaginary                     -- C99
+      nullptr_t                      -- C23
       struct-or-union-specifier
       enum-specifier
       typedef-name.
@@ -424,6 +425,9 @@ c_pretty_printer::simple_type_specifier (tree t)
       else
 	translate_string ("<anonymous>");
       break;
+    case NULLPTR_TYPE:
+      pp_c_ws_string (this, "nullptr_t");
+      break;
 
     default:
       pp_unsupported_tree (this, t);
@@ -678,6 +682,7 @@ c_pretty_printer::direct_abstract_declarator (tree t)
     case COMPLEX_TYPE:
     case TYPE_DECL:
     case ERROR_MARK:
+    case NULLPTR_TYPE:
       break;
 
     default:
@@ -1219,6 +1224,8 @@ c_pretty_printer::constant (tree e)
 	  pp_c_character_constant (this, e);
 	else if (TREE_CODE (type) == ENUMERAL_TYPE)
 	  pp_c_enumeration_constant (this, e);
+	else if (NULLPTR_TYPE_P (type))
+	  pp_string (this, "nullptr");
 	else
 	  pp_c_integer_constant (this, e);
       }
diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
index 18083d59618..013fe6b2a53 100644
--- a/gcc/c/c-convert.cc
+++ b/gcc/c/c-convert.cc
@@ -133,6 +133,14 @@ c_convert (tree type, tree expr, bool init_const)
 	(loc, type, c_objc_common_truthvalue_conversion (input_location, expr));
 
     case POINTER_TYPE:
+      /* The type nullptr_t may be converted to a pointer type.  The result is
+	 a null pointer value.  */
+      if (NULLPTR_TYPE_P (TREE_TYPE (e)))
+	{
+	  ret = build_int_cst (type, 0);
+	  goto maybe_fold;
+	}
+      gcc_fallthrough ();
     case REFERENCE_TYPE:
       ret = convert_to_pointer (type, e);
       goto maybe_fold;
@@ -180,7 +188,16 @@ c_convert (tree type, tree expr, bool init_const)
       return ret;
     }
 
-  error ("conversion to non-scalar type requested");
+  /* If we are converting to nullptr_t, don't say "non-scalar type" because
+     the nullptr_t type is a scalar type.  Only nullptr_t shall be converted
+     to nullptr_t.  */
+  if (code == NULLPTR_TYPE)
+    {
+      error ("conversion from %qT to %qT", TREE_TYPE (e), type);
+      inform (input_location, "only %qT can be converted to %qT", type, type);
+    }
+  else
+    error ("conversion to non-scalar type requested");
   return error_mark_node;
 }
 
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index ae8990c138f..ac4394c9dc5 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -4531,6 +4531,12 @@ c_init_decl_processing (void)
   pushdecl (build_decl (UNKNOWN_LOCATION, TYPE_DECL, get_identifier ("_Bool"),
 			boolean_type_node));
 
+  /* C-specific nullptr initialization.  */
+  record_builtin_type (RID_MAX, "nullptr_t", nullptr_type_node);
+  /* The size and alignment of nullptr_t is the same as for a pointer to
+     character type.  */
+  SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
+
   input_location = save_loc;
 
   make_fname_decl = c_make_fname_decl;
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 759f200a7eb..2af3a614fb9 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -10243,6 +10243,14 @@ c_parser_postfix_expression (c_parser *parser)
 			 "%<depend%> clause");
 	  expr.set_error ();
 	  break;
+	/* C23 'nullptr' literal.  */
+	case RID_NULLPTR:
+	  c_parser_consume_token (parser);
+	  expr.value = nullptr_node;
+	  set_c_expr_source_range (&expr, tok_range);
+	  pedwarn_c11 (loc, OPT_Wpedantic,
+		       "ISO C does not support %qs before C2X", "nullptr");
+	  break;
 	default:
 	  c_parser_error (parser, "expected expression");
 	  expr.set_error ();
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index d37de2a313b..2ba48345a1b 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -133,6 +133,13 @@ null_pointer_constant_p (const_tree expr)
   /* This should really operate on c_expr structures, but they aren't
      yet available everywhere required.  */
   tree type = TREE_TYPE (expr);
+
+  /* An integer constant expression with the value 0, such an expression
+     cast to type void*, or the predefined constant nullptr, are a null
+     pointer constant.  */
+  if (NULLPTR_TYPE_P (type))
+    return true;
+
   return (TREE_CODE (expr) == INTEGER_CST
 	  && !TREE_OVERFLOW (expr)
 	  && integer_zerop (expr)
@@ -4575,7 +4582,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg,
     case TRUTH_NOT_EXPR:
       if (typecode != INTEGER_TYPE && typecode != FIXED_POINT_TYPE
 	  && typecode != REAL_TYPE && typecode != POINTER_TYPE
-	  && typecode != COMPLEX_TYPE)
+	  && typecode != COMPLEX_TYPE && typecode != NULLPTR_TYPE)
 	{
 	  error_at (location,
 		    "wrong type argument to unary exclamation mark");
@@ -5515,6 +5522,13 @@ build_conditional_expr (location_t colon_loc, tree ifexp, bool ifexp_bcp,
 	}
       result_type = type2;
     }
+  /* 6.5.15: "if one is a null pointer constant (other than a pointer) or has
+     type nullptr_t and the other is a pointer, the result type is the pointer
+     type."  */
+  else if (code1 == NULLPTR_TYPE && code2 == POINTER_TYPE)
+    result_type = type2;
+  else if (code1 == POINTER_TYPE && code2 == NULLPTR_TYPE)
+    result_type = type1;
 
   if (!result_type)
     {
@@ -7613,9 +7627,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 	error_at (location, msg);
       return error_mark_node;
     }
-  else if (codel == POINTER_TYPE && coder == INTEGER_TYPE)
+  else if (codel == POINTER_TYPE
+	   && (coder == INTEGER_TYPE || coder == NULLPTR_TYPE))
     {
-      /* An explicit constant 0 can convert to a pointer,
+      /* An explicit constant 0 or nullptr can convert to a pointer,
 	 or one that results from arithmetic, even including
 	 a cast to integer type.  */
       if (!null_pointer_constant)
@@ -7691,7 +7706,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 
       return convert (type, rhs);
     }
-  else if (codel == BOOLEAN_TYPE && coder == POINTER_TYPE)
+  else if (codel == BOOLEAN_TYPE
+	   /* The type nullptr_t may be converted to bool.  The
+	      result is false.  */
+	   && (coder == POINTER_TYPE || coder == NULLPTR_TYPE))
     {
       tree ret;
       bool save = in_late_binary_op;
@@ -12107,10 +12125,10 @@ build_binary_op (location_t location, enum tree_code code,
     case TRUTH_XOR_EXPR:
       if ((code0 == INTEGER_TYPE || code0 == POINTER_TYPE
 	   || code0 == REAL_TYPE || code0 == COMPLEX_TYPE
-	   || code0 == FIXED_POINT_TYPE)
+	   || code0 == FIXED_POINT_TYPE || code0 == NULLPTR_TYPE)
 	  && (code1 == INTEGER_TYPE || code1 == POINTER_TYPE
 	      || code1 == REAL_TYPE || code1 == COMPLEX_TYPE
-	      || code1 == FIXED_POINT_TYPE))
+	      || code1 == FIXED_POINT_TYPE || code1 ==  NULLPTR_TYPE))
 	{
 	  /* Result of these operations is always an int,
 	     but that does not mean the operands should be
@@ -12418,6 +12436,31 @@ build_binary_op (location_t location, enum tree_code code,
 	  result_type = type1;
 	  pedwarn (location, 0, "comparison between pointer and integer");
 	}
+      else if (null_pointer_constant_p (orig_op0)
+	       && null_pointer_constant_p (orig_op1))
+	{
+	  /* 6.5.9: One of the following shall hold:
+	      -- both operands have type nullptr_t;  */
+	  if (code0 == NULLPTR_TYPE && code1 == NULLPTR_TYPE)
+	    {
+	      result_type = nullptr_type_node;
+	      /* No need to convert the operands to result_type later.  */
+	      converted = 1;
+	    }
+	  /* -- one operand has type nullptr_t and the other is a null pointer
+	     constant.  We will have to convert the former to the type of the
+	     latter, because during gimplification we can't have mismatching
+	     comparison operand type.  We convert from nullptr_t to the other
+	     type, since only nullptr_t can be converted to nullptr_t.  Also,
+	     even a constant 0 is a null pointer constant, so we may have to
+	     create a pointer type from its type.  */
+	  else if (code0 == NULLPTR_TYPE)
+	    result_type = (INTEGRAL_TYPE_P (type1)
+			   ? build_pointer_type (type1) : type1);
+	  else
+	    result_type = (INTEGRAL_TYPE_P (type0)
+			   ? build_pointer_type (type0) : type0);
+	}
       if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
 	   || truth_value_p (TREE_CODE (orig_op0)))
 	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 3278b4114bd..eb461bf8374 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -187,9 +187,6 @@ enum cp_tree_index
     CPTI_NOEXCEPT_FALSE_SPEC,
     CPTI_NOEXCEPT_DEFERRED_SPEC,
 
-    CPTI_NULLPTR,
-    CPTI_NULLPTR_TYPE,
-
     CPTI_ANY_TARG,
 
     CPTI_MODULE_HWM,
@@ -254,8 +251,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
 #define conv_op_marker			cp_global_trees[CPTI_CONV_OP_MARKER]
 #define abort_fndecl			cp_global_trees[CPTI_ABORT_FNDECL]
 #define current_aggr			cp_global_trees[CPTI_AGGR_TAG]
-#define nullptr_node			cp_global_trees[CPTI_NULLPTR]
-#define nullptr_type_node		cp_global_trees[CPTI_NULLPTR_TYPE]
 /* std::align_val_t */
 #define align_type_node			cp_global_trees[CPTI_ALIGN_TYPE]
 
@@ -4405,9 +4400,6 @@ get_vec_init_expr (tree t)
    || TREE_CODE (TYPE) == REAL_TYPE \
    || TREE_CODE (TYPE) == COMPLEX_TYPE)
 
-/* True iff TYPE is cv decltype(nullptr).  */
-#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
-
 /* [basic.types]
 
    Arithmetic types, enumeration types, pointer types,
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index ff56fddba54..ba4fb21d36a 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -4793,16 +4793,10 @@ cxx_init_decl_processing (void)
 	  }
       }
 
-    nullptr_type_node = make_node (NULLPTR_TYPE);
-    TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
-    TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
-    TYPE_UNSIGNED (nullptr_type_node) = 1;
-    TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
+    /* C++-specific nullptr initialization.  */
     if (abi_version_at_least (9))
       SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
-    SET_TYPE_MODE (nullptr_type_node, ptr_mode);
     record_builtin_type (RID_MAX, "decltype(nullptr)", nullptr_type_node);
-    nullptr_node = build_int_cst (nullptr_type_node, 0);
   }
 
   if (! supports_one_only ())
diff --git a/gcc/ginclude/stddef.h b/gcc/ginclude/stddef.h
index 79e296d4a66..2ccf411ed88 100644
--- a/gcc/ginclude/stddef.h
+++ b/gcc/ginclude/stddef.h
@@ -443,6 +443,14 @@ typedef struct {
 #endif
 #endif /* C++11.  */
 
+#if (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202000L)
+#ifndef _GCC_NULLPTR_T
+#define _GCC_NULLPTR_T
+  typedef __typeof__(nullptr) nullptr_t;
+/* ??? This doesn't define __STDC_VERSION_STDDEF_H__ yet.  */
+#endif
+#endif /* C23.  */
+
 #endif /* _STDDEF_H was defined this time */
 
 #endif /* !_STDDEF_H && !_STDDEF_H_ && !_ANSI_STDDEF_H && !__STDDEF_H__
diff --git a/gcc/testsuite/gcc.dg/Wcxx-compat-2.c b/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
index 4578bece109..61d33a0b028 100644
--- a/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
+++ b/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
@@ -18,7 +18,6 @@ int friend;			/* { dg-warning "5:keyword" } */
 int mutable;			/* { dg-warning "5:keyword" } */
 int namespace;			/* { dg-warning "5:keyword" } */
 int new;			/* { dg-warning "5:keyword" } */
-int nullptr;			/* { dg-warning "5:keyword" } */
 int operator;			/* { dg-warning "5:keyword" } */
 int private;			/* { dg-warning "5:keyword" } */
 int protected;			/* { dg-warning "5:keyword" } */
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-1.c b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
new file mode 100644
index 00000000000..49e7031c4d9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
@@ -0,0 +1,239 @@
+/* Test basic usage of C23 nullptr.  */
+/* { dg-do run } */
+/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
+
+#include <stdarg.h>
+
+typedef __typeof__(nullptr) nullptr_t;
+
+void f1 (nullptr_t) { }
+void f2 (int *) { }
+void f3 (_Bool) { }
+nullptr_t cmp (void) { return nullptr; }
+
+/* The type nullptr_t shall not be converted to any type other than bool or
+   a pointer type.  No type other than nullptr_t shall be converted to nullptr_t.  */
+void
+test1 (void)
+{
+  const nullptr_t nptr = nullptr;
+  static nullptr_t static_nptr;
+  int *p1 = nullptr;
+  void *p2 = nullptr;
+  float *p3 = nullptr;
+  void (*p4)(int) = nullptr;
+  int (*p5)[10] = nullptr;
+  int *p6 = nptr;
+  void *p7 = nptr;
+  float *p8 = nptr;
+  void (*p9)(int) = nptr;
+  int (*p10)[10] = nptr;
+  int *p11 = (int *) nullptr;
+  int *p12 = (int *) nptr;
+  if (nullptr || p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8 || p9 || p10
+      || p11 || p12)
+    __builtin_abort ();
+
+  _Bool b1 = nullptr;
+  _Bool b2 = (_Bool) nullptr;
+  _Bool b3 = nptr;
+  _Bool b4 = (_Bool) nptr;
+  if (b1 || b2 || b3 || b4 || (_Bool) nullptr || (_Bool) nptr)
+   __builtin_abort ();
+
+  __auto_type a1 = nullptr;
+  __auto_type a2 = nptr;
+
+  /* We can convert nullptr_t to nullptr_t.  */
+  __typeof__(nullptr) x = nullptr;
+  f1 (x);
+  f1 (nullptr);
+  f2 (x);
+  f2 (nullptr);
+  f3 (nullptr);
+
+  const nullptr_t np1 = nullptr;
+  const nullptr_t np2 = np1;
+}
+
+/* Test valid comparison.  */
+void
+test2 (int *p)
+{
+  /* If both operands have type nullptr_t or one operand has type nullptr_t
+     and the other is a null pointer constant, they compare equal.  */
+  const nullptr_t nptr = nullptr;
+  int r = 0;
+
+  r |= nullptr != nullptr;
+  r |= cmp () != nullptr;
+  r |= nullptr != cmp ();
+  r |= !(nullptr == nullptr);
+  r |= !(cmp () == nullptr);
+  r |= !(nullptr == cmp ());
+  r |= nullptr != (void *) 0;
+  r |= !(nullptr == (void *) 0);
+  r |= (void *) 0 != nullptr;
+  r |= !((void *) 0 == nullptr);
+  r |= nullptr != 0;
+  r |= 0 != nullptr;
+  r |= !(nullptr == 0);
+  r |= !(0 == nullptr);
+  r |= nullptr != 0u;
+  r |= 0u != nullptr;
+  r |= !(nullptr == 0u);
+  r |= !(0u == nullptr);
+
+  r |= nptr != nptr;
+  r |= cmp () != nptr;
+  r |= nptr != cmp ();
+  r |= !(nptr == nptr);
+  r |= !(cmp () == nptr);
+  r |= !(nptr == cmp ());
+  r |= nptr != (void *) 0;
+  r |= !(nptr == (void *) 0);
+  r |= (void *) 0 != nptr;
+  r |= !((void *) 0 == nptr);
+  r |= nptr != 0;
+  r |= 0 != nptr;
+  r |= !(nptr == 0);
+  r |= !(0 == nptr);
+  r |= nptr != 0u;
+  r |= 0u != nptr;
+  r |= !(nptr == 0u);
+  r |= !(0u == nptr);
+  if (r)
+    __builtin_abort ();
+
+  (void) (p == nullptr);
+  (void) (p != nullptr);
+  (void) (nullptr == p);
+  (void) (nullptr != p);
+}
+
+/* Test ?:.  */
+void
+test3 (int *p, _Bool b)
+{
+  int x = nullptr ? 1 : 2;
+  (void) x;
+  const nullptr_t nptr = nullptr;
+  /* One of the following shall hold for the second and third operands:
+     -- both operands have nullptr_t type.  */
+  __auto_type r1 = b ? nullptr : nullptr;
+  __auto_type r2 = b ? nptr : nptr;
+  /* -- one operand is a pointer and the other is a null pointer constant
+     or has type nullptr_t;  */
+  __auto_type r3 = b ? p : nullptr;
+  __auto_type r4 = b ? nullptr : p;
+  __auto_type r5 = b ? nptr : p;
+  __auto_type r6 = b ? p : nptr;
+  __auto_type r7 = b ? 0 : p;
+  __auto_type r8 = b ? p : 0;
+  __auto_type r9 = b ? p : cmp ();
+  __auto_type r10 = b ?  cmp () : p;
+}
+
+/* Simple assignment.  */
+void
+test4 (void)
+{
+  /* -- the left operand has an atomic, qualified, or unqualified version of
+     the nullptr_t type and the type of the right is nullptr_t;  */
+  nullptr_t n1;
+  n1 = nullptr;
+  const nullptr_t n2 = nullptr;
+  _Atomic nullptr_t n3 = nullptr;
+  volatile nullptr_t n4 = nullptr;
+  /* -- the left operand is an atomic, qualified, or unqualified pointer,
+     and the type of the right is nullptr_t;  */
+  int *p1 = cmp ();
+  _Atomic int *p2 = cmp ();
+  const int *volatile p3 = cmp ();
+  const int *const *const p4 = cmp ();
+  double (*const p5)(void) = n1;
+  /* -- the left operand is an atomic, qualified, or unqualified bool, and
+     the type of the right is nullptr_t;  */
+  _Bool b1;
+  b1 = cmp ();
+  const _Bool b2 = nullptr;
+  _Atomic _Bool b3;
+  b3 = n1;
+  (void) b1;
+  (void) b3;
+}
+
+/* var_arg etc.  */
+static void
+test5 (int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (va_arg (ap, void *))
+    __builtin_abort ();
+}
+
+/* Operand of alignas, sizeof or typeof operators.  */
+void
+test6 (void)
+{
+  _Static_assert (sizeof (nullptr) == sizeof (void *), "sizeof (nullptr)");
+  _Static_assert (sizeof (nullptr_t) == sizeof (void *), "sizeof (nullptr_t)");
+  _Static_assert (sizeof (nullptr) == sizeof (char *), "sizeof (nullptr)");
+  _Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
+  _Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
+  __typeof__(nullptr) t = nullptr;
+  f1 (t);
+  _Alignas (nullptr_t) char i1 = 'q';
+
+  _Static_assert (_Generic (nullptr, nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (t, nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (cmp (), nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (0, nullptr_t: 1, int: 2, default: 0) == 2, "_Generic");
+  _Static_assert (_Generic ((void *)0, nullptr_t: 1, void *: 2, default: 0) == 2, "_Generic");
+  _Static_assert (_Generic (nullptr, nullptr_t: 1, void *: 2, default: 0) == 1, "_Generic");
+}
+
+/* Play with !, ||, &&. */
+void
+test7 (void)
+{
+  if (nullptr)
+    __builtin_abort ();
+  if (1 && nullptr)
+    __builtin_abort ();
+  if (0 || nullptr)
+    __builtin_abort ();
+  if (nullptr && 1)
+    __builtin_abort ();
+  if (nullptr || 0)
+    __builtin_abort ();
+  if (!nullptr)
+    {
+    }
+  else
+    __builtin_abort ();
+  while (nullptr)
+    __builtin_abort ();
+  int i = 0;
+  do
+    ++i;
+  while (nullptr);
+  if (i != 1)
+    __builtin_abort ();
+  for (;nullptr;)
+    __builtin_abort ();
+}
+
+int
+main (void)
+{
+  int i = 42;
+  test1 ();
+  test2 (&i);
+  test3 (&i, 0);
+  test4 ();
+  test5 (42, nullptr);
+  test6 ();
+  test7 ();
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-2.c b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
new file mode 100644
index 00000000000..2cc88353581
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
@@ -0,0 +1,9 @@
+/* Test nullptr_t from <stddef.h..  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+#include <stddef.h>
+
+void f(nullptr_t);
+_Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
+_Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-3.c b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
new file mode 100644
index 00000000000..3f37c9b17ed
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
@@ -0,0 +1,62 @@
+/* Test wrong usage of C23 nullptr.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
+
+typedef __typeof__(nullptr) nullptr_t;
+
+void g (nullptr_t); /* { dg-message "expected .nullptr_t. but argument is of type .int." } */
+
+void
+test1 (int *p)
+{
+  (void) (p > nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p >= nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p < nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p <= nullptr); /* { dg-error "ordered comparison" } */
+  (void) (nullptr == 1); /* { dg-error "invalid operands" } */
+  (void) (1 == nullptr); /* { dg-error "invalid operands" } */
+  (void) (nullptr != 1); /* { dg-error "invalid operands" } */
+  (void) (1 != nullptr); /* { dg-error "invalid operands" } */
+  (void) (1 > nullptr); /* { dg-error "invalid operands" } */
+}
+
+void
+test2 (void)
+{
+  const nullptr_t nptr = nullptr;
+  int p = nullptr; /* { dg-error "incompatible types" } */
+  float d = nullptr; /* { dg-error "incompatible types" } */
+  char arr[10] = { nullptr }; /* { dg-error "incompatible types" } */
+
+  /* No type other than nullptr_t shall be converted to nullptr_t.  */
+  const nullptr_t n = 0; /* { dg-error "invalid initializer" } */
+  +(nullptr_t) 0; /* { dg-error "conversion from .int. to .nullptr_t." } */
+
+  g (0); /* { dg-error "incompatible type" } */
+
+  int i = 42 + nullptr; /* { dg-error "invalid operands" } */
+
+  /* The assignment of an object of type nullptr_t with a value of another
+     type, even if the value is a null pointer constant, is a constraint
+     violation.  */
+  nullptr_t m;
+  m = 0; /* { dg-error "incompatible types" } */
+  (void) m;
+  nullptr_t o = 0; /* { dg-error "invalid initializer" } */
+
+  switch (nullptr); /* { dg-error "switch quantity not an integer" } */
+}
+
+/* If a second or third operand of type nullptr_t is used that is not a null
+   pointer constant and the other operand is not a pointer or does not have
+   itself nullptr_t, a constraint is violated even if that other operand is
+   a null pointer constant such as 0.  */
+void
+test3 (_Bool b, int i)
+{
+  const nullptr_t nptr = nullptr;
+  __auto_type a1 = b ? nptr : i; /* { dg-error "type mismatch" } */
+  __auto_type a2 = b ? i : nptr; /* { dg-error "type mismatch" } */
+  __auto_type a3 = b ? nptr : 0; /* { dg-error "type mismatch" } */
+  __auto_type a4 = b ? 0 : nptr; /* { dg-error "type mismatch" } */
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-4.c b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
new file mode 100644
index 00000000000..5b15e75d159
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
@@ -0,0 +1,10 @@
+/* Test that we warn about `nullptr' pre-C2X.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c17 -pedantic-errors" } */
+
+int *
+fn (int *p)
+{
+  p = nullptr; /* { dg-error "ISO C does not support .nullptr. before C2X" } */
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-5.c b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
new file mode 100644
index 00000000000..7479ab4ea1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
@@ -0,0 +1,11 @@
+/* Test that -Wc11-c2x-compat issues a warning (not a pedwarn) about
+   `nullptr' in C2X.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors -Wc11-c2x-compat" } */
+
+int *
+fn (int *p)
+{
+  p = nullptr; /* { dg-warning "ISO C does not support .nullptr. before C2X" } */
+  return p;
+}

base-commit: 4991e20923b658ce9fbdf5621cab39f71b98fbc2
-- 
2.37.1


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c: Implement C23 nullptr (N3042)
  2022-08-13 21:35 [PATCH] c: Implement C23 nullptr (N3042) Marek Polacek
@ 2022-08-15 17:48 ` Joseph Myers
  2022-08-24 18:24   ` [PATCH v2] " Marek Polacek
  2022-08-15 20:03 ` [PATCH] " Jason Merrill
  1 sibling, 1 reply; 9+ messages in thread
From: Joseph Myers @ 2022-08-15 17:48 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches, Jason Merrill

On Sat, 13 Aug 2022, Marek Polacek via Gcc-patches wrote:

> This patch also defines nullptr_t in <stddef.h>.  I'm uncertain about
> the __STDC_VERSION__ version I should be checking.  Also, I'm not

We're using defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L until 
the final version for C23 is settled.

> defining __STDC_VERSION_STDDEF_H__ yet, because I don't know what value
> it should be defined to.  Do we know yet?

No, and Jens's comments on the editorial review before CD ballot include 
that lots of headers don't yet have such a macro definition but should 
have one, as well as needing consistency for the numbers.

We won't know the final values for these macros until much later, because 
the timescale depends on whether ISO decides to delay things at any point 
by coming up with a long list of editorial issues required to follow the 
JTC1 Directives as they did for C17 (objections to particular words 
appearing in non-normative text, etc.).

> -  { "nullptr",		RID_NULLPTR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
> +  { "nullptr",		RID_NULLPTR,	D_CXX11 | D_CXXWARN },

You need to use D_C2X (which doesn't yet exist).  In pre-C23 modes, 
nullptr needs to be a normal identifier that can be used in all contexts 
where identifiers can be used, not a keyword at all (and then you need a 
c11-nullptr*.c test to verify that use of it as an identifier works as 
expected).

> diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
> index 18083d59618..013fe6b2a53 100644
> --- a/gcc/c/c-convert.cc
> +++ b/gcc/c/c-convert.cc
> @@ -133,6 +133,14 @@ c_convert (tree type, tree expr, bool init_const)
>  	(loc, type, c_objc_common_truthvalue_conversion (input_location, expr));
>  
>      case POINTER_TYPE:
> +      /* The type nullptr_t may be converted to a pointer type.  The result is
> +	 a null pointer value.  */
> +      if (NULLPTR_TYPE_P (TREE_TYPE (e)))
> +	{
> +	  ret = build_int_cst (type, 0);
> +	  goto maybe_fold;
> +	}

That looks like it would lose side-effects.  You need to preserve 
side-effects in an expression of nullptr_t type being converted to a 
pointer type, and need an execution testcase that verifies such 
side-effects are preserved.

Also, you need to make sure that (void *)nullptr is not treated as a null 
pointer constant, only a null pointer; build_int_cst (type, 0) would 
produce a null pointer constant when type is void *.  (void *)nullptr 
should be handled similarly to (void *)(void *)0, which isn't a null 
pointer constant either.

> @@ -133,6 +133,13 @@ null_pointer_constant_p (const_tree expr)
>    /* This should really operate on c_expr structures, but they aren't
>       yet available everywhere required.  */
>    tree type = TREE_TYPE (expr);
> +
> +  /* An integer constant expression with the value 0, such an expression
> +     cast to type void*, or the predefined constant nullptr, are a null
> +     pointer constant.  */
> +  if (NULLPTR_TYPE_P (type))
> +    return true;

That looks wrong.  You need to distinguish null pointer constants of type 
nullptr_t (nullptr, possibly enclosed in parentheses, possibly the 
selected alternative from _Generic) from all other expressions of type 
nullptr_t (including (nullptr_t)nullptr, which isn't a null pointer 
constant any more than (void *)(void *)0).

Then, for each context where it matters whether a nullptr_t value is a 
null pointer constant, there need to be testcases that the two cases are 
properly distinguished.  This includes at least equality comparisons with 
a pointer that is not a null pointer constant (seem only to be allowed 
with nullptr, not with other nullptr_t expressions).  (I think for 
conditional expressions, conditionals between nullptr_t and an integer 
null pointer constant are always invalid, whether or not the nullptr_t is 
a null pointer constant, while conditionals between nullptr_t and a 
pointer are always valid.)

> +/* The type nullptr_t shall not be converted to any type other than bool or
> +   a pointer type.  No type other than nullptr_t shall be converted to nullptr_t.  */

That's other than *void*, bool or a pointer type.  (That's a correct fix 
to the N3042 wording in N3047.  There are other problems in the 
integration of nullptr in N3047 that are only fixed in my subsequent fixes 
as part of the editorial review - and many issues with integration of 
other papers that haven't yet been fixed, I currently have 25 open merge 
requests resulting from editorial review.)  And of course conversions from 
nullptr_t to void should be tested.

> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-4.c b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> new file mode 100644
> index 00000000000..5b15e75d159
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> @@ -0,0 +1,10 @@
> +/* Test that we warn about `nullptr' pre-C2X.  */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c17 -pedantic-errors" } */

This test is wrong - it's a normal identifier pre-C2x - but tests for 
previous standard versions shouldn't be called c2x-* in any case.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c: Implement C23 nullptr (N3042)
  2022-08-13 21:35 [PATCH] c: Implement C23 nullptr (N3042) Marek Polacek
  2022-08-15 17:48 ` Joseph Myers
@ 2022-08-15 20:03 ` Jason Merrill
  2022-08-24 18:24   ` Marek Polacek
  1 sibling, 1 reply; 9+ messages in thread
From: Jason Merrill @ 2022-08-15 20:03 UTC (permalink / raw)
  To: Marek Polacek, GCC Patches, Joseph Myers

On 8/13/22 14:35, Marek Polacek wrote:
> This patch implements the C23 nullptr literal:
> <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm>, which is
> intended to replace the problematic definition of NULL which might be
> either of integer type or void*.
> 
> Since C++ has had nullptr for over a decade now, it was relatively easy
> to just move the built-in node definitions from the C++ FE to the C/C++
> common code.  Also, our DWARF emitter already handles NULLPTR_TYPE by
> emitting DW_TAG_unspecified_type.  However, I had to handle a lot of
> contexts such as ?:, comparison, conversion, etc.
> 
> There are some minor differences, e.g. in C you can do
> 
>    bool b = nullptr;
> 
> but in C++ you have to use direct-initialization:
> 
>    bool b{nullptr};
> 
> And I think that
> 
>    nullptr_t n = 0;
> 
> is only valid in C++.
> 
> Of course, C doesn't have to handle mangling, RTTI, substitution,
> overloading, ...
> 
> This patch also defines nullptr_t in <stddef.h>.  I'm uncertain about
> the __STDC_VERSION__ version I should be checking.  Also, I'm not
> defining __STDC_VERSION_STDDEF_H__ yet, because I don't know what value
> it should be defined to.  Do we know yet?
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

The C++ changes are OK, but you probably want a comment in 
c_common_nodes_and_builtins that we aren't setting the alignment there 
for C++ backward ABI bug compatibility.  Or perhaps set it there and 
then break it in the C++ front end when abi < 9.

> gcc/c-family/ChangeLog:
> 
> 	* c-common.cc (c_common_reswords): Enable nullptr in C.
> 	(c_common_nodes_and_builtins): Create the built-in node for nullptr.
> 	* c-common.h (enum c_tree_index): Add CTI_NULLPTR, CTI_NULLPTR_TYPE.
> 	(nullptr_node): Define.
> 	(nullptr_type_node): Define.
> 	(NULLPTR_TYPE_P): Define.
> 	* c-pretty-print.cc (c_pretty_printer::simple_type_specifier): Handle
> 	NULLPTR_TYPE.
> 	(c_pretty_printer::direct_abstract_declarator): Likewise.
> 	(c_pretty_printer::constant): Likewise.
> 
> gcc/c/ChangeLog:
> 
> 	* c-convert.cc (c_convert) <case POINTER_TYPE>: Handle NULLPTR_TYPE.
> 	Give a better diagnostic when converting to nullptr_t.
> 	* c-decl.cc (c_init_decl_processing): Perform C-specific nullptr
> 	initialization.
> 	* c-parser.cc (c_parser_postfix_expression): Handle RID_NULLPTR.
> 	* c-typeck.cc (null_pointer_constant_p): Return true for NULLPTR_TYPE_P.
> 	(build_unary_op) <case TRUTH_NOT_EXPR>: Handle NULLPTR_TYPE.
> 	(build_conditional_expr): Handle the case when the second/third operand
> 	is NULLPTR_TYPE and third/second operand is POINTER_TYPE.
> 	(convert_for_assignment): Handle converting an expression of type
> 	nullptr_t to pointer/bool.
> 	(build_binary_op) <case TRUTH_XOR_EXPR>: Handle NULLPTR_TYPE.
> 	<case EQ_EXPR>: Likewise.
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (enum cp_tree_index): Remove CTI_NULLPTR, CTI_NULLPTR_TYPE.
> 	Move it to c_tree_index.
> 	(nullptr_node): No longer define here.
> 	(nullptr_type_node): Likewise.
> 	(NULLPTR_TYPE_P): Likewise.
> 	* decl.cc (cxx_init_decl_processing): Only keep C++-specific nullptr
> 	initialization; move the shared code to c_common_nodes_and_builtins.
> 
> gcc/ChangeLog:
> 
> 	* ginclude/stddef.h: Define nullptr_t.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* gcc.dg/Wcxx-compat-2.c: Remove nullptr test.
> 	* gcc.dg/c2x-nullptr-1.c: New test.
> 	* gcc.dg/c2x-nullptr-2.c: New test.
> 	* gcc.dg/c2x-nullptr-3.c: New test.
> 	* gcc.dg/c2x-nullptr-4.c: New test.
> 	* gcc.dg/c2x-nullptr-5.c: New test.
> ---
>   gcc/c-family/c-common.cc             |  13 +-
>   gcc/c-family/c-common.h              |   8 +
>   gcc/c-family/c-pretty-print.cc       |   7 +
>   gcc/c/c-convert.cc                   |  19 ++-
>   gcc/c/c-decl.cc                      |   6 +
>   gcc/c/c-parser.cc                    |   8 +
>   gcc/c/c-typeck.cc                    |  55 +++++-
>   gcc/cp/cp-tree.h                     |   8 -
>   gcc/cp/decl.cc                       |   8 +-
>   gcc/ginclude/stddef.h                |   8 +
>   gcc/testsuite/gcc.dg/Wcxx-compat-2.c |   1 -
>   gcc/testsuite/gcc.dg/c2x-nullptr-1.c | 239 +++++++++++++++++++++++++++
>   gcc/testsuite/gcc.dg/c2x-nullptr-2.c |   9 +
>   gcc/testsuite/gcc.dg/c2x-nullptr-3.c |  62 +++++++
>   gcc/testsuite/gcc.dg/c2x-nullptr-4.c |  10 ++
>   gcc/testsuite/gcc.dg/c2x-nullptr-5.c |  11 ++
>   16 files changed, 448 insertions(+), 24 deletions(-)
>   create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-5.c
> 
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index 6e41ceb38e9..809e7ff5804 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -500,7 +500,7 @@ const struct c_common_resword c_common_reswords[] =
>     { "namespace",	RID_NAMESPACE,	D_CXXONLY | D_CXXWARN },
>     { "new",		RID_NEW,	D_CXXONLY | D_CXXWARN },
>     { "noexcept",		RID_NOEXCEPT,	D_CXXONLY | D_CXX11 | D_CXXWARN },
> -  { "nullptr",		RID_NULLPTR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
> +  { "nullptr",		RID_NULLPTR,	D_CXX11 | D_CXXWARN },
>     { "operator",		RID_OPERATOR,	D_CXXONLY | D_CXXWARN },
>     { "private",		RID_PRIVATE,	D_CXX_OBJC | D_CXXWARN },
>     { "protected",	RID_PROTECTED,	D_CXX_OBJC | D_CXXWARN },
> @@ -4723,6 +4723,17 @@ c_common_nodes_and_builtins (void)
>     null_node = make_int_cst (1, 1);
>     TREE_TYPE (null_node) = c_common_type_for_size (POINTER_SIZE, 0);
>   
> +  /* Create the built-in nullptr node.  This part of its initialization is
> +     common to C and C++.  The front ends can further adjust its definition
> +     in {c,cxx}_init_decl_processing.  */
> +  nullptr_type_node = make_node (NULLPTR_TYPE);
> +  TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
> +  TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
> +  TYPE_UNSIGNED (nullptr_type_node) = 1;
> +  TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
> +  SET_TYPE_MODE (nullptr_type_node, ptr_mode);
> +  nullptr_node = build_int_cst (nullptr_type_node, 0);
> +
>     /* Since builtin_types isn't gc'ed, don't export these nodes.  */
>     memset (builtin_types, 0, sizeof (builtin_types));
>   }
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index c06769b6f0b..d30174334c2 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -375,6 +375,8 @@ enum c_tree_index
>       CTI_DEFAULT_FUNCTION_TYPE,
>   
>       CTI_NULL,
> +    CTI_NULLPTR,
> +    CTI_NULLPTR_TYPE,
>   
>       /* These are not types, but we have to look them up all the time.  */
>       CTI_FUNCTION_NAME_DECL,
> @@ -534,6 +536,9 @@ extern const unsigned int num_c_common_reswords;
>   
>   /* The node for C++ `__null'.  */
>   #define null_node                       c_global_trees[CTI_NULL]
> +/* The nodes for `nullptr'.  */
> +#define nullptr_node                    c_global_trees[CTI_NULLPTR]
> +#define nullptr_type_node               c_global_trees[CTI_NULLPTR_TYPE]
>   
>   extern GTY(()) tree c_global_trees[CTI_MAX];
>   
> @@ -1009,6 +1014,9 @@ extern void c_parse_final_cleanups (void);
>   #define DECL_UNNAMED_BIT_FIELD(NODE) \
>     (DECL_C_BIT_FIELD (NODE) && !DECL_NAME (NODE))
>   
> +/* True iff TYPE is cv decltype(nullptr).  */
> +#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
> +
>   extern tree do_case (location_t, tree, tree);
>   extern tree build_stmt (location_t, enum tree_code, ...);
>   extern tree build_real_imag_expr (location_t, enum tree_code, tree);
> diff --git a/gcc/c-family/c-pretty-print.cc b/gcc/c-family/c-pretty-print.cc
> index 71a0cb51093..efa1768f4d6 100644
> --- a/gcc/c-family/c-pretty-print.cc
> +++ b/gcc/c-family/c-pretty-print.cc
> @@ -321,6 +321,7 @@ pp_c_pointer (c_pretty_printer *pp, tree t)
>         _Bool                          -- C99
>         _Complex                       -- C99
>         _Imaginary                     -- C99
> +      nullptr_t                      -- C23
>         struct-or-union-specifier
>         enum-specifier
>         typedef-name.
> @@ -424,6 +425,9 @@ c_pretty_printer::simple_type_specifier (tree t)
>         else
>   	translate_string ("<anonymous>");
>         break;
> +    case NULLPTR_TYPE:
> +      pp_c_ws_string (this, "nullptr_t");
> +      break;
>   
>       default:
>         pp_unsupported_tree (this, t);
> @@ -678,6 +682,7 @@ c_pretty_printer::direct_abstract_declarator (tree t)
>       case COMPLEX_TYPE:
>       case TYPE_DECL:
>       case ERROR_MARK:
> +    case NULLPTR_TYPE:
>         break;
>   
>       default:
> @@ -1219,6 +1224,8 @@ c_pretty_printer::constant (tree e)
>   	  pp_c_character_constant (this, e);
>   	else if (TREE_CODE (type) == ENUMERAL_TYPE)
>   	  pp_c_enumeration_constant (this, e);
> +	else if (NULLPTR_TYPE_P (type))
> +	  pp_string (this, "nullptr");
>   	else
>   	  pp_c_integer_constant (this, e);
>         }
> diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
> index 18083d59618..013fe6b2a53 100644
> --- a/gcc/c/c-convert.cc
> +++ b/gcc/c/c-convert.cc
> @@ -133,6 +133,14 @@ c_convert (tree type, tree expr, bool init_const)
>   	(loc, type, c_objc_common_truthvalue_conversion (input_location, expr));
>   
>       case POINTER_TYPE:
> +      /* The type nullptr_t may be converted to a pointer type.  The result is
> +	 a null pointer value.  */
> +      if (NULLPTR_TYPE_P (TREE_TYPE (e)))
> +	{
> +	  ret = build_int_cst (type, 0);
> +	  goto maybe_fold;
> +	}
> +      gcc_fallthrough ();
>       case REFERENCE_TYPE:
>         ret = convert_to_pointer (type, e);
>         goto maybe_fold;
> @@ -180,7 +188,16 @@ c_convert (tree type, tree expr, bool init_const)
>         return ret;
>       }
>   
> -  error ("conversion to non-scalar type requested");
> +  /* If we are converting to nullptr_t, don't say "non-scalar type" because
> +     the nullptr_t type is a scalar type.  Only nullptr_t shall be converted
> +     to nullptr_t.  */
> +  if (code == NULLPTR_TYPE)
> +    {
> +      error ("conversion from %qT to %qT", TREE_TYPE (e), type);
> +      inform (input_location, "only %qT can be converted to %qT", type, type);
> +    }
> +  else
> +    error ("conversion to non-scalar type requested");
>     return error_mark_node;
>   }
>   
> diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
> index ae8990c138f..ac4394c9dc5 100644
> --- a/gcc/c/c-decl.cc
> +++ b/gcc/c/c-decl.cc
> @@ -4531,6 +4531,12 @@ c_init_decl_processing (void)
>     pushdecl (build_decl (UNKNOWN_LOCATION, TYPE_DECL, get_identifier ("_Bool"),
>   			boolean_type_node));
>   
> +  /* C-specific nullptr initialization.  */
> +  record_builtin_type (RID_MAX, "nullptr_t", nullptr_type_node);
> +  /* The size and alignment of nullptr_t is the same as for a pointer to
> +     character type.  */
> +  SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
> +
>     input_location = save_loc;
>   
>     make_fname_decl = c_make_fname_decl;
> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index 759f200a7eb..2af3a614fb9 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -10243,6 +10243,14 @@ c_parser_postfix_expression (c_parser *parser)
>   			 "%<depend%> clause");
>   	  expr.set_error ();
>   	  break;
> +	/* C23 'nullptr' literal.  */
> +	case RID_NULLPTR:
> +	  c_parser_consume_token (parser);
> +	  expr.value = nullptr_node;
> +	  set_c_expr_source_range (&expr, tok_range);
> +	  pedwarn_c11 (loc, OPT_Wpedantic,
> +		       "ISO C does not support %qs before C2X", "nullptr");
> +	  break;
>   	default:
>   	  c_parser_error (parser, "expected expression");
>   	  expr.set_error ();
> diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
> index d37de2a313b..2ba48345a1b 100644
> --- a/gcc/c/c-typeck.cc
> +++ b/gcc/c/c-typeck.cc
> @@ -133,6 +133,13 @@ null_pointer_constant_p (const_tree expr)
>     /* This should really operate on c_expr structures, but they aren't
>        yet available everywhere required.  */
>     tree type = TREE_TYPE (expr);
> +
> +  /* An integer constant expression with the value 0, such an expression
> +     cast to type void*, or the predefined constant nullptr, are a null
> +     pointer constant.  */
> +  if (NULLPTR_TYPE_P (type))
> +    return true;
> +
>     return (TREE_CODE (expr) == INTEGER_CST
>   	  && !TREE_OVERFLOW (expr)
>   	  && integer_zerop (expr)
> @@ -4575,7 +4582,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg,
>       case TRUTH_NOT_EXPR:
>         if (typecode != INTEGER_TYPE && typecode != FIXED_POINT_TYPE
>   	  && typecode != REAL_TYPE && typecode != POINTER_TYPE
> -	  && typecode != COMPLEX_TYPE)
> +	  && typecode != COMPLEX_TYPE && typecode != NULLPTR_TYPE)
>   	{
>   	  error_at (location,
>   		    "wrong type argument to unary exclamation mark");
> @@ -5515,6 +5522,13 @@ build_conditional_expr (location_t colon_loc, tree ifexp, bool ifexp_bcp,
>   	}
>         result_type = type2;
>       }
> +  /* 6.5.15: "if one is a null pointer constant (other than a pointer) or has
> +     type nullptr_t and the other is a pointer, the result type is the pointer
> +     type."  */
> +  else if (code1 == NULLPTR_TYPE && code2 == POINTER_TYPE)
> +    result_type = type2;
> +  else if (code1 == POINTER_TYPE && code2 == NULLPTR_TYPE)
> +    result_type = type1;
>   
>     if (!result_type)
>       {
> @@ -7613,9 +7627,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
>   	error_at (location, msg);
>         return error_mark_node;
>       }
> -  else if (codel == POINTER_TYPE && coder == INTEGER_TYPE)
> +  else if (codel == POINTER_TYPE
> +	   && (coder == INTEGER_TYPE || coder == NULLPTR_TYPE))
>       {
> -      /* An explicit constant 0 can convert to a pointer,
> +      /* An explicit constant 0 or nullptr can convert to a pointer,
>   	 or one that results from arithmetic, even including
>   	 a cast to integer type.  */
>         if (!null_pointer_constant)
> @@ -7691,7 +7706,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
>   
>         return convert (type, rhs);
>       }
> -  else if (codel == BOOLEAN_TYPE && coder == POINTER_TYPE)
> +  else if (codel == BOOLEAN_TYPE
> +	   /* The type nullptr_t may be converted to bool.  The
> +	      result is false.  */
> +	   && (coder == POINTER_TYPE || coder == NULLPTR_TYPE))
>       {
>         tree ret;
>         bool save = in_late_binary_op;
> @@ -12107,10 +12125,10 @@ build_binary_op (location_t location, enum tree_code code,
>       case TRUTH_XOR_EXPR:
>         if ((code0 == INTEGER_TYPE || code0 == POINTER_TYPE
>   	   || code0 == REAL_TYPE || code0 == COMPLEX_TYPE
> -	   || code0 == FIXED_POINT_TYPE)
> +	   || code0 == FIXED_POINT_TYPE || code0 == NULLPTR_TYPE)
>   	  && (code1 == INTEGER_TYPE || code1 == POINTER_TYPE
>   	      || code1 == REAL_TYPE || code1 == COMPLEX_TYPE
> -	      || code1 == FIXED_POINT_TYPE))
> +	      || code1 == FIXED_POINT_TYPE || code1 ==  NULLPTR_TYPE))
>   	{
>   	  /* Result of these operations is always an int,
>   	     but that does not mean the operands should be
> @@ -12418,6 +12436,31 @@ build_binary_op (location_t location, enum tree_code code,
>   	  result_type = type1;
>   	  pedwarn (location, 0, "comparison between pointer and integer");
>   	}
> +      else if (null_pointer_constant_p (orig_op0)
> +	       && null_pointer_constant_p (orig_op1))
> +	{
> +	  /* 6.5.9: One of the following shall hold:
> +	      -- both operands have type nullptr_t;  */
> +	  if (code0 == NULLPTR_TYPE && code1 == NULLPTR_TYPE)
> +	    {
> +	      result_type = nullptr_type_node;
> +	      /* No need to convert the operands to result_type later.  */
> +	      converted = 1;
> +	    }
> +	  /* -- one operand has type nullptr_t and the other is a null pointer
> +	     constant.  We will have to convert the former to the type of the
> +	     latter, because during gimplification we can't have mismatching
> +	     comparison operand type.  We convert from nullptr_t to the other
> +	     type, since only nullptr_t can be converted to nullptr_t.  Also,
> +	     even a constant 0 is a null pointer constant, so we may have to
> +	     create a pointer type from its type.  */
> +	  else if (code0 == NULLPTR_TYPE)
> +	    result_type = (INTEGRAL_TYPE_P (type1)
> +			   ? build_pointer_type (type1) : type1);
> +	  else
> +	    result_type = (INTEGRAL_TYPE_P (type0)
> +			   ? build_pointer_type (type0) : type0);
> +	}
>         if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
>   	   || truth_value_p (TREE_CODE (orig_op0)))
>   	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 3278b4114bd..eb461bf8374 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -187,9 +187,6 @@ enum cp_tree_index
>       CPTI_NOEXCEPT_FALSE_SPEC,
>       CPTI_NOEXCEPT_DEFERRED_SPEC,
>   
> -    CPTI_NULLPTR,
> -    CPTI_NULLPTR_TYPE,
> -
>       CPTI_ANY_TARG,
>   
>       CPTI_MODULE_HWM,
> @@ -254,8 +251,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>   #define conv_op_marker			cp_global_trees[CPTI_CONV_OP_MARKER]
>   #define abort_fndecl			cp_global_trees[CPTI_ABORT_FNDECL]
>   #define current_aggr			cp_global_trees[CPTI_AGGR_TAG]
> -#define nullptr_node			cp_global_trees[CPTI_NULLPTR]
> -#define nullptr_type_node		cp_global_trees[CPTI_NULLPTR_TYPE]
>   /* std::align_val_t */
>   #define align_type_node			cp_global_trees[CPTI_ALIGN_TYPE]
>   
> @@ -4405,9 +4400,6 @@ get_vec_init_expr (tree t)
>      || TREE_CODE (TYPE) == REAL_TYPE \
>      || TREE_CODE (TYPE) == COMPLEX_TYPE)
>   
> -/* True iff TYPE is cv decltype(nullptr).  */
> -#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
> -
>   /* [basic.types]
>   
>      Arithmetic types, enumeration types, pointer types,
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index ff56fddba54..ba4fb21d36a 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -4793,16 +4793,10 @@ cxx_init_decl_processing (void)
>   	  }
>         }
>   
> -    nullptr_type_node = make_node (NULLPTR_TYPE);
> -    TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
> -    TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
> -    TYPE_UNSIGNED (nullptr_type_node) = 1;
> -    TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
> +    /* C++-specific nullptr initialization.  */
>       if (abi_version_at_least (9))
>         SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
> -    SET_TYPE_MODE (nullptr_type_node, ptr_mode);
>       record_builtin_type (RID_MAX, "decltype(nullptr)", nullptr_type_node);
> -    nullptr_node = build_int_cst (nullptr_type_node, 0);
>     }
>   
>     if (! supports_one_only ())
> diff --git a/gcc/ginclude/stddef.h b/gcc/ginclude/stddef.h
> index 79e296d4a66..2ccf411ed88 100644
> --- a/gcc/ginclude/stddef.h
> +++ b/gcc/ginclude/stddef.h
> @@ -443,6 +443,14 @@ typedef struct {
>   #endif
>   #endif /* C++11.  */
>   
> +#if (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202000L)
> +#ifndef _GCC_NULLPTR_T
> +#define _GCC_NULLPTR_T
> +  typedef __typeof__(nullptr) nullptr_t;
> +/* ??? This doesn't define __STDC_VERSION_STDDEF_H__ yet.  */
> +#endif
> +#endif /* C23.  */
> +
>   #endif /* _STDDEF_H was defined this time */
>   
>   #endif /* !_STDDEF_H && !_STDDEF_H_ && !_ANSI_STDDEF_H && !__STDDEF_H__
> diff --git a/gcc/testsuite/gcc.dg/Wcxx-compat-2.c b/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
> index 4578bece109..61d33a0b028 100644
> --- a/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
> +++ b/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
> @@ -18,7 +18,6 @@ int friend;			/* { dg-warning "5:keyword" } */
>   int mutable;			/* { dg-warning "5:keyword" } */
>   int namespace;			/* { dg-warning "5:keyword" } */
>   int new;			/* { dg-warning "5:keyword" } */
> -int nullptr;			/* { dg-warning "5:keyword" } */
>   int operator;			/* { dg-warning "5:keyword" } */
>   int private;			/* { dg-warning "5:keyword" } */
>   int protected;			/* { dg-warning "5:keyword" } */
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-1.c b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
> new file mode 100644
> index 00000000000..49e7031c4d9
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
> @@ -0,0 +1,239 @@
> +/* Test basic usage of C23 nullptr.  */
> +/* { dg-do run } */
> +/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
> +
> +#include <stdarg.h>
> +
> +typedef __typeof__(nullptr) nullptr_t;
> +
> +void f1 (nullptr_t) { }
> +void f2 (int *) { }
> +void f3 (_Bool) { }
> +nullptr_t cmp (void) { return nullptr; }
> +
> +/* The type nullptr_t shall not be converted to any type other than bool or
> +   a pointer type.  No type other than nullptr_t shall be converted to nullptr_t.  */
> +void
> +test1 (void)
> +{
> +  const nullptr_t nptr = nullptr;
> +  static nullptr_t static_nptr;
> +  int *p1 = nullptr;
> +  void *p2 = nullptr;
> +  float *p3 = nullptr;
> +  void (*p4)(int) = nullptr;
> +  int (*p5)[10] = nullptr;
> +  int *p6 = nptr;
> +  void *p7 = nptr;
> +  float *p8 = nptr;
> +  void (*p9)(int) = nptr;
> +  int (*p10)[10] = nptr;
> +  int *p11 = (int *) nullptr;
> +  int *p12 = (int *) nptr;
> +  if (nullptr || p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8 || p9 || p10
> +      || p11 || p12)
> +    __builtin_abort ();
> +
> +  _Bool b1 = nullptr;
> +  _Bool b2 = (_Bool) nullptr;
> +  _Bool b3 = nptr;
> +  _Bool b4 = (_Bool) nptr;
> +  if (b1 || b2 || b3 || b4 || (_Bool) nullptr || (_Bool) nptr)
> +   __builtin_abort ();
> +
> +  __auto_type a1 = nullptr;
> +  __auto_type a2 = nptr;
> +
> +  /* We can convert nullptr_t to nullptr_t.  */
> +  __typeof__(nullptr) x = nullptr;
> +  f1 (x);
> +  f1 (nullptr);
> +  f2 (x);
> +  f2 (nullptr);
> +  f3 (nullptr);
> +
> +  const nullptr_t np1 = nullptr;
> +  const nullptr_t np2 = np1;
> +}
> +
> +/* Test valid comparison.  */
> +void
> +test2 (int *p)
> +{
> +  /* If both operands have type nullptr_t or one operand has type nullptr_t
> +     and the other is a null pointer constant, they compare equal.  */
> +  const nullptr_t nptr = nullptr;
> +  int r = 0;
> +
> +  r |= nullptr != nullptr;
> +  r |= cmp () != nullptr;
> +  r |= nullptr != cmp ();
> +  r |= !(nullptr == nullptr);
> +  r |= !(cmp () == nullptr);
> +  r |= !(nullptr == cmp ());
> +  r |= nullptr != (void *) 0;
> +  r |= !(nullptr == (void *) 0);
> +  r |= (void *) 0 != nullptr;
> +  r |= !((void *) 0 == nullptr);
> +  r |= nullptr != 0;
> +  r |= 0 != nullptr;
> +  r |= !(nullptr == 0);
> +  r |= !(0 == nullptr);
> +  r |= nullptr != 0u;
> +  r |= 0u != nullptr;
> +  r |= !(nullptr == 0u);
> +  r |= !(0u == nullptr);
> +
> +  r |= nptr != nptr;
> +  r |= cmp () != nptr;
> +  r |= nptr != cmp ();
> +  r |= !(nptr == nptr);
> +  r |= !(cmp () == nptr);
> +  r |= !(nptr == cmp ());
> +  r |= nptr != (void *) 0;
> +  r |= !(nptr == (void *) 0);
> +  r |= (void *) 0 != nptr;
> +  r |= !((void *) 0 == nptr);
> +  r |= nptr != 0;
> +  r |= 0 != nptr;
> +  r |= !(nptr == 0);
> +  r |= !(0 == nptr);
> +  r |= nptr != 0u;
> +  r |= 0u != nptr;
> +  r |= !(nptr == 0u);
> +  r |= !(0u == nptr);
> +  if (r)
> +    __builtin_abort ();
> +
> +  (void) (p == nullptr);
> +  (void) (p != nullptr);
> +  (void) (nullptr == p);
> +  (void) (nullptr != p);
> +}
> +
> +/* Test ?:.  */
> +void
> +test3 (int *p, _Bool b)
> +{
> +  int x = nullptr ? 1 : 2;
> +  (void) x;
> +  const nullptr_t nptr = nullptr;
> +  /* One of the following shall hold for the second and third operands:
> +     -- both operands have nullptr_t type.  */
> +  __auto_type r1 = b ? nullptr : nullptr;
> +  __auto_type r2 = b ? nptr : nptr;
> +  /* -- one operand is a pointer and the other is a null pointer constant
> +     or has type nullptr_t;  */
> +  __auto_type r3 = b ? p : nullptr;
> +  __auto_type r4 = b ? nullptr : p;
> +  __auto_type r5 = b ? nptr : p;
> +  __auto_type r6 = b ? p : nptr;
> +  __auto_type r7 = b ? 0 : p;
> +  __auto_type r8 = b ? p : 0;
> +  __auto_type r9 = b ? p : cmp ();
> +  __auto_type r10 = b ?  cmp () : p;
> +}
> +
> +/* Simple assignment.  */
> +void
> +test4 (void)
> +{
> +  /* -- the left operand has an atomic, qualified, or unqualified version of
> +     the nullptr_t type and the type of the right is nullptr_t;  */
> +  nullptr_t n1;
> +  n1 = nullptr;
> +  const nullptr_t n2 = nullptr;
> +  _Atomic nullptr_t n3 = nullptr;
> +  volatile nullptr_t n4 = nullptr;
> +  /* -- the left operand is an atomic, qualified, or unqualified pointer,
> +     and the type of the right is nullptr_t;  */
> +  int *p1 = cmp ();
> +  _Atomic int *p2 = cmp ();
> +  const int *volatile p3 = cmp ();
> +  const int *const *const p4 = cmp ();
> +  double (*const p5)(void) = n1;
> +  /* -- the left operand is an atomic, qualified, or unqualified bool, and
> +     the type of the right is nullptr_t;  */
> +  _Bool b1;
> +  b1 = cmp ();
> +  const _Bool b2 = nullptr;
> +  _Atomic _Bool b3;
> +  b3 = n1;
> +  (void) b1;
> +  (void) b3;
> +}
> +
> +/* var_arg etc.  */
> +static void
> +test5 (int i, ...)
> +{
> +  va_list ap;
> +  va_start (ap, i);
> +  if (va_arg (ap, void *))
> +    __builtin_abort ();
> +}
> +
> +/* Operand of alignas, sizeof or typeof operators.  */
> +void
> +test6 (void)
> +{
> +  _Static_assert (sizeof (nullptr) == sizeof (void *), "sizeof (nullptr)");
> +  _Static_assert (sizeof (nullptr_t) == sizeof (void *), "sizeof (nullptr_t)");
> +  _Static_assert (sizeof (nullptr) == sizeof (char *), "sizeof (nullptr)");
> +  _Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
> +  _Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
> +  __typeof__(nullptr) t = nullptr;
> +  f1 (t);
> +  _Alignas (nullptr_t) char i1 = 'q';
> +
> +  _Static_assert (_Generic (nullptr, nullptr_t: 1, default: 0) == 1, "_Generic");
> +  _Static_assert (_Generic (t, nullptr_t: 1, default: 0) == 1, "_Generic");
> +  _Static_assert (_Generic (cmp (), nullptr_t: 1, default: 0) == 1, "_Generic");
> +  _Static_assert (_Generic (0, nullptr_t: 1, int: 2, default: 0) == 2, "_Generic");
> +  _Static_assert (_Generic ((void *)0, nullptr_t: 1, void *: 2, default: 0) == 2, "_Generic");
> +  _Static_assert (_Generic (nullptr, nullptr_t: 1, void *: 2, default: 0) == 1, "_Generic");
> +}
> +
> +/* Play with !, ||, &&. */
> +void
> +test7 (void)
> +{
> +  if (nullptr)
> +    __builtin_abort ();
> +  if (1 && nullptr)
> +    __builtin_abort ();
> +  if (0 || nullptr)
> +    __builtin_abort ();
> +  if (nullptr && 1)
> +    __builtin_abort ();
> +  if (nullptr || 0)
> +    __builtin_abort ();
> +  if (!nullptr)
> +    {
> +    }
> +  else
> +    __builtin_abort ();
> +  while (nullptr)
> +    __builtin_abort ();
> +  int i = 0;
> +  do
> +    ++i;
> +  while (nullptr);
> +  if (i != 1)
> +    __builtin_abort ();
> +  for (;nullptr;)
> +    __builtin_abort ();
> +}
> +
> +int
> +main (void)
> +{
> +  int i = 42;
> +  test1 ();
> +  test2 (&i);
> +  test3 (&i, 0);
> +  test4 ();
> +  test5 (42, nullptr);
> +  test6 ();
> +  test7 ();
> +}
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-2.c b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
> new file mode 100644
> index 00000000000..2cc88353581
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
> @@ -0,0 +1,9 @@
> +/* Test nullptr_t from <stddef.h..  */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c2x -pedantic-errors" } */
> +
> +#include <stddef.h>
> +
> +void f(nullptr_t);
> +_Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
> +_Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-3.c b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
> new file mode 100644
> index 00000000000..3f37c9b17ed
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
> @@ -0,0 +1,62 @@
> +/* Test wrong usage of C23 nullptr.  */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
> +
> +typedef __typeof__(nullptr) nullptr_t;
> +
> +void g (nullptr_t); /* { dg-message "expected .nullptr_t. but argument is of type .int." } */
> +
> +void
> +test1 (int *p)
> +{
> +  (void) (p > nullptr); /* { dg-error "ordered comparison" } */
> +  (void) (p >= nullptr); /* { dg-error "ordered comparison" } */
> +  (void) (p < nullptr); /* { dg-error "ordered comparison" } */
> +  (void) (p <= nullptr); /* { dg-error "ordered comparison" } */
> +  (void) (nullptr == 1); /* { dg-error "invalid operands" } */
> +  (void) (1 == nullptr); /* { dg-error "invalid operands" } */
> +  (void) (nullptr != 1); /* { dg-error "invalid operands" } */
> +  (void) (1 != nullptr); /* { dg-error "invalid operands" } */
> +  (void) (1 > nullptr); /* { dg-error "invalid operands" } */
> +}
> +
> +void
> +test2 (void)
> +{
> +  const nullptr_t nptr = nullptr;
> +  int p = nullptr; /* { dg-error "incompatible types" } */
> +  float d = nullptr; /* { dg-error "incompatible types" } */
> +  char arr[10] = { nullptr }; /* { dg-error "incompatible types" } */
> +
> +  /* No type other than nullptr_t shall be converted to nullptr_t.  */
> +  const nullptr_t n = 0; /* { dg-error "invalid initializer" } */
> +  +(nullptr_t) 0; /* { dg-error "conversion from .int. to .nullptr_t." } */
> +
> +  g (0); /* { dg-error "incompatible type" } */
> +
> +  int i = 42 + nullptr; /* { dg-error "invalid operands" } */
> +
> +  /* The assignment of an object of type nullptr_t with a value of another
> +     type, even if the value is a null pointer constant, is a constraint
> +     violation.  */
> +  nullptr_t m;
> +  m = 0; /* { dg-error "incompatible types" } */
> +  (void) m;
> +  nullptr_t o = 0; /* { dg-error "invalid initializer" } */
> +
> +  switch (nullptr); /* { dg-error "switch quantity not an integer" } */
> +}
> +
> +/* If a second or third operand of type nullptr_t is used that is not a null
> +   pointer constant and the other operand is not a pointer or does not have
> +   itself nullptr_t, a constraint is violated even if that other operand is
> +   a null pointer constant such as 0.  */
> +void
> +test3 (_Bool b, int i)
> +{
> +  const nullptr_t nptr = nullptr;
> +  __auto_type a1 = b ? nptr : i; /* { dg-error "type mismatch" } */
> +  __auto_type a2 = b ? i : nptr; /* { dg-error "type mismatch" } */
> +  __auto_type a3 = b ? nptr : 0; /* { dg-error "type mismatch" } */
> +  __auto_type a4 = b ? 0 : nptr; /* { dg-error "type mismatch" } */
> +}
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-4.c b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> new file mode 100644
> index 00000000000..5b15e75d159
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> @@ -0,0 +1,10 @@
> +/* Test that we warn about `nullptr' pre-C2X.  */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c17 -pedantic-errors" } */
> +
> +int *
> +fn (int *p)
> +{
> +  p = nullptr; /* { dg-error "ISO C does not support .nullptr. before C2X" } */
> +  return p;
> +}
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-5.c b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
> new file mode 100644
> index 00000000000..7479ab4ea1d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
> @@ -0,0 +1,11 @@
> +/* Test that -Wc11-c2x-compat issues a warning (not a pedwarn) about
> +   `nullptr' in C2X.  */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c2x -pedantic-errors -Wc11-c2x-compat" } */
> +
> +int *
> +fn (int *p)
> +{
> +  p = nullptr; /* { dg-warning "ISO C does not support .nullptr. before C2X" } */
> +  return p;
> +}
> 
> base-commit: 4991e20923b658ce9fbdf5621cab39f71b98fbc2


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2] c: Implement C23 nullptr (N3042)
  2022-08-15 17:48 ` Joseph Myers
@ 2022-08-24 18:24   ` Marek Polacek
  2022-08-25 17:28     ` Joseph Myers
  0 siblings, 1 reply; 9+ messages in thread
From: Marek Polacek @ 2022-08-24 18:24 UTC (permalink / raw)
  To: Joseph Myers; +Cc: GCC Patches

On Mon, Aug 15, 2022 at 05:48:34PM +0000, Joseph Myers wrote:
> On Sat, 13 Aug 2022, Marek Polacek via Gcc-patches wrote:
> 
> > This patch also defines nullptr_t in <stddef.h>.  I'm uncertain about
> > the __STDC_VERSION__ version I should be checking.  Also, I'm not
> 
> We're using defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L until 
> the final version for C23 is settled.
 
OK, adjusted.

> > defining __STDC_VERSION_STDDEF_H__ yet, because I don't know what value
> > it should be defined to.  Do we know yet?
> 
> No, and Jens's comments on the editorial review before CD ballot include 
> that lots of headers don't yet have such a macro definition but should 
> have one, as well as needing consistency for the numbers.
> 
> We won't know the final values for these macros until much later, because 
> the timescale depends on whether ISO decides to delay things at any point 
> by coming up with a long list of editorial issues required to follow the 
> JTC1 Directives as they did for C17 (objections to particular words 
> appearing in non-normative text, etc.).
 
Ack, thanks.

> > -  { "nullptr",		RID_NULLPTR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
> > +  { "nullptr",		RID_NULLPTR,	D_CXX11 | D_CXXWARN },
> 
> You need to use D_C2X (which doesn't yet exist).  In pre-C23 modes, 
> nullptr needs to be a normal identifier that can be used in all contexts 
> where identifiers can be used, not a keyword at all (and then you need a 
> c11-nullptr*.c test to verify that use of it as an identifier works as 
> expected).

Fixed.  Adding D_C2X meant that I had to enlarge struct c_common_resword
by a word.
 
> > diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
> > index 18083d59618..013fe6b2a53 100644
> > --- a/gcc/c/c-convert.cc
> > +++ b/gcc/c/c-convert.cc
> > @@ -133,6 +133,14 @@ c_convert (tree type, tree expr, bool init_const)
> >  	(loc, type, c_objc_common_truthvalue_conversion (input_location, expr));
> >  
> >      case POINTER_TYPE:
> > +      /* The type nullptr_t may be converted to a pointer type.  The result is
> > +	 a null pointer value.  */
> > +      if (NULLPTR_TYPE_P (TREE_TYPE (e)))
> > +	{
> > +	  ret = build_int_cst (type, 0);
> > +	  goto maybe_fold;
> > +	}
> 
> That looks like it would lose side-effects.  You need to preserve 
> side-effects in an expression of nullptr_t type being converted to a 
> pointer type, and need an execution testcase that verifies such 
> side-effects are preserved.

Ah, of course.  Fixed by building up a COMPOUND_EXPR.  Covered by
c2x-nullptr-5.c.
 
> Also, you need to make sure that (void *)nullptr is not treated as a null 
> pointer constant, only a null pointer; build_int_cst (type, 0) would 
> produce a null pointer constant when type is void *.  (void *)nullptr 
> should be handled similarly to (void *)(void *)0, which isn't a null 
> pointer constant either.

That (void *)nullptr is not considered an NPC ought to be achieved by
build_c_cast wrapping the result of c_convert in a NOP_EXPR:

  /* Don't allow the results of casting to floating-point or complex
     types be confused with actual constants, or casts involving
     integer and pointer types other than direct integer-to-integer
     and integer-to-pointer be confused with integer constant
     expressions and null pointer constants.  */

Then null_pointer_constant_p sees the NOP_EXPR and returns false.  I've
added a comment explaining that.

> > @@ -133,6 +133,13 @@ null_pointer_constant_p (const_tree expr)
> >    /* This should really operate on c_expr structures, but they aren't
> >       yet available everywhere required.  */
> >    tree type = TREE_TYPE (expr);
> > +
> > +  /* An integer constant expression with the value 0, such an expression
> > +     cast to type void*, or the predefined constant nullptr, are a null
> > +     pointer constant.  */
> > +  if (NULLPTR_TYPE_P (type))
> > +    return true;
> 
> That looks wrong.  You need to distinguish null pointer constants of type 
> nullptr_t (nullptr, possibly enclosed in parentheses, possibly the 
> selected alternative from _Generic) from all other expressions of type 
> nullptr_t (including (nullptr_t)nullptr, which isn't a null pointer 
> constant any more than (void *)(void *)0).

Ah, okay.  I had just copied what we do in C++ in null_ptr_cst_p and the
rest of the patch worked under that assumption.  I've added some tests
for this too.  Except I don't really understand the _Generic comment so
I only have tests for _Generic that were in the previous version.

Changing null_pointer_constant_p mean that I had to adjust
build_binary_op/EQ_EXPR.
 
> Then, for each context where it matters whether a nullptr_t value is a 
> null pointer constant, there need to be testcases that the two cases are 
> properly distinguished.  This includes at least equality comparisons with 
> a pointer that is not a null pointer constant (seem only to be allowed 
> with nullptr, not with other nullptr_t expressions).  (I think for 
> conditional expressions, conditionals between nullptr_t and an integer 
> null pointer constant are always invalid, whether or not the nullptr_t is 
> a null pointer constant, while conditionals between nullptr_t and a 
> pointer are always valid.)

I see, it's because 6.5.9 says that if one of the operands of == or !=
has type nullptr_t but the other one doesn't, the other operand has to
be an NPC, not just a null pointer.

This should be tested in c2x-nullptr-1.c:test2 and c2x-nullptr-3.c:test1.
For instance, "p == (nullptr_t)nullptr", where p is a pointer, does not
compile.
 
> > +/* The type nullptr_t shall not be converted to any type other than bool or
> > +   a pointer type.  No type other than nullptr_t shall be converted to nullptr_t.  */
> 
> That's other than *void*, bool or a pointer type.  (That's a correct fix 
> to the N3042 wording in N3047.  There are other problems in the 
> integration of nullptr in N3047 that are only fixed in my subsequent fixes 
> as part of the editorial review - and many issues with integration of 
> other papers that haven't yet been fixed, I currently have 25 open merge 
> requests resulting from editorial review.)  And of course conversions from 
> nullptr_t to void should be tested.

Thanks, tests added to c2x-nullptr-1.c:test1.  I notice that 6.3.2.4 still
says "The type nullptr_t may be converted to bool or to a pointer type";
isn't it missing the ", void" here too?
 
> > diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-4.c b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> > new file mode 100644
> > index 00000000000..5b15e75d159
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> > @@ -0,0 +1,10 @@
> > +/* Test that we warn about `nullptr' pre-C2X.  */
> > +/* { dg-do compile } */
> > +/* { dg-options "-std=c17 -pedantic-errors" } */
> 
> This test is wrong - it's a normal identifier pre-C2x - but tests for 
> previous standard versions shouldn't be called c2x-* in any case.

I see.  I've removed this test and instead added c17-nullptr-2.c.

Thank you for the careful review.

Bootstrap/regtest running on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch implements the C23 nullptr literal:
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm> (with
wording fixes from N3047), which is intended to replace the problematic
definition of NULL which might be either of integer type or void*.

Since C++ has had nullptr for over a decade now, it was relatively easy
to just move the built-in node definitions from the C++ FE to the C/C++
common code.  Also, our DWARF emitter already handles NULLPTR_TYPE by
emitting DW_TAG_unspecified_type.  However, I had to handle a lot of
contexts such as ?:, comparison, conversion, etc.

There are some minor differences, e.g. in C you can do

  bool b = nullptr;

but in C++ you have to use direct-initialization:

  bool b{nullptr};

And I think that

  nullptr_t n = 0;

is only valid in C++.

Of course, C doesn't have to handle mangling, RTTI, substitution,
overloading, ...

This patch also defines nullptr_t in <stddef.h>.  However, it does not
define __STDC_VERSION_STDDEF_H__ yet, because we don't know yet what value
it should be defined to.

gcc/c-family/ChangeLog:

	* c-common.cc (c_common_reswords): Enable nullptr in C2X.
	(c_common_nodes_and_builtins): Create the built-in node for nullptr.
	* c-common.h (enum c_tree_index): Add CTI_NULLPTR, CTI_NULLPTR_TYPE.
	(struct c_common_resword): Resize the disable member.
	(D_C2X): Add.
	(nullptr_node): Define.
	(nullptr_type_node): Define.
	(NULLPTR_TYPE_P): Define.
	* c-pretty-print.cc (c_pretty_printer::simple_type_specifier): Handle
	NULLPTR_TYPE.
	(c_pretty_printer::direct_abstract_declarator): Likewise.
	(c_pretty_printer::constant): Likewise.

gcc/c/ChangeLog:

	* c-convert.cc (c_convert) <case POINTER_TYPE>: Handle NULLPTR_TYPE.
	Give a better diagnostic when converting to nullptr_t.
	* c-decl.cc (c_init_decl_processing): Perform C-specific nullptr
	initialization.
	* c-parser.cc (c_parse_init): Maybe OR D_C2X into mask.
	(c_parser_postfix_expression): Handle RID_NULLPTR.
	* c-typeck.cc (null_pointer_constant_p): Return true when expr is
	nullptr_node.
	(build_unary_op) <case TRUTH_NOT_EXPR>: Handle NULLPTR_TYPE.
	(build_conditional_expr): Handle the case when the second/third operand
	is NULLPTR_TYPE and third/second operand is POINTER_TYPE.
	(convert_for_assignment): Handle converting an expression of type
	nullptr_t to pointer/bool.
	(build_binary_op) <case TRUTH_XOR_EXPR>: Handle NULLPTR_TYPE.
	<case EQ_EXPR>: Handle comparing operands of type nullptr_t.

gcc/cp/ChangeLog:

	* cp-tree.h (enum cp_tree_index): Remove CTI_NULLPTR, CTI_NULLPTR_TYPE.
	Move it to c_tree_index.
	(nullptr_node): No longer define here.
	(nullptr_type_node): Likewise.
	(NULLPTR_TYPE_P): Likewise.
	* decl.cc (cxx_init_decl_processing): Only keep C++-specific nullptr
	initialization; move the shared code to c_common_nodes_and_builtins.

gcc/ChangeLog:

	* ginclude/stddef.h: Define nullptr_t.

gcc/testsuite/ChangeLog:

	* gcc.dg/c11-nullptr-1.c: New test.
	* gcc.dg/c17-nullptr-1.c: New test.
	* gcc.dg/c17-nullptr-2.c: New test.
	* gcc.dg/c2x-nullptr-1.c: New test.
	* gcc.dg/c2x-nullptr-2.c: New test.
	* gcc.dg/c2x-nullptr-3.c: New test.
	* gcc.dg/c2x-nullptr-4.c: New test.
	* gcc.dg/c2x-nullptr-5.c: New test.
---
 gcc/c-family/c-common.cc             |  20 ++-
 gcc/c-family/c-common.h              |  37 ++--
 gcc/c-family/c-pretty-print.cc       |   7 +
 gcc/c/c-convert.cc                   |  25 ++-
 gcc/c/c-decl.cc                      |   6 +
 gcc/c/c-parser.cc                    |  10 ++
 gcc/c/c-typeck.cc                    |  57 +++++-
 gcc/cp/cp-tree.h                     |   8 -
 gcc/cp/decl.cc                       |   8 +-
 gcc/ginclude/stddef.h                |   8 +
 gcc/testsuite/gcc.dg/c11-nullptr-1.c |  10 ++
 gcc/testsuite/gcc.dg/c17-nullptr-1.c |  10 ++
 gcc/testsuite/gcc.dg/c17-nullptr-2.c |  10 ++
 gcc/testsuite/gcc.dg/c2x-nullptr-1.c | 259 +++++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/c2x-nullptr-2.c |   9 +
 gcc/testsuite/gcc.dg/c2x-nullptr-3.c |  78 ++++++++
 gcc/testsuite/gcc.dg/c2x-nullptr-4.c |  11 ++
 gcc/testsuite/gcc.dg/c2x-nullptr-5.c |  14 ++
 18 files changed, 545 insertions(+), 42 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/c11-nullptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c17-nullptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c17-nullptr-2.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-2.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-3.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-4.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-5.c

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 6e41ceb38e9..82ebe7c4502 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -324,8 +324,10 @@ static bool nonnull_check_p (tree, unsigned HOST_WIDE_INT);
    if they match the mask.
 
    Masks for languages:
-   C --std=c89: D_C99 | D_CXXONLY | D_OBJC | D_CXX_OBJC
-   C --std=c99: D_CXXONLY | D_OBJC
+   C --std=c89: D_C99 | D_C2X | D_CXXONLY | D_OBJC | D_CXX_OBJC
+   C --std=c99: D_C2X | D_CXXONLY | D_OBJC
+   C --std=c17: D_C2X | D_CXXONLY | D_OBJC
+   C --std=c2x: D_CXXONLY | D_OBJC
    ObjC is like C except that D_OBJC and D_CXX_OBJC are not set
    C++ --std=c++98: D_CONLY | D_CXX11 | D_CXX20 | D_OBJC
    C++ --std=c++11: D_CONLY | D_CXX20 | D_OBJC
@@ -500,7 +502,7 @@ const struct c_common_resword c_common_reswords[] =
   { "namespace",	RID_NAMESPACE,	D_CXXONLY | D_CXXWARN },
   { "new",		RID_NEW,	D_CXXONLY | D_CXXWARN },
   { "noexcept",		RID_NOEXCEPT,	D_CXXONLY | D_CXX11 | D_CXXWARN },
-  { "nullptr",		RID_NULLPTR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
+  { "nullptr",		RID_NULLPTR,	D_C2X | D_CXX11 | D_CXXWARN },
   { "operator",		RID_OPERATOR,	D_CXXONLY | D_CXXWARN },
   { "private",		RID_PRIVATE,	D_CXX_OBJC | D_CXXWARN },
   { "protected",	RID_PROTECTED,	D_CXX_OBJC | D_CXXWARN },
@@ -4723,6 +4725,18 @@ c_common_nodes_and_builtins (void)
   null_node = make_int_cst (1, 1);
   TREE_TYPE (null_node) = c_common_type_for_size (POINTER_SIZE, 0);
 
+  /* Create the built-in nullptr node.  This part of its initialization is
+     common to C and C++.  The front ends can further adjust its definition
+     in {c,cxx}_init_decl_processing.  In particular, we aren't setting the
+     alignment here for C++ backward ABI bug compatibility.  */
+  nullptr_type_node = make_node (NULLPTR_TYPE);
+  TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
+  TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
+  TYPE_UNSIGNED (nullptr_type_node) = 1;
+  TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
+  SET_TYPE_MODE (nullptr_type_node, ptr_mode);
+  nullptr_node = build_int_cst (nullptr_type_node, 0);
+
   /* Since builtin_types isn't gc'ed, don't export these nodes.  */
   memset (builtin_types, 0, sizeof (builtin_types));
 }
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c06769b6f0b..e7b0fd1309d 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -375,6 +375,8 @@ enum c_tree_index
     CTI_DEFAULT_FUNCTION_TYPE,
 
     CTI_NULL,
+    CTI_NULLPTR,
+    CTI_NULLPTR_TYPE,
 
     /* These are not types, but we have to look them up all the time.  */
     CTI_FUNCTION_NAME_DECL,
@@ -409,7 +411,7 @@ struct c_common_resword
 {
   const char *const word;
   ENUM_BITFIELD(rid) const rid : 16;
-  const unsigned int disable   : 16;
+  const unsigned int disable   : 32;
 };
 
 /* Mode used to build pointers (VOIDmode means ptr_mode).  */
@@ -447,19 +449,20 @@ extern machine_mode c_default_pointer_mode;
 #define D_CONLY		0x0001	/* C only (not in C++).  */
 #define D_CXXONLY	0x0002	/* C++ only (not in C).  */
 #define D_C99		0x0004	/* In C, C99 only.  */
-#define D_CXX11         0x0008	/* In C++, C++11 only.  */
-#define D_EXT		0x0010	/* GCC extension.  */
-#define D_EXT89		0x0020	/* GCC extension incorporated in C99.  */
-#define D_ASM		0x0040	/* Disabled by -fno-asm.  */
-#define D_OBJC		0x0080	/* In Objective C and neither C nor C++.  */
-#define D_CXX_OBJC	0x0100	/* In Objective C, and C++, but not C.  */
-#define D_CXXWARN	0x0200	/* In C warn with -Wcxx-compat.  */
-#define D_CXX_CONCEPTS  0x0400	/* In C++, only with concepts.  */
-#define D_TRANSMEM	0X0800	/* C++ transactional memory TS.  */
-#define D_CXX_CHAR8_T	0X1000	/* In C++, only with -fchar8_t.  */
-#define D_CXX20		0x2000  /* In C++, C++20 only.  */
-#define D_CXX_COROUTINES 0x4000  /* In C++, only with coroutines.  */
-#define D_CXX_MODULES	0x8000  /* In C++, only with modules.  */
+#define D_C2X		0x0008	/* In C, C2X only.  */
+#define D_CXX11         0x0010	/* In C++, C++11 only.  */
+#define D_EXT		0x0020	/* GCC extension.  */
+#define D_EXT89		0x0040	/* GCC extension incorporated in C99.  */
+#define D_ASM		0x0080	/* Disabled by -fno-asm.  */
+#define D_OBJC		0x0100	/* In Objective C and neither C nor C++.  */
+#define D_CXX_OBJC	0x0200	/* In Objective C, and C++, but not C.  */
+#define D_CXXWARN	0x0400	/* In C warn with -Wcxx-compat.  */
+#define D_CXX_CONCEPTS  0x0800	/* In C++, only with concepts.  */
+#define D_TRANSMEM	0x1000	/* C++ transactional memory TS.  */
+#define D_CXX_CHAR8_T	0x2000	/* In C++, only with -fchar8_t.  */
+#define D_CXX20		0x4000  /* In C++, C++20 only.  */
+#define D_CXX_COROUTINES 0x8000  /* In C++, only with coroutines.  */
+#define D_CXX_MODULES	0x10000  /* In C++, only with modules.  */
 
 #define D_CXX_CONCEPTS_FLAGS D_CXXONLY | D_CXX_CONCEPTS
 #define D_CXX_CHAR8_T_FLAGS D_CXXONLY | D_CXX_CHAR8_T
@@ -534,6 +537,9 @@ extern const unsigned int num_c_common_reswords;
 
 /* The node for C++ `__null'.  */
 #define null_node                       c_global_trees[CTI_NULL]
+/* The nodes for `nullptr'.  */
+#define nullptr_node                    c_global_trees[CTI_NULLPTR]
+#define nullptr_type_node               c_global_trees[CTI_NULLPTR_TYPE]
 
 extern GTY(()) tree c_global_trees[CTI_MAX];
 
@@ -1009,6 +1015,9 @@ extern void c_parse_final_cleanups (void);
 #define DECL_UNNAMED_BIT_FIELD(NODE) \
   (DECL_C_BIT_FIELD (NODE) && !DECL_NAME (NODE))
 
+/* True iff TYPE is cv decltype(nullptr).  */
+#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
+
 extern tree do_case (location_t, tree, tree);
 extern tree build_stmt (location_t, enum tree_code, ...);
 extern tree build_real_imag_expr (location_t, enum tree_code, tree);
diff --git a/gcc/c-family/c-pretty-print.cc b/gcc/c-family/c-pretty-print.cc
index 71a0cb51093..efa1768f4d6 100644
--- a/gcc/c-family/c-pretty-print.cc
+++ b/gcc/c-family/c-pretty-print.cc
@@ -321,6 +321,7 @@ pp_c_pointer (c_pretty_printer *pp, tree t)
       _Bool                          -- C99
       _Complex                       -- C99
       _Imaginary                     -- C99
+      nullptr_t                      -- C23
       struct-or-union-specifier
       enum-specifier
       typedef-name.
@@ -424,6 +425,9 @@ c_pretty_printer::simple_type_specifier (tree t)
       else
 	translate_string ("<anonymous>");
       break;
+    case NULLPTR_TYPE:
+      pp_c_ws_string (this, "nullptr_t");
+      break;
 
     default:
       pp_unsupported_tree (this, t);
@@ -678,6 +682,7 @@ c_pretty_printer::direct_abstract_declarator (tree t)
     case COMPLEX_TYPE:
     case TYPE_DECL:
     case ERROR_MARK:
+    case NULLPTR_TYPE:
       break;
 
     default:
@@ -1219,6 +1224,8 @@ c_pretty_printer::constant (tree e)
 	  pp_c_character_constant (this, e);
 	else if (TREE_CODE (type) == ENUMERAL_TYPE)
 	  pp_c_enumeration_constant (this, e);
+	else if (NULLPTR_TYPE_P (type))
+	  pp_string (this, "nullptr");
 	else
 	  pp_c_integer_constant (this, e);
       }
diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
index 18083d59618..6e7491339d4 100644
--- a/gcc/c/c-convert.cc
+++ b/gcc/c/c-convert.cc
@@ -133,6 +133,20 @@ c_convert (tree type, tree expr, bool init_const)
 	(loc, type, c_objc_common_truthvalue_conversion (input_location, expr));
 
     case POINTER_TYPE:
+      /* The type nullptr_t may be converted to a pointer type.  The result is
+	 a null pointer value.  */
+      if (NULLPTR_TYPE_P (TREE_TYPE (e)))
+	{
+	  /* To make sure that (void *)nullptr is not a null pointer constant,
+	     build_c_cast will create an additional NOP_EXPR around the result
+	     of this conversion.  */
+	  if (TREE_SIDE_EFFECTS (e))
+	    ret = build2 (COMPOUND_EXPR, type, e, build_int_cst (type, 0));
+	  else
+	    ret = build_int_cst (type, 0);
+	  goto maybe_fold;
+	}
+      gcc_fallthrough ();
     case REFERENCE_TYPE:
       ret = convert_to_pointer (type, e);
       goto maybe_fold;
@@ -180,7 +194,16 @@ c_convert (tree type, tree expr, bool init_const)
       return ret;
     }
 
-  error ("conversion to non-scalar type requested");
+  /* If we are converting to nullptr_t, don't say "non-scalar type" because
+     the nullptr_t type is a scalar type.  Only nullptr_t shall be converted
+     to nullptr_t.  */
+  if (code == NULLPTR_TYPE)
+    {
+      error ("conversion from %qT to %qT", TREE_TYPE (e), type);
+      inform (input_location, "only %qT can be converted to %qT", type, type);
+    }
+  else
+    error ("conversion to non-scalar type requested");
   return error_mark_node;
 }
 
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 9e590c66dae..cbba0c62f64 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -4531,6 +4531,12 @@ c_init_decl_processing (void)
   pushdecl (build_decl (UNKNOWN_LOCATION, TYPE_DECL, get_identifier ("_Bool"),
 			boolean_type_node));
 
+  /* C-specific nullptr initialization.  */
+  record_builtin_type (RID_MAX, "nullptr_t", nullptr_type_node);
+  /* The size and alignment of nullptr_t is the same as for a pointer to
+     character type.  */
+  SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
+
   input_location = save_loc;
 
   make_fname_decl = c_make_fname_decl;
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 759f200a7eb..8520fd0e2a8 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -119,6 +119,8 @@ c_parse_init (void)
   mask |= D_CXXONLY;
   if (!flag_isoc99)
     mask |= D_C99;
+  if (!flag_isoc2x)
+    mask |= D_C2X;
   if (flag_no_asm)
     {
       mask |= D_ASM | D_EXT;
@@ -10243,6 +10245,14 @@ c_parser_postfix_expression (c_parser *parser)
 			 "%<depend%> clause");
 	  expr.set_error ();
 	  break;
+	/* C23 'nullptr' literal.  */
+	case RID_NULLPTR:
+	  c_parser_consume_token (parser);
+	  expr.value = nullptr_node;
+	  set_c_expr_source_range (&expr, tok_range);
+	  pedwarn_c11 (loc, OPT_Wpedantic,
+		       "ISO C does not support %qs before C2X", "nullptr");
+	  break;
 	default:
 	  c_parser_error (parser, "expected expression");
 	  expr.set_error ();
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index de8780a1502..3b5275b6e0a 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -133,6 +133,13 @@ null_pointer_constant_p (const_tree expr)
   /* This should really operate on c_expr structures, but they aren't
      yet available everywhere required.  */
   tree type = TREE_TYPE (expr);
+
+  /* An integer constant expression with the value 0, such an expression
+     cast to type void*, or the predefined constant nullptr, are a null
+     pointer constant.  */
+  if (expr == nullptr_node)
+    return true;
+
   return (TREE_CODE (expr) == INTEGER_CST
 	  && !TREE_OVERFLOW (expr)
 	  && integer_zerop (expr)
@@ -4575,7 +4582,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg,
     case TRUTH_NOT_EXPR:
       if (typecode != INTEGER_TYPE && typecode != FIXED_POINT_TYPE
 	  && typecode != REAL_TYPE && typecode != POINTER_TYPE
-	  && typecode != COMPLEX_TYPE)
+	  && typecode != COMPLEX_TYPE && typecode != NULLPTR_TYPE)
 	{
 	  error_at (location,
 		    "wrong type argument to unary exclamation mark");
@@ -5515,6 +5522,13 @@ build_conditional_expr (location_t colon_loc, tree ifexp, bool ifexp_bcp,
 	}
       result_type = type2;
     }
+  /* 6.5.15: "if one is a null pointer constant (other than a pointer) or has
+     type nullptr_t and the other is a pointer, the result type is the pointer
+     type."  */
+  else if (code1 == NULLPTR_TYPE && code2 == POINTER_TYPE)
+    result_type = type2;
+  else if (code1 == POINTER_TYPE && code2 == NULLPTR_TYPE)
+    result_type = type1;
 
   if (!result_type)
     {
@@ -7613,12 +7627,13 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 	error_at (location, msg);
       return error_mark_node;
     }
-  else if (codel == POINTER_TYPE && coder == INTEGER_TYPE)
+  else if (codel == POINTER_TYPE
+	   && (coder == INTEGER_TYPE || coder == NULLPTR_TYPE))
     {
-      /* An explicit constant 0 can convert to a pointer,
-	 or one that results from arithmetic, even including
-	 a cast to integer type.  */
-      if (!null_pointer_constant)
+      /* An explicit constant 0 or type nullptr_t can convert to a pointer,
+	 or one that results from arithmetic, even including a cast to
+	 integer type.  */
+      if (!null_pointer_constant && coder != NULLPTR_TYPE)
 	switch (errtype)
 	  {
 	  case ic_argpass:
@@ -7691,7 +7706,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 
       return convert (type, rhs);
     }
-  else if (codel == BOOLEAN_TYPE && coder == POINTER_TYPE)
+  else if (codel == BOOLEAN_TYPE
+	   /* The type nullptr_t may be converted to bool.  The
+	      result is false.  */
+	   && (coder == POINTER_TYPE || coder == NULLPTR_TYPE))
     {
       tree ret;
       bool save = in_late_binary_op;
@@ -12107,10 +12125,10 @@ build_binary_op (location_t location, enum tree_code code,
     case TRUTH_XOR_EXPR:
       if ((code0 == INTEGER_TYPE || code0 == POINTER_TYPE
 	   || code0 == REAL_TYPE || code0 == COMPLEX_TYPE
-	   || code0 == FIXED_POINT_TYPE)
+	   || code0 == FIXED_POINT_TYPE || code0 == NULLPTR_TYPE)
 	  && (code1 == INTEGER_TYPE || code1 == POINTER_TYPE
 	      || code1 == REAL_TYPE || code1 == COMPLEX_TYPE
-	      || code1 == FIXED_POINT_TYPE))
+	      || code1 == FIXED_POINT_TYPE || code1 ==  NULLPTR_TYPE))
 	{
 	  /* Result of these operations is always an int,
 	     but that does not mean the operands should be
@@ -12418,6 +12436,27 @@ build_binary_op (location_t location, enum tree_code code,
 	  result_type = type1;
 	  pedwarn (location, 0, "comparison between pointer and integer");
 	}
+      /* 6.5.9: One of the following shall hold:
+	 -- both operands have type nullptr_t;  */
+      else if (code0 == NULLPTR_TYPE && code1 == NULLPTR_TYPE)
+	{
+	  result_type = nullptr_type_node;
+	  /* No need to convert the operands to result_type later.  */
+	  converted = 1;
+	}
+    /* -- one operand has type nullptr_t and the other is a null pointer
+       constant.  We will have to convert the former to the type of the
+       latter, because during gimplification we can't have mismatching
+       comparison operand type.  We convert from nullptr_t to the other
+       type, since only nullptr_t can be converted to nullptr_t.  Also,
+       even a constant 0 is a null pointer constant, so we may have to
+       create a pointer type from its type.  */
+      else if (code0 == NULLPTR_TYPE && null_pointer_constant_p (orig_op1))
+	result_type = (INTEGRAL_TYPE_P (type1)
+		       ? build_pointer_type (type1) : type1);
+      else if (code1 == NULLPTR_TYPE && null_pointer_constant_p (orig_op0))
+	result_type = (INTEGRAL_TYPE_P (type0)
+		       ? build_pointer_type (type0) : type0);
       if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
 	   || truth_value_p (TREE_CODE (orig_op0)))
 	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 9f2ff3728b4..c897da204fe 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -187,9 +187,6 @@ enum cp_tree_index
     CPTI_NOEXCEPT_FALSE_SPEC,
     CPTI_NOEXCEPT_DEFERRED_SPEC,
 
-    CPTI_NULLPTR,
-    CPTI_NULLPTR_TYPE,
-
     CPTI_ANY_TARG,
 
     CPTI_MODULE_HWM,
@@ -254,8 +251,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
 #define conv_op_marker			cp_global_trees[CPTI_CONV_OP_MARKER]
 #define abort_fndecl			cp_global_trees[CPTI_ABORT_FNDECL]
 #define current_aggr			cp_global_trees[CPTI_AGGR_TAG]
-#define nullptr_node			cp_global_trees[CPTI_NULLPTR]
-#define nullptr_type_node		cp_global_trees[CPTI_NULLPTR_TYPE]
 /* std::align_val_t */
 #define align_type_node			cp_global_trees[CPTI_ALIGN_TYPE]
 
@@ -4405,9 +4400,6 @@ get_vec_init_expr (tree t)
    || TREE_CODE (TYPE) == REAL_TYPE \
    || TREE_CODE (TYPE) == COMPLEX_TYPE)
 
-/* True iff TYPE is cv decltype(nullptr).  */
-#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
-
 /* [basic.types]
 
    Arithmetic types, enumeration types, pointer types,
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 84a1a011341..d46a347a6c7 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -4793,16 +4793,10 @@ cxx_init_decl_processing (void)
 	  }
       }
 
-    nullptr_type_node = make_node (NULLPTR_TYPE);
-    TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
-    TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
-    TYPE_UNSIGNED (nullptr_type_node) = 1;
-    TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
+    /* C++-specific nullptr initialization.  */
     if (abi_version_at_least (9))
       SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
-    SET_TYPE_MODE (nullptr_type_node, ptr_mode);
     record_builtin_type (RID_MAX, "decltype(nullptr)", nullptr_type_node);
-    nullptr_node = build_int_cst (nullptr_type_node, 0);
   }
 
   if (! supports_one_only ())
diff --git a/gcc/ginclude/stddef.h b/gcc/ginclude/stddef.h
index 79e296d4a66..315ff786694 100644
--- a/gcc/ginclude/stddef.h
+++ b/gcc/ginclude/stddef.h
@@ -443,6 +443,14 @@ typedef struct {
 #endif
 #endif /* C++11.  */
 
+#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L)
+#ifndef _GCC_NULLPTR_T
+#define _GCC_NULLPTR_T
+  typedef __typeof__(nullptr) nullptr_t;
+/* ??? This doesn't define __STDC_VERSION_STDDEF_H__ yet.  */
+#endif
+#endif /* C23.  */
+
 #endif /* _STDDEF_H was defined this time */
 
 #endif /* !_STDDEF_H && !_STDDEF_H_ && !_ANSI_STDDEF_H && !__STDDEF_H__
diff --git a/gcc/testsuite/gcc.dg/c11-nullptr-1.c b/gcc/testsuite/gcc.dg/c11-nullptr-1.c
new file mode 100644
index 00000000000..c4faedc2c91
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c11-nullptr-1.c
@@ -0,0 +1,10 @@
+/* Test that in pre-C23 modes, nullptr is a normal identifier,
+   not a keyword.  */
+/* { dg-options "-std=c11 -pedantic-errors" } */
+
+int nullptr;
+
+void
+f (int nullptr)
+{
+}
diff --git a/gcc/testsuite/gcc.dg/c17-nullptr-1.c b/gcc/testsuite/gcc.dg/c17-nullptr-1.c
new file mode 100644
index 00000000000..92e43b9df23
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c17-nullptr-1.c
@@ -0,0 +1,10 @@
+/* Test that in pre-C23 modes, nullptr is a normal identifier,
+   not a keyword.  */
+/* { dg-options "-std=c17 -pedantic-errors" } */
+
+int nullptr;
+
+void
+f (int nullptr)
+{
+}
diff --git a/gcc/testsuite/gcc.dg/c17-nullptr-2.c b/gcc/testsuite/gcc.dg/c17-nullptr-2.c
new file mode 100644
index 00000000000..a6ad7703eeb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c17-nullptr-2.c
@@ -0,0 +1,10 @@
+/* Test that we don't predefine `nullptr' pre-C2X.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c17 -pedantic-errors" } */
+
+int *
+fn (int *p)
+{
+  p = nullptr; /* { dg-error "'nullptr' undeclared" } */
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-1.c b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
new file mode 100644
index 00000000000..ca01b3e3296
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
@@ -0,0 +1,259 @@
+/* Test valid usage of C23 nullptr.  */
+/* { dg-do run } */
+/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
+
+#include <stdarg.h>
+
+typedef __typeof__(nullptr) nullptr_t;
+
+void f1 (nullptr_t) { }
+void f2 (int *) { }
+void f3 (_Bool) { }
+nullptr_t cmp (void) { return nullptr; }
+
+/* The type nullptr_t shall not be converted to any type other than void, bool or
+   a pointer type.  No type other than nullptr_t shall be converted to nullptr_t.  */
+void
+test1 (void)
+{
+  const nullptr_t nptr = nullptr;
+  static nullptr_t static_nptr;
+  int *p1 = nullptr;
+  void *p2 = nullptr;
+  float *p3 = nullptr;
+  void (*p4)(int) = nullptr;
+  int (*p5)[10] = nullptr;
+  int *p6 = nptr;
+  void *p7 = nptr;
+  float *p8 = nptr;
+  void (*p9)(int) = nptr;
+  int (*p10)[10] = nptr;
+  int *p11 = (int *) nullptr;
+  int *p12 = (int *) nptr;
+  if (nullptr || p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8 || p9 || p10
+      || p11 || p12)
+    __builtin_abort ();
+
+  _Bool b1 = nullptr;
+  _Bool b2 = (_Bool) nullptr;
+  _Bool b3 = nptr;
+  _Bool b4 = (_Bool) nptr;
+  if (b1 || b2 || b3 || b4 || (_Bool) nullptr || (_Bool) nptr)
+   __builtin_abort ();
+
+  __auto_type a1 = nullptr;
+  __auto_type a2 = nptr;
+
+  /* We can convert nullptr_t to nullptr_t.  */
+  __typeof__(nullptr) x = nullptr;
+  f1 (x);
+  f1 (nullptr);
+  f2 (x);
+  f2 (nullptr);
+  f3 (nullptr);
+
+  const nullptr_t np1 = nullptr;
+  const nullptr_t np2 = np1;
+  (void) nullptr;
+  (void) np1;
+  (void) np2;
+  (void) cmp ();
+  (void)(nullptr_t) nullptr;
+}
+
+/* Test valid comparison.  */
+void
+test2 (int *p)
+{
+  /* If both operands have type nullptr_t or one operand has type nullptr_t
+     and the other is a null pointer constant, they compare equal.  */
+  const nullptr_t nptr = nullptr;
+  int r = 0;
+
+  /* Both operands have type nullptr_t.  */
+  r |= nullptr != nullptr;
+  r |= cmp () != nullptr;
+  r |= nullptr != cmp ();
+  r |= !(nullptr == nullptr);
+  r |= !(cmp () == nullptr);
+  r |= !(nullptr == cmp ());
+  r |= nptr != nptr;
+  r |= cmp () != nptr;
+  r |= nptr != cmp ();
+  r |= !(nptr == nptr);
+  r |= !(cmp () == nptr);
+  r |= !(nptr == cmp ());
+
+ /* One operand has type nullptr_t and the other is a null pointer constant.  */
+  r |= nullptr != (void *) 0;
+  r |= (nullptr) != (void *) 0;
+  r |= !(nullptr == (void *) 0);
+  r |= (void *) 0 != nullptr;
+  r |= (void *) 0 != (nullptr);
+  r |= !((void *) 0 == nullptr);
+  r |= nullptr != 0;
+  r |= (nullptr) != 0;
+  r |= 0 != nullptr;
+  r |= 0 != (nullptr);
+  r |= !(nullptr == 0);
+  r |= !(0 == nullptr);
+  r |= nullptr != 0u;
+  r |= 0u != nullptr;
+  r |= !(nullptr == 0u);
+  r |= !(0u == nullptr);
+  r |= nptr != (void *) 0;
+  r |= !(nptr == (void *) 0);
+  r |= (void *) 0 != nptr;
+  r |= !((void *) 0 == nptr);
+  r |= nptr != 0;
+  r |= 0 != nptr;
+  r |= !(nptr == 0);
+  r |= !(0 == nptr);
+  r |= nptr != 0u;
+  r |= 0u != nptr;
+  r |= !(nptr == 0u);
+  r |= !(0u == nptr);
+  if (r)
+    __builtin_abort ();
+
+  /* One operand is a pointer and the other is a null pointer constant.  */
+  (void) (p == nullptr);
+  (void) (p != nullptr);
+  (void) (nullptr == p);
+  (void) (nullptr != p);
+  (void) (p == (nullptr));
+  (void) (p != (nullptr));
+  (void) ((nullptr) == p);
+  (void) ((nullptr) != p);
+  (void) ((void *)nullptr == nullptr);
+  (void) ((void *)nullptr != nullptr);
+  (void) (nullptr == (void *)nullptr);
+  (void) (nullptr != (void *)nullptr);
+}
+
+/* Test ?:.  */
+void
+test3 (int *p, _Bool b)
+{
+  int x = nullptr ? 1 : 2;
+  (void) x;
+  const nullptr_t nptr = nullptr;
+  /* One of the following shall hold for the second and third operands:
+     -- both operands have nullptr_t type.  */
+  __auto_type r1 = b ? nullptr : nullptr;
+  __auto_type r2 = b ? nptr : nptr;
+  /* -- one operand is a pointer and the other is a null pointer constant
+     or has type nullptr_t;  */
+  __auto_type r3 = b ? p : nullptr;
+  __auto_type r4 = b ? nullptr : p;
+  __auto_type r5 = b ? nptr : p;
+  __auto_type r6 = b ? p : nptr;
+  __auto_type r7 = b ? 0 : p;
+  __auto_type r8 = b ? p : 0;
+  __auto_type r9 = b ? p : cmp ();
+  __auto_type r10 = b ?  cmp () : p;
+}
+
+/* Simple assignment.  */
+void
+test4 (void)
+{
+  /* -- the left operand has an atomic, qualified, or unqualified version of
+     the nullptr_t type and the type of the right is nullptr_t;  */
+  nullptr_t n1;
+  n1 = nullptr;
+  const nullptr_t n2 = nullptr;
+  _Atomic nullptr_t n3 = nullptr;
+  volatile nullptr_t n4 = nullptr;
+  /* -- the left operand is an atomic, qualified, or unqualified pointer,
+     and the type of the right is nullptr_t;  */
+  int *p1 = cmp ();
+  _Atomic int *p2 = cmp ();
+  const int *volatile p3 = cmp ();
+  const int *const *const p4 = cmp ();
+  double (*const p5)(void) = n1;
+  /* -- the left operand is an atomic, qualified, or unqualified bool, and
+     the type of the right is nullptr_t;  */
+  _Bool b1;
+  b1 = cmp ();
+  const _Bool b2 = nullptr;
+  _Atomic _Bool b3;
+  b3 = n1;
+  (void) b1;
+  (void) b3;
+}
+
+/* var_arg etc.  */
+static void
+test5 (int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (va_arg (ap, void *))
+    __builtin_abort ();
+}
+
+/* Operand of alignas, sizeof or typeof operators.  */
+void
+test6 (void)
+{
+  _Static_assert (sizeof (nullptr) == sizeof (void *), "sizeof (nullptr)");
+  _Static_assert (sizeof (nullptr_t) == sizeof (void *), "sizeof (nullptr_t)");
+  _Static_assert (sizeof (nullptr) == sizeof (char *), "sizeof (nullptr)");
+  _Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
+  _Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
+  __typeof__(nullptr) t = nullptr;
+  f1 (t);
+  _Alignas (nullptr_t) char i1 = 'q';
+
+  _Static_assert (_Generic (nullptr, nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (t, nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (cmp (), nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (0, nullptr_t: 1, int: 2, default: 0) == 2, "_Generic");
+  _Static_assert (_Generic ((void *)0, nullptr_t: 1, void *: 2, default: 0) == 2, "_Generic");
+  _Static_assert (_Generic (nullptr, nullptr_t: 1, void *: 2, default: 0) == 1, "_Generic");
+}
+
+/* Play with !, ||, &&. */
+void
+test7 (void)
+{
+  if (nullptr)
+    __builtin_abort ();
+  if (1 && nullptr)
+    __builtin_abort ();
+  if (0 || nullptr)
+    __builtin_abort ();
+  if (nullptr && 1)
+    __builtin_abort ();
+  if (nullptr || 0)
+    __builtin_abort ();
+  if (!nullptr)
+    {
+    }
+  else
+    __builtin_abort ();
+  while (nullptr)
+    __builtin_abort ();
+  int i = 0;
+  do
+    ++i;
+  while (nullptr);
+  if (i != 1)
+    __builtin_abort ();
+  for (;nullptr;)
+    __builtin_abort ();
+}
+
+int
+main (void)
+{
+  int i = 42;
+  test1 ();
+  test2 (&i);
+  test3 (&i, 0);
+  test4 ();
+  test5 (42, nullptr);
+  test6 ();
+  test7 ();
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-2.c b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
new file mode 100644
index 00000000000..2cc88353581
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
@@ -0,0 +1,9 @@
+/* Test nullptr_t from <stddef.h..  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+#include <stddef.h>
+
+void f(nullptr_t);
+_Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
+_Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-3.c b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
new file mode 100644
index 00000000000..91e9b1eb883
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
@@ -0,0 +1,78 @@
+/* Test wrong usage of C23 nullptr.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
+
+typedef __typeof__(nullptr) nullptr_t;
+
+void g (nullptr_t); /* { dg-message "expected .nullptr_t. but argument is of type .int." } */
+nullptr_t cmp (void);
+
+void
+test1 (int *p)
+{
+  (void) (p > nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p >= nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p < nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p <= nullptr); /* { dg-error "ordered comparison" } */
+  (void) (nullptr == 1); /* { dg-error "invalid operands" } */
+  (void) (1 == nullptr); /* { dg-error "invalid operands" } */
+  (void) (nullptr != 1); /* { dg-error "invalid operands" } */
+  (void) (1 != nullptr); /* { dg-error "invalid operands" } */
+  (void) (1 > nullptr); /* { dg-error "invalid operands" } */
+
+  /* "(nullptr_t)nullptr" has type nullptr_t but isn't an NPC.  */
+  (void) ((nullptr_t)nullptr == p); /* { dg-error "invalid operands" } */
+  (void) ((nullptr_t)nullptr != p); /* { dg-error "invalid operands" } */
+  (void) (p == (nullptr_t)nullptr); /* { dg-error "invalid operands" } */
+  (void) (p != (nullptr_t)nullptr); /* { dg-error "invalid operands" } */
+  (void) (cmp () == p); /* { dg-error "invalid operands" } */
+  (void) (cmp () != p); /* { dg-error "invalid operands" } */
+  (void) (p == cmp ()); /* { dg-error "invalid operands" } */
+  (void) (p != cmp ()); /* { dg-error "invalid operands" } */
+  /* "(void *)nullptr" is not an NPC, either.  */
+  (void) ((void *)nullptr == cmp ()); /* { dg-error "invalid operands" } */
+  (void) ((void *)nullptr != cmp ()); /* { dg-error "invalid operands" } */
+  (void) (cmp () == (void *)nullptr); /* { dg-error "invalid operands" } */
+  (void) (cmp () != (void *)nullptr); /* { dg-error "invalid operands" } */
+}
+
+void
+test2 (void)
+{
+  const nullptr_t nptr = nullptr;
+  int p = nullptr; /* { dg-error "incompatible types" } */
+  float d = nullptr; /* { dg-error "incompatible types" } */
+  char arr[10] = { nullptr }; /* { dg-error "incompatible types" } */
+
+  /* No type other than nullptr_t shall be converted to nullptr_t.  */
+  const nullptr_t n = 0; /* { dg-error "invalid initializer" } */
+  +(nullptr_t) 0; /* { dg-error "conversion from .int. to .nullptr_t." } */
+
+  g (0); /* { dg-error "incompatible type" } */
+
+  int i = 42 + nullptr; /* { dg-error "invalid operands" } */
+
+  /* The assignment of an object of type nullptr_t with a value of another
+     type, even if the value is a null pointer constant, is a constraint
+     violation.  */
+  nullptr_t m;
+  m = 0; /* { dg-error "incompatible types" } */
+  (void) m;
+  nullptr_t o = 0; /* { dg-error "invalid initializer" } */
+
+  switch (nullptr); /* { dg-error "switch quantity not an integer" } */
+}
+
+/* If a second or third operand of type nullptr_t is used that is not a null
+   pointer constant and the other operand is not a pointer or does not have
+   itself nullptr_t, a constraint is violated even if that other operand is
+   a null pointer constant such as 0.  */
+void
+test3 (_Bool b, int i)
+{
+  const nullptr_t nptr = nullptr;
+  __auto_type a1 = b ? nptr : i; /* { dg-error "type mismatch" } */
+  __auto_type a2 = b ? i : nptr; /* { dg-error "type mismatch" } */
+  __auto_type a3 = b ? nptr : 0; /* { dg-error "type mismatch" } */
+  __auto_type a4 = b ? 0 : nptr; /* { dg-error "type mismatch" } */
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-4.c b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
new file mode 100644
index 00000000000..7479ab4ea1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
@@ -0,0 +1,11 @@
+/* Test that -Wc11-c2x-compat issues a warning (not a pedwarn) about
+   `nullptr' in C2X.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors -Wc11-c2x-compat" } */
+
+int *
+fn (int *p)
+{
+  p = nullptr; /* { dg-warning "ISO C does not support .nullptr. before C2X" } */
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-5.c b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
new file mode 100644
index 00000000000..27803f7d03f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
@@ -0,0 +1,14 @@
+/* Test that we don't lose side-effects when converting from nullptr_t.  */
+/* { dg-do run } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+int i;
+nullptr_t fn () { ++i; return nullptr; }
+
+int
+main ()
+{
+  int *p = fn ();
+  if (i != 1)
+    __builtin_abort ();
+}

base-commit: 530f80451a9e76896a0294e0f4bd59baff1ac27f
-- 
2.37.2


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c: Implement C23 nullptr (N3042)
  2022-08-15 20:03 ` [PATCH] " Jason Merrill
@ 2022-08-24 18:24   ` Marek Polacek
  0 siblings, 0 replies; 9+ messages in thread
From: Marek Polacek @ 2022-08-24 18:24 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches, Joseph Myers

On Mon, Aug 15, 2022 at 04:03:13PM -0400, Jason Merrill wrote:
> On 8/13/22 14:35, Marek Polacek wrote:
> > This patch implements the C23 nullptr literal:
> > <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm>, which is
> > intended to replace the problematic definition of NULL which might be
> > either of integer type or void*.
> > 
> > Since C++ has had nullptr for over a decade now, it was relatively easy
> > to just move the built-in node definitions from the C++ FE to the C/C++
> > common code.  Also, our DWARF emitter already handles NULLPTR_TYPE by
> > emitting DW_TAG_unspecified_type.  However, I had to handle a lot of
> > contexts such as ?:, comparison, conversion, etc.
> > 
> > There are some minor differences, e.g. in C you can do
> > 
> >    bool b = nullptr;
> > 
> > but in C++ you have to use direct-initialization:
> > 
> >    bool b{nullptr};
> > 
> > And I think that
> > 
> >    nullptr_t n = 0;
> > 
> > is only valid in C++.
> > 
> > Of course, C doesn't have to handle mangling, RTTI, substitution,
> > overloading, ...
> > 
> > This patch also defines nullptr_t in <stddef.h>.  I'm uncertain about
> > the __STDC_VERSION__ version I should be checking.  Also, I'm not
> > defining __STDC_VERSION_STDDEF_H__ yet, because I don't know what value
> > it should be defined to.  Do we know yet?
> > 
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> The C++ changes are OK, but you probably want a comment in
> c_common_nodes_and_builtins that we aren't setting the alignment there for
> C++ backward ABI bug compatibility.  Or perhaps set it there and then break
> it in the C++ front end when abi < 9.

Thanks!  I added a comment to that effect in the v2 patch I just posted.

Marek


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] c: Implement C23 nullptr (N3042)
  2022-08-24 18:24   ` [PATCH v2] " Marek Polacek
@ 2022-08-25 17:28     ` Joseph Myers
  2022-08-25 20:51       ` [PATCH v3] " Marek Polacek
  0 siblings, 1 reply; 9+ messages in thread
From: Joseph Myers @ 2022-08-25 17:28 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On Wed, 24 Aug 2022, Marek Polacek via Gcc-patches wrote:

> Ah, okay.  I had just copied what we do in C++ in null_ptr_cst_p and the
> rest of the patch worked under that assumption.  I've added some tests
> for this too.  Except I don't really understand the _Generic comment so
> I only have tests for _Generic that were in the previous version.

The point is that e.g.

_Generic(0, int : nullptr)

is treated the same as nullptr (so is a null pointer constant), just as 
(nullptr) is.

> Thanks, tests added to c2x-nullptr-1.c:test1.  I notice that 6.3.2.4 still
> says "The type nullptr_t may be converted to bool or to a pointer type";
> isn't it missing the ", void" here too?

In general none of the subclauses under 6.3.2 about individual kinds of 
types tend to discuss the possibility of conversion to void.

> +/* Simple assignment.  */
> +void
> +test4 (void)
> +{
> +  /* -- the left operand has an atomic, qualified, or unqualified version of
> +     the nullptr_t type and the type of the right is nullptr_t;  */
> +  nullptr_t n1;
> +  n1 = nullptr;
> +  const nullptr_t n2 = nullptr;
> +  _Atomic nullptr_t n3 = nullptr;
> +  volatile nullptr_t n4 = nullptr;

These qualified cases are all actually initialization, not assignment; I 
think both assignment and initialization (and argument passing and return) 
should be tested for the permitted cases for assignment.

> +/* Test nullptr_t from <stddef.h..  */

Typo, "<stddef.h." should be <stddef.h>".

> +/* If a second or third operand of type nullptr_t is used that is not a null
> +   pointer constant and the other operand is not a pointer or does not have
> +   itself nullptr_t, a constraint is violated even if that other operand is
> +   a null pointer constant such as 0.  */

The "that is not a null pointer constant" in that footnote is a bit odd, 
since it's also a constraint violation (and should be tested as such) to 
have a conditional expression between e.g. nullptr and 0.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3] c: Implement C23 nullptr (N3042)
  2022-08-25 17:28     ` Joseph Myers
@ 2022-08-25 20:51       ` Marek Polacek
  2022-08-25 21:12         ` Joseph Myers
  0 siblings, 1 reply; 9+ messages in thread
From: Marek Polacek @ 2022-08-25 20:51 UTC (permalink / raw)
  To: Joseph Myers; +Cc: GCC Patches

On Thu, Aug 25, 2022 at 05:28:09PM +0000, Joseph Myers wrote:
> On Wed, 24 Aug 2022, Marek Polacek via Gcc-patches wrote:
> 
> > Ah, okay.  I had just copied what we do in C++ in null_ptr_cst_p and the
> > rest of the patch worked under that assumption.  I've added some tests
> > for this too.  Except I don't really understand the _Generic comment so
> > I only have tests for _Generic that were in the previous version.
> 
> The point is that e.g.
> 
> _Generic(0, int : nullptr)
> 
> is treated the same as nullptr (so is a null pointer constant), just as 
> (nullptr) is.

Ah, of course, I was thinking of nullptr_t...

Tests added.  Fortunately this works as expected and no functional
changes were needed.
 
> > Thanks, tests added to c2x-nullptr-1.c:test1.  I notice that 6.3.2.4 still
> > says "The type nullptr_t may be converted to bool or to a pointer type";
> > isn't it missing the ", void" here too?
> 
> In general none of the subclauses under 6.3.2 about individual kinds of 
> types tend to discuss the possibility of conversion to void.
> 
> > +/* Simple assignment.  */
> > +void
> > +test4 (void)
> > +{
> > +  /* -- the left operand has an atomic, qualified, or unqualified version of
> > +     the nullptr_t type and the type of the right is nullptr_t;  */
> > +  nullptr_t n1;
> > +  n1 = nullptr;
> > +  const nullptr_t n2 = nullptr;
> > +  _Atomic nullptr_t n3 = nullptr;
> > +  volatile nullptr_t n4 = nullptr;
> 
> These qualified cases are all actually initialization, not assignment; I 
> think both assignment and initialization (and argument passing and return) 
> should be tested for the permitted cases for assignment.

OK, more tests added.
 
> > +/* Test nullptr_t from <stddef.h..  */
> 
> Typo, "<stddef.h." should be <stddef.h>".

Fixed.
 
> > +/* If a second or third operand of type nullptr_t is used that is not a null
> > +   pointer constant and the other operand is not a pointer or does not have
> > +   itself nullptr_t, a constraint is violated even if that other operand is
> > +   a null pointer constant such as 0.  */
> 
> The "that is not a null pointer constant" in that footnote is a bit odd, 
> since it's also a constraint violation (and should be tested as such) to 
> have a conditional expression between e.g. nullptr and 0.

Okay, I've added some more tests to c2x-nullptr-3.c.

Thanks!

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch implements the C23 nullptr literal:
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm> (with
wording fixes from N3047), which is intended to replace the problematic
definition of NULL which might be either of integer type or void*.

Since C++ has had nullptr for over a decade now, it was relatively easy
to just move the built-in node definitions from the C++ FE to the C/C++
common code.  Also, our DWARF emitter already handles NULLPTR_TYPE by
emitting DW_TAG_unspecified_type.  However, I had to handle a lot of
contexts such as ?:, comparison, conversion, etc.

There are some minor differences, e.g. in C you can do

  bool b = nullptr;

but in C++ you have to use direct-initialization:

  bool b{nullptr};

And I think that

  nullptr_t n = 0;

is only valid in C++.

Of course, C doesn't have to handle mangling, RTTI, substitution,
overloading, ...

This patch also defines nullptr_t in <stddef.h>.  However, it does not
define __STDC_VERSION_STDDEF_H__ yet, because we don't know yet what value
it should be defined to.

gcc/c-family/ChangeLog:

	* c-common.cc (c_common_reswords): Enable nullptr in C2X.
	(c_common_nodes_and_builtins): Create the built-in node for nullptr.
	* c-common.h (enum c_tree_index): Add CTI_NULLPTR, CTI_NULLPTR_TYPE.
	(struct c_common_resword): Resize the disable member.
	(D_C2X): Add.
	(nullptr_node): Define.
	(nullptr_type_node): Define.
	(NULLPTR_TYPE_P): Define.
	* c-pretty-print.cc (c_pretty_printer::simple_type_specifier): Handle
	NULLPTR_TYPE.
	(c_pretty_printer::direct_abstract_declarator): Likewise.
	(c_pretty_printer::constant): Likewise.

gcc/c/ChangeLog:

	* c-convert.cc (c_convert) <case POINTER_TYPE>: Handle NULLPTR_TYPE.
	Give a better diagnostic when converting to nullptr_t.
	* c-decl.cc (c_init_decl_processing): Perform C-specific nullptr
	initialization.
	* c-parser.cc (c_parse_init): Maybe OR D_C2X into mask.
	(c_parser_postfix_expression): Handle RID_NULLPTR.
	* c-typeck.cc (null_pointer_constant_p): Return true when expr is
	nullptr_node.
	(build_unary_op) <case TRUTH_NOT_EXPR>: Handle NULLPTR_TYPE.
	(build_conditional_expr): Handle the case when the second/third operand
	is NULLPTR_TYPE and third/second operand is POINTER_TYPE.
	(convert_for_assignment): Handle converting an expression of type
	nullptr_t to pointer/bool.
	(build_binary_op) <case TRUTH_XOR_EXPR>: Handle NULLPTR_TYPE.
	<case EQ_EXPR>: Handle comparing operands of type nullptr_t.

gcc/cp/ChangeLog:

	* cp-tree.h (enum cp_tree_index): Remove CTI_NULLPTR, CTI_NULLPTR_TYPE.
	Move it to c_tree_index.
	(nullptr_node): No longer define here.
	(nullptr_type_node): Likewise.
	(NULLPTR_TYPE_P): Likewise.
	* decl.cc (cxx_init_decl_processing): Only keep C++-specific nullptr
	initialization; move the shared code to c_common_nodes_and_builtins.

gcc/ChangeLog:

	* ginclude/stddef.h: Define nullptr_t.

gcc/testsuite/ChangeLog:

	* gcc.dg/c11-nullptr-1.c: New test.
	* gcc.dg/c17-nullptr-1.c: New test.
	* gcc.dg/c17-nullptr-2.c: New test.
	* gcc.dg/c2x-nullptr-1.c: New test.
	* gcc.dg/c2x-nullptr-2.c: New test.
	* gcc.dg/c2x-nullptr-3.c: New test.
	* gcc.dg/c2x-nullptr-4.c: New test.
	* gcc.dg/c2x-nullptr-5.c: New test.
---
 gcc/c-family/c-common.cc             |  20 +-
 gcc/c-family/c-common.h              |  37 ++--
 gcc/c-family/c-pretty-print.cc       |   7 +
 gcc/c/c-convert.cc                   |  25 ++-
 gcc/c/c-decl.cc                      |   6 +
 gcc/c/c-parser.cc                    |  10 +
 gcc/c/c-typeck.cc                    |  57 ++++-
 gcc/cp/cp-tree.h                     |   8 -
 gcc/cp/decl.cc                       |   8 +-
 gcc/ginclude/stddef.h                |   8 +
 gcc/testsuite/gcc.dg/c11-nullptr-1.c |  10 +
 gcc/testsuite/gcc.dg/c17-nullptr-1.c |  10 +
 gcc/testsuite/gcc.dg/c17-nullptr-2.c |  10 +
 gcc/testsuite/gcc.dg/c2x-nullptr-1.c | 298 +++++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/c2x-nullptr-2.c |   9 +
 gcc/testsuite/gcc.dg/c2x-nullptr-3.c |  80 +++++++
 gcc/testsuite/gcc.dg/c2x-nullptr-4.c |  11 +
 gcc/testsuite/gcc.dg/c2x-nullptr-5.c |  14 ++
 18 files changed, 586 insertions(+), 42 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/c11-nullptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c17-nullptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c17-nullptr-2.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-2.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-3.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-4.c
 create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-5.c

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 6e41ceb38e9..82ebe7c4502 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -324,8 +324,10 @@ static bool nonnull_check_p (tree, unsigned HOST_WIDE_INT);
    if they match the mask.
 
    Masks for languages:
-   C --std=c89: D_C99 | D_CXXONLY | D_OBJC | D_CXX_OBJC
-   C --std=c99: D_CXXONLY | D_OBJC
+   C --std=c89: D_C99 | D_C2X | D_CXXONLY | D_OBJC | D_CXX_OBJC
+   C --std=c99: D_C2X | D_CXXONLY | D_OBJC
+   C --std=c17: D_C2X | D_CXXONLY | D_OBJC
+   C --std=c2x: D_CXXONLY | D_OBJC
    ObjC is like C except that D_OBJC and D_CXX_OBJC are not set
    C++ --std=c++98: D_CONLY | D_CXX11 | D_CXX20 | D_OBJC
    C++ --std=c++11: D_CONLY | D_CXX20 | D_OBJC
@@ -500,7 +502,7 @@ const struct c_common_resword c_common_reswords[] =
   { "namespace",	RID_NAMESPACE,	D_CXXONLY | D_CXXWARN },
   { "new",		RID_NEW,	D_CXXONLY | D_CXXWARN },
   { "noexcept",		RID_NOEXCEPT,	D_CXXONLY | D_CXX11 | D_CXXWARN },
-  { "nullptr",		RID_NULLPTR,	D_CXXONLY | D_CXX11 | D_CXXWARN },
+  { "nullptr",		RID_NULLPTR,	D_C2X | D_CXX11 | D_CXXWARN },
   { "operator",		RID_OPERATOR,	D_CXXONLY | D_CXXWARN },
   { "private",		RID_PRIVATE,	D_CXX_OBJC | D_CXXWARN },
   { "protected",	RID_PROTECTED,	D_CXX_OBJC | D_CXXWARN },
@@ -4723,6 +4725,18 @@ c_common_nodes_and_builtins (void)
   null_node = make_int_cst (1, 1);
   TREE_TYPE (null_node) = c_common_type_for_size (POINTER_SIZE, 0);
 
+  /* Create the built-in nullptr node.  This part of its initialization is
+     common to C and C++.  The front ends can further adjust its definition
+     in {c,cxx}_init_decl_processing.  In particular, we aren't setting the
+     alignment here for C++ backward ABI bug compatibility.  */
+  nullptr_type_node = make_node (NULLPTR_TYPE);
+  TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
+  TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
+  TYPE_UNSIGNED (nullptr_type_node) = 1;
+  TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
+  SET_TYPE_MODE (nullptr_type_node, ptr_mode);
+  nullptr_node = build_int_cst (nullptr_type_node, 0);
+
   /* Since builtin_types isn't gc'ed, don't export these nodes.  */
   memset (builtin_types, 0, sizeof (builtin_types));
 }
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c06769b6f0b..e7b0fd1309d 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -375,6 +375,8 @@ enum c_tree_index
     CTI_DEFAULT_FUNCTION_TYPE,
 
     CTI_NULL,
+    CTI_NULLPTR,
+    CTI_NULLPTR_TYPE,
 
     /* These are not types, but we have to look them up all the time.  */
     CTI_FUNCTION_NAME_DECL,
@@ -409,7 +411,7 @@ struct c_common_resword
 {
   const char *const word;
   ENUM_BITFIELD(rid) const rid : 16;
-  const unsigned int disable   : 16;
+  const unsigned int disable   : 32;
 };
 
 /* Mode used to build pointers (VOIDmode means ptr_mode).  */
@@ -447,19 +449,20 @@ extern machine_mode c_default_pointer_mode;
 #define D_CONLY		0x0001	/* C only (not in C++).  */
 #define D_CXXONLY	0x0002	/* C++ only (not in C).  */
 #define D_C99		0x0004	/* In C, C99 only.  */
-#define D_CXX11         0x0008	/* In C++, C++11 only.  */
-#define D_EXT		0x0010	/* GCC extension.  */
-#define D_EXT89		0x0020	/* GCC extension incorporated in C99.  */
-#define D_ASM		0x0040	/* Disabled by -fno-asm.  */
-#define D_OBJC		0x0080	/* In Objective C and neither C nor C++.  */
-#define D_CXX_OBJC	0x0100	/* In Objective C, and C++, but not C.  */
-#define D_CXXWARN	0x0200	/* In C warn with -Wcxx-compat.  */
-#define D_CXX_CONCEPTS  0x0400	/* In C++, only with concepts.  */
-#define D_TRANSMEM	0X0800	/* C++ transactional memory TS.  */
-#define D_CXX_CHAR8_T	0X1000	/* In C++, only with -fchar8_t.  */
-#define D_CXX20		0x2000  /* In C++, C++20 only.  */
-#define D_CXX_COROUTINES 0x4000  /* In C++, only with coroutines.  */
-#define D_CXX_MODULES	0x8000  /* In C++, only with modules.  */
+#define D_C2X		0x0008	/* In C, C2X only.  */
+#define D_CXX11         0x0010	/* In C++, C++11 only.  */
+#define D_EXT		0x0020	/* GCC extension.  */
+#define D_EXT89		0x0040	/* GCC extension incorporated in C99.  */
+#define D_ASM		0x0080	/* Disabled by -fno-asm.  */
+#define D_OBJC		0x0100	/* In Objective C and neither C nor C++.  */
+#define D_CXX_OBJC	0x0200	/* In Objective C, and C++, but not C.  */
+#define D_CXXWARN	0x0400	/* In C warn with -Wcxx-compat.  */
+#define D_CXX_CONCEPTS  0x0800	/* In C++, only with concepts.  */
+#define D_TRANSMEM	0x1000	/* C++ transactional memory TS.  */
+#define D_CXX_CHAR8_T	0x2000	/* In C++, only with -fchar8_t.  */
+#define D_CXX20		0x4000  /* In C++, C++20 only.  */
+#define D_CXX_COROUTINES 0x8000  /* In C++, only with coroutines.  */
+#define D_CXX_MODULES	0x10000  /* In C++, only with modules.  */
 
 #define D_CXX_CONCEPTS_FLAGS D_CXXONLY | D_CXX_CONCEPTS
 #define D_CXX_CHAR8_T_FLAGS D_CXXONLY | D_CXX_CHAR8_T
@@ -534,6 +537,9 @@ extern const unsigned int num_c_common_reswords;
 
 /* The node for C++ `__null'.  */
 #define null_node                       c_global_trees[CTI_NULL]
+/* The nodes for `nullptr'.  */
+#define nullptr_node                    c_global_trees[CTI_NULLPTR]
+#define nullptr_type_node               c_global_trees[CTI_NULLPTR_TYPE]
 
 extern GTY(()) tree c_global_trees[CTI_MAX];
 
@@ -1009,6 +1015,9 @@ extern void c_parse_final_cleanups (void);
 #define DECL_UNNAMED_BIT_FIELD(NODE) \
   (DECL_C_BIT_FIELD (NODE) && !DECL_NAME (NODE))
 
+/* True iff TYPE is cv decltype(nullptr).  */
+#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
+
 extern tree do_case (location_t, tree, tree);
 extern tree build_stmt (location_t, enum tree_code, ...);
 extern tree build_real_imag_expr (location_t, enum tree_code, tree);
diff --git a/gcc/c-family/c-pretty-print.cc b/gcc/c-family/c-pretty-print.cc
index 71a0cb51093..efa1768f4d6 100644
--- a/gcc/c-family/c-pretty-print.cc
+++ b/gcc/c-family/c-pretty-print.cc
@@ -321,6 +321,7 @@ pp_c_pointer (c_pretty_printer *pp, tree t)
       _Bool                          -- C99
       _Complex                       -- C99
       _Imaginary                     -- C99
+      nullptr_t                      -- C23
       struct-or-union-specifier
       enum-specifier
       typedef-name.
@@ -424,6 +425,9 @@ c_pretty_printer::simple_type_specifier (tree t)
       else
 	translate_string ("<anonymous>");
       break;
+    case NULLPTR_TYPE:
+      pp_c_ws_string (this, "nullptr_t");
+      break;
 
     default:
       pp_unsupported_tree (this, t);
@@ -678,6 +682,7 @@ c_pretty_printer::direct_abstract_declarator (tree t)
     case COMPLEX_TYPE:
     case TYPE_DECL:
     case ERROR_MARK:
+    case NULLPTR_TYPE:
       break;
 
     default:
@@ -1219,6 +1224,8 @@ c_pretty_printer::constant (tree e)
 	  pp_c_character_constant (this, e);
 	else if (TREE_CODE (type) == ENUMERAL_TYPE)
 	  pp_c_enumeration_constant (this, e);
+	else if (NULLPTR_TYPE_P (type))
+	  pp_string (this, "nullptr");
 	else
 	  pp_c_integer_constant (this, e);
       }
diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
index 18083d59618..6e7491339d4 100644
--- a/gcc/c/c-convert.cc
+++ b/gcc/c/c-convert.cc
@@ -133,6 +133,20 @@ c_convert (tree type, tree expr, bool init_const)
 	(loc, type, c_objc_common_truthvalue_conversion (input_location, expr));
 
     case POINTER_TYPE:
+      /* The type nullptr_t may be converted to a pointer type.  The result is
+	 a null pointer value.  */
+      if (NULLPTR_TYPE_P (TREE_TYPE (e)))
+	{
+	  /* To make sure that (void *)nullptr is not a null pointer constant,
+	     build_c_cast will create an additional NOP_EXPR around the result
+	     of this conversion.  */
+	  if (TREE_SIDE_EFFECTS (e))
+	    ret = build2 (COMPOUND_EXPR, type, e, build_int_cst (type, 0));
+	  else
+	    ret = build_int_cst (type, 0);
+	  goto maybe_fold;
+	}
+      gcc_fallthrough ();
     case REFERENCE_TYPE:
       ret = convert_to_pointer (type, e);
       goto maybe_fold;
@@ -180,7 +194,16 @@ c_convert (tree type, tree expr, bool init_const)
       return ret;
     }
 
-  error ("conversion to non-scalar type requested");
+  /* If we are converting to nullptr_t, don't say "non-scalar type" because
+     the nullptr_t type is a scalar type.  Only nullptr_t shall be converted
+     to nullptr_t.  */
+  if (code == NULLPTR_TYPE)
+    {
+      error ("conversion from %qT to %qT", TREE_TYPE (e), type);
+      inform (input_location, "only %qT can be converted to %qT", type, type);
+    }
+  else
+    error ("conversion to non-scalar type requested");
   return error_mark_node;
 }
 
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 9e590c66dae..cbba0c62f64 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -4531,6 +4531,12 @@ c_init_decl_processing (void)
   pushdecl (build_decl (UNKNOWN_LOCATION, TYPE_DECL, get_identifier ("_Bool"),
 			boolean_type_node));
 
+  /* C-specific nullptr initialization.  */
+  record_builtin_type (RID_MAX, "nullptr_t", nullptr_type_node);
+  /* The size and alignment of nullptr_t is the same as for a pointer to
+     character type.  */
+  SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
+
   input_location = save_loc;
 
   make_fname_decl = c_make_fname_decl;
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 759f200a7eb..8520fd0e2a8 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -119,6 +119,8 @@ c_parse_init (void)
   mask |= D_CXXONLY;
   if (!flag_isoc99)
     mask |= D_C99;
+  if (!flag_isoc2x)
+    mask |= D_C2X;
   if (flag_no_asm)
     {
       mask |= D_ASM | D_EXT;
@@ -10243,6 +10245,14 @@ c_parser_postfix_expression (c_parser *parser)
 			 "%<depend%> clause");
 	  expr.set_error ();
 	  break;
+	/* C23 'nullptr' literal.  */
+	case RID_NULLPTR:
+	  c_parser_consume_token (parser);
+	  expr.value = nullptr_node;
+	  set_c_expr_source_range (&expr, tok_range);
+	  pedwarn_c11 (loc, OPT_Wpedantic,
+		       "ISO C does not support %qs before C2X", "nullptr");
+	  break;
 	default:
 	  c_parser_error (parser, "expected expression");
 	  expr.set_error ();
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index de8780a1502..3b5275b6e0a 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -133,6 +133,13 @@ null_pointer_constant_p (const_tree expr)
   /* This should really operate on c_expr structures, but they aren't
      yet available everywhere required.  */
   tree type = TREE_TYPE (expr);
+
+  /* An integer constant expression with the value 0, such an expression
+     cast to type void*, or the predefined constant nullptr, are a null
+     pointer constant.  */
+  if (expr == nullptr_node)
+    return true;
+
   return (TREE_CODE (expr) == INTEGER_CST
 	  && !TREE_OVERFLOW (expr)
 	  && integer_zerop (expr)
@@ -4575,7 +4582,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg,
     case TRUTH_NOT_EXPR:
       if (typecode != INTEGER_TYPE && typecode != FIXED_POINT_TYPE
 	  && typecode != REAL_TYPE && typecode != POINTER_TYPE
-	  && typecode != COMPLEX_TYPE)
+	  && typecode != COMPLEX_TYPE && typecode != NULLPTR_TYPE)
 	{
 	  error_at (location,
 		    "wrong type argument to unary exclamation mark");
@@ -5515,6 +5522,13 @@ build_conditional_expr (location_t colon_loc, tree ifexp, bool ifexp_bcp,
 	}
       result_type = type2;
     }
+  /* 6.5.15: "if one is a null pointer constant (other than a pointer) or has
+     type nullptr_t and the other is a pointer, the result type is the pointer
+     type."  */
+  else if (code1 == NULLPTR_TYPE && code2 == POINTER_TYPE)
+    result_type = type2;
+  else if (code1 == POINTER_TYPE && code2 == NULLPTR_TYPE)
+    result_type = type1;
 
   if (!result_type)
     {
@@ -7613,12 +7627,13 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 	error_at (location, msg);
       return error_mark_node;
     }
-  else if (codel == POINTER_TYPE && coder == INTEGER_TYPE)
+  else if (codel == POINTER_TYPE
+	   && (coder == INTEGER_TYPE || coder == NULLPTR_TYPE))
     {
-      /* An explicit constant 0 can convert to a pointer,
-	 or one that results from arithmetic, even including
-	 a cast to integer type.  */
-      if (!null_pointer_constant)
+      /* An explicit constant 0 or type nullptr_t can convert to a pointer,
+	 or one that results from arithmetic, even including a cast to
+	 integer type.  */
+      if (!null_pointer_constant && coder != NULLPTR_TYPE)
 	switch (errtype)
 	  {
 	  case ic_argpass:
@@ -7691,7 +7706,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 
       return convert (type, rhs);
     }
-  else if (codel == BOOLEAN_TYPE && coder == POINTER_TYPE)
+  else if (codel == BOOLEAN_TYPE
+	   /* The type nullptr_t may be converted to bool.  The
+	      result is false.  */
+	   && (coder == POINTER_TYPE || coder == NULLPTR_TYPE))
     {
       tree ret;
       bool save = in_late_binary_op;
@@ -12107,10 +12125,10 @@ build_binary_op (location_t location, enum tree_code code,
     case TRUTH_XOR_EXPR:
       if ((code0 == INTEGER_TYPE || code0 == POINTER_TYPE
 	   || code0 == REAL_TYPE || code0 == COMPLEX_TYPE
-	   || code0 == FIXED_POINT_TYPE)
+	   || code0 == FIXED_POINT_TYPE || code0 == NULLPTR_TYPE)
 	  && (code1 == INTEGER_TYPE || code1 == POINTER_TYPE
 	      || code1 == REAL_TYPE || code1 == COMPLEX_TYPE
-	      || code1 == FIXED_POINT_TYPE))
+	      || code1 == FIXED_POINT_TYPE || code1 ==  NULLPTR_TYPE))
 	{
 	  /* Result of these operations is always an int,
 	     but that does not mean the operands should be
@@ -12418,6 +12436,27 @@ build_binary_op (location_t location, enum tree_code code,
 	  result_type = type1;
 	  pedwarn (location, 0, "comparison between pointer and integer");
 	}
+      /* 6.5.9: One of the following shall hold:
+	 -- both operands have type nullptr_t;  */
+      else if (code0 == NULLPTR_TYPE && code1 == NULLPTR_TYPE)
+	{
+	  result_type = nullptr_type_node;
+	  /* No need to convert the operands to result_type later.  */
+	  converted = 1;
+	}
+    /* -- one operand has type nullptr_t and the other is a null pointer
+       constant.  We will have to convert the former to the type of the
+       latter, because during gimplification we can't have mismatching
+       comparison operand type.  We convert from nullptr_t to the other
+       type, since only nullptr_t can be converted to nullptr_t.  Also,
+       even a constant 0 is a null pointer constant, so we may have to
+       create a pointer type from its type.  */
+      else if (code0 == NULLPTR_TYPE && null_pointer_constant_p (orig_op1))
+	result_type = (INTEGRAL_TYPE_P (type1)
+		       ? build_pointer_type (type1) : type1);
+      else if (code1 == NULLPTR_TYPE && null_pointer_constant_p (orig_op0))
+	result_type = (INTEGRAL_TYPE_P (type0)
+		       ? build_pointer_type (type0) : type0);
       if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
 	   || truth_value_p (TREE_CODE (orig_op0)))
 	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 9f2ff3728b4..c897da204fe 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -187,9 +187,6 @@ enum cp_tree_index
     CPTI_NOEXCEPT_FALSE_SPEC,
     CPTI_NOEXCEPT_DEFERRED_SPEC,
 
-    CPTI_NULLPTR,
-    CPTI_NULLPTR_TYPE,
-
     CPTI_ANY_TARG,
 
     CPTI_MODULE_HWM,
@@ -254,8 +251,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
 #define conv_op_marker			cp_global_trees[CPTI_CONV_OP_MARKER]
 #define abort_fndecl			cp_global_trees[CPTI_ABORT_FNDECL]
 #define current_aggr			cp_global_trees[CPTI_AGGR_TAG]
-#define nullptr_node			cp_global_trees[CPTI_NULLPTR]
-#define nullptr_type_node		cp_global_trees[CPTI_NULLPTR_TYPE]
 /* std::align_val_t */
 #define align_type_node			cp_global_trees[CPTI_ALIGN_TYPE]
 
@@ -4405,9 +4400,6 @@ get_vec_init_expr (tree t)
    || TREE_CODE (TYPE) == REAL_TYPE \
    || TREE_CODE (TYPE) == COMPLEX_TYPE)
 
-/* True iff TYPE is cv decltype(nullptr).  */
-#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
-
 /* [basic.types]
 
    Arithmetic types, enumeration types, pointer types,
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 84a1a011341..d46a347a6c7 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -4793,16 +4793,10 @@ cxx_init_decl_processing (void)
 	  }
       }
 
-    nullptr_type_node = make_node (NULLPTR_TYPE);
-    TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
-    TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
-    TYPE_UNSIGNED (nullptr_type_node) = 1;
-    TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
+    /* C++-specific nullptr initialization.  */
     if (abi_version_at_least (9))
       SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
-    SET_TYPE_MODE (nullptr_type_node, ptr_mode);
     record_builtin_type (RID_MAX, "decltype(nullptr)", nullptr_type_node);
-    nullptr_node = build_int_cst (nullptr_type_node, 0);
   }
 
   if (! supports_one_only ())
diff --git a/gcc/ginclude/stddef.h b/gcc/ginclude/stddef.h
index 79e296d4a66..315ff786694 100644
--- a/gcc/ginclude/stddef.h
+++ b/gcc/ginclude/stddef.h
@@ -443,6 +443,14 @@ typedef struct {
 #endif
 #endif /* C++11.  */
 
+#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L)
+#ifndef _GCC_NULLPTR_T
+#define _GCC_NULLPTR_T
+  typedef __typeof__(nullptr) nullptr_t;
+/* ??? This doesn't define __STDC_VERSION_STDDEF_H__ yet.  */
+#endif
+#endif /* C23.  */
+
 #endif /* _STDDEF_H was defined this time */
 
 #endif /* !_STDDEF_H && !_STDDEF_H_ && !_ANSI_STDDEF_H && !__STDDEF_H__
diff --git a/gcc/testsuite/gcc.dg/c11-nullptr-1.c b/gcc/testsuite/gcc.dg/c11-nullptr-1.c
new file mode 100644
index 00000000000..c4faedc2c91
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c11-nullptr-1.c
@@ -0,0 +1,10 @@
+/* Test that in pre-C23 modes, nullptr is a normal identifier,
+   not a keyword.  */
+/* { dg-options "-std=c11 -pedantic-errors" } */
+
+int nullptr;
+
+void
+f (int nullptr)
+{
+}
diff --git a/gcc/testsuite/gcc.dg/c17-nullptr-1.c b/gcc/testsuite/gcc.dg/c17-nullptr-1.c
new file mode 100644
index 00000000000..92e43b9df23
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c17-nullptr-1.c
@@ -0,0 +1,10 @@
+/* Test that in pre-C23 modes, nullptr is a normal identifier,
+   not a keyword.  */
+/* { dg-options "-std=c17 -pedantic-errors" } */
+
+int nullptr;
+
+void
+f (int nullptr)
+{
+}
diff --git a/gcc/testsuite/gcc.dg/c17-nullptr-2.c b/gcc/testsuite/gcc.dg/c17-nullptr-2.c
new file mode 100644
index 00000000000..a6ad7703eeb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c17-nullptr-2.c
@@ -0,0 +1,10 @@
+/* Test that we don't predefine `nullptr' pre-C2X.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c17 -pedantic-errors" } */
+
+int *
+fn (int *p)
+{
+  p = nullptr; /* { dg-error "'nullptr' undeclared" } */
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-1.c b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
new file mode 100644
index 00000000000..9501b514f1c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
@@ -0,0 +1,298 @@
+/* Test valid usage of C23 nullptr.  */
+/* { dg-do run } */
+/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
+
+#include <stdarg.h>
+
+typedef __typeof__(nullptr) nullptr_t;
+
+void f1 (nullptr_t) { }
+void f2 (int *) { }
+void f3 (_Bool) { }
+nullptr_t cmp (void) { return nullptr; }
+
+/* The type nullptr_t shall not be converted to any type other than void, bool or
+   a pointer type.  No type other than nullptr_t shall be converted to nullptr_t.  */
+void
+test1 (void)
+{
+  const nullptr_t nptr = nullptr;
+  static nullptr_t static_nptr;
+  int *p1 = nullptr;
+  void *p2 = nullptr;
+  float *p3 = nullptr;
+  void (*p4)(int) = nullptr;
+  int (*p5)[10] = nullptr;
+  int *p6 = nptr;
+  void *p7 = nptr;
+  float *p8 = nptr;
+  void (*p9)(int) = nptr;
+  int (*p10)[10] = nptr;
+  int *p11 = (int *) nullptr;
+  int *p12 = (int *) nptr;
+  int *p13 = (nullptr);
+  int *p14 = _Generic(0, int : nullptr);
+  if (nullptr || p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8 || p9 || p10
+      || p11 || p12 || p13 || p14)
+    __builtin_abort ();
+
+  _Bool b1 = nullptr;
+  _Bool b2 = (_Bool) nullptr;
+  _Bool b3 = nptr;
+  _Bool b4 = (_Bool) nptr;
+  _Bool b5 = _Generic(0, int : nullptr);
+  if (b1 || b2 || b3 || b4 || b5 || (_Bool) nullptr || (_Bool) nptr)
+   __builtin_abort ();
+
+  __auto_type a1 = nullptr;
+  __auto_type a2 = nptr;
+
+  /* We can convert nullptr_t to nullptr_t.  */
+  __typeof__(nullptr) x = nullptr;
+  f1 (x);
+  f1 (nullptr);
+  f1 (_Generic(0, int : nullptr));
+  f2 (x);
+  f2 (nullptr);
+  f3 (nullptr);
+
+  const nullptr_t np1 = nullptr;
+  const nullptr_t np2 = np1;
+  (void) nullptr;
+  (void) np1;
+  (void) np2;
+  (void) cmp ();
+  (void)(nullptr_t) nullptr;
+}
+
+/* Test valid comparison.  */
+void
+test2 (int *p)
+{
+  /* If both operands have type nullptr_t or one operand has type nullptr_t
+     and the other is a null pointer constant, they compare equal.  */
+  const nullptr_t nptr = nullptr;
+  int r = 0;
+
+  /* Both operands have type nullptr_t.  */
+  r |= nullptr != nullptr;
+  r |= cmp () != nullptr;
+  r |= nullptr != cmp ();
+  r |= !(nullptr == nullptr);
+  r |= !(cmp () == nullptr);
+  r |= !(nullptr == cmp ());
+  r |= nptr != nptr;
+  r |= cmp () != nptr;
+  r |= nptr != cmp ();
+  r |= !(nptr == nptr);
+  r |= !(cmp () == nptr);
+  r |= !(nptr == cmp ());
+
+ /* One operand has type nullptr_t and the other is a null pointer constant.  */
+  r |= nullptr != (void *) 0;
+  r |= _Generic(0, int : nullptr) != (void *) 0;
+  r |= (nullptr) != (void *) 0;
+  r |= !(nullptr == (void *) 0);
+  r |= (void *) 0 != nullptr;
+  r |= (void *) 0 != (nullptr);
+  r |= !((void *) 0 == nullptr);
+  r |= nullptr != 0;
+  r |= _Generic(0, int : nullptr) != 0;
+  r |= (nullptr) != 0;
+  r |= 0 != nullptr;
+  r |= 0 != (nullptr);
+  r |= !(nullptr == 0);
+  r |= !(0 == nullptr);
+  r |= nullptr != 0u;
+  r |= 0u != nullptr;
+  r |= !(nullptr == 0u);
+  r |= !(0u == nullptr);
+  r |= nptr != (void *) 0;
+  r |= !(nptr == (void *) 0);
+  r |= (void *) 0 != nptr;
+  r |= !((void *) 0 == nptr);
+  r |= nptr != 0;
+  r |= 0 != nptr;
+  r |= !(nptr == 0);
+  r |= !(0 == nptr);
+  r |= nptr != 0u;
+  r |= 0u != nptr;
+  r |= !(nptr == 0u);
+  r |= !(0u == nptr);
+  r |= nptr != _Generic(0, int : nullptr);
+  r |= _Generic(0, int : nullptr) != nptr;
+  if (r)
+    __builtin_abort ();
+
+  /* One operand is a pointer and the other is a null pointer constant.  */
+  (void) (p == nullptr);
+  (void) (p != nullptr);
+  (void) (nullptr == p);
+  (void) (nullptr != p);
+  (void) (p == (nullptr));
+  (void) (p != (nullptr));
+  (void) ((nullptr) == p);
+  (void) ((nullptr) != p);
+  (void) ((void *)nullptr == nullptr);
+  (void) ((void *)nullptr != nullptr);
+  (void) (nullptr == (void *)nullptr);
+  (void) (nullptr != (void *)nullptr);
+  (void) (p == _Generic(0, int : nullptr));
+  (void) (p != _Generic(0, int : nullptr));
+  (void) (_Generic(0, int : nullptr) == p);
+  (void) (_Generic(0, int : nullptr) != p);
+}
+
+/* Test ?:.  */
+void
+test3 (int *p, _Bool b)
+{
+  int x = nullptr ? 1 : 2;
+  (void) x;
+  const nullptr_t nptr = nullptr;
+  /* One of the following shall hold for the second and third operands:
+     -- both operands have nullptr_t type.  */
+  __auto_type r1 = b ? nullptr : nullptr;
+  __auto_type r2 = b ? nptr : nptr;
+  /* -- one operand is a pointer and the other is a null pointer constant
+     or has type nullptr_t;  */
+  __auto_type r3 = b ? p : nullptr;
+  __auto_type r4 = b ? nullptr : p;
+  __auto_type r5 = b ? nptr : p;
+  __auto_type r6 = b ? p : nptr;
+  __auto_type r7 = b ? 0 : p;
+  __auto_type r8 = b ? p : 0;
+  __auto_type r9 = b ? p : cmp ();
+  __auto_type r10 = b ?  cmp () : p;
+  __auto_type r11 = b ? p : _Generic(0, int : nullptr);
+  __auto_type r12 = b ? _Generic(0, int : nullptr) : p;
+}
+
+void test_arg1 (const nullptr_t, _Atomic nullptr_t, volatile nullptr_t) { }
+void test_arg2 (_Atomic int *, const int *, volatile int *) { }
+void test_arg3 (_Atomic _Bool, const _Bool, volatile _Bool) { }
+nullptr_t retn (void) { return nullptr; }
+_Atomic int *ai (void) { return nullptr; }
+const int *ci (void) { return nullptr; }
+volatile int *vi (void) { return nullptr; }
+_Bool retb (void) { return nullptr; }
+
+/* Simple assignment.  */
+void
+test4 (void)
+{
+  /* -- the left operand has an atomic, qualified, or unqualified version of
+     the nullptr_t type and the type of the right is nullptr_t;  */
+  nullptr_t n1;
+  const nullptr_t n2 = nullptr;
+  _Atomic nullptr_t n3 = nullptr;
+  volatile nullptr_t n4 = nullptr;
+  _Atomic volatile nullptr_t n5 = nullptr;
+  n1 = nullptr;
+  n3 = nullptr;
+  n4 = nullptr;
+  n5 = nullptr;
+  n5 = _Generic(0, int : nullptr);
+  /* -- the left operand is an atomic, qualified, or unqualified pointer,
+     and the type of the right is nullptr_t;  */
+  int *p1 = cmp ();
+  _Atomic int *p2 = cmp ();
+  const int *volatile p3 = cmp ();
+  const int *const *const p4 = cmp ();
+  double (*const p5)(void) = n1;
+  p2 = _Generic(0, int : nullptr);
+  p3 = nullptr;
+  /* -- the left operand is an atomic, qualified, or unqualified bool, and
+     the type of the right is nullptr_t;  */
+  _Bool b1;
+  b1 = cmp ();
+  const _Bool b2 = nullptr;
+  _Atomic _Bool b3;
+  b3 = n1;
+  (void) b1;
+  (void) b3;
+  (void) n3;
+  (void) n4;
+  (void) n5;
+  (void) p2;
+  (void) p3;
+
+  test_arg1 (nullptr, nullptr, nullptr);
+  test_arg2 (nullptr, nullptr, nullptr);
+  test_arg3 (nullptr, nullptr, nullptr);
+}
+
+/* var_arg etc.  */
+static void
+test5 (int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (va_arg (ap, void *))
+    __builtin_abort ();
+}
+
+/* Operand of alignas, sizeof or typeof operators.  */
+void
+test6 (void)
+{
+  _Static_assert (sizeof (nullptr) == sizeof (void *), "sizeof (nullptr)");
+  _Static_assert (sizeof (nullptr_t) == sizeof (void *), "sizeof (nullptr_t)");
+  _Static_assert (sizeof (nullptr) == sizeof (char *), "sizeof (nullptr)");
+  _Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
+  _Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
+  __typeof__(nullptr) t = nullptr;
+  f1 (t);
+  _Alignas (nullptr_t) char i1 = 'q';
+
+  _Static_assert (_Generic (nullptr, nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (t, nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (cmp (), nullptr_t: 1, default: 0) == 1, "_Generic");
+  _Static_assert (_Generic (0, nullptr_t: 1, int: 2, default: 0) == 2, "_Generic");
+  _Static_assert (_Generic ((void *)0, nullptr_t: 1, void *: 2, default: 0) == 2, "_Generic");
+  _Static_assert (_Generic (nullptr, nullptr_t: 1, void *: 2, default: 0) == 1, "_Generic");
+}
+
+/* Play with !, ||, &&. */
+void
+test7 (void)
+{
+  if (nullptr)
+    __builtin_abort ();
+  if (1 && nullptr)
+    __builtin_abort ();
+  if (0 || nullptr)
+    __builtin_abort ();
+  if (nullptr && 1)
+    __builtin_abort ();
+  if (nullptr || 0)
+    __builtin_abort ();
+  if (!nullptr)
+    {
+    }
+  else
+    __builtin_abort ();
+  while (nullptr)
+    __builtin_abort ();
+  int i = 0;
+  do
+    ++i;
+  while (nullptr);
+  if (i != 1)
+    __builtin_abort ();
+  for (;nullptr;)
+    __builtin_abort ();
+}
+
+int
+main (void)
+{
+  int i = 42;
+  test1 ();
+  test2 (&i);
+  test3 (&i, 0);
+  test4 ();
+  test5 (42, nullptr);
+  test6 ();
+  test7 ();
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-2.c b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
new file mode 100644
index 00000000000..b6105657362
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
@@ -0,0 +1,9 @@
+/* Test nullptr_t from <stddef.h>.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+#include <stddef.h>
+
+void f(nullptr_t);
+_Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
+_Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-3.c b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
new file mode 100644
index 00000000000..34e3e03ba9d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
@@ -0,0 +1,80 @@
+/* Test wrong usage of C23 nullptr.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
+
+typedef __typeof__(nullptr) nullptr_t;
+
+void g (nullptr_t); /* { dg-message "expected .nullptr_t. but argument is of type .int." } */
+nullptr_t cmp (void);
+
+void
+test1 (int *p)
+{
+  (void) (p > nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p >= nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p < nullptr); /* { dg-error "ordered comparison" } */
+  (void) (p <= nullptr); /* { dg-error "ordered comparison" } */
+  (void) (nullptr == 1); /* { dg-error "invalid operands" } */
+  (void) (1 == nullptr); /* { dg-error "invalid operands" } */
+  (void) (nullptr != 1); /* { dg-error "invalid operands" } */
+  (void) (1 != nullptr); /* { dg-error "invalid operands" } */
+  (void) (1 > nullptr); /* { dg-error "invalid operands" } */
+
+  /* "(nullptr_t)nullptr" has type nullptr_t but isn't an NPC.  */
+  (void) ((nullptr_t)nullptr == p); /* { dg-error "invalid operands" } */
+  (void) ((nullptr_t)nullptr != p); /* { dg-error "invalid operands" } */
+  (void) (p == (nullptr_t)nullptr); /* { dg-error "invalid operands" } */
+  (void) (p != (nullptr_t)nullptr); /* { dg-error "invalid operands" } */
+  (void) (cmp () == p); /* { dg-error "invalid operands" } */
+  (void) (cmp () != p); /* { dg-error "invalid operands" } */
+  (void) (p == cmp ()); /* { dg-error "invalid operands" } */
+  (void) (p != cmp ()); /* { dg-error "invalid operands" } */
+  /* "(void *)nullptr" is not an NPC, either.  */
+  (void) ((void *)nullptr == cmp ()); /* { dg-error "invalid operands" } */
+  (void) ((void *)nullptr != cmp ()); /* { dg-error "invalid operands" } */
+  (void) (cmp () == (void *)nullptr); /* { dg-error "invalid operands" } */
+  (void) (cmp () != (void *)nullptr); /* { dg-error "invalid operands" } */
+}
+
+void
+test2 (void)
+{
+  const nullptr_t nptr = nullptr;
+  int p = nullptr; /* { dg-error "incompatible types" } */
+  float d = nullptr; /* { dg-error "incompatible types" } */
+  char arr[10] = { nullptr }; /* { dg-error "incompatible types" } */
+
+  /* No type other than nullptr_t shall be converted to nullptr_t.  */
+  const nullptr_t n = 0; /* { dg-error "invalid initializer" } */
+  +(nullptr_t) 0; /* { dg-error "conversion from .int. to .nullptr_t." } */
+
+  g (0); /* { dg-error "incompatible type" } */
+
+  int i = 42 + nullptr; /* { dg-error "invalid operands" } */
+
+  /* The assignment of an object of type nullptr_t with a value of another
+     type, even if the value is a null pointer constant, is a constraint
+     violation.  */
+  nullptr_t m;
+  m = 0; /* { dg-error "incompatible types" } */
+  (void) m;
+  nullptr_t o = 0; /* { dg-error "invalid initializer" } */
+
+  switch (nullptr); /* { dg-error "switch quantity not an integer" } */
+}
+
+/* If a second or third operand of type nullptr_t is used that is not a null
+   pointer constant and the other operand is not a pointer or does not have
+   itself nullptr_t, a constraint is violated even if that other operand is
+   a null pointer constant such as 0.  */
+void
+test3 (_Bool b, int i)
+{
+  const nullptr_t nptr = nullptr;
+  __auto_type a1 = b ? nptr : i; /* { dg-error "type mismatch" } */
+  __auto_type a2 = b ? i : nptr; /* { dg-error "type mismatch" } */
+  __auto_type a3 = b ? nptr : 0; /* { dg-error "type mismatch" } */
+  __auto_type a4 = b ? 0 : nptr; /* { dg-error "type mismatch" } */
+  __auto_type a5 = b ? 0 : nullptr; /* { dg-error "type mismatch" } */
+  __auto_type a6 = b ? nullptr : 0; /* { dg-error "type mismatch" } */
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-4.c b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
new file mode 100644
index 00000000000..7479ab4ea1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
@@ -0,0 +1,11 @@
+/* Test that -Wc11-c2x-compat issues a warning (not a pedwarn) about
+   `nullptr' in C2X.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors -Wc11-c2x-compat" } */
+
+int *
+fn (int *p)
+{
+  p = nullptr; /* { dg-warning "ISO C does not support .nullptr. before C2X" } */
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-5.c b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
new file mode 100644
index 00000000000..27803f7d03f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
@@ -0,0 +1,14 @@
+/* Test that we don't lose side-effects when converting from nullptr_t.  */
+/* { dg-do run } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+int i;
+nullptr_t fn () { ++i; return nullptr; }
+
+int
+main ()
+{
+  int *p = fn ();
+  if (i != 1)
+    __builtin_abort ();
+}

base-commit: 980e0aa0ce3bdfec61ca766a71a31a89c12f882e
-- 
2.37.2


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v3] c: Implement C23 nullptr (N3042)
  2022-08-25 20:51       ` [PATCH v3] " Marek Polacek
@ 2022-08-25 21:12         ` Joseph Myers
  2022-08-25 22:14           ` Marek Polacek
  0 siblings, 1 reply; 9+ messages in thread
From: Joseph Myers @ 2022-08-25 21:12 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On Thu, 25 Aug 2022, Marek Polacek via Gcc-patches wrote:

> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

This version is OK.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v3] c: Implement C23 nullptr (N3042)
  2022-08-25 21:12         ` Joseph Myers
@ 2022-08-25 22:14           ` Marek Polacek
  0 siblings, 0 replies; 9+ messages in thread
From: Marek Polacek @ 2022-08-25 22:14 UTC (permalink / raw)
  To: Joseph Myers; +Cc: GCC Patches

On Thu, Aug 25, 2022 at 09:12:07PM +0000, Joseph Myers wrote:
> On Thu, 25 Aug 2022, Marek Polacek via Gcc-patches wrote:
> 
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> This version is OK.

Thanks a lot.  I've gone ahead and updated GCC 13 changes.html:

commit 54b8bcfe7c65290942c5fccd89edd658b97774af
Author: Marek Polacek <polacek@redhat.com>
Date:   Thu Aug 25 18:12:15 2022 -0400

    gcc-13/changes: Update with recent C23 and C++23 proposals

diff --git a/htdocs/gcc-13/changes.html b/htdocs/gcc-13/changes.html
index 03a77276..c58ca1d4 100644
--- a/htdocs/gcc-13/changes.html
+++ b/htdocs/gcc-13/changes.html
@@ -73,6 +73,13 @@ a work-in-progress.</p>
 
 <h3 id="c">C</h3>
 <ul>
+  <li>Several C23 features have been implemented:
+    <ul>
+      <li><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm">N3042</a>,
+	  Introduce the nullptr constant</li>
+      <li>Support for empty initializer braces</li>
+    </ul>
+  </li>
   <li>New warnings:
     <ul>
       <li><code>-Wenum-int-mismatch</code> warns about mismatches between an
@@ -93,8 +100,20 @@ a work-in-progress.</p>
 	  reference binding to temporary
 	  (<a href="https://gcc.gnu.org/PR104477">PR104477</a>)
       </li>
+      <li><a href="https://wg21.link/p2327">P2327R1</a>, De-deprecating volatile
+	  compound operations
+      </li>
+      <li><a href="https://wg21.link/p2437">P2437R1</a>, Support for
+	  <code>#warning</code>
+	  (<a href="https://gcc.gnu.org/PR106646">PR106646</a>)
+      </li>
+      <li><a href="https://wg21.link/p2290">P2290R3</a>, Delimited escape sequences
+	  (<a href="https://gcc.gnu.org/PR106645">PR106645</a>)
+      </li>
     </ul>
   </li>
+  <li>The <code>-Wpessimizing-move</code> and <code>-Wredundant-move</code>
+      warnings have been extended to warn in more contexts.</li>
 </ul>
 
 <!-- <h4 id="libstdcxx">Runtime Library (libstdc++)</h4> -->


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2022-08-25 22:14 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-08-13 21:35 [PATCH] c: Implement C23 nullptr (N3042) Marek Polacek
2022-08-15 17:48 ` Joseph Myers
2022-08-24 18:24   ` [PATCH v2] " Marek Polacek
2022-08-25 17:28     ` Joseph Myers
2022-08-25 20:51       ` [PATCH v3] " Marek Polacek
2022-08-25 21:12         ` Joseph Myers
2022-08-25 22:14           ` Marek Polacek
2022-08-15 20:03 ` [PATCH] " Jason Merrill
2022-08-24 18:24   ` Marek Polacek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).