digitalmars.D - Compiler could elide many more postblit constructor calls
- TommiT (25/25) Jun 29 2013 Disclaimer: The "discovery" I'm about to describe here seems so
- TommiT (10/27) Jun 29 2013 One important related detail:
- Diggory (4/36) Jun 29 2013 Unless the function is pure, this is only possible for immutable
- TommiT (20/58) Jun 30 2013 I'm not sure I follow. Are you saying...
- anonymous (16/50) Jun 30 2013 3) foo mutates the data through a mutable global.
- TommiT (5/56) Jun 30 2013 If this is indeed Diggory's point, it's not very pointy (sorry).
- anonymous (8/68) Jun 30 2013 void foo(const S s)
- TommiT (9/16) Jun 30 2013 Ok, I see your point now. The function needs to be pure to elide
- Diggory (10/14) Jun 30 2013 If the postblit is omitted, and the function or code it calls
- TommiT (5/11) Jun 30 2013 Good point. So, in order to elide postblit when a const variable
- TommiT (12/20) Jun 30 2013 A typo. It should be:
- TommiT (10/10) Jun 30 2013 Perhaps an interesting observation about this is that while in
- Diggory (6/16) Jun 30 2013 Precisely, although something that could improve performance even
- TommiT (2/2) Jul 03 2013 I posted an enhancement request for this:
Disclaimer: The "discovery" I'm about to describe here seems so obvious that I'm inclined to think that I've made some mistake. The sole purpose of postblit constructors is to provide value semantics to structs which have mutable indirection (variables which have only immutable indirection have value semantics implicitly). Variables that are const/immutable can't have mutable indirection. Therefore, when making a copy from a const/immutable variable to a const/immutable variable, there's no need to call the postblit constructor. Example: ---- struct S { int[] values; this(this) { values = values.dup; } } void foo(const S) { } void main() { const S s; foo(s); // No need to call postblit }
Jun 29 2013
On Saturday, 29 June 2013 at 13:47:36 UTC, TommiT wrote:[..] Example: ---- struct S { int[] values; this(this) { values = values.dup; } } void foo(const S) { } void main() { const S s; foo(s); // No need to call postblit }One important related detail: If the compiler decides to elide the postblit of S on the call to foo(s), then the destructor of S (if S happened to have one) should not be called when the call to foo exits and the argument passed to foo goes out of scope. The logic behind this is that when we omit the postblit on the argument that's passed by value, it is as-if we had passed the argument by const reference (except that the argument is considered local to foo, i.e. not returnable by reference and what not).
Jun 29 2013
On Saturday, 29 June 2013 at 17:57:33 UTC, TommiT wrote:On Saturday, 29 June 2013 at 13:47:36 UTC, TommiT wrote:Unless the function is pure, this is only possible for immutable parameters otherwise the original variable may be modified while inside the function even if it is passed by const.[..] Example: ---- struct S { int[] values; this(this) { values = values.dup; } } void foo(const S) { } void main() { const S s; foo(s); // No need to call postblit }One important related detail: If the compiler decides to elide the postblit of S on the call to foo(s), then the destructor of S (if S happened to have one) should not be called when the call to foo exits and the argument passed to foo goes out of scope. The logic behind this is that when we omit the postblit on the argument that's passed by value, it is as-if we had passed the argument by const reference (except that the argument is considered local to foo, i.e. not returnable by reference and what not).
Jun 29 2013
On Sunday, 30 June 2013 at 02:20:24 UTC, Diggory wrote:On Saturday, 29 June 2013 at 17:57:33 UTC, TommiT wrote:I'm not sure I follow. Are you saying... 1) the function foo could cast away const and modify s or 2) some other thread could modify s while foo is executing case 1: void foo(const S s) { S m = cast(S) s; s.values[0] = 42; } I'm not sure how this is in D, but I think in C++, casting away const and then modifying the variable is potentially undefined behaviour. I think the compiler should be free to assume that the programmer hasn't written code that has undefined behaviour. case 2: I don't think another thread could be modifying s, because it's not shared, i.e. it's a thread local variable. If the parameter to foo was "shared const S", then it could be modified by another thread, and thus the postblit could not be elided.On Saturday, 29 June 2013 at 13:47:36 UTC, TommiT wrote:Unless the function is pure, this is only possible for immutable parameters otherwise the original variable may be modified while inside the function even if it is passed by const.[..] Example: ---- struct S { int[] values; this(this) { values = values.dup; } } void foo(const S) { } void main() { const S s; foo(s); // No need to call postblit }One important related detail: If the compiler decides to elide the postblit of S on the call to foo(s), then the destructor of S (if S happened to have one) should not be called when the call to foo exits and the argument passed to foo goes out of scope. The logic behind this is that when we omit the postblit on the argument that's passed by value, it is as-if we had passed the argument by const reference (except that the argument is considered local to foo, i.e. not returnable by reference and what not).
Jun 30 2013
On Sunday, 30 June 2013 at 07:27:06 UTC, TommiT wrote:On Sunday, 30 June 2013 at 02:20:24 UTC, Diggory wrote:[...]On Saturday, 29 June 2013 at 17:57:33 UTC, TommiT wrote:On Saturday, 29 June 2013 at 13:47:36 UTC, TommiT wrote:[..] Example: ---- struct S { int[] values; this(this) { values = values.dup; } } void foo(const S) { } void main() { const S s; foo(s); // No need to call postblit }3) foo mutates the data through a mutable global. int[] data = [1, 2, 3]; void foo(const S) { data[0] = 42; } void main() { const S s = S(data); foo(s); } Pure functions can't access globals, so they're fine here. (I'm rather ignorant about that whole postblit thing, just trying to clarify Diggory's point as I understand it.)Unless the function is pure, this is only possible for immutable parameters otherwise the original variable may be modified while inside the function even if it is passed by const.I'm not sure I follow. Are you saying... 1) the function foo could cast away const and modify s or 2) some other thread could modify s while foo is executing
Jun 30 2013
On Sunday, 30 June 2013 at 08:16:44 UTC, anonymous wrote:On Sunday, 30 June 2013 at 07:27:06 UTC, TommiT wrote:If this is indeed Diggory's point, it's not very pointy (sorry). The compiler should be able to safely omit the postblit on 's' when it passes it to foo (and the possible destructor of foo's argument inside foo when foo exits).On Sunday, 30 June 2013 at 02:20:24 UTC, Diggory wrote:[...]On Saturday, 29 June 2013 at 17:57:33 UTC, TommiT wrote:On Saturday, 29 June 2013 at 13:47:36 UTC, TommiT wrote:[..] Example: ---- struct S { int[] values; this(this) { values = values.dup; } } void foo(const S) { } void main() { const S s; foo(s); // No need to call postblit }3) foo mutates the data through a mutable global. int[] data = [1, 2, 3]; void foo(const S) { data[0] = 42; } void main() { const S s = S(data); foo(s); } Pure functions can't access globals, so they're fine here. (I'm rather ignorant about that whole postblit thing, just trying to clarify Diggory's point as I understand it.)Unless the function is pure, this is only possible for immutable parameters otherwise the original variable may be modified while inside the function even if it is passed by const.I'm not sure I follow. Are you saying... 1) the function foo could cast away const and modify s or 2) some other thread could modify s while foo is executing
Jun 30 2013
On Sunday, 30 June 2013 at 08:34:20 UTC, TommiT wrote:On Sunday, 30 June 2013 at 08:16:44 UTC, anonymous wrote:void foo(const S s) { data[0] = 42; assert(s.values[0] == 1); } With the postblit, the assert holds. Without it, it would fail. Isn't that problematic?On Sunday, 30 June 2013 at 07:27:06 UTC, TommiT wrote:If this is indeed Diggory's point, it's not very pointy (sorry). The compiler should be able to safely omit the postblit on 's' when it passes it to foo (and the possible destructor of foo's argument inside foo when foo exits).On Sunday, 30 June 2013 at 02:20:24 UTC, Diggory wrote:[...]On Saturday, 29 June 2013 at 17:57:33 UTC, TommiT wrote:On Saturday, 29 June 2013 at 13:47:36 UTC, TommiT wrote:[..] Example: ---- struct S { int[] values; this(this) { values = values.dup; } } void foo(const S) { } void main() { const S s; foo(s); // No need to call postblit }3) foo mutates the data through a mutable global. int[] data = [1, 2, 3]; void foo(const S) { data[0] = 42; } void main() { const S s = S(data); foo(s); } Pure functions can't access globals, so they're fine here. (I'm rather ignorant about that whole postblit thing, just trying to clarify Diggory's point as I understand it.)Unless the function is pure, this is only possible for immutable parameters otherwise the original variable may be modified while inside the function even if it is passed by const.I'm not sure I follow. Are you saying... 1) the function foo could cast away const and modify s or 2) some other thread could modify s while foo is executing
Jun 30 2013
On Sunday, 30 June 2013 at 08:59:14 UTC, anonymous wrote:void foo(const S s) { data[0] = 42; assert(s.values[0] == 1); } With the postblit, the assert holds. Without it, it would fail. Isn't that problematic?Ok, I see your point now. The function needs to be pure to elide postblit on a copy from a const variable to a const variable. The function doesn't need to be pure to elide postblit on a copy from immutable variable to const/immutable variable. Although, if the compiler sees the function body, it could probably do some clever static analysis to see if it's possible that the const data is mutated, and elide the postblit if the answer is "no".
Jun 30 2013
If this is indeed Diggory's point, it's not very pointy (sorry). The compiler should be able to safely omit the postblit on 's' when it passes it to foo (and the possible destructor of foo's argument inside foo when foo exits).If the postblit is omitted, and the function or code it calls accesses the const variable through a global mutable reference then that function will be able to see those changes despite the parameter being passed by value. Without this optimisation it would not be able to see the changes. Also, the function needs to be strongly pure, not just weakly pure, otherwise the calling code could pass in both the const variable and a non-const reference to that variable. If the callee modifies the non-const reference it would see the changes to the const parameter which it shouldn't be able to do.
Jun 30 2013
On Sunday, 30 June 2013 at 09:24:28 UTC, Diggory wrote:[..] Also, the function needs to be strongly pure, not just weakly pure, otherwise the calling code could pass in both the const variable and a non-const reference to that variable. If the callee modifies the non-const reference it would see the changes to the const parameter which it shouldn't be able to do.Good point. So, in order to elide postblit when a const variable is passed by value as a const argument, the function would not only need to be pure, but also all of its parameters would have to be either const or immutable.
Jun 30 2013
On Sunday, 30 June 2013 at 09:38:49 UTC, TommiT wrote:[..] So, in order to elide postblit when a const variable is passed by value as a const argument, the function would not only need to be pure, but also all of its parameters would have to be either const or immutable.All along I've been saying "when const variable is passed to a function by const value...", but the postblit elision can be done also when a mutable variable is passed by const value (to a strongly pure function).
Jun 30 2013
To take the full advantage of this optimization, a change in lambda function parameter type inference might be necessary, which sadly would be a breaking change: struct S { int get() { return 11; } int get() const { return 22; } } void main() { S s; int n = (a) { return a.get(); }(s); // [1] writeln(n); // [2] } [1]: I think this lambda instantiates to something like: int __fun(S a) { return a.get(); } ...but it'd be better if it first attempted to instantiate to: int __fun(const S a) { return a.get(); } ...and resort to passing by mutable value only if the first attempt fails. [2]: Currently prints 11, but would print 22 if this breaking change were made.
Jun 30 2013
Yet one small observation: This optimization would mean that a lot of the use cases of "auto ref const MyType" parameters (the upcoming non-templated auto ref feature... although I don't know if that's the syntax for it) could be replaced by using "const MyType" parameters. Or, if you look at it from the other side of the coin: if you always took function arguments by "auto ref const MyType", there wouldn't be any functions to apply this optimization for.
Jul 01 2013
On Sunday, 30 June 2013 at 07:27:06 UTC, TommiT wrote:[..] case 1: void foo(const S s) { S m = cast(S) s; s.values[0] = 42; }A typo. It should be: case 1: void foo(const S s) { S m = cast(S) s; m.values[0] = 42; } On Sunday, 30 June 2013 at 02:20:24 UTC, Diggory wrote:Unless the function is pure, this is only possible for [..]I don't see what kind of a difference the pureness of foo would make in either of those two cases I wrote about in my previous post.
Jun 30 2013
Perhaps an interesting observation about this is that while in C++ const correctness literally never improves performance [1], in D (if these optimizations were implemented) const correctness could potentially increase performance considerably (if there are expensive postblits). Thus, functions should take arguments by const value rather than value, when the type of the parameter is either templated or has postblit. [1] Except with constant folding or when the compiler can put const data to rommable memory and then share it with multiple instances of the same program.
Jun 30 2013
On Sunday, 30 June 2013 at 10:21:12 UTC, TommiT wrote:Perhaps an interesting observation about this is that while in C++ const correctness literally never improves performance [1], in D (if these optimizations were implemented) const correctness could potentially increase performance considerably (if there are expensive postblits). Thus, functions should take arguments by const value rather than value, when the type of the parameter is either templated or has postblit. [1] Except with constant folding or when the compiler can put const data to rommable memory and then share it with multiple instances of the same program.Precisely, although something that could improve performance even more would be escape analysis. In this example, if the compiler can prove that there are no global/parameter mutable references to a given variable then it will be able to apply the optimisation regardless of purity.
Jun 30 2013
I posted an enhancement request for this: http://d.puremagic.com/issues/show_bug.cgi?id=10527
Jul 03 2013