changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/obj/hash/castable.lisp

changeset 670: 6856c021d084
parent: 9fa3b9154bb2
author: Richard Westhaver <ellis@rwest.io>
date: Mon, 23 Sep 2024 21:14:10 -0400
permissions: -rw-r--r--
description: add dir-locals to skel, fix package lock violation in castable, move .sk files
1 ;;; lib/obj/hash/castable.lisp --- CAS Table
2 
3 ;; This implementation was written by Shinmera:
4 ;; https://github.com/Shinmera/luckless/blob/master/cat.lisp
5 
6 ;; It is based on the JVM implementation of some concurrent data
7 ;; structures.
8 
9 ;;; Code:
10 (in-package :obj/hash)
11 
12 (eval-always
13  (defstruct (prime (:constructor prime (value)))
14  (value nil :type t))
15 
16  (defmethod make-load-form ((self prime) &optional environment)
17  (declare (ignore environment))
18  `(prime 'prime)))
19 
20 (defconstant max-spin 2)
21 (defconstant reprobe-limit 10)
22 (defconstant min-size-log 3)
23 (defconstant min-size (ash 1 min-size-log))
24 (defconstant no-match-old 'no-match-old)
25 (defconstant match-any 'match-any)
26 (defconstant tombstone 'tombstone)
27 (defconstant tombprime (if (boundp 'tombprime) tombprime (prime tombstone)))
28 (defconstant no-value 'no-value)
29 
30 ;;; Cat
31 (defstruct (cat
32  (:constructor %make-cat (next table))
33  (:conc-name %cat-))
34  (resizers 0 :type fixnum)
35  (next nil :type t)
36  (sum-cache most-negative-fixnum :type fixnum)
37  (fuzzy-sum-cache 0 :type fixnum)
38  (fuzzy-time 0 :type fixnum)
39  (table nil :type simple-vector))
40 
41 (defun make-cat (next size initial-element)
42  (declare (type fixnum initial-element))
43  (declare (type fixnum size))
44  (let ((table (make-array size :initial-element 0)))
45  (setf (aref table 0) initial-element)
46  (%make-cat next table)))
47 
48 (declaim (ftype (function (cat fixnum) fixnum) cat-sum))
49 ;; L199 long sum(long)
50 (defun cat-sum (cat mask)
51  (declare (type fixnum mask))
52  (declare (optimize speed))
53  (let ((sum (%cat-sum-cache cat)))
54  (cond ((/= most-negative-fixnum sum)
55  sum)
56  (T
57  (setf sum (if (null (%cat-next cat))
58  0
59  (cat-sum (%cat-next cat) mask)))
60  (let ((%t (%cat-table cat)))
61  (dotimes (i (length %t))
62  (incf sum (logand (svref %t i) (lognot mask))))
63  (setf (%cat-sum-cache cat) sum)
64  sum)))))
65 
66 (declaim (ftype (function (cat fixnum) fixnum) cat-sum~))
67 ;; L212 long estimate_sum(long)
68 (defun cat-sum~ (cat mask)
69  (declare (type fixnum mask))
70  (declare (optimize speed))
71  (cond ((<= (length (%cat-table cat)) 64)
72  (cat-sum cat mask))
73  (T
74  (let ((millis (get-internal-real-time)))
75  (when (/= millis (%cat-fuzzy-time cat))
76  (setf (%cat-fuzzy-sum-cache cat) (cat-sum cat mask))
77  (setf (%cat-fuzzy-time cat) millis))
78  (%cat-fuzzy-sum-cache cat)))))
79 
80 ;;; Counter
81 (defstruct (counter (:constructor make-counter ())
82  (:conc-name %counter-))
83  ;; Why is this slot at L97, after all the methods? I almost missed it, reading
84  ;; the source code.
85  (cat (make-cat NIL 4 0) :type cat))
86 
87 (declaim (inline decf-counter))
88 ;; L41 decrement(), but with a delta argument.
89 (defun decf-counter (counter &optional (delta 1))
90  (declare (type fixnum delta))
91  (counter-add-if-mask counter (- delta) 0))
92 
93 (declaim (inline incf-counter))
94 ;; L43 increment(), but with a delta increment.
95 (defun incf-counter (counter &optional (delta 1))
96  (declare (type fixnum delta))
97  (counter-add-if-mask counter delta 0))
98 
99 ;; L48 set(long)
100 (defun (setf counter-value) (x counter)
101  (declare (optimize speed))
102  (declare (type fixnum x))
103  (loop with new = (make-cat NIL 4 x)
104  until (sb-ext:cas (%counter-cat counter) (%counter-cat counter) new)))
105 
106 (declaim (inline counter-value))
107 ;; L59 get()
108 (defun counter-value (counter)
109  (cat-sum (%counter-cat counter) 0))
110 
111 (declaim (inline counter-value~))
112 ;; L69 estimate_get()
113 (defun counter-value~ (counter)
114  (cat-sum~ (%counter-cat counter) 0))
115 
116 ;; L150 add_if_mask(long, long, int, ConcurrentAutoTable)
117 ;; This is a method in the CAT in the Java implementation, but here the
118 ;; ConcurrentAutoTable (counter) is the object being acted upon.
119 (defun counter-add-if-mask (counter x mask)
120  (declare (type fixnum x mask))
121  (declare (optimize speed))
122  (let* ((cat (%counter-cat counter))
123  (%t (%cat-table cat))
124  (idx (logand *global-hash* (1- (length %t))))
125  (old (the fixnum (svref %t idx)))
126  ;; Try once quickly
127  (ok (sb-ext:cas (svref %t idx) (logand old (lognot mask)) (+ old x))))
128  (flet ((fail () (return-from counter-add-if-mask old)))
129  ;; Clear the cache
130  (when (/= (%cat-sum-cache cat) most-negative-fixnum)
131  (setf (%cat-sum-cache cat) most-negative-fixnum))
132  (when ok (fail))
133  (when (/= 0 (logand old mask)) (fail))
134  ;; Try some more
135  (let ((cnt 0))
136  (declare (type fixnum cnt))
137  (loop (setf old (the fixnum (svref %t idx)))
138  (when (/= 0 (logand old mask)) (fail))
139  (when (sb-ext:cas (svref %t idx) old (+ old x)) (return))
140  (incf cnt))
141  ;; Make sure we don't spin too long
142  (when (< cnt MAX-SPIN) (fail))
143  ;; Or grow too big
144  (when (<= (* 1024 1024) (length %t)) (fail))
145  ;; We are contending too much, increase the size in hopes it'll help
146  (let ((r (%cat-resizers cat))
147  (newbytes (ash (ash (length %t) 1) 4)))
148  (declare (type fixnum r newbytes))
149  (loop while (not (sb-ext:cas (%cat-resizers cat) r (+ r newbytes)))
150  do (setf r (%cat-resizers cat)))
151  (incf r newbytes)
152  ;; Already doubled up, don't bother
153  (unless (eql cat (%counter-cat counter))
154  (fail))
155  ;; Did we try to allocate too often already?
156  (when (/= 0 (ash r -17))
157  (sleep (/ (ash r -17) 1000))
158  (unless (eql cat (%counter-cat counter))
159  (fail)))
160  ;; Try to extend the CAT once, if it fails another thread
161  ;; already did it for us so we don't have to retry.
162  (let ((new (make-cat cat (* (length %t) 2) 0)))
163  (sb-ext:cas (%counter-cat counter) cat new)
164  (fail)))))))
165 
166 ;;; CAS Table
167 (declaim (ftype (function (unsigned-byte) fixnum) rehash)
168  (inline rehash))
169 
170 (defun rehash (h)
171  "Spread bits of the hash H around."
172  (declare (optimize speed))
173  (declare (type (integer 0) h))
174  (let ((h (logand h most-positive-fixnum)))
175  (declare (type (unsigned-byte 64) h))
176  (incf h (logior (logand most-positive-fixnum (ash h 15)) #xffffcd7d))
177  (setf h (logior h (ash h -10)))
178  (incf h (logand most-positive-fixnum (ash h 3)))
179  (setf h (logior h (ash h -6)))
180  (incf h (logand most-positive-fixnum (+ (ash h 2) (ash h 14))))
181  (setf h (logior h (ash h -16)))
182  (logand h most-positive-fixnum)))
183 
184 (declaim (inline reprobe-limit))
185 (defun reprobe-limit (len)
186  (+ reprobe-limit (ash len -2)))
187 
188 ;; L713, private class CHM
189 ;; "The control structure for the NonBlockingHashMap"
190 (defstruct (chm
191  (:constructor make-chm (size))
192  (:conc-name %chm-))
193  (size (error "no size?") :type counter)
194  (slots (make-counter) :type counter)
195  (newkvs NIL :type (or null simple-vector))
196  (resizers 0 :type fixnum)
197  (copy-idx 0 :type fixnum)
198  (copy-done 0 :type fixnum))
199 (declaim (ftype (function (chm) fixnum)
200  %chm-resizers %chm-copy-idx %chm-copy-done))
201 
202 ;; L716, int size()
203 (declaim (inline chm-size))
204 (defun chm-size (chm)
205  (counter-value (%chm-size chm)))
206 
207 ;; L729, int slots()
208 (declaim (inline chm-slots))
209 (defun chm-slots (chm)
210  (counter-value (%chm-slots chm)))
211 
212 (declaim (inline cas-newkvs))
213 ;; L742, boolean CAS_newkvs(Object[])
214 (defun cas-newkvs (chm newkvs)
215  (loop while (null (%chm-newkvs chm))
216  do (when (sb-ext:cas (%chm-newkvs chm) NIL newkvs)
217  (return T))
218  finally (return NIL)))
219 
220 ;; Heuristic to test if the table is too full and we should make a new one.
221 (declaim (inline table-full-p))
222 ;; L780, boolean tableFull(int, int)
223 (defun table-full-p (chm reprobe-cnt len)
224  (and (<= REPROBE-LIMIT reprobe-cnt)
225  (<= (reprobe-limit len) (counter-value~ (%chm-slots chm)))))
226 
227 (defstruct (castable
228  (:constructor %make-castable (kvs last-resize test hasher))
229  (:conc-name %castable-))
230  (kvs (error "no KVS?") :type simple-vector)
231  (last-resize (error "no LAST-RESIZE?") :type fixnum)
232  (reprobes (make-counter) :type counter)
233  (test (error "no TEST?") :type (function (T T) boolean))
234  (hasher (error "no HASHER?") :type (function (T) fixnum)))
235 
236 (declaim (inline chm))
237 (declaim (ftype (function (simple-vector) chm) chm))
238 ;; L138, static CHM chm(Object[])
239 (defun chm (kvs)
240  (svref kvs 0))
241 
242 (declaim (inline hashes))
243 (declaim (ftype (function (simple-vector) (simple-array fixnum)) hashes))
244 ;; L139, static int[] hashes(Object[])
245 (defun hashes (kvs)
246  (svref kvs 1))
247 
248 (declaim (inline len))
249 (declaim (ftype (function (simple-vector) (unsigned-byte 32)) len))
250 ;; L140, static int len(Object[])
251 (defun len (kvs)
252  (the (unsigned-byte 32) (ash (- (length kvs) 2) -1)))
253 
254 (declaim (inline key))
255 (declaim (ftype (function (simple-vector (unsigned-byte 32)) T) key))
256 ;; L175 static Object key(Object[], int)
257 (defun key (kvs idx)
258  (svref kvs (+ 2 (ash idx 1))))
259 
260 (declaim (inline val))
261 (declaim (ftype (function (simple-vector (unsigned-byte 32)) T) val))
262 ;; L176 static Object val(Object[], int)
263 (defun val (kvs idx)
264  (svref kvs (+ 3 (ash idx 1))))
265 
266 (declaim (inline cas-key))
267 ;; L177 static boolean CAS_key(Object[], int, Object, Object)
268 (defun cas-key (kvs idx old key)
269  (declare (simple-vector kvs))
270  (sb-ext:cas (svref kvs (+ 2 (ash idx 1))) old key))
271 
272 (declaim (inline cas-val))
273 ;; L180 static boolean CAS_val(Object[], int, Object, Object)
274 (defun cas-val (kvs idx old val)
275  (declare (simple-vector kvs))
276  (sb-ext:cas (svref kvs (+ 3 (ash idx 1))) old val))
277 
278 ;; L237 long reprobes()
279 (defun reprobes (table)
280  (prog1 (counter-value (%castable-reprobes table))
281  (setf (%castable-reprobes table) (make-counter))))
282 
283 (defun determine-hasher (test)
284  ;; TODO 2024-09-23:
285  (or (cond ;; ((eq test #'eq) #'sb-impl::eq-hash) ;; package-lock violation
286  ((eq test #'eql) #'sb-impl::eql-hash)
287  ((eq test #'equal) #'sb-impl::equal-hash)
288  ((eq test #'equalp) #'sb-impl::equalp-hash)
289  ;; FIXME: implement own equalp hash
290  (t (third (find test sb-impl::*user-hash-table-tests* :key #'second))))
291  (error "Don't know a hasher for ~a." test)))
292 
293 (defvar *maximum-size* (* 8 1024 1024))
294 (defun make-castable (&key test size hash-function)
295  (let* ((size (min *maximum-size* (max MIN-SIZE (or size 0))))
296  (test (etypecase test
297  (null #'eql)
298  (function test)
299  (symbol (fdefinition test))))
300  (hash-function (etypecase hash-function
301  (null (determine-hasher test))
302  (function hash-function)
303  (symbol (fdefinition hash-function)))))
304  (let ((power-of-two (expt 2 (integer-length size))))
305  (let ((kvs (make-array (+ 2 (ash power-of-two 1)) :initial-element NO-VALUE)))
306  (setf (svref kvs 0) (make-chm (make-counter)))
307  (setf (svref kvs 1) (make-array power-of-two :element-type 'fixnum :initial-element 0))
308  (%make-castable kvs (get-internal-real-time) test hash-function)))))
309 
310 (declaim (inline hash))
311 (defun hash (table thing)
312  (rehash (funcall (%castable-hasher table) thing)))
313 
314 ;; Not `int size()` from the original! This is more like HASH-TABLE-SIZE, as
315 ;; it is the number of mappings that can be held right now.
316 (defun castable-size (table)
317  (/ (- (length (%castable-kvs table)) 2) 2))
318 
319 ;; L281 int size()
320 (defun castable-count (table)
321  (chm-size (chm (%castable-kvs table))))
322 
323 (defun castable-test (table)
324  (%castable-test table))
325 
326 (defun castable-hasher (table)
327  (%castable-hasher table))
328 
329 ;; L321 TypeV putIfAbsent(TypeK, TypeV)
330 (defun put-if-absent (table key value)
331  (multiple-value-bind (out present?)
332  (put-if-match table key value TOMBSTONE)
333  (declare (ignore out))
334  (not present?)))
335 ;; Missing: containsValue
336 ;; L342 boolean replace(TypeK, TypeV)
337 (defun put-if-present (table key value)
338  (multiple-value-bind (out present?)
339  (put-if-match table key value MATCH-ANY)
340  (if present?
341  (funcall (%castable-test table) out value)
342  nil)))
343 ;; L347 boolean replace(TypeK, TypeV, TypeV)
344 (defun put-if-equal (table key new-value old-value)
345  (multiple-value-bind (out present?)
346  (put-if-match table key new-value old-value)
347  (if present?
348  (funcall (%castable-test table) old-value out)
349  nil)))
350 ;; Missing: clone
351 
352 ;; L313 put(TypeK, TypeV)
353 (defun (setf getchash) (value key table &key (if-exists :overwrite) (if-does-not-exist :overwrite))
354  (ecase if-exists
355  (:overwrite
356  (ecase if-does-not-exist
357  (:overwrite
358  (put-if-match table key value NO-MATCH-OLD))
359  (:error
360  (unless (put-if-present table key value)
361  (error "Key does not exist in table.")))
362  ((NIL)
363  (put-if-present table key value))))
364  (:error
365  (ecase if-does-not-exist
366  (:overwrite
367  (unless (put-if-absent table key value)
368  (error "Key already exists in table.")))
369  (:error
370  (error "Key either does or does not exist in table."))
371  ((NIL)
372  (when (nth-value 1 (getchash key table))
373  (error "Key already exists in table.")))))
374  ((NIL)
375  (ecase if-does-not-exist
376  (:overwrite
377  (put-if-absent table key value))
378  (:error
379  (unless (nth-value 1 (getchash key table))
380  (error "Key does not exist in table.")))
381  ((NIL)
382  NIL))))
383  value)
384 
385 (define-compiler-macro (setf getchash) (value key table &key (if-exists :overwrite) (if-does-not-exist :create))
386  (let ((v (gensym "VALUE")))
387  `(let ((,v ,value))
388  ,(ecase if-exists
389  (:overwrite
390  (ecase if-does-not-exist
391  (:create
392  `(put-if-match ,table ,key ,v NO-MATCH-OLD))
393  (:error
394  `(unless (put-if-present ,table ,key ,v)
395  (error "Key does not exist in table.")))
396  ((NIL)
397  `(put-if-present ,table ,key ,v))))
398  (:error
399  (ecase if-does-not-exist
400  (:create
401  `(unless (put-if-absent ,table ,key ,v)
402  (error "Key already exists in table.")))
403  (:error
404  `(error "Key either does or does not exist in table."))
405  ((NIL)
406  `(when (nth-value 1 (getchash ,key ,table))
407  (error "Key already exists in table.")))))
408  ((NIL)
409  (ecase if-does-not-exist
410  (:create
411  `(put-if-absent ,table ,key ,v))
412  (:error
413  `(unless (nth-value 1 (getchash ,key ,table))
414  (error "Key does not exist in table.")))
415  ((NIL)
416  NIL))))
417  ,v)))
418 
419 ;; Close to L329 TypeV remove(Object)
420 ;; REMHASH returns true if there was a mapping and false otherwise, but
421 ;; remove() returns `null` or the old value.
422 (defun remchash (key table)
423  (if (eq TOMBSTONE (%put-if-match table (%castable-kvs table) key TOMBSTONE NO-MATCH-OLD))
424  NIL
425  T))
426 
427 ;; Close to L334 boolean remove(Object, Object)
428 (defun try-remchash (table key val)
429  (multiple-value-bind (out present?)
430  (put-if-match table key TOMBSTONE val)
431  (if present?
432  (funcall (%castable-test table) out val)
433  nil)))
434 ;; L352 TypeV putIfMatch(Object, Object, Object)
435 (defun put-if-match (table key new old)
436  (let ((res (%put-if-match table (%castable-kvs table) key new old)))
437  (assert (not (prime-p res)))
438  (assert (not (eq res NO-VALUE)))
439  (if (eq res TOMBSTONE)
440  (values NIL NIL)
441  (values res T))))
442 ;; L372 void clear()
443 (defun clrchash (table)
444  (let ((new (%castable-kvs (make-castable))))
445  (loop until (sb-ext:cas (%castable-kvs table) (%castable-kvs table) new))))
446 
447 (declaim (inline keyeq))
448 ;; L467 boolean keyeq(Object, Object, int[], int, int)
449 (defun keyeq (k key hashes hash fullhash test)
450  (declare (type fixnum hash fullhash))
451  (declare (type (function (T T) boolean) test))
452  (declare (type (simple-array fixnum (*)) hashes))
453  (declare (optimize speed))
454  (or (eq k key)
455  ;; Key does not match exactly, so try more expensive comparison.
456  (and ;; If the hash exists, does it match?
457  (or (= (aref hashes hash) 0)
458  (= (aref hashes hash) fullhash))
459  ;; Avoid testing tombstones
460  (not (eq k TOMBSTONE))
461  ;; Call test function for real comparison
462  (funcall test key k))))
463 ;; L502 Object get_impl(NonBlockingHashMap, Object[], Object, int)
464 (defun %getchash (table kvs key fullhash)
465  (declare (type castable table))
466  (declare (type simple-vector kvs))
467  (declare (type fixnum fullhash))
468  (declare (optimize speed))
469  (let* ((len (len kvs))
470  (chm (chm kvs))
471  (hashes (hashes kvs))
472  (idx (logand fullhash (1- len)))
473  (test (%castable-test table))
474  (reprobe-cnt 0))
475  (declare (fixnum reprobe-cnt))
476  ;; Spin for a hit
477  (loop (let ((k (key kvs idx))
478  (v (val kvs idx)))
479  ;; Early table miss
480  (when (eq k NO-VALUE) (return NO-VALUE))
481  (let ((newkvs (%chm-newkvs chm)))
482  ;; Compare the keys
483  (when (keyeq k key hashes idx fullhash test)
484  ;; If we are not copying at the moment, we're done.
485  (unless (prime-p v)
486  (return (if (eq v TOMBSTONE)
487  NO-VALUE
488  v)))
489  ;; Copy in progress, help with copying and retry.
490  (return (%getchash table
491  (copy-slot-and-check chm table kvs idx key)
492  key
493  fullhash)))
494  ;; If we exceed reprobes, help resizing.
495  (when (or (<= (reprobe-limit len) (incf reprobe-cnt))
496  (eq key TOMBSTONE))
497  (if (null newkvs)
498  ;; Nothing here.
499  (return NO-VALUE)
500  ;; Retry in a new table copy
501  (return (%getchash table (help-copy table newkvs) key fullhash))))
502  ;; Reprobe.
503  (setf idx (logand (1+ idx) (1- len))))))))
504 
505 ;; L495 TypeV get(Object)
506 (defun getchash (key table &optional default)
507  (declare (optimize speed))
508  (let* ((fullhash (hash table key))
509  (value (%getchash table (%castable-kvs table) key fullhash)))
510  ;; Make sure we never return primes
511  (check-type value (not prime))
512  (if (eql value NO-VALUE)
513  (values default NIL)
514  (values value T))))
515 
516 ;; L555 Object putIfMatch(NonBlockingHashMap, Object[], Object, Object, Object)
517 (defun %put-if-match (table kvs key put exp)
518  (declare (type castable table))
519  (declare (type simple-vector kvs))
520  (declare (optimize speed))
521  (assert (and (not (prime-p put))
522  (not (prime-p exp))))
523  (let* ((fullhash (hash table key))
524  (len (len kvs))
525  (chm (chm kvs))
526  (hashes (hashes kvs))
527  (test (%castable-test table))
528  (idx (logand fullhash (1- len)))
529  (reprobe-cnt 0)
530  (k NO-VALUE) (v NO-VALUE)
531  (newkvs NIL))
532  (declare (type fixnum idx reprobe-cnt))
533  ;; Spin for a hit
534  (loop (setf v (val kvs idx))
535  (setf k (key kvs idx))
536  ;; Is the slot free?
537  (when (eq k NO-VALUE)
538  ;; No need to put a tombstone in an empty field
539  (when (eq put TOMBSTONE)
540  (return-from %put-if-match put))
541  ;; Claim the spot
542  (when (cas-key kvs idx NO-VALUE key)
543  (incf-counter (%chm-slots chm))
544  (setf (aref hashes idx) fullhash)
545  (return))
546  ;; We failed, update the key
547  (setf k (key kvs idx))
548  (assert (not (eq k NO-VALUE))))
549  ;; Okey, we have a key there
550  (setf newkvs (%chm-newkvs chm))
551  ;; Test if this is our key
552  (when (keyeq k key hashes idx fullhash test)
553  (return))
554  ;; If we exceed reprobes, start resizing
555  (when (or (<= (reprobe-limit len) (incf reprobe-cnt))
556  (eq key TOMBSTONE))
557  (setf newkvs (resize chm table kvs))
558  (unless (eq exp NO-VALUE) (help-copy table newkvs))
559  (return-from %put-if-match
560  (%put-if-match table newkvs key put exp)))
561  ;; Reprobe.
562  (setf idx (logand (1+ idx) (1- len))))
563  ;; We found a key slot, time to update it
564  ;; Fast-path
565  (when (eq put v) (return-from %put-if-match v))
566  ;; Check if we want to move to a new table
567  (when (and ;; Do we have a new table already?
568  (null newkvs)
569  ;; Check the value
570  (or (and (eq v NO-VALUE) (table-full-p chm reprobe-cnt len))
571  (prime-p v)))
572  (setf newkvs (resize chm table kvs)))
573  ;; Check if we are indeed moving and retry
574  (unless (null newkvs)
575  (return-from %put-if-match
576  (%put-if-match table (copy-slot-and-check chm table kvs idx exp) key put exp)))
577  ;; Finally we can do the update
578  (loop (check-type v (not prime))
579  ;; If we don't match the old, bail out
580  (when (and (not (eq exp NO-MATCH-OLD))
581  (not (eq v exp))
582  (or (not (eq exp MATCH-ANY))
583  (eq v TOMBSTONE)
584  (eq v NO-VALUE))
585  (not (and (eq v NO-VALUE) (eq exp TOMBSTONE)))
586  (or (eq exp NO-VALUE) (not (funcall test exp v))))
587  (return v))
588  ;; Perform the change
589  (when (cas-val kvs idx v put)
590  ;; Okey, we got it, update the size
591  (unless (eq exp NO-VALUE)
592  (when (and (or (eq v NO-VALUE) (eq v TOMBSTONE))
593  (not (eq put TOMBSTONE)))
594  (incf-counter (%chm-size chm)))
595  (when (and (not (or (eq v NO-VALUE) (eq v TOMBSTONE)))
596  (eq put TOMBSTONE))
597  (decf-counter (%chm-size chm))))
598  (return (if (and (eq v NO-VALUE) (not (eq exp NO-VALUE)))
599  TOMBSTONE
600  v)))
601  ;; CAS failed, retry
602  (setf v (val kvs idx))
603  ;; If we got a prime we need to restart from the beginning
604  (when (prime-p v)
605  (return (%put-if-match table (copy-slot-and-check chm table kvs idx exp) key put exp))))))
606 
607 ;; L699 Object[] help_copy(Object[])
608 (declaim (inline help-copy))
609 (defun help-copy (table helper)
610  (declare (type castable table))
611  (declare (optimize speed))
612  (let* ((topkvs (%castable-kvs table))
613  (topchm (chm topkvs)))
614  (unless (null (%chm-newkvs topchm))
615  (%help-copy topchm table topkvs NIL))
616  helper))
617 
618 ;; L794 Object[] resize(NonBlockingHashMap, Object[])
619 (defun resize (chm table kvs)
620  (declare (type chm chm))
621  (declare (type castable table))
622  (declare (type simple-array kvs))
623  (declare (optimize speed))
624  (assert (eq chm (chm kvs)))
625  ;; Check for resize in progress
626  (let ((newkvs (%chm-newkvs chm)))
627  (unless (null newkvs)
628  ;; Use the new table already
629  (return-from resize newkvs))
630  (let* ((oldlen (len kvs))
631  (sz (chm-size chm))
632  (newsz sz))
633  (declare (type fixnum oldlen sz newsz))
634  ;; Heuristic for new size
635  (when (<= (ash oldlen -2) sz)
636  (setf newsz (ash oldlen 1))
637  (when (<= sz (ash oldlen -1))
638  (setf newsz (ash oldlen 2))))
639  ;; Much denser table with more reprobes
640  #+(or)
641  (when (<= (ash oldlen -1) sz)
642  (setf newsz (ash oldlen 1)))
643  ;; Was the last resize recent? If so, double again
644  ;; to accommodate tables with lots of inserts at the moment.
645  (let ((tm (get-internal-real-time)))
646  (when (and (<= newsz oldlen)
647  ;; If we resized less than a second ago
648  (<= tm (+ (%castable-last-resize table)
649  INTERNAL-TIME-UNITS-PER-SECOND))
650  ;; And we have plenty of dead keys
651  (<= (ash sz 1) (counter-value~ (%chm-slots chm))))
652  (setf newsz (ash oldlen 1))))
653  ;; Don't shrink
654  (when (< newsz oldlen) (setf newsz oldlen))
655  (let ((size MIN-SIZE)
656  (r (%chm-resizers chm)))
657  (declare (type fixnum size))
658  ;; Convert to power of two
659  (loop while (< size newsz)
660  do (setf size (ash size 1)))
661  ;; Limit the number of threads resizing things
662  (loop until (sb-ext:cas (%chm-resizers chm) r (1+ r))
663  do (setf r (%chm-resizers chm)))
664  ;; Size calculation: 2 words per table + extra
665  ;; NOTE: The original assumes 32 bit pointers, we conditionalise
666  (let ((megs (ash (ash (+ (* size 2) 4)
667  #+64-BIT 4 #-64-BIT 3)
668  -20)))
669  (declare (type fixnum megs))
670  (when (and (<= 2 r) (< 0 megs))
671  (setf newkvs (%chm-newkvs chm))
672  (unless (null newkvs)
673  (return-from resize newkvs))
674  ;; We already have two threads trying a resize, wait
675  (sleep (/ (* 8 megs) 1000))))
676  ;; Last check
677  (setf newkvs (%chm-newkvs chm))
678  (unless (null newkvs)
679  (return-from resize newkvs))
680  ;; Allocate the array
681  (setf newkvs (make-array (+ 2 (* 2 size)) :initial-element NO-VALUE))
682  (setf (svref newkvs 0) (make-chm (%chm-size chm)))
683  (setf (svref newkvs 1) (make-array size :element-type 'fixnum :initial-element 0))
684  ;; Check again after the allocation
685  (unless (null (%chm-newkvs chm))
686  (return-from resize (%chm-newkvs chm)))
687  ;; CAS the table in. We can let the GC handle deallocation. Thanks, GC!
688  (if (cas-newkvs chm newkvs)
689  newkvs
690  (%chm-newkvs chm))))))
691 
692 ;; L906 help_copy_impl(NonBlockingHashMap, Object[], boolean)
693 (defun %help-copy (chm table oldkvs copy-all)
694  (declare (type chm chm))
695  (declare (type castable table))
696  (declare (type simple-array oldkvs))
697  (declare (optimize speed))
698  (assert (eq chm (chm oldkvs)))
699  (let* ((newkvs (%chm-newkvs chm))
700  (oldlen (len oldkvs))
701  (min-copy-work (min oldlen 1024))
702  (panic-start -1)
703  (copy-idx -9999))
704  (declare (type fixnum oldlen min-copy-work copy-idx panic-start))
705  (assert (not (null newkvs)))
706  ;; Loop while there's work to be done
707  (loop while (< (%chm-copy-done chm) oldlen)
708  do ;; We panic if we tried to copy twice and it failed.
709  (when (= -1 panic-start)
710  (setf copy-idx (%chm-copy-idx chm))
711  (loop while (and (< copy-idx (ash oldlen 1))
712  (not (sb-ext:cas (%chm-copy-idx chm) copy-idx (+ copy-idx min-copy-work))))
713  do (setf copy-idx (%chm-copy-idx chm)))
714  (unless (< copy-idx (ash oldlen 1))
715  (setf panic-start copy-idx)))
716  ;; Okey, now perform the copy.
717  (let ((workdone 0))
718  (declare (type fixnum workdone))
719  (dotimes (i min-copy-work)
720  (when (copy-slot table (logand (+ copy-idx i) (1- oldlen))
721  oldkvs newkvs)
722  (incf workdone)))
723  ;; Promote our work
724  (when (plusp workdone)
725  (copy-check-and-promote chm table oldkvs workdone))
726  (incf copy-idx min-copy-work)
727  ;; End early if we shouldn't copy everything.
728  (when (and (not copy-all) (= -1 panic-start))
729  (return-from %help-copy))))
730  ;; Promote again in case we race on end of copy
731  (copy-check-and-promote chm table oldkvs 0)))
732 
733 ;; Copy the slot and check that we have done so successfully.
734 ;; L970 Object[] copy_slot_and_check(NonBlockingHashMap, Object[], int, Object)
735 (defun copy-slot-and-check (chm table oldkvs idx should-help)
736  (declare (type chm chm))
737  (declare (type castable table))
738  (declare (type simple-array oldkvs))
739  (declare (optimize speed))
740  (assert (eq chm (chm oldkvs)))
741  (let ((newkvs (%chm-newkvs chm)))
742  (assert (not (null newkvs)))
743  (when (copy-slot table idx oldkvs (%chm-newkvs chm))
744  (copy-check-and-promote chm table oldkvs 1))
745  (if should-help
746  (help-copy table newkvs)
747  newkvs)))
748 
749 ;; L983 copy_check_and_promote(NonBlockingHashMap, Object[], int)
750 (defun copy-check-and-promote (chm table oldkvs work-done)
751  (declare (type chm chm))
752  (declare (type castable table))
753  (declare (type simple-array oldkvs))
754  (declare (type fixnum work-done))
755  (declare (optimize speed))
756  (assert (eq chm (chm oldkvs)))
757  (let ((oldlen (len oldkvs))
758  (copy-done (%chm-copy-done chm)))
759  (assert (<= (+ copy-done work-done) oldlen))
760  (when (< 0 work-done)
761  (loop until (sb-ext:cas (%chm-copy-done chm) copy-done (+ copy-done work-done))
762  do (setf copy-done (%chm-copy-done chm))
763  (assert (<= (+ copy-done work-done) oldlen))))
764  ;; Check for copy being completely done and promote
765  (when (and (= (+ copy-done work-done) oldlen)
766  (eq (%castable-kvs table) oldkvs)
767  (sb-ext:cas (%castable-kvs table) oldkvs (%chm-newkvs chm)))
768  (setf (%castable-last-resize table) (get-internal-real-time)))))
769 
770 ;; Copy one slot into the new table,
771 ;; returns true if we are sure that the new table has a value.
772 ;; L1023 boolean copy_slot(NonBlockingHashMap, int, Object[], Object[])
773 (defun copy-slot (table idx oldkvs newkvs)
774  (declare (type castable table))
775  (declare (type fixnum idx))
776  (declare (type simple-array oldkvs newkvs))
777  (declare (optimize speed))
778  ;; First tombstone the key blindly.
779  (let (key)
780  (loop while (eq (setf key (key oldkvs idx)) NO-VALUE)
781  do (cas-key oldkvs idx NO-VALUE TOMBSTONE))
782  ;; Prevent new values from showing up in the old table
783  (let ((oldval (val oldkvs idx)))
784  (loop until (prime-p oldval)
785  for box = (if (or (eq oldval NO-VALUE)
786  (eq oldval TOMBSTONE))
787  TOMBPRIME
788  (prime oldval))
789  do (when (cas-val oldkvs idx oldval box)
790  ;; We made sure to prime the value to prevent updates.
791  (when (eq box TOMBPRIME)
792  (return-from copy-slot T))
793  (setf oldval box)
794  (return))
795  ;; Retry on CAS failure
796  (setf oldval (val oldkvs idx)))
797  (when (eq oldval TOMBPRIME)
798  ;; We already completed the copy
799  (return-from copy-slot NIL))
800  ;; Finally do the actual copy, but only if we would write into
801  ;; a null. Otherwise, someone else already copied.
802  (let ((old-unboxed (prime-value oldval)))
803  (assert (not (eq old-unboxed TOMBSTONE)))
804  (prog1 (eq NO-VALUE (%put-if-match table newkvs key old-unboxed NO-VALUE))
805  ;; Now that the copy is done, we can stub out the old key completely.
806  (loop until (cas-val oldkvs idx oldval TOMBPRIME)
807  do (setf oldval (val oldkvs idx))))))))
808 
809 (defun mapchash (function table)
810  (let (snapshot-kvs)
811  (loop for top-kvs = (%castable-kvs table)
812  for top-chm = (chm top-kvs)
813  for newkvs = (%chm-newkvs top-chm)
814  until (null newkvs)
815  do (help-copy table newkvs)
816  finally (setf snapshot-kvs top-kvs))
817  (loop for position from 2 below (length snapshot-kvs) by 2
818  for key = (aref snapshot-kvs position)
819  for value = (aref snapshot-kvs (1+ position))
820  unless (or (eq key NO-VALUE)
821  (eq key TOMBSTONE)
822  (eq value NO-VALUE))
823  do (funcall function key value))))
824 
825 (defmethod print-object ((table castable) stream)
826  (print-unreadable-object (table stream :type t :identity t)
827  (format stream ":test ~s :count ~s :size ~s"
828  (castable-test table)
829  (castable-count table)
830  (castable-size table))))