不,并且它没有意义在高级别的 nop 操作映射到程序集 nop。为什么?因为as if rule 声明编译器“需要模拟(仅)可观察行为”。根据定义,nop 操作没有任何可观察的行为(在标准中定义的“抽象机器”上)。
从更实际的方面来看,从 C++ 源文件生成的汇编指令与 C++ 指令没有一对一的关系。大多数情况下,一条 C++ 指令由多条汇编指令组成,多条 C++ 指令被一条汇编指令替换,甚至一些 C++ 指令由于算法转换或死代码消除以及在该指令之上没有任何类型的汇编对应部分重新排列,算法在各处进行了转换。
例如
我能想到的最激进的例子之一就是编译器可以做的算法转换是将递归函数转换为简单的迭代循环:
auto sum(int* v, int len)
{
if (len == 0)
return 0;
return v[0] + sum(v + 1, len - 1);
}
一个简单的递归函数,用于计算向量元素的总和。
这是clang 与-O1 生成的:
sum(int*, int): # @sum(int*, int)
xor eax, eax
test esi, esi
je .LBB0_2
.LBB0_1: # =>This Inner Loop Header: Depth=1
add eax, dword ptr [rdi]
add rdi, 4
add esi, -1
jne .LBB0_1
.LBB0_2:
ret
从递归到循环的巧妙算法转换。 nop C++ 指令适合在程序集中的什么位置?
在-O3生成的程序集中怎么样?:
sum(int*, int): # @sum(int*, int)
test esi, esi
je .LBB0_1
lea edx, [rsi - 1]
add rdx, 1
xor eax, eax
cmp rdx, 8
jae .LBB0_4
mov rcx, rdi
jmp .LBB0_7
.LBB0_1:
xor eax, eax
ret
.LBB0_4:
mov r8d, esi
and r8d, 7
sub rdx, r8
sub esi, edx
lea rcx, [rdi + 4*rdx]
add rdi, 16
pxor xmm0, xmm0
pxor xmm1, xmm1
.LBB0_5: # =>This Inner Loop Header: Depth=1
movdqu xmm2, xmmword ptr [rdi - 16]
paddd xmm0, xmm2
movdqu xmm2, xmmword ptr [rdi]
paddd xmm1, xmm2
add rdi, 32
add rdx, -8
jne .LBB0_5
paddd xmm1, xmm0
pshufd xmm0, xmm1, 78 # xmm0 = xmm1[2,3,0,1]
paddd xmm0, xmm1
pshufd xmm1, xmm0, 229 # xmm1 = xmm0[1,1,2,3]
paddd xmm1, xmm0
movd eax, xmm1
test r8d, r8d
je .LBB0_8
.LBB0_7: # =>This Inner Loop Header: Depth=1
add eax, dword ptr [rcx]
add rcx, 4
add esi, -1
jne .LBB0_7
.LBB0_8:
ret
编译器在这里进行一些循环展开。循环现在被转换为一个头部,它使所有元素达到倍数,一个主体,其中使用矢量化同时添加对齐的元素组,以及一个尾部,它获取无法填充对齐组的其余元素.而且……有趣的是……原来的 C++ 源代码甚至没有循环。因此,如果您在源代码中有nop,您会将它放在程序集中的什么位置?它没有可观察到的效果,因此编译器没有理由可以利用它来确定将它放在这个经过大量转换的代码中的什么位置。
即使您想到了一些巧妙的规则并设法指定C++ nop 将映射到程序集的位置,那会有多大用处?程序集nop 的用途与程序算法无关,但与架构实现细节有关,例如 RAW 依赖项等。使用 C++,您无需对架构细节进行建模(至少不是在标准 C++ 中),而是与架构无关的算法,因此您无法拥有完全与架构细节相关的指令。
问题是 C++ 确实有一个 nop 指令。它是空语句;;。它在 C++ 语法和语义级别上很有用:
// find the end of the string:
for (const ch* end = str; *ch != '\0'; ++end)
; // <-- empty statement. A nop instruction.
但是由于上述规则和共鸣,生成程序集nop 指令没有意义。