借り初めのひみつきち

仮ブログです。

WASMでPCエミュレータ作った。

WASM で PC エミュレータ作りました☆(ゝω・)vキャピ

f:id:neriring16:20190618000038p:plain

github.com

※ 現時点では自作のOSすら起動しません。

すでにいろんなエミュレータが存在しているので今更感あるかもしれないですが、このエミュレーターは WebAssembly を使っているのが大きな特徴です。

もともと Web ブラウザーで動作するPCエミュレーターを探していて、いくつかあることはあるのですが、どれも不満がありました。

筆者は数年前に Web で動作する PC エミュレーターを試作したことがありました。
サーバーサイドで既存のエミュレーターを動作させて WebSocket で入出力だけブラウザ側が担当する実装でした。
クライアントの処理が軽いので軽快に動作しましたが、動作するサーバーの問題があって公開前に開発中止になりました。

クライアント単体で完結できるものとして JavaScript を利用したものがよくありますが、パフォーマンス的に満足できるものはひとつもありませんでした。

WebAssembly の環境も整ってきたので自作するかーということになりました。

SharedArrayBuffer と Atomics

技術調査をしていて一点気になっていたことがありました。

一般に GUI 環境では GUI イベントを処理するためのスレッドがあります。
JavaScript の場合は GUI イベントとメインのスクリプトが同じスレッドで動作するので、重たいスクリプト処理が走っていると GUI イベントに素早く反応できずに操作性が悪くなります。かといって GUI イベントのために小刻みに動作を中断するとエミュレーターのパフォーマンスが非常に悪くなります。
こういう場合、重い計算処理は GUI スレッドとは別のスレッドで処理するのが普通です。

JavaScript では Worker という仕組みを使ってマルチスレッドを実現できます。
Worker スレッドで動作するスクリプトGUI スレッドとは別のスレッドで動作するため、 GUI スレッドは GUI イベントに、エミュレータスレッドはエミュレータ処理にそれぞれ専念することができます。

しかし一方で、エミュレーターは常に処理しているわけではありません。
全力で処理したい時もあるけど、ユーザーの操作待ちなどで休んでる時もあります。
こういう場合、 JavaScript では SharedArrayBuffer と Atomics を使って待ち合わせを行うことができます。
エミュレータースレッドで入力待ちが発生した場合は Atomics.wait を使ってスリープし、 GUI スレッドで入力イベントを受け取ったら Atomics.notify を使ってエミュレータスレッドを起こすことができます。

これで、全力で処理するときは全力で処理し、入力待ちで休んでるときはほとんど CPU を消費しないようになり、ぼくの要求するパフォーマンスを満たすことができるようになりました。

しかし、 SharedArrayBuffer はセキュリティ上の理由により Chrome 以外のほとんどのブラウザーでは無効になっているという悲しいニュースがありました。これを解決するには別の手段を探す必要があるかもしれません。

ModR/M とフラグ

実装上の問題としては、筆者は過去に 80 系 CPU のような単純なエミュレーターは自作したことあるのですが、 x86 系の本格的なエミュレーターを作るのは初めてでした。
8086 の特徴として多くの汎用命令で ModR/M と呼ばれるエンコーディングが使われています。
しかしこの ModR/M が曲者で、 8086 エミュレーターで一番面倒な場所だと思います。ここだけで1日くらい消費しました。

ModR/M デコーダーを実装して多くの命令が動作するようになりましたが、今度はフラグの動作にハマりました。
OF や CF が実機と結果が異なるのです。
これらのフラグは条件分岐でよく使われるフラグなので正しく実装しないとほとんどのプログラムが動作しません。
自作 VM なら比較命令と条件分岐命令を統合させてフラグの動きを考えないようにできますが、実際の CPU をエミュレーションするので真面目に実装する必要があります。

そんなこんなでとりあえず簡単なタイプライター的動作をする BIOS が動く程度にはなったので公開します:;(∩´﹏`∩);: