5 ;;;; SB-EXT:COMPARE-AND-SWAP is the public API for now.
7 ;;;; Internally our interface has CAS, GET-CAS-EXPANSION, DEFINE-CAS-EXPANDER,
8 ;;;; DEFCAS, and #'(CAS ...) functions -- making things mostly isomorphic with
11 (defglobal **cas-expanders** (make-hash-table :test #'eq :synchronized t))
13 (define-function-name-syntax cas (list)
14 (destructuring-bind (cas symbol) list
18 ;;; This is what it all comes down to.
19 (def!macro cas (place old new &environment env)
20 "Synonym for COMPARE-AND-SWAP.
22 Addtionally DEFUN, DEFGENERIC, DEFMETHOD, FLET, and LABELS can be also used to
23 define CAS-functions analogously to SETF-functions:
27 (defun (cas foo) (old new)
28 (cas (symbol-value '*foo*) old new))
30 First argument of a CAS function is the expected old value, and the second
31 argument of is the new value. Note that the system provides no automatic
32 atomicity for CAS functions, nor can it verify that they are atomic: it is up
33 to the implementor of a CAS function to ensure its atomicity.
35 EXPERIMENTAL: Interface subject to change."
36 (multiple-value-bind (temps place-args old-temp new-temp cas-form)
37 (get-cas-expansion place env)
38 `(let* (,@(mapcar #'list temps place-args)
43 (defun get-cas-expansion (place &optional environment)
45 "Analogous to GET-SETF-EXPANSION. Returns the following six values:
47 * list of temporary variables
49 * list of value-forms whose results those variable must be bound
51 * temporary variable for the old value of PLACE
53 * temporary variable for the new value of PLACE
55 * form using the aforementioned temporaries which performs the
56 compare-and-swap operation on PLACE
58 * form using the aforementioned temporaries with which to perform a volatile
63 (get-cas-expansion '(car x))
64 ; => (#:CONS871), (X), #:OLD872, #:NEW873,
65 ; (SB-KERNEL:%COMPARE-AND-SWAP-CAR #:CONS871 #:OLD872 :NEW873).
68 (defmacro my-atomic-incf (place &optional (delta 1) &environment env)
69 (multiple-value-bind (vars vals old new cas-form read-form)
70 (get-cas-expansion place env)
71 (let ((delta-value (gensym \"DELTA\")))
72 `(let* (,@(mapcar 'list vars vals)
75 (,new (+ ,old ,delta-value)))
76 (loop until (eq ,old (setf ,old ,cas-form))
77 do (setf ,new (+ ,old ,delta-value)))
80 EXPERIMENTAL: Interface subject to change."
81 (flet ((invalid-place ()
82 (error "Invalid place to CAS: ~S" place)))
83 (let ((expanded (sb!xc:macroexpand place environment)))
84 (unless (consp expanded)
85 ;; FIXME: Allow (CAS *FOO* <OLD> <NEW>), maybe?
87 (let ((name (car expanded)))
88 (unless (symbolp name)
90 (let ((info (gethash name **cas-expanders**)))
94 (funcall info place environment))
97 ((setf info (info :function :structure-accessor name))
98 (expand-structure-slot-cas info name expanded))
102 (with-unique-names (old new)
106 (dolist (x (reverse (cdr expanded)))
107 (cond ((constantp x environment)
110 (let ((tmp (gensymify x)))
114 (values vars vals old new
115 `(funcall #'(cas ,name) ,old ,new ,@args)
116 `(,name ,@args)))))))))))
118 (defun expand-structure-slot-cas (dd name place)
119 (let* ((structure (dd-name dd))
120 (slotd (find name (dd-slots dd) :key #'dsd-accessor-name))
121 (index (dsd-index slotd))
122 (type (dsd-type slotd)))
123 (unless (eq t (dsd-raw-type slotd))
124 (error "Cannot use COMPARE-AND-SWAP with structure accessor ~
125 for a typed slot: ~S"
127 (when (dsd-read-only slotd)
128 (error "Cannot use COMPARE-AND-SWAP with structure accessor ~
129 for a read-only slot: ~S"
131 (destructuring-bind (op arg) place
133 (with-unique-names (instance old new)
134 (values (list instance)
138 `(truly-the (values ,type &optional)
139 (%compare-and-swap-instance-ref
140 (the ,structure ,instance)
144 `(,op ,instance))))))
146 (def!macro define-cas-expander (accessor lambda-list &body body)
147 "Analogous to DEFINE-SETF-EXPANDER. Defines a CAS-expansion for ACCESSOR.
148 BODY must return six values as specified in GET-CAS-EXPANSION.
150 Note that the system provides no automatic atomicity for CAS expansion, nor
151 can it verify that they are atomic: it is up to the implementor of a CAS
152 expansion to ensure its atomicity.
154 EXPERIMENTAL: Interface subject to change."
155 (with-unique-names (whole environment)
156 (multiple-value-bind (body decls doc)
157 (parse-defmacro lambda-list whole body accessor
159 :environment environment
161 `(eval-when (:compile-toplevel :load-toplevel :execute)
162 (setf (gethash ',accessor **cas-expanders**)
163 (lambda (,whole ,environment)
164 ,@(when doc (list doc))
168 (def!macro defcas (&whole form accessor lambda-list function
170 "Analogous to short-form DEFSETF. Defines FUNCTION as responsible
171 for compare-and-swap on places accessed using ACCESSOR. LAMBDA-LIST
172 must correspond to the lambda-list of the accessor.
174 Note that the system provides no automatic atomicity for CAS expansions
175 resulting from DEFCAS, nor can it verify that they are atomic: it is up to the
176 user of DEFCAS to ensure that the function specified is atomic.
178 EXPERIMENTAL: Interface subject to change."
179 (multiple-value-bind (reqs opts restp rest keyp keys allowp auxp)
180 (parse-lambda-list lambda-list)
181 (declare (ignore keys))
182 (when (or keyp allowp auxp)
183 (error "&KEY, &AUX, and &ALLOW-OTHER-KEYS not allowed in DEFCAS ~
184 lambda-list.~% ~S" form))
185 `(define-cas-expander ,accessor ,lambda-list
186 ,@(when docstring (list docstring))
187 (let ((temps (mapcar #'gensymify
189 (when restp (list (gensymify rest))))))
190 (args (list ,@(append reqs
192 (when restp (list rest)))))
194 (new (gensym "NEW")))
199 `(,',function ,@temps ,old ,new)
200 `(,',accessor ,@temps))))))
202 (def!macro compare-and-swap (place old new)
204 "Atomically stores NEW in PLACE if OLD matches the current value of PLACE.
205 Two values are considered to match if they are EQ. Returns the previous value
206 of PLACE: if the returned value is EQ to OLD, the swap was carried out.
208 PLACE must be an accessor form whose CAR is one of the following:
210 CAR, CDR, FIRST, REST, SVREF, SYMBOL-PLIST, SYMBOL-VALUE, SVREF, SLOT-VALUE
211 SB-MOP:STANDARD-INSTANCE-ACCESS, SB-MOP:FUNCALLABLE-STANDARD-INSTANCE-ACCESS,
213 or the name of a DEFSTRUCT created accessor for a slot whose declared type is
214 either FIXNUM or T. Results are unspecified if the slot has a declared type
215 other then FIXNUM or T.
217 In case of SLOT-VALUE, if the slot is unbound, SLOT-UNBOUND is called unless
218 OLD is EQ to SB-PCL:+SLOT-UNBOUND+ in which case SB-PCL:+SLOT-UNBOUND+ is
219 returned and NEW is assigned to the slot.
221 Additionally, the results are unspecified if there is an applicable method on
222 either SB-MOP:SLOT-VALUE-USING-CLASS, (SETF SB-MOP:SLOT-VALUE-USING-CLASS), or
223 SB-MOP:SLOT-BOUNDP-USING-CLASS.
225 EXPERIMENTAL: Interface subject to change."
226 `(cas ,place ,old ,new))
228 ;;; Out-of-line definitions for various primitive cas functions.
229 (macrolet ((def (name lambda-list ref &optional set)
230 #!+compare-and-swap-vops
231 (declare (ignore ref set))
232 `(defun ,name (,@lambda-list old new)
233 #!+compare-and-swap-vops
234 (,name ,@lambda-list old new)
235 #!-compare-and-swap-vops
238 ,(error "No COMPARE-AND-SWAP-VOPS on a threaded build?")
240 (let ((current (,ref ,@lambda-list)))
241 (when (eq current old)
243 `(,set ,@lambda-list new)
244 `(setf (,ref ,@lambda-list) new)))
246 (def %compare-and-swap-car (cons) car)
247 (def %compare-and-swap-cdr (cons) cdr)
248 (def %compare-and-swap-instance-ref (instance index) %instance-ref %instance-set)
249 (def %compare-and-swap-symbol-plist (symbol) symbol-plist)
250 (def %compare-and-swap-symbol-value (symbol) symbol-value)
251 (def %compare-and-swap-svref (vector index) svref))