[clang] 78e4237 - [clang][Interp] Support destructors
Timm Bäder via cfe-commits
cfe-commits at lists.llvm.org
Sun Mar 5 01:08:53 PST 2023
Author: Timm Bäder
Date: 2023-03-05T10:02:42+01:00
New Revision: 78e4237460bf58f3d6b75f275e0424f38e3b1d04
URL: https://github.com/llvm/llvm-project/commit/78e4237460bf58f3d6b75f275e0424f38e3b1d04
DIFF: https://github.com/llvm/llvm-project/commit/78e4237460bf58f3d6b75f275e0424f38e3b1d04.diff
LOG: [clang][Interp] Support destructors
Use the existing local variable cleanup infrastructure to implement
destruction.
Differential Revision: https://reviews.llvm.org/D137070
Added:
Modified:
clang/lib/AST/Interp/ByteCodeExprGen.cpp
clang/lib/AST/Interp/ByteCodeExprGen.h
clang/lib/AST/Interp/ByteCodeStmtGen.cpp
clang/test/AST/Interp/cxx20.cpp
Removed:
################################################################################
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index bc682c92c143f..488d07e09d9b5 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -26,10 +26,10 @@ namespace clang {
namespace interp {
/// Scope used to handle temporaries in toplevel variable declarations.
-template <class Emitter> class DeclScope final : public LocalScope<Emitter> {
+template <class Emitter> class DeclScope final : public VariableScope<Emitter> {
public:
DeclScope(ByteCodeExprGen<Emitter> *Ctx, const ValueDecl *VD)
- : LocalScope<Emitter>(Ctx), Scope(Ctx->P, VD) {}
+ : VariableScope<Emitter>(Ctx), Scope(Ctx->P, VD) {}
void addExtended(const Scope::Local &Local) override {
return this->addLocal(Local);
@@ -1857,6 +1857,80 @@ void ByteCodeExprGen<Emitter>::emitCleanup() {
C->emitDestruction();
}
+/// When calling this, we have a pointer of the local-to-destroy
+/// on the stack.
+/// Emit destruction of record types (or arrays of record types).
+/// FIXME: Handle virtual destructors.
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::emitRecordDestruction(const Descriptor *Desc) {
+ assert(Desc);
+ assert(!Desc->isPrimitive());
+ assert(!Desc->isPrimitiveArray());
+
+ // Arrays.
+ if (Desc->isArray()) {
+ const Descriptor *ElemDesc = Desc->ElemDesc;
+ const Record *ElemRecord = ElemDesc->ElemRecord;
+ assert(ElemRecord); // This is not a primitive array.
+
+ if (const CXXDestructorDecl *Dtor = ElemRecord->getDestructor();
+ Dtor && !Dtor->isTrivial()) {
+ for (ssize_t I = Desc->getNumElems() - 1; I >= 0; --I) {
+ if (!this->emitConstUint64(I, SourceInfo{}))
+ return false;
+ if (!this->emitArrayElemPtrUint64(SourceInfo{}))
+ return false;
+ if (!this->emitRecordDestruction(Desc->ElemDesc))
+ return false;
+ }
+ }
+ return this->emitPopPtr(SourceInfo{});
+ }
+
+ const Record *R = Desc->ElemRecord;
+ assert(R);
+ // First, destroy all fields.
+ for (const Record::Field &Field : llvm::reverse(R->fields())) {
+ const Descriptor *D = Field.Desc;
+ if (!D->isPrimitive() && !D->isPrimitiveArray()) {
+ if (!this->emitDupPtr(SourceInfo{}))
+ return false;
+ if (!this->emitGetPtrField(Field.Offset, SourceInfo{}))
+ return false;
+ if (!this->emitRecordDestruction(D))
+ return false;
+ }
+ }
+
+ // FIXME: Unions need to be handled
diff erently here. We don't want to
+ // call the destructor of its members.
+
+ // Now emit the destructor and recurse into base classes.
+ if (const CXXDestructorDecl *Dtor = R->getDestructor();
+ Dtor && !Dtor->isTrivial()) {
+ const Function *DtorFunc = getFunction(Dtor);
+ if (DtorFunc && DtorFunc->isConstexpr()) {
+ assert(DtorFunc->hasThisPointer());
+ assert(DtorFunc->getNumParams() == 1);
+ if (!this->emitDupPtr(SourceInfo{}))
+ return false;
+ if (!this->emitCall(DtorFunc, SourceInfo{}))
+ return false;
+ }
+ }
+
+ for (const Record::Base &Base : llvm::reverse(R->bases())) {
+ if (!this->emitGetPtrBase(Base.Offset, SourceInfo{}))
+ return false;
+ if (!this->emitRecordDestruction(Base.Desc))
+ return false;
+ }
+ // FIXME: Virtual bases.
+
+ // Remove the instance pointer.
+ return this->emitPopPtr(SourceInfo{});
+}
+
namespace clang {
namespace interp {
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h
index 231f39ff8bd6d..4d929278f29c8 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.h
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.h
@@ -256,6 +256,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
return FPO.getRoundingMode();
}
+ bool emitRecordDestruction(const Descriptor *Desc);
+
protected:
/// Variable to storage mapping.
llvm::DenseMap<const ValueDecl *, Scope::Local> Locals;
@@ -333,9 +335,20 @@ template <class Emitter> class LocalScope : public VariableScope<Emitter> {
this->Ctx->Descriptors[*Idx].emplace_back(Local);
}
+ /// Emit destruction of the local variable. This includes
+ /// object destructors.
void emitDestruction() override {
if (!Idx)
return;
+ // Emit destructor calls for local variables of record
+ // type with a destructor.
+ for (Scope::Local &Local : this->Ctx->Descriptors[*Idx]) {
+ if (!Local.Desc->isPrimitive() && !Local.Desc->isPrimitiveArray()) {
+ this->Ctx->emitGetPtrLocal(Local.Offset, SourceInfo{});
+ this->Ctx->emitRecordDestruction(Local.Desc);
+ }
+ }
+
this->Ctx->emitDestroy(*Idx, SourceInfo{});
}
diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
index a4be86c0e0639..547a24e5b19d3 100644
--- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
@@ -428,6 +428,7 @@ bool ByteCodeStmtGen<Emitter>::visitBreakStmt(const BreakStmt *S) {
if (!BreakLabel)
return false;
+ this->emitCleanup();
return this->jump(*BreakLabel);
}
@@ -436,6 +437,7 @@ bool ByteCodeStmtGen<Emitter>::visitContinueStmt(const ContinueStmt *S) {
if (!ContinueLabel)
return false;
+ this->emitCleanup();
return this->jump(*ContinueLabel);
}
diff --git a/clang/test/AST/Interp/cxx20.cpp b/clang/test/AST/Interp/cxx20.cpp
index cc37722730fc2..480d6e8e9a8a2 100644
--- a/clang/test/AST/Interp/cxx20.cpp
+++ b/clang/test/AST/Interp/cxx20.cpp
@@ -271,3 +271,250 @@ namespace ConstThis {
// ref-error {{must have constant destruction}} \
// ref-note {{in call to}}
};
+
+namespace Destructors {
+
+ class Inc final {
+ public:
+ int &I;
+ constexpr Inc(int &I) : I(I) {}
+ constexpr ~Inc() {
+ I++;
+ }
+ };
+
+ class Dec final {
+ public:
+ int &I;
+ constexpr Dec(int &I) : I(I) {}
+ constexpr ~Dec() {
+ I--;
+ }
+ };
+
+
+
+ constexpr int m() {
+ int i = 0;
+ {
+ Inc f1(i);
+ Inc f2(i);
+ Inc f3(i);
+ }
+ return i;
+ }
+ static_assert(m() == 3, "");
+
+
+ constexpr int C() {
+ int i = 0;
+
+ while (i < 10) {
+ Inc inc(i);
+ continue;
+ Dec dec(i);
+ }
+ return i;
+ }
+ static_assert(C() == 10, "");
+
+
+ constexpr int D() {
+ int i = 0;
+
+ {
+ Inc i1(i);
+ {
+ Inc i2(i);
+ return i;
+ }
+ }
+
+ return i;
+ }
+ static_assert(D() == 0, "");
+
+ constexpr int E() {
+ int i = 0;
+
+ for(;;) {
+ Inc i1(i);
+ break;
+ }
+ return i;
+ }
+ static_assert(E() == 1, "");
+
+
+ /// FIXME: This should be rejected, since we call the destructor
+ /// twice. However, GCC doesn't care either.
+ constexpr int ManualDtor() {
+ int i = 0;
+ {
+ Inc I(i); // ref-note {{destroying object 'I' whose lifetime has already ended}}
+ I.~Inc();
+ }
+ return i;
+ }
+ static_assert(ManualDtor() == 1, ""); // expected-error {{static assertion failed}} \
+ // expected-note {{evaluates to '2 == 1'}} \
+ // ref-error {{not an integral constant expression}} \
+ // ref-note {{in call to 'ManualDtor()'}}
+
+ constexpr void doInc(int &i) {
+ Inc I(i);
+ return;
+ }
+ constexpr int testInc() {
+ int i = 0;
+ doInc(i);
+ return i;
+ }
+ static_assert(testInc() == 1, "");
+ constexpr void doInc2(int &i) {
+ Inc I(i);
+ // No return statement.
+ }
+ constexpr int testInc2() {
+ int i = 0;
+ doInc2(i);
+ return i;
+ }
+ static_assert(testInc2() == 1, "");
+
+
+ namespace DtorOrder {
+ class A {
+ public:
+ int &I;
+ constexpr A(int &I) : I(I) {}
+ constexpr ~A() {
+ I = 1337;
+ }
+ };
+
+ class B : public A {
+ public:
+ constexpr B(int &I) : A(I) {}
+ constexpr ~B() {
+ I = 42;
+ }
+ };
+
+ constexpr int foo() {
+ int i = 0;
+ {
+ B b(i);
+ }
+ return i;
+ }
+
+ static_assert(foo() == 1337);
+ }
+
+ class FieldDtor1 {
+ public:
+ Inc I1;
+ Inc I2;
+ constexpr FieldDtor1(int &I) : I1(I), I2(I){}
+ };
+
+ constexpr int foo2() {
+ int i = 0;
+ {
+ FieldDtor1 FD1(i);
+ }
+ return i;
+ }
+
+ static_assert(foo2() == 2);
+
+ class FieldDtor2 {
+ public:
+ Inc Incs[3];
+ constexpr FieldDtor2(int &I) : Incs{Inc(I), Inc(I), Inc(I)} {}
+ };
+
+ constexpr int foo3() {
+ int i = 0;
+ {
+ FieldDtor2 FD2(i);
+ }
+ return i;
+ }
+
+ static_assert(foo3() == 3);
+
+ struct ArrD {
+ int index;
+ int *arr;
+ int &p;
+ constexpr ~ArrD() {
+ arr[p] = index;
+ ++p;
+ }
+ };
+ constexpr bool ArrayOrder() {
+ int order[3] = {0, 0, 0};
+ int p = 0;
+ {
+ ArrD ds[3] = {
+ {1, order, p},
+ {2, order, p},
+ {3, order, p},
+ };
+ // ds will be destroyed.
+ }
+ return order[0] == 3 && order[1] == 2 && order[2] == 1;
+ }
+ static_assert(ArrayOrder());
+
+
+ // Static members aren't destroyed.
+ class Dec2 {
+ public:
+ int A = 0;
+ constexpr ~Dec2() {
+ A++;
+ }
+ };
+ class Foo {
+ public:
+ static constexpr Dec2 a;
+ static Dec2 b;
+ };
+ static_assert(Foo::a.A == 0);
+ constexpr bool f() {
+ Foo f;
+ return true;
+ }
+ static_assert(Foo::a.A == 0);
+ static_assert(f());
+ static_assert(Foo::a.A == 0);
+
+
+ struct NotConstexpr {
+ NotConstexpr() {}
+ ~NotConstexpr() {}
+ };
+
+ struct Outer {
+ constexpr Outer() = default;
+ constexpr ~Outer();
+
+ constexpr int foo() {
+ return 12;
+ }
+
+ constexpr int bar()const {
+ return Outer{}.foo();
+ }
+
+ static NotConstexpr Val;
+ };
+
+ constexpr Outer::~Outer() {}
+
+ constexpr Outer O;
+ static_assert(O.bar() == 12);
+}
More information about the cfe-commits
mailing list