I have this code that emits some IL
instructions that calls string.IndexOf
on a null
object:
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
"Foo",
MethodAttributes.Public,
typeof(void), Array.Empty<Type>());
var methodInfo = typeof(string).GetMethod("IndexOf", new[] {typeof(char)});
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldnull);
ilGenerator.Emit(OpCodes.Ldc_I4_S, 120);
ilGenerator.Emit(OpCodes.Call, methodInfo);
ilGenerator.Emit(OpCodes.Ret);
This is the generated IL
code:
.method public instance int32 Foo() cil managed
{
// Code size 12 (0xc)
.maxstack 2
IL_0000: ldnull
IL_0001: ldc.i4.s 120
IL_0003: nop
IL_0004: nop
IL_0005: nop
IL_0006: call instance int32 [mscorlib]System.String::IndexOf(char)
IL_000b: ret
} // end of method MyDynamicType::Foo
As you can see there are three nop
instructions before the call
instruction.
First I thought about Debug/Release build but this is not compiler generated code, I am emitting raw IL code and expect to see it as is.
So my question is why are there three nop
instruction when I hadn't emitted any?
ILGenerator
is not very advanced, if you use theEmit(OpCode, Int32)
overload it will put the entireint32
in the instruction stream, no matter if the opcode isLdc_I4
(which actually takes 4 bytes of immediate) orLdc_I4_S
(which doesn't).So make sure to use the right overload:
The lemmas for the opcodes in the documentation specify which overload of
Emit
is the right one to use.In the reference source,
Emit
with anint
argument does this:Where
PutInteger4
writes four bytes to the byte array in which the IL is built up.The documentation of
Emit
says that the extra bytes will beNop
instructions, but that's only if they are actually zero. If the value being passed is "more wrong" (with the high bytes different from zero) then the effects can be worse, from invalid opcodes to operations that subtly corrupt results.