いまどきじゃないアセンブラプログラミング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
Top Related