Bug 7353 – NRVO not properly working with inferred return type
Status
RESOLVED
Resolution
FIXED
Severity
critical
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2012-01-23T07:27:00Z
Last change time
2012-02-17T19:33:33Z
Keywords
performance, pull
Assigned to
nobody
Creator
hoganmeier
Comments
Comment #0 by hoganmeier — 2012-01-23T07:27:36Z
import std.stdio;
struct S
{
static uint ci = 0;
uint i;
this(int x)
{
i = ci++;
writeln("new: ", i);
}
this(this)
{
i = ci++;
writeln("copy ", i);
}
~this()
{
writeln("del ", i);
}
S save1() // produces 2 copies in total
{
S s = this;
return s;
}
auto save2() // produces 3 copies in total
{
S s = this;
return s;
pragma(msg, typeof(return));
}
S save3()
{
return this;
}
}
void main()
{
{
S s = S(1);
S t = S(1);
t = s.save1();
}
writeln("-");
S.ci = 0;
{
S s = S(1);
S t = S(1);
t = s.save2();
}
writeln("-");
S.ci = 0;
{
S s = S(1);
S t = S(1);
t = s.save3();
}
}
$ dmd -run test.d
//or dmd -release -run test.d
//or dmd -release -O -run test.d
S
new: 0
new: 1
copy 2
del 1
del 2
del 0
-
new: 0
new: 1
copy 2
copy 3
del 2
del 1
del 3
del 0
-
new: 0
new: 1
copy 2
del 1
del 2
del 0
$ dmd -release -O -inline -run test.d
S
new: 0
new: 1
copy 2
del 1
del 2
del 0
-
new: 0
new: 1
copy 2
copy 3
del 2
del 1
del 3
del 0
-
new: 0
new: 1
del 1
del 0
del 0
Comment #1 by hoganmeier — 2012-01-23T07:32:24Z
Hmm that last one even looks like a wrong-code bug, 0 is deleted twice.
Comment #2 by k.hara.pg — 2012-02-15T06:39:48Z
Postblit is only in D2 spec, so this is not 'D1 & d2' issue.
I think save3() is inlining problem, then I've separated it as bug 7506.
Therefore I remove 'wrong-code' keyword.
----
Current compiler implementation disables nrvo with auto function.
I've post a pull to fix it.
https://github.com/D-Programming-Language/dmd/pull/722
Comment #3 by hoganmeier — 2012-02-15T08:48:44Z
You're right. The third one is an inlining problem.
But there's a different thing I'm now confused about. Why is postblit called after all?
It's assignment to t, not construction.
See the docs:
struct S { ... }
S s; // default construction of s
S t = s; // t is copy-constructed from s => this(this)
t = s; // t is assigned from s => opAssign
Comment #4 by k.hara.pg — 2012-02-15T09:16:26Z
(In reply to comment #3)
> You're right. The third one is an inlining problem.
>
> But there's a different thing I'm now confused about. Why is postblit called
> after all?
> It's assignment to t, not construction.
>
> See the docs:
> struct S { ... }
> S s; // default construction of s
> S t = s; // t is copy-constructed from s => this(this)
> t = s; // t is assigned from s => opAssign
The assignment to t does not call postblit. Because the built-in opAssign is implemented as 'swap and destroy' due to never create any unnecessary copy on assignment.
First of all, S has postblit, then dmd creates implicit built-in opAssing like follows:
ref S opAssign(S rhs) { // rhs receives rvalue, not lvalue
swap(this, rhs);
return this;
// rhs is destroyed the end of opAssign function scope
}
Next, the code sequence with explanation:
{
S s = S(1); // prints new: 0
S t = S(1); // prints new: 1
t = s.save1();
// is translated to t.opAssign(s.save1())
// inside save1():
S s = this; // prints copy 2
return s; // s is moved out by nrvo
// t.opAssign receives moved value, so is never copied
// inside t.opAssign():
// this (this.i==1) is swapped with rhs, and destroyed
// prints del 1
// s.i == 0
// t.i == 2
// destroyed local variables in reverse order
// prints del 2
// prints el 1
}
Last, the postblit call runs only once.
Comment #5 by hoganmeier — 2012-02-15T11:39:08Z
Ok, so in essence the postblit is the one called by 'S s = this;' in save1.
But what about save3?
Comment #6 by github-bugzilla — 2012-02-17T18:21:35Z