RISC-VのZicondとかいう排除アートについて

2023年9月にZicond拡張というのが、ろくに議論もされずに*1承認されたようです。 RISC-Vもその場の思い付きで命令セットを拡張するような、歴史に学ばない命令セットに堕したということですね。

Zicond拡張の機能はRISC命令セットとして素晴らしいものですが、そのエンコーディングは敵であるcmov命令を追い出すために非常に悪意のあるものとなっています。 また、cmov命令を追い出すだけならまだましで、そのエンコーディングの歪みのせいでRISC-V全体を使いづらくしています。 このようなものは、排除アートと呼んで差し支えないでしょう。 命令セットは芸術ですし、英語ではhostile instruction-set architectureとすればよさそうです。

Zicond拡張

Zicond拡張は、czero.eqz命令とczero.nez命令の二つだけからなる拡張です。 czero.eqz命令は、dst = rs2 == 0 ? 0 : rs1;を実行する命令です。 czero.nez命令は、dst = rs2 != 0 ? 0 : rs1;を実行する命令です。

これらの命令は、条件演算や条件転送を実現するために有用です。

条件演算や条件転送は、分岐予測ミスを避けるために有用です。 一方で、条件演算命令や条件転送命令は、RISC的な考え方と相性が悪いです。 条件演算を実現するためによく用いられるのはフラグレジスタですが、フラグレジスタのような暗黙のオペランドRISCの考え方と相性が悪いです。 また、条件転送は四オペランド命令になるので、これもRISCの標準的な命令フォーマットと相性が悪いです。 命令フォーマットに押し込めるだけであれば破壊的な命令にしてしまえばいいのですが、レジスタを自由に選べないのはRISCの考え方には反します。 そもそも、レジスタファイルの読み出しポートが三つ必要になるという本質的な問題が解決困難です。

Zicond拡張の提供する二命令は、この問題をうまく解決しています。 条件加算はczero.nez rd, rs2, rc; add rd, rs1, rdのように二命令で実現できます。 条件転送はczero.nez rd, rs1, rc; czero.eqz rtmp, rs2, rc; or rd, rd, rtmpのように三命令で実現できます。 このように複数命令に分解するときに役に立つ、汎用的なパーツとなる命令を見つけたというのがZicond拡張の優れている点なのでしょう。 単純な命令ではありますが、RISC命令セットの歴史を変える、偉大な一歩であることは間違いないと思います。

エンコーディング

czero.eqz命令とczero.nez命令のエンコーディングは、非常に邪悪なものになっています。 敵であるcmov命令への嫌がらせとしてこのエンコーディングを採用したものと思われます。

cmov命令に対する嫌がらせ

czero.eqz命令は、0000111_xxxxx_xxxxx_101_xxxxx_0110011というエンコーディングを持っています。 cmov命令は、xxxxx_11_xxxxx_xxxxx_101_xxxxx_0110011というエンコーディングを持つことが予定されていました。 これら二つは両立しません。

cmov命令は四オペランド命令であり、まとまったエンコーディング空間を要求します。 一方で、czero.eqz命令は普通の三オペランド命令であり、そのエンコーディングはかなり自由に決められたはずです。 それにもかかわらずピンポイントでこのエンコーディングを選んだということは、どうしてもcmov命令を追い出したいという政治的な意図が含まれているとが感じられます。

また、czero.eqz rd, rs1, rs2命令は、cmov rd, rs2, rs1, zero命令と同じ動作をします*2。 したがって、czero.eqz命令を00000_11_[rs2]_[rs1]_101_[ rd]_0110011というエンコーディングにしておけば、将来的にcmov命令を採用する余地が残されたはずです。 なぜなら、[rs3]_11_[rs2]_[rs1]_101_[ rd]_0110011というエンコーディングを持つcmov命令のrs3にゼロレジスタを指定したビットパターンと同じだからです。 それにもかかわらず、あえて00001_11_xxxxx_xxxxx_101_xxxxx_0110011というエンコーディングにしたことは、絶対にcmovを追い出すぞという意志が感じられます。

なお、このような将来の拡張を見越したエンコーディングは、zext.h命令で行われています。 zext.h命令は0000100_00000_[rs1]_100_[ rd]_011x011というエンコーディングを持ち、これは0000100_[rs2]_[rs1]_100_[ rd]_011x011というエンコーディングを持つpack[w]命令のrs2にゼロレジスタを指定したビットパターンと同じです。 他にも、rev8命令とorc.b命令が将来の拡張を見越したエンコーディングを持ちます。 これらの工夫が意図的に行われていることは、以下から明らかです。

  • 他の単項命令が全て0110000_xxxxx_xxxxx_001_xxxxx_001x011というエンコーディングを持っているにもかかわらず、それを使っていない
    • 単項命令判定回路の負担よりも拡張性を優先した
  • RV32とRV64で動作が同一なのにもかかわらず、zext.h命令のエンコーディングが異なる

デコーダへの無配慮

cmov命令を排除したいがための歪んだエンコーディングは、デコーダにも負担をかけています。

funct7

RISC-VのB拡張の命令は、funct7(ins[31:25])を次のように使っています。

  • ins[31]は、常に0
  • ins[30]は、標準的な機能の切り替えに使われることが多い
    • 標準ではadd/subの切り替え、srl/sraの切り替えに使われている
    • B拡張では、and/andnor/ornxor/xnorの切り替えに使われている
    • 他の命令では単に便利な1bitとして使っている気がする
  • ins[29]は、さらなる拡張を区別する便利な1bitとして使われている
  • ins[28]は、常に0
  • ins[27]は、ビット演算らしい*3命令を区別するビットとして使われることが多い(ビット演算らしい命令であれば1)
  • ins[26]は、四オペランド命令を区別するためのビットとして使われている(三入力一出力の命令であれば1)
  • ins[25]は、乗算系の命令を区別するためのビットとして使われることが多い(乗算系の命令であれば1)

Zicond拡張に含まれる命令は、この方針を完全に無視しています。 特にわざわざins[26]を1にしていることには、四オペランド命令拡張を追い出す意図が感じられます。

funct3

funct7と比べると、funct3は多少はデコーダに対して配慮があるエンコーディングになっています。 とはいえ、RISC-Vの新しい拡張命令セットが考え無しのエンコーディングを持っていることには変わりがありません。

RISC-Vの標準的な命令は、条件が反転した命令をfunct3[0]で区別していました。 例えば、beq/bne000/001を、blt/bge100/101を、bltu/bgeu110/111を、それぞれfunct3として持ちます。 またアトミック演算命令はfunct7の最上位三ビットで実質的なfunct3をエンコードすることを意図しており、amomin/amomax100/101を、amominu/amomaxu110/111を、それぞれfunct7の最上位三ビットとして持ちます*4。 つまり、条件が反転した命令をfunct3[0]で、符号の有無をfunct3[1]で、それぞれ区別しています。

一方で、新しい拡張として取り入れられたmin/maxはこの方針を踏襲していません。 min/max100/110というfunct3を、minu/maxu101/111というfunct3を持ちます。 つまり、符号の有無をfunct3[0]で、条件の反転をfunct3[1]で、それぞれ区別しており、標準的な命令のエンコーディングと異なります。 どうやらslt/sltuの対立(符号の有無をfunct3[0]で区別している)に合わせてしまったらしいです(Regularize MIN vs. MINU encoding to match SLT vs. SLTU by aswaterman · Pull Request #88 · riscv/riscv-bitmanip · GitHub)。 一応、div/divuなどの対立もfunct3[0]で区別しているため、一見合理性があるように見えます。 しかし、slt/sltudiv/divurem/remuはいずれも条件が反転した命令を持たない命令であり、そうでない命令と同列に扱うべきではありません。 min/max/minu/maxuエンコーディングblt/bge/bltu/bgeuamomin/amomax/amominu/amomaxuエンコーディングに合わせるべきでした。 czero.eqz/czero.nez命令のエンコーディングは、この失敗を上塗りするものとなっています。

そもそも、funct3を101/111とすること自体が全く理解ができません。 素直に考えればbeq/bneと揃えた000/001エンコーディングを持つべきですし、001が貴重であることを重視すれば010/011エンコーディングも悪くなかったでしょう。 条件の反転をfunct3[1]で区別することを重視したとしても、000/010100/110は十分に余っています。 あえてczero.eqz/czero.nezのfunct3を101/111としているのには、なにがなんでもcmov命令を排除するぞという意志が感じられます。

今後の拡張への無配慮

整数命令のオペコードマップを見ると、Zicond拡張がいかに傍若無人な位置に居座っているかがわかると思います。 他に入れる場所はいくらでもあったのに、なぜこのエンコーディングを選んだのか、非常に疑問です。 だいたい、Zbtを放棄する*5からと言って、こんなところに穴をあけてしまったら貴重な連続オペコード空間が失われるわけで、甚だ考え無しと言わざるを得ません。

図1: RISC-Vの整数命令のオペコードマップ(一部)

まとめ

  • Zicond拡張という、条件実行を容易にする拡張がろくに議論もされずに承認された
  • 敵であるcmovに対する嫌がらせとしか考えられないエンコーディングを持つ排除アートであり、RISC-Vの価値を大きく毀損した

参考文献

BMEXT命令とBMDEP命令のニーモニックについては、以下のコミットを参考にしました。

それ以外のRISC-V命令のニーモニック、動作、およびエンコーディングの仕様については、以下の文献(いずれもCC-BY 4.0)によります。

  • RISC-V Integer Conditional (Zicond) operations extension Version 1.0(RISC-V International)
  • RISC-V Bit-Manipulation ISA-extensions Version 1.0.0(著作権者不明。RISC-V(暗黙にRISC-V Internationalを意味する可能性もあり)またはJacob Bachmeyer, Allen Baum, Ari Ben, Alex Bradbury, Steven Braeger, Rogier Brussee, Michael Clark, Ken Dockser, Paul Donahue, Dennis Ferguson, Fabian Giesen, John Hauser, Robert Henry, Bruce Hoult, Po-wei Huang, Ben Marshall, Rex McCrary, Lee Moore, Jiří Moravec, Samuel Neves, Markus Oberhumer, Christopher Olson, Nils Pipenbrinck, Joseph Rahmeh, Xue Saw, Tommy Thorn, Philipp Tomsich, Avishai Tvila, Andrew Waterman, Thomas Wicki, and Claire Wolf)
  • RISC-V Bitmanip Extension Document Version 0.93(Jacob Bachmeyer, Allen Baum, Ari Ben, Alex Bradbury, Steven Braeger, Rogier Brussee, Michael Clark, Ken Dockser, Paul Donahue, Dennis Ferguson, Fabian Giesen, John Hauser, Robert Henry, Bruce Hoult, Po-wei Huang, Ben Marshall, Rex McCrary, Lee Moore, Jiří Moravec, Samuel Neves, Markus Oberhumer, Christopher Olson, Nils Pipenbrinck, Joseph Rahmeh, Xue Saw, Tommy Thorn, Avishai Tvila, Andrew Waterman, Thomas Wicki, and Claire Wolf)
  • The RISC-V Instruction Set Manual Volume I: Unprivileged ISA Document Version 20191213(Arvind, Krste Asanović, Rimas Avižienis, Jacob Bachmeyer, Christopher F. Batten, Allen J. Baum, Alex Bradbury, Scott Beamer, Preston Briggs, Christopher Celio, Chuanhua Chang, David Chisnall, Paul Clayton, Palmer Dabbelt, Ken Dockser, Roger Espasa, Shaked Flur, Stefan Freudenberger, Marc Gauthier, Andy Glew, Jan Gray, Michael Hamburg, John Hauser, David Horner, Bruce Hoult, Bill Huffman, Alexandre Joannou, Olof Johansson, Ben Keller, David Kruckemyer, Yunsup Lee, Paul Loewenstein, Daniel Lustig, Yatin Manerkar, Luc Maranget, Margaret Martonosi, Joseph Myers, Vijayanand Nagarajan, Rishiyur Nikhil, Jonas Oberhauser, Stefan O’Rear, Albert Ou, John Ousterhout, David Patterson, Christopher Pulte, Jose Renau, Josh Scheid, Colin Schmidt, Peter Sewell, Susmit Sarkar, Michael Taylor, Wesley Terpstra, Matt Thomas, Tommy Thorn, Caroline Trippel, Ray VanDeWalker, Muralidaran Vijayaraghavan, Megan Wachs, Andrew Waterman, Robert Watson, Derek Williams, Andrew Wright, Reinoud Zandijk, and Sizhuo Zhang)

追記(20223/10/09 00:30)

どうやら当初は1000000_[rs2]_[rs1]_01x_[rd ]_0110011というエンコーディングを想定していたようです(riscv-zicond/zicondops.adoc at 042bb651d2044510893a469b305f99324676f7c3 · riscv/riscv-zicond · GitHub)。ins[31]を1にしているのは慣習と乖離していますが(そもそも貴重なオペコード空間に穴をあけています)、排除アートにはなっていなかったようです。 その後、ARの割り当てにより、排除アートとなったようです(fix funct7 to match with AR (0b111 instead of 0b11) assignment · riscv/riscv-zicond@87b0c71 · GitHub)。 ARは、おそらくArchitecture Review Committeeだと思われます(RISC-V Lifecycle Guideに書いてある用語)。 なので、憎むべき相手はPhilipp TomsichさんではなくArchitecture Reviewのチームだということになります。

*1:Fast Trackなので議論時間が短かったのは事実です(参考:Specification Status - Home - RISC-V International)。

*2:cmov命令のアセンブリ表現では、条件演算子と似た見た目になるように条件オペランドrs2がrdの次に来ます。

*3:主観

*4:ただし、amoxor/amoor/amoandはこの方針と一貫していません。

*5:What's new for RISC-V in LLVM 16 - Muxupに"the previously proposed but now abandoned Zbt extension (part of the earlier bitmanip spec)"と書かれています。ただし、出典が明記されていないため、RISC-Vの公式の見解であるとは確認できませんでした。