Func-eval can fail while stopped in a non-optimized managed method that pushes more than 256 argument bytes

In this blog entry, Mike describes that func-eval will fail when not a GC-safe point.  In VS this results in the error "Cannot evaluate expression because a thread is stopped at a point where garbage collection is impossible, possibly because the code is optimized".  Typically non-optimized (debuggable) managed code is compiled as "fully-interruptible" which means every point is a GC-safe point.  However, occasionally I hear complaints that VS is giving the error even when in non-optimized code. 

In CLR 2.0, the x86 JIT has a limitiation where it will fall back to partially-interruptible code if more than 256 bytes of arguments are pushed onto the stack (which is normally very rare).  [Update: corrected claim that the limitation was introduced in 2.0, .NET 1.1 actually had an even smaller limit of 128 bytes].  For example, the code below passes two 128 byte structures by-value to the function 'BigFunc', and so is compiled as partially-interruptible which means FuncEval isn't necessarily possible at all points in the method.

image

In the above case, we can verify that the method Main is not fully-interruptible by using the GCInfo command in SOS:

.load sos
extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded

!GCInfo 003A00A4
entry point 003a0070
Normal JIT generated code
GC info 11e8a09800121a98
Method info block:
    method      size   = 0167
    prolog      size   = 32
    epilog      size   =  8
    epilog     count   =  1
    epilog      end    = yes 
    callee-saved regs  = EDI ESI EBX EBP
    ebp frame          = yes 
    fully interruptible= no 
    double align       = no 
    arguments size     =  0 DWORDs
    stack frame size   = 44 DWORDs
    untracked count    =  1
    var ptr tab count  =  1
    security check obj = yes
    exception handlers = yes
    edit & continue    = yes
    epilog        at   015F
    argTabOffset = 5 

First we load sos in the immediate window, then get an address in the method of interest (eg. by looking at the value of the EIP register while we're stopped there) and pass it to the !GCInfo command.  The "fully intteruptible=no" line is unusual for debuggable code (one way you can tell it's indeed debuggable is the "edit & continue = yes" which isn't normally the case in optimized code).

The other, more common, case where this happens is when calling a method with at least 64 arguments (each of 4 bytes).  Having a method that takes 64 arguments seems poor style to me (hard to read), but I've seen such code from code-generation tools.  Either way, passing 256 bytes by-value on the stack is generally a bad idea from a performance perspective anyway (it's a fair amount of copying of data).  It's usually better to group the data into a class and pass that, and/or pass large value-types by reference (eg. with the 'ref' keyword in C#).

We're looking into relaxing this restriction in a future version of the CLR, but in the meantime if you run into problems with this, I suggest you refactor your code to avoid having methods that take 256 bytes or more in arguments.  Perhaps somebody should write an FxCop rule for this.