了解一个函数的汇编指令,该函数对 std::array 的三个整数求和
Understanding assembly instructions for a function summing three ints of an std::array
我有以下 C++ 函数,它简单地对给定输入数组的三个元素求和。
#include <array>
using namespace std;
int square(array<int, 3> ar) {
int res = 0;
for(int idx = 0; idx < ar.size(); idx++){
res += ar[idx];
}
return res;
}
此代码使用 Clang 编译(gcc 和 icc 生成相同的代码)和编译器标志 -O3 生成以下 x86-64 程序集
sum(std::array<int, 3ul>):
mov rax, rdi
shr rax, 32
add eax, edi
add eax, esi
ret
我目前对程序集的解释是发生了以下情况:
- 64 位从 64 位输入寄存器 rdi 移动到 64 位输出寄存器 rax。这对应于 32 位整数。
- shr 将 rax 的内容移动 32 位,因此只保留 rdi 中包含的前 32 位 int。
- 将32位输入寄存器edi的内容加到32位输出寄存器eax
- 将第二个32位输入寄存器esi的内容加到eax
- 返回eax
但是我还有一些问题:
- 计算机能否像前两条指令那样简单地在 32 位和 64 位寄存器之间移动?
- shr 的使用不应该导致第一个 int 被添加两次,因为第二个 int 被移出了吗? (这跟endianes有关系吗?)
额外说明:当提供基于范围的 for 循环时,编译器会生成相同的汇编指令。
#include <array>
using namespace std;
int sum(array<int, 3> ar) {
int res = 0;
for(const auto& in: ar){
res += in;
}
return res;
}
您可以在此处找到示例:https://godbolt.org/z/s3fera7ca
数组被打包到寄存器中用于参数传递,就好像它是一个简单的 3 int
s 结构。
因此,两个 32 位 int
元素在第一个参数寄存器中传递,其余一个在第二个参数寄存器中传递。
鉴于此示例中不涉及内存,前两个如何打包到一个寄存器中似乎有些武断,而且需要明确的是,寄存器本身没有字节顺序的概念。字节序是由采用多个内存地址的数字数据引入的——而不是由寄存器中或寄存器中的任何内容引入:寄存器只能命名(在机器代码指令中),但不能寻址,因此,没有字节序的概念在寄存器中。
但是,对于确实涉及从内存存储和加载相同结构的其他一些操作,如果该打包遵循处理器的字节序,那么这是有效的,因此这是 ABI 设计者的合理选择,他们指定(按规则)结构的第一个元素、第二个元素和第三个元素在作为寄存器中的参数传递时的位置。
当遵循处理器字节序时,程序可以使用四字加载或存储和双字加载或存储来复制结构 — 64 位操作后跟 32 位操作。如果寄存器中没有遵循处理器的自然字节顺序(实际上仍然有效),那么将需要三个双字加载或存储操作,以获取数组元素 from/into 内存的正确顺序。
通过遵循自然字节序,机器代码可以混合 64 位和 32 位加载和存储操作,即使该结构仅包含 32 位项目。
How does edi fit into this?
edi
是array/structure的第一个元素。 rdi
>>
32
是第 2 个元素,因为它被打包到 rdi
的高 32 位,而第一个元素被打包到 [= 的低 32 位13=]。而esi
是第三个。
我有以下 C++ 函数,它简单地对给定输入数组的三个元素求和。
#include <array>
using namespace std;
int square(array<int, 3> ar) {
int res = 0;
for(int idx = 0; idx < ar.size(); idx++){
res += ar[idx];
}
return res;
}
此代码使用 Clang 编译(gcc 和 icc 生成相同的代码)和编译器标志 -O3 生成以下 x86-64 程序集
sum(std::array<int, 3ul>):
mov rax, rdi
shr rax, 32
add eax, edi
add eax, esi
ret
我目前对程序集的解释是发生了以下情况:
- 64 位从 64 位输入寄存器 rdi 移动到 64 位输出寄存器 rax。这对应于 32 位整数。
- shr 将 rax 的内容移动 32 位,因此只保留 rdi 中包含的前 32 位 int。
- 将32位输入寄存器edi的内容加到32位输出寄存器eax
- 将第二个32位输入寄存器esi的内容加到eax
- 返回eax
但是我还有一些问题:
- 计算机能否像前两条指令那样简单地在 32 位和 64 位寄存器之间移动?
- shr 的使用不应该导致第一个 int 被添加两次,因为第二个 int 被移出了吗? (这跟endianes有关系吗?)
额外说明:当提供基于范围的 for 循环时,编译器会生成相同的汇编指令。
#include <array>
using namespace std;
int sum(array<int, 3> ar) {
int res = 0;
for(const auto& in: ar){
res += in;
}
return res;
}
您可以在此处找到示例:https://godbolt.org/z/s3fera7ca
数组被打包到寄存器中用于参数传递,就好像它是一个简单的 3 int
s 结构。
因此,两个 32 位 int
元素在第一个参数寄存器中传递,其余一个在第二个参数寄存器中传递。
鉴于此示例中不涉及内存,前两个如何打包到一个寄存器中似乎有些武断,而且需要明确的是,寄存器本身没有字节顺序的概念。字节序是由采用多个内存地址的数字数据引入的——而不是由寄存器中或寄存器中的任何内容引入:寄存器只能命名(在机器代码指令中),但不能寻址,因此,没有字节序的概念在寄存器中。
但是,对于确实涉及从内存存储和加载相同结构的其他一些操作,如果该打包遵循处理器的字节序,那么这是有效的,因此这是 ABI 设计者的合理选择,他们指定(按规则)结构的第一个元素、第二个元素和第三个元素在作为寄存器中的参数传递时的位置。
当遵循处理器字节序时,程序可以使用四字加载或存储和双字加载或存储来复制结构 — 64 位操作后跟 32 位操作。如果寄存器中没有遵循处理器的自然字节顺序(实际上仍然有效),那么将需要三个双字加载或存储操作,以获取数组元素 from/into 内存的正确顺序。
通过遵循自然字节序,机器代码可以混合 64 位和 32 位加载和存储操作,即使该结构仅包含 32 位项目。
How does edi fit into this?
edi
是array/structure的第一个元素。 rdi
>>
32
是第 2 个元素,因为它被打包到 rdi
的高 32 位,而第一个元素被打包到 [= 的低 32 位13=]。而esi
是第三个。