易しいアセンブリ
易しいアセンブリ
この記事では自作アプリ(CASL2エディタとCOMET2エミュレータ)を用いて,実際にアセンブリ言語の雰囲気を紹介します.
後半では自作アプリの実装周りの紹介をします.最後まで読みましょう.
CASL2とCOMET2について
CASL2とは,IPA[1]が試験用に作成した仮想のアセンブリ言語です.その仮想言語に対応する仮想コンピュータをCOMET2と言います.
COMET2は16ビットマシンで命令数も30程度と少ないです.実用には向いていませんがビット数・命令数ともに少ないため学習するのに向いています.
また,仕様[2]も8ページと少ないため,軽い気持ちで学習を始めても怪我をする心配がありません.
自作アプリ
CASL2のシミュレータと呼ばれるものは公式や私のような他者含め多くの人が出していますが,今回は私が作成したアプリを利用していきます.
ここでは「CASL2シミュレータ」ではなく「CASL2エディタとCOMET2エミュレータ」と呼んでいますが,特に深い意味はありません.この自作アプリはウェブアプリとなっており,ブラウザ上で完結しています.特徴はシンタックスハイライトをサポートしているエディタと,レジスタの値をリアルタイムに反映する表示部分です.
結果を表示するため,結果だけを表示する実行環境と比べて実行速度面では大きく劣りますが,内部で何を行っているのかが視覚的に分かるため,学習を目的としているのであれば大いに役立つと思います.
また限定的ではありますが,一部のシンタックスエラーにも対応しています.
自作アプリの制約事項
- プログラムはメモリアドレス100にロードされます.
- 現時点では,プログラムの終了条件をスタックポインタが0になることを条件にしています.
これは,スタックポインタが0xffff
から始まることと,RET
命令時にスタックポインタが繰り上がることを利用しています.
CASL2
CASL2の基本文法は次のようになります([...]
は省略可能).
[<ラベル>]<空白><オペコード><空白>[<オペランド>]
これらの命令(オペコード)は1語[3],もしくは2語のCASL2の機械語に1対1に対応します(CASL2専用の命令やマクロと呼ばれるものなど,機械語の命令に1対1で対応しない命令も存在します).
幾つかのサンプルコードを用意したので実際に遊んでみましょう.ビットが変化するのを眺めるのは楽しいですよ. 全ての命令を知りたい場合は仕様を読みましょう.
c = a + b
それぞれのリンクからコードの実行ができます
GR1
をa
, GR2
をb
, GR0
をc
に見立てています.CASL2には c = a + b
を1行で表現できる命令がないため,c = a
とc += b
の2つに分けて実行しています.
ループ
簡単なループ処理であっても,アセンブリ言語だと少し面倒になります.
0
初期化は LAD GR0,0
などでも良かったのですが,ここでは XOR GR0,GR0
を利用しています.「どっちが良いの?」という問いには答え難いのですが,このアプリにおいてはXOR r1,r2
は1語命令,LAD r,adr,x
は2語命令なのでここではXORの方がメモリ効率が良いと言えます.CPL GR2,GR1
とJMI END
でループの終了条件を表しており,GR2がGR1より小さくなったら,ENDへJUMPしています.
再帰
先程のループを再帰にしてみました.PUSH
とPOP
を書かずにGR0
にどんどん加算して行っても良いのですが,動かした時の挙動が面白いのでこのように書いています.このプログラムでは1から100まで再帰的に数え上げた後,100から順に加算していきます.
また,CALL
命令はスタックにポインタを積んでいくため,先程のループと比べてSR
が絶えず変化していくところも面白いポイントです(CALL
の前にPUSH
命令も実行しているため,SR
は単にCALL
を読んだだけの場合の倍積まれています).
スタックの中身のイメージ |
---|
... |
ポインタ |
値 |
ポインタ |
値 |
... |
print(str)
文字列の出力は比較的簡単です.OUT命令(正確にはマクロ[4])のオペランドにメモリアドレスと文字数を受け取り,その文字数分だけ表示します.
実行すると,GR1
とGR2
に値が入っていると思います.これはマクロであるOUT
命令が展開されて別の命令になっているためです(COMET2にはOUTという命令は実際にはありません).OUT STR,13
はコンパイル時に次のように展開されます.
LAD GR1,STR
LAD GR2,13
SVC 2
print(n)
文字列の出力は簡単ですが,数値の出力は簡単にはいきません.数値の出力を行うには,数値を数字に変換する必要があるためです.
ここで問題が発生します.CASL2には乗算と除算に該当する命令がありません.なので,それらの命令を作成する必要があります.必要な関数は商と剰余を求める関数です.
CASL2 and Dart code
dartの実装をほぼそのまま移しました.1-100の総和にすると,この実装ではそれなりに時間がかかります.なのでここでは1-10の総和を求め,出力しています.パフォーマンスどうなの?という実装になってはいますが,ここでは見た目の面白さを優先しています.意欲に満ち溢れているあなたはパフォーマンス改善に取り組みましょう.スタックが積まれていくのは面白いです.シンプルなコードであってもアセンブリで書くとそれなりの分量になります.
逆ポーランド記法電卓
CASL2 and Dart code
逆ポーランド記法というものがあります.サンプルコードとして有名なので取り上げます.(Dartのコードを書いてからCASL2のコードを書いているのですが,それでもデバッグ機能の無さをを痛感しました.)
こちらは少し実行に時間がかかりますが,実行すると次のような結果を得られます(このサイズのコードは良いデバッグになりました).
自作アプリの実装周り
ここまで遊んできたアプリはCASL2・COMET2のコア実装に関する部分はDartのライブラリとして実装しており,そのライブラリを利用してFlutterで画面実装をしたものになります.
このライブラリは次のような方法で利用することができます.
import 'package:tiamat/tiamat.dart';
const src = '''
MAIN START
LAD GR1,1
RET
END
''';
void main() async {
final casl2 = Casl2.fromString(src);
final result = casl2.compile();
if (result.hasError) {
print(result.errors);
return;
}
final comet2 = Comet2();
final resource = await comet2.loadAndRun(result);
print(resource);
// Output:
// GR0: 0, GR1: 1, GR2: 0, GR3: 0, GR4: 0, GR5: 0, GR6: 0, GR7: 0, SP: 0, PR: 0, FR: 0
}
ね,簡単でしょ?
なぜライブラリなのか
「自作OS入門[5]」という本を完走し,「自作x86エミュレータ本[6]」をRustでやり終え,「そろそろ本の内容を写すだけじゃなくて何か作りたい」と2020年の末に思い至り作り始めたのがこのライブラリになります.「仕様簡単だし比較的簡単にエミュレータ作れるのでは??」という安易な発想からCASL2に目をつけました.
この時の要件は次のようなものです.
- CASL2の入力に対して,実行結果を得られること.
- 実行環境として最低限WebとCLIを満たすこと.
WebとCLIを実行環境に選んだ時点で,自分の中では実装言語がTypeScriptかDartに絞られました.Dartを採用した大きな理由としてFlutterの存在があります.これは元々,iOSやAndroidのクロスプラットフォームアプリ開発用のフレームワークですが,将来的にDesktopアプリ(Linuxを含む)やWebブラウザもサポートするという話が出ていました(この時点でFlutter2は世に出ていません).そしてDart本家が「Sound Null Safety!」と高らかにNULL安全を言語に導入すると発表していたのも「Dartのベータ版試してみたい」という気持ちを後押ししたため,結果的にTypeScriptではなくDartを採用しています.
余談ですが,JSへのトランスパイル路線を捨ててWASMを利用するのであれば言語の選択肢はさらに広がります.
今回の自作アプリはFlutterを用いて実装していますが,DartをWebで実行する(JSへトランスパイルする)のにFlutterは必須ではないため,ライブラリのサンプルアプリはDartのみで実装されています.
実行環境としてWebとCLIの両方をサポートする以上,ブラウザとCLIのIOに関わる部分(環境依存)を1つのコードで両方実装するというのは現実的ではありません.良い設計というものはそういった依存部分からコアロジックを切り離して行うものです.
であればCASL2のコンパイラやCOMET2のエミュレータを担うコアロジックをライブラリとして公開し,ライブラリを利用するアプリを作るのが良いのではないかということで,ライブラリとして実装しています.
そして公開されたこのライブラリは特定環境の依存を持たないため,Dart|Flutter
がサポートする全ての環境で利用することができます.
ライブラリの機能
自分がCASL2シミュレータと呼んでいない理由として,このライブラリがCASL2をパースして機械語に変換するコンパイラを担う部分と,機械語をロードしてCOMET2の動作を真似るエミュレータを担う部分の2つから構成されているからです.
なので,安易にこのライブラリをCASL2シミュレータと呼ぶのは違う気がしています.
ライブラリの初版を作成したのは1月ごろです.機械語の意味を解釈して実行するというのは,既に「自作エミュレータ本」をやっていた為問題にはなりませんでしたが,CASL2を機械語に翻訳するのはそれとは訳が違うということに作り始めてから気が付きました.この時参考にしたのは「WEB+DB PRESS Vol.120 自作OSx自作ブラウザで学ぶWebページが表示されるまで[7]」という雑誌の特集です.特集のHTMLをパースする実装を参考に初版のライブラリは完成しました.
初版のライブラリがやっていることを図式化すると次のようになります.
初版のライブラリでは,構文エラーを表示するための仕組みなどはありませんでしたが,当初の要件を満たすことができたため,ひとまず開発は終了しました.
ライブラリの育て方
6月ごろに「Goで作るインタプリタ[8]」という本に取り組んだことによりライブラリの反省点に対する修正目処が立ったため,ライブラリの再開発を始めました.
要件です.
- 可能なら構文エラーを表示すること.
- シンタックスハイライトを実装する手段の提供
- 途中経過を表示する手段の提供
この用件のもと出来上がったライブラリはこのようになっています.
図にすると分岐が増えた以外はAst変換が加わっただけですが,Token変換に関しては全て書き直しています.初版では1文字1文字が全てトークンでしたが,それを複数文字単位でトークン化しています.また,経過を取得するにあたり,JSの非同期とは(Dartの非同期とは)といった部分を忘れていたので調べ直したりもしていました.
これらの機能が加わったことで自作アプリはこのような実装になっています.
ライブラリを試す方法
ライブラリを作成する場合,実際にライブラリが正しく機能しているのかを試したくなりますが,ライブラリには関数void main()
を含んでいないため,実際に動かすためのコードが必要になります.しかしライブラリを作っているのにその都度それを動かすアプリを実装すると言うのは不毛です.そこで活躍したのがテストコードになります.テストコードを動作確認として書くだけでわざわざアプリを作らなくても良いですし,そのテストコードが動作を保証するものにもなります.私自身純粋なライブラリを実装する経験は初めてだったので,これは個人的に大きな収穫でした.特に命令の実装などは関数のIOと挙動が明確に分かるため,TDD[9]の実践が出来たのも大きいです.
最後に
最後までお付き合いいただきありがとうございました.
記事やアプリの至らない点に気が付いてしまったあなたはフィードバックをしましょう.
至らない点以外のフィードバックもお待ちしております.
ライブラリの今後
このライブラリには記事執筆中に見つけてしまったバグや未実装の機能が幾つかあります.逆アセンブリの実装で苦しんだので実装されたら良いなという言語拡張も幾つか思いつきました.利用者が増えたり,フィードバック多くもらえて気分がよくなったら実装するかもしれません.それらを列挙してこの記事の締めとします.
未実装の機能
-
=
を利用したリテラル記法(e.g.='A'
) - 全てのシンタックスエラーパターン
発覚したバグ
- マクロのラベルが効いていない
- いつの間にか消えた
NOP
入れたい言語拡張
-
JUMP
先のラベルをサブルーチン内に限定 - サブルーチン内のラベルスコープをサブルーチン内に限定
- 独自のマクロ定義
-
情報処理推進機構 ↩︎
-
https://www.jitec.ipa.go.jp/1_13download/shiken_yougo_ver4_3.pdf ↩︎
-
1語=16bits ↩︎
-
コンパイル時に複数の命令に展開されるもの. ↩︎
-
30日でできる! OS自作入門 (ISBN 978-4-8399-1984-9)
ゼロからのOS自作入門 (ISBN 978-4-8399-7586-9) ↩︎ -
自作エミュレータで学ぶx86アーキテクチャ (ISBN 978-4-8399-5474-1) ↩︎
-
WEB+DB PRESS Vol.120 (ISBN 978-4-297-11811-2) ↩︎
-
Go言語でつくるインタプリタ (ISBN 978-4-87311-822-2) ↩︎
-
テスト駆動開発 ↩︎
Discussion