SPEs library 0.1.1.0 ■はじめに  これは IBM Cell Broadband Engine の SPE プログラムの開発を補助する為のライブラリです。  このライブラリを使用するには Cell SDK 2.1 以降が必要です。  現在、以下のライブラリから構成されています: - POSIX routines いくつかのPOSIX関数が使用出来る。 - Subroutine Plug Engine SPE が SPE 上にプログラムをロードする事が出来る。 - far_ptr DMA を殆ど意識せずにメインメモリのアクセスを行えるクラス。 ■免責  このライブラリは BSD like license です。いかなる場合にも責任を負いません。  詳しくは LICENSE ファイルを参照して下さい。 ■互換性に関する注意  現在仕様が固まっていないため、短期間で仕様が変更される可能性があります。 ■インストール  ライブラリをインストールするには一連のコマンドを実行する必要があります。詳細は INSTALL を参照して下さい。 ■使用方法 □ヘッダファイル  libspes を使ったプログラムを書くには PPE、SPE 共に /usr/local/include/libspes.h を include します。 □ライブラリのリンク  PPE プログラムのリンク時には /usr/local/lib/ppe/libspes.a をリンクします。このとき libspe2 も同時にリンクする必要があります。また libspes.a は内部で C++ を使用しているため C 言語を使っていても C++ に関するライブラリをリンクしなくてはなりません。通常これを指定するのは面倒なので C 言語を使っている場合でも g++ を使ってリンクすると良いでしょう。  例えば foo.c から foo にコンパイルするには次のようになります: ppu-g++ -L/usr/local/lib/ppe foo.c -lspe2 -lspes -o foo □POSIX routines  いくつかのPOSIX関数が使用出来ます。PPEでspes_init()を呼ぶ必要がある事、関数の接頭辞にspes_が付く点を除き、そのまま利用が可能です。  実際の使用例は次のようなものです: ----- PPE ... /* spe_context_run に関する初期化 */ // libspesの初期化 spes_init(SPES_CALLBACK_ID); // SPE の起動 spe_context_run(); ----- SPE // メインメモリの確保 uint64_t ptr = spes_malloc(512); /* 処理 */ // メインメモリの解放 spes_free(ptr); -----  libspes/spe/spes_posix.hに含まれる全ての関数は、純粋なラッパーであり余計な処理は一切ありません。  例えば上記ではspes_malloc()とspes_free()を呼び出していますが、PPEのプログラムがmalloc()で確保したメモリをspes_free()で解放したり、spes_malloc()で確保したメモリをPPEのプログラムでfree()する事も可能です。  また、全ての関数はコールバックを利用してPPEの処理に移ります。この処理が完了するまで、関数を呼び出したSPEの処理は停止する事に注意して下さい。  通常、主要な計算に比べてI/Oの呼び出し回数は遥かに少ないため問題になりませんが、呼び出し回数が多い場合や大きなサイズの確保を行い、SPEの処理をブロックされる事が問題になる場合、これらの関数を利用せずに非同期に処理する必要があります。 ※非同期による処理はlibspesを利用しない実装になるため、この文章では取り扱いません。 □Subroutine Plug Engine  SPE プラグインはオブジェクトファイルを使用します。通常通り SPE 用のプログラムを記述し、コンパイルされたオブジェクトファイルがプラグインです。   現在 libspes は完全に自身のオブジェクトだけで完結している関数のみを扱う事が出来ます。他のオブジェクトの関数をコールする、変数を参照するといった事は出来ません。回避策としては関数ポインタや配列のポインタを引数として受け渡すなどして下さい。  libspes ではオブジェクトファイルを PPE 上にロードし、アドレスを SPE に伝えるところまでをサポートします。プラグインを使用するには、与えられたアドレスを使って PPE から SPE にプラグインを転送します。このとき転送の完了を確実にするために spu_sync_c() を呼び出した方が良いでしょう。  examples の subroutine に簡単なサンプルが入っているので参照して下さい。  実際には以下のような手順でプログラミングする事になります: ----- PPE ... /* spe_context_run に関する初期化 */ // libspesの初期化 spes_init(SPES_CALLBACK_ID); // SPE の起動 spe_context_run(); ----- SPE void (*func)(void); spes_object obj; spes_object_handle handle; spu_writech(MFC_WrTagMask, 1<<0); // オブジェクトを PPE 上にロード size = spes_loadobject(&handle, "object.o"); // オブジェクトのアドレスを取得 mfc_get(&obj, handle.obj, sizeof(obj), 0, 0, 0); spu_mfcstat(MFC_TAG_UPDATE_ALL); // オブジェクトをロードする為のメモリを確保 code = (char *)malloc(size); // オブジェクトを SPE に転送 mfc_get(code, obj.code, size, 0, 0, 0); spu_mfcstat(MFC_TAG_UPDATE_ALL); // リンク spes_linkobject(&handle, code); // 関数のアドレスを取得 func = (void (*func)(void))spes_getproc(&handle, "func"); spu_sync_c(); // もう PPE 上のオブジェクトとオブジェクト情報は必要ないので解放 spes_destroyobject(&handle); // 関数の実行 func(); // もう関数を実行する事が無ければメモリを解放 free(code); ----- □far_ptr  ポインタを使用したプログラムを段階的にSPEに移植する為の補助を行います。  far_ptrクラスは次のような機能を持ちます。 - 加算、減算によるポインタの移動。(p++; p--; p += n; p -= n;) - DMAを意識しないメインメモリの読み書き。(v = *p; *p = v;) - 参照からfar_ptrへの復帰。(p = &q[n]) - 他の型のfar_ptr同士のキャスト。 (far_ptr p; far_ptr q; p = (far_ptr)q;)  far_ptrクラスの各インスタンスはテンプレート引数Tのサイズをtバイトとすると、容量bバイトのバッファを用意します。このとき次に示すround_up<64>(t)はtのサイズを64の倍数に切り上げる事を示します。 b = round_up<64>(t) + 64  far_ptrクラスからoperator*(), operator[](), operator->()によって値を読み出す時にメインメモリから自動的にバッファリングされ、変更されたデータは離れたアドレス位置からの読み出しまたはデストラクタ~far_ptr()によってメインメモリに書き戻されます。  far_ptrクラスは一種のrestrictポインタと解釈する事ができ、同期は一切考慮されません。このため複数のプロセッサで同時に書き換える場合や、複数のポインタおよびfar_ptrクラスがbバイトの範囲内を同時に参照する場合にはfar_ptrクラスの使用者の責任で同期する必要があります。  fetch()は明示的にメインメモリをバッファリングします。この時ポインタは移動されません。将来ポインタがそこに移動する時に予め読み込んでおく事が出来ます。  commit()は明示的にバッファをメインメモリに書き戻します。主に同期に使用します。  flush()は明示的にバッファをメインメモリに書き戻し、バッファをクリアします。再度バッファリングする必要がある場合に使用します。  現在の制限事項として、fetch(), commit(), flush()はメモリの転送が終わるまで待たされます。  PPE上のポインタをSPEに移植する際の典型的な手順は以下の通りです。 1.ポインタをtypedefで置き換える ----- before int * p; ... p = (int *)q; ----- after typedef int * int_ptr; int_ptr p; ... p = (int_ptr)q; ----- 2.引数の型が限定されていない箇所を明示的にキャストする ----- before int_ptr p; ... printf("%d\n", *p); ----- after int_ptr p; ... // printf の引数の型は何でも受け付けてしまうため、キャストしないと int に限定されない printf("%d\n", (int)*p); ----- 3.プログラムがPPE上で正しく動く事を確認。 4.typedefをfar_ptrに置き換える(ここからSPEのプログラム) ----- before typedef int * int_ptr; int_ptr p; ... p = (int_ptr)q; ----- after typedef spes::far_ptr int_ptr; int_ptr p; ... p = (int_ptr)q; ----- 5.SPE上で正しく動くようになり、方針が固まったら明示的なDMAの使用を検討する