(Qiitaに書いた記事のクロス投稿です。元の記事公開日は 2019-05-04 です。)
きれいにまとまってないですが、箇条書き+α程度で雑にメモ。
TechRacho さんの週刊Railsウォッチ(20210209後編)で紹介されました(ありがとうございます 🙏)。こちらもあわせて読んでいただけるとよいかと。
https://github.com/sonota88/vm2gol-v1
完成直後(ライフゲームが最初に動いた時点)の状態も見られるようにタグを付けています。
https://github.com/sonota88/vm2gol-v1/tree/20180518
汚いしひどいコードなので正直恥ずかしいんですが、この状態も生々しくてコンテンツとしては面白いかもしれません。やけくそか。ヒマな人向け。
※ その後作り直した v2 もあります。その後の改良も加わったものが見たい、パーサまで揃ったものが見たいという方はこちらを見てください。
※ (2021-02-21 追記) v2 にいくつか機能を足してセルフホストできるようになりました。
どういうステップを踏んで何を実装していったか、具体的な話はこちらに書きました。あまり省略せずにその都度 diff を貼っていくスタイルです。
https://memo88.hatenablog.com/entry/2019/05/04/234516
サードパーティなライブラリへの依存はなく、Ruby(と標準ライブラリ)だけあれば動きます。
# コード生成
ruby vgcg.rb gol.vgt.json > gol.vga.yaml
# アセンブル
ruby vgasm.rb gol.vga.yaml > gol.vge.yaml
# VMで実行
ruby vgvm.rb gol.vge.yaml
これらの処理をまとめて行うために run.sh
というシェルスクリプトを使って動かしていました。
./run.sh gol.vgt.json
a
, b
, c
, d
, pc
, sp
, bp
, zf
, of
c
, d
, of
は途中で不要になったこういう YAML ファイル。バイナリではなくテキストですが、機械語のつもり。
---
- call
- 1029
- exit
- label
- vram_set
- push
- bp
- cp
- sp
- bp
- sub_sp
- 1
- set_reg_a
- "[bp+4]"
- set_reg_b
- "[bp+2]"
- mult_ab
- cp
- reg_a
...
YAML.load_file
したものをそのままメインメモリ領域にズボッと入れる。 富豪的。アセンブリコードも YAML。
- call main
- exit
- label to_vi
- push bp
- cp sp bp
- sub sp 1
- sub sp 1
- sub sp 1
- set_reg_d [bp+4]
- set_reg_a [bp+2]
- cp reg_d reg_b
- mult_ab
- cp reg_a [bp-3]
- set_reg_d [bp-3]
- set_reg_a [bp+3]
- cp reg_d reg_b
- add_ab_v2
- cp reg_a [bp-1]
...
パースをサボるために YAML にしたものの、インデントなどで構造を把握しやすくした方が良かったかなと思います。こんな感じで:
call main
exit
label vram_set
push bp
cp sp bp
sub_sp 1
...
この程度なら行ごとに line.strip.split(" ")
で済みそう(v2 ではそうしています)。
コード生成器は 600行弱。
コード生成器に与える高水準言語相当のコードは、フォーマットとしてはJSON(ただし、 //
でのコメントあり)。
["stmts"
// バッファ配列用
,["func"
,"to_vi"
,["w", "x", "y", "offset"]
,[
["var", "vi"] // vram index
,["var", "vi_buf"]
...
,["func"
// 関数名
,"main"
// 引数
,[]
// 本体
,[
["_debug", "start ----"]
,["var", "w"]
,["set", "w", 5]
,["var", "h"]
,["set", "h", 5]
// 配列の初期化 5x5, グライダー / x, y, val
,["set", "vram[1]", 1]
,["set", "vram[7]", 1]
,["set", "vram[10]", 1]
,["set", "vram[11]", 1]
,["set", "vram[12]", 1]
// メインループ
,["var", "cnt"]
,["while", ["eq", 1, 1], [
["set", "cnt", ["+", "cnt", 1]]
// gen_next_step
,["call", "gen_next_step_loop", "w", "h"]
// バッファの内容で置換
,["call", "replace_with_buf_v2"]
]]
]
]
]
JSON.parse
すれば構文木がゲットできる。ずるい。---- memory (vram) ----
..... .....
@.@.. ..@..
.@@.. @.@..
.@... .@@..
..... .....
不可欠でない部分はさくっと捨てる
VM〜コード生成部分を作る
汚くていい
作りつつその都度ブログ書いていくのもライブ感あって良さそうとも考えたけど、これもスピードが落ちそうなのでボツ。外部公開のことは考えないことに。
達人プログラマーでいうところの曳光弾。あれをやる。
少し例えが暴力的でしたが、この手は新規プロジェクト、特に今まで構築されたことがないようなものを実現する場合に適用することができます。あなたは射撃手のように暗闇の目標を狙わなければならないのです。元になるシステムが存在していない場合、ユーザーの要求は曖昧なものとなります。さらに、不慣れなアルゴリズム、開発技法、言語、ライブラリを使って、未知の世界に直面していくことになるのです。また、プロジェクトの完了まで長い期間がかかるため、作業環境も変化していく可能性が高いでしょう。
(達人プログラマー ピアソン・エデュケーション版 p48)
「正しく」やる時間がない人間は、重要な箇所だけに集中して枝葉を無視する。その結果、いくつかの細かい点は次のバージョンに回そうと計画する。ここで注意する点は、次のバージョンなど永久に作られないかもしれないということだ。しかし、「足りない部分は後でなんとかすればいい」と思い込むことで、脇道に迷い込むことを避けられる。それはまた、初期バージョンの細かな欠点についてのよい言い訳にもなる。
(UNIXという考え方 p34)
頓挫するから。
平日昼間は仕事だし、夜・朝もそんなに時間使えないし、土日もいろいろやることある。
これまでの経験でも、長期化するとなんか仕事が忙しくなってきて中断してそれっきりとか、他におもしろそう(で簡単そうなもの)なものを見つけてそっちをやり始めて逃避したりで結局頓挫するパターンが多くて身に染みているから。絶対頓挫する。100%頓挫する。自信を持って断言できる。
で、いったんオレオレの適当な方法で動くものを作れば、その後で「正しいやり方」の解説を見聞きしたときに「あーなるほど、似たようなことやってたわ~」とか「こうすれば良かったのかーっ」ってなるはず。それでよい。それは2週間を終えた後でよい。
とにかく頓挫だけはしたくなかった。頓挫しないことが最も重要な意思決定方針。
x = (2 * (3 + 4))
のように入れ子になっている式を
,["var", "x"] // 変数の宣言
,["set", "x", ["*", 2, ["+", 3, 4]]]
みたいに書きたくてあれこれ検討したものの、まじめに取り組むと何日か溶けそうな気がしたため
,["var", "temp"]
,["set", "temp", ["+", 3, 4]]
,["var", "x"]
,["set", "x", ["*", 2, "temp"]]
のように一時変数を使う形にして回避しました。うーん、これは悔しい。
CPU のことをよく知らなかったので 2016年末~2017年頭頃に何冊か流し読みして、半加算器動かして足し算できる、みたいなのは書いてみた(この頃はアセンブラ、コード生成器まで作る気はなかった)。
※なので、2週間でまったくのゼロから始めたわけではなくて、構想期間が1,2年くらいあり、その間に本やネットであれこれ読んだりいろいろ妄想したりしてました。その上で、考えても埒が明かないのでやっぱり実際に書いてみよう! となった、という流れです。
という感じ。つまり、半加算器~構文木の間で何が起こっているのかが分かっていなかった。
ライフゲームならテトリスより簡単そう(ユーザからの入力もない)だし、簡単な割には面白い動きが生成されるので自己満足度高そう。「ライフゲームが動いたら嬉しいだろうなぁ…」と、完成したときの満足感をモワモワ~と妄想しつつ……
Rebuild: 153: Connecting The Dots (rui314)
40分あたりから 8cc の話。インクリメンタルに作ると良いという話。これを聞いたとき、やっぱりそうですよねえ、そうだろうなあと思った記憶があります。明らかに当企画のきっかけの一つです。
その後 Turing Complete FM が始まり、なんか楽しそうなのでやってみたい! という気分が盛り上がりました。
時間とやる気が揃ったら続きをやりたい。下記は「今回やってないこと」「今回捨てた・削ったもの」のリストでもあります。
[bp+1]
とかを VM が直接(正規表現で…)解釈しているので、もうちょっと機械語っぽくざっくり書くとこんな感じですが、実際は行きつ戻りつしていて、終盤でもライフゲーム書きながらVM部分を修正したりしてました。
こちらで製作過程を書いていますので詳しくはそっちを見ていただければと思います。
ひとりでCPUとエミュレータとコンパイラを作る Advent Calendar 2017 - Qiita
くじけちゃいけない! マシン語入門 平塚憲晴 : 平塚憲晴 : Free Download, Borrow, and Streaming : Internet Archive
※流し読み含む
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント