ucid-stubIRの設計
ucid処理系はC API記述から"ABIを抽出する"ためのCコードを出力する必要が有る。従来はUCID記述のS式から直接出力していたが、メンテナンス性がかなりダメだったのでちゃんとIR(intermediate representation)を決めてやりなおすことにした。
ucid処理系がCコードを出力することで入手するABI情報には以下が有る:
多くのFFIシステムでは、関数呼び出し規約のようなABIで明確に定義されるものはホスト言語側に実装している(libffiのような動的ブリッジを使用するケースも有る)。このため、FFIコードを実行するためにCコンパイラを要求しない。
pFFIは、これらは固定された呼び出し規約を持つCコードによって実装され、ホスト言語にはABI知識を要求しない。この性質は事実上ABIの存在しないemscriptenのようなC言語環境で有利に働く。
いくつかのABI情報は相互に関連している。例えば:
- コンパイル環境セットアップは全てのstubIRフォーマットで共通して使用される。必要なincludeやdefineをCコードに挿入する。
- マクロのインスタンス化は、マクロを呼び出すためのC関数を生成するだけなので、更に生成された関数の呼び出し規約も取得する必要が有る。つまり、stubIR処理系が別のstubIRを生成することになる。
ucid-stubIRはPOSIX(いわゆるPOSIXシステムコールやpthread)とWin32を記述するのに十分な能力が有ることが目標となる。ただし、Win32のUNICODEマクロによるAPIの切り替えは考慮しない。
"クロス"ABI抽出
ホストとターゲットが一致する場合は、ABI抽出はあまり大きな問題では無い。(Win32を除いて、)ホストで動作するC言語コンパイラは大抵のシチュエーションで存在するため、動作要件に含めることも可能と言える。
しかし、ホストとターゲットが一致しない場合は難しい。例えば、シンボル SIGINT の値をSchemeコード側で利用することを考える。
- a) 値を動的に取得する
通常のシチュエーションでは、値を動的に取得することがstraight forwardな解決策と言える。独自のシンボルテーブルを持ち、実行時にシンボルテーブルを参照する。
#include <sys/signal.h> // コンパイル環境セットアップIRによって生成されたinclude UCID_SYMTAB symtab__signal = { ..., { "SIGINT", SIGINT }, ... }; // シンボル値IRによって生成されたシンボルテーブル
この方法の欠点は、具体的な値はコードを実行するまで分からない点にある。これは最適化やコードの検証を制約する。もちろん、POSIXやWin32のようなABI安定なAPIであれば事前に実行して持っておくという解決策が有るが、その辺の適当なライブラリを使用する場合には問題になる。
(同じPOSIX APIであっても、例えばi386上のLinuxで生成したABI stubをARM Linuxで流用できるわけではない点に注意する。)
- b) 値を静的に取得する
ELFをパースする根性が有れば、コンパイラによって"値を取得するためのELF"を生成させ、そのELFのシンボルのサイズを取得することでコードを一切実行せずにシンボル値を取得することができる。(リロケーションやその他の事情により、シンボルの値そのものはこの目的に使用できないことが多い。)
この方法はFreeBSDのASSYMマクロで使用されている。
#define ASSYM_BIAS 0x10000 /* avoid zero-length arrays */ #define ASSYM_ABS(value) ((value) < 0 ? -((value) + 1) + 1ULL : (value)) #define ASSYM(name, value) \ char name ## sign[((value) < 0 ? 1 : 0) + ASSYM_BIAS]; \ char name ## w0[(ASSYM_ABS(value) & 0xFFFFU) + ASSYM_BIAS]; \ char name ## w1[((ASSYM_ABS(value) & 0xFFFF0000UL) >> 16) + ASSYM_BIAS]; \ char name ## w2[((ASSYM_ABS(value) & 0xFFFF00000000ULL) >> 32) + ASSYM_BIAS]; \ char name ## w3[((ASSYM_ABS(value) & 0xFFFF000000000000ULL) >> 48) + ASSYM_BIAS]
このASSYMマクロが使用できるとすると、
#include <sys/signal.h> ASSYM(SIGINT_,SIGINT);
のコードは、5つの配列SIGINT_sign、SIGINT_w0、SIGINT_w1、SIGINT_w2、SIGINT_w3を生成する。これらのELFシンボルのサイズを取得し、BIASを除いて合成することでシンボルの値を得ることができる。