特集
MIPSなのにx86とARMアプリを高速に実行できる中国製CPU「龍芯」のカラクリ
2017年3月10日 16:28
先日、中国製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命令をベースに、よく使われる典型的な演算向けにいくつかの独自拡張を行なう程度に留めている。
x86/ARMバイナリ変換のための拡張
命令体系のみならず、ハードウェア自身にも大きな拡張が施されている。LoongISAのTLB(Translation Look-aside Buffer:仮想アドレスと論理アドレスを対応させた情報を一時的に保存したバッファメモリ)では、“ホストの仮想アドレスをホストの物理アドレス”に変換するテーブルと、“ゲストの仮想アドレスをホストの物理アドレス”に変換するテーブルの両方を同時に保存できる。この2つを識別するIDを持たせ、SETMEMというプリフィックス命令を追加することで、バイナリ変換時のメモリアクセスを削減させた。
また、本来バイナリ変換を行なう際に得られた命令は「データ」として扱われてしまう。もし1次データキャッシュから1次命令キャッシュに命令を移そうとすると、いったんメインメモリ上に書き出さなければならない。そこで龍芯では自動的にデータキャッシュと命令キャッシュの一致性を管理するようにし、簡単な操作だけで命令キャッシュと一致させられるようにした。
龍芯CPUでは、x86/ARM/MIPS間のレジスタ数の違いも吸収できるよう拡張した。x86では8個の汎用レジスタ、ARMでは16個の汎用レジスタを持つが、MIPSでは32個の汎用レジスタを持つ。このため汎用レジスタについては問題なくエミュレーションできる(が、龍芯最新のGS464Eアーキテクチャでは64bit/128個の汎用レジスタに拡張しているようだ)。
一方浮動小数点レジスタは、x86(x87)が8個の浮動小数点レジスタと16個の256bit浮動小数点/SIMDレジスタ、ARMが16個の128bit浮動小数点/SIMDレジスタとなっている。MIPSは32個の128bit浮動小数点/SIMDレジスタで、bit数が足りないものが存在する。そこでLoongISAでは幅を256bitに拡張したレジスタを64個備え、いずれのバイナリ変換でもボトルネックにならないようにした(ただしGS464Eアーキテクチャ解説内では、128bit/64個となっている)。
MIPS | x86 | ARM | 龍芯(GS464Eアーキテクチャ) | |||||
---|---|---|---|---|---|---|---|---|
幅 | 個数 | 幅 | 個数 | 幅 | 個数 | 幅 | 個数 | |
汎用レジスタ | 32bit | 32 | 32bit | 8 | 32bit | 16 | 64bit | 128 |
浮動小数点レジスタ | SIMDレジスタと共用 | 80bit(x87) | 8(x87) | SIMDレジスタと共用 | SIMDレジスタと共用 | |||
SIMDレジスタ | 128bit | 32 | 256bit | 16 | 128bit | 16 | 256bit | 64 |
伝統的なMIPSのシステムでは、ゲストが特権命令を発行しリソースにアクセスしようとすると例外処理に陥り、ホストのOSがカーネルモードをエミュレーションし、性能低下してしまうことが頻発する。そこでLoongISAでは仮想化された有限特権状態「ゲストモード」を新たに用意。ゲストモードで相応するリソースにアクセスする時に特権を与え、エミュレーションを省略できるようにした。
さらに、ゲストモード専用のコントロールレジスタを用意。ホストのコントロールレジスタと分離させ、シームレスな切り替えを実現する。
上に書いた以外にも、この論文には、記事では紹介しきれないほどの、x86/ARMバイナリ変換に関する最適化手法などについて記されている。同社の測定によると、龍芯のこのような実装、およびソフトウェアレベルでさまざまな最適化を施した結果、x86バイナリ変換実行は、MIPSネイティブ実行の約79.8%の性能が得られたという。
元も子もない言い方かもしれないが、龍芯の開発の真の目的は、MIPSのエコシステムの構築や拡大ではなく、既存のx86/ARMのエコシステムを十分に活かせるよう、バイナリ変換を高速実行することにある。MIPSのエコシステムがどうあがいてもx86およびARMに遠く及ばないということは、龍芯自身がよく理解しており、だからこそバイナリ変換アクセラレーションの技術開発に熱心なのだ。
論文の最後には龍芯の目標が掲げられており、1ステップ目としてWindowsが龍芯の上でスムーズに動作すること、2ステップ目としてAndroidが龍芯の上で動作し、最終的に龍芯を採用したモバイルデバイスが登場することを挙げている。龍芯のバイナリ変換アクセラレーションは、オマケというレベルではなく、至って真面目に実装されているのである。