Aug 112015
 

If there is one place where developers feel least guilty about allocating large amounts of memory, it’s in the local variables of the method. After all, local variables are short lived and as the method execution is over, the call stack is wound down and the return value is popped. This frees all of them for garbage  collection. This developer assumption might hold true for most methods, but not for all.

Let’s consider a simple method first. It allocates a large  1 million element long integer array and  returns the length of the array.

    class Program
    {
        static void Main(string[] args)
        {
            var a = new Foo();
            Console.WriteLine(a.Bar());
        }
    }
    class Foo
    {
        public int Bar()
        {
            var arr = Enumerable.Repeat(1, 1000000).ToArray();
            return arr.Length;
        }
    }

Let’s compile the program and open it in WinDbg. The commands for doing that are

  • .symfix (fixes the symbol path)
  • sxe ld:clrjit.dll (telling the code to break when clrjit is loaded)
  • g (continuing execution till the clrdll is loaded)
  • .loadby sos clr (This loads the SOS managed debugging extension)
  • !bpmd (Break when Program.main is executed)

Looking at the IL code of the Foo.Bar method, it’s pretty straightforward. A 1 million element array is created and then the ldloc.0 instruction loads the local variable on the stack. After the method returns, the local variable pointer no longer exists and the garbage collector is free to reclaim the memory for other objects.

Foo.Bar

This works quite well, but imagine a scenario where you might need access to the local variable even after the method execution is over.  One scenario is when the method returns  a Func delegate instead of an integer.

class Foo
{
    public Func<int> BarFunc()
    {
        var arr = Enumerable.Repeat(1, 1000000).ToArray();
        return () => arr.Length;
    }
}

Though this method will always return the same result as the previous method, the CLR cannot make that assumption and mark the integer array for collection. Because there is no guarantee that the returned delegate will be executed immediately or even just once, the CLR has to maintain a reference to the local variable even after the method execution is completed. The compiler resolves this dilemma by promoting the local variable on to the heap as a field of an autogenerated Type. Let’s see the IL generated when this new method is called.

Foo.BarFunc

 

This IL is considerably different. The newobj instruction creates an object of a new Type c__DisplayClass1 which we never created. That is the type which the compiler autogenerated and used for storing the local variable. Since the new type lives on the heap it’s lifetime is guaranteed till the return delegate’s reference is held on by the calling method.  We can verify this by examining the managed Heap

NewAutogeneratedType

 

…and the object of the autogenerated type shows our local variable now as a field.

DumpObject

 

If we modify our main method a bit and store the resulting delegate into a class level field , we can see that the GC maintains an explicit root to the object. In essence the object lives till the application execution is completed. This is unnecessary memory usage by the application.

class Program
{
    private static Func<int> classLevelVariable;

    static void Main(string[] args)
    {
        var a = new Foo();
        classLevelVariable = a.BarFunc();
        Console.ReadLine();
        classLevelVariable();
    }
}

Finding GCRoots for the object, we see that Garbage collector can never collect this object.

gcroots

This particular scenario might seem trivial, but in a LINQ-heavy production application it is very easy to lose track of the methods that are creating closures.  Awareness about the promotion of local variables can help prevent memory leaks and improve application performance.

 

Aug 072015
 

Microsoft recently open sourced the CLR and the framework libraries and published them on Github. Though a non production version has been open sourced for a long time under the name Rotor or SSCLI, this time there were no half measures. It gives the community the opportunity to raise issues and also fix them by creating pull requests.

The journey from source to executable code has two phases – first the compiler compiles the source code into the Intermediate Language (MSIL) and then the execution engine (CLR) converts the IL to machine specific assembly instructions. This allows .NET code to be executable across platforms and also be language agnostic as the runtime only understands MSIL.

When the program is executed, the CLR reads the type information from the assembly and creates in-memory structures to represent them. The main structures that represent a type at runtime are the MethodTable and the EEClass. The MethodTable contains “hot” data which is frequently accessed by the runtime to resolve method calls and for garbage collection. The EEClass on the other hand is a cold structure which has detailed structural information about the type including its fields and methods. This is used in Reflection. The main reason for splitting these structures is to optimize performance and keep the frequently accessed fields in as small a data structure  as possible. Every non-generic type has its own copy of the MethodTable and the EEClass, and the pointer to the MethodTable is stored in the first memory address location of each object. We can observe this by loading the SOS managed debugging extension in WinDbg

methodtablelayout

 

The DumpHeap command gives us the information of our type along with the the addresses of all the objects for the type. Using the WinDbg command dq to read the address at the memory address we see that the first memory address points to its MethodTable. There is another structure called the SyncBlock which exists at a negative offset to the MethodTable in the memory. This structure handles the thread synchronization information for the object.

This diagram from the SSCLI Essentials Book explains the relationship between various data structures very clearly.

objectlayout

As you can see the object header points to the MethodTable which in turns point to the EEClassSince the EEClass is not frequently used during runtime, this extra level of indirection doesn’t hurt performance. The MethodTable itself is followed by a call table – a table which contains the addresses of the virtual and non virtual methods to be executed for the type. Since the dispatch table is laid out at a fixed offset from the MethodTablethere is no pointer indirection to access the right method to call. One more thing to be noted about the CLR is that everything is loaded only when it’s needed to be executed. This holds true for both types and methods. When the CLR executes a method which creates another type, it creates the memory structures for the new type. However, even then the methods themselves are not compiled till the absolute last moment when they are needed to be executed.

In the above diagram, you can see the MethodTable vtable pointing to a thunk, which is called a prestub in .NET. When the method is first called, the prestub calls the JIT compiler. The JIT compiler is responsible for reading the MSIL opcode and generating the processor specific assembly code. Once the JIT Compilation is done, the address at which the compiled code resides is backpatched on to the call table. Subsequent calls to the method are directly executed without having to go through the compilation phase

Loading the MethodTable for our calculator type using the command DumpMT with the MD switch which also loads the MethodDescriptors.

methodtable

At this stage in the application execution, the object for Calculator class has been created but the AddTwoNumbers method hasn’t been executed yet. So the MethodDesc table shows that only the constructor method has been jitted but not the AddTwoNumbers method.  Seeing the MethodDescriptors for both the methods using the command !DumpMD

MethodDescriptors

 

The Constructor method now contains a code address, but the AddTwoNumbers doesn’t have code yet. Let’s step forward and see what happens after the method is jitted. Now the code address is replaced by an actual memory address which contains our machine specific assembly code. The next time this method is called, this assembly code will be directly executed.

afterjitting

To view the assembly, use the !u command followed by the code address.  Like in most languages, there are two registers ebp and esp to keep track of each stackframe. During a method call a new stackframe is created and the ebp maintains a pointer to the base of the stack. As code executes the esp register keeps track of how the stack grows and once execution completes, the stack is cleared and the epb value is popped.

assemblyccode

 

Now lets look at this from a code level. Detailed building and debugging instructions are given at the coreclr repo. The MethodTableBuilder class contains the method which loads the types. You could put a breakpoint here but it will keep breaking when system types are loading. To avoid this , put a breakpoint in the RunMain method in assembly.cpp class, and once it breaks then put the breakpoint in the CreateTypeHandle method. This will start breaking on your custom type creation.

createtypehandle

Below is the simple Calculator class code that we are calling. I just used the name of the executable as a Command Argument to run CoreRun.exe in the coreclr solution (Detailed instructions given in Github repo)

DebugCode

 

Now for the fun part – we start debugging the solution. The first step (after loading allocators) is to make sure all parent types are loaded. Since our type doesn’t inherit any class, its parent is System.Object. Once the Parent type is found (can’t be an interface, only a concrete type), it’s method table is returned to the MethodTableBuilder

loadparenttype

 

Then there are some additional checks to handle cases like enums, Generic method, excplicit layouts etc. I’ll skip over them for brevity. At this time we have started to build the MethodTable but not the EEClass. That is done in the next step.

eeclass

 

At this  stage, the CLR checks if the type implements any interfaces. Since interface calls are a bit more complex, there needs to be a relationship from the interface vtable to the implementing type, the calls are mapped using a slot map maintained on the implementing type’s MethodTable which maps it to the vtable slot on the interface. Since our Calculator Class doesn’t inherit interfaces, it will totally skip this block.

interfaces

Now we go into the final and most crucial method which will finally return the TypeHandle. If this method succeeds, then our type has been successfully loaded into memory.

bmtfinalmethod

The first thing the BuildMethodTableThrowing class does is to walk up the inheritance hierarchy and load the parent type. This holds for all methods except interfaces. An interface’s vtable will not contain the System.Object’s method calls. So the method builder will simply set the parent Type to null if the type being loaded is an interface.

interfaceInBuilder

After this, the method makes sure the type in question is not a value type, enum, remoting type, or being called via COM Interop. All this would be loaded differently then simple reference types deriving directly from System.Object. Then the MethodImpl attributes are checked since they impact how a type a loaded. Our Calculator class just skips over these checks. The next method is EnumerateClassMethods which iterates through all the methods and adds them to the MethodTable.

Now that the implemented methods are added to the MethodTable, we need to also add the parent type’s method calls to the current vtable. this is done by the methods ImportParentMethods, AllocateWorkingSlotTables and CopyParentVtable in the MethodBuilder class. Here virtual methods have to be handled differently since they can be overridden by the current type. For non virtual methods, a direct entry to the methods implemented by the Parent type should suffice.

First the maximum possible vtable size is computed. Next a temporary table is allocated for the maximum possible size of the table

maxVTableSize

Then the parent vTable methods are loaded to the Calculator type.

CopyParentVtable

After the Parent methods are added, the current type’s methods are added. We just have two methods – the Constructor and the AddTwoNumbers method. Here first the Virtual Methods are added and then the Non-Virtual ones. Since we didn’t define a custom constructor, it will just inherit the Default constructor and add it in the vtable. Once all virtual methods are added, the remaining methods will get the non vtable slots.

constructorCurrentType

Now that the type’s methods have been completely been loaded, the MethodDescriptors are  created. However the code for the methods is not called even once so it will simply be pointing to a stub waiting to be JIT compiled on execution. After this stage the remaining fields are placed in the MethodTable and some additional integrity checks are done. Finally the Type is loaded and is ready to be published

finalMethodTable

 

May 082010
 

When I looked at the first post in this series, I realized had jumped the gun a bit by going straight to generics and didn’t do enough justice to the fundamentals. So in this post, I have made an effort to go back to the basics.

The CLR (Common Language Runtime) is the heart of .NET. It’s the virtual execution system responsible for converting the platform neutral CIL (Common Intermediate Language) into platform specific and optimized code. The CLR provides services like memory management, garbage collection, exception handling and type verification. Thus it allows language designers to concentrate solely on outputting good CIL and provides a uniform API to allow language interoperability.

The only truth in .NET is the assembly code which is the final product. All the rest are virtual constraints enforced by the execution system in a very robust manner. For e.g. A memory address declared as int cannot take a string value, not because the memory is not able to take the value, but rather the CLR makes sure the values conform to the types declared – a feature called type verification. Usually this happens at the compilation level, but still rechecked at runtime.

Microsoft released the code of the CLI (Common Language Infrastructure) under the name SSCLI (Shared Source CLI). It can be downloaded here. Joel Pobar and others wrote a great book about it. Unfortunately the 2.0 version is still a draft.

Type safety is the most important aspect of .NET programming and a lot of thought went into it. A type can be thought of as a contract which the objects need to conform to. For e.g. in the following code, the Person class is a type – which is supposed to have five public variables and one method. Any object that claims itself to be a person type must necessarily fulfill this contract – or the CLR will reject it during runtime. While working with the more mature compilers like C# and VC++, these checks are already done while converting the code to CIL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person
{
    public string _name;
    public int _ssn;
    public char _middleName;
    public decimal _phoneNumber;
    public char _bloodGroup;
 
    public Person(string name, int ssn, char middleName, decimal phoneNumber, char bloodGroup)
    {
        this._name = name;
        this._ssn = ssn;
        this._middleName = middleName;
        this._phoneNumber = phoneNumber;
        this._bloodGroup = bloodGroup;
    }
 
    public string GetSomeDetails()
    {
        return String.Empty;
    }
}
 
static void Main(string[] args)
{
    Person _p = new Person("John Doe", 4454353, 'B', 324242432, 'O');
    _p.GetSomeDetails();
}

The code for object class can be found at sscli20\clr\src\vm\object.h in the SSCLI code and the Type class at \sscli\clr\src\vm\typehandle.h. The Type class which is extensively used in Reflection for reading the type metadata is a wrapper for this TypeHandle class. Lets look at the underlying code for some familiar methods of the TypeHandle, some of which you see in Type Class. So every object that declares itself of this type, indirectly points to the data structure to define itself.

1
2
3
4
5
6
7
8
    BOOL IsEnum() const;
    BOOL IsFnPtrType() const;
    inline MethodTable* AsMethodTable() const;
    inline TypeDesc* AsTypeDesc() const;
    BOOL IsValueType() const;
    BOOL IsInterface() const;
    BOOL IsAbstract() const;
    WORD GetNumVirtuals() const;

The MethodTable that you see is the datastructure which contains the frequently used fields needed to call the methods. Along with another structure called EEClass, it defines the type identity of an object in .NET. The difference is that the MethodTable contains data that is frequently accessed by the runtime while the EEClass is a larger store of type metadata. This metadata helps querying type information and dynamically invoking methods using the Reflection API. Using the SOS dll’s DumpHeap command, the address of all the types can be gotten and used to see the EEClass and MethodTables. Lets examine the Person type in the above example.

.load SOS
extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\SOS.dll loaded
 
!DumpHeap -type Person
PDB symbol for mscorwks.dll not loaded
 Address       MT     Size
020a34d4 001530f0       36
total 1 objects
Statistics:
      MT    Count    TotalSize Class Name
001530f0        1           36 DebugApp.Person
Total 1 objects
 
//Getting the address and using the DumpObj command
 
!DumpObj 020a34d4
Name: DebugApp.Person
MethodTable: 001530f0
EEClass: 001513d0
Size: 36(0x24) bytes
 (D:\Ganesh Ranganathan\Documents\Visual Studio 2005\Projects\DebugApp\DebugApp\bin\Debug\DebugApp.exe)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
70d00b68  4000001        4        System.String  0 instance 020a34b0 _name
70d02db4  4000002        8         System.Int32  1 instance  4454353 _ssn
70d01848  4000003        c          System.Char  1 instance       42 _middleName
70cd7f00  4000004       10       System.Decimal  1 instance 020a34e4 _phoneNumber
70d01848  4000005        e          System.Char  1 instance       4f _bloodGroup

Lets dissect this output. First the DumpObj command lists both the MethodTable and the EEClass address and the proceeds to list the fields . See how the value column lists the direct value for the int and char fields while the address is listed for the reference type string. However the bigger decimal type, which actually is a struct and hence a value type, displays the reference. Though SOS displays the reference, we can observe that the address is actually an offset from the object header, which means that it is still stored by value and not the reference. Looking at the memory window for the string and decimal fields’s address gives their original values.


Viewing the object in the memory window shows a pattern of how the runtime stores the values in memory. The object starts with a reference to the MethodTable, then the fields are lined up. It can be observed that there is a difference in how the runtime stores the values of the fields and how we defined them. For e.g. The two character fields are pushed together in spite of not being declared sequentially. This is done to save memory and the runtime is able to manage this situation because all it stores is the memory offset of the fields from the header. To avoid this behavior, types can be decorated with the [StructLayout(LayoutKind.Sequential)] attribute, often used while marshalling data out of managed code, because unmanaged code cant deal with such vagaries. You should also pin your objects, especially references while passing them to unmanaged code, because the runtime keeps moving the memory blocks around.

Now lets look at the MethodTable through SOS. As you can see, every type also inherits the methods from its parent, in this case System.Object. The MethodTable also contains a pointer to the EEClass. When it is laid out during type creation, the method points to a temporary piece of code called a thunk. The thunk in turn calls the JIT compiler and asks it to compile the method. This lazy compilation works wonders for performance and the memory footprint. Once the method is compiled the JIT updates the method to point to the compiled code instead of the thunk.

!DumpMT -MD 001230f0
EEClass: 001213d0
Module: 00122c5c
Name: DebugApp.Person
mdToken: 02000005  (D:\Ganesh Ranganathan\Documents\Visual Studio 2005\Projects\DebugApp\DebugApp\bin\Debug\DebugApp.exe)
BaseSize: 0x24
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
70c56aa0   70ad4a34   PreJIT System.Object.ToString()
70c56ac0   70ad4a3c   PreJIT System.Object.Equals(System.Object)
70c56b30   70ad4a6c   PreJIT System.Object.GetHashCode()
70cc7550   70ad4a90   PreJIT System.Object.Finalize()
0012c030   001230c4      JIT DebugApp.Person..ctor(System.String, Int32, Char, System.Decimal, Char)
0012c038   001230d4     NONE DebugApp.Person.GetSomeDetails()

You can see the JIT column says none for the GetSomeDetails method and thats because it hasnt been called yet. After its called for the first time, the method is JIT compiled and the MethodDesc shows the code address where the compiled code can be found. Note however, that the MethodDesc is not the usual route for the runtime to execute methods, it is rather done directly. Only when the method is invoked by its name, is the MethodDesc required.

!DumpMT -MD 001230f0
EEClass: 001213d0
Module: 00122c5c
Name: DebugApp.Person
mdToken: 02000005  (D:\Ganesh Ranganathan\Documents\Visual Studio 2005\Projects\DebugApp\DebugApp\bin\Debug\DebugApp.exe)
BaseSize: 0x24
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
70c56aa0   70ad4a34   PreJIT System.Object.ToString()
70c56ac0   70ad4a3c   PreJIT System.Object.Equals(System.Object)
70c56b30   70ad4a6c   PreJIT System.Object.GetHashCode()
70cc7550   70ad4a90   PreJIT System.Object.Finalize()
0012c030   001230c4      JIT DebugApp.Person..ctor(System.String, Int32, Char, System.Decimal, Char)
0012c038   001230d4      JIT DebugApp.Person.GetSomeDetails()
 
!DumpMD 001230d4
Method Name: DebugApp.Person.GetSomeDetails()
Class: 001213d0
MethodTable: 001230f0
mdToken: 06000007
Module: 00122c5c
IsJitted: yes
CodeAddr: 009f01c8

In this post we saw basic functioning of the CLR and how it creates and stores internal data structures to facilitate code execution at the same time abstracting away all the gory details from the developer and allowing him to solely concentrate on his applications. Below the hood everything is simply memory addresses pointing to each other and a bunch of assembly code. To give it such a high degree of structure and definition is by no means an easy task. Hats off to the developers in the .NET and Java teams!! Hope I am able to reach their skill levels one day. 🙂

Mar 082010
 

I have started this series of posts dedicated to exploring the internals of the .NET framework and runtime. To get started on probing of internals of .NET, we need a debugging extension called SOS (Sons of Strike). Visual Studio comes packaged with this assembly (SOS.dll). The first posts is about one of the most important features of .NET 2.0 over 1.1 – Generics or “Parametric Polymorphism”. Wikipedia defines Generics as “a style of computer programming in which algorithms are written in terms of to-be-specified-later types that are then instantiated when needed for specific types provided as parameters”.

First, we need to set up Visual studio  to use the Sons of Strike. A few simple steps mentioned below are needed to accomplish this:-

  • Enable Unmanaged Debugging in the Project Properties

  • Set the symbol server to  *C:\localcache*http://msdl.microsoft.com/download/symbols. The symbol server can be set by going to Tools -> Options-> Debugging. This step allows Visual studio to download the symbols needed for debugging from the Microsoft server.
  • Set a breakpoint in the code and press F5 to start the program in debug mode. Once the application hits the breakpoint, load the SOS.dll using the command .load SOS in the Immediate Window.

Now we are ready to use the debugging extension. And SOS is also compatible with debugging tools like WinDbg in case you are comfortable with using that. Click here for a list of SOS commands.

Now, lets see the code that we would use for seeing how generics work. It consists of a Generic Type Customer which contains a list of the generic type and a public integer property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Employee
    {
 
        public int EmployeeID;
        public static int age;
        public static string Name;
    }
 
    class Customer<T>
    {
        public int CustomerID;
        List<T> _innerList = new List<T>();
 
        public List<T> ListItems { get { return _innerList; } }
 
        public void AddItems(T _addObject)
        {
            _innerList.Add(_addObject);
        }
    }

The main method to call this code is below. We create the Customer class in three types – a string, an integer and a custom Employee type. Of these the string and Employee are reference types while integer is a primitive value type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
        {
            Customer<string> _cxStringObj = new Customer<string>();
            Customer<int> _cxIntObj = new Customer<int>();
            Customer<Employee> _cxEmpObj = new Customer<Employee>();
 
            _cxStringObj.CustomerID = 34;
            _cxStringObj.AddItems("Ganesh");
 
            _cxIntObj.CustomerID = 23;
            _cxIntObj.AddItems(43);
 
            _cxEmpObj.CustomerID = 34;
            _cxEmpObj.AddItems(new Employee());
        }

Every .NET class has two main data structures it uses to maintain type identity – the MethodTable and EEClass. Every object has a pointer to its MethodTable, and this methodtable contains a pointer to the EEClass. In .NET 1.1, the EE class was the strongest identifier of Type Identity of an object, however with .NET 2.0 and the implementation of Generics, Generic Types can share the same EEClass, and the MethodTable takes on the mantle of being the unique type identifier.

There are two main ways of implementing Generics which were proposed by the researchers who were working at Microsoft (This paper provides details of the implementation).

  • Expansion: In this way, whenever the runtime encounters a generic type, the code could be generated on the fly for the type being called. For e.g. in our code, when the object Customer<string> was encountered, it could have expanded into the string representation of the object. When the type Customer<int> was encountered, a similar expansion can be done for it.
  • Code sharing: In this approach only a single instantiation is done for the generic type, and it is reused for all the calls.

The method chosen in the end was a mixture of both these approaches. Generic types share as much code as possible They contain the same EEClass as mentioned before, but also have distinct MethodTables. Even though they have distinct MethodTables, code is shared between compatible types (all reference types).

Fire up the debugger and load the SOS.dll. To see the particular instances of the type on the heap give the Command DumpHeap -type . Once all the three objects have been created, lets see the result of this command.

!DumpHeap -type Customer
 Address       MT     Size
01ed35cc 002331d8       16
01ed3604 00233284       16
01ed3638 0023336c       16
total 3 objects
Statistics:
      MT    Count    TotalSize Class Name
0023336c        1           16 GenericsTry.Customer`1[[GenericsTry.Employee, GenericsTry]]
00233284        1           16 GenericsTry.Customer`1[[System.Int32, mscorlib]]
002331d8        1           16 GenericsTry.Customer`1[[System.String, mscorlib]]
Total 3 objects

As you can see from the MT column, all the three objects have different MethodTables. Lets see the MethodTables for each of the three type instantiations.

The command is DumpMT -MD . First the Employee type. Think of the MethodTable as an extension to a conventional vtable which simply contains the method pointers. The MethodTable’s vtable points to 7 slots, 4 of which are inherited from the parent Object class and three which are defined by the object itself. If you see the JIT status, there is a YES and NONE. The NONE means the method has never been called by the JIT Compiler and it points to a temporary stub. The first time the method is called, it is compiled on the fly and the temporary stub is replaced with the actual method pointer. To optimize performance, JIT defers execution and compilation till as late as possible.

!DumpMT -MD 0023336c
EEClass: 00231420
Module: 00232c5c
Name: GenericsTry.Customer`1[[GenericsTry.Employee, GenericsTry]]
mdToken: 02000004  (Projects\GenericsTry\GenericsTry\bin\Debug\GenericsTry.exe)
BaseSize: 0x10
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
70766aa0   705e4a34   PreJIT System.Object.ToString()
70766ac0   705e4a3c   PreJIT System.Object.Equals(System.Object)
70766b30   705e4a6c   PreJIT System.Object.GetHashCode()
707d7550   705e4a90   PreJIT System.Object.Finalize()
0023c058   0023313c     NONE GenericsTry.Customer`1[[System.__Canon, mscorlib]].get_ListItems()
0023c060   00233148     NONE GenericsTry.Customer`1[[System.__Canon, mscorlib]].AddItems(System.__Canon)
0023c068   00233154      JIT GenericsTry.Customer`1[[System.__Canon, mscorlib]]..ctor()

Lets see the MethodTable for the integer type. As you can see the EEClass for the Integer and Employee types are not same, this is because they are not compatible types and hence no code sharing can take place

!DumpMT -MD 00233284
EEClass: 002314b4
Module: 00232c5c
Name: GenericsTry.Customer`1[[System.Int32, mscorlib]]
mdToken: 02000004  (\GenericsTry\GenericsTry\bin\Debug\GenericsTry.exe)
BaseSize: 0x10
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
70766aa0   705e4a34   PreJIT System.Object.ToString()
70766ac0   705e4a3c   PreJIT System.Object.Equals(System.Object)
70766b30   705e4a6c   PreJIT System.Object.GetHashCode()
707d7550   705e4a90   PreJIT System.Object.Finalize()
0023c080   00233254     NONE GenericsTry.Customer`1[[System.Int32, mscorlib]].get_ListItems()
0023c088   00233260     NONE GenericsTry.Customer`1[[System.Int32, mscorlib]].AddItems(Int32)
0023c090   0023326c      JIT GenericsTry.Customer`1[[System.Int32, mscorlib]]..ctor()

The last one is the string type. This brings us to interesting conclusions. Compare the first MethodTable with the last one. Both are reference types and hence compatible with other. It can be observed that the string and Employee tables have the same EEClass 00231420. The integer type however has the EEClass 002314b4. There is no way for primitive value types to share code with each other since they differ in size. For e.g. If we created a bool type for Customer class, it would still have a different EEClass and MethodTables.

!DumpMT -MD 002331d8
EEClass: 00231420
Module: 00232c5c
Name: GenericsTry.Customer`1[[System.String, mscorlib]]
mdToken: 02000004  (\GenericsTry\GenericsTry\bin\Debug\GenericsTry.exe)
BaseSize: 0x10
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
70766aa0   705e4a34   PreJIT System.Object.ToString()
70766ac0   705e4a3c   PreJIT System.Object.Equals(System.Object)
70766b30   705e4a6c   PreJIT System.Object.GetHashCode()
707d7550   705e4a90   PreJIT System.Object.Finalize()
0023c058   0023313c     NONE GenericsTry.Customer`1[[System.__Canon, mscorlib]].get_ListItems()
0023c060   00233148     NONE GenericsTry.Customer`1[[System.__Canon, mscorlib]].AddItems(System.__Canon)
0023c068   00233154      JIT GenericsTry.Customer`1[[System.__Canon, mscorlib]]..ctor()

One more thing of notice is the Method Descriptors for the first and third types. The AddItems method in Customer class has the same Method descriptor for both the string and Employee implementation. So even after a MethodTable is duplicated, both those entries still point to the same Method Description. Even the name has the type System.__Canon, which is dynamically replaced by the type on which the method is called. This is an illustration of the second approach of code sharing given by the researchers from Microsoft.

Once the code for AddItems method is called for the first time, it is JIT Compiled and assigned a code address. The Method Descriptor holds this code address which is assembly code and can be viewed by the SOS command !u.  Till the method is Jitted, there is junk value in this field on the MethodDescriptor.

!DumpMD 00233148
Method Name: GenericsTry.Customer`1[[System.__Canon, mscorlib]].AddItems(System.__Canon)
Class: 00231420
MethodTable: 0023316c
mdToken: 06000005
Module: 00232c5c
IsJitted: yes
CodeAddr: 00aa0298

Running the CodeAddress would give you a bunch of assembly code that is executed when the method is called.

!u 00aa0298
Normal JIT generated code
GenericsTry.Customer`1[[System.__Canon, mscorlib]].AddItems(System.__Canon)
Begin 00aa0298, size 42
>>> 00AA0298 55               push        ebp
00AA0299 8BEC             mov         ebp,esp
00AA029B 57               push        edi
00AA029C 56               push        esi
00AA029D 53               push        ebx
00AA029E 83EC34           sub         esp,34h
00AA02A1 33C0             xor         eax,eax
00AA02A3 8945F0           mov         dword ptr [ebp-10h],eax
00AA02A6 33C0             xor         eax,eax
00AA02A8 8945E4           mov         dword ptr [ebp-1Ch],eax
00AA02AB 894DC4           mov         dword ptr [ebp-3Ch],ecx
00AA02AE 8955C0           mov         dword ptr [ebp-40h],edx
00AA02B1 833D142E230000   cmp         dword ptr ds:[00232E14h],0
00AA02B8 7405             je          00AA02BF
00AA02BA E8BABC3571       call        71DFBF79 (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
00AA02BF 90               nop
00AA02C0 8B45C4           mov         eax,dword ptr [ebp-3Ch]
00AA02C3 8B4804           mov         ecx,dword ptr [eax+4]
00AA02C6 8B55C0           mov         edx,dword ptr [ebp-40h]
00AA02C9 3909             cmp         dword ptr [ecx],ecx
00AA02CB E8E0F1D16F       call        707BF4B0 (System.Collections.Generic.List`1[[System.__Canon, mscorlib]].Add(System.__Canon), mdToken: 060019f1)
00AA02D0 90               nop
00AA02D1 90               nop
00AA02D2 8D65F4           lea         esp,[ebp-0Ch]
00AA02D5 5B               pop         ebx
00AA02D6 5E               pop         esi
00AA02D7 5F               pop         edi
00AA02D8 5D               pop         ebp
00AA02D9 C3               ret

In this post, we have looked at how the CLR treats Generics and how it takes care of performance implications of Generics code by sharing as much code as possible without having to needlessly box and unbox the types being passed to the Generic classes.

Oct 202009
 

All .NET code irrespective of the language it is written in, finally is stored in MSIL or CIL or just IL (phew!!). IL can be considered analogous to the Java bytecode. This IL is translated to executable machine code at runtime by the CLR (Common Language Runtime). IL is a stack based intermediate between machine code and high level language.

IL plays a major role in one of the biggest USPs of .NET – its platform independant nature. It neither depends on the platform nor the operating system. This makes .NET code highly portable in nature. All the target user needs is the CLR for his platform and OS. IL can be viewed using IL Disassembler, a tool distributed with Visual studio. IL disassembler can provide valuable insights on how the compiler works behind the scenes and makes life easier for us. Kudos to Visual Studio!!

ildasm

IL is almost human readable, the ldarg instructions are for loading the arguments for the method. The callvirt is for calling a function. The NOP instruction here is scattered through this IL code because it was taken from a debug build assembly. This instruction helps in debugging with features like “edit and continue” which is invaluable for us developers.

Now that we have seen IL, lets see how this actually continues on its journey from high-level languages to low-level machine code. This is done by the CLR using something called JIT (Just in Time) compilation. Just in Time compilation compiles code only when it is needed, i.e. just before it detects the call to the method. This method is highly optimized and is amazing in enhancing performance of the application. If the CLR compiled the whole assembly into native code before starting, it would be overkill, since there are good chances that many of the application’s methods might not be needed at runtime.

How does it do it? CLR uses a “stub” for every method which is always executed first before the method call. This stub calls the JIT compiler which verifies the code and transalated it into machine code. Now this machine code is not discarded once the method is done executing. It is saved in the memory and after that point the method call would not hit the stub but rather the saved native code. In short this is Just in time compilation.

But the JIT compiler’s role is not limited merely to compile IL to native code and shift memory instructions, it performs a host of other functions as well, like:-

  • Verification: JIT goes through the IL code of the method before transalating it into machine code and make sure there is nothing wrong with the code, In case there its, we would end up with an exception. 🙁
  • Optimization: The JIT compiler actually tweaks our code for us. It identifies calls to short methods and copies them to the calling method which could be faster than passing arguments. Its like a developer standing behind you and doing your code review, without claiming the credit!! The real power of optimization can be seen by difference in execution times of the debug and release builds. I remember an OP on MSDN forums found a 30 fold difference between debug and build. This can be extremely useful for loops with a lot of iterations.
  • Claiming it back: Sometimes, when memory is running low, the JIT compiler will free some of the memory occupied by the native code and place back the stub on the freed method. Any subsequent calls must be recompiled.

It is amazing how such complex processing is done behind the scenes to ensure that the developer never has to care about how objects are created (it cant be ignored totally though). This allows him/her to concentrate more on his application logic to come up with better code.