[prev in list] [next in list] [prev in thread] [next in thread] 

List:       sbcl-devel
Subject:    Re: [Sbcl-devel] [Sbcl-commits] master: Hash-cons all MEMBER types
From:       Douglas Katzman via Sbcl-devel <sbcl-devel () lists ! sourceforge ! net>
Date:       2023-01-31 2:53:10
Message-ID: CAOrNasyC_WHRbkijEfFOVdYRcvLRcPqvgg5-L5=T8cYGNN_h6w () mail ! gmail ! com
[Download RAW message or body]

[Attachment #2 (multipart/alternative)]


I added some printing if there are any elements in that table, and I've not
seen it print anything, so I don't know what it's doing for you. But it
does depend on GC behavior so I made it tolerant of worse GC. Not sure why
that should be needed, but I think it's still a valid test.

On Mon, Jan 30, 2023 at 9:29 PM Stas Boukarev <stassats@gmail.com> wrote:

> I'm randomly getting things like
> ::: UNEXPECTED-FAILURE :XSET-STABLE-HASH-WEAKNESS due to SIMPLE-ERROR:
>         "The assertion
>          (< (HASH-TABLE-COUNT SB-KERNEL::*XSET-STABLE-HASHES*) 100) failed
> with
>          (HASH-TABLE-COUNT SB-KERNEL::*XSET-STABLE-HASHES*) = 145."
>
> On Sat, Dec 31, 2022 at 8:07 PM snuglas via Sbcl-commits
> <sbcl-commits@lists.sourceforge.net> wrote:
> >
> > The branch "master" has been updated in SBCL:
> >        via  c7c449b50fb072cabd9517879ac7b1e75d68c768 (commit)
> >       from  ef088c6fbce7dca5032aa08a3702b7f622b41d32 (commit)
> >
> > - Log -----------------------------------------------------------------
> > commit c7c449b50fb072cabd9517879ac7b1e75d68c768
> > Author: Douglas Katzman <dougk@google.com>
> > Date:   Sat Dec 31 03:11:15 2022 -0500
> >
> >     Hash-cons all MEMBER types
> >
> >     Types whose members include arbitrary objects such as lists or
> strings will now
> >     choose and memoize per-element pseudorandom hashes as needed, while
> trying to
> >     avoid creating a forever-growing set of objects that acquired a
> stable hash,
> >     as the object->hash association is dropped when no XSET refers to an
> object.
> > ---
> >  src/code/type-class.lisp          | 121
> ++++++++++++--------------------------
> >  src/code/type.lisp                |  62 +++++++++++++++----
> >  src/code/xset.lisp                | 106
> +++++++++++++++++++++++----------
> >  src/compiler/ctype.lisp           |   2 +-
> >  src/compiler/generic/genesis.lisp |  19 ++++--
> >  tests/typehashmix.impure.lisp     |  30 ++++++++++
> >  6 files changed, 207 insertions(+), 133 deletions(-)
> >
> > diff --git a/src/code/type-class.lisp b/src/code/type-class.lisp
> > index e04e04c5e..f14dadab9 100644
> > --- a/src/code/type-class.lisp
> > +++ b/src/code/type-class.lisp
> > @@ -734,9 +734,10 @@
> >
> >  ;; Singleton MEMBER types are best dealt with via a weak-value
> hash-table because:
> >  ;; * (MEMBER THING) might lack an address-insensitive hash for THING
> > -;;   but src/code/hashset can not use address-based hashes. This
> limitation is unique
> > -;;   to MEMBER types because other CTYPE instances are compositions of
> CTYPES
> > -;;   where all subparts have assigned hashes.
> > +;;   but src/code/hashset goes through a lot of rigmarole to handle
> address-bashed
> > +;;   hashing, and the end result for a single key would laboriously
> emulate an EQL table.
> > +;;   This is especially important for the compiler because each time it
> asks itself the
> > +;;   CTYPE-OF a constant leaf, the answer might be a singleton MEMBER
> type.
> >  ;; * Symbols have slightly bad SXHASH values (by language requirement):
> >  ;;    "For any two objects, x and y which are symbols and which are
> similar
> >  ;;    (sxhash x) and (sxhash y) yield the same mathematical value even
> if x and y exist
> > @@ -747,35 +748,6 @@
> >  ;;   will cause the hashset to exceed its probe sequence length limit.
> >  ;;   This isn't to say we couldn't assign some bits of SYMBOL-HASH
> pseudorandomly,
> >  ;;   and mask them out in the value returned by CL:SXHASH.
> > -;;
> > -;; Also: XSETs containing arbitrary objects such as strings and lists
> don't have
> > -;; a good hash at all. There is not really a way to compute a mixed hash
> > -;; other than by pinning all objects in the XSET and taking their
> addresses.
> > -;; Then we'd need to figure out that GC happened, and it becomes a pain.
> > -;; This seems more complicated than the situation warrants.
> > -;; So we'll just give up on hash-consing which should fix lp#1999687
> > -;;
> > -;; An outline of a better design would be as follows:
> > -;; - create a global hash-table of objects which were placed in an XSET
> > -;;   other than the nicely hashable object types. Call this
> xset-key->hash-mapping.
> > -;;   This mapping does not need to be weak, because it will have a way
> of purging it.
> > -;;   Each value in the table is a cons of a random hash and the number
> of XSETs
> > -;;   using the key. Increment the refcount when making a new XSET with
> that key.
> > -;; - when hashing the XSET, look up its keys (other than EQL hashable)
> in the global table
> > -;; - attach a finalizer to the XSET. The finalizer's job is to
> decrement the refcount
> > -;;   on each key in the global mappping. When the count reaches zero
> there are no
> > -;;   XSETs that refer to the key, and the random hash can be removed.
> > -;; Why make a refcounted table? Because otherwise there is no way to
> remove mapping
> > -;; entries for objects that outlive the MEMBER-TYPE's use of the object
> but where the
> > -;; MEMBER type itself is dead. Worst-case, every object in the lisp
> image could at some
> > -;; point appear in a MEMBER type but then never be needed again with
> regard to type
> > -;; system operations. So you'd have created a permanent mapping of
> every object to
> > -;; a random hash for no good reason.
> > -
> > -;; Why the singleton table is so important is that any time the
> compiler asks itself
> > -;; the ctype-of a constant leaf, it might yield `(MEMBER ,the-constant).
> > -;; So then you end up with an assortment of random objects that don't
> hash
> > -;; nicely in a ctype hashset, but are OK in a hash-table.
> >  (define-load-time-global *eql-type-cache* ; like EQL-SPECIALIZER-TABLE
> in PCL
> >      (sb-impl::make-system-hash-table :test 'eql :weakness :value
> :synchronized nil))
> >
> > @@ -784,9 +756,8 @@
> >         (heap-allocated-p ,obj)))
> >
> >  (defvar *hashsets-preloaded* nil)
> > -(defmacro new-ctype (pseudonym &rest initargs)
> > -  (let* ((name (if (eq pseudonym 'eql) 'member-type pseudonym))
> > -         (allocator (package-symbolicate "SB-KERNEL" "!ALLOC-" name))
> > +(defmacro new-ctype (name &rest initargs)
> > +  (let* ((allocator (package-symbolicate "SB-KERNEL" "!ALLOC-" name))
> >           (hashset (package-symbolicate "SB-KERNEL" "*" name
> "-HASHSET*"))
> >           (bits (ctype-class-bits (ctype-instance->type-class name))))
> >      #+sb-xc-host ; allocate permanent data, and insert into cache if
> not found
> > @@ -803,32 +774,7 @@
> >         (unless *hashsets-preloaded*
> >           (write-string "CTYPE hashset preload failure")
> >           (sb-vm:ldb-monitor))
> > -       ,(case pseudonym
> > -          (eql ; as per above remarks: use hash-table, not hashset
> > -            `(let* ((xset ,(first initargs))
> > -                    (zeros ,(second initargs))
> > -                    (key (first (or zeros (xset-data xset))))
> > -                    (table *eql-type-cache*))
> > -               (with-system-mutex ((hash-table-lock table))
> > -                ;; This is like ENSURE-GETHASH but it potentially
> copies the key
> > -                (or (gethash key table)
> > -                    ;; hope no off-heap pointers buried within KEY
> > -                    (let ((key (cond ((numberp key)
> (sb-vm:copy-number-to-heap key))
> > -                                     ((safe-member-type-elt-p key) key)
> > -                                     (t
> > -                                      (warn "Off-heap hash-table key @
> ~X"
> > -                                            (get-lisp-obj-address key))
> > -                                      key))))
> > -                      (setf (gethash key table) (copy-ctype temp)))))))
> > -          (member-type ; problem case: don't always know how to hash
> well
> > -           `(let ((xset ,(first initargs)))
> > -              (flet ((hashable (x) (typep x '(or symbol number
> character instance))))
> > -                (if (xset-every #'hashable xset)
> > -                    (hashset-insert-if-absent ,hashset temp
> #'copy-ctype)
> > -                    ;; otherwise just copy it always (for now)
> > -                    (copy-ctype temp)))))
> > -          (t
> > -            `(hashset-insert-if-absent ,hashset temp #'copy-ctype))))))
> > +       (hashset-insert-if-absent ,hashset temp #'copy-ctype))))
> >
> >  ;;; The NAMED-TYPE is used to represent *, T and NIL, the standard
> >  ;;; special cases, as well as other special cases needed to
> > @@ -878,6 +824,14 @@
> >  (def-type-model (member-type (:constructor* nil (xset fp-zeroes)))
> >    (xset nil :type xset :hasher xset-elts-hash :test xset=)
> >    (fp-zeroes nil :type list :hasher hash-fp-zeros :test fp-zeros=)))
> > +(define-load-time-global *xset-mutex* (or #-sb-xc-host
> (sb-thread:make-mutex)))
> > +;;; This hashset is guarded by *XSET-MUTEX*. It is _not_ declared as
> synchronized
> > +;;; so that HASHSET-INSERT-IF-ABSENT should not acquire a mutex inside
> a mutex
> > +;;; (stable hashes have to be assigned while holding the lock)
> > +(define-load-time-global *member/eq-type-hashset*
> > +    (make-hashset 32 #'member-type-equiv #'calc-member-type-hash
> > +                  :weakness t :synchronized nil))
> > +(pushnew '*member/eq-type-hashset* *ctype-hashsets*)
> >
> >  ;;; An ARRAY-TYPE is used to represent any array type, including
> >  ;;; things such as SIMPLE-BASE-STRING.
> > @@ -1248,6 +1202,8 @@
> >
> >  ;;; Return the name of the global hashset that OBJ (a CTYPE instance)
> >  ;;; would be stored in, if it were stored in one.
> > +;;; This is only for bootstrap, and not 100% precise as it does not know
> > +;;; about the other MEMBER type containers.
> >  (defun ctype->hashset-sym (obj)
> >    (macrolet ((generate  ()
> >                 (collect ((clauses))
> > @@ -1331,25 +1287,21 @@
> >  #-sb-xc-host
> >  (progn
> >  (defglobal *!initial-ctypes* nil)
> > -(defun preload-ctype-hashsets (&aux permtypes)
> > -  (declare (ignorable permtypes))
> > +(defun preload-ctype-hashsets ()
> >    (dolist (pair (nreverse *!initial-ctypes*))
> > -    (destructuring-bind (instance . hashset-symbol) pair
> > -      (cond ((not hashset-symbol)
> > -             ;; There are very few which aren't in a hashset:
> > -             ;; - (6) NAMED-TYPEs
> > -             ;; - (1) MEMBER-TYPE NULL
> > -             ;; - (3) BASE-CHAR, EXTENDED-CHAR, CHARACTER
> > -             ;; - (1) CONS
> > -             (push instance permtypes))
> > -            ;; Mandatory special-case for singleton MEMBER types
> > -            ((and (member-type-p instance) (not (cdr
> (member-type-members instance))))
> > -             (setf (gethash (car (member-type-members instance))
> *eql-type-cache*)
> > -                   instance))
> > +    (let ((instance (car pair))
> > +          (container (symbol-value (cdr pair))))
> > +      (cond ((hash-table-p container)
> > +             (aver (member-type-p instance))
> > +             ;; As of this writing there are only two EQL types to
> preload:
> > +             ;; one is in the IR1-transform of FORMAT with stream (EQL
> T),
> > +             ;; the other is CHECK-ARG-TYPE looking for (EQL DUMMY)
> type.
> > +             (let ((key (first (member-type-members instance))))
> > +               (aver (not (gethash key container)))
> > +               (setf (gethash key container) instance)))
> >              (t
> > -             (let ((hashset (symbol-value hashset-symbol)))
> > -               (aver (not (hashset-find hashset instance))) ; instances
> are dumped bottom-up
> > -               (hashset-insert hashset instance))))
> > +             (aver (not (hashset-find container instance))) ; instances
> are built bottom-up
> > +             (hashset-insert container instance)))
> >        (labels ((ensure-interned-list (list hashset)
> >                   (let ((found (hashset-find hashset list)))
> >                     (when (and found (neq found list))
> > @@ -1367,7 +1319,7 @@
> >                         (bug "genesis dumped bad instance within ~X"
> >                              (get-lisp-obj-address instance)))))))
> >          (etypecase instance
> > -          ((or named-type numeric-type member-type character-set-type ;
> nothing extra to do
> > +          ((or numeric-type member-type character-set-type ; nothing
> extra to do
> >             #+sb-simd-pack simd-pack-type #+sb-simd-pack-256
> simd-pack-256-type
> >             hairy-type))
> >            (args-type
> > @@ -1387,7 +1339,6 @@
> >             (ensure-interned-list (compound-type-types instance)
> *ctype-set-hashset*))
> >            (negation-type
> >             (check (negation-type-type instance)))))))
> > -  (aver (= (length permtypes) (+ 11 #-sb-unicode -2)))
> >    #+sb-devel (setq *hashsets-preloaded* t))
> >  (preload-ctype-hashsets))
> >
> > @@ -1530,10 +1481,16 @@
> >
> >  ;;; Copy X to the heap, give it a random hash, and if it is a MEMBER
> type
> >  ;;; then assert that all members are cacheable.
> > +#+sb-xc-host
> > +(defun copy-ctype (x)
> > +  (let ((bits (logior (logand (ctype-random) +type-hash-mask+)
> (type-%bits x))))
> > +    (etypecase x
> > +      (member-type
> > +       (!alloc-member-type bits (member-type-xset x)
> (member-type-fp-zeroes x))))))
> >  #-sb-xc-host
> >  (defun copy-ctype (x)
> >    (declare (type ctype x))
> > -  (declare (sb-c::tlab :system) (inline !copy-xset))
> > +  (declare (sb-c::tlab :system) (inline !new-xset))
> >    #+c-stack-is-control-stack (aver (stack-allocated-p x))
> >    (labels ((copy (x)
> >               ;; Return a heap copy of X if X was arena or
> stack-allocated.
> > @@ -1559,7 +1516,7 @@
> >                      ;; While we could use (load-time-value) to referece
> a constant empty xset
> >                      ;; there's really no point to doing that.
> >                      (collect ((elts))
> > -                      (dolist (x data (!copy-xset (xset-list-size xset)
> (elts)))
> > +                      (dolist (x data (!new-xset (elts) (xset-extra
> xset)))
> >                          (elts (cond ((numberp x)
> (sb-vm:copy-number-to-heap x))
> >                                      ((safe-member-type-elt-p x) x)
> >                                      ;; surely things will go haywire if
> this occurs
> > diff --git a/src/code/type.lisp b/src/code/type.lisp
> > index 71b35509a..73c4da809 100644
> > --- a/src/code/type.lisp
> > +++ b/src/code/type.lisp
> > @@ -341,6 +341,9 @@
> >                unparsed
> >                (nconc unparsed '(&optional))))))
> >
> > +#+sb-xc-host ; why only on the host? Shouldn't we always declaim the
> ftype??
> > +(declaim (ftype (sfunction (ctype ctype) (values t t)) type=))
> > +
> >  ;;; Return true if LIST1 and LIST2 have the same elements in the same
> >  ;;; positions according to TYPE=. We return NIL, NIL if there is an
> >  ;;; uncertain comparison.
> > @@ -3814,6 +3817,13 @@ used for a COMPLEX component.~:@>"
> >  ;; Return possibly a union of a MEMBER type and a NUMERIC type,
> >  ;; or just one or the other, or *EMPTY-TYPE* depending on what's in the
> XSET
> >  ;; and the FP-ZEROES. XSET must not contains characters or real numbers.
> > +;; MEMBER types go into one of three hash containers:
> > +;;  - *EQL-TYPE-CACHE* holds singleton types. A weak hash-table
> suffices for this.
> > +;;  - *MEMBER-TYPE-HASHSET* holds types whose members are
> {NUMBER|CHARACTER|SYMBOL}.
> > +;;    Intrinsically each element has a stable hash, making it possible
> to
> > +;;    hash-cons XSETs without complications for EQ-comparable keys.
> > +;;  - *MEMBER/EQ-TYPE-HASHSET* is the general case, allowing a mixture
> of objects
> > +;;;   hashed by content-dependent hash and/or pseudorandom opaque hash.
> >  (defun make-member-type (xset fp-zeroes)
> >    ;; if we have a pair of zeros (e.g. 0.0d0 and -0.0d0), then we can
> >    ;; canonicalize to (DOUBLE-FLOAT 0.0d0 0.0d0), because numeric
> > @@ -3851,18 +3861,48 @@ used for a COMPLEX component.~:@>"
> >        ;; Bypass the hashset for type NULL because it's so important
> >        (return-from make-member-type
> >          (inline-cache-ctype (!alloc-member-type
> (pack-interned-ctype-bits 'member)
> > -                                                (xset-from-list '(nil))
> > +                                                (!new-xset '(nil) 1)
> >                                                  '())
> >                              null))))
> > -    (let ((member-type
> > -           (case (+ (length unpaired) (xset-count xset))
> > -             (0 nil) ; nil
> > -             ;; It's slightly suboptimal to use two DX-lets, but to
> remedy that,
> > -             ;; a single macro invocation would need to select which
> hash collection
> > -             ;; to look in. More easy would be to paste the macro guts
> here
> > -             ;; with suitable alteration, which I don't want to do.
> > -             (1 (new-ctype eql xset unpaired)) ; most common case
> > -             (t (new-ctype member-type xset unpaired)))))
> > +    (let* ((count (+ (length unpaired) (xset-count xset)))
> > +           (member-type
> > +            (unless (= count 0)
> > +              (dx-let ((temp (!alloc-member-type (ctype-class-bits
> 'member)
> > +                                                 xset unpaired)))
> > +                (cond
> > +                  ((= count 1)
> > +                   (let ((container *eql-type-cache*)
> > +                         (key (first (or unpaired (xset-data xset)))))
> > +                     (with-system-mutex ((hash-table-lock container))
> > +                       ;; This is like ENSURE-GETHASH but it
> potentially copies the key
> > +                       (or (gethash key container)
> > +                           (let ((copy (copy-ctype temp)))
> > +                             ;; re-fetch KEY from XSET in case it was
> copied.
> > +                             ;; hope no off-heap pointers buried within
> KEY.
> > +                             (setf (gethash (first (member-type-members
> copy)) container)
> > +                                   copy))))))
> > +                  ((xset-every (lambda (x) (typep x '(or symbol number
> character))) xset)
> > +                   (hashset-insert-if-absent *member-type-hashset* temp
> #'copy-ctype))
> > +                  (t
> > +                   (binding*
> > +                       ((container *member/eq-type-hashset*)
> > +                        ((result foundp)
> > +                         (with-system-mutex (*xset-mutex*)
> > +                           (xset-generate-stable-hashes xset)
> > +                           (acond ((hashset-find container temp)
> > +                                   (xset-delete-stable-hashes xset) ;
> inside the mutex scope
> > +                                   (values it t))
> > +                                  (t
> > +                                   (values (hashset-insert container
> (copy-ctype temp))
> > +                                           nil))))))
> > +                     (unless foundp ; "use" the var binding if
> #+sb-xc-host
> > +                       #-sb-xc-host ; attach finalizer (outside the
> mutex scope)
> > +                       (let ((xset (member-type-xset result))) ; in
> case XSET was copied
> > +                         (finalize
> > +                          result (lambda ()
> > +                                   (with-system-mutex (*xset-mutex*)
> > +                                     (xset-delete-stable-hashes
> xset))))))
> > +                     result)))))))
> >        ;; The actual member-type contains the XSET (with no FP zeroes),
> >        ;; and a list of unpaired zeroes.
> >        (if (not float-types)
> > @@ -4631,8 +4671,6 @@ used for a COMPLEX component.~:@>"
> >
> >  (define-type-class cons :enumerable nil :might-contain-other-types nil)
> >
> > -#+sb-xc-host
> > -(declaim (ftype (sfunction (ctype ctype) (values t t)) type=))
> >  (defun make-cons-type (car-type cdr-type)
> >    (aver (not (or (eq car-type *wild-type*)
> >                   (eq cdr-type *wild-type*))))
> > diff --git a/src/code/xset.lisp b/src/code/xset.lisp
> > index a6a86cb16..c8faa2bfb 100644
> > --- a/src/code/xset.lisp
> > +++ b/src/code/xset.lisp
> > @@ -22,25 +22,31 @@
> >  ;;;; requires a function as the first argument -- not a function
> >  ;;;; designator.
> >  ;;;;
> > -;;;; XSET-LIST-SIZE is true only for XSETs whose data is stored into a
> > -;;;; list -- XSET-COUNT returns the real value.
> > -;;;;
> >  ;;;; Note: XSET always uses EQL as the equivalence test
> >
> >  (in-package "SB-KERNEL")
> >
> >  (defstruct (xset (:constructor alloc-xset)
> > -                 (:constructor !copy-xset (list-size data))
> > +                 (:constructor !new-xset (data extra))
> >                   (:copier nil)
> >                   (:predicate nil))
> > -  (list-size 0 :type index)
> > -  (data nil :type (or list hash-table)))
> > +  (data nil :type (or list hash-table))
> > +  ;; EXTRA is a dual-purpose slot: initially it holds the number of
> items
> > +  ;; in LIST. If the list becomes a hash-table, then EXTRA becomes 0.
> > +  ;; An XSET can be optionally have a vector of stable hashes, 1 per
> element.
> > +  ;; The hash vector if present goes in EXTRA, and the vector length
> > +  ;; is the same as the list length. After creating a hash vector, it
> is forbidden
> > +  ;; to add more elements to the set. In this manner we can avoid
> adding a subtype
> > +  ;; of XSET stably-hashed-xset, or wasting a slot that would almost
> never be used.
> > +  ;; (99.999% of all XSETs do not need stable hashes)
> > +  (extra 0 :type (or simple-vector index)))
> >  (declaim (freeze-type xset))
> >
> >  (defun xset-count (xset)
> >    (let ((data (xset-data xset)))
> >      (if (listp data)
> > -        (xset-list-size xset)
> > +        (let ((extra (xset-extra xset)))
> > +          (if (fixnump extra) extra (length extra)))
> >          (hash-table-count data))))
> >
> >  (defun map-xset (function xset)
> > @@ -60,28 +66,21 @@
> >
> >  ;;; Checks that the element is not in the set yet.
> >  (defun add-to-xset (elt xset)
> > -  (let ((data (xset-data xset))
> > -        (size (xset-list-size xset)))
> > +  (let ((data (xset-data xset)))
> >      (if (listp data)
> > -        (if (< size +xset-list-size-limit+)
> > -            (unless (member elt data :test #'eql)
> > -              (setf (xset-list-size xset) (1+ size)
> > -                    (xset-data xset) (cons elt data)))
> > -            (let ((table (make-hash-table :size (* 2 size) :test
> #'eql)))
> > -              (setf (gethash elt table) t)
> > -              (dolist (x data)
> > -                (setf (gethash x table) t))
> > -              (setf (xset-data xset) table)))
> > +        (let ((size (xset-extra xset)))
> > +          (if (< size +xset-list-size-limit+)
> > +              (unless (member elt data :test #'eql)
> > +                (setf (xset-extra xset) (1+ size)
> > +                      (xset-data xset) (cons elt data)))
> > +              (let ((table (make-hash-table :size (* 2 size) :test
> #'eql)))
> > +                (setf (gethash elt table) t)
> > +                (dolist (x data)
> > +                  (setf (gethash x table) t))
> > +                (setf (xset-extra xset) 0 ; looks nice to clear it
> > +                      (xset-data xset) table))))
> >          (setf (gethash elt data) t))))
> >
> > -;; items must be canonical - no duplicates - and few in number.
> > -(defun xset-from-list (items)
> > -  (let ((n (length items)))
> > -    (aver (<= n +xset-list-size-limit+))
> > -    (let ((xset (alloc-xset)))
> > -      (setf (xset-list-size xset) n (xset-data xset) items)
> > -      xset)))
> > -
> >  (defun xset-union (a b)
> >    (let ((xset (alloc-xset)))
> >      (map-xset (lambda (x)
> > @@ -186,12 +185,16 @@
> >  (defun xset-elts-hash (xset)
> >    (let ((h 0))
> >      (declare (sb-xc:fixnum h))
> > -    (map-xset (lambda (x)
> > -                ;; Rather than masking each intermediate result to
> MOST-POSITIVE-FIXNUM,
> > -                ;; allow bits to rollover into the sign bit
> > -                (when (typep x '(or symbol number character
> #-sb-xc-host instance))
> > -                  (setq h (plus-mod-fixnum (sb-xc:sxhash x) h))))
> > -              xset)
> > +    ;; Rather than masking each intermediate result to
> MOST-POSITIVE-FIXNUM,
> > +    ;; allow bits to rollover into the sign bit
> > +    (let ((hashes (xset-extra xset)))
> > +      (if (simple-vector-p hashes)
> > +          (dovector (x hashes)
> > +            (setq h (plus-mod-fixnum h (truly-the fixnum (if (listp x)
> (cdr x) x)))))
> > +          (map-xset (lambda (x)
> > +                      (when (typep x '(or symbol number character))
> > +                        (setq h (plus-mod-fixnum (sb-xc:sxhash x) h))))
> > +                    xset)))
> >      ;; Now mix the bits thoroughly and then mask to a positive fixnum.
> >      ;; I think this does not need to be compatible between host and
> target.
> >      ;; But I'm trying to make it compatible anyway because I'm not 100%
> sure
> > @@ -203,3 +206,42 @@
> >             #+sb-xc-host (ldb (byte sb-vm:n-word-bits 0) (ash h
> sb-vm:n-fixnum-tag-bits))
> >             #-sb-xc-host (get-lisp-obj-address h)))
> >        (logand (sb-impl::murmur3-fmix-word word-bits)
> most-positive-fixnum))))
> > +
> > +;;; Stably-hashed XSETs that have elements which are not nicely
> EQL-hashable
> > +;;; rely on a global table that maps any object to a pseudorandom hash.
> > +;;; The table keys are refcounted so that they can be deleted when no
> XSET
> > +;;; references a particular key. Caller MUST provide synchronization.
> > +(define-load-time-global *xset-stable-hashes* (make-hash-table :test
> 'eq))
> > +
> > +(defun xset-generate-stable-hashes (xset &aux (hashmap
> *xset-stable-hashes*))
> > +  #-sb-xc-host (declare (notinline sb-impl::eql-hash)) ; forward ref
> > +  (flet ((get-stable-hash-cell (obj)
> > +           (let ((cell (gethash obj hashmap)))
> > +             (cond (cell
> > +                    (incf (car cell))
> > +                    cell)
> > +                   (t
> > +                    (setf (gethash obj hashmap) (cons 1
> (ctype-random))))))))
> > +    (let ((hashes (make-array (xset-count xset)))
> > +          (i 0))
> > +      (map-xset (lambda (elt)
> > +                  (multiple-value-bind (hashval eq?)
> > +                      #+sb-xc-host (if (sb-xc:typep elt '(or symbol
> character number))
> > +                                       (values (sb-xc:sxhash elt) nil)
> > +                                       (values 4 ; chosen by algorithm
> of https://xkcd.com/221/
> > +                                               t)) ; yes, it's
> address-based
> > +                      #-sb-xc-host (sb-impl::eql-hash elt)
> > +                    (setf (aref hashes i) (if eq? (get-stable-hash-cell
> elt) hashval))
> > +                    (incf i)))
> > +                xset)
> > +      (setf (xset-extra xset) hashes)))
> > +  xset)
> > +(defun xset-delete-stable-hashes (xset &aux (hashmap
> *xset-stable-hashes*))
> > +  (let ((hashes (the simple-vector (xset-extra xset)))
> > +        (index -1))
> > +    ;; Iteration order will be the same as it was in
> GENERATE-STABLE-HASHES
> > +    (map-xset (lambda (elt &aux (cell (aref hashes (incf index))))
> > +                (when (and (listp cell)
> > +                           (zerop (decf (car cell)))) ; element is not
> used in any XSET
> > +                  (remhash elt hashmap)))
> > +              xset)))
> > diff --git a/src/compiler/ctype.lisp b/src/compiler/ctype.lisp
> > index 18deeec53..7b846ee2e 100644
> > --- a/src/compiler/ctype.lisp
> > +++ b/src/compiler/ctype.lisp
> > @@ -242,7 +242,7 @@ and no value was provided for it." name))))))))))
> >       (let* ((ctype (lvar-type lvar))
> >              (int (funcall *ctype-test-fun* ctype type)))
> >         (cond ((not int)
> > -              (unless (type= ctype (specifier-type '(member dummy)))
> > +              (unless (type= ctype (specifier-type '(eql dummy)))
> >                  (note-lossage "The ~:R argument is a ~S, not a ~S."
> >                                n (type-specifier ctype) (type-specifier
> type)))
> >                nil)
> > diff --git a/src/compiler/generic/genesis.lisp
> b/src/compiler/generic/genesis.lisp
> > index 18b0a5aad..dd83acc31 100644
> > --- a/src/compiler/generic/genesis.lisp
> > +++ b/src/compiler/generic/genesis.lisp
> > @@ -1084,6 +1084,8 @@ core and return a descriptor to it."
> >      (loop (if (cold-null list) (return n))
> >            (incf n)
> >            (setq list (cold-cdr list)))))
> > +(defun cold-push (item symbol)
> > +  (cold-set symbol (cold-cons item (cold-symbol-value symbol))))
> >
> >  ;;; Make a simple-vector on the target that holds the specified
> >  ;;; OBJECTS, and return its descriptor.
> > @@ -1489,13 +1491,18 @@ core and return a descriptor to it."
> >                (let ((cell (cold-find-classoid-cell (classoid-name obj)
> :create t)))
> >                  (write-slots cell :classoid result)))
> >               ((ctype-p obj)
> > +              ;; If OBJ belongs in a hash container, then deduce which
> >                (let* ((hashset (sb-kernel::ctype->hashset-sym obj))
> > -                     (entry-p (and hashset (hashset-find (symbol-value
> hashset) obj))))
> > -                ;; Record for preloading in hashset
> > -                (cold-set 'sb-kernel::*!initial-ctypes*
> > -                 (cold-cons (cold-cons result (if entry-p (cold-intern
> hashset)
> > -                                                  *nil-descriptor*))
> > -                            (cold-symbol-value
> 'sb-kernel::*!initial-ctypes*))))))
> > +                     (preload
> > +                      (cond ((and hashset (hashset-find (symbol-value
> hashset) obj))
> > +                             hashset)
> > +                            ((and (member-type-p obj)
> > +                                  ;; NULL is a hardwired case in the
> MEMBER type constructor
> > +                                  (neq obj (specifier-type 'null))
> > +                                  (type-singleton-p obj))
> > +                             'sb-kernel::*eql-type-cache*))))
> > +                (when preload ; Record it
> > +                  (cold-push (cold-cons result preload)
> 'sb-kernel::*!initial-ctypes*)))))
> >         result))))
> >
> >  ;;; Convert a layout to a wrapper and back.
> > diff --git a/tests/typehashmix.impure.lisp
> b/tests/typehashmix.impure.lisp
> > index 70290c9ea..0aac4027c 100644
> > --- a/tests/typehashmix.impure.lisp
> > +++ b/tests/typehashmix.impure.lisp
> > @@ -370,3 +370,33 @@
> >            collect (list i tp))
> >      (let ((post (compute-max-psl hs)))
> >        (assert (<= (- post pre) 2)))))
> > +
> > +(defvar a "foo")
> > +(defvar b '(nil t))
> > +(defvar c #*101)
> > +(with-test (:name :hash-cons-member-type)
> > +  (assert (eq (sb-kernel:specifier-type `(member ,a ,b ,c))
> > +              (sb-kernel:specifier-type `(member ,c ,a ,b))))
> > +  (assert (eq (sb-kernel:specifier-type `(member ,a ,b ,c))
> > +              (sb-kernel:specifier-type `(member ,b ,c ,a)))))
> > +
> > +(with-test (:name :hash-cons-member-type-large)
> > +  (let ((numbers ; force the XSET to be represented as a hash-table
> > +         (loop for i below 30 collect (complex (coerce i 'single-float)
> i)))
> > +        (list '(thing)))
> > +    (assert (hash-table-p
> > +             (sb-kernel::xset-data
> > +              (sb-kernel::member-type-xset
> > +               (sb-kernel:specifier-type `(member ,@numbers))))))
> > +    (assert (eq (sb-kernel:specifier-type `(member ,a ,@numbers ,list))
> > +                (sb-kernel:specifier-type `(member ,list ,a
> ,@numbers))))))
> > +
> > +;(print sb-kernel::*xset-stable-hashes*)
> > +(gc :full t)
> > +#+sb-thread (sb-kernel:run-pending-finalizers)
> > +(with-test (:name :xset-stable-hash-weakness)
> > +  ;; After running the :MEMBER-TYPE-HASH-MIXER test, there were >5000
> entries
> > +  ;; in the *XSET-STABLE-HASHES* table for me.
> > +  ;; The preceding GC should have had some effect.
> > +  (assert (< (hash-table-count sb-kernel::*xset-stable-hashes*)
> > +             100)))
> >
> > -----------------------------------------------------------------------
> >
> >
> > hooks/post-receive
> > --
> > SBCL
> >
> >
> > _______________________________________________
> > Sbcl-commits mailing list
> > Sbcl-commits@lists.sourceforge.net
> > https://lists.sourceforge.net/lists/listinfo/sbcl-commits
>

[Attachment #5 (text/html)]

<div dir="ltr"><div dir="ltr">I added some printing if there are any elements in that \
table, and I&#39;ve not seen it print anything, so I don&#39;t know what it&#39;s \
doing for you. But it does depend on GC behavior so I made it tolerant of worse GC. \
Not sure why that should be needed, but I think it&#39;s still a valid \
test.</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Jan \
30, 2023 at 9:29 PM Stas Boukarev &lt;<a \
href="mailto:stassats@gmail.com">stassats@gmail.com</a>&gt; \
wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px \
0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">I&#39;m randomly \
getting things like<br> ::: UNEXPECTED-FAILURE :XSET-STABLE-HASH-WEAKNESS due to \
SIMPLE-ERROR:<br>  &quot;The assertion<br>
              (&lt; (HASH-TABLE-COUNT SB-KERNEL::*XSET-STABLE-HASHES*) 100) failed \
                with<br>
              (HASH-TABLE-COUNT SB-KERNEL::*XSET-STABLE-HASHES*) = 145.&quot;<br>
<br>
On Sat, Dec 31, 2022 at 8:07 PM snuglas via Sbcl-commits<br>
&lt;<a href="mailto:sbcl-commits@lists.sourceforge.net" \
target="_blank">sbcl-commits@lists.sourceforge.net</a>&gt; wrote:<br> &gt;<br>
&gt; The branch &quot;master&quot; has been updated in SBCL:<br>
&gt;            via   c7c449b50fb072cabd9517879ac7b1e75d68c768 (commit)<br>
&gt;           from   ef088c6fbce7dca5032aa08a3702b7f622b41d32 (commit)<br>
&gt;<br>
&gt; - Log -----------------------------------------------------------------<br>
&gt; commit c7c449b50fb072cabd9517879ac7b1e75d68c768<br>
&gt; Author: Douglas Katzman &lt;<a href="mailto:dougk@google.com" \
target="_blank">dougk@google.com</a>&gt;<br> &gt; Date:     Sat Dec 31 03:11:15 2022 \
-0500<br> &gt;<br>
&gt;        Hash-cons all MEMBER types<br>
&gt;<br>
&gt;        Types whose members include arbitrary objects such as lists or strings \
will now<br> &gt;        choose and memoize per-element pseudorandom hashes as \
needed, while trying to<br> &gt;        avoid creating a forever-growing set of \
objects that acquired a stable hash,<br> &gt;        as the object-&gt;hash \
association is dropped when no XSET refers to an object.<br> &gt; ---<br>
&gt;   src/code/type-class.lisp               | 121 \
++++++++++++--------------------------<br> &gt;   src/code/type.lisp                  \
|   62 +++++++++++++++----<br> &gt;   src/code/xset.lisp                        | 106 \
+++++++++++++++++++++++----------<br> &gt;   src/compiler/ctype.lisp                 \
|     2 +-<br> &gt;   src/compiler/generic/genesis.lisp |   19 ++++--<br>
&gt;   tests/typehashmix.impure.lisp        |   30 ++++++++++<br>
&gt;   6 files changed, 207 insertions(+), 133 deletions(-)<br>
&gt;<br>
&gt; diff --git a/src/code/type-class.lisp b/src/code/type-class.lisp<br>
&gt; index e04e04c5e..f14dadab9 100644<br>
&gt; --- a/src/code/type-class.lisp<br>
&gt; +++ b/src/code/type-class.lisp<br>
&gt; @@ -734,9 +734,10 @@<br>
&gt;<br>
&gt;   ;; Singleton MEMBER types are best dealt with via a weak-value hash-table \
because:<br> &gt;   ;; * (MEMBER THING) might lack an address-insensitive hash for \
THING<br> &gt; -;;     but src/code/hashset can not use address-based hashes. This \
limitation is unique<br> &gt; -;;     to MEMBER types because other CTYPE instances \
are compositions of CTYPES<br> &gt; -;;     where all subparts have assigned \
hashes.<br> &gt; +;;     but src/code/hashset goes through a lot of rigmarole to \
handle address-bashed<br> &gt; +;;     hashing, and the end result for a single key \
would laboriously emulate an EQL table.<br> &gt; +;;     This is especially important \
for the compiler because each time it asks itself the<br> &gt; +;;     CTYPE-OF a \
constant leaf, the answer might be a singleton MEMBER type.<br> &gt;   ;; * Symbols \
have slightly bad SXHASH values (by language requirement):<br> &gt;   ;;      \
&quot;For any two objects, x and y which are symbols and which are similar<br> &gt;   \
;;      (sxhash x) and (sxhash y) yield the same mathematical value even if x and y \
exist<br> &gt; @@ -747,35 +748,6 @@<br>
&gt;   ;;     will cause the hashset to exceed its probe sequence length limit.<br>
&gt;   ;;     This isn&#39;t to say we couldn&#39;t assign some bits of SYMBOL-HASH \
pseudorandomly,<br> &gt;   ;;     and mask them out in the value returned by \
CL:SXHASH.<br> &gt; -;;<br>
&gt; -;; Also: XSETs containing arbitrary objects such as strings and lists don&#39;t \
have<br> &gt; -;; a good hash at all. There is not really a way to compute a mixed \
hash<br> &gt; -;; other than by pinning all objects in the XSET and taking their \
addresses.<br> &gt; -;; Then we&#39;d need to figure out that GC happened, and it \
becomes a pain.<br> &gt; -;; This seems more complicated than the situation \
warrants.<br> &gt; -;; So we&#39;ll just give up on hash-consing which should fix \
lp#1999687<br> &gt; -;;<br>
&gt; -;; An outline of a better design would be as follows:<br>
&gt; -;; - create a global hash-table of objects which were placed in an XSET<br>
&gt; -;;     other than the nicely hashable object types. Call this \
xset-key-&gt;hash-mapping.<br> &gt; -;;     This mapping does not need to be weak, \
because it will have a way of purging it.<br> &gt; -;;     Each value in the table is \
a cons of a random hash and the number of XSETs<br> &gt; -;;     using the key. \
Increment the refcount when making a new XSET with that key.<br> &gt; -;; - when \
hashing the XSET, look up its keys (other than EQL hashable) in the global table<br> \
&gt; -;; - attach a finalizer to the XSET. The finalizer&#39;s job is to decrement \
the refcount<br> &gt; -;;     on each key in the global mappping. When the count \
reaches zero there are no<br> &gt; -;;     XSETs that refer to the key, and the \
random hash can be removed.<br> &gt; -;; Why make a refcounted table? Because \
otherwise there is no way to remove mapping<br> &gt; -;; entries for objects that \
outlive the MEMBER-TYPE&#39;s use of the object but where the<br> &gt; -;; MEMBER \
type itself is dead. Worst-case, every object in the lisp image could at some<br> \
&gt; -;; point appear in a MEMBER type but then never be needed again with regard to \
type<br> &gt; -;; system operations. So you&#39;d have created a permanent mapping of \
every object to<br> &gt; -;; a random hash for no good reason.<br>
&gt; -<br>
&gt; -;; Why the singleton table is so important is that any time the compiler asks \
itself<br> &gt; -;; the ctype-of a constant leaf, it might yield `(MEMBER \
,the-constant).<br> &gt; -;; So then you end up with an assortment of random objects \
that don&#39;t hash<br> &gt; -;; nicely in a ctype hashset, but are OK in a \
hash-table.<br> &gt;   (define-load-time-global *eql-type-cache* ; like \
EQL-SPECIALIZER-TABLE in PCL<br> &gt;         (sb-impl::make-system-hash-table :test \
&#39;eql :weakness :value :synchronized nil))<br> &gt;<br>
&gt; @@ -784,9 +756,8 @@<br>
&gt;              (heap-allocated-p ,obj)))<br>
&gt;<br>
&gt;   (defvar *hashsets-preloaded* nil)<br>
&gt; -(defmacro new-ctype (pseudonym &amp;rest initargs)<br>
&gt; -   (let* ((name (if (eq pseudonym &#39;eql) &#39;member-type pseudonym))<br>
&gt; -              (allocator (package-symbolicate &quot;SB-KERNEL&quot; \
&quot;!ALLOC-&quot; name))<br> &gt; +(defmacro new-ctype (name &amp;rest \
initargs)<br> &gt; +   (let* ((allocator (package-symbolicate &quot;SB-KERNEL&quot; \
&quot;!ALLOC-&quot; name))<br> &gt;                 (hashset (package-symbolicate \
&quot;SB-KERNEL&quot; &quot;*&quot; name &quot;-HASHSET*&quot;))<br> &gt;             \
(bits (ctype-class-bits (ctype-instance-&gt;type-class name))))<br> &gt;         \
#+sb-xc-host ; allocate permanent data, and insert into cache if not found<br> &gt; \
@@ -803,32 +774,7 @@<br> &gt;              (unless *hashsets-preloaded*<br>
&gt;                 (write-string &quot;CTYPE hashset preload failure&quot;)<br>
&gt;                 (sb-vm:ldb-monitor))<br>
&gt; -           ,(case pseudonym<br>
&gt; -               (eql ; as per above remarks: use hash-table, not hashset<br>
&gt; -                  `(let* ((xset ,(first initargs))<br>
&gt; -                              (zeros ,(second initargs))<br>
&gt; -                              (key (first (or zeros (xset-data xset))))<br>
&gt; -                              (table *eql-type-cache*))<br>
&gt; -                       (with-system-mutex ((hash-table-lock table))<br>
&gt; -                        ;; This is like ENSURE-GETHASH but it potentially \
copies the key<br> &gt; -                        (or (gethash key table)<br>
&gt; -                              ;; hope no off-heap pointers buried within \
KEY<br> &gt; -                              (let ((key (cond ((numberp key) \
(sb-vm:copy-number-to-heap key))<br> &gt; -                                           \
((safe-member-type-elt-p key) key)<br> &gt; -                                         \
(t<br> &gt; -                                                         (warn \
&quot;Off-heap hash-table key @ ~X&quot;<br> &gt; -                                   \
(get-lisp-obj-address key))<br> &gt; -                                                \
key))))<br> &gt; -                                 (setf (gethash key table) \
(copy-ctype temp)))))))<br> &gt; -               (member-type ; problem case: \
don&#39;t always know how to hash well<br> &gt; -                 `(let ((xset \
,(first initargs)))<br> &gt; -                     (flet ((hashable (x) (typep x \
&#39;(or symbol number character instance))))<br> &gt; -                        (if \
(xset-every #&#39;hashable xset)<br> &gt; -                              \
(hashset-insert-if-absent ,hashset temp #&#39;copy-ctype)<br> &gt; -                  \
;; otherwise just copy it always (for now)<br> &gt; -                              \
(copy-ctype temp)))))<br> &gt; -               (t<br>
&gt; -                  `(hashset-insert-if-absent ,hashset temp \
#&#39;copy-ctype))))))<br> &gt; +           (hashset-insert-if-absent ,hashset temp \
#&#39;copy-ctype))))<br> &gt;<br>
&gt;   ;;; The NAMED-TYPE is used to represent *, T and NIL, the standard<br>
&gt;   ;;; special cases, as well as other special cases needed to<br>
&gt; @@ -878,6 +824,14 @@<br>
&gt;   (def-type-model (member-type (:constructor* nil (xset fp-zeroes)))<br>
&gt;      (xset nil :type xset :hasher xset-elts-hash :test xset=)<br>
&gt;      (fp-zeroes nil :type list :hasher hash-fp-zeros :test fp-zeros=)))<br>
&gt; +(define-load-time-global *xset-mutex* (or #-sb-xc-host \
(sb-thread:make-mutex)))<br> &gt; +;;; This hashset is guarded by *XSET-MUTEX*. It is \
_not_ declared as synchronized<br> &gt; +;;; so that HASHSET-INSERT-IF-ABSENT should \
not acquire a mutex inside a mutex<br> &gt; +;;; (stable hashes have to be assigned \
while holding the lock)<br> &gt; +(define-load-time-global \
*member/eq-type-hashset*<br> &gt; +      (make-hashset 32 #&#39;member-type-equiv \
#&#39;calc-member-type-hash<br> &gt; +                           :weakness t \
:synchronized nil))<br> &gt; +(pushnew &#39;*member/eq-type-hashset* \
*ctype-hashsets*)<br> &gt;<br>
&gt;   ;;; An ARRAY-TYPE is used to represent any array type, including<br>
&gt;   ;;; things such as SIMPLE-BASE-STRING.<br>
&gt; @@ -1248,6 +1202,8 @@<br>
&gt;<br>
&gt;   ;;; Return the name of the global hashset that OBJ (a CTYPE instance)<br>
&gt;   ;;; would be stored in, if it were stored in one.<br>
&gt; +;;; This is only for bootstrap, and not 100% precise as it does not know<br>
&gt; +;;; about the other MEMBER type containers.<br>
&gt;   (defun ctype-&gt;hashset-sym (obj)<br>
&gt;      (macrolet ((generate   ()<br>
&gt;                          (collect ((clauses))<br>
&gt; @@ -1331,25 +1287,21 @@<br>
&gt;   #-sb-xc-host<br>
&gt;   (progn<br>
&gt;   (defglobal *!initial-ctypes* nil)<br>
&gt; -(defun preload-ctype-hashsets (&amp;aux permtypes)<br>
&gt; -   (declare (ignorable permtypes))<br>
&gt; +(defun preload-ctype-hashsets ()<br>
&gt;      (dolist (pair (nreverse *!initial-ctypes*))<br>
&gt; -      (destructuring-bind (instance . hashset-symbol) pair<br>
&gt; -         (cond ((not hashset-symbol)<br>
&gt; -                    ;; There are very few which aren&#39;t in a hashset:<br>
&gt; -                    ;; - (6) NAMED-TYPEs<br>
&gt; -                    ;; - (1) MEMBER-TYPE NULL<br>
&gt; -                    ;; - (3) BASE-CHAR, EXTENDED-CHAR, CHARACTER<br>
&gt; -                    ;; - (1) CONS<br>
&gt; -                    (push instance permtypes))<br>
&gt; -                  ;; Mandatory special-case for singleton MEMBER types<br>
&gt; -                  ((and (member-type-p instance) (not (cdr (member-type-members \
instance))))<br> &gt; -                    (setf (gethash (car (member-type-members \
instance)) *eql-type-cache*)<br> &gt; -                             instance))<br>
&gt; +      (let ((instance (car pair))<br>
&gt; +               (container (symbol-value (cdr pair))))<br>
&gt; +         (cond ((hash-table-p container)<br>
&gt; +                    (aver (member-type-p instance))<br>
&gt; +                    ;; As of this writing there are only two EQL types to \
preload:<br> &gt; +                    ;; one is in the IR1-transform of FORMAT with \
stream (EQL T),<br> &gt; +                    ;; the other is CHECK-ARG-TYPE looking \
for (EQL DUMMY) type.<br> &gt; +                    (let ((key (first \
(member-type-members instance))))<br> &gt; +                       (aver (not \
(gethash key container)))<br> &gt; +                       (setf (gethash key \
container) instance)))<br> &gt;                     (t<br>
&gt; -                    (let ((hashset (symbol-value hashset-symbol)))<br>
&gt; -                       (aver (not (hashset-find hashset instance))) ; instances \
are dumped bottom-up<br> &gt; -                       (hashset-insert hashset \
instance))))<br> &gt; +                    (aver (not (hashset-find container \
instance))) ; instances are built bottom-up<br> &gt; +                    \
(hashset-insert container instance)))<br> &gt;            (labels \
((ensure-interned-list (list hashset)<br> &gt;                             (let \
((found (hashset-find hashset list)))<br> &gt;                                (when \
(and found (neq found list))<br> &gt; @@ -1367,7 +1319,7 @@<br>
&gt;                                      (bug &quot;genesis dumped bad instance \
within ~X&quot;<br> &gt;                                             \
(get-lisp-obj-address instance)))))))<br> &gt;               (etypecase instance<br>
&gt; -               ((or named-type numeric-type member-type character-set-type ; \
nothing extra to do<br> &gt; +               ((or numeric-type member-type \
character-set-type ; nothing extra to do<br> &gt;                    #+sb-simd-pack \
simd-pack-type #+sb-simd-pack-256 simd-pack-256-type<br> &gt;                    \
hairy-type))<br> &gt;                  (args-type<br>
&gt; @@ -1387,7 +1339,6 @@<br>
&gt;                    (ensure-interned-list (compound-type-types instance) \
*ctype-set-hashset*))<br> &gt;                  (negation-type<br>
&gt;                    (check (negation-type-type instance)))))))<br>
&gt; -   (aver (= (length permtypes) (+ 11 #-sb-unicode -2)))<br>
&gt;      #+sb-devel (setq *hashsets-preloaded* t))<br>
&gt;   (preload-ctype-hashsets))<br>
&gt;<br>
&gt; @@ -1530,10 +1481,16 @@<br>
&gt;<br>
&gt;   ;;; Copy X to the heap, give it a random hash, and if it is a MEMBER type<br>
&gt;   ;;; then assert that all members are cacheable.<br>
&gt; +#+sb-xc-host<br>
&gt; +(defun copy-ctype (x)<br>
&gt; +   (let ((bits (logior (logand (ctype-random) +type-hash-mask+) (type-%bits \
x))))<br> &gt; +      (etypecase x<br>
&gt; +         (member-type<br>
&gt; +           (!alloc-member-type bits (member-type-xset x) (member-type-fp-zeroes \
x))))))<br> &gt;   #-sb-xc-host<br>
&gt;   (defun copy-ctype (x)<br>
&gt;      (declare (type ctype x))<br>
&gt; -   (declare (sb-c::tlab :system) (inline !copy-xset))<br>
&gt; +   (declare (sb-c::tlab :system) (inline !new-xset))<br>
&gt;      #+c-stack-is-control-stack (aver (stack-allocated-p x))<br>
&gt;      (labels ((copy (x)<br>
&gt;                       ;; Return a heap copy of X if X was arena or \
stack-allocated.<br> &gt; @@ -1559,7 +1516,7 @@<br>
&gt;                                 ;; While we could use (load-time-value) to \
referece a constant empty xset<br> &gt;                                 ;; \
there&#39;s really no point to doing that.<br> &gt;                                 \
(collect ((elts))<br> &gt; -                                 (dolist (x data \
(!copy-xset (xset-list-size xset) (elts)))<br> &gt; +                                 \
(dolist (x data (!new-xset (elts) (xset-extra xset)))<br> &gt;                        \
(elts (cond ((numberp x) (sb-vm:copy-number-to-heap x))<br> &gt;                      \
((safe-member-type-elt-p x) x)<br> &gt;                                               \
;; surely things will go haywire if this occurs<br> &gt; diff --git \
a/src/code/type.lisp b/src/code/type.lisp<br> &gt; index 71b35509a..73c4da809 \
100644<br> &gt; --- a/src/code/type.lisp<br>
&gt; +++ b/src/code/type.lisp<br>
&gt; @@ -341,6 +341,9 @@<br>
&gt;                        unparsed<br>
&gt;                        (nconc unparsed &#39;(&amp;optional))))))<br>
&gt;<br>
&gt; +#+sb-xc-host ; why only on the host? Shouldn&#39;t we always declaim the \
ftype??<br> &gt; +(declaim (ftype (sfunction (ctype ctype) (values t t)) type=))<br>
&gt; +<br>
&gt;   ;;; Return true if LIST1 and LIST2 have the same elements in the same<br>
&gt;   ;;; positions according to TYPE=. We return NIL, NIL if there is an<br>
&gt;   ;;; uncertain comparison.<br>
&gt; @@ -3814,6 +3817,13 @@ used for a COMPLEX component.~:@&gt;&quot;<br>
&gt;   ;; Return possibly a union of a MEMBER type and a NUMERIC type,<br>
&gt;   ;; or just one or the other, or *EMPTY-TYPE* depending on what&#39;s in the \
XSET<br> &gt;   ;; and the FP-ZEROES. XSET must not contains characters or real \
numbers.<br> &gt; +;; MEMBER types go into one of three hash containers:<br>
&gt; +;;   - *EQL-TYPE-CACHE* holds singleton types. A weak hash-table suffices for \
this.<br> &gt; +;;   - *MEMBER-TYPE-HASHSET* holds types whose members are \
{NUMBER|CHARACTER|SYMBOL}.<br> &gt; +;;      Intrinsically each element has a stable \
hash, making it possible to<br> &gt; +;;      hash-cons XSETs without complications \
for EQ-comparable keys.<br> &gt; +;;   - *MEMBER/EQ-TYPE-HASHSET* is the general \
case, allowing a mixture of objects<br> &gt; +;;;     hashed by content-dependent \
hash and/or pseudorandom opaque hash.<br> &gt;   (defun make-member-type (xset \
fp-zeroes)<br> &gt;      ;; if we have a pair of zeros (e.g. 0.0d0 and -0.0d0), then \
we can<br> &gt;      ;; canonicalize to (DOUBLE-FLOAT 0.0d0 0.0d0), because \
numeric<br> &gt; @@ -3851,18 +3861,48 @@ used for a COMPLEX \
component.~:@&gt;&quot;<br> &gt;            ;; Bypass the hashset for type NULL \
because it&#39;s so important<br> &gt;            (return-from make-member-type<br>
&gt;               (inline-cache-ctype (!alloc-member-type (pack-interned-ctype-bits \
&#39;member)<br> &gt; -                                                               \
(xset-from-list &#39;(nil))<br> &gt; +                                                \
(!new-xset &#39;(nil) 1)<br> &gt;                                                     \
&#39;())<br> &gt;                                             null))))<br>
&gt; -      (let ((member-type<br>
&gt; -                 (case (+ (length unpaired) (xset-count xset))<br>
&gt; -                    (0 nil) ; nil<br>
&gt; -                    ;; It&#39;s slightly suboptimal to use two DX-lets, but to \
remedy that,<br> &gt; -                    ;; a single macro invocation would need to \
select which hash collection<br> &gt; -                    ;; to look in. More easy \
would be to paste the macro guts here<br> &gt; -                    ;; with suitable \
alteration, which I don&#39;t want to do.<br> &gt; -                    (1 (new-ctype \
eql xset unpaired)) ; most common case<br> &gt; -                    (t (new-ctype \
member-type xset unpaired)))))<br> &gt; +      (let* ((count (+ (length unpaired) \
(xset-count xset)))<br> &gt; +                 (member-type<br>
&gt; +                  (unless (= count 0)<br>
&gt; +                     (dx-let ((temp (!alloc-member-type (ctype-class-bits \
&#39;member)<br> &gt; +                                                               \
xset unpaired)))<br> &gt; +                        (cond<br>
&gt; +                           ((= count 1)<br>
&gt; +                             (let ((container *eql-type-cache*)<br>
&gt; +                                      (key (first (or unpaired (xset-data \
xset)))))<br> &gt; +                                (with-system-mutex \
((hash-table-lock container))<br> &gt; +                                   ;; This is \
like ENSURE-GETHASH but it potentially copies the key<br> &gt; +                      \
(or (gethash key container)<br> &gt; +                                         (let \
((copy (copy-ctype temp)))<br> &gt; +                                            ;; \
re-fetch KEY from XSET in case it was copied.<br> &gt; +                              \
;; hope no off-heap pointers buried within KEY.<br> &gt; +                            \
(setf (gethash (first (member-type-members copy)) container)<br> &gt; +               \
copy))))))<br> &gt; +                           ((xset-every (lambda (x) (typep x \
&#39;(or symbol number character))) xset)<br> &gt; +                             \
(hashset-insert-if-absent *member-type-hashset* temp #&#39;copy-ctype))<br> &gt; +    \
(t<br> &gt; +                             (binding*<br>
&gt; +                                   ((container *member/eq-type-hashset*)<br>
&gt; +                                    ((result foundp)<br>
&gt; +                                      (with-system-mutex (*xset-mutex*)<br>
&gt; +                                         (xset-generate-stable-hashes xset)<br>
&gt; +                                         (acond ((hashset-find container \
temp)<br> &gt; +                                                     \
(xset-delete-stable-hashes xset) ; inside the mutex scope<br> &gt; +                  \
(values it t))<br> &gt; +                                                   (t<br>
&gt; +                                                     (values (hashset-insert \
container (copy-ctype temp))<br> &gt; +                                               \
nil))))))<br> &gt; +                                (unless foundp ; &quot;use&quot; \
the var binding if #+sb-xc-host<br> &gt; +                                   \
#-sb-xc-host ; attach finalizer (outside the mutex scope)<br> &gt; +                  \
(let ((xset (member-type-xset result))) ; in case XSET was copied<br> &gt; +          \
(finalize<br> &gt; +                                       result (lambda ()<br>
&gt; +                                                     (with-system-mutex \
(*xset-mutex*)<br> &gt; +                                                        \
(xset-delete-stable-hashes xset))))))<br> &gt; +                                \
result)))))))<br> &gt;            ;; The actual member-type contains the XSET (with \
no FP zeroes),<br> &gt;            ;; and a list of unpaired zeroes.<br>
&gt;            (if (not float-types)<br>
&gt; @@ -4631,8 +4671,6 @@ used for a COMPLEX component.~:@&gt;&quot;<br>
&gt;<br>
&gt;   (define-type-class cons :enumerable nil :might-contain-other-types nil)<br>
&gt;<br>
&gt; -#+sb-xc-host<br>
&gt; -(declaim (ftype (sfunction (ctype ctype) (values t t)) type=))<br>
&gt;   (defun make-cons-type (car-type cdr-type)<br>
&gt;      (aver (not (or (eq car-type *wild-type*)<br>
&gt;                             (eq cdr-type *wild-type*))))<br>
&gt; diff --git a/src/code/xset.lisp b/src/code/xset.lisp<br>
&gt; index a6a86cb16..c8faa2bfb 100644<br>
&gt; --- a/src/code/xset.lisp<br>
&gt; +++ b/src/code/xset.lisp<br>
&gt; @@ -22,25 +22,31 @@<br>
&gt;   ;;;; requires a function as the first argument -- not a function<br>
&gt;   ;;;; designator.<br>
&gt;   ;;;;<br>
&gt; -;;;; XSET-LIST-SIZE is true only for XSETs whose data is stored into a<br>
&gt; -;;;; list -- XSET-COUNT returns the real value.<br>
&gt; -;;;;<br>
&gt;   ;;;; Note: XSET always uses EQL as the equivalence test<br>
&gt;<br>
&gt;   (in-package &quot;SB-KERNEL&quot;)<br>
&gt;<br>
&gt;   (defstruct (xset (:constructor alloc-xset)<br>
&gt; -                          (:constructor !copy-xset (list-size data))<br>
&gt; +                          (:constructor !new-xset (data extra))<br>
&gt;                             (:copier nil)<br>
&gt;                             (:predicate nil))<br>
&gt; -   (list-size 0 :type index)<br>
&gt; -   (data nil :type (or list hash-table)))<br>
&gt; +   (data nil :type (or list hash-table))<br>
&gt; +   ;; EXTRA is a dual-purpose slot: initially it holds the number of items<br>
&gt; +   ;; in LIST. If the list becomes a hash-table, then EXTRA becomes 0.<br>
&gt; +   ;; An XSET can be optionally have a vector of stable hashes, 1 per \
element.<br> &gt; +   ;; The hash vector if present goes in EXTRA, and the vector \
length<br> &gt; +   ;; is the same as the list length. After creating a hash vector, \
it is forbidden<br> &gt; +   ;; to add more elements to the set. In this manner we \
can avoid adding a subtype<br> &gt; +   ;; of XSET stably-hashed-xset, or wasting a \
slot that would almost never be used.<br> &gt; +   ;; (99.999% of all XSETs do not \
need stable hashes)<br> &gt; +   (extra 0 :type (or simple-vector index)))<br>
&gt;   (declaim (freeze-type xset))<br>
&gt;<br>
&gt;   (defun xset-count (xset)<br>
&gt;      (let ((data (xset-data xset)))<br>
&gt;         (if (listp data)<br>
&gt; -            (xset-list-size xset)<br>
&gt; +            (let ((extra (xset-extra xset)))<br>
&gt; +               (if (fixnump extra) extra (length extra)))<br>
&gt;               (hash-table-count data))))<br>
&gt;<br>
&gt;   (defun map-xset (function xset)<br>
&gt; @@ -60,28 +66,21 @@<br>
&gt;<br>
&gt;   ;;; Checks that the element is not in the set yet.<br>
&gt;   (defun add-to-xset (elt xset)<br>
&gt; -   (let ((data (xset-data xset))<br>
&gt; -            (size (xset-list-size xset)))<br>
&gt; +   (let ((data (xset-data xset)))<br>
&gt;         (if (listp data)<br>
&gt; -            (if (&lt; size +xset-list-size-limit+)<br>
&gt; -                  (unless (member elt data :test #&#39;eql)<br>
&gt; -                     (setf (xset-list-size xset) (1+ size)<br>
&gt; -                              (xset-data xset) (cons elt data)))<br>
&gt; -                  (let ((table (make-hash-table :size (* 2 size) :test \
#&#39;eql)))<br> &gt; -                     (setf (gethash elt table) t)<br>
&gt; -                     (dolist (x data)<br>
&gt; -                        (setf (gethash x table) t))<br>
&gt; -                     (setf (xset-data xset) table)))<br>
&gt; +            (let ((size (xset-extra xset)))<br>
&gt; +               (if (&lt; size +xset-list-size-limit+)<br>
&gt; +                     (unless (member elt data :test #&#39;eql)<br>
&gt; +                        (setf (xset-extra xset) (1+ size)<br>
&gt; +                                 (xset-data xset) (cons elt data)))<br>
&gt; +                     (let ((table (make-hash-table :size (* 2 size) :test \
#&#39;eql)))<br> &gt; +                        (setf (gethash elt table) t)<br>
&gt; +                        (dolist (x data)<br>
&gt; +                           (setf (gethash x table) t))<br>
&gt; +                        (setf (xset-extra xset) 0 ; looks nice to clear it<br>
&gt; +                                 (xset-data xset) table))))<br>
&gt;               (setf (gethash elt data) t))))<br>
&gt;<br>
&gt; -;; items must be canonical - no duplicates - and few in number.<br>
&gt; -(defun xset-from-list (items)<br>
&gt; -   (let ((n (length items)))<br>
&gt; -      (aver (&lt;= n +xset-list-size-limit+))<br>
&gt; -      (let ((xset (alloc-xset)))<br>
&gt; -         (setf (xset-list-size xset) n (xset-data xset) items)<br>
&gt; -         xset)))<br>
&gt; -<br>
&gt;   (defun xset-union (a b)<br>
&gt;      (let ((xset (alloc-xset)))<br>
&gt;         (map-xset (lambda (x)<br>
&gt; @@ -186,12 +185,16 @@<br>
&gt;   (defun xset-elts-hash (xset)<br>
&gt;      (let ((h 0))<br>
&gt;         (declare (sb-xc:fixnum h))<br>
&gt; -      (map-xset (lambda (x)<br>
&gt; -                        ;; Rather than masking each intermediate result to \
MOST-POSITIVE-FIXNUM,<br> &gt; -                        ;; allow bits to rollover \
into the sign bit<br> &gt; -                        (when (typep x &#39;(or symbol \
number character #-sb-xc-host instance))<br> &gt; -                           (setq h \
(plus-mod-fixnum (sb-xc:sxhash x) h))))<br> &gt; -                     xset)<br>
&gt; +      ;; Rather than masking each intermediate result to \
MOST-POSITIVE-FIXNUM,<br> &gt; +      ;; allow bits to rollover into the sign bit<br>
&gt; +      (let ((hashes (xset-extra xset)))<br>
&gt; +         (if (simple-vector-p hashes)<br>
&gt; +               (dovector (x hashes)<br>
&gt; +                  (setq h (plus-mod-fixnum h (truly-the fixnum (if (listp x) \
(cdr x) x)))))<br> &gt; +               (map-xset (lambda (x)<br>
&gt; +                                 (when (typep x &#39;(or symbol number \
character))<br> &gt; +                                    (setq h (plus-mod-fixnum \
(sb-xc:sxhash x) h))))<br> &gt; +                              xset)))<br>
&gt;         ;; Now mix the bits thoroughly and then mask to a positive fixnum.<br>
&gt;         ;; I think this does not need to be compatible between host and \
target.<br> &gt;         ;; But I&#39;m trying to make it compatible anyway because \
I&#39;m not 100% sure<br> &gt; @@ -203,3 +206,42 @@<br>
&gt;                    #+sb-xc-host (ldb (byte sb-vm:n-word-bits 0) (ash h \
sb-vm:n-fixnum-tag-bits))<br> &gt;                    #-sb-xc-host \
(get-lisp-obj-address h)))<br> &gt;            (logand (sb-impl::murmur3-fmix-word \
word-bits) most-positive-fixnum))))<br> &gt; +<br>
&gt; +;;; Stably-hashed XSETs that have elements which are not nicely \
EQL-hashable<br> &gt; +;;; rely on a global table that maps any object to a \
pseudorandom hash.<br> &gt; +;;; The table keys are refcounted so that they can be \
deleted when no XSET<br> &gt; +;;; references a particular key. Caller MUST provide \
synchronization.<br> &gt; +(define-load-time-global *xset-stable-hashes* \
(make-hash-table :test &#39;eq))<br> &gt; +<br>
&gt; +(defun xset-generate-stable-hashes (xset &amp;aux (hashmap \
*xset-stable-hashes*))<br> &gt; +   #-sb-xc-host (declare (notinline \
sb-impl::eql-hash)) ; forward ref<br> &gt; +   (flet ((get-stable-hash-cell (obj)<br>
&gt; +                 (let ((cell (gethash obj hashmap)))<br>
&gt; +                    (cond (cell<br>
&gt; +                              (incf (car cell))<br>
&gt; +                              cell)<br>
&gt; +                             (t<br>
&gt; +                              (setf (gethash obj hashmap) (cons 1 \
(ctype-random))))))))<br> &gt; +      (let ((hashes (make-array (xset-count \
xset)))<br> &gt; +               (i 0))<br>
&gt; +         (map-xset (lambda (elt)<br>
&gt; +                           (multiple-value-bind (hashval eq?)<br>
&gt; +                                 #+sb-xc-host (if (sb-xc:typep elt &#39;(or \
symbol character number))<br> &gt; +                                                  \
(values (sb-xc:sxhash elt) nil)<br> &gt; +                                            \
(values 4 ; chosen by algorithm of <a href="https://xkcd.com/221/" rel="noreferrer" \
target="_blank">https://xkcd.com/221/</a><br> &gt; +                                  \
t)) ; yes, it&#39;s address-based<br> &gt; +                                 \
#-sb-xc-host (sb-impl::eql-hash elt)<br> &gt; +                              (setf \
(aref hashes i) (if eq? (get-stable-hash-cell elt) hashval))<br> &gt; +               \
(incf i)))<br> &gt; +                        xset)<br>
&gt; +         (setf (xset-extra xset) hashes)))<br>
&gt; +   xset)<br>
&gt; +(defun xset-delete-stable-hashes (xset &amp;aux (hashmap \
*xset-stable-hashes*))<br> &gt; +   (let ((hashes (the simple-vector (xset-extra \
xset)))<br> &gt; +            (index -1))<br>
&gt; +      ;; Iteration order will be the same as it was in \
GENERATE-STABLE-HASHES<br> &gt; +      (map-xset (lambda (elt &amp;aux (cell (aref \
hashes (incf index))))<br> &gt; +                        (when (and (listp cell)<br>
&gt; +                                         (zerop (decf (car cell)))) ; element \
is not used in any XSET<br> &gt; +                           (remhash elt \
hashmap)))<br> &gt; +                     xset)))<br>
&gt; diff --git a/src/compiler/ctype.lisp b/src/compiler/ctype.lisp<br>
&gt; index 18deeec53..7b846ee2e 100644<br>
&gt; --- a/src/compiler/ctype.lisp<br>
&gt; +++ b/src/compiler/ctype.lisp<br>
&gt; @@ -242,7 +242,7 @@ and no value was provided for it.&quot; name))))))))))<br>
&gt;           (let* ((ctype (lvar-type lvar))<br>
&gt;                     (int (funcall *ctype-test-fun* ctype type)))<br>
&gt;              (cond ((not int)<br>
&gt; -                     (unless (type= ctype (specifier-type &#39;(member \
dummy)))<br> &gt; +                     (unless (type= ctype (specifier-type \
&#39;(eql dummy)))<br> &gt;                           (note-lossage &quot;The ~:R \
argument is a ~S, not a ~S.&quot;<br> &gt;                                            \
n (type-specifier ctype) (type-specifier type)))<br> &gt;                        \
nil)<br> &gt; diff --git a/src/compiler/generic/genesis.lisp \
b/src/compiler/generic/genesis.lisp<br> &gt; index 18b0a5aad..dd83acc31 100644<br>
&gt; --- a/src/compiler/generic/genesis.lisp<br>
&gt; +++ b/src/compiler/generic/genesis.lisp<br>
&gt; @@ -1084,6 +1084,8 @@ core and return a descriptor to it.&quot;<br>
&gt;         (loop (if (cold-null list) (return n))<br>
&gt;                  (incf n)<br>
&gt;                  (setq list (cold-cdr list)))))<br>
&gt; +(defun cold-push (item symbol)<br>
&gt; +   (cold-set symbol (cold-cons item (cold-symbol-value symbol))))<br>
&gt;<br>
&gt;   ;;; Make a simple-vector on the target that holds the specified<br>
&gt;   ;;; OBJECTS, and return its descriptor.<br>
&gt; @@ -1489,13 +1491,18 @@ core and return a descriptor to it.&quot;<br>
&gt;                        (let ((cell (cold-find-classoid-cell (classoid-name obj) \
:create t)))<br> &gt;                           (write-slots cell :classoid \
result)))<br> &gt;                       ((ctype-p obj)<br>
&gt; +                     ;; If OBJ belongs in a hash container, then deduce \
which<br> &gt;                        (let* ((hashset \
(sb-kernel::ctype-&gt;hashset-sym obj))<br> &gt; -                                \
(entry-p (and hashset (hashset-find (symbol-value hashset) obj))))<br> &gt; -         \
;; Record for preloading in hashset<br> &gt; -                        (cold-set \
&#39;sb-kernel::*!initial-ctypes*<br> &gt; -                          (cold-cons \
(cold-cons result (if entry-p (cold-intern hashset)<br> &gt; -                        \
*nil-descriptor*))<br> &gt; -                                          \
(cold-symbol-value &#39;sb-kernel::*!initial-ctypes*))))))<br> &gt; +                 \
(preload<br> &gt; +                                 (cond ((and hashset (hashset-find \
(symbol-value hashset) obj))<br> &gt; +                                            \
hashset)<br> &gt; +                                          ((and (member-type-p \
obj)<br> &gt; +                                                   ;; NULL is a \
hardwired case in the MEMBER type constructor<br> &gt; +                              \
(neq obj (specifier-type &#39;null))<br> &gt; +                                       \
(type-singleton-p obj))<br> &gt; +                                            \
&#39;sb-kernel::*eql-type-cache*))))<br> &gt; +                        (when preload \
; Record it<br> &gt; +                           (cold-push (cold-cons result \
preload) &#39;sb-kernel::*!initial-ctypes*)))))<br> &gt;              result))))<br>
&gt;<br>
&gt;   ;;; Convert a layout to a wrapper and back.<br>
&gt; diff --git a/tests/typehashmix.impure.lisp b/tests/typehashmix.impure.lisp<br>
&gt; index 70290c9ea..0aac4027c 100644<br>
&gt; --- a/tests/typehashmix.impure.lisp<br>
&gt; +++ b/tests/typehashmix.impure.lisp<br>
&gt; @@ -370,3 +370,33 @@<br>
&gt;                  collect (list i tp))<br>
&gt;         (let ((post (compute-max-psl hs)))<br>
&gt;            (assert (&lt;= (- post pre) 2)))))<br>
&gt; +<br>
&gt; +(defvar a &quot;foo&quot;)<br>
&gt; +(defvar b &#39;(nil t))<br>
&gt; +(defvar c #*101)<br>
&gt; +(with-test (:name :hash-cons-member-type)<br>
&gt; +   (assert (eq (sb-kernel:specifier-type `(member ,a ,b ,c))<br>
&gt; +                     (sb-kernel:specifier-type `(member ,c ,a ,b))))<br>
&gt; +   (assert (eq (sb-kernel:specifier-type `(member ,a ,b ,c))<br>
&gt; +                     (sb-kernel:specifier-type `(member ,b ,c ,a)))))<br>
&gt; +<br>
&gt; +(with-test (:name :hash-cons-member-type-large)<br>
&gt; +   (let ((numbers ; force the XSET to be represented as a hash-table<br>
&gt; +              (loop for i below 30 collect (complex (coerce i \
&#39;single-float) i)))<br> &gt; +            (list &#39;(thing)))<br>
&gt; +      (assert (hash-table-p<br>
&gt; +                    (sb-kernel::xset-data<br>
&gt; +                     (sb-kernel::member-type-xset<br>
&gt; +                       (sb-kernel:specifier-type `(member ,@numbers))))))<br>
&gt; +      (assert (eq (sb-kernel:specifier-type `(member ,a ,@numbers ,list))<br>
&gt; +                        (sb-kernel:specifier-type `(member ,list ,a \
,@numbers))))))<br> &gt; +<br>
&gt; +;(print sb-kernel::*xset-stable-hashes*)<br>
&gt; +(gc :full t)<br>
&gt; +#+sb-thread (sb-kernel:run-pending-finalizers)<br>
&gt; +(with-test (:name :xset-stable-hash-weakness)<br>
&gt; +   ;; After running the :MEMBER-TYPE-HASH-MIXER test, there were &gt;5000 \
entries<br> &gt; +   ;; in the *XSET-STABLE-HASHES* table for me.<br>
&gt; +   ;; The preceding GC should have had some effect.<br>
&gt; +   (assert (&lt; (hash-table-count sb-kernel::*xset-stable-hashes*)<br>
&gt; +                    100)))<br>
&gt;<br>
&gt; -----------------------------------------------------------------------<br>
&gt;<br>
&gt;<br>
&gt; hooks/post-receive<br>
&gt; --<br>
&gt; SBCL<br>
&gt;<br>
&gt;<br>
&gt; _______________________________________________<br>
&gt; Sbcl-commits mailing list<br>
&gt; <a href="mailto:Sbcl-commits@lists.sourceforge.net" \
target="_blank">Sbcl-commits@lists.sourceforge.net</a><br> &gt; <a \
href="https://lists.sourceforge.net/lists/listinfo/sbcl-commits" rel="noreferrer" \
target="_blank">https://lists.sourceforge.net/lists/listinfo/sbcl-commits</a><br> \
</blockquote></div></div>





_______________________________________________
Sbcl-devel mailing list
Sbcl-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sbcl-devel


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic