vm2gol v2 製作メモ(13) サブルーチンに引数を1個渡す / add_sp



前々回と前回でいろいろ片付いたので引数やります!

引数はスタックに置いて渡すそうです!

まず引数を1個だけ使うのをやってみます。 次のようにサブルーチン呼び出し(call)の直前でスタックに push します。

(※間違いがあります。後述)

サブルーチン呼び出し前:

sp bp 2
      3

引数を1個 push: push {引数}

sp    1 {引数}
   bp 2
      3

サブルーチン呼び出し: push bp

sp    0 2
      1 {引数}
   bp 2
      3

サブルーチン呼び出し: cp sp bp

sp bp 0 2
      1 {引数}
      2
      3

で、サブルーチン内でこの引数にアクセスする場合は bp + 1 の位置を見ればよい(※間違いがあります。後述)と。

ふむふむ。やってみましょう。 例として、サブルーチンの中で引数1 を reg_a にセットするだけ、 というのをやってみます。

# 13_one_arg.vga.txt

  push 11 # 引数1
  call sub
  exit

label sub
  push bp
  cp sp bp

  # サブルーチンの処理本体
  cp [bp+1] reg_a

  cp bp sp
  pop bp
  ret

いろいろ片付いたはずでしたが実際動かすとまだ足りないところがあります。 まず最初の push ができない。

vgvm.rb:182:in `block in start': Not yet implemented ("push") (11) (RuntimeError)

オペランドとして即値を受け取れていませんでした。対応させます。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -176,6 +176,8 @@ class Vm
         arg = @mem.main[@pc + 1]
         val_to_push =
           case arg
+          when Integer
+            arg
           when "bp"
             @bp
           else

次は……

vgvm.rb:216:in `copy': Not yet implemented ("copy src") ("[bp+1]") (RuntimeError)

うん、そんな気はしました。 cp のコピー元のとこを修正します。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -212,6 +212,8 @@ class Vm
         @sp
       when "bp"
         @bp
+      when /^\[bp\+(\d+)\]$/
+        @mem.stack[@bp + $1.to_i]
       else
         raise not_yet_impl("copy src", arg1)
       end

@bp + $1.to_i を返すのではなく、 「スタックの @bp + $1.to_i の位置にある値」を返したいので @mem.stack[@bp + $1.to_i] となります。

正規表現が若干ハードコーディング気味ですが動いたので気にせず進みます。 YAGNI です。

お次はこう:

vgvm.rb:227:in `copy': Not yet implemented ("copy dest") ("reg_a") (RuntimeError)

cp のコピー先を修正!

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -219,6 +219,8 @@ class Vm
       end
 
     case arg2
+    when "reg_a"
+      @reg_a = src_val
     when "bp"
       @bp = src_val
     when "sp"

エラーが出なくなりましたが、動きがおかしいですね。

(終了時の状態)

================================
reg_a(4) reg_b(0) reg_c(0) zf(0)
---- memory (main) ----
      00   ["push", 11]
      02   ["call", 7]
pc => 04   ["exit"]
      05 ["label", "sub"]
      07   ["push", "bp"]
      09   ["cp", "sp", "bp"]
      12   ["cp", "[bp+1]", "reg_a"]
      15   ["cp", "bp", "sp"]
      18   ["pop", "bp"]
      20   ["ret"]
---- memory (stack) ----
         0 3
         1 4
sp    => 2 11
   bp => 3 0

exit

引数として指定した 11 が reg_a にセットされてほしいのに、 4 がセットされています。

(しばし調べる)

分かりました。 サブルーチン呼び出しの際には引数だけじゃなくて 戻り先アドレスもスタックに積んでいたのでした。

0 3
1 4  ← 戻り先アドレス
2 11 ← 引数
3 0

なので、[bp+1] じゃなくて [bp+2] としないといけないのでした。

--- a/13_one_arg.vga.txt
+++ b/13_one_arg.vga.txt
@@ -7,7 +7,7 @@ label sub
   cp sp bp
 
   # サブルーチンの処理本体
-  cp [bp+1] reg_a
+  cp [bp+2] reg_a
 
   cp bp sp
   pop bp

アセンブリコードを修正して動かすと、終了時の状態がこうなります。

================================
reg_a(11) reg_b(0) reg_c(0) zf(0)
---- memory (main) ----
      00   ["push", 11]
      02   ["call", 7]
pc => 04   ["exit"]
      05 ["label", "sub"]
      07   ["push", "bp"]
      09   ["cp", "sp", "bp"]
      12   ["cp", "[bp+2]", "reg_a"]
      15   ["cp", "bp", "sp"]
      18   ["pop", "bp"]
      20   ["ret"]
---- memory (stack) ----
         0 3
         1 4
sp    => 2 11
   bp => 3 0

exit

こんどは reg_a にちゃんと 11 がセットされました! されましたが!!

spbp の位置がずれてますね……。 これはずれていてはいけない (サブルーチン呼び出し前と同じ状態になっていないといけない)はずです。

呼び出し規約は CDECL を参考にすることにしたので、ちょっと調べてみると、 CDECL では ret で呼び出し元に戻った後に sp の位置を戻すようで、 sp に 1 を足すためにまた専用命令である add_sp をシュッと追加します。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -152,6 +152,9 @@ class Vm
       when "add_ac"
         add_ac()
         @pc += pc_delta
+      when "add_sp"
+        @sp += @mem.main[@pc + 1]
+        @pc += pc_delta
       when "compare"
         compare()
         @pc += pc_delta
@@ -234,7 +237,7 @@ class Vm
     case operator
     when "cp"
       2
-    when "set_reg_a", "set_reg_b", "label", "call", "push", "pop"
+    when "set_reg_a", "set_reg_b", "label", "call", "push", "pop", "add_sp"
       1
     when "ret", "exit"
       0

アセンブリコードの方にも add_sp を追加。

--- a/13_one_arg.vga.txt
+++ b/13_one_arg.vga.txt
@@ -1,5 +1,6 @@
   push 11 # 引数1
   call sub
+  add_sp 1
   exit
 
 label sub

またまた動かすとこんどは……

================================
reg_a(11) reg_b(0) reg_c(0) zf(0)
---- memory (main) ----
      00   ["push", 11]
      02   ["call", 9]
      04   ["add_sp", 1]
pc => 06   ["exit"]
      07 ["label", "sub"]
      09   ["push", "bp"]
      11   ["cp", "sp", "bp"]
      14   ["cp", "[bp+2]", "reg_a"]
      17   ["cp", "bp", "sp"]
      20   ["pop", "bp"]
      22   ["ret"]
---- memory (stack) ----
         0 3
         1 4
         2 11
sp bp => 3 0

exit

sp が元の位置に戻りました! めでたしめでたし!!

修正後のアセンブリコードも改めて貼っておきます。

# 13_one_arg.vga.txt

  push 11 # 引数1
  call sub
  add_sp 1
  exit

label sub
  push bp
  cp sp bp

  # サブルーチンの処理本体
  cp [bp+2] reg_a

  cp bp sp
  pop bp
  ret