A Low-Level Virtual Machine?
The concept of something that is both "low-level" and a "virtual machine" (VM) is slightly strange. Most virtual machines are either very abstract or consist of only very thin abstraction layers. Perhaps the lowest-level virtual machine is Xen, which has the same non-privileged instructions as x86, but different privileged ones.
At the opposite extreme is something like the Java virtual machine. Java bytecode runs on a virtual-stack-based machine (all of the real stack-based machines except x87 having died out a long time ago). This is very different from any real machine, although it has some passing similarities to old LISP machines. The Java VM makes a number of assumptions about the code running on it, making it difficult to run anything that’s not a strongly typed language with a Java-esque system.
The abstract machine presented by LLVM is somewhere between the two. It’s close to an unlimited register machine (URM), which is a fairly common theoretical model for computing. A few things mark it as being different from a real computer:
- Each register is typed. In most computers, registers are typed to the extent that they are integer or floating-point quantities, but the LLVM type system goes far beyond this level.
- Each register is single-assignment. As discussed earlier, this form makes it much easier to perform a number of classes of optimization.
Compiling with LLVM is a two-stage process:
- The first step is to compile a program from source code to bytecode for the abstract machine. For some languages, this process may be two steps, going via a language-specific bytecode. After this, there are a few options.
- The LLVM bytecode can be interpreted directly. This is nice for debugging,
since the entire state of the machine can be inspected, and none of the type
information has yet been lost.
The bytecode also can be compiled to native code, either as a single-step operation or as part of a profiling-directed optimization pass. This arrangement allows the optimizer to examine the runtime behavior of the code and compile it on the fly. Alternatively, of course, it can be distributed in bytecode form, which is hardware-agnostic, and then compiled at install time, or JIT-compiled as it runs with the additional profiling information.