libspe and libspe2 december release
libspe-1.2.0, libspe2-2.0.1 がリリースされました。tarball は以下のメールに添付されています。
libspe-1.2.0, libspe2-2.0.1 がリリースされました。tarball は以下のメールに添付されています。
PS3Linux キタ
他にも spe-samples-1.0.tar.bz2 とか CELL-Linux-CL_20061110-ADDON.iso とか。
libspe2 のプロトタイプも来てます。
"PPE Serviced SPE C Library Functions" の 4 回目は libspe の処理につ いて解説します。
4.2 PPE Serviced SPE C Library Functions
この内、libspe で行うのは 5., 6., 7., 8. です。
1 回目に説明した通り、spu_run システムコールの返り値として SPE の stop 命令のオペランドが返ります。また、2, 3 回目に説明した通り、SPE の stop 命令の次のアドレスに OPCODE と 引数へのアドレスが格納されており、こ れらを以下のようにして処理していきます。
以下は do_spe_run() の関連コードを抜粋したものにコメントで解説を付け たものです。
int do_spe_run (void *ptr)
{
:
do {
// SPE thread を (再) 実行
ret = spe_run(runfd, &npc, &status);
// ret は SPU Status Register の値が入っている
// - 下位 16 bit [0:15] が状態を表す値
// - 上位 14 bit [16:29] が stop 命令のオペランド
// よって code は stop 命令のオペランドとなる
code = ret >> 16;
:
if (ret < 0) {
:
// code は libc のハンドラの場合は以下の値を取る
// C の関数の場合: SPE_C99_CLASS(0x2100)
// POSIX の関数の場合: SPE_POSIX1_CLASS(0x2101)
} else if ((code&0xff00)==0x2100) {
int callnum = code&0xff;
:
if (!handlers[callnum])
{
:
}
else
{
// 対象となるオペクラスハンドラを実行
// ちなみにオペクラスハンドラは 256 個作れますが、
// 2 つしか使ってません
int (*handler)(void *, unsigned int);
handler=handlers[callnum];
rc = handler(thread_store->mem_mmap_base, npc);
if (rc)
{
:
}
else
{
// npc がスタック上の bi 命令を指すように変更
npc += 4;
}
}
}
else if (code < 0x2000)
{
:
}
// 0x21?? の場合はエラーではなく、libc のハンドラ実行依頼
// なので SPE thread を再実行するためにループの先頭へ
} while (((code & 0xff00) == 0x2100 || ... );
}
お次は C99 のオペクラスハンドラ。
int default_c99_handler(unsigned long *base, unsigned long offset)
{
int op, opdata;
if (!base) {
DEBUG_PRINTF("%s: mmap LS required.\n", __func__);
return 1;
}
// stop 命令の次のアドレスの値から OPCODE を得る
offset = (offset & LS_ADDR_MASK) & ~0x1;
opdata = *((int *)((char *) base + offset));
op = SPE_C99_OP(opdata);
// 値のチェック
if ((op <= 0) || (op >= SPE_C99_NR_OPCODES)) {
DEBUG_PRINTF("%s: Unhandled type %08x\n", __func__,
SPE_C99_OP(opdata));
return 1;
}
// オペコードハンドラ実行
default_c99_funcs[op-1] ((char *) base, opdata);
return 0;
}
そして getc() に対応するオペコードハンドラである default_c99_handler_getc() です。
/**
* default_c99_handler_getc
* @ls: base pointer to SPE local-store area.
* @opdata: per C99 call opcode & data.
*
* SPE C99 library operation, per: ISO/IEC C Standard 9899:1999,
* implementing:
*
* int getc(FILE *stream);
*/
int default_c99_handler_getc(char *ls, unsigned long opdata)
{
// 引数へのポインタ arg0 を得る
// SPE は 128 bit 単位であるため、arg0 はこれを吸収するため
// の構造体 spe_reg128 へのポインタとなっている
DECL_1_ARGS();
// 返り値と errno を格納するポインタ (arg0 と同じ) を得る
DECL_RET();
FILE *stream;
int rc;
DEBUG_PRINTF("%s\n", __func__);
CHECK_C99_OPCODE(GETC);
// 引数から stream を得る
stream = get_FILE(arg0->slot[0]);
// getc() を実行
rc = getc(stream);
// 返り値 rc と errno を SPE のメモリイメージに格納
PUT_LS_RC(rc, 0, 0, errno);
return 0;
}
getc() 実行時の SPE thread スタックはこうなります。
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain |
|------------|
| |
|------------|
| lnop |
| bi $2 |
| opdata | <---- ncp
| stop |
|------------|
| slot[3] | * slot[] は各 32 bit で有効なデータがない状態
| slot[2] | を表しています
| slot[1] |
| stream | <---- arg0, ret
`------------'
low memory
getc() 実行後に 返り値 rc と errno を格納し、do_spe_run() 内で npc を +4 した後の SPE thread スタックに以下のようになります。
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain |
|------------|
| |
|------------|
| lnop |
| bi $2 | <---- ncp
| opdata |
| stop |
|------------|
| errno |
| slot[2] |
| slot[1] |
| rc | <---- arg0, ret
`------------'
low memory
可変引数関数の場合も同様ですが、以下に fprintf() 実行時 (PPE では vfprintf が実行されるため、対応するオペコードハンドラは default_c99_handler_vfprintf() となる) のスタックのみ示しておきます。
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain |<-----. <---- input SP
|------------| |
| Reg 79 | |
|------------| |
| Reg 78 | |
|------------| |
// ... // |
|------------| |
| Reg 6 | |
|------------| |
| Reg 5 |<--. |
|------------| | |
| slot[3] | | |
| slot[2] | | |
| slot[1] | | |
|caller_stack|------'
|------------| |
| slot[3] | |
| slot[2] | |
| slot[1] | |
| next_arg |---'
|------------|
| slot[3] |
| slot[2] |
| slot[1] |
| format | <---- arg1
|------------|
| slot[3] |
| slot[2] |
| slot[1] |
| stream | <---- arg0, ret
|============|
| |
|------------|
| |
|------------|
| lnop |
| bi $2 |
| opdata | <---- ncp
| stop |
`------------'
low memory
なお、現在は公開されてませんが、spe.c 内には
register_handler(void * handler, unsigned int callnum )
なる関数があり、ユーザー独自のハンドラを登録できるようにすることが検 討されていた形跡はあります。が、libspe の API としての公開はされていませ ん。
前回からちょっと時間が経ってしまいましたが、今回は SPE プログラムのイメー ジを PPE のプログラムに組み込む場合の話です。SPE thread を生成する前にそ の実行イメージを (PPE プログラムの空間から) 見えるようにする必要があるの ですが、それには 2 つの方法があります。
ここで後者に関しては spe_open_image() を実行すると SPE プログラムを mmap() し、返り値として spe_program_handle_t 型の変数 (ハンドラ) を返すの で、これをspe_create_thread() の引数として与えればよいことになります。
前者の場合も SPE オブジェクトは PPE プログラムにリンクされているんだ し、何かマクロでもかまして終わりだろと思って Toolchain に付属のサンプルプ ログラムを見てみると、ハンドラは定義されているが extern されていて、実体 がソースコードのどこにも見当たりません。もしかして libspe で何かやってい るのか?と思って見ましたが、何も見付けられず、、
で、サンプルの make 時のログでも見るかということで眺めてみたら、、見 知らぬものがありました。SPE オブジェクトを PPE プログラムにリンクする場合 には ar されたライブラリをリンクに使用しますが、このライブラリを作成する 前に ppu-embedspu というコマンドを実行しています。これは何?と思って見て みると shell script で書かれていて、引数に ハンドラ名や SPE プログラムを 指定すると、SPE プログラムの加工と対応するハンドラの作成をしてくれるよう です。
そんなこと書いてあったか?と思いマニュアルを見直すとたしかにありまし た。
CBE_SDK_Guide_1.1.pdf, p.28 "The ppu-embedspu tool that is part of the GNU tool chain is used to link a PPU executable and a SPU executable into a single Cell BE executable."
覚えてねーよ。
やっと解析開始ということで、第一回は libspe から始めます。
まずはマニュアルということで、SPE Runtime Management Library, Version 1.1 (libspe_v1.1.pdf) を読んで、解析に必要な情報を洗い出してみます。
ということで、これらを管理しているデータ構造と制御関数を探してみます。
spe.h を見てみると以下の構造体があります。
| struct group_list | SPE group リストの先頭データ |
| struct grpsElem | SPE group リストデータ |
| struct group_store | SPE group 管理データ |
| struct grpListElem | SPE thread リストデータ |
| struct thread_store | SPE thread 管理データ |
図で表すとこんな感じです。
+----------+ +--------+ +--------+
|group_list| -> |grpsElem| ----------------------> |grpsElem| -
+----------+ +--------+ +--------+
| |
V V
+-------------+ +-------------+
| group_store | | group_store |
| | | |
|+-----------+| |+-----------+|
||grpListElem|| ||grpListElem||
++-----------++ ++-----------++
| |
V
+-----------+ +--------------+
|grpListElem| -> | thread_store |
+-----------+ +--------------+
|
V
+-----------+ +--------------+
|grpListElem| -> | thread_store |
+-----------+ +--------------+
|
次にこれらのデータを制御する関数にどんなものがあるのか見てみると、spe.c に以下の関数がありました。
| srch_thread() | SPE thread 識別子 (speid) の検査 |
| srch_group() | SPE group 識別子 (gid) の検査 |
| add_group_to_groups() | SPE group を group list に追加 |
| add_group_to_groups() | SPE thread を SPE group に追加 |
| remove_thread_from_group() | SPE thread を SPE group から削除 |
となっており、一旦作成した SPE group は削除できないみたいです。
実際の使用方法に関しては、SPE thread のライフサイクル (生成、実行、終了) に関わる libspe の 3 API と SPE thread のスタート関数を見てみます。また、libspe はスレッドの管理に pthread を使用し、spe の制御は syscall を使用していますので、これらの関数を適当に抜き出しました。
なお、これらの関数は全て spe.c 内に書かれています。
spe_create_group()
-> spe_gid_setup() // SPE group 管理データ生成
-> calloc() // SPE group 識別子を生成
-> add_group_to_groups() // SPE group を group list に追加
spe_create_thread()
-> srch_group() // SPE group 識別子を検査
-> spe_setup() // SPE thread 管理データ生成
-> spe_gid_setup() // SPE group が指定されていない場合
-> calloc() // SPE group 識別子を生成
-> add_group_to_groups() // SPE group を group list に追加
-> calloc() // SPE thread 識別子を生成
-> spe_create()
-> syscall(spe_create) // spu context の生成
-> add_thread_to_group()
-> pthread_create(,,spe_thread,) // SPE thread の生成 (spe_thread() を実行)
spe_thread()
-> do_spe_run()
-> spe_run()
-> syscall(spe_run) // spu context の実行
// return すると終了
spe_wait()
-> srch_thread() // SPE thread 識別子を検査
-> pthread_join() // SPE thread の終了待ち
-> spe_cleanup() // SPE thread 管理データ削除
-> remove_thread_from_group() // SPE thread を SPE group から削除