Ninja Ferret

The random thoughts of a software developer

MSIL - 4. For Loops

So for completeness I thought that I would introduce you to basic loops, although I have no need of them in my intended use of Reflection.Emit. I am going to finish off my Calculator implementation by non-optimal implementation of the multiply method, I could make use of the OpCodes.Mul operator in the same way as I have used OpCodes.Add etc. but this would not demonstrate a basic for loop. As part of this I will also demonstrate calling a method of the generated class. The source code for this can be found in my subversion repository.

Implementing Multiply

I can create a loop that will make use of the Add() method to calculate the multiplication:
public int Multiply(int a, int b)
{
	int result = 0;
	for (int i = a; i > 0; i--)
	{
		result = Add(result, b);
	}
	return result;
}
So taking a look at ILDASM:
.method public hidebysig newslot virtual final
        instance int32  Multiply(int32 a,
                                 int32 b) cil managed
{
  // Code size       36 (0x24)
  .maxstack  3
  .locals init ([0] int32 result,
           [1] int32 i,
           [2] int32 CS$1$0000,
           [3] bool CS$4$0001)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldarg.1
  IL_0004:  stloc.1
  IL_0005:  br.s       IL_0016
  IL_0007:  nop
  IL_0008:  ldarg.0
  IL_0009:  ldloc.0
  IL_000a:  ldarg.2
  IL_000b:  call       instance int32 Inheritance.CalculatorImplementation::Add(int32,
                                                                                int32)
  IL_0010:  stloc.0
  IL_0011:  nop
  IL_0012:  ldloc.1
  IL_0013:  ldc.i4.1
  IL_0014:  sub
  IL_0015:  stloc.1
  IL_0016:  ldloc.1
  IL_0017:  ldc.i4.0
  IL_0018:  cgt
  IL_001a:  stloc.3
  IL_001b:  ldloc.3
  IL_001c:  brtrue.s   IL_0007
  IL_001e:  ldloc.0
  IL_001f:  stloc.2
  IL_0020:  br.s       IL_0022
  IL_0022:  ldloc.2
  IL_0023:  ret
} // end of method CalculatorImplementation::Multiply
The first thing is that we now have 4 local variables:
  • [0] int32 result - the integer variable to hold the result
  • [1] int32 i - the integer variable to hold the loop counter
  • [2] int32 CS$1$0000 - an integer variable, created by the compiler, that holds the return value
  • [3] bool CS$4$0001 - a boolean variable, created by the compiler, that holds the result of the loop condition for(int i = a; i > 0; i--)
Initially, the result variable is assigned the constant 0 (OpCodes.Ldc_I4_0) and the loop counter is assigned the value of the first parameter (OpCodes.Ldarg_1). The IL then switches execution to the location IL0016 that executes the condition and stores the result of that condition into the loop condition variable. If the loop condition variable is true execution then returns to location IL0007 to execute the main body of the loop. Note the use of the OpCodes.Cgt - greater than code. In the main body of the loop the this parameter (parameter 0) is pushed onto the stack followed by the current result variable and the second parameter. The stack is now set-up for a call into the Add method and the result is stored into the result variable. The loop counter variable is then decremented and the condition is evaluated once more. When the loop condition is false, the result variable is stored into the return variable which is then returned to the caller. So putting this into code:
private static void DefineMultiplyMethod(TypeBuilder typeBuilder, Type interfaceType)
{
	var interfaceMethod = interfaceType.GetMethod("Multiply");
	var addMethod = interfaceType.GetMethod("Add");
	var addParameters = addMethod
		.GetParameters()
		.Select(parameter => parameter.ParameterType)
		.ToArray();
	var methodBuilder = typeBuilder.DefineMethod("Multiply",
                             MethodAttributes.Public |
                             MethodAttributes.Virtual |
                             MethodAttributes.Final |
                             MethodAttributes.NewSlot |
                             MethodAttributes.HideBySig);
	methodBuilder.SetReturnType(interfaceMethod.ReturnType);
	var parameters = interfaceMethod
                             .GetParameters()
                             .Select(parameter => parameter.ParameterType)
                             .ToArray();
	methodBuilder.SetParameters(parameters);
	methodBuilder.InitLocals = true;

	var il = methodBuilder.GetILGenerator();
	var returnLabel = il.DefineLabel();
	var decisionLabel = il.DefineLabel();
	var loopStartLabel = il.DefineLabel();
	il.DeclareLocal(typeof(int));
	il.DeclareLocal(typeof(int));
	il.DeclareLocal(typeof(int));
	il.DeclareLocal(typeof(bool));
	il.Emit(OpCodes.Nop);
	il.Emit(OpCodes.Ldc_I4_0);
	il.Emit(OpCodes.Stloc_0);
	il.Emit(OpCodes.Ldarg_1);
	il.Emit(OpCodes.Stloc_1);
	il.Emit(OpCodes.Br_S, decisionLabel);
	il.MarkLabel(loopStartLabel);
	il.Emit(OpCodes.Nop);
	il.Emit(OpCodes.Ldarg_0);
	il.Emit(OpCodes.Ldloc_0);
	il.Emit(OpCodes.Ldarg_2);
	il.EmitCall(OpCodes.Call, addMethod, addParameters);
	il.Emit(OpCodes.Stloc_0);
	il.Emit(OpCodes.Nop);
	il.Emit(OpCodes.Ldloc_1);
	il.Emit(OpCodes.Ldc_I4_1);
	il.Emit(OpCodes.Sub);
	il.Emit(OpCodes.Stloc_1);
	il.MarkLabel(decisionLabel);
	il.Emit(OpCodes.Ldloc_1);
	il.Emit(OpCodes.Ldc_I4_0);
	il.Emit(OpCodes.Cgt);
	il.Emit(OpCodes.Stloc_3);
	il.Emit(OpCodes.Ldloc_3);
	il.Emit(OpCodes.Brtrue_S, loopStartLabel);
	il.Emit(OpCodes.Ldloc_0);
	il.Emit(OpCodes.Stloc_2);
	il.MarkLabel(returnLabel);
	il.Emit(OpCodes.Ldloc_2);
	il.Emit(OpCodes.Ret);
	typeBuilder.DefineMethodOverride(methodBuilder, interfaceMethod);
}
I now have a fully functional and fully working calculator class that implements the Calculator interface and has been generated dynamically at runtime. Next time, I will be looking into lambda expressions...

Tags:

blog comments powered by Disqus