Skip to content
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

Issue 21693 - Lower scoped destruction of extern(C++) class to destroy/dtor call #12265

Merged
merged 3 commits into from Mar 15, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/dmd/declaration.d
Expand Up @@ -1454,14 +1454,22 @@ extern (C++) class VarDeclaration : Declaration
//if (cd.isInterfaceDeclaration())
// error("interface `%s` cannot be scope", cd.toChars());

// Destroying C++ scope classes crashes currently. Since C++ class dtors are not currently supported, simply do not run dtors for them.
// See https://issues.dlang.org/show_bug.cgi?id=13182
if (cd.classKind == ClassKind.cpp)
{
break;
}
if (mynew || onstack) // if any destructors
{
// delete'ing C++ classes crashes (and delete is deprecated anyway)
if (cd.classKind == ClassKind.cpp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destroy should be used instead of delete for for D classes, too: https://issues.dlang.org/show_bug.cgi?id=21692

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destroy doesn't work for const/immutable either (unless we insert an explicit cast to mutable)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented another approach, it's now directly calling the dtor (effectively inlining the call to destroy).
This matches the implementation for structs and allows the hack to call dtors on immutable instances.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destroy doesn't work for const/immutable either (unless we insert an explicit cast to mutable)

Seems like it does (tested with v2.096.0). Anyway, the direct dtor call is better. Is there a reason you don't use it for D classes too? I find it inconsistent (can use scope const/immutable with C++ classes, but not with D ones) and don't really like deprecated expressions in compiler-generated AST nodes. ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it does (tested with v2.096.0).

Nevermind, seems like it was an issue with the lowering.

Is there a reason you don't use it for D classes too? I find it inconsistent (can use scope const/immutable with C++ classes, but not with D ones) and don't really like deprecated expressions in compiler-generated AST nodes. ;)

Mostly because destroy (and delete IIRC) refer to rt_finalize2 which does more cleanup than just calling the dtor. Not sure if those additional steps are required for stack allocated instances...

But lowering to destroy would still work for D classes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But lowering to destroy would still work for D classes.

Yeah that'd be great.

You're right, for D classes, there's apparently the need to descend to all base classes and invoke the dtors manually (via TypeInfo... argh), and the monitor might need cleanup too. LDC has a special case for these delete expressions for scope classes and tries to avoid calling druntime if the class has no finalizer and the monitor is null: https://github.com/ldc-developers/ldc/blob/937c57b8354d4821daededb35abdb77d16ce3737/gen/classes.cpp#L200-L222

Doing this here in the frontend directly would obviously be much nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the best way forward is to replace the TypeInfo based dtor-loop in DRuntime with a real dtor chain as done for C++ classes and to move the cleanup into `Object.~this()). That would cause less (magic) code scattered across dmd/druntime/ldc/... .

Anyway, lowering to destroy doesn't work yet.

  • it isn't alway @nogc
  • CTFE cannot handle destroy (it needs some special casing as done for delete)

{
// Don't call non-existant dtor
if (!cd.dtor)
break;

e = new VarExp(loc, this);
e.type = e.type.mutableOf().unSharedOf(); // Hack for mutable ctor on immutable instances
e = new DotVarExp(loc, e, cd.dtor, false);
e = new CallExp(loc, e);
break;
}

// delete this;
Expression ec;
ec = new VarExp(loc, this);
Expand Down
143 changes: 143 additions & 0 deletions test/runnable/cppdtor.d
@@ -0,0 +1,143 @@
/*
https://issues.dlang.org/show_bug.cgi?id=21693

RUN_OUTPUT:
---
CppA:
1: CppA.~this
CppB:
2: CppB.~this
2: CppA.~this
CppC:
3: CppC.~this
3: CppB.~this
3: CppA.~this
CppC:
4: CppC.~this
4: CppB.~this
4: CppA.~this
CppNoDestruct:
DA:
1: DA.~this
DB:
2: DB.~this
2: DA.~this
DC:
3: DC.~this
3: DB.~this
3: DA.~this
DC:
4: DC.~this
4: DB.~this
4: DA.~this
---
*/

extern (C) int printf(scope const char*, ...);

extern (C++) class CppA
{
int num;
this(int num)
{
this.num = num;
}

~this()
{
printf("%d: CppA.~this\n", num);
}
}

extern (C++) class CppB : CppA
{
this(int num)
{
super(num);
}

~this()
{
printf("%d: CppB.~this\n", num);
}
}

extern (C++) class CppC : CppB
{
this(int num)
{
super(num);
}

~this()
{
printf("%d: CppC.~this\n", num);
}
}

extern (D) class DA
{
int num;
this(int num)
{
this.num = num;
}

~this()
{
printf("%d: DA.~this\n", num);
}
}

extern (D) class DB : DA
{
this(int num)
{
super(num);
}

~this()
{
printf("%d: DB.~this\n", num);
}
}

extern (D) class DC : DB
{
this(int num)
{
super(num);
}

~this()
{
printf("%d: DC.~this\n", num);
}
}

extern (C++) class CppNoDestruct
{
int num;
this(int num)
{
this.num = num;
}
}

void main()
{
printf("CppA:\n"); { scope a = new CppA(1); }
printf("CppB:\n"); { scope CppA b = new CppB(2); }
printf("CppC:\n"); { scope CppA c = new CppC(3); }
printf("CppC:\n"); { scope CppB c2 = new CppC(4); }

printf("CppNoDestruct:\n");
{
scope const nd = new CppNoDestruct(1);
}

printf("DA:\n"); { scope a = new DA(1); }
printf("DB:\n"); { scope DA b = new DB(2); }
printf("DC:\n"); { scope DA c = new DC(3); }
printf("DC:\n"); { scope DB c2 = new DC(4); }
}
26 changes: 15 additions & 11 deletions test/runnable_cxx/cppa.d
Expand Up @@ -1376,16 +1376,16 @@ extern(C++)
__gshared char[32] traceBuf;
__gshared size_t traceBufPos;

// workaround for https://issues.dlang.org/show_bug.cgi?id=18986
version(OSX)
enum cppCtorReturnsThis = false;
else version(FreeBSD)
enum cppCtorReturnsThis = false;
else
enum cppCtorReturnsThis = true;

mixin template scopeAllocCpp(C)
{
// workaround for https://issues.dlang.org/show_bug.cgi?id=18986
version(OSX)
enum cppCtorReturnsThis = false;
else version(FreeBSD)
enum cppCtorReturnsThis = false;
else
enum cppCtorReturnsThis = true;

static if (cppCtorReturnsThis)
scope C ptr = new C;
else
Expand All @@ -1404,9 +1404,13 @@ void test15589b()
mixin scopeAllocCpp!Cpp15589DerivedVirtual derivedVirtual;
mixin scopeAllocCpp!Cpp15589IntroducingVirtual introducingVirtual;

introducingVirtual.ptr.destroy();
derivedVirtual.ptr.destroy();
derived.ptr.destroy();
// `scope` instances are destroyed automatically
static if (!cppCtorReturnsThis)
{
introducingVirtual.ptr.destroy();
derivedVirtual.ptr.destroy();
derived.ptr.destroy();
}
}
printf("traceBuf15589 %.*s\n", cast(int)traceBufPos, traceBuf.ptr);
assert(traceBuf[0..traceBufPos] == "IbVvBbs");
Expand Down