過去の難案件 PS2のカーネル開発

#はじめに
時はPlayStation2も　そろそろ終わり。
PS2互換機がゲームセンター等で使われていた時代の事です
私は当時　超新人だったんだけどね
ただ私は 大学を3ヶ月で光速中退して すぐにフリーランスになった変な経歴持ちです
そんな時　ある人物が 掲示板に メモリマネージャやDMA、３DCGについて質問をしていた
ちょうどその時期　仕事が楽だったので　光速で回答しました。

メモリマネージャ作りたいっていうので、簡単な方法として、連結リストでAllocateしていくと簡単だよ
DMAについては 方向のふらぐがこーであーするだけだよ
３DCGについては　DirectXを知識は入れてたので なんとなく回答
すると　やり取りから1週間で

>仕事してもらえますか？ PS2の自社タイトルを作ってます。コアエンジニアが不足しています

とメールアドレス宛にメッセージがきたので　請ける事に

これがすべての悲劇の始まりであった

#当時のゲーム機
いまでこそ　PS4、XBox、NintendoSwitch と、我々が使っているPCやスマホと同じパーツ、アーキテクチャでできているものの
当時のゲーム機は、ゲームに特化した 独自CPU、独自GPU、独自ハードをメーカーが作っていた

PS2に使われていたのは 東芝のEmotionEngine　通称EEである
このEEが 後にも先にもない変態CPUであり、当時は使い方さえあっていれば　恐ろしいパワーのあるCPUだった
（余談だが　後のPS3に使われたCPU Cellも変態である）
ただし　使い方が難しく、後期タイトルになるまで 能力を正しく使ったゲームはあまり出なかった

とにかく　PS2は変態ハード。これだけ覚えておけばいい

#PS2のスペック

PS2ハードウェア図は下記になる
http://glampert.com/2015/03-23/ps2-homebrew-hardware-and-ps2dev-sdk/
なんのこっちゃ？と思うかもしれないが

EECoreというのが通常のCPUのコアである。
ぶっちゃけ　結構すごいCPUだ
特に驚くのは 128ビット幅のDataBusを採用するのは世界初
https://ja.wikipedia.org/wiki/Emotion_Engine

DataBusが通常のCPUバスで、GS（GPU）やRDRAM（メモリ）とデータのやり取りを行なう
CC上にScratchPadがあるが、昔のハードではよくあった。キャッシュメモリと考え方は近いが
キャッシュと違い、自分ですべて制御する必要がある。
DataBusに比べ圧倒的に速いので、ScratchPadMemoryをうまく利用することが高速化に重要である
GSはいわゆるビデオカードで、頂点変換、ラスタライズ等を担当する

注目はVU0とVU1である。　VUは VectorUnitの意味で、いわゆるSIMD命令を行なう
念の為説明するが　SIMDとは、大きなレジスタで複数の計算を同時に行なうアセンブラ命令である
例えば 32ビット浮動小数点同士の掛け算を行なう場合、通常なら 4つの計算を行なうのに4回掛け算が必要なのが
128ビットレジスタを使い 32bitにPackして1命令で同時に４つの計算が行なう事ができる
VU0もVU1も128ビット幅を持っているので 32bit計算なら同時に４つ、16bitなら８つ計算できる
これは 画像や音声など大きなデータを計算するときに非常に有用である

DMAコントローラーがあり、EE Core、VU、GIF、Memory、I/O へ直接データを送れる
一応補足だが　DMAとは DirectMemoryAccessの略で、本来はデータを転送する際に 一度CPUを介して
データのやり取りが必要であり、CPUの負荷が増えるのだが
CPUを介さずに 直接メモリの送受信が出来る仕組みのことである

UV0はEE Coreと直接つながっておりコプロセッサーとして機能する
使い方は インラインアセンブラでSIMD命令をかけば VU0で計算される

特に変わってるのはVU1である
VU1はEE Coreと直接つながっておらず DataBusでつながっている。つまりEE Coreとは独立して動く
さらに GIF（GPUのインタフェース）へと Pass2 Pass3で直接つながっている
VU1は MacroModeとMicroModeの2つのモードがあり　切り替えて使うことができる
MacroModeでは VU1は EE Coreのコプロセッサーのような動作が可能
VU0と同じアセンブラを記述し、コードをDataBusを使いVU1に送り、結果をDataBusを使い EE Coreに返す
正直 あまり使わない。
VU1の使いみちは やはり MicroModeである
MicroModeでは EE Coreから命令セットを送り VU1内で処理を行い GIFを通して直接GSにデータを送る
つまり　頂点シェーダーに似たものである

これが　PS2が強かった＆変態だった＆難しかった　一番の原因である

#開発環境
DEV KITは　使いやすかった
が　問題は ライブラリである

基本的には　我々のような弱小会社にライブラリは提供されない
提供されるのは
`黒い本` と呼ばれる 5冊ぐらいのぶっとい本
中身は それぞれの機能のシステムコールの一覧だ
たとえば DMAの初期化は I/Oポート何番に 数字何を送れ、DMAを EE->VU1にするには I/Oポート何番に何を送れ・・
そんなのが　説明されている

正直　こんなのわかんねー！！　って投げ出したくなる

あとは　SONYさんのコードサンプルがもらえる
が、サンプルなので ライブラリ化はされていない

ライブラリが欲しい場合には 数百万円で大手メーカーが売ってくれたりする
が　結構高い
ので　自分たちで作ろうということになったらしい

#開発スタート
一応　今までPS2のライブラリを作っていたというシニアプログラマがある程度までライブラリを作っていた
ので、まだ全然手がついていないところから　担当することになった

ぶっちゃけ　シニアプログラマがお手上げしたところを作るということだ

一番お手上げしたのは　VU1プログラムだったので、まずはそれを作ることにした

##VU1
VU1は 今でいう頂点シェーダーのようなもので、GSに送る前に頂点データを変形させることができる
（もちろん　他の使い方も出来るが、案件では 頂点シェーダのみとして使っても良さそうだった）
例えば　モデルデータは ローカル座標でTポーズで立っているが
これにスキンメッシュを行いWVP行列を掛けて　GSに送る
ディフューズ、アンビエント、スペキュラー、テクスチャマッピングをかけてGSに送る
などだ
本当に頂点シェーダーみたいだ

ただ　恐ろしいのは　そのシェーダーの書き方である
HLSLやGLSLが当時存在しなかったのは仕方ないが　ガチでアセンブラである
しかも　後にも先にも触ったことのない　並列アセンブラ！！
色々と変態だったので　1つずつ紹介する

###命令パイプラインとストール
https://ja.wikipedia.org/wiki/%E5%91%BD%E4%BB%A4%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3

https://ja.wikipedia.org/wiki/%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E5%87%A6%E7%90%86

パイプライン自体は RISCでよくある5段パイプラインである
RISCだと命令長が全て同じなのである程度簡単に出来る
一応説明しておくと CPUはメモリから命令を取っただけでは実行できない
例えば上記だと

- IF (Instruction Fetch)
命令を命令キャッシュから読み出す
- ID (Instruction Decode/register read)
制御信号を生成し、レジスタ・ファイルをレジスタ指定子で参照する
- EX (EXecution/address calculation)
数値の計算やロード・ストアのデータやアドレス・分岐先の計算を行う
- MA (Memory Access)
ロード（メモリの読み出し）・ストア（メモリへの書き込み）を行う
- WB (Write Back)
レジスタにデータを書き込む

である
それぞれの命令をパイプライン化することで　実行を高速化している

ところが　このパイプラインでは色々なハザードがある
特に多いのが　データハザード

例えば　レジスタ R１に R2,R3,R4の値を加える場合

```
ADD R1, R2
ADD R1, R3
ADD R1, R4
```

と書いてしまうと、R1のレジスタのWBが終わるまで次の命令がストールするので　実際は

```
ADD R1, R2
NOP
NOP
NOP
ADD R1, R3
NOP
NOP
NOP
ADD R1, R4
```

と　とても無駄になる
ACCレジスタ使う等もあるが　R3を破壊してもいいならをテンポラリに使うと
(ニーモニック忘れたので適当)

```
ADD R1, R2
ADD R3, R4
NOP
NOP
NOP
ADD R1, R3
```

とすれば　いくぶん短くなるが
基本的にはNOPを絶滅するために、他の命令（順番かえても問題ないもの）を移動させ
NOPを減らしていく必要がある

###遅延（分岐）スロット
https://ja.wikipedia.org/wiki/%E9%81%85%E5%BB%B6%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88
よく Jumpは遅いというが、それは何故かといえば
遅延スロットの問題があるからだ
パイプラインの話をしたが、今のCPUはパイプラインやスーパースカラ化していて
１つの命令を多段パイプライン処理で高速化している
が、Jumpをした場合は そのときにフェッチしていた命令がすべて無駄になる
なぜなら　ジャンプ命令後の命令は　実行してはいけないからだ
そのため　ジャンプ命令をデコードでみつけると　命令フェッチを辞め、Jumpしたらパイプラインをクリアする必要がある
それがJumpすると遅くなる大きな理由だ

最近のCPUでは命令予測を行なうため、上記のフェッチミスが少なくなり　Jumpしても大きなペナルティは無くなって来ているが
当時のCPUでは　ペナルティは大きかった

そのため採用されていたのが　遅延分岐スロットだ！！

先程 デコードしたらJump命令だった場合、フェッチしたデータを破棄すると書いたが
コードの順番をかえて　Jumpでフェッチしていた命令を実行すれば ストールは最低限でおさまるはずだ

例でいうと

```
ADD R1, R2
JUMP xx
Jumpするので実行されないはずのコード
```

とコードを書くと JUMP命令がデコードされたときに　実行されないはずのコードがパイプラインにフェッチ済だ
ので　この命令をクリアしなければならず　ストール発生するので
Jumpでフェッチしていた命令は実行するという方針にかえて　上記を入れ替えて

```
JUMP xx
ADD R1, R2
Jumpするので実行されないはずのコード
```

とすれば、JUMP命令がデコードされたときには　実行してよい　ADD命令がフェッチされている
ここで　JUMPして フェッチされたコードを実行して　JUMP先の命令をフェッチ
とすれば　ストールを回避できる
ので　PS2では　この方法で　遅延分岐スロットはJUMP後に実行されることになっている

###並列アセンブラ
VU1が変態っていったけど、たしかに　各種ストールを自力で解決しなければならない（普通はコンパイラが解決）
それだけで変態っていうのは　可愛そうだが
並列アセンブラは　まさに変態だ！！

VU1は SIMD命令とMIPS命令の２つのコアがあり　レジスタやメモリを共有した状態で同時に走る
なんのこっちゃ？

```
ADD R1, R2       jnz R4.x, xxx
```

と　同じ行に２列で　アセンブラ命令をかける
左がSIMD命令で主にベクトルの演算を行い、右はMIPS命令で メモリのロードストア、分岐等を行なう
もちろんレジスタは共有しているので　データのストールや　遅延分岐スロットも両方に発生する
また　同じ命令も一部あるため、SIMD命令を MIPS側に追いやることで　クロック数を減らす努力も出来る

こうやって工夫すれば　簡単なスキンメッシュで ２２クロックぐらいになったりする！

##メモリアロケータ
PS2にはライブラリがなく、メモリ管理も独自で作る必要があった
わたしが　メモリアロケータは 連結リストが簡単ですよ って教えたのでそのまま実装していた
https://ja.wikipedia.org/wiki/%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88
しかし　これには大きな問題がある
前方から順番にAllocしていき、使わなくなればフラグをおとし、それ以降全てが未使用であれば削除する
という仕組みだが、途中に開放しないやつがいると　メモリが分断される
かといって　当時のゲーム機にGCなんていれる　そんな贅沢は出来ない

自分で掲示板で言ったので責任を感じ　なおすことに

Linuxのカーネルのコードを参考にし、バディアロケータを作った
https://codezine.jp/article/detail/9325
いちおうLinuxでは BuddyAllocatorの改良版の SlabAllocatorを採用しているが
基本は同じである

BuddyAllocatorも結局メモリ分断される危険性あるが、双方向リストよりはかなりマシだし
なにより　開放がビット演算でとても速いことだ
このアロケータにしてから　体感で３０％以上　メモリが有効に使えだした

やっぱ Linuxカーネルソースは　私の教科書だ

##DMA
その後手をつけたのは　DMAコントローラーだ
これは単に　前任者のバグだと思うが、よくDMAの方向がおかしくて固まっていた
ので、完全に制御した
特に大変なのが　割り込みだ
低レイヤーで書いているので、思わぬときに割り込みが入り、そこでDMAレジスタの値が書き換わったりしていた
ので、適切に　割り込み禁止、許可の制御を行った

##３Dライブラリ
オブジェクトを表示する程度の能力しかなかったため、作り直した
といっても　工数に余裕がなかったのでそのゲームに必要なものだけ
当たり判定を最低限作る。スフィア、BOX。
レイキャスト
アニメーション管理
モーフ
色々なシェーダー表現
・・・
技術的には基本的に枯れてるはずなので　物量との単純な戦い

##マルチスレッド
PS2自体 マルチスレッド使えたのか覚えてないが
PS2互換機 改良版なので　マルチスレッドが使えたので使った
もう　覚えてないぐらい　マニアックで苦労した覚えがある
ごめん　何も覚えてないので　そっとしておいてください・・

##サウンド
データを作ってDSPに送るだけなのに
元のライブラリは　音が永遠にループしたり途切れたりひどかったので
１から実装しなおした
やっぱり割り込み重要！

#シニアさんにやってもらったこと
モデルのコンバート
モデルの読み込み
メッシュデータをVU1に送るところ
ゲームに使うライブラリ
キー入力


あれれ？？　カーネル私一人で作ってるぞ？？？


#最後に
とりあえず　私がエンジン作ったゲームは無事発売され、海外版も作られ　ゲームセンターに可動された
同じエンジンを使って　オリジナルも作っていたが　エンジン作り終えたのでその初期に私は抜けたが
それらしいゲームは発売されていない

それいこう　その会社はゲームから足をあらったようだ。。