D言語 アプリケーションバイナリインターフェイス
D ABI(アプリケーション・バイナリインターフェイス) に従ったDの実装は、 他の実装によって生成されたライブラリや DLL などと相互利用することが可能なバイナリを出力します。
C ABI
この仕様で C ABI として参照するのは、対象とするシステムの C言語のアプリケーション・バイナリインターフェイスです。 C と D のコードは制限無くリンクでき、特に、D のコードからは C の ABI を持ったランタイムライブラリには完全にアクセス可能であるべきです。
エンディアン
データレイアウトの エンディアン (バイトオーダー) は、 対象マシンのエンディアンに従います。 Intel x86 CPU では、 値 0x0A0B0C0D がメモリ上で 0D 0C 0B 0A と表現される リトルエンディアン になります。
基本型
- bool
- 8 bit byte。0 が false、1 が true を表す
- byte
- 8 bit signed value
- ubyte
- 8 bit unsigned value
- short
- 16 bit signed value
- ushort
- 16 bit unsigned value
- int
- 32 bit signed value
- uint
- 32 bit unsigned value
- long
- 64 bit signed value
- ulong
- 64 bit unsigned value
- cent
- 128 bit signed value
- ucent
- 128 bit unsigned value
- float
- 32 bit IEEE 754 浮動小数点数
- double
- 64 bit IEEE 754 浮動小数点数
- real
- 実装定義の浮動小数点数。x86 では 80 bit IEEE 754 拡張実数
delegate
delegate は2つの部分から構成される fat pointer です:
オフセット | プロパティ | 内容 |
---|---|---|
0 | .ptr | コンテキストポインタ |
ptrsize | .funcptr | 関数ポインタ |
コンテキストポインタ は、クラスの this 参照か構造体の this ポインタ、あるいは クロージャ、 または周囲の関数のスタックフレーム(ネスト関数の場合)へのポインタです。
構造体
対象環境の C ABI の構造体レイアウトに従います。
クラス
オブジェクトのメモリ構成は次のようになっています:
サイズ | プロパティ | 内容 |
---|---|---|
ptrsize | .__vptr | vtableへのポインタ |
ptrsize | .__monitor | モニタ |
... | ... | 基底クラスの非staticメンバとinterfaceのvtableへのポインタ。継承のルートから順に。 |
... | 名前のついたフィールド | 非staticメンバ |
ptrsize... | このクラスの実装するinterfaceのvtableへのポインタ。左から右、直近の派生元から遠くの派生元、の順で。 |
vtableの構成は次の通り:
サイズ | 内容 |
---|---|
ptrsize | ClassInfoのインスタンスへのポインタ |
ptrsize... | 仮想メンバ関数へのポインタ |
クラスオブジェクトのインターフェイスへのキャストは、 オブジェクトのベースアドレスに、インターフェイスのvptrへのオフセットを加算することで行われます。 インターフェイスをクラス型へキャストし直すには、 引き算する正しいオフセットを求めるために、vtbl[0] の object.Interface エントリを参照します。 vtbl[] の中には、 thisポインタが正しくオブジェクトのインスタンスを参照するように調整するための サンク が生成され格納されます。
サンクは以下のようなコードになります:
ADD EAX,offset
JMP method
継承関係グラフの左端は、全てのインターフェイスが vptr を共有する、 単一継承モデルです。 (インターフェイスの多重継承によって)継承グラフが分岐する度に、 新し vptr が作られクラスのインスタンスに格納されます。 また、virtual メソッドがオーバーライドされるたびに、新しく vtbl[] が作られ新しいメソッドポインタをそこに格納する必要があります。
クラス定義:
class XXXX
{
....
};
は、以下のコードを生成します:
- ClassXXXX という名前の、Class クラスのインスタンス
- StaticClassXXXX という名前の、全ての静的メンバを定義する型
- StaticXXXX という名前の、静的メンバを保持する StaticClassXXXX のインスタンス
インターフェイス
インターフェイスは vtbl[] へのポインタです。 vtbl[0] は、object.Interface クラスの対応するインスタンスへのポインタです。 残りのエントリ vtbl[1..$] は、 そのインターフェイスで実装される仮想関数へのポインタが、 宣言された順番に入ります。
COM インターフェイスは、通常のインターフェイスと異なり、 vtbl[0] に object.Interface は入りません。 vtbl[0..$] のエントリ全てが仮想関数ポインタで、 宣言された順番に格納されます。 これは Windows の COM オブジェクトのレイアウトに合わせています。
C++ インターフェイスは通常のインターフェイスと異なり、 ターゲット環境での C++ の単一継承のクラスレイアウトと同じ形式に合わせた実装になります。
配列
動的な配列の構成は:
オフセット | プロパティ | 内容 |
---|---|---|
0 | .length | 配列のサイズ |
size_t | .ptr | 配列データへのポインタ |
動的な配列は次のように宣言されますが:
type[] array;
静的な配列の宣言は次のようになります:
type[dimension] array;
従って、静的な配列のサイズは常に型の一部として静的に得ることが出来ます。 このため、静的な配列についてはC言語と同様に実装されています。 静的な配列と動的な配列は簡単に相互変換が可能です。
連想配列
連想配列は、 実装ごとに定義される実体へのポインタ一つで構成されます。 現在の実装は internal/aaA.d から参照できます。
参照型
Dには参照型がありますが、明示的に表には出てきません。例えば、クラスは常に 参照によってアクセスされます。これはすなわち、クラスのインスタンスそのものは決して スタックに置かれたり関数のパラメタとして渡したりできないことを意味します。
静的な配列を関数へ渡すと、例え静的な配列として宣言されていても、 結果は実際には静的配列への参照となります。例をあげると:
int[3] abc;
関数へ abc を渡す際には、次のような暗黙の変換が引き起こされます:
void func(int[3] array); // 実際には <intの><サイズ3配列><への参照> void func(int* p); // abc は // 先頭要素へのポインタへ変換される void func(int[] array); // abc は動的配列へ変換される
名前マングリング
Dでは、型安全なリンクを実現するために、Dの識別子名を mangling してスコープと型の情報を埋め込んでいます。
MangledName: _D QualifiedName Type _D QualifiedName M Type QualifiedName: SymbolName SymbolName QualifiedName SymbolName: LName TemplateInstanceName
M は、そのシンボルが this ポインタを必要とする関数であることを表しています。
テンプレートのインスタンス名は、 そのパラメタ型と値をエンコードして保持します:
TemplateInstanceName: __T LName TemplateArgs Z TemplateArgs: TemplateArg TemplateArg TemplateArgs TemplateArg: T Type V Type Value S LName Value: n Number i Number N Number e HexFloat c HexFloat c HexFloat A Number Value... HexFloat: NAN INF NINF N HexDigits P Exponent HexDigits P Exponent Exponent: N Number Number HexDigits: HexDigit HexDigit HexDigits HexDigit: Digit A B C D E F
- n
- は null 引数を表します
- Number
- は正の数値リテラルです (文字リテラルを含みます)
- N Number
- は負の数値リテラルです
- e HexFloat
- は実数および虚数の浮動小数点数リテラルです。
- c HexFloat c HexFloat
- は複素数の浮動小数点数リテラルです。
- Width Number _ HexDigits
- Width は文字列中の文字が 1 byte (a), 2 bytes (w), 4 bytes (d) のいずれであるかを示します。 Number は文字列中の文字の数です。 HexDigits は文字列データの16進表現です。
- A Number Value...
- 配列リテラル。Value が Number 回繰り返す。
Name: Namestart Namestart Namechars Namestart: _ Alpha Namechar: Namestart Digit Namechars: Namechar Namechar Namechars
Name は標準的なDの識別子です。
LName: Number Name Number: Digit Digit Number Digit: 0 1 2 3 4 5 6 7 8 9
LName は、先頭に文字数を表す Number のついた Name です。
型名のマングリング
型の名前は以下のように単純な一列の文字列になります:
Type: Shared Const Invariant Wild TypeArray TypeStaticArray TypeAssocArray TypePointer TypeFunction TypeIdent TypeClass TypeStruct TypeEnum TypeTypedef TypeDelegate TypeNone TypeVoid TypeByte TypeUbyte TypeShort TypeUshort TypeInt TypeUint TypeLong TypeUlong TypeFloat TypeDouble TypeReal TypeIfloat TypeIdouble TypeIreal TypeCfloat TypeCdouble TypeCreal TypeBool TypeChar TypeWchar TypeDchar TypeTuple Shared: O Type Const: x Type Invariant: y Type Wild: Ng Type TypeArray: A Type TypeStaticArray: G Number Type TypeAssocArray: H Type Type TypePointer: P Type TypeFunction: CallConvention Arguments ArgClose Type CallConvention: F // D U // C W // Windows V // Pascal R // C++ Arguments: Argument Argument Arguments Argument: Type J Type // out K Type // ref L Type // lazy ArgClose X // variadic (T t,...) 形式 Y // variadic (T t...) 形式 Z // variadic ではない TypeIdent: I LName TypeClass: C LName TypeStruct: S LName TypeEnum: E LName TypeTypedef: T LName TypeDelegate: D TypeFunction TypeNone: n TypeVoid: v TypeByte: g TypeUbyte: h TypeShort: s TypeUshort: t TypeInt: i TypeUint: k TypeLong: l TypeUlong: m TypeFloat: f TypeDouble: d TypeReal: e TypeIfloat: o TypeIdouble: p TypeIreal: j TypeCfloat: q TypeCdouble: r TypeCreal: c TypeBool: b TypeChar: a TypeWchar: u TypeDchar: w TypeTuple: B Number Arguments
関数呼び出し規約
extern (C) と宣言された関数の呼び出し規約は、 ホスト環境で対応するCコンパイラの関数呼び出し規約と一致します。 extern (D) と宣言された関数の x86 での呼び出し規約をここで説明します。
レジスター使用規約
- EAX, ECX, EDX は作業用レジスタで、 関数の中で破壊してかまいません
- EBX, ESI, EDI, EBP は関数呼び出し前後で保存されなければなりません
- EFLAGS は、方向フラグが前方方向でなければならないことを除いて、 関数呼び出しの間で破壊されると仮定します
- 関数呼び出し時には FPU スタックは空になっていること
- FPU の制御ワードは関数呼び出し前後で保存されなければなりません
- 浮動小数点数の返値は FPU スタックで返却される。 使わない場合も呼び出し側でスタックをクリアしなければなりません
返値
- bool, byte, ubyte, short, ushort, int, uint, ポインタ, Object, インターフェイス型の返値は、 EAX レジスタで返されます
- long, ulong 型の引数は EDX,EAX レジスタで返されます。EDX に上位ワードが入ります。
- float, double, real, ifloat, idouble, ireal 型の返値は ST0 に返されます
- cfloat, cdouble, creal は ST1,ST0 で返されます。ST1 に実部、ST0 に虚部が入ります。
- 動的配列の返値では、ポインタが EDX、 長さが EAX に入ります。
- 連想配列は EAX で返されます。また、 EDX にはゴミが入ります。EDX の値は過去の連想配列の実装との 後方互換性のために残っているもので、 将来的には取り除かれる予定です。
- デリゲートは、関数ポインタをEDXに、 コンテキストポインタをEAXに入れて返されます。
- 1, 2, 4 バイトの構造体は EAX で返されます。
- 8 バイトの構造体は EDX,EAX レジスタで返されます。 EDXに上位ワードが入ります。
- そのほかのサイズの構造体は、 関数の隠れ引数として渡されたポインタの指す先に 返されます。
- コンストラクタはthisポインタをEAXに返します。
引数
可変個引数でない関数の引数:
foo(a1, a2, ..., an);
は、以下のように渡されます
a1 |
a2 |
... |
an |
hidden |
this |
hidden は、 構造体を返す必要があるときに現れます。 this は、メンバ関数に使うthisポインタや、 ネストした関数で使うコンテキストポインタを渡すときに現れます。
次の条件が満たされているときには、最後の引数は、 スタックではなくEAXレジスタで渡されます:
- EAX に収まるサイズである
- 3バイト構造体ではない
- 浮動小数点型でもない
引数は、 常に4バイト境界に切り上げてスタックに push されます。 上位ワードが先にpushされます。 out 引数と ref 引数はポインタが渡されます。 静的配列は先頭へのポインタが渡されます。 Windowsでは、real型は10バイトの量として、 creal型は20バイトの量としてpushされます。 Linuxでは、realは12バイト、 crealは二つの12バイトの量としてpushされます。 パディングの2バイトは'most significant'側に埋められます。 (訳注:ところどころ訳が怪しいかも)
呼び出された関数側が、スタックを復元します。
以下の形式の可変個引数関数の場合は、
void foo(int p1, int p2, int[] p3...) foo(a1, a2, ..., an);
このように引数が渡されます:
p1 |
p2 |
a3 |
hidden |
this |
可変個部分は動的配列に変換され、 あとは通常の関数と全く同様です。
以下の形式の可変個引数関数の場合は、
void foo(int p1, int p2, ...) foo(a1, a2, a3, ..., an);
このように引数が渡されます:
an |
... |
a3 |
a2 |
a1 |
_arguments |
hidden |
this |
呼び出し側がスタックを復元します。 _argptr は直接は渡されません。 呼び出された関数側が計算します。
関数属性
- Na
- pure
- Nb
- nothrow
例外処理
Windows
Microsoft Windows の構造化例外処理の規約に従う。
Linux and OSX
静的な範囲/ハンドラ対応表を用いる。 これはELFの例外ハンドラテーブルとは互換性がありません。 EBPでスタックフレームを保持しているとの仮定の下で、 スタックをたどります。例外ハンドラテーブル(EHテーブル)を持つ全ての関数で、 このEBPの規約が満たされている必要があります。
例外ハンドラのあるそれぞれの関数に対して、 EHテーブルのエントリが生成されます。
フィールド | 説明 |
---|---|
void* | 関数の開始アドレス |
DHandlerTable* | 対応するEHデータへのポインタ |
uint | 関数のサイズ。バイト単位 |
EHテーブルは、以下の特別なセグメントに配置され、 リンカによって一つに結合されます。
OS | セグメント名 |
---|---|
Windows | FI |
Linux | .deh_eh |
OSX | __deh_eh, __DATA |
例外ハンドラのそのほかのデータは任意の箇所に配置されます。 書き換えは不可能です。
フィールド | 説明 |
---|---|
void* | 関数の開始アドレス |
uint | ESPのEBPからのオフセット |
uint | 関数の開始アドレスからreturn codeまでのオフセット |
uint | DHandlerInfo[] の要素数 |
DHandlerInfo[] | ハンドラ情報の配列 |
フィールド | 説明 |
---|---|
uint | 関数の開始アドレスからtry区間の先頭までのオフセット |
uint | 同、tryの終端までのオフセット |
int | 一つ前のテーブルのインデックス |
uint | 0で内場合、テーブルの先頭から DCatchInfo までのオフセット |
void* | nullでない場合、finallyのコード |
フィールド | 説明 |
---|---|
uint | DCatchBlock[] の要素数 |
DCatchBlock[] | catch 情報の配列 |
フィールド | 説明 |
---|---|
ClassInfo | catchする例外の型 |
uint | catch変数の EBP からのオフセット |
void* | catch ハンドラのコード |
ガベージコレクション
インターフェイスが phobos/internal/gc に存在する
実行時補助関数
phobos/internal に存在する。
モジュール初期化と終了
モジュール内の静的コンストラクタは1つの関数へとまとめられます。 その関数へのポインタが、そのモジュールの ModuleInfo インスタンスの ctor メンバに格納されます。
モジュール内の静的コンストラクタは1つの関数へとまとめられます。 その関数へのポインタが、そのモジュールの ModuleInfo インスタンスの dtor メンバに格納されます。
単体テスト
モジュール内の静的コンストラクタは1つの関数へとまとめられます。 その関数へのポインタが、そのモジュールの ModuleInfo インスタンスの unitTest メンバに格納されます。
シンボリック・デバッグ
D には、既存のC/C++用デバッガでは表現できない型があります。 動的配列、連想配列、delegate がそれにあたります。 しかし、一般には関数呼び出し規約が構造体とは異なるために、 これらを構造体として表現すると C/C++ デバッガの動作ミスを引き起こしてしまいます。 これらデバッガへの対策として、現状では、 呼び出し規約がマッチするような型を選んでそれで置き換えて表現するようになっています。 -gc スイッチを指定した場合、dmd コンパイラはこのような C 形式シンボル情報のみを出力します。
D の型 | C 表現 |
---|---|
動的配列 | unsigned long long |
連想配列 | void* |
delegate | long long |
dchar | unsigned long |
新しい型を追加できるようなタイプのデバッガでは、 Dの型を完全サポートするために以下の情報を利用してください。
Codeview デバッガ拡張
D の dchar 型は特別なプリミティブ型 0x78 と表現されます。
D は、LF_OEM (0x0015) で示される Codeview OEM 汎用型情報レコードを使います。 形式は以下の通りです:
フィールドサイズ | 2 | 2 | 2 | 2 | 2 | 2 |
D の型 | Leaf Index | OEM Identifier | recOEM | num indices | type index | type index |
---|---|---|---|---|---|---|
動的配列 | LF_OEM | OEM | 1 | 2 | @index | @element |
連想配列 | LF_OEM | OEM | 2 | 2 | @key | @element |
delegate | LF_OEM | OEM | 3 | 2 | @this | @function |
OEM | 0x42 |
index | 配列の添え字のtype index |
key | 連想配列のkeyのtype index |
element | 連想配列のelementのtype index |
this | コンテキストポインタのtype index |
function | 関数のtype index |
これらの拡張は obj2asm を使うと綺麗に表示できます。
Ddbg デバッガがこの拡張に対応しています。
Dwarf デバッガ拡張
以下のleaf typeが追加されます:
D の型 | Identifier | Value | Format |
---|---|---|---|
動的配列 | DW_TAG_darray_type | 0x41 | DW_AT_type が要素の型 |
associative array | DW_TAG_aarray_type | 0x42 | DW_AT_type, が要素の型, DW_AT_containing_type がkeyの型 |
delegate | DW_TAG_delegate_type | 0x43 | DW_AT_type, が関数の型, DW_AT_containing_type が'this'の型 |
これらの拡張は dumpobj を使うと綺麗に表示できます。
ZeroBUGS デバッガがこの拡張に対応しています。