Chapter 12 Cross-Referencing
Facility
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:
- 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);
- the list of program contexts where a given
global variable may be read;
- the list of program contexts that bind a
global variable;
- the list of program contexts where a given
global variable may be modified during the execution of the
program.
A global variable is either a dynamic variable or a constant
variable, for instance declared using defvar
or defparameter or defconstant.
12.1 Populating the cross-reference
database
[Variable]
c:*record-xref-info*
When non-NIL, code that is compiled
(either using compile-file, or by calling
compile from the listener), will be analyzed
for cross-references. Defaults to nil.
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.
[Function]
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.
12.2 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:
[Function]
xref:who-calls function
Returns the list of xref-contexts where
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 defun) or to special forms (such as eval-when).
[Function]
xref:who-references global-variable
Returns the list of program contexts that
may reference global-variable.
[Function]
xref:who-binds global-variable
Returns a list of program contexts where
the specified global variable may be bound at runtime (for example
using LET).
[Function]
xref:who-sets global-variable
Returns a list of program contexts where
the given global variable may be modified at runtime (for example
using SETQ).
An xref-context is the originating site of a
cross-reference. It identifies a portion of a program, and is
defined by an xref-context structure, that
comprises a name, a source file and a source-path.
[Function]
xref:xref-context-name context
Returns the name slot of an xref-context,
which is one of:
[Function]
xref:xref-context-file context
Return the truename (in the sense of the
variable *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.
[Function]
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.
12.3 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.
(defvar *variable-one* 42)
(defun function-one (x)
(princ (* x *variable-one*)))
We save this code in a file named 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).
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">)
From this example, we see that the compiler has noted the call to
the global function princ in function-one, and the reference to the global variable
*variable-one*.
Suppose that we add the following code to the previous file.
(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))
In the following example, we detect references x and y.
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 if
will never be taken (this optimization is called dead-code
elimination). XREF will therefore not register a call to the
function sin from the function foo. Likewise, no calls to the functions sqrt and < are registered,
because the compiler has eliminated the code that evaluates the
condition. Finally, no call to the function expt is generated, because the compiler was able to
evaluate the result of the expression (expt 3
2) at compile-time (though a process called constant-folding).
;; zero call references are registered for this function!
(defun constantly-nine (x)
(if (< (sqrt x) 0)
(sin x)
(expt 3 2)))
12.4 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 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 XREF database currently accumulates
“stale” information: when compiling a file, it does not
delete any cross-references that may have previously been generated
for that file. This latter limitation will be removed in a future
release.
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 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.