June 21, 2019
On 6/21/2019 1:17 AM, Manu wrote:
> I agree it's 'theoretically' valid; that's why I suggested it and the
> construct makes sense.
> How would you write code that escapes the scope reference to another
> thread? This must require some @trusted code to achieve sharing
> between threads; at that point, the @trusted function is responsible
> for re-instating the thread-locality guarantees (which are trivial to
> implement). If you don't do @trusted mischief, there's no way to
> migrate the value across threads, so your synchronisation issue
> shouldn't exist if no @trusted code exists.

I attempted to explain this before. The trouble does NOT come from the shared function escaping a reference. It comes from the lack of memory coherency between threads.

Scope says NOTHING about synchronizing access to shared memory. NOTHING. Just because there's no longer a reference to the shared memory location from another thread does not mean all the memory caches are synchronized. For that, synchronization is needed, and your load of thread local `x` does NOT include the necessary synchronization.

If `x` was types as shared, the compiler WOULD include the necessary synchronization when reading it. That's what atomic reads are for. It's the entire point of atomic reads.


June 21, 2019
On Fri, Jun 21, 2019 at 7:05 PM Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On 6/21/2019 1:17 AM, Manu wrote:
> > I agree it's 'theoretically' valid; that's why I suggested it and the
> > construct makes sense.
> > How would you write code that escapes the scope reference to another
> > thread? This must require some @trusted code to achieve sharing
> > between threads; at that point, the @trusted function is responsible
> > for re-instating the thread-locality guarantees (which are trivial to
> > implement). If you don't do @trusted mischief, there's no way to
> > migrate the value across threads, so your synchronisation issue
> > shouldn't exist if no @trusted code exists.
>
> I attempted to explain this before.

And we heard you.

> The trouble does NOT come from the shared
> function escaping a reference. It comes from the lack of memory coherency
> between threads.

There is no "between threads" unless you have already entered @trusted land.
The reason for my proposal in the OP is precisely to prevent passing
data to other threads, that's the key that makes this promotion safe.

Can you show how you managed to get a ref to another thread without entering @trusted?

> Scope says NOTHING about synchronizing access to shared memory.

Yes, obviously. But the point is to prevent that situation occurring in the first place. There's no possibility of the synchronisation issue arising unless there's some way to escape a scope ref to another thread, which scope prevents.

Can you show how to escape a scope ref to another thread?

> NOTHING.

No really, we get it. This isn't a revelation to anyone.

> Just
> because there's no longer a reference to the shared memory location from another
> thread does not mean all the memory caches are synchronized. For that,
> synchronization is needed, and your load of thread local `x` does NOT include
> the necessary synchronization.

So, as I attempted to explain before; there ARE NO OTHER THREADS
unless you engage in @trusted activity.
There are no synchronisation issues unless there are other threads,
and there are no other threads because you couldn't possibly get a
reference to another thread.
Can you show how you escaped a reference to another thread without
entering @trusted territory?

Now, assuming we intend to write a @trusted machine to implement a parallel for, it's trivial to insert the 4 memory barriers to maintain data consistency. This is fine, we're in unsafe land, and there is no way to get to this situation without going unsafe.

> If `x` was types as shared, the compiler WOULD include the necessary synchronization when reading it.

It had better not. I don't believe it can know how to do that correctly. I don't think a type qualifier is even remotely enough information for the compiler to implement data fencing correctly and efficiently.

> That's what atomic reads are for. It's the
> entire point of atomic reads.

We're not talking about atomic reads here, we're talking about memory barriers.

`shared` just needs to separate thread-local from shared, and make
transfer between those two states unsafe. Then we can write the
libraries we need.
Remove all access from shared, then the only way we can do anything
with shared is to cast it away, which is a @system operation and as
such, places responsibility on us as library authors to do the right
stuff.

Novices don't write threading libraries. They tend to be very small in
surface area, and written once. It'll be fine, and it gives us the
control we need.
Anything more from the language at this very early stage is an over-reach.

If you want to explore @safe expansions to shared, then propose them as follow up, because they will be very controversial, whereas the foundation stuff is uncomplicated and mostly non-controversial. `shared` the language primitive is just not safe, and let us write the libraries.
June 21, 2019
On Tuesday, 18 June 2019 at 20:05:25 UTC, Walter Bright wrote:
> On 6/18/2019 6:29 AM, Steven Schveighoffer wrote:
>> Seems like it would be safe,
>
> When dealing with concurrency, that's like throwing a bunch of chemicals into a vat and saying it seems safe to drink :-)

That is precisely what many corporations do on a daily basis. You know rice is the #1 crop in the world and much of it is bleached... you know what they use for bleaching? And I'm sure your intelligent enough to know that no reaction or cleaning process is 100%....
June 21, 2019
On Friday, 21 June 2019 at 12:07:06 UTC, Manu wrote:
> Novices don't write threading libraries. They tend to be very small in
> surface area, and written once. It'll be fine, and it gives us the
> control we need.
> Anything more from the language at this very early stage is an over-reach.

Sorry but I don't think this is true, or at least need some facts/numbers.

I've seen "novices" or different type of people doing things that they shouldn't. For example, in the last 2 places that I worked, I saw management put developers (Like Desktop) without any experience in web to write Web APIs and the result was terrible, but they need to do otherwise they would be fired.

So just say novices barely it's to shallow.

By the way even experienced programmers can commit mistakes, and they way you talk you assume they not, and bugs are out there in most software and even from big companies.

Matheus.
June 22, 2019
On Fri, Jun 21, 2019 at 11:27 PM matheus via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Friday, 21 June 2019 at 12:07:06 UTC, Manu wrote:
> > Novices don't write threading libraries. They tend to be very
> > small in
> > surface area, and written once. It'll be fine, and it gives us
> > the
> > control we need.
> > Anything more from the language at this very early stage is an
> > over-reach.
>
> Sorry but I don't think this is true, or at least need some facts/numbers.
>
> I've seen "novices" or different type of people doing things that they shouldn't. For example, in the last 2 places that I worked, I saw management put developers (Like Desktop) without any experience in web to write Web APIs and the result was terrible, but they need to do otherwise they would be fired.
>
> So just say novices barely it's to shallow.
>
> By the way even experienced programmers can commit mistakes, and they way you talk you assume they not, and bugs are out there in most software and even from big companies.

1. This discussion only applies when writing @system code; I think
it's assumed that you take responsibility for your mistakes, no?
2. I'm not against @safe improvements, I just think that's VERY hard
to design, and we should have a useful low-level core in the language
before spending years arguing about some semantics for @safe
interactions (which I'm not sure exist; I think they are libraries).
June 21, 2019
On Friday, 21 June 2019 at 12:07:06 UTC, Manu wrote:
> There is no "between threads" unless you have already entered @trusted land.

@trusted doesn't break the guarantees advertized by the type system; when it does, it's a bug that should be fixed (in code, not in language). It's only the compiler is not smart enough to verify that @trusted code uphold the type system.
June 21, 2019
On Friday, 21 June 2019 at 17:32:08 UTC, Kagamin wrote:
> On Friday, 21 June 2019 at 12:07:06 UTC, Manu wrote:
>> There is no "between threads" unless you have already entered @trusted land.
>
> @trusted doesn't break the guarantees advertized by the type system; when it does, it's a bug that should be fixed (in code, not in language). It's only the compiler is not smart enough to verify that @trusted code uphold the type system.

@trusted is allowed to do whatever it wants internally, as long as it provides a *safe* interface. I believe that's what Manu's getting at in regards to his @safe/@trusted distinction in regards to threading.
June 21, 2019
On 20.06.19 03:30, Walter Bright wrote:
> On 6/19/2019 3:04 PM, Timon Gehr wrote:
>> I was talking about this:
>>
>> int x0;
>> void fun(scope ref shared(int) x1){
>>      assert(&x0 is &x1); //uh, oh
>>      // ...
>> }
>> fun(x);
> 
> I see.
> 
> 
>> 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.
> 
> `scope` only guarantees no references to the scoped variable exist following the exit of the function. What happens inside is anything goes as long as that invariant is maintained.

So in purely @safe code without any calls to @trusted functions you can't send that reference anywhere.

> That would include starting up a new thread that can access the scope variable, as long as the thread terminates its use of that variable before the function exits.
> ...


Yes, but terms such as "before" are tricky when multiple threads are involved. Semi-formally, what you are saying is that there has to be a path from each write access in each spawned thread to the return of the function in the happens- before graph. I don't think it is possible to establish this without making it valid to access an unique reference without further memory barriers. I don't think you can allow a race to exist between the function return and any write accesses to `scope`'d parameters.


> 
>> 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.
> 
> `scope` does not offer such guarantees. It only guarantees no leaks of the reference that survive the end of the function. It does not guarantee synchronization of memory caches.
> ...

My complaint is that the last two sentences appear mutually contradictory.

>>
>> 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.
> 
> That is not what `scope` does,

We are using the same definition of `scope`. You haven't however provided a formal definition of what "before" means.

> but the example is still correct because the language does not say that x can be converted to a local, and so it is not converted, and the memory synchronization of x is maintained and it works.
> ...

(Obviously that example works. That's not the point.)

> 
>> 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.
> 
> `scope` does not add such barriers. The implementer of foo may add a fence to do that, but the compiler doesn't know that and cannot rely on it.
> 
> 
>> 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?
> 
> AFAIK, another fence is required upon return of foo and before reading x. It's not just about whether an extra reference exists, it's about when the memory cache coherency happens.
> ...


I don't think it makes any sense to claim "no other reference exists" at a specific program point if at that point you didn't properly synchronize with parties potentially still holding such a reference.

June 21, 2019
On 6/21/2019 5:07 AM, Manu wrote:
> There is no "between threads" unless you have already entered @trusted land.
> The reason for my proposal in the OP is precisely to prevent passing
> data to other threads, that's the key that makes this promotion safe.

Scope doesn't do that. I keep saying this. But the premise is strange, anyway, as why would you want to convert a reference to shared if not to bass it to another thread?


> Can you show how you managed to get a ref to another thread without
> entering @trusted?
There's no such thing as a ref to another thread. You can pass a ref to another thread in a scope function, as long as the ref doesn't survive the scope of foo().

The compiler cannot check that, though, and so cannot bless the implicit conversion to shared. I've said this 4 or 5 times now.


> There's no possibility of the synchronisation
> issue arising unless there's some way to escape a scope ref to another
> thread, which scope prevents.

You're inventing semantics for scope that I've repeatedly told you are not there.


> We're not talking about atomic reads here, we're talking about memory barriers.

I recommend watching:

https://www.youtube.com/watch?v=A8eCGOqgvH4
https://www.youtube.com/watch?v=KeLBd2EJLOU

as you and I are not speaking the same language. In particular the sections on how atomics create sequentially consistent data-race-free reads and writes of variables. Not needing to read x atomically after foo() ends is just plain wrong.
June 22, 2019
On Sat., 22 Jun. 2019, 9:40 am Walter Bright via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On 6/21/2019 5:07 AM, Manu wrote:
> > There is no "between threads" unless you have already entered @trusted
> land.
> > The reason for my proposal in the OP is precisely to prevent passing data to other threads, that's the key that makes this promotion safe.
>
> Scope doesn't do that. I keep saying this. But the premise is strange,
> anyway,
> as why would you want to convert a reference to shared if not to bass it
> to
> another thread?
>
>
> > Can you show how you managed to get a ref to another thread without entering @trusted?
> There's no such thing as a ref to another thread. You can pass a ref to
> another
> thread in a scope function, as long as the ref doesn't survive the scope
> of foo().
>
> The compiler cannot check that, though, and so cannot bless the implicit conversion to shared. I've said this 4 or 5 times now.
>
>
> > There's no possibility of the synchronisation
> > issue arising unless there's some way to escape a scope ref to another
> > thread, which scope prevents.
>
> You're inventing semantics for scope that I've repeatedly told you are not there.


I've asked you 5 or 6 times to show how to pass a pointer to another thread
without violating scope.
Can you show it? You keep saying you can.


> We're not talking about atomic reads here, we're talking about memory barriers.
>
> I recommend watching:
>
> https://www.youtube.com/watch?v=A8eCGOqgvH4 https://www.youtube.com/watch?v=KeLBd2EJLOU
>
> as you and I are not speaking the same language. In particular the
> sections on
> how atomics create sequentially consistent data-race-free reads and writes
> of
> variables. Not needing to read x atomically after foo() ends is just plain
> wrong.


You need an acquire fence before the function returns, it's trivial.
In many cases though you don't need one, because you may have already
required one to determine that the workload is complete in the first place.
Don't attempt this in the @system language. Just let it be, we know what
we're doing.

If you want @safe expansion, that's future work, and I personally couldn't care less about that work. I can create safe libraries with @trusted functions, and I could do it _right now_.

>