Mar*_*oom 11
我假设读者习惯于文档的术语,并且他们可以对原始类型进行分类.
如果对象大小大于两个八字节,则在内存中传递:
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]
}
Run Code Online (Sandbox Code Playgroud)
如果它是非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]
}
Run Code Online (Sandbox Code Playgroud)
复制elision在这里工作
如果它包含未对齐的字段,则会在内存中传递:
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]
}
Run Code Online (Sandbox Code Playgroud)
如果以上都不是真的,则考虑对象的字段.
如果该字段之一本身是结构/类,则递归地应用该过程.
目标是对对象中的两个八字节(8B)中的每一个进行分类.
考虑每个8B的字段类别.
注意,由于上面的对齐要求,整数个字段总是占据一个8B.
集合C是8B的类,D是考虑类的字段的类.
让我们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;
}
Run Code Online (Sandbox Code Playgroud)
那么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);
}
Run Code Online (Sandbox Code Playgroud)
一旦我们得到每个8B的类,比如说C1和C2,那么
if (C1 == MEMORY || C2 == MEMORY)
C1 = C2 = MEMORY;
if (C2 == SSEUP AND C1 != SSE)
C2 = SSE;
Run Code Online (Sandbox Code Playgroud)
注意这是我对ABI文档中给出的算法的解释.
例
struct foo
{
unsigned long long a;
long double b;
};
unsigned long long foo(struct foo f)
{
return f.a;
}
Run Code Online (Sandbox Code Playgroud)
8Bs及其领域
第一个8B:a
第二个8B:b
a是INTEGER,所以第一个8B是INTEGER.
b是X87和X87UP所以第二个8B是MEMORY.最后一节是两个8B的MEMORY.
例
struct foo
{
double a;
long long b;
};
long long foo(struct foo f)
{
return f.b; //mov rax, rdi
}
Run Code Online (Sandbox Code Playgroud)
8Bs及其领域
第一个8B:a
第二个8B:b
a是SSE,所以第一个8B是SSE.
b是INTEGER所以第二个8B是INTEGER.
最后的类是计算出来的.
值将根据其类返回:
MEMORY
调用者将一个隐藏的第一个参数传递给函数,以便将结果存储到函数中.
在C++中,这通常涉及复制省略/返回值优化.必须返回此地址eax,从而将"通过引用"的MEMORY类返回到隐藏的调用者分配的缓冲区.
如果类型具有类MEMORY,则调用者为返回值提供空间,并在%rdi中传递此存储的地址,就好像它是函数的第一个参数一样.实际上,该地址成为"隐藏的"第一个参数.返回时,%rax将包含%rdi中调用者传入的地址.
INTEGER和POINTER
寄存器rax和rdx根据需要.
SSE和SSEUP
寄存器xmm0并xmm1根据需要.
X87和X87UP
寄存器st0
技术定义在这里.
ABI的定义如下.
如果de /构造函数是隐式声明的默认de /构造函数并且如果:
•其类没有虚函数,也没有虚基类,并且
•其类的所有直接基类都具有简单的de/constructors,并且
•对于类的所有非静态数据成员(类型或其数组) ,每个这样的类都有一个简单的de/constructor.
注意,每个8B被独立分类,以便可以相应地传递每个8B.
特别是,如果没有剩余的参数寄存器,它们可能最终在堆栈上.
此处记录了x86-64 ABI ,版本 252(根据我的回答是最新的 ABI)可在此处下载。
如果我正确阅读了第 21 页 et seq,它会说如果 sizeof(struct) 是 8 个字节或更少,那么它将被传递到一个普通的寄存器中。之后规则变得复杂,但我认为如果它是 9-16 个字节,它可能会在 SSE 寄存器中传递。
至于类,请记住类和结构之间的唯一区别是默认访问。 但是,规则明确指出,如果存在非平凡的复制构造函数或非平凡的析构函数,则该结构将作为隐藏引用传递。