Fix typos.
[sbcl.git] / src / code / cas.lisp
blob098e14f5619047cde59b926e6221f27d45956943
1 (in-package "SB!IMPL")
3 ;;;; COMPARE-AND-SWAP
4 ;;;;
5 ;;;; SB-EXT:COMPARE-AND-SWAP is the public API for now.
6 ;;;;
7 ;;;; Internally our interface has CAS, GET-CAS-EXPANSION, DEFINE-CAS-EXPANDER,
8 ;;;; DEFCAS, and #'(CAS ...) functions -- making things mostly isomorphic with
9 ;;;; SETF.
11 ;;; This is what it all comes down to.
12 (defmacro cas (place old new &environment env)
13 #!+sb-doc
14 "Synonym for COMPARE-AND-SWAP.
16 Additionally DEFUN, DEFGENERIC, DEFMETHOD, FLET, and LABELS can be also used to
17 define CAS-functions analogously to SETF-functions:
19 (defvar *foo* nil)
21 (defun (cas foo) (old new)
22 (cas (symbol-value '*foo*) old new))
24 First argument of a CAS function is the expected old value, and the second
25 argument of is the new value. Note that the system provides no automatic
26 atomicity for CAS functions, nor can it verify that they are atomic: it is up
27 to the implementor of a CAS function to ensure its atomicity.
29 EXPERIMENTAL: Interface subject to change."
30 (multiple-value-bind (temps place-args old-temp new-temp cas-form)
31 (get-cas-expansion place env)
32 `(let* (,@(mapcar #'list temps place-args)
33 (,old-temp ,old)
34 (,new-temp ,new))
35 ,cas-form)))
37 (eval-when (:compile-toplevel :load-toplevel :execute)
38 (defun get-cas-expansion (place &optional environment)
39 #!+sb-doc
40 "Analogous to GET-SETF-EXPANSION. Returns the following six values:
42 * list of temporary variables
44 * list of value-forms whose results those variable must be bound
46 * temporary variable for the old value of PLACE
48 * temporary variable for the new value of PLACE
50 * form using the aforementioned temporaries which performs the
51 compare-and-swap operation on PLACE
53 * form using the aforementioned temporaries with which to perform a volatile
54 read of PLACE
56 Example:
58 (get-cas-expansion '(car x))
59 ; => (#:CONS871), (X), #:OLD872, #:NEW873,
60 ; (SB-KERNEL:%COMPARE-AND-SWAP-CAR #:CONS871 #:OLD872 :NEW873).
61 ; (CAR #:CONS871)
63 (defmacro my-atomic-incf (place &optional (delta 1) &environment env)
64 (multiple-value-bind (vars vals old new cas-form read-form)
65 (get-cas-expansion place env)
66 (let ((delta-value (gensym \"DELTA\")))
67 `(let* (,@(mapcar 'list vars vals)
68 (,old ,read-form)
69 (,delta-value ,delta)
70 (,new (+ ,old ,delta-value)))
71 (loop until (eq ,old (setf ,old ,cas-form))
72 do (setf ,new (+ ,old ,delta-value)))
73 ,new))))
75 EXPERIMENTAL: Interface subject to change."
76 ;; FIXME: this seems wrong on two points:
77 ;; 1. if TRULY-THE had a CAS expander (which it doesn't) we'd want
78 ;; to use %MACROEXPAND[-1] so as not to lose the "truly-the"-ness
79 ;; 2. if both a CAS expander and a macro exist, the CAS expander
80 ;; should be preferred before macroexpanding (just like SETF does)
81 (let ((expanded (sb!xc:macroexpand place environment)))
82 (flet ((invalid-place ()
83 (error "Invalid place to CAS: ~S -> ~S" place expanded)))
84 (unless (consp expanded)
85 ;; FIXME: Allow (CAS *FOO* <OLD> <NEW>), maybe?
86 (invalid-place))
87 (let ((name (car expanded)))
88 (unless (symbolp name)
89 (invalid-place))
90 (acond
91 ((info :cas :expander name)
92 ;; CAS expander.
93 (funcall it expanded environment))
95 ;; Structure accessor
96 ((structure-instance-accessor-p name)
97 (expand-structure-slot-cas it name expanded))
99 ;; CAS function
101 (with-unique-names (old new)
102 (let ((vars nil)
103 (vals nil)
104 (args nil))
105 (dolist (x (reverse (cdr expanded)))
106 (cond ((sb!xc:constantp x environment)
107 (push x args))
109 (let ((tmp (gensymify x)))
110 (push tmp args)
111 (push tmp vars)
112 (push x vals)))))
113 (values vars vals old new
114 `(funcall #'(cas ,name) ,old ,new ,@args)
115 `(,name ,@args))))))))))
117 (defun expand-structure-slot-cas (info name place)
118 (let* ((dd (car info))
119 (structure (dd-name dd))
120 (slotd (cdr info))
121 (index (dsd-index slotd))
122 (type (dsd-type slotd))
123 (casser
124 (case (dsd-raw-type slotd)
125 ((t) '%instance-cas)
126 #!+(or x86 x86-64)
127 ((word) '%raw-instance-cas/word))))
128 (unless casser
129 (error "Cannot use COMPARE-AND-SWAP with structure accessor ~
130 for a typed slot: ~S"
131 place))
132 (when (dsd-read-only slotd)
133 (error "Cannot use COMPARE-AND-SWAP with structure accessor ~
134 for a read-only slot: ~S"
135 place))
136 (destructuring-bind (op arg) place
137 (aver (eq op name))
138 (with-unique-names (instance old new)
139 (values (list instance)
140 (list `(the ,structure ,arg))
143 `(truly-the (values ,type &optional)
144 (,casser ,instance ,index
145 (the ,type ,old)
146 (the ,type ,new)))
147 `(,op ,instance)))))))
149 (defmacro define-cas-expander (accessor lambda-list &body body)
150 #!+sb-doc
151 "Analogous to DEFINE-SETF-EXPANDER. Defines a CAS-expansion for ACCESSOR.
152 BODY must return six values as specified in GET-CAS-EXPANSION.
154 Note that the system provides no automatic atomicity for CAS expansion, nor
155 can it verify that they are atomic: it is up to the implementor of a CAS
156 expansion to ensure its atomicity.
158 EXPERIMENTAL: Interface subject to change."
159 `(eval-when (:compile-toplevel :load-toplevel :execute)
160 (setf (info :cas :expander ',accessor)
161 ,(make-macro-lambda `(cas-expand ,accessor) lambda-list body
162 'define-cas-expander accessor))))
164 ;; FIXME: this interface is bogus - short-form DEFSETF/CAS does not
165 ;; want a lambda-list. You just blindly substitute
166 ;; (CAS (PLACE arg1 ... argN) old new) -> (F arg1 ... argN old new).
167 ;; What role can this lambda-list have when there is no user-provided
168 ;; code to read the variables?
169 ;; And as mentioned no sbcl-devel, &REST is beyond bogus, it's broken.
171 (defmacro defcas (accessor lambda-list function &optional docstring)
172 #!+sb-doc
173 "Analogous to short-form DEFSETF. Defines FUNCTION as responsible
174 for compare-and-swap on places accessed using ACCESSOR. LAMBDA-LIST
175 must correspond to the lambda-list of the accessor.
177 Note that the system provides no automatic atomicity for CAS expansions
178 resulting from DEFCAS, nor can it verify that they are atomic: it is up to the
179 user of DEFCAS to ensure that the function specified is atomic.
181 EXPERIMENTAL: Interface subject to change."
182 (multiple-value-bind (llks reqs opts rest)
183 (parse-lambda-list lambda-list
184 :accept (lambda-list-keyword-mask '(&optional &rest))
185 :context "a DEFCAS lambda-list")
186 (declare (ignore llks))
187 `(define-cas-expander ,accessor ,lambda-list
188 ,@(when docstring (list docstring))
189 ;; FIXME: if a &REST arg is present, this is really weird.
190 (let ((temps (mapcar #'gensymify ',(append reqs opts rest)))
191 (args (list ,@(append reqs opts rest)))
192 (old (gensym "OLD"))
193 (new (gensym "NEW")))
194 (values temps
195 args
198 `(,',function ,@temps ,old ,new)
199 `(,',accessor ,@temps))))))
201 (defmacro compare-and-swap (place old new)
202 #!+sb-doc
203 "Atomically stores NEW in PLACE if OLD matches the current value of PLACE.
204 Two values are considered to match if they are EQ. Returns the previous value
205 of PLACE: if the returned value is EQ to OLD, the swap was carried out.
207 PLACE must be an CAS-able place. Built-in CAS-able places are accessor forms
208 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 than 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. Additionally, the results are
220 unspecified if there is an applicable method on either
221 SB-MOP:SLOT-VALUE-USING-CLASS, (SETF SB-MOP:SLOT-VALUE-USING-CLASS), or
222 SB-MOP:SLOT-BOUNDP-USING-CLASS.
224 Additionally, the PLACE can be a anything for which a CAS-expansion has been
225 specified using DEFCAS, DEFINE-CAS-EXPANDER, or for which a CAS-function has
226 been defined. (See SB-EXT:CAS for more information.)
228 `(cas ,place ,old ,new))
230 (define-cas-expander symbol-value (name &environment env)
231 (multiple-value-bind (tmp val cname)
232 (if (sb!xc:constantp name env)
233 (values nil nil (constant-form-value name env))
234 (values (gensymify name) name nil))
235 (let ((symbol (or tmp `',cname)))
236 (with-unique-names (old new)
237 (values (when tmp (list tmp))
238 (when val (list val))
241 (let ((slow
242 `(progn
243 (about-to-modify-symbol-value ,symbol 'compare-and-swap ,new)
244 (%compare-and-swap-symbol-value ,symbol ,old ,new))))
245 (if cname
246 (if (member (info :variable :kind cname) '(:special :global))
247 ;; We can generate the type-check reasonably.
248 `(%compare-and-swap-symbol-value
249 ',cname ,old (the ,(info :variable :type cname) ,new))
250 slow)
251 slow))
252 `(symbol-value ,symbol))))))
254 (define-cas-expander svref (vector index)
255 (with-unique-names (v i old new)
256 (values (list v i)
257 (list vector index)
260 `(locally (declare (simple-vector ,v))
261 (%compare-and-swap-svref ,v (check-bound ,v (length ,v) ,i) ,old ,new))
262 `(svref ,v ,i))))
264 ;;;; ATOMIC-INCF and ATOMIC-DECF
266 (eval-when (:compile-toplevel :load-toplevel :execute)
267 (defun expand-atomic-frob
268 (name specified-place diff env
269 &aux (place (sb!xc:macroexpand specified-place env)))
270 (declare (type (member atomic-incf atomic-decf) name))
271 (flet ((invalid-place ()
272 (error "Invalid first argument to ~S: ~S" name specified-place))
273 (compute-newval (old) ; used only if no atomic inc vop
274 `(logand (,(case name (atomic-incf '+) (atomic-decf '-)) ,old
275 (the sb!vm:signed-word ,diff)) sb!ext:most-positive-word))
276 (compute-delta () ; used only with atomic inc vop
277 `(logand ,(case name
278 (atomic-incf `(the sb!vm:signed-word ,diff))
279 (atomic-decf `(- (the sb!vm:signed-word ,diff))))
280 sb!ext:most-positive-word)))
281 (declare (ignorable #'compute-newval #'compute-delta))
282 (when (and (symbolp place)
283 (eq (info :variable :kind place) :global)
284 (type= (info :variable :type place) (specifier-type 'fixnum)))
285 ;; Global can't be lexically rebound.
286 (return-from expand-atomic-frob
287 `(truly-the fixnum (,(case name
288 (atomic-incf '%atomic-inc-symbol-global-value)
289 (atomic-decf '%atomic-dec-symbol-global-value))
290 ',place (the fixnum ,diff)))))
291 (unless (consp place) (invalid-place))
292 (destructuring-bind (op . args) place
293 ;; FIXME: The lexical environment should not be disregarded.
294 ;; CL builtins can't be lexically rebound, but structure accessors can.
295 (case op
296 (aref
297 (unless (singleton-p (cdr args))
298 (invalid-place))
299 (with-unique-names (array)
300 `(let ((,array (the (simple-array word (*)) ,(car args))))
301 #!+compare-and-swap-vops
302 (%array-atomic-incf/word
303 ,array
304 (check-bound ,array (array-dimension ,array 0) ,(cadr args))
305 ,(compute-delta))
306 #!-compare-and-swap-vops
307 ,(with-unique-names (index old-value)
308 `(without-interrupts
309 (let* ((,index ,(cadr args))
310 (,old-value (aref ,array ,index)))
311 (setf (aref ,array ,index) ,(compute-newval old-value))
312 ,old-value))))))
313 ((car cdr first rest)
314 (when (cdr args)
315 (invalid-place))
316 `(truly-the
317 fixnum
318 (,(case op
319 ((first car) (case name
320 (atomic-incf '%atomic-inc-car)
321 (atomic-decf '%atomic-dec-car)))
322 ((rest cdr) (case name
323 (atomic-incf '%atomic-inc-cdr)
324 (atomic-decf '%atomic-dec-cdr))))
325 ,(car args) (the fixnum ,diff))))
327 (when (or (cdr args)
328 ;; Because accessor info is identical for the writer and reader
329 ;; functions, without a SYMBOLP check this would erroneously allow
330 ;; (ATOMIC-INCF ((SETF STRUCT-SLOT) x))
331 (not (symbolp op))
332 (not (structure-instance-accessor-p op)))
333 (invalid-place))
334 (let* ((accessor-info (structure-instance-accessor-p op))
335 (slotd (cdr accessor-info))
336 (type (dsd-type slotd)))
337 (unless (and (eq 'sb!vm:word (dsd-raw-type slotd))
338 (type= (specifier-type type) (specifier-type 'sb!vm:word)))
339 (error "~S requires a slot of type (UNSIGNED-BYTE ~S), not ~S: ~S"
340 name sb!vm:n-word-bits type place))
341 (when (dsd-read-only slotd)
342 (error "Cannot use ~S with structure accessor for a read-only slot: ~S"
343 name place))
344 #!+compare-and-swap-vops
345 `(truly-the sb!vm:word
346 (%raw-instance-atomic-incf/word
347 (the ,(dd-name (car accessor-info)) ,@args)
348 ,(dsd-index slotd)
349 ,(compute-delta)))
350 #!-compare-and-swap-vops
351 (with-unique-names (structure old-value)
352 `(without-interrupts
353 (let* ((,structure ,@args)
354 (,old-value (,op ,structure)))
355 (setf (,op ,structure) ,(compute-newval old-value))
356 ,old-value))))))))))
358 (defmacro atomic-incf (&environment env place &optional (diff 1))
359 #!+sb-doc
360 #.(format nil
361 "Atomically increments PLACE by DIFF, and returns the value of PLACE before
362 the increment.
364 PLACE must access one of the following:
365 - a DEFSTRUCT slot with declared type (UNSIGNED-BYTE ~D~:*)
366 or AREF of a (SIMPLE-ARRAY (UNSIGNED-BYTE ~D~:*) (*))
367 The type SB-EXT:WORD can be used for these purposes.
368 - CAR or CDR (respectively FIRST or REST) of a CONS.
369 - a variable defined using DEFGLOBAL with a proclaimed type of FIXNUM.
370 Macroexpansion is performed on PLACE before expanding ATOMIC-INCF.
372 Incrementing is done using modular arithmetic,
373 which is well-defined over two different domains:
374 - For structures and arrays, the operation accepts and produces
375 an (UNSIGNED-BYTE ~D~:*), and DIFF must be of type (SIGNED-BYTE ~D).
376 ATOMIC-INCF of #x~x by one results in #x0 being stored in PLACE.
377 - For other places, the domain is FIXNUM, and DIFF must be a FIXNUM.
378 ATOMIC-INCF of #x~x by one results in #x~x
379 being stored in PLACE.
381 DIFF defaults to 1.
383 EXPERIMENTAL: Interface subject to change."
384 sb!vm:n-word-bits most-positive-word
385 sb!xc:most-positive-fixnum sb!xc:most-negative-fixnum)
386 (expand-atomic-frob 'atomic-incf place diff env))
388 (defmacro atomic-decf (&environment env place &optional (diff 1))
389 #!+sb-doc
390 #.(format nil
391 "Atomically decrements PLACE by DIFF, and returns the value of PLACE before
392 the decrement.
394 PLACE must access one of the following:
395 - a DEFSTRUCT slot with declared type (UNSIGNED-BYTE ~D~:*)
396 or AREF of a (SIMPLE-ARRAY (UNSIGNED-BYTE ~D~:*) (*))
397 The type SB-EXT:WORD can be used for these purposes.
398 - CAR or CDR (respectively FIRST or REST) of a CONS.
399 - a variable defined using DEFGLOBAL with a proclaimed type of FIXNUM.
400 Macroexpansion is performed on PLACE before expanding ATOMIC-DECF.
402 Decrementing is done using modular arithmetic,
403 which is well-defined over two different domains:
404 - For structures and arrays, the operation accepts and produces
405 an (UNSIGNED-BYTE ~D~:*), and DIFF must be of type (SIGNED-BYTE ~D).
406 ATOMIC-DECF of #x0 by one results in #x~x being stored in PLACE.
407 - For other places, the domain is FIXNUM, and DIFF must be a FIXNUM.
408 ATOMIC-DECF of #x~x by one results in #x~x
409 being stored in PLACE.
411 DIFF defaults to 1.
413 EXPERIMENTAL: Interface subject to change."
414 sb!vm:n-word-bits most-positive-word
415 sb!xc:most-negative-fixnum sb!xc:most-positive-fixnum)
416 (expand-atomic-frob 'atomic-decf place diff env))
418 ;; Interpreter stubs for ATOMIC-INCF.
419 #!+(and compare-and-swap-vops (host-feature sb-xc))
420 (progn
421 ;; argument types are declared in vm-fndb
422 (defun %array-atomic-incf/word (array index diff)
423 (%array-atomic-incf/word array index diff))
424 (defun %raw-instance-atomic-incf/word (instance index diff)
425 (%raw-instance-atomic-incf/word instance index diff)))