www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Order of evaluation - aka hidden undefined behaviours.

reply "Iain Buclaw" <ibuclaw ubuntu.com> writes:
Pop quiz!

Analyse this code:
----
string abc;

float[] A()
{
     abc ~= "A";
     return [];
}

float[] B()
{
     abc ~= "B";
     return [];
}

float[] C()
{
     abc ~= "C";
     return [];
}

void main()
{
     A()[] = B()[] + C()[];
     assert(abc == "???");
}
----

Without cheating, I invite people to have a good guess what 'abc' 
is equal to, but just to narrow it down.

1)  It isn't "ABC".
2)  On x86/x86_64, it isn't "ACB".
3)  On everything else, it's the reverse of what you'd expect on 
x86/x86_64.

The problem here is that the array operation A[] = B[] + C[] gets 
transformed into an extern(C) call.  And because there's no 
strict rules in place over the order of which it's parameters are 
evaluated, it could go either way (LTR, or RTL).

Serious note: This test is bogus as this and similar other 
failing tests on non-x86 platforms are not at all obvious to the 
users who get issues. So what are we to do about it?


Regards,
Iain.
Sep 25 2012
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/25/12 6:58 PM, Iain Buclaw wrote:
[snip]
 Serious note: This test is bogus as this and similar other failing tests
 on non-x86 platforms are not at all obvious to the users who get issues.
 So what are we to do about it?

The right answer is to have the front-end rewrite such expressions in a form that enforces correct order of operations. Andrei
Sep 25 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09/26/2012 12:58 AM, Iain Buclaw wrote:
 Pop quiz!

 Analyse this code:
 ----
 string abc;

 float[] A()
 {
      abc ~= "A";
      return [];
 }

 float[] B()
 {
      abc ~= "B";
      return [];
 }

 float[] C()
 {
      abc ~= "C";
      return [];
 }

 void main()
 {
      A()[] = B()[] + C()[];
      assert(abc == "???");
 }
 ----

 Without cheating, I invite people to have a good guess what 'abc' is
 equal to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect on
 x86/x86_64.

 The problem here is that the array operation A[] = B[] + C[] gets
 transformed into an extern(C) call.  And because there's no strict rules
 in place over the order of which it's parameters are evaluated, it could
 go either way (LTR, or RTL).

 Serious note: This test is bogus as this and similar other failing tests
 on non-x86 platforms are not at all obvious to the users who get issues.
 So what are we to do about it?


 Regards,
 Iain.

As far as my understanding of the issue goes, the correct answer is "ABC", and compilers that do not produce "ABC" are buggy. The solution is to transform the code to code that evaluates the arguments to the extern(C) call in the correct order and eg. stores them in temporaries before passing them on.
Sep 25 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 09/26/2012 01:31 AM, H. S. Teoh wrote:
 On Wed, Sep 26, 2012 at 01:10:00AM +0200, Timon Gehr wrote:
 On 09/26/2012 12:58 AM, Iain Buclaw wrote:

 string abc;

 float[] A()
 {
      abc ~= "A";
      return [];
 }

 float[] B()
 {
      abc ~= "B";
      return [];
 }

 float[] C()
 {
      abc ~= "C";
      return [];
 }

 void main()
 {
      A()[] = B()[] + C()[];
      assert(abc == "???");
 }
 ----

 Without cheating, I invite people to have a good guess what 'abc' is
 equal to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect on
 x86/x86_64.

 The problem here is that the array operation A[] = B[] + C[] gets
 transformed into an extern(C) call.  And because there's no strict rules
 in place over the order of which it's parameters are evaluated, it could
 go either way (LTR, or RTL).

 Serious note: This test is bogus as this and similar other failing tests
 on non-x86 platforms are not at all obvious to the users who get issues.
 So what are we to do about it?


 As far as my understanding of the issue goes, the correct answer is
 "ABC", and compilers that do not produce "ABC" are buggy.

 The solution is to transform the code to code that evaluates the
 arguments to the extern(C) call in the correct order and eg. stores
 them in temporaries before passing them on.

Shouldn't it be BCA? I'd expect the assignment operation to take place last, so the corresponding expression should be evaluated last. T

There are 3 'corresponding' side-effecting expressions. I expect left-to right, top to bottom evaluation. If BCA is desired it is possible to do auto b = B(), c = C(); A()[] = b[] + c[]; I think having a simple consistent rule for evaluation order is desirable.
Sep 25 2012
prev sibling parent reply Don Clugston <dac nospam.com> writes:
On 26/09/12 01:31, H. S. Teoh wrote:
 On Wed, Sep 26, 2012 at 01:10:00AM +0200, Timon Gehr wrote:
 On 09/26/2012 12:58 AM, Iain Buclaw wrote:

 string abc;

 float[] A()
 {
      abc ~= "A";
      return [];
 }

 float[] B()
 {
      abc ~= "B";
      return [];
 }

 float[] C()
 {
      abc ~= "C";
      return [];
 }

 void main()
 {
      A()[] = B()[] + C()[];
      assert(abc == "???");
 }
 ----

 Without cheating, I invite people to have a good guess what 'abc' is
 equal to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect on
 x86/x86_64.

 The problem here is that the array operation A[] = B[] + C[] gets
 transformed into an extern(C) call.  And because there's no strict rules
 in place over the order of which it's parameters are evaluated, it could
 go either way (LTR, or RTL).

 Serious note: This test is bogus as this and similar other failing tests
 on non-x86 platforms are not at all obvious to the users who get issues.
 So what are we to do about it?


 As far as my understanding of the issue goes, the correct answer is
 "ABC", and compilers that do not produce "ABC" are buggy.

 The solution is to transform the code to code that evaluates the
 arguments to the extern(C) call in the correct order and eg. stores
 them in temporaries before passing them on.

Shouldn't it be BCA? I'd expect the assignment operation to take place last, so the corresponding expression should be evaluated last.

I expected "BCA" too. Left associative expressions evaluated left-to-right, right associative expressions evaluated right-to-left.
Sep 26 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 09/26/2012 12:13 PM, Don Clugston wrote:
 On 26/09/12 01:31, H. S. Teoh wrote:
 On Wed, Sep 26, 2012 at 01:10:00AM +0200, Timon Gehr wrote:
 On 09/26/2012 12:58 AM, Iain Buclaw wrote:

 string abc;

 float[] A()
 {
      abc ~= "A";
      return [];
 }

 float[] B()
 {
      abc ~= "B";
      return [];
 }

 float[] C()
 {
      abc ~= "C";
      return [];
 }

 void main()
 {
      A()[] = B()[] + C()[];
      assert(abc == "???");
 }
 ----

 Without cheating, I invite people to have a good guess what 'abc' is
 equal to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect on
 x86/x86_64.

 The problem here is that the array operation A[] = B[] + C[] gets
 transformed into an extern(C) call.  And because there's no strict
 rules
 in place over the order of which it's parameters are evaluated, it
 could
 go either way (LTR, or RTL).

 Serious note: This test is bogus as this and similar other failing
 tests
 on non-x86 platforms are not at all obvious to the users who get
 issues.
 So what are we to do about it?


 As far as my understanding of the issue goes, the correct answer is
 "ABC", and compilers that do not produce "ABC" are buggy.

 The solution is to transform the code to code that evaluates the
 arguments to the extern(C) call in the correct order and eg. stores
 them in temporaries before passing them on.

Shouldn't it be BCA? I'd expect the assignment operation to take place last, so the corresponding expression should be evaluated last.

I expected "BCA" too. Left associative expressions evaluated left-to-right, right associative expressions evaluated right-to-left.

I don't think that makes any sense. Why would that be the case? a = b = c; => a.opAssign(b.opAssign(c)); foo() ^^ bar() ^^ qux() => pow(foo(), pow(bar(), qux()));
Sep 26 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Sep 26, 2012 at 01:10:00AM +0200, Timon Gehr wrote:
 On 09/26/2012 12:58 AM, Iain Buclaw wrote:

string abc;

float[] A()
{
     abc ~= "A";
     return [];
}

float[] B()
{
     abc ~= "B";
     return [];
}

float[] C()
{
     abc ~= "C";
     return [];
}

void main()
{
     A()[] = B()[] + C()[];
     assert(abc == "???");
}
----

Without cheating, I invite people to have a good guess what 'abc' is
equal to, but just to narrow it down.

1)  It isn't "ABC".
2)  On x86/x86_64, it isn't "ACB".
3)  On everything else, it's the reverse of what you'd expect on
x86/x86_64.

The problem here is that the array operation A[] = B[] + C[] gets
transformed into an extern(C) call.  And because there's no strict rules
in place over the order of which it's parameters are evaluated, it could
go either way (LTR, or RTL).

Serious note: This test is bogus as this and similar other failing tests
on non-x86 platforms are not at all obvious to the users who get issues.
So what are we to do about it?


 As far as my understanding of the issue goes, the correct answer is
 "ABC", and compilers that do not produce "ABC" are buggy.
 
 The solution is to transform the code to code that evaluates the
 arguments to the extern(C) call in the correct order and eg. stores
 them in temporaries before passing them on.

Shouldn't it be BCA? I'd expect the assignment operation to take place last, so the corresponding expression should be evaluated last. T -- Leather is waterproof. Ever see a cow with an umbrella?
Sep 25 2012
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/25/2012 3:58 PM, Iain Buclaw wrote:
 The problem here is that the array operation A[] = B[] + C[] gets transformed
 into an extern(C) call.  And because there's no strict rules in place over the
 order of which it's parameters are evaluated, it could go either way (LTR, or
RTL).

 Serious note: This test is bogus as this and similar other failing tests on
 non-x86 platforms are not at all obvious to the users who get issues. So what
 are we to do about it?

D needs to move towards a defined order of evaluation. I understand that there's a problem when using parts of a C compiler that feels free to reorder within the C rules. It's something we have to deal with sooner or later, by either: 1. adjusting the C optimizer so it follows D rules for D code 2. assigning terms to temporaries that are executed in a specific order by C rules
Sep 25 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/26/2012 12:36 AM, Iain Buclaw wrote:
 On Tuesday, 25 September 2012 at 23:39:39 UTC, Walter Bright wrote:
 On 9/25/2012 3:58 PM, Iain Buclaw wrote:
 The problem here is that the array operation A[] = B[] + C[] gets transformed
 into an extern(C) call.  And because there's no strict rules in place over the
 order of which it's parameters are evaluated, it could go either way (LTR, or
 RTL).

 Serious note: This test is bogus as this and similar other failing tests on
 non-x86 platforms are not at all obvious to the users who get issues. So what
 are we to do about it?

D needs to move towards a defined order of evaluation. I understand that there's a problem when using parts of a C compiler that feels free to reorder within the C rules. It's something we have to deal with sooner or later, by either: 1. adjusting the C optimizer so it follows D rules for D code 2. assigning terms to temporaries that are executed in a specific order by C rules

Indeed, but where does that leave code that gets compiled down to a extern(C) call?

C functions all seem to evaluate their args right-to-left, even though the C Standard doesn't specify that. So we should be all right by simply defining that D do it that way for C functions. It doesn't actually matter what order D does things, we just have to pick one. And so we might as well pick one that C compilers naturally do anyway.
 eg, in the OP, the following is generated:
 _arraySliceSliceAddSliceAssign_f(A[], C[], B[]);

 To say that all extern(C) calls must follow D rules for D code (LTR
evaluation),
 is to change the current behaviour of array operations on it's head, so we'll
 have to correct a whole lotta changes in code generation to alter the order of
 parameters, and ensure that both DRT supplied and internally generated
functions
 are corrected for this.

 Well... someone's got to do the grizzly bits that no one else wants to do. :~)


 Regards,
 Iain.

Sep 26 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09/26/2012 10:11 AM, Walter Bright wrote:
 On 9/26/2012 12:36 AM, Iain Buclaw wrote:
 On Tuesday, 25 September 2012 at 23:39:39 UTC, Walter Bright wrote:
 On 9/25/2012 3:58 PM, Iain Buclaw wrote:
 The problem here is that the array operation A[] = B[] + C[] gets
 transformed
 into an extern(C) call.  And because there's no strict rules in
 place over the
 order of which it's parameters are evaluated, it could go either way
 (LTR, or
 RTL).

 Serious note: This test is bogus as this and similar other failing
 tests on
 non-x86 platforms are not at all obvious to the users who get
 issues. So what
 are we to do about it?

D needs to move towards a defined order of evaluation. I understand that there's a problem when using parts of a C compiler that feels free to reorder within the C rules. It's something we have to deal with sooner or later, by either: 1. adjusting the C optimizer so it follows D rules for D code 2. assigning terms to temporaries that are executed in a specific order by C rules

Indeed, but where does that leave code that gets compiled down to a extern(C) call?

C functions all seem to evaluate their args right-to-left, even though the C Standard doesn't specify that.

On x86. It is unspecified, therefore different compilers on different architectures do it in different ways.
 So we should be all right by simply
 defining that D do it that way for C functions.

I disagree. Why should the calling convention have an impact on code semantics? The code reads LTR, therefore evaluation should consistently be LTR.
 It doesn't actually matter what order D does things, we just have to
 pick one. And so we might as well pick one that C compilers naturally do
 anyway.
 ...

There is no such thing. C and C++ get evaluation order thoroughly wrong.
Sep 26 2012
next sibling parent =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <alex lycus.org> writes:
On 26-09-2012 14:04, Timon Gehr wrote:
 On 09/26/2012 10:11 AM, Walter Bright wrote:
 On 9/26/2012 12:36 AM, Iain Buclaw wrote:
 On Tuesday, 25 September 2012 at 23:39:39 UTC, Walter Bright wrote:
 On 9/25/2012 3:58 PM, Iain Buclaw wrote:
 The problem here is that the array operation A[] = B[] + C[] gets
 transformed
 into an extern(C) call.  And because there's no strict rules in
 place over the
 order of which it's parameters are evaluated, it could go either way
 (LTR, or
 RTL).

 Serious note: This test is bogus as this and similar other failing
 tests on
 non-x86 platforms are not at all obvious to the users who get
 issues. So what
 are we to do about it?

D needs to move towards a defined order of evaluation. I understand that there's a problem when using parts of a C compiler that feels free to reorder within the C rules. It's something we have to deal with sooner or later, by either: 1. adjusting the C optimizer so it follows D rules for D code 2. assigning terms to temporaries that are executed in a specific order by C rules

Indeed, but where does that leave code that gets compiled down to a extern(C) call?

C functions all seem to evaluate their args right-to-left, even though the C Standard doesn't specify that.

On x86. It is unspecified, therefore different compilers on different architectures do it in different ways.
 So we should be all right by simply
 defining that D do it that way for C functions.

I disagree. Why should the calling convention have an impact on code semantics? The code reads LTR, therefore evaluation should consistently be LTR.

Completely agree with this. Calling convention is irrelevant. Let us not inherit the C/C++ mess.
 It doesn't actually matter what order D does things, we just have to
 pick one. And so we might as well pick one that C compilers naturally do
 anyway.
 ...

There is no such thing. C and C++ get evaluation order thoroughly wrong.

-- Alex Rønne Petersen alex lycus.org http://lycus.org
Sep 26 2012
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/26/2012 5:04 AM, Timon Gehr wrote:
 Why should the calling convention have an impact on code
 semantics?

Maximizing performance.
 The code reads LTR, therefore evaluation should consistently be LTR.

The best order is the one that generates the fewest temporaries, and that's the order that should be defined for D.
Sep 27 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 09/27/2012 12:38 PM, Walter Bright wrote:
 On 9/26/2012 5:04 AM, Timon Gehr wrote:
 Why should the calling convention have an impact on code
 semantics?

Maximizing performance.

Optimisations by definition do not have an impact on code semantics.
 The code reads LTR, therefore evaluation should consistently be LTR.

The best order is the one that generates the fewest temporaries, and that's the order that should be defined for D.

The number of temporaries is not affected by the order of evaluation. Just push the arguments on the stack out-of-order. Furthermore, the 'natural' order of C argument evaluation differs between architectures, making the entire point moot.
Sep 27 2012
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 09/27/2012 01:43 PM, bearophile wrote:
 Walter Bright:

 The best order is the one that generates the fewest temporaries, and
 that's the order that should be defined for D.

Java and Python and C# give a precedent, maybe many programmers expect a behavior like those ones.

Well, those can use a calling convention that optimally fits the evaluation order, as they do not provide zero-overhead interaction with C code.
Sep 27 2012
prev sibling parent =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <alex lycus.org> writes:
On 26-09-2012 13:45, Iain Buclaw wrote:
 On Wednesday, 26 September 2012 at 08:12:16 UTC, Walter Bright wrote:
 On 9/26/2012 12:36 AM, Iain Buclaw wrote:
 On Tuesday, 25 September 2012 at 23:39:39 UTC, Walter Bright wrote:
 On 9/25/2012 3:58 PM, Iain Buclaw wrote:
 The problem here is that the array operation A[] = B[] + C[] gets
 transformed
 into an extern(C) call.  And because there's no strict rules in
 place over the
 order of which it's parameters are evaluated, it could go either
 way (LTR, or
 RTL).

 Serious note: This test is bogus as this and similar other failing
 tests on
 non-x86 platforms are not at all obvious to the users who get
 issues. So what
 are we to do about it?

D needs to move towards a defined order of evaluation. I understand that there's a problem when using parts of a C compiler that feels free to reorder within the C rules. It's something we have to deal with sooner or later, by either: 1. adjusting the C optimizer so it follows D rules for D code 2. assigning terms to temporaries that are executed in a specific order by C rules

Indeed, but where does that leave code that gets compiled down to a extern(C) call?

C functions all seem to evaluate their args right-to-left, even though the C Standard doesn't specify that. So we should be all right by simply defining that D do it that way for C functions. It doesn't actually matter what order D does things, we just have to pick one. And so we might as well pick one that C compilers naturally do anyway.

Speaking as a generalisation, no that is not true. C functions for x86/x86_64 all seem to evaluate their args right-to-left. However - and this may be different from compiler to compiler - GCC at least does left-to-right evaluation for everything else (ARM, SPARC, etc) because that is the order that arguments are pushed onto the stack for those architecture backends. If right-to-left should be the strict standard to follow for extern(C), then document this behaviour as part of the specification, and I'll implement it. :~) Regards Iain

There is a pull request that does this, but it has not yet been merged because it's waiting on compilers to catch up... ;) -- Alex Rønne Petersen alex lycus.org http://lycus.org
Sep 26 2012
prev sibling next sibling parent "Iain Buclaw" <ibuclaw ubuntu.com> writes:
On Tuesday, 25 September 2012 at 23:39:39 UTC, Walter Bright 
wrote:
 On 9/25/2012 3:58 PM, Iain Buclaw wrote:
 The problem here is that the array operation A[] = B[] + C[] 
 gets transformed
 into an extern(C) call.  And because there's no strict rules 
 in place over the
 order of which it's parameters are evaluated, it could go 
 either way (LTR, or RTL).

 Serious note: This test is bogus as this and similar other 
 failing tests on
 non-x86 platforms are not at all obvious to the users who get 
 issues. So what
 are we to do about it?

D needs to move towards a defined order of evaluation. I understand that there's a problem when using parts of a C compiler that feels free to reorder within the C rules. It's something we have to deal with sooner or later, by either: 1. adjusting the C optimizer so it follows D rules for D code 2. assigning terms to temporaries that are executed in a specific order by C rules

Indeed, but where does that leave code that gets compiled down to a extern(C) call? eg, in the OP, the following is generated: _arraySliceSliceAddSliceAssign_f(A[], C[], B[]); To say that all extern(C) calls must follow D rules for D code (LTR evaluation), is to change the current behaviour of array operations on it's head, so we'll have to correct a whole lotta changes in code generation to alter the order of parameters, and ensure that both DRT supplied and internally generated functions are corrected for this. Well... someone's got to do the grizzly bits that no one else wants to do. :~) Regards, Iain.
Sep 26 2012
prev sibling next sibling parent "Iain Buclaw" <ibuclaw ubuntu.com> writes:
On Wednesday, 26 September 2012 at 08:12:16 UTC, Walter Bright 
wrote:
 On 9/26/2012 12:36 AM, Iain Buclaw wrote:
 On Tuesday, 25 September 2012 at 23:39:39 UTC, Walter Bright 
 wrote:
 On 9/25/2012 3:58 PM, Iain Buclaw wrote:
 The problem here is that the array operation A[] = B[] + C[] 
 gets transformed
 into an extern(C) call.  And because there's no strict rules 
 in place over the
 order of which it's parameters are evaluated, it could go 
 either way (LTR, or
 RTL).

 Serious note: This test is bogus as this and similar other 
 failing tests on
 non-x86 platforms are not at all obvious to the users who 
 get issues. So what
 are we to do about it?

D needs to move towards a defined order of evaluation. I understand that there's a problem when using parts of a C compiler that feels free to reorder within the C rules. It's something we have to deal with sooner or later, by either: 1. adjusting the C optimizer so it follows D rules for D code 2. assigning terms to temporaries that are executed in a specific order by C rules

Indeed, but where does that leave code that gets compiled down to a extern(C) call?

C functions all seem to evaluate their args right-to-left, even though the C Standard doesn't specify that. So we should be all right by simply defining that D do it that way for C functions. It doesn't actually matter what order D does things, we just have to pick one. And so we might as well pick one that C compilers naturally do anyway.

Speaking as a generalisation, no that is not true. C functions for x86/x86_64 all seem to evaluate their args right-to-left. However - and this may be different from compiler to compiler - GCC at least does left-to-right evaluation for everything else (ARM, SPARC, etc) because that is the order that arguments are pushed onto the stack for those architecture backends. If right-to-left should be the strict standard to follow for extern(C), then document this behaviour as part of the specification, and I'll implement it. :~) Regards Iain
Sep 26 2012
prev sibling next sibling parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 25 September 2012 at 22:58:11 UTC, Iain Buclaw wrote:
 Pop quiz!

 Without cheating, I invite people to have a good guess what 
 'abc' is equal to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect 
 on x86/x86_64.

I'd say abc's value is "unspecified", and any attempt at predicting it would be bogus. That's my answer anyways
 The problem here is that the array operation A[] = B[] + C[] 
 gets transformed into an extern(C) call.  And because there's 
 no strict rules in place over the order of which it's 
 parameters are evaluated, it could go either way (LTR, or RTL).

I don't see how the "extern(C)" is involved here, since it is the D compiler that first evaluates A(), B() and C() before passing the making the C function call. Or did I miss something?
Sep 26 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09/26/2012 02:07 PM, monarch_dodra wrote:
 On Tuesday, 25 September 2012 at 22:58:11 UTC, Iain Buclaw wrote:
 Pop quiz!

 Without cheating, I invite people to have a good guess what 'abc' is
 equal to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect on
 x86/x86_64.

I'd say abc's value is "unspecified", and any attempt at predicting it would be bogus. That's my answer anyways ...

Why? This is not useful behaviour.
Sep 26 2012
parent reply =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <alex lycus.org> writes:
On 26-09-2012 15:34, monarch_dodra wrote:
 On Wednesday, 26 September 2012 at 12:48:19 UTC, Timon Gehr wrote:
 On 09/26/2012 02:07 PM, monarch_dodra wrote:
 On Tuesday, 25 September 2012 at 22:58:11 UTC, Iain Buclaw wrote:
 Pop quiz!

 Without cheating, I invite people to have a good guess what 'abc' is
 equal to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect on
 x86/x86_64.

I'd say abc's value is "unspecified", and any attempt at predicting it would be bogus. That's my answer anyways ...

Why? This is not useful behaviour.

What do you mean "why"? Because order of evaluation is not specified, so the value of abc is not specified. That was my answer. The question wasn't really "what would useful behavior be". IMO: useful behavior would be if it was explicitly illegal to modify (or modify + read) the same value twice in the same expression. I'd rather expressions such as: A()[] = B()[] + C()[]; x[i] = ++i + 1; Be illegal rather than have (an arbitrarily defined) specified behavior. Trying to specify a specific behavior in such cases is opening a can of worms.

No it isn't. There's a perfectly sensible, sane, and intuitive fix for this: always evaluate arguments left-to-right. No exceptions. It's not that complicated. -- Alex Rønne Petersen alex lycus.org http://lycus.org
Sep 26 2012
parent =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <alex lycus.org> writes:
On 26-09-2012 19:23, Jonathan M Davis wrote:
 On Wednesday, September 26, 2012 17:12:49 Alex Rønne Petersen wrote:
 On 26-09-2012 15:34, monarch_dodra wrote:
 IMO: useful behavior would be if it was explicitly illegal to modify (or
 modify + read) the same value twice in the same expression. I'd rather
 expressions such as:
 A()[] = B()[] + C()[];
 x[i] = ++i + 1;

 Be illegal rather than have (an arbitrarily defined) specified behavior.
 Trying to specify a specific behavior in such cases is opening a can of
 worms.

No it isn't. There's a perfectly sensible, sane, and intuitive fix for this: always evaluate arguments left-to-right. No exceptions. It's not that complicated.

I'd still consider it to be bad practice to do anything which relies on the order of evaluation of function arguments, because the order is undefined in other languages, and if you get into the habit of doing stuff like func(++i, ++i, ++i) in D, you're going to be screwed when you have to program in other languages. It's just cleaner to avoid that kind of stuff, and I'd love it if it were illegal (a single ++i is fine - it's using i in multiple arguments _and_ modifying it in one of them which is the problem). That being said, defining the order will at least reduce bugs, even if it's considered bad practice to do anything in a function call which would rely on the arguments being evaluated in a particular order. - Jonathan M Davis

I do not know any other languages than C and C++ that leave it undefined. -- Alex Rønne Petersen alex lycus.org http://lycus.org
Sep 27 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 26 September 2012 13:07, monarch_dodra <monarchdodra gmail.com> wrote:
 On Tuesday, 25 September 2012 at 22:58:11 UTC, Iain Buclaw wrote:
 Pop quiz!

 Without cheating, I invite people to have a good guess what 'abc' is equal
 to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect on
 x86/x86_64.

I'd say abc's value is "unspecified", and any attempt at predicting it would be bogus. That's my answer anyways
 The problem here is that the array operation A[] = B[] + C[] gets
 transformed into an extern(C) call.  And because there's no strict rules in
 place over the order of which it's parameters are evaluated, it could go
 either way (LTR, or RTL).

I don't see how the "extern(C)" is involved here, since it is the D compiler that first evaluates A(), B() and C() before passing the making the C function call. Or did I miss something?

There is no physical code generation from the frontend that says "evaluate this". What it passes to be backend for this operation is a function call. So the backend determines the order of evaluation depending on the order of parameters. -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';
Sep 26 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Wednesday, 26 September 2012 at 12:48:19 UTC, Timon Gehr wrote:
 On 09/26/2012 02:07 PM, monarch_dodra wrote:
 On Tuesday, 25 September 2012 at 22:58:11 UTC, Iain Buclaw 
 wrote:
 Pop quiz!

 Without cheating, I invite people to have a good guess what 
 'abc' is
 equal to, but just to narrow it down.

 1)  It isn't "ABC".
 2)  On x86/x86_64, it isn't "ACB".
 3)  On everything else, it's the reverse of what you'd expect 
 on
 x86/x86_64.

I'd say abc's value is "unspecified", and any attempt at predicting it would be bogus. That's my answer anyways ...

Why? This is not useful behaviour.

What do you mean "why"? Because order of evaluation is not specified, so the value of abc is not specified. That was my answer. The question wasn't really "what would useful behavior be". IMO: useful behavior would be if it was explicitly illegal to modify (or modify + read) the same value twice in the same expression. I'd rather expressions such as: A()[] = B()[] + C()[]; x[i] = ++i + 1; Be illegal rather than have (an arbitrarily defined) specified behavior. Trying to specify a specific behavior in such cases is opening a can of worms.
Sep 26 2012
prev sibling next sibling parent "David Nadlinger" <see klickverbot.at> writes:
On Wednesday, 26 September 2012 at 12:29:38 UTC, Iain Buclaw 
wrote:
 On 26 September 2012 13:07, monarch_dodra 
 <monarchdodra gmail.com> wrote:
 I don't see how the "extern(C)" is involved here, since it is 
 the D compiler
 that first evaluates A(), B() and C() before passing the 
 making the C
 function call. Or did I miss something?

There is no physical code generation from the frontend that says "evaluate this". What it passes to be backend for this operation is a function call. So the backend determines the order of evaluation depending on the order of parameters.

I'm not quite sure what you are trying to say here; I guess it depends on the definition of "backend". monarch_dodra is right in so far as the effects of "extern(C)" are an implementation detail coming from the particular way your compiler "glue code" is written; there is no fundamental reason why it should be important for parameter evaluation at all. Speaking of it, what is the reason that the actual order of formal parameters (in terms of register allocation/stack layout) is reversed in the D calling convention, compared to extern (C) (e.g. on *nix x86_64)? David
Sep 26 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Wednesday, 26 September 2012 at 15:12:05 UTC, Alex Rønne 
Petersen wrote:
 On 26-09-2012 15:34, monarch_dodra wrote:
 [SNIP]

No it isn't. There's a perfectly sensible, sane, and intuitive fix for this: always evaluate arguments left-to-right. No exceptions. It's not that complicated.

You know, I think you are right on second though. However, it is a matter of not mistaking "left to right evaluation of arguments" with normal the associative behavior of operators. EG: (a = (b = c) = d): *a is evaluated *b is evaluated *c is evaluated *(b = c) is evaluad into a temp called bc *d is evaluated *bc = d is evaluated into bcd *a = bcd is evaluated. So back to my example: (where i starts at 1) x[i++] = ++i + 1; *i++ is evaluated to 1, and i is now 2. *x[i++] is now a reference to x[1] *++i is evaluated to 3 *++i + 1 is 4 *x[1] becomes 4 Yeah... it looks like it works.
Sep 26 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 26 September 2012 16:25, David Nadlinger <see klickverbot.at> wrote:
 On Wednesday, 26 September 2012 at 12:29:38 UTC, Iain Buclaw wrote:
 On 26 September 2012 13:07, monarch_dodra <monarchdodra gmail.com> wrote:
 I don't see how the "extern(C)" is involved here, since it is the D
 compiler
 that first evaluates A(), B() and C() before passing the making the C
 function call. Or did I miss something?

There is no physical code generation from the frontend that says "evaluate this". What it passes to be backend for this operation is a function call. So the backend determines the order of evaluation depending on the order of parameters.

I'm not quite sure what you are trying to say here; I guess it depends on the definition of "backend". monarch_dodra is right in so far as the effects of "extern(C)" are an implementation detail coming from the particular way your compiler "glue code" is written; there is no fundamental reason why it should be important for parameter evaluation at all.

Yes, this is what's done in GDC. All parameters with side effects have their result evaluated and saved before calling the extern(D) functions.
 Speaking of it, what is the reason that the actual order of formal
 parameters (in terms of register allocation/stack layout) is reversed in the
 D calling convention, compared to extern (C) (e.g. on *nix x86_64)?

DMD pushes arguments on the stack in reverse order to that of x86/x86_64. So it gives the *impression* of LTR evaluation, however, the parameters themselves aren't actually explicitly evaluated / saved in temporaries prior to calling. Regards -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';
Sep 26 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 26 September 2012 16:10, Alex R=F8nne Petersen <alex lycus.org> wrote:
 On 26-09-2012 13:45, Iain Buclaw wrote:
 On Wednesday, 26 September 2012 at 08:12:16 UTC, Walter Bright wrote:
 On 9/26/2012 12:36 AM, Iain Buclaw wrote:
 On Tuesday, 25 September 2012 at 23:39:39 UTC, Walter Bright wrote:
 On 9/25/2012 3:58 PM, Iain Buclaw wrote:
 The problem here is that the array operation A[] =3D B[] + C[] gets
 transformed
 into an extern(C) call.  And because there's no strict rules in
 place over the
 order of which it's parameters are evaluated, it could go either
 way (LTR, or
 RTL).

 Serious note: This test is bogus as this and similar other failing
 tests on
 non-x86 platforms are not at all obvious to the users who get
 issues. So what
 are we to do about it?

D needs to move towards a defined order of evaluation. I understand that there's a problem when using parts of a C compiler that feels free to reorder within the C rules. It's something we have to deal with sooner or later, by either: 1. adjusting the C optimizer so it follows D rules for D code 2. assigning terms to temporaries that are executed in a specific order by C rules

Indeed, but where does that leave code that gets compiled down to a extern(C) call?

C functions all seem to evaluate their args right-to-left, even though the C Standard doesn't specify that. So we should be all right by simply defining that D do it that way for C functions. It doesn't actually matter what order D does things, we just have to pick one. And so we might as well pick one that C compilers naturally do anyway.

Speaking as a generalisation, no that is not true. C functions for x86/x86_64 all seem to evaluate their args right-to-left. However - and this may be different from compiler to compiler - GCC at least does left-to-right evaluation for everything else (ARM, SPARC, etc) because that is the order that arguments are pushed onto the stack for those architecture backends. If right-to-left should be the strict standard to follow for extern(C), then document this behaviour as part of the specification, and I'll implement it. :~) Regards Iain

There is a pull request that does this, but it has not yet been merged because it's waiting on compilers to catch up... ;)

Got a link? :~) --=20 Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';
Sep 26 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, September 26, 2012 17:12:49 Alex Rønne Petersen wrote:
 On 26-09-2012 15:34, monarch_dodra wrote:
 IMO: useful behavior would be if it was explicitly illegal to modify (or
 modify + read) the same value twice in the same expression. I'd rather
 expressions such as:
 A()[] = B()[] + C()[];
 x[i] = ++i + 1;
 
 Be illegal rather than have (an arbitrarily defined) specified behavior.
 Trying to specify a specific behavior in such cases is opening a can of
 worms.

No it isn't. There's a perfectly sensible, sane, and intuitive fix for this: always evaluate arguments left-to-right. No exceptions. It's not that complicated.

I'd still consider it to be bad practice to do anything which relies on the order of evaluation of function arguments, because the order is undefined in other languages, and if you get into the habit of doing stuff like func(++i, ++i, ++i) in D, you're going to be screwed when you have to program in other languages. It's just cleaner to avoid that kind of stuff, and I'd love it if it were illegal (a single ++i is fine - it's using i in multiple arguments _and_ modifying it in one of them which is the problem). That being said, defining the order will at least reduce bugs, even if it's considered bad practice to do anything in a function call which would rely on the arguments being evaluated in a particular order. - Jonathan M Davis
Sep 26 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 27 September 2012 11:38, Walter Bright <newshound2 digitalmars.com> wrote:
 On 9/26/2012 5:04 AM, Timon Gehr wrote:
 Why should the calling convention have an impact on code
 semantics?

Maximizing performance.
 The code reads LTR, therefore evaluation should consistently be LTR.

The best order is the one that generates the fewest temporaries, and that's the order that should be defined for D.

Can't see how you can get any fewer temporaries when following the rule: If it has side-effects, cache it's return value. -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';
Sep 27 2012
prev sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Walter Bright:

 The best order is the one that generates the fewest 
 temporaries, and that's the order that should be defined for D.

Java and Python and C# give a precedent, maybe many programmers expect a behavior like those ones. Bye, bearophile
Sep 27 2012