株式会社シジャム・ビーティービー

libspe

libspe and libspe2 december release

libspe-1.2.0, libspe2-2.0.1 がリリースされました。tarball は以下のメールに添付されています。

PS3Linux and libspe2

PS3Linux キタ

他にも spe-samples-1.0.tar.bz2 とか CELL-Linux-CL_20061110-ADDON.iso とか。

libspe2 のプロトタイプも来てます。

libc 解析 take.4 (libspe)

"PPE Serviced SPE C Library Functions" の 4 回目は libspe の処理につ いて解説します。

4.2 PPE Serviced SPE C Library Functions

  1. The SPE constructs a local store image of the input and outout parameters.
  2. The SPE creates a 32-bit message consisting of an opcode and pointer to the local store parameter image array.
  3. The SPE executes a Stop and Signal instruction.
  4. The PPE detects the Stop and Signal.
  5. The PPE invoke a specialist to service the request according to the message opcode.
  6. The PPE services the requested function.
  7. The PPE returns results in the local store image array.
  8. The PPE resumes SPE execution.
  9. The SPE returns that results back to the caller.

この内、libspe で行うのは 5., 6., 7., 8. です。

1 回目に説明した通り、spu_run システムコールの返り値として SPE の stop 命令のオペランドが返ります。また、2, 3 回目に説明した通り、SPE の stop 命令の次のアドレスに OPCODE と 引数へのアドレスが格納されており、こ れらを以下のようにして処理していきます。

  • lispe の spe_create_thread() API で生成された PPE thread の本体であ る do_spe_run() が spu_run システムコールを発行して SPE thread を実行し ています。SPE thread が stop 命令を発行すると、SPE thread が停止し、 spu_run システムコールから復帰。
  • do_spe_run() は、返り値に含まれる stop 命令のオペランド (OPCLASS) を 見ることで、対応する libc のオペクラスハンドラ ( default_c99_handler() or default_posix1_handler() ) を実行。
  • オペクラスハンドラは、stop 命令の次に書かれた OPCODE を見ることで対 応する libc のオペコードハンドラ (default_{c99,posix1}_handler_*) を実行。
  • オペコードハンドラは libc 関数を実行し、実行結果 (返り値と errno) を SPE のメモリイメージに格納。
  • do_spe_run() が spu_run システムコールを発行して SPE を再実行。

以下は 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 としての公開はされていませ ん。

libspe 解析 take.2

前回からちょっと時間が経ってしまいましたが、今回は SPE プログラムのイメー ジを PPE のプログラムに組み込む場合の話です。SPE thread を生成する前にそ の実行イメージを (PPE プログラムの空間から) 見えるようにする必要があるの ですが、それには 2 つの方法があります。

  • PPE プログラム生成時に SPE のオブジェクトをリンクする
  • PPE プログラムから spe_open_image() する

ここで後者に関しては 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 解析 take.1

やっと解析開始ということで、第一回は libspe から始めます。

まずはマニュアルということで、SPE Runtime Management Library, Version 1.1  (libspe_v1.1.pdf) を読んで、解析に必要な情報を洗い出してみます。

  • SPE thread と SPE group という管理単位がある
  • SPE thread は 処理単位 (spe で実行されるプログラム) 毎に生成される
  • SPE thread は SPE group に属する
  • SPE thread の属性 (policy, priority) は 所属する SPE group によって決まる
  • (当然だが) SPE thread と SPE group は複数生成可能

ということで、これらを管理しているデータ構造と制御関数を探してみます。

データ構造

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 group 生成関数


spe_create_group()
       -> spe_gid_setup()            // SPE group 管理データ生成
            -> calloc()              // SPE group 識別子を生成
            -> add_group_to_groups() // SPE group を group list に追加

SPE thread 生成関数


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 スタート関数


spe_thread()
  -> do_spe_run()
       -> spe_run()
            -> syscall(spe_run)      // spu context の実行
                                     // return すると終了

SPE thread の状態変化 (終了) を待つ関数


spe_wait()
  -> srch_thread()                   // SPE thread 識別子を検査
  -> pthread_join()                  // SPE thread の終了待ち
  -> spe_cleanup()                   // SPE thread 管理データ削除
  -> remove_thread_from_group()      // SPE thread を SPE group から削除

わかったこと

  • spe_{get,set}_priority() より pthread_setschedparam がコメントになっているので priority 操作は未対応?
  • spe_{get,set}_affinity() より affinity 操作は未対応
  • spe_{get,set}_context() より context 操作は未対応