changelog shortlog graph tags branches changeset files revisions annotate raw help

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

changeset 441: 9fa3b9154bb2
parent: 2b7f0c032fc7
child: 6856c021d084
author: Richard Westhaver <ellis@rwest.io>
date: Thu, 13 Jun 2024 21:57:36 -0400
permissions: -rw-r--r--
description: add pod/containerfile, rm pod/buildah, some work on zstd
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  (or (cond ((eq test #'eq) #'sb-impl::eq-hash)
285  ((eq test #'eql) #'sb-impl::eql-hash)
286  ((eq test #'equal) #'sb-impl::equal-hash)
287  ((eq test #'equalp) #'sb-impl::equalp-hash)
288  ;; FIXME: implement own equalp hash
289  (t (third (find test sb-impl::*user-hash-table-tests* :key #'second))))
290  (error "Don't know a hasher for ~a." test)))
291 
292 (defvar *maximum-size* (* 8 1024 1024))
293 (defun make-castable (&key test size hash-function)
294  (let* ((size (min *maximum-size* (max MIN-SIZE (or size 0))))
295  (test (etypecase test
296  (null #'eql)
297  (function test)
298  (symbol (fdefinition test))))
299  (hash-function (etypecase hash-function
300  (null (determine-hasher test))
301  (function hash-function)
302  (symbol (fdefinition hash-function)))))
303  (let ((power-of-two (expt 2 (integer-length size))))
304  (let ((kvs (make-array (+ 2 (ash power-of-two 1)) :initial-element NO-VALUE)))
305  (setf (svref kvs 0) (make-chm (make-counter)))
306  (setf (svref kvs 1) (make-array power-of-two :element-type 'fixnum :initial-element 0))
307  (%make-castable kvs (get-internal-real-time) test hash-function)))))
308 
309 (declaim (inline hash))
310 (defun hash (table thing)
311  (rehash (funcall (%castable-hasher table) thing)))
312 
313 ;; Not `int size()` from the original! This is more like HASH-TABLE-SIZE, as
314 ;; it is the number of mappings that can be held right now.
315 (defun castable-size (table)
316  (/ (- (length (%castable-kvs table)) 2) 2))
317 
318 ;; L281 int size()
319 (defun castable-count (table)
320  (chm-size (chm (%castable-kvs table))))
321 
322 (defun castable-test (table)
323  (%castable-test table))
324 
325 (defun castable-hasher (table)
326  (%castable-hasher table))
327 
328 ;; L321 TypeV putIfAbsent(TypeK, TypeV)
329 (defun put-if-absent (table key value)
330  (multiple-value-bind (out present?)
331  (put-if-match table key value TOMBSTONE)
332  (declare (ignore out))
333  (not present?)))
334 ;; Missing: containsValue
335 ;; L342 boolean replace(TypeK, TypeV)
336 (defun put-if-present (table key value)
337  (multiple-value-bind (out present?)
338  (put-if-match table key value MATCH-ANY)
339  (if present?
340  (funcall (%castable-test table) out value)
341  nil)))
342 ;; L347 boolean replace(TypeK, TypeV, TypeV)
343 (defun put-if-equal (table key new-value old-value)
344  (multiple-value-bind (out present?)
345  (put-if-match table key new-value old-value)
346  (if present?
347  (funcall (%castable-test table) old-value out)
348  nil)))
349 ;; Missing: clone
350 
351 ;; L313 put(TypeK, TypeV)
352 (defun (setf getchash) (value key table &key (if-exists :overwrite) (if-does-not-exist :overwrite))
353  (ecase if-exists
354  (:overwrite
355  (ecase if-does-not-exist
356  (:overwrite
357  (put-if-match table key value NO-MATCH-OLD))
358  (:error
359  (unless (put-if-present table key value)
360  (error "Key does not exist in table.")))
361  ((NIL)
362  (put-if-present table key value))))
363  (:error
364  (ecase if-does-not-exist
365  (:overwrite
366  (unless (put-if-absent table key value)
367  (error "Key already exists in table.")))
368  (:error
369  (error "Key either does or does not exist in table."))
370  ((NIL)
371  (when (nth-value 1 (getchash key table))
372  (error "Key already exists in table.")))))
373  ((NIL)
374  (ecase if-does-not-exist
375  (:overwrite
376  (put-if-absent table key value))
377  (:error
378  (unless (nth-value 1 (getchash key table))
379  (error "Key does not exist in table.")))
380  ((NIL)
381  NIL))))
382  value)
383 
384 (define-compiler-macro (setf getchash) (value key table &key (if-exists :overwrite) (if-does-not-exist :create))
385  (let ((v (gensym "VALUE")))
386  `(let ((,v ,value))
387  ,(ecase if-exists
388  (:overwrite
389  (ecase if-does-not-exist
390  (:create
391  `(put-if-match ,table ,key ,v NO-MATCH-OLD))
392  (:error
393  `(unless (put-if-present ,table ,key ,v)
394  (error "Key does not exist in table.")))
395  ((NIL)
396  `(put-if-present ,table ,key ,v))))
397  (:error
398  (ecase if-does-not-exist
399  (:create
400  `(unless (put-if-absent ,table ,key ,v)
401  (error "Key already exists in table.")))
402  (:error
403  `(error "Key either does or does not exist in table."))
404  ((NIL)
405  `(when (nth-value 1 (getchash ,key ,table))
406  (error "Key already exists in table.")))))
407  ((NIL)
408  (ecase if-does-not-exist
409  (:create
410  `(put-if-absent ,table ,key ,v))
411  (:error
412  `(unless (nth-value 1 (getchash ,key ,table))
413  (error "Key does not exist in table.")))
414  ((NIL)
415  NIL))))
416  ,v)))
417 
418 ;; Close to L329 TypeV remove(Object)
419 ;; REMHASH returns true if there was a mapping and false otherwise, but
420 ;; remove() returns `null` or the old value.
421 (defun remchash (key table)
422  (if (eq TOMBSTONE (%put-if-match table (%castable-kvs table) key TOMBSTONE NO-MATCH-OLD))
423  NIL
424  T))
425 
426 ;; Close to L334 boolean remove(Object, Object)
427 (defun try-remchash (table key val)
428  (multiple-value-bind (out present?)
429  (put-if-match table key TOMBSTONE val)
430  (if present?
431  (funcall (%castable-test table) out val)
432  nil)))
433 ;; L352 TypeV putIfMatch(Object, Object, Object)
434 (defun put-if-match (table key new old)
435  (let ((res (%put-if-match table (%castable-kvs table) key new old)))
436  (assert (not (prime-p res)))
437  (assert (not (eq res NO-VALUE)))
438  (if (eq res TOMBSTONE)
439  (values NIL NIL)
440  (values res T))))
441 ;; L372 void clear()
442 (defun clrchash (table)
443  (let ((new (%castable-kvs (make-castable))))
444  (loop until (sb-ext:cas (%castable-kvs table) (%castable-kvs table) new))))
445 
446 (declaim (inline keyeq))
447 ;; L467 boolean keyeq(Object, Object, int[], int, int)
448 (defun keyeq (k key hashes hash fullhash test)
449  (declare (type fixnum hash fullhash))
450  (declare (type (function (T T) boolean) test))
451  (declare (type (simple-array fixnum (*)) hashes))
452  (declare (optimize speed))
453  (or (eq k key)
454  ;; Key does not match exactly, so try more expensive comparison.
455  (and ;; If the hash exists, does it match?
456  (or (= (aref hashes hash) 0)
457  (= (aref hashes hash) fullhash))
458  ;; Avoid testing tombstones
459  (not (eq k TOMBSTONE))
460  ;; Call test function for real comparison
461  (funcall test key k))))
462 ;; L502 Object get_impl(NonBlockingHashMap, Object[], Object, int)
463 (defun %getchash (table kvs key fullhash)
464  (declare (type castable table))
465  (declare (type simple-vector kvs))
466  (declare (type fixnum fullhash))
467  (declare (optimize speed))
468  (let* ((len (len kvs))
469  (chm (chm kvs))
470  (hashes (hashes kvs))
471  (idx (logand fullhash (1- len)))
472  (test (%castable-test table))
473  (reprobe-cnt 0))
474  (declare (fixnum reprobe-cnt))
475  ;; Spin for a hit
476  (loop (let ((k (key kvs idx))
477  (v (val kvs idx)))
478  ;; Early table miss
479  (when (eq k NO-VALUE) (return NO-VALUE))
480  (let ((newkvs (%chm-newkvs chm)))
481  ;; Compare the keys
482  (when (keyeq k key hashes idx fullhash test)
483  ;; If we are not copying at the moment, we're done.
484  (unless (prime-p v)
485  (return (if (eq v TOMBSTONE)
486  NO-VALUE
487  v)))
488  ;; Copy in progress, help with copying and retry.
489  (return (%getchash table
490  (copy-slot-and-check chm table kvs idx key)
491  key
492  fullhash)))
493  ;; If we exceed reprobes, help resizing.
494  (when (or (<= (reprobe-limit len) (incf reprobe-cnt))
495  (eq key TOMBSTONE))
496  (if (null newkvs)
497  ;; Nothing here.
498  (return NO-VALUE)
499  ;; Retry in a new table copy
500  (return (%getchash table (help-copy table newkvs) key fullhash))))
501  ;; Reprobe.
502  (setf idx (logand (1+ idx) (1- len))))))))
503 
504 ;; L495 TypeV get(Object)
505 (defun getchash (key table &optional default)
506  (declare (optimize speed))
507  (let* ((fullhash (hash table key))
508  (value (%getchash table (%castable-kvs table) key fullhash)))
509  ;; Make sure we never return primes
510  (check-type value (not prime))
511  (if (eql value NO-VALUE)
512  (values default NIL)
513  (values value T))))
514 
515 ;; L555 Object putIfMatch(NonBlockingHashMap, Object[], Object, Object, Object)
516 (defun %put-if-match (table kvs key put exp)
517  (declare (type castable table))
518  (declare (type simple-vector kvs))
519  (declare (optimize speed))
520  (assert (and (not (prime-p put))
521  (not (prime-p exp))))
522  (let* ((fullhash (hash table key))
523  (len (len kvs))
524  (chm (chm kvs))
525  (hashes (hashes kvs))
526  (test (%castable-test table))
527  (idx (logand fullhash (1- len)))
528  (reprobe-cnt 0)
529  (k NO-VALUE) (v NO-VALUE)
530  (newkvs NIL))
531  (declare (type fixnum idx reprobe-cnt))
532  ;; Spin for a hit
533  (loop (setf v (val kvs idx))
534  (setf k (key kvs idx))
535  ;; Is the slot free?
536  (when (eq k NO-VALUE)
537  ;; No need to put a tombstone in an empty field
538  (when (eq put TOMBSTONE)
539  (return-from %put-if-match put))
540  ;; Claim the spot
541  (when (cas-key kvs idx NO-VALUE key)
542  (incf-counter (%chm-slots chm))
543  (setf (aref hashes idx) fullhash)
544  (return))
545  ;; We failed, update the key
546  (setf k (key kvs idx))
547  (assert (not (eq k NO-VALUE))))
548  ;; Okey, we have a key there
549  (setf newkvs (%chm-newkvs chm))
550  ;; Test if this is our key
551  (when (keyeq k key hashes idx fullhash test)
552  (return))
553  ;; If we exceed reprobes, start resizing
554  (when (or (<= (reprobe-limit len) (incf reprobe-cnt))
555  (eq key TOMBSTONE))
556  (setf newkvs (resize chm table kvs))
557  (unless (eq exp NO-VALUE) (help-copy table newkvs))
558  (return-from %put-if-match
559  (%put-if-match table newkvs key put exp)))
560  ;; Reprobe.
561  (setf idx (logand (1+ idx) (1- len))))
562  ;; We found a key slot, time to update it
563  ;; Fast-path
564  (when (eq put v) (return-from %put-if-match v))
565  ;; Check if we want to move to a new table
566  (when (and ;; Do we have a new table already?
567  (null newkvs)
568  ;; Check the value
569  (or (and (eq v NO-VALUE) (table-full-p chm reprobe-cnt len))
570  (prime-p v)))
571  (setf newkvs (resize chm table kvs)))
572  ;; Check if we are indeed moving and retry
573  (unless (null newkvs)
574  (return-from %put-if-match
575  (%put-if-match table (copy-slot-and-check chm table kvs idx exp) key put exp)))
576  ;; Finally we can do the update
577  (loop (check-type v (not prime))
578  ;; If we don't match the old, bail out
579  (when (and (not (eq exp NO-MATCH-OLD))
580  (not (eq v exp))
581  (or (not (eq exp MATCH-ANY))
582  (eq v TOMBSTONE)
583  (eq v NO-VALUE))
584  (not (and (eq v NO-VALUE) (eq exp TOMBSTONE)))
585  (or (eq exp NO-VALUE) (not (funcall test exp v))))
586  (return v))
587  ;; Perform the change
588  (when (cas-val kvs idx v put)
589  ;; Okey, we got it, update the size
590  (unless (eq exp NO-VALUE)
591  (when (and (or (eq v NO-VALUE) (eq v TOMBSTONE))
592  (not (eq put TOMBSTONE)))
593  (incf-counter (%chm-size chm)))
594  (when (and (not (or (eq v NO-VALUE) (eq v TOMBSTONE)))
595  (eq put TOMBSTONE))
596  (decf-counter (%chm-size chm))))
597  (return (if (and (eq v NO-VALUE) (not (eq exp NO-VALUE)))
598  TOMBSTONE
599  v)))
600  ;; CAS failed, retry
601  (setf v (val kvs idx))
602  ;; If we got a prime we need to restart from the beginning
603  (when (prime-p v)
604  (return (%put-if-match table (copy-slot-and-check chm table kvs idx exp) key put exp))))))
605 
606 ;; L699 Object[] help_copy(Object[])
607 (declaim (inline help-copy))
608 (defun help-copy (table helper)
609  (declare (type castable table))
610  (declare (optimize speed))
611  (let* ((topkvs (%castable-kvs table))
612  (topchm (chm topkvs)))
613  (unless (null (%chm-newkvs topchm))
614  (%help-copy topchm table topkvs NIL))
615  helper))
616 
617 ;; L794 Object[] resize(NonBlockingHashMap, Object[])
618 (defun resize (chm table kvs)
619  (declare (type chm chm))
620  (declare (type castable table))
621  (declare (type simple-array kvs))
622  (declare (optimize speed))
623  (assert (eq chm (chm kvs)))
624  ;; Check for resize in progress
625  (let ((newkvs (%chm-newkvs chm)))
626  (unless (null newkvs)
627  ;; Use the new table already
628  (return-from resize newkvs))
629  (let* ((oldlen (len kvs))
630  (sz (chm-size chm))
631  (newsz sz))
632  (declare (type fixnum oldlen sz newsz))
633  ;; Heuristic for new size
634  (when (<= (ash oldlen -2) sz)
635  (setf newsz (ash oldlen 1))
636  (when (<= sz (ash oldlen -1))
637  (setf newsz (ash oldlen 2))))
638  ;; Much denser table with more reprobes
639  #+(or)
640  (when (<= (ash oldlen -1) sz)
641  (setf newsz (ash oldlen 1)))
642  ;; Was the last resize recent? If so, double again
643  ;; to accommodate tables with lots of inserts at the moment.
644  (let ((tm (get-internal-real-time)))
645  (when (and (<= newsz oldlen)
646  ;; If we resized less than a second ago
647  (<= tm (+ (%castable-last-resize table)
648  INTERNAL-TIME-UNITS-PER-SECOND))
649  ;; And we have plenty of dead keys
650  (<= (ash sz 1) (counter-value~ (%chm-slots chm))))
651  (setf newsz (ash oldlen 1))))
652  ;; Don't shrink
653  (when (< newsz oldlen) (setf newsz oldlen))
654  (let ((size MIN-SIZE)
655  (r (%chm-resizers chm)))
656  (declare (type fixnum size))
657  ;; Convert to power of two
658  (loop while (< size newsz)
659  do (setf size (ash size 1)))
660  ;; Limit the number of threads resizing things
661  (loop until (sb-ext:cas (%chm-resizers chm) r (1+ r))
662  do (setf r (%chm-resizers chm)))
663  ;; Size calculation: 2 words per table + extra
664  ;; NOTE: The original assumes 32 bit pointers, we conditionalise
665  (let ((megs (ash (ash (+ (* size 2) 4)
666  #+64-BIT 4 #-64-BIT 3)
667  -20)))
668  (declare (type fixnum megs))
669  (when (and (<= 2 r) (< 0 megs))
670  (setf newkvs (%chm-newkvs chm))
671  (unless (null newkvs)
672  (return-from resize newkvs))
673  ;; We already have two threads trying a resize, wait
674  (sleep (/ (* 8 megs) 1000))))
675  ;; Last check
676  (setf newkvs (%chm-newkvs chm))
677  (unless (null newkvs)
678  (return-from resize newkvs))
679  ;; Allocate the array
680  (setf newkvs (make-array (+ 2 (* 2 size)) :initial-element NO-VALUE))
681  (setf (svref newkvs 0) (make-chm (%chm-size chm)))
682  (setf (svref newkvs 1) (make-array size :element-type 'fixnum :initial-element 0))
683  ;; Check again after the allocation
684  (unless (null (%chm-newkvs chm))
685  (return-from resize (%chm-newkvs chm)))
686  ;; CAS the table in. We can let the GC handle deallocation. Thanks, GC!
687  (if (cas-newkvs chm newkvs)
688  newkvs
689  (%chm-newkvs chm))))))
690 
691 ;; L906 help_copy_impl(NonBlockingHashMap, Object[], boolean)
692 (defun %help-copy (chm table oldkvs copy-all)
693  (declare (type chm chm))
694  (declare (type castable table))
695  (declare (type simple-array oldkvs))
696  (declare (optimize speed))
697  (assert (eq chm (chm oldkvs)))
698  (let* ((newkvs (%chm-newkvs chm))
699  (oldlen (len oldkvs))
700  (min-copy-work (min oldlen 1024))
701  (panic-start -1)
702  (copy-idx -9999))
703  (declare (type fixnum oldlen min-copy-work copy-idx panic-start))
704  (assert (not (null newkvs)))
705  ;; Loop while there's work to be done
706  (loop while (< (%chm-copy-done chm) oldlen)
707  do ;; We panic if we tried to copy twice and it failed.
708  (when (= -1 panic-start)
709  (setf copy-idx (%chm-copy-idx chm))
710  (loop while (and (< copy-idx (ash oldlen 1))
711  (not (sb-ext:cas (%chm-copy-idx chm) copy-idx (+ copy-idx min-copy-work))))
712  do (setf copy-idx (%chm-copy-idx chm)))
713  (unless (< copy-idx (ash oldlen 1))
714  (setf panic-start copy-idx)))
715  ;; Okey, now perform the copy.
716  (let ((workdone 0))
717  (declare (type fixnum workdone))
718  (dotimes (i min-copy-work)
719  (when (copy-slot table (logand (+ copy-idx i) (1- oldlen))
720  oldkvs newkvs)
721  (incf workdone)))
722  ;; Promote our work
723  (when (plusp workdone)
724  (copy-check-and-promote chm table oldkvs workdone))
725  (incf copy-idx min-copy-work)
726  ;; End early if we shouldn't copy everything.
727  (when (and (not copy-all) (= -1 panic-start))
728  (return-from %help-copy))))
729  ;; Promote again in case we race on end of copy
730  (copy-check-and-promote chm table oldkvs 0)))
731 
732 ;; Copy the slot and check that we have done so successfully.
733 ;; L970 Object[] copy_slot_and_check(NonBlockingHashMap, Object[], int, Object)
734 (defun copy-slot-and-check (chm table oldkvs idx should-help)
735  (declare (type chm chm))
736  (declare (type castable table))
737  (declare (type simple-array oldkvs))
738  (declare (optimize speed))
739  (assert (eq chm (chm oldkvs)))
740  (let ((newkvs (%chm-newkvs chm)))
741  (assert (not (null newkvs)))
742  (when (copy-slot table idx oldkvs (%chm-newkvs chm))
743  (copy-check-and-promote chm table oldkvs 1))
744  (if should-help
745  (help-copy table newkvs)
746  newkvs)))
747 
748 ;; L983 copy_check_and_promote(NonBlockingHashMap, Object[], int)
749 (defun copy-check-and-promote (chm table oldkvs work-done)
750  (declare (type chm chm))
751  (declare (type castable table))
752  (declare (type simple-array oldkvs))
753  (declare (type fixnum work-done))
754  (declare (optimize speed))
755  (assert (eq chm (chm oldkvs)))
756  (let ((oldlen (len oldkvs))
757  (copy-done (%chm-copy-done chm)))
758  (assert (<= (+ copy-done work-done) oldlen))
759  (when (< 0 work-done)
760  (loop until (sb-ext:cas (%chm-copy-done chm) copy-done (+ copy-done work-done))
761  do (setf copy-done (%chm-copy-done chm))
762  (assert (<= (+ copy-done work-done) oldlen))))
763  ;; Check for copy being completely done and promote
764  (when (and (= (+ copy-done work-done) oldlen)
765  (eq (%castable-kvs table) oldkvs)
766  (sb-ext:cas (%castable-kvs table) oldkvs (%chm-newkvs chm)))
767  (setf (%castable-last-resize table) (get-internal-real-time)))))
768 
769 ;; Copy one slot into the new table,
770 ;; returns true if we are sure that the new table has a value.
771 ;; L1023 boolean copy_slot(NonBlockingHashMap, int, Object[], Object[])
772 (defun copy-slot (table idx oldkvs newkvs)
773  (declare (type castable table))
774  (declare (type fixnum idx))
775  (declare (type simple-array oldkvs newkvs))
776  (declare (optimize speed))
777  ;; First tombstone the key blindly.
778  (let (key)
779  (loop while (eq (setf key (key oldkvs idx)) NO-VALUE)
780  do (cas-key oldkvs idx NO-VALUE TOMBSTONE))
781  ;; Prevent new values from showing up in the old table
782  (let ((oldval (val oldkvs idx)))
783  (loop until (prime-p oldval)
784  for box = (if (or (eq oldval NO-VALUE)
785  (eq oldval TOMBSTONE))
786  TOMBPRIME
787  (prime oldval))
788  do (when (cas-val oldkvs idx oldval box)
789  ;; We made sure to prime the value to prevent updates.
790  (when (eq box TOMBPRIME)
791  (return-from copy-slot T))
792  (setf oldval box)
793  (return))
794  ;; Retry on CAS failure
795  (setf oldval (val oldkvs idx)))
796  (when (eq oldval TOMBPRIME)
797  ;; We already completed the copy
798  (return-from copy-slot NIL))
799  ;; Finally do the actual copy, but only if we would write into
800  ;; a null. Otherwise, someone else already copied.
801  (let ((old-unboxed (prime-value oldval)))
802  (assert (not (eq old-unboxed TOMBSTONE)))
803  (prog1 (eq NO-VALUE (%put-if-match table newkvs key old-unboxed NO-VALUE))
804  ;; Now that the copy is done, we can stub out the old key completely.
805  (loop until (cas-val oldkvs idx oldval TOMBPRIME)
806  do (setf oldval (val oldkvs idx))))))))
807 
808 (defun mapchash (function table)
809  (let (snapshot-kvs)
810  (loop for top-kvs = (%castable-kvs table)
811  for top-chm = (chm top-kvs)
812  for newkvs = (%chm-newkvs top-chm)
813  until (null newkvs)
814  do (help-copy table newkvs)
815  finally (setf snapshot-kvs top-kvs))
816  (loop for position from 2 below (length snapshot-kvs) by 2
817  for key = (aref snapshot-kvs position)
818  for value = (aref snapshot-kvs (1+ position))
819  unless (or (eq key NO-VALUE)
820  (eq key TOMBSTONE)
821  (eq value NO-VALUE))
822  do (funcall function key value))))
823 
824 (defmethod print-object ((table castable) stream)
825  (print-unreadable-object (table stream :type t :identity t)
826  (format stream ":test ~s :count ~s :size ~s"
827  (castable-test table)
828  (castable-count table)
829  (castable-size table))))