You can execute Common Lisp code in CMUCL in several different ways: using the interpreter, the byte-code compiler, or the native-code compiler. This document provides more information on the differences between these parts of CMUCL.
CMUCL has the following:
- A trivial "baby" interpreter, that handles things like function application and SETQ, and which is present in all CMUCL cores, even those without a compiler present.
- A complete, "grownup" interpreter, which can handle the full
language. It works by using the first stages of the compiler, to
convert the source code to the IR1 internal representation (which is
a flow-graph based representation, with implicit control-flow
information; also called ICR: Implicit Continuation Representation),
which is then interpreted. This interpreter depends on the compiler
being present (and thus can't be used when CMUCL is rebuilding
itself; this is why there's also a baby interpreter). Since it
converts before interpreting, it behaves like a compiler when it
comes to things like macro-expansion, or handling of circular source
The interpreter is invoked when you enter forms into the listener (i.e. the interactive read-eval-print loop), or when you
LOADcode from a file without first compiling it using
- A byte-code compiler. This also works off the IR1 internal
representation, but instead of interpreting that directly, it
transforms it to byte-codes for a stack-based VM, which can be
written to a processor-independent byte-fasl file. The byte-code is
finally interpreted by a byte-code interpreter.
The main advantage of the byte-code compiler are space-savings, since the byte-coded representation is fairly compact (the cmu-user manual gives a factor of 6), yet still faster (by an order of magnitude, says the manual) than interpretation. It also gives you processor-independence, though not endianness-independence.
You can byte-compile whole files, or you can tell the file compiler to only byte-compile certain parts, like e.g. top-level forms, which aren't usually time-critical (this is the default).
- The native compiler. This also runs through IR1, which is -- after extensive optimizations -- transformed into IR2, the virtual machine representation (VMR). The virtual machine that the VMR is based-on is defined by the processor-specific backends of the compiler, so that it can be tailored to the target processor as needed, yet still allows things like register allocation algorithms, etc. to be shared between backends. Finally the VMR is converted to assembly code, through the VOP's code-generators. The assembly code is slightly optimized, then assembled to machine code, and finally emitted to the FASL file.
Controlling the choice of compiler
The baby interpreter is only used when evaluating simple forms at the
listener: simple function calls,
SETQ, PROGN and so forth. Anything more
complicated at the listener is evaluated by the grownup interpreter.
Compilation is provoked by the function
COMPILE, or by compiling from a file with
COMPILE-FILE. The choice between
native and byte-code compilation depends on a number of factors, as
documented in the CMUCL
adapted from a USENET posting by Pierre Mai, in a posting to comp.lang.lisp on 2002-02-14