Search code examples
c#il

Invalid IL program, what am I doing wrong? (simple if-else code)


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

Solution

  • I think you need to call DeclareLocal() to declare your local variables before you can use them.