为什么';br.s';这种情况下使用的IL操作码

本文关键字:IL 操作码 br 为什么 这种情况下 | 更新日期: 2023-09-27 18:26:19

出于教育目的,我正在学习一些IL(主要是因为我很好奇引擎盖下的"%"发生了什么(原来是rem),并开始偏离主题…)。

我写了一个方法,只是返回true来稍微分解一下,并想知道"br.s"操作码:

.method public hidebysig static bool  ReturnTrue() cil managed
{
  // Code size       7 (0x7)
  .maxstack  1
  .locals init ([0] bool CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  stloc.0
  IL_0003:  br.s       IL_0005
  IL_0005:  ldloc.0
  IL_0006:  ret
} // End of method Primes::ReturnTrue

在ldc.i4.1在堆栈上推送1,stloc.0将其放在第0个本地之后,br.s基本上(据我所知)在IL_0005行对ldloc.0执行"goto"操作。

为什么会这样?为什么根本没有IL_0004行,所以可以省略它?

为什么';br.s';这种情况下使用的IL操作码

该分支用于调试目的,返回值已被计算并存储,现在可以"调用"调试器。这与方法条目中的NOP相同。

关于IL_0004,正如@hvd所说,br.s有一个地址,不适合"一行",一个字节(我不知道你对寻址有多熟悉,但一条指令通常是一个字节,也就是8位,以及地址或偏移量,通常是8位、16位或32位。在这种情况下,我们有一个偏移量为8位的8位操作码。维基百科有一篇关于CIL OP代码的好文章)。

此外,假设您的方法有多个返回,例如通过if-分支,所有返回都跳到末尾,在您的情况下是IL_0005,因此在函数返回处只需要一个断点。

这是递归下降语法分析器的一个非常常见的工件,就像C#编译器使用的一样。去除这些分支需要一个窥视孔优化器。

当编译器自己优化了一个琐碎的操作,其结果可以在编译时确定时,可能会发生这种情况。C#编译器没有窥视孔优化器,因为它不是必要的,抖动负责消除这些不必要的分支。一般来说,将优化器置于抖动中是一种成功的策略,每个语言编译器都从中受益。使编译器非常简单,并且只在一个(或几个)地方编写和维护代码优化器会花费大量费用。

这不是抖动优化器的终点,整个方法将在运行时消失。由于方法的返回值在编译时是已知的,因此调用该方法的任何代码都很有可能得到实质性的优化。看到这种MSIL在其他方面是一个强烈的提示,表明您的代码可以很容易地简化或有错误:)

这在Visual Studio 2013上没有发生,似乎是开发人员最终修复了它。在VS2013上会是这样。

.method public hidebysig static bool  ReturnTrue() cil managed
{
  .maxstack  1
  ldc.i4.1
  ret
} // End of method Primes::ReturnTrue