繁体   English   中英

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

[英]possible to do if (!boolvar) { … in 1 asm instruction?

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

是否有可能重写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版本,但我不知道如何以某种方式使用它,所以我可以一方面打开编译器优化,同时没有一个空的if语句优化掉(或者有if语句与其内容一起优化,给出一些非通用的答案)

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

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


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


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

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

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

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

register int boolvar asm ("ecx");

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

test ecx, ecx
jz ...

在处理器上更快。

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

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

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

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

x86中的条件分支取决于标志寄存器中的值。 为了编译为单个指令,其他一些代码将需要设置正确的标志,所以剩下的就是像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

我的假设是错的

你有几个假设是错误的。 首先你应该知道1条指令不一定比多条指令快。 例如,在较新的μarchs中, test可以与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的答案一样直接跳转

另外86具有有条件的移动,因此,如果内if是一个简单的赋值那么它可能在1个指令来完成。 下面是一个示例及其输出

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;
}

编译为单个xor而没有任何跳转

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM