PDF

Linux on z Systems 向けインライン・アセンブリーの高度な
機能
IBM z Systems の IBM XL C/C++ コンパイラーとインライン・アセンブリー
を使用してパフォーマンスを向上させる
Anh Tuyen Tran
Software Developer
IBM
2015年 10月 22日
この記事では、Linux on z Systems 向けの IBM XL コンパイラーでサポートされているインライ
ン・アセンブリーの高度な機能について説明します。対象とする読者は、IBM XL コンパイラー
が提供する最適化の範囲を超えて、アプリケーションの中で最もパフォーマンスに影響する
コード部分を微調整したいと思っているソフトウェア技術者です。
はじめに
2015年にリリースされた IBM XL C/C++ for Linux on z Systems, V1.1 コンパイラーでは、ユーザーが
記述したアセンブラー命令を直接 C/C++ プログラムに組み込む機能 (インライン・アセンブリー)
をサポートしています。これにより、上級ユーザーには、チップ・レベルの命令を使用できると
いう高い柔軟性がもたらされます。インライン・アセンブリーを利用すると、ソフトウェア技術
者は C/C++ プログラムの中で最もパフォーマンスに影響する部分をアセンブラー・コードでハン
ドコーディングすることができます。そのため、プログラマーの才能を最大限に活かして、アプ
リケーションの実行にかかる時間をさらに縮めることができます。
この記事では、Linux on z Systems 向けの IBM XL コンパイラーでサポートされているインライン・
アセンブリーの高度な機能を紹介する目的で、アセンブリー・ラベル、基本分岐、相対分岐、入
出力オペランドのシンボル名、マッチング制約、そしてクロバー・リストに記載されたレジス
ターについて詳しく説明します。この記事で取り上げる範囲は、汎用レジスターを扱うアセンブ
ラー命令に限定し、ベクトル・レジスターや浮動小数点レジスターについては、別の記事に譲る
ことにします。対象とする読者は、Linux on z Systems のコンパイラーが提供する最適化の範囲を
超えて、高速なアプリケーションの中で最もパフォーマンスに影響するコード部分を微調整した
いと思っている上級ソフトウェア技術者です。
アセンブリー・ラベル
コンパイラーは、コンパイル・プロセスにおいて、ユーザー・プログラムの中で宣言された変
数や関数のそれぞれに対する内部名をオブジェクト・ファイルの中に作成します。この内部名
© Copyright IBM Corporation 2015
Linux on z Systems 向けインライン・アセンブリーの高度な機能
商標
ページ 1 / 11
developerWorks®
ibm.com/developerWorks/jp/
はアセンブリー・コードの中で対応する変数や関数を参照する場合にも使用されます。アセン
ブリー・ラベル機能を使用すると、ユーザーはオブジェクト・ファイルの中にある特定の変数
や関数の内部名を制御することができます。アセンブリー・コードが生成されると、アセンブ
リー・ラベルで指定された名前は、そのラベルに対応する変数または関数の名前となります。
従って、int func( ) asm ("my_function") と宣言すると、オブジェクト・ファイル内の関数
func の名前は慣習的な _func ではなく、my_function となります。
この機能の 1 つの使用例として、通常は関数や変数の名前の前にアンダーバーが付加されるシ
ステムであっても、アンダーバーで始まらない名前をリンカー用に定義する使い方が考えられ
ます。ただし、アセンブリー・ラベルの指定を適用できる対象は、グローバル変数の宣言と、グ
ローバル関数のプロトタイプ宣言のみであることに注意して下さい。
C プログラム label_b.c (リスト 1) と label_a.c (リスト 2) は、関数プロトタイプにアセンブリー・
ラベルを使用する方法を示すコード・スニペットです。
リスト 1. 関数 func_asm を定義する label_b.c
int func_asm() {
return 55;
}
//func_asm is defined here
ファイル label_b.c の中で、関数 func_asm( ) が定義されています。
リスト 2. は関数名をアセンブリー・ラベルに関連付ける label_a.c
int func() asm("func_asm");
int main() {
return func();
}
// func is associated with “func_asm”
// func is called
ファイル label_a.c では、1 行目のアセンブリー・ステートメントによって、関数 func が
func_asm という名前に関連付けられています。3 行目では関数 func( ) が呼び出されています
が、この関数の定義はありません。このコードによって期待される動作は、func が func_asm に
バインドされ、関数 func( ) の呼び出しが func_asm( ) の呼び出しになるというものです。
label_a.c と label_b.c をコンパイル、リンクして、生成された実行ファイルを実行すると、成功す
るはずです。実行によって 55 が返されますが、これはシンボル func が func_asm に関連付けられ
ているためです。図 1 はプログラム label_a.c に対して生成されたアセンブリー・コードを示して
います。これを見ると、func: [ BRASL %r14, func_asm ] の代わりに func_asm という名前が使
用されていることがわかります。
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 2 / 11
ibm.com/developerWorks/jp/
developerWorks®
図 1. func ではなく func_asm を呼び出す label_a.c
ラベルへの分岐
ラベルへの分岐には、基本分岐と相対分岐という 2 通りの方法があります。基本分岐では、分岐
命令は特定の条件に応じて、ある 1 つのラベルへと分岐します。ラベルはプログラムの中で一
意に定義されていなければなりません。相対分岐では、ターゲット・ラベルは分岐命令の位置に
対して相対的なものになります。ターゲット・ラベルが分岐命令よりも前にある場合には、b と
いう文字を分岐アドレスに追加します (b は backward (戻る) を意味します)。同様に、ターゲッ
ト・ラベルが分岐命令よりも後にある場合には、f という文字を分岐アドレスに追加します (f は
forward (進む) を意味します)。
基本分岐
リスト 3 は基本分岐の使い方の例です。
リスト 3. 基本分岐の例
int absoluteValue(int a) {
asm (" CFI %0, 0\n"
" BRC 0xA, DONE\n"
" LCR %0, %0\n"
" DONE:\n"
:"+r"(a)
);
return a;
}
表 1 はリスト 3 の 2 行目で使用されている CFI (即値と比較する) 命令の条件コードとマスクの関
係を示しています。
表 1. CFI 命令の条件コードとマスクの関係
0 との比較
条件コード
マスク・ビット
a=0
0 = 002
1000
a<0
1 = 012
0100
a>0
2 = 102
0010
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 3 / 11
developerWorks®
ibm.com/developerWorks/jp/
リスト 3 の 2 行目では、CFI 命令によって変数 a (%0) をゼロと比較しています。a == 0 または a >
0 の場合には、条件コードは 0 または 2 に設定されます (表 1 の2 行目と 4 行目)。条件コード 0 と
2 を組み合わせた場合のマスク・ビットは 10102 であり、10102 は 16 進で表現すると 0xA です。
従って 3 行目の分岐命令では、この 0xA に基づいて分岐が行われ、a >= 0 の場合には 5 行目のラ
ベル DONE へと分岐することになり、この関数は 4 行目の LCR 命令を実行せずに a の値を返しま
す。一方 a < 0 の場合には分岐が起こらず、4 行目の LCR 命令によって a の補数が a にロードさ
れた後、a が返されます。つまり、この関数は実質的に a の絶対値を返します。この例では、4 行
目の LCR の実行をスキップするために、ラベル DONE への基本分岐が使用されています。
相対分岐
リスト 4 の例では、相対分岐を使用してループバックしています。
リスト 4. 相対分岐を使用した擬似コード
asm ( "1:
\n"
"DoSomeWork\n"
"BRCT %0, 1b \n"
:"+r"(limit)
);
BRCT (カウントを基準にした相対分岐) 命令は、第 1 オペランド limit の値 (%0) から 1 を減算し、
その結果を第 1 オペランドに戻して保存します。結果がゼロでない場合、BRCT 命令は、第 2 オ
ペランドである 1b (つまり、ラベル 1-backward) で指定されるアドレスへと分岐します。ラベル 1
は、分岐命令に対して「戻った (backward) 1 行目になります。この例では、limit がゼロでない限
り、BRCT 命令によって limit がデクリメントされて、ラベル 1 へとループバックします。limit
がゼロになると、ループが終了します。
相対分岐では、ラベル名に含められるのは数字のみであることに注意して下さい。基本分岐のラ
ベルには、この前提条件は適用されません。また、ラベルは同じアセンブリー・ステートメント
内になければなりません。別のアセンブリー・ステートメント内のラベルへのジャンプはサポー
トされていません。
シンボル名
入出力オペランドをシンボル名で定義することもできます。アセンブリー・コードの中では、シ
ンボル名を参照することができます。シンボル名は角括弧で囲んで指定し、その後に制約ストリ
ングを続けます。アセンブリー・コードの中でシンボル名を参照するには、パーセント記号の後
にオペランドの数字を続けるのではなく、%[name] を使用します。シンボル名は、C の有効な変数
名にすることができ、その名前が前後の C コードの中で定義されていても構いません。ただし、
シンボル名は各インライン・アセンブリー・ステートメントの中で一意でなければなりません。
リスト 5 のスニペットでは、シンボル名として [results]、[first]、[second] を使用して、それぞれ
が第 0 オペランド、第 1 オペランド、第 2 オペランドを表しています。このステートメントでは
%0、%1、%2 を参照せずに %[result]、%[first]、%[second] を参照しています。
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 4 / 11
ibm.com/developerWorks/jp/
developerWorks®
リスト 5. シンボル名の使い方の例
int main(){
int sum = 0, one=1, two = 2;
asm ("AR %[result], %[first]\n"
"AR %[result], %[second]\n"
:[result] "+r"(sum)
:[first] "r"(one), [second] "r"(two)
);
return sum == 3 ? 0 : 1;
}
マッチング制約
0, 1, … 9 はマッチング制約です。マッチング制約は、入力オペランドと、番号で指定された出力
オペランドの両方に同じレジスターを割り当てるよう、コンパイラーに指示するために使用さ
れます。マッチング制約はこのようなものとして、入力オペランドにのみ使用することができま
す。このマッチング制約は、複数ある演算の 1 つが前の演算の結果を入力として使用する場合に
は不可欠です。マッチング制約がない場合、コンパイラーは入力オペランドと出力オペランドの
両方に同じレジスターを使用しなければならないことを認識できません。
リスト 6 の C プログラム example07a.c では、マッチング制約を使用せずにプログラムを実行した
場合に誤った結果が生成される例です。
リスト 6. マッチング制約を使用しないため、誤った結果が生成される example07a.c
#include <stdio.h>
int main () {
int a = 10, b = 200, c = 3000;
printf ("INITIAL: a = %d, b = %d, c = %d\n", a, b, c );
asm ("LR %0, %2\n"
"LR %1, %3\n"
:"=r"(a),"=r"(b)
:"r"(c), "r"(a));
printf ("RESULT : a = %d, b = %d, c = %d\n", a, b, c );
return 0;
}
5 行目にある 1 つ目の LR (レジスターのロード) 命令では、a に c がロードされます。c は 3000 で
あるため、a は 3000 になります。次に、6 行目にある 2 つ目の LR 命令で b に a がロードされま
す。もしプログラマーの意図が、1 つ目の LR によって更新された a の値 (つまり、3000) を b に
ロードすることであるなら、example07a.c の結果はそのようになるとは限りません。LR の 2 回の
呼び出しで、同じ変数 a に対して、コンパイラーが同じレジスターを使用する保証はないからで
す。同じレジスターが使用されなければ、更新前の a の値である 10 が b にロードされます。リス
ト 7 には、example07a.c をコンパイルして実行すると、誤った結果が生成されて b が 3000 ではな
く (ほとんどの場合、3000 になりますが)、10 になる様子を示しています。
リスト 7. example07a.c をコンパイルして実行する
xlc -o example07a example07a.c;
./example07a
INITIAL: a = 10, b = 200, c = 3000
RESULT : a = 3000, b = 10, c = 3000
<- b is loaded with a, but b is 10 while a is 3000
ユーザーの意図は、更新された a の値を b にロードすることなので、マッチング制約を使用し
て、5 行目の LR 命令の出力を 6 行目の LR 命令の入力として使用するよう、コンパイラーに指示
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 5 / 11
developerWorks®
ibm.com/developerWorks/jp/
しなければなりません。マッチング制約を使用すると、コンパイラーはどちらの LR 命令を実行す
る場合も変数 a に対して同じレジスターを選択します。リスト 8 は、マッチング制約を使用する
ように修正したバージョンの C プログラム example07b.c を示しています。
リスト 8. マッチング制約を使用した example07b.c
#include <stdio.h>
int main () {
int a = 10, b = 200, c = 3000;
printf ("INITIAL: a = %d, b = %d, c = %d\n", a, b, c );
asm ("LR %0, %2\n"
"LR %1, %3\n"
:"=r"(a),"=r"(b)
:"r"(c), "0"(a));
printf ("RESULT : a = %d, b = %d, c = %d\n", a, b, c );
return 0;
}
修正されたプログラム example07b.c では、8 行目でマッチング制約 "0"(a) を使用して、入力オペ
ランド a (%3) には 0 番目の出力オペランド a と同じレジスターを使用するよう、コンパイラーに
指示しています。5 行目にある 1 つ目の LR 命令は、3000 が設定されている c を a にロードし、6
行目にある 2 つ目の LR 命令は入力オペランド a に対して同じレジスターを使用するため、意図し
たとおり 3000 という値が b にロードされます。
図 4 は、example07a.c 用に生成されたアセンブリー・ファイル (図の中の左側) と example07b.c 用
に生成されたアセンブリー・ファイル (図の中の右側) の違いを示しています。マッチング制約が
ない場合 (example07a.c)、コンパイラーが出力オペランド a と入力オペランド a のそれぞれに対
し、2 つの異なるレジスター r1 と r5 を使用しているのが明らかです。マッチング制約を使用して
いる example07b.c では、どちらの LR 演算にも同じ r1 レジスターを使用しています。
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 6 / 11
ibm.com/developerWorks/jp/
developerWorks®
図 2. マッチング制約の有無によって異なる、生成されるコード
表 2 はマッチング制約を使用していない example07a.c について説明したものです。r1 で行われ
た更新は r5 の入力値とは無関係であるため、更新された値は 2 つ目の LR 命令では使用されませ
ん。
表 2. マッチング制約を使用しない場合のコード (example07a.s)
アセンブリー・コード
説明
BRASL %r14,printf
printf INITIAL …を呼び出します
L %r3,168(,%r15)
c の値を r15+168 から r3 レジスターにロードし、r3 は 3000 を保持しま
す
L %r5,176(,%r15)
a の値を r15+176 から r5 レジスターにロードし、r5 は 10 を保持します
#GS00000
ユーザーのインライン・アセンブラー命令を開始します
LR %r1, %r3
r3 (c の値、つまり 3000) を r1 (a) にロードします
LR %r0, %r5
r5 (更新前の a の値、つまり 10) を r0 (b) にロードします
#GE00000
ユーザーのインライン・アセンブラー命令を終了します
一方、マッチング制約を使用する example07b.c のアセンブリー・コードを見ると、変数 a に同じ
r1 レジスターが使用されていることがわかります。1 つ目の LR 命令が実行されると r1 は更新さ
れ、更新された値が 2 つ目の LR 命令の入力値になります。そのため、b には更新された a の値が
適切にロードされます。
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 7 / 11
developerWorks®
ibm.com/developerWorks/jp/
表 3. マッチング制約を使用した場合に生成されるコード (example07b.s)
アセンブリー・コード
説明
BRASL %r14,printf
printf INITIAL … を呼び出します
L %r3,168(,%r15)
c の値を r15+168 から r3 レジスターにロードし、r3 は 3000 を保持しま
す
L %r1,176(,%r15)
a の値を r15+176 から r1 レジスターにロードし、r1 は 10 を保持します
#GS00000
ユーザーのインライン・アセンブラー命令を開始します
LR %r1, %r3
r3 (c の値、つまり 3000) を r1 (a) にロードします
LR %r0, %r1
r1 (更新された a の値、つまり 3000) を r0 (b) にロードします
#GE00000
ユーザーのインライン・アセンブラー命令を終了します
クロバー・リスト上のレジスター名
アセンブラー命令の中で、入出力オペランドのリストに記載されていないレジスターを使用また
は更新する場合、ユーザーは影響を受けるすべてのレジスターをクロバー・リストに記載しなけ
ればなりません。この情報を基に、コンパイラーはインライン・アセンブリー・ステートメント
の演算を実行します。
リスト 9 はアセンブラー命令のオペランドとして汎用レジスター r7 が明示的に指定されている例
を示しています。
リスト 9. 入出力オペランド・リストに記載されていないレジスターを使用する
example09.c
#include <stdio.h>
int main () {
int a = 15, b = 20;
printf ("INITIAL: a = %d, b = %d\n", a, b );
asm ("LR
7, %1\n"
"MSR %0, 7\n"
:"+r"(a)
:"r"(b)
:"r7"
);
printf ("RESULT : a = %d, b = %d\n", a, b );
return 0;
}
5 行目の LR 命令は出力オペランドとして r7 レジスターを指定しています。6 行目の MSR 命令も
入力オペランドとして r7 を使用しています。r7 レジスターはアセンブラー命令のオペランドとし
て使用されていますが、入出力オペランドのリストに r7 は記載されていません。そのため、r7 を
クロバー・リストに追加して、r7 が使用されることをコンパイラーに指示する必要があります。
一般に、プログラムが正しいことを確実にするには、アセンブラー命令によって影響を受けるす
べてのレジスターをオペランド・リストまたはクロバー・リストのいずれかに記載する必要があ
ります。コンパイラーはその情報に基づいてレジスターの割り当てを調整します。
使用するレジスターを変えた場合のコードの違いを比較すると、特定のレジスターをクロバー・
リストに含めることによるパフォーマンスへの影響がわかります。リスト 10 に示すプログラム
example09a.c では、r7 レジスターではなく r1 レジスターを使用しています。
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 8 / 11
ibm.com/developerWorks/jp/
developerWorks®
リスト 10. 異なるレジスターをクロバー・リストに含める example09a.c
#include <stdio.h>
int main () {
int a = 15, b = 20;
printf ("INITIAL: a = %d, b = %d\n", a, b );
asm ("LR
1, %1\n"
"MSR %0, 1\n"
:"+r"(a)
:"r"(b)
:"r1"
);
printf ("RESULT : a = %d, b = %d\n", a, b );
return 0;
}
図 3 はコンパイラーによって生成された 2 つのアセンブリー・ファイルを比較しています。左側
のファイルは r7 を使用し、右側のファイルは r1 を使用しています。
図 3. 異なるレジスターをクロバー・リストに含める場合のコードを比較する
図 3 の右側では、r1 レジスターをクロバー・リストに含めた場合に、コンパイラーが変数 b に
対して r3 レジスターを選択することがわかります [ L %r3,168(,%r15) ]。もっと重要な点とし
て、r1 が選択された場合、コンパイラーはクロバー・リストに含められたレジスターの内容を保
存しません。r7 が選択されると、r7 レジスターの内容は R15+56 で示される場所に保存されます
[ STG %r7,56(,%r15) ]。これはつまり、r7 ではなく r1 をクロバー・リストに含めると、STORE
命令を 1 つ減らせるということです。図 6 は、適切なレジスターを選択してクロバー・リストに
含めることで、パフォーマンスが向上する可能性があることを示しています。
明示的にレジスターを指定するのが望ましくない場合には、ユーザーは、コンパイラーが適切な
レジスターを選択するように、コードに変更を加えることができます。以下の例では、一時的
なレジスター・オペランドを追加し、またマッチング制約を使用することにより、そのオペラン
ドが入出力オペランドの両方に使用されるようにしています。この具体的なコードが含まれた
example09b.c をリスト 11 に示します。
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 9 / 11
developerWorks®
ibm.com/developerWorks/jp/
リスト 11. コンパイラーがレジスターを選択するように変更したコード
#include <stdio.h>
int main () {
int a = 15, b = 20, tmp = 1;
printf ("INITIAL: a = %d, b = %d\n", a, b );
asm ("LR %1, %2\n"
"MSR %0, %3\n"
:"+r"(a), "=r"(tmp)
:"r"(b) , "1"(tmp)
);
printf ("RESULT : a = %d, b = %d\n", a, b );
return 0;
}
まとめ
インライン・アセンブリーを使用すると、C/C++ プログラムにアセンブラー命令を直接組み込む
ことができます。この機能により、上級ユーザーは特定のコード部分にアセンブラー命令をハン
ドコーディングすることで、アプリケーションのパフォーマンスをさらに向上させることができ
ます。IBM XL コンパイラーでは、最適化の各レベルで生成されたコードをさらに最適化するため
に、非常に高度な処理を行います。そのため、インライン ASM でパフォーマンスを向上させるに
は、ユーザーはターゲット・コードの実行に関して詳細に理解している必要があります。埋め込
まれたアセンブラー命令によってパフォーマンスがどう影響を受けるかを注意深く分析し、綿密
なプランニングと徹底的なテストを行うことが、パフォーマンスの向上を実現するには不可欠で
す。
謝辞
この記事の作成にあたって助言してくださった Visda Vokhshoori 女史と Nha-Vy Tran 女史に感謝
いたします。
参考文献
• IBM XL C/C++ for Linux on z Systems 製品ページにアクセスして、詳細な情報を入手してくだ
さい。
• Rational C/C++ Cafe コミュニティーに参加して、他のメンバーとつながってください。
参考資料
• 「z/Architecture Principles of Operation」(IBM Publications、No. SA22-7832-10)
• 「z/Architecture Reference Summary」(IBM Publications、No. SA22-7871-08)
• 「Inline assembly statements」(XL C/C++ for Linux on z Systems, V1.1、2015年6月1日更新)
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 10 / 11
ibm.com/developerWorks/jp/
developerWorks®
著者について
Anh Tuyen Tran
Anh Tuyen Tran は、カナダのトロント大学のコンピューター・サイエンス学部ソフ
トウェア・エンジニアリング・グループで学士号を取得しており、日本の山口大学
の金融関連の分野で修士号を取得しています。2007年からは IBM でソフトウェア
開発者として働いており、当初はデバッガー開発チームに、そして現在はコンパイ
ラー・チームに所属しています。2009年から 2013年まではコンパイラー分野で Test
Servers Administration チームのチーム・リーダーを務めていました。Anh は、Linux
on z Systems 向けインライン・アセンブリーのドキュメントや、IBM POWER8 アーキ
テクチャーの組み込み機能である、ハードウェア・トランザクション・メモリー機
能および暗号 (AES および SHA) 機能のドキュメントの作成における、主要なコント
リビューターの一人あるとともに、IBM コンパイラーに関する何本もの記事の執筆も
行っています。
© Copyright IBM Corporation 2015
(www.ibm.com/legal/copytrade.shtml)
商標
(www.ibm.com/developerworks/jp/ibm/trademarks/)
Linux on z Systems 向けインライン・アセンブリーの高度な機能
ページ 11 / 11