[clang] [clang] Add `::_placement_new` expression for built-in global placement new (PR #72209)

via cfe-commits cfe-commits at lists.llvm.org
Mon Nov 13 21:16:03 PST 2023


https://github.com/MaxEW707 created https://github.com/llvm/llvm-project/pull/72209

https://godbolt.org/z/7Khh33fxo for reference

#### Background

I work on a large software project where debug performance and compile-time performance are considered high priority.
Clang has already done a lot in this area which we very much appreciate especially since a lot of C++ constructs that should be language level constructs are implemented in code instead. We always appreciate builtins over function calls or template meta-programming for example.

We use clang on a variety of platforms some which do not use `libcxx` and some which have their own platform vendor stl implementations.
The project I work on also has our own stl generic library with our own differences, extensions, additions, removals from `std`, etc.
However sometimes we have to interface with vendor code or third-party libs that expose a `std` interface or include `std` headers.

Allocations in our project are tightly controlled and objects may be allocated from a variety of heaps or scratch buffers. 
We do not use global new or class specific new.
Therefore we rely heavily on the reserved global placement new to construct objects in memory.

#### Problem

In general `<new>` is expensive to include and also brings in a lot of transitive includes.
`libcxx` has a config option to remove some of these: https://github.com/llvm/llvm-project/blob/main/libcxx/include/new#L369
However our software builds on a variety of stl implementations which we cannot control.
We avoid including stl headers however when we need to interface with vendor code in platform cpp files sometimes they get included from vendor headers.

Clang already treats the reserved global placement new as a builtin if it is declared.
We forward declare this operator new on platforms that use clang but do not ship with `libcxx`.

`libcxx` puts the `abi_tag` attribute on its declaration. This means if `<new>` is included after our declaration we get a compiler error since `abi_tag` cannot be re-declared.
I tried to first get a PR up to `libcxx` [here](https://github.com/llvm/llvm-project/pull/70538) but it was deemed that we shouldn't work around the `std`.

Constructing objects in memory is such a vital part of C++ and I believe that this support should be a zero-cost abstraction in the language.

#### Solution

I decided to add a clang specific `::_placement_new` expression that is treated as a builtin.
This expression does the equivalent of the reserved global placement new expression without requiring library support.

This route had a couple extra benefits as well which are absolutely no header includes and absolutely no overload resolution to find the best operator new function for the new expression.
This is in addition to the already intended benefits of no debug overhead, no linker symbols and no debug symbols.

If this solution is not viable I would like to try to come up with a solution that gives us all the benefits of clangs builtin support for the reserved global placement new operator without having to work around different `std` implementations.
While I ran into an issue with `libcxx`, as noted above we use clang and vendor forks of clang with vendor specific stl implementations, we may very well run into some issue with another vendor stl implementation on clang in the future.

>From 75cf305fe732d00be910a6aa0afe79953c5b7186 Mon Sep 17 00:00:00 2001
From: MaxEW707 <82551778+MaxEW707 at users.noreply.github.com>
Date: Sun, 12 Nov 2023 11:36:58 -0500
Subject: [PATCH] Implement `::_placement_new` expression for built-in global
 placement new without header includes, overload resolution or dealing with
 std libraries that declare the global placement new with attributes that
 cannot be redeclared.

---
 clang/include/clang/AST/ExprCXX.h             | 23 +++++++++
 clang/include/clang/AST/Stmt.h                |  4 ++
 .../clang/Basic/DiagnosticParseKinds.td       |  2 +
 clang/include/clang/Basic/TokenKinds.def      |  1 +
 clang/include/clang/Sema/Sema.h               |  4 +-
 clang/lib/AST/ASTImporter.cpp                 | 18 ++++---
 clang/lib/AST/ExprCXX.cpp                     | 34 +++++++++++++
 clang/lib/AST/ExprConstant.cpp                |  7 ++-
 clang/lib/AST/JSONNodeDumper.cpp              |  1 +
 clang/lib/AST/StmtPrinter.cpp                 |  5 +-
 clang/lib/AST/StmtProfile.cpp                 |  1 +
 clang/lib/AST/TextNodeDumper.cpp              |  2 +
 clang/lib/CodeGen/CGExprCXX.cpp               |  8 ++--
 clang/lib/CodeGen/CodeGenModule.cpp           |  5 +-
 clang/lib/Interpreter/Interpreter.cpp         |  4 +-
 clang/lib/Parse/ParseDecl.cpp                 |  6 ++-
 clang/lib/Parse/ParseExpr.cpp                 |  2 +-
 clang/lib/Parse/ParseExprCXX.cpp              | 19 ++++++--
 clang/lib/Parse/ParseObjc.cpp                 |  1 +
 clang/lib/Parse/ParseTentative.cpp            |  3 +-
 clang/lib/Sema/SemaExprCXX.cpp                | 48 ++++++++++++++-----
 clang/lib/Sema/TreeTransform.h                |  6 +--
 clang/lib/Serialization/ASTReaderStmt.cpp     |  2 +
 clang/lib/Serialization/ASTWriterStmt.cpp     |  2 +
 .../Checkers/CheckPlacementNew.cpp            |  2 +-
 .../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp |  9 ++--
 clang/test/AST/ast-dump-expr.cpp              | 14 ++++++
 clang/test/CodeGenCXX/new.cpp                 | 44 ++++++++++++++++-
 clang/test/SemaCXX/new-delete.cpp             |  7 +++
 29 files changed, 234 insertions(+), 50 deletions(-)

diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 24278016431837b..d760af796aea28f 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -2282,6 +2282,13 @@ class CXXNewExpr final
              QualType Ty, TypeSourceInfo *AllocatedTypeInfo, SourceRange Range,
              SourceRange DirectInitRange);
 
+  /// Build a c++ builtin placement new expression
+  CXXNewExpr(Expr *PlacementArg,
+             SourceRange TypeIdParens, std::optional<Expr *> ArraySize,
+             CXXNewInitializationStyle InitializationStyle, Expr *Initializer,
+             QualType Ty, TypeSourceInfo *AllocatedTypeInfo, SourceRange Range,
+             SourceRange DirectInitRange);
+
   /// Build an empty c++ new expression.
   CXXNewExpr(EmptyShell Empty, bool IsArray, unsigned NumPlacementArgs,
              bool IsParenTypeId);
@@ -2297,6 +2304,14 @@ class CXXNewExpr final
          QualType Ty, TypeSourceInfo *AllocatedTypeInfo, SourceRange Range,
          SourceRange DirectInitRange);
 
+  /// Create a c++ builtin placement new expression.
+  static CXXNewExpr *
+  CreatePlacementNew(const ASTContext &Ctx, Expr *PlacementArg,
+                     SourceRange TypeIdParens, std::optional<Expr *> ArraySize,
+                     CXXNewInitializationStyle InitializationStyle, Expr *Initializer,
+                     QualType Ty, TypeSourceInfo *AllocatedTypeInfo, SourceRange Range,
+                     SourceRange DirectInitRange);
+
   /// Create an empty c++ new expression.
   static CXXNewExpr *CreateEmpty(const ASTContext &Ctx, bool IsArray,
                                  bool HasInit, unsigned NumPlacementArgs,
@@ -2332,6 +2347,12 @@ class CXXNewExpr final
   FunctionDecl *getOperatorDelete() const { return OperatorDelete; }
   void setOperatorDelete(FunctionDecl *D) { OperatorDelete = D; }
 
+  bool isReservedPlacementNew() const {
+    if (CXXNewExprBits.IsPlacementNewExpr)
+      return true;
+    return OperatorNew->isReservedGlobalPlacementOperator();
+  }
+
   bool isArray() const { return CXXNewExprBits.IsArray; }
 
   /// This might return std::nullopt even if isArray() returns true,
@@ -2387,6 +2408,8 @@ class CXXNewExpr final
 
   bool isGlobalNew() const { return CXXNewExprBits.IsGlobalNew; }
 
+  bool isPlacementNewExpr() const { return CXXNewExprBits.IsPlacementNewExpr; }
+
   /// Whether this new-expression has any initializer at all.
   bool hasInitializer() const {
     switch (getInitializationStyle()) {
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index da7b37ce0e1211f..2dc3746aee4fd71 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -878,6 +878,10 @@ class alignas(void *) Stmt {
     LLVM_PREFERRED_TYPE(bool)
     unsigned IsParenTypeId : 1;
 
+    /// True is this if the builtin placement-new expression.
+    LLVM_PREFERRED_TYPE(bool)
+    unsigned IsPlacementNewExpr : 1;
+
     /// The number of placement new arguments.
     unsigned NumPlacementArgs;
   };
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index de180344fcc5c74..c0ca7529fda892d 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -255,6 +255,8 @@ def err_expected_lbrace_in_compound_literal : Error<
   "expected '{' in compound literal">;
 def err_expected_while : Error<"expected 'while' in do/while loop">;
 
+def err_placement_new_expected_one_argument : Error<"expected only one argument in placement params">;
+
 def err_expected_semi_after_stmt : Error<"expected ';' after %0 statement">;
 def err_expected_semi_after_expr : Error<"expected ';' after expression">;
 def err_extraneous_token_before_semi : Error<"extraneous '%0' before ';'">;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 3ce317d318f9bb6..a2dfc1a9e08691a 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -365,6 +365,7 @@ KEYWORD(typeid                      , KEYCXX)
 KEYWORD(using                       , KEYCXX)
 KEYWORD(virtual                     , KEYCXX)
 KEYWORD(wchar_t                     , WCHARSUPPORT)
+KEYWORD(_placement_new              , KEYCXX)
 
 // C++ 2.5p2: Alternative Representations.
 CXX_KEYWORD_OPERATOR(and     , ampamp)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f69f366c1750918..ab9a0d0713435b6 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -6810,14 +6810,14 @@ class Sema final {
                                        bool ListInitialization);
 
   /// ActOnCXXNew - Parsed a C++ 'new' expression.
-  ExprResult ActOnCXXNew(SourceLocation StartLoc, bool UseGlobal,
+  ExprResult ActOnCXXNew(SourceLocation StartLoc, bool UseGlobal, bool IsPlacementNewExpr,
                          SourceLocation PlacementLParen,
                          MultiExprArg PlacementArgs,
                          SourceLocation PlacementRParen,
                          SourceRange TypeIdParens, Declarator &D,
                          Expr *Initializer);
   ExprResult
-  BuildCXXNew(SourceRange Range, bool UseGlobal, SourceLocation PlacementLParen,
+  BuildCXXNew(SourceRange Range, bool UseGlobal, bool IsPlacementNewExpr, SourceLocation PlacementLParen,
               MultiExprArg PlacementArgs, SourceLocation PlacementRParen,
               SourceRange TypeIdParens, QualType AllocType,
               TypeSourceInfo *AllocTypeInfo, std::optional<Expr *> ArraySize,
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index c4e931e220f69b5..3204fb69b3440f7 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -8102,12 +8102,18 @@ ExpectedStmt ASTNodeImporter::VisitCXXNewExpr(CXXNewExpr *E) {
       ImportContainerChecked(E->placement_arguments(), ToPlacementArgs))
     return std::move(Err);
 
-  return CXXNewExpr::Create(
-      Importer.getToContext(), E->isGlobalNew(), ToOperatorNew,
-      ToOperatorDelete, E->passAlignment(), E->doesUsualArrayDeleteWantSize(),
-      ToPlacementArgs, ToTypeIdParens, ToArraySize, E->getInitializationStyle(),
-      ToInitializer, ToType, ToAllocatedTypeSourceInfo, ToSourceRange,
-      ToDirectInitRange);
+  if (E->isPlacementNewExpr())
+    return CXXNewExpr::CreatePlacementNew(
+        Importer.getToContext(), ToPlacementArgs[0], ToTypeIdParens, ToArraySize,
+        E->getInitializationStyle(), ToInitializer, ToType, ToAllocatedTypeSourceInfo,
+        ToSourceRange, ToDirectInitRange);
+  else
+    return CXXNewExpr::Create(
+        Importer.getToContext(), E->isGlobalNew(), ToOperatorNew,
+        ToOperatorDelete, E->passAlignment(), E->doesUsualArrayDeleteWantSize(),
+        ToPlacementArgs, ToTypeIdParens, ToArraySize, E->getInitializationStyle(),
+        ToInitializer, ToType, ToAllocatedTypeSourceInfo, ToSourceRange,
+        ToDirectInitRange);
 }
 
 ExpectedStmt ASTNodeImporter::VisitCXXDeleteExpr(CXXDeleteExpr *E) {
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 83af7998f683382..7c7f4c2f31542b9 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -207,6 +207,7 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew,
   bool IsParenTypeId = TypeIdParens.isValid();
   CXXNewExprBits.IsParenTypeId = IsParenTypeId;
   CXXNewExprBits.NumPlacementArgs = PlacementArgs.size();
+  CXXNewExprBits.IsPlacementNewExpr = false;
 
   if (ArraySize)
     getTrailingObjects<Stmt *>()[arraySizeOffset()] = *ArraySize;
@@ -234,6 +235,18 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew,
   setDependence(computeDependence(this));
 }
 
+CXXNewExpr::CXXNewExpr(Expr *PlacementArg,
+                       SourceRange TypeIdParens, std::optional<Expr *> ArraySize,
+                       CXXNewInitializationStyle InitializationStyle, Expr *Initializer,
+                       QualType Ty, TypeSourceInfo *AllocatedTypeInfo, SourceRange Range,
+                       SourceRange DirectInitRange)
+    : CXXNewExpr(true, nullptr, nullptr, false, false,
+                 ArrayRef<Expr *>(&PlacementArg, 1), TypeIdParens, ArraySize,
+                 InitializationStyle, Initializer, Ty,
+                 AllocatedTypeInfo, Range, DirectInitRange) {
+    CXXNewExprBits.IsPlacementNewExpr = true;
+}
+
 CXXNewExpr::CXXNewExpr(EmptyShell Empty, bool IsArray,
                        unsigned NumPlacementArgs, bool IsParenTypeId)
     : Expr(CXXNewExprClass, Empty) {
@@ -265,6 +278,25 @@ CXXNewExpr *CXXNewExpr::Create(
                  AllocatedTypeInfo, Range, DirectInitRange);
 }
 
+CXXNewExpr *CXXNewExpr::CreatePlacementNew(
+    const ASTContext &Ctx, Expr *PlacementArg,
+    SourceRange TypeIdParens, std::optional<Expr *> ArraySize,
+    CXXNewInitializationStyle InitializationStyle, Expr *Initializer,
+    QualType Ty, TypeSourceInfo *AllocatedTypeInfo, SourceRange Range,
+    SourceRange DirectInitRange) {
+  bool IsArray = ArraySize.has_value();
+  bool HasInit = Initializer != nullptr;
+  bool IsParenTypeId = TypeIdParens.isValid();
+  void *Mem =
+      Ctx.Allocate(totalSizeToAlloc<Stmt *, SourceRange>(
+                       IsArray + HasInit + 1, IsParenTypeId),
+                   alignof(CXXNewExpr));
+  return new (Mem)
+      CXXNewExpr(PlacementArg, TypeIdParens,
+                 ArraySize, InitializationStyle, Initializer, Ty,
+                 AllocatedTypeInfo, Range, DirectInitRange);
+}
+
 CXXNewExpr *CXXNewExpr::CreateEmpty(const ASTContext &Ctx, bool IsArray,
                                     bool HasInit, unsigned NumPlacementArgs,
                                     bool IsParenTypeId) {
@@ -277,6 +309,8 @@ CXXNewExpr *CXXNewExpr::CreateEmpty(const ASTContext &Ctx, bool IsArray,
 }
 
 bool CXXNewExpr::shouldNullCheckAllocation() const {
+  if (isPlacementNewExpr())
+    return false;
   if (getOperatorNew()->getLangOpts().CheckNew)
     return true;
   return !getOperatorNew()->hasAttr<ReturnsNonNullAttr>() &&
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index e16fec6109e744e..7652e22a6a51b4f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -9740,7 +9740,7 @@ static bool EvaluateArrayNewConstructExpr(EvalInfo &Info, LValue &This,
                                           QualType AllocType);
 
 bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
-  if (!Info.getLangOpts().CPlusPlus20)
+  if (!Info.getLangOpts().CPlusPlus20 && !E->isPlacementNewExpr())
     Info.CCEDiag(E, diag::note_constexpr_new);
 
   // We cannot speculatively evaluate a delete expression.
@@ -9751,8 +9751,7 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
 
   bool IsNothrow = false;
   bool IsPlacement = false;
-  if (OperatorNew->isReservedGlobalPlacementOperator() &&
-      Info.CurrentCall->isStdFunction() && !E->isArray()) {
+  if (E->isReservedPlacementNew() && Info.CurrentCall->isStdFunction() && !E->isArray()) {
     // FIXME Support array placement new.
     assert(E->getNumPlacementArgs() == 1);
     if (!EvaluatePointer(E->getPlacementArg(0), Result, Info))
@@ -9760,7 +9759,7 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
     if (Result.Designator.Invalid)
       return false;
     IsPlacement = true;
-  } else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
+  } else if (E->isPlacementNewExpr() || !OperatorNew->isReplaceableGlobalAllocationFunction()) {
     Info.FFDiag(E, diag::note_constexpr_new_non_replaceable)
         << isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
     return false;
diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp
index ace5178bf625828..82c4f5e64416e21 100644
--- a/clang/lib/AST/JSONNodeDumper.cpp
+++ b/clang/lib/AST/JSONNodeDumper.cpp
@@ -1350,6 +1350,7 @@ void JSONNodeDumper::VisitCXXNewExpr(const CXXNewExpr *NE) {
   attributeOnlyIfTrue("isGlobal", NE->isGlobalNew());
   attributeOnlyIfTrue("isArray", NE->isArray());
   attributeOnlyIfTrue("isPlacement", NE->getNumPlacementArgs() != 0);
+  attributeOnlyIfTrue("isPlacementNewExpr", NE->isPlacementNewExpr());
   switch (NE->getInitializationStyle()) {
   case CXXNewInitializationStyle::None:
   case CXXNewInitializationStyle::Implicit:
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index ab4a013de5f552c..ce3d9b64de76ab0 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2271,7 +2271,10 @@ void StmtPrinter::VisitCXXScalarValueInitExpr(CXXScalarValueInitExpr *Node) {
 void StmtPrinter::VisitCXXNewExpr(CXXNewExpr *E) {
   if (E->isGlobalNew())
     OS << "::";
-  OS << "new ";
+  if (E->isPlacementNewExpr())
+    OS << "_placement_new ";
+  else
+    OS << "new ";
   unsigned NumPlace = E->getNumPlacementArgs();
   if (NumPlace > 0 && !isa<CXXDefaultArgExpr>(E->getPlacementArg(0))) {
     OS << "(";
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 8128219dd6f63c9..c3590f50eebfd2e 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -2096,6 +2096,7 @@ void StmtProfiler::VisitCXXNewExpr(const CXXNewExpr *S) {
   ID.AddInteger(S->getNumPlacementArgs());
   ID.AddBoolean(S->isGlobalNew());
   ID.AddBoolean(S->isParenTypeId());
+  ID.AddBoolean(S->isPlacementNewExpr());
   ID.AddInteger(llvm::to_underlying(S->getInitializationStyle()));
 }
 
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index e8274fcd5cfe9cb..57b6b9087826355 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -1356,6 +1356,8 @@ void TextNodeDumper::VisitCXXNewExpr(const CXXNewExpr *Node) {
     OS << " global";
   if (Node->isArray())
     OS << " array";
+  if (Node->isPlacementNewExpr())
+    OS << " builtin placement-new expression";
   if (Node->getOperatorNew()) {
     OS << ' ';
     dumpBareDeclRef(Node->getOperatorNew());
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 98ae56e2df88184..cfc16f2ffd3ad04 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -693,7 +693,7 @@ static CharUnits CalculateCookiePadding(CodeGenFunction &CGF,
 
   // No cookie is required if the operator new[] being used is the
   // reserved placement operator new[].
-  if (E->getOperatorNew()->isReservedGlobalPlacementOperator())
+  if (E->isReservedPlacementNew())
     return CharUnits::Zero();
 
   return CGF.CGM.getCXXABI().GetArrayCookieSize(E);
@@ -1584,7 +1584,7 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) {
   // operator, just "inline" it directly.
   Address allocation = Address::invalid();
   CallArgList allocatorArgs;
-  if (allocator->isReservedGlobalPlacementOperator()) {
+  if (E->isReservedPlacementNew()) {
     assert(E->getNumPlacementArgs() == 1);
     const Expr *arg = *E->placement_arguments().begin();
 
@@ -1599,6 +1599,7 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) {
 
     // Set up allocatorArgs for the call to operator delete if it's not
     // the reserved global operator.
+    assert(!E->isPlacementNewExpr() || !E->getOperatorDelete());
     if (E->getOperatorDelete() &&
         !E->getOperatorDelete()->isReservedGlobalPlacementOperator()) {
       allocatorArgs.add(RValue::get(allocSize), getContext().getSizeType());
@@ -1724,8 +1725,7 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) {
   // vptrs information which may be included in previous type.
   // To not break LTO with different optimizations levels, we do it regardless
   // of optimization level.
-  if (CGM.getCodeGenOpts().StrictVTablePointers &&
-      allocator->isReservedGlobalPlacementOperator())
+  if (CGM.getCodeGenOpts().StrictVTablePointers && E->isReservedPlacementNew())
     result = Builder.CreateLaunderInvariantGroup(result);
 
   // Emit sanitizer checks for pointer value now, so that in the case of an
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 4c7f516e308ca00..d0ed0289bcce52a 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -3837,7 +3837,10 @@ namespace {
     }
 
     bool VisitCXXNewExpr(CXXNewExpr *E) {
-      SafeToInline = E->getOperatorNew()->hasAttr<DLLImportAttr>();
+      if (E->isPlacementNewExpr())
+        SafeToInline = true;
+      else
+        SafeToInline = E->getOperatorNew()->hasAttr<DLLImportAttr>();
       return SafeToInline;
     }
   };
diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp
index 7968c62cbd3e7b3..93ba4a060342a22 100644
--- a/clang/lib/Interpreter/Interpreter.cpp
+++ b/clang/lib/Interpreter/Interpreter.cpp
@@ -606,8 +606,8 @@ class RuntimeInterfaceBuilder
       Expr *Args[] = {AllocCall.get()};
       ExprResult CXXNewCall = S.BuildCXXNew(
           E->getSourceRange(),
-          /*UseGlobal=*/true, /*PlacementLParen=*/SourceLocation(), Args,
-          /*PlacementRParen=*/SourceLocation(),
+          /*UseGlobal=*/true, /*IsPlacementNewExpr=*/false,
+          /*PlacementLParen=*/SourceLocation(), Args, /*PlacementRParen=*/SourceLocation(),
           /*TypeIdParens=*/SourceRange(), TSI->getType(), TSI, std::nullopt,
           E->getSourceRange(), E);
 
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 78c3ab72979a007..18b2fd86e566e5c 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -5457,7 +5457,8 @@ bool Parser::isTypeSpecifierQualifier() {
 
   case tok::coloncolon:   // ::foo::bar
     if (NextToken().is(tok::kw_new) ||    // ::new
-        NextToken().is(tok::kw_delete))   // ::delete
+        NextToken().is(tok::kw_delete) || // ::delete
+        NextToken().is(tok::kw__placement_new)) // ::_placement_new
       return false;
 
     if (TryAnnotateTypeOrScopeToken())
@@ -5650,7 +5651,8 @@ bool Parser::isDeclarationSpecifier(
     if (!getLangOpts().CPlusPlus)
       return false;
     if (NextToken().is(tok::kw_new) ||    // ::new
-        NextToken().is(tok::kw_delete))   // ::delete
+        NextToken().is(tok::kw_delete) || // ::delete
+        NextToken().is(tok::kw__placement_new)) // ::_placement_new
       return false;
 
     // Annotate typenames and C++ scope specifiers.  If we get one, just
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 53fba3b2f59242b..9b2e03221d5d407 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -1682,7 +1682,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
     // ::new -> [C++] new-expression
     // ::delete -> [C++] delete-expression
     SourceLocation CCLoc = ConsumeToken();
-    if (Tok.is(tok::kw_new)) {
+    if (Tok.is(tok::kw_new) || Tok.is(tok::kw__placement_new)) {
       if (NotPrimaryExpression)
         *NotPrimaryExpression = true;
       Res = ParseCXXNewExpression(true, CCLoc);
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 99b4931004546c1..f21680834cfc531 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -186,7 +186,7 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
   if (Tok.is(tok::coloncolon)) {
     // ::new and ::delete aren't nested-name-specifiers.
     tok::TokenKind NextKind = NextToken().getKind();
-    if (NextKind == tok::kw_new || NextKind == tok::kw_delete)
+    if (NextKind == tok::kw_new || NextKind == tok::kw_delete || NextKind == tok::kw__placement_new)
       return false;
 
     if (NextKind == tok::l_brace) {
@@ -3171,7 +3171,8 @@ bool Parser::ParseUnqualifiedId(CXXScopeSpec &SS, ParsedType ObjectType,
 ///
 ExprResult
 Parser::ParseCXXNewExpression(bool UseGlobal, SourceLocation Start) {
-  assert(Tok.is(tok::kw_new) && "expected 'new' token");
+  assert((Tok.is(tok::kw_new) || Tok.is(tok::kw__placement_new)) && "expected 'new' token");
+  const bool IsPlacementNewExpr = Tok.is(tok::kw__placement_new);
   ConsumeToken();   // Consume 'new'
 
   // A '(' now can be a new-placement or the '(' wrapping the type-id in the
@@ -3201,6 +3202,14 @@ Parser::ParseCXXNewExpression(bool UseGlobal, SourceLocation Start) {
       return ExprError();
     }
 
+    if (IsPlacementNewExpr) {
+      if (PlacementArgs.size() != 1) {
+        Diag(PlacementLParen, diag::err_placement_new_expected_one_argument);
+        SkipUntil(tok::semi, StopAtSemi | StopBeforeMatch);
+        return ExprError();
+      }
+    }
+
     if (PlacementArgs.empty()) {
       // Reset the placement locations. There was no placement.
       TypeIdParens = T.getRange();
@@ -3227,6 +3236,10 @@ Parser::ParseCXXNewExpression(bool UseGlobal, SourceLocation Start) {
         }
       }
     }
+  } else if (IsPlacementNewExpr) {
+    Diag(Tok, diag::err_expected_lparen_after) << "::_placement_new";
+    SkipUntil(tok::semi, StopAtSemi | StopBeforeMatch);
+    return ExprError();
   } else {
     // A new-type-id is a simplified type-id, where essentially the
     // direct-declarator is replaced by a direct-new-declarator.
@@ -3295,7 +3308,7 @@ Parser::ParseCXXNewExpression(bool UseGlobal, SourceLocation Start) {
   if (Initializer.isInvalid())
     return Initializer;
 
-  return Actions.ActOnCXXNew(Start, UseGlobal, PlacementLParen,
+  return Actions.ActOnCXXNew(Start, UseGlobal, IsPlacementNewExpr, PlacementLParen,
                              PlacementArgs, PlacementRParen,
                              TypeIdParens, DeclaratorInfo, Initializer.get());
 }
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index c0261c462b8834e..10764caf645dbea 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -1120,6 +1120,7 @@ IdentifierInfo *Parser::ParseObjCSelectorPiece(SourceLocation &SelectorLoc) {
   case tok::kw_mutable:
   case tok::kw_namespace:
   case tok::kw_new:
+  case tok::kw__placement_new:
   case tok::kw_operator:
   case tok::kw_private:
   case tok::kw_protected:
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index 28decc4fc43f9b8..623e3eb4ed70316 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -1445,7 +1445,8 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,
   case tok::coloncolon: {    // ::foo::bar
     const Token &Next = NextToken();
     if (Next.isOneOf(tok::kw_new,       // ::new
-                     tok::kw_delete))   // ::delete
+                     tok::kw_delete,    // ::delete
+                     tok::kw__placement_new)) // ::_placement_new
       return TPResult::False;
     [[fallthrough]];
   }
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 023411c7edc946b..b8e606b3a45b965 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -1875,7 +1875,7 @@ static bool doesUsualArrayDeleteWantSize(Sema &S, SourceLocation loc,
 /// \param Initializer The initializing expression or initializer-list, or null
 ///   if there is none.
 ExprResult
-Sema::ActOnCXXNew(SourceLocation StartLoc, bool UseGlobal,
+Sema::ActOnCXXNew(SourceLocation StartLoc, bool UseGlobal, bool IsPlacementNewExpr,
                   SourceLocation PlacementLParen, MultiExprArg PlacementArgs,
                   SourceLocation PlacementRParen, SourceRange TypeIdParens,
                   Declarator &D, Expr *Initializer) {
@@ -1940,7 +1940,7 @@ Sema::ActOnCXXNew(SourceLocation StartLoc, bool UseGlobal,
   if (ParenListExpr *List = dyn_cast_or_null<ParenListExpr>(Initializer))
     DirectInitRange = List->getSourceRange();
 
-  return BuildCXXNew(SourceRange(StartLoc, D.getEndLoc()), UseGlobal,
+  return BuildCXXNew(SourceRange(StartLoc, D.getEndLoc()), UseGlobal, IsPlacementNewExpr,
                      PlacementLParen, PlacementArgs, PlacementRParen,
                      TypeIdParens, AllocType, TInfo, ArraySize, DirectInitRange,
                      Initializer);
@@ -1997,7 +1997,7 @@ void Sema::diagnoseUnavailableAlignedAllocation(const FunctionDecl &FD,
   }
 }
 
-ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal,
+ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, bool IsPlacementNewExpr,
                              SourceLocation PlacementLParen,
                              MultiExprArg PlacementArgs,
                              SourceLocation PlacementRParen,
@@ -2286,9 +2286,10 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal,
   bool PassAlignment = getLangOpts().AlignedAllocation &&
                        Alignment > NewAlignment;
 
+  bool HaveDependentPlacementTypes = AllocType->isDependentType() || Expr::hasAnyTypeDependentArguments(PlacementArgs);
+
   AllocationFunctionScope Scope = UseGlobal ? AFS_Global : AFS_Both;
-  if (!AllocType->isDependentType() &&
-      !Expr::hasAnyTypeDependentArguments(PlacementArgs) &&
+  if (!HaveDependentPlacementTypes && !IsPlacementNewExpr &&
       FindAllocationFunctions(
           StartLoc, SourceRange(PlacementLParen, PlacementRParen), Scope, Scope,
           AllocType, ArraySize.has_value(), PassAlignment, PlacementArgs,
@@ -2298,15 +2299,29 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal,
   // If this is an array allocation, compute whether the usual array
   // deallocation function for the type has a size_t parameter.
   bool UsualArrayDeleteWantsSize = false;
-  if (ArraySize && !AllocType->isDependentType())
+  if (!IsPlacementNewExpr && ArraySize && !AllocType->isDependentType())
     UsualArrayDeleteWantsSize =
         doesUsualArrayDeleteWantSize(*this, StartLoc, AllocType);
 
-  SmallVector<Expr *, 8> AllPlaceArgs;
-  if (OperatorNew) {
+  if (IsPlacementNewExpr && !HaveDependentPlacementTypes) {
+    assert(PlacementArgs.size() == 1);
+    assert(UseGlobal);
+    QualType VoidPtr = Context.getPointerType(Context.VoidTy);
+
+    InitializedEntity Entity = InitializedEntity::InitializeParameter(Context, VoidPtr, false);
+    ExprResult ArgE = PerformCopyInitialization(Entity, SourceLocation(), PlacementArgs[0], false, false);
+    if (ArgE.isInvalid())
+        return ExprError();
+
+    Expr *Arg = ArgE.getAs<Expr>();
+    CheckArrayAccess(Arg);
+
+    PlacementArgs[0] = Arg;
+  } else if (OperatorNew) {
     auto *Proto = OperatorNew->getType()->castAs<FunctionProtoType>();
     VariadicCallType CallType = Proto->isVariadic() ? VariadicFunction
                                                     : VariadicDoesNotApply;
+    SmallVector<Expr *, 8> AllPlaceArgs;
 
     // We've already converted the placement args, just fill in any default
     // arguments. Skip the first parameter because we don't have a corresponding
@@ -2471,21 +2486,28 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal,
 
   // Mark the new and delete operators as referenced.
   if (OperatorNew) {
+    assert(!IsPlacementNewExpr);
     if (DiagnoseUseOfDecl(OperatorNew, StartLoc))
       return ExprError();
     MarkFunctionReferenced(StartLoc, OperatorNew);
   }
   if (OperatorDelete) {
+    assert(!IsPlacementNewExpr);
     if (DiagnoseUseOfDecl(OperatorDelete, StartLoc))
       return ExprError();
     MarkFunctionReferenced(StartLoc, OperatorDelete);
   }
 
-  return CXXNewExpr::Create(Context, UseGlobal, OperatorNew, OperatorDelete,
-                            PassAlignment, UsualArrayDeleteWantsSize,
-                            PlacementArgs, TypeIdParens, ArraySize, InitStyle,
-                            Initializer, ResultType, AllocTypeInfo, Range,
-                            DirectInitRange);
+  if (IsPlacementNewExpr)
+    return CXXNewExpr::CreatePlacementNew(Context, PlacementArgs[0], TypeIdParens, ArraySize,
+                                          InitStyle, Initializer, ResultType, AllocTypeInfo,
+                                          Range, DirectInitRange);
+  else
+    return CXXNewExpr::Create(Context, UseGlobal, OperatorNew, OperatorDelete,
+                              PassAlignment, UsualArrayDeleteWantsSize,
+                              PlacementArgs, TypeIdParens, ArraySize, InitStyle,
+                              Initializer, ResultType, AllocTypeInfo, Range,
+                              DirectInitRange);
 }
 
 /// Checks that a type is suitable as the allocated type
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index e24f710fdedd4e2..5fc64fa50dd431b 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -3343,7 +3343,7 @@ class TreeTransform {
   ///
   /// By default, performs semantic analysis to build the new expression.
   /// Subclasses may override this routine to provide different behavior.
-  ExprResult RebuildCXXNewExpr(SourceLocation StartLoc, bool UseGlobal,
+  ExprResult RebuildCXXNewExpr(SourceLocation StartLoc, bool UseGlobal, bool IsPlacementNewExpr,
                                SourceLocation PlacementLParen,
                                MultiExprArg PlacementArgs,
                                SourceLocation PlacementRParen,
@@ -3351,7 +3351,7 @@ class TreeTransform {
                                TypeSourceInfo *AllocatedTypeInfo,
                                std::optional<Expr *> ArraySize,
                                SourceRange DirectInitRange, Expr *Initializer) {
-    return getSema().BuildCXXNew(StartLoc, UseGlobal,
+    return getSema().BuildCXXNew(StartLoc, UseGlobal, IsPlacementNewExpr,
                                  PlacementLParen,
                                  PlacementArgs,
                                  PlacementRParen,
@@ -12539,7 +12539,7 @@ TreeTransform<Derived>::TransformCXXNewExpr(CXXNewExpr *E) {
   }
 
   return getDerived().RebuildCXXNewExpr(
-      E->getBeginLoc(), E->isGlobalNew(),
+      E->getBeginLoc(), E->isGlobalNew(), E->isPlacementNewExpr(),
       /*FIXME:*/ E->getBeginLoc(), PlacementArgs,
       /*FIXME:*/ E->getBeginLoc(), E->getTypeIdParens(), AllocType,
       AllocTypeInfo, ArraySize, E->getDirectInitRange(), NewInit.get());
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index d7d0c0e5bb21b47..7f55e308fa1bb08 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1874,6 +1874,8 @@ void ASTStmtReader::VisitCXXNewExpr(CXXNewExpr *E) {
   E->CXXNewExprBits.UsualArrayDeleteWantsSize = Record.readInt();
   E->CXXNewExprBits.StoredInitializationStyle = Record.readInt();
 
+  E->CXXNewExprBits.IsPlacementNewExpr = Record.readInt();
+
   assert((IsArray == E->isArray()) && "Wrong IsArray!");
   assert((HasInit == E->hasInitializer()) && "Wrong HasInit!");
   assert((NumPlacementArgs == E->getNumPlacementArgs()) &&
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 59be6828fafabf6..2fb5f40e6f448ac 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1799,6 +1799,8 @@ void ASTStmtWriter::VisitCXXNewExpr(CXXNewExpr *E) {
   Record.push_back(E->doesUsualArrayDeleteWantSize());
   Record.push_back(E->CXXNewExprBits.StoredInitializationStyle);
 
+  Record.push_back(E->isPlacementNewExpr());
+
   Record.AddDeclRef(E->getOperatorNew());
   Record.AddDeclRef(E->getOperatorDelete());
   Record.AddTypeSourceInfo(E->getAllocatedTypeSourceInfo());
diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
index 99e11a15c08dc2c..e1bdcae0377e7ef 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
@@ -295,7 +295,7 @@ bool PlacementNewChecker::checkPlaceIsAlignedProperly(const CXXNewExpr *NE,
 void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE,
                                        CheckerContext &C) const {
   // Check only the default placement new.
-  if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator())
+  if (!NE->isReservedPlacementNew())
     return;
 
   if (NE->getNumPlacementArgs() == 0)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index 504fd7f05e0f99b..339b26d44cc8ad0 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -1004,8 +1004,9 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
   SVal symVal = UnknownVal();
   FunctionDecl *FD = CNE->getOperatorNew();
 
-  bool IsStandardGlobalOpNewFunction =
-      FD->isReplaceableGlobalAllocationFunction();
+  bool IsStandardGlobalOpNewFunction = false;
+  if (FD)
+    IsStandardGlobalOpNewFunction = FD->isReplaceableGlobalAllocationFunction();
 
   ProgramStateRef State = Pred->getState();
 
@@ -1046,7 +1047,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
     // where new can return NULL. If we end up supporting that option, we can
     // consider adding a check for it here.
     // C++11 [basic.stc.dynamic.allocation]p3.
-    if (const auto *ProtoType = FD->getType()->getAs<FunctionProtoType>())
+    if (const auto *ProtoType = FD ? FD->getType()->getAs<FunctionProtoType>() : nullptr)
       if (!ProtoType->isNothrow())
         if (auto dSymVal = symVal.getAs<DefinedOrUnknownSVal>())
           State = State->assume(*dSymVal, true);
@@ -1099,7 +1100,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
   // CXXNewExpr, we need to make sure that the constructed object is not
   // immediately invalidated here. (The placement call should happen before
   // the constructor call anyway.)
-  if (FD->isReservedGlobalPlacementOperator()) {
+  if (CNE->isReservedPlacementNew()) {
     // Non-array placement new should always return the placement location.
     SVal PlacementLoc = State->getSVal(CNE->getPlacementArg(0), LCtx);
     Result = svalBuilder.evalCast(PlacementLoc, CNE->getType(),
diff --git a/clang/test/AST/ast-dump-expr.cpp b/clang/test/AST/ast-dump-expr.cpp
index 69e65e22d61d0d0..7fddd913786cc1e 100644
--- a/clang/test/AST/ast-dump-expr.cpp
+++ b/clang/test/AST/ast-dump-expr.cpp
@@ -155,6 +155,12 @@ void UnaryExpressions(int *p) {
   // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int *' <LValueToRValue>
   // CHECK-NEXT: DeclRefExpr 0x{{[^ ]*}} <col:8> 'int *' lvalue ParmVar 0x{{[^ ]*}} 'p' 'int *'
 
+  ::_placement_new (p) int;
+  // CHECK: CXXNewExpr 0x{{[^ ]*}} <line:[[@LINE-1]]:3, col:24> 'int *' global builtin placement-new expression
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void *' <BitCast>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int *' <LValueToRValue>
+  // CHECK-NEXT: DeclRefExpr 0x{{[^ ]*}} <col:21> 'int *' lvalue ParmVar 0x{{[^ ]*}} 'p' 'int *'
+
   new (p) int{12};
   // CHECK: CXXNewExpr 0x{{[^ ]*}} <line:[[@LINE-1]]:3, col:17> 'int *' Function 0x{{[^ ]*}} 'operator new' 'void *(std::size_t, void *)'
   // CHECK-NEXT: InitListExpr 0x{{[^ ]*}} <col:14, col:17> 'int'
@@ -163,6 +169,14 @@ void UnaryExpressions(int *p) {
   // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int *' <LValueToRValue>
   // CHECK-NEXT: DeclRefExpr 0x{{[^ ]*}} <col:8> 'int *' lvalue ParmVar 0x{{[^ ]*}} 'p' 'int *'
 
+  ::_placement_new (p) int{12};
+  // CHECK: CXXNewExpr 0x{{[^ ]*}} <line:[[@LINE-1]]:3, col:30> 'int *' global builtin placement-new expression
+  // CHECK-NEXT: InitListExpr 0x{{[^ ]*}} <col:27, col:30> 'int'
+  // CHECK-NEXT: IntegerLiteral 0x{{[^ ]*}} <col:28> 'int' 12
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void *' <BitCast>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int *' <LValueToRValue>
+  // CHECK-NEXT: DeclRefExpr 0x{{[^ ]*}} <col:21> 'int *' lvalue ParmVar 0x{{[^ ]*}} 'p' 'int *'
+
   ::delete p;
   // CHECK: CXXDeleteExpr 0x{{[^ ]*}} <line:[[@LINE-1]]:3, col:12> 'void' global Function 0x{{[^ ]*}} 'operator delete' 'void (void *) noexcept'
   // CHECK-NEXT: ImplicitCastExpr
diff --git a/clang/test/CodeGenCXX/new.cpp b/clang/test/CodeGenCXX/new.cpp
index e278d9acfe9ee23..7cd3232a295e0a1 100644
--- a/clang/test/CodeGenCXX/new.cpp
+++ b/clang/test/CodeGenCXX/new.cpp
@@ -219,6 +219,15 @@ namespace test15 {
     new (p, true) A();
   }
 
+  // CHECK-LABEL:    define{{.*}} void @_ZN6test156test0cEPv(
+  // CHECK:      [[P:%.*]] = load ptr, ptr
+  // CHECK-NOT:  icmp eq ptr [[P]], null
+  // CHECK-NOT:  br i1
+  // CHECK-NEXT: call void @_ZN6test151AC1Ev(ptr {{[^,]*}} [[P]])
+  void test0c(void *p) {
+    ::_placement_new (p) A();
+  }
+
   // CHECK-LABEL:    define{{.*}} void @_ZN6test156test1aEPv(
   // CHECK:      [[P:%.*]] = load ptr, ptr
   // CHECK-NOT:  icmp eq ptr [[P]], null
@@ -251,9 +260,24 @@ namespace test15 {
     new (p, true) A[5];
   }
 
+  // CHECK-LABEL:    define{{.*}} void @_ZN6test156test1cEPv(
+  // CHECK:      [[P:%.*]] = load ptr, ptr
+  // CHECK-NOT:  icmp eq ptr [[P]], null
+  // CHECK-NOT:  br i1
+  // CHECK-NEXT: [[END:%.*]] = getelementptr inbounds [[A:.*]], ptr [[P]], i64 5
+  // CHECK-NEXT: br label
+  // CHECK:      [[CUR:%.*]] = phi ptr [ [[P]], {{%.*}} ], [ [[NEXT:%.*]], {{%.*}} ]
+  // CHECK-NEXT: call void @_ZN6test151AC1Ev(ptr {{[^,]*}} [[CUR]])
+  // CHECK-NEXT: [[NEXT]] = getelementptr inbounds [[A]], ptr [[CUR]], i64 1
+  // CHECK-NEXT: [[DONE:%.*]] = icmp eq ptr [[NEXT]], [[END]]
+  // CHECK-NEXT: br i1 [[DONE]]
+  void test1c(void *p) {
+    ::_placement_new (p) A[5];
+  }
+
   // TODO: it's okay if all these size calculations get dropped.
   // FIXME: maybe we should try to throw on overflow?
-  // CHECK-LABEL:    define{{.*}} void @_ZN6test155test2EPvi(
+  // CHECK-LABEL:    define{{.*}} void @_ZN6test156test2aEPvi(
   // CHECK:      [[N:%.*]] = load i32, ptr
   // CHECK-NEXT: [[T0:%.*]] = sext i32 [[N]] to i64
   // CHECK-NEXT: [[P:%.*]] = load ptr, ptr
@@ -263,9 +287,25 @@ namespace test15 {
   // CHECK-NEXT: br label
   // CHECK:      [[CUR:%.*]] = phi ptr [ [[P]],
   // CHECK-NEXT: call void @_ZN6test151AC1Ev(ptr {{[^,]*}} [[CUR]])
-  void test2(void *p, int n) {
+  void test2a(void *p, int n) {
     new (p) A[n];
   }
+
+  // TODO: it's okay if all these size calculations get dropped.
+  // FIXME: maybe we should try to throw on overflow?
+  // CHECK-LABEL:    define{{.*}} void @_ZN6test156test2bEPvi(
+  // CHECK:      [[N:%.*]] = load i32, ptr
+  // CHECK-NEXT: [[T0:%.*]] = sext i32 [[N]] to i64
+  // CHECK-NEXT: [[P:%.*]] = load ptr, ptr
+  // CHECK-NEXT: [[ISEMPTY:%.*]] = icmp eq i64 [[T0]], 0
+  // CHECK-NEXT: br i1 [[ISEMPTY]],
+  // CHECK:      [[END:%.*]] = getelementptr inbounds [[A]], ptr [[P]], i64 [[T0]]
+  // CHECK-NEXT: br label
+  // CHECK:      [[CUR:%.*]] = phi ptr [ [[P]],
+  // CHECK-NEXT: call void @_ZN6test151AC1Ev(ptr {{[^,]*}} [[CUR]])
+  void test2b(void *p, int n) {
+    ::_placement_new (p) A[n];
+  }
 }
 
 namespace PR10197 {
diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp
index 0270e42b7389fec..20871c6ebc9bdf0 100644
--- a/clang/test/SemaCXX/new-delete.cpp
+++ b/clang/test/SemaCXX/new-delete.cpp
@@ -58,6 +58,9 @@ void good_news()
   U *pu = new (ps) U;
   V *pv = new (ps) V;
 
+  U *pu2 = ::_placement_new ((S*)0) U;
+  int *pi2 = ::_placement_new ((void*)0) int;
+
   pi = new (S(1.0f, 2)) int;
 
   (void)new int[true];
@@ -87,6 +90,10 @@ void bad_news(int *ip)
   (void)new; // expected-error {{expected a type}}
   (void)new 4; // expected-error {{expected a type}}
   (void)new () int; // expected-error {{expected expression}}
+  (void)::_placement_new () int; // expected-error {{expected expression}}
+  (void)_placement_new (ip) int; // expected-error {{expected expression}}
+  (void)::_placement_new int; // expected-error {{expected '(' after '::_placement_new'}}
+  (void)::_placement_new (ip, ip) int; // expected-error {{expected only one argument in placement params}}
   (void)new int[1.1];
 #if __cplusplus <= 199711L
   // expected-error at -2 {{array size expression must have integral or enumeration type, not 'double'}}



More information about the cfe-commits mailing list