[dmd-concurrency] tail-shared by default?

Steve Schveighoffer schveiguy at yahoo.com
Fri Jan 8 22:48:56 PST 2010


----- Original Message ----

> From: Walter Bright <walter at digitalmars.com>
> 
> Steve Schveighoffer wrote:
> > ----- Original Message ----
> > 
> >  
> >> From: Walter Bright 
> >> 
> >> Tail-shared doesn't work as soon as classes come into the picture (or any 
> reference types).
> >>    
> > 
> > class C
> > {}
> > 
> > void foo()
> > {
> >    shared C c = new shared(C);
> > }
> > 
> > In my scheme, c is tail-shared.  All reference types, including pointers and 
> class references, are tail-shared (note the subject line).  The only exception 
> is shared global data.
> > 
> >  
> 
> I hate to say it, but that simply doesn't work in any consistent manner. Types 
> must be composable, with composable properties. Having types dependent on where 
> they are declared is a pending disaster

I just realized the type isn't dependent, the semantics of access are.  The type of globals can still be tail-shared, but accessing a tail-shared global variable results in barriers around the access, since it lives in a shared space (see the model for GDATA below).

> (what about type aliases, template type 
> parameters, etc.?). Having no way to specify that c itself is shared is a 
> disaster (how would you compose a C* ? Where does the shared go?).

The only time the reference c itself uses shared semantics is when it's a stored as a global or static variable.  You can never specify that the *reference* is shared otherwise (to do so makes no sense to me).

aliases and template parameters all define shared the same way.  The non-tail-shared class reference does not exist as a type, because the only shared reference type is tail-shared.

For instance, in IFTI, if you did this:

foo(T)(T t) {writeln(typeid(T));}

class C
{
   int x;
}

void main()
{
   shared C c = new shared(C);
   foo(c);
   foo(&c.x);
   foo(c.x);
}

You should get:

shared(C)
shared(int)*
int

where the first 2 are tail-shared.

Here is another way to think about it.  Pretend all global shared data goes into a structure called GDATA, and there is exactly one singleton reference to that data.  That reference is a tail-shared reference.  It might look like this:

shared int x;
shared C c;

translates to

struct GDATA
{
   int x;
   C c;
}

__gshared shared(GDATA) * globalNamespace;

Any access to x or c becomes globalNamespace.x or globalNamespace.c.

This is exactly how I see the underlying implementation anyway.

if you pass an address to globalNamespace.x, that address is of type shared(int)*, not shared(int *), because shared(int *) makes the stack variable shared right out of the gate!  There is no reason to do that, *and* sharing stack data is a recipe for memory corruption.

shared has 2 features -- one is the type propogation to ensure you don't lose the fact you are dealing with shared data, and the other is the compiler's treatment of access to shared data (i.e. adding barriers).  The latter can be different between contexts of where a variable is declared because the compiler has access to that info, the former must be consistent across all contexts.  My scheme is consistent for the former, and uses the context available to the compiler to ensure the latter is safe.

> I've been down this path before. It doesn't work. I learned my lesson . I 
> know it's not obvious why it fails, which is why I spent so much time trying to 
> make it work.

It's a completely different idea than tail-const as an *option*.  Here, tail-shared is the *only* option.  I know it's not obvious that it's completely different from const, but it is.  Trust me, I understand the reluctance to look at it because I spent a lot of time trying to come up with a good way to do tail-const also.  Just try to find an example that proves it doesn't work, and you will see that it's unbreakable.

I think the idea is sound because you *must* pass a pointer to shared data into a function, you can't actually pass the real data, so the pointer itself that lives on the stack should *never* be shared, it's always thread local.  Sharing stack data would be more of a pending disaster in my opinion, since stack data is deallocated at will by returning from a function!

-Steve



      


More information about the dmd-concurrency mailing list