ABI规范定义here http://refspecs.linuxbase.org/elf/x86_64-abi-0.21.pdf.
有新版本可用here https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf.
我假设读者习惯了文档的术语并且他们可以对原始类型进行分类。
如果对象大小大于两个八字节,则将其传递到内存中:
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,则在内存中传递:
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 http://en.cppreference.com/w/cpp/language/copy_elision is at work here
如果它包含未对齐的字段,则它会在内存中传递:
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) 中的每一个进行分类。
考虑每个8B的字段的类别。
请注意,由于上述对齐要求,整数个字段始终占据 1 个 8B。
Set C是 8B 级并且D是考虑类别中字段的类别。
Let new_class
被伪定义为
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,那么
if (C1 == MEMORY || C2 == MEMORY)
C1 = C2 = MEMORY;
if (C2 == SSEUP AND C1 != SSE)
C2 = SSE;
Note这是我对ABI文档中给出的算法的解释。
Example
struct foo
{
unsigned long long a;
long double b;
};
unsigned long long foo(struct foo f)
{
return f.a;
}
8B 及其领域
First 8B: a
第二个8B: b
a
是 INTEGER,所以前 8B 是 INTEGER。b
是X87和X87UP,所以第二个8B是MEMORY。
两个 8B 的最后一个类别都是 MEMORY。
Example
struct foo
{
double a;
long long b;
};
long long foo(struct foo f)
{
return f.b; //mov rax, rdi
}
8B 及其领域
First 8B: a
第二个8B: b
a
是SSE,所以第一个8B是SSE。
b
是 INTEGER,所以第二个 8B 是 INTEGER。
最终的班级是计算出来的。
返回值
这些值将相应地返回到它们的类:
-
MEMORY
调用者将一个隐藏的第一个参数传递给函数,以便将结果存储到其中。
在 C++ 中,这通常涉及复制省略/返回值优化。
该地址必须返回到eax
,从而返回MEMORY类“通过引用”到隐藏的调用者分配的缓冲区。
如果类型具有 MEMORY 类,则调用者为返回提供空间
值并在 %rdi 中传递此存储的地址,就好像它是第一个一样
函数的参数。实际上,该地址首先成为“隐藏”地址
争论。
返回时 %rax 将包含由 %rax 传入的地址
%rdi 中的调用者。
INTEGER and POINTER
寄存器rax
and rdx
如所须。
SSE and SSEUP寄存器xmm0
and xmm1
如所须。
X87 AND X87UP登记册st0
PODs
技术定义是here http://en.cppreference.com/w/cpp/concept/PODType.
ABI 的定义如下。
如果 de/构造函数是隐式声明的默认 de/构造函数并且如果:
• 它的类没有虚函数和虚基类,并且
• 其类的所有直接基类都有简单的 de/构造函数,并且
• 对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类都有一个简单的析构函数/构造函数。
请注意,每个 8B 都是独立分类的,因此每个 8B 都可以相应通过。
特别是,如果没有剩余的参数寄存器,它们可能最终会出现在堆栈上。