x86-64 上的 C++:何时在寄存器中传递和返回结构/类?
假设 Linux 上的 x86-64 ABI,在 C++ 中,在什么条件下,结构会传递给寄存器中的函数还是堆栈中的函数?在什么条件下它们会在寄存器中返回?课程的答案会改变吗?
Assuming the x86-64 ABI on Linux, under what conditions in C++ are structs passed to functions in registers vs. on the stack? Under what conditions are they returned in registers? And does the answer change for classes?
如果它有助于简化答案,您可以假设一个参数/返回值并且没有浮点值.
If it helps simplify the answer, you can assume a single argument/return value and no floating point values.
推荐答案
定义了 ABI 规范 此处.
此处提供了较新的版本.
The ABI specification is defined here.
A newer version is available here.
我假设读者已经习惯了文档的术语并且他们可以对原始类型进行分类.
I assume the reader is accustomed to the terminology of the document and that they can classify the primitive types.
如果对象大小大于两个八字节,则在内存中传递:
If the object size is larger than two eight-bytes, it is passed in memory:
struct foo
{
unsigned long long a;
unsigned long long b;
unsigned long long c; //Commenting this gives mov rax, rdi
};
unsigned long long foo(struct foo f)
{
return f.a; //mov rax, QWORD PTR [rsp+8]
}
如果是非POD,则在内存中传递:
If it is non POD, it is passed in memory:
struct foo
{
unsigned long long a;
foo(const struct foo& rhs){} //Commenting this gives mov rax, rdi
};
unsigned long long foo(struct foo f)
{
return f.a; //mov rax, QWORD PTR [rdi]
}
复制省略在这里起作用
Copy elision is at work here
如果它包含未对齐的字段,则在内存中传递:
If it contains unaligned fields, it passed in memory:
struct __attribute__((packed)) foo //Removing packed gives mov rax, rsi
{
char b;
unsigned long long a;
};
unsigned long long foo(struct foo f)
{
return f.a; //mov rax, QWORD PTR [rsp+9]
}
<小时>
如果以上都不为真,则考虑对象的字段.
如果字段之一本身是结构/类,则递归应用该过程.
目标是对对象中的两个八字节 (8B) 中的每一个进行分类.
If none of the above is true, the fields of the object are considered.
If one of the field is itself a struct/class the procedure is recursively applied.
The goal is to classify each of the two eight-bytes (8B) in the object.
考虑每个8B的字段的类别.
请注意,由于上述对齐要求,整数个字段总是完全占用一个 8B.
The the class of the fields of each 8B are considered.
Note that an integral number of fields always totally occupy one 8B thanks to the alignment requirement of above.
设置C为8B类,D为考虑类中的字段类.
让 new_class
伪定义为
Set C be the class of the 8B and D be the class of the field in consideration class.
Let new_class
be pseudo-defined as
cls new_class(cls D, cls C)
{
if (D == NO_CLASS)
return C;
if (D == MEMORY || C == MEMORY)
return MEMORY;
if (D == INTEGER || C == INTEGER)
return INTEGER;
if (D == X87 || C == X87 || D == X87UP || C == X87UP)
return MEMORY;
return SSE;
}
那么8B的类计算如下
C = NO_CLASS;
for (field f : fields)
{
D = get_field_class(f); //Note this may recursively call this proc
C = new_class(D, C);
}
一旦我们有了每个 8B 的类,比如说 C1 和 C2,那么
Once we have the class of each 8Bs, say C1 and C2, than
if (C1 == MEMORY || C2 == MEMORY)
C1 = C2 = MEMORY;
if (C2 == SSEUP AND C1 != SSE)
C2 = SSE;
注意这是我对 ABI 文档中给出的算法的解释.
Note This is my interpretation of the algorithm given in the ABI document.
示例
struct foo
{
unsigned long long a;
long double b;
};
unsigned long long foo(struct foo f)
{
return f.a;
}
8B 及其领域
前 8B:a
第二个 8B:b
a
是整数,所以第一个 8B 是整数.b
是 X87 和 X87UP,所以第二个 8B 是 MEMORY.最后一节课是两个 8B 的 MEMORY.
a
is INTEGER, so the first 8B is INTEGER.
b
is X87 and X87UP so the second 8B is MEMORY.
The final class is MEMORY for both 8Bs.
示例
struct foo
{
double a;
long long b;
};
long long foo(struct foo f)
{
return f.b; //mov rax, rdi
}
8B 及其领域
前 8B:a
第二个 8B:b
a
是 SSE,所以第一个 8B 是 SSE.b
是整数,所以第二个 8B 是整数.
a
is SSE, so the first 8B is SSE.
b
is INTEGER so the second 8B is INTEGER.
最终的类是计算出来的.
The final classes are the one calculated.
根据它们的类返回值:
内存
调用者将隐藏的第一个参数传递给函数,以便将结果存储到其中.
在 C++ 中,这通常涉及复制省略/返回值优化.这个地址必须返回到eax
,从而将 MEMORY 类通过引用"返回到一个隐藏的、调用者、分配的缓冲区.
MEMORY
The caller passes an hidden, first, argument to the function for it to store the result into.
In C++ this often involves a copy elision/return value optimisation. This address must be returned back intoeax
, thereby returning MEMORY classes "by reference" to an hidden, caller, allocated buffer.
如果类型有类 MEMORY,则调用者提供返回空间value 并在 %rdi 中传递这个存储的地址,就好像它是第一个函数的参数.实际上,这个地址首先变成了隐藏的"争论.返回时 %rax 将包含由%rdi 中的调用者.
If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first argument to the function. In effect, this address becomes a "hidden" first argument. On return %rax will contain the address that has been passed in by the caller in %rdi.
INTEGER 和 POINTER
根据需要注册 rax
和 rdx
.
SSE 和 SSEUP寄存器 xmm0
和 xmm1
根据需要.
SSE and SSEUP
The registers xmm0
and xmm1
as needed.
X87 和 X87UP寄存器 st0
技术定义是这里.
ABI 的定义报告如下.
The definition from the ABI is reported below.
如果一个 de/constructor 是一个隐式声明的默认 de/constructor 并且如果:
A de/constructor is trivial if it is an implicitly-declared default de/constructor and if:
? 它的类没有虚函数和虚基类,并且
? 其类的所有直接基类都有简单的析构函数,并且
? 对于其类的所有非静态数据成员,属于类类型(或其数组),每个这样的类都有一个简单的 de/constructor.
? its class has no virtual functions and no virtual base classes, and
? all the direct base classes of its class have trivial de/constructors, and
? for all the nonstatic data members of its class that are of class type (or array thereof), each such class has a trivial de/constructor.
<小时>
请注意,每个 8B 都是独立分类的,以便可以相应地通过.
特别是,如果没有更多的参数寄存器,它们可能会在堆栈中结束.
Note that each 8B is classified independently so that each one can be passed accordingly.
Particularly, they may end up on the stack if there are no more parameter registers left.
相关文章