目次


Linux on z Systems 向けインライン・アセンブリーの基礎

IBM z Systems の IBM XL C/C++ コンパイラーとインライン・アセンブリーを使用してパフォーマンスを向上させる

Comments

はじめに

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 向けインライン・アセンブリーの高度な機能」で取り上げます。今回の記事で取り上げる範囲は、汎用レジスターに関連するアセンブラー命令にとどめます。ベクトル・レジスターおよび浮動小数点レジスターについては、別の記事で詳しく取り上げる予定です。対象とする読者は、Linux on z Systems のコンパイラーが提供する最適化の範囲を超えて、高速なアプリケーションの中で最もパフォーマンスに影響するコード部分の微調整をする方法を詳しく調べたいと思っている上級ソフトウェア技術者です。

アセンブラー命令とインライン・アセンブリー・ステートメント

プラットフォームに依存するインライン・アセンブリー・ステートメント

各インライン・アセンブリー・ステートメントには、アセンブラー命令が任意の数 (ゼロ個も可) だけカプセル化されます。アセンブラー命令はハードウェア・レベルの命令であり、アーキテクチャーに固有です。プラットフォームが異なれば、実行する処理の性質が似通っている場合であっても、アセンブラー命令がまったく異なる場合があります。例えば、IBM Power Architecture の 2 つのレジスターの内容に対して実行される加算命令は、3 つのオペランド R1、R2、R3 を受け付けます。

caxo. R3, R2, R1

この演算では、レジスター R1 とレジスター R2 に格納されている値を加算してから、その結果をレジスター R3 に格納するとともに、条件レジスターの内容と、演算のステータスに関する固定小数点例外レジスターの内容を更新します。

一方、IBM z Systems で上記に対応する命令は、以下の 2 つのオペランドだけを受け付けます。

AR R2, R1

この命令は、レジスター R1 に格納されている値と、レジスター R2 に格納されている値を加算してから、その結果をレジスター R2 に格納します。また、演算とオーバーフローのステータスに関するプログラム・ステータス・ワード (PSW) に含まれる 4 ビット値の条件コードを更新します。z Systems プロセッサーには条件レジスターはありません。

ただし、明確なオペランド・ファシリティーがインストールされていれば、z Systems に対する ARK 命令と AGRK 命令は、3 つのオペランドを受け付けることに注意してください。

アプリケーションの移植性を確保するには、インライン・アセンブリー・ステートメントが使用されているコード・セクションを、z Systems に固有の適切なマクロで保護する必要があります。IBM XL コンパイラーが定義しているすべてのマクロを網羅したリストを入手するには、-qshowmacros -P オプションを指定して C コードをコンパイルします。このオプションを指定すると、すべてのマクロ定義がプリプロセスされて出力されます。

インライン・アセンブリー・ステートメントの構成

インライン・アセンブリーは、ユーザーによって C/C++ プログラムに埋め込まれたステートメントであり、指定されたアセンブラー命令を、生成されるコードにインライン化するようコンパイラーに指示します。インライン・アセンブリー・ステートメントは、以下の主要なコンポーネントで構成されます。

  • キーワード asm__asm、または __asm__: アセンブリー・ステートメントの開始を示します。
  • オプション・キーワード volatile: アセンブリー・ブロックの値が変更される可能性があることをコンパイラーに伝えます。
  • 1 つ以上のアセンブラー命令 (別名、コード書式文字列): コードにインライン化するアセンブラー命令です。
  • 出力オペランド・リスト: アセンブラー命令の出力を表示するリストです。
  • 入力オペランド・リスト: アセンブラー命令への入力を含めるリストです。
  • オプションのクロバー・リスト: アセンブラー命令によって影響を受けるレジスター、条件コード、メモリーに関する情報をコンパイラーに伝えます。

図 1 では、インライン・アセンブリー・ステートメントの主要なコンポーネントを一般化しています。主要なコンポーネントについての詳細は、この記事の後のほうのセクションで説明します。

図 1. インライン・アセンブリー・ステートメントを一般化したもの

インライン・アセンブリー・ステートメントに含めるアセンブラー命令のオペランド

インライン・アセンブリー・ステートメントには、任意の個数 (ゼロ個も可) のアセンブラー命令が含まれます。アセンブラー命令にオペランドがある場合、オペランドはリテラルまたはパラメーターのいずれかです。オペランドがパラメーターの場合、それは結合されたオペランド・リストに含まれるものでなければなりません。パラメーターは、% 記号と、リストでのその位置番号で識別されます (位置番号はゼロから始まります)。図 2 に、出力オペランド・リストと入力オペランド・リストに基づいてオペランドを識別する方法を示します。

図 2. 結合されたオペランド・リスト

インライン・アセンブリーでは、プログラムの残りの部分とのすべての関連付けを、出力オペランド・リストと入力オペランド・リストによって確立する必要があります。オペランド・リストを介さずに直接外部シンボルを参照することはできません。

ユーザーのアセンブラー命令をサポートするためにコンパイラーが追加する命令

インライン・アセンブリー機能を使用すれば、ユーザーはコード内で直接アセンブラー命令を操作するのではなく、オペランド・リストに含まれるシンボルに対して必要なアセンブラー命令を適用できるので、サポートする処理に対して準備をする負担がなくなります。アセンブラー命令を円滑に実行するために必要な処理は、コンパイラーが準備してくれます。例えば、目的の演算が 2 つの変数を加算することである場合、ユーザーは 2 つのオペランドとして機能する 2 つの変数を指定して、加算命令 (例えば、AR) を呼び出すことができます。その他の作業 (ジョブを実行するレジスターを選択する、レジスターに値をロードする、演算の完了後にメモリー位置に値を格納するなど) は、コンパイラーが行ってくれます。

以下の 2 つのリストに、同じプログラムの異なるバージョンを記載します。example01.c はインライン・アセンブリー・ステートメントを使用しないバージョン、example02.c はインライン・アセンブリー・ステートメントを使用したバージョンです。

リスト 1. example01.c: インライン・アセンブリー・ステートメントを使用しない C プログラム
#include <stdio.h>
int main () {
   int array[] = { 1, 2, 3 };
   printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]);
   printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]);
   return 0;
}

example01.c プログラムのライン 4 とライン 5 はまったく同じです。この例ではこれらのコード行を、コンパイラーがインライン・アセンブリー・ステートメントを扱う際に行う準備をデモンストレーションするためのマーカーとして使用しています。example02.c プログラムは、example01.c のライン 4 とライン 5 の間にインライン・アセンブリー・ステートメントを追加することによって作成されたものです。以下のステートメントは、加算命令 (AR) を使用して、array[2] の値を array[1] の値に加算します。

リスト 2. example02.c: インライン・アセンブリー・ステートメントを使用した C プログラム
#include <stdio.h>
int main () {
   int array[] = { 1, 2, 3 };
   printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]);
   asm ("AR %0, %1 \n" 
       :"+r"(array[1]) 
       :"r"(array[2]) );
   printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]);
   return 0;
}

AR 命令は、オペランドの合計を最初のオペランドに格納します。具体的には、%0 (array[1])%1 (array[2]) を加算してから、その結果を %0 (array[1]) に格納します。ランタイム時に example02.c が出力する配列の 2 番目の要素 (array[1]) は 5 になります (リスト 3 を参照)。

リスト 3. example02.c を実行する
xlc –o  example02 ./example02.c
./example02  
array = [ 1, 2, 3]
array = [ 1, 5, 3]         <- The 2nd element of the array becomes 5, which is the sum of 2+3

2 つのケースで生成されたアセンブリー・コードを並べて対比させると、コンパイラーが example02.c のライン 5 からライン 7 でインライン・アセンブリー・ステートメントをサポートするために行っている処理が明らかになります。example01.cexample02.c のアセンブリー・ファイルを生成するには、-S オプションを指定してプログラムをコンパイルします。

リスト 4. -S オプションを指定してアセンブリー・ファイルを作成する
xlc –c –S example01.c example02.c

図 3 では、コンパイラーによって生成された example01.cexample02.c のアセンブリー・ファイルを比較しています。図 3 で明白なように、example01.s (図 3 の左側) と example02.s (図 3 の右側) との違いから明らかなことは、命令 AR %r0, %r1 をコードにインライン化する前に、コンパイラーは追加の処理を準備していることです。コンパイラーは、汎用レジスター (r0 および r1) を選択し、これらのレジスターに適切な値をロードしてから、AR 命令を呼び出します。また、AR 命令を実行した後は、計算された値を配列に格納します。インライン・アセンブリー・ステートメントが正常に実行されるためには、これらのサポート命令が必要です。

図 3. AR をサポートするためにコンパイラーが追加した処理

強調表示されたスニペットに含まれる処理の詳細については、表 1 を参照してください。

表 1. コンパイラーによるインライン・アセンブリー・ステートメントの処理
C コードアセンブラー・コード処理
printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]); BRASL %r14,printf準備を完了し、ライン 4 で printf を呼び出す
(コンパイラーによって追加される命令)L %r1,184(,%r15)array[2] の値をレジスター r1 にロードする
MVC 168(4,%r15),180(%r15) array[1] の値を位置 r15+168 にコピーする
L %r0,168(,%r15) 現在位置 r15+168 にある array[1] の値を r0 にロードする
asm ("AR %0, %1 \n" :"+r"(array[1]) :"r"(array[2]) );#GS00000ユーザーのアセンブラー命令のインライン化を開始する
AR %r0, %r1 ユーザーのアセンブラー命令をインライン化する
#GE00000ユーザーのアセンブラー命令のインライン化を終了する
(コンパイラーによって追加される命令)ST %r0,168(,%r15) r0 (array[1]) を位置 r15+168 に再び格納する
MVC 180(4,%r15),168(%r15)位置 r15+168 にある値を位置 r15+180 にコピーする (array[1] を更新)
printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]); LA %r2,16(,%r13)2 回目の printf の呼び出しを準備する

制約、修飾子、出力オペランド・リスト、入力オペランド・リスト、クロバー・リスト

それぞれの出力オペランドは、一対の制約(C/C++ 式) からなります。出力オペランドには、修飾子を指定しなければなりません。入力オペランドも一対の制約(C/C++ 式) からなりますが、修飾子はありません。クロバー・リストは、入力オペランド・リストと出力オペランド・リストに含まれていないエンティティーに、アセンブラー命令が影響するかどうかをコンパイラーに伝えます。出力オペランド・リスト、入力オペランド・リスト、クロバー・リストは空の場合もあります。リストに複数のメンバーを含める場合は、各メンバーをコンマで区切ります。

制約

制約とは、付随するオペランドのタイプを記述する文字列リテラルです。制約は、アセンブラー命令が要求するオペランドのタイプと一致しなければなりません。この記事では、とりわけよく使われる制約をいくつか説明します。サポートされる制約を記載したリストは、コンパイラーの今後のリリースではさらに大きくなる可能性があります。製品ごとにサポートされている制約を網羅した最新のリストについては、コンパイラー・マニュアルを参照してください。

汎用レジスターの a、d、r 制約

adr 制約は、汎用レジスターをオペランドとして要求するアセンブラー命令で使用されます。C/C++ プログラムでは、adr 制約は整数のシンボルを表します。例えば、リスト 5 に記載する example03.c プログラムのインライン・アセンブリー・ステートメントは、LPGFR 命令を使用して、整数変数の正の値を自身にロードし、実質的にあらゆる整数変数を絶対値に変えます。変数 a は整数であり、LPGFR 命令はオペランドとして汎用レジスターを受け付けるため、LPGFR では adr のどれでも有効な制約として使用することができます。

リスト 5. example03.c: 汎用レジスターに対する a、d、r 制約の使用
#include<stdio.h>
int abs_a(int a){
    asm (" LPGFR %0, %0\n" :"+a"(a) );        //a constraint
    return a;
}
int abs_d(int a){
    asm (" LPGFR %0, %0\n" :"+d"(a) );        //d constraint
    return a;
}
int abs_r(int a){
    asm (" LPGFR %0, %0\n" :"+r"(a) );        //r constraint
    return a;
}

int main() {
    int x = -5;
    printf( "Absolute value of %d is %d (a constraint)\n", x, abs_a(x) );
    printf( "Absolute value of %d is %d (d constraint)\n", x, abs_d(x) );
    printf( "Absolute value of %d is %d (r constraint)\n", x, abs_r(x) );
    x = 12;
    printf( "Absolute value of %d is %d (a constraint)\n", x, abs_a(x) );
    printf( "Absolute value of %d is %d (d constraint)\n", x, abs_d(x) );
    printf( "Absolute value of %d is %d (r constraint)\n", x, abs_r(x) );
}

プログラム example03.c を実行すると、-5 および 12 の絶対値が出力されます。

注: IBM z/Architecture では、アドレス指定で r0 を使用することはできません。このことから、r0 を除くすべての汎用レジスターには、制約 a (「a」は address の略) を使用することができます。

長さ 2 バイトまでの定数値の I、J、および K 制約

IJおよび K 制約は、即値オペランドを要求するアセンブラー命令で使用することができます。C/C++ プログラムでは、I、J、および K 制約は最大 16 ビットの整数定数または文字定数を表します。例えば、リスト 6 に記載するプログラム example04.c のインライン・アセンブリー・ステートメントは、I、J、および K 制約のそれぞれを、MVI (Move Immediate) を扱う際には 1 バイトの文字として、AHI (Add Haft-word Immediate) を扱う際には 2 バイトの整数として使用します。このように IJおよび K 制約を使用できるのは、MVI 命令では 1 バイトの即値を要求し、AHI 命令では 2 バイトの整数定数を要求するからです。

リスト 6. I、J、および K 制約を使用した example04.c
#include<stdio.h>
int main() {
    char text[]=”ibm”;
    asm(“MVI %0,%1\n”:”=m”(text[0]):”I”(‘I’));         //I for 1-byte char
    asm(“MVI %0,%1\n”:”=m”(text[1]):”J”(‘B’));         //J for 1-byte char
    asm(“MVI %0,%1\n”:”=m”(text[2]):”K”(‘M’));         //K for 1-byte char
    printf (“Expected IBM , got %s\n”, text);         

    int x = 0;
    asm(“AHI %0,%1\n”:”+r”(x):”I”(0x1FFF));         //I for 2-byte int
    asm(“AHI %0,%1\n”:”+r”(x):”J”(0x1FFF));         //J for 2-byte int
    asm(“AHI %0,%1\n”:”+r”(x):”K”(0x1FFF));         //K for 2-byte int
    printf (“Expected 0x5FFD, got 0x%X\n”, x);
    return 0;
}

プログラム example04.c を実行すると、要求される値が出力されます。

長さ 4 バイトまでの定数値の g、i、および n 制約

giおよび n 制約は、即値オペランドを要求するアセンブラー命令で使用することができます。C/C++ プログラムでは、giおよび n 制約は最大 32 ビットの整数定数または文字定数を表すために用いることができます。例えば、リスト 7 に記載するプログラム example05.c のインライン・アセンブリー・ステートメントは、gi、および n 制約のそれぞれを、MVI を扱う際には 1 バイトの文字として、AHI を扱う際には 2 バイトの整数として、AFI (Add Full-word Immediate) で演算を行う際には 4 バイトの整数として使用します。gi、および n 制約をこのように使用できるのは、MVI 命令では 1 バイトの即値を要求し、AHI 命令では 2 バイトの整数定数を要求し、AFI 命令では 4 バイトの整数定数を要求するからです。

リスト 7. g、i、および n 制約を使用した example05.c
#include<stdio.h>
int main() {
    char text[]="xlc";
    asm("MVI %0,%1\n":"=m"(text[0]):"i"('X'));        //i for 1-byte char
    asm("MVI %0,%1\n":"=m"(text[1]):"n"('L'));        //n for 1-byte char
    asm("MVI %0,%1\n":"=m"(text[2]):"g"('C'));        //g for 1-byte char
    printf ("Expected XLC, got %s\n", text);

    int x = 0;
    asm("AHI %0,%1\n":"+r"(x):"i"(0x1FFF));        //i for 2-byte int
    asm("AHI %0,%1\n":"+r"(x):"n"(0x1FFF));        //n for 2-byte int
    asm("AHI %0,%1\n":"+r"(x):"g"(0x1FFF));        //g for 2-byte int
    printf ("Expected 0x5FFD, got 0x%X\n", x);

    x = 0;
    asm("AFI %0,%1\n":"+r"(x):"i"(0x1FFFFFF));        //i for 4-byte int
    asm("AFI %0,%1\n":"+r"(x):"n"(0x1FFFFFF));        //n for 4-byte int
    asm("AFI %0,%1\n":"+r"(x):"g"(0x1FFFFFF));        //g for 4-byte int
    printf ("Expected 0x5FFFFFD, got 0x%X\n", x);
    return 0;
}

プログラム example05.c を実行すると、要求される値が出力されます。

メモリー制約としての Q、g、m、o

Qgmおよび o 制約は、アセンブラー命令のメモリー・オペランドとして使用され、オペランドの形式は D(X, B) であることが要求されます。ここで、D はディスプレイスメント、X はインデックス・レジスター、B はベース・レジスターです。C/C++ プログラムで整数シンボルを表すには、Qgmおよび o 制約を使用することができます。例えば、リスト 8 に記載するプログラム example06.c のインライン・アセンブリー・ステートメントは、ST (ストア) 命令を扱う際に、Qgmおよび o 制約のそれぞれをメモリー制約として使用して配列の要素を更新します。

リスト 8. Q、g、m、および o 制約の使用方法をデモンストレーションする example06.c
#include <stdio.h>
int main () {
   int a[] = { 1, 2, 3, 4 };
   int b[] = { 10, 20, 30, 40 };
   printf ( "a = [ %d, %d, %d, %d ]\n", a[0], a[1], a[2], a[3] );

   asm ("ST %1,%0\n":"=Q"(a[0]):"r"(b[0]));        //Q as memory constraint
   asm ("ST %1,%0\n":"=g"(a[1]):"r"(b[1]));        //g as memory constraint
   asm ("ST %1,%0\n":"=m"(a[2]):"r"(b[2]));        //m as memory constraint
   asm ("ST %1,%0\n":"=o"(a[3]):"r"(b[3]));        //o as memory constraint

   printf ( "a = [ %d, %d, %d, %d ]\n", a[0], a[1], a[2], a[3] );
   return 0;
}

マッチング制約としての 0, 1, 2, … 9

0, 1, … 9 はマッチング制約です。マッチング制約は、入力オペランドと、番号で指定された出力オペランドの両方に同じレジスターを割り当てるよう、コンパイラーに指示するために使用されます。マッチング制約はこのようなものとして、入力オペランドにのみ使用することができます。このマッチング制約は、複数ある演算の 1 つが前の演算の結果を入力として使用する場合には不可欠です。マッチング制約がない場合、コンパイラーは入力オペランドと出力オペランドの両方に同じレジスターを使用しなければならないことを認識できません。マッチング制約の使用例については、記事「Linux on z Systems 向けインライン・アセンブリーの高度な機能」で詳しく説明します。

修飾子

修飾子は、対応するオペランドに関する詳細情報をコンパイラーに伝える目的で追加されます。サポートされる修飾子のリストは、コンパイラーの今後のリリースではさらに大きくなる可能性があります。製品ごとにサポートされている修飾子を網羅したリストについては、コンパイラー・マニュアルを参照してください。Linux on z Systems では現時点で、以下の修飾子がサポートされています。

  • 修飾子「=」: 命令のオペランドが書き込み専用であることを示します。前の値は破棄され、出力データで置き換えられます。
  • 修飾子「+」: 命令によってオペランドの読み取り/書き込みが行われることを示します。
  • 修飾子「&」: 命令が完了する前に、入力オペランドを使用してオペランドを変更できることを示します。
  • 修飾子「%」: このオペランドとこれに続くオペランドに関して交換可能な命令であることを宣言します。つまり、命令を生成する際に修飾子「%」を伴うオペランドと次のオペランドの順番は、安全に入れ替えられることを意味します。したがって、修飾子「%」を最後のオペランドに指定することはできません。修飾子「%」の目的は、コンパイラーにコードを最適化する機会を与えることです。コンパイラーが 2 つのオペランドの順番を入れ替えたほうがパフォーマンスを向上させられることを証明できる場合、これらの交換可能なオペランドを使用して処理を進めることができます。

「=」(書き込み専用) 修飾子と「+」(読み取り/書き込み) 修飾子は、重要です。この 2 つの修飾子を誤って指定すると、予期せぬ結果を招く場合があります。これらの修飾子による影響をデモンストレーションするために、リスト 9 に記載するプログラム example08a.c では、「+」修飾子を使用すべき場所で「=」修飾子を使用しています。

リスト 9. 誤った出力修飾子を使用した example08a.c
#include <stdio.h>
int main () {
   int a = 10, b = 200;
   printf ("INITIAL: a = %d, b = %d\n", a, b );        // Modifier “=” is used for a
   asm ("AR %0, %1\n" 
            :"=r"(a) 
            :"r"(b));
   printf ("RESULT : a = %d, b = %d\n", a, b );
   return 0;
}

このプログラムの意図は、変数 ab の合計を a に代入することです。そのために、プログラムでは AR 命令を使用しています。AR 命令は、両方のオペランドの値を加算した後、その合計値を最初のオペランドに格納します。したがって、最初のオペランドでは、読み取り処理書き込み処理の両方が実行されます。最初のオペランド (変数 a) には書き込み処理だけが必要であることをコンパイラーに伝えるために、ここでは修飾子「=」が使用されています。これによってコンパイラーは誤った方向に導かれ、example08a.c コードは正しく実行されないことになります。

リスト 10. 誤った出力修飾子を使用した場合の結果
xlc -o example08a ./example08a.c  
./example08a
INITIAL: a = 10, b = 200
RESULT : a = 200, b = 200        <- a is supposed to be 210

適切な修飾子「+」を使用すると、正しい結果が生成されます。図 4 に、それぞれのケースでコンパイラーが生成するコードの違いを示します。左側が不適切な修飾子を使用した場合、右側が適切な修飾子を使用した場合の結果です。

図 4. 異なる修飾子を使用した場合に生成されるコードの違い

生成されたアセンブリー・コードの違いから、書き込み専用修飾子「=」を使用すると、実際にコンパイラーは誤った方向に導かれることが明らかです。左側のパネルを見るとわかるように、コンパイラーはレジスター r0 へのロード [ L %r0,172(,%r15) ] を省略して、r0 に対して AR 命令 [ AR %r0, %r1 ] を呼び出しています。その結果、AR 命令での r0 には、変数 a の値が格納されて使用されるのではなく、たまたま r0 に格納されている値が使用されることになります。これが、修飾子「=」によって誤った結果が生成される理由です。一方、右側のパネルに示されているように適切な修飾子「+」を使用すれば、この問題は回避されます。この場合、コンパイラーは適切な値をレジスター r0 にロードして [ L %r0,172(,%r15) ] から、r0 を使用します。

不適切な修飾子を使用しないようにする唯一の方法は、対応するアセンブラー命令の定義を参照してからアセンブラー命令を使用することです。詳細については、「z/Architecture Principles of Operation」(IBM Publications、No. SA22-7832-10) を参照してください。「参考資料」セクションに、正式版へのリンクが記載されています。

出力オペランド・リスト

出力オペランド・リストは、任意の個数 (ゼロ個も可) の出力オペランドで構成されます。出力オペランドがない場合、リストは空になります。その場合、インライン・アセンブリー・ステートメント内の出力オペランド・リストは、対応する「:」のみになります。リスト上にある複数の出力オペランドは、コンマで区切られます。それぞれのオペランドは、必須修飾子 (「+」または「=」)、制約、および括弧で囲まれた C/C++ 式からなります。C/C++ 式の値は、インライン・アセンブリー・ステートメントに含まれるアセンブラー命令の出力オペランドとして使用されます。出力オペランドは、変更可能な lvalue でなければなりません。

入力オペランド・リスト

入力オペランド・リストは、任意の個数 (ゼロ個も可) の入力オペランドで構成されます。入力オペランドがない場合、リストは空になります。その場合、インライン・アセンブリー・ステートメント内の入力オペランド・リストは、対応する「:」のみになります。リスト上にある複数の入力オペランドは、コンマで区切られます。それぞれのオペランドは、制約と、括弧で囲まれた C/C++ 式からなります。C/C++ 式の値は、インライン・アセンブリー・ステートメントに含まれるアセンブラー命令の入力オペランドとして使用されます。

クロバー・リスト

クロバー・リストは、(a) メモリー、(b) 条件コード、(c) レジスター名からなるコンマ区切りリストです。クロバー・リストに設定するすべての値は、二重引用符で囲み、コンマで区切る必要があります。クロバー・リストの目的は、出力オペランド・リストにも入力オペランド・リストにも記載されていないエンティティーが、アセンブラー命令によって更新される可能性があることをコンパイラーに伝えることです。

(a) クロバー・リストでのメモリーの指定

インライン・アセンブリー・ステートメントに属するアセンブラー命令が、入力オペランド・リストと出力オペランド・リストに記載されていないエンティティーに対して読み取りまたは書き込みを行える場合、そのステートメントのクロバー・リストにメモリーを追加する必要があります。その一例は、入力オペランドが指すメモリーにアクセスするアセンブラー命令です。この場合、クロバー・リストにメモリーを追加するのは、コンパイラーが他のメモリー参照をまたいでアセンブラー命令を移動しないこと、さらには使用されるデータがアセンブリー・ステートメントの完了後に有効になることを確実にするためです。ただし、クロバー・リストにメモリーを追加すると、不要なリロードが多数行われることになるため、ハードウェアのプリフェッチによるメリットが損なわれます。こうした理由から、クロバー・リストにメモリーを追加する際には、慎重に行うようにして、回避可能なパフォーマンス低下を避けるようにしてください。

(b) クロバー・リストでの条件コードの指定

CompareAddSubtract などの多くのアセンブラー命令によって、条件コードは更新されます。コンパイラーにこの事実を伝えるには、クロバー・リストに「cc」を追加する必要があります。条件コードを変更するアセンブラー命令の完全なリストについては、「z/Architecture Principles of Operation」を参照してください。

(c) クロバー・リストでのレジスター名

出力オペランド・リストと入力オペランド・リストに記載されていないレジスターを、アセンブラー命令で使用または更新する場合には、影響を受けるすべてのレジスターをクロバー・リストに記載する必要があります。コンパイラーは、クロバー・リストの情報に基づいて、インライン・アセンブリー・ステートメントの処理を円滑に行います。アセンブリー・ステートメントで使用されるレジスターをクロバー・リストに記載しない場合には、コンパイラーはそのレジスターが使用されることを認識しません。その場合、コンパイラーがそのレジスターを別の目的で使用して、その結果として計算値が誤ったものになる可能性があります。

コンパイラーは独自の処理のためにいくつかのレジスターを予約することに注意してください。予約済みレジスターをクロバー・リストに含めることはできません。表 2 に、IBM XL コンパイラーを使用する場合に指定されるレジスターの用途を記載します。

表 2. z Systems で IBM XL コンパイラーによって指定されるレジスターの用途
レジスター名特殊な用途ユーザーによる使用の可/不可
r2、r3パラメーター、戻り値可 (慎重に使用すること)
r4、r5、r6パラメーター可 (慎重に使用すること)
r13リテラル・プールのベース・レジスター不可
r14戻りアドレス可 (慎重に使用すること)
r15スタック・ポインター不可

クロバー・リストの使用例については、記事「Linux on z Systems 向けインライン・アセンブリーの高度な機能」で詳しく説明しています。

まとめ

インライン・アセンブリーは、ユーザーにとって、アセンブラー命令を直接 C/C++ プログラムに組み込む手段となります。上級ユーザーはこの機能を使用して特定のコード・セクションのアセンブラー命令をハンドコーディングすることで、さらにパフォーマンスを向上させることができます。IBM XL コンパイラーは、最適化の各レベルで生成されるコードを最適化するために、極めて高度なタスクを実行します。そのため、インライン ASM でパフォーマンスを向上させるには、ユーザーはターゲット・コードの実行に関して詳細に理解している必要があります。埋め込まれたアセンブラー命令によってパフォーマンスがどう影響を受けるかを注意深く分析し、綿密なプランニングと徹底的なテストを行うことが、パフォーマンスの向上を実現するには不可欠です。

この記事では、Linux on z Systems 向けインライン・アセンブリーの基礎についてのみ説明しました。高度な機能については、記事「Linux on z Systems 向けインライン・アセンブリーの高度な機能」で説明します。

謝辞

この記事の作成にあたって助言してくださった Visda Vokhshoori 女史と Nha-Vy Tran 女史に感謝いたします。

参考文献

  • IBM XL C/C++ for Linux on z Systems 製品ページにアクセスして、詳細な情報を入手してください。
  • Rational C/C++ Cafe コミュニティーに参加して、他のメンバーとつながってください。

参考資料


ダウンロード可能なリソース


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=1017473
ArticleTitle=Linux on z Systems 向けインライン・アセンブリーの基礎
publish-date=10222015