assembly 使用条件移动指令的汇编算法中的执行顺序

rjzwgtxy  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(142)

我最近一直在研究一个汇编算法,用于计算两个长整数之间的绝对差,我对存在条件移动指令时的执行顺序有一些担心。下面是一个有问题的汇编代码片段:

.globl _start

_start:
    movq $3, %rsi # x
    movq $5, %rdi # y
    
    movq %rdi, %rax
    subq %rsi, %rdi
    subq %rax, %rsi
    cmovgq %rsi, %rdi
    
    movq $60, %rax
    syscall

字符串
在提供的汇编代码中,cmovgq指令用于根据先前比较的结果(x > y)有条件地移动减法结果(x - yy - x)。我关心的是处理器是否在等待减法结果(subq %rax, %rsi)之前执行cmovgq指令。
我知道减法指令和条件移动指令之间没有显式的数据依赖关系,但是,由于现代处理器的流水线行为,我不确定可能出现的潜在问题。
为了提供上下文,该汇编算法的相应C代码如下:

long absdiff(long x, long y) {
    long result = 0;
    // unpredictable branch
    if(x < y) {
        result = y - x;
    } else {
        result = x - y;
    }
    return result;
}


我将非常感谢社区对这些指令的执行顺序以及流水线是否会在该算法中引入任何问题的任何见解,解释或建议。

0sgqnhkj

0sgqnhkj1#

cmov有3个输入:两个寄存器(显式操作数)和FLAGS(隐式)。

adcsbb相同,除了它们也将FLAGS作为输出写入。
sub写入FLAGS和cmovg阅读FLAGS之间的数据依赖性,每一位都是真实的,就像sub指令在cmovg读取之前写入rsirdi之间的数据依赖性一样。
从形式上讲,这是CPU必须遵守的RAW(写后读)数据危险。参见维基百科了解经典的RISC有序管道和一般的data hazards
(All当前的x86-64 CPU执行乱序执行,调度程序跟踪哪些值已就绪,因此如果有空闲的执行单元,哪些uop已准备好运行。寄存器重命名避免了WAR和WAW危险。请参阅Modern Microprocessors A 90-Minute Guide!和真实的CPU(如Sandybridge)的深入分析,Sandybridge是英特尔当前大核CPU家族的第一个。)

**cmov并不特殊,它只是一个具有3个输入和1个输出的ALU选择操作。**不像32位ARM predicate 指令,其中假 predicate 将其转换为NOP,因此即使从错误地址加载也不会出错。(不像x86 cmov,但像APX cfcmov,有条件地错误cmov条件加载或存储。)
FLAGS像其他寄存器一样被重命名,因此CPU通过FLAGS与通过整数寄存器完全相同地尊重数据依赖性。单个寄存器文件条目可以保存整数寄存器和FLAGS,因此CPU可以将写入FLAGS和寄存器的指令(如sub)视为具有单个输出。

一个写寄存器但不写FLAGS的后续指令(如莱亚)可以只留下指向RAT(寄存器分配表)中较旧物理寄存器的FLAGS,或者一个写FLAGS但不写寄存器的指令(如cmcadd [mem], eax)必须只为FLAGS分配一个PRF条目,而只留下整数寄存器指向的旧条目。

**流水线无序执行的基本规则是,单个线程运行 * 就好像 * CPU按程序顺序一次运行一条指令。**一条指令总是可以看到前面指令的结果,而永远看不到未来指令的结果。如果不是这样,CPU将无法正确实现伊萨规范,无法运行现有代码。即设计将有缺陷。

(这里我只是在谈论正确性,而不是像缓存冲突这样的性能差异--由于早期执行较晚的加载而导致的未命中,这些加载会驱逐地址尚未就绪的加载所需的数据。微体系结构状态可以依赖于时序细节; Spectre和其他CPU漏洞使用时序将微体系结构状态转换为体系结构状态(寄存器或内存中的长期值))
一些ISA的执行模型不是按照程序顺序一次只执行一个。(或asm程序员)找到3(例如)独立的操作以放入一个宽的字中,所以CPU不必检查它们之间的依赖关系或危险。从现在开始,The Mill的加载会产生一个值,比如5条指令(取决于微架构,所以如果发生变化,你必须重新编译)。所以你可以一直阅读旧值,直到那时,但也隐藏了一些加载延迟。
用于x86(以及当前主流的ISA,如RISC-V和AArch 64),执行模型确实是指令按程序顺序一次执行一个。它基本上就像C++ as-if规则:在内部,它可以做任何事情,从而为单个线程产生相同的寄存器和内存值。内存-排序规则定义了查看共享内存的其他线程允许或不允许查看的操作顺序。(内存排序与执行顺序不同,例如,store buffers会自行创建内存重新排序,至少是StoreLoad,即使在有序CPU上也是如此。存储缓冲区对于存储指令的推测执行非常重要。)

相关问题