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

Randall Beer beer at eecs.cwru.edu
Thu Sep 30 14:32:28 UTC 2004


It might help to know that instances of ObjC classes and their "Lisp" 
subclasses are actually MACPTRs to the underlying ObjC instances, plus 
some auxiliary storage in a Lisp hash table that holds any associated 
Lisp slot values.  The main reason for doing it this way rather than 
"mirroring" every ObjC with a Lisp instance was to avoid having to 
convert back and forth between these two objects on every message send.

Thus, (MAKE-INSTANCE 'SOME-LISP-SUBCLASS-OF-AN-OBJC-CLASS ...) actually 
returns a MACPTR to foreign storage that ObjC recognizes as an 
instance.  Because this MACPTR is stored in a hash table with its 
associated Lisp-level slots, it will not be GC'd.

I'm certainly not claiming that the current setup is the best, only 
that the instances in your example will not be GC'd. We clearly have 
some work to do on better integrating ObjC and Lisp memory management, 
and Gary's previous message explains the various subtleties involved 
and some of the approaches that we have discussed.

Randy

On Sep 30, 2004, at 8:42 AM, alex crain wrote:

>
> Maybe I'm just being dense, but I don't see how this answers my
> question, which is: Regardless of how the MACPTR is handled,
> in my example there is a lisp object that is only referenced by
> a COCOA object and has no other presence in the lisp system.
> I would expect it to be garbage collected because lisp doesn't
> know about things internal to COCOA classes.
>
> To modify your example:
>
> (defparameter *big-list-of-all-windows* (make-objc-instance 
> 'ns:ns-array))
>
> (defun make-window (&rest args)
>   (let* ((w (apply 'make-instance 'some-lisp-subclass-of-an-objc-class 
> args)))
>       (send *big-list-of-all-windows* :add-object w)))
>
> Make window creates a lisp instance and adds it to a cocoa array 
> before the
> local reference goes out of scope. How does lisp know not to GC my 
> window
> objects?
>
>
> On Sep 29, 2004, at 10:46 PM, Gary Byers wrote:
>
>>
>>
>> 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.
>>
>> _______________________________________________
>> Openmcl-devel mailing list
>> Openmcl-devel at clozure.com
>> http://clozure.com/mailman/listinfo/openmcl-devel
>>
>
> _______________________________________________
> Openmcl-devel mailing list
> Openmcl-devel at clozure.com
> http://clozure.com/mailman/listinfo/openmcl-devel
>




More information about the Openmcl-devel mailing list