Jump to page: 1 2
Thread overview
DIP 1016 should use expression lowering, not statement lowering
Jan 29
Rubn
Jan 30
Kagamin
January 29
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
January 29
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...
January 29
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.
January 29
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.
January 29
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.
January 29
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
{
}
January 30
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.
January 30
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.
January 30
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
January 30
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
« First   ‹ Prev
1 2