A breach of immutability due to memory implicit conversions to immutable without synchronisation, maybe??
John Colvin
john.loughran.colvin at gmail.com
Mon Nov 12 14:54:12 UTC 2018
On Monday, 12 November 2018 at 09:11:04 UTC, John Colvin wrote:
> On Sunday, 11 November 2018 at 20:47:16 UTC, Steven
> Schveighoffer wrote:
>> On 11/11/18 2:21 PM, John Colvin wrote:
>>> Take a look at this (I think?) supposed-to-be-thread-safe
>>> code according to the rules of D:
>>>
>>> import std.stdio;
>>> import core.thread;
>>> import core.atomic;
>>>
>>> int* foo() pure
>>> {
>>> auto ret = new int;
>>> *ret = 3; // MODIFY
>>> return ret;
>>> }
>>>
>>> shared immutable(int)* g;
>>>
>>> void bar()
>>> {
>>> immutable(int)* d = null;
>>> while (d is null)
>>> d = g.atomicLoad;
>>> assert(*d == 3); // READ
>>> assert(*d == 3); // READ AGAIN
>>> }
>>>
>>> void main()
>>> {
>>> auto t = new Thread(&bar).start();
>>> immutable(int)* a = foo();
>>> g.atomicStore(a);
>>> t.join;
>>> }
>>>
>>> What stops the CPU executing this such that MODIFY happens
>>> between READ and READ AGAIN ?
>>>
>>> To aid in the thought experiment, imagine if we replace `*ret
>>> = 3` with `*ret = longComputation()`? It might help your
>>> reasoning about re-ordering if you consider `foo` inlined.
>>
>> Hm... I wouldn't expect the compiler would allow reordering
>> across a return boundary. Even if the inlining occurs.
>> Implicit conversion of pure return values depends on that.
>>
>> I don't know all the rules of reordering, but this would
>> definitely cause races if it was allowed to reorder the
>> storage of data in *d, and the storage of d into g.
>
> The compiler can definitely re-order over return boundaries in
> general, but perhaps there is some special logic to prevent
> mistakes in these cases?
>
>> Wait, wouldn't the atomicStore create a barrier, such that all
>> the code before it must execute?
>>
>> -Steve
>
> Not a full memory barrier on most (all relevant?) platforms.
Hmm, ok, so it seems that perhaps this only occurs with relaxed
memory ordering, because with acquire-release (or better) if bar
sees the result of main's write to g (i.e. it gets out the while
loop) then it must also see all side-effects of everything main
did before that write.
However, with relaxed memory ordering I'm now more convinced that
this would break immutability, which in turn means that the
oft-made statement "immutable data doesn't require
synchronisation" isn't true.
More information about the Digitalmars-d
mailing list