【问题标题】:Why is IL.Emit method adding additional nop instructions?为什么 IL.Emit 方法要添加额外的 nop 指令?
【发布时间】:2018-09-16 00:28:35
【问题描述】:

我的这段代码会发出一些 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 指令?

【问题讨论】:

  • 您的问题可能与"Why does generated IL code start with a Nop?"重复吗?
  • 很高兴有 3 个额外的点来断点你的代码:P 但它很奇怪
  • @Arend 我不这么认为,因为那里没有使用 emit,编译器添加了 nop 指令以启用调试。您只能在指令上设置断点,但想象在方法的开头设置断点,您可以在第一个大括号上设置断点,因为大括号不包含在生成的 IL 中,编译器会发出 nop 指令,允许您在那里设置断点。
  • 除了出色的答案之外,我想说不要太担心 nops 偶尔会出现。无论如何,它们都会被 JIT 忽略。

标签: c# cil reflection.emit ilgenerator


【解决方案1】:

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 sourceEmitint 参数中这样做:

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 指令,但前提是它们实际上为零。如果传递的值“更错误”(高字节不为零),那么效果可能会更糟,从无效的操作码到微妙地破坏结果的操作。

【讨论】:

  • 不错的收获!我已经通过转换为字节重新编译,确实 nop 指令消失了。
【解决方案2】:

IlGenerator.Emit 的documentation 提到了这一点:

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

指令值在 OpCodes 中定义。

文档中提到了您的指示

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 1970-01-01
    • 2015-03-07
    • 2015-10-22
    • 2020-11-16
    • 2012-08-31
    • 1970-01-01
    相关资源
    最近更新 更多