@node Discriminating Functions
@comment  node-name,  next,  previous,  up
@chapter Discriminating Functions

@menu
* The Initial Discriminating Function::
* Method-Based Discriminating Functions::
* Accessor Discriminating Functions::
* Cacheing and Dispatch Functions::
* The Cacheing Mechanism::
@end menu

The Common Lisp Object System specifies a great deal of run-time
customizeability, such as class redefinition, generic function and
method redefinition, addition and removal of methods and redefinitions
of method combinations.  The additional flexibility defined by the
Metaobject Protocol, specifying the generic functions called to achieve
the effects of CLOS operations (and allowing many of them to be
overridden by the user) makes any form of optimization seem intractable.
And yet such optimization is necessary to achieve reasonable
performance: the MOP specifies that a slot access looks up the class of
the object, and the slot definition from that class and the slot name,
and then invokes a generic function specialized on those three
arguments.  This is clearly going to act against the user's intuition
that a slot access given an instance should be relatively fast.

The optimizations performed cannot be done wholly at compile-time,
however, thanks to all of these possibilities for run-time redefinition
and extensibility.  This section describes the optimizations performed
in SBCL's CLOS implementation in computing and calling the effective
method for generic functions.

@node The Initial Discriminating Function
@comment  node-name,  next,  previous,  up
@section The Initial Discriminating Function

@findex compute-discriminating-function
@findex sb-mop:compute-discriminating-function

The system method on @code{SB-MOP:COMPUTE-DISCRIMINATING-FUNCTION},
under most circumstances, returns a function which closes over a
structure of type @code{SB-PCL::INITIAL}, and which calls
@code{SB-PCL::INITIAL-DFUN}.  This discriminating function is
responsible for implementing the computation of the applicable methods,
the effective method, and thence the result of the call to the generic
function.  In addition, it implements optimization of these steps, based
on the arguments it has been called with since the discriminating
function was installed and the methods of the generic function.

@float Figure,fig:dfun-transitions
@image{discriminating-functions}
@end float

For each substantive change of the generic function (such as addition or
removal of a method, or other reinitialization) the discriminating
function is reset to its initial state.

The initial discriminating function can transition into a discriminating
function optimized for the methods on the generic function
(@code{SB-PCL::NO-METHODS}, @code{SB-PCL::DEFAULT-METHOD-ONLY},
@code{SB-PCL::CONSTANT-VALUE}), for slot access
(@code{SB-PCL::ONE-CLASS}, @code{SB-PCL::TWO-CLASS},
@code{SB-PCL::ONE-INDEX}, @code{SB-PCL::N-N}@footnote{Would be better
named as @code{M-N}, as there is no requirement for the number of
classes and number of indices to be the same.}), or for dispatch based
on its arguments (@code{SB-PCL::CACHING}, @code{SB-PCL::DISPATCH}).
Those in the second category can transition into the third, or into a
@code{SB-PCL::CHECKING} state where the choice between
@code{SB-PCL::CACHING} and @code{SB-PCL::DISPATCH} has not yet been
made.

The possible transitions are shown in @ref{fig:dfun-transitions}.

@node Method-Based Discriminating Functions
@comment  node-name,  next,  previous,  up
@section Method-Based Discriminating Functions

@findex no-applicable-method

The method-based discriminating functions are used if all the methods of
the generic function at the time of the first call are suitable:
therefore, these discriminating function strategies do not transition
into any of the other states unless the generic function is
reinitialized.  Of these discriminating functions, the simplest is the
@code{SB-PCL::NO-METHODS}, which is appropriate when the generic
function has no methods.  In this case, the discriminating function
simply performs an argument count check@footnote{Actually, this bit
isn't currently done.  Oops.} and then calls
@code{NO-APPLICABLE-METHOD} with the appropriate arguments.

If all of the specializers in all methods of the generic function are
the root of the class hierarchy, @code{t}, then no discrimination need
be performed: all of the methods are applicable on every
call@footnote{Hm, there might be another problem with argument count
here.}.  In this case, the @code{SB-PCL::DEFAULT-METHOD-ONLY}
discriminating function can call the effective method directly, as it
will be the same for every generic function call.@footnote{I wonder if
we're invalidating this right if we define a method on
compute-applicable-methods...}

If all methods of the generic function are known by the system to be
side-effect-free and return constants, and the generic function has
standard-method-combination and no eql-specialized methods, then the
@code{SB-PCL::CONSTANT-VALUE} discriminating function can simply cache
the return values for given argument types.  Though this may initially
appear to have limited applicability, type predicates are usually of
this form, as in @ref{ex:pred}@footnote{There is vestigial code in SBCL
for a currently unused specialization of @code{SB-PCL::CONSTANT-VALUE}
for boolean values only.}.

@float Example,ex:pred
@example
(defgeneric foop (x))
(defmethod foop ((foo foo)) t)
(defmethod foop (object) nil)
@end example
@end float

More details of the cacheing mechanism are given in @ref{The Cacheing
Mechanism} below.

@node Accessor Discriminating Functions
@comment  node-name,  next,  previous,  up
@section Accessor Discriminating Functions

Accessor Discriminating Functions are used when the effective method of
all calls is an access to a slot, either reading, writing or checking
boundness@footnote{Although there is ordinarily no way for a user to
define a boundp method, some automatically generated generic functions
have them.}; for this path to apply, there must be no non-standard
methods on @code{SB-MOP:SLOT-VALUE-USING-CLASS} and its siblings.  The
first state is @code{SB-PCL::ONE-CLASS}, entered when one class of
instance has been accessed; the discriminating function here closes over
the wrapper of the class and the slot index, and accesses the slot of
the instance directly.

If a direct instance of another class is passed to the generic function
for slot access, then another accessor discriminating function is
created: if the index of the slot in the slots vector of each instance
is the same, then a @code{SB-PCL::TWO-CLASS} function is created,
closing over the two class wrappers and the index and performing the
simple dispatch.  If the slot indexes are not the same, then we go to
the @code{SB-PCL::N-N} state.

For slot accesses for more than two classes with the same index, we move
to the @code{SB-PCL::ONE-INDEX} state which maintains a cache of
wrappers for which the slot index is the same.  If at any point the slot
index for an instance is not the same, the state moves to
@code{SB-PCL::N-N}, which maintains a cache of wrappers and their
associated indexes; if at any point an effective method which is not a
simple slot access is encountered, then the discriminating function
moves into the @code{SB-PCL::CHECKING}, @code{SB-PCL::CACHING} or
@code{SB-PCL::DISPATCH} states.

@node Cacheing and Dispatch Functions
@comment  node-name,  next,  previous,  up
@section Cacheing and Dispatch Functions

@code{SB-PCL::CACHING} functions simply cache effective methods as a
function of argument wrappers, while @code{SB-PCL::DISPATCH} functions
have code that computes the actual dispatch.  @code{SB-PCL::CHECKING}
functions have a cache, but on cache misses will recompute whether or
not to generate a @code{SB-PCL::CACHING} or @code{SB-PCL::DISPATCH}
function.

(FIXME: I'm actually not certain about the above paragraph.  Read the
code again and see if it makes any more sense.)

@node The Cacheing Mechanism
@comment  node-name,  next,  previous,  up
@section The Cacheing Mechanism

In general, the cacheing mechanism works as follows: each class has an
associated wrapper, with some number of uniformly-distributed random
hash values associated with it; each cache has an associated index into
this pseudovector of random hash values.  To look a value up from a
cache from a single class, the hash corresponding to the cache's index
is looked up and reduced to the size of the cache (by bitmasking, for
cache sizes of a power of two); then the entry at that index is looked
up and compared for identity with the wrapper in question.  If it
matches, this is a hit; otherwise the cache is walked sequentially from
this index, skipping the 0th entry.  If the original index is reached,
the cache does not contain the value sought@footnote{Actually, there's
some kind of scope for overflow.}.

To add an entry to a cache, much the same computation is executed.
However, if there is a collision in hash values, before the cache is
grown, an attempt is made to fill the cache using a different index into
the wrappers' hash values.

Wrappers are invalidated for caches by setting all of their hash values
to zero.  (Additionally, they are invalidated by setting their
@code{depthoid} to -1, to communicate to structure type testers, and
their @code{invalid} to non-@code{nil}, communicating to
@code{obsolete-instance-trap}.

The hash value for multiple dispatch is computed by summing all of the
individual hash values from each wrapper (excluding arguments for which
all methods have @code{t} specializers, for which no dispatch
computation needs to be done), jumping to the cache miss case if any
wrapper has a zero hash index.

(FIXME: As of sbcl-0.9.x.y, the generality of multiple hash values per
wrapper was removed, as it appeared to do nothing in particular for
performance in real-world situations.)

References (O for working BibTeX):

The CLOS standards proposal

Kiczales and Rodruigez

AMOP
