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

Re: Compilation implications

>               (A) "Reconstructor" functions for instances

I've been giving this a little thought.  I have modified our compiler to
call the generic function RECONSTRUCTION-FORM when it needs to dump an
object that is not one of the primitive types it knows how to handle
directly.  It takes the object as its argument and returns a form to be
evaluated to reconstruct the object (or else you get a "no applicable
method" error).  With that hook, it then becomes possible to define a
general-purpose object dumper in portable CLOS code, which I hereby donate
to the public domain:

(defclass fasdump-mixin () ()
  (:documentation "Including this class allows instances to be dumped to object files."))
(defmacro push-key-and-value (key value list)
  `(setf ,list (list* ,key ,value ,list)))
(defmethod reconstruction-form ((object fasdump-mixin))
  (let ((plist '())
	(class-name (type-of object))
	(unbound-slots '())
	(allocation-args '()))
    (dolist (slot (class-slots (class-of object)))
      (let ((name (slot-description-name slot)))
	(when (eq (slot-description-allocation slot) ':instance)
	  (if (slot-boundp object name)
	      (push-key-and-value name (slot-value object name) plist)
	    (push name unbound-slots)))))
    (if (and (null unbound-slots) (null allocation-args))
	`(remake-object ',class-name ',plist)
      `(remake-object ',class-name ',plist ',unbound-slots ',allocation-args))))

(defun remake-object (class-name &optional slots-and-values unbound-slots allocation-args)
  (let* ((class1 (find-class class-name))
	 (object (progn (unless (class-finalized-p class1)
			  (finalize-inheritance class1))
			(apply #'allocate-instance class1 allocation-args)))
	 (class (class-of object))
	 (deleted-slots '())
	 (plist '())
	 (added-slots '())
	 (default '#,(cons nil nil)))
    (do ((tail slots-and-values (cddr tail)))
	((atom tail))
      (let ((slot-name (first tail)))
	(if (slot-exists-p-using-class class object slot-name)
	    (setf (slot-value-using-class class object slot-name)
		  (second tail))
	  (progn (push slot-name deleted-slots)
		 (push-key-and-value slot-name (second tail) plist)))))
    (dolist (slot (class-slots class))
      (let ((slot-name (slot-description-name slot)))
	(when (and (eq (slot-description-allocation slot) ':instance)
		   (eq (getf slots-and-values slot-name default) default)
		   (not (member slot-name unbound-slots :test #'eq)))
	    (push slot-name added-slots))))
    (when (or deleted-slots added-slots)
      (update-instance-for-redefined-class object added-slots deleted-slots plist))

Note that this does not require the slots to have initargs, and note the
use of UPDATE-INSTANCE-FOR-REDEFINED-CLASS at the end to take account of
any changes in the class definition between compilation and loading.  If
any slot values are themselves class instances, then the compiler will
invoke RECONSTRUCTION-FORM on them in the process of writing out the form
returned by the first call.

While this works, I don't think this is an ideal solution because it
doesn't handle the case of recursive data structures.  A better solution
might be something along the lines of having the generic function return
four values:
  1. The class name.
  2. A list of additional arguments for ALLOCATE-INSTANCE.
  3. The name of an initialization function.
  4. Initialization arguments.
The loader would then first do 
  (APPLY #'ALLOCATE-INSTANCE (FIND-CLASS class-name) other-allocate-args)
to create the object before it begins reading the next two items, and then
finally do 
  (APPLY (SYMBOL-FUNCTION init-function) the-object initialization-args)
to fill in the slots.

> Possibly, EQL specializers could raise this question.  Upon re-reading, 
> the language of 88-002R now seems a bit vague to me, and possibly open 
> to misinterpretation, as to just when an EQL parameter specializer form 
> is evaluated.

Yes, I ran into this when I wrote a method that looked something like
which wouldn't work without doing something like this:

(defmethod reconstruction-form ((object class))
  (let ((class-name (class-name object)))
    (if (and (symbolp class-name)
	     (eq (find-class class-name nil) object))
	`(find-class ',class-name)
      (error "Can't dump ~S to object file because it doesn't have a proper name." object))))

but I'm not sure if this is what the designers of CLOS had in mind.

>              (B) References to classes "by name"
> The analogy between FIND-PACKAGE and FIND-CLASS suggests that class 
> objects are in the same "database" category as packages.  Shouldn't
> they be referenced "by name" in compiled file? 

That sounds right to me.

> I realize this may pose some ordering constraints on the executions in a 
> file, if the compiled version is to operate correctly -- i.e., classes might
> have to be defined before the use of any constant that "names" that class.  
> Such constraints may not be as feasible as they are in the package world 
> (Indeed, some folks even dislike the package ordering constraints!).  Can 
> a forward-reference, "placeholder" be substituted for a class that hasn't
> yet been defined, but is referenced by a quoted constant?

We ran into this problem where we optimize calls to TYPEP to specialized
code using the class object, which might not be defined yet when the code
is loaded, so at that point the loader constructs a dummy class definition
and trusts it to be redefined correctly later.  So far, that seems to be
working ok, but, again, I'm not sure if that is really considered proper.