Author: cor3ntin Date: 2024-08-23T17:24:08+02:00 New Revision: 6e78aef646c22b7087cbf7939c8016f4f59614a1
URL: https://github.com/llvm/llvm-project/commit/6e78aef646c22b7087cbf7939c8016f4f59614a1 DIFF: https://github.com/llvm/llvm-project/commit/6e78aef646c22b7087cbf7939c8016f4f59614a1.diff LOG: [Clang] Implement P2747 constexpr placement new (#104586) The implementation follows the resolution of CWG2922 Added: clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp Modified: clang/docs/ReleaseNotes.rst clang/include/clang/Basic/DiagnosticASTKinds.td clang/lib/AST/ExprConstant.cpp clang/lib/Frontend/InitPreprocessor.cpp clang/test/AST/ByteCode/new-delete.cpp clang/test/CXX/drs/cwg29xx.cpp clang/test/Lexer/cxx-features.cpp clang/test/SemaCXX/constant-expression-cxx2a.cpp clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp clang/www/cxx_dr_status.html clang/www/cxx_status.html Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index fb3c2f699964c3..baedc3cd6f03fc 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -118,6 +118,8 @@ C++2c Feature Support - Implemented `P2893R3 Variadic Friends <https://wg21.link/P2893>`_ +- Implemented `P2747R2 constexpr placement new <https://wg21.link/P2747R2>`_. + C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ - Removed the restriction to literal types in constexpr functions in C++23 mode. @@ -125,6 +127,7 @@ C++23 Feature Support C++20 Feature Support ^^^^^^^^^^^^^^^^^^^^^ + Resolutions to C++ Defect Reports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index f317c5ac44f32b..45ad84831589b1 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -333,7 +333,8 @@ def note_constexpr_new : Note< def note_constexpr_new_non_replaceable : Note< "call to %select{placement|class-specific}0 %1">; def note_constexpr_new_placement : Note< - "this placement new expression is not yet supported in constant expressions">; + "this placement new expression is not supported in constant expressions " + "%select{|before C++2c}0">; def note_constexpr_placement_new_wrong_type : Note< "placement new would change type of storage from %0 to %1">; def note_constexpr_new_negative : Note< diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 5540f58b526705..826cc5f58bdf51 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6691,7 +6691,9 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange, if (Size && Size > Value.getArrayInitializedElts()) expandArray(Value, Value.getArraySize() - 1); - for (; Size != 0; --Size) { + // The size of the array might have been reduced by + // a placement new. + 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 +10005,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() == 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 +10023,31 @@ 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 (Info.CurrentCall->isStdFunction() || Info.getLangOpts().CPlusPlus26) { + if (!EvaluatePointer(E->getPlacementArg(0), Result, Info)) + return false; + if (Result.Designator.Invalid) + return false; + TargetType = E->getPlacementArg(0)->getType(); + IsPlacement = true; + } else { + Info.FFDiag(E, diag::note_constexpr_new_placement) + << /*C++26 feature*/ 1 << E->getSourceRange(); + return false; + } + } else if (E->getNumPlacementArgs()) { + Info.FFDiag(E, diag::note_constexpr_new_placement) + << /*Unsupported*/ 0 << E->getSourceRange(); + return false; + } 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 +10055,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 +10148,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/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp index 6bb30bc19f110c..a7be4102fd0a05 100644 --- a/clang/test/AST/ByteCode/new-delete.cpp +++ b/clang/test/AST/ByteCode/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 {{this placement new expression is not supported in constant expressions before C++2c}} \ // 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 supported in constant expressions}} \ // expected-note {{subexpression not valid in a constant expression}} break; @@ -328,7 +328,7 @@ namespace placement_new_delete { case 4: // FIXME: This technically follows the standard's rules, but it seems // unreasonable to expect implementations to support this. - delete new (std::align_val_t{64}) Overaligned; // ref-note {{placement new expression is not yet supported}} \ + delete new (std::align_val_t{64}) Overaligned; // ref-note {{this placement new expression is not supported in constant expressions}} \ // 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..2515785f47bf19 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 >= 202400L + +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..36d4d25c48471b 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 supported in constant expressions}} break; case 1: @@ -1012,7 +1012,7 @@ namespace placement_new_delete { case 4: // FIXME: This technically follows the standard's rules, but it seems // unreasonable to expect implementations to support this. - delete new (std::align_val_t{64}) Overaligned; // expected-note {{placement new expression is not yet supported}} + delete new (std::align_val_t{64}) Overaligned; // expected-note {{this placement new expression is not supported in constant expressions}} break; } diff --git a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp index 357dc67bd5ad22..6d9c0b607d8a67 100644 --- a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp +++ b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp @@ -1,6 +1,7 @@ -// RUN: %clang_cc1 -std=c++2a -verify %s -DNEW=__builtin_operator_new -DDELETE=__builtin_operator_delete -// RUN: %clang_cc1 -std=c++2a -verify %s "-DNEW=operator new" "-DDELETE=operator delete" -// RUN: %clang_cc1 -std=c++2a -verify %s "-DNEW=::operator new" "-DDELETE=::operator delete" +// RUN: %clang_cc1 -std=c++2a -verify=expected,cxx20 %s -DNEW=__builtin_operator_new -DDELETE=__builtin_operator_delete +// RUN: %clang_cc1 -std=c++2a -verify=expected,cxx20 %s "-DNEW=operator new" "-DDELETE=operator delete" +// RUN: %clang_cc1 -std=c++2a -verify=expected,cxx20 %s "-DNEW=::operator new" "-DDELETE=::operator delete" +// RUN: %clang_cc1 -std=c++2c -verify=expected,cxx26 %s "-DNEW=::operator new" "-DDELETE=::operator delete" constexpr bool alloc_from_user_code() { void *p = NEW(sizeof(int)); // expected-note {{cannot allocate untyped memory in a constant expression; use 'std::allocator<T>::allocate'}} @@ -90,9 +91,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() { // cxx20-error {{constexpr function never produces a constant expression}} int a; - new (&a) int(42); // expected-note {{call to placement 'operator new'}} + new (&a) int(42); // cxx20-note {{this placement new expression is not supported in constant expressions before C++2c}} return a == 42; } diff --git a/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp b/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp new file mode 100644 index 00000000000000..a29fb981cedbf0 --- /dev/null +++ b/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp @@ -0,0 +1,116 @@ +// RUN: %clang_cc1 -std=c++2c -verify %s + + +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;} + + +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() { + 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() { + 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)) {} // #no-deallocation + 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 b = conversion(); // expected-error {{call to consteval function 'conversion' is not a constant expression}} \ + // expected-note {{in call to 'conversion()'}} +int c = indeterminate(); // expected-error {{call to consteval function 'indeterminate' is not a constant expression}} \ + // expected-note {{in call to 'indeterminate()'}} +int d = array1(); +int e = array2(); // expected-error {{call to consteval function 'array2' is not a constant expression}} \ + // expected-note {{in call to 'array2()'}} +int alloc1 = (alloc(), 0); +int alloc2 = (alloc_err(), 0); // expected-error {{call to consteval function 'alloc_err' is not a constant expression}} + // expected-note@#no-deallocation {{allocation performed here was not deallocated}} + +constexpr int *intptr() { + return new int; +} + +constexpr bool yay() { + int *ptr = new (intptr()) int(42); + bool ret = *ptr == 42; + delete ptr; + return ret; +} +static_assert(yay()); + +constexpr bool blah() { + int *ptr = new (intptr()) int[3]{ 1, 2, 3 }; // expected-note {{placement new would change type of storage from 'int' to 'int[3]'}} + bool ret = ptr[0] == 1 && ptr[1] == 2 && ptr[2] == 3; + delete [] ptr; + return ret; +} +static_assert(blah()); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to 'blah()'}} + +constexpr int *get_indeterminate() { + int *evil; + return evil; // expected-note {{read of uninitialized object is not allowed in a constant expression}} +} + +constexpr bool bleh() { + int *ptr = new (get_indeterminate()) int; // expected-note {{in call to 'get_indeterminate()'}} + return true; +} +static_assert(bleh()); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to 'bleh()'}} diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html index a8d2d813d0f536..395b5d3bff49a6 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> _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits