;;;; -*- indent-tabs-mode: nil -*-
|
|
;;;
|
|
;;; swank-mkcl.lisp --- SLIME backend for MKCL.
|
|
;;;
|
|
;;; This code has been placed in the Public Domain. All warranties
|
|
;;; are disclaimed.
|
|
;;;
|
|
|
|
;;; Administrivia
|
|
|
|
(defpackage swank/mkcl
|
|
(:use cl swank/backend))
|
|
|
|
(in-package swank/mkcl)
|
|
|
|
;;(declaim (optimize (debug 3)))
|
|
|
|
(defvar *tmp*)
|
|
|
|
(defimplementation gray-package-name ()
|
|
'#:gray)
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel)
|
|
|
|
(swank/backend::import-swank-mop-symbols :clos
|
|
;; '(:eql-specializer
|
|
;; :eql-specializer-object
|
|
;; :generic-function-declarations
|
|
;; :specializer-direct-methods
|
|
;; :compute-applicable-methods-using-classes)
|
|
nil
|
|
))
|
|
|
|
|
|
;;; UTF8
|
|
|
|
(defimplementation string-to-utf8 (string)
|
|
(mkcl:octets (si:utf-8 string)))
|
|
|
|
(defimplementation utf8-to-string (octets)
|
|
(string (si:utf-8 octets)))
|
|
|
|
|
|
;;;; TCP Server
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel)
|
|
;; At compile-time we need access to the sb-bsd-sockets package for the
|
|
;; the following code to be read properly.
|
|
;; It is a bit a shame we have to load the entire module to get that.
|
|
(require 'sockets))
|
|
|
|
|
|
(defun resolve-hostname (name)
|
|
(car (sb-bsd-sockets:host-ent-addresses
|
|
(sb-bsd-sockets:get-host-by-name name))))
|
|
|
|
(defimplementation create-socket (host port &key backlog)
|
|
(let ((socket (make-instance 'sb-bsd-sockets:inet-socket
|
|
:type :stream
|
|
:protocol :tcp)))
|
|
(setf (sb-bsd-sockets:sockopt-reuse-address socket) t)
|
|
(sb-bsd-sockets:socket-bind socket (resolve-hostname host) port)
|
|
(sb-bsd-sockets:socket-listen socket (or backlog 5))
|
|
socket))
|
|
|
|
(defimplementation local-port (socket)
|
|
(nth-value 1 (sb-bsd-sockets:socket-name socket)))
|
|
|
|
(defimplementation close-socket (socket)
|
|
(sb-bsd-sockets:socket-close socket))
|
|
|
|
(defun accept (socket)
|
|
"Like socket-accept, but retry on EINTR."
|
|
(loop (handler-case
|
|
(return (sb-bsd-sockets:socket-accept socket))
|
|
(sb-bsd-sockets:interrupted-error ()))))
|
|
|
|
(defimplementation accept-connection (socket
|
|
&key external-format
|
|
buffering timeout)
|
|
(declare (ignore timeout))
|
|
(sb-bsd-sockets:socket-make-stream (accept socket)
|
|
:output t ;; bogus
|
|
:input t ;; bogus
|
|
:buffering buffering ;; bogus
|
|
:element-type (if external-format
|
|
'character
|
|
'(unsigned-byte 8))
|
|
:external-format external-format
|
|
))
|
|
|
|
(defimplementation preferred-communication-style ()
|
|
:spawn
|
|
)
|
|
|
|
(defvar *external-format-to-coding-system*
|
|
'((:iso-8859-1
|
|
"latin-1" "latin-1-unix" "iso-latin-1-unix"
|
|
"iso-8859-1" "iso-8859-1-unix")
|
|
(:utf-8 "utf-8" "utf-8-unix")))
|
|
|
|
(defun external-format (coding-system)
|
|
(or (car (rassoc-if (lambda (x) (member coding-system x :test #'equal))
|
|
*external-format-to-coding-system*))
|
|
(find coding-system (si:all-encodings) :test #'string-equal)))
|
|
|
|
(defimplementation find-external-format (coding-system)
|
|
#+unicode (external-format coding-system)
|
|
;; Without unicode support, MKCL uses the one-byte encoding of the
|
|
;; underlying OS, and will barf on anything except :DEFAULT. We
|
|
;; return NIL here for known multibyte encodings, so
|
|
;; SWANK:CREATE-SERVER will barf.
|
|
#-unicode (let ((xf (external-format coding-system)))
|
|
(if (member xf '(:utf-8))
|
|
nil
|
|
:default)))
|
|
|
|
|
|
|
|
;;;; Unix signals
|
|
|
|
(defimplementation install-sigint-handler (handler)
|
|
(let ((old-handler (symbol-function 'si:terminal-interrupt)))
|
|
(setf (symbol-function 'si:terminal-interrupt)
|
|
(if (consp handler)
|
|
(car handler)
|
|
(lambda (&rest args)
|
|
(declare (ignore args))
|
|
(funcall handler)
|
|
(continue))))
|
|
(list old-handler)))
|
|
|
|
|
|
(defimplementation getpid ()
|
|
(mkcl:getpid))
|
|
|
|
(defimplementation set-default-directory (directory)
|
|
(mk-ext::chdir (namestring directory))
|
|
(default-directory))
|
|
|
|
(defimplementation default-directory ()
|
|
(namestring (mk-ext:getcwd)))
|
|
|
|
(defmacro progf (plist &rest forms)
|
|
`(let (_vars _vals)
|
|
(do ((p ,plist (cddr p)))
|
|
((endp p))
|
|
(push (car p) _vars)
|
|
(push (cadr p) _vals))
|
|
(progv _vars _vals ,@forms)
|
|
)
|
|
)
|
|
|
|
(defvar *inferior-lisp-sleeping-post* nil)
|
|
|
|
(defimplementation quit-lisp ()
|
|
(progf (ignore-errors (eval (read-from-string "swank::*saved-global-streams*"))) ;; restore original IO streams.
|
|
(when *inferior-lisp-sleeping-post* (mt:semaphore-signal *inferior-lisp-sleeping-post*))
|
|
;;(mk-ext:quit :verbose t)
|
|
))
|
|
|
|
|
|
;;;; Compilation
|
|
|
|
(defvar *buffer-name* nil)
|
|
(defvar *buffer-start-position*)
|
|
(defvar *buffer-string*)
|
|
(defvar *compile-filename*)
|
|
|
|
(defun signal-compiler-condition (&rest args)
|
|
(signal (apply #'make-condition 'compiler-condition args)))
|
|
|
|
#|
|
|
(defun handle-compiler-warning (condition)
|
|
(signal-compiler-condition
|
|
:original-condition condition
|
|
:message (format nil "~A" condition)
|
|
:severity :warning
|
|
:location
|
|
(if *buffer-name*
|
|
(make-location (list :buffer *buffer-name*)
|
|
(list :offset *buffer-start-position* 0))
|
|
;; ;; compiler::*current-form*
|
|
;; (if compiler::*current-function*
|
|
;; (make-location (list :file *compile-filename*)
|
|
;; (list :function-name
|
|
;; (symbol-name
|
|
;; (slot-value compiler::*current-function*
|
|
;; 'compiler::name))))
|
|
(list :error "No location found.")
|
|
;; )
|
|
)))
|
|
|#
|
|
|
|
#|
|
|
(defun condition-location (condition)
|
|
(let ((file (compiler:compiler-message-file condition))
|
|
(position (compiler:compiler-message-file-position condition)))
|
|
(if (and position (not (minusp position)))
|
|
(if *buffer-name*
|
|
(make-buffer-location *buffer-name*
|
|
*buffer-start-position*
|
|
position)
|
|
(make-file-location file position))
|
|
(make-error-location "No location found."))))
|
|
|#
|
|
|
|
(defun condition-location (condition)
|
|
(if *buffer-name*
|
|
(make-location (list :buffer *buffer-name*)
|
|
(list :offset *buffer-start-position* 0))
|
|
;; ;; compiler::*current-form* ;
|
|
;; (if compiler::*current-function* ;
|
|
;; (make-location (list :file *compile-filename*) ;
|
|
;; (list :function-name ;
|
|
;; (symbol-name ;
|
|
;; (slot-value compiler::*current-function* ;
|
|
;; 'compiler::name)))) ;
|
|
(if (typep condition 'compiler::compiler-message)
|
|
(make-location (list :file (namestring (compiler:compiler-message-file condition)))
|
|
(list :end-position (compiler:compiler-message-file-end-position condition)))
|
|
(list :error "No location found."))
|
|
)
|
|
)
|
|
|
|
(defun handle-compiler-message (condition)
|
|
(unless (typep condition 'compiler::compiler-note)
|
|
(signal-compiler-condition
|
|
:original-condition condition
|
|
:message (princ-to-string condition)
|
|
:severity (etypecase condition
|
|
(compiler:compiler-fatal-error :error)
|
|
(compiler:compiler-error :error)
|
|
(error :error)
|
|
(style-warning :style-warning)
|
|
(warning :warning))
|
|
:location (condition-location condition))))
|
|
|
|
(defimplementation call-with-compilation-hooks (function)
|
|
(handler-bind ((compiler:compiler-message #'handle-compiler-message))
|
|
(funcall function)))
|
|
|
|
(defimplementation swank-compile-file (input-file output-file
|
|
load-p external-format
|
|
&key policy)
|
|
(declare (ignore policy))
|
|
(with-compilation-hooks ()
|
|
(let ((*buffer-name* nil)
|
|
(*compile-filename* input-file))
|
|
(handler-bind (#|
|
|
(compiler::compiler-note
|
|
#'(lambda (n)
|
|
(format t "~%swank saw a compiler note: ~A~%" n) (finish-output) nil))
|
|
(compiler::compiler-warning
|
|
#'(lambda (w)
|
|
(format t "~%swank saw a compiler warning: ~A~%" w) (finish-output) nil))
|
|
(compiler::compiler-error
|
|
#'(lambda (e)
|
|
(format t "~%swank saw a compiler error: ~A~%" e) (finish-output) nil))
|
|
|#
|
|
)
|
|
(multiple-value-bind (output-truename warnings-p failure-p)
|
|
(compile-file input-file :output-file output-file :external-format external-format)
|
|
(values output-truename warnings-p
|
|
(or failure-p
|
|
(and load-p (not (load output-truename))))))))))
|
|
|
|
(defimplementation swank-compile-string (string &key buffer position filename policy)
|
|
(declare (ignore filename policy))
|
|
(with-compilation-hooks ()
|
|
(let ((*buffer-name* buffer)
|
|
(*buffer-start-position* position)
|
|
(*buffer-string* string))
|
|
(with-input-from-string (s string)
|
|
(when position (file-position position))
|
|
(compile-from-stream s)))))
|
|
|
|
(defun compile-from-stream (stream)
|
|
(let ((file (mkcl:mkstemp "TMP:MKCL-SWANK-TMPXXXXXX"))
|
|
output-truename
|
|
warnings-p
|
|
failure-p
|
|
)
|
|
(with-open-file (s file :direction :output :if-exists :overwrite)
|
|
(do ((line (read-line stream nil) (read-line stream nil)))
|
|
((not line))
|
|
(write-line line s)))
|
|
(unwind-protect
|
|
(progn
|
|
(multiple-value-setq (output-truename warnings-p failure-p)
|
|
(compile-file file))
|
|
(and (not failure-p) (load output-truename)))
|
|
(when (probe-file file) (delete-file file))
|
|
(when (probe-file output-truename) (delete-file output-truename)))))
|
|
|
|
|
|
;;;; Documentation
|
|
|
|
(defun grovel-docstring-for-arglist (name type)
|
|
(flet ((compute-arglist-offset (docstring)
|
|
(when docstring
|
|
(let ((pos1 (search "Args: " docstring)))
|
|
(if pos1
|
|
(+ pos1 6)
|
|
(let ((pos2 (search "Syntax: " docstring)))
|
|
(when pos2
|
|
(+ pos2 8))))))))
|
|
(let* ((docstring (si::get-documentation name type))
|
|
(pos (compute-arglist-offset docstring)))
|
|
(if pos
|
|
(multiple-value-bind (arglist errorp)
|
|
(ignore-errors
|
|
(values (read-from-string docstring t nil :start pos)))
|
|
(if (or errorp (not (listp arglist)))
|
|
:not-available
|
|
arglist
|
|
))
|
|
:not-available ))))
|
|
|
|
(defimplementation arglist (name)
|
|
(cond ((and (symbolp name) (special-operator-p name))
|
|
(let ((arglist (grovel-docstring-for-arglist name 'function)))
|
|
(if (consp arglist) (cdr arglist) arglist)))
|
|
((and (symbolp name) (macro-function name))
|
|
(let ((arglist (grovel-docstring-for-arglist name 'function)))
|
|
(if (consp arglist) (cdr arglist) arglist)))
|
|
((or (functionp name) (fboundp name))
|
|
(multiple-value-bind (name fndef)
|
|
(if (functionp name)
|
|
(values (function-name name) name)
|
|
(values name (fdefinition name)))
|
|
(let ((fle (function-lambda-expression fndef)))
|
|
(case (car fle)
|
|
(si:lambda-block (caddr fle))
|
|
(t (typecase fndef
|
|
(generic-function (clos::generic-function-lambda-list fndef))
|
|
(compiled-function (grovel-docstring-for-arglist name 'function))
|
|
(function :not-available)))))))
|
|
(t :not-available)))
|
|
|
|
(defimplementation function-name (f)
|
|
(si:compiled-function-name f)
|
|
)
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel)
|
|
;; At compile-time we need access to the walker package for the
|
|
;; the following code to be read properly.
|
|
;; It is a bit a shame we have to load the entire module to get that.
|
|
(require 'walker))
|
|
|
|
(defimplementation macroexpand-all (form &optional env)
|
|
(declare (ignore env))
|
|
(walker:macroexpand-all form))
|
|
|
|
(defimplementation describe-symbol-for-emacs (symbol)
|
|
(let ((result '()))
|
|
(dolist (type '(:VARIABLE :FUNCTION :CLASS))
|
|
(let ((doc (describe-definition symbol type)))
|
|
(when doc
|
|
(setf result (list* type doc result)))))
|
|
result))
|
|
|
|
(defimplementation describe-definition (name type)
|
|
(case type
|
|
(:variable (documentation name 'variable))
|
|
(:function (documentation name 'function))
|
|
(:class (documentation name 'class))
|
|
(t nil)))
|
|
|
|
;;; Debugging
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel)
|
|
(import
|
|
'(si::*break-env*
|
|
si::*ihs-top*
|
|
si::*ihs-current*
|
|
si::*ihs-base*
|
|
si::*frs-base*
|
|
si::*frs-top*
|
|
si::*tpl-commands*
|
|
si::*tpl-level*
|
|
si::frs-top
|
|
si::ihs-top
|
|
si::ihs-fun
|
|
si::ihs-env
|
|
si::sch-frs-base
|
|
si::set-break-env
|
|
si::set-current-ihs
|
|
si::tpl-commands)))
|
|
|
|
(defvar *backtrace* '())
|
|
|
|
(defun in-swank-package-p (x)
|
|
(and
|
|
(symbolp x)
|
|
(member (symbol-package x)
|
|
(list #.(find-package :swank)
|
|
#.(find-package :swank/backend)
|
|
#.(ignore-errors (find-package :swank-mop))
|
|
#.(ignore-errors (find-package :swank-loader))))
|
|
t))
|
|
|
|
(defun is-swank-source-p (name)
|
|
(setf name (pathname name))
|
|
#+(or)
|
|
(pathname-match-p
|
|
name
|
|
(make-pathname :defaults swank-loader::*source-directory*
|
|
:name (pathname-name name)
|
|
:type (pathname-type name)
|
|
:version (pathname-version name)))
|
|
nil)
|
|
|
|
(defun is-ignorable-fun-p (x)
|
|
(or
|
|
(in-swank-package-p (frame-name x))
|
|
(multiple-value-bind (file position)
|
|
(ignore-errors (si::compiled-function-file (car x)))
|
|
(declare (ignore position))
|
|
(if file (is-swank-source-p file)))))
|
|
|
|
(defmacro find-ihs-top (x)
|
|
(declare (ignore x))
|
|
'(si::ihs-top))
|
|
|
|
(defimplementation call-with-debugging-environment (debugger-loop-fn)
|
|
(declare (type function debugger-loop-fn))
|
|
(let* (;;(*tpl-commands* si::tpl-commands)
|
|
(*ihs-base* 0)
|
|
(*ihs-top* (find-ihs-top 'call-with-debugging-environment))
|
|
(*ihs-current* *ihs-top*)
|
|
(*frs-base* (or (sch-frs-base 0 #|*frs-top*|# *ihs-base*) (1+ (frs-top))))
|
|
(*frs-top* (frs-top))
|
|
(*read-suppress* nil)
|
|
;;(*tpl-level* (1+ *tpl-level*))
|
|
(*backtrace* (loop for ihs from 0 below *ihs-top*
|
|
collect (list (si::ihs-fun ihs)
|
|
(si::ihs-env ihs)
|
|
nil))))
|
|
(declare (special *ihs-current*))
|
|
(loop for f from *frs-base* to *frs-top*
|
|
do (let ((i (- (si::frs-ihs f) *ihs-base* 1)))
|
|
(when (plusp i)
|
|
(let* ((x (elt *backtrace* i))
|
|
(name (si::frs-tag f)))
|
|
(unless (mkcl:fixnump name)
|
|
(push name (third x)))))))
|
|
(setf *backtrace* (remove-if #'is-ignorable-fun-p (nreverse *backtrace*)))
|
|
(setf *tmp* *backtrace*)
|
|
(set-break-env)
|
|
(set-current-ihs)
|
|
(let ((*ihs-base* *ihs-top*))
|
|
(funcall debugger-loop-fn))))
|
|
|
|
(defimplementation call-with-debugger-hook (hook fun)
|
|
(let ((*debugger-hook* hook)
|
|
(*ihs-base* (find-ihs-top 'call-with-debugger-hook)))
|
|
(funcall fun)))
|
|
|
|
(defimplementation compute-backtrace (start end)
|
|
(when (numberp end)
|
|
(setf end (min end (length *backtrace*))))
|
|
(loop for f in (subseq *backtrace* start end)
|
|
collect f))
|
|
|
|
(defimplementation format-sldb-condition (condition)
|
|
"Format a condition for display in SLDB."
|
|
;;(princ-to-string condition)
|
|
(format nil "~A~%In thread: ~S" condition mt:*thread*)
|
|
)
|
|
|
|
(defun frame-name (frame)
|
|
(let ((x (first frame)))
|
|
(if (symbolp x)
|
|
x
|
|
(function-name x))))
|
|
|
|
(defun function-position (fun)
|
|
(multiple-value-bind (file position)
|
|
(si::compiled-function-file fun)
|
|
(and file (make-location
|
|
`(:file ,(if (stringp file) file (namestring file)))
|
|
;;`(:position ,position)
|
|
`(:end-position , position)))))
|
|
|
|
(defun frame-function (frame)
|
|
(let* ((x (first frame))
|
|
fun position)
|
|
(etypecase x
|
|
(symbol (and (fboundp x)
|
|
(setf fun (fdefinition x)
|
|
position (function-position fun))))
|
|
(function (setf fun x position (function-position x))))
|
|
(values fun position)))
|
|
|
|
(defun frame-decode-env (frame)
|
|
(let ((functions '())
|
|
(blocks '())
|
|
(variables '()))
|
|
(setf frame (si::decode-ihs-env (second frame)))
|
|
(dolist (record frame)
|
|
(let* ((record0 (car record))
|
|
(record1 (cdr record)))
|
|
(cond ((or (symbolp record0) (stringp record0))
|
|
(setq variables (acons record0 record1 variables)))
|
|
((not (mkcl:fixnump record0))
|
|
(push record1 functions))
|
|
((symbolp record1)
|
|
(push record1 blocks))
|
|
(t
|
|
))))
|
|
(values functions blocks variables)))
|
|
|
|
(defimplementation print-frame (frame stream)
|
|
(let ((function (first frame)))
|
|
(let ((fname
|
|
;;; (cond ((symbolp function) function)
|
|
;;; ((si:instancep function) (slot-value function 'name))
|
|
;;; ((compiled-function-p function)
|
|
;;; (or (si::compiled-function-name function) 'lambda))
|
|
;;; (t :zombi))
|
|
(si::get-fname function)
|
|
))
|
|
(if (eq fname 'si::bytecode)
|
|
(format stream "~A [Evaluation of: ~S]"
|
|
fname (function-lambda-expression function))
|
|
(format stream "~A" fname)
|
|
)
|
|
(when (si::closurep function)
|
|
(format stream
|
|
", closure generated from ~A"
|
|
(si::get-fname (si:closure-producer function)))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defimplementation frame-source-location (frame-number)
|
|
(nth-value 1 (frame-function (elt *backtrace* frame-number))))
|
|
|
|
(defimplementation frame-catch-tags (frame-number)
|
|
(third (elt *backtrace* frame-number)))
|
|
|
|
(defimplementation frame-locals (frame-number)
|
|
(loop for (name . value) in (nth-value 2 (frame-decode-env (elt *backtrace* frame-number)))
|
|
with i = 0
|
|
collect (list :name name :id (prog1 i (incf i)) :value value)))
|
|
|
|
(defimplementation frame-var-value (frame-number var-id)
|
|
(cdr (elt (nth-value 2 (frame-decode-env (elt *backtrace* frame-number))) var-id)))
|
|
|
|
(defimplementation disassemble-frame (frame-number)
|
|
(let ((fun (frame-fun (elt *backtrace* frame-number))))
|
|
(disassemble fun)))
|
|
|
|
(defimplementation eval-in-frame (form frame-number)
|
|
(let ((env (second (elt *backtrace* frame-number))))
|
|
(si:eval-in-env form env)))
|
|
|
|
#|
|
|
(defimplementation gdb-initial-commands ()
|
|
;; These signals are used by the GC.
|
|
#+linux '("handle SIGPWR noprint nostop"
|
|
"handle SIGXCPU noprint nostop"))
|
|
|
|
(defimplementation command-line-args ()
|
|
(loop for n from 0 below (si:argc) collect (si:argv n)))
|
|
|#
|
|
|
|
;;;; Inspector
|
|
|
|
(defmethod emacs-inspect ((o t))
|
|
; ecl clos support leaves some to be desired
|
|
(cond
|
|
((streamp o)
|
|
(list*
|
|
(format nil "~S is an ordinary stream~%" o)
|
|
(append
|
|
(list
|
|
"Open for "
|
|
(cond
|
|
((ignore-errors (interactive-stream-p o)) "Interactive")
|
|
((and (input-stream-p o) (output-stream-p o)) "Input and output")
|
|
((input-stream-p o) "Input")
|
|
((output-stream-p o) "Output"))
|
|
`(:newline) `(:newline))
|
|
(label-value-line*
|
|
("Element type" (stream-element-type o))
|
|
("External format" (stream-external-format o)))
|
|
(ignore-errors (label-value-line*
|
|
("Broadcast streams" (broadcast-stream-streams o))))
|
|
(ignore-errors (label-value-line*
|
|
("Concatenated streams" (concatenated-stream-streams o))))
|
|
(ignore-errors (label-value-line*
|
|
("Echo input stream" (echo-stream-input-stream o))))
|
|
(ignore-errors (label-value-line*
|
|
("Echo output stream" (echo-stream-output-stream o))))
|
|
(ignore-errors (label-value-line*
|
|
("Output String" (get-output-stream-string o))))
|
|
(ignore-errors (label-value-line*
|
|
("Synonym symbol" (synonym-stream-symbol o))))
|
|
(ignore-errors (label-value-line*
|
|
("Input stream" (two-way-stream-input-stream o))))
|
|
(ignore-errors (label-value-line*
|
|
("Output stream" (two-way-stream-output-stream o)))))))
|
|
((si:instancep o) ;;t
|
|
(let* ((cl (si:instance-class o))
|
|
(slots (clos::class-slots cl)))
|
|
(list* (format nil "~S is an instance of class ~A~%"
|
|
o (clos::class-name cl))
|
|
(loop for x in slots append
|
|
(let* ((name (clos::slot-definition-name x))
|
|
(value (if (slot-boundp o name)
|
|
(clos::slot-value o name)
|
|
"Unbound"
|
|
)))
|
|
(list
|
|
(format nil "~S: " name)
|
|
`(:value ,value)
|
|
`(:newline)))))))
|
|
(t (list (format nil "~A" o)))))
|
|
|
|
;;;; Definitions
|
|
|
|
(defimplementation find-definitions (name)
|
|
(if (fboundp name)
|
|
(let ((tmp (find-source-location (symbol-function name))))
|
|
`(((defun ,name) ,tmp)))))
|
|
|
|
(defimplementation find-source-location (obj)
|
|
(setf *tmp* obj)
|
|
(or
|
|
(typecase obj
|
|
(function
|
|
(multiple-value-bind (file pos) (ignore-errors (si::compiled-function-file obj))
|
|
(if (and file pos)
|
|
(make-location
|
|
`(:file ,(if (stringp file) file (namestring file)))
|
|
`(:end-position ,pos) ;; `(:position ,pos)
|
|
`(:snippet
|
|
,(with-open-file (s file)
|
|
(file-position s pos)
|
|
(skip-comments-and-whitespace s)
|
|
(read-snippet s))))))))
|
|
`(:error (format nil "Source definition of ~S not found" obj))))
|
|
|
|
;;;; Profiling
|
|
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel)
|
|
;; At compile-time we need access to the profile package for the
|
|
;; the following code to be read properly.
|
|
;; It is a bit a shame we have to load the entire module to get that.
|
|
(require 'profile))
|
|
|
|
|
|
(defimplementation profile (fname)
|
|
(when fname (eval `(profile:profile ,fname))))
|
|
|
|
(defimplementation unprofile (fname)
|
|
(when fname (eval `(profile:unprofile ,fname))))
|
|
|
|
(defimplementation unprofile-all ()
|
|
(profile:unprofile-all)
|
|
"All functions unprofiled.")
|
|
|
|
(defimplementation profile-report ()
|
|
(profile:report))
|
|
|
|
(defimplementation profile-reset ()
|
|
(profile:reset)
|
|
"Reset profiling counters.")
|
|
|
|
(defimplementation profiled-functions ()
|
|
(profile:profile))
|
|
|
|
(defimplementation profile-package (package callers methods)
|
|
(declare (ignore callers methods))
|
|
(eval `(profile:profile ,(package-name (find-package package)))))
|
|
|
|
|
|
;;;; Threads
|
|
|
|
(defvar *thread-id-counter* 0)
|
|
|
|
(defvar *thread-id-counter-lock*
|
|
(mt:make-lock :name "thread id counter lock"))
|
|
|
|
(defun next-thread-id ()
|
|
(mt:with-lock (*thread-id-counter-lock*)
|
|
(incf *thread-id-counter*))
|
|
)
|
|
|
|
(defparameter *thread-id-map* (make-hash-table))
|
|
(defparameter *id-thread-map* (make-hash-table))
|
|
|
|
(defvar *thread-id-map-lock*
|
|
(mt:make-lock :name "thread id map lock"))
|
|
|
|
(defparameter +default-thread-local-variables+
|
|
'(*macroexpand-hook*
|
|
*default-pathname-defaults*
|
|
*readtable*
|
|
*random-state*
|
|
*compile-print*
|
|
*compile-verbose*
|
|
*load-print*
|
|
*load-verbose*
|
|
*print-array*
|
|
*print-base*
|
|
*print-case*
|
|
*print-circle*
|
|
*print-escape*
|
|
*print-gensym*
|
|
*print-length*
|
|
*print-level*
|
|
*print-lines*
|
|
*print-miser-width*
|
|
*print-pprint-dispatch*
|
|
*print-pretty*
|
|
*print-radix*
|
|
*print-readably*
|
|
*print-right-margin*
|
|
*read-base*
|
|
*read-default-float-format*
|
|
*read-eval*
|
|
*read-suppress*
|
|
))
|
|
|
|
(defun thread-local-default-bindings ()
|
|
(let (local)
|
|
(dolist (var +default-thread-local-variables+ local)
|
|
(setq local (acons var (symbol-value var) local))
|
|
)))
|
|
|
|
;; mkcl doesn't have weak pointers
|
|
(defimplementation spawn (fn &key name initial-bindings)
|
|
(let* ((local-defaults (thread-local-default-bindings))
|
|
(thread
|
|
;;(mt:make-thread :name name)
|
|
(mt:make-thread :name name
|
|
:initial-bindings (nconc initial-bindings
|
|
local-defaults))
|
|
)
|
|
(id (next-thread-id)))
|
|
(mt:with-lock (*thread-id-map-lock*)
|
|
(setf (gethash id *thread-id-map*) thread)
|
|
(setf (gethash thread *id-thread-map*) id))
|
|
(mt:thread-preset
|
|
thread
|
|
#'(lambda ()
|
|
(unwind-protect
|
|
(progn
|
|
;;(format t "~&Starting thread: ~S.~%" name) (finish-output)
|
|
(mt:thread-detach nil)
|
|
(funcall fn))
|
|
(progn
|
|
;;(format t "~&Wrapping up thread: ~S.~%" name) (finish-output)
|
|
(mt:with-lock (*thread-id-map-lock*)
|
|
(remhash thread *id-thread-map*)
|
|
(remhash id *thread-id-map*))
|
|
;;(format t "~&Finished thread: ~S~%" name) (finish-output)
|
|
))))
|
|
(mt:thread-enable thread)
|
|
(mt:thread-yield)
|
|
thread
|
|
))
|
|
|
|
(defimplementation thread-id (thread)
|
|
(block thread-id
|
|
(mt:with-lock (*thread-id-map-lock*)
|
|
(or (gethash thread *id-thread-map*)
|
|
(let ((id (next-thread-id)))
|
|
(setf (gethash id *thread-id-map*) thread)
|
|
(setf (gethash thread *id-thread-map*) id)
|
|
id)))))
|
|
|
|
(defimplementation find-thread (id)
|
|
(mt:with-lock (*thread-id-map-lock*)
|
|
(gethash id *thread-id-map*)))
|
|
|
|
(defimplementation thread-name (thread)
|
|
(mt:thread-name thread))
|
|
|
|
(defimplementation thread-status (thread)
|
|
(if (mt:thread-active-p thread)
|
|
"RUNNING"
|
|
"STOPPED"))
|
|
|
|
(defimplementation make-lock (&key name)
|
|
(mt:make-lock :name name :recursive t))
|
|
|
|
(defimplementation call-with-lock-held (lock function)
|
|
(declare (type function function))
|
|
(mt:with-lock (lock) (funcall function)))
|
|
|
|
(defimplementation current-thread ()
|
|
mt:*thread*)
|
|
|
|
(defimplementation all-threads ()
|
|
(mt:all-threads))
|
|
|
|
(defimplementation interrupt-thread (thread fn)
|
|
(mt:interrupt-thread thread fn))
|
|
|
|
(defimplementation kill-thread (thread)
|
|
(mt:interrupt-thread thread #'mt:terminate-thread)
|
|
)
|
|
|
|
(defimplementation thread-alive-p (thread)
|
|
(mt:thread-active-p thread))
|
|
|
|
(defvar *mailbox-lock* (mt:make-lock :name "mailbox lock"))
|
|
(defvar *mailboxes* (list))
|
|
(declaim (type list *mailboxes*))
|
|
|
|
(defstruct (mailbox (:conc-name mailbox.))
|
|
thread
|
|
locked-by
|
|
(mutex (mt:make-lock :name "thread mailbox"))
|
|
(semaphore (mt:make-semaphore))
|
|
(queue '() :type list))
|
|
|
|
(defun mailbox (thread)
|
|
"Return THREAD's mailbox."
|
|
(mt:with-lock (*mailbox-lock*)
|
|
(or (find thread *mailboxes* :key #'mailbox.thread)
|
|
(let ((mb (make-mailbox :thread thread)))
|
|
(push mb *mailboxes*)
|
|
mb))))
|
|
|
|
(defimplementation send (thread message)
|
|
(handler-case
|
|
(let* ((mbox (mailbox thread))
|
|
(mutex (mailbox.mutex mbox)))
|
|
;; (mt:interrupt-thread
|
|
;; thread
|
|
;; (lambda ()
|
|
;; (mt:with-lock (mutex)
|
|
;; (setf (mailbox.queue mbox)
|
|
;; (nconc (mailbox.queue mbox) (list message))))))
|
|
|
|
;; (format t "~&! thread = ~S~% thread = ~S~% message = ~S~%"
|
|
;; mt:*thread* thread message) (finish-output)
|
|
(mt:with-lock (mutex)
|
|
(setf (mailbox.locked-by mbox) mt:*thread*)
|
|
(setf (mailbox.queue mbox)
|
|
(nconc (mailbox.queue mbox) (list message)))
|
|
;;(format t "*") (finish-output)
|
|
(handler-case
|
|
(mt:semaphore-signal (mailbox.semaphore mbox))
|
|
(condition (condition)
|
|
(format t "Something went bad with semaphore-signal ~A" condition) (finish-output)
|
|
;;(break)
|
|
))
|
|
(setf (mailbox.locked-by mbox) nil)
|
|
)
|
|
;;(format t "+") (finish-output)
|
|
)
|
|
(condition (condition)
|
|
(format t "~&Error in send: ~S~%" condition) (finish-output))
|
|
)
|
|
)
|
|
|
|
;; (defimplementation receive ()
|
|
;; (block got-mail
|
|
;; (let* ((mbox (mailbox mt:*thread*))
|
|
;; (mutex (mailbox.mutex mbox)))
|
|
;; (loop
|
|
;; (mt:with-lock (mutex)
|
|
;; (if (mailbox.queue mbox)
|
|
;; (return-from got-mail (pop (mailbox.queue mbox)))))
|
|
;; ;;interrupt-thread will halt this if it takes longer than 1sec
|
|
;; (sleep 1)))))
|
|
|
|
|
|
(defimplementation receive-if (test &optional timeout)
|
|
(handler-case
|
|
(let* ((mbox (mailbox (current-thread)))
|
|
(mutex (mailbox.mutex mbox))
|
|
got-one)
|
|
(assert (or (not timeout) (eq timeout t)))
|
|
(loop
|
|
(check-slime-interrupts)
|
|
;;(format t "~&: ~S~%" mt:*thread*) (finish-output)
|
|
(handler-case
|
|
(setq got-one (mt:semaphore-wait (mailbox.semaphore mbox) 2))
|
|
(condition (condition)
|
|
(format t "~&In (swank-mkcl) receive-if: Something went bad with semaphore-wait ~A~%" condition)
|
|
(finish-output)
|
|
nil
|
|
)
|
|
)
|
|
(mt:with-lock (mutex)
|
|
(setf (mailbox.locked-by mbox) mt:*thread*)
|
|
(let* ((q (mailbox.queue mbox))
|
|
(tail (member-if test q)))
|
|
(when tail
|
|
(setf (mailbox.queue mbox) (nconc (ldiff q tail) (cdr tail)))
|
|
(setf (mailbox.locked-by mbox) nil)
|
|
;;(format t "~&thread ~S received: ~S~%" mt:*thread* (car tail))
|
|
(return (car tail))))
|
|
(setf (mailbox.locked-by mbox) nil)
|
|
)
|
|
|
|
;;(format t "/ ~S~%" mt:*thread*) (finish-output)
|
|
(when (eq timeout t) (return (values nil t)))
|
|
;; (unless got-one
|
|
;; (format t "~&In (swank-mkcl) receive-if: semaphore-wait timed out!~%"))
|
|
)
|
|
)
|
|
(condition (condition)
|
|
(format t "~&Error in (swank-mkcl) receive-if: ~S, ~A~%" condition condition) (finish-output)
|
|
nil
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
(defmethod stream-finish-output ((stream stream))
|
|
(finish-output stream))
|
|
|
|
|
|
;;
|
|
|
|
;;#+windows
|
|
(defimplementation doze-in-repl ()
|
|
(setq *inferior-lisp-sleeping-post* (mt:make-semaphore))
|
|
;;(loop (sleep 1))
|
|
(mt:semaphore-wait *inferior-lisp-sleeping-post*)
|
|
(mk-ext:quit :verbose t)
|
|
)
|
|
|