assembly - 为什么 GCC 选择 dword movl 将长移位计数复制到 CL?
问题描述
在《计算机系统:程序员的视角》第三章中,在谈到移位操作时,给出了一个示例程序:
long shift_left4_rightn(long x, long n)
{
x <<= 4;
x >>= n;
return x;
}
它的汇编代码如下(可在 Godbolt 编译器资源管理器上使用 x86-64 的 GCC10.2重现-O1
。 -O2
以不同的顺序安排指令,但仍用于movl
ECX):
shift_left4_rightn:
endbr64
movq %rdi, %rax Get x
salq $4, %rax x <<= 4
movl %esi, %ecx Get 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 位,其中写入合并到旧值而不是零扩展)。 为什么 GCC 不使用部分寄存器?- 由于不同微架构的各种原因,包括在这种情况下对 RCX 旧值的错误依赖。
- 首选 32 位操作数大小,因为它是 x86-64 机器代码中的默认值,不需要任何前缀。对于任何指令,它至少与任何其他操作数大小一样快。
有趣的事实:即使 arg 是uint8_t
,编译仍然希望使用movl %esi, %ecx
. 当 arg 值仅在 SIL 中时,您会认为读取更宽的寄存器可能会造成部分寄存器停顿,但 x86-64 System V 调用约定的非官方扩展是调用者应将窄 args 扩展为零或符号至少32 位。所以我们可以假设它是用至少 32 位操作编写的。
其他一些选择的具体缺点:
movq %rsi, %rcx
- 浪费一个 REX 前缀(代码大小的缺点)。movb %sil, %cl
- 写入部分寄存器,但仍需要 REX 前缀才能访问 SIL。movzbl %sil, %ecx
- 代码大小:2 字节操作码,需要 REX 才能读取 SIL。此外,AMD CPU 仅对movl
/进行 mov-elimination(零延迟)movq
,而不是 movzx。movw %si, %cx
- 零优势,需要操作数大小的前缀并写入部分寄存器。movzwl %si, %ecx
- 与movq
代码大小相关,但即使在英特尔 CPU 上也无法消除 mov。
有趣的事实:如果我们用一个虚拟 arg 填充,所以n
到达 RDX,GCC 仍然选择movl %edx, %ecx
,即使movb %dl, %cl
代码大小相同(访问 DL 不需要 REX)。所以是的,GCC 绝对是在避免字节操作数大小。
有趣的事实 2:不幸的是,Clang 确实浪费了一个 REX movq
,错过了这个优化。 https://godbolt.org/z/6GWhMd
但如果我们计算 arg unsigned char
,clang 和 GCC 都使用movl
而不是movb
,幸运的是。 https://godbolt.org/z/e95WP8
推荐阅读
- xamarin - 带有 MVVMCross 和 Storyboard 的本机 Xamarin.IOS 应用程序无法正常工作
- visual-studio - UWP 的访问权限
- angular - 如何在 iisnode 上部署 Angular 通用应用程序
- mysql - 如何获取登录错误消息以在 Ruby on Rails 5.2 中工作?
- wso2 - WSO2物联网服务器修改
- vb.net - 嗨,我正在做一个电子抽奖项目
- android - Android短信权限问题
- python - 在 PyTorch 中实现“无限循环”数据集和数据加载器
- android - 无法在本机反应中制作签名的 apk
- html - 让浏览器将本地存储的文件夹视为站点根目录?