Learning .NET Core Backwards: IL

If you haven’t read the Introduction to my backwards learnings, start by doing that!

With this, why don’t we jump into it? I wrote the following il code into a file I named noop.il.

The corresponding il code for Mono can be found in Appendix A.

Let’s walk through the interesting bits:

.assembly 'noop' {}


The biggest unit of organisation in a clr application is the assembly. We might get to learn more about assemblies later, but for now, we only need to know that our code lives in an assembly. Here, we declare this assembly, name it noop, and don’t give any other options.

.module noop


Assemblies contain, among other things, modules, which contain the actual code we want to run. The most common case is that all code in one assembly is also in one module, but this is not necessary.

In our case, we happen to use the same for the module as the assembly. It could have been called anything, but this is fine.

.class private sealed abstract auto ansi beforefieldinit
Program extends [mscorlib]System.Object


Then we define our Program class! This is a mouthful of il, but if you slow down it’s not so bad.

The class is private (which the clr allows for the class containing the entrypoint.) It is also sealed and abstract which might sound weird: one is saying “You can’t inherit from this!”, and the other is saying “You must inherit from this!”. Of course, this has a natural interpretation: this is what in C# world would have been called a static class. There can be no instances of it, because to create instances from it, one would have to first inherit it, which is disallowed.

The auto keyword means that the jit compiler has some freedom in how to structure the data in the object. I don’t know what ansi beforefieldinit means, but I also don’t care very much about it.

The final thing we can see is that the Program class inherits from System.Object: this is a requirement. Everything must, eventually, inherit from System.Object.

This also reveals something about namespaces in the clr: they aren’t. Namespaces are a purely aesthetic feature in C# and F#; they are just a way to automatically add a string to every type name you make. So for example, if you define a type called Object inside a namespace System block, what actually happens in the IL code is that a type called System.Object gets defined.

Moving on!

.method private static hidebysig default
void Main (string[] args)
cil managed
{
.entrypoint
.maxstack 8
ret
}


The Main method is largely self-explainatory. We will get into some of the details surrounding it later on.

Appendix A

noop.il

.assembly extern mscorlib
{
.ver 4:0:0:0
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
}
.assembly 'noop' {}
.module noop

.class private sealed abstract auto ansi beforefieldinit
Program extends [mscorlib]System.Object
{
.method private static hidebysig default
void Main (string[] args)
cil managed
{
.entrypoint
.maxstack 8
ret
}
}


helloworld.il

.assembly extern mscorlib
{
.ver 4:0:0:0
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
}
.assembly 'helloworld' {}
.module helloworld

.class private sealed abstract auto ansi beforefieldinit
Program extends [mscorlib]System.Object
{
.method private static hidebysig default
void Main (string[] args)
cil managed
{
.entrypoint
.maxstack 8
ldstr "Hello, World!"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}