diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -256,6 +256,8 @@ return FPO.getRoundingMode(); } + bool emitRecordDestruction(const Descriptor *Desc); + protected: /// Variable to storage mapping. llvm::DenseMap Locals; @@ -333,9 +335,20 @@ 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/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -26,10 +26,10 @@ namespace interp { /// Scope used to handle temporaries in toplevel variable declarations. -template class DeclScope final : public LocalScope { +template class DeclScope final : public VariableScope { public: DeclScope(ByteCodeExprGen *Ctx, const ValueDecl *VD) - : LocalScope(Ctx), Scope(Ctx->P, VD) {} + : VariableScope(Ctx), Scope(Ctx->P, VD) {} void addExtended(const Scope::Local &Local) override { return this->addLocal(Local); @@ -1857,6 +1857,80 @@ 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 +bool ByteCodeExprGen::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 differently 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/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp --- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -428,6 +428,7 @@ if (!BreakLabel) return false; + this->emitCleanup(); return this->jump(*BreakLabel); } @@ -436,6 +437,7 @@ 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 --- a/clang/test/AST/Interp/cxx20.cpp +++ b/clang/test/AST/Interp/cxx20.cpp @@ -271,3 +271,250 @@ // 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); +}