;;; Basics (defstruct xxx yyy) (macrolet ((test (init op) `(with-test (:name (:cas :basics ,(intern (symbol-name op) "KEYWORD"))) (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 (eval "foo"))) ; hide the compile-time type error (multiple-value-bind (res err) (ignore-errors (compare-and-swap (,op x) nil nil)) (unless (not res) (error "Wanted NIL and type-error, got: ~S" 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 (with-test (:name (:cas :tls)) (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*))) (with-test (:name (:cas :unbound)) (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) (with-test (:name (:cas :unbound 2)) (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 (with-test (:name (:cas :svref)) (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 (with-test (:name (:cas :svref :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 (with-test (:name (:cas :svref :type)) (multiple-value-bind (res err) (ignore-errors (compare-and-swap (svref (eval "foo") 1) 1 2)) (assert (not res)) (assert (typep err 'type-error)))) ;; Check that we don't modify constants (defconstant +a-constant+ 42) (with-test (:name (:cas :symbol-value :constant-modification)) (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) (with-test (:name (:cas :symbol-value :type-checking)) (assert (eq :error (handler-case (sb-ext:compare-and-swap (symbol-value '*a-boolean*) t (eval 42)) (error () :error)))) (let ((name '*a-boolean*)) (assert (eq :error (handler-case (sb-ext:compare-and-swap (symbol-value name) t (eval 42)) (error () :error)))))) ;;;; ATOMIC-INCF and ATOMIC-DECF (we should probably rename this file atomic-ops...) (defstruct box (word 0 :type sb-vm:word)) ;; Have the following tests check that CAS access to the superclass ;; works in the presence of a subclass sharing the conc-name. (defstruct (subbox (:include box) (:conc-name "BOX-"))) (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-decf (box-word box)))) (with-test (:name :atomic-incf/decf) (let ((box (make-box))) (inc-box box 10000) (assert (= 10000 (box-word box))) (dec-box box 10000) (assert (= 0 (box-word box))))) (with-test (:name :atomic-incf-wraparound) (let ((box (make-box :word sb-ext:most-positive-word))) (sb-ext:atomic-incf (box-word box) 2) (assert (= 1 (box-word box))))) (with-test (:name :atomic-decf-wraparound) (let ((box (make-box :word 0))) (sb-ext:atomic-decf (box-word box) 2) (assert (= (- (ash 1 sb-vm:n-word-bits) 2) (box-word box))))) (with-test (:name :cas-raw-instance-ref-word :implemented-on (:vop-existsp "SB-KERNEL:%RAW-INSTANCE-CAS/WORD")) (let ((foo (make-box :word 42))) ;; basic smoke test - not checking for atomicity or anything (assert (eql (cas (box-word foo) 42 43) 42)) (assert (eql (cas (box-word foo) 43 44) 43)))) (with-test (:name :atomic-incf-full-call-lp1381867 :implemented-on (:vop-existsp "SB-KERNEL:%RAW-INSTANCE-ATOMIC-INCF/WORD")) ;; contortions to avoid reader errors (let* ((%riai/w (intern "%RAW-INSTANCE-ATOMIC-INCF/WORD" "SB-KERNEL")) (form `(locally ;; Rebind %RAW-INSTANCE-ATOMIC-INCF/WORD as a local ;; function. Declaring it locally notinline fails because ;; it is marked with the ALWAYS-TRANSLATABLE attribute, so ;; it's a bug to call it even though we've asked to call ;; it. (Maybe that's a bug?) And I don't want to call it ;; explictly - I want the macro to do it so that I don't ;; have to inject any of the sign masking noise and such. (declare (disable-package-locks ,%riai/w)) (let ((b (make-box :word 0)) (delta (- (ash 1 (1- sb-vm:n-word-bits)))) (f (fdefinition ',%riai/w))) (flet ((,%riai/w (a b c) (funcall f a b c))) (assert (= (atomic-incf (box-word b) delta) 0)) (assert (= (atomic-incf (box-word b) delta) (ash 1 (1- sb-vm:n-word-bits)))) (assert (= (box-word b) 0)) (atomic-decf (box-word b)) (assert (= (box-word b) sb-ext:most-positive-word))))))) (eval form))) #+sb-thread (with-test (:name (:atomic-incf/decf :threads)) (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))))) (defglobal **my-atomic-counter* 0) (declaim (fixnum **my-atomic-counter*)) ;; Assert that safe (atomic-incf car) type-checks the car. (with-test (:name :atomic-incf-car-safe) (let ((x (cons (1+ most-positive-fixnum) 0))) ; a bignum (assert (eq (handler-case (atomic-incf (car x)) (type-error () 'win)) 'win)))) ;; Basic correctness tests, not testing atomicity (macrolet ((test-place (place) `(progn ;; using a constant for the delta (assert (eq (atomic-incf ,place 2) 0)) (assert (eq ,place 2)) (assert (eq (atomic-incf ,place -1) 2)) (assert (eq ,place 1)) (assert (eq (atomic-decf ,place 1) 1)) (assert (eq ,place 0)) ;; using a non-constant for the delta (assert (eq (atomic-incf ,place (eval 2)) 0)) (assert (eq ,place 2)) (assert (eq (atomic-incf ,place (eval -1)) 2)) (assert (eq ,place 1)) (assert (eq (atomic-decf ,place (eval 1)) 1)) (assert (eq ,place 0)) (setf ,place most-positive-fixnum) (atomic-incf ,place) (assert (eq ,place most-negative-fixnum)) (atomic-decf ,place) (assert (eq ,place most-positive-fixnum))))) (with-test (:name (:atomic-incf :global-var)) (test-place **my-atomic-counter*)) (with-test (:name (:atomic-incf :car)) (let ((x (cons 0 'foo))) (test-place (car x)))) (with-test (:name (:atomic-incf :cdr)) (let ((x (cons 'foo 0))) (test-place (cdr x)))) ;; Fast code for (atomic-{incf|decf} {car|cdr}) is decidedly unsafe ;; on x86-64. Ensure basic correctness when used correctly. (with-test (:name (:atomic-incf-fast :car)) (let ((x (cons 0 'foo))) (declare (optimize (safety 0))) (test-place (car x)))) (with-test (:name (:atomic-incf-fast :cdr)) (let ((x (cons 'foo 0))) (declare (optimize (safety 0))) (test-place (cdr x))))) ;;; STANDARD-INSTANCE-ACCESS, FUNCALLABLE-STANDARD-INSTANCE-ACCESS (defclass sia-cas-test () ((a :initarg :a) (b :initarg :b))) (with-test (:name (:cas :standard-instance-access)) (flet ((slot-loc (slot class) (sb-mop:slot-definition-location (find slot (sb-mop:class-slots class) :key #'sb-mop:slot-definition-name)))) (let* ((class (find-class 'sia-cas-test)) (instance (make-instance class :a 'a :b 'b)) (a-loc (slot-loc 'a class)) (b-loc (slot-loc 'b class))) (assert (eq 'a (slot-value instance 'a))) (assert (eq 'a (compare-and-swap (sb-mop:standard-instance-access instance a-loc) 'x 'oops))) (assert (eq 'a (sb-mop:standard-instance-access instance a-loc))) (assert (eq 'a (compare-and-swap (sb-mop:standard-instance-access instance a-loc) 'a 'a2))) (assert (eq 'a2 (sb-mop:standard-instance-access instance a-loc))) (assert (eq 'a2 (slot-value instance 'a))) (assert (eq 'b (slot-value instance 'b))) (assert (eq 'b (sb-mop:standard-instance-access instance b-loc)))))) (defclass fia-cas-test (sb-mop:funcallable-standard-object) ((a :initarg :a) (b :initarg :b)) (:metaclass sb-mop:funcallable-standard-class)) (with-test (:name (:cas :standard-instance-access)) (flet ((slot-loc (slot class) (sb-mop:slot-definition-location (find slot (sb-mop:class-slots class) :key #'sb-mop:slot-definition-name)))) (let* ((class (find-class 'fia-cas-test)) (instance (make-instance class :a 'a :b 'b)) (a-loc (slot-loc 'a class)) (b-loc (slot-loc 'b class))) (sb-mop:set-funcallable-instance-function instance (lambda () :ok)) (eq :ok (funcall instance)) (assert (eq 'a (slot-value instance 'a))) (assert (eq 'a (compare-and-swap (sb-mop:funcallable-standard-instance-access instance a-loc) 'x 'oops))) (assert (eq 'a (sb-mop:funcallable-standard-instance-access instance a-loc))) (assert (eq 'a (compare-and-swap (sb-mop:funcallable-standard-instance-access instance a-loc) 'a 'a2))) (assert (eq 'a2 (sb-mop:funcallable-standard-instance-access instance a-loc))) (assert (eq 'a2 (slot-value instance 'a))) (assert (eq 'b (slot-value instance 'b))) (assert (eq 'b (sb-mop:funcallable-standard-instance-access instance b-loc)))))) ;;; SLOT-VALUE (defclass standard-thing () ((x :initform 42) (y))) (defmethod slot-unbound ((class standard-class) (obj standard-thing) slot) (list :unbound slot)) (defmethod slot-missing ((class standard-class) (obj standard-thing) slot op &optional val) (list :missing slot op val)) (with-test (:name (:cas :slot-value :standard-object)) (let ((x (make-instance 'standard-thing))) (assert (eql 42 (slot-value x 'x))) (assert (eql 42 (compare-and-swap (slot-value x 'x) 0 :foo))) (assert (eql 42 (slot-value x 'x))) (assert (eql 42 (compare-and-swap (slot-value x 'x) 42 :foo))) (assert (eql :foo (slot-value x 'x))))) (with-test (:name (:cas :slot-value :slot-unbound)) (let ((x (make-instance 'standard-thing))) (assert (equal '(:unbound y) (slot-value x 'y))) (assert (equal '(:unbound y) (compare-and-swap (slot-value x 'y) 0 :foo))) (assert (equal '(:unbound y) (slot-value x 'y))) (assert (eq sb-pcl:+slot-unbound+ (compare-and-swap (slot-value x 'y) sb-pcl:+slot-unbound+ :foo))) (assert (eq :foo (slot-value x 'y))))) (with-test (:name (:cas :slot-value :slot-missing)) (let ((x (make-instance 'standard-thing))) (assert (equal '(:missing z slot-value nil) (slot-value x 'z))) (assert (equal '(:missing z sb-ext:cas (0 :foo)) (compare-and-swap (slot-value x 'z) 0 :foo))) (assert (equal '(:missing z slot-value nil) (slot-value x 'z))))) (defclass non-standard-class (standard-class) ()) (defmethod sb-mop:validate-superclass ((class non-standard-class) (superclass standard-class)) t) (defclass non-standard-thing-0 () ((x :initform 13)) (:metaclass non-standard-class)) (defclass non-standard-thing-1 () ((x :initform 13)) (:metaclass non-standard-class)) (defclass non-standard-thing-2 () ((x :initform 13)) (:metaclass non-standard-class)) (defclass non-standard-thing-3 () ((x :initform 13)) (:metaclass non-standard-class)) (defvar *access-list* nil) (defmethod sb-mop:slot-value-using-class ((class non-standard-class) (obj non-standard-thing-1) slotd) (let ((v (call-next-method))) (push :read *access-list*) v)) (defmethod (setf sb-mop:slot-value-using-class) (value (class non-standard-class) (obj non-standard-thing-2) slotd) (let ((v (call-next-method))) (push :write *access-list*) v)) (defmethod sb-mop:slot-boundp-using-class ((class non-standard-class) (obj non-standard-thing-3) slotd) (let ((v (call-next-method))) (push :boundp *access-list*) v)) (with-test (:name (:cas :slot-value :non-standard-object :standard-access)) (let ((x (make-instance 'non-standard-thing-0))) (assert (eql 13 (slot-value x 'x))) (assert (eql 13 (compare-and-swap (slot-value x 'x) 0 :bar))) (assert (eql 13 (slot-value x 'x))) (assert (eql 13 (compare-and-swap (slot-value x 'x) 13 :bar))) (assert (eql :bar (slot-value x 'x))))) (with-test (:name (:cas :slot-value :non-standard-object :slot-value-using-class)) (setf *access-list* nil) (let ((x (make-instance 'non-standard-thing-1))) (declare (notinline slot-value)) (assert (null *access-list*)) (assert (eql 13 (slot-value x 'x))) (assert (equal '(:read) *access-list*)) (assert (eq :error (handler-case (compare-and-swap (slot-value x 'x) 0 :bar) (error () :error)))) (assert (eql 13 (slot-value x 'x))) (assert (equal '(:read :read) *access-list*)))) (with-test (:name (:cas :slot-value :non-standard-object :setf-slot-value-using-class)) (setf *access-list* nil) (let ((x (make-instance 'non-standard-thing-2))) (assert (equal '(:write) *access-list*)) (assert (eql 13 (slot-value x 'x))) (assert (equal '(:write) *access-list*)) (assert (eq :error (handler-case (compare-and-swap (slot-value x 'x) 0 :bar) (error () :error)))) (assert (eql 13 (slot-value x 'x))) (assert (equal '(:write) *access-list*)))) (with-test (:name (:cas :slot-value :non-standard-object :slot-boundp-using-class)) (setf *access-list* nil) (let ((x (make-instance 'non-standard-thing-3))) (assert (equal '(:boundp) *access-list*)) (assert (eql 13 (slot-value x 'x))) (assert (eq :error (handler-case (compare-and-swap (slot-value x 'x) 0 :bar) (error () :error)))) (assert (eql 13 (slot-value x 'x))))) (defvar *foo* nil) (defun foo () *foo*) (defun (cas foo) (old new) (cas (symbol-value '*foo*) old new)) (with-test (:name (:cas :defun)) (assert (null (foo))) (assert (null (cas (foo) nil t))) (assert (eq t (foo))) (assert (eq t (cas (foo) nil :oops))) (assert (eq t (foo)))) (with-test (:name (:cas :flet)) (let (x) (flet (((cas x) (old new) (let ((tmp x)) (when (eq tmp old) (setf x new)) tmp)) (x () x)) (assert (null (x))) (assert (null (cas (x) nil t))) (assert (eq t (x))) (assert (eq t (cas (x) nil :oops))) (assert (eq t (x)))))) (defgeneric (cas thing) (old new thing)) (defmethod (cas thing) (old new (thing cons)) (cas (car thing) old new)) (defmethod (cas thing) (old new (thing symbol)) (cas (symbol-value thing) old new)) (defgeneric thing (thing) (:method ((x cons)) (car x)) (:method ((x symbol)) (symbol-value x))) (with-test (:name (:cas :defgeneric)) (let ((a (list nil)) (b (gensym "X"))) (set b nil) (assert (null (thing a))) (assert (null (thing b))) (assert (null (cas (thing a) nil t))) (assert (null (cas (thing b) nil t))) (assert (eq t (thing a))) (assert (eq t (thing b))) (assert (eq t (cas (thing a) nil :oops))) (assert (eq t (cas (thing b) nil :oops))) (assert (eq t (thing a))) (assert (eq t (thing b))))) ;;; SYMBOL-VALUE with a constant argument used to return a bogus read-form (with-test (:name :symbol-value-cas-expansion) (multiple-value-bind (vars vals old new cas-form read-form) (get-cas-expansion `(symbol-value t)) (declare (ignore old new cas-form)) (assert (not vars)) (assert (not vals)) (assert (eq t (eval read-form)))) (multiple-value-bind (vars vals old new cas-form read-form) (get-cas-expansion `(symbol-value *)) (declare (ignore old new cas-form)) (let ((* :foo)) (assert (eq :foo (eval `(let (,@(mapcar 'list vars vals)) ,read-form))))) (let ((* :bar)) (assert (eq :bar (eval `(let (,@(mapcar 'list vars vals)) ,read-form))))))) (with-test (:name :atomic-push :skipped-on (not :sb-thread)) (let ((store (cons nil nil)) (n 100000)) (symbol-macrolet ((x (car store)) (y (cdr store))) (dotimes (i n) (push i y)) (mapc #'sb-thread:join-thread (loop repeat (ecase sb-vm:n-word-bits (32 100) (64 1000)) collect (sb-thread:make-thread (lambda () (loop for z = (atomic-pop y) while z do (atomic-push z x) (sleep 0.00001)))))) (assert (not y)) (assert (eql n (length x)))))) (with-test (:name :local-special-symbol-value) (assert (= (funcall (compile nil `(lambda () (let ((x 10)) (declare (special x)) (cas (symbol-value 'x) 10 12) x)))) 12)) (assert (= (funcall (compile nil `(lambda () (let ((x (list 1))) (declare (special x)) (atomic-pop (symbol-value 'x)))))) 1))) (defclass cas-fsc (generic-function) ((a :initform 2)) (:metaclass sb-mop:funcallable-standard-class)) (with-test (:name :cas-funcallable-instance) (let ((x (make-instance 'cas-fsc))) (assert (= (funcall (compile nil `(lambda (x) (cas (slot-value x 'a) 0 1))) x) 2)) (assert (eql (slot-value x 'a) 2)) (assert (= (funcall (compile nil `(lambda (x) (cas (slot-value x 'a) 2 3))) x) 2)) (assert (eql (slot-value x 'a) 3)))) (in-package "SB-VM") ;;; This file defines a structure, so is an 'impure' test (defstruct my-struct ;; The slots under test have to be naturally aligned for a double-Lispword, ;; at least on x86-64, so add a random slot if there is no layout slot. #+compact-instance-header fluff one two three four) (locally (declare (muffle-conditions style-warning)) ; functions don't exist for non-x86 (defun test-a-cons (acons oldcar oldcdr newcar newcdr) (%cons-cas-pair acons oldcar oldcdr newcar newcdr)) (defun test-a-vect (avect ind old1 old2 new1 new2) (%vector-cas-pair avect ind old1 old2 new1 new2)) (defun test-a-struct (inst ind old1 old2 new1 new2) (%instance-cas-pair inst ind old1 old2 new1 new2)) (defun test-wide-cmpxchg () #-arm64 (let ((x (cons 'a 'b))) (multiple-value-bind (old1 old2) (test-a-cons x 'a 'b 'foo 'bar) (assert (and (eq old1 'a) (eq old2 'b) (equal x '(foo . bar))))) (multiple-value-bind (old1 old2) (test-a-cons x 0 0 1 2) (assert (and (eq old1 'foo) (eq old2 'bar) (equal x '(foo . bar)))))) ;; This is just testing that the offsets are correct. ;; Correct working of the instruction is tested by the CONS example. (let ((x (make-array 6 :initial-element nil))) (multiple-value-bind (old1 old2) (test-a-vect x 2 nil nil 'foo 'bar) (assert (and (null old1) (null old2) (equalp x #(nil nil foo bar nil nil)))))) #-arm64 ;; Same remark applies - just check that the offset to the slot is right. (let ((s (make-my-struct :three 'the :four 'floor))) ;; in slots 3 and 4 put your bootee (a baby shoe, i.e.) on the floor (multiple-value-bind (old1 old2) (test-a-struct s 3 'the 'floor 'your 'bootee) (assert (and (eq old1 'the) (eq old2 'floor) (eq (my-struct-three s) 'your) (eq (my-struct-four s) 'bootee))))) t)) (defun should-test-dblcas () #+arm64 (or (member :arm-v8.1 sb-c:*backend-subfeatures*) (with-open-file (f "/proc/cpuinfo" :if-does-not-exist nil) (and f (loop repeat 4 thereis (let ((line (read-line f))) (and (search "Features" line) (search " atomics " line))))))) #+(or x86 x86-64) (multiple-value-bind (a b c d) (%cpu-identification 0 0) (declare (ignore b c d)) ;; paranoidly check for whether we can execute function ID 1 (and (>= a 1) ; the highest function ID (multiple-value-bind (a b c d) (%cpu-identification 1 0) (declare (ignore a b) (ignorable c d)) ;; paranoidly check for CMPXCHGxB presence ;; constants from Table 3-20 and 3-21 of Intel manual (and #+x86 (logbitp 8 d) #+x86-64 (logbitp 13 c)))))) (test-util:with-test (:name :wide-compare-and-exchange :skipped-on (not (or :arm64 :x86 :x86-64))) (if (should-test-dblcas) (test-wide-cmpxchg) (format t "Double-width compare-and-swap NOT TESTED~%"))) (test-util:with-test (:name :cas-sap-ref-smoke-test :skipped-on (not :sb-thread) :broken-on :ppc) (let ((data (make-array 1 :element-type 'sb-vm:word))) (sb-sys:with-pinned-objects (data) (let ((sap (sb-sys:vector-sap data))) ;; It's important to exercise an initial value that has lots of bits on, ;; because I made at least two mistakes in the x86-64 lispword-sized vop: ;; 1. it was using a :dword move where it should have used a :qword ;; 2. it was emitting constants as bignums instead of inline raw constants (macrolet ((test (signedp nbits newval &aux (ref (symbolicate (if signedp "SIGNED-" "") "SAP-REF-" nbits)) (init (if signedp -1 (ldb (byte nbits 0) most-positive-word)))) `(progn ;; (format t "Testing ~a with initial bits ~x~%" ',ref ,init) (setf (,ref sap 0) ,init) (let ((old (cas (,ref sap 0) 0 5))) (assert (eql old ,init)) ; actual old (assert (eql (,ref sap 0) ,init))) ; memory should not have changed (let ((old (cas (,ref sap 0) ,init ,newval))) (assert (eql old ,init)) ; actual old (assert (eql (,ref sap 0) ,newval)))))) ; should have changed #+64-bit (test nil 64 #xdeadc0fefe00) #+64-bit (test t 64 most-negative-fixnum) (test nil 32 #xbabab00e) ;; on riscv I did not implement these sizes, and ;; ppc64 might get "illegal instruction" depending on the particular CPU #-(or riscv ppc64 loongarch64) (test nil 16 #xfafa) #-(or riscv ppc64 loongarch64) (test nil 8 #xbb) ) ;; SAP-REF-SAP (setf (aref data 0) 0) (let ((old (cas (sap-ref-sap sap 0) (int-sap 1) (int-sap #xffff)))) (assert (sb-sys:sap= old (int-sap 0))) ; actual old (assert (sb-sys:sap= (sap-ref-sap sap 0) (int-sap 0)))) ; memory should not have changed (let ((old (cas (sap-ref-sap sap 0) (int-sap 0) (int-sap sb-ext:most-positive-word)))) (assert (sb-sys:sap= old (int-sap 0))) (assert (sb-sys:sap= (sap-ref-sap sap 0) (int-sap sb-ext:most-positive-word)))) ;; SAP-REF-LISPOBJ (setf (aref data 0) sb-vm:nil-value) (let ((old (cas (sap-ref-lispobj sap 0) t '*print-base*))) (assert (eq old nil)) ; actual old (assert (eq (sap-ref-lispobj sap 0) nil))) ; memory should not have changed (let ((old (cas (sap-ref-lispobj sap 0) nil t))) (assert (eq old nil)) (assert (eq (sap-ref-lispobj sap 0) t))))))) (test-util:with-test (:name :cas-sb16 :skipped-on (or :interpreter (not (or :arm64 :x86-64)))) (let ((a (make-array 1 :element-type '(signed-byte 16)))) (flet ((cas-sb16 (sap old new) (cas (sb-sys:signed-sap-ref-16 sap 0) old new))) (setf (aref a 0) -1000) (loop for old from -1000 to 1000 do (sb-sys:with-pinned-objects (a) (let ((actual (cas-sb16 (sb-sys:vector-sap a) old (1+ old)))) (assert (= actual old)) (assert (= (aref a 0) (1+ old))))))))) (test-util:with-test (:name :cas-sap-ref-stress-test :skipped-on (not :sb-thread)) (let ((data (make-array 1 :element-type 'sb-vm:word :initial-element 0))) (sb-sys:with-pinned-objects (data) (let ((sap (sb-sys:vector-sap data)) (n-threads 3) (n-increments 100000) (threads)) (flet ((increment () (let ((fails 0)) (dotimes (i n-increments fails) (let ((old (sap-ref-32 sap 0))) (loop (let ((actual (cas (sap-ref-32 sap 0) old (1+ old)))) (if (eq actual old) (return)) (incf fails) (setq old actual)))))))) (dotimes (i n-threads) (push (sb-thread:make-thread #'increment) threads)) (mapc 'sb-thread:join-thread threads) (assert (= (sap-ref-32 sap 0) (* n-threads n-increments)))))))) (define-alien-variable "small_generation_limit" (signed 8)) ;; PPC64 shouldn't fail, but depending on the CPU revision it might not ;; have the needed instruction, and I don't know how to test for it. ;; And surely it doesn't really depend on endian-ness, but the machine ;; that I'm testing on which is little-endian passes the test. #+(and sb-thread (or x86-64 (and ppc64 little-endian))) (progn (defun cas-an-alien-byte (x y) (cas small-generation-limit x y)) (compile 'cas-an-alien-byte) (test-util:with-test (:name :cas-alien) (assert (= small-generation-limit 1)) (assert (= (cas-an-alien-byte 0 5) 1)) (assert (= (cas-an-alien-byte 1 6) 1)) (assert (= small-generation-limit 6)) (setf small-generation-limit 1))) (test-util:with-test (:name :cas-aref :implemented-on (or :arm64 :x86-64)) (dolist (bits '(8 16 32 #+64-bit 64)) (let ((unsigned (make-array '(3 3) :element-type `(unsigned-byte ,bits) :initial-element 0)) (signed (make-array '(3 3) :element-type `(signed-byte ,bits) :initial-element 0)) (uint 0) (sint -5)) (dotimes (i 3) (dotimes (j 3) (cas (aref unsigned i j) 0 (incf uint)) (cas (aref signed i j) 0 (incf sint)))) (assert (equalp unsigned #2A((1 2 3) (4 5 6) (7 8 9)))) (assert (equalp signed #2A((-4 -3 -2) (-1 0 1) (2 3 4))))))) (test-util:with-test (:name :cas-aref-float :implemented-on :x86-64) (dolist (et '(single-float double-float)) (let ((a (make-array 4 :element-type et))) (dotimes (i 4) (cas (aref a i) (coerce 0 et) (coerce (1+ i) et))) (assert (equalp a #(1d0 2d0 3d0 4d0)))))) (declaim (global *not-ab-global*)) (defun cas-not-ab-global (old new) (cas *not-ab-global* old new)) (compile 'cas-not-ab-global) (test-util:with-test (:name :cas-not-always-bound-global) (assertoid:assert-error (cas-not-ab-global 1 2) cell-error))