Adaと64bit呼び出し規約とlldiv
lldivを呼ぶ関数を書いてたらちょっと驚きました。
こういうの。
procedure call_lldiv (x, y : long_long_integer; q, r : out long_long_integer) is type lldiv_t is record q, r : long_long_integer; end record; pragma Convention (C, lldiv_t); function lldiv (x, y : long_long_integer) return lldiv_t; pragma Import (C, lldiv, "lldiv"); result : lldiv_t; begin result := lldiv (x, y); q := result.q; r := result.r; end call_lldiv;
最適化してコンパイルする。
gcc -S -O3 call_lldiv.adb
すると……
.text .align 4,0x90 .globl __ada_call_lldiv __ada_call_lldiv: LFB1: subq $40, %rsp LCFI0: call _lldiv addq $40, %rsp LCFI1: ret
えっ。
GNATフロントエンドのスカラー値のoutパラメータの扱いは参照渡しではなくて所謂multi-value returnで、%rax、%rdxで返すっぽいです。で、64ビット呼び出し規約では小さな構造体もレジスタ返しってことになっているらしくて、lldivも値を%rax、%rdxで返すっぽくて、引数並びも一致してるから何もせずにcallして何もせずにretすることになったようです。
ですもんで-m32ですとこうなる。
.text .align 4,0x90 .globl __ada_call_lldiv __ada_call_lldiv: LFB1: pushl %esi LCFI0: subl $56, %esp LCFI1: movl 76(%esp), %eax leal 32(%esp), %ecx movl 80(%esp), %edx movl %ecx, (%esp) movl 64(%esp), %esi movl %eax, 12(%esp) movl 68(%esp), %eax movl %edx, 16(%esp) movl 72(%esp), %edx movl %eax, 4(%esp) movl %edx, 8(%esp) call _lldiv LCFI2: subl $4, %esp LCFI3: movl 32(%esp), %eax movl 36(%esp), %edx movl %eax, (%esi) movl 40(%esp), %eax movl %edx, 4(%esi) movl 44(%esp), %edx movl %eax, 8(%esi) movl %edx, 12(%esi) movl %esi, %eax addl $56, %esp LCFI4: popl %esi LCFI5: ret $4
で、これを見てしまうと、どうせならスタックを$40ほど取って戻してしてるのも最適化して欲しくて、そうすれば-foptimize-sibling-callsも働いてjmp一個にできるはずなんですけれど、このスタックが強敵で色々オプションを試しても消えてくれません。なんでー?