diff --git a/compiler/xref.lisp b/compiler/xref.lisp new file mode 100644 index 0000000000000000000000000000000000000000..c874df1ad9da7e1fc603251b7eed8ab9a7d66e76 --- /dev/null +++ b/compiler/xref.lisp @@ -0,0 +1,394 @@ +;;; xref.lisp -- a cross-reference facility for CMUCL +;;; +;;; Author: Eric Marsden <emarsden@laas.fr> +;;; +(ext:file-comment + "$Header: /Volumes/share2/src/cmucl/cvs2git/cvsroot/src/compiler/xref.lisp,v 1.1 2003/02/05 19:36:39 emarsden Exp $") +;; +;; This code was written as part of the CMUCL project and has been +;; placed in the public domain. +;; +;; +;; The cross-referencing facility provides the ability to discover +;; information such as which functions call which other functions and +;; in which program contexts a given global variables may be used. The +;; cross-referencer maintains a database of cross-reference +;; information which can be queried by the user to provide answers to +;; questions like: +;; +;; - the program contexts where a given function may be called, +;; either directly or indirectly (via its function-object). +;; +;; - the program contexts where a global variable (ie a dynamic +;; variable or a constant variable -- something declared with +;; DEFVAR or DEFPARAMETER or DEFCONSTANT) may be read, or bound, or +;; modified. +;; +;; More details are available in "Cross-Referencing Facility" chapter +;; of the CMUCL User's Manual. +;; +;; +;; Missing functionality: +;; +;; - there is no mechanism to save cross-reference information in a +;; FASL file, so you need to recompile all files in the current image +;; +;; - maybe add macros EXT:WITH-XREF, and an :xref option to +;; COMPILE-FILE, as per ACL. +;; +;; The cross-reference facility is implemented by walking the IR1 +;; representation that is generated by CMUCL when compiling (for both +;; native and byte-compiled code, and irrespective of whether you're +;; compiling from a file, from a stream, or interactively from the +;; listener). + + +;; types of names that we can see in IR1 LEAF nodes: +;; +;; - symbol that is a function-name or a macro name +;; - strings of the form "DEFINE-COMPILER-MACRO ~A" +;; - strings of the form "DEFMETHOD FOOBAR (SPECIALIZER1 SPECIALIZER2)" +;; - strings of the form "defstruct foo" +;; - strings of the form "Creation Form for #<kernel::class-cell struct-two>" +;; +;; and it would be nice to change this to +;; +;; - (:method foobar (specializer1 specializer2)) +;; - (:flet external-function internal-function) +;; - (:labels external-function internal-function) +;; - (:internal containing-function 0) for an anonymous lambda +;; - (:internal (flet external internal) 0) for anonymous lambda inside an FLET/LABELS + + +(in-package :xref) + +(export '(init-xref-database + register-xref + who-calls + who-references + who-binds + who-sets + #+pcl who-subclasses + #+pcl who-superclasses + make-xref-context + xref-context-name + xref-context-file + xref-context-source-path)) + + +(defstruct (xref-context (:print-function %print-xref-context)) + name + (file *compile-file-truename*) + (source-path nil)) + +(defun %print-xref-context (s stream d) + (declare (ignore d)) + (format stream "#<xref-context ~S~@[ in ~S~]>" + (xref-context-name s) + (xref-context-file s))) + + +;; program contexts where a globally-defined function may be called at runtime +(defvar *who-calls* (make-hash-table :test #'eq)) + +;; program contexts where a global variable may be referenced +(defvar *who-references* (make-hash-table :test #'eq)) + +;; program contexts where a global variable may be bound +(defvar *who-binds* (make-hash-table :test #'eq)) + +;; program contexts where a global variable may be set +(defvar *who-sets* (make-hash-table :test #'eq)) + +;; you can print these conveniently with code like +;; (maphash (lambda (k v) (format t "~S <-~{ ~S~^,~}~%" k v)) xref::*who-sets*) +;; or +;; (maphash (lambda (k v) (format t "~S <-~% ~@<~@;~S~^~%~:>~%" k v)) xref::*who-calls*) + + +(defun register-xref (type target context) + (declare (type xref-context context)) + (let ((database (ecase type + (:calls *who-calls*) + (:references *who-references*) + (:binds *who-binds*) + (:sets *who-sets*)))) + (if (gethash target database) + (pushnew context (gethash target database) :test 'equal) + (setf (gethash target database) (list context))))) + +;; INIT-XREF-DATABASE -- interface +;; +(defun init-xref-database () + "Reinitialize the cross-reference database." + (setf *who-calls* (make-hash-table :test #'eq)) + (setf *who-references* (make-hash-table :test #'eq)) + (setf *who-binds* (make-hash-table :test #'eq)) + (setf *who-sets* (make-hash-table :test #'eq)) + (values)) + + +;; WHO-CALLS -- interface +;; +(defun who-calls (function &key (reverse nil)) + "Return a list of those program contexts where a globally-defined +function may be called at runtime." + (declare (type (or symbol function) function)) + (let ((name (etypecase function + (symbol function) + ;; FIXME what about MACRO-FUNCTION? + (function (multiple-value-bind (ignore ignored fname) + (function-lambda-expression function) + (declare (ignore ignore ignored)) + (unless fname + (error "Function ~a has no name" function)) + fname)))) + (fun (etypecase function + (symbol (symbol-function function)) + (function function)))) + (if reverse + ;; this depends on the function having been loaded (rather than only + ;; on it having been compiled), and on the source file that it was + ;; compiled from being accessible. + (let ((debug-fun (di::function-debug-function fun)) + (called (list))) + (unless debug-fun + (error "Function ~a has no debug information" function)) + (di::do-debug-function-blocks (block debug-fun) + (di::do-debug-block-locations (loc block) + (di::fill-in-code-location loc) + (when (eq :call-site (di::compiled-code-location-kind loc)) + (let* ((loc (debug::maybe-block-start-location loc)) + (form-num (di:code-location-form-number loc))) + (multiple-value-bind (translations form) + (debug::get-top-level-form loc) + (unless (< form-num (length translations)) + (error "Source path no longer exists")) + (pushnew (car (di:source-path-context form (aref translations form-num) 0)) + called)))))) + called) + (gethash name *who-calls*)))) + +;; WHO-REFERENCES -- interface +;; +(defun who-references (global-variable) + "Return a list of those program contexts where GLOBAL-VARIABLE +may be referenced at runtime." + (declare (type symbol global-variable)) + (gethash global-variable *who-references*)) + +;; WHO-BINDS -- interface +;; +(defun who-binds (global-variable) + "Return a list of those program contexts where GLOBAL-VARIABLE may +be bound at runtime." + (declare (type symbol global-variable)) + (gethash global-variable *who-binds*)) + +;; WHO-SETS -- interface +;; +(defun who-sets (global-variable) + "Return a list of those program contexts where GLOBAL-VARIABLE may +be set at runtime." + (declare (type symbol global-variable)) + (gethash global-variable *who-sets*)) + + +;; introspection functions from the CLOS metaobject protocol + +;; WHO-SUBCLASSES -- interface +;; +#+pcl +(defun who-subclasses (class) + (declare (type class class)) + (pcl::class-direct-subclasses class)) + +;; WHO-SUPERCLASSES -- interface +;; +#+pcl +(defun who-superclasses (class) + (declare (type class class)) + (pcl::class-direct-superclasses class)) + +;; generic functions defined for this class +#+pcl +(defun who-specializes (class) + (declare (type class class)) + (let ((pcl-class (etypecase class + (lisp:class (pcl::coerce-to-pcl-class class)) + (pcl::class class)))) + (pcl::specializer-direct-methods pcl-class))) + + + +(in-package :compiler) + + +(defun prettiest-caller-name (lambda-node toplevel-name) + (cond + ((not lambda-node) + (list :anonymous toplevel-name)) + + ((not (eq (lambda-home lambda-node) lambda-node)) + (let ((home (lambda-name (lambda-home lambda-node))) + (here (lambda-name lambda-node))) + (cond ((and home here) + (list :internal home here)) + (t + (or here toplevel-name))))) + + ((lambda-calls lambda-node) + (let ((home (lambda-name (lambda-home lambda-node))) + (here (lambda-name lambda-node))) + (cond ((and home here) + (list :internal home here)) + (t + (or here toplevel-name))))) + + ;; for DEFMETHOD forms + ((eql 0 (search "defmethod" toplevel-name :test 'char-equal)) + ;; FIXME this probably won't handle FLET/LABELS inside a + ;; defmethod properly ... + (let* ((readable (substitute #\? #\# toplevel-name)) + (listed (concatenate 'string "(" readable ")")) + (*read-eval* nil) + (list (ignore-errors (read-from-string listed)))) + (cons :method (rest list)))) + + ;; LET and FLET bindings introduce new unnamed LAMBDA nodes. + ;; If the home slot contains a lambda with a nice name, we use + ;; that; otherwise fall back on the toplevel-name. + ((not (lambda-name lambda-node)) + (let ((home (lambda-home lambda-node))) + (or (and home (lambda-name home)) + toplevel-name))) + + ((and (listp (lambda-name lambda-node)) + (eq :macro (first (lambda-name lambda-node)))) + (lambda-name lambda-node)) + + ;; a reference from a macro is named (:macro name) + ((eql 0 (search "defmacro" toplevel-name :test 'char-equal)) + (list :macro (subseq toplevel-name 9))) + + ;; probably "Top-Level Form" + ((stringp (lambda-name lambda-node)) + (lambda-name lambda-node)) + + ;; probably (setf foo) + ((listp (lambda-name lambda-node)) + (lambda-name lambda-node)) + + (t + ;; distinguish between nested functions (FLET/LABELS) and + ;; global functions by checking whether the node has a HOME + ;; slot that is different from itself. Furthermore, a LABELS + ;; node at the first level inside a lambda may have a + ;; self-referential home slot, but still be internal. + (cond ((not (eq (lambda-home lambda-node) lambda-node)) + (list :internal + (lambda-name (lambda-home lambda-node)) + (lambda-name lambda-node))) + ((lambda-calls lambda-node) + (list :internal/calls + (lambda-name (lambda-home lambda-node)) + (lambda-name lambda-node))) + (t (lambda-name lambda-node)))))) + + +;; RECORD-NODE-XREFS -- internal +;; +;; TOPLEVEL-NAME is an indication of the name of the COMPONENT that +;; contains this node, or NIL if it was really "Top-Level Form". +(defun record-node-xrefs (node toplevel-name) + (declare (type node node)) + (let ((context (xref:make-xref-context))) + (when *compile-file-truename* + (setf (xref:xref-context-source-path context) + (reverse + (source-path-original-source + (node-source-path node))))) + (typecase node + (ref + (let* ((leaf (ref-leaf node)) + (lexenv (ref-lexenv node)) + (lambda (lexenv-lambda lexenv)) + (caller (prettiest-caller-name lambda toplevel-name))) + + (setf (xref:xref-context-name context) caller) + (typecase leaf + ;; a reference to a LEAF of type GLOBAL-VAR + (global-var + (let ((called (global-var-name leaf))) + ;; a reference to #'C::%SPECIAL-BIND means that we are + ;; binding a special variable. The information on which + ;; variable is being bound, and within which function, is + ;; available in the ref's LEXENV object. + (cond ((eq called 'c::%special-bind) + (setf (xref:xref-context-name context) (caar (lexenv-blocks lexenv))) + (xref:register-xref :binds (caar (lexenv-variables lexenv)) context)) + ;; we're not interested in lexical environments + ;; that have no name; they are mostly due to code + ;; inserted by the compiler (eg calls to %VERIFY-ARGUMENT-COUNT) + ((not caller) + (format t "XREF: ignoring call to ~a in ~a because nil lexical environment~%" + called lambda)) + ;; we're not interested in lexical environments + ;; named "Top-Level Form". + ((and (stringp caller) + (string= "Top-Level Form" caller)) + t) + ((not called) + (format t "XREF: ignoring call from ~a in ~a because caller ~a unnamed~%" + caller lambda)) + ((eq :global-function (global-var-kind leaf)) + (xref:register-xref :calls called context)) + ((eq :special (global-var-kind leaf)) + (xref:register-xref :references called context))))) + ;; a reference to a LEAF of type CONSTANT + (constant + (let ((called (constant-name leaf))) + (and called + (not (eq called t)) ; ignore references to trivial variables + caller + (not (and (stringp caller) (string= "Top-Level Form" caller))) + (xref:register-xref :references called context))))))) + + ;; a variable is being set + (cset + (let* ((variable (set-var node)) + (lexenv (set-lexenv node))) + (and (global-var-p variable) + (eq :special (global-var-kind variable)) + (let* ((lblock (first (lexenv-blocks lexenv))) + (user (or (and lblock (car lblock)) toplevel-name)) + (used (global-var-name variable))) + (setf (xref:xref-context-name context) user) + (and user used (xref:register-xref :sets used context)))))) + + ;; nodes of type BIND are used to bind symbols to LAMBDA objects + ;; (including for macros), but apparently not for bindings of + ;; variables. + (bind + t)))) + + +;; RECORD-COMPONENT-XREFS -- internal +;; +(defun record-component-xrefs (component) + (declare (type component component)) + (do ((block (block-next (component-head component)) (block-next block))) + ((null (block-next block))) + (let ((fun (block-home-lambda block)) + (name (component-name component)) + (this-cont (block-start block)) + (last (block-last block))) + (unless (eq :deleted (functional-kind fun)) + (loop + (let ((node (continuation-next this-cont))) + (record-node-xrefs node name) + (let ((cont (node-cont node))) + (when (eq node last) (return)) + (setq this-cont cont)))))))) + +;; EOF diff --git a/docs/cmu-user/cmu-user.tex b/docs/cmu-user/cmu-user.tex index 0334d5307ba56d633d8824e388e13012c44d3c8d..69b2fc22480eb266004ddcb338b360c8117d7072 100644 --- a/docs/cmu-user/cmu-user.tex +++ b/docs/cmu-user/cmu-user.tex @@ -97,7 +97,7 @@ language implementation, programming environment} \input{ipc} \input{internet} \input{debug-internals} - +\input{cross-referencing} \twocolumn \cindex{Function Index} diff --git a/docs/cmu-user/cross-referencing.tex b/docs/cmu-user/cross-referencing.tex new file mode 100644 index 0000000000000000000000000000000000000000..74ae78fcee5a249b871a72edee3edeb8b829a7f2 --- /dev/null +++ b/docs/cmu-user/cross-referencing.tex @@ -0,0 +1,280 @@ +\chapter{Cross-Referencing Facility} +\label{xref} +\cindex{cross-referencing} +\credits{by Eric Marsden} + +The \cmucl{} cross-referencing facility (abbreviated XREF) assists in +the analysis of static dependency relationships in a program. It +provides introspection capabilities such as the ability to know which +functions may call a given function, and the program contexts in which +a particular global variable is used. The compiler populates a +database of cross-reference information, which can be queried by the +user to know: + +\begin{itemize} +\item +the list of program contexts (functions, macros, top-level forms) +where a given function may be called at runtime, either directly or +indirectly (via its function-object); + +\item +the list of program contexts where a given global variable may be +read; + +\item +the list of program contexts that bind a global variable; + +\item +the list of program contexts where a given global variable may be +modified during the execution of the program. +\end{itemize} + +A global variable is either a dynamic variable or a constant variable, +for instance declared using \code{defvar} or \code{defparameter} or +\code{defconstant}. + + +\section{Populating the cross-reference database} + +\begin{defvar}{c:}{record-xref-info} + When non-NIL, code that is compiled (either using + \code{compile-file}, or by calling \code{compile} from the + listener), will be analyzed for cross-references. Defaults to + \nil{}. +\end{defvar} + +Cross-referencing information is only generated by the compiler; the +interpreter does not populate the cross-reference database. XREF +analysis is independent of whether the compiler is generating native +code or byte code, and of whether it is compiling from a file, from a +stream, or is invoked interactively from the listener. + +\begin{defun}{xref:}{init-xref-database}{} + Reinitializes the database of cross-references. This can be used to + reclaim the space occupied by the database contents, or to discard + stale cross-reference information. +\end{defun} + + + +\section{Querying the cross-reference database} + +\cmucl{} provides a number of functions in the XREF package that may +be used to query the cross-reference database: + +\begin{defun}{xref:}{who-calls}{\args \var{function}} + Returns the list of xref-contexts where \var{function} (either a + symbol that names a function, or a function object) may be called + at runtime. XREF does not record calls to macro-functions (such as + \code{defun}) or to special forms (such as \code{eval-when}). +\end{defun} + +\begin{defun}{xref:}{who-references}{\args \var{global-variable}} + Returns the list of program contexts that may reference + \var{global-variable}. +\end{defun} + +\begin{defun}{xref:}{who-binds}{\args \var{global-variable}} + Returns a list of program contexts where the specified global + variable may be bound at runtime (for example using \code{LET}). +\end{defun} + +\begin{defun}{xref:}{who-sets}{\args \var{global-variable}} + Returns a list of program contexts where the given global variable + may be modified at runtime (for example using \code{SETQ}). +\end{defun} + +An \textit{xref-context} is the originating site of a cross-reference. +It identifies a portion of a program, and is defined by an +\code{xref-context} structure, that comprises a name, a source file and a +source-path. + +\begin{defun}{xref:}{xref-context-name}{\args \var{context}} + Returns the name slot of an xref-context, which is one of: +\begin{itemize} +\item +a global function, which is named by a symbol or by a list of the form +\code{(setf\ foo)}. + +\item +a macro, named by a symbol. + +\item +an inner function (\code{flet}, \code{labels}, anonymous lambdas) that +is named by its containing function, as a string. + +\item +a method, named by a string of the form +\verb|"DEFMETHOD FOO (SPECIALIZER1 SPECIALIZER2)"|. + +\item +a string \verb|"Top-Level Form"| that identifies a reference from a +top-level form. Note that multiple references from top-level forms +will only be listed once. + +\item +a compiler-macro, named by a string of the form +\verb|"DEFINE-COMPILER-MACRO FOO"|. + +\item +a string such as \verb|"DEFSTRUCT FOO"|, identifying a reference from +within a structure accessor or constructor or copier. + +\item +a string such as +\begin{verbatim} + "Creation Form for #<KERNEL::CLASS-CELL STRUCT-FOO>" +\end{verbatim} +\end{itemize} +\end{defun} + + +\begin{defun}{xref:}{xref-context-file}{context} + Return the truename (in the sense of the variable + \vindexed{compile-file-truename}) of the source file from which the + referencing forms were compiled. This slot will be \nil{} if the + code was compiled from a stream, or interactively from the + listener. +\end{defun} + +\begin{defun}{xref:}{xref-context-source-path}{context} + Return a list of positive integers identifying the form that + contains the cross-reference. The first integer in the source-path + is the number of the top-level form containing the cross-reference + (for example, 2 identifies the second top-level form in the source + file). The second integer in the source-path identifies the form + within this top-level form that contains the cross-reference, and so + on. This function will always return \nil{} if the file slot of an + xref-context is \nil{}. + +% While walking the top-level form, count one in depth-first order for +% each subform that is a cons. +\end{defun} + + + + +\section{Example usage} + +In this section, we will illustrate use of the XREF facility on a +number of simple examples. + +Consider the following program fragment, that defines a global +variable and a function. + +\begin{verbatim} + (defvar *variable-one* 42) + + (defun function-one (x) + (princ (* x *variable-one*))) +\end{verbatim} + +We save this code in a file named \code{example.lisp}, enable +cross-referencing, clear any previous cross-reference information, +compile the file, and can then query the cross-reference database +(output has been modified for readability). + +\begin{verbatim} + USER> (setf c:*record-xref-info* t) + USER> (xref:init-xref-database) + USER> (compile-file "example") + USER> (xref:who-calls 'princ) + (#<xref-context function-one in #p"example.lisp">) + USER> (xref:who-references '*variable-one*) + (#<xref-context function-one in #p"example.lisp">) +\end{verbatim} + +From this example, we see that the compiler has noted the call to the +global function \code{princ} in \code{function-one}, and the reference +to the global variable \code{*variable-one*}. + +Suppose that we add the following code to the previous file. + +\begin{verbatim} +(defconstant +constant-one+ 1) + +(defstruct struct-one + slot-one + (slot-two +constant-one+ :type integer) + (slot-three 42 :read-only t)) + +(defmacro with-different-one (&body body) + `(let ((*variable-one* 666)) + ,@body)) + +(defun get-variable-one () *variable-one*) + +(defun (setf get-variable-one) (new-value) + (setq *variable-one* new-value)) +\end{verbatim} + +In the following example, we detect references x and y. + + +% FIXME add function with LABELS, a binding, a set + + + +The following function illustrates the effect that various forms of +optimization carried out by the \cmucl{} compiler can have on the +cross-references that are reported for a particular program. The +compiler is able to detect that the evaluated condition is always +false, and that the first clause of the \code{if} will never be taken +(this optimization is called dead-code elimination). XREF will +therefore not register a call to the function \code{sin} from the +function \code{foo}. Likewise, no calls to the functions \code{sqrt} +and \verb|<| are registered, because the compiler has eliminated the +code that evaluates the condition. Finally, no call to the function +\code{expt} is generated, because the compiler was able to evaluate +the result of the expression \code{(expt 3 2)} at compile-time (though +a process called constant-folding). + +\begin{verbatim} +;; zero call references are registered for this function! +(defun constantly-nine (x) + (if (< (sqrt x) 0) + (sin x) + (expt 3 2))) +\end{verbatim} + + +\section{Limitations of the cross-referencing facility} + +No cross-reference information is available for interpreted functions. +The cross-referencing database is not persistent: unless you save an +image using \code{save-lisp}, the database will be empty each time +\cmucl{} is restarted. There is no mechanism that saves +cross-reference information in FASL files, so loading a system from +compiled code will not populate the cross-reference database. + +The cross-referencing facility is only able to analyze the static +dependencies in a program; it does not provide any information about +runtime (dynamic) dependencies. For instance, XREF is able to identify +the list of program contexts where a given function may be called, but +is not able to determine which contexts will be activated when the +program is executed with a specific set of input parameters. However, +the static analysis that is performed by the \cmucl{} compiler does +allow XREF to provide more information than would be available from a +mere syntactic analysis of a program. References that occur from +within unreachable code will not be displayed by XREF, because the +\cmucl{} compiler deletes dead code before cross-references are +analyzed. Certain ``trivial'' function calls (where the result of the +function call can be evaluated at compile-time) may be eliminated by +optimizations carried out by the compiler; see the example below. + +If you examine the entire database of cross-reference information (by +accessing undocumented internals of the XREF package), you will note +that XREF notes ``bogus'' cross-references to function calls that are +inserted by the compiler. For example, in safe code, the \cmucl{} +compiler inserts a call to an internal function called +\code{c::\%verify-argument-count}, so that the number of arguments +passed to the function is checked each time it is called. The XREF +facility does not distinguish between user code and these forms that +are introduced during compilation. This limitation should not be +visible if you use the documented functions in the XREF package. + +As of the 18e release of \cmucl{}, the cross-referencing facility is +experimental; expect details of its implementation to change in future +releases. In particular, the names given to CLOS methods and to inner +functions will change in future releases. +