I'm learning IL. I found that LINQpad is actually great for writing C# code and immediately viewing the generated IL. Much faster than VS/ILSpy.
I wrote this simple code:
int x = 10;
int y = 20;
if (x > y)
Console.WriteLine("X is greater");
else
Console.WriteLine("y is greater");
and got:
IL_0001: ldc.i4.s 0A
IL_0003: stloc.0 // x
IL_0004: ldc.i4.s 14
IL_0006: stloc.1 // y
IL_0007: ldloc.0 // x
IL_0008: ldloc.1 // y
IL_0009: cgt
IL_000B: ldc.i4.0
IL_000C: ceq
IL_000E: stloc.2 // CS$4$0000
IL_000F: ldloc.2 // CS$4$0000
IL_0010: brtrue.s IL_001F
IL_0012: ldstr "X is greater"
IL_0017: call System.Console.WriteLine
IL_001C: nop
IL_001D: br.s IL_002A
IL_001F: ldstr "y is greater"
IL_0024: call System.Console.WriteLine
One thing I didn't quite get, and felt redundant is the stloc.2
and ldloc.2
before brtrue.s
- why do we need to store and load the value that we got back from ceq
since it's already pushed by ceq
in the stack?
So I went and gave it a try myself, without doing those two commands:
static void Main()
{
var asm_name = new AssemblyName("Asm");
var asm_builder = AppDomain.CurrentDomain.DefineDynamicAssembly(asm_name, AssemblyBuilderAccess.RunAndSave);
var mod_builder = asm_builder.DefineDynamicModule("Mod", "Test.dll");
var type_builder = mod_builder.DefineType("Test", TypeAttributes.Public);
var cmp_builder = type_builder.DefineMethod("Compare",
MethodAttributes.Public | MethodAttributes.HideBySig,
CallingConventions.Standard,
typeof(void),
Type.EmptyTypes);
var WL = typeof(Console).GetMethod("WriteLine",
BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null);
// int x = 10;
// int y = 20;
// if (x > y)
// WL("x is greater");
// else
// WL("y is greater");
var il = cmp_builder.GetILGenerator();
var ygtX = il.DefineLabel(); // defines a label for y > x
il.Emit(OpCodes.Ldc_I4, 0xA); // push 10
il.Emit(OpCodes.Stloc_0); // pop 10 to location 0 (x)
il.Emit(OpCodes.Ldc_I4, 0x14); // push 20
il.Emit(OpCodes.Stloc_1); // pop 20 to location 1 (y)
il.Emit(OpCodes.Ldloc_0); // push x
il.Emit(OpCodes.Ldloc_1); // push y
il.Emit(OpCodes.Cgt); // pops x, y and compares them. pushes 1 if x > y else 0
il.Emit(OpCodes.Ldc_I4_0); // push 0
il.Emit(OpCodes.Ceq); // pops the last two values, pushes 1 if equal else 0
il.Emit(OpCodes.Brtrue_S, ygtX); // pops the last value, branches to ygt label if value is true (== 1)
il.Emit(OpCodes.Ldstr, "x is greater"); // pushes string
il.Emit(OpCodes.Call, WL); // pops string, calls WriteLine with it
il.MarkLabel(ygtX); // marks the beginning of y > x label
il.Emit(OpCodes.Ldstr, "y is greater"); // pushes string
il.Emit(OpCodes.Call, WL); // pops string, calls WriteLine with it
il.Emit(OpCodes.Ret);
var type = type_builder.CreateType();
var print = type.GetMethod("Compare");
var cmp = (Action)Delegate.CreateDelegate(typeof(Action), null, print);
cmp();
asm_builder.Save("Test.dll");
}
Running that throws:
Unhandled Exception: System.InvalidProgramException: Common Language Runtime detected an invalid program.
Adding those two instructions (the store and load before the branch as seen in the first IL snippet) didn't help.
What am I doing wrong?
Thanks for any help.
EDIT: Here's the optimized output for LINQpad. Makes more sense. These two commands were indeed redundant.
IL_0000: ldc.i4.s 0A
IL_0002: stloc.0 // x
IL_0003: ldc.i4.s 14
IL_0005: stloc.1 // y
IL_0006: ldloc.0 // x
IL_0007: ldloc.1 // y
IL_0008: ble.s IL_0015
IL_000A: ldstr "X is greater"
IL_000F: call System.Console.WriteLine
IL_0014: ret
IL_0015: ldstr "y is greater"
IL_001A: call System.Console.WriteLine
I think you need to call DeclareLocal()
to declare your local variables before you can use them.