Problem with .maxstack in IL-roundtripping tools

I previously posted a tool to allow inline IL in C# / VB.Net.  

At the IL level, the CLR needs to know the maximum stack depth of each method. This can be specified in ILasm via the “.maxstack” directive.
In that post I wrongfully said that if you leave off the “.maxstack” directive from an IL body that ILasm will recompute the stack depth.  My inline IL tool relied on that because it would intentionally strip the existing .maxstack directive, inject new IL into the method, and then expect ILasm to recompute a new value for the combined IL body.

It turns out that ILasm will actually just default to a value of 8 and not recompute the maximum stack depth. (Kudos to Mike Goatly for stumbling on this bug).
Thus the inline IL tool is flawed. This limitation also makes tools based off round-tripping IL significantly less viable. I’ll update that original post with these new findings.

You can verify this. Say you have a trivial C# program (called t.cs), like the following:

class Foo {
static void Main() {
System.Console.WriteLine("Hi!");
}
}

1) Compile it:
     Csc t.cs  

2) Then use ildasm to produce a file containing the IL (t.il).
     Ildasm t.exe /out=t.il

3) You’ll notice this IL for the main method:

 .method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size   13 (0xd)
   .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr   "Hi!"
  IL_0006:  call   void [mscorlib]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  ret
} // end of method Foo::Main

4) Now edit t.il to remove the .maxstack directive

5) Reassemble.
     Ilasm t.il

6) Inspect the resulting t.exe in ilasm and notice that the .maxstack directive is back and defaults to 8.

Just for kicks, edit t.il and change the .maxstack directive to something other than 8, like “.maxstack 19”.  Repeat step 5 and 6 and you’ll see that the value 19 was preserved in the roundtripping.

I asked Serge Lidin  (ILAsm guru in the CLR) about this. Defaulting to some arbitrary value seems very fragile. I specifically wondered why ILAsm wouldn’t just compute the maxstack if the the “.maxstack” directive was missing. Or at least flag a compile time error. Once you’ve parsed the IL, computing max-stack is very easy. In contrast, tools blindly manipulating IL snippets would take a high activation cost to figure out the maxstack.  He agreed that would be good to recompute but explained that nobody had requested that feature. Maybe in V3 CLR.