[LispM-Hackers] Function calling

James A. Crippen james@unlambda.com
15 Oct 2001 18:00:55 -0800


Paul Fuqua <pf@ti.com> writes:

>     Date: 15 Oct 2001 16:27:16 -0800
>     From: james@unlambda.com (James A. Crippen)
>     
>     We need to implement M-memory and A-memory.
> 
> I wrestled with this question back when I was considering an
> implementation of my own.  The key issue was whether it was possible to
> faithfully emulate the Explorer if I never went below the
> macroinstruction level.  Call instructions were part of the issue, as
> were some of the weirder miscops and the whole GC thing.

Aha, I knew you'd weigh in here.  Your advice is quite welcome.

I'm having a hard time discerning where the separation between the
macrocode and microcode lies.  I thought I had a pretty clear view of
it, but the deeper I go the harder it is to tell.

What I'm trying to think of now is that all visible macrocode
behavior is stuff we should implement.  The internal operations for
each macrocode instruction is microcode-specific, and we shouldn't try
for exactness here, but efficiency.  So the dividing line is between
behavior that is visible to Lisp, and behavior that isn't.  Visible
behavior is macrocode stuff we should emulate, invisible behavior is
non-visible stuff we shouldn't emulate.

That's a simple and fairly consistent view, but the problem is that as
I read the Lisp sources I see again and again where Lisp (or parts of
it, anyway) know all sorts of details about how the microcode level of
the machine works.  Let's take the stack group structure for instance.
In the dynamic section of the SG structure are slots for the micro-pc
address for trap signalling, saved m-flags, the saved vma-register,
and a bunch of other really low level cruft.  When we try to activate
a stack group that has all this information, how do we interpret it?
Who put that data there?  Can we be certain that there's not some
random magic Lisp function that will twiddle those values when we're
not looking?

I guess the real problem is verifying that parts of Lisp won't care
about the microcode level cruft that is visible to Lisp.  If we can
blindly assume that Lisp doesn't give two shits about what the
saved-m-flags are and won't modify that value (or anything else like
it) with the expectation that something different will happen, why
then our troubles would be over.  But I just can't tell from the data
that I have.

> Viewed from the macroop level, the Explorer is a simple stack machine.
> A call instruction is either one of the specific ones (CALL-1-PUSH,
> say), in which case the instruction itself indicates how many arguments
> are supplied and how to dispose of the result, or one of the general
> ones, in which case the preceding instructions have set up the stack,
> including leaving the call-info word on top.
> 
> In that view, the function pointer can be derived from the instruction
> (it's usually FEF-relative, so the initial pointer is in a slot in the
> function header, and it'll either point to a functional object or be a
> transparent pointer of some kind), the argument pointer (which points at
> the first argument) can be calculated easily, and the various bits of
> the call-info word are available when needed.

So we could calculate all this stuff at call time and perhaps store it
in some processor state structure for quick reference.  Which is I
suppose exactly what the microcode was doing, but in a roundabout,
hardwarily complexified way.

> A call instruction does only a few things:  sets the function state to
> point to the new function and its first instruction, adjusts the
> argument pointer, pushes a call frame (five words containing the old
> state), and adjusts the local pointer (top of stack after the call
> frame).  The call-info word is just a way of condensing details into a
> single word for the microcode, and the dispatching is just because we
> allowed so many things to be funcalled.
> 
> I'm rambling.

If that's rambling I'd love to hear more... :^)

> Anyway, my point is that I'm not sure we really need to get into the
> M-memory/VMA/microcode level to actually make things work.

I'd dearly love not to care about any of that cruft.  The more I look
at it the uglier it looks to implement.

From what I can tell by reading your message (and I haven't read it
*too* carefully) we still need the A-memory because a lot of state
and random flags and values get written there.  Mouse stuff, TV stuff,
etc.  Lisp seems to know a lot about things in the A-memory, as well.
We'd need to implement A-memory internally and then map its values to
the appropriate memory locations, or perhaps trap reads/writes on
those memory locations and return/set A-memory values.

>                         We need the PDL, PDL pointer, and PDL index.  The
>     PDL is 1K by 32-bit words.  These shouldn't be difficult.  I may
>     implement them in the next day or so.
> 
> The PDL *buffer* is a speed hack for the stack, a small cache of the top
> of it inside the processor.  It ought to be transparent, too, and
> shouldn't be visible at the macroop level, either.  The PDL as a whole
> is just the system stack.

Ah, so the RegPDL and SpecPDL should be in some memory region then,
right?  Yeah, SSDN 7.8.3.3 says that a computation's SpecPDL is
represented with an array of ART-SPECIAL-PDL.  It's just a Q array but
not all elements of it are valid (hence it needs protection from the
GC, I think).

The microcode visible action in the case of the PDLs is that if a PDL
needs to grow then it traps to the Error Handler which then allocates
a larger array and structure-forwards the old PDL to the new one.

>     The two components which trouble me are the MD and VMA registers.
> 
> At the microcode level, we'd write an address to VMA and a few cycles
> later the data would appear in MD (or we'd put something in MD and write
> to VMA a different way and the data would be written).  It's loads and
> stores at a finer degree of detail.  At the macrocode level, you
> wouldn't even see them.

Okay, they're ignorable then.  We can do a fetch atomically, where the
microcode had to wait some cycles.

[...]
> In terms of macroop destinations, D-INDS just meant that the result
> wasn't pushed, so the instruction was for effect rather than for
> value; however, you could still branch on the result in the next
> instruction.
> 
> Thus, "MINUSP D-INDS" would do something like pop the top of the stack
> and stick T in the magic register if it was a number less than zero, or
> NIL if it was positive, or trap if it wasn't a number.  "MINUSP D-PDL"
> would push the T or NIL on the stack.

Basically then the indicators could be implemented by an e3u32 stuck
into a random class.  Is it allowed to have *any* value or just T or
NIL?  If it's only a T/NIL value then it could just be a boolean
somewhere.  If it could be any value then I suppose we'd use an e3Word
on its lonesome.  From what I could see of usage of the indicators
it seemed to be boolean.

'james

-- 
James A. Crippen <james@unlambda.com> ,-./-.  Anchorage, Alaska,
Lambda Unlimited: Recursion 'R' Us   |  |/  | USA, 61.2069 N, 149.766 W,
Y = \f.(\x.f(xx)) (\x.f(xx))         |  |\  | Earth, Sol System,
Y(F) = F(Y(F))                        \_,-_/  Milky Way.