Struggling to implement parallel foreach...

Timon Gehr timon.gehr at gmx.ch
Tue Jun 18 13:24:58 UTC 2019


On 18.06.19 14:00, Nicholas Wilson wrote:
> On Tuesday, 18 June 2019 at 00:17:06 UTC, Timon Gehr wrote:
>> Unfortunately I have another paper deadline in a few weeks, but maybe 
>> I can set aside a few weekends this summer to try to fix function 
>> qualifiers.
> 
> I intend to beat you to it :)
> ...

Great. :)

> Suleyman Sahmi has located where things go right for the struct case[1], 
> so it shouldn't be too hard to manufacture a fix for the closure case.
> 
> [1]: 
> https://github.com/dlang/dmd/blob/f455995f234a2091c4482bff222a2977dbfa186b/src/dmd/expressionsem.d#L885-L886 
> 

I believe it might be easier to locate where things go right for 
`shared` and `immutable` capturing and fix the logic there. I provide 
tests below.

Note that a few missing checks and const-promotions when capturing are 
not the only issue with nested function qualifiers. If the following 
isses are fixed, we should be in pretty good shape (but probably I 
forgot about something):

0. Qualified capturing is not implemented fully correctly, it is 
particularly bad for `const`. Full test case:

void fun(inout(int)*){
     int* x;
     const(int*) cx;
     immutable(int*) ix;
     shared(int*) sx;
     shared(const(int*)) scx;
     inout(int*) wx;
     shared(inout(int*)) swx;
     const(inout(int*)) cwx;
     shared(const(inout(int*))) scwx;
     void foo(){
         int* x=x;
         const(int)* cx=cx; // ok
         immutable(int)* ix=ix; // ok
         shared(int)* sx=sx; // ok
         shared(const(int*)) scx=scx; // ok
         inout(int)* wx=wx; // ok
         shared(inout(int))* swx=swx; // ok
         const(inout(int))* cwx=cwx; // ok
         shared(const(inout(int)))* scwx=scwx; // ok
     }
     void fooc()const{
         int* x=x; // currently ok, shouldn't compile
         const(int)* x2=x; // ok
         const(int)* cx=cx; // ok
         immutable(int)* ix=ix; // ok
         shared(int)* sx=sx; // currently ok, shouldn't compile
         const(shared(int))* sx2=sx; // ok
         shared(const(int*)) scx=scx; // ok
         inout(int)* wx=wx; // currently ok, shouldn't compile
         const(inout(int))* wx2=wx; // ok
         shared(inout(int))* swx=swx; // currently ok, shouldn't compile
         shared(const(inout(int)))* swx2=swx; // ok
         const(inout(int))* cwx=cwx; // ok
         shared(const(inout(int)))* scwx=scwx; // ok
     }
     void fooi()immutable{
         //int* x=x; // error, correct
         //const(int)* cx=cx; // error, correct
         immutable(int)* ix=ix; // ok
         //shared(int)* sx=sx; // error, correct
         //shared(const(int*)) scx=scx; // error, correct
         //inout(int)* wx=wx; // error, correct
         //shared(inout(int))* swx=swx; // error, correct
         //const(inout(int))* cwx=cwx; // error, correct
         //shared(const(inout(int)))* scwx=scwx; // error, correct
     }
     void foos()shared{
         //int* x=x; // error, correct
         //const(int)* cx=cx; // error, correct
         immutable(int)* ix=ix; // ok
         shared(int)* sx=sx; // ok
         shared(const(int*)) scx=scx; // ok
         //inout(int)* wx=wx; // error, correct
         //shared(inout(int))* swx=swx; // currently error, should work
         //const(inout(int))* cwx=cwx; // error, correct
         //shared(const(inout(int)))* scwx=scwx; // currently error, 
should work
     }
     void foosc()shared const{
         //int* x=x; // error, correct
         //const(int)* cx=cx; // error, correct
         immutable(int)* ix=ix; // ok
         //shared(int)* sx=sx; // error, correct
         //const(shared(int))* sx2=sx; // currently error, should work
         shared(const(int*)) scx=scx; // ok
         //inout(int)* wx=wx; // error, correct
         //const(inout(int))* wx2=wx; // currently error, should work
         //shared(inout(int))* swx=swx; // error, correct
         //const(shared(inout(int)))* swx2=swx; // currently error, 
should work
         //const(inout(int))* cwx=cwx; // error, correct
         //shared(const(inout(int)))* scwx=scwx; // currently error, 
should work
     }
     void foow()inout{
         int* x=x; // currently ok, shouldn't compile
         immutable(int)* ix=ix; // ok
         shared(int)* sx=sx; // currently ok, shouldn't compile
         inout(int)* wx=wx; // ok
         shared(inout(int))* swx=swx; // ok
         const(inout(int))* cwx=cwx; // ok
         shared(const(inout(int)))* scwx=scwx; // ok
     }
     void foosw()shared inout{
         //int* x=x; // error, correct
         immutable(int)* ix=ix; // ok
         //shared(int)* sx=sx; // error, correct
         //inout(int)* wx=wx; // error, correct
         shared(inout(int))* swx=swx; // ok
         //const(inout(int))* cwx=cwx; // error, correct
         shared(const(inout(int)))* scwx=scwx; // ok
     }
     void fooscw()shared const inout{
         //int* x=x; // error, correct
         immutable(int)* ix=ix; // ok
         //shared(int)* sx=sx; // error, correct
         //inout(int)* wx=wx; // error, correct
         //shared(inout(int))* swx=swx; // error, correct
         //const(shared(inout(int)))* swx2=swx; // currently error, 
should compile
         //const(inout(int))* cwx=cwx; // error, correct
         shared(const(inout(int)))* scwx=scwx; // ok
     }
}



1. I can break the type system like this:

void main(){
     int* x=new int;
     struct S{
         int* delegate()pure dg1;
         int* dg2()pure immutable{
             return dg1(); // this shouldn't compile
         }
     }
     // you may think the next line is the problem, but this is actually ok:
     auto s=immutable(S)(()=>x);
     immutable(int*) y=s.dg2();
     assert(x is y); // mutable/immutable aliasing
}

The problem is that it is possible to call a immutable(T 
delegate(Args)). This is wrong. It should only be possible to call a 
qualified delegate if the delegate context has the respective (or a 
stronger) qualifier.

So

const(int* delegate()) dg = ...;
dg(); // should be error

const(int* delegate()immutable) dg = ...;
dg(); // this is fine

Note that the obvious idea of saying that the qualifier of the delegate 
transitively applies to the opaque delegate context does not work, 
because e.g. the delegate context cannot implicitly pick up a `const`, 
as the function pointer will access the context in a way that is typed 
mutable.


2. nested functions of `pure` functions are forced to be `pure`, but 
this is nonsense, they should just infer purity, like it is done for @safe:

int foo(){ return 2; }

void main()pure{
     enum x=(()=>foo())(); // Error: `pure` delegate `tt.main.__lambda1` 
cannot call impure function `tt.foo`
}
(I picked this example because here it is obvious that the error is 
nonsense, but this is a more general problem and not restricted to CTFE.)

In contrast, it works perfectly fine for @safe:

int foo(){ return 2; }

void main()@safe{
     enum x=(()=>foo())(); // ok!
}

I think it is terrible that the implementations of `@safe` inference and 
`pure` inference were allowed to diverge at all.


3. The same problem exists for @nogc, but the fix is easy. Here it is:
https://github.com/dlang/dmd/pull/9922#issuecomment-499544100

(I will create a pull request eventually, unless someone beats me to it. 
I would still need to write some tests.)


4. nested functions should infer `const`, `immutable`, `shared` and 
`inout` qualifiers based on the qualifiers of variables that they capture.


5. it should be possible to drop delegate context qualifiers:

int delegate()const dgc;
int delegate() dgc2=dgc; // this is correctly accepted
int delegate()immutable dgi;
int delegate() dgi2=dgi; // this is rejected incorrectly
int delegate() dgi3=()=>dgi(); // ugly workaround
int delegate()shared dgs;
int delegate() dgs2=dgs; // this is rejected incorrectly
int delegate() dgs3=()=>dgs(); // ugly workaround


6. `inout` should be implemented in a way that prevents scope confusion 
by design.


More information about the Digitalmars-d mailing list