[Openmcl-devel] how does this work, exactly?

Gary Byers gb at clozure.com
Wed Sep 29 19:46:20 PDT 2004



On Wed, 29 Sep 2004, alex crain wrote:

>
> I create some classes:
>
> (defclass tab-view-item (ns:ns-tab-view-item)
> 	...
> 	(:metaclass ns:+ns-object))
>
> (defclass tab-view (ns:ns-tab-view)
> 	...
> 	(:metaclass ns:+ns-object))
>
> And I create some instances...
>
> (send (make-instance 'tab-view)
> 	 :add-tab-view-item (make-instance 'tab-view-item))
>
> I now have an instance of tab-view-item that is referenced by the
> tab-view cocoa object but not, as far as I can tell, anywhere
> in the lisp system. So, why doesn't lisp garbage collect my
> tab-view-item?

How does this work ?

? (#_malloc 20)

On a foreign function call like this, two blocks of memory get allocated:

1) a block of "foreign memory", at least 20 bytes long in this
   example, in some area of memory managed by malloc

2) A lisp object of type MACPTR, which references (1).  Note that many
   MACPTRs can reference the same address; MACPTRs that reference the
   same address are EQL to each other, but not necessarily EQ.

If the GC can prove that a lisp object of type MACPTR isn't referenced
from any non-garbage lisp object, it can reclaim the memory used by
that MACPTR (just as for conses, strings, functions,
standard-instances, etc.)  That has nothing to do in general with
whether or not any block of foreign memory that the MACPTR references
remains allocated.

A MACPTR to an ObjC instance tries to cache that fact; conceptually,
it changes its class from MACPTR to the (foreign) class of the instance.

In general, an Objective C instance will remain allocated as long as
its reference count is greater than 0.  It's tempting to say that
whenever lisp code creates a MACPTR which referencs an ObjC intance
it should increment that instance's reference count; whenever the GC
discovers that such a MACPTR is about to become garbage, it should
arrange to decrement the ObjC object's reference count.  This would
help to prevent situations where a MACPTR has classified itself as
an instance of some ObjC class but the instance in question has been
deallocated.

Unfortunately, I don't think that a scheme like that would scale well.
(If it worked perfectly, foreign pointers that would otherwise be freed
would linger until the GC could prove that there were no references
to them, which might have adverse effects.

I'd started to implement another (less ambitios) scheme, that involved
"canonicalizing" the MACPTRs used to reference ObjC object (so that
there was generally something more of a 1:1 correspondence.)  That
scheme ran into other problems related to how pthread cleanup functions
work in Darwin (threads become unable to respond to signals), and I
backed out of that.

I ultimately think that the "right" approach to this involves deeper
integration of Lisp's GC and ObjC's memory management scheme (sneaking
a GC-integrated zone_malloc implementation in there somewhere.)

Unless/until something like this is done, ObjC instances aren't
first-class lisp objects; this means that it's possible for "an instance
of foreign class X" to have its class changed to "freed, possibly no
longer mapped chunk of memory", "total garbage", "instance of some
other class entirely", etc. behind lisp's back.

The fact that that scenario is -possible- doesn't mean that it's likely
to occur; it's more likely to occur when dealing with ObjC objects that're
typically short-lived (NSEvents ...) than with objects that're typically
long-lived (NSWindows ...).

Suppose that you had:

(defparameter *big-list-of-all-windows* ())

(defun make-window (&rest args)
  (let* ((w (apply (make-instance 'some-window-class args))))
    (push w *big-list-of-all-windows*)
    w))

(defun do-all-windows (f)
   (dolist (w *big-list-of-all-windows*) (funcall f w)))

That kind of code (as written) is a bad idea: if instances of
SOME-WINDOW-CLASS are full-fledged, first-class lisp objects, they'd
never get GC'ed as long as they were referenced from
*BIG-LIST-OF-ALL-WINDOWS*, and you'd typically want to do something
like:

(defmethod window-close ((w some-window-class))
  (setq *big-list-of-all-windows* (delete w *big-list-of-all-windows*))
  (call-next-method))

If "instances of SOME-WINDOW-CLASS" are not full-fledged and first-
class, it'd be even more critical to avoid having references to them
after they're closed.




More information about the Openmcl-devel mailing list