先日、中国製CPU「龍芯」を搭載したノートを記事にしたところ、多くの読者から反響があった。その大半は、龍芯はMIPSアーキテクチャのCPUなのに、x86とARMで書かれたアプリをバイナリ変換して実行する「LoongBT」はどういう仕組みなのか、という疑問だ。 筆者も疑問に思っていたのだが、中国の「国家自然科学基金基礎研究知識庫」にその答えがあった。龍芯の開発に携わった開発者らが、科学誌「中国科学」に投稿した論文で、その全貌が明らかとなっている。 結論から言ってしまうと、LoongBTはソフトウェアとハードウェア双方の協力によって、x86またはARM向けにネイティブでコンパイルしたプログラムを、逐次バイナリ変換しながら龍芯上で走らせるための仕組みだ。龍芯はx86とARMのバイナリ変換を高速で行なうための拡張命令を搭載しており、仮想化ソフト側がそれを利用すれば、高速にエミュレーションを実行できる。 龍芯はMIPSに準拠したプロセッサだが、どこかから買ったIPをそのまま実装しているわけではない。命令体系はMIPS64に準拠しているものの、アーキテクチャは完全に独自開発されたものである。最新の「龍芯3 3A2000」は、2015年に開発した「GS464E」アーキテクチャに基いている。 このため、オリジナルのMIPSにはないさまざまな特徴を備えており、命令セットも独自拡張された「LoongISA」を使用している。当然、MIPS命令はそのまま実行できるのだが、x86とARMのバイナリ変換を高速で実行するための独自命令やレジスタの拡張も取り入れられている。 拡張命令は、MIPS命令体系のUDI(User Defined Interface)を用いて、空きスロットに少量だけ実装した。しかし、スロット数以上にバイナリ変換を高速化するための拡張命令数の方が多いため、独自のプリフィックスによって命令を拡張し、プリフィックスが付与されたオリジナル命令が来た際に、オリジナル命令とは異なる振る舞うようにし、命令スロット不足を補っている。

x86/ARMのバイナリ変換に特化した命令体系 さて、x86やARMのバイナリを逐次変換しながらMIPSで実行する際にボトルネックとなる要素はいくつもあるが、その1つとして挙げられるのが、x86およびARMでは、演算結果が得られたとともにフラグが生成される点だ。 例えばx86ではEFLAGSと呼ばれるレジスタのうち、6bitが演算結果と関係があるフラグが生成される。桁上りを示すキャリーフラグ(CF)、偶数を示すパリティフラグ(PF)、調整フラグ(AF)、ゼロフラグ(ZF)、符号フラグ(SF)、そしてオーバーフローフラグ(OF)などだ。ARMも同様に4bitのフラグを格納するレジスタがある。 【表1】x86のEFLAGSレジスタ bit 略称 意味 0 CF キャリーフラグ 2 PF パリティフラグ 4 AF 調整フラグ 6 ZF ゼロフラグ 7 SF 符号フラグ 11 OF オーバーフローフラグ 【表2】ARMのNZCVレジスタ bit 略称 意味 31 N 1が負、0が正または0 30 Z 1が0、0がそれ以外 29 C 1キャリー/ボロー発生、0が桁あふれなし 38 V 1がオーバーフロー、0がオーバーフローなし 一方、MIPSにはこれらのフラグが用意されておらず、条件遷移はレジスタの内容を直接比較して行なう。このため、ソフトウェアでx86およびARMの演算結果フラグを実装すると、十数行にも及び、大きなボトルネックが発生する。そこでLoongISAではx86とARMの演算結果フラグを生成するプリフィックス命令「SETFLAG」を追加、そのフラグをMIPSの汎用レジスタに保存するようにした。 x86(x87)特有の浮動小数点フォーマットへの対応も特徴の1つ。x87には8個の浮動小数点レジスタがあるが、このレジスタの番号は固定ではなく、浮動小数点状態のTOP域を基に番号を決定する。ソフトウェアで実装すると、これもボトルネックとなる。 このためLoongISAでは3bitのTOPレジスタおよび対応命令を実装し、バイナリ変換の段階で対応するレジスタの番号を割り振るようにした。これも10行ほどの命令を削減できる。さらに、x86にはx87の8個のレジスタ内容を示す10bitのTagレジスタがあるが、LoongISAでは拡張命令により、MIPSの汎用レジスタでTagレジスタをエミュレートする。 x87では80bitの浮動小数点演算をサポートするが、RISCプロセッサは64bitまでしかサポートせず、相互変換には40行ほどの命令を消費する。そこでLoongISAでは64bitレジスタに入っている80bitの浮動小数点を64bitに変換してレジスタに格納する命令と、80bitの浮動小数点を64bit×2に変換して2つのレジスタに格納する命令を実装した。 このほか、MIPSとx86およびARMとでは異なるメモリアクセスのアライメントの問題の解消、ARM特有のMOV命令に対応するための命令なども実装されている。 x86やARMではこれらの基本命令のみならず、SIMD(Single Instruction Multiple Data)の実装もポピュラーになってきている。例えばIntelのAVXレジスタは256bit、ARMのSIMDレジスタは128bitの長さとなっている。しかしこれらのSIMD命令は、各々の特徴はあるものの実現する機能はほぼ同じであり、またいかなる複雑なSIMD命令でも、シンプルな基本命令に書き直せる。LoongISAではRISC本来のシンプルさを保つために、256bitのMIPSのSIMD命令をベースに、よく使われる典型的な演算向けにいくつかの独自拡張を行なう程度に留めている。