Slot-Value
**********

The ANSI Common Lisp standard specifies slot-value, (setf slot-value), slot-boundp and slot-makunbound for standard-objects, and furthermore suggests that these be implemented in terms of Metaobject generic functions slot-value-using-class, (setf slot-value-using-class), slot-boundp-using-class and slot-makunbound-using-class.  To make performance of these operators tolerable, a number of optimizations are performed, at both compile-time and run-time (Note that ,at present, slot-makunbound and slot-makunbound-using-class are not optimized in any of the ways mentioned below.).

Basic Implementation
====================

All of the following, while described in terms of slot-value, also applies to (setf slot-value) and to slot-boundp, and could in principle be extended to slot-makunbound.

The basic implementation of slot-value, following the suggestion in the standards document, is shown in ex:slot-value; the implementation of the other slot operators is similar.  The work to be done simply to arrive at the generic function call is already substantial: we need to look up the object's class and iterate over the class' slots to find a slot of the right name, only then are we in a position to call the generic function which implements the slot access directly.

     (defun slot-value (object slot-name)
       (let* ((class (class-of object))
              (slot-definition (find-slot-definition class slot-name)))
         (if (null slot-definition)
             (values (slot-missing class object slot-name 'slot-value))
             (slot-value-using-class class object slot-definition))))

The basic implementation of slot-value-using-class specialized on the standard metaobject classes is shown in ex:slot-value-using-class.  First, we check for an obsolete instance (that is, one whose class has been redefined since the object was last accessed; if it has, the object must be updated by update-instance-for-redefined-class); then, we acquire the slot's storage location from the slot definition, the value from the instance's slot vector, and then after checking the value against the internal unbound marker, we return it.

     (defmethod slot-value-using-class
         ((class std-class)
          (object standard-object)
          (slotd standard-effective-slot-definition))
       (check-obsolete-instance object)
       (let* ((location (slot-definition-location slotd))
              (value
               (etypecase location
                 (fixnum (clos-slots-ref (instance-slots object) location))
                 (cons (cdr location)))))
         (if (eq value +slot-unbound+)
             (values (slot-unbound class object (slot-definition-name slotd)))
             value)))

Clearly, all of this activity will cause the performance of clos slot access to compare poorly with structure slot access; while there will be of necessity a slowdown between the slot accesses because the structure class need not be redefineable (while redefinition of standard-object classes is extremely common), the overhead presented in the above implementation is excessive.

Compiler Transformations
========================

The compiler can assist in optimizing calls to slot-value: in particular, and despite the highly-dynamic nature of CLOS, compile-time knowledge of the name of the slot being accessed permits precomputation of much of the access (along with a branch to the slow path in case the parameters of the access change between compile-time and run-time).

Within Methods
--------------

If the object being accessed is a required parameter to the method, where the parameter variable is unmodified in the method body, and the slot name is a compile-time constant, then fast slot access can be supported through permutation vectors.

(FIXME: what about the metaclasses of the object?  Does it have to be standard-class, or can it be funcallable-standard-class?  Surely structure-class objects could be completely optimized if the class definition and slot name are both known at compile-time.)

Permutation vectors are built up and maintained to associate a compile-time index associated with a slot name with an index into the slot vector for a class of objects.  The permutation vector applicable to a given method call (FIXME: or effective method? set of classes? something else?) is passed to the method body, and slots are accessed by looking up the index to the slot vector in the permutation vector, then looking up the value from the slot vector.  (FIXME: a diagram would help, if I understood this bit well enough to draw a diagram).

Subsequent redefinitions of classes or of methods on slot-value-using-class cause an invalid index to be written into the permutation vector, and the call falls back to a full call to slot-value.

If the conditions for (structure or) permutation vector slot access optimization are not met, optimization of slot-value within methods falls back to the same as for calls to slot-value outside of methods, below.

Outside of Methods
------------------

A call to slot-value with a compile-time constant slot NAME argument is compiled into a call to a generic function named (sb-pcl::slot-accessor :global NAME sb-pcl::reader), together with code providing load-time assurance (via load-time-value) that the generic function is bound and has a suitable accessor method.  This generic function then benefits from the same optimizations as ordinary accessors, described in Accessor Discriminating Functions.

(FIXME: how does this get invalidated if we later add methods on slot-value-using-class?  Hm, maybe it isn't.  I think this is probably a bug, and that adding methods to slot-value-using-class needs to invalidate accessor caches.  Bah, humbug.  Test code in ex:buggycache, and note that I think that the analogous case involving adding or removing methods from compute-applicable-methods is handled correctly by update-all-c-a-m-gf-info.)

     (defclass foo () ((a :initform 0)))
     (defun foo (x) (slot-value x 'a))
     (foo (make-instance 'foo)) ; => 0
     (defmethod slot-value-using-class :after
       ((class std-class) (object foo)
        (slotd standard-effective-slot-definition))
       (print "hi"))
     (foo (make-instance 'foo)) ; => 0, no print
     (defclass bar (foo) ((a :initform 1)))
     (foo (make-instance 'bar)) ; => 1  and prints "hi"
     (foo (make-instance 'foo)) ; => 0, no print

MOP Optimizations
=================

Even when nothing is known at compile-time about the call to slot-value, it is possible to do marginally better than in ex:slot-value-using-class.  Each effective slot definition metaobject can cache its own effective method, and the discriminating function for slot-value-using-class is set to simply call the function in its slot definition argument.

(FIXME: I'm pretty sure this is a bad plan in general.  Or rather, it's probably a good plan, but the effective methods should probably be computed lazily rather than eagerly.  The default image has 8589 closures implementing this optimization: 3 (slot-value, set-slot-value and slot-boundp) for each of 2863 effective slots.)

(Also note that this optimization depends on not being able to specialize the new-value argument to {(setf slot-value-using-class).)