Skip to content

[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

Merged
merged 7 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,16 @@ 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.

C++20 Feature Support
^^^^^^^^^^^^^^^^^^^^^


Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down
63 changes: 40 additions & 23 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -10030,22 +10023,38 @@ 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();
const InitListExpr *ResizedArrayILE = nullptr;
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);
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Frontend/InitPreprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions clang/test/AST/ByteCode/new-delete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down
26 changes: 26 additions & 0 deletions clang/test/CXX/drs/cwg29xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)'}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// expected-note@-1 {{in call to 'nondeterministic(true)'}}
// expected-note@-1 {{in call to 'nondeterministic(true)'}}

Copy link
Contributor

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.

}
#endif
2 changes: 1 addition & 1 deletion clang/test/Lexer/cxx-features.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions clang/test/SemaCXX/constant-expression-cxx2a.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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;
}

Expand Down
12 changes: 7 additions & 5 deletions clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp
Original file line number Diff line number Diff line change
@@ -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'}}
Expand Down Expand Up @@ -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;
}

Expand Down
116 changes: 116 additions & 0 deletions clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp
Original file line number Diff line number Diff line change
@@ -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()'}}
2 changes: 1 addition & 1 deletion clang/www/cxx_dr_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion clang/www/cxx_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading