cs: better equal-secondary-hash-code
Since CS doesn't use secondary hash code internally, the `equal-secondary-hash-code` function wasn't really implemented. Implement it reasonably for applications that might use it to implement other data structures. Testing exposed other problems related to error reporting for a broken hash-function result and for using values within immutable hash tables. Closes #3536
This commit is contained in:
parent
a2a456cd2d
commit
3c382284a4
|
@ -990,6 +990,43 @@
|
||||||
|
|
||||||
;; ----------------------------------------
|
;; ----------------------------------------
|
||||||
|
|
||||||
|
(let ([got-here? #f])
|
||||||
|
(struct foo2 (f g) #:transparent
|
||||||
|
#:property prop:equal+hash
|
||||||
|
(list (λ (a b recur) #f)
|
||||||
|
(λ (a recur) 0)
|
||||||
|
(λ (a recur) (set! got-here? #t) 0)))
|
||||||
|
(define (check-secondary-used v)
|
||||||
|
(set! got-here? #f)
|
||||||
|
(equal-secondary-hash-code v)
|
||||||
|
got-here?)
|
||||||
|
(test #t check-secondary-used (foo2 0 "ggg"))
|
||||||
|
;; Although nothing promises that we'll hash an element within a
|
||||||
|
;; list, vector, etc., the current implementation is meant to
|
||||||
|
;; do so in at least these cases:
|
||||||
|
(test #t check-secondary-used (list (foo2 0 "ggg")))
|
||||||
|
(test #t check-secondary-used (cons 6 (foo2 0 "ggg")))
|
||||||
|
(test #t check-secondary-used (vector (foo2 0 "ggg")))
|
||||||
|
(test #t check-secondary-used (box (foo2 0 "ggg")))
|
||||||
|
(test #t check-secondary-used (hash 'ok (foo2 0 "ggg"))))
|
||||||
|
|
||||||
|
;; ----------------------------------------
|
||||||
|
|
||||||
|
(let ([got-here? #f])
|
||||||
|
(struct foo (f g)
|
||||||
|
#:property prop:equal+hash
|
||||||
|
(list (λ (a b recur) #f)
|
||||||
|
(λ (a recur) 'wrong)
|
||||||
|
(λ (a recur) 'wrong)))
|
||||||
|
(err/rt-test (equal-hash-code (foo 1 2))
|
||||||
|
exn:fail:contract?
|
||||||
|
#rx"hash procedure returned a value other than an exact integer")
|
||||||
|
(err/rt-test (equal-secondary-hash-code (foo 1 2))
|
||||||
|
exn:fail:contract?
|
||||||
|
#rx"hash procedure returned a value other than an exact integer"))
|
||||||
|
|
||||||
|
;; ----------------------------------------
|
||||||
|
|
||||||
(let ()
|
(let ()
|
||||||
(define-struct foo (a [b #:mutable]) #:transparent)
|
(define-struct foo (a [b #:mutable]) #:transparent)
|
||||||
(define-struct (bar foo) (f g)
|
(define-struct (bar foo) (f g)
|
||||||
|
|
|
@ -281,12 +281,14 @@ adjust-framework-boot-compress:
|
||||||
$(BOOTSTRAP_RACKET) $(srcdir)/adjust-compress.rkt @BOOT_COMPRESS_COMP@ $(FW_BOOT_DEST)/petite.boot $(FW_BOOT_DEST)/scheme.boot $(FW_BOOT_DEST)/racket.boot
|
$(BOOTSTRAP_RACKET) $(srcdir)/adjust-compress.rkt @BOOT_COMPRESS_COMP@ $(FW_BOOT_DEST)/petite.boot $(FW_BOOT_DEST)/scheme.boot $(FW_BOOT_DEST)/racket.boot
|
||||||
|
|
||||||
mac-embed-boot-fw:
|
mac-embed-boot-fw:
|
||||||
|
rm -f $(EMBED_DEST)
|
||||||
cp $(EMBED_SRC) $(EMBED_DEST)
|
cp $(EMBED_SRC) $(EMBED_DEST)
|
||||||
$(STRIP_SIGNATURE) $(EMBED_DEST)
|
$(STRIP_SIGNATURE) $(EMBED_DEST)
|
||||||
|
|
||||||
FW_BOOT_DESTS = $(FW_BOOT_DEST)/petite.boot $(FW_BOOT_DEST)/scheme.boot $(FW_BOOT_DEST)/racket.boot
|
FW_BOOT_DESTS = $(FW_BOOT_DEST)/petite.boot $(FW_BOOT_DEST)/scheme.boot $(FW_BOOT_DEST)/racket.boot
|
||||||
|
|
||||||
mac-embed-boot-static:
|
mac-embed-boot-static:
|
||||||
|
rm -f $(EMBED_DEST)
|
||||||
cp $(EMBED_SRC) $(EMBED_DEST)
|
cp $(EMBED_SRC) $(EMBED_DEST)
|
||||||
$(STRIP_SIGNATURE) $(EMBED_DEST)
|
$(STRIP_SIGNATURE) $(EMBED_DEST)
|
||||||
$(BOOTSTRAP_RACKET) $(srcdir)/embed-boot.rkt @BOOT_COMPRESS_COMP@ $(EMBED_SRC) $(EMBED_DEST) $(FW_BOOT_DESTS)
|
$(BOOTSTRAP_RACKET) $(srcdir)/embed-boot.rkt @BOOT_COMPRESS_COMP@ $(EMBED_SRC) $(EMBED_DEST) $(FW_BOOT_DESTS)
|
||||||
|
|
|
@ -1139,12 +1139,13 @@
|
||||||
;; rare.
|
;; rare.
|
||||||
(hash-code-combine hc (cnode-hash c))])))]
|
(hash-code-combine hc (cnode-hash c))])))]
|
||||||
[else
|
[else
|
||||||
|
(let ([offset (fx+ HAMT-STATIC-FIELD-COUNT child-count key-count)])
|
||||||
(let loop ([i 0] [hc hc])
|
(let loop ([i 0] [hc hc])
|
||||||
(cond
|
(cond
|
||||||
[(fx< i val-count)
|
[(fx< i val-count)
|
||||||
(loop (fx1+ i)
|
(loop (fx1+ i)
|
||||||
(hash-code-combine hc (hash (stencil-vector-ref n (fx+ i child-count key-count)))))]
|
(hash-code-combine hc (hash (stencil-vector-ref n offset))))]
|
||||||
[else hc]))]))))
|
[else hc])))]))))
|
||||||
|
|
||||||
(define (bnode-fold n f nil)
|
(define (bnode-fold n f nil)
|
||||||
(let* ([mask (stencil-vector-mask n)]
|
(let* ([mask (stencil-vector-mask n)]
|
||||||
|
|
|
@ -61,6 +61,14 @@
|
||||||
v
|
v
|
||||||
(bitwise-and v (greatest-fixnum))))
|
(bitwise-and v (greatest-fixnum))))
|
||||||
|
|
||||||
|
(define (->fx/checked who v)
|
||||||
|
(cond
|
||||||
|
[(fixnum? v) v]
|
||||||
|
[(bignum? v) (bitwise-and v (greatest-fixnum))]
|
||||||
|
[else (raise-arguments-error who
|
||||||
|
"hash procedure returned a value other than an exact integer"
|
||||||
|
"result" v)]))
|
||||||
|
|
||||||
;; Mostly copied from Chez Scheme's "newhash.ss":
|
;; Mostly copied from Chez Scheme's "newhash.ss":
|
||||||
(define number-hash
|
(define number-hash
|
||||||
(lambda (z)
|
(lambda (z)
|
||||||
|
@ -89,8 +97,8 @@
|
||||||
;; We don't use `equal-hash` because we need impersonators to be able
|
;; We don't use `equal-hash` because we need impersonators to be able
|
||||||
;; to generate the same hash code as the unwrapped value.
|
;; to generate the same hash code as the unwrapped value.
|
||||||
(define (equal-hash-code x)
|
(define (equal-hash-code x)
|
||||||
(call-with-values (lambda () (equal-hash-loop x 0 0))
|
(let-values ([(hc burn) (equal-hash-loop x 0 0)])
|
||||||
(lambda (hc burn) (logand hc (most-positive-fixnum)))))
|
hc))
|
||||||
|
|
||||||
;; A #t result implies that `equal-hash-code` and equality checking is
|
;; A #t result implies that `equal-hash-code` and equality checking is
|
||||||
;; pretty fast
|
;; pretty fast
|
||||||
|
@ -104,20 +112,8 @@
|
||||||
(not (#%$record-hash-procedure x)))))
|
(not (#%$record-hash-procedure x)))))
|
||||||
|
|
||||||
(define (equal-secondary-hash-code x)
|
(define (equal-secondary-hash-code x)
|
||||||
(cond
|
(let-values ([(hc burn) (equal-secondary-hash-loop x 0 0)])
|
||||||
[(boolean? x) 1]
|
hc))
|
||||||
[(null? x) 2]
|
|
||||||
[(number? x) 3]
|
|
||||||
[(char? x) 4]
|
|
||||||
[(symbol? x) 5]
|
|
||||||
[(string? x) 6]
|
|
||||||
[(bytevector? x) 7]
|
|
||||||
[(box? x) 8]
|
|
||||||
[(pair? x) 9]
|
|
||||||
[(vector? x) (vector-length x)]
|
|
||||||
[(#%$record? x) (eq-hash-code (record-rtd x))]
|
|
||||||
[(impersonator? x) (equal-secondary-hash-code (impersonator-val x))]
|
|
||||||
[else 100]))
|
|
||||||
|
|
||||||
(define MAX-HASH-BURN 128)
|
(define MAX-HASH-BURN 128)
|
||||||
|
|
||||||
|
@ -167,7 +163,8 @@
|
||||||
[(and (#%$record? x) (#%$record-hash-procedure x))
|
[(and (#%$record? x) (#%$record-hash-procedure x))
|
||||||
=> (lambda (rec-hash)
|
=> (lambda (rec-hash)
|
||||||
(let ([burn (fx+ burn 2)])
|
(let ([burn (fx+ burn 2)])
|
||||||
(let ([hc (fx+/wraparound hc (->fx
|
(let ([hc (fx+/wraparound hc (->fx/checked
|
||||||
|
'equal-hash-code
|
||||||
(rec-hash x (lambda (x)
|
(rec-hash x (lambda (x)
|
||||||
(let-values ([(hc0 burn0) (equal-hash-loop x burn 0)])
|
(let-values ([(hc0 burn0) (equal-hash-loop x burn 0)])
|
||||||
(set! burn burn0)
|
(set! burn burn0)
|
||||||
|
@ -180,6 +177,71 @@
|
||||||
(equal-hash-loop (impersonator-val x) burn hc)]
|
(equal-hash-loop (impersonator-val x) burn hc)]
|
||||||
[else (values (fx+/wraparound hc (eq-hash-code x)) burn)]))
|
[else (values (fx+/wraparound hc (eq-hash-code x)) burn)]))
|
||||||
|
|
||||||
|
(define (equal-secondary-hash-loop x burn hc)
|
||||||
|
(cond
|
||||||
|
[(fx> burn MAX-HASH-BURN) (values hc burn)]
|
||||||
|
[(boolean? x) (values (fx+/wraparound hc 1) burn)]
|
||||||
|
[(null? x) (values (fx+/wraparound hc 2) burn)]
|
||||||
|
[(number? x) (values (fx+/wraparound hc (number-secondary-hash x)) burn)]
|
||||||
|
[(char? x) (values (fx+/wraparound hc (fxnot (char->integer x))) burn)]
|
||||||
|
[(symbol? x) (values (fx+/wraparound hc (fxnot (symbol-hash x))) burn)]
|
||||||
|
[(string? x) (values (fx+/wraparound hc (fxnot (string-hash x))) burn)]
|
||||||
|
[(bytevector? x) (values (fx+/wraparound hc (equal-hash x)) burn)]
|
||||||
|
[(fxvector? x) (values (fx+/wraparound hc (equal-hash x)) burn)]
|
||||||
|
[(flvector? x) (values (fx+/wraparound hc (equal-hash x)) burn)]
|
||||||
|
[(box? x) (equal-secondary-hash-loop (unbox x) (fx+ burn 1) (fx+/wraparound hc 10))]
|
||||||
|
[(pair? x)
|
||||||
|
(let-values ([(hc0 burn) (equal-secondary-hash-loop (car x) (fx+ burn 2) 0)])
|
||||||
|
(let ([hc (fx+/wraparound (mix-hash-code hc) hc0)])
|
||||||
|
(equal-secondary-hash-loop (cdr x) burn hc)))]
|
||||||
|
[(vector? x)
|
||||||
|
(let ([len (vector-length x)])
|
||||||
|
(let vec-loop ([i 0] [burn burn] [hc (mix-hash-code hc)])
|
||||||
|
(cond
|
||||||
|
[(fx= i len) (values hc burn)]
|
||||||
|
[else
|
||||||
|
(let-values ([(hc0 burn) (equal-secondary-hash-loop (vector-ref x i) (fx+ burn 2) 0)])
|
||||||
|
(vec-loop (fx+ i 1)
|
||||||
|
burn
|
||||||
|
(fx+/wraparound (mix-hash-code hc) hc0)))])))]
|
||||||
|
[(hash? x)
|
||||||
|
;; Treat hash-table hashing specially, so it can be order-insensitive
|
||||||
|
(let ([burn (fx* (fxmax burn 1) 2)])
|
||||||
|
(let ([hc (fx+/wraparound hc (->fx (hash-hash-code
|
||||||
|
x
|
||||||
|
(lambda (x)
|
||||||
|
(let-values ([(hc0 burn0) (equal-secondary-hash-loop x burn 0)])
|
||||||
|
hc0)))))])
|
||||||
|
(values hc burn)))]
|
||||||
|
[(and (#%$record? x)
|
||||||
|
(or (struct-property-ref 'secondary-hash (#%$record-type-descriptor x) #f)
|
||||||
|
;; to use default hash proc as default secondary hash proc:
|
||||||
|
(#%$record-hash-procedure x)))
|
||||||
|
=> (lambda (rec-hash)
|
||||||
|
(let ([burn (fx+ burn 2)])
|
||||||
|
(let ([hc (fx+/wraparound hc (->fx/checked
|
||||||
|
'equal-secondary-hash-code
|
||||||
|
(rec-hash x (lambda (x)
|
||||||
|
(let-values ([(hc0 burn0) (equal-secondary-hash-loop x burn 0)])
|
||||||
|
(set! burn burn0)
|
||||||
|
hc0)))))])
|
||||||
|
(values hc burn))))]
|
||||||
|
[(impersonator? x)
|
||||||
|
(equal-secondary-hash-loop (impersonator-val x) burn hc)]
|
||||||
|
[else (values (fx+/wraparound hc (fxnot (eq-hash-code x))) burn)]))
|
||||||
|
|
||||||
|
(define number-secondary-hash
|
||||||
|
(lambda (z)
|
||||||
|
(cond
|
||||||
|
[(fixnum? z) (fxnot z)]
|
||||||
|
[(flonum? z) (fxsll (#3%$flhash z) 2)]
|
||||||
|
[(bignum? z) (fx+/wraparound (integer-length z)
|
||||||
|
(bitwise-bit-field z 0 (fx- (fixnum-width) 1)))]
|
||||||
|
[(ratnum? z) (hash-code-combine (number-secondary-hash (numerator z))
|
||||||
|
(number-secondary-hash (denominator z)))]
|
||||||
|
[else (hash-code-combine (number-secondary-hash (real-part z))
|
||||||
|
(number-secondary-hash (imag-part z)))])))
|
||||||
|
|
||||||
(define (hash-code-combine hc v)
|
(define (hash-code-combine hc v)
|
||||||
(fx+/wraparound (mix-hash-code (->fx hc))
|
(fx+/wraparound (mix-hash-code (->fx hc))
|
||||||
(->fx v)))
|
(->fx v)))
|
||||||
|
|
|
@ -356,7 +356,8 @@
|
||||||
(record-type-hash-procedure rtd (let ([p (caddr guarded-val)])
|
(record-type-hash-procedure rtd (let ([p (caddr guarded-val)])
|
||||||
(if (#%procedure? p)
|
(if (#%procedure? p)
|
||||||
p
|
p
|
||||||
(lambda (v h) (|#%app| p v h))))))
|
(lambda (v h) (|#%app| p v h)))))
|
||||||
|
(struct-property-set! 'secondary-hash rtd (cadddr guarded-val)))
|
||||||
(struct-property-set! prop rtd guarded-val)
|
(struct-property-set! prop rtd guarded-val)
|
||||||
(values (hash-set ht prop check-val)
|
(values (hash-set ht prop check-val)
|
||||||
(append
|
(append
|
||||||
|
|
Loading…
Reference in New Issue
Block a user