- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
これまでは、 その都度の目標に向けて適当に作り、バグや not yet impl で例外が出たら その都度潰していく、という進め方でやってきました。
そういう方針なので、それはそれでいいのですが、 同じようなことの繰り返しになってちょっと煩雑&退屈なので、 特に瑣末なものは先に潰してから本題に入ろうと思います。 なるべくライフゲームに専念できるように。
(これはブログ記事化する都合上先回りしているだけで、 そういう都合がなければ先回りしなくていいと思います。 v1 のときは当然やってませんでした)
というわけで、まずは codegen_set()
でローカル変数が解決できていなかった箇所を修正。
--- a/vgcg.rb +++ b/vgcg.rb @@ -266,6 +266,8 @@ def codegen_set(fn_arg_names, lvar_names, rest) "reg_a" when fn_arg_names.include?(rest[1]) to_fn_arg_addr(fn_arg_names, rest[1]) + when lvar_names.include?(rest[1]) + to_lvar_addr(lvar_names, rest[1]) when /^vram\[(.+)\]$/ =~ rest[1] vram_addr = $1 alines << " get_vram #{vram_addr} reg_a"
それから、 codegen_stmts()
が _cmt
に対応していなかったのを修正:
--- a/vgcg.rb +++ b/vgcg.rb @@ -397,6 +397,8 @@ def codegen_stmts(fn_arg_names, lvar_names, rest) alines += codegen_set(fn_arg_names, lvar_names, stmt_rest) when "while" alines += codegen_while(fn_arg_names, lvar_names, stmt_rest) + when "_cmt" + alines += codegen_comment(stmt_rest[0]) else raise not_yet_impl("stmt_head", stmt_head) end
はい、ではライフゲームに戻りましょう。
まずは前回のコードをコピーして、と。
cp 31_count_alive_2.vgt.json 32_adjust_index.vgt.json
前回は8つのセルを使った生存カウントができるところまで作りましたが、 まだ完全ではありません。
盤面の上の辺と下の辺、 左の辺と右の辺をつなげてトーラス(※1)にする必要があります。
※1 トーラス - Wikipedia
https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%BC%E3%83%A9%E3%82%B9
Ruby で書いたコードでいうとここ。 端から飛び出してしまったら反対側の端にワープしてほしい。
xl = (x == 0 ) ? $w - 1 : x - 1 xr = (x == $w - 1) ? 0 : x + 1 yt = (y == 0 ) ? $h - 1 : y - 1 yb = (y == $h - 1) ? 0 : y + 1
今の vgt コードはこうなっています(座標補正なしの状態)。
, ["set", "xl", ["+", "x", -1]] , ["set", "xr", ["+", "x", 1]] , ["set", "yt", ["+", "y", -1]] , ["set", "yb", ["+", "y", 1]]
この部分を書き換えていきます。
まずは xl
に着目して左端の処理だけをやってみましょう。
--- a/32_adjust_index.vgt.json +++ b/32_adjust_index.vgt.json @@ -39,6 +39,15 @@ , ["set", "yt", ["+", "y", -1]] , ["set", "yb", ["+", "y", 1]] + , ["_cmt", "★ 補正の直前"] + , ["case" + , [["eq", "xl", -1] + , ["_cmt", "★ -1 だった場合"] + , ["set", "xl", ["+", "w", -1]] + ] + ] + , ["_cmt", "★ 補正の直後"] + , ["var", "tmp"] , ["_cmt", "左上"]
一度 xl = x - 1
を実行してから、
xl == -1
だったら xl = w - 1
で代入しなおす、というやり方にしました。
生存セルの初期化処理はいったん消して、 注目するセルを (0, 2) にして動かしてみましょう。
--- a/32_adjust_index.vgt.json +++ b/32_adjust_index.vgt.json @@ -99,18 +99,7 @@ , ["var", "y"] , ["set", "y", 0] - , ["call", "vram_set", "w", 1, 1, 1] - , ["call", "vram_set", "w", 2, 1, 1] - // , ["call", "vram_set", "w", 3, 1, 1] - - , ["call", "vram_set", "w", 1, 2, 1] - , ["call", "vram_set", "w", 3, 2, 1] - - // , ["call", "vram_set", "w", 1, 3, 1] - , ["call", "vram_set", "w", 2, 3, 1] - , ["call", "vram_set", "w", 3, 3, 1] - - , ["call", "count_alive", "w", 2, 2] + , ["call", "count_alive", "w", 0, 2] ] ]
とりあえず実行:
$ ./run.sh 32_adjust_index.vgt.json vgvm.rb:365:in `num_args_for': Invalid operator (-1) (RuntimeError) from vgvm.rb:31:in `dump_main' from vgvm.rb:382:in `dump_v2' from vgvm.rb:149:in `start' from vgvm.rb:470:in `<main>'
ふーむ。num_args_for()
でこけましたね。なんじゃこりゃ?
いきなり終了してしまうためメモリのダンプ表示が見れませんが、 こういう場合は落ち着いてアセンブリコード(または機械語コード)を確認します。
cat -n tmp/32_adjust_index.vga.txt の出力の一部: 73 _cmt ★~補正の直前 74 # 条件 1_0: ["eq", "xl", -1] 75 set_reg_a xl 76 set_reg_b -1 77 compare 78 jump_eq when_1_0 79 jump end_case_1 80 label when_1_0 81 _cmt ★ -1 だった場合 82 set xl + w -1 # ● 83 jump end_case_1 84 label end_case_1 85 _cmt ★~補正の直後
● のとこがなんか無茶なことになってますね……。
codegen_case()
のここです。
cond, *rest = when_block # ... then_alines = ["label when_#{label_id}_#{when_idx}"] rest.each {|stmt| then_alines << " " + stmt.join(" ") }
あーこれはだめですね。 これを書いたときはやっつけで済ませて、動いたのでよし、 としていましたが、 やっつけでは済まない事態が訪れました。ちゃんとやりましょう。
ちゃんとやるといっても、
ここでやっているのは「文の連なり」の変換(コード生成)なので、
codegen_stmts()
に置き換えるだけでいいはず。
--- a/vgcg.rb +++ b/vgcg.rb @@ -18,7 +18,7 @@ def to_lvar_addr(lvar_names, lvar_name) "[bp-#{index + 1}]" end -def codegen_case(when_blocks) +def codegen_case(fn_arg_names, lvar_names, when_blocks) alines = [] $label_id += 1 label_id = $label_id @@ -40,9 +40,7 @@ def codegen_case(when_blocks) alines << " jump_eq when_#{label_id}_#{when_idx}" then_alines = ["label when_#{label_id}_#{when_idx}"] - rest.each {|stmt| - then_alines << " " + stmt.join(" ") - } + then_alines += codegen_stmts(fn_arg_names, lvar_names, rest) then_alines << " jump end_case_#{label_id}" then_bodies << then_alines else @@ -365,7 +363,7 @@ def codegen_func_def(rest) when "return" alines += codegen_return(lvar_names, stmt_rest) when "case" - alines += codegen_case(stmt_rest) + alines += codegen_case(fn_arg_names, lvar_names, stmt_rest) when "while" alines += codegen_while(fn_arg_names, lvar_names, stmt_rest) when "_cmt"
動かします。
================================ 41: reg_a(3) reg_b(1) reg_c(0) zf(0) ---- memory (main) ---- 99 ["add_ab"] 100 ["cp", "reg_a", "[bp-2]"] 103 ["set_reg_a", "[bp+3]"] 105 ["set_reg_b", 1] 107 ["add_ab"] 108 ["cp", "reg_a", "[bp-3]"] 111 ["set_reg_a", "[bp+4]"] 113 ["set_reg_b", -1] 115 ["add_ab"] 116 ["cp", "reg_a", "[bp-4]"] 119 ["set_reg_a", "[bp+4]"] 121 ["set_reg_b", 1] 123 ["add_ab"] 124 ["cp", "reg_a", "[bp-5]"] 127 ["_cmt", "★~補正の直前"] pc => 129 ["set_reg_a", "xl"] 131 ["set_reg_b", -1] 133 ["compare"] 134 ["jump_eq", 140] 136 ["jump", 154] 138 ["label", "when_1_0"] 140 ["_cmt", "★~-1~だった場合"] 142 ["set_reg_a", "[bp+2]"] 144 ["set_reg_b", -1] 146 ["add_ab"] 147 ["cp", "reg_a", "[bp-2]"] 150 ["jump", 154] 152 ["label", "end_case_1"] 154 ["_cmt", "★~補正の直後"] 156 ["sub_sp", 1] 158 ["_cmt", "左上"] ---- memory (stack) ---- 25 0 26 0 27 0 28 0 29 0 30 0 31 0 32 0 sp => 33 3 34 1 35 1 36 -1 37 0 bp => 38 47 39 403 40 5 41 0 ---- memory (vram) ---- ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... vgvm.rb:430:in `set_reg_a': Not yet implemented ("val") ("xl") (RuntimeError) from vgvm.rb:166:in `block in start' from vgvm.rb:152:in `loop' from vgvm.rb:152:in `start' from vgvm.rb:470:in `<main>'
補正処理の一番最初での参照が解決されずに xl
が残っているな、
と当たりを付けて、このアセンブリコードを生成している箇所を探します。
codegen_case()
ですね。ここ:
def codegen_case(fn_arg_names, lvar_names, when_blocks) # ... case cond_head when "eq" alines << " set_reg_a #{cond_rest[0]}" # ★ alines << " set_reg_b #{cond_rest[1]}" alines << " compare" alines << " jump_eq when_#{label_id}_#{when_idx}" then_alines = ["label when_#{label_id}_#{when_idx}"] then_alines += codegen_stmts(fn_arg_names, lvar_names, rest) then_alines << " jump end_case_#{label_id}" then_bodies << then_alines else # ...
なんだか codegen_case()
がやっつけすぎな気がしますが、いいんですこれで。
YAGNI だから。
今が直すタイミングだからこれでいいの!!
(ということにして……)
上のコードの ★ のとこでローカル変数の参照が解決できてないわけで、 また同じパターンだなーと言って、これまでのように書き換えて解決してもいいのですが、 実はここは横着できます。
ここでやっているのは「式のコンパイル」なのですが、
そのためのメソッド codegen_exp()
がもうすでにあります。
これを使い回せないでしょうか?
codegen_exp()
の方ではローカル変数の解決も実装済みです。
codegen_exp()
では、
オペレータが eq
の場合は、
2つのオペランドが同じ値だったら 1 を、
異なっていたら 0 を reg_a
にセットします。
なので、まず codegen_exp()
を実行して、
その結果を見て分岐するようにすればいいんじゃないでしょうか?
やってみます。
--- a/vgcg.rb +++ b/vgcg.rb @@ -34,8 +34,12 @@ def codegen_case(fn_arg_names, lvar_names, when_blocks) case cond_head when "eq" - alines << " set_reg_a #{cond_rest[0]}" - alines << " set_reg_b #{cond_rest[1]}" + # 式の結果が reg_a に入る + alines += codegen_exp(fn_arg_names, lvar_names, cond) + + # 式の結果と比較するための値を reg_b に入れる + alines << " set_reg_b 1" + alines << " compare" alines << " jump_eq when_#{label_id}_#{when_idx}"
実行!
$ ./run.sh 32_adjust_index.vgt.json # 略 ================================ 50: reg_a(1) reg_b(1) reg_c(0) zf(1) ---- memory (main) ---- 127 ["_cmt", "★~補正の直前"] 129 ["set_reg_a", "[bp-2]"] 131 ["set_reg_b", -1] 133 ["compare"] 134 ["jump_eq", 142] 136 ["set_reg_a", 0] 138 ["jump", 146] 140 ["label", "then_2"] 142 ["set_reg_a", 1] 144 ["label", "end_eq_2"] 146 ["set_reg_b", 1] 148 ["compare"] 149 ["jump_eq", 155] 151 ["jump", 169] 153 ["label", "when_1_0"] pc => 155 ["_cmt", "★~-1~だった場合"] 157 ["set_reg_a", "[bp+2]"] 159 ["set_reg_b", -1] 161 ["add_ab"] 162 ["cp", "reg_a", "[bp-2]"] 165 ["jump", 169] 167 ["label", "end_case_1"] 169 ["_cmt", "★~補正の直後"] 171 ["sub_sp", 1] 173 ["_cmt", "左上"] 175 ["push", "[bp-4]"] 177 ["push", "[bp-2]"] 179 ["push", "[bp+2]"] 181 ["_cmt", "call_set~~vram_get"] 183 ["call", 41] 185 ["add_sp", 3]
「-1 だった場合」の分岐に入ってます。よしよし。
================================ 56: reg_a(4) reg_b(-1) reg_c(0) zf(1) ---- memory (main) ---- 140 ["label", "then_2"] 142 ["set_reg_a", 1] 144 ["label", "end_eq_2"] 146 ["set_reg_b", 1] 148 ["compare"] 149 ["jump_eq", 155] 151 ["jump", 169] 153 ["label", "when_1_0"] 155 ["_cmt", "★~-1~だった場合"] 157 ["set_reg_a", "[bp+2]"] 159 ["set_reg_b", -1] 161 ["add_ab"] 162 ["cp", "reg_a", "[bp-2]"] 165 ["jump", 169] 167 ["label", "end_case_1"] pc => 169 ["_cmt", "★~補正の直後"] 171 ["sub_sp", 1] 173 ["_cmt", "左上"] 175 ["push", "[bp-4]"] 177 ["push", "[bp-2]"] 179 ["push", "[bp+2]"] 181 ["_cmt", "call_set~~vram_get"] 183 ["call", 41] 185 ["add_sp", 3] 187 ["cp", "reg_a", "[bp-6]"] 190 ["set_reg_a", "[bp-1]"] 192 ["set_reg_b", "[bp-6]"] 194 ["add_ab"] 195 ["cp", "reg_a", "[bp-1]"] 198 ["_cmt", "上"] ---- memory (stack) ---- 25 0 26 0 27 0 28 0 29 0 30 0 31 0 32 0 sp => 33 3 34 1 35 1 36 4 ... xl 37 0 bp => 38 47 39 418 40 5 41 0
補正の直後では xl == 4
になっています! ヨシ!
今回はここまで!