- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
道具が用意できたので引数渡しをやりたい! のですが、今回はリファクタリング回にします。
前回やっつけで追加した copy_sp_to_bp
と copy_bp_to_sp
を整理して、
せっかくのリファクタリング回なので他の部分もあわせて整理しておきます。
まず、
case arg when "bp" @bp else raise "push: not yet implemented (#{arg})" end
のように、 case 式で分岐させ、 else で「その値に関する処理はまだ実装してないよ」と例外を投げている箇所が ありますが、以後も出てくるので共通化しておきます。
これは別ファイル common.rb
に抽出します。
# common.rb def p_e(*args) args.each{ |arg| $stderr.puts arg.inspect } end def pp_e(*args) args.each{ |arg| $stderr.puts arg.pretty_inspect } end def not_yet_impl(*args) "Not yet implemented" + args .map{ |arg| " (#{ arg.inspect })" } .join("") end
ついでにデバッグ用の p_e
, pp_e
というべんりメソッドも足しておきました。
not_yet_impl()
の中で例外を投げるとこまでやってしまおうかとも
考えましたが、
なるべく単機能(例外用のメッセージを組み立てるだけ)になっている方がよいかな?
とか、
呼び出し元を読んだときにraise していることが分かった方が明示的でよいかな?
などと考えて控えめにしておきました。
呼び出し元を修正します。
--- a/vgvm.rb +++ b/vgvm.rb @@ -2,6 +2,8 @@ require 'pp' require 'yaml' +require './common' + module TermColor RESET = "\e[m" RED = "\e[0;31m" @@ -174,7 +176,7 @@ class Vm when "bp" @bp else - raise "push: not yet implemented (#{arg})" + raise not_yet_impl("push", arg) end @sp -= 1 @mem.stack[@sp] = val_to_push @@ -185,7 +187,7 @@ class Vm when "bp" @bp = @mem.stack[@sp] else - raise "pop: not yet implemented (#{arg})" + raise not_yet_impl("pop", arg) end @sp += 1 @pc += 2
次に、値のコピーまわりを整理します。
前回はとにかく早く動かしたかったので bp, sp 間のコピー用に専用の命令を追加してしまいましたが、 コピー用の命令は今後もいろいろ使うので、 この段階で汎用化しておきます。
一般的な(?)アセンブリ言語では こういうとき mov(move)という命令を使うようです。 ただし、自分が見聞きした範囲でも
という意見がちらほらあるようで(どのくらいポピュラーかは分かりませんが……)、個人的にも微妙に感じたので、 それならいっそのこと cp (copy) にしてはどうか、となりました。
MOV命令の元になった英語は「move」で、移動という意味なのですが、 代入って移動とは似ているけどちょっと違いますよね。
(中略)
すなおにコピー命令とかだったら説明が簡単だったのになあ。 どうしてMOV命令ということになったのかは、筆者にも分かりません。
(30日でできる! OS自作入門 p32)
オペランドの順番問題については、 これは UNIX の cp コマンドと同じインターフェイスにしてみます。 つまり、コピー元、コピー先、の順番です。 分かりやすいですね? 自分にとって分かりやすいのでこれでいいということにします。 よく知られた既存のインターフェイスに合わせることで覚える手間をなくそう、余分な負荷を削ろうという工夫のつもりでもあります。
どうせ自分向けに作っているオレオレのものなので、
こういうのは「自分が覚えやすいかどうか」を基準にして決めてしまえばよい、と割り切っています(jump_eq
のような他の命令の名前なんかもそういう基準で適当に決めています)。
適当に決めたので適当に修正しましょう。
まず cp
命令を追加。
--- a/vgvm.rb +++ b/vgvm.rb @@ -143,6 +143,12 @@ class Vm when "copy_sp_to_bp" @bp = @sp @pc += 1 + when "cp" + copy( + @mem.main[@pc + 1], + @mem.main[@pc + 2] + ) + @pc += 3 when "add_ab" add_ab() @pc += 1 @@ -200,8 +206,31 @@ class Vm end end + def copy(arg1, arg2) + src_val = + case arg1 + when "sp" + @sp + when "bp" + @bp + else + raise not_yet_impl("copy src", arg1) + end + + case arg2 + when "bp" + @bp = src_val + when "sp" + @sp = src_val + else + raise not_yet_impl("copy dest", arg2) + end + end + def self.num_args_for(operator) case operator + when "cp" + 2 when "set_reg_a", "set_reg_b", "label", "call", "push", "pop" 1 when "ret", "exit", "copy_bp_to_sp", "copy_sp_to_bp"
copy_bp_to_sp
と copy_sp_to_bp
はもう使わないので消しておきます。
--- a/vgvm.rb +++ b/vgvm.rb @@ -137,12 +137,6 @@ class Vm n = @mem.main[@pc + 1] @reg_c = n @pc += 2 - when "copy_bp_to_sp" - @sp = @bp - @pc += 1 - when "copy_sp_to_bp" - @bp = @sp - @pc += 1 when "cp" copy( @mem.main[@pc + 1], @@ -233,7 +227,7 @@ class Vm 2 when "set_reg_a", "set_reg_b", "label", "call", "push", "pop" 1 - when "ret", "exit", "copy_bp_to_sp", "copy_sp_to_bp" + when "ret", "exit" 0 else raise "Invalid operator (#{operator})"
前回のアセンブリコードを修正。
--- a/11_bp_push_pop.vga.txt +++ b/11_bp_push_pop.vga.txt @@ -6,13 +6,13 @@ label sub # 前処理 push bp - copy_sp_to_bp + cp sp bp # サブルーチン本体の処理 set_reg_a 2 # 後片付け - copy_bp_to_sp + cp bp sp pop bp ret
修正後のアセンブリコードが
cp bp sp
となって、アセンブリのコードなんだかシェルスクリプトなんだか よく分からない感じになって楽しくなりましたね(?)。
そういえば特に何も言わずにやってますが、 VM の命令名とアセンブリのニーモニックが一致しているので こういう場合もアセンブラは修正なしです。
つくづく影の薄いアセンブラ……。
あと気になっていたのが、命令ごとのオペランド(引数)の数に関する知識が
vgvm.rb
のコード中に散らばっていることです。
せっかく Vm.num_args_for
がすでにあるので、これを使いまわしましょう。
(今のうちに対処しておかないとこの先辛いってわけではなく、 とりあえず DRY にしときましょうか、という程度のノリです。)
@@ -118,6 +118,9 @@ class Vm loop do # operator op = @mem.main[@pc] + + pc_delta = 1 + Vm.num_args_for(op) + case op when "exit" $stderr.puts "exit" @@ -125,32 +128,32 @@ class Vm when "set_reg_a" n = @mem.main[@pc + 1] set_reg_a(n) - @pc += 2 + @pc += pc_delta when "set_reg_b" n = @mem.main[@pc + 1] set_reg_b(n) - @pc += 2 + @pc += pc_delta when "set_reg_c" n = @mem.main[@pc + 1] set_reg_c(n) 以下同様
修正した 11_bp_push_pop.vga.txt
を実行してみます。
$ ./run.sh 11_bp_push_pop.vga.txt (略) ================================ reg_a(2) reg_b(3) reg_c(0) zf(0) ---- memory (main) ---- 00 ["set_reg_a", 1] 02 ["call", 9] 04 ["set_reg_b", 3] pc => 06 ["exit"] 07 ["label", "sub"] 09 ["push", "bp"] 11 ["cp", "sp", "bp"] 14 ["set_reg_a", 2] 16 ["cp", "bp", "sp"] 19 ["pop", "bp"] 21 ["ret"] ---- memory (stack) ---- 0 0 1 3 2 4 sp bp => 3 0 exit $
問題ないですね! 次回は引数渡しに戻ります!!!!