August 26, 2016
On Friday, 26 August 2016 at 08:44:54 UTC, kink wrote:
> On Friday, 26 August 2016 at 05:50:52 UTC, Basile B. wrote:
>> On Thursday, 25 August 2016 at 22:37:13 UTC, kinke wrote:
>>> On Thursday, 25 August 2016 at 18:15:47 UTC, Basile B. wrote:
>>> From my perspective, the problem with this example isn't missed optimization potential. It's the code itself. Why waste implementation efforts for such optimizations, if that would only reward people writing such ugly code with an equal performance to a more sane `2 * foo.foo()`? The latter is a) shorter, b) also faster with optimizations turned off and c) IMO simply clearer.
>>
>> You're too focused on the example itself (Let's find an non trivial example, but then the asm generated would be longer). The point you miss is that it just *illustrates* what should happend when many calls to a pure const function are occur in a single sub program.
>
> I know that it's just an illustration. But I surely don't like any function with repeated calls to this pure function. Why not have the developer code in a sensible style (cache that result once for that whole 'subprogram' manually) if performance is a concern? A compiler penalizing such bad coding style is absolutely fine by me.

To be honnest I would have expected another criticism against this optimization idea, which is that the aggregate can expose shared methods that could, this time, modify the state, invalidating the automatic cache produced by the optim for one thread, even if this scenario is not plausible without hacks (e.g casting away the "shared" part in the type of a variable that's used in the const pure function.
August 26, 2016
On Friday, 26 August 2016 at 09:30:52 UTC, Timon Gehr wrote:
> Better performance is better even when it is not the primary concern.
> It's not the compiler's business to judge coding style

It's free to choose not to implement complex optimizations just so that people get super duper performance for crappy code, especially with the limited manpower we got. Fixing severe DMD bugs (there are still enough of those) is 100x more important.

> // original code. not "bad".
>
> int foo(int x) pure{ ... }
>
> int bar(int x) pure{ return foo(x) + foo(5-x); }
>
> void main(){
>     writeln(bar(5));
> }
>
> // ==> inlining
>
> void main(){
>     writeln(foo(5)+foo(10-5));
> }
>
> // ==> constant folding, "bad" code
>
> void main(){
>     writeln(foo(5)+foo(5));
> }

Inlining and subsequent constant folding are only available if the callee isn't an external. For those cases, existing LLVM/GCC optimizations kick in and render at least this idea (automatic caching of pure function calls) obsolete (in most cases), see the LDC asm.
This is my main point. Higher-level, D-specific optimizations would be nice, but modern backends, coupled with optimized builds (`ldc2 -singleobj m1.d m2.d ...`) eat some of the ideas here for breakfast. So I'd focus on cases where LDC/GDC don't exploit optimization potential already.
August 26, 2016
On Thursday, 25 August 2016 at 14:42:28 UTC, Basile B. wrote:
> 
> I'll add
>
> * create temporaries based on the const function attribute.

Struct method constness (as in your example) does not mean that the return value is constant when calling it twice in a row. As pointed out by others, the function needs to be pure. Dlang pureness is not a strong enough guarantee.
For example, this is explicitly allowed by the spec:
```
pure int foo()
{
    debug writeln("in foo()"); // ok, impure code allowed in debug statement
    return 1;
}
```
That makes it illegal to transform `foo()+foo()` to `a=foo(); a+a`, at least in debug builds.

David discusses your proposed optimization, and why it cannot be done in general (!) on Dlang pure functions.
http://klickverbot.at/blog/2012/05/purity-in-d/

-Johan

August 26, 2016
On Friday, 26 August 2016 at 10:51:15 UTC, Johan Engelen wrote:
> On Thursday, 25 August 2016 at 14:42:28 UTC, Basile B. wrote:
>> 
>> I'll add
>>
>> * create temporaries based on the const function attribute.
>
> Struct method constness (as in your example) does not mean that the return value is constant when calling it twice in a row. As pointed out by others, the function needs to be pure. Dlang pureness is not a strong enough guarantee.
> For example, this is explicitly allowed by the spec:
> ```
> pure int foo()
> {
>     debug writeln("in foo()"); // ok, impure code allowed in debug statement
>     return 1;
> }
> ```
> That makes it illegal to transform `foo()+foo()` to `a=foo(); a+a`, at least in debug builds.
>
> David discusses your proposed optimization, and why it cannot be done in general (!) on Dlang pure functions.
> http://klickverbot.at/blog/2012/05/purity-in-d/
>
> -Johan

Here's an example that doesn't even need to use debug statements, and is perfectly legal.

class Test
{
    int n;

    void setN(int val) pure
    {
        n = val;
    }

    int getN() const pure
    {
        return n;
    }
}

import std.stdio;

void main()
{
    auto t = new Test();
    writeln(t.getN()); //Prints 0
    t.setN(1);
    writeln(t.getN()); //Prints 1
}
August 26, 2016
On Friday, 26 August 2016 at 14:03:13 UTC, Meta wrote:
> On Friday, 26 August 2016 at 10:51:15 UTC, Johan Engelen wrote:
>> On Thursday, 25 August 2016 at 14:42:28 UTC, Basile B. wrote:
>>> 
>>> I'll add
>>>
>>> * create temporaries based on the const function attribute.
>>
>> Struct method constness (as in your example) does not mean that the return value is constant when calling it twice in a row. As pointed out by others, the function needs to be pure. Dlang pureness is not a strong enough guarantee.
>> For example, this is explicitly allowed by the spec:
>> ```
>> pure int foo()
>> {
>>     debug writeln("in foo()"); // ok, impure code allowed in debug statement
>>     return 1;
>> }
>> ```
>> That makes it illegal to transform `foo()+foo()` to `a=foo(); a+a`, at least in debug builds.
>>
>> David discusses your proposed optimization, and why it cannot be done in general (!) on Dlang pure functions.
>> http://klickverbot.at/blog/2012/05/purity-in-d/
>>
>> -Johan
>
> Here's an example that doesn't even need to use debug statements, and is perfectly legal.
>
> class Test
> {
>     int n;
>
>     void setN(int val) pure
>     {
>         n = val;
>     }
>
>     int getN() const pure
>     {
>         return n;
>     }
> }
>
> import std.stdio;
>
> void main()
> {
>     auto t = new Test();
>     writeln(t.getN()); //Prints 0
>     t.setN(1);
>     writeln(t.getN()); //Prints 1
> }

That's another story. Of course the optimization pass for this should check that **ALL** the calls to Test in a sub program (or in this scope if you want) are const... Which is not the case here.
August 26, 2016
On Fri, 26 Aug 2016 10:32:55 +0000, kink wrote:
> Inlining and subsequent constant folding are only available if the callee isn't an external.

The optimizations being discussed are a result of purity guarantees that have nothing to do with inlining. The example showed constant folding as a way to get code that the compiler could obviously optimize (assuming it took advantage of purity) without that fact necessarily being obvious to the programmer.

These purity guarantees are available even if the compiler has no access to the source code of the pure function.

As a more concrete example, let's suppose I am dynamically linking to a compression library and compiling with a D interface file rather than full source. It has as one of its functions:

extern(D) size_t maxEncodedSize(size_t inputSize) pure;

I have a couple structs that happen to be the same size:

struct EntityID {
  ulong id;
  ulong family;
}
struct GPSCoordinates {
  double latitude, longitude;
}

I need to compress both of them and send them over the wire in one bundle, and I want to allocate a buffer in advance, so I write:

auto size =
    maxEncodedSize(EntityID.sizeof) +
    maxEncodedSize(GPSCoordinates.sizeof);
ubyte[] buf = new ubyte[size];

With pure, the compiler can rewrite that to:

ulong size = maxEncodedSize(16) << 1;
ubyte[] buf = new ubyte[size];

No inlining. No constant folding.
August 26, 2016
On Friday, 26 August 2016 at 14:12:24 UTC, Basile B. wrote:
> That's another story. Of course the optimization pass for this should check that **ALL** the calls to Test in a sub program (or in this scope if you want) are const... Which is not the case here.

My point is that getN is strongly pure but is not referentially transparent. Purity is broken in a strange way that you wouldn't expect, and you don't even have to use an escape hatch like debug.
August 26, 2016
On Friday, 26 August 2016 at 14:12:24 UTC, Basile B. wrote:
> On Friday, 26 August 2016 at 14:03:13 UTC, Meta wrote:
>> On Friday, 26 August 2016 at 10:51:15 UTC, Johan Engelen wrote:
>>> On Thursday, 25 August 2016 at 14:42:28 UTC, Basile B. wrote:
>>>> 
>>>> I'll add
>>>>
>>>> * create temporaries based on the const function attribute.
>>>
>>> Struct method constness (as in your example) does not mean that the return value is constant when calling it twice in a row. As pointed out by others, the function needs to be pure. Dlang pureness is not a strong enough guarantee.
>>> For example, this is explicitly allowed by the spec:
>>> ```
>>> pure int foo()
>>> {
>>>     debug writeln("in foo()"); // ok, impure code allowed in debug statement
>>>     return 1;
>>> }
>>> ```
>>> That makes it illegal to transform `foo()+foo()` to `a=foo(); a+a`, at least in debug builds.
>>>
>>> David discusses your proposed optimization, and why it cannot be done in general (!) on Dlang pure functions.
>>> http://klickverbot.at/blog/2012/05/purity-in-d/
>>>
>>> -Johan
>>
>> Here's an example that doesn't even need to use debug statements, and is perfectly legal.
>>
>> class Test
>> {
>>     int n;
>>
>>     void setN(int val) pure
>>     {
>>         n = val;
>>     }
>>
>>     int getN() const pure
>>     {
>>         return n;
>>     }
>> }
>>
>> import std.stdio;
>>
>> void main()
>> {
>>     auto t = new Test();
>>     writeln(t.getN()); //Prints 0
>>     t.setN(1);
>>     writeln(t.getN()); //Prints 1
>> }
>
> That's another story. Of course the optimization pass for this should check that **ALL** the calls to Test in a sub program (or in this scope if you want) are const... Which is not the case here.

void foo(Bar bar)
{
    foreach(i; 0 .. bar.length) // bar.length can be evaluated once
    {
       stuffA.a = bar[i].propertyA // bar[i] can be cached
       stuffB.b = bar[i].propertyB // to get propertyB
    }
}

with

interface Bar
{
    size_t length() pure const;
    Stuff opIndex(size_t) pure const;
}
August 26, 2016
On Friday, 26 August 2016 at 14:03:13 UTC, Meta wrote:
> On Friday, 26 August 2016 at 10:51:15 UTC, Johan Engelen wrote:
>> On Thursday, 25 August 2016 at 14:42:28 UTC, Basile B. wrote:
>>> 
>>> I'll add
>>>
>>> * create temporaries based on the const function attribute.
>>
>> Struct method constness (as in your example) does not mean that the return value is constant when calling it twice in a row. As pointed out by others, the function needs to be pure. Dlang pureness is not a strong enough guarantee.
>> For example, this is explicitly allowed by the spec:
>> ```
>> pure int foo()
>> {
>>     debug writeln("in foo()"); // ok, impure code allowed in debug statement
>>     return 1;
>> }
>> ```
>> That makes it illegal to transform `foo()+foo()` to `a=foo(); a+a`, at least in debug builds.
>>
>> David discusses your proposed optimization, and why it cannot be done in general (!) on Dlang pure functions.
>> http://klickverbot.at/blog/2012/05/purity-in-d/
>>
>> -Johan
>
> Here's an example that doesn't even need to use debug statements, and is perfectly legal.
>
> class Test
> {
>     int n;
>
>     void setN(int val) pure
>     {
>         n = val;
>     }
>
>     int getN() const pure
>     {
>         return n;
>     }
> }

getN() is not pure, simple as that (and an ideal compiler should complain in that case). A function is pure if it depends only of the state passed as parameter. If it touches memory that is set outside its scope it is NOT PURE!!!

>
> import std.stdio;
>
> void main()
> {
>     auto t = new Test();
>     writeln(t.getN()); //Prints 0
>     t.setN(1);
>     writeln(t.getN()); //Prints 1

It has to, as getN() is not pure.

> }

In case you didn't get it, getN() is not pure. Marking it as such is a BUG!.


August 26, 2016
On Friday, 26 August 2016 at 17:52:36 UTC, Meta wrote:
> On Friday, 26 August 2016 at 14:12:24 UTC, Basile B. wrote:
>> That's another story. Of course the optimization pass for this should check that **ALL** the calls to Test in a sub program (or in this scope if you want) are const... Which is not the case here.
>
> My point is that getN is strongly pure but is not referentially transparent. Purity is broken in a strange way that you wouldn't expect, and you don't even have to use an escape hatch like debug.

getN() is not pure at all, it refers to an object (in C term) outside of its scope not depending on its parameter. It is irrelevant if the outside object is a global variable or a member. Access to a memory defined outside of its scope breaks pureness.