如果(!boolvar){...在1个asm指令中可以做什么?

时间:2013-08-29 16:12:09

标签: c assembly x86

这个问题更多的是出于好奇而非必要:

是否可以以某种方式重写c代码if ( !boolvar ) { ...,以便将其编译为1 cpu指令?

我试过在理论层面考虑这个问题,这就是我想出的:

if ( !boolvar ) { ...

需要首先否定变量,然后根据该变量进行分支 - > 2条指令(否定+分支)

if ( boolvar == false ) { ...

需要将false值加载到寄存器中,然后根据该值进行分支 - > 2条指令(加载+分支)

if ( boolvar != true ) { ...

需要将true的值加载到寄存器中然后根据该分支(“branch-if-not-equal”)分支 - > 2条指令(load +“branch-if-not-equal”)

我的假设错了吗?我有什么东西可以忽略吗?

我知道我可以生成程序的中间asm版本,但我不知道如何以某种方式使用它,所以我一方面可以打开编译器优化,同时又没有空{{1} }语句优化了(或者将if语句与其内容一起优化,给出一些非通用的答案)

P.S。:当然我也搜索谷歌和SO这个,但有这么短的搜索条件我真的找不到任何有用的东西

P.P.S。我对一个语义上等效的版本没问题,这个版本不是语法等价的,例如不使用if


编辑:如果我对发出的asm指令的假设是错误的,请随时纠正我。


Edit2:我实际上已经学习了大约15年前,并在大约5年前重新学习了alpha架构,但我希望我的问题仍然足够明确,以弄清楚我在问什么。此外,如果它有助于找到一个好的答案,你可以自由地假设消费者cpus中常见的任何类型的处理器扩展,直到AVX2(截至撰写本文时的当前haswell cpu)。

3 个答案:

答案 0 :(得分:3)

在我的帖子结束时,它会说明为什么你不应该针对这种行为(在x86上)。

正如Jerry Coffin写的那样,x86中的大多数跳转取决于标志寄存器。

但有一个例外:j*cxz指令集在ecx / rcx寄存器为零时跳转。为此,您需要确保boolvar使用ecx注册。您可以通过专门将其分配给该寄存器来实现这一目标

register int boolvar asm ("ecx");

但到目前为止,并非所有编译器都使用j*cxz指令集。 icc有一个标志可以让它做到这一点,但通常不建议这样做。英特尔手册说明了两条指令

test ecx, ecx
jz ...

在处理器上更快。

这样做的原因是x86是CISC(复杂)指令集。在实际的硬件中,虽然处理器会将在asm中作为一条指令出现的复杂指令拆分成多个微指令,然后以RISC方式执行。这就是为什么并非所有指令都需要相同的执行时间的原因,有时多个小指令比一个指令更快。

testjz是单个微指令,但jecxz将被分解为这两个。

存在j*cxz指令集的唯一原因是,如果要在不修改标志寄存器的情况下进行条件跳转。

答案 1 :(得分:1)

是的,它是可能的 - 但这样做取决于此代码的上下文。

x86中的条件分支取决于flags寄存器中的值。为了将其编译成单个指令,其他一些代码将需要设置正确的标志,因此剩下的所有内容都是jnz wherever之类的单个指令。

例如:

boolvar = x == y;
if (!boolvar) {
    do_something();
}

...可能最终呈现为:

    mov eax, x
    cmp eax, y    ; `boolvar = x == y;`
    jz @f
    call do_something
@@:

根据您的观点,它甚至可以编译为仅部分指令。例如,相当多的指令可以"谓词",因此只有在某些先前定义的条件为真时才执行它们。在这种情况下,您可能有一条指令用于设置" boolvar"到正确的值,然后是一个有条件地调用一个函数,所以没有一个(完整的)指令对应于if语句本身。

虽然你不太可能在体面的C语言中看到它,但单个汇编语言指令可能包含更多内容。举个明显的例子,考虑一下:

    x = 10;
looptop:
    -- x;
    boolvar = x == 0;
    if (!boolvar)
        goto looptop;

这整个序列可以编译成类似的东西:

    mov ecx, 10
looptop:
    loop looptop

答案 2 :(得分:1)

  

我的假设是错误的

你有几个假设错了。首先你应该知道1条指令不一定比多条指令快。例如,在较新的μarchtest可以与jcc进行宏融合,因此2条指令将作为一条运行。或者划分是如此之慢,以至于可能已经完成了数十或数百个更简单的指令。如果if块比多个指令慢,那么将if块编译为单个指令是不值得的

此外,if ( !boolvar ) { ...不需要 首先否定变量,然后根据 进行分支。 x86中的大多数跳转都基于标志,它们同时具有yes和no条件,因此不需要否定该值。我们可以简单地跳到非零而不是跳零(

同样地,if ( boolvar == false ) { ...不需要 将false值加载到寄存器中,然后根据 进行分支。 false是一个等于0的常量,可以作为立即数嵌入到指令中(如cmp reg, 0)。但是对于零检查,那么只需一个简单的test reg, reg即可。然后jnzjz将用于跳转零/非零,这将与之前的test指令融合为一个

可以将if标题或正文编译成单个指令,但这完全取决于您需要做什么,以及使用了什么条件。因为boolvar的标志可能已经可以从前一个语句中获得,所以下一行中的if块可以像Jerry Coffin的回答一样直接跳转

此外x86具有条件移动,因此如果if内部是一个简单的赋值,则可以在1条指令中完成。以下是example and its output

int f(bool condition, int x, int y)
{
    int ret = x;
    if (!condition)
        ret = y;
    return ret;
}

f(bool, int, int):
        test    dil, dil ; if(!condition)
        mov     eax, edx ; ret = y
        cmovne  eax, esi ; if(condition) ret = x
        ret

其他一些情况甚至不需要条件移动或跳跃。例如

bool f(bool condition)
{
    bool ret = false;
    if (!condition)
        ret = true;
    return ret;
}

编译为a single xor without any jump at all

f(bool):
        mov     eax, edi
        xor     eax, 1
        ret

ARM体系结构(v7及更低版本)可以将任何指令作为条件运行,因此可以只转换为一条指令

例如以下循环

while (i != j)
{
   if (i > j)
   {
       i -= j;
   }
   else
   {
       j -= i;
   }
}

可以转换为ARM程序集

loop:   CMP  Ri, Rj         ; set condition "NE" if (i != j),
                            ;               "GT" if (i > j),
                            ;            or "LT" if (i < j)
        SUBGT  Ri, Ri, Rj   ; if "GT" (Greater Than), i = i-j;
        SUBLT  Rj, Rj, Ri   ; if "LT" (Less Than), j = j-i;
        BNE  loop           ; if "NE" (Not Equal), then loop