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 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
12 (defun expand-structure-slot-cas (info name place
)
13 (let* ((dd (car info
))
14 (structure (dd-name dd
))
16 (index (dsd-index slotd
))
17 (type (dsd-type slotd
))
19 (case (dsd-raw-type slotd
)
22 ((word) '%raw-instance-cas
/word
))))
24 (error "Cannot use COMPARE-AND-SWAP with structure accessor ~
27 (when (dsd-read-only slotd
)
28 (error "Cannot use COMPARE-AND-SWAP with structure accessor ~
29 for a read-only slot: ~S"
31 (destructuring-bind (op arg
) place
33 (with-unique-names (instance old new
)
34 (values (list instance
)
35 (list `(the ,structure
,arg
))
38 `(truly-the (values ,type
&optional
)
39 (,casser
,instance
,index
44 ;;; FIXME: the generated code for CAS on a defglobal contains a
45 ;;; use of UNBOUND-SYMBOL-ERROR. This is is not unique to CAS, viz.:
46 ;;; * (defglobal *ggg* 3)
47 ;;; * (disassemble '(lambda () (symbol-value '*ggg*)))
49 ;;; ; 98: L0: CC0A BREAK 10 ; error trap
50 ;;; ; 9A: 06 BYTE #X06 ; UNBOUND-SYMBOL-ERROR
51 ;;; ; 9B: 00 BYTE #X00 ; RAX
52 ;;; whereas evaluating the atom *GGG* is perfectly fine.
53 ;;; In fact not only is the unbound check emitted, so is the TLS value check,
54 ;;; but there's no CAS expander for SYMBOL-GLOBAL-VALUE, so we can't
55 ;;; macroexpand ATOMIC-PUSH into (CAS (SYMBOL-GLOBAL-VALUE ...))
57 (defun get-cas-expansion (place &optional environment
)
58 "Analogous to GET-SETF-EXPANSION. Returns the following six values:
60 * list of temporary variables
62 * list of value-forms whose results those variable must be bound
64 * temporary variable for the old value of PLACE
66 * temporary variable for the new value of PLACE
68 * form using the aforementioned temporaries which performs the
69 compare-and-swap operation on PLACE
71 * form using the aforementioned temporaries with which to perform a volatile
76 (get-cas-expansion '(car x))
77 ; => (#:CONS871), (X), #:OLD872, #:NEW873,
78 ; (SB-KERNEL:%COMPARE-AND-SWAP-CAR #:CONS871 #:OLD872 :NEW873).
81 (defmacro my-atomic-incf (place &optional (delta 1) &environment env)
82 (multiple-value-bind (vars vals old new cas-form read-form)
83 (get-cas-expansion place env)
84 (let ((delta-value (gensym \"DELTA\")))
85 `(let* (,@(mapcar 'list vars vals)
88 (,new (+ ,old ,delta-value)))
89 (loop until (eq ,old (setf ,old ,cas-form))
90 do (setf ,new (+ ,old ,delta-value)))
93 EXPERIMENTAL: Interface subject to change."
94 ;; FIXME: this seems wrong on two points:
95 ;; 1. if TRULY-THE had a CAS expander (which it doesn't) we'd want
96 ;; to use %MACROEXPAND[-1] so as not to lose the "truly-the"-ness
97 ;; 2. if both a CAS expander and a macro exist, the CAS expander
98 ;; should be preferred before macroexpanding (just like SETF does)
99 (let ((expanded (sb!xc
:macroexpand place environment
)))
100 (flet ((invalid-place ()
101 (error "Invalid place to CAS: ~S -> ~S" place expanded
)))
102 (unless (consp expanded
)
103 (cond ((and (symbolp expanded
)
104 (member (info :variable
:kind expanded
)
105 '(:global
:special
)))
106 (setq expanded
`(symbol-value ',expanded
)))
109 (let ((name (car expanded
)))
110 (unless (symbolp name
)
113 ((info :cas
:expander name
)
115 (funcall it expanded environment
))
117 ;; Structure accessor
118 ((structure-instance-accessor-p name
)
119 (expand-structure-slot-cas it name expanded
))
123 (with-unique-names (old new
)
127 (dolist (x (reverse (cdr expanded
)))
128 (cond ((sb!xc
:constantp x environment
)
131 (let ((tmp (gensymify x
)))
135 (values vars vals old new
136 `(funcall #'(cas ,name
) ,old
,new
,@args
)
137 `(,name
,@args
))))))))))
140 ;;; This is what it all comes down to.
141 (defmacro cas
(place old new
&environment env
)
142 "Synonym for COMPARE-AND-SWAP.
144 Additionally DEFUN, DEFGENERIC, DEFMETHOD, FLET, and LABELS can be also used to
145 define CAS-functions analogously to SETF-functions:
149 (defun (cas foo) (old new)
150 (cas (symbol-value '*foo*) old new))
152 First argument of a CAS function is the expected old value, and the second
153 argument of is the new value. Note that the system provides no automatic
154 atomicity for CAS functions, nor can it verify that they are atomic: it is up
155 to the implementor of a CAS function to ensure its atomicity.
157 EXPERIMENTAL: Interface subject to change."
158 (multiple-value-bind (temps place-args old-temp new-temp cas-form
)
159 (get-cas-expansion place env
)
160 `(let* (,@(mapcar #'list temps place-args
)
165 (defmacro define-cas-expander
(accessor lambda-list
&body body
)
166 "Analogous to DEFINE-SETF-EXPANDER. Defines a CAS-expansion for ACCESSOR.
167 BODY must return six values as specified in GET-CAS-EXPANSION.
169 Note that the system provides no automatic atomicity for CAS expansion, nor
170 can it verify that they are atomic: it is up to the implementor of a CAS
171 expansion to ensure its atomicity.
173 EXPERIMENTAL: Interface subject to change."
174 `(eval-when (:compile-toplevel
:load-toplevel
:execute
)
175 (setf (info :cas
:expander
',accessor
)
176 ,(make-macro-lambda `(cas-expand ,accessor
) lambda-list body
177 'define-cas-expander accessor
))))
179 ;; FIXME: this interface is bogus - short-form DEFSETF/CAS does not
180 ;; want a lambda-list. You just blindly substitute
181 ;; (CAS (PLACE arg1 ... argN) old new) -> (F arg1 ... argN old new).
182 ;; What role can this lambda-list have when there is no user-provided
183 ;; code to read the variables?
184 ;; And as mentioned no sbcl-devel, &REST is beyond bogus, it's broken.
186 (defmacro defcas
(accessor lambda-list function
&optional docstring
)
187 "Analogous to short-form DEFSETF. Defines FUNCTION as responsible
188 for compare-and-swap on places accessed using ACCESSOR. LAMBDA-LIST
189 must correspond to the lambda-list of the accessor.
191 Note that the system provides no automatic atomicity for CAS expansions
192 resulting from DEFCAS, nor can it verify that they are atomic: it is up to the
193 user of DEFCAS to ensure that the function specified is atomic.
195 EXPERIMENTAL: Interface subject to change."
196 (multiple-value-bind (llks reqs opts rest
)
197 (parse-lambda-list lambda-list
198 :accept
(lambda-list-keyword-mask '(&optional
&rest
))
199 :context
"a DEFCAS lambda-list")
200 (declare (ignore llks
))
201 `(define-cas-expander ,accessor
,lambda-list
202 ,@(when docstring
(list docstring
))
203 ;; FIXME: if a &REST arg is present, this is really weird.
204 (let ((temps (mapcar #'gensymify
',(append reqs opts rest
)))
205 (args (list ,@(append reqs opts rest
)))
207 (new (gensym "NEW")))
212 `(,',function
,@temps
,old
,new
)
213 `(,',accessor
,@temps
))))))
215 (defmacro compare-and-swap
(place old new
)
216 "Atomically stores NEW in PLACE if OLD matches the current value of PLACE.
217 Two values are considered to match if they are EQ. Returns the previous value
218 of PLACE: if the returned value is EQ to OLD, the swap was carried out.
220 PLACE must be an CAS-able place. Built-in CAS-able places are accessor forms
221 whose CAR is one of the following:
223 CAR, CDR, FIRST, REST, SVREF, SYMBOL-PLIST, SYMBOL-VALUE, SVREF, SLOT-VALUE
224 SB-MOP:STANDARD-INSTANCE-ACCESS, SB-MOP:FUNCALLABLE-STANDARD-INSTANCE-ACCESS,
226 or the name of a DEFSTRUCT created accessor for a slot whose declared type is
227 either FIXNUM or T. Results are unspecified if the slot has a declared type
228 other than FIXNUM or T.
230 In case of SLOT-VALUE, if the slot is unbound, SLOT-UNBOUND is called unless
231 OLD is EQ to SB-PCL:+SLOT-UNBOUND+ in which case SB-PCL:+SLOT-UNBOUND+ is
232 returned and NEW is assigned to the slot. Additionally, the results are
233 unspecified if there is an applicable method on either
234 SB-MOP:SLOT-VALUE-USING-CLASS, (SETF SB-MOP:SLOT-VALUE-USING-CLASS), or
235 SB-MOP:SLOT-BOUNDP-USING-CLASS.
237 Additionally, the PLACE can be a anything for which a CAS-expansion has been
238 specified using DEFCAS, DEFINE-CAS-EXPANDER, or for which a CAS-function has
239 been defined. (See SB-EXT:CAS for more information.)
241 `(cas ,place
,old
,new
))
243 (define-cas-expander symbol-value
(name &environment env
)
244 (multiple-value-bind (tmp val cname
)
245 (if (sb!xc
:constantp name env
)
246 (values nil nil
(constant-form-value name env
))
247 (values (gensymify name
) name nil
))
248 (let ((symbol (or tmp
`',cname
)))
249 (with-unique-names (old new
)
250 (values (when tmp
(list tmp
))
251 (when val
(list val
))
256 (about-to-modify-symbol-value ,symbol
'compare-and-swap
,new
)
257 (%compare-and-swap-symbol-value
,symbol
,old
,new
))))
259 (if (member (info :variable
:kind cname
) '(:special
:global
))
260 ;; We can generate the type-check reasonably.
261 `(%compare-and-swap-symbol-value
262 ',cname
,old
(the ,(info :variable
:type cname
) ,new
))
265 `(symbol-value ,symbol
))))))
267 (define-cas-expander svref
(vector index
)
268 (with-unique-names (v i old new
)
273 `(locally (declare (simple-vector ,v
))
274 (%compare-and-swap-svref
,v
(check-bound ,v
(length ,v
) ,i
) ,old
,new
))
277 ;;;; ATOMIC-INCF and ATOMIC-DECF
279 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
280 (defun expand-atomic-frob
281 (name specified-place diff env
282 &aux
(place (sb!xc
:macroexpand specified-place env
)))
283 (declare (type (member atomic-incf atomic-decf
) name
))
284 (flet ((invalid-place ()
285 (error "Invalid first argument to ~S: ~S" name specified-place
))
286 (compute-newval (old) ; used only if no atomic inc vop
287 `(logand (,(case name
(atomic-incf '+) (atomic-decf '-
)) ,old
288 (the sb
!vm
:signed-word
,diff
)) sb
!ext
:most-positive-word
))
289 (compute-delta () ; used only with atomic inc vop
291 (atomic-incf `(the sb
!vm
:signed-word
,diff
))
292 (atomic-decf `(- (the sb
!vm
:signed-word
,diff
))))
293 sb
!ext
:most-positive-word
)))
294 (declare (ignorable #'compute-newval
#'compute-delta
))
295 (when (and (symbolp place
)
296 (eq (info :variable
:kind place
) :global
)
297 (type= (info :variable
:type place
) (specifier-type 'fixnum
)))
298 ;; Global can't be lexically rebound.
299 (return-from expand-atomic-frob
300 `(truly-the fixnum
(,(case name
301 (atomic-incf '%atomic-inc-symbol-global-value
)
302 (atomic-decf '%atomic-dec-symbol-global-value
))
303 ',place
(the fixnum
,diff
)))))
304 (unless (consp place
) (invalid-place))
305 (destructuring-bind (op . args
) place
306 ;; FIXME: The lexical environment should not be disregarded.
307 ;; CL builtins can't be lexically rebound, but structure accessors can.
310 (unless (singleton-p (cdr args
))
312 (with-unique-names (array)
313 `(let ((,array
(the (simple-array word
(*)) ,(car args
))))
314 #!+compare-and-swap-vops
315 (%array-atomic-incf
/word
317 (check-bound ,array
(array-dimension ,array
0) ,(cadr args
))
319 #!-compare-and-swap-vops
320 ,(with-unique-names (index old-value
)
322 (let* ((,index
,(cadr args
))
323 (,old-value
(aref ,array
,index
)))
324 (setf (aref ,array
,index
) ,(compute-newval old-value
))
326 ((car cdr first rest
)
332 ((first car
) (case name
333 (atomic-incf '%atomic-inc-car
)
334 (atomic-decf '%atomic-dec-car
)))
335 ((rest cdr
) (case name
336 (atomic-incf '%atomic-inc-cdr
)
337 (atomic-decf '%atomic-dec-cdr
))))
338 ,(car args
) (the fixnum
,diff
))))
341 ;; Because accessor info is identical for the writer and reader
342 ;; functions, without a SYMBOLP check this would erroneously allow
343 ;; (ATOMIC-INCF ((SETF STRUCT-SLOT) x))
345 (not (structure-instance-accessor-p op
)))
347 (let* ((accessor-info (structure-instance-accessor-p op
))
348 (slotd (cdr accessor-info
))
349 (type (dsd-type slotd
)))
350 (unless (and (eq 'sb
!vm
:word
(dsd-raw-type slotd
))
351 (type= (specifier-type type
) (specifier-type 'sb
!vm
:word
)))
352 (error "~S requires a slot of type (UNSIGNED-BYTE ~S), not ~S: ~S"
353 name sb
!vm
:n-word-bits type place
))
354 (when (dsd-read-only slotd
)
355 (error "Cannot use ~S with structure accessor for a read-only slot: ~S"
357 #!+compare-and-swap-vops
358 `(truly-the sb
!vm
:word
359 (%raw-instance-atomic-incf
/word
360 (the ,(dd-name (car accessor-info
)) ,@args
)
363 #!-compare-and-swap-vops
364 (with-unique-names (structure old-value
)
366 (let* ((,structure
,@args
)
367 (,old-value
(,op
,structure
)))
368 (setf (,op
,structure
) ,(compute-newval old-value
))
371 (defmacro atomic-incf
(&environment env place
&optional
(diff 1))
373 "Atomically increments PLACE by DIFF, and returns the value of PLACE before
376 PLACE must access one of the following:
377 - a DEFSTRUCT slot with declared type (UNSIGNED-BYTE ~D~:*)
378 or AREF of a (SIMPLE-ARRAY (UNSIGNED-BYTE ~D~:*) (*))
379 The type SB-EXT:WORD can be used for these purposes.
380 - CAR or CDR (respectively FIRST or REST) of a CONS.
381 - a variable defined using DEFGLOBAL with a proclaimed type of FIXNUM.
382 Macroexpansion is performed on PLACE before expanding ATOMIC-INCF.
384 Incrementing is done using modular arithmetic,
385 which is well-defined over two different domains:
386 - For structures and arrays, the operation accepts and produces
387 an (UNSIGNED-BYTE ~D~:*), and DIFF must be of type (SIGNED-BYTE ~D).
388 ATOMIC-INCF of #x~x by one results in #x0 being stored in PLACE.
389 - For other places, the domain is FIXNUM, and DIFF must be a FIXNUM.
390 ATOMIC-INCF of #x~x by one results in #x~x
391 being stored in PLACE.
395 EXPERIMENTAL: Interface subject to change."
396 sb
!vm
:n-word-bits most-positive-word
397 sb
!xc
:most-positive-fixnum sb
!xc
:most-negative-fixnum
)
398 (expand-atomic-frob 'atomic-incf place diff env
))
400 (defmacro atomic-decf
(&environment env place
&optional
(diff 1))
402 "Atomically decrements PLACE by DIFF, and returns the value of PLACE before
405 PLACE must access one of the following:
406 - a DEFSTRUCT slot with declared type (UNSIGNED-BYTE ~D~:*)
407 or AREF of a (SIMPLE-ARRAY (UNSIGNED-BYTE ~D~:*) (*))
408 The type SB-EXT:WORD can be used for these purposes.
409 - CAR or CDR (respectively FIRST or REST) of a CONS.
410 - a variable defined using DEFGLOBAL with a proclaimed type of FIXNUM.
411 Macroexpansion is performed on PLACE before expanding ATOMIC-DECF.
413 Decrementing is done using modular arithmetic,
414 which is well-defined over two different domains:
415 - For structures and arrays, the operation accepts and produces
416 an (UNSIGNED-BYTE ~D~:*), and DIFF must be of type (SIGNED-BYTE ~D).
417 ATOMIC-DECF of #x0 by one results in #x~x being stored in PLACE.
418 - For other places, the domain is FIXNUM, and DIFF must be a FIXNUM.
419 ATOMIC-DECF of #x~x by one results in #x~x
420 being stored in PLACE.
424 EXPERIMENTAL: Interface subject to change."
425 sb
!vm
:n-word-bits most-positive-word
426 sb
!xc
:most-negative-fixnum sb
!xc
:most-positive-fixnum
)
427 (expand-atomic-frob 'atomic-decf place diff env
))
429 ;; Interpreter stubs for ATOMIC-INCF.
430 #!+(and compare-and-swap-vops
(host-feature sb-xc
))
432 ;; argument types are declared in vm-fndb
433 (defun %array-atomic-incf
/word
(array index diff
)
434 (%array-atomic-incf
/word array index diff
))
435 (defun %raw-instance-atomic-incf
/word
(instance index diff
)
436 (%raw-instance-atomic-incf
/word instance index diff
)))