gcc - yyparse (printf debug with dump-tree)

init は飛ばして(@@@) 字句解析もつまらんから飛ばして (@@@) 、構文解析を見ていこうと思う。(*lang_hooks.parse_file) (set_yydebug); と呼ばれているフックの先は c_common_parse_file のようだ。 pch_init なんてのがあってさすが 3.4 と思った。 c_parse_file ではすぐに yyparse に入ってしまう。

yyparse は c-parse.y にあるのだが、少しびっくりすることにこのファイルは c-parse.in から生成されている。 C と Objective-C のパーサを切り分けるためのようだ。まあつまるところ生成された c-parse.y を読めば良いと思う。さて、 yacc のソースは書く分には良いのですけど処理が追いにくいのがやっかいだ。とはいえとても重要な場所だと思うので、簡単なソースを書いてそれに対する挙動を逐一追っていって理解を深めようと思う。

とりあえずとても簡単な例から考える。

/* main.c */
int main() {
  return 0;
}

これに対してどういう手順で木構造が生成されているかを調べる。逐一追う方法としては printf を埋め込んでやることにする。埋める場所のあたりを付けるために c-parse.in を読むと、 fndef は今回の場合、

	  declspecs_ts setspecs declarator
		{ if (! start_function (current_declspecs, $3,
					all_prefix_attributes))
		    YYERROR1;
		}
	  old_style_parm_decls save_location
		{ DECL_SOURCE_LOCATION (current_function_decl) = $6;
		  store_parm_decls (); }
	  compstmt_or_error
		{ finish_function ();
		  POP_DECLSPEC_STACK; }

がマッチしそうだ。というわけで start_function 内に渡した引数をダンプするものと、 compstmt_or_error で関数の実装をダンプさせてやれば良いだろう。

普段から tree をダンプしたりすると gcc のビルド時に動く xgcc などでもダンプされて五月蝿いので、 -v オプションが付いている時のみダンプする。そのために c-opts.c の bool verbose の static をはずし、 c-decl.c の冒頭で、 extern bool verbose; を書き、 c-decl.c の start_function 内に、

  if (verbose) {
    FILE* fp = fopen("log", "a");
    fprintf(fp, "start_function = {\n");
    fprintf(fp, " declspecs =\n");
    dump_node(declspecs, 0, fp);
    fprintf(fp, " declarator =\n");
    dump_node(declarator, 0, fp);
    fprintf(fp, " attributes =\n");
    if (attributes) dump_node(attributes, 0, fp);
    fprintf(fp, "}\n");
    fclose(fp);
  }

を埋め、 c-parse.in の冒頭に同じく extern bool verbose; を書き、 compstmt: の中に、

          if (verbose) {
            FILE* fp = fopen("log", "a");
            fprintf(fp, "compstmt =\n");
            dump_node($1, 0, fp);
            fclose(fp);
          }

を埋める。これで関数宣言と関数の定義の節の tree がダンプされるはず。早速 tree-dump.c を読んでおいた効果があらわれるわけ。

で、 main.c をダンプすると、

start_function = {
 declspecs =
@1      tree_list        valu: @2
@2      identifier_node  strg: int      lngt: 3
 declarator =
@1      call_expr        fn  : @2       args: @3
@2      identifier_node  strg: main     lngt: 4
@3      tree_list
 attributes =
}
compstmt =
@1      compound_stmt    line: 1        next: @2
@2      scope_stmt       line: 2        begn           clnp
                         next: @3
@3      return_stmt      line: 2        expr: @4       next: @5
@4      modify_expr      type: @6       op 0: @7       op 1: @8
@5      scope_stmt       line: 3        end            clnp
@6      integer_type     name: @9       size: @10      algn: 32
                         prec: 32       min : @11      max : @12
@7      result_decl      type: @6       srcp: main.c:1
                         size: @10      algn: 32
@8      integer_cst      type: @6       low : 0

この後もだらだらと続くのだが、これ以上先は srcp: :0 などと書いてあって、 main.c の内容とは深く関係無いだろう。この :0 についてはまた後で調べる必要があると思われる(@@@)。

しかし dump-tree の出力はわかりやすい。きちんとそれらしいものができていることがわかる。後で気付いたことだが、このダンプコードを埋め込んだおかげで理解が大幅に容易になった。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h