New Compiler Switches in Allegro CL 8.2

The behavior of the compiler in Allegro CL is controlled in part by compiler switches. These switches are described in the section Declarations and optimizations in compiling.htm. They are variables whose values are functions which takes as arguments the values of the compiler optimization qualities safety, space, speed, debug, and compilation-speed and return t or nil as the switch should or should not affect the compilation. (The values of the variables can also be t or nil, meaning the switch is always on or always off.)

There are twenty-four switches. The function explain-compiler-settings displays the switch values for specified values of the optimization qualities (defaulting to the current global values). Obviously using five values to control 24 values may result in some switches not having the value you want in order that others do have the desired value, but you can change the variable values so you get what you want easily enough, as described in the documentation.

In this note, we discuss the four new switches in release 8.2. The new switches are:

optimize-large-functions-switch

When this switch is true, the compiler will do its best to optimize large functions. If this switch is nil, the compiler will detect certain cases when optimizations are taking too long and stop further optimization. The exact internal mechanics are complicated and difficult to explain, so we will not try. The compiler notices that internal structures are exceeding a certain size (and inferring that compilation is taking too long). If you notice that compilation takes much longer than expected, try setting this switch to nil and see if that speeds things up. Note that when you produce production code, you might want to take the time to do the fully optimized compilation in order to deliver fully optimized code.

save-source-level-debug-info-switch

This switch is only relevant when compiling files. It causes the compiler to annotate the fasl (compiled-lisp) file with information useful for source-level debugging (see The source stepper in debugging.htm) and for coverage analysis (see with-coverage). This switch can in some cases greatly increase compilation time so you might turn it off, at least for those files you do not think need debugging, if compilation speed becomes an issue.

generate-accurate-x86-float-code-switch

This is an interesting issue. Certain 32-bit X86 processor chips have two modes for certain floating-point calculations, doing 80 calculations or 32/64-bit calculations. Under certain circumstances, unless certain flags are set, you can get different answers from run to run, with the differences affecting the lowest bits.

Setting the flags so the calculations always produce the same results causes a significant slowdown to the code. When this was first noticed, we overloaded the declared-fixnums-remain-fixnums-switch so when that switch was true, we also would not set the flags so floating-point calculations might differ from run to run as well. However we recognized that was undesirable so now this new switch controls setting the flags in floating-point calculations and declared-fixnums-remain-fixnums-switch controls fixnum arithmetic only.

People should keep in mind that floating-point calculations are inherently inaccurate. The inaccuracy introduced by this problem is no greater than that introduced normally. The amount is magnified by unstable calculations (such as inverting a matrix whose determinant is very close to zero) but such calculations are very inaccurate anyway. (The point is that in such case, you may notice the difference because it will no longer be only in the lowest bits.) However, it is true that users are often disturbed when what are apparently the same calculatiopns produce different answers. It is up to the programmer whether to have faster programs but occasional slight differences in results, or slower programs and the same results.

verify-type-declarations-switch

This switch, when true, causes code to be added which verifies variable type declarations so whenever a variable is bound or set to a value, that value is checked to be of the declared type. For example, in the let form, if in the body the variable var is set to a value which is not a fixnum, an error is signaled:

(let ((var 0))
  (declare (fixnum var))
  (<body>)
  )

Note that this tests inititializations which are not explicit, such as:

(defun foo (n)
  (declare (fixnum n) (optimize (speed 3) (safety 2)))
  (let (var)
    (declare (fixnum var))
    (setq var n)
    (print (isqrt var))
   ))
(compile 'foo)

cl-user(41): (foo 10)
Error: about to bind var to nil, which is not of type
       (integer -536870912 536870911).
  [condition type: type-error]

Restart actions (select using :continue):
 0: store the value anyway.
 1: Return to Top Level (an "abort" restart).
 2: Abort entirely from this (lisp) process.
[1] cl-user(42): 

Because an initial value is not specified for var, it is initialized to nil, and even though that value is never used, it still violates the declaration.

More problematic is declarations about the iteration variable in dolist. Consider this code:

(defun foo (lis)
  (declare (optimize (speed 3) (safety 2)))
  (let ((sum 0))
    (declare (fixnum sum))
    (dolist (i lis)
      (declare (fixnum i))
      (setq sum (+ sum i)))
    sum))
(compile 'foo)

cl-user(45): (foo '(1 2 3 4))
Error: about to setq i to nil, which is not of type
       (integer -536870912 536870911).
  [condition type: type-error]

Restart actions (select using :continue):
 0: store the value anyway.
 1: Return to Top Level (an "abort" restart).
 2: Abort entirely from this (lisp) process.
[1] cl-user(46): 

The list passed to foo indeed contains fixnums only, so where is i bound to nil? The specification for dolist says that the interation variable is set to nil when the loop is complete and the return form (the optional third element of the loop variable binding form) is evaluated, and that form defaults to the form nil if unsupplied. Therefore, even if you do not specify a return form, your iteration variable will be set to nil sometime unless the loop is terminated (by return, for example) before it completes and the ANS is clear that the scope of the declaration in the dolist includes the return form, even if that form is not supplied.

If you have such declarations (and thus find yourself getting an error), you should either change the declaration, perhaps to (or nil <previous type specification>), or be sure this compiler switch is nil when the function containing the dolist form is compiled. If you want the switch true in general, you can use this undocumented (for now) utility to set the switch value locally:

(excl::compiler-let ((comp:verify-type-declarations-switch nil))
  (defun foo (lis)
    (declare (optimize (speed 3) (safety 2)))
    (let ((sum 0))
      (declare (fixnum sum))
      (dolist (i lis)
        (declare (fixnum i))
      (  setq sum (+ sum i)))
      sum)))

We have not discussed the values of speed, safety, etc. that make these switches true or nil. That information is part of the documentation of the switches and is better looked for there.

Copyright © 2023 Franz Inc., All Rights Reserved | Privacy Statement Twitter