c# - How to us CIL opcode bne.un vs beq, and bne.un.s vs beq.s, unsigned vs signed equality
问题描述
I understand that beq
is applicable to more scenarios than bne.un
(and it's equality vs inequality), but I'm wondering if there's any difference. For instance, suppose I use a char
and have x == y
, I would expect beq.un
, but that doesn't exist, so I get bne.un
, but when i use x != y
, I get beq
. How is it that an unsigned comparison can lead to a signed comparison instruction? Does it even matter? (hint: probably not, because that is what the compilers do, but why then the difference?).
Example:
.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
}
Example C# code:
// 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;
}
}
See the sharplab code:
I'm surprised at the difference here between how ==
gets translated to the (seemingly more correct) bne.un.s
, and that !=
gets translated to the more general beq.s
without using any unsigned instruction.
The compiled JITted machine code doesn't show that this leads to different assembly (which is good):
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
But is this always the case? The documentation on these opcodes doesn't suggest it is wrong to use either opcode as long as the outcome is correct.
Background: I came across this idiosyncrasy while working on this PR for F# on the not
function and got curious.
解决方案
The "unsigned" bit is odd. The docs for beq
don't mention signed/unsigned values or floats, but the docs for bne.un
explicitly say that it applies to unsigned values and floats. However, the compiler uses bne.un
for signed values as well as unsigned values.
Regardless, the docs for beq
do say:
The effect is the same as performing a
ceq
instruction followed by abrtrue
branch to the specific target instruction
while the docs for bne.un
say:
The effect is identical to performing a
ceq
instruction followed by abrfalse
branch to the specific target instruction
so I think it's safe to assume that they're exact opposites of each other, and can be used in any place that ceq
can be used.
The docs for ceq
mention floats, but not signed / unsigned ints.
My suspicion is that beq.un
is so named because it branches if the values it's given are equal, or are unordered floats ("branch if equal or unordered floats). It's possible that an over-zealous documentation writer misinterpreted "un" to mean "unsigned" (as it does for the rest of the bxx.un
instructions), and this propagated.
Signedness matters when comparing ints (i.e. asking whether one is greater than or less than another), but not for testing whether they're equal: if both operands have the same signedness, you just need to check whether their bit patterns are identical. This is why you have e.g. both ble
and ble.un
, but there's no beq.un
.
推荐阅读
- python - 使用python循环将数据插入sql表
- r - 安装 ComplexHeatmap 的问题。错误:包“集群”的编译失败
- lua - 如何为 wrk2 安装 Lua 模块(json、uuid 等)
- node.js - 当它们被不同的模块用作依赖项时,如何避免多次安装相同的node.js包?
- react-apollo - 将查询参数传递给 React Apollo POST 请求
- node.js - 第二次调用后的 SequelizeAssociationError
- excel - 在 Excel 模板中找不到带有“转到”的命名范围
- haskell - 获取列表的第二个元素
- python - 仅当线程当前未在 Python 中运行时才在循环中运行线程
- python - Python内置过滤列表不返回无项目