valid uses of shared

Artur Skawina art.08.09 at gmail.com
Fri Jun 8 07:57:15 PDT 2012


On 06/08/12 06:03, Steven Schveighoffer wrote:
> On Thu, 07 Jun 2012 20:58:13 -0400, Artur Skawina <art.08.09 at gmail.com> wrote:
> 
>> On 06/08/12 01:51, Steven Schveighoffer wrote:
>>>
>>> The following would be illegal:
>>>
>>> struct X
>>> {
>>>   shared int x; // illegal
>>>   shared(int)* y; // legal
>>>
>>>   shared(X) *next; // legal
>>> }
>>
>> Note that the type of 'x' in
>>
>>    shared struct S {
>>       int x;
>>    }
>>
>> should probably be 'shared(int)'.
>> Which lets you safely take an address of an aggregates field.
>
> That's one of the things I'm wondering about.  Should it be allowed?

Must be. We're talking about the next generation of a high level assembler,
not logo. :)


> 
> I agree that the type should be shared(int), but the type should not transfer to function calls or auto, it should be sticky to the particular variable.  Only references should be sticky typed.

The problem with this is that it should be symmetrical, IOW the conversion
from non-shared to shared would also have to be (implicitly) allowed.
A type that converts to both would be better, even if harder to implement.

>> And I'm not sure if marking a struct and class as shared would work
>> correctly right now, it's probably too easy to lose the 'shared' qualifier.
> 
> Right, I was thinking shared structs do not make sense, since I don't think shared members do not make sense.  Either a whole struct/class is shared or it is not.  Because you can only put classes on the heap, shared makes sense as an attribute for a class.
> 
> But then again, it might make sense to say "this struct is only ever shared, so it should be required to go on the heap".  I like your idea later about identifying shared struct types that should use synchronization.

Of course shared structs make sense, it's what allows implementing any
non-trivial shared type. 

   static Atomic!int counter;

inside a function is perfectly fine. And, as somebody already mentioned
in this thread, omitting 'static' should cause a build failure; right
now it is accepted, even when written as

   shared Atomic!int counter;

The problem? 'shared' is silently dropped. Move the counter from a struct
into a function after realizing it's only accessed from one place, forget
to add 'static' - and the result will compile w/o even a warning. 

> 
>>> If you notice, I never allow shared values to be stored on the stack, they are always going to be stored on the heap.  We can use this to our advantage -- using special allocators that are specific to shared data, we can ensure the synchronization tools necessary to protect this data gets allocated on the heap along side it.  I'm not sure exactly how this could work, but I was thinking, instead of allocating a monitor based on the *type* (i.e. a class), you allocate it based on whether it's *shared* or not.  Since I can never create a shared struct X on the stack, it must be in the heap, so...
>>>
>>> struct X
>>> {
>>>    int y;
>>> }
>>>
>>> shared(X) *x = new shared(X);
>>>
>>> synchronized(x) // scope-locks hidden allocated monitor object
>>> {
>>>    x.y = 5;
>>> }
>>>
>>> x.y = 5; // should we disallow this, or maybe even auto-lock x?
>>>
>>> Hm... another idea -- you can't extract any piece of an aggregate.  That is, it would be illegal to do:
>>>
>>> shared(int)* myYptr = &x.y;
>>>
>>> because that would work around the synchronization.
>>
>> That's too restrictive. It would overload 'shared' even more. If you
>> want that kind of synchronize magic to work, just allow:
>>
>>    shared synchronized(optional_locking_primitive) struct S {
>>       ...
>>    }
>>
>> And *now* 'x.y = 5' can do its magic, while '&x.y' can be disallowed.
>>
>>
>>    shared struct S {
>>       Atomic!int i;
>>    }
>>    shared(S)* p = ...
>>    p.i += 1;
>>
>> should work, so accessing fields must remain possible.
> 
> OK.  You are right, synchronized may be overkill for basic types.
> 
>>> auto would have to strip shared:
>>>
>>> auto myY = x.y; // typeof(myY) == int.
>>
>> Hmm, i'm not sure about this, maybe it should be disallowed; it could
>> only strip 'shared' from the head anyway.
> 
> Yes, head stripping.  I think it should be allowed.  For instance, if you wanted to read a shared double, and take the cosine of it, this should be allowed:
> 
> auto n = cos(sharedValue);
> 
> If it's not, the alternatives are:
> 
> auto n = cos(cast()sharedValue);
> 
> or
> 
> double v = sharedValue; // explicit removal of shared
> auto m = cos(v);
> 
> Neither of these look necessary, I think just allowing shared value types to automatically convert to non-shared versions works the best.

If 'shared(VT)' implicitly converts to VT, then

   auto myY = x.y; // typeof(myY) == shared(int)

would still be fine. So would

  auto n = cos(x,y); // assuming some weird cos() that works on ints ;)

But I'm not sure allowing these implicit conversions is a good idea.
At least not yet. :)

>>> This is definitely not a complete proposal.  But I wonder if this is the right direction?
>>
>> I think it is.
> 
> good.

It's a small step in the right direction.

artur


More information about the Digitalmars-d mailing list