Implementing a Loop
If anyone has ever touched any assembler then you will be aware of opcodes that do comparisons, this includes assembly languages like MIPS.
I present first the C# code of the for loop.
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
}
}
}
Now here is my implementation of the same using MSIL.
.assembly extern mscorlib {}
.assembly lesthan
{
.ver 1:0:0:0
}
.module LessThan.exe
.method static void main()
{
.entrypoint
.maxstack 2
.locals init (int32, int32)
ldc.i4 10
stloc.0
ldc.i4 0
stloc.1
Expr:
ldloc.1
ldloc.0
blt Body
ret
Body:
ldloc.1
call void [mscorlib]System.Console::WriteLine(int32)
ldc.i4 1
ldloc.1
add
stloc.1
br Expr
}
This code is pretty easy to disassemble. I create a branch block that defines the expression (.Expr) i (where i is the current value in mem location 1) < 10 (location 0) if this expression is true then we branch to .Body which basically prints out the current value of i as well as incrementing that value by 1, we then check the expression holds true again - if so we go again, if not we return power to the caller.
There is a key difference between what the C# compiler will give you and what I have given you, the first is that I have used fairly meaningful branch names, and the second is that I have decided to initialize two local variables holding both the counter (initially 0) and the max (10) - the C# compiler in the above code will only create a local variable for i, 10 will be loaded onto the stack when required.
The C# compiler will honour the fact that 10 is not assigned to any variable so the below is a more accurate representation of what the compiler will generate us, i is a local variable where as the integer 10 is loaded onto the stack when the expression is evaluated.
.assembly extern mscorlib {}
.assembly lesthanC
{
.ver 1:0:0:0
}
.module LessThanC.exe
.method static void main()
{
.entrypoint
.maxstack 2
.locals init (int32)
ldc.i4 0
stloc.0
Expr:
ldloc.0
ldc.i4 10
blt Body
ret
Body:
ldloc.0
call void [mscorlib]System.Console::WriteLine(int32)
ldc.i4 1
ldloc.0
add
stloc.0
br Expr
}
Here is in fact what the C# compiler generates for us (release mode, code optimization).
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 20 (0x14)
.maxstack 2
.locals init ([0] int32 i)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: br.s IL_000e
IL_0004: ldloc.0
IL_0005: call void [mscorlib]System.Console::WriteLine(int32)
IL_000a: ldloc.0
IL_000b: ldc.i4.1
IL_000c: add
IL_000d: stloc.0
IL_000e: ldloc.0
IL_000f: ldc.i4.s 10
IL_0011: blt.s IL_0004
IL_0013: ret
} // end of method Program::Main
Have a look at the MSIL. We first store the int 0 onto the stack, then we branch to IL_000e - here we check that i < 10 if so we branch to IL_0004 where we load the value of i onto the stack then print it out to the console window, we then push i onto the stack, and then push 1 onto the stack then add the two popping them both off the stack and store the result of the addition we then perform the i < 10 expression again until it evaluates to false then we return power to the caller.