vm2gol v2 製作メモ(32) 座標の補正


これまでは、 その都度の目標に向けて適当に作り、バグや not yet impl で例外が出たら その都度潰していく、という進め方でやってきました。

そういう方針なので、それはそれでいいのですが、 同じようなことの繰り返しになってちょっと煩雑&退屈なので、 特に瑣末なものは先に潰してから本題に入ろうと思います。 なるべくライフゲームに専念できるように。

(これはブログ記事化する都合上先回りしているためで、 そういう都合がなければ先回りしなくていいと思います)


というわけで、まずは 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 になっています! ヨシ!

今回はここまで!