CELLプロセッサ--overlay


overlay

Cell SDK 2.1 インストール (Momonga rev:15290)

Cell SDK 2.1 がリリースされましたので、Momonga trunk rev:15290 にインストールしました。と言ってもベースは Fedora に近いので、なんの問題もなく toolchain のインストールとサンプル等のコンパイルができてます。

で、肝心の Cell SDK 2.0 との差分ですが、先日リリースした x264 for cell を 2.0, 2.1 でコンパイルして、spu 用のバイナリを readelf したところ、printf() 等の newlib 関連の関数のサイズには違いがありましたが、x264 の関数のサイズには違いはありませんでした。

但し、spu overlays のサンプル (overlay) の spu 用バイナリを objdump -d してみると、overlay 用に toolchain が用意するコードが若干変わっているようです (se.o は相変わらず、非 overlay section です)。

SPU code overlays take.2

今回は、overlay をどのように実現しているか、特に linker が実行バイナ リ作成時に自動的に追加する部分を中心にソースコードとアセンブラを追ってみ ます。

使用したのは overlay のサンプルとして提供されている simple です。

  • /opt/ibm/cell-sdk/prototype/src/samples/overlay/simple

overlay 機能に必要なデータとサイズ

データは以下の 2 つの構造体の配列から構成されており、_ovly_table[partition number] には各 segment の情報が入り、_ovly_buf_table[buf] には各 region にどの segment がロードされているかを表す partition number が入っています。

struct {
        u32 vma;    // SPU local store address that the section is loaded to.
        u32 size;   // Size of the overlay in bytes.
        u32 offset; // Offset in SPE executable where the section can be found.
        u32 buf;    // One-origin index into the _ovly_buf_table.
} _ovly_table[];

struct {
        u32 mapped; // One-origin index into _ovly_table for the
                    // currently loaded overlay. 0 if none.
} _ovly_buf_table[];

また、必要となるサイズですが、

  • r: region の数
  • s: segment の数
  • f: 他の segment から呼ばれる (stub が必要な) 関数の数

とすると

  • manager: about 400 bytes
  • tables: s * 16 + r * 4 bytes
  • stubs: f * 8 + s * 8 bytes

となります。table と stub のサイズに関しては、以後の関連部分のコメン トを追加してあります。

サンプル (simple) では各配列は以下のようになっています。

 // 以下の table が segment の数 (s) 分追加 (s*16 bytes)
00001050 < _ovly_table >:
 // partition number = 0
 1050:       00 00 0a 60     // u32 vma
 1054:       00 00 00 60     // u32 size
 1058:       00 00 0b 60     // u32 offset
 105c:       00 00 00 01     // u32 buf
 // partition number = 1
 1060:       00 00 0a 60     // u32 vma
 1064:       00 00 00 60     // u32 size
 1068:       00 00 0b c0     // u32 offset
 106c:       00 00 00 01     // u32 buf

 // 以下の table が region の数 (r) 分追加 (r*4 bytes)
 // region 毎に現在有効になっている segment index を保存
00001070 < _ovly_buf_table >:
 // buf (region index) = 1
 1070:       00 00 00 00

overlay 関数 (stub 関数) の呼び出し

main() 内から overlay 関数 (o1_test1(), o1_test2(), o2_test1(), o2_test2()) を呼び出していますが、呼び出しが各関数に対応した stub の呼び 出しに変わっています。このように stub 関数を介在させて、stub 関数内で overlay による code 追加を行うことで text section を変更しないで済みます。

00000300 < main >:

 300:   il      $3,101             # 65 // o1_test1(101) に対応する
 310:   brsl    $0,90 <_start+0x60># 90 // stub 関数の呼び出し
             :
 340:   il      $3,102             # 66 // o1_test2(102) に対応する
 344:   brsl    $0,90 <_start+0x60># 90 // stub 関数の呼び出し
             :        
 368:   il      $3,201             # c9 // o2_test1(201) に対応する
 36c:   brsl    $0,b0 <_start+0x80># b0 // stub 関数の呼び出し
             :
 390:   il      $3,202             # ca // o2_test2(202) に対応する
 394:   brsl    $0,a8 <_start+0x78># a8 // stub 関数の呼び出し
             :

stub 関数

start: の後ろに overlay 関数用の stub があります。この stub では、 overlay 処理を行う manager (__ovly_load) への引数 (overlay 関数の address と partition number) をレジスタに代入して manager に分岐します。

00000030 < _start >:
             :
  88:   brsl    $0,300 
# 300 // main() 呼び出し 8c: brsl $0,418 # 418 // 終了処理 // 以下のいずれかの stub が 関数の数 (f) 分追加 (f*8 bytes) 90: ila $79,2656 # a60 // o1_test1() のアドレス 94: br a0 <_start+0x70># a0 98: ila $79,2704 # a90 // o1_test2() のアドレス 9c: nop $0 // 以下の stub が segment の数 (s) 分追加 (s*8 bytes) a0: ila $78,1 // o1_*() の partition number a4: br f0 <__ovly_load># f0 // manager に分岐 a8: ila $79,2656 # a60 // o2_test1() のアドレス ac: br b8 <_start+0x88># b8 b0: ila $79,2704 # a90 // o2_test2() のアドレス b4: nop $0 b8: ila $78,2 // o2_*() の partition number bc: br f0 <__ovly_load># f0 00000a60 < o1_test1 >: 00000a90 < o1_test2 >: 00000a60 < o2_test1 >: 00000a90 < o2_test2 >:

manager (__ovly_load) 関数

ソースコードは spu_ovl.S (rev 1.1) です。開発ツリーのソースと spu toolchain のソースは License が異なる程度 で違いはありません。大まかな処理の流れは以下のようになります。

  1. 引数として、以下を受け取り。
    • $78: overlay 関数の partition number
    • $79: overlay 関数の address
  2. スタックの back chain を遡って、overlay 関数からの呼び出しであるかを 探索。lr には、対応する関数が overlay 関数である場合、
    • return address (通常はこれのみ)
    • 一つ前の overlay 関数の address
    • 一つ前の overlay 関数の partition number
    • partition number
    が入っており、lr に格納されている return address がoverlay 関数に共通の address (__ovly_return) かどうかで判定。
  3. 上記のフォーマットで呼び出された関数用の lr を作成。
  4. partition number が 0 の場合は overlay 関数の呼び出しではないた め、__ovly_load_restore へ分岐して復帰処理。
  5. _ovly_table[partition number] から overlay 領域の情報を取得。
  6. _ovly_buf_table[buf] と partiton number を比較して、すでに領域がロー ドされているかを判定し、ロードされていない場合は DMA 転送。
  7. overlay 関数の呼び出し。
  8. overlay 関数から戻ってきたら
    • $78: 一つ前の overlay 関数の partition number
    • $79: 一つ前の overlay 関数の address
    として 1. から再実行。

このように一つ前の overlay 関数の情報を記録しておいて、overlay 関数から 戻ってきた後に、一つ前の overlay 関数の呼び出しであるかのように manager(__ovly_load) を再実行することで、前回説明した overlay region 間を ピンポンする関数呼び出しのような自由度の高い関数呼び出しを可能にしていま す。

_ovly_table[], _ovly_buf_table[] の作成

処理しているソースは binutils/bfd/ の cpu-spu.c, bfd/elf32-spu.c です。ソースも若干異なるようなのですが、 linker(ld, bfd) がどのような処理をしているのかさっぱりわからないので今回 はパスで、、

SPU code overlay take.1

Cell SDK 2.0 に追加された SPU code overlays を試してみました。この機 能の説明は cd image (CellSDK20.iso) の pdf/cpbprg00.pdf - "Software Development Kit 2.0 Programmer's Guide Version 1.0" Chapter 4. SPU code overlays にあります。

overlay 機能の概要

詳細は上記を参照ですが、基本的には、linker script の変更のみで overlay 機能を使用することが可能です。overlay 実現に必要なものは linker が実行バイナリ作成時に自動的に追加してくれます。

overlay するためには、関数の実体がどの section にあるか?や、いつ section を入れ換えるか?を管理する必要があり、linker の手助けなしにやろう とすると結構手間がかかるのですが、linker script を変更するだけでよいとい うことになると、手軽にいろいろな構成が試せるので、ハマればかなり強力な機 能だと思います。

overlay 機能を使用する場合の linker script の変更点

以下の default で使用される linker script と、overlay 機能のサンプル (simple) で使用される linker script の diff を見てもらえば一目瞭然ですが、 OVERLAY 出力 section の追加と、overlay 対象の入力 section の移動を行うだ けです。

$ diff -u /opt/cell/toolchain-3.3/spu/lib/ldscripts/elf32_spu.x \
          /opt/ibm/cell-sdk/prototype/src/samples/overlay/simple/spu_olay/ld.script

--- /opt/cell/toolchain-3.3/spu/lib/ldscripts/elf32_spu.x
+++ sdk-2.0_overlay/simple/spu_olay/ld.script
@@ -3,7 +3,7 @@
              "elf32-spu")
 OUTPUT_ARCH(spu)
 ENTRY(_start)
-SEARCH_DIR("=/usr/spu/lib");
+SEARCH_DIR("/opt/cell/toolchain-3.3/spu/spu/lib");
 /* Do we need any of these for elf?
    __DYNAMIC = 0;    */
 SECTIONS
@@ -51,12 +51,17 @@
   .plt            : { *(.plt) }
   .text           :
   {
-    *(.text .stub .text.* .gnu.linkonce.t.*)
+    *( EXCLUDE_FILE(olay1/*.o olay2/*.o) .text .stub .text.* .gnu.linkonce.t.*)
     KEEP (*(.text.*personality*))
     *(.spu.elf)
     /* .gnu.warning sections are handled specially by elf32.em.  */
     *(.gnu.warning)
   } =0
+  OVERLAY :
+  {
+    .segment1 {olay1/*.o(.text)}
+    .segment2 {olay2/*.o(.text)}
+  }
   .fini           :
   {
     KEEP (*(.fini))
@@ -180,7 +185,6 @@
   .debug_funcnames 0 : { *(.debug_funcnames) }
   .debug_typenames 0 : { *(.debug_typenames) }
   .debug_varnames  0 : { *(.debug_varnames) }
-  .note.spu_name 0 : { *(.note.spu_name) }
   PROVIDE (__stack = 0x3fff0);
   /DISCARD/ : { *(.note.GNU-stack) }
 }

いろいろ試す

用意されたサンプルをただ実行してもしょうがないので、変更を加えて、以 下のことができるか確かめてみました。もとのサンプルは overview を使用して います。

overview には se.o が overlay されないバグがありますが、これも修正し ています。

overlay region 間をピンポンする関数呼び出し

ここでピンポンとは、ある region (reg A) の segment (seg A1) にある関 数 (func A1) から他の region (reg B) にある関数 (func B) が呼ばれた場合に、 func B から reg A の seg A1 と異なる segment (seg A2) にある関数 (func A2) を呼ぶことです。

これが出来ると overlay の自由度はかなり高まります。

今回は、sj.c 内に sj() という関数を作成し、 sf() [reg 2, seg 3] -> sh() [reg 3, seg 5] -> sj() [reg 2, seg 7] という 順番で呼び出しを行っています。

data section の overlay

C 言語 (それとも OS のプロセス空間?) の規格的には、全ての data は全 ての text から参照可能になっている必要がありますが、運用として、あるdata はある関数からしか参照しないようにすることは可能であり、overlay 対象とし て考えられるため、実現できるか確かめてみました。

ただし、const でない場合や、overlay region 間をピンポンする関数呼び出 し を考慮すると、適用はよく考えないとハマることになります。それでもローカ ル変数や、malloc で確保している領域を含めて overlay を検討すると LS は有 効活用できます。(例えば、同じ region に入れる segment のサイズに差があ り、この差で data が吸収できる場合)

今回は、sj.c で static char 型の配列 (.data)、static const char の文 字列 (.rodata) を定義して、text と同じ segment に入れています。ついでに 例として、文字列を書き換えても反映されないバグも入れています。

実行結果

以下のように sj() も実行され、message (Hello World!) もきちんと表示 されているようです。

また、上記のバグは 2 回目の message は "modified!" と表示されるよう に書き換えているのにそれが変更されていないことを指しています。

$ ./overlay_overview
Entering main
sa prints 97
sb prints 98
sc prints 100100099
sd prints 200200100
se prints 200200101
succeeded! expecting 101, got 101
succeeded! expecting 100, got 100
sf prints 200300102
sh prints 300500104
sj prints 300600106
sj overlay message: Hello World!
succeeded! expecting 106, got 106
succeeded! expecting 104, got 104
si prints 300600105
succeeded! expecting 105, got 105
succeeded! expecting 102, got 102
succeeded! expecting 99, got 99
sg prints 100400103
sh prints 300500104
sj prints 300600106
sj overlay message: Hello World!
succeeded! expecting 106, got 106
succeeded! expecting 104, got 104
si prints 300600105
succeeded! expecting 105, got 105
succeeded! expecting 103, got 103
succeeded! expecting 98, got 98
succeeded! expecting 97, got 97
Exiting main, status=0

HOME |  企業情報 |  事業内容 |  採用情報 |  社内活動 |  お問合せ |  サイトマップ
.
Copyright(c)2006 Sijam.Inc all rights Reserved
システム開発 株式会社シジャム