;;; newhash.ss
;;; Copyright 1984-2017 Cisco Systems, Inc.
;;; 
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;; 
;;; http://www.apache.org/licenses/LICENSE-2.0
;;; 
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

#|
Documentation notes:
- hashtable-copy can create immutable weak eq hashtables. an immutable weak
  hashtable is immutable in the sense that it cannot be modified by
  hashtable-set! or hashtable-update!, but the disappearance of key, val
  pairs can be detected with hashtable-size, hashtable-keys, and
  hashtable-entries.
- symbols are collectable, so weak hash tables should not be used to create
  permanent associations with symbols as keys
- a weak or ephemeron generic hashtable maps keys to #f using weak
  pairs, and then uses a weak or ephemeron eq hashtable to map the key
  to its value; use the size of the eq hashtable as the generic hashtable's
  size
- an eqv hashtable pairs an eq hashtable and an generic hashtable
|#

#|
; csv7:
(define make-hash-table)                 ; weakflag
(define hash-table?)                     ; x
(define put-hash-table!)                 ; hashtable key obj
(define get-hash-table)                  ; hashtable key default
(define remove-hash-table!)              ; hashtable key
(define hash-table-map)                  ; hashtable proc
(define hash-table-for-each)             ; hashtable proc

;;; r6rs:
(define make-eq-hashtable)               ; [k], k >= 0
(define make-eqv-hashtable)              ; [k], k >= 0
(define make-hashtable)                  ; hashproc equivproc [k], k >= 0
(define hashtable?)                      ; x
(define hashtable-size)                  ; hashtable
(define hashtable-ref)                   ; hashtable key default
(define hashtable-set!)                  ; hashtable key obj
(define hashtable-delete!)               ; hashtable key
(define hashtable-contains?)             ; hashtable key
(define hashtable-update!)               ; hashtable key proc default
(define hashtable-copy)                  ; hashtable [mutableflag]
(define hashtable-clear!)                ; hashtable [k], k >= 0
(define hashtable-keys)                  ; hashtable
(define hashtable-entries)               ; hashtable
(define hashtable-cells)                 ; hashtable
(define hashtable-equivalence-function)  ; hashtable
(define hashtable-hash-function)         ; hashtable
(define hashtable-mutable?)              ; hashtable
(define equal-hash)                      ; obj
(define string-hash)                     ; string
(define string-ci-hash)                  ; string
(define symbol-hash)                     ; symbol

;;; other generic hash operators
(define hashtable-cell)
(define hashtable-ref-cell)              ; hashtable key
(define hashtable-weak?)                 ; hashtable
(define hashtable-ephemeron?)            ; hashtable

;;; eq-hashtable operators
(define make-weak-eq-hashtable)          ; [k], k >= 0
(define eq-hashtable-ref)                ; eq-hashtable key default
(define eq-hashtable-ref-cell)           ; eq-hashtable key
(define eq-hashtable-contains?)          ; eq-hashtable key
(define eq-hashtable-set!)               ; eq-hashtable key obj
(define eq-hashtable-update!)            ; eq-hashtable key proc default
(define eq-hashtable-cell)               ; eq-hashtable key default
(define eq-hashtable-delete!)            ; eq-hashtable key
(define eq-hashtable-weak?)              ; eq-hashtable
(define eq-hashtable-ephemeron?)         ; eq-hashtable

;;; eq-hashtable operators
(define make-symbol-hashtable)           ; [k], k >= 0
(define symbol-hashtable-ref)            ; symbol-hashtable key default
(define symbol-hashtable-ref-cell)       ; symbol-hashtable key
(define symbol-hashtable-contains?)      ; symbol-hashtable key
(define symbol-hashtable-set!)           ; symbol-hashtable key obj
(define symbol-hashtable-update!)        ; symbol-hashtable key proc default
(define symbol-hashtable-cell)           ; symbol-hashtable key default
(define symbol-hashtable-delete!)        ; symbol-hashtable key

;;; eqv-hashtable operators
(define make-weak-eqv-hashtable)         ; [k], k >= 0
  
;;; unsafe hashtable operators
(define $make-eq-hashtable)              ; fxminlen subtype, fxminlen = 2^n, n >= 0
(define $eq-hashtable-keys)              ; eq-hashtable
(define $eq-hashtable-values)            ; eq-hashtable
(define $eq-hashtable-entries)           ; eq-hashtable
(define $eq-hashtable-cells)             ; eq-hashtable
(define $eq-hashtable-copy)              ; eq-hashtable [mutableflag]
(define $eq-hashtable-clear!)            ; eq-hashtable [fxminlen]
(define $hashtable-cells)                ; hashtable [fxminlen]

;;; inspection
(define $hashtable-veclen)
(define $hashtable-report)
|#

(let ()
  (include "hashtable-types.ss")

  (define do-hash
    (lambda (hash x mask who)
      ; NB: the hash function should return a nonnegative exact integer.
      ; NB: we check only that it returns an exact integer, i.e., extend the semantics to
      ; NB: allow negative exact integers.
      (let ([i (hash x)])
        (cond
          [(fixnum? i) (fxlogand (fixmix i) mask)]
          [(bignum? i) (fxlogand (fixmix (bitwise-and i (most-positive-fixnum))) mask)]
          [else ($oops who "invalid hash-function ~s return value ~s for ~s" hash i x)]))))

  (define size->minlen
    (lambda (who k)
      (define maxbits (fx- (fixnum-width) 4))
      (cond
        [(and (fixnum? k) (fx>= k 0))
         (fxmax 8 (fxsll 1 (fxmin maxbits (fxlength (fx- k 1)))))]
        [(and (bignum? k) (>= k 0)) (fxsll 1 maxbits)]
        [else ($oops who "invalid size argument ~s" k)])))

  (define $gen-hashtable-ref
    (lambda (h x v who)
      (let ([vec (ht-vec h)] [equiv? (gen-ht-equiv? h)])
        (let loop ([b (vector-ref vec (do-hash (gen-ht-hash h) x (fx- (vector-length vec) 1) who))])
          (if (null? b)
              v
              (let ([a (car b)])
                (if (equiv? (car a) x)
                    (let ([eqht (gen-ht-eqht h)])
                      (if eqht
                          (eq-hashtable-ref eqht (car a) v)
                          (cdr a)))
                    (loop (cdr b)))))))))

  (define $gen-hashtable-ref-cell
    (lambda (h x who)
      (let ([vec (ht-vec h)] [equiv? (gen-ht-equiv? h)])
        (let loop ([b (vector-ref vec (do-hash (gen-ht-hash h) x (fx- (vector-length vec) 1) who))])
          (if (null? b)
              #f
              (let ([a (car b)])
                (if (equiv? (car a) x)
                    (let ([eqht (gen-ht-eqht h)])
                      (if eqht
                          (eq-hashtable-ref-cell eqht (car a))
                          a))
                    (loop (cdr b)))))))))

  (define $gen-hashtable-contains?
    (lambda (h x who)
      (let ([vec (ht-vec h)] [equiv? (gen-ht-equiv? h)])
        (let loop ([b (vector-ref vec (do-hash (gen-ht-hash h) x (fx- (vector-length vec) 1) who))])
          (and (not (null? b))
               (or (equiv? (caar b) x)
                   (loop (cdr b))))))))

  (module ($gen-hashtable-set! $gen-hashtable-update! $gen-hashtable-cell $gen-hashtable-delete!)
    (define-syntax incr-size!
      (syntax-rules ()
        [(_ h eqht vec who)
         (let ([size (fx+ (ht-size h) 1)] [n (vector-length vec)])
           (ht-size-set! h size)
           (when (and (fx> size n) (fx< n (fxsrl (most-positive-fixnum) 1)))
             (adjust! h vec (if eqht (fxmin (fxsll n 1) (vector-length (ht-vec eqht))) (fxsll n 1)) who)))]))

    (define-syntax decr-size!
      (syntax-rules ()
        [(_ h eqht vec who)
         (let ([size (fx- (ht-size h) 1)] [n (vector-length vec)])
           (ht-size-set! h size)
           (when (and (fx< size (fxsrl n 2)) (fx> n (ht-minlen h)))
             (adjust! h vec (if eqht (fxmin (fxsrl n 1) (vector-length (ht-vec eqht))) (fxsrl n 1)) who)))]))

    (define adjust!
      (lambda (h vec1 n2 who)
        (let ([vec2 (make-vector n2 '())]
              [mask2 (fx- n2 1)]
              [hash (gen-ht-hash h)])
          (vector-for-each
            (lambda (b)
              (for-each
                (lambda (a)
                  (let ([k (car a)])
                    (if (eq? k #!bwp)
                        (ht-size-set! h (fx- (ht-size h) 1))
                        (let ([hc (do-hash hash k mask2 who)])
                          (vector-set! vec2 hc (cons a (vector-ref vec2 hc)))))))
                b))
            vec1)
          (ht-vec-set! h vec2))))

    (define $gen-hashtable-set!
      (lambda (h x v who)
        (let ([vec (ht-vec h)] [equiv? (gen-ht-equiv? h)])
          (let ([idx (do-hash (gen-ht-hash h) x (fx- (vector-length vec) 1) who)])
            (let ([bucket (vector-ref vec idx)])
              (let loop ([b bucket])
                (if (null? b)
                    (let ([eqht (gen-ht-eqht h)])
                      (cond
                        [eqht
                         (eq-hashtable-set! eqht x v)
                         (vector-set! vec idx (cons (weak-cons x '()) bucket))]
                        [else
                         (vector-set! vec idx (cons (cons x v) bucket))])
                      (incr-size! h eqht vec who))
                    (let ([a (car b)])
                      (if (equiv? (car a) x)
                          (let ([eqht (gen-ht-eqht h)])
                            (if eqht
                                (eq-hashtable-set! eqht (car a) v)
                                (set-cdr! a v)))
                          (loop (cdr b)))))))))))

    (define $gen-hashtable-update!
      (lambda (h x p v who)
        (let ([vec (ht-vec h)] [equiv? (gen-ht-equiv? h)])
          (let ([idx (do-hash (gen-ht-hash h) x (fx- (vector-length vec) 1) who)])
            (let ([bucket (vector-ref vec idx)])
              (let loop ([b bucket])
                (if (null? b)
                    (let ([eqht (gen-ht-eqht h)])
                      (cond
                        [eqht
                         (eq-hashtable-set! eqht x (p v))
                         (vector-set! vec idx (cons (weak-cons x '()) bucket))]
                        [else
                         (vector-set! vec idx (cons (cons x (p v)) bucket))])
                      (incr-size! h eqht vec who))
                    (let ([a (car b)])
                      (if (equiv? (car a) x)
                          (let ([eqht (gen-ht-eqht h)])
                            (if eqht
                                (let ([c (eq-hashtable-cell eqht (car a) v)])
                                  (set-cdr! c (p (cdr c))))
                                (set-cdr! a (p (cdr a)))))
                          (loop (cdr b)))))))))))

    (define $gen-hashtable-cell
      (lambda (h x v who)
        (let ([vec (ht-vec h)] [equiv? (gen-ht-equiv? h)])
          (let ([idx (do-hash (gen-ht-hash h) x (fx- (vector-length vec) 1) who)])
            (let ([bucket (vector-ref vec idx)])
              (let loop ([b bucket])
                (if (null? b)
                    (let* ([eqht (gen-ht-eqht h)]
                           [a (cond
                                [eqht
                                 (let ([a (eq-hashtable-cell eqht x v)])
                                   (vector-set! vec idx (cons (weak-cons x '()) bucket))
                                   a)]
                                [else
                                 (let ([a (cons x v)])
                                   (vector-set! vec idx (cons a bucket))
                                   a)])])
                      (incr-size! h eqht vec who)
                      a)
                    (let ([a (car b)])
                      (if (equiv? (car a) x)
                          (let ([eqht (gen-ht-eqht h)])
                            (if eqht
                                (eq-hashtable-cell eqht (car a) v)
                                a))
                          (loop (cdr b)))))))))))

    (define $gen-hashtable-delete!
      (lambda (h x who)
        (let ([vec (ht-vec h)] [equiv? (gen-ht-equiv? h)])
          (let ([idx (do-hash (gen-ht-hash h) x (fx- (vector-length vec) 1) who)])
            (let loop ([b (vector-ref vec idx)] [p #f])
              (unless (null? b)
                (let ([a (car b)])
                  (if (equiv? (car a) x)
                      (let ([eqht (gen-ht-eqht h)])
                        (when eqht (eq-hashtable-delete! eqht (car a)))
                        (if p (set-cdr! p (cdr b)) (vector-set! vec idx (cdr b)))
                        (decr-size! h eqht vec who))
                      (loop (cdr b) b))))))))))

  (module ($gen-hashtable-copy $symbol-hashtable-copy)
    (define copy-hashtable-vector
      (lambda (h eq-ht)
        (let* ([vec1 (ht-vec h)]
               [n (vector-length vec1)]
               [vec2 (make-vector n '())])
          (do ([i 0 (fx+ i 1)])
            ((fx= i n))
            (vector-set! vec2 i
              (if eq-ht
                  (map (lambda (a) (weak-cons (car a) '()))
                       (vector-ref vec1 i))
                  (map (lambda (a) (cons (car a) (cdr a)))
                       (vector-ref vec1 i)))))
          vec2)))

    (define $gen-hashtable-copy
      (lambda (h mutable?)
        (let ([eq-ht (gen-ht-eqht h)])
          (make-gen-ht 'generic mutable? (copy-hashtable-vector h eq-ht) (ht-minlen h) (ht-size h)
                       (gen-ht-hash h) (gen-ht-equiv? h) (and eq-ht ($eq-hashtable-copy eq-ht mutable?))))))

    (define $symbol-hashtable-copy
      (lambda (h mutable?)
        (make-symbol-ht 'symbol mutable? (copy-hashtable-vector h #f) (ht-minlen h) (ht-size h)
          (symbol-ht-equiv? h)))))

  (define $ht-hashtable-clear!
    (lambda (h minlen)
      (ht-vec-set! h (make-vector minlen '()))
      (ht-minlen-set! h minlen)
      (ht-size-set! h 0)))

  (define $gen-hashtable-clear!
    (lambda (h minlen)
      (let ([h (gen-ht-eqht h)]) (when h ($eq-hashtable-clear! h minlen)))
      ($ht-hashtable-clear! h minlen)))

  (define $ht-hashtable-keys
    (lambda (h max-sz)
      (let ([size (fxmin max-sz (ht-size h))])
        (let ([keys (make-vector size)]
              [vec (ht-vec h)])
          (let ([n (vector-length vec)])
            (let f ([i 0] [ikey 0])
              (unless (or (fx= i n) (fx= ikey size))
                (let g ([b (vector-ref vec i)] [ikey ikey])
                  (if (or (null? b) (fx= ikey size))
                      (f (fx+ i 1) ikey)
                      (begin
                        (vector-set! keys ikey (caar b))
                        (g (cdr b) (fx+ ikey 1))))))))
          keys))))

  (define $gen-hashtable-keys
    (lambda (h max-sz)
      (let ([eqht (gen-ht-eqht h)])
        (if eqht
            ($eq-hashtable-keys eqht max-sz)
            ($ht-hashtable-keys h max-sz)))))

  (define $ht-hashtable-values
    (lambda (h max-sz)
      (let ([size (fxmin max-sz (ht-size h))])
        (let ([vals (make-vector size)]
              [vec (ht-vec h)])
          (let ([n (vector-length vec)])
            (let f ([i 0] [ival 0])
              (unless (or (fx= i n) (fx= ival size))
                (let g ([b (vector-ref vec i)] [ival ival])
                  (if (or (null? b) (fx= ival size))
                      (f (fx+ i 1) ival)
                      (begin
                        (vector-set! vals ival (cdar b))
                        (g (cdr b) (fx+ ival 1))))))))
          vals))))

  (define $gen-hashtable-values
    (lambda (h max-sz)
      (let ([eqht (gen-ht-eqht h)])
        (if eqht
            ($eq-hashtable-values eqht max-sz)
            ($ht-hashtable-values h max-sz)))))

  (define $ht-hashtable-entries
    (lambda (h max-sz)
      (let ([size (fxmin max-sz (ht-size h))])
        (let ([keys (make-vector size)]
              [vals (make-vector size)]
              [vec (ht-vec h)])
          (let ([n (vector-length vec)])
            (let f ([i 0] [ikey 0])
              (unless (or (fx= i n) (fx= ikey size))
                (let g ([b (vector-ref vec i)] [ikey ikey])
                  (if (or (null? b) (fx= ikey size))
                      (f (fx+ i 1) ikey)
                      (let ([a (car b)])
                        (vector-set! keys ikey (car a))
                        (vector-set! vals ikey (cdr a))
                        (g (cdr b) (fx+ ikey 1))))))))
          (values keys vals)))))

  (define $gen-hashtable-entries
    (lambda (h max-sz)
      (let ([eqht (gen-ht-eqht h)])
        (if eqht
            ($eq-hashtable-entries eqht max-sz)
            ($ht-hashtable-entries h max-sz)))))

  (define $ht-hashtable-cells
    (lambda (h max-sz)
      (let ([sz (fxmin max-sz (ht-size h))])
        (let ([cells (make-vector sz)]
              [vec (ht-vec h)])
          (let ([n (vector-length vec)])
            (let f ([i 0] [icell 0])
              (unless (or (fx= i n) (fx= icell sz))
                (let g ([b (vector-ref vec i)] [icell icell])
                  (if (or (null? b) (fx=? icell sz))
                      (f (fx+ i 1) icell)
                      (let ([a (car b)])
                        (vector-set! cells icell a)
                        (g (cdr b) (fx+ icell 1))))))))
          cells))))

  (define $gen-hashtable-cells
    (lambda (h max-sz)
      (let ([eqht (gen-ht-eqht h)])
        (if eqht
            ($eq-hashtable-cells eqht max-sz)
            ($ht-hashtable-cells h max-sz)))))

  (define eqv-generic?
    (lambda (x)
      ; all numbers except fixnums must go through generic hashtable
      (or (flonum? x) (bignum? x) (ratnum? x) ($exactnum? x) ($inexactnum? x))))

  (define $eqv-hashtable-ref
    (lambda (h x v who)
      (if (eqv-generic? x)
          ($gen-hashtable-ref (eqv-ht-genht h) x v who)
          (#3%eq-hashtable-ref (eqv-ht-eqht h) x v))))

  (define $eqv-hashtable-ref-cell
    (lambda (h x who)
      (if (eqv-generic? x)
          ($gen-hashtable-ref-cell (eqv-ht-genht h) x who)
          (#3%eq-hashtable-ref-cell (eqv-ht-eqht h) x))))

  (define $eqv-hashtable-contains?
    (lambda (h x who)
      (if (eqv-generic? x)
          ($gen-hashtable-contains? (eqv-ht-genht h) x who)
          (#3%eq-hashtable-contains? (eqv-ht-eqht h) x))))

  (define $eqv-hashtable-set!
    (lambda (h x v who)
      (if (eqv-generic? x)
          ($gen-hashtable-set! (eqv-ht-genht h) x v who)
          (#3%eq-hashtable-set! (eqv-ht-eqht h) x v))))

  (define $eqv-hashtable-update!
    (lambda (h x p v who)
      (if (eqv-generic? x)
          ($gen-hashtable-update! (eqv-ht-genht h) x p v who)
          (#3%eq-hashtable-update! (eqv-ht-eqht h) x p v))))

  (define $eqv-hashtable-cell
    (lambda (h x v who)
      (if (eqv-generic? x)
          ($gen-hashtable-cell (eqv-ht-genht h) x v who)
          (#3%eq-hashtable-cell (eqv-ht-eqht h) x v))))

  (define $eqv-hashtable-delete!
    (lambda (h x who)
      (if (eqv-generic? x)
          ($gen-hashtable-delete! (eqv-ht-genht h) x who)
          (#3%eq-hashtable-delete! (eqv-ht-eqht h) x))))

  (define $eqv-hashtable-copy
    (lambda (h mutable?)
      (make-eqv-ht 'eqv mutable?
        ($eq-hashtable-copy (eqv-ht-eqht h) mutable?)
        ($gen-hashtable-copy (eqv-ht-genht h) mutable?))))

  (module ($eqv-hashtable-keys $eqv-hashtable-values $eqv-hashtable-entries $eqv-hashtable-cells)
    (define vector-append
      (lambda (v1 v2)
        (let ([n1 (vector-length v1)] [n2 (vector-length v2)])
          (if (fx= n1 0)
              v2
              (if (fx= n2 0)
                  v1
                  (let ([v (make-vector (fx+ n1 n2))])
                    (do ([i 0 (fx+ i 1)])
                        ((fx= i n1))
                      (vector-set! v i (vector-ref v1 i)))
                    (do ([i 0 (fx+ i 1)] [j n1 (fx+ j 1)])
                        ((fx= i n2))
                      (vector-set! v j (vector-ref v2 i)))
                    v))))))
    (define $eqv-hashtable-keys
      (lambda (h max-sz)
        (let* ([keys1 ($eq-hashtable-keys (eqv-ht-eqht h) max-sz)]
               [keys2 ($gen-hashtable-keys (eqv-ht-genht h) (fx- max-sz (vector-length keys1)))])
          (vector-append keys1 keys2))))
    (define $eqv-hashtable-values
      (lambda (h max-sz)
        (let* ([vals1 ($eq-hashtable-values (eqv-ht-eqht h) max-sz)]
               [vals2 ($gen-hashtable-values (eqv-ht-genht h) (fx- max-sz (vector-length vals1)))])
          (vector-append vals1 vals2))))
    (define $eqv-hashtable-entries
      (lambda (h max-sz)
        (let*-values ([(keys1 vals1) ($eq-hashtable-entries (eqv-ht-eqht h) max-sz)]
                      [(keys2 vals2) ($gen-hashtable-entries (eqv-ht-genht h) (fx- max-sz (vector-length keys1)))])
          (values
            (vector-append keys1 keys2)
            (vector-append vals1 vals2)))))
    (define $eqv-hashtable-cells
      (lambda (h max-sz)
        (let* ([cells1 ($eq-hashtable-cells (eqv-ht-eqht h) max-sz)]
               [cells2 ($gen-hashtable-cells (eqv-ht-genht h) (fx- max-sz (vector-length cells1)))])
          (vector-append cells1 cells2)))))

  (define (fixmix x)
    ;; Since mutable hash tables tend to use the low bits of a hash code,
    ;; make sure higher bits of a fixnum are represented there
    (let* ([x1 (constant-case ptr-bits
                 [(64) (fxxor x (fxand (fxsrl x 32) #xFFFFFFFF))]
                 [else x])]
           [x2 (fxxor x1 (fxand (fxsrl x1 16) #xFFFF))]
           [x3 (fxxor x2 (fxand (fxsrl x2 8) #xFF))])
      x3))

  (define number-hash
    (lambda (z)
      (cond
        [(fixnum? z) (if (fx< z 0) (fxnot z) z)]
        [(flonum? z) ($flhash z)]
        [(bignum? z) (let ([len (integer-length z)]
                           [update (lambda (hc k)
                                     (let ([hc2 (fx+/wraparound hc (fxsll/wraparound (fx+/wraparound hc k) 10))])
                                       (fxlogxor hc2 (fxsrl hc2 6))))])
                       (let loop ([i 0] [hc 0])
                         (cond
                           [(fx>= i len) hc]
                           [else
                            (let ([next-i (fx+ i (fx- (fixnum-width) 1))])
                              (loop next-i
                                    (bitwise-and
                                     (most-positive-fixnum)
                                     (update (bitwise-bit-field z i next-i)
                                             hc))))])))]
        [(ratnum? z) (number-hash (+ (* (numerator z) 5) (denominator z)))]
        [else (logxor (lognot (number-hash (real-part z))) (number-hash (imag-part z)))])))

  (set! $make-eq-hashtable ; assumes minlen is a power of two >= 1
    (lambda (minlen subtype)
      (make-eq-ht 'eq #t ($make-eqhash-vector minlen) minlen 0 subtype)))

  (set-who! $hashtable-veclen
    (lambda (h)
      (unless (xht? h) ($oops who "~s is not a hashtable" h))
      (case (xht-type h)
        [(eqv) (values (vector-length (ht-vec (eqv-ht-eqht h))) (vector-length (ht-vec (eqv-ht-genht h))))]
        [(generic) (let ([eqht (gen-ht-eqht h)]
                         [len (vector-length (ht-vec h))])
                     (if eqht
                         (values len (vector-length (ht-vec eqht)))
                         len))]
        [else (vector-length (ht-vec h))])))

  (set-who! $ht-veclen
    (lambda (h)
      (unless (ht? h) ($oops who "~s is not an ht" h))
      (vector-length (ht-vec h))))

  (set-who! $ht-minlen
    (lambda (h)
      (unless (ht? h) ($oops who "~s is not an ht" h))
      (ht-minlen h)))

  (let ()
    (define report
      (lambda (h bucket-length)
        (define (rnd n) (/ (round (* (inexact n) 100)) 100))
        (let ([vec (ht-vec h)])
          (let ([n (vector-length vec)])
            (let f ([i 0] [cnt 0] [m 0] [ss 0])
              (if (= i n)
                  (let ([mean (/ cnt n)])
                    (printf
                      "size, count, max, mean, std = ~s, ~s, ~s, ~s, ~s~%"
                      n cnt m (rnd mean)
                      (rnd (* (sqrt (- (/ ss n) (* mean mean)))))))
                  (let ([k (bucket-length (vector-ref vec i))])
                    (f (+ i 1) (+ cnt k) (max k m) (+ ss (* k k))))))))))
    (define eq-bucket-length
      (lambda (b)
        (if (fixnum? b) 0 (fx1+ (eq-bucket-length ($tlc-next b))))))
    (set-who! $hashtable-report
      (lambda (h)
        (unless (xht? h) ($oops who "~s is not a hashtable" h))
        (case (xht-type h)
          [(eq) (report h eq-bucket-length)]
          [(eqv)
           (report (eqv-ht-eqht h) eq-bucket-length)
           (report (eqv-ht-genht h) length)]
          [else (report h length)]))))

 ; csv7 interface
  (set! make-hash-table
    (case-lambda
      [() ($make-eq-hashtable (constant hashtable-default-size) (constant eq-hashtable-subtype-normal))]
      [(weak?) ($make-eq-hashtable (constant hashtable-default-size)
                                   (if weak?
                                       (constant eq-hashtable-subtype-weak)
                                       (constant eq-hashtable-subtype-normal)))]))

  (set! hash-table?
    (lambda (x)
      (eq-ht? x)))

  (set-who! put-hash-table!
    (lambda (h x v)
      (unless (eq-ht? h) ($oops who "~s is not an eq hashtable" h))
      (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
      (#3%eq-hashtable-set! h x v)))

  (set-who! get-hash-table
    (lambda (h x d)
      (unless (eq-ht? h) ($oops who "~s is not an eq hashtable" h))
      (#3%eq-hashtable-ref h x d)))

  (set-who! remove-hash-table!
    (lambda (h x)
      (unless (eq-ht? h) ($oops who "~s is not an eq hashtable" h))
      (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
      (#3%eq-hashtable-delete! h x)))

  (set-who! hash-table-map
    (lambda (h p)
      (unless (eq-ht? h) ($oops who "~s is not an eq hashtable" h))
      (unless (procedure? p) ($oops who "~s is not a procedure" p))
      (let-values ([(keys vals) ($eq-hashtable-entries h (most-positive-fixnum))])
        (let f ([i (vector-length keys)] [ls '()])
          (if (fx= i 0)
              ls
              (let ([i (fx- i 1)])
                (f i (cons (p (vector-ref keys i) (vector-ref vals i)) ls))))))))

  (set-who! hash-table-for-each
    (lambda (h p)
      (unless (eq-ht? h) ($oops who "~s is not an eq hashtable" h))
      (unless (procedure? p) ($oops who "~s is not a procedure" p))
      (let-values ([(keys vals) ($eq-hashtable-entries h (most-positive-fixnum))])
        (vector-for-each p keys vals))))

  (set-who! make-eq-hashtable
    (case-lambda
      [() ($make-eq-hashtable (constant hashtable-default-size) (constant eq-hashtable-subtype-normal))]
      [(k) ($make-eq-hashtable (size->minlen who k) (constant eq-hashtable-subtype-normal))]))

  (set-who! make-weak-eq-hashtable
    (case-lambda
      [() ($make-eq-hashtable (constant hashtable-default-size) (constant eq-hashtable-subtype-weak))]
      [(k) ($make-eq-hashtable (size->minlen who k) (constant eq-hashtable-subtype-weak))]))

  (set-who! make-ephemeron-eq-hashtable
    (case-lambda
      [() ($make-eq-hashtable (constant hashtable-default-size) (constant eq-hashtable-subtype-ephemeron))]
      [(k) ($make-eq-hashtable (size->minlen who k) (constant eq-hashtable-subtype-ephemeron))]))

  (let ()
    (define $make-hashtable
      (lambda (minlen hash equiv? subtype)
        (if (and (eq? hash symbol-hash)
                 (eq? subtype (constant eq-hashtable-subtype-normal))
                 (or (eq? equiv? eq?)
                     (eq? equiv? symbol=?)
                     (eq? equiv? eqv?)
                     (eq? equiv? equal?)))
            (make-symbol-ht 'symbol #t (make-vector minlen '()) minlen 0 equiv?)
            (make-gen-ht 'generic #t (make-vector minlen '()) minlen 0 hash equiv?
                         (cond
                           [(eq? subtype (constant eq-hashtable-subtype-normal)) #f]
                           [else ($make-eq-hashtable minlen subtype)])))))
    (define $make-eqv-hashtable
      (lambda (minlen subtype)
        (make-eqv-ht 'eqv #t
          ($make-eq-hashtable minlen subtype)
          ($make-hashtable minlen number-hash eqv? subtype))))
    (define $make-gen-hashtable
      (case-lambda
        [(who hash equiv? subtype)
         (unless (procedure? hash) ($oops who "~s is not a procedure" hash))
         (unless (procedure? equiv?) ($oops who "~s is not a procedure" equiv?))
         ($make-hashtable (constant hashtable-default-size) hash equiv? subtype)]
        [(who hash equiv? k subtype)
         (unless (procedure? hash) ($oops who "~s is not a procedure" hash))
         (unless (procedure? equiv?) ($oops who "~s is not a procedure" equiv?))
         ($make-hashtable (size->minlen who k) hash equiv? subtype)]))
    (set-who! make-hashtable
      (case-lambda
        [(hash equiv?) ($make-gen-hashtable who hash equiv? (constant eq-hashtable-subtype-normal))]
        [(hash equiv? k) ($make-gen-hashtable who hash equiv? k (constant eq-hashtable-subtype-normal))]))
    (set-who! make-weak-hashtable
      (case-lambda
        [(hash equiv?) ($make-gen-hashtable who hash equiv? (constant eq-hashtable-subtype-weak))]
        [(hash equiv? k) ($make-gen-hashtable who hash equiv? k (constant eq-hashtable-subtype-weak))]))
    (set-who! make-ephemeron-hashtable
      (case-lambda
        [(hash equiv?) ($make-gen-hashtable who hash equiv? (constant eq-hashtable-subtype-ephemeron))]
        [(hash equiv? k) ($make-gen-hashtable who hash equiv? k (constant eq-hashtable-subtype-ephemeron))]))
    (set-who! make-eqv-hashtable
      (case-lambda
        [() ($make-eqv-hashtable (constant hashtable-default-size) (constant eq-hashtable-subtype-normal))]
        [(k) ($make-eqv-hashtable (size->minlen who k) (constant eq-hashtable-subtype-normal))]))
    (set-who! make-weak-eqv-hashtable
      (case-lambda
        [() ($make-eqv-hashtable (constant hashtable-default-size) (constant eq-hashtable-subtype-weak))]
        [(k) ($make-eqv-hashtable (size->minlen who k) (constant eq-hashtable-subtype-weak))]))
    (set-who! make-ephemeron-eqv-hashtable
      (case-lambda
        [() ($make-eqv-hashtable (constant hashtable-default-size) (constant eq-hashtable-subtype-ephemeron))]
        [(k) ($make-eqv-hashtable (size->minlen who k) (constant eq-hashtable-subtype-ephemeron))])))

  (set! eq-hashtable-ref
    (lambda (h x v)
      (unless (eq-ht? h)
        ($oops 'eq-hashtable-ref "~s is not an eq hashtable" h))
      (#3%eq-hashtable-ref h x v)))

  (set-who! eq-hashtable-ref-cell
    (lambda (h x)
      (unless (eq-ht? h)
        ($oops who "~s is not an eq hashtable" h))
      (unless (xht-mutable? h)
        ($oops who "~s is not mutable" h))
      (#3%eq-hashtable-ref-cell h x)))

  (set! eq-hashtable-contains?
    (lambda (h x)
      (unless (eq-ht? h)
        ($oops 'eq-hashtable-contains? "~s is not an eq hashtable" h))
      (#3%eq-hashtable-contains? h x)))

  (set! eq-hashtable-set!
    (lambda (h x v)
      (unless (eq-ht? h)
        ($oops 'eq-hashtable-set! "~s is not an eq hashtable" h))
      (unless (xht-mutable? h)
        ($oops 'eq-hashtable-set! "~s is not mutable" h))
      (#3%eq-hashtable-set! h x v)))

  (set! eq-hashtable-update!
    (lambda (h x p v)
      (unless (eq-ht? h)
        ($oops 'eq-hashtable-update! "~s is not an eq hashtable" h))
      (unless (xht-mutable? h)
        ($oops 'eq-hashtable-update! "~s is not mutable" h))
      (unless (procedure? p)
        ($oops 'eq-hashtable-update! "~s is not a procedure" p))
      (#3%eq-hashtable-update! h x p v)))

  (set-who! eq-hashtable-cell
    (lambda (h x v)
      (unless (eq-ht? h)
        ($oops who "~s is not an eq hashtable" h))
      (unless (xht-mutable? h)
        ($oops who "~s is not mutable" h))
      (#3%eq-hashtable-cell h x v)))

  (set-who! eq-hashtable-try-atomic-cell
    (lambda (h x v)
      (unless (eq-ht? h)
        ($oops who "~s is not an eq hashtable" h))
      (unless (xht-mutable? h)
        ($oops who "~s is not mutable" h))
      (#3%eq-hashtable-try-atomic-cell h x v)))

  (set! eq-hashtable-delete!
    (lambda (h x)
      (unless (eq-ht? h)
        ($oops 'eq-hashtable-delete! "~s is not an eq hashtable" h))
      (unless (xht-mutable? h)
        ($oops 'eq-hashtable-delete! "~s is not mutable" h))
      (#3%eq-hashtable-delete! h x)))

  (set-who! eq-hashtable-weak?
    (lambda (h)
      (unless (eq-ht? h) ($oops who "~s is not an eq hashtable" h))
      (eq? (constant eq-hashtable-subtype-weak) (eq-ht-subtype h))))

  (set-who! eq-hashtable-ephemeron?
    (lambda (h)
      (unless (eq-ht? h) ($oops who "~s is not an eq hashtable" h))
      (eq? (constant eq-hashtable-subtype-ephemeron) (eq-ht-subtype h))))

  (set-who! hashtable-weak?
    (lambda (h)
      (unless (xht? h) ($oops who "~s is not a hashtable" h))
      (case (xht-type h)
        [(eq) (eq? (constant eq-hashtable-subtype-weak) (eq-ht-subtype h))]
        [(eqv) (eq? (constant eq-hashtable-subtype-weak) (eq-ht-subtype (eqv-ht-eqht h)))]
        [(generic) (let ([eq-ht (gen-ht-eqht h)])
                     (and eq-ht (eq? (constant eq-hashtable-subtype-weak) (eq-ht-subtype eq-ht))))]
        [else #f])))

  (set-who! hashtable-ephemeron?
    (lambda (h)
      (unless (xht? h) ($oops who "~s is not a hashtable" h))
      (case (xht-type h)
        [(eq) (eq? (constant eq-hashtable-subtype-ephemeron) (eq-ht-subtype h))]
        [(eqv) (eq? (constant eq-hashtable-subtype-ephemeron) (eq-ht-subtype (eqv-ht-eqht h)))]
        [(generic) (let ([eq-ht (gen-ht-eqht h)])
                     (and eq-ht (eq? (constant eq-hashtable-subtype-ephemeron) (eq-ht-subtype eq-ht))))]
        [else #f])))

  (set-who! symbol-hashtable-ref
    (lambda (h x v)
      (unless (symbol-ht? h) ($oops who "~s is not a symbol hashtable" h))
      (unless (symbol? x) ($oops who "~s is not a symbol" x))
      (#3%symbol-hashtable-ref h x v)))

    (set-who! symbol-hashtable-ref-cell
      (lambda (h x)
        (unless (symbol-ht? h) ($oops who "~s is not a symbol hashtable" h))
        (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
        (unless (symbol? x) ($oops who "~s is not a symbol" x))
        (#3%symbol-hashtable-ref-cell h x)))

  (set-who! symbol-hashtable-contains?
    (lambda (h x)
      (unless (symbol-ht? h) ($oops who "~s is not a symbol hashtable" h))
      (unless (symbol? x) ($oops who "~s is not a symbol" x))
      (#3%symbol-hashtable-contains? h x)))

  (set-who! symbol-hashtable-set!
    (lambda (h x v)
      (unless (symbol-ht? h) ($oops who "~s is not a symbol hashtable" h))
      (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
      (unless (symbol? x) ($oops who "~s is not a symbol" x))
      (#3%symbol-hashtable-set! h x v)))

  (set-who! symbol-hashtable-update!
    (lambda (h x p v)
      (unless (symbol-ht? h) ($oops who "~s is not a symbol hashtable" h))
      (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
      (unless (symbol? x) ($oops who "~s is not a symbol" x))
      (unless (procedure? p)
        ($oops who "~s is not a procedure" p))
      (#3%symbol-hashtable-update! h x p v)))

  (set-who! symbol-hashtable-cell
    (lambda (h x v)
      (unless (symbol-ht? h) ($oops who "~s is not a symbol hashtable" h))
      (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
      (unless (symbol? x) ($oops who "~s is not a symbol" x))
      (#3%symbol-hashtable-cell h x v)))

  (set-who! symbol-hashtable-delete!
    (lambda (h x)
      (unless (symbol-ht? h) ($oops who "~s is not a symbol hashtable" h))
      (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
      (unless (symbol? x) ($oops who "~s is not a symbol" x))
      (#3%symbol-hashtable-delete! h x)))

  (set-who! hashtable-ref
    (lambda (h x v)
      (unless (xht? h)
        ($oops who "~s is not a hashtable" h))
      (case (xht-type h)
        [(eq) (#3%eq-hashtable-ref h x v)]
        [(symbol)
         (unless (symbol? x) ($oops 'symbol-hash "~s is not a symbol" x))
         (#3%symbol-hashtable-ref h x v)]
        [(eqv) ($eqv-hashtable-ref h x v who)]
        [else ($gen-hashtable-ref h x v who)])))

  (set-who! hashtable-ref-cell
    (lambda (h x)
      (unless (xht? h)
        ($oops who "~s is not a hashtable" h))
      (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
      (case (xht-type h)
        [(eq) (#3%eq-hashtable-ref-cell h x)]
        [(symbol)
         (unless (symbol? x) ($oops 'symbol-hash "~s is not a symbol" x))
         (#3%symbol-hashtable-ref-cell h x)]
        [(eqv) ($eqv-hashtable-ref-cell h x who)]
        [else ($gen-hashtable-ref-cell h x who)])))

  (set-who! hashtable-contains?
    (lambda (h x)
      (unless (xht? h)
        ($oops who "~s is not a hashtable" h))
      (case (xht-type h)
        [(eq) (#3%eq-hashtable-contains? h x)]
        [(symbol)
         (unless (symbol? x) ($oops 'symbol-hash "~s is not a symbol" x))
         (#3%symbol-hashtable-contains? h x)]
        [(eqv) ($eqv-hashtable-contains? h x who)]
        [else ($gen-hashtable-contains? h x who)])))

  (set-who! hashtable-set!
    (lambda (h x v)
      (unless (xht? h)
        ($oops who "~s is not a hashtable" h))
      (unless (xht-mutable? h)
        ($oops who "~s is not mutable" h))
      (case (xht-type h)
        [(eq) (#3%eq-hashtable-set! h x v)]
        [(symbol)
         (unless (symbol? x) ($oops 'symbol-hash "~s is not a symbol" x))
         (#3%symbol-hashtable-set! h x v)]
        [(eqv) ($eqv-hashtable-set! h x v who)]
        [else ($gen-hashtable-set! h x v who)])))

  (set-who! hashtable-update!
    (lambda (h x p v)
      (unless (xht? h)
        ($oops who "~s is not a hashtable" h))
      (unless (xht-mutable? h)
        ($oops who "~s is not mutable" h))
      (unless (procedure? p)
        ($oops who "~s is not a procedure" p))
      (case (xht-type h)
        [(eq) (#3%eq-hashtable-update! h x p v)]
        [(symbol)
         (unless (symbol? x) ($oops 'symbol-hash "~s is not a symbol" x))
         (#3%symbol-hashtable-update! h x p v)]
        [(eqv) ($eqv-hashtable-update! h x p v who)]
        [else ($gen-hashtable-update! h x p v who)])))

  (set-who! hashtable-cell
    (lambda (h x v)
      (unless (xht? h)
        ($oops who "~s is not a hashtable" h))
      (unless (xht-mutable? h) ($oops who "~s is not mutable" h))
      (case (xht-type h)
        [(eq) (#3%eq-hashtable-cell h x v)]
        [(symbol)
         (unless (symbol? x) ($oops 'symbol-hash "~s is not a symbol" x))
         (#3%symbol-hashtable-cell h x v)]
        [(eqv) ($eqv-hashtable-cell h x v who)]
        [else ($gen-hashtable-cell h x v who)])))

  (set-who! hashtable-delete!
    (lambda (h x)
      (unless (xht? h)
        ($oops who "~s is not a hashtable" h))
      (unless (xht-mutable? h)
        ($oops who "~s is not mutable" h))
      (case (xht-type h)
        [(eq) (#3%eq-hashtable-delete! h x)]
        [(symbol)
         (unless (symbol? x) ($oops 'symbol-hash "~s is not a symbol" x))
         (#3%symbol-hashtable-delete! h x)]
        [(eqv) ($eqv-hashtable-delete! h x who)]
        [else ($gen-hashtable-delete! h x who)])))

  (set! hashtable-copy
    (rec hashtable-copy
      (case-lambda
        [(h) (hashtable-copy h #f)]
        [(h mutable?)
         (unless (xht? h)
           ($oops 'hashtable-copy "~s is not a hashtable" h))
         (case (xht-type h)
           [(eq) ($eq-hashtable-copy h (and mutable? #t))]
           [(symbol) ($symbol-hashtable-copy h (and mutable? #t))]
           [(eqv) ($eqv-hashtable-copy h (and mutable? #t))]
           [else ($gen-hashtable-copy h (and mutable? #t))])])))

  (set-who! hashtable-clear!
    (let ()
      (case-lambda
        [(h)
         (unless (xht? h)
           ($oops who "~s is not a hashtable" h))
         (unless (xht-mutable? h)
           ($oops who "~s is not mutable" h))
         (case (xht-type h)
           [(eq) ($eq-hashtable-clear! h (ht-minlen h))]
           [(eqv)
            (let ([h (eqv-ht-eqht h)]) ($eq-hashtable-clear! h (ht-minlen h)))
            (let ([h (eqv-ht-genht h)]) ($gen-hashtable-clear! h (ht-minlen h)))]
           [(generic) ($gen-hashtable-clear! h (ht-minlen h))]
           [else ($ht-hashtable-clear! h (ht-minlen h))])]
        [(h k)
         (unless (xht? h)
           ($oops who "~s is not a hashtable" h))
         (unless (xht-mutable? h)
           ($oops who "~s is not mutable" h))
         (let ([minlen (size->minlen who k)])
           (case (xht-type h)
             [(eq) ($eq-hashtable-clear! h minlen)]
             [(eqv)
              ($eq-hashtable-clear! (eqv-ht-eqht h) minlen)
              ($gen-hashtable-clear! (eqv-ht-genht h) minlen)]
             [(generic) ($gen-hashtable-clear! h minlen)]
             [else ($ht-hashtable-clear! h minlen)]))])))

  (let ()
    (define (invalid-length who max-sz)
      ($oops who "~s is not a valid length" max-sz))
    (define (invalid-table who h)
      ($oops who "~s is not a hashtable" h))

    (define-syntax hashtable-content-dispatch
      (syntax-rules ()
        [(_ who $eq-hashtable-content $eqv-hashtable-content $gen-hashtable-content $ht-hashtable-content)
         (let ()
           (define (dispatch h max-sz)
             (unless (xht? h) (invalid-table who h))
             (case (xht-type h)
               [(eq) ($eq-hashtable-content h max-sz)]
               [(eqv) ($eqv-hashtable-content h max-sz)]
               [(generic) ($gen-hashtable-content h max-sz)]
               [else ($ht-hashtable-content h max-sz)]))
           (case-lambda
            [(h max-sz)
             (cond
              [(fixnum? max-sz)
               (unless (fx>= max-sz 0) (invalid-length who max-sz))
               (dispatch h max-sz)]
              [(bignum? max-sz)
               (unless (>= max-sz 0) (invalid-length who max-sz))
               (dispatch h (most-positive-fixnum))]
              [else (invalid-length who max-sz)])]
            [(h) (dispatch h (most-positive-fixnum))]))]))

    (set-who! hashtable-keys
      (hashtable-content-dispatch who
                                  $eq-hashtable-keys
                                  $eqv-hashtable-keys
                                  $gen-hashtable-keys
                                  $ht-hashtable-keys))

    (set-who! #(r6rs: hashtable-keys)
      (lambda (h)
        (unless (xht? h) (invalid-table who h))
        (case (xht-type h)
          [(eq) ($eq-hashtable-keys h (most-positive-fixnum))]
          [(eqv) ($eqv-hashtable-keys h (most-positive-fixnum))]
          [(generic) ($gen-hashtable-keys h (most-positive-fixnum))]
          [else ($ht-hashtable-keys h (most-positive-fixnum))])))

    (set-who! hashtable-values
      (hashtable-content-dispatch who
                                  $eq-hashtable-values
                                  $eqv-hashtable-values
                                  $gen-hashtable-values
                                  $ht-hashtable-values))

    (set-who! hashtable-entries
      (hashtable-content-dispatch who
                                  $eq-hashtable-entries
                                  $eqv-hashtable-entries
                                  $gen-hashtable-entries
                                  $ht-hashtable-entries))

    (set-who! #(r6rs: hashtable-entries)
      (lambda (h)
        (unless (xht? h) (invalid-table who h))
        (case (xht-type h)
          [(eq) ($eq-hashtable-entries h (most-positive-fixnum))]
          [(eqv) ($eqv-hashtable-entries h (most-positive-fixnum))]
          [(generic) ($gen-hashtable-entries h (most-positive-fixnum))]
          [else ($ht-hashtable-entries h (most-positive-fixnum))])))

    (set-who! $hashtable-cells
      (hashtable-content-dispatch who
                                  $eq-hashtable-cells
                                  $eqv-hashtable-cells
                                  $gen-hashtable-cells
                                  $ht-hashtable-cells)))

  (set-who! hashtable-cells
    (case-lambda
     [(h max-sz)
      (unless (xht? h)
        ($oops who "~s is not a hashtable" h))
      (unless (xht-mutable? h)
        ($oops who "~s is not mutable" h))
      (unless (and (integer? max-sz) (exact? max-sz) (not (negative? max-sz)))
        ($oops who "~s is not a valid length" max-sz))
      (let ([max-sz (if (fixnum? max-sz) max-sz (hashtable-size h))])
        (case (xht-type h)
          [(eq) ($eq-hashtable-cells h max-sz)]
          [(eqv) ($eqv-hashtable-cells h max-sz)]
          [(generic) ($gen-hashtable-cells h max-sz)]
          [else ($ht-hashtable-cells h max-sz)]))]
     [(h)
      (hashtable-cells h (and (xht? h) (hashtable-size h)))]))

  (set! hashtable-size
    (let ([$gen-ht-size (lambda (h)
                          (let ([eqht (gen-ht-eqht h)])
                            (if eqht
                                (ht-size eqht)
                                (ht-size h))))])
      (lambda (h)
        (unless (xht? h) ($oops 'hashtable-size "~s is not a hashtable" h))
        (case (xht-type h)
          [(eqv) (fx+ (ht-size (eqv-ht-eqht h))
                      ($gen-ht-size (eqv-ht-genht h)))]
          [(generic) ($gen-ht-size h)]
          [else (ht-size h)]))))

  (set! hashtable-mutable?
    (lambda (h)
      (unless (xht? h)
        ($oops 'hashtable-mutable? "~s is not a hashtable" h))
      (xht-mutable? h)))

  (set! hashtable?
    (lambda (x)
      (xht? x)))

  (set! eq-hashtable?
    (lambda (x)
      (eq-ht? x)))

  (set! symbol-hashtable?
    (lambda (x)
      (symbol-ht? x)))

  (set-who! $hashtable-size->minlen
    (lambda (k)
      (size->minlen who k)))

  (set-who! hashtable-hash-function
    (lambda (h)
      (unless (xht? h) ($oops who "~s is not an eq hashtable" h))
      (case (xht-type h)
        [(eq eqv) #f]
        [(symbol) symbol-hash]
        [else (gen-ht-hash h)])))

  (set-who! hashtable-equivalence-function
    (lambda (h)
      (unless (xht? h) ($oops who "~s is not an eq hashtable" h))
      (case (xht-type h)
        [(eq) eq?]
        [(symbol) (symbol-ht-equiv? h)]
        [(eqv) eqv?]
        [else (gen-ht-equiv? h)])))

  (let ()
    (define (hcabs hc) (if (fx< hc 0) (fxnot hc) hc))

    (define (update hc k)
      (let ([hc2 (fx+/wraparound hc (fxsll/wraparound (fx+/wraparound hc k) 10))])
        (fxlogxor hc2 (fxsrl hc2 6))))

    (define bytevector-hash
      (lambda (bv)
        (define (bvupdate hc bv i)
          (update hc (bytevector-u8-ref bv i)))
        (let ([n (bytevector-length bv)])
          (if (fx<= n 16)
              (do ([i 0 (fx+ i 1)] [hc 440697712 (bvupdate hc bv i)])
                ((fx= i n) (hcabs hc)))
              (do ([i 0 (fx+ i 1)]
                   [hc 440697712 (bvupdate hc bv i)])
                  ((fx= i 5)
                   (do ([i (fx- n 5) (fx+ i 1)]
                        [hc hc (bvupdate hc bv i)])
                       ((fx= i n)
                        (let ([stride (fxsrl n 4)])
                          (do ([i 5 (fx+ i stride)]
                               [hc hc (bvupdate hc bv i)])
                              ((fx>= i n) (hcabs hc))))))))))))

    (set-who! string-hash
      (lambda (s)
        (define (strupdate hc s i)
          (update hc (char->integer (string-ref s i))))
        (unless (string? s) ($oops who "~s is not a string" s))
        (let ([n (string-length s)])
          (if (fx<= n 16)
              (do ([i 0 (fx+ i 1)] [hc 523658599 (strupdate hc s i)])
                  ((fx= i n) (hcabs hc)))
              (do ([i 0 (fx+ i 1)]
                   [hc 523658599 (strupdate hc s i)])
                  ((fx= i 5)
                   (do ([i (fx- n 5) (fx+ i 1)]
                        [hc hc (strupdate hc s i)])
                       ((fx= i n)
                        (let ([stride (fxsrl n 4)])
                          (do ([i 5 (fx+ i stride)]
                               [hc hc (strupdate hc s i)])
                              ((fx>= i n) (hcabs hc))))))))))))

    (set-who! string-ci-hash
      (lambda (s)
        (define (charupdate hc c) (update hc (char->integer c)))
        (unless (string? s) ($oops who "~s is not a string" s))
        (let ([n (string-length s)])
          (let f ([i 0] [hc 523658599])
            (if (fx= i n)
                (hcabs hc)
                (let g ([c* ($string-char-foldcase (string-ref s i))] [hc hc])
                  (if (char? c*)
                      (f (fx+ i 1) (charupdate hc c*))
                      (g (cdr c*) (charupdate hc (car c*))))))))))

    (set-who! symbol-hash
      (lambda (x)
        (unless (symbol? x) ($oops who "~s is not a symbol" x))
        (or ($symbol-hash x)
            (and (gensym? x) (begin (gensym->unique-string x) ($symbol-hash x)))
            ($oops who "symbol hash is not set for ~s" x))))

    (set-who! equal-hash
      (lambda (x)
        (define (f x hc i)
          (let ([i (fx- i 1)])
            (define-syntax (vector-hash stx)
              (syntax-case stx ()
                [(_ base vector-length vector-ref)
                 #'(let ([n (vector-length x)] [hc (update hc base)])
                     (if (fx= n 0)
                         (values hc i)
                         (let g ([j 0] [hc hc] [i i])
                           (if (or (fx= j n) (fx= i 0))
                               (values hc i)
                               (let ([i/2 (fxsrl (fx+ i 1) 1)])
                                 (let-values ([(hc i^) (f (vector-ref x j) hc i/2)])
                                   (g (fx+ j 1) hc (fx+ (fx- i i/2) i^))))))))]))
            (cond
              [(fx<= i 0) (values hc 0)]
              [(pair? x)
               (let ([i/2 (fxsrl (fx+ i 1) 1)])
                 (let-values ([(hc i^) (f (car x) (update hc 119001092) i/2)])
                   (f (cdr x) hc (fx+ (fx- i i/2) i^))))]
              [(vector? x) (vector-hash 513566316 vector-length vector-ref)]
              [($stencil-vector? x)
               (let ([n ($stencil-vector-length x)] [hc (update hc 517766377)])
                 (if (fx= n 0)
                     (values hc i)
                     (let g ([j 0] [hc hc] [i i])
                       (if (or (fx= j n) (fx= i 0))
                           (values hc i)
                           (let ([i/2 (fxsrl (fx+ i 1) 1)])
                             (let-values ([(hc i^) (f ($stencil-vector-ref x j) hc i/2)])
                               (g (fx+ j 1) hc (fx+ (fx- i i/2) i^))))))))]
              [(null? x) (values (update hc 496904691) i)]
              [(box? x) (f (unbox x) (update hc 410225874) i)]
              [(symbol? x) (values (update hc (symbol-hash x)) i)]
              [(string? x) (values (update hc (string-hash x)) i)]
              [(number? x) (values (update hc (number-hash x)) i)]
              [(bytevector? x) (values (update hc (bytevector-hash x)) i)]
              [(boolean? x) (values (update hc (if x 336200167 307585980)) i)]
              [(char? x) (values (update hc (char->integer x)) i)]
              [(and ($record? x) ($record-hash-procedure x))
               => (lambda (rec-hash)
                    (let ([new-i i])
                      (let ([sub-hc (rec-hash
                                      x
                                      (lambda (v)
                                        (if (fx<= new-i 0)
                                            0
                                            (let-values ([(sub-hc sub-i) (f v 0 i)])
                                              (set! new-i sub-i)
                                              sub-hc))))])
                        (let ([hc (update hc (if (fixnum? sub-hc)
                                                 sub-hc
                                                 (modulo (abs sub-hc) (greatest-fixnum))))])
                          (values hc new-i)))))]
              [(fxvector? x) (vector-hash 513577316 fxvector-length fxvector-ref)]
              [(flvector? x) (vector-hash 513599316 flvector-length flvector-ref)]
              [else (values (update hc 120634730) i)])))
        (let-values ([(hc i) (f x 523658599 64)])
          (hcabs hc)))))

  (record-writer (type-descriptor hashtable)
    (lambda (x p wr)
      (display "#<hashtable>" p)))

  (record-writer (type-descriptor eq-ht)
    (lambda (x p wr)
      (display "#<eq hashtable>" p)))

  (record-writer (type-descriptor eqv-ht)
    (lambda (x p wr)
      (display "#<eqv hashtable>" p)))
)

;;; eq hashtable operations must be compiled with
;;; generate-interrupt-trap #f and optimize-level 3
;;; so they can't be interrupted by a collection
;;; see also library routines in library.ss
(eval-when (compile)
  (generate-interrupt-trap #f)
  (optimize-level 3))

(let ()
  (include "hashtable-types.ss")

  (set! $eq-hashtable-keys
    (lambda (h max-sz)
      (let ([vec (ht-vec h)] [size (fxmin max-sz (ht-size h))])
        (let ([n (vector-length vec)] [keys (make-vector size)])
          (let outer ([i 0] [j 0])
            (if (or (fx= i n) (fx= j size))
                keys
                (let inner ([b (vector-ref vec i)] [j j])
                  (if (or (fixnum? b) (fx= j size))
                      (outer (fx+ i 1) j)
                      (let ([keyval ($tlc-keyval b)])
                        (vector-set! keys j (car keyval))
                        (inner ($tlc-next b) (fx+ j 1)))))))))))

  (set! $eq-hashtable-values
    (lambda (h max-sz)
      (let ([vec (ht-vec h)] [size (fxmin max-sz (ht-size h))])
        (let ([n (vector-length vec)] [vals (make-vector size)])
          (let outer ([i 0] [j 0])
            (if (or (fx= i n) (fx= j size))
                vals
                (let inner ([b (vector-ref vec i)] [j j])
                  (if (or (fixnum? b) (fx= j size))
                      (outer (fx+ i 1) j)
                      (let ([keyval ($tlc-keyval b)])
                        (vector-set! vals j (cdr keyval))
                        (inner ($tlc-next b) (fx+ j 1)))))))))))

  (set! $eq-hashtable-entries
    (lambda (h max-sz)
      (let ([vec (ht-vec h)] [size (fxmin max-sz (ht-size h))])
        (let ([n (vector-length vec)]
              [keys (make-vector size)]
              [vals (make-vector size)])
          (let outer ([i 0] [j 0])
            (if (or (fx= i n) (fx= j size))
                (values keys vals)
                (let inner ([b (vector-ref vec i)] [j j])
                  (if (or (fixnum? b) (fx= j size))
                      (outer (fx+ i 1) j)
                      (let ([keyval ($tlc-keyval b)])
                        (vector-set! keys j (car keyval))
                        (vector-set! vals j (cdr keyval))
                        (inner ($tlc-next b) (fx+ j 1)))))))))))

  (set! $eq-hashtable-cells
    (lambda (h max-sz)
      (let ([vec (ht-vec h)] [size (fxmin max-sz (ht-size h))])
        (let ([n (vector-length vec)] [cells (make-vector size)])
          (let outer ([i 0] [j 0])
            (if (or (fx= i n) (fx= j size))
                cells
                (let inner ([b (vector-ref vec i)] [j j])
                  (if (or (fixnum? b) (fx= j size))
                      (outer (fx+ i 1) j)
                      (let ([keyval ($tlc-keyval b)])
                        (vector-set! cells j keyval)
                        (inner ($tlc-next b) (fx+ j 1)))))))))))

  (set! $eq-hashtable-copy
    (lambda (h1 mutable?)
      (let ([subtype (eq-ht-subtype h1)])
        (let* ([vec1 (ht-vec h1)]
               [n (vector-length vec1)]
               [vec2 ($make-eqhash-vector n)]
               [h2 (make-eq-ht 'eq mutable? vec2 (ht-minlen h1) (ht-size h1) subtype)])
          (let outer ([i 0])
            (if (fx= i n)
                h2
                (begin
                  (vector-set! vec2 i
                    (let inner ([b (vector-ref vec1 i)])
                      (if (fixnum? b)
                          b
                          ($make-tlc h2
                            (let* ([keyval ($tlc-keyval b)] [key (car keyval)] [val (cdr keyval)])
                              (cond
                               [(eq? subtype (constant eq-hashtable-subtype-normal)) (cons key val)]
                               [(eq? subtype (constant eq-hashtable-subtype-weak)) (weak-cons key val)]
                               [else (ephemeron-cons key val)]))
                            (inner ($tlc-next b))))))
                  (outer (fx+ i 1)))))
          h2))))

  (set! $eq-hashtable-clear!
    (lambda (h minlen)
      (let* ([vec (ht-vec h)] [n (vector-length vec)])
        (do ([i 0 (fx+ i 1)])
            ((fx= i n))
          (let loop ([b (vector-ref vec i)])
            (if (fixnum? b)
                (vector-set! vec i i)
                (let ([next ($tlc-next b)])
                  ($set-tlc-next! b #f)
                  (loop next)))))
        (ht-size-set! h 0)
        (unless (fx= n minlen)
          (ht-vec-set! h ($make-eqhash-vector minlen))))))
  
  (let ()
    ;; An equal/hash mapping contains an equal or hash procedure (or #f)
    ;; plus the rtd where the procedure was installed. It also has a weak
    ;; list of uids for child rtds that have inherited the setting, in
    ;; case the rtd's setting changes.
    (define-record-type equal/hash
      (fields maybe-proc rtd (mutable inheritors))
      (nongenerative)
      (sealed #t)
      (protocol
        (lambda (new)
          (lambda (maybe-proc rtd)
            (new maybe-proc rtd '())))))

    (let ()
      (define (get-equal/hash who rtd key)
        (unless (record-type-descriptor? rtd)
          ($oops who "~s is not a record-type descriptor" rtd))
        (let ([e/h ($sgetprop (record-type-uid  rtd) key #f)])
          (and e/h
               (eq? (equal/hash-rtd e/h) rtd)
               (equal/hash-maybe-proc e/h))))
      (define (set-equal/hash! who rtd key proc)
        (unless (record-type-descriptor? rtd)
          ($oops who "~s is not a record-type descriptor" rtd))
        (unless (or (not proc) (procedure? proc))
          ($oops who "~s is not a procedure or #f" proc))
        (with-tc-mutex
          (let* ([uid (record-type-uid rtd)]
                 [old-e/h ($sgetprop uid key #f)])
            ;; Remove the old record from anywhere that it's inherited,
            ;; and a later lookup will re-inherit:
            (when old-e/h
              (for-each
                (lambda (uid)
                  (unless (bwp-object? uid)
                    (when (eq? ($sgetprop uid key #f) old-e/h)
                      ($sremprop uid key))))
                (equal/hash-inheritors old-e/h)))
            (if proc
                ($sputprop uid key (make-equal/hash proc rtd))
                ($sremprop uid key)))))
      (set-who! record-type-equal-procedure
        (case-lambda
          [(rtd) (get-equal/hash who rtd 'equal-proc)]
          [(rtd equal-proc) (set-equal/hash! who rtd 'equal-proc equal-proc)]))
      (set-who! record-type-hash-procedure
        (case-lambda
          [(rtd) (get-equal/hash who rtd 'hash-proc)]
          [(rtd hash-proc) (set-equal/hash! who rtd 'hash-proc hash-proc)])))

    (let ()
      ;; Gets an `equal/hash` record for the given rtd, finding
      ;; it from a parent rtd and caching if necessary:
      (define (lookup-equal/hash record key)
        (let* ([rtd ($record-type-descriptor record)] [uid (record-type-uid rtd)])
          ; Get out quick w/o mutex if equal/hash record is present
          (or ($sgetprop uid key #f)
              (with-tc-mutex
                (let f ([uid uid] [rtd rtd])
                  ;; Double-check first time around to avoid a race
                  (or ($sgetprop uid key #f)
                      (let ([parent-rtd (record-type-parent rtd)])
                        (if parent-rtd
                            ;; Cache parent's value, and register as an inheritor:
                            (let ([e/h (f (record-type-uid parent-rtd) parent-rtd)])
                              (equal/hash-inheritors-set! e/h (weak-cons uid (equal/hash-inheritors e/h)))
                              ($sputprop uid key e/h)
                              e/h)
                            ;; Cache an empty `equal/hash` record:
                            (let ([e/h (make-equal/hash #f rtd)])
                              ($sputprop uid key e/h)
                              e/h)))))))))
      (let ()
        (define (lookup-equal-procedure record1 record2)
          (let ([e/h (lookup-equal/hash record1 'equal-proc)])
            (let ([proc (equal/hash-maybe-proc e/h)])
              (if proc
                  (and
                    (eq? (equal/hash-rtd (lookup-equal/hash record2 'equal-proc)) (equal/hash-rtd e/h))
                    proc)
                  (let ([default-proc (default-record-equal-procedure)])
                    (and default-proc
                         (not (equal/hash-maybe-proc (lookup-equal/hash record2 'equal-proc)))
                         default-proc))))))
        (set-who! $record-equal-procedure
          (lambda (record1 record2)
            (lookup-equal-procedure record1 record2)))
        (set-who! record-equal-procedure
          (lambda (record1 record2)
            (unless ($record? record1) ($oops who "~s is not a record" record1))
            (unless ($record? record2) ($oops who "~s is not a record" record2))
            (lookup-equal-procedure record1 record2))))
      (let ()
        (define (lookup-hash-procedure record)
          (or (equal/hash-maybe-proc (lookup-equal/hash record 'hash-proc))
              (default-record-hash-procedure)))
        (set-who! $record-hash-procedure
          (lambda (record)
            (lookup-hash-procedure record)))
        (set-who! record-hash-procedure
          (lambda (record)
            (unless ($record? record) ($oops who "~s is not a record" record))
            (lookup-hash-procedure record))))))
)
