www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Order of evaluation of a += a++;

reply "deadalnix" <deadalnix gmail.com> writes:
My understanding is that, as per spec, a += k is rewriten as :

a = cast(typeof(a)) (a + k);

Which make our example looks like :

a = cast(typeof(a)) (a + a++);
a = cast(typeof(a)) (a + { auto olda = a; a++; return olda; }());

The whole damn thing should end up with newa == 2 * olda . This 
is what SDC does. DMD tells me newa == 2 * olda + 1, as it 
evaluate a++ before that the value of a for the sum.

I though we decided to do everything LTR, but discussing with 
Andrei, it seems that Walter oppose this in some special cases. I 
think we should keep it consistent and do LTR all the way down.

Relevant bug report: 
https://issues.dlang.org/show_bug.cgi?id=14364
Mar 30 2015
next sibling parent reply "Temtaime" <temtaime gmail.com> writes:
So in SDC there's no difference between a += a and a += a++ ? 
Damn SDC!

I think DMD does it right. This is commented behavior. And nobody 
will change that: as there are many cries in bugzilla for example 
for phobos fixes « no, we cannot change it, it will break 
existing code ! », so like this nobody will change evaluation 
order.
Mar 30 2015
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Monday, 30 March 2015 at 20:31:10 UTC, Temtaime wrote:
 I think DMD does it right. This is commented behavior.
This is certainly not. SDC act as per spec. DMD act as per DMD. But maybe random evaluation order with incorrect documentation is what we want.
Mar 30 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/30/15 4:12 PM, deadalnix wrote:
 On Monday, 30 March 2015 at 20:31:10 UTC, Temtaime wrote:
 I think DMD does it right. This is commented behavior.
This is certainly not. SDC act as per spec. DMD act as per DMD. But maybe random evaluation order with incorrect documentation is what we want.
As an aside, we can change the reference to do what we think is right. The principled way is to go by two rules: 1. Use left-to-right wherever there's a choice 2. Express complex operations in terms of lowerings. So now we have to figure e1 += e2. By rule (2) we should go with this lowering: e1 += e2 -\> (ref a, b) { a = cast(typeof(a)) (a + b); }(e1, e2) By rule (1) we should evaluate e1 before e2 when passing into the lambda. To apply that to i += i++, recall this lowering: i++ -\> (ref x) { auto y = x; ++x; return y; }(i) which takes as to this lowering: i = i++ -\> (ref a, b) { a = cast(typeof(a)) (a + b); }( // lambda1 i, (ref x) { auto y = x; ++x; return y; }(i) // lambda2 ) So first we evaluate the address of i (trivial). Then we evaluate lambda2, which increments i before the += lambda1. So assuming i starts at 5, by the time lambda1 is called i is 6 and b has value 5. Then lambda1 executes, bringing i to 11. It seems that dmd is correct and we should fix the spec and sdc. Andrei
Mar 30 2015
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Tuesday, 31 March 2015 at 00:38:25 UTC, Andrei Alexandrescu 
wrote:
 On 3/30/15 4:12 PM, deadalnix wrote:
 On Monday, 30 March 2015 at 20:31:10 UTC, Temtaime wrote:
 I think DMD does it right. This is commented behavior.
This is certainly not. SDC act as per spec. DMD act as per DMD. But maybe random evaluation order with incorrect documentation is what we want.
As an aside, we can change the reference to do what we think is right. The principled way is to go by two rules: 1. Use left-to-right wherever there's a choice 2. Express complex operations in terms of lowerings. So now we have to figure e1 += e2. By rule (2) we should go with this lowering: e1 += e2 -\> (ref a, b) { a = cast(typeof(a)) (a + b); }(e1, e2) By rule (1) we should evaluate e1 before e2 when passing into the lambda. To apply that to i += i++, recall this lowering: i++ -\> (ref x) { auto y = x; ++x; return y; }(i) which takes as to this lowering: i = i++ -\> (ref a, b) { a = cast(typeof(a)) (a + b); }( // lambda1 i, (ref x) { auto y = x; ++x; return y; }(i) // lambda2 ) So first we evaluate the address of i (trivial). Then we evaluate lambda2, which increments i before the += lambda1. So assuming i starts at 5, by the time lambda1 is called i is 6 and b has value 5. Then lambda1 executes, bringing i to 11. It seems that dmd is correct and we should fix the spec and sdc. Andrei
Why are you doing the replacement top/down rather than bottom up ?
Mar 30 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/30/15 5:49 PM, deadalnix wrote:
 Why are you doing the replacement top/down rather than bottom up ?
What would be the alternative? -- Andrei
Mar 30 2015
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Tuesday, 31 March 2015 at 01:01:24 UTC, Andrei Alexandrescu 
wrote:
 On 3/30/15 5:49 PM, deadalnix wrote:
 Why are you doing the replacement top/down rather than bottom 
 up ?
What would be the alternative? -- Andrei
Doing the replacement bottom up : a = a++; a += { auto olda = a; a = a + 1; return olda; }(); a = cast(typeof(a)) (a + { auto olda = a; a = a + 1; return olda; }()); Other code transformation do not require a function call to take place, so why would this require one (which force eager evaluation of parameters) ? That is also inconsistent with LTR evaluation. On the other hand, that would make behavior consistent with what opOpAssign would give, so why not.
Mar 30 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/30/15 8:49 PM, deadalnix wrote:
 On Tuesday, 31 March 2015 at 01:01:24 UTC, Andrei Alexandrescu wrote:
 On 3/30/15 5:49 PM, deadalnix wrote:
 Why are you doing the replacement top/down rather than bottom up ?
What would be the alternative? -- Andrei
Doing the replacement bottom up : a = a++; a += { auto olda = a; a = a + 1; return olda; }(); a = cast(typeof(a)) (a + { auto olda = a; a = a + 1; return olda; }());
You need another lowering here because you evaluate a twice. Consider: import std.stdio; int a; ref int fun() { writeln("meh"); return a; } void main() { fun() = i++; }
 Other code transformation do not require a function call to take place,
 so why would this require one (which force eager evaluation of
 parameters) ?
Everything needs to be evaluated (and only once).
 That is also inconsistent with LTR evaluation.
I don't see how.
 On the other hand, that
 would make behavior consistent with what opOpAssign would give, so why not.
Whatever makes all sleep at night. Could you please submit a PR for the spec with the lowerings discussed? Andrei
Mar 30 2015
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Tuesday, 31 March 2015 at 06:20:22 UTC, Andrei Alexandrescu 
wrote:
 On 3/30/15 8:49 PM, deadalnix wrote:
 On Tuesday, 31 March 2015 at 01:01:24 UTC, Andrei Alexandrescu 
 wrote:
 On 3/30/15 5:49 PM, deadalnix wrote:
 Why are you doing the replacement top/down rather than 
 bottom up ?
What would be the alternative? -- Andrei
Doing the replacement bottom up : a = a++; a += { auto olda = a; a = a + 1; return olda; }(); a = cast(typeof(a)) (a + { auto olda = a; a = a + 1; return olda; }());
You need another lowering here because you evaluate a twice. Consider: import std.stdio; int a; ref int fun() { writeln("meh"); return a; } void main() { fun() = i++; }
Yeah, I had an int in mind. Make it { aPtr = &a; *aPtr = cast(typeof(a)) (*aPtr + { auto olda = *aPtr; *aPtr = a + 1; return olda; }()); return *aPtr; }()
 Other code transformation do not require a function call to 
 take place,
 so why would this require one (which force eager evaluation of
 parameters) ?
Everything needs to be evaluated (and only once).
 That is also inconsistent with LTR evaluation.
I don't see how.
a appear on the left of a++. a should be evaluated before a++. You propose that it isn't.
 On the other hand, that
 would make behavior consistent with what opOpAssign would 
 give, so why not.
Whatever makes all sleep at night. Could you please submit a PR for the spec with the lowerings discussed?
Mar 30 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/30/15 11:34 PM, deadalnix wrote:
 On Tuesday, 31 March 2015 at 06:20:22 UTC, Andrei Alexandrescu wrote:
 On 3/30/15 8:49 PM, deadalnix wrote:
 On Tuesday, 31 March 2015 at 01:01:24 UTC, Andrei Alexandrescu wrote:
 On 3/30/15 5:49 PM, deadalnix wrote:
 Why are you doing the replacement top/down rather than bottom up ?
What would be the alternative? -- Andrei
Doing the replacement bottom up : a = a++; a += { auto olda = a; a = a + 1; return olda; }(); a = cast(typeof(a)) (a + { auto olda = a; a = a + 1; return olda; }());
You need another lowering here because you evaluate a twice. Consider: import std.stdio; int a; ref int fun() { writeln("meh"); return a; } void main() { fun() = i++; }
Yeah, I had an int in mind. Make it { aPtr = &a; *aPtr = cast(typeof(a)) (*aPtr + { auto olda = *aPtr; *aPtr = a + 1; return olda; }()); return *aPtr; }()
So you propose the following lowering for e1 += e2: e1 += e2 -\> { auto __p = &(e1); *__p = cast(typeof(e1)) (*__p + e2); }() This looks contrived and it's no surprise is inconsistent with opOpAssign as you yourself noted. I prefer the lowering that identifies += to a function calls with two parameters: e1 += e2 -\> (ref a, b) { a = (cast(typeof(a)) (a + b); }(e1, e2)
 Other code transformation do not require a function call to take place,
 so why would this require one (which force eager evaluation of
 parameters) ?
Everything needs to be evaluated (and only once).
 That is also inconsistent with LTR evaluation.
I don't see how.
a appear on the left of a++. a should be evaluated before a++. You propose that it isn't.
I see, thanks. Guess we should change the spec no matter what. My money is on the lowering I mentioned. Andrei
Mar 30 2015
parent reply "Orvid King" <blah38621 gmail.com> writes:
On Tuesday, 31 March 2015 at 06:50:16 UTC, Andrei Alexandrescu 
wrote:
 On 3/30/15 11:34 PM, deadalnix wrote:
 On Tuesday, 31 March 2015 at 06:20:22 UTC, Andrei Alexandrescu 
 wrote:
 On 3/30/15 8:49 PM, deadalnix wrote:
 On Tuesday, 31 March 2015 at 01:01:24 UTC, Andrei 
 Alexandrescu wrote:
 On 3/30/15 5:49 PM, deadalnix wrote:
 Why are you doing the replacement top/down rather than 
 bottom up ?
What would be the alternative? -- Andrei
Doing the replacement bottom up : a = a++; a += { auto olda = a; a = a + 1; return olda; }(); a = cast(typeof(a)) (a + { auto olda = a; a = a + 1; return olda; }());
You need another lowering here because you evaluate a twice. Consider: import std.stdio; int a; ref int fun() { writeln("meh"); return a; } void main() { fun() = i++; }
Yeah, I had an int in mind. Make it { aPtr = &a; *aPtr = cast(typeof(a)) (*aPtr + { auto olda = *aPtr; *aPtr = a + 1; return olda; }()); return *aPtr; }()
So you propose the following lowering for e1 += e2: e1 += e2 -\> { auto __p = &(e1); *__p = cast(typeof(e1)) (*__p + e2); }() This looks contrived and it's no surprise is inconsistent with opOpAssign as you yourself noted. I prefer the lowering that identifies += to a function calls with two parameters: e1 += e2 -\> (ref a, b) { a = (cast(typeof(a)) (a + b); }(e1, e2)
 Other code transformation do not require a function call to 
 take place,
 so why would this require one (which force eager evaluation 
 of
 parameters) ?
Everything needs to be evaluated (and only once).
 That is also inconsistent with LTR evaluation.
I don't see how.
a appear on the left of a++. a should be evaluated before a++. You propose that it isn't.
I see, thanks. Guess we should change the spec no matter what. My money is on the lowering I mentioned. Andrei
I might be a bit confused here, but from what you're saying: b += a++; and b += ++a; Are doing the same thing in DMD?
Mar 31 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 3/31/15 12:18 AM, Orvid King wrote:
 I might be a bit confused here, but from what you're saying:
 b += a++;
 and
 b += ++a;

 Are doing the same thing in DMD?
No. Lowering of e1 += e2++ is: e1 += e2++ -\> (ref a, b) { a = (cast(typeof(a)) (a + b); }( e1, (ref a) { auto x = a; ++a; return x; }(e2) ) whereas lowering of e1 += ++e2 is: e1 += ++e2 -\> (ref a, b) { a = (cast(typeof(a)) (a + b); }( e1, (ref a) { ++a; return a; }(e2) ) Andrei
Mar 31 2015
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Tuesday, 31 March 2015 at 08:17:31 UTC, Andrei Alexandrescu 
wrote:
 On 3/31/15 12:18 AM, Orvid King wrote:
 I might be a bit confused here, but from what you're saying:
 b += a++;
 and
 b += ++a;

 Are doing the same thing in DMD?
No. Lowering of e1 += e2++ is: e1 += e2++ -\> (ref a, b) { a = (cast(typeof(a)) (a + b); }( e1, (ref a) { auto x = a; ++a; return x; }(e2) ) whereas lowering of e1 += ++e2 is: e1 += ++e2 -\> (ref a, b) { a = (cast(typeof(a)) (a + b); }( e1, (ref a) { ++a; return a; }(e2) ) Andrei
Fixed in SDC.
Apr 04 2015
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 4/4/15 7:54 PM, deadalnix wrote:
 On Tuesday, 31 March 2015 at 08:17:31 UTC, Andrei Alexandrescu wrote:
 On 3/31/15 12:18 AM, Orvid King wrote:
 I might be a bit confused here, but from what you're saying:
 b += a++;
 and
 b += ++a;

 Are doing the same thing in DMD?
No. Lowering of e1 += e2++ is: e1 += e2++ -\> (ref a, b) { a = (cast(typeof(a)) (a + b); }( e1, (ref a) { auto x = a; ++a; return x; }(e2) ) whereas lowering of e1 += ++e2 is: e1 += ++e2 -\> (ref a, b) { a = (cast(typeof(a)) (a + b); }( e1, (ref a) { ++a; return a; }(e2) ) Andrei
Fixed in SDC.
Awesome, thanks! -- Andrei
Apr 04 2015
prev sibling parent "w0rp" <devw0rp gmail.com> writes:
I'll say +1 for making everything LTR, as you say.
Mar 30 2015