820 lines
32 KiB
Scheme
820 lines
32 KiB
Scheme
;;;;;; Riatables: alternative hash tables -*- Scheme -*-
|
||
;;;;;; Thread-unsafe implementation
|
||
|
||
;;; Taylor Campbell wrote this code; he places it in the public domain.
|
||
|
||
(define-record-type table type/table
|
||
(really-make-table id
|
||
template
|
||
size
|
||
gc-stamp
|
||
bucket-vector)
|
||
table?
|
||
(id table-id)
|
||
(template table-template)
|
||
(size table-size set-table-size!)
|
||
(gc-stamp table-gc-stamp set-table-gc-stamp!)
|
||
(bucket-vector table-bucket-vector set-table-bucket-vector!))
|
||
|
||
(define-record-discloser type/table
|
||
(lambda (table)
|
||
`(,(or (table-type-name table) 'table)
|
||
,@(cond ((table-id table) => list) (else '()))
|
||
,(table-size table))))
|
||
|
||
|
||
|
||
;;; --------------------
|
||
;;; Parameters
|
||
|
||
;;; These are just guesses. I ought to fill in better suggestions at
|
||
;;; some point, but these will do for now.
|
||
|
||
;;; The number of buckets to put in freshly created tables.
|
||
|
||
(define initial-bucket-count 10)
|
||
|
||
;;; The next number of buckets to have in a table after PREVIOUS.
|
||
|
||
(define (next-bucket-count previous)
|
||
(+ (* previous 2) 1))
|
||
|
||
;;; The maximum average number of entries to have in a bucket.
|
||
|
||
(define maximum-average-bucket-entries 3)
|
||
|
||
|
||
|
||
;;; --------------------
|
||
;;; Table templates
|
||
|
||
;;; This code is rather too verbose for my tastes. At least there can
|
||
;;; be a DEFINE-TEMPLATE-ACCESSOR macro to reduce code tedium.
|
||
|
||
(define-record-type table-template type/table-template
|
||
(really-make-table-template type-name
|
||
key-predicate
|
||
key-comparator
|
||
key-hasher
|
||
gc-sensitive?
|
||
head-weak?
|
||
tail-weak?
|
||
searcher
|
||
expander)
|
||
table-template?
|
||
(type-name table-template-type-name)
|
||
(key-predicate table-template-key-predicate)
|
||
(key-comparator table-template-key-comparator)
|
||
(key-hasher table-template-key-hasher)
|
||
(gc-sensitive? table-template-gc-sensitive?)
|
||
(head-weak? table-template-head-weak?)
|
||
(tail-weak? table-template-tail-weak?)
|
||
(searcher table-template-searcher)
|
||
(expander table-template-expander))
|
||
|
||
(define-record-discloser type/table-template
|
||
(lambda (t)
|
||
`(table-template ,@(cond ((table-template-type-name t) => list)
|
||
(else '())))))
|
||
|
||
(define-syntax define-template-accessors
|
||
(syntax-rules ()
|
||
((define-template-accessors (table template) ...)
|
||
(begin (define (table t)
|
||
(template (table-template t)))
|
||
...))))
|
||
|
||
(define-template-accessors
|
||
(table-type-name table-template-type-name)
|
||
(table-key-predicate table-template-key-predicate)
|
||
(table-key-comparator table-template-key-comparator)
|
||
(table-key-hasher table-template-key-hasher)
|
||
(table-gc-sensitive? table-template-gc-sensitive?)
|
||
(table-head-weak? table-template-head-weak?)
|
||
(table-tail-weak? table-template-tail-weak?)
|
||
(table-searcher table-template-searcher)
|
||
(table-expander table-template-expander))
|
||
|
||
(define (make-table-template key? key= key-hash
|
||
gc? head-weak? tail-weak?
|
||
type-name)
|
||
(let* ((rehasher (make-table-rehasher key-hash
|
||
gc? head-weak? tail-weak?))
|
||
(hasher (make-table-hasher key-hash gc? rehasher)))
|
||
(really-make-table-template
|
||
type-name
|
||
key? key= key-hash
|
||
gc? head-weak? tail-weak?
|
||
(make-table-searcher key= hasher gc?
|
||
head-weak? tail-weak?)
|
||
(make-table-expander hasher gc?
|
||
head-weak? tail-weak?
|
||
rehasher))))
|
||
|
||
(define (make-weak-table-template key? key= key-hash gc? type-name)
|
||
(make-table-template key? key= key-hash gc? #t #t type-name))
|
||
(define (make-strong-table-template key? key= key-hash gc? type-name)
|
||
(make-table-template key? key= key-hash gc? #f #f type-name))
|
||
|
||
(define (modify-table-template-weakness template head-weak? tail-weak?)
|
||
(make-table-template (table-template-key-predicate template)
|
||
(table-template-key-comparator template)
|
||
(table-template-key-hasher template)
|
||
(table-template-gc-sensitive? template)
|
||
head-weak?
|
||
tail-weak?
|
||
(table-template-type-name template)))
|
||
|
||
(define-syntax define-weakness-modifiers
|
||
(syntax-rules ()
|
||
((define-weakness-modifiers
|
||
(name template head? tail?)
|
||
...)
|
||
(begin (define (name template)
|
||
(modify-table-template-weakness template head? tail?))
|
||
...))))
|
||
|
||
(define-weakness-modifiers
|
||
(weaken-table-template t #t #t)
|
||
(head-weaken-table-template t #t (table-template-tail-weak? t))
|
||
(tail-weaken-table-template t (table-template-head-weak? t) #t)
|
||
(strengthen-table-template t #f #f)
|
||
(head-strengthen-table-template t #f (table-template-tail-weak? t))
|
||
(tail-strengthen-table-template t (table-template-head-weak? t) #f))
|
||
|
||
|
||
|
||
;;; --------------------
|
||
;;; Constructors
|
||
|
||
(define (%make-table-from-template template suggested-size id)
|
||
(let ((gc? (table-template-gc-sensitive? template)))
|
||
(really-make-table id
|
||
template
|
||
0
|
||
(and gc? (gc-stamp))
|
||
(make-vector
|
||
(if (and suggested-size
|
||
(> suggested-size
|
||
maximum-average-bucket-entries))
|
||
(quotient suggested-size
|
||
maximum-average-bucket-entries)
|
||
initial-bucket-count)
|
||
(empty-bucket)))))
|
||
|
||
(define (table-constructor template)
|
||
(let ((name `(table-constructor ,template)))
|
||
(lambda size+id
|
||
(receive (suggested-size id)
|
||
(table-cons-options size+id name)
|
||
(%make-table-from-template template suggested-size id)))))
|
||
|
||
(define (make-table-from-template template . size+id)
|
||
(receive (suggested-size id)
|
||
(table-cons-options size+id make-table-from-template)
|
||
(%make-table-from-template template suggested-size id)))
|
||
|
||
(define (table-cons-options options callee)
|
||
(if (null? options)
|
||
(values #f #f)
|
||
(let ((size (car options))
|
||
(more (cdr options)))
|
||
(if (or (integer? size) (not size))
|
||
(if (null? more)
|
||
(values size #f)
|
||
(let ((name (car more))
|
||
(more (cdr more)))
|
||
(if (null? more)
|
||
(values size name)
|
||
(error "extraneous arguments" more callee))))
|
||
(error "invalid table size suggestion argument"
|
||
size
|
||
callee)))))
|
||
|
||
|
||
|
||
;;; --------------------
|
||
;;; Main table operations
|
||
|
||
;;; This is a macro so that it gets integrated and debugging info is
|
||
;;; retained about the procedure.
|
||
|
||
(define-syntax define-keyed-table-operation
|
||
(syntax-rules ()
|
||
((define-keyed-table-operation (name table-arg key-arg arg ...)
|
||
(bucket-index-var entry-var)
|
||
body1 body2 ...)
|
||
(define (name table-arg key-arg arg ...)
|
||
(cond ((not (table? table-arg))
|
||
(call-error "invalid table argument" name
|
||
table-arg key-arg arg ...))
|
||
((not ((table-key-predicate table-arg) key-arg))
|
||
(call-error "invalid key argument" name
|
||
table-arg key-arg arg ...))
|
||
(else
|
||
(receive (bucket-index-var entry-var)
|
||
((table-searcher table-arg)
|
||
table-arg key-arg)
|
||
body1 body2 ...)))))))
|
||
|
||
(define-keyed-table-operation (table-entry table key)
|
||
(bucket-index entry)
|
||
(and entry
|
||
(let ((tail-weak? (table-tail-weak? table)))
|
||
(cond ((not tail-weak?)
|
||
(entry-value entry))
|
||
((weak-entry-value entry))
|
||
(else ; Broken weak entry.
|
||
(expunge-entry-from-table! table bucket-index entry)
|
||
#f)))))
|
||
|
||
(define-keyed-table-operation (set-table-entry! table key value)
|
||
(bucket-index entry)
|
||
(cond ((not value)
|
||
(cond (entry
|
||
(expunge-entry-from-table! table bucket-index entry)
|
||
(if (table-tail-weak? table)
|
||
(set-weak-entry-value! entry #f)
|
||
(set-entry-value! entry #f)))))
|
||
(entry
|
||
(if (table-tail-weak? table)
|
||
(set-weak-entry-value! entry #f)
|
||
(set-entry-value! entry #f)))
|
||
(bucket-index
|
||
((table-expander table) table bucket-index key value))))
|
||
|
||
(define-keyed-table-operation (modify-table-entry! table key modifier)
|
||
(bucket-index entry)
|
||
(cond (entry
|
||
;; We don't need to set the entry's value to be #F here: both
|
||
;; of the modifiers will set the entry to #F if MODIFIER
|
||
;; returns it.
|
||
(if (not (if (table-tail-weak? table)
|
||
(modify-weak-entry-value! entry modifier)
|
||
(modify-entry-value! entry modifier)))
|
||
(expunge-entry-from-table! table bucket-index entry)))
|
||
((modifier #f)
|
||
=> (lambda (new-value)
|
||
((table-expander table)
|
||
table bucket-index key new-value)))))
|
||
|
||
(define-keyed-table-operation (pop-table-entry! table key)
|
||
(bucket-index entry)
|
||
(and entry
|
||
(begin (expunge-entry-from-table! table bucket-index entry)
|
||
(if (table-tail-weak? entry)
|
||
(weak-entry-value entry)
|
||
(entry-value entry)))))
|
||
|
||
(define (walk-table table proc)
|
||
(let* ((buckets (table-bucket-vector table))
|
||
(bcount (vector-length buckets)))
|
||
(let outer-loop ((alist '())
|
||
(i 0))
|
||
(if (= i bcount)
|
||
(walk-table-alist alist proc
|
||
(table-head-weak? table)
|
||
(table-tail-weak? table))
|
||
(let inner-loop ((alist alist)
|
||
(bucket (vector-ref buckets i)))
|
||
(if (bucket-empty? bucket)
|
||
(outer-loop (+ i 1))
|
||
(inner-loop (cons (let ((entry (bucket-entry bucket)))
|
||
(cons (entry-key entry)
|
||
(entry-value entry)))
|
||
alist)
|
||
(bucket-next bucket))))))))
|
||
|
||
(define (walk-table-alist alist proc head-weak? tail-weak?)
|
||
;++ Probably shouldn't break the weakness abstraction here. (Weak
|
||
;++ pointers are supposed to be dealt with only by WEAK-ENTRY-VALUE
|
||
;++ &c.; they're meant to be invisible to all other code here.)
|
||
;++ Also, WALK-TABLE should expunge broken entries; it shouldn't be
|
||
;++ the job of this to filter them out when determining what to pass
|
||
;++ to PROC. Better yet, there would just be a TABLE->ALIST that
|
||
;++ does everything right. I'm too lazy now.
|
||
(for-each (if head-weak?
|
||
(if tail-weak?
|
||
(lambda (key.value)
|
||
(cond ((weak-pointer-ref (car key.value))
|
||
=> (lambda (key)
|
||
(cond ((weak-pointer-ref
|
||
(cdr key.value))
|
||
=> (lambda (value)
|
||
(proc key value))))))))
|
||
(lambda (key.value)
|
||
(cond ((weak-pointer-ref (car key.value))
|
||
=> (lambda (key)
|
||
(proc key (cdr key.value)))))))
|
||
(if tail-weak?
|
||
(lambda (key.value)
|
||
(cond ((weak-pointer-ref (cdr key.value))
|
||
=> (lambda (value)
|
||
(proc (car key.value) value)))))
|
||
(lambda (key.value)
|
||
(proc (car key.value) (cdr key.value)))))
|
||
alist))
|
||
|
||
|
||
|
||
;;; --------------------
|
||
;;; Buckets
|
||
|
||
;;; Buckets are represented as alists, because ASSQ in the Scheme48 VM
|
||
;;; is fast. However, this is no longer useful: using ASSQ was an
|
||
;;; optimization hack that would lose with optimistic concurrency, so
|
||
;;; it is no longer used. A more space-efficient representation could,
|
||
;;; and probably should, be chosen now with no adverse effects on time
|
||
;;; efficiency.
|
||
|
||
;;; All these trivial procedures are procedures, not direct aliases, so
|
||
;;; that the silly Scheme48 automatic procedure integrator, which has
|
||
;;; much too low a threshold and no provision for alias integration,
|
||
;;; will integrate them.
|
||
|
||
(define (empty-bucket) '())
|
||
(define (bucket-empty? bucket) (null? bucket))
|
||
(define (bucket-nonempty? bucket) (pair? bucket))
|
||
|
||
(define (make-bucket entry next) (cons entry next))
|
||
|
||
(define (bucket-entry bucket) (car bucket))
|
||
(define (bucket-next bucket) (cdr bucket))
|
||
(define (set-bucket-next! bucket next) (set-cdr! bucket next))
|
||
|
||
(define (entry-maker head-weak? tail-weak?)
|
||
(cond ((and head-weak? tail-weak?)
|
||
make-weak-entry)
|
||
(head-weak? make-head-weak-entry)
|
||
(tail-weak? make-tail-weak-entry)
|
||
(else make-strong-entry)))
|
||
|
||
(define (make-weak-entry key value)
|
||
(cons (make-weak-pointer key)
|
||
(make-weak-pointer value)))
|
||
(define (make-head-weak-entry key value)
|
||
(cons (make-weak-pointer key) value))
|
||
(define (make-tail-weak-entry key value)
|
||
(cons key (make-weak-pointer value)))
|
||
(define (make-strong-entry key value)
|
||
(cons key value))
|
||
|
||
(define (entry-key entry) (car entry))
|
||
(define (weak-entry-key entry) (weak-pointer-ref (car entry)))
|
||
|
||
(define (entry-value entry) (cdr entry))
|
||
(define (weak-entry-value entry) (weak-pointer-ref (cdr entry)))
|
||
|
||
(define (set-entry-value! entry value)
|
||
(set-cdr! entry value))
|
||
(define (set-weak-entry-value! entry value)
|
||
(set-entry-value! entry (make-weak-pointer value)))
|
||
|
||
;;; If MODIFIER returns #F, the entry is to be deleted. It would seem
|
||
;;; superfluous to set it if it is #F, but the entry, if it is to be
|
||
;;; deleted, needs to be set to #F. This is because of a delicate case
|
||
;;; in thread synchronization:
|
||
;;; - Thread A is rehashing a table; it is in the middle of dumping
|
||
;;; the bucket that contains an entry E, which it has already
|
||
;;; dumped.
|
||
;;; - Thread A is suspended; thread B gets to run.
|
||
;;; - Thread B tries to expunge E from the bucket, but it's absent,
|
||
;;; so it assumes that another thread concurrently deleted it.
|
||
;;; Now the entry E is still in the table, even though thread B thought
|
||
;;; it must have been deleted.
|
||
|
||
(define (modify-entry-value! entry modifier)
|
||
(let ((new-value (modifier (entry-value entry))))
|
||
(set-entry-value! entry new-value)
|
||
new-value))
|
||
(define (modify-weak-entry-value! entry modifier)
|
||
(let ((new-value (modifier (weak-entry-value entry))))
|
||
(set-weak-entry-value! entry new-value)
|
||
new-value))
|
||
|
||
;;; It might be better to write a purely functional, allocation-only
|
||
;;; version of this, to perform as little proposal logging as possible.
|
||
;;; Then again, it might make GCs more frequent.
|
||
;++ But couldn't that be handled by having two versions, one for GC-
|
||
;++ sensitive tables and one for GC-insensitive tables, with the latter
|
||
;++ of which allocation has no problems?
|
||
|
||
(define (expunge-entry-from-bucket! bucket entry)
|
||
(cond ((bucket-empty? bucket) bucket)
|
||
((eq? entry (bucket-entry bucket))
|
||
(bucket-next bucket))
|
||
(else
|
||
(let loop ((b (bucket-next bucket))
|
||
(lag bucket))
|
||
;; It is OK for BUCKET not to contain ENTRY: another thread
|
||
;; may have concurrently deleted it before the call to
|
||
;; EXPUNGE-ENTRY-FROM-BUCKET! but after the search for the
|
||
;; entry.
|
||
(if (bucket-nonempty? b)
|
||
(cond ((eq? (bucket-entry b) entry)
|
||
(set-bucket-next! lag (bucket-next b))
|
||
bucket)
|
||
(else
|
||
(loop (bucket-next b) b))))))))
|
||
|
||
|
||
|
||
;;; --------------------
|
||
;;; Bucket operations
|
||
|
||
(define (make-bucket-searcher key= gc? head-weak? tail-weak?)
|
||
((cond ((and gc? (or head-weak? tail-weak?))
|
||
make-gc-weak-bucket-searcher)
|
||
((and head-weak? tail-weak?)
|
||
make-weak-bucket-searcher)
|
||
(head-weak? make-head-weak-bucket-searcher)
|
||
(tail-weak? make-tail-weak-bucket-searcher)
|
||
(else make-strong-bucket-searcher))
|
||
key=))
|
||
|
||
(define-syntax define-bucket-searcher
|
||
(syntax-rules ()
|
||
((define-bucket-searcher name
|
||
entry-var
|
||
(key-var key-expression)
|
||
test)
|
||
(define (name key=)
|
||
(letrec ((expunge-broken-initials
|
||
(lambda (bucket delta)
|
||
(if (bucket-empty? bucket)
|
||
(values bucket delta)
|
||
(let* ((entry-var (bucket-entry bucket))
|
||
(next (bucket-next bucket))
|
||
(key-var key-expression))
|
||
(if test
|
||
(values bucket delta)
|
||
(expunge-broken-initials next
|
||
(+ delta 1)))))))
|
||
(search
|
||
(lambda (bucket lag key delta)
|
||
(if (bucket-empty? bucket)
|
||
(values #f delta)
|
||
(let* ((entry-var (bucket-entry bucket))
|
||
(next (bucket-next bucket))
|
||
(key-var key-expression))
|
||
(cond ((not test)
|
||
(set-bucket-next! lag next)
|
||
(search next lag key (+ delta 1)))
|
||
((key= key-var key)
|
||
(values entry-var delta))
|
||
(else
|
||
(search next bucket key delta))))))))
|
||
(lambda (table index bucket key)
|
||
(receive (bucket* delta)
|
||
(expunge-broken-initials bucket 0)
|
||
(if (positive? delta)
|
||
(vector-set! (table-bucket-vector table)
|
||
index
|
||
bucket*))
|
||
(receive (entry delta)
|
||
(if (bucket-empty? bucket*)
|
||
(values #f delta)
|
||
(let ((entry-var (bucket-entry bucket*))
|
||
(next (bucket-next bucket*)))
|
||
(if (key= key-expression key)
|
||
(values entry-var delta)
|
||
(search next
|
||
bucket*
|
||
key
|
||
delta))))
|
||
(set-table-size! table (- (table-size table) delta))
|
||
entry))))))))
|
||
|
||
(define-bucket-searcher make-weak-bucket-searcher
|
||
entry
|
||
(key (weak-entry-key entry))
|
||
(and key (weak-entry-value entry)))
|
||
|
||
(define-bucket-searcher make-head-weak-bucket-searcher
|
||
entry
|
||
(key (weak-entry-key entry))
|
||
key)
|
||
|
||
(define-bucket-searcher make-tail-weak-bucket-searcher
|
||
entry
|
||
(key (entry-key entry))
|
||
(weak-entry-value entry))
|
||
|
||
;;; This is just like the strong bucket searcher, except that it looks
|
||
;;; at the weak pointer keys' contents, not at the keys themselves.
|
||
;;; This works because, if a key was broken at the last GC, the rehash
|
||
;;; of the table would have cleared it out before calling this.
|
||
|
||
(define (make-gc-weak-bucket-searcher key=)
|
||
(lambda (table index bucket key)
|
||
(let loop ((bucket bucket))
|
||
(if (bucket-empty? bucket)
|
||
#f
|
||
(let ((entry (bucket-entry bucket)))
|
||
(if (key= (weak-entry-key entry) key)
|
||
entry
|
||
(loop (bucket-next bucket))))))))
|
||
|
||
(define (make-strong-bucket-searcher key=)
|
||
;** EXPUNGE WHEN OPTIMISTIC CONCURRENCY IS IMPLEMENTED
|
||
;** (ASSQ doesn't log)
|
||
(if (eq? key= eq?)
|
||
(lambda (table index bucket key) ;+++ Performance hack: ASSQ is
|
||
(assq key bucket)) ;+++ a VM primitive, so fast.
|
||
(letrec ((loop (lambda (table index bucket key)
|
||
(if (bucket-empty? bucket)
|
||
#f
|
||
(let ((entry (bucket-entry bucket)))
|
||
(if (key= (entry-key entry) key)
|
||
entry
|
||
(loop table
|
||
index
|
||
(bucket-next bucket)
|
||
key)))))))
|
||
loop)))
|
||
|
||
|
||
|
||
(define (make-bucket-dumper key-hash head-weak? tail-weak?)
|
||
((cond ((and head-weak? tail-weak?)
|
||
make-weak-bucket-dumper)
|
||
(head-weak? make-head-weak-bucket-dumper)
|
||
(tail-weak? make-tail-weak-bucket-dumper)
|
||
(else make-strong-bucket-dumper))
|
||
key-hash))
|
||
|
||
(define-syntax define-bucket-dumper
|
||
(syntax-rules ()
|
||
((define-bucket-dumper name
|
||
entry
|
||
(key key-expression)
|
||
test)
|
||
(define (name key-hash)
|
||
(lambda (bucket-vector old-buckets index)
|
||
(let ((mod (vector-length bucket-vector)))
|
||
(let loop ((bucket (vector-ref old-buckets index))
|
||
(count 0))
|
||
(if (bucket-empty? bucket)
|
||
count
|
||
(let* ((entry (bucket-entry bucket))
|
||
(next (bucket-next bucket))
|
||
(key key-expression))
|
||
(loop next
|
||
(if test
|
||
(let ((hash (key-hash key mod)))
|
||
(if (not (eq? hash index))
|
||
(begin (set-bucket-next!
|
||
bucket
|
||
(vector-ref bucket-vector
|
||
hash))
|
||
(vector-set! bucket-vector
|
||
hash
|
||
bucket)))
|
||
(+ count 1))
|
||
count)))))))))))
|
||
|
||
(define-bucket-dumper make-weak-bucket-dumper
|
||
entry
|
||
(key (weak-entry-key entry))
|
||
(and key (weak-entry-value entry)))
|
||
|
||
(define-bucket-dumper make-head-weak-bucket-dumper
|
||
entry
|
||
(key (weak-entry-key entry))
|
||
key)
|
||
|
||
(define-bucket-dumper make-tail-weak-bucket-dumper
|
||
entry
|
||
(key (entry-key entry))
|
||
(weak-entry-value entry))
|
||
|
||
(define (make-strong-bucket-dumper key-hash)
|
||
(letrec ((loop
|
||
(lambda (buckets index bucket count mod)
|
||
(if (bucket-empty? bucket)
|
||
count
|
||
(let ((next (bucket-next bucket))
|
||
(hash
|
||
(key-hash (entry-key (bucket-entry bucket))
|
||
mod)))
|
||
;; Prevent accidentally creating a circular bucket.
|
||
(if (not (eq? hash index))
|
||
(begin (set-bucket-next! bucket
|
||
(vector-ref buckets
|
||
hash))
|
||
(vector-set! buckets hash bucket)))
|
||
(loop buckets index next (+ count 1) mod))))))
|
||
(lambda (bucket-vector old-buckets index)
|
||
(loop bucket-vector
|
||
index
|
||
(vector-ref old-buckets index)
|
||
0
|
||
(vector-length bucket-vector)))))
|
||
|
||
|
||
|
||
;;; --------------------
|
||
;;; Internal hash table procedures
|
||
|
||
(define (make-table-rehasher key-hash gc? head-weak? tail-weak?)
|
||
((if gc?
|
||
make-gc-table-rehasher
|
||
make-stable-table-rehasher)
|
||
(make-bucket-dumper key-hash head-weak? tail-weak?)))
|
||
|
||
;;; GC-sensitive table rehashers have to be very carefully constructed.
|
||
;;; They may not allocate, and it must be absolutely certain whether or
|
||
;;; not they succeeded in hashing immediately following a certain GC
|
||
;;; stamp.
|
||
|
||
(define (make-gc-table-rehasher dump-bucket!)
|
||
(letrec ((attempt-rehash!
|
||
(make-gc-attempted-table-rehasher dump-bucket!))
|
||
(loop (lambda (table new-vector)
|
||
(if (attempt-rehash! table (gc-stamp) new-vector)
|
||
#f
|
||
(loop table new-vector)))))
|
||
(lambda (table stamp new-vector)
|
||
(or (attempt-rehash! table stamp new-vector)
|
||
(loop table new-vector)))))
|
||
|
||
;;; This returns a procedure that tries to copy all the buckets from
|
||
;;; TABLE's existing bucket vector to NEW-VECTOR.
|
||
|
||
(define (make-gc-attempted-table-rehasher dump-bucket!)
|
||
(define (loop table stamp new-vector old-buckets old-length
|
||
i new-size)
|
||
(cond ((not (eq? stamp (gc-stamp)))
|
||
;; Immediately abort, even if we finished copying all of the
|
||
;; buckets, because our efforts were for naught.
|
||
(vector-fill! new-vector (empty-bucket))
|
||
#f)
|
||
((= i old-length)
|
||
;; We succeeded in rehashing and a GC didn't occur to
|
||
;; circumvent us. Of course, a GC could still occur in the
|
||
;; next couple instructions, but that will be caught in the
|
||
;; next table lookup; what is important is that the table
|
||
;; table was completely rehashed safely.
|
||
(set-table-gc-stamp! table stamp)
|
||
(set-table-bucket-vector! table new-vector)
|
||
(set-table-size! table new-size)
|
||
#t)
|
||
(else
|
||
;; Copy the bucket at I & proceed happily. DUMP-BUCKET!
|
||
;; could test the stamp itself, but it's probably a lot
|
||
;; cheaper just to test it after every bucket copied.
|
||
(loop table stamp new-vector old-buckets old-length
|
||
(+ i 1)
|
||
(+ (dump-bucket! new-vector old-buckets i)
|
||
new-size)))))
|
||
(lambda (table stamp new-vector)
|
||
(let ((old-buckets (table-bucket-vector table)))
|
||
(loop table stamp new-vector
|
||
old-buckets
|
||
(vector-length old-buckets)
|
||
0 0))))
|
||
|
||
(define (make-stable-table-rehasher dump-bucket!)
|
||
(lambda (table stamp new-vector)
|
||
(let* ((old-buckets (table-bucket-vector table))
|
||
(old-length (vector-length old-buckets)))
|
||
(do ((i 0 (+ i 1))
|
||
(new-size 0
|
||
(+ (dump-bucket! new-vector old-buckets i)
|
||
new-size)))
|
||
((= i old-length)
|
||
(set-table-bucket-vector! table
|
||
new-vector)
|
||
(set-table-size! table new-size)
|
||
#t)))))
|
||
|
||
(define (make-table-hasher key-hash gc? rehasher)
|
||
(if gc?
|
||
(make-gc-table-hasher key-hash rehasher)
|
||
(lambda (table key)
|
||
(key-hash key (vector-length (table-bucket-vector table))))))
|
||
|
||
(define (make-gc-table-hasher key-hash rehash-table!)
|
||
(letrec ((retry
|
||
(lambda (table key mod new-vector)
|
||
(let* ((hash (key-hash key mod))
|
||
(stamp (gc-stamp)))
|
||
(if (or (eq? (table-gc-stamp table) stamp)
|
||
(rehash-table! table stamp new-vector))
|
||
hash
|
||
(retry table key mod new-vector))))))
|
||
(lambda (table key)
|
||
(let ((mod (vector-length (table-bucket-vector table))))
|
||
(let ((stamp (gc-stamp)))
|
||
(if (eq? (table-gc-stamp table) stamp)
|
||
(let* ((hash (key-hash key mod)))
|
||
(if (eq? (gc-stamp) stamp)
|
||
hash
|
||
;;; These next two calls are duplicated, but it is
|
||
;;; needed so we don't allocate unnecessarily.
|
||
(retry table key mod
|
||
(make-vector mod (empty-bucket)))))
|
||
(retry table key mod
|
||
(make-vector mod (empty-bucket)))))))))
|
||
|
||
(define (make-table-searcher key= table-hash gc? head-weak? tail-weak?)
|
||
(let ((search-bucket (make-bucket-searcher key= gc?
|
||
head-weak? tail-weak?)))
|
||
(lambda (table key)
|
||
(let* ((hash (table-hash table key))
|
||
(bucket-vector (table-bucket-vector table))
|
||
(bucket (vector-ref bucket-vector hash)))
|
||
(values hash (search-bucket table hash bucket key))))))
|
||
|
||
(define (make-table-expander table-hash gc?
|
||
head-weak? tail-weak?
|
||
rehash-table!)
|
||
(let ((make-entry (entry-maker head-weak? tail-weak?))
|
||
(rehash! (if gc?
|
||
(lambda (table new-vector)
|
||
(let loop ((stamp (table-gc-stamp table)))
|
||
(if (rehash-table! table stamp new-vector)
|
||
(values)
|
||
(loop (gc-stamp)))))
|
||
(lambda (table new-vector)
|
||
(rehash-table! table #f new-vector)))))
|
||
(lambda (table cached-hash key value)
|
||
(let* ((hash (if (maybe-expand-table! table rehash!)
|
||
cached-hash
|
||
(table-hash table key)))
|
||
;; Note the LET*: the rehash might set the table's bucket
|
||
;; vector.
|
||
(bucket-vector (table-bucket-vector table)))
|
||
(vector-set! bucket-vector hash
|
||
(make-bucket (make-entry key value)
|
||
(vector-ref bucket-vector hash)))
|
||
(set-table-size! table (+ (table-size table) 1))))))
|
||
|
||
(define (maybe-expand-table! table rehash!)
|
||
(let loop ((first? #t))
|
||
(let ((size (table-size table)))
|
||
(cond ((<= size (* (vector-length (table-bucket-vector table))
|
||
maximum-average-bucket-entries))
|
||
first?)
|
||
(else
|
||
(rehash! table
|
||
(make-vector (next-bucket-count size)
|
||
(empty-bucket)))
|
||
(loop #f))))))
|
||
|
||
(define (expunge-entry-from-table! table bucket-index entry)
|
||
(let ((bucket-vector (table-bucket-vector table)))
|
||
(vector-set! bucket-vector bucket-index
|
||
(expunge-entry-from-bucket!
|
||
(vector-ref bucket-vector bucket-index)
|
||
entry))))
|
||
|
||
|
||
|
||
;;; --------------------
|
||
;;; Various table templates
|
||
|
||
(define-syntax define-table-types
|
||
(syntax-rules ()
|
||
((define-table-types
|
||
(template-name constructor-name template-exp)
|
||
...)
|
||
(begin (begin (define template-name template-exp)
|
||
(define constructor-name
|
||
(table-constructor template-name)))
|
||
...))))
|
||
|
||
(define-table-types
|
||
(object-table-template make-object-table
|
||
(make-strong-table-template (lambda (x) #t) eq?
|
||
modular-descriptor-hash
|
||
#t ; GC-sensitive hash function
|
||
'object-table))
|
||
(weak-object-table-template make-weak-object-table
|
||
(make-weak-table-template (lambda (x) #t) eq?
|
||
modular-descriptor-hash
|
||
#t ; GC-sensitive hash function
|
||
'weak-object-table))
|
||
(string-table-template make-string-table
|
||
(make-strong-table-template string? string=?
|
||
modular-string-hash
|
||
#f ; GC-insensitive hash function
|
||
'string-table))
|
||
(string-ci-table-template make-string-ci-table
|
||
(make-strong-table-template string? string-ci=?
|
||
modular-string-ci-hash
|
||
#f ; GC-insensitive hash function
|
||
'string-ci-table))
|
||
(integer-table-template make-integer-table
|
||
(make-strong-table-template integer? =
|
||
modular-integer-hash
|
||
#f ; GC-insensitive hash function
|
||
'integer-table))
|
||
(symbol-table-template make-symbol-table
|
||
(make-strong-table-template symbol? eq?
|
||
(lambda (s mod)
|
||
(modular-string-hash (symbol->string s)
|
||
mod))
|
||
#f ; GC-insensitive hash function
|
||
'symbol-table)))
|