vm2gol v2 製作メモ(37) 完成!


はい、前回でとうとう 1回のターンが動くように……なってないんですねこれが。 1回のターンに必要な処理がもう1つ残ってます。 VRAM のバッファ領域に書き込んだ次世代の状態をメイン領域に戻す処理です。

やりましょう!


まずは、二重ループの部分が大きくなってきたので 別の関数に分けます。

分かりやすい diff にならなかったので 修正後のものを貼ります。

変数宣言と、 x, y の初期化と 二重ループの部分を make_next_gen() に抽出しました。

, ["func", "make_next_gen", ["w", "h"]
  , [
      ["var", "x"]
    , ["set", "x", 0]

    , ["var", "y"]
    , ["set", "y", 0]

    , ["var", "count"]
      // 注目しているセルの現世代の生死
    , ["var", "current_val"]
      // 注目しているセルの次世代の生死
    , ["var", "next_val"]

    , ["while", ["neq", "y", "h"]
      , [
          ["set", "x", 0]
        , ["while", ["neq", "x", "w"]
          , [
              ["call_set", "count", ["count_alive", "w", "h", "x", "y"]]
            , ["_cmt", "★ count_alive から戻った直後"]

            , ["_cmt", "★次世代の生死決定の直前"]

            , ["call_set", "current_val", ["vram_get", "w", "x", "y"]]

            , ["call_set", "next_val", ["calc_next_gen", "current_val", "count"]]

            , ["_cmt", "★次世代の生死決定の直後"]

            , ["call", "vram_set_buf", "w", "x", "y", "next_val"]
            , ["_cmt", "★ vram_set_buf から戻った直後"]

            , ["set", "x", ["+", "x", 1]]
            ]
          ]
        , ["set", "y", ["+", "y", 1]]
        ]
      ]
    ]
  ]

, ["func", "main", []
  , [
    // ...

      ["var", "w"] // 盤面の幅
    , ["set", "w", 5]
    , ["var", "h"] // 盤面の高さ
    , ["set", "h", 5]

      // 初期状態の設定
    , ["call", "vram_set", "w", 1, 0, 1]
    , ["call", "vram_set", "w", 2, 1, 1]
    , ["call", "vram_set", "w", 0, 2, 1]
    , ["call", "vram_set", "w", 1, 2, 1]
    , ["call", "vram_set", "w", 2, 2, 1]

    , ["call", "make_next_gen", "w", "h"]
    ]
  ]

実行。

$ ./run.sh gol.vgt.json 
vgcg.rb:208:in `block in codegen_call': Not yet implemented ("w") (RuntimeError)
    from vgcg.rb:198:in `each'
    from vgcg.rb:198:in `codegen_call'
    from vgcg.rb:400:in `block in codegen_stmts'
    from vgcg.rb:394:in `each'
    from vgcg.rb:394:in `codegen_stmts'
    from vgcg.rb:93:in `codegen_while'
    from vgcg.rb:408:in `block in codegen_stmts'
    from vgcg.rb:394:in `each'
    from vgcg.rb:394:in `codegen_stmts'
    from vgcg.rb:93:in `codegen_while'
    from vgcg.rb:375:in `block in codegen_func_def'
    from vgcg.rb:356:in `each'
    from vgcg.rb:356:in `codegen_func_def'
    from vgcg.rb:398:in `block in codegen_stmts'
    from vgcg.rb:394:in `each'
    from vgcg.rb:394:in `codegen_stmts'
    from vgcg.rb:427:in `codegen'
    from vgcg.rb:439:in `<main>'

うーむ、この期に及んでまだ出ますね……。 call のときに関数の引数の参照が解決できてないようです。 修正します。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -191,7 +191,7 @@ def codegen_exp(fn_arg_names, lvar_names, exp)
   alines
 end
 
-def codegen_call(lvar_names, stmt_rest)
+def codegen_call(fn_arg_names, lvar_names, stmt_rest)
   alines = []
 
   fn_name, *fn_args = stmt_rest
@@ -201,6 +201,9 @@ def codegen_call(lvar_names, stmt_rest)
       alines << "  push #{fn_arg}"
     when String
       case
+      when fn_arg_names.include?(fn_arg)
+        fn_arg_addr = to_fn_arg_addr(fn_arg_names, fn_arg)
+        alines << "  push #{fn_arg_addr}"
       when lvar_names.include?(fn_arg)
         lvar_addr = to_lvar_addr(lvar_names, fn_arg)
         alines << "  push #{lvar_addr}"
@@ -357,7 +360,7 @@ def codegen_func_def(rest)
     stmt_head, *stmt_rest = stmt
     case stmt_head
     when "call"
-      alines += codegen_call(lvar_names, stmt_rest)
+      alines += codegen_call(fn_arg_names, lvar_names, stmt_rest)
     when "call_set"
       alines += codegen_call_set(fn_arg_names, lvar_names, stmt_rest)
     when "var"
@@ -397,7 +400,7 @@ def codegen_stmts(fn_arg_names, lvar_names, rest)
     when "func"
       alines += codegen_func_def(stmt_rest)
     when "call"
-      alines += codegen_call(lvar_names, stmt_rest)
+      alines += codegen_call(fn_arg_names, lvar_names, stmt_rest)
     when "call_set"
       alines += codegen_call_set(fn_arg_names, lvar_names, stmt_rest)
     when "set"

一度動かして、問題なさそうなので、 replace_with_buf() を追加して、 make_next_gen() の次に呼び出します。

VRAM のメイン領域をバッファ領域の内容で置き換える処理です。これは割と簡単。

--- a/gol.vgt.json
+++ b/gol.vgt.json
@@ -268,6 +268,25 @@
     ]
   ]
 
+, ["func", "replace_with_buf", []
+  , [
+      ["var", "vi"]
+    , ["set", "vi", 0]
+
+    , ["var", "vi_buf"]
+    , ["var", "temp"]
+
+    , ["while", ["neq", "vi", 25]
+      , [
+          ["set", "vi_buf", ["+", "vi", 25]]
+        , ["set", "temp", "vram[vi_buf]"]
+        , ["set", "vram[vi]", "temp"]
+        , ["set", "vi", ["+", "vi", 1]]
+        ]
+      ]
+    ]
+  ]
+
 , ["func", "main", []
   , [
     //   ["var", "tmp"]
@@ -300,6 +319,7 @@
     , ["call", "vram_set", "w", 2, 2, 1]
 
     , ["call", "make_next_gen", "w", "h"]
+    , ["call", "replace_with_buf"]
     ]
   ]

実行!

vgvm.rb:293:in `block in start': Not yet implemented ("arg1") ("vi_buf") (RuntimeError)
    from vgvm.rb:152:in `loop'
    from vgvm.rb:152:in `start'
    from vgvm.rb:470:in `<main>'

修正!!

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -273,10 +273,20 @@ def codegen_set(fn_arg_names, lvar_names, rest)
       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]
+    when /^vram\[(\d+)\]$/ =~ rest[1]
       vram_addr = $1
       alines << "  get_vram #{vram_addr} reg_a"
       "reg_a"
+    when /^vram\[([a-z_][a-z0-9_]*)\]$/ =~ rest[1]
+      dest = $1
+      case
+      when lvar_names.include?(dest)
+        lvar_addr = to_lvar_addr(lvar_names, dest)
+        alines << "  get_vram #{ lvar_addr } reg_a"
+      else
+        raise not_yet_impl("rest", rest)
+      end
+      "reg_a"
     else
       raise not_yet_impl("set src_val", rest)
     end

場当たり的な感じがしますがもうちょっとなので逃げ切りたい!

vgvm.rb:264:in `block in start': Not yet implemented ("set_vram") ("[bp-3]") (RuntimeError)
    from vgvm.rb:152:in `loop'
    from vgvm.rb:152:in `start'
    from vgvm.rb:470:in `<main>'

修正!!

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -260,6 +260,9 @@ class Vm
           when /^\[bp\+(\d+)\]$/
             stack_addr = @bp + $1.to_i
             @mem.stack[stack_addr]
+          when /^\[bp-(\d+)\]$/
+            stack_addr = @bp - $1.to_i
+            @mem.stack[stack_addr]
           else
             raise not_yet_impl("set_vram", arg2)
           end

実行!!

================================
19517: reg_a(0) reg_b(1) reg_c(0) zf(0)
---- memory (main) ----
      00   ["call", 1374]
pc => 02   ["exit"]
      03 ["label", "to_vi"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["sub_sp", 1]
      12   ["set_reg_a", "[bp+4]"]
      14   ["set_reg_b", "[bp+2]"]
      16   ["mult_ab"]
      17   ["cp", "reg_a", "[bp-1]"]
      20   ["sub_sp", 1]
      22   ["set_reg_a", "[bp-1]"]
      24   ["set_reg_b", "[bp+3]"]
      26   ["add_ab"]
      27   ["cp", "reg_a", "[bp-2]"]
      30   ["set_reg_a", "[bp-2]"]
      32   ["set_reg_b", "[bp+5]"]
---- memory (stack) ----
         41 0
         42 25
         43 47
         44 1473
         45 5
         46 5
         47 49
         48 2
sp bp => 49 0
---- memory (vram) ----
..... .....
..... @.@..
..... .@@..
..... .@...
..... .....
exit

グェーッッ! 全部死んだッ!!!

デバッグ! 修正!!

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -278,10 +278,10 @@ def codegen_set(fn_arg_names, lvar_names, rest)
       alines << "  get_vram #{vram_addr} reg_a"
       "reg_a"
     when /^vram\[([a-z_][a-z0-9_]*)\]$/ =~ rest[1]
-      dest = $1
+      var_name = $1
       case
-      when lvar_names.include?(dest)
-        lvar_addr = to_lvar_addr(lvar_names, dest)
+      when lvar_names.include?(var_name)
+        lvar_addr = to_lvar_addr(lvar_names, var_name)
         alines << "  get_vram #{ lvar_addr } reg_a"
       else
         raise not_yet_impl("rest", rest)

ここの前後ですでに dest という名前の変数を使っていて、 ここで dest に別の値を入れてしまうとまずいのでした *1

実行!!!

================================
19517: reg_a(0) reg_b(1) reg_c(0) zf(0)
---- memory (main) ----
      00   ["call", 1374]
pc => 02   ["exit"]
      03 ["label", "to_vi"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["sub_sp", 1]
      12   ["set_reg_a", "[bp+4]"]
      14   ["set_reg_b", "[bp+2]"]
      16   ["mult_ab"]
      17   ["cp", "reg_a", "[bp-1]"]
      20   ["sub_sp", 1]
      22   ["set_reg_a", "[bp-1]"]
      24   ["set_reg_b", "[bp+3]"]
      26   ["add_ab"]
      27   ["cp", "reg_a", "[bp-2]"]
      30   ["set_reg_a", "[bp-2]"]
      32   ["set_reg_b", "[bp+5]"]
---- memory (stack) ----
         41 49
         42 25
         43 47
         44 1473
         45 5
         46 5
         47 49
         48 2
sp bp => 49 0
---- memory (vram) ----
..... .....
@.@.. @.@..
.@@.. .@@..
.@... .@...
..... .....
exit

よっしゃ!!!!


このままゴールまでいきましょう!!!!!!!!!!

--- a/gol.vgt.json
+++ b/gol.vgt.json
@@ -318,8 +318,12 @@
     , ["call", "vram_set", "w", 1, 2, 1]
     , ["call", "vram_set", "w", 2, 2, 1]
 
-    , ["call", "make_next_gen", "w", "h"]
-    , ["call", "replace_with_buf"]
+    , ["while", ["eq", 0, 0]
+      , [
+          ["call", "make_next_gen", "w", "h"]
+        , ["call", "replace_with_buf"]
+        ]
+      ]
     ]
   ]

グワーッッッ!! 動いた!!! 動きました!!!!!! (めちゃくちゃ遅いけど!!!!)


いやー、できるかどうかよく分からないとこから始めて とうとうここまで辿り着きましたね……感無量……。

とりあえずここでゴールです! 目標は達成しました。

Mission accomplished. (TODO ランボー/怒りの脱出 のキャプチャ画像を貼る*2


次回、いくつか落ち穂拾い的な修正をやって いったん終わりにしようと思います。

*1:ここちょっと不自然ですが、不自然なのは下書きを書いた後で変数名を変えてしまったためです。実際に発生したことではありますし、おもしろかったので再現しておきました。

*2:貼りませんが