local refs

Stephen Weeks MLton@sourcelight.com
Fri, 7 Dec 2001 11:24:53 -0800


> And the reason we do a copyCurrent at the beginning of the program
> is because spawning a function f should execute f in essentially an
> empty stack;

Yes.

> when f finishes executing, control doesn't resume at the point of
> the spawn (if f raises or doesn't exit properly, we get nasty
> messages);

Yes.  This is one of those places where we rely on the basis library
implementation (lines 49-56 of thread.sml) to avoid seg faults.

> likewise, f's execution shouldn't keep live the stuff on the stack at the
> point of the spawn.

Right.
 
> > BTW, there is still the usual mess with separating copyCurrent and
> > copyToThread into two pieces, one that does the copy, and the other
> > that extracts it from saved.  But that's an orthogonal issue.
>
> Right; invokeRuntime's don't return arguments, because the invocation
> might switch threads, and we don't have anywhere to stash the return
> result until we get back to the thread that wanted it.  So, we store the
> return result in globals and fetch them with ffi's.  This, in turn, only
> works out in this case because we ensure that no other thread trashes the
> stored values by using atomicBegin/End.  So, maybe a better
> correspondence is:
> 
> > 	    val copyCurrent: unit -> preThread
> corresponds to:  (atomicBegin (); copyCurrent () ; saved (); atomicEnd ())

Yes, this is done in cont.sml.  It is not done in thread.sml because
the call to copyCurrent there happens before any threads (or signal
handlers) exist.

> > 	    val copyToThread: preThread -> thread
> corresponds to:  (atomicBegin (); copy () ; saved (); atomicEnd ())

Yep.  The uses of Thread.copy in cont.sml and thread.sml are wrapped
in atomics.

There is one other issue about the separation that is ringing alarms
in my brain.  Notice that all ot the uses of Thread.saved immediately
follow the call to copy or copyCurrent, except for one.  In
Cont.callcc, the use of Thread.saved is carefully placed down the
Original branch of the case instead of immediately after the
Thread.copyCurrent.  I remember moving it down there at some point.

Aha -- it's there because it only makes sense to call Thread.saved ()
after the original call to copyCurrent.  When threads/conts return
there later, a call to Thread.saved doesn't make sense.  What a mess.
I would think that for later returns, gcState.savedThread would be ==
0x1, so it shouldn't matter.  But I have some vague recollection of a
segfault or assertion failure when the Tread.saved was before the
branch.

Any thoughts on whether it matters or not?  If not, then I would like
to make the uses of (Thread.copyCurrent (); Thread.saved ()) in
cont.sml and thread.sml consistent.  Either both calls to Thread.saved
are before the case or both are down the original branch.

> > A local ref can be flattened if all uses are assigns or derefs and it
> > all uses that are live across a call to Thread_copyCurrent are of the
> > same flavor.
> > (which I believe is your original definition, modulo terminology :-)
> > 
> > The reasoning behind the flattening rule is that even if blocks are
> > multiThreaded, because threads cannot be copied at arbitrary points, a
> > ref can only be shared if a copyCurrent happens in the thread after
> > the ref's creation.
> 
> I think we need "live across a call to a potential Thread_copyCurrent", by
> which I mean either an actual call to Thread_copyCurrent or a non-tail
> call to a function that reaches a call to Thread_copyCurrent.

Agreed.  So we first compute a fixed-point on functions to determine
whether or not they call Thread_copyCurrent.  Then, backpropagate ref
uses to determine if they are live at copy points.  All as you
described before.

> > Unfortunately, the existence of the Thread.copyCurrent at the
> > beginning of the program kills localization, since every label and
> > function (except for main) become multi-used.  One possible fix to
> > this would be to have a new primitive for creating a raw thread that
> > is only used by Thread.spawn, hence removing the copyCurrent from the
> > beginning of the program (leaving copyCurrent only used by callcc).
> > But I'm not sure offhand what that primitive should look like.  I
> > think I'll sleep on it.  Over and out.
> 
> That is somewhat unfortunate.  You mean something like a primitive
> threadNew that essentially returns an empty stack? 

Yeah, but what I can't see is how to get the code started running in
the new thread.