The CLR is the core of Microsoft’s .NET technology. During the last week I’ve learned quite a bit about the CLR from CodeFez readers and other sources. I’m still not an expert on this difficult subject, but I now hope I know enough to at least advance the subject beyond last week’s article.
I’m now convinced that the CLR is a virtual machine. However, I have come to have a deeper appreciate for virtual machines, and to understand that there are good reasons why virtual machines such as the JVM or CLR can at times equal or even outperform standard code.
As I’ll explain in depth later in this article, my reasons for believing the CLR is a virtual machine are threefold, though the first two reasons are closely related:
The architecture of the CLR is remarkably similar to the architecture of the Java Virtual Machine. The two are not identical, but they share enough in common that if one wants to claim that JVM is a virtual machine, and most would agree that it is, then the CLR must also be a virtual machine.
The CLR, like the JVM, is an abstract stack machine. As you will see later in this article, an abstract stack machine is, by its very definition, a virtual machine. Neither the JVM nor the CLR let you access the CPU, its registers, or its stack. Instead, they both present you with a view of an abstract stack and abstract heap which are both managed by either the CLR or the JVM. It is this abstract, virtual, machine, that is used when you program with either the JVM or the CLR. Since both the JVM and the CLR are abstract stack machines, then they both must be virtual machines. If you are short on patience, you can skip directly to the heart of this article, which is my discussion of abstract stack machines.
There is a general consensus among many authorities that the CLR is a virtual machine. In particular, the words "virtual machine," and the word "virtual," are used over and over again when describing .NET and CLR technology. For instance, the Wikipedia defines the CLR as a virtual machine, Mono defines their implementation of the CLR as a virtual machine, and the CLR is mentioned as an example of virtual machine in the Wikipedia definition of virtual machines. It’s also interesting to note that a primary document from the Microsoft web site describes the CLR as a Virtual Execution System. The document in question is the ECMA specification, which was written in part by Microsoft. In that document, the VES is defined as "an environment for executing managed code. It provides direct support for a set of built-in data types, defines a hypothetical machine with an associated machine model and state, a set of control flow constructs, and an exception handling model."
The points outlined in step three of these three points speak for themselves. They show that it is common to refer to the CLR as a virtual machine. However, the issues discussed in the first two points are more complicated, but also more directly relevant to the current discussion. After all, the fact that some authorities call the CLR a virtual machine doesn’t prove that it is a virtual machine. After all, those authorities, no matter how well qualified, could be wrong. It’s not likely that they are wrong, but it is possible. But a discussion about similarities between the JVM and CLR is enhanced if it is more than an argument from authority. The same applies to a discussion of abstract stack machines. As a result, in this article, I’ll concentrate on these points. In particular, I’ll begin by showing similarities between the CLR and the JVM.
Compiling Code for the JVM and the CLR
There is a huge and obvious similarity between C# and Java code. However, this fact alone does not illustrate that the CLR and the JVM have any deeper, architectural, similarities. C# is just one way of writing code for the .NET virtual machine, just as Java is just one way to write code for the Java Virtual Machine. The machines themselves, however, are not defined in high level languages such as Java, C#, Python or Visual Basic. Instead, they are defined in IL, or intermediate level code. In other words, the machine depends not on the syntax of C# or Java, but on the syntax of intermediate level code.
NOTE: Compiling to the CLR or the JVM is usually at least a two step process. First source is compiled to an intermediate language (IL), and then later the IL is translated or compiled into machine code. As we will see, it is not normally possible to execute code in the CLR or JVM without first translating it into valid IL code. It is therefore the syntax and semantics of IL code that define these virtual machines. In fact, I’ll argue in this article that an understanding of the CLR begins with an understanding of the IL virtual machine.
More fertile ground is found when one turns away from high level languages, and begins looking at the compilation process. Both Microsoft and Sun use a specialized, multi-phase, compilation process. In particular, both compilers take multiple passes over the code:
First they both create IL code from a high level language such as Java or C#.
Then they use machine specific optimizations to tune that code for a particular type of computer
Finally, they both compile that code to machine code and perform additional optimizations.
When reviewing this process, it is important to understand that we are not comparing the abstract definitions of the CLR and the JVM, but the actual implementations of those machines as produced by Microsoft and Sun. In other words, there is nothing in the definition of the JVM or the CLR that insists that the compilation process must proceed as outlined here. In fact, there are implementations of the JVM and the CLR that do not follow this process in precise detail. However, Microsoft .NET and the Sun HotSpot technologies do implement the JVM and CLR as outlined here. In particular, they use what are called JIT’s, or Just in Time Compilers, to create optimized machine code in the last phases of this process.
To really understand the similarity between the Sun and Microsoft implementations of the JVM and the CLR, you need to look at the last step in some detail. Both the CLR and the JVM do not compile the entire program to machine code at once. Instead, they both elect to compile the code one method or one class at a time. The first time you touch a method in either the CLR or the JVM, it is often compiled to machine code, and then need not be compiled again until the program is reloaded. This means that in both Java and in .NET, if you load two instances of a program at the same time, they will both need to be separately recompiled to machine code. In both Java and .NET, if you unload a program and relaunch it, then the IL must be recompiled to machine code. Conversely, in both instances, after code has been touched once, it is typically not recompiled until the program is unloaded from memory. However, there are cases when both of these virtual machines might recompile code at runtime if such a recompilation will lead to faster or safer code.
It is also important to point out that both Microsoft and Java have special compilers that are designed to compile IL to machine code before the program runs. However, a discussion of the Microsoft NGEN technology and the related Java technologies is not included in this article. It should be noted, however, that compiling code in this way usually does not bring significant performance benefits to either the JVM or the CLR. The reasons for this will be explained briefly in the section on performance near the end of this article.
Does Microsoft Call the CLR at Runtime?
Many of the points outlined so far emerged during the on line discussion of last week’s article on virtual machines. However, one thoughtful participant in that discussion made an interesting observation. He said that the compilation process for the two systems might be similar, but that .NET was fundamentally different from the JVM because "As I understand it a Java app calls into the JVM while it is running, but a .NET app does not call into the CLR." If true, this statement would highlight a significant difference between the JVM and the CLR. So let’s take some time to see if it is true.
During this discussion, the key point to grasp is that the letters CLR stand for the Common Language Runtime. One would assume that something that is called a "runtime" is used at runtime. But let’s dig a little deeper to see exactly how it is used.
It turns out that most .NET applications do in fact explicitly call into the CLR. In particular, there is a part of the CLR called the Base Class Library, or BCL. The BCL is laid out as part of the CLR in the ECMA specification. In an interview on Channel Nine, Kit George, the Microsoft Program Manager for the BCL, takes obvious pride in asserting that the BCL is part of the CLR.
The Base Class Library includes System.Collections, as well as support for string handling and file I/O. In other words, if your .NET program uses collections, handles strings, or does file I/O, then it is likely calling into the CLR.
But the tie between a .NET application and the CLR goes much deeper than simple calls into the CLR. You can’t load any .NET application or library without first loading the CLR into memory. The .NET PE format used in all .NET application and libraries has a section in it called the CLR header. The CLR header is executed immediately after a .NET program is loaded into memory. In fact, the purpose of this header is to ensure that the CLR is already loaded into memory, or to load the CLR into memory if necessary. In other words, you can’t run a .NET program without having the CLR loaded into memory. The very file format of .NET applications, as defined in the ECMA specification, and as implemented by Microsoft .NET, includes a section designed to load the CLR into memory.
It would be interesting, and perhaps slightly humorous, if the CLR were simply loaded into memory and then disappeared into the background and never got used again. However, this is not the case. Instead, it is usually active throughout the run of an application. It is doing many things, but the primary thing it does is provide a framework for the execution of managed .NET code.
As explained in the ECMA specification created by Microsoft and other companies, at the heart of the CLR lies the Virtual Execution System. The VES "is responsible for loading and running programs written for the CLI. It provides the services needed to execute managed code and data, using the metadata to connect separately generated modules together at runtime (late binding)." In short, whether or not a program calls into the CLR, it is running inside of the CLR, and without the CLR it cannot execute any code. Since code execution in the CLR and JVM is a dynamic process that typically goes on continually throughout the lifetime of a program, it is obvious that the CLR and your program are bound together in ways that transcend the simple act of calling into the CLR. The most important and intimate relationship between your code and the CLR is not when you call into the CLR, but the fact that your code is hosted, managed, and executed by the CLR.
To me, this symbiotic relationship between .NET code and the Virtual Execution System fits the prototypical definition of a virtual machine. In other words, the CLR is a software machine, a virtual machine, designed to host .NET programs. However, to really understand why the CLR is a virtual machine, you need to go one step further and examine what Simon Robinson, in his book "Expert .NET 1.1 Programming" calls an abstract stack machine.
Both the JVM and the CLR are abstract stack machines. What does this mean?
If you are writing 80×86 assembly code, then you are writing directly to the specification of a particular processor designed by Intel. This CPU has registers, and a stack, and supports the concept of pointers to memory.
Neither the JVM nor the CLR directly model their software on an INTEL CPU. Neither of them even have an abstraction for the concept of a hardware register. Neither of them can access the hardware stack which is managed by the CPU. In fact, neither the JVM nor the CLR give you direct access to the system heap. Instead, you write to a virtual machine which has little in common with the underlying CPU. Simon Robinson points out that this virtual machine has seven parts:
An area for Static Fields
A Managed Heap
A Local Memory pool containing:
An Evaluation Stack
A Dynamic Memory Pool
A Local Variable Table
A Method Argument Table
The heart of this system is the evaluation stack.
The beautiful thing about an abstract stack machine is that it provides a very simple way to check on the safety of any particular method call. Both the CLR and the JVM manage a virtual stack that is typically used to hold the parameters passed to methods, and to hold the results passed back from methods. Type safety in .NET and the JVM consists primarily of making sure that the code defined by programmer is going to fit safely on that virtual stack. If it does fit safely on that stack, then the code compiles, and is blessed as safe, "managed" code. If it does not fit safely on that stack, then compilation fails. In other words, the virtual stack is defined in such a way that code cannot be placed on it unless it is type checked and considered safe to execute.
This system provides two great virtues:
It is not a real machine, but a virtual machine, and hence provides an abstraction away from any particular hardware or particular operating system. CLR and JVM code don’t need a particular piece of hardware, they just need an implementation of their virtual machine, of their abstract stack machine. They don’t need an Intel processor, and they don’t need Windows. They need their virtual machine. In one case the virtual machine is called the JVM, in a second case it is called the CLR. But they are both virtual machines. Because of these IL virtual machines, it is possible to compile IL code from both the JVM and the CLR to run on either Windows or Linux. You can’t run JVM code on the CLR, and you can’t run CLR code on the JVM. However, you can compile Java code to IL and execute it unchanged on any proper JVM, whether it is hosted on Linux, Windows, or a cell phone. Likewise, you can compile .NET code with Visual Studio to IL and run it unchanged on Windows, on a cell phone, or on Linux, using Mono.
The second great thing about abstract stack machines is that they provide a simple, neat way to check the type safety of your code. All that matters is whether or not your code fits on the virtual stack created by either the CLR or the JVM. If it does, then the code will run on the CLR or JVM virtual machines.
Now that we have established that the CLR is a virtual machine, the next step is to understand why performance on virtual machines can be so efficient. In the discussion that follows, I will try to highlight one or two prototypical examples of how virtual machine optimizations take place. This is not meant to be a complete list, but only to give examples that I feel are representative of the type of optimizations performed by both the CLR and the JVM.
The key reason why a virtual machine can sometimes be faster than normal code is that the entirety of a program need not be compiled in order to run inside the JVM or CLR. Instead, only code that is actually being executed will be compiled. It is possible to perform optimization on small chunks of code that could not be performed on an entire program.
NOTE: In the discussion that follows, I talk about inlining a method. Normally, if you want to call a method, you must jump from one place in memory to the place where the called method resides. This jump takes time and resources. A compiler can, however, inline a method by moving it wholesale into the current memory location. It is therefore not necessary to jump in memory from one place to another. This can be a big performance boost in some cases.
Both the JVM and the CLR can inline methods on the fly, when necessary, without any request from the programmer. Normally, in a standard compiler, it is not possible to inline a virtual method. This can be a big problem for languages like Java and C#, where most methods are virtual by default. However, the JVM is smart enough to look at virtual methods, and decide if, in this particular case, on this particular machine, with this amount of code in play, it is possible to inline a particular virtual method. In particular, it will decide if it can resolve the address of the method at compile time, rather than at run time. If it can do so, then it will. If, later on, it decides that it is no longer possible to inline a virtual method, then it will stop doing so.
Microsoft has not had time yet to advance to this level of optimizing virtual methods, but it does inline many methods where possible, so long as the entire method is smaller than 32 bytes. This means that the CLR, like the JVM, can optimize code that appears in loops by adding inlining. Over time, Microsoft will surely add the ability to automatically inline virtual methods.
Another obvious advantage of not having to compile the whole program ahead of time is that the whole compiled program need not be loaded into memory at start up. In other words, if your code at first only calls a few small methods or classes, then only those chunks of code will be compiled, and hence only that small amount of code will need to be loaded into memory. This leads to faster start up time, and helps explain why NGEN and related Java technologies are not necessarily faster than JITed code. If you compile the whole program ahead of time with NGEN, then the whole program must be loaded entirely into memory at startup. This can, in some cases, slow program execution. Also, decisions about how to optimize code based on which code is in memory at a particular time cannot be used in pre-compiled code, and hence programs compiled with a tool like NGEN can be slower than JITed code.
I should add that the Microsoft abstract stack machine is very clever about using registers. I mentioned earlier that the CLR and JVM abstract stack machines are not based on the real underlying machine, and that it does not model CPU registers or the hardware stack. However, the CLR is very clever about placing some parts of the virtual stack in registers when possible. This can’t be done with objects, but it can be done with simple values such as integers. This can improve performance in an application. As far as I know, the JVM does not have a similar optimization.
So far, we have talked about how JITed code can outperform code compiled in a normal manner. Yet anyone who has used the CLR or JVM knows that not all programs run in a virtual machine are faster than normal programs. In part, this is because the CLR or JVM needs to be loaded into memory, which takes time and valuable RAM. However, to understand this subject in more depth we need to go back and think some more about the abstract stack machine.
Earlier in this article there was discussion about how the JVM and CLR compile IL down to machine code. It is, however, important to understand that this machine code is still executed inside the JVM or CLR virtual machines. That is, they follow the abstract stack machine model. It may be true that a particular JIT is capable of compiling code that does not fit on the abstract stack, but that fact is irrelevant because the code would never have been compiled to IL unless it fit inside the CLR or JVM virtual machines. This is why it is not always interesting to discuss the details of how JIT’s produce machine code, or whether or not that code calls into the CLR. It does in fact call into the CLR, and the CLR calls into it. But that is not important, because no .NET code, and no JVM code can be executed except on the abstract stack that is part of both the CLR and the JVM.
I should also point out that neither the CLR nor the JVM depends on the presence of a finely tuned JIT compiler. In fact, there is nothing in either the CLR or JVM specification that says that such a highly optimized compiler need exist. JIT’s are really an added feature that both Microsoft and Sun have introduced to improve performance. In short, when you are trying to understand the CLR or the JVM, concentrating on the machine code produced by a JIT can be a distraction. Both the CLR and the JVM have JIT’s, but that is not what is important about the CLR or the JVM. Instead, you should concentrate on the fact that both of these virtual machines are built around an abstract stack. It is this stack that makes the safety and portability of the CLR and the JVM possible. A JIT is a highly valuable feature, and I think everyone should use one, but they are not part of the core of either the JVM or the CLR.
This article described how both the Java Hot Spot JIT technology and the Microsoft CLR compile bytecode to machine code. Both the Java Virtual Machine and the Microsoft CLR virtual machine compile code only the first time it is run during a particular session. If you close down a .NET application, then the code needs to be recompiled when you launch it again. If you have two instances of a .NET application running at one time, then the code for each needs to be compiled separately. Both environments compile code on demand, that is, they do not compile the whole program at once, but compile on the fly and as necessary. In short, both the CLR and the JVM have very similar compilation systems.
It turns out that the compiled machine code in a .NET application has a very intimate relationship with the CLR. In many cases, it literally calls into the CLR. In all cases, it cannot run or execute without the assistance of the CLR.
Finally, at the end of this article, it became clear that the CLR sets up an IL virtual machine that is built around a single abstract stack. This stack provides a high level of code safety, and also provides running code with a virtual machine inside of which it can execute. This virtual machine isolates the program from the particular features of the machine on which it runs.
In writing this article, I have explained why the CLR and the JVM are both virtual machines. In the process, I have come to have a deeper understanding of, and appreciation for, what virtual machines like the CLR or JVM do for developers. In my previous article, I asked if virtual machines were worth the price that we pay for them in terms of memory usage and machine resources. At this stage, I’d be prepared to answer in the affirmative to this question. Sun did a remarkable thing when they created the JVM, and Microsoft’s implementation of this same technology is equally impressive. I have been using the JVM heavily for years, and I have been using various implementations of the CLR for over a year now. I plan to continue using both technologies, and my appreciation for them only grows as I learn more about them.
A virtual machine is a machine completely defined and implemented in software rather than hardware. It is often referred to as a "runtime environment"; code compiled for such a machine is typically called bytecode.
From the Wikipedia definition of a virtual machine:
More modern examples include the specification of the Java virtual machine and the Common Language Infrastructure virtual machine is at the heart of the Microsoft .NET initiative.
Reference the IL Virtual Machine: http://www.microsoft.com/australia/events/teched2003/tracks/tools.asp