"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"に含めます。
Intel 8086、およびその後方互換性を持つマイクロプロセッサの命令セットアーキテクチャの総称。 16ビットの8086で登場し、32ビット拡張の80386, 486などインテルの32ビットプロセッサ (後にIA-32と命名)、および AMDなどの互換プロセッサを含む。 更に広義には64ビット拡張のx64を含むことがある。
80386の開発の際に定義された、16ビットx86を32ビットに拡張した命令セットアーキテクチャは 元々は「32ビット x86アーキテクチャ」と呼ばれていたが、 Intelが "IA-64" アーキテクチャを発表した際に "IA-32" と改名された。 なお "IA-32" と "IA-64" は名前が似ているが、全く異なるアーキテクチャで互換性がないので、混同しないこと。
IntelとHP (ヒューレット・パッカード)が共同開発した Itanium プロセッサ用の 64bit マイクロプロセッサ命令セット・アーキテクチャ。 VLIW (Very Long Word Instruction Set Computer) 用のアーキテクチャであり "x64" とは全く異なるので注意すること。 Itanium プロセッサは x86 プロセッサとの互換性がなかったために、ほとんど流行らずに消えていった。
x64またはx86-64とは、x86アーキテクチャを64ビットに拡張した命令セットアーキテクチャ。 実際には、AMDが発表したAMD64命令セット、続けてインテルが採用したIntel 64命令セット(かつてIA-32eまたはEM64Tと呼ばれていた)などを含む、各社のAMD64互換命令セットの総称である。x86命令セットと互換性を持っていることから、広義にはx86にx64を含む場合がある。
IA-32 の8本 (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP) の汎用レジスタに 更に 8 本(R8 〜 R15 )が追加され、ビット幅も32ビットから64ビットに拡張された。
128ビットの XMM レジスタがIA-32 では8本存在したが、16本に増やされた。
48ビット ($2^{48}$バイト=$2^8$テラバイト=256テラバイト) に拡張された。
x86やx64のアセンブラは 「インテル構文 (Intel Syntax)」と「AT&T構文」の2種類があります。 次のような違いがあります。
レジスタ名の前に%をつける。 Operation Source Destination の順である。 レジスタ間接アドレッシングは、レジスタ名に"()"をつけて表現する。
レジスタ名の前に%をつけない。 Operation Destination Source の順である。 レジスタ間接アドレッシングは、レジスタ名に"[]"をつけて表現する。
この文書では gcc が採用している AT&T 構文にしたがうものとします。
本ページの実行例は以下の環境で実行したものです。
gcc -v の実行結果 |
bash-3.2$ gcc -v |
as -v の実行結果 |
bash-3.2$ as -v |
section segname , sectname [[[ , type ] , attribute ] , sizeof_stub ] |
ディレクティブ | セクション | 説明 |
---|---|---|
.text | __TEXT,__text | 命令を配置するセクションを開始する |
.data | __DATA,__data | 初期値を持つデータを配置するセクションを開始する |
.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 3 $2^3 = 8$ バイトのアラインメントに合わせる
.org expression [ , fill_expression ] |
(例) .org 0x100 100H番地から開始する
.ascii [ "string" ] [ , "string" ] ... .asciiz [ "string" ] [ , "string" ] ... |
(例) .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 |
命令の後置記号 | ビット幅 | バイト幅 | 備考 | 例 |
---|---|---|---|---|
b | 8 | 1 | byte | movb |
w | 16 | 2 | word | movw |
l | 32 | 4 | long | movl |
q | 64 | 8 | quad | movq |
レジスタ{前|後}置記号 | 場所 | ビット幅 | バイト幅 | 備考 | 例 |
---|---|---|---|---|---|
h | 後 | 8 | 1 | high | %ah |
l | 後 | 8 | 1 | low | %hl |
x | 後 | 16 | 2 | eXtended | %ax |
e | 前 | 32 | 4 | Extended | %eax |
r | 前 | 64 | 8 | %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 レジスタへ
32 ビット Windows | 32 ビット Linux | 64 ビット Windows | 64 ビット 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 |
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$ |
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; } |
show_log('sample4.c をコンパイルして実行する', 'src/sample4.log'); ?>
macOS で演習を行うものとします。
x64 のアセンブリ言語で書いたプログラムを実行させてみることにしましょう。 macOS 上で「ターミナル」すなわちシェル(対話環境)を起動して下さい。
プログラムの多くの部分はC言語で記述し、部分的に x64 のアセンブリ言語で記述することにしましょう。
提出ファイル | ex5a.s |
---|---|
コメント欄: | なし |
提出先: | 「宿題提出Web: コンピュータ・アーキテクチャ:課題5a」 http://karel.tsuda.ac.jp/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) { ... } ← コンパイル時に、いつでも偽と判明する
提出ファイル | ex6a.s |
---|---|
コメント欄: | なし |
提出先: | 「宿題提出Web: コンピュータ・アーキテクチャ:課題6a」 http://karel.tsuda.ac.jp/class/arch/local/handin/up.php?id=kadai6a |
for 文のコードを調べなさい。 適切なCの関数を ex6a.c に定義し、gccを使ってアセブリ言語のコード ex6a.s を出力し、 それにわかりやすいコメントを書き加えてから提出して下さい。
提出ファイル | ex7a.s |
---|---|
コメント欄: | なし |
提出先: | 「宿題提出Web: コンピュータ・アーキテクチャ:課題7a」 http://karel.tsuda.ac.jp/class/arch/local/handin/up.php?id=kadai7a |
1から「引数で指定された整数」までの和を返す関数を 機械語で書きなさい。 Cの関数の中に __asm__ 文でアセンブリ言語の命令を書くこと。 それにわかりやすいコメントを書き加えてから提出しなさい。
提出ファイル | ex8a.c |
---|---|
コメント欄: | なし |
提出先: | 「宿題提出Web: コンピュータ・アーキテクチャ:課題7a」 http://karel.tsuda.ac.jp/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言語で関数を作っておいてそれをコンパイルしてアセンブリ言語を出させる方法で、 上記の機械語だけを使ったコードがでるかもしれません。
提出ファイル | ex8b.c |
---|---|
コメント欄: | なし |
提出先: | 「宿題提出Web: コンピュータ・アーキテクチャ:課題8b」 http://karel.tsuda.ac.jp/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言語で関数を作っておいてそれをコンパイルしてアセンブリ言語を出させる 方法では、上記の機械語だけを使ったコードはでないでしょう。 自分で考えて書くことが重要です。
提出ファイル | ex9a.c |
---|---|
コメント欄: | なし |
提出先: | 「宿題提出Web: コンピュータ・アーキテクチャ:課題9a」 http://karel.tsuda.ac.jp/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__( 機械語命令 : 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 |