Why is IL.Emit method adding additional nop instructions?

Why is IL.Emit method adding additional nop instructions?



I have this code that emits some IL instructions that calls string.IndexOf on a null object:


IL


string.IndexOf


null


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:


IL


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


nop


call



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?


nop






Is your question perhaps a duplicate of "Why does generated IL code start with a Nop?"?

– Arend
Sep 16 '18 at 0:36







Yay for 3 extra spots to break point your code :P however it is curious

– Michael Randall
Sep 16 '18 at 0:37







@Arend I don't think so because emit is not used there, nop instructions are added by compiler to enable debugging. you can only set breakpoints on instructions but imagine putting a break point to the start of a method, you would set it on the first curly brace, since the curly brace is not included in the generated IL, compiler emits nop instruction that allows you to set a breakpoint there.

– Selman Genç
Sep 16 '18 at 0:39






In addition to the excellent answer, I'd say not to worry much about nops appearing once in a while. They are ignored by JIT anyway.

– IllidanS4
Sep 16 '18 at 11:51




2 Answers
2



ILGenerator is not very advanced, if you use the Emit(OpCode, Int32) overload it will put the entire int32 in the instruction stream, no matter if the opcode is Ldc_I4 (which actually takes 4 bytes of immediate) or Ldc_I4_S (which doesn't).


ILGenerator


Emit(OpCode, Int32)


int32


Ldc_I4


Ldc_I4_S



So make sure to use the right overload:


ilGenerator.Emit(OpCodes.Ldc_I4_S, (byte)120);



The lemmas for the opcodes in the documentation specify which overload of Emit is the right one to use.


Emit



In the reference source, Emit with an int argument does this:


Emit


int


public virtual void Emit(OpCode opcode, int arg)

// Puts opcode onto the stream of instructions followed by arg
EnsureCapacity(7);
InternalEmit(opcode);
PutInteger4(arg);



Where PutInteger4 writes four bytes to the byte array in which the IL is built up.


PutInteger4



The documentation of Emit says that the extra bytes will be Nop 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.


Emit


Nop






nice catch! I have recompiled with casting to byte and indeed the nop instructions were gone.

– Selman Genç
Sep 16 '18 at 0:53


nop



The documentation of IlGenerator.Emit mentions this:



Remarks If the opcode parameter requires an argument, the caller must
ensure that the argument length matches the length of the declared
parameter. Otherwise, results will be unpredictable. For example, if
the Emit instruction requires a 2-byte operand and the caller supplies
a 4-byte operand, the runtime will emit two additional bytes to the
instruction stream. These extra bytes will be Nop instructions.



The instruction values are defined in OpCodes.



And the documentation mentions about your instruction



Ldc_I4_S

Pushes the supplied int8 value onto the evaluation stack as an int32, short form.



It seems the three extra nops are coming from the int8 instead of the int32.



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)