PFI Seminar 2010/02/18

Post on 17-Dec-2014

3.405 views 1 download

description

 

Transcript of PFI Seminar 2010/02/18

いまどきじゃないアセンブラプログラミングx86アセンブリ言語の基礎からSSEまで

1

自己紹介

久保田展行

◦ @nobu_k, id:nobu-q

検索エンジンSedueを作ってます

2

本日の内容

x86アセンブリ言語の基礎

インラインアセンブラで遊ぶ

SSEを使ってみる

3

アセンブリ言語とは

機械語に近いプログラミング言語

◦ 機械語と一対一で対応

4

if (x < 0) {x = -x;

}

mov eax, [ebp + 8]cmp eax, 0jge L1neg eax

L1:

8B 45 08 3D 00 00 00 007D 02 F7 D8

C言語アセンブリ言語

機械語読むのは厳しい

まだマシ

アセンブリ言語の特徴

移植性が低い

◦ CPUや処理系によってすべてが変わる

読み書きが大変

◦ 可読性がものすごく低い

高級言語ではできないこともできる

◦ コンパイラが使えない命令も扱える

5

今日扱うアセンブリ言語

x86のアセンブリ言語(Intel形式)

なんでx86?

◦ 資料が豊富

◦ ツールが充実

◦ 比較的どこにでもある

◦ SSEを使いたい

6

lヽ ノ l l l l ヽ ヽ)'ーーノ( | | | 、 / l| l ハヽ |ー‐''"l

/ S | | |/| ハ / / ,/ /|ノ /l / l l l| l S ヽl ・ i´ | ヽ、| |r|| | //--‐'" `'メ、_lノ| / ・ /| S l トー-トヽ| |ノ ''"´` rー-/// | S || ・ |/ | l ||、 ''""" j ""''/ | |ヽl ・ || E | | l | ヽ, ― / | | l E || !! | / | | | ` ー-‐ ' ´|| ,ノ| | | !! |

ノー‐---、,| / │l、l |レ' ,ノノ ノハ、_ノヽ

SSEとは

x86 CPUの拡張命令

◦ SIMD(Single Instruction Multiple Data)

まだ人間がコンパイラに勝てる分野

◦ 時間の問題かもしれないけれど

SSEはC言語からも使えるが・・・

◦ 選択肢の一つとしてアセンブリ言語を

7

前提知識

C言語の知識

◦ ポインタとメモリアドレスの関係

◦ 文法の知識はそんなにいらない

8

本日の目標

SSEを独習できるようになる

9

ことば

アセンブリ言語

◦ プログラミング言語の一種

アセンブル

◦ アセンブリ言語を機械語に翻訳する作業

アセンブラ

◦ アセンブルするプログラム

「アセンブリ言語」という意味でアセンブラと言うこともよくある

10

ことば

____

/ \ /\ キリッ. / (ー) (ー)\

/ ⌒(__人__)⌒ \ < アセンブラとは| |r┬-| | アセンブリ言語を\ `ー'´ / アセンブルする

ノ \ プログラムである/´ ヽ

| l \ヽ -一''''''"~~``'ー--、 -一'''''''ー-、.

ヽ ____(⌒)(⌒)⌒) ) (⌒_(⌒)⌒)⌒))11

X86 アセンブリ言語(32BIT)

12

プログラミングに必要な要素

変数

◦ レジスタ

◦ メモリ

Cの演算子っぽいもの

◦ 命令

制御構造(ifとかループとか

◦ あとで

13

レジスタ

CPUの中にある小さく速いメモリ

◦ スレッドごとに割り当てられる

◦ 個数が限られている

CPU メモリ ディスク

大容量・低速

高速・小容量

L1キャッシュ

L2キャッシュレジスタ

14

x86 の汎用レジスタ

8個の32bitレジスタ

それぞれ役割はあるが、絶対ではない

◦ ただし esp は除く(ebpも、かもしれない)

レジスタ 名前 役割

eax Accumulator Register 演算

ebx Base Register 32bit環境では自由

ecx Counter Register カウンタ

edx Data Register eaxの補助

esi Source Index データの読み込み元

edi Destination Index データの書き込み先

ebp Base Pointer フレームポインタ的ななにか

esp Stack Pointer スタックのトップを指す15

32bit,16bit,8bitレジスタ

eax

ax

alah

32bit

16bit

8biteax, ebx, ecx, edx

esi,edi,ebp,esp

si,di,bp,sp

32bit

16bit

上位8bit 下位8bit

16

sil,dil,... 8bit

AMD64にはあるっぽい

メモリ

レジスタが足りないときはメモリを

[base + index * scale + disp]

base: 汎用レジスタindex: esp以外の汎用レジスタscale: 1, 2, 4, 8disp: 定数

例: [ebp + ecx * 4 + 8]

17

メモリの使い方

int a[];a[n];

aのアドレス: eaxn: ecxa[n]: [eax + ecx * 4]

struct {char x;int y;

} s;

sのアドレス: eaxx: [eax]y: [eax + 4]

型情報がないのでバイト数を明示的に指定する必要がある

int *p;char *q;

p: eax*p: dword ptr [eax]

q: esi*q: byte ptr [esi]

配列aのn番目にアクセスしたい

*p, *qとしたい

18パディングに注意

命令

シンプルな命令セットで構成される

◦ かなりの数の命令がある(100以上)

一つ一つの命令ができることは少ない

命令 dst, src dst op= src

a = b + c - d;a = b;a += c;a -= d;

たとえば多くの算術命令では

オペランド

ニーモニックmnemonic

2項演算

細かく分解

19複雑な命令は・・・

オペランドには何が使える?

レジスタ

メモリ

即値

◦ 生の値(10, 255 etc

◦ srcのみ

命令 dst, src

オペランド

20

オペランドの制限

dst,srcに指定可能な組み合わせ

◦ 命令によって異なる

dst src

レジスタ レジスタ

レジスタ 即値

レジスタ メモリ

メモリ レジスタ

メモリ 即値

メモリ-メモリは無い

mov mem2, mem1mov reg, mem1mov mem2, reg

21

オペランドの制限2

dst,srcは同じサイズでないとダメ

mov eax, bx

32bit 16bit

mov eax, ebx

22

mov [メモリ], ebx

もう片方のオペランドからサイズを推定してくれる

代入・算術命令

2項演算

mov x, yadd x, ysub x, yand x, yor x, yxor x, y

x = y;x += y;x -= y;x &= y;x |= y;x ^= y;

かけ算、わり算、シフトは後ほど!

a = b;a += c;a -= d;

mov a, badd a, csub a, d

a = b + c - d;

23

コード例

mov eax, 10mov ebx, 20add eax, ebxmov dword ptr [esi + ecx * 4], eax

24

その他の算術命令

単項演算

inc xdec xneg xnot x

x = x + 1;x = x - 1;x = -x;x = ~x;

25

/y

掛け算・割り算

div y

mul x edx eaxeax * x

上位32bit

下位32bit

64bit

edx eax

=

eax

edx

商が32bitに収まらないと例外が発生

余り

26

符号

2の補数表現

◦ 符号を意識しなくても演算できる

◦ 意識しないとダメなケースもある

255 + 254 = 256 + 253

-1 + -2 = -3

11111111 + 11111110= 1 11111101

8bitの計算

内部的にはどちらも同じことをしている

2進数で表すと

27

符号付き命令

idiv, imul

◦ 割り算は符号のありなしで結果が変わる -1/2を符号無しで計算すると0xffffffff/2になる

◦ imulはmulのエイリアス

シフト命令◦ 論理シフト(符号無し)

shr(Javaでいう>>>), shl(<<)

◦ 算術シフト(符号付き) sar(>>), sal(<<)

◦ ローテートもある

28

その他の命令

算術命令以外の要素

◦ 比較、論理演算、条件分岐

◦ スタック操作

◦ 関数呼び出し

あとで解説します!

29

命令仕様の確認・調査方法

IA-32 アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル中巻(上下)

アセンブリリストを出力◦ cl /Fa

◦ gcc -S

逆アセンブル

30

制御構造は?

if, for, whileどこいってもうたんや

そんな軟弱なものはない!!

◦ あるのは(条件付き)gotoのみ

31

ここまででわかったもの

レジスタ

◦ 8個の32bit汎用レジスタ

メモリの使い方(アドレッシング

基本的な算術命令

◦ 指定できるオペランドの制限

32

これからわかるもの

ちゃんとしたアセンブリ言語の書き方

条件分岐

ループ

関数の呼び出し方

インラインアセンブラを使って覚えていきます。

33

C/C++でアセンブリ言語を使う

Visual C++で頑張るアセンブラプログラミング

34

インラインアセンブラ

C/C++の中でアセンブラを使える

VC++(32bit)でやってみよう

◦ AMD64モードでは使えない・・・?

int f() {C言語;__asm {

ここだけアセンブリ言語!!}C言語;

}

35

まずは足し算から

#include <stdio.h>int main() {

int a = 10, b = 20, c;c = a + b;printf("%d¥n", c);

}

#include <stdio.h>int main() {

int a = 10, b = 20, c;__asm {

ここで足してみる}printf("%d¥n", c);

}

36

足し算

#include <stdio.h>int main() {

int a = 10, b = 20, c;c = a + b;printf("%d¥n", c);

}

#include <stdio.h>int main() {

int a = 10, b = 20, c;__asm {

add a, bmov c, a

}printf("%d¥n", c);

}

できた!?

37

オペランドの制約

__asm {add a, bmov c, a

}

両方のオペランドにメモリを指定することはできない

a,b,cは関数mainのローカル変数つまりスタック(メモリ)上にある

__asm {mov eax, aadd a, bmov c, a

}

__asm {mov eax, aadd eax, bmov c, eax

}

int main() {int a = 10, b = 20, c;

aをeaxに置き換え一度レジスタへ

38

足し算: 完成版

#include <stdio.h>int main() {

int a = 10, b = 20, c;c = a + b;printf("%d¥n", c);

}

#include <stdio.h>int main() {

int a = 10, b = 20, c;__asm {

mov eax, aadd eax, bmov c, eax

}printf("%d¥n", c);

}

39

addを関数にしてみる

#include <stdio.h>int add(int a, int b) {

return a + b;}int main() {

printf("%d¥n",add(10, 20));

}

#include <stdio.h>int add(int a, int b) {

__asm {ここに書く

}}int main() {

printf("%d¥n",add(10, 20));

}

呼ばれる側をインラインアセンブラで実装

40

素直に実装: add

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

41

返値はどうやって返す?

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, bmov a, eax

}return a;

}int main() {

printf("%d¥n",add(10, 20));

}

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

このままでOKでした

return もいらない一度aに入れ直してあげればOK?

実は…

42

返値の返し方

呼び出し規約

◦ eax で返値を返す決まりになってる

◦ あとでまた詳しく

32bit以上のものはどうやって返す?,.-─ ─-、─-、

, イ)ィ -─ ──- 、ミヽノ /,.-‐'"´ `ヾj ii / Λ

,イ// ^ヽj(二フ'"´ ̄`ヾ、ノイ{ノ/,/ミ三ニヲ´ ゙、ノi!{V /ミ三二,イ , -─ Yソレ'/三二彡イ .:ィこラ ;:こラ j{V;;;::. ;ヲヾ!V ー '′ i ー ' ソVニミ( 入 、 r j ,′ヾミ、`ゝ ` ー--‐'ゞニ<‐-イ

ヽ ヽ -''ニニ‐ /| `、 ⌒ ,/| > ---- r‐'´ヽ_ |

ヽ _ _ 」

ググレカス [ gugurecus ]

(西暦一世紀前半~没年丌明)43

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

分岐: abs

#include <stdio.h>int abs(int x) {

if (x > 0) return x;else return -x;// return x > 0 ? x : -x;

}int main() {

printf("%d¥n", abs(-7));}

#include <stdio.h>int abs(int x) {

__asm {ここに書く

}}int main() {

printf("%d¥n", abs(-7));}

44

x86アセンブリ言語での分岐

if (条件 == true) goto end;

条件がfalseのときに実行したい処理

end:

「条件がtrueだったらgoto」という処理しか実行できない

45

if の書き方

if (x < y) {hogehoge

}

if (!(x < y))goto end;

hogehogeend:

if (x >= y)goto end;

hogehogeend:

mov eax, xcmp eax, yjge endhogehoge

end:

ifの中を実行したいので条件を反転させる

!を取る

アセンブリ言語化

!?

46

cmp & jmp

条件分岐=比較&ジャンプ

cmp

jge

◦ 条件付きジャンプ命令(ブランチ命令)

47

mov eax, xcmp eax, yjge endhogehoge

end:

cmpは何をするのか

値の比較を行う

実は引き算をしている◦ dstの値を変更しない引き算

演算結果に関する情報をフラグレジスタにセット

48

x == yx > yx < y

x - y == 0x - y > 0x - y < 0

減算結果を0と比較すると大小関係が分かる

フラグレジスタ(eflags)

32bitのレジスタ

各bitが状態を表す IA-32 インテルアーキテクチャソフトウェア・デベロッパーズ・マニュアル上巻より

よく使うのは CF, ZF, SF, OF の4つ

49

CF, ZF, SF, OF

フラグ

名前 意味 例(8bit)

CF キャリーフラグ

計算結果がレジスタのサイズに収まらなかった

3-255=2

ZF ゼロフラグ 計算結果が0になった 4-4=0

SF サインフラグ 計算結果が符号付きになった(最上位bitが1になった)

5-7=-2

OF オーバーフローフラグ

符号付き演算の結果がオーバーフローした

127+1=-128

-120-9=127

cmpにより、これらのフラグが変化する

50

条件付きジャンプ

フラグレジスタの値に応じてジャンプ

◦ jcc命令

命令 条件

jc CF=1

jnc CF=0

jz ZF=1

jnz ZF=0

js SF=1

jns SF=0

jo OF=1

jno OF=0

51

ジャンプ命令のエイリアス

エイリアスがたくさんある

詳しくはマニュアルを:jcc

わしのエイリアスは108式まであるぞ

比較演算子 対応する命令 フラグ条件

x=y je, jz ZF=1

x!=y jne,jnz ZF=0

x<y jl, jnge (SF XOR OF)=0

x<=y jle,jng ((SF XOR OF) OR ZF)=1

x>y jg, jnle ((SF XOR OF) OR ZF)=0

x>=y jge, jnl (SF XOR OF)=1

cmp x, y としたときのジャンプ表(符号付き比較の場合)

52

符号無し条件分岐

比較演算子 対応する命令 フラグ条件

x=y je, jz ZF=1

x!=y jne,jnz ZF=0

x<y jb,jnae CF=1

x<=y jbe,jna (CF OR ZF)=1

x>y ja, jnbe (CF OR ZF)=0

x>=y jae, jnb CF=0

cmp x, y としたときのジャンプ表(符号無し比較の場合)

53

条件分岐: abs

完成させてみる

if (x > 0) return x;else return -x;

if (x < 0) x = -x;return x;

if (x >= 0) goto L1;x = -x;

L1:return x;

mov eax, xcmp eax, 0jge L1neg eax

L1:

54

elseを消す

gotoに直す

条件分岐: abs

#include <stdio.h>int abs(int x) {

if (x > 0) return x;else return -x;// return x > 0 ? x : -x;

}int main() {

printf("%d¥n", abs(-7));}

#include <stdio.h>int abs(int x) {

__asm {mov eax, xcmp eax, 0jge L1neg eax

L1:}

}int main() {

printf("%d¥n", abs(-7));}

55

returnは丌要

ループとメモリ参照: strlen

#include <stdio.h>int strlen(const char* s) {

int i = 0;while (s[i]) i++;return i;

}int main() {

printf("%d¥n",strlen("abcdef"));

}

#include <stdio.h>int strlen(const char* s) {

__asm {ここに書く

}}int main() {

printf("%d¥n",strlen("abfdef"));

}

56

ループ

基本は if と goto

int i = 0;while (s[i]) i++;

int i = 0;L1:

if (s[i] == 0) goto L2;i++;goto L1;

L2:

int i = 0;L1:

cmp s[i], 0je L2inc ijmp L1

L2:

あとはメモリ参照をどうするか

57

ifとgotoに変換

部分的にアセンブリ言語に

メモリの使い方[base + index * n + disp]

n: 1, 2, 4, 8disp: 即値

メモリからはオペランドのサイズがわからないので、念のため明示的に指定する (記法はアセンブラ依存

int i;const char* s;cmp s[i], 0

ecx = 0 ; iの代わりedx = s ; sの代わりcmp edx[ecx], 0

xor ecx, ecxmov edx, scmp [edx + ecx], 0

xor ecx, ecxmov edx, scmp byte ptr [edx + ecx], 0

雰囲気としては・・・

58

メモリの使い方

xor ecx, ecxmov edx, s

L1:cmp byte ptr [edx + ecx], 0je L2inc ecxjmp L1

L2:mov eax, ecx

int i = 0;L1:

cmp s[i], 0je L2inc ijmp L1

L2:

59

ecxをeaxにすることで最後のmovをなくすこともできる

今書いたものに置き換え

ループとメモリ参照: strlen

60

#include <stdio.h>int strlen(const char* s) {

int i = 0;while (s[i]) i++;return i;

}int main() {

printf("%d¥n",strlen("abcdef"));

}

#include <stdio.h>int strlen(const char* s) {

__asm {xor eax, eaxmov edx, s

L1:cmp byte ptr[edx+eax],0je L2inc eaxjmp L1

L2:}

}int main() {

printf("%d¥n",strlen("abfdef"));

}

フラグレジスタの補足

変化する条件は?

◦ なにか演算を行う

add や and などでも変化する

mov ecx, nLOOP:

ループの処理

dec ecxjnz LOOP

N回ループのイディオム

test eax, eaxjnz NONZERO

0だったときの処理

NONZERO:

0チェックのイディオム

cmpの場合 cmp eax, 0 とするが、即値(32bit)分命令長が長くなる。test eax, eax なら2バイトで済む。

dec ecxでZFが立つとループ終了

61

testはcmpの&演算版

関数呼び出し自分で作った関数を__asmの中から呼んでみる

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

int r; // 返値用__asm {

ここでaddを呼び出すmov r, eax

}printf("%d¥n", r);

}

62

関数呼び出し:必要な処理

引数を渡す

関数を実行する

関数から戻ってくる

後始末

全体で統一する必要がある

◦ 呼び出し規約

63

呼び出し規約

呼び出し規約で定義されるもの◦ 引数の渡し方 スタックで渡す?レジスタで渡す?

◦ 返値の扱い方 eaxで返す?他の手段で返す?

◦ レジスタの使い方 レジスタの値は自由に変えちゃってOK?

いろいろ種類がある◦ 今日扱うのは cdecl

64

cdecl

x86な環境ではよく使われている

仕様

◦ 引数はスタック経由で渡す

◦ 返値はeaxで返す(float,doubleの場合はst(0))

◦ eax,ecx,edxは自由に使える

それ以外は保存しなければならない

FPUに関しては今日は扱わない

65

引数:スタックメモリ

関数用に確保されているメモリ領域

ローカル変数もここに確保される

スタック操作は push/pop 命令で行う

66

push/pop

スタック操作用の命令

push eax

・・・

eaxの値

pop eax

・・・

x

成長方向

67

x

スタックとメモリアドレス

・・・

0x00000000

メモリアドレスの小さい方向に向かって伸びる

0xffffffff

esp

push eaxsub esp, 4mov [esp], eax

等価

pushed

メモリアドレス

pop eaxmov eax, [esp]add esp, 4

等価

espはスタックのトップを指す

68

引数の渡し方再び

引数をpushする順序も決まっている

後ろの引数からスタックに積む

関数呼び出しは call 命令で

int x, y;add(x, y);

push ypush xここでaddを呼び出す

69

関数呼び出し: call命令

...call add...

int add(int x, int y) {__asm {

mov eax, xadd eax, y

}}

関数の先頭アドレスまでジャンプ!

callの次の命令のところまでジャンプして戻る

70

どうやって戻ってくる?

call命令の次の命令のアドレスが分かればOK

EIP レジスタから取得する

EIP レジスタ

特殊なレジスタ

◦ 次に実行する命令のアドレスを持つ

プログラムカウンタ(pc)

インストラクションポインタ(ip)

op1 hoge, hogecall addop2 hoge, hoge

call を実行する段階では、eipはop2を指している

71

eip

call/ret

・・・

引数2

引数1

・・・

EIP

call Function次の命令

Function()......ret

EIPをpopして、ジャンプ

pop return_addrjmp return_addr

push eipjmp Function

72

ret 命令

自分で書いて良い?

◦ インラインアセンブラではダメ!

後処理を自分で正しく書けるならOK

73

int add(int x, int y) {__asm {

mov eax, xadd eax, yret

}コンパイラによって生成される後処理用コードret

}

自分でretを呼ぶと後処理が実行されない

スタックの掃除

・・・

引数3

引数2

・・・

引数1

esp

retで呼び出し元へ戻ってきたが、スタックにはゴミ(引数)が残っている

push 引数3push 引数2push 引数1call Function; 帰ってきた

pop regpop regpop reg

add esp, 引数のバイトサイズ

cdeclでは呼び出し元(caller)が後始末をすることになっている。

Win32 APIの呼び出し規約、stdcallでは呼び出され側(callee)が後始末をする。74

3個分pop

関数呼び出し: add

75

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

int r; // 返値用__asm {

push 20push 10call addadd esp, 8mov r, eax

}printf("%d¥n", r);

}

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

補足:関数呼び出しとスタック

76

function:push ebpmov ebp, esp

関数の処理

pop ebpret

・・・

引数2

引数1

・・・

EIP

古いebp ebp

esp

関数はこのように書く習慣がある

補足:ローカル変数

77

・・・

引数2

引数1

・・・

EIP

古いebp

ローカル変数

ローカル変数

ローカル変数function:push ebpmov ebp, espsub esp, 12

関数の処理

add esp, 12pop ebpret

ebp

esp

[ebp - 4]

[ebp - 8]

[ebp - 12]

ローカル変数も自給自足

補足:引数へのアクセス

78

int add(int a, int b) {__asm {

mov eax, aadd eax, b

}}

int add(int a, int b) {__asm {

mov eax, [ebp + 8]add eax, [ebp + 12]

}}

VC++の場合は、インラインアセンブラが自動で置き換えてくれる

・・・

引数2

引数1

・・・

EIP

古いebp

ローカル変数

ローカル変数

ローカル変数

ebp

esp

[ebp - 4]

[ebp - 8]

[ebp - 12]

[ebp + 8]

[ebp + 12]

ここまでのまとめ

x86アセンブリ言語の基礎

インラインアセンブラを使った

◦ 単純な演算

◦ 関数記述

◦ 条件分岐

◦ ループ

◦ メモリアクセス

◦ 関数呼び出し

79

SSE

80

SSE: Streaming SIMD Extensions

SIMD

◦ Single Instruction Multiple Data

バージョン

◦ SSE, SSE2, SSE3, etc

Pentium4 ならSSE2までOK

81

S3 S2 S1 S0

D3 D2 D1 D0

S3+D3 S2+D2 S1+D1 S0+D0

+

画像処理などで大活躍

SSEを使用可能かチェック

CPUID命令

◦ CPUの情報を取得するための命令

◦ 特定のバージョンのSSEが使えるかどうか

◦ 本日は省略

今日はSSE2まで

◦ たぶんみんな使える

たぶん 使えなくても落ちるだけなのでだいじょうぶ

82

SSEのレジスタ

mm0~mm7の8個

◦ 64bitレジスタ

◦ MMX

◦ 整数演算

xmm0~xmm7の8個

◦ 128bitレジスタ

◦ 整数演算&浮動小数点数演算

◦ AMD64だとさらに8本追加されている

83

mm0

mm1

xmm0

mm2

mm3

mm4

mm5

mm6

mm7

xmm1

xmm2

xmm3

xmm4

xmm5

xmm6

xmm7

mmレジスタ

84

byte byte byte byte byte byte byte byte

word word word word

dword dword

packed byte

packed word

packed double word

64bit

x87の浮動小数点数演算と同時に使用することはできない

SIMD前提のレジスタ

xmmレジスタ

float, double(SSE2)を扱える

mmレジスタ2個分の働き(SSE2)

85

float float float float

float

double double

double

128bit

packed

single precision

scalar

single precision

scalar

double precision

packed

double precision

x87の浮動小数点数演算と同時に使用できる(しないけど

時間がないのでサンプルで

エセαブレンドを実装する

86

void blend(float *dst, const float *src, float a, int n) {int i;for (i = 0; i < n; i++)

dst[i] = (1 - a) * dst[i] + a * src[i];}

void blend(float *dst, const float *src, float a, int n) {int i;for (i = 0; i < n; i++)

dst[i] = dst[i] + a * (src[i] - dst[i]);}

乗算を減らしておく

その前に・・・

// 4個ずつまとめて計算したい

xmmに値をロード

87

// edi=dst, esi=srcmovaps xmm1, [edi]movaps xmm2, [esi]

dst,srcをロード

movss xmm0, a

aをロード

dst[i+3] dst[i+2] dst[i+1] dst[i+0]

a

src[i+3] src[i+2] src[i+1] src[i+0]

xmm0

xmm1

xmm2

転送命令:float, double用

88

float *p;mov eax, pmovss xmm0, [eax]

*p xmm0

float p[];mov eax, pmovaps xmm0, [eax]

p[3] p[2] p[1] p[0] xmm0

mov(a|u)??ss: floatsd: doubleps: float * 4pd: double * 2

movap?: 16バイトアラインメントを前提

movup?: アラインメントされてなくても大丈夫

計算部分

89

dst[i] = dst[i] + a * (src[i] - dst[i]);

xmm0 = axmm1 = dst[i];xmm2 = src[i];

xmm2 -= xmm1;xmm2 *= xmm0;xmm1 += xmm2;

dst[i] = xmm1;

float s = src[i];s -= dst[i];s *= a;dst[i] += s;

mov edi, dstmov esi, src

movss xmm0, amovaps xmm1, [edi]movaps xmm2, [esi]

subps xmm2, xmm1mulps xmm2, xmm0addps xmm1, xmm2

dst[i] = xmm1;

subps

90

D3 D2 D1 D0

S3 S2 S1 S0

D3-S3 D2-S2 D1-S1 D0-S0

ーxmm1

xmm2

subps xmm2, xmm1

xmm2

末尾のpsをpdにするとdouble用の命令になる

mulps・・・の前に

91

axmm0

今のαは・・・

a a a axmm0

こうしないと4個同時に乗算できない

movss xmm0, a

シャッフル

92

shufps xmm0, xmm0, 0

a a a axmm0

dd cc bb aashufps dst, src, imm8 imm8

レジスタ番号(2bit)

src用 dst用

D3 D2 D1 D0dst

S3 S2 S1 S0src

S2 S2 D1 D3dst

10 10 01 11imm8

7 0 (bit)

計算結果をメモリへ転送

93

mov edi, dstmov esi, src

movss xmm0, ashufps xmm0, xmm0, 0movaps xmm1, [edi]movaps xmm2, [esi]

subps xmm2, xmm1mulps xmm2, xmm0addps xmm1, xmm2

dst[i] = xmm1;

mov edi, dstmov esi, src

movss xmm0, ashufps xmm0, xmm0, 0movaps xmm1, [edi]movaps xmm2, [esi]

subps xmm2, xmm1mulps xmm2, xmm0addps xmm1, xmm2

movntps [edi], xmm1

キャッシュを意識した転送

94

S3 S2 S1 S0xmm1

αブレンド後の結果を4個まとめて転送

D3 D2 D1 D0dst

dstへ書き込んだら、同じ場所へはもうアクセスしない

キャッシュする意味がない(もったいない

movnt??命令はキャッシュを有効活用するためのヒントを不える

ループをつけて完成

95

void blend(float *dst, const float *src, float a, int n) {__asm {

movss xmm0, amov edi, dstmov esi, srcmov eax, nshufps xmm0, xmm0, 0 // [a, a, a, a]

L1:movaps xmm1, [edi]movaps xmm2, [esi]subps xmm2, xmm1 // src - dstmulps xmm2, xmm0 // * aaddps xmm1, xmm2 // + dstmovntps [edi], xmm1 // dstへ結果をコピーadd esi, 16 // float 4個分ポインタを進めるadd edi, 16sub eax, 4 // n -= 4jnz L1 // if (n == 0) break;

}

プログラムを簡単にするために、nが4の倍数であることを仮定してます

16-byte alignedであることを仮定しています

気になるパフォーマンスは

環境◦ OS: Ubuntu9.10

◦ CPU Intel Core2 Quad 3.0GHz(AMD64)

◦ メモリ 8GB

コンパイラとコンパイルオプション◦ g++ 4.4.1, オプション -O3 -msse2

2^28要素のfloat配列を使ってαブレンド 結果

◦ Cで書いたもの: 0.58sec

◦ SSEバージョン: 0.50sec

◦ 約1.15倍速 ちょっと残念な結果に・・・

96

その他のSSE命令

多すぎて全部紹介できません

整数の飽和演算

パックド論理演算(4個同時にandとか)

平方根や絶対値の同時計算

マスク生成用比較命令◦ 条件を満たした要素が0xff..ffになる

使いどころが分からない命令も・・・◦ movmskpsってなんに使うんですか?

97

SSEまとめ

使えるレジスタ・命令が増えただけ

◦ プログラミングの基本は一緒

基本さえ押さえてしまえば調べながら自力でプログラムを書ける

98

まとめ

99

今日やったこと

x86アセンブリ言語の基礎

x86アセンブリ言語の書き方

◦ 演算、条件分岐、関数呼び出し

SSEの概要とちょっとしたサンプル

100

おまけ

101

Xbyak(カイビャック)

x86, x64用JITアセンブラ for C++

◦ Windows, Mac, Linuxで使えます

特徴

◦ 関数単位で記述

◦ 実行時に定数を埋め込むこんだりできる

◦ 動的コード生成

条件に合わせて最適化可能

インラインアセンブラと比較して

◦ どの環境でも同じ記法が使えるので便利

102

Xbyak:続きはウェブで!

103

http://homepage1.nifty.com/herumi/soft/xbyak.html

MASM32

Windows Driver Kit(旧DDK)に入っているmasmに皮をかぶせたもの

◦ masm32.com

フルアセンブリで記述可

サンプルコードもいっぱい

Win32APIも簡単に呼べる

マクロで楽々プログラミング

ぐぐってみてね

104

ご静聴ありがとうございました

急ぎ足になってしまってすみません

少しでもアセンブリ言語を学ぶハードルが低くなればうれしいです!!

105