自作インタプリタを少しずつ進化させて、複数のCPUやOSをサポートできるようになりました。クラス設計で試行錯誤した過程を残しておきます。
UNIX V6 (PDP-11)
基底クラスを共有する形でPDP-11とUNIX V6のクラスを追加しました。この段階ではOSでCPUを挟むような形がうまく適合できています。基底クラスはUNIX共通の機能を実装しているためVMUnixと名前を変え、派生クラスは名前空間に分離した上ですべてVMに名前を統一しました。
MINIXはUNIX V7のシステムコール互換から出発したため、大半のシステムコールはUNIX V6とMINIX 2の間で共通しています。どちらも16bitでプロセスのメモリモデルも似ています。CPUの命令セットとシステムコールのABIを追加実装した形です。
インポート
以前作成したPDP-11のインタプリタからコードを移植しました。
既に作りこんでいたクラスの継承関係にうまくはめ込むには、単純なインポートではなくリファクタリングが必要でした。その際にバグが入り込んで手こずりました。詳細はブログに書きました。
リファクタリングの成果として、アドレッシングモードの実装は以前よりもシンプルなものとなり、命令解析のキャッシングにより動作速度も向上しました。
CPUの分離
今回インタプリタを開発する目的は、UNIX V6の8086への移植をサポートすることです。カーネルの移植に先立ってコンパイラの移植から着手しますが、出力結果が正しく動作するかを確認するには、何らかの方法でバイナリを動作させる必要があります。そのため立ち位置の近いMINIX 2のインタプリタから始めて、システムコールをV6にすり寄せることで仮想的な8086版V6環境の実現を狙いました。
継承によるモデルでは親クラスが固定されているため、UNIX V6が継承するCPUを交換することができません。CPUとOSの継承関係を逆にすると、今度はOSが交換できなくなってしまいます。単一継承の限界です。
CPUとOSの継承関係を分離して、相互参照(下図の赤線)する形にリファクタリングしました。
単一継承での結合に比べてシステムコールにオーバーヘッドがありますが、実行時間の大半はCPU命令に費やされているので、ほとんど影響はありません。
UNIX V6 (8086)
UNIX V6の実装のうち、CPUに依存する部分(システムコールのABI)を分離して、8086用を追加しました。これにより目的だった仮想的な8086版V6環境が完成しました。
感想
結果だけ見ると、最初からクラス設計を決めておけば良かったようにも思えます。しかし当初は手探りで完成形のイメージもなかったため、適切に設計できたか疑問です。リファクタリングを重ねながらイメージを固めていく方法を採らざるを得ませんでした。
最後のステップ(8086版V6)は拍子抜けするくらいあっさりできました。リファクタリングを重ねて依存関係を整理したのが効果を発揮しました。
オブジェクト指向で設計しており、関数型の概念は取り入れていません。OSバンドルのコンパイラがC++11をサポートしていない環境を考慮したためです。将来的にインタプリタの対応アーキテクチャを増やしていくことになれば、OSバンドルや動作速度にはある程度妥協して、他の言語に移行することも考えています。
UNIX V6の8086への移植はハッカソンとして開催します。
このインタプリタが役立つことを祈っています。