続CPUエミュレーションを速くしたい - 静的バイナリ変換のハイブリッドシステム

前回の続きです。


どうやって、デコンパイルで生成したJavaコード実行と、エミュレーション実行を、都合よく切り替えるか、という
お話です。

以下が簡単化した擬似コードです。

	public void Execute()
	{		
		while (clk > 0) {
			int beforeClk = clk;
			
			デコンパイルしたコード列
			
			if (beforeClk == clk) {
				エミュレーション実行();
			}
		}
	}

Executeメソッドが、6502コアとなるメソッドです。
デコンパイルしたコード列」という部分に、6502コードからJavaへ変換したソースコード
埋め込まれているという前提です。


clkが6502コアを駆動する残りクロック数を表す変数です。
clkが0より大きい間、6502コアを駆動します。


ポイントとなるのが、デコンパイルしたコード列を実行する前に、clkの値を別の変数へ保存しておきます。
それがbeforeClkという変数です。


デコンパイルしたコード列」を実行した結果、通常はclkの値が減少します。
もし、clkが減少しなかった場合は、それはデコンパイルしたコードでは実行が不可能なパターンだったということになります。


そこで、clkが減少していない場合、つまりbeforeClkがclkと同値だった場合は、エミュレーション実行に切り替えます。


この仕組みによって、自己書換などのパターンでも、うまく実行することができるようになります。
その部分はエミュレーションによって実行されるようになります。


また、デコンパイルしたコード列で、元の6502のコードをすべてカバーしていなくても、エミュレーション実行が
カバーしてくれるようになるため、デコンパイルしたコード列は部分的でも良いというメリットが生まれます。


ファミコンソフトの6502コード中の、メインループとなる何度も繰り返し実行される部分のみ、デコンパイルをかけておき
高速に実行するということが可能になります。
メインループ以外の実行回数が少ない部分は、エミュレーション実行とすることで、コードサイズの増加を防ぐことができます。


長いため一部省略していますが、具体的には以下のようなコードになります。

class NES {
	protected int PC;
	private int CLK;
	private int A;
	private int T;
	private int X;
	private int Y;
	private int SP;
	private int C;
	private int I;
	private int D;
	private int B;
	private int R;
	private int V;
	protected final int[] wram;
	protected final int[] prgRom;
	private final PPU ppu;
	private final PAD pad;
	private final NESApu nesApu;
	
	private final void ExecuteEmulation()
	{
		while (clk > 0) {
			switch (LdMemB_PRG(pc++)) {
				6502エミュレーションコア
			}
		}
	}
	
	public final void Execute()
	{		
		while (CLK > 0) {
			int beforeClk = CLK;
			
			// デコンパイルしたコード列
			switch (pc) {
				case 0x8000: clk -= 2; I = SFLG.I;
				case 0x8001: clk -= 2; D = 0;
				case 0x8002: clk -= 2; a = 16;
				case 0x8004: clk -= 4; ppu.SetControlRegister1(a);
				case 0x8007: clk -= 2; X = 255;
				case 0x8009: clk -= 2; SP = X;
				case 0x800A: clk -= 4; t = a = ppu.GetStatusRegister();
				case 0x800D: clk -= 2; if ((t & 0x80) == 0) { clk--; pc = 0x800A; break; }
				case 0x800F: clk -= 4; t = a = ppu.GetStatusRegister();
				case 0x8012: clk -= 2; if ((t & 0x80) == 0) { clk--; pc = 0x800F; break; }
				case 0x8014: clk -= 2; Y = 254;
				case 0x8016: clk -= 2; X = 5;
				case 0x8018: clk -= 4; a = LdMemB(2007 + X);
				case 0x801B: clk -= 2; t = a - 10; c = (~t >> 8) & 1; t &= 0xFF;
				case 0x801D: clk -= 2; if (c != 0) { clk--; pc = 0x802B; break; }
				case 0x801F: clk -= 2; t = X = (X - 1) & 0xFF;
				case 0x8020: clk -= 2; if ((t & 0x80) == 0) { clk--; pc = 0x8018; break; }
				case 0x8022: clk -= 4; a = wram[2047 & 0x7FF];
				case 0x8025: clk -= 2; t = a - 165; c = (~t >> 8) & 1; t &= 0xFF;
				case 0x8027: clk -= 2; if (t > 0) { clk--; pc = 0x802B; break; }
				case 0x8029: clk -= 2; t = Y = 214;
				case 0x802B: clk -= 6; PushStackW(0x802B + 2); pc = 0x90CC; break;
				case 0x802E: clk -= 4; nesApu.writeReg(0x4011 , a);
				case 0x8031: clk -= 4; wram[1904 & 0x7FF] = a;
				case 0x8034: clk -= 2; a = 165;
				case 0x8036: clk -= 4; wram[2047 & 0x7FF] = a;
				case 0x8039: clk -= 4; wram[1959 & 0x7FF] = a;
				case 0x803C: clk -= 2; a = 15;
				case 0x803E: clk -= 4; nesApu.writeReg(0x4015 , a);
				case 0x8041: clk -= 2; t = a = 6;
				case 0x8043: clk -= 4; ppu.SetControlRegister2(a);
				case 0x8046: clk -= 6; PushStackW(0x8046 + 2); pc = 0x8220; break;
				case 0x8049: clk -= 6; PushStackW(0x8049 + 2); pc = 0x8E19; break;
				case 0x804C: clk -= 6; t = (wram[1908 & 0x7FF] + 1) & 0xFF; wram[1908 & 0x7FF] = t;
				case 0x804F: clk -= 4; a = wram[1912 & 0x7FF];
				case 0x8052: clk -= 2; t = a |= 128;
				case 0x8054: clk -= 6; PushStackW(0x8054 + 2); pc = 0x8EED; break;
				case 0x8057: clk -= 3; pc = 0x8057; break;
				case 0x805A: clk -= 6; a |= LdMemB(LdWramW((164 + X) & 0xFF));
				case 0x805C: clk -= 2; Y = (Y + 1) & 0xFF;
				case 0x805D: clk -= 4; t = X - wram[16 & 0x7FF]; c = (~t >> 8) & 1;
				case 0x8060: clk -= 6; t = a ^= LdMemB(LdWramW((65 + X) & 0xFF));
				case 0x8062: clk -= 3; pc = 0x3C34; break;
				
				/* 中略 */
				
				default: break translation_loop;
			}
			
			if (beforeClk == CLK) {
				ExecuteEmulation();
			}
		}
	}
}


また、Executeメソッド内のデコンパイルによって作られたコードの脇に、6502の逆アセンブル結果を
くっつけたものを載せておきます。
6502のコードがどのようにJavaに変換されたのか分かると思います。

	public final void Execute()
	{		
		while (CLK > 0) {
			int beforeClk = CLK;
			
			// デコンパイルしたコード列
			switch (pc) {
				case 0x8000: clk -= 2; I = SFLG.I;                                                    // 8000 : 78               sei                     ;
				case 0x8001: clk -= 2; D = 0;                                                         // 8001 : D8               cld                     ;
				case 0x8002: clk -= 2; a = 16;                                                        // 8002 : A9 10            lda     #$10            ;
				case 0x8004: clk -= 4; ppu.SetControlRegister1(a);                                    // 8004 : 8D 00 20         sta     $2000           ;PPU 制御Reg.#1
				case 0x8007: clk -= 2; X = 255;                                                       // 8007 : A2 FF            ldx     #$FF            ;
				case 0x8009: clk -= 2; SP = X;                                                        // 8009 : 9A               txs                     ;
				case 0x800A: clk -= 4; t = a = ppu.GetStatusRegister();                               // 800A : AD 02 20         lda     $2002           ;PPU Status Reg.
				case 0x800D: clk -= 2; if ((t & 0x80) == 0) { clk--; pc = 0x800A; break; }            // 800D : 10 FB            bpl     $800A           ;
				case 0x800F: clk -= 4; t = a = ppu.GetStatusRegister();                               // 800F : AD 02 20         lda     $2002           ;PPU Status Reg.
				case 0x8012: clk -= 2; if ((t & 0x80) == 0) { clk--; pc = 0x800F; break; }            // 8012 : 10 FB            bpl     $800F           ;
				case 0x8014: clk -= 2; Y = 254;                                                       // 8014 : A0 FE            ldy     #$FE            ;
				case 0x8016: clk -= 2; X = 5;                                                         // 8016 : A2 05            ldx     #$05            ;
				case 0x8018: clk -= 4; a = LdMemB(2007 + X);                                          // 8018 : BD D7 07         lda     $07D7,x         ;
				case 0x801B: clk -= 2; t = a - 10; c = (~t >> 8) & 1; t &= 0xFF;                      // 801B : C9 0A            cmp     #$0A            ;
				case 0x801D: clk -= 2; if (c != 0) { clk--; pc = 0x802B; break; }                     // 801D : B0 0C            bcs     $802B           ;
				case 0x801F: clk -= 2; t = X = (X - 1) & 0xFF;                                        // 801F : CA               dex                     ;
				case 0x8020: clk -= 2; if ((t & 0x80) == 0) { clk--; pc = 0x8018; break; }            // 8020 : 10 F6            bpl     $8018           ;
				case 0x8022: clk -= 4; a = wram[2047 & 0x7FF];                                        // 8022 : AD FF 07         lda     $07FF           ;
				case 0x8025: clk -= 2; t = a - 165; c = (~t >> 8) & 1; t &= 0xFF;                     // 8025 : C9 A5            cmp     #$A5            ;
				case 0x8027: clk -= 2; if (t > 0) { clk--; pc = 0x802B; break; }                      // 8027 : D0 02            bne     $802B           ;
				case 0x8029: clk -= 2; t = Y = 214;                                                   // 8029 : A0 D6            ldy     #$D6            ;
				case 0x802B: clk -= 6; PushStackW(0x802B + 2); pc = 0x90CC; break;                    // 802B : 20 CC 90         jsr     $90CC           ;
				case 0x802E: clk -= 4; nesApu.writeReg(0x4011 , a);                                   // 802E : 8D 11 40         sta     $4011           ;DPCM 音量Reg.
				case 0x8031: clk -= 4; wram[1904 & 0x7FF] = a;                                        // 8031 : 8D 70 07         sta     $0770           ;
				case 0x8034: clk -= 2; a = 165;                                                       // 8034 : A9 A5            lda     #$A5            ;
				case 0x8036: clk -= 4; wram[2047 & 0x7FF] = a;                                        // 8036 : 8D FF 07         sta     $07FF           ;
				case 0x8039: clk -= 4; wram[1959 & 0x7FF] = a;                                        // 8039 : 8D A7 07         sta     $07A7           ;
				case 0x803C: clk -= 2; a = 15;                                                        // 803C : A9 0F            lda     #$0F            ;
				case 0x803E: clk -= 4; nesApu.writeReg(0x4015 , a);                                   // 803E : 8D 15 40         sta     $4015           ;SoundChannel 制御Reg.
				case 0x8041: clk -= 2; t = a = 6;                                                     // 8041 : A9 06            lda     #$06            ;
				case 0x8043: clk -= 4; ppu.SetControlRegister2(a);                                    // 8043 : 8D 01 20         sta     $2001           ;PPU 制御Reg.#2
				case 0x8046: clk -= 6; PushStackW(0x8046 + 2); pc = 0x8220; break;                    // 8046 : 20 20 82         jsr     $8220           ;
				case 0x8049: clk -= 6; PushStackW(0x8049 + 2); pc = 0x8E19; break;                    // 8049 : 20 19 8E         jsr     $8E19           ;
				case 0x804C: clk -= 6; t = (wram[1908 & 0x7FF] + 1) & 0xFF; wram[1908 & 0x7FF] = t;   // 804C : EE 74 07         inc     $0774           ;
				case 0x804F: clk -= 4; a = wram[1912 & 0x7FF];                                        // 804F : AD 78 07         lda     $0778           ;
				case 0x8052: clk -= 2; t = a |= 128;                                                  // 8052 : 09 80            ora     #$80            ;
				case 0x8054: clk -= 6; PushStackW(0x8054 + 2); pc = 0x8EED; break;                    // 8054 : 20 ED 8E         jsr     $8EED           ;
				case 0x8057: clk -= 3; pc = 0x8057; break;                                            // 8057 : 4C 57 80         jmp     $8057           ;
				case 0x805A: clk -= 6; a |= LdMemB(LdWramW((164 + X) & 0xFF));                        // 805A : 01 A4            ora     ($A4,x)         ;
				case 0x805C: clk -= 2; Y = (Y + 1) & 0xFF;                                            // 805C : C8               iny                     ;
				case 0x805D: clk -= 4; t = X - wram[16 & 0x7FF]; c = (~t >> 8) & 1;                   // 805D : EC 10 00         cpx     $0010           ;
				case 0x8060: clk -= 6; t = a ^= LdMemB(LdWramW((65 + X) & 0xFF));                     // 8060 : 41 41            eor     ($41,x)         ;
				case 0x8062: clk -= 3; pc = 0x3C34; break;                                            // 8062 : 4C 34 3C         jmp     $3C34           ;
				
				/* 中略 */
				
				default: break translation_loop;
			}
			
			if (beforeClk == CLK) {
				ExecuteEmulation();
			}
		}
	}