为什么IL.Emit方法添加额外的nop指令?

Sel*_*enç 12 c# cil reflection.emit ilgenerator

我有这样的代码,发出一些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);
Run Code Online (Sandbox Code Playgroud)

这是生成的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
Run Code Online (Sandbox Code Playgroud)

如您所见,nop指令前有三条call指令.

首先我考虑了Debug/Release构建,但这不是编译器生成的代码,我发出了原始的IL代码,并希望看到它原样.

所以我的问题是为什么nop当我没有发出任何指令时有三条指令?

har*_*old 13

ILGenerator不是很高级,如果你使用Emit(OpCode, Int32)重载,它将整个int32放在指令流中,无论操作码是什么Ldc_I4(实际上需要4个字节的立即数)或Ldc_I4_S(它没有).

所以一定要使用正确的重载:

ilGenerator.Emit(OpCodes.Ldc_I4_S, (byte)120);
Run Code Online (Sandbox Code Playgroud)

文档中操作码引理指定哪个重载Emit是正确的.


参考源中,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);
}
Run Code Online (Sandbox Code Playgroud)

其中PutInteger4将四个字节写入构建IL的字节数组.

文档Emit说明额外的字节将是Nop指令,但只有当它们实际上为零时才是.如果传递的值是"更多错误"(高字节与零不同)那么效果可能会更糟,从无效的操作码到巧妙地破坏结果的操作.


Are*_*end 5

IlGenerator.Emit 的文档提到了这一点:

备注如果操作码参数需要参数,则调用者必须确保参数长度与声明的参数的长度匹配.否则,结果将无法预测.例如,如果Emit指令需要一个2字节的操作数,并且调用者提供一个4字节的操作数,则运行时将向指令流发出两个额外的字节.这些额外的字节将是Nop指令.

指令值在OpCodes中定义.

文档提到了你的指示

Ldc_I4_S
将提供的 int8值作为 int32短格式推送到评估堆栈.

似乎三个额外的nops来自int8而不是int32.