アウトオブオーダ実行をベクトル命令でサポートすると、LMUL>1の時のレジスタ読み出し、書き込み操作が問題となる。論理的に考えれば、LMUL>1であったとしても、複数のベクトル・レジスタを1つのベクトル・レジスタと考えて読み書きをすれば問題ないはず。
しかし、アウトオブオーダ実行により物理レジスタを用意すると、個々のベクトル・レジスタは連続した場所に割り当てられることはなく、リネーム時にフリーリストから分断した物理レジスタに割り当てられることになる。
これにより、LMUL=8とした場合、1命令で8つのベクトルレジスタを用意する必要があり、デコード時にLMUL数分だけサイクル数が余計にかかることになる。これを許容するかどうかというところになるのだが、いろいろと問題が考えられそうな気がしている:
- リネーム時に、8つのレジスタを同時にリネームすることは負荷が大きすぎるので非現実的
- 8つの個々のマイクロ命令に分割するとなると、バックエンドの命令発行エントリ数を圧迫することになる。
したがって、あくまでLMUL=8であっても、単一の命令をディスパッチし、バックエンドでは1つ命令発行エントリを消費するのが現実的だと思われる。
そう考えると、やはりLMULが1だろうが8だろうがリネームの回数を1回に抑える方法を考える必要がある。一番現実的に思われるのはLMULを変更するたびに物理レジスタをソートしなおすというものだ。
そもそもLMUL=8のときに物理レジスタがバラバラになってしまうのが問題なので、LMULの値を変更(もしくは小さい値から大きい値に変更する)場合には、物理レジスタの番号が順番になるのようにソートしてしまえばよいのではないかということになる。
例えばLMUL=1の初期状態で、LMUL=1の時に以下のような論理・物理レジスタのマッピングであったとする。
v0 | 100 |
---|---|
v1 | 107 |
v2 | 35 |
v3 | 12 |
v4 | 8 |
v5 | 0 |
v6 | 62 |
v7 | 99 |
LMUL=8になった時点で、これらをすべてシーケンシャルに変えてしまえばよい。まあ基本的には0から順番に割り当てなおすことになると思われる。
v0 | 0 |
---|---|
v1 | 1 |
v2 | 2 |
v3 | 3 |
v4 | 4 |
v5 | 5 |
v6 | 6 |
v7 | 7 |
で、常に新たに物理レジスタを割り当てるときは8の倍数で割り当て、そこから8個の物理レジスタをシーケンシャルに確保しておけばよい。こうすることで、常に1回の物理レジスタのリネームで仮想的に8つの物理レジスタを確保できることになる。
では具体的にどのようにこの並べ替えを実現するか。vsetvli
命令によりレジスタ幅が変化した場合を考える。
vsetvli x10, x11, e32, m8 vadd.vv v8, v16, v24
vsetvli命令が、過去のLMUL値と値が異なることを見つけると、例外を発行してマイクロ命令を動かす。マイクロ命令では、すべてのバラバラの物理レジスタに置かれているデータを、シーケンシャルなレジスタ配置に直す必要があり、このためには一度メモリにスタックし、シーケンシャルに並び替えられた物理レジスタに再びロードし直すというのが求められそうな気がする。
vs1r v0, (backup + vlenb* 0) vs1r v1, (backup + vlenb* 1) ... vs1r v31, (backup + vlenb*31) vl1r v0, (backup + vlenb* 0) // 物理レジスタ0 vl1r v1, (backup + vlenb* 1) // 物理レジスタ1 ... vl1r v31, (backup + vlenb*31) // 物理レジスタ31
こうすると、残念ながら常にLMULの値を変更すると64命令分のマイクロ命令が動作するため少なくとも64サイクルのペナルティが生じることになる。メモリのレイテンシの問題もあり非常に不便だと思う。しかしまあそもそもベンチマークにおいてLMULを変更する場合がほとんどないことを考えれば、これくらいのペナルティは許容すべきなのかもしれないというのはある。
もうちょっとレジスタ内部で移動ができないかというのを考えるのだが、マイクロ命令なぞ使わずに物理レジスタ内でソーティングネットワークなど作って置き換えることができないかとも考えるが、、、
あるいはLMULなど関係なくもっと軽率に、Widening/Narrowing命令とか、型の違うベクトルロード・ストア命令を実行されるとそのたびにLMULが動的に変わってしまうのでそのたびにソーティングが発生するのはどうにももったいない。