|
Allegro CL version 11.0 |
The Common Lisp standard requires that a Common Lisp system have a compiler and a loader. Since running interpreted code in any Common Lisp system is slow compared to running compiled code, most users will want to compile functions after trying them out in interpreted mode. A compiled function will run many times faster than its interpreted counterpart.
There are several implementation-dependent facets to a Common Lisp compiler. These include the naming of source and object files, how declarations are handled, and how floating-point and other optimizations are performed. These issues are discussed in this document.
Symbols relating to the compiler that are not standard CL symbols (like compile) are typically in the :compiler
package.
Building an image with build-lisp-image (see building-images.html) with include-compiler nil
(or include-compiler true and discard-compiler also true) results in a compilerless Lisp image.
Standard runtime images (see runtime.html) may not have the compiler included. In such images, it is an error to call compile or compile-file directly or indirectly. The value of the *compiler-not-available-warning* (discussed further below) variable is ignored in standard runtime images and thus does not affect whether or not an error is signaled. The condition signaled is runtime-restriction
.
The remainder of this section discusses images which are not standard runtime images.
It is possible to load the compiler into a compilerless image. You do so by evaluating (require :compiler)
. However, it may be difficult or impossible to compile everything that would have been compiled had the compiler been present earlier (difficult or impossible because you do not know what the things are). Therefore, parts of Lisp will run slower than necessary after the compiler is loaded with no space saving (since you loaded the compiler). In short, if you intend to use the compiler, start with an image that includes it!
But note that you can build an image with the compiler included during the build and discarded after the build completes. This might be very useful when creating an application for distribution when you are not licensed to distribute an image with the compiler. If you specify :include-compiler t
and :discard-compiler t
in a call to build-lisp-image or generate-application (which accepts build-lisp-image arguments), the compiler will be present while the image is built and discarded at the end. Thus, things like applying advice (see fwrappers-and-advice.html) can be done and compiled during the build. (Note that the new fwrapper facility, described in the same document, works with objects that can be pre-compiled.)
If you use a compilerless Lisp, the system will (in the default) print a warning every time you or the system tries to call compile. For example, consider the following script from a compilerless Lisp image:
1): (defun foo nil nil)
USER(
FOO2): (compile 'foo)
USER(not loaded -- function left uncompiled
Warning: Compiler
NIL
NIL
NIL3): USER(
The text of the warning changes according to what the system was unable to compile. We emphasized that compile can be called by the system since some operations, including calling advise under certain circumstances, calling the (obsolete) function defforeign (but not calling its replacement macro def-foreign-call) in a fasl file, and doing non-standard method combinations in CLOS or MOP usage, do cause the compiler to be called and the warning to be printed. It is possible to suppress these warnings, either globally or locally. The variable *compiler-not-available-warning* controls whether the warning is printed or not.
We do not recommend setting *compiler-not-available-warning* to nil
because interpreted code will run significantly more slowly than compiled and users should know when the compiler was called so they can expect degraded performance.
It is possible to suppress the warning locally. The warnings actually signal the condition compiler-not-available-warning. Thus, it is possible to suppress these warnings by wrapping the following code around sections likely to produce such unwanted warnings:
handler-bind ((excl:compiler-not-available-warning
(lambda (c) (declare (ignore c)) (muffle-warning))))
#'(
<code-which--may-directly-or-indirectly-calls-compile>)
Note again that it is possible to build an image with the compiler included during the build and discarded at the end. Things processed during the build will be processed with the compiler but the final image will not have it (and thus suitable for delivery when the license does not permit distribution of images containing the compiler, such as standard runtime images, see runtime.html). See the description of the include-compiler and discard-compiler arguments to build-lisp-image in building-images.html.
The Common Lisp function compile-file is used to compile a file containing Common Lisp source code. We describe that function here to point out some Allegro-CL-specific arguments. The link above is to the ANSI description.
Function, package: common-lisp
Arguments: input-filename &key output-file xref verbose print fasl-circle load-after-compile (script t)
This function will compile the Common Lisp forms in input-filename. input-filename must be a pathname, a string, or a stream.
input-filename is merged with *default-pathname-defaults*. If the input-filename name has no type, then compile-file uses the types in the list *source-file-types*. The name of the output file may be specified by the output-file keyword argument. The default type of the output file is specified by the variable *fasl-default-type*.
The output file will have a text header (readable, e.g., by the Unix command head) which provides information such as the time of creation and the user who created the file.
This function returns three values. The first value is the truename of the output file, or nil
if the file could not be created. The second value is nil
if no compiler diagnostics were issued, and true otherwise. The third value is nil
if no compiler diagnostics other than style warnings were issued. If the third returned value is true, there were serious compiler diagnostics issued or other conditions of type error
or type warning
were signaled during compilation.
The xref keyword argument is a boolean that controls whether cross-referencing information is stored in the output file. The default value of the xref keyword argument is the value of *record-xref-info*.
The verbose keyword argument is a boolean that controls printing of the filename that is currently being compiled. The default value of the verbose keyword argument is the value of compile-verbose.
The print keyword argument is a boolean that controls whether compile-file prints the top-level form currently being compiled. The default value of the print keyword argument is the value of compile-print.
The fasl-circle keyword argument is an Allegro CL extension (i.e. may not be supported in versions of Common Lisp other than Allegro CL). If the value of this argument is nil
, there are two effects:
make-load-form processing is not done. CLOS instances can be written to fasl files only if make-load-form processing is done and structures are dumped as tagged vectors (instead of using make-load-form processing).
The requirement that the compiler is required to preserve eql-ness within a file compiled by compile-file -- will not be met.
On the other hand, compilation speed is typically faster when this argument is nil
. This argument defaults to the value of *fasl-circle-default*.
The load-after-compile keyword argument is an Allegro CL extension (i.e. may not be supported in versions of Common Lisp other than Allegro CL). If the value of this argument is true (default is nil
), the fasl (compiled Lisp) file will be loaded when compilation completes. See also the t
top-level command, which also compiles and loads files.
The script keyword argument, when true (the default), allows scripts with initial lines starting with #! to be compiled. Those lines are ignored by the Lisp compiler when script is true.
For example, consider the following script file test.cl:
10.0/bin/linuxamd64.64/mlisp -#T
#! /fi/cl/format t "Hello world!~%") (
When we run it in a UNIX shell, we get:
% test.cl
Hello world! %
Compiling it with compile-file works okay with script t
and fails with script nil
:
20): (compile-file "test.sh" :script t)
cl-user(;;; Compiling file test.sh
;;; Writing fasl file test.fasl
;;; Fasl write complete
"/net/gemini/home/dm/test.fasl"
#Pnil
nil
21): (compile-file "test.sh" :script nil)
cl-user(;;; Compiling file test.sh
; While file-compiling #'"test.sh" in #P"test.sh":
function defined for #\!. [file position = 2]
Error: No dispatch condition type: reader-error]
[
Restart actions (select using :continue):0: retry the compilation of test.sh
1: continue compiling test.sh but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
1] cl-user(22): [
Calling Allegro CL in a UNIX script and the meaning of arguments like -#T are described in Starting on UNIX using a shell script in startup.html.
If a definition of an object (with a defining macro like defpackage, defmacro, defstruct, etc.) within a file being compiled with compile-file persists in Lisp after the compilation is complete, the definition is said to be persistent. See Persistence of defining forms encountered by compile-file for details. The short story is, absent explicit direction (such as with an eval-when), the only persistent defining form is defpackage.
The :cf top-level command invokes compile-file. :cf
does not accept keyword arguments but does accept multiple input-filename arguments. :cf
reads its arguments as strings, so you should not surround input-filenames with double quotes. Thus
:cf foo.cl
is equivalent to
compile-file "foo.cl") (
Suppose in a running Lisp, the symbols foo
and foom
do not name any objects, indeed do not even exist in any package. Consider the following file named
;; file foo.cl begin
in-package :user)
(
defpackage :foo (:use :cl :excl))
(defmacro foom (x) x)
(defun foo (y) (cons y y))
(defclass foo () ())
(defvar foo nil)
(
;; file foo.cl end
We now compile the file using compile-file:
compile-file "foo.cl") (
After the file compilation completes, in the running Lisp, does foo
now name a package, a function, a class, and a global variable? Does foom
name a macro?
The answer is, only the package is now defined in the Lisp:
16): (describe 'foo)
cl-user(symbol.
foo is a
It is unbound.the common-lisp-user package.
It is internal in 17): (find-package :foo)
cl-user(
#<The foo package>18): (describe 'foom)
cl-user(symbol.
foom is a
It is unbound.the common-lisp-user package.
It is internal in 19): cl-user(
The ANSI spec does not say whether defining forms persist after a compile-file (i.e. the definition should be available in the running Lisp when compilation is complete). The behavior of Allegro CL in releases prior to 7.0 was uneven, with some defining forms persisting and others not. Starting in release 7.0, the behavior is consistent: only defpackage forms persist.
We believe this new consistency will make it easier to know what the best compilation strategy is, particularly now that we support compiler enviroments (see environments.html). Because it is easy to ensure definitions persist by using eval-when's, we believe that the extra programming burden is slight.
Here is a list of defining macros. Those marked with (*) persisted in earlier releases but no longer persist. More complicated situations are also noted:
If you wish a defining form to persist, wrap it in an appropriate eval-when form, such as:
eval-when (compile load eval) (defmacro foo ...)) (
Definitions do not persist (absent eval-when's) from one file to another within a compilation unit:
;; file foo1.cl begin
in-package :user)
(
defvar *foo1* nil)
(;; file foo1.cl end
;; file foo2.cl begin
in-package :user)
(
defvar *foo2* nil)
(
defun bar ()
(
*foo1*);; file foo2.cl end
1): :cf foo1
cl-user(;;; Compiling file foo1.cl
;;; Writing fasl file foo1.fasl
;;; Fasl write complete
2): :cf foo2
cl-user(;;; Compiling file foo2.cl
; While compiling bar:
special.
Warning: Free reference to undeclared variable *foo1* assumed ;;; Writing fasl file foo2.fasl
;;; Fasl write complete
3): (with-compilation-unit ()
cl-user(compile-file "foo1.cl")
(compile-file "foo2.cl"))
(;;; Compiling file foo1.cl
;;; Writing fasl file foo1.fasl
;;; Fasl write complete
;;; Compiling file foo2.cl
; While compiling bar:
special.
Warning: Free reference to undeclared variable *foo1* assumed ;;; Writing fasl file foo2.fasl
;;; Fasl write complete
"foo2.fasl"
#pt
t
4): cl-user(
Compiled Lisp files are called fasl files. ('Fasl' stands for fast loading.) By default, compile-file creates a file with type fasl containing the compiled Lisp code. See File types for information on how to change the default type of a compiled file.
Fasl files are not human readable, but they do have headers which contain readable information, including:
There are 10 readable lines at the top of a fasl file. The UNIX head() command will print the readable head of a fasl file (assuming it prints the usual 10 lines by default).
If you try to load an inappropriate fasl file into a Lisp image, an error is signaled. One such error is file-incompatible-fasl-error, which is signaled when you try to load a fasl file compiled in an earlier version (so the R/S number and the fasl version do not match). Another error is fasl-casemode-mismatch, which is signaled when an attempt is made to load a file compiled in case-insensitive-upper (ansi) into a case-sensitive-lower (modern) mode image.
A fasl file is considered a Portable fasl file, if it was built with no native instruction-level code in it. Such a fasl file is marked with "Portable: fully byte-compiled" in its header. The header of a fasl file named foo.fasl can be viewed by typing
head foo.fasl
into a Unix or Cygwin shell.
Being portable means that the file has no assumptions about architecture, endianness, or word size and the result of reading that fasl file into a Lisp of differing chartacteristics is generally a successful load. There are a few exceptions to this, however:
The usefulness of this portable fasl resides mainly in the utility of excl:fasl-write and excl:fasl-read. Most fasl files generated by the compiler have at least top-level-forms, which are implemented as single-use native-code segments, and which thus result in the fasl file not being marked as portable.
A new strategy for compile-file introduced in release 11.0 reduces problems and allows more intuitive file compilation. Instead of writing directly to a target fasl-file, the compiler will first open a temporary file (using make-temp-file-name and will only copy the file to its final destination if/when the compilation and the writing of the fasl-file are complete.
Positive side-effects of this change are:
Aborted compile-file executions no longer cause the original fasl-file to be left in a truncated state, if it existed before the compile-file. The final move from the temporary file to the fasl-file is less likely to fail, so fasl files are left intact until the last possible moment.
compile-file output can be sent to non-file streams. For example, if a file-compilation is desired to a bit-bucket, one can use this model:
compile-file "xyz.cl" :output-file
("/dev/null" #+mswindows "nul") #-mswindows
and it will work intuitively, even though no final file is produced.
Note that if *compile-verbose* is true, compile-file now prints the name of the temporary file it is writing to, and then prints where it finally places the fasl file.
Note also that since make-temp-file-name (with cft
as the prefix) is used, the temporary file will be created in the directory selected by temporary-directory.
Another less obvious change to compile-file is that major sections of the fasl file (the main object section and optionally documentation, cross-reference, source-file-info, and source-level-debug info) are written separately and then discarded, so as to save space and time during the gathering and writing of the info. The resultant sizes of the fasl files may be a bit larger due to less reuse of duplicate data between sections, but usually only by a few percent, and anomalous cases where there is a data explosion might fit better into reasonable timeframes because data waiting to be written is kept in the lisp heap for less time.
The macro ensuring-compiled-body is like progn in that it executes its body forms sequentially, but if encountered in interpreted code, it wraps the body in a lambda
and passes it to compile-lambda-expr-in-env for compilation, and then funcalls the result. Thus the code will be executed compiled in all cases. No (accessible) function is created. Surrounding code not wrapped in ensuring-compiled-body is not compiled.
The Allegro CL compiler signals conditions when it detects difficulties or potential difficulties with the code being compiled. Conditions specific to the compiler tend to be warnings, but general warnings and errors can also happen while compiling as well. As with all conditions a source-context is generated and stored into the condition, but also when compiling a compiler backtrace is generated and lodged within the condition for later printing by the :error top-level-command.
Whenever a condition is generated while compiling (i.e. while compile-file or compile are operating), a compiler backtrace is generated and installed into the condition's source-context structure. This compiler backtrace contains a list of forms that the compiler is processing, with the innermost form first and the outermost form last (presumably a block form). When it is printed, it represents a backtrace of what the compiler was working on at the time of the condition.
Here are two examples of many of the aspects of compiler-backtraces at work, including position-information if available. The following source is used for these two examples in file backtrace.cl
:
defmacro even-number-case (value &body clauses)
(cond ,@(mapcar (lambda (clause)
`(destructuring-bind (number &rest body) clause
(unless (evenp number)
(error "Only even numbers are allowed."))
(= ,value ,number)
`((
,@body)))
clauses)))
defun foo (x)
(let ((y (* x 2)))
(/ y 2)
(even-number-case (2 :two)
(4 :four)
(5 :fix)
(8 :eight)
(10 :ten)))) (
The examples below will show the basic 3 components of a condition being printed during compilation:
The printing is in three sections, some of which could be elided based on the situation:
the location of the condition, if available,
the condition's actual formatted message, and
the compiler-backtrace, if appropriate.
Note that these examples will not call out the error location quite as efficiently as it could. See below for a more precise example.
This example shows a compiler-backtrace when the debug optimization quality is less than 3. Note that the best the compiler can do here to generate position-information is to note the starting location of the defun form.
;;; We start by ensuring that we see everything printed:
;;;
1): (setq *print-right-margin* 150)
cl-user(150
;;; Now, when compiling, and finding an error while expanding the macro,
;;; we can see the error and its backtrace.
;;;
2): :cf backtrace
cl-user(;;; Compiling file backtrace.cl
; While file-compiling #'foo in #P"backtrace.cl"
; starting at file character position 279:
Error: Only even numbers are allowed.when processing
Problem detected / y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten))
(even-number-case (let ((y (* x 2))) (even-number-case (/ y 2) (2 :two) ...))
inside (block foo (let ((y (* x 2))) (even-number-case (/ y 2) (2 :two) ...)))
inside (
Restart actions (select using :continue):0: retry the compilation of backtrace.cl
1: continue compiling backtrace.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
;;; Note that the :error command reproduces the 3-part printing of the
;;; condition.
;;;
1] cl-user(3): :error
[; While file-compiling #'foo in #P"backtrace.cl"
; starting at file character position 279:
Only even numbers are allowed.
Restart actions (select using :continue):0: retry the compilation of backtrace.cl
1: continue compiling backtrace.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
when processing
Problem detected / y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten))
(even-number-case (let ((y (* x 2))) (even-number-case (/ y 2) (2 :two) ...))
inside (block foo (let ((y (* x 2))) (even-number-case (/ y 2) (2 :two) ...)))
inside (1] cl-user(4): [
This example shows similar results as above, but compiling with the debug optimization quality set to 3, which also shows position-information closer to the actual error. For position information even closer to the error, see the example below.
;;; We start by ensuring that we see everything printed and source-debug info present
;;;
1): (setq *print-right-margin* 150)
cl-user(150
2): (declaim (optimize debug))
cl-user(t
;;; Now, when compiling, and finding an error while expanding the macro,
;;; we can see the error and its backtrace. Note that the position information
;;; is better; the offending form is available and its location is identified.
;;; Note also that the compiler-backtrace also identifies the locations of each
;;; form within the compilation context:
;;;
3): :cf backtrace
cl-user(;;; Compiling file backtrace.cl
; While file-compiling #'foo in #P"backtrace.cl"
; between file character positions 319 (inclusively) and 424 (exclusively)
;[form: "(even-number-case (/ y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten))"]:
Error: Only even numbers are allowed.when processing
Problem detected / y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten)) [start:319, end:424]
(even-number-case (let ((y (* x ...))) ...) [start:296, end:425]
inside (
Restart actions (select using :continue):0: retry the compilation of backtrace.cl
1: continue compiling backtrace.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
;;; Note that the :error command reproduces the 3-part printing of the
;;; condition, including the file-positions.
;;;
1] cl-user(4): :error
[; While file-compiling #'foo in #P"backtrace.cl"
; between file character positions 319 (inclusively) and 424 (exclusively)
;[form: "(even-number-case (/ y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten))"]:
Only even numbers are allowed.
Restart actions (select using :continue):0: retry the compilation of backtrace.cl
1: continue compiling backtrace.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
when processing
Problem detected / y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten)) [start:319, end:424]
(even-number-case (let ((y (* x ...))) ...) [start:296, end:425]
inside (1] cl-user(5): [
This example of with-current-source-form macro usage shows how errors can be called out even closer to the source of the error when complex forms from macroexpansions are being processed.
Contrast this example with the high debug example above where the error can be called out only as specifically as the even-number-case form.
Also demonstrated is the use of the :verbose
option to the :error top-level-command and also *print-verbose-source-context*
The following file named with-src.cl
is used:
defmacro even-number-case (value &body clauses)
(cond ,@(mapcar (lambda (clause)
`(
(excl::with-current-source-form (clause)destructuring-bind (number &rest body) clause
(unless (evenp number)
(error "Only even numbers are allowed."))
(= ,value ,number)
`((
,@body))))
clauses)))
defun foo (x)
(let ((y (* x 2)))
(/ y 2)
(even-number-case (2 :two)
(4 :four)
(5 :fix)
(8 :eight)
(10 :ten)))) (
and the transcript is shown here annotated:
;;; Let's start with a simple compilation of the file. Note that the presence
;;; of the excl::with-current-source-form macro has narrowed down the offending
;;; form to the bad (i.e. odd entry) form. Note also that the backtrace is
;;; relatively neat, but that we can't see the whole let or block form:
;;;
1): :cf with-src
cl-user(;;; Compiling file with-src.cl
; While file-compiling #'foo in #P"with-src.cl"
; starting at file character position 450:
Error: Only even numbers are allowed.when processing
Problem detected 5 :fix)
(/ y 2) (2 :two) ...)
inside (even-number-case (let ((y (* x ...))) ...)
inside (block foo ...)
inside (
Restart actions (select using :continue):0: retry the compilation of with-src.cl
1: continue compiling with-src.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
;;; So let's ask for the verbose printout. Now we can see the whole
;;; forms, but they're on multiple lines. This can be what we want,
;;; or it might be too verbose:
;;;
1] cl-user(2): :error :verbose t
[; While file-compiling #'foo in #P"with-src.cl"
; starting at file character position 450:
Only even numbers are allowed.
Restart actions (select using :continue):0: retry the compilation of with-src.cl
1: continue compiling with-src.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
when processing
Problem detected 5 :fix)
(/ y 2) (2 :two) (4 :four) (5 :fix) (8 :eight)
inside (even-number-case (10 :ten))
(let ((y (* x 2)))
inside (/ y 2) (2 :two) (4 :four) (5 :fix)
(even-number-case (8 :eight) (10 :ten)))
(block foo
inside (let ((y (* x 2)))
(/ y 2) (2 :two) (4 :four) (5 :fix)
(even-number-case (8 :eight) (10 :ten))))
(
;;; So let's try again with :verbose nil; we get the same result
;;; as the original error:
;;;
1] cl-user(3): :error :verbose nil
[; While file-compiling #'foo in #P"with-src.cl"
; starting at file character position 450:
Only even numbers are allowed.
Restart actions (select using :continue):0: retry the compilation of with-src.cl
1: continue compiling with-src.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
when processing
Problem detected 5 :fix)
(/ y 2) (2 :two) ...)
inside (even-number-case (let ((y (* x ...))) ...)
inside (block foo ...)
inside (1] cl-user(4): :res
[
;;; Now we experiment with the variable and with some higher debug: the
;;; variable is nil (it was never changed) so we set it to t:
;;;
5): *print-verbose-source-context*
cl-user(nil
6): (setq *print-verbose-source-context* t)
cl-user(t
7): (declaim (optimize debug))
cl-user(t
;;; Note now that the initial error is verbose, and that it has called out
;;; the specific offending form:
;;;
8): :cf with-src
cl-user(;;; Compiling file with-src.cl
; While file-compiling #'foo in #P"with-src.cl"
; between file character positions 490 (inclusively) and 595 (exclusively)
;[form: "(even-number-case (/ y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten))"]:
Error: Only even numbers are allowed.when processing
Problem detected 5 :fix)
(553, end:561]
[start:/ y 2) (2 :two) (4 :four) (5 :fix) (8 :eight)
inside (even-number-case (10 :ten))
(490, end:595]
[start:let ((y (* x 2)))
inside (/ y 2) (2 :two) (4 :four) (5 :fix)
(even-number-case (8 :eight) (10 :ten)))
(467, end:596]
[start:
Restart actions (select using :continue):0: retry the compilation of with-src.cl
1: continue compiling with-src.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
;;; If we ask for a non-verbose error message again, we get it:
;;;
1] cl-user(9): :error :verbose nil
[; While file-compiling #'foo in #P"with-src.cl"
; between file character positions 490 (inclusively) and 595 (exclusively)
;[form: "(even-number-case (/ y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten))"]:
Only even numbers are allowed.
Restart actions (select using :continue):0: retry the compilation of with-src.cl
1: continue compiling with-src.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
when processing
Problem detected 5 :fix) [start:553, end:561]
(/ y 2) (2 :two) ...) [start:490, end:595]
inside (even-number-case (let ...) [start:467, end:596]
inside (
;;; But note that we haven't changed the variable back to nil:
;;;
1] cl-user(10): *print-verbose-source-context*
[t
;;; To affect the variable in this thread, we must use :always or :never:
;;;
1] cl-user(11): :error :verbose :never
[; While file-compiling #'foo in #P"with-src.cl"
; between file character positions 490 (inclusively) and 595 (exclusively)
;[form: "(even-number-case (/ y 2) (2 :two) (4 :four) (5 :fix) (8 :eight) (10 :ten))"]:
Only even numbers are allowed.
Restart actions (select using :continue):0: retry the compilation of with-src.cl
1: continue compiling with-src.cl but generate no output file
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
when processing
Problem detected 5 :fix) [start:553, end:561]
(/ y 2) (2 :two) ...) [start:490, end:595]
inside (even-number-case (let ...) [start:467, end:596]
inside (
;;; And that does it:
;;;
1] cl-user(12): *print-verbose-source-context*
[nil
1] cl-user(13): [
The Allegro CL compiler signals warnings when it detects potential difficulties with the code being compiled. We describe these warnings here. These are not errors and the compilation will complete and compiled code will be produced. The warnings simply flag potential problems, perhaps uncovering a coding error which will cause a program problem even though the code compiles.
The following condition classes are among those that might be signaled:
compiler-free-reference-warning: signaled when the compiler sees a variable it doesn't recognize (and thus treats it as a special variable).
compiler-inconsistent-name-usage-warning: signaled whenever variable or tag names are used in a manner inconsistent with their possibly intended usage, either unused (and for variables, not declared ignore or ignorable), or, again for variables, declared ignore but used.
compiler-no-in-package-warning: this warning is no longer used.
compiler-undefined-functions-called-warning: signaled when the compiler notices that there are calls to not-yet-defined functions in a function being compiled. This will not be signaled when the function is defined in the same file or is defined within the body of with-compilation-unit.
compiler-unreachable-code-warning: signaled whenever a cond clause or a case clause cannot be reached, presumably because of the presence of a t
or otherwise
clause before the unreachable clause. This warning is suppressed in code wrapped with the with-unreachable-code-allowed macro.
If you try to compile a file that contains an incomplete form (because of, e.g., a missing closing parenthesis), compile-file signals an error with condition end-of-file
. Consider the following file missing-paren.cl:
defun foo nil nil)
(defun bar (a b) (+ a b) (
The closing parenthesis is missing from the definition of bar. When Lisp tries to compile this file, it signals an error:
USER(3): :cf missing-paren.cl
; --- Compiling file /net/rubix/usr/tech/dm/missing-paren.cl ---
; Compiling FOO
Error: eof encountered on stream"
#<EXCL::CHARACTER-INPUT-FILE-STREAM
#p"/net/rubix/usr/tech/dm/missing-paren.cl" pos 45 @ #xa99c12>
starting at position 20.
[condition type: END-OF-FILE]
Restart actions (select using :continue):
0: retry the compilation of /net/rubix/usr/tech/dm/missing-paren.cl
1: continue compiling /net/rubix/usr/tech/dm/missing-paren.cl but generate no
output file
[1] USER(4):
```lisp
Note the line:
```lisp
starting at position 20.
That indicates that the incomplete form starts at position 20 in the file. Opening the file in Emacs and entering the command C-u 20 C-f (assuming standard keybindings, that means move forward 20 characters) should bring you to the beginning of the incomplete form.
The variables compile-print and compile-verbose provide the defaults for the print and verbose arguments to compile-file. Those arguments control how much information compile-file will print out. These variables are a standard part of common lisp.
The default source-file type in Allegro CL is cl. The default compiled-file type in Allegro CL is fasl, which is a mnemonic for fast-loadable file. The default file types may be changed to suit your preferences.
The variable *source-file-types* is a list of pathname types of Common Lisp source files. The initial value of this variable is the list
"cl" "lisp" "lsp" nil) (
This means that if no file type is specified for the argument of compile-file (or the top-level command t
), files with types cl, lisp, lsp, and no type will be looked for. For example
compile-file "foo") (
will cause the compiler to first look for the file foo.cl, then for foo.lisp, the foo.lsp, and finally foo. Users may want to change the value of *source-file-types*. Some prefer not to allow files with no type to be looked at, since this prevents Lisp from trying to compile inappropriate files, or even a directory. Evaluating the form
setq sys:*source-file-types* '("cl" "lisp" "lsp")) (
will cause the compiler to look for files with file type cl, lisp and lsp. Then
compile-file "foo") (
will look for foo.cl, foo.lisp, and foo.lsp but not foo. The first file found will be the one compiled.
If you change *source-file-types*, you may also wish to change *load-search-list* and *require-search-list* so that the functions load and require will look for files with the desired file types as well. See Search lists and its subsections in loading.html for a description of these variables.
When a file is compiled, a new file containing the compiled Lisp code is created. This file will have the type specified by the variable *fasl-default-type*. The initial value of this variable is "fasl". You may change its value to any string you wish. If you change the value of this variable, you should also modify the load and require search lists so those functions will find the correct files. Throughout the documentation, files containing compiled Lisp code are called fasl files and are assumed to have a file type of fasl. You should understand that fasl really denotes the value of *fasl-default-type*.
Compiling Lisp code involves many compromises. Achieving very high speed generally involves sacrificing graceful recovery from errors and even the detection of errors. The latter may cause serious problems such as wrong answers or errors that result from the propagation of the original undiscovered error. On the other hand, interpreted code is very easy to debug but is too slow for practical applications.
Fortunately, most program needs can be satisfied on some middle ground. The software development cycle generally begins with interpreted code and ends with well-optimized code. Progressing through this cycle, higher speed is achieved without significant loss of confidence because of the increase in the correctness and robustness of the Lisp code as development proceeds. (It is important to note that optimizations do not affect the behavior of correct code with expected inputs. However, it is not always easy to prove that the inputs will always be what they are expected to be or that a complex program is indeed correct.) This section provides enough information so that a programmer may make intelligent decisions about performance compromises. Pointers for choosing speed and safety values further discusses the issue.
Among specific trade-offs in compiling code are the verification of the number and types of arguments passed to functions, providing adequate information for debugging, and including code to detect interrupts. The Allegro CL compiler has been designed to be highly parameterized. Optimizations performed by the compiler may be controlled to a significant degree by the user. The compiler of course supports the primitive Common Lisp safety, space, speed, debug, and compilation-speed optimization declarations. (Allegro CL accepts integer values between 0 and 3 inclusive, higher values representing greater importance to the corresponding aspect of code generation.) More significantly, Allegro CL provides a number of optimization switches that control specific aspects of code generation. These switches are in the form of variables that are bound to functions of the five optimization parameters safety, space, speed, debug, and compilation-speed.
The initial values of the optimization qualities are set when Allegro CL is built, controlled by the build-lisp-image arguments :opt-safety, :opt-space, :opt-speed, and :opt-debug (see Arguments to build-lisp-image 2: defaults not inherited from the running image in building-images.html).
The default values for safety, space, and speed in an image created by build-lisp-image and in prebuilt images supplied in the Allegro CL distribution is 1. The default value for debug is 2. The default value for compilation-speed is 1.
You can set the values of the four qualities in various ways. One way is globally, with proclaim as follows:
proclaim '(optimize (safety <em>n1</em>)
(space <em>n2</em>)
(speed <em>n3</em>)
(debug <em>n4</em>)
(compilation-speed <em>n5</em>))) (
where n1, n2, n3, n4, and n5 are integers from 0 to 3. The values may also be set with the top-level command t
. This command also displays the current values of the qualities (there is no portable way to access those values in Common Lisp).
The function explain-compiler-settings prints the value that will be returned by each compiler switch if called with specific settings of the optimization qualities. Called with no arguments, it describes the behavior with the current settings. It takes keyword arguments named by the optimization qualities (safety
, space
, speed
, debug
, and compilation-speed
). Values specified for those arguments causes information on the values of compiler switches using the specified values (and the current values, where unspecified) to be printed.
The variables discussed in the remainder of this section specify what the various settings of safety, space, speed, debug, and compilation-speed do. Their values are functions that return t
or nil
for the given settings of safety, space, speed, debug, and compilation-speed. You may change the definitions by binding (or setting) the variables to new functions.
You can also set (or bind) a variable to t
or nil
and this will be interpreted as a function that always returns t
or nil
(respectively) meaning the associated switch is always on (t
) or off (nil
). Any new function used should accept five arguments. The system calls the function with the (lexically) current values of safety, space, speed debug, and compilation-speed.
Following the table describing the switches, we give examples of erroneous code run interpreted (which produces the same error behavior as running with the switches at their safest setting) and run compiled with the switches at their least safe setting. What you will notice in the latter case is that erroneous code either results in a less informative error or perhaps in a wrong answer but no error. These examples are not intended to deter you from using compiler optimizations. Rather, we want to make you aware of the dangers of less safe code and show what error messages to expect when the switches are at their high speed settings. All switches are named by symbols in the compiler
package.
Switch | Description | Initial value true when: |
compile-format-strings-switch | When true, a format string (the second required argument to
format) is converted to a tree structure at compile time. If nil , the
conversion is done at run time, resulting in slower printing but smaller code. |
True when speed is greater than space. |
compile-macroexpansions-for-safety-switch | This switch is consulted at macroexpansion time, if the compiler is present in the image. Regular macros as well as compiler-macros are examined. Currently this switch only affects how defstruct accessors are expanded. If the switch returns true, an accessor that checks for the correct struct type is generated. | True when safety is 3. |
declared-fixnums-remain-fixnums-switch | If true, the compiler will generate code that
assumes the sums and differences of declared fixnums are
fixnums and that the negative of a single declared fixnum is a fixnum.
Warning: if this switch returns true during
compilation but the sum or difference of two declared fixnums or the
negative of a single fixnum is not a fixnum, the compiled code will
silently produce erroneous results. Taking the negative includes
applying abs to a
negative fixnum. Note that |
True only if speed is 3 and safety is 0. |
generate-interrupt-checks-switch | If true, code is added at the
beginning of the code vector
for a compiled function and at the end of a loop in a
compiled function that checks for an interrupt
(Control-C on Unix, Break/Pause on Windows). Note that an infinite loop that does not call functions will not be interruptable except by multiple Control-C's (Unix) and using the Franz icon on the system tray (Windows). See startup.html for more information on interrupting when all else fails. |
True unless speed is 3 and safety is 0. |
internal-optimize-switch | When true, the register-linking internal optimization is performed, resulting in faster but harder to debug code. |
True when compilation-speed is less than 2 and either speed is
greater than 2 or debug less than
3. Returns nil when compilation-speed is 2 or
3 regardless of other values.
|
optimize-fslot-value-switch | When true, calls to fslot-value-typed will be opencoded if possible. See < href="ftype.html">ftype.html | True when speed is greater than safety. |
optimize-large-functions-switch | When true, the compiler should continue to provide high optimization even if the function has become very large (and thus compilation speed is reduced). | True when compilation-speed is less that 2. |
save-local-names-switch | If true, the names of local variables will be saved in compiled code. This makes debugging easier. | True when debug is greater than 1. |
save-local-scopes-switch | If true, information about when local variables are alive is saved in compiled code, making debugging more easy. | True only when debug is 3. |
tail-call-self-merge-switch | If true, the compiler will perform self-tail-merging (for functions which call themselves). See Tail merging discussion below for more information on tail-merging. | True if speed is greater than 0. |
tail-call-non-self-merge-switch | If true, the compiler will perform non-self-tail-merging (for functions in the tail position different than the function being executed). See Tail merging discussion below for more information on tail-merging. | True if speed is greater than 1 and debug less than 2. (This is more restrictive than tail-call-self-merge-switch described just above in this table because all references to the caller function are off the stack in this case but at least one call remains on the stack in the self-merge case.) |
trust-declarations-switch |
If true, the compiler will trust declarations in code (perhaps other than
dynamic-extent declarations -- see next entry) and produce code (when
it can) that is optimized given the declarations. These declarations
typically specify the type of values of variables. If nil , declarations will be ignored except
(declare notinline) and (declare
special) which are always complied with. | True if speed is greater than safety. |
trust-dynamic-extent-declarations-switch | If true and if trust-declarations-switch is also true, the compiler will trust dynamic-extent declarations in code and produce code (when it can) that is optimized given the declarations. Having a switch which controls dynamic-extent declarations when declarations are trusted (via the general trust-declarations-switch) allows dynamic-extent declarations to be ignored while other declarations are trusted. This may be desirable when using multiprocessing, as described in Stack consing, avoiding consing using apply, and stack allocation. | True if speed is greater than or equal to safety. |
trust-table-case-argument-switch |
If true, the compiler will compile suitable
|
True if speed is 3 and safety is 0. |
verify-argument-count-switch | If true, the compiler will add code that checks that the correct number of arguments are passed to a function. | True if speed is less than 3 or safety is greater than 0. |
verify-car-cdr-switch | If true, code calling car
and cdr
will check that the argument is appropriate (i.e. a list).
The switch is only effective on
platforms which have :verify-car-cdr on
the *features* list.
Platforms lacking that feature ignore this switch since the
verification is done differently but always. |
True if speed is less than 3 or safety is greater than 1. |
verify-funcalls-switch | If false, the compiler will compile certain calls to funcall in such a way that the call immediately jumps to the funcall'ed function's start address. This speeds up funcall'ing at the cost of harder debugging (the stepper and the runtime analyzer call-counter will not see such calls). | True (i.e. slower funcall's) if speed is less than 3 or if safety is greater than 1 or if debug is greater than 0. |
verify-non-generic-switch | If true, code is generated that an object of undeclared type is of the correct type when it appears as the argument to specialized functions like svref and rplaca. Thus the argument to svref must be a simple vector and if this switch is true, code will check that (and give a meaningful error message). See True for speed less than 3 or safety greater than 1. | |
verify-symbol-value-is-bound-switch |
If true, code will be added to ensure that a symbol is bound before
the value of that symbol is used. The result (as shown in
Bound symbol example below) is that an error
with an appropriate message will be signaled in code compiled with
this switch true. In code compiled with this switch nil , behavior is undefined but it may be that no
error is signaled.
|
True if speed is less than 3 or safety is greater than 1. |
verify-stack-switch |
This switch only affects platforms with |
True if speed is less than 3 or safety is greater than 0. |
reorder-folded-constants-switch | when true the constant-folding mechanism in the compiler aggressively reorders expressions so that constants can be merged at various levels of computation. This is not a problem for integers, but floating point calculations are sensitive to the order of evaluation. See the variable description for examples of different result when code is run interpreted and when compiler with this switch true. | True when speed is 3 and safety is 0 or 1. |
save-source-level-debug-info-switch | True if debug is greater than 2. | |
save-source-level-debug-info-switch |
This switch does not affect code speed. Instead, it tells the compiler
to save information in fasl files useful for source-level debugging
(see
The source stepper
in building-images.html) and for
coverage analysis (see
with-coverage).
This saved information does
not affect the actual compiled code (which will be the same whether
this switch is true or nil ) though it does
make fasl files bigger than they otherwise would be.
|
True if debug is greater than 2. |
generate-accurate-x86-float-code-switch |
This switch affects 32-bit x86 processors only. When true, flags are
set that ensure floating-point operations are done in a consistent
mode, so the same calculations have the same result from run to
run. When nil , those flags are not set so the
computation is faster but mat have different results in the lowest
bits from run to run.
|
False (faster, possibly slightly different results from run to run on 32-bit x86) only when speed is 3 and safety is 0. |
align-branch-targets-switch |
This switch affects x86 64-bit platforms only. It is ignored on all other platforms. The
value many be an integer as well and t or
nil . When true, the compiler ensures certain
code branches are optimally aligned. An integer value controls which
branches are affected. See the
generate-accurate-x86-float-code-switch
page for details.
|
The initial value is a function which returns true if speed is 3 and compilation-speed is 0 or 1. |
verify-format-argument-count-switch |
This switch tells the compiler whether to check
format
statements during compilation to
determine whether there are a sufficient number of arguments provided.
It differs from other switches in returning 6 possible values
(0, nil , 1, 2, t , 3,
and 4). 0 and nil mean do not checking. 1
means collect information only. 2 and t mean
collect information and also signal a style warning if a form with two
few arguments is encountered. 3 and 4 mean do all of 2 and also signal
a style warning for forms with too many arguments (3 and 4) and one
for forms that are too complex to evaluate (4 only). See
the verify-format-argument-count-switch page
for details.
|
The initial value is a function which returns 1 for safety 0 and compilation-speed 0 or 1; returns 2 for safety 2 or 3 and compilation-speed 0 or 1; and returns 0 for compilation speed 2 or 3. |
Some switches are no longer used but their associated variables still exist though setting them has no effect. The switches are comp:generate-inline-call-tests-switch, comp:peephole-optimize-switch, and comp:save-arglist-switch.
The following two code examples illustrate the effect of the comp:declared-fixnums-remain-fixnums-switch switch. In the first, we define a function that simply adds its two arguments and contains declarations that both the arguments are fixnums. When compiled with this switch returning nil
, the function correctly returns the bignum resulting from the arguments in the example. When compiled with this switch returning t
, the function returns a wrong answer. The second example shows that this can even happen when taking the negative of a single fixnum or (equivalently) taking the absolute value of a negative fixnum. most-positive-fixnum and most-negative-fixnum have different absolute values, and so taking the negative of the bigger (in absolute value) produces a bignum.
;; This example is run in a 32-bit Lisp. The behavior would be the
;; same (with different values) in a 64-bit Lisp.
;;
28): (defun frf (n m) (declare (fixnum n) (fixnum m))
USER(+ n m))
(
FRF29): (setq bn (- most-positive-fixnum 20))
USER(536870891 ;; 20 less than most-positive-fixnum
30): (frf bn 50000)
USER(536920891 ;; This value is a bignum:
31): (bignump *)
USER(
T32): (proclaim '(optimize (safety 0) (speed 3)))
USER(
T33): (compile 'frf)
USER(
FRF
NIL
NIL33): (frf bn 50000)
USER(268385477 ;; wrong answer!
-34):
USER(
;; In this example, here run in a 64-bit Lisp,
;; we see (- most-negative-fixnum) is a bignum.
;; That is also true in 32-bit Lisps.
2): (defun my-abs (value)
cg-user(declare (optimize (speed 3) (safety 0))
(fixnum value))
(abs value))
(
my-abs3): (compile *)
cg-user(
my-abs5): (my-abs most-negative-fixnum)
cg-user(1152921504606846976 ;; wrong answer, should be positive
-6): (bignump (- most-negative-fixnum))
cg-user(t
7): cg-user(
Very serious errors can occur when this switch returns nil
and the wrong number of arguments are passed. These errors are not necessarily repeatable. We give a simple example where you get a less than useful error message, but you should be aware that much more serious (possibly fatal) errors can result from code where the number of arguments are not checked. About 3 instructions (the exact number is platform dependent and ranges from 1 to 4) are added to a function call when this switch returns t
.
1): (defun foo (a b c) (+ a c))
USER(
FOO2): (foo 1 2)
USER(2 args, wanted at least 3.
Error: FOO got condition type: PROGRAM-ERROR]
[1] USER(3): :pop
[4): (proclaim '(optimize (speed 3) (safety 0)))
USER(
T5): (compile 'foo)
USER(; While compiling FOO:
Warning: variable B is never used
FOO
T
T6): (foo 1)
USER(type number 4 @ #x8666c>
#<unknown object of 7):
USER(
;; Note you might also get an error, although not one that
;; mentions the number of arguments.
Note that no error is signaled. Note further that it is possible for a fatal garbage collection error to result from passing the wrong number of arguments.
The following examples show what happens when the switch returns t
and when it returns nil
.
39): (defun foo (vec n) (svref vec n))
USER(
FOO40): (setq v (list 'a 'b 'c 'd 'e))
USER(
(A B C D E)41): (foo v 3)
USER(vector object passed to svref: (A B C D E)
Error: Illegal 1] USER(42): :pop
[43): (proclaim '(optimize (speed 3) (safety 0)))
USER(
T44): (compile 'foo)
USER(
FOO
NIL
NIL45): (foo v 3)
USER(signal number 10 (Bus error)
Error: Received condition type: SIMPLE-ERROR]
[
;; Or it might seem to work but return a bogus value.
1] USER(46): [
The following example shows what happens if this switch returns nil
. In that case, the symbol-value location is simply read. If the symbol is in fact unbound, an apparently valid but in fact bogus value may be obtained. In the example, that bogus value is passed to the function + and an error is signaled because it is not a valid argument to +. However, it may be that no error will be signaled and computation will continue, resulting in later confusing or uninterpretable errors or in invalid results.
60): (defun foo (n) (+ n bar))
USER(
FOO61): (foo 1)
USER(the value of the unbound variable `BAR'.
Error: Attempt to take condition type: UNBOUND-VARIABLE]
[1] USER(62): :pop
[63): (proclaim '(optimize (speed 3) (safety 0)))
USER(
T64): (compile 'foo)
USER(; While compiling FOO:
special
Warning: Symbol BAR declared
FOO
T
T65): (foo 1)
USER(+
Error: (NIL) is an illegal argument to condition type: TYPE-ERROR]
[
;; You may see a different error.
1] USER(66): [
Consider the following function definition:
defun foo (lis)
(pprint lis)
(list-length lis)) (
When you call foo with a list as an argument, the list is pretty printed and its length is returned. But note that by the time list-length is called, no more information about foo is needed but, in the interpreter at least, the call to foo remains on the stack. The compiler can tail merge foo in such a way that the call to list-length is changed to a jump. In that case, when list-length is reached the stack looks as if list-length was called directly and foo was never called at all. The side effects of calling foo, in this case, pretty printing the list passed as an argument, have all occurred by the time list-length is called.
Unwrapping the stack in this fashion is a benefit because it saves stack space and can (under the correct circumstances) avoid creating some stack frames all together. However, it can make debugging harder (because the stack backtrace printed by t
will not reflect the actual sequence of function calls, but see the discussion of ghost frames in debugging.html) and it can skew profiling data (because, looking at our example, a sample taken after list-length is called will not charge time or space to foo because foo is off the stack).
The two switches comp:tail-call-self-merge-switch and comp:tail-call-non-self-merge-switch control tail merging.
comp:tail-call-self-merge-switch will have an effect when its value is true at the beginning of the function being self-called (and no other time). comp:tail-call-non-self-merge-switch has effect only when true at the point of the call.
Section 12.1.1.1 in the CL spec allows for processing of numeric forms in any order. This allows compilers to rewrite terms for efficiency. However, if calculations differ between compiled and interpreted code, results may also differ, especially when manipulating float values. Note that this difference almost never happens for integers, although there may indeed be cases where the comp:declared-fixnums-remain-fixnums-switch is true and the order of evaluation may determine whether or not the intermediate calculation overflows or not.
In the case of floats. the problem occurs when combinations of larger with smaller floats change the rounding based on the order of evaluation. An example below shows several cases where the order of evaluation changes the outcome. Both calculations are correct, but perhaps one is preferred. One interesting case is case number 7: the initial form is (+ (+ c 360) d -360)
. An aggeressive constant-folding algorithm can reduce the expression by combining the 360 and the -360, leaving the results equivalent to (+ c d)
. One could argue that this is the correct result and if c
and d
were both integers, the results would always be consistent with and equivalent to (+ c d)
. However, given choices of c
and d
to be floating point values, especially values that are orders of magnitude different than the 360 that is being added and subtracted, the results can cause the first addition of 360 to be swamped and the results different. For example, substituting 44.8315e20 for c
and -44.8315e20 for d
, we would expect c and d to cancel each other and the result to be 0.0. Instead, we have (+ (+ 44.8315e20 360) -44.8315e20 -360) => -360.0
which is wrong mathematically, but correct for the ordering of the floating point calculations. Compiling this expression with comp:reorder-folded-constants-switch
returning nil
(e.g. with speed and safety both at 1) yields this -360.0 result as well as running the expression interpreted, and compiling with comp:reorder-folded-constants-switch
true (e.g. speed = 3, safety = 1) will return the 0.0 result.
Note that Allegro CL's compiler and interpreter both normally group multiple terms in a +
or -
operation from left to right.
Here are a number of test cases that demonstrate the possible inconsistencies between compilation with comp:reorder-folded-constants-switch
true and other compilations or interpreted code. The code is shown first, then the compilation at speed=1
, (i.e. comp:reorder-folded-constants-switch => nil
), followed by compilation at speed=3
(i.e. comp:reorder-folded-constants-switch => true
), followed finally by uncompiled (interpreted) execution. The anomalous situations are #2, #4, and #7.
1): (shell "cat mini-test.lisp")
cl-user(
defmacro do-macroexpand (form &environment env)
(let* ((opt (sys::declaration-information 'optimize env))
(speed (cadr (assoc 'speed opt)))
(safety (cadr (assoc 'safety opt))))
(let ((new-env (sys::make-augmentable-environment-boa :compiler)))
`(setq new-env
(
(sys:augment-environment new-envoptimize (speed ,speed)
:declare '((safety ,safety)))))
(
(excl::compiler-macroexpand ,form new-env))))
defun mini-repro (c d)
(let* ((a 44.8315)
(320.0)
(b -320.0)
(bm + b 360)))
(pre-add (format t "
( A = ~a
B = ~a
BM = ~a
PRE-ADD = ~a
C = ~a
D = ~a
1 expr: (- A PRE-ADD) ~32t => ~40t ~a
value: ~a
2 expr: (- A (+ B 360)) ~32t => ~40t ~a
value: ~a
3 expr: (- A B 360) ~32t => ~40t ~a
value: ~a
4 expr: (+ A (+ BM -360)) ~32t => ~40t ~a
value: ~a
5 expr: (+ A Bm -360) ~32t => ~40t ~a
value: ~a
6 expr: (- (- A B) 360) ~32t => ~40t ~a
value: ~a
7 expr: (+ (+ c 360) d -360) ~32t => ~40t ~a
value: ~a
"
a b bm pre-add c d- a pre-add))
(do-macroexpand '(- a pre-add)
(- a (+ b 360)))
(do-macroexpand '(- a (+ b 360))
(- a b 360))
(do-macroexpand '(- a b 360)
(+ a (+ bm -360)))
(do-macroexpand '(+ a (+ bm -360))
(+ a bm -360))
(do-macroexpand '(+ a bm -360)
(- (- a b) 360))
(do-macroexpand '(- (- a b) 360)
(+ (+ c 360) d -360))
(do-macroexpand '(+ (+ c 360) d -360)
(
)))0
2): :cf mini-test
cl-user(;;; Compiling file mini-test.lisp
;;; Writing fasl file /tmp/cfta304440938521
;;; Moving fasl file /tmp/cfta304440938521 to mini-test.fasl
;;; Fasl write complete
3): :ld mini-test
cl-user(; Fast loading /net/fire/acl/duane/devel/src/mini-test.fasl
4): (mini-repro 44.8315e20 -44.8315e20)
cl-user(
= 44.8315
A = -320.0
B = 320.0
BM = 40.0
PRE-ADD = 4.48315e+21
C = -4.48315e+21
D
1 expr: (- A PRE-ADD) => (-_2op a pre-add)
4.831501
value:
2 expr: (- A (+ B 360)) => (-_2op a (+_2op b 360))
4.831501
value:
3 expr: (- A B 360) => (-_2op (-_2op a b) 360)
4.8315125
value:
4 expr: (+ A (+ BM -360)) => (+_2op a (-_2op bm 360))
4.831501
value:
5 expr: (+ A Bm -360) => (-_2op (+_2op a bm) 360)
4.8315125
value:
6 expr: (- (- A B) 360) => (-_2op (-_2op a b) 360)
4.8315125
value:
7 expr: (+ (+ c 360) d -360) => (-_2op
360) d)
(+_2op (+_2op c 360)
360.0
value: -nil
5): (declaim (optimize speed))
cl-user(t
6): :cf mini-test
cl-user(;;; Compiling file mini-test.lisp
;;; Writing fasl file /tmp/cfta304440938522
;;; Moving fasl file /tmp/cfta304440938522 to mini-test.fasl
;;; Fasl write complete
7): :ld mini-test
cl-user(; Fast loading /net/fire/acl/duane/devel/src/mini-test.fasl
8): (mini-repro 44.8315e20 -44.8315e20)
cl-user(
= 44.8315
A = -320.0
B = 320.0
BM = 40.0
PRE-ADD = 4.48315e+21
C = -4.48315e+21
D
1 expr: (- A PRE-ADD) => (-_2op a pre-add)
4.831501
value:
2 expr: (- A (+ B 360)) => (-_2op (-_2op a b) 360)
4.8315125
value:
3 expr: (- A B 360) => (-_2op (-_2op a b) 360)
4.8315125
value:
4 expr: (+ A (+ BM -360)) => (-_2op (+_2op a bm) 360)
4.8315125
value:
5 expr: (+ A Bm -360) => (-_2op (+_2op a bm) 360)
4.8315125
value:
6 expr: (- (- A B) 360) => (-_2op (-_2op a b) 360)
4.8315125
value:
7 expr: (+ (+ c 360) d -360) => (+_2op c d)
0.0
value: nil
9): :ld mini-test.lisp
cl-user(; Loading /net/fire/acl/duane/devel/src/mini-test.lisp
10): (mini-repro 44.8315e20 -44.8315e20)
cl-user(
= 44.8315
A = -320.0
B = 320.0
BM = 40.0
PRE-ADD = 4.48315e+21
C = -4.48315e+21
D
1 expr: (- A PRE-ADD) => (-_2op a pre-add)
4.831501
value:
2 expr: (- A (+ B 360)) => (-_2op (-_2op a b) 360)
4.831501
value:
3 expr: (- A B 360) => (-_2op (-_2op a b) 360)
4.8315125
value:
4 expr: (+ A (+ BM -360)) => (-_2op (+_2op a bm) 360)
4.831501
value:
5 expr: (+ A Bm -360) => (-_2op (+_2op a bm) 360)
4.8315125
value:
6 expr: (- (- A B) 360) => (-_2op (-_2op a b) 360)
4.8315125
value:
7 expr: (+ (+ c 360) d -360) => (+_2op c d)
360.0
value: -nil
11): cl-user(
The user may change the code which determines how any of these variables behaves by redefining the function which is the value of the variable. We do not recommend changing the global value of the switch variables as that might affect background compilations (the compiler is called in the background for various reasons). Instead, you can wrap the code in file in a compiler-let form or, for calling compile at the top-level, wrap the call in a lat or let* form.
The value of a switch variable should be either a function, as we describe below, or a suitable value, that is t
or nil
for all switches except comp:verify-format-argument-count-switch and t
, nil
, 0, 1, 2, 3, or 4 for that switch.
A suitable function must take as arguments safety, space, speed, debug, and compilation-speed. It is important that the function be compiled since it may be called many times during a compilation and an interpreted function will slow down the compiler. Here is the general form which modifies a switch variable during file compilation. var identifies the switch variable you wish to change.
compiler-let
(
((var compile nil '(lambda (safety space speed debug compilation-speed)
(-1
form
...
form-n))))and forms to compile]
[definitions, etc. )
where form-n
returns nil
or t
as a function of safety, space, speed, debug, and compilation-speed.
Note that we wrapped the function definition with compile. Note too that such a form will not affect the compilation of the file in which it is itself compiled (since the variable will not be redefined in time to affect the compile-file).
For example, if the following code is wrapped around form to compile in a file, the compiler will not save local scopes if speed is greater than 1 or if debug is less than 2 or if space is greater than
compiler-let
(
((compiler:save-local-scopes-switchcompile nil '(lambda (safety space speed debug)
(declare (ignore debug))
(cond ((> speed 1) nil)
(< debug 2) nil)
((> space 1) nil)
((t t))))))
(;; forms to compile
)
The initial values of safety and speed are both set during image build (with build-lisp-image) using the opt-speed, opt-safety, opt-space, opt-debug, and opt-compilation-speed arguments. See building-images.html.
If you declare a variable to be of a particular type, as in
defun foo (x)
(declare (optimize (speed 3)) (fixnum x))
(1+ x)) (
you are making a promise about your actions: you are promising not to call foo with a non-fixnum argument. If you do call foo with a non-fixnum argument (we assume the trust-declarations-switch was true, its default behavior for speed 3), then the result might be an incorrect value.
But there is typically no runtime check that values of the correct type are being passed to foo. If you call foo with a float or a bignum, the possibly incorrect result is produced silently.
You can have runtime checks added for type declarations and let and lambda bindings and setq's by compiling while the comp:verify-type-declarations-switch switch is true. See the description for an example. Note that with the default settings, comp:verify-type-declarations-switch and the various relevant trust-* switches are all true only for speed 3/safety 2. But note that is an odd declaration combination: when all are true, extra code will be added to verify type declarations, and if the data verifies, the code that operates on the declared values will be optimized to use those declarations. Depending on the expense of the particular type declaration, the generated code might or might not be faster than simply allowing generic builtin functions to handle the case. Consider an overly-helpful declaration like
declare (type x (integer 100 127)))
(- x 1)) ... (foo (
This code will be a lot more expensive than a simple fixnum declaration, since the compiler promises to check both limits.
The value of a compiler switch can be t
or nil
as well as a function. t
is interpreted as a function that always return true and so causes the switch to always be on. nil
is interpreted as a function that always returns false and so causes the switch to always be off. These settings are particularly useful when binding the variables during a specific compilation.
Delcaration, excl package
This declaration has the same meaning and syntax as the Common Lisp type declaration, but it will be trusted even when the compiler switch trust-declarations-switch is nil
, which means most declarations are not trusted. This allows you to specify certain declarations as always trusted even if you are running at high safety.
This declaration should be used sparingly if at all. It is designed for cases where code will not compile unless the type is known (which occurs, for example, during bootstrapping but rarely in normal cases).
What values should you choose for speed and safety? It is tempting to set speed to 3 and safety to 0 so that compiled code will run as fast as possible, and devil take the hindmost. Our experience is that people who do this say that Allegro CL is fast but lacks robustness, while people who use the more conservative default settings of 1 and 1 feel that Allegro CL is very robust but occasionally seems a bit sluggish. Which you prefer is for you to decide. The following points should be considered.
If you are going to set speed globally to 3, we strongly discourage you from also globally setting safety to 0 rather than 1. There are only 5 differences between the two safety settings: at safety 0, (1) argument count checking is disabled (see verify-argument-count-switch); (2) interrupt checking is disabled (see generate-interrupt-checks-switch); (3) sums and differences of fixnums are assumed to be fixnums (see declared-fixnums-remain-fixnums-switch); (4) case is compiled in table-driven fashioon (see trust-table-case-argument-switch); and (5) on 32-bit x86 only, floating-point calculations are done without the flags set which guarantee that the results are the same from run to run (generate-accurate-x86-float-code-switch). Thus you may not be able cleanly to break out of an infinite loop; passing the wrong number of arguments may cause unrepeatable, possibly fatal errors; adding fixnums whose sum is a bignum will silently produce the wrong answer; and (again 32-bit x86 only) the lower bits of floating-point calculation may vary from run to run. We recommend that those switches should only be set in an unsafe manner when compiling functions where it is very unlikely that the code is incorrect. You can set safety to 0 when compiling such functions by use of a declaration within the defun form.
If strange errors occur whose cause cannot be discovered, recompile the code at a more safe setting of speed and safety and run it again. An error will usually occur (not necessarily in the same place), the error message should be more informative, and the debugger should have more information. If no error occurs, it may be that you have an incorrect declaration (e.g. declaring a value to be a single-float when it is in fact something else, like nil
). Look particularly at initial values, making sure the initial value is of the declared type.
You might consider resetting the switch variables to t
or nil
(as appropriate) as that can ensure the switch has a known, unambiguous value unaffected by declarations within a function definition or anything else.
The compiler in Allegro CL has the ability to compile floating-point operations in-line. In order to take full advantage of this feature, the compiler must know what can be done in-line. For it to know this, the user must, through declarations, inform the compiler of the type of arguments to operations. The compiler will attempt to propagate this type information to other operations, but this is not as easy as it might seem on first glance. Because mathematical operations in Common Lisp are generic, that is they accept arguments of any type - fixed, real, complex - and produce results of the appropriate type, the compiler cannot assume results in cases when the user may think the situation is clear. For example, the user may know that only positive numbers are being passed to sqrt, but unless the compiler knows it too, it will not assume the result is real and not complex. The compiler can tell the user what it does know and what it is doing. See Help with declarations: the :explain declaration below. With this information, the user can add declarations as needed to speed up the compiled code. The process should be viewed as interactive, with the user tuning code in response to what the compiler says it knows.
The compiler will expand in-line (open-code) a floating-point numeric function only if the function is one which it knows how to open-code, and the types of the function's arguments and result are known at compile time to be those for which the open-coder is appropriate. Finally, at the point of compilation the compiler switch function which is the value of trust-declarations-switch must return true. The default version of this function returns true if speed is greater than safety but users can change the values for which this switch returns true. explain-compiler-settings can be called to see what the value of that switch (and all others) will be given the current values of safety, space, speed, and debug.
The floating-point functions below are subject to opencoding. In each case, the function result must be declared or derivable to be either single-float or double-float. (Note that in this implementation, the type short-float is equivalent to single-float and long-float is equivalent to double-float.) The arguments to the arithmetic and trigonometric functions must be specifically one or the other of the two floating types, or signed integer constants representable in 32 bits (on 32-bit lisps) or 64 bits (on 64-bit lisps), or else computed integer values of fixnum range. If these conditions are not met, the function will not be open-coded and instead a normal call to the generic version of the function will be compiled. Note that it is not sufficient to declare a value to be a float, it must be declared as either a single-float or a double-float.
We are often asked exactly which functions will opencode in Allegro CL. Unfortunately, it is not easy to provide an answer. Firstly, because the compiler is different on each different architecture, the answer is different in different implementations. Secondly, the same function may opencode in one case but not in another on the same machine because of the way it is affected by surrounding code. The :explain
and :explain :inlining
declarations described in the section Help with declarations: the :explain declaration assists with determining what did and did not opencode. However, the following functions are candidates to be open-coded in most platforms or in the platforms noted.
(simple-array single-float (*))
(simple-array double-float (*))
Note that the list is not exhaustive in either direction. Not all the listed functions and operations opencode on all machines and functions and operations not listed do opencode on some machines. As said above, the :explain
declaration described in the section Help with declarations: the :explain declaration assists with determining what did and did not opencode.
The compiler must be supplied with type declarations if it is to open-code certain common operations without type-checking. The compiler includes a type propagator that tries to derive the results of a function call (or special operator) from what it knows about its arguments (or subforms). The type propagator greatly reduces the amount of type declaration necessary to achieve opencoding. However, the programmer may need to examine the inferences made by the propagator to determine what additional declarations would increase code speed. Although supplying type declarations to the compiler is very simple, it is a task surprisingly prone to error. The usual error is that well-intentioned declarations are insufficient to tell the compiler everything it needs to know.
For example, the programmer might neglect to declare the seemingly obvious fact that the result of a certain sqrt of a double-float is also a double-float. However, sqrt in Common Lisp returns a complex if its argument is negative, so the compiler cannot assume a real result. The only impact of insufficient declarations is that some open-coders will not be invoked. However, it can be awkward for the user to determine whether or not any particular call was open-coded, and if not, why. trace and disassemble will provide the information, but these are clumsy tools for the purpose.
In order to provide better information about what the compiler is doing, a new declaration, :explain, has been added to Allegro CL.
Arguments: [:calls | (:calls t) | (:calls nil)] [:types | (:types t) | (:types nil)] [:boxing | (:boxing t) | (:boxing nil)] [:variables | (:variables t) | (:variables nil)] [:tailmerging | (:tailmerging t) | (:tailmerging nil)] [:inlining | (:inlining t) | (:inlining nil)]
This declaration instructs the compiler to report or not to report information about argument types and non-in-line calls, boxed floats and variables stored in registers, tail-merging, and why inlining might not have succeeded. Reporting is enabled when a quality appears alone or in a list with t
. It is disabled when a quality appears in a list with nil
. Initially, no :explain
qualities are enabled.
This declaration may be placed anywhere that a normal declaration may be, and can be proclaimed (with proclaim or declaim). The compiler will report information within the scope of the declaration. Note that results may differ from one platform to another. Again, the general form of the declaration is (these forms must be placed in a location where declares are allowed, of course):
declare (:explain :quality ...)) ;; explanation for :quality
(;; will be output
;; or
declare (:explain (:quality t) ...) ;; explanation for :quality
(;; will be output
;; or
declare (:explain (:quality nil) ...) ;; explanation for :quality
(;; will not be output
;; Thus
declare (:explain :types (:boxing nil) (:variables t) :tailmerging))
(;; will enable explaining for :types, :variables, and :tailmerging
;; and disable it for :boxing. Explaining for :calls is not affected
;; (off if it was already off, on if it was already on).
The arguments control various kinds of reporting the compiler will make during compilation. The arguments may appear in either order, and either may be omitted. By default, no information of either type is printed. The declaration causes the compiler to print information during compilation, and obeys normal declaration scoping rules. It has no effect on code generation. Here are the various :explain qualities:
:types
: when this quality is in effect, the compiler will report for each function call it compiles (in-line or not) the types of each argument along with the result type. Further information and examples are in Calls and types explanation in compiler-explanations.html.
:calls
: when this quality is in effect, the compiler will report when it generates code for any non-in-line function call. :types
and :calls
operate independently. Further information and examples are in Calls and types explanation in compiler-explanations.html. :calls
should not be used with :inlining
since calls information is provided by the :inlining explanation.
:boxing
: when this quality is in effect, the compiler will tell you when code is generated to box a number if it is possible for the code not to be boxed. A number is boxed when it is converted from its machine representation to the Lisp representation. For floats, the machine representation is one (for singles) or two (for doubles) words. Lisp adds an extra word, which contains a pointer and a type code. For fixnums, boxing simply involves a left shift of two bits. For bignums which are in the range of machine integers, boxing again adds an additional word. Further information and examples are in Boxing explanation in compiler-explanations.html. :boxing
should not be used with :inlining
since boxing information is provided by the :inlining explanation.
:variables
: when this quality is in effect, the compiler will report whether local variables (in function definitions) are being stored in registers or in memory. Since storing variables in registers results in faster code, the information printed when this variable is in effect may help in recasting function definitions to allow for more locals to be stored in registers. Further information and examples are in Variables explanation in compiler-explanations.html.
:tailmerging
: when this quality is in effect, the compiler will report why a tail-merge is or is not being done for a function in tail-position. Further information and examples are in Tail-merging explanation in compiler-explanations.html.
:inlining
: when this quality is in effect, information about why a function you might expect to be inlined was not in fact inlined by the compiler. Further information and examples are in Inlining explanation in compiler-explanations.html. (Essentially all the information printed by the :explain :calls
and :explain :boxing
declarations is printed by the :explain :inlining
declaration, so you should use either :inlining or :boxing and :calls, but not both.)
The reports are printed during the code generation phase of compilation. Code generation occurs fairly late during compilation, after macroexpansion and other code transformations have taken place. The function calls explained by the compiler will therefore deviate in certain ways from the original user code. For example, a two-operand + operation is transformed into a call to the more efficient excl::+_2op function. In general, the user should easily be able to relate the code generator's output to the original code.
The code generator works by doing a depth-first walk of the transformed code tree it receives from early compiler phases. In this tree walk, the :types
printout happens during the descent into a branch of the tree. The :calls
printout happens as the instructions are actually generated, during the ascending return from the branch.
The reason :explain
is implemented as a declaration is so the user can gain fairly fine-grained control of its scope. This can be important when tuning large functions. The tool does require editing the source code to be compiled, but presumably the user is in the process of editing the code to add declarations anyway. These options may also be enabled and disabled globally by use of proclaim or declaim, although they may produce a lot of output.
The lines of explanation output are labeled with abbreviations which identify what type of explanation is being produced and what the compiler is doing. These abbreviations are listed in the compiler-explanations.html.
There are other declarations which affect (or in some cases do not affect) the operation of the compiler, as we describe under the next several headings.
The inline
declaration is ignored by the compiler, but see define-compiler-macro, which will define a macro for the compiler to use for an operator which is not declared notinline
.
At appropriate settings of speed and safety, the compiler will inline whatever it can. Only predefined system functions can be inlined. User defined functions are never compiled inline. (The compiler will observe the notinline
declaration, however, so you can suppress inlining of specific functions if you want.)
We have been asked why the inline declaration is ignored. It is not that we believe that inlining user functions does not provide any advantages, it is that we believe that there are other improvements that will provide more advantages. Because we have now implemented compiler environments (see environments.html), we are in a better position to implement inlining of user functions in a later release.
Note that inlining is not an unmixed blessing. It increases the amount of space used by a function (since both the function definition and the block to stick in when the function is inlined have to be created and stored) and it makes debugging harder (by making the compiled code diverge from the source code). It is also prone to subtle errors (on the programmers side) and bugs (on our side).
Defstruct accessors will be compiled inline under two conditions:
The structure is not of type list. (Accessors for structures of type list are never inlined).
The values of speed and safety are such that the compiler switch verify-non-generic-switch returns nil
. Using the default value, that switch will return nil
when speed is 3 and safety is 0 or 1.
Stack consing
On some machines, stack consing of &rest arguments, closures, and some lists and vectors is supported. To be stored on the stack, an object must be declared dynamic-extent, the compiler must trust that declaration (see trust-dynamic-extent-declarations-switch), and the object must be suitable for stack consing. Stack consing means that objects are stored on the stack and not written to memory. This can provide a significant space saving if the objects are truly temporary, which they often are. Here is an example of such a declaration for an &rest argument. The function foo checks whether the first argument is identical to any later argument.
defun foo (a &rest b)
(declare (dynamic-extent b))
(dolist (val b)
(eq a val) then (return t)))) (if* (
Please note that care should exercised when using stack consing and multiprocessing, since a switch between one thread/process and another causes (part of) the stack to be saved, so the stack-consed objects and values are not available to the thread/process being switched into. For a binding or object that has dynamic extent, that extent is only valid when Lisp is executing on the thread/process that creates the binding or stack-conses the object. When (on Windows, which uses OS-threads) a thread switch is executed, the extents of all dynamic-extent data and bindings are temporarily exited, to be re-established when another thread switch returns to the original thread. Since only on Windows separate Lisp lightweight processes run on separate threads, it is important on Unix that dynamic-extent data (which may be stack-consed) not be referenced while executing on a different process. On Unix, a process-wait wait-function argument should never be declared dynamic-extent, since it was funcall'ed from other stack-groups. However, on Windows, wait functions are run only in their own threads, so stack consing in wait functions should work.
Dynamic-extent declarations are only observed at values of safety, space, speed, and debug for which trust-dynamic-extent-declarations-switch returns true and trust-declarations-switch is also true.
If a call to make-list has a constant size, declarations are trusted, the list is made the value of a variable, and the variable is declared as dynamic-extent, then it will be stack-allocated and initialized. The initial-value keyword can be used to specify the value. An attempt to make a list of a variable size with make-list will result in heap consing.
Dynamic-extent argument properties are automatically declared on all defun forms. This gives the compiler the ability to make assumptions about the dynamic extent use of arguments passed into these functions, and to generate more efficient code. The compiler generally tracks these properties for functions that it knows about, e.g. mapcar. Dynamic-extent declarations extend this to user-defined functions and to redefinitions. Warnings are also provided for redeclared definitions and for definitions that occur after the function's first usage. To allow declaration before the first use, a new macro called defun-proto is provided.
Avoiding consing with apply using a &rest
In certain cases apply is now compiled more efficiently, to remove consing. This is the so-called applyn optimization. Consider the following code:
defun foo (x &rest y)
(apply some-fun 1 x 2 y)) (
The &rest
argument is used for nothing more than to supply a variable length argument list to apply. This case is now compiled in such a way that
The last argument to apply is not actually used, but an index to the n'th argument to foo is compiled instead.
The &rest
argument is considered dead and as if declared ignored.
All aspects of the function are preserved (e.g. possible argument checking for a minimum but not a maximum number of arguments, etc.)
In this optimized case, the code works exactly as it did when the &rest
argument was consed, but without the consing. Circumstances that will cause this optimization to not be used are if the &rest argument:
Optimization hint: If you have a function like
defun wrap-it (flag &rest x &key &allow-other-keys)
(when flag
(setq x (list* :new-key 10 x)))
(apply 'wrapped flag x)) (
then the optimization will not take effect. If non-consed operation is desired, then the following modification will allow the optimization:
defun wrap-it (flag &rest x &key &allow-other-keys)
(if flag
(apply 'wrapped flag :new-key 10 x)
(apply 'wrapped flag x))) (
Stack allocation
The type of arrays (and thus vectors) supported in Allegro CL are discussed in Data types and array types in implementation.html. Of those, the following types of vectors can now be stack-allocated (N/A means not stack-allocable on the indicated platform, in some cases because that type of specialized array is not supported and in other cases because that type of specialized simply cannot be allocated on the stack; * means the initial-element value is ignored when the array is stack allocated):
element type | initializable in 32-bit Lisps? (see below) | initializable in 64-bit Lisps? (see below) |
t |
yes | yes |
(unsigned-byte 64) |
N/A | yes |
(signed-byte 64) |
N/A | yes |
(unsigned-byte 32) |
yes | no |
(signed-byte 32) |
yes | no |
(unsigned-byte 16) |
no | no |
(signed-byte 16) |
no | no |
(unsigned-byte 8) |
no | no |
(signed-byte 8) |
no | no |
character |
no | no |
(unsigned-byte 4) |
no | no |
bit |
no | no |
excl:foreign |
yes | yes |
single-float |
* | no |
double-float |
N/A | * |
fixnum |
yes | yes |
To get a stack-allocated vector, the following coding practice should be used:
declare (optimize <values that make trust-declarations-switch true>))
(;; with initial variable value, (speed 3) (safety 1) will work
... let ((x (make-array n <<em>options</em>>)))
(declare (dynamic-extent x)) ...) (
where n
is a constant integer, and options are limited to :element-type
(and :initial-element
, if the array is initializable according to the table above). All other forms might cause heap allocation of the array. Specifying an initial-element when the array is not, according to the table above, initializable will cause the array to be heap allocated except in the cases denoted by a * (single-float arrays on 32-bit and double-float arrays on 64-bit): in those cases the array will be stack-allocated but the value specified by the initial-element argument will be ignored.
Other functions that allow stack-consing if conditions are right are cons, list, list*, make-list, with-stack-fobject, with-stack-fobjects, with-stack-list, and with-stack-list*.
A typep-transformer allows the compiler to transform a form like
typep x 'foo) (
into a form like
funcall some-function x) (
For example, the Allegro CL compiler will already transform typep forms where the type is defined as:
deftype foo () `(satisfies foop)) (
into
funcall 'foop x) (
The ability to add typep-transformers described here allows types defined with a more complicated syntax than (satisfies 'some-function) to be similarly transformed. The user must supply the appropriate predicate function and call the following function to associate the type with the predicate.
The function add-typep-transformer takes a type and a predicate function which will allow the compiler to transform forms like
typep x 'type) (
into the form:
funcall <predicate> x) (
add-typep-transformer has a keyword argument re-expand which, when specified true, causes
typep x 'type) (
to be transformed into the form:
(<predicate> x)
That allows a compiler-macro for
The function remove-typep-transformer removes the association between the type and the predicate.
For example, suppose we have defined a type called angle
, which is a normalized angular measurement:
deftype angle () '(real #.(* -2 pi) #.(* 2 pi))) (
Suppose further that we have a time critical function that takes two arguments, checks to be sure they are angles, adds them together, checks to make sure the result is an angle, and returns it:
defun add-two-angles (a b)
(declare (optimize (speed 3)))
(unless (typep a 'angle)
(error "~s is not an angle" a))
(unless (typep b 'angle)
(error "~s is not an angle" b))
(let ((sum (+ (the angle a) (the angle b))))
(unless (typep sum 'angle)
(error "Sum (~s) of angles is not an angle" sum))
( sum))
As is, 10,000 calls of this function (given legal arguments) takes about 1.5 CPU seconds (on my test machine). Suppose we're unhappy with that and want to speed it up by adding a typep-transformer, without having to change the coding of add-two-angles. Further suppose that we're usually dealing with single precision floating point numbers. Here's a way we can do it. We first define our predicate function. Note that we put the most likely case (single-float) as the first choice in the typecase form.
defun anglep (x)
(declare (optimize (speed 3) (safety 0)))
(typecase x
(single-float (and (>= (the single-float x)
(float (* -2 pi) 0.0s0))
#.(<= (the single-float x)
(float (* 2 pi) 0.0s0))))
#.(fixnum (and (>= (the fixnum x) #.(truncate (* -2 pi)))
(<= (the fixnum x) #.(truncate (* 2 pi)))))
(double-float (and (>= (the double-float x)
(float (* -2 pi) 0.0d0))
#.(<= (the double-float x)
(float (* 2 pi) 0.0d0)))))) #.(
We then call add-typep-transformer to make the compiler aware of our predicate:
(excl:add-typep-transformer 'angle 'anglep)
Now if we recompile add-two-angles and call it another 10,000 times with the same arguments, it only takes about .25 CPU seconds, a 6 fold improvement (you may see a different speedup ratio).
See the discussion in implementation.html and the description of *cltl1-compile-file-toplevel-compatibility-p* for information on special handling of certain top-level forms in a file. The issue is whether the forms are treated (during the compile) as if wrapped in an
eval-when (compile) ...) (
A top-level form in a file is one which is not a subform of anything except perhaps progn. In CLtL-1 CL, top-level forms involving calls to the following functions were treated as if wrapped in such an eval-when when compiled. In ANSI CL, they are not. You can arrange to have the CLtL-1 behavior as described in [implementation.htm. The affected functions are:
make-package
shadow
shadowing-import
export
unexport
require
use-package
unuse-package
import
Consider the following definition:
defun foo (x123 y123 z123)
(declare (fixnum x123 y123 z124))
(+ x123 y123 z123)) (
Unless care is taken in looking at this definition, it is easy not to notice that there is a spelling error in this definition (z124 instead of z123). And yet, since declarations are optional in Common Lisp, such a spelling error would not make a functional difference in the compiled code, and so it is likely to go completely unnoticed, even when the code goes into production, unless careful optimization is done.
When compiling this function, this kind of error results in a warning. Specifically, if a type declaration finds a name which is unknown lexically, a warning will be generated. In this eample case, the warning will be for z124, which apparently should have corresponded to z123 instead.
This warning will not occur for any correct portable code. The only questionable situation is the case where the variable is truly unknown, and thus would have already been giving a warning for a variable that will be assumed to be special, as in this example:
defun xxx ()
(declare (fixnum xyz))
(1+ xyz)) (
Now, two warnings will (likely) be given: one for the fact that xyz
is unknown in the declaration, and one for the fact that xyz is unknown in the code and is this assumed special.
Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.
|
Allegro CL version 11.0 |