In order to use a non-descriptor representation for a variable or expression intermediate value, the compiler must be able to prove that the value is always of a particular type having a non-descriptor representation. Type inference (see section 5.3) often needs some help from user-supplied declarations. The best kind of type declaration is a variable type declaration placed at the binding point:
Use of the, or of variable declarations not at the binding form is insufficient to allow non-descriptor representation of the variable--with these declarations it is not certain that all values of the variable are of the right type. It is sometimes useful to introduce a gratuitous binding that allows the compiler to change to a non-descriptor representation, like:(let ((x (car l))) (declare (single-float x)) ...)
The declaration on the inner x is necessary here due to a phase ordering problem. Although the compiler will eventually prove that the outer x is a (signed-byte 32) within that etypecase branch, the inner x would have been optimized away by that time. Declaring the type makes let optimization more cautious.(etypecase x ((signed-byte 32) (let ((x x)) (declare (type (signed-byte 32) x)) ...)) ...)
Note that storing a value into a global (or special) variable always forces a descriptor representation. Wherever possible, you should operate only on local variables, binding any referenced globals to local variables at the beginning of the function, and doing any global assignments at the end.
Efficiency notes signal use of inefficient representations, so programmer's needn't continuously worry about the details of representation selection (see section 5.13.3.)