说明了同时表达数据危害和控制危害的代码?

时间:2020-02-29 18:13:16

标签: assembly cpu-architecture

这是数据危害的示例:

MOV AL , 25 
MOV BL , 10 
ADD CL , AL , BL
LOAD DL , CL

在此汇编代码中,我们可以看到LOAD DL指令, 仅当执行ADD CL,AL,BL指令时才能执行CL。 因此,这两个指令都依赖于CL寄存器。 为了处理数据危害问题,我们可以减少 还有每个周期的指令数。这样可以避免寄存器之间的依赖性。

这是冲突危险(分支危险)的示例:

MOV AL , 25  
MOV BL , 10 
CALL branchement
LOAD DL , BL
ADD DL , AL

在此汇编代码中,两条指令LOAD AD,BL和ADD DL, AL甚至在执行指令之前就已经在管道中 呼叫。在这种情况下,不满足进行连接的条件,这会导致控制危险。 为防止此类冲突再次发生,建议您 在加载新指令之前清空管道

我想用同一代码查看两种危害(控制危害+数据危害)的示例

编辑

混合2给我这个:

MOV AL , 25 ; 
MOV BL , 10 ; 
CALL branchement
ADD DL , AL
LOAD DL , BL

1 个答案:

答案 0 :(得分:1)

Actual 8086根本没有流水线(预取除外);它是微码的。在开始下一条指令的解码之前,它完成了对一条指令的写回;唯一的危害是分支之后丢弃预取缓冲区。

x86指令很难流水线化(尤其是内存目标);直到486 / Pentium才真正完成,然后复杂的指令将使有序流水线停滞(基本上是一条指令中的危险,例如add [edx], eaxpop eax)。直到Pentium Pro(P6微体系结构)才可以有效地处理类似的指令(通过解码到1个或更多的微指令,并通过乱序的exec处理这些指令)。请参阅Agner Fog的微体系结构指南https://agner.org/optimize/

(实际的P6-系列和其他乱序的exec x86微体系结构通过寄存器重命名来隐藏WAW和WAR危险。请参见Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables? (Unrolling FP loops with multiple accumulators)Deoptimizing a program for the pipeline in Intel Sandybridge-family CPUs。)

您显示的代码不是严格的x86;没有LOAD助记符; x86的纯加载指令称为mov。同样,LOAD DL, BL没有任何意义;两个操作数都不能寻址内存;它们只是8位寄存器。如果要在寄存器之间复制,那也就是mov dl, bl


我想在同一代码中看到两种危害(控制危害+数据危害)的示例

一个简单的示例是一个间接分支(控制危险),其目标最近被写入(真正的RAW数据依赖性)。

例如如果我们采用16位模式(因为您提到8086):

   push  offset target     ; modifies SP (the stack pointer), then stores to memory at SS:SP
   ret                     ; ordinary near return = pop ip

target:
   push  123

ret有2个输入:

  • SP寄存器(仅由pop写入:RAW危害)
  • SP指向的内存(也只是由pop写入,也有RAW内存危害)。

RET写SP(战争危险,尽管RET本身是最后一个读者)。如果我们认为推入和退回都写SP,也请WAW。

RET使用从内存加载的地址进行间接跳转(基本上是pop ip)(如果有管道,则存在控制危险)。当前所有的CPU都会错误地预测ret,因为它们具有特殊的调用/退出预测变量堆栈,该堆栈假定ret会跳转到匹配的call的返回地址,就像正常的代码用法一样。 (http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/

到达目标地址的push 123

  • 读取和写入SP(RAW和WAR危险)
  • 将上一个推送写入的内存(WAW内存危害)和刚刚读取的RET(WAR内存危害)写入内存。

如果您只想查看ret / push对,请在push之后放置一个ret,并压入一个可能错误预测的分支的“影子”。


当然,具有存储转发功能的存储缓冲区可以隐藏/处理内存数据危害,从而有效地重命名内存/缓存位置。 (x86的内存排序模型基本上是程序顺序+具有存储转发功能的存储缓冲区:允许内核在全局可见之前重新加载其自己的存储。)

现代x86 CPU使用“堆栈引擎”通过堆栈指针处理RAW数据依赖关系链,该堆栈引擎可以跟踪每个时钟周期到堆栈指针的多个偏移量。 (同样重要的是,无需额外的uop即可在后端实际对E / RSP进行添加,因此push / pop可以是单个uop。)因此,这实际上是一种替代零延迟机制,用于执行堆栈指令的堆栈指针修改部分。直接使用E / RSP(例如mov bp, sp)会导致堆栈同步uop(在Intel CPU上),将保存的偏移量清零并将其应用于后端的值。如果偏移量不为零。