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 09:19:26 UTC 2018


On Sunday, 11 November 2018 at 23:29:13 UTC, Rubn wrote:
> On Sunday, 11 November 2018 at 19:21:10 UTC, 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.
>>
>> Is implicit conversion to the shared part of immutable 
>> actually safe, or does it secretly hide data races?
>>
>> Another way of putting this:
>> Is `ret` secretly shared due to implicit conversion to 
>> immutable?
>
>
> Maybe I'm missing something but:
>
> void bar()
> {
>     immutable(int)* d = null;
>     while (d is null)
>     	d = g.atomicLoad;
>     assert(*d == 3); // READ
>     assert(*d == 3); // READ - these two reads will always be 
> the same
> }
>
> They will always be the same as what you are changing is the 
> pointer. But here you atomicLoad() the pointer. So you will 
> always have the same pointer. The value will only change if you 
> do another atomicLoad or change the value that the pointer 
> points to (which you don't do anywhere in your example).

The MODIFY line changes the value where the pointer looks. I'm 
asking whether it's possible that either READ happens before 
MODIFY (which would be v. bad). Using two reads instead of one 
was just to turn the (potential) ordering bug in to a (potential) 
breach of immutability.

> int* foo() /* pure */
> {
>     static auto ret = new int; // Note: Static here
>     *ret = 3;  // MODIFY
>     return ret;
> }
>
> You might have a problem if "ret" here is static. But then I 
> don't think the function would be pure anymore. Or rather at 
> the very least it shouldn't be pure.

Yes, that isn't pure, so the implicit conversion to immutable 
wouldn't work. It also doesn't compile at all due to using 
non-constant ctfe pointer as an initialiser.


More information about the Digitalmars-d mailing list