vm2gol v2 製作メモ(15) ローカル変数 / sub_sp



今回はローカル変数をやります!


これまでスタックをこんな風に使っていました。

ベースポインタ
戻り先アドレス
引数1
引数2

入れ子の呼び出しがある場合はこう:

ベースポインタ
戻り先アドレス
引数1
引数2
旧ベースポインタ
戻り先アドレス
引数1
引数2

ローカル変数もなんとここに突っ込むそうです。 どう突っ込むかというと、こう:

ローカル変数2
ローカル変数1
ベースポインタ
戻り先アドレス
引数1
引数2
ローカル変数2
ローカル変数1
旧ベースポインタ
戻り先アドレス
引数1
引数2

入れ子なしで見てみます。

      ローカル変数2 (bp-2)
      ローカル変数1 (bp-1)
bp => ベースポインタ
      戻り先アドレス
      引数1 (bp+2)
      引数2 (bp+3)

呼びだされた先のサブルーチンで ローカル変数1を宣言するとスタックポインタが -1 され、 その次に ローカル変数2を宣言するとスタックポインタがさらに -1 されます。

引数1, 2 を使うときは bp+2, bp+3 を見ていましたが、 ローカル変数はベースポインタの上の方を見て、 ローカル変数1, 2 はそれぞれ bp-1, bp-2 の位置を使って読み書きすればよいと。

こういう仕組みになっているそうです!   すごいですね!   すごいですね!   今「すごいですね!」って2回言いました。 私はすごいなー巧みな仕組みだなーっていうかこんなんでいいんだ!?   と関心して目から鱗でした。 サブルーチン呼び出し、引数渡し、ローカル変数を使うのに スタック1つで用が足りてしまうんですね!   この歳になるまで知りませんでしたよ!!!!

ローカル変数1個だけ

関心したところで実際に書いてみます。 一度に少しずつということで、引数と値の返却はなしで、 まずはローカル変数1個だけ。

# 15_local_var.vga.txt

  call sub
  exit

label sub
  push bp
  cp sp bp

  # サブルーチンの処理本体
  set_reg_a 11
  sub_sp 1         # ローカル変数1の宣言(領域確保)
  cp reg_a [bp-1]  # ローカル変数1に値をセット
  cp [bp-1] reg_b  # ローカル変数1の値を参照して reg_b にコピー

  cp bp sp
  pop bp
  ret

まだ sub_sp を実装していないので、動かすとこうなります。

$ ./run.sh 15_local_var.vga.txt 
vgvm.rb:252:in `num_args_for': Invalid operator (sub_sp) (RuntimeError)

sub_sp を追加します。

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

次はこうなります。

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

copy() を修正。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -219,6 +219,8 @@ class Vm
   def copy(arg1, arg2)
     src_val =
       case arg1
+      when "reg_a"
+        @reg_a
       when "sp"
         @sp
       when "bp"
vgvm.rb:244:in `copy': Not yet implemented ("copy dest") ("[bp-1]") (RuntimeError)

修正します!(ローカル変数へのコピー)

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -240,6 +240,8 @@ class Vm
       @bp = src_val
     when "sp"
       set_sp(src_val)
+    when /^\[bp-(\d+)\]$/
+      @mem.stack[@bp - $1.to_i] = src_val
     else
       raise not_yet_impl("copy dest", arg2)
     end
vgvm.rb:231:in `copy': Not yet implemented ("copy src") ("[bp-1]") (RuntimeError)

修正!!(ローカル変数からのコピー)

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

最後まで動きました!!!

================================
reg_a(11) reg_b(11) reg_c(0) zf(0)
---- memory (main) ----
      00   ["call", 5]
pc => 02   ["exit"]
      03 ["label", "sub"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["set_reg_a", 11]
      12   ["sub_sp", 1]
      14   ["cp", "reg_a", "[bp-1]"]
      17   ["cp", "[bp-1]", "reg_b"]
      20   ["cp", "bp", "sp"]
      23   ["pop", "bp"]
      25   ["ret"]
---- memory (stack) ----
         41 0
         42 0
         43 0
         44 0
         45 0
         46 11 ... ローカル変数1
         47 49
         48 2
sp bp => 49 0

exit

reg_a → ローカル変数1( [bp-1] ) → reg_b の順番で値がコピーされています。

ローカル変数2個

ローカル変数2個もやってみます。

# 15_local_vars.vga.txt

  call sub
  exit

label sub
  push bp
  cp sp bp

  # サブルーチンの処理本体
  set_reg_a 11
  sub_sp 1         # ローカル変数1の宣言(領域確保)
  cp reg_a [bp-1]  # ローカル変数1に値をセット
  cp [bp-1] reg_b  # ローカル変数1の値を参照して reg_b にコピー

  set_reg_a 12
  sub_sp 1         # ローカル変数2の宣言(領域確保)
  cp reg_a [bp-2]  # ローカル変数2に値をセット
  cp [bp-2] reg_b  # ローカル変数2の値を参照して reg_b にコピー

  cp bp sp
  pop bp
  ret

2個でも大丈夫でした!

================================
reg_a(12) reg_b(12) reg_c(0) zf(0)
---- memory (main) ----
      00   ["call", 5]
pc => 02   ["exit"]
      03 ["label", "sub"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["set_reg_a", 11]
      12   ["sub_sp", 1]
      14   ["cp", "reg_a", "[bp-1]"]
      17   ["cp", "[bp-1]", "reg_b"]
      20   ["set_reg_a", 12]
      22   ["sub_sp", 1]
      24   ["cp", "reg_a", "[bp-2]"]
      27   ["cp", "[bp-2]", "reg_b"]
      30   ["cp", "bp", "sp"]
      33   ["pop", "bp"]
      35   ["ret"]
---- memory (stack) ----
         41 0
         42 0
         43 0
         44 0
         45 12 ... ローカル変数2
         46 11 ... ローカル変数1
         47 49
         48 2
sp bp => 49 0

exit

3個以上の場合も同じ要領でできそうですね。