D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 16197 - Constructors/postblits and destructors don't match up for array initialisation
Summary: Constructors/postblits and destructors don't match up for array initialisation
Status: RESOLVED FIXED
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P3 enhancement
Assignee: No Owner
URL: http://dlang.org/
Keywords:
Depends on:
Blocks:
 
Reported: 2016-06-23 15:08 UTC by Eyal
Modified: 2017-08-07 13:16 UTC (History)
6 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Eyal 2016-06-23 15:08:44 UTC
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 Eyal 2016-06-23 15:09:20 UTC
Specifically, the call to slice bit-blits the elements into the array but does not call postblit!
Comment 2 Eyal 2016-06-23 15:38:44 UTC
Oops, this is an ldc bug, not a dmd bug.
Comment 3 David Nadlinger 2016-06-23 16:30:55 UTC
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 Steven Schveighoffer 2016-06-23 16:51:08 UTC
(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 David Nadlinger 2016-06-23 16:53:19 UTC
(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 Steven Schveighoffer 2016-06-23 17:06:11 UTC
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 Max Samukha 2016-06-23 19:12:14 UTC
(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 anonymous4 2016-06-24 09:04:00 UTC
(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 Walter Bright 2017-05-12 11:57:56 UTC
(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 David Nadlinger 2017-05-12 12:29:51 UTC
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 Walter Bright 2017-05-12 13:26:13 UTC
Another effect you are seeing is default construction, which should account for more destructors and postblits than constructor calls.
Comment 12 Walter Bright 2017-05-12 13:27:45 UTC
https://github.com/dlang/dmd/pull/6774

David, if you feel there are further issues here, please file a separate issue with a minimal example.
Comment 13 Eyal 2017-05-12 16:01:22 UTC
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 Walter Bright 2017-05-12 16:47:15 UTC
(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 github-bugzilla 2017-05-22 06:44:40 UTC
Commits pushed to master at https://github.com/dlang/dmd

https://github.com/dlang/dmd/commit/10c4f4acb524940cf5747bf6406dd51a13e7fbd2
fix Issue 16197 - Constructors/postblits and destructors don't match up for array initialisation

https://github.com/dlang/dmd/commit/ca5a785e5ebe56a1019189801002ea214362a1f6
Merge pull request #6774 from WalterBright/fix16197

fix Issue 16197 - Constructors/postblits and destructors don't match …
merged-on-behalf-of: Daniel Murphy <yebblies@gmail.com>
Comment 16 github-bugzilla 2017-06-17 11:34:22 UTC
Commits pushed to stable at https://github.com/dlang/dmd

https://github.com/dlang/dmd/commit/10c4f4acb524940cf5747bf6406dd51a13e7fbd2
fix Issue 16197 - Constructors/postblits and destructors don't match up for array initialisation

https://github.com/dlang/dmd/commit/ca5a785e5ebe56a1019189801002ea214362a1f6
Merge pull request #6774 from WalterBright/fix16197
Comment 17 github-bugzilla 2017-08-07 13:16:07 UTC
Commits pushed to newCTFE at https://github.com/dlang/dmd

https://github.com/dlang/dmd/commit/10c4f4acb524940cf5747bf6406dd51a13e7fbd2
fix Issue 16197 - Constructors/postblits and destructors don't match up for array initialisation

https://github.com/dlang/dmd/commit/ca5a785e5ebe56a1019189801002ea214362a1f6
Merge pull request #6774 from WalterBright/fix16197