Struggling to implement parallel foreach...

Timon Gehr timon.gehr at gmx.ch
Sat Jun 15 04:05:29 UTC 2019


On 15.06.19 04:40, Manu wrote:
> On Fri, Jun 14, 2019 at 3:35 PM Timon Gehr via Digitalmars-d
> <digitalmars-d at puremagic.com> wrote:
>>
>> On 14.06.19 20:51, Manu wrote:
>>> On Fri, Jun 14, 2019 at 8:05 AM Kagamin via Digitalmars-d
>>> <digitalmars-d at puremagic.com> wrote:
>>>>
>>>> On Friday, 14 June 2019 at 09:04:42 UTC, Manu wrote:
>>>>> Right, exactly... the compile error should be at the assignment
>>>>> of the
>>>>> not-shared closure to the shared delegate. The error would be
>>>>> something like "can't assign `Context*` to `shared(Context)*`".
>>>>> So, the function can certainly exist, but for shared, you
>>>>> should get
>>>>> an error when you attempt to call it passing the closure to the
>>>>> function.
>>>>
>>>> The shared function attribute means that its context is shared
>>>> similar to object methods, the error is an attempt to implicitly
>>>> leak unshared data into that shared context, that's why indeed
>>>> function itself is incorrect. Declare the variable as shared and
>>>> it will work.
>>>
>>> No, you've misunderstood. The qualifier does NOT apply to the context
>>> as it should, that's the issue I'm reporting.
>>
>> There is a bug, but It appears you don't understand what it is. Why do
>> you insist on reporting the bug in a particular wrong way instead of
>> trying to understand what is actually going on?
> ...

(I'm going to ignore your other post, as it is just more rambling.)

Note that this part of the language is extremely poorly implemented in 
DMD right now. There are quite a few independent bugs plaguing local 
function and delegate qualifiers. But the fact that you can't access an 
unshared variable from a `shared` local function is by design; it is not 
one of those bugs. The fact that `const` on local functions does not 
work properly has no bearing on `shared` on local functions. DMD 
implements those things independently.

> So, what is actually going on?

(NOTE: I am first explaining the actual design here, not the buggy 
implementation. This would make your parallel `foreach` code compile. 
You can't argue against what is below on the basis that it is obviously 
nonsense because it prevents your parallel `foreach` from compiling.)

For member functions, the qualifiers affect the this pointer. I.e., you 
need to construct a differently-qualified receiver to call a `shared` or 
`immutable` member function. This is not the only behavior that would 
make sense. Qualifiers on member functions could also just restrict how 
that member function accesses other members.

For local functions, the qualifiers specify how the context is 
_accessed_, not how the entire thing is qualified. If your local 
function is `shared` that means the function may only access `shared` data.

void main(){
     int x; // not shared, yet part of stack frame
     shared(int) y;
     int foo()shared{
         // x=2; // error, x is not shared
         return y; // ok
     }
     // ...
}

Similarly, if your local function is `immutable`, it may only access 
`immutable` data. If your local function is `const`, it may only access 
`const` data, but as everything implicitly converts to `const`, it can 
actually access everything, it may just not modify it, accessed 
variables are `const`-qualified. (It's a bug in DMD that they are not.)

(Const is a special case because this is the only case where you can 
interpret what is going on as slapping the `const` qualifier on the 
entire context. It is not necessarily the best way to think about what 
is going on.)

This is the way this was intended to work, but apparently it was never 
fully implemented, leaving behind quite a few type system holes, for 
example:

void main(){
     int x;
     void bar(){
         x=2;
     }
     void foo()shared{
         bar(); // this shouldn't compile, but it does
     }
}

Local functions should _infer_ those attributes. So if your function 
(like the implicitly-generated lambda representing your `foreach` body), 
only accesses `shared` variables, it should be automatically 
`shared`-qualified.

> (I believe I'm aware what is going on, because behaviour is self-evident).

(You have demonstrated that this is not the case.)

> And more importantly, how is it useful? 

You have argued that there shouldn't be any way to call 
`shared`-qualified local functions. I don't understand how you can 
possibly think that behavior is useful. It would preclude your parallel 
`foreach` code from working!

The intended design is useful in the sense that it would make your 
parallel `foreach` code work in a compiler-checked thread safe way, 
because the compiler would simply automatically check that the loop body 
only accesses shared variables (or variables local to the loop body, of 
course).

> Why is it so useful that it should it violate default behaviour, and expectation?

It does not.

> I know how it's not useful.
> 

I am not defending the fact that your parallel `foreach` code does not 
compile! This has to be fixed.

But if it is fixed, and if D passes a shared delegate to your parallel 
`foreach`, where `shared` means it is @safe to call that delegate from 
other threads, how would that not be useful?


More information about the Digitalmars-d mailing list