- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
分岐ができたので次はループをやりたいですね。 簡単なところで、数を1ずつ増やしていくカウンタとか。
["var", "a"] , ["set", "a", 0] , ["while" , ["eq", 0, 0] // 条件: 必ず真になるようにして無限ループさせる , [ // ループの本体 ["set", "a", {a+1}] // 変数 a をインクリメント ] ]
これをやりたいんですが、足し算がまだできないので、 先に組み込みの足し算機能を作ります。
足し算
こんな構文を用意して……
["+", 1, 2]
と思ったのですが、実際に使うときは式単体ではなく 式を評価した結果をローカル変数に入れて使うでしょうから、 set文を改造して、
["set", {ローカル変数}, ["+", 1, 2]]
という構文で書けるようにします。
vgtコードです。
// 22_set_add.vgt.json ["stmts" , ["func", "main" , [] , [ ["var", "a"] , ["set", "a", ["+", 1, 2]] ] ] ]
こういうアセンブリコードになってほしい(set文の部分のみ):
set_reg_a 1 set_reg_b 2 add_ab # 結果が reg_a に入る cp reg_a [bp-1] # ローカル変数にセット
修正しましょう。
codegen_func_def()
の case 文が長くなってきたので、
まずは set文の部分をメソッドに抽出します。
動作は変わらず、単純なリファクタリングです。
--- a/vgcg.rb +++ b/vgcg.rb @@ -52,6 +52,27 @@ def codegen_case(when_blocks) alines end +def codegen_set(fn_arg_names, lvar_names, rest) + alines = [] + lvar_name = rest[0] + + src_val = + case + when rest[1].is_a?(Integer) + rest[1] + when fn_arg_names.include?(rest[1]) + fn_arg_pos = fn_arg_names.index(rest[1]) + 2 + "[bp+#{fn_arg_pos}]" + else + raise not_yet_impl("set src_val", rest) + end + + lvar_pos = lvar_names.index(lvar_name) + 1 + alines << " cp #{src_val} [bp-#{lvar_pos}]" + + alines +end + def codegen_func_def(rest) alines = [] @@ -94,21 +115,7 @@ def codegen_func_def(rest) lvar_names << stmt_rest[0] alines << " sub_sp 1" when "set" - lvar_name = stmt_rest[0] - - 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}]" + alines += codegen_set(fn_arg_names, lvar_names, stmt_rest) when "return" val = stmt_rest[0] alines << " set_reg_a #{val}"
はい、では足し算とローカル変数へのセットをやりましょう。
--- a/vgcg.rb +++ b/vgcg.rb @@ -52,6 +52,25 @@ def codegen_case(when_blocks) alines end +def codegen_exp(exp) + alines = [] + operator, *args = exp + + left = args[0] + right = args[1] + + case operator + when "+" + alines << " set_reg_a #{left}" + alines << " set_reg_b #{right}" + alines << " add_ab" + else + raise not_yet_impl("operator", operator) + end + + alines +end + def codegen_set(fn_arg_names, lvar_names, rest) alines = [] lvar_name = rest[0] @@ -60,6 +79,10 @@ def codegen_set(fn_arg_names, lvar_names, rest) case when rest[1].is_a?(Integer) rest[1] + when rest[1].is_a?(Array) + exp = rest[1] + alines += codegen_exp(exp) + "reg_a" when fn_arg_names.include?(rest[1]) fn_arg_pos = fn_arg_names.index(rest[1]) + 2 "[bp+#{fn_arg_pos}]"
exp は expression(式)の略です。
set文の2つ目の引数が配列だった場合の分岐を追加して、
codegen_exp()
では ["+", 1, 2]
を評価した結果を
reg_a
に入れて codegen_set()
に戻る、という作りです。
この diff だと codegen_set()
がどうなったか
ちょっと分かりにくいかもしれないので全部貼っておきます。
def codegen_set(fn_arg_names, lvar_names, rest) alines = [] lvar_name = rest[0] src_val = case when rest[1].is_a?(Integer) rest[1] when rest[1].is_a?(Array) exp = rest[1] alines += codegen_exp(exp) "reg_a" when fn_arg_names.include?(rest[1]) fn_arg_pos = fn_arg_names.index(rest[1]) + 2 "[bp+#{fn_arg_pos}]" else raise not_yet_impl("set src_val", rest) end lvar_pos = lvar_names.index(lvar_name) + 1 alines << " cp #{src_val} [bp-#{lvar_pos}]" alines end
run.sh で実行して、足し算の結果がローカル変数 a
にセットされた直後の状態:
================================ reg_a(3) reg_b(2) 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 ["set_reg_a", 1] 14 ["set_reg_b", 2] 16 ["add_ab"] 17 ["cp", "reg_a", "[bp-1]"] pc => 20 ["cp", "bp", "sp"] 23 ["pop", "bp"] 25 ["ret"] ---- memory (stack) ---- 38 0 39 0 40 0 41 0 42 0 43 0 44 0 45 0 sp => 46 3 ... ローカル変数 a bp => 47 49 48 2 49 0
よしよし。
ローカル変数のインクリメント
即値どうしの足し算ができるようになりましたが、 カウンタで必要なのはローカル変数のインクリメントです。 ローカル変数と即値の足し算にも対応させましょう。
vgtコード:
// 22_lvar_increment.vgt.json ["stmts" , ["func", "main" , [] , [ ["var", "a"] , ["set", "a", 12] , ["set", "a", ["+", "a", 1]] ] ] ]
ちなみに現時点では次のようになってしまってダメです。
reg_a
に "a"
という文字列が入ってしまい、
文字列と数を足し算しようとしてエラーになります。
================================ reg_a("a") reg_b(1) 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 ["set_reg_a", "a"] 17 ["set_reg_b", 1] pc => 19 ["add_ab"] 20 ["cp", "reg_a", "[bp-1]"] 23 ["cp", "bp", "sp"] 26 ["pop", "bp"] 28 ["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 vgvm.rb:303:in `+': no implicit conversion of Fixnum into String (TypeError) from vgvm.rb:303:in `add_ab' from vgvm.rb:155:in `block in start' from vgvm.rb:126:in `loop' from vgvm.rb:126:in `start' from vgvm.rb:330:in `<main>'
修正します。 ローカル変数を解決しないといけないですね。 これは前にもやったので同じようにやればよいでしょう。
--- a/vgcg.rb +++ b/vgcg.rb @@ -52,11 +52,26 @@ def codegen_case(when_blocks) alines end -def codegen_exp(exp) +def codegen_exp(lvar_names, exp) alines = [] operator, *args = exp - left = args[0] + left = + case args[0] + when Integer + args[0] + when String + case + when lvar_names.include?(args[0]) + lvar_pos = lvar_names.index(args[0]) + 1 + "[bp-#{lvar_pos}]" + else + raise not_yet_impl("left", args[0]) + end + else + raise not_yet_impl("left", args[0]) + end + right = args[1] case operator @@ -81,7 +96,7 @@ def codegen_set(fn_arg_names, lvar_names, rest) rest[1] when rest[1].is_a?(Array) exp = rest[1] - alines += codegen_exp(exp) + alines += codegen_exp(lvar_names, exp) "reg_a" when fn_arg_names.include?(rest[1]) fn_arg_pos = fn_arg_names.index(rest[1]) + 2
動かしてみると……
================================ reg_a("[bp-1]") reg_b(1) 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 ["set_reg_a", "[bp-1]"] 17 ["set_reg_b", 1] pc => 19 ["add_ab"] 20 ["cp", "reg_a", "[bp-1]"] 23 ["cp", "bp", "sp"] 26 ["pop", "bp"] 28 ["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 vgvm.rb:303:in `+': no implicit conversion of Fixnum into String (TypeError) from vgvm.rb:303:in `add_ab' from vgvm.rb:155:in `block in start' from vgvm.rb:126:in `loop' from vgvm.rb:126:in `start' from vgvm.rb:330:in `<main>'
ダメですね。なんでしょう?
えーと……あ、今度は reg_a
に "[bp-1]"
という文字列が入っちゃってます。
コード生成時にローカル変数が解決されて [bp-1]
になり、
そのまま機械語コードになっているところまではOK。
ということはこれはコード生成器やアセンブラではなく VM の問題です。
set_reg_a
が即値にしか対応していなかったのが原因です。
オペランドが [bp-1]
となっていたら、
[bp-1]
が指しているスタックの実アドレスを reg_a
にセットしてほしい。
--- a/vgvm.rb +++ b/vgvm.rb @@ -134,8 +134,8 @@ class Vm $stderr.puts "exit" exit when "set_reg_a" - n = @mem.main[@pc + 1] - @reg_a = n + val = @mem.main[@pc + 1] + set_reg_a(val) @pc += pc_delta when "set_reg_b" n = @mem.main[@pc + 1] @@ -307,6 +307,19 @@ class Vm @reg_a = @reg_a + @reg_c end + def set_reg_a(val) + @reg_a = + case val + when Integer + val + when /^\[bp-(\d+)\]$/ + stack_addr = @bp - $1.to_i + @mem.stack[stack_addr] + else + raise not_yet_impl("val", val) + end + end + def compare @zf = (@reg_a == @reg_b) ? 1 : 0 end
これで動くようになりました。
================================ reg_a(13) reg_b(1) 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 ["set_reg_a", "[bp-1]"] 17 ["set_reg_b", 1] 19 ["add_ab"] 20 ["cp", "reg_a", "[bp-1]"] pc => 23 ["cp", "bp", "sp"] 26 ["pop", "bp"] 28 ["ret"] ---- memory (stack) ---- 38 0 39 0 40 0 41 0 42 0 43 0 44 0 45 0 sp => 46 13 bp => 47 49 48 2 49 0
12 + 1 が bp-1 の位置(ローカル変数 a
)にセットされてます!
カウンタを作るための準備ができたので次は while
を……
といきたいところですが、長くなってきたので次に回します!
今回はここまで!!