| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
As described in [see section Steele:84], Common-Lisp
classes: proclamations and others. A proclamation is a global
declaration given by the function proclaim, the top-level macro
defvar, or the top-level macro defparameter. Once given, a
proclamation remains effective during the ECL session unless it is shadowed
by a local declaration or is canceled by another proclamation. Any other
declaration is a local declaration and is given only by the special form
declare. A local declaration remains in effect only within the body of
the construct that surrounds the declaration.
In the following nonsensical example borrowed from Chapter 9 of [see section Steele:84],
(defun nonsense (k x z)
(foo z x)
(let ((j (foo k x))
(x (* k k)))
(declare (inline foo) (special x z))
(foo x j z)))
|
inline and the special declarations both remain in effect within the
surrounding let form. In this case, we say that the let form is
the surrounding construct of these declarations.
The ECL interpreter does actually check whether the value of the
form conforms to the data type specified by value-type and
signals an error if the value does not. The type checking is performed by
the function typep. For example,
(the fixnum (foo)) |
is equivalent to
(let ((values (multiple-value-list (foo))))
(cond ((endp values) (error ``Too few return values."))
((not (endp (cdr values)))
(error ``Too many return values."))
((typep (car values) 'fixnum) (car values))
(t (error ``~s is not of type fixnum." (car values)))))
|
On the other hand, the ECL compiler uses the special form to obtain type information for compiled code optimization. No code for runtime type-checking is embedded in the compiled code.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
object
declaration specifier which is specific to ECL.
type proclamation (type type var1 var2 ...) specifies
that the dynamic values of the named variables are of the type type. A
local type declaration specifies that the variables mentioned are bound
by the surrounding construct and have values of the type type during
execution of the surrounding construct. The compiler issues a warning if one
of the named variables is not bound by the surrounding construct. The
information given by type declarations is used by the compiler to
optimize the compiled code. The behavior of the compiled code is unpredictable
if a wrong type declaration is supplied. The compiler detects certain
wrong type declarations at compile time.
For example,
>(defun foo (x y) (declare (fixnum x) (character y)) (setq x y) ...)) foo >(compile 'foo) ; (defun foo ...) is being compiled. ;; Warning: Type mismatches between x and y. |
See Section 7.3 for further information on type declarations.
(type type var1 var2
...), provided that type is one of the symbols in Table 4-1 of
[see section Steele:84], other than function. Declaration specifications
that begin with function are regarded as function declarations
(see below).
function declaration is used to obtain type information for function
call forms. That is, a function declaration specifies the argument and
the return types of each form that calls the named function.
(defun foo () (declare (function bar (character) fixnum)) (+ (bar (atcholi1)) (bar (atcholi2)))) |
In this example, the function declaration specifies that the two
functions atcholi1 and atcholi2 both return character objects
when called within the body of foo, and that the function bar returns
fixnum objects when called within the body of foo. The type information
given by function declarations is used by the compiler to optimize the compiled
code. The behavior of the compiled code is unpredictable if a wrong
function declaration is supplied. The compiler detects certain wrong
function declarations at compile time.
For example,
>(defun foo (x)
(declare (fixnum x)
(function bar (character) fixnum))
(bar x))
foo
>(compile 'foo)
; (defun foo ...) is being compiled.
;; Warning: The type of the form x is not character.
|
However, the compiler does not check the number of arguments, and thus, the following function definition will be compiled successfully without any warnings.
(defun foo () (declare (function bar (character character) fixnum)) (+ (bar (atcholi1)) (bar (atcholi2) (atcholi3) (atcholi4)))) |
For this definition, the compiler assumes that the three functions
atcholi1, atcholi2, and atcholi3 will return fixnum
objects. The return type of atcholi4 is unknown at compile time.
The complete syntax of a function declaration is:
(function function-name
({type}* [{&optional
{(values {type}* ) | {type}*}
)
|
Although &optional@c, &rest@c, and &key
argument types, only those types are recognized that appear before any
such markers and the rest of the list is simply ignored. Note that functions
with &optional@c, &rest@c, or &key
function declarations because of the use of function declarations
mentioned above.
The values construct in the specification of return types is almost
useless: (function function-name argument-types (values
type1 type2 ...)) is equivalent to (function
function-name argment-types type1 type2 ...).
See Section 7.3 for further information on function declarations.
function. (ftype (function . rest) function-name-1
... function-name-n) is equivalent to n consecutive
function declarations (function function-name-1
. rest) ... (function function-name-n . rest).
(notinline function1 function2 ...) specifies that the
compiler should not compile the named functions in-line. Calls to the
named functions can be traced and an event (see Section 5.4) is pushed
on the event stack when any one of the named functions is invoked.
inline proclamation cancels currently effective notinline
proclamations, and a local inline declaration locally shadows currently
effective notinline declarations.
>(defun foo (x)
(cons (car x)
(locally (declare (inline car)) (car x))))
foo
>(defun bar (x)
(cons (car x)
(locally (declare (inline car)) (car x))))
foo
>(proclaim '(notinline car))
nil
>(compile 'foo)
...
>(proclaim '(inline car))
nil
>(compile 'bar)
...
|
Usually, primitive functions such as car are compiled in-line.
Therefore, in this example, only the first call to car within foo
is compiled not in-line.
In general, the ECL compiler compiles functions in-line whenever possible.
Thus an inline declaration (inline function1 function2 ...)
is worthless if none of the named functions have previously been declared to be
notinline.
(ignore var1 ... varn) causes the compiler not to issue a
warning even if the named variables are never referred to. The compiler issues
a warning if one of the named variables is not bound by the surrounding
construct, or if a named variable is actually referred to. ignore
proclamations are simply ignored.
optimize qualities listed in the
[see section Steele:84].
speed and compilation-speed are used to set up the optimization
switch of the C language compiler which is invoked to compile the C-language
code generated by the ECL compiler (see Chapter 6). (optimize (speed
n)) and (optimize (compilation-speed m)) are equivalent,
where n and m are integers between 0 and 3, and m is equal to
3-n. When a ECL session is started, the speed quality is set
to 3. That is, by default, the compiler generates the fastest code in the
longest compilation time. The space quality specifies whether the code
size is important or not: The compiled code is a little bit larger and faster
when compiled with the space quality 0, than when compiled with the space
quality 1, 2, or 3. When a ECL session is started, the space
quality is set to 0. The safety quality determines how much runtime
error checking code should be embedded in the compiled code. If the
safety quality is 0, the compiled code scarcely does runtime error
checking. If the safety quality is 1, then the compiled code for a
function will check the number of arguments to the function at runtime. If the
safety quality is 2 or 3, then the compiled code does full runtime error
checking. In addition, the highest quality value 3 causes the compiler to
treat all functions as if they were declared to be notinline. When a
ECL session is started, the safety quality is set to 0.
declaration declaration is used exactly as specified in the
[see section Steele:84].
(object var1 ... varn) affects only variable bindings and
specifies that the named variables can be allocated in the C stack (see Section
7.3). The compiler issues a warning if one of the named variables is not bound
by the surrounding construct. object proclamations are simply ignored.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Whenever a declaration is encountered, each type specifier (if any) in the declaration is converted to one of the following type specifiers, which are collectively called the significant type specifiers.
|------------ fixnum
|------------ character
|------------ short-float
|------------ long-float
t --|---- (array t) -------------- (vector t)
|---- (array fixnum) --------- (vector fixnum)
|---- (array string-char) --- string
|---- (array short-float) --- (vector short-float)
|---- (array long-float) --- (vector long-float)
|---- (array bit) ----------- bit-vector
|
Here, the lines indicate subtype relations; the right type is a subtype of the
left type. For instance, (vector t) is a subtype of (array t)
and T@c, and (array t) itself is a subtype of T@c. However,
(array t) and (array string-char) are disjoint types.
The function subtypep is used for the conversion to significant type
specifiers: If the first value of (subtypep raw-type type) is
T
specifier raw-type in the declaration is converted to type. If
there are more than one such significant type specifiers, then the type
specifier that is a subtype of other specifiers is selected. For example, type
specifiers fixnum@c, (mod 3), and (member 0 1) are all
converted to fixnum@c, though they are also subtypes of T@c.
Because of this type specifier conversion, ECL may sometimes regard two
seemingly distinct declarations as the same. For example, the following
type declarations are completely equivalent, internally in ECL.
declare (type fixnum x)) declare (type (mod 3) x)) declare (type (member 0 1) x)) |
proclamation are also converted to significant type specifiers.
Thus, for example,
>(proclaim '(function foo (fixnum) fixnum)) nil >(proclamation '(function foo ((mod 3)) (member 0 1))) t >(proclamation '(function foo (number) character)) nil |
The first call to proclamation returns T
3)} and (member 0 1) are converted to fixnum
function type of foo is checked.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Arguments to functions, lexical and temporary variables are allocated on the C stack. Temporary values saved on the C stack may sometimes be represented as raw data instead of pointers to heap-allocated cells. Accessing such raw data on the C stack results in faster compiled code, partly because no pointer dereferencing operation is necessary, and partly because no cell is newly allocated on the heap when a new object is created. This is particularly helpful for numeric code which computes with floating point numbers.
ECL uses a conservative garbage collector to scan the C stack and find references to live object.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
If a lexical variable is declared to be of fixnum@c, character,
short-float, long-float, or their subtypes, then it is allocated
on the C stack rather than on the value stack. In addition, the variable
always has a raw datum as its value: 32 bit signed integer for fixnums, 8 bit
character code with 24 bit padding for characters (remember that the font and
bit fields of ECL characters are always 0), 32 bit floating point
representation for short-floats, and 64 bit floating point representation for
long-floats. Similarly, if a lexical variable is named in an object
declaration (see Section 7.1), then it is allocated on the C stack but, in this
case, the variable always has a cell pointer as its value. The user is
strongly recommended to make sure that objects stored in such an object
variable may never be garbage collected unexpectedly. For example,
(do ((x (foo) (cdr x)))
((endp x))
(let ((y (car x)))
(declare (object y))
(bar y)))
|
this object declaration is completely safe because the value of the
variable y is always a substructure of the value of x, which in
turn is protected against garbage collection. Incidentally, loop variables of
dolist may always be declared as object variables, since the
dolist form has essentially the same control structure as the do
form above. On the other hand, the result of evaluation of the following form
is unpredictable, because the cons cell pointed to from the object
variable z may be garbage collected before bar is called.
(let ((z (cons x y)))
(declare (object z))
(foo (cons x y))
(bar z))
|
Lexical variables that are not declared to be of fixnum@c,
character@c, short-float@c, long-float@c, or their
subtypes, and that are not named in object declarations are usually
allocated on the value stack, but may possibly be allocated on the C stack
automatically by the compiler.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
+ is among
such functions.
(let ((x 1))
(declare (fixnum x))
...
(setq x (+ x 2))
...
)
|
In the compiled code for this let form, the raw fixnum datum (i.e., the
32 bit signed integer) stored in x is simply incremented by 2 and the
resulting 32 bit signed integer is stored back into x. The compiler is
sure that the addition for 32 bit signed integers will be performed on the call
to +, because the arguments are both fixnums and the return value must
be also a fixnum since the value is to be assigned to the fixnum
variable. The knowledge of both the argument types and the return type is
necessary for this decision: Addition of two fixnums may possibly produce a
bignum and addition of two bignums may happen to produce a fixnum value. If
either the argument type or the return type were not known to the compiler, the
general addition function would be called to handle the general case. In the
following form, for example, the compiler cannot be sure that the return value
of the multiplication is a fixnum or that the arguments of the addition are
fixnums.
(setq x (+ (* x 3) 2)) |
In order to obtain the optimal code, a the special form should
surround the multiplication.
(setq x (+ (the fixnum (* x 3)) 2)) |
Built-in Common-Lisp
+, -,
1+, 1-, *, floor, mod, /, and
expt.
eq, eql, equal,
zerop, plusp, minusp, =, /=, <,
<=, >, >=, char=, char/=, char<,
char<=, char>, and char>=.
nth, nthcdr, length, and
elt.
svref, char, schar,
and aref (see below).
char-code, code-char,
and float.
As mentioned in Section 2.5.1, array elements are represented in one of six ways depending on the type of the array. By supplying appropriate array type declarations, array access and update operations can handle raw data stored in arrays. For example,
(let ((a (make-array n :element-type 'fixnum))
(sum 0))
(declare (type (array fixnum) a)
(fixnum sum))
(dotimes (i n) ;;; Array initialization.
(declare (fixnum i))
(setf (aref a i) i))
....
(dotimes (i n) ;;; Summing up the elements.
(declare (fixnum i))
(setq sum (+ (aref a i) sum)))
....
)
|
The setf form replaces the i-th element of the array a by the raw
fixnum value of i. The aref form retrieves the raw fixnum datum
stored in a. This raw datum is then added to the raw fixnum value of
the fixnum variable sum, producing the raw fixnum datum to be stored in
sum. Similar raw data handling is possible for arrays of types
(array fixnum), (vector fixnum),
(array string-char), string,
(array short-float), (vector short-float),
(array long-float), and (vector long-float).
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Function proclamations (function function-name (arg-type1
arg-type2 ...) return-type) or its equivalents give the compiler
the chance to generate compiled code so that arguments to the named functions
and resulting values of the named functions will be passed via the C stack,
thus increasing the efficiency of calls to these functions. Such
arguments/values passing via the C stack is possible only if the called
function is also defined in the same source file. This is because the code for
the called function must have two entries: One entry for C arguments/values
passing and another for ordinary Lisp arguments/values passing. (An ordinary
function has only the latter entry.) When the latter entry is used, the
arguments are unboxed and passed to the former entry. On return from
the function, the resulting value is cast into a Lisp data type.
A good example of this follows.
(eval-when (compile)
(proclaim '(function tak (fixnum fixnum fixnum) fixnum)))
(defun tak (x y z)
(declare (fixnum x y z))
(if (not (< y x))
z
(tak (tak (1- x) y z)
(tak (1- y) z x)
(tak (1- z) x y))))
;;; Call (tak 18 12 6).
|
When tak is called with the arguments 18, 12, and 6, the
raw fixnum data of the arguments are set to the parameters x, y,
z. After that, only raw C data are used to perform the execution: No
cell pointers are newly allocated nor even referenced. The built-in functions
< and 1- directly operate on the raw data. Only at the return from
the top-level call of tak, the resulting raw data value (which happens
to be 7) is reallocated on the heap. Note that both the function
proclamation and the local fixnum
obtain the optimal code. The function proclamation is necessary for
arguments/values passing via the C stack and the fixnum
is necessary to unbox the parameters into C variables.
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |