c: Implement C23 nullptr (N3042)

Message ID 20220813213504.568937-1-polacek@redhat.com
State New
Headers
Series c: Implement C23 nullptr (N3042) |

Commit Message

Marek Polacek Aug. 13, 2022, 9:35 p.m. UTC
  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


base-commit: 4991e20923b658ce9fbdf5621cab39f71b98fbc2
  

Comments

Joseph Myers Aug. 15, 2022, 5:48 p.m. UTC | #1
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.
  
Jason Merrill Aug. 15, 2022, 8:03 p.m. UTC | #2
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
  
Marek Polacek Aug. 24, 2022, 6:24 p.m. UTC | #3
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
  

Patch

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;
+}