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.