;;; -*- Mode: Lisp; Package: FSet; Syntax: ANSI-Common-Lisp -*-

(in-package :fset)

;;; File: port.lisp
;;; Contents: Portability-related definitions for FSet.
;;;
;;; This file is part of FSet.  Copyright (c) 2007-2012 Scott L. Burson.
;;; FSet is licensed under the Lisp Lesser GNU Public License, or LLGPL.
;;; See: http://opensource.franz.com/preamble.html
;;; This license provides NO WARRANTY.


;;; On non-kernel-threads implementations, we use something like
;;; `without-interrupts'.  On kernel-threads implementations, we have to do
;;; real locking.

#+(and allegro (not os-threads))
(progn
  (defun make-lock (&optional name)
    (declare (ignore name))
    nil)
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (declare (ignore lock wait?))
    `(excl:without-interrupts . ,body))
  (defmacro read-memory-barrier ()
    'nil)
  (defmacro write-memory-barrier ()
    'nil))

#+(and allegro os-threads)          ; &&& untested
(progn
  (defun make-lock (&optional name)
    (apply #'mp:make-process-lock (and name `(:name ,name))))
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    `(mp:with-process-lock (,lock :timeout (if ,wait? nil 0))
       . ,body))
  ;; For those implementations that support SMP but don't give us direct ways
  ;; to generate memory barriers, we assume that grabbing a lock suffices.
  (deflex *Memory-Barrier-Lock*
    (mp:make-process-lock :name "Memory Barrier Lock"))
  (defmacro read-memory-barrier ()
    '(mp:with-process-lock (*Memory-Barrier-Lock*)
       nil))
  (defmacro write-memory-barrier ()
    '(mp:with-process-lock (*Memory-Barrier-Lock*)
       nil)))


#+lispworks
(progn
  (defun make-lock (&optional name)
    (apply #'mp:make-lock (and name `(:name ,name))))
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    `(mp:with-lock (,lock :timeout (if ,wait? nil 0))
       . ,body))
  ;; For those implementations that support SMP but don't give us direct ways
  ;; to generate memory barriers, we assume that grabbing a lock suffices.
  (deflex *Memory-Barrier-Lock*
    (mp:make-lock :name "Memory Barrier Lock"))
  (defmacro read-memory-barrier ()
    '(mp:with-lock (*Memory-Barrier-Lock*)
       nil))
  (defmacro write-memory-barrier ()
    '(mp:with-lock (*Memory-Barrier-Lock*)
       nil)))


#+cmu
(progn
  (defun make-lock (&optional name)
    (declare (ignore name))
    nil)
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (declare (ignore lock wait?))
    `(sys:without-interrupts . ,body))
  (defmacro read-memory-barrier ()
    'nil)
  (defmacro write-memory-barrier ()
    'nil))


#+(and sbcl (not sb-thread))
(progn
  (defun make-lock (&optional name)
    (declare (ignore name))
    nil)
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (declare (ignore lock wait?))
    `(progn
       . ,body))
  (progn
    (defmacro read-memory-barrier ()
      'nil)
    (defmacro write-memory-barrier ()
      'nil)))

#+(and sbcl sb-thread)
(progn
  (defun make-lock (&optional name)
    (apply #'sb-thread:make-mutex (and name `(:name ,name))))
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    `(sb-thread:with-mutex (,lock :wait-p ,wait?)
       . ,body))
  ;; For those implementations that support SMP but don't give us direct ways
  ;; to generate memory barriers, we assume that grabbing a lock suffices.
  (deflex *Memory-Barrier-Lock*
    (sb-thread:make-mutex :name "Memory Barrier Lock"))
  (defmacro read-memory-barrier ()
    '(sb-thread:with-mutex (*Memory-Barrier-Lock*)
       nil))
  (defmacro write-memory-barrier ()
    '(sb-thread:with-mutex (*Memory-Barrier-Lock*)
       nil)))


#+scl
(progn
  (defun make-lock (&optional name)
    (thread:make-lock name :type ':recursive :auto-free t))
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    `(thread:with-lock-held (,lock "Lock Wait" :wait ,wait?)
       . ,body))
  (defmacro read-memory-barrier ()
    '(kernel:read-memory-barrier))
  (defmacro write-memory-barrier ()
    '(kernel:write-memory-barrier)))


#+openmcl
(progn
  (defun make-lock (&optional name)
    (ccl:make-lock name))
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (let ((lock-var (gensym "LOCK-"))
	  (wait?-var (gensym "WAIT?-"))
	  (try-succeeded?-var (gensym "TRY-SUCCEEDED?-")))
      `(let ((,lock-var ,lock)
	     . ,(and (not (eq wait? 't))
		     `((,wait?-var ,wait?)
		       (,try-succeeded?-var nil))))
	 ,(if (eq wait? 't)
	      `(ccl:with-lock-grabbed (,lock-var)
		. ,body)
	    `(unwind-protect
		 (and (or ,wait?-var (and (ccl:try-lock ,lock-var)
					  (setq ,try-succeeded?-var t)))
		      (ccl:with-lock-grabbed (,lock-var)
			. ,body))
	       (when ,try-succeeded?-var
		 (ccl:release-lock ,lock-var)))))))
  ;; For those implementations that support SMP but don't give us direct ways
  ;; to generate memory barriers, we assume that grabbing a lock suffices.
  (deflex *Memory-Barrier-Lock*
    (ccl:make-lock "Memory Barrier Lock"))
  (defmacro read-memory-barrier ()
    `(ccl:with-lock-grabbed (*Memory-Barrier-Lock*)
       nil))
  (defmacro write-memory-barrier ()
    `(ccl:with-lock-grabbed (*Memory-Barrier-Lock*)
       nil)))


#+(and genera new-scheduler)
(progn
  (defun make-lock (&optional name)
    (process:make-lock name))
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (declare (ignore wait?))
    `(process:with-lock (,lock)
       . ,body))
  (defmacro read-memory-barrier ()
    'nil)
  (defmacro read-memory-barrier ()
    'nil))


#+clisp
(progn
  (defun make-lock (&optional name)
    (declare (ignore name))
    nil)
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (declare (ignore lock wait?))
    `(progn . ,body))
  (defmacro read-memory-barrier ()
    'nil)
  (defmacro write-memory-barrier ()
    'nil))


#+(and ecl (not threads))
(progn
  (defun make-lock (&optional name)
    (declare (ignore name))
    nil)
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (declare (ignore lock wait?))
    `(progn . ,body))
  (defmacro read-memory-barrier ()
    'nil)
  (defmacro write-memory-barrier ()
    'nil))

#+(and ecl threads)
(progn
  (defun make-lock (&optional name)
    (apply #'mp:make-lock (and name `(:name ,name))))
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (let ((lock-var (gensym "LOCK-"))
	  (wait?-var (gensym "WAIT?-"))
	  (try-succeeded?-var (gensym "TRY-SUCCEEDED?-")))
      `(let ((,lock-var ,lock)
	     . ,(and (not (eq wait? 't))
		     `((,wait?-var ,wait?)
		       (,try-succeeded?-var nil))))
	 ,(if (eq wait? 't)
	      `(mp:with-lock (,lock-var)
		. ,body)
	    `(unwind-protect
		 (and (or ,wait?-var (and (mp:get-lock ,lock-var nil)
					  (setq ,try-succeeded?-var t)))
		      (mp:with-lock (,lock-var)
			. ,body))
  	       (when ,try-succeeded?-var
		 (mp:giveup-lock ,lock-var)))))))
  (deflex *ECL-Read-Memory-Barrier-Lock*
    (mp:make-lock :name "Read Memory Barrier Lock"))
  (defmacro read-memory-barrier ()
    '(mp:with-lock (*ECL-Read-Memory-Barrier-Lock*)
       nil))
  (deflex *ECL-Write-Memory-Barrier-Lock*
    (mp:make-lock :name "Write Memory Barrier Lock"))
  (defmacro write-memory-barrier ()
    '(mp:with-lock (*ECL-Write-Memory-Barrier-Lock*)
       nil)))


#+abcl
(progn
  (defun make-lock (&optional name)
    (declare (ignore name))
    (threads:make-mutex))
  (defmacro with-lock ((lock &key (wait? t)) &body body)
    (declare (ignore wait?))
    `(threads:with-mutex (,lock)
       . ,body))
  ;; For those implementations that support SMP but don't give us direct ways
  ;; to generate memory barriers, we assume that grabbing a lock suffices.
  (deflex *Memory-Barrier-Lock*
    (threads:make-mutex))
  (defmacro read-memory-barrier ()
    '(threads:with-mutex (*Memory-Barrier-Lock*)
       nil))
  (defmacro write-memory-barrier ()
    '(threads:with-mutex (*Memory-Barrier-Lock*)
       nil)))


;;; ----------------

;;; Constants used by the tuple implementation.  We choose the widths of
;;; two bitfields to fit in a fixnum less the sign bit.
;;; These numbers are noncritical except possibly for small fixnums.

;;; Fixnum widths of known implementations:
;;; SBCL >= 1.0.53, 64-bit:		62
;;; ECL, 64-bit:			61
;;; SBCL < 1.0.53, OpenMCL/Clozure CL,
;;;   Scieneer CL, 64-bit		60
;;; CLISP, 64-bit			48
;;; Symbolics L-, I-machine; ABCL	31
;;; Allegro, CMUCL, SBCL, ECL
;;;   LispWorks (most), 32-bit		29
;;; CLISP, 32-bit; CADR, LMI Lambda	24
;;; LispWorks 4 on Linux		23

(defconstant Tuple-Value-Index-Size
  (floor (+ 5 (integer-length most-positive-fixnum)) 3)
  "This limits the number of key/value pairs in any tuple.")

(defconstant Tuple-Key-Number-Size
  (- (integer-length most-positive-fixnum) Tuple-Value-Index-Size)
  "This limits the number of tuple-keys that can exist in a session.")

(defconstant Tuple-Key-Number-Mask
  (1- (ash 1 Tuple-Key-Number-Size)))


;;; ----------------

#-lispworks
(defun base-char-p (x)
  (typep x 'base-char))

;;; I think this may be faster than `(typep x 'base-char)'.  Maybe not.
#+lispworks
(defun base-char-p (x) (lw:base-char-p x))

#-lispworks
(declaim (inline base-char-p))


;;; SBCL has a distinct `extended-char' type but no `make-char'.
#+sbcl
(defun make-char (code bits)
  ;; Kinda weird, but this is only used by the test suite to generate random chars.
  (code-char (+ code (ash bits 8))))

#+lispworks
(defun make-char (code bits)
  ;; Unfortunately, an attempt to use `bits' runs into LispWorks bugs; e.g.,
  ;; `(concatenate 'string ...)' tries to make a `base-string', and fails.
  (declare (ignore bits))
  (code-char code))


;;; I'm one of these weird people who detests `loop' (except in its CLtL1 form).
(defmacro while (pred &body body)
  `(do () ((not ,pred))
     . ,body))


;;; ----------------

;;; A macro used mostly by the bag code to get generic arithmetic in speed-3
;;; routines without all those compiler notes from CMUCL, SBCL, or Scieneer
;;; CL.
(defmacro gen (op &rest args)
  (let ((vars (mapcar (lambda (x) (and (not (or (symbolp x) (numberp x)))
				       (gensym "VAR-")))
		      args)))
    `(let ,(cl:remove nil (mapcar (lambda (var arg)
				    (and var `(,var ,arg)))
				  vars args))
       (locally (declare (optimize (speed 1) (safety 1)))
	 (,op . ,(mapcar (lambda (var arg) (or var arg))
			 vars args))))))


;;; This little oddity exists because of a limitation in Python (that's the
;;; CMUCL compiler).  Given a call to `length' on type `(or null simple-vector)',
;;; Python isn't quite smart enough to optimize the call unless we do the case
;;; breakdown for it like this.
#+(or cmu scl)
(defmacro length-nv (x)
  (ext:once-only ((x x))
    `(if (null ,x) 0 (cl:length ,x))))
#+sbcl
(defmacro length-nv (x)
  (sb-ext::once-only ((x x))
    `(if (null ,x) 0 (cl:length ,x))))
#-(or cmu scl sbcl)
(defmacro length-nv (x)
  `(length ,x))
