Open3

nccc: エンディアン独立に向けたAPI/ABI調整

okuokuokuoku

現在のNCCCでは、全てのC関数が uint64_t* を受けて uint64_t* に結果を書き込むというルールにしている。これだと float のような型をやりとりするケースでCやC++のStrict aliasing ruleに反しているし、さらにビッグエンディアン環境では結果が曖昧なのでC/C++標準に適合したやりかたに変えたい。

union とメンバのアクティベーション

簡単には、

union nccv64_u {
  uint8_t u8;
  int8_t s8;
     :
  uintptr_t uptr;
  intptr_t sptr;
  float f32;
  double f64;
};

typedef union nccv64_u nccv64;

のような union を定義して、それ経由でアクセスすることで目的を達成できる。ただしC++にはunionのメンバに "アクティブ" の概念があり、

  • union のインスタンスは1度に1つのアクティブメンバしか持てない
  • メンバに対して書き込みを行うとそのメンバがアクティブになる
  • アクティブでないメンバからの読み取りは未定義挙動である

というルールがある。更にアクティブなメンバの切り替えを constexpr 内で行えないなど微妙な制約があった(C++20で無くなった)。

https://cpprefjp.github.io/lang/cpp20/changing_the_active_member_of_a_union_inside_constexpr.html

(この違いにより、Cでは unionをtype punningのために使えるが、C++ではC++20の bit_cast まで良い方法が無かった。

https://cpprefjp.github.io/reference/bit/bit_cast.html

従来は void* を確保して memcpy するか、普通にunsafeにキャストして no-strict-aliasing にコンパイルするという方法が使われていた。NCCCのユースケースでは、書き込み型と読み取り型は常に一致するのでこの問題はない。)

okuokuokuoku

モジュールroot

NCCC経由で呼出せるAPIの集合を "NCCCコレクション" と呼ぶ。コレクションは名前で区別される複数のNCCCライブラリを含められる。コレクションの配置方法には複数あり:

  • module: DLLやShared libraryの形で提供される。 <NCCC_MODULE_PREFIX>/nccc_<collection_name>.dll のように配置される。これらのDLLは <collection_name>_nccc_root_00 のみ エクスポートする
  • embedded: 処理系に内蔵される。処理系依存の方法で <collection_name>_nccc_root_00 に相当するポインタを取得できるようになっている

さらに特殊なケースとしてbootstrapがある。bootstrapはNCCCのサブセットであり、ABIの取得に使用される。また、RibbonやDuktapeのような標準でFFI機能を備えていない処理系ではFFI機能自体がNCCCで提供されるため、そこでもbootstrapが使用される。

CMake統合

NCCCコレクションやそれに含まれるライブラリはCMakeでビルドされることが前提となっている。モジュールrootやstubに相当するコードはビルド時にCMakeスクリプトによって生成される。

NCCC_HOST_DEV

(未実装: コレクションを /usr/lib 等にインストールする場合に偽にする)

NCCC_LOCATION_<COLLECTION>

コレクションの配置方法を選択する。 EMBEDDED の場合は組込用のライブラリターゲット nccc_embedded_libsOBJECT ライブラリとして生成される。未設定の場合は MODULE ライブラリとなる。

convert_nccc_stub(stubfile filename)

スタブ関数を含んだCソースコードを生成する。 stubfile に記述された内容に従ってNCCCラッパーを作成する。

add_nccc_library(tgt src_or_lib ...)

NCCCライブラリを表すCMakeターゲットを生成する。ターゲットは OBJECT ライブラリとなる。

add_nccc_collection(tgt lib ...)

NCCCコレクションを表すCMakeターゲットを生成する。

okuokuokuoku

型とABI bootstrap

NCCCはWebAssemblyを意識しているものの、型システムは大きく異なる。NCCCには符号があり、短いサイズの整数型も規定している(C構造体を表現する必要性から)。

型は番号で区別される:

#define CITYPE_VALUE_u32 0
#define CITYPE_VALUE_u64 1
#define CITYPE_VALUE_f32 2
#define CITYPE_VALUE_f64 3
#define CITYPE_VALUE_s32 4
#define CITYPE_VALUE_s64 5
#define CITYPE_VALUE_ptr 6
#define CITYPE_VALUE_uptr 7
#define CITYPE_VALUE_sptr 8
#define CITYPE_VALUE_u8 9
#define CITYPE_VALUE_u16 10
#define CITYPE_VALUE_s8 11
#define CITYPE_VALUE_s16 12

ポインタ型は都合3種類ある。 ptr はアドレスを格納するためのポインタ型、 uptr および sptr はアドレスと同じ巾の整数を格納するための整数型となる。

ABI bootstrap

ブートストラップライブラリは特殊なNCCCライブラリで、NCCC呼出しを行うために必要なプリミティブを提供する。bootstrapは1つのNCCC関数のみ提供する。

get_abi_info [u64] => [u64]

get_abi_info はABIの情報を表わす 8bit整数の配列 を返却する。サブコードとして64bit整数を与える。ポインタは(32bitsアーキテクチャであっても)64bit整数で返却する。

サブコード 0 = verinfo は、文字列 NCCC64v0\0 となる。

サブコード 1 = typeinfo は、型の数だけ以下のデータを繰り返す。

<bytelen> <typeid> <offset> <coretype>

bytelen は 1: 8bit 、 2: 16bit 、 4: 32bit 、 8: 64bit となる。ゼロは終端を表す。

typeid はその型に与えられた型IDとなる。

offset は64bit長の値フィールドにデータを格納する際のオフセットとなる。 エンディアンを表現するものではない 点に注意。

coretype は 1: 符号無整数、2: 符号付整数、 3: 浮動小数点数 、 4: ポインタ となる。