Very limited shared promotion
Timon Gehr
timon.gehr at gmx.ch
Wed Jun 19 22:04:49 UTC 2019
On 19.06.19 23:03, Walter Bright wrote:
> On 6/19/2019 6:45 AM, Timon Gehr wrote:
>> On 19.06.19 11:13, Walter Bright wrote:
>>>
>>> Using the resulting value of x:
>>>
>>> int x;
>>> void fun(scope ref shared(int) x) { ... }
>>> fun(x); // implicit promotion to shared in this case
>>> int y = x; // add this line
>>>
>>> And now there's a problem. The compiler thinks x is thread local, so
>>> it doesn't add synchronization to the read of x.
>>
>> Probably `fun` should be `immutable` or `shared` as well. Otherwise
>> there is no way to tell at the call site that it does not also capture
>> `x` mutably, in which case the implicit promotion couldn't be allowed.
>
> `scope` prevents capture of a reference to `x`, mutable or not, that
> persists after the return from `fun`.
> ...
I was talking about this:
int x0;
void fun(scope ref shared(int) x1){
assert(&x0 is &x1); //uh, oh
// ...
}
fun(x);
> ...
>>
>> The compiler can allow the implicit conversion because in @safe code
>> there is no way to send `scope` data to other threads and @trusted
>> code has to respect `scope` guarantees.
>
> The scope guarantees are NOT sequential consistency data-race-free
> guarantees for multithreaded access.
> ...
Right now, in `@safe` code it guarantees that there is _no_
multithreaded access, no? How do you leak a `scope` reference to another
thread? If there is _no_ multithreaded access, you don't get any data
races, so `scope` guarantees no data races on the `scope`d memory
location given that the reference wasn't accessible from multiple
threads before.
>
>>> In other words, adding `scope` does not guarantee SC-DRF (Sequential
>>> Consistency - Data Race Free).
>> I think it does. The challenge is to ensure that the implicit
>> promotion to `shared` doesn't result in `shared`/unshared aliasing. I
>> don't think there is a problem with implicitly casting back to
>> unshared, as the called function promises not to leak the references.
>
> It does not. A write to an atomic is:
>
> write variable
> read fence
>
> A read from an atomic is:
>
> read fence
> read variable
>
> SC-DRF of atomics absolutely depends on this. The example code does:
>
> thread 1:
> write x
> read fence
>
> thread 2:
> read x
>
> This is wrong wrong wrong from what I know about atomics.
> ...
Yes, what's above certainly does not work. My point was that that is not
what the code would do, at least not legally. If the above happens,
there would be some `@trusted` code somewhere that you can blame for it.
Sending a `scope` variable to another thread is a `@system` operation.
In order to ensure that `scope` guarantees are met, the `@trusted` code
that performs the send needs to add memory barriers that ensure that all
writes to the `scope` variable are visible on the current thread before
it returns. So on thread 2 you would actually also have barriers before
`x` is accessed again.
Let's consider a case _without_ implicit promotion:
shared(int) x=0; // x is shared all the way
static void foo(scope ref shared(int) x)@trusted{
// possibly send &x to other threads that mutate it and
// then forget about &x
// ...
}
foo(x);
int y=x; // read x
enforce(x==y); // read x again
My claim is that if the enforcement fails, this indicates a bug in
`foo`, because it violates guarantees you can derive from the fact that
`x` is a local variable that is only passed to some other code via a
`scope`d parameter: when `x` is read for the first time, we know that
actually no other thread can have a reference to it which it can use for
a write, so its value has to remain constant.
It should therefore be possible to read `x` without any further barriers
after `foo` returns. This is because `foo` already needs to ensure all
writes from other threads are visible.
It is possible that I am missing something (even though what you wrote
is not it). Is there a possible implementation of `foo` that would be
correct (in a language that does not have any implicit `shared`
promotion), but for which a data race on `x` would result if we didn't
add another fence before reading `x` after `foo` has returned?
>
> ...
>
> 5. When a data-race-free solution is found that doesn't follow fencing
> protocols, it doesn't work.
We are on the same page on this. :)
More information about the Digitalmars-d
mailing list