如何解决为什么GCC选择了dword movl将很长的班次计数复制到CL?
在计算机系统:程序员的视线的第三章中,在讨论移位操作时给出了一个示例程序:
long shift_left4_rightn(long x,long n)
{
x <<= 4;
x >>= n;
return x;
}
其汇编代码如下(可复制GCC10.2 -O1
for x86-64 on the Godbolt compiler explorer。-O2
以不同的顺序安排指令,但仍将movl
用于ECX):
shift_left4_rightn :
endbr64
movq%rdi,%rax 获取x
salq 4美元,%rax x
movl%esi,%ecx 获取n
sarq%cl,%rax x >> = n
ret
我想知道为什么获取n的汇编代码是movl %esi,%ecx
而不是movq %rsi,%rcx
,因为n
是一个四字。
另一方面,如果考虑优化,movb %sil,%cl
可能更合适,因为移位量仅使用单字节寄存器元素%cl
,而那些较高的位都将被忽略。
结果,我真的不知道在处理长整数时使用“ movl%esi,%ecx ”的原因。
解决方法
是的,GCC意识到sar
将忽略高位。
那么movl
是应用两个简单的优化规则的自然结果:
- 避免写入部分寄存器(即8或16位,其中写入会合并到旧值而不是零扩展中)。 Why doesn't GCC use partial registers?-由于不同的微体系结构,出于各种原因,包括在这种情况下对RCX的旧值的错误依赖。
- Prefer 32-bit operand size,因为它是x86-64机器代码中的默认值,不需要任何前缀。而且对于任何指令,它至少与其他任何操作数大小一样快。
有趣的事实:即使arg是uint8_t
,编译程序还是希望使用movl %esi,%ecx
。您可能会认为,当arg值仅在SIL中时,读取更宽的寄存器可能会导致部分寄存器停顿,但是对x86-64 System V调用约定的非官方扩展是callers should zero or sign extend narrow args to at least 32-bit。因此,我们可以假定它是使用至少32位操作编写的。
其他一些选择的具体缺点:
-
movq %rsi,%rcx
-浪费REX前缀(代码大小不足)。 -
movb %sil,%cl
-写入部分寄存器,但仍需要REX前缀才能访问SIL。 -
movzbl %sil,%ecx
-代码大小:2字节操作码,需要REX才能读取SIL。另外,AMD CPU仅对movl
/movq
进行消除运动(零延迟),而不是movzx。 -
movw %si,%cx
-零优势,需要操作数大小的前缀并写入部分寄存器。 -
movzwl %si,%ecx
-与movq
捆绑在一起以获取代码大小,但即使在Intel CPU上也无法消除移动消除。
有趣的事实:如果我们使用虚拟参数填充,使得n
到达RDX,即使movl %edx,%ecx
的代码大小相同,GCC仍会选择movb %dl,%cl
(不需要REX访问DL)。所以是的,GCC绝对避免使用字节操作数大小。
有趣的事实2:不幸的是,Clang确实浪费了movq
上的REX,却错过了此优化。 https://godbolt.org/z/6GWhMd
但是,如果我们将计数设为arg unsigned char
,那么幸运的是clang和GCC都使用movl
而不是movb
。 https://godbolt.org/z/e95WP8
在可能的情况下,编译器更喜欢32位寄存器而不是64位寄存器,因为使用64位寄存器需要额外的“ REX”前缀字节。
对rsi\esi
寄存器的最低字节的选择同样适用,这在32位编码中不可用,因此需要前缀。正如彼得·科德斯(Peter Cordes)所评论的那样,由于称为partial register stalls的时间惩罚,编译器通常避免使用8位寄存器,这是CPU如何检测依赖链,乱序执行和重命名寄存器的内部原因。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。