[clang] 243b355 - [clang][Interp] Support destructors
Timm Bäder via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 30 04:17:24 PDT 2023
Author: Timm Bäder
Date: 2023-03-30T13:17:04+02:00
New Revision: 243b355ee0f063cff4988f061215839020c2175b
URL: https://github.com/llvm/llvm-project/commit/243b355ee0f063cff4988f061215839020c2175b
DIFF: https://github.com/llvm/llvm-project/commit/243b355ee0f063cff4988f061215839020c2175b.diff
LOG: [clang][Interp] Support destructors
Emit destructors for non-primitive (array) variables on scope ends.
Differential Revision: https://reviews.llvm.org/D145545
Added:
Modified:
clang/lib/AST/Interp/ByteCodeExprGen.cpp
clang/lib/AST/Interp/ByteCodeExprGen.h
clang/lib/AST/Interp/ByteCodeStmtGen.cpp
clang/lib/AST/Interp/ByteCodeStmtGen.h
clang/test/AST/Interp/cxx20.cpp
Removed:
################################################################################
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index 0056da191765b..fff2425bedf42 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..03d2eb87b5e1c 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.h
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.h
@@ -29,6 +29,7 @@ class QualType;
namespace interp {
template <class Emitter> class LocalScope;
+template <class Emitter> class DestructorScope;
template <class Emitter> class RecordScope;
template <class Emitter> class VariableScope;
template <class Emitter> class DeclScope;
@@ -189,6 +190,7 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
private:
friend class VariableScope<Emitter>;
friend class LocalScope<Emitter>;
+ friend class DestructorScope<Emitter>;
friend class RecordScope<Emitter>;
friend class DeclScope<Emitter>;
friend class OptionScope<Emitter>;
@@ -256,6 +258,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;
@@ -304,7 +308,7 @@ template <class Emitter> class VariableScope {
}
virtual void emitDestruction() {}
-
+ virtual void emitDestructors() {}
VariableScope *getParent() const { return Parent; }
protected:
@@ -314,15 +318,26 @@ template <class Emitter> class VariableScope {
VariableScope *Parent;
};
-/// Scope for local variables.
-///
-/// When the scope is destroyed, instructions are emitted to tear down
-/// all variables declared in this scope.
+/// Generic scope for local variables.
template <class Emitter> class LocalScope : public VariableScope<Emitter> {
public:
LocalScope(ByteCodeExprGen<Emitter> *Ctx) : VariableScope<Emitter>(Ctx) {}
- ~LocalScope() override { this->emitDestruction(); }
+ /// Emit a Destroy op for this scope.
+ ~LocalScope() override {
+ if (!Idx)
+ return;
+ this->Ctx->emitDestroy(*Idx, SourceInfo{});
+ }
+
+ /// Overriden to support explicit destruction.
+ void emitDestruction() override {
+ if (!Idx)
+ return;
+ this->emitDestructors();
+ this->Ctx->emitDestroy(*Idx, SourceInfo{});
+ this->Idx = std::nullopt;
+ }
void addLocal(const Scope::Local &Local) override {
if (!Idx) {
@@ -333,21 +348,51 @@ template <class Emitter> class LocalScope : public VariableScope<Emitter> {
this->Ctx->Descriptors[*Idx].emplace_back(Local);
}
- void emitDestruction() override {
+ void emitDestructors() override {
if (!Idx)
return;
- this->Ctx->emitDestroy(*Idx, SourceInfo{});
+ // 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);
+ }
+ }
}
-protected:
/// Index of the scope in the chain.
std::optional<unsigned> Idx;
};
+/// Emits the destructors of the variables of \param OtherScope
+/// when this scope is destroyed. Does not create a Scope in the bytecode at
+/// all, this is just a RAII object to emit destructors.
+template <class Emitter> class DestructorScope final {
+public:
+ DestructorScope(LocalScope<Emitter> &OtherScope) : OtherScope(OtherScope) {}
+
+ ~DestructorScope() { OtherScope.emitDestructors(); }
+
+private:
+ LocalScope<Emitter> &OtherScope;
+};
+
+/// Like a regular LocalScope, except that the destructors of all local
+/// variables are automatically emitted when the AutoScope is destroyed.
+template <class Emitter> class AutoScope : public LocalScope<Emitter> {
+public:
+ AutoScope(ByteCodeExprGen<Emitter> *Ctx)
+ : LocalScope<Emitter>(Ctx), DS(*this) {}
+
+private:
+ DestructorScope<Emitter> DS;
+};
+
/// Scope for storage declared in a compound statement.
-template <class Emitter> class BlockScope final : public LocalScope<Emitter> {
+template <class Emitter> class BlockScope final : public AutoScope<Emitter> {
public:
- BlockScope(ByteCodeExprGen<Emitter> *Ctx) : LocalScope<Emitter>(Ctx) {}
+ BlockScope(ByteCodeExprGen<Emitter> *Ctx) : AutoScope<Emitter>(Ctx) {}
void addExtended(const Scope::Local &Local) override {
// If we to this point, just add the variable as a normal local
@@ -359,9 +404,9 @@ template <class Emitter> class BlockScope final : public LocalScope<Emitter> {
/// Expression scope which tracks potentially lifetime extended
/// temporaries which are hoisted to the parent scope on exit.
-template <class Emitter> class ExprScope final : public LocalScope<Emitter> {
+template <class Emitter> class ExprScope final : public AutoScope<Emitter> {
public:
- ExprScope(ByteCodeExprGen<Emitter> *Ctx) : LocalScope<Emitter>(Ctx) {}
+ ExprScope(ByteCodeExprGen<Emitter> *Ctx) : AutoScope<Emitter>(Ctx) {}
void addExtended(const Scope::Local &Local) override {
if (this->Parent)
diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
index c5f87a1c9faac..1735b9b0272b1 100644
--- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
@@ -194,6 +194,23 @@ bool ByteCodeStmtGen<Emitter>::visitStmt(const Stmt *S) {
}
}
+/// Visits the given statment without creating a variable
+/// scope for it in case it is a compound statement.
+template <class Emitter>
+bool ByteCodeStmtGen<Emitter>::visitLoopBody(const Stmt *S) {
+ if (isa<NullStmt>(S))
+ return true;
+
+ if (const auto *CS = dyn_cast<CompoundStmt>(S)) {
+ for (auto *InnerStmt : CS->body())
+ if (!visitStmt(InnerStmt))
+ return false;
+ return true;
+ }
+
+ return this->visitStmt(S);
+}
+
template <class Emitter>
bool ByteCodeStmtGen<Emitter>::visitCompoundStmt(
const CompoundStmt *CompoundStmt) {
@@ -306,11 +323,15 @@ bool ByteCodeStmtGen<Emitter>::visitWhileStmt(const WhileStmt *S) {
if (!this->jumpFalse(EndLabel))
return false;
- if (!this->visitStmt(Body))
- return false;
+ LocalScope<Emitter> Scope(this);
+ {
+ DestructorScope<Emitter> DS(Scope);
+ if (!this->visitLoopBody(Body))
+ return false;
+ }
+
if (!this->jump(CondLabel))
return false;
-
this->emitLabel(EndLabel);
return true;
@@ -325,15 +346,21 @@ bool ByteCodeStmtGen<Emitter>::visitDoStmt(const DoStmt *S) {
LabelTy EndLabel = this->getLabel();
LabelTy CondLabel = this->getLabel();
LoopScope<Emitter> LS(this, EndLabel, CondLabel);
+ LocalScope<Emitter> Scope(this);
this->emitLabel(StartLabel);
- if (!this->visitStmt(Body))
- return false;
- this->emitLabel(CondLabel);
- if (!this->visitBool(Cond))
- return false;
+ {
+ DestructorScope<Emitter> DS(Scope);
+
+ if (!this->visitLoopBody(Body))
+ return false;
+ this->emitLabel(CondLabel);
+ if (!this->visitBool(Cond))
+ return false;
+ }
if (!this->jumpTrue(StartLabel))
return false;
+
this->emitLabel(EndLabel);
return true;
}
@@ -350,6 +377,7 @@ bool ByteCodeStmtGen<Emitter>::visitForStmt(const ForStmt *S) {
LabelTy CondLabel = this->getLabel();
LabelTy IncLabel = this->getLabel();
LoopScope<Emitter> LS(this, EndLabel, IncLabel);
+ LocalScope<Emitter> Scope(this);
if (Init && !this->visitStmt(Init))
return false;
@@ -360,11 +388,17 @@ bool ByteCodeStmtGen<Emitter>::visitForStmt(const ForStmt *S) {
if (!this->jumpFalse(EndLabel))
return false;
}
- if (Body && !this->visitStmt(Body))
- return false;
- this->emitLabel(IncLabel);
- if (Inc && !this->discard(Inc))
- return false;
+
+ {
+ DestructorScope<Emitter> DS(Scope);
+
+ if (Body && !this->visitLoopBody(Body))
+ return false;
+ this->emitLabel(IncLabel);
+ if (Inc && !this->discard(Inc))
+ return false;
+ }
+
if (!this->jump(CondLabel))
return false;
this->emitLabel(EndLabel);
@@ -386,38 +420,40 @@ bool ByteCodeStmtGen<Emitter>::visitCXXForRangeStmt(const CXXForRangeStmt *S) {
LabelTy CondLabel = this->getLabel();
LabelTy IncLabel = this->getLabel();
LoopScope<Emitter> LS(this, EndLabel, IncLabel);
- {
- ExprScope<Emitter> ES(this);
- // Emit declarations needed in the loop.
- if (Init && !this->visitStmt(Init))
- return false;
- if (!this->visitStmt(RangeStmt))
- return false;
- if (!this->visitStmt(BeginStmt))
- return false;
- if (!this->visitStmt(EndStmt))
- return false;
+ // Emit declarations needed in the loop.
+ if (Init && !this->visitStmt(Init))
+ return false;
+ if (!this->visitStmt(RangeStmt))
+ return false;
+ if (!this->visitStmt(BeginStmt))
+ return false;
+ if (!this->visitStmt(EndStmt))
+ return false;
- // Now the condition as well as the loop variable assignment.
- this->emitLabel(CondLabel);
- if (!this->visitBool(Cond))
- return false;
- if (!this->jumpFalse(EndLabel))
- return false;
+ // Now the condition as well as the loop variable assignment.
+ this->emitLabel(CondLabel);
+ if (!this->visitBool(Cond))
+ return false;
+ if (!this->jumpFalse(EndLabel))
+ return false;
- if (!this->visitVarDecl(LoopVar))
- return false;
+ if (!this->visitVarDecl(LoopVar))
+ return false;
+
+ // Body.
+ LocalScope<Emitter> Scope(this);
+ {
+ DestructorScope<Emitter> DS(Scope);
- // Body.
- if (!this->visitStmt(Body))
+ if (!this->visitLoopBody(Body))
return false;
this->emitLabel(IncLabel);
if (!this->discard(Inc))
return false;
- if (!this->jump(CondLabel))
- return false;
}
+ if (!this->jump(CondLabel))
+ return false;
this->emitLabel(EndLabel);
return true;
@@ -428,6 +464,7 @@ bool ByteCodeStmtGen<Emitter>::visitBreakStmt(const BreakStmt *S) {
if (!BreakLabel)
return false;
+ this->VarScope->emitDestructors();
return this->jump(*BreakLabel);
}
@@ -436,6 +473,7 @@ bool ByteCodeStmtGen<Emitter>::visitContinueStmt(const ContinueStmt *S) {
if (!ContinueLabel)
return false;
+ this->VarScope->emitDestructors();
return this->jump(*ContinueLabel);
}
diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.h b/clang/lib/AST/Interp/ByteCodeStmtGen.h
index 6b3644ad13469..8d9277a11dd7d 100644
--- a/clang/lib/AST/Interp/ByteCodeStmtGen.h
+++ b/clang/lib/AST/Interp/ByteCodeStmtGen.h
@@ -54,6 +54,7 @@ class ByteCodeStmtGen final : public ByteCodeExprGen<Emitter> {
// Statement visitors.
bool visitStmt(const Stmt *S);
bool visitCompoundStmt(const CompoundStmt *S);
+ bool visitLoopBody(const Stmt *S);
bool visitDeclStmt(const DeclStmt *DS);
bool visitReturnStmt(const ReturnStmt *RS);
bool visitIfStmt(const IfStmt *IS);
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