Have I made a mistake in this IL I'm not seeing?
Asked Answered
C

1

8

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'
Coseismal answered 4/2, 2013 at 16:2 Comment(9)
Save the file to disk, and run PEVerify. That will quickly tell you the error ;pPythagoras
@Pythagoras peverify gives me type load failed errors (even without the broken code) though all the non-system assemblies I reference are in the current directoryCoseismal
Most errors in PEVerify cannot be ignored. I suggest you fix all of them. ;p You can try ignore 'MD' errors. From a quick peek, it looks like int32 is on the stack when calling at IL_0054 instead of your instance (this) and the MylibValue arg.Pythagoras
@Pythagoras this and the arg are pushed at IL_001D and IL_0021 respectively. I'll look harder at the loop but I didn't think I had left an int on the stack. Google has been no help in fixing the type load failed peverify error, but all code except this methods runs as expected regardless of it.Coseismal
Run PEVerify with the /IL switch, this should ignore the type errors unless they are marked as IL.Pythagoras
@Pythagoras apparently using the ExplicitLayout attribute can mess up peverify. Got it working and it tells me what I already know: stack depth differs based on the branch. I guess this means the CLR wont accept my approach to this problem (pushing arguments onto the stack in a loop). I'll try to find a way for the compiler to generate verifiable code I suppose. EDIT: actually it gives me a few more erros, maybe there's hope yet.Coseismal
Yes, the stack needs to be deterministic :( BTW, working code does not always need to be verifiable. Another option is to do things like I did in IronScheme (due to the difficulty emitting it): github.com/leppie/IronScheme/blob/master/IronScheme/…Pythagoras
This seems suspect: 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. It looks as though you are adding ldloc.1 and ldc.i4.1, but not doing anything with ldarg.1, ldloc.1 and ldelem. Am I reading that wrong? Perhaps you could give some psuedo-code to demonstrate what it is supposed to be doing.Sommelier
Also, I think that IL_000d: ble IL_001d should be "bge" because you want to skip the throw if Length is greater-than-or-equal than 0; not less-than-or-equal than 0.Sommelier
C
7

@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.

Coseismal answered 4/2, 2013 at 17:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.