www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - DIP 1016 should use expression lowering, not statement lowering

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
While writing this example:

int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
if (alloc.reallocate(a, 200 * int.sizeof))
{
     assert(a.length == 200);
}

=====>

int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
void[] __temp0 = a;
if (alloc.reallocate(__temp0, 200 * int.sizeof)
{
     assert(a.length == 200);
}

I noticed a problem - the lowering as informally described in DIP 1016 
makes it difficult to figure how function calls present in control 
statements like if, while, etc. should behave. Where should the 
temporary go? An expression-based lowering clarifies everything. A 
statement-based lowering would need to work on a case basis for all 
statements involving expressions.


Andrei
Jan 29 2019
next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Tuesday, 29 January 2019 at 11:52:40 UTC, Andrei Alexandrescu 
wrote:
 While writing this example:

 int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
 if (alloc.reallocate(a, 200 * int.sizeof))
 {
     assert(a.length == 200);
 }

 =====>

 int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
 void[] __temp0 = a;
 if (alloc.reallocate(__temp0, 200 * int.sizeof)
 {
     assert(a.length == 200);
 }

 I noticed a problem - the lowering as informally described in 
 DIP 1016 makes it difficult to figure how function calls 
 present in control statements like if, while, etc. should 
 behave. Where should the temporary go? An expression-based 
 lowering clarifies everything. A statement-based lowering would 
 need to work on a case basis for all statements involving 
 expressions.
On the contrary, an expression lowering cannot inject temporary declarations and is impossible. The correct lowering in the case for `if` & friends follows the form of C++ initialiser conditions(?) i.e: if (auto val = expr(); val) { ... }, or the slightly more ugly valid D: if ((){return expr(); }()) { ... } this lambdification will work for just about anything: if, while, assert...
Jan 29 2019
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
On 1/29/19 10:44 AM, Nicholas Wilson wrote:
 On Tuesday, 29 January 2019 at 11:52:40 UTC, Andrei Alexandrescu wrote:
 While writing this example:

 int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
 if (alloc.reallocate(a, 200 * int.sizeof))
 {
     assert(a.length == 200);
 }

 =====>

 int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
 void[] __temp0 = a;
 if (alloc.reallocate(__temp0, 200 * int.sizeof)
 {
     assert(a.length == 200);
 }

 I noticed a problem - the lowering as informally described in DIP 1016 
 makes it difficult to figure how function calls present in control 
 statements like if, while, etc. should behave. Where should the 
 temporary go? An expression-based lowering clarifies everything. A 
 statement-based lowering would need to work on a case basis for all 
 statements involving expressions.
On the contrary, an expression lowering cannot inject temporary declarations and is impossible. The correct lowering in the case for `if` & friends follows the form of C++ initialiser conditions(?) i.e:  if (auto val = expr(); val) { ... },
Since we don't have these constructs, lowering would need to explain what happens here. Not difficult, but would be nice if we could avoid.
   or the slightly more ugly valid D:
 
   if ((){return expr(); }()) { ... }
 
 this lambdification will work for just about anything: if, while, assert...
Yes, converting to lambdas is nice and easy and requires no statement enumeration - just lower expressions to lambdas and be done with it. It's okay if the resulting code is ugly, it won't be user-visible.
Jan 29 2019
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 29 January 2019 at 15:48:23 UTC, Andrei Alexandrescu 
wrote:
 On 1/29/19 10:44 AM, Nicholas Wilson wrote:
   if (auto val = expr(); val) { ... },
Since we don't have these constructs, lowering would need to explain what happens here.
Nitpick, but D has something very similar to that: if(auto val = expr()) { ... } it just depends on val implicitly casting to bool.
 It's okay if the resulting code is ugly, it won't be 
 user-visible.
We do have to be careful about this - error messages sometimes leak that ugly code out.
Jan 29 2019
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
On 1/29/19 10:57 AM, Adam D. Ruppe wrote:
 On Tuesday, 29 January 2019 at 15:48:23 UTC, Andrei Alexandrescu wrote:
 On 1/29/19 10:44 AM, Nicholas Wilson wrote:
   if (auto val = expr(); val) { ... },
Since we don't have these constructs, lowering would need to explain what happens here.
Nitpick, but D has something very similar to that: if(auto val = expr()) { ... } it just depends on val implicitly casting to bool.
 It's okay if the resulting code is ugly, it won't be user-visible.
We do have to be careful about this - error messages sometimes leak that ugly code out.
Yah, it did happen in the past that implementations have been switched from lowering to a dedicated case. Lowerings do remain a terrific tool for conveying semantics and DIP authors should not worry about implementation details.
Jan 29 2019
prev sibling parent Rubn <where is.this> writes:
On Tuesday, 29 January 2019 at 15:44:02 UTC, Nicholas Wilson 
wrote:
 On Tuesday, 29 January 2019 at 11:52:40 UTC, Andrei 
 Alexandrescu wrote:
 While writing this example:

 int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
 if (alloc.reallocate(a, 200 * int.sizeof))
 {
     assert(a.length == 200);
 }

 =====>

 int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
 void[] __temp0 = a;
 if (alloc.reallocate(__temp0, 200 * int.sizeof)
 {
     assert(a.length == 200);
 }

 I noticed a problem - the lowering as informally described in 
 DIP 1016 makes it difficult to figure how function calls 
 present in control statements like if, while, etc. should 
 behave. Where should the temporary go? An expression-based 
 lowering clarifies everything. A statement-based lowering 
 would need to work on a case basis for all statements 
 involving expressions.
On the contrary, an expression lowering cannot inject temporary declarations and is impossible. The correct lowering in the case for `if` & friends follows the form of C++ initialiser conditions(?) i.e: if (auto val = expr(); val) { ... }, or the slightly more ugly valid D: if ((){return expr(); }()) { ... } this lambdification will work for just about anything: if, while, assert...
If it a condition then you can do the following in C++: if(int* val = expr()) { // use val, not nullptr } Where it is useful is in the following case: if(int val = expr(); val != -1) { } D follows C++'s construct for initializing a variable in control blocks. The only exception I think is for switch. switch(int val = expr()) // ok in C++, not ok in D { }
Jan 29 2019
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
On Tuesday, 29 January 2019 at 11:52:40 UTC, Andrei Alexandrescu 
wrote:
 Where should the temporary go?
Doesn't D already specify allocation and lifetime of temporaries? AIU the DIP doesn't invent the notion of a temporary.
Jan 30 2019
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
On 1/30/19 3:34 AM, Kagamin wrote:
 On Tuesday, 29 January 2019 at 11:52:40 UTC, Andrei Alexandrescu wrote:
 Where should the temporary go?
Doesn't D already specify allocation and lifetime of temporaries? AIU the DIP doesn't invent the notion of a temporary.
My bad, I overloaded the term "temporary". I meant the variable inserted by the lowering.
Jan 30 2019
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/29/19 6:52 AM, Andrei Alexandrescu wrote:
 While writing this example:
 
 int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
 if (alloc.reallocate(a, 200 * int.sizeof))
 {
      assert(a.length == 200);
 }
 
 =====>
 
 int[] a = cast(int[]) alloc.allocate(100 * int.sizeof);
 void[] __temp0 = a;
 if (alloc.reallocate(__temp0, 200 * int.sizeof)
 {
      assert(a.length == 200);
 }
 
 I noticed a problem - the lowering as informally described in DIP 1016 
 makes it difficult to figure how function calls present in control 
 statements like if, while, etc. should behave. Where should the 
 temporary go? An expression-based lowering clarifies everything. A 
 statement-based lowering would need to work on a case basis for all 
 statements involving expressions.
I don't think it's very difficult or confusing. Any rvalue in any expression that requires ref should be implicitly declared before the statement that contains the expression, and include a new scope: <statement involving rvalues r1, r2, r3, ... rN> ----> { auto _tmpr1 = r1; auto _tmpr2 = r2; auto _tmpr3 = r3; ... auto _tmprN = rN; <statement with _tmpr1, _tmpr2, _tmpr3, ... _tmprN> } Essentially, nothing is different from existing semantics today, when rvalues are used and provides reference semantics (yes, it's possible, see tempCString). They live until the end of the statement. It's how this has to be. It can't be expression based. -Steve
Jan 30 2019
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/30/19 9:20 PM, Steven Schveighoffer wrote:

 Essentially, nothing is different from existing semantics today, when 
 rvalues are used and provides reference semantics (yes, it's possible, 
 see tempCString). They live until the end of the statement. It's how 
 this has to be. It can't be expression based.
I came up with this idea based on tempCString, but it doesn't work: struct RV(T) { T theThing; ref T getIt() { return theThing; } alias getIt this; } auto rv(T)(T t) { import std.algorithm : move; return RV!T(move(t)); } Of course, we'd need to suppress destructors here potentially. I tried to use it: import std.stdio; void foo(ref int i) {writeln("i is ", i);} void main() { foo(1.rv); } But it fails: Error: function testrvalue.foo(ref int i) is not callable using argument types (RV!int) cannot pass rvalue argument rv(1) of type RV!int to parameter ref int i If I manually execute the alias this, it works: foo(1.rv.getIt) So I don't get why it doesn't work. But if that was fixed, could be a potential workaround without requiring a DIP. -Steve
Jan 30 2019
parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Thursday, 31 January 2019 at 02:29:47 UTC, Steven 
Schveighoffer wrote:
 I came up with this idea based on tempCString, but it doesn't 
 work:

 So I don't get why it doesn't work. But if that was fixed, 
 could be a potential workaround without requiring a DIP.
Thats nice! But it doesn't fix the problem that in generic code you don't know without checking if you need to do that. Also the template bloat.
Jan 30 2019
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/30/19 10:03 PM, Nicholas Wilson wrote:
 On Thursday, 31 January 2019 at 02:29:47 UTC, Steven Schveighoffer wrote:
 I came up with this idea based on tempCString, but it doesn't work:

 So I don't get why it doesn't work. But if that was fixed, could be a 
 potential workaround without requiring a DIP.
Thats nice! But it doesn't fix the problem that in generic code you don't know without checking if you need to do that. Also the template bloat.
Yeah, it could do this too: auto ref rv(T)(auto ref T t) { static if(__traits(isRef, t)) return t; else /* do the other stuff */ } But yes, template bloat. Plus having to put rv on everything... would suck. The DIP to me seems like it should be good with the clarification of not applying to lvalue -> rvalue auto translations. -Steve
Jan 30 2019