[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

change to instance structure protocol



The current instance structure protocol specifies that the slot access
functions (e.g. SLOT-VALUE) are implemented in terms of slot acccess
generic functions (i.e. SLOT-VALUE-USING-CLASS).  The generic functions
dispatch on the (class of) the class and the (class of) the object.

This protocol has always had a problem, specifically if you want to
specialize access to some but not all of a class's slots, it is a bit
awkward.  You have to write a method on the generic function which first
tests to see if the slot in question is a special one; if it is, special
code is run, otherwise you do a CALL-NEXT-METHOD.

This problem gets even worse when we consider the problem of retaining
the implementations ability to do optimized slot access.  If the user
defines a method on a slot access generic function, the implementation
has to call that method (it must deoptimize slot access); but even if in
fact only some of the class's slots are affected, the performance of the
access to all of them will be affected.  The problem is the same one of
resolution, the current protocol only specializes on the class and the
object, not on individual slots.

The solution we have been using is to have a generic function called
SLOT-DEFINITION-ELIDE-PORTABLE-ACCESS-METHOD-P which allows the to test
whether it can retain its optimization in some cases.  But, this is a
hack, it distinguishes "compiled" and "interpreted" code in a weird way,
and is just plain blecherous.  (For reference, the part of the newest
MOP which describes this is included at the end of this message.)

There is an elegant solution, but I have refrained from proposing it for
some time.  Now, it really seems worth proposing.

The basic idea is to change the last (slot-name) argument to all of the
SLOT-XXX-USING-CLASS generic functions.  The new value would be the
actual effective slot definition metaobject rather than the slot name.
The slot access functions would be specified to lookup the effective
slot definition object using CLASS-SLOTS, SLOT-DEFINITION-NAME and EQL.

Model code:

(defun slot-value (object slot-name)
  (let* ((class (class-of object))
         (slot-definition (find slot-name (class-slots class)
                                :key #'slot-definition-name
                                :test #'eql)))
    (if (null slot-definition)
        (slot-missing object slot-name 'slot-value)
        (slot-value-using-class class object slot-definition))))

(defmethod slot-value-using-class ((class standard-class)
                                   (object standard-object)
                                   (slotd standard-effective-slot-definition))
  ..)

Given this, a user that wants to specialize the access to some but not
all slots of a class can do so by controlling the class of the effective
slot definition metaobjects.

(defclass my-class (standard-class) ())

(defmethod effective-slot-definition-class ((class my-class) direct-slots)
  (if <looking at the direct slots tells me this slot will be special>
      (find-class 'special-slot)
      (call-next-method)))

(defclass special-slot (standard-effective-slot-definition) ())

(defmethod slot-value-using-class ((class my-class) object (slotd special-slot))
  <do whatever it is>)

In effect, what has happened is that we have increased the "resolution"
of the specialization in the slot access protocol.  It is now possible
to specialize on a set of slots whereas before it was only possible to
specialize on the class and object.

With this new protocol, the implementation's ability to elide calls to
specified generic functions (and portable methods) arises the same way
it does in other parts of the protocol.  An advance test of method
applicability (`snooping' is what JonL would call it I guess) can see
that no portable methods are applicable and then retain the inline call.

This change is incompatible in syntax and the like, but my guess is that
any implementation technique that used to work still works.

Does anyone object to this change?


*** For reference, the part of the MOP (draft) which describes ***
*** the way things work currently.                             ***


\beginSubsection{Instance Structure Protocol}

The instance structure protocol is a three layer protocol.  The upper layer
is responsible for implementing the behavior of the slot access functions
like {\bf slot-value} and {\bf (setf slot-value)}.  The middle layer
controls the interaction between portable specializations of slot access
behavior and any implementation-specific optimizations of slot access.  The
lowest layer controls the implementation of instances, providing limited
mechanisms for direct access to the storage associated with an instance.

For each CLOS slot access function, there is a corresponding generic
function which actually provides the behavior of the function.  When called,
the slot access function simply calls the corresponding generic function,
and returns its result.  The arguments passed on to the generic function
include one additional value, the class of the {\it object} argument, which
always immediately precedes the {\it object} argument

The correspondences between slot access function and underlying slot access
generic function are as follows:

\boxfig
{\dimen0=.5pc
\def\slotargs{}%{(\it object slot-name\bf)}
\def\gfslotargs{}%{(\it class object slot-name\bf)}
\tabskip \dimen0 plus .5 fil
\halign to \hsize {#\hfil&#\hfil\cr
\noalign{\vskip -9pt}
\bf Slot Access Function &\bf Corresponding Slot Access Generic Function\cr 
\noalign{\vskip 2pt\hrule\vskip 2pt}
\bf slot-boundp       \slotargs &\bf slot-boundp-using-class       \gfslotargs\cr
\bf slot-exists-p     \slotargs &\bf slot-exists-p-using-class     \gfslotargs\cr
\bf slot-makunbound   \slotargs &\bf slot-makunbound-using-class   \gfslotargs\cr
\bf slot-value        \slotargs &\bf slot-value-using-class        \gfslotargs\cr
\bf (setf slot-value) \slotargs &\bf (setf slot-value-using-class) \gfslotargs\cr 
\noalign{\vskip -9pt}
}}
\caption{}
\endfig

While not actually specified, it is expected that most implementations will
have some mechanism for optimizing slot access.  At the time this document
is being written, most implementations optimize calls to {\bf slot-value}
and {\bf (setf slot-value)} which occur in the body of {\bf defmethod}
forms, and which appear implicitly in automatically generated slot
accessors.  The effect of these optimizations is to elide the actual call to
the corresponding slot access generic function by ``in-lining'' the behavior
of the generic function's specified method.

In the presence of applicable portable methods on slot access generic
functions, any such optimization must ensure that these methods are called;
just as if no optimization had been done in the first place.  But, in many
cases, portable methods on the slot access generic functions do not actually
affect the behavior of access to all the slots of instances.  Often, only a
small number of an instance's slots are affected.  In such cases, it is
desirable to allow the implementation's optimization mechanism to avoid the
call to the portable method, if it can be determined in advance that the
slot in question is not affected by any of the portable methods.

For any given class, this interaction between the implementation's
optimization mechanism and portable slot access methods is controlled on a
per-slot basis, by the corresponding effective slot definition metaobject.
When the class is finalized, the generic function {\bf
slot-definition-elide-portable-access-method-p} is called on each of the
effective slots.  If this generic function returns true, the implementation
is permitted (but not required) to elide the call to any applicable portable
methods on the slot access generic functions---the implementation is free to
run its optimized slot access code.  If this generic function returns false,
applicable portable methods on the slot access generic functions must be
called.

The only specified method on the generic function {\bf
slot-definition-elide-portable-access-method-p} always returns false.  Thus,
the default behavior is for applicable portable methods on slot access
generic functions to always be called.

\beginExample The following code demonstrates a typical use of this mechanism.
The class metaobject class {\bf my-class} supports the normal {\bf
:instance} and {\bf :class} allocated slots.  In addition, {\bf my-class}
supports a variant kind of slot for which the access must be done in a
special way.  For any variant slot, the predicate {\bf my-magic-slot-p}
returns true when called on the corresponding effective slot definition
metaobject.

The example shows the method on {\bf slot-value-using-class} which would be
defined to implement the behavior of the variant slots.  When the slot with
the given name is not a variant slot, {\bf call-next-method} is called to
invoke the normal implementation of slot access.  The method on {\bf
slot-definition-elide-portable-access-method-p} can be read as an
``up-front'' guarantee about whether or not the method on {\bf
slot-value-using-class} will end up calling and returning the result of {\bf
call-next-method}.

\screen!

(defmethod slot-value-using-class ((class my-class) object slot-name)
  (let ((slotd (find slot-name (class-slots class)
                     :key #'slot-definition-name)))
    (if (my-magic-slot-p slotd)
        <<do-something-special>>
        (call-next-method))))

(defmethod slot-definition-elide-portable-access-method-p 
           ((class my-class) effective-slot-definition)
  (null (my-magic-slot-p effective-slot-definition)))

\endscreen!

\endExample