Hi Michael,

> After working on this for a little while, I came to the opinion that
> now would be a good time to introduce the "stack frame" object, and
> stuff all those additional arguments in there, to be optimised out of
> existence when safe to do so.
>
> Your suggestions are valuable, but they are beyond my capacity right
> now (they require too much platform-specific tweaking, and I only
> really know Linux/i386).

I hope this can be done without any platform-specific code at all.

> I did have an idea
> for a portable prototype of the "stack frame" object, so I've
> implemented that.  The overview follows:

When I said that stack frames should be objects I was thinking of
something slightly different.  (Begin digression.)  Here's a typical
stack frame:

| argN    | (higher addresses)
| ...     |
| arg1    |
| linkage |
| savedPC |
| savedFP | <- new FP
| locals  |
| ...     |
| ...     | <- new SP

In all the architectures I know about, the word immediately before the
saved frame pointer (i.e., '((void **)newFP)[-1]') is the first of the
local variables (temporary variables and/or saved argument registers
and/or saved callee-preserved registers and/or similar).  If we choose
to save a vtable pointer as the first (highest address) local, which
we are completely free to do, then the frame pointer looks like an
object pointer and we can send messages to it to manipulate it and all
the other frame 'objects' linked through their first oops.  Since any
object can grow in either direction (toward higher and/or lower
addresses) from its _vtable[-1] slot, we can store meta data at
newFP[-2], newFP[-3] etc., that helps the methods in the vtable (at
newFP[-1]) interpret the contents of the frame 'object' stored at
newFP[0], newFP[1], etc.  (End digression.)

What you are proposing is more like a 'message send description'.
It's quite different, but I like it a lot.  It groups receiver,
argument, context and lookup information into a single object; it
simplifies the ABI and potentially makes sends faster; and it helps a
lot with the long-term goal of unifying block objects, methods,
closures, and 'lambda's.  Best of all, it gives us all the advantages
that sometimes cause 'parallel' stacks to be invented without having
to invent a 'parallel' stack.

Let's try to simplify the implementation without losing any of the
generality, and rename __frame to __send to avoid any confusion with
the 'stack frames are objects' plan.  Here's what I ended up with:

struct __send
{
  oop           _vtable[0];
  oop           selector;
  oop           receiver;
  unsigned int  argumentCount;
  oop           closure;
  oop           state;
};

typedef oop (*_imp_t)(struct __send *send, oop receiver);

_imp_t _bind(struct __send *send, oop receiver);

#define _send(MSG, RCV, NARG, ARGS...)
  ({
    _out.send.selector= (MSG);
    _out.send.receiver= (RCV);
    _out.send.argumentCount= (NARG);
    _bind((struct __send *)&_out.send, _out.send.receiver)
((struct __send *)&_out.send.selector, _out.send.receiver, ##ARGS);
  })

_imp_t _bind(struct __send *send, oop receiver)
{
  /* closure = lookup send->selector in receiver's vtable
               and set fragment if using prototypes */
  send->closure= closure;
  send->state= using prototypes ? fragment : receiver;
  return closure->method;
}

oop myMethod(oop send, oop recevier, oop args...)
{
  struct { oop _vtable;  struct __send send; } _out= { send__vtable };
  ...
  result= _send(s_frobble, anObject, 1));
  ...
}

> If you like this approach, I can easily push stateful_self into the
> frame object as well.

I like the above for several reasons:

1. As much as possible is passed through the __send structure.  (The
   second argument to _bind() is needed to support super and similar.)

2. The ABI is effectively a struct return, and generates precisely the
   same code (modulo the initialisation of selector and receiver) in
   both caller and callee on the architectures I've tested (i386,
   ppc).

3. It's infinitely extensible (you can keep adding members to __send
   until the proverbial cows come home).

4. We're not limited to storing a simple argument count in the __send
   thing; we could store a structure (or a string) describing
   precisely the (static) types of the arguments: 'type safe' varargs,
   debugging, etc., utopia.

4. It's 'forward compatible' with the plan (when the C compiler goes
   up against the wall) to eliminate the current 'two-phase' send
   convention: the caller really should push everything (args
   included) _before_ calling _bind(), which can then just tail call
   the implementation of the method.  This will make dispatch on an
   arbitrary set of arguments much more efficient.  (IOW, _bind()
   should really be a method of, or inherited by, the selector object
   that compiled the original send site.)

> If you consider "as a method argument" to be a highly optimised
> version of thread-local storage, then I think I've done this. ;)

Registers and stack are essentially the same thing w.r.t. thread-local
storage.  The only real alternatives are data keyed on thread id
(expensive) or global memory swizzled by a cooperating thread context
switcher (won't scale to SMP).  I like the __send thing stored in the
stack + passing it through an explicit argument every bit as much as
stealing an 'available register' to pass it invisibly (in no small part
because the positions of the arguments don't change ;^p).

Cheers,
Ian


_______________________________________________
fonc mailing list
[email protected]
http://vpri.org/mailman/listinfo/fonc

Reply via email to