Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!tut.cis.ohio-state.edu!parc.xerox.com!Gregor From: Gregor@parc.xerox.com (Gregor Kiczales) Newsgroups: comp.lang.clos Subject: Re: Need help with modifying slot access behavior Message-ID: <90Nov30.120733pst.168@spade.parc.xerox.com> Date: 30 Nov 90 20:07:19 GMT References: <10580@jpl-devvax.JPL.NASA.GOV> Sender: welch@tut.cis.ohio-state.edu Distribution: inet Organization: CommonLoops Lines: 291 Organization: NASA/Jet Propulsion Laboratory Date: Thu, 29 Nov 1990 13:19:18 PST From: kandt%ai-jupiter.uucp%jpl-devvax.uucp@elroy.jpl.nasa.gov (Kirk Kandt) This concerns 5/22/89 Victoria PCL. I'm running Lucid 3.0. My comments will be with regard to the newer version of PCL. They apply, in essence, to older PCLs as well, but it probably isn't worth the effort to fix both the older and newer PCLs as I suggest. (defmethod slot-value-using-class ((class lazy-class) object slot-name) ...) (defclass kirk () ((x :initform 0 :initarg :x :accessor x)) (:metaclass lazy-class)) (setq kirk (make-instance 'kirk :x 1)) Calling SLOT-VALUE always does the right thing; calling the X generic fn does not. The question is why does (X KIRK) quit calling SLOT-VALUE-USING-CLASS after the second time? More importantly, how can I make sure that it always calls SLOT-VALUE-USING-CLASS? Or, how can I change PCL to generate accessors that include the COND in SLOT-VALUE-USING-CLASS :AROUND? There are actually two problems, both caused by stupidity on the part of the person who wrote PCL. Your message exhibits only one of them, but unless I am wrong, the following code would fail as well: (defmethod foo ((o kirk)) (slot-value o 'x)) That is, it wouldn't end up calling your special method on SLOT-VALUE-USING-CLASS. The second problem can be fixed, I believe, by fixing the PRIMARY-PV-CACHE-MISS function in the file vector.lisp. The first problem (the one you demonstrate), that automatically generated accessors don't call your methods on slot-value-using-class, can be fixed, I believe, by fixing the function ACCESSOR-MISS-VALUES in the file dfun.lisp. The changes you want to make are going to be something like: (defun accessor-miss-values (generic-function applicable args) (declare (values type index)) (let ((type (and (eq (generic-function-method-combination generic-function) *standard-method-combination*) (every #'(lambda (m) (null (method-qualifiers m))) applicable) (cond ((standard-reader-method-p (car applicable)) (AND (EQUAL (COMPUTE-APPLICABLE-METHODS #'SLOT-VALUE-USING-CLASS (CLASS-OF (CAR ARGS)) (CAR ARGS) (STANDARD-ACCESSOR-METHOD-SLOT-NAME (CAR APPLICABLE))) (COMPUTE-APPLICABLE-METHODS #'SLOT-VALUE-USING-CLASS (FIND-CLASS 'STANDARD-CLASS) (MAKE-INSTANCE 'STANDARD-OBJECT) 'FOO)) 'READER)) ((standard-writer-method-p (car applicable)) 'writer) (t nil))))) (values type (and type (let ((wrapper (wrapper-of (case type (reader (car args)) (writer (cadr args))))) (slot-name (accessor-method-slot-name (car applicable)))) (or (instance-slot-index wrapper slot-name) (assq slot-name (wrapper-class-slots wrapper)))))))) Except that: 1) You need to make them for writers too. 2) The hack of computing the normal applicable methods should be cached somehow. 3) All of this would be easier, and perform better, if you also changed PCL to use the new proposed protocol for SLOT-VALUE-USING-CLASS (as described in the message below). I hope someone will decide to make these changes to PCL. If they do, we can add them to the sources on arisia. Gregor --- From: Gregor Kiczales To: MOP.PARC@xerox.com Subject: change to instance structure protocol Date: Thu, 22 Nov 1990 12:59:24 -0800 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 (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)) ) 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) <> (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