sunterlib/s48/riatables/scheme/riatable.scm

820 lines
32 KiB
Scheme
Raw Normal View History

2005-06-27 00:51:10 -04:00
;;;;;; 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)))