WebAssembly(WASM)版N80のビルド環境の備忘録
N80をブラウザで動くようにしたもの。
元々、N80はC言語で書かれているので、これをWASM向けにコンパイル。画面回りはWeb GPU、サウンドはWeb Audioに差し替えて、javascriptでWorkerを使って実装(ソース:n80wasm.zip)。
js側からWASMのN80(Z80)をステップ実行させる形で動作する。WASM側では画面の描画データ(テクスチャー用)、音声データを作成して共有メモリを通じてjs側に渡し、Web GPUで描画、Web Audioで音声再生を行う。逆にキー入力や各種バイナリイメージデータはjsで受けてWASMに渡す。Web GPUのframeに合わせてCPUの速度を調整している。WASMを読み込むjsやWeb AudioはWokerで動作する。
Windows11上でVSCodeを使って開発(Linux上でも同様の設定でビルドは出来と思われるが試してはいない)。WASMなので最終的にはhttpsでホスティングする必要がある。但し、開発中はpythonのhttp.serverとchromeの組み合わせで実行し確認できる。これらはVSCodeのタスクやデバッガーを使って行える(tasks.jsonやlaunch.json参照)。
コンパイラはemsdkのemccを使い、cmake+ninjaでビルドする。また、VSCodeのターミナルにemsdkのPATH等の環境変数を設定したり、C++拡張機能の為のコンパイラ設定ファイルも.vscodeに含まれている(settings.jsonやc_cpp_properties.json参照)。
C言語で書かれたN80本体ソースのビルドが必要。n80wasm/wasm内でcmakeとninjaでビルドを行う。実際はVSCodeのtasks.jsonに"build-wasm"が用意されているので、これを実行すればよい。cmakeは後述のCMakeLists.txtに記載された指示に従ってビルドの計画を立て、ninjaが実行する。ビルドが完了すると"app.wasm"と"app.js"が生成される。
また、デバッグ用に"app.wasm.map"も出力されるので、ソースレベルでのデバッグも可能(但し、chromeのDevToolsでもソースにブレークポイントは置けるが、C言語の変数レベルで値をウォッチするのは難しい。基本、WASMのVMレベルのスタック変数しか見えなかった)。
ビルドに成功したら、実際に動かしてみる。WASMはローカルファイル(*.html)からは直接動かせないので仮のサーバーを立てる必要がある。また、セキュリティー的にも厳しいのでchromeのオプションでこれらを無効にする。
VSCodeのタスクに"Run Python Server"を用意してあるので、それを実行してlocalhostでサーバーを立ち上げてから、launch.jsonの"Debug Chrome"をデバッガから実行するとブラウザが開いてN80wasmが実行される(はず)。又、使い終わったローカルサーバーは^Cで止める。
まずは、開発に必要なツール類をインストールする(例として"D:\Develop"に導入):
※開発ツールやソースの場所へのパス指定は適宜読み替えて実行環境に合わせて設定し直す必要あり。
D:\develop> git clone https://github.com/emscripten-core/emsdk.git
D:\develop> cd emsdk
D:\develop\emsdk> emsdk install latest
D:\develop\emsdk> emsdk activate latest
※開発時はlatest=5.0.4だったが、後に6.0.7でもビルドはできた。
https://cmake.org/download/
https://github.com/ninja-build/ninja/releases
ビルド用のbatファイルを用意する(n80wasm/wasm/build_wasm.bat)
@echo off
cd /d D:\source\n80wasm\wasm
set EMSDK_PY=
:: 手動で入れた Pythonがある場合は無効化する(PATHから外す)
set PATH=%PATH:C:\Python314\Scripts\;=%
set PATH=%PATH:C:\Python314\;=%
set PATH=%PATH:C:\Python310\Scripts\;=%
set PATH=%PATH:C:\Python310\;=%
set PATH=%PATH:C:\Python38\Scripts\;=%
set PATH=%PATH:C:\Python38\;=%
set PATH=%PATH:C:\Users\user\AppData\Roaming\Python\Python38\Scripts;=%
:: cmakeとninjaのPATHを追加
set PATH=D:\develop\cmake-3.31.11-windows-x86_64\bin;D:\develop\ninja;%PATH%
:: emsdkを使う為の環境設定batを実行する
call D:\develop\emsdk\emsdk_env.bat
rem --- CMake configure ---
call emcmake cmake ..
rem --- CMake build ---
cmake --build .
cmake用の設定ファイルを用意する(n80wasm/CMakeLists.txt)
cmake_minimum_required(VERSION 3.20)
project(MyWasmProject)
# 出力形式(ESM + MODULARIZE)
set(CMAKE_EXECUTABLE_SUFFIX ".js")
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/*.c"
"${CMAKE_CURRENT_SOURCE_DIR}/*.h"
)
add_executable(app ${SRC_FILES})
# Emscripten のオプション
set_target_properties(app PROPERTIES
LINK_FLAGS "-O3 -s WASM=1 -s MODULARIZE=1 -s EXPORT_ES6=1"
)
target_compile_options(app PRIVATE
"-O3"
"-g"
"-gsource-map"
"-msimd128"
)
target_link_options(app PRIVATE
#"-sVERBOSE=1"
#"--no-progress"
"-sASSERTIONS"
"-sINITIAL_MEMORY=16777216"
"-sALLOW_MEMORY_GROWTH=0"
"-sSHARED_MEMORY=1"
"-sIMPORTED_MEMORY=1"
"-sERROR_ON_UNDEFINED_SYMBOLS=0"
"-sUSE_PTHREADS=1"
"-sEXPORTED_FUNCTIONS=['_malloc','_free']"
"-sEXPORTED_RUNTIME_METHODS=['cwrap','ccall','UTF8ToString','HEAPU8', 'wasmMemory' ]"
"-gsource-map"
"--source-map-base=./"
)
emccで特にエラーも出ずにコンパイルも通り、あっけなくブラウザで動いてしまった(まぁ、チャッピーに散々お世話になったんだけど)。WebGPUを使っているので、古いPCやブラウザ、iPhone(XR)では動かなかったが、iPad ProやiPhone17eでは動作した。
単なる好奇心による実験的なものであったが、Webで動いちゃうかぁ、、、改めて驚いている。