Application Binary Interface
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 |
ubyte | 8 bit unsigned |
short | 16 bit signed |
ushort | 16 bit unsigned |
int | 32 bit signed |
uint | 32 bit unsigned |
long | 64 bit signed |
ulong | 64 bit unsigned |
cent | 128 bit signed |
ucent | 128 bit unsigned |
float | 32 bit IEEE 754 浮動小数点数 |
double | 64 bit IEEE 754 浮動小数点数 |
real | 実装定義の浮動小数点数。 x86 では 80 bit IEEE 754 拡張実数 |
delegate
delegate は2つの部分から構成される fat pointers です:
オフセット | プロパティ | 内容 |
---|---|---|
0 | .ptr | コンテキストポインタ |
ptrsize | .funcptr | 関数ポインタ |
コンテキストポインタ は、クラスの this 参照か構造体の this ポインタ、あるいは クロージャ、 または周囲の関数のスタックフレーム(ネスト関数の場合)へのポインタです。
構造体
対象環境の C ABI の構造体レイアウトに従います。
クラス
オブジェクトのメモリ構成は次のようになっています:
サイズ | プロパティ | 内容 |
---|---|---|
ptrsize | .__vptr | vtableへのポインタ |
ptrsize | .__monitor | モニタ |
... | ... | 基底クラスの非staticメンバとinterfaceのvtableへのポインタ。継承のルートから順に。 |
... | named fields | 非staticメンバ |
ptrsize... | このクラスの実装するinterfaceのvtableへのポインタ。左から右、直近の派生元から遠くの派生元、の順で。 |
vtableの構成は次の通り:
サイズ | 内容 |
---|---|
ptrsize | TypeInfo のインスタンスへのポインタ |
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言語と同様に実装されています。 静的な配列と動的な配列は簡単に相互変換が可能です。
連想配列
連想配列は、 実装ごとに定義される実体へのポインタ一つで構成されます。 現在の実装は rt/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...
S 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...
- 配列リテラル。 Number が配列の長さを表します。 通常の配列では Value が Number 回繰り返し、 連想配列では 2 * Number 回並びます。
- S 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
Immutable
Wild
TypeArray
TypeNewArray
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
Immutable:
y Type
Wild:
Ng Type
TypeArray:
A Type
TypeNewArray:
Ne Type
TypeStaticArray:
G Number Type
TypeAssocArray:
H Type Type
TypePointer:
P Type
TypeFunction:
CallConvention FuncAttrs Arguments ArgClose Type
CallConvention:
F // D
U // C
W // Windows
V // Pascal
R // C++
FuncAttrs:
FuncAttr
FuncAttr FuncAttrs
FuncAttr:
empty
FuncAttrPure
FuncAttrNothrow
FuncAttrProperty
FuncAttrRef
FuncAttrTrusted
FuncAttrSafe
FuncAttrPure:
Na
FuncAttrNothrow:
Nb
FuncAttrRef:
Nc
FuncAttrProperty:
Nd
FuncAttrTrusted:
Ne
FuncAttrSafe:
Nf
Arguments:
Argument
Argument Arguments
Argument:
Argument2
M Argument2 // scope
Argument2:
Type
J Type // out
K Type // ref
L Type // lazy
ArgClose
X // 可変長引数 (T t,...) 形式
Y // 可変長引数 (T t...) 形式
Z // 可変長引数でない
TypeIdent:
I QualifiedName
TypeClass:
C QualifiedName
TypeStruct:
S QualifiedName
TypeEnum:
E QualifiedName
TypeTypedef:
T QualifiedName
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) と extern (D) と宣言された関数の呼び出し規約は、 基本的に、ホスト環境で対応するCコンパイラの関数呼び出し規約と一致します。 例外として、Windows x86 では extern (D) の呼び出し規約は以下のようになっています。
レジスター使用規約
- 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 で返されます。
- 参照は、ポインタを EAX に入れて返されます
- デリゲートは、関数ポインタを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 は直接は渡されません。 呼び出された関数側が計算します。
例外処理
Windows
Microsoft Windows の構造化例外処理の規約に従います。
Linux, FreeBSD, OS X
静的な範囲/ハンドラ対応表を用います。 これはELFの例外ハンドラテーブルとは互換性がありません。 EBP/RBPでスタックフレームを保持しているとの仮定の下で、 スタックをたどります。例外ハンドラテーブル(EHテーブル)を持つ全ての関数で、 このEBP/RBPの規約が満たされている必要があります。
例外ハンドラのあるそれぞれの関数に対して、 EHテーブルのエントリが生成されます。
フィールド | 説明 |
---|---|
void* | 関数の開始アドレス |
DHandlerTable* | 対応するEHデータへのポインタ |
uint | 関数のサイズ。バイト単位 |
EHテーブルは、以下の特別なセグメントに配置され、 リンカによって一つに結合されます。
OS | セグメント名 |
---|---|
Windows | FI |
Linux | .deh_eh |
FreeBSD | .deh_eh |
OS X | __deh_eh, __DATA |
例外ハンドラのそのほかのデータは任意の箇所に配置されます。 書き換えは不可能です。
フィールド | 説明 |
---|---|
void* | 関数の開始アドレス |
uint | ESP/RSPのEBP/RBPからのオフセット |
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/RBP からのオフセット |
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 |
Tこれらの拡張は 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 デバッガがこの拡張に対応しています。
これらの Dwarf 拡張は最近の gcc の追加機能と衝突するため取り除かれています。