vm2gol v2 製作メモ(5) 条件分岐(compare, jump_eq)



チンタラやっててなかなか進みませんが一歩ずつやっていきます。

ループができるようになったら、次は条件分岐ですよ! プログラムといえば条件分岐!

条件分岐はどうやるかというと…… comparejump_eq という命令を導入します。 こいつを使って前回のカウンタを改造して、 値が 3 になったら 0 に戻ってまた 1 ずつ増えるというカウンタを作りましょう。

BASIC 風に書くとこうでしょうか:

10 set_reg_c 1  # これは最初に1回だけ実行される
20 set_reg_b 3  # これは最初に1回だけ実行される
30 set_reg_a 0
40 add_ac       # a + c の結果を a にセット
50 compare      # reg_a と reg_b を比較してフラグをセットする
60 jump_eq 30   # reg_a と reg_b が等しかったら、30番地に戻って reg_a の値を 0 に戻す
                # そうでなければ何もせず次に進む
70 jump 40      # reg_a と reg_b が等しくない場合

機械語コードに翻訳します。

# 05_compare_jump_eq.vge.yaml

[
  # 0
  "set_reg_c", 1,
  # 2
  "set_reg_b", 3,
  # 4
  "set_reg_a", 0,
  # 6
  "add_ac",
  "compare",
  "jump_eq", 4,
  "jump", 6
]

まず、レジスタreg_a, reg_b の2つだけだとめんどくさくなりそうな気がしたので、 reg_c を使うようにしてみました。

reg_a に足すべき値である 1 を reg_c にセットしておき、 add_acreg_c の値を reg_a に足します。

set_reg_cadd_ac はまだないので追加します。 set_reg_aadd_ab とほとんど同じです。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -35,6 +35,10 @@ class Vm
         n = @mem[@pc + 1]
         @reg_b = n
         @pc += 2
+      when "set_reg_c"
+        n = @mem[@pc + 1]
+        @reg_c = n
+        @pc += 2
       when "add_ab"
         add_ab()
         @pc += 1
--- a/vgvm.rb
+++ b/vgvm.rb
@@ -42,6 +42,9 @@ class Vm
       when "add_ab"
         add_ab()
         @pc += 1
+      when "add_ac"
+        add_ac()
+        @pc += 1
       when "jump"
         addr = @mem[@pc + 1]
         @pc = addr
@@ -74,6 +77,10 @@ class Vm
   def add_ab
     @reg_a = @reg_a + @reg_b
   end
+
+  def add_ac
+    @reg_a = @reg_a + @reg_c
+  end
 end
 
 exe_file = ARGV[0]

add_abadd_ac も 1行しかないのでメソッドにしなくてよかったかもしれませんね…… まあ、いちいち直していくのも煩雑なのでこのまま進めます)


準備ができたところで今回の目玉である comparejump_eq の登場です!

compare の動作はこう:

reg_a と reg_b の値を比較し、
  等しければ:     ゼロフラグに 1 をセット
  等しくなければ: ゼロフラグに 0 をセット

jump_eq の動作はこう:

ゼロフラグの値が
  0 だったら: 何もせず次の命令に進む(プログラムカウンタを素直に進める)
  1 だったら: 引数で指定されたアドレスにジャンプする

比較とジャンプの 2ステップに別れるんですね。ふむふむー。

で、この 2つのステップを仲介しているのがゼロフラグ。 ゼロフラグもレジスタの一種で、これも今回追加します。


まずゼロフラグ。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -12,6 +12,9 @@ class Vm
     @reg_b = 0
     @reg_c = 0
 
+    # flag
+    @zf = 0
+
     @mem = []
   end

comparejump_eq を追加。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -48,9 +48,15 @@ class Vm
       when "add_ac"
         add_ac()
         @pc += 1
+      when "compare"
+        compare()
+        @pc += 1
       when "jump"
         addr = @mem[@pc + 1]
         @pc = addr
+      when "jump_eq"
+        addr = @mem[@pc + 1]
+        jump_eq(addr)
       else
         raise "Unknown operator (#{op})"
       end
@@ -84,6 +90,18 @@ class Vm
   def add_ac
     @reg_a = @reg_a + @reg_c
   end
+
+  def compare
+    @zf = (@reg_a == @reg_b) ? 1 : 0
+  end
+
+  def jump_eq(addr)
+    if @zf == 1
+      @pc = addr
+    else
+      @pc += 2
+    end
+  end
 end
 
 exe_file = ARGV[0]

ステップ数が増えて、今のままだとダンプ出力が長くなってしまいます。 ここに長々としたのを貼るのもなんなので、 dump というメソッドを作ってダンプ出力を見やすくしました。

--- a/vgvm.rb
+++ b/vgvm.rb
@@ -23,6 +23,8 @@ class Vm
   end
 
   def start
+    dump() # 初期状態
+
     loop do
       # operator
       op = @mem[@pc]
@@ -62,11 +64,19 @@ class Vm
       end
 
       # 1命令実行するごとにダンプしてちょっと待つ
-      pp self
+      dump()
       sleep 1
     end
   end
 
+  def dump
+    puts "pc(%2d) | reg_a(%d) b(%d) c(%d) | zf(%d)" % [
+      @pc,
+      @reg_a, @reg_b, @reg_c,
+      @zf
+    ]
+  end
+
   def set_mem(addr, n)
     @mem[addr] = n
   end
@@ -108,6 +118,5 @@ exe_file = ARGV[0]
 
 vm = Vm.new
 vm.load_program(exe_file)
-pp vm # 初期状態
 
 vm.start

結果です! いい感じですね!

$ ruby vgvm.rb 05_compare_jump_eq.vge.yaml 
pc( 0) | reg_a(0) b(0) c(0) | zf(0)
pc( 2) | reg_a(0) b(0) c(1) | zf(0)
pc( 4) | reg_a(0) b(3) c(1) | zf(0)
pc( 6) | reg_a(0) b(3) c(1) | zf(0)
pc( 7) | reg_a(1) b(3) c(1) | zf(0)
pc( 8) | reg_a(1) b(3) c(1) | zf(0)
pc(10) | reg_a(1) b(3) c(1) | zf(0)
pc( 6) | reg_a(1) b(3) c(1) | zf(0)
pc( 7) | reg_a(2) b(3) c(1) | zf(0)
pc( 8) | reg_a(2) b(3) c(1) | zf(0)
pc(10) | reg_a(2) b(3) c(1) | zf(0)
pc( 6) | reg_a(2) b(3) c(1) | zf(0)
pc( 7) | reg_a(3) b(3) c(1) | zf(0)
pc( 8) | reg_a(3) b(3) c(1) | zf(1) … ゼロフラグが立った!
pc( 4) | reg_a(3) b(3) c(1) | zf(1) … 4番地にジャンプしている!
pc( 6) | reg_a(0) b(3) c(1) | zf(1) … reg_a が 0 にリセットされた!
pc( 7) | reg_a(1) b(3) c(1) | zf(1) … ↓ 以下くりかえしている!
pc( 8) | reg_a(1) b(3) c(1) | zf(0)
pc(10) | reg_a(1) b(3) c(1) | zf(0)
pc( 6) | reg_a(1) b(3) c(1) | zf(0)
pc( 7) | reg_a(2) b(3) c(1) | zf(0)
pc( 8) | reg_a(2) b(3) c(1) | zf(0)
pc(10) | reg_a(2) b(3) c(1) | zf(0)
pc( 6) | reg_a(2) b(3) c(1) | zf(0)
pc( 7) | reg_a(3) b(3) c(1) | zf(0)
pc( 8) | reg_a(3) b(3) c(1) | zf(1)
pc( 4) | reg_a(3) b(3) c(1) | zf(1)
pc( 6) | reg_a(0) b(3) c(1) | zf(1)
pc( 7) | reg_a(1) b(3) c(1) | zf(1)
(略)

同じ項目が縦に並んで、変化が見やすくなりました。