Coverage report: /home/ellis/.stash/quicklisp/dists/ultralisp/software/trivial-garbage-trivial-garbage-20231019214921/trivial-garbage.lisp

KindCoveredAll%
expression2275 29.3
branch02 0.0
Key
Not instrumented
Conditionalized out
Executed
Not executed
 
Both branches taken
One branch taken
Neither branch taken
1
 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-lisp; Base: 10 -*-
2
 ;;;; The above modeline is required for Genera. Do not change.
3
 ;;
4
 ;;; trivial-garbage.lisp --- Trivial Garbage!
5
 ;;;
6
 ;;; This software is placed in the public domain by Luis Oliveira
7
 ;;; <loliveira@common-lisp.net> and is provided with absolutely no
8
 ;;; warranty.
9
 
10
 #+xcvb (module ())
11
 
12
 (defpackage #:trivial-garbage
13
   (:use #:cl)
14
   (:shadow #:make-hash-table)
15
   (:nicknames #:tg)
16
   (:export #:gc
17
            #:make-weak-pointer
18
            #:weak-pointer-value
19
            #:weak-pointer-p
20
            #:make-weak-hash-table
21
            #:hash-table-weakness
22
            #:finalize
23
            #:cancel-finalization)
24
   (:documentation
25
    "@a[http://common-lisp.net/project/trivial-garbage]{trivial-garbage}
26
     provides a portable API to finalizers, weak hash-tables and weak
27
     pointers on all major implementations of the Common Lisp
28
     programming language. For a good introduction to these
29
     data-structures, have a look at
30
     @a[http://www.haible.de/bruno/papers/cs/weak/WeakDatastructures-writeup.html]{Weak
31
     References: Data Types and Implementation} by Bruno Haible.
32
 
33
     Source code is available at
34
     @a[https://github.com/trivial-garbage/trivial-garbage]{github},
35
     which you are welcome to use for submitting patches and/or
36
     @a[https://github.com/trivial-garbage/trivial-garbage/issues]{bug
37
     reports}. Discussion takes place on
38
     @a[http://lists.common-lisp.net/cgi-bin/mailman/listinfo/trivial-garbage-devel]{trivial-garbage-devel
39
     at common-lisp.net}.
40
 
41
     @a[http://common-lisp.net/project/trivial-garbage/releases/]{Tarball
42
     releases} are available, but the easiest way to install this
43
     library is via @a[http://www.quicklisp.org/]{Quicklisp}:
44
     @code{(ql:quickload :trivial-garbage)}.
45
 
46
     @begin[Weak Pointers]{section}
47
     A @em{weak pointer} holds an object in a way that does not prevent
48
     it from being reclaimed by the garbage collector.  An object
49
     referenced only by weak pointers is considered unreachable (or
50
     \"weakly reachable\") and so may be collected at any time. When
51
     that happens, the weak pointer's value becomes @code{nil}.
52
 
53
     @aboutfun{make-weak-pointer}
54
     @aboutfun{weak-pointer-value}
55
     @aboutfun{weak-pointer-p}
56
     @end{section}
57
 
58
     @begin[Weak Hash-Tables]{section}
59
     A @em{weak hash-table} is one that weakly references its keys
60
     and/or values. When both key and value are unreachable (or weakly
61
     reachable) that pair is reclaimed by the garbage collector.
62
 
63
     @aboutfun{make-weak-hash-table}
64
     @aboutfun{hash-table-weakness}
65
     @end{section}
66
 
67
     @begin[Finalizers]{section}
68
     A @em{finalizer} is a hook that is executed after a given object
69
     has been reclaimed by the garbage collector.
70
 
71
     @aboutfun{finalize}
72
     @aboutfun{cancel-finalization}
73
     @end{section}"))
74
 
75
 (in-package #:trivial-garbage)
76
 
77
 ;;;; GC
78
 
79
 (defun gc (&key full verbose)
80
   "Initiates a garbage collection. @code{full} forces the collection
81
    of all generations, when applicable. When @code{verbose} is
82
    @em{true}, diagnostic information about the collection is printed
83
    if possible."
84
   (declare (ignorable verbose full))
85
   #+(or cmu scl) (ext:gc :verbose verbose :full full)
86
   #+sbcl (sb-ext:gc :full full)
87
   #+allegro (excl:gc (not (null full)))
88
   #+(or abcl clisp) (ext:gc)
89
   #+ecl (si:gc t)
90
   #+openmcl (ccl:gc)
91
   #+corman (ccl:gc (if full 3 0))
92
   #+lispworks (hcl:gc-generation (if full t 0))
93
   #+clasp (gctools:garbage-collect)
94
   #+mezzano (mezzano.extensions:gc :full full)
95
   #+genera (scl:let-globally ((si:gc-report-stream *standard-output*)
96
                               (si:gc-reports-enable verbose)
97
                               (si:gc-ephemeral-reports-enable verbose)
98
                               (si:gc-warnings-enable verbose))
99
              (if full
100
                  (sys:gc-immediately t)
101
                  (si:ephemeral-gc-flip))))
102
 
103
 ;;;; Weak Pointers
104
 
105
 #+openmcl
106
 (defvar *weak-pointers* (cl:make-hash-table :test 'eq :weak :value)
107
   "Weak value hash-table mapping between pseudo weak pointers and its values.")
108
 
109
 #+genera
110
 (defvar *weak-pointers* (scl:make-hash-table :test 'eq :gc-protect-values nil)
111
   "Weak value hash-table mapping between pseudo weak pointers and its values.")
112
 
113
 #+(or allegro openmcl lispworks genera)
114
 (defstruct (weak-pointer (:constructor %make-weak-pointer))
115
   #-(or openmcl genera) pointer)
116
 
117
 (defun make-weak-pointer (object)
118
   "Creates a new weak pointer which points to @code{object}. For
119
    portability reasons, @code{object} must not be @code{nil}."
120
   (assert (not (null object)))
121
   #+sbcl (sb-ext:make-weak-pointer object)
122
   #+(or cmu scl) (ext:make-weak-pointer object)
123
   #+clisp (ext:make-weak-pointer object)
124
   #+abcl (ext:make-weak-reference object)
125
   #+ecl (ext:make-weak-pointer object)
126
   #+allegro
127
   (let ((wv (excl:weak-vector 1)))
128
     (setf (svref wv 0) object)
129
     (%make-weak-pointer :pointer wv))
130
   #+(or openmcl genera)
131
   (let ((wp (%make-weak-pointer)))
132
     (setf (gethash wp *weak-pointers*) object)
133
     wp)
134
   #+corman (ccl:make-weak-pointer object)
135
   #+lispworks
136
   (let ((array (make-array 1 :weak t)))
137
     (setf (svref array 0) object)
138
     (%make-weak-pointer :pointer array))
139
   #+clasp (core:make-weak-pointer object)
140
   #+mezzano (mezzano.extensions:make-weak-pointer object))
141
 
142
 #-(or allegro openmcl lispworks genera)
143
 (defun weak-pointer-p (object)
144
   "Returns @em{true} if @code{object} is a weak pointer and @code{nil}
145
    otherwise."
146
   #+sbcl (sb-ext:weak-pointer-p object)
147
   #+(or cmu scl) (ext:weak-pointer-p object)
148
   #+clisp (ext:weak-pointer-p object)
149
   #+abcl (typep object 'ext:weak-reference)
150
   #+ecl (typep object 'ext:weak-pointer)
151
   #+corman (ccl:weak-pointer-p object)
152
   #+clasp (core:weak-pointer-valid object)
153
   #+mezzano (mezzano.extensions:weak-pointer-p object))
154
 
155
 (defun weak-pointer-value (weak-pointer)
156
   "If @code{weak-pointer} is valid, returns its value. Otherwise,
157
    returns @code{nil}."
158
   #+sbcl (values (sb-ext:weak-pointer-value weak-pointer))
159
   #+(or cmu scl) (values (ext:weak-pointer-value weak-pointer))
160
   #+clisp (values (ext:weak-pointer-value weak-pointer))
161
   #+abcl (values (ext:weak-reference-value weak-pointer))
162
   #+ecl (values (ext:weak-pointer-value weak-pointer))
163
   #+allegro (svref (weak-pointer-pointer weak-pointer) 0)
164
   #+(or openmcl genera) (values (gethash weak-pointer *weak-pointers*))
165
   #+corman (ccl:weak-pointer-obj weak-pointer)
166
   #+lispworks (svref (weak-pointer-pointer weak-pointer) 0)
167
   #+clasp (core:weak-pointer-value weak-pointer)
168
   #+mezzano (values (mezzano.extensions:weak-pointer-value object)))
169
 
170
 ;;;; Weak Hash-tables
171
 
172
 ;;; Allegro can apparently create weak hash-tables with both weak keys
173
 ;;; and weak values but it's not obvious whether it's an OR or an AND
174
 ;;; relation. TODO: figure that out.
175
 
176
 (defun weakness-keyword-arg (weakness)
177
   (declare (ignorable weakness))
178
   #+(or sbcl abcl clasp ecl-weak-hash mezzano) :weakness
179
   #+(or clisp openmcl) :weak
180
   #+lispworks :weak-kind
181
   #+allegro (case weakness (:key :weak-keys) (:value :values))
182
   #+cmu :weak-p
183
   #+genera :gc-protect-values)
184
 
185
 (defvar *weakness-warnings* '()
186
   "List of weaknesses that have already been warned about this
187
    session.  Used by `weakness-missing'.")
188
 
189
 (defun weakness-missing (weakness errorp)
190
   "Signal an error or warning, depending on ERRORP, about lack of Lisp
191
    support for WEAKNESS."
192
   (cond (errorp
193
          (error "Your Lisp does not support weak ~(~A~) hash-tables."
194
                 weakness))
195
         ((member weakness *weakness-warnings*) nil)
196
         (t (push weakness *weakness-warnings*)
197
          (warn "Your Lisp does not support weak ~(~A~) hash-tables."
198
                weakness))))
199
 
200
 (defun weakness-keyword-opt (weakness errorp)
201
   (declare (ignorable errorp))
202
   (ecase weakness
203
     (:key
204
      #+(or lispworks sbcl abcl clasp clisp openmcl ecl-weak-hash mezzano) :key
205
      #+(or allegro cmu) t
206
      #-(or lispworks sbcl abcl clisp openmcl allegro cmu ecl-weak-hash clasp mezzano)
207
      (weakness-missing weakness errorp))
208
     (:value
209
      #+allegro :weak
210
      #+(or clisp openmcl sbcl abcl lispworks cmu ecl-weak-hash mezzano) :value
211
      #+genera nil
212
      #-(or allegro clisp openmcl sbcl abcl lispworks cmu ecl-weak-hash mezzano genera)
213
      (weakness-missing weakness errorp))
214
     (:key-or-value
215
      #+(or clisp sbcl abcl cmu mezzano) :key-or-value
216
      #+lispworks :either
217
      #-(or clisp sbcl abcl lispworks cmu mezzano)
218
      (weakness-missing weakness errorp))
219
     (:key-and-value
220
      #+(or clisp abcl sbcl cmu ecl-weak-hash mezzano) :key-and-value
221
      #+lispworks :both
222
      #-(or clisp sbcl abcl lispworks cmu ecl-weak-hash mezzano)
223
      (weakness-missing weakness errorp))))
224
 
225
 (defun make-weak-hash-table (&rest args &key weakness (weakness-matters t)
226
                              #+openmcl (test #'eql)
227
                              &allow-other-keys)
228
   "Returns a new weak hash table. In addition to the standard
229
    arguments accepted by @code{cl:make-hash-table}, this function adds
230
    extra keywords: @code{:weakness} being the kind of weak table it
231
    should create, and @code{:weakness-matters} being whether an error
232
    should be signalled when that weakness isn't available (the default
233
    is to signal an error).  @code{weakness} can be one of @code{:key},
234
    @code{:value}, @code{:key-or-value}, @code{:key-and-value}.
235
 
236
    If @code{weakness} is @code{:key} or @code{:value}, an entry is
237
    kept as long as its key or value is reachable, respectively. If
238
    @code{weakness} is @code{:key-or-value} or @code{:key-and-value},
239
    an entry is kept if either or both of its key and value are
240
    reachable, respectively.
241
 
242
    @code{tg::make-hash-table} is available as an alias for this
243
    function should you wish to import it into your package and shadow
244
    @code{cl:make-hash-table}."
245
   (remf args :weakness)
246
   (remf args :weakness-matters)
247
   (if weakness
248
       (let ((arg (weakness-keyword-arg weakness))
249
             (opt (weakness-keyword-opt weakness weakness-matters)))
250
         (apply #-genera #'cl:make-hash-table #+genera #'scl:make-hash-table
251
                #+openmcl :test #+openmcl (if (eq opt :key) #'eq test)
252
                #+clasp :test #+clasp #'eq
253
                (if arg
254
                    (list* arg opt args)
255
                    args)))
256
       (apply #'cl:make-hash-table args)))
257
 
258
 ;;; If you want to use this function to override CL:MAKE-HASH-TABLE,
259
 ;;; it's necessary to shadow-import it. For example:
260
 ;;;
261
 ;;;   (defpackage #:foo
262
 ;;;     (:use #:common-lisp #:trivial-garbage)
263
 ;;;     (:shadowing-import-from #:trivial-garbage #:make-hash-table))
264
 ;;;
265
 (defun make-hash-table (&rest args)
266
   (apply #'make-weak-hash-table args))
267
 
268
 (defun hash-table-weakness (ht)
269
   "Returns one of @code{nil}, @code{:key}, @code{:value},
270
    @code{:key-or-value} or @code{:key-and-value}."
271
   #-(or allegro sbcl abcl clisp cmu openmcl lispworks
272
         ecl-weak-hash clasp mezzano genera)
273
   (declare (ignore ht))
274
   ;; keep this first if any of the other lisps bugously insert a NIL
275
   ;; for the returned (values) even when *read-suppress* is NIL (e.g. clisp)
276
   #.(if (find :sbcl *features*)
277
         (if (find-symbol "HASH-TABLE-WEAKNESS" "SB-EXT")
278
             (read-from-string "(sb-ext:hash-table-weakness ht)")
279
             nil)
280
         (values))
281
   #+abcl (sys:hash-table-weakness ht)
282
   #+ecl-weak-hash (ext:hash-table-weakness ht)
283
   #+allegro (cond ((excl:hash-table-weak-keys ht) :key)
284
                    ((eq (excl:hash-table-values ht) :weak) :value))
285
   #+clisp (ext:hash-table-weak-p ht)
286
   #+cmu (let ((weakness (lisp::hash-table-weak-p ht)))
287
           (if (eq t weakness) :key weakness))
288
   #+openmcl (ccl::hash-table-weak-p ht)
289
   #+lispworks (system::hash-table-weak-kind ht)
290
   #+clasp (core:hash-table-weakness ht)
291
   #+mezzano (mezzano.extensions:hash-table-weakness ht)
292
   #+genera (if (null (getf (cli::basic-table-options ht) :gc-protect-values t))
293
                :value
294
                nil))
295
 
296
 ;;;; Finalizers
297
 
298
 ;;; Note: Lispworks can't finalize gensyms.
299
 
300
 #+(or allegro clisp lispworks openmcl)
301
 (defvar *finalizers*
302
   (cl:make-hash-table :test 'eq
303
                       #+allegro :weak-keys #+:allegro t
304
                       #+(or clisp openmcl) :weak
305
                       #+lispworks :weak-kind
306
                       #+(or clisp openmcl lispworks) :key)
307
   "Weak hashtable that holds registered finalizers.")
308
 
309
 #+corman
310
 (progn
311
   (defvar *finalizers* '()
312
     "Weak alist that holds registered finalizers.")
313
 
314
   (defvar *finalizers-cs* (threads:allocate-critical-section)))
315
 
316
 #+lispworks
317
 (progn
318
   (hcl:add-special-free-action 'free-action)
319
   (defun free-action (object)
320
     (let ((finalizers (gethash object *finalizers*)))
321
       (unless (null finalizers)
322
         (mapc #'funcall finalizers)))))
323
 
324
 #+mezzano
325
 (progn
326
   (defvar *finalizers*
327
     (cl:make-hash-table :test 'eq :weakness :key)
328
     "Weak hashtable that holds registered finalizers.")
329
   (defvar *finalizers-lock* (mezzano.supervisor:make-mutex '*finalizers-lock*)))
330
 
331
 ;;; Note: ECL bytecmp does not perform escape analysis and unused
332
 ;;; variables are not optimized away from its lexenv. That leads to
333
 ;;; closing over whole definition lexenv. That's why we define
334
 ;;; EXTEND-FINALIZER-FN which defines lambda outside the lexical scope
335
 ;;; of FINALIZE (which inludes object) - to prevent closing over
336
 ;;; finalized object. This problem does not apply to C compiler.
337
 
338
 #+ecl
339
 (defun extend-finalizer-fn (old-fn new-fn)
340
   (if (null old-fn)
341
       (lambda (obj)
342
         (declare (ignore obj))
343
         (funcall new-fn))
344
       (lambda (obj)
345
         (declare (ignore obj))
346
         (funcall new-fn)
347
         (funcall old-fn nil))))
348
 
349
 (defun finalize (object function)
350
   "Pushes a new @code{function} to the @code{object}'s list of
351
    finalizers. @code{function} should take no arguments. Returns
352
    @code{object}.
353
 
354
    @b{Note:} @code{function} should not attempt to look at
355
    @code{object} by closing over it because that will prevent it from
356
    being garbage collected."
357
   #+genera (declare (ignore object function))
358
   #+(or cmu scl) (ext:finalize object function)
359
   #+sbcl (sb-ext:finalize object function :dont-save t)
360
   #+abcl (ext:finalize object function)
361
   #+ecl (let* ((old-fn (ext:get-finalizer object))
362
                (new-fn (extend-finalizer-fn old-fn function)))
363
           (ext:set-finalizer object new-fn)
364
           object)
365
   #+allegro
366
   (progn
367
     (push (excl:schedule-finalization
368
            object (lambda (obj) (declare (ignore obj)) (funcall function)))
369
           (gethash object *finalizers*))
370
     object)
371
   #+clasp (progn (gctools:finalize object (lambda (obj) (declare (ignore obj)) (funcall function))) object)
372
   #+clisp
373
   ;; The CLISP code used to be a bit simpler but we had to workaround
374
   ;; a bug regarding the interaction between GC and weak hashtables.
375
   ;; See <http://article.gmane.org/gmane.lisp.clisp.general/11028>
376
   ;; and <http://article.gmane.org/gmane.lisp.cffi.devel/994>.
377
   (multiple-value-bind (finalizers presentp)
378
       (gethash object *finalizers* (cons 'finalizers nil))
379
     (unless presentp
380
       (setf (gethash object *finalizers*) finalizers)
381
       (ext:finalize object (lambda (obj)
382
                              (declare (ignore obj))
383
                              (mapc #'funcall (cdr finalizers)))))
384
     (push function (cdr finalizers))
385
     object)
386
   #+openmcl
387
   (progn
388
     (ccl:terminate-when-unreachable
389
      object (lambda (obj) (declare (ignore obj)) (funcall function)))
390
     ;; store number of finalizers
391
     (incf (gethash object *finalizers* 0))
392
     object)
393
   #+corman
394
   (flet ((get-finalizers (obj)
395
            (assoc obj *finalizers* :test #'eq :key #'ccl:weak-pointer-obj)))
396
     (threads:with-synchronization *finalizers-cs*
397
       (let ((pair (get-finalizers object)))
398
         (if (null pair)
399
             (push (list (ccl:make-weak-pointer object) function) *finalizers*)
400
             (push function (cdr pair)))))
401
     (ccl:register-finalization
402
      object (lambda (obj)
403
               (threads:with-synchronization *finalizers-cs*
404
                 (mapc #'funcall (cdr (get-finalizers obj)))
405
                 (setq *finalizers*
406
                       (delete obj *finalizers*
407
                               :test #'eq :key #'ccl:weak-pointer-obj)))))
408
     object)
409
   #+lispworks
410
   (progn
411
     (let ((finalizers (gethash object *finalizers*)))
412
       (unless finalizers
413
         (hcl:flag-special-free-action object))
414
       (setf (gethash object *finalizers*)
415
             (cons function finalizers)))
416
     object)
417
   #+mezzano
418
   (mezzano.supervisor:with-mutex (*finalizers-lock*)
419
     (let ((finalizer-key (gethash object *finalizers*)))
420
       (unless finalizer-key
421
         (setf finalizer-key
422
               (mezzano.extensions:make-weak-pointer
423
                object
424
                :finalizer (lambda ()
425
                             (let ((finalizers (mezzano.supervisor:with-mutex (*finalizers-lock*)
426
                                                 (prog1
427
                                                     (gethash finalizer-key *finalizers*)
428
                                                   (remhash finalizer-key *finalizers*)))))
429
                               (mapc #'funcall finalizers)))))
430
         (setf (gethash object *finalizers*) finalizer-key))
431
       (push function (gethash finalizer-key *finalizers*)))
432
     ;; Make sure the object doesn't actually get captured by the finalizer lambda.
433
     (prog1 object
434
       (setf object nil)))
435
   #+genera
436
   (error "Finalizers are not available in Genera."))
437
 
438
 (defun cancel-finalization (object)
439
   "Cancels all of @code{object}'s finalizers, if any."
440
   #+genera (declare (ignore object))
441
   #+cmu (ext:cancel-finalization object)
442
   #+scl (ext:cancel-finalization object nil)
443
   #+sbcl (sb-ext:cancel-finalization object)
444
   #+abcl (ext:cancel-finalization object)
445
   #+ecl (ext:set-finalizer object nil)
446
   #+allegro
447
   (progn
448
     (mapc #'excl:unschedule-finalization
449
           (gethash object *finalizers*))
450
     (remhash object *finalizers*))
451
   #+clasp (gctools:definalize object)
452
   #+clisp
453
   (multiple-value-bind (finalizers present-p)
454
       (gethash object *finalizers*)
455
     (when present-p
456
       (setf (cdr finalizers) nil))
457
     (remhash object *finalizers*))
458
   #+openmcl
459
   (let ((count (gethash object *finalizers*)))
460
     (unless (null count)
461
       (dotimes (i count)
462
         (ccl:cancel-terminate-when-unreachable object))))
463
   #+corman
464
   (threads:with-synchronization *finalizers-cs*
465
     (setq *finalizers*
466
           (delete object *finalizers* :test #'eq :key #'ccl:weak-pointer-obj)))
467
   #+lispworks
468
   (progn
469
     (remhash object *finalizers*)
470
     (hcl:flag-not-special-free-action object))
471
   #+mezzano
472
   (mezzano.supervisor:with-mutex (*finalizers-lock*)
473
     (let ((finalizer-key (gethash object *finalizers*)))
474
       (when finalizer-key
475
         (setf (gethash finalizer-key *finalizers*) '()))))
476
   #+genera
477
   (error "Finalizers are not available in Genera."))