PFN福田圭祐による東大大学院「融合情報学特別講義Ⅲ」(2022年10月19日)の講義資料です。
・Introduction to Preferred Networks
・Our developments to date
・Our research & platform
・Simulation ✕ AI
23. 代入・算術命令
かけ算、わり算、
2項演算 シフトは後ほど!
mov x, y x = y;
add x, y x += y;
sub x, y x -= y;
and x, y x &= y;
or x, y x |= y;
xor x, y x ^= y;
a = b; mov a, b
a = b + c - d; a += c; add a, c
a -= d; sub a, d
23
36. まずは足し算から
#include <stdio.h>
#include <stdio.h> int main() {
int main() { int a = 10, b = 20, c;
int a = 10, b = 20, c; __asm {
c = a + b; ここで足してみる
printf("%d¥n", c); }
} printf("%d¥n", c);
}
36
37. 足し算
#include <stdio.h>
#include <stdio.h> int main() {
int main() { int a = 10, b = 20, c;
int a = 10, b = 20, c; __asm {
c = a + b; add a, b
printf("%d¥n", c); mov c, a
} }
printf("%d¥n", c);
}
できた!?
37
38. オペランドの制約
両方のオペランドにメモリを指定することはできない
__asm { __asm {
__asm {
mov eax, a mov eax, a
add a, b
add a, b add eax, b
mov c, a
mov c, a mov c, eax
}
} }
一度レジスタへ aをeaxに置き換え
int main() {
int a = 10, b = 20, c;
a,b,cは関数mainのローカル変数
つまりスタック(メモリ)上にある
38
39. 足し算: 完成版
#include <stdio.h>
int main() {
#include <stdio.h> int a = 10, b = 20, c;
int main() { __asm {
int a = 10, b = 20, c; mov eax, a
c = a + b; add eax, b
printf("%d¥n", c); mov c, eax
} }
printf("%d¥n", c);
}
39
40. addを関数にしてみる
#include <stdio.h>
#include <stdio.h> int add(int a, int b) {
int add(int a, int b) { __asm {
return a + b; ここに書く
} }
int main() { }
printf("%d¥n", int main() {
add(10, 20)); printf("%d¥n",
} add(10, 20));
}
呼ばれる側をインラインアセンブラで実装
40
41. 素直に実装: add
#include <stdio.h>
int add(int a, int b) {
__asm {
mov eax, a
add eax, b
}
}
int main() {
printf("%d¥n",
add(10, 20));
}
41
42. 返値はどうやって返す?
このままでOKでした
#include <stdio.h>
int add(int a, int b) { #include <stdio.h>
__asm { int add(int a, int b) {
mov eax, a __asm {
add eax, b mov eax, a
mov a, eax 実は… add eax, b
} }
return a; }
} int main() {
int main() { printf("%d¥n",
printf("%d¥n", add(10, 20));
add(10, 20)); }
}
return もいらない
一度aに入れ直してあげればOK?
42
43. 返値の返し方
呼び出し規約
◦ eax で返値を返す決まりになってる
◦ あとでまた詳しく
32bit以上のものはどうやって返す?
#include <stdio.h> ,.-─ ─-、─-、
int add(int a, int b) { , イ)ィ -─ ──- 、ミヽ
ノ /,.-‐'"´ `ヾj ii / Λ
__asm { ,イ// ^ヽj(二フ'"´ ̄`ヾ、ノイ{
ノ/,/ミ三ニヲ´ ゙、ノi!
mov eax, a {V /ミ三二,イ , -─ Yソ
add eax, b レ'/三二彡イ .:ィこラ ;:こラ j{
V;;;::. ;ヲヾ!V ー '′ i ー ' ソ
} Vニミ( 入 、
ヾミ、`ゝ ` ー--‐'ゞニ<‐-イ
r j ,′
} ヽ ヽ -''ニニ‐ /
| `、 ⌒ ,/
int main() { | > ---- r‐'´
ヽ_ |
printf("%d¥n", ヽ__」
add(10, 20)); ググレカス [ gugurecus ]
} (西暦一世紀前半~没年丌明)
43
44. 分岐: abs
#include <stdio.h> #include <stdio.h>
int abs(int x) { int abs(int x) {
if (x > 0) return x; __asm {
else return -x; ここに書く
// return x > 0 ? x : -x; }
} }
int main() { int main() {
printf("%d¥n", abs(-7)); printf("%d¥n", abs(-7));
} }
44
46. if の書き方
ifの中を実行したいので
条件を反転させる
if (x < y) { if (!(x < y))
hogehoge goto end;
} hogehoge
end:
if (x >= y) mov eax, x
goto end; cmp eax, y
!?
hogehoge jge end
end: アセンブリ言語化 hogehoge
end:
!を取る
46
47. cmp & jmp
条件分岐=比較&ジャンプ mov eax, x
cmp eax, y
jge end
hogehoge
cmp end:
jge
◦ 条件付きジャンプ命令(ブランチ命令)
47
48. cmpは何をするのか
値の比較を行う
実は引き算をしている
◦ dstの値を変更しない引き算
x == y x - y == 0
減算結果を0と比較すると
x > y x - y > 0
大小関係が分かる
x < y x - y < 0
演算結果に関する情報を
フラグレジスタにセット
48
53. 符号無し条件分岐
cmp x, y としたときのジャンプ表(符号無し比較の場合)
比較演算子 対応する命令 フラグ条件
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
53
54. 条件分岐: abs
完成させてみる
elseを消す
if (x > 0) return x; if (x < 0) x = -x;
else return -x; return x;
if (x >= 0) goto L1; mov eax, x
x = -x; cmp eax, 0
L1: jge L1
return x; neg eax
L1:
gotoに直す
54
55. 条件分岐: abs
#include <stdio.h>
int abs(int x) {
#include <stdio.h> __asm {
int abs(int x) { mov eax, x
if (x > 0) return x; cmp eax, 0
else return -x; jge L1
// return x > 0 ? x : -x; neg eax
} L1:
int main() { }
printf("%d¥n", abs(-7)); }
} int main() {
printf("%d¥n", abs(-7));
}
returnは丌要
55
56. ループとメモリ参照: strlen
#include <stdio.h> #include <stdio.h>
int strlen(const char* s) { int strlen(const char* s) {
int i = 0; __asm {
while (s[i]) i++; ここに書く
return i; }
} }
int main() { int main() {
printf("%d¥n", printf("%d¥n",
strlen("abcdef")); strlen("abfdef"));
} }
56
57. ループ
基本は if と goto
ifとgotoに変換
int i = 0;
L1:
int i = 0;
if (s[i] == 0) goto L2;
while (s[i]) i++;
i++;
goto L1;
L2:
int i = 0;
L1:
cmp s[i], 0
je L2
inc i
jmp L1 あとはメモリ参照をどうするか
L2:
部分的にアセンブリ言語に 57
59. メモリの使い方
今書いたものに置き換え
xor ecx, ecx
int i = 0; mov edx, s
L1: L1:
cmp s[i], 0 cmp byte ptr [edx + ecx], 0
je L2 je L2
inc i inc ecx
jmp L1 jmp L1
L2: L2:
mov eax, ecx
ecxをeaxにすることで最後の
movをなくすこともできる
59
60. ループとメモリ参照: strlen
#include <stdio.h>
int strlen(const char* s) {
__asm {
xor eax, eax
mov edx, s
#include <stdio.h> L1:
int strlen(const char* s) { cmp byte ptr[edx+eax],0
int i = 0; je L2
while (s[i]) i++; inc eax
return i; jmp L1
} L2:
int main() { }
printf("%d¥n", }
strlen("abcdef")); int main() {
} printf("%d¥n",
strlen("abfdef"));
}
60
61. フラグレジスタの補足
変化する条件は?
◦ なにか演算を行う
add や and などでも変化する
N回ループのイディオム 0チェックのイディオム
mov ecx, n test eax, eax
testは
LOOP: jnz NONZERO
cmpの
&演算版
ループの処理 0だったときの処理
dec ecx NONZERO:
jnz LOOP
dec ecxでZFが立つと cmpの場合 cmp eax, 0 とするが、
ループ終了 即値(32bit)分命令長が長くなる。
test eax, eax なら2バイトで済む。 61
62. 関数呼び出し
自分で作った関数を
__asmの中から呼んでみる
#include <stdio.h>
int add(int a, int b) {
#include <stdio.h>
__asm {
int add(int a, int b) {
mov eax, a
__asm {
add eax, b
mov eax, a
}
add eax, b
}
}
int main() {
}
int r; // 返値用
int main() {
__asm {
printf("%d¥n",
ここでaddを呼び出す
add(10, 20));
mov r, eax
}
}
printf("%d¥n", r);
}
62
72. call/ret
call Function
次の命令
push eip EIP
jmp Function 引数1
引数2
・・・
Function() ・・・
...
... EIPをpopして、ジャンプ
ret
pop return_addr
jmp return_addr
72
73. ret 命令
自分で書いて良い?
◦ インラインアセンブラではダメ!
後処理を自分で正しく書けるならOK
int add(int x, int y) {
__asm {
mov eax, x
add eax, y
自分でretを呼ぶと
ret 後処理が実行されない
}
コンパイラによって生成される
後処理用コード
ret
}
73
74. スタックの掃除
retで呼び出し元へ戻ってきたが、
スタックにはゴミ(引数)が残っている
push 引数3 引数1
push 引数2
push 引数1
esp 引数2
call Function 引数3
; 帰ってきた ・・・
・・・
pop reg cdeclでは呼び出し元(caller)が
pop reg 3個分pop 後始末をすることになっている。
pop reg
add esp, 引数のバイトサイズ
Win32 APIの呼び出し規約、stdcallでは
呼び出され側(callee)が後始末をする。 74
75. 関数呼び出し: add
#include <stdio.h>
int add(int a, int b) {
__asm {
mov eax, a
#include <stdio.h> add eax, b
int add(int a, int b) { }
__asm { }
mov eax, a int main() {
add eax, b int r; // 返値用
} __asm {
} push 20
int main() { push 10
printf("%d¥n", call add
add(10, 20)); add esp, 8
} mov r, eax
}
printf("%d¥n", r);
}
75
84. mmレジスタ
byte byte byte byte byte byte byte byte packed byte
word word word word packed word
dword dword packed double word
64bit SIMD前提の
レジスタ
x87の浮動小数点数演算と
同時に使用することはできない
84
86. 時間がないのでサンプルで
エセαブレンドを実装する
void blend(float *dst, const float *src, float a, int n) {
int i;
for (i = 0; i < n; i++) // 4個ずつまとめて計算したい
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]);
}
乗算を減らしておく
86