Ten little endian boys (リトルエンディアンCPUでビッグエンディアンMPUをエミュレートする)
タイトルについては「特に意味はない」です。
前回の記事で、「keropiはビッグエンディアンのMC68000 MPUをリトルエンディアンのx86マシンで効率よく動作させるために、おもしろいテクニックを使っています」と述べましたが、今回はこの部分を実際に、ソースを追いながら解説していきたいと思います。
まずは、ビッグエンディアンとリトルエンディアンについて簡単におさらいです。
たとえば変数aに0x12345678という4バイトの値が格納されている場合、これをメモリに格納すると、
ところで、X68000の搭載するMPU MC68000はビッグエンディアンです。今回解析しているX68000エミュレータのkeropiが動作するx86はリトルエンディアン。
つまりkeropiはリトルエンディアンマシンでビッグエンディアンマシンをエミュレートしていることになります。
PSPもMIPSをリトルエンディアンで動作させているので、keropiをベースにX68000 for PSPを作る場合はエンディアン部分の修正は不要になりそうです。AndroidはとりあえずARMマシンをターゲットにしたいですが、これもリトルエンディアンでしょうか。
さて、リトルエンディアンのマシンでリトルエンディアンのマシンをエミュレートする場合は、変数a=0x12345678をメモリ上のアドレスpに格納するには、
簡単なプログラムで確認してみましょう。
前回の記事で、「keropiはビッグエンディアンのMC68000 MPUをリトルエンディアンのx86マシンで効率よく動作させるために、おもしろいテクニックを使っています」と述べましたが、今回はこの部分を実際に、ソースを追いながら解説していきたいと思います。
まずは、ビッグエンディアンとリトルエンディアンについて簡単におさらいです。
たとえば変数aに0x12345678という4バイトの値が格納されている場合、これをメモリに格納すると、
それぞれ格納されます。ビッグエンディアンのマシンはメモリ上に0x12, 0x34, 0x56, 0x78という順番で、
リトルエンディアンのマシンはメモリ上に0x78, 0x56, 0x34, 0x12という順番で、
ところで、X68000の搭載するMPU MC68000はビッグエンディアンです。今回解析しているX68000エミュレータのkeropiが動作するx86はリトルエンディアン。
つまりkeropiはリトルエンディアンマシンでビッグエンディアンマシンをエミュレートしていることになります。
PSPもMIPSをリトルエンディアンで動作させているので、keropiをベースにX68000 for PSPを作る場合はエンディアン部分の修正は不要になりそうです。AndroidはとりあえずARMマシンをターゲットにしたいですが、これもリトルエンディアンでしょうか。
さて、リトルエンディアンのマシンでリトルエンディアンのマシンをエミュレートする場合は、変数a=0x12345678をメモリ上のアドレスpに格納するには、
と何も考えずにすみます。*p = a;
簡単なプログラムで確認してみましょう。
結果は以下の通り、リトルエンディアンのメモリ並びになっています。#include <stdio.h>
main()
{
unsigned int a = 0x12345678;
unsigned int b;
unsigned int *p;
unsigned char *s;
p = &b;
*p = a;
printf("0x%x\n", *p);
s = (unsigned char *)p;
printf("0x%x 0x%x 0x%x 0x%x\n", *s, *(s + 1), *(s + 2), *(s + 3));
}
ところが、今回はリトルエンディアンマシンでビッグエンディアンマシンをエミュレートする必要があるため、変数a=0x12345678をメモリ上のアドレスpに格納するには、例えば0x12345678
0x78 0x56 0x34 0x12
という感じに順番を並べ替えなければなりません。もう少し違う手法もあると思いますが、どうしても手数は多くなってしまいそうです。*p = a << 24 | 0xff0000 & a << 8 | 0xff00 & a >> 8 | a >> 24;
こちらも簡単なプログラムで確認してみましょう。
実行結果は以下の通り。#include <stdio.h>
main()
{
unsigned int a = 0x12345678;
unsigned int b;
unsigned int *p;
unsigned char *s;
p = &b;
*p = a << 24 | 0xff0000 & a << 8 | 0xff00 & a >> 8 | a >> 24;
s = (unsigned char *)p;
printf("0x%x 0x%x 0x%x 0x%x\n", *s, *(s + 1), *(s + 2), *(s + 3));
}
0x12 0x34 0x56 0x78
エンディアンの異なるCPUをエミュレートするのは、バイトオーダーを入れ替えなければならないのでとても面倒です。面倒なだけなら良いのですが、手数が増えるということは、性能面での劣化を意味します。
例えばVRAMへの書き込み処理が性能劣化するのならば、グラフィック処理だけ多少遅くなるというペナルティで済みますが、実際にはそんなに甘くはなく、そこにはフォンノイマン型コンピュータの宿命が待ち受けているわけです。
すなわち、メモリ上にプログラムが格納されており、それを逐一読みだしては実行するというサイクルが必要であり、これはコンピュータをエミュレートするうえで非常に大きな比重を占めています。つまりこの部分の性能劣化はエミュレータ全体の性能劣化に直結することになります。
逆に言えば、ここの性能劣化をなるべく最小限に食い止めたいわけです。
つづく。
(すみません、keropiの解析までたどり着きませんでしたが力尽きたので次回に続きます。)
Comment
コメントの投稿
Trackback
http://hissorii.blog45.fc2.com/tb.php/214-efeb96ad