;;; Basics

(defstruct xxx yyy)

(macrolet ((test (init op)
             `(let ((x ,init)
                    (y (list 'foo))
                    (z (list 'bar)))
                (assert (eql nil (compare-and-swap (,op x) nil y)))
                (assert (eql y (compare-and-swap (,op x) nil z)))
                (assert (eql y (,op x)))
                (let ((x "foo"))
                  (multiple-value-bind (res err)
                     (ignore-errors (compare-and-swap (,op x) nil nil))
                    (assert (not res))
                    (assert (typep err 'type-error)))))))
  (test (cons nil :no) car)
  (test (cons nil :no) first)
  (test (cons :no nil) cdr)
  (test (cons :no nil) rest)
  (test '.foo. symbol-plist)
  (test (progn (set '.bar. nil) '.bar.) symbol-value)
  (test (make-xxx) xxx-yyy))

(defvar *foo*)

;;; thread-local bindings

(let ((*foo* 42))
  (let ((*foo* nil))
    (assert (eql nil (compare-and-swap (symbol-value '*foo*) nil t)))
    (assert (eql t (compare-and-swap (symbol-value '*foo*) nil :foo)))
    (assert (eql t *foo*)))
  (assert (eql 42 *foo*)))

;;; unbound symbols + symbol-value

(assert (not (boundp '*foo*)))

(multiple-value-bind (res err)
    (ignore-errors (compare-and-swap (symbol-value '*foo*) nil t))
  (assert (not res))
  (assert (typep err 'unbound-variable)))

(defvar *bar* t)

(let ((*bar* nil))
   (makunbound '*bar*)
   (multiple-value-bind (res err)
       (ignore-errors (compare-and-swap (symbol-value '*bar*) nil t))
     (assert (not res))
     (assert (typep err 'unbound-variable))))

;;; SVREF

(defvar *v* (vector 1))

;; basics
(assert (eql 1 (compare-and-swap (svref *v* 0) 1 2)))
(assert (eql 2 (compare-and-swap (svref *v* 0) 1 3)))
(assert (eql 2 (svref *v* 0)))

;; bounds
(multiple-value-bind (res err)
    (ignore-errors (compare-and-swap (svref *v* -1) 1 2))
  (assert (not res))
  (assert (typep err 'type-error)))
(multiple-value-bind (res err)
    (ignore-errors (compare-and-swap (svref *v* 1) 1 2))
  (assert (not res))
  (assert (typep err 'type-error)))

;; type of the first argument
(multiple-value-bind (res err)
    (ignore-errors (compare-and-swap (svref "foo" 1) 1 2))
    (assert (not res))
    (assert (typep err 'type-error)))

;; Check that we don't modify constants
(defconstant +a-constant+ 42)
(assert
 (eq :error
     (handler-case
         (sb-ext:compare-and-swap (symbol-value '+a-constant+) 42 13)
       (error () :error))))
(let ((name '+a-constant+))
  (assert
   (eq :error
       (handler-case
           (sb-ext:compare-and-swap (symbol-value name) 42 13)
         (error () :error)))))

;; Check that we don't mess declaimed types
(declaim (boolean *a-boolean*))
(defparameter *a-boolean* t)
(assert
 (eq :error
     (handler-case
         (sb-ext:compare-and-swap (symbol-value '*a-boolean*) t 42)
       (error () :error))))
(let ((name '*a-boolean*))
  (assert
   (eq :error
       (handler-case
           (sb-ext:compare-and-swap (symbol-value name) t 42)
         (error () :error)))))

;;;; ATOMIC-INCF (we should probably rename this file atomic-ops...)

(defstruct box
  (word 0 :type sb-vm:word))

(defun inc-box (box n)
  (declare (fixnum n) (box box))
  (loop repeat n
        do (sb-ext:atomic-incf (box-word box))))

(defun dec-box (box n)
  (declare (fixnum n) (box box))
  (loop repeat n
        do (sb-ext:atomic-incf (box-word box) -1)))

(let ((box (make-box)))
  (inc-box box 10000)
  (assert (= 10000 (box-word box)))
  (dec-box box 10000)
  (assert (= 0 (box-word box))))

#+sb-thread
(let* ((box (make-box))
       (threads (loop repeat 64
                      collect (sb-thread:make-thread (lambda ()
                                                       (inc-box box 1000)
                                                       (dec-box box 10000)
                                                       (inc-box box 10000)
                                                       (dec-box box 1000))
                                                     :name "inc/dec thread"))))
  (mapc #'sb-thread:join-thread threads)
  (assert (= 0 (box-word box))))
