vm2gol v2 製作メモ(18) ローカル変数の宣言と代入 / var, set文


今回はローカル変数の宣言と代入をやります。

アセンブリをやっていたときはループ、条件分岐、 それからサブルーチン〜という順番でやっていました。 それとは違う順番になっていますが、 特に意図したものではなく、次にやることを気分で適当に決めていたためです。 )

vgtコード:

// 18_local_var.vgt.json

["stmts"
, ["func", "main"
  , []
  , [
      ["var", "a"] // ← これ!
    ]
  ]
]

ローカル変数の宣言のために、

["var", "{ローカル変数名}"]

という構文を追加します (func に var と来るとちょっと GO言語っぽいですね)。

期待するアセンブリコード出力:

  call main
  exit

label main
  push bp
  cp sp bp

  # 関数の処理本体
  sub_sp 1 # ← これが加わる

  cp bp sp
  pop bp
  ret

これだけなら楽勝ですね。 codegen_func_def()case の分岐を1つ増やすだけ。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -25,6 +25,8 @@ def codegen_func_def(rest)
     when "call"
       fn_name = stmt_rest[0]
       alines << "  call #{fn_name}"
+    when "var"
+      alines << "  sub_sp 1"
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end

宣言するだけでは意味がないので、変数に値をセットしてみましょう。

またまた文法を拡張して、こんどは変数に値を代入する

["set", "{ローカル変数名}", {セットしたい値}]

という構文を追加します。

vgtコードの方にも追加。

--- a/18_local_var.vgt.json
+++ b/18_local_var.vgt.json
@@ -3,6 +3,7 @@
   , []
   , [
       ["var", "a"]
+    , ["set", "a", 12]
     ]
   ]
 ]

ちなみに、var a = 12; のように、 宣言と同時に値を代入する構文は用意しませんでした。 varset を使うことで同等のことがすでにできているので、 さっさと次に進もうと考えたためです。

( ただ、ちょっとだけとはいえ煩雑ではあったので、 利便性のために追加しておいても良かったかなという気もします。 そんなに難しくなさそうですし。 )


で、(1個目の)ローカル変数への値のセットは、アセンブリでは cp 12 [bp-1] のように変換されてほしいので、そのように修正します。

参考: (15) ローカル変数 / sub_sp

var と同じように codegen_func_def() の分岐に追加します (ひとまず [bp-1] はハードコーディングで……)。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -27,6 +27,10 @@ def codegen_func_def(rest)
       alines << "  call #{fn_name}"
     when "var"
       alines << "  sub_sp 1"
+    when "set"
+      lvar_name = stmt_rest[0]
+      val = stmt_rest[1]
+      alines << "  cp #{val} [bp-1]"
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end

アセンブリコードに変換してみましょう。

$ ruby vgcg.rb 18_local_var.vgt.json 
  call main
  exit

label main
  push bp
  cp sp bp

  # 関数の処理本体
  sub_sp 1      # ローカル変数を宣言(格納場所を確保)
  cp 12 [bp-1]  # ローカル変数に値を代入(確保した場所に cp)

  cp bp sp
  pop bp
  ret

よさそうですね。 run.sh で実行すると……

vgvm.rb:233:in `copy': Not yet implemented ("copy src") (12) (RuntimeError)
        from vgvm.rb:149:in `block in start'
        from vgvm.rb:126:in `loop'
        from vgvm.rb:126:in `start'
        from vgvm.rb:328:in `<main>'

えーっとこれは…… 即値は cp 命令のコピー元として使えないようになってますね。 VM を修正します。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -219,6 +219,8 @@ class Vm
   def copy(arg1, arg2)
     src_val =
       case arg1
+      when Integer
+        arg1
       when "reg_a"
         @reg_a
       when "sp"

あらためて実行すると……こんどは大丈夫。

================================
reg_a(0) reg_b(0) reg_c(0) zf(0)
---- memory (main) ----
      00   ["call", 5]
      02   ["exit"]
      03 ["label", "main"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["sub_sp", 1]
      12   ["cp", 12, "[bp-1]"]
pc => 15   ["cp", "bp", "sp"]
      18   ["pop", "bp"]
      20   ["ret"]
---- memory (stack) ----
         38 0
         39 0
         40 0
         41 0
         42 0
         43 0
         44 0
         45 0
sp    => 46 12 ... ローカル変数にセットした値
   bp => 47 49
         48 2
         49 0

set文が動きました!! めでたしめでたし。


めでたく動いたので、ハードコーディングした部分をいい感じにしましょう。 ここをいい感じにすると 2個目以降のローカル変数も使えるようになるはず。

vgtコード:

// 18_local_vars.vgt.json

["stmts"
, ["func", "main"
  , []
  , [
      ["var", "a"]
    , ["set", "a", 12]  // [bp-1] にセットされてほしい

    , ["var", "b"]
    , ["set", "b", 34]  // [bp-2] にセットされてほしい
    ]
  ]
]

上のコメントに書いているように、 1個目の引数 a[bp-1] 、 2個目の引数 b[bp-2] に値がセットされるように変換したい。

変数名から「その変数が何番目に宣言されたか」が分かればよいので、 何かしらのマッピング情報を用意すればいいでしょう。

マッピングってことはハッシュかな、と一瞬考えましたが、 何番目かが分かればいいだけなので、 ハッシュじゃなくて配列を用意して、 変数が出現した(宣言された)順番に追加しとけばいいんじゃないでしょうか。

それでやってみましょう。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -19,6 +19,9 @@ def codegen_func_def(rest)
 
   alines << ""
   alines << "  # 関数の処理本体"
+
+  lvar_names = []
+
   body.each {|stmt|
     stmt_head, *stmt_rest = stmt
     case stmt_head
@@ -26,11 +29,13 @@ def codegen_func_def(rest)
       fn_name = stmt_rest[0]
       alines << "  call #{fn_name}"
     when "var"
+      lvar_names << stmt_rest[0]
       alines << "  sub_sp 1"
     when "set"
       lvar_name = stmt_rest[0]
       val = stmt_rest[1]
-      alines << "  cp #{val} [bp-1]"
+      lvar_pos = lvar_names.index(lvar_name) + 1
+      alines << "  cp #{val} [bp-#{lvar_pos}]"
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end
$ ruby vgcg.rb 18_local_vars.vgt.json 
  call main
  exit

label main
  push bp
  cp sp bp

  # 関数の処理本体
  sub_sp 1
  cp 12 [bp-1]
  sub_sp 1
  cp 34 [bp-2]

  cp bp sp
  pop bp
  ret

うまくいったようです!

================================
reg_a(0) reg_b(0) reg_c(0) zf(0)
---- memory (main) ----
      00   ["call", 5]
      02   ["exit"]
      03 ["label", "main"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["sub_sp", 1]
      12   ["cp", 12, "[bp-1]"]
      15   ["sub_sp", 1]
      17   ["cp", 34, "[bp-2]"]
pc => 20   ["cp", "bp", "sp"]
      23   ["pop", "bp"]
      25   ["ret"]
---- memory (stack) ----
         37 0
         38 0
         39 0
         40 0
         41 0
         42 0
         43 0
         44 0
sp    => 45 34 ... 2個目のローカル変数
         46 12 ... 1個目のローカル変数
   bp => 47 49
         48 2
         49 0

2個目のローカル変数 b に値をセットした直後の様子。 いい感じです!


これができたら、main から呼び出した先の関数でも 同じようにローカル変数が使えるようになってるんじゃないでしょうか。

調子に乗って試してみましょう。

vgtコード:

// 18_local_vars_call.vgt.json

["stmts"

, ["func", "main"
  , []
  , [
      ["var", "a"]
    , ["set", "a", 12]
    , ["var", "b"]
    , ["set", "b", 34]
    , ["call", "fn_sub"]
    ]
  ]

, ["func", "fn_sub"
  , []
  , [
      ["var", "a"]
    , ["set", "a", 56]
    , ["var", "b"]
    , ["set", "b", 78]
    ]
  ]

]

fn_sub() でローカル変数 b に値がセットされた直後の状態:

================================
reg_a(0) reg_b(0) reg_c(0) zf(0)
---- memory (main) ----
      00   ["call", 5]
      02   ["exit"]
      03 ["label", "main"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["sub_sp", 1]
      12   ["cp", 12, "[bp-1]"]
      15   ["sub_sp", 1]
      17   ["cp", 34, "[bp-2]"]
      20   ["call", 30]
      22   ["cp", "bp", "sp"]
      25   ["pop", "bp"]
      27   ["ret"]
      28 ["label", "fn_sub"]
      30   ["push", "bp"]
      32   ["cp", "sp", "bp"]
      35   ["sub_sp", 1]
      37   ["cp", 56, "[bp-1]"]
      40   ["sub_sp", 1]
      42   ["cp", 78, "[bp-2]"]
pc => 45   ["cp", "bp", "sp"]
      48   ["pop", "bp"]
      50   ["ret"]
---- memory (stack) ----
         33 0
         34 0
         35 0
         36 0
         37 0
         38 0
         39 0
         40 0
sp    => 41 78 ... fn_sub のローカル変数2
         42 56 ... fn_sub のローカル変数1
   bp => 43 47
         44 22
         45 34 ... main のローカル変数2
         46 12 ... main のローカル変数1
         47 49
         48 2
         49 0

おお〜いいですね〜。すんなり〜。 main と同じ変数名( a, b )を使っても ちゃんと別のものとして扱われています。