ucid-stubIRの設計

ucid処理系はC API記述から"ABIを抽出する"ためのCコードを出力する必要が有る。従来はUCID記述のS式から直接出力していたが、メンテナンス性がかなりダメだったのでちゃんとIR(intermediate representation)を決めてやりなおすことにした。
ucid処理系がCコードを出力することで入手するABI情報には以下が有る:

  • コンパイル環境セットアップ
  • シンボルの値とサイズ
  • 型サイズ(構造体メンバ/単純な型)
  • 構造体レイアウト
  • マクロのインスタンス化(構造体代入/関数マクロ)
  • 関数呼び出し規約(in/out)

多くの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を除いて合成することでシンボルの値を得ることができる。