Proyek rekompilasi statis (static recompilation) seperti porting engine Nintendo 64 pada GoldenEye-Recomp menerjemahkan machine code MIPS langsung menjadi source code C yang ekuivalen. Validasi manual berbasis observasi visual tidak efektif untuk mendeteksi silent regression pada logika low-level seperti rounding error floating-point, desinkronisasi flag status, atau off-by-one pointer arithmetic. Pendekatan yang andal menuntut verifikasi fungsional terotomatisasi berbasis perbandingan bit-level snapshot memory antara binary rujukan (melalui emulator/trace asli) dan binary hasil rekompilasi.

Tantangan Determinisme pada Binary Recompilation

Agar snapshot memory dapat dibandingkan secara deterministik antar-frame, runtime engine harus diisolasi dari efek non-deterministik sistem operasi host:

  • Input Handling: Seluruh pemanggilan polling controller harus digantikan oleh array input terrekam (macro replay).
  • Timers & Clocks: Clock real-time host (misalnya QueryPerformanceCounter atau clock_gettime) harus di-stub dengan fixed-delta clock berbasis tick CPU target.
  • Pointer Alignment & Padding: Compiler host (x86_64) memiliki konvensi alignment dan padding struct yang berbeda dibanding arsitektur target 32-bit big-endian. Recompiled code wajib menggunakan direct-memory mapping berbasis base address flat (virtual RAM offset) atau struct dengan atribut __attribute__((packed)) bila merepresentasikan I/O registers langsung.
  • Floating Point Inconsistency: Perbedaan penanganan FPU denormalized numbers dan precision rounding mode antara target dan host harus dinormalisasi, misalnya via software-emulated FPU routines atau setting control word FPU host.

Strategi Verifikasi: Snapshot Memory dan Hashing Frame

Testing pipeline dijalankan secara lock-step:

  1. Eksekusi frame game pada binary asli/emulator rujukan dengan sequence controller input terdefinisi. Simpan memory dump segmen BSS, Heap, dan Register File ke storage.
  2. Eksekusi input yang persis sama pada C recompilation binary.
  3. Pada boundary frame (misalnya tepat setelah interrupt vertical blanking/VBlank selesai), ambil snapshot segment data dan bandingkan nilainya.
  4. Gunakan fast-hashing algorithm (misalnya xxHash64 atau CRC32) untuk membandingkan blok memori besar tiap tick, kemudian jalankan byte-by-byte diff hanya ketika checksum mismatch terdeteksi.

Implementasi Test Harness C Minimal

Contoh test harness berikut mendefinisikan state register CPU target, virtual RAM terisolasi, serta fungsi pembanding snapshot untuk mendeteksi titik awal terjadinya regresi state.

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#define VRAM_SIZE 0x800000 /* 8 MB emulated RDRAM */

typedef struct {
    uint32_t gpr[32];
    uint32_t pc;
    uint32_t hi;
    uint32_t lo;
} TargetCpuState;

typedef struct {
    TargetCpuState cpu;
    uint8_t ram[VRAM_SIZE];
} ExecutionContext;

/* Context binary rujukan vs recompilation target */
static ExecutionContext g_reference_ctx;
static ExecutionContext g_recomp_ctx;

/* Ponytail: implementasi hash 32-bit sederhana FNV-1a. Ganti ke xxHash jika buffer >100MB */
static uint32_t compute_hash(const void *data, size_t size) {
    const uint8_t *bytes = (const uint8_t *)data;
    uint32_t hash = 2166136261u;
    for (size_t i = 0; i < size; ++i) {
        hash ^= bytes[i];
        hash *= 16777619u;
    }
    return hash;
}

bool verify_frame_state(uint32_t frame_index) {
    /* 1. Bandingkan CPU Registers */
    if (memcmp(&g_reference_ctx.cpu, &g_recomp_ctx.cpu, sizeof(TargetCpuState)) != 0) {
        fprintf(stderr, "[FAIL] Frame %u: CPU State mismatch!\n", frame_index);
        for (int i = 0; i < 32; ++i) {
            if (g_reference_ctx.cpu.gpr[i] != g_recomp_ctx.cpu.gpr[i]) {
                fprintf(stderr, "  r%02d: Expected 0x%08X, Got 0x%08X\n",
                        i, g_reference_ctx.cpu.gpr[i], g_recomp_ctx.cpu.gpr[i]);
            }
        }
        if (g_reference_ctx.cpu.pc != g_recomp_ctx.cpu.pc) {
            fprintf(stderr, "  PC : Expected 0x%08X, Got 0x%08X\n",
                    g_reference_ctx.cpu.pc, g_recomp_ctx.cpu.pc);
        }
        return false;
    }

    /* 2. Bandingkan RAM checksum */
    uint32_t ref_hash = compute_hash(g_reference_ctx.ram, VRAM_SIZE);
    uint32_t rec_hash = compute_hash(g_recomp_ctx.ram, VRAM_SIZE);

    if (ref_hash != rec_hash) {
        fprintf(stderr, "[FAIL] Frame %u: RAM hash mismatch! Ref: 0x%08X, Recomp: 0x%08X\n", 
                frame_index, ref_hash, rec_hash);
        
        /* Byte-by-byte scan untuk identifikasi offset memory corruption */
        for (size_t offset = 0; offset < VRAM_SIZE; ++offset) {
            if (g_reference_ctx.ram[offset] != g_recomp_ctx.ram[offset]) {
                fprintf(stderr, "  First delta at 0x%08zX: Expected 0x%02X, Got 0x%02X\n",
                        offset, g_reference_ctx.ram[offset], g_recomp_ctx.ram[offset]);
                break;
            }
        }
        return false;
    }

    printf("[PASS] Frame %u determinism verified. Hash: 0x%08X\n", frame_index, ref_hash);
    return true;
}

/* Self-check runnable entry */
int main(void) {
    /* Setup state awal sinkron */
    memset(&g_reference_ctx, 0, sizeof(ExecutionContext));
    memset(&g_recomp_ctx, 0, sizeof(ExecutionContext));
    g_reference_ctx.cpu.pc = 0x80000400;
    g_recomp_ctx.cpu.pc = 0x80000400;

    /* Simulasi eksekusi frame 1: Identik */
    verify_frame_state(1);

    /* Simulasi bug pada recompilation frame 2: Silent off-by-one pointer write */
    g_recomp_ctx.ram[0x12F0] = 0x5A;
    verify_frame_state(2);

    return 0;
}

Debugging Mismatch dan Isolasi Regresi

Ketika deteksi memory mismatch terpicu, lakukan isolasi menggunakan teknik berikut:

  • Symbol Map Translation: Petakan memory address hasil diff ke tabel symbol hasil kompilasi awal target (melalui file .map). Jika deviasi terjadi di address 0x80054320, temukan variabel atau struct array yang menempati slot tersebut untuk melokalisir modul penyebab.
  • Instruction Execution Trace: Jika state register CPU berbeda, gunakan checkpoint log instruksi per basic block. Binary recompilation dapat diinjeksi macro logging trace pada setiap block entry untuk membandingkan urutan Program Counter terhadap trace emulator rujukan.
  • Dynamic Memory Unmasking: Pisahkan data scratchpad/framebuffer video dari hashing loop. Framebuffer GPU sering kali memiliki toleransi rendering backend yang berbeda dan dapat mengaburkan verifikasi logika engine murni (physics, gameplay state, AI logic).