sunterlib/s48/riatables/scheme/riatable.scm

820 lines
32 KiB
Scheme
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;;;;; 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)))