"Thomas F. Burdick" <tfb@...> writes:
> There's a thread on comp.lang.lisp where someone's asking about how to
> micro-optimize his code in LispWorks, and it got me wondering about
> how to do that in SBCL. I don't remember ever seeing this in the
> CMUCL manual, but I apologize if it's covered there. So, if I had an
> inner loop that I couldn't get the compiler to produce sufficiently
> optimized code for, how would I go about writing it in assembly?
It's not covered in the manual, and I doubt it's officially supported
either. But it's something I spent some time with recently, so here's
the short guide. Note that this is largely based on experimentation
and looking at the source code, so is quite likely all wrong. People
who know more than me are welcome to chime in.
1) you need a VOP
A VOP is a Virtual OPeration, which is (more or less) the basic thing
that the compiler backend works on. grep compiler/target/*.lisp in
SBCL source for lots of examples.
Here's (part of) one of the ones I wrote for thread switching support:
(:args (from :scs (sb-vm::sap-reg)))
;; splat reg_nl0
(inst bis from from (regtn 16))
(inst ldq (regtn 28) (+ 24 (* 8 28)) (regtn 16))
(inst ldq (regtn 29) (+ 24 (* 8 29)) (regtn 16))
(inst ldq (regtn 30) (+ 24 (* 8 30)) (regtn 16))
(inst ldq (regtn 16) (+ 24 (* 8 16)) (regtn 16))
This is for the Alpha backend, so if you don't recognise the
mnemonics, that's why. One possible reason, at least. Alternatively
you're reading this in rot13, or you need to turn the contrast up.
The vop is called LOAD-SOME-REGISTERS-FROM-THREAD. The keywords -
:args - the VOP has an argument FROM which it expects to find in a
SAP-REG. This is a storage class; you can see how these are set up in
src/compiler/target/vm.lisp - in this case a sap-reg is any
:arg-types - you have to call this with a SYSTEM-AREA-DESCRIPTOR
:translate - you can call this vop by writing
(load-some-registers-from-thread foo) . Note that this doesn't have
to be the same thing as the VOP name. For example, if you look at
compiler/alpha/cell.lisp to see that the instance-length VOP would
actually get called as (%instance-length instance)
:generator is where the "cost" of the operation (if you have several
VOPs to do the same thing, I think this gets used to select between
them. Tends to relate to the number of instructions that follow it,
but can be anything you like) and the actual assembler to run.
Register arguments are what we call TNs. Once back in the mists of
time TN actually stood for something, but I don't know what (I
vaguely recall reading that it was "Temporary Name", but I can't find
any documentation any more to confirm this) - but anyway, look in your
vm.lisp to find out which registers are named and how. Alpha has
(defregtn zero any-reg)
(defregtn null descriptor-reg)
(defregtn code descriptor-reg)
(defregtn alloc any-reg)
(defregtn bsp any-reg)
(defregtn csp any-reg)
(defregtn cfp any-reg)
(defregtn nsp any-reg)
defregtn is a macro which creates TNs called zero-tn, null-tn,
code-tn, etc etc. In the VOP I've shown I needed to deal with other registers
as well (on thread switch we're presently saving and restoring _all_
registers). Not knowing the "approved" way to do this, if there is
one, I defined a regtn macro:
(defmacro regtn (offset)
`(make-random-tn :kind :normal
:sc (sc-or-lose 'descriptor-reg) :offset ,offset))
so I could just say (regtn 28) for register number 28. This is
probably evil not least because it's declaring them all as descriptor
regs and they're not, but I can fix that up another time.
You can see more exciting stuff about TNs in compiler/vop.lisp
2) you need to make it a known function.
(sb-c:defknown load-some-registers-from-thread (system-area-pointer) (values))
3) Maybe it's going to be a function itself, maybe you want it to be
inlined. (declaim (inline ...)) works how you'd expect it to.
4) There you go!
http://ww.telent.net/cliki/ - Link farm for free CL-on-Unix resources