Open4

NCCC(Normalized C Calling Convention)

okuokuokuoku

今後のWebGL-Nativeの実装で頻出するNCCCについての説明。

okuokuokuoku

NCCCとは?

(Yuniとか他のプロジェクトでもNCCCの用語を使っているが、それらより今回はちょっと単純化している。)

NCCC(Normalized C Calling Convention、正規化C呼び出し規約)とは、個人的なSchemeプロジェクト全般で採用している呼び出し規約で、 言語処理系に必要最低限の追加でFFI (ここではC言語関数呼び出し) 機能を追加する 事を目的としている。

NCCC環境では、言語処理系(WebGL-Nativeの場合WebAssemblyやJavaScript)からは、

void function(const uint64_t* in, uint64_t* out);

という形式の関数 のみ 呼び出すことができる。もっとも、実際に呼び出したいAPIは、

void *malloc(size_t size);

のような形式をしていて当然NCCCには適合しないため、NCCCのツールチェーンは、

void nccc_malloc(const uint64_t* in, uint64_t* out){
  size_t size;
  void* ret;
  size = in[0];
  ret = malloc(size);
  out[0] = ret;
}

のように、関数をNCCC形式にラップするスタブ(stub)を生成する機能も提供する。このスタブは事前にコンパイルしてDLLなりなんなりで言語処理系にロードしておく必要がある。

okuokuokuoku

NCCC のメリット

NCCCのメリットとしては:

  1. ABIや呼び出し規約の存在しない環境でも使える 。要するにWebAssemblyでも実装できる。NCCCの考え方自体はWebAssemblyよりも古くから存在するが、そもそも常識的な言語処理系には通常最初からFFI機能が備わっているので。。
  2. 実装が容易である 。Schemeのように大量の言語処理系が存在する中で処理系間で共有できるFFIを現実的な手間で実装しようとするとNCCCのようにシグネチャを絞るしか方法が無い気がしている。JavaScriptやWebAssemblyでも既に大量の実装が存在し、状況はSchemeに近い。
  3. 単一のスタブを複数の言語で使いまわせる 。生成したNCCCスタブはJavaScriptだけでなくScheme等他の言語でNCCCなFFIを実装した場合にも使いまわせる。
  4. 呼出しのproxyが容易である 。内製のツールでは、NCCCスタブの形でAPIトレーサーを実装している。
  5. 依存関係がない 。FFIの実装によく利用される libffiとかdyncall https://www.dyncall.org/ に依存せず、かつ、これらが対応するOSやCPUにも依存しない。
  6. (現実的な手間で) 合成可能である 。 NCCC関数からNCCC関数を呼出すといったフローを、ホスト言語の介在なく実現できる。

XMLをJsonが駆逐したように、複雑で表現力が高く効率的な既存のものよりも、単純で誰でも理解できる方向に寄せることで立場を作ろうとしている。

NCCC のデメリット

通常のFFIと異なり、事前にスタブをビルドしておく必要があり、ビルド環境のないシステムでは使えない。

また、多少の実行時オーバーヘッドが存在する。

okuokuokuoku

NCCC 関数

NCCCの関数は入力である in と、出力である out の2つのポインタを取る。入力や出力がゼロ個の場合は、該当するポインタを nullptr にしても良い。

void function(const uint64_t* in, uint64_t* out);

関数のドキュメントでは以下のような表記を用いる。

[size] => [ptr]

これは上記の nccc_malloc が1入力1出力であることを表わす。

また、NCCCでは多値を出力しても良い。つまり、1つの関数は2つ以上の値を出力しても良い。 関数が入出力する個数を把握して適切なサイズのバッファを用意するのは呼出し側の責任となる 。特に、NCCC自体は入力に応じて出力の個数が変化することを禁止しない。

64bit入出力

NCCCでは、常に整数も浮動小数点も64bit巾のフィールドに格納される。つまり、(amd64で言うところの、) intfloatを渡した場合は上位32bitは無駄になる。

ちょっともったいない気もするが、64bit単位の処理をサポートしない処理系はほぼ存在しないため単純化を取った。