如何使用CIL操作码bne.un与beq,以及bne.un.s与beq.s,无符号与有符号相等



我知道beqbne.un适用于更多的场景(这是平等与不平等(,但我想知道是否有什么区别。例如,假设我使用一个char,并且有x == y,我会期望beq.un,但这并不存在,所以我得到bne.un,但当我使用x != y时,我得到了beq。无符号比较是如何产生有符号比较指令的?这有关系吗?(提示:可能不是,因为编译器就是这么做的,但为什么会有区别呢?(。

示例:

.method public static char  test(char x) cil managed
{
// Code size       11 (0xb)
.maxstack  8
IL_0000:  ldc.i4.s   97
IL_0002:  ldarg.0
IL_0003:  beq.un.s   IL_0008
IL_0005:  ldc.i4.s   65
IL_0007:  ret
IL_0008:  ldc.i4.s   66
IL_000a:  ret
}

vs:

.method public static char  test(char x) cil managed
{
// Code size       11 (0xb)
.maxstack  8
IL_0000:  ldc.i4.s   97
IL_0002:  ldarg.0
IL_0003:  beq.s      IL_0008
IL_0005:  ldc.i4.s   65
IL_0007:  ret
IL_0008:  ldc.i4.s   66
IL_000a:  ret
}

示例C#代码:

// this compiles to `bne.un.s`
public class C {
public bool M(char x) {
if(x == 'A')
return true;
return false;
}
}
// this compiles to `beq.s`
public class C {
public bool M(char x) {
if(x != 'A')
return true;
return false;
}
}

查看sharplab代码:

  • 第一个例子
  • 第二个例子

我很惊讶==如何被翻译成(看起来更正确的(bne.un.s,以及!=如何在不使用任何无符号指令的情况下被翻译成更通用的beq.s

编译的JITted机器代码没有显示这会导致不同的组装(这很好(:

L0000: movzx eax, dx
L0003: cmp eax, 0x41
L0006: jne short L000e   ; will be je for `!=`
L0008: mov eax, 1
L000d: ret
L000e: xor eax, eax
L0010: ret

但情况总是这样吗?关于这些操作码的文档并不表明,只要结果正确,使用任何一个操作码都是错误的。

背景:我在为F#做not函数的PR时遇到了这种特质,并感到好奇。

;无符号的";比特是奇数。beq的文档没有提到有符号/无符号值或浮点值,但bne.un的文档明确表示它适用于无符号值和浮点值。但是,编译器对有符号值和无符号值都使用bne.un

不管怎样,beq的文档确实说:

效果与执行ceq指令后执行brtrue分支到特定目标指令相同

bne.un的文档说:

效果与执行ceq指令后执行brfalse分支到特定目标指令相同

所以我认为可以放心地假设它们彼此完全相反,并且可以在任何可以使用ceq的地方使用。

ceq的文档提到了浮点,但没有提到有符号/无符号的int。


我怀疑beq.un之所以如此命名,是因为它在给定的值相等或是无序浮点的情况下分支(如果equal或un有序浮点,则b分支(。一个过于热心的文档作者可能误解了";un"意思是";无符号的";(就像它对bxx.un指令的其余部分所做的那样(,并且这种传播。

有符号性在比较int(即询问一个大于还是小于另一个(时很重要,但在测试它们是否相等时则不重要:如果两个操作数都有相同的有符号性,则只需要检查它们的位模式是否相同。这就是为什么你有bleble.un,但没有beq.un

最新更新