Skip to content

[clang][Interp] Compile field+base destruction into class dtor func #102871

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 1 commit into from
Aug 13, 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
97 changes: 58 additions & 39 deletions clang/lib/AST/Interp/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4856,13 +4856,59 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
return this->emitRetVoid(SourceInfo{});
}

template <class Emitter>
bool Compiler<Emitter>::compileDestructor(const CXXDestructorDecl *Dtor) {
const RecordDecl *RD = Dtor->getParent();
const Record *R = this->getRecord(RD);
if (!R)
return false;

if (!Dtor->isTrivial() && Dtor->getBody()) {
if (!this->visitStmt(Dtor->getBody()))
return false;
}

if (!this->emitThis(Dtor))
return false;

assert(R);
if (!R->isUnion()) {
// 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->emitGetPtrField(Field.Offset, SourceInfo{}))
return false;
if (!this->emitDestruction(D))
return false;
if (!this->emitPopPtr(SourceInfo{}))
return false;
}
}
}

for (const Record::Base &Base : llvm::reverse(R->bases())) {
if (!this->emitGetPtrBase(Base.Offset, SourceInfo{}))
return false;
if (!this->emitRecordDestruction(Base.R))
return false;
if (!this->emitPopPtr(SourceInfo{}))
return false;
}

// FIXME: Virtual bases.
return this->emitPopPtr(Dtor) && this->emitRetVoid(Dtor);
}

template <class Emitter>
bool Compiler<Emitter>::visitFunc(const FunctionDecl *F) {
// Classify the return type.
ReturnType = this->classify(F->getReturnType());

if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(F))
return this->compileConstructor(Ctor);
if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(F))
return this->compileDestructor(Dtor);

// Emit custom code if this is a lambda static invoker.
if (const auto *MD = dyn_cast<CXXMethodDecl>(F);
Expand Down Expand Up @@ -5573,46 +5619,19 @@ bool Compiler<Emitter>::emitComplexComparison(const Expr *LHS, const Expr *RHS,
template <class Emitter>
bool Compiler<Emitter>::emitRecordDestruction(const Record *R) {
assert(R);
if (!R->isUnion()) {
// 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->emitGetPtrField(Field.Offset, SourceInfo{}))
return false;
if (!this->emitDestruction(D))
return false;
if (!this->emitPopPtr(SourceInfo{}))
return false;
}
}
}

// 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)
return false;
assert(DtorFunc->hasThisPointer());
assert(DtorFunc->getNumParams() == 1);
if (!this->emitDupPtr(SourceInfo{}))
return false;
if (!this->emitCall(DtorFunc, 0, SourceInfo{}))
return false;
}

for (const Record::Base &Base : llvm::reverse(R->bases())) {
if (!this->emitGetPtrBase(Base.Offset, SourceInfo{}))
return false;
if (!this->emitRecordDestruction(Base.R))
return false;
if (!this->emitPopPtr(SourceInfo{}))
return false;
}
const CXXDestructorDecl *Dtor = R->getDestructor();
if (!Dtor || Dtor->isTrivial())
return true;

// FIXME: Virtual bases.
return true;
assert(Dtor);
const Function *DtorFunc = getFunction(Dtor);
if (!DtorFunc)
return false;
assert(DtorFunc->hasThisPointer());
assert(DtorFunc->getNumParams() == 1);
if (!this->emitDupPtr(SourceInfo{}))
return false;
return this->emitCall(DtorFunc, 0, SourceInfo{});
}
/// When calling this, we have a pointer of the local-to-destroy
/// on the stack.
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/Interp/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
const QualType DerivedType);
bool emitLambdaStaticInvokerBody(const CXXMethodDecl *MD);
bool compileConstructor(const CXXConstructorDecl *Ctor);
bool compileDestructor(const CXXDestructorDecl *Dtor);

bool checkLiteralType(const Expr *E);

Expand Down
24 changes: 0 additions & 24 deletions clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -867,23 +867,6 @@ static bool runRecordDestructor(InterpState &S, CodePtr OpPC,
return false;
}

// Fields.
for (const Record::Field &Field : llvm::reverse(R->fields())) {
const Descriptor *D = Field.Desc;
if (D->isRecord()) {
if (!runRecordDestructor(S, OpPC, BasePtr.atField(Field.Offset), D))
return false;
} else if (D->isCompositeArray()) {
const Descriptor *ElemDesc = Desc->ElemDesc;
assert(ElemDesc->isRecord());
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
if (!runRecordDestructor(S, OpPC, BasePtr.atIndex(I).narrow(),
ElemDesc))
return false;
}
}
}

// Destructor of this record.
if (const CXXDestructorDecl *Dtor = R->getDestructor();
Dtor && !Dtor->isTrivial()) {
Expand All @@ -895,13 +878,6 @@ static bool runRecordDestructor(InterpState &S, CodePtr OpPC,
if (!Call(S, OpPC, DtorFunc, 0))
return false;
}

// Bases.
for (const Record::Base &Base : llvm::reverse(R->bases())) {
if (!runRecordDestructor(S, OpPC, BasePtr.atField(Base.Offset), Base.Desc))
return false;
}

return true;
}

Expand Down
3 changes: 1 addition & 2 deletions clang/test/AST/Interp/cxx20.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,6 @@ namespace DefinitionLoc {
// both-note {{non-constexpr constructor}}
}

/// FIXME: Call base dtors when explicitly calling dtor.
namespace VirtDtor {
class B {
public:
Expand Down Expand Up @@ -900,5 +899,5 @@ namespace VirtDtor {
return buff[0] == a && buff[1] == b;
}

static_assert(test('C', 'B')); // expected-error {{failed}}
static_assert(test('C', 'B'));
}
5 changes: 2 additions & 3 deletions clang/test/AST/Interp/new-delete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -514,8 +514,7 @@ namespace DeleteRunsDtors {
static_assert(abc2() == 1);
}

/// FIXME: There is a slight difference in diagnostics here, because we don't
/// create a new frame when we delete record fields or bases at all.
/// FIXME: There is a slight difference in diagnostics here.
namespace FaultyDtorCalledByDelete {
struct InnerFoo {
int *mem;
Expand All @@ -536,7 +535,7 @@ namespace FaultyDtorCalledByDelete {
a = new int(13);
IF.mem = new int(100);
}
constexpr ~Foo() { delete a; }
constexpr ~Foo() { delete a; } // expected-note {{in call to}}
};

constexpr int abc() {
Expand Down
31 changes: 31 additions & 0 deletions clang/test/AST/Interp/records.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1595,4 +1595,35 @@ namespace VirtDtor {
}
static_assert(virt_dtor(0, "ZYX"));
}

namespace DtorDestroysFieldsAfterSelf {
struct S {
int a = 10;
constexpr ~S() {
a = 0;
}

};
struct F {
S s;
int a;
int &b;
constexpr F(int a, int &b) : a(a), b(b) {}
constexpr ~F() {
b += s.a;
}
};

constexpr int foo() {
int a = 10;
int b = 5;
{
F f(a, b);
}

return b;
}

static_assert(foo() == 15);
}
#endif
Loading