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
fix bugzilla Issue 24368 - destruction of parameter should be done by… #16145
base: master
Are you sure you want to change the base?
Conversation
Thanks for your pull request, @WalterBright! Bugzilla references
Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + dmd#16145" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this could do with a changelog entry
It could, indeed. First I have to get it to work. |
I'm on the fence of just calling this wontfix. What's the reason for mimicking C++ here when the D method is perfectly acceptable? |
compiler/src/dmd/target.d
Outdated
return os == Target.OS.Windows || | ||
// C++ on non-Windows platforms has the caller destroying the arguments | ||
tf.linkage != LINK.cpp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd limit this to just tf.linkage == LINK.d
, it seems odd to be potentially calling a destructor on a parameter passed from a C/ObjC program.
return os == Target.OS.Windows || | |
// C++ on non-Windows platforms has the caller destroying the arguments | |
tf.linkage != LINK.cpp; | |
return os == Target.OS.Windows || | |
// C++ on non-Windows platforms has the caller destroying the arguments | |
tf.linkage == LINK.d; |
|
In what way does it need to be fixed? There's no examples where C++/D interop is broken because
Maybe that was something else? Both gdc and ldc do callee for
Stack frames/closures? That's a separate thing - or are you saying that you have an example where a closure variable is (somehow) passed by reference instead of copied and has its destructor called by the callee? Reflecting some more, maybe you're referring to how non-trivial types are passed around? In gdc it's always by reference (so the front-end better do the copy on the caller side else we're in trouble).
That's illegal.
|
At best, this is an optimization, consider: struct Dtor
{
int field;
~this() { }
}
void callee(Dtor param)
{
// __dtor(param)
}
void caller(Dtor param)
{
callee(param); // __copy(param)
// __dtor(param)
}
void main()
{
Dtor var;
caller(var); // __copy(var)
// __dtor(var)
} That is an awful lot of copying and destructoring. If instead we were doing caller destruction. struct Dtor
{
int field;
~this() { }
}
void callee(Dtor param)
{
}
void caller(Dtor param)
{
callee(param); // ref
}
void main()
{
Dtor var;
caller(var); // ref
// __dtor(var)
} No copying, one destructor call. |
Nope, this doesn't affect the number of copies/destructions (since lvalues need to be copied implicitly, and the low-level ref is to that caller-allocated copy, not to the original lvalue). AFAICT, the only difference is where the cleanup ends up in, in pseudo-code either
Doing this in the callee seems more intuitive to me - it's a local by-value param of callee, so naturally destroyed by the callee. And I suspect having the cleanup/finally in the callee might enable better optimizations, as it can be optimized along with the function body, guaranteed in the same translation unit. |
You can't pass a non-trivially copyable type by-value, as that would require a temporary. The front-end then must make an explicit copy (temporary) before passing it. |
Sure, allocation + copying (in the lvalue argument case, otherwise just passing a ref to the rvalue for perfect forwarding without moving) needs to be performed by the caller, always. But IMO that doesn't make it more natural to handle its destruction there too. But more importantly, EH overhead can be avoided by letting the callee destruct it; e.g., it might not throw, without explicit |
The testing issue is important. We have a lot of tests in the D test suite to verify that the destructors are working properly. We have pretty much none for C++ destructor semantics. By making the D destructor ABI the same as the C++ destructor ABI, we can have confidence that both are implemented correctly.
I hadn't thought of that. A good point. @ibuclaw is there someone you can ask why gdc does caller destruction? |
Well one argument against callee destruction is the following example with 2 temporaries: void callee(Dtor a, Dtor b) {}
void caller() { callee(makeDtor(), makeDtor()); } What we do is create 2 temporaries, and if |
It isn't. Maybe the example was confusing by using the same name everywhere. |
g++ certainly does caller destruction. |
Yes, and that is supported by this target hook. Did you mean for me to find out why g++ does caller destruction? I can certainly have a dig around and see what I can come up with. |
The obvious/benign reason is that it's just easier to represent lifetimes if the caller does destruction. Consider the following transformation sequence:
|
Yes, I think that would be valuable information. |
Thanks for the example. I think it does make the code easier to reason about, and as @kinke mentioned, there wouldn't be the complexity of what if a following argument threw an exception and the callee was never called, then the caller would still have to do the destruction. |
dee9209
to
9b84263
Compare
Getting this mysterious error:
__pfx is declared in expressionsem.d and goes down a different path when needsDtor is true. https://github.com/dlang/dmd/blame/master/compiler/src/dmd/expressionsem.d#L3498 |
@dkorpel do you have any insight on this problem? |
I don't know what causes the error yet, but with the help of dustmite I made this reduced test case: @safe:
SafeRefCounted map(return scope SafeRefCounted r)
{
return r;
}
struct SafeRefCounted
{
~this() scope { }
int* p;
}
SafeRefCounted listdir()
{
return SafeRefCounted().map;
} |
Wow, nice work, @dkorpel |
9b84263
to
9d394bf
Compare
@dkorpel your reduced test case made for an easy fix. Thanks! |
9d394bf
to
0368151
Compare
The latest problem is another __pfx one:
which is triggering:
in e2ir.d. Apparently the comma-expression is causing the failure. The trigger is compiling phobos/std/typecons.d:
The |
0368151
to
3447121
Compare
Trying dustmite on it ... |
The miracle of dustmite reduces it to:
|
42e89ec
to
49abd54
Compare
|
Another new one:
|
Blocked by dlang/phobos#8909 |
49abd54
to
e0ad491
Compare
e0ad491
to
d354cc7
Compare
… caller
This is a risky change!