x64 Assembly Language


2017.11.11: created by

概要

"x64" とは Intel と AMD の 32 ビット x86 命令セット・アーキテクチャ (Instruction Set Archtecture) の64 ビット拡張を指す名称です。時に "x86-64" や "x86_64" と呼ばれることがあります。

この講義では世間で広く使われている用語の使い方にしたがうものとします。

すなわち、Intel の 8086 (16ビット CPU) を拡張した 32bit までの CPUのアーキテクチャをまとめて "x86" と呼び、 さらにその "x86"を64 ビットに拡張したアーキテクチャを "x64" と呼びます。 AMDのアーキテクチャに関しても、"x86"と互換性のある32ビットまでのCPUを"x86"に、 64ビットCPUを"x64"に含めます。

用語のまとめ


x86からx64への拡張

アセンブラによる構文の違い

x86やx64のアセンブラは 「インテル構文 (Intel Syntax)」と「AT&T構文」の2種類があります。 次のような違いがあります。

この文書では gcc が採用している AT&T 構文にしたがうものとします。


実験環境

本ページの実行例は以下の環境で実行したものです。


アセンブリ言語のディレクティブ

Apple公式へジャンプ

現在のセクションを指定するディレクティブ

section  segname , sectname [[[ , type ] , attribute ] , sizeof_stub ]
指定したセクションを開始する
ディレクティブセクション説明
.text __TEXT,__text 命令を配置するセクションを開始する
.data __DATA,__data 初期値を持つデータを配置するセクションを開始する

Location Counter を動かすディレクティブ

.align     align_expression [ , 1byte_fill_expression [, max_bytes_to_fill] ]
.p2align   align_expression [ , 1byte_fill_expression [, max_bytes_to_fill] ]
.p2alignw  align_expression [ , 2byte_fill_expression [, max_bytes_to_fill] ]
.p2alignl  align_expression [ , 4byte_fill_expression [, max_bytes_to_fill] ]
.align32   align_expression [ , 4byte_fill_expression [, max_bytes_to_fill] ]
align ディレクティブは location counter を align_expression の境界に動かす。 $2^{0}$ から $2^{15}$ まで指定できる。
  (例) .align 3     $2^3 = 8$ バイトのアラインメントに合わせる
.org  expression [ , fill_expression ]
location counter をその値に設定する。 fill_expression が指定された場合は、 現在のlocation counter と新しい値の間をfill_expressionの値で埋める。
  (例)  .org 0x100    100H番地から開始する

データ生成用ディレクティブ

.ascii   [ "string" ] [ , "string" ] ...
.asciiz  [ "string" ] [ , "string" ] ...
.ascii はアスキー文字列を生成する。.asziizは最後に'\0' を追加する。
  (例) .ascii "apple\0"
       .asciiz "apple"
.byte  [ expression ] [ , expression ] ...
  1バイトデータを生成する
.short  [ expression ] [ , expression ] ...
  2バイトデータを生成する
.long  [ expression ] [ , expression ] ...
  4バイトデータを生成する
.quad  [ expression ] [ , expression ] ...
  8バイトデータを生成する
(例)
.byte  74,0112,0x4A,0x4a,'J                             | the same byte
.short 64206,0175316,0xface                             | the same short
.long  -1234,037777775456,0xfffffb2e                    | the same long
.quad  -1234,01777777777777777775456,0xfffffffffffffb2e | the same quad

シンボルを扱うディレクティブ

.globl  symbol_name
symbol_name を外部に見せる( external にする)。

x64 アセンブラの表記法

命令を表すニーモニックの最後についている1文字で、扱うデータのバイト長を表します。
命令の後置記号ビット幅バイト幅備考
b81bytemovb
w162wordmovw
l324longmovl
q648quadmovq
レジスタの前または後についている1文字で、扱うレジスタのバイト長を表します。
レジスタ{前|後}置記号場所ビット幅バイト幅備考
h81high%ah
l81low%hl
x162eXtended%ax
e324Extended%eax
r648%rax

  (例)
  movb $1, %al     # 16ビット ax レジスタの下位8bitへ
  movb $1, %ah     # 16ビット ax レジスタの上位8bitへ
  movw $1, %ax     # 16bitビット ax レジスタへ
  movl $1, %eax    # 32bitビット eax レジスタへ
  movq $1, %rax    # 64bitビット rax レジスタへ

Cコンパイラとアセンブラ

x64 のプログラミングにおいて、レジスタの使い方はOSによって異なる部分があります。 それぞれのOSの関連文書にざっと目を通しておくことを勧めます。

汎用レジスタの使い方

32 ビット Windows32 ビット Linux64 ビット Windows64 ビット Linux
{e|r}ax戻り値を入れるレジスタ
{e|r}cx自由に使っていいレジスタ引数を入れるレジスタ
{e|r}dx自由に使っていいレジスタ引数を入れるレジスタ
{e|r}bx自由に使っていいレジスタ保存しなければならないレジスタ
{e|r}spスタックポインタ
{e|r}bp元の値を保存しなければならないレジスタ
{e|r}si元の値を保存しなければならないレジスタ引数を入れるレジスタ
{e|r}di元の値を保存しなければならないレジスタ引数を入れるレジスタ
r8~9-引数を入れるレジスタ
r10~11-自由に使っていいレジスタ
r12~15-元の値を保存しなければならないレジスタ

関数呼び出しの際に、x86では 引数はすべてスタックに積むのが一般的(処理系依存でレジスタ渡しの場合もありましたが)でしたが、 x64 においては一部の引数をレジスタで渡し、残りをスタックに積んで渡します。 このときに、どのレジスタを使うか、レジスタで渡す引数分のメモリをスタック上に確保するかどうか、 などが処理系依存です。

System V ABI (Application Binary Interface) は「関数呼出規約(calling convention)」や「実行可能オブジェクトやリンキング・ファイルのフォーマット (Executable and Linkable Format (ELF)」 などを定めた仕様であり、Linuux や BSD をはじめとする主要なUnixで採用されています。その中で x86-64 の呼び出し規約は次のように定義されています。

関数へのパラメータは rdi, rsi, rdx, rcx, r8, r9 レジスタで渡され、これ以上のパラメータはスタックに逆順で積まれる。 関数は call 命令によって呼び出され、次の命令のアドレスをスタックにpushし、指定されたアドレスにjumpする。 関数から戻るのは ret 命令によってであり、スタックからアドレスを取り出してそこにjumpする。 call命令を実行する時はスタックは16byteにalignされていなければならない。
関数では、rbp, r12, r13, r14, r15 レジスタの値は保持する(変更されない)が、 rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11 の値は保持されない。

x64のWindowsでは 最初の4個の引数は rcx, rdx, r8, r9 レジスタ(およびXMM0 〜 XMM3)を用いて、残りはスタックに積んで渡されます。 引数がレジスタで渡される場合でも、スタック上には必ず4個分のパラメータのための領域が確保されます。

Apple の Introduction ot OS X ABI Function Call Guide によれば、 Apple の x86-64 Code Model によれば ローカルなデータへのアクセスは RIP レジスタからの間接アドレッシングによってアクセスしなくてはいけません

関数呼び出しの戻り値は、64ビットに収まるスカラー型は rax に入れて、非スカラー型は XMM0 に入れて返されます。


コマンド
  $ gcc -O -S sample.c
    Cのソースファイル sample.c をコンパイルしてアセンブリ言語のファイル sample.s を生成する
  $ as sample.s -o sample.o
    アセンブリ言語のファイル sample.s をアセンブルしてオブジェクトファイル sample.o を生成する
  $ ld sample.o -e _main -o a.out
    オブジェクトファイルやライブラリをリンクして実行可能ファイル a.out  を生成する
  $ ./a.out
    実行可能ファイル a.out の実行を開始する

値を返す関数のアセンブリ・コード

sample1.c
int sample1() {
  return 5;
}


sample1.s
生成方法: gcc -O -S sample1.c
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_sample1
	.p2align	4, 0x90
_sample1:                               ## @sample1
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	movl	$5, %eax
	popq	%rbp
	retq
	.cfi_endproc


.subsections_via_symbols

引数を渡す関数のアセンブリ・コード

sample2の実行例
vermeer-2:i486 nitta$ gcc -O sample2a.c sample2b.c sample2c.c -o sample2
vermeer-2:i486 nitta$ ./sample2
450


sample2a.c
int sum(int x1,int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9) {
  int ret = 0;
  ret = x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9;
  return ret;
}


sample2a.s
生成方法: gcc -O -S sample2a.c
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_sum
	.p2align	4, 0x90
_sum:                                   ## @sum
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
                                        ## kill: %ESI<def> %ESI<kill> %RSI<def>
                                        ## kill: %EDI<def> %EDI<kill> %RDI<def>
	leal	(%rdi,%rsi), %eax
	addl	%edx, %eax
	addl	%ecx, %eax
	addl	%r8d, %eax
	addl	%r9d, %eax
	addl	16(%rbp), %eax
	addl	24(%rbp), %eax
	addl	32(%rbp), %eax
	popq	%rbp
	retq
	.cfi_endproc


.subsections_via_symbols


sample2b.c
#include <stdio.h>
extern int sum(int,int,int,int,int,int,int,int,int);
void foo() {
  int ret = 0;
  ret = sum(10, 20, 30, 40, 50, 60, 70, 80, 90);
  printf("%d\n",ret);
}


sample2b.s
生成方法: gcc -O -S sample2b.c
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_foo
	.p2align	4, 0x90
_foo:                                   ## @foo
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	subq	$8, %rsp
	movl	$10, %edi
	movl	$20, %esi
	movl	$30, %edx
	movl	$40, %ecx
	movl	$50, %r8d
	movl	$60, %r9d
	pushq	$90
	pushq	$80
	pushq	$70
	callq	_sum
	addq	$32, %rsp
	movl	%eax, %ecx
	leaq	L_.str(%rip), %rdi
	xorl	%eax, %eax
	movl	%ecx, %esi
	popq	%rbp
	jmp	_printf                 ## TAILCALL
	.cfi_endproc

	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"%d\n"


.subsections_via_symbols


sample2c.c
extern void foo();
int main(int argc, char** argv) {
  foo();
  return 0;
}


sample2c.s
生成方法: gcc -O -S sample2c.c
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	xorl	%eax, %eax
	callq	_foo
	xorl	%eax, %eax
	popq	%rbp
	retq
	.cfi_endproc


.subsections_via_symbols

Cコンパイラのオプティマイズ(最適化)・オプション

sample3.c
extern void exit(int);
int main(int argc,char** argv) {
  exit(0);
}


sample3.s
生成方法: gcc -S sample3.c
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movl	$5, %eax
	movl	$0, -4(%rbp)
	movl	%edi, -8(%rbp)
	movq	%rsi, -16(%rbp)
	movl	%eax, %edi
	callq	_exit
	.cfi_endproc


.subsections_via_symbols


sample3.s
生成方法: gcc -O -S sample3.c
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	movl	$5, %edi
	callq	_exit
	.cfi_endproc


.subsections_via_symbols


sample3.c をオプティマイズ・オプションを変えてコンパイルしてみる。
オブジェクト・ファイルを逆アセンブルして出力されたアセンブル言語を調べる。
実行可能ファイルを生成して実行する。
vermeer-2:i486 nitta$ gcc -c -g sample3.c
vermeer-2:i486 nitta$ objdump -d -S sample3.o

sample3.o:     file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_main:
; int main(int argc,char** argv) {
       0:      55 	   pushq %rbp
       1:      48 89 e5    movq	 %rsp, %rbp
       4:      48 83 ec 10 	 subq  $16, %rsp
       8:      b8 05 00 00 00 	 movl  $5, %eax
       d:      c7 45 fc 00 00 00 00    movl	$0, -4(%rbp)
      14:      89 7d f8    movl	 %edi, -8(%rbp)
      17:      48 89 75 f0 	 movq  %rsi, -16(%rbp)
; exit(5);
      1b:	89 c7	movl	%eax, %edi
      1d:	e8 00 00 00 00 	callq 0 <_main+0x22>
vermeer-2:i486 nitta$ gcc -O -c -g sample3.c
vermeer-2:i486 nitta$ objdump -d -S sample3.o

sample3.o:     file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_main:
; int main(int argc,char** argv) {
       0:      55 	   pushq %rbp
       1:      48 89 e5    movq	 %rsp, %rbp
; exit(5);
       4:	bf 05 00 00 00	movl	$5, %edi
       9:	e8 00 00 00 00 	callq	0 <_main+0xE>
vermeer-2:i486 nitta$ gcc -O sample3.c -o sample3
vermeer-2:i486 nitta$ ./sample3
vermeer-2:i486 nitta$ echo $?
5
vermeer-2:i486 nitta$ 

C言語の関数内にアセンブリ言語のコードを記述する

sample4.c
#include <stdio.h>

int sum(int x1,int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9) {
  int ret;
  __asm__("                                  \n\
	leal	(%%rdi,%%rsi), %%eax         \n\
	addl	%%edx, %%eax                 \n\
	addl	%%ecx, %%eax                 \n\
	addl	%%r8d, %%eax                 \n\
	addl	%%r9d, %%eax                 \n\
	addl	16(%%rbp), %%eax             \n\
	addl	24(%%rbp), %%eax             \n\
	addl	32(%%rbp), %%eax             \n\
"
	  : "=a"(ret)
	  );
  return ret;
}

void foo() {
  int ret = 0;
  ret = sum(10, 20, 30, 40, 50, 60, 70, 80, 90);
  printf("%d\n",ret);
}

int main(int argc, char** argv) {
  foo();
  return 0;
}


sample4.c をコンパイルして実行する
vermeer-2:i486 nitta$ gcc sample4.c -o sample4
vermeer-2:i486 nitta$ ./sample4
450


演習


macOS で演習を行うものとします。

x64 のアセンブリ言語で書いたプログラムを実行させてみることにしましょう。 macOS 上で「ターミナル」すなわちシェル(対話環境)を起動して下さい。

プログラムの多くの部分はC言語で記述し、部分的に x64 のアセンブリ言語で記述することにしましょう。


課題5a: if文のコードを調べる

提出ファイルex5a.s
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題5a」 http://ynitta.com/class/arch/local/handin/up.php?id=kadai5a

if 文のコードを調べなさい。 適切なCの関数を ex5a.c に定義し、gccを使ってアセブリ言語のコード ex5a.s を出力し、 それにわかりやすいコメントを書き加えてから 提出しなさい。

GCCのアセンブリ言語の中では、

がコメントとなります。

あまりに簡単な条件式でコンパイラが成り立つか否かを判断できる場合は、 if 文のコードをださないことがあることに注意して下さい。 つまり、以下のようなCのコードを書いても駄目だ、ということです。

  (例)
    if (1 > 0) { ... } ←コンパイル時に、いつでも真と判明する
  (例2)
    int a = 500, b=450;
    if (a < b) { ... } ← コンパイル時に、いつでも偽と判明する


課題6a: for文のコードを調べる

提出ファイルex6a.s
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題6a」 http://ynitta.com/class/arch/local/handin/up.php?id=kadai6a

for 文のコードを調べなさい。 適切なCの関数を ex6a.c に定義し、gccを使ってアセブリ言語のコード ex6a.s を出力し、 それにわかりやすいコメントを書き加えてから提出して下さい。


課題7a: 値を返す関数をアセンブリ言語で記述する

提出ファイルex7a.s
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題7a」 http://ynitta.com/class/arch/local/handin/up.php?id=kadai7a

1から「引数で指定された整数」までの和を返す関数を 機械語で書きなさい。 Cの関数の中に __asm__ 文でアセンブリ言語の命令を書くこと。 それにわかりやすいコメントを書き加えてから提出しなさい。


課題8a: 引数を受け取り値を返す関数をアセンブリ言語で記述する

提出ファイルex8a.c
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題7a」 http://ynitta.com/class/arch/local/handin/up.php?id=kadai8a

引数として与えられた 64bit整数が奇数であれば1を、 奇数でなければ 0 を返す関数 int isodd(int n) の 本体をアセンブリ言語で書きなさい。

ただし、 多少実行速度が遅くても構わないので、__asm__ の中では 機械語命令としては 「andq, movq だけ」 を使うこと。ラベル、レジスタ名、定数などは自由に使用して構わない。

動作することを確認したら、ソースを提出しなさい。

ex8a.c
long isodd(long n) {
  long ret = 0;
  __asm__("  \n\
    # andq と movq 命令だけを使って題意の動作をするプログラムを書きなさい \n\
"
	  : "=a"(ret));
  return ret;
}


oddmain.c
#include <stdio.h>
#include <stdlib.h>
extern long isodd(int);
int main(int argc, char **argv) {
  long n;
  printf("input integer: ");
  scanf("%ld",&n);
  printf("%ld\n",isodd(n));
}


oddmain.c + ex8a.c の実行例
bash-3.2$ gcc -O oddmain.c ex8a.c -o ex8a
bash-3.2$ ./ex8a
input integer: 5
1
bash-3.2$ ./ex8a
input integer: 4
0

[注意] C言語で関数を作っておいてそれをコンパイルしてアセンブリ言語を出させる方法で、 上記の機械語だけを使ったコードがでるかもしれません。


課題8b: 引数を受け取り値を返す関数をアセンブリ言語で記述する

提出ファイルex8b.c
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題8b」 http://ynitta.com/class/arch/local/handin/up.php?id=kadai8b

引数として与えられた 64bit整数が奇数であれば1を、 奇数でなければ 0 を返す関数 int isodd(int n) の 本体をアセンブリ言語で書きなさい。

ただし、 多少実行速度が遅くても構わないので、__asm__ の中では 機械語命令としては 「movq, testq, jz, jmp だけ」 を使うこと。ラベル、レジスタ名、定数などは自由に使用して構わない。

動作することを確認したら、ソースを提出しなさい。

ex8b.c
long isodd(long n) {
  long ret = 0;
  __asm__("          \n\
    # movq, testq, jz, jmp 命令だけを使って題意の動作をするプログラムを書きなさい。
    # jump 先を指定するためにラベルを定義する必要があります。
"
	  : "=a"(ret));
  return ret;
}


oddmain.c + ex8b.c の実行例
bash-3.2$ gcc -O oddmain.c ex8b.c -o ex8b
bash-3.2$ ./ex8b
input integer: 5
1
bash-3.2$ ./ex8b
input integer: 4
0

[注意] C言語で関数を作っておいてそれをコンパイルしてアセンブリ言語を出させる 方法では、上記の機械語だけを使ったコードはでないでしょう。 自分で考えて書くことが重要です。


追加問題

課題9a: 再帰関数をアセンブリ言語で記述する

提出ファイルex9a.c
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題9a」 http://ynitta.com/class/arch/local/handin/up.php?id=kadai9a

フィボナッチ数を再帰的に計算する関数 long fib(long) の本体をアセンブリ言語で書きなさい。

  fib(0) = 1
  fib(1) = 1
  fib(n) = fib(n-2) + fib(n-1)

基本的に次と同等の動作をする関数を定義してほしい。

fib.c
long fib(long n) {
  long result = 1L;
   if (n >= 2) {
     result = fib(n-1) + fib(n-2);
   }
  return result;
}

ただし、Cコンパイラに余計なことさせないために関数宣言は void fib(void) とすること。 また、__asm__ の中で使うことのできる機械語命令は、以下の種類だけとする。

  movq, addq, subq, cmpq, pushq, popq, jl, jle, jz, jn, jne, jmp

ラベル、レジスタ名、定数などは自由に使用して構わない。 メモリとのデータのやり取りが最小限になるように工夫すること。 すなわち、引数や途中結果などを保存するには、 値が保存されることが保証されているレジスタ(たとえば rbx, r12 〜 r15 など) を pushq して用いること。

動作することを確認したら、ソースを提出しなさい。

[注意] gcc ではleaf function (他の関数を呼び出さない関数)に対する最適化として Red zone を使用することがある。 すなわち、スタック・ポインタを動かさずに、スタックポインタよりも 128 byte 小さい アドレスまでのメモリ領域を勝手に使うことがある。 したがって、gccがleaf functionとして認識している関数の中で、__asm__ の中で callq (関数呼び出し)を行う場合は、-mno-red-zone オプションを使って Red zoneによる最適化を禁止する必要がある。

ex9a.cの骨子
void fib(void) として宣言する
void fib(void) {
  // push %rbp
  // movq %rsp, %rbp
  // これより上は、 コンパイラが自動的に生成する
  __asm__ __volatile__(
                         // %%rdi に引数が入っている
                         // 返り値は %%rax に入れる
		       :
		       );
  // これより下はコンパイラが自動的に生成する
  // pop %rbp
  // retq
}


fibmain.c
#include <stdio.h>
#include <stdlib.h>
extern long fib(long);
int main(int argc, char **argv) {
  long n;
  printf("input integer: ");
  scanf("%ld",&n);
  printf("%ld\n",fib(n));
}


ex9a.c + fibmain.c の実行例
monet:src nitta$ gcc -mno-red-zone -O ex9a.c fibmain.c -o ex9a
monet:src nitta$ ./ex9a
input integer: 1
1
monet:src nitta$ ./ex9a
input integer: 2
2
monet:src nitta$ ./ex9a
input integer: 5
8
monet:src nitta$ ./ex9a
input integer: 20
10946

補足説明

__asm__文では次のシンタックスで、C言語の変数をアセンブリ言語の記述の中で利用することができる。
__asm__( 機械語命令 : output : input : 値を変更するregister)

便利な機能ではあるが、一部無駄が生じることがあるので注意が必要である。 たとえば下の例では、ret変数 -16(%rbp) と %rax レジスタの間で無駄な転送が行われている。

ex9b.c
long fib(long n) {
  long ret;
  __asm__ __volatile__ ("movq $1, %%rax ;"   // 返り値に初期値1を入れてから
			"movq %1, %%r10 ;"   // 引数 n の値を %r10 (自由に使える)へ
			"cmpq $2, %%r10 ;"   // n < 2 ならば MYLABEL01 へジャンプ
			"jl  MYLABEL01  ;" 
			"subq $1, %%r10 ;"     // r10 =  n - 1
			"movq %%r10, %%rdi ;"
			"callq _fib     ;"   // fib(n-1)
			"movq %%rax, %0 ;"   // ret に返りを保存
			"movq %1, %%r10 ;"   // %r10 = n - 2
			"subq $2, %%r10 ;"
			"movq %%r10, %%rdi ;" 
			"callq _fib     ;"   // fib(n-2)
			"addq %0, %%rax ;"   // %rax = fib(n-2) + ret
			"MYLABEL01:     ;"
			"movq %%rax, %0 ;" // ret = %rax
			: "=g"(ret)        // %0 としてアクセスできる(出力用)
			: "g"(n));         // %1 としてアクセスできる(入力用)
  return ret;
}


ex9b.s
生成方法: gcc -mno-red-zone -O -S ex9b.c
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_fib
	.p2align	4, 0x90
_fib:                                   ## @fib
	.cfi_startproc
## BB#0:
	pushq	%rbp
Lcfi0:
	.cfi_def_cfa_offset 16
Lcfi1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Lcfi2:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movq	%rdi, -8(%rbp)
	## InlineAsm Start
	movq	$1, %rax
	movq	-8(%rbp), %r10
	cmpq	$2, %r10
	jl	MYLABEL01
	subq	$1, %r10
	movq	%r10, %rdi
	callq	_fib
	movq	%rax, -16(%rbp)
	movq	-8(%rbp), %r10
	addq	$-2, %r10
	movq	%r10, %rdi
	callq	_fib
	addq	-16(%rbp), %rax
MYLABEL01:
	movq	%rax, -16(%rbp)

	## InlineAsm End
	movq	-16(%rbp), %rax
	addq	$16, %rsp
	popq	%rbp
	retq
	.cfi_endproc


.subsections_via_symbols


fibmain2.c
#include <stdio.h>
#include <stdlib.h>
extern long fib(long);
int main(int argc, char **argv) {
  printf("%ld\n",fib(45L));
}


ex9b.c + fibmain2.c の実行例
monet:src nitta$ gcc -mno-red-zone -O ex9b.c fibmain2.c -o ex9b-time
monet:src nitta$ time ./ex9b-time
1836311903

real	0m7.395s
user	0m7.370s
sys	0m0.006s

次のようなコードでもまだ無駄がある。 引数 n とメモリ -8(%rbp) の間で無駄なデータ転送が行われる。

ex9c.c
long fib(long n) {
  long ret;
  __asm__ __volatile__ ("movq $1, %%rax ;"   // 返り値に初期値1を入れてから
			"movq %1, %%rdi ;"   // 引数 n の値を %rdi へ
			"cmpq $2, %%rdi ;"   // n < 2 ならば MYLABEL01 へジャンプ
			"jl  MYLABEL01  ;" 
			"subq $1, %%rdi ;"   // r10 =  n - 1
			"callq _fib     ;"   // fib(n-1)
			"movq %%rax, %0 ;"   // ret に返りを保存
			"movq %1, %%rdi ;"   // %rdi = n - 2
			"subq $2, %%rdi ;"
			"callq _fib     ;"   // fib(n-2)
			"addq %0, %%rax ;"   // %rax = fib(n-2) + ret
			"MYLABEL01:     ;"
			"movq %%rax, %0 ;" // ret = %rax
			: "=g"(ret)        // %0 としてアクセスできる(出力用)
			: "g"(n));         // %1 としてアクセスできる(入力用)
  return ret;
}


ex9c.s
生成方法: gcc -mno-red-zone -O -S ex9c.c
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 12
	.globl	_fib
	.p2align	4, 0x90
_fib:                                   ## @fib
	.cfi_startproc
## BB#0:
	pushq	%rbp
Lcfi0:
	.cfi_def_cfa_offset 16
Lcfi1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Lcfi2:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movq	%rdi, -8(%rbp)
	## InlineAsm Start
	movq	$1, %rax
	movq	-8(%rbp), %r10
	cmpq	$2, %r10
	jl	MYLABEL01
	subq	$1, %r10
	movq	%r10, %rdi
	callq	_fib
	movq	%rax, -16(%rbp)
	movq	-8(%rbp), %r10
	addq	$-2, %r10
	movq	%r10, %rdi
	callq	_fib
	addq	-16(%rbp), %rax
MYLABEL01:
	movq	%rax, -16(%rbp)

	## InlineAsm End
	movq	-16(%rbp), %rax
	addq	$16, %rsp
	popq	%rbp
	retq
	.cfi_endproc


.subsections_via_symbols


ex9c.c + fibmain2.c の実行例
monet:src nitta$ gcc -mno-red-zone -O ex9c.c fibmain2.c -o ex9c-time
monet:src nitta$ time ./ex9c-time
1836311903

real	0m7.368s
user	0m7.342s
sys	0m0.006s
参考
ex9a.c + fibmain2.c の実行例
monet:src nitta$ gcc -mno-red-zone -O ex9a.c fibmain2.c -o ex9a-time
monet:src nitta$ time ./ex9a-time
1836311903

real	0m5.927s
user	0m5.908s
sys	0m0.006s


http://ynitta.com/