CIL programming tutorial – The Basics Part II

In the previous tutorial we have explored the very basics of IL programming and now we will complete our understanding the basics. The following topics will be covered:

  1. Conditions
  2. Local variables
  3. Loops
  4. Boxing and unboxing
  5. Ref and out parameter modifiers

Let’s get started!

Conditions

Unlike how C# handles conditions in IL conditions are implemented the same way as in Assembly. This is called branching that means you execute an instruction and if it’s result is true then the execution flow jumps to the instruction provided for the branching instruction. Let’s see an example:

.class public Tutorial02.Program
{
    .method public static bool IsBiggerThan( int32, int32)
    {
        .maxstack 2    // []
        ldarg.0        // [4]
        ldarg.1        // [8,4]
        bgt.s Bigger   // []

        ldc.i4.0       // [0]
        ret            // []

        Bigger:
        ldc.i4.1       // [1]
        ret            // []
    }
}

Here we use the bgt.s instruction which takes two values from the stack and checks whether the second value is bigger than the first one. If it is, then it jumps to the instruction after the label named Bigger.

E.g. the first argument is 4 and the second is 8, in this case the stack will have the values 8 and 4 (from top to bottom). When the bgt.s instruction is executed, first the value 8 then 4 will be popped from the stack and the bgt.s checks whether 4 is bigger than 8. As it is not bigger, then s won’t jump to the Bigger label and the code execution continues with ldc.i4.0 and ret instructions.

An interesting fact is that you can only load int32, int64, float and double constants, meaning that you need to use ldc.i4.0 to load a boolean value onto the stack or the ldc.i4.1 to load a true value.

Local variables

Before continuing with loops, first we need to understand how to use local variables, as we will need to store the index for the loops.

To declare a local variable, you will need to place the following code after the .maxstack x declaration:

.locals init (
    [0] <type> <name>
)

Remember, placing this codeblock either before the .maxstack declaration or after an instruction, you will get a System.InvalidProgramException during runtime when trying to JIT the code.

Let’s see an example of using a local variable for storing the result of an addition of two int arguments:

.class public Tutorial02.Program
{
    .method public static int32 AddWithLocal( int32 a, int32 b)
    {
        .maxstack 2
        .locals init(
            int32 result
        )

        ldarg.0
        ldarg.1
        add

        stloc.0
        ldloc.0
        ret
    }
}

Of course, if you are in Release mode, or the “Optimize code” option is checked in, the IL code of the following C# function:

public static int AddWithLocal(int a, int b)
{
    var result = a;
    result += b;

    return result;
}

The IL code of this a local variable, as it will be removed during the optimization process, so it’s IL code will be the same as what we created in the last tutorial:

.class public Tutorial02.Program
{
    .method public static int32 AddWithLocal(int32 , int32 )
    {
        .maxstack 2
        ldarg.0
        ldarg.1
        add
        ret
    }
}

Loops

Loops use the same branching instructions the conditions do for execution with using a local counter variable and its made of the following steps:

  1. Initialize and store the counter variable
  2. Check loop condition (if unfullfilled, jump after 5.
  3. Execute modification statement (most of the times increment)
  4. Execute the loop body
  5. Jump to 2.
.class public Tutorial02.Program
{
    .method public static int32 ToPower(int32 base, int32 exponent)
    {
        .maxstack 2
        .locals init (
            [0] int32 result,
            [1] int32 i
         )

         //load 1 to result then 0 to i
         ldc.i4.1
         stloc.0
         ldc.i4.0
         stloc.1

         //start of the loop
         br.s CheckCondition

         LoopStart:
         ldloc.0
         ldarg.0
         mul
         stloc.0

         ldloc.1
         ldc.i4.1
         add
         stloc.1

         //Checking the loop condition
         CheckCondition:
         ldloc.1
         ldarg.1
         blt.s LoopStart
         //end of the loop

         ldloc.0
         ret
    }
}

In this example we can see two more branching instruction, the blt.s which is the counterpart of the bgt.s and br.s which is the unconditional branching instruction.

Boxing

During C# programming in most cases you don’t need to bother about boxing and unboxing as boxing happens implicitly and unboxing used less frequently since ArrayList was replaced with generics and dynamic was introduced. Still in custom validators or model binders you can still encounter unboxing.

An example for boxing which happens behind the scenes is when you call Console.WriteLine the similar way like string.format with a format string and an object parameter.

.class public Tutorial02.Program
{
    .method public static void Boxing( int32)
    {
        .maxstack 2

        ldstr "The boxed value is: {0}"
        ldarg.0
        box [mscorlib]System.Int32
        call void [mscorlib]System.Console::WriteLine( string, object)

        ret
    }
}

you don’t have to bother about boxing and unboxing as it occures automatically in the background, however in IL, you have to explicitly call the box operation as well.

Unboxing

Let’s see a simplified example for unboxing where we convert an object to an int:

.class public Tutorial02.Program
{
    .method public static int32 UnboxInt(object)
    {
        .maxstack 1
ldarg.0
        unbox.any [mscorlib]System.Int32

        ret
    }
}

In this example we use the unbox.any instruction which is one of the two unboxing instruction. The other one is the unbox operation which cannot be used with the Nullable<?> value type and returns the address of the variable.

Out and ref parameter modifiers

One other difference between C# and IL is that IL has references and instructions for direct address manipulations just like C++ does. When working with the out and ref parameter modifiers, we will see how they are used.

A usual occurrence of the out modifier is creating an parsing function that returns a bool indicating success or failure and the result separately (JSON parsing anyone? 🙂 ) such as the already existing bool TryParse(string input, out int result) function. However we haven’t learned the usage of try – catch, we will simplify this code by returning true and setting the result to 1

.class public Tutorial02.Program
{
    .method public static bool TryParse( string s, [out] int32& result)
    {
        ldarg.1    // [result&]
        ldc.i4.0   // [0, result&]
        stind.i4   // []

        ldc.i4.1   // [1]
        ret        // []
    }
}

The difference between out int and int parameters are:

  • usage of the [out] keyword
  • using int32& instead of int
  • storing the value with the stind.i4 instruction

For the ref modifier we will implement the classic example for swapping two integers:

.class public Tutorial02.Program
{
    .method public static void SwapWithRefParameter( int32& a, int32& b)
    {
        .locals init(
            [0] int32 temp
        )

        ldarg.0    // [a&]
        ldind.i4   // [a]
        stloc.0    // []

        ldarg.0    // [a&]
        ldarg.1    // [b&, a&]
        ldind.i4   // [b, a&]
        stind.i4   // []

        ldarg.1    // [b&]
        ldloc.0    // [temp, b&]
        stind.i4   // []

        ret
    }
}

Here we see again the stind.i4 instruction function for storing an int32 value on a specific address, and its counterpart lding.i4 for loading an in32 value from a specific address.

And this is it for the second tutorial, in the next ones we will learn about classes, objects and all related stuff.

You can check out the code on the il-programming-tutorials github repository.

Advertisements

4 thoughts on “CIL programming tutorial – The Basics Part II

  1. Greg

    Hey, dolinkamark.

    Just wanted to let you know that those two articles were extremely well written, and that thanks to them and your article about Mono.Cecil, I discovered a lot of things I’ll love working with.

    Keep up the great work!

    Reply
    1. Dolinka Márk Post author

      Thank you Greg! It’s great to hear that you found my article and tutorials useful 🙂 It also gives me a lot of motivation to finish my ongoing, not-yet-published stuff.

      Have a nice day!

      Reply

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