[clang] [clang][bytecode] Implement placement-new (PR #107033)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 2 17:59:59 PDT 2024
https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/107033
If we have a placement-new destination already, use that instead of allocating a new one.
Tests are partially based on `test/SemaCXX/cxx2c-constexpr-placement-new.cpp`.
>From 588703b329b186c3e85291abd63c8cf18fcb2296 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 3 Sep 2024 02:52:35 +0200
Subject: [PATCH] [clang][bytecode] Implement placement-new
---
clang/lib/AST/ByteCode/Compiler.cpp | 85 +++++++----
clang/lib/AST/ByteCode/Interp.cpp | 45 ++++++
clang/lib/AST/ByteCode/Interp.h | 10 ++
clang/lib/AST/ByteCode/Opcodes.td | 10 ++
clang/test/AST/ByteCode/placement-new.cpp | 177 ++++++++++++++++++++++
5 files changed, 298 insertions(+), 29 deletions(-)
create mode 100644 clang/test/AST/ByteCode/placement-new.cpp
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 554e23e272e41c..92332fbfdfd4e0 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -2822,10 +2822,11 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
std::optional<PrimType> ElemT = classify(ElementType);
unsigned PlacementArgs = E->getNumPlacementArgs();
bool IsNoThrow = false;
+ const Expr *PlacementDest = nullptr;
// FIXME: Better diagnostic. diag::note_constexpr_new_placement
if (PlacementArgs != 0) {
- // The only new-placement list we support is of the form (std::nothrow).
+ // new-placement list of the form (std::nothrow).
//
// FIXME: There is no restriction on this, but it's not clear that any
// other form makes any sense. We get here for cases such as:
@@ -2835,27 +2836,37 @@ bool Compiler<Emitter>::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 (PlacementArgs != 1 || !E->getPlacementArg(0)->getType()->isNothrowT())
+ if (PlacementArgs == 1) {
+ const Expr *Arg1 = E->getPlacementArg(0);
+ if (Arg1->getType()->isNothrowT()) {
+ if (!this->discard(Arg1))
+ return false;
+ IsNoThrow = true;
+ } else {
+ // If we have a placement-new destination, we'll later use that instead
+ // of allocating.
+ PlacementDest = Arg1;
+ }
+ } else {
return this->emitInvalid(E);
-
- if (!this->discard(E->getPlacementArg(0)))
- return false;
- IsNoThrow = true;
+ }
}
const Descriptor *Desc;
- if (ElemT) {
- if (E->isArray())
- Desc = nullptr; // We're not going to use it in this case.
- else
- Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
- /*IsConst=*/false, /*IsTemporary=*/false,
- /*IsMutable=*/false);
- } else {
- Desc = P.createDescriptor(
- E, ElementType.getTypePtr(),
- E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
- /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
+ if (!PlacementDest) {
+ if (ElemT) {
+ if (E->isArray())
+ Desc = nullptr; // We're not going to use it in this case.
+ else
+ Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
+ /*IsConst=*/false, /*IsTemporary=*/false,
+ /*IsMutable=*/false);
+ } else {
+ Desc = P.createDescriptor(
+ E, ElementType.getTypePtr(),
+ E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
+ /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
+ }
}
if (E->isArray()) {
@@ -2872,26 +2883,42 @@ bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
PrimType SizeT = classifyPrim(Stripped->getType());
- if (!this->visit(Stripped))
- return false;
-
- if (ElemT) {
- // N primitive elements.
- if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E))
+ if (PlacementDest) {
+ if (!this->visit(PlacementDest))
+ return false;
+ if (!this->visit(Stripped))
+ return false;
+ if (!this->emitCheckNewTypeMismatchArray(SizeT, E, E))
return false;
} else {
- // N Composite elements.
- if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E))
+ if (!this->visit(Stripped))
return false;
+
+ if (ElemT) {
+ // N primitive elements.
+ if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E))
+ return false;
+ } else {
+ // N Composite elements.
+ if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E))
+ return false;
+ }
}
if (Init && !this->visitInitializer(Init))
return false;
} else {
- // Allocate just one element.
- if (!this->emitAlloc(Desc, E))
- return false;
+ if (PlacementDest) {
+ if (!this->visit(PlacementDest))
+ return false;
+ if (!this->emitCheckNewTypeMismatch(E, E))
+ return false;
+ } else {
+ // Allocate just one element.
+ if (!this->emitAlloc(Desc, E))
+ return false;
+ }
if (Init) {
if (ElemT) {
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 30ccceb42eb374..8a3cd55b5ad539 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -986,6 +986,51 @@ void diagnoseEnumValue(InterpState &S, CodePtr OpPC, const EnumDecl *ED,
}
}
+bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
+ std::optional<uint64_t> ArraySize) {
+ const Pointer &Ptr = S.Stk.peek<Pointer>();
+
+ if (!CheckStore(S, OpPC, Ptr))
+ return false;
+
+ const auto *NewExpr = cast<CXXNewExpr>(E);
+ QualType StorageType = Ptr.getType();
+
+ if (isa_and_nonnull<CXXNewExpr>(Ptr.getFieldDesc()->asExpr())) {
+ // FIXME: Are there other cases where this is a problem?
+ StorageType = StorageType->getPointeeType();
+ }
+
+ const ASTContext &ASTCtx = S.getASTContext();
+ QualType AllocType;
+ if (ArraySize) {
+ AllocType = ASTCtx.getConstantArrayType(
+ NewExpr->getAllocatedType(),
+ APInt(64, static_cast<uint64_t>(*ArraySize), false), nullptr,
+ ArraySizeModifier::Normal, 0);
+ } else {
+ AllocType = NewExpr->getAllocatedType();
+ }
+
+ unsigned StorageSize = 1;
+ unsigned AllocSize = 1;
+ if (const auto *CAT = dyn_cast<ConstantArrayType>(AllocType))
+ AllocSize = CAT->getZExtSize();
+ if (const auto *CAT = dyn_cast<ConstantArrayType>(StorageType))
+ StorageSize = CAT->getZExtSize();
+
+ if (AllocSize > StorageSize ||
+ !ASTCtx.hasSimilarType(ASTCtx.getBaseElementType(AllocType),
+ ASTCtx.getBaseElementType(StorageType))) {
+ S.FFDiag(S.Current->getLocation(OpPC),
+ diag::note_constexpr_placement_new_wrong_type)
+ << StorageType << AllocType;
+ return false;
+ }
+
+ return true;
+}
+
bool Interpret(InterpState &S, APValue &Result) {
// The current stack frame when we started Interpret().
// This is being used by the ops to determine wheter
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index c1423a060bcb97..a66723acbaf62c 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -3151,6 +3151,16 @@ inline bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T) {
return false;
}
+/// Check if the initializer and storage types of a placement-new expression
+/// match.
+bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
+ std::optional<uint64_t> ArraySize = std::nullopt);
+
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+bool CheckNewTypeMismatchArray(InterpState &S, CodePtr OpPC, const Expr *E) {
+ const auto &Size = S.Stk.pop<T>();
+ return CheckNewTypeMismatch(S, OpPC, E, static_cast<uint64_t>(Size));
+}
//===----------------------------------------------------------------------===//
// Read opcode arguments
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 46247688d4ef85..980204cf1ba071 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -786,4 +786,14 @@ def Free : Opcode {
let Args = [ArgBool];
}
+def CheckNewTypeMismatch : Opcode {
+ let Args = [ArgExpr];
+}
+
+def CheckNewTypeMismatchArray : Opcode {
+ let Types = [IntegerTypeClass];
+ let Args = [ArgExpr];
+ let HasGroup = 1;
+}
+
def IsConstantContext: Opcode;
diff --git a/clang/test/AST/ByteCode/placement-new.cpp b/clang/test/AST/ByteCode/placement-new.cpp
new file mode 100644
index 00000000000000..1fcbdd706b18b6
--- /dev/null
+++ b/clang/test/AST/ByteCode/placement-new.cpp
@@ -0,0 +1,177 @@
+// RUN: %clang_cc1 -std=c++2c -fexperimental-new-constant-interpreter -verify=expected,both %s
+// RUN: %clang_cc1 -std=c++2c -verify=ref,both %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 auto ok1() {
+ bool b;
+ new (&b) bool(true);
+ return b;
+}
+static_assert(ok1());
+
+consteval auto ok2() {
+ int b;
+ new (&b) int(12);
+ return b;
+}
+static_assert(ok2() == 12);
+
+
+consteval auto ok3() {
+ float b;
+ new (&b) float(12.0);
+ return b;
+}
+static_assert(ok3() == 12.0);
+
+
+consteval auto ok4() {
+ _BitInt(11) b;
+ new (&b) _BitInt(11)(37);
+ return b;
+}
+static_assert(ok4() == 37);
+
+consteval auto fail1() {
+ int b;
+ new (&b) float(1.0); // both-note {{placement new would change type of storage from 'int' to 'float'}}
+ return b;
+}
+static_assert(fail1() == 0); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+consteval int fail2() {
+ int i;
+ new (static_cast<void*>(&i)) float(0); // both-note {{placement new would change type of storage from 'int' to 'float'}}
+ return 0;
+}
+static_assert(fail2() == 0); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+consteval int indeterminate() {
+ int * indeterminate;
+ new (indeterminate) int(0); // both-note {{read of uninitialized object is not allowed in a constant expression}}
+ return 0;
+}
+static_assert(indeterminate() == 0); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+consteval int array1() {
+ int i[2];
+ new (&i) int[]{1,2};
+ return i[0] + i[1];
+}
+static_assert(array1() == 3);
+
+consteval int array2() {
+ int i[2];
+ new (static_cast<void*>(&i)) int[]{1,2};
+ return i[0] + i[1];
+}
+static_assert(array2() == 3);
+
+consteval int array3() {
+ int i[1];
+ new (&i) int[2]; // both-note {{placement new would change type of storage from 'int[1]' to 'int[2]'}}
+ return 0;
+}
+static_assert(array3() == 0); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+consteval int array4() {
+ int i[2];
+ new (&i) int[]{12};
+ return i[0];
+}
+static_assert(array4() == 12);
+
+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 }; // both-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()); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'blah()'}}
+
+
+constexpr int *get_indeterminate() {
+ int *evil;
+ return evil; // both-note {{read of uninitialized object is not allowed in a constant expression}}
+}
+
+constexpr bool bleh() {
+ int *ptr = new (get_indeterminate()) int; // both-note {{in call to 'get_indeterminate()'}}
+ return true;
+}
+static_assert(bleh()); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to 'bleh()'}}
+
+namespace records {
+ class S {
+ public:
+ float f;
+ };
+
+ constexpr bool record1() {
+ S s(13);
+ new (&s) S(42);
+ return s.f == 42;
+ }
+ static_assert(record1());
+
+ S GlobalS;
+ constexpr bool record2() {
+ new (&GlobalS) S(42); // both-note {{a constant expression cannot modify an object that is visible outside that expression}}
+ return GlobalS.f == 42;
+ }
+ static_assert(record2()); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+
+ constexpr bool record3() {
+ S ss[3];
+
+ new (&ss) S[]{{1}, {2}, {3}};
+
+ return ss[0].f == 1 && ss[1].f == 2 && ss[2].f == 3;
+ }
+ static_assert(record3());
+
+ struct F {
+ float f;
+ };
+ struct R {
+ F f;
+ int a;
+ };
+ constexpr bool record4() {
+ R r;
+ new (&r.f) F{42.0};
+ new (&r.a) int(12);
+
+ return r.f.f == 42.0 && r.a == 12;
+ }
+ static_assert(record4());
+}
+
+
More information about the cfe-commits
mailing list