Search code examples
reflection.emitilildasm

CLR IL-significance of square bracket on .locals init


I am trying to generate a dynamic assembly using Reflection & Emit in .NET. I am getting an error, "Common Language Runtime detected an invalid program." I created another program which has the functionality I want using hard-coded types. The functionality I am trying to write will ultimately use dynamic types, but I can use ILDasm to see the IL I need to generate. I am comparing the IL I am generating with the IL which the compiler generates. In the .locals init declaration of one method I see there is an extra item in the compiler-generated code,

compiler-generated:

.locals init ([0] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000,
           [1] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0001)

mine:

.locals init (class [System.Core]System.Linq.Expressions.ParameterExpression V_0,  
       class [System.Core]System.Linq.Expressions.ParameterExpression[] V_1)

I don't understand the significance of the "[0]" and "[1]" in the compiler-generated code. Can anyone tell me what it means?

As a more general question, I can follow most ILDasm output without too much trouble. But every so often I run across a problematic expression. For instance, in this line from ILDasm

callvirt   instance class [EntityFramework]System.Data.Entity.ModelConfiguration.EntityTypeConfiguration`1<!!0> [EntityFramework]System.Data.Entity.DbModelBuilder::Entity<class DynamicEdmxTrial.HardFooAsset>()

the "!!0" probably refers to the generic type of the Entity<>, but I don't know for sure, and I wonder if there is a key to ILDasm output that would explain its more obscure output to me.


Solution

  • The specification is freely available here. It takes a little getting used to, but most details are easily found once you figure out the structure.

    !! is listed in II.7.1 Types:

    Type ::=       | Description                             | Clause
      ‘!’ Int32    | Generic parameter in a type definition, | §II.9.1
                   | accessed by index from 0                |
    | ‘!!’ Int32   | Generic parameter in a method           | §II.9.2
                   | definition, accessed by index from 0    |
    ...
    

    In other words, inside a method that C# would call f<T, U>(), !!0 would be T, and !!1 would be U.

    However, the [0] is a good question. The spec does not seem to address it. The .locals directive is described in II.15.4.1.3 The .locals directive, which lists the syntax as

    MethodBodyItem ::= ...
     | .locals [ init ] ‘(’ LocalsSignature ‘)’
    LocalsSignature ::= Local [ ‘,’ Local ]*
    Local ::= Type [ Id ]
    

    There is nothing that seems to allow [0] there unless it is part of a Type, and Type does not allow anything starting with [ either. My guess is that this is an undocumented peculiarity specific to Microsoft's implementation, intended to help the human reader see that location 0 is local variable CS$0$0000, for when the generated instructions access local variables by index.

    Experimenting with ILAsm shows that this is exactly what it means. Taking a simple C# program:

    static class Program {
        static void Main() {
            int i = 0, j = 1;
        }
    }
    

    and compiling and then disassembling it (csc test.cs && ildasm /text test.exe >test.il) shows:

    ....
    .locals init (int32 V_0,
             int32 V_1)
    IL_0000:  nop
    IL_0001:  ldc.i4.0
    IL_0002:  stloc.0
    IL_0003:  ldc.i4.1
    IL_0004:  stloc.1
    IL_0005:  ret
    ....
    

    Modifying the .locals to

    .locals init ([0] int32 V_0, [0] int32 V_1)
    

    gives a useful warning message:

    test.il(41) : warning : Local var slot 0 is in use
    

    And indeed, declaring variables of different types, then reordering them using [2], [1], [0], assembling and immediately disassembling the result, shows that the variables got reordered.