14

我有这段代码会发出一些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);

这是生成的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

如您所见,nop指令前有三个call指令。

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

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

4

2 回答 2

16

ILGenerator不是很先进,如果您使用Emit(OpCode, Int32)重载,它会将整个指令流放入int32指令流中,无论操作码是Ldc_I4(实际上需要 4 个字节的立即数)还是Ldc_I4_S(不需要)。

因此,请确保使用正确的重载:

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

文档中操作码的引理指定使用哪个重载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);
}

wherePutInteger4将四个字节写入建立 IL 的字节数组。

的文档Emit说额外的字节将是Nop指令,但前提是它们实际上为零。如果传递的值“更错误”(高字节不为零),那么效果可能会更糟,从无效的操作码到巧妙地破坏结果的操作。

于 2018-09-16T00:48:51.860 回答
6

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

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

指令值在操作码中定义。

文档中提到了您的指示

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

似乎三个额外的 nop 来自 int8 而不是 int32。

于 2018-09-16T00:49:25.047 回答