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() {

1) Compile it:
     Csc t.cs  

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

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

.method private hidebysig static void  Main() cil managed
// 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 to remove the .maxstack directive

5) Reassemble.

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

Just for kicks, edit 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.


Comments (3)

  1. FWIW, I think the default stack value of 8 comes from the CLI specification and not necessarily from ILAsm. In Partition II, "24.4.2 Tiny Format", it states when a tiny header is used as the method header type "The operand stack need be no bigger than 8 entries." But I agree that it would be cool to have ILAsm compute the .maxstack for you.

  2. jmstall says:

    Kevin – you’re correct (that’s what Serge said when I talked to him about it).

    There’s certainly no innate correlation between default stack size for a tiny header and default maxstack value in ilasm; so using tiny header’s defaults for maxstack seems silly.

    Afterall, once you’re editing things at the IL level (and not worrying about binary encodings), "tiny header" becomes just a meaningless concept.