1 ;;;; Copyright (C) 1999, 2000, 2001, 2003, 2006, 2009 Free Software Foundation, Inc.
3 ;;;; This library is free software; you can redistribute it and/or
4 ;;;; modify it under the terms of the GNU Lesser General Public
5 ;;;; License as published by the Free Software Foundation; either
6 ;;;; version 3 of the License, or (at your option) any later version.
8 ;;;; This library is distributed in the hope that it will be useful,
9 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 ;;;; Lesser General Public License for more details.
13 ;;;; You should have received a copy of the GNU Lesser General Public
14 ;;;; License along with this library; if not, write to the Free Software
15 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 ;; There are circularities here; you can't import (oop goops compile)
20 ;; before (oop goops). So when compiling, make sure that things are
22 (eval-when (compile) (resolve-module '(oop goops)))
24 (define-module (oop goops dispatch)
25 :use-module (oop goops)
26 :use-module (oop goops util)
27 :use-module (oop goops compile)
28 :export (memoize-method!)
32 (define *dispatch-module* (current-module))
35 ;;; Generic functions have an applicable-methods cache associated with
36 ;;; them. Every distinct set of types that is dispatched through a
37 ;;; generic adds an entry to the cache. This cache gets compiled out to
38 ;;; a dispatch procedure. In steady-state, this dispatch procedure is
39 ;;; never recompiled; but during warm-up there is some churn, both to
40 ;;; the cache and to the dispatch procedure.
42 ;;; So what is the deal if warm-up happens in a multithreaded context?
43 ;;; There is indeed a window between missing the cache for a certain set
44 ;;; of arguments, and then updating the cache with the newly computed
45 ;;; applicable methods. One of the updaters is liable to lose their new
48 ;;; This is actually OK though, because a subsequent cache miss for the
49 ;;; race loser will just cause memoization to try again. The cache will
50 ;;; eventually be consistent. We're not mutating the old part of the
51 ;;; cache, just consing on the new entry.
53 ;;; It doesn't even matter if the dispatch procedure and the cache are
54 ;;; inconsistent -- most likely the type-set that lost the dispatch
55 ;;; procedure race will simply re-trigger a memoization, but since the
56 ;;; winner isn't in the effective-methods cache, it will likely also
57 ;;; re-trigger a memoization, and the cache will finally be consistent.
58 ;;; As you can see there is a possibility for ping-pong effects, but
59 ;;; it's unlikely given the shortness of the window between slot-set!
60 ;;; invocations. We could add a mutex, but it is strictly unnecessary,
61 ;;; and would add runtime cost and complexity.
64 (define (emit-linear-dispatch gf-sym nargs methods free rest?)
65 (define (gen-syms n stem)
66 (let lp ((n (1- n)) (syms '()))
69 (lp (1- n) (cons (gensym stem) syms)))))
70 (let* ((args (gen-syms nargs "a"))
71 (types (gen-syms nargs "t")))
72 (let lp ((methods methods)
74 (exp `(cache-miss ,gf-sym
75 ,(if rest? `(cons* ,@args rest) args))))
78 (values `(,(if rest? `(,@args . rest) args)
79 (let ,(map (lambda (t a)
86 (let preddy ((free free)
88 (specs (vector-ref (car methods) 1))
91 (let ((m-sym (gensym "p")))
93 (acons (vector-ref (car methods) 3)
98 `(apply ,m-sym ,@args rest)
101 (let ((var (assq-ref free (car specs))))
106 (cons `(eq? ,(car types) ,var)
108 (let ((var (gensym "c")))
109 (preddy (acons (car specs) var free)
112 (cons `(eq? ,(car types) ,var)
115 (define (compute-dispatch-procedure gf cache)
117 (let lp ((ls cache) (nreq -1) (nrest -1))
120 (collate (make-vector (1+ nreq) '())
121 (make-vector (1+ nrest) '())))
122 ((vector-ref (car ls) 2) ; rest
123 (lp (cdr ls) nreq (max nrest (vector-ref (car ls) 0))))
125 (lp (cdr ls) (max nreq (vector-ref (car ls) 0)) nrest)))))
126 (define (collate req rest)
131 ((vector-ref (car ls) 2) ; rest
132 (let ((n (vector-ref (car ls) 0)))
133 (vector-set! rest n (cons (car ls) (vector-ref rest n)))
136 (let ((n (vector-ref (car ls) 0)))
137 (vector-set! req n (cons (car ls) (vector-ref req n)))
139 (define (emit req rest)
140 (let ((gf-sym (gensym "g")))
141 (define (emit-rest n clauses free)
142 (if (< n (vector-length rest))
143 (let ((methods (vector-ref rest n)))
146 (emit-rest (1+ n) clauses free))
147 ;; FIXME: hash dispatch
151 (emit-linear-dispatch gf-sym n methods free #t))
152 (lambda (clause free)
153 (emit-rest (1+ n) (cons clause clauses) free))))))
154 (emit-req (1- (vector-length req)) clauses free)))
155 (define (emit-req n clauses free)
157 (comp `(lambda ,(map cdr free)
158 (case-lambda ,@clauses))
160 (let ((methods (vector-ref req n)))
163 (emit-req (1- n) clauses free))
164 ;; FIXME: hash dispatch
168 (emit-linear-dispatch gf-sym n methods free #f))
169 (lambda (clause free)
170 (emit-req (1- n) (cons clause clauses) free))))))))
173 (if (or (zero? (vector-length rest))
174 (null? (vector-ref rest 0)))
175 (list `(args (cache-miss ,gf-sym args)))
177 (acons gf gf-sym '()))))
178 (define (comp exp vals)
179 (let ((p ((@ (system base compile) compile) exp #:env *dispatch-module*)))
185 ;; o/~ ten, nine, eight
186 ;; sometimes that's just how it goes
189 ;; get out before it blows o/~
191 (define timer-init 10)
192 (define *in-progress* (make-fluid))
193 (fluid-set! *in-progress* '())
195 (define (delayed-compile gf)
196 (let ((timer timer-init))
200 (set! timer (1- timer))
201 (cache-dispatch gf args))
203 (let ((in-progress (fluid-ref *in-progress*)))
204 (if (memq gf in-progress)
205 (cache-dispatch gf args)
206 (with-fluids ((*in-progress* (cons gf in-progress)))
207 (let ((dispatch (compute-dispatch-procedure
208 gf (slot-ref gf 'effective-methods))))
209 (slot-set! gf 'dispatch-procedure dispatch)
210 (apply dispatch args))))))))))
212 (define (cache-dispatch gf args)
213 (define (map-until n f ls)
214 (if (or (zero? n) (null? ls))
216 (cons (f (car ls)) (map-until (1- n) f (cdr ls)))))
217 (let ((types (map-until (slot-ref gf 'n-specialized) class-of args)))
218 (let lp ((cache (slot-ref gf 'effective-methods)))
220 (cache-miss gf args))
221 ((equal? (vector-ref (car cache) 1) types)
222 (apply (vector-ref (car cache) 3) args))
223 (else (lp (cdr cache)))))))
225 (define (cache-miss gf args)
226 (apply (memoize-method! gf args (slot-ref gf '%cache)) args))
228 (define (memoize-effective-method! gf args applicable)
229 (define (first-n ls n)
230 (if (or (zero? n) (null? ls))
232 (cons (car ls) (first-n (cdr ls) (- n 1)))))
235 (memoize n #f (map class-of args)))
236 ((= n (slot-ref gf 'n-specialized))
237 (memoize n #t (map class-of (first-n args n))))
239 (parse (1+ n) (cdr ls)))))
240 (define (memoize len rest? types)
241 (let* ((cmethod (compute-cmethod applicable types))
242 (cache (cons (vector len types rest? cmethod)
243 (slot-ref gf 'effective-methods))))
244 (slot-set! gf 'effective-methods cache)
245 (slot-set! gf 'dispatch-procedure (delayed-compile gf))
251 ;;; This file implements method memoization. It will finally be
252 ;;; implemented on C level in order to obtain fast generic function
253 ;;; application also during the first pass through the code.
261 (define hashset-index 9)
263 (define hash-threshold 3)
264 (define initial-hash-size 4) ;must be a power of 2 and >= hash-threshold
266 (define initial-hash-size-1 (- initial-hash-size 1))
268 (define the-list-of-no-method '(no-method))
274 ;; (#@dispatch args N-SPECIALIZED #((TYPE1 ... . CMETHOD) ...) GF)
275 ;; (#@dispatch args N-SPECIALIZED HASHSET MASK
276 ;; #((TYPE1 ... . CMETHOD) ...)
283 (define method-cache-entries cadddr)
285 (define (set-method-cache-entries! mcache entries)
286 (set-car! (cdddr mcache) entries))
288 (define (method-cache-n-methods exp)
289 (n-cache-methods (method-cache-entries exp)))
291 (define (method-cache-methods exp)
292 (cache-methods (method-cache-entries exp)))
296 (define (set-hashed-method-cache-hashset! exp hashset)
297 (set-car! (cdddr exp) hashset))
299 (define (set-hashed-method-cache-mask! exp mask)
300 (set-car! (cddddr exp) mask))
302 (define (hashed-method-cache-entries exp)
305 (define (set-hashed-method-cache-entries! exp entries)
306 (set-car! (list-cdr-ref exp 5) entries))
310 (define (method-cache-generic-function exp)
311 (list-ref exp (if (method-cache-hashed? exp) 6 4)))
315 (define (method-cache-hashed? x)
316 (integer? (cadddr x)))
318 (define max-non-hashed-index (- hash-threshold 2))
320 (define (passed-hash-threshold? exp)
321 (and (> (vector-length (method-cache-entries exp)) max-non-hashed-index)
322 (struct? (car (vector-ref (method-cache-entries exp)
323 max-non-hashed-index)))))
325 ;;; Converting a method cache to hashed form
327 (define (method-cache->hashed! exp)
328 (set-cdr! (cddr exp) (cons 0 (cons initial-hash-size-1 (cdddr exp))))
335 (define (n-cache-methods entries)
336 (do ((i (- (vector-length entries) 1) (- i 1)))
337 ((or (< i 0) (struct? (car (vector-ref entries i))))
340 (define (cache-methods entries)
341 (do ((i (- (vector-length entries) 1) (- i 1))
342 (methods '() (let ((entry (vector-ref entries i)))
343 (if (or (not (pair? entry)) (struct? (car entry)))
352 (define (method-cache-insert! exp entry)
353 (let* ((entries (method-cache-entries exp))
354 (n (n-cache-methods entries)))
355 (if (>= n (vector-length entries))
357 (let ((new-entries (make-vector (* 2 (vector-length entries))
358 the-list-of-no-method)))
361 (vector-set! new-entries i (vector-ref entries i)))
362 (vector-set! new-entries n entry)
363 (set-method-cache-entries! exp new-entries))
364 (vector-set! entries n entry))))
366 (define (hashed-method-cache-insert! exp entry)
367 (let* ((cache (hashed-method-cache-entries exp))
368 (size (vector-length cache)))
369 (let* ((entries (cons entry (cache-methods cache)))
370 (size (if (<= (length entries) size)
372 ;; larger size required
373 (let ((new-size (* 2 size)))
374 (set-hashed-method-cache-mask! exp (- new-size 1))
378 (do ((hashset 0 (+ 1 hashset)))
379 ((= hashset hashsets))
380 (let* ((test-cache (make-vector size the-list-of-no-method))
381 (misses (cache-try-hash! min-misses hashset test-cache entries)))
382 (cond ((zero? misses)
385 (set! cache test-cache)
386 (set! hashset (- hashsets 1)))
387 ((< misses min-misses)
388 (set! min-misses misses)
390 (set! cache test-cache)))))
391 (set-hashed-method-cache-hashset! exp best)
392 (set-hashed-method-cache-entries! exp cache))))
398 (define (cache-hashval hashset entry)
399 (let ((hashset-index (+ hashset-index hashset)))
401 (classes entry (cdr classes)))
402 ((not (and (pair? classes) (struct? (car classes))))
404 (set! sum (+ sum (struct-ref (car classes) hashset-index))))))
406 (define (cache-try-hash! min-misses hashset cache entries)
407 (let ((mask (- (vector-length cache) 1)))
408 (let outer ((in entries) (max-misses 0))
411 (let inner ((i (logand mask (cache-hashval hashset (car in))))
414 ((and (pair? (vector-ref cache i))
415 (eq? (car (vector-ref cache i)) 'no-method))
416 (vector-set! cache i (car in))
417 (outer (cdr in) (if (> misses max-misses) misses max-misses)))
419 (let ((misses (+ 1 misses)))
420 (if (>= misses min-misses)
421 misses ;; this is a return, yo.
422 (inner (logand mask (+ i 1)) misses))))))))))
428 (define (memoize-method! gf args exp)
429 (let ((applicable ((if (eq? gf compute-applicable-methods)
430 %compute-applicable-methods
431 compute-applicable-methods)
434 (memoize-effective-method! gf args applicable)
435 ;; *fixme* dispatch.scm needs rewriting Since the current
436 ;; code mutates the method cache, we have to work on a
437 ;; copy. Otherwise we might disturb another thread
438 ;; currently dispatching on the cache. (No need to copy
440 (let* ((new (list-copy exp))
442 (cond ((method-cache-hashed? new)
443 (method-cache-install! hashed-method-cache-insert!
444 new args applicable))
445 ((passed-hash-threshold? new)
446 (method-cache-install! hashed-method-cache-insert!
447 (method-cache->hashed! new)
451 (method-cache-install! method-cache-insert!
452 new args applicable)))))
453 (set-cdr! (cdr exp) (cddr new))
456 (no-applicable-method gf args)))))
458 (set-procedure-property! memoize-method! 'system-procedure #t)
460 (define method-cache-install!
463 (if (or (zero? n) (null? ls))
465 (cons (car ls) (first-n (cdr ls) (- n 1)))))))
466 (lambda (insert! exp args applicable)
467 (let* ((specializers (method-specializers (car applicable)))
469 (if (list? specializers)
470 (length specializers)
471 (+ 1 (slot-ref (method-cache-generic-function exp)
473 (let* ((types (map class-of (first-n args n-specializers)))
474 (cmethod (compute-cmethod applicable types)))
475 (insert! exp (append types cmethod)) ; entry = types + cmethod
476 cmethod))))) ; cmethod