Invalid IL generated by Emit method?

  • Thread starter Thread starter Gianluca
  • Start date Start date
G

Gianluca

This is the simplified code I'm trying to generate:

ilg.DeclareLocal(typeof(int)); // define local integer
Label exit = ilg.DefineLabel(); // define "exit" label

ilg.Emit(OpCodes.Ldc_I4_0); // put 0 on the stack
ilg.Emit(OpCodes.Stloc_0); // set the local integer to 0

ilg.Emit(OpCodes.Ldloc_0); // load the local integer (0) on the stack
ilg.Emit(OpCodes.Ldc_I4_1); // load integer 1 on the stack
ilg.Emit(OpCodes.Beq, exit); // if the two values on the stack ar equal,
jump to "exit"
ilg.Emit(OpCodes.Ldarg_1); // load the first argument on the stack
ilg.MarkLabel(exit); // : Exit
ilg.Emit(OpCodes.Ret); // return

This is stripped down version of something that makes more sense than this.
It's a simple "public object GetValue(object value)" function and according
to the IL above it will simply return the first argument since 0 and 1 are
not equal, the branch is not executed and the first argument is loaded on
the stack and returned to the caller.

When executing this function I get a "Common Language Runtime detected an
invalid program." system exception. Now, if you move the label to another
place it works fine:

ilg.DeclareLocal(typeof(int)); // define local integer
Label exit = ilg.DefineLabel(); // define "exit" label

ilg.Emit(OpCodes.Ldc_I4_0); // put 0 on the stack
ilg.Emit(OpCodes.Stloc_0); // set the local integer to 0


ilg.MarkLabel(exit); // : Exit
ilg.Emit(OpCodes.Ldloc_0); // load the local integer (0) on the stack
ilg.Emit(OpCodes.Ldc_I4_1); // load integer 1 on the stack
ilg.Emit(OpCodes.Beq, exit); // if the two values on the stack ar equal,
jump to "exit"
ilg.Emit(OpCodes.Ldarg_1); // load the first argument on the stack
ilg.Emit(OpCodes.Ret); // return

As you can see the lable has been move before the comparison and if the Beq
is executed it would end up in an infinite loop. BUt since 0 and 1 are not
equal the sample code above works perfectly fine.

Anyone has an explanation for this other than a bug somewhere in the JIT,
Emit method or something else?
 
Hello

The problem is that in case of the branch is executed, there will be nothing
in the stack at the return statement, if the branch is not executed there
will be arg1 will be in the stack. The JIT compiler makes sure at that all
paths that lead to any instruction will have the same state of the stack.

Best regards,
Sherif El-Metainy
 
Thanks! I was going nuts over this. I was trying to load each element of an
object[] array on the stack and redirect the call to a "wrapped" function
and could not explain why seemingly correct IL code kept failing.
Rearranging the labels worked, at times...

I thought of some kind of restriction for branching but couldn't find any
documentation over this. Well, thanks again.
 
ops, but this makes it impossible to write a dynamic binder class. I'm
writing a dynamic IL generator for speeding up reflection. It works very
well for accessing fields and it extremely faster than reflection. But I
have a problem now when generating IL for calling methods. I have a
generating method called "Invoke" on an interface which takes the arguments
as object[] args. Just like MethodInfo.Invoke(). The IL code should "unpack"
the arguments and place them on the evaluation stack and then call the
reflected method. The end result would be prefectly valid stack state but
the JIT doesn't a loop that loads the stack dynamically because at the first
junction it detects an invalid stack state. Is there a way to turn this
thing off?

arg_2 is object[] args

il.DeclareLocal(typeof(int));
// i = args.Length
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
il.Emit(OpCodes.Stloc_0);
// :loop
il.MarkLabel(loop);
// i = i - 1
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Stloc_0);
// if i < 0 then exit
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Blt_S, exit);
// push args
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldelem_Ref);
// loop
il.Emit(OpCodes.Br_S, loop);
// :exit
il.MarkLabel(exit);
// call
ils.EmitCall(OpCodes.Call, method, null);

At the call the stack could be empty or could contain the unpacked
arguments. Any idea on how to turn off the stack state restriction?


Gianluca said:
Thanks! I was going nuts over this. I was trying to load each element of an
object[] array on the stack and redirect the call to a "wrapped" function
and could not explain why seemingly correct IL code kept failing.
Rearranging the labels worked, at times...

I thought of some kind of restriction for branching but couldn't find any
documentation over this. Well, thanks again.


"Sherif El-Metainy" <elmeteny REMOVETHIS at thewayout NOSPAM dot net> wrote
in message news:[email protected]...
Hello

The problem is that in case of the branch is executed, there will be nothing
in the stack at the return statement, if the branch is not executed there
will be arg1 will be in the stack. The JIT compiler makes sure at that all
paths that lead to any instruction will have the same state of the stack.

Best regards,
Sherif El-Metainy
 
ok, the solution is to generate the correct number of load operations for
the actual method being wrapped.

Gianluca said:
ops, but this makes it impossible to write a dynamic binder class. I'm
writing a dynamic IL generator for speeding up reflection. It works very
well for accessing fields and it extremely faster than reflection. But I
have a problem now when generating IL for calling methods. I have a
generating method called "Invoke" on an interface which takes the arguments
as object[] args. Just like MethodInfo.Invoke(). The IL code should "unpack"
the arguments and place them on the evaluation stack and then call the
reflected method. The end result would be prefectly valid stack state but
the JIT doesn't a loop that loads the stack dynamically because at the first
junction it detects an invalid stack state. Is there a way to turn this
thing off?

arg_2 is object[] args

il.DeclareLocal(typeof(int));
// i = args.Length
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
il.Emit(OpCodes.Stloc_0);
// :loop
il.MarkLabel(loop);
// i = i - 1
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Stloc_0);
// if i < 0 then exit
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Blt_S, exit);
// push args
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldelem_Ref);
// loop
il.Emit(OpCodes.Br_S, loop);
// :exit
il.MarkLabel(exit);
// call
ils.EmitCall(OpCodes.Call, method, null);

At the call the stack could be empty or could contain the unpacked
arguments. Any idea on how to turn off the stack state restriction?


Gianluca said:
Thanks! I was going nuts over this. I was trying to load each element of an
object[] array on the stack and redirect the call to a "wrapped" function
and could not explain why seemingly correct IL code kept failing.
Rearranging the labels worked, at times...

I thought of some kind of restriction for branching but couldn't find any
documentation over this. Well, thanks again.


"Sherif El-Metainy" <elmeteny REMOVETHIS at thewayout NOSPAM dot net> wrote
in message news:[email protected]...
Hello

The problem is that in case of the branch is executed, there will be nothing
in the stack at the return statement, if the branch is not executed there
will be arg1 will be in the stack. The JIT compiler makes sure at that all
paths that lead to any instruction will have the same state of the stack.

Best regards,
Sherif El-Metainy

This is the simplified code I'm trying to generate:

ilg.DeclareLocal(typeof(int)); // define local integer
Label exit = ilg.DefineLabel(); // define "exit" label

ilg.Emit(OpCodes.Ldc_I4_0); // put 0 on the stack
ilg.Emit(OpCodes.Stloc_0); // set the local integer to 0

ilg.Emit(OpCodes.Ldloc_0); // load the local integer (0) on the stack
ilg.Emit(OpCodes.Ldc_I4_1); // load integer 1 on the stack
ilg.Emit(OpCodes.Beq, exit); // if the two values on the stack ar
equal,
jump to "exit"
ilg.Emit(OpCodes.Ldarg_1); // load the first argument on the stack
ilg.MarkLabel(exit); // : Exit
ilg.Emit(OpCodes.Ret); // return

This is stripped down version of something that makes more sense than
this.
It's a simple "public object GetValue(object value)" function and
according
to the IL above it will simply return the first argument since 0 and
1
are
not equal, the branch is not executed and the first argument is
loaded
on
the stack and returned to the caller.

When executing this function I get a "Common Language Runtime
detected
an
invalid program." system exception. Now, if you move the label to another
place it works fine:

ilg.DeclareLocal(typeof(int)); // define local integer
Label exit = ilg.DefineLabel(); // define "exit" label

ilg.Emit(OpCodes.Ldc_I4_0); // put 0 on the stack
ilg.Emit(OpCodes.Stloc_0); // set the local integer to 0


ilg.MarkLabel(exit); // : Exit
ilg.Emit(OpCodes.Ldloc_0); // load the local integer (0) on the stack
ilg.Emit(OpCodes.Ldc_I4_1); // load integer 1 on the stack
ilg.Emit(OpCodes.Beq, exit); // if the two values on the stack ar
equal,
jump to "exit"
ilg.Emit(OpCodes.Ldarg_1); // load the first argument on the stack
ilg.Emit(OpCodes.Ret); // return

As you can see the lable has been move before the comparison and if the
Beq
is executed it would end up in an infinite loop. BUt since 0 and 1
are
not
equal the sample code above works perfectly fine.

Anyone has an explanation for this other than a bug somewhere in the JIT,
Emit method or something else?
 
Back
Top