Bug 16197 – Constructors/postblits and destructors don't match up for array initialisation
Status
RESOLVED
Resolution
FIXED
Severity
enhancement
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2016-06-23T15:08:00Z
Last change time
2017-08-07T13:16:07Z
Assigned to
nobody
Creator
eyal
Comments
Comment #0 by eyal — 2016-06-23T15:08:44Z
Post-blit not being called properly:
import std.stdio:writeln;
struct Elem {
int x = -1;
this(int x) { this.x = x; writeln("CTOR ", x); }
this(this) { writeln("POSTBLIT ", x); }
~this() { if (x!=-1) writeln("DTOR " , x); }
}
struct Ctr {
Elem[3] arr;
Elem[] slice() { return arr; }
Elem[3] arrVal() { return arr; }
}
void main() {
auto p = Ctr();
p.arr = [Elem(1), Elem(2), Elem(3)];
{
writeln("slice rval -> arr {");
Elem[3] _arr = p.slice;
writeln("}");
}
{
writeln("arr rval -> arr {");
Elem[3] _arr = p.arrVal();
writeln("}");
}
}
Prints out:
CTOR 1
CTOR 2
CTOR 3
slice rval -> arr {
}
DTOR 3
DTOR 2
DTOR 1
arr rval -> arr {
POSTBLIT 1
POSTBLIT 2
POSTBLIT 3
}
DTOR 3
DTOR 2
DTOR 1
DTOR 3
DTOR 2
DTOR 1
Note that there are more DTOR's than CTOR/POSTBLITS.
I believe dmd should maintain the same number of CTOR+POSTBLIT calls as DTOR calls, or all user invariants get broken.
Comment #1 by eyal — 2016-06-23T15:09:20Z
Specifically, the call to slice bit-blits the elements into the array but does not call postblit!
Comment #2 by eyal — 2016-06-23T15:38:44Z
Oops, this is an ldc bug, not a dmd bug.
Comment #3 by code — 2016-06-23T16:30:55Z
DMD from Git master (v2.072.0-devel-20e1c81) seems to call too many postblits/ctors, though, when initialising the Ctr instances and then assigning the Elem literals:
---
construct Ctr {
POSTBLIT -1 (7FFF5C07F598)
POSTBLIT -1 (7FFF5C07F59C)
POSTBLIT -1 (7FFF5C07F5A0)
}
assign arr {
CTOR 1 (7FFF5C07F5B4)
CTOR 2 (7FFF5C07F5C0)
CTOR 3 (7FFF5C07F5C4)
}
slice rval -> arr {
POSTBLIT 1 (7FFF5C07F5D0)
POSTBLIT 2 (7FFF5C07F5D4)
POSTBLIT 3 (7FFF5C07F5D8)
}
DTOR 3 (7FFF5C07F5D8)
DTOR 2 (7FFF5C07F5D4)
DTOR 1 (7FFF5C07F5D0)
arr rval -> arr {
POSTBLIT 1 (7FFF5C07F520)
POSTBLIT 2 (7FFF5C07F524)
POSTBLIT 3 (7FFF5C07F528)
}
DTOR 3 (7FFF5C07F5F0)
DTOR 2 (7FFF5C07F5EC)
DTOR 1 (7FFF5C07F5E8)
DTOR 3 (7FFF5C07F5A0)
DTOR 2 (7FFF5C07F59C)
DTOR 1 (7FFF5C07F598)
---
Note that the CTOR-created instances (7FFF5C07F5B4 and so on) are never destructed.
(The output is from a slightly modified test case that prints the struct address in parens:
---
import std.stdio:writeln;
struct Elem {
int x = -1;
this(int x) { this.x = x; writeln("CTOR ", x, " (", cast(void*)&this, ")"); }
this(this) { writeln("POSTBLIT ", x, " (", cast(void*)&this, ")"); }
~this() { if (x!=-1) writeln("DTOR " , x, " (", cast(void*)&this, ")"); }
}
struct Ctr {
Elem[3] arr;
Elem[] slice() { return arr; }
Elem[3] arrVal() { return arr; }
}
void main() {
writeln("construct Ctr {");
auto p = Ctr();
writeln("}");
writeln("assign arr {");
p.arr = [Elem(1), Elem(2), Elem(3)];
writeln("}");
{
writeln("slice rval -> arr {");
Elem[3] _arr = p.slice;
writeln("}");
}
{
writeln("arr rval -> arr {");
Elem[3] _arr = p.arrVal();
writeln("}");
}
}
---
)
Comment #4 by schveiguy — 2016-06-23T16:51:08Z
(In reply to David Nadlinger from comment #3)
> Note that the CTOR-created instances (7FFF5C07F5B4 and so on) are never
> destructed.
I don't think this is really a sign of anything, since the compiler is free to move around instances of the struct.
Comment #5 by code — 2016-06-23T16:53:19Z
(In reply to Steven Schveighoffer from comment #4)
> I don't think this is really a sign of anything, since the compiler is free
> to move around instances of the struct.
I'm not sure how to reconcile the number of ctor/postblit calls with the number of dtor calls, though.
Comment #6 by schveiguy — 2016-06-23T17:06:11Z
I'm not saying there's no issue here, just that the fact that you are not matching the addresses of the structs together doesn't necessarily mean anything bad. The compiler could be running the ctor on some piece of memory, and then just moving into the right spot without calling postblit or dtor (seems wasteful, but certainly legal).
BTW, things can likely get a lot clearer if we reduce the array size to 1. No reason to triple the output!
BTW, the postblit for the -1 values seems incorrect...
Comment #7 by maxsamukha — 2016-06-23T19:12:14Z
(In reply to Steven Schveighoffer from comment #6)
>
> BTW, the postblit for the -1 values seems incorrect...
Definitely incorrect (dmd):
struct Elem {
int x = -1;
this(this) { writeln("POSTBLIT ", x); }
~this() { writeln("DTOR " , x); }
}
struct Ctr {
Elem[1] arr;
}
void main() {
auto p = Ctr();
}
prints:
POSTBLIT
DTOR
Should be either two destructors or no postblit. For "Elem arr" instead of "Elem[1] arr", only the destructor is correctly called once.
Comment #8 by dfj1esp02 — 2016-06-24T09:04:00Z
(In reply to David Nadlinger from comment #5)
> I'm not sure how to reconcile the number of ctor/postblit calls with the
> number of dtor calls, though.
ctor+postblit=dtor indeed, the original test case correctly tracks dtors by printing value assigned to the x field.
Comment #9 by bugzilla — 2017-05-12T11:57:56Z
(In reply to Max Samukha from comment #7)
> Should be either two destructors or no postblit. For "Elem arr" instead of
> "Elem[1] arr", only the destructor is correctly called once.
What's happening here is that arrays are constructed by calling _d_arraysetctor(). That works by passing it an instance of the object Elem being constructed, which is the Elem.init object, and then copying it into arr[1]. The copy operation invokes the postblit.
The code doing the postblit is here:
https://github.com/dlang/druntime/blob/master/src/rt/arrayassign.d#L245
Then when it goes out of scope, the destructor is called.
That's why you see a single postblit and a single destructor.
The code is not incorrect, this is not a bug. However, it can be improved to skip the postblit for default initialization.
Therefore, I'm going to re-categorize this as an enhancement request.
Comment #10 by code — 2017-05-12T12:29:51Z
Could you please comment on the other test cases as well? I'm not convinced that they are equivalent (i.e. equivalently valid behaviour).
Comment #11 by bugzilla — 2017-05-12T13:26:13Z
Another effect you are seeing is default construction, which should account for more destructors and postblits than constructor calls.
The dmd output was correct (ignoring irrelevant POSTBLIT -1) - w.r.t number of ctors/postblits & dtors.
The ldc output is now correct in version:
LDC - the LLVM D compiler (1.1.0git-3139c87):
based on DMD v2.071.2 and LLVM 3.8.0
built with LDC - the LLVM D compiler (31eb0f)
So the bug can be closed as fixed afaic.
Comment #14 by bugzilla — 2017-05-12T16:47:15Z
(In reply to Eyal from comment #13)
> So the bug can be closed as fixed afaic.
Thanks! But I still think you'll like the PR I made for this, as it improves performance.
Comment #15 by github-bugzilla — 2017-05-22T06:44:40Z