Finalizing foreign pointers just late enough
SBCL exposes a low-level type, system-area-pointer
s (SAPs), which are roughtly
equivalent to void *
pointers in C. Since it’s so low level, we allow ourselves a lot of
tricks to ensure performance. In particular, SAPs may be represented as raw
addresses in machine registers. In order to simplify the implementation of this fairly
useful optimization, they are given the same leeway as numbers with respect to EQ
:
SAPs that represent the same address, even from multiple evaluations of the same
bindings or value, are not guaranteed to be EQ
. When SAPs are compiled to
machine registers, this lets us simply re-create a type-generic heap value as
needed.
CFFI chose to directly expose SAPs in its user-facing interface. Finalizing SAPs is obviously a no-no: multiple references to the same (semantically) SAP can randomly be transformed into references to an arbitrary number of (physically) different objects.
If you want to finalize potentially-strange system-provided types, it’s probably better to wrap them in a read-only structure, and finalize that structure; for example:
(defstruct (wrapper (:constructor make-wrapper (pointer))) (pointer nil :read-only t)) (defun gced-foreign-alloc (type &rest rest) (let* ((ptr (apply #’foreign-alloc type rest)) (wrapper (make-wrapper ptr))) (tg:finalize wrapper (lambda () (foreign-free ptr)))))