vm2gol v2 製作メモ(20) 値を返却してローカル変数に代入 / return, call_set文


関数呼び出しができて、 ローカル変数が使えるようになって、 引数も渡せるようになりました。

あと関数まわりは返り値をどうにかすれば一通り必要なものが揃います。


今回は次の2つのステップに分けます。

  • 値を返すだけ
  • 返された値を(呼び出し元の)ローカル変数にセットする。

ではやっていきましょう。

値を返すだけ

まずはこれをコンパイルします。

// 20_return.vgt.json

["stmts"

, ["func", "main"
  , []
  , [
      ["call", "fn_sub"]
    ]
  ]

, ["func", "fn_sub"
  , []
  , [
      ["return", 12]
    ]
  ]

]

値を返すのは「reg_a に値をセットして ret」すればよいので、 これは簡単ですね。

※参考: (14) 複数の引数を渡す / スタックオーバーフロー対策 / 返り値

["return", {返却する値}]

という構文を追加して、修正します。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -52,6 +52,9 @@ def codegen_func_def(rest)
 
       lvar_pos = lvar_names.index(lvar_name) + 1
       alines << "  cp #{val} [bp-#{lvar_pos}]"
+    when "return"
+      val = stmt_rest[0]
+      alines << "  set_reg_a #{val}"
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end

簡単ですね。

結果はこうなります。 fn_sub() から戻った直後の状態。

$ ./run.sh 20_return.vgt.json 

(略)

================================
reg_a(12) 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   ["call", 22]
pc => 12   ["add_sp", 0]
      14   ["cp", "bp", "sp"]
      17   ["pop", "bp"]
      19   ["ret"]
      20 ["label", "fn_sub"]
      22   ["push", "bp"]
      24   ["cp", "sp", "bp"]
      27   ["set_reg_a", 12]
      29   ["cp", "bp", "sp"]
      32   ["pop", "bp"]
      34   ["ret"]
---- memory (stack) ----
         39 0
         40 0
         41 0
         42 0
         43 0
         44 0
         45 47
         46 12
sp bp => 47 49
         48 2
         49 0

reg_a に返り値がセットされています。

というわけで、単純な返却はこれで OK。

返り値をローカル変数に代入

次に、返り値をローカル変数に代入します。

["set", "{ローカル変数名}", ["call", "fn_sub"]]

set 構文を改造してこんな感じでローカル変数にセットするとかっこいいかな? というのをまずは考えたのですが、 set の2番めの引数が即値か関数呼び出しかで分岐して……とやっていくと 複雑になりそうな気がして(そうでもない?)、 call_set という専用の構文を新たに追加することにしました。

こうです:

[
  "call_set"
, "{ローカル変数名}"
, [
    "{呼び出す関数名}"
  , {引数1}
  , {引数2}
  , ...
  ]
]

今見ると、内側の括弧をなくして

[
  "call_set"
, "{ローカル変数名}"
, "{呼び出す関数名}"
, {引数1}
, {引数2}
, ...
]

とした方が call の構文に近くて良かったかも…… という気もしますが、うーんまあいいか (適当)。

vgtコードを用意して

// 20_call_set.vgt.json

["stmts"

, ["func", "main"
  , []
  , [
      ["var", "a"]
    , ["call_set", "a", ["fn_sub"]]
    ]
  ]

, ["func", "fn_sub"
  , []
  , [
      ["return", 12]
    ]
  ]

]

call_set 文の処理を追加します。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -33,6 +33,17 @@ def codegen_func_def(rest)
       }
       alines << "  call #{fn_name}"
       alines << "  add_sp #{fn_args.size}"
+    when "call_set"
+      lvar_name, fn_temp = stmt_rest
+      fn_name, *fn_args = fn_temp
+      fn_args.reverse.each {|fn_arg|
+        alines << "  push #{fn_arg}"
+      }
+      alines << "  call #{fn_name}"
+      alines << "  add_sp #{fn_args.size}"
+
+      lvar_pos = lvar_names.index(lvar_name) + 1
+      alines << "  cp reg_a [bp-#{lvar_pos}]"
     when "var"
       lvar_names << stmt_rest[0]
       alines << "  sub_sp 1"

call の処理をコピペしてちょこっと直して、 最後に cp reg_a [bp-N] を追加した形ですね。 関数の返り値は reg_a にセットすることにしているので、コピー元は reg_a 固定です。

動かしてみます。

================================
reg_a(12) 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   ["call", 27]
      14   ["add_sp", 0]
      16   ["cp", "reg_a", "[bp-1]"] ... 返り値をローカル変数 a に代入
pc => 19   ["cp", "bp", "sp"]
      22   ["pop", "bp"]
      24   ["ret"]
      25 ["label", "fn_sub"]
      27   ["push", "bp"]
      29   ["cp", "sp", "bp"]
      32   ["set_reg_a", 12]
      34   ["cp", "bp", "sp"]
      37   ["pop", "bp"]
      39   ["ret"]
---- memory (stack) ----
         38 0
         39 0
         40 0
         41 0
         42 0
         43 0
         44 47
         45 14
sp    => 46 12 ... ローカル変数 a に代入された
   bp => 47 49
         48 2
         49 0

fn_sub() から main() に戻ってローカル変数 a に返り値を代入した直後です。 いいですね!

これで、関数まわりで必要そうなものがひととおり (といってもほんとに必要最低限ですが)整備できました!