了解一个函数的汇编指令,该函数对 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

我目前对程序集的解释是发生了以下情况:

  1. 64 位从 64 位输入寄存器 rdi 移动到 64 位输出寄存器 rax。这对应于 32 位整数。
  2. shr 将 rax 的内容移动 32 位,因此只保留 rdi 中包含的前 32 位 int。
  3. 将32位输入寄存器edi的内容加到32位输出寄存器eax
  4. 将第二个32位输入寄存器esi的内容加到eax
  5. 返回eax

但是我还有一些问题:

  1. 计算机能否像前两条指令那样简单地在 32 位和 64 位寄存器之间移动?
  2. 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 ints 结构。

因此,两个 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是第三个。