Jump to page: 1 2
Thread overview
Compiler could elide many more postblit constructor calls
Jun 29, 2013
TommiT
Jun 29, 2013
TommiT
Jun 30, 2013
Diggory
Jun 30, 2013
TommiT
Jun 30, 2013
anonymous
Jun 30, 2013
TommiT
Jun 30, 2013
anonymous
Jun 30, 2013
TommiT
Jun 30, 2013
Diggory
Jun 30, 2013
TommiT
Jun 30, 2013
TommiT
Jun 30, 2013
TommiT
Jul 01, 2013
TommiT
Jun 30, 2013
TommiT
Jun 30, 2013
TommiT
Jun 30, 2013
Diggory
Jul 03, 2013
TommiT
June 29, 2013
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
}
June 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).
June 30, 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:
>> [..]
>>
>> 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).

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.
June 30, 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:
>> 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).
>
> 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

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.

June 30, 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.
June 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
>>>> }
[...]
>>
>> 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

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.)
June 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:
>> 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
>>>>> }
> [...]
>>>
>>> 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
>
> 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.)

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).
June 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:
>> 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
>>>>>> }
>> [...]
>>>>
>>>> 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
>>
>> 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.)
>
> 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).

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?
June 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".
June 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.
« First   ‹ Prev
1 2