复制构造函数和 const& 与 ARM ABI

问题描述 投票:0回答:1

在选择按值传递与按常量传递时,我试图理解并利用 ARMv8 ABI。特别是,我有一个结构,用 ARM 术语来说是“同质浮点聚合 (HFA)”。我将使用带有 -O 编译器开关的 armv8-a clang 18.1.0 粘贴来自 godbolt 的一些结果。 (我在使用 Xcode 15 的 Mac 上看到类似的反汇编结果。)

struct Thing {
    const double x,y;
    Thing( double a, double b) : x(a), y(b){}
    Thing( const Thing& other) = default;
    // Thing( const Thing& other) : x(other.x), y(other.y) {}
};

double diff( Thing t) {
    return t.x - t.y;
}

double baz(double x, double y)
{
    Thing i = {x, 5.};
    return diff(i);
}

我将把默认的复制构造函数与我输入的进行比较。如上所述,我得到:

diff(Thing):                          // @diff(Thing)
        fsub    d0, d0, d1
        ret
baz(double, double):                               // @baz(double, double)
        fmov    d1, #-5.00000000
        fadd    d0, d0, d1
        ret

看起来不错。

diff(Thing)
的接口是让调用者将两个双精度值粘贴到寄存器对 (d0,d1) 中,并在寄存器 d0 中返回答案。任何地方都没有访问内存——没有
SP
LP
的破坏;没有在任何地方创建堆栈框架;没有必要让争论永远存在于记忆中。而且
baz(double, double)
看起来也不错。一切都内嵌了——没有
Thing
甚至变得栩栩如生。

但是当我切换到另一个版本的复制构造函数时,我得到了这个

diff(Thing)
:

diff(Thing):                          // @diff(Thing)
        ldp     d0, d1, [x0]
        fsub    d0, d0, d1
        ret

突然,调用者需要将

Thing
放置在内存中的某个位置,并在 x0 中传递指向该内存的指针。然后
diff(Thing)
必须从该内存位置获取这对双精度数。哎呀。

diff(Thing)
签名没有改变,但突然ABI完全不同了!这种巨大的差异是否记录在某处?为什么我的手写复制构造函数不如默认复制构造函数那么好?我很高兴有这么多内联
baz
,但我想知道我可以挂在墙上的最佳实践是什么?例如,对于这样的结构我想要:

bool operator==( const Thing& lhs, const Thing& rhs)

bool operator==( Thing lhs, Thing rhs)

(let the compiler make one)

我想要解释上面的巨大差异以及关于值与常量和参数的建议。

c++ pass-by-reference arm64 pass-by-value abi
1个回答
0
投票

我不是 C++ ABI 专家,但我会尝试一下。

ARM64 与大多数现代平台一样,通常遵循 Itanium C++ ABI,并进行一些与此处不相关的修改。 Itanium ABI 定义了“对于调用来说非常重要”的类型概念,这迫使它“始终在内存中传递,而不是在寄存器中传递”。 如果满足以下条件,则类型被认为对于调用而言是重要的:

它有一个不平凡的复制构造函数、移动构造函数或析构函数,或者

其所有复制和移动构造函数都被删除。
  • “简单复制构造函数”的概念在 C++ 标准中定义,
  • class.copy.ctor p11
:

如果不是的话,类 X 的复制/移动构造函数是微不足道的 用户提供并且如果:

类 X 没有虚函数 ([class.virtual]) 和虚基类 ([class.mi]),并且

选择复制/移动每个直接基类子对象的构造函数很简单,并且
  • 对于 X 的每个类类型(或其数组)的非静态数据成员,选择复制/移动该成员的构造函数是 微不足道;
  • 否则复制/移动构造函数是不平凡的。
您的复制构造函数

Thing( const Thing& other) : x(other.x), y(other.y) {}

由用户提供。因此,它不是一个简单的复制构造函数,因此它使得

Thing
对于调用而言非常重要,因此它不能在寄存器中传递。 您的复制构造函数实际上
所做的事情
与此分析无关,即使它执行与默认复制构造函数完全相同的操作。

© www.soinside.com 2019 - 2024. All rights reserved.