vm2gol v2 製作メモ(23) 組み込みの eq / while文



準備が整ったので while やります!

  ["var", "a"]
, ["set", "a", 0]
, ["while", ["eq", 0, 0]
  , ["set", "a", ["+", "a", 1]]
  ]

こんな感じでやりたいのですが!!

["eq", 0, 0] の部分の eq がまだでしたね……準備が整ってませんでした!! やっつけましょう!

組み込みの eq 演算子

テスト用の vgtコードです:

// 23_eq.vgt.json

["stmts"
, ["func", "main"
  , []
  , [
      ["eq", 0, 0]
    , ["eq", 0, 1]
    ]
  ]
]

前回追加した + と同じ並びに eq のための分岐を追加します。 case文のときに似たことやったので同じ感じでさらっと追加 (共通化できそうな気もしますがひとまず放置で)。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -79,6 +79,24 @@ def codegen_exp(lvar_names, exp)
     alines << "  set_reg_a #{left}"
     alines << "  set_reg_b #{right}"
     alines << "  add_ab"
+  when "eq"
+    $label_id += 1
+    label_id = $label_id
+
+    alines << "  set_reg_a #{left}"
+    alines << "  set_reg_b #{right}"
+    alines << "  compare"
+    alines << "  jump_eq then_#{label_id}"
+
+    # else
+    alines << "  set_reg_a 0"
+    alines << "  jump end_eq_#{label_id}"
+
+    # then
+    alines << "label then_#{label_id}"
+    alines << "  set_reg_a 1"
+
+    alines << "label end_eq_#{label_id}"
   else
     raise not_yet_impl("operator", operator)
   end
@@ -154,6 +172,8 @@ def codegen_func_def(rest)
       alines << "  sub_sp 1"
     when "set"
       alines += codegen_set(fn_arg_names, lvar_names, stmt_rest)
+    when "eq"
+      alines += codegen_exp(lvar_names, stmt)
     when "return"
       val = stmt_rest[0]
       alines << "  set_reg_a #{val}"

実行して、

["eq", 0, 0] を実行した直後 => reg_a に 1(真)がセットされた
["eq", 0, 1] を実行した直後 => reg_a に 0(偽)がセットされた

こうなっていることが確認できました。OK。

今度こそ準備が整ったので while文やりましょう!

while文

while文はこんな構文にします。

["while", {条件}
, [
    // ループの本体
    {文1}
  , {文2}
  ...
  , {文n}
  ]
]

while文のためのサンプルプログラムを用意します。 無限ループでローカル変数 a をインクリメントするだけ。

// 23_while.vgt.json

["stmts"
, ["func", "main"
  , []
  , [
      ["var", "a"]
    , ["set", "a", 0]
    , ["while", ["eq", 0, 0]
      , [
          ["set", "a", ["+", "a", 1]]
        ]
      ]
    ]
  ]
]

これをコンパイルすると while文の部分がこんなアセンブリコードになってほしい:

label while_{id}
  # ここで条件を評価して結果を reg_a に入れる
  set_reg_b 1  # 比較用の値(真)
  compare
  jump_eq true_{id}
  jump end_while_{id}

label true_{id}
  # 真の場合の処理
  jump while_{id} # ループの先頭に戻る

label end_while_{id}

はい、ではまずハードコーディングで追加して、と。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -52,6 +52,40 @@ def codegen_case(when_blocks)
   alines
 end
 
+def codegen_while()
+  alines = []
+
+  $label_id += 1
+  label_id = $label_id
+
+  alines << ""
+
+  # ループの先頭
+  alines << "label while_#{label_id}"
+
+  # TODO 条件の評価
+  alines << "  set_reg_a 1"
+  alines << "  set_reg_b 1"
+  alines << "  compare"
+
+  # true の場合ループの本体を実行
+  alines << "  jump_eq true_#{label_id}"
+
+  # false の場合ループを抜ける
+  alines << "  jump end_while_#{label_id}"
+
+  alines << "label true_#{label_id}"
+  alines << "  # TODO ループの本体"
+
+  # ループの先頭に戻る
+  alines << "  jump while_#{label_id}"
+
+  alines << "label end_while_#{label_id}"
+  alines << ""
+
+  alines
+end
+
 def codegen_exp(lvar_names, exp)
   alines = []
   operator, *args = exp
@@ -179,6 +213,8 @@ def codegen_func_def(rest)
       alines << "  set_reg_a #{val}"
     when "case"
       alines += codegen_case(stmt_rest)
+    when "while"
+      alines += codegen_while()
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end

一応動かして問題なさそうなのを確認してから、 条件の評価部分で codegen_exp() を呼び出すように書き換え。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -52,7 +52,8 @@ def codegen_case(when_blocks)
   alines
 end
 
-def codegen_while()
+def codegen_while(lvar_names, rest)
+  cond_exp, body = rest
   alines = []
 
   $label_id += 1
@@ -63,8 +64,8 @@ def codegen_while()
   # ループの先頭
   alines << "label while_#{label_id}"
 
-  # TODO 条件の評価
-  alines << "  set_reg_a 1"
+  # 条件の評価 ... 結果が reg_a に入る
+  alines += codegen_exp(lvar_names, cond_exp)
   # 比較対象の値(真)をセット
   alines << "  set_reg_b 1"
   alines << "  compare"
@@ -215,7 +216,7 @@ def codegen_func_def(rest)
     when "case"
       alines += codegen_case(stmt_rest)
     when "while"
-      alines += codegen_while()
+      alines += codegen_while(lvar_names, stmt_rest)
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end

そして、ループ本体部分を修正。 ここは codegen_stmts() に丸投げでいいはず。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -77,7 +77,8 @@ def codegen_while(lvar_names, rest)
   alines << "  jump end_while_#{label_id}"
 
   alines << "label true_#{label_id}"
-  alines << "  # TODO ループの本体"
+  # ループの本体
+  alines += codegen_stmts(body)
 
   # ループの先頭に戻る
   alines << "  jump while_#{label_id}"

動くかな?

$ ./run.sh 23_while.vgt.json 
vgcg.rb:243:in `block in codegen_stmts': Not yet implemented ("stmt_head") ("set") (RuntimeError)

codegen_stmts() を set文に対応させます。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -239,6 +239,8 @@ def codegen_stmts(rest)
     case stmt_head
     when "func"
       alines += codegen_func_def(stmt_rest)
+    when "set"
+      alines += codegen_set(fn_arg_names, lvar_names, stmt_rest)
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end

再度実行。

$ ./run.sh 23_while.vgt.json 
vgcg.rb:243:in `block in codegen_stmts': undefined local variable or method `fn_arg_names' for main:Object (NameError)

関数の引数が解決できないので、適宜メソッドの引数で渡すように修正。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -52,7 +52,7 @@ def codegen_case(when_blocks)
   alines
 end
 
-def codegen_while(lvar_names, rest)
+def codegen_while(fn_arg_names, lvar_names, rest)
   cond_exp, body = rest
   alines = []
 
@@ -78,7 +78,7 @@ def codegen_while(lvar_names, rest)
 
   alines << "label true_#{label_id}"
   # ループの本体
-  alines += codegen_stmts(body)
+  alines += codegen_stmts(fn_arg_names, lvar_names, body)
 
   # ループの先頭に戻る
   alines << "  jump while_#{label_id}"
@@ -217,7 +217,7 @@ def codegen_func_def(rest)
     when "case"
       alines += codegen_case(stmt_rest)
     when "while"
-      alines += codegen_while(lvar_names, stmt_rest)
+      alines += codegen_while(fn_arg_names, lvar_names, stmt_rest)
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end
@@ -231,7 +231,7 @@ def codegen_func_def(rest)
   alines
 end
 
-def codegen_stmts(rest)
+def codegen_stmts(fn_arg_names, lvar_names, rest)
   alines = []
 
   rest.each do |stmt|
@@ -257,7 +257,7 @@ def codegen(tree)
 
   head, *rest = tree
   # assert head == "stmts"
-  alines += codegen_stmts(rest)
+  alines += codegen_stmts([], [], rest)
 
   alines
 end

トップレベルの呼び出しのときは 関数の引数もローカル変数も存在しないので codegen_stmts([], [], rest) のように空の配列を渡しておけばいいでしょう。

これで動くようになりました!

================================
reg_a(4) reg_b(1) reg_c(0) zf(1)
---- memory (main) ----
      00   ["call", 5]
      02   ["exit"]
      03 ["label", "main"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["sub_sp", 1]
      12   ["cp", 0, "[bp-1]"]
      15 ["label", "while_1"]
pc => 17   ["set_reg_a", 0]
      19   ["set_reg_b", 0]
      21   ["compare"]
      22   ["jump_eq", 30]
      24   ["set_reg_a", 0]
      26   ["jump", 34]
      28 ["label", "then_2"]
      30   ["set_reg_a", 1]
      32 ["label", "end_eq_2"]
      34   ["set_reg_b", 1]
      36   ["compare"]
      37   ["jump_eq", 43]
      39   ["jump", 55]
      41 ["label", "true_1"]
      43   ["set_reg_a", "[bp-1]"]
      45   ["set_reg_b", 1]
      47   ["add_ab"]
      48   ["cp", "reg_a", "[bp-1]"]
      51   ["jump", 17]
      53 ["label", "end_while_1"]
      55   ["cp", "bp", "sp"]
      58   ["pop", "bp"]
      60   ["ret"]
---- memory (stack) ----
         38 0
         39 0
         40 0
         41 0
         42 0
         43 0
         44 0
         45 0
sp    => 46 4  ... ローカル変数 a
   bp => 47 49
         48 2
         49 0

これを実行すると、同じ箇所がぐるぐる実行されて ローカル変数 a の値が 1 ずつ増えていく様子が観察できます!

というわけで while で無限ループができました!!