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