以下、本発明に係るコンパイラの実施の形態について図面を用いて詳細に説明する。
本実施の形態におけるコンパイラは、C言語等の高級言語で記述されたソースプログラムを特定のプロセッサが実行できる機械語プログラムに翻訳するクロスコンパイラであり、生成する機械語プログラムのコードサイズや実行時間に関する最適化の指示をきめ細かく指定することができるという特徴を有する。
まず、本実施の形態におけるコンパイラの対象となるプロセッサの一例について、図1〜図36を用いて説明する。
本実施の形態におけるコンパイラの対象となるプロセッサは、例えば、通常のマイコンに比べて実行可能な命令の並列性が高く、AVメディア系信号処理技術分野をターゲットとして開発された汎用プロセッサである。
図1は、そのようなプロセッサの概略ブロック図の一例である。このプロセッサ1は、命令制御部10、デコード部20、レジスタファイル30、演算部40、I/F部50、命令メモリ部60、データメモリ部70、拡張レジスタ部80及びI/Oインターフェース部90から構成される。演算部40は、SIMD型命令の演算を実行する算術論理・比較演算器41〜43、乗算・積和演算器44、バレルシフタ45、除算器46及び変換器47からなる。乗算・積和演算器44は、ビット精度を落とさないように、最長で65ビットで累算する。また、乗算・積和演算器44は、算術論理・比較演算器41〜43と同様、SIMD型命令の実行が可能である。更に、このプロセッサ1は、算術論理・比較演算命令が最大3並列実行可能である。
図2は、算術論理・比較演算器41〜43の概略図を示す。算術論理・比較演算器41〜43それぞれは、ALU部41a、飽和処理部41b及びフラグ部41cから構成される。ALU部41aは、算術演算器、論理演算器、比較器、TST器からなる。対応する演算データのビット幅は、8ビット(演算器を4並列で使用)、16ビット(演算器を2並列で使用)、32ビットである(全演算器で32ビットデータ処理)。更に算術演算結果に対しては、フラグ部41c等により、オーバーフローの検出とコンディションフラグの生成が行われる。各演算器、比較器、TST器の結果は、算術右シフト、飽和処理部41bによる飽和、最大・最小値検出、絶対値生成処理が行われる。
図3は、バレルシフタ45の構成を示すブロック図である。バレルシフタ45は、セレクタ45a、45b、上位バレルシフタ45c、下位バレルシフタ45d及び飽和処理部45eから構成され、データの算術シフト(2の補数体系のシフト)または、論理シフト(符号なしシフト)を実行する。通常は、32ビットもしくは、64ビットのデータを入出力としている。レジスタ30a、30bに格納された被シフトデータに対して、別のレジスタまたは即値でシフト量が指定される。データは、左63ビット〜右63ビットの算術または論理シフトが行われ、入力ビット長で出力される。
また、バレルシフタ45は、SIMD型命令に対して、8、16、32、64ビットのデータをシフトすることができる。例えば、8ビットデータのシフトを4並列で処理することができる。
算術シフトは、2の補数体系のシフトであり、加算や減算時の小数点の位置合わせや、2のべき乗の乗算(2、2の2乗、2の(−1)乗、2の(−2)乗倍など)等のために行われる。
図4は、変換器47の構成を示すブロック図である。変換器47は、飽和ブロック(SAT)47a、BSEQブロック47b、MSKGENブロック47c、VSUMBブロック47、BCNTブロック47e及びILブロック47fから構成される。
飽和ブロック(SAT)47aは、入力データに対する飽和処理を行う。32ビットデータを飽和処理するブロックを2つ持つことにより、2並列のSIMD型命令をサポートする。
BSEQブロック47bは、MSBから連続する0か1をカウントする。
MSKGENブロック47cは、指定されたビット区間を1、それ以外を0として出力する。
VSUMBブロック47dは、入力データを指定されたビット幅に区切り、その総和を出力する。
BCNTブロック47eは、入力データで1となっているビットの数をカウントする。
ILブロック47fは、入力データを指定されたビット幅に区切り、各データブロックを入れ換えた値を出力する。
図5は、除算器46の構成を示すブロック図である。除算器46は、被除数を64ビット、除数を32ビットとし、商と剰余を32ビットずつ出力する。商と剰余を求めるまでに34サイクルを必要とする。符号付き、符号なし、両方のデータを扱うことが可能である。ただし、被除数と除数において符号の有無の設定は共通とする。その他、オーバーフローフラグ、0除算フラグを出力する機能を有する。
図6は、乗算・積和演算器44の構成を示すブロック図である。乗算・積和演算器44は、2つの32ビット乗算器(MUL)44a、44b、3つの64ビット加算器(Adder)44c〜44e、セレクタ44f及び飽和処理部(Saturation)44gから構成され、以下の乗算、積和演算を行う。
・32×32ビットのsignedの乗算、積和、積差演算
・32×32ビットのunsignedの乗算
・16×16ビットの2並列のsignedの乗算、積和、積差演算
・32×16ビットの2並列のsignedの乗算、積和、積差演算
これらの演算を整数、固定小数点フォーマット(h1、h2、w1、w2)のデータに対して行う。また、これらの演算に対し、丸め、飽和を行う。
図7は、命令制御部10の構成を示すブロック図である。命令制御部10は、命令キャッシュ10a、アドレス管理部10b、命令バッファ10c〜10e、ジャンプバッファ10f及びローテーション部(rotation)10gから構成され、通常時及び分岐時の命令供給を行う。128ビットの命令バッファを3つ(命令バッファ10c〜10e)持つことにより、最大並列実行数に対応している。分岐処理に関しては、分岐実行前に、ジャンプバッファ10f等を介して、後述するTARレジスタに予め分岐先アドレスを格納する(settar命令)。TARレジスタに格納された分岐先アドレスを使用して、分岐を行う。
なお、本実施の形態におけるコンパイラの対象となるプロセッサ1は、例えば、VLIWアーキテクチャを持つプロセッサである。ここで、VLIWアーキテクチャとは、1つの命令語中に複数の命令(ロード、ストア、演算、分岐など)を格納し、それらを全て同時に実行するアーキテクチャである。プログラマは、並列実行可能な命令を1つの発行グループとして記述することによって、その発行グループを並列処理させることができる。本明細書では、発行グループの区切りを";;"で示す。以下に表記例を示す。
(例1)
mov r1, 0x23;;
この命令記述は、命令movのみを実行することを意味する。
(例2)
mov r1, 0x38
add r0, r1, r2
sub r3, r1, r2;;
これらの命令記述は、命令mov、add、subを3並列で実行することを意味する。
命令制御部10は、発行グループを識別し、デコード部20に送る。デコード部20では、発行グループの命令を解析し、必要な資源を制御する。
次に、このようなプロセッサ1が備えるレジスタについて説明する。
プロセッサ1のレジスタセットは、以下の表1に示される通りである。
また、このようなプロセッサ1のフラグセット(後述する条件フラグレジスタ等で管理されるフラグ)は、以下の表2に示される通りである。
図8は、汎用レジスタ(R0〜R31)30aの構造を示す図である。汎用レジスタ(R0〜R31)30aは、実行対象となっているタスクのコンテキストの一部を構成し、データまたはアドレスを格納する32ビットのレジスタ群である。なお、汎用レジスタR30およびR31は、それぞれグローバルポインタ、スタックポインタとして、ハードウェアが使用する。
図9は、リンクレジスタ(LR)30cの構造を示す図である。なお、このリンクレジスタ(LR)30cと関連して、このプロセッサ1は、図示されていない退避レジスタ(SVR)も備える。リンクレジスタ(LR)30cは、関数コール時のリターンアドレスを格納する32ビットのレジスタである。なお、退避レジスタ(SVR)は、関数コール時の条件フラグレジスタのコンディションフラグ(CFR.CF)を退避する16ビットのレジスタである。リンクレジスタ(LR)30cは、後述する分岐レジスタ(TAR)と同様に、ループ高速化にも使用される。下位1ビットは常に0が読み出されるが、書き込み時には0を書き込む必要がある。
例えば、call(brl, jmpl)命令を実行した場合には、このプロセッサ1は、リンクレジスタ(LR)30cに戻りアドレスを退避し、退避レジスタ(SVR)にコンディションフラグ(CFR.CF)を退避する。また、jmp命令を実行した場合には、リンクレジスタ(LR)30cから戻りアドレス(分岐先アドレス)を取り出し、プログラムカウンタ(PC)を復帰させる。さらに、ret(jmpr)命令を実行した場合には、リンクレジスタ(LR)30cから分岐先アドレス(戻りアドレス)を取り出し、プログラムカウンタ(PC)に格納(復帰)する。さらに、退避レジスタ(SVR)からコンディションフラグを取り出し、条件フラグレジスタ(CFR)32のコンディションンフラグ領域CFR.CFに格納(復帰)する。
図10は、分岐レジスタ(TAR)30dの構造を示す図である。分岐レジスタ(TAR)30dは、分岐ターゲットアドレスを格納する32ビットのレジスタである。主に、ループの高速化に用いられる。下位1ビットは常に0が読み出されるが、書き込み時には0を書き込む必要がある。
例えば、jmp, jloop命令を実行した場合には、プロセッサ1は、分岐レジスタ(TAR)30dから分岐先アドレスを取り出し、プログラムカウンタ(PC)に格納する。分岐レジスタ(TAR)30dに格納されたアドレスの命令が分岐用命令バッファに格納されている場合は、分岐ペナルティが0になる。分岐レジスタ(TAR)30dにループの先頭アドレスを格納しておくことでループを高速化することができる。
図11は、プログラム状態レジスタ(PSR)31の構造を示す図である。プログラム状態レジスタ(PSR)31は、実行対象となっているタスクのコンテキストの一部を構成し、以下に示されるプロセッサ状態情報を格納する32ビットのレジスタである。
ビットSWE:VMP(Virtual Multi-Processor)のLP(Logical Processor)切替えイネーブルを示す。「0」はLP切替え不許可を示し、「1」はLP切替え許可を示す。
ビットFXP:固定小数点モードを示す。「0」はモード0を示し、「1」はモード1を示す。
ビットIH:割込み処理フラグであり、マスカブル割込み処理中であることを示す。「1」は割込み処理中であることを示し、「0」は割込み処理中でないことを示す。割込みが発生すると自動的にセットされる。rti命令で割込みから復帰したところが、他の割込み処理中かプログラム処理中であるのかを見分けるために使用される。
ビットEH:エラーまたはNMIを処理中であることを示すフラグである。「0」はエラー/NMI割込み処理中でないことを示し、「1」はエラー/NMI割込み処理中であることを示す。EH=1のとき、非同期エラーまたはNMIが発生した場合は、マスクされる。また、VMPイネーブル時はVMPのプレート切り替えがマスクされる。
ビットPL[1:0]:特権レベルを示す。「00」は特権レベル0、つまり、プロセッサアブストラクションレベルを示し、「01」は特権レベル1(設定できない)を示し、「10」は特権レベル2、つまり、システムプログラムレベルを示し、「11」は特権レベル3、つまり、ユーザプログラムレベルを示す。
ビットLPIE3:LP固有割込み3イネーブルを示す。「1」は割込み許可を示し、「0」は割込み不許可を示す。
ビットLPIE2:LP固有割込み2イネーブルを示す。「1」は割込み許可を示し、「0」は割込み不許可を示す。
ビットLPIE1:LP固有割込み1イネーブルを示す。「1」は割込み許可を示し、「0」は割込み不許可を示す。
ビットLPIE0:LP固有割込み0イネーブルを示す。「1」は割込み許可を示し、「0」は割込み不許可を示す。
ビットAEE:ミスアライメント例外イネーブルを示す。「1」はミスアライメント例外許可を示し、「0」はミスアライメント例外不許可を示す。
ビットIE:レベル割込みイネーブルを示す。「1」はレベル割込み許可を示し、「0」はレベル割込み不許可を示す。
ビットIM[7:0]:割込みマスクを示す。レベル0〜7まで定義され、個々のレベルでマスクすることができる。レベル0が最も高いレベルとなる。IMによりマスクされていない割込み要求のうち最も高いレベルを持った割込み要求のみがプロセッサ1に受理される。割込み要求を受理すると受理したレベル以下のレベルはハードウェアで自動的にマスクされる。IM[0]はレベル0のマスクであり、IM[1]はレベル1のマスクであり、IM[2]はレベル2のマスクであり、IM[3]はレベル3のマスクであり、IM[4]はレベル4のマスクであり、IM[5]はレベル5のマスクであり、IM[6]はレベル6のマスクであり、IM[7]はレベル7のマスクである。
reserved:予約ビットを示す。常に0が読み出される。書き込む時は0を書き込む必要がある。
図12は、条件フラグレジスタ(CFR)32の構造を示す図である。条件フラグレジスタ(CFR)32は、実行対象となっているタスクのコンテキストの一部を構成する32ビットのレジスタであり、コンディションフラグ(条件フラグ)、オペレーションフラグ(演算フラグ)、ベクタコンディションフラグ(ベクタ条件フラグ)、演算命令用ビット位置指定フィールド、SIMDデータアライン情報フィールドから構成される。
ビットALN[1:0]:アラインモードを示す。valnvc命令のアラインモードを設定する。
ビットBPO[4:0]:ビットポジションを示す。ビット位置指定の必要な命令で使用する。
ビットVC0〜VC3:ベクタ条件フラグである。LSB側のバイトあるいはハーフワードから順にVC0に対応し、MSB側がVC3に対応する。
ビットOVS:オーバーフローフラグ(サマリー)である。飽和発生やオーバーフロー検出でセットされる。検出されなかった場合は、命令実行前の値を保持する。クリアはソフトで行う必要がある。
ビットCAS:キャリーフラグ(サマリー)である。addc命令でキャリーまたはsubc命令でボローが発生した場合セットされる。addc命令でキャリーもしくはsubc命令でボローが発生しなかった場合は、命令実行前の値を保持する。クリアはソフトで行う必要がある。
ビットC0〜C7:コンディションフラグである。条件付き実行命令における条件(TRUE/FALSE)を示す。条件付き命令の条件とビットC0〜C7との対応は、命令に含まれるプレディケート・ビットによって決定される。なお、フラグC7は常に値が1である。フラグC7へのFALSE条件の反映(0書き込み)は無視される。
reserved:予約ビットを示す。常に0が読み出される。書き込む時は0を書き込む必要がある。
図13は、アキュムレータ(M0,M1)30bの構造を示す図である。このアキュムレータ(M0,M1)30bは、実行対象となっているタスクのコンテキストの一部を構成し、図13(a)に示される32ビットレジスタMH0-MH1(乗除算・積和用レジスタ(上位32ビット))と、図13(b)に示される32ビットレジスタML0-ML1乗除算・積和用レジスタ(下位32ビット)とからなる。
レジスタMH0-MHは、乗算命令では結果の上位32ビットを格納するのに使用される。積和命令ではアキュムレータの上位32ビットとして使用される。また、ビットストリームを取り扱う場合に汎用レジスタと組み合わせて使用することができる。レジスタML0-ML1は、乗算命令では結果の下位32ビットを格納するのに使用される。積和命令ではアキュムレータの下位32ビットとして使用される。
図14は、プログラムカウンタ(PC)33の構造を示す図である。このプログラムカウンタ(PC)33は、実行対象となっているタスクのコンテキストの一部を構成し、実行中の命令のアドレスを保持する32ビットのカウンタである。下位1ビットは常に0が格納される。
図15は、PC退避用レジスタ(IPC)34の構造を示す図である。このPC退避用レジスタ(IPC)34は、実行対象となっているタスクのコンテキストの一部を構成する32ビットのレジスタであり、下位1ビットは常に0が読み出されるが、書き込み時には0を書き込む必要がある。
図16は、PSR退避用レジスタ(IPSR)35の構造を示す図である。このPSR退避用レジスタ(IPSR)35は、実行対象となっているタスクのコンテキストの一部を構成し、プログラム状態レジスタ(PSR)31を退避するための32ビットのレジスタであり、プログラム状態レジスタ(PSR)31の予約ビットに対応する部分は常に0が読み出されるが、書き込み時には0を書き込む必要がある。
次に、本実施の形態におけるコンパイラの対象となるプロセッサ1のメモリ空間について説明する。例えば、プロセッサ1では、4GBのリニアなメモリ空間を32分割し、128MB単位の空間に命令SRAM(Static RAM)とデータSRAMが割り当てられる。この128MBの空間を1ブロックとして、SAR(SRAM Area Register)にアクセスしたいブロックを設定する。アクセスされたアドレスがSARで設定された空間である場合は、直接命令SRAM/データSRAMに対してアクセスを行うが、SARで設定された空間でない場合は、バスコントローラ(BCU)に対してアクセス要求を出する。BCUにはオン・チップ・メモリ(OCM)、外部メモリ、外部デバイス、I/Oポート等が接続されており、それらのデバイスに対して読み書きを行うことができる。
図17は、本実施の形態におけるコンパイラの対象となるプロセッサ1のパイプライン動作を示すタイミング図である。このプロセッサ1は、本図に示されるように、例えば、基本的に命令フェッチ、命令割り当て(ディスパッチ)、デコード、実行、書き込みの5段パイプラインで構成されている。
図18は、このようなプロセッサ1による命令実行時の各パイプライン動作を示すタイミング図である。命令フェッチステージでは、プログラムカウンタ(PC)33で指定されるアドレスの命令メモリをアクセスし、命令を命令バッファ10c〜10e等に転送する。命令割り当てステージでは、分岐系命令に対する分岐先アドレス情報の出力、入力レジスタ制御信号の出力、可変長命令の割り当てを行い、命令をインストラクションレジスタ(IR)に転送する。デコードステージでは、IRをデコード部20に入力し、演算器制御信号、メモリアクセス信号を出力する。実行ステージでは、演算を実行、演算結果をデータメモリか汎用レジスタ(R0〜R31)30aに出力する。書き込みステージでは、データ転送、演算結果を汎用レジスタに格納する。
本実施の形態におけるコンパイラの対象となるプロセッサ1は、例えば、VLIWアーキテクチャにより上記の処理を最高3並列で行うことができる。したがって、図18に示された動作については、本プロセッサ1は、図19に示されるタイミングで並列に実行する。
次に、以上のように構成されたプロセッサ1の命令セットの例について説明する。
以下の表3〜表5は、本実施の形態におけるコンパイラの対象となるプロセッサ1が実行する命令をカテゴリー別に分類した表である。
なお、表中の「演算器」は、その命令が使用する演算器を示す。演算器の略号の意味は次の通りである。つまり、「A」はALU命令、「B」は分岐命令、「C」は変換命令、「DIV」は除算命令、「DBGM」はデバッグ命令、「M」はメモリアクセス命令、「S1」、「S2」はシフト命令、「X1」、「X2」は乗算命令を意味する。
図20は、このようなプロセッサ1が実行する命令のフォーマットの例を示す図である。そのフォーマットには、図20(a)に示される16ビット命令フォーマットと、図20(b)に示される32ビット命令フォーマットとがある。
なお、図中における略号の意味は次の通りである。つまり、「E」はエンドビット(並列実行の境界)、「F」はフォーマットビット(00、01、10:16ビット命令フォーマット、11:32ビット命令フォーマット)、「P」はプレディケート(実行条件:8個の条件フラグC0〜C7のいずれかを指定)、「OP」はオペコードフィールド、「R」はレジスタフィールド、「I」は即値フィールド、「D」ディスプースメントフィールドを意味する。なお、「E」フィールドはVLIWに特有のもので、E=0の命令は次の命令と並列に実行される。つまり、「E」フィールドによって並列度が可変のVLIWを実現している。
図21〜図36は、プロセッサ1が実行する命令の概略的な機能を説明する図である。つまり、図21は、カテゴリー「ALUadd(加算)系」に属する命令を説明する図であり、図22は、カテゴリー「ALUsub(減算)系」に属する命令を説明する図であり、図23は、カテゴリー「ALUlogic(論理演算)系ほか」に属する命令を説明する図であり、図24は、カテゴリー「CMP(比較演算)系」に属する命令を説明する図であり、図25は、カテゴリー「mul(乗算)系」に属する命令を説明する図であり、図26は、カテゴリー「mac(積和演算)系」に属する命令を説明する図であり、図27は、カテゴリー「msu(積差演算)系」に属する命令を説明する図であり、図28は、カテゴリー「MEMld(メモリ読み出し)系」に属する命令を説明する図であり、図29は、カテゴリー「MEMstore(メモリ書き出し)系」に属する命令を説明する図であり、図30は、カテゴリー「BRA(分岐)系」に属する命令を説明する図であり、図31は、カテゴリー「BSasl(算術バレルシフト)系ほか」に属する命令を説明する図であり、図32は、カテゴリー「BSlsr(論理バレルシフト)系ほか」に属する命令を説明する図であり、図33は、カテゴリー「CNVvaln(算術変換)系」に属する命令を説明する図であり、図34は、カテゴリー「CNV(一般変換)系」に属する命令を説明する図であり、図35は、カテゴリー「SATvlpk(飽和処理)系」に属する命令を説明する図であり、図36は、カテゴリー「ETC(その他)系」に属する命令を説明する図である。
これらの図において、項目「SIMD」は、その命令の型(SISD(SINGLE)かSIMDかの区別)を示し、項目「サイズ」は、演算の対象となる個々のオペランドのサイズを示し、項目「命令」は、その命令のオペコードを示し、項目「オペランド」は、その命令のオペランドを示し、項目「CFR」は、条件フラグレジスタの変化を示し、項目「PSR」は、プロセッサ状態レジスタの変化を示し、項目「代表的な動作」は、動作の概要を示し、項目「演算器」は、使用される演算器を示し、項目「3116」は、命令のサイズを示す。
以下に、後述する具体例で使用される主な命令についてのプロセッサ1の動作を説明する。
ld Rb,(Ra,D10)
レジスタRaにディスプレースメント値(D10)を加算したアドレスからワードデータをレジスタRbにロードする。
ldh Rb,(Ra+)I9
レジスタRaが示すアドレスからハーフワードデータを符号拡張してロードする。さらに、レジスタRaに即値(I9)を加算し、レジスタRaに格納する。
ldp Rb:Rb+1,(Ra+)
レジスタRaが示すアドレスからレジスタRbとRb+1に2 つのワードデータを符号拡張してロードする。さらに、レジスタRaに 8 を加算し、レジスタRaに格納する。
ldhp Rb:Rb+1, (Ra+)
レジスタRaが示すアドレスから 2 つのハーフワードデータを符号拡張してロードする。さらに、レジスタRaに 4 を加算し、レジスタRaに格納する。
setlo Ra,I16
レジスタRaに即値(I16)を符号拡張して格納する。
sethi Ra,I16
レジスタRaの上位 16 bitに即値(I16)を格納する。レジスタRaの下位 16 bitには影響しない。
ld Rb,(Ra)
レジスタRaが示すアドレスからワードデータをレジスタRbにロードする。
add Rc,Ra,Rb
レジスタRaとRbを加算し、レジスタRbに格納する。
addu Rb,GP,I13
レジスタGPに即値(I13)を加算し、レジスタRbに格納する。
st (GP,D13),Rb
レジスタGPにディスプレースメント値(D13)を加算したアドレスに、レジスタRbに格納されたハーフワードデータをストアする。
sth (Ra+)I9,Rb
レジスタRaが示すアドレスに、レジスタRbに格納されたハーフワードデータをストアする。さらに、レジスタRaに即値(I9)を加算し、レジスタRaに格納する。
stp (Ra+),Rb:Rb+1
レジスタRaが示すアドレスに、レジスタRbとRb+1に格納された 2 つのワードデータをストアする。さらに、レジスタRaに 8 を加算し、レジスタRaに格納する。
Ret
サブルーチンコールからのリターンに使用する。LRに格納されているアドレスに分岐する。SVR.CFをCFR.CFに転送する。
mov Ra,I16
レジスタRaに値(I16)を符号拡張して格納する。
settar C6,D9
以下の処理を行う。 (1) PCとディスプレースメント値(D9)を加算したアドレスを分岐レジスタTARに格納する。 (2) そのアドレスの命令をフェッチして分岐用命令バッファに格納する。 (3) C6を 1 にセットする。
settar C6,Cm,D9
以下の処理を行う。 (1) PCとディスプレースメント値(D9)を加算したアドレスを分岐レジスタTARに格納する。 (2) そのアドレスの命令をフェッチして分岐用命令バッファに格納する。 (3) C6を 1 に、Cmを 0 にセットする。
settar C6,C2:C4,D9
以下の処理を行う。 (1) PCとディスプレースメント値(D9)を加算したアドレスを分岐レジスタTARに格納する。 (2) そのアドレスの命令をフェッチして分岐用命令バッファに格納する。 (3) C4とC6を 1 に、C2とC3を 0 にセットする。
jloop C6,TAR,Ra2,-1
ループで使用する。以下の処理を行う。 (1) レジスタRa2に -1 を加算し、レジスタRa2に格納する。レジスタRa2が 0 より小さくなるとC6に 0 をセットする。 (2) 分岐レジスタTARが示すアドレスにジャンプする。
jloop C6,Cm,TAR,Ra2,-1
ループで使用する。以下の処理を行う。 (1) Cm に 1 をセットする。 (2) レジスタRa2に -1 を加算し、レジスタRa2に格納する。レジスタRa2が 0 より小さくなるとC6に 0 をセットする。 (3) 分岐レジスタTARが示すアドレスにジャンプする。
jloop C6,C2:C4,TAR,Ra2,-1
ループで使用する。以下の処理を行う。(1) C3をC2に転送し、C4をC3とC6に転送する。(2) レジスタRa2に -1 を加算し、レジスタRa2に格納する。レジスタRa2が 0 より小さくなるとC4に 0 をセットする。(3) 分岐レジスタTARが示すアドレスにジャンプする。
mul Mm,Rb,Ra,I8
レジスタRaと即値(I8)を符号付乗算し、結果をレジスタMmとレジスタRbに格納する。
mac Mm,Rc,Ra,Rb,Mn
レジスタRaとRbを整数乗算し、レジスタMnと加算する。結果をレジスタMmとレジスタRcに格納する。
lmac Mm,Rc,Ra,Rb,Mn
レジスタRbをハーフワードベクタ形式で扱う。レジスタRaとRbの下位 16 bitを整数乗算し、レジスタMnと加算する。結果をレジスタMmとレジスタRcに格納する。
jloop C6,C2:C4,TAR,Ra2,-1
ループで使用する。以下の処理を行う。(1) C3をC2に転送し、C4をC3とC6に転送する。(2) レジスタRa2に -1 を加算し、レジスタRa2に格納する。レジスタRa2が 0 より小さくなるとC4に 0 をセットする。(3) 分岐レジスタTARが示すアドレスにジャンプする。
asr Rc,Ra,Rb
レジスタRaをRbが示すビット数だけ算術右シフトする。レジスタRbは±31 以内に飽和され、負の場合は算術左シフトになる。
br D9
現在のPCに、ディスプレースメント値(D9)を加算し、そのアドレスに分岐する。
jmpf TAR
分岐レジスタTARに格納されているアドレスに分岐する。
cmpCC Cm,Ra,I5
CC には次のCC比較条件を記述可能である。
eq/ne/gt/ge/gtu/geu/le/lt/leu/ltu
CCがeq/ne/gt/ge/le/ltの場合、I5は符号付きの値で、符号拡張して比較する。CCがgtu/geu/leu/ltuの場合、I5は符号なしの値である。
[コンパイラ]
次に、以上のプロセッサ1をターゲットとする本実施の形態におけるコンパイラについて説明する。
図37は、本実施の形態におけるコンパイラ100の構成を示す機能ブロック図である。このコンパイラ100は、C言語等の高級言語で記述指定されたソースプログラム101を、上述のプロセッサ1をターゲットプロセッサとする機械語プログラム102に変換するクロスコンパイラであり、パーソナルコンピュータ等のコンピュータ上で実行されるプログラムによって実現され、大きく分けて、解析部110と、最適化部120と、出力部130とから構成される。
解析部110は、コンパイルの対象となるソースプログラム101及びこのコンパイラ100に対するユーザからの指示等を字句解析することによって、コンパイラ100に対する指示(オプション及びプラグマ)については最適化部120や出力部130に伝達し、コンパイルの対象となるプログラムについては内部形式データに変換したりする。
なお、「オプション」とは、コンパイラ100を起動する際に、コンパイルの対象となるソースプログラム101の指定とともに、ユーザが任意に指定することができるコンパイラ100への指示であり、生成する機械語プログラム102のコードサイズ及び実行時間を最適化するための指示等が含まれる。例えば、ユーザは、ソースプログラム101「sample.c」をコンパイルするときに、コマンド「ammmp-cc」を用いて、コンピュータ上で、
c:\>ammmp-cc -o -max-gp-datasize=40 sample.c
と入力することができる。このコマンドにおける付加的な指示「-o」及び「-max-gp-datasize=40」がオプションである。このようなオプションによる指示は、ソースプログラム101全体に対する指示として扱われる。
また、「プラグマ(又は、プラグマ指令)」とは、ソースプログラム101中にユーザが任意に指定(配置)することができるコンパイラ100への指示であり、オプションと同様に、生成する機械語プログラム102のコードサイズ及び実行時間を最適化するための指示等が含まれる。本実施の形態におけるコンパイラ100では、「#pragma」で始まる文字列である。例えば、ユーザは、ソースプログラム101中に、
#pragma_no_gp_access 変数名
というステートメントを記述しておくことができる。このステートメントがプラグマ(プラグマ指令)である。このようなプラグマは、オプションと異なり、当該プラグマの直後に配置された変数やループ処理等だけに対する個別的な指示として扱われる。
最適化部120は、解析部110から出力されたソースプログラム101(内部形式データ)に対して、解析部110からの指示等に従って、(1)実行速度の向上を優先した最適化、(2)コードサイズの削減を優先した最適化、(3)実行速度とコードサイズの両方の最適化、の中から選択された最適化を実現するための全体的な最適化処理を行うことに加えて、ユーザによるオプション及びプラグマによって指定された個別的な最適化処理を行う処理部(グローバル領域割り付け部121、ソフトウェアパイプライニング部122、ループアンローリング部123、if変換部124及びペア命令生成部125)を有する。
グローバル領域割り付け部121は、グローバル領域(共通のデータ領域として関数を超えて参照可能なメモリ領域)に配置する変数(配列)の最大データサイズの指定、グローバル領域に配置させる変数の指定、及び、グローバル領域に配置させない変数の指定に関するオプション及びプラグマに従った最適化処理を行う。
ソフトウェアパイプライニング部122は、ソフトウェアパイプライニングを行わない旨の指示、プロログ部・エピログ部が除去できる範囲でソフトウェアパイプライニングを行う旨の指示、及び、プロログ部・エピログ部を除去せずに可能な範囲でソフトウェアパイプライニングを行う旨の指示に関するオプション及びプラグマに従った最適化処理を行う。
ループアンローリング部123は、ループアンローリングを行う旨の指示、ループアンローリングを行わない旨の指示、ループが繰り返される最低回数の保証、ループが偶数回繰り返される旨の保証、及び、ループが奇数回繰り返される旨の保証に関するオプション及びプラグマに従った最適化処理を行う。
if変換部124は、if変換を行う旨の指示、及び、if変換を行わない旨の指示に関するオプション及びプラグマに従った最適化処理を行う。
ペア命令生成部125は、配列と構造体の先頭アドレスのアラインの指定、及び、関数引数のポインタ変数やローカルポインタ変数の指すデータのアライメントの保証に関するプラグマに従った最適化処理を行う。
出力部130は、最適化部120による最適化処理が施されたソースプログラム101に対して、内部形式データを対応する機械語命令に置き換えたり、ラベルやモジュール等のアドレスを解決したりすることで、機械語プログラム102を生成し、ファイル等として出力する。
次に、以上のように構成された本実施の形態におけるコンパイラ100の特徴的な動作について具体例を示しながら説明する。
[グローバル領域割り付け部121]
まず、グローバル領域割り付け部121の動作とその意義について説明する。グローバル領域割り付け部121は、大きく分けて、(1)グローバル領域配置の最大データサイズの指定に関する最適化と、(2)グローバル領域配置の指定に関する最適化とを行う。
まず、(1)グローバル領域配置の最大データサイズの指定に関する最適化について説明する。
上記プロセッサ1には、グローバルポインタレジスタ(gp;汎用レジスタR30)が用意されており、グローバル領域(以下gp領域とする)の先頭のアドレスを保持している。gp領域先頭からのディスプレースメントが最大14ビットの範囲については、1命令でアクセスすることが可能である。
このgp領域には、外部変数・静的変数等の配列を配置することが可能である。ただし、1命令でアクセスできる範囲を超えた場合、逆に性能が低下するため注意が必要である。
図38(a)は、グローバル領域におけるデータ等の配置例を示す図である。ここでは、配列Aのデータサイズは、最大データサイズを超えない値であり、配列Cのデータサイズは、最大データサイズを超える値である。
gp領域に実体が収まっている配列Aへのアクセスは、以下の例のように、1命令で可能である。
例:ld r1,(gp,_A - .MN.gptop);;
なお、この例において、「.MN.gptop」は、グローバルポインタレジスタと同じアドレスを指すセクション名(ラベル)である。
一方、gp領域配置の最大データサイズを超える配列Cの場合は、gp領域以外に実体が配置され、配列Cのアドレスのみがgp領域に配置される(なお、後述の、#pragma _no_gp_access指令を使用した場合は、gp領域に実体もアドレスも格納されない)。
この場合、配列Cへのアクセスは、以下の例のように、複数命令必要になる。
例: gpアドレス間接アクセスの場合
ld r1,(gp,_C$ - .MN.gptop);;
ld r1,(r1,8);;
例: 絶対アドレスアクセスの場合
setlo r0,LO(_C+8);;
sethi r0,HI(_C+8);;
ld r0,(r0);;
なお、図38(b)に示されるグローバル領域以外の領域における配置例のように、gp領域の1命令でアクセスできる範囲外に実体が配置された配列Zの場合でも、以下のようなコードが生成される。
ld r0,(gp,_Z - .MN.gptop);;
このコードは、1命令でアクセスできる範囲を超えているため、リンカにより複数命令に展開される。よって、1命令アクセスにはならない。
なお、gp領域の1命令アクセス範囲は、最大14ビット範囲であるが、オブジェクトの型サイズにより、その範囲は異なる。つまり、8バイト型であれば14ビット範囲であり、4バイト型であれば13ビット範囲であり、2バイト型であれば12ビット範囲であり、1バイト型であれば11ビット範囲である。
コンパイラ100は、最大データサイズ(デフォルト32バイト)以下の配列・構造体の実体をgp領域に配置する。一方、グローバル領域配置の最大データサイズを超えるオブジェクトに関しては、gp領域以外に実体を配置し、gp領域にはオブジェクトの先頭アドレスのみを配置する。
ここで、gp領域に余裕があれば、データサイズ32バイト以上のオブジェクトも配置させた方が、より良いコードを生成することが可能である。
そこで、以下のオプションを用いることで、ユーザは、この最大データサイズを任意の値に指定することが可能となっている。
・コンパイルオプション
-mmax-gp-datasize=NUM
ここで、NUMは、グローバル領域に配置できる、一つの配列および構造体の最大データサイズの指定バイト(デフォルト32バイト)である。
図39は、グローバル領域割り付け部121の動作を示すフローチャートである。解析部110によって上記オプションが検出された場合には(ステップS100、S101)、グローバル領域割り付け部121は、ソースプログラム101で宣言されている指定されたサイズ(NUMバイト)以下の全ての変数(配列)については、グローバル領域に配置し、NUMバイトを超える変数については、その先頭アドレスだけをグローバル領域に配置し、その実体をグローバル領域以外のメモリ領域に配置する(ステップS102)。このオプションによって、速度向上とサイズ削減という最適化が可能となる。
なお、この-mmax-gp-datasizeオプションでは、変数個々の配置指定はできない。個々の変数に対してgp領域に配置する/しないを指定するには、後述する#pragma _gp_access指令を使用すればよい。また、外部変数・静的変数は、可能な限り1命令アクセス可能なgp領域に配置することが好ましい。さらに、extern宣言を使用して、他のファイルで定義される外部変数にアクセスする場合は、その外部変数のサイズを省略せずに、明記することが好ましい。例えば、外部変数の定義が、
int a[8];
とされている場合、使用するファイルでは、
extern int a[8];
と宣言することが好ましい。
なお、extern宣言外部変数に #pragma _gp_access指令を使用する場合、定義の指定(配置する領域)と使用側の指定(アクセスする方法)を必ず合わせる必要がある。
次に、このようなオプション用いることによる最適化の具体例を示す。
図40は、最大データサイズを変更した場合の最適化の具体例を示す図である。つまり、デフォルトの状態でコンパイルし、その結果、まだgp領域に空きがあるので、以下のコマンド例のように、最大データサイズを変更してコンパイルした場合における両ケースで得られる生成コードの例が示されている。
c:\>ammmp-cc -O -mmax-gp-datasize=40 sample.c
ここでは、配列cのオブジェクトサイズが、40バイトであるとする。本図の左欄は、デフォルトの状態でコンパイルされた場合に生成されるコードの例であり、本図の右欄は、最大データサイズを40に変更してコンパイルされた場合に生成されるコードの例である。なお、本図の最上段、中上段、中下段、最下段は、それぞれ、各欄のタイトル、サンプルプログラム(ソースプログラム101)、そこから生成されるコード(機械語プログラム102)、その生成コードのサイクル数及びコードサイズを示している(以下、最適化の具体例を示す他の図についても同様)。
本図の左欄の生成コードから分かるように、配列cの実体がgp領域以外に配置されているため、複数命令での絶対アドレスアクセスになっている。一方、本図の右欄の生成コードから分かるように、配列cの実体がgp領域に置かれるように最大データサイズ(40)が指定されたので、配列cの実体がgp領域に配置され、1命令アクセスでのgp相対アクセスになり、実行速度が向上される。つまり、デフォルトでは、10サイクルで実行される8バイトのコードが生成されるのに対し、最大データサイズの変更によって、7サイクルで実行される5バイトのコードが生成される。
もう一つの具体例として、ファイル外定義の外部変数の場合の具体例を図41に示す。ここでは、gp領域配置の最大データサイズは40であるとする。本図の左欄は、ファイル外定義の外部変数に関してサイズ指定がない場合に生成されるコードの例を示し、本図の右欄は、サイズ指定がある場合に生成されるコードの例を示している。
本図の左欄に示されるように、外部定義配列aと外部定義配列cのサイズが共に不明なので、gp領域に配置されているのか否かコンパイラ100では判断できず、複数命令での絶対アドレスアクセスのコードが生成されている。
一方、本図の右欄に示されるように、配列aの定義サイズが40バイト以下なので、gp領域に実体を配置し、グローバルポインタレジスタ(gp)を用いて1命令でのgp相対アクセスのコードが生成されている。また、外部定義配列cのサイズも明示的に指定されており、gp領域配置の最大データサイズ以下であるため、配列cの実体がgp領域に配置されているものとし、gp相対アクセスのコードが生成されている。このように、ファイル外定義の外部変数のサイズを指定しない場合には、10サイクルで実行される12バイトのコードが生成されるのに対し、最大データサイズの変更と外部変数のサイズ指定とを行った場合には、7サイクルで実行される5バイトのコードが生成される。
次に、グローバル領域割り付け部121による、(2)グローバル領域配置の指定に関する最適化について説明する。
前述の、グローバル領域配置の最大データサイズ指定( -mmax-gp-datasizeオプション)では、最大データサイズでのみ、gp領域の配置を指定するため、期待しない変数までgp領域に配置されることがある。
そこで、変数ごとにgp領域の配置を指定する#pragma指令が用意されている。
・#pragma指令
#pragma _no_gp_access 変数名 [,変数名,...]
#pragma _gp_access 変数名 [,変数名,...]
ここで、[]内は省略可能を意味する。複数指定する場合、","(カンマ)で変数名を区切ればよい。なお、オプションとプラグマ指令とが重複又は矛盾した場合は、プラグマ指令が優先する。
このようなプラグマ指令に対して、コンパイラ100は次のように動作する。つまり、図39において、解析部110によってプラグマ指令「#pragma _no_gp_access 変数名 [,変数名,...]」が検出された場合には(S100、S101)、グローバル領域割り付け部121は、ここで指定された変数については、オプション指定にかかわらず、グローバル領域に配置させないコードを生成し(ステップS103)、一方、解析部110によってプラグマ指令「#pragma _gp_access 変数名 [,変数名,...]」が検出された場合には(S100、S101)、グローバル領域割り付け部121は、ここで指定された変数については、オプション指定にかかわらず、グローバル領域に配置させるコードを生成する(ステップS104)。これらの#pragma指令によって、速度向上とサイズ削減という最適化が可能となる。
なお、#pragma _no_gp_access指令が指定された場合は、グローバル領域割り付け部121は、その変数については、gp領域に実体もアドレスも配置しない。また、最大データサイズ指定よりも、#pragma _gp_access指令の方を優先する。もし、同一の変数に関して異なる指定が現れた場合には、コンパイラ100の動作は不定となる。外部変数・静的変数については、可能な限り1命令アクセス可能なgp領域に配置することが好ましい。
次に、このようなプラグマ指令用いることによる最適化の具体例を示す。#pragma _gp_access指令を使用すると、gp領域配置の最大データサイズ以上の外部変数・静的変数をgp領域に配置させることができので、その好適例を示す。
図42は、#pragma _no_gp_access指令を用いた場合に生成されるコードの例(左欄)と、#pragma _gp_access指令を用いた場合に生成されるコードの例(右欄)とを示す図である。
本図の左欄に示されるように、配列cのサイズは40バイトなのでデフォルトの場合、先頭アドレスのみgp領域に配置され、実体はgp領域に配置されない。また、外部定義されている配列aのサイズが32バイトなのでデフォルトの場合は、gp領域に実体が配置されているとコンパイラ100は判断する。
しかし、#pragma _no_gp_access指令により、配列cについては、gp領域には先頭アドレスも実体も配置されずに、gp領域以外に実体が配置され、絶対アドレスアクセスのコードが生成される。外部定義の配列aについても、gp領域以外に実体が配置されているとして、絶対アドレスアクセスのコードが生成される。
一方、本図の右欄に示されるように、配列cはサイズが40バイトなので、デフォルトの場合ではgp領域に配置されないが、#pragma _gp_access指令により配列cの実体がgp領域に配置される。ファイル外定義の配列aは、サイズが不明であるが#pragma指令により、gp領域に配置されているものとし、gp相対アクセスコードが生成される。
このように、#pragma _no_gp_access指令を用いた場合には、10サイクルで実行される12バイトのコードが生成されるのに対し、#pragma _gp_access指令を用いた場合には、7サイクルで実行される5バイトのコードが生成される。
なお、extern宣言外部変数に#pragma _gp_access指令を使用する場合、定義の指定(配置する領域)と使用側の指定(アクセスする方法)を必ず合わせておくことが好ましい。
[ソフトウェアパイプライニング部122]
次に、ソフトウェアパイプライニング部122の動作とその意義について説明する。
ソフトウェアパイプライニング最適化は、ループ高速化手法の1つである。この最適化が行われると、ループ構造がプロログ部、カーネル部、エピログ部に変換される。なお、ソフトウェアパイプライニング最適化は、それによって実行速度が向上されると判断された場合に行われる。カーネル部は、各イタレーション(繰り返し)をその前後のイタレーションとオーバーラップさせる。これにより、1イタレーション毎の平均処理時間が削減される。
図43(a)は、ループ処理におけるプロログ部・カーネル部・エピログ部の概念図である。ここでは、イタレーション間で依存関係のない命令X、Y、Zが5回繰り返される場合の命令コード、実行イメージ、生成コードイメージの例が示されている。なお、ループ処理とは、for文、while文、do文等による繰り返し処理である。
ここで、プロログ部、エピログ部は、可能であれば、図43(b)及び(c)のプロセスに示されるように、除去される。しかし、不可能であれば除去されず、コードサイズが増加することがある。そのため、ソフトウェアパイプライニング最適化の動作を指定するオプション及び#pragma指令が用意されている。
図43(b)は、ループ処理におけるプロログ部・エピログ部を除去するための処理を示す概念図である。つまり、図43(a)で示されたイタレーション間で依存関係のない命令X、Y、Zの5回の繰り返しについて、前記のプロログ部・カーネル部・エピログ部の概念図で示したループの生成コードイメージを並び替えたものが示されている。ただし、図中の [] のついた命令は、読み込まれるが実行されないとする。
このようにすると、プロログ部・エピログ部は、カーネル部と同じ命令並びになることがわかる。よって、ループ回数は、プロログ部・エピログ部の実行分(4回)だけ増えるが、[]のついた命令をプレディケート(実行条件)によって制御することにより、図43(c)に示されるように、カーネル部だけでコードを生成することができる。
図43(c)に示された生成コードの実行順序は、以下のようになる。
1回目においては、プレディケート[C2]、[C3]の付加された命令は実行されない。よって、[C4]X のみ実行される。
2回目においては、プレディケート[C2]の付加された命令は実行されない。よって、[C3]Y 、[C4]X のみ実行される。
3〜5回目においては、[C2]Z、 [C3]Y、 [C4]X すべてが実行される。
6回目においては、プレディケート[C4]の付加された命令は実行されない。よって、[C2]Zと [C3]Y のみ実行される。
7回目においては、プレディケート[C3]、[C4]の付加された命令は実行されない。よって、[C2]Z のみ実行される。
このように、カーネル部のループ1回目、2回目でプロログ部を、6回目、7回目でエピログ部を実行していることになる。
よって、プロログ部・エピログ部のあるループでは、コードサイズが増加するが、ループ回数が減少するため、実行速度向上を期待できる。逆に、プロログ部・エピログ部を除去したループでは、コードサイズを削減できるが、ループ回数が増加するため、実行サイクル数が増加する。
そこで、このような最適化の選択を指定可能にするために、以下のコンパイルオプションとプラグマ指令が用意されている。
・コンパイルオプション
-fno-software-pipelining
・#pragma指令
#pragma _no_software_pipelining
#pragma _software_pipelining_no_proepi
#pragma _software_pipelining_with_proepi
なお、オプションとプラグマ指令が重複又は矛盾した場合には、プラグマ指令が優先する。
図44は、ソフトウェアパイプライニング部122の動作を示すフローチャートである。解析部110によってオプション「-fno-software-pipelining」が検出された場合には(ステップS110、S111)、ソフトウェアパイプライニング部122は、対象となるソースプログラム101中の全てのループ処理に対してソフトウェアパイプライニング最適化を行わない(ステップS112)。このオプションによって、コードサイズが増加してしまうことが回避される。
また、解析部110によってプラグマ指令「#pragma _no_software_pipelining」が検出された場合には(ステップS110、S111)、ソフトウェアパイプライニング部122は、オプション指定にかかわらず、この指定の直後に置かれている1つのループ処理について、ソフトウェアパイプライニング最適化を行わない(ステップS113)。これによって、コードサイズが削減される。
また、解析部110によってプラグマ指令「#pragma _software_pipelining_no_proepi」が検出された場合には(ステップS110、S111)、ソフトウェアパイプライニング部122は、オプション指定にかかわらず、この指定の直後に置かれている1つのループ処理について、プロログ部・エピログ部が除去できる範囲でソフトウェアパイプライニング最適化を行う(ステップS114)。これによって、速度の向上とサイズの削減化が図られる。
また、解析部110によってプラグマ指令「#pragma _software_pipelining_with_proepi」が検出された場合には(ステップS110、S111)、ソフトウェアパイプライニング部122は、オプション指定にかかわらず、この指定の直後に置かれている1つのループ処理について、プロログ部・エピログ部を除去せずに、可能な範囲で、ソフトウェアパイプライニング最適化を行う(ステップS115)。これによって、速度が向上される。
なお、ソフトウェアパイプライニング部122は、#pragma _software_pipelining_no_proepi指令に対しては、プロログ部・エピログ部を除去できる範囲でソフトウェアパイプライニング最適化を行うが、#pragma _software_pipelining_with_proepi指令に対しては、プロログ部・エピログ部の除去が可能であっても、除去しない。プロログ部・エピログ部の除去可能なループであっても、図45に示される例のように、プロログ部・エピログ部の除去を抑制することにより、コードサイズは増加するが、実行速度の向上を期待できるからである。また、後述するように、ループ処理の最低繰り返し回数がソフトウェアパイプライニングによって重なり合うイタレーション数以上である場合には、ソフトウェアパイプライニング部122は、ソフトウェアパイプライニングによる最適化を行う。
図45は、ソフトウェアパイプライニング最適化の例を示す図である。なお、この例では、ソフトウェアパイプライニング最適化を行うために、コンパイルオプション-O(実行速度とコードサイズ削減の最適化)をつけてコンパイルされている。
本図の左欄の中下段に示された機械語プログラム102の例から分かるように、デフォルトのソフトウェアパイプライニング最適化が行われた場合には、プロログ部・エピログ部のコードも除去され、ループ回数が101回でカーネル部のサイクル数が2サイクルとなり、合計207サイクルで実行され、ループの性能が向上している。
一方、本図の右欄の中上段に示されたソースプログラム101から分かるように、左欄のソースプログラム101に対して #pragma _software_pipelining_with_proepi指令が追加指定され、ループのプロログ部・エピログ部の除去を抑制した例となっている。これにより、右欄の中下段に示された機械語プログラム102の例から分かるように、プロログ部・エピログ部のコードが生成されるため、左側と比べコードサイズが増加しているが、ループ回数が99回に減少しており、カーネル部サイクル数が2サイクルであるため、合計204サイクルで実行され、左欄の場合よりも更に実行速度が向上している。なお、プロログ部・エピログ部が周辺コードと並列実行可能な場合には、プロログ部・エピログ部による速度低下の影響は隠蔽できる。
[ループアンローリング部123]
次に、ループアンローリング部123の動作とその意義について説明する。ループアンローリング部123は、大きく分けて、(1)ループアンローリングの指定に関する最適化と、(2)ループの繰り返し回数についての保証に関する最適化とを行う。
まず、(1)ループアンローリングの指定に関する最適化について説明する。
ループアンローリング最適化とは、ループ高速化手法の1つである。複数のイタレーションを同時に実行することでループ内の実行を高速化する。ループアンローリング最適化を行うことにより、ldp/stp命令の生成や並列度の向上により、実行速度の向上を図ることができる。しかし、コードサイズが増加することと、場合によっては、レジスタ不足によるスピルが発生し、逆に性能が低下してしまう場合がある。
なお、ロードペア(ストアペア)命令(ldp/stp命令)とは、二つのロード命令(ストア命令)を1命令で実現した命令である。また、「スピル」とは、空きレジスタを確保する為に、使用されているレジスタを一時的にスタックに退避させることである。この場合には、レジスタの退避・復帰のためにロード・ストア命令が生成される。
このようなループアンローリング最適化の動作を指定するオプション及び#pragma指令が用意されている。
・コンパイルオプション
-fno-loop-unroll
・#pragma指令
#pragma _loop_unroll
#pragma _no_loop_unroll
図46は、ループアンローリング部123の動作を示すフローチャートである。解析部110によってオプション「-fno-loop-unroll」が検出された場合には(ステップS120、S121)、ループアンローリング部123は、対象となるソースプログラム101中の全てのループ処理に対してループアンローリング最適化を行わない(ステップS122)。このオプションによって、コードサイズが増加してしまうことが回避される。
また、解析部110によってプラグマ指令「#pragma _loop_unroll」が検出された場合には(ステップS120、S121)、ループアンローリング部123は、直後に置かれている1つのループ処理に対してループアンローリング最適化を行う(ステップS123)。これによって、速度が向上される。
また、解析部110によってプラグマ指令「#pragma _no_loop_unroll」が検出された場合には(ステップS120、S121)、ループアンローリング部123は、直後に置かれている1つのループ処理に対してループアンローリング最適化を行わない(ステップS124)。これによってコードサイズの増加が回避される。
なお、最適化レベル指定に-O/-Ot(実行速度を優先した最適化)が指定されている場合は、ループアンローリング部123は、ループアンローリング最適化が可能であるなら、デフォルトでループアンローリング最適化を行う。最適化レベル指定に-Os(コードサイズ削減を優先した最適化)が指定されている場合は、ループアンローリング部123は、ループアンローリング最適化を行わない。よって、ユーザは、これらの最適化レベル指定コンパイルオプションと組み合わせて、個々のループのループアンローリング最適化の適用を、#pragma _no_loop_unroll指令及び#pragma _loop_unroll 指令で制御することが可能である。
図47は、#pragma _loop_unroll指令による最適化の例を示す図である。本図の左欄は、最適化レベル指定コンパイルオプション -Oのみをつけてコンパイルした場合の例であり、本図の右欄は、#pragma _loop_unroll指令を組み合わせてコンパイルした場合の例である。
本図の左欄の中下段に示された機械語プログラム102の例から分かるように、プロログ部・エピログ部が除去されたソフトウェアパイプライニング最適化が適用されている。そのため、カーネル部の3命令(2サイクル)が101回実行され、全体として合計207サイクルかかっている。
一方、右欄の中下段に示された機械語プログラム102の例から分かるように、左側と同様にソフトウェアパイプライニング最適化が行われ、プロログ部・エピログ部が削除されている。それに、この右欄の機械語プログラム102では、ループアンローリング最適化により、ループ回数が半減しているため、カーネル部の6命令(2サイクル)が52回実行され、全体として合計110サイクルで実行され、速度が向上している。
次に、ペアメモリアクセス命令(ldp/stp)の生成によりループアンローリング最適化をより効果的に使用する方法を示す。
ループアンローリング最適化では、現在のイタレーションと次のイタレーションを同時に実行するため、以下のような連続する領域のデータのロード・ストアが生成される場合がある。
ld r1,(r4);;
ld r2,(r4,4);;
アクセスするデータが、必ず8バイトアラインされて配置されているならば、以下のようなペアメモリアクセス命令(ldp命令)を生成することができる。
ldp r1:r2,(r4+);;
図48は、ペアメモリアクセス命令(ldp/stp)の生成によりループアンロー
リング最適化をより効果的に使用する例を示す図である。ここでは、ソフトウェアパイプライニング最適化が適用されている。
本図の右欄の例では、中下段に示された機械語プログラム102の例から分かるように、ループアンローリング最適化によりループ回数が半減されている。また、#pragma _align_local_pointer指令を使用して、ポインタ変数 pa、pbが8バイトアラインされているアドレスと明示することにより、ロードペア(ストアペア)命令が生成される。
これらの最適化により、左欄の例では、カーネル部の5命令3サイクルが101回実行され、全体として合計308サイクルであるが、右欄の例では、カーネル部の7命令3サイクルが半分の51回実行され、全体として合計158サイクルで実行され、速度が向上している。
次に、ループアンローリング部123による、(2)ループの繰り返し回数の保証に関する最適化について説明する。
プログラムの記述上、コンパイラ100ではループ回数を特定することができない場合、ループ高速化の各最適化を効果的に行うことができない。
そこで、ユーザは、下記に示す#pragma指令にて、ループ回数の情報を提供することにより、より効果的にソフトウェアパイプライニング等のループ高速化の最適化を行わせることができる。
・#pragma指令
#pragma _min_iteration=NUM
#pragma _iteration_even
#pragma _iteration_odd
図46において、解析部110によってプラグマ指令「#pragma _min_iteration=NUM」が検出された場合には(ステップS120、S121)、ループアンローリング部123は、直後に置かれている1つのループ処理が最低NUM回繰り返されることを前提に、ループアンローリング最適化を行う(ステップS125)。例えば、例えば、保証された繰り返し最低回数がループアンローリングによる展開数以上である場合に、ループアンローリング部123は、そのループ処理のループアンロールを行う。これによって、速度の向上とサイズの削減が図られる。
また、解析部110によってプラグマ指令「#pragma _iteration_even」が検出された場合には(ステップS120、S121)、ループアンローリング部123は、直後に置かれている1つのループ処理が偶数回繰り返されることを前提に、ループアンローリング最適化を行う(ステップS126)。これによって、実行速度が向上される。
また、解析部110によってプラグマ指令「#pragma _iteration_odd」が検出された場合には(ステップS120、S121)、ループアンローリング部123は、直後に置かれている1つのループ処理が奇数回繰り返されることを前提に、ループアンローリング最適化を行う(ステップS126)。これによって、実行速度が向上される。
なお、#pragma _min_iteration指令で1以上の値を指定した場合、1回もループを通らない場合のために生成されるエスケープコードを除去できるという効果もある。また、繰り返し回数が不明なループに対して、ループアンローリング最適化を期待する場合、遇数回ループか奇数回ループかが決まっているならば、_iteration_even /#pragma _iteration_odd指令を使用することにより、ループアンローリング最適化の適用が可能になるため、実行速度向上を期待することができる。
図49は、#pragma _min_iteration指令による最適化の例を示す図である。ここでは、繰り返し回数が不明なループでの、#pragma _min_iteration指令の使用効果が示されている。ただし、サイクル比較のため、引数endの値を100とする。
本図の左欄では、中下段に示された機械語プログラム102の例から分かるように、ループ回数が不明なため、一度もループを実行しない場合にループ本体を飛び越すための cmple/br 命令(エスケープコード)が生成されている。また、ループ命令の生成を行うことができないため、加算命令・比較命令・ジャンプ命令でループが生成されている。サイクル数は、ループ部が7命令4サイクルの100回繰り返しとなり、全体として合計405サイクルとなっている。
一方、本図の右欄では、中上段に示されたソースプログラム101の例から分かるように、繰り返し回数が不明であるが、最低4回繰り返されることが#pragma _min_iteration指令で指定されている。これにより、ループ回数が0回の場合を考慮する必要が無いため、ループアンローリング部123は、エスケープコードを生成する必要がなくなる。
また、ループ最低回数を考慮して、ループアンローリング部123は、ループ命令を生成することができる。例えば、保証された繰り返し最低回数(4)がループアンローリングによる展開数(この例では、3サイクル)以上であるので、ループアンローリング部123は、ループアンロールを行う。
さらに、この例では、さらにソフトウェアパイプライニング最適化が可能になっている。これは、保証されたループの繰り返し最低回数(4)がソフトウェアパイプライニングによって重なり合うイタレーション数以上であったために、ソフトウェアパイプライニング部122がソフトウェアパイプライニングによる最適化を行ったためである。
右欄の中下段に示された機械語プログラム102の例から分かるように、サイクル数は、ループ部が5命令3サイクルの101回繰り返しとなり、全体として合計308サイクルとなり、実行速度とサイズ削減が実現されている。
図50及び図51は、#pragma _iteration_even/#pragma _iteration_odd指令による最適化の例を示す図である。図50は、ループ回数が不明の場合におけるソースプログラム101の例(左欄)と、そこから生成される機械語プログラム102の例(右欄)を示す図である。本図から分かるように、実際のループ回数が不明な場合、ループアンローリング最適化は適用できない。これは、ループ回数が偶数回の場合と奇数回の場合では、ループアンローリング最適化によって生成されるコードが異なるためである。
ところが、図51に示されるように、繰り返し回数が不明なループの場合でも、遇数回ループか奇数回ループかを指定することにより、ループアンローリング最適化を適用することができる。
本図の左欄では、ループ回数が偶数回であることを、#pragma _iteration_even指令で指定されているため、ループアンローリング部123によるループアンローリング最適化が行われ、左欄の中下段に示された機械語プログラム102の例から分かるように、偶数回用のコードが生成されている。
また、本図の右欄では、ループ回数が奇数回であることを、#pragma _iteration_odd指令により指定されているため、ループアンローリング部123によるループアンローリング最適化が行われ、右欄の中下段に示された機械語プログラム102の例から分かるように、奇数回用のコードが生成されている。この右欄の例から分かるように、左欄に示された偶数回の場合の生成コードと初期化部・ループ部はほぼ同じで、後処理部に、ループの最後の一回分を実行するコードが生成されている。
このように、ループ回数が不明であっても、偶数回であるか奇数回であるかを保証することで、ループアンローリング部123は、ループアンローリング最適化を行うことができ、これによって実行速度が向上される。
[if変換部124]
次に、if変換部124の動作とその意義について説明する。
通常、C言語プログラムのif構造をコンパイルすると、分岐命令(br命令)が生成される。これに対して、if変換とは、C言語プログラムのif構造を分岐命令を用いることなく、条件付き実行命令だけに書き換えることである。これによって、実行順序が固定化される(順次実行となる)るので、パイプラインの乱れが回避され、実行速度が向上され得る。なお、条件付き実行命令とは、その命令に含まれる条件(プレディケート)がプロセッサ1の状態(コンディションフラグ)と一致している場合にだけ実行される命令である。
if変換により、if構造のワーストケースにおける実行時間は短縮されるが、ベストケースにおける実行時間は(短縮後の)ワースト実行時間と等しくなる。そのために、if構造の特性(条件成立・不成立それぞれの発生頻度や各パスの実行サイクル数)に応じて、if変換を適用すべき場合とすべきでない場合がある。
このため、ユーザは、適用の可否をコンパイルオプションや#pragma指令で指示することができる。
・コンパイルオプション
-fno-if-conversion
・#pragma指令
#pragma _if_conversion
#pragma _no_if_conversion
なお、オプションとプラグマ指令が重複又は矛盾した場合には、プラグマ指令が優先する。
図52は、if変換部124の動作を示すフローチャートである。解析部110によってオプション「-fno-if-conversion」が検出された場合には(ステップS130、S131)、if変換部124は、対象となるソースプログラム101中の全てのif構造文に対してif変換を行わない(ステップS132)。なお、本オプションが検出されない場合は、if変換部124は、if変換が可能であり、かつ、そのワーストケースの時間がif変換前に対して短いif構造文である場合に、そのif構造文をif変換する。
また、解析部110によってプラグマ指令「#pragma _if_conversion」が検出された場合には(ステップS130、S131)、if変換部124は、オプション指定にかかわらず、直後に置かれている1つのif構造文に対して、可能であればif変換を行う(ステップS133)。これによって、速度が向上される。
また、解析部110によってプラグマ指令「#pragma _no_if_conversion」が検出された場合には(ステップS130、S131)、if変換部124は、オプション指定にかかわらず、直後に置かれている1つのif構造文に対して、if変換を行わない(ステップS134)。これによって、速度が向上される。
図53は、#pragma _no_if_conversion指令でコンパイルした場合と、#pragma _if_conversion指令でコンパイルした場合の機械語プログラム102の例を示す図である。
本図の左欄では、中下段の機械語プログラム102の例から分かるように、if変換を抑制したことにより、分岐命令が生成されている(実行サイクル数:5あるいは7、 コードサイズ:12バイト)。
一方、本図の右欄では、中下段の機械語プログラム102の例から分かるように、#pragma指令によってif変換を行うこととしたことにより、分岐命令が、条件付き命令(プレディケート付き命令)に置き換わっている(実行サイクル数:4、 コードサイズ:8バイト)。このように、if変換を実施することで、実行速度比1.25倍、 コードサイズ比67%が達成されている。
[ペア命令生成部125]
次に、ペア命令生成部125の動作とその意義について説明する。ペア命令生成部125は、大きく分けて、(1)配列・構造体のアラインメントの設定に関する最適化と、(2)仮引数ポインタ・ローカルポインタのアラインの保証に関する最適化とを行う。
まず、(1)配列・構造体のアラインメントの設定に関する最適化について説明する。
ユーザは、以下のオプションを用いて、配列と構造体の先頭アドレスのアラインを指定することができる。アラインメントを調整することで、メモリアクセス命令のペアリング(2つのレジスタとメモリ間の転送をひとつの命令で行うこと)が可能となり、実行速度の向上が期待できる。その反面、アラインメント値を大きくすると、データの未使用領域が増加し、データサイズが増大する可能性がある。
・コンパイルオプション
-falign_char_array=NUM (NUM=2,4または8)
-falign_short_array=NUM (NUM=4または8)
-falign_int_array=NUM (NUM=8)
-falign_all_array=NUM (NUM=2,4または8)
-falign_struct=NUM (NUM=2,4または8)
上記オプションは、上から順に、char型の配列、short型整数、int型整数、それら3つののデータ型全ての配列、構造体のアラインメントを指定している。また、"NUM"は、アラインするサイズ(バイト)を示す。
図54は、ペア命令生成部125の動作を示すフローチャートである。解析部110によって上記オプションのいずれかが検出された場合には(ステップS140、S141)、ペア命令生成部125は、対象となるソースプログラム101で宣言されている指定された型の全ての配列又は構造体について、その先頭アドレスが指定されたNUMバイトのアラインとなるように配列又は構造体をメモリに配置し、その配列又は構造体にアクセスする命令については、可能な場合に、ペアリング(2つのレジスタとメモリ間の転送を並行して行う命令の生成)を行う(ステップS142)。これによって、実行速度が向上される。
図55は、サンプルプログラムをオプションなしでコンパイルした場合と、オプション'-falign-short-array=4'でコンパイルした場合のアセンブリコードを示す図である。
本図の左欄に示されたオプションなしの場合、中下段に示された機械語プログラム102の例から分かるように、アラインメントが不明のため、ロード命令のペアリング(2つのレジスタとメモリ間の転送をひとつの命令で行う)ができない(実行サイクル数:25、 コードサイズ:22)。
一方、本図の右欄に示されたオプションありの場合、配列が4バイトでアラインされるため、中下段に示された機械語プログラム102の例から分かるように、最適化部120によるペアリングが実現されている(実行サイクル数:15、 コードサイズ:18)。このように、アラインメントの指定によって、実行速度比1.67倍、 コードサイズ比82%が達成されている。
次に、ペア命令生成部125による、(2)仮引数ポインタ・ローカルポインタのアラインの保証に関する最適化を説明する。
ユーザは、以下のプラグマ指令を用いて、関数引数のポインタ変数の指すデータのアラインメントや、ローカルポインタ変数の指すデータのアラインメントを保証することで、最適化部120によるメモリアクセス命令のペアリングが可能となり、実行速度の向上が期待できる。
・#pragma指令
#pragma _align_parm_pointer=NUM 変数名 [, 変数名, …]
#pragma _align_local_pointer=NUM 変数名 [, 変数名, …]
なお、"NUM"はアラインするサイズ(2,4又は8バイト)を表す。また、上記#pragma指令で保証されたポインタ変数の指すデータが指定されたバイト境界にアラインされていなかった場合には、プログラムの正常動作は保証されない。
図54において、解析部110によってプラグマ指令「#pragma _align_parm_pointer=NUM 変数名 [, 変数名, …]」が検出された場合には(ステップS140、S141)、ペア命令生成部125は、"変数名"で示される引数のポインタ変数の指すデータが引数渡しの時点でNUMバイトにアラインされているものとし、その配列にアクセスする命令については、可能な場合に、ペアリングを行う(ステップS143)。これによって、実行速度が向上される。
また、解析部110によってプラグマ指令「#pragma _align_local_pointer=NUM 変数名[, 変数名, …]」が検出された場合には(ステップS140、S141)、ペア命令生成部125は、"変数名"で示されるローカルポインタ変数の指すデータが関数内部で常にNUMバイトでアラインされているものとし、その配列にアクセスする命令については、可能な場合に、ペアリングを行う(ステップS144)。これによって、実行速度が向上される。
図56は、プラグマ指令「#pragma _align_parm_pointer=NUM 変数名 [, 変数名, …]」による最適化の例を示す図である。
本図の左欄に示されるように、#pragma _align_parm_pointerを与えない場合、ポインタ変数srcの指すデータのアラインメントが不明なため、中下段に示された機械語プログラム102の例から分かるように、各データはそれぞれ独立にロードされる(実行サイクル数160、コードサイズ:24バイト)。
一方、本図の右欄に示されるように、#pragma指令を与えると、データは4バイト境界にアラインされるため、中下段の機械語プログラム102の例から分かるように、メモリ読み出しのペアリングが行われる(実行サイクル数:107、 コードサイズ:18バイト)。このように、アライメントを指定することで、実行速度比1.50倍、 コードサイズ比43%が達成される。
図57は、プラグマ指令「#pragma _align_local_pointer=NUM 変数名 [, 変数名, …]」による最適化の例を示す図である。
本図の左欄に示されるように、#pragma _align_local_pointerを与えない場合、ポインタ変数from、toの指すデータのアラインメントが不明なため、中下段に示された機械語プログラム102の例から分かるように、配列要素はそれぞれ独立にロードされる(実行サイクル数:72、 コードサイズ:30)。
一方、本図の右欄に示されるように、#pragma _align_parm_pointerを与えることで、中下段に示された機械語プログラム102の例から分かるように、ポインタ変数from、toの指すデータが4バイト境界にアラインされていることを利用したメモリ読み出しのペアリングが可能となる。(実行サイクル数:56、 コードサイズ:22)。このように、アライメントを指定することで、実行速度比1.32倍、 コードサイズ比73%が達成される。