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.
Alternatively, the ::xref option to compile-file may be
specified to populate the cross-reference database when compiling a
file. In this case, loading the generated fasl file in a fresh lisp
will also populate the cross-reference database.
[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.
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.