;;; -*- indent-tabs-mode: nil; outline-regexp: ";;;;+" -*-
|
|
;;;
|
|
;;; Scieneer Common Lisp code for SLIME.
|
|
;;;
|
|
;;; This code has been placed in the Public Domain. All warranties
|
|
;;; are disclaimed.
|
|
;;;
|
|
|
|
(defpackage swank/scl
|
|
(:use cl swank/backend swank/source-path-parser swank/source-file-cache))
|
|
|
|
(in-package swank/scl)
|
|
|
|
|
|
|
|
;;; swank-mop
|
|
|
|
(import-swank-mop-symbols :clos '(:slot-definition-documentation))
|
|
|
|
(defun swank-mop:slot-definition-documentation (slot)
|
|
(documentation slot t))
|
|
|
|
|
|
;;;; TCP server
|
|
;;;
|
|
;;; SCL only supports the :spawn communication style.
|
|
;;;
|
|
|
|
(defimplementation preferred-communication-style ()
|
|
:spawn)
|
|
|
|
(defimplementation create-socket (host port &key backlog)
|
|
(let ((addr (resolve-hostname host)))
|
|
(ext:create-inet-listener port :stream :host addr :reuse-address t
|
|
:backlog (or backlog 5))))
|
|
|
|
(defimplementation local-port (socket)
|
|
(nth-value 1 (ext::get-socket-host-and-port (socket-fd socket))))
|
|
|
|
(defimplementation close-socket (socket)
|
|
(ext:close-socket (socket-fd socket)))
|
|
|
|
(defimplementation accept-connection (socket
|
|
&key external-format buffering timeout)
|
|
(let ((buffering (or buffering :full))
|
|
(fd (socket-fd socket)))
|
|
(loop
|
|
(let ((ready (sys:wait-until-fd-usable fd :input timeout)))
|
|
(unless ready
|
|
(error "Timeout accepting connection on socket: ~S~%" socket)))
|
|
(let ((new-fd (ignore-errors (ext:accept-tcp-connection fd))))
|
|
(when new-fd
|
|
(return (make-socket-io-stream new-fd external-format
|
|
(ecase buffering
|
|
((t) :full)
|
|
((nil) :none)
|
|
(:line :line)))))))))
|
|
|
|
(defimplementation set-stream-timeout (stream timeout)
|
|
(check-type timeout (or null real))
|
|
(if (fboundp 'ext::stream-timeout)
|
|
(setf (ext::stream-timeout stream) timeout)
|
|
(setf (slot-value (slot-value stream 'lisp::stream) 'lisp::timeout)
|
|
timeout)))
|
|
|
|
;;;;; Sockets
|
|
|
|
(defun socket-fd (socket)
|
|
"Return the file descriptor for the socket represented by 'socket."
|
|
(etypecase socket
|
|
(fixnum socket)
|
|
(stream (sys:fd-stream-fd socket))))
|
|
|
|
(defun resolve-hostname (hostname)
|
|
"Return the IP address of 'hostname as an integer (in host byte-order)."
|
|
(let ((hostent (ext:lookup-host-entry hostname)))
|
|
(car (ext:host-entry-addr-list hostent))))
|
|
|
|
(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")
|
|
(:euc-jp "euc-jp" "euc-jp-unix")))
|
|
|
|
(defimplementation find-external-format (coding-system)
|
|
(car (rassoc-if (lambda (x) (member coding-system x :test #'equal))
|
|
*external-format-to-coding-system*)))
|
|
|
|
(defun make-socket-io-stream (fd external-format buffering)
|
|
"Create a new input/output fd-stream for 'fd."
|
|
(cond ((not external-format)
|
|
(sys:make-fd-stream fd :input t :output t :buffering buffering
|
|
:element-type '(unsigned-byte 8)))
|
|
(t
|
|
(let* ((stream (sys:make-fd-stream fd :input t :output t
|
|
:element-type 'base-char
|
|
:buffering buffering
|
|
:external-format external-format)))
|
|
;; Ignore character conversion errors. Without this the
|
|
;; communication channel is prone to lockup if a character
|
|
;; conversion error occurs.
|
|
(setf (lisp::character-conversion-stream-input-error-value stream)
|
|
#\?)
|
|
(setf (lisp::character-conversion-stream-output-error-value stream)
|
|
#\?)
|
|
stream))))
|
|
|
|
|
|
;;;; Stream handling
|
|
|
|
(defimplementation gray-package-name ()
|
|
'#:ext)
|
|
|
|
|
|
;;;; Compilation Commands
|
|
|
|
(defvar *previous-compiler-condition* nil
|
|
"Used to detect duplicates.")
|
|
|
|
(defvar *previous-context* nil
|
|
"Previous compiler error context.")
|
|
|
|
(defvar *buffer-name* nil
|
|
"The name of the Emacs buffer we are compiling from.
|
|
Nil if we aren't compiling from a buffer.")
|
|
|
|
(defvar *buffer-start-position* nil)
|
|
(defvar *buffer-substring* nil)
|
|
|
|
(defimplementation call-with-compilation-hooks (function)
|
|
(let ((*previous-compiler-condition* nil)
|
|
(*previous-context* nil)
|
|
(*print-readably* nil))
|
|
(handler-bind ((c::compiler-error #'handle-notification-condition)
|
|
(c::style-warning #'handle-notification-condition)
|
|
(c::warning #'handle-notification-condition))
|
|
(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)
|
|
(ext:*ignore-extra-close-parentheses* nil))
|
|
(multiple-value-bind (output-file warnings-p failure-p)
|
|
(compile-file input-file
|
|
:output-file output-file
|
|
:external-format external-format)
|
|
(values output-file warnings-p
|
|
(or failure-p
|
|
(when load-p
|
|
;; Cache the latest source file for definition-finding.
|
|
(source-cache-get input-file
|
|
(file-write-date input-file))
|
|
(not (load output-file)))))))))
|
|
|
|
(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-substring* string))
|
|
(with-input-from-string (stream string)
|
|
(ext:compile-from-stream
|
|
stream
|
|
:source-info `(:emacs-buffer ,buffer
|
|
:emacs-buffer-offset ,position
|
|
:emacs-buffer-string ,string))))))
|
|
|
|
|
|
;;;;; Trapping notes
|
|
;;;
|
|
;;; We intercept conditions from the compiler and resignal them as
|
|
;;; `swank:compiler-condition's.
|
|
|
|
(defun handle-notification-condition (condition)
|
|
"Handle a condition caused by a compiler warning."
|
|
(unless (eq condition *previous-compiler-condition*)
|
|
(let ((context (c::find-error-context nil)))
|
|
(setq *previous-compiler-condition* condition)
|
|
(setq *previous-context* context)
|
|
(signal-compiler-condition condition context))))
|
|
|
|
(defun signal-compiler-condition (condition context)
|
|
(signal 'compiler-condition
|
|
:original-condition condition
|
|
:severity (severity-for-emacs condition)
|
|
:message (brief-compiler-message-for-emacs condition)
|
|
:source-context (compiler-error-context context)
|
|
:location (if (read-error-p condition)
|
|
(read-error-location condition)
|
|
(compiler-note-location context))))
|
|
|
|
(defun severity-for-emacs (condition)
|
|
"Return the severity of 'condition."
|
|
(etypecase condition
|
|
((satisfies read-error-p) :read-error)
|
|
(c::compiler-error :error)
|
|
(c::style-warning :note)
|
|
(c::warning :warning)))
|
|
|
|
(defun read-error-p (condition)
|
|
(eq (type-of condition) 'c::compiler-read-error))
|
|
|
|
(defun brief-compiler-message-for-emacs (condition)
|
|
"Briefly describe a compiler error for Emacs.
|
|
When Emacs presents the message it already has the source popped up
|
|
and the source form highlighted. This makes much of the information in
|
|
the error-context redundant."
|
|
(princ-to-string condition))
|
|
|
|
(defun compiler-error-context (error-context)
|
|
"Describe a compiler error for Emacs including context information."
|
|
(declare (type (or c::compiler-error-context null) error-context))
|
|
(multiple-value-bind (enclosing source)
|
|
(if error-context
|
|
(values (c::compiler-error-context-enclosing-source error-context)
|
|
(c::compiler-error-context-source error-context)))
|
|
(if (and enclosing source)
|
|
(format nil "~@[--> ~{~<~%--> ~1:;~A~> ~}~%~]~@[~{==>~%~A~^~%~}~]"
|
|
enclosing source))))
|
|
|
|
(defun read-error-location (condition)
|
|
(let* ((finfo (car (c::source-info-current-file c::*source-info*)))
|
|
(file (c::file-info-name finfo))
|
|
(pos (c::compiler-read-error-position condition)))
|
|
(cond ((and (eq file :stream) *buffer-name*)
|
|
(make-location (list :buffer *buffer-name*)
|
|
(list :offset *buffer-start-position* pos)))
|
|
((and (pathnamep file) (not *buffer-name*))
|
|
(make-location (list :file (unix-truename file))
|
|
(list :position (1+ pos))))
|
|
(t (break)))))
|
|
|
|
(defun compiler-note-location (context)
|
|
"Derive the location of a complier message from its context.
|
|
Return a `location' record, or (:error <reason>) on failure."
|
|
(if (null context)
|
|
(note-error-location)
|
|
(let ((file (c::compiler-error-context-file-name context))
|
|
(source (c::compiler-error-context-original-source context))
|
|
(path
|
|
(reverse
|
|
(c::compiler-error-context-original-source-path context))))
|
|
(or (locate-compiler-note file source path)
|
|
(note-error-location)))))
|
|
|
|
(defun note-error-location ()
|
|
"Pseudo-location for notes that can't be located."
|
|
(list :error "No error location available."))
|
|
|
|
(defun locate-compiler-note (file source source-path)
|
|
(cond ((and (eq file :stream) *buffer-name*)
|
|
;; Compiling from a buffer
|
|
(make-location (list :buffer *buffer-name*)
|
|
(list :offset *buffer-start-position*
|
|
(source-path-string-position
|
|
source-path *buffer-substring*))))
|
|
((and (pathnamep file) (null *buffer-name*))
|
|
;; Compiling from a file
|
|
(make-location (list :file (unix-truename file))
|
|
(list :position (1+ (source-path-file-position
|
|
source-path file)))))
|
|
((and (eq file :lisp) (stringp source))
|
|
;; No location known, but we have the source form.
|
|
;; XXX How is this case triggered? -luke (16/May/2004)
|
|
;; This can happen if the compiler needs to expand a macro
|
|
;; but the macro-expander is not yet compiled. Calling the
|
|
;; (interpreted) macro-expander triggers IR1 conversion of
|
|
;; the lambda expression for the expander and invokes the
|
|
;; compiler recursively.
|
|
(make-location (list :source-form source)
|
|
(list :position 1)))))
|
|
|
|
(defun unix-truename (pathname)
|
|
(ext:unix-namestring (truename pathname)))
|
|
|
|
|
|
|
|
;;; TODO
|
|
(defimplementation who-calls (name) nil)
|
|
(defimplementation who-references (name) nil)
|
|
(defimplementation who-binds (name) nil)
|
|
(defimplementation who-sets (name) nil)
|
|
(defimplementation who-specializes (symbol) nil)
|
|
(defimplementation who-macroexpands (name) nil)
|
|
|
|
|
|
;;;; Find callers and callees
|
|
;;;
|
|
;;; Find callers and callees by looking at the constant pool of
|
|
;;; compiled code objects. We assume every fdefn object in the
|
|
;;; constant pool corresponds to a call to that function. A better
|
|
;;; strategy would be to use the disassembler to find actual
|
|
;;; call-sites.
|
|
|
|
(declaim (inline map-code-constants))
|
|
(defun map-code-constants (code fn)
|
|
"Call 'fn for each constant in 'code's constant pool."
|
|
(check-type code kernel:code-component)
|
|
(loop for i from vm:code-constants-offset below (kernel:get-header-data code)
|
|
do (funcall fn (kernel:code-header-ref code i))))
|
|
|
|
(defun function-callees (function)
|
|
"Return 'function's callees as a list of functions."
|
|
(let ((callees '()))
|
|
(map-code-constants
|
|
(vm::find-code-object function)
|
|
(lambda (obj)
|
|
(when (kernel:fdefn-p obj)
|
|
(push (kernel:fdefn-function obj) callees))))
|
|
callees))
|
|
|
|
(declaim (ext:maybe-inline map-allocated-code-components))
|
|
(defun map-allocated-code-components (spaces fn)
|
|
"Call FN for each allocated code component in one of 'spaces. FN
|
|
receives the object as argument. 'spaces should be a list of the
|
|
symbols :dynamic, :static, or :read-only."
|
|
(dolist (space spaces)
|
|
(declare (inline vm::map-allocated-objects)
|
|
(optimize (ext:inhibit-warnings 3)))
|
|
(vm::map-allocated-objects
|
|
(lambda (obj header size)
|
|
(declare (type fixnum size) (ignore size))
|
|
(when (= vm:code-header-type header)
|
|
(funcall fn obj)))
|
|
space)))
|
|
|
|
(declaim (ext:maybe-inline map-caller-code-components))
|
|
(defun map-caller-code-components (function spaces fn)
|
|
"Call 'fn for each code component with a fdefn for 'function in its
|
|
constant pool."
|
|
(let ((function (coerce function 'function)))
|
|
(declare (inline map-allocated-code-components))
|
|
(map-allocated-code-components
|
|
spaces
|
|
(lambda (obj)
|
|
(map-code-constants
|
|
obj
|
|
(lambda (constant)
|
|
(when (and (kernel:fdefn-p constant)
|
|
(eq (kernel:fdefn-function constant)
|
|
function))
|
|
(funcall fn obj))))))))
|
|
|
|
(defun function-callers (function &optional (spaces '(:read-only :static
|
|
:dynamic)))
|
|
"Return 'function's callers. The result is a list of code-objects."
|
|
(let ((referrers '()))
|
|
(declare (inline map-caller-code-components))
|
|
(map-caller-code-components function spaces
|
|
(lambda (code) (push code referrers)))
|
|
referrers))
|
|
|
|
(defun debug-info-definitions (debug-info)
|
|
"Return the defintions for a debug-info. This should only be used
|
|
for code-object without entry points, i.e., byte compiled
|
|
code (are theree others?)"
|
|
;; This mess has only been tested with #'ext::skip-whitespace, a
|
|
;; byte-compiled caller of #'read-char .
|
|
(check-type debug-info (and (not c::compiled-debug-info) c::debug-info))
|
|
(let ((name (c::debug-info-name debug-info))
|
|
(source (c::debug-info-source debug-info)))
|
|
(destructuring-bind (first) source
|
|
(ecase (c::debug-source-from first)
|
|
(:file
|
|
(list (list name
|
|
(make-location
|
|
(list :file (unix-truename (c::debug-source-name first)))
|
|
(list :function-name (string name))))))))))
|
|
|
|
(defun valid-function-name-p (name)
|
|
(or (symbolp name) (and (consp name)
|
|
(eq (car name) 'setf)
|
|
(symbolp (cadr name))
|
|
(not (cddr name)))))
|
|
|
|
(defun code-component-entry-points (code)
|
|
"Return a list ((name location) ...) of function definitons for
|
|
the code omponent 'code."
|
|
(let ((names '()))
|
|
(do ((f (kernel:%code-entry-points code) (kernel::%function-next f)))
|
|
((not f))
|
|
(let ((name (kernel:%function-name f)))
|
|
(when (valid-function-name-p name)
|
|
(push (list name (function-location f)) names))))
|
|
names))
|
|
|
|
(defimplementation list-callers (symbol)
|
|
"Return a list ((name location) ...) of callers."
|
|
(let ((components (function-callers symbol))
|
|
(xrefs '()))
|
|
(dolist (code components)
|
|
(let* ((entry (kernel:%code-entry-points code))
|
|
(defs (if entry
|
|
(code-component-entry-points code)
|
|
;; byte compiled stuff
|
|
(debug-info-definitions
|
|
(kernel:%code-debug-info code)))))
|
|
(setq xrefs (nconc defs xrefs))))
|
|
xrefs))
|
|
|
|
(defimplementation list-callees (symbol)
|
|
(let ((fns (function-callees symbol)))
|
|
(mapcar (lambda (fn)
|
|
(list (kernel:%function-name fn)
|
|
(function-location fn)))
|
|
fns)))
|
|
|
|
|
|
;;;; Resolving source locations
|
|
;;;
|
|
;;; Our mission here is to "resolve" references to code locations into
|
|
;;; actual file/buffer names and character positions. The references
|
|
;;; we work from come out of the compiler's statically-generated debug
|
|
;;; information, such as `code-location''s and `debug-source''s. For
|
|
;;; more details, see the "Debugger Programmer's Interface" section of
|
|
;;; the SCL manual.
|
|
;;;
|
|
;;; The first step is usually to find the corresponding "source-path"
|
|
;;; for the location. Once we have the source-path we can pull up the
|
|
;;; source file and `READ' our way through to the right position. The
|
|
;;; main source-code groveling work is done in
|
|
;;; `source-path-parser.lisp'.
|
|
|
|
(defvar *debug-definition-finding* nil
|
|
"When true don't handle errors while looking for definitions.
|
|
This is useful when debugging the definition-finding code.")
|
|
|
|
(defmacro safe-definition-finding (&body body)
|
|
"Execute 'body and return the source-location it returns.
|
|
If an error occurs and `*debug-definition-finding*' is false, then
|
|
return an error pseudo-location.
|
|
|
|
The second return value is 'nil if no error occurs, otherwise it is the
|
|
condition object."
|
|
`(flet ((body () ,@body))
|
|
(if *debug-definition-finding*
|
|
(body)
|
|
(handler-case (values (progn ,@body) nil)
|
|
(error (c) (values (list :error (princ-to-string c)) c))))))
|
|
|
|
(defun code-location-source-location (code-location)
|
|
"Safe wrapper around `code-location-from-source-location'."
|
|
(safe-definition-finding
|
|
(source-location-from-code-location code-location)))
|
|
|
|
(defun source-location-from-code-location (code-location)
|
|
"Return the source location for 'code-location."
|
|
(let ((debug-fun (di:code-location-debug-function code-location)))
|
|
(when (di::bogus-debug-function-p debug-fun)
|
|
;; Those lousy cheapskates! They've put in a bogus debug source
|
|
;; because the code was compiled at a low debug setting.
|
|
(error "Bogus debug function: ~A" debug-fun)))
|
|
(let* ((debug-source (di:code-location-debug-source code-location))
|
|
(from (di:debug-source-from debug-source))
|
|
(name (di:debug-source-name debug-source)))
|
|
(ecase from
|
|
(:file
|
|
(location-in-file name code-location debug-source))
|
|
(:stream
|
|
(location-in-stream code-location debug-source))
|
|
(:lisp
|
|
;; The location comes from a form passed to `compile'.
|
|
;; The best we can do is return the form itself for printing.
|
|
(make-location
|
|
(list :source-form (with-output-to-string (*standard-output*)
|
|
(debug::print-code-location-source-form
|
|
code-location 100 t)))
|
|
(list :position 1))))))
|
|
|
|
(defun location-in-file (filename code-location debug-source)
|
|
"Resolve the source location for 'code-location in 'filename."
|
|
(let* ((code-date (di:debug-source-created debug-source))
|
|
(source-code (get-source-code filename code-date)))
|
|
(with-input-from-string (s source-code)
|
|
(make-location (list :file (unix-truename filename))
|
|
(list :position (1+ (code-location-stream-position
|
|
code-location s)))
|
|
`(:snippet ,(read-snippet s))))))
|
|
|
|
(defun location-in-stream (code-location debug-source)
|
|
"Resolve the source location for a 'code-location from a stream.
|
|
This only succeeds if the code was compiled from an Emacs buffer."
|
|
(unless (debug-source-info-from-emacs-buffer-p debug-source)
|
|
(error "The code is compiled from a non-SLIME stream."))
|
|
(let* ((info (c::debug-source-info debug-source))
|
|
(string (getf info :emacs-buffer-string))
|
|
(position (code-location-string-offset
|
|
code-location
|
|
string)))
|
|
(make-location
|
|
(list :buffer (getf info :emacs-buffer))
|
|
(list :offset (getf info :emacs-buffer-offset) position)
|
|
(list :snippet (with-input-from-string (s string)
|
|
(file-position s position)
|
|
(read-snippet s))))))
|
|
|
|
;;;;; Function-name locations
|
|
;;;
|
|
(defun debug-info-function-name-location (debug-info)
|
|
"Return a function-name source-location for 'debug-info.
|
|
Function-name source-locations are a fallback for when precise
|
|
positions aren't available."
|
|
(with-struct (c::debug-info- (fname name) source) debug-info
|
|
(with-struct (c::debug-source- info from name) (car source)
|
|
(ecase from
|
|
(:file
|
|
(make-location (list :file (namestring (truename name)))
|
|
(list :function-name (string fname))))
|
|
(:stream
|
|
(assert (debug-source-info-from-emacs-buffer-p (car source)))
|
|
(make-location (list :buffer (getf info :emacs-buffer))
|
|
(list :function-name (string fname))))
|
|
(:lisp
|
|
(make-location (list :source-form (princ-to-string (aref name 0)))
|
|
(list :position 1)))))))
|
|
|
|
(defun debug-source-info-from-emacs-buffer-p (debug-source)
|
|
"Does the `info' slot of 'debug-source contain an Emacs buffer location?
|
|
This is true for functions that were compiled directly from buffers."
|
|
(info-from-emacs-buffer-p (c::debug-source-info debug-source)))
|
|
|
|
(defun info-from-emacs-buffer-p (info)
|
|
(and info
|
|
(consp info)
|
|
(eq :emacs-buffer (car info))))
|
|
|
|
|
|
;;;;; Groveling source-code for positions
|
|
|
|
(defun code-location-stream-position (code-location stream)
|
|
"Return the byte offset of 'code-location in 'stream. Extract the
|
|
toplevel-form-number and form-number from 'code-location and use that
|
|
to find the position of the corresponding form.
|
|
|
|
Finish with 'stream positioned at the start of the code location."
|
|
(let* ((location (debug::maybe-block-start-location code-location))
|
|
(tlf-offset (di:code-location-top-level-form-offset location))
|
|
(form-number (di:code-location-form-number location)))
|
|
(let ((pos (form-number-stream-position tlf-offset form-number stream)))
|
|
(file-position stream pos)
|
|
pos)))
|
|
|
|
(defun form-number-stream-position (tlf-number form-number stream)
|
|
"Return the starting character position of a form in 'stream.
|
|
'tlf-number is the top-level-form number.
|
|
'form-number is an index into a source-path table for the TLF."
|
|
(multiple-value-bind (tlf position-map) (read-source-form tlf-number stream)
|
|
(let* ((path-table (di:form-number-translations tlf 0))
|
|
(source-path
|
|
(if (<= (length path-table) form-number) ; source out of sync?
|
|
(list 0) ; should probably signal a condition
|
|
(reverse (cdr (aref path-table form-number))))))
|
|
(source-path-source-position source-path tlf position-map))))
|
|
|
|
(defun code-location-string-offset (code-location string)
|
|
"Return the byte offset of 'code-location in 'string.
|
|
See 'code-location-stream-position."
|
|
(with-input-from-string (s string)
|
|
(code-location-stream-position code-location s)))
|
|
|
|
|
|
;;;; Finding definitions
|
|
|
|
;;; There are a great many different types of definition for us to
|
|
;;; find. We search for definitions of every kind and return them in a
|
|
;;; list.
|
|
|
|
(defimplementation find-definitions (name)
|
|
(append (function-definitions name)
|
|
(setf-definitions name)
|
|
(variable-definitions name)
|
|
(class-definitions name)
|
|
(type-definitions name)
|
|
(compiler-macro-definitions name)
|
|
(source-transform-definitions name)
|
|
(function-info-definitions name)
|
|
(ir1-translator-definitions name)))
|
|
|
|
;;;;; Functions, macros, generic functions, methods
|
|
;;;
|
|
;;; We make extensive use of the compile-time debug information that
|
|
;;; SCL records, in particular "debug functions" and "code
|
|
;;; locations." Refer to the "Debugger Programmer's Interface" section
|
|
;;; of the SCL manual for more details.
|
|
|
|
(defun function-definitions (name)
|
|
"Return definitions for 'name in the \"function namespace\", i.e.,
|
|
regular functions, generic functions, methods and macros.
|
|
'name can any valid function name (e.g, (setf car))."
|
|
(let ((macro? (and (symbolp name) (macro-function name)))
|
|
(special? (and (symbolp name) (special-operator-p name)))
|
|
(function? (and (valid-function-name-p name)
|
|
(ext:info :function :definition name)
|
|
(if (symbolp name) (fboundp name) t))))
|
|
(cond (macro?
|
|
(list `((defmacro ,name)
|
|
,(function-location (macro-function name)))))
|
|
(special?
|
|
(list `((:special-operator ,name)
|
|
(:error ,(format nil "Special operator: ~S" name)))))
|
|
(function?
|
|
(let ((function (fdefinition name)))
|
|
(if (genericp function)
|
|
(generic-function-definitions name function)
|
|
(list (list `(function ,name)
|
|
(function-location function)))))))))
|
|
|
|
;;;;;; Ordinary (non-generic/macro/special) functions
|
|
;;;
|
|
;;; First we test if FUNCTION is a closure created by defstruct, and
|
|
;;; if so extract the defstruct-description (`dd') from the closure
|
|
;;; and find the constructor for the struct. Defstruct creates a
|
|
;;; defun for the default constructor and we use that as an
|
|
;;; approximation to the source location of the defstruct.
|
|
;;;
|
|
;;; For an ordinary function we return the source location of the
|
|
;;; first code-location we find.
|
|
;;;
|
|
(defun function-location (function)
|
|
"Return the source location for FUNCTION."
|
|
(cond ((struct-closure-p function)
|
|
(struct-closure-location function))
|
|
((c::byte-function-or-closure-p function)
|
|
(byte-function-location function))
|
|
(t
|
|
(compiled-function-location function))))
|
|
|
|
(defun compiled-function-location (function)
|
|
"Return the location of a regular compiled function."
|
|
(multiple-value-bind (code-location error)
|
|
(safe-definition-finding (function-first-code-location function))
|
|
(cond (error (list :error (princ-to-string error)))
|
|
(t (code-location-source-location code-location)))))
|
|
|
|
(defun function-first-code-location (function)
|
|
"Return the first code-location we can find for 'function."
|
|
(and (function-has-debug-function-p function)
|
|
(di:debug-function-start-location
|
|
(di:function-debug-function function))))
|
|
|
|
(defun function-has-debug-function-p (function)
|
|
(di:function-debug-function function))
|
|
|
|
(defun function-code-object= (closure function)
|
|
(and (eq (vm::find-code-object closure)
|
|
(vm::find-code-object function))
|
|
(not (eq closure function))))
|
|
|
|
|
|
(defun byte-function-location (fn)
|
|
"Return the location of the byte-compiled function 'fn."
|
|
(etypecase fn
|
|
((or c::hairy-byte-function c::simple-byte-function)
|
|
(let* ((component (c::byte-function-component fn))
|
|
(debug-info (kernel:%code-debug-info component)))
|
|
(debug-info-function-name-location debug-info)))
|
|
(c::byte-closure
|
|
(byte-function-location (c::byte-closure-function fn)))))
|
|
|
|
;;; Here we deal with structure accessors. Note that `dd' is a
|
|
;;; "defstruct descriptor" structure in SCL. A `dd' describes a
|
|
;;; `defstruct''d structure.
|
|
|
|
(defun struct-closure-p (function)
|
|
"Is 'function a closure created by defstruct?"
|
|
(or (function-code-object= function #'kernel::structure-slot-accessor)
|
|
(function-code-object= function #'kernel::structure-slot-setter)
|
|
(function-code-object= function #'kernel::%defstruct)))
|
|
|
|
(defun struct-closure-location (function)
|
|
"Return the location of the structure that 'function belongs to."
|
|
(assert (struct-closure-p function))
|
|
(safe-definition-finding
|
|
(dd-location (struct-closure-dd function))))
|
|
|
|
(defun struct-closure-dd (function)
|
|
"Return the defstruct-definition (dd) of FUNCTION."
|
|
(assert (= (kernel:get-type function) vm:closure-header-type))
|
|
(flet ((find-layout (function)
|
|
(sys:find-if-in-closure
|
|
(lambda (x)
|
|
(let ((value (if (di::indirect-value-cell-p x)
|
|
(c:value-cell-ref x)
|
|
x)))
|
|
(when (kernel::layout-p value)
|
|
(return-from find-layout value))))
|
|
function)))
|
|
(kernel:layout-info (find-layout function))))
|
|
|
|
(defun dd-location (dd)
|
|
"Return the location of a `defstruct'."
|
|
;; Find the location in a constructor.
|
|
(function-location (struct-constructor dd)))
|
|
|
|
(defun struct-constructor (dd)
|
|
"Return a constructor function from a defstruct definition.
|
|
Signal an error if no constructor can be found."
|
|
(let ((constructor (or (kernel:dd-default-constructor dd)
|
|
(car (kernel::dd-constructors dd)))))
|
|
(when (or (null constructor)
|
|
(and (consp constructor) (null (car constructor))))
|
|
(error "Cannot find structure's constructor: ~S"
|
|
(kernel::dd-name dd)))
|
|
(coerce (if (consp constructor) (first constructor) constructor)
|
|
'function)))
|
|
|
|
;;;;;; Generic functions and methods
|
|
|
|
(defun generic-function-definitions (name function)
|
|
"Return the definitions of a generic function and its methods."
|
|
(cons (list `(defgeneric ,name) (gf-location function))
|
|
(gf-method-definitions function)))
|
|
|
|
(defun gf-location (gf)
|
|
"Return the location of the generic function GF."
|
|
(definition-source-location gf (clos:generic-function-name gf)))
|
|
|
|
(defun gf-method-definitions (gf)
|
|
"Return the locations of all methods of the generic function GF."
|
|
(mapcar #'method-definition (clos:generic-function-methods gf)))
|
|
|
|
(defun method-definition (method)
|
|
(list (method-dspec method)
|
|
(method-location method)))
|
|
|
|
(defun method-dspec (method)
|
|
"Return a human-readable \"definition specifier\" for METHOD."
|
|
(let* ((gf (clos:method-generic-function method))
|
|
(name (clos:generic-function-name gf))
|
|
(specializers (clos:method-specializers method))
|
|
(qualifiers (clos:method-qualifiers method)))
|
|
`(method ,name ,@qualifiers ,specializers
|
|
#+nil (clos::unparse-specializers specializers))))
|
|
|
|
;; XXX maybe special case setters/getters
|
|
(defun method-location (method)
|
|
(function-location (clos:method-function method)))
|
|
|
|
(defun genericp (fn)
|
|
(typep fn 'generic-function))
|
|
|
|
;;;;;; Types and classes
|
|
|
|
(defun type-definitions (name)
|
|
"Return `deftype' locations for type NAME."
|
|
(maybe-make-definition (ext:info :type :expander name) 'deftype name))
|
|
|
|
(defun maybe-make-definition (function kind name)
|
|
"If FUNCTION is non-nil then return its definition location."
|
|
(if function
|
|
(list (list `(,kind ,name) (function-location function)))))
|
|
|
|
(defun class-definitions (name)
|
|
"Return the definition locations for the class called NAME."
|
|
(if (symbolp name)
|
|
(let ((class (find-class name nil)))
|
|
(etypecase class
|
|
(null '())
|
|
(structure-class
|
|
(list (list `(defstruct ,name)
|
|
(dd-location (find-dd name)))))
|
|
(standard-class
|
|
(list (list `(defclass ,name)
|
|
(class-location (find-class name)))))
|
|
((or built-in-class
|
|
kernel:funcallable-structure-class)
|
|
(list (list `(kernel::define-type-class ,name)
|
|
`(:error
|
|
,(format nil "No source info for ~A" name)))))))))
|
|
|
|
(defun class-location (class)
|
|
"Return the `defclass' location for CLASS."
|
|
(definition-source-location class (class-name class)))
|
|
|
|
(defun find-dd (name)
|
|
"Find the defstruct-definition by the name of its structure-class."
|
|
(let ((layout (ext:info :type :compiler-layout name)))
|
|
(if layout
|
|
(kernel:layout-info layout))))
|
|
|
|
(defun condition-class-location (class)
|
|
(let ((name (class-name class)))
|
|
`(:error ,(format nil "No location info for condition: ~A" name))))
|
|
|
|
(defun make-name-in-file-location (file string)
|
|
(multiple-value-bind (filename c)
|
|
(ignore-errors
|
|
(unix-truename (merge-pathnames (make-pathname :type "lisp")
|
|
file)))
|
|
(cond (filename (make-location `(:file ,filename)
|
|
`(:function-name ,(string string))))
|
|
(t (list :error (princ-to-string c))))))
|
|
|
|
(defun definition-source-location (object name)
|
|
`(:error ,(format nil "No source info for: ~A" object)))
|
|
|
|
(defun setf-definitions (name)
|
|
(let ((function (or (ext:info :setf :inverse name)
|
|
(ext:info :setf :expander name))))
|
|
(if function
|
|
(list (list `(setf ,name)
|
|
(function-location (coerce function 'function)))))))
|
|
|
|
|
|
(defun variable-location (symbol)
|
|
`(:error ,(format nil "No source info for variable ~S" symbol)))
|
|
|
|
(defun variable-definitions (name)
|
|
(if (symbolp name)
|
|
(multiple-value-bind (kind recorded-p) (ext:info :variable :kind name)
|
|
(if recorded-p
|
|
(list (list `(variable ,kind ,name)
|
|
(variable-location name)))))))
|
|
|
|
(defun compiler-macro-definitions (symbol)
|
|
(maybe-make-definition (compiler-macro-function symbol)
|
|
'define-compiler-macro
|
|
symbol))
|
|
|
|
(defun source-transform-definitions (name)
|
|
(maybe-make-definition (ext:info :function :source-transform name)
|
|
'c:def-source-transform
|
|
name))
|
|
|
|
(defun function-info-definitions (name)
|
|
(let ((info (ext:info :function :info name)))
|
|
(if info
|
|
(append (loop for transform in (c::function-info-transforms info)
|
|
collect (list `(c:deftransform ,name
|
|
,(c::type-specifier
|
|
(c::transform-type transform)))
|
|
(function-location (c::transform-function
|
|
transform))))
|
|
(maybe-make-definition (c::function-info-derive-type info)
|
|
'c::derive-type name)
|
|
(maybe-make-definition (c::function-info-optimizer info)
|
|
'c::optimizer name)
|
|
(maybe-make-definition (c::function-info-ltn-annotate info)
|
|
'c::ltn-annotate name)
|
|
(maybe-make-definition (c::function-info-ir2-convert info)
|
|
'c::ir2-convert name)
|
|
(loop for template in (c::function-info-templates info)
|
|
collect (list `(c::vop ,(c::template-name template))
|
|
(function-location
|
|
(c::vop-info-generator-function
|
|
template))))))))
|
|
|
|
(defun ir1-translator-definitions (name)
|
|
(maybe-make-definition (ext:info :function :ir1-convert name)
|
|
'c:def-ir1-translator name))
|
|
|
|
|
|
;;;; Documentation.
|
|
|
|
(defimplementation describe-symbol-for-emacs (symbol)
|
|
(let ((result '()))
|
|
(flet ((doc (kind)
|
|
(or (documentation symbol kind) :not-documented))
|
|
(maybe-push (property value)
|
|
(when value
|
|
(setf result (list* property value result)))))
|
|
(maybe-push
|
|
:variable (multiple-value-bind (kind recorded-p)
|
|
(ext:info variable kind symbol)
|
|
(declare (ignore kind))
|
|
(if (or (boundp symbol) recorded-p)
|
|
(doc 'variable))))
|
|
(when (fboundp symbol)
|
|
(maybe-push
|
|
(cond ((macro-function symbol) :macro)
|
|
((special-operator-p symbol) :special-operator)
|
|
((genericp (fdefinition symbol)) :generic-function)
|
|
(t :function))
|
|
(doc 'function)))
|
|
(maybe-push
|
|
:setf (if (or (ext:info setf inverse symbol)
|
|
(ext:info setf expander symbol))
|
|
(doc 'setf)))
|
|
(maybe-push
|
|
:type (if (ext:info type kind symbol)
|
|
(doc 'type)))
|
|
(maybe-push
|
|
:class (if (find-class symbol nil)
|
|
(doc 'class)))
|
|
(maybe-push
|
|
:alien-type (if (not (eq (ext:info alien-type kind symbol) :unknown))
|
|
(doc 'alien-type)))
|
|
(maybe-push
|
|
:alien-struct (if (ext:info alien-type struct symbol)
|
|
(doc nil)))
|
|
(maybe-push
|
|
:alien-union (if (ext:info alien-type union symbol)
|
|
(doc nil)))
|
|
(maybe-push
|
|
:alien-enum (if (ext:info alien-type enum symbol)
|
|
(doc nil)))
|
|
result)))
|
|
|
|
(defimplementation describe-definition (symbol namespace)
|
|
(describe (ecase namespace
|
|
(:variable
|
|
symbol)
|
|
((:function :generic-function)
|
|
(symbol-function symbol))
|
|
(:setf
|
|
(or (ext:info setf inverse symbol)
|
|
(ext:info setf expander symbol)))
|
|
(:type
|
|
(kernel:values-specifier-type symbol))
|
|
(:class
|
|
(find-class symbol))
|
|
(:alien-struct
|
|
(ext:info :alien-type :struct symbol))
|
|
(:alien-union
|
|
(ext:info :alien-type :union symbol))
|
|
(:alien-enum
|
|
(ext:info :alien-type :enum symbol))
|
|
(:alien-type
|
|
(ecase (ext:info :alien-type :kind symbol)
|
|
(:primitive
|
|
(let ((alien::*values-type-okay* t))
|
|
(funcall (ext:info :alien-type :translator symbol)
|
|
(list symbol))))
|
|
((:defined)
|
|
(ext:info :alien-type :definition symbol))
|
|
(:unknown :unknown))))))
|
|
|
|
;;;;; Argument lists
|
|
|
|
(defimplementation arglist (fun)
|
|
(multiple-value-bind (args winp)
|
|
(ext:function-arglist fun)
|
|
(if winp args :not-available)))
|
|
|
|
(defimplementation function-name (function)
|
|
(cond ((eval:interpreted-function-p function)
|
|
(eval:interpreted-function-name function))
|
|
((typep function 'generic-function)
|
|
(clos:generic-function-name function))
|
|
((c::byte-function-or-closure-p function)
|
|
(c::byte-function-name function))
|
|
(t (kernel:%function-name (kernel:%function-self function)))))
|
|
|
|
|
|
;;; A harder case: an approximate arglist is derived from available
|
|
;;; debugging information.
|
|
|
|
(defun debug-function-arglist (debug-function)
|
|
"Derive the argument list of DEBUG-FUNCTION from debug info."
|
|
(let ((args (di::debug-function-lambda-list debug-function))
|
|
(required '())
|
|
(optional '())
|
|
(rest '())
|
|
(key '()))
|
|
;; collect the names of debug-vars
|
|
(dolist (arg args)
|
|
(etypecase arg
|
|
(di::debug-variable
|
|
(push (di::debug-variable-symbol arg) required))
|
|
((member :deleted)
|
|
(push ':deleted required))
|
|
(cons
|
|
(ecase (car arg)
|
|
(:keyword
|
|
(push (second arg) key))
|
|
(:optional
|
|
(push (debug-variable-symbol-or-deleted (second arg)) optional))
|
|
(:rest
|
|
(push (debug-variable-symbol-or-deleted (second arg)) rest))))))
|
|
;; intersperse lambda keywords as needed
|
|
(append (nreverse required)
|
|
(if optional (cons '&optional (nreverse optional)))
|
|
(if rest (cons '&rest (nreverse rest)))
|
|
(if key (cons '&key (nreverse key))))))
|
|
|
|
(defun debug-variable-symbol-or-deleted (var)
|
|
(etypecase var
|
|
(di:debug-variable
|
|
(di::debug-variable-symbol var))
|
|
((member :deleted)
|
|
'#:deleted)))
|
|
|
|
(defun symbol-debug-function-arglist (fname)
|
|
"Return FNAME's debug-function-arglist and %function-arglist.
|
|
A utility for debugging DEBUG-FUNCTION-ARGLIST."
|
|
(let ((fn (fdefinition fname)))
|
|
(values (debug-function-arglist (di::function-debug-function fn))
|
|
(kernel:%function-arglist (kernel:%function-self fn)))))
|
|
|
|
|
|
;;;; Miscellaneous.
|
|
|
|
(defimplementation macroexpand-all (form &optional env)
|
|
(declare (ignore env))
|
|
(macroexpand form))
|
|
|
|
(defimplementation set-default-directory (directory)
|
|
(setf (ext:default-directory) (namestring directory))
|
|
;; Setting *default-pathname-defaults* to an absolute directory
|
|
;; makes the behavior of MERGE-PATHNAMES a bit more intuitive.
|
|
(setf *default-pathname-defaults* (pathname (ext:default-directory)))
|
|
(default-directory))
|
|
|
|
(defimplementation default-directory ()
|
|
(namestring (ext:default-directory)))
|
|
|
|
(defimplementation pathname-to-filename (pathname)
|
|
(ext:unix-namestring pathname nil))
|
|
|
|
(defimplementation getpid ()
|
|
(unix:unix-getpid))
|
|
|
|
(defimplementation lisp-implementation-type-name ()
|
|
(if (eq ext:*case-mode* :upper) "scl" "scl-lower"))
|
|
|
|
(defimplementation quit-lisp ()
|
|
(ext:quit))
|
|
|
|
;;; source-path-{stream,file,string,etc}-position moved into
|
|
;;; source-path-parser
|
|
|
|
|
|
;;;; Debugging
|
|
|
|
(defvar *sldb-stack-top*)
|
|
|
|
(defimplementation call-with-debugging-environment (debugger-loop-fn)
|
|
(let* ((*sldb-stack-top* (or debug:*stack-top-hint* (di:top-frame)))
|
|
(debug:*stack-top-hint* nil)
|
|
(kernel:*current-level* 0))
|
|
(handler-bind ((di::unhandled-condition
|
|
(lambda (condition)
|
|
(error 'sldb-condition
|
|
:original-condition condition))))
|
|
(funcall debugger-loop-fn))))
|
|
|
|
(defun frame-down (frame)
|
|
(handler-case (di:frame-down frame)
|
|
(di:no-debug-info () nil)))
|
|
|
|
(defun nth-frame (index)
|
|
(do ((frame *sldb-stack-top* (frame-down frame))
|
|
(i index (1- i)))
|
|
((zerop i) frame)))
|
|
|
|
(defimplementation compute-backtrace (start end)
|
|
(let ((end (or end most-positive-fixnum)))
|
|
(loop for f = (nth-frame start) then (frame-down f)
|
|
for i from start below end
|
|
while f collect f)))
|
|
|
|
(defimplementation print-frame (frame stream)
|
|
(let ((*standard-output* stream))
|
|
(handler-case
|
|
(debug::print-frame-call frame :verbosity 1 :number nil)
|
|
(error (e)
|
|
(ignore-errors (princ e stream))))))
|
|
|
|
(defimplementation frame-source-location (index)
|
|
(code-location-source-location (di:frame-code-location (nth-frame index))))
|
|
|
|
(defimplementation eval-in-frame (form index)
|
|
(di:eval-in-frame (nth-frame index) form))
|
|
|
|
(defun frame-debug-vars (frame)
|
|
"Return a vector of debug-variables in frame."
|
|
(di::debug-function-debug-variables (di:frame-debug-function frame)))
|
|
|
|
(defun debug-var-value (var frame location)
|
|
(let ((validity (di:debug-variable-validity var location)))
|
|
(ecase validity
|
|
(:valid (di:debug-variable-value var frame))
|
|
((:invalid :unknown) (make-symbol (string validity))))))
|
|
|
|
(defimplementation frame-locals (index)
|
|
(let* ((frame (nth-frame index))
|
|
(loc (di:frame-code-location frame))
|
|
(vars (frame-debug-vars frame)))
|
|
(loop for v across vars collect
|
|
(list :name (di:debug-variable-symbol v)
|
|
:id (di:debug-variable-id v)
|
|
:value (debug-var-value v frame loc)))))
|
|
|
|
(defimplementation frame-var-value (frame var)
|
|
(let* ((frame (nth-frame frame))
|
|
(dvar (aref (frame-debug-vars frame) var)))
|
|
(debug-var-value dvar frame (di:frame-code-location frame))))
|
|
|
|
(defimplementation frame-catch-tags (index)
|
|
(mapcar #'car (di:frame-catches (nth-frame index))))
|
|
|
|
(defimplementation return-from-frame (index form)
|
|
(let ((sym (find-symbol (symbol-name '#:find-debug-tag-for-frame)
|
|
:debug-internals)))
|
|
(if sym
|
|
(let* ((frame (nth-frame index))
|
|
(probe (funcall sym frame)))
|
|
(cond (probe (throw (car probe) (eval-in-frame form index)))
|
|
(t (format nil "Cannot return from frame: ~S" frame))))
|
|
"return-from-frame is not implemented in this version of SCL.")))
|
|
|
|
(defimplementation activate-stepping (frame)
|
|
(set-step-breakpoints (nth-frame frame)))
|
|
|
|
(defimplementation sldb-break-on-return (frame)
|
|
(break-on-return (nth-frame frame)))
|
|
|
|
;;; We set the breakpoint in the caller which might be a bit confusing.
|
|
;;;
|
|
(defun break-on-return (frame)
|
|
(let* ((caller (di:frame-down frame))
|
|
(cl (di:frame-code-location caller)))
|
|
(flet ((hook (frame bp)
|
|
(when (frame-pointer= frame caller)
|
|
(di:delete-breakpoint bp)
|
|
(signal-breakpoint bp frame))))
|
|
(let* ((info (ecase (di:code-location-kind cl)
|
|
((:single-value-return :unknown-return) nil)
|
|
(:known-return (debug-function-returns
|
|
(di:frame-debug-function frame)))))
|
|
(bp (di:make-breakpoint #'hook cl :kind :code-location
|
|
:info info)))
|
|
(di:activate-breakpoint bp)
|
|
`(:ok ,(format nil "Set breakpoint in ~A" caller))))))
|
|
|
|
(defun frame-pointer= (frame1 frame2)
|
|
"Return true if the frame pointers of FRAME1 and FRAME2 are the same."
|
|
(sys:sap= (di::frame-pointer frame1) (di::frame-pointer frame2)))
|
|
|
|
;;; The PC in escaped frames at a single-return-value point is
|
|
;;; actually vm:single-value-return-byte-offset bytes after the
|
|
;;; position given in the debug info. Here we try to recognize such
|
|
;;; cases.
|
|
;;;
|
|
(defun next-code-locations (frame code-location)
|
|
"Like `debug::next-code-locations' but be careful in escaped frames."
|
|
(let ((next (debug::next-code-locations code-location)))
|
|
(flet ((adjust-pc ()
|
|
(let ((cl (di::copy-compiled-code-location code-location)))
|
|
(incf (di::compiled-code-location-pc cl)
|
|
vm:single-value-return-byte-offset)
|
|
cl)))
|
|
(cond ((and (di::compiled-frame-escaped frame)
|
|
(eq (di:code-location-kind code-location)
|
|
:single-value-return)
|
|
(= (length next) 1)
|
|
(di:code-location= (car next) (adjust-pc)))
|
|
(debug::next-code-locations (car next)))
|
|
(t
|
|
next)))))
|
|
|
|
(defun set-step-breakpoints (frame)
|
|
(let ((cl (di:frame-code-location frame)))
|
|
(when (di:debug-block-elsewhere-p (di:code-location-debug-block cl))
|
|
(error "Cannot step in elsewhere code"))
|
|
(let* ((debug::*bad-code-location-types*
|
|
(remove :call-site debug::*bad-code-location-types*))
|
|
(next (next-code-locations frame cl)))
|
|
(cond (next
|
|
(let ((steppoints '()))
|
|
(flet ((hook (bp-frame bp)
|
|
(signal-breakpoint bp bp-frame)
|
|
(mapc #'di:delete-breakpoint steppoints)))
|
|
(dolist (code-location next)
|
|
(let ((bp (di:make-breakpoint #'hook code-location
|
|
:kind :code-location)))
|
|
(di:activate-breakpoint bp)
|
|
(push bp steppoints))))))
|
|
(t
|
|
(break-on-return frame))))))
|
|
|
|
|
|
;; XXX the return values at return breakpoints should be passed to the
|
|
;; user hooks. debug-int.lisp should be changed to do this cleanly.
|
|
|
|
;;; The sigcontext and the PC for a breakpoint invocation are not
|
|
;;; passed to user hook functions, but we need them to extract return
|
|
;;; values. So we advice di::handle-breakpoint and bind the values to
|
|
;;; special variables.
|
|
;;;
|
|
(defvar *breakpoint-sigcontext*)
|
|
(defvar *breakpoint-pc*)
|
|
|
|
(defun sigcontext-object (sc index)
|
|
"Extract the lisp object in sigcontext SC at offset INDEX."
|
|
(kernel:make-lisp-obj (vm:ucontext-register sc index)))
|
|
|
|
(defun known-return-point-values (sigcontext sc-offsets)
|
|
(let ((fp (system:int-sap (vm:ucontext-register sigcontext
|
|
vm::cfp-offset))))
|
|
(system:without-gcing
|
|
(loop for sc-offset across sc-offsets
|
|
collect (di::sub-access-debug-var-slot fp sc-offset sigcontext)))))
|
|
|
|
;;; SCL returns the first few values in registers and the rest on
|
|
;;; the stack. In the multiple value case, the number of values is
|
|
;;; stored in a dedicated register. The values of the registers can be
|
|
;;; accessed in the sigcontext for the breakpoint. There are 3 kinds
|
|
;;; of return conventions: :single-value-return, :unknown-return, and
|
|
;;; :known-return.
|
|
;;;
|
|
;;; The :single-value-return convention returns the value in a
|
|
;;; register without setting the nargs registers.
|
|
;;;
|
|
;;; The :unknown-return variant is used for multiple values. A
|
|
;;; :unknown-return point consists actually of 2 breakpoints: one for
|
|
;;; the single value case and one for the general case. The single
|
|
;;; value breakpoint comes vm:single-value-return-byte-offset after
|
|
;;; the multiple value breakpoint.
|
|
;;;
|
|
;;; The :known-return convention is used by local functions.
|
|
;;; :known-return is currently not supported because we don't know
|
|
;;; where the values are passed.
|
|
;;;
|
|
(defun breakpoint-values (breakpoint)
|
|
"Return the list of return values for a return point."
|
|
(flet ((1st (sc) (sigcontext-object sc (car vm::register-arg-offsets))))
|
|
(let ((sc (locally (declare (optimize (ext:inhibit-warnings 3)))
|
|
(alien:sap-alien *breakpoint-sigcontext* (* unix:ucontext))))
|
|
(cl (di:breakpoint-what breakpoint)))
|
|
(ecase (di:code-location-kind cl)
|
|
(:single-value-return
|
|
(list (1st sc)))
|
|
(:known-return
|
|
(let ((info (di:breakpoint-info breakpoint)))
|
|
(if (vectorp info)
|
|
(known-return-point-values sc info)
|
|
(progn
|
|
;;(break)
|
|
(list "<<known-return convention not supported>>" info)))))
|
|
(:unknown-return
|
|
(let ((mv-return-pc (di::compiled-code-location-pc cl)))
|
|
(if (= mv-return-pc *breakpoint-pc*)
|
|
(mv-function-end-breakpoint-values sc)
|
|
(list (1st sc)))))))))
|
|
|
|
(defun mv-function-end-breakpoint-values (sigcontext)
|
|
(let ((sym (find-symbol
|
|
(symbol-name '#:function-end-breakpoint-values/standard)
|
|
:debug-internals)))
|
|
(cond (sym (funcall sym sigcontext))
|
|
(t (di::get-function-end-breakpoint-values sigcontext)))))
|
|
|
|
(defun debug-function-returns (debug-fun)
|
|
"Return the return style of DEBUG-FUN."
|
|
(let* ((cdfun (di::compiled-debug-function-compiler-debug-fun debug-fun)))
|
|
(c::compiled-debug-function-returns cdfun)))
|
|
|
|
(define-condition breakpoint (simple-condition)
|
|
((message :initarg :message :reader breakpoint.message)
|
|
(values :initarg :values :reader breakpoint.values))
|
|
(:report (lambda (c stream) (princ (breakpoint.message c) stream))))
|
|
|
|
#+nil
|
|
(defimplementation condition-extras ((c breakpoint))
|
|
;; simply pop up the source buffer
|
|
`((:short-frame-source 0)))
|
|
|
|
(defun signal-breakpoint (breakpoint frame)
|
|
"Signal a breakpoint condition for BREAKPOINT in FRAME.
|
|
Try to create a informative message."
|
|
(flet ((brk (values fstring &rest args)
|
|
(let ((msg (apply #'format nil fstring args))
|
|
(debug:*stack-top-hint* frame))
|
|
(break 'breakpoint :message msg :values values))))
|
|
(with-struct (di::breakpoint- kind what) breakpoint
|
|
(case kind
|
|
(:code-location
|
|
(case (di:code-location-kind what)
|
|
((:single-value-return :known-return :unknown-return)
|
|
(let ((values (breakpoint-values breakpoint)))
|
|
(brk values "Return value: ~{~S ~}" values)))
|
|
(t
|
|
#+(or)
|
|
(when (eq (di:code-location-kind what) :call-site)
|
|
(call-site-function breakpoint frame))
|
|
(brk nil "Breakpoint: ~S ~S"
|
|
(di:code-location-kind what)
|
|
(di::compiled-code-location-pc what)))))
|
|
(:function-start
|
|
(brk nil "Function start breakpoint"))
|
|
(t (brk nil "Breakpoint: ~A in ~A" breakpoint frame))))))
|
|
|
|
#+nil
|
|
(defimplementation sldb-break-at-start (fname)
|
|
(let ((debug-fun (di:function-debug-function (coerce fname 'function))))
|
|
(cond ((not debug-fun)
|
|
`(:error ,(format nil "~S has no debug-function" fname)))
|
|
(t
|
|
(flet ((hook (frame bp &optional args cookie)
|
|
(declare (ignore args cookie))
|
|
(signal-breakpoint bp frame)))
|
|
(let ((bp (di:make-breakpoint #'hook debug-fun
|
|
:kind :function-start)))
|
|
(di:activate-breakpoint bp)
|
|
`(:ok ,(format nil "Set breakpoint in ~S" fname))))))))
|
|
|
|
(defun frame-cfp (frame)
|
|
"Return the Control-Stack-Frame-Pointer for FRAME."
|
|
(etypecase frame
|
|
(di::compiled-frame (di::frame-pointer frame))
|
|
((or di::interpreted-frame null) -1)))
|
|
|
|
(defun frame-ip (frame)
|
|
"Return the (absolute) instruction pointer and the relative pc of FRAME."
|
|
(if (not frame)
|
|
-1
|
|
(let ((debug-fun (di::frame-debug-function frame)))
|
|
(etypecase debug-fun
|
|
(di::compiled-debug-function
|
|
(let* ((code-loc (di:frame-code-location frame))
|
|
(component (di::compiled-debug-function-component debug-fun))
|
|
(pc (di::compiled-code-location-pc code-loc))
|
|
(ip (sys:without-gcing
|
|
(sys:sap-int
|
|
(sys:sap+ (kernel:code-instructions component) pc)))))
|
|
(values ip pc)))
|
|
((or di::bogus-debug-function di::interpreted-debug-function)
|
|
-1)))))
|
|
|
|
(defun frame-registers (frame)
|
|
"Return the lisp registers CSP, CFP, IP, OCFP, LRA for FRAME-NUMBER."
|
|
(let* ((cfp (frame-cfp frame))
|
|
(csp (frame-cfp (di::frame-up frame)))
|
|
(ip (frame-ip frame))
|
|
(ocfp (frame-cfp (di::frame-down frame)))
|
|
(lra (frame-ip (di::frame-down frame))))
|
|
(values csp cfp ip ocfp lra)))
|
|
|
|
(defun print-frame-registers (frame-number)
|
|
(let ((frame (di::frame-real-frame (nth-frame frame-number))))
|
|
(flet ((fixnum (p) (etypecase p
|
|
(integer p)
|
|
(sys:system-area-pointer (sys:sap-int p)))))
|
|
(apply #'format t "~
|
|
CSP = ~X
|
|
CFP = ~X
|
|
IP = ~X
|
|
OCFP = ~X
|
|
LRA = ~X~%" (mapcar #'fixnum
|
|
(multiple-value-list (frame-registers frame)))))))
|
|
|
|
|
|
(defimplementation disassemble-frame (frame-number)
|
|
"Return a string with the disassembly of frames code."
|
|
(print-frame-registers frame-number)
|
|
(terpri)
|
|
(let* ((frame (di::frame-real-frame (nth-frame frame-number)))
|
|
(debug-fun (di::frame-debug-function frame)))
|
|
(etypecase debug-fun
|
|
(di::compiled-debug-function
|
|
(let* ((component (di::compiled-debug-function-component debug-fun))
|
|
(fun (di:debug-function-function debug-fun)))
|
|
(if fun
|
|
(disassemble fun)
|
|
(disassem:disassemble-code-component component))))
|
|
(di::bogus-debug-function
|
|
(format t "~%[Disassembling bogus frames not implemented]")))))
|
|
|
|
|
|
;;;; Inspecting
|
|
|
|
(defconstant +lowtag-symbols+
|
|
'(vm:even-fixnum-type
|
|
vm:instance-pointer-type
|
|
vm:other-immediate-0-type
|
|
vm:list-pointer-type
|
|
vm:odd-fixnum-type
|
|
vm:function-pointer-type
|
|
vm:other-immediate-1-type
|
|
vm:other-pointer-type)
|
|
"Names of the constants that specify type tags.
|
|
The `symbol-value' of each element is a type tag.")
|
|
|
|
(defconstant +header-type-symbols+
|
|
(labels ((suffixp (suffix string)
|
|
(and (>= (length string) (length suffix))
|
|
(string= string suffix :start1 (- (length string)
|
|
(length suffix)))))
|
|
(header-type-symbol-p (x)
|
|
(and (suffixp (symbol-name '#:-type) (symbol-name x))
|
|
(not (member x +lowtag-symbols+))
|
|
(boundp x)
|
|
(typep (symbol-value x) 'fixnum))))
|
|
(remove-if-not #'header-type-symbol-p
|
|
(append (apropos-list (symbol-name '#:-type) :vm)
|
|
(apropos-list (symbol-name '#:-type) :bignum))))
|
|
"A list of names of the type codes in boxed objects.")
|
|
|
|
(defimplementation describe-primitive-type (object)
|
|
(with-output-to-string (*standard-output*)
|
|
(let* ((lowtag (kernel:get-lowtag object))
|
|
(lowtag-symbol (find lowtag +lowtag-symbols+ :key #'symbol-value)))
|
|
(format t "lowtag: ~A" lowtag-symbol)
|
|
(when (member lowtag (list vm:other-pointer-type
|
|
vm:function-pointer-type
|
|
vm:other-immediate-0-type
|
|
vm:other-immediate-1-type
|
|
))
|
|
(let* ((type (kernel:get-type object))
|
|
(type-symbol (find type +header-type-symbols+
|
|
:key #'symbol-value)))
|
|
(format t ", type: ~A" type-symbol))))))
|
|
|
|
(defmethod emacs-inspect ((o t))
|
|
(cond ((di::indirect-value-cell-p o)
|
|
`("Value: " (:value ,(c:value-cell-ref o))))
|
|
((alien::alien-value-p o)
|
|
(inspect-alien-value o))
|
|
(t
|
|
(scl-inspect o))))
|
|
|
|
(defun scl-inspect (o)
|
|
(destructuring-bind (text labeledp . parts)
|
|
(inspect::describe-parts o)
|
|
(list* (format nil "~A~%" text)
|
|
(if labeledp
|
|
(loop for (label . value) in parts
|
|
append (label-value-line label value))
|
|
(loop for value in parts for i from 0
|
|
append (label-value-line i value))))))
|
|
|
|
(defmethod emacs-inspect ((o function))
|
|
(let ((header (kernel:get-type o)))
|
|
(cond ((= header vm:function-header-type)
|
|
(list* (format nil "~A is a function.~%" o)
|
|
(append (label-value-line*
|
|
("Self" (kernel:%function-self o))
|
|
("Next" (kernel:%function-next o))
|
|
("Name" (kernel:%function-name o))
|
|
("Arglist" (kernel:%function-arglist o))
|
|
("Type" (kernel:%function-type o))
|
|
("Code" (kernel:function-code-header o)))
|
|
(list
|
|
(with-output-to-string (s)
|
|
(disassem:disassemble-function o :stream s))))))
|
|
((= header vm:closure-header-type)
|
|
(list* (format nil "~A is a closure.~%" o)
|
|
(append
|
|
(label-value-line "Function" (kernel:%closure-function o))
|
|
`("Environment:" (:newline))
|
|
(loop for i from 0 below (- (kernel:get-closure-length o)
|
|
(1- vm:closure-info-offset))
|
|
append (label-value-line
|
|
i (kernel:%closure-index-ref o i))))))
|
|
((eval::interpreted-function-p o)
|
|
(scl-inspect o))
|
|
(t
|
|
(call-next-method)))))
|
|
|
|
|
|
(defmethod emacs-inspect ((o kernel:code-component))
|
|
(append
|
|
(label-value-line*
|
|
("code-size" (kernel:%code-code-size o))
|
|
("entry-points" (kernel:%code-entry-points o))
|
|
("debug-info" (kernel:%code-debug-info o))
|
|
("trace-table-offset" (kernel:code-header-ref
|
|
o vm:code-trace-table-offset-slot)))
|
|
`("Constants:" (:newline))
|
|
(loop for i from vm:code-constants-offset
|
|
below (kernel:get-header-data o)
|
|
append (label-value-line i (kernel:code-header-ref o i)))
|
|
`("Code:" (:newline)
|
|
, (with-output-to-string (s)
|
|
(cond ((kernel:%code-debug-info o)
|
|
(disassem:disassemble-code-component o :stream s))
|
|
(t
|
|
(disassem:disassemble-memory
|
|
(disassem::align
|
|
(+ (logandc2 (kernel:get-lisp-obj-address o)
|
|
vm:lowtag-mask)
|
|
(* vm:code-constants-offset vm:word-bytes))
|
|
(ash 1 vm:lowtag-bits))
|
|
(ash (kernel:%code-code-size o) vm:word-shift)
|
|
:stream s)))))))
|
|
|
|
(defmethod emacs-inspect ((o kernel:fdefn))
|
|
(label-value-line*
|
|
("name" (kernel:fdefn-name o))
|
|
("function" (kernel:fdefn-function o))
|
|
("raw-addr" (sys:sap-ref-32
|
|
(sys:int-sap (kernel:get-lisp-obj-address o))
|
|
(* vm:fdefn-raw-addr-slot vm:word-bytes)))))
|
|
|
|
(defmethod emacs-inspect ((o array))
|
|
(cond ((kernel:array-header-p o)
|
|
(list* (format nil "~A is an array.~%" o)
|
|
(label-value-line*
|
|
(:header (describe-primitive-type o))
|
|
(:rank (array-rank o))
|
|
(:fill-pointer (kernel:%array-fill-pointer o))
|
|
(:fill-pointer-p (kernel:%array-fill-pointer-p o))
|
|
(:elements (kernel:%array-available-elements o))
|
|
(:data (kernel:%array-data-vector o))
|
|
(:displacement (kernel:%array-displacement o))
|
|
(:displaced-p (kernel:%array-displaced-p o))
|
|
(:dimensions (array-dimensions o)))))
|
|
(t
|
|
(list* (format nil "~A is an simple-array.~%" o)
|
|
(label-value-line*
|
|
(:header (describe-primitive-type o))
|
|
(:length (length o)))))))
|
|
|
|
(defmethod emacs-inspect ((o simple-vector))
|
|
(list* (format nil "~A is a vector.~%" o)
|
|
(append
|
|
(label-value-line*
|
|
(:header (describe-primitive-type o))
|
|
(:length (c::vector-length o)))
|
|
(unless (eq (array-element-type o) 'nil)
|
|
(loop for i below (length o)
|
|
append (label-value-line i (aref o i)))))))
|
|
|
|
(defun inspect-alien-record (alien)
|
|
(with-struct (alien::alien-value- sap type) alien
|
|
(with-struct (alien::alien-record-type- kind name fields) type
|
|
(append
|
|
(label-value-line*
|
|
(:sap sap)
|
|
(:kind kind)
|
|
(:name name))
|
|
(loop for field in fields
|
|
append (let ((slot (alien::alien-record-field-name field)))
|
|
(label-value-line slot (alien:slot alien slot))))))))
|
|
|
|
(defun inspect-alien-pointer (alien)
|
|
(with-struct (alien::alien-value- sap type) alien
|
|
(label-value-line*
|
|
(:sap sap)
|
|
(:type type)
|
|
(:to (alien::deref alien)))))
|
|
|
|
(defun inspect-alien-value (alien)
|
|
(typecase (alien::alien-value-type alien)
|
|
(alien::alien-record-type (inspect-alien-record alien))
|
|
(alien::alien-pointer-type (inspect-alien-pointer alien))
|
|
(t (scl-inspect alien))))
|
|
|
|
;;;; Profiling
|
|
(defimplementation profile (fname)
|
|
(eval `(profile:profile ,fname)))
|
|
|
|
(defimplementation unprofile (fname)
|
|
(eval `(profile:unprofile ,fname)))
|
|
|
|
(defimplementation unprofile-all ()
|
|
(eval `(profile:unprofile))
|
|
"All functions unprofiled.")
|
|
|
|
(defimplementation profile-report ()
|
|
(eval `(profile:report-time)))
|
|
|
|
(defimplementation profile-reset ()
|
|
(eval `(profile:reset-time))
|
|
"Reset profiling counters.")
|
|
|
|
(defimplementation profiled-functions ()
|
|
profile:*timed-functions*)
|
|
|
|
(defimplementation profile-package (package callers methods)
|
|
(profile:profile-all :package package
|
|
:callers-p callers
|
|
#+nil :methods #+nil methods))
|
|
|
|
|
|
;;;; Multiprocessing
|
|
|
|
(defimplementation spawn (fn &key name)
|
|
(thread:thread-create fn :name (or name "Anonymous")))
|
|
|
|
(defvar *thread-id-counter* 0)
|
|
(defvar *thread-id-counter-lock* (thread:make-lock "Thread ID counter"))
|
|
|
|
(defimplementation thread-id (thread)
|
|
(thread:with-lock-held (*thread-id-counter-lock*)
|
|
(or (getf (thread:thread-plist thread) 'id)
|
|
(setf (getf (thread:thread-plist thread) 'id)
|
|
(incf *thread-id-counter*)))))
|
|
|
|
(defimplementation find-thread (id)
|
|
(block find-thread
|
|
(thread:map-over-threads
|
|
#'(lambda (thread)
|
|
(when (eql (getf (thread:thread-plist thread) 'id) id)
|
|
(return-from find-thread thread))))))
|
|
|
|
(defimplementation thread-name (thread)
|
|
(princ-to-string (thread:thread-name thread)))
|
|
|
|
(defimplementation thread-status (thread)
|
|
(let ((dynamic-values (thread::thread-dynamic-values thread)))
|
|
(if (zerop dynamic-values) "Exited" "Running")))
|
|
|
|
(defimplementation make-lock (&key name)
|
|
(thread:make-lock name))
|
|
|
|
(defimplementation call-with-lock-held (lock function)
|
|
(declare (type function function))
|
|
(thread:with-lock-held (lock) (funcall function)))
|
|
|
|
(defimplementation current-thread ()
|
|
thread:*thread*)
|
|
|
|
(defimplementation all-threads ()
|
|
(let ((all-threads nil))
|
|
(thread:map-over-threads #'(lambda (thread) (push thread all-threads)))
|
|
all-threads))
|
|
|
|
(defimplementation interrupt-thread (thread fn)
|
|
(thread:thread-interrupt thread #'(lambda ()
|
|
(sys:with-interrupts
|
|
(funcall fn)))))
|
|
|
|
(defimplementation kill-thread (thread)
|
|
(thread:destroy-thread thread))
|
|
|
|
(defimplementation thread-alive-p (thread)
|
|
(not (zerop (thread::thread-dynamic-values thread))))
|
|
|
|
(defvar *mailbox-lock* (thread:make-lock "Mailbox lock" :interruptible nil))
|
|
|
|
(defstruct (mailbox)
|
|
(lock (thread:make-lock "Thread mailbox" :type :error-check
|
|
:interruptible nil)
|
|
:type thread:error-check-lock)
|
|
(queue '() :type list))
|
|
|
|
(defun mailbox (thread)
|
|
"Return 'thread's mailbox."
|
|
(sys:without-interrupts
|
|
(thread:with-lock-held (*mailbox-lock*)
|
|
(or (getf (thread:thread-plist thread) 'mailbox)
|
|
(setf (getf (thread:thread-plist thread) 'mailbox)
|
|
(make-mailbox))))))
|
|
|
|
(defimplementation send (thread message)
|
|
(let* ((mbox (mailbox thread))
|
|
(lock (mailbox-lock mbox)))
|
|
(sys:without-interrupts
|
|
(thread:with-lock-held (lock "Mailbox Send")
|
|
(setf (mailbox-queue mbox) (nconc (mailbox-queue mbox)
|
|
(list message)))))
|
|
(mp:process-wakeup thread)))
|
|
|
|
#+nil
|
|
(defimplementation receive ()
|
|
(receive-if (constantly t)))
|
|
|
|
(defimplementation receive-if (test &optional timeout)
|
|
(let ((mbox (mailbox thread:*thread*)))
|
|
(assert (or (not timeout) (eq timeout t)))
|
|
(loop
|
|
(check-slime-interrupts)
|
|
(sys:without-interrupts
|
|
(mp:with-lock-held ((mailbox-lock mbox))
|
|
(let* ((q (mailbox-queue mbox))
|
|
(tail (member-if test q)))
|
|
(when tail
|
|
(setf (mailbox-queue mbox)
|
|
(nconc (ldiff q tail) (cdr tail)))
|
|
(return (car tail))))))
|
|
(when (eq timeout t) (return (values nil t)))
|
|
(mp:process-wait-with-timeout
|
|
"Mailbox read wait" 0.5 (lambda () (some test (mailbox-queue mbox)))))))
|
|
|
|
|
|
|
|
(defimplementation emacs-connected ())
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;Trace implementations
|
|
;; In SCL, we have:
|
|
;; (trace <name>)
|
|
;; (trace (method <name> <qualifier>? (<specializer>+)))
|
|
;; (trace :methods t '<name>) ;;to trace all methods of the gf <name>
|
|
;; <name> can be a normal name or a (setf name)
|
|
|
|
(defun tracedp (spec)
|
|
(member spec (eval '(trace)) :test #'equal))
|
|
|
|
(defun toggle-trace-aux (spec &rest options)
|
|
(cond ((tracedp spec)
|
|
(eval `(untrace ,spec))
|
|
(format nil "~S is now untraced." spec))
|
|
(t
|
|
(eval `(trace ,spec ,@options))
|
|
(format nil "~S is now traced." spec))))
|
|
|
|
(defimplementation toggle-trace (spec)
|
|
(ecase (car spec)
|
|
((setf)
|
|
(toggle-trace-aux spec))
|
|
((:defgeneric)
|
|
(let ((name (second spec)))
|
|
(toggle-trace-aux name :methods name)))
|
|
((:defmethod)
|
|
nil)
|
|
((:call)
|
|
(destructuring-bind (caller callee) (cdr spec)
|
|
(toggle-trace-aux (process-fspec callee)
|
|
:wherein (list (process-fspec caller)))))))
|
|
|
|
(defun process-fspec (fspec)
|
|
(cond ((consp fspec)
|
|
(ecase (first fspec)
|
|
((:defun :defgeneric) (second fspec))
|
|
((:defmethod)
|
|
`(method ,(second fspec) ,@(third fspec) ,(fourth fspec)))
|
|
;; this isn't actually supported
|
|
((:labels) `(labels ,(process-fspec (second fspec)) ,(third fspec)))
|
|
((:flet) `(flet ,(process-fspec (second fspec)) ,(third fspec)))))
|
|
(t
|
|
fspec)))
|
|
|
|
;;; Weak datastructures
|
|
|
|
;;; Not implemented in SCL.
|
|
(defimplementation make-weak-key-hash-table (&rest args)
|
|
(apply #'make-hash-table :weak-p t args))
|