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.