为什么这个规格的显示器在Kaby Lake上不能正常工作?

5

我正在尝试在我的Kabe Lake 7600U上创建一个 specpoline (参见 Henry Wong),我使用的是CentOS 7。

完整的测试存储库可在GitHub上找到。

我的specpoline版本如下(参见spec.asm):

specpoline:
        ;Long dependancy chain
        fld1
        TIMES 4 f2xm1
        fcos
        TIMES 4 f2xm1
        fcos
        TIMES 4 f2xm1

        %ifdef ARCH_STORE
            mov DWORD [buffer], 241     ;Store in the first line
        %endif

        add rsp, 8
        ret

这个版本与Henry Wong的版本不同之处在于流程被转移为架构路径。原版使用固定地址,而我将目标传递到堆栈中。这样一来,add rsp, 8 将会删除原始返回地址并使用人工地址。
在函数的第一部分,我使用一些旧的FPU指令创建一个长延迟依赖链,然后再创建一个独立的链试图欺骗CPU返回栈预测器。

代码描述

使用FLUSH+RELOAD1将specpoline插入到分析上下文中,同一汇编文件还包含:

buffer

一个连续的缓冲区跨越了256个不同的高速缓存行,每个高速缓存行之间由GAP-1行分隔,总共为256*64*GAP字节。

GAP用于防止硬件预取。

下面是图形化描述(每个索引紧随其后)。

F+R buffer

timings

一个由256个DWORD组成的数组,每个条目都保存着访问F+R缓冲区中相应行所需的核心周期时间。

flush

一个小函数,用于触碰F+R缓冲区中的每一页(使用存储器操作,确保COW在我们这一边),并驱逐指定的行。

'profile`

标准的配置文件函数,使用lfence+rdtsc+lence来从F+R缓冲区的每一行加载数据,并将结果存储在timings数组中。

leak

这是真正工作的函数,调用specpoline在推测路径上放置一个存储器操作,并在体系结构路径上调用profile函数。

;Flush the F+R lines
        call flush

        ;Unaligned stack, don't mind
        lea rax, [.profile]
        push rax
        call specpoline

        ;O.O 0
        ; o o o SPECULATIVE PATH
        ;0.0 O

        %ifdef SPEC_STORE
            mov DWORD [buffer], 241        ;Just a number
        %endif

        ud2                             ;Stop speculation

   .profile:
        ;Ll Ll
        ;  !  !  ARCHITECTURAL PATH
        ;Ll Ll
        
        ;Fill the timings array
        call profile

一个小的C程序被用来“引导”测试工具。

运行测试

代码使用预处理条件来有条件地在架构路径(实际上是在specpoline本身中)放置存储,如果定义了ARCH_STORE,并且有条件地在推测路径中放置存储,如果定义了SPEC_STORE

两个存储访问F+R缓冲区的第一行。

运行make run_specmake run_arch将使用相应的符号汇编spec.asm,编译测试并运行它。

测试显示F+R缓冲区每行的时间。

在架构路径中存储

 38    230    258    250    212    355    230    223    214    212    220    216    206    212    212    234
213    222    216    212    212    210   1279    222    226    301    258    217    208    212    208    212
208    208    208    216    210    212    214    213    211    213    254    216    210    224    211    209
258    212    214    224    220    227    222    224    208    212    212    210    210    224    213    213
207    212    254    224    209    326    225    216    216    224    214    210    208    222    213    236
234    208    210    222    228    223    208    210    220    212    258    223    210    218    210    218
210    218    212    214    208    209    209    225    206    208    206   1385    207    226    220    208
224    212    228    213    209    226    226    210    226    212    228    222    226    214    230    212
230    211    226    218    228    212    234    223    228    216    228    212    224    225    228    226
228    242    268    226    226    229    224    226    224    212    299    216    228    211    226    212
230    216    228    224    228    216    228    218    228    218    227    226    230    222    230    225
228    226    224    218    225    252    238    220    229   1298    228    216    228    208    230    225
226    224    226    210    238    209    234    224    226    255    230    226    230    206    227    209
226    224    228    226    223    246    234    226    227    228    230    216    228    211    238    216
228    222    226    227    226    240    236    225    226    212    226    226    226    223    228    224
228    224    229    214    224    226    224    218    229    238    234    226    225    240    236    210

存储于投机路径中

298    216    212    205    205   1286    206    206    208    251    204    206    206    208    208    208
206    206    230    204    206    208    208    208    210    206    202    208    206    204    256    208
206    208    203    206    206    206    206    206    208    209    209    256    202    204    206    210
252    208    216    206    204    206    252    232    218    208    210    206    206    206    212    206
206    206    206    242    207    209    246    206    206    208    210    208    204    208    206    204
204    204    206    210    206    208    208    232    230    208    204    210   1287    204    238    207
207    211    205    282    202    206    212    208    206    206    204    206    206    210    232    209
205    207    207    211    205    207    209    205    205    211    250    206    208    210    278    242
206    208    204    206    208    204    208    210    206    206    206    206    206    208    204    210
206    206    208    242    206    208    206    208    208    210    210    210    202    232    205    207
209    207    211    209    207    209    212    206    232    208    210    244    204    208    255    208
204    210    206    206    206   1383    209    209    205    209    205    246    206    210    208    208
206    206    204    204    208    246    206    206    204    234    207    244    206    206    208    206
208    206    206    206    206    212    204    208    208    202    208    208    208    208    206    208
250    208    214    206    206    206    206    208    203    279    230    206    206    210    242    209
209    205    211    213    207    207    209    207    207    211    205    203    207    209    209    207

我在体系结构路径中放置了一个存储器来测试时序功能,似乎可以工作。
然而,我无法通过投机路径获得相同的结果。
为什么CPU没有进行投机执行存储操作?

1 我承认我从未花时间区分所有缓存分析技术。我希望我使用了正确的名称。通过FLUSH+RELOAD,我指的是清除一组行,推测执行一些代码,然后记录访问每个清除行所需的时间的过程。


你的“长依赖链”与那些微码化的x87指令相差很远,它包含了许多uops。在SKL上,fcos需要53-105个uops,并且吞吐量为50-130个周期。因此,每个uop的延迟大约为1个周期。如果你想要在RS中放置最多的uop并产生最大的延迟,那么sqrtpd依赖链可能是你最好的选择。 - Peter Cordes
1
@PeterCordes 我最初使用了sqrtpd并得到了类似的结果。然而,我没有初始化用作输入(和输出)的XMM寄存器,认为这并不重要。我再次进行了测试,但这次我使用了两个值为1e200的双精度数来初始化寄存器,结果是间歇性的。有时候该行会被推测获取,有时候则不会。似乎movups xmm0, [...]后跟十个sqrtpd xmm0, xmm0是最佳组合,尽管不是100%可靠。现在有点晚了,我明天会更新答案。感谢您的建议! - Margaret Bloom
1个回答

4
你的“长依赖链”与那些微码x87指令相差很远。fcos 在SKL上需要53-105个uops,吞吐量为50-130个周期。因此,它的延迟是每个uop一个周期,并且调度器/预约站(RS)在SKL/KBL中“仅”有97个条目。此外,将后续指令送入乱序后端可能会成为问题,因为微码接管了前端,并且需要某种机制来决定下一步发出哪些uops,这可能取决于某些计算结果。(已知uops的数量取决于数据。)
如果你想要从充满未执行uops的RS中获得最大延迟,则sqrtpd 依赖链可能是最好的选择。例如:
    xorps  xmm0,xmm0                   ; avoid subnormals that might trigger FP assists
    times 40 sqrtsd xmm0, xmm0

    ; then make the store of the new ret addr dependent on that chain
    movd   ebx, xmm0
    ; and  ebx, 0            ; not needed, sqrt(0) = 0.0 = integer bit pattern 0
    mov [rsp+rbx], rax
    ret

自 Nehalem 以来,英特尔 CPU 通过分支顺序缓冲区实现了快速恢复分支失误的能力,该缓冲区快照了 OoO 状态(包括 RAT 和可能的 RS)当 Skylake CPU 预测错误分支时会发生什么?因此,它们可以恢复到错误预测的确切状态,而不必等待错误预测成为退役状态。 mov [rsp], rax 可以在进入 RS 后立即执行,或者至少不依赖于 sqrt 依赖链。只要存储转发可以产生值,ret uop 就可以执行并检查预测,同时在 sqrt 依赖链仍在计算时检测到错误预测。(ret 是用于加载端口和端口 6 的 1 个微融合 uop,其中执行单元是 taken-branch。) 将 sqrtsd 依赖链与存储新的返回地址耦合可以防止 ret 过早执行执行 执行端口中的 ret uop = 检查预测并检测是否有错误预测。
与Meltdown相比,错误路径会一直运行,直到出现故障的负载达到退役为止,而您希望它尽快执行(只是不要退役)。但通常情况下,您希望将整个Meltdown攻击放在其他东西的阴影下,例如TSX或specpoline,在这种情况下,您需要像这样的东西,并将整个Meltdown放在此dep链的阴影下。然后Meltdown就不需要自己的sqrtsd dep链了。
"

(vsqrtpd ymm仍然是SKL上的1个uop,与xmm相比吞吐量更差,但具有相同的延迟。因此使用sqrtsd,因为它具有相同的长度,并且可能更节能。)

在SKL/KBL上,最佳情况下的延迟为15个周期,最坏情况下为16个周期(https://agner.org/optimize),因此无论从哪个输入开始,都几乎没有影响。

"
我最初使用了sqrtpd并得到了类似的结果。然而,我没有初始化用作输入(和输出)的XMM寄存器,认为这并不重要。我再次进行了测试,但这次我使用了两个值为1e200的double来初始化寄存器,结果是不稳定的。有时候该行会被推测性地获取,有时候则不会。

如果XMM0包含一个子规范数(例如,位模式是一个小整数),sqrtpd将需要微码辅助(fp_assist.any perf counter)。即使结果是正常的,但输入是子规范的也是如此。我在SKL上测试了这两种情况,使用以下循环:

  pcmpeqd   xmm0,xmm0
  psrlq     xmm0, 61        ; or 31 for a subnormal input whose sqrt is normalized
  addpd     xmm0,xmm0       ; avoid domain-crossing vec-int -> vec-fp weirdness

  mov   ecx, 10000000
.loop:
    sqrtpd  xmm1, xmm0
    dec    ecx
    jnz   .loop

 mov eax,1
 int 0x80   ; sys_exit

perf stat -e task-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread,fp_assist.any 显示对于非规范化输入,每次迭代会有1个辅助,发射了951M uops(每次迭代用时约160个周期)。因此,在这种情况下,可以得出sqrtpd的微码辅助需要大约95个uops,并且当它连续发生时,吞吐成本为约160个周期。

与输入为NaN(全1)的总共发射20M uops相比,每次迭代用时4.5个周期。 (循环运行10M sqrtpd uops和10M宏融合dec/jcc uops。)


1
我用存储到[rsp]来替换add rsp, 8,以便更加稳定地获得结果,就像Henry Wong所做的那样。也许是add指令的调度不完美之类的原因,我也说不清楚。总之,你的观察帮了我很多忙。 - Margaret Bloom
2
实际上,我必须使用“ xorps xmm0,xmm0 / TIMES 10 sqrtpd xmm0,xmm0 / movq rbx,xmm0 / mov [rsp + rbx],rax / ret”来创建单个依赖链。否则,CPU会在执行存储到“[rsp]”之前过早地进行校正,以使回调栈预测器的预测得到修正,直到推测路径执行得太远。 - Margaret Bloom
1
@MargaretBloom:啊,对了。自 Nehalem 以来,英特尔 CPU 具有分支缺失的快速恢复功能,其中分支顺序缓冲区会快照 OoO 状态(包括 RAT 和可能的 RS)当 Skylake CPU 预测错误分支时会发生什么?。因此,它可以精确地恢复到错误预测状态,而无需等待错误预测成为退役状态。我没有仔细查看你正在做什么;在 Meltdown 中,“错误”的路径会一直运行,直到故障负载达到退役状态,但这里不是这种情况。 - Peter Cordes
1
@MargaretBloom:已更新我的答案,感谢您提供的详细信息,让我明白了如何解决问题。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接