-
-
Notifications
You must be signed in to change notification settings - Fork 411
Add __delete for migration from delete #2037
Conversation
|
Thanks for your pull request, @wilzbach! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. |
|
|
||
| $(CONSOLE | ||
| sed "s/delete \(.*\);/__delete(\1);/" -i **/*.d | ||
| ) |
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 suggest adding something to the effect that this is intended to only be used as a last resort when destroy is not an option. At least that's my understanding: We want to migrate users to destroy.
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.
Potential verbage: "Users should prefer object.destroy to explicitly finalize objects, and only resort to __delete when object.destroy would not be a feasible option."
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.
The sed command could be a bit refined: s/\Wdelete[ \t]+\(.*\);/__delete(\1);/g
src/core/memory.d
Outdated
| This function has been added to provide an easy transition from `delete`. | ||
| It performs the same functionality as `delete`. | ||
| $(REF destroy, object) is `@safe` and should be used if possible. |
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.
Again, I think this should have some information explaining that users should first look to destroy and only to __delete when destroy is not an option.
src/foo.d
Outdated
| @@ -0,0 +1,151 @@ | |||
| import std.stdio; | |||
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.
What is this file (src/foo.d) doing in this commit?
|
Looks like you committed an extra file by mistake |
|
What advantages does this have over the (poorly named) destroy? |
|
Still, my understanding was that (years ago when delete was first put for deprecation) destroy was added as a migration path - by Andrei himself I believe. Obviously that function has failed spectacularly if this is needed. 🙄 |
A bit of history I could dug up, but it seems that |
| memory. | ||
| If the garbage collector was not used to allocate the memory for | ||
| the instance, undefined behavior will result. | ||
| ) |
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.
The implementation just sets the pointer to null. I don't think that initiates an immediate call to the GC to free the memory.
src/core/memory.d
Outdated
| a.test = "exists"; | ||
| auto b = &a; | ||
| auto c = &b; | ||
| delete c; |
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.
Shouldn't this be __delete?
src/core/memory.d
Outdated
| auto b = a; | ||
| __delete(b); | ||
| assert(b is null); | ||
| assert(a == [1, 2, 3]); |
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.
If I'm understanding correctly, you are accessing through a after b was __deleteed. According to the documentation comments, that's undefined behavior.
src/core/memory.d
Outdated
| } | ||
| else | ||
| { | ||
| static assert(0, "It is not possible to delete: " ~ T.stringof); |
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.
"It is not possible to delete`" ~ T.stringof ~ "`"
231 is titled "Rename clear to destroy". So it could always be my memory that is incorrect. :-) |
|
I am a bit surprised thought that Andrei wants this Personally, I think that anyone looking to do something like this should not be using GC-allocated memory, but it's not like we're completely against letting folks risk blowing their own feet off - we just don't want it to be easy to do that by default. |
changelog/core-memory-__delete.dd
Outdated
| `core.memory.__delete` has been added | ||
|
|
||
| $(REF __delete, core, memory) allows easy migration from the deprecated `delete`. | ||
| `__delete` behaves exactly like `delete` and will call the class dtor immediately |
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.
delete/__delete apply to structs and arrays as well, so please update the documentation
|
|
||
| $(CONSOLE | ||
| sed "s/delete \(.*\);/__delete(\1);/" -i **/*.d | ||
| ) |
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.
The sed command could be a bit refined: s/\Wdelete[ \t]+\(.*\);/__delete(\1);/g
src/core/memory.d
Outdated
| } | ||
| else static if (is(T == struct)) | ||
| { | ||
| _d_delstruct(cast(void**) &t, typeid(T)); |
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.
Wait, this is incorrect. __delete should be only applicable to pointers to struct. Let's not innovate here.
src/core/memory.d
Outdated
| } | ||
| else static if (is(T == U*, U)) | ||
| { | ||
| t = null; |
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.
this should free memory
src/core/memory.d
Outdated
| } | ||
| else static if (is(T : E[], E)) | ||
| { | ||
| t = null; |
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.
this should free memory
Thanks for the quip. The olden idea is to keep the safer |
915aeab to
444ab75
Compare
|
Alrighty I went over the implementation again and it now matches with DMD 1:1 -> https://github.com/dlang/dmd/blob/v2.078.0/src/dmd/e2ir.d#L3886 The only thing that I couldn't figure out is how to detect at compile-time whether |
changelog/core-memory-__delete.dd
Outdated
| `core.memory.__delete` has been added | ||
|
|
||
| $(REF __delete, core, memory) allows easy migration from the deprecated `delete`. | ||
| `__delete` behaves exactly like $(LINK2 $(ROOT_DIR)spec/expression.html#delete_expressions, `delete`): |
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.
One of the procedures we need to perform to deprecate and eventually remove delete is to remove all references to it in the spec (see dlang/dlang.org#1941). I suggest removing the link to the spec.
src/core/memory.d
Outdated
| there is a destructor for that class, the destructor | ||
| is called for that object instance. | ||
| ) | ||
| $(LI Otherwise, the garbage collector is called to immediately free the |
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.
This "otherwise" is ambiguous because it's applied to a conjunction. Does it refer to the fact that t is a class object reference, to the fact there is a destructor, or to the conjunction? (It applies to the second clause only). I'll make an edit to clarify.
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.
FYI: That was taken 1:1 from the spec ;-)
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.
@wilzbach @WalterBright and I are at POPL 2018 and one thing that became abundantly clear is we must overhaul the spec making it radically more precise. More on that later.
|
@wilzbach I just sent a patch to the doc - we should reuse |
src/core/memory.d
Outdated
| $(UL | ||
| $(LI | ||
| Calls `.object.destroy(*x)` (if `x` is a pointer) or `.object.destroy(x)` | ||
| (otherwise) to destroy the referred entity. |
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.
That's strictly speaking not true for class/interface objects and raw memory:
Exact behavior
- If
xis a class or interface object ->rt_finalize
Lines 163 to 167 in 098a08a
| else | |
| { | |
| rt_finalize(cast(void*) *p); | |
| } | |
| GC.free(cast(void*) *p); |
Of course, object.destroy does call rt_finalize:
Lines 2887 to 2896 in 098a08a
| void destroy(T)(T obj) if (is(T == class)) | |
| { | |
| rt_finalize(cast(void*)obj); | |
| } | |
| /// ditto | |
| void destroy(T)(T obj) if (is(T == interface)) | |
| { | |
| destroy(cast(Object)obj); | |
| } |
FTR rt_finalize is deprecated, but rt_finalize2 does call a few interesting things:
Lines 1382 to 1419 in 098a08a
| debug(PRINTF) printf("rt_finalize2(p = %p)\n", p); | |
| auto ppv = cast(void**) p; | |
| if(!p || !*ppv) | |
| return; | |
| auto pc = cast(ClassInfo*) *ppv; | |
| try | |
| { | |
| if (det || collectHandler is null || collectHandler(cast(Object) p)) | |
| { | |
| auto c = *pc; | |
| do | |
| { | |
| if (c.destructor) | |
| (cast(fp_t) c.destructor)(cast(Object) p); // call destructor | |
| } | |
| while ((c = c.base) !is null); | |
| } | |
| if (ppv[1]) // if monitor is not null | |
| _d_monitordelete(cast(Object) p, det); | |
| if (resetMemory) | |
| { | |
| auto w = (*pc).initializer; | |
| p[0 .. w.length] = w[]; | |
| } | |
| } | |
| catch (Exception e) | |
| { | |
| import core.exception : onFinalizeError; | |
| onFinalizeError(*pc, e); | |
| } | |
| finally | |
| { | |
| *ppv = null; // zero vptr even if `resetMemory` is false | |
| } |
- If
xis a reference to a struct ->Typeinfo_Struct.destroy:
Lines 177 to 183 in 098a08a
| extern (C) void _d_delstruct(void** p, TypeInfo_Struct inf) | |
| { | |
| if (*p) | |
| { | |
| debug(PRINTF) printf("_d_delstruct(%p, %p)\n", *p, cast(void*)inf); | |
| inf.destroy(*p); |
Here it's all about calling the dtor:
Lines 1194 to 1199 in 098a08a
| if (xdtor) | |
| { | |
| if (m_flags & StructFlags.isDynamicType) | |
| (*xdtorti)(p, this); | |
| else | |
| (*xdtor)(p); |
- If
xis a reference to an arrays -> for all structs within:Typeinfo_Struct.destroy:
Line 1169 in 098a08a
| finalize_array(start, length, ti); |
Lines 1347 to 1356 in 098a08a
| void finalize_array(void* p, size_t size, const TypeInfo_Struct si) | |
| { | |
| // Due to the fact that the delete operator calls destructors | |
| // for arrays from the last element to the first, we maintain | |
| // compatibility here by doing the same. | |
| auto tsize = si.tsize; | |
| for (auto curP = p + size - tsize; curP >= p; curP -= tsize) | |
| { | |
| // call destructor | |
| si.destroy(curP); |
- Otherwise only
GC.freeis called.
src/core/memory.d
Outdated
| Frees the memory allocated for `x`. If `x` is a reference to a class | ||
| or interface, the memory allocated for the underlying instance is freed. If `x` is | ||
| a pointer, the memory allocated for the pointed-to object is freed. If `x` is a | ||
| built-in array or associative array, the memory allocated for the array is freed. |
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.
Associative arrays aren't allowed: https://run.dlang.io/is/CtfP8f
src/core/memory.d
Outdated
| ) | ||
| Params: | ||
| t = aggregate object that should be destroyed |
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.
Reminder to myself: I will rename this to x then.
|
From my memory:
IMO, the |
Nice (I just removed your trailing white-space in case you were wondering why I force-pushed your commit). |
|
@wilzbach yah I wonder if we could submit a github issue - would be great if their editor supported trailing space elimination |
|
@wilzbach agreed about the discrepancies - in this case we must work backwards: |
https://github.com/isaacs/github
What do you suggest? Do you want to change I would simply change the wording to sth. like:
|
|
Yes, |
src/core/memory.d
Outdated
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `typeid(*x).destroy(x)` (if `x` is pointer to a struct) to destroy the referred entity. | ||
| Arrays of structs call `typeid(*x).destroy(x)` for each element. |
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.
This could also state: Arrays of structs call typeid(x).destroy(&x) for each element (that's what the implementation does below).
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 think it should be .object.destroy(e) for each element e in the array.
| assert(a is null); | ||
| } | ||
|
|
||
| // __delete returns with no effect if x is null |
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.
Is there a simple way to add a test this for "no effect"?
src/core/memory.d
Outdated
| $(UL | ||
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `typeid(*x).destroy(x)` (if `x` is pointer to a struct) to destroy the referred entity. |
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.
Here it should just be .object.destroy(*x), no? It's faster and better documented.
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.
Are you sure about object.destroy being faster?
TypeInfo_Struct.destroy:
Lines 1192 to 1201 in 18be8bf
| final override void destroy(void* p) const | |
| { | |
| if (xdtor) | |
| { | |
| if (m_flags & StructFlags.isDynamicType) | |
| (*xdtorti)(p, this); | |
| else | |
| (*xdtor)(p); | |
| } | |
| } |
object.destroy:
Lines 2954 to 2965 in 18be8bf
| void destroy(T)(ref T obj) if (is(T == struct)) | |
| { | |
| _destructRecurse(obj); | |
| () @trusted { | |
| auto buf = (cast(ubyte*) &obj)[0 .. T.sizeof]; | |
| auto init = cast(ubyte[])typeid(T).initializer(); | |
| if (init.ptr is null) // null ptr means initialize to 0s | |
| buf[] = 0; | |
| else | |
| buf[] = init[]; | |
| } (); | |
| } |
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.
The former is an indirect call and the second is a direct call. Truth be told, destroy does more work because it reinitializes the object. In the case of __delete we don't care to reinitialize the object because we just wrote in the documentation that access after __delete is undefined behavior.
So all in all the best way to go is to write in the documentation that we call destroy and actually call _destructRecurse. That covers all defined cases and is fast.
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.
_destructRecurse is private and in object - so we can't even do package(core).
src/core/memory.d
Outdated
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `typeid(*x).destroy(x)` (if `x` is pointer to a struct) to destroy the referred entity. | ||
| Arrays of structs call `typeid(*x).destroy(x)` for each element. |
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 think it should be .object.destroy(e) for each element e in the array.
src/core/memory.d
Outdated
| // See also: https://github.com/dlang/dmd/blob/v2.078.0/src/dmd/e2ir.d#L3886 | ||
| static if (is(T == interface)) | ||
| { | ||
| (cast(Object) x).destroy; |
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 think this should do what the spec says it does: .object.destroy(cast(Object) x); This is a pedantic point because Object does not define a method destroy.
src/core/memory.d
Outdated
| } | ||
| else static if (is(T == class)) | ||
| { | ||
| (cast(Object) x).destroy; |
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.
Same here, .object.destroy(cast(Object) x); would clarify lookup
src/core/memory.d
Outdated
| else static if (is(T == U*, U)) | ||
| { | ||
| static if (is(U == struct)) | ||
| typeid(U).destroy(x); |
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.
As mentioned, I think this is less efficient - it entails an indirect call instead of simply calling the destructor (if defined) or doing nothing (if not). I suggest: .object.destroy(*x)
src/core/memory.d
Outdated
| static if (is(E == struct)) | ||
| { | ||
| foreach (ref e; x) | ||
| typeid(E).destroy(&e); |
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.
same here
|
|
||
| static if (is(T == interface) || is(T == class) || is(T == U2*, U2) || is(T : E2[], E2)) | ||
| { | ||
| GC.free(&x); |
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'm not sure about this in the case of classes and interfaces. For those shouldn't the call be GC.free(cast(void*) x)?
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.
Added tests for GC.free
src/core/memory.d
Outdated
| $(UL | ||
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `(*x).__xdtor()` (if `x` is pointer to a struct and a custom destructor exists) to destroy the referred entity. |
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.
That's exactly what we do, but I wasn't whether there's a more elegant way to put it - __xdtor is a bit ugly, but it's still better than ".object.destroy(*x), but doesn't initialize the struct to T.init"
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.
People cannot be expected to know what __xdtor does, but they do know what a destructor is. So the text should be:
"Calls the destructor ~this() for the object referred to by x (if x is a class or interface reference) or for the object pointed to by x (if x is a pointer to a struct). If no destructor is defined, this step has no effect."
|
So are we ready to 🚀 here? |
andralex
left a comment
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.
A few more nits to look at.
src/core/memory.d
Outdated
| pragma(mangle, "free") void fakePureFree(void* ptr); | ||
| } | ||
|
|
||
| extern(C) private @system nothrow |
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.
they are @nogc as well
src/core/memory.d
Outdated
| The `delete` keyword allowed to free GC-allocated memory. | ||
| As this is inherently not `@safe`, it has been deprecated. | ||
| This function has been added to provide an easy transition from `delete`. | ||
| It performs the same functionality as `delete`. |
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.
Instead of assuming people know delete and defining __delete in terms of it, describe __delete as "destroy then deallocate" and at the very end "see also the deprecated keyword" etc.
src/core/memory.d
Outdated
| $(UL | ||
| $(LI | ||
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `(*x).__xdtor()` (if `x` is pointer to a struct and a custom destructor exists) to destroy the referred entity. |
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.
People cannot be expected to know what __xdtor does, but they do know what a destructor is. So the text should be:
"Calls the destructor ~this() for the object referred to by x (if x is a class or interface reference) or for the object pointed to by x (if x is a pointer to a struct). If no destructor is defined, this step has no effect."
| Calls `.object.destroy(x)` (if `x` is a class or interface object) or | ||
| `(*x).__xdtor()` (if `x` is pointer to a struct and a custom destructor exists) to destroy the referred entity. | ||
| Arrays of structs with a custom destructor call `x.__xdtor()` for each element in the array. | ||
| ) |
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.
"Arrays of structs call the destructor, if defined, for each element in the array."
src/core/memory.d
Outdated
| void __delete(T)(ref T x) @system | ||
| { | ||
| static void _destructRecurse(S)(ref S s) | ||
| if (is(S == struct)) |
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.
unindent
| { | ||
| static if (__traits(hasMember, S, "__xdtor") && | ||
| // Bugzilla 14746: Check that it's the exact member of S. | ||
| __traits(isSame, S, __traits(parent, s.__xdtor))) |
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 see the bugzilla is fixed, why do we need this?
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.
Because fixing 14746, added this to _destructRecurse: 1a2290b
andralex
left a comment
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.
ping @wilzbach
a675d5c to
57cbf25
Compare
Sorry. Pong. Addressed the final nits & squashed everything into one commit. |
See also:
I didn't add any tests for Class deallocators
as they have been deprecated since D2 and we call the existing functions.
CC @JinShil