I'm working on a compiler using System.Reflection.Emit, and I'm getting JIT limitation errors I can't figure out. The problem occurs in my implementation of function handles. I.e. generating the code for
function foo() { }
f = foo;
f();
Due to specifications beyond my control, the language is dynamically typed, so I can't know how many arguments f will expect at compile time. To counter this, rather than emitting a Ldftn for foo, I generate a new method, λfoo, that takes an array of the arguments given in the call expression and pushes them onto the eval stack for foo. Is that allowed in the CLR?
What I get right now is a "JIT has encountered an internal limitation" exception (or "CLR has detected an invalid program" if I save the assembly and run it instead of calling it from memory) with a stack trace showing it happens in λfoo. This is the IL I'm generating.
.method private instance class [MylibInterop]MylibInterop.MylibValue
'λfoo'(class [MylibInterop]MylibInterop.MylibValue[] A_1) cil managed
{
// Code size 90 (0x5a)
.maxstack 10
.locals init (int32 V_0,
int32 V_1)
IL_0000: ldarg.1
IL_0001: call instance int32 [mscorlib]System.Array::get_Length()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4 0x0
IL_000d: ble IL_001d
IL_0012: ldstr "Too many arguments to lambda call"
IL_0017: newobj instance void [mscorlib]System.Exception::.ctor(string)
IL_001c: throw
IL_001d: ldarg.0
IL_001e: ldc.i4.0
IL_001f: stloc.1
IL_0020: ldloc.0
IL_0021: newobj instance void [MylibInterop]MylibInterop.MylibValue::.ctor(int32)
IL_0026: ldloc.1
IL_0027: ldloc.0
IL_0028: bge IL_003d
IL_002d: ldarg.1
IL_002e: ldloc.1
IL_002f: ldelem [MylibInterop]MylibInterop.MylibValue
IL_0034: ldloc.1
IL_0035: ldc.i4.1
IL_0036: add
IL_0037: stloc.1
IL_0038: br IL_0026
IL_003d: ldloc.0
IL_003e: stloc.1
IL_003f: ldloc.1
IL_0040: ldc.i4 0x0
IL_0045: bge IL_0054
IL_004a: ldnull
IL_004b: ldloc.1
IL_004c: ldc.i4.1
IL_004d: add
IL_004e: stloc.1
IL_004f: br IL_003f
IL_0054: call instance class [MylibInterop]MylibInterop.MylibValue debug.Program::foo(class [MylibInterop]MylibInterop.MylibValue)
IL_0059: ret
} // end of method Program::'λfoo'
@leppie got it in the comments: the stack needs to be deterministic; it isn't in the code I was generating (even if I know it's pushing the right number of args). I was able to get around this because the compiler had enough information to unroll the loop (hence the constants in the generated IL) and thus produce a deterministic stack.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With