2012年4月13日金曜日

Power アーキテクチャーのためのアセンブリー言語: 第 3 回: PowerPC 分岐プロセッサーでのプログラミング


分岐レジスター

PowerPC での分岐は、条件レジスター、カウント・レジスター、そしてリンク・レジスターという 3 つの専用レジスターを利用します。

条件レジスター

条件レジスターは概念的には 7 つのフィールドで構成されます。各フィールドは 4 ビット単位のセグメントで、命令に対する結果についてのステータス情報を格納するために使用されます。7 つのフィールドのうち、2 つはこの後説明する専用フィールドで、残りの 5 つは一般的な用途に使用できます。これらのフィールドには、cr0 から cr7 までの名前が付けられています。

最初のフィールドである cr0 は、即値オペランド以外のオペランドを使用する (若干の例外はありますが) 固定小数点演算命令の結果を格納するのに使用されます。演算結果はゼロと比較され、対応するビットがセットされます (負、ゼロ、または正)。演算関連の命令でその命令に cr0 をセットさせたい場合は、単に命令の最後にピリオド (.) を追加します。例えば、add 4, 5, 6 とすると、レジスター 5 をレジスター 6 に加算し、その結果はレジスター 4 に格納されますが、cr0 にはステータス・ビットがセットされません。一方、add. 4, 5, 6 とすると、内容は同じでも、算出された値に応じて cr0 のビットがセットされます。cr0 は、比較命令で使用されるデフォルト・フィールドでもあります。

2 番目のフィールド (cr1) は、命令名の後にピリオドを使った浮動小数点命令が使用します。浮動小数点演算については、この記事では説明しません。

それぞれのフィールドには 4 つのビットがあります。これらのビットの使用方法は、どの命令を使用しているかによって異なります。以下に、考えられる使用方法をリストします (浮動小数点での使用方法もリストしていますが、この記事では説明しません)。


条件レジスターのフィールド・ビット
ビットニーモニック固定小数点比較固定小数点演算浮動小数点比較浮動小数点演算
0ltより小さいより小さい例外サマリー
1gtより大きいより大きい有効化例外サマリー
2eq等しいゼロ等しい無効演算例外サマリー
3soサマリー・オーバーフローサマリー・オーバーフロー比較不能オーバーフロー例外

これらのフィールドに暗黙的にアクセスする方法および直接的にアクセスする方法については、後で説明します。

条件レジスターと汎用レジスター間のロードは、mtcr、mtcrf、および mfcr を使って行います。mtcr は指定された汎用レジスターを条件レジスターに移し、mfcr は条件レジスターを汎用レジスターに移します。mtcrf は、汎用レジスターから条件レジスターをロードしますが、ロードされるのは最初のオペランドである 8 ビット・マスクで指定されたフィールドだけです。

いくつか例を挙げてみましょう。


リスト 1. 条件レジスターの転送例
                  #Copy register 4 to the condition register mtcr 4  #Copy the condition register to register 28 mfcr 28  #Copy fields 0, 1, 2, and 7 from register 18 to the condition register mtcrf 0b11100001, 18 

カウント・レジスターとリンク・レジスター

リンク・レジスター (LR) は、分岐命令からの戻りアドレスを格納する専用レジスターです。すべての分岐命令は、リンク・レジスターを設定するように指定できます。この場合、分岐が発生するとリンク・レジスターが現行命令の直後に続く命令のアドレスに設定されます。命令の最後に文字 l を追加すると、分岐命令はリンク・レジスターを設定するようになります。例えば、b は無条件分岐命令、bl はリンク・レジスターを設定する無条件分岐命令となります。

カウント・レジスター (CTR) は、ループ・カウンターを格納するように設計された専用レジスターです。特殊分岐命令はカウント・レジスターをデクリメントしたり、CTR がゼロに達したかどうかによって条件分岐することができます。

リンク・レジスターとカウント・レジスターはどちらも分岐宛先として使用できます。bctr はカウント・レジスターに指定されたアドレスに分岐し、blr はリンク・レジスターに指定されたアドレスに分岐します。

また、汎用レジスターからリンク・レジスターとカウント・レジスターをロードしてコピーすることもできます。リンク・レジスターでは、mtlr は指定レジスターの値をリンク・レジスターに移し、mflr はリンク・レジスターの値を汎用レジスターに移します。カウント・レジスターの場合、これに対応するのは mtctr と mfctr です。

無条件分岐

PowerPC 命令セットでの無条件分岐は、I-Form 命令形式を使用します。

I-Form 命令形式

ビット 0 ~ 5

オペコード

ビット 6 ~ 29

絶対または相対分岐アドレス


何パーセントの労働者は労働組合に属している
ビット 30

絶対アドレス・ビット。このフィールドがセットされている場合、命令は絶対アドレスとして解釈されます。セットされていない場合は、相対アドレスとして解釈されます。

ビット 31

リンク・ビット。このフィールドがセットされている場合、命令はリンク・レジスターに次の命令のアドレスを設定します。

前述したように、文字 l を分岐命令に追加するとリンク・ビットがセットされるので、「戻りアドレス」(分岐から戻った後の命令) がリンク・レジスターに格納されます。末尾に文字 a を追加すると (l が使用されている場合はその後)、指定されたアドレスは絶対アドレスとなります (こうすると分岐宛先があまりにも限定されるため、ユーザー・レベルのコードに a が使用されることはあまりありません)。

以下のリスト 2 は無条件分岐を示した後、終了します (branch_example.s と入力してください)。


リスト 2. 無条件分岐の例
                  ### ENTRY POINT DECLARATION ### .section .opd, "aw" .align 3 .globl _start _start: .quad ._start, .TOC#branch to target t2 ._start: b t2  t1: #branch to target t3, setting the link register bl t3 #This is the instruction that it returns to b t4  t2: #branch to target t1 as an absolute address ba t1  t3: #branch to the address specified in the link register #(i.e. the return address) blr  t4: li 0, 1 li 3, 0 sc 

上記をアセンブル、リンク、そして実行するには、以下のようにします。

as -a64 branch_example.s -o branch_example.o
ld -melf64ppc branch_example.o -o branch_example
./branch_example

b と ba のターゲットは、命令ではそれぞれ異なったコードになりますが、アセンブリー言語では両方とも同じように指定されることに注目してください。ターゲット・アドレスは、アセンブラーとリンカーによって自動的に相対アドレスまたは絶対アドレスに変換されます。

条件分岐

レジスターの比較

レジスターを他のレジスターまたは即値オペランドと比較して、条件レジスターの対応するステータス・ビットをセットするには、cmp 命令を使用します。デフォルトでは、固定小数点比較命令は cr0 で結果を格納しますが、このフィールドはオプションの第 1 オペランドとしても指定できます。比較命令は、リスト 3 のように書きます。


リスト 3. 比較命令の例
                  #Compare register 3 and register 4 as doublewords (64 bits) cmpd 3, 4  #Compare register 5 and register 10 as unsigned doublewords (64 bits) cmpld 5, 10  #Compare register 6 with the number 12 as words (32 bits) cmpwi 6, 12  #Compare register 30 and register 31 as doublewords (64 bits) #and store the result in cr4 cmpd cr4, 30, 31 

上記のリストを見るとわかるように、d はオペランドをダブルワードとして指定し、w はオペランドをシングルワードとして指定します。i は最終オペランドがレジスターではなく即値であることを示し、l はプロセッサーに、符号付き比較ではなく符号なし比較 (論理比較とも呼ばれます) を行うように指示します。

これらの命令がそれぞれ (この記事ですでに概説したように) 条件レジスターの対応するビットをセットするため、条件分岐命令で条件レジスターを使用できるようになります。

条件分岐の基本

条件分岐は無条件分岐に比べ、柔軟性には遥かに優れていますが、分岐可能な距離が犠牲となります。条件分岐が使用する命令形式は B-Form です。

B-Form 命令形式

ビット 0 ~ 5

オペコード

ビット 6 ~ 10

ビットのテスト方法、カウンター・レジスターを使用するかどうか、そして使用する場合はその方法、さらに分岐予測のヒントに関して使用するオプションを指定します (BO フィールドと呼ばれます)。

ビット 11 ~ 15

テストする条件レジスター内のビットを指定します (BIフィールドと呼ばれます)。

ビット 16 ~ 29

絶対アドレスまたは相対アドレス

ビット 30

アドレッシング・モード。0 に設定されている場合、指定されたアドレスは相対アドレスであると見なされます。1 に設定されている場合、アドレスは絶対アドレスであると見なされます。

ビット 31

リンク・ビット。1 に設定されている場合、リンク・レジスターは現行命令に続くアドレスに設定されます。0 に設定されている場合、リンク・レジスターは設定されません。


ここで、iは12定規を印刷することができますか?

以上のように、分岐のモードと条件を指定するには 10 ビットすべてが使用されるため、アドレス・サイズは 14 ビットに制限されてしまいます (16K の範囲のみ)。これは関数内での小さなジャンプには使用できますが、それ以外にはあまり使用できません。この 16K の範囲内にない関数を条件付きでコールするためには、正しい場所への無条件分岐が含まれる命令へ条件分岐するコードが必要になります。

条件分岐の基本形は、以下のようになります。

bc BO, BI, address
bcl BO, BI, address
bca BO, BI, address
bcla BO, BI, address

この基本形では、BO と BI は番号です。ありがたいことに、すべての番号とそれぞれの意味を暗記する必要はありません。ここでも救いの手を差し伸べるのは PowerPC 命令セットの拡張ニーモニック (連載の第 1 回で説明) で、この拡張ニーモニックのおかげでフィールド番号すべてを覚えなくても済むからです。無条件分岐と同様に、リンク・レジスターを設定するには命令名に I を追加し、命令に相対アドレスではなく絶対アドレスを使用させるには a を追加します。

単純な比較を行い、等しい場合に分岐する条件分岐の (拡張ニーモニックを使用しない) 基本形は、以下のようになります。


リスト 4. 条件分岐の基本形
                  #compare register 4 and 5 cmpd 4, 5 #branch if they are equal bc 12, 2 address 

bc は「条件付きで分岐 (branch conditionally)」を表し、12 (BO オペランド) は指定の条件レジスター・フィールドがセットされている場合に分岐することを意味します。分岐予測ヒントはなく、2 (BI オペランド) は条件レジスター内でテストするビットです (等価ビット)。このような分岐コード番号と条件レジスター・ビット番号すべてを覚えられる人はほとんどいないでしょう。初心者なら、それはなおさらのことです。また、覚えられたとしても役に立つとは思えません。拡張ニーモニックによってコードは簡潔になり、コードの読み書きやデバッグが容易になります。

拡張ニーモニックを指定する方法は何通りかあります。ここで取り上げる方法は、命令名と命令の BO オペランド (モードの指定) を組み合わせるというものです。最も単純なニーモニックは bt とbf で、bt は条件レジスターの指定ビットが true の場合に分岐し、bf は条件レジスターの指定ビットが false の場合に分岐します。さらに、条件レジスター・ビットはニーモニックでも指定できます。例えば 4*cr3+eq と指定すると、cr3 のビット 2 がテストされます (4* とあるのは、各フィールドが 4 ビット幅だからです)。ビット・フィールドのそれぞれのビットに使用できるニーモニックは、条件レジスターについての説明で記載したとおりです。フィールドを指定しないでビットだけを指定すると、命令が cr0 にデフォルト設定します。

以下にいくつかの例を挙げます。


リスト 5. 単純な条件分岐
                  #Branch if the equal bit of cr0 is set bt eq, where_i_want_to_go  #Branch if the equal bit of cr1 is not set bf 4*cr1+eq, where_i_want_to_go  #Branch if the negative bit (mnemonic is "lt") of cr5 is set bt 4*cr5+lt, where_i_want_to_go 

もう 1 つの拡張ニーモニックのセットは、命令、BO オペランド、条件ビット (フィールドではなく) を組み合わせるというもので、各種の一般的な条件分岐におおよそ「従来」のようなニーモニックを使用します。例えば、bne my_destination (my_destination と等しくなければ分岐) は、bf eq, my_destination (eq ビットが my_destination に対して false であれば分岐) と同じことです。このニーモニックのセットで異なる条件レジスター・フィールドを使うには、単にターゲット・アドレスの前にあるオペランドにそのフィールドを指定します (bne cr4, my_destination など)。このパターンに従った分岐ニーモニックには、blt (より小さい)、ble (より小さいか等しい)、beq (等しい)、bge (より大きいか等しい)、bgt (より大きい)、bnl (以上)、bne (等しくない)、bng (以下)、bso (サマリー・オーバーフロー)、bns (サマリー・オーバーフローでない)、bun (比較不能 - 浮動小数点固有)、そして bnu (比較不能でない - 浮動小数点固有) があります。

いずれのニーモニックと拡張ニーモニックでも、I または a、あるいはその両方を追加してリンク・レジスターや絶対アドレッシングを有効にできます。

拡張ニーモニックを使うと、一層読み書きしやすいプログラミング・スタイルが実現します。高度な条件分岐の場合、拡張ニーモニックは役に立つだけでなく、なくてはならないものです。

条件レジスターのその他の機能

条件レジスターには複数のフィールドがあるため、それぞれの演算と比較に異なるフィールドを使用し、それから論理演算を使用して条件を組み合わせることができます。論理演算はいずれも、cr target_bit, operand_bit_1, operand_bit_2 という形になります。例えば、cr2 の eq ビットと cr7 の lt ビットで論理 and を行い、cr0 の eq ビットに結果を格納するには、crand 4*cr0+eq, 4*cr2+eq, 4*cr7+lt とします。

mcrf を使うと、条件レジスター・フィールドを移動できます。例えば mcrf cr1, cr4 と書き込むと、cr4 が cr1 にコピーされます。


すべてを学ぶ方法

分岐命令は、分岐プロセッサーに分岐予測のヒントを与えることもできます。ほとんどの条件分岐命令では、命令に + を追加すると、分岐プロセッサーに分岐が発生する可能性が高いという信号が送られます。命令に - を追加した場合は、分岐がおそらく発生しないという信号が送られます。ただし、通常はこのような信号が必要になることはありません。一般的に、POWER5 CPU 内の分岐プロセッサーには優れた分岐予測能力があるからです。

カウント・レジスターの使用

カウント・レジスターはループ・カウンター専用のレジスターです。条件レジスター・ビットのテスト方法を指定した上で条件分岐の BO オペランド (モードの制御) を使用すると、カウント・レジスターをデクリメントしてテストできます。カウント・レジスターで可能な操作には、以下の 2 つがあります。

  • カウント・レジスターをデクリメントして、ゼロになったら分岐する
  • カウント・レジスターをデクリメントして、ゼロ以外になったら分岐する

上記のカウント・レジスター操作は、単独で使用することも、条件レジスターのテストと併せて使用することもできます。

拡張ニーモニックでは、b の直後に dz または dnz を追加するとカウント・レジスターの動作が指定されます。追加の条件または命令修飾子はその後に続きます。一例として、ループを 100 回繰り返すには、カウント・レジスターに 100 という数値をロードし、bdnz を使ってループを制御します。このコードは以下のようになります。


リスト 6. カウンター制御によるループの例
                  #The count register has to be loaded through a general-purpose register #Load register 31 with the number 100 li 31, 100 #Move it to the count register mtctr 31  #Loop start address loop_start:  ###loop body goes here###  #Decrement count register and branch if it becomes nonzero bdnz loop_start  #Code after loop goes here 

カウンター・テストは別のテストと組み合わせることもできます。例えば、ループに早期終了条件が必要だとします。以下のコードは、レジスター 24 がレジスター 28 に等しい場合の早期終了条件です。


リスト 7. 分岐と組み合わせたカウンター・レジスターの例
                  #The count register has to be loaded through a general-purpose register #Load register 31 with the number 100 li 31, 100 #Move it to the count register mtctr 31  #Loop start address loop_start:  ###loop body goes here###  #Check for early exit condition (reg 24 == reg 28) cmpd 24, 28  #Decrement and branch if not zero, and also test for early exit condition bdnzf eq, loop_start  #Code after loop goes here 

上記のように、条件分岐命令を追加しなくても、比較命令だけで条件分岐をループ・カウンターの分岐にマージできます。

総仕上げ

ここで、今までの情報を実用化してみます。

最初のプログラムは第 1 回の記事で入力した最大値プログラムの更新バージョンで、今回学習した内容に従って作り直します。前のバージョンでは、レジスターを使って読み取り中のアドレスを格納し、コードは間接アドレッシングによって値をロードしていました。今回のプログラムではインデックス付き間接アドレッシング・モードを使用し、ベース・アドレス用のレジスターとインデックス用のレジスターを使います。さらに、インデックスはゼロから始まって昇順に進むのではなく、余計な比較命令を省くために終わりから始めに向かって降順にカウントしていきます。このデクリメントによって、条件レジスターは (ゼロとの明示的な比較とは対照的に) 暗黙のうちに設定されるため、条件分岐命令で使用できるようになります。新しいバージョンは以下のとおりです (max_enhanced.s と入力してください)。


リスト 8. 最大値プログラムの拡張バージョン
                  ###PROGRAM DATA### .data .align 3  value_list: .quad 23, 50, 95, 96, 37, 85 value_list_end:  #Compute a constant holding the size of the list .equ value_list_size, value_list_end - value_list  ###ENTRY POINT DECLARATION### .section .opd, "aw" .global _start .align 3 _start: .quad ._start, .TOCDATA_SIZE, 8  #REGISTER USAGE #Register 3 -- current maximum #Register 4 -- list address #Register 5 -- current index #Register 6 -- current value #Register 7 -- size of data (negative)  #Load the address of the list ld 4, value_lis.opd, "aw" .global find_maximum_value .align 3 find_maximum_value: .quad .find_maximum_value, .TOC.align 3  #size of array members .equ DATA_SIZE, 8  #function begin .find_maximum_value: #REGISTER USAGE #Register 3 -- list address #Register 4 -- list size (elements) #Register 5 -- current index in bytes (starts as list size in bytes)  #Register 6 -- current value #Register 7 -- current maximum #Register 8 -- size of data  #Register 3 and 4 are already loaded -- passed in from calling function li 8, -DATA_SIZE  #Extend the number of elements to the size of the array #(shifting to multiply by 8) sldi 5, 4, 3  #Set current maximum to 0 li, 7, 0 loop: #Go to next value; set status register (in cr0) add. 5, 5, 8  #Load Value (X-Form - adds reg. 3 + reg. 5 to get the final address) ldx 6, 3, 5  #Unsigned comparison of current value to current maximum (use cr7) cmpld cr7, 6, 7  #if the current one is greater, set it bt 4*cr7+gt, set_new_maximum set_new_maximum_ret:  #Loop unless the last index decrement resulted in zero bf eq, loop  #AFTER THE LOOP #Move result to return value mr 3, 7  #return blr  set_new_maximum: mr 7, 6 b set_new_maximum_ret 

上記は前のバージョンによく似ていますが、以下の点が主な違いとなっています。


  • 初期条件は、ハードコーディングされる代わりにパラメーターで渡されます。
  • 関数内でのレジスターの使用方法が、渡されるパラメーターの並びと一致するように変更されています。
  • リンク・レジスターの内容を維持するため、set_new_maximum で無駄なリンク・レジスターの使い方をしなくなっています。

このプログラムが操作する C 言語のデータ型は unsigned long long ですが、これでは書くのが面倒なので、例えば uint64 のように型定義します。その場合、関数のプロトタイプは以下のようになります。

 uint64 find_maximum_value(uint64[] value_list, uint64 num_values); 

以下は、この新しい関数をテストする短いドライバー・プログラムです (use_max.c と入力してください)。


リスト 10. 最大値関数を使用した単純な C プログラム
                  #include   typedef unsigned long long uint64;  uint64 find_maximum_value(uint64[], uint64);  int main() { uint64 my_values[] = {2364, 666, 7983, 456923, 555, 34}; uint64 max = find_maximum_value(my_values, 6); printf("The maximum value is: %llu\n", max); return 0; } 

このプログラムをコンパイルして実行するには、単純に以下のようにします。

 gcc -m64 use_max.c max_function.s -o maximum ./maximum 

ここでは値をシェルに戻すのではなく、実際にはフォーマット設定した出力を行っているため、配列要素のサイズである 64 ビットすべてを利用できます。

単純な関数コールは、パフォーマンスが許す限り非常に安価です。単純化された関数コール ABI は完全に標準なので、コア・ループにカスタム・アセンブリー言語のスピードを必要とし、その他の部分には高級言語の表現力と使いやすさを必要とする混合言語プログラムを作成するには取り掛かりやすい手段となります。

まとめ

分岐プロセッサーのことをくまなく知ることが、一層効率的な PowerPC コードの作成に役立ちます。プログラマーは各種の条件レジスター・フィールドを使用して、巧妙に条件を省いたり、組み合わせたりすることができます。カウント・レジスターを使うと、効率的なループのコーディングに役立ちます。また、単純な関数によって、初心者プログラマーでも高級言語プログラム向けの便利なアセンブリー言語関数を作成できるようになります。

次回の記事では、関数コールのための PowerPC ABI を取り上げ、PowerPC プラットフォームではスタックがどのように機能するのかを詳しく説明する予定です。

参考文献

学ぶために

製品や技術を入手するために

議論するために

著者について

Jonathan BartlettはLinuxアセンブリー言語を使ったプログラミングの入門書Programming from the Ground Upの著者です。New Media Worxでの主席開発者であり、顧客向けにWebアプリケーションや、ビデオ、キヨスク、デスクトップなどのアプリケーションを開発しています。連絡先はjohnny/p>

お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

この記事を評価する

コメント



These are our most popular posts:

ニーモニックキーおよびアクセスキーの定義

ニーモニックキーおよびアクセスキーの定義. このドキュメントでは、次のトピックについて 説明します。 ... までニーモニック文字に下線が付けられません。 ただし、この新しい 動作は、常にニーモニック文字に下線が付けられるようにユーザーによって変更できます 。 read more

9. 基本的なコンポーネント3 (5)

キーボードから入力があった場合に、何らかの動作をするようにできます。最も簡単 なのは「ニーモニック」を利用する事です。 ... 変数を与えます。1つ目の引数でどのキー を押すか、2つ目の引数で同時に押されるファンクションキーを指定します。2つ目の引数 を ... read more

Power アーキテクチャーのためのアセンブリー言語: 第 3 回: PowerPC ...

2007年1月17日... を使用してプログラムがどのように動作するか、また PowerPC 命令セットがどのよう にしてメモリーをアドレス指定するか、 ... なく) を組み合わせるというもので、各種の一般 的な条件分岐におおよそ「従来」のようなニーモニックを使用します。 read more

アセンブラ技術交流できますかね?

現在、R8C/M1xがお気に入りで32KBのROMをどう使いこなすか思案中です。 アセンブラ ... ただ、命令数を増やす目的なら公開してもよさそうな気がします。 おそらく 、 ... Re:隠れニーモニックの確認ありがとうございます. ... 大昔にZ80のセカンドソース 品でもニーモニックは割り当てられていませんが、予想通りの動作をするものがありま した。 read more

0 件のコメント:

コメントを投稿