2008年11月30日日曜日

GCC 4 のデータ

GCC 4 について学ぶ

GCC は 30 のアーキテクチャに対応し 1500 万行もあるらしい。

GCC Internals, D. Novillo, 2007 International Symposium on Code Generation and Optimization (CGO), San Jose, California, March 2007.

diego novillo は 220 万 LOC だと書いている (SLOCCount というツールを使ったらしい)

どういう勘定をしたのだろうか。いくらなんでも 1500 万 LOC は多すぎる気がする。

最新の GCC 4.4-2008128 で数えてみる。
~/work/gcc/gcc-4.4-20081128$ find . -print0 | xargs -0 wc -l
668335 合計

まぁ、こんなものだと思う。snapshot 版だから中身が少ないのかもしれないけど。

試しに 4.3.2.tar.bz2 でもやってみた。
~/work/gcc/gcc-4.3.2$ find . -print0 | xargs -0 wc -l
235370 合計

う~ん。

2008年11月28日金曜日

コード挿入

デバッグやプロファイルやセキュリティチェックなどを目的として、任意のコードを仕込みたいという需要は多いと思う。

やり方としては、GIMPLE レベルで仕込む方法と、MD レベルで仕込む方法の、主に 2 種類ぐらいがあるようだ。

GIMPLE レベルで何かを仕込むのは、rtx に対する知識が必要だし、最適化の過程でコードの順番などが入れ替わる可能性もある。

MD レベルの方が、最適化の後だし、基本的には GCC のインラインアセンブラのような感じに記述できるので、比較的容易な気がする。

GCC の SSP (Stack Smashing Protector) は、セキュリティ用のコードを MD ファイルで仕込んでいるらしい。

ちょっと gcc-4.3.2/gcc/config/i386/i386.md を見てみる。
; SSP patterns
(UNSPEC_SP_SET 100)
(UNSPEC_SP_TEST 101)
(UNSPEC_SP_TLS_SET 102)
(UNSPEC_SP_TLS_TEST 103)
...

(define_expand "stack_protect_set"
[(match_operand 0 "memory_operand" "")
(match_operand 1 "memory_operand" "")]
""
{
#ifdef TARGET_THREAD_SSP_OFFSET
if (TARGET_64BIT)
emit_insn (gen_stack_tls_protect_set_di (operands[0],
GEN_INT (TARGET_THREAD_SSP_OFFSET)));
else
emit_insn (gen_stack_tls_protect_set_si (operands[0],
GEN_INT (TARGET_THREAD_SSP_OFFSET)));
#else
if (TARGET_64BIT)
emit_insn (gen_stack_protect_set_di (operands[0], operands[1]));
else
emit_insn (gen_stack_protect_set_si (operands[0], operands[1]));
#endif
DONE;
})

(define_insn "stack_protect_set_si"
[(set (match_operand:SI 0 "memory_operand" "=m")
(unspec:SI [(match_operand:SI 1 "memory_operand" "m")] UNSPEC_SP_SET))
(set (match_scratch:SI 2 "=&r") (const_int 0))
(clobber (reg:CC FLAGS_REG))]
""
"mov{l}\t{%1, %2|%2, %1}\;mov{l}\t{%2, %0|%0, %2}\;xor{l}\t%2, %2"
[(set_attr "type" "multi")])

(define_insn "stack_protect_set_di"
[(set (match_operand:DI 0 "memory_operand" "=m")
(unspec:DI [(match_operand:DI 1 "memory_operand" "m")] UNSPEC_SP_SET))
(set (match_scratch:DI 2 "=&r") (const_int 0))
(clobber (reg:CC FLAGS_REG))]
"TARGET_64BIT"
"mov{q}\t{%1, %2|%2, %1}\;mov{q}\t{%2, %0|%0, %2}\;xor{l}\t%k2, %k2"
[(set_attr "type" "multi")])

(define_insn "stack_tls_protect_set_si"
[(set (match_operand:SI 0 "memory_operand" "=m")
(unspec:SI [(match_operand:SI 1 "const_int_operand" "i")] UNSPEC_SP_TLS_SET))
(set (match_scratch:SI 2 "=&r") (const_int 0))
(clobber (reg:CC FLAGS_REG))]
""
"mov{l}\t{%%gs:%P1, %2|%2, DWORD PTR gs:%P1}\;mov{l}\t{%2, %0|%0, %2}\;xor{l}\t%2, %2"
[(set_attr "type" "multi")])

(define_insn "stack_tls_protect_set_di"
[(set (match_operand:DI 0 "memory_operand" "=m")
(unspec:DI [(match_operand:DI 1 "const_int_operand" "i")] UNSPEC_SP_TLS_SET))
(set (match_scratch:DI 2 "=&r") (const_int 0))
(clobber (reg:CC FLAGS_REG))]
"TARGET_64BIT"
{
/* The kernel uses a different segment register for performance reasons; a
system call would not have to trash the userspace segment register,
which would be expensive */
if (ix86_cmodel != CM_KERNEL)
return "mov{q}\t{%%fs:%P1, %2|%2, QWORD PTR fs:%P1}\;mov{q}\t{%2, %0|%0, %2}\;xor{l}\t%k2, %k2";
else
return "mov{q}\t{%%gs:%P1, %2|%2, QWORD PTR gs:%P1}\;mov{q}\t{%2, %0|%0, %2}\;xor{l}\t%k2, %k2";
}
[(set_attr "type" "multi")])

(define_expand "stack_protect_test"
[(match_operand 0 "memory_operand" "")
(match_operand 1 "memory_operand" "")
(match_operand 2 "" "")]
""
{
rtx flags = gen_rtx_REG (CCZmode, FLAGS_REG);
ix86_compare_op0 = operands[0];
ix86_compare_op1 = operands[1];
ix86_compare_emitted = flags;

#ifdef TARGET_THREAD_SSP_OFFSET
if (TARGET_64BIT)
emit_insn (gen_stack_tls_protect_test_di (flags, operands[0],
GEN_INT (TARGET_THREAD_SSP_OFFSET)));
else
emit_insn (gen_stack_tls_protect_test_si (flags, operands[0],
GEN_INT (TARGET_THREAD_SSP_OFFSET)));
#else
if (TARGET_64BIT)
emit_insn (gen_stack_protect_test_di (flags, operands[0], operands[1]));
else
emit_insn (gen_stack_protect_test_si (flags, operands[0], operands[1]));
#endif
emit_jump_insn (gen_beq (operands[2]));
DONE;
})

(define_insn "stack_protect_test_si"
[(set (match_operand:CCZ 0 "flags_reg_operand" "")
(unspec:CCZ [(match_operand:SI 1 "memory_operand" "m")
(match_operand:SI 2 "memory_operand" "m")]
UNSPEC_SP_TEST))
(clobber (match_scratch:SI 3 "=&r"))]
""
"mov{l}\t{%1, %3|%3, %1}\;xor{l}\t{%2, %3|%3, %2}"
[(set_attr "type" "multi")])

(define_insn "stack_protect_test_di"
[(set (match_operand:CCZ 0 "flags_reg_operand" "")
(unspec:CCZ [(match_operand:DI 1 "memory_operand" "m")
(match_operand:DI 2 "memory_operand" "m")]
UNSPEC_SP_TEST))
(clobber (match_scratch:DI 3 "=&r"))]
"TARGET_64BIT"
"mov{q}\t{%1, %3|%3, %1}\;xor{q}\t{%2, %3|%3, %2}"
[(set_attr "type" "multi")])

(define_insn "stack_tls_protect_test_si"
[(set (match_operand:CCZ 0 "flags_reg_operand" "")
(unspec:CCZ [(match_operand:SI 1 "memory_operand" "m")
(match_operand:SI 2 "const_int_operand" "i")]
UNSPEC_SP_TLS_TEST))
(clobber (match_scratch:SI 3 "=r"))]
""
"mov{l}\t{%1, %3|%3, %1}\;xor{l}\t{%%gs:%P2, %3|%3, DWORD PTR gs:%P2}"
[(set_attr "type" "multi")])

(define_insn "stack_tls_protect_test_di"
[(set (match_operand:CCZ 0 "flags_reg_operand" "")
(unspec:CCZ [(match_operand:DI 1 "memory_operand" "m")
(match_operand:DI 2 "const_int_operand" "i")]
UNSPEC_SP_TLS_TEST))
(clobber (match_scratch:DI 3 "=r"))]
"TARGET_64BIT"
{
/* The kernel uses a different segment register for performance reasons; a
system call would not have to trash the userspace segment register,
which would be expensive */
if (ix86_cmodel != CM_KERNEL)
return "mov{q}\t{%1, %3|%3, %1}\;xor{q}\t{%%fs:%P2, %3|%3, QWORD PTR fs:%P2}";
else
return "mov{q}\t{%1, %3|%3, %1}\;xor{q}\t{%%gs:%P2, %3|%3, QWORD PTR gs:%P2}";
}
[(set_attr "type" "multi")])

抽象構文

具象構文 (concrete syntax) というのは、プログラムのテキスト表現。要するに文字列。

コンパイラの中では、データ構造(内部表現)に変換される。
データ構造は文字列ではないんだけど、一対一に具象構文と対応するので、抽象構文表現 (abstract syntax representation) とも呼ばれる。
構文は木構造(ツリー)になるので、抽象構文木と言われることが多い。

プログラム ⇔ GENERIC (プログラムの抽象構文木)
アセンブリ ⇔ RTL IR (アセンブリの抽象構文木)

という対応関係がある。コンパイラにとっては、具象表現は単なる入出力フォーマットに過ぎない。
GCC のフレームワークは、対応している全ての言語のセマンティクスを表現できる。
その意味で、構文は GCC にとっては瑣末な問題となる。
どの言語で書かれていても、同じセマンティクスに対応するシンタクスからは、同じコードが出る。
セマンティクスの表現が内部表現そのものであると言える。

最適化は GIMPLE という単純な表現に変換してから行われる。

フロントエンド : GENERIC
ミドルエンド  : GIMPLE
バックエンド  : IR RTL

この構造は GCC 4.0 からで、さらに柔軟な最適化フレームワークを求めていた Redhat によって達成された。
それまでは、個別のフロントエンドで RTL を出すところまでを対応していた。言語ごとの共通表現は RTL しか無かった。
最適化も、RTL のレベルの局所的なものだけが共通化されていた。
現在の GCC では、ベクトル化や OpenMP なども、共通のミドルエンドルーチンで最適化される。

GCC の内部で、GENERICS は tree と呼ばれるデータ構造で扱われる。
IR RTL は rtx と呼ばれるデータ構造で扱われる。

RTL にも二種類があって、MD RTL と IR RTL がある。

MD は Machine Description (マシン定義)
IR は Intermediate Representation (中間表現)

MD RTL は、パターンマッチングルールの記述で、データ構造ではなく、直接 C コードに変換され、GCC 本体にコンパイル・リンクされる。
中間表現が IR RTL で記述されるのだから、変換ルールも RTL で記述すると、パターンマッチングが簡単に書ける。
MD RTL は、巨大な switch case 文も展開される。2 万行ぐらいの i386.md を変換すると、11 万行ぐらいの C プログラムになる。
./configure --targe=... で指定されるとき、どの md ファイルが組み込まれるのかが決定する。

2008年11月27日木曜日

MD ファイルからアセンブリが作られるまで

最後のアセンブリをファイルに fprintf (のようなこと。実際はフォーマット文字列を自前で解釈して putc(c, asm_out_file) とかしている) するところ。
  1. FINAL.C      :  final_scan_insn()
  2. FINAL.C      :   output_asm_insn()

gcc-4.3.2/gcc/final.c
  1. /* Find the proper template for this insn.  */  
  2. template = get_insn_template (insn_code_number, insn);  

  1. /* Output assembler code from the template.  */  
  2. output_asm_insn (template, recog_data.operand);  

template は const char * で、fprintf の format のような感じの文字列 (例 : "lea{l}\t{%a1, %0|%0, %a1}\0")

この template は、insn_data[] というテーブルを引いてるだけ。
  1. const char *  
  2. get_insn_template (int code, rtx insn)  
  3. {  
  4.   switch (insn_data[code].output_format)  
  5.     {  
  6.     case INSN_OUTPUT_FORMAT_SINGLE:  
  7.       return insn_data[code].output.single;  
  8.     case INSN_OUTPUT_FORMAT_MULTI:  
  9.       return insn_data[code].output.multi[which_alternative];  
  10.     case INSN_OUTPUT_FORMAT_FUNCTION:  
  11.       gcc_assert (insn);  
  12.       return (*insn_data[code].output.function) (recog_data.operand, insn);  
  13.   
  14.     default:  
  15.       gcc_unreachable ();  
  16.     }  
  17. }  

insn_data[] は、insn-output.c の中で定数として持たれている。
  1. const struct insn_data insn_data[] =   
  2. {  
  3.   /* ../../gcc-4.3.2/gcc/config/i386/i386.md:694 */  
  4.   {  
  5.     "*cmpsi_ccno_1",  
  6. #if HAVE_DESIGNATED_INITIALIZERS  
  7.     { .multi = output_0 },  
  8. #else  
  9.     { 0, output_0, 0 },  
  10. #endif  
  11.     0,  
  12.     &operand_data[1],  
  13.     2,  
  14.     0,  
  15.     2,  
  16.     2  
  17.   },  
  18.   /* ../../gcc-4.3.2/gcc/config/i386/i386.md:706 */  
  19.   {  
  20.     "*cmpsi_minus_1",  
  21. #if HAVE_DESIGNATED_INITIALIZERS  
  22.     { .single =  
  23. #else  
  24.     {  
  25. #endif  
  26.     "cmp{l}\t{%1, %0|%0, %1}",  
  27. #if HAVE_DESIGNATED_INITIALIZERS  
  28.     },  
  29. #else  
  30.     0, 0 },  
  31. #endif  
  32.     0,  
  33.     &operand_data[3],  
  34.     2,  
  35.     0,  
  36.     2,  
  37.     1  
  38.   },  
  39. ...  

さっきデバッガで見てたテンプレート "lea{l}\t{%a1, %0|%0, %a1}" はここか。
  1. /* ../../gcc-4.3.2/gcc/config/i386/i386.md:5571 */  
  2. {  
  3.   "*lea_1",  
  4. f HAVE_DESIGNATED_INITIALIZERS  
  5.   { .single =  
  6. lse  
  7.   {  
  8. ndif  
  9.   "lea{l}\t{%a1, %0|%0, %a1}",  
  10. f HAVE_DESIGNATED_INITIALIZERS  
  11.   },  
  12. lse  
  13.   0, 0 },  
  14. ndif  
  15.   0,  
  16.   &operand_data[359],  
  17.   2,  
  18.   0,  
  19.   1,  
  20.   1  
  21. },  

そしてこの insn_data[] は、i386.md などの MD ファイルから genoutput で生成されている。

(define_insn "*cmpsi_ccno_1"
[(set (reg FLAGS_REG)
(compare (match_operand:SI 0 "nonimmediate_operand" "r,?mr")
(match_operand:SI 1 "const0_operand" "n,n")))]
"ix86_match_ccmode (insn, CCNOmode)"
"@
test{l}\t%0, %0
cmp{l}\t{%1, %0|%0, %1}"
[(set_attr "type" "test,icmp")
(set_attr "length_immediate" "0,1")
(set_attr "mode" "SI")])

(define_insn "*cmpsi_minus_1"
[(set (reg FLAGS_REG)
(compare (minus:SI (match_operand:SI 0 "nonimmediate_operand" "rm,r")
(match_operand:SI 1 "general_operand" "ri,mr"))
(const_int 0)))]
"ix86_match_ccmode (insn, CCGOCmode)"
"cmp{l}\t{%1, %0|%0, %1}"
[(set_attr "type" "icmp")
(set_attr "mode" "SI")])

...

(define_insn "*lea_1"
[(set (match_operand:SI 0 "register_operand" "=r")
(match_operand:SI 1 "no_seg_address_operand" "p"))]
"!TARGET_64BIT"
"lea{l}\t{%a1, %0|%0, %a1}"
[(set_attr "type" "lea")
(set_attr "mode" "SI")])

Machine Description の資料

GCC Internals - Machine Desc

Writing GCC Machine Descriptions / Systematic Development of GCC MD

関数呼び出しを埋め込む

GCC は -finstrument-functions で任意のプロファイル関数を関数の enter/return 場所に仕込むことができる。

Graphvizによるファンクション・コールの視覚化

ちなみに、このプロファイル関数 (__cyg_profile_function_enter/exit()) は決め打ちで埋め込まれるので、C++ の場合は extern "C" で宣言しなければならない。

How to use '-finstrument-functions' in C++ programs ?

この機能の実装は、単に関数呼び出しの RTX を直接展開することで行われているようだ。

[tree-ssa][rfc] -finstrument-functions at tree level

gcc-4.3.2/gcc/builtins.c
  1. static rtx  
  2. expand_builtin_profile_func (bool exitp)  
  3. {  
  4.  rtx this, which;  
  5.   
  6.  this = DECL_RTL (current_function_decl);  
  7.  gcc_assert (MEM_P (this));  
  8.  this = XEXP (this, 0);  
  9.   
  10.  if (exitp)  
  11.    which = profile_function_exit_libfunc;  
  12.  else  
  13.    which = profile_function_entry_libfunc;  
  14.   
  15.  emit_library_call (which, LCT_NORMAL, VOIDmode, 2, this, Pmode,  
  16.        expand_builtin_return_addr (BUILT_IN_RETURN_ADDRESS,  
  17.        0),  
  18.        Pmode);  
  19.   
  20.  return const0_rtx;  
  21. }  

gcc-4.3.2/gcc/libfuncs.h
  1. #define profile_function_exit_libfunc (libfunc_table[LTI_profile_function_exit])  

gcc-4.3.2/gcc/optabs.c
  1. rtx libfunc_table[LTI_MAX];  

  1. rtx  
  2. init_one_libfunc (const char *name)  
  3. {  
  4.  rtx symbol;  
  5.   
  6.  /* Create a FUNCTION_DECL that can be passed to 
  7.     targetm.encode_section_info.  */  
  8.  /* ??? We don't have any type information except for this is 
  9.     a function.  Pretend this is "int foo()".  */  
  10.  tree decl = build_decl (FUNCTION_DECL, get_identifier (name),  
  11.      build_function_type (integer_type_node, NULL_TREE));  
  12.  DECL_ARTIFICIAL (decl) = 1;  
  13.  DECL_EXTERNAL (decl) = 1;  
  14.  TREE_PUBLIC (decl) = 1;  
  15.   
  16.  symbol = XEXP (DECL_RTL (decl), 0);  
  17.   
  18.  /* Zap the nonsensical SYMBOL_REF_DECL for this.  What we're left with 
  19.     are the flags assigned by targetm.encode_section_info.  */  
  20.  SET_SYMBOL_REF_DECL (symbol, 0);  
  21.   
  22.  return symbol;  
  23. }  

単に external でかつ artificial な関数呼び出しを作っているようだ。
  1.  abort_libfunc = init_one_libfunc ("abort");  
  2.  memcpy_libfunc = init_one_libfunc ("memcpy");  
  3.  memmove_libfunc = init_one_libfunc ("memmove");  
  4.  memcmp_libfunc = init_one_libfunc ("memcmp");  
  5.  memset_libfunc = init_one_libfunc ("memset");  
  6.  setbits_libfunc = init_one_libfunc ("__setbits");  
  7.   
  8. #ifndef DONT_USE_BUILTIN_SETJMP  
  9.  setjmp_libfunc = init_one_libfunc ("__builtin_setjmp");  
  10.  longjmp_libfunc = init_one_libfunc ("__builtin_longjmp");  
  11. #else  
  12.  setjmp_libfunc = init_one_libfunc ("setjmp");  
  13.  longjmp_libfunc = init_one_libfunc ("longjmp");  
  14. #endif  
  15.  unwind_sjlj_register_libfunc = init_one_libfunc ("_Unwind_SjLj_Register");  
  16.  unwind_sjlj_unregister_libfunc  
  17.    = init_one_libfunc ("_Unwind_SjLj_Unregister");  
  18.   
  19.  /* For function entry/exit instrumentation.  */  
  20.  profile_function_entry_libfunc  
  21.    = init_one_libfunc ("__cyg_profile_func_enter");  
  22.  profile_function_exit_libfunc  
  23.    = init_one_libfunc ("__cyg_profile_func_exit");  
  24.   
  25.  gcov_flush_libfunc = init_one_libfunc ("__gcov_flush");  

gcc-4.3.2/gcc/calls.h
  1. /* Output a library call to function FUN (a SYMBOL_REF rtx) 
  2.    (emitting the queue unless NO_QUEUE is nonzero), 
  3.    for a value of mode OUTMODE, 
  4.    with NARGS different arguments, passed as alternating rtx values 
  5.    and machine_modes to convert them to. 
  6.  
  7.    FN_TYPE should be LCT_NORMAL for `normal' calls, LCT_CONST for `const' 
  8.    calls, LCT_PURE for `pure' calls, LCT_CONST_MAKE_BLOCK for `const' calls 
  9.    which should be enclosed in REG_LIBCALL/REG_RETVAL notes, 
  10.    LCT_PURE_MAKE_BLOCK for `purep' calls which should be enclosed in 
  11.    REG_LIBCALL/REG_RETVAL notes with extra (use (memory (scratch)), 
  12.    or other LCT_ value for other types of library calls.  */  
  13.   
  14. void  
  15. emit_library_call (rtx orgfun, enum libcall_type fn_type,  
  16.      enum machine_mode outmode, int nargs, ...)  
  17. {  
  18.   va_list p;  
  19.   
  20.   va_start (p, nargs);  
  21.   emit_library_call_value_1 (0, orgfun, NULL_RTX, fn_type, outmode, nargs, p);  
  22.   va_end (p);  
  23. }  

同ファイル内の emit_library_call_value_1 は非常に巨大。

2008年11月26日水曜日

最適化の対象となるデータ構造

tree-inline.c などで定義されている、プロシージャ間 (interprocedural)  にまたがる最適化パスは cgraph_*() という関数名が付いていることが多い。

tree-ssa-*.c や tree-vect*.c などで定義されている最適化パスは cfg_*() という関数名が付いていることが多い。

紛らわしいのでまとめてみる。

http://diablo.elis.ugent.be/obfuscation_orig

  • call graph (CG)
    手続きの呼び出し関係グラフ。CG のノードが CFG。
  • control flow graph (CFG)
    手続き内部のフローグラフ。CFC のノードが BB。
  • basic blocks (BB)
    基本処理単位

ミドルエンドからバックエンドまでのフレームワーク

http://www.airs.com/dnovillo/Papers/

GCC Internals, D. Novillo, 2007 International Symposium on Code Generation and Optimization (CGO), San Jose, California, March 2007.(slides).
  • GCC のミドルエンドとバックエンドはまとめて実行される。
ミドルエンドは、GIMPLE → GIMPLE  な最適化と GIMPLE → RTL の変換が混在する、最適化パスのリスト。
最適化パスは、関数ポインタ execute や実行条件 gate(-On オプションによって制御される最適化強度など)が含まれる構造体 (tree-pass.h)
  1. /* Describe one pass.  */  
  2. struct tree_opt_pass  
  3. {  
  4. /* Terse name of the pass used as a fragment of the dump file name.  */  
  5. const char *name;  
  6.   
  7. /* If non-null, this pass and all sub-passes are executed only if 
  8.   the function returns true.  */  
  9. bool (*gate) (void);  
  10.   
  11. /* This is the code to run.  If null, then there should be sub-passes 
  12.   otherwise this pass does nothing.  The return value contains 
  13.   TODOs to execute in addition to those in TODO_flags_finish.   */  
  14. unsigned int (*execute) (void);  
  15.   
  16. /* A list of sub-passes to run, dependent on gate predicate.  */  
  17. struct tree_opt_pass *sub;  
  18.   
  19. /* Next in the list of passes to run, independent of gate predicate.  */  
  20. struct tree_opt_pass *next;  
  21.   
  22. /* Static pass number, used as a fragment of the dump file name.  */  
  23. int static_pass_number;  
  24.   
  25. /* The timevar id associated with this pass.  */  
  26. /* ??? Ideally would be dynamically assigned.  */  
  27. unsigned int tv_id;  
  28.   
  29. /* Sets of properties input and output from this pass.  */  
  30. unsigned int properties_required;  
  31. unsigned int properties_provided;  
  32. unsigned int properties_destroyed;  
  33.   
  34. /* Flags indicating common sets things to do before and after.  */  
  35. unsigned int todo_flags_start;  
  36. unsigned int todo_flags_finish;  
  37.   
  38. /* Letter for RTL dumps.  */  
  39. char letter;  
  40. };  

によって表現され、execute_pass_list() によって実行される (passes.c)。
  1. void  
  2. execute_pass_list (struct tree_opt_pass *pass)  
  3. {  
  4. do  
  5. {  
  6.   if (execute_one_pass (pass) && pass->sub)  
  7.     execute_pass_list (pass->sub);  
  8.   pass = pass->next;  
  9. }  
  10. while (pass);  
  11. }  

バックエンドは、RTR からのコード生成。
  • バックエンドも execute_pass_list() によって実行されるパスの一つ。
この点が GCC でもっともわかりにくいところ。
関数ポインタを含んだ構造体が使われまくるため静的な解析が難しい。
デバッガを使わなければ、どの関数が実行されるのかを突き止めるのはほぼ不可能。

パスリストの初期化は、同ファイル内の init_optimization_passed()
  1. /* Construct the pass tree.  The sequencing of passes is driven by 
  2. the cgraph routines: 
  3.  
  4. cgraph_finalize_compilation_unit () 
  5.     for each node N in the cgraph 
  6.  cgraph_analyze_function (N) 
  7.      cgraph_lower_function (N) -> all_lowering_passes 
  8.  
  9. If we are optimizing, cgraph_optimize is then invoked: 
  10.  
  11. cgraph_optimize () 
  12.     ipa_passes ()    -> all_ipa_passes 
  13.     cgraph_expand_all_functions () 
  14.         for each node N in the cgraph 
  15.      cgraph_expand_function (N) 
  16.  tree_rest_of_compilation (DECL (N))  -> all_passes 
  17. */  
  18.   
  19. void  
  20. init_optimization_passes (void)  
  21. {  
  22. struct tree_opt_pass **p;  
  23.   
  24. #define NEXT_PASS(PASS)  (p = next_pass_1 (p, &PASS))  
  25.   
  26. /* All passes needed to lower the function into shape optimizers can 
  27.  operate on.  These passes are always run first on the function, but 
  28.  backend might produce already lowered functions that are not processed 
  29.  by these passes.  */  
  30. p = &all_lowering_passes;  
  31. NEXT_PASS (pass_remove_useless_stmts);  
  32. NEXT_PASS (pass_mudflap_1);  
  33. NEXT_PASS (pass_lower_omp);  
  34. NEXT_PASS (pass_lower_cf);  
  35. NEXT_PASS (pass_refactor_eh);  
  36. NEXT_PASS (pass_lower_eh);  
  37. NEXT_PASS (pass_build_cfg);  
  38. NEXT_PASS (pass_lower_complex_O0);  
  39. NEXT_PASS (pass_lower_vector);  
  40. NEXT_PASS (pass_warn_function_return);  
  41. NEXT_PASS (pass_build_cgraph_edges);  
  42. NEXT_PASS (pass_inline_parameters);  
  43. *p = NULL;  
  44.   
  45. ...  
  46.   
  47. NEXT_PASS (pass_final);  
  48. }  
  49.    NEXT_PASS (pass_df_finish);  
  50.  }  
  51. NEXT_PASS (pass_clean_state);  
  52. *p = NULL;  
  53.   
  54. #undef NEXT_PASS  
  55.   
  56. /* Register the passes with the tree dump code.  */  
  57. register_dump_files (all_lowering_passes, false, PROP_gimple_any);  
  58. all_lowering_passes->todo_flags_start |= TODO_set_props;  
  59. register_dump_files (all_ipa_passes, true,  
  60.       PROP_gimple_any | PROP_gimple_lcf | PROP_gimple_leh  
  61.       | PROP_cfg);  
  62. register_dump_files (all_passes, false,  
  63.       PROP_gimple_any | PROP_gimple_lcf | PROP_gimple_leh  
  64.       | PROP_cfg);  
  65. }  

一番最後に、前回触れた、RTL からアセンブリを生成する rest_of_handle_final() が含まれた構造体 pass_final が追加されていることがわかる。バックエンドも、パスの一つになっている。

pass_final は final.c において実体が定義されている。final は必ず実行されるパスなので、gate が NULL になる。最適化レベルによっては実行されないようなパスの場合は、gate() の中で optimize というグローバルなフラグに応じて実行するか否かを判断する。
  1. struct tree_opt_pass pass_final =  
  2. {  
  3. NULL,                                 /* name */  
  4. NULL,                                 /* gate */  
  5. rest_of_handle_final,                 /* execute */  
  6. NULL,                                 /* sub */  
  7. NULL,                                 /* next */  
  8. 0,                                    /* static_pass_number */  
  9. TV_FINAL,                             /* tv_id */  
  10. 0,                                    /* properties_required */  
  11. 0,                                    /* properties_provided */  
  12. 0,                                    /* properties_destroyed */  
  13. 0,                                    /* todo_flags_start */  
  14. TODO_ggc_collect,                     /* todo_flags_finish */  
  15. 0                                     /* letter */  
  16. };  

ここまでに示した、ひたすらパスを実行するフレームワークが、GCC のミドルエンド以降の肝となる。これさえ理解すれば、後はひたすら個別のパスのコードを追っていけば、いつかは GCC をマスターできるはず。

アセンブリが出るまで

  1. MAIN.C      : main()
  2. TOPLEV.C    :  toplev_main()
  3. TOPLEV.C    :   do_compile()
  4. TOPLEV.C    :    compile_file()
  5. C-OPTS.C     :    c_common_parse_file()
  6. C-PARSER.C    :     c_parse_file()
  7. C-PARSER.C    :      c_parser_translation_unit()
  8. C-PARSER.C    :       c_parser_external_declaration()
  9. C-PARSER.C    :        c_parser_declaration_or_fndef()
  10. C-DECL.C     :         finish_function()
  11. CGRAPHUNIT.C  :          cgraph_finalize_function()
  12. CGRAPHUNIT.C  :           cgraph_assemble_pending_functions()
  13. CGRAPHUNIT.C  :           cgraph_expand_function()
  14. TREE-OPTIMIZE.C :            tree_rest_of_compilation()
  15. PASSES.C     :               execute_pass_list() // 最適化パスのリスト実行
  16. PASSES.C     :                execute_one_pass()
  17. FINAL.C      :                 rest_of_handle_final() // Turn the RTL into assembly. struct tree_opt_pass pass_final->execute()
  18. FINAL.C      :                  final() // バックエンドも最後のパス
  19. FINAL.C      :                   final_scan_insn()
  20. FINAL.C      :                    output_asm_insn()