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

Why is IL.Emit method adding additional nop instructions?

我有这段代码发出一些 IL 指令,在 null 对象上调用 string.IndexOf

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

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

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

所以我的问题是为什么在我没有发出任何指令的情况下有三个 nop 指令?

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

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

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

文档中的 lemmas for the opcodes 指定 Emit 的哪个重载是正确的。


reference source 中,Emitint 参数是这样做的:

public virtual void Emit(OpCode opcode, int arg) 
{
    // Puts opcode onto the stream of instructions followed by arg
    EnsureCapacity(7);
    InternalEmit(opcode);
    PutInteger4(arg);
}

其中 PutInteger4 向构建 IL 的字节数组写入四个字节。

Emit 的文档说额外的字节将是 Nop 指令,但这只是在它们实际上为零的情况下。如果传递的值是 "more wrong"(高字节不为零),那么效果可能会更糟,从无效的操作码到巧妙地破坏结果的操作。

IlGenerator.Emit的documentation提到了这个:

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.

文档中提到了您的说明

Ldc_I4_S
将提供的 int8 值作为 int32 的缩写形式推入计算堆栈。

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