[Openmcl-devel] CCL LAP info...

Jon S. Anthony j-anthony at comcast.net
Sun Feb 14 18:11:00 PST 2010


On Sun, 2010-02-14 at 12:58 -0700, Gary Byers wrote:
> 
> On Sun, 14 Feb 2010, Jon S. Anthony wrote:
> 

> > Thanks, that is some valuable info!  BTW, I've seen TCR and this fn
> > being "established" and such, and have wondered what they are being used
> > for/as.  Couple sentence description?
> 
> TCR = "Thread Context Record" - lisp-specific thread-local data.  If the
> OS allows us to, we try to keep a thread's TCR in an otherwise-unused segment
> register on x86.
> 
> %fn = the current function.

Valuable input!

> rme did a blog post on how CCL implements
> closures a month or so ago:
> 
> <http://ccl.clozure.com/blog/>
> 
> which explains a lot about the physical structure of functions (closures
> and others) in x86 ccl.

Thanks a lot - this is very good stuff.


> The %fn register is mostly used to reference lisp objects as constants.
> A function that references the symbol X will probably contain an
> instruction that looks something like:
> 
>    [12]    (movl (@ 'X (% fn)) (% arg_z))
> 
> The disassembler recognizes that as a constant reference and displays
> the constant symbolically; the actual instruction looks more like:
> 
>    [12]    (movl (@ n (% fn)) (% arg_z))
> 
> where N is an integer whose value is "the number of bytes from the
> start of the current function to the place where the reference to X
> is stored in the function's constants".  Unless you're debugging
> the assembler/LAP, the value of N isn't very interesting; if you're
> writing LAP code, you can use the (@ 'foo (% fn)) syntax to both
> ensure that a constant reference will be allocated for FOO and to
> determine the correct value of N to encode in the instruction.
> 
> "the current function" changes every time a function is called or
> returned to.  (You can think of this as meaning that %fn is an
> implicit argument to each function, or that %fn is saved and restored
> by the caller.)  We don't actually save it, but we do need to restore
> it (as the first thing that a function does on entry and as the first
> thing that a function does after it returns from a function it calls.)
> The instruction that's used to (re-)establish the %fn register is a
> little weird and has to be handled specially on ia32; the disassembler
> shows it as (recover-fn), and ccl:compiler;X86;x86-lapmacros.lisp defines
> a LAP macro of the same name.
> 
> An initial recover-fn pseudo-instruction gets generated automatically;
> other lap macros defined in the file mentioned above ensure that
> a RECOVER-FN happens after function calls.  (Aside from allowing a
> function to reference constants, %fn is important to the GC, which
> uses the RECOVER-FN pseudo-intstructions to identify the function
> that contains them; the use of RECOVER-FN is not optional.)

All of this is quite enlightening and very valuable info.  The
description of %fn and the RECOVER-FN clears up several fuzzy bits in my
understanding.  Many thanks!


> > So, this just returns the stack arg (arg1) in standard return reg argz
> > and then pops off the arg1 and 2 extra reserved (alignment??) words.
> 
> If the caller pushes any outgoing arguments (e.g., passes more than the
> 2 register args on ia32), it has to first reserve space on the stack 
> that the callee can use to establish a stack frame.  (This is a bit hard
> to concisely explain and may be a bit hard to understand, but it has to
> do with the fact that functions on X86 are always entered with the return
> address on top of the stack and that can be awkard (especially if the callee
> can be called with different numbers of arguments.)

So, the stack on entry is:

TOS       ----> RA
...       ----> Stack args if any (right most on top?)
Reserve1  ----> -21
Reserve2  ----> -21


> Reserving space for the callee's canonicalized stack frame involves
> pushing two words before any stack arguments are pushed.  The value of
> each of those two words is conventionally the value of
> TARGET::RESERVED-FRAME-MARKER; you could actually push any legal value,
> but using that marker makes it easier to recognize outgoing function
> calls in backtrace.
> 
> ? (defun foo () (abs (bar 1 2 3)))
> FOO     ; and a warning about BAR, probably.
> ? (disassemble *)
> 
> ;;; (bar 1 2 3)
>    [12]    (pushl ($ -21))       ; x8632::reserved-frame-marker
>    [14]    (pushl ($ -21))       ; x8632::reserved-frame-marker

Ha!  I've been wondering what that -21 stuff was!


>    [16]    (pushl ($ 4))         ; 1
>    [18]    (movl ($ 8) (% arg_y)) ; 2
>    [23]    (movl ($ 12) (% arg_z)) ; 3
>    [28]    (movl ($ 12) (% temp1)) ; 3 args
>    [33]    (movl (@ 'BAR (% fn)) (% temp0))
> 
> 
> >
> >
> >> ? (defun 3-arg-caller ()
> >>     (3-arg-taker 1 2 3)
> >>     'foo))
> >> 3-ARG-CALLER
> >> ? (df *)
> >
> > Simple, but handy!
> >
> >
> >> ;;; (defun 3-arg-caller () (3-arg-taker 1 2 3) 'foo)
> >>   [0]     (recover-fn)
> >>   [5]     (testl (% temp1) (% temp1))
> >>   [7]     (jne L66)
> >>   [9]     (pushl (% ebp))
> >>   [10]    (movl (% esp) (% ebp))
> >>
> >> ;;; (3-arg-taker 1 2 3)
> >>
> >>           ;; here the caller reserves space for a frame
> >>   [12]    (pushl ($ -21))
> >>   [14]    (pushl ($ -21))
> >>           ;; pass the first parameter on the stack
> >>           ;; (the number 4 represents fixnum 1)
> >>   [16]    (pushl ($ 4))
> >
> > This is very interesting - this seems to indicate that CCL uses the
> > _low_ order bits as the tag bits?  That would explain some things...
> 
> I'm not aware of a native CL implementation that doesn't use the low
> bits for tagging.  (MCL 1.0 didn't, but that was over 20 years ago.)

Which only goes to show how much I know :0.  And after you mention it,
thinking about it for like 5 seconds reveals why this makes sense...

/Jon





More information about the Openmcl-devel mailing list