JIT のコードを読んでみた
内山 雄司 (@y__uti)
2016-12-11 第7回闇PHP勉強会
自己紹介
内山 雄司 (@y__uti)
◦ http://y-uti.hatenablog.jp/ (phpusers-ja)
仕事
◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています
興味
◦ プログラミング言語処理系
◦ 機械学習
2016-12-11 第七回 闇PHP勉強会 2
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.0.12 7.1.0RC4 JIT (0edf1e9)
Intel Core i5-3337U 1.80GHz
2GB Memory
CentOS 7 (VM on Windows7)
各 10 回の実行の平均
JIT による速度向上
2016-12-11 第七回 闇PHP勉強会 5
http://news.php.net/php.internals/96613
開発者 (Dmitry Stogov 氏) による 2016-10-26 の投稿
◦ bench.php では 3 倍の速度向上
◦ "real-life apps" では大きな差はない
本日の発表内容
どのように実装されているかソースコードを追ってみました
1. プログラム実行時に JIT コンパイルを行う仕掛け
◦ OPcache を拡張して opcode handler を差し替え
◦ 関数の実行ごとに JIT 起動条件を確認する
2. JIT コンパイルの処理内容
◦ CFG, SSA, call-graph 等の情報に基づき DynASM を利用してコード生成
◦ 生成されたコードを実行するように opcode handler を再度差し替え
この一枚で理解できてしまったガチ勢はマサカリの準備を!
2016-12-11 第七回 闇PHP勉強会 6
JIT コンパイルの仕掛け
2016-12-11 第七回 闇PHP勉強会 7
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 関数で実行する
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);
◦ バイトコード命令列を実行する
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 が命令数を表す
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;
}
...
}
...
読みやすさのため、一部のマクロを展開して掲載しています
JIT コンパイル付きの実行
op_array (関数) ごとに以下の処理を行う
2016-12-11 第七回 闇PHP勉強会 12
コンパイルする?
元のコードを実行
コード生成
生成された
コードを実行
開始
終了
No Yes
JIT コンパイルの準備
(通常の) コンパイル時に handler を書き換える
2016-12-11 第七回 闇PHP勉強会 13
コンパイルする?
元のコードを実行
コード生成
生成された
コードを実行
開始
終了
No Yes
handler
handler
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!]
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(...);
}
...
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 を差し替えている
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
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 コンパイルを実行
...
}
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)();
// 本来の処理を実行
}
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 の対象とする
◦ 最初のリクエストの実行で判断
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;
}
}
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 も同様の実装
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" ではない
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" ではない
ここまでのまとめ
JIT のソースコードを追ってみました
1. プログラム実行時に JIT コンパイルを行う仕掛け
◦ OPcache を拡張して opcode handler を差し替え
◦ 関数の実行ごとに JIT 起動条件を確認する
2016-12-11 第七回 闇PHP勉強会 25
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 *op_array, zend_script *script, const zend_op *rt_opline)
{
...
JIT コンパイルのための解析 (OPcache の実装を利用)
◦ 制御フローグラフの構築
◦ データフロー解析 (ZEND_JIT_LEVEL_OPT_FUNC 以上)
◦ コールグラフの構築 (ZEND_JIT_LEVEL_OPT_FUNCS 以上)
コード生成
◦ opcode ごとに機械語を生成
◦ コード生成の仕組みには DynASM を利用
コード生成の概要 [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;
}
コード生成の実装
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 に変換される
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_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
大雑把な理解
アセンブリのテンプレートエンジン
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
コード生成の概要 [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);
}
◦ 以後は生成された機械語をハンドラとして実行する
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) {
$sum += $i;
}
return $sum;
}
$a = 10;
$ans = f($a);
echo $ans;
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
◦ デバッグ情報の表示内容を指定
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)
...
関数 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
生成コードの確認
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
...
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 系の命令は遷移先を直接指定する形に展開
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
...
◦ 命令の処理を展開して高速に実行
◦ 型推論なし
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 形式のデータフロー解析) に基づく最適化
より積極的な最適化レベル
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
まとめ
JIT のソースコードを追ってみました
1. プログラム実行時に JIT コンパイルを行う仕掛け
◦ OPcache を拡張して opcode handler を差し替え
◦ 関数の実行ごとに JIT 起動条件を確認する
2. JIT コンパイルの処理内容
◦ CFG, SSA, call-graph 等の情報に基づき DynASM を利用してコード生成
◦ 生成されたコードを実行するように opcode handler を再度差し替え
2016-12-11 第七回 闇PHP勉強会 44
補足
このスライドは下記のコードに基づいて作成しました
◦ Repository: https://github.com/zendtech/php-src.git
◦ Branch: jit-dynasm
◦ Commit: 39a5bd9
2016-12-11 第七回 闇PHP勉強会 45
JIT コンパイルの例 (追加)
2016-12-11 第七回 闇PHP勉強会 46
サンプルプログラム
以下の PHP プログラムを考える
2016-12-11 第七回 闇PHP勉強会 47
<?php
function f()
{
$a = 1;
++$a;
++$a;
++$a;
return $a;
}
echo f();
◦ JIT コンパイラはどのようなコードを生成するか
前提として
最適化コンパイラ (たとえば 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
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) だということは分からない
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
...
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 が取り得る型と値が解析されている
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 を返すような最適化はしない

JIT のコードを読んでみた

  • 1.
    JIT のコードを読んでみた 内山 雄司(@y__uti) 2016-12-11 第7回闇PHP勉強会
  • 2.
    自己紹介 内山 雄司 (@y__uti) ◦http://y-uti.hatenablog.jp/ (phpusers-ja) 仕事 ◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています 興味 ◦ プログラミング言語処理系 ◦ 機械学習 2016-12-11 第七回 闇PHP勉強会 2
  • 3.
    JIT for PHPproject 2016-12-11 第七回 闇PHP勉強会 3 http://news.php.net/php.internals/95531
  • 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.
    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.
    本日の発表内容 どのように実装されているかソースコードを追ってみました 1. プログラム実行時に JITコンパイルを行う仕掛け ◦ OPcache を拡張して opcode handler を差し替え ◦ 関数の実行ごとに JIT 起動条件を確認する 2. JIT コンパイルの処理内容 ◦ CFG, SSA, call-graph 等の情報に基づき DynASM を利用してコード生成 ◦ 生成されたコードを実行するように opcode handler を再度差し替え この一枚で理解できてしまったガチ勢はマサカリの準備を! 2016-12-11 第七回 闇PHP勉強会 6
  • 7.
  • 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.
    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.
    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.
    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.
    JIT コンパイル付きの実行 op_array (関数)ごとに以下の処理を行う 2016-12-11 第七回 闇PHP勉強会 12 コンパイルする? 元のコードを実行 コード生成 生成された コードを実行 開始 終了 No Yes
  • 13.
    JIT コンパイルの準備 (通常の) コンパイル時にhandler を書き換える 2016-12-11 第七回 闇PHP勉強会 13 コンパイルする? 元のコードを実行 コード生成 生成された コードを実行 開始 終了 No Yes handler handler
  • 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.
    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.
    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.
    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.
    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.
    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.
    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.
    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.
    ZEND_JIT_ON_HOT_COUNTERS in ext/opcache/jit/zend_jit_vm_helpers.c 実行のたびに hotcounter を減じて 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.
    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.
    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.
    ここまでのまとめ JIT のソースコードを追ってみました 1. プログラム実行時にJIT コンパイルを行う仕掛け ◦ OPcache を拡張して opcode handler を差し替え ◦ 関数の実行ごとに JIT 起動条件を確認する 2016-12-11 第七回 闇PHP勉強会 25
  • 26.
  • 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.
    コード生成の概要 [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.
    コード生成の実装 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.
    DynASM is a DynamicAssembler for code generation engines. 2016-12-11 第七回 闇PHP勉強会 30 https://luajit.org/dynasm.html
  • 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.
    大雑把な理解 アセンブリのテンプレートエンジン 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.
    コード生成の概要 [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.
  • 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.
    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.
    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.
    関数 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.
    生成コードの確認 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.
    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.
    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.
    ZEND_JIT_LEVEL_OPT_FUNC "Optimized JIT basedon 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.
    より積極的な最適化レベル ZEND_JIT_LEVEL_OPT_FUNCS ◦ "Optimized JITbased 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.
    まとめ JIT のソースコードを追ってみました 1. プログラム実行時にJIT コンパイルを行う仕掛け ◦ OPcache を拡張して opcode handler を差し替え ◦ 関数の実行ごとに JIT 起動条件を確認する 2. JIT コンパイルの処理内容 ◦ CFG, SSA, call-graph 等の情報に基づき DynASM を利用してコード生成 ◦ 生成されたコードを実行するように opcode handler を再度差し替え 2016-12-11 第七回 闇PHP勉強会 44
  • 45.
  • 46.
  • 47.
    サンプルプログラム 以下の PHP プログラムを考える 2016-12-11第七回 闇PHP勉強会 47 <?php function f() { $a = 1; ++$a; ++$a; ++$a; return $a; } echo f(); ◦ JIT コンパイラはどのようなコードを生成するか
  • 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.
    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.
    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.
    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.
    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 を返すような最適化はしない