- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
今回は関数の引数をやります。こんな感じで進めましょう。
- 引数を1個渡す
- 渡した引数をローカル変数に代入
- 引数を2個渡してローカル変数に代入
引数を1個渡す
これを動かします。
// 19_func_arg.vgt.json ["stmts" , ["func", "main" , [] , [ ["call", "fn_sub", 34] // 引数 34 を渡す ] ] , ["func", "fn_sub" , ["arg1"] // 引数を arg1 として受け取る , [ ] ] ]
call文を変更して、3個目以降の要素として引数を渡すようにしてみます。
["call", "{関数名}", 引数1, 引数2, ...]
アセンブリではサブルーチンに引数を渡すときどうやってたか、 ちょっとおさらい。
こんな感じでしたね。
- call の前に引数を逆順で push して、
- 呼び出し先のサブルーチンでは bp+N で参照する
- サブルーチンから戻ったときに
add_sp
でスタックポインタを戻す
ということは、引数が1個のときは次のようなアセンブリコードに変換されてほしい。
# 略 label main push 34 # 引数を push call fn_sub add_sp 1 # sp を調整 label fn_sub # bp+N で参照
call の前の push
と call の後の add_sp
を
追加で出力すればよさそうです。
まずはハードコーディングで。
--- a/vgcg.rb +++ b/vgcg.rb @@ -27,7 +27,9 @@ def codegen_func_def(rest) case stmt_head when "call" fn_name = stmt_rest[0] + alines << " push 34" alines << " call #{fn_name}" + alines << " add_sp 1" when "var" lvar_names << stmt_rest[0] alines << " sub_sp 1"
コンパイルすると期待するアセンブリコードが出力され、問題なさそうです。 単に2行追加されるだけですからね。
ではハードコーディングした部分を書き換えていきましょう。
push に渡すのは引数の内容なので、どうすればいいかというと……
["call", "fn_sub", 34] // 引数 34 を渡す
「3個目以降を引数とする」としたので、 call文の配列の 3つ目の要素を使えばいいですね。
add_sp
に渡す 1 は単に引数の個数を与えてやればいいので、
特に難しくなさそうです。
修正します。
--- a/vgcg.rb +++ b/vgcg.rb @@ -26,10 +26,10 @@ def codegen_func_def(rest) stmt_head, *stmt_rest = stmt case stmt_head when "call" - fn_name = stmt_rest[0] - alines << " push 34" + fn_name, *fn_args = stmt_rest + alines << " push #{fn_args[0]}" alines << " call #{fn_name}" - alines << " add_sp 1" + alines << " add_sp #{fn_args.size}" when "var" lvar_names << stmt_rest[0] alines << " sub_sp 1"
run.sh
で実行して確認。
================================ 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 ["push", 34] 12 ["call", 24] 14 ["add_sp", 1] 16 ["cp", "bp", "sp"] 19 ["pop", "bp"] 21 ["ret"] 22 ["label", "fn_sub"] 24 ["push", "bp"] 26 ["cp", "sp", "bp"] pc => 29 ["cp", "bp", "sp"] 32 ["pop", "bp"] 34 ["ret"] ---- memory (stack) ---- 36 0 37 0 38 0 39 0 40 0 41 0 42 0 43 0 sp bp => 44 47 45 14 46 34 ... bp+2 の位置に arg1 の値がセットされている 47 49 48 2 49 0
よしよし。
渡した引数をローカル変数に代入
渡した引数は使わないと意味がありません。 次は引数を参照してローカル変数に値をセットしてみましょう。
--- a/19_func_arg.vgt.json +++ b/19_func_arg.vgt.json @@ -10,6 +10,8 @@ , ["func", "fn_sub" , ["arg1"] // 引数を arg1 として受け取る , [ + ["var", "a"] + , ["set", "a", "arg1"] ] ]
とりあえず実行してみると……
$ ./run.sh 19_func_arg.vgt.json (略) ================================ 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 ["push", 34] 12 ["call", 24] 14 ["add_sp", 1] 16 ["cp", "bp", "sp"] 19 ["pop", "bp"] 21 ["ret"] 22 ["label", "fn_sub"] 24 ["push", "bp"] 26 ["cp", "sp", "bp"] 29 ["sub_sp", 1] pc => 31 ["cp", "arg1", "[bp-1]"] 34 ["cp", "bp", "sp"] 37 ["pop", "bp"] 39 ["ret"] ---- memory (stack) ---- 35 0 36 0 37 0 38 0 39 0 40 0 41 0 42 0 sp => 43 0 bp => 44 47 45 14 46 34 47 49 48 2 49 0 vgvm.rb:235:in `copy': Not yet implemented ("copy src") ("arg1") (RuntimeError) from vgvm.rb:149:in `block in start' from vgvm.rb:126:in `loop' from vgvm.rb:126:in `start' from vgvm.rb:330:in `<main>'
ふむ……。
現時点ではこのようなアセンブリコードが出力されています。
# 略 label fn_sub push bp cp sp bp # 関数の処理本体 sub_sp 1 cp arg1 [bp-1] … 問題の箇所 cp bp sp pop bp ret
VM(CPU)がこれを見て、 「コピー元が arg1 となってるけど arg1 ってなんやねん」 となってるわけですね。 ごもっとも。 VMちゃんにも分かる形にしてあげないと。
arg1
というのは関数呼び出しで渡された引数の名前なので、
これが [bp+2]
となるように変換できればよさそうです。
ローカル変数のときは
lvar_pos = lvar_names.index(lvar_name) + 1 alines << " cp #{src_val} [bp-#{lvar_pos}]"
のようにして [bp-N]
の形にしていましたが、
同じようなやり方でいけるんじゃないでしょうか。
--- a/vgcg.rb +++ b/vgcg.rb @@ -10,6 +10,7 @@ def codegen_func_def(rest) alines = [] fn_name = rest[0] + fn_arg_names = rest[1] body = rest[2] alines << "" @@ -35,7 +36,18 @@ def codegen_func_def(rest) alines << " sub_sp 1" when "set" lvar_name = stmt_rest[0] - val = stmt_rest[1] + + val = + case + when stmt_rest[1].is_a?(Integer) + stmt_rest[1] + when fn_arg_names.include?(stmt_rest[1]) + fn_arg_pos = fn_arg_names.index(stmt_rest[1]) + 2 + "[bp+#{fn_arg_pos}]" + else + raise not_yet_impl("set val", stmt_rest) + end + lvar_pos = lvar_names.index(lvar_name) + 1 alines << " cp #{val} [bp-#{lvar_pos}]" else
動かしてみます。
================================ 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 ["push", 34] 12 ["call", 24] 14 ["add_sp", 1] 16 ["cp", "bp", "sp"] 19 ["pop", "bp"] 21 ["ret"] 22 ["label", "fn_sub"] 24 ["push", "bp"] 26 ["cp", "sp", "bp"] 29 ["sub_sp", 1] 31 ["cp", "[bp+2]", "[bp-1]"] pc => 34 ["cp", "bp", "sp"] 37 ["pop", "bp"] 39 ["ret"] ---- memory (stack) ---- 35 0 36 0 37 0 38 0 39 0 40 0 41 0 42 0 sp => 43 34 ... [bp-1] ローカル変数 a bp => 44 47 45 14 46 34 ... [bp+2] 引数 arg1 47 49 48 2 49 0
["set", "a", "arg1"]
を実行した直後の状態です。
うまく動いてますね。
「実際のコンピュータでもメモリからメモリへのコピーってできるんだっけ?」 というとこが気になりますが、 ここはいったんできるということにして進めます。 あとで確認するかも。
なんとなれば、レジスタを経由させて
cp arg1 reg_a cp reg_a a
とすれば回避できそうなどと考えつつ、とにかく進めます。
引数を2個渡す
さて、引数の参照部分もクリアできたので、 2個以上の引数でも動くようにしましょう。
vgtコードはこう。
// 19_func_args.vgt.json ["stmts" , ["func", "main" , [] , [ ["call", "fn_sub", 34, 56] // 引数 34, 56 を渡す ] ] , ["func", "fn_sub" , ["arg1", "arg2"] // 引数を arg1, arg2 として受け取る , [ ["var", "a"] , ["set", "a", "arg1"] , ["var", "b"] , ["set", "b", "arg2"] ] ] ]
call文を変換している部分が今どうなっているかというと、
when "call" fn_name, *fn_args = stmt_rest alines << " push #{fn_args[0]}" alines << " call #{fn_name}" alines << " add_sp #{fn_args.size}"
こうなっていて、 fn_args[0]
だけを push しています。
ここを複数にしてやればよくて、ただし逆順に push する点に気をつける、と。
修正します。
--- a/vgcg.rb +++ b/vgcg.rb @@ -28,7 +28,9 @@ def codegen_func_def(rest) case stmt_head when "call" fn_name, *fn_args = stmt_rest - alines << " push #{fn_args[0]}" + fn_args.reverse.each {|fn_arg| + alines << " push #{fn_arg}" + } alines << " call #{fn_name}" alines << " add_sp #{fn_args.size}" when "var"
これだけですね。動かします。
================================ 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 ["push", 56] 12 ["push", 34] 14 ["call", 26] 16 ["add_sp", 2] 18 ["cp", "bp", "sp"] 21 ["pop", "bp"] 23 ["ret"] 24 ["label", "fn_sub"] 26 ["push", "bp"] 28 ["cp", "sp", "bp"] 31 ["sub_sp", 1] 33 ["cp", "[bp+2]", "[bp-1]"] 36 ["sub_sp", 1] 38 ["cp", "[bp+3]", "[bp-2]"] pc => 41 ["cp", "bp", "sp"] 44 ["pop", "bp"] 46 ["ret"] ---- memory (stack) ---- 33 0 34 0 35 0 36 0 37 0 38 0 39 0 40 0 sp => 41 56 ... b 42 34 ... a bp => 43 47 44 16 45 34 ... arg1 46 56 ... arg2 47 49 48 2 49 0
["set", "b", "arg2"]
を実行した直後。
いいですね!
というわけで今回は引数渡しと、呼び出し先での参照ができるようになりました!!