我正在尝试在我的specpoline (cfr. Henry Wong)上创建一个Kabe lake 7600U,我正在运行CentOS 7。
完整的测试存储库可在GitHub上找到。
我的版本的specpoline如下(cfr. 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用于防止硬件预取。
下面是图形描述(每个索引都在另一个之后)。
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_spec
和make 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。 SKZ的fcos
为53-105 uops,循环吞吐量为50-130。因此,每个uop延迟约为1个周期,而调度程序/预留站(RS)“仅”在SKL / KBL中有97个条目。此外,将后续指令送入无序后端可能是一个问题,因为微代码接管前端并需要某种机制来决定接下来要发出哪些uop,这可能取决于某些计算的结果。 (已知uop的数量与数据有关。)
如果你想从RS充满未执行的uops的最大延迟,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)What exactly happens when a skylake CPU mispredicts a branch?的分支订单缓冲区快速恢复分支未命中。因此,他们可以完全恢复到错误预测,而无需等待错误预测成为退休状态。
mov [rsp], rax
可以在进入RS后立即执行,或者至少不依赖于sqrt
dep链。只要存储转发可以产生该值,ret
uop就可以执行并检查预测,并在sqrt dep链仍处于运算状态时检测错误预测。 (ret
是1个微融合uop,用于加载端口+端口6,其中采用分支执行单元。)
将sqrtsd
dep链耦合到存储新的返回地址可以防止ret
提前执行。在执行端口执行ret
uop =检查预测并检测错误预测(如果有)。
(与Meltdown形成对比,“错误”路径一直运行,直到故障负载达到退役状态,并且你希望它尽快执行(只是不退休)。但是你通常希望将整个Meltdown攻击置于别的阴影之下,像TSX或specpoline,在这种情况下你需要这样的东西,并在这个dep链的阴影下完全崩溃。然后Meltdown不需要它自己的sqrtsd
dep链。)
(vsqrtpd ymm
在SKL上仍然是1 uop,吞吐量比xmm差,但它具有相同的延迟。所以使用sqrtsd
因为它的长度相同,并且可能更节能。)
最好的延迟是15个周期,而最差的情况是16个SKL / KBL(https://agner.org/optimize),所以你开始的输入几乎不重要。
我最初使用sqrtpd得到了类似的结果。但是我没有初始化用作输入(和输出)的XMM寄存器,认为它无关紧要。我再次测试,但这次我用两个值1e200的双倍初始化寄存器,我得到的是间歇性结果。有时这条线是在某些时候推测性地获取的。
如果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 -etask-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微秒,并且当它背靠背发生时吞吐量成本约为160个周期。
对于输入= NaN(全1)的20M uops发布总数,每次迭代4.5个周期。 (循环运行10M sqrtpd
uops,以及10M宏融合dec / jcc uops。)