● SPARCコンパイラの翻訳例 図1はソース・プログラムの例である。これは、最大公約数を求める手続きgcd のCプログラムである。 1 /* 2 * sample program fragment 3 */ 4 5 int gcd(int x, int y) 6 { 7 while (x != 0) { 8 int z; 9 10 z = x; 11 x = y % x; 12 y = z; 13 } 14 return y; 15 } 図1. C言語のソース・プログラム 図1のプログラムをSPARCのアセンブリ言語に翻訳したものが図2である。1行目 から7行目まではアセンブラへの疑似命令で、このプログラムがテキスト・セ グメントに配置されることと、gcdという名前がこのプログラムの外から参照 可能な大域的な手続き名であることを宣言している。また、この手続きが、割 算を行う.remという大域的な手続きを参照することも宣言されている。 8行目は手続きgcdのエントリ・ポイントがここであることを示している。9行 名から13行目までのコードは、いわゆるコーリング・シーケンス(機械語に翻 訳された手続きが呼び出されるときに実行される一連の命令のこと、5.?参照) の一部で、仮引数領域と局所変数領域の確保と初期化を行う。10行目はスタッ ク・ポインタを動かすことにより領域を確保し、12行目(13行目)は、レジスタ i0(レジスタi1)に入っている第一引数x (第二引数y)の値をスタック上の領域 に格納する。 14行目は、ソース・プログラムの7行目のwhileループの開始を示すラベルであ る。15行目から18行目までのコードがソース・プログラムのwhile文の条件 x != 0 を評価する部分である。15行目はスタック上の変数xの値をレジスタo0 にロードし、16行目でこれを0と比較する。17行目で比較の結果が等しくなけ れば、ラベル.LL4の命令にジャンプする。ただし、SPARCのアーキテクチャで は、ジャンプはただちに行われずに、次の一命令を実行してから行われる(こ れをdelayed jumpという)ので、18行目には何も行わない命令(nop)を挿入して いる。 22行目と23行目のコードはソース・プログラムの10行目の代入文に対応し、24 行目から29行目までのコードはソース・プログラムの11行目の代入文に対応し ている。27行目で、割算を行う手続きを呼び出している。ちなみに、初期の SPARCアーキテクチャには割算命令がなかったために、どのSPARCアーキテクチ ャでも汎用的に動かそうとすると、このように割算を行う手続きを呼び出すよ うなコードが出力される。なお、手続き呼び出しcallもdelayed jumpであるの で、その直後にnopが挿入されている。29行目では割算の結果(この場合は剰余) をスタック上の変数xに格納している。 30行目と31行目は最後の代入文(ソース・プログラムの12行目)に対応している。 その後、32行目でwhileループの先頭にジャンプする。これもdelayed jumpな ので、直後にnopが挿入されている。 SPARC上のC言語のための手続きのコーリング・シーケンスではレジスタi0へ返 り値をロードする決まりとなっているので、35行目で変数yの値をレジスタi0 へロードしている。その直後に、手続きからの戻りのためのシーケンスにジャ ンプしている(36行目と37行目)。 38行目から40行目までが手続きからの戻りのためのシーケンスである。ret命 令もdelayed jumpであるが、ここではrestore命令(スタックの復帰)で埋めら れている。 手続きの最後にも若干の疑似命令がある。 1 .file "foo.c" 2 .global .rem 3 .section ".text" 4 .align 4 5 .global gcd 6 .type gcd,#function 7 .proc 04 8 gcd: 9 !#PROLOGUE# 0 10 save %sp,-120,%sp 11 !#PROLOGUE# 1 12 st %i0,[%fp+68] 13 st %i1,[%fp+72] 14 .LL2: 15 ld [%fp+68],%o0 16 cmp %o0,0 17 bne .LL4 18 nop 19 b .LL3 20 nop 21 .LL4: 22 ld [%fp+68],%o0 23 st %o0,[%fp-20] 24 ld [%fp+72],%o1 25 mov %o1,%o0 26 ld [%fp+68],%o1 27 call .rem,0 28 nop 29 st %o0,[%fp+68] 30 ld [%fp-20],%o0 31 st %o0,[%fp+72] 32 b .LL2 33 nop 34 .LL3: 35 ld [%fp+72],%i0 36 b .LL1 37 nop 38 .LL1: 39 ret 40 restore 41 .LLfe1: 42 .size gcd,.LLfe1-gcd 43 .ident "GCC: (GNU) 2.6.0" 図2. SPARCアセンブリ出力(最適化レベル0) 図2の機械語のプログラムには、見ての通り、多くの無駄がある。UNIX上のCコ ンパイラでは、-O というオプションを指定することにより、機械語のコード の最適化のレベルを変更することができる。図3は、最適化のレベルを1にした ときのコンパイラの出力である。以下、図2との違いだけを説明する。 まず、変数x,y,zについては、スタック上に領域を確保してしない。引数xとy には、それぞれ、レジスタo1とo0を用い、変数zにはレジスタl0を用いている。 12行目はorcc命令で、i0(第一引数)とg0(常に0が入っている)のorをo1(変数x) に入れ、その結果に従ってcondition flagを設定する。13行目はこの condition flagによるジャンプであるが、delayed jumpの機能を用いて、ジャ ンプの前にi1(第二引数)の内容をo0(変数y)に移している。 13行目のジャンプはループの条件に対応しているが、これが実行されるのはル ープの最初の一回目だけである。二回目以降のジャンプは19行目によって行わ れる。 レジスタo0とo1は、手続き.remへの入力としても用いられている。 以上のように、最適化の結果、メモリ(スタック)およびレジスタ間のデータの 移動が減り、無駄なジャンプがなくなっている。これらは、アーキテクチャに あまり依存しない最適化だが、この例でのdelayed jumpに関する最適化のよう に、アーキテクチャに強く依存した最適化技法もある。 1 .file "foo.c" 2 .global .rem 3 .section ".text" 4 .align 4 5 .global gcd 6 .type gcd,#function 7 .proc 04 8 gcd: 9 !#PROLOGUE# 0 10 save %sp,-112,%sp 11 !#PROLOGUE# 1 12 orcc %i0,%g0,%o1 13 be .LL3 14 mov %i1,%o0 15 .LL4: 16 call .rem,0 17 mov %o1,%l0 18 orcc %o0,%g0,%o1 19 bne .LL4 21 mov %l0,%o0 22 .LL3: 23 mov %o0,%i0 24 ret 25 restore 26 .LLfe1: 27 .size gcd,.LLfe1-gcd 28 .ident "GCC: (GNU) 2.6.0" 図3. SPARCアセンブリ出力(最適化レベル1)