-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[Clang] Implement P2747 constexpr placement new #104586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-clang Author: cor3ntin (cor3ntin) ChangesIn C++26 and as an extension in C++20. Full diff: https://github.com/llvm/llvm-project/pull/104586.diff 12 Files Affected:
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 114e742f3561b7..296bb85f5e72b2 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1506,6 +1506,7 @@ Attributes on Structured Bindings __cpp_structured_bindings C+
Pack Indexing __cpp_pack_indexing C++26 C++03
``= delete ("should have a reason");`` __cpp_deleted_function C++26 C++03
Variadic Friends __cpp_variadic_friend C++26 C++03
+``constexpr`` placement new __cpp_constexpr C++26 C++20
-------------------------------------------- -------------------------------- ------------- -------------
Designated initializers (N494) C99 C89
Array & element qualification (N2607) C23 C89
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ffdd063ec99037..22864062fba7fd 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -73,7 +73,7 @@ C++ Specific Potentially Breaking Changes
template <> // error: extraneous template head
template <typename T>
void f();
-
+
ABI Changes in This Version
---------------------------
@@ -131,6 +131,9 @@ C++2c Feature Support
- Implemented `P2893R3 Variadic Friends <https://wg21.link/P2893>`_
+- Implemented `P2747R2 constexpr placement new <https://wg21.link/P2747R2>`_.
+
+
Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index f317c5ac44f32b..569d2cc20a526c 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -335,7 +335,7 @@ def note_constexpr_new_non_replaceable : Note<
def note_constexpr_new_placement : Note<
"this placement new expression is not yet supported in constant expressions">;
def note_constexpr_placement_new_wrong_type : Note<
- "placement new would change type of storage from %0 to %1">;
+ "placement new would change type of storage from %0 to %1">;
def note_constexpr_new_negative : Note<
"cannot allocate array; evaluated array bound %0 is negative">;
def note_constexpr_new_too_large : Note<
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 09edbb6641650a..50cffa95fc01ce 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -44,18 +44,23 @@
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/CurrentSourceLocExprScope.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/OSLog.h"
#include "clang/AST/OptionalDiagnostic.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/StmtVisitor.h"
+#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/LangStandard.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/APFixedPoint.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/SaveAndRestore.h"
#include "llvm/Support/SipHash.h"
@@ -6691,7 +6696,7 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange,
if (Size && Size > Value.getArrayInitializedElts())
expandArray(Value, Value.getArraySize() - 1);
- for (; Size != 0; --Size) {
+ for (Size = Value.getArraySize(); Size != 0; --Size) {
APValue &Elem = Value.getArrayInitializedElt(Size - 1);
if (!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, -1) ||
!HandleDestructionImpl(Info, CallRange, ElemLV, Elem, ElemT))
@@ -10003,23 +10008,14 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
return false;
FunctionDecl *OperatorNew = E->getOperatorNew();
+ QualType AllocType = E->getAllocatedType();
+ QualType TargetType = AllocType;
bool IsNothrow = false;
bool IsPlacement = false;
- if (OperatorNew->isReservedGlobalPlacementOperator() &&
- Info.CurrentCall->isStdFunction() && !E->isArray()) {
- // FIXME Support array placement new.
- assert(E->getNumPlacementArgs() == 1);
- if (!EvaluatePointer(E->getPlacementArg(0), Result, Info))
- return false;
- if (Result.Designator.Invalid)
- return false;
- IsPlacement = true;
- } else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
- Info.FFDiag(E, diag::note_constexpr_new_non_replaceable)
- << isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
- return false;
- } else if (E->getNumPlacementArgs()) {
+
+ if (E->getNumPlacementArgs() && E->getNumPlacementArgs() == 1 &&
+ E->getPlacementArg(0)->getType()->isNothrowT()) {
// The only new-placement list we support is of the form (std::nothrow).
//
// FIXME: There is no restriction on this, but it's not clear that any
@@ -10030,14 +10026,25 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
// (which should presumably be valid only if N is a multiple of
// alignof(int), and in any case can't be deallocated unless N is
// alignof(X) and X has new-extended alignment).
- if (E->getNumPlacementArgs() != 1 ||
- !E->getPlacementArg(0)->getType()->isNothrowT())
- return Error(E, diag::note_constexpr_new_placement);
-
LValue Nothrow;
if (!EvaluateLValue(E->getPlacementArg(0), Nothrow, Info))
return false;
IsNothrow = true;
+ } else if (OperatorNew->isReservedGlobalPlacementOperator()) {
+ if (!EvaluatePointer(E->getPlacementArg(0), Result, Info))
+ return false;
+ if (Result.Designator.Invalid)
+ return false;
+ /// if(!lifetimeStartedInEvaluation(Info, Result.getLValueBase()))
+ // return false;
+ TargetType = E->getPlacementArg(0)->getType();
+ IsPlacement = true;
+ } else if (E->getNumPlacementArgs()) {
+ return Error(E, diag::note_constexpr_new_placement);
+ } else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
+ Info.FFDiag(E, diag::note_constexpr_new_non_replaceable)
+ << isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
+ return false;
}
const Expr *Init = E->getInitializer();
@@ -10045,7 +10052,6 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
const CXXConstructExpr *ResizedArrayCCE = nullptr;
bool ValueInit = false;
- QualType AllocType = E->getAllocatedType();
if (std::optional<const Expr *> ArraySize = E->getArraySize()) {
const Expr *Stripped = *ArraySize;
for (; auto *ICE = dyn_cast<ImplicitCastExpr>(Stripped);
@@ -10139,9 +10145,17 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
bool found(APValue &Subobj, QualType SubobjType) {
// FIXME: Reject the cases where [basic.life]p8 would not permit the
// old name of the object to be used to name the new object.
- if (!Info.Ctx.hasSameUnqualifiedType(SubobjType, AllocType)) {
- Info.FFDiag(E, diag::note_constexpr_placement_new_wrong_type) <<
- SubobjType << AllocType;
+ unsigned SubobjectSize = 1;
+ unsigned AllocSize = 1;
+ if (auto *CAT = dyn_cast<ConstantArrayType>(AllocType))
+ AllocSize = CAT->getZExtSize();
+ if (auto *CAT = dyn_cast<ConstantArrayType>(SubobjType))
+ SubobjectSize = CAT->getZExtSize();
+ if (SubobjectSize < AllocSize ||
+ !Info.Ctx.hasSimilarType(Info.Ctx.getBaseElementType(SubobjType),
+ Info.Ctx.getBaseElementType(AllocType))) {
+ Info.FFDiag(E, diag::note_constexpr_placement_new_wrong_type)
+ << SubobjType << AllocType;
return false;
}
Value = &Subobj;
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 4f2856dd2247f8..61260a3379828d 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -660,7 +660,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
Builder.defineMacro("__cpp_unicode_literals", "200710L");
Builder.defineMacro("__cpp_user_defined_literals", "200809L");
Builder.defineMacro("__cpp_lambdas", "200907L");
- Builder.defineMacro("__cpp_constexpr", LangOpts.CPlusPlus26 ? "202306L"
+ Builder.defineMacro("__cpp_constexpr", LangOpts.CPlusPlus26 ? "202406L"
: LangOpts.CPlusPlus23 ? "202211L"
: LangOpts.CPlusPlus20 ? "201907L"
: LangOpts.CPlusPlus17 ? "201603L"
diff --git a/clang/test/AST/Interp/new-delete.cpp b/clang/test/AST/Interp/new-delete.cpp
index 6bb30bc19f110c..0c823e7000f853 100644
--- a/clang/test/AST/Interp/new-delete.cpp
+++ b/clang/test/AST/Interp/new-delete.cpp
@@ -245,7 +245,7 @@ namespace std {
namespace PlacementNew {
constexpr int foo() { // both-error {{never produces a constant expression}}
char c[sizeof(int)];
- new (c) int{12}; // ref-note {{call to placement 'operator new'}} \
+ new (c) int{12}; // ref-note {{placement new would change type of storage from 'char' to 'int'}} \
// expected-note {{subexpression not valid in a constant expression}}
return 0;
}
@@ -309,7 +309,7 @@ namespace placement_new_delete {
constexpr bool bad(int which) {
switch (which) {
case 0:
- delete new (placement_new_arg{}) int; // ref-note {{call to placement 'operator new'}} \
+ delete new (placement_new_arg{}) int; // ref-note {{this placement new expression is not yet supported in constant expression}} \
// expected-note {{subexpression not valid in a constant expression}}
break;
diff --git a/clang/test/CXX/drs/cwg29xx.cpp b/clang/test/CXX/drs/cwg29xx.cpp
index 8cac9f283980b6..7e04aa0c3a4942 100644
--- a/clang/test/CXX/drs/cwg29xx.cpp
+++ b/clang/test/CXX/drs/cwg29xx.cpp
@@ -23,3 +23,29 @@ struct S {
friend class C<Ts>::Nested...; // expected-error {{friend declaration expands pack 'Ts' that is declared it its own template parameter list}}
};
} // namespace cwg2917
+
+#if __cplusplus >= 202002
+
+namespace std {
+ using size_t = decltype(sizeof(0));
+};
+void *operator new(std::size_t, void *p) { return p; }
+void* operator new[] (std::size_t, void* p) {return p;}
+
+
+namespace cwg2922 { // cwg2922: 20 open 2024-07-10
+union U { int a, b; };
+constexpr U nondeterministic(bool i) {
+ if(i) {
+ U u;
+ new (&u) int();
+ // expected-note@-1 {{placement new would change type of storage from 'U' to 'int'}}
+ return u;
+ }
+ return {};
+}
+constexpr U _ = nondeterministic(true);
+// expected-error@-1 {{constexpr variable '_' must be initialized by a constant expression}} \
+// expected-note@-1 {{in call to 'nondeterministic(true)'}}
+}
+#endif
diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp
index 1c51013ca06f77..4a06d29ae9dbc6 100644
--- a/clang/test/Lexer/cxx-features.cpp
+++ b/clang/test/Lexer/cxx-features.cpp
@@ -317,7 +317,7 @@
#error "wrong value for __cpp_lambdas"
#endif
-#if check(constexpr, 0, 200704, 201304, 201603, 201907, 202211, 202306)
+#if check(constexpr, 0, 200704, 201304, 201603, 201907, 202211, 202406L)
#error "wrong value for __cpp_constexpr"
#endif
diff --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
index e4d97dcb73562d..b8cbf723a96a76 100644
--- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
@@ -994,7 +994,7 @@ namespace placement_new_delete {
constexpr bool bad(int which) {
switch (which) {
case 0:
- delete new (placement_new_arg{}) int; // expected-note {{call to placement 'operator new'}}
+ delete new (placement_new_arg{}) int; // expected-note {{this placement new expression is not yet supported in constant expressions}}
break;
case 1:
diff --git a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp
index 357dc67bd5ad22..b02c90b88d1ac6 100644
--- a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp
+++ b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp
@@ -90,9 +90,10 @@ constexpr int no_deallocate_nonalloc = (std::allocator<int>().deallocate((int*)&
// expected-note@-2 {{declared here}}
void *operator new(std::size_t, void *p) { return p; }
-constexpr bool no_placement_new_in_user_code() { // expected-error {{never produces a constant expression}}
+void* operator new[] (std::size_t, void* p) {return p;}
+constexpr bool no_placement_new_in_user_code() {
int a;
- new (&a) int(42); // expected-note {{call to placement 'operator new'}}
+ new (&a) int(42);
return a == 42;
}
@@ -239,3 +240,80 @@ void f() {
}
}
+
+namespace placement_new {
+
+template <typename T, typename U>
+constexpr void f(T* t, U u) {
+ new (t) U(u);
+}
+
+consteval int ok() {
+ int i;
+ new (&i) int(0);
+ new (&i) int[1]{1};
+ new (static_cast<void*>(&i)) int(0);
+ return 0;
+}
+
+consteval int conversion() { // expected-error {{consteval function never produces a constant expression}}
+ int i;
+ new (static_cast<void*>(&i)) float(0);
+ // expected-note@-1 {{placement new would change type of storage from 'int' to 'float'}}
+ return 0;
+}
+
+consteval int indeterminate() {
+ int * indeterminate;
+ new (indeterminate) int(0);
+ // expected-note@-1 {{read of uninitialized object is not allowed in a constant expression}}
+ return 0;
+}
+
+consteval int array1() {
+ int i[2];
+ new (&i) int[]{1,2};
+ new (&i) int[]{1};
+ new (&i) int(0);
+ new (static_cast<void*>(&i)) int[]{1,2};
+ new (static_cast<void*>(&i)) int[]{1};
+ return 0;
+}
+
+consteval int array2() { // expected-error {{consteval function never produces a constant expression}}
+ int i[1];
+ new (&i) int[2];
+ //expected-note@-1 {{placement new would change type of storage from 'int[1]' to 'int[2]'}}
+ return 0;
+}
+
+struct S{
+ int* i;
+ constexpr S() : i(new int(42)) {} // expected-note {{allocation performed here was not deallocated}}
+ constexpr ~S() {delete i;}
+};
+
+consteval void alloc() {
+ S* s = new S();
+ s->~S();
+ new (s) S();
+ delete s;
+}
+
+
+consteval void alloc_err() {
+ S* s = new S();
+ new (s) S();
+ delete s;
+}
+
+
+
+int a = ok();
+int c = indeterminate(); // expected-error {{call to consteval function 'placement_new::indeterminate' is not a constant expression}} \
+ // expected-note {{in call to 'indeterminate()'}}
+int d = array1();
+int alloc1 = (alloc(), 0);
+int alloc2 = (alloc_err(), 0); // expected-error {{call to consteval function 'placement_new::alloc_err' is not a constant expression}}
+
+}
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index c901e1ce0e15b3..de84d94c20a152 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -17348,7 +17348,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2922.html">2922</a></td>
<td>open</td>
<td>constexpr placement-new is too permissive</td>
- <td align="center">Not resolved</td>
+ <td title="Clang 20 implements 2024-07-10 resolution" align="center">Not Resolved*</td>
</tr></table>
</div>
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index faee8b578b6242..58bbb12a76dd75 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -208,7 +208,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td><tt>constexpr</tt> placement new</td>
<td><a href="https://wg21.link/P2747R2">P2747R2</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 20</td>
</tr>
<tr>
<td>Deleting a Pointer to an Incomplete Type Should be Ill-formed</td>
|
67028aa
to
36eb12a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DR testing side looks good
} | ||
constexpr U _ = nondeterministic(true); | ||
// expected-error@-1 {{constexpr variable '_' must be initialized by a constant expression}} \ | ||
// expected-note@-1 {{in call to 'nondeterministic(true)'}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// expected-note@-1 {{in call to 'nondeterministic(true)'}} | |
// expected-note@-1 {{in call to 'nondeterministic(true)'}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still outstanding, albeit minor.
clang/lib/AST/ExprConstant.cpp
Outdated
return false; | ||
if (Result.Designator.Invalid) | ||
return false; | ||
/// if(!lifetimeStartedInEvaluation(Info, Result.getLValueBase())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debugging leftovers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for working on this! Given that it's an extension in C++20 mode, we're missing diagnostics and test coverage for issuing extension/precompat diagnostics.
|
||
struct S{ | ||
int* i; | ||
constexpr S() : i(new int(42)) {} // expected-note {{allocation performed here was not deallocated}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What diagnostic is this note associated with?
// expected-note {{in call to 'indeterminate()'}} | ||
int d = array1(); | ||
int alloc1 = (alloc(), 0); | ||
int alloc2 = (alloc_err(), 0); // expected-error {{call to consteval function 'placement_new::alloc_err' is not a constant expression}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other test cases to consider:
constexpr int *hmm() {
return new int[2];
}
constexpr bool yay() {
int *ptr = new (hmm()) int[2]{ 1, 2 };
bool ret = ptr[0] == 1 && ptr[1] == 2;
delete [] ptr;
return ret;
}
static_assert(yay());
constexpr bool blah() {
int *ptr = new (hmm()) int[3]{ 1, 2, 3 }; // you're a bad person who should feel bad
bool ret = ptr[0] == 1 && ptr[1] == 2 && ptr[2] == 3;
delete [] ptr;
return ret;
}
static_assert(blah()); // Not a constant expression
int *oh_no() {
int *evil;
return evil;
}
constexpr bool blech() {
int *ptr = new (oh_no()) int; // you're a bad person who should feel bad
return true;
}
static_assert(blech()); // Not a constant expression
No longer an extension after our last chat - i just forgot to update the description |
In C++26 and as an extension in C++20
ee36efe
to
5c4225b
Compare
@AaronBallman ping :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
The implementation follows the resolution of CWG2922
The implementation follows the resolution of CWG2922