SlideShare a Scribd company logo
1 of 45
Download to read offline
Xbyakの紹介とその周辺
カーネル/VM探検隊@関西 9回目 2018/9/22
光成滋生
• @herumi
• https://github.com/herumi/she-wasm
• ペアリングベースの準同型暗号のWebAssembly向け実装
• IEEE trans. on Computers 2014, AsiaCCS 2018など
• https://github.com/herumi/bls
• BLS署名の実装
• Dfinityというブロックチェーン系ベンチャーが使ってる
自己紹介
2 / 45
• C++用のx86/x64専用JITアセンブラ
• https://github.com/herumi/xbyak
• 自分が使いたい(暗号用に開発を始めた)アセンブラ
• 開発を初めてもうすぐ12年目
• AVX-512フルサポート
• ちなみに現在(2018/9)Intelの一番長い名前の命令は
• vgf2p8affineinvqb(17文字)
• Galois Field Affine Transformation Inverse
• 𝐹28の元𝑥に対して𝐴𝑥−1 + 𝐵を計算する
Xbyakって何?
3 / 45
• NASM, gasなどの通常の静的なアセンブラに比べて
• コードを書きやすい(個人の感想)
• C++との連係がしやすい(個人の...)
• VMを書きやすい(略)
• V8やWebkitなどのJavaScriptエンジンも同等の
JITアセンブラを持ってる(ARMなどにも対応してる)
従来のアセンブラとの比較
4 / 45
• 整数nを返す関数を生成するクラス
• インスタンスを作成し関数ポインタを取り出して実行
実行時に整数nを返す関数を生成
struct Code : Xbyak::CodeGenerator {
Code(int n) {
mov(eax, n); // 注意 インラインアセンブラではない
ret(); // 純粋なC++のコード
}
};
Code c1(3);
auto f = c1.getCode<int (*)()>();// intを返す関数ポインタ
printf("%d¥n", f()); // 3
実行時にコード生成
mov eax, 3
ret
5 / 45
• MASMに似せるための各種演算子オーバーロード
• Intel命令をそのまま書けるので直感的に操作しやすい
• 小さなブロックを組み合わせて作る感覚が楽しい
雰囲気
void gen_add(const RegExp& dst, const RegExp& src);
void f(int n) {
auto addr = n > 0 ? rsi + rax * 8 + n * 8 : rdi;
mov(rax, ptr[addr]);
vgatherqpd(zmm5 | k7, ptr [rax + 64 + zmm21 * 2]);
gen_add(rsp + 8, rsi + 8);
}
6 / 45
• template引数で長さを指定する整数クラス
• 最大の大きさを指定するNはコンパイル時指定
• 実際の整数の大きさを指定するnは実行時指定
• このようなNを外部アセンブラと連係するのは面倒
固定多倍長加算
template<size_t N>
struct Int {
void init(size_t n); // n * 64 bit整数として利用(n < N)
uint64_t d[N];
// z = x + y
void (*add)(Int& z, const Int& x, const Int& y);
};
7 / 45
• 64 * n-bit加算(ビット長に応じたコード生成)
多倍長加算の例
GenAdd(int n) {
for (int i = 0; i < n; i++) {
mov(rax, ptr [x+i*8]);
if (i == 0) add(rax, ptr [y+i*8]);
else adc(rax, ptr [y+i*8]);
mov(ptr [z+i*8], rax);
}
ret(); }
add3:
mov rax, [rsi]
add rax, [rdx]
mov [rdi], rax
mov rax, [rsi + 8]
adc rax, [rdx + 8]
mov [rdi + 8], rax
mov rax, [rsi + 16]
adc rax, [rdx + 16]
mov [rdi + 16], rax
ret
add2:
mov rax, [rsi]
add rax, [rdx]
mov [rdi], rax
mov rax, [rsi + 8]
adc rax, [rdx + 8]
mov[rdi + 8], eax
ret
N=2 N=3
8 / 45
• LLVMのbitコード(VMのコード)で書けば各種
CPUへの最適化コードが出力されるバラ色の世界
• と思っていたときもあった
• 意外と各種アーキテクチャに縛られる
• x64向けにはコード生成されるがARMではランタイムエラー
• WebAssemblyへもランタイムエラー
• 32bit/64bit CPU向けに別々にコードを書く必要(私の経験)
• VMとは
• 手書き最適化に比べて1~2割遅い(ことが多い)
• Intel専用命令使いたい・勝手に使われたくない
• もちろん開発コスト比を考慮すると○
LLVMのJITでええんじゃね?
9 / 45
• LLVMの例
• x64での128bit演算出力
• 命令順序を除いて先ほどのコードと同一コードを生成
• 1024bitなら?
128~1024bitの加算
define void @add128(i128* %pz, i128* %px, i128* %py)
{
%x = load i128, i128* %px
%y = load i128, i128* %py
%z = add i128 %x, %y
store i128 %z, i128* %pz
ret void
}
10 / 45
• 怒濤のレジスタスピル(溢れ)
• xとyを足してzに配置するとき
下位レジスタから順にやれば
データの退避は不要
• それを把握していないので
一度スタックにコピーしてる
• 無駄にSIMDを使って遅くなる
こともある
• 余談
• llcに-pre-RA-sched=list-ilp
-max-sched-reorder=16
でspillしなくなる(たまたま?)
• llc --help-hiddenすると大量の隠しオプションが現れる
• バージョン非互換なものも多い
1024bitの加算の出力
movq 16(%rsi), %r13
movq (%rsi), %rbx
movq 8(%rsi), %r15
movq 120(%rdx), %rax
movq %rax, -8(%rsp) # 8-byte Spill
movq 112(%rdx), %rax
movq 104(%rdx), %rcx
movq %rcx, -24(%rsp) # 8-byte Spill
movq 96(%rdx), %rcx
movq 88(%rdx), %rbp
movq %rbp, -32(%rsp) # 8-byte Spill
movq 80(%rdx), %r8
movq 72(%rdx), %r12
movq 64(%rdx), %r14
movq 56(%rdx), %rbp
addq (%rdx), %rbx
movq %rbx, -16(%rsp) # 8-byte Spill
...
11 / 45
• PS2エミュレータ PCSX2
• https://github.com/PCSX2/pcsx2 ; plugin/shaderまわり
• (当時)画像処理部分を画面のRGBのデータ並び(ビデオカ
ードごとに違ってたりした)に応じた最適なコードをtempate
を使ってコンパイル時コード生成
• バイナリコードの肥大化
• 実行時生成にすることでバイナリコードの削減と高速化
• mrubyのJIT by @miura1729さん
• https://github.com/miura1729/mruby/
• 解説記事
https://qiita.com/miura1729/items/a1828849ec8fec596e74
• マンデルブロートなどの計算主体なものはx10ぐらいらしい
利用例
12 / 45
• 正規表現JITエンジン by @sinya8282
• https://github.com/sinya8282/Regen
• JavaScript VMのJIT by @Constellation
• https://github.com/Constellation/iv/
• http://labs.cybozu.co.jp/youth.html
サイボウズ・ラボユース生によるJIT
13 / 45
• Citra(shaderのJIT部分に使ってるもよう)
• https://github.com/citra-emu/citra
• https://github.com/citra-
emu/citra/blob/master/src/video_core/shader/shader_jit_x64_
compiler.cpp
• log2やexp2なども実行時コード生成してる
3DSエミュレータ
SSE4.1が使えれば
それを利用
無ければ代替命令を
生成
14 / 45
• 機械学習・深層学習ライブラリ
• https://github.com/intel/mkl-dnn
• 最新CPUのAVX-512命令v4fmaddpsなども利用
• jit_avx512_common_conv_winograd_kernel_f32.cpp
• templateとlambdaとアセンブラの混在感が味わい深い?
Intel MKL-DNN
15 / 45
• 自動パフォーマンスチューニングワークショップの
Intelの招待講演
• http://iwapt.org/2018/iwapt2018_proceedings/SarahKnepper_j
it_compilation_iwapt.pdf
• ありがたいことにほとんどXbyakの使い方
iWAPT2018
16 / 45
• 直感的にJITコードを記述できる
• 前述のPDFから
行列演算の端数処理
生成コード
17 / 45
• CPUのL2, L3キャッシュサイズを取得
• https://github.com/intel/mkl-
dnn/blob/19588d1484911a3dc7933b32ce71d2f1b9bbbb78/sr
c/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp#L580
• データサイズやレジスタの個数を計算しながら適切な
ループサイズを計算してJITしてる模様(詳細は未読)
キャッシュサイズを意識
const int L2_cap
= get_cache_size(2, true) / sizeof(float);
const int L3_capacity
= get_cache_size(3, false) / sizeof(float);
18 / 45
• Intelによる小さい行列計算専用JITライブラリ
• https://github.com/hfp/libxsmm
• Cでゼロから作られてる(without Xbyak)
• https://www.ixpug.org/images/docs/IXPUG_Annual_Spring_Co
nference_2018/09-PABST-Libxsmm.pdf
LIBXSMM
19 / 45
• メモリを確保
• malloc/_aligned_malloc/posix_memalign
• 命令フォーマットにしたがってバイトコードを展開
• modRM, SIB, rex, vex, evex, etc., AVX-512は結構大変
• exec属性を付与して実行
• mprotect(Linux), VirtualProtect(Win)
• ぶっちゃけ原理は簡単(作り込みは大変)
• だが10年以上やってるといろいろ経験する
Xbyakの実装
20 / 45
• jnl(jump if not less)がIntelコンパイラでエラー
• UNIX系コンパイラは各種特殊数学関数を持っている
• jnlは次数nの第1種Bessel関数のlong double版
• j1とかy0とかynなど、そんなグローバル関数が!と思うもの
がいろいろ
• https://www.gnu.org/software/libc/manual/html_node/Speci
al-Functions.html
• コンパイラの実装によっては関数はマクロでもよいらしい
• C++17ではまともな名前でcmathに登場
• j1 → std::cyl_bessel_j(1, x)
• yn(n, x) → std::neumann(n, x)
いろいろなトラブル
21 / 45
• and関数を作る
• もちろんCでコンパイルできる
• がC++ではエラー
gccで通るがg++で通らないコード
>g++ -c c++ t.c
t.cpp:1:9: error: expected unqualified-id before 'int'
int and(int x, int y)
^~~
t.cpp:1:9: error: expected ')' before 'int'
t.cpp:1:9: error: expected initializer before 'int'
>gcc -c t.c
int and(int x, int y) {
return x & y;
}
22 / 45
• C++ではand, or, xor, not, and_eq, bitorなどが予約語
• 関数名に使えない
• VCではiso646.hをincludeしないなら使える
• gcc/clangでは-fno-operator-namesオプションで無効化
• うっかり忘れると一見意味不明な大量のエラー
• Xbyakでは
• and_(), or_()などアンダースコアをつけた名前に変更
• 後方互換性のためand(), or()などもサポート
• -fno-operator-namesなしで使おうとすると
"use -fno-operator-names option"という#errorを表示してる
• どうやって?
代替表現(Alternative representations)
23 / 45
• プリプロセッサの仕様を利用する
• notが予約語(operator~の代替表現)の場合
• #ifの後ろは~+0 = -1 ≠ 0となりtrueなので#errorで止まる
• notが予約語でない場合
• プリプロセッサの中で定義されないマクロの値は0
• 0 +0 = 0で#ifが実行されない
トリック(by @digitalghost)
#if not +0
#error "use -fno-operator-names"
#endif
24 / 45
• N = 32700ぐらいでエラー
• WindowsでならNがもっと大きくても動く
• メモリが足りないわけではない
Linuxでたくさんnewできないという報告
struct Code : Xbyak::CodeGenerator {
Code(int x) {
mov(eax, x);
ret();
}
};
std::vector<std::unique_ptr<Code>> v(N);
for (int i = 0; i < N; i++) {
v[i] = std::make_unique<Code>(i);
}
25 / 45
• 1プロセスあたりのメモリマップの上限
• デフォルト65536
• Xbyakのposix_memalign + mprotectは2個消費する
• スレッドも1スレッドあたり2個消費する
• 上限に達するとmprotectはENOMEMを返す
• XBYAK_USE_MMAP_ALLOCATORを定義すると大丈夫
• posix_memalignの代わりにmmapを使うallocator
• 何故かこの上限に掛からなくなる
• https://www.kernel.org/doc/Documentation/sysctl/vm.t
xtにはmmap, mprotect, madviseの呼び出しに影響とある
• 70万個とかでも作れるようになる
/proc/sys/vm/max_map_count
26 / 45
• 最初は従来のアセンブラのラベルを模倣
• ローカルラベル
• MASMライクな@@, @b, @f
古典的なラベル
L("loop");
...
dec(ecx);
jnz("loop");
inLocalLabel(); // ピリオドで始まるラベルは
L(".lp");
jmp(".lp");
outLocalLabel(); // この区間内でだけ有効なローカルラベル
27 / 45
• JITならではの要件
• ラベルは文字列ではなく変数であってほしい
柔軟なラベル
Label generate_function(int type) {
Label entry = L();
// typeに応じて関数生成
return entry;
}
Label add = generate_function(TypeAdd);
Label sub = generate_function(TypeSub);
Label mul = generate_function(TypeMul);
call(add);
28 / 45
• ラベルを即値として扱う
ジャンプテーブルも作りたい
Label labelTbl, L0, L1, L2;
mov(rax, labelTbl); // アドレスを代入
jmp(ptr [rax + rcx * sizeof(void*)]);
jmp(ptr [rip + L0]);// L0への相対アドレッシングジャンプ
// ジャンプテーブル
L(labelTbl);
putL(L0); // ラベルのアドレスをメモリに配置
putL(L1);
L(L0);
mov(a, ret0);
ret();
L(L1);
...
29 / 45
• コード生成時にジャンプ先は未定
• その後エラー処理コードを生成してから
generate_function()のエラー先を決定したい
プレースフォルダ的な使い方
std::pair<Label, Label> generate_function() {
Label entry = L();
Label error;
..
jmp(error); // エラー処理に飛ぶ(が飛び先未定)
return {entry, error};
}
30 / 45
• 2個のラベルをリンクさせる
assignL(dstLabel, srcLabel);
{add, err1} = gen_func(...); // 飛び先未定のラベル
{sub, err2} = gen_func(...); // 飛び先未定のラベル
closeErr = L();
// closeのエラー処理
criticalErr = L();
// criticalエラーの処理
asignL(err1, closeErr); //add関数内のエラーはcloseErr
asignL(err2, criticalErr); //sub関数のエラーはcriticalErr
31 / 45
• 8文字からなる言語
• [ ; ポインタが示す値が0なら]にジャンプ
• ] ; 対応する[にジャンプ
• while (*cur) { ... }に相当
BrainfuckのJIT
stack<Label> labelB, labelF;
case '[':
labelB.push(L());
mov(eax, cur);
test(eax, eax);
Label F;
jz(F, T_NEAR);
labelF.push(F);
break;
case ']':
jmp(labelB.top()); labelB.pop();
L(labelF.top()); labelF.pop();
break;
B: // [
mov rax, [rcx]
test eax, eax
jz F
... // ネストする
jmp B // ]
F:
32 / 45
• ラベルがL()でアドレス確定されるごとに
• そのラベルを参照している全ての未定義一覧のアドレス解決
• ラベルがジャンプ命令で指定されるごとに
• 既にラベル先が確定しているものはアドレス確定
• その時点で行き先が未定義のもののものは未定義一覧に追加
• + 相対ジャンプで管理すべきものもある
• assignL()はリンクを変更
• ローカルラベルやスコープを抜けたラベルは管理外に
• 数が多いので保持し続けるとラベル解決の速度劣化に
• ラベルはコピーされるとスコープの外に出ることがある
• 参照カウンタで管理
ラベル管理の内部
33 / 45
• L()やjmp(label)が呼ばれるごとに
• ローカルラベルやスコープを抜けたラベルは管理外に
• 数が多いので保持し続けるとラベル解決の速度劣化の要因
• ラベルはコピーされるとスコープの外に出ることがある
• 参照カウンタで管理
ラベル管理の内部
未定義ラベルULの集合U
ULにリンクするjmpの場所を保持
定義済みラベルDLの集合D
ULにリンクするjmpのaddrを解決
L(UL)で
ラベル追加
jmp(label)のlabelが
Dに無い
Dにある→addr解決
jmp(UL);
...
jnc(UL);
...
putL(UL);
assignL(UL, DL);で
ラベル移動
34 / 45
• JITコードはプロファイラを使っても分からない
• VTuneなどはそれぞれJITコードを教えるAPIがある
• cf. http://herumi.in.coocan.jp/prog/profile.html#USEVT
プロファイラ
35 / 45
• perfの場合/tmp/perf-<pid>.mapに1行ずつ
を書いておくと集計時に自動的に利用してくれる
perf with JIT code
アドレス サイズ 名前
PerfMap::set(const void *p, size_t n, const char *name) {
fprintf(fp, "%llx %zx %s¥n", (long long)p, n, name);
}
PerfMap pm;
pm.set(c.getCode(), c.getSize(), "fff");
pm.set(c2.getCode(), c2.getSize(), "ggg");
36 / 45
• testはnasm, yasmなどのツールの出力と比較して確認
• 新命令はツールが間違ってることが多いので悩ましい
• nasmは何度もバグ報告してる
• 昔はyasmの方が信頼性が高かったが最近更新されてない
• Intelのマニュアルが間違ってることもある
• 印象に残っているバグをいくつか
• VM上で未定義命令エラー
• cpuidを見てCPUが新命令に対応している判別してコード生成
• host CPUはその命令に対応しているがgestのVMは非対応
しかしVMはhostのcpuidを返していた
• 生成された命令を実行してillegal instruction(ややこしい)
バグ
37 / 45
• t.asm
• yasm -f win32 -l t.lst t.asm
• EBFEではなくEB00が正解
• 生成オブジェクトは正しいEB00を出力
• 正しく実行はできるので結構悩んだ
• チケットには2011年に登録されていた
• https://tortall.lighthouseapp.com/projects/78676/tickets/233
-byte-code-in-listing-differs-from-emitted-bytes
• 実は最新版でも直ってない
yasmのlstとobjの不一致
jmp L
L:
1 %line 1+1 t.asm
2 00000000 EBFE jmp L
3 L:
38 / 45
• 別の命令(1to2)を挟むと逆アセンブル結果がバグる
• 最初は正しい出力なので混乱した
• https://sourceware.org/bugzilla/show_bug.cgi?id=23025
• 報告して10時間でpatchが作成された
objdumpの逆アセンブル出力
>objdump -M x86-64 -D -b binary -m i386 vcvtpd2dq.bin
67 c5 fb e6 40 20 vcvtpd2dqx 0x20(%eax),%xmm0; (X)
67 c5 ff e6 40 20 vcvtpd2dqy 0x20(%eax),%xmm0; (Y)
67 62 f1 ff 18 e6 40 04 vcvtpd2dq 0x20(%eax){1to2},%xmm0
67 c5 fb e6 40 20 vcvtpd2dq 0x20(%eax),%xmm0 ; (X')
67 c5 ff e6 40 20 vcvtpd2dq 0x20(%eax),%xmm0 ; (Y')
39 / 45
• vgatherdps(zmm0|k1, ptr [rax + zmm18]); が
vgatherdps(zmm0|k1, ptr [rax + zmm2]);になるバグ
• VSIBエンコーディング
• 従来のSIB ; [eax + ebx * scale + offset] ; レジスタ8種類
• 64bit対応 ; 16種類レジスタを表現するため1bit増える
• その1bitはREXプレフィックスの中に
• VSIB ; AVX2でSIMDレジスタを指定できるようになった
XbyakのVSIBエンコーディングバグ
SDM2.3.12 Vector SIB(VSIB) Memory Addressing 40 / 45
• VSIBやREXでは足りない
• ?mm16~?mm31までのレジスタはどうやって指定?
• EVEX.vvvvビット
• addpd(zmm31, zmm30, zmm20);などはちゃんと実装していた
• SDM2.6.1 Instruction Format and EVEX
• VSIBのときEVEX.V'をoffにするのだった
• 単なる見落とだがVSIB Memory Addressingのところの記述は
変わってないし……(言い訳)
AVX-512でレジスタは32個
EVEXV’ High-16 NDS/VIDX register specifier
P[19] Combine with EVEX.vvvv or when VSIB present.
41 / 45
• cpuid(eax=0x0b, ecx=0/1)でSMTやCOREの数を取得
• cpuid(eax=0x4, ecx=cache_level)で各階層の情報を取得
• VMのせいか時々おかしい値に
キャッシュサイズの取得はややこしい
42 / 45
• Intel Software Development Emulator
• https://software.intel.com/en-us/articles/intel-software-
development-emulator
• 当たり前だが一番信頼できる
• xed -mpx -64 -ir <rawobj>でdisassemblerとして利用可能
• vgatherdps(zmm0|k1, ptr[rax + zmm2]);
の出力は正しくdisasできるのに
vgatherdps(zmm0|k1, ptr[rax + zmm0]);
の出力はエラー
• 両方とも同じエンコードパスを通るのに何故?
Intel SDEの仕様に悩む
ERROR: GATHER_REGS Could not decode at offset: 0x0
PC: 0x0: [62F27D49920400]
43 / 45
• gather命令はindex == destinationのとき使えない
• Note that: If any pair of the index, mask, or destination
registers are the same, this instruction results a UD
fault.
• decode errではなく実行時エラーならよかったんだが
エンコードは正しいがUDのため弾かれた
44 / 45
• 自分で小さいVM作ってみると面白いかも
• 狙ったコードを生成できると嬉しい
• 命令数が多い(マニュアルが分厚い)と大変だ
まとめ
45 / 45

More Related Content

What's hot

20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチンyohhoy
 
冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safeKumazaki Hiroki
 
高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装MITSUNARI Shigeo
 
条件分岐とcmovとmaxps
条件分岐とcmovとmaxps条件分岐とcmovとmaxps
条件分岐とcmovとmaxpsMITSUNARI Shigeo
 
Intro to SVE 富岳のA64FXを触ってみた
Intro to SVE 富岳のA64FXを触ってみたIntro to SVE 富岳のA64FXを触ってみた
Intro to SVE 富岳のA64FXを触ってみたMITSUNARI Shigeo
 
DSIRNLP #3 LZ4 の速さの秘密に迫ってみる
DSIRNLP #3 LZ4 の速さの秘密に迫ってみるDSIRNLP #3 LZ4 の速さの秘密に迫ってみる
DSIRNLP #3 LZ4 の速さの秘密に迫ってみるAtsushi KOMIYA
 
暗号技術の実装と数学
暗号技術の実装と数学暗号技術の実装と数学
暗号技術の実装と数学MITSUNARI Shigeo
 
不遇の標準ライブラリ - valarray
不遇の標準ライブラリ - valarray不遇の標準ライブラリ - valarray
不遇の標準ライブラリ - valarrayRyosuke839
 
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)Kuniyasu Suzaki
 
SQLチューニング入門 入門編
SQLチューニング入門 入門編SQLチューニング入門 入門編
SQLチューニング入門 入門編Miki Shimogai
 
ARM CPUにおけるSIMDを用いた高速計算入門
ARM CPUにおけるSIMDを用いた高速計算入門ARM CPUにおけるSIMDを用いた高速計算入門
ARM CPUにおけるSIMDを用いた高速計算入門Fixstars Corporation
 
プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造Takuya Akiba
 
基礎線形代数講座
基礎線形代数講座基礎線形代数講座
基礎線形代数講座SEGADevTech
 
Apache Arrow - データ処理ツールの次世代プラットフォーム
Apache Arrow - データ処理ツールの次世代プラットフォームApache Arrow - データ処理ツールの次世代プラットフォーム
Apache Arrow - データ処理ツールの次世代プラットフォームKouhei Sutou
 
プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法Takuya Akiba
 
Union find(素集合データ構造)
Union find(素集合データ構造)Union find(素集合データ構造)
Union find(素集合データ構造)AtCoder Inc.
 

What's hot (20)

20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン
 
冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safe
 
高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装
 
条件分岐とcmovとmaxps
条件分岐とcmovとmaxps条件分岐とcmovとmaxps
条件分岐とcmovとmaxps
 
Intro to SVE 富岳のA64FXを触ってみた
Intro to SVE 富岳のA64FXを触ってみたIntro to SVE 富岳のA64FXを触ってみた
Intro to SVE 富岳のA64FXを触ってみた
 
DSIRNLP #3 LZ4 の速さの秘密に迫ってみる
DSIRNLP #3 LZ4 の速さの秘密に迫ってみるDSIRNLP #3 LZ4 の速さの秘密に迫ってみる
DSIRNLP #3 LZ4 の速さの秘密に迫ってみる
 
暗号技術の実装と数学
暗号技術の実装と数学暗号技術の実装と数学
暗号技術の実装と数学
 
Marp Tutorial
Marp TutorialMarp Tutorial
Marp Tutorial
 
不遇の標準ライブラリ - valarray
不遇の標準ライブラリ - valarray不遇の標準ライブラリ - valarray
不遇の標準ライブラリ - valarray
 
プログラムを高速化する話
プログラムを高速化する話プログラムを高速化する話
プログラムを高速化する話
 
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)
 
llvm入門
llvm入門llvm入門
llvm入門
 
SQLチューニング入門 入門編
SQLチューニング入門 入門編SQLチューニング入門 入門編
SQLチューニング入門 入門編
 
Glibc malloc internal
Glibc malloc internalGlibc malloc internal
Glibc malloc internal
 
ARM CPUにおけるSIMDを用いた高速計算入門
ARM CPUにおけるSIMDを用いた高速計算入門ARM CPUにおけるSIMDを用いた高速計算入門
ARM CPUにおけるSIMDを用いた高速計算入門
 
プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造
 
基礎線形代数講座
基礎線形代数講座基礎線形代数講座
基礎線形代数講座
 
Apache Arrow - データ処理ツールの次世代プラットフォーム
Apache Arrow - データ処理ツールの次世代プラットフォームApache Arrow - データ処理ツールの次世代プラットフォーム
Apache Arrow - データ処理ツールの次世代プラットフォーム
 
プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法
 
Union find(素集合データ構造)
Union find(素集合データ構造)Union find(素集合データ構造)
Union find(素集合データ構造)
 

Similar to Xbyakの紹介とその周辺

高速な暗号実装のためにしてきたこと
高速な暗号実装のためにしてきたこと高速な暗号実装のためにしてきたこと
高速な暗号実装のためにしてきたことMITSUNARI Shigeo
 
Node予備校 vol.1 名古屋
Node予備校 vol.1 名古屋Node予備校 vol.1 名古屋
Node予備校 vol.1 名古屋Mori Shingo
 
WASM(WebAssembly)入門 ペアリング演算やってみた
WASM(WebAssembly)入門 ペアリング演算やってみたWASM(WebAssembly)入門 ペアリング演算やってみた
WASM(WebAssembly)入門 ペアリング演算やってみたMITSUNARI Shigeo
 
ROP Illmatic: Exploring Universal ROP on glibc x86-64 (ja)
ROP Illmatic: Exploring Universal ROP on glibc x86-64 (ja)ROP Illmatic: Exploring Universal ROP on glibc x86-64 (ja)
ROP Illmatic: Exploring Universal ROP on glibc x86-64 (ja)inaz2
 
スタート低レイヤー #0
スタート低レイヤー #0スタート低レイヤー #0
スタート低レイヤー #0Kiwamu Okabe
 
関東GPGPU勉強会 LLVM meets GPU
関東GPGPU勉強会 LLVM meets GPU関東GPGPU勉強会 LLVM meets GPU
関東GPGPU勉強会 LLVM meets GPUTakuro Iizuka
 
SmartNews TechNight Vol5 : SmartNews AdServer 解体新書 / ポストモーテム
SmartNews TechNight Vol5 : SmartNews AdServer 解体新書 / ポストモーテムSmartNews TechNight Vol5 : SmartNews AdServer 解体新書 / ポストモーテム
SmartNews TechNight Vol5 : SmartNews AdServer 解体新書 / ポストモーテムSmartNews, Inc.
 
Pythonによる並列プログラミング -GPGPUも-
Pythonによる並列プログラミング   -GPGPUも- Pythonによる並列プログラミング   -GPGPUも-
Pythonによる並列プログラミング -GPGPUも- Yusaku Watanabe
 
StackExchangeで見たシステムプログラミング案件
StackExchangeで見たシステムプログラミング案件StackExchangeで見たシステムプログラミング案件
StackExchangeで見たシステムプログラミング案件yaegashi
 
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)明日使える超高速Ruby - RXbyak (Mitaka.rb #5)
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)Shuyo Nakatani
 
これからのコンピューティングとJava(Hacker Tackle)
これからのコンピューティングとJava(Hacker Tackle)これからのコンピューティングとJava(Hacker Tackle)
これからのコンピューティングとJava(Hacker Tackle)なおき きしだ
 
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)Takeshi Yamamuro
 
ホームディレクトリに埋もれた便利なコードをさがせ!
ホームディレクトリに埋もれた便利なコードをさがせ!ホームディレクトリに埋もれた便利なコードをさがせ!
ホームディレクトリに埋もれた便利なコードをさがせ!Yohei Fushii
 
Fpga online seminar by fixstars (1st)
Fpga online seminar by fixstars (1st)Fpga online seminar by fixstars (1st)
Fpga online seminar by fixstars (1st)Fixstars Corporation
 
ACRiウェビナー:岩渕様ご講演資料
ACRiウェビナー:岩渕様ご講演資料ACRiウェビナー:岩渕様ご講演資料
ACRiウェビナー:岩渕様ご講演資料直久 住川
 
zend_parse_parametersと64bit環境
zend_parse_parametersと64bit環境zend_parse_parametersと64bit環境
zend_parse_parametersと64bit環境Yo Ya
 
もしも… Javaでヘテロジニアスコアが使えたら…
もしも… Javaでヘテロジニアスコアが使えたら…もしも… Javaでヘテロジニアスコアが使えたら…
もしも… Javaでヘテロジニアスコアが使えたら…Yasumasa Suenaga
 
Cプログラマのためのカッコつけないプログラミングの勧め
Cプログラマのためのカッコつけないプログラミングの勧めCプログラマのためのカッコつけないプログラミングの勧め
Cプログラマのためのカッコつけないプログラミングの勧めMITSUNARI Shigeo
 

Similar to Xbyakの紹介とその周辺 (20)

高速な暗号実装のためにしてきたこと
高速な暗号実装のためにしてきたこと高速な暗号実装のためにしてきたこと
高速な暗号実装のためにしてきたこと
 
Node予備校 vol.1 名古屋
Node予備校 vol.1 名古屋Node予備校 vol.1 名古屋
Node予備校 vol.1 名古屋
 
WASM(WebAssembly)入門 ペアリング演算やってみた
WASM(WebAssembly)入門 ペアリング演算やってみたWASM(WebAssembly)入門 ペアリング演算やってみた
WASM(WebAssembly)入門 ペアリング演算やってみた
 
ROP Illmatic: Exploring Universal ROP on glibc x86-64 (ja)
ROP Illmatic: Exploring Universal ROP on glibc x86-64 (ja)ROP Illmatic: Exploring Universal ROP on glibc x86-64 (ja)
ROP Illmatic: Exploring Universal ROP on glibc x86-64 (ja)
 
スタート低レイヤー #0
スタート低レイヤー #0スタート低レイヤー #0
スタート低レイヤー #0
 
関東GPGPU勉強会 LLVM meets GPU
関東GPGPU勉強会 LLVM meets GPU関東GPGPU勉強会 LLVM meets GPU
関東GPGPU勉強会 LLVM meets GPU
 
SmartNews TechNight Vol5 : SmartNews AdServer 解体新書 / ポストモーテム
SmartNews TechNight Vol5 : SmartNews AdServer 解体新書 / ポストモーテムSmartNews TechNight Vol5 : SmartNews AdServer 解体新書 / ポストモーテム
SmartNews TechNight Vol5 : SmartNews AdServer 解体新書 / ポストモーテム
 
Pythonによる並列プログラミング -GPGPUも-
Pythonによる並列プログラミング   -GPGPUも- Pythonによる並列プログラミング   -GPGPUも-
Pythonによる並列プログラミング -GPGPUも-
 
StackExchangeで見たシステムプログラミング案件
StackExchangeで見たシステムプログラミング案件StackExchangeで見たシステムプログラミング案件
StackExchangeで見たシステムプログラミング案件
 
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)明日使える超高速Ruby - RXbyak (Mitaka.rb #5)
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)
 
これからのコンピューティングとJava(Hacker Tackle)
これからのコンピューティングとJava(Hacker Tackle)これからのコンピューティングとJava(Hacker Tackle)
これからのコンピューティングとJava(Hacker Tackle)
 
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
 
ホームディレクトリに埋もれた便利なコードをさがせ!
ホームディレクトリに埋もれた便利なコードをさがせ!ホームディレクトリに埋もれた便利なコードをさがせ!
ホームディレクトリに埋もれた便利なコードをさがせ!
 
Fpga online seminar by fixstars (1st)
Fpga online seminar by fixstars (1st)Fpga online seminar by fixstars (1st)
Fpga online seminar by fixstars (1st)
 
ACRiウェビナー:岩渕様ご講演資料
ACRiウェビナー:岩渕様ご講演資料ACRiウェビナー:岩渕様ご講演資料
ACRiウェビナー:岩渕様ご講演資料
 
zend_parse_parametersと64bit環境
zend_parse_parametersと64bit環境zend_parse_parametersと64bit環境
zend_parse_parametersと64bit環境
 
もしも… Javaでヘテロジニアスコアが使えたら…
もしも… Javaでヘテロジニアスコアが使えたら…もしも… Javaでヘテロジニアスコアが使えたら…
もしも… Javaでヘテロジニアスコアが使えたら…
 
Zynga
ZyngaZynga
Zynga
 
Aws privte20110406 arai
Aws privte20110406 araiAws privte20110406 arai
Aws privte20110406 arai
 
Cプログラマのためのカッコつけないプログラミングの勧め
Cプログラマのためのカッコつけないプログラミングの勧めCプログラマのためのカッコつけないプログラミングの勧め
Cプログラマのためのカッコつけないプログラミングの勧め
 

More from MITSUNARI Shigeo

範囲証明つき準同型暗号とその対話的プロトコル
範囲証明つき準同型暗号とその対話的プロトコル範囲証明つき準同型暗号とその対話的プロトコル
範囲証明つき準同型暗号とその対話的プロトコルMITSUNARI Shigeo
 
暗認本読書会13 advanced
暗認本読書会13 advanced暗認本読書会13 advanced
暗認本読書会13 advancedMITSUNARI Shigeo
 
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgenIntel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgenMITSUNARI Shigeo
 
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法MITSUNARI Shigeo
 
WebAssembly向け多倍長演算の実装
WebAssembly向け多倍長演算の実装WebAssembly向け多倍長演算の実装
WebAssembly向け多倍長演算の実装MITSUNARI Shigeo
 
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化MITSUNARI Shigeo
 
BLS署名の実装とその応用
BLS署名の実装とその応用BLS署名の実装とその応用
BLS署名の実装とその応用MITSUNARI Shigeo
 
LazyFP vulnerabilityの紹介
LazyFP vulnerabilityの紹介LazyFP vulnerabilityの紹介
LazyFP vulnerabilityの紹介MITSUNARI Shigeo
 

More from MITSUNARI Shigeo (20)

範囲証明つき準同型暗号とその対話的プロトコル
範囲証明つき準同型暗号とその対話的プロトコル範囲証明つき準同型暗号とその対話的プロトコル
範囲証明つき準同型暗号とその対話的プロトコル
 
暗認本読書会13 advanced
暗認本読書会13 advanced暗認本読書会13 advanced
暗認本読書会13 advanced
 
暗認本読書会12
暗認本読書会12暗認本読書会12
暗認本読書会12
 
暗認本読書会11
暗認本読書会11暗認本読書会11
暗認本読書会11
 
暗認本読書会10
暗認本読書会10暗認本読書会10
暗認本読書会10
 
暗認本読書会9
暗認本読書会9暗認本読書会9
暗認本読書会9
 
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgenIntel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
 
暗認本読書会8
暗認本読書会8暗認本読書会8
暗認本読書会8
 
暗認本読書会7
暗認本読書会7暗認本読書会7
暗認本読書会7
 
暗認本読書会6
暗認本読書会6暗認本読書会6
暗認本読書会6
 
暗認本読書会5
暗認本読書会5暗認本読書会5
暗認本読書会5
 
暗認本読書会4
暗認本読書会4暗認本読書会4
暗認本読書会4
 
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
 
私とOSSの25年
私とOSSの25年私とOSSの25年
私とOSSの25年
 
WebAssembly向け多倍長演算の実装
WebAssembly向け多倍長演算の実装WebAssembly向け多倍長演算の実装
WebAssembly向け多倍長演算の実装
 
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
 
楕円曲線と暗号
楕円曲線と暗号楕円曲線と暗号
楕円曲線と暗号
 
HPC Phys-20201203
HPC Phys-20201203HPC Phys-20201203
HPC Phys-20201203
 
BLS署名の実装とその応用
BLS署名の実装とその応用BLS署名の実装とその応用
BLS署名の実装とその応用
 
LazyFP vulnerabilityの紹介
LazyFP vulnerabilityの紹介LazyFP vulnerabilityの紹介
LazyFP vulnerabilityの紹介
 

Xbyakの紹介とその周辺

  • 2. • @herumi • https://github.com/herumi/she-wasm • ペアリングベースの準同型暗号のWebAssembly向け実装 • IEEE trans. on Computers 2014, AsiaCCS 2018など • https://github.com/herumi/bls • BLS署名の実装 • Dfinityというブロックチェーン系ベンチャーが使ってる 自己紹介 2 / 45
  • 3. • C++用のx86/x64専用JITアセンブラ • https://github.com/herumi/xbyak • 自分が使いたい(暗号用に開発を始めた)アセンブラ • 開発を初めてもうすぐ12年目 • AVX-512フルサポート • ちなみに現在(2018/9)Intelの一番長い名前の命令は • vgf2p8affineinvqb(17文字) • Galois Field Affine Transformation Inverse • 𝐹28の元𝑥に対して𝐴𝑥−1 + 𝐵を計算する Xbyakって何? 3 / 45
  • 4. • NASM, gasなどの通常の静的なアセンブラに比べて • コードを書きやすい(個人の感想) • C++との連係がしやすい(個人の...) • VMを書きやすい(略) • V8やWebkitなどのJavaScriptエンジンも同等の JITアセンブラを持ってる(ARMなどにも対応してる) 従来のアセンブラとの比較 4 / 45
  • 5. • 整数nを返す関数を生成するクラス • インスタンスを作成し関数ポインタを取り出して実行 実行時に整数nを返す関数を生成 struct Code : Xbyak::CodeGenerator { Code(int n) { mov(eax, n); // 注意 インラインアセンブラではない ret(); // 純粋なC++のコード } }; Code c1(3); auto f = c1.getCode<int (*)()>();// intを返す関数ポインタ printf("%d¥n", f()); // 3 実行時にコード生成 mov eax, 3 ret 5 / 45
  • 6. • MASMに似せるための各種演算子オーバーロード • Intel命令をそのまま書けるので直感的に操作しやすい • 小さなブロックを組み合わせて作る感覚が楽しい 雰囲気 void gen_add(const RegExp& dst, const RegExp& src); void f(int n) { auto addr = n > 0 ? rsi + rax * 8 + n * 8 : rdi; mov(rax, ptr[addr]); vgatherqpd(zmm5 | k7, ptr [rax + 64 + zmm21 * 2]); gen_add(rsp + 8, rsi + 8); } 6 / 45
  • 7. • template引数で長さを指定する整数クラス • 最大の大きさを指定するNはコンパイル時指定 • 実際の整数の大きさを指定するnは実行時指定 • このようなNを外部アセンブラと連係するのは面倒 固定多倍長加算 template<size_t N> struct Int { void init(size_t n); // n * 64 bit整数として利用(n < N) uint64_t d[N]; // z = x + y void (*add)(Int& z, const Int& x, const Int& y); }; 7 / 45
  • 8. • 64 * n-bit加算(ビット長に応じたコード生成) 多倍長加算の例 GenAdd(int n) { for (int i = 0; i < n; i++) { mov(rax, ptr [x+i*8]); if (i == 0) add(rax, ptr [y+i*8]); else adc(rax, ptr [y+i*8]); mov(ptr [z+i*8], rax); } ret(); } add3: mov rax, [rsi] add rax, [rdx] mov [rdi], rax mov rax, [rsi + 8] adc rax, [rdx + 8] mov [rdi + 8], rax mov rax, [rsi + 16] adc rax, [rdx + 16] mov [rdi + 16], rax ret add2: mov rax, [rsi] add rax, [rdx] mov [rdi], rax mov rax, [rsi + 8] adc rax, [rdx + 8] mov[rdi + 8], eax ret N=2 N=3 8 / 45
  • 9. • LLVMのbitコード(VMのコード)で書けば各種 CPUへの最適化コードが出力されるバラ色の世界 • と思っていたときもあった • 意外と各種アーキテクチャに縛られる • x64向けにはコード生成されるがARMではランタイムエラー • WebAssemblyへもランタイムエラー • 32bit/64bit CPU向けに別々にコードを書く必要(私の経験) • VMとは • 手書き最適化に比べて1~2割遅い(ことが多い) • Intel専用命令使いたい・勝手に使われたくない • もちろん開発コスト比を考慮すると○ LLVMのJITでええんじゃね? 9 / 45
  • 10. • LLVMの例 • x64での128bit演算出力 • 命令順序を除いて先ほどのコードと同一コードを生成 • 1024bitなら? 128~1024bitの加算 define void @add128(i128* %pz, i128* %px, i128* %py) { %x = load i128, i128* %px %y = load i128, i128* %py %z = add i128 %x, %y store i128 %z, i128* %pz ret void } 10 / 45
  • 11. • 怒濤のレジスタスピル(溢れ) • xとyを足してzに配置するとき 下位レジスタから順にやれば データの退避は不要 • それを把握していないので 一度スタックにコピーしてる • 無駄にSIMDを使って遅くなる こともある • 余談 • llcに-pre-RA-sched=list-ilp -max-sched-reorder=16 でspillしなくなる(たまたま?) • llc --help-hiddenすると大量の隠しオプションが現れる • バージョン非互換なものも多い 1024bitの加算の出力 movq 16(%rsi), %r13 movq (%rsi), %rbx movq 8(%rsi), %r15 movq 120(%rdx), %rax movq %rax, -8(%rsp) # 8-byte Spill movq 112(%rdx), %rax movq 104(%rdx), %rcx movq %rcx, -24(%rsp) # 8-byte Spill movq 96(%rdx), %rcx movq 88(%rdx), %rbp movq %rbp, -32(%rsp) # 8-byte Spill movq 80(%rdx), %r8 movq 72(%rdx), %r12 movq 64(%rdx), %r14 movq 56(%rdx), %rbp addq (%rdx), %rbx movq %rbx, -16(%rsp) # 8-byte Spill ... 11 / 45
  • 12. • PS2エミュレータ PCSX2 • https://github.com/PCSX2/pcsx2 ; plugin/shaderまわり • (当時)画像処理部分を画面のRGBのデータ並び(ビデオカ ードごとに違ってたりした)に応じた最適なコードをtempate を使ってコンパイル時コード生成 • バイナリコードの肥大化 • 実行時生成にすることでバイナリコードの削減と高速化 • mrubyのJIT by @miura1729さん • https://github.com/miura1729/mruby/ • 解説記事 https://qiita.com/miura1729/items/a1828849ec8fec596e74 • マンデルブロートなどの計算主体なものはx10ぐらいらしい 利用例 12 / 45
  • 13. • 正規表現JITエンジン by @sinya8282 • https://github.com/sinya8282/Regen • JavaScript VMのJIT by @Constellation • https://github.com/Constellation/iv/ • http://labs.cybozu.co.jp/youth.html サイボウズ・ラボユース生によるJIT 13 / 45
  • 14. • Citra(shaderのJIT部分に使ってるもよう) • https://github.com/citra-emu/citra • https://github.com/citra- emu/citra/blob/master/src/video_core/shader/shader_jit_x64_ compiler.cpp • log2やexp2なども実行時コード生成してる 3DSエミュレータ SSE4.1が使えれば それを利用 無ければ代替命令を 生成 14 / 45
  • 15. • 機械学習・深層学習ライブラリ • https://github.com/intel/mkl-dnn • 最新CPUのAVX-512命令v4fmaddpsなども利用 • jit_avx512_common_conv_winograd_kernel_f32.cpp • templateとlambdaとアセンブラの混在感が味わい深い? Intel MKL-DNN 15 / 45
  • 18. • CPUのL2, L3キャッシュサイズを取得 • https://github.com/intel/mkl- dnn/blob/19588d1484911a3dc7933b32ce71d2f1b9bbbb78/sr c/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp#L580 • データサイズやレジスタの個数を計算しながら適切な ループサイズを計算してJITしてる模様(詳細は未読) キャッシュサイズを意識 const int L2_cap = get_cache_size(2, true) / sizeof(float); const int L3_capacity = get_cache_size(3, false) / sizeof(float); 18 / 45
  • 19. • Intelによる小さい行列計算専用JITライブラリ • https://github.com/hfp/libxsmm • Cでゼロから作られてる(without Xbyak) • https://www.ixpug.org/images/docs/IXPUG_Annual_Spring_Co nference_2018/09-PABST-Libxsmm.pdf LIBXSMM 19 / 45
  • 20. • メモリを確保 • malloc/_aligned_malloc/posix_memalign • 命令フォーマットにしたがってバイトコードを展開 • modRM, SIB, rex, vex, evex, etc., AVX-512は結構大変 • exec属性を付与して実行 • mprotect(Linux), VirtualProtect(Win) • ぶっちゃけ原理は簡単(作り込みは大変) • だが10年以上やってるといろいろ経験する Xbyakの実装 20 / 45
  • 21. • jnl(jump if not less)がIntelコンパイラでエラー • UNIX系コンパイラは各種特殊数学関数を持っている • jnlは次数nの第1種Bessel関数のlong double版 • j1とかy0とかynなど、そんなグローバル関数が!と思うもの がいろいろ • https://www.gnu.org/software/libc/manual/html_node/Speci al-Functions.html • コンパイラの実装によっては関数はマクロでもよいらしい • C++17ではまともな名前でcmathに登場 • j1 → std::cyl_bessel_j(1, x) • yn(n, x) → std::neumann(n, x) いろいろなトラブル 21 / 45
  • 22. • and関数を作る • もちろんCでコンパイルできる • がC++ではエラー gccで通るがg++で通らないコード >g++ -c c++ t.c t.cpp:1:9: error: expected unqualified-id before 'int' int and(int x, int y) ^~~ t.cpp:1:9: error: expected ')' before 'int' t.cpp:1:9: error: expected initializer before 'int' >gcc -c t.c int and(int x, int y) { return x & y; } 22 / 45
  • 23. • C++ではand, or, xor, not, and_eq, bitorなどが予約語 • 関数名に使えない • VCではiso646.hをincludeしないなら使える • gcc/clangでは-fno-operator-namesオプションで無効化 • うっかり忘れると一見意味不明な大量のエラー • Xbyakでは • and_(), or_()などアンダースコアをつけた名前に変更 • 後方互換性のためand(), or()などもサポート • -fno-operator-namesなしで使おうとすると "use -fno-operator-names option"という#errorを表示してる • どうやって? 代替表現(Alternative representations) 23 / 45
  • 24. • プリプロセッサの仕様を利用する • notが予約語(operator~の代替表現)の場合 • #ifの後ろは~+0 = -1 ≠ 0となりtrueなので#errorで止まる • notが予約語でない場合 • プリプロセッサの中で定義されないマクロの値は0 • 0 +0 = 0で#ifが実行されない トリック(by @digitalghost) #if not +0 #error "use -fno-operator-names" #endif 24 / 45
  • 25. • N = 32700ぐらいでエラー • WindowsでならNがもっと大きくても動く • メモリが足りないわけではない Linuxでたくさんnewできないという報告 struct Code : Xbyak::CodeGenerator { Code(int x) { mov(eax, x); ret(); } }; std::vector<std::unique_ptr<Code>> v(N); for (int i = 0; i < N; i++) { v[i] = std::make_unique<Code>(i); } 25 / 45
  • 26. • 1プロセスあたりのメモリマップの上限 • デフォルト65536 • Xbyakのposix_memalign + mprotectは2個消費する • スレッドも1スレッドあたり2個消費する • 上限に達するとmprotectはENOMEMを返す • XBYAK_USE_MMAP_ALLOCATORを定義すると大丈夫 • posix_memalignの代わりにmmapを使うallocator • 何故かこの上限に掛からなくなる • https://www.kernel.org/doc/Documentation/sysctl/vm.t xtにはmmap, mprotect, madviseの呼び出しに影響とある • 70万個とかでも作れるようになる /proc/sys/vm/max_map_count 26 / 45
  • 27. • 最初は従来のアセンブラのラベルを模倣 • ローカルラベル • MASMライクな@@, @b, @f 古典的なラベル L("loop"); ... dec(ecx); jnz("loop"); inLocalLabel(); // ピリオドで始まるラベルは L(".lp"); jmp(".lp"); outLocalLabel(); // この区間内でだけ有効なローカルラベル 27 / 45
  • 28. • JITならではの要件 • ラベルは文字列ではなく変数であってほしい 柔軟なラベル Label generate_function(int type) { Label entry = L(); // typeに応じて関数生成 return entry; } Label add = generate_function(TypeAdd); Label sub = generate_function(TypeSub); Label mul = generate_function(TypeMul); call(add); 28 / 45
  • 29. • ラベルを即値として扱う ジャンプテーブルも作りたい Label labelTbl, L0, L1, L2; mov(rax, labelTbl); // アドレスを代入 jmp(ptr [rax + rcx * sizeof(void*)]); jmp(ptr [rip + L0]);// L0への相対アドレッシングジャンプ // ジャンプテーブル L(labelTbl); putL(L0); // ラベルのアドレスをメモリに配置 putL(L1); L(L0); mov(a, ret0); ret(); L(L1); ... 29 / 45
  • 30. • コード生成時にジャンプ先は未定 • その後エラー処理コードを生成してから generate_function()のエラー先を決定したい プレースフォルダ的な使い方 std::pair<Label, Label> generate_function() { Label entry = L(); Label error; .. jmp(error); // エラー処理に飛ぶ(が飛び先未定) return {entry, error}; } 30 / 45
  • 31. • 2個のラベルをリンクさせる assignL(dstLabel, srcLabel); {add, err1} = gen_func(...); // 飛び先未定のラベル {sub, err2} = gen_func(...); // 飛び先未定のラベル closeErr = L(); // closeのエラー処理 criticalErr = L(); // criticalエラーの処理 asignL(err1, closeErr); //add関数内のエラーはcloseErr asignL(err2, criticalErr); //sub関数のエラーはcriticalErr 31 / 45
  • 32. • 8文字からなる言語 • [ ; ポインタが示す値が0なら]にジャンプ • ] ; 対応する[にジャンプ • while (*cur) { ... }に相当 BrainfuckのJIT stack<Label> labelB, labelF; case '[': labelB.push(L()); mov(eax, cur); test(eax, eax); Label F; jz(F, T_NEAR); labelF.push(F); break; case ']': jmp(labelB.top()); labelB.pop(); L(labelF.top()); labelF.pop(); break; B: // [ mov rax, [rcx] test eax, eax jz F ... // ネストする jmp B // ] F: 32 / 45
  • 33. • ラベルがL()でアドレス確定されるごとに • そのラベルを参照している全ての未定義一覧のアドレス解決 • ラベルがジャンプ命令で指定されるごとに • 既にラベル先が確定しているものはアドレス確定 • その時点で行き先が未定義のもののものは未定義一覧に追加 • + 相対ジャンプで管理すべきものもある • assignL()はリンクを変更 • ローカルラベルやスコープを抜けたラベルは管理外に • 数が多いので保持し続けるとラベル解決の速度劣化に • ラベルはコピーされるとスコープの外に出ることがある • 参照カウンタで管理 ラベル管理の内部 33 / 45
  • 34. • L()やjmp(label)が呼ばれるごとに • ローカルラベルやスコープを抜けたラベルは管理外に • 数が多いので保持し続けるとラベル解決の速度劣化の要因 • ラベルはコピーされるとスコープの外に出ることがある • 参照カウンタで管理 ラベル管理の内部 未定義ラベルULの集合U ULにリンクするjmpの場所を保持 定義済みラベルDLの集合D ULにリンクするjmpのaddrを解決 L(UL)で ラベル追加 jmp(label)のlabelが Dに無い Dにある→addr解決 jmp(UL); ... jnc(UL); ... putL(UL); assignL(UL, DL);で ラベル移動 34 / 45
  • 36. • perfの場合/tmp/perf-<pid>.mapに1行ずつ を書いておくと集計時に自動的に利用してくれる perf with JIT code アドレス サイズ 名前 PerfMap::set(const void *p, size_t n, const char *name) { fprintf(fp, "%llx %zx %s¥n", (long long)p, n, name); } PerfMap pm; pm.set(c.getCode(), c.getSize(), "fff"); pm.set(c2.getCode(), c2.getSize(), "ggg"); 36 / 45
  • 37. • testはnasm, yasmなどのツールの出力と比較して確認 • 新命令はツールが間違ってることが多いので悩ましい • nasmは何度もバグ報告してる • 昔はyasmの方が信頼性が高かったが最近更新されてない • Intelのマニュアルが間違ってることもある • 印象に残っているバグをいくつか • VM上で未定義命令エラー • cpuidを見てCPUが新命令に対応している判別してコード生成 • host CPUはその命令に対応しているがgestのVMは非対応 しかしVMはhostのcpuidを返していた • 生成された命令を実行してillegal instruction(ややこしい) バグ 37 / 45
  • 38. • t.asm • yasm -f win32 -l t.lst t.asm • EBFEではなくEB00が正解 • 生成オブジェクトは正しいEB00を出力 • 正しく実行はできるので結構悩んだ • チケットには2011年に登録されていた • https://tortall.lighthouseapp.com/projects/78676/tickets/233 -byte-code-in-listing-differs-from-emitted-bytes • 実は最新版でも直ってない yasmのlstとobjの不一致 jmp L L: 1 %line 1+1 t.asm 2 00000000 EBFE jmp L 3 L: 38 / 45
  • 39. • 別の命令(1to2)を挟むと逆アセンブル結果がバグる • 最初は正しい出力なので混乱した • https://sourceware.org/bugzilla/show_bug.cgi?id=23025 • 報告して10時間でpatchが作成された objdumpの逆アセンブル出力 >objdump -M x86-64 -D -b binary -m i386 vcvtpd2dq.bin 67 c5 fb e6 40 20 vcvtpd2dqx 0x20(%eax),%xmm0; (X) 67 c5 ff e6 40 20 vcvtpd2dqy 0x20(%eax),%xmm0; (Y) 67 62 f1 ff 18 e6 40 04 vcvtpd2dq 0x20(%eax){1to2},%xmm0 67 c5 fb e6 40 20 vcvtpd2dq 0x20(%eax),%xmm0 ; (X') 67 c5 ff e6 40 20 vcvtpd2dq 0x20(%eax),%xmm0 ; (Y') 39 / 45
  • 40. • vgatherdps(zmm0|k1, ptr [rax + zmm18]); が vgatherdps(zmm0|k1, ptr [rax + zmm2]);になるバグ • VSIBエンコーディング • 従来のSIB ; [eax + ebx * scale + offset] ; レジスタ8種類 • 64bit対応 ; 16種類レジスタを表現するため1bit増える • その1bitはREXプレフィックスの中に • VSIB ; AVX2でSIMDレジスタを指定できるようになった XbyakのVSIBエンコーディングバグ SDM2.3.12 Vector SIB(VSIB) Memory Addressing 40 / 45
  • 41. • VSIBやREXでは足りない • ?mm16~?mm31までのレジスタはどうやって指定? • EVEX.vvvvビット • addpd(zmm31, zmm30, zmm20);などはちゃんと実装していた • SDM2.6.1 Instruction Format and EVEX • VSIBのときEVEX.V'をoffにするのだった • 単なる見落とだがVSIB Memory Addressingのところの記述は 変わってないし……(言い訳) AVX-512でレジスタは32個 EVEXV’ High-16 NDS/VIDX register specifier P[19] Combine with EVEX.vvvv or when VSIB present. 41 / 45
  • 42. • cpuid(eax=0x0b, ecx=0/1)でSMTやCOREの数を取得 • cpuid(eax=0x4, ecx=cache_level)で各階層の情報を取得 • VMのせいか時々おかしい値に キャッシュサイズの取得はややこしい 42 / 45
  • 43. • Intel Software Development Emulator • https://software.intel.com/en-us/articles/intel-software- development-emulator • 当たり前だが一番信頼できる • xed -mpx -64 -ir <rawobj>でdisassemblerとして利用可能 • vgatherdps(zmm0|k1, ptr[rax + zmm2]); の出力は正しくdisasできるのに vgatherdps(zmm0|k1, ptr[rax + zmm0]); の出力はエラー • 両方とも同じエンコードパスを通るのに何故? Intel SDEの仕様に悩む ERROR: GATHER_REGS Could not decode at offset: 0x0 PC: 0x0: [62F27D49920400] 43 / 45
  • 44. • gather命令はindex == destinationのとき使えない • Note that: If any pair of the index, mask, or destination registers are the same, this instruction results a UD fault. • decode errではなく実行時エラーならよかったんだが エンコードは正しいがUDのため弾かれた 44 / 45
  • 45. • 自分で小さいVM作ってみると面白いかも • 狙ったコードを生成できると嬉しい • 命令数が多い(マニュアルが分厚い)と大変だ まとめ 45 / 45