Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Analysis of the compiled CIL (Part 1)

0.00/5 (No votes)
30 Mar 2007 2  
Analysis of the CIL generated by the .net compiler for 4 Languages in VS 2005

Introduction

In this article we will be taking a look at the Common Intermediate Language (CIL) that is generated by the various language compilers in Visual Studio 2005.

What is the .NET Framework?

For purpose of brevity the answer the answer to this question would go as follows: The .NET framework is a software component that can be added to Microsoft Windows operating system (although there is some work in place for this to be able to run on Linux and other operating systems). The .NET Framework consists of a collection of classes that contain pre-coded solutions to common programming requirements. Applications that are developed for the .NET Framework run in a software environment that manages the program's runtime requirements. This runtime environment is known as the Common Language Runtime (CLR). The CLR provides the appearance of an application virtual machine, so that programmers need not consider the capabilities of the specific CPU that will execute the program. The CLR also provides other important services such as security guarantees, memory management, and exception handling.

The runtime also accelerates developer productivity. For example, programmers can write applications in their development language of choice, yet take full advantage of the runtime, the class library, and components written in other languages by other developers. Any compiler vendor who chooses to target the runtime can do so. Language compilers that target the .NET Framework make the features of the .NET Framework available to existing code written in that language, greatly easing the migration process for existing applications.

  • The most important component of the .NET Framework lies in the Common Language Infrastructure, or CLI. The purpose of the CLI is to provide a language-agnostic platform for application development. To clarify, the CLI is a specification, not an implementation, and is often confused with the (CLR)

The CLI consists of 5 Primary parts:

  1. Common Type System (CTS)
  2. Common Language Specification (CLS)
  3. Common Intermediate Language (CIL)
  4. Just in Time Compiler (JIT)
  5. Virtual execution system (VES)

A detailed discussion of all 5 these parts is out of the scope of this article. However I feel some explanation of the following two components required

  • Common Language Specification (CLS) - Is a set of specifications that Microsoft has supplied to help compiler vendors. These specifications dictate the minimum group of features that a .NET language must have.
  • Common Intermediate Language (CIL) - is the lowest-level human-readable programming language in the Common Language Infrastructure and in the .NET Framework. Languages which target the .NET Framework compile to CIL, which is assembled into bytecode. CIL resembles an object oriented assembly language, and is entirely stack-based. It is executed by a virtual machine.
  • Just In Time Compiler (JIT) - (JIT) compiling enables all managed code to run in the native machine language of the system on which it is executing. Meanwhile, the memory manager removes the possibilities of fragmented memory and increases memory locality-of-reference to further increase performance.

Are all .NET languages born equal?

Regardless of which .NET programming language is used, the output of the language compiler is a representation of the same logic in CIL. Before the program is executed, CIL is compiled to object code appropriate for the machine on which the program is executing. This last compilation step is usually performed by the CLR component of the .NET Framework at the moment the program invoked, though it can be manually performed at an earlier stage.

The Concept

In order to examine if there is actually a difference in the CIL compiled by the four compilers I developed four identical applications in the four Main languages offered in my version of Visual Studio 2005 (VB.NET, C#, C++ and J#). I followed the exact same steps in creating the applications. I Quadrifurcated the applications and threw in some bad habits. I would compiled all these applications then took a look at their MSIL compilers.

The Applications

I created console application that basically contained a stopwatch to time how long the application took to execute. The Application itself basically contained a Loop that iterated through a thousand times and concatenated a string. The result if the string concatenation was printed out to the console and also printed out the length of time it it took to execute.

I omitted the use of the StringBuilder class, in all these applications, this was done intentionally, as this was also a test to see the efficiency of the StringBuilder class. If you read Part 2 of this article you can see the performance gains supplied by the StringBuilder class.

The Code

VB.NET

Imports System.Diagnostics
Module Module1
 Sub Main()
  Dim strSomeString As String = ""
  Dim sw As New Stopwatch
  sw.Start()
  For i As Integer = 0 To 1000
   strSomeString += "Test "
  Next
  sw.Stop()
  Console.WriteLine(strSomeString)
  Console.WriteLine(sw.ElapsedMilliseconds)
  Console.ReadLine()
End Sub
End Module

C#

using System;
using System.Diagnostics;
namespace ConsoleCSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            string strSomeString = "";
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 1000; i++)
            {
                strSomeString += "Test ";
            }
            sw.Stop();
            Console.WriteLine(strSomeString);
            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}

J#

package ConsoleJSharp;
import System.*;
import System.Diagnostics.*;
public class Program
{
    public static void main(String[] args)
    {
        String strSomeString = "";
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 1000; i++)
        {
            strSomeString += "Test ";
        }
        sw.Stop();
        Console.WriteLine(strSomeString);
        Console.WriteLine(sw.get_ElapsedMilliseconds());
        Console.ReadLine();
    }
}

C++

#include "stdafx.h"

#using <mscorlib.dll>
using namespace System;
using namespace System::Diagnostics;

int main(array<System::String ^> ^args)
{
    String^ strSomeString = "";
    Stopwatch^ sw = gcnew Stopwatch;
    sw->Start();
    for (int i = 0;i< 1000 ;i++)
    {
        strSomeString +=  "Test ";
    }
    sw->Stop();
    Console::WriteLine(strSomeString);
    Console::WriteLine(sw->ElapsedMilliseconds);
    Console::ReadLine();
}

The compiled CIL

VB.NET

.method public static void  Main() cil managed
{
    .entrypoint
        .custom instance void
        [mscorlib]
        System.STAThreadAttribute::.ctor()=(01 00 00 00)
            // Code size       83 (0x53)
            .maxstack  2
            .locals init (
            [0] string strSomeString,
            [1] class [System]System.Diagnostics.Stopwatch sw,
            [2] int32 i, [3] int32 VB$CG$t_i4$S0 
            )
IL_0000: nop
IL_0001: ldstr  ""
IL_0006: stloc.0
IL_0007: newobj instance void
         [System]
         System.Diagnostics.Stopwatch::.ctor()
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: callvirt instance void
         [System]
         System.Diagnostics.Stopwatch::Start()
IL_0013: nop
IL_0014: ldc.i4.0
IL_0015: stloc.2
IL_0016: ldloc.0
IL_0017: ldstr  "Test "
IL_001c: call   string
             [mscorlib]
             System.String::Concat(string, string)
IL_0021: stloc.0
IL_0022: nop
IL_0023: ldloc.2
IL_0024: ldc.i4.1
IL_0025: add.ovf
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: ldc.i4   0x3e8
IL_002d: stloc.3
IL_002e: ldloc.3
IL_002f: ble.s    IL_0016
IL_0031: ldloc.1
IL_0032: callvirt instance void
         [System]
         System.Diagnostics.Stopwatch::Stop()
IL_0037: nop
IL_0038: ldloc.0
IL_0039: call     void
         [mscorlib]
         System.Console::WriteLine(string)
IL_003e: nop
IL_003f: ldloc.1
IL_0040: callvirt instance int64
         [System]
         System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0045: call     void
         [mscorlib]
         System.Console::WriteLine(int64)
IL_004a: nop
IL_004b: call     string
         [mscorlib]
         System.Console::ReadLine()
IL_0050: pop
IL_0051: nop
IL_0052:  ret
} // end of method Module1::Main

C#

.method private hidebysig static void  Main(string[] args) cil managed
{
    .entrypoint
        // Code size       87 (0x57)

        .maxstack  2
        .locals init (
        [0] string strSomeString,
        [1] class [System]System.Diagnostics.Stopwatch sw,
        [2] int32 i,
        [3] bool CS$4$0000 
        )
IL_0000:  nop
IL_0001:  ldstr    ""
IL_0006:  stloc.0
IL_0007:  newobj  instance void
          [System]
          System.Diagnostics.Stopwatch::.ctor()
IL_000c:  stloc.1
IL_000d:  ldloc.1
IL_000e:  callvirt instance void
          [System]
          System.Diagnostics.Stopwatch::Start()
IL_0013:  nop
IL_0014:  ldc.i4.0
IL_0015:  stloc.2
IL_0016:  br.s    IL_002a
IL_0018:  nop
IL_0019:  ldloc.0
IL_001a:  ldstr   "Test "
IL_001f:  call    string 
              [mscorlib]
              System.String::Concat(string,string)
IL_0024:  stloc.0
IL_0025:  nop
IL_0026:  ldloc.2
IL_0027:  ldc.i4.1
IL_0028:  add
IL_0029:  stloc.2
IL_002a:  ldloc.2
IL_002b:  ldc.i4   0x3e8
IL_0030:  clt
IL_0032:  stloc.3
IL_0033:  ldloc.3
IL_0034:  brtrue.s IL_0018
IL_0036:  ldloc.1
IL_0037:  callvirt instance void
          [System]
          System.Diagnostics.Stopwatch::Stop()
IL_003c:  nop
IL_003d:  ldloc.0
IL_003e:  call     void
          [mscorlib]
          System.Console::WriteLine(string)
IL_0043:  nop
IL_0044:  ldloc.1
IL_0045:  callvirt instance int64
          [System]
          System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_004a:  call  void
          [mscorlib]
          System.Console::WriteLine(int64)
IL_004f:  nop
IL_0050:  call   string
          [mscorlib]
          System.Console::ReadLine()
IL_0055:  pop
IL_0056:  ret
} // end of method Program::Main

J#

.method public hidebysig static void main(string[]args)cil managed
{
    .entrypoint
        // Code size       108 (0x6c)
        .maxstack  2
        .locals init
        (
        [0] string strSomeString,
        [1] class [System]System.Diagnostics.Stopwatch sw,
        [2] int32 i 
        )
IL_0000: ldtoken [vjslib]
    com.ms.vjsharp.lang.ObjectImpl
IL_0005: call    void
         [mscorlib]
         System.Runtime.CompilerServices.
             RuntimeHelpers::RunClassConstructor
             (
             valuetype [mscorlib]System.RuntimeTypeHandle
             )
IL_000a: ldstr  ""
IL_000f: stloc.0
IL_0010: newobj  instance void 
         [System]
         System.Diagnostics.Stopwatch::.ctor()
IL_0015: stloc.1
IL_0016: ldloc.1
IL_0017: callvirt instance void
         [System]
         System.Diagnostics.Stopwatch::Start()
IL_001c:  ldc.i4.0
IL_001d:  stloc.2
IL_001e:  br.s   IL_003f
IL_0020:  newobj instance void
          [vjslib]
          java.lang.StringBuffer::.ctor()
IL_0025:  ldloc.0
IL_0026:  callvirt instance class 
          [vjslib]
          java.lang.StringBuffer
              [vjslib]
              java.lang.StringBuffer::append(string)
IL_002b:  ldstr    "Test "
IL_0030:  callvirt instance class
              [vjslib]
              java.lang.StringBuffer
                  [vjslib]
                  java.lang.StringBuffer::append(string)
IL_0035:  callvirt instance string
          [vjslib]
          java.lang.StringBuffer::ToString()
IL_003a:  stloc.0
IL_003b:  ldloc.2
IL_003c:  ldc.i4.1
IL_003d:  add
IL_003e:  stloc.2
IL_003f:  ldloc.2
IL_0040:  ldc.i4   0x3e8
IL_0045:  blt.s    IL_0020
IL_0047:  ldloc.1
IL_0048:  callvirt instance void
          [System]
          System.Diagnostics.Stopwatch::Stop()
IL_004d: ldloc.0
IL_004e: call      void
         [mscorlib]
         System.Console::WriteLine(string)
IL_0053:  ldloc.1
IL_0054:  callvirt instance int64
          [System]
          System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0059:  call  void
          [mscorlib]
          System.Console::WriteLine(int64)
IL_005e:  call   string
          [mscorlib]
          System.Console::ReadLine()
IL_0063:  pop
IL_0064:  call  void
          [vjslib]
          com.ms.vjsharp.util.Utilities::cleanupAfterMainReturns()
IL_0069:  br.s  IL_006b
IL_006b: ret
} // end of method Program::main

C++

.method assembly static int32  main(string[] args) cil managed
{
    // Code size       83 (0x53)

    .maxstack  2
        .locals (
        [0] class [System]System.Diagnostics.Stopwatch sw,
        [1] string strSomeString,
        [2] int32 i 
        )
IL_0000:  ldnull
IL_0001:  stloc.1
IL_0002:  ldnull
IL_0003:  stloc.0
IL_0004:  ldstr      ""
IL_0009:  stloc.1
IL_000a:  newobj instance void
          [System]
          System.Diagnostics.Stopwatch::.ctor()
IL_000f:  stloc.0
IL_0010:  ldloc.0
IL_0011:  call    instance void
          [System]
          System.Diagnostics.Stopwatch::Start()
IL_0016:  ldc.i4.0
IL_0017:  stloc.2
IL_0018:  br.s       IL_001e
IL_001a:  ldloc.2
IL_001b:  ldc.i4.1
IL_001c:  add
IL_001d:  stloc.2
IL_001e:  ldloc.2
IL_001f:  ldc.i4     0x3e8
IL_0024:  bge.s      IL_0034
IL_0026:  ldloc.1
IL_0027:  ldstr   "Test "
IL_002c:  call     string
              [mscorlib]
              System.String::Concat
                  (
                  string,
                  string
                  )
IL_0031:  stloc.1
IL_0032:  br.s       IL_001a
IL_0034:  ldloc.0
IL_0035:  call   instance void
          [System]
          System.Diagnostics.Stopwatch::Stop()
IL_003a:  ldloc.1
IL_003b:  call   void
          [mscorlib]
          System.Console::WriteLine(string)
IL_0040:  ldloc.0
IL_0041:  call   instance int64
          [System]
          System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0046:  call    void
          [mscorlib]
          System.Console::WriteLine(int64)
IL_004b:  call   string
          [mscorlib]
          System.Console::ReadLine()
IL_0050:  pop
IL_0051:  ldc.i4.0
IL_0052:  ret
} // end of method 'Global Functions'::main

Points to note

  • There were slight differences in the output generated by the various compilers, especially at the start of each code segment.
  • VB and C# CIL look very similar.
  • I have put How the Compiler has declared all local variables in bold at the beginning of the method
  • The VB Console wizard generates a Module, instead of a class

Why does VB generate a Module?

There has been a lot of discussion within the VB developer community whether modules are an Object Oriented Programming (OOP) approach. It is a common misconception that Modules are a carry over from VB6 and should therefore be avoided at all costs. Although a complete discussion on this hot topic of conversation is not in the scope of this article, I will point out that Modules in VB.NET are indeed object-oriented. You should think of a module as a special class type that cannot be used to create objects. It can contain only shared members; it cannot contain instance members. Although every member of a module is implicitly shared, you'll experience a compile-time error if you add the Shared keyword to any one.

Modules don't exist in the other languages so what makes VB so special?

One of the exciting new additions to the C# language in .NET 2.0 is the addition of static classes. Static classes and class members are used to create data and functions that can be accessed without creating an instance of the class. Static class members can be used to separate data and behavior that is independent of any object identity: the data and functions do not change regardless of what happens to the object. Static classes can be used when there is no data or behavior in the class that depends on object identity. So static classes can be thought of C#'s answer to the VB's humble module.

Whats wrong with the code?

Although this code at first glance looks fairly optimum and is functioning and the code can be compiled without their respective compilers throwing up any nasty errors. The code in essence not truly .NET-based code. If you were to run the code one would notice that the simple string concatenation routines can take anywhere between 10 - 44 ms (Milliseconds) to complete irrespective of what language you choose to run. Can this be optimized and what other problems can you see?

In Part 2 I will illustrate how even a simple application like this one can be optimized.

Sources

For some background information for my article I used some information obtained from Wikipedia on the .NET Framework.

History

  • 06-10-06 Draft Version Part 1
  • 09-10-06 Slightly updated to improve formatting and changed title
  • 10-10-06 Updated with some more background information

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here