+ (let ((ctor (%make-ctor function-name class-name nil initargs safe-p)))
+ (install-initial-constructor ctor :force-p t)
+ (push ctor *all-ctors*)
+ (setf (fdefinition function-name) ctor)
+ ctor)))
+\f
+;;; *****************
+;;; Inline CTOR cache
+;;; *****************
+;;;
+;;; The cache starts out as a list of CTORs, sorted with the most recently
+;;; used CTORs near the head. If it expands too much, we switch to a vector
+;;; with a simple hashing scheme.
+
+;;; Find CTOR for KEY (which is a class or class name) in a list. If the CTOR
+;;; is in the list but not one of the 4 first ones, return a new list with the
+;;; found CTOR at the head. Thread-safe: the new list shares structure with
+;;; the old, but is not desctructively modified. Returning the old list for
+;;; hits close to the head reduces ping-ponging with multiple threads seeking
+;;; the same list.
+(defun find-ctor (key list)
+ (labels ((walk (tail from-head depth)
+ (declare (fixnum depth))
+ (if tail
+ (let ((ctor (car tail)))
+ (if (eq (ctor-class-or-name ctor) key)
+ (if (> depth 3)
+ (values ctor
+ (nconc (list ctor) (nreverse from-head) (cdr tail)))
+ (values ctor
+ list))
+ (walk (cdr tail)
+ (cons ctor from-head)
+ (logand #xf (1+ depth)))))
+ (values nil list))))
+ (walk list nil 0)))
+
+(declaim (inline sxhash-symbol-or-class))
+(defun sxhash-symbol-or-class (x)
+ (cond ((symbolp x) (sxhash x))
+ ((std-instance-p x) (std-instance-hash x))
+ ((fsc-instance-p x) (fsc-instance-hash x))
+ (t
+ (bug "Something strange where symbol or class expected."))))
+
+;;; Max number of CTORs kept in an inline list cache. Once this is
+;;; exceeded we switch to a table.
+(defconstant +ctor-list-max-size+ 12)
+;;; Max table size for CTOR cache. If the table fills up at this size
+;;; we keep the same size and drop 50% of the old entries.
+(defconstant +ctor-table-max-size+ (expt 2 8))
+;;; Even if there is space in the cache, if we cannot fit a new entry
+;;; with max this number of collisions we expand the table (if possible)
+;;; and rehash.
+(defconstant +ctor-table-max-probe-depth+ 5)
+
+(defun make-ctor-table (size)
+ (declare (index size))
+ (let ((real-size (power-of-two-ceiling size)))
+ (if (< real-size +ctor-table-max-size+)
+ (values (make-array real-size :initial-element nil) nil)
+ (values (make-array +ctor-table-max-size+ :initial-element nil) t))))
+
+(declaim (inline mix-ctor-hash))
+(defun mix-ctor-hash (hash base)
+ (logand most-positive-fixnum (+ hash base 1)))
+
+(defun put-ctor (ctor table)
+ (cond ((try-put-ctor ctor table)
+ (values ctor table))
+ (t
+ (expand-ctor-table ctor table))))
+
+;;; Thread-safe: if two threads write to the same index in parallel, the other
+;;; result is just lost. This is not an issue as the CTORs are used as their
+;;; own keys. If both were EQ, we're good. If non-EQ, the next time the other
+;;; one is needed we just cache it again -- hopefully not getting stomped on
+;;; that time.
+(defun try-put-ctor (ctor table)
+ (declare (simple-vector table) (optimize speed))
+ (let* ((class (ctor-class-or-name ctor))
+ (base (sxhash-symbol-or-class class))
+ (hash base)
+ (mask (1- (length table))))
+ (declare (fixnum base hash mask))
+ (loop repeat +ctor-table-max-probe-depth+
+ do (let* ((index (logand mask hash))
+ (old (aref table index)))
+ (cond ((and old (neq class (ctor-class-or-name old)))
+ (setf hash (mix-ctor-hash hash base)))
+ (t
+ (setf (aref table index) ctor)
+ (return-from try-put-ctor t)))))
+ ;; Didn't fit, must expand
+ nil))
+
+(defun get-ctor (class table)
+ (declare (simple-vector table) (optimize speed))
+ (let* ((base (sxhash-symbol-or-class class))
+ (hash base)
+ (mask (1- (length table))))
+ (declare (fixnum base hash mask))
+ (loop repeat +ctor-table-max-probe-depth+
+ do (let* ((index (logand mask hash))
+ (old (aref table index)))
+ (if (and old (eq class (ctor-class-or-name old)))
+ (return-from get-ctor old)
+ (setf hash (mix-ctor-hash hash base)))))
+ ;; Nothing.
+ nil))
+
+;;; Thread safe: the old table is read, but if another thread mutates
+;;; it while we're reading we still get a sane result -- either the old
+;;; or the new entry. The new table is locally allocated, so that's ok
+;;; too.
+(defun expand-ctor-table (ctor old)
+ (declare (simple-vector old))
+ (let* ((old-size (length old))
+ (new-size (* 2 old-size))
+ (drop-random-entries nil))
+ (tagbody
+ :again
+ (multiple-value-bind (new max-size-p) (make-ctor-table new-size)
+ (let ((action (if drop-random-entries
+ ;; Same logic as in method caches -- see comment
+ ;; there.
+ (randomly-punting-lambda (old-ctor)
+ (try-put-ctor old-ctor new))
+ (lambda (old-ctor)
+ (unless (try-put-ctor old-ctor new)
+ (if max-size-p
+ (setf drop-random-entries t)
+ (setf new-size (* 2 new-size)))
+ (go :again))))))
+ (aver (try-put-ctor ctor new))
+ (dotimes (i old-size)
+ (let ((old-ctor (aref old i)))
+ (when old-ctor
+ (funcall action old-ctor))))
+ (return-from expand-ctor-table (values ctor new)))))))
+
+(defun ctor-list-to-table (list)
+ (let ((table (make-ctor-table (length list))))
+ (dolist (ctor list)
+ (setf table (nth-value 1 (put-ctor ctor table))))
+ table))
+
+(defun ensure-cached-ctor (class-name store initargs safe-code-p)
+ (flet ((maybe-ctor-for-caching ()
+ (if (typep class-name '(or symbol class))
+ (let ((name (make-ctor-function-name class-name initargs safe-code-p)))
+ (ensure-ctor name class-name initargs safe-code-p))
+ ;; Invalid first argument: let MAKE-INSTANCE worry about it.
+ (return-from ensure-cached-ctor
+ (values (lambda (&rest ctor-parameters)
+ (let (mi-initargs)
+ (doplist (key value) initargs
+ (push key mi-initargs)
+ (push (if (constantp value)
+ value
+ (pop ctor-parameters))
+ mi-initargs))
+ (apply #'make-instance class-name (nreverse mi-initargs))))
+ store)))))
+ (if (listp store)
+ (multiple-value-bind (ctor list) (find-ctor class-name store)
+ (if ctor
+ (values ctor list)
+ (let ((ctor (maybe-ctor-for-caching)))
+ (if (< (length list) +ctor-list-max-size+)
+ (values ctor (cons ctor list))
+ (values ctor (ctor-list-to-table list))))))
+ (let ((ctor (get-ctor class-name store)))
+ (if ctor
+ (values ctor store)
+ (put-ctor (maybe-ctor-for-caching) store))))))