vm2gol v2 (49) codegen_case を移植しやすいように変更



まずはしょぼいミスを修正……。

--- a/vgparser.rb
+++ b/vgparser.rb
@@ -478,7 +478,7 @@ if $PROGRAM_NAME == __FILE__
 
   begin
     tree = parser.parse()
-  rescue ParseError => e
+  rescue Parser::ParseError => e
     parser.dump_state()
     raise e
   end

codegen_case の変更

C言語に移植しているところなのですが、 生成したアセンブリコードを alines に溜めて受け渡していくのをめんどくさがって printf() でその都度出力するスタイルで書き進めていたら codegen_case() の実装でひっかかりました。

Cのコーディングをがんばる方向も考えましたが、 そもそも then_bodies が文字列の配列の配列となる時点で 若干複雑で、もうちょいどうにかならんかなという気がします。 それに、 then_bodies なんてものが出てくる時点で ここだけちょっと例外的なんですよね(気にはなっていた)。

というわけで、移植元の Ruby版の方から変えて、移植しやすくすることにしました。

case の動作としては等価で、 出力されるアセンブリコード(と機械語コード)の表現が変わる形です。


これはアセンブリコードで変更を見た方が分かりやすいと思います。

# 変更前:

  {条件式1の評価}
  compare
  jump_eq then_1

  {条件式2の評価}
  compare
  jump_eq then_2

label then_1
  {then_1 の場合の処理}
  jump end_case

label then_2
  {then_2 の場合の処理}
  jump end_case

label end_case

これまでは、このように各条件式が真になった場合のコードを then_bodies に溜めておいて、 後ろの方でまとめて出力していました。

まあ急いで作っていてその時パッと思いついたのをそのまま書いたというものです。 深い理由があってこのようになっているわけではありません。 変えてもいいでしょう。

で、どうするか。 jump_neq (比較結果が偽の場合にジャンプする)という VM 命令を追加して、 次のようなアセンブリコードを出力するのはどうかと考えました

# 案1(没):

  {条件式1の評価}
  compare
  jump_neq end_when_1
  {条件式1が真の場合の処理}
  jump end_case            # 最後までスキップ
label end_when_1           # 偽だった場合ここにジャンプ
                           # → 次の式の評価へ進む
  {条件式2の評価}
  compare
  jump_neq end_when_2
  {条件式2が真の場合の処理}
  jump end_case
label end_when_2

label end_case

すっきりしてていい感じなのですが、 VMアセンブラまで修正が必要になるのと、 今回のこれだけのために命令を追加するのも大げさ (せっかく命令を追加してもここだけでしか使われない)かなと思って、 結局 jump_eq でなんとかする次の方式にしました。

# 案2(採用):

  {条件式1の評価}
  compare
  jump_eq when_1
  jump end_when_1
label when_1               # 真だった場合ここにジャンプ
  {条件式1が真の場合の処理}
  jump end_case            # 最後までスキップ
label end_when_1           # 偽だった場合ここにジャンプ
                           # → 次の式の評価へ進む
  {条件式2の評価}
  compare
  jump_eq when_2
  jump end_when_2
label when_2
  {条件式2が真の場合の処理}
  jump end_case
label end_when_2

label end_case

というわけで差分がこう。 then_bodies に加えて then_alines も不要になりました。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -36,10 +36,10 @@ def codegen_case(fn_arg_names, lvar_names, when_blocks)
   label_id = $label_id
 
   when_idx = -1
-  then_bodies = []
 
   label_end = "end_case_#{label_id}"
   label_when_head = "when_#{label_id}"
+  label_end_when_head = "end_when_#{label_id}"
 
   when_blocks.each do |when_block|
     when_idx += 1
@@ -56,24 +56,24 @@ def codegen_case(fn_arg_names, lvar_names, when_blocks)
       alines << "  set_reg_b 1"
 
       alines << "  compare"
-      alines << "  jump_eq #{label_when_head}_#{when_idx}"
+      alines << "  jump_eq #{label_when_head}_#{when_idx}"  # 真の場合
+      alines << "  jump #{label_end_when_head}_#{when_idx}" # 偽の場合
+
+      # 真の場合ここにジャンプ
+      alines << "label #{label_when_head}_#{when_idx}"
+
+      alines += codegen_stmts(fn_arg_names, lvar_names, rest)
+
+      alines << "  jump #{label_end}"
+
+      # 偽の場合ここにジャンプ
+      alines << "label #{label_end_when_head}_#{when_idx}"
 
-      then_alines = ["label #{label_when_head}_#{when_idx}"]
-      then_alines += codegen_stmts(fn_arg_names, lvar_names, rest)
-      then_alines << "  jump #{label_end}"
-      then_bodies << then_alines
     else
       raise not_yet_impl("cond_head", cond_head)
     end
   end
 
-  # すべての条件が偽だった場合
-  alines << "  jump #{label_end}"
-
-  then_bodies.each {|then_alines|
-    alines += then_alines
-  }
-
   alines << "label #{label_end}"
 
   alines

出力されるアセンブリコードがちょっとだけ複雑になり、 一方でコード生成器の複雑さは少し減りました。 悪くはないんじゃないかなと思います。 少なくとも移植はしやすくなりました。