Both in the runtime and in Lisp releasing a spinlock was a simple
assignment. That doesn't work because the new value may not make it to
main memory by the time another CPU wants to acquire it making it
needlessly slow. Worse, it also allows the CPU to reorder instructions
from the critical section after the release.
There is a spinlock implementation for MIPS in the runtime, but it's
not used as we don't have threads on that platform. I don't know if
it's broken too.
(defun release-spinlock (spinlock)
(declare (optimize (speed 3) (safety 0)))
(defun release-spinlock (spinlock)
(declare (optimize (speed 3) (safety 0)))
- (setf (spinlock-value spinlock) nil)
- nil)
+ ;; Simply setting SPINLOCK-VALUE to NIL is not enough as it does not
+ ;; propagate to other processors, plus without a memory barrier the
+ ;; CPU might reorder instructions allowing code from the critical
+ ;; section to leak out. Use COMPARE-AND-SWAP for the memory barrier
+ ;; effect and do some sanity checking while we are at it.
+ (unless (eq *current-thread*
+ (sb!ext:compare-and-swap (spinlock-value spinlock)
+ *current-thread* nil))
+ (error "Only the owner can release the spinlock ~S." spinlock)))
-static inline void
-release_spinlock(volatile lispobj *word)
-{
- *word=0;
-}
-
static inline lispobj
swap_lispobjs(volatile lispobj *dest, lispobj value)
{
static inline lispobj
swap_lispobjs(volatile lispobj *dest, lispobj value)
{
+static inline void
+release_spinlock(volatile lispobj *word)
+{
+ /* A memory barrier is needed, use swap_lispobjs. See comment in
+ * RELEASE-SPINLOCK in target-thread.lisp. */
+ swap_lispobjs(word,0);
+}
+
#endif /* _X86_64_ARCH_H */
#endif /* _X86_64_ARCH_H */
-static inline void
-release_spinlock(volatile lispobj *word)
-{
- *word=0;
-}
-
#include <stdio.h>
static inline lispobj
#include <stdio.h>
static inline lispobj
+static inline void
+release_spinlock(volatile lispobj *word)
+{
+ /* A memory barrier is needed, use swap_lispobjs. See comment in
+ * RELEASE-SPINLOCK in target-thread.lisp. */
+ swap_lispobjs(word,0);
+}
+
extern void fast_bzero_detect(void *, size_t);
extern void (*fast_bzero_pointer)(void *, size_t);
extern void fast_bzero_detect(void *, size_t);
extern void (*fast_bzero_pointer)(void *, size_t);
;;; checkins which aren't released. (And occasionally for internal
;;; versions, especially for internal versions off the main CVS
;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)
;;; checkins which aren't released. (And occasionally for internal
;;; versions, especially for internal versions off the main CVS
;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)