New function SB-EXT:ASSERT-VERSION->=
[sbcl.git] / src / code / late-extensions.lisp
index 45fe8b4..bcfaafe 100644 (file)
                    sb!vm:n-word-bytes)
                 sb!vm:instance-pointer-lowtag)))))))
 
-(defmacro compare-and-swap (place old new &environment env)
-  "Atomically stores NEW in PLACE if OLD matches the current value of PLACE.
-Two values are considered to match if they are EQ. Returns the previous value
-of PLACE: if the returned value is EQ to OLD, the swap was carried out.
-
-PLACE must be an accessor form whose CAR is one of the following:
-
- CAR, CDR, FIRST, REST, SYMBOL-PLIST, SYMBOL-VALUE, SVREF
-
-or the name of a DEFSTRUCT created accessor for a slot whose declared type is
-either FIXNUM or T. Results are unspecified if the slot has a declared type
-other then FIXNUM or T.
-
-EXPERIMENTAL: Interface subject to change."
-  (flet ((invalid-place ()
-           (error "Invalid first argument to COMPARE-AND-SWAP: ~S" place)))
-    (unless (consp place)
-      (invalid-place))
-  ;; FIXME: Not the nicest way to do this...
-  (destructuring-bind (op &rest args) place
-    (case op
-      ((car first)
-       `(%compare-and-swap-car (the cons ,@args) ,old ,new))
-      ((cdr rest)
-       `(%compare-and-swap-cdr (the cons ,@args) ,old ,new))
-      (symbol-plist
-       `(%compare-and-swap-symbol-plist (the symbol ,@args) ,old (the list ,new)))
-      (symbol-value
-       (destructuring-bind (name) args
-         (flet ((slow (symbol)
-                  (with-unique-names (n-symbol n-old n-new)
-                    `(let ((,n-symbol ,symbol)
-                           (,n-old ,old)
-                           (,n-new ,new))
-                       (declare (symbol ,n-symbol))
-                       (about-to-modify-symbol-value ,n-symbol 'compare-and-swap ,n-new)
-                       (%compare-and-swap-symbol-value ,n-symbol ,n-old ,n-new)))))
-           (if (sb!xc:constantp name env)
-               (let ((cname (constant-form-value name env)))
-                 (if (eq :special (info :variable :kind cname))
-                     ;; Since we know the symbol is a special, we can just generate
-                     ;; the type check.
-                     `(%compare-and-swap-symbol-value
-                       ',cname ,old (the ,(info :variable :type cname) ,new))
-                     (slow (list 'quote cname))))
-               (slow name)))))
-      (svref
-       (let ((vector (car args))
-             (index (cadr args)))
-         (unless (and vector index (not (cddr args)))
-           (invalid-place))
-         (with-unique-names (v)
-           `(let ((,v ,vector))
-              (declare (simple-vector ,v))
-              (%compare-and-swap-svref ,v (%check-bound ,v (length ,v) ,index) ,old ,new)))))
-      (t
-       (let ((dd (info :function :structure-accessor op)))
-         (if dd
-             (let* ((structure (dd-name dd))
-                    (slotd (find op (dd-slots dd) :key #'dsd-accessor-name))
-                    (index (dsd-index slotd))
-                    (type (dsd-type slotd)))
-               (unless (eq t (dsd-raw-type slotd))
-                 (error "Cannot use COMPARE-AND-SWAP with structure accessor for a typed slot: ~S"
-                        place))
-               (when (dsd-read-only slotd)
-                 (error "Cannot use COMPARE-AND-SWAP with structure accessor for a read-only slot: ~S"
-                        place))
-               `(truly-the (values ,type &optional)
-                           (%compare-and-swap-instance-ref (the ,structure ,@args)
-                                                           ,index
-                                                           (the ,type ,old) (the ,type ,new))))
-             (error "Invalid first argument to COMPARE-AND-SWAP: ~S" place))))))))
-
-(macrolet ((def (name lambda-list ref &optional set)
-             #!+compare-and-swap-vops
-             (declare (ignore ref set))
-             `(defun ,name (,@lambda-list old new)
-                #!+compare-and-swap-vops
-                (,name ,@lambda-list old new)
-                #!-compare-and-swap-vops
-                (let ((current (,ref ,@lambda-list)))
-                  (when (eq current old)
-                    ,(if set
-                         `(,set ,@lambda-list new)
-                         `(setf (,ref ,@lambda-list) new)))
-                  current))))
-  (def %compare-and-swap-car (cons) car)
-  (def %compare-and-swap-cdr (cons) cdr)
-  (def %compare-and-swap-instance-ref (instance index) %instance-ref %instance-set)
-  (def %compare-and-swap-symbol-plist (symbol) symbol-plist)
-  (def %compare-and-swap-symbol-value (symbol) symbol-value)
-  (def %compare-and-swap-svref (vector index) svref))
+;;;; ATOMIC-INCF and ATOMIC-DECF
 
 (defun expand-atomic-frob (name place diff)
   (flet ((invalid-place ()
@@ -423,6 +331,7 @@ See also the declarations SB-EXT:GLOBAL and SB-EXT:ALWAYS-BOUND."
                (go :restart)))))))
 
 (defmacro wait-for (test-form &key timeout)
+  #!+sb-doc
   "Wait until TEST-FORM evaluates to true, then return its primary value.
 If TIMEOUT is provided, waits at most approximately TIMEOUT seconds before
 returning NIL.
@@ -434,3 +343,144 @@ deadline.
 Experimental: subject to change without prior notice."
   `(dx-flet ((wait-for-test () (progn ,test-form)))
      (%wait-for #'wait-for-test ,timeout)))
+
+(defmacro with-progressive-timeout ((name &key seconds)
+                                    &body body)
+  #!+sb-doc
+  "Binds NAME as a local function for BODY. Each time #'NAME is called, it
+returns SECONDS minus the time that has elapsed since BODY was entered, or
+zero if more time than SECONDS has elapsed. If SECONDS is NIL, #'NAME
+returns NIL each time."
+  (with-unique-names (deadline time-left sec)
+    `(let* ((,sec ,seconds)
+            (,deadline
+              (when ,sec
+                (+ (get-internal-real-time)
+                   (round (* ,seconds internal-time-units-per-second))))))
+       (flet ((,name ()
+                (when ,deadline
+                  (let ((,time-left (- ,deadline (get-internal-real-time))))
+                    (if (plusp ,time-left)
+                        (* (coerce ,time-left 'single-float)
+                           ,(/ 1.0 internal-time-units-per-second))
+                        0)))))
+         ,@body))))
+
+(defmacro atomic-update (place update-fn &rest arguments &environment env)
+  #!+sb-doc
+  "Updates PLACE atomically to the value returned by calling function
+designated by UPDATE-FN with ARGUMENTS and the previous value of PLACE.
+
+PLACE may be read and UPDATE-FN evaluated and called multiple times before the
+update succeeds: atomicity in this context means that value of place did not
+change between the time it was read, and the time it was replaced with the
+computed value.
+
+PLACE can be any place supported by SB-EXT:COMPARE-AND-SWAP.
+
+Examples:
+
+  ;;; Conses T to the head of FOO-LIST.
+  (defstruct foo list)
+  (defvar *foo* (make-foo))
+  (atomic-update (foo-list *foo*) #'cons t)
+
+  (let ((x (cons :count 0)))
+     (mapc #'sb-thread:join-thread
+           (loop repeat 1000
+                 collect (sb-thread:make-thread
+                          (lambda ()
+                            (loop repeat 1000
+                                  do (atomic-update (cdr x) #'1+)
+                                     (sleep 0.00001))))))
+     ;; Guaranteed to be (:COUNT . 1000000) -- if you replace
+     ;; atomic update with (INCF (CDR X)) above, the result becomes
+     ;; unpredictable.
+     x)
+"
+  (multiple-value-bind (vars vals old new cas-form read-form)
+      (get-cas-expansion place env)
+    `(let* (,@(mapcar 'list vars vals)
+            (,old ,read-form))
+       (loop for ,new = (funcall ,update-fn ,@arguments ,old)
+             until (eq ,old (setf ,old ,cas-form))
+             finally (return ,new)))))
+
+(defmacro atomic-push (obj place &environment env)
+  #!+sb-doc
+  "Like PUSH, but atomic. PLACE may be read multiple times before
+the operation completes -- the write does not occur until such time
+that no other thread modified PLACE between the read and the write.
+
+Works on all CASable places."
+  (multiple-value-bind (vars vals old new cas-form read-form)
+      (get-cas-expansion place env)
+    `(let* (,@(mapcar 'list vars vals)
+            (,old ,read-form)
+            (,new (cons ,obj ,old)))
+       (loop until (eq ,old (setf ,old ,cas-form))
+             do (setf (cdr ,new) ,old)
+             finally (return ,new)))))
+
+(defmacro atomic-pop (place &environment env)
+  #!+sb-doc
+  "Like POP, but atomic. PLACE may be read multiple times before
+the operation completes -- the write does not occur until such time
+that no other thread modified PLACE between the read and the write.
+
+Works on all CASable places."
+  (multiple-value-bind (vars vals old new cas-form read-form)
+      (get-cas-expansion place env)
+    `(let* (,@(mapcar 'list vars vals))
+       (loop for ,old = ,read-form
+             for ,new = (cdr ,old)
+             until (eq ,old (setf ,old ,cas-form))
+             finally (return (car ,old))))))
+
+(defun split-version-string (string)
+  (loop
+    with start = 0
+    and end = (length string)
+    while (and start (< start end))
+    for subversion = (multiple-value-bind (subversion next)
+                         (parse-integer string :start start
+                                               :junk-allowed t)
+                       (setf start
+                             (and subversion
+                                  next
+                                  (< next end)
+                                  (eql #\. (aref string next))
+                                  (1+ next)))
+                       subversion)
+    when subversion
+      collect subversion))
+
+(defun version>= (x y)
+  (unless (or x y)
+    (return-from version>= t))
+  (let ((head-x (or (first x) 0))
+        (head-y (or (first y) 0)))
+    (or (> head-x head-y)
+        (and (= head-x head-y)
+             (version>= (rest x) (rest y))))))
+
+(defun assert-version->= (&rest subversions)
+  #!+sb-doc
+  "Asserts that the current SBCL is of version equal to or greater than
+the version specified in the arguments.  A continuable error is signaled
+otherwise.
+
+The arguments specify a sequence of subversion numbers in big endian order.
+They are compared lexicographically with the runtime version, and versions
+are treated as though trailed by an unbounded number of 0s.
+
+For example, (assert-version->= 1 1 4) asserts that the current SBCL is
+version 1.1.4[.0.0...] or greater, and (assert-version->= 1) that it is
+version 1[.0.0...] or greater."
+  (let ((version (split-version-string (lisp-implementation-version))))
+    (unless (version>= version subversions)
+      (cerror "Disregard this version requirement."
+              "SBCL ~A is too old for this program (version ~{~A~^.~} ~
+               or later is required)."
+              (lisp-implementation-version)
+              subversions))))