WATaBoy: JIT-ing Game Boy Instructions to WebAssembly

WATaBoy: JIT-ing Game Boy Instructions to WebAssembly

WATaBoy는 JIT‑to‑Wasm 아키텍처를 구현한 개념 증명용 Game Boy 에뮬레이터입니다. 런타임에 WebAssembly 바이트코드를 생성하고, 이를 브라우저의 JS 엔진이 네이티브 머신 코드로 컴파일함으로써, WATaBoy는 네이티브 인터프리터보다 뛰어난 성능을 달성합니다.

Performance Results

JIT‑to‑Wasm 에뮬레이션은 CPU‑집약적인 작업에서 Wasm 기반 인터프리터와 네이티브 인터프리터 모두보다 우수한 성능을 보입니다. 포켓몬 블루를 에뮬레이션한 벤치마크에서, JIT‑to‑Wasm 접근 방식은 네이티브 인터프리터보다 약 1.2배, Wasm에서 실행되는 인터프리터보다 1.5배 빠른 것으로 나타났습니다.

Browser Engine Comparison

주요 브라우저 엔진마다 성능 차이가 있었으며, Safari가 이 구현에 대해 가장 높은 효율성을 보였습니다. JIT가 특정 엔진에 맞춰 튜닝되지 않았음에도 불구하고, Safari의 성능 우위는 iOS의 WebKit‑전용 특성이 JIT‑to‑Wasm 에뮬레이션의 성능을 본질적으로 제한하지 않음을 시사합니다.

Technical Implementation

WATaBoy는 iOS와 같이 Apple이 JIT 컴파일을 웹 브라우저(JavaScriptCore/WebKit) 내에서만 허용하는 플랫폼의 제한을 우회하기 위해 "JIT‑to‑Wasm" 접근 방식을 사용합니다. 네이티브 머신 코드를 직접 생성하는 대신, 에뮬레이터는 Wasm 바이트코드를 생성하고, 브라우저가 이를 네이티브 코드로 컴파일합니다.

Wasm Codegen and Linking in Rust

이 구현은 Rust로 작성되었으며 wasm-encoder 크레이트를 이용해 Wasm 명령을 출력합니다. Wasm은 하버드 아키텍처를 사용하기 때문에, 생성된 바이트코드는 직접 실행될 수 없습니다. 프로세스는 다음 네 단계 파이프라인을 따릅니다:

  1. Compile & Instantiate: 바이트코드를 브라우저의 JavaScript 임베더에 전달해 컴파일하고 인스턴스화합니다.
  2. Link: 생성된 함수를 메인 Wasm 모듈의 간접 함수 테이블에 추가합니다.
  3. Dispatch: call_indirect Wasm 명령을 사용해 함수를 실행합니다.

이를 가능하게 하기 위해 프로젝트는 Nightly Rust와 asm_experimental_arch 기능을 사용해 인라인 Wasm을 작성하고, JavaScript가 간접 함수 테이블에 접근하고 확장할 수 있도록 LLD 플래그(--export-table--growable-table)를 전달합니다.

Cycle-Accuracy and JIT Strategy

사이클 정확성을 유지하면서 JIT를 사용하기 위해 WATaBoy는 GameRoy에서 영감을 받은 다음 기술을 적용합니다:

  • Interrupt Prediction: 인터럽트가 발생할 시점을 예측해 JIT 블록이 종료되어야 하는 시점을 결정합니다.
  • Interpreter Fallback: JIT 블록이 인터럽트될 가능성이 있을 때 인터프리터로 되돌아갑니다.
  • Lazy Evaluation: 메모리‑맵드 I/O를 통해 접근되는 비‑CPU 구성 요소(MMIO 등)를 지연 평가합니다.

Limitations and Future Work

JIT‑to‑Wasm 접근 방식은 성공적이지만 몇 가지 제한 사항이 남아 있습니다:

  • Codegen Ergonomics: 현재 Wasm 바이트코드 생성은 맞춤형이며, DynASM이나 Cranelift 같은 도구에서 제공하는 견고한 툴링이 부족합니다.
  • Memory Constraints: Dolphin의 "fastmem" 매핑과 같은 일부 저수준 최적화는 잘못된 메모리 접근이 복구 불가능하기 때문에 Wasm에서는 구현할 수 없습니다.
  • PPU Bottlenecks: 아직 구현되지 않은 인터럽트 예측 때문에 픽셀 처리 유닛(PPU) 에뮬레이션에 많은 런타임이 소요됩니다.

Community Insights

개발자들 사이의 논의에 따르면, 네이티브 인터프리터의 오버헤드가 Wasm보다 훨씬 크기 때문에 성능 향상이 기대된다고 합니다. 한 기여자는 다음과 같이 언급했습니다:

"WASM 오버헤드는 약 20%, 인터프리터 오버헤드는 약 1000%입니다."

다른 개발자들은 런타임 데이터를 기반으로 핫 경로를 실시간으로 재컴파일하는 방식이 NES를 목표로 하는 정적 재컴파일 프로젝트에서도 입증된 전략이라고 지적했습니다.


SUMMARY: WATaBoy는 WebAssembly(Wasm)를 목표로 하는 Just‑In‑Time(JIT) 컴파일러가 Game Boy 에뮬레이션에서 네이티브 인터프리터보다 더 높은 성능을 낼 수 있음을 보여주며, iOS와 같이 제한된 플랫폼에서도 고성능 에뮬레이션을 구현할 수 있는 잠재적인 경로를 제시합니다.

TITLE: WATaBoy: JIT-ing Game Boy Instructions to WebAssembly

Sources