Enable conditionalized definition of %RAW-INSTANCE-CAS/WORD on x86
[sbcl.git] / src / code / cas.lisp
blob234b25280ea0203d8135df84370172876cfc2674
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 (def!macro 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 (defun get-cas-expansion (place &optional environment)
38 #!+sb-doc
39 "Analogous to GET-SETF-EXPANSION. Returns the following six values:
41 * list of temporary variables
43 * list of value-forms whose results those variable must be bound
45 * temporary variable for the old value of PLACE
47 * temporary variable for the new value of PLACE
49 * form using the aforementioned temporaries which performs the
50 compare-and-swap operation on PLACE
52 * form using the aforementioned temporaries with which to perform a volatile
53 read of PLACE
55 Example:
57 (get-cas-expansion '(car x))
58 ; => (#:CONS871), (X), #:OLD872, #:NEW873,
59 ; (SB-KERNEL:%COMPARE-AND-SWAP-CAR #:CONS871 #:OLD872 :NEW873).
60 ; (CAR #:CONS871)
62 (defmacro my-atomic-incf (place &optional (delta 1) &environment env)
63 (multiple-value-bind (vars vals old new cas-form read-form)
64 (get-cas-expansion place env)
65 (let ((delta-value (gensym \"DELTA\")))
66 `(let* (,@(mapcar 'list vars vals)
67 (,old ,read-form)
68 (,delta-value ,delta)
69 (,new (+ ,old ,delta-value)))
70 (loop until (eq ,old (setf ,old ,cas-form))
71 do (setf ,new (+ ,old ,delta-value)))
72 ,new))))
74 EXPERIMENTAL: Interface subject to change."
75 ;; FIXME: this seems wrong on two points:
76 ;; 1. if TRULY-THE had a CAS expander (which it doesn't) we'd want
77 ;; to use %MACROEXPAND[-1] so as not to lose the "truly-the"-ness
78 ;; 2. if both a CAS expander and a macro exist, the CAS expander
79 ;; should be preferred before macroexpanding (just like SETF does)
80 (let ((expanded (sb!xc:macroexpand place environment)))
81 (flet ((invalid-place ()
82 (error "Invalid place to CAS: ~S -> ~S" place expanded)))
83 (unless (consp expanded)
84 ;; FIXME: Allow (CAS *FOO* <OLD> <NEW>), maybe?
85 (invalid-place))
86 (let ((name (car expanded)))
87 (unless (symbolp name)
88 (invalid-place))
89 (acond
90 ((info :cas :expander name)
91 ;; CAS expander.
92 (funcall it expanded environment))
94 ;; Structure accessor
95 ((structure-instance-accessor-p name)
96 (expand-structure-slot-cas it name expanded))
98 ;; CAS function
100 (with-unique-names (old new)
101 (let ((vars nil)
102 (vals nil)
103 (args nil))
104 (dolist (x (reverse (cdr expanded)))
105 (cond ((constantp x environment)
106 (push x args))
108 (let ((tmp (gensymify x)))
109 (push tmp args)
110 (push tmp vars)
111 (push x vals)))))
112 (values vars vals old new
113 `(funcall #'(cas ,name) ,old ,new ,@args)
114 `(,name ,@args))))))))))
116 (defun expand-structure-slot-cas (info name place)
117 (let* ((dd (car info))
118 (structure (dd-name dd))
119 (slotd (cdr info))
120 (index (dsd-index slotd))
121 (type (dsd-type slotd))
122 (casser
123 (case (dsd-raw-type slotd)
124 ((t) '%instance-cas)
125 #!+(or x86 x86-64)
126 ((word) '%raw-instance-cas/word))))
127 (unless casser
128 (error "Cannot use COMPARE-AND-SWAP with structure accessor ~
129 for a typed slot: ~S"
130 place))
131 (when (dsd-read-only slotd)
132 (error "Cannot use COMPARE-AND-SWAP with structure accessor ~
133 for a read-only slot: ~S"
134 place))
135 (destructuring-bind (op arg) place
136 (aver (eq op name))
137 (with-unique-names (instance old new)
138 (values (list instance)
139 (list `(the ,structure ,arg))
142 `(truly-the (values ,type &optional)
143 (,casser ,instance ,index
144 (the ,type ,old)
145 (the ,type ,new)))
146 `(,op ,instance))))))
148 (def!macro define-cas-expander (accessor lambda-list &body body)
149 #!+sb-doc
150 "Analogous to DEFINE-SETF-EXPANDER. Defines a CAS-expansion for ACCESSOR.
151 BODY must return six values as specified in GET-CAS-EXPANSION.
153 Note that the system provides no automatic atomicity for CAS expansion, nor
154 can it verify that they are atomic: it is up to the implementor of a CAS
155 expansion to ensure its atomicity.
157 EXPERIMENTAL: Interface subject to change."
158 `(eval-when (:compile-toplevel :load-toplevel :execute)
159 (setf (info :cas :expander ',accessor)
160 ,(make-macro-lambda `(cas-expand ,accessor) lambda-list body
161 'define-cas-expander accessor))))
163 ;; FIXME: this interface is bogus - short-form DEFSETF/CAS does not
164 ;; want a lambda-list. You just blindly substitute
165 ;; (CAS (PLACE arg1 ... argN) old new) -> (F arg1 ... argN old new).
166 ;; What role can this lambda-list have when there is no user-provided
167 ;; code to read the variables?
168 ;; And as mentioned no sbcl-devel, &REST is beyond bogus, it's broken.
170 (def!macro defcas (accessor lambda-list function &optional docstring)
171 #!+sb-doc
172 "Analogous to short-form DEFSETF. Defines FUNCTION as responsible
173 for compare-and-swap on places accessed using ACCESSOR. LAMBDA-LIST
174 must correspond to the lambda-list of the accessor.
176 Note that the system provides no automatic atomicity for CAS expansions
177 resulting from DEFCAS, nor can it verify that they are atomic: it is up to the
178 user of DEFCAS to ensure that the function specified is atomic.
180 EXPERIMENTAL: Interface subject to change."
181 (multiple-value-bind (llks reqs opts rest)
182 (parse-lambda-list lambda-list
183 :accept (lambda-list-keyword-mask '(&optional &rest))
184 :context "a DEFCAS lambda-list")
185 (declare (ignore llks))
186 `(define-cas-expander ,accessor ,lambda-list
187 ,@(when docstring (list docstring))
188 ;; FIXME: if a &REST arg is present, this is really weird.
189 (let ((temps (mapcar #'gensymify ',(append reqs opts rest)))
190 (args (list ,@(append reqs opts rest)))
191 (old (gensym "OLD"))
192 (new (gensym "NEW")))
193 (values temps
194 args
197 `(,',function ,@temps ,old ,new)
198 `(,',accessor ,@temps))))))
200 (def!macro compare-and-swap (place old new)
201 #!+sb-doc
202 "Atomically stores NEW in PLACE if OLD matches the current value of PLACE.
203 Two values are considered to match if they are EQ. Returns the previous value
204 of PLACE: if the returned value is EQ to OLD, the swap was carried out.
206 PLACE must be an CAS-able place. Built-in CAS-able places are accessor forms
207 whose CAR is one of the following:
209 CAR, CDR, FIRST, REST, SVREF, SYMBOL-PLIST, SYMBOL-VALUE, SVREF, SLOT-VALUE
210 SB-MOP:STANDARD-INSTANCE-ACCESS, SB-MOP:FUNCALLABLE-STANDARD-INSTANCE-ACCESS,
212 or the name of a DEFSTRUCT created accessor for a slot whose declared type is
213 either FIXNUM or T. Results are unspecified if the slot has a declared type
214 other then FIXNUM or T.
216 In case of SLOT-VALUE, if the slot is unbound, SLOT-UNBOUND is called unless
217 OLD is EQ to SB-PCL:+SLOT-UNBOUND+ in which case SB-PCL:+SLOT-UNBOUND+ is
218 returned and NEW is assigned to the slot. Additionally, the results are
219 unspecified if there is an applicable method on either
220 SB-MOP:SLOT-VALUE-USING-CLASS, (SETF SB-MOP:SLOT-VALUE-USING-CLASS), or
221 SB-MOP:SLOT-BOUNDP-USING-CLASS.
223 Additionally, the PLACE can be a anything for which a CAS-expansion has been
224 specified using DEFCAS, DEFINE-CAS-EXPANDER, or for which a CAS-function has
225 been defined. (See SB-EXT:CAS for more information.)
227 `(cas ,place ,old ,new))
229 ;;; Out-of-line definitions for various primitive cas functions.
230 (macrolet ((def (name lambda-list ref &optional set)
231 #!+compare-and-swap-vops
232 (declare (ignore ref set))
233 `(defun ,name (,@lambda-list old new)
234 #!+compare-and-swap-vops
235 (,name ,@lambda-list old new)
236 #!-compare-and-swap-vops
237 (progn
238 #!+sb-thread
239 ,(error "No COMPARE-AND-SWAP-VOPS on a threaded build?")
240 #!-sb-thread
241 (let ((current (,ref ,@lambda-list)))
242 ;; Shouldn't this be inside a WITHOUT-INTERRUPTS ?
243 (when (eq current old)
244 ,(if set
245 `(,set ,@lambda-list new)
246 `(setf (,ref ,@lambda-list) new)))
247 current)))))
248 (def %compare-and-swap-car (cons) car)
249 (def %compare-and-swap-cdr (cons) cdr)
250 (def %instance-cas (instance index) %instance-ref %instance-set)
251 #!+(or x86-64 x86)
252 (def %raw-instance-cas/word (instance index)
253 %raw-instance-ref/word
254 %raw-instance-set/word)
255 (def %compare-and-swap-symbol-info (symbol) symbol-info)
256 (def %compare-and-swap-symbol-value (symbol) symbol-value)
257 (def %compare-and-swap-svref (vector index) svref))
259 ;; Atomic increment/decrement ops on tagged storage cells (as contrasted with
260 ;; specialized arrays and raw structure slots) are defined in terms of CAS.
262 ;; This code would be more concise if workable versions
263 ;; of +-MODFX, --MODFX were defined generically.
264 #-sb-xc-host
265 (macrolet ((modular (fun a b)
266 #!+(or x86 x86-64)
267 `(,(let ((*package* (find-package "SB!VM")))
268 (symbolicate fun "-MODFX"))
269 ,a ,b)
270 #!-(or x86 x86-64)
271 ;; algorithm of https://graphics.stanford.edu/~seander/bithacks
272 `(let ((res (logand (,fun ,a ,b)
273 (ash sb!ext:most-positive-word
274 (- sb!vm:n-fixnum-tag-bits))))
275 (m (ash 1 (1- sb!vm:n-fixnum-bits))))
276 (- (logxor res m) m))))
278 ;; Atomically frob the CAR or CDR of a cons, or a symbol-value.
279 ;; The latter will be a global value because the ATOMIC-INCF/DECF
280 ;; macros work on a symbol only if it is known global.
281 (macrolet ((def-frob (name op type slot)
282 `(defun ,name (place delta)
283 (declare (type ,type place) (type fixnum delta))
284 (loop (let ((old (the fixnum (,slot place))))
285 (when (eq (cas (,slot place) old
286 (modular ,op old delta)) old)
287 (return old)))))))
288 (def-frob %atomic-inc-symbol-global-value + symbol symbol-value)
289 (def-frob %atomic-dec-symbol-global-value - symbol symbol-value)
290 (def-frob %atomic-inc-car + cons car)
291 (def-frob %atomic-dec-car - cons car)
292 (def-frob %atomic-inc-cdr + cons cdr)
293 (def-frob %atomic-dec-cdr - cons cdr)))