如需转载,请注明本文出处。

实现中的简化


根据 AMD64-ABI 文档,对参数分类(Classification)是比较复杂的,由于 abc 中对这些情况做了简化,所以本文是以此为依据的。主要的简化是:

  1. 不支持 80 bit 的 long double,也就是说 long double 和 double 在实现上是一样的,都只有 64 bit。
  2. 不支持 _Complex, _Imaginary 关键字。

作了以上的简化后,可能带来的问题是,如果代码中、或者其他现有的库使用了 long double, _Complex_Imaginary,会导致不兼容。

分类(Classification)


在决定参数或返回值是通过哪个寄存器还是内存传递前,需要对它进行分类。根据第一节的简化,对于任意类型,可以分成以下几类:

  • NO_CALSS: 无类别,用于算法初始化。
  • INTEGER: 使用通用寄存器传递。
  • SSE: 使用 SSE 寄存器传递。
  • MEMORY: 使用内存传递。

因为作了简化处理,不然还有以下几个类别:SSEUP, X87, X87UP, COMPLEX_X87。

算法

首先,所有参数都按 8 字节 round up。

  1. _Bool, char, short, int, long, long long, pointerINTEGER
  2. float, double, long doubleSSE
  3. 对于 struct, array, union,则:
    • 如果 size > 16字节,则为 MEMORY
    • 否则,size <= 16字节,按照8字节分为两部分,两个部分单独分开处理(也就是说,如果 0 < size <= 8,返回一个分类;如果 8 < size <= 16,返回两个分类)。对于每个部分,初始化为 NO_CLASS。递归的处理本部分的每个 field,每次都只有两个 field 比较:
    1. 如果两个的分类相同,则结果就是此分类。
    2. 如果其中一个分类是 NO_CALSS,则结果为另一个分类。
    3. 如果其中一个分类是 MEMORY,则结果为 MEMORY
    4. 如果其中一个分类是 INTEGER,则结果为 INTEGER
    5. 否则结果为 SSE

注:函数参数和返回类型都不可能为 array,但 struct 或 union 的 field 中可能有 array 类型。

函数参数的传递


首先按照上面的分类算法从左到右对各个参数分类。

  1. 如果分类是 MEMORY,则使用栈传递此参数。
  2. 如果分类是 INTEGER,则按顺序使用以下6个寄存器来传递此参数:rdi, rsi, rdx, rcx, r8, r9
  3. 如果分类是 SSE,则按顺序使用以下8个寄存器来传递此参数:xmm0 ~ xmm7

如果寄存器已使用完了,则使用栈传递参数。参数被 push 进栈的顺序按照从右到左的顺序,也就是说,前面的参数被安排在栈的低地址端。

函数返回值的传递


首先按照上面的分类算法对返回值进行分类。

  1. 如果分类是 MEMORY,则调用者负责提供空间来保存此返回值,并且将该空间的地址作为本函数的第一个参数,通过寄存器 rdi 传进来,也就是说函数多了一个隐藏的参数。在返回时,rax 需要被设置成传进来的 rdi 的值。
  2. 如果分类是 INTEGER,则按顺序使用 rax, rdx 返回此值。
  3. 如果分类是 SSE,则按顺序使用 xmm0, xmm1 返回此值。

举个例子


下面的例子是从 AMD64 ABI 文档中摘抄的,并不是根据上面的简化版本,但仍有参考意义。除了 long double 外,其他的都跟上面的简化版本一致。

typedef struct {
  int a, b;
  double d;
} structparm;
structparm s;
int e, f, g, h, i, j, k;
long double ld;
double m, n;

extern void func (int e, int f,
                  structparm s, int g, int h,
                  long double ld, double m,
                  double n, int i, int j, int k);

func (e, f, s, g, h, ld, m, n, i, j, k);

分配结果:

General Purpose Registers Floating Point Registers Stack Frame Offset
%rdi: e %xmm0: s.d 0: ld
%rsi: f %xmm1: m 16: j
%rdx: s.a, s.b %xmm2: n 24: k
%rcx: g    
%r8: h    
%r9: i    

寄存器


General Purpose Registers

64 32 16 8 PRESERVED ARG RETURN
rax eax ax al     RET #1
rbx ebx bx bl callee preserved    
rcx ecx cx cl   ARG #4  
rdx edx dx dl   ARG #3 RET #2
rdi edi di dil   ARG #1  
rsi esi si sil   ARG #2  
rbp ebp bp   callee preserved    
rsp esp sp   callee preserved    
r8 r8d r8w r8b   ARG #5  
r9 r9d r9w r9b   ARG #6  
r10 r10d r10w r10b      
r11 r11d r11w r11b      
r12 r12d r12w r12b callee preserved    
r13 r13d r13w r13b callee preserved    
r14 r14d r14w r14b callee preserved    
r15 r15d r15w r15b callee preserved    

SSE Registers

Register ARG RETURN
xmm0 ARG #1 RET #1
xmm1 ARG #2 RET #2
xmm2 ARG #3  
xmm3 ARG #4  
xmm4 ARG #5  
xmm5 ARG #6  
xmm6 ARG #7  
xmm7 ARG #8  
xmm8 ~ xmm15    

函数栈帧布局

通常布局

High  | ...           | (1st argument on stack)
      +---------------+ <--- 16(rbp)
      | return address|
      +---------------+ <--- 8(rbp)
      | saved rbp     |
      +---------------+ <--- rbp
      | preserved regs|
      +---------------+
      | local vars    |
      +---------------+
      | params        | (params in registers)
      +---------------+
      | call params   | (for all func calls in this function)
Low   +---------------+ <--- rsp
      | red zone      |

包含可变参数时的布局

High  | ...           | (1st argument on stack)
      +---------------+ <--- 16(rbp)
      | return address|
      +---------------+ <--- 8(rbp)
      | saved rbp     |
      +---------------+ <--- rbp
      | preserved regs|
      +---------------+
      | reg save area | (all params in registers)
      +---------------+
      | local vars    |
      +---------------+
      | call params   | (for all func calls in this function)
Low   +---------------+ <--- rsp
      | red zone      |


Register Save Area:

High  | xmm7          |
      +---------------+ -16(rbp)
      | xmm6          |
      +---------------+ -32(rbp)
      | xmm5          |
      +---------------+ -48(rbp)
      | xmm4          |
      +---------------+ -64(rbp)
      | xmm3          |
      +---------------+ -80(rbp)
      | xmm2          |
      +---------------+ -96(rbp)
      | xmm1          |
      +---------------+ -112(rbp)
      | xmm0          |
      +---------------+ -128(rbp)
      | r9            |
      +---------------+ -136(rbp)
      | r8            |
      +---------------+ -144(rbp)
      | rcx           |
      +---------------+ -152(rbp)
      | rdx           |
      +---------------+ -160(rbp)
      | rsi           |
      +---------------+ -168(rbp)
      | rdi           |
Low   +---------------+ -176(rbp)

参考文献


  1. AMD64 ABI v0.90 (Dec 2, 2003): http://people.freebsd.org/~obrien/amd64-elf-abi.pdf