diff mbox series

c++: Add __builtin_operator_{new,delete} support

Message ID Zy5NALg16bu/u4Od@tucnak
State New
Headers show
Series c++: Add __builtin_operator_{new,delete} support | expand

Commit Message

Jakub Jelinek Nov. 8, 2024, 5:40 p.m. UTC
Hi!

clang++ adds __builtin_operator_{new,delete} builtins which as documented
work similarly to ::operator {new,delete}, except that it is an error
if the called ::operator {new,delete} is not a replaceable global operator
and allow optimizations which C++ normally allows just when those are used
from new/delete expressions https://eel.is/c++draft/expr.new#14
When using these builtins, the same optimizations can be done even when
using those builtins.

For GCC we note that in the CALL_FROM_NEW_OR_DELETE_P flag on CALL_EXPRs.
The following patch implements it as a C++ FE keyword (because passing
references through ... changes the argument and so BUILT_IN_FRONTEND
builtin can't be used), just attempts to call the ::operator {new,delete}
and if it isn't replaceable, diagnoses it.

So far lightly tested, ok for trunk if it passes bootstrap/regtest
(note, libstdc++ already uses the builtin)?

2024-11-08  Jakub Jelinek  <jakub@redhat.com>

gcc/c-family/
	* c-common.h (enum rid): Add RID_BUILTIN_OPERATOR_NEW
	and RID_BUILTIN_OPERATOR_DELETE.
	(names_builtin_p): Change return type from bool to int.
	* c-common.cc (c_common_reswords): Add __builtin_operator_new
	and __builtin_operator_delete.
gcc/c/
	* c-decl.cc (names_builtin_p): Change return type from
	bool to int, adjust return statments.
gcc/cp/
	* parser.cc (cp_parser_postfix_expression): Handle
	RID_BUILTIN_OPERATOR_NEW and RID_BUILTIN_OPERATOR_DELETE.
	* cp-objcp-common.cc (names_builtin_p): Change return type from
	bool to int, adjust return statments.  Handle
	RID_BUILTIN_OPERATOR_NEW and RID_BUILTIN_OPERATOR_DELETE.
	* pt.cc (tsubst_expr) <case CALL_EXPR>: Handle
	CALL_FROM_NEW_OR_DELETE_P.
gcc/
	* doc/extend.texi (New/Delete Builtins): Document
	__builtin_operator_new and __builtin_operator_delete.
gcc/testsuite/
	* g++.dg/ext/builtin-operator-new-1.C: New test.
	* g++.dg/ext/builtin-operator-new-2.C: New test.
	* g++.dg/ext/builtin-operator-new-3.C: New test.


	Jakub

Comments

Jakub Jelinek Nov. 11, 2024, 8:46 a.m. UTC | #1
On Fri, Nov 08, 2024 at 06:40:16PM +0100, Jakub Jelinek wrote:
> clang++ adds __builtin_operator_{new,delete} builtins which as documented
> work similarly to ::operator {new,delete}, except that it is an error
> if the called ::operator {new,delete} is not a replaceable global operator
> and allow optimizations which C++ normally allows just when those are used
> from new/delete expressions https://eel.is/c++draft/expr.new#14
> When using these builtins, the same optimizations can be done even when
> using those builtins.
> 
> For GCC we note that in the CALL_FROM_NEW_OR_DELETE_P flag on CALL_EXPRs.
> The following patch implements it as a C++ FE keyword (because passing
> references through ... changes the argument and so BUILT_IN_FRONTEND
> builtin can't be used), just attempts to call the ::operator {new,delete}
> and if it isn't replaceable, diagnoses it.
> 
> So far lightly tested, ok for trunk if it passes bootstrap/regtest
> (note, libstdc++ already uses the builtin)?

Bootstrapped/regtested successfully on x86_64-linux and i686-linux.

	Jakub
Jason Merrill Nov. 11, 2024, 6:45 p.m. UTC | #2
On 11/8/24 12:40 PM, Jakub Jelinek wrote:
> Hi!
> 
> clang++ adds __builtin_operator_{new,delete} builtins which as documented
> work similarly to ::operator {new,delete}, except that it is an error
> if the called ::operator {new,delete} is not a replaceable global operator
> and allow optimizations which C++ normally allows just when those are used
> from new/delete expressions https://eel.is/c++draft/expr.new#14
> When using these builtins, the same optimizations can be done even when
> using those builtins.
> 
> For GCC we note that in the CALL_FROM_NEW_OR_DELETE_P flag on CALL_EXPRs.
> The following patch implements it as a C++ FE keyword (because passing
> references through ... changes the argument and so BUILT_IN_FRONTEND
> builtin can't be used), just attempts to call the ::operator {new,delete}
> and if it isn't replaceable, diagnoses it.
> 
> So far lightly tested, ok for trunk if it passes bootstrap/regtest
> (note, libstdc++ already uses the builtin)?

OK.

> 2024-11-08  Jakub Jelinek  <jakub@redhat.com>
> 
> gcc/c-family/
> 	* c-common.h (enum rid): Add RID_BUILTIN_OPERATOR_NEW
> 	and RID_BUILTIN_OPERATOR_DELETE.
> 	(names_builtin_p): Change return type from bool to int.
> 	* c-common.cc (c_common_reswords): Add __builtin_operator_new
> 	and __builtin_operator_delete.
> gcc/c/
> 	* c-decl.cc (names_builtin_p): Change return type from
> 	bool to int, adjust return statments.
> gcc/cp/
> 	* parser.cc (cp_parser_postfix_expression): Handle
> 	RID_BUILTIN_OPERATOR_NEW and RID_BUILTIN_OPERATOR_DELETE.
> 	* cp-objcp-common.cc (names_builtin_p): Change return type from
> 	bool to int, adjust return statments.  Handle
> 	RID_BUILTIN_OPERATOR_NEW and RID_BUILTIN_OPERATOR_DELETE.
> 	* pt.cc (tsubst_expr) <case CALL_EXPR>: Handle
> 	CALL_FROM_NEW_OR_DELETE_P.
> gcc/
> 	* doc/extend.texi (New/Delete Builtins): Document
> 	__builtin_operator_new and __builtin_operator_delete.
> gcc/testsuite/
> 	* g++.dg/ext/builtin-operator-new-1.C: New test.
> 	* g++.dg/ext/builtin-operator-new-2.C: New test.
> 	* g++.dg/ext/builtin-operator-new-3.C: New test.
> 
> --- gcc/c-family/c-common.h.jj	2024-10-25 10:00:29.314770060 +0200
> +++ gcc/c-family/c-common.h	2024-11-08 16:53:01.198538630 +0100
> @@ -168,6 +168,7 @@ enum rid
>     RID_ADDRESSOF,
>     RID_BUILTIN_LAUNDER,
>     RID_BUILTIN_BIT_CAST,
> +  RID_BUILTIN_OPERATOR_NEW, RID_BUILTIN_OPERATOR_DELETE,
>   
>     /* C++11 */
>     RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
> @@ -840,7 +841,7 @@ extern bool in_late_binary_op;
>   extern const char *c_addr_space_name (addr_space_t as);
>   extern tree identifier_global_value (tree);
>   extern tree identifier_global_tag (tree);
> -extern bool names_builtin_p (const char *);
> +extern int names_builtin_p (const char *);
>   extern tree c_linkage_bindings (tree);
>   extern void record_builtin_type (enum rid, const char *, tree);
>   extern void start_fname_decls (void);
> --- gcc/c-family/c-common.cc.jj	2024-10-30 07:59:36.347587874 +0100
> +++ gcc/c-family/c-common.cc	2024-11-08 15:33:51.733170328 +0100
> @@ -434,6 +434,8 @@ const struct c_common_resword c_common_r
>     { "__builtin_counted_by_ref", RID_BUILTIN_COUNTED_BY_REF, D_CONLY },
>     { "__builtin_has_attribute", RID_BUILTIN_HAS_ATTRIBUTE, 0 },
>     { "__builtin_launder", RID_BUILTIN_LAUNDER, D_CXXONLY },
> +  { "__builtin_operator_new", RID_BUILTIN_OPERATOR_NEW, D_CXXONLY },
> +  { "__builtin_operator_delete", RID_BUILTIN_OPERATOR_DELETE, D_CXXONLY },
>     { "__builtin_shuffle", RID_BUILTIN_SHUFFLE, 0 },
>     { "__builtin_shufflevector", RID_BUILTIN_SHUFFLEVECTOR, 0 },
>     { "__builtin_stdc_bit_ceil", RID_BUILTIN_STDC, D_CONLY },
> --- gcc/c/c-decl.cc.jj	2024-10-31 21:17:06.857021211 +0100
> +++ gcc/c/c-decl.cc	2024-11-08 16:56:56.153195293 +0100
> @@ -11751,10 +11751,10 @@ identifier_global_tag (tree t)
>     return NULL_TREE;
>   }
>   
> -/* Returns true if NAME refers to a built-in function or function-like
> -   operator.  */
> +/* Returns non-zero (result of __has_builtin) if NAME refers to a built-in
> +   function or function-like operator.  */
>   
> -bool
> +int
>   names_builtin_p (const char *name)
>   {
>     tree id = get_identifier (name);
> @@ -11775,12 +11775,12 @@ names_builtin_p (const char *name)
>       case RID_CHOOSE_EXPR:
>       case RID_OFFSETOF:
>       case RID_TYPES_COMPATIBLE_P:
> -      return true;
> +      return 1;
>       default:
>         break;
>       }
>   
> -  return false;
> +  return 0;
>   }
>   
>   /* In C, the only C-linkage public declaration is at file scope.  */
> --- gcc/cp/parser.cc.jj	2024-11-06 18:53:10.815844090 +0100
> +++ gcc/cp/parser.cc	2024-11-08 17:52:04.332208389 +0100
> @@ -7733,6 +7733,8 @@ cp_parser_postfix_expression (cp_parser
>       case RID_BUILTIN_SHUFFLEVECTOR:
>       case RID_BUILTIN_LAUNDER:
>       case RID_BUILTIN_ASSOC_BARRIER:
> +    case RID_BUILTIN_OPERATOR_NEW:
> +    case RID_BUILTIN_OPERATOR_DELETE:
>         {
>   	vec<tree, va_gc> *vec;
>   
> @@ -7819,6 +7821,39 @@ cp_parser_postfix_expression (cp_parser
>   	      }
>   	    break;
>   
> +	  case RID_BUILTIN_OPERATOR_NEW:
> +	  case RID_BUILTIN_OPERATOR_DELETE:
> +	    tree fn;
> +	    fn = ovl_op_identifier (keyword == RID_BUILTIN_OPERATOR_NEW
> +				    ? NEW_EXPR : DELETE_EXPR);
> +	    fn = lookup_qualified_name (global_namespace, fn);
> +	    postfix_expression = finish_call_expr (fn, &vec, true, false,
> +						   tf_warning_or_error);
> +	    if (postfix_expression != error_mark_node)
> +	      {
> +		tree call = extract_call_expr (postfix_expression);
> +		fn = cp_get_callee_fndecl_nofold (call);
> +		if (fn ? !DECL_IS_REPLACEABLE_OPERATOR (fn)
> +		       : !processing_template_decl)
> +		  {
> +		    auto_diagnostic_group d;
> +		    if (keyword == RID_BUILTIN_OPERATOR_NEW)
> +		      error_at (loc, "call to %<__builtin_operator_new%> "
> +				     "does not select replaceable global "
> +				     "allocation function");
> +		    else
> +		      error_at (loc, "call to %<__builtin_operator_delete%> "
> +				     "does not select replaceable global "
> +				     "deallocation function");
> +		    if (fn)
> +		      inform (DECL_SOURCE_LOCATION (fn),
> +			      "selected function declared here");
> +		  }
> +		else if (call && TREE_CODE (call) == CALL_EXPR)
> +		  CALL_FROM_NEW_OR_DELETE_P (call) = 1;
> +	      }
> +	    break;
> +
>   	  default:
>   	    gcc_unreachable ();
>   	  }
> --- gcc/cp/cp-objcp-common.cc.jj	2024-10-24 18:53:38.326085876 +0200
> +++ gcc/cp/cp-objcp-common.cc	2024-11-08 16:57:52.061399744 +0100
> @@ -545,10 +545,10 @@ identifier_global_tag (tree name)
>     return ret;
>   }
>   
> -/* Returns true if NAME refers to a built-in function or function-like
> -   operator.  */
> +/* Returns non-zero (result of __has_builtin) if NAME refers to a built-in
> +   function or function-like operator.  */
>   
> -bool
> +int
>   names_builtin_p (const char *name)
>   {
>     tree id = get_identifier (name);
> @@ -556,23 +556,23 @@ names_builtin_p (const char *name)
>       {
>         if (TREE_CODE (binding) == FUNCTION_DECL
>   	  && DECL_IS_UNDECLARED_BUILTIN (binding))
> -	return true;
> +	return 1;
>   
>         /* Handle the case when an overload for a  built-in name exists.  */
>         if (TREE_CODE (binding) != OVERLOAD)
> -	return false;
> +	return 0;
>   
>         for (ovl_iterator it (binding); it; ++it)
>   	{
>   	  tree decl = *it;
>   	  if (DECL_IS_UNDECLARED_BUILTIN (decl))
> -	    return true;
> +	    return 1;
>   	}
>       }
>   
>     /* Check for built-in traits.  */
>     if (IDENTIFIER_TRAIT_P (id))
> -    return true;
> +    return 1;
>   
>     /* Also detect common reserved C++ words that aren't strictly built-in
>        functions.  */
> @@ -587,12 +587,15 @@ names_builtin_p (const char *name)
>       case RID_BUILTIN_ASSOC_BARRIER:
>       case RID_BUILTIN_BIT_CAST:
>       case RID_OFFSETOF:
> -      return true;
> +      return 1;
> +    case RID_BUILTIN_OPERATOR_NEW:
> +    case RID_BUILTIN_OPERATOR_DELETE:
> +      return 201802L;
>       default:
>         break;
>       }
>   
> -  return false;
> +  return 0;
>   }
>   
>   /* Register c++-specific dumps.  */
> --- gcc/cp/pt.cc.jj	2024-11-06 18:53:10.819844033 +0100
> +++ gcc/cp/pt.cc	2024-11-08 18:11:01.996073922 +0100
> @@ -21242,6 +21242,30 @@ tsubst_expr (tree t, tree args, tsubst_f
>   		  else if (TREE_CODE (call) == AGGR_INIT_EXPR)
>   		    AGGR_INIT_EXPR_MUST_TAIL (call) = mtc;
>   		}
> +	    if (CALL_FROM_NEW_OR_DELETE_P (t))
> +	      {
> +		tree call = extract_call_expr (ret);
> +		tree fn = cp_get_callee_fndecl_nofold (call);
> +		if (fn ? !DECL_IS_REPLACEABLE_OPERATOR (fn)
> +		       : !processing_template_decl)
> +		  {
> +		    auto_diagnostic_group d;
> +		    location_t loc = cp_expr_loc_or_input_loc (t);
> +		    if (!fn || IDENTIFIER_NEW_OP_P (DECL_NAME (fn)))
> +		      error_at (loc, "call to %<__builtin_operator_new%> "
> +				     "does not select replaceable global "
> +				     "allocation function");
> +		    else
> +		      error_at (loc, "call to %<__builtin_operator_delete%> "
> +				     "does not select replaceable global "
> +				     "deallocation function");
> +		    if (fn)
> +		      inform (DECL_SOURCE_LOCATION (fn),
> +			      "selected function declared here");
> +		  }
> +		else if (call && TREE_CODE (call) == CALL_EXPR)
> +		  CALL_FROM_NEW_OR_DELETE_P (call) = 1;
> +	      }
>   	    if (warning_suppressed_p (t, OPT_Wpessimizing_move))
>   	      /* This also suppresses -Wredundant-move.  */
>   	      suppress_warning (ret, OPT_Wpessimizing_move);
> --- gcc/doc/extend.texi.jj	2024-11-07 13:16:47.863196448 +0100
> +++ gcc/doc/extend.texi	2024-11-08 17:30:47.677330935 +0100
> @@ -90,6 +90,7 @@ extensions, accepted by GCC in C90 mode
>   * x86 specific memory model extensions for transactional memory:: x86 memory models.
>   * Object Size Checking:: Built-in functions for limited buffer overflow
>                           checking.
> +* New/Delete Builtins:: Built-in functions for C++ allocations and deallocations.
>   * Other Builtins::      Other built-in functions.
>   * Target Builtins::     Built-in functions specific to particular targets.
>   * Target Format Checks:: Format checks specific to particular targets.
> @@ -14313,6 +14314,33 @@ format string @var{fmt}.  If the compile
>   is called and the @var{flag} argument passed to it.
>   @enddefbuiltin
>   
> +@node New/Delete Builtins
> +@section Built-in functions for C++ allocations and deallocations
> +@findex __builtin_operator_new
> +@findex __builtin_operator_delete
> +Calling these C++ built-in functions is similar to calling
> +@code{::operator new} or @code{::operator delete} with the same arguments,
> +except that it is an error if the selected @code{::operator new} or
> +@code{::operator delete} overload is not a replaceable global operator
> +and for optimization purposes calls to pairs of these functions can be
> +omitted if access to the allocation is optimized out, or could be replaced
> +with implementation provided buffer on the stack, or multiple allocation
> +calls can be merged into a single allocation.  In C++ such optimizations
> +are normally allowed just for calls to such replaceable global operators
> +from @code{new} and @code{delete} expressions.
> +
> +@smallexample
> +void foo () @{
> +  int *a = new int;
> +  delete a; // This pair of allocation/deallocation operators can be omitted
> +	    // or replaced with int _temp; int *a = &_temp; etc.@:
> +  void *b = ::operator new (32);
> +  ::operator delete (b); // This one cannnot.
> +  void *c = __builtin_operator_new (32);
> +  __builtin_operator_delete (c); // This one can.
> +@}
> +@end smallexample
> +
>   @node Other Builtins
>   @section Other Built-in Functions Provided by GCC
>   @cindex built-in functions
> --- gcc/testsuite/g++.dg/ext/builtin-operator-new-1.C.jj	2024-11-08 16:34:52.905011446 +0100
> +++ gcc/testsuite/g++.dg/ext/builtin-operator-new-1.C	2024-11-08 17:38:43.996566453 +0100
> @@ -0,0 +1,106 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdump-tree-gimple -fdump-tree-optimized" }
> +// { dg-final { scan-tree-dump " = operator new \\\(" "gimple" } }
> +// { dg-final { scan-tree-dump "operator delete \\\(" "gimple" } }
> +// { dg-final { scan-tree-dump-not "operator new \\\(" "optimized" } }
> +// { dg-final { scan-tree-dump-not "operator delete \\\(" "optimized" } }
> +
> +#include <new>
> +
> +#if __has_builtin (__builtin_operator_new) != 201802L
> +#error "Unexpected value of __has_builtin (__builtin_operator_new)"
> +#endif
> +#if __has_builtin (__builtin_operator_delete) != 201802L
> +#error "Unexpected value of __has_builtin (__builtin_operator_delete)"
> +#endif
> +
> +void
> +foo ()
> +{
> +  void *a = __builtin_operator_new (32);
> +  __builtin_operator_delete (a);
> +#if __cpp_sized_deallocation
> +  a = __builtin_operator_new (32);
> +  __builtin_operator_delete (a, 32);
> +#endif
> +#if __cpp_aligned_new
> +  void *b = __builtin_operator_new (32, std::align_val_t(32));
> +  __builtin_operator_delete (b, std::align_val_t(32));
> +#if __cpp_sized_deallocation
> +  b = __builtin_operator_new (32, std::align_val_t(32));
> +  __builtin_operator_delete (b, 32, std::align_val_t(32));
> +#endif
> +#endif
> +  void *c = __builtin_operator_new (32, std::nothrow);
> +  __builtin_operator_delete (c, std::nothrow);
> +#if __cpp_aligned_new
> +  void *d = __builtin_operator_new (32, std::align_val_t(32), std::nothrow);
> +  __builtin_operator_delete (d, std::align_val_t(32), std::nothrow);
> +#endif
> +  void *e = __builtin_operator_new (1.f);
> +  __builtin_operator_delete (e);
> +}
> +
> +template <int N>
> +void
> +bar ()
> +{
> +  void *a = __builtin_operator_new (32);
> +  __builtin_operator_delete (a);
> +#if __cpp_sized_deallocation
> +  a = __builtin_operator_new (32);
> +  __builtin_operator_delete (a, 32);
> +#endif
> +#if __cpp_aligned_new
> +  void *b = __builtin_operator_new (32, std::align_val_t(32));
> +  __builtin_operator_delete (b, std::align_val_t(32));
> +#if __cpp_sized_deallocation
> +  b = __builtin_operator_new (32, std::align_val_t(32));
> +  __builtin_operator_delete (b, 32, std::align_val_t(32));
> +#endif
> +#endif
> +  void *c = __builtin_operator_new (32, std::nothrow);
> +  __builtin_operator_delete (c, std::nothrow);
> +#if __cpp_aligned_new
> +  void *d = __builtin_operator_new (32, std::align_val_t(32), std::nothrow);
> +  __builtin_operator_delete (d, std::align_val_t(32), std::nothrow);
> +#endif
> +  void *e = __builtin_operator_new (1.f);
> +  __builtin_operator_delete (e);
> +}
> +
> +template <typename T, typename U, typename V, typename W>
> +void
> +baz (T sz, V v, W w)
> +{
> +  U a = __builtin_operator_new (sz);
> +  __builtin_operator_delete (a);
> +#if __cpp_sized_deallocation
> +  a = __builtin_operator_new (sz);
> +  __builtin_operator_delete (a, sz);
> +#endif
> +#if __cpp_aligned_new
> +  U b = __builtin_operator_new (sz, std::align_val_t(sz));
> +  __builtin_operator_delete (b, std::align_val_t(sz));
> +#if __cpp_sized_deallocation
> +  b = __builtin_operator_new (sz, std::align_val_t(sz));
> +  __builtin_operator_delete (b, sz, std::align_val_t(sz));
> +#endif
> +#endif
> +  U c = __builtin_operator_new (sz, v);
> +  __builtin_operator_delete (c, v);
> +#if __cpp_aligned_new
> +  U d = __builtin_operator_new (sz, std::align_val_t(sz), v);
> +  __builtin_operator_delete (d, std::align_val_t(sz), v);
> +#endif
> +  U e = __builtin_operator_new (w);
> +  __builtin_operator_delete (e);
> +}
> +
> +void
> +qux ()
> +{
> +  bar <0> ();
> +  baz <std::size_t, void *, const std::nothrow_t &, float> (32, std::nothrow,
> +							    1.f);
> +}
> --- gcc/testsuite/g++.dg/ext/builtin-operator-new-2.C.jj	2024-11-08 16:42:34.552451715 +0100
> +++ gcc/testsuite/g++.dg/ext/builtin-operator-new-2.C	2024-11-08 18:06:47.469683258 +0100
> @@ -0,0 +1,49 @@
> +// { dg-do compile }
> +
> +#include <new>
> +
> +void *operator new (std::size_t, float, double);	// { dg-message "selected function declared here" }
> +void operator delete (void *, float, double);		// { dg-message "selected function declared here" }
> +
> +void
> +foo (void *x)
> +{
> +  void *a = __builtin_operator_new (32, 1.f, 32.);	// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
> +  __builtin_operator_delete (a, 1.f, 32.);		// { dg-error "call to '__builtin_operator_delete' does not select replaceable global deallocation function" }
> +  void *b = __builtin_operator_new (32, x);		// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
> +  void *c = __builtin_operator_new (32, 1LL, 1L, 1);	// { dg-error "no matching function for call to 'operator new\\\(int, long long int, long int, int\\\)'" }
> +  __builtin_operator_delete (c, 1LL, 1L, 1);		// { dg-error "no matching function for call to 'operator delete\\\(void\\\*&, long long int, long int, int\\\)'" }
> +  void *d = __builtin_operator_new ();			// { dg-error "no matching function for call to 'operator new\\\(\\\)'" }
> +  __builtin_operator_delete ();				// { dg-error "no matching function for call to 'operator delete\\\(\\\)'" }
> +}
> +
> +template <int N>
> +void
> +bar (void *x)
> +{
> +  void *a = __builtin_operator_new (32, 1.f, 32.);	// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
> +  __builtin_operator_delete (a, 1.f, 32.);		// { dg-error "call to '__builtin_operator_delete' does not select replaceable global deallocation function" }
> +  void *b = __builtin_operator_new (32, x);		// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
> +  void *c = __builtin_operator_new (32, 1LL, 1L, 1);	// { dg-error "no matching function for call to 'operator new\\\(int, long long int, long int, int\\\)'" }
> +  __builtin_operator_delete (c, 1LL, 1L, 1);		// { dg-error "no matching function for call to 'operator delete\\\(void\\\*&, long long int, long int, int\\\)'" }
> +  void *d = __builtin_operator_new ();			// { dg-error "no matching function for call to 'operator new\\\(\\\)'" }
> +  __builtin_operator_delete ();				// { dg-error "no matching function for call to 'operator delete\\\(\\\)'" }
> +}
> +
> +template <typename T, typename U, typename V>
> +void
> +baz (void *x, T sz, V v)
> +{
> +  U a = __builtin_operator_new (sz, 1.f, 32.);		// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
> +  __builtin_operator_delete (a, 1.f, 32.);		// { dg-error "call to '__builtin_operator_delete' does not select replaceable global deallocation function" }
> +  U b = __builtin_operator_new (sz, x);			// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
> +  U c = __builtin_operator_new (sz, v, 1L, 1);		// { dg-error "no matching function for call to 'operator new\\\(int&, long long int&, long int, int\\\)'" }
> +  __builtin_operator_delete (c, v, 1L, 1);		// { dg-error "no matching function for call to 'operator delete\\\(void\\\*&, long long int&, long int, int\\\)'" }
> +}
> +
> +void
> +qux (void *x, void *y)
> +{
> +  bar <0> (x);
> +  baz <int, void *, long long> (y, 32, 1LL);
> +}
> --- gcc/testsuite/g++.dg/ext/builtin-operator-new-3.C.jj	2024-11-08 18:12:07.583145144 +0100
> +++ gcc/testsuite/g++.dg/ext/builtin-operator-new-3.C	2024-11-08 18:17:46.562357225 +0100
> @@ -0,0 +1,47 @@
> +// { dg-do run }
> +
> +#include <new>
> +
> +void
> +use (void *p)
> +{
> +  int *volatile q = new (p) int;
> +  *q = 42;
> +  (*q)++;
> +  if (*q != 43)
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  void *volatile a = __builtin_operator_new (sizeof (int));
> +  use (a);
> +  __builtin_operator_delete (a);
> +#if __cpp_sized_deallocation
> +  a = __builtin_operator_new (sizeof (int));
> +  use (a);
> +  __builtin_operator_delete (a, sizeof (int));
> +#endif
> +#if __cpp_aligned_new
> +  void *volatile b = __builtin_operator_new (sizeof (int), std::align_val_t(alignof (int) * 4));
> +  use (b);
> +  __builtin_operator_delete (b, std::align_val_t(alignof (int) * 4));
> +#if __cpp_sized_deallocation
> +  b = __builtin_operator_new (sizeof (int), std::align_val_t(alignof (int) * 4));
> +  use (b);
> +  __builtin_operator_delete (b, sizeof (int), std::align_val_t(alignof (int) * 4));
> +#endif
> +#endif
> +  void *volatile c = __builtin_operator_new (sizeof (int), std::nothrow);
> +  use (c);
> +  __builtin_operator_delete (c, std::nothrow);
> +#if __cpp_aligned_new
> +  void *volatile d = __builtin_operator_new (sizeof (int), std::align_val_t(alignof (int) * 4), std::nothrow);
> +  use (d);
> +  __builtin_operator_delete (d, std::align_val_t(sizeof (int)), std::nothrow);
> +#endif
> +  void *volatile e = __builtin_operator_new (1.f);
> +  use (e);
> +  __builtin_operator_delete (e);
> +}
> 
> 	Jakub
>
Sam James Nov. 11, 2024, 6:47 p.m. UTC | #3
Jakub Jelinek <jakub@redhat.com> writes:

> On Fri, Nov 08, 2024 at 06:40:16PM +0100, Jakub Jelinek wrote:
>> clang++ adds __builtin_operator_{new,delete} builtins which as documented
>> work similarly to ::operator {new,delete}, except that it is an error
>> if the called ::operator {new,delete} is not a replaceable global operator
>> and allow optimizations which C++ normally allows just when those are used
>> from new/delete expressions https://eel.is/c++draft/expr.new#14
>> When using these builtins, the same optimizations can be done even when
>> using those builtins.
>> 
>> For GCC we note that in the CALL_FROM_NEW_OR_DELETE_P flag on CALL_EXPRs.
>> The following patch implements it as a C++ FE keyword (because passing
>> references through ... changes the argument and so BUILT_IN_FRONTEND
>> builtin can't be used), just attempts to call the ::operator {new,delete}
>> and if it isn't replaceable, diagnoses it.
>> 
>> So far lightly tested, ok for trunk if it passes bootstrap/regtest
>> (note, libstdc++ already uses the builtin)?
>
> Bootstrapped/regtested successfully on x86_64-linux and i686-linux.

Maybe tag PR110137 given it's very related (and of interest to people
CC'd on the bug).

>
> 	Jakub
Jakub Jelinek Nov. 11, 2024, 6:56 p.m. UTC | #4
On Mon, Nov 11, 2024 at 06:47:43PM +0000, Sam James wrote:
> > Bootstrapped/regtested successfully on x86_64-linux and i686-linux.
> 
> Maybe tag PR110137 given it's very related (and of interest to people
> CC'd on the bug).

It is maybe related, but it is a distinct enhancement, so I've committed it
without that PR in the commit message.

	Jakub
Sam James Nov. 11, 2024, 7:05 p.m. UTC | #5
Jakub Jelinek <jakub@redhat.com> writes:

> On Mon, Nov 11, 2024 at 06:47:43PM +0000, Sam James wrote:
>> > Bootstrapped/regtested successfully on x86_64-linux and i686-linux.
>> 
>> Maybe tag PR110137 given it's very related (and of interest to people
>> CC'd on the bug).
>
> It is maybe related, but it is a distinct enhancement, so I've committed it
> without that PR in the commit message.

OK, fair enough.

>
> 	Jakub
Jan Hubicka Nov. 13, 2024, 1:46 p.m. UTC | #6
> Hi!
> 
> clang++ adds __builtin_operator_{new,delete} builtins which as documented
> work similarly to ::operator {new,delete}, except that it is an error
> if the called ::operator {new,delete} is not a replaceable global operator
> and allow optimizations which C++ normally allows just when those are used
> from new/delete expressions https://eel.is/c++draft/expr.new#14
> When using these builtins, the same optimizations can be done even when
> using those builtins.
> 
> For GCC we note that in the CALL_FROM_NEW_OR_DELETE_P flag on CALL_EXPRs.
> The following patch implements it as a C++ FE keyword (because passing
> references through ... changes the argument and so BUILT_IN_FRONTEND
> builtin can't be used), just attempts to call the ::operator {new,delete}
> and if it isn't replaceable, diagnoses it.
> 
> So far lightly tested, ok for trunk if it passes bootstrap/regtest
> (note, libstdc++ already uses the builtin)?
> 
Thanks a lot for the patch!   I checked the counts of calls to operator
delete (by greping disasembly for call.*_Zdl) in clang 20 binary built
with -O3 -DNDEBUG and get:

 after patch       70576
 before patch      70366

So the patch certainly does something. (Based on earlier Martin's counts
I hoped for more, but it is a start).  Curiously clang 18 built binary
has about 50k calls. These counts are not really comparable due to
inlining differences but it would be really nice to know why there is
such a huge difference.

Counting actual runtime calls using ltrace and "grep _Zdl | wc -l"
during tramp3d compilation show no significant difference between
patched/unpatched gcc and clang 18.

Honza
diff mbox series

Patch

--- gcc/c-family/c-common.h.jj	2024-10-25 10:00:29.314770060 +0200
+++ gcc/c-family/c-common.h	2024-11-08 16:53:01.198538630 +0100
@@ -168,6 +168,7 @@  enum rid
   RID_ADDRESSOF,
   RID_BUILTIN_LAUNDER,
   RID_BUILTIN_BIT_CAST,
+  RID_BUILTIN_OPERATOR_NEW, RID_BUILTIN_OPERATOR_DELETE,
 
   /* C++11 */
   RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
@@ -840,7 +841,7 @@  extern bool in_late_binary_op;
 extern const char *c_addr_space_name (addr_space_t as);
 extern tree identifier_global_value (tree);
 extern tree identifier_global_tag (tree);
-extern bool names_builtin_p (const char *);
+extern int names_builtin_p (const char *);
 extern tree c_linkage_bindings (tree);
 extern void record_builtin_type (enum rid, const char *, tree);
 extern void start_fname_decls (void);
--- gcc/c-family/c-common.cc.jj	2024-10-30 07:59:36.347587874 +0100
+++ gcc/c-family/c-common.cc	2024-11-08 15:33:51.733170328 +0100
@@ -434,6 +434,8 @@  const struct c_common_resword c_common_r
   { "__builtin_counted_by_ref", RID_BUILTIN_COUNTED_BY_REF, D_CONLY },
   { "__builtin_has_attribute", RID_BUILTIN_HAS_ATTRIBUTE, 0 },
   { "__builtin_launder", RID_BUILTIN_LAUNDER, D_CXXONLY },
+  { "__builtin_operator_new", RID_BUILTIN_OPERATOR_NEW, D_CXXONLY },
+  { "__builtin_operator_delete", RID_BUILTIN_OPERATOR_DELETE, D_CXXONLY },
   { "__builtin_shuffle", RID_BUILTIN_SHUFFLE, 0 },
   { "__builtin_shufflevector", RID_BUILTIN_SHUFFLEVECTOR, 0 },
   { "__builtin_stdc_bit_ceil", RID_BUILTIN_STDC, D_CONLY },
--- gcc/c/c-decl.cc.jj	2024-10-31 21:17:06.857021211 +0100
+++ gcc/c/c-decl.cc	2024-11-08 16:56:56.153195293 +0100
@@ -11751,10 +11751,10 @@  identifier_global_tag (tree t)
   return NULL_TREE;
 }
 
-/* Returns true if NAME refers to a built-in function or function-like
-   operator.  */
+/* Returns non-zero (result of __has_builtin) if NAME refers to a built-in
+   function or function-like operator.  */
 
-bool
+int
 names_builtin_p (const char *name)
 {
   tree id = get_identifier (name);
@@ -11775,12 +11775,12 @@  names_builtin_p (const char *name)
     case RID_CHOOSE_EXPR:
     case RID_OFFSETOF:
     case RID_TYPES_COMPATIBLE_P:
-      return true;
+      return 1;
     default:
       break;
     }
 
-  return false;
+  return 0;
 }
 
 /* In C, the only C-linkage public declaration is at file scope.  */
--- gcc/cp/parser.cc.jj	2024-11-06 18:53:10.815844090 +0100
+++ gcc/cp/parser.cc	2024-11-08 17:52:04.332208389 +0100
@@ -7733,6 +7733,8 @@  cp_parser_postfix_expression (cp_parser
     case RID_BUILTIN_SHUFFLEVECTOR:
     case RID_BUILTIN_LAUNDER:
     case RID_BUILTIN_ASSOC_BARRIER:
+    case RID_BUILTIN_OPERATOR_NEW:
+    case RID_BUILTIN_OPERATOR_DELETE:
       {
 	vec<tree, va_gc> *vec;
 
@@ -7819,6 +7821,39 @@  cp_parser_postfix_expression (cp_parser
 	      }
 	    break;
 
+	  case RID_BUILTIN_OPERATOR_NEW:
+	  case RID_BUILTIN_OPERATOR_DELETE:
+	    tree fn;
+	    fn = ovl_op_identifier (keyword == RID_BUILTIN_OPERATOR_NEW
+				    ? NEW_EXPR : DELETE_EXPR);
+	    fn = lookup_qualified_name (global_namespace, fn);
+	    postfix_expression = finish_call_expr (fn, &vec, true, false,
+						   tf_warning_or_error);
+	    if (postfix_expression != error_mark_node)
+	      {
+		tree call = extract_call_expr (postfix_expression);
+		fn = cp_get_callee_fndecl_nofold (call);
+		if (fn ? !DECL_IS_REPLACEABLE_OPERATOR (fn)
+		       : !processing_template_decl)
+		  {
+		    auto_diagnostic_group d;
+		    if (keyword == RID_BUILTIN_OPERATOR_NEW)
+		      error_at (loc, "call to %<__builtin_operator_new%> "
+				     "does not select replaceable global "
+				     "allocation function");
+		    else 
+		      error_at (loc, "call to %<__builtin_operator_delete%> "
+				     "does not select replaceable global "
+				     "deallocation function");
+		    if (fn)
+		      inform (DECL_SOURCE_LOCATION (fn),
+			      "selected function declared here");
+		  }
+		else if (call && TREE_CODE (call) == CALL_EXPR)
+		  CALL_FROM_NEW_OR_DELETE_P (call) = 1;
+	      }
+	    break;
+
 	  default:
 	    gcc_unreachable ();
 	  }
--- gcc/cp/cp-objcp-common.cc.jj	2024-10-24 18:53:38.326085876 +0200
+++ gcc/cp/cp-objcp-common.cc	2024-11-08 16:57:52.061399744 +0100
@@ -545,10 +545,10 @@  identifier_global_tag (tree name)
   return ret;
 }
 
-/* Returns true if NAME refers to a built-in function or function-like
-   operator.  */
+/* Returns non-zero (result of __has_builtin) if NAME refers to a built-in
+   function or function-like operator.  */
 
-bool
+int
 names_builtin_p (const char *name)
 {
   tree id = get_identifier (name);
@@ -556,23 +556,23 @@  names_builtin_p (const char *name)
     {
       if (TREE_CODE (binding) == FUNCTION_DECL
 	  && DECL_IS_UNDECLARED_BUILTIN (binding))
-	return true;
+	return 1;
 
       /* Handle the case when an overload for a  built-in name exists.  */
       if (TREE_CODE (binding) != OVERLOAD)
-	return false;
+	return 0;
 
       for (ovl_iterator it (binding); it; ++it)
 	{
 	  tree decl = *it;
 	  if (DECL_IS_UNDECLARED_BUILTIN (decl))
-	    return true;
+	    return 1;
 	}
     }
 
   /* Check for built-in traits.  */
   if (IDENTIFIER_TRAIT_P (id))
-    return true;
+    return 1;
 
   /* Also detect common reserved C++ words that aren't strictly built-in
      functions.  */
@@ -587,12 +587,15 @@  names_builtin_p (const char *name)
     case RID_BUILTIN_ASSOC_BARRIER:
     case RID_BUILTIN_BIT_CAST:
     case RID_OFFSETOF:
-      return true;
+      return 1;
+    case RID_BUILTIN_OPERATOR_NEW:
+    case RID_BUILTIN_OPERATOR_DELETE:
+      return 201802L;
     default:
       break;
     }
 
-  return false;
+  return 0;
 }
 
 /* Register c++-specific dumps.  */
--- gcc/cp/pt.cc.jj	2024-11-06 18:53:10.819844033 +0100
+++ gcc/cp/pt.cc	2024-11-08 18:11:01.996073922 +0100
@@ -21242,6 +21242,30 @@  tsubst_expr (tree t, tree args, tsubst_f
 		  else if (TREE_CODE (call) == AGGR_INIT_EXPR)
 		    AGGR_INIT_EXPR_MUST_TAIL (call) = mtc;
 		}
+	    if (CALL_FROM_NEW_OR_DELETE_P (t))
+	      {
+		tree call = extract_call_expr (ret);
+		tree fn = cp_get_callee_fndecl_nofold (call);
+		if (fn ? !DECL_IS_REPLACEABLE_OPERATOR (fn)
+		       : !processing_template_decl)
+		  {
+		    auto_diagnostic_group d;
+		    location_t loc = cp_expr_loc_or_input_loc (t);
+		    if (!fn || IDENTIFIER_NEW_OP_P (DECL_NAME (fn)))
+		      error_at (loc, "call to %<__builtin_operator_new%> "
+				     "does not select replaceable global "
+				     "allocation function");
+		    else 
+		      error_at (loc, "call to %<__builtin_operator_delete%> "
+				     "does not select replaceable global "
+				     "deallocation function");
+		    if (fn)
+		      inform (DECL_SOURCE_LOCATION (fn),
+			      "selected function declared here");
+		  }
+		else if (call && TREE_CODE (call) == CALL_EXPR)
+		  CALL_FROM_NEW_OR_DELETE_P (call) = 1;
+	      }
 	    if (warning_suppressed_p (t, OPT_Wpessimizing_move))
 	      /* This also suppresses -Wredundant-move.  */
 	      suppress_warning (ret, OPT_Wpessimizing_move);
--- gcc/doc/extend.texi.jj	2024-11-07 13:16:47.863196448 +0100
+++ gcc/doc/extend.texi	2024-11-08 17:30:47.677330935 +0100
@@ -90,6 +90,7 @@  extensions, accepted by GCC in C90 mode
 * x86 specific memory model extensions for transactional memory:: x86 memory models.
 * Object Size Checking:: Built-in functions for limited buffer overflow
                         checking.
+* New/Delete Builtins:: Built-in functions for C++ allocations and deallocations.
 * Other Builtins::      Other built-in functions.
 * Target Builtins::     Built-in functions specific to particular targets.
 * Target Format Checks:: Format checks specific to particular targets.
@@ -14313,6 +14314,33 @@  format string @var{fmt}.  If the compile
 is called and the @var{flag} argument passed to it.
 @enddefbuiltin
 
+@node New/Delete Builtins
+@section Built-in functions for C++ allocations and deallocations
+@findex __builtin_operator_new
+@findex __builtin_operator_delete
+Calling these C++ built-in functions is similar to calling
+@code{::operator new} or @code{::operator delete} with the same arguments,
+except that it is an error if the selected @code{::operator new} or
+@code{::operator delete} overload is not a replaceable global operator
+and for optimization purposes calls to pairs of these functions can be
+omitted if access to the allocation is optimized out, or could be replaced
+with implementation provided buffer on the stack, or multiple allocation
+calls can be merged into a single allocation.  In C++ such optimizations
+are normally allowed just for calls to such replaceable global operators
+from @code{new} and @code{delete} expressions.
+
+@smallexample
+void foo () @{
+  int *a = new int;
+  delete a; // This pair of allocation/deallocation operators can be omitted
+	    // or replaced with int _temp; int *a = &_temp; etc.@:
+  void *b = ::operator new (32);
+  ::operator delete (b); // This one cannnot.
+  void *c = __builtin_operator_new (32);
+  __builtin_operator_delete (c); // This one can.
+@}
+@end smallexample
+
 @node Other Builtins
 @section Other Built-in Functions Provided by GCC
 @cindex built-in functions
--- gcc/testsuite/g++.dg/ext/builtin-operator-new-1.C.jj	2024-11-08 16:34:52.905011446 +0100
+++ gcc/testsuite/g++.dg/ext/builtin-operator-new-1.C	2024-11-08 17:38:43.996566453 +0100
@@ -0,0 +1,106 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -fdump-tree-gimple -fdump-tree-optimized" }
+// { dg-final { scan-tree-dump " = operator new \\\(" "gimple" } }
+// { dg-final { scan-tree-dump "operator delete \\\(" "gimple" } }
+// { dg-final { scan-tree-dump-not "operator new \\\(" "optimized" } }
+// { dg-final { scan-tree-dump-not "operator delete \\\(" "optimized" } }
+
+#include <new>
+
+#if __has_builtin (__builtin_operator_new) != 201802L
+#error "Unexpected value of __has_builtin (__builtin_operator_new)"
+#endif
+#if __has_builtin (__builtin_operator_delete) != 201802L
+#error "Unexpected value of __has_builtin (__builtin_operator_delete)"
+#endif
+
+void
+foo ()
+{
+  void *a = __builtin_operator_new (32);
+  __builtin_operator_delete (a);
+#if __cpp_sized_deallocation
+  a = __builtin_operator_new (32);
+  __builtin_operator_delete (a, 32);
+#endif
+#if __cpp_aligned_new
+  void *b = __builtin_operator_new (32, std::align_val_t(32));
+  __builtin_operator_delete (b, std::align_val_t(32));
+#if __cpp_sized_deallocation
+  b = __builtin_operator_new (32, std::align_val_t(32));
+  __builtin_operator_delete (b, 32, std::align_val_t(32));
+#endif
+#endif
+  void *c = __builtin_operator_new (32, std::nothrow);
+  __builtin_operator_delete (c, std::nothrow);
+#if __cpp_aligned_new
+  void *d = __builtin_operator_new (32, std::align_val_t(32), std::nothrow);
+  __builtin_operator_delete (d, std::align_val_t(32), std::nothrow);
+#endif
+  void *e = __builtin_operator_new (1.f);
+  __builtin_operator_delete (e);
+}
+
+template <int N>
+void
+bar ()
+{
+  void *a = __builtin_operator_new (32);
+  __builtin_operator_delete (a);
+#if __cpp_sized_deallocation
+  a = __builtin_operator_new (32);
+  __builtin_operator_delete (a, 32);
+#endif
+#if __cpp_aligned_new
+  void *b = __builtin_operator_new (32, std::align_val_t(32));
+  __builtin_operator_delete (b, std::align_val_t(32));
+#if __cpp_sized_deallocation
+  b = __builtin_operator_new (32, std::align_val_t(32));
+  __builtin_operator_delete (b, 32, std::align_val_t(32));
+#endif
+#endif
+  void *c = __builtin_operator_new (32, std::nothrow);
+  __builtin_operator_delete (c, std::nothrow);
+#if __cpp_aligned_new
+  void *d = __builtin_operator_new (32, std::align_val_t(32), std::nothrow);
+  __builtin_operator_delete (d, std::align_val_t(32), std::nothrow);
+#endif
+  void *e = __builtin_operator_new (1.f);
+  __builtin_operator_delete (e);
+}
+
+template <typename T, typename U, typename V, typename W>
+void
+baz (T sz, V v, W w)
+{
+  U a = __builtin_operator_new (sz);
+  __builtin_operator_delete (a);
+#if __cpp_sized_deallocation
+  a = __builtin_operator_new (sz);
+  __builtin_operator_delete (a, sz);
+#endif
+#if __cpp_aligned_new
+  U b = __builtin_operator_new (sz, std::align_val_t(sz));
+  __builtin_operator_delete (b, std::align_val_t(sz));
+#if __cpp_sized_deallocation
+  b = __builtin_operator_new (sz, std::align_val_t(sz));
+  __builtin_operator_delete (b, sz, std::align_val_t(sz));
+#endif
+#endif
+  U c = __builtin_operator_new (sz, v);
+  __builtin_operator_delete (c, v);
+#if __cpp_aligned_new
+  U d = __builtin_operator_new (sz, std::align_val_t(sz), v);
+  __builtin_operator_delete (d, std::align_val_t(sz), v);
+#endif
+  U e = __builtin_operator_new (w);
+  __builtin_operator_delete (e);
+}
+
+void
+qux ()
+{
+  bar <0> ();
+  baz <std::size_t, void *, const std::nothrow_t &, float> (32, std::nothrow,
+							    1.f);
+}
--- gcc/testsuite/g++.dg/ext/builtin-operator-new-2.C.jj	2024-11-08 16:42:34.552451715 +0100
+++ gcc/testsuite/g++.dg/ext/builtin-operator-new-2.C	2024-11-08 18:06:47.469683258 +0100
@@ -0,0 +1,49 @@ 
+// { dg-do compile }
+
+#include <new>
+
+void *operator new (std::size_t, float, double);	// { dg-message "selected function declared here" }
+void operator delete (void *, float, double);		// { dg-message "selected function declared here" }
+
+void
+foo (void *x)
+{
+  void *a = __builtin_operator_new (32, 1.f, 32.);	// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
+  __builtin_operator_delete (a, 1.f, 32.);		// { dg-error "call to '__builtin_operator_delete' does not select replaceable global deallocation function" }
+  void *b = __builtin_operator_new (32, x);		// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
+  void *c = __builtin_operator_new (32, 1LL, 1L, 1);	// { dg-error "no matching function for call to 'operator new\\\(int, long long int, long int, int\\\)'" }
+  __builtin_operator_delete (c, 1LL, 1L, 1);		// { dg-error "no matching function for call to 'operator delete\\\(void\\\*&, long long int, long int, int\\\)'" }
+  void *d = __builtin_operator_new ();			// { dg-error "no matching function for call to 'operator new\\\(\\\)'" }
+  __builtin_operator_delete ();				// { dg-error "no matching function for call to 'operator delete\\\(\\\)'" }
+}
+
+template <int N>
+void
+bar (void *x)
+{
+  void *a = __builtin_operator_new (32, 1.f, 32.);	// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
+  __builtin_operator_delete (a, 1.f, 32.);		// { dg-error "call to '__builtin_operator_delete' does not select replaceable global deallocation function" }
+  void *b = __builtin_operator_new (32, x);		// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
+  void *c = __builtin_operator_new (32, 1LL, 1L, 1);	// { dg-error "no matching function for call to 'operator new\\\(int, long long int, long int, int\\\)'" }
+  __builtin_operator_delete (c, 1LL, 1L, 1);		// { dg-error "no matching function for call to 'operator delete\\\(void\\\*&, long long int, long int, int\\\)'" }
+  void *d = __builtin_operator_new ();			// { dg-error "no matching function for call to 'operator new\\\(\\\)'" }
+  __builtin_operator_delete ();				// { dg-error "no matching function for call to 'operator delete\\\(\\\)'" }
+}
+
+template <typename T, typename U, typename V>
+void
+baz (void *x, T sz, V v)
+{
+  U a = __builtin_operator_new (sz, 1.f, 32.);		// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
+  __builtin_operator_delete (a, 1.f, 32.);		// { dg-error "call to '__builtin_operator_delete' does not select replaceable global deallocation function" }
+  U b = __builtin_operator_new (sz, x);			// { dg-error "call to '__builtin_operator_new' does not select replaceable global allocation function" }
+  U c = __builtin_operator_new (sz, v, 1L, 1);		// { dg-error "no matching function for call to 'operator new\\\(int&, long long int&, long int, int\\\)'" }
+  __builtin_operator_delete (c, v, 1L, 1);		// { dg-error "no matching function for call to 'operator delete\\\(void\\\*&, long long int&, long int, int\\\)'" }
+}
+
+void
+qux (void *x, void *y)
+{
+  bar <0> (x);
+  baz <int, void *, long long> (y, 32, 1LL);
+}
--- gcc/testsuite/g++.dg/ext/builtin-operator-new-3.C.jj	2024-11-08 18:12:07.583145144 +0100
+++ gcc/testsuite/g++.dg/ext/builtin-operator-new-3.C	2024-11-08 18:17:46.562357225 +0100
@@ -0,0 +1,47 @@ 
+// { dg-do run }
+
+#include <new>
+
+void
+use (void *p)
+{
+  int *volatile q = new (p) int;
+  *q = 42;
+  (*q)++;
+  if (*q != 43)
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  void *volatile a = __builtin_operator_new (sizeof (int));
+  use (a);
+  __builtin_operator_delete (a);
+#if __cpp_sized_deallocation
+  a = __builtin_operator_new (sizeof (int));
+  use (a);
+  __builtin_operator_delete (a, sizeof (int));
+#endif
+#if __cpp_aligned_new
+  void *volatile b = __builtin_operator_new (sizeof (int), std::align_val_t(alignof (int) * 4));
+  use (b);
+  __builtin_operator_delete (b, std::align_val_t(alignof (int) * 4));
+#if __cpp_sized_deallocation
+  b = __builtin_operator_new (sizeof (int), std::align_val_t(alignof (int) * 4));
+  use (b);
+  __builtin_operator_delete (b, sizeof (int), std::align_val_t(alignof (int) * 4));
+#endif
+#endif
+  void *volatile c = __builtin_operator_new (sizeof (int), std::nothrow);
+  use (c);
+  __builtin_operator_delete (c, std::nothrow);
+#if __cpp_aligned_new
+  void *volatile d = __builtin_operator_new (sizeof (int), std::align_val_t(alignof (int) * 4), std::nothrow);
+  use (d);
+  __builtin_operator_delete (d, std::align_val_t(sizeof (int)), std::nothrow);
+#endif
+  void *volatile e = __builtin_operator_new (1.f);
+  use (e);
+  __builtin_operator_delete (e);
+}