CIL programming tutorial – The Basics Part I

Some days ago I realized that it will be 2 years now since I’ve published my article Reweaving IL code with Mono.Cecil on Codeproject and one thing I wanted to do is to write tutorials that teaches coding in the Common Intermediate Language.

Why create another tutorials about IL coding you ask? Because while there are some good ones out there, I wanted to create a series of tutorials with a practical approach to CIL programming with the learning by doing approach 🙂

Getting started

For this tutorial you will need the IL Support Visual Studio extension which can be downloaded from here.

Creating your first IL code

After installing the IL Support extension, create a new project and select the Console application with IL Support template from the Templates / Visual C# / IL Support folder.

After the project is created, let’s look at these files:

  • Program.cs
  • Program.il

As you would expect, the Program.cs contains the good old entry point for the console application and the .il will contain the CIL code. These files also have an example that shows how to use the IL code in the application.

In the Program.cs you have the following method declaration as well:

[MethodImpl(MethodImplOptions.ForwardRef)]
public static extern int Square(int number);

In this declaration two things are necessary regardless the function signature:

  • the extern keyword, which tells the compiler that the method is declared somewhere else
  • and the MethodImplAttribute with the MethodImplOptions.ForwardRef tells that it will be defined later on, so don’t look for it in this specific dll or somewhere else.

Now let’s see the IL code which takes an integer input and returns its squared value:

.class public Tutorial01.Program
{
    .method public static int32 Square(int32 number) cil managed
    {
        .maxstack 2
        ldarg.0
        dup
        mul
        ret
    }
}

So what does the previous IL code do? The instructions mean the following:

  • ldarg.0 loads the 0th argument to the evaluation stack
  • dup duplicates the value at the top of the stack and pushes the duplicated value onto the stack
  • mul pops two values from the stack, calculates their product and pushes it onto the stack
  • ret returns the top value of the stack (number^2)

Before continuing let’s repeat after me: IL coding is easy. While this code can appear to be hard, in fact it is very simple after you accept that IL is not a low-level equivalent of C#, it is a stack based programming language with OOP capabilites.

Next thing you need to know about is the evaluation stack. This is where every data needs to be loaded before you can do anything to them. Be it an arithmetic instruction’s data or the arguments when calling a function first you have to load them to the evaluation stack.

What you need to keep in mind is how the evaluation stack looks like when an operation is executed, so the code won’t break because the evaluation stack is messed up. At first it could help a lot if you comment the evaluation stack values at each line, such as (if number is 9 for example):

ldarg.0  // [9]
dup      // [9,9]
mul      // [81]
ret      // []

Second example – Hello World

Let’s see another example, the obligatory hello world:

.method public static void HelloWorld() cil managed
{
    .maxstack 1
    ldstr "Hello World"
    call void [mscorlib]System.Console::WriteLine(string)
    ret
}

This is even simpler than the first example as it contains three operations:

  • ldstr “HelloWorld” – Load the string “Hello World”
  • call …WriteLine – Calls the console writeline function
  • ret – return from the function

Remember, that you will need the ret instruction even if the function does not return a value, otherwise the program will throw a System.InvalidProgramException when it tries to execute the function.

Basic instructions

Calling a function

The signature is basically: call []::()
Its quite verbose, but at the same time very straightforward. One thing to remember is that the namespaces in the .NET framework can span into multiple dlls, however on the msdn site, the assembly is also listed at every class reference page.

Loading instructions

Load argument (ldarg):

  • ldarg.0
  • ldarg.1
  • ldarg.2
  • ldarg.3
  • ldarg.s

Load constant (ldc) / 32 bit integers

  • ldc.i4.0
  • ldc.i4.1
  • ….
  • ldc.i4.8
  • ldc.i4.s
  • ldc.i4

Here you can see a lot of variants for loading an argument or an integer value. In a later tutorial there will be more about this.

Arithmetic instructions

  • add – addition
  • div – division
  • mul – multiplication
  • sub – substraction

All of the four of these instructions require 2 values at the stack to work (they will use the two value at the top of the stack) and the result will be pushed onto the stack.

Other

dup – duplicates the topmost value of the evaluation stack and pushes it onto the stack.

A full list of the IL instructions can be found here and hopefully in the later tutorials will cover most of them.

Loading arguments example

You must have noticed in the above list the ldarg.0, ldarg.1 etc. shortcuts and the generic ldarg.s instuctions. The reasons for using shortcuts are to have smaller code and faster JIT compilation, as the ldarg.0 instruction takes up only 1 byte, while ldarg.s takes up 2 bytes of space, while the generic ldarg takes up 4 bytes of space.

Let’s see an example where we need to use the ldarg.s instruction:

.method public static int32 MultiplySum(int32 a, int32 b, int32 c, int32 d, int32 e)
{
   .maxstack 2
    ldarg.0
    ldarg.1
    add
    ldarg.2
    add
    ldarg.3
    add
    ldarg.s e
    add
    ldc.i4 1024
    mul
    ret
}

In case we had a function where there are less than five arguments, we don’t have to name them at all, e.g:

.method public static int32 Add(int32, int32)
{
    .maxstack 2
    ldarg.0
    ldarg.1
    add
    ret
}

Exercises

Exercise 1
Create a function which takes an int parameter, adds the integer value 10 to it, then writes it to the console. The function should not return a value.

Exercise 2
Implement a function that takes two integers, adds 5 to both of them, then returns their product.

Exercise 3
Implement a function with two arguments, both integers, which calls the Min function of the Math class and returns its value.

Both the examples and the solutions for the exercises can be found in il-programming-tutorials github repository.

Have fun and tinker with the IL code a little! 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s