バックエンド

あー。なんとなくわかってきた。多分、内部では

  • まず、フロントエンドから来たコードを最も単純なRTLに変換する。
  • んで、それを適当に変換しまくる。変換しまくったRTLのうち、ターゲットとなっているCPUで使えるRTLを選んどいて、それのコストを計算。
  • で、コストの一番低いやつを生成する。


こんな動きをしてるんじゃないかと思う。
で、バックエンドが記述しないといけないのが、「そのCPUで使える命令で表現できるRTLは何か」っていうのだ。


例えば、

a = b+c

っていう式があったとして、こいつを単純なRTLに変換すると、

;; (mem:SI x)    は、変数xのメモリ (実際は、(mem:SI (symbol_ref:SI ("x")))ってなるかも)
;; (set src dst) は、srcの値をdstにする
;; (plus x y)    は、xとyを足した値
;; SIっていうのは値のサイズを表すシンボル。SIはSingle Integerで、int一個分(多分32bit)。

(set (mem:SI a) (plus (mem:SI b) (mem:SI c)))   ;; bとcを足したのをaに入れる

こんな感じじゃないかと思う。(若干省略して書いてます)
んで、メモリーうしの足し算なんて普通のCPUではサポートしてないので、これが、

; 20 は 20番目のレジスタっていう意味 (多分CPUのレジスタ数によって変わる)
(set (reg:SI 20) (mem:SI b))   ; 変数bの値は20番目のレジスタに入れる
(set (reg:SI 21) (mem:SI c))   ; 変数cは21番目

(set (mem:SI a)
     (plus (reg:SI 20) 
           (reg:SI 21))) ; でそれを変数aに流す

こーいうふうに変換される。
で、CPUのmdに

;; レジスタとレジスタを足してメモリかレジスタに入れるRTLとマッチ
(define_insn "addsi3"
     [(set match_operand:SI 0 "general_operand" "ro"              ; general_operandはメモリかレジスタとマッチ
           (plus:SI (match_operand:SI 1 "register_operand" "r")   ; register_operandはレジスタとだけマッチ
                    (match_operand:SI 2 "register_operand" "r")))]
     ""
     "add  %0, %1, %2")

こんなふうに書いてあったら、上のRTLと一致できるのでこれで命令を生成する、と。
でも、普通のCPUはレジスタレジスタを加算したやつをメモリに直接ほうりこむなんてできない(よね)。多分柔軟なCPUでも、「レジスタorメモリとレジスタを加算したやつをレジスタにほうりこむ」くらいしかできない、と思う。で、それをmdで書くと

;; レジスタとレジスタorメモリを足してレジスタに入れるRTLとマッチ
(define_insn "addsi3"
     [(set match_operand:SI 0 "register_operand" "r"               ; destination は レジスタだけ
           (plus:SI (match_operand:SI 1 "register_operand" "r")    ; operand 1 はレジスタだけ
                    (match_operand:SI 2 "general_operand" "ro")))] ; operand 2 はレジスタでもメモリでもマッチ
     ""
     "add  %0, %1, %2")

こんな感じ。
で、これがあると、RTLをこれにマッチするように変換。
多分、

(set (reg:SI 20) (mem:SI b))  ; 変数bの値を20番目へ

(set (reg:SI 21)              ; 21番目のレジスタへ
     (plus (reg:SI 20)        ; 20番目のレジスタと
           (mem:SI c)))       ; 変数cの値を足した値を入れる

(set (mem:SI a) (reg:SI 21)   ; 21番目のレジスタの値を変数aへ入れる

こう変換されるんじゃないかと。で、これで、今のマシンと一致できるので、生成、といった感じじゃないかなぁ…
add命令がオペランド3つも取れることは珍しいかもしれないので、普通のCPUではもう一回変換が必要かもしれない。


実際にソース読んで解析したわけではないので、ひょっとしたら間違ってるかもしれないけど、色々動きを見る限りこういう解釈であってると思う。


んで、この、「CPUの命令とRTLでマッチするのを探す」っていう部分なんだけど、これがまた、「mdファイルからCPUの特徴を見てマッチング用のC関数を生成する」っていうふうになってて、GCC書いてる人達はほんとに「Cプログラムを生成するCプログラム」っていうのが好きだなぁ…って感じで、なにをどうやったらそーいうイカれた楽しいプログラムを多人数で協調しながら書いていけるのかというは結構謎だ、というか、まあ、そのへんは格の違いなんだろうなぁ…(意味不明な感想)。
とりあえず、よくわからん人は自動生成されたinsn-recog.cでも読むといいのではないかと。自動生成されたコードなんて読むもんじゃない気もするけど、イカれっぷりを感じれるというかなんというか。

参考:

//www.taplas.org/~sumii/tsugcc/tsugcc.html">TSU-GCC 制作記:実際にバックエンドを書いた人の記録。非常に参考になります。
//gcc.gnu.org/onlinedocs/gccint/">gccint.info:フロントエンドと違って結構まともな資料がある!!フロントエンドと違ってソースコード読んでもどうにもならないほど複雑だから資料がある、という見方もできるけど。