x86 ASM:无用的条件跳转?

x86 ASM: useless conditional jump?

我正在查看以下 x86 汇编代码(Intel 语法):

movzx   eax, al
and     eax, 3
cmp     eax, 3
ja      loc_6BE9A0

据我了解,这应该等于 C:

中的类似内容
eax &= 0xFF;
eax &= 3;
if (eax > 3)
   loc_6BE9A0();

这似乎没有多大意义,因为这个条件永远不会为真(因为 eax 如果之前用 3 进行运算,则永远不会大于 3)。我是不是遗漏了什么,或者这真的只是一个不必要的条件?

而且:movzx eax, al 也不是必需的,如果它在那之后立即与 3 进行运算,对吗?

我问这个是因为我不太熟悉汇编语言,所以我不确定我是否遗漏了什么。

您是对的:鉴于以下 andmovzx 是多余的。它可能是由非优化编译器生成的。

是的,如果此代码直接执行,则永远不会执行 ja 跳转。但是,如果其他地方有直接跳转到 cmp(甚至 ja)的代码,cmp/ja 可能不会完全没用。

这是多余的,不是您在优化的 asm 中看到的东西。

即使 cmp/ja 可能是来自其他地方的跳转目标,现有的优化编译器(如 GCC、clang、MSVC 和 ICC)也会(我很确定)执行 jmp 或不同的代码布局,而不是让执行落入始终为假的条件分支。优化器会知道这条执行路径不需要条件分支,因此会确保它没有遇到条件分支。 (即使这会额外花费 jmp。)

这可能是一个不错的选择,即使在这种方式可以节省一些代码大小的假设情况下也是如此,因为您不想用不必要的条件分支污染/稀释分支预测历史,并且分支可以预测错误。


但是在调试模式下,一些编译器比其他编译器更能够关闭他们的大脑来优化单个语句或表达式。 (Across statements they'd always spill/reload vars to memory, 除非你使用 register int foo;)

我能够欺骗 clang -O0 和 MSVC 发出准确的指令序列。还有类似的东西,但 GCC 更糟糕。 (令人惊讶的是,gcc -O0 仍然在单个表达式中进行一些优化,例如对 x /= 10; 使用乘法逆,并为 if(false) 删除死代码。与 MSVC 实际上将 0 放入寄存器和测试它是 0。)

void dummy();
unsigned char set_al();
int foo(void) {
    if ((set_al() & 3) <= 3U)
        dummy();
    return 0;
}

clang12.0 for x86-64 Linux (on Godbolt)

        push    rbp
        mov     rbp, rsp
        call    set_al()
        movzx   eax, al            # The redundant sequence
        and     eax, 3
        cmp     eax, 3
        ja      .LBB0_2
        call    dummy()
.LBB0_2:
        xor     eax, eax
        pop     rbp
        ret

MSVC 包含相同的序列。 GCC10.3 类似但更糟,在寄存器中实现布尔值并 testing 它。 (两者也在同一个神箭link)

## GCC10.3
 ... set up RBP as a frame pointer
        movzx   eax, al            # The redundant sequence
        and     eax, 3
        cmp     eax, 3
        setbe   al
        test    al, al             # even worse than just jnbe
        je      .L2
        call    dummy()
.L2:
        mov     eax, 0
        pop     rbp
        ret

由于 char 来自内存而不是 return 值,GCC 即使在调试模式下也确实优化了比较:

int bar(unsigned char *p) {
    if ((*p & 3) <= 3U)
        dummy();
    return 0;
}
# GCC 10.3 -O0
bar(unsigned char*):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16                # space to spill the function arg
        mov     QWORD PTR [rbp-8], rdi
        call    dummy()                # unconditional call
        mov     eax, 0
        leave
        ret

clang 和 MSVC 做测试,都用 asm like

#MSVC19.28 (VS16.9)  default options (debug mode)
     ...
        movzx   eax, BYTE PTR [rax]
        and     eax, 3
        cmp     eax, 3
        ja      SHORT $LN2@bar
     ...