Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
JIT のコードを読んでみた
内山 雄司 (@y__uti)
2016-12-11 第7回闇PHP勉強会
自己紹介
内山 雄司 (@y__uti)
◦ http://y-uti.hatenablog.jp/ (phpusers-ja)
仕事
◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています
興味
◦ プログラミング言語処理系
◦ ...
JIT for PHP project
2016-12-11 第七回 闇PHP勉強会 3
http://news.php.net/php.internals/95531
(第 107 回 PHP 勉強会での発表資料より)
ベンチマーク結果
2016-12-11 第七回 闇PHP勉強会 4
0.00
0.05
0.10
0.15
0.20
0.25
0.30
0.35
0.40
実行時間(秒)
5.6.27 7....
JIT による速度向上
2016-12-11 第七回 闇PHP勉強会 5
http://news.php.net/php.internals/96613
開発者 (Dmitry Stogov 氏) による 2016-10-26 の投稿
◦ be...
本日の発表内容
どのように実装されているかソースコードを追ってみました
1. プログラム実行時に JIT コンパイルを行う仕掛け
◦ OPcache を拡張して opcode handler を差し替え
◦ 関数の実行ごとに JIT 起動条件を...
JIT コンパイルの仕掛け
2016-12-11 第七回 闇PHP勉強会 7
PHP の処理の流れ
以下のコマンドを実行すると何が起きるのか
2016-12-11 第七回 闇PHP勉強会 8
$ php foo.php
処理の流れ
1. sapi/cli/php_cli.c の main 関数から処理が始まり
2. Ze...
zend_execute_scripts 関数
in Zend/zend.c
PHP 処理系によるプログラム実行の「かなめ」
◦ ファイルをバイトコード命令列にコンパイルする
2016-12-11 第七回 闇PHP勉強会 9
op_array ...
op_array 構造体
in Zend/zend_compile.h
バイトコード命令列 + 各種情報
◦ 関数ごとに一つ
◦ トップレベルに書かれたコード用に一つ
2016-12-11 第七回 闇PHP勉強会 10
struct _zend...
execute_ex 関数
in Zend/zend_vm_execute.h
各バイトコード命令 (zend_op 構造体) の handler を実行
2016-12-11 第七回 闇PHP勉強会 11
...
while (1) {
.....
JIT コンパイル付きの実行
op_array (関数) ごとに以下の処理を行う
2016-12-11 第七回 闇PHP勉強会 12
コンパイルする?
元のコードを実行
コード生成
生成された
コードを実行
開始
終了
No Yes
JIT コンパイルの準備
(通常の) コンパイル時に handler を書き換える
2016-12-11 第七回 闇PHP勉強会 13
コンパイルする?
元のコードを実行
コード生成
生成された
コードを実行
開始
終了
No Yes
hand...
OPcache の処理
起動時に zend_compile_file 関数を置き換える
in ext/opcache/ZendAccelerator.c
2016-12-11 第七回 闇PHP勉強会 14
static int accel_st...
persistent_compile_file 関数
in ext/opcache/ZendAccelerator.c
2016-12-11 第七回 闇PHP勉強会 15
/* zend_compile() replacement */
zen...
JIT までの道のり
in ext/opcache/ZendAccelerator.c
2016-12-11 第七回 闇PHP勉強会 16
static zend_persistent_script *cache_script_in_share...
JIT コンパイルのトリガー
in ext/opcache/jit/zend_jit.h
いくつかの選択肢が提供されている
◦ ZEND_JIT_ON_SCRIPT_LOAD スクリプトのロード時
◦ ZEND_JIT_ON_FIRST_EXE...
ZEND_JIT_ON_FIRST_EXEC
in ext/opcache/jit/zend_jit.c
2016-12-11 第七回 闇PHP勉強会 18
opline->handler = (const void*)zend_runtime...
ZEND_JIT_ON_PROF_REQUEST
in ext/opcache/jit/zend_jit.c
2016-12-11 第七回 闇PHP勉強会 19
opline->handler = zend_jit_profile_helper...
ZEND_JIT_ON_PROF_REQUEST
in ext/opcache/jit/zend_jit.c
OPcache が deactivate されるときに JIT コンパイルを行う
2016-12-11 第七回 闇PHP勉強会 20
...
ZEND_JIT_ON_HOT_COUNTERS
in ext/opcache/jit/zend_jit.c
2016-12-11 第七回 闇PHP勉強会 21
return zend_jit_setup_hot_counters(op_arr...
ZEND_JIT_ON_HOT_COUNTERS
in ext/opcache/jit/zend_jit_vm_helpers.c
実行のたびに hot counter を減じて 0 以下になったらコンパイル
2016-12-11 第七回 闇P...
ZEND_JIT_ON_SCRIPT_LOAD
in ext/opcache/jit/zend_jit.c
2016-12-11 第七回 闇PHP勉強会 23
return zend_real_jit_func(op_array, script...
ZEND_JIT_ON_DOC_COMMENT
in ext/opcache/jit/zend_jit.c
2016-12-11 第七回 闇PHP勉強会 24
if (zend_needs_manual_jit(op_array)) {
ret...
ここまでのまとめ
JIT のソースコードを追ってみました
1. プログラム実行時に JIT コンパイルを行う仕掛け
◦ OPcache を拡張して opcode handler を差し替え
◦ 関数の実行ごとに JIT 起動条件を確認する
20...
JIT コンパイルの処理内容
2016-12-11 第七回 闇PHP勉強会 26
zend_real_jit_func
in ext/opcache/jit/zend_jit.c
2016-12-11 第七回 闇PHP勉強会 27
static int zend_real_jit_func(
zend_op_array *o...
コード生成の概要 [1/2]
in ext/opcache/jit/zend_jit.c
JIT コンパイルの本体
2016-12-11 第七回 闇PHP勉強会 28
static int zend_jit(
zend_op_array *op...
コード生成の実装
DynASM のフォーマットで記述
in ext/opcache/jit/zend_jit_x86.dasc
2016-12-11 第七回 闇PHP勉強会 29
static int zend_jit_inc_dec(
das...
DynASM
is a Dynamic Assembler for code generation engines.
2016-12-11 第七回 闇PHP勉強会 30
https://luajit.org/dynasm.html
生成されるコード
DynASM のフォーマットでの記述から
in ext/opcache/jit/zend_jit_x86.dasc
2016-12-11 第七回 闇PHP勉強会 31
if (opline->opcode == ZEND_PR...
大雑把な理解
アセンブリのテンプレートエンジン
2016-12-11 第七回 闇PHP勉強会 32
| inc aword [FP + opline->op1.var]
dasm_put(Dst, 749, opline->op1.var);
...
コード生成の概要 [2/2]
in ext/opcache/jit/zend_jit.c
JIT コンパイルの本体
2016-12-11 第七回 闇PHP勉強会 33
... // すべての opcode を処理した後
handler = da...
JIT コンパイルの例
2016-12-11 第七回 闇PHP勉強会 34
サンプルプログラム
0 から 9 までの和を計算して表示する
2016-12-11 第七回 闇PHP勉強会 35
<?php
function f($a)
{
$sum = 0;
for ($i = 0; $i < $a; ++$i) {
$s...
JIT の動作の確認
JIT コンパイルの結果を表示する
2016-12-11 第七回 闇PHP勉強会 36
$ ./sapi/cli/php
-d zend_extension=$(pwd)/modules/opcache.so
-d opc...
CFG 構築結果の表示
ZEND_JIT_DEBUG_SSA (= 2) を立てるとフロー解析結果を表示
2016-12-11 第七回 闇PHP勉強会 37
f: ; (lines=9, args=1, vars=3, tmps=1, ssa_...
関数 f の CFG
2016-12-11 第七回 闇PHP勉強会 38
BB0: CV0($a) = RECV 1 // $a = 第 1 引数
BB1: CV1($sum) = QM_ASSIGN int(0) // $sum = 0
CV...
生成コードの確認
ZEND_JIT_DEBUG_ASM (= 1) を立てると生成されるコードを表示
2016-12-11 第七回 闇PHP勉強会 39
JIT$f: ; (/home/y-uti/php-jit-bench/sample-co...
ZEND_JIT_LEVEL_MINIMAL
"Subroutine threading"
2016-12-11 第七回 闇PHP勉強会 40
...
.L1:
call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HAND...
ZEND_JIT_LEVEL_INLINE
"Selective inline threading"
2016-12-11 第七回 闇PHP勉強会 41
...
.L1:
mov $0x0, 0x60(%r14) // val($sum) = ...
ZEND_JIT_LEVEL_OPT_FUNC
"Optimized JIT based on Type-Inference"
2016-12-11 第七回 闇PHP勉強会 42
...
.L2:
cmp $0x4, 0x68(%r14) //...
より積極的な最適化レベル
ZEND_JIT_LEVEL_OPT_FUNCS
◦ "Optimized JIT based on Type-Inference and call-tree"
ZEND_JIT_LEVEL_OPT_SCRIPT
◦ ...
まとめ
JIT のソースコードを追ってみました
1. プログラム実行時に JIT コンパイルを行う仕掛け
◦ OPcache を拡張して opcode handler を差し替え
◦ 関数の実行ごとに JIT 起動条件を確認する
2. JIT ...
補足
このスライドは下記のコードに基づいて作成しました
◦ Repository: https://github.com/zendtech/php-src.git
◦ Branch: jit-dynasm
◦ Commit: 39a5bd9
2...
JIT コンパイルの例 (追加)
2016-12-11 第七回 闇PHP勉強会 46
サンプルプログラム
以下の PHP プログラムを考える
2016-12-11 第七回 闇PHP勉強会 47
<?php
function f()
{
$a = 1;
++$a;
++$a;
++$a;
return $a;
}
echo f()...
前提として
最適化コンパイラ (たとえば gcc) なら直接 4 を返せる
2016-12-11 第七回 闇PHP勉強会 48
// sample2.c
int f()
{
int a = 1;
++a;
++a;
++a;
return a;...
ZEND_JIT_LEVEL_INLINE
「データフロー解析を行わない」とは?
2016-12-11 第七回 闇PHP勉強会 49
BB0: start exit lines=[0-4]
; level=0
CV0($a) = QM_ASSI...
ZEND_JIT_LEVEL_INLINE
以下のコードが生成される
2016-12-11 第七回 闇PHP勉強会 50
JIT$f: ; (/home/y-uti/php-jit-bench/sample-code/sample2.php)
...
ZEND_JIT_LEVEL_OPT_FUNC
データフロー解析を行う
2016-12-11 第七回 闇PHP勉強会 51
BB0: start exit lines=[0-4]
; level=0
#1.CV0($a) [long] RANG...
ZEND_JIT_LEVEL_OPT_FUNC
以下のコードが生成される
2016-12-11 第七回 闇PHP勉強会 52
JIT$f: ; (/home/y-uti/php-jit-bench/sample-code/sample2.php...
Upcoming SlideShare
Loading in …5
×

JIT のコードを読んでみた

9,329 views

Published on

第 7 回 闇 PHP 勉強会での発表資料です。PHP 8 への搭載を目指して開発が進められている JIT のソースコードを読んでみて、どのような実装になっているかを紹介したものです。

Published in: Technology

JIT のコードを読んでみた

  1. 1. JIT のコードを読んでみた 内山 雄司 (@y__uti) 2016-12-11 第7回闇PHP勉強会
  2. 2. 自己紹介 内山 雄司 (@y__uti) ◦ http://y-uti.hatenablog.jp/ (phpusers-ja) 仕事 ◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています 興味 ◦ プログラミング言語処理系 ◦ 機械学習 2016-12-11 第七回 闇PHP勉強会 2
  3. 3. JIT for PHP project 2016-12-11 第七回 闇PHP勉強会 3 http://news.php.net/php.internals/95531
  4. 4. (第 107 回 PHP 勉強会での発表資料より) ベンチマーク結果 2016-12-11 第七回 闇PHP勉強会 4 0.00 0.05 0.10 0.15 0.20 0.25 0.30 0.35 0.40 実行時間(秒) 5.6.27 7.0.12 7.1.0RC4 JIT (0edf1e9) Intel Core i5-3337U 1.80GHz 2GB Memory CentOS 7 (VM on Windows7) 各 10 回の実行の平均
  5. 5. JIT による速度向上 2016-12-11 第七回 闇PHP勉強会 5 http://news.php.net/php.internals/96613 開発者 (Dmitry Stogov 氏) による 2016-10-26 の投稿 ◦ bench.php では 3 倍の速度向上 ◦ "real-life apps" では大きな差はない
  6. 6. 本日の発表内容 どのように実装されているかソースコードを追ってみました 1. プログラム実行時に JIT コンパイルを行う仕掛け ◦ OPcache を拡張して opcode handler を差し替え ◦ 関数の実行ごとに JIT 起動条件を確認する 2. JIT コンパイルの処理内容 ◦ CFG, SSA, call-graph 等の情報に基づき DynASM を利用してコード生成 ◦ 生成されたコードを実行するように opcode handler を再度差し替え この一枚で理解できてしまったガチ勢はマサカリの準備を! 2016-12-11 第七回 闇PHP勉強会 6
  7. 7. JIT コンパイルの仕掛け 2016-12-11 第七回 闇PHP勉強会 7
  8. 8. PHP の処理の流れ 以下のコマンドを実行すると何が起きるのか 2016-12-11 第七回 闇PHP勉強会 8 $ php foo.php 処理の流れ 1. sapi/cli/php_cli.c の main 関数から処理が始まり 2. Zend/zend.c の zend_execute_scripts 関数が呼ばれ 3. zend_compile_file 関数でコンパイルして 4. zend_execute 関数で実行する
  9. 9. zend_execute_scripts 関数 in Zend/zend.c PHP 処理系によるプログラム実行の「かなめ」 ◦ ファイルをバイトコード命令列にコンパイルする 2016-12-11 第七回 闇PHP勉強会 9 op_array = zend_compile_file(file_handle, type); zend_execute(op_array, retval); ◦ バイトコード命令列を実行する
  10. 10. op_array 構造体 in Zend/zend_compile.h バイトコード命令列 + 各種情報 ◦ 関数ごとに一つ ◦ トップレベルに書かれたコード用に一つ 2016-12-11 第七回 闇PHP勉強会 10 struct _zend_op { const void *handler; znode_op op1; znode_op op2; znode_op result; uint32_t extended_value; uint32_t lineno; zend_uchar opcode; zend_uchar op1_type; zend_uchar op2_type; zend_uchar result_type; }; struct _zend_op_array { zend_uchar type; zend_uchar arg_flags[3]; ... uint32_t last; zend_op *opcodes; ... }; 一対多 last が命令数を表す
  11. 11. execute_ex 関数 in Zend/zend_vm_execute.h 各バイトコード命令 (zend_op 構造体) の handler を実行 2016-12-11 第七回 闇PHP勉強会 11 ... while (1) { ... if (UNEXPECTED( (ret = ((opcode_handler_t)OPLINE->handler)()) != 0)) { ... return; } ... } ... 読みやすさのため、一部のマクロを展開して掲載しています
  12. 12. JIT コンパイル付きの実行 op_array (関数) ごとに以下の処理を行う 2016-12-11 第七回 闇PHP勉強会 12 コンパイルする? 元のコードを実行 コード生成 生成された コードを実行 開始 終了 No Yes
  13. 13. JIT コンパイルの準備 (通常の) コンパイル時に handler を書き換える 2016-12-11 第七回 闇PHP勉強会 13 コンパイルする? 元のコードを実行 コード生成 生成された コードを実行 開始 終了 No Yes handler handler
  14. 14. OPcache の処理 起動時に zend_compile_file 関数を置き換える in ext/opcache/ZendAccelerator.c 2016-12-11 第七回 闇PHP勉強会 14 static int accel_startup(zend_extension *extension) { ... /* Override compiler */ accelerator_orig_compile_file = zend_compile_file; zend_compile_file = persistent_compile_file; ... } persistent_compile_file 関数がやること ◦ コンパイルしたデータ構造をキャッシュ (本来の仕事) ◦ OPcache 独自の最適化 (おまけ?) ◦ JIT コンパイルのための handler 書き換え (JIT を有効にしたときのみ)[New!]
  15. 15. persistent_compile_file 関数 in ext/opcache/ZendAccelerator.c 2016-12-11 第七回 闇PHP勉強会 15 /* zend_compile() replacement */ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) { ... // キャッシュ済みでなければ if (!persistent_script) { // コンパイルして persistent_script = opcache_compile_file(...); if (persistent_script) { // キャッシュする persistent_script = cache_script_in_shared_memory(...); } ...
  16. 16. JIT までの道のり in ext/opcache/ZendAccelerator.c 2016-12-11 第七回 闇PHP勉強会 16 static zend_persistent_script *cache_script_in_shared_memory( ... ) in ext/opcache/zend_persist.c zend_persistent_script *zend_accel_script_persist( ... ) static void zend_persist_op_array_ex( ... ) in ext/opcache/zend_persist.c in ext/opcache/jit/zend_jit.c ZEND_API int zend_jit_op_array( zend_op_array *op_array, zend_script *script) ◦ この関数で handler を差し替えている
  17. 17. JIT コンパイルのトリガー in ext/opcache/jit/zend_jit.h いくつかの選択肢が提供されている ◦ ZEND_JIT_ON_SCRIPT_LOAD スクリプトのロード時 ◦ ZEND_JIT_ON_FIRST_EXEC 最初の実行 ◦ ZEND_JIT_ON_PROF_REQUEST 実行頻度 (割合) の高い関数 ◦ ZEND_JIT_ON_HOT_COUNTERS 実行回数が閾値を超えたとき ◦ ZEND_JIT_ON_DOC_COMMENT DocComment の指定に従う 2016-12-11 第七回 闇PHP勉強会 17
  18. 18. ZEND_JIT_ON_FIRST_EXEC in ext/opcache/jit/zend_jit.c 2016-12-11 第七回 闇PHP勉強会 18 opline->handler = (const void*)zend_runtime_jit; zend_runtime_jit 関数は無条件に JIT コンパイルを開始 in ext/opcache/jit/zend_jit.c static void ZEND_FASTCALL zend_runtime_jit(void) { ... opline->handler = ZEND_FUNC_INFO(op_array); // 本来の handler を復元 ... /* perform real JIT for this function */ zend_real_jit_func(op_array, NULL, NULL); // JIT コンパイルを実行 ... }
  19. 19. ZEND_JIT_ON_PROF_REQUEST in ext/opcache/jit/zend_jit.c 2016-12-11 第七回 闇PHP勉強会 19 opline->handler = zend_jit_profile_helper; zend_jit_profile_helper は op_array ごとの実行回数を数える in ext/opcache/jit/zend_jit_vm_helpers.c void ZEND_FASTCALL zend_jit_profile_helper(void) { zend_op_array *op_array = (zend_op_array*)EX(func); const void *handler = (const void*)ZEND_FUNC_INFO(op_array); // 本来の handler を取得 ++(ZEND_COUNTER_INFO(op_array)); // この op_array の実行回数 ++zend_jit_profile_counter; // 全 op_array の実行回数の総和 return ((zend_vm_opcode_handler_t)handler)(); // 本来の処理を実行 }
  20. 20. ZEND_JIT_ON_PROF_REQUEST in ext/opcache/jit/zend_jit.c OPcache が deactivate されるときに JIT コンパイルを行う 2016-12-11 第七回 闇PHP勉強会 20 void zend_jit_check_funcs(HashTable *function_table, zend_bool is_method) { ... zend_ulong counter = (zend_ulong)ZEND_COUNTER_INFO(op_array); if (((double)counter / (double)zend_jit_profile_counter) > ZEND_JIT_PROF_THRESHOLD) { zend_real_jit_func(op_array, NULL, NULL); } ... } ◦ 実行回数の比率が閾値を超えた op_array を JIT の対象とする ◦ 最初のリクエストの実行で判断
  21. 21. ZEND_JIT_ON_HOT_COUNTERS in ext/opcache/jit/zend_jit.c 2016-12-11 第七回 闇PHP勉強会 21 return zend_jit_setup_hot_counters(op_array); 基本ブロック単位で実行回数を計測する in ext/opcache/jit/zend_jit.c static int zend_jit_setup_hot_counters(zend_op_array *op_array) { ... // Control Flow Graph を構築 opline->handler = (const void*)zend_jit_func_counter_helper; for (i = 0; i < cfg.blocks_count; i++) { ... op_array->opcodes[cfg.blocks[i].start].handler = (const void*)zend_jit_loop_counter_helper; } }
  22. 22. ZEND_JIT_ON_HOT_COUNTERS in ext/opcache/jit/zend_jit_vm_helpers.c 実行のたびに hot counter を減じて 0 以下になったらコンパイル 2016-12-11 第七回 闇PHP勉強会 22 void ZEND_FASTCALL zend_jit_func_counter_helper(void) { ... zend_jit_hot_counters[n] -= ZEND_JIT_HOT_FUNC_COST; if (UNEXPECTED(zend_jit_hot_counters[n] <= 0)) { zend_jit_hot_counters[n] = ZEND_JIT_HOT_COUNTER_INIT; zend_jit_hot_func(execute_data, opline); // handler を復元して JIT 実行 } else { zend_vm_opcode_handler_t *handlers = (zend_vm_opcode_handler_t*)ZEND_FUNC_INFO(&EX(func)->op_array); handlers[opline - EX(func)->op_array.opcodes](); } } ◦ zend_jit_loop_counter_helper も同様の実装
  23. 23. ZEND_JIT_ON_SCRIPT_LOAD in ext/opcache/jit/zend_jit.c 2016-12-11 第七回 闇PHP勉強会 23 return zend_real_jit_func(op_array, script, NULL); OPcache が op_array を処理するタイミングでコンパイルする その意味で、これは "Just In Time" ではない
  24. 24. ZEND_JIT_ON_DOC_COMMENT in ext/opcache/jit/zend_jit.c 2016-12-11 第七回 闇PHP勉強会 24 if (zend_needs_manual_jit(op_array)) { return zend_real_jit_func(op_array, script, NULL); } else { return SUCCESS; } OPcache が op_array を処理するタイミングでコンパイルする DocComment に @jit を指定した関数のみ対象 これも "Just In Time" ではない
  25. 25. ここまでのまとめ JIT のソースコードを追ってみました 1. プログラム実行時に JIT コンパイルを行う仕掛け ◦ OPcache を拡張して opcode handler を差し替え ◦ 関数の実行ごとに JIT 起動条件を確認する 2016-12-11 第七回 闇PHP勉強会 25
  26. 26. JIT コンパイルの処理内容 2016-12-11 第七回 闇PHP勉強会 26
  27. 27. zend_real_jit_func in ext/opcache/jit/zend_jit.c 2016-12-11 第七回 闇PHP勉強会 27 static int zend_real_jit_func( zend_op_array *op_array, zend_script *script, const zend_op *rt_opline) { ... JIT コンパイルのための解析 (OPcache の実装を利用) ◦ 制御フローグラフの構築 ◦ データフロー解析 (ZEND_JIT_LEVEL_OPT_FUNC 以上) ◦ コールグラフの構築 (ZEND_JIT_LEVEL_OPT_FUNCS 以上) コード生成 ◦ opcode ごとに機械語を生成 ◦ コード生成の仕組みには DynASM を利用
  28. 28. コード生成の概要 [1/2] in ext/opcache/jit/zend_jit.c JIT コンパイルの本体 2016-12-11 第七回 闇PHP勉強会 28 static int zend_jit( zend_op_array *op_array, zend_ssa *ssa, const zend_op *rt_opline) { ... // op_array に含まれる各 opcode をコンパイルする switch (opline->opcode) { ... case ZEND_PRE_INC: case ZEND_PRE_DEC: case ZEND_POST_INC: case ZEND_POST_DEC: if (!zend_jit_inc_dec(&dasm_state, opline, op_array, ssa)) { goto jit_failure; } goto done; }
  29. 29. コード生成の実装 DynASM のフォーマットで記述 in ext/opcache/jit/zend_jit_x86.dasc 2016-12-11 第七回 闇PHP勉強会 29 static int zend_jit_inc_dec( dasm_State **Dst, const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa) { ... || if ((opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) && || opline->result_type != IS_UNUSED) { | ZVAL_COPY_VALUE FP + opline->result.var, FP + opline->op1.var, MAY_BE_LONG, r0, eax, r1 || } if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) { | inc aword [FP + opline->op1.var] } else { | dec aword [FP + opline->op1.var] } ... ◦ dynasm.lua によって zend_jit_x86.c に変換される
  30. 30. DynASM is a Dynamic Assembler for code generation engines. 2016-12-11 第七回 闇PHP勉強会 30 https://luajit.org/dynasm.html
  31. 31. 生成されるコード DynASM のフォーマットでの記述から in ext/opcache/jit/zend_jit_x86.dasc 2016-12-11 第七回 闇PHP勉強会 31 if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) { | inc aword [FP + opline->op1.var] } else { | dec aword [FP + opline->op1.var] } if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) { //| inc aword [FP + opline->op1.var] dasm_put(Dst, 749, opline->op1.var); } else { //| dec aword [FP + opline->op1.var] dasm_put(Dst, 755, opline->op1.var); } 以下のような C のコードが生成される in ext/opcache/jit/zend_jit_x86.c
  32. 32. 大雑把な理解 アセンブリのテンプレートエンジン 2016-12-11 第七回 闇PHP勉強会 32 | inc aword [FP + opline->op1.var] dasm_put(Dst, 749, opline->op1.var); static const unsigned char dasm_actions[5499] = { 248,10,198,4,37,237,0,128,60,37,237,0,15,132, ... "inc aword [FP + ]" ... } zend_jit_x86.dasc zend_jit_x86.c + テンプレートの定義 テンプレートの適用 dynasm.lua
  33. 33. コード生成の概要 [2/2] in ext/opcache/jit/zend_jit.c JIT コンパイルの本体 2016-12-11 第七回 闇PHP勉強会 33 ... // すべての opcode を処理した後 handler = dasm_link_and_encode( &dasm_state, op_array, &ssa->cfg, rt_opline, NULL); } ◦ 以後は生成された機械語をハンドラとして実行する
  34. 34. JIT コンパイルの例 2016-12-11 第七回 闇PHP勉強会 34
  35. 35. サンプルプログラム 0 から 9 までの和を計算して表示する 2016-12-11 第七回 闇PHP勉強会 35 <?php function f($a) { $sum = 0; for ($i = 0; $i < $a; ++$i) { $sum += $i; } return $sum; } $a = 10; $ans = f($a); echo $ans;
  36. 36. JIT の動作の確認 JIT コンパイルの結果を表示する 2016-12-11 第七回 闇PHP勉強会 36 $ ./sapi/cli/php -d zend_extension=$(pwd)/modules/opcache.so -d opcache.enable_cli=1 -d opcache.jit=15 -d opcache.jit_buffer_size=32M -d opcache.jit_debug=3 sample1.php 2>&1 ◦ opcache.jit ◦ トリガー (10 の位) とレベル (1 の位) をまとめて指定 ◦ opcache.jit_debug ◦ デバッグ情報の表示内容を指定
  37. 37. CFG 構築結果の表示 ZEND_JIT_DEBUG_SSA (= 2) を立てるとフロー解析結果を表示 2016-12-11 第七回 闇PHP勉強会 37 f: ; (lines=9, args=1, vars=3, tmps=1, ssa_vars=0) ; (JIT) ; /home/y-uti/php-jit-bench/sample-code/sample1.php:3-10 BB0: start lines=[0-0] ; to=(BB1) ; level=0 ; children=(BB1) CV0($a) = RECV 1 // 仮引数 $a に 1 番目の実引数を受け取る BB1: follow entry lines=[1-3] ; from=(BB0) ; to=(BB3) ; idom=BB0 ; level=1 ; children=(BB3) CV1($sum) = QM_ASSIGN int(0) // $sum = 0 CV2($i) = QM_ASSIGN int(0) // $i = 0 JMP BB3 // 基本ブロック BB3 にジャンプ BB2: target lines=[4-5] ; from=(BB3) ...
  38. 38. 関数 f の CFG 2016-12-11 第七回 闇PHP勉強会 38 BB0: CV0($a) = RECV 1 // $a = 第 1 引数 BB1: CV1($sum) = QM_ASSIGN int(0) // $sum = 0 CV2($i) = QM_ASSIGN int(0) // $i = 0 JMP BB3 // jump to BB3 BB2: CV1($sum) = ADD CV1($sum) CV2($i) // $sum = $sum + $i PRE_INC CV2($i) // ++$i BB3: T3 = IS_SMALLER CV2($i) CV0($a) // $i < $a ? JMPNZ T3 BB2 // if true jump to BB2 BB4: RETURN CV1($sum) // return $sum
  39. 39. 生成コードの確認 ZEND_JIT_DEBUG_ASM (= 1) を立てると生成されるコードを表示 2016-12-11 第七回 闇PHP勉強会 39 JIT$f: ; (/home/y-uti/php-jit-bench/sample-code/sample1.php) sub $0x8, %rsp call ZEND_RECV_SPEC_HANDLER cmp $0x0, EG(exception) jnz JIT$$exception_handler jmp .L1 .ENTRY1: sub $0x8, %rsp .L1: call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLER call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLER jmp .L3 .L2: mov $0x7fcd524d6c10, %r15 call ZEND_ADD_SPEC_CV_CV_HANDLER cmp $0x0, EG(exception) jnz JIT$$exception_handler call ZEND_PRE_INC_LONG_OR_DOUBLE_SPEC_TMPVARCV_RETVAL_UNUSED_HANDLER ...
  40. 40. ZEND_JIT_LEVEL_MINIMAL "Subroutine threading" 2016-12-11 第七回 闇PHP勉強会 40 ... .L1: call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLER call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLER jmp .L3 .L2: mov $0x7fcd524d6c10, %r15 call ZEND_ADD_SPEC_CV_CV_HANDLER cmp $0x0, EG(exception) jnz JIT$$exception_handler call ZEND_PRE_INC_LONG_OR_DOUBLE_SPEC_TMPVARCV_RETVAL_UNUSED_HANDLER cmp $0x0, EG(exception) jnz JIT$$exception_handler .L3: ... ◦ PHP のハンドラ (in Zend/zend_vm_execute.h) 呼び出しを並べる ◦ JMP 系の命令は遷移先を直接指定する形に展開
  41. 41. ZEND_JIT_LEVEL_INLINE "Selective inline threading" 2016-12-11 第七回 闇PHP勉強会 41 ... .L1: mov $0x0, 0x60(%r14) // val($sum) = 0 mov $0x4, 0x68(%r14) // typeinfo($sum) = IS_LONG (= 4) mov $0x0, 0x70(%r14) // val($i) = 0 mov $0x4, 0x78(%r14) // typeinfo($i) = IS_LONG jmp .L3 .L2: mov $0x7fd46c0d6c10, %r15 call ZEND_ADD_SPEC_CV_CV_HANDLER // $sum = $sum + $i (これは展開されない) cmp $0x0, EG(exception) jnz JIT$$exception_handler cmp $0x4, 0x78(%r14) jnz .L6 ... ◦ 命令の処理を展開して高速に実行 ◦ 型推論なし
  42. 42. ZEND_JIT_LEVEL_OPT_FUNC "Optimized JIT based on Type-Inference" 2016-12-11 第七回 闇PHP勉強会 42 ... .L2: cmp $0x4, 0x68(%r14) // $sum の型が IS_LONG か調べる jnz .L10 // IS_LONG でなければ .L10 へ cmp $0x4, 0x78(%r14) // $i の型が IS_LONG か調べる jnz .L8 // IS_LONG でなければ .L8 へ mov 0x60(%r14), %rax // %rax = $sum add 0x70(%r14), %rax // %rax = %rax + $i jo .L9 // オーバーフローしたら .L9 へ mov %rax, 0x60(%r14) // $sum = %rax .L3: cmp $0x4, 0x78(%r14) // $i の型が IS_LONG か調べる jnz .L13 // IS_LONG でなければ .L13 へ inc 0x70(%r14) // ++$i jo .L12 // オーバーフローしたら .L12 へ ... ◦ 型推論 (SSA 形式のデータフロー解析) に基づく最適化
  43. 43. より積極的な最適化レベル ZEND_JIT_LEVEL_OPT_FUNCS ◦ "Optimized JIT based on Type-Inference and call-tree" ZEND_JIT_LEVEL_OPT_SCRIPT ◦ "Optimized JIT based on Type-Inference and inner-procedure analises" (サンプルプログラムでは違いが見えなかったので割愛) 2016-12-11 第七回 闇PHP勉強会 43
  44. 44. まとめ JIT のソースコードを追ってみました 1. プログラム実行時に JIT コンパイルを行う仕掛け ◦ OPcache を拡張して opcode handler を差し替え ◦ 関数の実行ごとに JIT 起動条件を確認する 2. JIT コンパイルの処理内容 ◦ CFG, SSA, call-graph 等の情報に基づき DynASM を利用してコード生成 ◦ 生成されたコードを実行するように opcode handler を再度差し替え 2016-12-11 第七回 闇PHP勉強会 44
  45. 45. 補足 このスライドは下記のコードに基づいて作成しました ◦ Repository: https://github.com/zendtech/php-src.git ◦ Branch: jit-dynasm ◦ Commit: 39a5bd9 2016-12-11 第七回 闇PHP勉強会 45
  46. 46. JIT コンパイルの例 (追加) 2016-12-11 第七回 闇PHP勉強会 46
  47. 47. サンプルプログラム 以下の PHP プログラムを考える 2016-12-11 第七回 闇PHP勉強会 47 <?php function f() { $a = 1; ++$a; ++$a; ++$a; return $a; } echo f(); ◦ JIT コンパイラはどのようなコードを生成するか
  48. 48. 前提として 最適化コンパイラ (たとえば gcc) なら直接 4 を返せる 2016-12-11 第七回 闇PHP勉強会 48 // sample2.c int f() { int a = 1; ++a; ++a; ++a; return a; } $ gcc -O -S sample2.c $ cat sample2.s .file "sample2.c" .text .globl f .type f, @function f: .LFB0: .cfi_startproc movl $4, %eax ret .cfi_endproc .LFE0: .size f, .-f .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-4)" .section .note.GNU-stack,"",@progbits
  49. 49. ZEND_JIT_LEVEL_INLINE 「データフロー解析を行わない」とは? 2016-12-11 第七回 闇PHP勉強会 49 BB0: start exit lines=[0-4] ; level=0 CV0($a) = QM_ASSIGN int(1) PRE_INC CV0($a) PRE_INC CV0($a) PRE_INC CV0($a) RETURN CV0($a) ◦ 最初の $a = 1 は右辺が定数なので整数だと分かる ◦ 後続の ++$a で $a が整数 (IS_LONG) だということは分からない
  50. 50. ZEND_JIT_LEVEL_INLINE 以下のコードが生成される 2016-12-11 第七回 闇PHP勉強会 50 JIT$f: ; (/home/y-uti/php-jit-bench/sample-code/sample2.php) sub $0x8, %rsp mov $0x1, 0x50(%r14) mov $0x4, 0x58(%r14) cmp $0x4, 0x58(%r14) jnz .L5 inc 0x50(%r14) jo .L4 .L1: cmp $0x4, 0x58(%r14) jnz .L11 inc 0x50(%r14) jo .L10 .L2: cmp $0x4, 0x58(%r14) jnz .L17 inc 0x50(%r14) jo .L16 ...
  51. 51. ZEND_JIT_LEVEL_OPT_FUNC データフロー解析を行う 2016-12-11 第七回 闇PHP勉強会 51 BB0: start exit lines=[0-4] ; level=0 #1.CV0($a) [long] RANGE[1..1] = QM_ASSIGN int(1) PRE_INC #1.CV0($a) [long] RANGE[1..1] -> #2.CV0($a) [long] RANGE[2..2] PRE_INC #2.CV0($a) [long] RANGE[2..2] -> #3.CV0($a) [long] RANGE[3..3] PRE_INC #3.CV0($a) [long] RANGE[3..3] -> #4.CV0($a) [long] RANGE[4..4] RETURN #4.CV0($a) [long] RANGE[4..4] ◦ 各命令で $a が取り得る型と値が解析されている
  52. 52. ZEND_JIT_LEVEL_OPT_FUNC 以下のコードが生成される 2016-12-11 第七回 闇PHP勉強会 52 JIT$f: ; (/home/y-uti/php-jit-bench/sample-code/sample2.php) sub $0x8, %rsp mov $0x1, 0x50(%r14) mov $0x4, 0x58(%r14) inc 0x50(%r14) inc 0x50(%r14) inc 0x50(%r14) mov 0x10(%r14), %rcx test %rcx, %rcx jz .L1 mov 0x50(%r14), %rdx mov %rdx, (%rcx) mov $0x4, 0x8(%rcx) .L1: ... ◦ 整数型でありオーバーフローもしないことが分かっている ◦ しかし、あくまでも「バイトコード命令ごとに」コード生成 ◦ gcc のように 4 を返すような最適化はしない

×