www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Attributes lost in TypeInfo, please advise

reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
Consider the following:

---
struct S
{
     ~this()  safe {}
}

void foo()  safe
{
     S s;
     // OK
}

void bar()  safe
{
     S s;
     destroy(s);
     // test.d(15): Error: safe function 'test.bar' cannot call 
system function 'object.destroy!(S).destroy'
}
---

`destroy` is used in algorithms using low-level operations like 
`emplace[Ref]`, and while `destroy` itself is a template and thus 
enjoys attribute inference, it calls the non-generic 
typeid(T).destroy function, which is unconditionally  system. 
This unsafety then propagates all the way up to high-level code 
that is otherwise inferred to be safe.

The `postblit` TypeInfo method, used from `emplace[Ref]`, has the 
same problem.

Is it possible to call the destructor or postblit constructor 
directly, and will they correctly destruct/copy recursively like 
TypeInfo.destroy/postblit do? If so, we should change `destroy` 
and `emplaceImpl` to use direct calls ASAP.
Feb 11 2015
next sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Thursday, 12 February 2015 at 04:08:23 UTC, Jakob Ovrum wrote:
 Is it possible to call the destructor or postblit constructor 
 directly
yes, they are available as obj.__dtor() and obj.__postblit(); But...
 and will they correctly destruct/copy recursively
No. extern(C) trusted void printf(const char*); struct Child { safe ~this() { printf("child dtor\n"); } } struct Parent { Child c; safe ~this() { printf("parent dtor\n"); } } void main() safe { Parent p; p.__dtor(); } parent dtor // this is the one from the p.__dtor // note it did NOT run child dtor parent dtor // the natural destruction from going out of scope child dtor // ...which also calls the child So you'd have to loop through all members in a custom destroy function and call them yourself. Then attribute inference should work.
Feb 11 2015
parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Thursday, 12 February 2015 at 04:18:06 UTC, Adam D. Ruppe 
wrote:
 On Thursday, 12 February 2015 at 04:08:23 UTC, Jakob Ovrum 
 wrote:
 Is it possible to call the destructor or postblit constructor 
 directly
yes, they are available as obj.__dtor() and obj.__postblit(); But...
 and will they correctly destruct/copy recursively
No.
Thanks.
 So you'd have to loop through all members in a custom destroy 
 function and call them yourself. Then attribute inference 
 should work.
I feared as much. I'll cook something up and send a PR for review.
Feb 11 2015
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 2/11/15 11:08 PM, Jakob Ovrum wrote:
 Consider the following:

 ---
 struct S
 {
      ~this()  safe {}
 }

 void foo()  safe
 {
      S s;
      // OK
 }

 void bar()  safe
 {
      S s;
      destroy(s);
      // test.d(15): Error: safe function 'test.bar' cannot call system
 function 'object.destroy!(S).destroy'
 }
 ---

 `destroy` is used in algorithms using low-level operations like
 `emplace[Ref]`, and while `destroy` itself is a template and thus enjoys
 attribute inference, it calls the non-generic typeid(T).destroy
 function, which is unconditionally  system. This unsafety then
 propagates all the way up to high-level code that is otherwise inferred
 to be safe.

 The `postblit` TypeInfo method, used from `emplace[Ref]`, has the same
 problem.

 Is it possible to call the destructor or postblit constructor directly,
 and will they correctly destruct/copy recursively like
 TypeInfo.destroy/postblit do? If so, we should change `destroy` and
 `emplaceImpl` to use direct calls ASAP.
The reason typeid.destroy is used is because it does what you think calling the destructor directly should do. As Adam pointed out, __dtor does not do it right. Note, there is a hidden function generated by the compiler, that TypeInfo.destroy maps to. There is no way to call it directly. There is a bug report that shows why we do it that way (recall that destroy was once named clear): https://issues.dlang.org/show_bug.cgi?id=5667 I think given the necessity of the above (which was not discussed or noticed in that bug report), we should add a way to call the true destructor properly in the compiler. -Steve
Feb 12 2015
next sibling parent "Benjamin Thaut" <code benjamin-thaut.de> writes:
On Thursday, 12 February 2015 at 12:59:39 UTC, Steven 
Schveighoffer wrote:
 I think given the necessity of the above (which was not 
 discussed or noticed in that bug report), we should add a way 
 to call the true destructor properly in the compiler.

 -Steve
Yes please. Its also going to genereate more optimal code. Calling the destructor through the TypeInfo leads to two unnecessary indirections. Kind Regards Benjamin Thaut
Feb 12 2015
prev sibling parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Thursday, 12 February 2015 at 12:59:39 UTC, Steven 
Schveighoffer wrote:
 I think given the necessity of the above (which was not 
 discussed or noticed in that bug report), we should add a way 
 to call the true destructor properly in the compiler.

 -Steve
I think these do the right thing with only marginal overhead: --- void destructRecurse(S)(ref S s) if (is(S == struct)) { static if (__traits(hasMember, S, "__dtor")) s.__dtor(); foreach_reverse (ref field; s.tupleof) { alias Field = typeof(field); static if (is(Field == struct) && hasElaborateDestructor!Field) destructRecurse(field); } } void postblitRecurse(S)(ref S s) if (is(S == struct)) { foreach (ref field; s.tupleof) { alias Field = typeof(field); static if (is(Field == struct) && hasElaborateCopyConstructor!Field) postblitRecurse(field); } static if (__traits(hasMember, S, "__postblit")) s.__postblit(); } --- I notice now it is missing proper handling of fixed-length arrays: I'll add that. Anything else missing? Efficiency-wise they should at least be a lot better than the status quo - two indirect calls. For absolutely optimal performance it relies on the inliner, but if it is demonstrated to be a problem compared to the compiler-generated solution, it could always generate optimal code with some hands-on string mixins :) I am aware that std.traits is not available in object_.d - the hasElaborate* templates are fairly simple and easy to reimplement.
Feb 12 2015
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 2/12/15 11:01 PM, Jakob Ovrum wrote:
 On Thursday, 12 February 2015 at 12:59:39 UTC, Steven Schveighoffer wrote:
 I think given the necessity of the above (which was not discussed or
 noticed in that bug report), we should add a way to call the true
 destructor properly in the compiler.
I think these do the right thing with only marginal overhead: --- void destructRecurse(S)(ref S s) if (is(S == struct)) { static if (__traits(hasMember, S, "__dtor")) s.__dtor(); foreach_reverse (ref field; s.tupleof) { alias Field = typeof(field); static if (is(Field == struct) && hasElaborateDestructor!Field) destructRecurse(field); } } void postblitRecurse(S)(ref S s) if (is(S == struct)) { foreach (ref field; s.tupleof) { alias Field = typeof(field); static if (is(Field == struct) && hasElaborateCopyConstructor!Field) postblitRecurse(field); } static if (__traits(hasMember, S, "__postblit")) s.__postblit(); } --- I notice now it is missing proper handling of fixed-length arrays: I'll add that. Anything else missing? Efficiency-wise they should at least be a lot better than the status quo - two indirect calls. For absolutely optimal performance it relies on the inliner, but if it is demonstrated to be a problem compared to the compiler-generated solution, it could always generate optimal code with some hands-on string mixins :) I am aware that std.traits is not available in object_.d - the hasElaborate* templates are fairly simple and easy to reimplement.
Thanks, but I wonder, aren't we simply duplicating what the compiler does? It seems kind of wasteful to have 2 identical functions. Plus, if any behavior changes, I'd rather have one place to change it. Last thing we need is subtle differences between builtin destruction and explicit destruction. I really think either the compiler should be redirected to call this function (ideal) or we should be able to call the compiler function directly. -Steve
Feb 13 2015