D の ABI
#D で面白いことを教えてもらいました。
import std.stdio; void func(int a, int b, int c, int d) { writefln(&a); writefln(&b); writefln(&c); writefln(&d); } int main() { func(1,2,3,4); return 0; }
DMD で上記のコードをコンパイルして実行すると、以下のような出力が得られます。
bff9cdc4 bff9cdc0 bff9cdbc bff9cdb0
これを見ると、a-b, b-c のメモリの間隔は 4byte なのですが、 c と d のメモリ間隔は 12byte になっています。これは D の ABI によるものだということです。 D の ABI では、最後の引数はレジスタ渡しになるとのことで、そのため &d の計算をするために関数のコンテキストで 8byte スタック消費した後の空間に d の位置を確保するためらしいです。
というわけで確認。
import std.stdio; void func(int a) { int i; asm { mov i, EAX; } writefln(i); } int main() { func(1); return 0; }
結果、ちゃんと 1 が出ました。この挙動は extern(C) したらもちろん消えます。これって C から目デマングルした D の関数呼んだら悲しいことになるということですね気をつけましょう誰もそんなことしません。あと gdc はこの挙動をしない模様。そりゃそうか。
あと、以下のようなものは、
void func(int a, int b) { writefln(a); writefln(b); }
より、
void func(int a, int b) { writefln(b); writefln(a); }
の方が速いみたいです。逆アセして確認しました。前者の逆アセ。
0804bb1c <_D1a4funcFiiZv>: 804bb1c: 55 push %ebp 804bb1d: 8b ec mov %esp,%ebp 804bb1f: 83 ec 04 sub $0x4,%esp 804bb22: 89 45 fc mov %eax,0xfffffffc(%ebp) 804bb25: ff 75 08 pushl 0x8(%ebp) 804bb28: ff 35 1c b9 05 08 pushl 0x805b91c 804bb2e: ff 35 18 b9 05 08 pushl 0x805b918 804bb34: e8 0b 55 00 00 call 8051044 <_D3std5stdio8writeflnFYv> 804bb39: ff 75 fc pushl 0xfffffffc(%ebp) 804bb3c: ff 35 1c b9 05 08 pushl 0x805b91c 804bb42: ff 35 18 b9 05 08 pushl 0x805b918 804bb48: e8 f7 54 00 00 call 8051044 <_D3std5stdio8writeflnFYv> 804bb4d: 83 c4 18 add $0x18,%esp 804bb50: c9 leave 804bb51: c2 04 00 ret $0x4
後者の逆アセ。
0804bb1c <_D1a4funcFiiZv>: 804bb1c: 55 push %ebp 804bb1d: 8b ec mov %esp,%ebp 804bb1f: 50 push %eax 804bb20: ff 35 14 b9 05 08 pushl 0x805b914 804bb26: ff 35 10 b9 05 08 pushl 0x805b910 804bb2c: e8 0b 55 00 00 call 805103c <_D3std5stdio8writeflnFYv> 804bb31: ff 75 08 pushl 0x8(%ebp) 804bb34: ff 35 14 b9 05 08 pushl 0x805b914 804bb3a: ff 35 10 b9 05 08 pushl 0x805b910 804bb40: e8 f7 54 00 00 call 805103c <_D3std5stdio8writeflnFYv> 804bb45: 83 c4 18 add $0x18,%esp 804bb48: 5d pop %ebp 804bb49: c2 04 00 ret $0x4
ていうか私だけが知らんかったなら悲しいな。